toga-ai 1.0.129 → 1.0.131

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  | Doc | Framework | Summary | Files |
4
4
  |-----|-----------|---------|-------|
5
- | [Compass ASN → ItemFulfillment Auto-Creation](features/asn-to-item-fulfillment.md) | 2.0 | For Compass USA, posting an AdvanceShippingNotice (ASN) auto-creates the ItemFulfillment (IF) on the upstream SalesOrder. | _underscore/Model/Compass/AdvanceShippingNotice.php, _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 |
5
+ | [Compass ASN → ItemFulfillment Auto-Creation](features/asn-to-item-fulfillment.md) | 2.0 | For Compass USA, posting an AdvanceShippingNotice (ASN) auto-creates the ItemFulfillment (IF) on the upstream SalesOrder. | _underscore/Model/Compass/AdvanceShippingNotice.php, _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 |
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, dbchanges2/Client_Compass/2026-06-15a - FixItemFulfillmentTrackingNumberJoins.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, worker/crons/toga2/compass/workflow/3a_import_office_depot_purchase_orders.php |
8
8
  | [Compass USA](profile.md) | 2.0 | Compass USA is a TOGA client running a multi-tier supply-chain commerce operation. | |
@@ -5,7 +5,7 @@ project: _Underscore
5
5
  client: compass-usa
6
6
  type: client-feature
7
7
  status: active
8
- updated: 2026-06-16
8
+ updated: 2026-06-18
9
9
  owners: [jcardinal]
10
10
  files:
11
11
  - _underscore/Model/Compass/AdvanceShippingNotice.php
@@ -16,6 +16,7 @@ files:
16
16
  - dbchanges2/Client_Compass/2026-06-16 - CleanupSA132763CrossLineTracking.sql
17
17
  - dbchanges2/Client_Compass/2026-06-16b - CleanupSA132743CrossLineTracking.sql
18
18
  - dbchanges2/Client_Compass/2026-06-16c - BackfillSA132763C40QYUCTracking.sql
19
+ - dbchanges2/Client_Compass/2026-06-18a - CleanupSA132898DuplicateTracking.sql
19
20
  related:
20
21
  - ../../../2.0/apps/_underscore/features/recursive-item-fulfillments.md
21
22
  ---
@@ -54,6 +55,11 @@ the gotcha about already-fulfilled orders). Flow:
54
55
  - **`remaining <= 0`** (reconcile path, new): the tracking targets are the **existing IFIs**
55
56
  for that SOI (resolved via `ItemFulfillmentItems.salesOrderItemId` → their `itemFulfillmentId`),
56
57
  **not** an SO-number lookup — NetSuite/toga2-supply IFs are `F`-numbered, not SO-numbered.
58
+ As of **2026-06-18** an existing IFI that **already carries item-level tracking is skipped**
59
+ here (`itemFulfillmentItemHasTracking()`): reconcile only attaches onto untracked
60
+ out-of-band IFIs. An already-tracked, fully-fulfilled line revisited by a later ASN is a
61
+ vendor re-transmission and must not gain a second tracking number (see the duplicate-tracking
62
+ gotcha).
57
63
  4. Copy the ASN item's tracking (`AdvanceShippingNoticeItems_TrackingNumbers`) onto each target
58
64
  IFI via `POST /item-fulfillment-item-tracking-numbers` → `ItemFulfillmentItems_TrackingNumbers`
59
65
  — **idempotent** (skipped if the bridge row already exists; ASNs get re-POSTed).
@@ -176,8 +182,29 @@ Compass Canada (`Model/Compass/Canada/`) is a separate sub-client. The Compass c
176
182
  `NOT EXISTS`. ~116 PO items across other Compass orders share the off-by-one fingerprint and
177
183
  are not yet repaired — a broader reviewed backfill is still outstanding.
178
184
 
185
+ - **Duplicate tracking from vendor ASN re-transmission (root cause, fixed forward 2026-06-18)
186
+ — DISTINCT from the cross-line contamination above.** Compass vendors (Office Depot, Strategic
187
+ Systems) re-send the **same** shipment ASN file for several days; one PO can receive many ASNs
188
+ (e.g. SA132898 / PO `50305272-1` got 6). The first ASN takes the create path and attaches its
189
+ tracking; a later ASN re-listing the same, now fully-fulfilled line takes the **reconcile path**
190
+ and — pre-fix — attached its *different* tracking number onto the existing IFI. On a qty-1 line
191
+ this yields two item-level tracking rows (plus a propagated package row). Unlike the cross-line
192
+ bug, **both** tracking numbers here map cleanly to the correct line via their own valid ASN
193
+ items, so the `NOT EXISTS (ASN item with matching VendorItems.itemId)` cleanup guard does **not**
194
+ catch it — diagnose instead by an IFI carrying more item-level tracking numbers than its
195
+ fulfilled quantity. Fix: the reconcile path now skips any IFI that already has item-level
196
+ tracking (`itemFulfillmentItemHasTracking()`), so only untracked out-of-band IFIs receive ASN
197
+ tracking. The legitimate out-of-band case (IFI has no tracking, one ASN arrives) is preserved.
198
+ Forward-only; SA132898 repaired by `2026-06-18a` (delete the later tracking + orphaned package
199
+ row, keep the original; no backfill needed since the correct number is already present).
200
+
179
201
  ## Change history
180
202
  Dated one-liners, newest first.
203
+ - 2026-06-18 — Reconcile path now skips IFIs that already carry item-level tracking
204
+ (`itemFulfillmentItemHasTracking()`), stopping duplicate/different tracking numbers from
205
+ vendor ASN re-transmissions piling onto fully-fulfilled lines (qty-1 SA132898 / USB31CC1 had
206
+ 2). Distinct root cause from the 2026-06-16 cross-line contamination. SA132898 repaired by
207
+ `2026-06-18a` (cleanup-only; original tracking already correct). (jcardinal)
181
208
  - 2026-06-16 — Identified cross-line tracking contamination root cause: bad
182
209
  `SalesOrderItems_PurchaseOrderItems` rows (PO-local lineNumber collision in
183
210
  `_Model_Compass_PurchaseOrder`, fixed forward 2026-06-11 `ec935478`; and the worker ODP-PO
@@ -2,5 +2,5 @@
2
2
 
3
3
  | Doc | Framework | Summary | Files |
4
4
  |-----|-----------|---------|-------|
5
- | [Credit Card Receipt Processing](features/receipt-processing.md) | 2.0 | Automated processing of credit card receipts uploaded to SharePoint. | worker2/Worker/Client/TowFoundation.php, worker2/Worker/Client/TowFoundation/ProcessReceipts.php |
5
+ | [Credit Card Receipt Processing](features/receipt-processing.md) | 2.0 | Automated processing of credit card receipts uploaded to SharePoint. | worker2/Worker/Client/TowFoundation.php, worker2/Worker/Client/TowFoundation/ProcessReceipts.php, worker2/Worker/Client/TowFoundation/TowFoundationVendors.php |
6
6
  | [Tow Foundation](profile.md) | 2.0 | Tow Foundation is a nonprofit client. | |
@@ -5,11 +5,12 @@ project: Worker
5
5
  client: tow-foundation
6
6
  type: client-feature
7
7
  status: active
8
- updated: 2026-06-12
8
+ updated: 2026-06-18
9
9
  owners: ["rgirish"]
10
10
  files:
11
11
  - worker2/Worker/Client/TowFoundation.php
12
12
  - worker2/Worker/Client/TowFoundation/ProcessReceipts.php
13
+ - worker2/Worker/Client/TowFoundation/TowFoundationVendors.php
13
14
  related:
14
15
  - clients/tow-foundation/profile.md
15
16
  ---
@@ -23,7 +24,7 @@ Excel file per person, uploads it to SharePoint, and sends a summary email.
23
24
 
24
25
  Invocation: `{"action": "Client/TowFoundation/ProcessReceipts/Run", "parameters": {}}`
25
26
 
26
- Optional filter parameters: `limit` (int), `year` (string e.g. `"2026"`), `person` (string e.g. `"Brent Peterkin"`).
27
+ Optional filter parameters: `limit` (int), `year` (string e.g. `"2026"`), `person` (string e.g. `"Brent Peterkin"`), `billingCycle` (string — matches the **end** of the billing-cycle folder name, e.g. `"06-03-2026"` matches `"Amex ending in 06-03-2026"`, `"Mastercard ending in 06-03-2026"`, etc.).
27
28
 
28
29
  ## Key files / entry points
29
30
 
@@ -48,7 +49,7 @@ Credit Card Receipts/
48
49
 
49
50
  1. **Auth** — OAuth2 client-credentials token from Microsoft Graph
50
51
  2. **Walk** — `walkReceiptsFolder()` enumerates all receipt files and statement Excels
51
- 3. **Filter** — optional `$year` / `$person` / `$limit` applied to the receipt list
52
+ 3. **Filter** — optional `$year` / `$person` / `$billingCycle` / `$limit` applied to the receipt list. `$billingCycle` uses `str_ends_with()` so passing just the date (e.g. `"06-03-2026"`) matches any card type. Person folders named `"Brent Peterkin CC receipts"` are normalized to `"Brent Peterkin"` via regex strip of ` CC receipts` suffix before any map lookups.
52
53
  4. **Pre-load statements** — all billing-cycle `.xlsx` files under `reports/` are downloaded
53
54
  and parsed upfront via PhpSpreadsheet (auto-detect header row by scanning for
54
55
  description/amount keywords)
@@ -59,8 +60,9 @@ Credit Card Receipts/
59
60
  - Cross-verify amount ± $0.01 against parsed statement; if matched, override `payment_memo` with statement description
60
61
  - Build base filename (no suffix yet) and store in `$pendingRenames`
61
62
  - On extract failure → move to `Archive/exception/` immediately
62
- 6. **Collision detection** — count base name occurrences across all pending renames
63
- 7. **Pass 2 — Move** — for each pending rename:
63
+ 6. **Deduplication** — after Pass 1, receipts sharing identical `(personName, invoice_date, vendor_name, total)` are deduplicated; only the first is kept, the rest are logged as `DUPLICATE skipped` and dropped. This catches the same receipt uploaded twice to SharePoint.
64
+ 7. **Collision detection** — count base name occurrences across remaining pending renames
65
+ 8. **Pass 2 — Move** — for each pending rename:
64
66
  - If base name appears more than once, assign `_a`, `_b`, `_c`... suffix to **all** colliding files (including the first)
65
67
  - PATCH SharePoint to rename + move to `Archive/` (flat — no person subfolder)
66
68
  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.
@@ -80,6 +82,25 @@ Amex billing cycle runs 3rd-to-3rd. `computeRefNo()` returns the cycle-end date
80
82
  - Charge on or before the 3rd → cycle ends on the 3rd of the same month
81
83
  - Charge after the 3rd → cycle ends on the 3rd of the next month
82
84
 
85
+ ### QB vendor matching
86
+ `TowFoundationVendors.php` holds 2,994 vendor names exported verbatim from Tow Foundation's
87
+ QuickBooks vendor list (June 2026). `matchQbVendor()` runs three passes against this list:
88
+ 1. Exact match (case-insensitive)
89
+ 2. Substring match — QB name contains extracted name or vice versa (min 4 chars); picks shortest match on multiple hits
90
+ 3. `similar_text()` similarity ≥ 80% — picks highest scorer
91
+
92
+ Returns the exact QB vendor string or `''` if no confident match. The AI only extracts
93
+ `vendor_name`; QB matching is done entirely in PHP — no tokens spent per receipt on the list.
94
+
95
+ To update the vendor list: re-export from QuickBooks → run the extraction script →
96
+ replace `TowFoundationVendors.php`. The matching logic is in `matchQbVendor()` and needs no changes.
97
+
98
+ ### AI category values
99
+ The AI is prompted to return **specific expense types** — e.g. `"Dining"`, `"Travel"`,
100
+ `"Accommodation"`, `"Transportation"`, `"Office Supplies"`, `"Entertainment"`,
101
+ `"Professional Services"`, `"Subscriptions"`. It must not use broad accounting labels
102
+ like `"Administrative Expenses"`.
103
+
83
104
  ### Per-person constants
84
105
  `CLASS_MAP` and `PAYMENT_ACCOUNT_MAP` in `ProcessReceipts.php` map each cardholder's name
85
106
  to their QuickBooks Class and Payment Account strings. Ryan Farrell uses Bank of America
@@ -133,7 +154,13 @@ Fatal errors send only to `NOTIFY_EMAIL_DEV` (no CC/BCC).
133
154
  `Credit Card Receipts/3. QB Excel/` (not inside each person's subfolder). This folder
134
155
  exists at the root of `Credit Card Receipts/` alongside the person folders.
135
156
 
157
+ - **Person folder name normalization** — SharePoint folders are named `"Brent Peterkin CC receipts"` but `CLASS_MAP` / `PAYMENT_ACCOUNT_MAP` keys are just `"Brent Peterkin"`. The ` CC receipts` suffix is stripped via regex in `walkReceiptsFolder()`. Without this, Class and Payment Account columns are blank for those persons.
158
+ - **`billingCycle` filter is suffix-match, not exact** — always pass just the date portion (`"06-03-2026"`), not the full folder name. Passing the full name (`"Amex ending in 06-03-2026"`) also works but would miss Mastercard folders.
159
+ - **Ole Mole not in QB vendor list** — as of June 2026 this vendor is not in Tow Foundation's QuickBooks, so `qb_vendor` will be blank for Ole Mole charges until they add it to QB and the vendor list is refreshed.
160
+ - **Duplicate detection is within-run only** — if the same receipt was processed in a prior run and archived, it won't be caught. The dedup only covers receipts present in the current run's `$pendingRenames`.
161
+
136
162
  ## Change history
137
163
 
164
+ - 2026-06-18 — QB vendor matching via local fuzzy match against `TowFoundationVendors.php` (2,994 vendors); AI category prompt updated to return specific types (Dining, Travel, etc.); duplicate receipt detection added (person+date+vendor+amount dedup); `billingCycle` filter parameter added (suffix-match — card-type agnostic); person folder ` CC receipts` suffix stripped so CLASS_MAP/PAYMENT_ACCOUNT_MAP resolve correctly (rgirish)
138
165
  - 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)
139
166
  - 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.129",
3
+ "version": "1.0.131",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",