toga-ai 1.0.685 → 1.0.686

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.
@@ -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)_ — 20 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
8
- - **worker** (Worker) — 29 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
8
+ - **worker** (Worker) — 30 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)
@@ -6,10 +6,11 @@
6
6
  | [Prudential: Dell LCH IOP transmissions (LCHRequestV2)](features/dell-lch-iop-transmissions.md) | 1.0 | Outbound order transmissions from the 1.0 worker tier to Dell's Lifecycle Hub (LCH) ITSM integration. | library/app/api/delllch.php, library/app/apitransaction.php, worker/crons/toga2/prudential/transmissions_to_dell_usa.php, worker/crons/toga2/prudential/transmissions_to_dell_ireland.php, worker/crons/toga2/prudential/transmissions_to_dell_india.php, worker/crons/toga2/prudential/generate_sales_and_purchase_orders_from_service_requests.php |
7
7
  | [Prudential: Device information import + unit→contact linking (import_device_information.php)](features/device-information-import-and-contact-linking.md) | 1.0 | Prudential's **device-sync** cron (`worker/crons/toga2/prudential/import_device_information.php`) pulls device/asset records (from ServiceNow / the Dell CMDB fe | worker/crons/toga2/prudential/import_device_information.php, worker/crons/toga2/prudential/backfill_unit_contacts.php, dbchanges2/Client_Prudential/2026-07-07 - Contact Dedup Merge.sql |
8
8
  | [Prudential: OneUptime order-pipeline backlog monitors (Monitor/Prudential/*)](features/oneuptime-order-pipeline-monitors.md) | 2.0 | Three worker2 OneUptime push monitors (TRUE-80587) that watch the Prudential/Dell order pipeline for **stalls**, which were previously invisible — a stuck pipel | worker2/Worker/Monitor/Prudential.php, worker2/Config/production.ini, dbchanges2/Core/2026-08-07a - Prudential AIG ServiceNow OneUptime monitor crons.sql, dbchanges2/Client_Prudential/2026-04-08 - Insert Customers.sql |
9
+ | [Prudential: order email PDF attachments (which guide goes on which email)](features/order-email-pdf-attachments.md) | 1.0 | Every customer-facing Prudential order email carries S3-hosted PDF guides, and **which** guide is attached is decided by the service request type. | worker/crons/toga2/prudential/generate_sales_and_purchase_orders_from_service_requests.php, worker/crons/toga2/prudential/transmissions_to_netsuite.php, worker/crons/toga2/prudential/transmit_ordershipped_updates_prudential.php, worker/crons/toga2/prudential/transmit_closecomplete_updates_prudential.php, worker/crons/toga2/prudential/test_new_hire_email.php |
9
10
  | [Prudential: Service Request Regional Address Validation](features/service-request-address-validation.md) | 2.0 | The `prePost` interceptor on `_Model_Prudential_ServiceRequest` validates `deliverToAddress` fields differently depending on which Prudential regional customer | _underscore/Model/Prudential/ServiceRequest.php, _underscore/Test/Prudential/ServiceRequestTest.php |
10
11
  | [Prudential: Service Request rejection alert email](features/service-request-rejection-alert-email.md) | 2.0 | When a Prudential ServiceNow→TOGa service-request submission (`POST /v2/service-requests`) is **rejected by validation**, TOGa now sends a real-time internal al | worker2/Worker/Client/Prudential/reports/ReqRejectionEmail.php, _underscore/Model/Prudential/ServiceRequest.php |
11
12
  | [Prudential: Sales Orders CSV export (TOGa 2.5 Supply)](features/supply-orders-csv-export.md) | 2.0 | The **Export** button on the TOGa 2.5 Supply *Sales Orders* page is **Prudential-only**: it downloads a 22-column CSV (`prudential-orders-<YYYY-MM-DD>.csv`) cov | toga25-supply/src/pages/SalesOrders/viewModel/FIELDS/PRUDENTIAL/exportApiFields.json, toga25-supply/src/pages/SalesOrders/api/prudentialExportApi.ts, toga25-supply/src/pages/SalesOrders/hooks/usePrudentialOrdersExport.ts, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx |
12
- | [Prudential Order Shipped Email — transmit_ordershipped_updates_prudential.php](features/transmit-ordershipped-email.md) | 1.0 | Cron script that transmits "Order Shipped" updates to ServiceNow (RITM) and sends a shipped notification email to the end user. | worker/crons/toga2/prudential/transmit_ordershipped_updates_prudential.php, worker/crons/toga2/prudential/transmit_closecomplete_updates_prudential.php, worker/crons/toga2/prudential/transmit_rejected_cancelled_updates_prudential.php, worker/crons/toga2/prudential/generate_sales_and_purchase_orders_from_service_requests.php, worker/crons/toga2/prudential_beta/generate_sales_and_purchase_orders_from_service_requests.php, worker/crons/notifications/reports/prudential_exception_report.php |
13
+ | [Prudential Order Shipped Email — transmit_ordershipped_updates_prudential.php](features/transmit-ordershipped-email.md) | 1.0 | Cron script that transmits "Order Shipped" updates to ServiceNow (RITM) and sends a shipped notification email to the end user. | worker/crons/toga2/prudential/transmit_ordershipped_updates_prudential.php, worker/crons/toga2/prudential/transmissions_to_netsuite.php, worker/crons/toga2/prudential/transmit_closecomplete_updates_prudential.php, worker/crons/toga2/prudential/transmit_rejected_cancelled_updates_prudential.php, worker/crons/toga2/prudential/generate_sales_and_purchase_orders_from_service_requests.php, worker/crons/toga2/prudential_beta/generate_sales_and_purchase_orders_from_service_requests.php, worker/crons/notifications/reports/prudential_exception_report.php |
13
14
  | [Prudential Financial](profile.md) | 2.0 | Prudential is a TOGA client whose device-fulfillment flow is driven by **Dell** via the Dell API (`Client_Prudential.Apis.id = 2`). | |
14
15
  | [Prudential: Dell ASN failed POST backfill replay](workflows/dell-asn-backfill-replay.md) | 2.0 | When Dell ASN POSTs fail in bulk (e.g. | |
15
16
  | [Prudential — HTTP/2 disabled on the Commerce ALB (proxy workaround) + open re-enable action](workflows/http2-alb-workaround.md) | 2.0 | Prudential users hit **`ERR_HTTP2_PROTOCOL_ERROR`** in Chrome and Edge (both Chromium) loading `https://prudential.togacommerce.com`, while the site worked fine | |
@@ -0,0 +1,141 @@
1
+ ---
2
+ title: "Prudential: order email PDF attachments (which guide goes on which email)"
3
+ framework: "1.0"
4
+ repo: worker
5
+ project: Worker
6
+ client: prudential
7
+ type: client-feature
8
+ status: active
9
+ updated: 2026-08-28
10
+ owners: ["bala"]
11
+ files:
12
+ - worker/crons/toga2/prudential/generate_sales_and_purchase_orders_from_service_requests.php
13
+ - worker/crons/toga2/prudential/transmissions_to_netsuite.php
14
+ - worker/crons/toga2/prudential/transmit_ordershipped_updates_prudential.php
15
+ - worker/crons/toga2/prudential/transmit_closecomplete_updates_prudential.php
16
+ - worker/crons/toga2/prudential/test_new_hire_email.php
17
+ related:
18
+ - transmit-ordershipped-email.md
19
+ - ../profile.md
20
+ ---
21
+
22
+ ## Summary
23
+ Every customer-facing Prudential order email carries S3-hosted PDF guides, and **which** guide is
24
+ attached is decided by the service request type. The rule was inconsistent between stages — the
25
+ same request type received different documents depending on whether the email came from
26
+ order-created, shipped, or delivered — and a new hire was being sent the *return* instructions
27
+ instead of a setup guide. As of 2026-08-27 one rule applies at **every** stage:
28
+
29
+ > **New hire → getting-started guides only (Windows 11 + Mac).
30
+ > Reclaim, Breakfix, Swap, Label, Refresh → the return-instructions guide.**
31
+
32
+ Reported by the client (Gary Yerman) against the new-hire order-created email; the fix for that
33
+ specific branch shipped as **TRUE-81393**, and the consistency pass across the other stages
34
+ followed in the same session.
35
+
36
+ ## The rule, by stage
37
+
38
+ | Service request type | Order created | Shipped | Delivered |
39
+ |---|---|---|---|
40
+ | New (1) | Win11 + Mac | Win11 + Mac | Win11 + Mac |
41
+ | Reclaim (5) | Return | Return | Return |
42
+ | Breakfix (2) | Win11 + Return | Win11 + Mac + Return | Win11 + Mac + Return |
43
+ | Swap (4) | Win11 + Return | Win11 + Mac + Return | Win11 + Mac + Return |
44
+ | Label (6) | Win11 + Return | Win11 + Mac + Return | Win11 + Mac + Return |
45
+ | Refresh (3) | Win11 + Mac + Return | Win11 + Mac + Return | Win11 + Mac + Return |
46
+
47
+ The load-bearing part is the **Return** column: no non-New type may go out without the return
48
+ guide, and New must never carry it.
49
+
50
+ ## Which cron sends which email
51
+
52
+ All four are `active: 1` in `worker/schedules/cron.worker.sync.json`:
53
+
54
+ | Stage | Cron | Branches on |
55
+ |---|---|---|
56
+ | Order created | `generate_sales_and_purchase_orders_from_service_requests.php` | `serviceRequestTypeId` (`1`, `5`, `3`, `else`) |
57
+ | Shipped | `transmissions_to_netsuite.php` | type **name** (`$reqType == "New"`, `else`) |
58
+ | Shipped | `transmit_ordershipped_updates_prudential.php` | type **name** (`$reqType == "New"`, `else`) |
59
+ | Delivered | `transmit_closecomplete_updates_prudential.php` | type **name** (`$reqType == "New"`, `else`) |
60
+
61
+ **Two different crons send the shipped email** (`transmissions_to_netsuite.php` and
62
+ `transmit_ordershipped_updates_prudential.php` — both also stamp
63
+ `c_dtTransmittedOrderShippedUpdateToPrudential`). Any attachment change to the shipped email must
64
+ be made in **both**, or the two senders drift apart.
65
+
66
+ The order-created cron switches on the numeric `serviceRequestTypeId`, while the shipped and
67
+ delivered crons switch on the type **name string** read from
68
+ `serviceRequest.serviceRequestType.name` in the TOGa API response. The names match the enum
69
+ exactly (`New`, `Reclaim`), so the string comparisons are safe — but the two styles mean a newly
70
+ added request type lands in the `else` branch everywhere and is never matched by name.
71
+
72
+ ## The PDF assets
73
+
74
+ All three live under `https://external-client-files.s3.us-west-2.amazonaws.com/prudential/` and
75
+ all three returned HTTP 200 (verified 2026-08-27). Two of them are **renamed on attach**:
76
+
77
+ | S3 object | Attached to the email as | Size |
78
+ |---|---|---|
79
+ | `EquipMe-Win11-v1.pdf` | `Getting_Started_Guide_Windows11.pdf` | 773,591 B |
80
+ | `2026_Aug_PrudentialMacSetupGuide_ZCC.pdf` | `PrudentialMacSetupGuide.pdf` | 7,543,154 B |
81
+ | `Prudential_Instruction-2Page-V1.pdf` | (unchanged) | 100,224 B |
82
+
83
+ The Mac guide dominates: a new-hire email is roughly **11 MB** once base64-encoded. Grepping for
84
+ the attached-as filename will not find the S3 object — search the S3 basename.
85
+
86
+ ## How to change an attachment safely
87
+
88
+ 1. Every fetch goes through the file-scope `safeFetchPdf()` helper, never bare
89
+ `file_get_contents()`. Under the 1.0 framework's `E_ALL` + `App_Error::handleError`, a failed
90
+ S3 pull is converted to an `ErrorException` and **throws at the call site**, killing the cron
91
+ mid-batch before any `!== false` guard can run. Full root cause in
92
+ `transmit-ordershipped-email.md`. `transmissions_to_netsuite.php` was the last file still
93
+ fetching directly and calling `addAttachment()` unconditionally (which would also mail a
94
+ 0-byte file under a real guide's name); it was converted 2026-08-27.
95
+ 2. Each PDF gets its **own** `if ($pdfX !== false)` guard around the `file_put_contents` +
96
+ `addAttachment` pair — never a joint `&&`. One failed PDF must drop only itself; the email
97
+ always sends.
98
+ 3. **Put the return-guide fetch *before* the `if ($reqType != 'Reclaim')` guard** in the shipped
99
+ and delivered crons. Everything inside that guard is skipped for Reclaim — and Reclaim is
100
+ precisely the type that most needs the return instructions. That guard is how Reclaim ended up
101
+ receiving zero attachments.
102
+
103
+ ## Testing
104
+
105
+ `crons/toga2/prudential/test_new_hire_email.php` (local, untracked) rebuilds the new-hire email
106
+ verbatim — same subject, same HTML template, same S3 fetches — mails it to a hardcoded
107
+ `$testRecipients` list, and prints each PDF's attached/missing state and byte size before
108
+ sending. It **writes nothing**: no sales order, no purchase order, no api2 call, no SQL. It
109
+ deliberately calls only `App_Framework_Worker::initialize()` and **not**
110
+ `App_Framework::cronInitialization()`, because that INSERTs a `CronJobExecutions` row.
111
+
112
+ Run it from the worker repo's `crons/` directory with the `library` and `_underscore` repos on
113
+ the include path:
114
+
115
+ ```
116
+ php -d include_path=".;<library>;<_underscore>" toga2/prudential/test_new_hire_email.php
117
+ ```
118
+
119
+ ## Gotchas / known gaps
120
+
121
+ - **`prudential_beta/` does not match prod.**
122
+ `crons/toga2/prudential_beta/generate_sales_and_purchase_orders_from_service_requests.php` is
123
+ `active: 1` on the same schedule but points at the beta DB and still references the retired
124
+ `Prudential_Return_Instructions.pdf` (and the old `PrudentialMacSetupGuide.pdf`). **A beta test
125
+ will not reproduce prod attachment behavior.** Left as-is deliberately; the developer is aware.
126
+ - **Breakfix / Swap / Label get no Mac guide on the order-created email** (they do get it on
127
+ shipped and delivered). Known, and left alone on purpose — not a bug to "fix" unasked.
128
+ - The delivered cron (`transmit_closecomplete_updates_prudential.php`) already followed the rule
129
+ and was deliberately **not** touched.
130
+
131
+ ## Change history
132
+ - 2026-08-27 — Client-reported: the new-hire order-created email attached the return-instructions
133
+ PDF and carried no Mac guide. Swapped it to Win11 + Mac
134
+ (`2026_Aug_PrudentialMacSetupGuide_ZCC.pdf` attached as `PrudentialMacSetupGuide.pdf`) and
135
+ dropped the return doc (TRUE-81393). Then made the rule consistent across every stage: added the
136
+ return guide to the order-created Reclaim branch (previously zero attachments) and to the
137
+ Breakfix/Swap/Label catch-all; added it for every non-New type in **both** shipped crons, placed
138
+ ahead of the `!= 'Reclaim'` guard so Reclaim actually receives it; and removed it from the
139
+ New-hire shipped email in both. Converted the last direct `file_get_contents` PDF fetches in
140
+ `transmissions_to_netsuite.php` to `safeFetchPdf()` with guarded write+attach. Added
141
+ `test_new_hire_email.php` as a write-free manual harness (bala)
@@ -6,16 +6,18 @@ project: Worker
6
6
  client: prudential
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-07-10
10
- owners: ["rgirish"]
9
+ updated: 2026-08-28
10
+ owners: ["rgirish", "bala"]
11
11
  files:
12
12
  - worker/crons/toga2/prudential/transmit_ordershipped_updates_prudential.php
13
+ - worker/crons/toga2/prudential/transmissions_to_netsuite.php
13
14
  - worker/crons/toga2/prudential/transmit_closecomplete_updates_prudential.php
14
15
  - worker/crons/toga2/prudential/transmit_rejected_cancelled_updates_prudential.php
15
16
  - worker/crons/toga2/prudential/generate_sales_and_purchase_orders_from_service_requests.php
16
17
  - worker/crons/toga2/prudential_beta/generate_sales_and_purchase_orders_from_service_requests.php
17
18
  - worker/crons/notifications/reports/prudential_exception_report.php
18
19
  related:
20
+ - order-email-pdf-attachments.md
19
21
  - ../profile.md
20
22
  ---
21
23
 
@@ -39,9 +41,13 @@ Iterates the same POs collected in Phase 1. For each:
39
41
  1. Fetches PO details via TOGa API (`GET /purchase-orders/<uuid>`).
40
42
  2. Fetches tracking number via TOGa API (`GET /tracking-numbers`).
41
43
  3. Builds and sends the shipped email based on `serviceRequestType`:
42
- - **New** — "Your new hire equipment has shipped" with 3 PDF attachments (Win11 guide, return instructions, Mac setup guide).
43
- - **Breakfix / Refresh** — "Your equipment has been shipped" with 2 PDF attachments (Win11 + Mac setup guide), skipped for Reclaim.
44
- - **Reclaim** "Your equipment has been shipped" with no PDF attachments.
44
+ - **New** — "Your new hire equipment has shipped", Win11 + Mac setup guides.
45
+ - **Everything else** — "Your equipment has been shipped", return-instructions guide for
46
+ every type (including Reclaim), plus Win11 + Mac for the non-Reclaim types.
47
+
48
+ **The attachment set changed on 2026-08-27** — which guide goes on which email, at every
49
+ stage of the order, is now owned by `order-email-pdf-attachments.md`. Read that before
50
+ changing any attachment here.
45
51
  4. Stamps `c_dtEmailSentOrderShipped = NOW()` on the PO.
46
52
 
47
53
  ## Data model
@@ -80,6 +86,18 @@ TrackingNumbers WHERE purchaseOrderId = PurchaseOrders.id AND TrackingNumbers.st
80
86
  orders that genuinely qualified for a delivery email. Over 2026-07-01..07-09 this dropped 6 false
81
87
  positives (25 → 19 flagged) while retaining all 19 genuine discrepancies.
82
88
 
89
+ ## A second cron sends the same shipped email
90
+
91
+ `transmissions_to_netsuite.php` (also `active: 1`) builds and sends the **same** shipped email —
92
+ same subjects, same branch shape — and also stamps `c_dtTransmittedOrderShippedUpdateToPrudential`.
93
+ It is easy to miss: it is not named like the other `transmit_*` crons. Any change to the shipped
94
+ email's content or attachments must be applied to **both** files or the two senders drift.
95
+
96
+ It was also the last Prudential cron still calling `file_get_contents()` directly for its PDFs and
97
+ calling `addAttachment()` unconditionally — so a failed S3 pull threw and killed the batch, and a
98
+ false return would have mailed a 0-byte file under a real guide's name. Converted to
99
+ `safeFetchPdf()` with per-PDF guards on 2026-08-27.
100
+
83
101
  ## Sibling scripts sharing this pattern
84
102
  The same two-phase (transmit-then-email) shape and the same PDF-fetch fragility exist in three
85
103
  sibling crons, all fixed the same way this session (2026-07-06):
@@ -175,6 +193,13 @@ stamp (crash hits the next iteration). With multiple New-type POs, a crash on PO
175
193
  all subsequent POs in the same run.
176
194
 
177
195
  ## Change history
196
+ - 2026-08-27 — Extended the `safeFetchPdf()` + per-PDF-guard pattern to
197
+ `transmissions_to_netsuite.php`, the last cron still fetching PDFs with bare
198
+ `file_get_contents()` and attaching unconditionally. Also recorded that this cron is a **second
199
+ sender of the shipped email** alongside `transmit_ordershipped_updates_prudential.php`. The
200
+ shipped email's attachment set was reworked the same day to match the new client-agreed rule
201
+ (New = setup guides only; every other type gets the return guide, Reclaim included) — see
202
+ order-email-pdf-attachments.md (bala)
178
203
  - 2026-07-10 — Third regression of the "Order Shipped / Order Delivered Email Not Sent" symptom.
179
204
  True trigger: `createAssetAndRetryPut()` called the nonexistent `App_Database::escapeString()`
180
205
  (~line 158 in `transmit_ordershipped` + `transmit_closecomplete`) — a fatal `Error` under E_ALL
@@ -27,6 +27,7 @@ related:
27
27
  - features/device-information-import-and-contact-linking.md
28
28
  - workflows/http2-alb-workaround.md
29
29
  - features/supply-orders-csv-export.md
30
+ - features/order-email-pdf-attachments.md
30
31
  ---
31
32
 
32
33
  ## Summary
@@ -63,7 +64,11 @@ order-status transmissions.
63
64
  - PO lifecycle is tracked by `purchaseOrderStageId`. The `dtAcknowledged` column is **unused
64
65
  (always NULL)** — do not key logic on it.
65
66
 
66
- `ServiceRequests.serviceRequestTypeId`: **1 = New Hire, 2 = Breakfix, 3 = Refresh, 5 = Reclaim**.
67
+ `ServiceRequests.serviceRequestTypeId` (full set, verified against prod `Client_Prudential`
68
+ 2026-08-27): **1 = New, 2 = Breakfix, 3 = Refresh, 4 = Swap, 5 = Reclaim, 6 = Label**.
69
+ The order-created cron branches on this id; the shipped/delivered crons branch on the type
70
+ **name** string (`serviceRequest.serviceRequestType.name`), so 4 and 6 only ever reach an
71
+ `else` branch.
67
72
 
68
73
  ## Gotchas
69
74
  - **Commerce (`prudential.togacommerce.com`):** Prudential's corporate TLS-inspection proxy
@@ -98,6 +103,8 @@ order-status transmissions.
98
103
  - features/transmit-ordershipped-email.md
99
104
  - features/device-information-import-and-contact-linking.md — device-sync cron, unit→contact
100
105
  linking, the "null null" contact-export incident + dedup/backfill remediation.
106
+ - features/order-email-pdf-attachments.md — which PDF guide is attached to which customer
107
+ order email at which stage, the S3 asset list, and the four crons that send them.
101
108
  - features/supply-orders-csv-export.md — the Prudential-only Sales Orders CSV export in TOGa 2.5
102
109
  Supply (`toga25-supply`): its two queries, why contacts join `SalesOrders.contactId` (not
103
110
  `Units.contactId`, NULL until Dell assigns the asset), and the `ServiceRequests_Units` fan-out.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.685",
3
+ "version": "1.0.686",
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",