toga-ai 1.0.628 → 1.0.630
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/features/compass-ma-sales-order-exception-report.md +4 -2
- package/knowledge/1.0/apps/worker/features/netsuite-sales-order-sales-rep-sourcing.md +117 -0
- package/knowledge/1.0/apps/worker/features/staples-cxml-order-import.md +19 -2
- package/knowledge/1.0/standards/backend-php.md +12 -1
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/compass-usa/workflows/odp-order-pipeline-to-netsuite.md +39 -2
- package/knowledge/clients/staples/profile.md +8 -2
- package/knowledge/sessions/2026-08-20-talos-shared-component-apeterson.md +72 -0
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
| [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/update_salesorder_status_from_odp.php, worker/crons/toga2/compasscanada/workflow/3_update_salesorder_status_from_grand_and_toy.php, worker/crons/toga2/compasscanada/send_delivered_email.php, worker/crons/toga2/compasscanada/workflow/test_partial_in_transit_email.php, worker/crons/toga2/compasscanada/workflow/test_partial_delivered_email.php, library/app/client/compasscanada.php |
|
|
9
9
|
| [Elite TOGA 2.0 → TOGaDeskSupport Standalone Attachment Sync](features/elite-togadesk-attachment-sync.md) | `sync_togadesk_elite_attachments.php` is a standalone cron (every 5 minutes) that syncs file attachments from TOGA 2.0 into TOGaDeskSupport for Elite. | worker/crons/toga2/elite/sync_togadesk_elite_attachments.php, worker/crons/toga2/elite/test_sync_togadesk_elite_attachments.php |
|
|
10
10
|
| [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/checker.php, worker2/Component/Forecast/SaleImport/SaleImport.php, test/@dave/looper.php, test/@dave/reconcile_netsuite_totals.php, test/@dave/fixer.php, test/@dave/analyze_netsuite_forecast_diff.php, test/@dave/trueup_sales.php, test/@dave/reconcile_drift_2023plus.php, test/@dave/probe_invoice_gap_2026.php, test/@dave/probe_creditmemo_gap_detail.php, test/@dave/trueup_open_orders.php, test/@dave/loop_trueup_open_orders.php, test/@dave/trueup_opportunities.php, test/@dave/probe_sales_gap_direct.php, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, test/@dave/probe_drift_so_dates.php, test/@dave/probe_profit_invoices.php, test/@dave/probe_profit_gap.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php, worker/crons/toga2/forecast2/periodic_forecast_discrepancy_fix_open_orders.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/schedules/cron.worker.infrastructure.json |
|
|
11
|
+
| [NetSuite Sales Order Sales Rep Sourcing (Staples & ODP EDI orders)](features/netsuite-sales-order-sales-rep-sourcing.md) | How the **sales rep** on a NetSuite Sales Order is determined for the two 1.0 `worker` EDI order-creation integrations (Staples cXML and Compass/ODP EDI). | worker/crons/toga2/compass/workflow/5_create_netsuite_sales_orders_from_office_depot_purchase_orders.php, worker/crons/sync/staples/sync_staples_cxml.php, test/@Mark/NetSuite/TRUE_80451_customer_salesrep_diag.php |
|
|
11
12
|
| [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/crons/toga2/netsuite/sync_togasupply_elite.php, worker/schedules/cron.worker.sync.json, dbchanges2/_modules/netsuite/2026-04-01 - Parameters.sql, library/app/api/toga2.php, library/app/api/netsuite/rest.php, library/app/framework.php, library/app/systemmonitor/netsuiteintegration.php, test/@srija/Elite Testing/Service Requests/test_sync_togasupply_elite_section.php, test/@srija/Elite Testing/Service Requests/test_diagnose_togasupply_elite.php |
|
|
12
13
|
| [OneUptime Server monitor + disk/memory hygiene on the 1.0 worker EB host](features/oneuptime-server-monitor-host-hygiene.md) | The 1.0 `agilant-worker` EB environment runs on the **legacy Amazon Linux 1 PHP 7.2 platform** (Apache httpd/prefork, s3fs mounts, cron) and repeatedly went dow | worker/.ebextensions/040_disk_memory_hygiene.config, worker/.ebextensions/045_oneuptime_agent.config, worker/ebs/cron.worker.php, worker/ebs/mount-s3fs-folders.php, worker/ebs/apache_settings.php, worker/ebs/setup_phpini.php |
|
|
13
14
|
| [OneUptime external uptime monitoring for 1.0 workers](features/oneuptime-worker-uptime-monitoring.md) | Every 1.0 worker box self-reports its liveness to an external OneUptime monitor once per minute by curl-POSTing to a per-worker "Incoming Request" heartbeat URL | library/app/worker.php, worker/crons/worker/worker_heartbeat.php |
|
|
@@ -6,8 +6,8 @@ project: Worker
|
|
|
6
6
|
client: compass-usa
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: ["bala"]
|
|
9
|
+
updated: 2026-08-21
|
|
10
|
+
owners: ["bala", "mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/compass/workflow/7_generate_ma_sales_order_exception_report.php
|
|
13
13
|
related: []
|
|
@@ -73,6 +73,8 @@ Compass USA only — this is a Compass-specific integration cron.
|
|
|
73
73
|
`db_beta_compass`.
|
|
74
74
|
|
|
75
75
|
## Change history
|
|
76
|
+
- 2026-08-21 — Removed the departing employee `mreich@togatech.com` from this cron's hardcoded
|
|
77
|
+
recipient list (TRUE-80451). Recipients are in-script, not DB-driven. (mhammontree)
|
|
76
78
|
- 2026-06-18 — Added stage-based exclusion so orders closed/canceled on ODP's end drop off the
|
|
77
79
|
report: `(salesOrderStageId IS NULL OR salesOrderStageId NOT IN (8, 9))` in the main query.
|
|
78
80
|
Removing specific stuck orders is then a data change (set the ODP SO's `salesOrderStageId` to
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: NetSuite Sales Order Sales Rep Sourcing (Staples & ODP EDI orders)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: worker
|
|
5
|
+
project: Worker
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-08-21
|
|
10
|
+
owners: [mhammontree]
|
|
11
|
+
files:
|
|
12
|
+
- worker/crons/toga2/compass/workflow/5_create_netsuite_sales_orders_from_office_depot_purchase_orders.php
|
|
13
|
+
- worker/crons/sync/staples/sync_staples_cxml.php
|
|
14
|
+
- test/@Mark/NetSuite/TRUE_80451_customer_salesrep_diag.php
|
|
15
|
+
related:
|
|
16
|
+
- 1.0/apps/worker/features/staples-cxml-order-import.md
|
|
17
|
+
- clients/compass-usa/workflows/odp-order-pipeline-to-netsuite.md
|
|
18
|
+
- clients/staples/profile.md
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Summary
|
|
22
|
+
|
|
23
|
+
How the **sales rep** on a NetSuite Sales Order is determined for the two 1.0 `worker` EDI
|
|
24
|
+
order-creation integrations (Staples cXML and Compass/ODP EDI). This is load-bearing and
|
|
25
|
+
non-obvious: **NetSuite will never source the sales rep from the real buying account** in
|
|
26
|
+
either integration, because both send the *parent* customer as the order `entity`.
|
|
27
|
+
|
|
28
|
+
## How it works
|
|
29
|
+
|
|
30
|
+
Both integrations build the NetSuite `SalesOrder` with:
|
|
31
|
+
|
|
32
|
+
- `entity` = the **PARENT** customer — Staples **30057**, ODP **35581 "ODP Veyer (B2B)"**.
|
|
33
|
+
- the real buying account carried only in the **custom** field `custbody_end_customer`.
|
|
34
|
+
|
|
35
|
+
NetSuite's native `salesRep` sourcing reads **`entity`**, and a custom field does not drive
|
|
36
|
+
it. So "just don't send a sales rep" yields the **parent's** rep, never the sub-customer's.
|
|
37
|
+
Sub-customer rep auto-assignment would only apply if `entity` *were* the sub-customer (e.g. a
|
|
38
|
+
UI-entered order) — a natural but wrong assumption.
|
|
39
|
+
|
|
40
|
+
The rep lives in the **native `salesRep` field (a RecordRef)** on the Customer record — not a
|
|
41
|
+
`custentity_*` field. `salesTeamList` was empty on every record checked (Team Selling is not in
|
|
42
|
+
play). `Customer->salesRep` hands straight to `SalesOrder->salesRep`.
|
|
43
|
+
|
|
44
|
+
### ODP cron 5 — rep read from the end customer (TRUE-80451)
|
|
45
|
+
|
|
46
|
+
`5_create_netsuite_sales_orders_from_office_depot_purchase_orders.php` previously hardcoded
|
|
47
|
+
employee internalId **34877** as `$salesOrder->salesRep`. It now calls a local helper
|
|
48
|
+
`getNetSuiteCustomerSalesRepRef()`, which reads the **end customer's** native `salesRep`
|
|
49
|
+
RecordRef via `App_NetSuite::getCustomer()`. Implementation rules that matter:
|
|
50
|
+
|
|
51
|
+
- **Order of operations:** the rep assignment sits **after the item loop**, not before the
|
|
52
|
+
shipping-method block, because the end customer depends on `$isTabletSku`, which is not
|
|
53
|
+
computed until the item loop runs.
|
|
54
|
+
- The two bare end-customer ids are now named constants `END_CUSTOMER_LAPTOP_LIFECYCLE`
|
|
55
|
+
(36431) and `END_CUSTOMER_TABLETS_PROGRAM` (37022), and the ternary appears **once**.
|
|
56
|
+
- **Lookups are cached per customer internalId for the life of the run** (cache array passed
|
|
57
|
+
by reference), so a batch makes 1–2 `getCustomer()` calls, not one per order. Use
|
|
58
|
+
**`array_key_exists`, not `isset`** — the cache intentionally stores `null` for "customer
|
|
59
|
+
has no rep".
|
|
60
|
+
- **A failed lookup is caught, `error_log`'d, and treated as "no rep"** — never rethrown.
|
|
61
|
+
`App_NetSuite::getCustomer()` throws, and in 1.0 an uncaught exception `exit()`s the
|
|
62
|
+
process, which on this every-10-minutes cron would stop order creation for the whole batch.
|
|
63
|
+
- **When there is no rep, `salesRep` is left UNSET** rather than assigned an empty RecordRef,
|
|
64
|
+
so NetSuite sources it from the order entity as before and a hollow RecordRef cannot fail
|
|
65
|
+
the `add()`.
|
|
66
|
+
- **Behavior-neutral on deploy:** both end customers currently carry rep 34877 — the same
|
|
67
|
+
value that was hardcoded. Reassigning a rep is now a **NetSuite data task**, not a code
|
|
68
|
+
change.
|
|
69
|
+
|
|
70
|
+
### Verified NetSuite values (2026-08-21)
|
|
71
|
+
|
|
72
|
+
| Customer | internalId | Native `salesRep` |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| Staples (parent) | 30057 | Paulina Szeliga (50695) |
|
|
75
|
+
| Staples child — Integra Partners | 50697 | Nicholas Vance (497) |
|
|
76
|
+
| ODP Veyer (B2B) (parent) | 35581 | Paulina Szeliga |
|
|
77
|
+
| ODP sub — Laptop Lifecycle | 36431 | Matthew Reich (34877) |
|
|
78
|
+
| ODP sub — Tablets Program | 37022 | Matthew Reich (34877) |
|
|
79
|
+
|
|
80
|
+
ODP hierarchy is three levels: **35581 → 36430 (Compass Group) → 36431 / 37022**.
|
|
81
|
+
|
|
82
|
+
## Gotchas / known issues
|
|
83
|
+
|
|
84
|
+
**Assigning a STRING to `salesRep` is silently discarded.** In
|
|
85
|
+
`worker/crons/sync/staples/sync_staples_cxml.php` (~line 138),
|
|
86
|
+
`$salesOrder->salesRep = 'Aaron Myall';` sets a plain string on a field NetSuite types as a
|
|
87
|
+
**RecordRef**. NetSuite drops it — proven empirically: sales order **286680** came out with
|
|
88
|
+
**Paulina Szeliga** (the parent's rep), not Aaron Myall. The line has no effect and actively
|
|
89
|
+
misleads; it made TRUE-80451 look like a two-line find-and-replace. **Separate-ticket
|
|
90
|
+
candidate — deliberately left untouched** (out of the agreed scope). While there, the comment
|
|
91
|
+
`//sales rep` above the `custbody_ctc_inside_sales_transaction` block in the same file is
|
|
92
|
+
**wrong**: that block sets **Inside Sales Support** (hardcoded Nicholas Vance, 497), not the
|
|
93
|
+
sales rep.
|
|
94
|
+
|
|
95
|
+
**NetSuite system notes cannot tell "we sent it" from "NetSuite sourced it."** At create
|
|
96
|
+
time NetSuite attributes its **own** sourced defaults to `Context: SOAP Web Services`,
|
|
97
|
+
identically to fields we actually send. A system-notes row is therefore **not** proof our
|
|
98
|
+
code set a value. Distinguish only by checking whether the code sends that field. Fields we
|
|
99
|
+
never send — Nexus, Terms, Subsidiary, Bill To, Currency, Sales Rep Location — carry the same
|
|
100
|
+
fingerprint.
|
|
101
|
+
|
|
102
|
+
**A read-only diagnostic script is kept for reuse** at
|
|
103
|
+
`test/@Mark/NetSuite/TRUE_80451_customer_salesrep_diag.php` — dumps native `salesRep`,
|
|
104
|
+
`salesTeamList`, and the whole `customFieldList` for any list of customer internalIds. It
|
|
105
|
+
must bootstrap **`App_Framework_Sandbox::initialize()`** (the `test` repo's APPROOT class,
|
|
106
|
+
which calls `registerNetSuiteToolkit`), **not `App_Framework_Worker`**. That mismatch is a
|
|
107
|
+
fatal "class not found" and is a recurring trip-up for scripts copied out of `worker` into
|
|
108
|
+
`test`.
|
|
109
|
+
|
|
110
|
+
## Change history
|
|
111
|
+
- 2026-08-21 — First KB entry (TRUE-80451). Documented that both Staples and ODP send the
|
|
112
|
+
**parent** as SO `entity` with the buying account only in `custbody_end_customer`, so
|
|
113
|
+
NetSuite never sources the sub-customer's rep; ODP cron 5's hardcoded rep 34877 replaced
|
|
114
|
+
with an end-customer `salesRep` lookup (cached, `array_key_exists`, failure = no rep, unset
|
|
115
|
+
rather than empty RecordRef, moved after the item loop); recorded verified rep values and
|
|
116
|
+
the ODP three-level hierarchy; captured the discarded-string `salesRep` dead code in
|
|
117
|
+
`sync_staples_cxml.php` and the system-notes `SOAP Web Services` false-positive. (mhammontree)
|
|
@@ -6,12 +6,13 @@ project: Worker
|
|
|
6
6
|
client: staples
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
10
|
-
owners: [jcardinal]
|
|
9
|
+
updated: 2026-08-21
|
|
10
|
+
owners: [jcardinal, mhammontree]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/sync/staples/sync_staples_cxml.php
|
|
13
13
|
related:
|
|
14
14
|
- clients/staples/profile.md
|
|
15
|
+
- 1.0/apps/worker/features/netsuite-sales-order-sales-rep-sourcing.md
|
|
15
16
|
---
|
|
16
17
|
|
|
17
18
|
## Summary
|
|
@@ -73,6 +74,16 @@ for ~6 days.
|
|
|
73
74
|
**`getSavedSearch(8412)` is called once per order inside the file loop** — expensive
|
|
74
75
|
(~20s/file). A future refactor should fetch/cache it once per run.
|
|
75
76
|
|
|
77
|
+
**The `salesRep` assignment on this cron is dead code.** `$salesOrder->salesRep = 'Aaron Myall';`
|
|
78
|
+
(~line 138) assigns a plain **string** to a field NetSuite types as a **RecordRef**; NetSuite
|
|
79
|
+
silently discards it. Proven empirically: SO **286680** came out with **Paulina Szeliga** — the
|
|
80
|
+
parent customer 30057's rep — not Aaron Myall. Because `entity` is always the parent, NetSuite
|
|
81
|
+
sources the parent's rep and the custom `custbody_end_customer` never drives it. Separate-ticket
|
|
82
|
+
candidate; left in place as of 2026-08-21. Also: the comment `//sales rep` above the
|
|
83
|
+
`custbody_ctc_inside_sales_transaction` block is **wrong** — that block sets **Inside Sales
|
|
84
|
+
Support** (hardcoded Nicholas Vance, internalId 497). Full mechanics in
|
|
85
|
+
[NetSuite Sales Order Sales Rep Sourcing](netsuite-sales-order-sales-rep-sourcing.md).
|
|
86
|
+
|
|
76
87
|
**Recommended hardening (NOT yet implemented):**
|
|
77
88
|
- Add the missing `else` to email + log add failures (mirror the existing "missing items"
|
|
78
89
|
email) and leave the file for retry.
|
|
@@ -93,4 +104,10 @@ Because the full inbound cXML lives in `FileLog.fileData`, ship-to identity and
|
|
|
93
104
|
can be recovered directly from the legacy `Logs` DB without touching SFTP.
|
|
94
105
|
|
|
95
106
|
## Change history
|
|
107
|
+
- 2026-08-21 — Recorded that `$salesOrder->salesRep = 'Aaron Myall'` is **dead code** (string into
|
|
108
|
+
a RecordRef field; NetSuite discards it — SO 286680 came out with the parent's rep, Paulina
|
|
109
|
+
Szeliga) and that the adjacent `//sales rep` comment actually describes Inside Sales Support
|
|
110
|
+
(Nicholas Vance, 497). Removed the departing employee `mreich@togatech.com` from the sibling
|
|
111
|
+
scheduled cron `staples_asn_netsuite.php` recipient list. No behavior change to this cron.
|
|
112
|
+
(mhammontree)
|
|
96
113
|
- 2026-08-13 — Documented feature (first KB entry). Diagnosed a ~6-day, ~30-order backlog stuck on SFTP `/in`: NetSuite rejected every add with `USER_ERROR` "Location" because new child customer INTEGRA PARTNERS INC. (Staples CustomerID 03716185, under parent 30057) was not set up in NetSuite → no saved-search-8412 match → NULL line-item Location. 28 of 30 stuck files were this account; 2 were CustomerID 05611619 (existed but missing Customer Location). Fixed by business admins creating the customer with External Customer ID + Customer Location; re-running the cron drained the backlog. Confirmed working. (jcardinal)
|
|
@@ -5,7 +5,7 @@ project: Library
|
|
|
5
5
|
client: shared
|
|
6
6
|
type: standard
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-08-
|
|
8
|
+
updated: 2026-08-21
|
|
9
9
|
owners: [jcardinal, rgirish, mhammontree, ajean]
|
|
10
10
|
files: []
|
|
11
11
|
related:
|
|
@@ -315,6 +315,17 @@ check and the read (TOCTOU), and the resulting warning would `exit()` the
|
|
|
315
315
|
`send_emails` cron **mid-loop**, stranding every queued email behind it. Any file read
|
|
316
316
|
inside a loop that processes a queue takes pattern 1.
|
|
317
317
|
|
|
318
|
+
**Exception — `empty()` / `isset()` on a chained property access raises nothing.**
|
|
319
|
+
`empty($order->customer->salesRep)` and `isset($a->b->c)` are *not* warning sources: both are
|
|
320
|
+
language constructs that evaluate the whole chain in an isolated, non-warning context, so a null or
|
|
321
|
+
missing intermediate yields `true`/`false` rather than an "Attempt to read property on null"
|
|
322
|
+
warning. They therefore need **no** local error handler and no
|
|
323
|
+
`setThrowExceptionsEnabled(false)` wrapper — adding one is noise. The escalation rule above
|
|
324
|
+
applies to *reads* (`$order->customer->salesRep` used as a value, `file_get_contents`, array
|
|
325
|
+
offsets on null), not to `empty()`/`isset()` guards. Prefer `empty()`/`isset()` (or `??`) as the
|
|
326
|
+
guard precisely because it is warning-free. Established on TRUE-80451, where a php-reviewer pass
|
|
327
|
+
raised this exact false positive against an `empty()` chain guard.
|
|
328
|
+
|
|
318
329
|
### Start the session before App_Error's fatal handler — and set the handler in code, not INI
|
|
319
330
|
|
|
320
331
|
On EB PHP 8.5 / AL2023, the `.so` extension and cookie flags aside, two rules make Redis
|
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)_ — 19 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
|
|
8
|
-
- **worker** (Worker) —
|
|
8
|
+
- **worker** (Worker) — 26 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) — 12 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
|
|
@@ -6,8 +6,8 @@ project: Worker
|
|
|
6
6
|
client: compass-usa
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
10
|
-
owners: ["rgirish", "bala", "dfranks", "jcardinal"]
|
|
9
|
+
updated: 2026-08-21
|
|
10
|
+
owners: ["rgirish", "bala", "dfranks", "jcardinal", "mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/compass/workflow/1_transmit_compass_sales_orders_to_mits.php
|
|
13
13
|
- worker/crons/toga2/compass/workflow/2_transmit_mits_purchase_orders_to_vendors.php
|
|
@@ -18,6 +18,7 @@ files:
|
|
|
18
18
|
- worker/schedules/cron.worker.sync.json
|
|
19
19
|
- library/app/client/compass.php
|
|
20
20
|
related:
|
|
21
|
+
- 1.0/apps/worker/features/netsuite-sales-order-sales-rep-sourcing.md
|
|
21
22
|
- clients/compass-usa/features/odp-edi-855-acknowledgement-and-overquantity-guard.md
|
|
22
23
|
- clients/compass-usa/workflows/odp-edi-import-recovery.md
|
|
23
24
|
- clients/compass-usa/workflows/order-lifecycle-and-data-integrity.md
|
|
@@ -124,6 +125,32 @@ duplicates and backfills whatever is missing**:
|
|
|
124
125
|
a **zero-item ODP SalesOrder sits in this queue retrying every 10 minutes** until its lines are
|
|
125
126
|
restored — the retry loop is the symptom, not the cause.
|
|
126
127
|
|
|
128
|
+
### Cron 5 sales rep — sourced from the end customer, not hardcoded (TRUE-80451)
|
|
129
|
+
|
|
130
|
+
The SO `entity` is the **parent** customer **35581 "ODP Veyer (B2B)"**; the real buying account
|
|
131
|
+
rides only in the custom field `custbody_end_customer`. NetSuite's native `salesRep` sourcing
|
|
132
|
+
reads `entity`, so it can only ever produce the **parent's** rep — sending nothing does *not*
|
|
133
|
+
get you the sub-customer's rep. Cron 5 therefore looks the rep up itself: it reads the **end
|
|
134
|
+
customer's** native `salesRep` RecordRef via `App_NetSuite::getCustomer()`, replacing the
|
|
135
|
+
previously hardcoded employee 34877. End customers are the named constants
|
|
136
|
+
`END_CUSTOMER_LAPTOP_LIFECYCLE` (36431) and `END_CUSTOMER_TABLETS_PROGRAM` (37022), chosen by
|
|
137
|
+
`$isTabletSku` — which is why the rep assignment lives **after the item loop**, not up with the
|
|
138
|
+
shipping-method block. Lookups are cached per internalId for the run (1–2 `getCustomer()` calls
|
|
139
|
+
per batch), a failed lookup is logged and treated as "no rep" rather than thrown (an uncaught
|
|
140
|
+
exception `exit()`s this every-10-minutes cron and would stop the whole batch), and "no rep"
|
|
141
|
+
leaves `salesRep` **unset**. Rep reassignment is now a NetSuite data change, not a code change.
|
|
142
|
+
Full mechanics, verified rep values and the three-level ODP hierarchy:
|
|
143
|
+
[NetSuite Sales Order Sales Rep Sourcing](../../../1.0/apps/worker/features/netsuite-sales-order-sales-rep-sourcing.md).
|
|
144
|
+
|
|
145
|
+
### Editing cron recipient lists — a grep hit is not a live hit
|
|
146
|
+
|
|
147
|
+
Email recipients for this pipeline are hardcoded in the cron scripts, and **several dormant
|
|
148
|
+
copies of the same script exist** under `crons/toga2/compass/edi/` and
|
|
149
|
+
`crons/toga2/compass/DOA_edi_odp_flow/`. Before changing a recipient list, cross-check the file
|
|
150
|
+
against `worker/schedules/*.json` for a referencing entry whose `active` is not `0`. Only the
|
|
151
|
+
scheduled copies matter; editing a dormant copy is churn, and *missing* a scheduled one leaves
|
|
152
|
+
the old recipient live.
|
|
153
|
+
|
|
127
154
|
## The active flow is `workflow/` — the other ODP folders are legacy
|
|
128
155
|
The **live** Office Depot EDI flow is entirely under
|
|
129
156
|
`worker/crons/toga2/compass/workflow/`: **3a** import (every 5 min), **4** transmit-PO-
|
|
@@ -215,6 +242,16 @@ wrong record makes every order look "stuck." Use the **join**, never a number ma
|
|
|
215
242
|
transmission feature.)
|
|
216
243
|
|
|
217
244
|
## Change history
|
|
245
|
+
- 2026-08-21 — **Cron 5 sales rep** no longer hardcodes employee 34877: it reads the end
|
|
246
|
+
customer's native NetSuite `salesRep` RecordRef (`App_NetSuite::getCustomer()`, cached per
|
|
247
|
+
run, failures logged and treated as "no rep", left unset when absent), with the end-customer
|
|
248
|
+
ids promoted to named constants and the assignment moved after the item loop (it depends on
|
|
249
|
+
`$isTabletSku`). Behavior-neutral on deploy — both end customers currently carry 34877.
|
|
250
|
+
Recorded that `entity` = parent 35581 means NetSuite can never source the sub-customer's rep.
|
|
251
|
+
Removed the departing employee `mreich@togatech.com` from the recipient lists of the
|
|
252
|
+
scheduled crons 3a, 5, 7 and `notifications/reports/compass_daily_mits_orders.php`, leaving
|
|
253
|
+
dormant `edi/` and `DOA_edi_odp_flow/` copies alone; added the "verify against
|
|
254
|
+
`schedules/*.json` before editing a recipient list" method. (TRUE-80451, mhammontree)
|
|
218
255
|
- 2026-08-13 — Added **cron 4** (transmit ODP PO acknowledgements, every 15 min) and noted
|
|
219
256
|
**cron 6** (generate ASN); flagged the active `workflow/` folder vs the legacy
|
|
220
257
|
`compass/edi/`, `compass/DOA_edi_odp_flow/`, `compass/DOA_Scripts/` folders (incl. the
|
|
@@ -9,14 +9,15 @@ project: Worker
|
|
|
9
9
|
client: staples
|
|
10
10
|
type: profile
|
|
11
11
|
status: active
|
|
12
|
-
updated: 2026-08-
|
|
13
|
-
owners: [jcardinal, bala]
|
|
12
|
+
updated: 2026-08-21
|
|
13
|
+
owners: [jcardinal, bala, mhammontree]
|
|
14
14
|
files:
|
|
15
15
|
- worker/crons/sync/staples/sync_staples_cxml.php
|
|
16
16
|
- worker/crons/sync/staples/staples_asn_netsuite.php
|
|
17
17
|
related:
|
|
18
18
|
- ../../1.0/apps/worker/features/staples-cxml-order-import.md
|
|
19
19
|
- ./features/oneuptime-sftp-folder-monitors.md
|
|
20
|
+
- ../../1.0/apps/worker/features/netsuite-sales-order-sales-rep-sourcing.md
|
|
20
21
|
---
|
|
21
22
|
|
|
22
23
|
## Summary
|
|
@@ -42,3 +43,8 @@ NetSuite — as a child customer of 30057, with its **External Customer ID** = t
|
|
|
42
43
|
`CustomerID` **and** a valid **Customer Location** — *before* its first order arrives.
|
|
43
44
|
Miss either and inbound orders for that account silently jam on SFTP with no error (see the
|
|
44
45
|
feature doc's gotchas).
|
|
46
|
+
|
|
47
|
+
**Sales rep on Staples sales orders:** the SO `entity` is always parent **30057**, so NetSuite
|
|
48
|
+
sources **30057's** rep (currently Paulina Szeliga, 50695) regardless of which child account
|
|
49
|
+
bought — see
|
|
50
|
+
[NetSuite Sales Order Sales Rep Sourcing](../../1.0/apps/worker/features/netsuite-sales-order-sales-rep-sourcing.md).
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: session
|
|
3
|
+
slug: talos-shared-component
|
|
4
|
+
title: Talos shared AI-assistant component (blox) + supply integration
|
|
5
|
+
author: apeterson
|
|
6
|
+
repos: [toga-blox, toga25-supply]
|
|
7
|
+
framework: "2.0"
|
|
8
|
+
client: shared
|
|
9
|
+
status: active
|
|
10
|
+
created: 2026-08-20
|
|
11
|
+
updated: 2026-08-20
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Session: talos-shared-component
|
|
15
|
+
**Date:** 2026-08-20
|
|
16
|
+
**Project/Repo:** toga-blox + toga25-supply (2.0)
|
|
17
|
+
**Task:** Build Talos (AI assistant: header launcher button + slide-out chat panel) as a shared, fully-themeable blox component (TRUE-80692) and wire it into toga25-supply for all clients (TRUE-80693), UI-shell-only with a stub onSend.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What WORKED
|
|
22
|
+
- New `src/components/Talos/` in toga-blox-npm: TalosLauncher, TalosPanel, TalosMessage, types.ts, theme.ts, helpers.tsx, stub.ts, Talos.module.css, index.ts — exported from `src/components/index.ts`. `npm run build` clean; files land in `dist/components/Talos/`. TS typecheck clean.
|
|
23
|
+
- Pure-UI + injected adapter design: `TalosAdapter` = onSend(text)=>Promise<TalosResponse>, onOpenRecord(id), onAction(key), onDockWidthChange(px), onAttach(). No window.* globals. `createTalosStubAdapter()` ships for backend-free demos.
|
|
24
|
+
- Controlled state: open/expanded/width owned by host via onOpenChange/onExpandedChange/onWidthChange.
|
|
25
|
+
- Full CSS tokenization: every color/font-size/weight/line-height/radius/shadow/dimension is `var(--talos-*, default)`. Talos.module.css IS the default theme.
|
|
26
|
+
- Theme override model: typed `TalosTheme` (theme.ts) + `talosThemeToVars()` applied as inline `--talos-*` on launcher/panel/scrim roots; unset keys fall through to defaults. Any --talos-* is also overridable via host CSS.
|
|
27
|
+
- Supply wiring (AppLayout.tsx): owns talosOpen/Expanded/Width/DockWidth state, memoized stub adapter (onOpenRecord -> navigate `/sales-orders?sales-orders=<id>`; onAction "approvals" -> `/sales-orders`; onDockWidthChange -> reserve width). Launcher in Header via new presentational `talosLauncher` slot + `.headerActions` flex wrapper; TalosPanel at app root. Enabled for ALL clients. Owl asset copied to supply `src/assets/talos-owl.png`, passed as branding.avatarSrc (fixes button + welcome orb + message avatar). Plus Jakarta Sans loaded in supply `src/index.css`. Supply typecheck: 0 errors.
|
|
28
|
+
- Header-overlap bug fixed: dock-width reservation moved to the OUTER app wrapper (marginRight) so the header shifts with content and the launcher/avatar stay visible.
|
|
29
|
+
- Two ecc:react-reviewer passes applied (quality + fidelity-vs-prototype). See Decisions.
|
|
30
|
+
- Committed + pushed both branches: toga-blox TRUE-80692 (commit 2c34dec1), toga25-supply TRUE-80693 (commit a346833). Upstreams set.
|
|
31
|
+
- Captured to KB: 2 feature docs published to _main (2.0/apps/toga-blox/features/talos-assistant.md, 2.0/apps/toga25-supply/features/talos-integration.md).
|
|
32
|
+
|
|
33
|
+
## What did NOT work — DO NOT RETRY THESE
|
|
34
|
+
- Fetching the claude.ai `/design/p/...` design URL via WebFetch or the Chrome DevTools MCP browser: blocked by Cloudflare bot challenge (`cf-mitigated: challenge`) + a login/human-check that fails in an automation-controlled browser. Not an auth-cookie issue. Resolution: user downloaded the bundle locally (`~/Downloads/TOGa Supply (Pipeline)/demo/talos.jsx`). Do not re-attempt remote fetch of design URLs.
|
|
35
|
+
- Reserving dock width only on `<main>`: leaves the header's right edge under the fixed docked panel, hiding the launcher + avatar (blox Header is normal-flow, NOT fixed). Must reserve on the outer wrapper instead.
|
|
36
|
+
- Keeping `window.dispatchEvent(new Event("resize"))` on resize drag-end (from prototype): flagged CRITICAL as adapter-bypass; removed. Host gets width via onWidthChange/onDockWidthChange instead — do not re-add.
|
|
37
|
+
|
|
38
|
+
## Not tried yet (candidates for next session)
|
|
39
|
+
- Spacing-token pass: ~38 padding/gap/margin literals in Talos.module.css are still hardcoded, so density is NOT themeable. Add `--talos-space-*` tokens and/or a `density` preset prop ("comfortable"|"compact") — awaiting UX/UI input (handoff recap given to team).
|
|
40
|
+
- Structural variants: launcher `variant` ("icon"|"pill") and/or optional panel-mode props (e.g. drop expanded mode for a small Desk side chat) — awaiting UX input.
|
|
41
|
+
- Real `onSend` backend adapter (this ticket is UI-shell + stub only) — separate later ticket.
|
|
42
|
+
- Publish toga-blox TRUE-80692 to npm (per `_sandbox-dev` dist-tag workflow) so consumers other than supply (which uses the local symlink) get Talos.
|
|
43
|
+
- Wire a supply-specific `theme` object if supply should diverge from the default look (currently uses default).
|
|
44
|
+
- Approvals action only routes to `/sales-orders`; deeper auto-apply of the approvals filter (local page state, not URL) is not wired.
|
|
45
|
+
|
|
46
|
+
## Current file state
|
|
47
|
+
| File | Status | Notes |
|
|
48
|
+
|------|--------|-------|
|
|
49
|
+
| toga-blox src/components/Talos/*.tsx,.ts,.css | Created | Full component; committed TRUE-80692; built to dist |
|
|
50
|
+
| toga-blox src/components/index.ts | Modified | Added `export * from "./Talos"` |
|
|
51
|
+
| toga25-supply src/layout/AppLayout/AppLayout.tsx | Modified | Talos state + stub adapter + launcher/panel mount + dock-width reservation + owl branding |
|
|
52
|
+
| toga25-supply src/components/Header/Header.tsx | Modified | `talosLauncher` slot prop (committed earlier in 2ea8d3f) |
|
|
53
|
+
| toga25-supply src/components/Header/Header.module.css | Modified | `.headerActions` flex wrapper |
|
|
54
|
+
| toga25-supply src/index.css | Modified | Plus Jakarta Sans @import |
|
|
55
|
+
| toga25-supply src/assets/talos-owl.png | Created | Owl asset from design bundle |
|
|
56
|
+
|
|
57
|
+
## Decisions made
|
|
58
|
+
- UI-shell only this ticket: build component + adapter, wire supply with stub onSend; real backend later. Rejected: wiring a live backend now (out of scope).
|
|
59
|
+
- Injected props/context adapter (no window.* globals). Rejected: the prototype's window.__* bridge (implicit coupling, not reusable).
|
|
60
|
+
- Fully tokenized styling; Talos.module.css is the DEFAULT theme; consumers override via typed `TalosTheme` prop and/or raw --talos-* CSS. Rejected: (a) hardcoded literals; (b) stripping all defaults so every consumer must ship a full theme (renders unstyled standalone).
|
|
61
|
+
- Font loaded by the HOST (supply index.css), not a forced @import inside the shared CSS; component default family is "Plus Jakarta Sans". Rationale: avoids a network/CSP side effect in the shared lib while still giving prototype typography.
|
|
62
|
+
- Review pass 1 (quality) fixes: removed hardcoded "JB" -> branding.userInitials + glyph; removed window resize dispatch; per-conversation busy guard; resize listeners in an effect (cleanup + rAF throttle + keyboard role="separator"); ESC handler via ref; diffed dock-width broadcast (avoids inline-adapter re-render loop); composer aria-label; SSR-safe portal; memoized TalosMessage + handlers.
|
|
63
|
+
- Review pass 2 (fidelity): restored 11px card radius (own `--talos-radius-card` token); restored attach/paper-clip button wired to optional onAttach. Deliberately KEPT OUT: window resize dispatch and the brand "online" status row (dead CSS in the prototype too).
|
|
64
|
+
|
|
65
|
+
## Blockers
|
|
66
|
+
None. Both branches pushed; KB captured. Next work is UX-input-gated (density/variants) or a separate ticket (real backend, blox npm publish).
|
|
67
|
+
|
|
68
|
+
## Exact next step
|
|
69
|
+
> When UX/UI returns input on density: do the spacing-token pass in `toga-blox-npm/src/components/Talos/Talos.module.css` (tokenize the ~38 padding/gap/margin literals into `--talos-space-*`) and add a `density?: "comfortable" | "compact"` prop to TalosPanel that swaps a spacing preset; expose the new tokens in `TalosTheme` (theme.ts). Then `npm run build`. Separately, when ready for other consumers: publish toga-blox TRUE-80692 to npm via the `_sandbox-dev` dist-tag workflow.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
_Saved by /session-save on 2026-08-20_
|
package/package.json
CHANGED