toga-ai 1.0.270 → 1.0.272
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/standards/backend-php.md +30 -2
- package/knowledge/2.0/apps/worker2/INDEX.md +1 -0
- package/knowledge/2.0/apps/worker2/features/clickup-design-sprint-automation.md +111 -0
- package/knowledge/INDEX.md +2 -2
- package/knowledge/clients/compass-usa/INDEX.md +1 -0
- package/knowledge/clients/compass-usa/workflows/odp-order-pipeline-to-netsuite.md +113 -0
- package/knowledge/clients/compass-usa/workflows/order-lifecycle-and-data-integrity.md +1 -0
- package/knowledge/clients/prudential/INDEX.md +1 -1
- package/knowledge/clients/prudential/features/transmit-ordershipped-email.md +64 -14
- package/knowledge/clients/prudential/profile.md +10 -5
- package/package.json +1 -1
|
@@ -5,8 +5,8 @@ project: Library
|
|
|
5
5
|
client: shared
|
|
6
6
|
type: standard
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-06
|
|
9
|
-
owners: [jcardinal]
|
|
8
|
+
updated: 2026-07-06
|
|
9
|
+
owners: [jcardinal, rgirish]
|
|
10
10
|
files: []
|
|
11
11
|
related:
|
|
12
12
|
- ../apps/library/architecture.md
|
|
@@ -395,6 +395,34 @@ class Browser_Datagrid_Accounts extends Browser_Datagrid {
|
|
|
395
395
|
* The `@` error-suppression operator is acceptable only for defensive file I/O (e.g. reading an optional cache file); do not use it to hide real errors.
|
|
396
396
|
* Use structured logging to capture application behavior and errors.
|
|
397
397
|
|
|
398
|
+
### PHP warnings throw — guard fragile I/O, do not rely on `!== false`
|
|
399
|
+
|
|
400
|
+
The bootstrap sets `error_reporting(E_ALL)` and `App_Error::handleError` (`library/app/error.php`)
|
|
401
|
+
converts **every** PHP warning into an `ErrorException`. Consequences you must design around:
|
|
402
|
+
|
|
403
|
+
- Warning-emitting calls **throw at the call site** rather than returning their documented
|
|
404
|
+
failure value. `file_get_contents()` on an unreachable/403/timed-out URL raises an
|
|
405
|
+
`ErrorException` **inside** the call — so a following `if ($x !== false)` guard never runs.
|
|
406
|
+
- In a loop (e.g. a cron), one such throw aborts the **entire** remaining batch, not just the
|
|
407
|
+
current item. This is especially dangerous in two-phase "stamp then act" crons where earlier
|
|
408
|
+
phases have already committed state.
|
|
409
|
+
|
|
410
|
+
Wrap fragile I/O and return the failure value yourself:
|
|
411
|
+
|
|
412
|
+
```php
|
|
413
|
+
function safeFetchPdf(string $url) {
|
|
414
|
+
try {
|
|
415
|
+
return file_get_contents($url);
|
|
416
|
+
} catch (\ErrorException $e) {
|
|
417
|
+
error_log('PDF fetch failed: ' . $e->getMessage());
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Then guard each dependent step **independently** (not a joint `&&`) so one failure degrades
|
|
424
|
+
gracefully instead of dropping unrelated work or crashing the loop.
|
|
425
|
+
|
|
398
426
|
## Security Best Practices
|
|
399
427
|
|
|
400
428
|
### Input Validation and Sanitization
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [Worker (worker2) Architecture](architecture.md) | Worker (repo `worker2`) is an AWS Elastic Beanstalk **Worker Tier** application that processes background jobs. | worker2/Controller/Index.php, worker2/Worker/, worker2/LambdaFunctions/, _underscore/Worker.php |
|
|
6
6
|
| [ClickUp Connectivity Watchdog](features/clickup-connectivity-watchdog.md) | A cron watchdog that emails when the ClickUp integration looks disconnected during business hours. | worker2/Worker/Clickup/Health.php, worker2/Database/ClickupHealthWatchdog.sql |
|
|
7
|
+
| [ClickUp Design Sprint Automation (Final Design Outcome)](features/clickup-design-sprint-automation.md) | `_Worker_Clickup_Design` is meant to drive the design-sprint workflow in ClickUp via the API, replacing a set of native ClickUp automations. | worker2/Worker/Clickup/Design.php, worker2/Worker/Clickup.php |
|
|
7
8
|
| [ClickUp GitHub-tab Auto-linking & Ticket-id Branch Naming](features/clickup-github-autolink.md) | How ClickUp surfaces branches/PRs/commits in a ticket's **GitHub tab**, and the branch / PR-title naming convention that triggers it. | |
|
|
8
9
|
| [ClickUp Project & Opportunity Multi-List Routing](features/clickup-project-routing.md) | Routes ClickUp tasks into the correct **secondary multi-list memberships** based on their custom-field values, via the `clickup` webhook. | worker2/Worker/Clickup/Project.php, worker2/Worker/Clickup.php |
|
|
9
10
|
| [ClickUp Rich-Text Custom Fields via Quill Delta (API)](features/clickup-richtext-api.md) | ClickUp custom text fields (type `text` and long-text) support rich formatting only through a **Quill Delta** written to the undocumented `value_richtext` key o | test/@dave/clickup_md2delta.js, .claude/skills/plan-ticket/scripts/clickup.js |
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: ClickUp Design Sprint Automation (Final Design Outcome)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: worker2
|
|
5
|
+
project: Worker
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-07-06
|
|
10
|
+
owners: ["ajean"]
|
|
11
|
+
files:
|
|
12
|
+
- worker2/Worker/Clickup/Design.php
|
|
13
|
+
- worker2/Worker/Clickup.php
|
|
14
|
+
related:
|
|
15
|
+
- ./clickup-work-type-automation.md
|
|
16
|
+
- ./team-sprint-management.md
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Summary
|
|
20
|
+
`_Worker_Clickup_Design` is meant to drive the design-sprint workflow in ClickUp via the API,
|
|
21
|
+
replacing a set of native ClickUp automations. When a task's **Final Design Outcome**
|
|
22
|
+
drop-down is set to an actionable value, the worker either spawns an iteration task or
|
|
23
|
+
completes the task, and a daily cron reminds the Design Approver of pending reviews.
|
|
24
|
+
|
|
25
|
+
**Critical current state (as of 2026-07-06):** the worker's
|
|
26
|
+
`handleFinalDesignOutcome` has been **silently no-opping in production** — the native
|
|
27
|
+
ClickUp automations it was supposed to replace were never disabled and had been doing the
|
|
28
|
+
real work all along, masking the failure. Do not assume the API path is live. See Gotchas
|
|
29
|
+
and the open action item below before touching this code.
|
|
30
|
+
|
|
31
|
+
## Key files / entry points
|
|
32
|
+
- `Worker/Clickup/Design.php` → `_Worker_Clickup_Design` — the design-sprint engine.
|
|
33
|
+
- `handleFinalDesignOutcome(...)` — the entry that decides spawn vs. complete (currently a
|
|
34
|
+
silent no-op in prod).
|
|
35
|
+
- `resolveFinalDesignOutcomeOptionId(...)` (Design.php:298-317) — maps the drop-down value
|
|
36
|
+
to an option ID.
|
|
37
|
+
- `spawnIterationTask(...)` / `carryDesignApprover(...)` — duplicate the task and copy the
|
|
38
|
+
Design Approver.
|
|
39
|
+
- `completeTask(...)` — closes an approved task.
|
|
40
|
+
- `SendReviewReminders(...)` — daily reminder cron action.
|
|
41
|
+
- `TRUE_DESIGN_SPRINTS_FOLDER_ID` constant (Design.php:27,50).
|
|
42
|
+
- `Worker/Clickup.php` (dispatcher ~lines 822-875) — routes ClickUp webhooks; the Design
|
|
43
|
+
Approver / status guard lives at 822-833.
|
|
44
|
+
|
|
45
|
+
## How it works
|
|
46
|
+
1. A ClickUp webhook reaches `_Worker_Clickup`; the dispatcher (Clickup.php ~822-875) routes
|
|
47
|
+
design-folder events into `_Worker_Clickup_Design`.
|
|
48
|
+
2. The worker acts on **only two** Final Design Outcome values:
|
|
49
|
+
- **"Iterated – New Task"** (option `3f835ac8…`) → `spawnIterationTask` — duplicates the
|
|
50
|
+
ticket into a new iteration task and carries over the Design Approver.
|
|
51
|
+
- **"Approved"** (option `2d17868b…`) → `completeTask`.
|
|
52
|
+
3. The full Final Design Outcome option set is: In Progress, In Review, **Iterated – New
|
|
53
|
+
Task**, **Approved**, Cancelled. Only the two bolded values are actionable.
|
|
54
|
+
4. A separate ClickUp automation (`a64c863a`) sets the outcome to **"In Review"** when a
|
|
55
|
+
task's *status* changes to Iterated. "In Review" is **not** actionable — so
|
|
56
|
+
`status → Iterated` alone never triggers the API. The worker acts only when the Design
|
|
57
|
+
Approver **manually** selects one of the two actionable outcome values.
|
|
58
|
+
5. Daily reminder cron `Core.CronJobs` id 21 ("Design Review Reminders", `0 9 * * 1-5`,
|
|
59
|
+
action `Clickup/Design/SendReviewReminders`) nudges the approver about pending reviews.
|
|
60
|
+
|
|
61
|
+
## Data model
|
|
62
|
+
Configuration lives as ClickUp identifiers (folder IDs, custom-field IDs, drop-down option
|
|
63
|
+
UUIDs), not in the DB. The custom-field / option-ID constants are defined in `_underscore`
|
|
64
|
+
(`_production`) — confirmed present (worker2 PR #80, _underscore PR #624); the
|
|
65
|
+
undefined-constant theory was ruled out.
|
|
66
|
+
|
|
67
|
+
## Client variations
|
|
68
|
+
None — internal TOGA Technology (`true`) design-sprint tooling.
|
|
69
|
+
|
|
70
|
+
## Gotchas / known issues
|
|
71
|
+
- **Silent no-op masked by native automations (root cause of TRUE-79685).** The worker's
|
|
72
|
+
docstring claims it replaced the native automations, but they were never disabled. Native
|
|
73
|
+
automation `c25fd345` ("When Final Design Outcome is Iterated, duplicate the ticket and
|
|
74
|
+
close the original") was doing the duplicate+close and **hiding** that the worker never
|
|
75
|
+
spawned. Only after disabling **all** native automations did setting Final Design Outcome →
|
|
76
|
+
"Iterated – New Task" on TRUE-80046 (prod job 295376, isSuccess=1) produce **no** spawn,
|
|
77
|
+
exposing the failure.
|
|
78
|
+
- **OPEN — two prime suspects for the silent no-op** (need a combined code + live-API session
|
|
79
|
+
to confirm/fix):
|
|
80
|
+
1. **Folder-gate ID mismatch.** `TRUE_DESIGN_SPRINTS_FOLDER_ID = '90115939842'`
|
|
81
|
+
(Design.php:27,50) vs. the file-header comment naming `90020178491`. `90115939842` is a
|
|
82
|
+
valid folder (the daily reminder cron GETs `/folder/90115939842/list` successfully) but
|
|
83
|
+
may not be the folder the automation-test tasks live in — in which case the folder gate
|
|
84
|
+
returns early on every task. **Verify:** compare `getTaskDetails` `folder.id` for
|
|
85
|
+
TRUE-80046 against the constant.
|
|
86
|
+
2. **Strict `===` mismatch in `resolveFinalDesignOutcomeOptionId`** (Design.php:298-317). If
|
|
87
|
+
`getTaskDetails` returns the `drop_down` value in a shape (string `orderindex`, or an
|
|
88
|
+
option-id) that matches neither `option->id === value` nor `option->orderindex === value`,
|
|
89
|
+
it returns `null` → silent no-op. Fix should be an explicit **cast/validate at the
|
|
90
|
+
ClickUp-payload boundary**, not merely switching `===` to `==`.
|
|
91
|
+
- **Latent bug — spurious "Design Review" email on freshly spawned tasks.** Spawned iteration
|
|
92
|
+
tasks transiently pass through "iterated" status during creation (a ClickUp list
|
|
93
|
+
automation), and `spawnIterationTask → carryDesignApprover` copies the Design Approver onto
|
|
94
|
+
the new task. If that copy lands while the new task is momentarily "iterated", the dispatcher
|
|
95
|
+
guard (Clickup.php:822-833: `custom_field.name == 'Design Approver' && status == 'iterated'`)
|
|
96
|
+
fires a **spurious** "Action Needed: Design Review" email. Candidate fix: gate
|
|
97
|
+
`sendDesignApproverEmail` on the change being user-driven (history `user.id != ClickBot -1`).
|
|
98
|
+
- **Native automations must be disabled once the worker is fixed.** The worker was meant to
|
|
99
|
+
replace: `c25fd345`, `272f743e` (Automation #39, native "email Aaron when iterated"),
|
|
100
|
+
`a64c863a`, `7739cc41`, `08570f31`, `70b09962`. Keep sprint-readiness automations only:
|
|
101
|
+
`10df1786`, `f6214678`. **INTERIM:** if the design team needs the workflow before the worker
|
|
102
|
+
is fixed, re-enable **only** `c25fd345` (temporary — this reintroduces the Iteration-reset
|
|
103
|
+
and spurious-email issues).
|
|
104
|
+
|
|
105
|
+
## Change history
|
|
106
|
+
- 2026-07-06 — Diagnosed the "design-sprint API automation not running" report (TRUE-79685;
|
|
107
|
+
tests TRUE-80026/80046): `handleFinalDesignOutcome` is silently no-opping in prod, masked by
|
|
108
|
+
never-disabled native automation `c25fd345`. Narrowed to two suspects (folder-gate ID
|
|
109
|
+
mismatch, strict-`===` option resolution) pending a code+live-API fix session. Also
|
|
110
|
+
identified a latent spurious-email bug on spawned tasks and clarified that only
|
|
111
|
+
"Iterated – New Task" and "Approved" outcomes are actionable. (ajean)
|
package/knowledge/INDEX.md
CHANGED
|
@@ -5,7 +5,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
5
5
|
## 1.0 framework
|
|
6
6
|
|
|
7
7
|
- **library** (Library) _(framework core)_ — 11 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
|
|
8
|
-
- **worker** (Worker) —
|
|
8
|
+
- **worker** (Worker) — 13 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
|
|
9
9
|
- **togadesk** (TOGa Desk) — 8 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
|
|
10
10
|
- **togaview** (TOGa View) — 6 doc(s) → [1.0/apps/togaview/INDEX.md](1.0/apps/togaview/INDEX.md)
|
|
11
11
|
- **webhook** (Webhook) — 1 doc(s) → [1.0/apps/webhook/INDEX.md](1.0/apps/webhook/INDEX.md)
|
|
@@ -17,7 +17,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
17
17
|
## 2.0 framework
|
|
18
18
|
|
|
19
19
|
- **_underscore** (_Underscore) _(framework core)_ — 22 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
20
|
-
- **worker2** (Worker) —
|
|
20
|
+
- **worker2** (Worker) — 26 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
21
21
|
- **api2** (API) — 7 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
22
22
|
- **dbchanges2** (Database Changes) _(framework core)_ — 3 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
23
23
|
- **toga2-supply** (TOGa Supply) — 3 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
|
|
@@ -7,4 +7,5 @@
|
|
|
7
7
|
| [Compass MITS PO → SO Item Linking](features/mits-po-to-so-item-linking.md) | 2.0 | MITS sends Compass inbound Purchase Orders (`POST /v2/purchase-orders`) against a Sales Order (`mitsSalesOrder`). | _underscore/Model/Compass/PurchaseOrder.php, worker/crons/toga2/compass/workflow/3a_import_office_depot_purchase_orders.php |
|
|
8
8
|
| [Compass MITS PO Transmission to Vendors](features/mits-po-transmission-to-vendors.md) | 2.0 | The 1.0 worker cron `2_transmit_mits_purchase_orders_to_vendors.php` transmits Compass PurchaseOrders to their vendors (Office Depot, Strategic Systems, Compass | worker/crons/toga2/compass/workflow/2_transmit_mits_purchase_orders_to_vendors.php, library/app/client/compass.php |
|
|
9
9
|
| [Compass USA](profile.md) | 2.0 | Compass USA is a TOGA client running a multi-tier supply-chain commerce operation. | |
|
|
10
|
+
| [Compass ODP Order Pipeline to NetSuite (numbered worker crons)](workflows/odp-order-pipeline-to-netsuite.md) | 1.0 | The end-to-end **Compass Office Depot (ODP) order → NetSuite** pipeline as it actually runs through the 1.0 `worker` crons under `worker/crons/toga2/compass/`, | worker/crons/toga2/compass/workflow/1_transmit_compass_sales_orders_to_mits.php, worker/crons/toga2/compass/workflow/2_transmit_mits_purchase_orders_to_vendors.php, worker/crons/toga2/compass/edi/1_download_edi_s3_create_po_toga.php, worker/crons/toga2/compass/workflow/5_create_netsuite_sales_orders_from_office_depot_purchase_orders.php, library/app/client/compass.php |
|
|
10
11
|
| [Compass Order Lifecycle & Data-Integrity Invariants](workflows/order-lifecycle-and-data-integrity.md) | 2.0 | End-to-end map of how a Compass order flows through the `Client_Compass` (2.0) database and the **expected raw-data shape** at each link/ASN/IF level. | |
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Compass ODP Order Pipeline to NetSuite (numbered worker crons)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: worker
|
|
5
|
+
project: Worker
|
|
6
|
+
client: compass-usa
|
|
7
|
+
type: workflow
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-07-01
|
|
10
|
+
owners: ["rgirish"]
|
|
11
|
+
files:
|
|
12
|
+
- worker/crons/toga2/compass/workflow/1_transmit_compass_sales_orders_to_mits.php
|
|
13
|
+
- worker/crons/toga2/compass/workflow/2_transmit_mits_purchase_orders_to_vendors.php
|
|
14
|
+
- worker/crons/toga2/compass/edi/1_download_edi_s3_create_po_toga.php
|
|
15
|
+
- worker/crons/toga2/compass/workflow/5_create_netsuite_sales_orders_from_office_depot_purchase_orders.php
|
|
16
|
+
- library/app/client/compass.php
|
|
17
|
+
related:
|
|
18
|
+
- clients/compass-usa/workflows/order-lifecycle-and-data-integrity.md
|
|
19
|
+
- clients/compass-usa/features/mits-po-transmission-to-vendors.md
|
|
20
|
+
- clients/compass-usa/features/mits-po-to-so-item-linking.md
|
|
21
|
+
- clients/compass-usa/profile.md
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Summary
|
|
25
|
+
The end-to-end **Compass Office Depot (ODP) order → NetSuite** pipeline as it actually runs
|
|
26
|
+
through the 1.0 `worker` crons under `worker/crons/toga2/compass/`, and the **diagnostic
|
|
27
|
+
method** for "this Compass order never reached NetSuite" support tickets. This is the *cron
|
|
28
|
+
mechanics* companion to the 2.0 [Compass Order Lifecycle & Data-Integrity
|
|
29
|
+
Invariants](order-lifecycle-and-data-integrity.md) (which covers the raw-data shape / repair
|
|
30
|
+
side of the same chain). All facts verified against the live `prod` / `Client_Compass` schema
|
|
31
|
+
and the cron source. The crons are `App_` (1.0) framework code; the data lives in the 2.0
|
|
32
|
+
`Client_Compass` / `Logs_Compass` schema.
|
|
33
|
+
|
|
34
|
+
## The pipeline (customerId / vendorId, in run order)
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
Commerce → Compass SalesOrder (SalesOrders.customerId = 2)
|
|
38
|
+
[cron 1_transmit_compass_sales_orders_to_mits] → MITS (API)
|
|
39
|
+
MITS → PurchaseOrder (PurchaseOrders.vendorId = 1 = OFFICE DEPOT; POST /v2/purchase-orders; PO number e.g. 50305906-1)
|
|
40
|
+
[cron 2_transmit_mits_purchase_orders_to_vendors] → ODP (cXML; email fallback per integration) [sets PurchaseOrders.dtSubmitted]
|
|
41
|
+
ODP → 850 EDI dropped to S3 (AS2) (bucket agilant-as2, prefix OfficeDepot/)
|
|
42
|
+
[cron edi/1_download_edi_s3_create_po_toga] → creates ODP SalesOrder (customerId = 1); deletes the S3 file after processing
|
|
43
|
+
ODP SalesOrder (customerId = 1)
|
|
44
|
+
[cron 5_create_netsuite_sales_orders_from_office_depot_purchase_orders, hourly] → NetSuite SO
|
|
45
|
+
writes back c_dtTransmittedToNetsuite + c_netsuiteInternalSalesOrderId ONTO the ODP SO (customerId=1)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## The crons
|
|
49
|
+
|
|
50
|
+
1. **`workflow/1_transmit_compass_sales_orders_to_mits.php`** — transmits the Compass
|
|
51
|
+
`SalesOrder` (customerId=2) to MITS via API.
|
|
52
|
+
2. **`workflow/2_transmit_mits_purchase_orders_to_vendors.php`** — transmits the MITS-created
|
|
53
|
+
PO to ODP. ODP integration is **cXML** (auth audience `"Office Depot (Cxml)"`); the script
|
|
54
|
+
also supports an **email fallback** path per the vendor's integration type. Sets
|
|
55
|
+
`PurchaseOrders.dtSubmitted` when transmitted (~2 min after PO creation). (See the dedicated
|
|
56
|
+
[MITS PO Transmission to Vendors](../features/mits-po-transmission-to-vendors.md) feature for
|
|
57
|
+
the item-less-PO gotcha and the CXML-vs-EMAIL routing detail.)
|
|
58
|
+
3. **`edi/1_download_edi_s3_create_po_toga.php`** — pulls the ODP **850** from S3 bucket
|
|
59
|
+
`agilant-as2`, prefix `OfficeDepot/` (delivered via AS2), and creates a downstream **ODP
|
|
60
|
+
SalesOrder** (customerId=1 = Office Depot). **Deletes the file from S3 after processing** —
|
|
61
|
+
so an absent S3 object is expected once ingested, not evidence of a miss.
|
|
62
|
+
4. **`workflow/5_create_netsuite_sales_orders_from_office_depot_purchase_orders.php`** — runs
|
|
63
|
+
**hourly**; picks up ODP SalesOrders
|
|
64
|
+
(`OfficeDepotSalesOrders.customerId = 1 AND c_dtTransmittedToNetsuite IS NULL`), creates the
|
|
65
|
+
SO in NetSuite, and writes back `c_dtTransmittedToNetsuite` + `c_netsuiteInternalSalesOrderId`
|
|
66
|
+
**onto the ODP SalesOrder (customerId=1), never the Compass SO (customerId=2)**.
|
|
67
|
+
**Exclusion filters** (an order legitimately waiting is not a bug):
|
|
68
|
+
- `CompassSalesOrders.number NOT LIKE 'MA%'` — MA orders are excluded.
|
|
69
|
+
- **computer-kit orders** (Bundles 187–196) wait for a **2nd PO** before syncing.
|
|
70
|
+
- a **30-minute delay** after the first ODP PO is created.
|
|
71
|
+
|
|
72
|
+
## Constants & identities (`library/app/client/compass.php`)
|
|
73
|
+
- `App_Client_Compass::VENDOR_ID__OFFICE_DEPOT = 1` — `Vendors.id = 1` = "OFFICE DEPOT".
|
|
74
|
+
- `App_Client_Compass::CUSTOMER_ID__OFFICE_DEPOT = 1` — `Customers.id = 1` = "Office Depot".
|
|
75
|
+
- Compass customer SOs are `customerId = 2`. (Agilant/NetSuite SOs are `customerId = 3`; see the
|
|
76
|
+
lifecycle doc.)
|
|
77
|
+
|
|
78
|
+
## Diagnosing "this Compass order never reached NetSuite" (support-ticket method)
|
|
79
|
+
|
|
80
|
+
The NetSuite writeback lands on the **downstream ODP SalesOrder (customerId=1)**, so checking the
|
|
81
|
+
wrong record makes every order look "stuck." Use the **join**, never a number match:
|
|
82
|
+
|
|
83
|
+
- **Do NOT judge the Compass SO (customerId=2) by its NetSuite fields.**
|
|
84
|
+
`c_dtTransmittedToNetsuite` / `c_netsuiteInternalSalesOrderId` are **ALWAYS NULL on
|
|
85
|
+
customerId=2 SOs by design** — only the downstream ODP SO (customerId=1) carries them.
|
|
86
|
+
(Verified: of 1,827 customerId=2 SOs since 2026-06-01, **zero** have NetSuite fields set.)
|
|
87
|
+
- **Do NOT search for the downstream ODP SalesOrder by the MITS PO number**
|
|
88
|
+
(e.g. `number LIKE '%50305906%'`). ODP SalesOrders are numbered with **ODP's own** order
|
|
89
|
+
numbers (e.g. `471134097001`), not the MITS PO number. Traverse the bridge instead.
|
|
90
|
+
- **Link tables are directional and distinct:**
|
|
91
|
+
- `SalesOrders_PurchaseOrders` = **SO→PO** (a SalesOrder to the PO it generated).
|
|
92
|
+
- `PurchaseOrders_SalesOrders` = **PO→SO** (a PurchaseOrder to the downstream ODP SalesOrder
|
|
93
|
+
created from ODP's 850).
|
|
94
|
+
- To find the ODP SO for a Compass PO, **traverse `PurchaseOrders_SalesOrders`.**
|
|
95
|
+
- **To confirm a PO actually reached ODP:** check `PurchaseOrders.dtSubmitted` is set **AND**
|
|
96
|
+
that downstream ODP SO(s) exist via the `PurchaseOrders_SalesOrders` join — their existence
|
|
97
|
+
proves ODP received the PO and 850'd back.
|
|
98
|
+
- **`PurchaseOrderNotes` is NOT the record of truth.** That table is **empty across ALL POs** —
|
|
99
|
+
do not read its emptiness as evidence a transmission script never ran. (Transmission evidence
|
|
100
|
+
is `dtSubmitted` + the `Logs_Compass.Record` "transmitted to Vendor" note — see the MITS PO
|
|
101
|
+
transmission feature.)
|
|
102
|
+
|
|
103
|
+
## Change history
|
|
104
|
+
- 2026-07-01 — Documented the numbered ODP→NetSuite worker-cron pipeline (crons 1/2/edi-1/5),
|
|
105
|
+
the AS2 850 S3 hand-off (`agilant-as2` / `OfficeDepot/`), the customerId=1-vs-2 NetSuite
|
|
106
|
+
writeback rule + cron-5 exclusion filters, and the "order not in NetSuite" diagnostic method
|
|
107
|
+
(join not number-match; empty `PurchaseOrderNotes` is not evidence). Production-support
|
|
108
|
+
investigation; no code change. (rgirish)
|
|
109
|
+
|
|
110
|
+
## Related docs
|
|
111
|
+
- [Compass Order Lifecycle & Data-Integrity Invariants](order-lifecycle-and-data-integrity.md)
|
|
112
|
+
- [Compass MITS PO Transmission to Vendors](../features/mits-po-transmission-to-vendors.md)
|
|
113
|
+
- [Compass MITS PO → SO Item Linking](../features/mits-po-to-so-item-linking.md)
|
|
@@ -15,6 +15,7 @@ related:
|
|
|
15
15
|
- clients/compass-usa/features/item-fulfillment-tracking-tableview.md
|
|
16
16
|
- clients/compass-usa/features/mits-po-transmission-to-vendors.md
|
|
17
17
|
- 2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md
|
|
18
|
+
- clients/compass-usa/workflows/odp-order-pipeline-to-netsuite.md
|
|
18
19
|
---
|
|
19
20
|
|
|
20
21
|
## Summary
|
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
| [Prudential: Dell ASN units PRE/POST interceptor (legacy key + flat tracking)](features/dell-asn-units-interceptor.md) | 2.0 | After the tracking-number bridge migration, the ASN unit route was renamed (`advance-shipping-notice-units` → `advance-shipping-notice-item-units`), so the inhe | _underscore/Model/Prudential/AdvanceShippingNotice.php, dbchanges2/Client_Prudential/2026-06-10 - AsnUnitsInterceptor.sql |
|
|
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, worker/crons/toga2/prudential/transmissions_to_dell.php, worker/crons/toga2/prudential_beta/transmissions_to_dell_india.php, worker/crons/toga2/prudential_beta/transmissions_to_dell_ireland.php, worker/crons/toga2/prudential_beta/transmissions_to_dell_usa.php |
|
|
7
7
|
| [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 |
|
|
8
|
-
| [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 |
|
|
8
|
+
| [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/generate_sales_and_purchase_orders_from_service_requests.php, worker/crons/toga2/prudential_beta/generate_sales_and_purchase_orders_from_service_requests.php |
|
|
9
9
|
| [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`). | |
|
|
10
10
|
| [Prudential: Dell ASN failed POST backfill replay](workflows/dell-asn-backfill-replay.md) | 2.0 | When Dell ASN POSTs fail in bulk (e.g. | |
|
|
@@ -6,10 +6,13 @@ project: Worker
|
|
|
6
6
|
client: prudential
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-06
|
|
9
|
+
updated: 2026-07-06
|
|
10
10
|
owners: ["rgirish"]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/prudential/transmit_ordershipped_updates_prudential.php
|
|
13
|
+
- worker/crons/toga2/prudential/transmit_closecomplete_updates_prudential.php
|
|
14
|
+
- worker/crons/toga2/prudential/generate_sales_and_purchase_orders_from_service_requests.php
|
|
15
|
+
- worker/crons/toga2/prudential_beta/generate_sales_and_purchase_orders_from_service_requests.php
|
|
13
16
|
related:
|
|
14
17
|
- ../profile.md
|
|
15
18
|
---
|
|
@@ -49,22 +52,68 @@ Iterates the same POs collected in Phase 1. For each:
|
|
|
49
52
|
`c_dtTransmittedOrderShippedUpdateToPrudential IS NOT NULL` AND `c_dtEmailSentOrderShipped IS NULL`
|
|
50
53
|
as "Order Shipped Email Not Sent". This appears daily in the Prudential REQ Processing Report email.
|
|
51
54
|
|
|
55
|
+
## Sibling scripts sharing this pattern
|
|
56
|
+
The same two-phase (transmit-then-email) shape and the same PDF-fetch fragility exist in three
|
|
57
|
+
sibling crons, all fixed the same way this session (2026-07-06):
|
|
58
|
+
- `transmit_closecomplete_updates_prudential.php` — the "Order Closed/Complete" analogue. Phase 1
|
|
59
|
+
stamps `c_dtTransmittedOrderClosedUpdateToPrudential`; Phase 2 stamps `c_dtEmailSentOrderClosed`.
|
|
60
|
+
Same stranding flaw (Phase-1 SELECT filters only on the transmitted column).
|
|
61
|
+
- `generate_sales_and_purchase_orders_from_service_requests.php` (prod and `prudential_beta/`) —
|
|
62
|
+
got the PDF crash-proofing only. Their re-processing is gated by SalesOrder/PurchaseOrder
|
|
63
|
+
**existence**, not a timestamp, so no self-heal was added there (it would risk duplicate SO/PO
|
|
64
|
+
or duplicate emails).
|
|
65
|
+
|
|
52
66
|
## Gotchas / known issues
|
|
53
67
|
|
|
54
|
-
**
|
|
55
|
-
The
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
`
|
|
59
|
-
|
|
68
|
+
**ROOT CAUSE — `file_get_contents` in a 1.0 cron THROWS, it does not return false (discovered 2026-07-06):**
|
|
69
|
+
The 1.0 framework bootstrap sets `error_reporting(E_ALL)` and `App_Error::handleError`
|
|
70
|
+
(`library/app/error.php` ~line 348) converts **every** PHP warning into an `ErrorException`.
|
|
71
|
+
So `file_get_contents()` on an unreachable / 403 / timed-out S3 URL **throws at the call site**,
|
|
72
|
+
before any `if ($pdf !== false)` guard can ever run. This is why the pre-existing `!== false`
|
|
73
|
+
guards (and the 2026-06-18 guard) never worked: the exception is raised **inside**
|
|
74
|
+
`file_get_contents`, not returned by it. Failures are often transient (the same URL returns 200
|
|
75
|
+
on a later check), which is why the "Order Shipped email not sent" issue kept recurring after
|
|
76
|
+
multiple prior "fixes" that only swapped individual PDF filenames.
|
|
77
|
+
|
|
78
|
+
**Fix — `safeFetchPdf()` + independent per-PDF guards (2026-07-06):**
|
|
79
|
+
Added a file-scope helper `function safeFetchPdf(string $url)` that wraps `file_get_contents`
|
|
80
|
+
in `try/catch (\ErrorException)` and returns `false` on failure — so a failed fetch now really
|
|
81
|
+
does return false. Every PDF fetch was converted to `safeFetchPdf()`, and each
|
|
82
|
+
`file_put_contents` + `$mail->addAttachment` is guarded by its **own independent**
|
|
83
|
+
`if ($pdfGuideX !== false)` — NOT a joint `&&`, so one failed PDF drops only that attachment and
|
|
84
|
+
the other attachments and the email itself still go out. **The email must always send; a
|
|
85
|
+
missing/failed PDF must never crash the loop or block the send.** Applied across all four files.
|
|
86
|
+
|
|
87
|
+
**Two-phase stranding is permanent — recovery MUST be date-bounded (discovered 2026-07-06):**
|
|
88
|
+
Phase 1 stamps the transmitted column and the Phase-1 SELECT filters **only** on that column
|
|
89
|
+
being NULL. So any Phase-2 crash strands that PO's email **forever** (transmitted flag already
|
|
90
|
+
set → never re-selected). Prod inspection found ~8,070 ordershipped + ~11,343 closecomplete POs
|
|
91
|
+
historically stranded (transmitted set, email NULL) — but **most are OLD rows never
|
|
92
|
+
back-populated and are NOT owed an email**; only ~21 shipped / ~60 close were genuinely recent
|
|
93
|
+
(last 7 days). **Any stranding-recovery query MUST be date-bounded**, or you will mass-email
|
|
94
|
+
customers about months-old orders. `prudential_exception_report.php` already flags these
|
|
95
|
+
(transmitted set + email NULL). This session deliberately did NOT auto-recover stranded POs —
|
|
96
|
+
see Scope decision below.
|
|
97
|
+
|
|
98
|
+
**Scope decision — fix-forward only, no automated self-heal (decided 2026-07-06):**
|
|
99
|
+
An earlier iteration built a bounded 7-day self-heal SELECT; the developer decided to keep the
|
|
100
|
+
fix to **forward crash-proofing only** ("just make sure we send emails; if the PDF fails, send
|
|
101
|
+
without attachment"). Already-stranded emails are NOT auto-recovered. The generate scripts got
|
|
102
|
+
crash-proofing only for the existence-gated reason above. Do not "helpfully" re-add an unbounded
|
|
103
|
+
self-heal.
|
|
60
104
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
105
|
+
**Wrong Mac PDF URL (partially fixed 2026-06-18, fully fixed 2026-07-06):**
|
|
106
|
+
`PrudentialMacSetupGuide_15.6_v4.pdf` (403 since ~May 5 2026) should be
|
|
107
|
+
`PrudentialMacSetupGuide.pdf`. The 2026-06-18 fix corrected it only in `ordershipped`; the stale
|
|
108
|
+
URL was still live in 3 of the sibling files and was corrected this session.
|
|
64
109
|
|
|
65
|
-
**
|
|
66
|
-
|
|
67
|
-
|
|
110
|
+
**Pre-existing issues surfaced this session (NOT fixed — awareness only):**
|
|
111
|
+
- SQL-injection risk: unescaped `$reqNumber` concatenated into a `LIKE '%...%'` query in the
|
|
112
|
+
Phase-2 body of both `transmit_ordershipped` and `transmit_closecomplete`.
|
|
113
|
+
- `transmit_closecomplete` calls `App_Database::escapeString()` (~line 158) which does not exist
|
|
114
|
+
on `App_Database` (only `sqlEscape` does) — latent error in the `createAssetAndRetryPut` path.
|
|
115
|
+
- Separate inbound issue: Dell SR re-POST hitting `ServiceRequests.number` UNIQUE constraint →
|
|
116
|
+
API 400 "Duplicate entry SR129013" (REQ4444748). Not part of the email fix.
|
|
68
117
|
|
|
69
118
|
**Re-queueing stuck POs:**
|
|
70
119
|
If `c_dtEmailSentOrderShipped` is NULL but `c_dtTransmittedOrderShippedUpdateToPrudential`
|
|
@@ -83,4 +132,5 @@ stamp (crash hits the next iteration). With multiple New-type POs, a crash on PO
|
|
|
83
132
|
all subsequent POs in the same run.
|
|
84
133
|
|
|
85
134
|
## Change history
|
|
86
|
-
- 2026-06
|
|
135
|
+
- 2026-07-06 — Found the real root cause: `file_get_contents` throws (E_ALL→ErrorException) so the `!== false` guards (incl. the 2026-06-18 one) were never effective. Added `safeFetchPdf()` (try/catch ErrorException → false) + independent per-PDF guards; email now always sends even if a PDF fails. Extended the same fix to `transmit_closecomplete` and both `generate_sales_and_purchase_orders_from_service_requests` scripts, and finished the stale Mac PDF URL fix in the 3 files the 2026-06-18 change missed. Documented the permanent two-phase stranding flaw and the date-bounded-recovery requirement; scope kept to fix-forward (no auto self-heal) by developer decision (rgirish)
|
|
136
|
+
- 2026-06-18 — Fixed 403 crash: corrected Mac PDF URL (`_15.6_v4` → `PrudentialMacSetupGuide.pdf`) and added a `!== false` guard on all S3 fetches in New branch. NOTE (2026-07-06): this guard was ineffective — the exception is thrown inside `file_get_contents`, not returned (rgirish)
|
|
@@ -12,7 +12,7 @@ project: _Underscore
|
|
|
12
12
|
client: prudential
|
|
13
13
|
type: profile
|
|
14
14
|
status: active
|
|
15
|
-
updated: 2026-06
|
|
15
|
+
updated: 2026-07-06
|
|
16
16
|
owners: ["jcardinal", "rgirish"]
|
|
17
17
|
files: []
|
|
18
18
|
related:
|
|
@@ -45,10 +45,15 @@ order-status transmissions.
|
|
|
45
45
|
PRE/POST translation that keeps their feed working after the tracking-number bridge migration.
|
|
46
46
|
- Service request region is resolved from `Customers.name` string (`'USA'`/`'India'`/`'Ireland'`).
|
|
47
47
|
Renaming those customer records in the DB would silently break regional validation routing.
|
|
48
|
-
- `transmit_ordershipped_updates_prudential.php`
|
|
49
|
-
|
|
50
|
-
`
|
|
51
|
-
|
|
48
|
+
- `transmit_ordershipped_updates_prudential.php` **and** `transmit_closecomplete_updates_prudential.php`
|
|
49
|
+
run in two phases. Phase 1 stamps the transmitted column immediately
|
|
50
|
+
(`c_dtTransmittedOrderShipped…` / `c_dtTransmittedOrderClosed…`); Phase 2 sends the email and
|
|
51
|
+
stamps a separate column (`c_dtEmailSentOrderShipped` / `c_dtEmailSentOrderClosed`). The Phase-1
|
|
52
|
+
SELECT filters only on the transmitted column, so any Phase-2 crash strands that PO's email
|
|
53
|
+
**forever** — and any recovery query MUST be date-bounded (most historically-stranded rows are
|
|
54
|
+
old and NOT owed an email). Note: PDF fetches via `file_get_contents` in these 1.0 crons **throw**
|
|
55
|
+
(E_ALL→ErrorException), so `!== false` guards do not work — use the `safeFetchPdf()` pattern. See
|
|
56
|
+
the exception report and the transmit-ordershipped-email feature doc.
|
|
52
57
|
|
|
53
58
|
## Related docs
|
|
54
59
|
- Dell ASN units interceptor.
|
package/package.json
CHANGED