toga-ai 1.0.1197 → 1.0.1198

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.
@@ -22,7 +22,7 @@
22
22
  | [Diagnosing frozen 1.0 worker cron check-ins (Sentry "missed" flood)](workflows/diagnosing-frozen-cron-checkins.md) | Ordered path to diagnose frozen 1.0 worker cron timestamps + a Sentry `worker1` "missed" flood; open when check-ins stall and you suspect a wedged lock. |
23
23
  | [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 |
24
24
  | [Mapping NetSuite customer address-book entries to Toga Locations (process_netsuite_addressbook_mapping.php)](workflows/netsuite-addressbook-location-mapping.md) | Operator CLI that maps a client customer's NetSuite address-book entry ids to Toga Locations (`Locations_Addresses.c_netsuiteInternalAddressbookId`), so TO impo |
25
- | [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 |
25
+ | [Deduplicating NetSuite duplicate Items across client DBs (recursive MERGE)](workflows/netsuite-duplicate-items-dedup.md) | Per-client cleanup collapsing duplicate `Items` rows (same `partNumber`, or same NetSuite id after a rename) to one canonical via recursive MERGE; open before r |
26
26
  | [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 |
27
27
  | [Re-processing NetSuite orders, fulfillments, receipts and inventory adjustments (process_netsuite_*.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/inventory adj |
28
28
  | [Running a 1.0 worker cron locally without touching prod](workflows/running-a-worker-cron-locally.md) | How to run a real 1.0 worker cron end to end on a Windows laptop with every DB, NetSuite, email and Sentry call kept local; open before running any `worker/cron |
@@ -6,12 +6,15 @@ project: Worker
6
6
  client: shared
7
7
  type: workflow
8
8
  status: active
9
- updated: 2026-10-21
10
- owners: [jcardinal, rgirish]
9
+ updated: 2026-10-29
10
+ owners: [jcardinal, rgirish, bala]
11
11
  files:
12
12
  - library/app/api/toga2.php
13
13
  - worker/crons/toga2/netsuite/common_sync_togasupply.php
14
+ - worker1.5/crons/toga2/netsuite/common_sync_togasupply.php
15
+ - dbchanges2/Client_Elite/2026-10-29a - MergeRenamedSurfaceLaptopItems.sql
14
16
  related:
17
+ - ../../../clients/elite/features/netsuite-togasupply-sync.md
15
18
  - ../features/netsuite-togasupply-per-client-sync.md
16
19
  - ../../library/features/netsuite-suiteql-rest-shim.md
17
20
  - ../../../clients/compass-usa/workflows/catalog-fold-reversal.md
@@ -19,7 +22,7 @@ related:
19
22
  - ../../../2.0/apps/dbchanges2/features/netsuite-id-unique-keys.md
20
23
  ---
21
24
 
22
- Per-client cleanup collapsing duplicate `Items` rows (same `partNumber`) to one canonical via recursive MERGE; open before running it — and read the catalog-blind warning first, it broke Compass + Quad.
25
+ Per-client cleanup collapsing duplicate `Items` rows (same `partNumber`, or same NetSuite id after a rename) to one canonical via recursive MERGE; open before running it — and read the catalog-blind warning first, it broke Compass + Quad.
23
26
 
24
27
  ## Summary
25
28
 
@@ -48,11 +51,23 @@ One-time, per-client data cleanup that collapses **duplicate `Items` rows sharin
48
51
 
49
52
  **Deliverables (live in the repo, not the KB):** `test/team/2.0 deployment/duplicate_items_merge_analysis.sql` (read-only per-client duplicate + collision report); the per-client merge migration (the recursive MERGE implementing steps 2–6).
50
53
 
54
+ ## Merging one renamed item (hand-written `Client_<Name>` file)
55
+
56
+ A NetSuite item rename makes a second `Items` row with the same `c_netsuiteInternalItemId` (cause in Gotchas). New units, SO / invoice / adjustment lines and vendor items land on the new item; history stays on the old one. Fix = merge new into old, then rename old. Worked example: `dbchanges2/Client_Elite/2026-10-29a - MergeRenamedSurfaceLaptopItems.sql` (TRUE-83395, Elite NetSuite 1390621/1390622, Items 285→63, 284→66).
57
+
58
+ 1. **Count every FK child of the duplicate items and units on prod right before writing the file.** The sync keeps adding rows (Elite `SalesOrderItems` went 8→9 within an hour).
59
+ 2. **Check unique keys for clashes before re-pointing:** `Units (itemId, serialNumber)`, `VendorItems (vendorId, itemId, vendorPartNumber)`, `ItemReceiptItemUnits (itemReceiptItemId, unitId)`. A serial received after the rename exists on both items, so merge that unit first (move its `*ItemUnits` rows to the old unit, copy over `locationId` / `c_netsuiteInternalInventoryAssignmentId`, delete the new unit).
60
+ 3. **Re-point** `Units`, `SalesOrderItems`, `InvoiceItems`, `InventoryAdjustmentItems`, `TransferOrderItems`, `VendorItems` new→old. Vendor items keep their PO lines and new vendor part numbers.
61
+ 4. **Delete the new item, THEN rename the old one.** A rename first fails on `Items` UNIQUE `(catalogId, manufacturerId, partNumber)`, because both rows share catalog + manufacturer.
62
+ 5. **Match every statement by `uuid` AND `c_netsuiteInternalItemId`**, never by NetSuite id alone (it is not unique per item, e.g. `X` and `X_` items share one). Rows missing in an env make the statement a no-op, so one file runs on prod and dev-sandbox.
63
+ 6. **One transaction.** Every FK to `Items` and `Units` in client DBs is `RESTRICT`, so a missed link fails the delete and rolls back instead of losing data.
64
+ 7. **Run with the `mysql` command-line client (stops on error), right after a client sync run ends.** The sync is `*/5` and loads Items/Units at run start; a run in flight fails once on the deleted rows.
65
+
51
66
  ## Gotchas
52
67
 
53
68
  - **The sync item lookup is Agilant-catalog-only.** Any non-Agilant item is **invisible** to `common_sync_togasupply.php` and will be **re-created** — the whole mechanism behind the duplicates. Deploy the catalog-stamp fix before (and independently of) this cleanup.
54
- - **`Items` has NO unique constraint on `partNumber`.** Duplicates persist silently; nothing at the DB level prevents them, which is why this is a MERGE-and-clean rather than a rely-on-the-constraint fix.
55
- - **New duplicates after this cleanup are keyed by NetSuite item id, not part number (2026-10).** The item sync looks up by part number, so a renamed or `_`/`-`-suffixed part number gets a new item for the same `c_netsuiteInternalItemId`. The TRUE-82291 dedupe merges by NetSuite id and a UNIQUE key on it waits for the sync fix — see [NetSuite id UNIQUE keys](../../../2.0/apps/dbchanges2/features/netsuite-id-unique-keys.md).
69
+ - **`Items` UNIQUE key is `(catalogId, manufacturerId, partNumber)`, not `partNumber` alone.** Same part number in another catalog, with another manufacturer, or with NULL catalog passes silently, which is why this is a MERGE-and-clean. The key also blocks renaming an item onto a part number a duplicate still holds.
70
+ - **New duplicates after this cleanup come from NetSuite renames (2026-10).** The 1.0 sync (`getCreateItem` in `toga2.php`, plus the inline item creates in the inventory-adjustment path and the item-fulfillment no-sales-order path) finds an existing item ONLY by normalized `partNumber` (uppercase, trailing `_` stripped) from the Agilant-catalog lookup built at run start in `common_sync_togasupply.php`. `c_netsuiteInternalItemId` is only stamped on create or filled when empty, never used to match. So any rename (or `-` suffix) makes a new item for the same NetSuite id. Units match by `(item, serialNumber)`, so a unit received after the rename duplicates too. Fix one pair by hand: [Merging one renamed item](#merging-one-renamed-item-hand-written-client_name-file). The TRUE-82291 dedupe merges by NetSuite id and a UNIQUE key on it waits for the sync fix — see [NetSuite id UNIQUE keys](../../../2.0/apps/dbchanges2/features/netsuite-id-unique-keys.md).
56
71
  - **Client DBs are schema-similar but NOT identical.** Some lack optional tables (e.g. `EntitlementServiceItems`). Run the merge **continue-on-error** so a missing-table MySQL **1146** is a **benign skip**, not a failed migration.
57
72
  - **A plain `UPDATE IGNORE` + delete-leftover is insufficient.** Dupe-vs-dupe unique-key collisions leave orphaned child FKs; only the survivor-per-key re-pointing (step 5) is correct.
58
73
  - **⚠ The merge is CATALOG-BLIND — it does NOT handle multi-catalog clients.** It groups on `UPPER(partNumber)` only, with an Agilant-preferred canonical rule. For a client that keeps the same part number in two intentional catalogs (Compass: store vs Agilant), it folds legitimate per-catalog twins together and **destroys** the store items. Confirm a client is single-catalog before running; otherwise make the dedup catalog-scoped. The Compass fold deleted 199 store items and broke MITS — see the [catalog-fold reversal](../../../clients/compass-usa/workflows/catalog-fold-reversal.md).
@@ -60,6 +75,7 @@ One-time, per-client data cleanup that collapses **duplicate `Items` rows sharin
60
75
  - **Blast radius of the 2026-08-31 `duplicate_items_merge_ALL_CLIENTS.sql` (assessment complete 2026-09-01):** it ran on 17 clients (Masonite, Ncci, Erau, Shrss, Wmchealth, Trividiahealth, Prudential, Miamidade, Browardsheriff, Nychh, Canon, Quad, Compass, Elite, Growrk, Spglobal, Endeavorhealth). **Only 2 were damaged — Compass and Quad, both two-catalog TOGa Commerce clients.** The other 15 needed no reversal: 13 single-catalog "Agilant"-only clients were a legitimate within-catalog dedup, NYCHH mixed Etilize + NetSuite in one catalog (no fold possible), and Prudential was cleared (single populated catalog; NULL `assetType` is its normal state). Triage per client with `Catalogs` LEFT JOIN `Items` (empty catalogs still show) + the pipeline signal (`c_etilizeProductId` = store vs `c_netsuiteInternalItemId` = Agilant); a client is damaged only if it has a two-catalog store/procurement design AND catalog-1 was folded into catalog-2 AND storefront/transmit depends on catalog-1. See the Compass [catalog-fold reversal](../../../clients/compass-usa/workflows/catalog-fold-reversal.md) and [Quad catalog-fold reversal](../../../clients/quad/workflows/catalog-fold-reversal.md).
61
76
 
62
77
  ## Change history
78
+ - 2026-10-29 — Rename cause pinned down (part-number-only match, NetSuite id never matched); single renamed-item merge recipe from Elite TRUE-83395; corrected the `Items` unique-key gotcha. (bala)
63
79
  - 2026-10-21 — Linked the TRUE-82291 NetSuite-id dedupe + UNIQUE keys; new duplicates come from the part-number lookup. (rgirish)
64
80
  - 2026-09-16 — Catalog-blind correction and blast-radius assessment (Compass + Quad damaged). (jcardinal)
65
81
 
@@ -67,6 +83,7 @@ One-time, per-client data cleanup that collapses **duplicate `Items` rows sharin
67
83
 
68
84
  - [NetSuite id UNIQUE keys](../../../2.0/apps/dbchanges2/features/netsuite-id-unique-keys.md)
69
85
  - [NetSuite → TOGa Supply Per-Client Sync](../features/netsuite-togasupply-per-client-sync.md)
86
+ - [Elite NetSuite sync](../../../clients/elite/features/netsuite-togasupply-sync.md) (renamed-item merge example)
70
87
  - [NetSuite SuiteQL REST shim](../../library/features/netsuite-suiteql-rest-shim.md)
71
88
  - [Compass catalog-fold reversal](../../../clients/compass-usa/workflows/catalog-fold-reversal.md)
72
89
  - [Compass item catalogs & duplicate items](../../../clients/compass-usa/features/item-catalogs-and-duplicate-items.md)
@@ -6,7 +6,7 @@ project: Worker
6
6
  client: elite
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-10-22
9
+ updated: 2026-10-29
10
10
  owners: ["snaredla", "jcardinal", "rgirish", "bala"]
11
11
  files:
12
12
  - worker/crons/toga2/netsuite/sync_togasupply_elite.php
@@ -23,6 +23,7 @@ files:
23
23
  - dbchanges2/Client_Elite/_modules.txt
24
24
  - dbchanges2/Client_Elite/2026-09-04a - EliteItemAssetTypeApiWritePermission.sql
25
25
  - dbchanges2/_modules/netsuite/2026-09-17a - StaleSalesOrderRetirementAcl.sql
26
+ - dbchanges2/Client_Elite/2026-10-29a - MergeRenamedSurfaceLaptopItems.sql
26
27
  - library/app/api/toga2.php
27
28
  - library/app/api/netsuite/rest.php
28
29
  related:
@@ -31,6 +32,7 @@ related:
31
32
  - ../../../1.0/apps/library/features/netsuite-item-class-sync.md
32
33
  - ../../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md
33
34
  - ../../../1.0/apps/worker/workflows/onboarding-client-to-netsuite-togasupply-sync.md
35
+ - ../../../1.0/apps/worker/workflows/netsuite-duplicate-items-dedup.md
34
36
  - ./salesorder-netsuite-push.md
35
37
  - ./supply2-scope.md
36
38
  - ../../growrk/features/units-netsuite-custom-fields.md
@@ -421,6 +423,7 @@ Set in `sync_togasupply_elite.php`; they feed the [inventory formula](./inventor
421
423
 
422
424
  ## Gotchas / known issues
423
425
 
426
+ - **A NetSuite item rename makes a duplicate Elite item.** The sync matches items by part number only. 2026-10-26/27: NetSuite 1390621 / 1390622 (Surface Laptop 5, renamed "... - STANDARD" / "... - DEVELOPER") became new Items 285 / 284 beside 63 / 66. Merged back and renamed by `Client_Elite/2026-10-29a` (TRUE-83395). Recipe for the next one: [Merging one renamed item](../../../1.0/apps/worker/workflows/netsuite-duplicate-items-dedup.md#merging-one-renamed-item-hand-written-client_name-file).
424
427
  - **Full NS↔toga recheck 2026-10-01: SO/IF/PO/Invoice/IA reconcile.** Expected differences (by design, not bugs):
425
428
  - SOs closed in NS (all item lines closed, only Ship/Tax open, no related records) are deleted by the [closed-order rule](../../../1.0/apps/library/features/netsuite-closed-order-rule.md) (10 today).
426
429
  - An invoice billed against a $0 hold SO (stored as a TransferOrder) is skipped — e.g. 6215693.
@@ -471,6 +474,7 @@ Set in `sync_togasupply_elite.php`; they feed the [inventory formula](./inventor
471
474
  rewinding `NETSUITE_LAST_SYNC_DATETIME_SALES_ORDERS`.
472
475
 
473
476
  ## Change history
477
+ - 2026-10-29 — Duplicate Items 284/285 from a NetSuite rename merged into 63/66 and renamed (`Client_Elite/2026-10-29a`). TRUE-83395 (bala)
474
478
  - 2026-10-22 — Unit serial details sync + new-unit condition "Used" turned on for Elite. TRUE-82991 (bala)
475
479
  - 2026-10-01 — Full NS↔toga recheck: list of by-design differences; hand-added users linked to existing Contacts; item descriptions now from NS item record ([doc](../../../1.0/apps/library/features/netsuite-item-description-sync.md)), items 228/80/89/90 backfilled by hand. (rgirish)
476
480
  - 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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.1197",
3
+ "version": "1.0.1198",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",