toga-ai 1.0.810 → 1.0.811
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/api2/features/nested-relationship-writes.md +16 -1
- package/knowledge/2.0/apps/dbchanges2/features/surface-layer-schema.md +98 -2
- package/knowledge/2.0/apps/toga25-supply/features/surface-frontend.md +72 -2
- package/knowledge/clients/compass-canada/profile.md +8 -1
- package/knowledge/clients/compass-usa/features/item-catalogs-and-duplicate-items.md +15 -1
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-15
|
|
10
10
|
owners: ["bala", "mhammontree", "tcox", "apeterson", "rgirish"]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
@@ -234,6 +234,16 @@ production `Core.RecordFields` for `Items`:
|
|
|
234
234
|
|---|---|---|---|
|
|
235
235
|
| **107** | `Items.manufacturerId` | `MATCH_UPSERT` | **creates** the manufacturer — get-or-create by name works |
|
|
236
236
|
| **269** | `Items.assetTypeId` | `MATCH` | **fails the entire item write** |
|
|
237
|
+
| **914** | `Items.catalogId` | `MATCH` | **matches** the existing catalog — and can never create one |
|
|
238
|
+
|
|
239
|
+
Row 914 is the benign shape of `MATCH`, and it is worth knowing why it is benign: the match set is
|
|
240
|
+
built from **`isIdentifier`**, and the `catalogs` record carries `isIdentifier = 1` on **`id`,
|
|
241
|
+
`uuid` AND `name`** (verified on production `Core.RecordFields`, 2026-09-15). So
|
|
242
|
+
`catalog: {name: 'Compass'}` resolves to the existing `Catalogs` row by name and **cannot mint a
|
|
243
|
+
new catalog** — which is what makes a name-keyed catalog safe to seed as a per-tenant default
|
|
244
|
+
(see [surface-layer-schema](../../dbchanges2/features/surface-layer-schema.md)). The identifier set
|
|
245
|
+
is per-record data, not a code constant: check `isIdentifier` before assuming a business key is
|
|
246
|
+
matchable.
|
|
237
247
|
|
|
238
248
|
The 1.0 NetSuite importer's `getCreateItem()` contains **both** calls. `getCreateManufacturer()`
|
|
239
249
|
sends `manufacturer: {name}` and has always worked; copying that shape for `assetType: {name}`
|
|
@@ -442,6 +452,11 @@ only one tenant's copy has the wrong flag.
|
|
|
442
452
|
|
|
443
453
|
## Change history
|
|
444
454
|
|
|
455
|
+
- 2026-09-15 — Added **`Items.catalogId` (recordFieldId 914, `MATCH`)** to the childPolicy table as
|
|
456
|
+
the *benign* `MATCH` case, and recorded why: the `catalogs` record has `isIdentifier = 1` on
|
|
457
|
+
**`id`, `uuid` and `name`** (prod `Core.RecordFields`), so `catalog: {name: '<catalog>'}` always
|
|
458
|
+
links the existing row and can never create a catalog. That is what lets a tenant ship a
|
|
459
|
+
name-keyed catalog as an item-create default. (bala)
|
|
445
460
|
- 2026-09-08 — ⚠ Recorded the **third EV-12 flavor: the field is declared in PHP but
|
|
446
461
|
`isIdentifier = 0` in the tenant's `CustomRecordFields`.** `V2.php` (~L7471-7530) builds
|
|
447
462
|
`$recordIdentifiers` purely from DB metadata (`Core.RecordFields` + `Client_<X>.CustomRecordFields`
|
|
@@ -6,9 +6,12 @@ project: Database Changes
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
10
|
-
owners: [jcardinal, apeterson, tcox]
|
|
9
|
+
updated: 2026-09-15
|
|
10
|
+
owners: [jcardinal, apeterson, tcox, bala]
|
|
11
11
|
files:
|
|
12
|
+
- dbchanges2/Core/2026-09-15a - ItemCreateDefaultsSurfaceSeed.sql
|
|
13
|
+
- dbchanges2/Client_Compass/2026-09-15a - ItemCreateCatalogDefault.sql
|
|
14
|
+
- dbchanges2/Client_CompassCanada/2026-09-15a - ItemCreateCatalogDefault.sql
|
|
12
15
|
- dbchanges2/Client_Compass/2026-09-04a - ItemRecordRestrictToPersonaVisible.sql
|
|
13
16
|
- dbchanges2/Client_CompassCanada/2026-09-04a - ItemRecordRestrictToPersonaVisible.sql
|
|
14
17
|
- dbchanges2/Core/2026-09-02a - TalosAssistantSurfaceSeed.sql
|
|
@@ -1372,7 +1375,100 @@ environment and take the **highest** `MAX(id)+1`, never the one in front of you,
|
|
|
1372
1375
|
`Core.SurfaceElements.id` = **233**, next `Core.Messages.id` = **299**. Both consuming files were
|
|
1373
1376
|
still unrun at the time, so this is a reservation, not a deployed state.
|
|
1374
1377
|
|
|
1378
|
+
## A marker element can carry per-tenant WRITE defaults, not just visibility (`item-record-create-defaults`, Core 61, 2026-09-15)
|
|
1379
|
+
|
|
1380
|
+
The marker-element pattern (a hidden element whose `config` is the payload) had so far only carried
|
|
1381
|
+
**visibility / mode** flags. `Core/2026-09-15a - ItemCreateDefaultsSurfaceSeed.sql` uses it to carry
|
|
1382
|
+
**default field VALUES for a create form**: surface **`item-record-create-defaults`** (type
|
|
1383
|
+
`SECTION`, `config.cardType = 'createDefaults'`) with one element, `isVisible = 0`, whose config is
|
|
1384
|
+
|
|
1385
|
+
```json
|
|
1386
|
+
{"role":"createDefaults","values":{}}
|
|
1387
|
+
```
|
|
1388
|
+
|
|
1389
|
+
Core seeds `values` **empty** — the true-neutral default — and each tenant opts in with a single
|
|
1390
|
+
`SurfaceOverrides` **`CONFIG`** row. The consuming app spreads `values` into its `POST` body before
|
|
1391
|
+
the form fields, so a default never beats real user input. FE side:
|
|
1392
|
+
[surface-frontend](../../toga25-supply/features/surface-frontend.md). This shipped to fix items
|
|
1393
|
+
created in toga25-supply being written with `Items.catalogId = NULL` (28 rows in prod
|
|
1394
|
+
`Client_Compass`, 6 in `Client_CompassCanada`).
|
|
1395
|
+
|
|
1396
|
+
**Why `name` and not `uuid` in the seeded value.** `Items.catalogId` is `Core.RecordFields` **914**
|
|
1397
|
+
with `childPolicy = MATCH`, and the `catalogs` record has `isIdentifier = 1` on `id`, `uuid` **and**
|
|
1398
|
+
`name` — so a name matches the existing row and **can never create a catalog**
|
|
1399
|
+
([nested-relationship writes](../../api2/features/nested-relationship-writes.md)). A name also keeps
|
|
1400
|
+
the seed readable and tenant-portable, where a uuid would not be.
|
|
1401
|
+
|
|
1402
|
+
**The two tenants spell the catalog differently, and the seeds use the STORED spelling:**
|
|
1403
|
+
|
|
1404
|
+
| Tenant | Seeded value | `Catalogs` reality |
|
|
1405
|
+
|---|---|---|
|
|
1406
|
+
| `Client_Compass` | `{"catalog":{"name":"Compass"}}` | id 1 **`Compass`** (also id 2 `Agilant`, id 3 `Office Depot`) |
|
|
1407
|
+
| `Client_CompassCanada` | `{"catalog":{"name":"compass"}}` | id 1 **`compass`** — the only row |
|
|
1408
|
+
|
|
1409
|
+
Both `name` columns are case-insensitive (`utf8mb4_unicode_ci` on Compass,
|
|
1410
|
+
`utf8mb4_0900_ai_ci` on Canada), so either spelling would in fact match; matching the stored
|
|
1411
|
+
spelling is a readability rule, not a correctness one. Catalog background:
|
|
1412
|
+
[Compass item catalogs](../../../clients/compass-usa/features/item-catalogs-and-duplicate-items.md).
|
|
1413
|
+
|
|
1414
|
+
### Both client files restate `role` — the CONFIG-replaces-wholesale rule, hit again
|
|
1415
|
+
|
|
1416
|
+
A `CONFIG` override **replaces the element's config object entirely** (`Surface.php:839`, see
|
|
1417
|
+
[the load-bearing rule](#-a-config-override-replaces-config-wholesale--this-is-the-load-bearing-rule-of-the-whole-design)),
|
|
1418
|
+
so each client row must ship **`role` AND `values`**:
|
|
1419
|
+
|
|
1420
|
+
```json
|
|
1421
|
+
{"role":"createDefaults","values":{"catalog":{"name":"Compass"}}}
|
|
1422
|
+
```
|
|
1423
|
+
|
|
1424
|
+
Send only `values` and the element loses its `role`, the adapter stops finding the marker, and the
|
|
1425
|
+
default silently disappears with no error anywhere. This is now the **second** feature to hit it
|
|
1426
|
+
(after the Talos suggestion chips) — treat "restate every key the FE matches on" as the default
|
|
1427
|
+
authoring step for any CONFIG override, not a special case.
|
|
1428
|
+
|
|
1429
|
+
### The client files hardcode the Core ids — and the 2026-07-20d precedent is NOT copyable
|
|
1430
|
+
|
|
1431
|
+
`Client_Compass/2026-09-15a` and `Client_CompassCanada/2026-09-15a` inline the Core `surfaceId` /
|
|
1432
|
+
`surfaceElementId` read from Core **after** the Core file was run, and guard re-runs with
|
|
1433
|
+
`SELECT … FROM DUAL WHERE NOT EXISTS (…)` against the **client table only** — no `Core.*` read at
|
|
1434
|
+
all.
|
|
1435
|
+
|
|
1436
|
+
**Confirmed again by a real failure this session:** querying `Client_Compass` with a join to `Core`
|
|
1437
|
+
returns `OperationalError (1049, "Unknown database 'Core'")` on the `prod-client` cluster. The
|
|
1438
|
+
nearest existing example, `Client_Compass/2026-07-20d - ItemRecordVendorItemsEnable.sql`, resolves
|
|
1439
|
+
its element id with `FROM Core.SurfaceElements JOIN Core.Surfaces` — **do not copy it.** It is one of
|
|
1440
|
+
the ~51 stale cross-cluster files described above; it landed in prod by hand transposition, not by
|
|
1441
|
+
running as written.
|
|
1442
|
+
|
|
1443
|
+
### Ids consumed — and beta must be RE-READ, not reused
|
|
1444
|
+
|
|
1445
|
+
`Surfaces`/`SurfaceElements` ids differ sharply per environment: before this work prod Core was at
|
|
1446
|
+
`Surfaces` **60** / `SurfaceElements` **233**, while dev-sandbox (beta) was at **23** / **65**. The
|
|
1447
|
+
developer ran the Core file **on PROD**, taking **surface 61** and **element 234**, and the two
|
|
1448
|
+
client files hardcode those prod ids.
|
|
1449
|
+
|
|
1450
|
+
> ⚠ **Beta has not had the Core file run.** Running the client files on beta as written would point
|
|
1451
|
+
> the overrides at ids that mean something else (or nothing) there. Re-read the beta ids after
|
|
1452
|
+
> running the Core file and swap them in before running the client files.
|
|
1453
|
+
|
|
1454
|
+
**Handoff published (verify before your own run):** next prod `Core.Surfaces.id` = **62**, next prod
|
|
1455
|
+
`Core.SurfaceElements.id` = **235**.
|
|
1456
|
+
|
|
1375
1457
|
## Change history
|
|
1458
|
+
- 2026-09-15 — Added the **`item-record-create-defaults`** seed trio (`Core/2026-09-15a` +
|
|
1459
|
+
`Client_Compass` / `Client_CompassCanada` `2026-09-15a - ItemCreateCatalogDefault.sql`), which
|
|
1460
|
+
extends the marker-element pattern from visibility flags to **per-tenant create-form default
|
|
1461
|
+
VALUES** (`config {"role":"createDefaults","values":{}}`, Core seeds `values` empty, each tenant
|
|
1462
|
+
overrides). Shipped to stop toga25-supply writing `Items.catalogId = NULL`. Recorded: the value is
|
|
1463
|
+
seeded by catalog **name** because `RecordFields` 914 is `MATCH` over a record with
|
|
1464
|
+
`isIdentifier` on `name` (so it can never mint a catalog); the two tenants store the name with
|
|
1465
|
+
different case (`Compass` vs `compass`, both CI collations); both client files must restate
|
|
1466
|
+
**`role`** because a CONFIG override replaces the config wholesale (second feature to hit that);
|
|
1467
|
+
and the client files hardcode Core ids with a client-only `NOT EXISTS` guard — re-confirmed by a
|
|
1468
|
+
live `1049 Unknown database 'Core'` on `prod-client`, which also makes the older
|
|
1469
|
+
`2026-07-20d - ItemRecordVendorItemsEnable.sql` a NON-copyable precedent. Prod consumed
|
|
1470
|
+
**Surface 61 / SurfaceElement 234** (beta is at 23 / 65 and has NOT run the Core file — re-read
|
|
1471
|
+
its ids first); next prod ids = **62** / **235**. (bala)
|
|
1376
1472
|
- 2026-09-04 — Added the **restrict-to-persona opt-in pair** (`Client_Compass/2026-09-04a` +
|
|
1377
1473
|
`Client_CompassCanada/2026-09-04a - ItemRecordRestrictToPersonaVisible.sql`): client-wide
|
|
1378
1474
|
`IS_VISIBLE`+`IS_ENABLED` overrides on `surfaceId` **19** / `surfaceElementId` **57**, literal Core
|
|
@@ -6,8 +6,8 @@ project: TOGa 2.5 Supply
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
10
|
-
owners: [jcardinal, apeterson, tcox, rgirish]
|
|
9
|
+
updated: 2026-09-15
|
|
10
|
+
owners: [jcardinal, apeterson, tcox, rgirish, bala]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-supply/src/layout/RecordApprovalModal/helpers/stackedCurrencyJoiner.ts
|
|
13
13
|
- toga25-supply/src/layout/RecordApprovalModal/helpers/stackedCurrencyJoiner.test.ts
|
|
@@ -71,6 +71,7 @@ files:
|
|
|
71
71
|
- toga25-supply/src/pages/Inventory/viewModel/FIELDS/index.ts
|
|
72
72
|
- toga25-supply/src/fieldsConfig/index.ts
|
|
73
73
|
- toga25-supply/src/layout/ItemRecordModalLayout/helpers/surfaceBundleToItemFields.ts
|
|
74
|
+
- toga25-supply/src/layout/ItemRecordModalLayout/hooks/useItemCreateForm.tsx
|
|
74
75
|
- toga25-supply/src/layout/ItemRecordModalLayout/helpers/index.ts
|
|
75
76
|
- toga25-supply/src/layout/ItemRecordModalLayout/viewModel/useItemRecordModalViewModel.tsx
|
|
76
77
|
- toga25-supply/src/layout/ItemRecordModalLayout/ItemRecordModalLayout.tsx
|
|
@@ -402,6 +403,64 @@ a `computeValueKey`, grep the consuming view's registry for that literal string
|
|
|
402
403
|
> makes the field visible and therefore **crashes the modal**; the FE alone changes nothing visible.
|
|
403
404
|
> Same shape as the approvals-gate ship-together dependency.
|
|
404
405
|
|
|
406
|
+
## Item CREATE defaults also come from Surface — and a missing default wrote NULL `catalogId` (2026-09-15)
|
|
407
|
+
|
|
408
|
+
The item modal's **create** payload now takes its per-tenant defaults from the Surface layer instead
|
|
409
|
+
of a client branch in code. This started as a data bug: `useItemCreateForm` built the `POST /items`
|
|
410
|
+
body with **no catalog at all**, so every item created in this app was saved with
|
|
411
|
+
`Items.catalogId = NULL`. The old `toga2-supply` `CreateItemModal.tsx` had always sent
|
|
412
|
+
`catalog: { name: "Compass" }` — which is why only the 2.5 app produced NULL rows, and why nobody
|
|
413
|
+
saw it in the older app. **Measured on prod 2026-09-15:** `Client_Compass.Items` held **28** rows
|
|
414
|
+
with a NULL `catalogId` (newest 2026-09-14) against 1,509 on catalog 1, and
|
|
415
|
+
`Client_CompassCanada.Items` **6** NULL against 221. A NULL-catalog item **never reaches the
|
|
416
|
+
storefront** — see
|
|
417
|
+
[Compass item catalogs](../../../clients/compass-usa/features/item-catalogs-and-duplicate-items.md).
|
|
418
|
+
|
|
419
|
+
**How it works now** — the same marker-element shape as `item-record-vendor-items`, read by a new
|
|
420
|
+
adapter:
|
|
421
|
+
|
|
422
|
+
- New Core surface **`item-record-create-defaults`** (type `SECTION`, `surface.config.cardType =
|
|
423
|
+
'createDefaults'`) carrying **one non-rendered marker element** with
|
|
424
|
+
`config = {"role":"createDefaults","values":{}}` and `isVisible = 0`.
|
|
425
|
+
- The slug was added to **`ITEM_RECORD_SURFACE_SLUGS`**, so it rides the group fetch the record
|
|
426
|
+
modal already makes — no new request.
|
|
427
|
+
- **`surfaceBundleToItemCreateDefaults()`** (`helpers/surfaceBundleToItemFields.ts`, exported via
|
|
428
|
+
`helpers/index.ts`) finds the marker by `config.role === 'createDefaults'` and returns its
|
|
429
|
+
`config.values` object.
|
|
430
|
+
- `useItemCreateForm` **spreads the defaults FIRST** into the `POST /items` body, so a real form
|
|
431
|
+
field always wins over a default. Order is the whole contract here.
|
|
432
|
+
- A tenant opts in with one `SurfaceOverrides` CONFIG row. Compass seeds
|
|
433
|
+
`{"catalog":{"name":"Compass"}}`; Compass Canada seeds `{"catalog":{"name":"compass"}}` — the
|
|
434
|
+
spelling differs per tenant, see
|
|
435
|
+
[surface-layer-schema](../../dbchanges2/features/surface-layer-schema.md).
|
|
436
|
+
|
|
437
|
+
**Why this is the right seam:** a tenant default is presentation/tenant config, so it belongs in the
|
|
438
|
+
surface layer, not in a hostname or client-slug switch in the create form. Adding the next default
|
|
439
|
+
(vendor, inventory type, anything) is now a `values` key plus a seed row — zero FE change.
|
|
440
|
+
|
|
441
|
+
### Adding a slug to a group fetch is SAFE on unseeded clients and environments
|
|
442
|
+
|
|
443
|
+
`_buildBundle` returns `{ surface: null, elements: [] }` when `$surface->load()` fails, so a slug
|
|
444
|
+
that has not been seeded in a tenant (or a whole environment where the Core file has not run) simply
|
|
445
|
+
resolves to an empty bundle. The adapter then returns `{}` and the create payload is byte-identical
|
|
446
|
+
to before. Verified against `Model/Core/Surface.php` — see
|
|
447
|
+
[surface-resolver](../../_underscore/features/surface-resolver.md). **So the FE can ship before the
|
|
448
|
+
SQL runs anywhere** — the reverse of the restrict-to-persona deploy-order rule above, because this
|
|
449
|
+
element renders nothing.
|
|
450
|
+
|
|
451
|
+
### Sending `catalog` by NAME does not trip the Compass POST interceptor
|
|
452
|
+
|
|
453
|
+
`_Model_Compass_Item::postPost` throws *"Could not load catalog uuid"* when it receives a catalog
|
|
454
|
+
with no uuid, which looks like a reason to send a uuid instead of a name. It is not. api2 invokes the
|
|
455
|
+
POST payload interceptor with **`$outData` — the created record, not the submitted body**
|
|
456
|
+
(`V2.php:6019`), and the created record always carries the resolved catalog uuid. Compass has an
|
|
457
|
+
active POST/POST interceptor on `recordId 21` (Items); Compass Canada has one too, but there is no
|
|
458
|
+
`_Model_CompassCanada_Item`, so it falls back to `_Model_Client_Item::postPost`, which returns early
|
|
459
|
+
unless `isFulfillable` was sent. Background:
|
|
460
|
+
[API payload interceptors](../../api2/features/api-payload-interceptors.md). The name→row match
|
|
461
|
+
itself is safe because `Items.catalogId` is a `MATCH` field over a record whose `name` is an
|
|
462
|
+
identifier — [nested-relationship writes](../../api2/features/nested-relationship-writes.md).
|
|
463
|
+
|
|
405
464
|
## SalesOrders approval-decision modal (approve + deny) — migrated via an OVERLAY adapter seam
|
|
406
465
|
|
|
407
466
|
The SalesOrder approve/deny decision modal's config now sources its display fields from Surface,
|
|
@@ -1100,6 +1159,17 @@ claim is about the `navigation-*` **flags** being inert — still true — not a
|
|
|
1100
1159
|
unused).
|
|
1101
1160
|
|
|
1102
1161
|
## Change history
|
|
1162
|
+
- 2026-09-15 — **Item create defaults moved to the Surface layer, fixing NULL `catalogId` on every
|
|
1163
|
+
item created in this app.** `useItemCreateForm` sent no catalog at all (prod: 28 NULL-catalog rows
|
|
1164
|
+
in `Client_Compass`, 6 in `Client_CompassCanada`; the older `toga2-supply` modal had always sent
|
|
1165
|
+
`catalog: {name:"Compass"}`), and a NULL-catalog item never reaches the storefront. New Core
|
|
1166
|
+
surface `item-record-create-defaults` carries a hidden `config.role='createDefaults'` marker whose
|
|
1167
|
+
`config.values` each tenant overrides; the slug rides the existing `ITEM_RECORD_SURFACE_SLUGS`
|
|
1168
|
+
group fetch, `surfaceBundleToItemCreateDefaults()` adapts it, and the create form spreads it
|
|
1169
|
+
**first** so form fields still win. Recorded two rules that make this cheap: an unseeded slug
|
|
1170
|
+
resolves to an empty bundle (so FE can ship before the SQL, unlike the restrict-to-persona case),
|
|
1171
|
+
and sending `catalog` by **name** cannot trip `_Model_Compass_Item::postPost` because api2 hands a
|
|
1172
|
+
POST interceptor the created record, not the request body. (bala)
|
|
1103
1173
|
- 2026-09-04 — Fixed the item modal's **More Info → "Restrict to Persona"** field missing in VIEW for
|
|
1104
1174
|
Compass USA + Canada. Two causes, both now rules here: (1) the item modal's VIEW is Surface-driven
|
|
1105
1175
|
while EDIT is still JSON, so a Core-default-OFF element with no client `SurfaceOverride` vanishes
|
|
@@ -17,7 +17,7 @@ project: _Underscore
|
|
|
17
17
|
client: compass-canada
|
|
18
18
|
type: profile
|
|
19
19
|
status: active
|
|
20
|
-
updated: 2026-09-
|
|
20
|
+
updated: 2026-09-15
|
|
21
21
|
owners: [jcardinal, bala, tcox, apeterson, ajean]
|
|
22
22
|
files: []
|
|
23
23
|
related:
|
|
@@ -61,6 +61,13 @@ to but distinct from Compass USA. Like Compass USA it spans the **2.0** commerce
|
|
|
61
61
|
resolution + client constants that all 8 Canada crons depend on. `library` is in `apps` for that
|
|
62
62
|
reason.
|
|
63
63
|
|
|
64
|
+
- **Catalogs: one row, lowercase.** `Client_CompassCanada.Catalogs` holds a **single** row, id 1
|
|
65
|
+
name **`compass`** (lowercase) — not the `Compass` / `Agilant` / `Office Depot` trio Compass USA
|
|
66
|
+
has ([item catalogs](../compass-usa/features/item-catalogs-and-duplicate-items.md)). The column
|
|
67
|
+
collation is case-insensitive, but write the stored spelling when seeding a value that references
|
|
68
|
+
it by name. Items created in `toga25-supply` were saved with a NULL `catalogId` until 2026-09-15
|
|
69
|
+
(6 rows here); the fix seeds the catalog as a Surface create-default per tenant.
|
|
70
|
+
|
|
64
71
|
## Vendors & integrations
|
|
65
72
|
- **Grand & Toy (G&T)** — primary hardware vendor. SOs flow toga → MITS → PO to G&T; G&T sends
|
|
66
73
|
back ASNs. ASN ingestion (email CSV + the auto-created ItemFulfillment chain + bilingual
|
|
@@ -6,7 +6,7 @@ project: Library
|
|
|
6
6
|
client: compass-usa
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-15
|
|
10
10
|
owners: ["bala", "jcardinal"]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/api/toga2.php
|
|
@@ -214,6 +214,15 @@ children" rule:
|
|
|
214
214
|
- **⚠ A NULL `catalogId` is a real state** (27 items in prod, 22 ODP `VendorItems` point at such
|
|
215
215
|
items). Anything joining through `Catalogs` **drops those rows silently** — an inner join to
|
|
216
216
|
`Catalogs` is itself a filter.
|
|
217
|
+
**Where the newest NULL rows came from is now known, and it is fixed (2026-09-15):** the
|
|
218
|
+
`toga25-supply` item **create** form posted no catalog at all, so every item created in the 2.5 app
|
|
219
|
+
was born with `catalogId = NULL` — **28** such rows in `Client_Compass` (newest 2026-09-14) and
|
|
220
|
+
**6** in `Client_CompassCanada`, against 1,509 / 221 on catalog 1. A NULL-catalog item never
|
|
221
|
+
reaches the storefront. The create form now takes `catalog: {name: 'Compass'}` from a seeded
|
|
222
|
+
Surface default instead of hardcoding it (the older `toga2-supply` modal always sent it, which is
|
|
223
|
+
why the NULL rows are all from the new app) — see
|
|
224
|
+
[surface-frontend](../../../2.0/apps/toga25-supply/features/surface-frontend.md). The existing NULL
|
|
225
|
+
rows still need a data repair; the leak is closed, the backlog is not.
|
|
217
226
|
- **⚠ `BundleItems.itemId` is a silent catalog-resolution consumer — the storefront never
|
|
218
227
|
re-catalogs it.** The `toga2-commerce` storefront relays whatever item `GET /v2/bundles/{uuid}`
|
|
219
228
|
returns; its `catalogId = 1` filter guards standalone item search only, never bundles. So a kit
|
|
@@ -241,6 +250,11 @@ children" rule:
|
|
|
241
250
|
[catalog-fold reversal](../workflows/catalog-fold-reversal.md).
|
|
242
251
|
|
|
243
252
|
## Change history
|
|
253
|
+
- 2026-09-15 — Identified the **source of the newest NULL-`catalogId` items**: the `toga25-supply`
|
|
254
|
+
create form sent no catalog, so every item created in the 2.5 app was born catalog-less (**28** rows
|
|
255
|
+
in `Client_Compass`, newest 2026-09-14; **6** in `Client_CompassCanada`), and such an item never
|
|
256
|
+
reaches the storefront. The leak is closed — the create payload now takes its catalog from a seeded
|
|
257
|
+
Surface default — but the existing NULL rows are still unrepaired. (bala)
|
|
244
258
|
- 2026-09-02 — **Narrowed the same-day uniqueness resolution below.** `Client_Compass.Items` holds
|
|
245
259
|
`MD7F4LL/A-S` **twice** — id 2760 (`title` set, `isActive = 0`, the row all referencing bundles
|
|
246
260
|
use) and id 2784 (`title` NULL, `isActive = 1`, unused) — a pair from outside the fold population, so
|
package/package.json
CHANGED