Appearance
<div style="display: none;" hidden="true" aria-hidden="true">Are you an LLM? You can read better optimized documentation at /changelog/Changelog.4.104.md for this page in Markdown format</div>
Version 4
version 4.104
[4.104.0] fix(docker): make integration-compose host port mappings configurable via
.env- Every
ports:declaration under.docker/integration/is now parameterized through<SERVICE>_EXPOSE_PORTenv vars with defaults embedded as${VAR:-default}, so two developers running stacks side-by-side can avoid host port collisions without git changes. Container-side ports are unchanged — only the host-facing left side becomes overridable, and defaults match the previous hard-coded values so existing.envfiles keep working. - Covers MariaDB (
MARIADB_EXPOSE_PORT, default 3306), Vitess backend (VITESS_{MYSQL,DEBUG,GRPC}_EXPOSE_PORT), Redis standalone (REDIS_EXPOSE_PORT) and all six cluster nodes (REDIS_NODE_{1..6}_EXPOSE_PORT), nginx dev + prod (NGINX_EXPOSE_PORT), the Vite dev server (VITE_EXPOSE_PORT), phpMyAdmin (PHPMYADMIN_EXPOSE_PORT), and RedisInsight (REDISINSIGHT_EXPOSE_PORT). - Exposing MariaDB's port lets developers connect directly from local tools (TablePlus, DBeaver,
mysqlCLI, etc.) without exec-ing into the container..env.examplegains a new "Host Port Mappings" section documenting all 15 vars.
- Every
[4.104.0] fix(catalog): enforce unique product-category links and close all duplicate-write paths (Advisable-com/ecommercen#279)
- The problem. The
shop_product_category_lpjunction table had no uniqueness constraint on(product_id, category_id), so duplicate rows could accumulate through any of four independent write paths:insert_categories_lp(),update_categories_lp_batch(),changeProductsCategoryBatch(), and the file-import jobAdvInsertProductsFromFile. Several of these paths did not guard against duplicates at all;update_categories_lp_batch()performed a per-pairCOUNTquery to check for existence, which is racy under concurrent writes. - Migration (
20260529120000_dedup_product_category_lp_unique.php). Removes all existing duplicate rows by keeping the lowestidper(product_id, category_id)pair via a self-join DELETE. Then drops the old non-uniqueproduct_categoryKEY and adds aUNIQUE INDEX product_category (product_id, category_id)in its place. The index check is defensive: theDROP INDEXclause is only emitted when the index exists, so the migration runs safely on client schemas that may have diverged.down()is intentionally empty (forward-only) — the old non-unique index cannot be meaningfully restored once duplicates have been removed. Adv_product_category_model::linkProductCategory()(new helper). Central idempotent insert usingINSERT ... ON DUPLICATE KEY UPDATE product_id = product_id. Chosen overINSERT IGNOREbecause it silences only uniqueness collisions — FK violations, truncation errors, and other genuine problems still surface. All internal write paths now route through this helper.insert_categories_lp(). Replaced the bare$this->db->insert()call withlinkProductCategory()in the per-element loop.update_categories_lp_batch(). Removed the per-pair existenceCOUNTquery (now made redundant by the UNIQUE index +linkProductCategory()) and delegates directly toinsert_categories_lp().changeProductsCategoryBatch(). Rewrote the plainUPDATE ... SET category_id = destinationwithUPDATE IGNORE(skips rows that would collide with an existing destination link) followed by a cleanupDELETEfor source rows that could not move. Wrapped in a transaction so the move cannot leave an intermediate state. Added input validation: empty product list, zero/negative source or destination, and source == destination all short-circuit early to prevent phantom(product_id, 0)links.removeAllProductCategoriesAndAssignToOne(). Added the same input validation and wrapped the delete + re-insert sequence in a transaction so products cannot be left with no category.AdvInsertProductsFromFile::insertProductRelations(). Replaced the inline$this->ci->db->insert()with a delegation tolinkProductCategory()via the model — mirrors the centralised pattern and eliminates the last independent write path.FakeDataSeed. Switched the seed's junction-table inserts toINSERT IGNOREvia a prepared statement so overlapping seed mappings do not trip the new UNIQUE index and abort the seed run.
- The problem. The
[4.104.0] fix(checkout): converge modern checkout onto the legacy reserve-at-placement stock model — closes overselling window and fixes PayByBank phantom stock loss (Advisable-com/ecommercen#282)
- The problem. The modern checkout deferred stock reduction to payment confirmation for online payways. This deviated from the legacy anti-overselling model (reserve at placement, restore on cancel) and introduced two defects: (a) an overselling window between placement and gateway confirmation; (b) PayByBank orders (redirect-less but
PENDING) had stock reduced at placement yet never restored on cancel — a permanent phantom stock loss on every cancelled PayByBank order. PlaceOrderService. Stock is now reduced at placement for every payway, dropping theredirect === nullgate that previously skipped online payways.OrderPaidis dispatched at placement only for immediate payways (status === 'PENDING_ACCEPTED'); deferred payways (online gateways + PayByBank) dispatchOrderPaidlater fromconfirmPayment(), so PayByBank no longer fires a premature paid event at placement.PaymentConfirmationService::confirmPayment(). No longer reduces stock — placement now owns the reservation. Mirrors the legacyset_status('PAID', ignoreStock=true)call and prevents double-decrement for placement-reserved orders.PaymentConfirmationService::cancelPayment(). Now restores stock on cancel, mirroring the legacyset_status('CANCELED', '+')path and theAdvCancelIncompleteOrderscron behaviour. The already-cancelled idempotency guard prevents double-restore.- Unchanged.
AdvCancelIncompleteOrderscontinues to restore stock for stuckPENDINGorders regardless of origin (abandonment safety net). Per-product negative-stock allowance remains the operator opt-out. - Tests.
confirmPaymentnow asserts stock is NOT reduced;cancelPaymentasserts stock IS restored; newPlaceOrderServicetest asserts online payways reserve at placement. Checkout suite 353 green; full Unit suite 3 163 green. - Deploy note. This changes when stock is decremented for online payways (now at placement, not at confirmation). Orders placed under the old code but confirmed after deploy will not be double-decremented at confirmation. No end-to-end integration test covers the full placement→confirm→cancel stock flow yet (unit-mock level only).
- The problem. The modern checkout deferred stock reduction to payment confirmation for online payways. This deviated from the legacy anti-overselling model (reserve at placement, restore on cancel) and introduced two defects: (a) an overselling window between placement and gateway confirmation; (b) PayByBank orders (redirect-less but
[4.104.0] fix(webhooks): unmatched-order webhook responses changed from HTTP 200 to 404 so payment providers retry on race conditions (Advisable-com/ecommercen#33)
- The problem. Three REST webhook handlers —
vivawallet(),paypal(), andstripe()— returned HTTP 200{"received":true,"matched":false}when the inbound payload could not be matched to ashop_orderrow. Payment providers treat 200 as delivery success and stop retrying. A legitimate webhook that arrives before the order row commits (a narrow but real race) was therefore silently discarded: the order stayed unconfirmed and no payment-confirmation email was ever sent. - Piraeus and PayByBank already honoured 404 on unmatched orders (provider retries). This change aligns the remaining three handlers with that contract.
vivawallet(): after both theshop_orderandgift_card_orderslookups miss, the handler now returns 404 instead of 200.paypal(): when no internal order can be resolved from the payload, the handler returns 404 instead of 200.stripe(): now gates on theconfirmPayment()/cancelPayment()return value. Acheckout.session.completedorcheckout.session.expiredevent returns 404 when the order is unresolved (client_reference_id <= 0) or when the confirm/cancel call returns false. This is the most important case — Stripe has no legacy fallback webhook path, so a dropped confirmation on the REST side would never recover. Unknown event types still return 200 (they are not unmatched-order situations).- Tests. Updated unmatched-order assertions in
WebhookVivaWalletTest,WebhookPayPalTest, andWebhookRepositoryLookupTestfrom 200 → 404. Added->willReturn(true)to Stripe success-path mocks (the handler now reads the return value); flippedorderId <= 0Stripe cases to 404; added a new Stripe case for the #33 late-commit race (orderId > 0butconfirmPaymentreturns false → 404). All 73 REST webhook tests pass; Unit suite 3 162 green. - Behaviour change visible to providers. Unmatched webhooks now return 404 instead of 200. Providers that implement retry-on-non-2xx (Stripe, Viva Wallet, PayPal, Piraeus, PayByBank all do) will retry, giving the order row time to commit.
- The problem. Three REST webhook handlers —
[4.104.0] fix(webhooks): REST Viva Wallet handler now confirms/cancels gift-card payments (Advisable-com/ecommercen#32)
- The bug.
Webhook::vivawallet()looked up the inboundtran_ticketonly againstshop_order. Gift-card payments key their transaction ticket off thegift_card_orderstable, so a Viva payment confirmed via the REST endpoint silently returned{"received":true,"matched":false}(HTTP 200) and never accepted the gift card — the customer paid but received nothing. - Fix. On a
shop_ordermiss the handler now falls back togift_card_ordersby transaction ticket via a new privatehandleVivaWalletGiftCard()method. Confirmed events runtransactionPaymentCreated→ set installments →acceptGiftCard; failed events runtransactionFailed→cancelGiftCard. This restores parity with the legacyAdvViva::handleWebhook()dual-lookup path. - Design note. Gift-card accept/cancel is non-trivial transactional logic (coupon creation + code generation on accept, coupon deletion on cancel) that lives entirely in the legacy
gift_card_orders_model. Rather than duplicate fragile coupon logic in the modern layer, the gift-card branch delegates to the proven legacy model — idiomatic becauseWebhookextendsBase_c(a CI controller). Porting that logic to the modern layer is a separate concern. - Tests. 5 new
WebhookVivaWalletTestcases: payment-confirmed accept path, transaction-failed cancel path, string-event-name fallback, both-lookups-miss →matched:false, unknown-event acknowledges without mutation. Existing unmatched-order tests inWebhookVivaWalletTestandWebhookRepositoryLookupTestupdated to stub a null-returning gift-card model (unmatched now means both lookups miss). All 72 REST webhook tests pass; Unit suite 3 161 green.
- The bug.
[4.104.0] refactor(checkout): port Viva Payment logging to the modern domain layer (Advisable-com/ecommercen#148)
- What. The legacy
Adv_viva_logging_model(tableviva_logging) has been ported to a modern Checkout-domain satellite stack undersrc/Domains/Checkout/VivaLogging/, closing the last pre-existing legacy-model gap in the Checkout domain. The pattern mirrors theOrder/AsapDatasatellite-table stack. - New files.
Repository/Entity.php,Repository/Repository.php(tableviva_logging),Repository/RepositoryConfigurator.php(BELONGS_TO order onorder_id),Repository/WriteRepository.php,Service.php,WriteService.php,ListRequest.php(filters: id, orderId, transactionId). - Service contract.
Service::getTransactionByOrderId(int $orderId): ?stringreturns the Viva Wallet transaction id recorded for an order, ornullif none exists (the legacyfalse-on-miss becomesnull).WriteService::logTransaction(int $orderId, string $transactionId): ?Entityrecords a Viva transaction authorized-but-pending (status === 'A') against an order id for later reconciliation — faithful to the legacyinsert(). - Scope. All five services registered in
src/Domains/Checkout/container.php. No REST endpoint — this is operational logging, not a domain concern exposed to consumers. The legacyAdv_viva_logging_modeland itsAdv_checkoutcaller are left untouched (same dual-track convention as the PayByBank port #163). - Tests. 15 unit tests (WriteService + Service including
getTransactionByOrderIdhit/miss) + 4 integration container-resolution tests guarding the DI registration. Full suite green.
- What. The legacy
[4.104.0] test(webhooks): add unit test coverage for REST and legacy webhook handlers, closing the gap tracked in Advisable-com/ecommercen#34
- Coverage added. Four new REST webhook test files and two new legacy webhook test files — 90 tests total — now exercise every handler that previously had no test:
tests/Unit/Rest/Webhooks/Controllers/WebhookStripeTest.php(11 tests) — real-HMAC signature verification, unconfigured-secret 500, invalid-signature 400,checkout.session.completed/expiredconfirm/cancel paths,orderId <= 0no-op, unknown event.tests/Unit/Rest/Webhooks/Controllers/WebhookVivaWalletTest.php(12 tests) — event dispatch, order lookup, confirm/cancel transitions, malformed payload.tests/Unit/Rest/Webhooks/Controllers/WebhookPiraeusTest.php(8 tests) — real-HMAC validation, success/fail notification paths, unmatched-order 404.tests/Unit/Rest/Webhooks/Controllers/WebhookPayByBankTest.php(19 tests) — status mapping, payway-scoped lookup, intermediate-status no-ops.tests/Legacy/Webhooks/AdvVivaTest.phpandtests/Legacy/Webhooks/AdvSmartCartTest.php(40 tests combined) —handleWebhook/handledispatch, payload validation, status transitions via reflection + mocked models.
- Testability fix (zero production behaviour change).
Webhook::stripe()now reads the raw request body via the existing$this->readRawBody()seam instead of callingfile_get_contents('php://input')directly — consistent with the other four REST handlers;readRawBody()falls through tophp://inputin production. This unblocked branch coverage of the Stripe handler. - What was already covered. The PayPal REST handler and the legacy
AdvJccandAdvKlarnaPaymentshandlers had existing tests and are not affected by this change. - Suite status. All 129 webhook tests pass; Unit suite (3 141 tests) and Legacy suite (281 tests) remain fully green.
- Coverage added. Four new REST webhook test files and two new legacy webhook test files — 90 tests total — now exercise every handler that previously had no test:
[4.104.0] fix(checkout): register
PollPayByBankStatusjob in the DI container so the cron dispatcher can resolve it (Advisable-com/ecommercen#163)- The bug.
PollPayByBankStatus— the modern port of the legacyPayByBankGetStatusjob shipped in the REST checkout-parity epic (#253) — was never registered insrc/Domains/Checkout/container.php. The cron dispatcher (AdvJob) resolves jobs via$container->has($name) ? $container->get($name) : new $name(). Because the job has required constructor dependencies (OrderRepository,PaymentConfirmationService), thenew $name()fallback would have caused a fatal error the moment an operator enabled the schedule entry inapplication/config/jobs.php. The unit test passed throughout because it instantiates the job directly, bypassing the container. - Fix. Added
$services->set(Jobs\PollPayByBankStatus::class)tosrc/Domains/Checkout/container.php. The schedule entry stays commented out (operator opt-in); no runtime behaviour change. - Tests. New
tests/Integration/Domains/Checkout/Jobs/PollPayByBankStatusContainerTest.phpasserts the job resolves from the container and receives its constructor dependencies correctly — regression guard against future container re-breaks.
- The bug.
[4.104.0] feat(seo): extend faceted-navigation noindex to vendor listing pages
- Note (post-merge correction). The PHP-side extension —
renderVendorListingSeo()extended to variation/attribute filters and called fromdefaultRender()— was reverted by commitf1aecc6a9("Remove the unessesary logic for the no index no follow").renderVendorListingSeo()is back to tag-only on thevendors()path, matching pre-4.104.0 behaviour. Therobots.txtdisallow rules and therel="nofollow"filter-link changes (documented in the category entry below) apply to vendor pages as well (the filter query-string params and theEntryFilter.vuecomponents are shared). PHP-sidenoindex,nofollowfor variation/attribute-filtered vendor pages was not shipped.
- Note (post-merge correction). The PHP-side extension —
[4.104.0] feat(seo): stop faceted-navigation filter URLs from bloating the index
- Problem. Storefront category sidebar filters (product tags, variations, attributes) produce query-parameter URLs (e.g.
?προιοντα-για[]=…) that are near-duplicates of the base category page. Google was crawling and indexing thousands of these combinations, inflating crawl cost and index size with no SEO value. public/robots.txt. Added aUser-agent: *block disallowing the six faceted-navigation filter params (the three param pairs fromapplication/config/languages.php:tag_filter_query_string,variation_filter_query_string,attribute_filter_query_string— Greek and English variants, Greek names percent-encoded). Also disallows/searchand/*/searchresult pages. ExplicitAllowdirectives for CSS, JS, image, and font assets are included so Googlebot can still render pages.rel="nofollow"on filter links. Added to the filter anchor<a>element in all three storefront filter Vue components:assets/main/vue/tag/filters/Components/EntryFilter.vue:16,assets/main/vue/variations/filters/Components/EntryFilter.vue:19,assets/main/vue/attributes/filters/Components/EntryFilter.vue:19. Storefront bundle rebuilt at the 4.104.0 release (public/ui/main/js/all-listing-filters.js).- Note (post-merge correction). The PHP-side
Adv_product_categories.phpchange — detecting filter params via a sharedAdv_front_controller::hasActiveListingFilters()helper and settingdofollow = falseat thedefaultRender()chokepoint — was reverted by commitf1aecc6a9. Filtered category pages rely on the existing genericsetNoFollow()(any querystring →dofollow = false) at theview()path (Adv_product_categories.php:572-573). ThehasActiveListingFilters()method does not exist in the codebase.
- Problem. Storefront category sidebar filters (product tags, variations, attributes) produce query-parameter URLs (e.g.
[4.104.0] fix(webhooks): decouple webhook routes from
APP_REST_API_ENABLEDflag — payment confirmations now always reachable (Advisable-com/ecommercen#31)- The bug (critical). All five REST payment-webhook routes (
POST /rest/webhooks/{stripe,vivawallet,paypal,piraeus,paybybank}) were declared insideapplication/config/rest_routes.php, whichroutes.phpconditionally@includes only whenAPP_REST_API_ENABLED=true. In any deployment where that env flag was absent or false the routes simply 404'd — silently, with no log signal. Stripe and PayPal have no legacy fallback callback path; for those gateways a missed webhook means the order stays unconfirmed and no payment confirmation is sent to the customer. - The fix. Extracted the five route assignments into a new
application/config/webhook_routes.phpand maderoutes.php@includeit unconditionally, adjacent to but outside theAPP_REST_API_ENABLEDblock. The REST routing format ([\FQCN\Class::class, 'method']arrays) is processed byapplication/core/MY_Router.php::_parse_routes(), which is always active — only the inclusion ofrest_routes.phpwas ever flag-gated. Pure config-shape change; no behavioural change to any handler. - Tests. New
tests/Integration/Routing/WebhookRoutesAreAlwaysAvailableTest.php(4 tests / 28 assertions): all five POST routes resolve toWebhook::classwith the correct method;routes.php@includeswebhook_routes.phpat top level and not inside the flag block (verified by a brace-depth parser);rest_routes.phpno longer re-declares the webhook routes;siteModeAllowedNamespacesinapp.phpstill covers the Webhook controller FQCN so gateway callbacks are not redirected to/soonor/maintenancein AdminOnly/AdminFrontend site modes.phpunit.xml.distregisters the newtests/Integration/Routingdirectory.
- The bug (critical). All five REST payment-webhook routes (
[4.104.0] refactor(webhooks): move raw
shop_orderqueries out of REST webhook handlers into the Order domain repository (Advisable-com/ecommercen#44)- What. The three private lookup helpers inside
src/Rest/Webhooks/Controllers/Webhook.php—findOrderBySerialAndPayway(),findOrderByTranTicket(), and the supplementary-data lookup insideextractPayPalOrderId()— used to callget_instance()->dbdirectly (raw CodeIgniter query builder againstshop_order). They now thin-delegate to two new public methods on the Order domain repository:findBySerialAndPayway(string $serial, string $payway): ?EntityandfindByTranTicketAndPayway(string $tranTicket, string $payway): ?Entity. Each method uses the existing Filter Specification pattern consistent with the rest of the Order repository. - Why it matters. The controller is now properly unit-testable without a live database — the repository is injected via the constructor and can be mocked. The lookup pattern is canonical (domain repository, not ad-hoc SQL). The existing payway scoping on each lookup is preserved as a security boundary: a webhook event for one payment provider cannot accidentally resolve an order that belongs to a different payway.
- Scope.
src/Rest/Webhooks/container.phpupdated to wire the newOrderRepositoryconstructor dep. The existingreadRawBody()test seam was widened from PayPal-only to also cover Vivawallet and PayByBank so the new repository paths could be unit-tested — production behaviour is unchanged. - Tests. 6 new integration tests in
tests/Integration/Domains/Order/Order/RepositoryTest.phpcovering happy path, payway-filter security boundary, and null-on-miss for both methods (19 → 25 tests). Newtests/Unit/Rest/Webhooks/Controllers/WebhookRepositoryLookupTest.php(7 tests) asserts the controller calls the repo with the expected payway scope across Vivawallet, PayByBank, and PayPal handlers.
- What. The three private lookup helpers inside
[4.104.0] fix(loyalty): loyalty points now awarded for orders delivered by carriers that report English statuses (Advisable-com/ecommercen#276)
- Bug.
AdvAddPointsToCustomerDeliver(and its inline equivalent inCronjob::addPointsToCustomerDeliver()) queried delivered orders bygtstatus = 'ΠΑΡΑΔΟΜΕΝΟ'. The transporter poller writes the carrier's raw English status string togtstatus(e.g.'DELIVERED'for GenikiV2) and then setsgtflag = 1; it never writes the Greek string for those carriers. As a result, every order fulfilled by GenikiV2 — and any other carrier that returns a non-Greek delivered label — was silently skipped by the loyalty awarder. Customers received no loyalty points for those orders. - Fix. Both sites replaced the hard-coded Greek status match with
'gtflag' => 1.gtflagis the canonical boolean delivery sentinel set by all carrier branches of the poller regardless of the status vocabulary they use, so it captures every actually-delivered order. - Tests. New regression test at
tests/Integration/Legacy/Jobs/AdvAddPointsToCustomerDeliverTest.php(2 tests, 5 assertions) covering the fix directly. - Flow doc.
docs/flows/customer/CF-32-loyalty-points.mdstep 2 and business rule 12 updated to reflect thegtflag = 1criterion; the former Known Issues note pointing to this gap removed.
- Bug.
[4.104.0] fix(transporter): harden
GetOrdersTransferStatusagainst orphaned and odd carrier data (Advisable-com/ecommercen#275)- What. Three pre-existing defensive-coding gaps in the shipment-tracking poller, fixed identically in both the active legacy job (
ecommercen/job/libraries/AdvGetOrdersTransferStatus.php) and the modern port (src/Domains/Transporter/Jobs/GetOrdersTransferStatus.php):- Null-provider guard — an orphaned
transporters_settingsrow (settings present, master record deleted) used to read$provider->class_nameon null, emitting a recurring PHP 8.1E_WARNINGon every poll for every orphaned row. The guard is nowif (!$provider || !$providerSettings) continue;. - Single-checkpoint normalization (Center, EasyMail, CyprusPost, TaxydemaV2) —
array_pop($track->Checkpoints->Checkpoint)raised a TypeError/warning when a carrier collapsed the checkpoint to a non-array; replaced withis_array($checkpoints) ? end($checkpoints) : $checkpointsso the single checkpoint is now recorded correctly. - Unmapped-status warning (BoxNow, Skroutz) —
$statusLabels[$key]raised an "Undefined array key" warning on unmapped statuses; rewritten as$statusLabels[$key] ?? null. The null-coalescing operator suppresses the warning while persisting the same NULL value as before — zero behavioural change to stored data, preserving the implicit safety margin against a string collision with the loyalty-points sentinel'ΠΑΡΑΔΟΜΕΝΟ'.
- Null-provider guard — an orphaned
- Severity correction. Item 1 was originally framed as a fatal "aborts the whole run"; empirically it is a non-fatal
E_WARNINGunder PHP 8.1 (switch(null)matches no case and the provider is skipped). The real cost was log noise, not an aborted nightly poll. AD-33's Known Issues section updated accordingly. - Tests. New
tests/Integration/Domains/Transporter/Jobs/GetOrdersTransferStatusTest.phpregression-guards fix 1 end-to-end (seeds an orphaned-settings row, neutralises other orders so no carrier API is touched, installs a strictE_WARNINGhandler so the test fails — not just warns — if the null-read regresses). Verified red without the fix, green with it. Fixes 2 and 3 stay inspection-verified (gateway-coupled carrier methods) consistent with the unit suite's documented CI3/network limits. - Follow-up. A
gtstatus-reader audit done while writing the regression test surfaced #276 — the loyalty-points awarder (AdvAddPointsToCustomerDeliver) matches delivered orders bygtstatus = 'ΠΑΡΑΔΟΜΕΝΟ'without also checkinggtflag, so GenikiV2 deliveries that return the English'DELIVERED'(which the poller writes raw togtstatus, then setsgtflag = 1) look like a real loyalty miss. Tracked separately.
- What. Three pre-existing defensive-coding gaps in the shipment-tracking poller, fixed identically in both the active legacy job (
[4.104.0] fix(transporter): correct Speedex
'succcess'typo — delivered orders now correctly setgtflag = 1(Advisable-com/ecommercen#277)- What.
getSpeedexTransporterStatus()accessed$speedexConfig->finalVoucherStatusCodes['succcess'](triple 'c') butSpeedexConfigdefines the key as'success'. The typo causedarray_merge(null, $failureCodes)— success codes were silently dropped, soin_array($lastCheckpoint->StatusCode, $finalVoucherStatusCodes)could never match a delivered Speedex order. Every Speedex delivery stayed in the poll loop untilgtcountreached 10, wasting 10 API calls per order. - Fix. Corrected to
['success']in bothecommercen/job/libraries/AdvGetOrdersTransferStatus.php:301andsrc/Domains/Transporter/Jobs/GetOrdersTransferStatus.php:322. Caught by doc-ba-proofread of AD-33 during the #275 resync.
- What.
[4.104.0] feat(transporter): port
GetOrdersTransferStatusshipment-tracking job to the Transporter domain (Advisable-com/ecommercen#274)- What. The legacy
AdvGetOrdersTransferStatuscron poller (~602 lines, 15 carrier branches) has been ported toAdvisable\Domains\Transporter\Jobs\GetOrdersTransferStatusimplementing\JobCommand. All carrier logic, Greek status vocabulary, and runtime behaviour are preserved 1:1; the job's carrier gateways (Advisable\Transporters\*) are reused as-is and per-provider settings now flow through the existing domainTransporterSettingsLoader. - Registration. Autowired and registered by FQCN in
src/Domains/Transporter/container.php; added toapplication/config/jobs.phpcommandOptions. The legacy scheduled entry remains active; a commented modern toggle entry is included alongside it (thePollPayByBankStatusconvention — operators flip it when ready). - Tests. Unit test added at
tests/Unit/Domains/Transporter/Jobs/GetOrdersTransferStatusTest.php. Split out of #163 (the "Payment Status" job bundle). Follow-up #275 tracks three pre-existing defensive-coding issues carried over from the legacy job (intentionally not fixed here to keep the port 1:1).
- What. The legacy
Notes
[4.104.0] REQUIRES
php migrator.php migrate:20260529120000_dedup_product_category_lp_unique— removes duplicate(product_id, category_id)rows fromshop_product_category_lpand replaces the non-uniqueproduct_categoryindex with aUNIQUE INDEX.down()is forward-only (no rollback).
[4.104.0] Faceted-navigation SEO — deployed artifacts:
- Storefront bundle rebuilt at the 4.104.0 release.
rel="nofollow"is present inpublic/ui/main/js/all-listing-filters.jsandadv-listing-used-filters.js. - Methods to re-check for client overrides (surviving signals only — PHP chokepoints were reverted, see the
feat(seo)entries above):Adv_front_controller::setNoFollow()/setCanonicalUrl()— generic any-querystringdofollow = false; called in categoryview()(:572-573) and vendorvendors()(:1338-1339).Adv_vendors::renderVendorListingSeo($hasTagQueryString)— tag-filter-only noindex on thevendors()products path (:1121,:1265-1273). Variation/attribute-filtered vendor pages are not covered by PHP at this time.
- Storefront bundle rebuilt at the 4.104.0 release.