toga-ai 1.0.758 → 1.0.760

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-09-02
9
+ updated: 2026-09-03
10
10
  owners: [dfranks, jcardinal, kyalamarthi]
11
11
  files:
12
12
  - library/app/api/netsuite/rest.php
@@ -197,6 +197,23 @@ Forecast2 tables already store.
197
197
  same underlying trap of reusing a sales-order-shaped assumption on a purchase order. Full
198
198
  engine-side impact + the tier-wide per-client backfill live on the
199
199
  [per-client-sync doc](../../worker/features/netsuite-togasupply-per-client-sync.md).
200
+ - **⚠ The three detail-fetch shims were inconsistent about `->lastModifiedDate` — two omitted it and
201
+ froze their sections once a per-record cursor started reading it (fixed 2026-09-03).**
202
+ `fetchItemReceiptById` set `$shim->lastModifiedDate` from its REST header, but **`fetchItemFulfillmentById`
203
+ and `fetchInventoryAdjustmentById` did not** (and InventoryAdjustment's SuiteQL header didn't even SELECT
204
+ `lastmodifieddate`). This was harmless until the 2026-09-02 per-record resume cursor: because
205
+ `syncItemFulfillmentFromNetsuite(&$rec)` / `syncInventoryAdjustmentFromNetsuite(&$rec)` **reassign** their
206
+ by-reference record to the fetch-by-id shim, the engine's cursor PUT then read `$rec->lastModifiedDate` on
207
+ the **replaced** object → *Undefined property: stdClass::$lastModifiedDate* → `App_Error` `exit()` → the
208
+ ITEM_FULFILLMENTS / INVENTORY_ADJUSTMENTS section froze at `1-RUNNING`, window ÷3 (GroWrk's fulfillments
209
+ re-threw every 5 min; InvAdj was latent only because no client had processed one since the rename). **Fix
210
+ (`library` only, `php -l` clean):** `$shim->lastModifiedDate = (string)(...)` in **both** shims —
211
+ fulfillment from the REST header, inventory-adjustment after adding
212
+ `TO_CHAR(lastmodifieddate,'YYYY-MM-DD HH24:MI:SS') AS lastmodifieddate` to its header SELECT. **Lesson:
213
+ a fetch-by-id shim consumed as the replacement for a `&$byReference` record must carry every field the
214
+ caller reads after the call — `lastModifiedDate` first.** Sales orders / POs / invoices don't reassign, so
215
+ they were unaffected. Engine-side detail on the
216
+ [per-client-sync doc](../../worker/features/netsuite-togasupply-per-client-sync.md).
200
217
  - **A shim must produce the SOAP object's SHAPE, not just its scalar fields — an absent nested list
201
218
  crashes 1.0 (2026-08-29).** `fetchCustomerById()`'s `addressbookList` was where the AIG lookup phase
202
219
  crashed: a REST customer with no/odd address made the shim omit the nested `addressbook` structure the
@@ -290,6 +307,14 @@ None — uniform across clients (NetSuite is a single shared account).
290
307
 
291
308
  ## Change history
292
309
 
310
+ - 2026-09-03 — Recorded that **`fetchItemFulfillmentById` and `fetchInventoryAdjustmentById` omitted
311
+ `->lastModifiedDate`** (unlike `fetchItemReceiptById`), which froze ITEM_FULFILLMENTS + INVENTORY_ADJUSTMENTS
312
+ once the 2026-09-02 per-record cursor read that field off the by-reference record these shims replace. GroWrk
313
+ was the reported alert; shared code so all four active togasupply clients (GroWrk, NYCHH, Prudential-EDI,
314
+ S&P Global) were affected. Fixed by setting `$shim->lastModifiedDate` in both shims (adding `lastmodifieddate`
315
+ to InventoryAdjustment's SuiteQL SELECT). `library`-only, `php -l` clean, no data lost, self-heals after
316
+ deploy. Lesson: a fetch-by-id shim used to replace a `&$byReference` record must carry every field the
317
+ caller reads afterward. (jcardinal)
293
318
  - 2026-09-02 — Recorded that **SuiteQL returns date columns in the account DISPLAY format (date-only
294
319
  `M/D/YYYY` here) unless wrapped in `TO_CHAR`** — a bare `lastmodifieddate` silently drops the time and
295
320
  is locale-dependent. This defeated the per-client sync's per-record resume cursor (stored `9/2/2026|id`,
@@ -6,7 +6,7 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-02
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-02
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-02
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). 🚨 **Applied to PROD
101
- only so far** (view 16 rebased, chain returns 1131 rows) — **NOT run on client-sandbox** (still the
102
- old sales-order-items version); the parent 2026-08-31a went to prod + client-sandbox, this one is
103
- prod-only. **OPEN:** the parent `purchase-orders` view filters to vendor 172 (`apiWhereClause
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.758",
3
+ "version": "1.0.760",
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",