toga-ai 1.0.766 → 1.0.768
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/features/netsuite-suiteql-rest-shim.md +27 -1
- package/knowledge/1.0/apps/worker/INDEX.md +1 -0
- package/knowledge/1.0/apps/worker/features/canon-received-po-netsuite-order-sync.md +112 -0
- package/knowledge/1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md +82 -23
- package/knowledge/INDEX.md +2 -1
- package/knowledge/clients/adyen/INDEX.md +5 -0
- package/knowledge/clients/adyen/profile.md +59 -0
- package/knowledge/clients/canon/profile.md +7 -2
- package/knowledge/clients/elite/features/netsuite-togasupply-sync.md +16 -2
- package/knowledge/clients/growrk/INDEX.md +1 -1
- package/knowledge/clients/growrk/features/transfer-order-flow.md +45 -3
- package/knowledge/clients/nychh/features/netsuite-inventory-adjustment-fulfillment-link.md +15 -4
- package/knowledge/clients/nychh/features/netsuite-transfer-order-import.md +29 -11
- package/package.json +1 -1
|
@@ -7,7 +7,7 @@ client: shared
|
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
9
|
updated: 2026-09-03
|
|
10
|
-
owners: [dfranks, jcardinal, kyalamarthi]
|
|
10
|
+
owners: [dfranks, jcardinal, kyalamarthi, rgirish]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/api/netsuite/rest.php
|
|
13
13
|
- library/app/netsuite.php
|
|
@@ -78,6 +78,22 @@ Forecast2 tables already store.
|
|
|
78
78
|
a per-type factor (it double-negates reversals back to positive). The shim flips
|
|
79
79
|
`amount = -foreignamount`, `costEstimate = -costestimate`.
|
|
80
80
|
|
|
81
|
+
- **⚠ `->holdInvoice` on a sales-order shim is NOT a native NetSuite field — it is normalised from
|
|
82
|
+
the custom checkbox `custbody_asi_hold` (internal id 4082), and that field is SHARED by all
|
|
83
|
+
clients.** NetSuite REST exposes no top-level `holdInvoice`. The `"asi"` prefix is **Agilant
|
|
84
|
+
Solutions Inc** (the company), *not* a client prefix — do not read it as client-specific. The read
|
|
85
|
+
lives **inside the shim**, in all three fetch paths: `buildSalesOrderShims()` (~L533, feeding both
|
|
86
|
+
`listSalesOrders()` and `fetchSalesOrderByIdFull()`) and `fetchSalesOrderById()` (~L1537), so
|
|
87
|
+
callers such as `App_Api_Toga2::isTransferOrder()` read an already-normalised boolean. (An earlier
|
|
88
|
+
version read `$nsOrder->holdInvoice` in the caller with a `custbody_hold_invoice` fallback — the
|
|
89
|
+
wrong field name; commit `07c31e24`, 2026-08-19, found the real one and moved the read down here.)
|
|
90
|
+
`worker/crons/toga2/compass/edi/2_create_so_netsuite.php` **writes** the same field.
|
|
91
|
+
**⚠ The SOAP client `library/app/netsuite.php` never sets `holdInvoice` at all**, so any detection
|
|
92
|
+
that depends on it (e.g. the `zero_dollar_hold` transfer-order mode — see
|
|
93
|
+
[per-client sync](../../worker/features/netsuite-togasupply-per-client-sync.md)) is **silently
|
|
94
|
+
always-false** on a SOAP path. Verified not a live risk: the togasupply engine calls
|
|
95
|
+
`App_Api_Netsuite_Rest::` on every sales-order/receipt/fulfillment path.
|
|
96
|
+
|
|
81
97
|
- **`createdFrom` is not a usable SuiteQL column.** `transaction.createdfrom` is **always NULL**
|
|
82
98
|
in SuiteQL (26/26). It is recoverable from `previoustransactionlinelink.previousdoc` excluding
|
|
83
99
|
`previoustype = 'Opprtnty'` (an invoice links to both its SO and its opportunity; REST's
|
|
@@ -307,6 +323,16 @@ None — uniform across clients (NetSuite is a single shared account).
|
|
|
307
323
|
|
|
308
324
|
## Change history
|
|
309
325
|
|
|
326
|
+
- 2026-09-03 — Documented that a sales-order shim's **`->holdInvoice` is not a native NetSuite
|
|
327
|
+
field**: it is normalised inside the shim from the custom checkbox **`custbody_asi_hold` (id
|
|
328
|
+
4082)**, which is **shared across all clients** ("asi" = Agilant Solutions Inc, not a client
|
|
329
|
+
prefix). Set in all three fetch paths (`buildSalesOrderShims()` feeding `listSalesOrders()` /
|
|
330
|
+
`fetchSalesOrderByIdFull()`, plus `fetchSalesOrderById()`); traced to commit `07c31e24`
|
|
331
|
+
(2026-08-19), which corrected the earlier caller-side read of `custbody_hold_invoice`. Recorded the
|
|
332
|
+
trap that the **SOAP client `app/netsuite.php` never sets it**, so hold-based detection is silently
|
|
333
|
+
always-false there — verified harmless today because the togasupply engine is REST-only.
|
|
334
|
+
Read-only session; no change to `rest.php`. (rgirish)
|
|
335
|
+
|
|
310
336
|
- 2026-09-03 — Recorded that **`fetchItemFulfillmentById` and `fetchInventoryAdjustmentById` omitted
|
|
311
337
|
`->lastModifiedDate`** (unlike `fetchItemReceiptById`), which froze ITEM_FULFILLMENTS + INVENTORY_ADJUSTMENTS
|
|
312
338
|
once the 2026-09-02 per-record cursor read that field off the by-reference record these shims replace. GroWrk
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [Worker (1.0 Framework) Architecture](architecture.md) | `worker` is the legacy (**1.0** `App_` framework) **background-job tier**. | worker/index.php, worker/_/app/framework.php, worker/crons/, worker/schedules/, worker/ebs/cron.worker.php, worker/.ebextensions/035_cron.worker.config, worker/crons/infrastructure/execute_dbchanges.php, worker/.ebextensions/030_dbchanges.config |
|
|
6
|
+
| [Canon received-PO → NetSuite sales order sync](features/canon-received-po-netsuite-order-sync.md) | A 1.0 `worker` cron that reads Canon customer purchase orders out of the **Vision** databases and creates the matching **sales order in NetSuite**. | worker/crons/sync/sync_received_po.php, library/netsuitetoolkit/NSPHPClient.php |
|
|
6
7
|
| [Compass MA Sales Order Exception Report](features/compass-ma-sales-order-exception-report.md) | A worker cron that emails operations the "Compass Refresh Exception Report" — Compass `MA%` sales orders whose corresponding Office Depot (ODP) sales order has | worker/crons/toga2/compass/workflow/7_generate_ma_sales_order_exception_report.php, worker/schedules/cron.worker.sync.json |
|
|
7
8
|
| [Compass Manager Approval Reminder Emails (1.0 worker crons)](features/compass-manager-approval-reminder-emails.md) | Two 1.0 worker crons nag approvers about sales orders still waiting on a decision — one per Compass tenant. | worker/crons/toga2/compass/compass_email_reminders.php, worker/crons/toga2/compasscanada/compass_email_reminders.php, worker/schedules/cron.worker.sync.json, worker1.5/crons/toga2/compass/compass_email_reminders.php, worker1.5/schedules/cron.worker.json |
|
|
8
9
|
| [Compass Partial In-Transit & Delivered Emails (per package)](features/compass-partial-in-transit-delivered-emails.md) | Compass USA and Compass Canada send a **per-package** in-transit email (and a matching delivered email) instead of one email listing the whole order. | worker/crons/toga2/compass/update_salesorder_status_from_odp.php, worker/crons/toga2/compass/send_delivered_email.php, worker/crons/toga2/compasscanada/update_salesorder_status_from_odp.php, worker/crons/toga2/compasscanada/workflow/3_update_salesorder_status_from_grand_and_toy.php, worker/crons/toga2/compasscanada/send_delivered_email.php, worker/crons/toga2/compasscanada/workflow/test_partial_in_transit_email.php, worker/crons/toga2/compasscanada/workflow/test_partial_delivered_email.php, library/app/client/compasscanada.php |
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Canon received-PO → NetSuite sales order sync
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: worker
|
|
5
|
+
project: Worker
|
|
6
|
+
client: canon
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-09-03
|
|
10
|
+
owners: ["mhammontree"]
|
|
11
|
+
files:
|
|
12
|
+
- worker/crons/sync/sync_received_po.php
|
|
13
|
+
- library/netsuitetoolkit/NSPHPClient.php
|
|
14
|
+
related:
|
|
15
|
+
- ./netsuite-togasupply-per-client-sync.md
|
|
16
|
+
- ../../../clients/canon/profile.md
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Summary
|
|
20
|
+
A 1.0 `worker` cron that reads Canon customer purchase orders out of the **Vision** databases and
|
|
21
|
+
creates the matching **sales order in NetSuite**. Before it can create the order it must turn every
|
|
22
|
+
Vision part number into a real NetSuite item — bundles are exploded into components and prefixes are
|
|
23
|
+
stripped. If any line cannot be matched, no order is created and operations gets a "not every part
|
|
24
|
+
number could be located" email.
|
|
25
|
+
|
|
26
|
+
**Critical rule:** the prefix strip list (`ASI-`, `/4YR`, `TOGA-`) appears **twice** in the file —
|
|
27
|
+
line ~54 and line ~233. A new SKU prefix must be added to **both** or the sync silently searches
|
|
28
|
+
NetSuite for the bundle name itself and fails.
|
|
29
|
+
|
|
30
|
+
## Key files / entry points
|
|
31
|
+
- `worker/crons/sync/sync_received_po.php` — the whole sync (selection, bundle explode, NetSuite
|
|
32
|
+
item lookup, order create, all three emails).
|
|
33
|
+
- `library/netsuitetoolkit/NSPHPClient.php` line 200 — sets `SOAP_SINGLE_ELEMENT_ARRAYS`.
|
|
34
|
+
|
|
35
|
+
## How it works
|
|
36
|
+
1. **Selection.** Picks up rows from `Vision.CustomerPurchaseOrders` joined to
|
|
37
|
+
`CustomerPurchaseOrderItems` on `customerPurchaseOrderId`, where
|
|
38
|
+
`companyId = 13785 AND amiExportEnabled = 1 AND exportedToAmi = 0 AND IFNULL(netsuiteOrderNumber,'') LIKE ''`.
|
|
39
|
+
2. **Bundle explode.** Custom-catalog bundles are expanded into their component part numbers
|
|
40
|
+
**before** any NetSuite search, using `Vision_Catalog.CustomProducts` joined through
|
|
41
|
+
`CustomCatalogCache` → `Products` for Canon companies **13785 / 18610**.
|
|
42
|
+
3. **Prefix normalization.** Both the explode map (line ~54) and the per-line lookup (line ~233) run
|
|
43
|
+
`trim(str_replace(['ASI-', '/4YR', 'TOGA-'], '', $partNumber))`.
|
|
44
|
+
4. **NetSuite item lookup** (~line 250) walks the search results and deliberately **skips
|
|
45
|
+
`ItemGroup` records**: `if (get_class($record) != 'ItemGroup') { $internalId = $record->internalId; break; }`.
|
|
46
|
+
If nothing is left, `$allItemsLocated` goes false and the failure email fires.
|
|
47
|
+
5. **Quantity.** `billTo` selects the NetSuite customer and whether `$enforceQuantityOne` is set:
|
|
48
|
+
- `HPFS-LEASES` → 2839 — `enforceQuantityOne = true`
|
|
49
|
+
- `ACH-ASILEASE` → 28537 — `enforceQuantityOne = true`
|
|
50
|
+
- `ACH-HP-ACCESSOR` → 21908 if the order number starts with `9`, else 21055 — `false`
|
|
51
|
+
With `enforceQuantityOne = true` an exploded **bundle** line is written at quantity 1.
|
|
52
|
+
6. **Price.** The exploded bundle's main line is priced from
|
|
53
|
+
`IFNULL(CustomProducts.listPrice, PriceProfilePartRules.rate)` — the Vision catalog price is
|
|
54
|
+
taken as-is; the sync does **not** compute cost + margin.
|
|
55
|
+
7. **Email.** Three distinct bodies: SUCCESSFUL, a generic FAILED, and the "not every part number
|
|
56
|
+
could be located" FAILED that lists `$partNumbersCouldNotFind`. **Only the third names part
|
|
57
|
+
numbers** — that is the tell for a lookup miss vs. a NetSuite write failure. Recipients are
|
|
58
|
+
hardcoded near the top (`$emailOrdersTo` mfrye, cmullen; CC jblevins, sking).
|
|
59
|
+
|
|
60
|
+
## Data model
|
|
61
|
+
- `Vision.CustomerPurchaseOrders`, `Vision.CustomerPurchaseOrderItems` (schema `Vision`,
|
|
62
|
+
environment `legacy`).
|
|
63
|
+
- `Vision_Catalog.CustomProducts`, `CustomCatalogCache`, `Products`, `PriceProfilePartRules`.
|
|
64
|
+
- Canon's catalog is 21 parts: 19 plain, 1 `ASI-` prefixed, and exactly one `TOGA-` prefixed
|
|
65
|
+
(`TOGA-MGEE4LL/A`) — the only entry with both `assetTagging` and `biosTagging` set.
|
|
66
|
+
|
|
67
|
+
## Client variations
|
|
68
|
+
Canon-only cron (`companyId 13785`, catalog companies 13785 / 18610). No other client runs it.
|
|
69
|
+
|
|
70
|
+
## Gotchas / known issues
|
|
71
|
+
- **A missing prefix makes the bundle name look like a part number.** `TOGA-MGEE4LL/A` was searched
|
|
72
|
+
literally; in NetSuite that name is an **Item Group** (internal id 1609121) whose components are
|
|
73
|
+
`MGEE4LL/A`, `SVC-TS-BIOSTAG`, `SVC-TS-ASSETTAG`. Because the lookup skips `ItemGroup`, the search
|
|
74
|
+
"found" the item and still returned nothing usable. The real item is `MGEE4LL/A`
|
|
75
|
+
(Serialized Inventory Item, internal id 1607214). Add any new prefix to **both** `str_replace`
|
|
76
|
+
arrays.
|
|
77
|
+
- **Quantity 12 on a lease PO means 12 monthly payments for ONE machine, not 12 machines.** Verified
|
|
78
|
+
against the manually keyed records for PO 9318575 (PO 169288 TD Synnex, SO 285071) — both show
|
|
79
|
+
`MGEE4LL/A` qty 1. Do **not** "fix" `$enforceQuantityOne` to pass the PO quantity through.
|
|
80
|
+
**Non-bundle** parts are not exploded and therefore DO keep the PO quantity (e.g. older
|
|
81
|
+
`ASI-MW1H3LL/A` orders imported at qty 12).
|
|
82
|
+
- **Catalog price can be stale, and that is accepted.** `TOGA-MGEE4LL/A` `listPrice` is $5,118.45;
|
|
83
|
+
the manually keyed SO 285071 used $5,106.45 (TD Synnex cost $4,749.00 + 7% margin = $357.45 GP).
|
|
84
|
+
Decision 2026-09-03: ship as-is — the sync should not compute margin; a reviewer adjusts the
|
|
85
|
+
auto-created order.
|
|
86
|
+
- **⚠ OPEN LATENT BUG (not fixed, needs its own ticket).** In the per-line NetSuite lookup, if
|
|
87
|
+
`$response->searchResult->status->isSuccess` is FALSE the code does **nothing** — the line is
|
|
88
|
+
silently skipped, `$allItemsLocated` stays TRUE, and the sales order is created in NetSuite
|
|
89
|
+
**missing that line**, with no email and no error. A NetSuite search outage or throttled call
|
|
90
|
+
produces a silently short order. This violates the fail-loud rule in `1.0/standards/backend-php.md`
|
|
91
|
+
via an implicit no-op rather than an explicit catch.
|
|
92
|
+
- **A fixed PO does not re-run.** Rows are flagged `exportedToAmi = 1` even when the export failed,
|
|
93
|
+
so deploying a fix does **not** replay them. POs 9318538, 9318575 and 9318670 (all
|
|
94
|
+
`TOGA-MGEE4LL/A`, qty 12, `netsuiteOrderNumber` NULL) were already flagged and need manual entry
|
|
95
|
+
or a flag reset. 9318575 is in NetSuite as SO 285071, keyed manually.
|
|
96
|
+
- **Ruled out — do not re-investigate.** (a) The single-result SOAP array trap does not apply: the
|
|
97
|
+
lookup is guarded by `is_array($response->searchResult->recordList->record)`, and
|
|
98
|
+
`library/netsuitetoolkit/NSPHPClient.php` line 200 sets `SOAP_SINGLE_ELEMENT_ARRAYS`, so a
|
|
99
|
+
one-record search is still an array. (b) Commit `a3e33776c` (TRUE-75559, 2026-03-17) did not
|
|
100
|
+
introduce the skip-ItemGroup behavior — it only deleted a commented-out block; the older
|
|
101
|
+
commented version actually *preferred* the ItemGroup, so the flip happened years earlier under a
|
|
102
|
+
different file path.
|
|
103
|
+
|
|
104
|
+
## Change history
|
|
105
|
+
- 2026-09-03 — Added `TOGA-` to the prefix strip list at both line 54 and line 233 so Canon's new
|
|
106
|
+
bundle SKU `TOGA-MGEE4LL/A` resolves to `MGEE4LL/A` instead of the NetSuite Item Group; documented
|
|
107
|
+
the qty-1 lease rule, the accept-catalog-price decision, and the silent `isSuccess=false` skip
|
|
108
|
+
(TRUE-80811, mhammontree).
|
|
109
|
+
|
|
110
|
+
## Related docs
|
|
111
|
+
- [NetSuite → TOGa Supply per-client sync](./netsuite-togasupply-per-client-sync.md)
|
|
112
|
+
- [Canon profile](../../../clients/canon/profile.md)
|
|
@@ -7,7 +7,7 @@ client: shared
|
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
9
|
updated: 2026-09-03
|
|
10
|
-
owners: ["dfranks", "bala", "jcardinal", "mhammontree", "snaredla"]
|
|
10
|
+
owners: ["dfranks", "bala", "jcardinal", "mhammontree", "snaredla", "rgirish"]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/netsuite/common_sync_togasupply.php
|
|
13
13
|
- worker/crons/toga2/netsuite/sync_togasupply_canon.php
|
|
@@ -463,29 +463,71 @@ the code deploy).
|
|
|
463
463
|
|
|
464
464
|
## Client variations
|
|
465
465
|
|
|
466
|
-
- **Transfer-order
|
|
467
|
-
|
|
468
|
-
`TransferOrders` vs `SalesOrders` by
|
|
469
|
-
`
|
|
470
|
-
(NYCHH's `sync_togasupply_hh.php` is the only one that does). The signal is the NetSuite custom
|
|
471
|
-
checkbox `custbody_stocking_order`, read by `App_Api_Toga2::isTransferOrder()`/`isStockingOrder()`
|
|
472
|
-
— **not** a dollar/hold heuristic. Full mechanics, PO bridges, and origin-location resolution:
|
|
473
|
-
[NYCHH NetSuite → TransferOrders import](../../../clients/nychh/features/netsuite-transfer-order-import.md).
|
|
474
|
-
- **Transfer-order integration is governed by TWO DISTINCT wrapper flags — a master enable switch and a
|
|
475
|
-
NYCHH-only classification-mode switch (do not conflate them).**
|
|
466
|
+
- **Transfer-order integration is governed by TWO DISTINCT wrapper constants — a master enable switch
|
|
467
|
+
and an explicit per-client detection MODE (do not conflate them).** These orders **masquerade as
|
|
468
|
+
NetSuite sales orders** and are split into `TransferOrders` vs `SalesOrders` by
|
|
469
|
+
`App_Api_Toga2::isTransferOrder()`.
|
|
476
470
|
- **`IS_ENABLED_INTEGRATION_TRANSFER_ORDERS`** — the master "do transfer-order integration at all"
|
|
477
|
-
switch, defined in every `sync_togasupply_*.php` launcher. `true` for **GroWrk, Adyen,
|
|
478
|
-
NYCHH**; `false` for
|
|
479
|
-
`
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
471
|
+
switch, defined in every `sync_togasupply_*.php` launcher. `true` for **GroWrk, Adyen, and
|
|
472
|
+
NYCHH**; `false` for Elite (pre-wired, not on) and the rest. **Every one of the 7
|
|
473
|
+
`isTransferOrder()` call sites is guarded by this**, so the mode throw below can never fire for a
|
|
474
|
+
client with transfer orders off.
|
|
475
|
+
- **`TRANSFER_ORDER_DETECTION_MODE`** — the classification mode, one of two `App_Api_Toga2` class
|
|
476
|
+
constants (see next bullet). Required whenever the master switch is on.
|
|
477
|
+
|
|
478
|
+
### ⚠ `TRANSFER_ORDER_DETECTION_MODE` — explicit per client, NO default, throws if unset (2026-09-03)
|
|
479
|
+
|
|
480
|
+
`isTransferOrder()` used to branch on a boolean `IS_ENABLED_TRANSFER_ORDER_STOCKING_FLAG_ROUTING`
|
|
481
|
+
(defined only in NYCHH's launcher) and **otherwise fall through to GroWrk's legacy `$0 total +
|
|
482
|
+
holdInvoice` rule**. That made the legacy rule an **implicit, silent default**: a new client turning
|
|
483
|
+
transfer orders on without the constant silently got another client's classification applied to its
|
|
484
|
+
orders, on a green-looking run. That boolean is **removed everywhere**.
|
|
485
|
+
|
|
486
|
+
Each launcher now declares an explicit mode before its `require_once`:
|
|
487
|
+
|
|
488
|
+
```php
|
|
489
|
+
const TRANSFER_ORDER_DETECTION_MODE = App_Api_Toga2::TRANSFER_ORDER_DETECTION_MODE__STOCKING_FLAG;
|
|
490
|
+
// or
|
|
491
|
+
const TRANSFER_ORDER_DETECTION_MODE = App_Api_Toga2::TRANSFER_ORDER_DETECTION_MODE__ZERO_DOLLAR_HOLD;
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
| Mode constant | Value | Rule | Clients |
|
|
495
|
+
|---|---|---|---|
|
|
496
|
+
| `TRANSFER_ORDER_DETECTION_MODE__STOCKING_FLAG` | `'stocking_flag'` | NetSuite checkbox `custbody_stocking_order` (id 7097) — **unchecked = transfer order** | NYCHH |
|
|
497
|
+
| `TRANSFER_ORDER_DETECTION_MODE__ZERO_DOLLAR_HOLD` | `'zero_dollar_hold'` | `holdInvoice === true` **AND** `total === 0` | GroWrk, Adyen, Elite (pre-set, unused — its master switch is off) |
|
|
498
|
+
|
|
499
|
+
`App_Api_Toga2::TRANSFER_ORDER_DETECTION_MODES` is the allowlist array. **`isTransferOrder()` THROWS
|
|
500
|
+
when the mode is undefined or not in the allowlist** — a deliberate fail-loud, chosen over defaulting
|
|
501
|
+
to the legacy rule, consistent with the 2026-08-27 "every section fails loud" policy. Consequence,
|
|
502
|
+
and it is not optional: **`library` and `worker` MUST deploy together.** A worker deploy without the
|
|
503
|
+
library (or vice versa) crashes GroWrk and Adyen on their next sync run.
|
|
504
|
+
|
|
505
|
+
- **A separate NYCHH-only flag, `IS_ENABLED_INVENTORY_ADJUSTMENT_FULFILLMENT_LINK`,** now guards
|
|
506
|
+
`attachInventoryAdjustmentToItemFulfillment()` (the `custbody_stock_adjustment` link). The removed
|
|
507
|
+
routing boolean had been doing double duty for it; it is an unrelated feature that merely has NYCHH
|
|
508
|
+
as its only client today. See
|
|
509
|
+
[NYCHH inventory-adjustment → item-fulfillment linking](../../../clients/nychh/features/netsuite-inventory-adjustment-fulfillment-link.md).
|
|
510
|
+
|
|
511
|
+
- **`->holdInvoice` is NOT a native NetSuite field** — it is normalised by the REST shim from the
|
|
512
|
+
custom checkbox `custbody_asi_hold` (internal id **4082**), which is **shared across all clients**
|
|
513
|
+
("asi" = Agilant Solutions Inc, the company, not a client prefix). `App_Api_Netsuite_Rest` sets it
|
|
514
|
+
in `buildSalesOrderShims()` (feeding `listSalesOrders()` + `fetchSalesOrderByIdFull()`) and in
|
|
515
|
+
`fetchSalesOrderById()`. **`app/netsuite.php` (SOAP) never sets it at all** — ZERO_DOLLAR_HOLD would
|
|
516
|
+
be silently always-false on a SOAP path. Not a live risk: `common_sync_togasupply.php` uses
|
|
517
|
+
`App_Api_Netsuite_Rest::` on **every** sales-order / receipt / fulfillment path, and all
|
|
518
|
+
transfer-order clients are on REST. Field detail:
|
|
519
|
+
[SuiteQL/REST shim](../../library/features/netsuite-suiteql-rest-shim.md).
|
|
520
|
+
|
|
521
|
+
- **⚠ OPEN (pre-existing, not fixed): an absent NetSuite `total` collapses to `0.0` and can
|
|
522
|
+
misclassify.** The ZERO_DOLLAR_HOLD branch reads
|
|
523
|
+
`$total = isset($nsOrder->total) ? (float) $nsOrder->total : 0.0;`. A REST response with a
|
|
524
|
+
missing/blank `total` (transient API failure — not a real $0 order) becomes `0.0`, and combined with
|
|
525
|
+
the hold flag set, a **non-zero order classifies as a transfer order**. This line predates the mode
|
|
526
|
+
refactor and was carried across unchanged; it goes against the fail-loud rule for cron/sync code.
|
|
527
|
+
Raised with the developer, left as-is pending a decision.
|
|
528
|
+
- **Full mechanics of the stocking-flag mode** (PO bridges, origin-location resolution):
|
|
529
|
+
[NYCHH NetSuite → TransferOrders import](../../../clients/nychh/features/netsuite-transfer-order-import.md).
|
|
530
|
+
Legacy-rule background: [GroWrk transfer order flow](../../../clients/growrk/features/transfer-order-flow.md).
|
|
489
531
|
- `isParentCustomer` — `true` walks NetSuite child customers (`listChildCustomers`); `false`
|
|
490
532
|
syncs the single customer. Prudential-EDI and the live Quad wrapper use `false`.
|
|
491
533
|
- The original combined-array entries used `App_Api_Toga2::CLIENT_UUID_*` consts for Quad and
|
|
@@ -1212,6 +1254,23 @@ the code deploy).
|
|
|
1212
1254
|
|
|
1213
1255
|
## Change history
|
|
1214
1256
|
|
|
1257
|
+
- 2026-09-03 — **Transfer-order detection is now an explicit per-client MODE with no default; the old
|
|
1258
|
+
`IS_ENABLED_TRANSFER_ORDER_STOCKING_FLAG_ROUTING` boolean is removed from both repos.**
|
|
1259
|
+
`isTransferOrder()` previously branched on that boolean (NYCHH-only) and otherwise fell through to
|
|
1260
|
+
GroWrk's `$0 total + holdInvoice` rule — an implicit silent default that would apply one client's
|
|
1261
|
+
classification to another's orders on a green-looking run. Launchers now set
|
|
1262
|
+
`TRANSFER_ORDER_DETECTION_MODE` to one of two new `App_Api_Toga2` constants
|
|
1263
|
+
(`__STOCKING_FLAG` = NYCHH, `__ZERO_DOLLAR_HOLD` = GroWrk/Adyen/Elite), validated against a
|
|
1264
|
+
`TRANSFER_ORDER_DETECTION_MODES` allowlist; an undefined/unknown mode **throws**. All 7 call sites
|
|
1265
|
+
are guarded by `IS_ENABLED_INTEGRATION_TRANSFER_ORDERS`, so the throw can't fire for a client with
|
|
1266
|
+
TOs off. The `custbody_stock_adjustment` adjustment→fulfillment link, which the removed boolean was
|
|
1267
|
+
also guarding, moved to its own `IS_ENABLED_INVENTORY_ADJUSTMENT_FULFILLMENT_LINK` (NYCHH only).
|
|
1268
|
+
Also recorded: `->holdInvoice` is the REST shim's normalisation of the **shared** custom checkbox
|
|
1269
|
+
`custbody_asi_hold` (id 4082, "asi" = Agilant Solutions Inc, not a client), never set on the SOAP
|
|
1270
|
+
path — safe today because this engine is REST-only; and the pre-existing `total ?: 0.0` default in
|
|
1271
|
+
the ZERO_DOLLAR_HOLD branch can misclassify on a transient NetSuite failure (open, unchanged).
|
|
1272
|
+
**`library` + `worker` must deploy together** or GroWrk and Adyen throw on their next run. (rgirish)
|
|
1273
|
+
|
|
1215
1274
|
- 2026-09-03 — **By-reference shim wrote the cursor on the WRONG CLOCK → ITEM_RECEIPTS + ITEM_FULFILLMENTS
|
|
1216
1275
|
re-matched the same record forever (fixed, `library` only).** Follow-on to the same-day by-reference
|
|
1217
1276
|
fix below: the shims set `lastModifiedDate` from the REST GET (UTC), `date()`-formatted in
|
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)_ — 24 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
|
|
8
|
-
- **worker** (Worker) —
|
|
8
|
+
- **worker** (Worker) — 33 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
|
|
9
9
|
- **dbchanges** (Database Changes) _(framework core)_ — 1 doc(s) → [1.0/apps/dbchanges/INDEX.md](1.0/apps/dbchanges/INDEX.md)
|
|
10
10
|
- **worker1.5** (Worker 1.5) — 0 doc(s) → [1.0/apps/worker1.5/INDEX.md](1.0/apps/worker1.5/INDEX.md)
|
|
11
11
|
- **togadesk** (TOGa Desk) — 13 doc(s) → [1.0/apps/togadesk/INDEX.md](1.0/apps/togadesk/INDEX.md)
|
|
@@ -45,6 +45,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
45
45
|
|
|
46
46
|
## Clients
|
|
47
47
|
|
|
48
|
+
- **Adyen** (`adyen`) → [clients/adyen/INDEX.md](clients/adyen/INDEX.md)
|
|
48
49
|
- **AIG (Staples Protection Plan)** (`aig`) → [clients/aig/INDEX.md](clients/aig/INDEX.md)
|
|
49
50
|
- **Canon** (`canon`) → [clients/canon/INDEX.md](clients/canon/INDEX.md)
|
|
50
51
|
- **Compass Canada** (`compass-canada`) → [clients/compass-canada/INDEX.md](clients/compass-canada/INDEX.md)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Client: Adyen `adyen`
|
|
2
|
+
|
|
3
|
+
| Doc | Framework | Summary | Files |
|
|
4
|
+
|-----|-----------|---------|-------|
|
|
5
|
+
| [Adyen](profile.md) | 2.0 | Adyen is a live TOGa Supply client (`Client_Adyen` database) on the shared **NetSuite → TOGa Supply** importer, with its own 1.0 launcher `worker/crons/toga2/ne | worker/crons/toga2/netsuite/sync_togasupply_adyen.php, library/app/api/toga2.php |
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Adyen
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
apps:
|
|
5
|
+
- worker
|
|
6
|
+
- library
|
|
7
|
+
- _underscore
|
|
8
|
+
- api2
|
|
9
|
+
- dbchanges2
|
|
10
|
+
project: Worker
|
|
11
|
+
client: adyen
|
|
12
|
+
type: profile
|
|
13
|
+
status: draft
|
|
14
|
+
updated: 2026-09-03
|
|
15
|
+
owners: ["rgirish"]
|
|
16
|
+
files:
|
|
17
|
+
- worker/crons/toga2/netsuite/sync_togasupply_adyen.php
|
|
18
|
+
- library/app/api/toga2.php
|
|
19
|
+
related:
|
|
20
|
+
- ../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md
|
|
21
|
+
- ../growrk/features/transfer-order-flow.md
|
|
22
|
+
- ../../2.0/apps/_underscore/features/acl-permission-chain.md
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Summary
|
|
26
|
+
|
|
27
|
+
Adyen is a live TOGa Supply client (`Client_Adyen` database) on the shared **NetSuite → TOGa Supply**
|
|
28
|
+
importer, with its own 1.0 launcher `worker/crons/toga2/netsuite/sync_togasupply_adyen.php`.
|
|
29
|
+
|
|
30
|
+
**This profile was created as a stub (2026-09-03).** Adyen had no `clients/` folder despite being a
|
|
31
|
+
live, scheduled client — it surfaced while refactoring transfer-order detection, because Adyen is one
|
|
32
|
+
of the clients whose launcher carries `IS_ENABLED_INTEGRATION_TRANSFER_ORDERS = true`. It is
|
|
33
|
+
deliberately marked `status: draft`: the entries below are what is verifiable from code and existing
|
|
34
|
+
docs, not a curated client picture. **Anyone doing Adyen work should flesh this out.** Whether Adyen
|
|
35
|
+
was previously undocumented intentionally has not been confirmed.
|
|
36
|
+
|
|
37
|
+
## What is known
|
|
38
|
+
|
|
39
|
+
- **NetSuite → TOGa Supply sync.** A per-client thin wrapper over the shared engine — full mechanics
|
|
40
|
+
on [per-client sync](../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md).
|
|
41
|
+
- **Transfer orders are ENABLED** (`IS_ENABLED_INTEGRATION_TRANSFER_ORDERS = true`). Adyen's NetSuite
|
|
42
|
+
transfer orders arrive **as NetSuite sales orders** and are classified by the
|
|
43
|
+
`TRANSFER_ORDER_DETECTION_MODE__ZERO_DOLLAR_HOLD` mode — `holdInvoice === true` **AND**
|
|
44
|
+
`total === 0`, the same rule as GroWrk (see
|
|
45
|
+
[GroWrk transfer order flow](../growrk/features/transfer-order-flow.md)). This mode is now declared
|
|
46
|
+
explicitly in Adyen's launcher; `App_Api_Toga2::isTransferOrder()` throws if it is missing, so
|
|
47
|
+
**`library` and `worker` must deploy together** for Adyen.
|
|
48
|
+
- ⚠ **Not independently verified against Adyen's NetSuite account.** The mode preserves the
|
|
49
|
+
behavior Adyen had under the old implicit default; nobody has confirmed with the client that $0 +
|
|
50
|
+
hold is the right signal *for Adyen*. Worth checking before trusting the split.
|
|
51
|
+
- **`Client_Adyen` is one of the client DBs with surface-layer / ACL drift** noted on the
|
|
52
|
+
[ACL permission chain](../../2.0/apps/_underscore/features/acl-permission-chain.md) and
|
|
53
|
+
[surface-layer schema](../../2.0/apps/dbchanges2/features/surface-layer-schema.md) docs.
|
|
54
|
+
|
|
55
|
+
## Open questions
|
|
56
|
+
|
|
57
|
+
- Formal/legal client name and account owner — recorded here simply as **Adyen**.
|
|
58
|
+
- Which repos beyond the `apps:` list above are genuinely in Adyen's scope.
|
|
59
|
+
- Whether Adyen has client-specific models/features that deserve their own docs.
|
|
@@ -8,11 +8,12 @@ project: Worker
|
|
|
8
8
|
client: canon
|
|
9
9
|
type: profile
|
|
10
10
|
status: active
|
|
11
|
-
updated: 2026-
|
|
12
|
-
owners: ["jcardinal"]
|
|
11
|
+
updated: 2026-09-03
|
|
12
|
+
owners: ["jcardinal", "mhammontree"]
|
|
13
13
|
files: []
|
|
14
14
|
related:
|
|
15
15
|
- ../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md
|
|
16
|
+
- ../../1.0/apps/worker/features/canon-received-po-netsuite-order-sync.md
|
|
16
17
|
---
|
|
17
18
|
|
|
18
19
|
## Summary
|
|
@@ -32,3 +33,7 @@ full mechanics and the diagnosis playbook live in
|
|
|
32
33
|
## Integration touchpoints
|
|
33
34
|
|
|
34
35
|
- **`worker` / `library` (1.0)** — the shared togasupply sync engine + `App_Api_Toga2` bridge.
|
|
36
|
+
- **`worker` (1.0) — received-PO → NetSuite sales orders.** `crons/sync/sync_received_po.php` reads
|
|
37
|
+
Canon POs from the **Vision** databases (`companyId 13785`, catalog companies 13785 / 18610),
|
|
38
|
+
explodes custom-catalog bundles into components, and creates the NetSuite sales order. See
|
|
39
|
+
[Canon received-PO → NetSuite sales order sync](../../1.0/apps/worker/features/canon-received-po-netsuite-order-sync.md).
|
|
@@ -6,8 +6,8 @@ project: Worker
|
|
|
6
6
|
client: elite
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
10
|
-
owners: ["snaredla", "jcardinal"]
|
|
9
|
+
updated: 2026-09-03
|
|
10
|
+
owners: ["snaredla", "jcardinal", "rgirish"]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/netsuite/sync_togasupply_elite.php
|
|
13
13
|
- worker/crons/toga2/netsuite/common_sync_togasupply.php
|
|
@@ -88,6 +88,13 @@ the trait declares must exist as a column, a `CustomRecordFields` row **and** an
|
|
|
88
88
|
so it always trails.)
|
|
89
89
|
- **`MIN_DATETIME_TO_CHECK_FOR_NETSUITE_DATA = '2018-01-01'`**, with the real start controlled by the
|
|
90
90
|
seeded `Parameters` cursors.
|
|
91
|
+
- **`TRANSFER_ORDER_DETECTION_MODE` is pre-set but NOT in use (2026-09-03).** Elite's wrapper declares
|
|
92
|
+
`App_Api_Toga2::TRANSFER_ORDER_DETECTION_MODE__ZERO_DOLLAR_HOLD` (`holdInvoice && total === 0`, the
|
|
93
|
+
GroWrk rule) so that the constant exists if transfer orders are ever switched on — but Elite's
|
|
94
|
+
master switch `IS_ENABLED_INTEGRATION_TRANSFER_ORDERS` is **`false`**, so `isTransferOrder()` is
|
|
95
|
+
never reached today. Before flipping the master switch, confirm Elite's NetSuite actually represents
|
|
96
|
+
transfer orders as $0 held sales orders; do not assume the pre-set mode is correct for them. Mode
|
|
97
|
+
mechanics: [per-client sync](../../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md).
|
|
91
98
|
|
|
92
99
|
## Gotchas / known issues
|
|
93
100
|
|
|
@@ -125,6 +132,13 @@ the trait declares must exist as a column, a `CustomRecordFields` row **and** an
|
|
|
125
132
|
|
|
126
133
|
## Change history
|
|
127
134
|
|
|
135
|
+
- 2026-09-03 — Elite's wrapper now declares `TRANSFER_ORDER_DETECTION_MODE =
|
|
136
|
+
App_Api_Toga2::TRANSFER_ORDER_DETECTION_MODE__ZERO_DOLLAR_HOLD` as part of the shared
|
|
137
|
+
transfer-order detection-mode refactor (the old `IS_ENABLED_TRANSFER_ORDER_STOCKING_FLAG_ROUTING`
|
|
138
|
+
boolean was removed and there is no longer a default mode). **Dormant for Elite** —
|
|
139
|
+
`IS_ENABLED_INTEGRATION_TRANSFER_ORDERS` is `false`, so `isTransferOrder()` is never reached; verify
|
|
140
|
+
the rule against Elite's NetSuite before enabling. (rgirish)
|
|
141
|
+
|
|
128
142
|
- 2026-09-02 — **Elite PURCHASE_ORDERS froze on a dropped PO line that still had a receipt** (shared
|
|
129
143
|
fix, `library` only — now DEPLOYED to `_production`, PO import confirmed unfrozen on Elite). Elite was
|
|
130
144
|
the confirmed prod case: `NETSUITE_EXECUTION_MODE_PURCHASE_ORDERS='1-RUNNING'`, watermark frozen at **2025-10-13**,
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
| Doc | Framework | Summary | Files |
|
|
4
4
|
|-----|-----------|---------|-------|
|
|
5
5
|
| [GroWrk — shipping-carrier cleanup, UPS visibility, and the NetSuite Fulfill-and-Ship punch-out URL](features/shipping-carrier-cleanup.md) | 2.0 | GroWrk (`Core.Clients` id **33**, DB `Client_Growrk`) is where the runaway `ShippingCarriers` duplication was found and fixed (TRUE-81576). | _underscore/Trait/Netsuite/SalesOrder.php, library/app/api/toga2.php, worker/crons/toga2/netsuite/common_sync_togasupply.php, toga2-supply/src/pages/EditShipment/api/UpdateShipmentApi.ts |
|
|
6
|
-
| [Transfer Order Flow (NetSuite SalesOrder workaround → dedicated TransferOrders)](features/transfer-order-flow.md) | 2.0 | GroWrk moves inventory between their own locations (warehouses, distribution points). | _underscore/Model/Client/TransferOrder.php, _underscore/Model/Client/TransferOrderItem.php, _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/ItemReceipt.php, _underscore/Model/Client/ItemReceiptItem.php, _underscore/Trait/Netsuite/TransferOrder.php, dbchanges2/Client_Growrk/, dbchanges2/Client/ |
|
|
6
|
+
| [Transfer Order Flow (NetSuite SalesOrder workaround → dedicated TransferOrders)](features/transfer-order-flow.md) | 2.0 | GroWrk moves inventory between their own locations (warehouses, distribution points). | _underscore/Model/Client/TransferOrder.php, _underscore/Model/Client/TransferOrderItem.php, _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/ItemReceipt.php, _underscore/Model/Client/ItemReceiptItem.php, _underscore/Trait/Netsuite/TransferOrder.php, dbchanges2/Client_Growrk/, dbchanges2/Client/, worker/crons/toga2/netsuite/sync_togasupply_growrk.php, library/app/api/toga2.php |
|
|
7
7
|
| [GroWrk NetSuite-module custom fields — the never-executed 2026-05-28 commit (Units + ServiceRequests 500s)](features/units-netsuite-custom-fields.md) | 2.0 | GroWrk is on the dbchanges2 **`netsuite`** module (`dbchanges2/Client_Growrk/_modules.txt`), so its Units carry the module's seven `c_` custom fields. | _underscore/Trait/Netsuite/Unit.php, _underscore/Model/Growrk/Unit.php, _underscore/Model/Growrk/ItemFulfillment.php, dbchanges2/Client_Growrk/2026-05-28.sql, dbchanges2/_modules/netsuite/2026-07-10a - UnitInventoryFields.sql, dbchanges2/Client_Growrk/2026-08-10 - GrowrkUnitInventoryFieldsCatchUp.sql, dbchanges2/Client_Growrk/2026-08-10b - GrowrkUnitItemDescriptionAcl.sql, dbchanges2/Client_Growrk/2026-08-10c - GrowrkServiceRequestCustomFieldsCatchUp.sql, dbchanges2/Client_Growrk/2026-08-10d - GrowrkServiceRequestTypeAndDispositionSeeds.sql, dbchanges2/Client_Growrk/_modules.txt |
|
|
8
8
|
| [GroWrk](profile.md) | 2.0 | GroWrk is a 2.0 client using TOGA for inventory and order management. | |
|
|
@@ -6,7 +6,7 @@ project: _Underscore
|
|
|
6
6
|
client: growrk
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-09-03
|
|
10
10
|
owners: ["rgirish"]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Client/TransferOrder.php
|
|
@@ -17,9 +17,14 @@ files:
|
|
|
17
17
|
- _underscore/Trait/Netsuite/TransferOrder.php
|
|
18
18
|
- dbchanges2/Client_Growrk/
|
|
19
19
|
- dbchanges2/Client/
|
|
20
|
+
- worker/crons/toga2/netsuite/sync_togasupply_growrk.php
|
|
21
|
+
- library/app/api/toga2.php
|
|
20
22
|
related:
|
|
21
23
|
- 2.0/apps/_underscore/features/recursive-item-fulfillments.md
|
|
22
24
|
- 2.0/apps/worker2/architecture.md
|
|
25
|
+
- ../../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md
|
|
26
|
+
- ../../../1.0/apps/library/features/netsuite-suiteql-rest-shim.md
|
|
27
|
+
- ../../nychh/features/netsuite-transfer-order-import.md
|
|
23
28
|
---
|
|
24
29
|
|
|
25
30
|
## Summary
|
|
@@ -40,8 +45,28 @@ GroWrk moves inventory between their own locations (warehouses, distribution poi
|
|
|
40
45
|
|
|
41
46
|
## How it works
|
|
42
47
|
|
|
43
|
-
### Identification signal
|
|
44
|
-
|
|
48
|
+
### Identification signal — now a NAMED, EXPLICIT mode (2026-09-03)
|
|
49
|
+
|
|
50
|
+
A NetSuite SalesOrder is a TransferOrder if and only if: `holdInvoice === true` **AND** `total === 0`.
|
|
51
|
+
Both conditions must hold — `holdInvoice` alone is not sufficient.
|
|
52
|
+
|
|
53
|
+
This rule is **GroWrk's**, not universal (NYCHH uses the `custbody_stocking_order` stocking flag), and
|
|
54
|
+
it is no longer an implicit fallback. It is now the named mode
|
|
55
|
+
`App_Api_Toga2::TRANSFER_ORDER_DETECTION_MODE__ZERO_DOLLAR_HOLD` (`'zero_dollar_hold'`), declared
|
|
56
|
+
explicitly in GroWrk's 1.0 launcher `worker/crons/toga2/netsuite/sync_togasupply_growrk.php`:
|
|
57
|
+
|
|
58
|
+
```php
|
|
59
|
+
const TRANSFER_ORDER_DETECTION_MODE = App_Api_Toga2::TRANSFER_ORDER_DETECTION_MODE__ZERO_DOLLAR_HOLD;
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`App_Api_Toga2::isTransferOrder()` **throws** if the mode is missing — so GroWrk's launcher and the
|
|
63
|
+
library must deploy together. Adyen and Elite use the same mode. Full mechanics:
|
|
64
|
+
[per-client sync — transfer-order detection mode](../../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md).
|
|
65
|
+
|
|
66
|
+
**`holdInvoice` is not a native NetSuite field** — the REST shim normalises it from the custom
|
|
67
|
+
checkbox `custbody_asi_hold` (id **4082**), which is shared by every client ("asi" = Agilant Solutions
|
|
68
|
+
Inc, the company). The SOAP client never sets it. See the
|
|
69
|
+
[SuiteQL/REST shim](../../../1.0/apps/library/features/netsuite-suiteql-rest-shim.md).
|
|
45
70
|
|
|
46
71
|
### Two-stage closure
|
|
47
72
|
- **Outbound:** An `ItemFulfillment` with `transferOrderId` set (not `salesOrderId`) closes the outbound side — items leave the origin location.
|
|
@@ -113,7 +138,24 @@ Full plan saved at `/Applications/AMPPS/www/transfer-order-plan.md`.
|
|
|
113
138
|
- **Lazy transaction gotcha.** `_Database::register()` auto-starts a lazy transaction since Apr 2026. The worker must call `_Database::transactionCommit(DB_CLIENT)` before any SQS dispatch or the TO insert rolls back silently.
|
|
114
139
|
- **Bridge table scope confirmed as all-clients.** The 8 bridge tables go in `dbchanges2/Client/` (not `Client_Growrk/`). Schema fixes (dtCreated etc.) stay in `Client_Growrk/` — other clients don't need them.
|
|
115
140
|
- **NS identification: both conditions required.** `holdInvoice=true` alone is not a safe signal — must also confirm `total === 0`.
|
|
141
|
+
- **⚠ An ABSENT NetSuite `total` reads as `0.0` and can misclassify a real order (open, unfixed).**
|
|
142
|
+
The 1.0 detection branch does `$total = isset($nsOrder->total) ? (float) $nsOrder->total : 0.0;`, so
|
|
143
|
+
a NetSuite REST response with a missing/blank total — a transient API failure, not a genuine $0
|
|
144
|
+
order — collapses to `0.0`; combined with the hold flag set, a **non-zero order is classified as a
|
|
145
|
+
transfer order**. Pre-existing, carried unchanged through the 2026-09-03 mode refactor, and raised
|
|
146
|
+
with the developer. If you see an unexpected TransferOrder for GroWrk, check this first.
|
|
147
|
+
- **The mode is now mandatory in the launcher.** Removing/forgetting
|
|
148
|
+
`TRANSFER_ORDER_DETECTION_MODE` in `sync_togasupply_growrk.php` makes `isTransferOrder()` **throw**
|
|
149
|
+
— GroWrk's sync will fail loud, by design, rather than silently reclassify.
|
|
116
150
|
|
|
117
151
|
## Change history
|
|
118
152
|
|
|
153
|
+
- 2026-09-03 — GroWrk's `holdInvoice && total === 0` rule was **formalised as the named mode**
|
|
154
|
+
`App_Api_Toga2::TRANSFER_ORDER_DETECTION_MODE__ZERO_DOLLAR_HOLD`, declared explicitly in
|
|
155
|
+
`worker/crons/toga2/netsuite/sync_togasupply_growrk.php`. It had been the *implicit fallback* in
|
|
156
|
+
`isTransferOrder()`, which silently applied GroWrk's rule to any other client that enabled transfer
|
|
157
|
+
orders; there is now no default and an unset mode throws. Recorded that `holdInvoice` is the REST
|
|
158
|
+
shim's normalisation of the shared custom checkbox `custbody_asi_hold` (id 4082) and that the SOAP
|
|
159
|
+
client never sets it, plus the open `total ?: 0.0` misclassification risk. **library + worker must
|
|
160
|
+
deploy together** or GroWrk throws on its next sync run. (rgirish)
|
|
119
161
|
- 2026-06-19 — Designed full transfer order flow; confirmed DB state in prod; produced 5-phase implementation plan pending team review (rgirish)
|
|
@@ -6,8 +6,8 @@ project: Library
|
|
|
6
6
|
client: nychh
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
10
|
-
owners: [jcardinal]
|
|
9
|
+
updated: 2026-09-03
|
|
10
|
+
owners: [jcardinal, rgirish]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/api/toga2.php
|
|
13
13
|
- library/app/api/netsuite/rest.php
|
|
@@ -46,7 +46,13 @@ on-demand fulfillment/sales-order import it triggers — was **migrated from SOA
|
|
|
46
46
|
|
|
47
47
|
Like [NYCHH transfer-order import](./netsuite-transfer-order-import.md), the FK columns and models
|
|
48
48
|
are **all-client infrastructure** but the **attach is gated NYCHH-only** by
|
|
49
|
-
`
|
|
49
|
+
`IS_ENABLED_INVENTORY_ADJUSTMENT_FULFILLMENT_LINK` (defined `true` in `sync_togasupply_hh.php`).
|
|
50
|
+
|
|
51
|
+
> **Renamed 2026-09-03.** This used to piggyback on the transfer-order routing boolean
|
|
52
|
+
> `IS_ENABLED_TRANSFER_ORDER_STOCKING_FLAG_ROUTING`, which was doing double duty. That constant is
|
|
53
|
+
> gone; the adjustment→fulfillment link now has its **own** flag and is fully independent of
|
|
54
|
+
> transfer-order detection (which is now a per-client *mode*, not a boolean — see
|
|
55
|
+
> [per-client sync](../../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md)).
|
|
50
56
|
|
|
51
57
|
## The two FK links (replaced the header/line bridge tables)
|
|
52
58
|
|
|
@@ -63,7 +69,7 @@ link is now a plain PUT of the FK uuid onto the parent record, not a bridge-row
|
|
|
63
69
|
## Attach code — `App_Api_Toga2::attachInventoryAdjustmentToItemFulfillment`
|
|
64
70
|
|
|
65
71
|
Lives in `library/app/api/toga2.php`, called from `syncInventoryAdjustmentFromNetsuite` **after the
|
|
66
|
-
header upsert**, gated on `
|
|
72
|
+
header upsert**, gated on `IS_ENABLED_INVENTORY_ADJUSTMENT_FULFILLMENT_LINK`. It:
|
|
67
73
|
|
|
68
74
|
1. reads `custbody_stock_adjustment` off the adjustment (the related Item Fulfillment),
|
|
69
75
|
2. resolves that fulfillment by `ItemFulfillments.c_netsuiteInternalItemFulfillmentId`,
|
|
@@ -221,6 +227,11 @@ this feature on the scheduled cron.
|
|
|
221
227
|
|
|
222
228
|
## Change history
|
|
223
229
|
|
|
230
|
+
- 2026-09-03 — **Gate flag split off onto its own constant.** The attach was guarded by the
|
|
231
|
+
transfer-order routing boolean `IS_ENABLED_TRANSFER_ORDER_STOCKING_FLAG_ROUTING`, which was doing
|
|
232
|
+
double duty; that constant was removed when transfer-order detection became a per-client *mode*.
|
|
233
|
+
The link is now gated by `IS_ENABLED_INVENTORY_ADJUSTMENT_FULFILLMENT_LINK` (NYCHH launcher only) —
|
|
234
|
+
same behavior, no longer coupled to routing. **library + worker deploy together.** (rgirish)
|
|
224
235
|
- 2026-09-01 — **Unfroze NYCHH `INVENTORY_ADJUSTMENTS` — serial-CASE mismatch in the `/units` lookup
|
|
225
236
|
(shared `library`, working-tree, not yet committed/deployed).** `syncInventoryAdjustmentFromNetsuite`
|
|
226
237
|
keyed the units lookup by the returned serial (`'n/a'`) but probed with the uppercased NetSuite serial
|
|
@@ -6,8 +6,8 @@ project: Library
|
|
|
6
6
|
client: nychh
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
10
|
-
owners: [jcardinal, bala]
|
|
9
|
+
updated: 2026-09-03
|
|
10
|
+
owners: [jcardinal, bala, rgirish]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/api/toga2.php
|
|
13
13
|
- library/app/api/netsuite/rest.php
|
|
@@ -65,11 +65,16 @@ private `linkPurchaseOrderToTransferOrder()` mirrors the existing sales-order PO
|
|
|
65
65
|
> [GroWrk transfer order flow](../../growrk/features/transfer-order-flow.md) — is client-specific and
|
|
66
66
|
> not universal; the TO-detection signal must be read per client.)
|
|
67
67
|
|
|
68
|
-
**Routing is
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
**Routing mode is declared per client — there is NO default (2026-09-03).** The old boolean
|
|
69
|
+
`IS_ENABLED_TRANSFER_ORDER_STOCKING_FLAG_ROUTING` is **gone**. NYCHH's wrapper now sets
|
|
70
|
+
|
|
71
|
+
```php
|
|
72
|
+
const TRANSFER_ORDER_DETECTION_MODE = App_Api_Toga2::TRANSFER_ORDER_DETECTION_MODE__STOCKING_FLAG;
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
in `worker/crons/toga2/netsuite/sync_togasupply_hh.php`. `isTransferOrder()` **throws** if the mode
|
|
76
|
+
is undefined or not in the `TRANSFER_ORDER_DETECTION_MODES` allowlist — see the shared
|
|
77
|
+
[per-client sync — transfer-order detection mode](../../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md).
|
|
73
78
|
|
|
74
79
|
## PO ↔ order bridge links (header + line)
|
|
75
80
|
|
|
@@ -246,16 +251,29 @@ refused on the prod client cluster):
|
|
|
246
251
|
broken.
|
|
247
252
|
- **Order 128829 (no location anywhere) is unresolved** — see the origin-resolution section. Do not
|
|
248
253
|
assume the importer is broken if this single order is stuck; every located order imports.
|
|
249
|
-
- **Routing is
|
|
250
|
-
|
|
251
|
-
`
|
|
252
|
-
`
|
|
254
|
+
- **Routing mode is declared by the wrapper and there is NO fallback.** If a future client should
|
|
255
|
+
route transfer orders by the stocking flag, add
|
|
256
|
+
`const TRANSFER_ORDER_DETECTION_MODE = App_Api_Toga2::TRANSFER_ORDER_DETECTION_MODE__STOCKING_FLAG;`
|
|
257
|
+
before its `require_once 'common_sync_togasupply.php';` — and confirm that client actually sets
|
|
258
|
+
`custbody_stocking_order` in NetSuite. Omitting the mode no longer lands everything in
|
|
259
|
+
SalesOrders; it **throws** (deliberate — see the shared engine doc).
|
|
260
|
+
- **The `custbody_stock_adjustment` inventory-adjustment link is on its OWN flag now.**
|
|
261
|
+
`attachInventoryAdjustmentToItemFulfillment()` used to piggyback on the old routing boolean; it is
|
|
262
|
+
now gated by `IS_ENABLED_INVENTORY_ADJUSTMENT_FULFILLMENT_LINK` (NYCHH-only) — see
|
|
263
|
+
[NYCHH inventory-adjustment → item-fulfillment linking](./netsuite-inventory-adjustment-fulfillment-link.md).
|
|
253
264
|
- **`custbody_stocking_order` must reach the shim.** `fetchSalesOrderById()` now carries the field
|
|
254
265
|
via `customFieldList`; a missing custom field read returns null → the order routes as a transfer
|
|
255
266
|
order (unchecked = transfer). Verify the field is populated in NetSuite before trusting the split.
|
|
256
267
|
|
|
257
268
|
## Change history
|
|
258
269
|
|
|
270
|
+
- 2026-09-03 — **`IS_ENABLED_TRANSFER_ORDER_STOCKING_FLAG_ROUTING` removed.** NYCHH now declares
|
|
271
|
+
`const TRANSFER_ORDER_DETECTION_MODE = App_Api_Toga2::TRANSFER_ORDER_DETECTION_MODE__STOCKING_FLAG;`
|
|
272
|
+
in `sync_togasupply_hh.php`; `isTransferOrder()` throws on an undefined/unknown mode instead of
|
|
273
|
+
silently falling back to GroWrk's `$0 + hold` rule. The `custbody_stock_adjustment`
|
|
274
|
+
inventory-adjustment → item-fulfillment link, which the old boolean was also guarding, moved to its
|
|
275
|
+
own NYCHH-only `IS_ENABLED_INVENTORY_ADJUSTMENT_FULFILLMENT_LINK`. **library and worker must deploy
|
|
276
|
+
together.** (rgirish)
|
|
259
277
|
- 2026-09-02 — Recorded **why** the importer can never resolve an origin from the NetSuite header:
|
|
260
278
|
on a NYCHH transfer order the **destination** location is the NetSuite customer and the **origin**
|
|
261
279
|
is the NetSuite warehouse, so `entity` is always the destination (verified in prod across all 1,930
|
package/package.json
CHANGED