toga-ai 1.0.238 → 1.0.240
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/worker/INDEX.md +1 -1
- package/knowledge/1.0/apps/worker/features/forecast2-netsuite-reconciliation.md +72 -1
- package/knowledge/2.0/apps/toga2-commerce/INDEX.md +1 -0
- package/knowledge/2.0/apps/toga2-commerce/features/expedited-shipping-gating.md +108 -0
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/compass-canada/profile.md +8 -2
- package/knowledge/clients/compass-usa/profile.md +13 -3
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
| [Worker (1.0 Framework) Architecture](architecture.md) | `worker` is the legacy (**1.0** `App_` framework) **background-job tier**. | worker/index.php, worker/_/app/framework.php, worker/crons/, worker/schedules/, worker/ebs/cron.worker.php, worker/.ebextensions/035_cron.worker.config |
|
|
6
6
|
| [Compass MA Sales Order Exception Report](features/compass-ma-sales-order-exception-report.md) | A worker cron that emails operations the "Compass Refresh Exception Report" — Compass `MA%` sales orders whose corresponding Office Depot (ODP) sales order has | worker/crons/toga2/compass/workflow/7_generate_ma_sales_order_exception_report.php |
|
|
7
7
|
| [Compass Partial In-Transit & Delivered Emails (per package)](features/compass-partial-in-transit-delivered-emails.md) | Compass USA and Compass Canada send a **per-package** in-transit email (and a matching delivered email) instead of one email listing the whole order. | worker/crons/toga2/compass/update_salesorder_status_from_odp.php, worker/crons/toga2/compasscanada/workflow/3_update_salesorder_status_from_grand_and_toy.php |
|
|
8
|
-
| [Forecast2 ↔ NetSuite Reconciliation & Trueup Tooling](features/forecast2-netsuite-reconciliation.md) | CLI tools to **audit** and **repair** drift between the production `Forecast` DB (core2) and NetSuite. | test/@dave/checker.php, test/@dave/looper.php, test/@dave/reconcile_netsuite_totals.php, test/@dave/fixer.php, test/@dave/analyze_netsuite_forecast_diff.php, test/@dave/trueup_sales.php, test/@dave/trueup_open_orders.php, test/@dave/loop_trueup_open_orders.php, test/@dave/trueup_opportunities.php, test/@dave/probe_sales_gap_direct.php, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, test/@dave/probe_drift_so_dates.php, test/@dave/probe_profit_invoices.php, test/@dave/probe_profit_gap.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php, worker/crons/toga2/forecast2/periodic_forecast_discrepancy_fix_open_orders.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/schedules/cron.worker.infrastructure.json |
|
|
8
|
+
| [Forecast2 ↔ NetSuite Reconciliation & Trueup Tooling](features/forecast2-netsuite-reconciliation.md) | CLI tools to **audit** and **repair** drift between the production `Forecast` DB (core2) and NetSuite. | test/@dave/checker.php, _underscore/Component/Forecast/SaleImport/SaleImport.php, test/@dave/looper.php, test/@dave/reconcile_netsuite_totals.php, test/@dave/fixer.php, test/@dave/analyze_netsuite_forecast_diff.php, test/@dave/trueup_sales.php, test/@dave/trueup_open_orders.php, test/@dave/loop_trueup_open_orders.php, test/@dave/trueup_opportunities.php, test/@dave/probe_sales_gap_direct.php, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, test/@dave/probe_drift_so_dates.php, test/@dave/probe_profit_invoices.php, test/@dave/probe_profit_gap.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php, worker/crons/toga2/forecast2/periodic_forecast_discrepancy_fix_open_orders.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/schedules/cron.worker.infrastructure.json |
|
|
9
9
|
| [NetSuite → TOGa Supply Per-Client Sync (thin wrappers)](features/netsuite-togasupply-per-client-sync.md) | Syncs NetSuite transactions (sales orders, purchase orders, invoices, item receipts, item fulfillments, inventory adjustments) into each TOGa Supply (2.0) clien | worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/crons/toga2/netsuite/sync_togasupply_canon.php, worker/schedules/cron.worker.sync.json, dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql |
|
|
10
10
|
| [Prudential: Send Shipments for the Day report (daily cron)](features/send-shipments-for-the-day.md) | Daily cron (9:00 PM) that emails Prudential and Dell stakeholders an Excel report of all devices shipped that day, including tracking number, serial number, emp | worker/crons/notifications/reports/send_shipments_for_the_day.php |
|
|
11
11
|
| [Onboarding a Client to the NetSuite TOGa Supply Sync](workflows/onboarding-client-to-netsuite-togasupply-sync.md) | How to add a new TOGa 2 client to the per-client NetSuite → TOGa Supply importer (`worker/crons/toga2/netsuite/`). | worker/crons/toga2/netsuite/sync_togasupply.php, worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/schedules/cron.worker.sync.json, dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql |
|
|
@@ -6,10 +6,11 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
9
|
+
updated: 2026-06-30
|
|
10
10
|
owners: [dfranks]
|
|
11
11
|
files:
|
|
12
12
|
- test/@dave/checker.php
|
|
13
|
+
- _underscore/Component/Forecast/SaleImport/SaleImport.php
|
|
13
14
|
- test/@dave/looper.php
|
|
14
15
|
- test/@dave/reconcile_netsuite_totals.php
|
|
15
16
|
- test/@dave/fixer.php
|
|
@@ -32,6 +33,7 @@ related:
|
|
|
32
33
|
- ../architecture.md
|
|
33
34
|
- ../../library/features/netsuite-suiteql-rest-shim.md
|
|
34
35
|
- ../../library/features/netsuite-suiteql-api-reference.md
|
|
36
|
+
- ../../_underscore/features/forecast-sale-import.md
|
|
35
37
|
---
|
|
36
38
|
|
|
37
39
|
## Summary
|
|
@@ -202,6 +204,47 @@ by reconciling a chosen tranDate range directly against NetSuite.
|
|
|
202
204
|
on `tranDate`, not NS `lastmodifieddate`, so a **symmetric** lastmodified fingerprint isn't possible — it
|
|
203
205
|
would require NS-pull-then-id-lookup or the recency-pruned approach.
|
|
204
206
|
|
|
207
|
+
- **JOURNAL ENTRIES are reconciled WITHIN the SALES category, not as a separate category (TRUE-79862
|
|
208
|
+
architecture decision, dfranks).** A JE is **not its own reconciliation category** — JE rows live in
|
|
209
|
+
the **same `Forecast.Sales` table** as the four sale types (`netsuiteTransactionType='journalEntry'`),
|
|
210
|
+
written by the same `_Component_Forecast_SaleImport` importer (see the sale-import doc's *JournalEntry
|
|
211
|
+
import* section). So the **sales pass covers the WHOLE `Sales` table**, sale-types + JEs together:
|
|
212
|
+
- **`fixer.php`** — `findSalesDiscrepancies` merges, on the **NS side**, the sale-type per-line aggregate
|
|
213
|
+
with `journalEntryNsTotals()` (a GL-account-grouped JE helper); the **FC side reads ALL `Sales` rows**
|
|
214
|
+
(no type filter); each discrepancy is tagged **`kind` (`'sale'` | `'je'`)**. The FIX dispatch routes
|
|
215
|
+
sale ids to `fixSales()` and JE ids to `fixJournalEntries()`. The per-corrector DELETE scoping stays
|
|
216
|
+
**per-type** (`fixSales` delete-candidates scoped to the 4 sale types; `fixJournalEntries` scoped to
|
|
217
|
+
`'journalEntry'`) so neither corrector deletes the other's rows.
|
|
218
|
+
- **`checker.php`** — the sales `nsSub` is a **`UNION ALL`** of the sale-type arm and a JE arm; the sales
|
|
219
|
+
`fcSub` reads **all `Sales` rows**. checker needs **no per-item/JE-specific logic** because its
|
|
220
|
+
fingerprint compares per-transaction (per-id) totals + the id-set, which are **item-agnostic**.
|
|
221
|
+
- **JE→`Forecast.Sales` reconciliation math + scope (mirrors `_Component_Forecast_SaleImport::buildJournalEntryRows`).**
|
|
222
|
+
Only JE lines posting to the revenue/cost **account-NUMBER allowlist** are sales-relevant: revenue
|
|
223
|
+
`41100/41300/41500`, cost `51100/51200` (exact-number, never prefix — see the sale-import doc for the
|
|
224
|
+
sub-account traps). `revenue = Σ(−foreignamount)` over revenue-acct lines; `profit = Σ(−foreignamount)`
|
|
225
|
+
over **all** allowlisted lines (cost lines net profit down via debit-positive `foreignamount`). Group by
|
|
226
|
+
**(local salesRep, item)** → one Sales row per group; synthetic `lineNumber` from
|
|
227
|
+
`ksort(json_encode([salesRepId,itemId]))` then `1..N` — **fixer MUST key identically** or its upsert
|
|
228
|
+
dupes against the webhook importer's rows. JE `foreignamount`s are **cent-precision**, so a per-JE
|
|
229
|
+
sum-then-round equals the importer's per-salesRep round-then-sum **exactly** — this is what keeps
|
|
230
|
+
checker's "in sync" == fixer's "nothing to fix" for JEs (the compute-identically invariant applied to JEs).
|
|
231
|
+
Scope facts: of **~1235 JEs** in 2025-01-01..present, only **~295** touch a revenue/cost account; **~34**
|
|
232
|
+
of those net to **0 rev AND 0 profit** and import no rows. JEs hitting only bank/equity/tax/AP accounts
|
|
233
|
+
are correctly excluded **by design** (e.g. JE 7213407 = a Bank↔Equity reclass, 0 allowlist lines → never
|
|
234
|
+
imported — not a miss). An accrual JE and its NetSuite auto-reversal are **separate transactions**, each
|
|
235
|
+
reconciled independently by its own `internalId`.
|
|
236
|
+
- **JE item dimension is NULL today; here is how to enable it later.** `itemId` is null on JE Sales rows
|
|
237
|
+
because the JE import path reads item **only** via the configurable `JE_LINE_ITEM_FIELD` (currently null)
|
|
238
|
+
— it does **not** read native `transactionline.item`, so even a populated native `tl.item` would NOT flow
|
|
239
|
+
through. To enable: (1) set `JE_LINE_ITEM_FIELD` to the source field (`'item'` for native, or a
|
|
240
|
+
`custcol_*` if a custom column is added) — `journalLineLookup` then resolves it via
|
|
241
|
+
`lookupId('Items','netsuiteItemInternalId',…)` and the group key already carries the item slot; AND (2)
|
|
242
|
+
update **`fixer.php`** to SELECT the item column in its JE SuiteQL, resolve NS item→local id (reuse
|
|
243
|
+
`createMissingForecastItemFromNetSuite` self-heal), and extend the group key to `(salesRep, item)`.
|
|
244
|
+
**`checker.php` needs no change.** Consider whether item should self-heal (salesRep does **not** — see
|
|
245
|
+
the sale-import doc). The salesRep dimension itself is the JE line custom column `custcol_sales_rep_line`
|
|
246
|
+
(wired under TRUE-79862).
|
|
247
|
+
|
|
205
248
|
## Data model
|
|
206
249
|
|
|
207
250
|
`Forecast.Sales`, `Forecast.OpenOrderItems` on the **core2** cluster
|
|
@@ -214,6 +257,16 @@ None — Forecast2 is a single shared dataset.
|
|
|
214
257
|
|
|
215
258
|
## Gotchas / known issues
|
|
216
259
|
|
|
260
|
+
- **CROSS-TYPE CONTAMINATION: once JEs share `Forecast.Sales`, any FC Sales read missing a type scope
|
|
261
|
+
treats JE rows as rogue sales.** Discovered live (TRUE-79862): the SALES fix **deleted 74 JE rows
|
|
262
|
+
(8 JEs) as "stale sales"** because `findSalesDiscrepancies`'s FC query had **no type filter**, so JE
|
|
263
|
+
rows looked like FC-only sales that the NS-sales aggregate never returns. **Correct resolution is NOT
|
|
264
|
+
to filter JEs OUT of the sales pass** — it is to reconcile the **whole table together** (NS side =
|
|
265
|
+
sale-types `UNION` JE; FC side = all rows) and **route the FIX by `kind`** (`'sale'`→`fixSales`,
|
|
266
|
+
`'je'`→`fixJournalEntries`), keeping each corrector's DELETE scoping per-type so neither deletes the
|
|
267
|
+
other's rows. (See the JE-within-Sales architecture under *How it works*.) General lesson: when two
|
|
268
|
+
transaction families share one table, an audit read with no type scope manufactures phantom FC-only
|
|
269
|
+
deltas for the other family.
|
|
217
270
|
- **An NS-DELETED invoice leaves STALE cost-only `Forecast.Sales` rows the add/update-only import can
|
|
218
271
|
never remove.** The 5-min `import_sales.php` cron is **add/update-only — it has no delete path** (only
|
|
219
272
|
the nightly discrepancy-fix deletes). So when an invoice is **deleted in NetSuite**, its already-imported
|
|
@@ -420,6 +473,24 @@ None — Forecast2 is a single shared dataset.
|
|
|
420
473
|
|
|
421
474
|
## Change history
|
|
422
475
|
|
|
476
|
+
- 2026-06-30 — **Folded JOURNAL ENTRIES into the SALES reconciliation in `fixer.php` + `checker.php`
|
|
477
|
+
(TRUE-79862).** Architecture decision (dfranks): a JE is **not its own category** — JE rows live in the
|
|
478
|
+
same `Forecast.Sales` table (`netsuiteTransactionType='journalEntry'`) as the four sale types, so the
|
|
479
|
+
sales pass reconciles the **whole table**. `fixer.findSalesDiscrepancies` merges the NS sale-type
|
|
480
|
+
aggregate with `journalEntryNsTotals()`, reads ALL FC Sales rows, tags each diff `kind` (`sale`|`je`),
|
|
481
|
+
and routes the FIX (`fixSales`/`fixJournalEntries`) with per-type DELETE scoping. `checker`'s sales
|
|
482
|
+
`nsSub` becomes a `UNION ALL` (sale-types + JE) over all FC Sales rows — no JE-specific logic since its
|
|
483
|
+
fingerprint is per-id/item-agnostic. **Gotcha recorded: cross-type contamination** — the initial
|
|
484
|
+
un-typed FC Sales read deleted 74 JE rows (8 JEs) as "stale sales"; fixed by reconciling the families
|
|
485
|
+
together + routing by kind, not by filtering JEs out. **JE math/scope** (mirrors
|
|
486
|
+
`buildJournalEntryRows`): allowlist accts 41100/41300/41500 rev + 51100/51200 cost; group by
|
|
487
|
+
(salesRep,item) with `ksort(json_encode([salesRep,item]))` synthetic lineNumber — fixer keys identically
|
|
488
|
+
to the importer or it dupes; cent-precision foreignamounts make sum-then-round == importer round-then-sum
|
|
489
|
+
(compute-identically holds for JEs). Of ~1235 JEs (2025→present) only ~295 hit a rev/cost acct, ~34 net
|
|
490
|
+
to $0 and import nothing; bank/equity/tax/AP-only JEs excluded by design (not misses). Accrual + NS
|
|
491
|
+
auto-reversal reconcile independently per internalId. **Item dimension is null today** (`JE_LINE_ITEM_FIELD`
|
|
492
|
+
null; native `tl.item` not read); enabling it needs the importer field set AND fixer SELECT/resolve/group-
|
|
493
|
+
by-(salesRep,item) — checker needs no change. (dfranks)
|
|
423
494
|
- 2026-06-29 — **Recorded that `fixer.php`'s OOI path lacks `locationId`/`quantityBackordered`/
|
|
424
495
|
`amountDue`** (planning for TRUE-79162). The Sales path already handles anchor-line `amountDue`
|
|
425
496
|
behind a `forecastColumnExists('Sales','amountDue')` guard; the OpenOrderItems path (lookup SELECT,
|
|
@@ -6,5 +6,6 @@
|
|
|
6
6
|
| [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
7
|
| [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
8
|
| [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 |
|
|
9
|
+
| [Config-Driven Expedited Shipping Gating (Cart)](features/expedited-shipping-gating.md) | On the toga2-commerce **Cart** page, expedited shipping options (**"2nd Day EOB"** and **"Next Day Air"**) are only offered in the *Shipping Method* dropdown wh | toga2-commerce/src/pages/Cart/helpers/shippingOptionGates.ts, toga2-commerce/src/pages/Cart/viewModel/FIELDS/shared/shippingOptionGates.ts, toga2-commerce/src/pages/Cart/view/cartForm/CartForm.tsx, toga2-commerce/src/pages/Cart/CartPage.tsx |
|
|
9
10
|
| [Multi-Tenant Resolution & Theming](features/multi-tenant-theming.md) | `toga2-commerce` serves multiple clients from one codebase. | src/themeConfig/themes.json, src/themeConfig/ThemeContext.tsx, src/themeConfig/types.ts, src/components/ThemeSwitcher/ThemeSwitcher.tsx, src/components/AuthLayout/AuthLayout.tsx, src/api/axiosInstance.ts, src/contexts/AuthContext.tsx, tailwind.config.js |
|
|
10
11
|
| [AWS Amplify Build & Deploy (non-prod environments)](workflows/amplify-build-and-deploy.md) | How `toga2-commerce` (React + Vite, "commerce2-react") builds and deploys on **AWS Amplify**. | toga2-commerce/amplify.yml, toga2-commerce/.gitattributes, toga2-commerce/package.json, toga2-commerce/.github/workflows/sync-stage-environments.yml |
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Config-Driven Expedited Shipping Gating (Cart)
|
|
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-30
|
|
10
|
+
owners: [tcox]
|
|
11
|
+
files:
|
|
12
|
+
- toga2-commerce/src/pages/Cart/helpers/shippingOptionGates.ts
|
|
13
|
+
- toga2-commerce/src/pages/Cart/viewModel/FIELDS/shared/shippingOptionGates.ts
|
|
14
|
+
- toga2-commerce/src/pages/Cart/view/cartForm/CartForm.tsx
|
|
15
|
+
- toga2-commerce/src/pages/Cart/CartPage.tsx
|
|
16
|
+
related:
|
|
17
|
+
- ../../../../clients/compass-usa/profile.md
|
|
18
|
+
- ../../../../clients/compass-canada/profile.md
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Summary
|
|
22
|
+
On the toga2-commerce **Cart** page, expedited shipping options (**"2nd Day EOB"** and
|
|
23
|
+
**"Next Day Air"**) are only offered in the *Shipping Method* dropdown when the cart contains
|
|
24
|
+
a **computer kit**. Otherwise they are filtered out of the options, and any previously
|
|
25
|
+
selected expedited method auto-resets to **"Standard Ground"**. The behavior is **config
|
|
26
|
+
driven** — a tenant opts in per shipping-method field via an `optionGates` entry; there is no
|
|
27
|
+
`if (clientSlug === ...)` branching. Omitting `optionGates` means expedited is always shown.
|
|
28
|
+
|
|
29
|
+
Applies today to all three tenants: **COMPASS**, **COMPASSCANADA** (English + French), and
|
|
30
|
+
**QUAD**.
|
|
31
|
+
|
|
32
|
+
## Key files / entry points
|
|
33
|
+
- `src/pages/Cart/helpers/shippingOptionGates.ts` (NEW) — pure helpers + types:
|
|
34
|
+
`ShippingOptionGate` / `ShippingOptionRule`, `filterShippingOptionsByGates`,
|
|
35
|
+
`getHiddenShippingOptionNames`, `evaluateShippingOptionRule`, `findShippingMethodField`
|
|
36
|
+
(resolves the shipping field by `valueKey`, **not** by index), `getBaseShippingOptionName` +
|
|
37
|
+
`SHIPPING_OPTION_LABEL_SEPARATOR`, and `STANDARD_GROUND_SHIPPING_NAME`.
|
|
38
|
+
- `src/pages/Cart/viewModel/FIELDS/shared/shippingOptionGates.ts` (NEW) — the
|
|
39
|
+
`EXPEDITED_SHIPPING_GATE` config constant: `optionNames` `["2nd Day EOB","Next Day Air"]`,
|
|
40
|
+
`visibleWhen { item: "primaryItem", path: "item.itemCategory.name", operator: "equals",
|
|
41
|
+
value: "COMPUTERS" }`.
|
|
42
|
+
- 15 `CARTPAGE.ts` files (COMPASS x4 roles, COMPASSCANADA ENGLISH x4 + FRENCH x4, QUAD x3) —
|
|
43
|
+
each shipping-method field opts in via `optionGates: [EXPEDITED_SHIPPING_GATE]`.
|
|
44
|
+
- `src/pages/Cart/view/cartForm/CartForm.tsx` — filters `shippingMethodOptions` through the
|
|
45
|
+
gates (reactive on `cartData.cartBundles`) before the select renders; resolves the shipping
|
|
46
|
+
field via `findShippingMethodField`; uses `SHIPPING_OPTION_LABEL_SEPARATOR` for the
|
|
47
|
+
cost-suffix label.
|
|
48
|
+
- `src/pages/Cart/CartPage.tsx` — the warning-modal effect derives expedited names from the
|
|
49
|
+
config gates; a NEW auto-reset effect (guarded on `dirtyFields.shippingMethod` + loaded
|
|
50
|
+
options) resets a now-hidden expedited selection back to Standard Ground.
|
|
51
|
+
|
|
52
|
+
## How it works
|
|
53
|
+
1. The config layer (`EXPEDITED_SHIPPING_GATE`) declares which option names are gated and the
|
|
54
|
+
condition under which they are *visible* (`visibleWhen`). A shipping-method field opts in by
|
|
55
|
+
listing the gate in `optionGates`.
|
|
56
|
+
2. A **"computer kit"** is identified by the bundle's **PRIMARY item** —
|
|
57
|
+
`bundleItemGroup.slug === "primary"` — having `item.itemCategory.name === "COMPUTERS"`. This
|
|
58
|
+
reuses the same `{ item, path, operator, value }` rule vocabulary as the existing bundle-page
|
|
59
|
+
one-per-order limit (`evaluateCartQuantityRestriction.ts` + `restrictedQuantity` in
|
|
60
|
+
`BUNDLEDETAILSPAGE.json` / `CartOverlay/Bundle.tsx`). `value` may be a string **or** a
|
|
61
|
+
`string[]`, so qualifying categories stay editable in config without code changes.
|
|
62
|
+
3. Cart contents are sourced from `useCartStoreZu` (`cartData.cartBundles`), so detection
|
|
63
|
+
reacts to add/remove. A computer kit plus a non-computer item (printer/accessory) still
|
|
64
|
+
shows expedited; removing the kit hides it.
|
|
65
|
+
4. `CartForm` runs `filterShippingOptionsByGates` over the fetched options before rendering the
|
|
66
|
+
select; `CartPage` runs the auto-reset effect so an already-chosen expedited method does not
|
|
67
|
+
silently survive once it is no longer offered.
|
|
68
|
+
|
|
69
|
+
This mirrors the toga2.5 / toga25-supply "named rule" / config-driven cart pattern: tenant
|
|
70
|
+
behavior is expressed in config, not in code branches.
|
|
71
|
+
|
|
72
|
+
## Gotchas
|
|
73
|
+
- **Compass MacBook catalog categorization is INCONSISTENT, and it breaks the computer-kit
|
|
74
|
+
assumption.** Verified in prod `Client_Compass.Items`: MacBooks are spread across at least
|
|
75
|
+
**four** categories — `"APPLE LAPTOP"` (e.g. 14" MacBook Pro M5, partNumber `MDE54LL/A-S`),
|
|
76
|
+
`"COMPUTERS"` (14"/16" MacBook Pro M4, `MW2W3LLA-S` / `MX2X3LLA-S`), `"MAC & ACCESSORIES"`
|
|
77
|
+
(~18 older M1/M2 MacBook Air/Pro models), and `"PDC NEW HIRE - APPLE"`. The gate matches
|
|
78
|
+
`itemCategory.name === "COMPUTERS"` **only**, so MacBooks tagged `APPLE LAPTOP` /
|
|
79
|
+
`MAC & ACCESSORIES` do **not** qualify for expedited shipping (and would not trigger the
|
|
80
|
+
one-per-order limit either). Confirmed live on beta and prod: the "Compass 14\" Macbook"
|
|
81
|
+
kit's primary item resolves to `APPLE LAPTOP`. The backend assumption (Alex) was that all
|
|
82
|
+
MacBooks were already `COMPUTERS`; the data shows otherwise. **Team decision this session:**
|
|
83
|
+
keep the rule keyed to `COMPUTERS` only (intended). The fix is **catalog data
|
|
84
|
+
normalization** (or extending the config `value` list to
|
|
85
|
+
`["COMPUTERS","APPLE LAPTOP"]`) — it is *not* a code gap. Hand the categorization to the
|
|
86
|
+
catalog/data team.
|
|
87
|
+
- **The gate is UI-only, not server-enforced.** Shipping methods come from `/shipping-methods`
|
|
88
|
+
(UPS carrier, fetched in `useCartViewModel`) and are filtered client-side. The gate is a
|
|
89
|
+
selection restriction, not order validation. Follow-up recommended: enforce
|
|
90
|
+
expedited-requires-kit at order submission in **api2**.
|
|
91
|
+
- **Only the top-level primary item is scanned.** Nested child-bundle items are not inspected
|
|
92
|
+
for the computer category (consistent with the existing `checkCartContainsComputerKit`
|
|
93
|
+
detector). Fine today, but a miss if a `COMPUTERS` item ever lives only in a nested child
|
|
94
|
+
bundle.
|
|
95
|
+
- **No automated test was added.** The repo has no unit-test runner (Cypress only). Verified
|
|
96
|
+
via `tsc -b` (clean, twice) and an independent maker≠checker code review. ESLint could not
|
|
97
|
+
run locally (missing `eslint-plugin-react-compiler` — environment issue).
|
|
98
|
+
|
|
99
|
+
## Change history
|
|
100
|
+
- 2026-06-30 — Built config-driven expedited-shipping gating: expedited methods shown only
|
|
101
|
+
when the cart holds a COMPUTERS-category computer kit, with auto-reset to Standard Ground
|
|
102
|
+
otherwise; tenant opt-in via `optionGates` (no client-slug branching). Code review fixed 3
|
|
103
|
+
findings: fail-open index read → resolve field by `valueKey`; substring `.includes` match →
|
|
104
|
+
base-name compare via `getBaseShippingOptionName`; edit-order silent-downgrade/load race →
|
|
105
|
+
reset guarded on dirty + loaded options. Documented the Compass MacBook category
|
|
106
|
+
inconsistency as a known gotcha (data-normalization item, not a code gap). (tcox)
|
|
107
|
+
</content>
|
|
108
|
+
</invoke>
|
package/knowledge/INDEX.md
CHANGED
|
@@ -27,7 +27,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
27
27
|
- **talos** (TOGa IQ) — 7 doc(s) → [2.0/apps/talos/INDEX.md](2.0/apps/talos/INDEX.md)
|
|
28
28
|
- **voice-to-voice** (TOGa Voice) — 4 doc(s) → [2.0/apps/voice-to-voice/INDEX.md](2.0/apps/voice-to-voice/INDEX.md)
|
|
29
29
|
- **ai-bdr** (AI-BDR) — 4 doc(s) → [2.0/apps/ai-bdr/INDEX.md](2.0/apps/ai-bdr/INDEX.md)
|
|
30
|
-
- **toga2-commerce** (TOGa Commerce) —
|
|
30
|
+
- **toga2-commerce** (TOGa Commerce) — 7 doc(s) → [2.0/apps/toga2-commerce/INDEX.md](2.0/apps/toga2-commerce/INDEX.md)
|
|
31
31
|
- **toga25-supply** (TOGa 2.5 Supply) — 7 doc(s) → [2.0/apps/toga25-supply/INDEX.md](2.0/apps/toga25-supply/INDEX.md)
|
|
32
32
|
- **toga-blox** (TOGa Blox) — 7 doc(s) → [2.0/apps/toga-blox/INDEX.md](2.0/apps/toga-blox/INDEX.md)
|
|
33
33
|
|
|
@@ -13,11 +13,12 @@ project: _Underscore
|
|
|
13
13
|
client: compass-canada
|
|
14
14
|
type: profile
|
|
15
15
|
status: active
|
|
16
|
-
updated: 2026-06-
|
|
17
|
-
owners: [jcardinal, bala]
|
|
16
|
+
updated: 2026-06-30
|
|
17
|
+
owners: [jcardinal, bala, tcox]
|
|
18
18
|
files: []
|
|
19
19
|
related:
|
|
20
20
|
- ../compass-usa/profile.md
|
|
21
|
+
- ../../2.0/apps/toga2-commerce/features/expedited-shipping-gating.md
|
|
21
22
|
---
|
|
22
23
|
|
|
23
24
|
## Summary
|
|
@@ -41,6 +42,11 @@ to but distinct from Compass USA. Like Compass USA it spans the **2.0** commerce
|
|
|
41
42
|
(`1_…` transmit SOs to MITS, `2_…` transmit POs to vendors, `3_…` status from G&T cXML,
|
|
42
43
|
`4_…` import G&T ASNs).
|
|
43
44
|
|
|
45
|
+
## Storefront notes (toga2-commerce)
|
|
46
|
+
- Runs the same `toga2-commerce` storefront (2.0, consumes api2), with English + French cart
|
|
47
|
+
configs (COMPASSCANADA ENGLISH / FRENCH). Cart expedited-shipping gating applies here too —
|
|
48
|
+
see [Config-Driven Expedited Shipping Gating](../../2.0/apps/toga2-commerce/features/expedited-shipping-gating.md).
|
|
49
|
+
|
|
44
50
|
## Notes
|
|
45
51
|
- Customer language preference: `UserGlobalSettings.settingId = 2` (`en` / `fr-CA`); customer-
|
|
46
52
|
facing emails are sent in EN or FR accordingly.
|
|
@@ -12,11 +12,12 @@ project: _Underscore
|
|
|
12
12
|
client: compass-usa
|
|
13
13
|
type: profile
|
|
14
14
|
status: active
|
|
15
|
-
updated: 2026-06-
|
|
16
|
-
owners: [jcardinal, bala]
|
|
15
|
+
updated: 2026-06-30
|
|
16
|
+
owners: [jcardinal, bala, tcox]
|
|
17
17
|
files: []
|
|
18
18
|
related:
|
|
19
19
|
- features/asn-to-item-fulfillment.md
|
|
20
|
+
- ../../2.0/apps/toga2-commerce/features/expedited-shipping-gating.md
|
|
20
21
|
---
|
|
21
22
|
|
|
22
23
|
## Summary
|
|
@@ -32,7 +33,16 @@ separate, related client (see its own profile).
|
|
|
32
33
|
Client-specific model overrides live under `_underscore/Model/Compass/`.
|
|
33
34
|
- **1.0:** worker crons under `worker/crons/toga2/compass/` handle email-based imports and
|
|
34
35
|
notifications.
|
|
35
|
-
- Storefront: compass.togacommerce.com / compass.togahub.com.
|
|
36
|
+
- Storefront: compass.togacommerce.com / compass.togahub.com. The storefront app repo is
|
|
37
|
+
**`toga2-commerce`** (2.0, consumes api2).
|
|
38
|
+
|
|
39
|
+
## Storefront notes (toga2-commerce)
|
|
40
|
+
- Cart expedited-shipping gating: expedited methods ("2nd Day EOB", "Next Day Air") are shown
|
|
41
|
+
only when the cart contains a COMPUTERS-category computer kit — see
|
|
42
|
+
[Config-Driven Expedited Shipping Gating](../../2.0/apps/toga2-commerce/features/expedited-shipping-gating.md).
|
|
43
|
+
**Known data issue:** many Compass MacBooks are categorized `APPLE LAPTOP` /
|
|
44
|
+
`MAC & ACCESSORIES`, not `COMPUTERS`, so those kits do **not** qualify for expedited — a
|
|
45
|
+
catalog-data normalization matter, not a code gap.
|
|
36
46
|
|
|
37
47
|
## Vendors & integrations
|
|
38
48
|
- **Office Depot (ODP)** — vendor id 1. ASNs arrive via **cXML** (direct V2 API) and via the
|
package/package.json
CHANGED