toga-ai 1.0.730 → 1.0.731

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: Library
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-08-30
9
+ updated: 2026-09-01
10
10
  owners: [dfranks, jcardinal, kyalamarthi]
11
11
  files:
12
12
  - library/app/api/netsuite/rest.php
@@ -169,6 +169,19 @@ Forecast2 tables already store.
169
169
  cross-checking item id, then number each line by the NetSuite `line` (fall back to `linesequencenumber`
170
170
  if the item ids disagree). Self-heals on the next receipt sync (it re-imports the PO). Same "join a
171
171
  NetSuite line on `line`, not `linesequencenumber`" lesson as the `tl.id` semantics above.
172
+ - **⚠ `listPurchaseOrders()` scoped POs by the VENDOR header `entity`, silently dropping every
173
+ drop-ship PO for all clients (fixed 2026-09-01).** It reused the shared `buildEntityInFilter()`
174
+ helper — correct for `listSalesOrders`/`listSales` (SO header `entity` = customer) but **wrong for
175
+ POs**, whose header `entity` is the **vendor**. The `AND entity IN (<customer ids>)` filter matched
176
+ ZERO customer POs, so the header query returned nothing and the section imported nothing — no error.
177
+ Replaced with a new `buildPurchaseOrderCustomerFilter()` that scopes by the customer on the LINES or
178
+ the end-customer body field:
179
+ `AND ( id IN (SELECT tl.transaction FROM transactionline tl WHERE tl.entity IN ($ids) AND tl.mainline = 'F') OR CUSTBODY_END_CUSTOMER IN ($ids) )`.
180
+ `buildEntityInFilter()` is unchanged. This is a **fetch-scope** bug (record never returned by the
181
+ `list*`), a class distinct from the per-id `fetchPurchaseOrderById` shim-shape gaps above — but the
182
+ same underlying trap of reusing a sales-order-shaped assumption on a purchase order. Full
183
+ engine-side impact + the tier-wide per-client backfill live on the
184
+ [per-client-sync doc](../../worker/features/netsuite-togasupply-per-client-sync.md).
172
185
  - **A shim must produce the SOAP object's SHAPE, not just its scalar fields — an absent nested list
173
186
  crashes 1.0 (2026-08-29).** `fetchCustomerById()`'s `addressbookList` was where the AIG lookup phase
174
187
  crashed: a REST customer with no/odd address made the shim omit the nested `addressbook` structure the
@@ -262,6 +275,14 @@ None — uniform across clients (NetSuite is a single shared account).
262
275
 
263
276
  ## Change history
264
277
 
278
+ - 2026-09-01 — Recorded that **`listPurchaseOrders()` scoped POs by the VENDOR header `entity`** (via
279
+ the reused `buildEntityInFilter()`), silently dropping every drop-ship / special-order PO for all
280
+ togasupply clients since the SOAP→REST migration. A PO header `entity` is the vendor, not the
281
+ customer, so `AND entity IN (<customer ids>)` matched zero rows and the section imported nothing with
282
+ no error. Fixed by a new `buildPurchaseOrderCustomerFilter()` that scopes by the line-level customer
283
+ or `CUSTBODY_END_CUSTOMER` body field; `buildEntityInFilter()` untouched. Fetch-scope bug (distinct
284
+ from the per-id shim-shape gaps). Engine impact + per-client backfill on the per-client-sync doc.
285
+ (jcardinal)
265
286
  - 2026-08-30 — Recorded that **`fetchPurchaseOrderById` numbered PO lines by `linesequencenumber` instead
266
287
  of the NetSuite `line`** — a receipt for a post-gap line (PO with a deleted middle line, real lines
267
288
  1,2,4) failed `EV-12` "unable to find a unique match" (GroWrk PO 168223). The PO record line sublist
@@ -389,6 +389,47 @@ Parameters are stored **per client DB** but accessed **through the TOGa2 API**,
389
389
  skipped; and because the location filter is shared, receipts belonging to **other** clients at a
390
390
  shared warehouse are returned and correctly skipped. Modelling receipts as "location-matched"
391
391
  produces wrong diagnostics.
392
+ - **⚠ The PURCHASE_ORDERS section silently dropped EVERY drop-ship / special-order PO on ALL
393
+ togasupply clients — a PO header `entity` is the VENDOR, not the customer (fixed 2026-09-01).**
394
+ `App_Api_Netsuite_Rest::listPurchaseOrders()` (`library/app/api/netsuite/rest.php`) scoped the PO
395
+ HEADER query with `AND entity IN (<client customer ids>)` via the shared `buildEntityInFilter()`
396
+ helper. That helper is correct for **sales** orders (an SO header `entity` IS the customer) but
397
+ **wrong for POs** — a PO header `entity` is the **vendor**. So the customer-id filter matched ZERO
398
+ of a client's POs, the `foreach` never ran, and there was **no interest check, no throw, no log** —
399
+ a pure fetch-scope miss. Proven on Elite PO tranid 128238 (NS internal 5412515, customer 36443,
400
+ vendor TD Synnex 20202): `... AND entity IN (36443)` returned 0 rows; unfiltered returned 1;
401
+ filtering by LINE entity (`transactionline.entity IN (36443)`) returned 1.
402
+ - **Why drop-ship POs vanished ENTIRELY (two-path gap).** Two import paths reach a PO and both
403
+ excluded drop-ship: (a) the dedicated PURCHASE_ORDERS section, via the header-entity filter above;
404
+ (b) the nested `syncItemReceiptFromNetsuite → syncPurchaseOrderFromNetsuite` path (via
405
+ `fetchPurchaseOrderById`, which has **no** entity filter) never fires for a drop-ship PO because a
406
+ drop-ship PO produces item **fulfillments**, not item **receipts**. Stock POs still imported via
407
+ the item-receipt path — which is why Elite had 51 NS POs (all stock, from the 2026-08-12 backfill)
408
+ yet was missing drop-ship ones like 128238. Drop-ship POs carry `customForm.internalId = 148`
409
+ ("ASI - Drop Ship Purchase Order"), the end customer on the transaction LINES
410
+ (`transactionline.entity`) and on `shipTo`, so the dedicated PURCHASE_ORDERS section is their
411
+ **only** import path.
412
+ - **Fix (library only, no SQL, no other repos):** added
413
+ `private static function buildPurchaseOrderCustomerFilter(array $entityInternalIds): string` and
414
+ switched `listPurchaseOrders()` to use it. It scopes POs by the customer on the LINES or the
415
+ end-customer body field instead of the vendor header:
416
+ `AND ( id IN (SELECT tl.transaction FROM transactionline tl WHERE tl.entity IN ($ids) AND tl.mainline = 'F') OR CUSTBODY_END_CUSTOMER IN ($ids) )`.
417
+ `buildEntityInFilter()` is left **untouched** (still correct for `listSalesOrders`/`listSales`/
418
+ `listItemFulfillments`, where header entity = customer). Empty-ids behavior preserved (returns
419
+ `''` = no scoping). Deployed; PO 128238 then imported for Elite (dev confirmed). **PO fetch must
420
+ scope by line-level customer / end-customer, never the vendor header entity — the opposite of the
421
+ sales-order shape, and the reason the reused helper silently lost data.** Cross-ref: the sibling
422
+ per-id path's own shim gaps are in the
423
+ [SuiteQL/REST shim doc](../../library/features/netsuite-suiteql-rest-shim.md).
424
+ - **⚠ Tier-wide backfill owed, per-client.** Because `listPurchaseOrders` is shared, EVERY
425
+ togasupply client has been missing its drop-ship POs since the SOAP→REST migration. Backfill each
426
+ client by rewinding `NETSUITE_LAST_SYNC_DATETIME_PURCHASE_ORDERS` (idempotent) — do it
427
+ **deliberately, one client at a time**, not all at once (Elite's first window 2024-04-15..04-18
428
+ alone held 269 POs).
429
+ - **Diagnostic lesson (durable):** a "silent, no-error, not-imported" record in this engine can be a
430
+ **fetch-scope mismatch** (the record is never returned by `list*`), distinct from a per-record
431
+ throw. The `$isInterested == 1` you see when feeding a single PO directly (or via the diagnose
432
+ script) can disagree with the scheduled section, which never fetched it.
392
433
  - **`Units.locationId` is never populated by this sync — transaction location lives on the item
393
434
  rows.** All 229 `Client_Elite.Units` have `locationId` NULL. `locationId` exists only on `Units`
394
435
  and `InventoryAdjustmentItems` (not on the `*ItemUnits` bridge tables), and
@@ -6,8 +6,8 @@ project: Worker
6
6
  client: elite
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-08-12
10
- owners: ["snaredla"]
9
+ updated: 2026-09-01
10
+ owners: ["snaredla", "jcardinal"]
11
11
  files:
12
12
  - worker/crons/toga2/netsuite/sync_togasupply_elite.php
13
13
  - worker/crons/toga2/netsuite/common_sync_togasupply.php
@@ -125,6 +125,17 @@ the trait declares must exist as a column, a `CustomRecordFields` row **and** an
125
125
 
126
126
  ## Change history
127
127
 
128
+ - 2026-09-01 — **Drop-ship / special-order POs were never importing for Elite** (e.g. PO tranid
129
+ 128238). Root cause was in shared code, not Elite: `listPurchaseOrders()` filtered the PO header by
130
+ the VENDOR `entity` (TD Synnex 20202) instead of the customer, so Elite's 36443-scoped filter matched
131
+ 0 POs, and drop-ship POs — which make item fulfillments, not receipts — had no other import path.
132
+ Fixed engine-side (new `buildPurchaseOrderCustomerFilter()` scoping by line-level customer /
133
+ end-customer); PO 128238 then imported (dev confirmed). Full write-up on the
134
+ [engine doc](../../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md) and
135
+ [SuiteQL/REST shim doc](../../../1.0/apps/library/features/netsuite-suiteql-rest-shim.md). Elite's
136
+ older stock-only PO count (51) will grow once its `NETSUITE_LAST_SYNC_DATETIME_PURCHASE_ORDERS` is
137
+ rewound to backfill drop-ship POs (idempotent; first 2024-04-15..04-18 window alone held 269 POs).
138
+ (jcardinal)
128
139
  - 2026-08-12 — TRUE-80499: **onboarded Elite to the NetSuite → TOGa Supply importer.** Built the
129
140
  wrapper (customer 36443, `isParentCustomer` false, 30-day window, invoices temporarily disabled),
130
141
  the schedule entry, 18 `_Model_Elite_*` netsuite trait models, the `_modules/netsuite` opt-in, and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.730",
3
+ "version": "1.0.731",
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",