toga-ai 1.0.750 → 1.0.752
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,8 +6,8 @@ project: Library
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: ["bala"]
|
|
9
|
+
updated: 2026-09-02
|
|
10
|
+
owners: ["bala", "jcardinal"]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/systemmonitor/netsuiteintegration.php
|
|
13
13
|
- worker/crons/infrastructure/system_monitors.php
|
|
@@ -104,8 +104,26 @@ 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
|
+
- **⚠ 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
|
+
|
|
107
117
|
## Change history
|
|
108
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)
|
|
123
|
+
- 2026-09-02 — Flagged that the sync's pending `NETSUITE_LAST_SYNC_DATETIME_*` →
|
|
124
|
+
`NETSUITE_LAST_SYNC_CURSOR_*` key rename (BUILT, not yet deployed) will make this monitor match zero rows
|
|
125
|
+
and stop alerting; it was intentionally left un-updated. Update the key list + parse the `<date>|<id>`
|
|
126
|
+
value when the rename ships. (jcardinal)
|
|
109
127
|
- 2026-07-10 — **Fixed the monitor timing out and silently not running since 2026-06-29.**
|
|
110
128
|
Rewrote `getClientStatus()` to read all six checkpoints per client in one TOGa2
|
|
111
129
|
`where..in` `/parameters` call (was 84 serialized calls ≈ 230s, over the 300s
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
| [Forecast2 ↔ NetSuite Reconciliation & Trueup Tooling](features/forecast2-netsuite-reconciliation.md) | CLI tools to **audit** and **repair** drift between the production `Forecast` DB (core2) and NetSuite. | test/@dave/checker.php, worker2/Component/Forecast/SaleImport/SaleImport.php, test/@dave/looper.php, test/@dave/reconcile_netsuite_totals.php, test/@dave/fixer.php, tools/bin/forecast/fixer.php, tools/bin/forecast/checker.php, test/@dave/analyze_netsuite_forecast_diff.php, test/@dave/trueup_sales.php, test/@dave/reconcile_drift_2023plus.php, test/@dave/probe_invoice_gap_2026.php, test/@dave/probe_creditmemo_gap_detail.php, test/@dave/trueup_open_orders.php, test/@dave/loop_trueup_open_orders.php, test/@dave/trueup_opportunities.php, test/@dave/probe_sales_gap_direct.php, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, test/@dave/probe_drift_so_dates.php, test/@dave/probe_profit_invoices.php, test/@dave/probe_profit_gap.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php, worker/crons/toga2/forecast2/periodic_forecast_discrepancy_fix_open_orders.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/schedules/cron.worker.infrastructure.json |
|
|
11
11
|
| [Forecast2 Supporting-Records Nightly Import (1.0 cron) — Customers & ConsolidatedCustomers](features/forecast2-supporting-records-import.md) | The nightly **1.0** pull that keeps the Forecast2 lookup/dimension tables (`Forecast.Accounts`, `Classifications`, `Customers`, `ConsolidatedCustomers`, `Employ | worker/crons/toga2/forecast2/import_supporting_records.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php, worker/schedules/cron.worker.infrastructure.json, worker/.ebextensions/009_setup_phpini.config, worker/crons/sync/netsuite/netsuite_customers.php, library/app/api/netsuite/rest.php, library/app/model/forecast2/customer.php, library/app/model/forecast2/consolidatedcustomer.php |
|
|
12
12
|
| [NetSuite Sales Order Sales Rep Sourcing (Staples & ODP EDI orders)](features/netsuite-sales-order-sales-rep-sourcing.md) | How the **sales rep** on a NetSuite Sales Order is determined for the two 1.0 `worker` EDI order-creation integrations (Staples cXML and Compass/ODP EDI). | worker/crons/toga2/compass/workflow/5_create_netsuite_sales_orders_from_office_depot_purchase_orders.php, worker/crons/sync/staples/sync_staples_cxml.php, test/@Mark/NetSuite/TRUE_80451_customer_salesrep_diag.php |
|
|
13
|
-
| [NetSuite → TOGa Supply Per-Client Sync (thin wrappers)](features/netsuite-togasupply-per-client-sync.md) | Syncs NetSuite transactions (sales orders, purchase orders, invoices, item receipts, item fulfillments, inventory adjustments) into each TOGa Supply (2.0) clien | worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/crons/toga2/netsuite/sync_togasupply_canon.php, worker/crons/toga2/netsuite/sync_togasupply_elite.php, worker/schedules/cron.worker.sync.json, dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql, library/app/api/toga2.php, library/app/api/netsuite/rest.php, library/app/framework.php, library/app/systemmonitor/netsuiteintegration.php, test/@srija/Elite Testing/Service Requests/test_sync_togasupply_elite_section.php, test/@srija/Elite Testing/Service Requests/test_diagnose_togasupply_elite.php |
|
|
13
|
+
| [NetSuite → TOGa Supply Per-Client Sync (thin wrappers)](features/netsuite-togasupply-per-client-sync.md) | Syncs NetSuite transactions (sales orders, purchase orders, invoices, item receipts, item fulfillments, inventory adjustments) into each TOGa Supply (2.0) clien | worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/crons/toga2/netsuite/sync_togasupply_canon.php, worker/crons/toga2/netsuite/sync_togasupply_elite.php, worker/schedules/cron.worker.sync.json, dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql, library/app/api/toga2.php, library/app/api/netsuite/rest.php, library/app/query.php, library/app/database.php, library/app/framework.php, library/app/systemmonitor/netsuiteintegration.php, dbchanges2/Client/2026-09-02b - NetsuiteSyncCursorRename.sql, test/@srija/Elite Testing/Service Requests/test_sync_togasupply_elite_section.php, test/@srija/Elite Testing/Service Requests/test_diagnose_togasupply_elite.php |
|
|
14
14
|
| [OneUptime Server monitor + disk/memory hygiene on the 1.0 worker EB host](features/oneuptime-server-monitor-host-hygiene.md) | The 1.0 `agilant-worker` EB environment runs on the **legacy Amazon Linux 1 PHP 7.2 platform** (Apache httpd/prefork, s3fs mounts, cron) and repeatedly went dow | worker/.ebextensions/040_disk_memory_hygiene.config, worker/.ebextensions/045_oneuptime_agent.config, worker/ebs/cron.worker.php, worker/ebs/mount-s3fs-folders.php, worker/ebs/apache_settings.php, worker/ebs/setup_phpini.php |
|
|
15
15
|
| [OneUptime external uptime monitoring for 1.0 workers](features/oneuptime-worker-uptime-monitoring.md) | Every 1.0 worker box self-reports its liveness to an external OneUptime monitor once per minute by curl-POSTing to a per-worker "Incoming Request" heartbeat URL | library/app/worker.php, worker/crons/worker/worker_heartbeat.php, worker/ebs/cron.worker.php, worker/.ebextensions/045_oneuptime_agent.config |
|
|
16
16
|
| [Prudential: Send Shipments for the Day report (daily cron)](features/send-shipments-for-the-day.md) | Daily cron (9:00 PM) that emails Prudential and Dell stakeholders an Excel report of all devices shipped that day, including tracking number, serial number, emp | worker/crons/notifications/reports/send_shipments_for_the_day.php |
|
|
@@ -16,8 +16,11 @@ files:
|
|
|
16
16
|
- dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql
|
|
17
17
|
- library/app/api/toga2.php
|
|
18
18
|
- library/app/api/netsuite/rest.php
|
|
19
|
+
- library/app/query.php
|
|
20
|
+
- library/app/database.php
|
|
19
21
|
- library/app/framework.php
|
|
20
22
|
- library/app/systemmonitor/netsuiteintegration.php
|
|
23
|
+
- dbchanges2/Client/2026-09-02b - NetsuiteSyncCursorRename.sql
|
|
21
24
|
- test/@srija/Elite Testing/Service Requests/test_sync_togasupply_elite_section.php
|
|
22
25
|
- test/@srija/Elite Testing/Service Requests/test_diagnose_togasupply_elite.php
|
|
23
26
|
related:
|
|
@@ -89,6 +92,36 @@ its IDLE write. **There is no timeout, memory cap, or resource limit anywhere in
|
|
|
89
92
|
never *tune the window size down*. (Corollary: a window that has grown back to the cap is positive
|
|
90
93
|
evidence the section is completing.)
|
|
91
94
|
|
|
95
|
+
### Per-record resume cursor — checkpoint after EACH record, not window-end (LIVE 2026-09-02)
|
|
96
|
+
|
|
97
|
+
The historical weakness of window-end-only checkpointing: a mid-window crash (poison record, or the
|
|
98
|
+
log-DB doom loop above) re-processes the **whole** window every run — the giant-fulfillment doom loop is a
|
|
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. **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).
|
|
103
|
+
|
|
104
|
+
- **Key rename per section:** `NETSUITE_LAST_SYNC_DATETIME_<SECTION>` → `NETSUITE_LAST_SYNC_CURSOR_<SECTION>`,
|
|
105
|
+
value format **`"<lastmodifieddate>|<netsuite internal id>"`**. The id breaks same-second ties.
|
|
106
|
+
- **Fetch change (`library/app/api/netsuite/rest.php`, all 6 header queries):** add
|
|
107
|
+
`ORDER BY lastmodifieddate, id` and change the lower bound to
|
|
108
|
+
`lastmodifieddate > :t OR (lastmodifieddate = :t AND id > :fromId)` (upper bound `<= :to` kept). A new
|
|
109
|
+
`$fromId` param was threaded into the 6 list funcs. Added `lastModifiedDate` to the fetched record object
|
|
110
|
+
for **PURCHASE_ORDERS, ITEM_RECEIPTS, ITEM_FULFILLMENTS, INVENTORY_ADJUSTMENTS** (SALES_ORDERS + INVOICES
|
|
111
|
+
already carried it).
|
|
112
|
+
- **Save after EACH main record** (one PUT of `lastmod|id`); on a clean window with 0 records, PUT
|
|
113
|
+
`"<windowTop>|0"` so empty date ranges still advance. No new `try/catch` — stays fail-loud.
|
|
114
|
+
- **Migration is MANDATORY, and runs BEFORE the code (see the deploy-order gotcha in Gotchas):**
|
|
115
|
+
`dbchanges2/Client/2026-09-02b - NetsuiteSyncCursorRename.sql` (Client fan-out) renames the keys **and**
|
|
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.
|
|
124
|
+
|
|
92
125
|
### ⚠ Per-record isolation is SECTION-SPECIFIC — and SALES_ORDERS has NONE (corrected 2026-08-17)
|
|
93
126
|
|
|
94
127
|
Whether a bad record is "stepped over" or freezes the section depends entirely on whether **that
|
|
@@ -305,6 +338,12 @@ Per-client `Parameters` table (2.0 client DB, e.g. `Client_Canon.Parameters`; co
|
|
|
305
338
|
|
|
306
339
|
Parameters are stored **per client DB** but accessed **through the TOGa2 API**, not direct SQL.
|
|
307
340
|
|
|
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).
|
|
346
|
+
|
|
308
347
|
## Client variations
|
|
309
348
|
|
|
310
349
|
- **Transfer-order routing is opt-in per wrapper (default OFF).** A client whose NetSuite
|
|
@@ -459,6 +498,19 @@ Parameters are stored **per client DB** but accessed **through the TOGa2 API**,
|
|
|
459
498
|
`$dateField='lastmodifieddate'` and the engine calls it without the 4th arg. So a backdated
|
|
460
499
|
`trandate` is irrelevant to whether an invoice imports, and a just-modified invoice still won't
|
|
461
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.
|
|
462
514
|
- **A parse error in the shared `library/app/api/netsuite/rest.php` takes down EVERY client at
|
|
463
515
|
once (tier-wide outage).** All wrappers autoload `App_Api_Netsuite_Rest` in the pre-section
|
|
464
516
|
LOOKUPS/setup phase of `common_sync_togasupply.php` (~line 433). Because PHP parses the whole
|
|
@@ -856,6 +908,15 @@ Parameters are stored **per client DB** but accessed **through the TOGa2 API**,
|
|
|
856
908
|
line). **Fix (`library`):** skip lines whose `adjustQtyBy` is null in **BOTH** passes over
|
|
857
909
|
`inventoryList->inventory` (the item-build pass and the serial/unit pass). Shared fix; unblocked NYCHH
|
|
858
910
|
inventory adjustments.
|
|
911
|
+
- **Prod-confirmed 2026-09-02** (commit `8bae8a2a`, 2026-08-29; the null-skip lands at
|
|
912
|
+
`toga2.php:5728` and `:5879`). Before it, NYCHH `INVENTORY_ADJUSTMENTS` was frozen `3-IDLE` (window
|
|
913
|
+
collapsed to ~3s), watermark stuck at **2022-12-28**, advancing only seconds/run — `Logs.Issue`
|
|
914
|
+
**542** (`clientId 3`), 35 occurrences 2026-08-28..09-01. The fix reached prod ~2026-09-01 afternoon
|
|
915
|
+
and **Issue 542 stopped 09-01 14:08**. This `3-IDLE`-crawl-with-frozen-watermark signature is the same
|
|
916
|
+
poison-record shrink as a `1-RUNNING` freeze — a section that throws on a record too old to fall
|
|
917
|
+
outside the collapsed window keeps re-throwing; once the window shrinks past that record's timestamp it
|
|
918
|
+
reports IDLE but crawls seconds/run and can never advance past it. Read it as "still throwing," not
|
|
919
|
+
"small/slow section" (see the item-fulfillment doom-loop gotcha below for the same signature).
|
|
859
920
|
- **⚠ Item-receipt sync froze on a PARTIAL receipt's NULL-quantity PO lines — the item-receipt TWIN of the
|
|
860
921
|
inventory-adjustment NULL-quantity fix above (fixed 2026-08-31).** For a partial item receipt NetSuite
|
|
861
922
|
returns **all** the PO's linked lines but sets `quantity=null` on the lines **not received on this
|
|
@@ -877,14 +938,33 @@ Parameters are stored **per client DB** but accessed **through the TOGa2 API**,
|
|
|
877
938
|
`transactionId` (`Logs.Event` carries `clientId`, `Logs.Issue` does not) → `Logs_Nychh.Api` on that
|
|
878
939
|
`transactionId` for the untruncated `EV-10`/1048 rejection and the payload showing the `quantity:null`
|
|
879
940
|
lines.
|
|
880
|
-
-
|
|
881
|
-
`NETSUITE_EXECUTION_MODE_ITEM_FULFILLMENTS='3-IDLE'` with the window
|
|
882
|
-
throwing recently) but currently **IDLE** — so it crawls a few seconds per run rather
|
|
883
|
-
hard `1-RUNNING` freeze.
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
941
|
+
- **NYCHH ITEM_FULFILLMENTS watermark frozen at 2021-12-29 (found 2026-08-31; ROOT-CAUSED 2026-09-02 —
|
|
942
|
+
the log-DB doom loop below).** `NETSUITE_EXECUTION_MODE_ITEM_FULFILLMENTS='3-IDLE'` with the window
|
|
943
|
+
collapsed to ~3s (throwing recently) but currently **IDLE** — so it crawls a few seconds per run rather
|
|
944
|
+
than a hard `1-RUNNING` freeze. This is the exact signature of the doom loop in the next gotcha: a giant
|
|
945
|
+
fulfillment throws every run, the window shrinks ÷3 until it no longer spans that fulfillment, then goes
|
|
946
|
+
IDLE but can never advance past it. Context: the 2026-08-31 full 19-client togasupply prod sweep found
|
|
947
|
+
all other clients healthy (`864000-IDLE`) except Elite SALES_ORDERS `864000-RUNNING` (healthy mid-run,
|
|
948
|
+
large window, not stuck) and AIG SALES_ORDERS `8-RUNNING` (the known held account).
|
|
949
|
+
- **⚠ Item-fulfillment DOOM LOOP from a dropped LOG-DB connection during a giant fulfillment (found
|
|
950
|
+
2026-09-02, NOT yet fixed — the per-record cursor below is the mitigation).** A single very large
|
|
951
|
+
fulfillment POSTs thousands of `/item-fulfillment-item-units` over api2 HTTP; that long unit-POST phase
|
|
952
|
+
lets the **worker's log-DB connection idle past MySQL `wait_timeout`**. The **next** fulfillment's refetch
|
|
953
|
+
— `fetchItemFulfillmentById` at `library/app/api/toga2.php:4440`, the **first line** of
|
|
954
|
+
`syncItemFulfillmentFromNetsuite` — tries to log that NetSuite REST call and dies at
|
|
955
|
+
`library/app/query.php:168` `mysqli_select_db` *"MySQL server has gone away / Connection timed out"*
|
|
956
|
+
(`Logs.Issue` **589**). The 1.0 framework escalates that warning to `exit`, so the run aborts mid-window;
|
|
957
|
+
the watermark only advances at window end (`finishModeIteration`), so the whole window — including the
|
|
958
|
+
giant fulfillment — **re-processes next run, times out at the same spot, and the window shrinks ÷3 each
|
|
959
|
+
run** (→ the `3-IDLE` crawl signature above once the window no longer spans the giant fulfillment).
|
|
960
|
+
- **Root of the crash:** the "gone away" recovery in `App_Query::execute` (`library/app/query.php`,
|
|
961
|
+
`database.php:431`) only guards the `mysqli_query` call (errno **2006** reconnect), **NOT** the
|
|
962
|
+
`mysqli_select_db` that runs before it — which is the first touch of the stale connection, so the
|
|
963
|
+
reconnect never gets a chance. A durable framework gap, not sync-specific.
|
|
964
|
+
- **Durable engine rule reconfirmed:** a shrinking window in this sync **ALWAYS** means a thrown
|
|
965
|
+
exception — there is **no** volume/time/memory backoff anywhere in the script. "Window not 864000" ⇒
|
|
966
|
+
something threw. The log-connection idle-out is a concrete cause to add to the poison-record and
|
|
967
|
+
shim-shape causes above.
|
|
888
968
|
- **⚠ Item-fulfillment stale-item prune must delete tracking-number children before the unit AND the item
|
|
889
969
|
(fixed 2026-08-30).** In `syncItemFulfillmentFromNetsuite`'s cleanup loop, deleting a stale
|
|
890
970
|
item-fulfillment-item-**unit** failed api2 `EV-11` / MySQL **1451** (FK
|
|
@@ -967,8 +1047,59 @@ Parameters are stored **per client DB** but accessed **through the TOGa2 API**,
|
|
|
967
1047
|
A "silent freeze" here is a serial/data collision, not necessarily a catalog/item-matching problem —
|
|
968
1048
|
read the untruncated Api-log rejection before assuming item matching.
|
|
969
1049
|
|
|
1050
|
+
- **⚠ The position key defaults to the EARLIEST date when MISSING — a code-only cursor rename WITHOUT
|
|
1051
|
+
the data migration resets every client to re-sync from scratch (found 2026-09-02).** When the section's
|
|
1052
|
+
position key is absent, the engine falls back to `MIN_DATETIME_TO_CHECK_FOR_NETSUITE_DATA`
|
|
1053
|
+
(`common_sync_togasupply.php` ~L573). So shipping the `NETSUITE_LAST_SYNC_DATETIME_*` →
|
|
1054
|
+
`NETSUITE_LAST_SYNC_CURSOR_*` rename in **code only** makes every client's old key invisible → each
|
|
1055
|
+
section restarts from the earliest date and re-syncs its entire history. The migration
|
|
1056
|
+
(`dbchanges2/Client/2026-09-02b - NetsuiteSyncCursorRename.sql`, Client fan-out) **must carry the value
|
|
1057
|
+
across** — it renames the key and appends `|0` in one `UPDATE ... REPLACE(...)`, re-runnable. **Ship the
|
|
1058
|
+
migration on every client DB before (or with) the code.**
|
|
1059
|
+
- **⚠ The NetSuite Sync Alert monitor will SILENTLY STOP after the cursor rename (known, accepted
|
|
1060
|
+
2026-09-02).** `App_SystemMonitor_NetSuiteIntegration` (`library/app/systemmonitor/netsuiteintegration.php`)
|
|
1061
|
+
hardcodes the old `NETSUITE_LAST_SYNC_DATETIME_*` keys, so once the rename ships its `where..in`
|
|
1062
|
+
`/parameters` read matches **zero rows** and it stops alerting on stale checkpoints. The developer
|
|
1063
|
+
**intentionally left the monitor un-updated** this session — a documented, accepted gap. Update the
|
|
1064
|
+
monitor's key list to `NETSUITE_LAST_SYNC_CURSOR_*` (and parse the `<date>|<id>` value) when the rename
|
|
1065
|
+
deploys, or NetSuite sync stalls go unmonitored. Cross-ref:
|
|
1066
|
+
[NetSuite Sync Alert Monitor](../../library/features/netsuite-sync-alert-monitor.md).
|
|
1067
|
+
|
|
970
1068
|
## Change history
|
|
971
1069
|
|
|
1070
|
+
- 2026-09-02 — **Per-record resume cursor DEPLOYED to prod (shared: `worker` + `library` + `dbchanges2`).**
|
|
1071
|
+
Now live: worker commit `06e8f9ef` + library commit `3a70b59c` (tips of `_production`); rename migration
|
|
1072
|
+
`Client/2026-09-02b - NetsuiteSyncCursorRename.sql` run on all client DBs **before** the code. Recorded the
|
|
1073
|
+
deploy-order rule (no old-key fallback — code-first would fall back to `MIN_DATETIME_TO_CHECK_FOR_NETSUITE_DATA`
|
|
1074
|
+
= `'2018-01-01'` and thundering-herd re-scan ~8 years for all clients; php + sql reviewers flagged it
|
|
1075
|
+
critical). Two open items: (1) the new-client seed `_modules/netsuite/2026-08-05 - CLEAN NETSUITE CLINET.SQL`
|
|
1076
|
+
was edited in place (dbchanges2 rule violation) — revert + add a new dated file; (2) optional tuning — batch
|
|
1077
|
+
the per-record checkpoint PUT every N records on high-count windows. Both still uncommitted. (jcardinal)
|
|
1078
|
+
- 2026-09-02 — **Per-record resume cursor BUILT (shared: `worker` + `library` + `dbchanges2`; shipped later
|
|
1079
|
+
the same day — see entry above).** Converts the section checkpoint from window-end-only to per-record
|
|
1080
|
+
keyset resume so a mid-window crash (poison record or the log-DB doom loop) resumes at the next record
|
|
1081
|
+
instead of re-processing the whole window. Position key `NETSUITE_LAST_SYNC_DATETIME_<SECTION>` →
|
|
1082
|
+
`NETSUITE_LAST_SYNC_CURSOR_<SECTION>`, value `"<lastmodifieddate>|<netsuite id>"` (id breaks same-second
|
|
1083
|
+
ties); `rest.php`'s 6 header queries gain `ORDER BY lastmodifieddate, id` + a keyset lower bound + a
|
|
1084
|
+
`$fromId` param; cursor PUT after each record (and `"<windowTop>|0"` on an empty window); `lastModifiedDate`
|
|
1085
|
+
added to the PO/IR/IF/InvAdj fetched objects. Migration `Client/2026-09-02b - NetsuiteSyncCursorRename.sql`
|
|
1086
|
+
(fan-out) renames the keys and appends `|0` — **mandatory**, because a missing key defaults to the earliest
|
|
1087
|
+
date and would re-sync every client's full history. Monitor `netsuiteintegration.php` intentionally left on
|
|
1088
|
+
the old DATETIME keys (will stop alerting after the rename — accepted). (jcardinal)
|
|
1089
|
+
- 2026-09-02 — **Item-fulfillment doom loop root-caused (found, NOT fixed — the cursor above mitigates).** A
|
|
1090
|
+
giant fulfillment's long unit-POST phase idles the worker's log-DB connection past MySQL `wait_timeout`; the
|
|
1091
|
+
next fulfillment's `fetchItemFulfillmentById` (`toga2.php:4440`) dies logging its NetSuite call at
|
|
1092
|
+
`query.php:168` `mysqli_select_db` "server has gone away" (`Logs.Issue 589`) → 1.0 warning→`exit` → window
|
|
1093
|
+
re-processes and shrinks ÷3 each run (the `3-IDLE` crawl). Root cause: `App_Query::execute`'s gone-away
|
|
1094
|
+
recovery (`query.php`/`database.php:431`) guards only the `mysqli_query` (2006 reconnect), not the
|
|
1095
|
+
`mysqli_select_db` before it. Explains the NYCHH ITEM_FULFILLMENTS freeze at 2021-12-29. Reconfirmed the
|
|
1096
|
+
engine rule: a shrinking window ALWAYS means a throw (no volume/time/memory backoff exists). (jcardinal)
|
|
1097
|
+
- 2026-09-02 — **Prod-confirmed the NYCHH inventory-adjustment NULL-`adjustQtyBy` freeze + its fix (commit
|
|
1098
|
+
`8bae8a2a`, `toga2.php:5728`/`:5879`).** `INVENTORY_ADJUSTMENTS` had been frozen `3-IDLE`, watermark stuck
|
|
1099
|
+
2022-12-28, crawling seconds/run — `Logs.Issue 542` (`clientId 3`), 35 occ 2026-08-28..09-01, EV-10/MySQL
|
|
1100
|
+
1048 "quantityAdjustBy cannot be null" on NetSuite serialized-detail sub-lines. The null-skip reached prod
|
|
1101
|
+
~2026-09-01 afternoon; Issue 542 stopped 09-01 14:08. Recorded that `3-IDLE`-crawl = the same poison-record
|
|
1102
|
+
shrink as `1-RUNNING`, not a slow section. (jcardinal)
|
|
972
1103
|
- 2026-09-02 — **PURCHASE_ORDERS cleanup DELETE freeze fixed (shared, `library` only — WRITTEN +
|
|
973
1104
|
REVIEWED, NOT yet committed/deployed).** `syncPurchaseOrderFromNetsuite`'s "remove dropped items"
|
|
974
1105
|
cleanup deleted a NetSuite-dropped PO line with a bare throwing DELETE; a leftover item receipt
|
package/package.json
CHANGED