toga-ai 1.0.98 → 1.0.100
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/INDEX.md +1 -0
- package/knowledge/1.0/apps/worker/workflows/onboarding-client-to-netsuite-togasupply-sync.md +116 -0
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/compass-usa/INDEX.md +1 -1
- package/knowledge/clients/compass-usa/features/asn-to-item-fulfillment.md +39 -1
- package/package.json +1 -1
|
@@ -5,3 +5,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
6
|
| [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 |
|
|
7
7
|
| [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 |
|
|
8
|
+
| [Onboarding a Client to the NetSuite TOGa Supply Sync](workflows/onboarding-client-to-netsuite-togasupply-sync.md) | How to add a new TOGa 2 client to the per-client NetSuite → TOGa Supply importer (`worker/crons/toga2/netsuite/`). | worker/crons/toga2/netsuite/sync_togasupply.php, worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/schedules/cron.worker.sync.json, dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql |
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Onboarding a Client to the NetSuite TOGa Supply Sync
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: worker
|
|
5
|
+
project: Worker
|
|
6
|
+
client: shared
|
|
7
|
+
type: workflow
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-16
|
|
10
|
+
owners: ["dfranks"]
|
|
11
|
+
files:
|
|
12
|
+
- worker/crons/toga2/netsuite/sync_togasupply.php
|
|
13
|
+
- worker/crons/toga2/netsuite/common_sync_togasupply.php
|
|
14
|
+
- worker/schedules/cron.worker.sync.json
|
|
15
|
+
- dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql
|
|
16
|
+
related: []
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Summary
|
|
20
|
+
How to add a new TOGa 2 client to the per-client NetSuite → TOGa Supply importer
|
|
21
|
+
(`worker/crons/toga2/netsuite/`). Each client gets a thin wrapper cron that sets its
|
|
22
|
+
`CLIENT_CONFIGURATION` + `IS_ENABLED_*` flags and then `require_once`s the shared
|
|
23
|
+
`common_sync_togasupply.php`, which builds all lookups and runs the 6 sync sections
|
|
24
|
+
(Sales Orders, Purchase Orders, Invoices, Item Receipts, Item Fulfillments, Inventory
|
|
25
|
+
Adjustments). Reads come from production NetSuite (REST/SuiteQL); writes go to the TOGa2
|
|
26
|
+
API (`App_Api_Toga2::send`), which lands in that client's `Client_<Name>` database.
|
|
27
|
+
|
|
28
|
+
## Steps
|
|
29
|
+
1. **Create the wrapper** `worker/crons/toga2/netsuite/sync_togasupply_<client>.php`,
|
|
30
|
+
modeled exactly on an existing one (e.g. `sync_togasupply_compass_usa.php`). Set
|
|
31
|
+
`CLIENT_CONFIGURATION` (`client`, `isParentCustomer`, `api`, `secret`,
|
|
32
|
+
`importCustomerPurchaseOrdersOnVendor`) and the constants. Shared constants are the same
|
|
33
|
+
across clients: `NETSUITE_CUSTOM_FIELD_ID__END_USER_CUSTOMER = 3149`,
|
|
34
|
+
`SHIPPING_LOCATION_TYPE_UUID = 527a3995-…`, `WAREHOUSE_LOCATION_TYPE_UUID = 78c24f51-…`,
|
|
35
|
+
and `importCustomerPurchaseOrdersOnVendor = 10ed18ec-0999-53d9-f9db-5892d90f09d4`.
|
|
36
|
+
Get `client` from `Core.Clients` (by name); get `api`/`secret` from
|
|
37
|
+
`Client_<Name>.Apis`. Lint with `C:\xampp7\php\php.exe -l` (prod is PHP 7.2).
|
|
38
|
+
2. **Determine `isParentCustomer` by probing NetSuite — do not assume.** It controls whether
|
|
39
|
+
the common body calls `listChildCustomers()` (parent) or `fetchCustomerById()` (flat).
|
|
40
|
+
`listChildCustomers(N)` runs `SELECT id FROM customer WHERE parent = N`. If the client's
|
|
41
|
+
NetSuite customer has **zero children**, `isParentCustomer` MUST be `false`, or the sync
|
|
42
|
+
builds an empty customer list, registers nothing, and imports nothing (silent no-op). See
|
|
43
|
+
Gotchas.
|
|
44
|
+
3. **Seed the Parameters table** (see the dedicated section below) — REQUIRED or the sync
|
|
45
|
+
aborts on its first parameter read.
|
|
46
|
+
4. **Add the schedule entry** in `worker/schedules/cron.worker.sync.json`: all
|
|
47
|
+
`sync_togasupply_*` jobs run `*/5 * * * *`, `active: 1`. The cron only runs once a
|
|
48
|
+
schedule entry references the file and the worker is redeployed.
|
|
49
|
+
5. **Deploy order matters:** the Parameters seed must hit prod `Client_<Name>` **before or
|
|
50
|
+
with** the cron going live, or the sync 404-aborts (and throws a Sentry error) every 5
|
|
51
|
+
minutes.
|
|
52
|
+
|
|
53
|
+
## Parameters seed (the required, easily-missed step)
|
|
54
|
+
The sync reads/writes per-client sync state via the TOGa2 API `/parameters` endpoint, which
|
|
55
|
+
is **update-only** — a `GET`/`PUT` on a non-existent key returns `404 EV-6`, and because
|
|
56
|
+
`App_Api_Toga2::send()` defaults `$throwExceptionOnApiError = true`, that 404 **throws and
|
|
57
|
+
aborts the entire run** before any record is processed. The `startModeIteration()` ternary
|
|
58
|
+
(`isSuccess ? value : MIN_DATETIME`) looks like it tolerates a missing key, but it never
|
|
59
|
+
gets there because `send()` throws first. Every live client (e.g. Compass) was seeded at
|
|
60
|
+
onboarding; a new client has none.
|
|
61
|
+
|
|
62
|
+
The `Parameters` *table* already exists in every `Client_<Name>` DB — you only seed **rows**.
|
|
63
|
+
Add a `dbchanges2/Client_<Name>/<date> - NetsuiteSyncParameters.sql` migration modeled on
|
|
64
|
+
`dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql`, inserting all **12** keys:
|
|
65
|
+
- 6 × `NETSUITE_LAST_SYNC_DATETIME_{SALES_ORDERS,PURCHASE_ORDERS,INVOICES,ITEM_RECEIPTS,ITEM_FULFILLMENTS,INVENTORY_ADJUSTMENTS}`
|
|
66
|
+
→ a start datetime. This sets how far back the initial backfill pulls (5-day windows, every
|
|
67
|
+
5 min). Pick deliberately: `2018-01-01` = full history; a recent date = go-forward only.
|
|
68
|
+
- 6 × `NETSUITE_EXECUTION_MODE_*` → `864000-IDLE` (the interval is clamped to
|
|
69
|
+
`MAX_TIME_WINDOW_TO_FETCH_FROM_NETSUITE_SECONDS = 432000` on first run, so the exact
|
|
70
|
+
seed interval is not critical; `IDLE` is what matters).
|
|
71
|
+
|
|
72
|
+
Use `(UUID(), '<key>', '<value>')` rows — `Parameters.uuid` is required and unique.
|
|
73
|
+
dbchanges2 files use the unqualified `Parameters` table name (deployer selects the DB from
|
|
74
|
+
the directory; no `USE`).
|
|
75
|
+
|
|
76
|
+
## Systems involved
|
|
77
|
+
- `worker` cron tier (1.0) — the wrapper + `common_sync_togasupply.php`.
|
|
78
|
+
- Production NetSuite (account `1095849`) via `App_Api_Netsuite_Rest` (SuiteQL/REST) — reads.
|
|
79
|
+
- TOGa2 API (`App_Api_Toga2`) — writes to `Client_<Name>` (+ logs to `Logs_<Name>`).
|
|
80
|
+
- `dbchanges2/Client_<Name>/` — the Parameters seed migration.
|
|
81
|
+
|
|
82
|
+
## Local testing
|
|
83
|
+
- The worker's `[api] _` and api2's `[database]/[databaseClient]/[database1]` on the dev
|
|
84
|
+
laptop point at local (`http://api2/v2` → local Apache/api2 → localhost MySQL). NetSuite is
|
|
85
|
+
the **production** account but reads are read-only.
|
|
86
|
+
- A local end-to-end run needs the client's schemas present locally: import both
|
|
87
|
+
`Client_<Name>` AND `Logs_<Name>` from the read replicas (client2 / logs clusters) into
|
|
88
|
+
local XAMPP. `Logs_<Name>` only needs the **schema** (`mysqldump --no-data`) — its API-log
|
|
89
|
+
history can be many GB; api2 connects to it (link `ClientLogs`) on every request and 500s
|
|
90
|
+
with "Unknown database 'logs_<name>'" if absent.
|
|
91
|
+
- Run the cron in place with `C:\xampp8\php\php.exe` from the `worker/` dir; the autoloader
|
|
92
|
+
walks up to `worker/_` for `__APPROOT__`. `App_Api_Netsuite_Rest::authenticate()` +
|
|
93
|
+
`App_ApiTransaction->setLogging(false)` is the laptop-safe NetSuite pattern (see
|
|
94
|
+
`test/@dave/nsq.php`, a read-only SuiteQL runner).
|
|
95
|
+
- Verified behavior: empty window → 0 writes, checkpoint advances (clean no-op); record
|
|
96
|
+
already present → `PUT` update (idempotent, keyed on `c_netsuiteInternalSalesOrderId`, no
|
|
97
|
+
dupes); record absent → `POST` insert. Checkpoints (`NETSUITE_LAST_SYNC_DATETIME_*`)
|
|
98
|
+
advance by one window and `NETSUITE_EXECUTION_MODE_*` flips back to `…-IDLE` when a section
|
|
99
|
+
finishes cleanly.
|
|
100
|
+
|
|
101
|
+
## Edge cases & escalation
|
|
102
|
+
- **`isParentCustomer` wrong** → silent no-op (true on a childless customer) or missed
|
|
103
|
+
end-user matching. Always probe NetSuite first.
|
|
104
|
+
- **Parameters not seeded in prod** → sync throws every 5 min; surfaces as a `worker1` Sentry
|
|
105
|
+
error and zero imports.
|
|
106
|
+
- **Missing catalog item** on an incoming order may throw mid-run depending on the section;
|
|
107
|
+
the checkpoint does not advance past a throwing window, so it self-heals once the dependency
|
|
108
|
+
exists.
|
|
109
|
+
|
|
110
|
+
## Change history
|
|
111
|
+
- 2026-06-16 — Documented the onboarding process after adding Quad (TRUE-79575): wrapper +
|
|
112
|
+
schedule + the required 12-key Parameters seed; captured the `isParentCustomer` NetSuite
|
|
113
|
+
probe and the local Client_/Logs_ schema requirement. (dfranks)
|
|
114
|
+
|
|
115
|
+
## Related docs
|
|
116
|
+
- [Forecast2 NetSuite reconciliation](../features/forecast2-netsuite-reconciliation.md)
|
package/knowledge/INDEX.md
CHANGED
|
@@ -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) —
|
|
8
|
+
- **worker** (Worker) — 5 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)
|
|
@@ -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, api2/Component/Api/Cxml/Cxml.php, dbchanges2/Client_Compass/2026-06-11 - AsnItemTrackingNumberAcl.sql, dbchanges2/Client_Compass/2026-06-15b - BackfillSA132781ItemFulfillmentTracking.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 |
|
|
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,13 +5,17 @@ project: _Underscore
|
|
|
5
5
|
client: compass-usa
|
|
6
6
|
type: client-feature
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-06-
|
|
8
|
+
updated: 2026-06-16
|
|
9
9
|
owners: [jcardinal]
|
|
10
10
|
files:
|
|
11
11
|
- _underscore/Model/Compass/AdvanceShippingNotice.php
|
|
12
|
+
- _underscore/Model/Compass/PurchaseOrder.php
|
|
12
13
|
- api2/Component/Api/Cxml/Cxml.php
|
|
13
14
|
- dbchanges2/Client_Compass/2026-06-11 - AsnItemTrackingNumberAcl.sql
|
|
14
15
|
- dbchanges2/Client_Compass/2026-06-15b - BackfillSA132781ItemFulfillmentTracking.sql
|
|
16
|
+
- dbchanges2/Client_Compass/2026-06-16 - CleanupSA132763CrossLineTracking.sql
|
|
17
|
+
- dbchanges2/Client_Compass/2026-06-16b - CleanupSA132743CrossLineTracking.sql
|
|
18
|
+
- dbchanges2/Client_Compass/2026-06-16c - BackfillSA132763C40QYUCTracking.sql
|
|
15
19
|
related:
|
|
16
20
|
- ../../../2.0/apps/_underscore/features/recursive-item-fulfillments.md
|
|
17
21
|
---
|
|
@@ -143,9 +147,43 @@ Compass Canada (`Model/Compass/Canada/`) is a separate sub-client. The Compass c
|
|
|
143
147
|
number) straight into queries. These are now passed through `_Database::escape()`.
|
|
144
148
|
- Separate latent bug in the 1.0 worker: the Strategic Systems cron's no-serials branch
|
|
145
149
|
builds `$itemLevelTrackingNumbers` but never attaches it to the ASN payload.
|
|
150
|
+
- **Cross-line tracking contamination from bad SOI↔POI bridge rows (root cause, fixed
|
|
151
|
+
forward 2026-06-11):** `postPost` resolves which IFI(s) a tracking number attaches to by
|
|
152
|
+
joining `AdvanceShippingNoticeItems → SalesOrderItems_PurchaseOrderItems → SalesOrderItems`.
|
|
153
|
+
It trusts that bridge completely, so a wrong bridge row silently sends one ASN's tracking
|
|
154
|
+
onto an unrelated part's IFI. Two distinct pre-fix bugs produced bad bridge rows on Compass
|
|
155
|
+
SA orders: (1) `_Model_Compass_PurchaseOrder` linked SO↔PO items by matching
|
|
156
|
+
`SalesOrderItems.lineNumber = PurchaseOrderItems.lineNumber` — but a PO item's lineNumber is
|
|
157
|
+
**PO-local** (each PO starts at 1), so when one SO is split across multiple POs the PO line
|
|
158
|
+
numbers collide with unrelated SO lines (e.g. SA132743: PO `50305071-1` line 1 = 910-006272
|
|
159
|
+
wrongly linked to SO line 1 = C40QYUC). Fixed in `_underscore` commit `ec935478` (2026-06-11,
|
|
160
|
+
"Only link SO/PO items for MR orders") — the lineNumber join is now MR-only; SA orders link
|
|
161
|
+
via `createdFromSalesOrderItem` (item identity). (2) The 1.0 worker cron
|
|
162
|
+
`3a_import_office_depot_purchase_orders.php` paired API-returned PO items to SOIs by array
|
|
163
|
+
index, producing off-by-one neighbor links (fixed forward to pair by lineNumber; see
|
|
164
|
+
SA132763 cleanup file header). **Diagnosing:** a bridge row is provably wrong when its
|
|
165
|
+
`SalesOrderItems.itemId <> VendorItems.itemId` (the linked SOI's product differs from the PO
|
|
166
|
+
item's actual product). The `2026-06-16*` cleanup migrations delete exactly those rows plus
|
|
167
|
+
the tracking they spilled, scoped to one order.
|
|
168
|
+
- **Retroactive cleanup MUST be paired with a backfill — a cleanup-only repair leaves the
|
|
169
|
+
line blank.** Removing the *wrong* tracking from a contaminated IFI does not restore the
|
|
170
|
+
*correct* one: the correct number often never reached the IF side (the SO was fulfilled
|
|
171
|
+
out-of-band before the 2026-06-15 reconcile fix, and the cleanup adds nothing). The original
|
|
172
|
+
`2026-06-16 - CleanupSA132763CrossLineTracking.sql` was cleanup-only and left C40QYUC on
|
|
173
|
+
SA132763 with no tracking at all; `2026-06-16c` backfilled it. When repairing an order, also
|
|
174
|
+
backfill each affected IFI's correct tracking (the tn carried by an ASN item whose
|
|
175
|
+
`VendorItems.itemId` matches the IFI's SOI item) at both item and package level, guarded by
|
|
176
|
+
`NOT EXISTS`. ~116 PO items across other Compass orders share the off-by-one fingerprint and
|
|
177
|
+
are not yet repaired — a broader reviewed backfill is still outstanding.
|
|
146
178
|
|
|
147
179
|
## Change history
|
|
148
180
|
Dated one-liners, newest first.
|
|
181
|
+
- 2026-06-16 — Identified cross-line tracking contamination root cause: bad
|
|
182
|
+
`SalesOrderItems_PurchaseOrderItems` rows (PO-local lineNumber collision in
|
|
183
|
+
`_Model_Compass_PurchaseOrder`, fixed forward 2026-06-11 `ec935478`; and the worker ODP-PO
|
|
184
|
+
cron's array-index pairing). Repaired SA132743 (`2026-06-16b`, cleanup + backfill) and
|
|
185
|
+
backfilled SA132763's C40QYUC (`2026-06-16c`) after finding the `2026-06-16` SA132763 cleanup
|
|
186
|
+
was cleanup-only and left the line blank. (jcardinal)
|
|
149
187
|
- 2026-06-15 — `postPost` + helpers refactored so **UUID is the reference field**, dropping all
|
|
150
188
|
reliance on the integer `id` returned from `internalApiRequest` (not always present). Helper
|
|
151
189
|
returns are uuid-only; `$trackingTargets`/`$packageTrackingByItemFulfillment` are uuid-keyed;
|
package/package.json
CHANGED