toga-ai 1.0.65 → 1.0.67
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.
|
@@ -6,3 +6,4 @@
|
|
|
6
6
|
| [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 |
|
|
7
7
|
| [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 |
|
|
8
8
|
| [Compass USA](profile.md) | 2.0 | Compass USA is a TOGA client running a multi-tier supply-chain commerce operation. | |
|
|
9
|
+
| [Compass Order Lifecycle & Data-Integrity Invariants](workflows/order-lifecycle-and-data-integrity.md) | 2.0 | End-to-end map of how a Compass order flows through the `Client_Compass` (2.0) database and the **expected raw-data shape** at each link/ASN/IF level. | |
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Compass Order Lifecycle & Data-Integrity Invariants
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
project: _Underscore
|
|
5
|
+
client: compass-usa
|
|
6
|
+
type: workflow
|
|
7
|
+
status: active
|
|
8
|
+
updated: 2026-06-12
|
|
9
|
+
owners: ["jcardinal"]
|
|
10
|
+
files: []
|
|
11
|
+
related:
|
|
12
|
+
- clients/compass-usa/profile.md
|
|
13
|
+
- clients/compass-usa/features/asn-to-item-fulfillment.md
|
|
14
|
+
- clients/compass-usa/features/mits-po-to-so-item-linking.md
|
|
15
|
+
- clients/compass-usa/features/item-fulfillment-tracking-tableview.md
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Summary
|
|
19
|
+
|
|
20
|
+
End-to-end map of how a Compass order flows through the `Client_Compass` (2.0) database and the
|
|
21
|
+
**expected raw-data shape** at each link/ASN/IF level. This is the reference for diagnosing and
|
|
22
|
+
retroactively repairing the order data, where 50+ historical bugs have left links, ASNs, and item
|
|
23
|
+
fulfillments out of the shape the workflow should produce. All facts below were verified against the
|
|
24
|
+
live `prod` / `Client_Compass` schema.
|
|
25
|
+
|
|
26
|
+
## The chain (supply direction, by customerId / vendorId)
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
Compass customer SO (SalesOrders.customerId = 2, number SA/MA/MR)
|
|
30
|
+
--SalesOrders_PurchaseOrders--> MITS PO (PurchaseOrders.customerId = 2, vendorId IN (1,26,25))
|
|
31
|
+
--PurchaseOrders_SalesOrders--> Office Depot SO (customerId = 1)
|
|
32
|
+
--SalesOrders_PurchaseOrders--> Office Depot PO (vendorId = 1)
|
|
33
|
+
--PurchaseOrders_SalesOrders--> Agilant/NetSuite SO (customerId = 3)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- **SA** orders are user-created in Compass then sent to MITS; **MR** orders are created
|
|
37
|
+
retroactively from the inbound MITS PO (they do not originate as Compass-entered SOs); MA exists too.
|
|
38
|
+
Structurally MR uses the same bridges as SA.
|
|
39
|
+
- One Compass SO item can split into multiple PO items (bundles / `c_isConfiguration`,
|
|
40
|
+
`parentSalesOrderItemId`, `bundleId`).
|
|
41
|
+
|
|
42
|
+
## Item-link bridges & the purity rule
|
|
43
|
+
|
|
44
|
+
- `SalesOrderItems_PurchaseOrderItems(salesOrderItemId, purchaseOrderItemId)` — a SO item → its PO item.
|
|
45
|
+
- `PurchaseOrderItems_SalesOrderItems(purchaseOrderItemId, salesOrderItemId)` — a PO item → its downstream SO item.
|
|
46
|
+
- Item identity: `PurchaseOrderItems.vendorItemId → VendorItems.itemId`. **`PurchaseOrderItems` has NO
|
|
47
|
+
`salesOrderItemId` column** — POI↔SOI is resolvable ONLY through these two bridges.
|
|
48
|
+
- **PURE link ⇔ `SalesOrderItems.itemId = linked POI's VendorItems.itemId`. SPURIOUS ⇔ they differ.**
|
|
49
|
+
Spurious links (the lineNumber-match bug, see mits-po-to-so-item-linking) cause phantom over-fulfillment.
|
|
50
|
+
- **Many-to-one is legitimate**, not spurious — several SOIs may link to one POI
|
|
51
|
+
(e.g. `PurchaseOrderItems_SalesOrderItems.purchaseOrderItemId = 174808` has 6 SOIs). Never treat
|
|
52
|
+
multiplicity as corruption; the only spuriousness test is the itemId mismatch.
|
|
53
|
+
|
|
54
|
+
## Two fulfillment flows (different expected IF shapes)
|
|
55
|
+
|
|
56
|
+
- **Flow A — direct ASN→IF** (MITS PO vendor in {25,26}, e.g. Strategic Systems; no Agilant leg):
|
|
57
|
+
an ASN against the MITS PO produces an ItemFulfillment on the Compass SO. Serialized units come
|
|
58
|
+
from the ASN units.
|
|
59
|
+
- **Flow B — Office Depot / NetSuite** (MITS PO vendor 1, with ODP SO/PO + Agilant SO): the
|
|
60
|
+
fulfillment **originates on the Agilant IF** (created by NetSuite sync; `c_netsuiteInternalItemFulfillmentId`
|
|
61
|
+
set) and **mirrors UP the chain**. Units come from NetSuite inventory assignment, NOT the ASN
|
|
62
|
+
(ODP ASN units are frequently tracking-only, `unitId` NULL).
|
|
63
|
+
|
|
64
|
+
### Upstream mirror direction (verified from real rows)
|
|
65
|
+
The **Compass-SO IF is the TOP** (`upstreamItemFulfillmentId = NULL`). The ODP IF's
|
|
66
|
+
`upstreamItemFulfillmentId` points to the Compass IF; the Agilant IF's points to the ODP IF. The
|
|
67
|
+
back-link chain climbs **Agilant → ODP → Compass**. Item back-links use
|
|
68
|
+
`ItemFulfillmentItems.upstreamItemFulfillmentItemId` the same direction and may be many-downstream →
|
|
69
|
+
one-upstream for bundles.
|
|
70
|
+
|
|
71
|
+
## Expected raw-data invariants (for detect-and-repair)
|
|
72
|
+
|
|
73
|
+
1. **Links:** every SOI that should be procured has exactly one PURE bridge to the matching POI on the
|
|
74
|
+
correct PO; spurious (itemId-mismatch) bridges should not exist; the full header chain
|
|
75
|
+
(SalesOrders_PurchaseOrders / PurchaseOrders_SalesOrders) should be intact at each present layer.
|
|
76
|
+
2. **Never over-fulfill:** `SUM(ItemFulfillmentItems.quantity)` per SOI must be `≤ SalesOrderItems.quantity`.
|
|
77
|
+
A shipped POI fanning out to many linked SOIs must not produce IF items totaling more than ordered.
|
|
78
|
+
3. **ASN/IF tracking is maintained at all three levels and rolls up:** a tracking number on a unit must
|
|
79
|
+
also be linked at the item level and the header level — on BOTH the ASN side
|
|
80
|
+
(`AdvanceShippingNoticeItemUnits_TrackingNumbers` → `AdvanceShippingNoticeItems_TrackingNumbers` →
|
|
81
|
+
`AdvanceShippingNotices_TrackingNumbers`) and the IF side
|
|
82
|
+
(`ItemFulfillmentItemUnits_TrackingNumbers` → `ItemFulfillmentItems_TrackingNumbers` →
|
|
83
|
+
`ItemFulfillments_TrackingNumbers`). Always link existing `TrackingNumbers`; never create them.
|
|
84
|
+
4. **Legacy stage:** a shipped IF (has items) with `itemFulfillmentStageId IS NULL` should be set to 3
|
|
85
|
+
("Shipped"). Stage 3 = Shipped; stages 1/2 are unused in prod.
|
|
86
|
+
5. **Mirror back-links:** every IF on a non-source SO (ODP/Agilant) should have
|
|
87
|
+
`upstreamItemFulfillmentId` set, and each of its items `upstreamItemFulfillmentItemId` set.
|
|
88
|
+
|
|
89
|
+
## Verified schema corrections (older code/scripts assume these wrongly)
|
|
90
|
+
- **`ItemFulfillmentPackages` does NOT exist** — header IF tracking is `ItemFulfillments_TrackingNumbers`.
|
|
91
|
+
- `ItemFulfillmentItemUnits` has **no `trackingNumberId`** — unit tracking is the bridge
|
|
92
|
+
`ItemFulfillmentItemUnits_TrackingNumbers`.
|
|
93
|
+
- ASN unit table is `AdvanceShippingNoticeItemUnits` (`unitId` nullable = tracking-only).
|
|
94
|
+
- `ItemFulfillments.number` is UNIQUE NOT NULL; `dateItemFulfillment` NOT NULL.
|
|
95
|
+
|
|
96
|
+
## Systems involved
|
|
97
|
+
`Client_Compass` (2.0) tables; the 1.0 worker crons under `worker/crons/toga2/compass/workflow/`
|
|
98
|
+
(transmit-to-MITS, transmit-to-vendors, ODP EDI import, Strategic Systems ASN import, NetSuite sync);
|
|
99
|
+
`api2` cXML ShipNotice handler; the `_underscore` recursive item-fulfillment engine.
|
|
100
|
+
|
|
101
|
+
## Retrofix tooling
|
|
102
|
+
A standalone repair script lives in the worker 1.0 test area (`test/@jeff/compass/retrofix.php`,
|
|
103
|
+
machine-local path tracked in dev memory). It sweeps Compass SOs newest-first, detects deviations from
|
|
104
|
+
the invariants above, and repairs via direct SQL on `db_prod2_compass`. Safety rules baked in: rehearsal
|
|
105
|
+
mode (logs intended writes, rolls back), per-phase gating, a hard block on any write to the
|
|
106
|
+
`TrackingNumbers` table, and the never-over-fulfill cap. It is a diagnostic/repair tool, not part of the
|
|
107
|
+
runtime workflow.
|
|
108
|
+
|
|
109
|
+
## Edge cases & escalation
|
|
110
|
+
- Incomplete orders (no PO/ASN/Agilant leg yet) are normal — repair only the layers that exist.
|
|
111
|
+
- Logs for confirming original payloads: `Logs_Compass.Api` (inbound MITS POs apiId=2; inbound cXML
|
|
112
|
+
ShipNotices), legacy `Logs.API` (outbound transmits), legacy `Logs.FileLog` (`ODP_EDI`, ~30-day retention).
|
|
113
|
+
- High-multiplier over-fulfillment (5×–20×) does not fit the split-PO spurious-link pattern — separate cause.
|
|
114
|
+
|
|
115
|
+
## Change history
|
|
116
|
+
- 2026-06-12 — Documented full lifecycle, both fulfillment flows, verified upstream-mirror direction,
|
|
117
|
+
the expected-shape invariants, and the retrofix tool (jcardinal)
|
|
@@ -5,7 +5,7 @@ project: Worker
|
|
|
5
5
|
client: tow-foundation
|
|
6
6
|
type: client-feature
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-06-
|
|
8
|
+
updated: 2026-06-12
|
|
9
9
|
owners: ["rgirish"]
|
|
10
10
|
files:
|
|
11
11
|
- worker2/Worker/Client/TowFoundation.php
|
|
@@ -35,15 +35,13 @@ Optional filter parameters: `limit` (int), `year` (string e.g. `"2026"`), `perso
|
|
|
35
35
|
### SharePoint folder structure
|
|
36
36
|
```
|
|
37
37
|
Credit Card Receipts/
|
|
38
|
+
3. QB Excel/ ← generated Excel files for ALL persons uploaded here after each run
|
|
38
39
|
{Person}/
|
|
39
40
|
{Year}/
|
|
40
41
|
{BillingCycleFolder}/ ← receipt files live here (e.g. "Amex ending in 06-03-2026")
|
|
41
42
|
{Person} reports/ ← .xlsx billing statements for cross-verification
|
|
42
|
-
Archive/
|
|
43
|
-
|
|
44
|
-
exception/
|
|
45
|
-
{Person}/ ← failed receipts land here (original name preserved)
|
|
46
|
-
3. QB Excel/ ← generated Excel files uploaded here after each run
|
|
43
|
+
Archive/ ← successfully processed receipts land here (renamed, flat)
|
|
44
|
+
Archive/exception/ ← failed receipts land here (original name preserved, flat)
|
|
47
45
|
```
|
|
48
46
|
|
|
49
47
|
### Processing flow
|
|
@@ -55,18 +53,18 @@ Credit Card Receipts/
|
|
|
55
53
|
and parsed upfront via PhpSpreadsheet (auto-detect header row by scanning for
|
|
56
54
|
description/amount keywords)
|
|
57
55
|
5. **Pass 1 — Extract** — for each receipt:
|
|
58
|
-
- Enforce size limit (4 MB images, 10 MB documents
|
|
56
|
+
- Enforce type + size limit: unsupported mime types (`application/octet-stream`) throw immediately; 4 MB cap for images, 10 MB for documents
|
|
59
57
|
- Download file bytes from SharePoint
|
|
60
58
|
- POST to Talos AI `/api/ai/generate` → structured `{vendor_name, invoice_date, total, payment_memo, category, ...}`
|
|
61
59
|
- Cross-verify amount ± $0.01 against parsed statement; if matched, override `payment_memo` with statement description
|
|
62
60
|
- Build base filename (no suffix yet) and store in `$pendingRenames`
|
|
63
|
-
- On extract failure → move to `Archive/exception
|
|
61
|
+
- On extract failure → move to `Archive/exception/` immediately
|
|
64
62
|
6. **Collision detection** — count base name occurrences across all pending renames
|
|
65
63
|
7. **Pass 2 — Move** — for each pending rename:
|
|
66
64
|
- If base name appears more than once, assign `_a`, `_b`, `_c`... suffix to **all** colliding files (including the first)
|
|
67
|
-
- PATCH SharePoint to rename + move to `Archive
|
|
65
|
+
- PATCH SharePoint to rename + move to `Archive/` (flat — no person subfolder)
|
|
68
66
|
8. **Excel generation** — one `.xlsx` per person (PhpSpreadsheet); columns: Row #, Account Name, QB Vendor, Payment Amount, Date, Payment Method, Payment Memo, QB Description, Class, Category, Payment Account, Ref No.
|
|
69
|
-
9. **SharePoint upload** — each Excel uploaded to `
|
|
67
|
+
9. **SharePoint upload** — each Excel uploaded to root-level `Credit Card Receipts/3. QB Excel/` via Graph API PUT (shared folder for all persons)
|
|
70
68
|
10. **Summary email** — sent with Excel files attached; To: Jheanelle, CC: Magdalena, BCC: devteam@togatech.com
|
|
71
69
|
|
|
72
70
|
### Renamed file format
|
|
@@ -124,7 +122,18 @@ Fatal errors send only to `NOTIFY_EMAIL_DEV` (no CC/BCC).
|
|
|
124
122
|
- **No client DB** — there is no audit trail in MySQL. All state lives in SharePoint folder
|
|
125
123
|
structure and email. If a run is interrupted mid-way, some receipts may be archived
|
|
126
124
|
without a corresponding Excel row.
|
|
125
|
+
- **Unsupported file types** — macOS `.textClipping` files and any file resolving to
|
|
126
|
+
`application/octet-stream` are rejected with a `ReceiptProcessingException` at the
|
|
127
|
+
size-check stage (before download). They land in `Archive/exception/` and appear in the
|
|
128
|
+
summary email so Jheanelle can clean them up manually.
|
|
129
|
+
- **Archive folders are flat** — processed files land directly in `Archive/` and failed
|
|
130
|
+
files in `Archive/exception/` with no extra person subfolder. The Archive is already
|
|
131
|
+
scoped inside `{Person}/{Year}/` so the extra nesting is redundant.
|
|
132
|
+
- **QB Excel goes to the root `3. QB Excel/` folder** — all persons' Excels land in
|
|
133
|
+
`Credit Card Receipts/3. QB Excel/` (not inside each person's subfolder). This folder
|
|
134
|
+
exists at the root of `Credit Card Receipts/` alongside the person folders.
|
|
127
135
|
|
|
128
136
|
## Change history
|
|
129
137
|
|
|
138
|
+
- 2026-06-12 — QB Excel upload moved to root-level `3. QB Excel/`; Archive/exception folders flattened (no person subfolder); unsupported mime types (`application/octet-stream`) now throw ReceiptProcessingException instead of being silently skipped (rgirish)
|
|
130
139
|
- 2026-06-11 — Added CC (Magdalena), BCC (devteam@togatech.com), per-cardholder Archive subfolders, Excel upload to "3. QB Excel" SharePoint folder, two-pass rename with alphabetical duplicate suffix, rename format changed to `YYYY.MM.DD Full Name_Vendor_Amount` (rgirish)
|
package/package.json
CHANGED