toga-ai 1.0.767 → 1.0.769
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-suiteql-rest-shim.md +27 -1
- package/knowledge/1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md +82 -23
- package/knowledge/2.0/apps/_underscore/features/acl-permission-chain.md +13 -1
- package/knowledge/2.0/apps/toga25-supply/INDEX.md +3 -3
- package/knowledge/2.0/apps/toga25-supply/architecture.md +13 -0
- package/knowledge/2.0/apps/toga25-supply/features/client-configurable-fields.md +39 -1
- package/knowledge/2.0/apps/toga25-supply/features/surface-frontend.md +42 -0
- package/knowledge/2.0/apps/toga25-supply/workflows/cypress-testing.md +256 -119
- package/knowledge/2.0/standards/frontend.md +94 -10
- package/knowledge/INDEX.md +1 -0
- package/knowledge/clients/adyen/INDEX.md +5 -0
- package/knowledge/clients/adyen/profile.md +59 -0
- package/knowledge/clients/elite/features/netsuite-togasupply-sync.md +16 -2
- package/knowledge/clients/growrk/INDEX.md +1 -1
- package/knowledge/clients/growrk/features/transfer-order-flow.md +45 -3
- package/knowledge/clients/nychh/features/netsuite-inventory-adjustment-fulfillment-link.md +15 -4
- package/knowledge/clients/nychh/features/netsuite-transfer-order-import.md +29 -11
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ client: shared
|
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
9
|
updated: 2026-09-03
|
|
10
|
-
owners: [dfranks, jcardinal, kyalamarthi]
|
|
10
|
+
owners: [dfranks, jcardinal, kyalamarthi, rgirish]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/api/netsuite/rest.php
|
|
13
13
|
- library/app/netsuite.php
|
|
@@ -78,6 +78,22 @@ Forecast2 tables already store.
|
|
|
78
78
|
a per-type factor (it double-negates reversals back to positive). The shim flips
|
|
79
79
|
`amount = -foreignamount`, `costEstimate = -costestimate`.
|
|
80
80
|
|
|
81
|
+
- **⚠ `->holdInvoice` on a sales-order shim is NOT a native NetSuite field — it is normalised from
|
|
82
|
+
the custom checkbox `custbody_asi_hold` (internal id 4082), and that field is SHARED by all
|
|
83
|
+
clients.** NetSuite REST exposes no top-level `holdInvoice`. The `"asi"` prefix is **Agilant
|
|
84
|
+
Solutions Inc** (the company), *not* a client prefix — do not read it as client-specific. The read
|
|
85
|
+
lives **inside the shim**, in all three fetch paths: `buildSalesOrderShims()` (~L533, feeding both
|
|
86
|
+
`listSalesOrders()` and `fetchSalesOrderByIdFull()`) and `fetchSalesOrderById()` (~L1537), so
|
|
87
|
+
callers such as `App_Api_Toga2::isTransferOrder()` read an already-normalised boolean. (An earlier
|
|
88
|
+
version read `$nsOrder->holdInvoice` in the caller with a `custbody_hold_invoice` fallback — the
|
|
89
|
+
wrong field name; commit `07c31e24`, 2026-08-19, found the real one and moved the read down here.)
|
|
90
|
+
`worker/crons/toga2/compass/edi/2_create_so_netsuite.php` **writes** the same field.
|
|
91
|
+
**⚠ The SOAP client `library/app/netsuite.php` never sets `holdInvoice` at all**, so any detection
|
|
92
|
+
that depends on it (e.g. the `zero_dollar_hold` transfer-order mode — see
|
|
93
|
+
[per-client sync](../../worker/features/netsuite-togasupply-per-client-sync.md)) is **silently
|
|
94
|
+
always-false** on a SOAP path. Verified not a live risk: the togasupply engine calls
|
|
95
|
+
`App_Api_Netsuite_Rest::` on every sales-order/receipt/fulfillment path.
|
|
96
|
+
|
|
81
97
|
- **`createdFrom` is not a usable SuiteQL column.** `transaction.createdfrom` is **always NULL**
|
|
82
98
|
in SuiteQL (26/26). It is recoverable from `previoustransactionlinelink.previousdoc` excluding
|
|
83
99
|
`previoustype = 'Opprtnty'` (an invoice links to both its SO and its opportunity; REST's
|
|
@@ -307,6 +323,16 @@ None — uniform across clients (NetSuite is a single shared account).
|
|
|
307
323
|
|
|
308
324
|
## Change history
|
|
309
325
|
|
|
326
|
+
- 2026-09-03 — Documented that a sales-order shim's **`->holdInvoice` is not a native NetSuite
|
|
327
|
+
field**: it is normalised inside the shim from the custom checkbox **`custbody_asi_hold` (id
|
|
328
|
+
4082)**, which is **shared across all clients** ("asi" = Agilant Solutions Inc, not a client
|
|
329
|
+
prefix). Set in all three fetch paths (`buildSalesOrderShims()` feeding `listSalesOrders()` /
|
|
330
|
+
`fetchSalesOrderByIdFull()`, plus `fetchSalesOrderById()`); traced to commit `07c31e24`
|
|
331
|
+
(2026-08-19), which corrected the earlier caller-side read of `custbody_hold_invoice`. Recorded the
|
|
332
|
+
trap that the **SOAP client `app/netsuite.php` never sets it**, so hold-based detection is silently
|
|
333
|
+
always-false there — verified harmless today because the togasupply engine is REST-only.
|
|
334
|
+
Read-only session; no change to `rest.php`. (rgirish)
|
|
335
|
+
|
|
310
336
|
- 2026-09-03 — Recorded that **`fetchItemFulfillmentById` and `fetchInventoryAdjustmentById` omitted
|
|
311
337
|
`->lastModifiedDate`** (unlike `fetchItemReceiptById`), which froze ITEM_FULFILLMENTS + INVENTORY_ADJUSTMENTS
|
|
312
338
|
once the 2026-09-02 per-record cursor read that field off the by-reference record these shims replace. GroWrk
|
|
@@ -7,7 +7,7 @@ client: shared
|
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
9
|
updated: 2026-09-03
|
|
10
|
-
owners: ["dfranks", "bala", "jcardinal", "mhammontree", "snaredla"]
|
|
10
|
+
owners: ["dfranks", "bala", "jcardinal", "mhammontree", "snaredla", "rgirish"]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/netsuite/common_sync_togasupply.php
|
|
13
13
|
- worker/crons/toga2/netsuite/sync_togasupply_canon.php
|
|
@@ -463,29 +463,71 @@ the code deploy).
|
|
|
463
463
|
|
|
464
464
|
## Client variations
|
|
465
465
|
|
|
466
|
-
- **Transfer-order
|
|
467
|
-
|
|
468
|
-
`TransferOrders` vs `SalesOrders` by
|
|
469
|
-
`
|
|
470
|
-
(NYCHH's `sync_togasupply_hh.php` is the only one that does). The signal is the NetSuite custom
|
|
471
|
-
checkbox `custbody_stocking_order`, read by `App_Api_Toga2::isTransferOrder()`/`isStockingOrder()`
|
|
472
|
-
— **not** a dollar/hold heuristic. Full mechanics, PO bridges, and origin-location resolution:
|
|
473
|
-
[NYCHH NetSuite → TransferOrders import](../../../clients/nychh/features/netsuite-transfer-order-import.md).
|
|
474
|
-
- **Transfer-order integration is governed by TWO DISTINCT wrapper flags — a master enable switch and a
|
|
475
|
-
NYCHH-only classification-mode switch (do not conflate them).**
|
|
466
|
+
- **Transfer-order integration is governed by TWO DISTINCT wrapper constants — a master enable switch
|
|
467
|
+
and an explicit per-client detection MODE (do not conflate them).** These orders **masquerade as
|
|
468
|
+
NetSuite sales orders** and are split into `TransferOrders` vs `SalesOrders` by
|
|
469
|
+
`App_Api_Toga2::isTransferOrder()`.
|
|
476
470
|
- **`IS_ENABLED_INTEGRATION_TRANSFER_ORDERS`** — the master "do transfer-order integration at all"
|
|
477
|
-
switch, defined in every `sync_togasupply_*.php` launcher. `true` for **GroWrk, Adyen,
|
|
478
|
-
NYCHH**; `false` for
|
|
479
|
-
`
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
471
|
+
switch, defined in every `sync_togasupply_*.php` launcher. `true` for **GroWrk, Adyen, and
|
|
472
|
+
NYCHH**; `false` for Elite (pre-wired, not on) and the rest. **Every one of the 7
|
|
473
|
+
`isTransferOrder()` call sites is guarded by this**, so the mode throw below can never fire for a
|
|
474
|
+
client with transfer orders off.
|
|
475
|
+
- **`TRANSFER_ORDER_DETECTION_MODE`** — the classification mode, one of two `App_Api_Toga2` class
|
|
476
|
+
constants (see next bullet). Required whenever the master switch is on.
|
|
477
|
+
|
|
478
|
+
### ⚠ `TRANSFER_ORDER_DETECTION_MODE` — explicit per client, NO default, throws if unset (2026-09-03)
|
|
479
|
+
|
|
480
|
+
`isTransferOrder()` used to branch on a boolean `IS_ENABLED_TRANSFER_ORDER_STOCKING_FLAG_ROUTING`
|
|
481
|
+
(defined only in NYCHH's launcher) and **otherwise fall through to GroWrk's legacy `$0 total +
|
|
482
|
+
holdInvoice` rule**. That made the legacy rule an **implicit, silent default**: a new client turning
|
|
483
|
+
transfer orders on without the constant silently got another client's classification applied to its
|
|
484
|
+
orders, on a green-looking run. That boolean is **removed everywhere**.
|
|
485
|
+
|
|
486
|
+
Each launcher now declares an explicit mode before its `require_once`:
|
|
487
|
+
|
|
488
|
+
```php
|
|
489
|
+
const TRANSFER_ORDER_DETECTION_MODE = App_Api_Toga2::TRANSFER_ORDER_DETECTION_MODE__STOCKING_FLAG;
|
|
490
|
+
// or
|
|
491
|
+
const TRANSFER_ORDER_DETECTION_MODE = App_Api_Toga2::TRANSFER_ORDER_DETECTION_MODE__ZERO_DOLLAR_HOLD;
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
| Mode constant | Value | Rule | Clients |
|
|
495
|
+
|---|---|---|---|
|
|
496
|
+
| `TRANSFER_ORDER_DETECTION_MODE__STOCKING_FLAG` | `'stocking_flag'` | NetSuite checkbox `custbody_stocking_order` (id 7097) — **unchecked = transfer order** | NYCHH |
|
|
497
|
+
| `TRANSFER_ORDER_DETECTION_MODE__ZERO_DOLLAR_HOLD` | `'zero_dollar_hold'` | `holdInvoice === true` **AND** `total === 0` | GroWrk, Adyen, Elite (pre-set, unused — its master switch is off) |
|
|
498
|
+
|
|
499
|
+
`App_Api_Toga2::TRANSFER_ORDER_DETECTION_MODES` is the allowlist array. **`isTransferOrder()` THROWS
|
|
500
|
+
when the mode is undefined or not in the allowlist** — a deliberate fail-loud, chosen over defaulting
|
|
501
|
+
to the legacy rule, consistent with the 2026-08-27 "every section fails loud" policy. Consequence,
|
|
502
|
+
and it is not optional: **`library` and `worker` MUST deploy together.** A worker deploy without the
|
|
503
|
+
library (or vice versa) crashes GroWrk and Adyen on their next sync run.
|
|
504
|
+
|
|
505
|
+
- **A separate NYCHH-only flag, `IS_ENABLED_INVENTORY_ADJUSTMENT_FULFILLMENT_LINK`,** now guards
|
|
506
|
+
`attachInventoryAdjustmentToItemFulfillment()` (the `custbody_stock_adjustment` link). The removed
|
|
507
|
+
routing boolean had been doing double duty for it; it is an unrelated feature that merely has NYCHH
|
|
508
|
+
as its only client today. See
|
|
509
|
+
[NYCHH inventory-adjustment → item-fulfillment linking](../../../clients/nychh/features/netsuite-inventory-adjustment-fulfillment-link.md).
|
|
510
|
+
|
|
511
|
+
- **`->holdInvoice` is NOT a native NetSuite field** — it is normalised by the REST shim from the
|
|
512
|
+
custom checkbox `custbody_asi_hold` (internal id **4082**), which is **shared across all clients**
|
|
513
|
+
("asi" = Agilant Solutions Inc, the company, not a client prefix). `App_Api_Netsuite_Rest` sets it
|
|
514
|
+
in `buildSalesOrderShims()` (feeding `listSalesOrders()` + `fetchSalesOrderByIdFull()`) and in
|
|
515
|
+
`fetchSalesOrderById()`. **`app/netsuite.php` (SOAP) never sets it at all** — ZERO_DOLLAR_HOLD would
|
|
516
|
+
be silently always-false on a SOAP path. Not a live risk: `common_sync_togasupply.php` uses
|
|
517
|
+
`App_Api_Netsuite_Rest::` on **every** sales-order / receipt / fulfillment path, and all
|
|
518
|
+
transfer-order clients are on REST. Field detail:
|
|
519
|
+
[SuiteQL/REST shim](../../library/features/netsuite-suiteql-rest-shim.md).
|
|
520
|
+
|
|
521
|
+
- **⚠ OPEN (pre-existing, not fixed): an absent NetSuite `total` collapses to `0.0` and can
|
|
522
|
+
misclassify.** The ZERO_DOLLAR_HOLD branch reads
|
|
523
|
+
`$total = isset($nsOrder->total) ? (float) $nsOrder->total : 0.0;`. A REST response with a
|
|
524
|
+
missing/blank `total` (transient API failure — not a real $0 order) becomes `0.0`, and combined with
|
|
525
|
+
the hold flag set, a **non-zero order classifies as a transfer order**. This line predates the mode
|
|
526
|
+
refactor and was carried across unchanged; it goes against the fail-loud rule for cron/sync code.
|
|
527
|
+
Raised with the developer, left as-is pending a decision.
|
|
528
|
+
- **Full mechanics of the stocking-flag mode** (PO bridges, origin-location resolution):
|
|
529
|
+
[NYCHH NetSuite → TransferOrders import](../../../clients/nychh/features/netsuite-transfer-order-import.md).
|
|
530
|
+
Legacy-rule background: [GroWrk transfer order flow](../../../clients/growrk/features/transfer-order-flow.md).
|
|
489
531
|
- `isParentCustomer` — `true` walks NetSuite child customers (`listChildCustomers`); `false`
|
|
490
532
|
syncs the single customer. Prudential-EDI and the live Quad wrapper use `false`.
|
|
491
533
|
- The original combined-array entries used `App_Api_Toga2::CLIENT_UUID_*` consts for Quad and
|
|
@@ -1212,6 +1254,23 @@ the code deploy).
|
|
|
1212
1254
|
|
|
1213
1255
|
## Change history
|
|
1214
1256
|
|
|
1257
|
+
- 2026-09-03 — **Transfer-order detection is now an explicit per-client MODE with no default; the old
|
|
1258
|
+
`IS_ENABLED_TRANSFER_ORDER_STOCKING_FLAG_ROUTING` boolean is removed from both repos.**
|
|
1259
|
+
`isTransferOrder()` previously branched on that boolean (NYCHH-only) and otherwise fell through to
|
|
1260
|
+
GroWrk's `$0 total + holdInvoice` rule — an implicit silent default that would apply one client's
|
|
1261
|
+
classification to another's orders on a green-looking run. Launchers now set
|
|
1262
|
+
`TRANSFER_ORDER_DETECTION_MODE` to one of two new `App_Api_Toga2` constants
|
|
1263
|
+
(`__STOCKING_FLAG` = NYCHH, `__ZERO_DOLLAR_HOLD` = GroWrk/Adyen/Elite), validated against a
|
|
1264
|
+
`TRANSFER_ORDER_DETECTION_MODES` allowlist; an undefined/unknown mode **throws**. All 7 call sites
|
|
1265
|
+
are guarded by `IS_ENABLED_INTEGRATION_TRANSFER_ORDERS`, so the throw can't fire for a client with
|
|
1266
|
+
TOs off. The `custbody_stock_adjustment` adjustment→fulfillment link, which the removed boolean was
|
|
1267
|
+
also guarding, moved to its own `IS_ENABLED_INVENTORY_ADJUSTMENT_FULFILLMENT_LINK` (NYCHH only).
|
|
1268
|
+
Also recorded: `->holdInvoice` is the REST shim's normalisation of the **shared** custom checkbox
|
|
1269
|
+
`custbody_asi_hold` (id 4082, "asi" = Agilant Solutions Inc, not a client), never set on the SOAP
|
|
1270
|
+
path — safe today because this engine is REST-only; and the pre-existing `total ?: 0.0` default in
|
|
1271
|
+
the ZERO_DOLLAR_HOLD branch can misclassify on a transient NetSuite failure (open, unchanged).
|
|
1272
|
+
**`library` + `worker` must deploy together** or GroWrk and Adyen throw on their next run. (rgirish)
|
|
1273
|
+
|
|
1215
1274
|
- 2026-09-03 — **By-reference shim wrote the cursor on the WRONG CLOCK → ITEM_RECEIPTS + ITEM_FULFILLMENTS
|
|
1216
1275
|
re-matched the same record forever (fixed, `library` only).** Follow-on to the same-day by-reference
|
|
1217
1276
|
fix below: the shims set `lastModifiedDate` from the REST GET (UTC), `date()`-formatted in
|
|
@@ -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: ["jcardinal", "mhammontree", "tcox", "bala", "apeterson"]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
@@ -380,6 +380,14 @@ across clients, establish **which app** the missing control is in. Detail:
|
|
|
380
380
|
[Surface Resolver](surface-resolver.md) and
|
|
381
381
|
[toga25-supply side navigation](../../toga25-supply/features/side-navigation-and-default-route.md).
|
|
382
382
|
|
|
383
|
+
> **Narrow scope precisely: the FLAGS are inert, the ENDPOINT is not gone.** "Dead config" above is
|
|
384
|
+
> about the `navigation-*` `AclActions` flags having no effect on the 2.5 UI — that stands, verified
|
|
385
|
+
> prod-wide. It is **not** a claim that `toga25-supply` has stopped calling `Page::meta`: verified
|
|
386
|
+
> 2026-09-03, five of its view models still call `GET /v2/pages/meta` (nothing in `src/` reads a
|
|
387
|
+
> field off the result). So a change to `Page::meta`'s request path can still reach the 2.5 app even
|
|
388
|
+
> though its ACL flags cannot reach the 2.5 nav. Detail:
|
|
389
|
+
> [Surface Frontend](../../toga25-supply/features/surface-frontend.md).
|
|
390
|
+
|
|
383
391
|
Worked example (Quad, 2026-08-26): the vendor-items **Add and Edit buttons both** read
|
|
384
392
|
`acl['vendor-items']['navigation-edit-on-vendor-items']` (AclActions id 7). `Client_Quad` had a row
|
|
385
393
|
for id 5 only, so the page was reachable but button-less, even though record 19 already granted role
|
|
@@ -598,6 +606,10 @@ hardcoded `Core.RecordFields` id literals instead of a subselect.
|
|
|
598
606
|
and every repo is on the **same branch** so the generated model matches the DB.
|
|
599
607
|
|
|
600
608
|
## Change history
|
|
609
|
+
- **2026-09-03** — Scoped the "dead config in the surface-driven 2.5 app" claim: the `navigation-*`
|
|
610
|
+
**flags** are inert (unchanged), but `toga25-supply` still **calls `GET /v2/pages/meta`** from five
|
|
611
|
+
view models, so `Page::meta` is not out of that app's request path. Additive clarification only.
|
|
612
|
+
(apeterson)
|
|
601
613
|
- **2026-09-01** — Four additions from a `PurchaseOrderItems._qty*` grant investigation.
|
|
602
614
|
(1) **Read `Core.Records.aclDatabase` FIRST** — both databases carry an `AclFieldPermissions`
|
|
603
615
|
table, so checking Core for a `CLIENT`-acl record (here `purchase-order-items`, record 18) returns
|
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
| [TOGa 2.5 Supply — Architecture](architecture.md) | `toga25-supply` ("TOGa 2.5 Supply") is the **React/TypeScript frontend** for the 2.0 Supply application — an iteration and improvement of `toga2-supply`. | toga25-supply/src/main.tsx, toga25-supply/src/App.tsx, toga25-supply/src/routes.tsx, toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/fieldsConfig/useClientFields.ts, toga25-supply/src/layout/, toga25-supply/src/pages/, toga25-supply/src/hooks/useTableCellInteractions.ts, toga25-supply/src/providers/TalosStreamProvider.tsx |
|
|
6
6
|
| [Action-Button Rule Engine (Flag / Rule grammar)](features/action-button-rule-engine.md) | A declarative, fully config-driven rule engine that resolves the boolean-ish flags (`isEnabled`, `isVisible`, `isComplete`) on SalesOrder action-button options. | toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.ts, toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.test.ts, toga25-supply/src/pages/SalesOrders/helpers/buildPatchedTenantFields.ts, toga25-supply/src/layout/RecordApprovalModal/view/ApprovalFlowDetailInputs.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/FIELDS/COMPASS/approvalActionFields.json, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/FIELDS/COMPASSCANADA/approvalActionFields.json, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/useApprovalModalViewModel.tsx |
|
|
7
7
|
| [Client API-Fetch Fields (useClientApiFields / apiFields.json)](features/client-api-fetch-fields.md) | The mechanism that resolves a client's **API-fetch projection** — which `fields` / `ojoin` / `join` / `where` to request from the TOGa API for a given page — fr | toga25-supply/src/fieldsConfig/resolveApiConfig.ts, toga25-supply/src/fieldsConfig/useClientApiFields.ts, toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/FIELDS/apiFields.json, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/FIELDS/apiFields.json, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/useItemRecordModalViewModel.tsx |
|
|
8
|
-
| [Client-Configurable Fields (useClientFields / fieldsConfig)](features/client-configurable-fields.md) | The mechanism for config that **varies by client** (or client × role) — field overrides, filter buttons, group-by options, column pickers, layout toggles — with | toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/fieldsConfig/useClientFields.ts, toga25-supply/src/pages/SalesOrders/viewModel/FIELDS/, toga25-supply/src/pages/Inventory/viewModel/FIELDS/, toga25-supply/src/pages/Inventory/README.md, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/FIELDS/, toga25-supply/src/layout/VendorItemRecordModalLayout/viewModel/FIELDS/, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/FIELDS/ |
|
|
8
|
+
| [Client-Configurable Fields (useClientFields / fieldsConfig)](features/client-configurable-fields.md) | The mechanism for config that **varies by client** (or client × role) — field overrides, filter buttons, group-by options, column pickers, layout toggles — with | toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/fieldsConfig/resolveRole.ts, toga25-supply/src/globalFieldsConfig/clientLoginFields.json, toga25-supply/src/fieldsConfig/useClientFields.ts, toga25-supply/src/pages/SalesOrders/viewModel/FIELDS/, toga25-supply/src/pages/Inventory/viewModel/FIELDS/, toga25-supply/src/pages/Inventory/README.md, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/FIELDS/, toga25-supply/src/layout/VendorItemRecordModalLayout/viewModel/FIELDS/, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/FIELDS/ |
|
|
9
9
|
| [Column Visibility (URL-driven show/hide columns)](features/column-visibility.md) | A "Columns" header button that opens a modal listing every column from the table meta, lets the user show/hide columns, adjusts the table live, and persists the | toga25-supply/src/components/ColumnVisibilityModal/, toga25-supply/src/surface/useColumnVisibilityModalConfig.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableData.tsx, dbchanges2/Core/2026-08-28b - TransferOrderListActionsSurfaceSeed.sql |
|
|
10
10
|
| [Force Logout on Deployment (useDeploymentGuard)](features/force-logout-on-deployment.md) | On large deployments the backend bumps the Core parameter `META_LAST_REFRESH_DATETIME`. | toga25-supply/src/hooks/useDeploymentGuard.tsx, toga25-supply/src/App.tsx |
|
|
11
11
|
| [Meta-Driven Page & Table Setup](features/meta-driven-table-data.md) | A page in this app is **meta-driven end to end**: the page view model fetches *page meta* (labels, sections, ACL) and *table meta* (the columns/fields + table s | toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/TransferOrders/viewModel/useTransferOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableState.ts, toga25-supply/src/hooks/useTablePageMeta.ts, toga-blox-npm/dist/hooks/useFetchPageMeta.d.ts, toga-blox-npm/dist/hooks/useFetchTablePageMeta.d.ts, toga-blox-npm/dist/hooks/useAssignTableFieldLabels.d.ts, toga-blox-npm/dist/components/Table/hooks/useTableData.d.ts |
|
|
12
12
|
| [Record Modals & Nested Tables](features/record-modals-and-nested-tables.md) | The repo's family of modal + nested-table patterns layered over toga-blox `TableRecordModal` and `PrimaryTable*Layout`. | toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/hooks/usePurchaseOrderDetails.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/FIELDS/apiFields.json, toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts, toga25-supply/src/layout/PrimaryTableServerLayout/PrimaryTableServerLayout.tsx, toga25-supply/src/layout/PrimaryTableServerLayout/types.ts, toga25-supply/src/layout/ItemRecordModalLayout/, toga25-supply/src/layout/SalesOrderRecordModalLayout/, toga25-supply/src/layout/SalesOrderItemsTableLayout/, toga25-supply/src/layout/ItemFulfillmentModal/, toga25-supply/src/layout/GenericNestedTables/GenericNestedTables.tsx, toga25-supply/src/layout/GenericNestedTables/GenericTableLayout.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/DEFAULT/inventoryGroupings.json, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/TransferOrderRecordModalLayout.tsx, toga25-supply/src/pages/TransferOrders/helpers/buildInventoryPurchaseOrderUrl.ts, toga25-supply/src/hooks/useTableCellInteractions.ts, toga25-supply/src/hooks/useServerTableUrlState.ts, toga25-supply/src/layout/RecordApprovalModal/helpers/handleFormatApprovalWorkflowPayload.ts, toga25-supply/src/layout/RecordApprovalModal/api/approvalDecisionsApi.ts, toga25-supply/src/layout/RecordApprovalModal/ApprovalModal.module.css |
|
|
13
13
|
| [Side navigation & default route — an empty nav renders a BLANK PAGE and gets reported as "cannot log in"](features/side-navigation-and-default-route.md) | The 2.5 side nav is **100% backend-driven** by the `navigation` surface bundle, and the same list also decides **which routes exist** and **where `/` lands**. | toga25-supply/src/routes.tsx, toga25-supply/src/layout/AppLayout/viewModel/useAppLayoutViewModel.ts, api2/Component/Api/V2/V2.php, _underscore/Model/Core/Surface.php |
|
|
14
14
|
| [SSO redirect & public-vs-user session gating (useAuthenticationFlow)](features/sso-redirect-and-session-gating.md) | How 2.5 Supply decides, on every navigation, whether an anonymous visitor should be bounced to their client's SSO IdP instead of the local `/login` form. | toga25-supply/src/hooks/useAuthenticationFlow.ts, toga25-supply/src/routes.tsx, toga25-supply/src/contexts/AuthContext.tsx, toga25-supply/src/api/api.ts |
|
|
15
|
-
| [Surface Frontend (DB-driven UI consumption, src/surface/)](features/surface-frontend.md) | The frontend consumer of the platform-wide Surface layer — DB-driven UI config fetched from `GET /v2/surfaces/meta?slug=<slug>` instead of statically-imported J | toga25-supply/src/layout/RecordApprovalModal/helpers/stackedCurrencyJoiner.ts, toga25-supply/src/layout/RecordApprovalModal/helpers/stackedCurrencyJoiner.test.ts, toga25-supply/src/App.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/DenialBanner.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/SalesOrderRecordModal.module.css, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/SalesOrderApprovalModalsLayout.tsx, toga25-supply/src/layout/RecordApprovalModal/RecordApprovalModalLayout.tsx, toga25-supply/src/layout/RecordApprovalModal/view/ApprovalTimelineView.tsx, toga25-supply/src/layout/RecordApprovalModal/ApprovalModal.module.css, toga25-supply/src/utils/formatDateTime.ts, toga25-supply/src/utils/index.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/layout/RecordApprovalModal/, toga25-supply/src/contexts/AuthContext.tsx, toga25-supply/src/surface/applyColSpan.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/sectionRenderers.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getVisibleSections.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderApprovalSummaryGrid.tsx, toga25-supply/src/surface/useFetchSurfaceMeta.ts, toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/ServiceRequestRecordModalLayout.tsx, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/view/ServiceRequestsView.tsx, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/viewModel/useServiceRequestRecordModalLayoutModel.tsx, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/viewModel/FIELDS/apiFields.json, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/SalesOrders/helpers/buildPatchedTenantFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/SalesOrderView.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderSummaryGrid.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getDetailSections.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderNotesSection.tsx, toga25-supply/src/pages/SalesOrders/helpers/cleanOrder.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/AdminNotesSection.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getAdminNotes.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/index.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/surface/resolveElementState.ts, toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.ts, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrderRowRecordState.ts, toga25-supply/src/surface/actionRegistry.ts, toga25-supply/src/surface/componentRegistry.tsx, toga25-supply/src/surface/SurfaceActionBar.tsx, toga25-supply/src/surface/SurfaceSection.tsx, toga25-supply/src/surface/resolve.ts, toga25-supply/src/surface/types.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/Login/LoginPage.tsx, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/view/SurfaceRowActions.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrderVip.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/Items/ItemsPage.tsx, toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx, toga25-supply/src/pages/VendorItems/VendorItemsPage.tsx, toga25-supply/src/pages/VendorItems/viewModel/useVendorItemsPageViewModel.tsx, toga25-supply/src/pages/Inventory/Inventory.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/index.ts, toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/layout/ItemRecordModalLayout/helpers/surfaceBundleToItemFields.ts, toga25-supply/src/layout/ItemRecordModalLayout/helpers/index.ts, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/useItemRecordModalViewModel.tsx, toga25-supply/src/layout/ItemRecordModalLayout/ItemRecordModalLayout.tsx, toga25-supply/src/layout/ItemRecordModalLayout/components/ItemRecordView.tsx, toga25-supply/src/surface/useStatusColors.ts, toga25-supply/src/surface/SurfaceHeader.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/helpers/surfaceBundlesToDecisionFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/useApprovalModalViewModel.tsx |
|
|
15
|
+
| [Surface Frontend (DB-driven UI consumption, src/surface/)](features/surface-frontend.md) | The frontend consumer of the platform-wide Surface layer — DB-driven UI config fetched from `GET /v2/surfaces/meta?slug=<slug>` instead of statically-imported J | toga25-supply/src/layout/RecordApprovalModal/helpers/stackedCurrencyJoiner.ts, toga25-supply/src/layout/RecordApprovalModal/helpers/stackedCurrencyJoiner.test.ts, toga25-supply/src/App.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/DenialBanner.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/SalesOrderRecordModal.module.css, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/SalesOrderApprovalModalsLayout.tsx, toga25-supply/src/layout/RecordApprovalModal/RecordApprovalModalLayout.tsx, toga25-supply/src/layout/RecordApprovalModal/view/ApprovalTimelineView.tsx, toga25-supply/src/layout/RecordApprovalModal/ApprovalModal.module.css, toga25-supply/src/utils/formatDateTime.ts, toga25-supply/src/utils/index.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/layout/RecordApprovalModal/, toga25-supply/src/contexts/AuthContext.tsx, toga25-supply/src/surface/applyColSpan.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/sectionRenderers.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getVisibleSections.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderApprovalSummaryGrid.tsx, toga25-supply/src/surface/useFetchSurfaceMeta.ts, toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/VendorItems/viewModel/useVendorItemsPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Bundles/viewModel/useBundlesPageViewModel.tsx, toga25-supply/src/layout/ItemFulfillmentModal/useItemFulfillmentModalViewModel.tsx, toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/ServiceRequestRecordModalLayout.tsx, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/view/ServiceRequestsView.tsx, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/viewModel/useServiceRequestRecordModalLayoutModel.tsx, toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/viewModel/FIELDS/apiFields.json, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/SalesOrders/helpers/buildPatchedTenantFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/SalesOrderView.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderSummaryGrid.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getDetailSections.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderNotesSection.tsx, toga25-supply/src/pages/SalesOrders/helpers/cleanOrder.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/AdminNotesSection.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getAdminNotes.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/index.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/surface/resolveElementState.ts, toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.ts, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrderRowRecordState.ts, toga25-supply/src/surface/actionRegistry.ts, toga25-supply/src/surface/componentRegistry.tsx, toga25-supply/src/surface/SurfaceActionBar.tsx, toga25-supply/src/surface/SurfaceSection.tsx, toga25-supply/src/surface/resolve.ts, toga25-supply/src/surface/types.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/Login/LoginPage.tsx, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/view/SurfaceRowActions.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrderVip.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/Items/ItemsPage.tsx, toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx, toga25-supply/src/pages/VendorItems/VendorItemsPage.tsx, toga25-supply/src/pages/VendorItems/viewModel/useVendorItemsPageViewModel.tsx, toga25-supply/src/pages/Inventory/Inventory.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/index.ts, toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/layout/ItemRecordModalLayout/helpers/surfaceBundleToItemFields.ts, toga25-supply/src/layout/ItemRecordModalLayout/helpers/index.ts, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/useItemRecordModalViewModel.tsx, toga25-supply/src/layout/ItemRecordModalLayout/ItemRecordModalLayout.tsx, toga25-supply/src/layout/ItemRecordModalLayout/components/ItemRecordView.tsx, toga25-supply/src/surface/useStatusColors.ts, toga25-supply/src/surface/SurfaceHeader.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/helpers/surfaceBundlesToDecisionFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/useApprovalModalViewModel.tsx |
|
|
16
16
|
| [Talos Integration (AppLayout host, live Aegra streaming, LangGraph→blox mapper)](features/talos-integration.md) | toga25-supply is the first host of the shared blox [Talos assistant](../../toga-blox/features/talos-assistant.md). | toga25-supply/src/api/talos.ts, toga25-supply/src/hooks/useTalosSession.ts, toga25-supply/src/hooks/useTalosThreads.ts, toga25-supply/src/hooks/useTalosThreads.test.ts, toga25-supply/src/hooks/useTalosSurface.ts, toga25-supply/src/hooks/useTalosSurface.test.ts, toga25-supply/src/App.tsx, toga25-supply/db-migrations/PLAYBOOK.md, dbchanges2/Core/2026-09-02a - TalosAssistantSurfaceSeed.sql, dbchanges2/Client_Nychh/2026-09-02a - TalosAssistantEnable.sql, toga25-supply/src/providers/TalosStreamProvider.tsx, toga25-supply/src/utils/talosMessages.ts, toga25-supply/src/utils/talosMessages.test.ts, toga25-supply/src/layout/AppLayout/AppLayout.tsx, toga25-supply/src/components/Header/Header.tsx, toga25-supply/src/components/Header/Header.module.css, toga25-supply/src/index.css, toga25-supply/src/assets/talos-owl.png |
|
|
17
17
|
| [Transfer Orders page (TableView → Core surfaces → React page + record modal)](features/transfer-orders-page.md) | The Transfer Orders screen — list + read-only record modal — built end to end on 2026-08-28 from a Claude Design prototype. | toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useCreateTransferOrder.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/PanelSelect.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/ContactSelect.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/SelectTargetLocationModal.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/TransferItemPickerTable.tsx, _underscore/Model/Client/TransferOrder.php, _underscore/Model/Client/TransferOrderNote.php, _underscore/Model/Client/TransferOrderNoteType.php, dbchanges2/Client/2026-09-01b - TransferOrderNotesTables.sql, dbchanges2/Client/2026-09-01c - TransferOrderContactCreatedByAndDescription.sql, dbchanges2/Core/2026-09-01c - TransferOrderNotesRecords.sql, dbchanges2/Core/2026-09-01d - TransferOrderContactCreatedByAndDescriptionRecordFields.sql, toga25-supply/src/pages/TransferOrders/TransferOrders.tsx, toga25-supply/src/pages/TransferOrders/hooks/useTransferOrdersTableState.ts, toga25-supply/src/pages/TransferOrders/viewModel/useTransferOrdersPageViewModel.tsx, toga25-supply/src/pages/TransferOrders/view/TransferOrdersTableLayout/TransferOrdersTableLayout.tsx, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/TransferOrderRecordModalLayout.tsx, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/hooks/useTransferOrderRecord.ts, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/viewModel/useTransferOrderRecordModalLayoutModel.ts, toga25-supply/src/pages/TransferOrders/view/TransferOrderRecordModalLayout/view/TransferOrderRecordView.tsx, toga25-supply/src/pages/TransferOrders/helpers/buildInventoryPurchaseOrderUrl.ts, toga25-supply/src/pages/TransferOrders/TransferOrder.module.css, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/CreateTransferOrderModal.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/index.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/usePurchaseOrderItemRows.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useTargetLocationOptions.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useTransferSourceLocation.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useLocationContacts.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useCreateLocationContact.ts, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/AddContactModal.tsx, toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/view/TransferNotesField.tsx, dbchanges2/Client/2026-09-01a - PurchaseOrderItemQtyFieldsApiRoleRead.sql, toga25-supply/src/surface/useColumnVisibilityModalConfig.ts, toga25-supply/src/routes.tsx, dbchanges2/Core/2026-08-28a - TransferOrderSurfaceSeed.sql, dbchanges2/Core/2026-08-28b - TransferOrderListActionsSurfaceSeed.sql, dbchanges2/Client/2026-08-28a - TransferOrderStageThemeTokens.sql, dbchanges2/Client/2026-08-28b - TransferOrderItemsTimestamps.sql, dbchanges2/Client_Nychh/2026-08-28a - TransferOrdersTableView.sql, dbchanges2/Client_Nychh/2026-08-28b - TransferOrdersNavigationEnable.sql, dbchanges2/Client_Nychh/2026-08-28c - TransferOrderRecordAcl.sql |
|
|
18
18
|
| [AWS Amplify Multi-Environment Deployment](workflows/amplify-deployment.md) | How `toga25-supply` deploys to **all** of its environments on AWS Amplify from a **single shared `amplify.yml`**. | toga25-supply/amplify.yml, toga25-supply/src/api/api.ts, toga25-supply/src/hooks/useAuthenticationFlow.ts, toga25-supply/vite.config.ts, toga25-supply/package.json, toga25-supply/.env.development |
|
|
19
|
-
| [Cypress Testing Harness (
|
|
19
|
+
| [Cypress Testing Harness (multi-tenant, fully stubbed)](workflows/cypress-testing.md) | The Cypress harness for `toga25-supply`: an **e2e** project and a **component** project, plus a stub layer that answers **every** `/v2` call so no test touches | toga25-supply/cypress.config.ts, toga25-supply/cypress/README.md, toga25-supply/cypress/tsconfig.json, toga25-supply/cypress/support/e2e.ts, toga25-supply/cypress/support/commands.ts, toga25-supply/cypress/support/component.tsx, toga25-supply/cypress/support/component-index.html, toga25-supply/cypress/support/tenants.ts, toga25-supply/cypress/support/api/stubApi.ts, toga25-supply/cypress/support/api/envelope.ts, toga25-supply/cypress/support/api/users.ts, toga25-supply/cypress/support/api/surfaces.ts, toga25-supply/cypress/support/api/session.ts, toga25-supply/cypress/e2e/login.cy.ts, toga25-supply/cypress/e2e/emptyNavigation.cy.ts, toga25-supply/cypress/component/harness.cy.tsx, toga25-supply/cypress/fixtures/tenants/COMPASS/surfaces/navigation.json, toga25-supply/cypress/fixtures/tenants/COMPASSCANADA/surfaces/navigation.json, toga25-supply/cypress/fixtures/tenants/NYCHH/surfaces/navigation.json, toga25-supply/cypress/fixtures/tenants/QUAD/surfaces/navigation.json, toga25-supply/scripts/cypress.mjs, toga25-supply/tsconfig.node.json, toga25-supply/package.json |
|
|
20
20
|
| [Porting a page (or query) from toga2-supply to toga25-supply](workflows/porting-a-page-from-toga2-supply.md) | `toga25-supply` re-implements pages that already exist in `toga2-supply`. | toga25-supply/src/pages/SalesOrders/viewModel/FIELDS/PRUDENTIAL/exportApiFields.json, toga25-supply/src/pages/SalesOrders/api/prudentialExportApi.ts, toga2-supply/src/pages/Orders/api/OrdersApi.ts, toga2-supply/src/components/ui/Tables/hooks/useExportableData.tsx |
|
|
@@ -19,6 +19,7 @@ files:
|
|
|
19
19
|
- toga25-supply/src/hooks/useTableCellInteractions.ts
|
|
20
20
|
- toga25-supply/src/providers/TalosStreamProvider.tsx
|
|
21
21
|
related:
|
|
22
|
+
- workflows/cypress-testing.md
|
|
22
23
|
- features/talos-integration.md
|
|
23
24
|
- features/client-configurable-fields.md
|
|
24
25
|
- features/meta-driven-table-data.md
|
|
@@ -70,6 +71,18 @@ toga-blox table/modal templates.
|
|
|
70
71
|
- **UI library:** `@agilant/toga-blox` provides `PrimaryTable*Layout`, `TableRecordModal`,
|
|
71
72
|
`BaseInput`, `BaseButton`, `DetailSection`, the `useTableData` hook, FontAwesome helpers, etc.
|
|
72
73
|
|
|
74
|
+
### Testing
|
|
75
|
+
|
|
76
|
+
Two harnesses, both fully stubbed — **no front-end test ever talks to a backend**:
|
|
77
|
+
|
|
78
|
+
- **Vitest + React Testing Library** (`npm test`) for logic: hooks, view models, auth context,
|
|
79
|
+
table URL-state, error classification.
|
|
80
|
+
- **Cypress** (`npm run cy:e2e:<tenant>`, `npm run cy:ct`) for the **tenant x role x feature**
|
|
81
|
+
matrix, driven by per-tenant surface **fixtures** rather than real logins or a seeded database.
|
|
82
|
+
|
|
83
|
+
The full design, run commands, guardrails and traps live in
|
|
84
|
+
[cypress-testing](workflows/cypress-testing.md) — read that before writing or changing a test.
|
|
85
|
+
|
|
73
86
|
## Directory layout (`src/`)
|
|
74
87
|
|
|
75
88
|
- `pages/<Entity>/` — page component (thin) + `viewModel/` (the `use*PageViewModel` hook) +
|
|
@@ -6,10 +6,12 @@ project: TOGa 2.5 Supply
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-09-03
|
|
10
10
|
owners: [apeterson]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-supply/src/fieldsConfig/index.ts
|
|
13
|
+
- toga25-supply/src/fieldsConfig/resolveRole.ts
|
|
14
|
+
- toga25-supply/src/globalFieldsConfig/clientLoginFields.json
|
|
13
15
|
- toga25-supply/src/fieldsConfig/useClientFields.ts
|
|
14
16
|
- toga25-supply/src/pages/SalesOrders/viewModel/FIELDS/
|
|
15
17
|
- toga25-supply/src/pages/Inventory/viewModel/FIELDS/
|
|
@@ -61,6 +63,37 @@ and the `useClientFields()` hook.
|
|
|
61
63
|
const tenantFields = clientFields.salesOrdersPageFields; // your key
|
|
62
64
|
```
|
|
63
65
|
|
|
66
|
+
### How `resolveRole` decides the role (verified 2026-09-03)
|
|
67
|
+
|
|
68
|
+
`src/fieldsConfig/resolveRole.ts` takes `(clientSlug, user)` and reads **boolean-ish string flags
|
|
69
|
+
off the `/v2/users` record** — `"1"` means set. The flag set and the returned role names **differ
|
|
70
|
+
per client**, and the checks are ordered, so the first match wins:
|
|
71
|
+
|
|
72
|
+
| clientSlug | flag (in order) | role returned |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `COMPASS`, `COMPASSCANADA` | `_isAdmin` | `ADMIN` |
|
|
75
|
+
| | `_isSuperAdmin` | `SUPERUSER` |
|
|
76
|
+
| | `_isSupervisor` | `MANAGER` |
|
|
77
|
+
| | none set | `USER` |
|
|
78
|
+
| `QUAD` | `_isGlobalAdmin` | `GLOBALADMIN` |
|
|
79
|
+
| | `_isBuyer` | `BUYER` |
|
|
80
|
+
| | `_isItShopper` | `ITSHOPPER` |
|
|
81
|
+
| | none set | `USER` |
|
|
82
|
+
| any other client | — | `DEFAULT` |
|
|
83
|
+
|
|
84
|
+
Two things follow that catch people out:
|
|
85
|
+
|
|
86
|
+
- **Which flags are even requested is per-client**, declared in
|
|
87
|
+
`src/globalFieldsConfig/clientLoginFields.json` (`userApiFields`). A flag the client does not
|
|
88
|
+
request is never fetched, so `resolveRole` cannot see it. `NYCHH`, for example, has no entry, so
|
|
89
|
+
no role flags are requested and the role is always `DEFAULT`.
|
|
90
|
+
- **An ungranted or unrequested field comes back ABSENT, not null**, from the API (see the
|
|
91
|
+
[ACL permission chain](../../_underscore/features/acl-permission-chain.md)) — so `user._isAdmin`
|
|
92
|
+
being `undefined` is the normal shape, not a bug.
|
|
93
|
+
|
|
94
|
+
The Cypress harness mirrors this table as `roleFlags` per tenant in `cypress/support/tenants.ts`;
|
|
95
|
+
keep the two in step. See [Cypress testing harness](../workflows/cypress-testing.md).
|
|
96
|
+
|
|
64
97
|
### By client × role vs. by client only
|
|
65
98
|
|
|
66
99
|
- **Client × role** (e.g. `salesOrdersPageFields` — ADMIN vs MANAGER see different filter buttons):
|
|
@@ -168,6 +201,11 @@ modal's chrome from the same per-client JSON, not from the component:
|
|
|
168
201
|
`vendorItemRecordViewFields` are record-modal-layout examples living under `src/layout/.../viewModel/FIELDS/`.
|
|
169
202
|
|
|
170
203
|
## Change history
|
|
204
|
+
- 2026-09-03 — Documented the actual `resolveRole` flag → role table (COMPASS/COMPASSCANADA:
|
|
205
|
+
`_isAdmin`/`_isSuperAdmin`/`_isSupervisor`, in that order, else `USER`; QUAD:
|
|
206
|
+
`_isGlobalAdmin`/`_isBuyer`/`_isItShopper`, else `USER`; every other client `DEFAULT`) and the
|
|
207
|
+
fact that which flags are requested is itself per-client, via
|
|
208
|
+
`globalFieldsConfig/clientLoginFields.json` — so NYCHH is always `DEFAULT`. (apeterson)
|
|
171
209
|
- 2026-07-20 — Extracted the **API-fetch projection** out of these presentation JSON blobs into a
|
|
172
210
|
new per-page `apiFields.json` + `useClientApiFields` mechanism (see
|
|
173
211
|
[client-api-fetch-fields](client-api-fetch-fields.md)). The `salesOrderDetailsConfig` block was
|
|
@@ -28,6 +28,11 @@ files:
|
|
|
28
28
|
- toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getVisibleSections.ts
|
|
29
29
|
- toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderApprovalSummaryGrid.tsx
|
|
30
30
|
- toga25-supply/src/surface/useFetchSurfaceMeta.ts
|
|
31
|
+
- toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx
|
|
32
|
+
- toga25-supply/src/pages/VendorItems/viewModel/useVendorItemsPageViewModel.tsx
|
|
33
|
+
- toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx
|
|
34
|
+
- toga25-supply/src/pages/Bundles/viewModel/useBundlesPageViewModel.tsx
|
|
35
|
+
- toga25-supply/src/layout/ItemFulfillmentModal/useItemFulfillmentModalViewModel.tsx
|
|
31
36
|
- toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts
|
|
32
37
|
- toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/ServiceRequestRecordModalLayout.tsx
|
|
33
38
|
- toga25-supply/src/pages/ServiceRequests/view/ServiceRequestRecordModalLayout/view/ServiceRequestsView.tsx
|
|
@@ -78,6 +83,7 @@ files:
|
|
|
78
83
|
- toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/helpers/surfaceBundlesToDecisionFields.ts
|
|
79
84
|
- toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/useApprovalModalViewModel.tsx
|
|
80
85
|
related:
|
|
86
|
+
- ../workflows/cypress-testing.md
|
|
81
87
|
- ../../_underscore/features/surface-resolver.md
|
|
82
88
|
- ../../_underscore/features/sales-order-denial-reason.md
|
|
83
89
|
- meta-driven-table-data.md
|
|
@@ -965,7 +971,43 @@ Final: the parameterized FE map above.
|
|
|
965
971
|
button except **View Log** renders and does nothing on click. Whether the SR modal gets its own
|
|
966
972
|
actions surface is **still an open decision**, not an oversight.
|
|
967
973
|
|
|
974
|
+
## ⚠ The app still calls the legacy `GET /v2/pages/meta`
|
|
975
|
+
|
|
976
|
+
Verified 2026-09-03. The Surface layer replaced `Page::meta` **as the source of nav/ACL/label
|
|
977
|
+
config** — that part of the story holds — but the **endpoint is still being called**. Five view
|
|
978
|
+
models call blox's `useFetchPageMeta` → `getPageMeta` → `apiGet("/pages/meta", {page, app})`:
|
|
979
|
+
|
|
980
|
+
- `src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx` (fires **twice** per landing-page load)
|
|
981
|
+
- `src/pages/VendorItems/viewModel/useVendorItemsPageViewModel.tsx`
|
|
982
|
+
- `src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx`
|
|
983
|
+
- `src/pages/Bundles/viewModel/useBundlesPageViewModel.tsx`
|
|
984
|
+
- `src/layout/ItemFulfillmentModal/useItemFulfillmentModalViewModel.tsx`
|
|
985
|
+
|
|
986
|
+
**Nothing in `src/` reads any field off the result.** The only references to `pageMeta.acl` are
|
|
987
|
+
*comments* in `src/layout/AppLayout/viewModel/useAppLayoutViewModel.ts` noting it was replaced. So
|
|
988
|
+
these are almost certainly dead weight left over from the Surface migration — but until they are
|
|
989
|
+
removed, the app still makes the call, and a request-level change to `/pages/meta` can still hit
|
|
990
|
+
this app.
|
|
991
|
+
|
|
992
|
+
Two consequences:
|
|
993
|
+
|
|
994
|
+
- Any test/stub layer must answer `/pages/meta` or the calls fall through (this is how it was
|
|
995
|
+
found — the Cypress fall-through guardrail flagged it on its **first** run, not a code read).
|
|
996
|
+
See [Cypress testing harness](../workflows/cypress-testing.md).
|
|
997
|
+
- **Removing the five calls is separate, unstarted work.** Stubbing them is not the fix.
|
|
998
|
+
|
|
999
|
+
Related: [legacy page meta](../../_underscore/features/page-meta-context-field-settings.md) and the
|
|
1000
|
+
[ACL permission chain](../../_underscore/features/acl-permission-chain.md) (whose "dead config"
|
|
1001
|
+
claim is about the `navigation-*` **flags** being inert — still true — not about the endpoint being
|
|
1002
|
+
unused).
|
|
1003
|
+
|
|
968
1004
|
## Change history
|
|
1005
|
+
- 2026-09-03 — Recorded that the 2.5 app **still calls `GET /v2/pages/meta`** from five view
|
|
1006
|
+
models (SalesOrders — twice per load — VendorItems, Inventory, Bundles, ItemFulfillmentModal)
|
|
1007
|
+
even though nothing in `src/` reads a field off the result. Found by the new Cypress
|
|
1008
|
+
fall-through guardrail. Qualifies, does not overturn, the "Page::meta is replaced" claim: the
|
|
1009
|
+
nav/ACL config really is surface-driven; the endpoint call is leftover. (apeterson)
|
|
1010
|
+
|
|
969
1011
|
- 2026-09-03 — **Built the stacked-Total `+` joiner in the Approval Workflow modal**
|
|
970
1012
|
(`withStackedCurrencyJoiner`, new `RecordApprovalModal/helpers/stackedCurrencyJoiner.ts` + 13
|
|
971
1013
|
vitest cases; one-line wiring in `RecordApprovalModalLayout` `renderField`, workflow modal only —
|