toga-ai 1.0.121 → 1.0.122

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.
@@ -3,7 +3,8 @@
3
3
  | Doc | Summary | Files |
4
4
  |-----|---------|-------|
5
5
  | [Worker (1.0 Framework) Architecture](architecture.md) | `worker` is the legacy (**1.0** `App_` framework) **background-job tier**. | worker/index.php, worker/_/app/framework.php, worker/crons/, worker/schedules/, worker/ebs/cron.worker.php, worker/.ebextensions/035_cron.worker.config |
6
- | [Compass Partial In-Transit & Delivered Emails (per package)](features/compass-partial-in-transit-delivered-emails.md) | Compass USA and Compass Canada send a **per-package** in-transit email (and a matching delivered email) instead of one email listing the whole order. | worker/crons/toga2/compass/update_salesorder_status_from_odp.php, worker/crons/toga2/compass/workflow/test_partial_in_transit_email.php, worker/crons/toga2/compasscanada/workflow/3_update_salesorder_status_from_grand_and_toy.php, worker/crons/toga2/compasscanada/workflow/test_partial_in_transit_email.php |
6
+ | [Compass MA Sales Order Exception Report](features/compass-ma-sales-order-exception-report.md) | A worker cron that emails operations the "Compass Refresh Exception Report" — Compass `MA%` sales orders whose corresponding Office Depot (ODP) sales order has | worker/crons/toga2/compass/workflow/7_generate_ma_sales_order_exception_report.php |
7
+ | [Compass Partial In-Transit & Delivered Emails (per package)](features/compass-partial-in-transit-delivered-emails.md) | Compass USA and Compass Canada send a **per-package** in-transit email (and a matching delivered email) instead of one email listing the whole order. | worker/crons/toga2/compass/update_salesorder_status_from_odp.php, worker/crons/toga2/compasscanada/workflow/3_update_salesorder_status_from_grand_and_toy.php |
7
8
  | [Forecast2 ↔ NetSuite Reconciliation & Trueup Tooling](features/forecast2-netsuite-reconciliation.md) | CLI tools to **audit** and **repair** drift between the production `Forecast` DB (core2) and NetSuite. | test/@dave/reconcile_netsuite_totals.php, test/@dave/analyze_netsuite_forecast_diff.php, test/@dave/trueup_sales.php, test/@dave/trueup_open_orders.php, test/@dave/trueup_opportunities.php, test/@dave/probe_sales_gap_direct.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
8
9
  | [NetSuite → TOGa Supply Per-Client Sync (thin wrappers)](features/netsuite-togasupply-per-client-sync.md) | Syncs NetSuite transactions (sales orders, purchase orders, invoices, item receipts, item fulfillments, inventory adjustments) into each TOGa Supply (2.0) clien | worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/crons/toga2/netsuite/sync_togasupply_canon.php, worker/schedules/cron.worker.sync.json, dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql |
9
10
  | [Prudential: Send Shipments for the Day report (daily cron)](features/send-shipments-for-the-day.md) | Daily cron (9:00 PM) that emails Prudential and Dell stakeholders an Excel report of all devices shipped that day, including tracking number, serial number, emp | worker/crons/notifications/reports/send_shipments_for_the_day.php |
@@ -0,0 +1,82 @@
1
+ ---
2
+ title: Compass MA Sales Order Exception Report
3
+ framework: "1.0"
4
+ repo: worker
5
+ project: Worker
6
+ client: compass-usa
7
+ type: feature
8
+ status: active
9
+ updated: 2026-06-18
10
+ owners: ["bala"]
11
+ files:
12
+ - worker/crons/toga2/compass/workflow/7_generate_ma_sales_order_exception_report.php
13
+ related: []
14
+ ---
15
+
16
+ ## Summary
17
+ A worker cron that emails operations the "Compass Refresh Exception Report" — Compass `MA%`
18
+ sales orders whose corresponding Office Depot (ODP) sales order has **not** been created in
19
+ NetSuite yet. It is a catch-the-stragglers report: orders that should have flowed to NetSuite
20
+ but haven't. Runs against the Compass client DB (`db_prod_compass` / `Client_Compass`).
21
+
22
+ ## Key files / entry points
23
+ - `worker/crons/toga2/compass/workflow/7_generate_ma_sales_order_exception_report.php` — the
24
+ whole job: one main SELECT, a per-row NetSuite recheck, and a PhpSpreadsheet Excel email.
25
+
26
+ ## How it works
27
+ 1. **Main query** walks a two-hop bridge chain to connect the customer order to the vendor order:
28
+ Compass SO (`number LIKE 'MA%'`) → `SalesOrders_PurchaseOrders` → Compass PO →
29
+ `PurchaseOrders_SalesOrders` → **ODP SO** (`customerId = OFFICE_DEPOT`) →
30
+ `SalesOrders_PurchaseOrders` → ODP PO. Note the two bridge tables are used in **opposite
31
+ directions** at each hop — `SalesOrders_PurchaseOrders` for SO→PO, `PurchaseOrders_SalesOrders`
32
+ for PO→SO. It also inner-joins the ODP SO's address, state, items, item→PO-item bridge, PO
33
+ items, and vendor items — so a row with any of those missing silently drops out.
34
+ 2. **Filter:** `MA%` Compass SO, ODP SO `customerId = App_Client_Compass::CUSTOMER_ID__OFFICE_DEPOT`
35
+ (= 1), and `OfficeDepotSalesOrders.c_netsuiteInternalSalesOrderId IS NULL`.
36
+ 3. **Per-row NetSuite recheck:** for each surviving row it looks for an **Agilant**-customer
37
+ (`CUSTOMER_ID__AGILANT` = 3) sales order tied to that ODP PO number. If found → it back-fills
38
+ `c_netsuiteInternalSalesOrderId` on the ODP SO and skips the row (drops it off the report
39
+ permanently). If not found → it writes the row to the Excel.
40
+ 4. Emails the workbook to operations only if at least one data row was written.
41
+
42
+ ## Data model
43
+ - `Client_Compass.SalesOrders` — the ODP sales order is the one keyed on (`id AS salesOrderId`,
44
+ `customerId = 1` Office Depot). Relevant columns: `c_netsuiteInternalSalesOrderId` (NULL until
45
+ the order exists in NetSuite) and `salesOrderStageId`.
46
+ - `Client_Compass.SalesOrderStages` — `5 = Pending Billing`, `6 = Pending Billing/Partially
47
+ Fulfilled`, `7 = Billed`, `8 = Canceled`, `9 = Closed`.
48
+ - Bridges: `SalesOrders_PurchaseOrders` (salesOrderId, purchaseOrderId) and
49
+ `PurchaseOrders_SalesOrders` (purchaseOrderId, salesOrderId) — distinct tables, distinct
50
+ directions.
51
+
52
+ ## Client variations
53
+ Compass USA only — this is a Compass-specific integration cron.
54
+
55
+ ## Gotchas / known issues
56
+ - **Two removal levers, and only one used to exist.** Historically the *only* way an order left
57
+ the report was `c_netsuiteInternalSalesOrderId` becoming non-NULL (set by the per-row recheck
58
+ when the NetSuite order is found). An order **closed/canceled on Office Depot's end** never gets
59
+ a NetSuite order, so it never gets that id, so it stuck on the report forever. The fix (2026-06)
60
+ added a **stage-based exclusion** so canceling/closing the ODP SO removes it.
61
+ - **To remove an order, mark the OFFICE DEPOT sales order — not the Compass SO.** The query
62
+ filters on `OfficeDepotSalesOrders.salesOrderStageId`; the Compass SO's stage is never read.
63
+ Setting the Compass `MA%` order's stage does nothing.
64
+ - **NULL-safe stage filter is mandatory.** Active orders normally have `salesOrderStageId = NULL`.
65
+ In SQL `NULL NOT IN (8, 9)` evaluates to *unknown* (not true), so a bare
66
+ `salesOrderStageId NOT IN (8, 9)` would drop **every NULL-stage order** — i.e. almost the whole
67
+ report. The condition must be `(salesOrderStageId IS NULL OR salesOrderStageId NOT IN (8, 9))`,
68
+ and it must be parenthesized because `AND` binds tighter than `OR`.
69
+ - **Don't fake the NetSuite id to hide an order.** Writing a bogus `c_netsuiteInternalSalesOrderId`
70
+ removes it from the report but corrupts the field for anyone who reads it later. Use the stage
71
+ lever instead.
72
+ - `$inProduction = true` switches the DB link to `db_prod_compass`; set false to test against
73
+ `db_beta_compass`.
74
+
75
+ ## Change history
76
+ - 2026-06-18 — Added stage-based exclusion so orders closed/canceled on ODP's end drop off the
77
+ report: `(salesOrderStageId IS NULL OR salesOrderStageId NOT IN (8, 9))` in the main query.
78
+ Removing specific stuck orders is then a data change (set the ODP SO's `salesOrderStageId` to
79
+ 8/9), not a code change. (bala)
80
+
81
+ ## Related docs
82
+ - [Compass USA profile](../../../clients/compass-usa/profile.md)
@@ -10,9 +10,7 @@ updated: 2026-06-18
10
10
  owners: ["bala"]
11
11
  files:
12
12
  - worker/crons/toga2/compass/update_salesorder_status_from_odp.php
13
- - worker/crons/toga2/compass/workflow/test_partial_in_transit_email.php
14
13
  - worker/crons/toga2/compasscanada/workflow/3_update_salesorder_status_from_grand_and_toy.php
15
- - worker/crons/toga2/compasscanada/workflow/test_partial_in_transit_email.php
16
14
  related: []
17
15
  ---
18
16
 
@@ -30,8 +28,6 @@ and earlier packages). The dynamic HTML is injected into a stored `EmailTemplate
30
28
  - `worker/crons/toga2/compass/update_salesorder_status_from_odp.php` — Compass USA prod cron.
31
29
  - `worker/crons/toga2/compasscanada/workflow/3_update_salesorder_status_from_grand_and_toy.php`
32
30
  — Compass Canada prod cron.
33
- - `*/workflow/test_partial_in_transit_email.php` — read-only prod test scripts for each client
34
- (email goes only to a test recipient, no DB writes). Use these to preview rendering.
35
31
  - Shared helper set in each cron: `getOrderFulfillmentData`, `buildItemRowsHtml`,
36
32
  `buildSectionHeaderHtml`, `buildPackageBlockHtml`, `buildTrackingUrl`, `buildOrderItemsHtml`.
37
33
 
@@ -106,14 +102,13 @@ the tracking number as emailed (it retries next run).
106
102
  `getOrderFulfillmentData` — same query as Compass USA. Canada was originally on the ASN chain
107
103
  because it had **zero `ItemFulfillmentItems`**; if that line-bridge data is not populated for a
108
104
  Canada order, `getOrderFulfillmentData` returns no packages and the cron falls back to the
109
- full email. Verify line-bridge data exists for Canada before relying on the partial path.
105
+ full email.
110
106
  - **Line bridge is moving-forward only** — it began populating ~2026-06-12; pre-cutoff tracking
111
107
  numbers are intentionally out of scope (driving-query date cutoff).
112
108
  - The driving query still uses the ASN chain (to find shipped orders + tracking) for **both**
113
109
  clients — that is correct and unrelated to the package-contents source.
114
- - Test scripts only ever email a test recipient and never write `c_dtInTransitEmailSent`.
115
- - **Not yet deployed.** These crons depend on the api2 POST scripted-API change and the
116
- `RecordScripts`/`AclRecordScripts` registration (prod done, beta pending) — see related docs.
110
+ - **Not yet deployed.** These crons depend on the api2 POST scripted-API change and its
111
+ `RecordScripts`/`AclRecordScripts` registration — see related docs.
117
112
 
118
113
  ## Change history
119
114
 
@@ -87,7 +87,7 @@ can keep using `sendEmail($api, ...)`.
87
87
  (previously the inactive case returned `false` and an SMTP failure was swallowed entirely).
88
88
  Any fire-and-forget caller now propagates that exception, which is intended (so an email is
89
89
  never silently marked as sent). Blast radius is every client, including the Compass/Quad
90
- `SalesOrder`/`ApprovalDecision` order-placed emails — verify on beta before shipping.
90
+ `SalesOrder`/`ApprovalDecision` order-placed emails.
91
91
 
92
92
  ## Change history
93
93
 
@@ -65,13 +65,9 @@ None — this is engine behavior. Per-client access is controlled by `AclRecordS
65
65
  - **Normal CRUD is unaffected.** The POST scripted block only fires when a POST Record Script
66
66
  is registered for the route; otherwise the request falls through to the usual create path
67
67
  (`if (!$isUsingScriptedCall && empty($routePairs))` / `locateRecord`).
68
- - **Registration is environment-specific.** The `RecordScripts` (Core) + `AclRecordScripts`
69
- (client) rows must exist in every environment the API reads. As of this writing they were
70
- run in **prod** for the email `sendEmail` script (Core + Client_Compass + Client_CompassCanada);
71
- **beta still needs them** before the POST path dispatches there.
72
- - **Deploys with `_underscore`.** api2 pulls `_underscore` at deploy; `http://api2` and
73
- `api.beta.togahub.com` resolve to deployed boxes (`/var/app/current`), not a local checkout —
74
- so testing the change requires deploying it, not just editing locally.
68
+ - **Registration is per-environment.** The `RecordScripts` (Core) + `AclRecordScripts`
69
+ (client) rows must exist in every environment the API reads; a missing POST `RecordScripts`
70
+ row means the POST path silently falls through to normal CRUD.
75
71
  - **CI commit policy.** Subject must be `TRUE-<ticket>: <Subject>` (≤80 chars, capitalized,
76
72
  imperative, no trailing period, more than one word).
77
73
 
@@ -5,7 +5,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
5
5
  ## 1.0 framework
6
6
 
7
7
  - **library** (Library) _(framework core)_ — 4 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
8
- - **worker** (Worker) — 7 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
8
+ - **worker** (Worker) — 9 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
9
9
  - **togadesk** (TOGa Desk) — 7 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
10
10
  - **togaview** (TOGa View) — 5 doc(s) → [1.0/apps/togaview/INDEX.md](1.0/apps/togaview/INDEX.md)
11
11
  - **webhook** (Webhook) — 1 doc(s) → [1.0/apps/webhook/INDEX.md](1.0/apps/webhook/INDEX.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.121",
3
+ "version": "1.0.122",
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",