toga-ai 1.0.932 → 1.0.933

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.
@@ -21,6 +21,7 @@
21
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 |
22
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 |
23
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`. |
24
+ | [Items.description from the NetSuite item record (Sales Description), refreshed every sync](features/netsuite-item-description-sync.md) | How the NetSuite sync sets and refreshes `Items.description` (all NetSuite clients); open when a toga item description is blank, stale, or differs from NetSuite |
24
25
  | [isFulfillable from NetSuite during Item Sync (Phase 1)](features/netsuite-item-isfulfillable-sync.md) | 1.0 Phase-1 stamping of the NetSuite `isfulfillable` flag onto the Agilant source item during sync; open before changing fulfillability sync or a client overrid |
25
26
  | [NetSuite SuiteQL/REST API Reference](features/netsuite-suiteql-api-reference.md) | Working reference for the Agilant NetSuite REST/SuiteQL integration (`App_Api_Netsuite_Rest`): auth, SuiteQL mechanics, table schemas, type/status codes, perfor |
26
27
  | [NetSuite SuiteQL/REST Shim — Field Semantics](features/netsuite-suiteql-rest-shim.md) | Non-obvious NetSuite SuiteQL/REST field semantics behind `App_Api_Netsuite_Rest` shims (signs, `tl.id`==line, `iscogs`, ShipItem cost, `createdFrom`, shim-shape |
@@ -0,0 +1,47 @@
1
+ ---
2
+ title: Items.description from the NetSuite item record (Sales Description), refreshed every sync
3
+ framework: "1.0"
4
+ repo: library
5
+ project: Library
6
+ client: shared
7
+ type: feature
8
+ status: active
9
+ updated: 2026-10-01
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
+ related:
16
+ - netsuite-item-class-sync.md
17
+ - netsuite-item-assettype-sync.md
18
+ - netsuite-item-isfulfillable-sync.md
19
+ - toga2-api-client-and-bridge.md
20
+ - ../../worker/features/netsuite-togasupply-per-client-sync.md
21
+ ---
22
+
23
+ How the NetSuite sync sets and refreshes `Items.description` (all NetSuite clients); open when a toga item description is blank, stale, or differs from NetSuite.
24
+
25
+ ## Summary
26
+ - Source = NetSuite **item record** Sales Description: SuiteQL `item.description`, fallback `item.purchasedescription`. Not the transaction line memo.
27
+ - Refreshed every time the sync touches an item or unit, for **all** NetSuite clients (no opt-in flag).
28
+ - Before 2026-10-01: set once at item create from the line memo, never updated; blank when the item was first seen on an item fulfillment or a memo-less line.
29
+
30
+ ## How it works
31
+ - `App_Api_Netsuite_Rest::fetchItemFlagsByIds()` selects `description, purchasedescription` and sets `$shim->description`.
32
+ - `App_Api_Toga2::getNetsuiteItemDescription()` — item-record text, else line text, cut to `MAX_LENGTH_ITEM_DESCRIPTION` (255, the column size).
33
+ - `App_Api_Toga2::refreshItemDescriptionFromNetsuite()` — PUT `/items/{uuid}` `description` only when NetSuite's value is non-empty and different; updates the caller's cached item so repeat lines do not PUT again.
34
+ - Call sites in `toga2.php`: `getCreateItem` (existing + create), `syncItemFulfillmentFromNetsuite` (line not on the SO), `syncInventoryAdjustmentFromNetsuite`, `syncItemReceiptFromNetsuite` (serialized + non-serialized), `syncTransferOrderFromNetsuite` (one batched flags lookup).
35
+ - `common_sync_togasupply.php` shared `GET /items` lookup requests `description`.
36
+
37
+ ### Rules
38
+ - Never blanks a stored description.
39
+ - Throws if the caller's item GET did not request `description` (fail loud, same pattern as itemClass) — otherwise every item would be re-written every run.
40
+ - Needs the API-role write grant on `Items.description` (RecordFields 109, roleId 3). Checked present in Elite, Adyen, Prudential, Growrk, Nychh.
41
+
42
+ ## Gotchas
43
+ - **⚠ `library` and `worker` MUST deploy together.** Library alone (2026-10-01 prod) made every client's SO sync throw `getCreateItem: the caller's item GET must request 'description'`. Tell-tale: trace `common_sync_togasupply.php:825` = old worker, `:827` = new — PHP reports the LAST argument line of a multi-line call.
44
+ - Items whose only NetSuite records will never resync keep the old text. Fix by hand (done for Elite items 228, 80, 89, 90). Elite item 125 is blank in NetSuite too — nothing to copy.
45
+
46
+ ## Change history
47
+ - 2026-10-01 — Built: description from NetSuite item record, refreshed on every touch, all clients; deploy-together incident; Elite one-time backfill. (rgirish)
@@ -34,6 +34,7 @@ related:
34
34
  - ../../../2.0/apps/_underscore/features/item-fulfillment-stage-lifecycle-and-order-status.md
35
35
  - ../../../clients/elite/features/netsuite-togasupply-sync.md
36
36
  - ../../library/features/netsuite-item-class-sync.md
37
+ - ../../library/features/netsuite-item-description-sync.md
37
38
  - ../../../clients/compass-usa/features/sales-order-line-renumbering.md
38
39
  - ../../../2.0/apps/_underscore/features/acl-permission-chain.md
39
40
  - ../../../2.0/apps/_underscore/features/sales-order-purchase-order-bridge-direction.md
@@ -1973,6 +1974,18 @@ library (or vice versa) crashes GroWrk and Adyen on their next sync run.
1973
1974
  invoice against a duplicated SO it will false-throw the same way, and **their duplicates have a DIFFERENT
1974
1975
  cause than Compass's Office-Depot dual-source.** Also a data-quality question: why do six non-Compass
1975
1976
  clients have duplicate SalesOrders rows at all.
1977
+ - **⚠ "refusing to import invoice … billed sales order not imported" can mean the SO is OLDER than the
1978
+ SO cursor (Adyen, 2026-10-01, issue 910).** Invoices from 2026-07-02 on billed 10 SOs last-modified
1979
+ 2025-11-04 → 2026-06-15, but `NETSUITE_LAST_SYNC_CURSOR_SALES_ORDERS` was 2026-07-31, so the SO sync
1980
+ never pulls them. Fix: wait for SO mode `IDLE`, then set the SO cursor (Eastern) before the oldest
1981
+ billed SO's lastmod (here `2025-11-01 00:00:00|0`, ~117 SOs re-pull). To see which invoice/SO it fails
1982
+ on now, read `Logs_<Tenant>.Api` `queryString` — `Logs.Issue` keeps the first message only.
1983
+ - **⚠ The receipt sync never rebuilds lines on an EXISTING receipt** — `itemReceiptItems` is sent on POST
1984
+ only. A receipt whose PO lines were deleted and re-created keeps `ItemReceiptItems.purchaseOrderItemId
1985
+ NULL` (and can gain duplicate unlinked rows), which skews On Hand. Detect:
1986
+ `ItemReceiptItems WHERE purchaseOrderItemId IS NULL AND transferOrderItemId IS NULL`. Fix: delete
1987
+ those receipts and rewind the IR cursor so they re-import (Elite 2026-10-01,
1988
+ `Client_Elite/2026-10-01b`).
1976
1989
  - **⚠ ROOT CAUSE of the duplicate `Items`: the inventory-adjustment item-create OMITTED the catalog
1977
1990
  (fixed 2026-08-31).** `getCreateItem` (`toga2.php:6233`) and the fulfillment create (~4951) stamp
1978
1991
  `'catalog' => ['name' => 'Agilant']`; `syncInventoryAdjustmentFromNetsuite`'s item-create did **not** →
@@ -2065,6 +2078,7 @@ library (or vice versa) crashes GroWrk and Adyen on their next sync run.
2065
2078
  [NetSuite Sync Alert Monitor](../../library/features/netsuite-sync-alert-monitor.md).
2066
2079
 
2067
2080
  ## Change history
2081
+ - 2026-10-01 — `Items.description` now from the NetSuite item record, refreshed every touch ([doc](../../library/features/netsuite-item-description-sync.md); library + worker deploy together). Gotchas: invoice billing an SO older than the SO cursor (Adyen); receipt lines never rebuilt on an existing receipt (Elite). (rgirish)
2068
2082
  - 2026-10-01 — NYCHH diagnosis traps recorded: the wrapper is gated to **once a day** by
2069
2083
  `cronInitialization(86400)` despite its `*/5` schedule entry, and the live cursors/modes live in
2070
2084
  `Client_Nychh.Parameters` — the `NETSUITE_HH_*` keys in `Core` are stale (May 2026) leftovers.
@@ -6,8 +6,8 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-23
10
- owners: [jcardinal, mhammontree]
9
+ updated: 2026-10-01
10
+ owners: [jcardinal, mhammontree, rgirish]
11
11
  files:
12
12
  - _underscore/Model/Client/User.php
13
13
  - worker2/Worker/Sync/ActiveDirectory.php
@@ -64,8 +64,10 @@ Northwell ServiceNow users arrive via api2 eBonding (`_Model_Northwell_Ticket` i
64
64
 
65
65
  - **⚠ A reconstructed `new _Model_Client_User($id)` does NOT carry `contactId`.** In `ActiveDirectory.php` the AD sync reads the existing `contactId` from the **bulk SELECT** and only creates a contact when it is truly missing. A naive `empty($user->contactId)` guard against a freshly reconstructed model reads null even when the row has a contact — and would **mint a duplicate contact every sync**. Trust the SELECT'd value, not the rehydrated model property.
66
66
  - **⚠ Set `primaryContactEmailAddressId` with a second server-side load+save, never a nested payload.** The nested singular-FK write re-parents (CREATE) or echoes null (UPDATE).
67
+ - **Adding a user by hand (dbchanges2 SQL): look up `ContactEmailAddresses` for an existing Contact first.** Sync-created Contacts often exist already (Elite: Contacts 29, 886, 1044 from the Elite sync, `Client_Elite/2026-10-01a`); link to them instead of minting a duplicate.
67
68
  - **Interceptor no-ops on a user with no email** — a no-email straggler gets no Contact from this path (the backfill migrations handle those name-only, per client). It is why the fan-out `NOT NULL` skips a client that still has a no-email residual (see [rerunnable-additive-inserts](../../dbchanges2/features/rerunnable-additive-inserts.md)).
68
69
 
69
70
  ## Change history
71
+ - 2026-10-01 — Hand-added users: link an existing Contact found via `ContactEmailAddresses` (Elite). (rgirish)
70
72
  - 2026-09-23 — Corrected the origin map: the Compass PEOPLE importer did **not** safely set `contactId` — it inserted `Users` first and linked afterwards, so the NOT NULL rollout broke it in prod (TRUE-82230). Added the write-order and `LAST_INSERT_ID()` gotchas for every raw-SQL creation path. (mhammontree)
71
73
  - 2026-09-21 — Initial. Decided `Client_*.Users.contactId` goes NOT NULL platform-wide (CTO Option B); built the shared `_Model_Client_User::postPost` interceptor (Core row: recordId 2, clientId+apiId NULL) to create+link a Contact for api2-created users; fixed the worker2 AD-sync and `cacheClickupUser` True paths; recorded the origin map and the reconstructed-model `contactId` gotcha. (jcardinal)
@@ -6,8 +6,8 @@ project: API
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-09-16
10
- owners: [apeterson, ajean]
9
+ updated: 2026-10-01
10
+ owners: [apeterson, ajean, rgirish]
11
11
  files:
12
12
  - api2/Component/Api/V2/V2.php
13
13
  - _underscore/String.php
@@ -26,16 +26,17 @@ How `/v2/auth/login` swaps to a cross-client (home) user's home client mid-reque
26
26
 
27
27
  ## How it works
28
28
 
29
- `_Component_Api_V2::execute()` (~L701-744):
30
- 1. The user is loaded **by email** from the current client's DB (~L705) — the client is resolved from `ClientEmailDomains`, **not** from any Bearer token.
29
+ `_Component_Api_V2::execute()` (~L762-812, re-checked 2026-10-01 — grep `homeClientUserId`, line numbers drift):
30
+ 1. The user is loaded **by email** from the current client's DB (~L762) — the client is resolved from `ClientEmailDomains`, **not** from any Bearer token.
31
31
  2. The submitted password is checked with `_String::passwordVerify()` → `password_verify()` (bcrypt) against the local user row (`_underscore/String.php` L563-576).
32
- 3. **If the password does NOT verify AND the loaded user row has a non-null `clientId` that differs from the current client**, the engine treats it as a **cross-client user**: it calls `registerClientDatabases($user->clientId)` and re-loads the *home* user via `homeClientUserId` (~L716-731). Authentication then completes against the home client.
32
+ 3. **If the password does NOT verify AND the loaded user row has a non-null `clientId` that differs from the current client**, the engine treats it as a **cross-client user**: it calls `registerClientDatabases($user->clientId)` and re-loads the *home* user via `homeClientUserId` (~L787-812). Authentication then completes against the home client.
33
33
 
34
34
  This is the same `homeClientUserId` bridge that [cross-client data retrieval](./cross-client-data-retrieval.md) uses to fan a home user across tenant DBs — here it governs which client a login actually authenticates against.
35
35
 
36
36
  ## Gotchas
37
37
 
38
38
  - **A login `EO-1 "Unknown database 'client_<x>'"` is the cross-client branch failing to open the home client's DB.** Observed: logging into **Elite** locally with `devteam@togatech.com` throws `EO-1 Unknown database 'client_true'`. That account is an internal TOGA (client `true` / `Client_True`) user shared into Elite via `homeClientUserId`; because its Elite-local password does not verify, the cross-client path tries to open `Client_True`, which does not exist in local dev. It works on the dev sandbox only because `Client_True` exists there.
39
+ - `devteam@togatech.com` in `Client_Elite`: `clientId` 1 (True), `homeClientUserId` 2 = `Client_True` devteam@goagilant.com (re-checked 2026-10-01).
39
40
  - **Local-dev workaround (never commit):** set the **local** `Client_<Client>` user's own bcrypt password hash to a known value so login succeeds on the **direct** path and never enters the cross-client branch. Generate with `_String::passwordHash()` / `password_hash($pw, PASSWORD_BCRYPT)`. A per-machine local fixup, not a code or data change to commit.
40
41
  - **The api2 dev server talks to `localhost` as `root` — sandbox DB edits are invisible to it.** Local stack traces show `mysqli_connect(["localhost","root",...])`. Any ACL/grant/password change must be applied to the **localhost** client DB, not a sandbox connection in a DB GUI, or the local API will never see it. (In DbGate: "Run" autocommits; a query-tab "unsaved" label refers to the unsaved `.sql` *script file*, not pending uncommitted data.) This exact confusion — a grant present in sandbox while the API read localhost — cost real debugging time.
41
42
  - **⚠ The domain lookup is `Core.ClientEmailDomains` at `V2.php:611-621`, and it only runs when the request host does not identify the client.** Confirmed 2026-09-04. Two follow-ons: an **editable `Users.email` is therefore a tenant-routing control**, and the session bind at `V2.php:762-765` attaches the session to whichever row carries the address (that path **does** filter `isActive = true`). Both matter to anything that writes an email — see [Users.email is a login identity](../../_underscore/features/user-email-as-identity-and-password-reset.md).
@@ -46,3 +47,6 @@ This is the same `homeClientUserId` bridge that [cross-client data retrieval](./
46
47
  - [Surface meta option](./surface-meta-option.md)
47
48
  - [Per-client database connections](../../_underscore/features/per-client-database-connections.md)
48
49
  - [Users.email as login identity & password reset](../../_underscore/features/user-email-as-identity-and-password-reset.md)
50
+
51
+ ## Change history
52
+ - 2026-10-01 — Re-checked V2.php line refs (~L762-812); worked example: devteam@togatech.com in Elite = clientId 1 (True), homeClientUserId 2. (rgirish)
@@ -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)_ — 30 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
7
+ - **library** (Library) _(framework core)_ — 31 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
8
8
  - **worker** (Worker) — 41 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)
@@ -54,6 +54,9 @@ Adyen live TOGa Supply client (`Client_Adyen`) on the 1.0 NetSuite→TOGa Supply
54
54
 
55
55
  - **⚠ Transfer-order mode not independently verified against Adyen's NetSuite account.** The mode preserves the behavior Adyen had under the old implicit default; nobody has confirmed with the client that $0 + hold is the right signal *for Adyen*. Check before trusting the split.
56
56
 
57
+ - **⚠ Invoice sync stuck 2026-10-01 (issue 910, "billed sales order not imported")** — the billed SOs are older than the SO cursor. Fix handed to developer: SO cursor → `2025-11-01 00:00:00|0` once SO mode is `IDLE`. Details on the [engine doc](../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md).
58
+ - **⚠ OPEN: `ShippingCarriers` has only FedEx + UPS.** Item-fulfillment tracking POST fails `EV-12` (issue 925) — likely a USPS/DHL carrier. Not fixed. See [shipping carriers](../../2.0/apps/_underscore/features/shipping-carriers-and-accounts.md).
59
+
57
60
  ### Environments — where `Client_Adyen` lives, and which one "beta" means
58
61
 
59
62
  - `toga_find_schema` returns the tenant DB in exactly **three** environments: **prod, dev-sandbox, client-sandbox**. There is **no `Client_Adyen` in `client-beta`**.
@@ -6,12 +6,13 @@ project: _Underscore
6
6
  client: elite
7
7
  type: client-feature
8
8
  status: active
9
- updated: 2026-09-30
9
+ updated: 2026-10-01
10
10
  owners: [rgirish]
11
11
  files:
12
12
  - _underscore/Model/Elite/Item.php
13
13
  - _underscore/Model/Client/Item.php
14
14
  - worker/crons/toga2/netsuite/sync_togasupply_elite.php
15
+ - dbchanges2/Client_Elite/2026-10-01b - EliteReimportReceiptsWithUnlinkedLines.sql
15
16
  related:
16
17
  - ../profile.md
17
18
  - ./netsuite-togasupply-sync.md
@@ -43,7 +44,13 @@ How Elite's `_qtyOnHand` / `_qtyAvailable` are computed (`_Model_Elite_Item`) so
43
44
  - **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
45
  - **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
46
  - Owned-warehouse scope is Elite-only today; other clients have fake "bay" warehouses (see [Elite sync](./netsuite-togasupply-sync.md)).
47
+ - **⚠ OPEN (developer to decide): Qty On Order is ALWAYS 0.** `Elite/Item.php` overrides `_qtyOrdered = _qtyReceived`, and `Client/Item.php` `_qtyOnOrder = Ordered − Received`. So "Qty Ordered" on the Inventory page means units received on POs. NetSuite has real on-order qty.
48
+ - **On Hand off by 1 = receipt lines with NULL `purchaseOrderItemId`** (PO lines deleted + re-created; sync never rebuilds lines on an existing receipt). Fix = delete the receipts + rewind the IR cursor — `Client_Elite/2026-10-01b` (receipts 6947587, 6957582, 7043074, 7068990; written, not yet run). Detection on the [engine doc](../../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md).
49
+ - **Part numbers match case-insensitively** — NS items 1505560 "iPhone 14" and 1505562 "IPhone 14" are ONE toga item. Sum both when comparing to NetSuite.
50
+ - **Compare to NetSuite** with SuiteQL `aggregateItemLocation` (location 196) for qty, `inventoryNumberLocation` for serials.
51
+ - **Client-facing guide** (Ordered, On Hand, Available, Fulfilled, Invoiced, On Order, worked example Docking Station 67 received + 100 adjusted − 15 SO − 69 TO = 83): https://claude.ai/artifact/2wrVUcTBesKEq2b2CsySKw. Artifact pages print badly — make the PDF with headless Chrome `--print-to-pdf` and `display:block` in `@media print`.
46
52
 
47
53
  ## Change history
54
+ - 2026-10-01 — Qty On Order always 0 (open); receipt-line On Hand fix `2026-10-01b`; case-insensitive part merge; NS compare queries; client guide link. (rgirish)
48
55
  - 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
56
  - 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-30
9
+ updated: 2026-10-01
10
10
  owners: ["snaredla", "jcardinal", "rgirish"]
11
11
  files:
12
12
  - worker/crons/toga2/netsuite/sync_togasupply_elite.php
@@ -420,6 +420,14 @@ Set in `sync_togasupply_elite.php`; they feed the [inventory formula](./inventor
420
420
 
421
421
  ## Gotchas / known issues
422
422
 
423
+ - **Full NS↔toga recheck 2026-10-01: SO/IF/PO/Invoice/IA reconcile.** Expected differences (by design, not bugs):
424
+ - 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).
425
+ - An invoice billed against a $0 hold SO (stored as a TransferOrder) is skipped — e.g. 6215693.
426
+ - Credit memos (`CustCred`) are stored in `Invoices`.
427
+ - IA 6326239 is a company-wide count at NS locations 4/5/6.
428
+ - Receipts at NS location 5 (New York) are excluded — `2026-09-28a` removed that Location (8 today).
429
+ - RMA return receipts are out of scope.
430
+ - **Users added by hand 2026-10-01** (`Client_Elite/2026-10-01a`): Base role, linked to the EXISTING sync-created Contacts (carry `c_eliteUserId`) — look up `ContactEmailAddresses` before creating a Contact. `devteam@togatech.com` here is a cross-client user (clientId 1 True, `homeClientUserId` 2) — see [cross-client login](../../../2.0/apps/api2/features/login-cross-client-user-resolution.md).
423
431
  - **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
432
  - **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
433
 
@@ -462,6 +470,7 @@ Set in `sync_togasupply_elite.php`; they feed the [inventory formula](./inventor
462
470
  rewinding `NETSUITE_LAST_SYNC_DATETIME_SALES_ORDERS`.
463
471
 
464
472
  ## Change history
473
+ - 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)
465
474
  - 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)
466
475
  - 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)
467
476
  - 2026-09-18 — **Stale-SalesOrder backfill is live: 218 → 187.** The retirement method needed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.932",
3
+ "version": "1.0.933",
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",