toga-ai 1.0.759 → 1.0.761
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/worker/features/netsuite-togasupply-per-client-sync.md +46 -0
- package/knowledge/2.0/apps/_underscore/features/tableview-joins.md +39 -1
- package/knowledge/2.0/apps/toga25-supply/features/record-modals-and-nested-tables.md +20 -2
- package/knowledge/clients/nychh/profile.md +22 -5
- package/package.json +1 -1
|
@@ -202,6 +202,43 @@ Prudential-EDI, S&P Global**.
|
|
|
202
202
|
- **Lesson:** any `sync*FromNetsuite()` that takes the record `&$byReference` and refetches must have
|
|
203
203
|
its fetch-by-id shim carry **every** field the caller reads afterward — `lastModifiedDate` first.
|
|
204
204
|
|
|
205
|
+
### ⚠ …but the by-reference shim wrote the cursor on the WRONG CLOCK → ITEM_RECEIPTS + ITEM_FULFILLMENTS re-matched forever (found + fixed 2026-09-03, NYCHH prod)
|
|
206
|
+
|
|
207
|
+
A follow-on to the by-reference fix directly above. Once the two shims carried a `lastModifiedDate`
|
|
208
|
+
again, the sections still could **not advance** — the value was in the **wrong timezone**, so
|
|
209
|
+
`lastmodifieddate > cursor` re-matched the **same record every run** (an infinite loop, no crash).
|
|
210
|
+
|
|
211
|
+
- **Root cause — two clocks.** The prior fix set `fetchItemFulfillmentById` / `fetchItemReceiptById`'s
|
|
212
|
+
`->lastModifiedDate` from the **REST record GET**, which returns **UTC** (e.g. `2024-08-06T14:45:00Z`).
|
|
213
|
+
`common_sync_togasupply.php` then `date()`-formats it in the script timezone **America/Chicago** →
|
|
214
|
+
`2024-08-06 09:45:00`, and stores that as the cursor. But the LIST fetch's `WHERE`
|
|
215
|
+
(`App_Api_Netsuite_Rest::listItem*`) compares `lastmodifieddate` via SuiteQL
|
|
216
|
+
`TO_CHAR(lastmodifieddate,'YYYY-MM-DD HH24:MI:SS')`, which is the **NetSuite account timezone =
|
|
217
|
+
Eastern** → `2024-08-06 10:45:16`. So the cursor was written **~1 hour behind** the clock the fetch
|
|
218
|
+
reads → `lastmodifieddate > cursor` was always still true for the record just processed → it was
|
|
219
|
+
re-fetched and re-processed forever and the watermark never moved.
|
|
220
|
+
- **Proven on NYCHH prod** (item receipt internalId `4953915`): REST GET `lastModifiedDate`
|
|
221
|
+
`2024-08-06T14:45:00Z` (UTC) · SuiteQL `2024-08-06 10:45:16` (Eastern) · stored cursor
|
|
222
|
+
`2024-08-06 09:45:00` (Chicago). ITEM_RECEIPTS was jammed on it; because that heavy receipt (nested
|
|
223
|
+
PO→SO reconcile) runs **before** fulfillments and starved the run, ITEM_FULFILLMENTS' window kept
|
|
224
|
+
collapsing to 6s and never grew to clear the empty gap to its first real record (2025-01-06; 1,294
|
|
225
|
+
NYCHH fulfillments waited after 2025-01-01).
|
|
226
|
+
- **INVENTORY_ADJUSTMENTS was NOT stuck** — `fetchInventoryAdjustmentById` already reads
|
|
227
|
+
`lastmodifieddate` from SuiteQL `TO_CHAR` (the right clock), so it advanced normally. **That is the
|
|
228
|
+
correct pattern**; the other two shims did not follow it.
|
|
229
|
+
- **Fix (library only, `php -l` clean, fail-loud preserved):** in `library/app/api/netsuite/rest.php`,
|
|
230
|
+
`fetchItemFulfillmentById` (~L2093) and `fetchItemReceiptById` (~L2254) now set
|
|
231
|
+
`$shim->lastModifiedDate` from a SuiteQL
|
|
232
|
+
`SELECT TO_CHAR(lastmodifieddate,'YYYY-MM-DD HH24:MI:SS') AS lastmodifieddate FROM transaction WHERE id = $id`
|
|
233
|
+
(Eastern) — matching `fetchInventoryAdjustmentById` — **instead of** the REST header's UTC
|
|
234
|
+
`$header->lastModifiedDate`. No shim reads the REST/UTC `lastModifiedDate` anymore. No worker change,
|
|
235
|
+
no SQL. After deploy the two sections resume and backfill on their own (idempotent).
|
|
236
|
+
- **Lesson:** the per-record cursor value MUST be written in the **same timezone the fetch `WHERE`
|
|
237
|
+
compares against** — SuiteQL account time (Eastern), never the REST record GET's UTC
|
|
238
|
+
`lastModifiedDate`. A cursor even an hour behind the fetch clock re-matches the same record forever.
|
|
239
|
+
This is the same SuiteQL-vs-REST clock trap as the date-only-format bug above — see the
|
|
240
|
+
[SuiteQL/REST shim doc](../../library/features/netsuite-suiteql-rest-shim.md).
|
|
241
|
+
|
|
205
242
|
### ⚠ Per-record isolation is SECTION-SPECIFIC — and SALES_ORDERS has NONE (corrected 2026-08-17)
|
|
206
243
|
|
|
207
244
|
Whether a bad record is "stepped over" or freezes the section depends entirely on whether **that
|
|
@@ -1175,6 +1212,15 @@ the code deploy).
|
|
|
1175
1212
|
|
|
1176
1213
|
## Change history
|
|
1177
1214
|
|
|
1215
|
+
- 2026-09-03 — **By-reference shim wrote the cursor on the WRONG CLOCK → ITEM_RECEIPTS + ITEM_FULFILLMENTS
|
|
1216
|
+
re-matched the same record forever (fixed, `library` only).** Follow-on to the same-day by-reference
|
|
1217
|
+
fix below: the shims set `lastModifiedDate` from the REST GET (UTC), `date()`-formatted in
|
|
1218
|
+
America/Chicago, but the LIST `WHERE` compares SuiteQL `TO_CHAR(lastmodifieddate)` in the NetSuite
|
|
1219
|
+
account tz (Eastern), so the cursor sat ~1h behind the fetch clock and `lastmodifieddate > cursor`
|
|
1220
|
+
always re-matched — infinite loop, no crash. `fetchItemFulfillmentById`/`fetchItemReceiptById`
|
|
1221
|
+
(`rest.php` ~L2093/~L2254) now read `lastmodifieddate` from SuiteQL `TO_CHAR` (Eastern), matching
|
|
1222
|
+
`fetchInventoryAdjustmentById` (already correct). Proven on NYCHH prod (item receipt 4953915). No
|
|
1223
|
+
worker/SQL change; sections self-backfill after deploy. (jcardinal)
|
|
1178
1224
|
- 2026-09-03 — **By-reference refetch dropped `lastModifiedDate` → ITEM_FULFILLMENTS + INVENTORY_ADJUSTMENTS
|
|
1179
1225
|
froze (fixed, `library` only).** GroWrk's ITEM_FULFILLMENTS was re-throwing every 5 min since the
|
|
1180
1226
|
2026-09-02 per-record cursor deploy (`Undefined property: stdClass::$lastModifiedDate` at
|
|
@@ -6,7 +6,7 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-03
|
|
10
10
|
owners: [bala, apeterson, jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Client/TableView.php
|
|
@@ -28,6 +28,7 @@ related:
|
|
|
28
28
|
- ../../../../clients/compass-canada/features/item-fulfillment-tableviews.md
|
|
29
29
|
- ./acl-permission-chain.md
|
|
30
30
|
- ../../toga25-supply/features/transfer-orders-page.md
|
|
31
|
+
- ../../toga25-supply/features/record-modals-and-nested-tables.md
|
|
31
32
|
---
|
|
32
33
|
|
|
33
34
|
## Summary
|
|
@@ -134,6 +135,36 @@ For NYCHH both hops are **100% populated** (PO items 1129/1129, VendorItems 2414
|
|
|
134
135
|
**INNER** — no empty-grid risk (third failure mode above). Verify FK population per tenant first: a
|
|
135
136
|
tenant with sparse `vendorItemId` would need LEFT OUTER or would empty the grid.
|
|
136
137
|
|
|
138
|
+
### A dropped FILTER-ONLY join must be re-added when a row-filter still targets that table
|
|
139
|
+
|
|
140
|
+
The rebase above **dropped** the old SalesOrders→bridge→PurchaseOrders chain (join ids 123/124/141)
|
|
141
|
+
because those joins showed **no column** — they only filtered rows. But a filter-only join is still
|
|
142
|
+
needed if something still filters on that table. NYCHH's Inventory "Items by Customer POs" grouping
|
|
143
|
+
drills into `items-for-purchase-orders` with an `additionalDataSlug` that binds a
|
|
144
|
+
`(PurchaseOrders.uuid:eq:...)` where clause (see
|
|
145
|
+
[record-modals-and-nested-tables](../../toga25-supply/features/record-modals-and-nested-tables.md),
|
|
146
|
+
Pattern 5, and
|
|
147
|
+
[tableview-apiwhereclause-row-filtering](../../api2/features/tableview-apiwhereclause-row-filtering.md)).
|
|
148
|
+
After the rebase the view no longer joined PurchaseOrders, so that filter failed with **error
|
|
149
|
+
3L-2492**.
|
|
150
|
+
|
|
151
|
+
Fix (confirmed working NYCHH 2026-09-03): **INSERT one filter-only `TableViewJoins` row** on the
|
|
152
|
+
`items-for-purchase-orders` view re-adding a real PurchaseOrders join — no column hangs off it:
|
|
153
|
+
|
|
154
|
+
- `joinRecordId = 17` (PurchaseOrders), `joinOnRecordFieldId = 81` (`PurchaseOrders.id`) as onA,
|
|
155
|
+
`parentRecordFieldId = 90` (`PurchaseOrderItems.purchaseOrderId`) as onB.
|
|
156
|
+
- `type INNER` — `purchaseOrderId` is always populated on PO items, so no empty-grid risk (third mode
|
|
157
|
+
above). `sortOrder 7` (after the existing 1–6).
|
|
158
|
+
- **No ACL row needed** if PurchaseOrders already has read grants for the caller's roles: NYCHH's
|
|
159
|
+
record 17 already had Base(1)/Developer(2)/API(3), so the ACL-INNER-JOIN joins query keeps it.
|
|
160
|
+
- Idempotent: key to the view by `TableViews.slug` (id-agnostic) with a fixed uuid and a `NOT EXISTS`
|
|
161
|
+
guard on `(tableViewId, joinRecordId=17)`. Core ids 17/81/90 verified stable/identical across prod
|
|
162
|
+
and client-sandbox.
|
|
163
|
+
|
|
164
|
+
**Both env states were verified rebased** (prod AND client-sandbox: view 16 at base recordId 18 with
|
|
165
|
+
no PurchaseOrders join) — the earlier "rebase is prod-only, not on client-sandbox" note is now stale.
|
|
166
|
+
Applied to client-sandbox then prod (Client_Nychh).
|
|
167
|
+
|
|
137
168
|
Core ids for this conversion (verified prod 2026-09-02, identical across clients/envs): **Records** —
|
|
138
169
|
sales-order-items 15, purchase-order-items 18, vendor-items 19, items 21; item-level SO/PO bridges
|
|
139
170
|
289 (downstream) / 290 (upstream). **RecordFields** — `PurchaseOrderItems.quantity` 94,
|
|
@@ -316,6 +347,13 @@ Core-level reference ids used (identical across all clients and environments): *
|
|
|
316
347
|
|
|
317
348
|
## Change history
|
|
318
349
|
|
|
350
|
+
- 2026-09-03 — Added the **filter-only join counterpart** to the PO-items rebase: dropping a
|
|
351
|
+
column-less join that only *filtered* rows (SalesOrders→bridge→PurchaseOrders, ids 123/124/141)
|
|
352
|
+
breaks any row-filter still targeting that table. NYCHH's `items-for-purchase-orders`
|
|
353
|
+
`(PurchaseOrders.uuid:eq:...)` filter errored **3L-2492** post-rebase; fix is one INNER filter-only
|
|
354
|
+
PurchaseOrders join (joinRecordId 17, onA `PurchaseOrders.id` 81, onB `PurchaseOrderItems.purchaseOrderId`
|
|
355
|
+
90, sortOrder 7, no column, no new ACL). Confirmed working; both prod and client-sandbox verified
|
|
356
|
+
rebased (the prior "prod-only" note is stale). (jcardinal)
|
|
319
357
|
- 2026-09-02 — Documented the **sales-order-item → purchase-order-item view conversion**: it needs an
|
|
320
358
|
extra **VendorItems** hop because `PurchaseOrderItems` has no `itemId` (catalog reached via
|
|
321
359
|
`vendorItemId → VendorItems (record 19) → itemId → Items`), unlike `SalesOrderItems` which joins
|
|
@@ -6,8 +6,8 @@ project: TOGa 2.5 Supply
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
10
|
-
owners: [apeterson, tcox]
|
|
9
|
+
updated: 2026-09-03
|
|
10
|
+
owners: [apeterson, tcox, jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/hooks/usePurchaseOrderDetails.ts
|
|
13
13
|
- toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx
|
|
@@ -127,6 +127,18 @@ level. Each level fetches its own meta + data and renders a `GenericTableLayout`
|
|
|
127
127
|
|
|
128
128
|
`additionalData` is produced per-level by callbacks (the WHERE-filter shape from Pattern 4). Each
|
|
129
129
|
level runs its own `useServerTableUrlState` and strips its own slug key, isolating URL state.
|
|
130
|
+
|
|
131
|
+
> **The FE APPENDS `.uuid` to a swap level's `additionalDataSlug` itself — so the stored surface
|
|
132
|
+
> value must be the BARE table name (`PurchaseOrders`), never `<Table>.uuid`.** The grouping config
|
|
133
|
+
> lives on `Core.SurfaceElements.config` (`inventory-list-groupings`, e.g. element key `posByItems`,
|
|
134
|
+
> in `$.levels[N].additionalDataSlug`). If you store `PurchaseOrders.uuid` there, the FE produces a
|
|
135
|
+
> **doubled** where clause `(PurchaseOrders.uuid.uuid:like:...)` and the filter never binds. Fix the
|
|
136
|
+
> stored value to just `PurchaseOrders` (JSON_SET keyed by `Surfaces.slug` + `config.key`, guarded to
|
|
137
|
+
> the stale value so it is re-runnable — id/uuid-agnostic because ids differ per env). Confirmed on
|
|
138
|
+
> NYCHH 2026-09-03: this Core row is all-client (no NYCHH SurfaceOverride), and its old
|
|
139
|
+
> `SalesOrders.uuid` was already wrong for a child view named `items-for-purchase-orders`. Sibling
|
|
140
|
+
> element `itemsByPOs` (view `purchase-orders_items`) still carries the stale `SalesOrders.uuid` —
|
|
141
|
+
> likely the same bug, not yet fixed.
|
|
130
142
|
A page-level `pageMeta` (from `useFetchPageMeta`) flows down for field-label overrides. The page
|
|
131
143
|
view model builds `TableLevel[]` arrays, picks the active one via a URL param, and passes `levels`
|
|
132
144
|
to `GenericNestedTables` with `key={activeGrouping.key}` to force remount and avoid stale state.
|
|
@@ -274,6 +286,12 @@ Scaffold per the `ItemRecordModalLayout` pattern (`src/layout/ItemRecordModalLay
|
|
|
274
286
|
[Transfer Orders page](transfer-orders-page.md).
|
|
275
287
|
|
|
276
288
|
## Change history
|
|
289
|
+
- 2026-09-03 — Documented that the FE **appends `.uuid`** to a `GenericNestedTables` swap level's
|
|
290
|
+
`additionalDataSlug`, so the stored `Core.SurfaceElements.config` value must be the **bare table
|
|
291
|
+
name** (`PurchaseOrders`), not `<Table>.uuid` — a `.uuid` in the stored value produces a doubled
|
|
292
|
+
`(PurchaseOrders.uuid.uuid:like:...)` where clause and the filter never binds. Found fixing the
|
|
293
|
+
NYCHH Inventory "Group: Items by Customer POs" grouping (`inventory-list-groupings`, element
|
|
294
|
+
`posByItems`); sibling `itemsByPOs` still stale. (jcardinal)
|
|
277
295
|
- 2026-09-02 — Fixed the approve/deny modal's Submit/Cancel row being cut off by a long "Reason for
|
|
278
296
|
Request" (found in testing by Skyler; approach agreed with David — grow the height a little, scroll
|
|
279
297
|
when it is very long). The single-action dialog is now taller but viewport-capped, and its action row
|
|
@@ -18,7 +18,7 @@ project: _Underscore
|
|
|
18
18
|
client: nychh
|
|
19
19
|
type: profile
|
|
20
20
|
status: active
|
|
21
|
-
updated: 2026-09-
|
|
21
|
+
updated: 2026-09-03
|
|
22
22
|
owners: ["jcardinal", "apeterson", "bala", "akhokhani"]
|
|
23
23
|
files:
|
|
24
24
|
- dbchanges2/Client_Nychh/2026-09-02a - TransferOrderNetsuitePushInterceptor.sql
|
|
@@ -97,15 +97,32 @@ table views. Client-specific DB change-sets live in `dbchanges2/Client_Nychh/`.
|
|
|
97
97
|
items 1129/1129, VendorItems 2414/2414) so both stay INNER. Seeds Developer-role record grants for
|
|
98
98
|
records 18 and 19 (both had only Base+API) plus Developer read on `PurchaseOrderItems.quantity`
|
|
99
99
|
(field 94); roles resolved by name, uuid-keyed, idempotent. Mechanism:
|
|
100
|
-
[tableview-joins](../../2.0/apps/_underscore/features/tableview-joins.md).
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
[tableview-joins](../../2.0/apps/_underscore/features/tableview-joins.md). **Applied to prod AND
|
|
101
|
+
client-sandbox** — both verified rebased 2026-09-03 (view 16 at base recordId 18, no PurchaseOrders
|
|
102
|
+
join). An earlier note here said this rebase was prod-only / not on client-sandbox; that is **stale**
|
|
103
|
+
— client-sandbox is also rebased. **OPEN:** the parent `purchase-orders` view filters to vendor 172
|
|
104
|
+
(`apiWhereClause
|
|
104
105
|
(vendorId:eq:172)`); this item view has no `apiWhereClause`, so it lists items for all POs — left
|
|
105
106
|
unfiltered (mirrors prior state), flagged as a decision for the dev. A "3L-2250" error reported
|
|
106
107
|
right after this deploy was **unrelated** — prod `Logs.Issue` reference **3L** is a Prudential ASN
|
|
107
108
|
error (`Client_Prudential.AdvanceShippingNoticeUnits` doesn't exist); the suffix is the occurrence
|
|
108
109
|
count (3L, occurrence 2250).
|
|
110
|
+
- **"Group: Items by Customer POs" grouping fixed to drill into PO items (2026-09-03).** After the
|
|
111
|
+
`items-for-purchase-orders` view was rebased to PO-items, the Inventory groupings surface
|
|
112
|
+
(`inventory-list-groupings`, element `posByItems`) was broken. Two fixes, both applied to
|
|
113
|
+
client-sandbox then prod (confirmed working):
|
|
114
|
+
1. **Stale filter slug** — the swap level's `additionalDataSlug` was `SalesOrders.uuid`; changed to
|
|
115
|
+
`PurchaseOrders`. The FE **appends `.uuid` itself**, so the stored value must be the bare table
|
|
116
|
+
name — `PurchaseOrders.uuid` produced a doubled `(PurchaseOrders.uuid.uuid:like:...)` clause.
|
|
117
|
+
This config is on `Core.SurfaceElements.config` (all-client, no NYCHH override); JSON_SET keyed by
|
|
118
|
+
`Surfaces.slug` + `config.key`, guarded/re-runnable. Sibling `itemsByPOs` still carries the stale
|
|
119
|
+
`SalesOrders.uuid` — likely the same bug, **not yet fixed**.
|
|
120
|
+
2. **Missing join** — the `(PurchaseOrders.uuid:eq:...)` filter errored **3L-2492** because the
|
|
121
|
+
rebase dropped the old SalesOrders→PurchaseOrders filter chain and the view no longer joined
|
|
122
|
+
PurchaseOrders. Added one filter-only INNER `TableViewJoins` row (joinRecordId 17, onA
|
|
123
|
+
`PurchaseOrders.id` 81, onB `PurchaseOrderItems.purchaseOrderId` 90, sortOrder 7, no column). No
|
|
124
|
+
new ACL — record 17 already has Base/Developer/API read. Mechanism:
|
|
125
|
+
[tableview-joins](../../2.0/apps/_underscore/features/tableview-joins.md).
|
|
109
126
|
- **`inventory_units` table view carries item detail as of 2026-08-18** — Part Number, Manufacturer
|
|
110
127
|
and Item Description sit directly after Asset Tag, reached through a **chained** Units → Items →
|
|
111
128
|
Manufacturers join (`dbchanges2/Client_Nychh/2026-08-18 - InventoryUnitsItemColumns.sql`, identical
|
package/package.json
CHANGED