toga-ai 1.0.290 → 1.0.291
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/2.0/apps/_underscore/features/surface-resolver.md +26 -0
- package/knowledge/2.0/apps/dbchanges2/INDEX.md +1 -1
- package/knowledge/2.0/apps/dbchanges2/features/surface-layer-schema.md +41 -3
- package/knowledge/2.0/apps/toga2-commerce/INDEX.md +1 -0
- package/knowledge/2.0/apps/toga2-commerce/features/cart-bundle-submission-and-identity.md +97 -0
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/compass-usa/INDEX.md +1 -0
- package/knowledge/clients/compass-usa/profile.md +9 -2
- package/knowledge/clients/compass-usa/workflows/cross-kit-bundle-corruption.md +113 -0
- package/package.json +1 -1
|
@@ -98,6 +98,26 @@ which surfaced latent resolution gaps in `_buildBundle`/`_resolveElement`:
|
|
|
98
98
|
- `bundle.surface.titleMessageKey` is set from `titleMessageId`.
|
|
99
99
|
- The 2nd `_loadMessages` return is merged into `$messageKeyById`; dangling message ids are logged.
|
|
100
100
|
|
|
101
|
+
## Vocabulary / label per-client relabeling (slugs are internal, labels are messages)
|
|
102
|
+
|
|
103
|
+
Clients **never rename a Vocabulary**. `Vocabularies.slug` (`sales-order-status`) and
|
|
104
|
+
`VocabularyTerms.value` (`pendingApproval`) are **stable internal identifiers, never shown to
|
|
105
|
+
users**. The displayed label always resolves from **`Core.Messages`** (every visible string is a
|
|
106
|
+
message key; per-term labels come from each `VocabularyTerm.labelMessageId`). A client customizes
|
|
107
|
+
the *displayed* name two ways, **without touching Core**:
|
|
108
|
+
|
|
109
|
+
1. **`SurfaceOverrides` row with `attribute = LABEL_MESSAGE`** — scoped by persona/role/language —
|
|
110
|
+
re-points an element's label to a different message.
|
|
111
|
+
2. **`MessageTranslations` row** (per `languageId`; a null translation falls back to
|
|
112
|
+
`Messages.defaultValue`).
|
|
113
|
+
|
|
114
|
+
Because status-comparison / gating rules compare the **`value`**, not the label, relabeling never
|
|
115
|
+
breaks logic.
|
|
116
|
+
|
|
117
|
+
> ⚠ **Unverified — flag for future confirmation:** whether a `SurfaceOverrides.attribute =
|
|
118
|
+
> LABEL_MESSAGE` value stores a **message id** or a **raw message key** is not yet confirmed. Verify
|
|
119
|
+
> against `Model/Core/Surface.php` `_loadOverrides`/`_buildBundle` before relying on either form.
|
|
120
|
+
|
|
101
121
|
## Caching & invalidation
|
|
102
122
|
|
|
103
123
|
- Disk cache keyed `surface:meta:{app}:{slug}:{client}:{sortedPersonaIds}:{sortedRoleIds}:{lang}`.
|
|
@@ -214,6 +234,12 @@ Core record grants + their logic-group expressions all evaluate `all`/`"1"`. The
|
|
|
214
234
|
match Compass, a follow-up migration aligning both `meta` and `meta-group` to roles 1,3,4 is needed.
|
|
215
235
|
|
|
216
236
|
## Change history
|
|
237
|
+
- 2026-07-01 — Clarified vocabulary/label per-client relabeling (new section): Vocabulary `slug`
|
|
238
|
+
and VocabularyTerm `value` are stable internal ids never shown to users; the displayed label
|
|
239
|
+
always resolves from `Core.Messages`; clients relabel via a `SurfaceOverrides` `LABEL_MESSAGE`
|
|
240
|
+
override (persona/role/language-scoped) or a `MessageTranslations` row (null → `defaultValue`),
|
|
241
|
+
and status-comparison rules compare `value` not the label so relabeling never breaks logic.
|
|
242
|
+
Flagged unverified: whether `LABEL_MESSAGE` stores a message id vs a raw key. (apeterson)
|
|
217
243
|
- 2026-07-01 — Documented the full route-level authorization mechanism (new section): the surfaces
|
|
218
244
|
script gate is `Client_<tenant>.AclRecordScripts` keyed on JWT `id.client.roles` (client roles,
|
|
219
245
|
not `core.roles`, not `Core.AclRecordPermissions`); element-drop is a separate
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [Database Changes (dbchanges2) Repository Architecture](architecture.md) | `dbchanges2` is the **schema-migration / SQL change-set repository** for the entire 2.0 platform. | Core/, Client/, Client_<Tenant>/, Logs/, Logs_Client/, _modules/ |
|
|
6
|
-
| [Surface Layer Schema (UI presentation/config tables)](features/surface-layer-schema.md) | The persistent schema for the platform-wide **Surface** UI presentation/configuration layer (see the `_underscore` [surface-resolver](../../_underscore/features | dbchanges2/Core/2026-06-25a - SurfaceCoreTables.sql, dbchanges2/Core/2026-06-25b - SurfaceRecordsAndFields.sql, dbchanges2/Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql, dbchanges2/Core/2026-06-29a - ItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql, dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.sql, dbchanges2/Core/2026-06-29d - VendorItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29e - InventorySurfaceSeed.sql, dbchanges2/Core/2026-06-30a - SurfaceMetaGroupAndSalesOrderSections.sql, dbchanges2/Client/2026-06-30a - SurfaceMetaGroupAcl.sql, dbchanges2/Client_Compass/2026-06-30a - SalesOrderDisplaySectionManagerOverrides.sql, dbchanges2/Client_CompassCanada/2026-06-30a - SalesOrderSurfaceManagerOverrides.sql, dbchanges2/Client_Quad/2026-06-30a - SalesOrderSurfaceClientOverrides.sql, dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client/2026-06-25b - SurfaceClientSeed.sql, dbchanges2/Client/2026-06-25c - SurfaceClientAcl.sql, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql |
|
|
6
|
+
| [Surface Layer Schema (UI presentation/config tables)](features/surface-layer-schema.md) | The persistent schema for the platform-wide **Surface** UI presentation/configuration layer (see the `_underscore` [surface-resolver](../../_underscore/features | dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client_Compass/2026-06-25d - SalesOrderSurfaceClientSeed.sql, _underscore/Model/Client/ThemeToken.php, toga25-supply/src/themeConfig.json, dbchanges2/Core/2026-06-25a - SurfaceCoreTables.sql, dbchanges2/Core/2026-06-25b - SurfaceRecordsAndFields.sql, dbchanges2/Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql, dbchanges2/Core/2026-06-29a - ItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql, dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.sql, dbchanges2/Core/2026-06-29d - VendorItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29e - InventorySurfaceSeed.sql, dbchanges2/Core/2026-06-30a - SurfaceMetaGroupAndSalesOrderSections.sql, dbchanges2/Client/2026-06-30a - SurfaceMetaGroupAcl.sql, dbchanges2/Client_Compass/2026-06-30a - SalesOrderDisplaySectionManagerOverrides.sql, dbchanges2/Client_CompassCanada/2026-06-30a - SalesOrderSurfaceManagerOverrides.sql, dbchanges2/Client_Quad/2026-06-30a - SalesOrderSurfaceClientOverrides.sql, dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client/2026-06-25b - SurfaceClientSeed.sql, dbchanges2/Client/2026-06-25c - SurfaceClientAcl.sql, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql |
|
|
7
7
|
| [2.0 New-Client Onboarding (manual process)](workflows/client-onboarding.md) | How to manually stand up a new 2.0 client (tenant). | Client/, Client_<Tenant>/, Core/, Logs_Client/ |
|
|
@@ -6,9 +6,13 @@ project: Database Changes
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: [jcardinal]
|
|
9
|
+
updated: 2026-07-01
|
|
10
|
+
owners: [jcardinal, apeterson]
|
|
11
11
|
files:
|
|
12
|
+
- dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql
|
|
13
|
+
- dbchanges2/Client_Compass/2026-06-25d - SalesOrderSurfaceClientSeed.sql
|
|
14
|
+
- _underscore/Model/Client/ThemeToken.php
|
|
15
|
+
- toga25-supply/src/themeConfig.json
|
|
12
16
|
- dbchanges2/Core/2026-06-25a - SurfaceCoreTables.sql
|
|
13
17
|
- dbchanges2/Core/2026-06-25b - SurfaceRecordsAndFields.sql
|
|
14
18
|
- dbchanges2/Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql
|
|
@@ -57,13 +61,41 @@ per-tenant deltas live in **Client** (mirrors `TableViews`/ACL/`ItemTranslations
|
|
|
57
61
|
| `Messages` | Core | i18n key catalog + ICU default value |
|
|
58
62
|
| `SurfaceOverrides` | Client | THE single sparse cascade table (+`c_longValue mediumtext`) replacing ~40 EAV tables |
|
|
59
63
|
| `MessageTranslations` | Client | per-language overlay (sidecar pattern) + `c_longValue` |
|
|
60
|
-
| `ThemeTokens` | Client | per-tenant semantic token → value (the only place a hex/icon name lives) |
|
|
64
|
+
| `ThemeTokens` | Client | per-tenant semantic token → value (the only place a hex/icon name lives) — see ThemeTokens section below |
|
|
61
65
|
|
|
62
66
|
`SurfaceOverrides` carries the entire cascade in one sparse table: scope columns
|
|
63
67
|
`personaId`/`roleId`/`languageId` (NULL = not scoped on that axis), an `attribute` ENUM, and
|
|
64
68
|
`value varchar(255)` + `c_longValue mediumtext`. Precedence **base < client < persona < role**
|
|
65
69
|
with language as an orthogonal overlay; the resolver applies it most-specific-last in one pass.
|
|
66
70
|
|
|
71
|
+
## ThemeTokens — per-tenant, physically isolated in each client DB
|
|
72
|
+
|
|
73
|
+
`ThemeTokens` lives **inside each tenant's own database** (`Client_Compass.ThemeTokens`,
|
|
74
|
+
`Client_Quad.ThemeTokens`, `Client_CompassCanada.ThemeTokens`) — **not** in Core. Per-tenant
|
|
75
|
+
physical DB isolation is the multi-tenancy mechanism; there is no confirmed Core-held default
|
|
76
|
+
ThemeTokens table. It is a **flat `slug → value` map** (no nesting):
|
|
77
|
+
|
|
78
|
+
| Column | Type | Notes |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `slug` | varchar(64), UNIQUE | e.g. `color.status.approved` |
|
|
81
|
+
| `category` | ENUM(`COLOR`,`BACKGROUND`,`SPACING`,`RADIUS`,`TYPOGRAPHY`,`ICON`) | |
|
|
82
|
+
| `name` | varchar | human label |
|
|
83
|
+
| `value` | varchar(64) | resolved hex / css unit / icon name |
|
|
84
|
+
|
|
85
|
+
`_Model_Client_ThemeToken.php` is the ORM model; `_Model_Core_Surface::resolve()` reads the
|
|
86
|
+
tenant's tokens during resolve to map element token-slugs → values (see
|
|
87
|
+
[surface-resolver](../../_underscore/features/surface-resolver.md)).
|
|
88
|
+
|
|
89
|
+
**Seed gap (known, not a capability gap):** the current Compass seed
|
|
90
|
+
(`Client_Compass/2026-06-25d - SalesOrderSurfaceClientSeed.sql`) covers **only semantic status
|
|
91
|
+
tokens** (~15 statuses × fg `color.status.*` + bg `bg.status.*` ≈ 30 rows). The ~1004 design-
|
|
92
|
+
foundation CSS vars (`--btn-*`, `--baseInput-*`, `--sideNav-*`, …) are **not** in the DB — they
|
|
93
|
+
still live only in the frontend file `toga25-supply/src/themeConfig.json` (single DEFAULT theme).
|
|
94
|
+
The `category` enum already includes `SPACING`/`RADIUS`/`TYPOGRAPHY`/`ICON`, so the table is
|
|
95
|
+
**designed** to hold the full foundation eventually; it just isn't seeded with it yet. Migrating
|
|
96
|
+
the foundation into `ThemeTokens` rows is the target end-state (see the theming-architecture
|
|
97
|
+
decision, held for senior review).
|
|
98
|
+
|
|
67
99
|
## Migration files (typed columns, not EAV)
|
|
68
100
|
|
|
69
101
|
CREATE order is FK-safe (`Messages → Actions → Vocabularies → VocabularyTerms → Surfaces →
|
|
@@ -158,6 +190,12 @@ SurfaceElements`). Core migrations run first; Client after. The session built/se
|
|
|
158
190
|
because `TOOLTIP_MESSAGE` is numeric-only (cosmetic, deferred).
|
|
159
191
|
|
|
160
192
|
## Change history
|
|
193
|
+
- 2026-07-01 — Documented `ThemeTokens` in detail (new section): it lives **inside each tenant's
|
|
194
|
+
own DB** (physical isolation, no confirmed Core default table), is a flat `slug→value` map with
|
|
195
|
+
the `category` ENUM(`COLOR`/`BACKGROUND`/`SPACING`/`RADIUS`/`TYPOGRAPHY`/`ICON`), and read by
|
|
196
|
+
`resolve()`. Recorded the known seed gap: only status tokens are seeded (~30 rows); the ~1004
|
|
197
|
+
design-foundation vars still live only in `toga25-supply/src/themeConfig.json` — the table is
|
|
198
|
+
designed to hold the foundation but isn't seeded with it yet. (apeterson)
|
|
161
199
|
- 2026-06-30 — Added the SalesOrders SECTION-surface migration seeds: Core `meta-group` RecordScript +
|
|
162
200
|
`order-recurring` + 5 display-toggle SECTION surfaces (each with a `sectionVisibility` marker
|
|
163
201
|
element, since the cascade overrides elements not a surface's own isVisible), the `meta-group`
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [TOGa Commerce (toga2-commerce / commerce2-react) Architecture](architecture.md) | `toga2-commerce` (npm package name **`commerce2-react`**, product name **TOGa Commerce**) is the customer-facing **B2B commerce storefront** of the 2.0 platform | src/main.tsx, src/App.tsx, src/routes.tsx, src/contexts/AuthContext.tsx, src/contexts/helpers/getLoginSettings.ts, src/api/axiosInstance.ts, src/stores/, src/themeConfig/ThemeContext.tsx, src/fieldsConfig/index.ts, src/hooks/useAssignClientFields.ts, vite.config.ts, package.json |
|
|
6
|
+
| [Cart Bundle Submission & the bundleUuid Identity Contract](features/cart-bundle-submission-and-identity.md) | How cart **bundles** (kits) are turned into `SalesOrderItems` when a cart is submitted or an existing order is edited, and the **identity-field contract** every | src/api/syncSalesOrderItemsFromLocalStorageCartToApi.ts, src/utils/formatSalesOrderBundlesFromApi.ts, src/stores/useCartStoreZu.ts, src/pages/OrderDetails/helpers/formatSalesOrderDataFromLocalStorage.ts, src/pages/OrderDetails/view/components/OrderItems.tsx |
|
|
6
7
|
| [Cart Notification Emails — duplicate prevention](features/cart-notification-emails.md) | On the cart "Notifications" section a user can add CC email addresses to an order. | src/pages/Cart/CartPage.tsx, src/pages/Cart/view/cartForm/CartForm.tsx, src/stores/useEmailOptionsStore.ts, src/stores/useCartSalesQuoteZu.ts, src/pages/Cart/viewModel/FIELDS/*/*/*/CARTPAGE.ts |
|
|
7
8
|
| [Cart Page — config-driven form architecture (current state + planned refactor)](features/cart-page-config-architecture.md) | The Cart page (`src/pages/Cart/`) is the most config-heavy page in `toga2-commerce`. | src/pages/Cart/CartPage.tsx, src/pages/Cart/view/cartForm/CartForm.tsx, src/pages/Cart/view/cartForm/CartFormSection.tsx, src/pages/Cart/view/cartForm/CartFormRenderer.tsx, src/pages/Cart/view/EditCart.tsx, src/pages/Cart/view/EditOrder.tsx, src/pages/Cart/viewModel/useEditOrderOrEditCartViewModel.ts, src/pages/Cart/viewModel/FIELDS/*/*/*/CARTPAGE.ts, src/hooks/useAssignClientFields.ts |
|
|
8
9
|
| [Client Fields — per-tenant / language / role content & config](features/client-fields.md) | Almost no user-facing text, field layout, or page config is hard-coded in `toga2-commerce`. | src/fieldsConfig/index.ts, src/fieldsConfig/getClientLoginFields.ts, src/fieldsConfig/clientFields/COMPASS.json, src/fieldsConfig/clientFields/COMPASSCANADA.json, src/fieldsConfig/clientFields/QUAD.json, src/hooks/useAssignClientFields.ts, src/hooks/useDynamicConditionalFieldOptions.ts, src/stores/useFieldsStore.ts, src/components/BaseDetailField/BaseDetailField.tsx |
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Cart Bundle Submission & the bundleUuid Identity Contract
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga2-commerce
|
|
5
|
+
project: TOGa Commerce
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-07-08
|
|
10
|
+
owners: ["apeterson"]
|
|
11
|
+
files:
|
|
12
|
+
- src/api/syncSalesOrderItemsFromLocalStorageCartToApi.ts
|
|
13
|
+
- src/utils/formatSalesOrderBundlesFromApi.ts
|
|
14
|
+
- src/stores/useCartStoreZu.ts
|
|
15
|
+
- src/pages/OrderDetails/helpers/formatSalesOrderDataFromLocalStorage.ts
|
|
16
|
+
- src/pages/OrderDetails/view/components/OrderItems.tsx
|
|
17
|
+
related:
|
|
18
|
+
- 2.0/apps/toga2-commerce/architecture.md
|
|
19
|
+
- clients/compass-usa/workflows/cross-kit-bundle-corruption.md
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Summary
|
|
23
|
+
|
|
24
|
+
How cart **bundles** (kits) are turned into `SalesOrderItems` when a cart is submitted or an
|
|
25
|
+
existing order is edited, and the **identity-field contract** every consumer of a cart bundle
|
|
26
|
+
must obey: the cart bundle object (`BundleForZuCart`) exposes **`bundleUuid`** as its identity —
|
|
27
|
+
there is **no `uuid` field** on it. Reading `bundle.uuid` returns `undefined` and silently
|
|
28
|
+
corrupts kit/fee attribution.
|
|
29
|
+
|
|
30
|
+
## How it works
|
|
31
|
+
|
|
32
|
+
The cart (`useCartStoreZu`) holds bundles alongside loose items. On submit/edit,
|
|
33
|
+
`syncSalesOrderItemsFromLocalStorageCartToApi.ts` (`syncSalesOrderLocalStorage`) builds the
|
|
34
|
+
`SalesOrderItems` payload:
|
|
35
|
+
|
|
36
|
+
- Loose items are pushed directly.
|
|
37
|
+
- For each cart bundle, `processBundleContents(bundle.bundleProgressContents, salesOrderItems,
|
|
38
|
+
bundle.bundleUuid)` emits the kit's product lines, stamping each with the bundle's identity.
|
|
39
|
+
- A second loop emits each bundle **fee** line, matching an existing SO item on **both**
|
|
40
|
+
`item.uuid` **and** `bundleItem.bundle.uuid === bundle.bundleUuid` so a fee is not matched
|
|
41
|
+
across bundles, then setting `bundleItem.bundle.uuid = bundle.bundleUuid`.
|
|
42
|
+
|
|
43
|
+
Downstream, `updateParentSalesOrderItemId` links each child line to its kit's **primary** by
|
|
44
|
+
comparing bundle identity, and `useCartSalesQuoteZu.updateSalesOrderItem`'s `findIndex` uses
|
|
45
|
+
`item.uuid` **plus** the bundle identity to place a line in the right kit.
|
|
46
|
+
|
|
47
|
+
On the read side, `formatSalesOrderBundlesFromApi.ts` (via `pairSalesOrderBundlesFromApi`)
|
|
48
|
+
rebuilds the cart bundles from the API by grouping API lines on their `parentSalesOrderItem`
|
|
49
|
+
identity.
|
|
50
|
+
|
|
51
|
+
## The bundleUuid identity contract
|
|
52
|
+
|
|
53
|
+
- `BundleForZuCart` exposes **`bundleUuid`** (the bundle/kit identity) and
|
|
54
|
+
**`bundleZuCartUuid`** (the per-cart-instance id). It has **no `uuid`**.
|
|
55
|
+
- Any code consuming a cart bundle must read **`bundleUuid`** (or `bundleZuCartUuid` when it
|
|
56
|
+
needs the per-cart instance) — never `bundle.uuid`.
|
|
57
|
+
- The producer field was renamed `uuid` → `bundleUuid` in commit **e5115167** *"Fix bundle
|
|
58
|
+
formatting during edit"* (2026-03-18), in `formatSalesOrderBundlesFromApi.ts` and
|
|
59
|
+
`useCartStoreZu.ts`. Several consumers were **not** updated at the time.
|
|
60
|
+
|
|
61
|
+
## Gotchas
|
|
62
|
+
|
|
63
|
+
- **Cart bundles flow through variables typed `any`** (`cartBundles.forEach((bundle: any) => …)`),
|
|
64
|
+
so TypeScript does **not** flag a wrong field read. A stale `bundle.uuid` compiles cleanly and
|
|
65
|
+
yields `undefined` at runtime — grep every new consumer by hand.
|
|
66
|
+
- **`bundle.uuid` (undefined) mis-attributes kit lines/fees.** When the submit path read
|
|
67
|
+
`bundle.uuid`, every emitted line got `bundleItem.bundle.uuid = undefined`, so (a)
|
|
68
|
+
`updateParentSalesOrderItemId` compared `undefined == undefined` → true for every line against
|
|
69
|
+
every primary, collapsing lines onto the **last** kit's primary; and (b)
|
|
70
|
+
`updateSalesOrderItem`'s `findIndex` degraded to matching on `item.uuid` alone — so lines that
|
|
71
|
+
**share** an `item.uuid` across kits (fees/warranties, the same catalog item reused in every
|
|
72
|
+
kit) collided and merged into the wrong kit. Products with unique `item.uuid`s mostly survived;
|
|
73
|
+
shared fee items reliably broke. This is why it only reproduced on **multi-kit orders that
|
|
74
|
+
share fee items**.
|
|
75
|
+
- **The code fix is preventive only.** Correcting the submit reads stops *new* corruption on
|
|
76
|
+
save; it does **not** heal already-persisted bad rows. Existing corrupted orders still render
|
|
77
|
+
fees under the wrong kit on edit because `pairSalesOrderBundlesFromApi` rebuilds the cart by
|
|
78
|
+
grouping on the (corrupted) `parentSalesOrderItem` identity from the API. See the Compass
|
|
79
|
+
detect-and-repair workflow (in `related`).
|
|
80
|
+
- **Still-stale consumers (not yet fixed, as of 2026-07-08):**
|
|
81
|
+
- `src/pages/OrderDetails/helpers/formatSalesOrderDataFromLocalStorage.ts:130` — `uuid:
|
|
82
|
+
bundle.uuid` (cosmetic: an `undefined` display field).
|
|
83
|
+
- `src/pages/OrderDetails/view/components/OrderItems.tsx:45` — React `key={bundle.uuid}`
|
|
84
|
+
(cosmetic: duplicate/`undefined` keys).
|
|
85
|
+
- `src/api/helpers/formatBundlesForSalesQuoteZuCartInEditMode.ts:82` still reads the old field
|
|
86
|
+
but is **dead code** (no imports).
|
|
87
|
+
|
|
88
|
+
## Change history
|
|
89
|
+
- 2026-07-08 — Fixed edit-order bundle submission attributing kit items/fees to the wrong kit:
|
|
90
|
+
`syncSalesOrderItemsFromLocalStorageCartToApi.ts` read `bundle.uuid` (undefined) instead of
|
|
91
|
+
`bundle.bundleUuid` at 3 sites; corrected all three. Root cause traced to the 2026-03-18
|
|
92
|
+
`uuid → bundleUuid` producer rename (commit e5115167) that left stale consumers. Fix is
|
|
93
|
+
preventive (does not heal persisted rows) and lives on branch TRUE-80097, not yet merged.
|
|
94
|
+
Documented the `bundleUuid` identity contract and the two remaining cosmetic stale consumers.
|
|
95
|
+
(apeterson)
|
|
96
|
+
</content>
|
|
97
|
+
</invoke>
|
package/knowledge/INDEX.md
CHANGED
|
@@ -28,7 +28,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
28
28
|
- **talos** (TOGa IQ) — 7 doc(s) → [2.0/apps/talos/INDEX.md](2.0/apps/talos/INDEX.md)
|
|
29
29
|
- **voice-to-voice** (TOGa Voice) — 4 doc(s) → [2.0/apps/voice-to-voice/INDEX.md](2.0/apps/voice-to-voice/INDEX.md)
|
|
30
30
|
- **ai-bdr** (AI-BDR) — 6 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
|
|
31
|
-
- **toga2-commerce** (TOGa Commerce) —
|
|
31
|
+
- **toga2-commerce** (TOGa Commerce) — 9 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
|
|
32
32
|
- **toga25-supply** (TOGa 2.5 Supply) — 8 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
|
|
33
33
|
- **toga-blox** (TOGa Blox) — 7 doc(s) → [2.0/apps/toga-blox/INDEX.md](2.0/apps/toga-blox/INDEX.md)
|
|
34
34
|
- **bdr** (BDR) — 0 doc(s) → [2.0/apps/bdr/INDEX.md](2.0/apps/bdr/INDEX.md)
|
|
@@ -8,5 +8,6 @@
|
|
|
8
8
|
| [Compass MITS PO → SO Item Linking](features/mits-po-to-so-item-linking.md) | 2.0 | MITS sends Compass inbound Purchase Orders (`POST /v2/purchase-orders`) against a Sales Order (`mitsSalesOrder`). | _underscore/Model/Compass/PurchaseOrder.php, worker/crons/toga2/compass/workflow/3a_import_office_depot_purchase_orders.php |
|
|
9
9
|
| [Compass MITS PO Transmission to Vendors](features/mits-po-transmission-to-vendors.md) | 2.0 | The 1.0 worker cron `2_transmit_mits_purchase_orders_to_vendors.php` transmits Compass PurchaseOrders to their vendors (Office Depot, Strategic Systems, Compass | worker/crons/toga2/compass/workflow/2_transmit_mits_purchase_orders_to_vendors.php, library/app/client/compass.php |
|
|
10
10
|
| [Compass USA](profile.md) | 2.0 | Compass USA is a TOGA client running a multi-tier supply-chain commerce operation. | |
|
|
11
|
+
| [Compass Cross-Kit Bundle Corruption — Detection & Repair](workflows/cross-kit-bundle-corruption.md) | 2.0 | A frontend regression in `toga2-commerce`'s edit-order bundle submission mis-attributed bundle (kit) line items and **fees/warranties** to the **wrong kit**, pe | src/api/syncSalesOrderItemsFromLocalStorageCartToApi.ts |
|
|
11
12
|
| [Compass ODP Order Pipeline to NetSuite (numbered worker crons)](workflows/odp-order-pipeline-to-netsuite.md) | 1.0 | The end-to-end **Compass Office Depot (ODP) order → NetSuite** pipeline as it actually runs through the 1.0 `worker` crons under `worker/crons/toga2/compass/`, | worker/crons/toga2/compass/workflow/1_transmit_compass_sales_orders_to_mits.php, worker/crons/toga2/compass/workflow/2_transmit_mits_purchase_orders_to_vendors.php, worker/crons/toga2/compass/edi/1_download_edi_s3_create_po_toga.php, worker/crons/toga2/compass/workflow/5_create_netsuite_sales_orders_from_office_depot_purchase_orders.php, library/app/client/compass.php |
|
|
12
13
|
| [Compass Order Lifecycle & Data-Integrity Invariants](workflows/order-lifecycle-and-data-integrity.md) | 2.0 | End-to-end map of how a Compass order flows through the `Client_Compass` (2.0) database and the **expected raw-data shape** at each link/ASN/IF level. | |
|
|
@@ -14,13 +14,15 @@ project: _Underscore
|
|
|
14
14
|
client: compass-usa
|
|
15
15
|
type: profile
|
|
16
16
|
status: active
|
|
17
|
-
updated: 2026-07-
|
|
18
|
-
owners: [jcardinal, bala, tcox]
|
|
17
|
+
updated: 2026-07-08
|
|
18
|
+
owners: [jcardinal, bala, tcox, apeterson]
|
|
19
19
|
files: []
|
|
20
20
|
related:
|
|
21
21
|
- features/asn-to-item-fulfillment.md
|
|
22
22
|
- features/cost-centers.md
|
|
23
|
+
- workflows/cross-kit-bundle-corruption.md
|
|
23
24
|
- ../../2.0/apps/toga2-commerce/features/expedited-shipping-gating.md
|
|
25
|
+
- ../../2.0/apps/toga2-commerce/features/cart-bundle-submission-and-identity.md
|
|
24
26
|
- ../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md
|
|
25
27
|
---
|
|
26
28
|
|
|
@@ -49,6 +51,11 @@ separate, related client (see its own profile).
|
|
|
49
51
|
**Known data issue:** many Compass MacBooks are categorized `APPLE LAPTOP` /
|
|
50
52
|
`MAC & ACCESSORIES`, not `COMPUTERS`, so those kits do **not** qualify for expedited — a
|
|
51
53
|
catalog-data normalization matter, not a code gap.
|
|
54
|
+
- **Cross-kit bundle corruption (edit-order):** a `toga2-commerce` submit bug attributed kit
|
|
55
|
+
line items and shared fees/warranties to the wrong kit; 55 Compass orders / 298 line items are
|
|
56
|
+
corrupted in `Client_Compass.SalesOrderItems` (Canada and Quad: zero). The code fix is
|
|
57
|
+
preventive; existing rows still need a DB data-fix. Detection query + remediation plan:
|
|
58
|
+
[Cross-Kit Bundle Corruption](workflows/cross-kit-bundle-corruption.md).
|
|
52
59
|
|
|
53
60
|
## Vendors & integrations
|
|
54
61
|
- **Office Depot (ODP)** — vendor id 1. ASNs arrive via **cXML** (direct V2 API) and via the
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Compass Cross-Kit Bundle Corruption — Detection & Repair
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga2-commerce
|
|
5
|
+
project: TOGa Commerce
|
|
6
|
+
client: compass-usa
|
|
7
|
+
type: workflow
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-07-08
|
|
10
|
+
owners: ["apeterson"]
|
|
11
|
+
files:
|
|
12
|
+
- src/api/syncSalesOrderItemsFromLocalStorageCartToApi.ts
|
|
13
|
+
related:
|
|
14
|
+
- 2.0/apps/toga2-commerce/features/cart-bundle-submission-and-identity.md
|
|
15
|
+
- clients/compass-usa/workflows/order-lifecycle-and-data-integrity.md
|
|
16
|
+
- clients/compass-usa/profile.md
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Summary
|
|
20
|
+
|
|
21
|
+
A frontend regression in `toga2-commerce`'s edit-order bundle submission mis-attributed bundle
|
|
22
|
+
(kit) line items and **fees/warranties** to the **wrong kit**, persisting corrupted rows in
|
|
23
|
+
`Client_Compass.SalesOrderItems`. Only **Compass** triggers it — it is the only tenant running
|
|
24
|
+
the multi-kit-plus-shared-fee edit flow. This doc is the read-only **detection** reference and
|
|
25
|
+
the **remediation** plan. The root cause is fixed (preventive), but **already-persisted rows are
|
|
26
|
+
not healed** and still need a DB data-fix.
|
|
27
|
+
|
|
28
|
+
Root cause and the field contract are in the toga2-commerce feature doc (see `related`); this
|
|
29
|
+
doc covers the **data signature, impact, and repair** in `Client_Compass`.
|
|
30
|
+
|
|
31
|
+
## The corruption signature
|
|
32
|
+
|
|
33
|
+
A **flat kit child line** in `SalesOrderItems` where:
|
|
34
|
+
- `bundleItemId` **IS NOT NULL** (it belongs to a kit), and
|
|
35
|
+
- `bundleId` **IS NULL** (this excludes legitimate *nested* child-bundles, which set `bundleId`), and
|
|
36
|
+
- `parentSalesOrderItemId` points to a primary whose kit differs from the line's own kit —
|
|
37
|
+
i.e. the child line's `BundleItems.bundleId` ≠ its parent primary's `BundleItems.bundleId`.
|
|
38
|
+
|
|
39
|
+
The test joins `SalesOrderItems → BundleItems` **twice** (once for the child line via its own
|
|
40
|
+
`bundleItemId`, once for its parent via the parent's `bundleItemId`) and compares
|
|
41
|
+
`BundleItems.bundleId`. A mismatch, within the same order, is the corruption.
|
|
42
|
+
|
|
43
|
+
## Detection query (read-only)
|
|
44
|
+
|
|
45
|
+
Run against `Client_Compass`, read-only (via the TOGA Database Integration MCP). Confirm exact
|
|
46
|
+
column names against the live schema before use:
|
|
47
|
+
|
|
48
|
+
```sql
|
|
49
|
+
SELECT child.salesOrderId,
|
|
50
|
+
child.id AS childSalesOrderItemId,
|
|
51
|
+
biChild.bundleId AS childKitBundleId,
|
|
52
|
+
parent.id AS parentSalesOrderItemId,
|
|
53
|
+
biParent.bundleId AS parentKitBundleId
|
|
54
|
+
FROM SalesOrderItems child
|
|
55
|
+
JOIN BundleItems biChild ON biChild.id = child.bundleItemId
|
|
56
|
+
JOIN SalesOrderItems parent ON parent.id = child.parentSalesOrderItemId
|
|
57
|
+
JOIN BundleItems biParent ON biParent.id = parent.bundleItemId
|
|
58
|
+
WHERE child.bundleItemId IS NOT NULL
|
|
59
|
+
AND child.bundleId IS NULL -- exclude legitimate nested child-bundles
|
|
60
|
+
AND biChild.bundleId <> biParent.bundleId; -- child line attributed to the wrong kit
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Impact (verified on prod, read-only, 2026-07-08)
|
|
64
|
+
|
|
65
|
+
| Schema | Corrupted orders | Corrupted line items |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| `Client_Compass` | **55** | **298** |
|
|
68
|
+
| `Client_CompassCanada` | 0 | 0 |
|
|
69
|
+
| `Client_Quad` | 0 | 0 |
|
|
70
|
+
|
|
71
|
+
Compass Canada and Quad are **zero impact** — neither runs the multi-kit + shared-fee edit flow.
|
|
72
|
+
**3 of the 55** Compass orders contain **nested bundles** — **SA119455, SA119508, SA133211** —
|
|
73
|
+
and need more careful remediation than the other **52 flat** orders.
|
|
74
|
+
|
|
75
|
+
## Remediation (NOT yet done — requires write access + review)
|
|
76
|
+
|
|
77
|
+
- **Fix:** repoint each stray child line's `parentSalesOrderItemId` to **its own kit's primary**
|
|
78
|
+
in the same order (i.e. the primary whose `BundleItems.bundleId` equals the child line's
|
|
79
|
+
`BundleItems.bundleId`).
|
|
80
|
+
- Handle the **3 nested-bundle orders separately** — do not batch them with the 52 flat orders.
|
|
81
|
+
- **Why the code fix alone is insufficient:** the preventive frontend fix stops new corruption on
|
|
82
|
+
save but does not touch existing rows. On edit, `pairSalesOrderBundlesFromApi` rebuilds the
|
|
83
|
+
cart by grouping API lines on the (still-corrupted) `parentSalesOrderItem` identity, so old
|
|
84
|
+
orders keep rendering fees under the wrong kit until the DB rows are repaired.
|
|
85
|
+
|
|
86
|
+
## Open questions — timeline is NOT conclusively the 2026-03-18 regression
|
|
87
|
+
|
|
88
|
+
The corruption was initially attributed to the 2026-03-18 `uuid → bundleUuid` rename (commit
|
|
89
|
+
e5115167). But **10 of the 55** impacted orders have `SalesOrders.dtUpdated` **before**
|
|
90
|
+
2026-03-18. Two unresolved possibilities:
|
|
91
|
+
1. `SalesOrders.dtUpdated` (the order **header**) is not the corruption clock — the edit flow
|
|
92
|
+
writes `SalesOrderItems` without bumping the header timestamp; or
|
|
93
|
+
2. An **earlier** bundle-matching defect produced the same signature — the submit file's history
|
|
94
|
+
shows *"Improve matching logic"* (Jan 2026) and *"Fix item duplications"* (Oct 2025) — i.e. a
|
|
95
|
+
**recurring** bundle-matching bug class, not one 4-month-old bug.
|
|
96
|
+
|
|
97
|
+
**Definitive check not yet run:** inspect `SalesOrderItems.dtUpdated` on the corrupted rows (not
|
|
98
|
+
the header) to date the corruption precisely.
|
|
99
|
+
|
|
100
|
+
## Systems involved
|
|
101
|
+
|
|
102
|
+
- `toga2-commerce` edit-order submit path (`syncSalesOrderItemsFromLocalStorageCartToApi.ts`) — the cause.
|
|
103
|
+
- `Client_Compass.SalesOrderItems` / `Client_Compass.BundleItems` — where the corruption lives.
|
|
104
|
+
- TOGA Database Integration MCP (`toga_query` etc.) — read-only prod detection.
|
|
105
|
+
|
|
106
|
+
## Change history
|
|
107
|
+
- 2026-07-08 — Documented the cross-kit bundle-attribution corruption: the signature, a read-only
|
|
108
|
+
detection query, prod impact (Compass 55 orders / 298 line items; Canada 0; Quad 0; 3
|
|
109
|
+
nested-bundle orders SA119455 / SA119508 / SA133211 needing separate handling), the
|
|
110
|
+
preventive-vs-remediation distinction, and the unresolved timeline. Root cause is the
|
|
111
|
+
toga2-commerce `bundleUuid` submit bug (see the feature doc). Remediation not yet performed.
|
|
112
|
+
(apeterson)
|
|
113
|
+
</content>
|
package/package.json
CHANGED