toga-ai 1.0.643 → 1.0.644

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.
@@ -16,7 +16,7 @@
16
16
  | [1.0 MVC Page Pattern & New-App Skeleton](features/mvc-page-pattern-and-app-skeleton.md) | This is the **reusable recipe for standing up a new 1.0 (`App_`) application** and for adding pages to one — the folder-based MVC routing, the page lifecycle, t | library/app/framework.php, library/app/frameworkindex.php, library/app/mvc.php, library/app/database.php, library/app/model.php, library/app/config.php |
17
17
  | [isFulfillable from NetSuite during Item Sync (Phase 1)](features/netsuite-item-isfulfillable-sync.md) | This is the **1.0 (Phase 1)** half of the `isFulfillable` feature: reading the NetSuite `isfulfillable` flag during item sync and stamping it onto the **Agilant | library/app/netsuite.php, library/app/api/toga2.php, worker/crons/toga2/netsuite/common_sync_togasupply.php, worker/crons/toga2/netsuite/backfill_isfulfillable_jul5.php |
18
18
  | [NetSuite SuiteQL/REST API Reference](features/netsuite-suiteql-api-reference.md) | General working reference for the Agilant NetSuite integration: how to authenticate, how SuiteQL behaves, and the confirmed schema of the tables/columns/codes w | library/app/api/netsuite/rest.php, library/ssl/netsuite_ec_key.pem, test/@dave/Junk Drawer/nsq.php |
19
- | [NetSuite SuiteQL/REST Shim — Field Semantics](features/netsuite-suiteql-rest-shim.md) | `App_Api_Netsuite_Rest` is the REST/SuiteQL replacement for the deprecated NetSuite SOAP toolkit. | library/app/api/netsuite/rest.php |
19
+ | [NetSuite SuiteQL/REST Shim — Field Semantics](features/netsuite-suiteql-rest-shim.md) | `App_Api_Netsuite_Rest` is the REST/SuiteQL replacement for the deprecated NetSuite SOAP toolkit. | library/app/api/netsuite/rest.php, library/app/netsuite.php |
20
20
  | [NetSuite Sync Alert Monitor (App_SystemMonitor_NetSuiteIntegration)](features/netsuite-sync-alert-monitor.md) | `App_SystemMonitor_NetSuiteIntegration` (`library/app/systemmonitor/netsuiteintegration.php`, title **"NetSuite Sync Alert"**) is a 1.0 system monitor that watc | library/app/systemmonitor/netsuiteintegration.php, worker/crons/infrastructure/system_monitors.php |
21
21
  | [Startech PC Matic B2B Sync (library)](features/startech-pcmaticb2b-sync.md) | `library/app/api/toga2.php` handles bidirectional ticket sync for PC Matic B2B between TOGaDesk 1.0 and TOGA 2.0. | library/app/api/toga2.php, library/app/api/startechticket.php, worker/crons/toga2/startech/common_import_supporting_records.php |
22
22
  | [App_Api_Toga2 — TOGa2 API Client & 1.0↔2.0 Sync Bridge](features/toga2-api-client-and-bridge.md) | `App_Api_Toga2` (`library/app/api/toga2.php`, ~8400 lines) is the **1.0-side client for the TOGa 2 (`_underscore`/api2) public API** *and* the home of the cross | library/app/api/toga2.php, worker/crons/toga2/aig/sync_togasupply_aig.php, worker/crons/toga2/wje/sync_togasupply_wje.php, test/@Mark/AIG/test_multi_email.php |
@@ -6,14 +6,16 @@ project: Library
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-11
10
- owners: [dfranks]
9
+ updated: 2026-08-25
10
+ owners: [dfranks, jcardinal]
11
11
  files:
12
12
  - library/app/api/netsuite/rest.php
13
+ - library/app/netsuite.php
13
14
  related:
14
15
  - netsuite-suiteql-api-reference.md
15
16
  - ../architecture.md
16
17
  - ../../worker/features/forecast2-netsuite-reconciliation.md
18
+ - ../../../2.0/apps/_underscore/features/netsuite-rest-client.md
17
19
  ---
18
20
 
19
21
  ## Summary
@@ -90,6 +92,35 @@ Forecast2 tables already store.
90
92
  - **TO_DATE rejects impossible dates** (e.g. `2026-06-31`) with an opaque HTTP 400 — validate
91
93
  calendar dates before building SuiteQL.
92
94
 
95
+ ## Transaction status sourcing (1.0) — and the 2026.2 REST `status.id` change
96
+
97
+ 1.0 reaches NetSuite over **two transports, and neither reads status off a REST record GET
98
+ body**:
99
+
100
+ - **REST adapter (`App_Api_Netsuite_Rest`, `rest.php`).** It *does* issue `/record/v1/...`
101
+ GETs, but it sources `status` from the **SuiteQL header row** (`$h->status`, from the
102
+ `SELECT ... status ... FROM ...` at ~L381/574/754 — already a letter) and maps that letter
103
+ to the SOAP-era text label via an internal **`$statusMap`** (~L471-472, L625-626,
104
+ L853-854/863). The REST GET body (`$rec`) is only read for shipMethod / opportunity / terms /
105
+ createdFrom / country — **never status**. Downstream forecast2 crons
106
+ (`worker/crons/toga2/forecast2/*`) compare `$nsRecord->status` to text, but that text is
107
+ *adapter-produced* (from the SuiteQL letter via `$statusMap`), not from the changing REST
108
+ field.
109
+ - **SOAP (`App_NetSuite`, `library/app/netsuite.php`).** Always uses `new NetSuiteService()`
110
+ (SuiteTalk toolkit) and reads transaction status off the SOAP record as text (e.g.
111
+ `$items->status != 'Billed'/'Cancelled'/'Closed'` ~L1151/L1434; `$nsInvoice->status ==
112
+ 'Paid In Full'` ~L2546). SOAP is a different API and is unaffected. (The many
113
+ `->status->isSuccess` / `statusDetail` reads elsewhere are the SOAP **response envelope**,
114
+ not the record's business status.)
115
+
116
+ > **NetSuite 2026.2 impact: none for 1.0.** 2026.2 standardizes the `status.id` returned by
117
+ > **REST record GETs** for transactions to single-letter codes across all types (some types
118
+ > return it as text like `"Open"` today); `status.refName` and SuiteQL are unchanged, and
119
+ > SOAP is a separate API. Since no 1.0 code reads `status.id` off a REST GET body, no patch is
120
+ > needed. NetSuite SuiteAnswers ID **89313**. The 2.0 side of the same conclusion (shared REST
121
+ > client + worker2 consumers) is in
122
+ > [`netsuite-rest-client.md`](../../../2.0/apps/_underscore/features/netsuite-rest-client.md).
123
+
93
124
  ## Data model
94
125
 
95
126
  Reads NetSuite `transaction`, `transactionline`, `previoustransactionlinelink` via SuiteQL and
@@ -108,6 +139,12 @@ None — uniform across clients (NetSuite is a single shared account).
108
139
 
109
140
  ## Change history
110
141
 
142
+ - 2026-08-25 — Documented **1.0 transaction-status sourcing across both transports** and the
143
+ conclusion that the **NetSuite 2026.2 REST `status.id` text→letter standardization does not
144
+ affect 1.0** (SuiteAnswers 89313). `App_Api_Netsuite_Rest` derives status from the SuiteQL
145
+ letter and maps it via an internal `$statusMap` — never from the REST GET body; forecast2
146
+ crons see that adapter-produced text, not the changing field; `library/app/netsuite.php` is
147
+ pure SOAP (a different API). Discovery/impact-analysis only — no code changed. (jcardinal)
111
148
  - 2026-06-11 — Documented bulk SuiteQL field semantics (tl.id==line, ShipItem NULL cost, iscogs
112
149
  COGS filter, createdFrom non-determinism, foreignamountunpaid, sign conventions) surfaced while
113
150
  moving the Forecast2 trueup tools off per-id REST GETs. (dfranks)
@@ -30,7 +30,7 @@
30
30
  | [_Model magic-field access (__get without __isset)](features/model-magic-field-access.md) | `_Model` exposes DB columns as "magic" properties via `__get()`, but it defines **no** `__isset()`. | _underscore/Model/Core/Model.php, _underscore/Model.php, _underscore/Model/Rate/Subscription.php |
31
31
  | [_Model::save() parent FK cascade — stored-SQL-field recompute deadlocks](features/model-save-parent-cascade-stored-field-deadlock.md) | `_Model::save()` runs a **generic parent foreign-key cascade**: inserting (or saving) a child row that carries an FK to a parent causes `_Model` to **re-load an | _underscore/Model.php, _underscore/Model/Client/PurchaseOrder.php, _underscore/Model/Client/AdvanceShippingNotice.php |
32
32
  | [_Model::save() vs raw _Query — no atomic conditional update](features/model-save-vs-query-atomic-update.md) | `_Model::save()` is a plain load-then-write ORM primitive and **cannot express an atomic conditional update** (an optimistic-concurrency / row-claim guard such | _underscore/Model.php, _underscore/Query.php, _underscore/Model/Rate/Subscription.php |
33
- | [NetSuite REST Client (_Component_Api_Netsuite) — record writes & SuiteQL](features/netsuite-rest-client.md) | `_Component_Api_Netsuite` is the **2.0 `_underscore` NetSuite REST client** — the shared primitive every worker2/api2 NetSuite caller uses for record GETs, Suit | _underscore/Component/Api/Netsuite/Netsuite.php |
33
+ | [NetSuite REST Client (_Component_Api_Netsuite) — record writes & SuiteQL](features/netsuite-rest-client.md) | `_Component_Api_Netsuite` is the **2.0 `_underscore` NetSuite REST client** — the shared primitive every worker2/api2 NetSuite caller uses for record GETs, Suit | _underscore/Component/Api/Netsuite/Netsuite.php, _underscore/Trait/Netsuite/SalesOrder.php, worker2/Worker/Netsuite/SalesOrder.php, worker2/Component/Forecast/SaleImport/SaleImport.php, worker2/Worker/Netsuite/Opportunity.php |
34
34
  | [NetSuite Sales Order sync — ship-to address, phone, and PO reference sourcing](features/netsuite-salesorder-address-phone-sync.md) | `_Trait_Netsuite_SalesOrder` is the **shared** sales-order importer composed into **22 client models** (every client on the dbchanges2 `netsuite` module). | _underscore/Trait/Netsuite/SalesOrder.php, _underscore/Model.php, dbchanges2/_modules/netsuite/2026-08-10a - AddressPhoneNumberApiRoleAcl.sql |
35
35
  | [Legacy page meta (Page::meta) & context-scoped ClientRecordFieldSettings](features/page-meta-context-field-settings.md) | `_Model_Core_Page::meta()` is the **legacy** page-meta resolver behind `GET /pages/meta?slug=<slug>` — still the live path for `toga2-supply` and other pre-Surf | _underscore/Model/Core/Page.php, _underscore/Model/Client/TableView.php, toga2-supply/src/components/ui/Tables/PrimaryTable/PrimaryTable.tsx |
36
36
  | [Per-Client Database Connections & the Local Logs Trap](features/per-client-database-connections.md) | When `_underscore` serves a request for a client it opens **three distinct per-client database connections**, not one. | _underscore/Database.php, _underscore/Model.php, _underscore/Query.php, _underscore/ApiRequest.php, _underscore/Model/Client/Logs/Api.php, api2/Controller/Index.php |
@@ -6,14 +6,19 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-08-20
9
+ updated: 2026-08-25
10
10
  owners: ["dfranks", "jcardinal", "bala"]
11
11
  files:
12
12
  - _underscore/Component/Api/Netsuite/Netsuite.php
13
+ - _underscore/Trait/Netsuite/SalesOrder.php
14
+ - worker2/Worker/Netsuite/SalesOrder.php
15
+ - worker2/Component/Forecast/SaleImport/SaleImport.php
16
+ - worker2/Worker/Netsuite/Opportunity.php
13
17
  related:
14
18
  - ../../worker2/features/netsuite-salesorder-open-orders-sync.md
15
19
  - ../../worker2/features/netsuite-salesorder-outbound-push.md
16
20
  - ../../worker2/features/netsuite-opportunity-sync.md
21
+ - ../../library/features/netsuite-suiteql-rest-shim.md
17
22
  - ../architecture.md
18
23
  ---
19
24
 
@@ -167,6 +172,40 @@ rather than a bad query. Confirmed live 2026-07-28.
167
172
  Surfaced building the NYCHH asset-tag backfill (see
168
173
  [nychh-asset-tag-backfill](../../worker2/features/nychh-asset-tag-backfill.md)).
169
174
 
175
+ ## NetSuite transaction status sourcing (2.0) — and the 2026.2 REST `status.id` change
176
+
177
+ **`fetchRecord()` returns the REST record UNTOUCHED — there is no central status
178
+ normalization in the shared client.** `fetchRecord()` (`Netsuite.php` ~L215-223) does
179
+ `send('GET', route)`, json-decodes, and hands the raw record back; it never reads or maps
180
+ `status`. So whether a 2.0 consumer is exposed to a change in the REST record's `status`
181
+ field depends entirely on *that consumer*, not on the client. Audited 2026-08-25 — every 2.0
182
+ consumer sources transaction status from a change-safe field:
183
+
184
+ - **`worker2/Worker/Netsuite/SalesOrder.php`** reads `status->refName` (the human-readable
185
+ label) and compares it to `OPEN_STATUSES` text — never `status.id`.
186
+ - **`worker2/Component/Forecast/SaleImport/SaleImport.php`** — `statusName()` (~L845-853) reads
187
+ `status->refName`, matched against `EXCLUDED_STATUSES` text. This is the shared engine that
188
+ Invoice / CashSale / CreditMemo / CashRefund import all delegate to.
189
+ - **`worker2/Worker/Netsuite/Opportunity.php`** — `fetchStatus()` (~L431-444) gets transaction
190
+ status via **SuiteQL** (`SELECT status FROM transaction`), which already returns the letter
191
+ and maps `A`/`C`/`D`. (Its `entityStatus.id` at ~L197 is a *different* field — an opaque
192
+ lookup key, not the transaction status.)
193
+ - **`_underscore/Trait/Netsuite/SalesOrder.php`** — the one text `switch ($nsOrder->status)`
194
+ (~L589-620) is driven by a **SOAP** `$service->get()` record, not REST.
195
+ - **`api2`** makes no NetSuite calls of its own — it only registers the SDK at boot and writes
196
+ one hardcoded outbound status literal (`PENDING_FULFILLMENT`); its `->status` reads are its
197
+ own HTTP response envelope. Not a NetSuite status consumer.
198
+
199
+ > **NetSuite 2026.2 impact: none for 2.0.** NetSuite 2026.2 standardizes the `status.id` field
200
+ > returned by **REST record GETs** for transaction records to single-letter codes (A/B/D/E/V)
201
+ > across all transaction types (today some types return `status.id` as text like `"Open"`);
202
+ > `status.refName` and SuiteQL are unchanged, and SOAP/SuiteTalk is a different API. Because no
203
+ > 2.0 code reads `status.id` off a REST GET body as text, no patch is needed. NetSuite
204
+ > SuiteAnswers ID **89313**. (Not audited: other 2.0 apps that may consume this client —
205
+ > `toga2-supply`, `toga25-supply`, `tools` — check them the same way if touched.) The 1.0 side
206
+ > of the same conclusion is in
207
+ > [`netsuite-suiteql-rest-shim.md`](../../library/features/netsuite-suiteql-rest-shim.md).
208
+
170
209
  ## Logging
171
210
 
172
211
  `send()` calls `setLogging(false)`, so **NetSuite REST request/response bodies are NOT written to
@@ -192,6 +231,14 @@ doc.)
192
231
 
193
232
  ## Change history
194
233
 
234
+ - 2026-08-25 — Documented **2.0 NetSuite transaction-status sourcing** and the conclusion that
235
+ the **NetSuite 2026.2 REST `status.id` text→letter standardization does not affect 2.0**
236
+ (SuiteAnswers 89313). `fetchRecord()` returns the REST record raw (no central status
237
+ normalization), and every audited consumer reads status from `status.refName` or SuiteQL, or
238
+ over SOAP — none reads REST `status.id` as text: worker2 SalesOrder/SaleImport (`refName`),
239
+ worker2 Opportunity (`fetchStatus()` via SuiteQL), `_underscore` SalesOrder trait (SOAP),
240
+ api2 (no NetSuite calls). Discovery/impact-analysis only — no code changed. (jcardinal)
241
+
195
242
  - 2026-08-20 — Added **idempotent creates via `externalId`**: `createRecord()` has no idempotency, so
196
243
  stamp an `externalId` (NetSuite rejects a duplicate within the account) and read the record back
197
244
  with `GET /record/v1/<record>/eid:<externalId>` — the non-SuiteQL fallback, which matters because
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.643",
3
+ "version": "1.0.644",
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",