toga-ai 1.0.51 → 1.0.53
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/INDEX.md +0 -1
- package/knowledge/INDEX.md +1 -2
- package/knowledge/clients/compass-usa/INDEX.md +1 -1
- package/knowledge/clients/compass-usa/features/asn-to-item-fulfillment.md +52 -10
- package/knowledge/registry.json +1 -2
- package/package.json +1 -1
- package/knowledge/2.0/apps/api2/bug/nested-field-foreign-key-resolution.md +0 -108
- package/knowledge/2.0/apps/toga2-commerce/INDEX.md +0 -6
- package/knowledge/2.0/apps/toga2-commerce/architecture.md +0 -68
- package/knowledge/2.0/apps/toga2-commerce/bug/item-view-bundle-vs-direct-fetch.md +0 -69
|
@@ -3,4 +3,3 @@
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [API (api2 / TOGa API v2) Architecture](architecture.md) | `api2` is the backend powering the public **TOGa 2.0 API**. | api2/Controller/Index.php, api2/Component/Api/V2/V2.php, api2/Component/Api/Cxml/Cxml.php, api2/Component/Api/V2/Response/Response.php, api2/Config/ |
|
|
6
|
-
| [Nested Field Foreign-Key Resolution (getChildOptionRecords)](bug/nested-field-foreign-key-resolution.md) | When a request supplies a `fields` option, the V2 JSON engine does NOT use the depth-bounded `getFullModelData()` serializer. | api2/Component/Api/V2/V2.php, _underscore/Model/Client/ItemCategoryFeature.php, _underscore/Model/Client/ItemFeature.php, _underscore/Model/Client/ItemCategoryFeatureGroup.php |
|
package/knowledge/INDEX.md
CHANGED
|
@@ -11,10 +11,9 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
11
11
|
|
|
12
12
|
- **_underscore** (_Underscore) _(framework core)_ — 4 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
13
13
|
- **worker2** (Worker) — 5 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
14
|
-
- **api2** (API) —
|
|
14
|
+
- **api2** (API) — 1 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
15
15
|
- **dbchanges2** (Database Changes) _(framework core)_ — 1 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
16
16
|
- **toga2-supply** (TOGa Supply) — 2 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
|
|
17
|
-
- **toga2-commerce** (TOGa Commerce) — 2 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
|
|
18
17
|
|
|
19
18
|
## Clients
|
|
20
19
|
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
| Doc | Framework | Summary | Files |
|
|
4
4
|
|-----|-----------|---------|-------|
|
|
5
|
-
| [Compass ASN → ItemFulfillment Auto-Creation](features/asn-to-item-fulfillment.md) | 2.0 | For Compass USA, posting an AdvanceShippingNotice (ASN) auto-creates the ItemFulfillment (IF) on the upstream SalesOrder. | _underscore/Model/Compass/AdvanceShippingNotice.php |
|
|
5
|
+
| [Compass ASN → ItemFulfillment Auto-Creation](features/asn-to-item-fulfillment.md) | 2.0 | For Compass USA, posting an AdvanceShippingNotice (ASN) auto-creates the ItemFulfillment (IF) on the upstream SalesOrder. | _underscore/Model/Compass/AdvanceShippingNotice.php, api2/Component/Api/Cxml/Cxml.php, dbchanges2/Client_Compass/2026-06-11 - AsnItemTrackingNumberAcl.sql |
|
|
6
6
|
| [Compass: Item Fulfillments for Sales Order Items TableView (tracking via bridge)](features/item-fulfillment-tracking-tableview.md) | 2.0 | The Compass TableView `item-fulfillments-for-sales-order-items` (`TableViews.id = 13` in Client_Compass) was rebuilt as part of the tracking-number bridge migra | dbchanges2/Client_Compass/2026-06-10 - ItemFulfillmentsForSalesOrderItemsTableView.sql |
|
|
7
7
|
| [Compass USA](profile.md) | 2.0 | Compass USA is a TOGA client running a multi-tier supply-chain commerce operation. | |
|
|
@@ -5,10 +5,12 @@ project: _Underscore
|
|
|
5
5
|
client: compass-usa
|
|
6
6
|
type: client-feature
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-06-
|
|
8
|
+
updated: 2026-06-11
|
|
9
9
|
owners: [jcardinal]
|
|
10
10
|
files:
|
|
11
11
|
- _underscore/Model/Compass/AdvanceShippingNotice.php
|
|
12
|
+
- api2/Component/Api/Cxml/Cxml.php
|
|
13
|
+
- dbchanges2/Client_Compass/2026-06-11 - AsnItemTrackingNumberAcl.sql
|
|
12
14
|
related:
|
|
13
15
|
- ../../../2.0/apps/_underscore/features/recursive-item-fulfillments.md
|
|
14
16
|
---
|
|
@@ -21,41 +23,81 @@ IFIUs for serialized units, and IF packages for tracking. ASNs arrive two ways:
|
|
|
21
23
|
**cXML** (direct V2 API) and the worker email cron
|
|
22
24
|
`3b_import_strategic_systems_advance_shipping_notices.php`.
|
|
23
25
|
|
|
26
|
+
Tracking numbers are propagated across **every** ASN and IF granularity (see *Tracking number
|
|
27
|
+
propagation* below), so a single ShipNotice tracking number lands at the header, item, and
|
|
28
|
+
unit level on both sides — not just on one unit.
|
|
29
|
+
|
|
24
30
|
## Key files / entry points
|
|
31
|
+
- `api2/Component/Api/Cxml/Cxml.php` — the cXML `ShipNoticeRequest` translator that builds
|
|
32
|
+
the `/advance-shipping-notices` POST payload.
|
|
25
33
|
- `_underscore/Model/Compass/AdvanceShippingNotice.php` — `postPost(&$api, &$payload)`.
|
|
26
34
|
- Fires only when the Compass ASN `ApiPayloadInterceptors` rows exist (interceptor is DB-driven).
|
|
35
|
+
- `Model/Compass/Usa/` and `Model/Compass/Canada/` ASN classes are **empty subclasses** that
|
|
36
|
+
inherit `postPost` from `_Model_Compass_AdvanceShippingNotice` — edit the parent.
|
|
27
37
|
|
|
28
38
|
## How it works
|
|
29
39
|
1. Read ASN → resolve PO → SO via `SalesOrders_PurchaseOrders`; skip if no SO.
|
|
30
40
|
2. Select ASN items whose SOI still has unfulfilled qty (`SOI.quantity > SUM(IFI.quantity)`).
|
|
31
41
|
3. Reuse the IF if one already exists with the SO number, else POST a new IF.
|
|
32
|
-
4. Per ASN item: POST an IFI (qty = ASN item qty)
|
|
33
|
-
|
|
34
|
-
|
|
42
|
+
4. Per ASN item: POST an IFI (qty = ASN item qty); then copy that ASN item's tracking numbers
|
|
43
|
+
(`AdvanceShippingNoticeItems_TrackingNumbers`) onto the IF item via
|
|
44
|
+
`POST /item-fulfillment-item-tracking-numbers` → `ItemFulfillmentItems_TrackingNumbers`.
|
|
45
|
+
5. Per ASN unit: if it has a Unit (serialized) → POST an IFIU (carrying unit tracking); if it
|
|
46
|
+
is tracking-only (`unitId IS NULL`) → collect its tracking number for an IF package instead.
|
|
35
47
|
6. Create one IF package per unique tracking number (header-level tracking merged with
|
|
36
48
|
tracking-only ASN-unit tracking, deduped by tracking-number uuid).
|
|
37
49
|
|
|
50
|
+
## Tracking number propagation
|
|
51
|
+
The cXML translator maps each `ShipControl` block's tracking number to a line via its
|
|
52
|
+
`LineNumber` Extrinsic (it iterates **all** ShipControls and **all** ShipNoticeItems, not just
|
|
53
|
+
`[0]`), then attaches the number at every level. End state for a cXML ShipNotice:
|
|
54
|
+
|
|
55
|
+
| Table | Populated by |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `AdvanceShippingNotices_TrackingNumbers` | cXML payload (ASN header) |
|
|
58
|
+
| `AdvanceShippingNoticeItems_TrackingNumbers` | cXML payload (matched line's item) |
|
|
59
|
+
| `AdvanceShippingNoticeItemUnits_TrackingNumbers` | cXML payload (tracking-only unit) |
|
|
60
|
+
| `ItemFulfillments_TrackingNumbers` | `postPost` IF package |
|
|
61
|
+
| `ItemFulfillmentItems_TrackingNumbers` | `postPost` (copies ASN-item tracking) |
|
|
62
|
+
| `ItemFulfillmentItemUnits_TrackingNumbers` | `postPost` — **only when serialized units exist** |
|
|
63
|
+
|
|
64
|
+
Fix-forward only (decided 2026-06-11): applies to new ShipNotices; existing records are not
|
|
65
|
+
backfilled.
|
|
66
|
+
|
|
38
67
|
## Data model
|
|
39
68
|
- `AdvanceShippingNoticeUnits.unitId` is nullable (tracking-only units have NULL).
|
|
40
69
|
- `ItemFulfillmentItemUnits.unitId` is **NOT NULL** — a tracking-only unit can never be an
|
|
41
|
-
IFIU; its tracking belongs on an `ItemFulfillmentPackage`.
|
|
42
|
-
|
|
43
|
-
|
|
70
|
+
IFIU; its tracking belongs on an `ItemFulfillmentPackage` / `ItemFulfillments_TrackingNumbers`.
|
|
71
|
+
So for a tracking-only ShipNotice (no serialized units, the Office Depot norm) the IFIU
|
|
72
|
+
bridge legitimately stays empty — there are no IF item units to attach to.
|
|
73
|
+
- Tracking bridge Records (Core): ASN = 216 (unit) / 217 (item) / 218 (header);
|
|
74
|
+
IF = 317 (header) / 318 (item) / 319 (unit). All six are registered inherent children of
|
|
75
|
+
their parents, so the nested payload keys are accepted with no metadata migration.
|
|
44
76
|
|
|
45
77
|
## Client variations
|
|
46
78
|
Compass USA handler (`Model/Compass/`), extending `_Model_Client_AdvanceShippingNotice`.
|
|
47
|
-
Compass Canada (`Model/Compass/Canada/`) is a separate sub-client.
|
|
79
|
+
Compass Canada (`Model/Compass/Canada/`) is a separate sub-client. The Compass cXML API
|
|
80
|
+
(Core `ClientApiIdentities` identity `153531108`, clientId 2) holds roles 1 and 3.
|
|
48
81
|
|
|
49
82
|
## Gotchas / known issues
|
|
83
|
+
- **Record 217 ACL gap (fixed 2026-06-11):** `AdvanceShippingNoticeItems_TrackingNumbers`
|
|
84
|
+
(Record 217) had **zero** `AclRecordPermissions` and its `advanceShippingNoticeItemId`
|
|
85
|
+
(RecordField 1440) / `trackingNumberId` (1441) fields had **zero** `AclFieldPermissions`,
|
|
86
|
+
so ASN-item-level tracking POSTs were rejected (`EZ-1`). `dbchanges2/Client_Compass/2026-06-11
|
|
87
|
+
- AsnItemTrackingNumberAcl.sql` grants role 3 CRUD + role 7 read at the record level and
|
|
88
|
+
roles 1/3/4 writable at the field level, mirroring sibling Record 216. **This SQL must be
|
|
89
|
+
executed against the Compass DB before the cXML change works end-to-end.**
|
|
50
90
|
- **Tracking-only ASN units (the majority — ~56k of ~101k in prod):** before 2026-06-08 the
|
|
51
91
|
IFIU query used `INNER JOIN Units`, silently dropping NULL-`unitId` rows, so cXML shipments
|
|
52
92
|
produced an IF + IFI with **no IFIU and no package — tracking was lost**. Fixed: `LEFT
|
|
53
93
|
OUTER JOIN Units` + route tracking-only units to IF packages. Historical records (e.g.
|
|
54
|
-
SO SA132502 / IF 66997) are **not backfilled** — fix-forward only
|
|
55
|
-
still an open decision.
|
|
94
|
+
SO SA132502 / IF 66997) are **not backfilled** — fix-forward only.
|
|
56
95
|
- Interceptor is **DB-driven**: `postPost` does nothing without the Compass
|
|
57
96
|
`ApiPayloadInterceptors` rows in that env.
|
|
58
97
|
- The IF is named after the SO number; an already-existing IF with that number is reused.
|
|
98
|
+
- **cXML SQL-injection hardening (2026-06-11):** the `ShipNoticeRequest` path interpolated the
|
|
99
|
+
cXML `Sender` Identity / SharedSecret and the OrderReference `orderID` (purchase order
|
|
100
|
+
number) straight into queries. These are now passed through `_Database::escape()`.
|
|
59
101
|
- Separate latent bug in the 1.0 worker: the Strategic Systems cron's no-serials branch
|
|
60
102
|
builds `$itemLevelTrackingNumbers` but never attaches it to the ASN payload.
|
|
61
103
|
|
package/knowledge/registry.json
CHANGED
|
@@ -5,6 +5,5 @@
|
|
|
5
5
|
{ "repo": "dbchanges2", "project": "Database Changes", "framework": "2.0", "role": "core", "dependsOn": [] },
|
|
6
6
|
{ "repo": "library", "project": "Library", "framework": "1.0", "role": "core", "dependsOn": [] },
|
|
7
7
|
{ "repo": "worker", "project": "Worker", "framework": "1.0", "role": "app", "dependsOn": [] },
|
|
8
|
-
{ "repo": "toga2-supply", "project": "TOGa Supply", "framework": "2.0", "role": "app", "dependsOn": ["api2"] }
|
|
9
|
-
{ "repo": "toga2-commerce", "project": "TOGa Commerce", "framework": "2.0", "role": "app", "dependsOn": ["api2"] }
|
|
8
|
+
{ "repo": "toga2-supply", "project": "TOGa Supply", "framework": "2.0", "role": "app", "dependsOn": ["api2"] }
|
|
10
9
|
]
|
package/package.json
CHANGED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Nested Field Foreign-Key Resolution (getChildOptionRecords)
|
|
3
|
-
framework: "2.0"
|
|
4
|
-
repo: api2
|
|
5
|
-
project: API
|
|
6
|
-
client: shared
|
|
7
|
-
type: feature
|
|
8
|
-
status: active
|
|
9
|
-
updated: 2026-06-11
|
|
10
|
-
owners: [bala]
|
|
11
|
-
files:
|
|
12
|
-
- api2/Component/Api/V2/V2.php
|
|
13
|
-
- _underscore/Model/Client/ItemCategoryFeature.php
|
|
14
|
-
- _underscore/Model/Client/ItemFeature.php
|
|
15
|
-
- _underscore/Model/Client/ItemCategoryFeatureGroup.php
|
|
16
|
-
related:
|
|
17
|
-
- 2.0/apps/toga2-commerce/bug/item-view-bundle-vs-direct-fetch.md
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## Summary
|
|
21
|
-
|
|
22
|
-
When a request supplies a `fields` option, the V2 JSON engine does NOT use the
|
|
23
|
-
depth-bounded `getFullModelData()` serializer. It uses a fields-driven serializer
|
|
24
|
-
(`V2.php` ~line 3383) that splits each nested field path into:
|
|
25
|
-
|
|
26
|
-
- **foreign-key sub-objects** (belongsTo) → resolved by `getChildOptionRecords()`
|
|
27
|
-
- **inherent children** (one-to-many) → resolved by recursive `processRoutePairs()`
|
|
28
|
-
|
|
29
|
-
There was a bug in `getChildOptionRecords()`: a foreign-key sub-object that sits on an
|
|
30
|
-
**inherent child which is itself reached underneath another foreign-key parent** was
|
|
31
|
-
fetched but never attached to the output, so it silently disappeared from the JSON.
|
|
32
|
-
|
|
33
|
-
This surfaced as the `toga2-commerce` item-view "Specifications" headings showing
|
|
34
|
-
`undefined` when an item was opened **through a bundle** (but not through a category).
|
|
35
|
-
|
|
36
|
-
## Key files / entry points
|
|
37
|
-
|
|
38
|
-
- `api2/Component/Api/V2/V2.php`
|
|
39
|
-
- `execute()` / `processRoutePairs()` — request lifecycle, depth setup (`DEFAULT_DEPTH = 3`).
|
|
40
|
-
- fields-driven serialization branch at ~line 3383 (`if (empty($selectFields))` else).
|
|
41
|
-
- `getChildOptionRecords()` (~line 5279) — resolves FK sub-objects and nested inherent children.
|
|
42
|
-
|
|
43
|
-
## How it works
|
|
44
|
-
|
|
45
|
-
For a request like `GET /items/{uuid}` with
|
|
46
|
-
`...itemFeatures.itemCategoryFeatures.itemCategoryFeatureGroup.name`:
|
|
47
|
-
|
|
48
|
-
- The item is the **top-level record**, so `itemFeatures` and `itemCategoryFeatures` are
|
|
49
|
-
handled as inherent children via the main `processRoutePairs` recursion, and
|
|
50
|
-
`itemCategoryFeatureGroup` (an FK on `ItemCategoryFeature`) resolves correctly. WORKS.
|
|
51
|
-
|
|
52
|
-
For `GET /bundles/{uuid}` with
|
|
53
|
-
`bundleItems.item.itemFeatures.itemCategoryFeatures.itemCategoryFeatureGroup.name`:
|
|
54
|
-
|
|
55
|
-
- The top record is the bundle; `item` is an **FK** on `bundleItem`. Stepping through that FK
|
|
56
|
-
routes the whole `item` subtree into `getChildOptionRecords()`.
|
|
57
|
-
- Inside it: `feature` is an FK → handled correctly (so `feature.measure.name` came back).
|
|
58
|
-
But `itemCategoryFeatures` is an **inherent child**, so it fell into the branch at
|
|
59
|
-
~line 5779 ("requesting all available data for an inherent child"). That branch fetched
|
|
60
|
-
each row's FK (`itemCategoryFeatureGroup`) into `$res` (~line 5840) and **discarded it** —
|
|
61
|
-
only scalar columns (`uuid`, `limitDirection`) were attached. The group fell out.
|
|
62
|
-
|
|
63
|
-
## The fix
|
|
64
|
-
|
|
65
|
-
Attach the resolved FK result to the inherent child row (after the `getChildOptionRecords`
|
|
66
|
-
call at ~line 5850):
|
|
67
|
-
|
|
68
|
-
```php
|
|
69
|
-
$outDataThisInherentChild[$inherentChildField] = $res;
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## Data model
|
|
73
|
-
|
|
74
|
-
Relationship chain (all `_Model_Client_*`, DB_CLIENT):
|
|
75
|
-
|
|
76
|
-
- `ItemFeature` (`itemId`, `featureId`) — inherent child of `Item`.
|
|
77
|
-
- `ItemCategoryFeature` (`itemFeatureId`, `itemCategoryFeatureGroupId`, `limitDirection`) —
|
|
78
|
-
inherent child of `ItemFeature`.
|
|
79
|
-
- `ItemCategoryFeatureGroup` (`itemCategoryId`, `name`) — FK target that holds the heading.
|
|
80
|
-
- `Feature` (`measureId`) -> `Measure` — the FK chain that always worked (different code arm).
|
|
81
|
-
|
|
82
|
-
## Client variations
|
|
83
|
-
|
|
84
|
-
None — this is a core engine fix affecting every client and every endpoint whose `fields`
|
|
85
|
-
request has the shape "FK -> inherent child -> FK". The common top-level case
|
|
86
|
-
(`/items`, `/sales-orders`, etc.) uses the other code path and is unaffected.
|
|
87
|
-
|
|
88
|
-
## Gotchas / known issues
|
|
89
|
-
|
|
90
|
-
- It is NOT a depth, ACL, or data problem. Verified against prod `Client_Compass`:
|
|
91
|
-
the group data exists (0 null `itemCategoryFeatureGroupId`), the
|
|
92
|
-
`item-category-feature-groups` record (126) has READ with `indirectRecordId = NULL`
|
|
93
|
-
(readable on any path), and no `minDepth` interceptor applies — both calls run at
|
|
94
|
-
`DEFAULT_DEPTH = 3`. `feature.measure.name` and `itemCategoryFeatureGroup.name` are at
|
|
95
|
-
identical nesting depth, so depth could never explain why one survived and the other did
|
|
96
|
-
not — the real cause is FK-vs-inherent-child handling.
|
|
97
|
-
- Tell-tale sign of the buggy branch: it ignores the requested sub-fields and dumps all
|
|
98
|
-
permitted scalar columns, so the response contained a `uuid` that was never requested.
|
|
99
|
-
- This branch still ignores `$varOptions` (over-returns columns) — the fix only makes FK
|
|
100
|
-
sub-objects consistent with the scalars. Acceptable and ACL-bounded, but slightly more
|
|
101
|
-
payload.
|
|
102
|
-
- Leftover dead code: a second `getChildOptionRecords()` call at ~line 5887 also computes
|
|
103
|
-
`$res` and discards it (and references `$newPreviousIdentifiers`, only defined when an FK
|
|
104
|
-
field was seen). Left untouched to keep the fix minimal; worth removing separately.
|
|
105
|
-
|
|
106
|
-
## Related docs
|
|
107
|
-
|
|
108
|
-
- 2.0/apps/toga2-commerce/bug/item-view-bundle-vs-direct-fetch.md
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
# toga2-commerce (TOGa Commerce) — 2.0 knowledge
|
|
2
|
-
|
|
3
|
-
| Doc | Summary | Files |
|
|
4
|
-
|-----|---------|-------|
|
|
5
|
-
| [TOGa Commerce (toga2-commerce) Architecture](architecture.md) | TOGa Commerce (npm package `commerce2-react`) is the multi-tenant 2.0 storefront. | toga2-commerce/src/App.tsx, toga2-commerce/src/routes.tsx, toga2-commerce/src/api/axiosInstance.ts, toga2-commerce/src/contexts/AuthContext.tsx, toga2-commerce/src/themeConfig/ThemeContext.tsx, toga2-commerce/src/stores/index.ts |
|
|
6
|
-
| [Item-View Bundle vs Direct Fetch](bug/item-view-bundle-vs-direct-fetch.md) | The item-view page (`/item-view`) fetches a single item two different ways depending on how the user reached it and what access they have: - **Direct** — `GET / | toga2-commerce/src/pages/ItemsView/api/ItemsApi.ts, toga2-commerce/src/pages/ItemsView/viewModel/useItemDetailsViewModel.ts |
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: TOGa Commerce (toga2-commerce) Architecture
|
|
3
|
-
framework: "2.0"
|
|
4
|
-
repo: toga2-commerce
|
|
5
|
-
project: TOGa Commerce
|
|
6
|
-
client: shared
|
|
7
|
-
type: architecture
|
|
8
|
-
status: active
|
|
9
|
-
updated: 2026-06-11
|
|
10
|
-
owners: [bala]
|
|
11
|
-
files:
|
|
12
|
-
- toga2-commerce/src/App.tsx
|
|
13
|
-
- toga2-commerce/src/routes.tsx
|
|
14
|
-
- toga2-commerce/src/api/axiosInstance.ts
|
|
15
|
-
- toga2-commerce/src/contexts/AuthContext.tsx
|
|
16
|
-
- toga2-commerce/src/themeConfig/ThemeContext.tsx
|
|
17
|
-
- toga2-commerce/src/stores/index.ts
|
|
18
|
-
related: []
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Summary
|
|
22
|
-
|
|
23
|
-
TOGa Commerce (npm package `commerce2-react`) is the multi-tenant 2.0 storefront. React 18 +
|
|
24
|
-
Vite + TypeScript + Tailwind. Its backend is api2 (2.0 REST); shared UI comes from the
|
|
25
|
-
`@agilant/toga-blox` component library. Multi-tenant by hostname — dev hosts include
|
|
26
|
-
`compass.togacommerce`, `compasscanada.togacommerce`, `quad.togacommerce`, `togacommerce`
|
|
27
|
-
(see the `npm run` scripts in `package.json`).
|
|
28
|
-
|
|
29
|
-
## Routing & shell
|
|
30
|
-
|
|
31
|
-
- `src/routes.tsx` — `createBrowserRouter` (react-router-dom v7). `PrivateRoute` gates all
|
|
32
|
-
authed pages behind `useAuthenticationFlow`; authed pages are wrapped in `AuthLayout`.
|
|
33
|
-
Pages: Home, Filter, ItemsView (`/item-view`), BundleView, Cart, OrderDetails, Account,
|
|
34
|
-
GetSupport, Login, ResetPassword, Privacy/Terms.
|
|
35
|
-
- `src/App.tsx` — provider stack: `PersistQueryClientProvider` (TanStack Query persisted to
|
|
36
|
-
`localStorage`, key `commerce`, 24h staleTime) -> `AuthProvider` -> `ThemeProvider` ->
|
|
37
|
-
`ToasterProvider` -> `RouterProvider`.
|
|
38
|
-
|
|
39
|
-
## State management
|
|
40
|
-
|
|
41
|
-
- **Server state:** TanStack Query, persisted to localStorage. Query defs in `src/queries/`.
|
|
42
|
-
- **Client state:** Zustand stores in `src/stores/` — cart (`useCartStoreZu`), edit-order mode
|
|
43
|
-
(`useEditOrderZu`), bundle builder, user, fields, view-as impersonation, etc.
|
|
44
|
-
- **Cart <-> API sync:** `src/api/` holds `syncSalesOrderFromApiToLocalStorage`,
|
|
45
|
-
`syncSalesOrderItemsFromLocalStorageCartToApi`, `syncSalesOrdersDataFromLocalStorageCartToApi`.
|
|
46
|
-
The cart lives in localStorage and syncs to api2 sales orders; "Edit Order" mode lets users
|
|
47
|
-
edit an existing sales order (App.tsx tracks a `synced` flag).
|
|
48
|
-
|
|
49
|
-
## Cross-cutting
|
|
50
|
-
|
|
51
|
-
- **Auth:** `src/contexts/AuthContext.tsx`, token-based, plus a "View As" user-impersonation
|
|
52
|
-
flow (`useReturnOriginalUserFromViewAsStore`).
|
|
53
|
-
- **Theming:** `src/themeConfig/ThemeContext.tsx` + `themes.json`, per tenant.
|
|
54
|
-
- **Dynamic fields:** `src/fieldsConfig/` and per-page `FIELDS/<CLIENT>/<LANG>/<ROLE>/*.json`
|
|
55
|
-
drive client-specific form/field sets.
|
|
56
|
-
- **HTTP:** axios instance in `src/api/axiosInstance.ts`; Sentry for error reporting;
|
|
57
|
-
react-hook-form for forms.
|
|
58
|
-
|
|
59
|
-
## Key decisions / gotchas
|
|
60
|
-
|
|
61
|
-
- `dependsOn: ["api2"]` — runtime dependency on the api2 REST surface (not a PHP class
|
|
62
|
-
dependency). Field requests use the api2 `fields` option; deep nested field paths are
|
|
63
|
-
subject to api2's serializer behavior (see related api2 doc on FK resolution).
|
|
64
|
-
|
|
65
|
-
## Related docs
|
|
66
|
-
|
|
67
|
-
- 2.0/apps/api2/bug/nested-field-foreign-key-resolution.md
|
|
68
|
-
- 2.0/apps/toga2-commerce/bug/item-view-bundle-vs-direct-fetch.md
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Item-View Bundle vs Direct Fetch
|
|
3
|
-
framework: "2.0"
|
|
4
|
-
repo: toga2-commerce
|
|
5
|
-
project: TOGa Commerce
|
|
6
|
-
client: shared
|
|
7
|
-
type: feature
|
|
8
|
-
status: active
|
|
9
|
-
updated: 2026-06-11
|
|
10
|
-
owners: [bala]
|
|
11
|
-
files:
|
|
12
|
-
- toga2-commerce/src/pages/ItemsView/api/ItemsApi.ts
|
|
13
|
-
- toga2-commerce/src/pages/ItemsView/viewModel/useItemDetailsViewModel.ts
|
|
14
|
-
related:
|
|
15
|
-
- 2.0/apps/api2/bug/nested-field-foreign-key-resolution.md
|
|
16
|
-
---
|
|
17
|
-
|
|
18
|
-
## Summary
|
|
19
|
-
|
|
20
|
-
The item-view page (`/item-view`) fetches a single item two different ways depending on how
|
|
21
|
-
the user reached it and what access they have:
|
|
22
|
-
|
|
23
|
-
- **Direct** — `GET /items/{uuid}` via `fetchItemDetails()`. Used for users with direct item
|
|
24
|
-
access. Fields come from the client/role `ITEMVIEWPAGEFIELDS.json` config.
|
|
25
|
-
- **Via bundle** — `GET /bundles/{uuid}` via `fetchItemDetailsViaBundle()`, then the bundle's
|
|
26
|
-
`bundleItems` are filtered client-side to the requested item uuid. Used for **indirect-access**
|
|
27
|
-
users (they can read the item through the bundle ACL but not the item endpoint directly).
|
|
28
|
-
Triggered when the URL has `type=bundleItem` and a `bundleUuid` query param.
|
|
29
|
-
|
|
30
|
-
## Key files / entry points
|
|
31
|
-
|
|
32
|
-
- `src/pages/ItemsView/api/ItemsApi.ts`
|
|
33
|
-
- `fetchItemDetails()` — direct `/items/{uuid}` fetch (fields from config, optional persona where).
|
|
34
|
-
- `fetchItemDetailsViaBundle()` — `/bundles/{uuid}` fetch with `bundleItems.item.*` fields,
|
|
35
|
-
then client-side filter to the matching item (API does not support nested-uuid where clauses).
|
|
36
|
-
- `src/pages/ItemsView/viewModel/useItemDetailsViewModel.ts`
|
|
37
|
-
- Chooses the fetch by `viewTypeParam === "bundleItem" && bundleUuid`.
|
|
38
|
-
- Groups features for the Specifications section by
|
|
39
|
-
`itemCategoryFeatures[0].itemCategoryFeatureGroup?.name`. If that group object is missing,
|
|
40
|
-
the heading key becomes the string `undefined`.
|
|
41
|
-
|
|
42
|
-
## How it works
|
|
43
|
-
|
|
44
|
-
1. Read `uuid`, `type`, `bundleUuid` from the URL query string.
|
|
45
|
-
2. If `type=bundleItem` and `bundleUuid` present → `fetchItemDetailsViaBundle`; else
|
|
46
|
-
`fetchItemDetails`.
|
|
47
|
-
3. Normalize the response to `{ data: { items } }` and group `itemFeatures` by their
|
|
48
|
-
category-feature group name for display.
|
|
49
|
-
|
|
50
|
-
## Client variations
|
|
51
|
-
|
|
52
|
-
Multi-tenant by host (compass / compasscanada / quad / togacommerce). Field sets are
|
|
53
|
-
per-client/per-language/per-role under
|
|
54
|
-
`src/pages/ItemsView/viewModel/FIELDS/<CLIENT>/<LANG>/<ROLE>/ITEMVIEWPAGEFIELDS.json`.
|
|
55
|
-
|
|
56
|
-
## Gotchas / known issues
|
|
57
|
-
|
|
58
|
-
- The two fetch paths request the same leaf fields but at different nesting depths
|
|
59
|
-
(`bundle -> bundleItems -> item -> ...` adds two levels). This exposed an api2 serializer
|
|
60
|
-
bug where the `itemCategoryFeatureGroup` foreign key was dropped only on the bundle path,
|
|
61
|
-
making Specifications headings render as `undefined`. Root cause and fix are documented in
|
|
62
|
-
the api2 feature doc below — the frontend grouping logic itself is correct and identical
|
|
63
|
-
on both paths.
|
|
64
|
-
- `fetchItemDetailsViaBundle` exists specifically for indirect-access users, so you cannot
|
|
65
|
-
simply switch it to `/items/{uuid}` — those users would hit an ACL denial.
|
|
66
|
-
|
|
67
|
-
## Related docs
|
|
68
|
-
|
|
69
|
-
- 2.0/apps/api2/bug/nested-field-foreign-key-resolution.md
|