toga-ai 1.0.751 → 1.0.753
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.
- package/knowledge/1.0/apps/library/features/netsuite-sync-alert-monitor.md +13 -9
- package/knowledge/1.0/apps/library/features/toga2-api-client-and-bridge.md +8 -3
- package/knowledge/1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md +95 -27
- package/knowledge/clients/elite/features/netsuite-togasupply-sync.md +13 -3
- package/package.json +1 -1
|
@@ -104,18 +104,22 @@ that worker job does the syncing, this library class notices when it stops.
|
|
|
104
104
|
checkpoints mean anything; and a client that is held / never transacts supply (AIG) must be
|
|
105
105
|
left **off** this list or it produces permanent false-positive tickets.
|
|
106
106
|
|
|
107
|
-
- **⚠
|
|
108
|
-
|
|
109
|
-
`NETSUITE_LAST_SYNC_DATETIME_<SECTION>` to
|
|
110
|
-
`"<lastmodifieddate>|<netsuite id>"`). This monitor's
|
|
111
|
-
`DATETIME` keys, so
|
|
112
|
-
reports OK forever — no stale-checkpoint alerts
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
107
|
+
- **⚠ THIS MONITOR IS NOW BLIND — the cursor-rename shipped 2026-09-02 and this monitor was NOT updated.**
|
|
108
|
+
The sync's per-record resume cursor deployed to prod 2026-09-02 (worker `06e8f9ef` + library `3a70b59c`),
|
|
109
|
+
renaming each section's position key from `NETSUITE_LAST_SYNC_DATETIME_<SECTION>` to
|
|
110
|
+
`NETSUITE_LAST_SYNC_CURSOR_<SECTION>` (value now `"<lastmodifieddate>|<netsuite id>"`). This monitor's
|
|
111
|
+
`NETSUITE_SYNC_PARAMETER_KEYS` still lists the old `DATETIME` keys, so its `where..in` `/parameters` read
|
|
112
|
+
now matches **zero rows** and it reports OK forever — **no stale-checkpoint alerts fire**. This was an
|
|
113
|
+
accepted gap at deploy time, but it is now live. **FIX OWED:** update `NETSUITE_SYNC_PARAMETER_KEYS` to the
|
|
114
|
+
`CURSOR` names **and** parse the `<date>|<id>` value (`strtotime` the part before `|`) before the 48h
|
|
115
|
+
compare. See the [per-client sync doc](../../worker/features/netsuite-togasupply-per-client-sync.md).
|
|
116
116
|
|
|
117
117
|
## Change history
|
|
118
118
|
|
|
119
|
+
- 2026-09-02 — **The sync's `NETSUITE_LAST_SYNC_DATETIME_*` → `NETSUITE_LAST_SYNC_CURSOR_*` rename SHIPPED to
|
|
120
|
+
prod (worker `06e8f9ef` + library `3a70b59c`), so this monitor is now BLIND** — its `where..in` read matches
|
|
121
|
+
zero rows and it reports OK forever. Monitor was not updated with the rename (accepted gap, now live). Fix
|
|
122
|
+
owed: update `NETSUITE_SYNC_PARAMETER_KEYS` to the CURSOR names + `strtotime` the part before `|`. (jcardinal)
|
|
119
123
|
- 2026-09-02 — Flagged that the sync's pending `NETSUITE_LAST_SYNC_DATETIME_*` →
|
|
120
124
|
`NETSUITE_LAST_SYNC_CURSOR_*` key rename (BUILT, not yet deployed) will make this monitor match zero rows
|
|
121
125
|
and stop alerting; it was intentionally left un-updated. Update the key list + parse the `<date>|<id>`
|
|
@@ -418,9 +418,14 @@ enable flags** and an optional `$monitorTogadeskDepartmentIds[]`:
|
|
|
418
418
|
still had a RESTRICT child (item receipt `ItemReceiptItems.purchaseOrderItemId`, or an SO/TO link).
|
|
419
419
|
Fixed to **unlink first, then delete**: delete the four PO bridge routes, unhook item receipts with
|
|
420
420
|
`PUT /item-receipt-items/{uuid}` `['purchaseOrderItem' => null]`, then DELETE the PO line kept
|
|
421
|
-
**throwing** as the fail-loud backstop — mirroring the SALES_ORDERS cleanup.
|
|
422
|
-
|
|
423
|
-
`ItemReceiptItems.purchaseOrderItemId`).
|
|
421
|
+
**throwing** as the fail-loud backstop — mirroring the SALES_ORDERS cleanup. **Now committed to
|
|
422
|
+
`_production` and confirmed live on Elite** (carried a schema deploy dependency: nullable
|
|
423
|
+
`ItemReceiptItems.purchaseOrderItemId`). **Follow-up fix same day:** the bridge-clear step's **GET**
|
|
424
|
+
was also a throwing `send()` — a client without an ACL grant on the transfer-order bridge routes
|
|
425
|
+
(Elite, Core records 329/330) got **403 EZ-2** and re-froze; the bridge/link GET loop now passes
|
|
426
|
+
`$throwExceptionOnApiError=false` and **skips** a link table on a no-access response (403 OR EZ-2),
|
|
427
|
+
while any other failure still throws. (Ops note: merging `library` to `_production` does not deploy
|
|
428
|
+
the worker boxes — a worker deploy does.) Full detail, the mirror-NetSuite decision, and the api2
|
|
424
429
|
bridge-route-key mechanic (kebab→camel, no pluralization) live on
|
|
425
430
|
[NetSuite → TOGa Supply per-client sync](../../worker/features/netsuite-togasupply-per-client-sync.md)
|
|
426
431
|
(2026-09-02). (jcardinal)
|
|
@@ -92,12 +92,14 @@ its IDLE write. **There is no timeout, memory cap, or resource limit anywhere in
|
|
|
92
92
|
never *tune the window size down*. (Corollary: a window that has grown back to the cap is positive
|
|
93
93
|
evidence the section is completing.)
|
|
94
94
|
|
|
95
|
-
### Per-record resume cursor — checkpoint after EACH record, not window-end (
|
|
95
|
+
### Per-record resume cursor — checkpoint after EACH record, not window-end (LIVE 2026-09-02)
|
|
96
96
|
|
|
97
97
|
The historical weakness of window-end-only checkpointing: a mid-window crash (poison record, or the
|
|
98
98
|
log-DB doom loop above) re-processes the **whole** window every run — the giant-fulfillment doom loop is a
|
|
99
99
|
direct consequence. This change converts the checkpoint to **per-record keyset (seek) resume** so a
|
|
100
|
-
transient crash resumes at the next record instead of redoing the window.
|
|
100
|
+
transient crash resumes at the next record instead of redoing the window. **Deployed to prod
|
|
101
|
+
2026-09-02** — worker commit `06e8f9ef` + library commit `3a70b59c` (both tips of `_production`), with the
|
|
102
|
+
rename migration run on every client DB first (see the deploy-order gotcha below).
|
|
101
103
|
|
|
102
104
|
- **Key rename per section:** `NETSUITE_LAST_SYNC_DATETIME_<SECTION>` → `NETSUITE_LAST_SYNC_CURSOR_<SECTION>`,
|
|
103
105
|
value format **`"<lastmodifieddate>|<netsuite internal id>"`**. The id breaks same-second ties.
|
|
@@ -109,10 +111,16 @@ transient crash resumes at the next record instead of redoing the window.
|
|
|
109
111
|
already carried it).
|
|
110
112
|
- **Save after EACH main record** (one PUT of `lastmod|id`); on a clean window with 0 records, PUT
|
|
111
113
|
`"<windowTop>|0"` so empty date ranges still advance. No new `try/catch` — stays fail-loud.
|
|
112
|
-
- **Migration is MANDATORY
|
|
114
|
+
- **Migration is MANDATORY, and runs BEFORE the code (see the deploy-order gotcha in Gotchas):**
|
|
113
115
|
`dbchanges2/Client/2026-09-02b - NetsuiteSyncCursorRename.sql` (Client fan-out) renames the keys **and**
|
|
114
|
-
appends `|0` to carry the value across; safe to re-run.
|
|
115
|
-
|
|
116
|
+
appends `|0` to carry the value across; safe to re-run. It was run on all client DBs first, then the
|
|
117
|
+
code deployed — the required order.
|
|
118
|
+
- **⚠ OPEN follow-up — new-client seed was edited in place (dbchanges2 rule violation).** The seed module
|
|
119
|
+
`dbchanges2/_modules/netsuite/2026-08-05 - CLEAN NETSUITE CLINET.SQL` had its 6 seed rows **renamed in
|
|
120
|
+
place** to CURSOR keys with `|0`. That breaks the dbchanges2 "never edit an already-applied file" rule and
|
|
121
|
+
the client-schema drift audit. **Revert that file and add a NEW dated `_modules/netsuite/` file** carrying
|
|
122
|
+
the same guarded `UPDATE ... WHERE key LIKE 'NETSUITE_LAST_SYNC_DATETIME_%'` so future new clients land on
|
|
123
|
+
CURSOR keys. Still uncommitted in the working tree.
|
|
116
124
|
|
|
117
125
|
### ⚠ Per-record isolation is SECTION-SPECIFIC — and SALES_ORDERS has NONE (corrected 2026-08-17)
|
|
118
126
|
|
|
@@ -330,9 +338,11 @@ Per-client `Parameters` table (2.0 client DB, e.g. `Client_Canon.Parameters`; co
|
|
|
330
338
|
|
|
331
339
|
Parameters are stored **per client DB** but accessed **through the TOGa2 API**, not direct SQL.
|
|
332
340
|
|
|
333
|
-
**
|
|
334
|
-
`NETSUITE_LAST_SYNC_CURSOR_<SECTION>` with value `"<lastmodifieddate>|<netsuite internal id>"`
|
|
335
|
-
per-record resume cursor in *How it works*. The `NETSUITE_EXECUTION_MODE_*` keys are
|
|
341
|
+
**Key rename (LIVE 2026-09-02):** the position keys are now
|
|
342
|
+
`NETSUITE_LAST_SYNC_CURSOR_<SECTION>` with value `"<lastmodifieddate>|<netsuite internal id>"` for all 6
|
|
343
|
+
sections — see the per-record resume cursor in *How it works*. The `NETSUITE_EXECUTION_MODE_*` keys are
|
|
344
|
+
unchanged. Renamed on every client DB by `Client/2026-09-02b - NetsuiteSyncCursorRename.sql` (run before
|
|
345
|
+
the code deploy).
|
|
336
346
|
|
|
337
347
|
## Client variations
|
|
338
348
|
|
|
@@ -488,6 +498,19 @@ per-record resume cursor in *How it works*. The `NETSUITE_EXECUTION_MODE_*` keys
|
|
|
488
498
|
`$dateField='lastmodifieddate'` and the engine calls it without the 4th arg. So a backdated
|
|
489
499
|
`trandate` is irrelevant to whether an invoice imports, and a just-modified invoice still won't
|
|
490
500
|
import until it is >24h old (the upper-bound cap).
|
|
501
|
+
- **⚠ DEPLOY ORDER: the cursor-rename migration MUST run on EVERY client DB before the worker/library
|
|
502
|
+
deploy — there is NO old-key fallback in code.** `Client/2026-09-02b - NetsuiteSyncCursorRename.sql`
|
|
503
|
+
renames `NETSUITE_LAST_SYNC_DATETIME_<SECTION>` → `NETSUITE_LAST_SYNC_CURSOR_<SECTION>`. If the code
|
|
504
|
+
ships first, `startModeIteration()` reads a CURSOR key that doesn't exist yet and falls back to
|
|
505
|
+
`MIN_DATETIME_TO_CHECK_FOR_NETSUITE_DATA` (= `'2018-01-01'` for 17 of 18 clients) — re-scanning ~8 years
|
|
506
|
+
across all sections and all clients at once, a thundering herd against NetSuite + api2. Both the php and
|
|
507
|
+
sql reviewers flagged this as critical. The 2026-09-02 deploy followed the correct order (migration on all
|
|
508
|
+
client DBs first, then code). Same rule for any future position-key rename: schema first, code second.
|
|
509
|
+
- **Per-record checkpoint PUT multiplies api2 round-trips on high-count catch-up windows (tuning note, not
|
|
510
|
+
shipped).** The per-record resume cursor does one `App_Api_Toga2::send` PUT per record. On a big backfill
|
|
511
|
+
window (e.g. sales orders / POs) that is one extra api2 round-trip per record. Optional future tuning:
|
|
512
|
+
batch the checkpoint every N records while still writing it **before** the next record's dispatch (so
|
|
513
|
+
crash-resume stays correct). Not built.
|
|
491
514
|
- **A parse error in the shared `library/app/api/netsuite/rest.php` takes down EVERY client at
|
|
492
515
|
once (tier-wide outage).** All wrappers autoload `App_Api_Netsuite_Rest` in the pre-section
|
|
493
516
|
LOOKUPS/setup phase of `common_sync_togasupply.php` (~line 433). Because PHP parses the whole
|
|
@@ -626,8 +649,8 @@ per-record resume cursor in *How it works*. The `NETSUITE_EXECUTION_MODE_*` keys
|
|
|
626
649
|
— for the throwing order.
|
|
627
650
|
|
|
628
651
|
- **⚠ The PURCHASE_ORDERS cleanup DELETE froze the whole section when NetSuite drops a PO line that
|
|
629
|
-
still has an item receipt or SO/TO link (fixed 2026-09-02, `library` only —
|
|
630
|
-
|
|
652
|
+
still has an item receipt or SO/TO link (fixed + DEPLOYED 2026-09-02, `library` only — committed to
|
|
653
|
+
`_production`, confirmed live on Elite).** Same freeze class as the SALES_ORDERS renumber-DELETE above, on a
|
|
631
654
|
different path. `syncPurchaseOrderFromNetsuite`'s cleanup loop ("remove remaining items if
|
|
632
655
|
updating", `library/app/api/toga2.php`) deleted a leftover Toga PO line with a **bare, throwing**
|
|
633
656
|
`send()` DELETE `/purchase-order-items/{uuid}`. When the dropped line still had an item receipt
|
|
@@ -658,6 +681,26 @@ per-record resume cursor in *How it works*. The `NETSUITE_EXECUTION_MODE_*` keys
|
|
|
658
681
|
`dbchanges2/Client/2026-06-19a - TransferOrderSchemaFixes.sql` (section 7, guarded/re-runnable).
|
|
659
682
|
**Confirm that migration has run on every client DB before deploying the code fix**, or the PUT
|
|
660
683
|
`purchaseOrderItem = null` fails.
|
|
684
|
+
- **⚠ Follow-up (deployed same day): the bridge-CLEAR GET must TOLERATE a 403 for a client that
|
|
685
|
+
lacks the bridge (transfer-order routes).** After the first fix deployed, Elite hit a NEW freeze.
|
|
686
|
+
The cleanup **GETs** each of the four bridge routes to find rows to remove, and that GET was a
|
|
687
|
+
**throwing** `send()`. Elite has **no ACL grant** on the transfer-order bridge Core records (**329**
|
|
688
|
+
`purchase-order-items-transfer-order-items`, **330** `transfer-order-items-purchase-order-items`)
|
|
689
|
+
because Elite does not use transfer orders, so `GET /purchase-order-items-transfer-order-items`
|
|
690
|
+
returned **403 EZ-2** ("no permission to read the specified fields", roleIds [1,3]) → the whole
|
|
691
|
+
section aborted, watermark still frozen at 2025-10-13. **Fix:** the bridge/link GET loop now calls
|
|
692
|
+
`App_Api_Toga2::send(..., $throwExceptionOnApiError = false)` and, on a **no-access** response
|
|
693
|
+
(status **403** OR error code **EZ-2**), **skips that link table** — no access means the client
|
|
694
|
+
cannot have rows there, so there is nothing to clear. **Any other failure still throws** (fail
|
|
695
|
+
loud). Covers both transfer-order bridge routes. Confirmed: Elite's PO import unfroze after
|
|
696
|
+
redeploy.
|
|
697
|
+
- **Confirming the new cleanup code is actually live (from `Logs_<Client>.Api`).** A run on the
|
|
698
|
+
**NEW** code shows, before the PO-line delete: `GET /purchase-order-item-sales-order-items`,
|
|
699
|
+
`GET /sales-order-item-purchase-order-items`, `GET /purchase-order-items-transfer-order-items`
|
|
700
|
+
(may be **403 and skipped**), `GET /item-receipt-items`, then `PUT /item-receipt-items/{uuid}`
|
|
701
|
+
(the unhook). The **OLD** code goes straight from the purchase-order PUT to a bare
|
|
702
|
+
`DELETE /purchase-order-items/{uuid}` with none of those. This call pattern is the ground truth
|
|
703
|
+
for whether the worker box is running the fix.
|
|
661
704
|
- **api2 route mechanics (verified against `api2/Component/Api/V2/V2.php`).** For these list/bridge
|
|
662
705
|
routes the response collection key is a **plain kebab→camel conversion of the route, no
|
|
663
706
|
pluralization** (`/purchase-order-item-sales-order-items` → `purchaseOrderItemSalesOrderItems`).
|
|
@@ -672,6 +715,14 @@ per-record resume cursor in *How it works*. The `NETSUITE_EXECUTION_MODE_*` keys
|
|
|
672
715
|
"move the receipt to the surviving line" is **unsafe** — double-count / `UNIQUE(itemReceiptId,
|
|
673
716
|
purchaseOrderItemId)` collision. Only clean cases self-heal; tangled ones must throw for a human.
|
|
674
717
|
|
|
718
|
+
- **⚠ Merging `library` to `_production` on GitHub does NOT update the running worker EC2 boxes — a
|
|
719
|
+
worker DEPLOY does.** After a `library` merge to `_production`, the worker instances keep running the
|
|
720
|
+
**OLD** `library` code across cron cycles until an actual worker deploy pushes it onto the boxes. On
|
|
721
|
+
2026-09-02 the PO-cleanup fix merged at 14:48 but the worker ran old code for multiple 5-min cron
|
|
722
|
+
cycles (the api2 log still showed the old bare DELETE, none of the new calls) until it was deployed.
|
|
723
|
+
**Before concluding a `library` fix "didn't work," confirm the worker box is actually running it** —
|
|
724
|
+
the `Logs_<Client>.Api` call pattern is the ground truth (see the PO-cleanup call-pattern note above).
|
|
725
|
+
|
|
675
726
|
- **⚠ Inventory-adjustment sync: two shared-engine bugs + it is still SOAP (2026-08-28).** Both fixed
|
|
676
727
|
in `syncInventoryAdjustmentFromNetsuite` (`library/app/api/toga2.php`) and reachable by any client
|
|
677
728
|
running inventory adjustments: (a) the stale-item prune deleted an `InventoryAdjustmentItem` before
|
|
@@ -1044,8 +1095,16 @@ per-record resume cursor in *How it works*. The `NETSUITE_EXECUTION_MODE_*` keys
|
|
|
1044
1095
|
|
|
1045
1096
|
## Change history
|
|
1046
1097
|
|
|
1047
|
-
- 2026-09-02 — **Per-record resume cursor
|
|
1048
|
-
|
|
1098
|
+
- 2026-09-02 — **Per-record resume cursor DEPLOYED to prod (shared: `worker` + `library` + `dbchanges2`).**
|
|
1099
|
+
Now live: worker commit `06e8f9ef` + library commit `3a70b59c` (tips of `_production`); rename migration
|
|
1100
|
+
`Client/2026-09-02b - NetsuiteSyncCursorRename.sql` run on all client DBs **before** the code. Recorded the
|
|
1101
|
+
deploy-order rule (no old-key fallback — code-first would fall back to `MIN_DATETIME_TO_CHECK_FOR_NETSUITE_DATA`
|
|
1102
|
+
= `'2018-01-01'` and thundering-herd re-scan ~8 years for all clients; php + sql reviewers flagged it
|
|
1103
|
+
critical). Two open items: (1) the new-client seed `_modules/netsuite/2026-08-05 - CLEAN NETSUITE CLINET.SQL`
|
|
1104
|
+
was edited in place (dbchanges2 rule violation) — revert + add a new dated file; (2) optional tuning — batch
|
|
1105
|
+
the per-record checkpoint PUT every N records on high-count windows. Both still uncommitted. (jcardinal)
|
|
1106
|
+
- 2026-09-02 — **Per-record resume cursor BUILT (shared: `worker` + `library` + `dbchanges2`; shipped later
|
|
1107
|
+
the same day — see entry above).** Converts the section checkpoint from window-end-only to per-record
|
|
1049
1108
|
keyset resume so a mid-window crash (poison record or the log-DB doom loop) resumes at the next record
|
|
1050
1109
|
instead of re-processing the whole window. Position key `NETSUITE_LAST_SYNC_DATETIME_<SECTION>` →
|
|
1051
1110
|
`NETSUITE_LAST_SYNC_CURSOR_<SECTION>`, value `"<lastmodifieddate>|<netsuite id>"` (id breaks same-second
|
|
@@ -1069,21 +1128,30 @@ per-record resume cursor in *How it works*. The `NETSUITE_EXECUTION_MODE_*` keys
|
|
|
1069
1128
|
1048 "quantityAdjustBy cannot be null" on NetSuite serialized-detail sub-lines. The null-skip reached prod
|
|
1070
1129
|
~2026-09-01 afternoon; Issue 542 stopped 09-01 14:08. Recorded that `3-IDLE`-crawl = the same poison-record
|
|
1071
1130
|
shrink as `1-RUNNING`, not a slow section. (jcardinal)
|
|
1072
|
-
- 2026-09-02 — **PURCHASE_ORDERS cleanup DELETE freeze fixed
|
|
1073
|
-
|
|
1074
|
-
cleanup deleted a NetSuite-dropped PO line
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
poison PO re-tried every 5 min). Fix
|
|
1078
|
-
routes, unhook item receipts via
|
|
1079
|
-
DELETE the PO line kept **throwing**
|
|
1080
|
-
NetSuite — remove our line + its links but
|
|
1081
|
-
log-and-skip (a log-and-skip guard was tried this
|
|
1082
|
-
dependency:** `ItemReceiptItems.purchaseOrderItemId`
|
|
1083
|
-
(`dbchanges2/Client/2026-06-19a - TransferOrderSchemaFixes.sql` §7) on every client
|
|
1084
|
-
Recorded the api2 bridge-route key mechanic (plain kebab→camel, no pluralization; PUT writes
|
|
1085
|
-
NULL) and Elite's 78 duplicate PO line-groups (4 with receipts, all tangled → must fail loud).
|
|
1086
|
-
|
|
1131
|
+
- 2026-09-02 — **PURCHASE_ORDERS cleanup DELETE freeze fixed + DEPLOYED, plus a follow-up 403 fix
|
|
1132
|
+
(shared, `library` only — committed to `_production`, confirmed live on Elite).**
|
|
1133
|
+
`syncPurchaseOrderFromNetsuite`'s "remove dropped items" cleanup deleted a NetSuite-dropped PO line
|
|
1134
|
+
with a bare throwing DELETE; a leftover item receipt (`ItemReceiptItems.purchaseOrderItemId`,
|
|
1135
|
+
RESTRICT) or SO/TO link → 1451 → EV-11 → threw → froze the whole PURCHASE_ORDERS section at
|
|
1136
|
+
`1-RUNNING` (Elite watermark stuck 2025-10-13, window ÷3 to 1s, poison PO re-tried every 5 min). Fix
|
|
1137
|
+
mirrors the SALES_ORDERS cleanup: unlink all four PO bridge routes, unhook item receipts via
|
|
1138
|
+
`PUT /item-receipt-items/{uuid}` `purchaseOrderItem = null`, then DELETE the PO line kept **throwing**
|
|
1139
|
+
as the fail-loud backstop. **Decision:** the sync mirrors NetSuite — remove our line + its links but
|
|
1140
|
+
keep the real records on the other side; never log-and-skip (a log-and-skip guard was tried this
|
|
1141
|
+
session and rejected as data damage). **Deploy dependency:** `ItemReceiptItems.purchaseOrderItemId`
|
|
1142
|
+
must be nullable (`dbchanges2/Client/2026-06-19a - TransferOrderSchemaFixes.sql` §7) on every client
|
|
1143
|
+
DB first. Recorded the api2 bridge-route key mechanic (plain kebab→camel, no pluralization; PUT writes
|
|
1144
|
+
SQL NULL) and Elite's 78 duplicate PO line-groups (4 with receipts, all tangled → must fail loud).
|
|
1145
|
+
**Follow-up after deploy:** the cleanup's **bridge-clear GET** was itself a throwing `send()`, and a
|
|
1146
|
+
client without an ACL grant on the transfer-order bridge routes (Elite — no transfer orders — Core
|
|
1147
|
+
records 329/330) got **403 EZ-2** on `GET /purchase-order-items-transfer-order-items` and re-froze;
|
|
1148
|
+
the bridge/link GET loop now passes `$throwExceptionOnApiError=false` and **skips** a link table on a
|
|
1149
|
+
no-access response (403 OR EZ-2), while any other failure still throws. Elite PO import unfroze after
|
|
1150
|
+
redeploy. **Two ops lessons:** (1) merging `library` to `_production` does NOT deploy the worker EC2
|
|
1151
|
+
boxes — only a worker deploy does (old code ran for cron cycles after the 14:48 merge); (2) the
|
|
1152
|
+
`Logs_<Client>.Api` call pattern (bridge GETs + item-receipt unhook PUT vs. a bare PO-line DELETE) is
|
|
1153
|
+
the ground truth for whether the fix is live. No prod data was modified (read-only SELECTs + NetSuite
|
|
1154
|
+
reads). (jcardinal)
|
|
1087
1155
|
- 2026-09-01 — **Customer-PO `dateOrder`/`cost` fixes + NYCHH inventory-adjustment serial-case unfreeze
|
|
1088
1156
|
(shared, `library` only, working-tree — not yet committed/deployed).** `syncSalesOrderFromNetsuite`
|
|
1089
1157
|
now sets the customer PO's `dateOrder` from the SO `tranDate` (was always NULL) and its PO lines'
|
|
@@ -126,8 +126,8 @@ the trait declares must exist as a column, a `CustomRecordFields` row **and** an
|
|
|
126
126
|
## Change history
|
|
127
127
|
|
|
128
128
|
- 2026-09-02 — **Elite PURCHASE_ORDERS froze on a dropped PO line that still had a receipt** (shared
|
|
129
|
-
fix, `library` only —
|
|
130
|
-
case: `NETSUITE_EXECUTION_MODE_PURCHASE_ORDERS='1-RUNNING'`, watermark frozen at **2025-10-13**,
|
|
129
|
+
fix, `library` only — now DEPLOYED to `_production`, PO import confirmed unfrozen on Elite). Elite was
|
|
130
|
+
the confirmed prod case: `NETSUITE_EXECUTION_MODE_PURCHASE_ORDERS='1-RUNNING'`, watermark frozen at **2025-10-13**,
|
|
131
131
|
window collapsed to 1s, same poison PO re-tried every 5 min. Root cause is shared, not Elite —
|
|
132
132
|
`syncPurchaseOrderFromNetsuite`'s cleanup DELETE threw on a leftover item receipt / SO link. Elite
|
|
133
133
|
data behind it: **78 duplicate `(purchaseOrder, vendorItem)` PO line-groups**, only **4** carry a
|
|
@@ -138,7 +138,17 @@ the trait declares must exist as a column, a `CustomRecordFields` row **and** an
|
|
|
138
138
|
(double-count / `UNIQUE(itemReceiptId, purchaseOrderItemId)` collision) — tangled cases fail loud
|
|
139
139
|
for a human, only clean cases self-heal. **Before deploy:** confirm
|
|
140
140
|
`ItemReceiptItems.purchaseOrderItemId` is nullable on `Client_Elite`
|
|
141
|
-
(`dbchanges2/Client/2026-06-19a - TransferOrderSchemaFixes.sql` §7).
|
|
141
|
+
(`dbchanges2/Client/2026-06-19a - TransferOrderSchemaFixes.sql` §7). **Follow-up after deploy:** a
|
|
142
|
+
second freeze appeared — the cleanup's **bridge-clear GET** was a throwing `send()`, and Elite has
|
|
143
|
+
**no ACL grant** on the transfer-order bridge Core records (**329**
|
|
144
|
+
`purchase-order-items-transfer-order-items`, **330** `transfer-order-items-purchase-order-items`)
|
|
145
|
+
because Elite doesn't use transfer orders, so `GET /purchase-order-items-transfer-order-items`
|
|
146
|
+
returned **403 EZ-2** (roleIds [1,3]) and aborted the section. Fixed by making the bridge GET loop
|
|
147
|
+
tolerate a no-access response (`$throwExceptionOnApiError=false`, skip that link table on 403/EZ-2,
|
|
148
|
+
else throw); Elite unfroze after redeploy. Note: merging `library` to `_production` did **not**
|
|
149
|
+
deploy the worker boxes — an actual worker deploy was required, and the `Logs_Elite.Api` call
|
|
150
|
+
pattern (bridge GETs + item-receipt unhook PUT vs. a bare PO-line DELETE) is how you confirm the fix
|
|
151
|
+
is live. Full mechanics on the
|
|
142
152
|
[engine doc](../../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md). (jcardinal)
|
|
143
153
|
- 2026-09-01 — **Drop-ship / special-order POs were never importing for Elite** (e.g. PO tranid
|
|
144
154
|
128238). Root cause was in shared code, not Elite: `listPurchaseOrders()` filtered the PO header by
|
package/package.json
CHANGED