toga-ai 1.0.706 → 1.0.708
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/library/INDEX.md +1 -0
- package/knowledge/1.0/apps/library/features/email-queue-attachments.md +6 -2
- package/knowledge/1.0/apps/library/features/email-test-mode-and-write-free-sends.md +128 -0
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/nychh/INDEX.md +1 -1
- package/knowledge/clients/nychh/features/transfer-order-inventory-quantities.md +95 -11
- package/knowledge/clients/prudential/INDEX.md +1 -1
- package/knowledge/clients/prudential/features/order-email-pdf-attachments.md +120 -17
- package/knowledge/clients/prudential/features/transmit-ordershipped-email.md +19 -1
- package/package.json +1 -1
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
| [Elite Freshservice Sync (library)](features/elite-freshservice-sync.md) | `App_Api_Toga2` in `library/app/api/toga2.php` orchestrates bidirectional sync between TOGA 2 and TOGaDesk. | library/app/api/toga2.php |
|
|
12
12
|
| [App_Email Queued Sending & Attachments (Common.EmailsQueued)](features/email-queue-attachments.md) | `App_Email::send()` can either send **inline** (PHPMailer talks to SES right there) or **queue** the message: `base64(serialize($this))` is inserted into `Commo | library/app/email.php, library/phpmailer/class.phpmailer.php, worker/crons/notifications/infrastructure/send_emails.php, worker/crons/notifications/covid/send_covid_pending_vaccination_approval.php, togadesk/desk/includes/functions.php |
|
|
13
13
|
| [Branded HTML Email Templates (App_Email_Template)](features/email-templates.md) | `App_Email_Template` (`app/email/template.php`) is the base class for branded HTML emails in the 1.0 (`App_`) framework. | library/app/email/template.php, library/app/email/agilant.php |
|
|
14
|
+
| [App_Email Side Effects & Test Mode (how to send a real email that writes nothing)](features/email-test-mode-and-write-free-sends.md) | `App_Email::send()` is **not** side-effect free. | library/app/email.php, library/app/framework.php |
|
|
14
15
|
| [Error Capture in 1.0 (App_Error_Capture → shared 2.0 Logs DB)](features/error-capture-1-0.md) | The 1.0 side of the platform error-reporting pipeline (TRUE-78188). | library/app/error/capture.php, library/app/error.php, library/app/exception/business.php, library/app/api/toga2.php, library/app/cloud.php, worker/config.worker.ini, worker/crons/toga2/compass/workflow/1_transmit_compass_sales_orders_to_mits.php |
|
|
15
16
|
| [HTTP 500 Error Monitor (App_SystemMonitor_500Error) — and why its \"Error Type\" is not a diagnosis](features/http-500-error-monitor.md) | `App_SystemMonitor_500Error` (`library/app/systemmonitor/500error.php`, title **"HTTP 500 Error Alert"**) is the 1.0 system monitor that watches **`Logs.Api` fo | library/app/systemmonitor/500error.php, worker/crons/infrastructure/system_monitors.php, api2/Controller/Index.php, _underscore/Error.php |
|
|
16
17
|
| [1.0 MVC Page Pattern & New-App Skeleton](features/mvc-page-pattern-and-app-skeleton.md) | This is the **reusable recipe for standing up a new 1.0 (`App_`) application** and for adding pages to one — the folder-based MVC routing, the page lifecycle, t | library/app/framework.php, library/app/frameworkindex.php, library/app/mvc.php, library/app/database.php, library/app/model.php, library/app/config.php |
|
|
@@ -6,8 +6,8 @@ project: Library
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
10
|
-
owners: ["mhammontree"]
|
|
9
|
+
updated: 2026-08-31
|
|
10
|
+
owners: ["mhammontree", "bala"]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/email.php
|
|
13
13
|
- library/phpmailer/class.phpmailer.php
|
|
@@ -16,6 +16,7 @@ files:
|
|
|
16
16
|
- togadesk/desk/includes/functions.php
|
|
17
17
|
related:
|
|
18
18
|
- ./email-templates.md
|
|
19
|
+
- ./email-test-mode-and-write-free-sends.md
|
|
19
20
|
- ../architecture.md
|
|
20
21
|
- ../../togadesk/features/notifications.md
|
|
21
22
|
---
|
|
@@ -105,6 +106,9 @@ togadesk first, let the queue drain empty, *then* worker.
|
|
|
105
106
|
no DB, no SMTP, confirmed failing against pre-fix HEAD.
|
|
106
107
|
|
|
107
108
|
## Change history
|
|
109
|
+
- 2026-08-31 — Cross-linked the companion doc on `send()`'s other side effects (the `Logs.Email`
|
|
110
|
+
write and `setIsLogEnabled(false)`, the `OUTBOUND_EMAIL_ENABLED` queue branch) and on how
|
|
111
|
+
`test_mode` rewrites the recipient list to `dev_email_to` (bala)
|
|
108
112
|
- 2026-08-20 — TRUE-80952: queued attachments now embed base64 bytes (`embedAttachmentsForQueue()`
|
|
109
113
|
+ `AddStringAttachment`) instead of paths that do not exist on the drain host; added
|
|
110
114
|
`MAX_QUEUED_ATTACHMENT_BYTES` with inline-send fallback and a scoped warning guard around the
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: App_Email Side Effects & Test Mode (how to send a real email that writes nothing)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: library
|
|
5
|
+
project: Library
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-08-31
|
|
10
|
+
owners: ["bala"]
|
|
11
|
+
files:
|
|
12
|
+
- library/app/email.php
|
|
13
|
+
- library/app/framework.php
|
|
14
|
+
related:
|
|
15
|
+
- ./email-queue-attachments.md
|
|
16
|
+
- ./email-templates.md
|
|
17
|
+
- ../architecture.md
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Summary
|
|
21
|
+
|
|
22
|
+
`App_Email::send()` is **not** side-effect free. By default it INSERTs a log row, and under
|
|
23
|
+
several ordinary conditions it INSERTs a queue row and sends nothing at all. Separately, it
|
|
24
|
+
**rewrites the recipient list** in test mode. Anyone writing a dry-run script, a template
|
|
25
|
+
preview, or a "let me just fire one test email" harness in the 1.0 tier needs both facts, or
|
|
26
|
+
they will either pollute a production table or wonder why the intended recipient never got the
|
|
27
|
+
mail.
|
|
28
|
+
|
|
29
|
+
Verified against `library/app/email.php` on 2026-08-31.
|
|
30
|
+
|
|
31
|
+
## What `send()` writes
|
|
32
|
+
|
|
33
|
+
| Write | When | How to suppress |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `Logs.Email` row (`App_Model_Logs_Email`) | every **inline** send, success or failure | `setIsLogEnabled(false)` |
|
|
36
|
+
| `Common.EmailsQueued` row (`App_Model_Common_EmailsQueued`) | `queueForSending` truthy, **or** outbound email is disabled and `sendEmailEvenIfOutboundEmailIsDisabled` is false | `setQueueForSending(false)` + `sendEmailEvenIfOutboundEmailIsDisabled = true` |
|
|
37
|
+
|
|
38
|
+
The queue-vs-send decision is a single condition (`email.php` ~L313):
|
|
39
|
+
|
|
40
|
+
```php
|
|
41
|
+
if ($this->queueForSending || (!$this->sendEmailEvenIfOutboundEmailIsDisabled && !$isOutboundEmailEnabled)) {
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`$isOutboundEmailEnabled` is read from the `Defaults` table
|
|
45
|
+
(`setting = 'OUTBOUND_EMAIL_ENABLED'`), so **an environment can silently convert your "send" into
|
|
46
|
+
a queue insert** with no code change on your side. The log write is inside the `else` branch, so a
|
|
47
|
+
queued message writes the queue row and **no** log row.
|
|
48
|
+
|
|
49
|
+
## The three switches for a genuinely write-free send
|
|
50
|
+
|
|
51
|
+
All three are required — any one of them left at its default reintroduces a write or swallows the
|
|
52
|
+
send:
|
|
53
|
+
|
|
54
|
+
1. `setQueueForSending(false)` — do not insert into `Common.EmailsQueued`.
|
|
55
|
+
2. `sendEmailEvenIfOutboundEmailIsDisabled = true` — do not fall into the queue branch just
|
|
56
|
+
because the `Defaults` flag is off.
|
|
57
|
+
3. `setIsLogEnabled(false)` — do not insert into `Logs.Email`.
|
|
58
|
+
|
|
59
|
+
## `App_Email` only ever *narrows* the recipient list
|
|
60
|
+
|
|
61
|
+
This is worth stating plainly because people assume the opposite when a test email "goes
|
|
62
|
+
somewhere unexpected". `send()` does exactly two things to the addresses you set:
|
|
63
|
+
|
|
64
|
+
- **Suppression** — every `to`/`cc`/`bcc` entry is dropped if
|
|
65
|
+
`App_Model_Common_EmailSuppressionList::isOkayToSendEmailTo()` says no.
|
|
66
|
+
- **Test mode** — if `forceInTestMode` is set **or** config `internal.test_mode == 1`, the whole
|
|
67
|
+
`to`/`cc`/`bcc` list is **discarded** and replaced with the single config value
|
|
68
|
+
`email.dev_email_to`. The body is prefixed with a red `DEVELOPMENT MODE - Intended Recipients:
|
|
69
|
+
…` banner naming who *would* have received it. With `test_mode = 1` and no `dev_email_to`
|
|
70
|
+
configured, `send()` **throws**.
|
|
71
|
+
|
|
72
|
+
There is **no** hidden bcc, no admin copy, no silent additional recipient anywhere in `send()`.
|
|
73
|
+
If someone received a test message, they were on your list or they are `dev_email_to`.
|
|
74
|
+
|
|
75
|
+
**Practical consequence:** a local/alpha/beta config has `test_mode = 1`, so *only* `dev_email_to`
|
|
76
|
+
ever receives your test. To have a second person actually receive a test message you must run the
|
|
77
|
+
script **on the worker server**, whose `config.worker.ini` has `test_mode = 0` — and at that point
|
|
78
|
+
your recipient list is real, so get it right.
|
|
79
|
+
|
|
80
|
+
## `cronInitialization()` writes too
|
|
81
|
+
|
|
82
|
+
`App_Framework::cronInitialization()` (`library/app/framework.php` ~L689) INSERTs a
|
|
83
|
+
`CronJobExecutions` row before your code runs (and UPDATEs it at the end). A write-free script
|
|
84
|
+
must **not** call it. It also does the two things you still need, so do them yourself:
|
|
85
|
+
|
|
86
|
+
```php
|
|
87
|
+
set_time_limit(900);
|
|
88
|
+
date_default_timezone_set('America/Chicago');
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Call `App_Framework_Worker::initialize()` (or the app's equivalent) for config/registry/DB, and
|
|
92
|
+
stop there.
|
|
93
|
+
|
|
94
|
+
## Recipe — a dry-run email script in the 1.0 worker tier
|
|
95
|
+
|
|
96
|
+
1. Bootstrap with `App_Framework_Worker::initialize()` only; set `set_time_limit()` and
|
|
97
|
+
`date_default_timezone_set()` yourself. **Do not** call `cronInitialization()`.
|
|
98
|
+
2. Read your sample data with a **single SELECT** — no models that `save()`, no api2 calls.
|
|
99
|
+
3. Build the mail exactly as the real code does (same template class, same subject, same S3
|
|
100
|
+
fetches) so you are testing the real body, not a paraphrase of it.
|
|
101
|
+
4. Set the three switches above.
|
|
102
|
+
5. Print, before sending, which attachments resolved and their byte sizes — a missing PDF is
|
|
103
|
+
otherwise invisible (see `email-queue-attachments.md`: PHPMailer 5 sends anyway).
|
|
104
|
+
|
|
105
|
+
Run it with the framework repos on the include path:
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
php -d include_path=".;<library>;<_underscore>" <path/to/script>.php
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Gotchas
|
|
112
|
+
|
|
113
|
+
- **A "successful" local test proves less than it looks.** `test_mode = 1` collapses both the
|
|
114
|
+
queue path (see `email-queue-attachments.md`) and the recipient list. Two different behaviors
|
|
115
|
+
you cannot exercise locally.
|
|
116
|
+
- **Deleting the log write is not the same as being read-only.** `setIsLogEnabled(false)` only
|
|
117
|
+
suppresses `Logs.Email`; the queue insert has its own switch and fires *instead of* sending.
|
|
118
|
+
- **Dry-run harness scripts are throwaway.** Keep them out of the repo — they carry hardcoded
|
|
119
|
+
recipient lists and bypass the real gating. Build one, use it, delete it; document the
|
|
120
|
+
*technique* here rather than committing the file.
|
|
121
|
+
|
|
122
|
+
## Change history
|
|
123
|
+
- 2026-08-31 — Documented that `App_Email::send()` writes `Logs.Email` unless
|
|
124
|
+
`setIsLogEnabled(false)`, that the queue branch also fires when `OUTBOUND_EMAIL_ENABLED` is off,
|
|
125
|
+
that `cronInitialization()` INSERTs `CronJobExecutions`, and that `send()` only ever narrows
|
|
126
|
+
recipients (suppression list + `test_mode` → `dev_email_to` with a DEVELOPMENT MODE banner —
|
|
127
|
+
never adds one). Written up after building a throwaway write-free harness for the Prudential
|
|
128
|
+
order emails (bala)
|
package/knowledge/INDEX.md
CHANGED
|
@@ -4,7 +4,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
4
4
|
|
|
5
5
|
## 1.0 framework
|
|
6
6
|
|
|
7
|
-
- **library** (Library) _(framework core)_ —
|
|
7
|
+
- **library** (Library) _(framework core)_ — 23 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
|
|
8
8
|
- **worker** (Worker) — 31 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)
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
| [NYCHH inventory-adjustment → item-fulfillment linking (direct FK columns, REST)](features/netsuite-inventory-adjustment-fulfillment-link.md) | 1.0 | For NYCHH stock adjustments, the NetSuite **inventory adjustment** carries a link to the **Item Fulfillment** it corrects, on the multi-select body custom field | library/app/api/toga2.php, library/app/api/netsuite/rest.php, worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/crons/toga2/netsuite/sync_togasupply_hh.php, worker/crons/toga2/netsuite/diagnose_hh_import_inventory_adjustment.php |
|
|
6
6
|
| [NYCHH NetSuite → TransferOrders import (stocking-flag routing, PO bridges, origin location)](features/netsuite-transfer-order-import.md) | 1.0 | NYCHH's NetSuite **transfer orders are represented as NetSuite sales orders** (same as the GroWrk pattern), so the shared NetSuite → TOGa Supply importer (`App_ | library/app/api/toga2.php, library/app/api/netsuite/rest.php, library/app/netsuite.php, worker/crons/toga2/netsuite/sync_togasupply_hh.php, worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/crons/toga2/netsuite/diagnose_hh_stuck_transfer_order.php, dbchanges2/Core/2026-08-27a - TransferOrderUuidSearchableIdentifier.sql, dbchanges2/Client/2026-08-28a - TransferOrderItemsTimestampColumns.sql, dbchanges2/Client/2026-08-28b - TransferOrderItemsTimestampColumnsReposition.sql, dbchanges2/Client/2026-08-29 - ItemReceiptTransferOrderIdFieldPermission.sql, dbchanges2/Client/2026-08-29b - ItemReceiptItemTransferOrderItemIdFieldPermission.sql |
|
|
7
7
|
| [NYCHH PO links are UPSTREAM — the downstream SO→PO route returns empty](features/po-number-upstream-direction.md) | 2.0 | NYCHH's sales orders are created **from the customer's purchase order**, so their SO↔PO links live in the **upstream** table `PurchaseOrders_SalesOrders` (route | _underscore/Model/Client/SalesOrder.php, _underscore/Trait/Netsuite/SalesOrder.php, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/hooks/usePurchaseOrderDetails.ts, dbchanges2/Client/2026-08-11b - SalesOrderPurchaseOrdersField.sql, dbchanges2/Client/2026-08-25 - SalesOrderPurchaseOrdersFieldAllClients.sql |
|
|
8
|
-
| [NYCHH transfer-order 2.0 model — inventory quantities + V2 field enablement](features/transfer-order-inventory-quantities.md) | 2.0 | The 2.0 (`_underscore`) side of NYCHH transfer-order support: a **two-branch inventory quantity model** on the NYCHH `Item` override, plus the **client-override | _underscore/Model/Nychh/Item.php, _underscore/Model/Nychh/TransferOrder.php, _underscore/Model/Nychh/TransferOrderStage.php, _underscore/Model/Client/TransferOrder.php, _underscore/Model/Client/TransferOrderItem.php, dbchanges2/Client_Nychh/2026-08-27a - TransferOrderCustomFieldWritePermission.sql, dbchanges2/Client_Nychh/2026-08-27b - TransferOrderStageStatusIdentifier.sql, dbchanges2/Client_Nychh/2026-08-28c - TransferOrderRecordAcl.sql, dbchanges2/Client/2026-08-28b - TransferOrderItemsTimestamps.sql |
|
|
8
|
+
| [NYCHH transfer-order 2.0 model — inventory quantities + V2 field enablement](features/transfer-order-inventory-quantities.md) | 2.0 | The 2.0 (`_underscore`) side of NYCHH transfer-order support: a **two-branch inventory quantity model** on the NYCHH `Item` override, plus the **client-override | _underscore/Model/Nychh/Item.php, _underscore/Model/Nychh/PurchaseOrderItem.php, _underscore/Model/Nychh/TransferOrder.php, _underscore/Model/Nychh/TransferOrderStage.php, _underscore/Model/Client/TransferOrder.php, _underscore/Model/Client/TransferOrderItem.php, dbchanges2/Client_Nychh/2026-08-27a - TransferOrderCustomFieldWritePermission.sql, dbchanges2/Client_Nychh/2026-08-27b - TransferOrderStageStatusIdentifier.sql, dbchanges2/Client_Nychh/2026-08-28c - TransferOrderRecordAcl.sql, dbchanges2/Client/2026-08-28b - TransferOrderItemsTimestamps.sql |
|
|
9
9
|
| [NYC Health & Hospitals](profile.md) | 2.0 | NYC Health & Hospitals (NYCHH) is a TOGA 2.0 client on the `_underscore` platform, prod schema `Client_Nychh`. | dbchanges2/Client_Nychh/2026-08-18 - InventoryUnitsItemColumns.sql, dbchanges2/Client_Nychh/2026-08-24 - FixInventoryGroupingsUnitsTopologyOverrideIds.sql, dbchanges2/Client_Nychh/2026-08-27a - TransferOrderCustomFieldWritePermission.sql, dbchanges2/Client_Nychh/2026-08-27b - TransferOrderStageStatusIdentifier.sql, dbchanges2/Client_Nychh/2026-08-28a - TransferOrdersTableView.sql, dbchanges2/Client_Nychh/2026-08-28b - TransferOrdersNavigationEnable.sql, dbchanges2/Client_Nychh/2026-08-28c - TransferOrderRecordAcl.sql, dbchanges2/Client/2026-08-28b - TransferOrderItemsTimestamps.sql |
|
|
@@ -6,10 +6,11 @@ project: _Underscore
|
|
|
6
6
|
client: nychh
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-31
|
|
10
10
|
owners: [jcardinal, apeterson]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Nychh/Item.php
|
|
13
|
+
- _underscore/Model/Nychh/PurchaseOrderItem.php
|
|
13
14
|
- _underscore/Model/Nychh/TransferOrder.php
|
|
14
15
|
- _underscore/Model/Nychh/TransferOrderStage.php
|
|
15
16
|
- _underscore/Model/Client/TransferOrder.php
|
|
@@ -25,6 +26,8 @@ related:
|
|
|
25
26
|
- ../../../2.0/apps/toga25-supply/features/transfer-orders-page.md
|
|
26
27
|
- ../../growrk/features/transfer-order-flow.md
|
|
27
28
|
- ./netsuite-transfer-order-import.md
|
|
29
|
+
- ./netsuite-inventory-adjustment-fulfillment-link.md
|
|
30
|
+
- ../../../2.0/apps/_underscore/features/sales-order-purchase-order-bridge-direction.md
|
|
28
31
|
- ../profile.md
|
|
29
32
|
---
|
|
30
33
|
|
|
@@ -47,20 +50,84 @@ NYCHH-specific composition.
|
|
|
47
50
|
|
|
48
51
|
## NYCHH-only quantity calculations (two-branch inventory model)
|
|
49
52
|
|
|
50
|
-
Per the 2026-08-17 "TOGa Supply"
|
|
51
|
-
`_Model_Nychh_Item` (`_underscore/Model/Nychh/Item.php`),
|
|
52
|
-
|
|
53
|
+
Per the 2026-08-17 and 2026-08-27 "TOGa Supply" meetings. **NYCHH ONLY** — the overrides live on the
|
|
54
|
+
client models `_Model_Nychh_Item` (`_underscore/Model/Nychh/Item.php`) and, as of 2026-08-31, the new
|
|
55
|
+
`_Model_Nychh_PurchaseOrderItem` (`_underscore/Model/Nychh/PurchaseOrderItem.php`), not the base.
|
|
56
|
+
Stocking orders bring stock **in**, transfer orders send it **out**, so the quantities reflect that
|
|
57
|
+
direction:
|
|
53
58
|
|
|
54
59
|
| Quantity | Definition |
|
|
55
60
|
|---|---|
|
|
56
|
-
| **
|
|
61
|
+
| **received** (stock-in) | net Σ `InventoryAdjustmentItems.quantityAdjustBy` on the item's **stocking** fulfillments |
|
|
62
|
+
| **on hand** | `received` − `fulfilled` (transfer-order item fulfillments) |
|
|
57
63
|
| **available** | `on hand` − `committed` |
|
|
58
64
|
| **fulfilled** | Σ shipped transfer-order fulfillments |
|
|
59
|
-
| **backordered** | `ordered` − `received` |
|
|
65
|
+
| **backordered** | `ordered` − `received` (floored at 0) |
|
|
60
66
|
|
|
61
|
-
`_qtyOnOrder` was added alongside these. The stock-
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
`_qtyOnOrder` was added alongside these. The stock-out side reads transfer orders (which is why the
|
|
68
|
+
1.0 importer must populate `ItemFulfillmentItems.transferOrderItemId` — see the import doc).
|
|
69
|
+
|
|
70
|
+
### Stock-in now reads inventory adjustments, NOT PO item receipts (changed 2026-08-31)
|
|
71
|
+
|
|
72
|
+
Per the **2026-08-27 TOGa Supply meeting**: NetSuite has no "stocking" concept, so the only way stock
|
|
73
|
+
is added is an **inventory adjustment** linked to the stocking order's item fulfillment (never
|
|
74
|
+
directly to the PO). So `_Model_Nychh_Item::_qtyReceived` was changed to the **net
|
|
75
|
+
`SUM(InventoryAdjustmentItems.quantityAdjustBy)`**, joined
|
|
76
|
+
`InventoryAdjustmentItems.itemFulfillmentItemId → ItemFulfillmentItems.salesOrderItemId →
|
|
77
|
+
SalesOrderItems` where `SalesOrderItems.itemId = Items.id`. This **supersedes** the old PO-item-receipt
|
|
78
|
+
basis. Because on-hand and backordered both derive from `received`, they now use adjustments too.
|
|
79
|
+
|
|
80
|
+
- **The adjustment↔fulfillment link** is the direct FK `InventoryAdjustmentItems.itemFulfillmentItemId`
|
|
81
|
+
(recent; NULL on older adjustments). Details on
|
|
82
|
+
[NYCHH inventory-adjustment → item-fulfillment linking](./netsuite-inventory-adjustment-fulfillment-link.md).
|
|
83
|
+
- **Implicit stocking filter:** joining through `ItemFulfillmentItems.salesOrderItemId` counts only
|
|
84
|
+
**stocking-side** fulfillments — transfer-side fulfillment items have that column NULL — and all
|
|
85
|
+
Client_Nychh SalesOrders are stocking orders by the import routing.
|
|
86
|
+
- **⚠ Reads 0.00 today, by design.** Verified in prod Client_Nychh 2026-08-31: `InventoryAdjustmentItems`
|
|
87
|
+
has 292 rows but **0** carry `itemFulfillmentItemId`, and all 351 `ItemFulfillmentItems` are
|
|
88
|
+
transfer-side (`salesOrderItemId = 0`). INNER JOINs make an unlinked adjustment contribute 0, so every
|
|
89
|
+
item reads 0 **until the NetSuite integration backfills the link** — expected, not a bug.
|
|
90
|
+
|
|
91
|
+
## Per-PO-line quantities — `_Model_Nychh_PurchaseOrderItem` (new 2026-08-31)
|
|
92
|
+
|
|
93
|
+
`_Model_Nychh_PurchaseOrderItem extends _Model_Client_PurchaseOrderItem` gives the **same quantities
|
|
94
|
+
as Item level, but limited to a single purchase-order line** (previously these existed only at Item
|
|
95
|
+
level), which NYCHH needs for PO-specific fulfillment.
|
|
96
|
+
|
|
97
|
+
- **`_qtyReceived`** — adjustment stock-in (same as Item) but scoped to the PO line via
|
|
98
|
+
`PurchaseOrderItems_SalesOrderItems` (the PO→SO stocking bridge; see the bridge note below).
|
|
99
|
+
- **`_qtyCommitted`** — Transfer-Order based, scoped to the PO line via
|
|
100
|
+
`PurchaseOrderItems_TransferOrderItems`, mirroring the Item-level two-branch model.
|
|
101
|
+
- **`_qtyOnHand` / `_qtyAvailable` / `_qtyBackordered` / `_qtyOnOrder`** are **re-declared verbatim** on
|
|
102
|
+
the child so early-bound `self::` rebinds to the new leaf `_qtyReceived`/`_qtyCommitted` (established
|
|
103
|
+
framework pattern — a composite `FIELD_SQL` method that calls `self::` must be re-declared on the
|
|
104
|
+
child, not inherited). `_qtyBackordered` is floored at 0 with `GREATEST`, matching Item level.
|
|
105
|
+
|
|
106
|
+
> **⚠ Signature must match the parent EXACTLY** when overriding a `FIELD_SQL` static method — an added
|
|
107
|
+
> param type is a **fatal at class load** that `php -l` does NOT catch. Verified this session by
|
|
108
|
+
> php-reviewer + sql-reviewer.
|
|
109
|
+
|
|
110
|
+
### Which SO↔PO bridge scopes the PO line (they are two directional tables, not duplicates)
|
|
111
|
+
|
|
112
|
+
The item-level bridges mirror the header-level SO↔PO direction split documented on
|
|
113
|
+
[SO↔PO bridge direction](../../../2.0/apps/_underscore/features/sales-order-purchase-order-bridge-direction.md):
|
|
114
|
+
|
|
115
|
+
- `PurchaseOrderItems_SalesOrderItems` — **SO items created FROM a PO item** (PO→SO stocking direction);
|
|
116
|
+
the **populated/canonical** one for stocking (682 rows in prod). PO-line stock-in scopes on this.
|
|
117
|
+
- `SalesOrderItems_PurchaseOrderItems` — PO items created FROM a SO item (3 rows).
|
|
118
|
+
|
|
119
|
+
Verified 0 SO lines map to >1 PO line, so PO-line `received` **cannot double-count**. Optional deferred
|
|
120
|
+
hardening (not done): a `UNIQUE(purchaseOrderItemId, salesOrderItemId)` on the bridge as defense-in-depth.
|
|
121
|
+
|
|
122
|
+
- **⚠ PO-level outbound reads 0 until a separate backfill.** `_qtyCommitted` depends on
|
|
123
|
+
`PurchaseOrderItems_TransferOrderItems`, which is currently **empty (0 rows)** in prod — so PO-level
|
|
124
|
+
outbound reads 0 until the transfer-order import populates that bridge. Separate import gap, not this change.
|
|
125
|
+
|
|
126
|
+
### Index coverage / schema
|
|
127
|
+
|
|
128
|
+
No `dbchanges2` file — pure calculated `FIELD_SQL` in PHP. All correlation columns
|
|
129
|
+
(`InventoryAdjustmentItems.itemFulfillmentItemId`, `ItemFulfillmentItems.salesOrderItemId`,
|
|
130
|
+
`SalesOrderItems.itemId`, both bridge FKs) are already indexed.
|
|
64
131
|
|
|
65
132
|
## V2 field enablement — how the TransferOrder POST was made to work
|
|
66
133
|
|
|
@@ -153,11 +220,28 @@ pattern (default a NOT NULL non-writable column in the model, never send it) is
|
|
|
153
220
|
trait on the client-override model) AND the ACL/metadata rows exist.** Declaring without the ACL
|
|
154
221
|
row, or granting the ACL without the declaration, each fails with its own error — see the peeled
|
|
155
222
|
sequence above.
|
|
156
|
-
- **These quantity overrides are NYCHH-only.** They live on `_Model_Nychh_Item
|
|
157
|
-
the base `
|
|
223
|
+
- **These quantity overrides are NYCHH-only.** They live on `_Model_Nychh_Item` and
|
|
224
|
+
`_Model_Nychh_PurchaseOrderItem`; do not lift them to the base `_Model_Client_*` — other clients do
|
|
225
|
+
not have the stocking/transfer two-branch model.
|
|
226
|
+
- **A 0.00 quantity for NYCHH is expected today, not a bug.** Stock-in reads inventory-adjustment links
|
|
227
|
+
that are not backfilled yet, and PO-level outbound reads a still-empty
|
|
228
|
+
`PurchaseOrderItems_TransferOrderItems` bridge (see above).
|
|
158
229
|
|
|
159
230
|
## Change history
|
|
160
231
|
|
|
232
|
+
- 2026-08-31 — **Stock-in flipped from PO item receipts → inventory adjustments** (per the 2026-08-27
|
|
233
|
+
TOGa Supply meeting: NetSuite has no stocking concept, stock is added only by an inventory adjustment
|
|
234
|
+
linked to the stocking order's item fulfillment). Changed `_Model_Nychh_Item::_qtyReceived` to net
|
|
235
|
+
`SUM(InventoryAdjustmentItems.quantityAdjustBy)` joined
|
|
236
|
+
`itemFulfillmentItemId → ItemFulfillmentItems.salesOrderItemId → SalesOrderItems` (`itemId = Items.id`);
|
|
237
|
+
on-hand and backordered follow. Added the **NYCHH-only `_Model_Nychh_PurchaseOrderItem`** for per-PO-line
|
|
238
|
+
quantities (same logic scoped to the PO line via `PurchaseOrderItems_SalesOrderItems` for received and
|
|
239
|
+
`PurchaseOrderItems_TransferOrderItems` for committed; composite `FIELD_SQL` methods re-declared verbatim
|
|
240
|
+
to rebind early-bound `self::`; backordered floored with `GREATEST`). Both read **0.00 in prod today** by
|
|
241
|
+
design — the adjustment↔fulfillment links are not backfilled (292 adjustment items, 0 linked; all 351
|
|
242
|
+
fulfillment items transfer-side) and `PurchaseOrderItems_TransferOrderItems` is empty. No dbchanges2
|
|
243
|
+
(pure `FIELD_SQL`; correlation columns already indexed); double-count ruled out (0 SO lines map to >1 PO
|
|
244
|
+
line). `php -l` clean; php-reviewer 0 critical, sql-reviewer warnings resolved. (jcardinal)
|
|
161
245
|
- 2026-08-28 — Discovered that the transfer-order enablement on **prod and sandbox-client is
|
|
162
246
|
UNTRACKED**: record ACL for 312/313 (roles 1 and 3), their logic-group/expression chain, field ACL
|
|
163
247
|
for all 17 RecordFields, and the `TransferOrderItems.dtCreated`/`dtUpdated` columns all exist with
|
|
@@ -6,7 +6,7 @@
|
|
|
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
|
|
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 |
|
|
10
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 |
|
|
11
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 |
|
|
12
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 |
|
|
@@ -6,17 +6,17 @@ project: Worker
|
|
|
6
6
|
client: prudential
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-31
|
|
10
10
|
owners: ["bala"]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/prudential/generate_sales_and_purchase_orders_from_service_requests.php
|
|
13
13
|
- worker/crons/toga2/prudential/transmissions_to_netsuite.php
|
|
14
14
|
- worker/crons/toga2/prudential/transmit_ordershipped_updates_prudential.php
|
|
15
15
|
- worker/crons/toga2/prudential/transmit_closecomplete_updates_prudential.php
|
|
16
|
-
- worker/crons/toga2/prudential/test_new_hire_email.php
|
|
17
16
|
related:
|
|
18
17
|
- transmit-ordershipped-email.md
|
|
19
18
|
- ../profile.md
|
|
19
|
+
- ../../../1.0/apps/library/features/email-test-mode-and-write-free-sends.md
|
|
20
20
|
---
|
|
21
21
|
|
|
22
22
|
## Summary
|
|
@@ -83,6 +83,64 @@ all three returned HTTP 200 (verified 2026-08-27). Two of them are **renamed on
|
|
|
83
83
|
The Mac guide dominates: a new-hire email is roughly **11 MB** once base64-encoded. Grepping for
|
|
84
84
|
the attached-as filename will not find the S3 object — search the S3 basename.
|
|
85
85
|
|
|
86
|
+
## The verbiage has to match what actually attached
|
|
87
|
+
|
|
88
|
+
Every attach is guarded by `if ($pdfGuideX !== false)`, but until 2026-08-31 the `$attachmentText`
|
|
89
|
+
paragraph that *describes* the attachments was unconditional. A failed S3 fetch therefore produced
|
|
90
|
+
an email whose copy pointed the recipient at guides that were not on the message — and it always
|
|
91
|
+
said "guides" plural even when a single guide attached.
|
|
92
|
+
|
|
93
|
+
The rule now, in every branch that was touched: **derive the sentence from the guards, not from
|
|
94
|
+
the intent.**
|
|
95
|
+
|
|
96
|
+
- Count (or list) the guides that actually attached.
|
|
97
|
+
- Nothing attached → `$attachmentText = ''` and the paragraph disappears entirely.
|
|
98
|
+
- One guide → "guide" / "is provided"; two → "guides" / "are provided"
|
|
99
|
+
(`$guideNoun`, `$guideVerb`).
|
|
100
|
+
- Name only the devices whose guide attached, built from `$pdfGuide11` / `$pdfGuideMac` and joined
|
|
101
|
+
with `implode(' and ', $attachedGuideDevices)`.
|
|
102
|
+
|
|
103
|
+
One subtlety in the Breakfix/Swap/Label order-created branch: that branch attaches the Windows 11
|
|
104
|
+
guide **and** the return-instructions doc, but the sentence is a *getting started* sentence. It is
|
|
105
|
+
therefore tied to `$pdfGuide11` alone — the return doc is not a getting started guide and must not
|
|
106
|
+
make that sentence appear:
|
|
107
|
+
|
|
108
|
+
```php
|
|
109
|
+
$attachmentText = ($pdfGuide11 !== false ? 'When you do end up receiving equipment, you can refer to the attached guide for step-by-step instructions on getting started.' : '');
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Fixed in four places (TRUE-81393, PR #1707, merged to `_production` 2026-08-31): the new-hire
|
|
113
|
+
order-created branch, the Breakfix/Swap/Label order-created catch-all, and the new-hire **and**
|
|
114
|
+
non-new-hire blocks in **both** shipped crons.
|
|
115
|
+
|
|
116
|
+
## Email template anatomy (and the dead per-item rows)
|
|
117
|
+
|
|
118
|
+
**The ordered items are never listed in these emails.** In each of the four crons, the
|
|
119
|
+
`$data = $data . "<tr>…"` append that sits *inside* the `foreach (... as $item)` loop is wrapped in
|
|
120
|
+
a `/* … */` comment. The loop still runs, and it still calls the Etilize/spexlive catalog API
|
|
121
|
+
(`ws-na1.spexlive.net/service/rest/catalog`) to resolve a product thumbnail into `$url` — but
|
|
122
|
+
nothing it produces reaches the message. Each body is only a header chunk plus a footer chunk. If
|
|
123
|
+
you are asked "why doesn't the item show up in the email", this is why; if you are asked to add
|
|
124
|
+
items back, the markup is already there, commented, and the thumbnail lookup already works.
|
|
125
|
+
|
|
126
|
+
There are **8 distinct templates**, each roughly 150–300 lines of inline HTML:
|
|
127
|
+
|
|
128
|
+
| Stage | Templates | Where |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| Order created | 4 — new hire, reclaim, refresh, Breakfix/Swap/Label catch-all | `generate_sales_and_purchase_orders_from_service_requests.php` |
|
|
131
|
+
| Shipped | 2 — new hire, non-new-hire | duplicated **identically** in `transmissions_to_netsuite.php` and `transmit_ordershipped_updates_prudential.php` |
|
|
132
|
+
| Delivered | 2 — new hire, non-new-hire | `transmit_closecomplete_updates_prudential.php` |
|
|
133
|
+
|
|
134
|
+
Only the new-hire and Breakfix/Swap/Label order-created templates carry the (commented) item loop;
|
|
135
|
+
the reclaim and refresh order-created branches have no item loop at all.
|
|
136
|
+
|
|
137
|
+
Variables the templates interpolate — useful if you are reproducing one outside its cron:
|
|
138
|
+
|
|
139
|
+
- **Order created** — fields off `$rowServiceRequest` (incl. `reqNumber`), plus `$state`,
|
|
140
|
+
`$salesOrderNumber` and `$attachmentText`.
|
|
141
|
+
- **Shipped / delivered** — flat locals: `$name`, `$line1`, `$line2`, `$city`, `$state`, `$zip`,
|
|
142
|
+
`$orderNumber`, `$reqNumber`, `$trackingNumber` / `$trackingNumbers[$index]`, `$attachmentText`.
|
|
143
|
+
|
|
86
144
|
## How to change an attachment safely
|
|
87
145
|
|
|
88
146
|
1. Every fetch goes through the file-scope `safeFetchPdf()` helper, never bare
|
|
@@ -100,24 +158,59 @@ the attached-as filename will not find the S3 object — search the S3 basename.
|
|
|
100
158
|
precisely the type that most needs the return instructions. That guard is how Reclaim ended up
|
|
101
159
|
receiving zero attachments.
|
|
102
160
|
|
|
103
|
-
## Testing
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
161
|
+
## Testing these emails without writing anything
|
|
162
|
+
|
|
163
|
+
**There is no committed test harness — do not go looking for one.** Three throwaway scripts
|
|
164
|
+
(`test_new_hire_email.php`, `test_prudential_order_emails.php`,
|
|
165
|
+
`test_prudential_email_templates.php`) were written under `crons/toga2/prudential/` on 2026-08-31
|
|
166
|
+
to mail every stage/type combination with the real templates and the real S3 PDFs. They were
|
|
167
|
+
**never committed and are no longer in the working tree**. What is durable is the technique.
|
|
168
|
+
|
|
169
|
+
A harness that mails a real, byte-identical copy of one of these emails while writing **nothing**
|
|
170
|
+
to the database needs all of this:
|
|
171
|
+
|
|
172
|
+
- `setQueueForSending(false)`, `sendEmailEvenIfOutboundEmailIsDisabled = true`, and
|
|
173
|
+
`setIsLogEnabled(false)` on the `App_Email` object. All three — the first two keep it out of
|
|
174
|
+
`Common.EmailsQueued`, the third keeps it out of `Logs.Email`. Full detail in
|
|
175
|
+
[App_Email Side Effects & Test Mode](../../../1.0/apps/library/features/email-test-mode-and-write-free-sends.md).
|
|
176
|
+
- `App_Framework_Worker::initialize()` **only**. Do **not** call
|
|
177
|
+
`App_Framework::cronInitialization()` — it INSERTs a `CronJobExecutions` row. Set
|
|
178
|
+
`set_time_limit()` and `date_default_timezone_set()` yourself instead.
|
|
179
|
+
- One read-only SELECT for the sample data (below), rather than the cron's api2 fetches.
|
|
180
|
+
- Print each PDF's resolved/missing state and byte size **before** sending; PHPMailer 5 will send
|
|
181
|
+
the message anyway if an attachment is unreadable.
|
|
182
|
+
|
|
183
|
+
**Run it on the worker server if anyone other than you needs to receive it.** Local and beta
|
|
184
|
+
configs have `internal.test_mode = 1`, which discards the recipient list and sends only to
|
|
185
|
+
`email.dev_email_to`. `config.worker.ini` on the worker server has `test_mode = 0`.
|
|
186
|
+
|
|
187
|
+
### Read-only query for a real sample request
|
|
188
|
+
|
|
189
|
+
One row per request type is enough to exercise every template. Join `ServiceRequests` to
|
|
190
|
+
`Addresses` on `deliverToAddressId`, `States` on `Addresses.stateId`, `Contacts` on
|
|
191
|
+
`requestedForContactId`, and **LEFT JOIN** `SalesOrders` on `SalesOrders.serviceRequestId`
|
|
192
|
+
(left, because an unprocessed request has no SO yet). Select
|
|
193
|
+
`ServiceRequests.c_reqNumber AS reqNumber`, `Contacts.firstName`, the address parts,
|
|
194
|
+
`States.code`, and `SalesOrders.number` — that is the exact set the shipped/delivered templates
|
|
195
|
+
interpolate.
|
|
196
|
+
|
|
197
|
+
All six request types have live rows in prod `Client_Prudential` (verified 2026-08-31); **Swap has
|
|
198
|
+
only 2 rows**, the rest have thousands, so filter narrowly for Swap or you will miss it.
|
|
118
199
|
|
|
119
200
|
## Gotchas / known gaps
|
|
120
201
|
|
|
202
|
+
- **`transmit_closecomplete_updates_prudential.php` still over-promises (~line 447).** Its
|
|
203
|
+
delivered email hardcodes "Guides for Windows 11 and MacBook devices are provided" with no
|
|
204
|
+
reference to whether either PDF actually attached — the exact bug fixed everywhere else on
|
|
205
|
+
2026-08-31. Left alone deliberately to keep PR #1707's diff to files already being changed.
|
|
206
|
+
**Worth a follow-up ticket.**
|
|
207
|
+
- **`transmissions_to_netsuite.php` also sends *delivered* emails — with no attachments at all.**
|
|
208
|
+
It carries its own "New hire equipment has been delivered" / "Your equipment has been delivered"
|
|
209
|
+
blocks (~L1765 and ~L2149) that make **zero** `addAttachment()` calls, while
|
|
210
|
+
`transmit_closecomplete_updates_prudential.php` sends the **same two subjects** with the guides
|
|
211
|
+
attached. **Whether both fire for the same order was not established** — if they do, the
|
|
212
|
+
recipient gets a duplicate delivered email, one of them attachment-less. Check this before
|
|
213
|
+
changing either.
|
|
121
214
|
- **`prudential_beta/` does not match prod.**
|
|
122
215
|
`crons/toga2/prudential_beta/generate_sales_and_purchase_orders_from_service_requests.php` is
|
|
123
216
|
`active: 1` on the same schedule but points at the beta DB and still references the retired
|
|
@@ -129,6 +222,16 @@ php -d include_path=".;<library>;<_underscore>" toga2/prudential/test_new_hire_e
|
|
|
129
222
|
and was deliberately **not** touched.
|
|
130
223
|
|
|
131
224
|
## Change history
|
|
225
|
+
- 2026-08-31 — CodeRabbit review follow-up (TRUE-81393, PR #1707, merged to `_production`): the
|
|
226
|
+
`$attachmentText` paragraph was unconditional while every attach was guarded, so a failed S3
|
|
227
|
+
fetch mailed copy referencing guides that were not attached, always pluralised. Now derived from
|
|
228
|
+
the guards in four places (new-hire and Breakfix/Swap/Label order-created branches, plus the
|
|
229
|
+
new-hire and non-new-hire blocks in both shipped crons), with the getting-started sentence tied
|
|
230
|
+
to the Windows 11 guide only. Also recorded: the per-item rows are commented out in all four
|
|
231
|
+
crons (items are never listed in any of these emails), the 8-template inventory, the
|
|
232
|
+
write-nothing test technique (the three throwaway harness scripts were never committed), and two
|
|
233
|
+
open items — the same over-promise still live in the delivered cron ~L447, and a second,
|
|
234
|
+
attachment-less delivered sender in `transmissions_to_netsuite.php` (bala)
|
|
132
235
|
- 2026-08-27 — Client-reported: the new-hire order-created email attached the return-instructions
|
|
133
236
|
PDF and carried no Mac guide. Swapped it to Win11 + Mac
|
|
134
237
|
(`2026_Aug_PrudentialMacSetupGuide_ZCC.pdf` attached as `PrudentialMacSetupGuide.pdf`) and
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: prudential
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-31
|
|
10
10
|
owners: ["rgirish", "bala"]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/prudential/transmit_ordershipped_updates_prudential.php
|
|
@@ -93,6 +93,14 @@ same subjects, same branch shape — and also stamps `c_dtTransmittedOrderShippe
|
|
|
93
93
|
It is easy to miss: it is not named like the other `transmit_*` crons. Any change to the shipped
|
|
94
94
|
email's content or attachments must be applied to **both** files or the two senders drift.
|
|
95
95
|
|
|
96
|
+
**And it does not stop at the shipped email — it also sends the two *delivered* emails.**
|
|
97
|
+
`transmissions_to_netsuite.php` carries "Prudential: New hire equipment has been delivered"
|
|
98
|
+
(~L1765) and "Prudential: Your equipment has been delivered" (~L2149), the **same two subjects**
|
|
99
|
+
that `transmit_closecomplete_updates_prudential.php` sends — except the `transmissions_to_netsuite`
|
|
100
|
+
versions make **no `addAttachment()` calls at all**. Whether both crons fire for the same order was
|
|
101
|
+
not established (2026-08-31); if they do, the customer receives a duplicate delivered email, one
|
|
102
|
+
copy of which is missing the guides. Establish this before touching either delivered path.
|
|
103
|
+
|
|
96
104
|
It was also the last Prudential cron still calling `file_get_contents()` directly for its PDFs and
|
|
97
105
|
calling `addAttachment()` unconditionally — so a failed S3 pull threw and killed the batch, and a
|
|
98
106
|
false return would have mailed a 0-byte file under a real guide's name. Converted to
|
|
@@ -193,6 +201,16 @@ stamp (crash hits the next iteration). With multiple New-type POs, a crash on PO
|
|
|
193
201
|
all subsequent POs in the same run.
|
|
194
202
|
|
|
195
203
|
## Change history
|
|
204
|
+
- 2026-08-31 — TRUE-81393 / PR #1707 (merged to `_production`): the shipped email's
|
|
205
|
+
`$attachmentText` paragraph was unconditional while each attach was guarded, so a failed S3 pull
|
|
206
|
+
produced copy promising guides that were not attached — and it said "guides" even for a single
|
|
207
|
+
guide. Both blocks in this cron (new hire and non-new-hire) and both in
|
|
208
|
+
`transmissions_to_netsuite.php` now build the sentence from `$pdfGuide11`/`$pdfGuideMac`, naming
|
|
209
|
+
only what attached and dropping the paragraph when nothing did. Also discovered: the per-item
|
|
210
|
+
`<tr>` append inside the `foreach ($purchaseOrderItems as $item)` loop is **commented out** here
|
|
211
|
+
and in the sibling crons, so ordered items never appear in these emails (the loop still calls the
|
|
212
|
+
Etilize thumbnail API for nothing); and `transmissions_to_netsuite.php` additionally sends the
|
|
213
|
+
two **delivered** emails with no attachments. See order-email-pdf-attachments.md (bala)
|
|
196
214
|
- 2026-08-27 — Extended the `safeFetchPdf()` + per-PDF-guard pattern to
|
|
197
215
|
`transmissions_to_netsuite.php`, the last cron still fetching PDFs with bare
|
|
198
216
|
`file_get_contents()` and attaching unconditionally. Also recorded that this cron is a **second
|
package/package.json
CHANGED