toga-ai 1.0.818 → 1.0.819
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/compass-partial-in-transit-delivered-emails.md +87 -14
- package/knowledge/2.0/apps/worker2/features/oneuptime-worker2-monitoring.md +44 -2
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/compass-usa/INDEX.md +1 -0
- package/knowledge/clients/compass-usa/features/tracking-number-origin-and-asn-link.md +115 -0
- package/package.json +1 -1
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-16
|
|
10
10
|
owners: ["bala"]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/compass/update_salesorder_status_from_odp.php
|
|
@@ -18,8 +18,10 @@ files:
|
|
|
18
18
|
- library/app/client/compasscanada.php
|
|
19
19
|
- worker/schedules/cron.worker.sync.json
|
|
20
20
|
- worker2/Worker/Monitor/Compass.php
|
|
21
|
+
- worker/crons/toga2/compass/workflow/3b_import_strategic_systems_advance_shipping_notices.php
|
|
21
22
|
- dbchanges2/Client_Compass/2026-09-03 - compass_intransit_email_backlog_backfill.sql
|
|
22
23
|
related:
|
|
24
|
+
- ../../../clients/compass-usa/features/tracking-number-origin-and-asn-link.md
|
|
23
25
|
- ../../../clients/compass-usa/features/contact-email-resolution.md
|
|
24
26
|
- ../../../2.0/apps/worker2/features/tracking-status-refresh.md
|
|
25
27
|
- ../../../2.0/apps/worker2/features/oneuptime-worker2-monitoring.md
|
|
@@ -56,9 +58,11 @@ and earlier packages). The dynamic HTML is injected into a stored `EmailTemplate
|
|
|
56
58
|
## How it works
|
|
57
59
|
|
|
58
60
|
1. **Driving query** finds shipped sales orders and their tracking numbers (`computedStatusSlug
|
|
59
|
-
= 'shipped'`, `c_dtInTransitEmailSent IS NULL`, rolling date window
|
|
61
|
+
= 'shipped'`, `c_dtInTransitEmailSent IS NULL`, rolling date window on
|
|
62
|
+
**`AdvanceShippingNotices.dtCreated`**) via the ASN chain
|
|
60
63
|
(`AdvanceShippingNoticeItemUnits_TrackingNumbers`), one row per `TrackingNumbers.number`,
|
|
61
|
-
**newest first and capped** — see *Draining the candidate set*
|
|
64
|
+
**newest first and capped** — see *Draining the candidate set* and *Which clock the window
|
|
65
|
+
runs on* below.
|
|
62
66
|
2. Per tracking number: skip if the shipment is already finished — checked first against the
|
|
63
67
|
stored `TrackingNumbers.status` (free), and only then with a live carrier call.
|
|
64
68
|
3. **`getOrderFulfillmentData`** returns the order's packages (grouped by tracking number,
|
|
@@ -73,6 +77,13 @@ and earlier packages). The dynamic HTML is injected into a stored `EmailTemplate
|
|
|
73
77
|
`App_Api_Toga2::send(..., 'POST', '/email-templates/sendEmail', $payload, [], true)`.
|
|
74
78
|
6. On a successful (non-throwing) send, set `c_dtInTransitEmailSent = NOW()`.
|
|
75
79
|
|
|
80
|
+
**The two loops behave differently, on purpose.** Loop 1 stamps `c_dtInTransitEmailSent` on any
|
|
81
|
+
row whose `TrackingNumbers.status` is in `TRACKING_FINAL_STATUSES` (`DELIVERED`,
|
|
82
|
+
`RETURNED_TO_SENDER`) and **sends nothing**. Loop 2 emails the rest and stamps after a successful
|
|
83
|
+
send. So a parcel that was already delivered by the time the cron reaches it is **silently
|
|
84
|
+
retired, never announced** — expected behaviour, and the reason a genuine gap can show up as
|
|
85
|
+
"22 rows missed, 1 email actually owed".
|
|
86
|
+
|
|
76
87
|
### Draining the candidate set: two-pass, newest-first, bounded
|
|
77
88
|
|
|
78
89
|
**This is the load-bearing part of the cron. Getting it wrong took Compass USA in-transit emails to
|
|
@@ -95,9 +106,10 @@ mark the row done"* cron:
|
|
|
95
106
|
2. **Newest first, hard cap.** `ORDER BY MAX(TrackingNumbers.dtCreated) DESC` +
|
|
96
107
|
`LIMIT IN_TRANSIT_EMAIL_BATCH_SIZE` (**400**). Today's shipments are served before any backlog,
|
|
97
108
|
so a backlog can delay old mail but can never starve new mail.
|
|
98
|
-
3. **Rolling window, not a fixed date.** `IN_TRANSIT_EMAIL_LOOKBACK_DAYS
|
|
99
|
-
`dtCreated >= DATE_SUB(NOW(), INTERVAL
|
|
100
|
-
ever-growing scan; a rolling window is self-limiting.
|
|
109
|
+
3. **Rolling window, not a fixed date.** `IN_TRANSIT_EMAIL_LOOKBACK_DAYS` →
|
|
110
|
+
`dtCreated >= DATE_SUB(NOW(), INTERVAL N DAY)`. A hardcoded cutoff date guarantees an
|
|
111
|
+
ever-growing scan; a rolling window is self-limiting. **Which `dtCreated` matters enormously —
|
|
112
|
+
see the next section.** (USA is at 7 days as of 2026-09-15; Canada is still at 14.)
|
|
101
113
|
4. **Trust the stored status before paying for a carrier call.** `isTrackingStatusFinal(?string)`
|
|
102
114
|
short-circuits when `TrackingNumbers.status` is already `DELIVERED` or `RETURNED_TO_SENDER`
|
|
103
115
|
(`TRACKING_FINAL_STATUSES`). worker2's
|
|
@@ -105,6 +117,37 @@ mark the row done"* cron:
|
|
|
105
117
|
keeps that column current for every client, so this 1.0 cron should not re-ask the carrier about
|
|
106
118
|
a finished shipment.
|
|
107
119
|
|
|
120
|
+
### Which clock the window runs on: `AdvanceShippingNotices.dtCreated`, never `TrackingNumbers.dtCreated`
|
|
121
|
+
|
|
122
|
+
**This is the single easiest way to break Compass shipping emails, and it has now broken them
|
|
123
|
+
twice.** For Compass a `TrackingNumbers` row is created by the **NetSuite invoice sync**, not by
|
|
124
|
+
the ASN. The ASN shows up days or weeks later and is the only thing that links the tracking number
|
|
125
|
+
to an order and therefore to a recipient. So `TrackingNumbers.dtCreated` is the **invoice** date,
|
|
126
|
+
and a lookback anchored on it measures the wrong thing entirely. Full derivation:
|
|
127
|
+
[where a Compass TrackingNumbers row comes from](../../../clients/compass-usa/features/tracking-number-origin-and-asn-link.md).
|
|
128
|
+
|
|
129
|
+
- **Window (this cron):** filter on `AdvanceShippingNotices.dtCreated` — "how long has this been
|
|
130
|
+
*sendable*". Fixed 2026-09-16 at `update_salesorder_status_from_odp.php` line ~432;
|
|
131
|
+
`IN_TRANSIT_EMAIL_LOOKBACK_DAYS` stays at **7**, and the comment above the constant now says why.
|
|
132
|
+
- **Age / alarm (the monitor):** use `GREATEST(TrackingNumbers.dtCreated,
|
|
133
|
+
AdvanceShippingNotices.dtCreated)`.
|
|
134
|
+
|
|
135
|
+
**What went wrong.** On 2026-09-15 11:35 commit `81824325` ("Changing in transit email limit to 7")
|
|
136
|
+
cut the lookback from 14 to 7 days. Because the window was anchored on the invoice date,
|
|
137
|
+
**22 shipments** invoiced 2026-09-03 11:17–11:38 whose ASNs only landed 2026-09-15 16:00–17:00 fell
|
|
138
|
+
outside the 7-day window and were never emailed: SA136625, SA136627, SA136643, SA136673, SA136680,
|
|
139
|
+
SA136694, MR243688, MR244839, MR244893, MR244904, MR245219, MR245303, MR245337, MR245404, MR245409,
|
|
140
|
+
MR245420, MR245424, MR245429, MR245441, MR245454, MR245462, MR245463. Since 2026-08-01, **53**
|
|
141
|
+
tracking rows had their ASN land more than 7 days after the tracking row (max gap **13 days**), so
|
|
142
|
+
the 14-day window had been masking the same design flaw all along.
|
|
143
|
+
|
|
144
|
+
**Verified on prod before shipping:** the ASN-anchored window returns the same 22 rows, drops **0**
|
|
145
|
+
rows the old window covered, and yields exactly **1** real catch-up email — MR245462 (tracking
|
|
146
|
+
`1Z8696XA0392020614`, still `IN_TRANSIT`). The other 21 already read `DELIVERED`, so loop 1 stamps
|
|
147
|
+
them and sends nothing.
|
|
148
|
+
|
|
149
|
+
**Status: edited in the working tree, not yet committed or deployed (2026-09-16).**
|
|
150
|
+
|
|
108
151
|
### Resolving the recipient: a `Users` row is NOT required (USA)
|
|
109
152
|
|
|
110
153
|
The To address originally came only from `Users.email`, via `INNER JOIN Users ON Users.contactId =
|
|
@@ -227,14 +270,30 @@ the tracking number as emailed (it retries next run).
|
|
|
227
270
|
(e.g. `update_salesorder_status_from_gt.php`) with `schedules/cron.worker.sync.json` repointed.**
|
|
228
271
|
Never put `&` (or any shell metacharacter) in a cron filename. See
|
|
229
272
|
[tracing a 1.0 worker cron run](../workflows/tracing-a-worker-cron-run-in-production.md).
|
|
230
|
-
- **⚠
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
that never drains.
|
|
273
|
+
- **⚠ CORRECTED 2026-09-16 — invoice tracking numbers ARE in scope; they are the normal origin.**
|
|
274
|
+
This doc previously said `POST /v2/invoice-tracking-numbers` rows are bridged only through
|
|
275
|
+
`InvoiceTrackingNumbers` and "never the ASN chain". **That was wrong.** For Compass the invoice
|
|
276
|
+
sync is what *creates* nearly every `TrackingNumbers` row (98 POSTs / 100 rows in the 2026-09-03
|
|
277
|
+
11:15–11:40 window, against 4 ASNs created all day); the ASN attaches to those same rows days or
|
|
278
|
+
weeks later. What survives is the narrow version: a tracking number that **never** receives an
|
|
279
|
+
ASN can never be emailed, so a naive `TrackingNumbers WHERE c_dtInTransitEmailSent IS NULL` count
|
|
280
|
+
with no ASN join reads as a backlog that never drains. And because the row is born at invoice
|
|
281
|
+
time, `TrackingNumbers.dtCreated` must never be used as the window clock — see
|
|
282
|
+
[tracking-number origin](../../../clients/compass-usa/features/tracking-number-origin-and-asn-link.md).
|
|
283
|
+
|
|
284
|
+
- **⚠ Do NOT make `3b_import_strategic_systems_advance_shipping_notices.php` email for
|
|
285
|
+
pre-existing tracking numbers — tried 2026-09-16 and REVERTED.** 3b gates its in-transit email on
|
|
286
|
+
`$isTrackingNumberInserted` (line ~722 in `processOfficeDepotFile`, line ~1431 in
|
|
287
|
+
`processStrategicSystems`): it only emails when that run INSERTs the tracking row. When the
|
|
288
|
+
invoice sync already created the number, 3b still builds the ASN link but sends nothing, which
|
|
289
|
+
looks like the bug. Changing the gate to "has this customer been told"
|
|
290
|
+
(`c_dtInTransitEmailSent IS NULL`) was implemented and then backed out, because **3b only ever
|
|
291
|
+
sends the plain in-transit template** `e936aa4b-d5b6-41dd-be1a-938b2b27af9e`, while this catch-up
|
|
292
|
+
cron chooses between the **partial** template `d9b3f2a7-1c84-4e6d-9f50-3a7c1e8b2d46` (split order
|
|
293
|
+
/ items still being prepared) and the plain one. Firing 3b for pre-existing tracking numbers would
|
|
294
|
+
**silently downgrade every split order from the partial email to the plain email**. 3b was also
|
|
295
|
+
never the real break: the catch-up cron already covers that case inside its window, so fixing the
|
|
296
|
+
window was the correct and far smaller change.
|
|
238
297
|
- **Clear an in-transit backlog by STAMPING, not by emailing — and refresh statuses first.**
|
|
239
298
|
Decided 2026-09-03 while draining the outage backlog
|
|
240
299
|
(`dbchanges2/Client_Compass/2026-09-03 - compass_intransit_email_backlog_backfill.sql`):
|
|
@@ -292,6 +351,20 @@ the tracking number as emailed (it retries next run).
|
|
|
292
351
|
interceptor and retire this 1.0 cron.
|
|
293
352
|
|
|
294
353
|
## Change history
|
|
354
|
+
- 2026-09-16 — **Re-anchored the in-transit window on `AdvanceShippingNotices.dtCreated`** (was
|
|
355
|
+
`TrackingNumbers.dtCreated`, `update_salesorder_status_from_odp.php` line ~432);
|
|
356
|
+
`IN_TRANSIT_EMAIL_LOOKBACK_DAYS` stays 7 with a comment explaining why. Cause: commit `81824325`
|
|
357
|
+
(2026-09-15 11:35) cut the lookback 14 → 7 while the window still measured the **invoice** date,
|
|
358
|
+
so 22 shipments invoiced 2026-09-03 whose ASNs landed 2026-09-15 fell out of the window (only
|
|
359
|
+
MR245462 still owed a real email; the other 21 read DELIVERED). Verified on prod: 22 rows in,
|
|
360
|
+
0 rows lost, 1 catch-up email. **Corrected the old "invoice tracking numbers never touch the ASN
|
|
361
|
+
chain" gotcha** — the invoice sync is in fact the normal origin of Compass `TrackingNumbers` rows;
|
|
362
|
+
new doc
|
|
363
|
+
[tracking-number origin and the ASN link](../../../clients/compass-usa/features/tracking-number-origin-and-asn-link.md).
|
|
364
|
+
Recorded the **rejected** alternative of making 3b email for pre-existing tracking numbers (it
|
|
365
|
+
would downgrade split orders from the partial template to the plain one). Not yet committed or
|
|
366
|
+
deployed. (bala)
|
|
367
|
+
|
|
295
368
|
- 2026-09-10 — Recorded that a zero-line-item ASN is invisible to this cron (candidates come from
|
|
296
369
|
`AdvanceShippingNoticeItemUnits(_TrackingNumbers)`), the failure mode Compass Canada's G&T
|
|
297
370
|
importer produced until 2026-09-08. (bala)
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-16
|
|
10
10
|
owners: ["jcardinal", "mhammontree", "bala"]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Monitor/Compass.php
|
|
@@ -23,6 +23,7 @@ files:
|
|
|
23
23
|
- worker2/Config/sandbox-dev.ini
|
|
24
24
|
related:
|
|
25
25
|
- ./oneuptime-incident-clickup-sync.md
|
|
26
|
+
- ../../../clients/compass-usa/features/tracking-number-origin-and-asn-link.md
|
|
26
27
|
- ../../../1.0/apps/worker/features/compass-partial-in-transit-delivered-emails.md
|
|
27
28
|
- ./monitoring-framework.md
|
|
28
29
|
- ./all-client-email-queue-monitor.md
|
|
@@ -110,7 +111,9 @@ Each measures one Compass/Office Depot integration metric and pushes a decided a
|
|
|
110
111
|
`s3://agilant-as2/OfficeDepot/OUTBOX/` (skips subfolders + directory placeholders).
|
|
111
112
|
- **InTransitEmailQueue** (DB, added 2026-09-04) — watches the Compass USA **in-transit
|
|
112
113
|
shipping-notification queue**: `TrackingNumbers` rows on the ASN unit bridge with
|
|
113
|
-
`c_dtInTransitEmailSent IS NULL` inside a
|
|
114
|
+
`c_dtInTransitEmailSent IS NULL` inside a **7-day window on `AdvanceShippingNotices.dtCreated`**
|
|
115
|
+
(exactly the window the 1.0 sending cron uses — see *Age the backlog on the clock the work
|
|
116
|
+
actually starts on* below).
|
|
114
117
|
Status-page name *"Shipping Notification Emails (USA, In Transit)"*. See
|
|
115
118
|
[why it exists](#watch-the-step-before-the-log-row-exists) below.
|
|
116
119
|
- **MaRefreshOrderExceptions** (DB, added 2026-09-08) — Compass **`MA%` employee-refresh**
|
|
@@ -244,6 +247,36 @@ deliberately does not drive the alarm** — otherwise the monitor would sit red
|
|
|
244
247
|
cron structurally cannot send. That is the *"backlog monitors must exclude known-permanently-stranded
|
|
245
248
|
rows"* rule.
|
|
246
249
|
|
|
250
|
+
### Age the backlog on the clock the work actually starts on
|
|
251
|
+
|
|
252
|
+
`InTransitEmailQueue` originally did both of these off `TrackingNumbers.dtCreated`, and both were
|
|
253
|
+
wrong for Compass, because a Compass `TrackingNumbers` row is created by the **NetSuite invoice
|
|
254
|
+
sync** and only becomes sendable when the ASN lands days or weeks later
|
|
255
|
+
([derivation](../../../clients/compass-usa/features/tracking-number-origin-and-asn-link.md)).
|
|
256
|
+
Three corrections (a. committed as `82d614c` and deployed; b. and c. in the working tree, not yet
|
|
257
|
+
deployed as of 2026-09-16):
|
|
258
|
+
|
|
259
|
+
a. **`oldestQueuedHours` / `lateRows` now age from
|
|
260
|
+
`GREATEST(TrackingNumbers.dtCreated, AdvanceShippingNotices.dtCreated)`** — "how long has this
|
|
261
|
+
been *sendable*", not "how long since we invoiced it".
|
|
262
|
+
b. **`$LOOKBACK_DAYS` 14 → 7**, to match the cron's `IN_TRANSIT_EMAIL_LOOKBACK_DAYS`.
|
|
263
|
+
c. **The WHERE lookback filters on `AdvanceShippingNotices.dtCreated`**, so the monitor counts
|
|
264
|
+
exactly the set the cron will work on.
|
|
265
|
+
|
|
266
|
+
**Why it matters — the monitor cried wolf, then muddied a real incident.** Two HIGH alarms fired on
|
|
267
|
+
2026-09-15 (15:00:46 and 15:35:46) reporting "oldest 90h / 91h" for rows that had been sendable for
|
|
268
|
+
*minutes*. Replaying both moments with the `GREATEST(...)` expression gives **1h** and **0h** with
|
|
269
|
+
**0** late rows, i.e. OK. A third alarm on 2026-09-16 08:55 was a **real** gap, and the 14-vs-7 day
|
|
270
|
+
mismatch made it confusing: the monitor was counting 22 rows the cron would never look at.
|
|
271
|
+
|
|
272
|
+
**Rule: a backlog monitor must age and window on the same clock, and the same column, as the tier
|
|
273
|
+
that does the work.** Any drift between the two turns the monitor into noise in both directions —
|
|
274
|
+
false alarms on rows that are fine, and silence on rows the cron cannot see.
|
|
275
|
+
|
|
276
|
+
Rows with no `Users` row and no `ContactEmailAddresses` fallback (typically EDI-created MR orders)
|
|
277
|
+
can never be emailed at all; the monitor reports those as `unroutableRows` and deliberately never
|
|
278
|
+
alarms on them.
|
|
279
|
+
|
|
247
280
|
> **⚠ Known divergence (2026-09-04):** the monitor decides routable/unroutable from the
|
|
248
281
|
> `Users` join **only**, while the sending cron now also falls back to
|
|
249
282
|
> `Contacts` / `ContactEmailAddresses`. So some rows counted as `unroutableRows` are in fact
|
|
@@ -543,6 +576,15 @@ check for another client.
|
|
|
543
576
|
monitors must pass the region (see [Cloud S3 helpers](../../_underscore/features/cloud-s3-helpers.md)).
|
|
544
577
|
|
|
545
578
|
## Change history
|
|
579
|
+
- 2026-09-16 — **`InTransitEmailQueue` re-anchored onto the sendable clock.** `oldestQueuedHours` /
|
|
580
|
+
`lateRows` now age from `GREATEST(TrackingNumbers.dtCreated, AdvanceShippingNotices.dtCreated)`
|
|
581
|
+
(committed `82d614c`, deployed); `$LOOKBACK_DAYS` 14 → 7 and the WHERE lookback moved onto
|
|
582
|
+
`AdvanceShippingNotices.dtCreated` to match the 1.0 cron exactly (working tree, not deployed).
|
|
583
|
+
Cause: a Compass `TrackingNumbers` row is born from the NetSuite invoice sync, so the old age
|
|
584
|
+
measured invoice age — two false HIGH alarms on 2026-09-15 ("oldest 90h / 91h") replay as 1h / 0h
|
|
585
|
+
with 0 late rows. Added the general rule that a backlog monitor must age and window on the same
|
|
586
|
+
clock and column as the tier that does the work. (bala)
|
|
587
|
+
|
|
546
588
|
- 2026-09-08 — Added an **11th monitor, `MaRefreshOrderExceptions`** (Compass `MA%`
|
|
547
589
|
employee-refresh orders with no NetSuite order; `Core.CronJobs` id 74; DEGRADED >10 waiting /
|
|
548
590
|
>32h, HIGH >20 / >72h) — full derivation in
|
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)_ — 26 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
|
|
8
|
-
- **worker** (Worker) —
|
|
8
|
+
- **worker** (Worker) — 36 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
|
|
9
9
|
- **dbchanges** (Database Changes) _(framework core)_ — 1 doc(s) → [1.0/apps/dbchanges/INDEX.md](1.0/apps/dbchanges/INDEX.md)
|
|
10
10
|
- **worker1.5** (Worker 1.5) — 0 doc(s) → [1.0/apps/worker1.5/INDEX.md](1.0/apps/worker1.5/INDEX.md)
|
|
11
11
|
- **togadesk** (TOGa Desk) — 13 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
| [Persona Model & Levy-Sector Gating (worker2 PEOPLE cron)](features/persona-model-and-levy-gating.md) | 2.0 | Compass USA catalogue visibility is driven by **personas** in `Client_Compass`. |
|
|
24
24
|
| [Compass Sales-Order Line-Number Renumbering (and why it hangs off the SO hooks)](features/sales-order-line-renumbering.md) | 2.0 | Compass sales-order lines must stay numbered **1..N with no gaps** after any add, edit, or delete — downstream MITS/PO linking reads `lineNumber` as an identity |
|
|
25
25
|
| [Stranded Approval Reassignment (repointing approvals off dead duplicate Compass Users rows)](features/stranded-approval-reassignment.md) | 2.0 | A Compass employee who leaves and comes back after more than `CONTACT_UNLINK_GRACE_DAYS` (5) is **inserted as a brand-new `Users` row** by the PEOPLE importer i |
|
|
26
|
+
| [Compass: where a `TrackingNumbers` row comes from (invoice sync, NOT the ASN) and how it reaches a customer](features/tracking-number-origin-and-asn-link.md) | 1.0 | For Compass USA a `TrackingNumbers` row is **born from the NetSuite invoice sync**, not from the ASN. |
|
|
26
27
|
| [Compass USA](profile.md) | 2.0 | Compass USA is a TOGA client running a multi-tier supply-chain commerce operation. |
|
|
27
28
|
| [Compass USA — reversing the 199-item catalog fold (MITS outage recovery)](workflows/catalog-fold-reversal.md) | 2.0 | The **root cause** of the Compass MITS transmission outage (the `assetType`-NULL and duplicate-vendor symptoms) was an ad-hoc cross-client **duplicate-items mer |
|
|
28
29
|
| [Changing what is IN a Compass kit (adding / swapping a BundleItems line)](workflows/changing-a-kit-line-item.md) | 2.0 | "Add this fee/component to every kit that has X" is a recurring Compass request, and it looks like one `INSERT` into `Client_Compass.BundleItems`. |
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Compass: where a `TrackingNumbers` row comes from (invoice sync, NOT the ASN) and how it reaches a customer
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: worker
|
|
5
|
+
project: Worker
|
|
6
|
+
client: compass-usa
|
|
7
|
+
type: client-feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-09-16
|
|
10
|
+
owners: ["bala"]
|
|
11
|
+
files:
|
|
12
|
+
- library/app/api/toga2.php
|
|
13
|
+
- worker/crons/toga2/netsuite/common_sync_togasupply.php
|
|
14
|
+
- worker/crons/toga2/netsuite/sync_togasupply_compass_usa.php
|
|
15
|
+
- worker/crons/toga2/compass/update_salesorder_status_from_odp.php
|
|
16
|
+
- worker2/Worker/Monitor/Compass.php
|
|
17
|
+
related:
|
|
18
|
+
- ../../../1.0/apps/worker/features/compass-partial-in-transit-delivered-emails.md
|
|
19
|
+
- ../../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md
|
|
20
|
+
- ./asn-to-item-fulfillment.md
|
|
21
|
+
- ./contact-email-resolution.md
|
|
22
|
+
- ../../../2.0/apps/worker2/features/oneuptime-worker2-monitoring.md
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Summary
|
|
26
|
+
|
|
27
|
+
For Compass USA a `TrackingNumbers` row is **born from the NetSuite invoice sync**, not from the
|
|
28
|
+
ASN. The ASN turns up days or weeks later and is the **only** thing that ties that tracking number
|
|
29
|
+
to a sales order, and therefore to a customer's email address.
|
|
30
|
+
|
|
31
|
+
The consequence, and the reason this doc exists: **`TrackingNumbers.dtCreated` is the invoice
|
|
32
|
+
date.** It is the wrong clock to time any shipping notification, backlog age, or SLA from. Anything
|
|
33
|
+
that asks *"when did we get this tracking number?"* and means *"when could we first have told the
|
|
34
|
+
customer?"* is wrong for Compass by up to two weeks.
|
|
35
|
+
|
|
36
|
+
## Key files / entry points
|
|
37
|
+
|
|
38
|
+
- `worker/crons/toga2/netsuite/sync_togasupply_compass_usa.php` → `common_sync_togasupply.php` →
|
|
39
|
+
`App_Api_Toga2::syncInvoiceFromNetsuite()` (`library/app/api/toga2.php`, function opens at
|
|
40
|
+
line 3203; the tracking-number POST is at line 3662).
|
|
41
|
+
- The POST is `POST /v2/invoice-tracking-numbers`, payload shaped as a nested object:
|
|
42
|
+
`{"invoice":{"uuid":"…"},"trackingNumber":{"number":"…","shippingCarrier":{…}}}`. api2 creates
|
|
43
|
+
the `TrackingNumbers` row **from that nested object** — so the row exists long before any ASN.
|
|
44
|
+
- `worker/crons/toga2/compass/update_salesorder_status_from_odp.php` — the consumer that must use
|
|
45
|
+
the ASN clock, not this one.
|
|
46
|
+
- `worker2/Worker/Monitor/Compass.php` → `InTransitEmailQueue` — the monitor over the same queue.
|
|
47
|
+
|
|
48
|
+
## How it works
|
|
49
|
+
|
|
50
|
+
1. The invoice sync imports a NetSuite invoice and POSTs each of its tracking numbers. That
|
|
51
|
+
**creates** `TrackingNumbers`, stamping `dtCreated` = the moment of the invoice import.
|
|
52
|
+
2. The invoice does **not** carry the order link. `Invoices.salesOrderId` is NULL on all of the
|
|
53
|
+
affected invoices, and across Compass since 2026-08-01 it is NULL on **1,868 of 2,022**
|
|
54
|
+
invoices. So at this point the tracking number belongs to nobody.
|
|
55
|
+
3. Later (hours to weeks) the ASN arrives, and **that** is what links the tracking number to a
|
|
56
|
+
customer. The only path is:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
TrackingNumbers
|
|
60
|
+
→ AdvanceShippingNoticeItemUnits_TrackingNumbers
|
|
61
|
+
→ AdvanceShippingNoticeItemUnits
|
|
62
|
+
→ AdvanceShippingNoticeItems
|
|
63
|
+
→ AdvanceShippingNotices.purchaseOrderId
|
|
64
|
+
→ SalesOrders_PurchaseOrders
|
|
65
|
+
→ SalesOrders
|
|
66
|
+
→ Contacts / Users (the email address)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
4. Only once that chain exists can the in-transit email be built or sent.
|
|
70
|
+
|
|
71
|
+
### How big the gap is (measured on prod)
|
|
72
|
+
|
|
73
|
+
- **2026-09-03, 11:15:56–11:40:20** — `Logs_Compass.Api` shows **98** `POST /v2/invoice-tracking-numbers`
|
|
74
|
+
from the worker instances. **100** `TrackingNumbers` rows were created in that window, while only
|
|
75
|
+
**4** ASNs were created in the whole of 09-03.
|
|
76
|
+
- Since **2026-08-01**, **53** tracking rows had their ASN land **more than 7 days** after the
|
|
77
|
+
tracking row, with a **maximum gap of 13 days**.
|
|
78
|
+
- The concrete failure: 22 shipments invoiced 2026-09-03 11:17–11:38 whose ASNs only landed
|
|
79
|
+
2026-09-15 16:00–17:00.
|
|
80
|
+
|
|
81
|
+
## Data model
|
|
82
|
+
|
|
83
|
+
- `TrackingNumbers.dtCreated` — **invoice import time** for Compass. Not the ship time, not the
|
|
84
|
+
time the customer became notifiable.
|
|
85
|
+
- `AdvanceShippingNotices.dtCreated` — the first moment a tracking number is attached to an order.
|
|
86
|
+
**This is the notifiable-from clock.**
|
|
87
|
+
- `Invoices.salesOrderId` — usually NULL for Compass; do not rely on it to reach the order.
|
|
88
|
+
- `InvoiceTrackingNumbers` — the invoice-side bridge. A row can sit on this bridge *and* later on
|
|
89
|
+
the ASN unit bridge; the two are not mutually exclusive.
|
|
90
|
+
|
|
91
|
+
## Gotchas / known issues
|
|
92
|
+
|
|
93
|
+
- **⚠ Never window, age, or alarm a Compass shipping feature on `TrackingNumbers.dtCreated`.** Use
|
|
94
|
+
`AdvanceShippingNotices.dtCreated` for a window, and `GREATEST(TrackingNumbers.dtCreated,
|
|
95
|
+
AdvanceShippingNotices.dtCreated)` when you need "how long has this been sendable". Getting this
|
|
96
|
+
wrong produced both a real missed-email gap and two false HIGH alarms — see
|
|
97
|
+
[Compass in-transit emails](../../../1.0/apps/worker/features/compass-partial-in-transit-delivered-emails.md).
|
|
98
|
+
- **⚠ Correction to an earlier belief.** It was previously recorded that invoice-created tracking
|
|
99
|
+
numbers "never touch the ASN chain" and are therefore out of scope for in-transit email. That is
|
|
100
|
+
wrong. They are the *normal* origin for Compass tracking numbers; the ASN chain simply attaches
|
|
101
|
+
to them later. What is true is the narrower statement: a tracking number that **never** gets an
|
|
102
|
+
ASN can never be emailed, and a naive `TrackingNumbers WHERE c_dtInTransitEmailSent IS NULL`
|
|
103
|
+
count (with no ASN join) reads those as a backlog that never drains.
|
|
104
|
+
- **A tracking number with no ASN has no customer.** There is no fallback route from invoice to
|
|
105
|
+
contact, because `Invoices.salesOrderId` is NULL. Waiting for the ASN is the design, not a bug.
|
|
106
|
+
- Credentials for the sync ride on `App_Api_Toga2` class constants (e.g.
|
|
107
|
+
`App_Api_Toga2::CLIENT_UUID_COMPASS`) — reference the constant, never the value.
|
|
108
|
+
|
|
109
|
+
## Change history
|
|
110
|
+
- 2026-09-16 — First write-up. Established that Compass `TrackingNumbers` rows are created by the
|
|
111
|
+
NetSuite invoice sync (`syncInvoiceFromNetsuite` → `POST /v2/invoice-tracking-numbers`), that
|
|
112
|
+
`Invoices.salesOrderId` is NULL on 1,868 of 2,022 Compass invoices since 2026-08-01, and that the
|
|
113
|
+
ASN chain is the only route from a tracking number to a customer. Measured the invoice→ASN gap
|
|
114
|
+
(53 rows over 7 days since 2026-08-01, max 13 days) and corrected the earlier "invoice tracking
|
|
115
|
+
numbers never touch the ASN chain" claim. (bala)
|
package/package.json
CHANGED