toga-ai 1.0.564 → 1.0.565
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/library/features/netsuite-item-isfulfillable-sync.md +32 -4
- package/knowledge/1.0/apps/worker/workflows/isfulfillable-multi-client-backfill.md +21 -1
- package/knowledge/2.0/apps/_underscore/INDEX.md +1 -1
- package/knowledge/2.0/apps/_underscore/features/fulfillable-item-propagation.md +69 -6
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/compass-canada/profile.md +13 -1
- package/knowledge/clients/compass-usa/INDEX.md +1 -0
- package/knowledge/clients/compass-usa/features/isfulfillable-data-quality-and-type-rule.md +187 -0
- package/knowledge/clients/compass-usa/profile.md +10 -1
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ project: Library
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-08-12
|
|
10
10
|
owners: [bala]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/netsuite.php
|
|
@@ -18,6 +18,7 @@ related:
|
|
|
18
18
|
- ../../worker/features/netsuite-togasupply-per-client-sync.md
|
|
19
19
|
- ../../worker/workflows/isfulfillable-multi-client-backfill.md
|
|
20
20
|
- ../../../2.0/apps/_underscore/features/fulfillable-item-propagation.md
|
|
21
|
+
- ../../../clients/compass-usa/features/isfulfillable-data-quality-and-type-rule.md
|
|
21
22
|
---
|
|
22
23
|
|
|
23
24
|
## Summary
|
|
@@ -85,9 +86,27 @@ physical goods from services — `itemtype` would, but the sync has never used `
|
|
|
85
86
|
`isSerialized`), so this feature intentionally reflects NetSuite's flag **as-is**. **Net effect:
|
|
86
87
|
nearly everything resolves to `fulfillable = 1`.**
|
|
87
88
|
|
|
88
|
-
- **Open product decision (
|
|
89
|
-
|
|
90
|
-
**
|
|
89
|
+
- **Open product decision (RESOLVED for Compass, 2026-08-12):** because services come back
|
|
90
|
+
fulfillable, the storefront makes everything clickable. A prod evidence review found that
|
|
91
|
+
`itemtype` **cannot confirm** fulfillability (physical types like DESKTOPS show 0% tracked) but a
|
|
92
|
+
**tracking ride-along test** cleanly disqualifies fee/software/miscellaneous lines. Compass adopted
|
|
93
|
+
a **type-derived override** of NetSuite's value — see
|
|
94
|
+
[Compass isFulfillable data quality & type rule](../../../clients/compass-usa/features/isfulfillable-data-quality-and-type-rule.md).
|
|
95
|
+
Read the write-time-guard gotcha below before implementing it anywhere.
|
|
96
|
+
|
|
97
|
+
## ⚠ CRITICAL — the existing-item refresh REVERTS any local override
|
|
98
|
+
`getCreateItem`'s refresh (added 2026-07-28) rewrites `isFulfillable` on **existing** items whenever
|
|
99
|
+
NetSuite's value differs from the stored value. NetSuite returns **`T` for service (`NonInvtPart`)
|
|
100
|
+
items**, so **any locally-derived `0` is flipped back to `1` on the next sync run**. A data migration
|
|
101
|
+
alone cannot hold an override — it silently reverts within a day.
|
|
102
|
+
|
|
103
|
+
Proven from the prod audit log: Compass items **2382 / 2384 / 2385** (`US-IOS-SSC-SHR`,
|
|
104
|
+
`US-IOS-VENUENEXT`, `US-IOS-VIRTUALMGR` — itemType `SERVICES`, assetType `CONSULTING`) were stamped
|
|
105
|
+
**NULL → 1 on 2026-08-04 13:56:00-01** by `apiId 1`, `userId NULL`, inside a 1–2 items/second cron
|
|
106
|
+
sweep that started 13:45 and set every item to `1`.
|
|
107
|
+
|
|
108
|
+
**Rule: any policy that overrides NetSuite's flag must be enforced at WRITE TIME** — a
|
|
109
|
+
`prePut`/`prePost` guard on Items in `_Model_Client_Item` — **in addition to** the backfill.
|
|
91
110
|
|
|
92
111
|
## Verification performed
|
|
93
112
|
- **Prod read-only dry-run:** 20 reachable Compass items; chain walk correct
|
|
@@ -106,6 +125,13 @@ nearly everything resolves to `fulfillable = 1`.**
|
|
|
106
125
|
in `Client_Compass.Apis` (name `Agilant`) — never reproduce the secret value.
|
|
107
126
|
|
|
108
127
|
## Change history
|
|
128
|
+
- 2026-08-12 — Prod investigation (no code change): recorded that the **existing-item refresh
|
|
129
|
+
reverts any local override** (NetSuite returns `T` for services; audit-log proof on Compass items
|
|
130
|
+
2382/2384/2385, stamped NULL→1 on 2026-08-04), so an override must be enforced by a write-time
|
|
131
|
+
`prePut`/`prePost` guard, not a migration. Closed the long-standing **open product decision** for
|
|
132
|
+
Compass — item type cannot *confirm* fulfillability, but the tracking ride-along test disqualifies
|
|
133
|
+
fee/software/miscellaneous lines; Compass adopted a type-derived override (see the client doc).
|
|
134
|
+
(bala)
|
|
109
135
|
- 2026-07-28 — Code-review hardening: (1) `getCreateItem` now **refreshes `isFulfillable` on
|
|
110
136
|
existing items** when NetSuite's value differs (not create-only), mirroring the serialized flag;
|
|
111
137
|
diff-only PUT keeps re-syncs no-op. (2) The two other `/items` creation paths
|
|
@@ -131,3 +157,5 @@ nearly everything resolves to `fulfillable = 1`.**
|
|
|
131
157
|
- [Phase 2 — isFulfillable propagation up the SO↔PO chain (2.0)](../../../2.0/apps/_underscore/features/fulfillable-item-propagation.md).
|
|
132
158
|
- [isFulfillable multi-client backfill (worker)](../../worker/workflows/isfulfillable-multi-client-backfill.md)
|
|
133
159
|
— the cross-client catch-up cron and its client-DB / catalog-matching gotchas.
|
|
160
|
+
- [Compass isFulfillable — Data Quality & the Type-Derived Rule](../../../clients/compass-usa/features/isfulfillable-data-quality-and-type-rule.md)
|
|
161
|
+
— measured prod state, the structural reach limit, and the Compass override + its write-time guard.
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-08-12
|
|
10
10
|
owners: [bala]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/netsuite/backfill_isfulfillable_all_clients.php
|
|
@@ -16,6 +16,7 @@ related:
|
|
|
16
16
|
- ../../library/features/netsuite-item-isfulfillable-sync.md
|
|
17
17
|
- ../../../2.0/apps/_underscore/features/fulfillable-item-propagation.md
|
|
18
18
|
- ../../../clients/compass-usa/profile.md
|
|
19
|
+
- ../../../clients/compass-usa/features/isfulfillable-data-quality-and-type-rule.md
|
|
19
20
|
---
|
|
20
21
|
|
|
21
22
|
## Summary
|
|
@@ -46,6 +47,20 @@ recursive up-chain propagation** — it never writes the DB directly and never w
|
|
|
46
47
|
beta/sandbox endpoint.
|
|
47
48
|
|
|
48
49
|
## Gotchas / known issues
|
|
50
|
+
- **⚠⚠ Re-running this backfill CANNOT close the remaining NULLs — they are structurally
|
|
51
|
+
unreachable (verified prod, 2026-08-12).** The cron only re-PUTs **Agilant source** items and
|
|
52
|
+
relies on the api2 interceptor to walk up the chain, which requires the client PO to have an
|
|
53
|
+
Agilant-tier SO behind it (a `PurchaseOrderItems_SalesOrderItems` row). **Vendor-direct POs have
|
|
54
|
+
none.** In prod `Client_Compass`, **zero** items with `isFulfillable IS NULL` reach the Agilant
|
|
55
|
+
tier (vs **52,990** line-level reaches for `isFulfillable = 1` items). Per vendor — items ordered /
|
|
56
|
+
reaching the Agilant tier: OFFICE DEPOT **497/124**; STRATEGIC SYSTEMS **66/0**; FREEDOM GROUPS LLC
|
|
57
|
+
**61/0**; PRESIDIO **45/0**; COMPASS GROUP **35/0**; ENCOMPASS SUPPLY CHAIN **2/0**. Only ODP
|
|
58
|
+
partially routes through Agilant NetSuite. **Do not schedule another run expecting these to fill
|
|
59
|
+
in** — closing the gap needs a different data source (see the
|
|
60
|
+
[Compass data-quality doc](../../../clients/compass-usa/features/isfulfillable-data-quality-and-type-rule.md)).
|
|
61
|
+
- **⚠ Compass Canada was never covered by this run.** `Client_CompassCanada` is **220 items, 100%
|
|
62
|
+
NULL** as of 2026-08-12 — the storefront Qty Fulfilled cell is dead across the entire Canada
|
|
63
|
+
tenant. Canada is not in the 17-client list above and has never been stamped at all.
|
|
49
64
|
- **Agilant catalog is matched by NAME, not id.** `Catalogs.name = 'Agilant'` is stable across every
|
|
50
65
|
client, but the `catalogId` **differs per client** — **Quad = 2, all other clients = 1** (verified
|
|
51
66
|
across all 17 client DBs). Hardcoding `catalogId = 2` would silently hit only Quad and miss the
|
|
@@ -73,6 +88,11 @@ recursive up-chain propagation** — it never writes the DB directly and never w
|
|
|
73
88
|
client backfilled from all its Agilant items.
|
|
74
89
|
|
|
75
90
|
## Change history
|
|
91
|
+
- 2026-08-12 — Prod audit of the post-run state: recorded that the remaining NULLs are
|
|
92
|
+
**structurally unreachable by this cron** (vendor-direct POs have no Agilant-tier SO for the
|
|
93
|
+
interceptor to walk; 0 NULL Compass items reach the Agilant tier, with per-vendor reach counts),
|
|
94
|
+
and that **Compass Canada was never covered** (220 items, 100% NULL). Post-run Compass state:
|
|
95
|
+
770 NULL / 25 zero / 697 one, with 12,318 order lines fulfilled but flagged NULL. (bala)
|
|
76
96
|
- 2026-07-28 — Built and ran the multi-client `backfill_isfulfillable_all_clients.php`: direct
|
|
77
97
|
client-DB scoping of Agilant-catalog items (matched by `Catalogs.name`, not id) keyed by
|
|
78
98
|
`c_netsuiteInternalItemId`, NetSuite reads by internal id (cached across clients), re-PUT to api2 so
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
| [Record-Changed Event Publishing (_Event::publish to SQS)](features/event-publish-sqs.md) | `_Event::publish()` (in `_underscore/Event.php`) is the PHP side of the real-time event pipeline. | _underscore/Event.php |
|
|
24
24
|
| [FIELD_STORAGE fields — per-row lazy hydration and the platform-wide missing-column 500](features/field-storage-row-hydration.md) | `FIELD_STORAGE` is the 2.0 field type for blob-backed columns (S3 or local folder). | _underscore/Model.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/Invoice.php, dbchanges2/Core/HISTORIC/2024/2024-11b - item-fulfillments.sql |
|
|
25
25
|
| [Forecast.Sales NetSuite import engine (real-time webhook)](features/forecast-sale-import.md) | Real-time importer that takes a NetSuite **sale** record and writes its lines into `Forecast.Sales` (the Forecast2 revenue table). | worker2/Component/Forecast/SaleImport/SaleImport.php, worker2/Component/Forecast/Db/Db.php, _underscore/Component/Api/Netsuite/Netsuite.php, worker2/Worker/Netsuite/Invoice.php, worker2/Worker/Netsuite/CashSale.php, worker2/Worker/Netsuite/CreditMemo.php, worker2/Worker/Netsuite/CashRefund.php, worker2/Worker/Netsuite/JournalEntry.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Worker/Netsuite/SalesOrder.php, dbchanges2/Forecast/2026-06-26a - Add journalEntry to Sales transaction type enum.sql, test/@dave/test_invoice_lifecycle.php, test/@dave/test_je_lifecycle.php, test/@dave/test_creditmemo_lifecycle.php, test/@dave/test_cashsale_lifecycle.php, test/@dave/test_cashrefund_lifecycle.php, test/@dave/test_fetchrecord_routes.php, test/@dave/verify_je_classification.php, test/@dave/probe_je_accounts.php, test/@dave/probe_je_shape.php, test/@dave/fixer.php, test/@dave/Junk Drawer/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/Junk Drawer/NetSuite/api-message-queue/dev_ue_api_msg_queue_enqueue.js |
|
|
26
|
-
| [isFulfillable Propagation Up the SO↔PO Chain](features/fulfillable-item-propagation.md) | `Items.isFulfillable` is a boolean that gates whether a storefront line's **Qty Fulfilled** cell is actionable. | _underscore/Model/Client/Item.php, _underscore/Model/Compass/Item.php, dbchanges2/Core/2026-07-17 - Items isFulfillable RecordField.sql, dbchanges2/Core/2026-07-17 - RegisterItemIsFulfillableInterceptors.sql, dbchanges2/Client/2026-07-17 - ItemsisFulfillable.sql |
|
|
26
|
+
| [isFulfillable Propagation Up the SO↔PO Chain](features/fulfillable-item-propagation.md) | `Items.isFulfillable` is a boolean that gates whether a storefront line's **Qty Fulfilled** cell is actionable. | _underscore/Model/Client/Item.php, _underscore/Model/Compass/Item.php, toga2-supply/src/pages/Orders/view/OrderView/components/sections/OrderItemsTableSection.tsx, dbchanges2/Core/2026-07-17 - Items isFulfillable RecordField.sql, dbchanges2/Core/2026-07-17 - RegisterItemIsFulfillableInterceptors.sql, dbchanges2/Client/2026-07-17 - ItemsisFulfillable.sql |
|
|
27
27
|
| [Item-Fulfillment Stage Lifecycle (picked/packed/shipped) & Order Status](features/item-fulfillment-stage-lifecycle-and-order-status.md) | Every ItemFulfillment (IF) now carries an explicit **stage** — picked → packed → shipped — resolved through `ItemFulfillmentStages → ItemFulfillmentStatuses` (m | _underscore/Model/Client/SalesOrder.php, _underscore/Model/Quad/SalesOrder.php, _underscore/Model/Compass/SalesOrder.php, _underscore/Model/Compass/SalesOrderStatus.php, _underscore/Model/Client/SalesOrderItem.php, _underscore/Model/Client/Item.php, _underscore/Model/Client/PurchaseOrderItem.php, library/app/api/toga2.php, dbchanges2/Client/2026-06-30a - BackfillNullStageItemFulfillmentsToShipped.sql, dbchanges2/Client/2026-06-30b - SalesOrderStatusesPickedPacked.sql, dbchanges2/Client/2026-06-30c - ItemFulfillmentStageIdNotNull.sql, dbchanges2/Client_CompassCanada/2026-06-30a - ItemFulfillmentLifecycleAndShippedBackfill.sql |
|
|
28
28
|
| [DB-free unit testing for _underscore model interceptors](features/model-interceptor-unit-testing.md) | `_underscore` shipped with **no** PHPUnit setup (no `composer.json`/`phpunit`; only vendored PhpOffice tests existed). | _underscore/Test/bootstrap.php, _underscore/Test/Prudential/ServiceRequestTest.php, test/@Bala/tests/netsuite_salesorder_payload_tests.php |
|
|
29
29
|
| [_Model magic-field access (__get without __isset)](features/model-magic-field-access.md) | `_Model` exposes DB columns as "magic" properties via `__get()`, but it defines **no** `__isset()`. | _underscore/Model/Core/Model.php, _underscore/Model.php, _underscore/Model/Rate/Subscription.php |
|
|
@@ -6,11 +6,12 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-08-12
|
|
10
10
|
owners: [bala]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Client/Item.php
|
|
13
13
|
- _underscore/Model/Compass/Item.php
|
|
14
|
+
- toga2-supply/src/pages/Orders/view/OrderView/components/sections/OrderItemsTableSection.tsx
|
|
14
15
|
- dbchanges2/Core/2026-07-17 - Items isFulfillable RecordField.sql
|
|
15
16
|
- dbchanges2/Core/2026-07-17 - RegisterItemIsFulfillableInterceptors.sql
|
|
16
17
|
- dbchanges2/Client/2026-07-17 - ItemsisFulfillable.sql
|
|
@@ -18,6 +19,7 @@ related:
|
|
|
18
19
|
- recursive-item-fulfillments.md
|
|
19
20
|
- ../../../1.0/apps/library/features/netsuite-item-isfulfillable-sync.md
|
|
20
21
|
- ../../../clients/compass-usa/profile.md
|
|
22
|
+
- ../../../clients/compass-usa/features/isfulfillable-data-quality-and-type-rule.md
|
|
21
23
|
---
|
|
22
24
|
|
|
23
25
|
## Summary
|
|
@@ -78,6 +80,35 @@ same bridge topology, walked for a different payload (a scalar flag rather than
|
|
|
78
80
|
too** — the same integrity class as the Compass off-by-one bridge bugs (see
|
|
79
81
|
[MITS PO → SO Item Linking](../../../clients/compass-usa/features/mits-po-to-so-item-linking.md)).
|
|
80
82
|
|
|
83
|
+
## Reach limit — vendor-fulfilled items can NEVER be reached (structural, verified 2026-08-12)
|
|
84
|
+
The walk only reaches a client item whose PO has an **Agilant-tier SO behind it** (a
|
|
85
|
+
`PurchaseOrderItems_SalesOrderItems` row). **Vendor-direct POs have none**, so an item that is only
|
|
86
|
+
ever fulfilled by an outside vendor **can never receive `isFulfillable` from this pipeline** — no
|
|
87
|
+
amount of re-running the backfill changes that. It is a **structural gap, not a catch-up gap.**
|
|
88
|
+
|
|
89
|
+
Prod `Client_Compass` (2026-08-12): **zero** items with `isFulfillable IS NULL` reach the Agilant
|
|
90
|
+
tier, versus **52,990** line-level reaches for `isFulfillable = 1` items. Per vendor (Compass items
|
|
91
|
+
ordered / reaching the Agilant tier): OFFICE DEPOT **497/124**; STRATEGIC SYSTEMS **66/0**; FREEDOM
|
|
92
|
+
GROUPS LLC **61/0**; PRESIDIO **45/0**; COMPASS GROUP **35/0**; ENCOMPASS SUPPLY CHAIN **2/0**. Only
|
|
93
|
+
ODP partially routes through Agilant NetSuite. Full analysis + the remediation options:
|
|
94
|
+
[Compass isFulfillable data quality & type rule](../../../clients/compass-usa/features/isfulfillable-data-quality-and-type-rule.md).
|
|
95
|
+
|
|
96
|
+
## Where the flag is consumed (frontend gate)
|
|
97
|
+
`toga2-supply/src/pages/Orders/view/OrderView/components/sections/OrderItemsTableSection.tsx` —
|
|
98
|
+
`isQtyFulfilledClickable` (~lines 21–30) gates the **Qty Fulfilled** cell on:
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
String(item?.Items?.isFulfillable) === "1" && Number(item?._qtyFulfilled) !== 0
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
- **`NULL` and `0` behave identically here** — both make the cell dead.
|
|
105
|
+
- The gate also carries a **hardcoded Compass exception**
|
|
106
|
+
(`hostName === COMPASS && AssetTypes.name === 'FEE' → false`) that duplicates the proposed
|
|
107
|
+
type-derived data rule; it should be **deleted once that rule lands in the DB** so the logic lives
|
|
108
|
+
in one place.
|
|
109
|
+
- Because the gate *also* requires `_qtyFulfilled !== 0`, an item that has never been fulfilled is
|
|
110
|
+
unclickable regardless of the flag's value.
|
|
111
|
+
|
|
81
112
|
## Data model / schema (deploy)
|
|
82
113
|
Three `dbchanges2` migrations register the field + hooks; **all must be present in the target
|
|
83
114
|
env or the flag write is rejected:**
|
|
@@ -90,9 +121,16 @@ env or the flag write is rejected:**
|
|
|
90
121
|
|
|
91
122
|
## Conflict rule
|
|
92
123
|
Multi-source kits (an item reachable from more than one source) use **LAST-WRITER-WINS**. A
|
|
93
|
-
deterministic `MIN`/`MAX` aggregate was **deliberately deferred**
|
|
124
|
+
deterministic `MIN`/`MAX` aggregate was **deliberately deferred** on the assumption that values are
|
|
94
125
|
uniformly `1` (see the Phase-1 gotcha: NetSuite flags nearly everything fulfillable).
|
|
95
126
|
|
|
127
|
+
**⚠ That assumption is no longer safe (2026-08-12).** Prod `Client_Compass` holds **25 items with
|
|
128
|
+
`isFulfillable = 0`** — all bundle headers, **14 with real fulfillments** (worst case `B4NY9UC`:
|
|
129
|
+
11,062 order lines / 2,864 fulfillment rows, its Qty Fulfilled cell disabled). NetSuite marks
|
|
130
|
+
group/kit **headers** `F`, but in TOGa **the header is the line that ships**. Mixed 0/1 values are
|
|
131
|
+
therefore real, and last-writer-wins can land on the wrong one. See the
|
|
132
|
+
[Compass data-quality doc](../../../clients/compass-usa/features/isfulfillable-data-quality-and-type-rule.md).
|
|
133
|
+
|
|
96
134
|
## Client variations
|
|
97
135
|
- **Compass** (USA/Canada, DB `Client_Compass`) is the built/verified client. Its `_Model_Compass_Item`
|
|
98
136
|
overrides `postPost`/`postPut` for a price override, so it **must** `parent::` up to the base or
|
|
@@ -123,12 +161,35 @@ uniformly `1` (see the Phase-1 gotcha: NetSuite flags nearly everything fulfilla
|
|
|
123
161
|
**resolved/substituted at deploy** — it cannot join across clusters at runtime.
|
|
124
162
|
- **⚠ Compass subclass shadowing.** If a future client subclass overrides Items `postPost`/`postPut`
|
|
125
163
|
without calling `parent::`, propagation silently stops for that client.
|
|
126
|
-
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
164
|
+
- **⚠ Chain-propagation writes are INVISIBLE to the audit log (debugging gotcha).**
|
|
165
|
+
`propagateFulfillableAcrossChain` issues a **raw `UPDATE Items … WHERE id IN (…)` through `_Query`**
|
|
166
|
+
(`Item.php` ~lines 562–571), bypassing the model layer and therefore the `Logs_<client>` audit
|
|
167
|
+
trail. A **sync PUT is logged; a chain propagation is silent** — so "no log row" does **not** mean
|
|
168
|
+
"never written". Use that as the discriminator between the two write paths: Compass item **2383**
|
|
169
|
+
(Agilant source 2720, NetSuite id 1536664) has **no** log row while 2382/2384/2385 do, which alone
|
|
170
|
+
identifies which path set each value.
|
|
171
|
+
**Query anchor:** prod `Core.RecordFields` **id 2434 = `Items.isFulfillable`** (`recordId 21`); join
|
|
172
|
+
`Logs_<client>.Record` (`recordId = 21`, `primaryKeyId = <itemId>`) to `Logs_<client>.RecordField`
|
|
173
|
+
`ON logRecordId` with `recordFieldId = 2434`. Same technique applies to any
|
|
174
|
+
interceptor-vs-propagation debugging.
|
|
175
|
+
- **Product decision — analyzed 2026-08-12, decision taken for Compass (was "unresolved").** Because
|
|
176
|
+
NetSuite's `isfulfillable` is `T` for services as well as physical goods (see Phase-1 doc),
|
|
177
|
+
propagation makes **everything** clickable. A prod evidence review showed item **type cannot
|
|
178
|
+
confirm** fulfillability (0%-tracked types like DESKTOPS are physical) but the **tracking
|
|
179
|
+
ride-along test can disqualify** fee/software/miscellaneous lines. Compass has since adopted a
|
|
180
|
+
**type-derived override** of NetSuite's flag, which **must be enforced at write time** because the
|
|
181
|
+
Phase-1 daily refresh reverts it. Rule, projected impact, caveats:
|
|
182
|
+
[Compass isFulfillable data quality & type rule](../../../clients/compass-usa/features/isfulfillable-data-quality-and-type-rule.md).
|
|
130
183
|
|
|
131
184
|
## Change history
|
|
185
|
+
- 2026-08-12 — Prod investigation (read-only, no code change): recorded the engine's **structural
|
|
186
|
+
reach limit** (vendor-direct POs have no Agilant-tier SO, so those items can never receive the
|
|
187
|
+
flag — 0 of the NULL Compass items reach the Agilant tier vs 52,990 reaches for `=1` items),
|
|
188
|
+
documented the **toga2-supply frontend gate** that consumes the flag (plus its hardcoded Compass
|
|
189
|
+
`FEE` exception to retire), added the **chain-propagation writes are invisible to the audit log**
|
|
190
|
+
gotcha with the `Core.RecordFields 2434` / `Logs_<client>` query anchor, and retired the
|
|
191
|
+
"values are uniformly 1" assumption behind the deferred conflict rule (25 Compass zeros, 14 with
|
|
192
|
+
real fulfillments). (bala)
|
|
132
193
|
- 2026-07-28 — Fixed a **read-replica routing** bug: `propagateFulfillableAcrossChain`'s source-item
|
|
133
194
|
read and the recursive chain-walk CTE now `setisReadHostEnabled(false)` so they run on the writer
|
|
134
195
|
and see the request's own open write transaction (read-your-writes) — a replica read could return
|
|
@@ -151,3 +212,5 @@ uniformly `1` (see the Phase-1 gotcha: NetSuite flags nearly everything fulfilla
|
|
|
151
212
|
bridge topology.
|
|
152
213
|
- [Compass MITS PO → SO Item Linking](../../../clients/compass-usa/features/mits-po-to-so-item-linking.md)
|
|
153
214
|
— how the SOI↔POI bridges get built (and the integrity bugs that break this walk).
|
|
215
|
+
- [Compass isFulfillable — Data Quality & the Type-Derived Rule](../../../clients/compass-usa/features/isfulfillable-data-quality-and-type-rule.md)
|
|
216
|
+
— measured prod state, why the NULLs are unreachable, and the Compass override.
|
package/knowledge/INDEX.md
CHANGED
|
@@ -18,7 +18,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
18
18
|
|
|
19
19
|
## 2.0 framework
|
|
20
20
|
|
|
21
|
-
- **_underscore** (_Underscore) _(framework core)_ —
|
|
21
|
+
- **_underscore** (_Underscore) _(framework core)_ — 55 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
22
22
|
- **worker2** (Worker) — 48 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
23
23
|
- **api2** (API) — 22 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
24
24
|
- **dbchanges2** (Database Changes) _(framework core)_ — 8 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
@@ -15,12 +15,13 @@ project: _Underscore
|
|
|
15
15
|
client: compass-canada
|
|
16
16
|
type: profile
|
|
17
17
|
status: active
|
|
18
|
-
updated: 2026-08-
|
|
18
|
+
updated: 2026-08-12
|
|
19
19
|
owners: [jcardinal, bala, tcox, apeterson]
|
|
20
20
|
files: []
|
|
21
21
|
related:
|
|
22
22
|
- ../compass-usa/profile.md
|
|
23
23
|
- ../compass-usa/features/approval-decision-flow.md
|
|
24
|
+
- ../compass-usa/features/isfulfillable-data-quality-and-type-rule.md
|
|
24
25
|
- features/french-order-email-localization.md
|
|
25
26
|
- ../../2.0/apps/toga2-commerce/features/expedited-shipping-gating.md
|
|
26
27
|
- ../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md
|
|
@@ -104,6 +105,17 @@ to but distinct from Compass USA. Like Compass USA it spans the **2.0** commerce
|
|
|
104
105
|
impact: 34 personas, 7,017 users with personas, 1,461 with more than one. Fix `5313a9b1` exists on
|
|
105
106
|
`TRUE-80672`/`_beta` but is **not in `_production`**. See
|
|
106
107
|
[Client Fields → Login-settings branches](../../2.0/apps/toga2-commerce/features/client-fields.md).
|
|
108
|
+
- **⚠ `Items.isFulfillable` is 100% NULL in Canada — the Qty Fulfilled cell is dead storefront-wide.**
|
|
109
|
+
All **220** `Client_CompassCanada` items are NULL (prod, 2026-08-12): Canada was **never** included
|
|
110
|
+
in the multi-client backfill and has never been stamped at all. Note also that Canada uses a
|
|
111
|
+
**different type vocabulary** than Compass USA — title-case `assetTypes` (`Equipment`, `Hardware`,
|
|
112
|
+
`Services`, `Fee`, `Consulting`) and its own `itemTypes` (`LAPTOP`, `MACBOOK`, `MONITORS`,
|
|
113
|
+
`CABLES`, `SERVICE`, `Services`, `CONSULTING`, `SOFTWARE LICENSING`), with **no `FEE` or
|
|
114
|
+
`SERVICEFEES` itemType at all**. The US-derived rule list still matches, because the columns are
|
|
115
|
+
`utf8mb4_0900_ai_ci` (case-insensitive) — **do not split it into two hand-maintained lists**, but
|
|
116
|
+
do re-confirm the Canada vocabulary separately. Projected impact of the proposed rule here: 13 → 0,
|
|
117
|
+
207 → 1. See
|
|
118
|
+
[isFulfillable — Data Quality & the Type-Derived Rule](../compass-usa/features/isfulfillable-data-quality-and-type-rule.md).
|
|
107
119
|
- The 2026-06-08 ASN → ItemFulfillment work was for **Compass USA**, not Compass Canada.
|
|
108
120
|
- **Order status is shipped-only**, same as Compass USA (shared `_Model_Compass_SalesOrder`).
|
|
109
121
|
Compass Canada's own IF lifecycle stages (picked/packed/shipped) + shipped backfill are seeded by
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
| [Compass Approval-Decision Flow (Notifications & Manager Reassignment)](features/approval-decision-flow.md) | 2.0 | Compass's sales-order approval flow — approval/notification email lists, **manager reassignment**, VIP auto-approve, and EN/FR localization — lives **entirely i | _underscore/Model/Compass/ApprovalDecision.php, _underscore/Model/Compass/SalesOrder.php, _underscore/Model/Compass/Usa/ApprovalDecision.php, _underscore/Model/Compass/Canada/ApprovalDecision.php |
|
|
6
6
|
| [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, _underscore/Model/Compass/PurchaseOrder.php, api2/Component/Api/Cxml/Cxml.php, dbchanges2/Client_Compass/2026-06-11 - AsnItemTrackingNumberAcl.sql, dbchanges2/Client_Compass/2026-06-15b - BackfillSA132781ItemFulfillmentTracking.sql, dbchanges2/Client_Compass/2026-06-16 - CleanupSA132763CrossLineTracking.sql, dbchanges2/Client_Compass/2026-06-16b - CleanupSA132743CrossLineTracking.sql, dbchanges2/Client_Compass/2026-06-16c - BackfillSA132763C40QYUCTracking.sql, dbchanges2/Client_Compass/2026-06-18a - CleanupSA132898DuplicateTracking.sql, dbchanges2/Client_Compass/2026-06-18b - CleanupSA132881DuplicateTracking.sql, dbchanges2/Client_Compass/2026-07-02a - FixSA133377TrackingSerialAndDuplicateIF.sql |
|
|
7
7
|
| [Cost Centers — Unit Locations, numeric-only policy](features/cost-centers.md) | 2.0 | A Compass "cost center" — the value a user picks in commerce and that lands on an order — is **not** a `CostCenters` row. | toga2-commerce/src/pages/Cart/api/CartApi.ts, worker1.5/crons/toga2/compass/import_locations.php, _underscore/Model/Compass/SalesOrder.php, api2/Component/Api/V2/V2.php, dbchanges2/Client_Compass/2026-07-06 - RemoveNonNumericCostCenters.sql |
|
|
8
|
+
| [Compass isFulfillable — Data Quality & the Type-Derived Rule](features/isfulfillable-data-quality-and-type-rule.md) | 2.0 | A **prod-data investigation (2026-08-12, read-only)** into why so many Compass storefront lines still have a dead **Qty Fulfilled** cell. | _underscore/Model/Client/Item.php, library/app/api/toga2.php, worker/crons/toga2/netsuite/backfill_isfulfillable_all_clients.php, toga2-supply/src/pages/Orders/view/OrderView/components/sections/OrderItemsTableSection.tsx |
|
|
8
9
|
| [Compass: Item-Fulfillment TableViews (for-sales-order-items & for-sales-orders, tracking via bridge)](features/item-fulfillment-tracking-tableview.md) | 2.0 | Two sibling Compass TableViews in `Client_Compass` display fulfilled items in toga2-supply, both driven by `TableViews` / `TableViewJoins` / `TableViewFields` c | dbchanges2/Client_Compass/2026-06-10 - ItemFulfillmentsForSalesOrderItemsTableView.sql, dbchanges2/Client_Compass/2026-06-11 - ItemFulfillmentsForSalesOrdersTableView.sql, dbchanges2/Client_Compass/2026-06-15a - FixItemFulfillmentTrackingNumberJoins.sql, dbchanges2/Client/2026-07-15a - ExcludeFeeItemsFromItemFulfillmentsForSalesOrdersView.sql |
|
|
9
10
|
| [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 |
|
|
10
11
|
| [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, worker/crons/toga2/compasscanada/workflow/2_transmit_mits_purchase_orders_to_vendors.php, library/app/client/compass.php |
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Compass isFulfillable — Data Quality & the Type-Derived Rule
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: _underscore
|
|
5
|
+
project: _Underscore
|
|
6
|
+
client: compass-usa
|
|
7
|
+
type: client-feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-08-12
|
|
10
|
+
owners: ["bala"]
|
|
11
|
+
files:
|
|
12
|
+
- _underscore/Model/Client/Item.php
|
|
13
|
+
- library/app/api/toga2.php
|
|
14
|
+
- worker/crons/toga2/netsuite/backfill_isfulfillable_all_clients.php
|
|
15
|
+
- toga2-supply/src/pages/Orders/view/OrderView/components/sections/OrderItemsTableSection.tsx
|
|
16
|
+
related:
|
|
17
|
+
- ../profile.md
|
|
18
|
+
- ../../compass-canada/profile.md
|
|
19
|
+
- ../../../2.0/apps/_underscore/features/fulfillable-item-propagation.md
|
|
20
|
+
- ../../../1.0/apps/library/features/netsuite-item-isfulfillable-sync.md
|
|
21
|
+
- ../../../1.0/apps/worker/workflows/isfulfillable-multi-client-backfill.md
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Summary
|
|
25
|
+
A **prod-data investigation (2026-08-12, read-only)** into why so many Compass storefront lines still
|
|
26
|
+
have a dead **Qty Fulfilled** cell. Conclusion in one line: the remaining `Items.isFulfillable`
|
|
27
|
+
NULLs are **not a backfill backlog — they are structurally unreachable**, and the values that *are*
|
|
28
|
+
set are partly wrong. This doc records the measured state of `Client_Compass` /
|
|
29
|
+
`Client_CompassCanada`, the evidence for (and against) deriving the flag from item type, the
|
|
30
|
+
**decision** to override NetSuite with a type-derived rule for Compass, and the **write-time guard**
|
|
31
|
+
that decision requires in order to survive the daily NetSuite sync.
|
|
32
|
+
|
|
33
|
+
No source files were changed and **no migration has been written yet**. All figures below were read
|
|
34
|
+
from **production** on **2026-08-12**.
|
|
35
|
+
|
|
36
|
+
## Measured state (prod, 2026-08-12)
|
|
37
|
+
- **`Client_Compass`, catalog 1:** **770 NULL / 25 zero / 697 one.**
|
|
38
|
+
- **12,318 Compass order lines have real `ItemFulfillmentItems` but a NULL flag** — the Qty
|
|
39
|
+
Fulfilled cell is dead on all of them. A further **4,873** lines sit behind `isFulfillable = 0`.
|
|
40
|
+
- **`Client_CompassCanada`: 220 items, 100% NULL.** Canada has **never** been stamped at all, so the
|
|
41
|
+
Qty Fulfilled cell is dead across the **entire** Canada storefront.
|
|
42
|
+
|
|
43
|
+
## Why the NULLs cannot be backfilled (structural, not a catch-up gap)
|
|
44
|
+
The flag is born on an **Agilant-catalog source item** during the 1.0 NetSuite item sync
|
|
45
|
+
([Phase 1](../../../1.0/apps/library/features/netsuite-item-isfulfillable-sync.md)) and then
|
|
46
|
+
propagates **up** the `SalesOrderItems`↔`PurchaseOrderItems` chain
|
|
47
|
+
([Phase 2](../../../2.0/apps/_underscore/features/fulfillable-item-propagation.md)). That walk needs
|
|
48
|
+
the Compass PO to have an **Agilant-tier SO behind it** (a `PurchaseOrderItems_SalesOrderItems`
|
|
49
|
+
row). **Vendor-direct POs have none**, so a vendor-fulfilled item can never receive the flag from
|
|
50
|
+
the current pipeline.
|
|
51
|
+
|
|
52
|
+
Verified in prod `Client_Compass`: **zero** items with `isFulfillable IS NULL` reach the Agilant
|
|
53
|
+
tier, against **52,990** line-level reaches for `isFulfillable = 1` items.
|
|
54
|
+
|
|
55
|
+
Compass items ordered / reaching the Agilant tier, per vendor:
|
|
56
|
+
|
|
57
|
+
| Vendor | Ordered | Reaching Agilant tier |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| OFFICE DEPOT | 497 | 124 |
|
|
60
|
+
| STRATEGIC SYSTEMS | 66 | 0 |
|
|
61
|
+
| FREEDOM GROUPS LLC | 61 | 0 |
|
|
62
|
+
| PRESIDIO | 45 | 0 |
|
|
63
|
+
| COMPASS GROUP | 35 | 0 |
|
|
64
|
+
| ENCOMPASS SUPPLY CHAIN | 2 | 0 |
|
|
65
|
+
|
|
66
|
+
**Consequence: re-running the multi-client backfill cannot move a single one of these.** Only ODP
|
|
67
|
+
partially routes through Agilant NetSuite.
|
|
68
|
+
|
|
69
|
+
## The 25 `isFulfillable = 0` Compass items are WRONG — they should be 1
|
|
70
|
+
All 25 are **bundle items**; **14 of them have real fulfillments**. Worst case **`B4NY9UC`**:
|
|
71
|
+
**11,062 order lines and 2,864 fulfillment rows** — the most-shipped laptop in the catalog, with its
|
|
72
|
+
Qty Fulfilled cell **disabled**. NetSuite marks group/kit **headers** `F`, but in TOGa **the header
|
|
73
|
+
IS the line that ships**. This supersedes the earlier assumption (in the Phase 1/Phase 2 docs) that
|
|
74
|
+
the zeros were harmless because values were "uniformly 1".
|
|
75
|
+
|
|
76
|
+
## Can item type reproduce NetSuite's flag? (evidence)
|
|
77
|
+
**The naive test fails.** "Does this type ever fulfil?" does not separate anything:
|
|
78
|
+
`SERVICES` 119/120 known-fulfillable, `SOFTWARE` 7/7, `FEE` 3/3, while `HARDWARE` fulfils on only
|
|
79
|
+
**26.9%** of lines. `inventoryType = HYBRID` is the **column default** = noise.
|
|
80
|
+
`EntitlementFulfillmentTypes` / `EntitlementFulfillmentMethods` are **EMPTY** in prod.
|
|
81
|
+
|
|
82
|
+
**The decisive test is the tracking "ride-along":** does a fulfillment row carry **its own** tracking
|
|
83
|
+
number, or does it share one with another line on the same shipment?
|
|
84
|
+
|
|
85
|
+
| Type | Fulfillment rows | With tracking | Ride-along (shares tracking) |
|
|
86
|
+
|---|---|---|---|
|
|
87
|
+
| itemType `FEE` | 9,639 | 1 (0.0%) | — (0 units) |
|
|
88
|
+
| assetType `FEE` | 10,071 | 22 | **22/22 = 100%** |
|
|
89
|
+
| itemType `SOFTWARE` | — | 129 | **129/129 = 100%** |
|
|
90
|
+
| assetType `MISCELLANEOUS` | — | 385 | **385/385 = 100%** |
|
|
91
|
+
| itemType `SERVICES` | — | 687 | 274 = **39.9%** (413 rows carry their OWN tracking) |
|
|
92
|
+
|
|
93
|
+
A 100% ride-along rate means the type **never has its own shipment** — a fee/licence line billed
|
|
94
|
+
alongside hardware. `SERVICES` fails that test: 413 service rows genuinely ship on their own.
|
|
95
|
+
|
|
96
|
+
**⚠ Absence of tracking does NOT prove non-fulfillable.** `DESKTOPS`, `CANTEEN TECHNOLOGY` and
|
|
97
|
+
`VENDING AND OCS ORDERING` all show **0% tracked** yet are physical goods. **Type can only
|
|
98
|
+
DISQUALIFY an item, never confirm one.**
|
|
99
|
+
|
|
100
|
+
## Decision — the type-derived rule for Compass (2026-08-12)
|
|
101
|
+
Developer's call; it **overrides NetSuite's value**:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
isFulfillable = 0 WHERE itemType OR assetType IN
|
|
105
|
+
('FEE','SERVICEFEES','SERVICES','SERVICE','CONSULTING')
|
|
106
|
+
isFulfillable = 1 otherwise
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Projected impact:
|
|
110
|
+
- **`Client_Compass`:** **186 → 0** (54 currently NULL + 131 currently **1** + 1 currently 0) and
|
|
111
|
+
**606 → 1**.
|
|
112
|
+
- **`Client_CompassCanada`:** **13 → 0**, **207 → 1**.
|
|
113
|
+
|
|
114
|
+
**⚠ Recorded caveat — the SERVICES/CONSULTING half is NOT supported by the evidence.** 413
|
|
115
|
+
`SERVICES` fulfillment rows carry their own tracking, and the rule flips **131 currently-clickable
|
|
116
|
+
items off**. That half **needs product sign-off**. `SOFTWARE` (100% ride-along) is the *stronger*
|
|
117
|
+
candidate on the evidence but was deliberately **left out of scope**.
|
|
118
|
+
|
|
119
|
+
## ⚠ CRITICAL — a backfill alone will be undone by the daily NetSuite sync
|
|
120
|
+
Phase 1's `getCreateItem` **refreshes `isFulfillable` on EXISTING items** whenever NetSuite's value
|
|
121
|
+
differs from the stored value. NetSuite returns **`T` for service (`NonInvtPart`) items**, so any
|
|
122
|
+
type-derived **0** is **flipped back to 1 on the next sync run**.
|
|
123
|
+
|
|
124
|
+
Proven from the audit log: Compass items **2382 / 2384 / 2385**
|
|
125
|
+
(`US-IOS-SSC-SHR`, `US-IOS-VENUENEXT`, `US-IOS-VIRTUALMGR` — itemType `SERVICES`, assetType
|
|
126
|
+
`CONSULTING`) were stamped **NULL → 1 on 2026-08-04 13:56:00-01** by `apiId 1`, `userId NULL`,
|
|
127
|
+
inside a **1–2 items/second cron sweep** that began 13:45 and set every item to 1.
|
|
128
|
+
|
|
129
|
+
**Therefore the rule MUST be enforced at write time** — a `prePut`/`prePost` guard on Items in
|
|
130
|
+
`_Model_Client_Item` — **in addition to** the backfill migration. A migration on its own silently
|
|
131
|
+
reverts within a day.
|
|
132
|
+
|
|
133
|
+
## Evidence-based alternative signals (if type is not wanted)
|
|
134
|
+
- **`ItemFulfillmentItems`** (via `SalesOrderItems.salesOrderItemId`) = **proven shipped**.
|
|
135
|
+
- **`VendorItems`** (`isActive = 1`) = **procurable from a vendor**.
|
|
136
|
+
|
|
137
|
+
Tiering the 770 NULL Compass items: **184** have fulfillment history · **286** have an active
|
|
138
|
+
`VendorItems` row · **300** have neither. The frontend gate already requires
|
|
139
|
+
`_qtyFulfilled !== 0`, so **tier-3 items cannot be clicked regardless of the flag**, and can be left
|
|
140
|
+
NULL.
|
|
141
|
+
|
|
142
|
+
## Where the storefront consumes the flag (+ a hardcode to retire)
|
|
143
|
+
`toga2-supply/src/pages/Orders/view/OrderView/components/sections/OrderItemsTableSection.tsx`,
|
|
144
|
+
`isQtyFulfilledClickable` (~lines 21–30):
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
String(item?.Items?.isFulfillable) === "1" && Number(item?._qtyFulfilled) !== 0
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
plus an extra **hardcoded** `hostName === COMPASS && AssetTypes.name === 'FEE' → false`.
|
|
151
|
+
|
|
152
|
+
- **NULL and 0 behave identically** at this gate.
|
|
153
|
+
- That **FEE hardcode duplicates the proposed data rule** — once the rule lands in the DB it should
|
|
154
|
+
be **deleted** so the logic lives in one place.
|
|
155
|
+
|
|
156
|
+
## Compass Canada uses a DIFFERENT type vocabulary
|
|
157
|
+
Canada's `assetTypes` are **title case** (`Equipment`, `Hardware`, `Services`, `Fee`, `Consulting`)
|
|
158
|
+
and its `itemTypes` are a **separate vocabulary** (`LAPTOP`, `MACBOOK`, `MONITORS`, `CABLES`,
|
|
159
|
+
`SERVICE`, `Services`, `CONSULTING`, `SOFTWARE LICENSING`) — it has **no `FEE` or `SERVICEFEES`
|
|
160
|
+
itemType at all**.
|
|
161
|
+
|
|
162
|
+
Because these tables are `utf8mb4_0900_ai_ci` (**case-insensitive**), the single US-derived
|
|
163
|
+
`IN ('FEE','SERVICEFEES',…)` list **matches Canada's title-case values** — verified against live
|
|
164
|
+
data. **Do not hand-maintain two string lists**, but **do** re-confirm the Canada vocabulary
|
|
165
|
+
separately, since the names genuinely differ and a US list *looks* wrong for Canada.
|
|
166
|
+
|
|
167
|
+
## Change history
|
|
168
|
+
- 2026-08-12 — Prod read-only investigation of `Items.isFulfillable` across `Client_Compass` /
|
|
169
|
+
`Client_CompassCanada`. Established that remaining NULLs are **structurally unreachable**
|
|
170
|
+
(vendor-direct POs have no Agilant-tier SO, so the chain walk has nothing to walk — 0 NULL items
|
|
171
|
+
reach the Agilant tier vs 52,990 reaches for `=1` items), that the **25 zeros are wrong** (all
|
|
172
|
+
bundle headers, 14 with real fulfillments, incl. `B4NY9UC` at 11,062 lines / 2,864 fulfillments),
|
|
173
|
+
and that **Canada is 100% NULL**. Proved item type cannot *confirm* fulfillability but the
|
|
174
|
+
**tracking ride-along test** cleanly disqualifies fee/software/miscellaneous lines (100%
|
|
175
|
+
ride-along) while `SERVICES` fails it (39.9%). **Decided** the type-derived rule
|
|
176
|
+
(`FEE/SERVICEFEES/SERVICES/SERVICE/CONSULTING → 0`, else 1) with the SERVICES/CONSULTING half
|
|
177
|
+
flagged for product sign-off, and established that it **must** be enforced by a `prePut`/`prePost`
|
|
178
|
+
guard because the daily sync's existing-item refresh reverts it (audit-log proof: items
|
|
179
|
+
2382/2384/2385 stamped NULL→1 on 2026-08-04). (bala)
|
|
180
|
+
|
|
181
|
+
## Related docs
|
|
182
|
+
- [Phase 1 — isFulfillable from NetSuite during item sync (1.0)](../../../1.0/apps/library/features/netsuite-item-isfulfillable-sync.md)
|
|
183
|
+
— where the value is born and the refresh that reverts overrides.
|
|
184
|
+
- [Phase 2 — isFulfillable propagation up the SO↔PO chain (2.0)](../../../2.0/apps/_underscore/features/fulfillable-item-propagation.md)
|
|
185
|
+
— the chain walk whose reach is the structural limit described here.
|
|
186
|
+
- [isFulfillable multi-client backfill (worker)](../../../1.0/apps/worker/workflows/isfulfillable-multi-client-backfill.md)
|
|
187
|
+
— the cron that **cannot** close this gap.
|
|
@@ -17,7 +17,7 @@ project: _Underscore
|
|
|
17
17
|
client: compass-usa
|
|
18
18
|
type: profile
|
|
19
19
|
status: active
|
|
20
|
-
updated: 2026-08-
|
|
20
|
+
updated: 2026-08-12
|
|
21
21
|
owners: [jcardinal, bala, tcox, apeterson, dfranks]
|
|
22
22
|
files: []
|
|
23
23
|
related:
|
|
@@ -27,6 +27,7 @@ related:
|
|
|
27
27
|
- features/mits-sales-order-transmission-alerting.md
|
|
28
28
|
- features/asn-to-item-fulfillment.md
|
|
29
29
|
- features/cost-centers.md
|
|
30
|
+
- features/isfulfillable-data-quality-and-type-rule.md
|
|
30
31
|
- features/approval-decision-flow.md
|
|
31
32
|
- workflows/cross-kit-bundle-corruption.md
|
|
32
33
|
- workflows/odp-duplicate-po-line-cleanup.md
|
|
@@ -134,4 +135,12 @@ separate, related client (see its own profile).
|
|
|
134
135
|
up the SO↔PO chain to the client-facing Compass item by an api2 interceptor
|
|
135
136
|
([Phase 2](../../2.0/apps/_underscore/features/fulfillable-item-propagation.md)). Verified on prod
|
|
136
137
|
Compass chains; a one-time July-5+ backfill re-PUTs source items through the same path.
|
|
138
|
+
**⚠ Prod audit 2026-08-12 — the flag is largely broken for Compass and NO backfill can fix it.**
|
|
139
|
+
770 NULL / 25 zero / 697 one; **12,318** order lines are really fulfilled but flagged NULL, so
|
|
140
|
+
their Qty Fulfilled cell is dead. The NULLs are **structurally unreachable** (vendor-direct POs
|
|
141
|
+
have no Agilant-tier SO to walk up from), and the 25 zeros are **wrong** (bundle headers that do
|
|
142
|
+
ship — incl. `B4NY9UC`, 11,062 lines). A **type-derived override** was decided and **must** be
|
|
143
|
+
enforced by a write-time guard, because the daily NetSuite sync reverts it. Evidence, rule,
|
|
144
|
+
projected impact and caveats:
|
|
145
|
+
[isFulfillable — Data Quality & the Type-Derived Rule](features/isfulfillable-data-quality-and-type-rule.md).
|
|
137
146
|
- This profile is a starting point; expand as more Compass-specific behavior is captured.
|
package/package.json
CHANGED