toga-ai 1.0.605 → 1.0.607
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/test/INDEX.md +1 -0
- package/knowledge/1.0/apps/test/features/compass-retrofix2-sql-generator.md +102 -0
- package/knowledge/1.0/apps/tools/INDEX.md +2 -2
- package/knowledge/1.0/apps/tools/architecture.md +36 -17
- package/knowledge/1.0/apps/tools/features/clickup-sprint-dashboard.md +69 -12
- package/knowledge/1.0/apps/tools/features/oneuptime-monitor-status-panel.md +235 -235
- package/knowledge/2.0/apps/_underscore/INDEX.md +1 -1
- package/knowledge/2.0/apps/_underscore/features/email-send-pipeline.md +22 -1
- package/knowledge/2.0/apps/_underscore/features/error-reporting-issue-event.md +16 -1
- package/knowledge/2.0/apps/api2/INDEX.md +1 -1
- package/knowledge/2.0/apps/api2/features/tableview-field-metadata.md +40 -0
- package/knowledge/2.0/apps/dbchanges2/features/surface-layer-schema.md +14 -1
- package/knowledge/2.0/apps/toga25-supply/INDEX.md +1 -1
- package/knowledge/2.0/apps/toga25-supply/features/surface-frontend.md +38 -1
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/compass-usa/workflows/order-lifecycle-and-data-integrity.md +41 -7
- package/knowledge/clients/quad/profile.md +9 -0
- package/package.json +1 -1
|
@@ -7,11 +7,13 @@ client: shared
|
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
9
|
updated: 2026-08-13
|
|
10
|
-
owners: ["bala", "tcox", "dfranks"]
|
|
10
|
+
owners: ["bala", "tcox", "dfranks", "apeterson"]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Email.php
|
|
13
13
|
- _underscore/String.php
|
|
14
14
|
- worker2/Worker/Infrastructure/Email/Send.php
|
|
15
|
+
- _underscore/Model/Client/Logs/Email.php
|
|
16
|
+
- _underscore/Model/Client/Logs/EmailAttachment.php
|
|
15
17
|
related:
|
|
16
18
|
- email-template-sending.md
|
|
17
19
|
- string-html-entity-helpers.md
|
|
@@ -84,6 +86,19 @@ derive one from the other).
|
|
|
84
86
|
reading the row, not by waiting for mail.
|
|
85
87
|
- **~1-minute latency, not instant.** Anything that assumes an email is sent synchronously at the
|
|
86
88
|
`send()` call is wrong — transmission happens on the next Send-worker tick.
|
|
89
|
+
- **⚠ The `Logs_<Client>.Email` + `EmailAttachment` queue tables must be PROVISIONED, or every
|
|
90
|
+
send throws `Table 'logs_<client>.email' doesn't exist`.** The two tables the pipeline INSERTs
|
|
91
|
+
into (`Email`, `EmailAttachment`, accessed via the `DB_CLIENT_LOGS` alias) are backed only by the
|
|
92
|
+
models `_underscore/Model/Client/Logs/Email.php` + `EmailAttachment.php` — **no dbchanges2
|
|
93
|
+
migration ever created them**, so a client whose `Logs_<Client>` schema predates (or never ran)
|
|
94
|
+
the create is missing them, and the very first `_Email::send()` for that client 500s *after* the
|
|
95
|
+
calling code is otherwise correct. Symptom seen on `logs_quad`: `"Table 'logs_quad.email' doesn't
|
|
96
|
+
exist"` immediately after the Quad approval-email code fix. Interim fix = hand-authored
|
|
97
|
+
`CREATE TABLE` DDL matching the `Logs_<Client>` house style (`status` ENUM
|
|
98
|
+
`PENDING`/`SENT`/`FAILED`; `body`/`to`/`cc`/`bcc` as text; **no FKs**, per the logs-DB
|
|
99
|
+
convention). **Durable follow-up (pending): add the `Email`/`EmailAttachment` tables to the
|
|
100
|
+
`Logs_<Client>` blank-client template + a dated dbchanges2 migration so every environment/client
|
|
101
|
+
inherits them.**
|
|
87
102
|
- **The Send worker forces HTML mode.** `sendEmail()` calls `$mailer->IsHTML(true)`
|
|
88
103
|
**unconditionally**, ignoring the sender's `setIsHtml(false)`. A caller that queued a
|
|
89
104
|
"plain-text" message is still transmitted HTML-mode. Author bodies accordingly.
|
|
@@ -160,6 +175,12 @@ derive one from the other).
|
|
|
160
175
|
for a hotfix.
|
|
161
176
|
|
|
162
177
|
## Change history
|
|
178
|
+
- 2026-08-18 — Recorded that the `Logs_<Client>.Email` + `EmailAttachment` queue tables are
|
|
179
|
+
**not created by any dbchanges2 migration** (only the models exist), so a client whose
|
|
180
|
+
`Logs_<Client>` schema lacks them 500s on the first `send()` with `"Table 'logs_<client>.email'
|
|
181
|
+
doesn't exist"` — hit on `logs_quad` right after the Quad approval-email fix. Interim = hand
|
|
182
|
+
DDL (status enum, text body/to/cc/bcc, no FKs); durable follow-up = add both tables to the
|
|
183
|
+
`Logs_<Client>` blank template + a dated migration. (apeterson)
|
|
163
184
|
- 2026-08-13 — Documented that **`send()` resolves the `Logs_<Client>` database itself** from the
|
|
164
185
|
client identifier (`Core.Clients` → `Databases` → `DatabaseHosts` for the environment + region) and
|
|
165
186
|
**throws unless exactly one row matches** — so `setClientIdentifier()` takes a lookup key that must
|
|
@@ -7,7 +7,7 @@ client: shared
|
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
9
|
updated: 2026-08-12
|
|
10
|
-
owners: ["dfranks", "jcardinal", "mhammontree", "ajean", "bala"]
|
|
10
|
+
owners: ["dfranks", "jcardinal", "mhammontree", "ajean", "bala", "apeterson"]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Error.php
|
|
13
13
|
- _underscore/Database.php
|
|
@@ -485,6 +485,15 @@ gracefully — it silently deletes all observability for the whole tier. Any cha
|
|
|
485
485
|
- **⚠ A missing `Logs.Issue` column silently disables ALL error capture on that environment** —
|
|
486
486
|
see the open defect above. Symptom: `identifiers.captureFailure` in the API response and nothing
|
|
487
487
|
anywhere else.
|
|
488
|
+
- **⚠ The whole `Logs` cluster schema must be PROVISIONED on every environment, or capture
|
|
489
|
+
itself throws and swallows the real error.** The six singular `Logs` tables
|
|
490
|
+
(`Issue`/`Event`/`IssueFingerprint`/…) are created by the `dbchanges2/Logs/` migrations; an
|
|
491
|
+
environment whose `Logs` DB never ran them (or ran a partial subset) fails *inside*
|
|
492
|
+
`_Error::captureException()` with e.g. `"Table 'Logs.IssueFingerprint' doesn't exist"`. Because
|
|
493
|
+
this is the error-capture layer, the failure masks the **original** exception, so a real 500 on
|
|
494
|
+
that box may be under-logged or invisible. Seen on the client sandbox environment (`IssueFingerprint`
|
|
495
|
+
absent). Fix = run the `dbchanges2/Logs/` migrations on that environment's `Logs` DB. Diagnose a
|
|
496
|
+
suspiciously-empty capture by confirming the `Logs` tables exist before chasing the app code.
|
|
488
497
|
- **Read-your-writes: reads go to the READ host and cannot see an uncommitted write.** A
|
|
489
498
|
second `$issue->save()` re-reads the row via `_Model::initialize()`, and that SELECT goes to
|
|
490
499
|
the read host — a separate connection that cannot see the still-uncommitted INSERT on the
|
|
@@ -652,6 +661,12 @@ clientUserId). **Neither was built.** As built instead:
|
|
|
652
661
|
|
|
653
662
|
## Change history
|
|
654
663
|
|
|
664
|
+
- 2026-08-18 — Recorded the environment-provisioning gotcha: the `Logs` cluster tables
|
|
665
|
+
(`Issue`/`Event`/`IssueFingerprint`/…) come from the `dbchanges2/Logs/` migrations, and an
|
|
666
|
+
environment missing them fails inside `_Error::captureException()` (e.g. `"Table
|
|
667
|
+
'Logs.IssueFingerprint' doesn't exist"`), masking the original exception. Seen on the client
|
|
668
|
+
sandbox environment. (apeterson)
|
|
669
|
+
|
|
655
670
|
- 2026-08-12 — **Corrected the 2026-08-06 open defect and marked it RESOLVED.** The sandbox-dev
|
|
656
671
|
`Logs.Issue` drift was **seven** missing columns, not one: `clickupPriority` (`2026-08-03a`) **plus the
|
|
657
672
|
whole `2026-08-05a` lifecycle migration** (`status`, `dtAutoResolved`, `baselineGapSeconds`,
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
| [TOGa IQ Sprint Dashboard API (Record Scripts)](features/sprint-dashboard-api.md) | The internal **TOGa IQ sprint dashboard** is served in production by **six api2 Record Scripts** on `_Model_Team_Sprint` (`_underscore/Model/Team/Sprint.php`), | _underscore/Model/Team/Sprint.php, api2/Component/Api/V2/V2.php, dbchanges2/Core/2026-07-24a - SprintDashboardRecordScripts.sql, dbchanges2/Client_True/2026-07-24b - SprintDashboardScriptAcl.sql |
|
|
19
19
|
| [Surface action-state via the surface=<slug> request option (M2M-safe)](features/surface-meta-option.md) | An opt-in V2 engine request option, `surface=<slug>`, that attaches per-record UI action state (`isVisible`/`isEnabled`) to a GET response **under `meta.surface | api2/Component/Api/V2/V2.php, _underscore/Model/Core/Surface.php |
|
|
20
20
|
| [TableView row-filtering via apiWhereClause (options.where grammar, end to end)](features/tableview-apiwhereclause-row-filtering.md) | `TableViews.apiWhereClause` (TEXT, nullable) is the sanctioned, code-free way to restrict or exclude rows from a 2.0 table view. | api2/Component/Api/V2/V2.php, _underscore/Model/Client/TableView.php, toga2-supply/src/api/toga.ts |
|
|
21
|
-
| [TableView field/column metadata (TableViewFields, hidden projected columns)](features/tableview-field-metadata.md) | The columns of a 2.0 table view are defined by DB metadata, not code. | _underscore/Model/Client/TableView.php, api2/Component/Api/V2/V2.php, dbchanges2/Client/2026-07-20 - ItemsUuidForPurchaseOrderItemsTableView.sql, dbchanges2/Core/2026-08-17a - ItemFulfillmentQuantityFieldTypeNumber.sql, dbchanges2/Client/2026-08-17a - ItemFulfillmentColumnsCopyable.sql |
|
|
21
|
+
| [TableView field/column metadata (TableViewFields, hidden projected columns)](features/tableview-field-metadata.md) | The columns of a 2.0 table view are defined by DB metadata, not code. | _underscore/Model/Client/TableView.php, api2/Component/Api/V2/V2.php, dbchanges2/Client/2026-07-20 - ItemsUuidForPurchaseOrderItemsTableView.sql, dbchanges2/Core/2026-08-17a - ItemFulfillmentQuantityFieldTypeNumber.sql, dbchanges2/Client/2026-08-17a - ItemFulfillmentColumnsCopyable.sql, dbchanges2/Client_Quad/2026-08-18c - SalesOrderListingSortByDateOrderDesc.sql |
|
|
22
22
|
| [Tickets API (/v2/tickets)](features/tickets-api.md) | The generic ticket endpoint of the 2.0 REST API. | Component/Api/V2/V2.php |
|
|
23
23
|
| [V2 API error/message codes (EV/EZ troubleshooting map)](features/v2-api-error-codes.md) | The V2 JSON engine (`Component/Api/V2/V2.php`) returns short **message codes** in the response `error` field, grouped by family: `EN-*` authentication, `EZ-*` a | api2/Component/Api/V2/V2.php, api2/Component/Api/V2/Response/Response.php, api2/Component/Api/V2/Response/Oauth/Oauth.php, api2/Controller/Index.php, toga2-supply/src/globalTypes.ts, toga2-supply/src/pages/Orders/api/OrdersApi.ts, toga2-supply/src/api/toga.ts, _underscore/Model/Client/TrackingNumber.php |
|
|
24
24
|
| [V2 request deadlock-retry — route-scoped in-process replay](features/v2-deadlock-retry.md) | api2's front controller can **detect a MySQL deadlock (1213) / lock-wait timeout (1205) and replay the whole request in-process**, so a transient lock collision | api2/Controller/Index.php, _underscore/Database.php, _underscore/Query.php |
|
|
@@ -14,6 +14,7 @@ files:
|
|
|
14
14
|
- dbchanges2/Client/2026-07-20 - ItemsUuidForPurchaseOrderItemsTableView.sql
|
|
15
15
|
- dbchanges2/Core/2026-08-17a - ItemFulfillmentQuantityFieldTypeNumber.sql
|
|
16
16
|
- dbchanges2/Client/2026-08-17a - ItemFulfillmentColumnsCopyable.sql
|
|
17
|
+
- dbchanges2/Client_Quad/2026-08-18c - SalesOrderListingSortByDateOrderDesc.sql
|
|
17
18
|
related:
|
|
18
19
|
- tableview-apiwhereclause-row-filtering.md
|
|
19
20
|
- ../../toga25-supply/features/meta-driven-table-data.md
|
|
@@ -93,8 +94,42 @@ per-client folder — the reference migration
|
|
|
93
94
|
`dbchanges2/Client/2026-07-20 - ItemsUuidForPurchaseOrderItemsTableView.sql` adds the hidden
|
|
94
95
|
`Items.uuid` column to the `items-for-purchase-orders` view and was verified against `Client_Nychh`.
|
|
95
96
|
|
|
97
|
+
## Default sort is DB-driven (`TableViews.sortPrimary*`), emitted as `meta.table.sort`
|
|
98
|
+
|
|
99
|
+
A view's **default sort** is not code — it lives on the `Client_*.TableViews` row:
|
|
100
|
+
|
|
101
|
+
- **`sortPrimaryTableViewFieldId`** → FK to `TableViewFields.id` (the column to sort by), plus
|
|
102
|
+
**`sortPrimaryDirection`** (`'ASC'`/`'DESC'`).
|
|
103
|
+
- **`sortSecondaryTableViewFieldId`** (+ its direction) for a tie-break sort.
|
|
104
|
+
|
|
105
|
+
The API emits it as **`meta.table.sort = [{slug, dir}]`** (the `slug` is the referenced
|
|
106
|
+
`TableViewFields.slug`), which the frontend applies as the initial ordering.
|
|
107
|
+
|
|
108
|
+
**To change a listing's default sort per client, use a multi-table JOIN UPDATE — never a
|
|
109
|
+
subquery on `TableViews`.** Because `sortPrimaryTableViewFieldId` points at a
|
|
110
|
+
`TableViewFields` row of the *same* view, a `SET ... = (SELECT id FROM TableViewFields WHERE
|
|
111
|
+
tableViewId = TableViews.id ...)` self-references `TableViews` and MySQL rejects it with
|
|
112
|
+
**error 1093** ("can't specify target table for update in FROM clause"). Instead JOIN
|
|
113
|
+
`TableViewFields` on its `slug` and set the id from the joined row:
|
|
114
|
+
|
|
115
|
+
```sql
|
|
116
|
+
UPDATE TableViews tv
|
|
117
|
+
JOIN TableViewFields tvf
|
|
118
|
+
ON tvf.tableViewId = tv.id AND tvf.slug = 'dateOrder'
|
|
119
|
+
SET tv.sortPrimaryTableViewFieldId = tvf.id,
|
|
120
|
+
tv.sortPrimaryDirection = 'DESC'
|
|
121
|
+
WHERE tv.slug = '<listing-slug>';
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
This is the same shape as the `Client_Compass` item-fulfillment view's default-sort override.
|
|
125
|
+
Applied for Quad: the sales-orders listing now defaults to `dateOrder DESC` (most recent
|
|
126
|
+
first) instead of `number DESC` (`dbchanges2/Client_Quad/2026-08-18c -
|
|
127
|
+
SalesOrderListingSortByDateOrderDesc.sql`).
|
|
128
|
+
|
|
96
129
|
## Gotchas / known issues
|
|
97
130
|
|
|
131
|
+
- **Default-sort updates must JOIN `TableViewFields`, not subquery `TableViews`** — a subquery
|
|
132
|
+
on the same view triggers MySQL error 1093. See *Default sort* above.
|
|
98
133
|
- **`recordFieldId` is cross-DB** — it indexes `Core.RecordFields`, not anything in the client DB.
|
|
99
134
|
Resolve the id in `Core`, but insert the `TableViewFields` row in the client DB.
|
|
100
135
|
- **Don't assume `Core.RecordFields`/`Core.Records` ids are stable across environments** — look them
|
|
@@ -108,6 +143,11 @@ per-client folder — the reference migration
|
|
|
108
143
|
|
|
109
144
|
## Change history
|
|
110
145
|
|
|
146
|
+
- 2026-08-18 — Documented DB-driven **default sort**: `TableViews.sortPrimaryTableViewFieldId`
|
|
147
|
+
(+ `sortPrimaryDirection`, and the secondary pair) FK into `TableViewFields`, emitted as
|
|
148
|
+
`meta.table.sort=[{slug,dir}]`; and the JOIN-`TableViewFields`-on-slug UPDATE recipe that
|
|
149
|
+
avoids MySQL error 1093 (a self-subquery on `TableViews` is rejected). Applied to Quad's
|
|
150
|
+
sales-orders listing (default `dateOrder DESC`). (apeterson)
|
|
111
151
|
- 2026-08-17 — Documented that a column's `type`/`precision` are canonical from `Core.RecordFields`
|
|
112
152
|
(no per-view override — a change hits every view/client) while `isCopyable`/`isSortable`/
|
|
113
153
|
`isFilterable`/`isVisible` are per-view `TableViewFields` flags. Confirmed setting
|
|
@@ -6,7 +6,7 @@ project: Database Changes
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-18
|
|
10
10
|
owners: [jcardinal, apeterson]
|
|
11
11
|
files:
|
|
12
12
|
- dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql
|
|
@@ -641,6 +641,19 @@ rule resumes.
|
|
|
641
641
|
messageKey; appId/recordId inherited from the `order-details` sibling by slug), Core→Core only
|
|
642
642
|
(prod-safe). Per-client SurfaceOverride divergence not yet seeded — shared Core default + FE wiring
|
|
643
643
|
only. (apeterson)
|
|
644
|
+
- 2026-08-18 — Seeded the **"Enter PO Details" row-action `MENU_ITEM`** on the
|
|
645
|
+
`sales-order-listing-row-actions` surface (id 3): `Core/2026-08-18a` adds Message
|
|
646
|
+
`salesOrder.action.enterPo` + Action `salesOrder.enterPo` (`OPEN_MODAL`,
|
|
647
|
+
`config {kind:poNumber, valueKey:poNumber}`) + a `renderType='MENU_ITEM'` element, seeded Core
|
|
648
|
+
**neutral off** (`isVisible=0`/`isEnabled=0`). A row action needs a full Action+Message+element
|
|
649
|
+
triple (not a clone of the record-actions BUTTON — different FE dispatch seam, see
|
|
650
|
+
[surface-frontend](../../toga25-supply/features/surface-frontend.md)). Quad opts in via
|
|
651
|
+
`Client_Quad/2026-08-18b` (`IS_VISIBLE=1` client-wide, `IS_ENABLED=1` roles 7/8/9,
|
|
652
|
+
`VISIBILITY_RULE` `order._status in [pendingApproval]`). Separately, `Client_Quad/2026-08-18a`
|
|
653
|
+
gates the existing record-actions "Enter PO Details" **BUTTON** (surface
|
|
654
|
+
`sales-order-record-actions`, `config.valueKey=poNumber`) with a `VISIBILITY_RULE` limited to
|
|
655
|
+
`pendingApproval` — Quad is single-stage (only `pendingApproval`), whereas Compass/CompassCanada
|
|
656
|
+
widen the same gate to include `pendingInitialApproval`. (apeterson)
|
|
644
657
|
- 2026-07-21 — Added the sales-order **Approve enable-gate** per-client overrides (`Client_*/2026-07-21a`):
|
|
645
658
|
Compass/CC ADMIN `ENABLED_RULE` = `(pendingInitialApproval AND stepTwoAssigned) OR pendingApproval`
|
|
646
659
|
on surface 8 el 17 + surface 3 el 29 (rule JSON in `c_longValue`; Compass UPDATEs its existing
|
|
@@ -10,6 +10,6 @@
|
|
|
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/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/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/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 |
|
|
13
|
-
| [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/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/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/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 |
|
|
13
|
+
| [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/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/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 |
|
|
14
14
|
| [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 |
|
|
15
15
|
| [Cypress Testing Harness (component + e2e)](workflows/cypress-testing.md) | The Cypress test harness for the `toga25-supply` frontend, bootstrapped from scratch (`cypress` was already a dependency but there was no config, no `cypress/` | toga25-supply/cypress.config.ts, toga25-supply/cypress/tsconfig.json, toga25-supply/cypress/support/component.tsx, toga25-supply/cypress/support/component-index.html, toga25-supply/cypress/support/e2e.ts, toga25-supply/cypress/support/commands.ts, toga25-supply/cypress/support/fixtures.ts, toga25-supply/cypress/support/mocks/useApprovalModalViewModel.ts, toga25-supply/cypress/component/SalesOrderApprovalModalsLayout.cy.tsx, toga25-supply/cypress/component/RecordApprovalModalLayout.cy.tsx, toga25-supply/cypress/component/EnterPoNumberModal.cy.tsx, toga25-supply/cypress/e2e/salesOrderApproval.cy.ts |
|
|
@@ -6,7 +6,7 @@ project: TOGa 2.5 Supply
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-18
|
|
10
10
|
owners: [jcardinal, apeterson]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-supply/src/surface/applyColSpan.ts
|
|
@@ -39,6 +39,7 @@ files:
|
|
|
39
39
|
- toga25-supply/src/pages/Login/LoginPage.tsx
|
|
40
40
|
- toga25-supply/src/pages/SalesOrders/SalesOrders.tsx
|
|
41
41
|
- toga25-supply/src/pages/SalesOrders/view/SurfaceRowActions.tsx
|
|
42
|
+
- toga25-supply/src/pages/SalesOrders/hooks/useSalesOrderVip.ts
|
|
42
43
|
- toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx
|
|
43
44
|
- toga25-supply/src/pages/Items/ItemsPage.tsx
|
|
44
45
|
- toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx
|
|
@@ -115,6 +116,24 @@ feature flag — see below). Backend: [surface-resolver](../../_underscore/featu
|
|
|
115
116
|
change.
|
|
116
117
|
- **`actionRegistry`** — string action key → existing app handler (the proven `MODAL_RENDERERS`
|
|
117
118
|
pattern). The DB ships keys + payloads; the app supplies the actual handlers.
|
|
119
|
+
|
|
120
|
+
### Row-menu `MENU_ITEM` dispatch is a DIFFERENT path than a record-actions `BUTTON` — you cannot clone one as the other
|
|
121
|
+
The listing **row menu** (`SurfaceRowActions.tsx`) and the record-modal **action bar**
|
|
122
|
+
(`SalesOrderTopBar` / `SurfaceActionBar`) dispatch through two distinct seams, so moving a
|
|
123
|
+
control from one surface to the other is **not** a copy of the element:
|
|
124
|
+
- **Row menu items** render as **`renderType='MENU_ITEM'`**, take their label from a
|
|
125
|
+
**`Core.Messages`** row, and dispatch **by `action.key` → `actionRegistry`**. So a new row
|
|
126
|
+
action needs a real **`Core.Action`** + a label **`Core.Message`** + an **`actionRegistry`
|
|
127
|
+
handler** keyed by that action.key.
|
|
128
|
+
- **Record-actions controls** render as **`renderType='BUTTON'`** and dispatch **by
|
|
129
|
+
`config.valueKey`** through `SalesOrderTopBar` (not the action.key/registry path).
|
|
130
|
+
- Therefore you **cannot** duplicate a record-actions BUTTON element onto a row-actions surface
|
|
131
|
+
and expect it to work — a row item must be authored as a MENU_ITEM with its own Action +
|
|
132
|
+
Message + registry handler. Concrete: "Enter PO Details" existed only as the record-actions
|
|
133
|
+
BUTTON; adding it to the `sales-order-listing-row-actions` surface required a new
|
|
134
|
+
`salesOrder.action.enterPo` Message + `salesOrder.enterPo` Action (`OPEN_MODAL`,
|
|
135
|
+
`config {kind:poNumber, valueKey:poNumber}`) + an `actionRegistry["salesOrder.enterPo"]`
|
|
136
|
+
entry (`forwardToDispatch("poNumber")` → `setApprovalModal({modalSelected:'poNumber'})`).
|
|
118
137
|
- **theme/message resolvers** (`resolve.ts`) — token → CSS and ICU message rendering.
|
|
119
138
|
- **`SurfaceActionBar`/`SurfaceActions` + `SurfaceSection`** — generic renderers.
|
|
120
139
|
|
|
@@ -512,6 +531,16 @@ developer asked for the **whole component**, not just badges. The old
|
|
|
512
531
|
|
|
513
532
|
## Gotchas
|
|
514
533
|
|
|
534
|
+
- **The VIP `/contacts` API call is gated by an approval-details element's `labelSuffix.valueKey`,
|
|
535
|
+
NOT by the element's `isVisible`.** `useSalesOrderVip.ts` decides whether to call `/contacts`
|
|
536
|
+
(which reads `Users.c_isVip`) by scanning the approval-details surface for an element whose
|
|
537
|
+
`labelSuffix.valueKey` **ends in `isVip`**. So for a client that does not have the VIP chip
|
|
538
|
+
configured (e.g. Quad), that call 403s `EZ-2` on `Users.c_isVip` unless the VIP `labelSuffix` is
|
|
539
|
+
stripped from the surface. `Core/2026-08-10a` strips the VIP `labelSuffix` from the Core base
|
|
540
|
+
surface (`JSON_REMOVE config.$.labelSuffix`); Compass + Compass Canada re-add the gold VIP chip
|
|
541
|
+
via a `CONFIG` override. Applying the Core seed turns VIP off for Quad/all-non-Compass while
|
|
542
|
+
keeping it on for Compass/Compass Canada — **no FE change needed**. The gate is the
|
|
543
|
+
`labelSuffix.valueKey`, not the visibility flag.
|
|
515
544
|
- **A named predicate that FAILS OPEN silently enables a button when its data isn't fetched.**
|
|
516
545
|
`stepTwoAssigned` returns `true` when `order.currentStage` is `null` (a fail-OPEN default). So if the
|
|
517
546
|
surface's approval-stage data is not plumbed in (e.g. `approvalsEnabled=false`, so
|
|
@@ -634,6 +663,14 @@ developer asked for the **whole component**, not just badges. The old
|
|
|
634
663
|
treat type-checking as pending. Runtime `GET /v2/surfaces/{slug}/meta` also not yet exercised.
|
|
635
664
|
|
|
636
665
|
## Change history
|
|
666
|
+
- 2026-08-18 — Documented that a listing **row-menu `MENU_ITEM`** dispatches by
|
|
667
|
+
`action.key`→`actionRegistry` (label from `Core.Messages`), a **different seam** than a
|
|
668
|
+
record-actions **`BUTTON`** (dispatched by `config.valueKey` via `SalesOrderTopBar`) — so a
|
|
669
|
+
BUTTON element cannot be cloned onto a row-actions surface; a row action needs its own Action +
|
|
670
|
+
Message + registry handler (built "Enter PO Details" as `salesOrder.enterPo`). Also recorded that
|
|
671
|
+
`useSalesOrderVip` gates the `/contacts` (`Users.c_isVip`) call by an approval-details element's
|
|
672
|
+
`labelSuffix.valueKey` ending in `isVip` — stripping the VIP `labelSuffix` (Core/2026-08-10a)
|
|
673
|
+
turns the call off for non-Compass clients (Quad) with no FE change. (apeterson)
|
|
637
674
|
- 2026-08-14 — Moved the whole Sales Order **Admin Notes** component into Surface (badge labels/colors,
|
|
638
675
|
chrome labels, layout toggles — not just badges), retiring the hardcoded
|
|
639
676
|
`addNotesFields.json → fieldsConfig → model → view` plumbing. New **`buildNotesComponent()`**
|
package/knowledge/INDEX.md
CHANGED
|
@@ -12,7 +12,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
12
12
|
- **togaview** (TOGa View) — 7 doc(s) → [1.0/apps/togaview/INDEX.md](1.0/apps/togaview/INDEX.md)
|
|
13
13
|
- **webhook** (Webhook) — 1 doc(s) → [1.0/apps/webhook/INDEX.md](1.0/apps/webhook/INDEX.md)
|
|
14
14
|
- **walmarttechservices** (Walmart Tech Services) — 1 doc(s) → [1.0/apps/walmarttechservices/INDEX.md](1.0/apps/walmarttechservices/INDEX.md)
|
|
15
|
-
- **test** (Test) —
|
|
15
|
+
- **test** (Test) — 15 doc(s) → [1.0/apps/test/INDEX.md](1.0/apps/test/INDEX.md)
|
|
16
16
|
- **toga** (TOGa) — 3 doc(s) → [1.0/apps/toga/INDEX.md](1.0/apps/toga/INDEX.md)
|
|
17
17
|
- **tools** (Tools) — 15 doc(s) → [1.0/apps/tools/INDEX.md](1.0/apps/tools/INDEX.md)
|
|
18
18
|
|
|
@@ -5,10 +5,11 @@ project: _Underscore
|
|
|
5
5
|
client: compass-usa
|
|
6
6
|
type: workflow
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-08-
|
|
8
|
+
updated: 2026-08-18
|
|
9
9
|
owners: ["jcardinal", "bala", "dfranks"]
|
|
10
10
|
files: []
|
|
11
11
|
related:
|
|
12
|
+
- 1.0/apps/test/features/compass-retrofix2-sql-generator.md
|
|
12
13
|
- clients/compass-usa/profile.md
|
|
13
14
|
- clients/compass-usa/features/asn-to-item-fulfillment.md
|
|
14
15
|
- clients/compass-usa/features/mits-po-to-so-item-linking.md
|
|
@@ -71,6 +72,27 @@ back-link chain climbs **Agilant → ODP → Compass**. Item back-links use
|
|
|
71
72
|
`ItemFulfillmentItems.upstreamItemFulfillmentItemId` the same direction and may be many-downstream →
|
|
72
73
|
one-upstream for bundles.
|
|
73
74
|
|
|
75
|
+
> **Mirror-gap root cause — a missing item bridge stalls the climb (verified 2026-08-18).** The
|
|
76
|
+
> recursive IF mirror climbs Agilant → ODP → Compass through **BOTH** bridge tables. If the
|
|
77
|
+
> `PurchaseOrderItems_SalesOrderItems` rung linking the Compass/MITS PO item → the ODP SO item is
|
|
78
|
+
> **missing**, the mirror **cannot map past the ODP SO** and never creates the ODP/Compass mirror IFs
|
|
79
|
+
> — the Compass SO shows "nothing fulfilled" even though the Agilant SO has an ItemFulfillment
|
|
80
|
+
> (`c_netsuiteInternalItemFulfillmentId` set, created by NetSuite sync). Orders SA130530, SA130334,
|
|
81
|
+
> SA130430 all had an intact header chain and every other item bridge, but were missing **only** that
|
|
82
|
+
> one rung (confirmed against a healthy order that had it). When diagnosing a Flow-B order that
|
|
83
|
+
> fulfilled upstream but shows empty on Compass, check that specific bridge first.
|
|
84
|
+
|
|
85
|
+
### Asset tags arrive as separate CG "units" (Flow B)
|
|
86
|
+
NetSuite delivers asset tags on **separate service-item lines** whose part number contains
|
|
87
|
+
`ASSETTAG` (e.g. `ODP-COMPASS-ASSETTAG1`). Older Compass imports landed each asset tag as its **own
|
|
88
|
+
`Units` row** whose `serialNumber` holds the CG value (e.g. `CG2000027730`), attached via
|
|
89
|
+
`ItemFulfillmentItemUnits` alongside the real serialized unit on the **same** fulfillment. The
|
|
90
|
+
forward fix (`library/app/api/toga2.php` ~lines 3688–3965) applies those asset tags to the serialized
|
|
91
|
+
items **in order** and sets `Units.assetTag` on the real device. Retroactively this is Phase 0 of the
|
|
92
|
+
[retrofix2 tool](../../../1.0/apps/test/features/compass-retrofix2-sql-generator.md); because units
|
|
93
|
+
are **shared up the chain** (same `Units.id` at every level), the correction must land before the
|
|
94
|
+
fulfillment mirror climbs.
|
|
95
|
+
|
|
74
96
|
## Expected raw-data invariants (for detect-and-repair)
|
|
75
97
|
|
|
76
98
|
1. **Links:** every SOI that should be procured has exactly one PURE bridge to the matching POI on the
|
|
@@ -141,12 +163,18 @@ column is `ON UPDATE CURRENT_TIMESTAMP`), which can look like a fresh modificati
|
|
|
141
163
|
`api2` cXML ShipNotice handler; the `_underscore` recursive item-fulfillment engine.
|
|
142
164
|
|
|
143
165
|
## Retrofix tooling
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
166
|
+
The current repair script is **`test/@jeff/compass/retrofix2.php`** (1.0 `App_`), which
|
|
167
|
+
**supersedes** the older `retrofix.php` sibling in the same folder. Unlike the description below of the
|
|
168
|
+
earlier tool, retrofix2 **emits review-first `.sql`** rather than writing the DB directly. It runs
|
|
169
|
+
three phases newest-first per SO — **Phase 0** (CG-unit asset-tag merge; must run first because units
|
|
170
|
+
are shared up the chain), **Phase 1** (header/link/bridge repairs, levels L1–L4, where L3 fixes the
|
|
171
|
+
`PurchaseOrderItems_SalesOrderItems` rung the mirror needs), **Phase 2** (fulfillment mirror climb).
|
|
172
|
+
Each level is wrapped in `runLevel()` so one level's exception (notably L1's unconditional NetSuite
|
|
173
|
+
call failing) is logged as a skip and swallowed, letting the independent NetSuite-free repairs still
|
|
174
|
+
emit. Write guards: `emit()` hard-blocks `Units` and `TrackingNumbers` writes; the Phase-0-only
|
|
175
|
+
`emitUnit()` permits `Units` UPDATE/DELETE but still blocks `TrackingNumbers`. Full structure in the
|
|
176
|
+
[retrofix2 feature doc](../../../1.0/apps/test/features/compass-retrofix2-sql-generator.md). It is a
|
|
177
|
+
diagnostic/repair tool, not part of the runtime workflow.
|
|
150
178
|
|
|
151
179
|
## Edge cases & escalation
|
|
152
180
|
- Incomplete orders (no PO/ASN/Agilant leg yet) are normal — repair only the layers that exist.
|
|
@@ -155,6 +183,12 @@ runtime workflow.
|
|
|
155
183
|
- High-multiplier over-fulfillment (5×–20×) does not fit the split-PO spurious-link pattern — separate cause.
|
|
156
184
|
|
|
157
185
|
## Change history
|
|
186
|
+
- 2026-08-18 — Recorded the **Flow-B mirror-gap root cause** (a missing `PurchaseOrderItems_SalesOrderItems`
|
|
187
|
+
rung stalls the recursive IF mirror past the ODP SO, so a fulfilled Agilant order shows empty on
|
|
188
|
+
Compass — orders SA130530/SA130334/SA130430), the **CG asset-tag "unit" data shape** (asset tags land
|
|
189
|
+
as their own CG-serial `Units` rows), and repointed the Retrofix tooling section to the new
|
|
190
|
+
**retrofix2.php** (review-first `.sql`; Phase 0/1/2; `runLevel()` level isolation; `emit()`/`emitUnit()`
|
|
191
|
+
write guards). (jcardinal)
|
|
158
192
|
- 2026-08-04 — Added the **"empty shell" order signature** — the ODP importer writes the SO header
|
|
159
193
|
and its items in two separate API requests, so a fatal on the second leaves a committed,
|
|
160
194
|
zero-item order — plus the three orphan sweeps that detect it (zero-item SOs, zero-item POs, ODP
|
|
@@ -82,4 +82,13 @@ Client-specific DB change-sets live in `dbchanges2/Client_Quad/`.
|
|
|
82
82
|
- Quad is one of only **two** clients (with Compass) whose fulfillment views are free of the
|
|
83
83
|
dangling `joinRecordId` 41 / `parentRecordFieldId` 211+321 config, because both were migrated to
|
|
84
84
|
base record 29. Ten other clients still carry it.
|
|
85
|
+
- **Single-stage approval (2026-08-18).** Quad's approval flow has only the `pendingApproval`
|
|
86
|
+
stage (no `pendingInitialApproval`), so its PO-details visibility/enable gates on the
|
|
87
|
+
sales-order surfaces use `order._status in [pendingApproval]` — contrast Compass/Compass Canada,
|
|
88
|
+
which widen the same gate to include `pendingInitialApproval`. Quad opted into the "Enter PO
|
|
89
|
+
Details" **row-action** menu item (`sales-order-listing-row-actions`) client-wide, enabled for
|
|
90
|
+
roles 7/8/9. Seed side: [Surface Layer Schema](../../2.0/apps/dbchanges2/features/surface-layer-schema.md).
|
|
91
|
+
- **Sales-orders listing default sort = `dateOrder DESC` (2026-08-18)** — most recent first
|
|
92
|
+
(was `number DESC`), set on Quad's `TableViews` row. See
|
|
93
|
+
[TableView field/column metadata](../../2.0/apps/api2/features/tableview-field-metadata.md).
|
|
85
94
|
- This profile is a starting point; expand as more Quad-specific behavior is captured.
|
package/package.json
CHANGED