toga-ai 1.0.243 → 1.0.245
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/2.0/apps/_underscore/features/forecast-sale-import.md +1 -0
- package/knowledge/2.0/apps/worker2/INDEX.md +1 -0
- package/knowledge/2.0/apps/worker2/features/netsuite-supporting-record-webhook-importer.md +135 -0
- package/knowledge/INDEX.md +2 -2
- package/knowledge/clients/prudential/INDEX.md +1 -0
- package/knowledge/clients/prudential/features/dell-lch-iop-transmissions.md +124 -0
- package/knowledge/clients/prudential/profile.md +8 -1
- package/package.json +1 -1
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
| [Monitoring Framework (Orchestrator + Child Monitors)](features/monitoring-framework.md) | A unified, DB-driven monitoring framework for business-critical data flows (Compass POs, Prudential asset imports, AIG closed claims, …). | worker2/Worker/Monitor.php, worker2/Worker/Monitors/, worker2/Worker/Monitors/RateEntitlement.php, worker2/Worker/Notification/Email.php, worker2/Worker/Rate.php, dbchanges2/Core/2026-05-21 - Monitors.sql, dbchanges2/Core/2026-06-29a - Rate Entitlement Contract Monitor.sql |
|
|
16
16
|
| [NetSuite → TOGA Opportunity Sync (API Message Queue + worker2 webhook)](features/netsuite-opportunity-sync.md) | Outbound sync from NetSuite to TOGA for the record types the Forecast2 importer pulls (opportunities first; sales/items/etc. | worker2/Worker/Netsuite.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Controller/Index.php, _underscore/Worker.php, test/@dave/NetSuite/api-message-queue/lib_amq_queue.js, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/NetSuite/api-message-queue/ue_amq_drain.js, test/@dave/NetSuite/api-message-queue/ss_amq_drain.js, test/@dave/NetSuite/api-message-queue/DEPLOY_RUNBOOK.md, test/@dave/clickup/backfill_opportunity_numbers.php, test/@dave/clickup/probe_opportunity_fields.php, test/@dave/probe_clickup_desc_match.php, test/@dave/test_model_load_behavior.php, dbchanges2/Forecast/2026-06-25a - Add unique index on Opportunities netsuiteOpportunityInternalId.sql, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
|
|
17
17
|
| [NetSuite → Forecast Open-Orders Sync (salesOrder webhook → OpenOrderItems)](features/netsuite-salesorder-open-orders-sync.md) | Webhook-driven, single-record port of the legacy open-orders importer (TRUE-79142). | worker2/Worker/Netsuite/SalesOrder.php, worker2/Worker/Netsuite.php, test/@dave/probe_salesorder_rest_shape.php, test/@dave/probe_open_order_lines.php, test/@dave/check_so_status.php, test/@dave/check_so_history.php, test/@dave/probe_so_rest_lines.php, test/@dave/probe_missing_oo_timing.php, test/@dave/probe_missing_oo_createdby.php, test/@dave/probe_drift_so_dates.php, test/@dave/probe_open_order_gating.php, worker/crons/toga2/forecast2/import_open_orders.php, worker/crons/toga2/forecast2/common_import_sales_from_netsuite.php |
|
|
18
|
+
| [NetSuite Supporting-Record Webhook Importer (the reusable recipe)](features/netsuite-supporting-record-webhook-importer.md) | A single **repeatable recipe** for porting a legacy daily-pull NetSuite *supporting-record* importer (the lookup/dimension tables behind Forecast2 — Employees, | worker2/Worker/Netsuite/Employee.php, worker2/Worker/Netsuite/Account.php, worker2/Worker/Netsuite/Classification.php, worker2/Worker/Netsuite/Customer.php, worker2/Worker/Netsuite/Item.php, worker2/Worker/Netsuite.php, _underscore/Model/Forecast/Employee.php, _underscore/Model/Forecast/Account.php, _underscore/Model/Forecast/Classification.php, _underscore/Component/Forecast/Db/Db.php, test/@dave/test_employee_lifecycle.php, test/@dave/test_account_lifecycle.php, test/@dave/test_classification_lifecycle.php, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, worker/crons/toga2/forecast2/import_supporting_records.php |
|
|
18
19
|
| [DB-Driven Notification (Internal) Email](features/notification-email.md) | Internal/notification emails (merge-conflict alerts, ops notices — anything system-generated, not client-facing transactional mail) are sent through one worker | worker2/Worker/Notification/Email.php, _underscore/Model/Client/EmailTemplate.php, dbchanges2/Client/2026-06-23a - EmailTemplateWrapper.sql, dbchanges2/Client_True/2026-06-23a - EmailTemplateWrapper.sql |
|
|
19
20
|
| [Startech Webhook Handler (worker2)](features/startech-webhook-handler.md) | Receives inbound webhook events from Startech (Easeedesk) and creates or updates the corresponding ticket in TOGA 2.0. | worker2/Worker/Startech.php |
|
|
20
21
|
| [Talos (TOGa IQ) Meeting-Notes Integration & Token Auto-Refresh (consumer)](features/talos-meeting-notes-integration.md) | How a **dev tool / agent consumes Talos (TOGa IQ)** to query the team meeting-notes corpus programmatically. | .claude/skills/plan-ticket/scripts/talos.js |
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: NetSuite Supporting-Record Webhook Importer (the reusable recipe)
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: worker2
|
|
5
|
+
project: Worker
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-30
|
|
10
|
+
owners: ["dfranks"]
|
|
11
|
+
files:
|
|
12
|
+
- worker2/Worker/Netsuite/Employee.php
|
|
13
|
+
- worker2/Worker/Netsuite/Account.php
|
|
14
|
+
- worker2/Worker/Netsuite/Classification.php
|
|
15
|
+
- worker2/Worker/Netsuite/Customer.php
|
|
16
|
+
- worker2/Worker/Netsuite/Item.php
|
|
17
|
+
- worker2/Worker/Netsuite.php
|
|
18
|
+
- _underscore/Model/Forecast/Employee.php
|
|
19
|
+
- _underscore/Model/Forecast/Account.php
|
|
20
|
+
- _underscore/Model/Forecast/Classification.php
|
|
21
|
+
- _underscore/Component/Forecast/Db/Db.php
|
|
22
|
+
- test/@dave/test_employee_lifecycle.php
|
|
23
|
+
- test/@dave/test_account_lifecycle.php
|
|
24
|
+
- test/@dave/test_classification_lifecycle.php
|
|
25
|
+
- test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js
|
|
26
|
+
- worker/crons/toga2/forecast2/import_supporting_records.php
|
|
27
|
+
related:
|
|
28
|
+
- ./netsuite-opportunity-sync.md
|
|
29
|
+
- ./netsuite-salesorder-open-orders-sync.md
|
|
30
|
+
- ../../_underscore/features/forecast-sale-import.md
|
|
31
|
+
- ../../_underscore/features/netsuite-rest-client.md
|
|
32
|
+
- ../architecture.md
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Summary
|
|
36
|
+
|
|
37
|
+
A single **repeatable recipe** for porting a legacy daily-pull NetSuite *supporting-record*
|
|
38
|
+
importer (the lookup/dimension tables behind Forecast2 — Employees, Accounts, Classifications,
|
|
39
|
+
Customers, Items) into a **real-time, webhook-driven, single-record upsert**. A NetSuite
|
|
40
|
+
create/edit/delete arrives at `webhook.togahub.com/netsuite`, the `_Worker_NetSuite::Webhook`
|
|
41
|
+
router dispatches `Netsuite/<Record>/<verb>`, and a thin per-record handler upserts (or
|
|
42
|
+
guarded-deletes) one row in the `Forecast` schema.
|
|
43
|
+
|
|
44
|
+
This is the supporting-record sibling of the transaction-family importers — the
|
|
45
|
+
[Opportunity sync](./netsuite-opportunity-sync.md) (header+children), the
|
|
46
|
+
[Open-Orders sync](./netsuite-salesorder-open-orders-sync.md), and the
|
|
47
|
+
[Forecast.Sales import engine](../../_underscore/features/forecast-sale-import.md). After
|
|
48
|
+
Items + Customers established it and Employees/Accounts/Classifications repeated it verbatim, the
|
|
49
|
+
**recipe is the durable knowledge**: the remaining siblings are mechanical. This doc captures the
|
|
50
|
+
recipe and its two recurring variants so a new one is a fill-in-the-blanks job, not a fresh design.
|
|
51
|
+
|
|
52
|
+
## The recipe (per supporting record)
|
|
53
|
+
|
|
54
|
+
1. **Handler** `worker2/Worker/Netsuite/<Record>.php`:
|
|
55
|
+
`abstract class _Worker_Netsuite_<Record> implements _Interface_Static_Webhook_Netsuite`.
|
|
56
|
+
The shared `_Worker_NetSuite::Webhook` router PascalCases the inbound `recordType` to find it —
|
|
57
|
+
**no router edit is needed.** Verbs:
|
|
58
|
+
- `post` / `put` → `syncRecord()` then `_Database::transactionCommit(self::DB_FORECAST)`
|
|
59
|
+
(the lazy-transaction discipline — `syncRecord` itself never commits; the caller owns the txn).
|
|
60
|
+
- `delete` → run the **referenced-guard** first, and commit **only if it actually deletes**.
|
|
61
|
+
2. **`syncRecord()`** (no commit): fetch the record via SuiteQL single-id form —
|
|
62
|
+
`_Component_Api_Netsuite::send('POST', QUERY_SUITEQL.'?limit=1', ['q'=>$sql], ['Prefer'=>'transient'])`
|
|
63
|
+
— then **upsert keyed on `netsuiteInternalId`**: `load()` the existing row, then **change-detect
|
|
64
|
+
with string-cast compares** (`_Number::nullableIntChanged` for nullable ints) before `save()`.
|
|
65
|
+
The guard is mandatory because **`save()` does NOT diff** — without the change-detect it would
|
|
66
|
+
re-write (and could re-touch) every field on every webhook.
|
|
67
|
+
3. **Model** `_underscore/Model/Forecast/<Record>.php extends _Model_True`, declaring **ONLY the
|
|
68
|
+
NetSuite-sourced columns.** Deliberately **omit externally-managed columns** so a webhook upsert's
|
|
69
|
+
`save()` can never null them — e.g. Employees' `teamId` / `departmentId` / `role` / `urlHeadshot`
|
|
70
|
+
are Power-BI/HR-managed and are left off the model.
|
|
71
|
+
4. **Delete guard** = a `_Component_Forecast_Db::<record>IsReferenced(int): bool` helper using
|
|
72
|
+
`SELECT EXISTS( … UNION ALL … )` across **every inbound RESTRICT FK**. Referenced → return a
|
|
73
|
+
*retained-message string* (do **NOT** throw, do **NOT** blast dependent rows); unreferenced →
|
|
74
|
+
hard-delete. All these helpers are added to the **same** `_underscore/Component/Forecast/Db/Db.php`
|
|
75
|
+
right after `lookupId()` (so independent sibling-ticket PRs branched from `_production` conflict
|
|
76
|
+
*trivially* on merge — keep every helper method).
|
|
77
|
+
5. **Enqueuer:** add `'<record>':'<record>'` to `RECORD_TYPE_MAP` in
|
|
78
|
+
`test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js` (account/customer/employee/
|
|
79
|
+
classification entries already exist).
|
|
80
|
+
6. **e2e harness** `test/@dave/test_<record>_lifecycle.php`: clone an existing one. It drives NetSuite
|
|
81
|
+
REST (check/create/get/update/delete) + the in-process worker verbs (post/put/verb-delete) + a local
|
|
82
|
+
dump of the resulting `Forecast` rows. Boots via chdir `worker2` + `require index.php`.
|
|
83
|
+
7. **Cron:** leave the legacy `SHOULD_SYNC_<X>` daily pull **ON** as a backstop (and, for self-ref
|
|
84
|
+
records, the parent/supervisor backfill). It shares `import_supporting_records.php` with the other
|
|
85
|
+
sections, so **never disable the wrapper.**
|
|
86
|
+
|
|
87
|
+
## Two recurring variants
|
|
88
|
+
|
|
89
|
+
- **Self-referential parent/supervisor FK** (`Employees.supervisorEmployeeId`,
|
|
90
|
+
`Classifications.parentClassificationId`) = **RESOLVE-OR-NULL, never recursive upsert.** Resolve the
|
|
91
|
+
parent's local id by its NetSuite internalId via `lookupId`; if absent store **NULL** (the daily cron
|
|
92
|
+
backfills the link later). Inserting a non-existent local parent id would violate or dangle the FK.
|
|
93
|
+
The delete guard for a self-ref record must **also** check for child rows
|
|
94
|
+
(`WHERE parent…Id = id`) to avoid orphaning the hierarchy.
|
|
95
|
+
- **Type/enum mapping** (Accounts): replicate the authoritative shim's `accttype`→canonical map
|
|
96
|
+
(`App_Api_Netsuite_Rest::listAccounts`, `library/app/api/netsuite/rest.php:218-237`) as a handler
|
|
97
|
+
const `ACCT_TYPE_MAP` so stored values stay format-consistent (`_income`, `_accountsReceivable`, …);
|
|
98
|
+
an unmapped value passes through raw. **Keep the const in sync with the shim.**
|
|
99
|
+
- **Name from a hierarchical NetSuite `name`/`fullname`** = the **leaf segment**
|
|
100
|
+
(`explode(' : ')` + `array_pop`), matching the SOAP/cron/Item resolver.
|
|
101
|
+
|
|
102
|
+
## Gotchas / known issues
|
|
103
|
+
|
|
104
|
+
- **SuiteQL over the REST client has NO server-side placeholder binding.** `_Component_Api_Netsuite::send`
|
|
105
|
+
passes the query string as-is, so the **`(int)` cast on an already-int id is the injection control** —
|
|
106
|
+
the same codebase-wide idiom used by `lookupId()` and the Item/Opportunity/Employee handlers. A
|
|
107
|
+
php-reviewer flags it **CRITICAL** on a fresh read; it is **acceptable here** because the id is a
|
|
108
|
+
local PK from `load()`, never user input. Document the cast-only mitigation with a one-line comment.
|
|
109
|
+
- **SuiteQL returns parent/supervisor reference ids as numeric STRINGS**, so the private resolve-helper
|
|
110
|
+
parameter must stay **untyped** (not `int|null`) or it `TypeError`s.
|
|
111
|
+
- **The supporting-record tables already carry a UNIQUE index on `netsuiteInternalId`** (Employees,
|
|
112
|
+
Accounts, Classifications, Customers, Items), so the upsert's `load()` resolves exactly one row — no
|
|
113
|
+
duplicate-amplification (unlike the [opportunity `load()` footgun](./netsuite-opportunity-sync.md)),
|
|
114
|
+
no migration needed.
|
|
115
|
+
- **`save()` does not diff** — see recipe step 2: the change-detect guard before `save()` is what makes
|
|
116
|
+
a no-change webhook a true no-op.
|
|
117
|
+
|
|
118
|
+
## Change history
|
|
119
|
+
|
|
120
|
+
- 2026-06-30 — Captured the supporting-record webhook-importer recipe and its two variants
|
|
121
|
+
(self-ref RESOLVE-OR-NULL parent FK + child-orphan delete-guard; Accounts `accttype` enum map; leaf
|
|
122
|
+
segment naming) after the third+ application (TRUE-79870 Employees, TRUE-79871 Accounts, TRUE-79872
|
|
123
|
+
Classifications) repeated the Items/Customers pattern verbatim. Recorded the model-omits-externally-
|
|
124
|
+
managed-columns rule, the `_Component_Forecast_Db::<record>IsReferenced` delete-guard contract
|
|
125
|
+
(retained-message string, never throw/blast), the trusted-int SuiteQL idiom (no REST placeholder
|
|
126
|
+
binding; cast-only mitigation), the SuiteQL-returns-string reference-id `TypeError` trap, and the
|
|
127
|
+
leave-the-legacy-cron-on-as-backstop boundary. (dfranks)
|
|
128
|
+
|
|
129
|
+
## Related docs
|
|
130
|
+
|
|
131
|
+
- [NetSuite → TOGA Opportunity Sync](./netsuite-opportunity-sync.md) — header+children transaction sibling.
|
|
132
|
+
- [NetSuite → Forecast Open-Orders Sync](./netsuite-salesorder-open-orders-sync.md).
|
|
133
|
+
- [Forecast.Sales NetSuite import engine](../../_underscore/features/forecast-sale-import.md) — the
|
|
134
|
+
revenue-transaction family + the shared `_Component_Forecast_Db` low-level helpers.
|
|
135
|
+
- [Worker (worker2) Architecture](../architecture.md).
|
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)_ — 10 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
|
|
8
|
-
- **worker** (Worker) —
|
|
8
|
+
- **worker** (Worker) — 12 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)_ — 19 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
20
|
-
- **worker2** (Worker) —
|
|
20
|
+
- **worker2** (Worker) — 23 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)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
| Doc | Framework | Summary | Files |
|
|
4
4
|
|-----|-----------|---------|-------|
|
|
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
|
+
| [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 |
|
|
6
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 |
|
|
7
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
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`). | |
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Prudential: Dell LCH IOP transmissions (LCHRequestV2)"
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: worker
|
|
5
|
+
project: Worker
|
|
6
|
+
client: prudential
|
|
7
|
+
type: client-feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-30
|
|
10
|
+
owners: ["rgirish"]
|
|
11
|
+
files:
|
|
12
|
+
- library/app/api/delllch.php
|
|
13
|
+
- worker/crons/toga2/prudential/transmissions_to_dell.php
|
|
14
|
+
- worker/crons/toga2/prudential_beta/transmissions_to_dell_india.php
|
|
15
|
+
- worker/crons/toga2/prudential_beta/transmissions_to_dell_ireland.php
|
|
16
|
+
- worker/crons/toga2/prudential_beta/transmissions_to_dell_usa.php
|
|
17
|
+
related:
|
|
18
|
+
- ../profile.md
|
|
19
|
+
- dell-asn-units-interceptor.md
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Summary
|
|
23
|
+
|
|
24
|
+
Outbound order transmissions from the 1.0 worker tier to Dell's Lifecycle Hub (LCH) ITSM
|
|
25
|
+
integration. As of Dell's **May 2025 IOP spec update**, the deprecated `LCHRequest` endpoint
|
|
26
|
+
was replaced by **`LCHRequestV2`** — a re-shaped payload with an explicit `requestEvent`
|
|
27
|
+
field. All four Prudential transmission scripts (3 beta regional + 1 production) and the 1.0
|
|
28
|
+
library client (`App_Api_Delllch`) were migrated. The OAuth and base-URL layer is unchanged;
|
|
29
|
+
only the request route and payload schema changed.
|
|
30
|
+
|
|
31
|
+
This is the **outbound** counterpart to the 2.0 inbound Dell ASN flow (see
|
|
32
|
+
`dell-asn-units-interceptor.md`).
|
|
33
|
+
|
|
34
|
+
## How it works
|
|
35
|
+
|
|
36
|
+
- **Route:** `api/v2/request/LCHRequest` → **`api/v2/request/LCHRequestV2`** in all four
|
|
37
|
+
transmission scripts.
|
|
38
|
+
- **Base URL / auth unchanged:** OAuth 2.0 `client_credentials` flow and base endpoints
|
|
39
|
+
(`iopv2/` prod, `iopv2np/` test) in `App_Api_Delllch::initialize()` are unchanged.
|
|
40
|
+
- **Event model (the key change):** the new schema adds a required **`requestEvent`** field —
|
|
41
|
+
`'Create'` for new orders, `'Update'` for updates, `'Cancel'` for cancellations. This
|
|
42
|
+
replaces the old overloaded `svcOrderType` that previously signalled both the SR type AND
|
|
43
|
+
the event (`'CANCEL'`/`'UPDATE'`). In V2, `requestType` carries the SR type only
|
|
44
|
+
(Refresh / Breakfix / Reclaim / Bulk).
|
|
45
|
+
- **Optional pre-submission validation:** `App_Api_Delllch::validateLCHRequestV2(array $payload, bool $isProduction = true): mixed`
|
|
46
|
+
calls `api/v2/validate/LCHRequestV2` — returns 200 on a valid payload, `422` with a
|
|
47
|
+
`problems[]` / `issues[]` array on schema violations. Useful for debugging a new payload
|
|
48
|
+
shape before sending a real order.
|
|
49
|
+
|
|
50
|
+
### Payload schema migration (old → new)
|
|
51
|
+
|
|
52
|
+
| Old (`LCHRequest`) | New (`LCHRequestV2`) |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| `svcOrderReqNumber` | `requestNumber` |
|
|
55
|
+
| `svcOrderType` (overloaded: type + event) | `requestType` (Refresh/Breakfix/Reclaim/Bulk only) + `requestEvent` (Create/Update/Cancel) |
|
|
56
|
+
| `svcOrderPriority` | `priority` |
|
|
57
|
+
| `customerName` / `customerTelephone` / `customerEmail` / `employeeID` / `employeeType` (flat) | `employee{}` object: `employee.name` / `.phone` / `.email` / `.id` / `.type` |
|
|
58
|
+
| `addresses[]` with `Ship To` element | `shipAddress{}` flat object |
|
|
59
|
+
| `addresses[]` with `Return To` element | `returnAddress{}` flat object (`addressTo`, `careOf`, `address1`, `address2`, `city`, `state`, `postalCode`, `countryCode`) |
|
|
60
|
+
| `returnItems[].itemType` / `.itemSerialNumber` / `.itemAssetTag` | `returnItems[].type` / `.serialNumber` / `.assetTag` |
|
|
61
|
+
| `updateDate` (UPDATE events) | `deliveryDate` |
|
|
62
|
+
| `updateReason` | `reason` |
|
|
63
|
+
|
|
64
|
+
**Removed fields (no longer in the V2 schema):** `lob`, `msgID`, `deviceType`,
|
|
65
|
+
`organizationalUnit`, `shippingMethod`, `profile`, `imageType`, `modeledAfter`,
|
|
66
|
+
`hdRetentionIndicator`, `cidCoverage`, `cidCaseIndicator`.
|
|
67
|
+
|
|
68
|
+
### SR-type / branch behavior
|
|
69
|
+
|
|
70
|
+
- **LABEL SR type:** old code set `svcOrderType='RECLAIM'` mid-payload; new code sets
|
|
71
|
+
`requestType='Reclaim'` in the LABEL branch.
|
|
72
|
+
- **RECLAIM:** `kitProfile='PACKAGING'` for all countries.
|
|
73
|
+
- **THIN CLIENT guard removed:** the old `if deviceType == 'THIN CLIENT' then deviceType = 'LAPTOP'`
|
|
74
|
+
mapping was deleted — `deviceType` no longer exists in the V2 schema.
|
|
75
|
+
|
|
76
|
+
### Country-specific logic (preserved across the migration)
|
|
77
|
+
|
|
78
|
+
- **India:** `employee.type='NA'` hardcoded for all SR types. India device tier map
|
|
79
|
+
(`Knowledge Worker`→`Standard Notebook`, `Developer`→`Developer Notebook`) preserved in the
|
|
80
|
+
BREAKFIX / REFRESH / else branches.
|
|
81
|
+
- **Ireland / USA:** `employee.type` initialised to `'STANDARD'` at base payload.
|
|
82
|
+
Brownfield/Apple device lookups via ServiceNow (`App_Api_Prudential::send()`) preserved.
|
|
83
|
+
Return address uses the `returnAddress{}` flat object built from
|
|
84
|
+
`$returnToAddress` / `$returnToAddressApple` arrays via `array_merge()` with an `addressTo`
|
|
85
|
+
override.
|
|
86
|
+
|
|
87
|
+
## Deployment order (Prudential standard: beta before production)
|
|
88
|
+
|
|
89
|
+
Beta scripts (`crons/toga2/prudential_beta/`) are migrated and verified **first**; only then
|
|
90
|
+
is the production script (`crons/toga2/prudential/transmissions_to_dell.php`,
|
|
91
|
+
`$inProduction = true`) touched. Beta scripts run against the beta DB
|
|
92
|
+
(`db_beta_toga2core` / `db_client_prudential`) so failures never reach live customer orders.
|
|
93
|
+
This is the established pattern for all Prudential script changes.
|
|
94
|
+
|
|
95
|
+
## Verification plan
|
|
96
|
+
|
|
97
|
+
1. Run beta scripts against the beta DB; check `Logs_Prudential` API table for the outbound
|
|
98
|
+
request body and Dell's 200/202 response.
|
|
99
|
+
2. Optionally call `App_Api_Delllch::validateLCHRequestV2()` on a sample payload to pre-check
|
|
100
|
+
the schema before sending a real order.
|
|
101
|
+
3. After beta passes a full order cycle (New → shipped), confirm the production script is live.
|
|
102
|
+
|
|
103
|
+
## Gotchas
|
|
104
|
+
- `requestType` is the SR type only (Refresh/Breakfix/Reclaim/Bulk) — do **not** put the event
|
|
105
|
+
there. Event signalling moved to the separate required `requestEvent` field
|
|
106
|
+
(Create/Update/Cancel). Conflating them is the easiest way to reproduce the old overloaded
|
|
107
|
+
`svcOrderType` bug under the new schema.
|
|
108
|
+
- Dell deprecated `LCHRequest`; any script still POSTing to the old route will fail. All four
|
|
109
|
+
transmission scripts must point at `LCHRequestV2`.
|
|
110
|
+
- The removed fields (e.g. `deviceType`, `lob`, `imageType`) will be rejected by V2 validation —
|
|
111
|
+
do not reintroduce them.
|
|
112
|
+
|
|
113
|
+
## Change history
|
|
114
|
+
- 2026-06-30 — Migrated all Prudential Dell transmissions (3 beta regional + production) and
|
|
115
|
+
`App_Api_Delllch` from deprecated `LCHRequest` to `LCHRequestV2` per Dell's May 2025 IOP
|
|
116
|
+
spec: re-shaped payload (employee/ship/return objects, renamed return-item keys), new
|
|
117
|
+
required `requestEvent` event field replacing overloaded `svcOrderType`, removed dropped
|
|
118
|
+
fields and the THIN CLIENT device guard, added `validateLCHRequestV2()` pre-submission
|
|
119
|
+
validation helper. Country-specific (India/Ireland/USA) device-tier and address logic
|
|
120
|
+
preserved. (rgirish)
|
|
121
|
+
|
|
122
|
+
## Related docs
|
|
123
|
+
- Prudential: Dell ASN units interceptor (inbound 2.0 counterpart).
|
|
124
|
+
- Prudential profile.
|
|
@@ -6,16 +6,19 @@ apps:
|
|
|
6
6
|
- api2
|
|
7
7
|
- dbchanges2
|
|
8
8
|
- websocket
|
|
9
|
+
- worker
|
|
10
|
+
- library
|
|
9
11
|
project: _Underscore
|
|
10
12
|
client: prudential
|
|
11
13
|
type: profile
|
|
12
14
|
status: active
|
|
13
|
-
updated: 2026-06-
|
|
15
|
+
updated: 2026-06-30
|
|
14
16
|
owners: ["jcardinal", "rgirish"]
|
|
15
17
|
files: []
|
|
16
18
|
related:
|
|
17
19
|
- features/dell-asn-units-interceptor.md
|
|
18
20
|
- features/service-request-address-validation.md
|
|
21
|
+
- features/dell-lch-iop-transmissions.md
|
|
19
22
|
---
|
|
20
23
|
|
|
21
24
|
## Summary
|
|
@@ -33,6 +36,9 @@ order-status transmissions.
|
|
|
33
36
|
- **Outbound (worker crons):** `transmissions_to_netsuite.php`, `transmit_ordershipped_updates_prudential.php`,
|
|
34
37
|
`transmit_closecomplete_updates_prudential.php`, `transmit_order_delivered_updates_from_fedex_toga2.php`,
|
|
35
38
|
`update_tracking_number_from_netsuite_to_toga.php`, `manually_insert_ASN.php`.
|
|
39
|
+
- **Outbound (Dell LCH ITSM):** `transmissions_to_dell.php` (prod) + `prudential_beta/transmissions_to_dell_{india,ireland,usa}.php`
|
|
40
|
+
POST to Dell's LCH `LCHRequestV2` endpoint via the 1.0 `App_Api_Delllch` client. See the
|
|
41
|
+
Dell LCH IOP transmissions feature doc.
|
|
36
42
|
|
|
37
43
|
## Gotchas
|
|
38
44
|
- Dell will not change their payload shape — see the Dell ASN units interceptor feature doc for the
|
|
@@ -46,5 +52,6 @@ order-status transmissions.
|
|
|
46
52
|
|
|
47
53
|
## Related docs
|
|
48
54
|
- Dell ASN units interceptor.
|
|
55
|
+
- Dell LCH IOP transmissions (LCHRequestV2, outbound 1.0).
|
|
49
56
|
- 2.0 _underscore: Tracking-Number Bridge Migration.
|
|
50
57
|
- features/transmit-ordershipped-email.md
|
package/package.json
CHANGED