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.
@@ -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-06-18
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-13
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-20
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
@@ -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) — 25 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
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-04
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-20
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.628",
3
+ "version": "1.0.630",
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",