toga-ai 1.0.923 → 1.0.924

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@ project: API
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-29
9
+ updated: 2026-10-01
10
10
  owners: ["bala", "mhammontree", "tcox", "apeterson", "rgirish", "jcardinal"]
11
11
  files:
12
12
  - toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useCreateTransferOrder.ts
@@ -194,9 +194,11 @@ behaviour — it bites any write to a nested child, not one feature. Worked exam
194
194
  - **Not every record is linkable by `uuid`** — read `searchableIdentifierFields` in the EV-12 body; fall back to `id` resolved by a natural key, never hardcoded.
195
195
  - **Declaring a `c_` field on the model does not make it searchable** — the `CustomRecordFields.isIdentifier` flag is the gate; set the flag, not the child policy.
196
196
  - **An identifier that is unique only inside a parent (e.g. `bundles.number` per catalog) links the WRONG record, silently.** The match uses only the identifier fields sent, so `bundle: {number: "001"}` hits another catalog's `001`. Send `uuid` (or add the scoping identifier) resolved in the caller. Seen on Prudential India REQs — see [bundle catalog resolution](../../../clients/prudential/features/service-request-bundle-catalog-resolution.md).
197
+ - **⚠ A nested `unit` with only an `item` (no serial/asset tag) REUSES one existing unit.** `ServiceRequests_Units.unitId` is `MATCH_UPSERT` and `Units.itemId` is an identifier, so `{unit:{item:{uuid}}}` matches the first unit of that item — every SR links the same unit. For serial-less requests attach a **bundle** instead (`ServiceRequests_Bundles.bundleId` is `MATCH`; send `{bundle:{uuid}}`). See [Adyen reclaim flow](../../../clients/adyen/features/service-request-salesorder-netsuite.md).
197
198
  - **Singular-FK back-reference defects:** UPDATE path drops the parent's forward FK (echoes `null`); CREATE path re-parents a value-matched child to the newest writer (stolen child row). One defect, two symptoms.
198
199
 
199
200
  ## Change history
201
+ - 2026-09-30 — Gotcha: item-only nested unit matches an existing unit; use a bundle for serial-less SRs. (rgirish)
200
202
  - 2026-09-29 — Gotcha: a per-parent identifier (`bundles.number`) matches across parents and links the wrong row. (rgirish)
201
203
  - 2026-09-24 — Documented that the engine recurses into a nested inherent child with `POST`
202
204
  hardcoded (`V2.php:5415`), so a child nested in a PUT is duplicated or fails 1062; update
@@ -6,11 +6,12 @@ project: Worker
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-16
10
- owners: ["snaredla", "bala"]
9
+ updated: 2026-10-01
10
+ owners: ["snaredla", "bala", "rgirish"]
11
11
  files:
12
12
  - worker2/Worker/Sync/ServiceRequest.php
13
13
  - _underscore/Model/Elite/ServiceRequest.php
14
+ - _underscore/Model/Adyen/ServiceRequest.php
14
15
  - dbchanges2/Core/2026-08-06a - Service request and sales order payload interceptors.sql
15
16
  related:
16
17
  - ./netsuite-salesorder-outbound-push.md
@@ -19,6 +20,8 @@ related:
19
20
  - ../../_underscore/features/units-for-items-for-purchase-orders.md
20
21
  - ../../../clients/elite/features/salesorder-netsuite-push.md
21
22
  - ../../../clients/elite/features/togadesk-service-request-intake.md
23
+ - ../../../clients/adyen/features/service-request-salesorder-netsuite.md
24
+ - ../../api2/features/nested-relationship-writes.md
22
25
  ---
23
26
 
24
27
  Worker `_Worker_Sync_ServiceRequest` that turns a Service Request → Sales Order → one PO per vendor for any tenant; open for the SR→SO→PO chain, its interceptor queueing, vendor resolution, or the `c_`-column probe.
@@ -34,7 +37,9 @@ _Worker_Sync_ServiceRequest::GenerateSalesOrderFromServiceRequest(
34
37
  ): string
35
38
  ```
36
39
 
37
- Queued from a client's `service-requests` `postPost` interceptor (Elite is first consumer — see [Elite desk intake](../../../clients/elite/features/togadesk-service-request-intake.md)); invocable directly for testing like any worker action.
40
+ Queued from a client's `service-requests` `postPost` interceptor; invocable directly for testing like any worker action. Consumers: **Elite** ([desk intake](../../../clients/elite/features/togadesk-service-request-intake.md)) and **Adyen** ([reclaim flow](../../../clients/adyen/features/service-request-salesorder-netsuite.md), built 2026-09-30, not deployed).
41
+
42
+ **Per-client config = `SALES_ORDER_DEFAULTS_BY_CLIENT`**, keyed by `Core.Clients.clientIdentifier`: `locationUuid` (required — NetSuite rejects an order with no location) + `salesOrderTypeUuid` (nullable). Elite → location `3ca47b91-…`, type drop-ship `5994b81d-…`; Adyen → location `191c75ff-f02e-4dda-abc6-c8d26c35da62`, no type. **A client missing from the map throws** — onboarding a client to this worker = add a map entry. The uuids are the same in beta and prod.
38
43
 
39
44
  **Proven end to end on dev-sandbox** (`https://api.dev.sandbox.togahub.com/v2`): one service request produced **SA100004** plus purchase order **P1**, both bridge rows linked, and a **rerun returned early** rather than duplicating.
40
45
 
@@ -50,8 +55,8 @@ Steps:
50
55
  1. **Resolve the tenant.** `getClientContextFromCore()` looks up by `Core.Clients.clientIdentifier`, registers the client DB connection, returns Toga API credentials. (Deliberate local copy of the same private in `_Worker_Netsuite_SalesOrder`; lift to a shared parent when a third caller appears.)
51
56
  2. **Guard.** `fetchServiceRequestFromToga2()` joins `SalesOrders` on `serviceRequestId`. If a sales order already exists, **return early** with a summary — makes the action idempotent under SQS redelivery and manual reruns. A request already in the **`Exception`** stage is skipped for the same reason.
52
57
  3. **Reconcile the requester's address** (`reconcileRequesterAddressInToga2`) and **opt the requester into Order Updates** (`optInRequesterToOrderUpdatesInToga2`).
53
- 4. **Build and POST the sales order** (`buildSalesOrderPayloadForToga2`): customer by uuid, `serviceRequestId`, `ticketId`, one `salesOrderItems` line per service-request unit (`lineNumber` assigned by the worker, quantity 1, price 0), and `shipToAddress` **only when the request has a deliverable address**.
54
- 5. **Generate purchase orders** (`tryGeneratePurchaseOrdersInToga2` → `generatePurchaseOrdersInToga2`): re-read created lines with `fetchSalesOrderItemsFromToga2()`, resolve a vendor per item with `fetchVendorItemsFromToga2()`, group lines by vendor, POST **one PO per vendor** with line numbers restarted per order.
58
+ 4. **Build and POST the sales order** (`buildSalesOrderPayloadForToga2`): customer by uuid, `serviceRequestId`, `ticketId`, `locationId` + type from the client's map, `shippingMethodId` = the SR's, else **`ShippingMethods` id 1**, and `shipToAddress` **only when the request has a deliverable address**. Lines come from `fetchServiceRequestLinesFromToga2()`: one line per SR **unit**; if the SR has no units, one line per **bundle item** (`ServiceRequests_Bundles` → `BundleItems` → `Items`). Each line is quantity 1, price 0, `lineNumber` assigned by the worker. No units and no bundle items → throws (no empty order).
59
+ 5. **Generate purchase orders** (`tryGeneratePurchaseOrdersInToga2` → `generatePurchaseOrdersInToga2`): re-read created lines with `fetchSalesOrderItemsFromToga2()`, resolve a vendor per item with `fetchVendorItemsFromToga2()`, group lines by vendor, POST **one PO per vendor** with line numbers restarted per order. Items with no vendor are listed in the job result as `No vendor for: …` — the job does **not** fail (Adyen has no `VendorItems`, so it gets no PO).
55
60
  6. **On a rejected sales-order POST**, `moveServiceRequestToExceptionInToga2()` parks the request in the `Exception` stage so the failure is visible *and* a redelivered job short-circuits on the step-2 stage guard.
56
61
 
57
62
  ### Vendor resolution replaces Prudential's hardcoded vendor
@@ -88,12 +93,14 @@ The three structural checks (string resolves → producer matches → intercepto
88
93
  - **⚠ Before nesting a child collection in a payload, confirm the child is registered in `Core.InherentRecordChildren` under that parent record.** `salesOrderItemsCommittedUnits` was briefly nested under sales-order lines; it is **impossible** — record 152 has **zero** `Core.InherentRecordChildren` parent registrations under record 15, nothing writes that table via the API at all, and its model's `unitId` is a foreign key to `_Model_Client_User`, not to a unit. A nested key the API does not recognize is not obviously rejected; run the `InherentRecordChildren` check rather than assuming.
89
94
  - **`shipToAddress` carries `childPolicy CREATE`** — posting an all-null address shape **creates a blank address row**. `hasDeliveryAddressForServiceRequest()` omits the key entirely instead.
90
95
  - **Line numbers are assigned by the worker and re-checked.** After the POST the worker maps created lines back by `lineNumber`; a line it cannot map means the API renumbered them, and that is **reported** rather than silently dropped from the PO.
91
- - **⚠ Never point this worker at Prudential.** Prudential service requests are **bundle-driven** (`BundleItems`), a shape this worker does not implement. It would produce wrong orders, not no orders.
96
+ - **⚠ Do not add Prudential to the map without a review.** The worker now reads bundle items, but only as plain qty-1 lines — not Prudential's `BundleItems.preferredVendorId` / Dell flow. Prudential is not in `SALES_ORDER_DEFAULTS_BY_CLIENT`, so a run for it throws.
97
+ - **Fallback ship method `ShippingMethods` id 1 is a per-tenant row id**, not a known method. When the SR names none, verify id 1 is the right NetSuite-mapped method for that client (open).
98
+ - **⚠ Inbound NetSuite sync can duplicate an order this worker created (all clients, open).** `library/app/api/toga2.php:1002` matches sales orders only by `c_netsuiteInternalSalesOrderId`. If the inbound sync runs after NetSuite has the order but before the push writes the id back, it imports a second copy.
92
99
  - **Placement:** cross-system sync workers live under `worker2/Worker/Sync/`. This worker does **not** belong in `Worker/Netsuite/` (which keeps the NetSuite push, `Worker/Netsuite/SalesOrder.php`) and not in `Worker/Client/`.
93
100
  - **⚠ Renaming this action is a TWO-REPO, single-deploy change.** The method name here and the `runTask(action: 'Sync/ServiceRequest/GenerateSalesOrderFromServiceRequest', …)` **string** in `_underscore/Model/Elite/ServiceRequest.php:44` must land and deploy **together** — the rename from `GenerateSalesOrder` on 2026-08-12 spans both. Ship either alone and the dispatcher targets a name that no longer exists; because `postPost` wraps `runTask` in try/catch + `error_log`, Sales Order generation then stops **silently**. Same exposure for `Sync/SalesOrderStatus/PostSalesOrderStatusToTogadesk1`. See [creating worker actions](./creating-worker-actions.md).
94
101
  - **⚠ The copied client-context helper has already DRIFTED.** `getClientContextFromCore()` here, the same private in `Worker/Sync/SalesOrderStatus.php`, and `getClientContext()` in `Worker/Netsuite/SalesOrder.php` are three copies — and only **this** one has `ORDER BY id ASC` on the `Apis` credential lookup, so the other two pick a **non-deterministic** API credential row (`Worker/Netsuite/SalesOrder.php` documents the flaw in a comment). Add the `ORDER BY` to all three if you touch any, and prefer extraction.
95
102
  - **The NetSuite actions in this chain are `Netsuite/SalesOrder/CreateNetSuite` and `Netsuite/SalesOrder/UpdateNetSuite`.** There is **no** `Netsuite/SalesOrder/Push` — do not write that string into a migration, a test or a doc.
96
- - **⚠ It does not set `locationId` — which blocks the NetSuite push downstream (open, 2026-08-20).** Orders this worker creates land with `locationId` **NULL** (prod Elite `SalesOrders` **279** / `SA100000`, from `serviceRequestId 3`), and `Netsuite/SalesOrder/CreateNetSuite` then fails loudly: *"no NetSuite location mapped on line 1 or on the order — map `Locations.c_netsuiteInternalLocationId`"*. **The mapping data is complete** (prod Elite `Locations` 1 and 2 → 196, 3 → 5), so this is a missing **field**, not a missing mapping. The fix belongs **here, not in the push**: an order should record where it ships from regardless of NetSuite, and `buildNetSuiteOrder()` deliberately refuses to guess a warehouse when the data does not say. See [the NetSuite push](./netsuite-salesorder-outbound-push.md).
103
+ - **`locationId` was missing until 2026-09-30.** Orders created before then (e.g. prod Elite `SalesOrders` 279 / `SA100000`) have `locationId` NULL and fail `Netsuite/SalesOrder/CreateNetSuite` with *"no NetSuite location mapped…"*. The worker now sets it from the client's map (not deployed yet). Keep the fix here — `buildNetSuiteOrder()` must never guess a warehouse.
97
104
 
98
105
  ### Known limitations — business blockers, not code defects
99
106
 
@@ -104,6 +111,9 @@ Recorded so nobody re-debugs them as bugs. All four confirmed against Elite's da
104
111
  - **Generated POs reach no vendor.** No `Integrations` rows and no `defaultPoSubmissionIntegrationId` configured, so nothing transmits them.
105
112
  - The worker is otherwise complete; these are data/config gaps for the client to close.
106
113
 
114
+ ## Change history
115
+ - 2026-09-30 — Per-client `SALES_ORDER_DEFAULTS_BY_CLIENT` (sets `locationId`, unknown client throws); bundle-item lines when an SR has no units; no-vendor items reported, not failed; Adyen added as second consumer. Not deployed. (rgirish)
116
+
107
117
  ## Related
108
118
 
109
119
  - [Toga → NetSuite Sales-Order Push](./netsuite-salesorder-outbound-push.md)
@@ -112,3 +122,4 @@ Recorded so nobody re-debugs them as bugs. All four confirmed against Elite's da
112
122
  - [Units for items for purchase orders](../../_underscore/features/units-for-items-for-purchase-orders.md)
113
123
  - [Elite Sales-Order NetSuite push](../../../clients/elite/features/salesorder-netsuite-push.md)
114
124
  - [Elite TOGaDesk service-request intake](../../../clients/elite/features/togadesk-service-request-intake.md)
125
+ - [Adyen reclaim SR → SO → NetSuite](../../../clients/adyen/features/service-request-salesorder-netsuite.md)
@@ -19,7 +19,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
19
19
 
20
20
  ## 2.0 framework
21
21
 
22
- - **_underscore** (_Underscore) _(framework core)_ — 93 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
22
+ - **_underscore** (_Underscore) _(framework core)_ — 94 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
23
23
  - **worker2** (Worker) — 80 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
24
24
  - **api2** (API) — 26 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
25
25
  - **dbchanges2** (Database Changes) _(framework core)_ — 22 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
@@ -2,4 +2,5 @@
2
2
 
3
3
  | Doc | Framework | Summary |
4
4
  |-----|-----------|---------|
5
+ | [Adyen — Reclaim Service Request → Sales Order → NetSuite (bundle-driven, no serials)](features/service-request-salesorder-netsuite.md) | 2.0 | Adyen's Reclaim SR → SO → NetSuite chain (same as Elite, but items come from a seeded bundle because Adyen sends no serials); open for the prePost bundle attach |
5
6
  | [Adyen](profile.md) | 2.0 | Adyen live TOGa Supply client (`Client_Adyen`) on the 1.0 NetSuite→TOGa Supply sync, status draft; open for the prod tracking-API test orders + SR↔NetSuite matc |
@@ -0,0 +1,59 @@
1
+ ---
2
+ title: "Adyen — Reclaim Service Request → Sales Order → NetSuite (bundle-driven, no serials)"
3
+ framework: "2.0"
4
+ repo: _underscore
5
+ project: _Underscore
6
+ client: adyen
7
+ type: client-feature
8
+ status: draft
9
+ updated: 2026-10-01
10
+ owners: ["rgirish"]
11
+ files:
12
+ - _underscore/Model/Adyen/ServiceRequest.php
13
+ - _underscore/Model/Adyen/SalesOrder.php
14
+ - worker2/Worker/Sync/ServiceRequest.php
15
+ - dbchanges2/Client_Adyen/2026-09-30a - ServiceRequestSalesOrderSetup.sql
16
+ related:
17
+ - ../../../2.0/apps/worker2/features/service-request-sales-order-generation.md
18
+ - ../../../2.0/apps/worker2/features/netsuite-salesorder-outbound-push.md
19
+ - ../../../2.0/apps/api2/features/api-payload-interceptors.md
20
+ - ../../../2.0/apps/api2/features/nested-relationship-writes.md
21
+ - ../../elite/features/salesorder-netsuite-push.md
22
+ - ../profile.md
23
+ ---
24
+
25
+ Adyen's Reclaim SR → SO → NetSuite chain (same as Elite, but items come from a seeded bundle because Adyen sends no serials); open for the prePost bundle attach, the 2026-09-30a setup migration, deploy order, or duplicate risks.
26
+
27
+ ## Summary
28
+
29
+ Adyen's API (`Apis` id 4) posts Service Requests, all of type **"Reclaim"**, and **cannot send serial numbers**. The chain is the same as Elite's: SR `postPost` → worker2 builds the Sales Order → SO `postPost` → NetSuite push. The only Adyen difference: the SR's items come from a seeded **"Reclaim" bundle**, attached in `prePost`.
30
+
31
+ **Status (2026-10-01): code + migration written, NOT run or deployed anywhere, not tested end to end.**
32
+
33
+ ## How it works
34
+
35
+ 1. `_Model_Adyen_ServiceRequest::prePost` — when type is `Reclaim` and the payload has **no units and no bundles**, adds `serviceRequestBundles: [{bundle:{uuid:'23717330-7672-4590-9e3d-7f4097c18723'}}]` (`RECLAIM_BUNDLE_UUID`).
36
+ 2. `_Model_Adyen_ServiceRequest::postPost` — same as Elite's: queues `Sync/ServiceRequest/GenerateSalesOrderFromServiceRequest`.
37
+ 3. The worker turns the bundle's items into SO lines (qty 1 each) and sets location "Adyen" from its client map — see [SR → SO generation](../../../2.0/apps/worker2/features/service-request-sales-order-generation.md). No PO: Adyen has no `VendorItems` (job reports `No vendor for: …`, does not fail).
38
+ 4. The existing `_Model_Adyen_SalesOrder::postPost` queues `Netsuite/SalesOrder/CreateNetSuite` ([shared push](../../../2.0/apps/worker2/features/netsuite-salesorder-outbound-push.md)).
39
+
40
+ **Why a bundle, not units:** a unit with only an item (no serial) matches an **existing** unit, so every SR would link the same unit — see [nested writes](../../../2.0/apps/api2/features/nested-relationship-writes.md). `ServiceRequests_Bundles.bundleId` is `MATCH` (links only), and Prudential already posts bundles daily with 201.
41
+
42
+ ### Setup migration — `dbchanges2/Client_Adyen/2026-09-30a - ServiceRequestSalesOrderSetup.sql`
43
+ - `ApiPayloadInterceptors`: record 35 (`service-requests`) PRE+POST / POST, scoped to Adyen API; record 14 (`sales-orders`) POST/POST and POST/PUT, `apiId` NULL (the worker writes as a different API identity).
44
+ - Items `SVC-FS-OFFBOARD-ADN` (NS 1579821) + `Misc Laptop` (NS 1545437, SERIALIZED); "Reclaim" bundle + 2 `BundleItems`.
45
+ - Location "Adyen" (NS location 70); ShippingCarrier FedEx + ShippingMethod Ground (NS 318627).
46
+ - `Customers.c_netsuiteInternalCustomerId = 31054`.
47
+
48
+ These values copy Adyen's hand-made NetSuite reclaim orders (e.g. 290879, 288260, 279871): always `SVC-FS-OFFBOARD-ADN` + one device item, location 70, ship method 318627.
49
+
50
+ ## Gotchas
51
+
52
+ - **⚠ Deploy order: worker2 → `_underscore` → SQL.** Running the SQL first registers a PRE/POST interceptor whose `prePost` method does not exist yet, so api2 fails **every** Adyen SR POST.
53
+ - **Before 2026-09-30a, `Client_Adyen` had 0 `ApiPayloadInterceptors` rows** — the Adyen model hooks committed 2026-08-24 never fired. "Nothing happened" on Adyen is a data check first.
54
+ - **Open — Adyen resending the same SR creates a duplicate.** `c_externalReferenceId` is not unique (SR100024 already duplicates SR100021 — see [profile](../profile.md)).
55
+ - **Open — NetSuite PO # (`otherrefnum`) will be the SR number**, not the employee name the hand-made orders carry. The profile's SR↔NetSuite matching reads that field.
56
+ - **Open — shipping method fallback** and the inbound-sync duplicate race are shared worker issues — see [SR → SO generation](../../../2.0/apps/worker2/features/service-request-sales-order-generation.md#gotchas).
57
+
58
+ ## Change history
59
+ - 2026-09-30 — Built: Reclaim bundle attach in `prePost`, Elite-style `postPost`, setup migration 2026-09-30a. Not deployed. (rgirish)
@@ -7,11 +7,12 @@ apps:
7
7
  - _underscore
8
8
  - api2
9
9
  - dbchanges2
10
+ - worker2
10
11
  project: Worker
11
12
  client: adyen
12
13
  type: profile
13
14
  status: draft
14
- updated: 2026-09-30
15
+ updated: 2026-10-01
15
16
  owners: ["rgirish"]
16
17
  files:
17
18
  - worker/crons/toga2/netsuite/sync_togasupply_adyen.php
@@ -22,7 +23,10 @@ files:
22
23
  - dbchanges2/Client_Adyen/2026-09-22a - AdyenApiTestSeed.sql
23
24
  - dbchanges2/Client_Adyen/2026-09-28a - LinkTestOrdersToNetsuite.sql
24
25
  - dbchanges2/Client_Adyen/2026-09-28b - RemoveUnmatchedTestOrders.sql
26
+ - _underscore/Model/Adyen/ServiceRequest.php
27
+ - dbchanges2/Client_Adyen/2026-09-30a - ServiceRequestSalesOrderSetup.sql
25
28
  related:
29
+ - ./features/service-request-salesorder-netsuite.md
26
30
  - ../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md
27
31
  - ../../1.0/apps/library/features/netsuite-suiteql-api-reference.md
28
32
  - ../growrk/features/transfer-order-flow.md
@@ -31,7 +35,7 @@ related:
31
35
  - ../../2.0/apps/dbchanges2/workflows/nonprod-metadata-drift-repair.md
32
36
  ---
33
37
 
34
- Adyen live TOGa Supply client (`Client_Adyen`) on the 1.0 NetSuite→TOGa Supply sync, status draft; open for the prod tracking-API test orders + SR↔NetSuite matching, the `isFulfillable` column gap, the beta=client-sandbox env trap, and the ACL findings.
38
+ Adyen live TOGa Supply client (`Client_Adyen`) on the 1.0 NetSuite→TOGa Supply sync, status draft; open for the prod tracking-API test orders + SR↔NetSuite matching, the `isFulfillable` column gap, the beta=client-sandbox env trap, the ACL findings, and the Reclaim SR → SO → NetSuite chain.
35
39
 
36
40
  ## Summary
37
41
 
@@ -42,7 +46,8 @@ Adyen live TOGa Supply client (`Client_Adyen`) on the 1.0 NetSuite→TOGa Supply
42
46
 
43
47
  - **NetSuite → TOGa Supply sync.** Per-client thin wrapper over the shared engine — full mechanics on [per-client sync](../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md).
44
48
  - **Transfer orders ENABLED** (`IS_ENABLED_INTEGRATION_TRANSFER_ORDERS = true`). Adyen's NetSuite transfer orders arrive **as NetSuite sales orders**, classified by `TRANSFER_ORDER_DETECTION_MODE__ZERO_DOLLAR_HOLD` — `holdInvoice === true` **AND** `total === 0`, same rule as GroWrk (see [GroWrk transfer order flow](../growrk/features/transfer-order-flow.md)). Mode is now declared explicitly in Adyen's launcher; `App_Api_Toga2::isTransferOrder()` throws if it is missing, so **`library` and `worker` must deploy together** for Adyen.
45
- - **IDs:** `Core.Clients` id **48**; NetSuite customer **31054** ("4950 Adyen").
49
+ - **IDs:** `Core.Clients` id **48**; NetSuite customer **31054** ("4950 Adyen"); Adyen API = `Apis` id 4.
50
+ - **Reclaim SR → Sales Order → NetSuite** (built 2026-09-30, not deployed): same chain as Elite, items from a seeded "Reclaim" bundle because Adyen sends no serials — see [Adyen reclaim flow](./features/service-request-salesorder-netsuite.md).
46
51
  - **`Client_Adyen`** is one of the client DBs with surface-layer / ACL drift noted on the [ACL permission chain](../../2.0/apps/_underscore/features/acl-permission-chain.md) and [surface-layer schema](../../2.0/apps/dbchanges2/features/surface-layer-schema.md) docs.
47
52
 
48
53
  ## Gotchas
@@ -123,5 +128,4 @@ The `AclRecordPermissions` prod-vs-dev-sandbox route-name diff from that session
123
128
 
124
129
  - Formal/legal client name and account owner — recorded here simply as **Adyen**.
125
130
  - Which repos beyond the `apps:` list are genuinely in Adyen's scope.
126
- - Whether Adyen has client-specific models/features that deserve their own docs.
127
131
  - Confirm with Adyen/ops: which FedEx package is outbound vs return; whether SR100024 should be closed; who sets up the NetSuite sync (customer id, Locations, cursors).
@@ -6,8 +6,8 @@ project: _Underscore
6
6
  client: elite
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-09-16
10
- owners: ["bala", "snaredla"]
9
+ updated: 2026-10-01
10
+ owners: ["bala", "snaredla", "rgirish"]
11
11
  files:
12
12
  - _underscore/Model/Elite/SalesOrder.php
13
13
  - _underscore/Model/Elite/ServiceRequest.php
@@ -90,6 +90,8 @@ Every environment points at the **same live NetSuite account 1095849** — there
90
90
  **Do not bulk-delete that range.** Two further NetSuite ids in it — **7369129** and **7374431** — were **not** created by this work. Any cleanup must be row by row. The general rule (testing against beta writes to production NetSuite **and** round-trips back into production Toga data) is recorded in [running worker2 locally](../../../2.0/apps/worker2/workflows/running-worker2-locally.md).
91
91
 
92
92
  ### The SR → SO worker does not set `locationId`, which blocks the push (hand-off)
93
+ > **Update 2026-09-30:** the worker now sets `locationId` from `SALES_ORDER_DEFAULTS_BY_CLIENT` (Elite → `3ca47b91-…`, type drop-ship). Written, **not deployed** — see [SR → SO generation](../../../2.0/apps/worker2/features/service-request-sales-order-generation.md).
94
+
93
95
  prod `SalesOrders` **279** (`SA100000`, from `serviceRequestId 3`, 2026-08-13 11:11) was created by `Sync/ServiceRequest/GenerateSalesOrder` with **`locationId` NULL**, and `buildNetSuiteOrder()` then failed loudly: *"no NetSuite location mapped on line 1 or on the order — map `Locations.c_netsuiteInternalLocationId`"*.
94
96
 
95
97
  **The mapping data is fine.** prod Elite `Locations`: id 1 *Elite* (uuid `3ca47b91-6d05-4e28-9f17-c0b82a6e1345`) → **196**, id 2 *Elite Eagan US HoldCo LLC* → **196**, id 3 *New York* → **5**. That location uuid is **identical in prod and dev-sandbox**, so the same payload value works in both.
@@ -116,6 +118,9 @@ The interceptor → `runTask` → SQS → worker2 chain **is** proven at volume
116
118
  - **api2's `error_log()` output is not in CloudWatch.** worker2 ships `/var/log/php-fpm/www-error.log` to the `worker` log group via a postdeploy hook; **api2 has no equivalent hook**, so the interceptor's silent-skip messages must be pulled from the EB request/full logs.
117
119
  - **Orders that arrived from NetSuite must not be pushed back** — the `c_netsuiteInternalSalesOrderId`-on-payload bail is what prevents an echo loop. Keep it in any new hook on this model.
118
120
 
121
+ ## Change history
122
+ - 2026-09-30 — SR → SO worker now sets the order location (fixes the `locationId` hand-off); not deployed. (rgirish)
123
+
119
124
  ## Related
120
125
  - [Toga → NetSuite Sales-Order Push (shared)](../../../2.0/apps/worker2/features/netsuite-salesorder-outbound-push.md)
121
126
  - [SR → SO generation](../../../2.0/apps/worker2/features/service-request-sales-order-generation.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.923",
3
+ "version": "1.0.924",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",