toga-ai 1.0.255 → 1.0.256
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/features/netsuite-togasupply-per-client-sync.md +7 -2
- package/knowledge/2.0/apps/_underscore/INDEX.md +1 -0
- package/knowledge/2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md +174 -0
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/compass-canada/profile.md +5 -0
- package/knowledge/clients/compass-usa/features/asn-to-item-fulfillment.md +12 -2
- package/knowledge/clients/compass-usa/profile.md +6 -0
- package/knowledge/clients/compass-usa/workflows/order-lifecycle-and-data-integrity.md +12 -4
- package/knowledge/clients/quad/profile.md +6 -0
- package/package.json +1 -1
|
@@ -6,8 +6,8 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06-
|
|
10
|
-
owners: ["dfranks"]
|
|
9
|
+
updated: 2026-06-30
|
|
10
|
+
owners: ["dfranks", "bala"]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/netsuite/common_sync_togasupply.php
|
|
13
13
|
- worker/crons/toga2/netsuite/sync_togasupply_canon.php
|
|
@@ -17,6 +17,7 @@ related:
|
|
|
17
17
|
- ../architecture.md
|
|
18
18
|
- forecast2-netsuite-reconciliation.md
|
|
19
19
|
- ../../library/features/toga2-api-client-and-bridge.md
|
|
20
|
+
- ../../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md
|
|
20
21
|
---
|
|
21
22
|
|
|
22
23
|
## Summary
|
|
@@ -150,6 +151,10 @@ Parameters are stored **per client DB** but accessed **through the TOGa2 API**,
|
|
|
150
151
|
|
|
151
152
|
## Change history
|
|
152
153
|
|
|
154
|
+
- 2026-06-30 — The IF section's `syncItemFulfillmentFromNetsuite` (`library/app/api/toga2.php`) now
|
|
155
|
+
imports **all** fulfillment statuses (`['_picked','_packed','_shipped']`, not just shipped),
|
|
156
|
+
stamping/advancing the IF stage from `c_netsuiteInternalItemFulfillmentStatus`. Full mechanics
|
|
157
|
+
live in the [IF stage lifecycle doc](../../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md). (bala)
|
|
153
158
|
- 2026-06-17 — Documented the full onboarding prerequisites (client→NetSuite customer link +
|
|
154
159
|
per-client `c_netsuite*` custom fields) after AIG ran but imported nothing. Added AIG + Growrk
|
|
155
160
|
thin wrappers, schedule entries, and dbchanges2 Parameters seeds; built AIG's
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
| [Client Email Template Sending](features/email-template-sending.md) | `_Model_Client_EmailTemplate` sends a stored, client-defined email template by UUID. | _underscore/Model/Client/EmailTemplate.php, _underscore/Model/Client/EmailTemplateOutgoingEmailAddress.php, _underscore/Email.php |
|
|
10
10
|
| [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 |
|
|
11
11
|
| [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). | _underscore/Component/Forecast/SaleImport/SaleImport.php, _underscore/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/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/NetSuite/api-message-queue/dev_ue_api_msg_queue_enqueue.js |
|
|
12
|
+
| [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 |
|
|
12
13
|
| [_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 |
|
|
13
14
|
| [NetSuite REST Client (_Component_Api_Netsuite) — record writes & SuiteQL](features/netsuite-rest-client.md) | `_Component_Api_Netsuite` is the **2.0 `_underscore` NetSuite REST client** — the shared primitive every worker2/api2 NetSuite caller uses for record GETs, Suit | _underscore/Component/Api/Netsuite/Netsuite.php |
|
|
14
15
|
| [Per-Client Database Connections & the Local Logs Trap](features/per-client-database-connections.md) | When `_underscore` serves a request for a client it opens **three distinct per-client database connections**, not one. | _underscore/Database.php, _underscore/ApiRequest.php, _underscore/Model/Client/Logs/Api.php |
|
package/knowledge/2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Item-Fulfillment Stage Lifecycle (picked/packed/shipped) & Order Status
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: _underscore
|
|
5
|
+
project: _Underscore
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-30
|
|
10
|
+
owners: [bala]
|
|
11
|
+
files:
|
|
12
|
+
- _underscore/Model/Client/SalesOrder.php
|
|
13
|
+
- _underscore/Model/Quad/SalesOrder.php
|
|
14
|
+
- _underscore/Model/Compass/SalesOrder.php
|
|
15
|
+
- _underscore/Model/Compass/SalesOrderStatus.php
|
|
16
|
+
- _underscore/Model/Client/SalesOrderItem.php
|
|
17
|
+
- _underscore/Model/Client/Item.php
|
|
18
|
+
- _underscore/Model/Client/PurchaseOrderItem.php
|
|
19
|
+
- library/app/api/toga2.php
|
|
20
|
+
- dbchanges2/Client/2026-06-30a - BackfillNullStageItemFulfillmentsToShipped.sql
|
|
21
|
+
- dbchanges2/Client/2026-06-30b - SalesOrderStatusesPickedPacked.sql
|
|
22
|
+
- dbchanges2/Client/2026-06-30c - ItemFulfillmentStageIdNotNull.sql
|
|
23
|
+
- dbchanges2/Client_CompassCanada/2026-06-30a - ItemFulfillmentLifecycleAndShippedBackfill.sql
|
|
24
|
+
related:
|
|
25
|
+
- recursive-item-fulfillments.md
|
|
26
|
+
- carrier-shipping-labels.md
|
|
27
|
+
- ../../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md
|
|
28
|
+
- ../../../clients/compass-usa/workflows/order-lifecycle-and-data-integrity.md
|
|
29
|
+
- ../../../clients/compass-usa/features/asn-to-item-fulfillment.md
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Summary
|
|
33
|
+
|
|
34
|
+
Every ItemFulfillment (IF) now carries an explicit **stage** — picked → packed → shipped —
|
|
35
|
+
resolved through `ItemFulfillmentStages → ItemFulfillmentStatuses` (matched by status `slug`).
|
|
36
|
+
This makes the *picked/packed* portion of the fulfillment lifecycle visible (previously only
|
|
37
|
+
*shipped* IFs were imported, so picked/packed were invisible) and drives a calculated order
|
|
38
|
+
`_status` state machine. Two deliberately different status policies exist:
|
|
39
|
+
|
|
40
|
+
- **Base + Quad:** order status walks the full machine — Pending → Picked → Packed →
|
|
41
|
+
Partially Fulfilled → Fulfilled — so non-Compass clients see the picked/packed lifecycle.
|
|
42
|
+
- **Compass USA + Compass Canada:** all IF stages are *imported*, but order status counts
|
|
43
|
+
**shipped only** — picked/packed never advance a Compass order's status. Compass walks
|
|
44
|
+
Pending → Partially Fulfilled → Fulfilled by shipped quantity alone.
|
|
45
|
+
|
|
46
|
+
Independently of order status, **inventory movement (`qtyFulfilled` / `qtyCommitted`) is
|
|
47
|
+
shipped-only for ALL clients** — picked/packed quantities never count as inventory movement.
|
|
48
|
+
|
|
49
|
+
## Key files / entry points
|
|
50
|
+
|
|
51
|
+
- **`Model/Client/SalesOrder.php`** — base `_status` calculated field. With `salesOrderStageId`
|
|
52
|
+
NULL and a non-SA order: 0 fulfillments → Pending Fulfillment; nothing shipped → Picked (if
|
|
53
|
+
any line picked) else Packed; something shipped → Partially Fulfilled (shipped qty < ordered)
|
|
54
|
+
else Fulfilled. An explicit `salesOrderStageId` overrides everything.
|
|
55
|
+
- **`Model/Quad/SalesOrder.php`** — Quad `_status`. `SA%` orders delegate to the parent; non-SA
|
|
56
|
+
orders use the inline picked/packed/partial/fulfilled machine.
|
|
57
|
+
- **`Model/Compass/SalesOrder.php`** — Compass `_status`, shipped-only: both the existence
|
|
58
|
+
`COUNT` and the shipped-qty `SUM` subqueries `INNER JOIN ItemFulfillmentStages →
|
|
59
|
+
ItemFulfillmentStatuses` filtered to the shipped status slug. Shared by Compass USA + Canada.
|
|
60
|
+
- **`Model/Compass/SalesOrderStatus.php`** — adds `const SLUG__PICKED = 'picked'` and
|
|
61
|
+
`SLUG__PACKED = 'packed'`.
|
|
62
|
+
- **`Model/Client/{SalesOrderItem,Item,PurchaseOrderItem}.php`** — the `_qtyFulfilled` /
|
|
63
|
+
`_qtyCommitted` calculated SQL fields (5 sites) gate the IF-item sum to the shipped stage via
|
|
64
|
+
the `ItemFulfillmentStages → ItemFulfillmentStatuses` join.
|
|
65
|
+
- **`library/app/api/toga2.php`** — `App_Api_Toga2::syncItemFulfillmentFromNetsuite` imports the
|
|
66
|
+
IF stage from NetSuite (see *How it works* / the per-client-sync doc).
|
|
67
|
+
|
|
68
|
+
## How it works
|
|
69
|
+
|
|
70
|
+
### IF stage resolution
|
|
71
|
+
|
|
72
|
+
A stage is resolved by joining `ItemFulfillmentStages → ItemFulfillmentStatuses` and matching the
|
|
73
|
+
status **`slug`** (`'picked'` / `'packed'` / `'shipped'`). The shipped checks in the status
|
|
74
|
+
machines use the constant `_Model_Client_ItemFulfillmentStatus::SLUG__SHIPPED` (never a hardcoded
|
|
75
|
+
`'shipped'`); the *picked* check stays a string literal because no `SLUG__PICKED` constant exists
|
|
76
|
+
on `ItemFulfillmentStatus` (the picked/packed slug constants live on the **Compass**
|
|
77
|
+
`SalesOrderStatus`, not the base IF status).
|
|
78
|
+
|
|
79
|
+
> **Stage resolution must use a single-row INNER JOIN on the status `slug`, not a scalar
|
|
80
|
+
> subquery on stage `name`** — a scalar subquery can throw "subquery returns more than 1 row".
|
|
81
|
+
> Compass USA + Canada lack the `c_netsuite*` column on stages, so slug is the only portable key.
|
|
82
|
+
|
|
83
|
+
### NetSuite import widening (change 2026-06-30)
|
|
84
|
+
|
|
85
|
+
`syncItemFulfillmentFromNetsuite` previously imported only *shipped* IFs. It now imports **all**
|
|
86
|
+
fulfillment statuses — filter widened to `['_picked','_packed','_shipped']`:
|
|
87
|
+
- **On create** it stamps the IF stage from the NetSuite ship status
|
|
88
|
+
(`c_netsuiteInternalItemFulfillmentStatus`) instead of hardcoding shipped.
|
|
89
|
+
- **On re-sync** it compares the current stage's `c_netsuiteInternalItemFulfillmentStatus` against
|
|
90
|
+
the NetSuite status and PUTs a new stage, so a fulfillment advances picked → packed → shipped.
|
|
91
|
+
- Applies to **all** clients including Compass / Compass Canada (which import all stages but still
|
|
92
|
+
count shipped-only for order status — see below).
|
|
93
|
+
|
|
94
|
+
### Order-status machines
|
|
95
|
+
|
|
96
|
+
| Client | Statuses walked | Driven by |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| Base, Quad (non-SA) | Pending → Picked → Packed → Partially Fulfilled → Fulfilled | all IF stages |
|
|
99
|
+
| Compass USA + Canada | Pending → Partially Fulfilled → Fulfilled | **shipped IF stage only** |
|
|
100
|
+
|
|
101
|
+
Quad `SA%` orders delegate to the base machine; an explicit `salesOrderStageId` overrides the
|
|
102
|
+
calculated status everywhere.
|
|
103
|
+
|
|
104
|
+
### Inventory movement (all clients)
|
|
105
|
+
|
|
106
|
+
`_qtyFulfilled` and `_qtyCommitted` sum only IF items whose stage is **shipped** — picked/packed
|
|
107
|
+
quantities are never counted as inventory movement, regardless of the new picked/packed
|
|
108
|
+
visibility in order status.
|
|
109
|
+
|
|
110
|
+
## Data model / schema (dbchanges2, 2026-06-30)
|
|
111
|
+
|
|
112
|
+
Fan-out (`Client/`, every client) + a Compass-Canada-specific set. Execution is alphabetical, so
|
|
113
|
+
`30a` (seed + backfill) runs before `30c` (NOT NULL alter):
|
|
114
|
+
|
|
115
|
+
- **`Client/2026-06-30a` — BackfillNullStageItemFulfillmentsToShipped:** idempotently seeds a
|
|
116
|
+
shipped `ItemFulfillmentStatus` + `ItemFulfillmentStage` (**unconditional** — only an
|
|
117
|
+
`INSERT…SELECT WHERE NOT EXISTS` slug guard, so fresh empty client DBs also get it), then
|
|
118
|
+
backfills NULL-stage `ItemFulfillments` to the shipped stage via `UPDATE…INNER JOIN` resolving
|
|
119
|
+
the stage through `ItemFulfillmentStatuses.slug = 'shipped' LIMIT 1` (single-row join, not a
|
|
120
|
+
scalar subquery).
|
|
121
|
+
- **`Client/2026-06-30b` — SalesOrderStatusesPickedPacked:** idempotent `INSERT…SELECT WHERE NOT
|
|
122
|
+
EXISTS` of picked + packed `SalesOrderStatuses`, `colorHex` stored **without** the leading `#`
|
|
123
|
+
(picked `F5A623`, packed `4DBCC4`).
|
|
124
|
+
- **`Client/2026-06-30c` — ItemFulfillmentStageIdNotNull:** `ALTER ItemFulfillments MODIFY
|
|
125
|
+
itemFulfillmentStageId INT UNSIGNED NOT NULL`. Safe because `30a` (alphabetically first)
|
|
126
|
+
guarantees every fulfillment already has a stage.
|
|
127
|
+
- **`Client_CompassCanada/2026-06-30a` — ItemFulfillmentLifecycleAndShippedBackfill:** creates
|
|
128
|
+
Canada's 3 `ItemFulfillmentStatuses` (picked/packed/shipped) and 3 `ItemFulfillmentStages`, all
|
|
129
|
+
idempotent (`INSERT…SELECT WHERE NOT EXISTS` on slug for statuses; stages guarded by `NOT
|
|
130
|
+
EXISTS` of a stage for that status slug), then the same single-row-join shipped backfill. Status
|
|
131
|
+
slug is UNIQUE and stage name is UNIQUE, so the guards align with the unique keys.
|
|
132
|
+
|
|
133
|
+
## Migration correctness rules (review-driven, baked into the above)
|
|
134
|
+
|
|
135
|
+
- **Seed migrations must be retry-safe:** use `INSERT…SELECT WHERE NOT EXISTS`, and resolve the
|
|
136
|
+
shipped stage via a single-row `INNER JOIN` on `slug` — never a scalar subquery on `name`
|
|
137
|
+
(which can throw "subquery returns more than 1 row" when more than one stage shares a name).
|
|
138
|
+
- **The shipped seed must be UNCONDITIONAL** (no `EXISTS`-fulfillments gate) so fresh client DBs
|
|
139
|
+
get a stage *before* the NOT NULL alter and *before* any future fulfillment insert.
|
|
140
|
+
|
|
141
|
+
## Gotchas / known issues
|
|
142
|
+
|
|
143
|
+
- **NULL-SUM correctness bug (fixed):** the base shipped-qty subquery returned NULL when no rows
|
|
144
|
+
matched (empty `SUM` = NULL), and `NULL < orderedQty` is not true, so empty orders read
|
|
145
|
+
**Fulfilled**. Wrap the shipped-qty subquery with `IFNULL(…, 0)` before comparing to the ordered
|
|
146
|
+
quantity.
|
|
147
|
+
- **Compass imports all stages but counts shipped-only.** Do not assume importing picked/packed
|
|
148
|
+
IFs changes a Compass order's status — by design it does not (the Compass supply UI must show
|
|
149
|
+
shipped-only progression). This supersedes the old "stages 1/2 are unused in prod" assumption in
|
|
150
|
+
the Compass order-lifecycle workflow.
|
|
151
|
+
- The base/Compass picked check is a string literal `'picked'` (no `SLUG__PICKED` on
|
|
152
|
+
`_Model_Client_ItemFulfillmentStatus`); only the shipped check uses a constant. Adding a base IF
|
|
153
|
+
picked/packed constant would let the literal be removed.
|
|
154
|
+
|
|
155
|
+
## Change history
|
|
156
|
+
- 2026-06-30 — Built the IF stage lifecycle end-to-end: NetSuite sync imports all
|
|
157
|
+
picked/packed/shipped statuses and advances stages on re-sync; base + Quad order `_status`
|
|
158
|
+
picked/packed/partial/fulfilled machine; Compass USA + Canada order status shipped-only;
|
|
159
|
+
`qtyFulfilled`/`qtyCommitted` shipped-only for all clients; dbchanges2 stage seed + backfill +
|
|
160
|
+
`itemFulfillmentStageId` NOT NULL (Client fan-out + Compass Canada). Fixed the NULL-SUM
|
|
161
|
+
Fulfilled bug (`IFNULL`). Baked in the retry-safe seed + single-row-join + unconditional-seed
|
|
162
|
+
migration rules. (bala)
|
|
163
|
+
|
|
164
|
+
## Related docs
|
|
165
|
+
- [Recursive Item Fulfillments](recursive-item-fulfillments.md) — the engine that mirrors IFs (and
|
|
166
|
+
now their stages) up the chain.
|
|
167
|
+
- [Carrier Shipping Labels & NetSuite IF](carrier-shipping-labels.md) — IF creation on the
|
|
168
|
+
fulfill-and-ship path.
|
|
169
|
+
- [NetSuite → TOGa Supply Per-Client Sync](../../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md)
|
|
170
|
+
— the importer surface; `syncItemFulfillmentFromNetsuite` is the stage-stamping entry point.
|
|
171
|
+
- [Compass Order Lifecycle & Data-Integrity Invariants](../../../clients/compass-usa/workflows/order-lifecycle-and-data-integrity.md)
|
|
172
|
+
— the stage-3-Shipped invariant updated by this work.
|
|
173
|
+
- [Compass ASN → ItemFulfillment Auto-Creation](../../../clients/compass-usa/features/asn-to-item-fulfillment.md)
|
|
174
|
+
— ASN-created IFs now resolve the shipped stage.
|
package/knowledge/INDEX.md
CHANGED
|
@@ -16,7 +16,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
16
16
|
|
|
17
17
|
## 2.0 framework
|
|
18
18
|
|
|
19
|
-
- **_underscore** (_Underscore) _(framework core)_ —
|
|
19
|
+
- **_underscore** (_Underscore) _(framework core)_ — 21 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
20
20
|
- **worker2** (Worker) — 24 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
21
21
|
- **api2** (API) — 7 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
22
22
|
- **dbchanges2** (Database Changes) _(framework core)_ — 3 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
@@ -20,6 +20,7 @@ files: []
|
|
|
20
20
|
related:
|
|
21
21
|
- ../compass-usa/profile.md
|
|
22
22
|
- ../../2.0/apps/toga2-commerce/features/expedited-shipping-gating.md
|
|
23
|
+
- ../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md
|
|
23
24
|
---
|
|
24
25
|
|
|
25
26
|
## Summary
|
|
@@ -56,4 +57,8 @@ to but distinct from Compass USA. Like Compass USA it spans the **2.0** commerce
|
|
|
56
57
|
values, which were then cleaned to English-only. See
|
|
57
58
|
[Assortment Name Translation](../../2.0/apps/_underscore/features/assortment-name-translation.md).
|
|
58
59
|
- The 2026-06-08 ASN → ItemFulfillment work was for **Compass USA**, not Compass Canada.
|
|
60
|
+
- **Order status is shipped-only**, same as Compass USA (shared `_Model_Compass_SalesOrder`).
|
|
61
|
+
Compass Canada's own IF lifecycle stages (picked/packed/shipped) + shipped backfill are seeded by
|
|
62
|
+
`dbchanges2/Client_CompassCanada/2026-06-30a - ItemFulfillmentLifecycleAndShippedBackfill.sql`.
|
|
63
|
+
See [IF stage lifecycle & order status](../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md).
|
|
59
64
|
- Related: [Compass USA](../compass-usa/profile.md).
|
|
@@ -5,8 +5,8 @@ project: _Underscore
|
|
|
5
5
|
client: compass-usa
|
|
6
6
|
type: client-feature
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-06-
|
|
9
|
-
owners: [jcardinal]
|
|
8
|
+
updated: 2026-06-30
|
|
9
|
+
owners: [jcardinal, bala]
|
|
10
10
|
files:
|
|
11
11
|
- _underscore/Model/Compass/AdvanceShippingNotice.php
|
|
12
12
|
- _underscore/Model/Compass/PurchaseOrder.php
|
|
@@ -20,6 +20,7 @@ files:
|
|
|
20
20
|
- dbchanges2/Client_Compass/2026-06-18b - CleanupSA132881DuplicateTracking.sql
|
|
21
21
|
related:
|
|
22
22
|
- ../../../2.0/apps/_underscore/features/recursive-item-fulfillments.md
|
|
23
|
+
- ../../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md
|
|
23
24
|
---
|
|
24
25
|
|
|
25
26
|
## Summary
|
|
@@ -149,6 +150,11 @@ Compass Canada (`Model/Compass/Canada/`) is a separate sub-client. The Compass c
|
|
|
149
150
|
- Interceptor is **DB-driven**: `postPost` does nothing without the Compass
|
|
150
151
|
`ApiPayloadInterceptors` rows in that env.
|
|
151
152
|
- The IF is named after the SO number; an already-existing IF with that number is reused.
|
|
153
|
+
- **ASN-created fulfillments land at the shipped stage (2026-06-30).** `resolveOrCreateItemFulfillment`
|
|
154
|
+
resolves the shipped `ItemFulfillmentStage` by status `slug` (portable across Compass USA + Canada,
|
|
155
|
+
which lack the `c_netsuite` column on stages) and sets `itemFulfillmentStage` on the payload — an
|
|
156
|
+
ASN means the goods shipped, so its IF must not be stage-less. See the
|
|
157
|
+
[IF stage lifecycle](../../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md) doc.
|
|
152
158
|
- **cXML SQL-injection hardening (2026-06-11):** the `ShipNoticeRequest` path interpolated the
|
|
153
159
|
cXML `Sender` Identity / SharedSecret and the OrderReference `orderID` (purchase order
|
|
154
160
|
number) straight into queries. These are now passed through `_Database::escape()`.
|
|
@@ -201,6 +207,10 @@ Compass Canada (`Model/Compass/Canada/`) is a separate sub-client. The Compass c
|
|
|
201
207
|
|
|
202
208
|
## Change history
|
|
203
209
|
Dated one-liners, newest first.
|
|
210
|
+
- 2026-06-30 — `resolveOrCreateItemFulfillment` now resolves the shipped `ItemFulfillmentStage` by
|
|
211
|
+
status slug and sets `itemFulfillmentStage` on the IF payload, so ASN-created fulfillments land
|
|
212
|
+
shipped (not stage-less) on both Compass USA + Canada. Part of the platform-wide IF stage
|
|
213
|
+
lifecycle work. (bala)
|
|
204
214
|
- 2026-06-18 — Reconcile path now skips IFIs that already carry item-level tracking
|
|
205
215
|
(`itemFulfillmentItemHasTracking()`), stopping duplicate/different tracking numbers from
|
|
206
216
|
vendor ASN re-transmissions piling onto fully-fulfilled lines (qty-1 SA132898 / USB31CC1 had
|
|
@@ -19,6 +19,7 @@ files: []
|
|
|
19
19
|
related:
|
|
20
20
|
- features/asn-to-item-fulfillment.md
|
|
21
21
|
- ../../2.0/apps/toga2-commerce/features/expedited-shipping-gating.md
|
|
22
|
+
- ../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md
|
|
22
23
|
---
|
|
23
24
|
|
|
24
25
|
## Summary
|
|
@@ -57,5 +58,10 @@ separate, related client (see its own profile).
|
|
|
57
58
|
`_Model_Compass_AdvanceShippingNotice::postPost` handler.
|
|
58
59
|
|
|
59
60
|
## Notes
|
|
61
|
+
- **Order status is shipped-only (2026-06-30).** Compass imports all IF stages
|
|
62
|
+
(picked/packed/shipped) but its order `_status` (`_underscore/Model/Compass/SalesOrder.php`,
|
|
63
|
+
shared by Compass USA + Canada) counts **shipped only** — picked/packed never advance a Compass
|
|
64
|
+
order. Contrast Quad, which gets the full picked/packed machine. See
|
|
65
|
+
[IF stage lifecycle & order status](../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md).
|
|
60
66
|
- Built on the shared 2.0 Recursive Item Fulfillments engine (upstream mirroring).
|
|
61
67
|
- This profile is a starting point; expand as more Compass-specific behavior is captured.
|
|
@@ -5,8 +5,8 @@ project: _Underscore
|
|
|
5
5
|
client: compass-usa
|
|
6
6
|
type: workflow
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-06-
|
|
9
|
-
owners: ["jcardinal"]
|
|
8
|
+
updated: 2026-06-30
|
|
9
|
+
owners: ["jcardinal", "bala"]
|
|
10
10
|
files: []
|
|
11
11
|
related:
|
|
12
12
|
- clients/compass-usa/profile.md
|
|
@@ -14,6 +14,7 @@ related:
|
|
|
14
14
|
- clients/compass-usa/features/mits-po-to-so-item-linking.md
|
|
15
15
|
- clients/compass-usa/features/item-fulfillment-tracking-tableview.md
|
|
16
16
|
- clients/compass-usa/features/mits-po-transmission-to-vendors.md
|
|
17
|
+
- 2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md
|
|
17
18
|
---
|
|
18
19
|
|
|
19
20
|
## Summary
|
|
@@ -82,8 +83,12 @@ one-upstream for bundles.
|
|
|
82
83
|
`AdvanceShippingNotices_TrackingNumbers`) and the IF side
|
|
83
84
|
(`ItemFulfillmentItemUnits_TrackingNumbers` → `ItemFulfillmentItems_TrackingNumbers` →
|
|
84
85
|
`ItemFulfillments_TrackingNumbers`). Always link existing `TrackingNumbers`; never create them.
|
|
85
|
-
4. **
|
|
86
|
-
|
|
86
|
+
4. **Stage is now mandatory:** as of 2026-06-30 `ItemFulfillments.itemFulfillmentStageId` is NOT NULL
|
|
87
|
+
and the picked/packed/shipped stages are all in use (resolved by status `slug`, not the legacy
|
|
88
|
+
numeric "stage 3 = Shipped"; stages 1/2 are no longer "unused"). The 2026-06-30a migration backfills
|
|
89
|
+
any NULL-stage IF to the shipped stage. Compass order `_status` still counts **shipped only** even
|
|
90
|
+
though all stages are imported — see the
|
|
91
|
+
[IF stage lifecycle doc](../../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md).
|
|
87
92
|
5. **Mirror back-links:** every IF on a non-source SO (ODP/Agilant) should have
|
|
88
93
|
`upstreamItemFulfillmentId` set, and each of its items `upstreamItemFulfillmentItemId` set.
|
|
89
94
|
|
|
@@ -114,5 +119,8 @@ runtime workflow.
|
|
|
114
119
|
- High-multiplier over-fulfillment (5×–20×) does not fit the split-PO spurious-link pattern — separate cause.
|
|
115
120
|
|
|
116
121
|
## Change history
|
|
122
|
+
- 2026-06-30 — Updated the IF-stage invariant: `itemFulfillmentStageId` is now NOT NULL and
|
|
123
|
+
picked/packed/shipped stages are all in use (resolved by slug), superseding "stage 3 = Shipped;
|
|
124
|
+
stages 1/2 unused". Compass order status remains shipped-only. (bala)
|
|
117
125
|
- 2026-06-12 — Documented full lifecycle, both fulfillment flows, verified upstream-mirror direction,
|
|
118
126
|
the expected-shape invariants, and the retrofix tool (jcardinal)
|
|
@@ -18,6 +18,7 @@ owners: ["jcardinal", "bala"]
|
|
|
18
18
|
files: []
|
|
19
19
|
related:
|
|
20
20
|
- ../../2.0/apps/_underscore/features/tracking-number-bridges.md
|
|
21
|
+
- ../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md
|
|
21
22
|
---
|
|
22
23
|
|
|
23
24
|
## Summary
|
|
@@ -42,6 +43,11 @@ Client-specific DB change-sets live in `dbchanges2/Client_Quad/`.
|
|
|
42
43
|
[Tracking-Number Bridge Migration](../../2.0/apps/_underscore/features/tracking-number-bridges.md).
|
|
43
44
|
|
|
44
45
|
## Notes
|
|
46
|
+
- **Order status: full picked/packed lifecycle (2026-06-30).** Quad gets the full
|
|
47
|
+
picked → packed → partially-fulfilled → fulfilled order `_status` machine
|
|
48
|
+
(`_underscore/Model/Quad/SalesOrder.php`): non-SA orders use the inline machine, `SA%` orders
|
|
49
|
+
delegate to the base `_Model_Client_SalesOrder`. Contrast Compass, which is shipped-only. See
|
|
50
|
+
[IF stage lifecycle & order status](../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md).
|
|
45
51
|
- Tracking data: record 318 (item-level) is currently empty for this client; their tracking
|
|
46
52
|
populates the IF/shipment level (record 317, ~98% coverage) and unit level (record 319). The
|
|
47
53
|
rebuilt views use 318 (per the Compass pattern) and will show tracking once item-level
|
package/package.json
CHANGED