toga-ai 1.0.917 → 1.0.919
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/knowledge/1.0/apps/library/INDEX.md +1 -0
- package/knowledge/1.0/apps/library/features/netsuite-closed-order-rule.md +45 -0
- package/knowledge/1.0/apps/library/features/netsuite-suiteql-api-reference.md +5 -1
- package/knowledge/1.0/apps/worker/INDEX.md +1 -1
- package/knowledge/1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md +30 -71
- package/knowledge/1.0/apps/worker/workflows/reprocessing-one-netsuite-order.md +17 -5
- package/knowledge/2.0/apps/worker2/features/qa-qc-review-pipeline.md +19 -1
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/adyen/profile.md +2 -2
- package/knowledge/clients/compass-usa/profile.md +2 -1
- package/knowledge/clients/elite/INDEX.md +1 -1
- package/knowledge/clients/elite/features/inventory-quantities-drop-ship.md +23 -63
- package/knowledge/clients/elite/features/netsuite-togasupply-sync.md +14 -1
- package/package.json +1 -1
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
| [Error Capture in 1.0 (App_Error_Capture → shared 2.0 Logs DB)](features/error-capture-1-0.md) | 1.0 error-reporting into the shared 2.0 Logs DB (`App_Error_Capture::captureException`), mirroring 2.0's Issue/Event; open when adding a 1.0 capture path or deb |
|
|
18
18
|
| [HTTP 500 Error Monitor (App_SystemMonitor_500Error) — email-only count; diagnose via error.id](features/http-500-error-monitor.md) | 1.0 monitor `App_SystemMonitor_500Error`: counts `Logs.Api` responseCode=500 in the last 5 min and emails devteam; no ClickUp. |
|
|
19
19
|
| [1.0 MVC Page Pattern & New-App Skeleton](features/mvc-page-pattern-and-app-skeleton.md) | Recipe for a new 1.0 (`App_`) app and for adding pages: folder-based MVC routing, page lifecycle, models, multi-DB config; open when standing up a 1.0 app or ad |
|
|
20
|
+
| [NetSuite Closed sales orders — keep if related records, delete if none](features/netsuite-closed-order-rule.md) | How the NetSuite → TOGa Supply sync treats a Closed SO (all clients): keep + import when anything was created from it, delete when nothing was; plus the ACL the |
|
|
20
21
|
| [NetSuite File Cabinet Content Retrieval via RESTlet (fetchInvoiceFile)](features/netsuite-filecabinet-restlet.md) | How 1.0 pulls NetSuite File Cabinet binary content (invoice PDFs) via a SuiteScript RESTlet (`fetchInvoiceFile`); open when fetching NetSuite file bytes over RE |
|
|
21
22
|
| [assetType from NetSuite itemtype during Item Sync (opt-in per client)](features/netsuite-item-assettype-sync.md) | Stamp `Items.assetTypeId` from NetSuite `item.itemtype` during item sync (opt-in per client, fill-NULL-only); open before enabling asset-type mapping for a clie |
|
|
22
23
|
| [NetSuite classification → ItemClasses sync (opt-in per client)](features/netsuite-item-class-sync.md) | `App_Api_Toga2::getCreateItemClass()` mirrors NetSuite's **classification tree** into the client's `ItemClasses` table and stamps `Items.itemClassId`. |
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "NetSuite Closed sales orders — keep if related records, delete if none"
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: library
|
|
5
|
+
project: Library
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-09-30
|
|
10
|
+
owners: ["rgirish"]
|
|
11
|
+
files:
|
|
12
|
+
- library/app/api/toga2.php
|
|
13
|
+
- library/app/api/netsuite/rest.php
|
|
14
|
+
- worker/crons/toga2/netsuite/common_sync_togasupply.php
|
|
15
|
+
- worker/crons/toga2/netsuite/process_netsuite_order.php
|
|
16
|
+
- worker/crons/toga2/netsuite/delete_closed_netsuite_orders.php
|
|
17
|
+
related:
|
|
18
|
+
- ../../worker/features/netsuite-togasupply-per-client-sync.md
|
|
19
|
+
- ../../worker/workflows/reprocessing-one-netsuite-order.md
|
|
20
|
+
- ../../../2.0/apps/_underscore/features/acl-permission-chain.md
|
|
21
|
+
- ../../../clients/compass-usa/profile.md
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
How the NetSuite → TOGa Supply sync treats a Closed SO (all clients): keep + import when anything was created from it, delete when nothing was; plus the ACL the delete needs.
|
|
25
|
+
|
|
26
|
+
## Summary
|
|
27
|
+
- **Rule (2026-09-29, all NetSuite sync clients):** a NetSuite SO in status `Closed`:
|
|
28
|
+
- **with** related records (any transaction whose `createdfrom` is the SO — fulfillment, invoice, PO, return) = **completed**. Keep it and import it like any order, shipments included.
|
|
29
|
+
- **with none** = **duplicate**. Never import it; delete the TOGa order if it was imported before it closed.
|
|
30
|
+
- **Replaces the 2026-09-23 decision "delete every Closed order".** That rule skipped closed orders that really shipped, so TOGa on-hand stayed above NetSuite.
|
|
31
|
+
|
|
32
|
+
## How it works
|
|
33
|
+
- `App_Api_Netsuite_Rest::hasRelatedTransactions(int $orderId)` — SuiteQL `COUNT(*)` on `transactionline` where `createdfrom = <id>` and `transaction <> <id>`.
|
|
34
|
+
- `App_Api_Toga2::isNetsuiteOrderClosedWithoutRelatedRecords($nsOrder)` = status `Closed` AND no related transactions. Used by the SO loop in `common_sync_togasupply.php`, `syncTransferOrderFromNetsuite`, and `process_netsuite_order.php`.
|
|
35
|
+
- `App_Api_Toga2::deleteClosedOrderFromNetsuite()` has its own **KEEP guard** (re-checks `hasRelatedTransactions`, logs `KEEP`, returns false). So every caller, including the one-time tool `delete_closed_netsuite_orders.php` (dry run unless `--execute`), is safe.
|
|
36
|
+
- The delete removes the SalesOrder/TransferOrder with its lines, invoices, fulfillments (units, tracking bridges) and bridge rows. Records on the other side of a bridge (PO, other SO, TrackingNumber, Unit) are never deleted — only the link row. Fails loud; a half-done delete is finished by the next run.
|
|
37
|
+
- Backfill for shipped closed orders that were skipped under the old rule: re-run their fulfillments with `process_netsuite_records.php` (see [re-processing workflow](../../worker/workflows/reprocessing-one-netsuite-order.md)).
|
|
38
|
+
|
|
39
|
+
## Gotchas
|
|
40
|
+
- **The delete reads every route in `CLOSED_ORDER_DELETE_ROUTES` (toga2.php) — the client's API role (roleId 3) needs read on ALL of them:** record permission + an expression on each logic group + field permission (at least `uuid`). One gap throws, and because it runs in the SO loop, **SALES_ORDERS freezes** (and the sections after it). Find gaps per client with `App_Api_Toga2::checkClosedOrderDeleteAccess()` (run by `delete_closed_netsuite_orders.php`; it lists every gap in one run). ACL layers: [acl-permission-chain](../../../2.0/apps/_underscore/features/acl-permission-chain.md).
|
|
41
|
+
- Worked example (Compass, 2026-09-29): records 312/313 (transfer-orders) had logic groups with no expression; 257 (entitlement-sales-orders) and 254 (sales-order-payments) had no `AclRecordPermissions`, then no `AclFieldPermissions` (`EZ-2` on `uuid`). Fix: `dbchanges2/Client_Compass/2026-09-29a - TransferOrderAclExpressions.sql`. Then reset `NETSUITE_EXECUTION_MODE_SALES_ORDERS` to `864000-IDLE`.
|
|
42
|
+
- Adding a route to `CLOSED_ORDER_DELETE_ROUTES` = a new ACL need on every client. Run the access check fleet-wide before deploy.
|
|
43
|
+
|
|
44
|
+
## Change history
|
|
45
|
+
- 2026-09-30 — Created. Closed SO with related records is now kept + imported; only Closed with none is deleted (replaces 2026-09-23 "delete every Closed"). Added KEEP guard in the delete and the ACL gotcha from the Compass SALES_ORDERS freeze. (rgirish)
|
|
@@ -6,7 +6,7 @@ project: Library
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-30
|
|
10
10
|
owners: ["dfranks", "jcardinal", "kyalamarthi", "ajean", "rgirish"]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/api/netsuite/rest.php
|
|
@@ -263,8 +263,12 @@ SalesOrd per-id GET counts by `lastmodifieddate` window: ~16,600 (90d) / ~61,900
|
|
|
263
263
|
- Field/relationship availability varies by NetSuite account **and** record type — probe the live account before assuming a column exists.
|
|
264
264
|
- **`IS NOT NULL` on a custom `transactionline` column inside an aggregate query is a ~15× planner trap — prefer `> 0` (or `NVL(col,0) <> 0`).** Filtering a custom employee-ref column (`custcol_sales_rep_line`) with `AND tl.custcol_sales_rep_line IS NOT NULL` inside a `GROUP BY` / power-sum aggregate (`MOD(SUM(MOD(id*id,P)),P)`) made the planner scan pathologically: **~122s** vs **~8s** for the semantically identical `AND tl.custcol_sales_rep_line > 0` (employee internalIds are always positive) — a ~15× regression purely from the predicate form. Measured against the Forecast2 reconciliation checker (TRUE-79968). Use `> 0` / `NVL(col,0) <> 0` for a custom ref column you only need to test for presence in an aggregate; reserve `IS NOT NULL` for non-aggregate row filters.
|
|
265
265
|
- **⚠ `> 0` is NOT a universal fix — it is column-dependent.** A predicate on a *different* custom column, `tl.custcol_item_sku > 0`, **never returned even as a STANDALONE aggregate** (not just inside the UNION+MOD fingerprint), while `tl.custcol_sales_rep_line > 0` in the same query stayed fast (~8s). Merely **SELECTing** the custom column is cheap either way. **Durable rule when a custom-column predicate hangs:** keep the fast predicates in SQL, **SELECT** the problem column, and apply that gate in **PHP** — do not assume `> 0` will rescue every custom column. (TRUE-79973, `test/@dave/checker.php` / `fixer.php`.)
|
|
266
|
+
- **Inventory quantities: use `aggregateitemlocation`, not `inventorybalance`.** `inventorybalance.quantityavailable` does NOT subtract SO commitments for many items, so it will not match the NetSuite UI. `aggregateitemlocation` gives `quantityonhand` / `quantityavailable` / `quantitycommitted` per item + location and matches the UI — use it to check TOGa inventory against NetSuite (Elite, 2026-09-29).
|
|
266
267
|
|
|
267
268
|
## Related
|
|
268
269
|
|
|
269
270
|
- `netsuite-suiteql-rest-shim.md` — deep field semantics (signs, `tl.id`==line, `iscogs`, ShipItem cost, `createdFrom`) that sit on top of these mechanics.
|
|
270
271
|
- `../../worker/features/forecast2-netsuite-reconciliation.md` — the audit/trueup tooling that consumes this API.
|
|
272
|
+
|
|
273
|
+
## Change history
|
|
274
|
+
- 2026-09-30 — Gotcha: `inventorybalance.quantityavailable` ignores SO commitments; use `aggregateitemlocation`. (rgirish)
|
|
@@ -22,5 +22,5 @@
|
|
|
22
22
|
| [isFulfillable Multi-Client Backfill (all togasupply clients)](workflows/isfulfillable-multi-client-backfill.md) | One-time backfill catching up `Items.isFulfillable` on existing items across all 17 togasupply clients; open when running/re-running it or deciding whether rema |
|
|
23
23
|
| [Deduplicating NetSuite duplicate Items across client DBs (recursive MERGE)](workflows/netsuite-duplicate-items-dedup.md) | Per-client cleanup collapsing duplicate `Items` rows (same `partNumber`) to one canonical via recursive MERGE; open before running it — and read the catalog-bli |
|
|
24
24
|
| [Onboarding a Client to the NetSuite TOGa Supply Sync](workflows/onboarding-client-to-netsuite-togasupply-sync.md) | How to add a new TOGa 2 client to the per-client NetSuite → TOGa Supply importer; open when onboarding a client, and read the vendor-seed / ACL / Parameters-see |
|
|
25
|
-
| [Re-processing
|
|
25
|
+
| [Re-processing NetSuite orders, fulfillments and receipts (process_netsuite_order.php / process_netsuite_records.php)](workflows/reprocessing-one-netsuite-order.md) | Run the real sync for given NetSuite ids on ONE client, without touching cursors: one order (+ its vendor POs), or a list of fulfillments/receipts. |
|
|
26
26
|
| [Tracing a 1.0 worker cron run in production (no stdout, silent skips)](workflows/tracing-a-worker-cron-run-in-production.md) | How to answer "did this cron run, and what did it do?" on the 1.0 worker tier (no usable stdout, skipped runs leave no trace); open before concluding a cron "ne |
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-30
|
|
10
10
|
owners: ["dfranks", "bala", "jcardinal", "mhammontree", "snaredla", "rgirish"]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/netsuite/common_sync_togasupply.php
|
|
@@ -141,6 +141,18 @@ can show an OLD line; trust the newest `Event.context.errorLine` for the real sp
|
|
|
141
141
|
gives `memoryPeakMb` — a small peak (e.g. 20 MB) rules out "giant record / OOM" and points at a
|
|
142
142
|
plain thrown warning instead.
|
|
143
143
|
|
|
144
|
+
**Fastest path to the real error (2026-09-29):**
|
|
145
|
+
|
|
146
|
+
- `N-RUNNING` with N shrinking run after run = that section throws every run. SALES_ORDERS aborts the whole
|
|
147
|
+
script, so the sections after it freeze too — fix SO first.
|
|
148
|
+
- Read the api2 rejection in `Logs_<Client>.Api`: `responseCode >= 400 AND responsePayload LIKE '%"api":"Agilant"%'`,
|
|
149
|
+
newest first, `LIMIT 1` + tight date filter.
|
|
150
|
+
- **`Logs.Issue` keeps only the FIRST trace per fingerprint** — its trace can name the wrong client and line.
|
|
151
|
+
Use `Logs.Event` (has `clientId`) or `Logs_<Client>.Api` instead.
|
|
152
|
+
- Fleet check: one query on prod-client, `UNION` over every `Client_*.Parameters`
|
|
153
|
+
(`WHERE name LIKE 'NETSUITE_EXECUTION_MODE_%'`). Wrap text columns in
|
|
154
|
+
`CONVERT(... USING utf8mb4) COLLATE utf8mb4_general_ci` or the UNION fails on mixed collations.
|
|
155
|
+
|
|
144
156
|
#### Manual reset after the fix: `864000-IDLE` — get both halves right (2026-09-08)
|
|
145
157
|
|
|
146
158
|
A section self-heals from `1-RUNNING` on its own, but climbing ×3 from a 1-second window back to the
|
|
@@ -499,42 +511,13 @@ record; every section behaves the way SALES_ORDERS always did (throw → freeze
|
|
|
499
511
|
poison record re-tried each run → go read the error). This is an application of the universal
|
|
500
512
|
coding-style rule *never catch-and-silently-ignore*, not a new engine-specific policy.
|
|
501
513
|
|
|
502
|
-
###
|
|
503
|
-
|
|
504
|
-
`common_sync_togasupply.php` (~L746) does
|
|
505
|
-
|
|
506
|
-
```php
|
|
507
|
-
if ($nsSalesOrder->status === 'Closed') { continue; }
|
|
508
|
-
```
|
|
509
|
-
|
|
510
|
-
**before** the `isTransferOrder()` branch. So a transfer order that was **closed in NetSuite never
|
|
511
|
-
reaches `App_Api_Toga2::syncTransferOrderFromNetsuite()`**, and that function's existing
|
|
512
|
-
`case 'Closed'` never runs. **`Closed` is the only status the loop drops** - Pending Fulfillment,
|
|
513
|
-
Partially Fulfilled, Billed and Cancelled all pass through and map fine.
|
|
514
|
-
|
|
515
|
-
There is no other way in. The item-fulfillment route (`syncSalesOrderFromNetsuite`) and 2.0's
|
|
516
|
-
`_Trait_Netsuite_TransferOrder::syncNetsuiteTransferOrder` both need a **fulfillment or item
|
|
517
|
-
receipt**, which an order closed with nothing shipped will never have. Result on NYCHH: **18 transfer
|
|
518
|
-
orders stuck on `Pending`, the oldest from 2022.**
|
|
519
|
-
|
|
520
|
-
**The fix - a stage-only write from INSIDE the closed block, before the `continue`:**
|
|
514
|
+
### Closed NetSuite orders (rule 2026-09-29)
|
|
521
515
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
-
|
|
527
|
-
that the skip exists to avoid cannot happen;
|
|
528
|
-
- a transfer order we never imported is **left alone**, not created.
|
|
529
|
-
|
|
530
|
-
**Why not just route closed transfers into the full `syncTransferOrderFromNetsuite`:** that function
|
|
531
|
-
**CREATES** a transfer order when it finds none, so it would invent rows out of years-old closed
|
|
532
|
-
NetSuite orders for Adyen, Elite and GroWrk. **17 of the 20 launchers have
|
|
533
|
-
`IS_ENABLED_INTEGRATION_TRANSFER_ORDERS` false**, so nothing changes for them; the three that have it
|
|
534
|
-
true all use `ZERO_DOLLAR_HOLD` detection.
|
|
535
|
-
|
|
536
|
-
**Status: written, NOT committed and NOT deployed** as of 2026-09-08. Touches `worker` and `library`,
|
|
537
|
-
so both deploy together (the standing rule for this pair).
|
|
516
|
+
A Closed SO **with** related records (fulfillment, invoice, PO…) is completed — imported like any order.
|
|
517
|
+
A Closed SO with **none** is a duplicate — never imported, deleted if already in TOGa. Replaces the
|
|
518
|
+
2026-09-23 "delete every Closed" rule and the 2026-09-08 stage-only `updateTransferOrderStageToClosed`
|
|
519
|
+
(removed). Full rule, KEEP guard and the ACL the delete needs (a gap freezes SALES_ORDERS):
|
|
520
|
+
[netsuite-closed-order-rule](../../library/features/netsuite-closed-order-rule.md).
|
|
538
521
|
|
|
539
522
|
### ⚠ An OUT-OF-SCOPE transfer-order destination customer wedged INVENTORY_ADJUSTMENTS — the importer now SKIPS it (fixed + deployed 2026-09-14)
|
|
540
523
|
|
|
@@ -1093,6 +1076,9 @@ discovered per-client.
|
|
|
1093
1076
|
scheduled wrapper runs. Uses `NETSUITE_SYNC_LOOKUPS_ONLY` (early return after LOOKUPS in
|
|
1094
1077
|
`common_sync_togasupply.php`).
|
|
1095
1078
|
|
|
1079
|
+
**Given fulfillments / receipts, any client:** `process_netsuite_records.php <client> --fulfillment=ids --receipt=ids`
|
|
1080
|
+
— same [workflow doc](../workflows/reprocessing-one-netsuite-order.md).
|
|
1081
|
+
|
|
1096
1082
|
Two CLI scripts written for the Elite backfill live under
|
|
1097
1083
|
`test/@srija/Elite Testing/Service Requests/` (they are operator tools, **not** scheduled crons —
|
|
1098
1084
|
do not add them to `cron.worker.sync.json`):
|
|
@@ -1771,30 +1757,13 @@ library (or vice versa) crashes GroWrk and Adyen on their next sync run.
|
|
|
1771
1757
|
onboarding effort.
|
|
1772
1758
|
Both are pre-existing and unrelated to this session's fixes.
|
|
1773
1759
|
|
|
1774
|
-
-
|
|
1775
|
-
|
|
1776
|
-
`
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
**GroWrk** location **1569**. Deployed.
|
|
1782
|
-
- **The inventory-ADJUSTMENT unit path now has the SAME auto-create (fixed 2026-08-30) — it did not
|
|
1783
|
-
before, which froze GroWrk.** `syncInventoryAdjustmentFromNetsuite`'s serialized-unit processing took
|
|
1784
|
-
the unit's location from the inventory number's `custitemnumber_location` (the unit's own current
|
|
1785
|
-
location, falling back to the adjustment line location), but — unlike `syncItemReceiptFromNetsuite` —
|
|
1786
|
-
**never auto-created a missing warehouse**, so `POST /units` failed **EV-12** "unable to find a unique
|
|
1787
|
-
match" on `location` (`searchCriteria c_netsuiteInternalLocationId=1596`; **0 rows** in
|
|
1788
|
-
`Client_Growrk.Locations` for 1596), leaving `NETSUITE_EXECUTION_MODE_INVENTORY_ADJUSTMENTS` stuck at
|
|
1789
|
-
`1-RUNNING`. **Fix:** capture the unit location's NAME (`$unitNetsuiteLocationName` from the
|
|
1790
|
-
`custitemnumber_location` ref, carried in `$unitsToGet` as `netsuiteLocationName`) and, before **both**
|
|
1791
|
-
unit-location reference points (the per-unit PUT-create path and the batch `POST /units` path),
|
|
1792
|
-
`POST /locations` when the id is not already in `$lookupWarehouseByNetsuiteLocationInternalId` and a
|
|
1793
|
-
name is present — same payload as the receipt sync (`name`, `c_netsuiteInternalLocationId`,
|
|
1794
|
-
`locationType = WAREHOUSE_LOCATION_TYPE_UUID`, `isActive=true`, `allowReceiving`/`allowSending=false`),
|
|
1795
|
-
then register it in the warehouse lookup. Guarded by `!isset(lookup[id]) && !empty(name)` so it fires
|
|
1796
|
-
only for genuinely-missing locations and never duplicates. Shared 1.0 path — benefits all
|
|
1797
|
-
serialized-adjustment clients.
|
|
1760
|
+
- **The sync NEVER auto-creates a warehouse Location (rule 2026-09-29, all clients).** An unknown NetSuite
|
|
1761
|
+
location throws via `App_Api_Toga2::throwIfUnknownWarehouse()` (toga2.php): "Verify it, create the Location with
|
|
1762
|
+
`c_netsuiteInternalLocationId` = <id>, then rerun." It replaced 7 `POST /locations` spots: receipt pre-check over
|
|
1763
|
+
ALL lines before any write, receipt + adjustment unit paths, TO origin + destination. A person verifies and creates
|
|
1764
|
+
the Location (client needs a Warehouse `LocationTypes` row, shared uuid `78c24f51-b33b-438b-e3f2-b6232d9ecb75`).
|
|
1765
|
+
- **Unit location comes from the receipt/adjustment LINE, not `custitemnumber_location`.** That field is a bin/bay
|
|
1766
|
+
custom list whose ids overlap real location ids, so reading it as a location put units in the wrong warehouse.
|
|
1798
1767
|
|
|
1799
1768
|
- **⚠ The exact api2 rejection detail lives in `Logs_<Client>.Api`, not `Logs.Event` (which TRUNCATES
|
|
1800
1769
|
it).** When a section throws on an api2 non-success, `Logs.Event` records the error envelope but
|
|
@@ -1983,17 +1952,6 @@ library (or vice versa) crashes GroWrk and Adyen on their next sync run.
|
|
|
1983
1952
|
invoice against a duplicated SO it will false-throw the same way, and **their duplicates have a DIFFERENT
|
|
1984
1953
|
cause than Compass's Office-Depot dual-source.** Also a data-quality question: why do six non-Compass
|
|
1985
1954
|
clients have duplicate SalesOrders rows at all.
|
|
1986
|
-
- **⚠ ITEM_RECEIPTS auto-creates a warehouse Location — the client's `LocationTypes` MUST have a
|
|
1987
|
-
"Warehouse" row or it EV-12-freezes (fixed 2026-09-17, Prudential).** `syncItemReceiptFromNetsuite`
|
|
1988
|
-
(`library/app/api/toga2.php`) `POST /v2/locations` with `locationType.uuid = WAREHOUSE_LOCATION_TYPE_UUID
|
|
1989
|
-
= 78c24f51-b33b-438b-e3f2-b6232d9ecb75` (:3997). `Client_Prudential.LocationTypes` had only a "Shipping"
|
|
1990
|
-
row — no "Warehouse" — so the reference didn't resolve → **EV-12** (prod `Logs.Issue` 757, clientId 5,
|
|
1991
|
-
ITEM_RECEIPTS `1-RUNNING`, cursor stuck 2026-09-14 15:12:43; confirmed via `Logs_Prudential.Api` `POST
|
|
1992
|
-
/v2/locations` 400). **Fix:** guarded/re-runnable INSERT of the Warehouse `LocationTypes` row using the
|
|
1993
|
-
**same literal uuid every client uses** (the documented shared-lookup-uuid exception to "uuids must be
|
|
1994
|
-
random"; `Client_Nychh` has it as id 2) —
|
|
1995
|
-
`dbchanges2/Client_Prudential/2026-09-17 - SeedWarehouseLocationType.sql`. **The live `LocationTypes`
|
|
1996
|
-
column is `name`, NOT the older `type`.** The section self-heals once the row exists (no manual mode reset).
|
|
1997
1955
|
- **⚠ ROOT CAUSE of the duplicate `Items`: the inventory-adjustment item-create OMITTED the catalog
|
|
1998
1956
|
(fixed 2026-08-31).** `getCreateItem` (`toga2.php:6233`) and the fulfillment create (~4951) stamp
|
|
1999
1957
|
`'catalog' => ['name' => 'Agilant']`; `syncInventoryAdjustmentFromNetsuite`'s item-create did **not** →
|
|
@@ -2086,6 +2044,7 @@ library (or vice versa) crashes GroWrk and Adyen on their next sync run.
|
|
|
2086
2044
|
[NetSuite Sync Alert Monitor](../../library/features/netsuite-sync-alert-monitor.md).
|
|
2087
2045
|
|
|
2088
2046
|
## Change history
|
|
2047
|
+
- 2026-09-30 — Closed SO rule: keep if related records, delete only if none ([doc](../../library/features/netsuite-closed-order-rule.md)). Sync never auto-creates warehouse Locations (`throwIfUnknownWarehouse`); unit location from the line, not `custitemnumber_location`. Added `process_netsuite_records.php` and the stuck-sync diagnosis path. (rgirish)
|
|
2089
2048
|
- 2026-09-24 — TO↔PO links: two-direction reconcile (vendor POs no longer upstream; retireStale moves downstream SO links to 326/330); fixed SO downstream line-link deletes on PO re-sync; new `process_netsuite_order.php` + `NETSUITE_SYNC_LOOKUPS_ONLY`. (jcardinal)
|
|
2090
2049
|
- 2026-09-22 — **The `Logs.Issue` 589 doom-loop's framework root cause is now fully explained and has
|
|
2091
2050
|
tooling, but this sync is NOT yet patched.** Three independent reasons the 2006 reconnect was dead
|
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "Re-processing
|
|
2
|
+
title: "Re-processing NetSuite orders, fulfillments and receipts (process_netsuite_order.php / process_netsuite_records.php)"
|
|
3
3
|
framework: "1.0"
|
|
4
4
|
repo: worker
|
|
5
5
|
project: Worker
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
10
|
-
owners: [jcardinal]
|
|
9
|
+
updated: 2026-09-30
|
|
10
|
+
owners: ["jcardinal", "rgirish"]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/netsuite/process_netsuite_order.php
|
|
13
|
+
- worker/crons/toga2/netsuite/process_netsuite_records.php
|
|
13
14
|
- worker/crons/toga2/netsuite/common_sync_togasupply.php
|
|
14
15
|
- library/app/api/netsuite/rest.php
|
|
15
16
|
related:
|
|
16
17
|
- ../features/netsuite-togasupply-per-client-sync.md
|
|
17
18
|
- ../../library/features/transfer-order-purchase-order-link-reconcile.md
|
|
18
19
|
- ../../../clients/nychh/features/netsuite-transfer-order-import.md
|
|
20
|
+
- ../../library/features/netsuite-closed-order-rule.md
|
|
19
21
|
---
|
|
20
22
|
|
|
21
|
-
Run the real
|
|
23
|
+
Run the real sync for given NetSuite ids on ONE client, without touching cursors: one order (+ its vendor POs), or a list of fulfillments/receipts. Open to heal, debug, or backfill specific records.
|
|
22
24
|
|
|
23
25
|
## Summary
|
|
24
26
|
- Manual CLI, **not scheduled** — never add to `cron.worker.*.json`.
|
|
@@ -39,14 +41,24 @@ Steps the script runs:
|
|
|
39
41
|
2. Defines `NETSUITE_SYNC_LOOKUPS_ONLY` and requires the wrapper → `common_sync_togasupply.php` builds config + lookups, then returns before any section.
|
|
40
42
|
3. `--tranid` → `App_Api_Netsuite_Rest::findSalesOrderIdByTranId()` (format-validated).
|
|
41
43
|
4. Refuses an order whose customer / end-user customer does not map to this client.
|
|
42
|
-
5. Closed → `deleteClosedOrderFromNetsuite
|
|
44
|
+
5. Closed with no related records → `deleteClosedOrderFromNetsuite` ([rule](../../library/features/netsuite-closed-order-rule.md)); Closed with related records syncs like any order; TO (detection mode) → `syncTransferOrderFromNetsuite` (includes the [two-direction PO link reconcile](../../library/features/transfer-order-purchase-order-link-reconcile.md)); else → `syncSalesOrderFromNetsuite`.
|
|
43
45
|
6. `listPurchaseOrderIdsCreatedFromOrder()` → `syncPurchaseOrderFromNetsuite` for every vendor PO created from the order.
|
|
44
46
|
|
|
45
47
|
Verify: grep the PHP error log for `reconcileTransferOrderPurchaseOrderLinks` and `syncPurchaseOrderFromNetsuite: DELETE`.
|
|
46
48
|
|
|
49
|
+
### Fulfillments / receipts: `process_netsuite_records.php`
|
|
50
|
+
```
|
|
51
|
+
php crons/toga2/netsuite/process_netsuite_records.php <client> --fulfillment=<id>[,<id>] --receipt=<id>[,<id>]
|
|
52
|
+
```
|
|
53
|
+
- Same guards: refuses while `sync_togasupply_<client>.php` runs; `NETSUITE_SYNC_LOOKUPS_ONLY`; no cursor writes; fail-loud.
|
|
54
|
+
- Runs the real `syncItemReceiptFromNetsuite` (receipts first; the receipt sync also syncs its PO) then `syncItemFulfillmentFromNetsuite`.
|
|
55
|
+
- Scope checks: a fulfillment's source SO customer (or end-user customer) must map to this client; with `IS_ENABLED_ITEM_RECEIPT_OWNED_WAREHOUSE_SCOPE` a receipt needs a line at an owned warehouse (`allowReceiving = 1`). Otherwise it throws "refusing to import it".
|
|
56
|
+
- Used 2026-09-29 to backfill 7 Elite closed-order shipments skipped under the old closed rule.
|
|
57
|
+
|
|
47
58
|
## Gotchas
|
|
48
59
|
- A TO skipped by the sync (out-of-scope customer) prints `NOT imported` — read the error log, not a script bug.
|
|
49
60
|
- Needs `IS_ENABLED_INTEGRATION_TRANSFER_ORDERS` true in the wrapper for TO routing; otherwise the order syncs as a sales order.
|
|
50
61
|
|
|
51
62
|
## Change history
|
|
63
|
+
- 2026-09-30 — Added `process_netsuite_records.php` (fulfillments/receipts by id); Closed routing follows the new keep/delete rule. (rgirish)
|
|
52
64
|
- 2026-09-24 — Created; prod-verified on NYCHH TO 11. (jcardinal)
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-29
|
|
10
10
|
owners: [jcardinal]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Clickup/QaReview.php
|
|
@@ -65,6 +65,20 @@ the QA/QC branch — see [QA/QC branch automation](./qa-qc-branch-automation.md)
|
|
|
65
65
|
**enters** "Development Finished". **Moved off the legacy leaving-trigger** — the old code triggered
|
|
66
66
|
when a task *left* a status; the QA/QC entry must be on entering the status.
|
|
67
67
|
|
|
68
|
+
### Review-field reset on status ENTRY (`_Worker_Clickup::resetReviewFieldOnStatusEntry`)
|
|
69
|
+
|
|
70
|
+
- Runs in the `taskStatusUpdated` loop before the status switch. Map `REVIEW_FIELD_ID_BY_ENTERED_STATUS`:
|
|
71
|
+
`ui review`→UI Review, `front-end review`→FE Review, `back-end review`→BE Review, `qa approved`→QC Defects;
|
|
72
|
+
sets the field to **Needs Review** so a stale value from a prior pass (Rework) does not carry over.
|
|
73
|
+
- Skips: out of `isTaskInQaQcScope`; task's current status != entered status (late/redelivered webhook);
|
|
74
|
+
already Needs Review. Feature subtasks are reset too.
|
|
75
|
+
- UI/FE/BE reset **only when blank or Denied** — an early **Approved/N/A is kept** (e.g. BE Approved by
|
|
76
|
+
`BeReview::FromPr`, which fires once; wiping it strands the task). QC Defects always resets.
|
|
77
|
+
- Kept early value → `replayEarlyReview()` dispatches **both** `Clickup/QaReview/Process` and
|
|
78
|
+
`Clickup/FeatureReview/Process` with a synthetic history item `{field:'custom_field', custom_field:{id}}`
|
|
79
|
+
(matches `QaReview::changedCustomFieldIds`), each in its own try/catch; safe because each skips the
|
|
80
|
+
other's task kind. Write failures are logged + Sentry, never thrown.
|
|
81
|
+
|
|
68
82
|
### QA review chain (`_Worker_Clickup_QaReview`)
|
|
69
83
|
|
|
70
84
|
- Three ordered review fields: **UI → FE (front-end) → BE (back-end)**.
|
|
@@ -151,6 +165,10 @@ when a task *left* a status; the QA/QC entry must be on entering the status.
|
|
|
151
165
|
- **The automation user needs edit rights on every field it writes** — a missing field permission
|
|
152
166
|
returns HTTP 401 `ACCESS_606`. See [credential rotation](../workflows/clickup-credential-rotation.md).
|
|
153
167
|
|
|
168
|
+
- **`FeatureReview::Process` returns `locked` with no retry.** A one-shot replay that hits the lock
|
|
169
|
+
leaves the feature waiting until the next review-field change. Workaround: re-select the field value
|
|
170
|
+
(fix deferred).
|
|
171
|
+
|
|
154
172
|
### Known security gaps (discovered, not yet fixed — separate tickets)
|
|
155
173
|
|
|
156
174
|
- **ClickUp and GitHub webhooks are NOT signature-verified today.** Verification (incl. the GitHub HMAC
|
package/knowledge/INDEX.md
CHANGED
|
@@ -4,7 +4,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
4
4
|
|
|
5
5
|
## 1.0 framework
|
|
6
6
|
|
|
7
|
-
- **library** (Library) _(framework core)_ —
|
|
7
|
+
- **library** (Library) _(framework core)_ — 30 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
|
|
8
8
|
- **worker** (Worker) — 40 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,7 +11,7 @@ project: Worker
|
|
|
11
11
|
client: adyen
|
|
12
12
|
type: profile
|
|
13
13
|
status: draft
|
|
14
|
-
updated: 2026-09-
|
|
14
|
+
updated: 2026-09-30
|
|
15
15
|
owners: ["rgirish"]
|
|
16
16
|
files:
|
|
17
17
|
- worker/crons/toga2/netsuite/sync_togasupply_adyen.php
|
|
@@ -64,7 +64,7 @@ Adyen tests the tracking API on **prod**. `2026-09-22a - AdyenApiTestSeed.sql` s
|
|
|
64
64
|
- **Why this prevents duplicates:** the sync matches existing rows by `c_netsuiteInternalSalesOrderId` / `c_netsuiteInternalItemFulfillmentId` (`_underscore/Trait/Netsuite/SalesOrder.php`, `ItemFulfillment.php`). It will replace the dummy `SalesOrderItems` (itemId 6).
|
|
65
65
|
- **⚠ Leftovers:** dummy POs `PO-SR%` and vendor `V-TEST-001` still exist for the linked SOs.
|
|
66
66
|
|
|
67
|
-
**⚠ The Adyen NetSuite sync has never run.** `Parameters` cursors stuck at `2026-08-20 00:00:00|0`, the `Customers` row has no `c_netsuiteInternalCustomerId`, no `Locations`. Set these up before expecting sync data.
|
|
67
|
+
**⚠ The Adyen NetSuite sync has never run.** `Parameters` cursors stuck at `2026-08-20 00:00:00|0`, the `Customers` row has no `c_netsuiteInternalCustomerId`, no `Locations`. Set these up before expecting sync data. **Also (2026-09-30): the launcher `sync_togasupply_adyen.php` is missing the constants `SHIPPING_LOCATION_TYPE_UUID`, `WAREHOUSE_LOCATION_TYPE_UUID` and `NETSUITE_CUSTOM_FIELD_ID__END_USER_CUSTOMER`** (Elite's launcher has them) — add them before the first run.
|
|
68
68
|
|
|
69
69
|
**How to match an Adyen SR to its NetSuite SO — there is NO shared key.**
|
|
70
70
|
|
|
@@ -21,7 +21,7 @@ project: _Underscore
|
|
|
21
21
|
client: compass-usa
|
|
22
22
|
type: profile
|
|
23
23
|
status: active
|
|
24
|
-
updated: 2026-09-
|
|
24
|
+
updated: 2026-09-30
|
|
25
25
|
owners: [jcardinal, bala, tcox, apeterson, dfranks, akhokhani, ajean, rgirish]
|
|
26
26
|
files: []
|
|
27
27
|
related:
|
|
@@ -219,6 +219,7 @@ separate, related client (see its own profile).
|
|
|
219
219
|
the ⚠ rule that **deactivation is raw bulk SQL, so no model hook ever fires on it**.
|
|
220
220
|
|
|
221
221
|
## Notes
|
|
222
|
+
- **⚠ SALES_ORDERS froze 2026-09-29 on ACL gaps the closed-order delete hit** (transfer-orders 312/313, entitlement-sales-orders 257, sales-order-payments 254). Fixed by `dbchanges2/Client_Compass/2026-09-29a - TransferOrderAclExpressions.sql`, then mode reset to `864000-IDLE`. Details: [closed-order rule](../../1.0/apps/library/features/netsuite-closed-order-rule.md).
|
|
222
223
|
- **⚠ The 1.0 NetSuite sync links item fulfillments ONLY to `customerId=3` / `locationId=615286`
|
|
223
224
|
SalesOrders (2026-09-16).** This Compass-specific hardcoded scope filter lives in
|
|
224
225
|
`library/app/api/toga2.php` (`syncItemFulfillmentFromNetsuite` ~L5062-5064,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
| Doc | Framework | Summary |
|
|
4
4
|
|-----|-----------|---------|
|
|
5
|
-
| [Elite inventory quantities —
|
|
5
|
+
| [Elite inventory quantities — owned-warehouse STOCK lines + Transfer Orders; drop ship excluded](features/inventory-quantities-drop-ship.md) | 2.0 | How Elite's `_qtyOnHand` / `_qtyAvailable` are computed (`_Model_Elite_Item`) so they match NetSuite: only STOCK lines at warehouses Elite owns, plus Transfer O |
|
|
6
6
|
| [Elite — NetSuite → TOGa Supply inbound sync (TRUE-80499 onboarding)](features/netsuite-togasupply-sync.md) | 1.0 | Elite is the 18th client on the shared NetSuite → TOGa Supply importer ([engine](../../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md)). |
|
|
7
7
|
| [Elite SalesOrder → NetSuite Push (postPost/postPut interceptors → worker2)](features/salesorder-netsuite-push.md) | 2.0 | Elite's event-driven SalesOrder → NetSuite push (postPost/postPut interceptors → worker2); open for the push trigger, interceptor registration, or the 2026-08-2 |
|
|
8
8
|
| [Elite — Sales Order stage change posts a reply on the TOGa Desk (1.0) ticket](features/salesorder-status-togadesk-reply.md) | 2.0 | How an Elite sales-order stage change posts a reply back onto the originating TOGa Desk (1.0) ticket; open for the cross-DB join path or the dedup key. |
|
|
@@ -1,89 +1,49 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "Elite inventory quantities —
|
|
2
|
+
title: "Elite inventory quantities — owned-warehouse STOCK lines + Transfer Orders; drop ship excluded"
|
|
3
3
|
framework: "2.0"
|
|
4
4
|
repo: _underscore
|
|
5
5
|
project: _Underscore
|
|
6
6
|
client: elite
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-30
|
|
10
10
|
owners: [rgirish]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Elite/Item.php
|
|
13
13
|
- _underscore/Model/Client/Item.php
|
|
14
|
+
- worker/crons/toga2/netsuite/sync_togasupply_elite.php
|
|
14
15
|
related:
|
|
15
16
|
- ../profile.md
|
|
16
17
|
- ./netsuite-togasupply-sync.md
|
|
17
18
|
- ../../../2.0/apps/_underscore/features/calculated-sql-fields.md
|
|
18
19
|
- ../../nychh/features/transfer-order-inventory-quantities.md
|
|
19
|
-
- ../../../1.0/apps/
|
|
20
|
+
- ../../../1.0/apps/library/features/netsuite-suiteql-api-reference.md
|
|
20
21
|
---
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Elite's Inventory page showed **negative Qty On Hand / Qty Available** (worst:
|
|
25
|
-
`SVC-CI-RETAINER-RS` at **-235**). Cause: **drop-ship lines are fulfilled but never received into
|
|
26
|
-
stock**, so the base `received - fulfilled` arithmetic went negative.
|
|
23
|
+
How Elite's `_qtyOnHand` / `_qtyAvailable` are computed (`_Model_Elite_Item`) so they match NetSuite: only STOCK lines at warehouses Elite owns, plus Transfer Order lines.
|
|
27
24
|
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
## Summary
|
|
26
|
+
- **On hand** = received − shipped (STOCK SO lines at an owned location + Transfer Order lines with an owned origin) + adjusted.
|
|
27
|
+
- **Available** = on hand − committed on STOCK SO lines at an owned location − committed on Transfer Order lines with an owned origin (`qtyCommittedTransferOrders`, new 2026-09-29).
|
|
28
|
+
- **Owned** = `Locations.allowReceiving = 1`. Today only Elite Location 1 = NetSuite 196 "Elite" (set by `Client_Elite/2026-09-28e`).
|
|
29
|
+
- Why Transfer Orders: Elite's $0 hold-invoice SOs import as TransferOrders, so their shipped and committed qty sit on `TransferOrderItems` and were ignored before.
|
|
30
|
+
- Verified on prod 2026-09-29 against NetSuite `aggregateitemlocation` (not `inventorybalance` — see [SuiteQL gotcha](../../../1.0/apps/library/features/netsuite-suiteql-api-reference.md)).
|
|
30
31
|
|
|
31
32
|
## How it works
|
|
33
|
+
- Private helpers on `_Model_Elite_Item`, used only by the two composites: `qtyFulfilledStockOnly`, `qtyFulfilledTransferOrders`, `qtyCommittedStockOnly`, `qtyCommittedTransferOrders`. Shipped = fulfillment status slug `shipped`.
|
|
34
|
+
- SO lines need `SalesOrderItems.locationId` at an owned location. The Elite sync stamps it from the NetSuite line location (`IS_ENABLED_NETSUITE_LINE_LOCATION`, see [Elite sync](./netsuite-togasupply-sync.md)). Lines shipped from NS 5 "Main" have NULL `locationId` and drop out.
|
|
35
|
+
- Leaf fields `_qtyFulfilled`, `_qtyCommitted`, `_qtyInvoiced`, `_qtyBackordered`, `_qtyOnOrder` stay inherited and count all lines (drop ship included).
|
|
36
|
+
- Base `_Model_Client_Item` composites use `static::` (not `self::`) so this override takes effect — framework-wide, see [calculated fields](../../../2.0/apps/_underscore/features/calculated-sql-fields.md).
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|---|---|
|
|
37
|
-
| `qtyFulfilledStockOnly()` | `_qtyOnHand` |
|
|
38
|
-
| `qtyCommittedStockOnly()` | `_qtyAvailable` |
|
|
39
|
-
|
|
40
|
-
`_qtyFulfilled` and `_qtyCommitted` themselves stay **inherited and unfiltered** — the filtering
|
|
41
|
-
lives in the composites, not in the leaf fields, so anything reading the leaf number still sees the
|
|
42
|
-
true shipped/committed total.
|
|
43
|
-
|
|
44
|
-
**Quantities that count ALL lines (drop ship included):** `_qtyOrdered`, `_qtyFulfilled`,
|
|
45
|
-
`_qtyInvoiced`, `_qtyBackordered`, `_qtyOnOrder`.
|
|
46
|
-
|
|
47
|
-
### Two deliberate decisions — do not "improve" either
|
|
48
|
-
|
|
49
|
-
- **No `GREATEST(..., 0)` floor.** Negatives must stay **visible** so the underlying data problem
|
|
50
|
-
can be traced. Flooring hides a real defect behind a clean-looking zero. This was decided
|
|
51
|
-
explicitly, after one iteration.
|
|
52
|
-
- **A NULL `fulfillmentType` is NOT treated as `STOCK`.** Only a line positively marked stock counts
|
|
53
|
-
toward on-hand. (NULL `fulfillmentType` has its own causes — see the
|
|
54
|
-
[importer doc](../../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md).)
|
|
55
|
-
|
|
56
|
-
### The base model had to change too (`self::` → `static::`)
|
|
57
|
-
|
|
58
|
-
`_Model_Client_Item`'s four composites (`_qtyOnHand`, `_qtyAvailable`, `_qtyBackordered`,
|
|
59
|
-
`_qtyOnOrder`) called their leaf methods with **`self::`**, which silently ignores a child override.
|
|
60
|
-
They were switched to **`static::`** in the same session. That is a **framework-level** change
|
|
61
|
-
affecting **all 25 client `Item` models** — full detail and the NYCHH watch item on
|
|
62
|
-
[FIELD_SQL calculated fields](../../../2.0/apps/_underscore/features/calculated-sql-fields.md).
|
|
63
|
-
|
|
64
|
-
## Verification
|
|
65
|
-
|
|
66
|
-
Run against prod data after the change: **0 negatives on all 79 non-service items.** The **7**
|
|
67
|
-
remaining negatives are all `SVC-` service items (received 0, fulfilled > 0), which the developer
|
|
68
|
-
explicitly said to ignore.
|
|
38
|
+
### Decisions — do not "improve"
|
|
39
|
+
- **No `GREATEST(..., 0)` floor.** Negatives stay visible: they mean a missing receipt or an unsynced `fulfillmentType`.
|
|
40
|
+
- **NULL `fulfillmentType` is NOT stock.** Only `'STOCK'` counts.
|
|
69
41
|
|
|
70
42
|
## Gotchas / known issues
|
|
71
|
-
|
|
72
|
-
- **
|
|
73
|
-
|
|
74
|
-
numbers. Undecided and not implemented. Facts established while investigating:
|
|
75
|
-
- **`_isServiceClass` does not exist** anywhere in the code (grepped all of `Model/` and
|
|
76
|
-
`Trait/`) — the migration comment referencing it is **stale**.
|
|
77
|
-
- The table is **`ItemClasses`**, not `ItemClassifications`, and it is a **tree**
|
|
78
|
-
(`parentItemClassId`).
|
|
79
|
-
- Service roots are **Lifecycle Services (1)**, **Advisory and Modernization (22)**, **True
|
|
80
|
-
Partner Services (40)** — but **6 non-service items** sit under the latter two, so matching on
|
|
81
|
-
root alone is too broad.
|
|
43
|
+
- **Inventory page filter** is data, in `Client_Elite/2026-09-28b`: hides `SVC-`/`SVT-`/`LIC-`/`CON-` parts, AppleCare, service item classes and drop-ship-only items; shows only rows with nonzero on hand or available.
|
|
44
|
+
- **Known gap: Docking 1 is below NetSuite** — RMA return receipts (created from a Return Authorization) are not imported by the sync, so returned stock is missing. Open.
|
|
45
|
+
- Owned-warehouse scope is Elite-only today; other clients have fake "bay" warehouses (see [Elite sync](./netsuite-togasupply-sync.md)).
|
|
82
46
|
|
|
83
47
|
## Change history
|
|
84
|
-
- 2026-09-
|
|
85
|
-
|
|
86
|
-
lines; no `GREATEST` floor (negatives must stay visible) and NULL `fulfillmentType` is not treated
|
|
87
|
-
as stock. Verified 0 negatives on all 79 non-service items in prod. Also recorded the paired base
|
|
88
|
-
change from `self::` to `static::` on `_Model_Client_Item`'s composites, and the open
|
|
89
|
-
`partNumber:excludes:SVC-` TableView filter that misses 18 service items. (rgirish)
|
|
48
|
+
- 2026-09-30 — New formula: owned-warehouse (`allowReceiving = 1`) STOCK SO lines + Transfer Order shipped/committed (`qtyCommittedTransferOrders`); verified vs NetSuite `aggregateitemlocation`. Inventory filter 2026-09-28b; RMA receipt gap recorded. (rgirish)
|
|
49
|
+
- 2026-09-17 — Drop ship excluded from `_qtyOnHand` / `_qtyAvailable` only; no floor; NULL `fulfillmentType` not stock; base composites `self::` → `static::`. (rgirish)
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: elite
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-30
|
|
10
10
|
owners: ["snaredla", "jcardinal", "rgirish"]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/netsuite/sync_togasupply_elite.php
|
|
@@ -409,8 +409,20 @@ configuration problem, and it only appears **after a section catches up** — wh
|
|
|
409
409
|
Elite's PO backfill just did. **NOT FIXED** (shared file, 22 clients). Full root cause on the
|
|
410
410
|
[engine doc](../../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md).
|
|
411
411
|
|
|
412
|
+
### Elite-only scope flags (2026-09-29) — Elite stock = its own warehouse only
|
|
413
|
+
|
|
414
|
+
Set in `sync_togasupply_elite.php`; they feed the [inventory formula](./inventory-quantities-drop-ship.md).
|
|
415
|
+
- `IS_ENABLED_ITEM_RECEIPT_OWNED_WAREHOUSE_SCOPE` — a receipt at an owned warehouse (`Locations.allowReceiving = 1`) is Elite stock even when the PO has no Elite customer.
|
|
416
|
+
- `IS_ENABLED_NETSUITE_LINE_LOCATION` — stamps `SalesOrderItems.locationId` from the NetSuite line location. Needs the API-role write grant on that field (`Client_Elite/2026-09-28e`).
|
|
417
|
+
- `IS_ENABLED_SKIP_TRANSFER_ORDERS_FROM_UNKNOWN_WAREHOUSE` — a TO shipped from a non-Elite NS location (e.g. 5 "Main") is skipped + logged instead of throwing.
|
|
418
|
+
- **Only Elite is safe for the owned-warehouse flag today.** Audit 2026-09-29: other clients have fake "bay" warehouses, and Trividia Locations 3/4 hold SBA customers 1171/22311 (open bug). Audit a client's `Locations` before turning it on.
|
|
419
|
+
- Owned today: Location 1 = NS 196 "Elite" (`allowReceiving = 1` via `2026-09-28e`, with its ACL).
|
|
420
|
+
|
|
412
421
|
## Gotchas / known issues
|
|
413
422
|
|
|
423
|
+
- **Data fixes 2026-09-28/29 (`dbchanges2/Client_Elite/`):** `2026-09-28b` inventory filter, `28d`, `28e` (ACL + `allowReceiving = 1` on NS 196), `2026-09-29a - EliteReceiptLinkAndMissingSalesOrder.sql` (relinks receipt 58882 line to PO line 1765; inserts missing SO 289992). 7 closed-order shipments backfilled with `process_netsuite_records.php` after the [closed-order rule](../../../1.0/apps/library/features/netsuite-closed-order-rule.md) change.
|
|
424
|
+
- **Open 2026-09-30:** Elite sync cron re-enabled locally in `worker/schedules/cron.worker.sync.json` — pending deploy. RMA return receipts are not imported (Docking 1 below NetSuite).
|
|
425
|
+
|
|
414
426
|
- **⚠ OPEN, unrelated: `GET /v2/notes/{uuid}/files` returns 405 `EV-7`** ("method not allowed /
|
|
415
427
|
route does not exist") repeatedly for Elite. Found while diagnosing the 2026-09-08 transfer-order
|
|
416
428
|
incident; it is **not** part of it. It does **not** abort the sync, and it was still occurring
|
|
@@ -450,6 +462,7 @@ Elite's PO backfill just did. **NOT FIXED** (shared file, 22 clients). Full root
|
|
|
450
462
|
rewinding `NETSUITE_LAST_SYNC_DATETIME_SALES_ORDERS`.
|
|
451
463
|
|
|
452
464
|
## Change history
|
|
465
|
+
- 2026-09-30 — Elite-only scope flags (owned-warehouse receipts, NS line location, skip TOs from unknown warehouse); NS 196 marked owned; receipt/SO data fixes; 7 closed-order shipments backfilled. (rgirish)
|
|
453
466
|
- 2026-09-24 — TO↔PO links now via the two-direction reconcile; some upstream rows were vendor POs; SO downstream line links were being deleted on every PO re-sync (55 of 97 SO-linked vendor POs had none) — fixed. (jcardinal)
|
|
454
467
|
- 2026-09-18 — **Stale-SalesOrder backfill is live: 218 → 187.** The retirement method needed
|
|
455
468
|
four more FK dependencies before it would run (fulfillment **header** `salesOrderId`, the
|
package/package.json
CHANGED