toga-ai 1.0.219 → 1.0.220

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.
@@ -7,7 +7,7 @@
7
7
  | [Assortment Name Translation (AssortmentTranslations sidecar)](features/assortment-name-translation.md) | Serves Assortment (product-grouping) **names** in multiple languages by adding a per-language **sidecar** table `AssortmentTranslations`, reusing the platform's | _underscore/Model/Client/AssortmentTranslation.php, dbchanges2/Client/2026-06-26a - AssortmentTranslations.sql, dbchanges2/Core/2026-06-26a - AssortmentTranslationsRecord.sql, dbchanges2/Client/2026-06-26b - AssortmentTranslationsAcl.sql |
8
8
  | [Carrier Shipping Labels (UPS/FedEx) & NetSuite Item Fulfillment](features/carrier-shipping-labels.md) | Backend mechanics behind TOGa Supply's Fulfill & Ship: buying a carrier label (UPS/FedEx), persisting it, and creating the NetSuite Item Fulfillment with tracki | _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Client/ItemFulfillments/TrackingNumber.php, _underscore/Component/Library/LabelPdf/LabelPdf.php, _underscore/Component/Library/Carriers/Ups/Ups.php, _underscore/Component/Library/Carriers/Fedex/Fedex.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Trait/Netsuite/SalesOrder.php, _underscore/Component/Library/NetSuite/NetSuite.php, _underscore/Model/Client/TrackingNumber.php, _underscore/Model/Client/ShippingMethod.php, _underscore/Model.php, _underscore/Cloud.php |
9
9
  | [Client Email Template Sending](features/email-template-sending.md) | `_Model_Client_EmailTemplate` sends a stored, client-defined email template by UUID. | _underscore/Model/Client/EmailTemplate.php, _underscore/Model/Client/EmailTemplateOutgoingEmailAddress.php, _underscore/Email.php |
10
- | [Forecast.Sales NetSuite import engine (real-time webhook)](features/forecast-sale-import.md) | Real-time importer that takes a NetSuite **sale** record and writes its lines into `Forecast.Sales` (the Forecast2 revenue table). | _underscore/Component/Forecast/SaleImport/SaleImport.php, _underscore/Component/Forecast/Db/Db.php, _underscore/Component/Api/Netsuite/Netsuite.php, worker2/Worker/Netsuite/Invoice.php, worker2/Worker/Netsuite/CashSale.php, worker2/Worker/Netsuite/CreditMemo.php, worker2/Worker/Netsuite/CashRefund.php, worker2/Worker/Netsuite/JournalEntry.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Worker/Netsuite/SalesOrder.php, dbchanges2/Forecast/2026-06-26a - Add journalEntry to Sales transaction type enum.sql, test/@dave/test_invoice_lifecycle.php, test/@dave/test_je_lifecycle.php, test/@dave/test_creditmemo_lifecycle.php, test/@dave/test_cashsale_lifecycle.php, test/@dave/test_cashrefund_lifecycle.php, test/@dave/test_fetchrecord_routes.php, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/NetSuite/api-message-queue/dev_ue_api_msg_queue_enqueue.js |
10
+ | [Forecast.Sales NetSuite import engine (real-time webhook)](features/forecast-sale-import.md) | Real-time importer that takes a NetSuite **sale** record and writes its lines into `Forecast.Sales` (the Forecast2 revenue table). | _underscore/Component/Forecast/SaleImport/SaleImport.php, _underscore/Component/Forecast/Db/Db.php, _underscore/Component/Api/Netsuite/Netsuite.php, worker2/Worker/Netsuite/Invoice.php, worker2/Worker/Netsuite/CashSale.php, worker2/Worker/Netsuite/CreditMemo.php, worker2/Worker/Netsuite/CashRefund.php, worker2/Worker/Netsuite/JournalEntry.php, worker2/Worker/Netsuite/Opportunity.php, worker2/Worker/Netsuite/SalesOrder.php, dbchanges2/Forecast/2026-06-26a - Add journalEntry to Sales transaction type enum.sql, test/@dave/test_invoice_lifecycle.php, test/@dave/test_je_lifecycle.php, test/@dave/test_creditmemo_lifecycle.php, test/@dave/test_cashsale_lifecycle.php, test/@dave/test_cashrefund_lifecycle.php, test/@dave/test_fetchrecord_routes.php, test/@dave/verify_je_classification.php, test/@dave/probe_je_accounts.php, test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js, test/@dave/NetSuite/api-message-queue/dev_ue_api_msg_queue_enqueue.js |
11
11
  | [_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 |
12
12
  | [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 |
13
13
  | [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/ApiRequest.php, _underscore/Model/Client/Logs/Api.php |
@@ -26,6 +26,8 @@ files:
26
26
  - test/@dave/test_cashsale_lifecycle.php
27
27
  - test/@dave/test_cashrefund_lifecycle.php
28
28
  - test/@dave/test_fetchrecord_routes.php
29
+ - test/@dave/verify_je_classification.php
30
+ - test/@dave/probe_je_accounts.php
29
31
  - test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js
30
32
  - test/@dave/NetSuite/api-message-queue/dev_ue_api_msg_queue_enqueue.js
31
33
  related:
@@ -167,11 +169,25 @@ item-line upsert/reconcile machinery (`syncLines`/`guardedInsert`/`deleteRows`).
167
169
  - Lines hitting a **revenue** or **cost** GL account are grouped by **(salesRep, item)**:
168
170
  `revenue = Σ(credit − debit)` over revenue lines, `cost = Σ(debit − credit)` over cost
169
171
  lines, `profit = revenue − cost`.
170
- - **Account classification is done on the fly by NetSuite `accttype`** — SuiteQL
171
- `SELECT id, accttype FROM account WHERE id IN (...)`, cached per run, bucketed via a tunable
172
- `JE_ACCTTYPE_BUCKETS` map. It is **NOT** a hardcoded account-id list. PROVISIONAL default:
173
- `Income → revenue`, `COGS → cost` (pending sales-team sign-off; open questions: whether
174
- `OthIncome`/`Expense` count, and the Income-typed but "Cost"-named accounts 430/433).
172
+ - **Account classification is an EXACT account-NUMBER allowlist** (sales-team-defined; this
173
+ *replaced* the old provisional `accttype`-bucket rule). Constants in
174
+ `_Component_Forecast_SaleImport`: `JE_REVENUE_ACCOUNT_NUMBERS = ['41100','41300','41500']`,
175
+ `JE_COST_ACCOUNT_NUMBERS = ['51100','51200']`. Any line whose account number is in neither
176
+ list is **ignored**. The helper `accountNumbers(accountIds)` (which **replaced**
177
+ `accountTypes()`) resolves each line account's `acctnumber` via SuiteQL
178
+ `SELECT id, acctnumber FROM account WHERE id IN (...)`, cached per run; matching is
179
+ **string membership of `acctnumber`**, not `accttype`.
180
+ - **Sub-accounts are EXCLUDED by design — exact match, never prefix.** This chart of accounts
181
+ has real traps that both a `accttype` rule AND a prefix match would mis-bucket: under **41300**
182
+ "Service Revenue" sit `41300.01` (Service Revenue-Agent, `accttype` Income), `41300.02`/`.03`
183
+ (Service Cost-Agent, `accttype` **Bank**), and `41300.04` (Service **Costs**-Agent, `accttype`
184
+ **Income** despite the cost name); **51200** has `51200.01` (COGS Service-Agent). Exact-number
185
+ matching sidesteps all of them.
186
+ - **Resolved account map** (number → internalId / name): `41100→245` Product Revenue,
187
+ `41300→339` Service Revenue, `41500→355` Pre-Sales Consulting; `51100→248` COGS Product,
188
+ `51200→249` COGS Service. Read-only resolver tool: `test/@dave/probe_je_accounts.php`.
189
+ - **Likely future one-line tweak:** if the Agent sub-accounts should count, add `41300.01` to
190
+ the revenue list and `51200.01` to the cost list.
175
191
  - Group key uses `json_encode([salesRep, item])` (not a string-join) to stay collision-safe
176
192
  once the fields go live — an empty-string separator would let `"5|" + null` collide.
177
193
 
@@ -281,6 +297,12 @@ live `_Component_Api_Netsuite::createRecord()` `#`-delimiter bug (still unfixed;
281
297
  5. **delete** the record.
282
298
  6. **verify** the local row is gone (`removeAll`).
283
299
 
300
+ > **Scope of the update step (what it does and does NOT cover):** the update step changes
301
+ > **only `tranDate`** — a tracked column, so it forces a real change-detection UPDATE while
302
+ > the amount stays unchanged. It does **NOT** exercise updates to revenue / customer /
303
+ > salesRep / item, nor line add/remove. A future tester verifying those must extend the
304
+ > harness; the current update-step assertion only proves the tracked-column UPDATE path.
305
+
284
306
  Poll the **local** Forecast DB between steps:
285
307
  `C:\xampp8\mysql\bin\mysql.exe -u root` → db **`forecast`**, table **`Sales`** (filter on the
286
308
  test internalId, or customerId `2` / itemId `25`).
@@ -301,9 +323,16 @@ test internalId, or customerId `2` / itemId `25`).
301
323
  - Item **103741** ("Test Other Charge for Sale") → local `forecast.Items` id **25**.
302
324
  - Location **5** ("Main") — required as a **header-level** field on creditMemo/cashSale/cashRefund
303
325
  and **line-level** on invoice.
304
- - **JE fixture:** subsidiary **1**; accounts **245** (Product Revenue / Income), **248**
305
- (COGS Product / COGS), **215** (Clearing - CSS — ignored, neither revenue nor cost); set a
306
- `reversalDate` so NetSuite auto-creates the reversal.
326
+ - **JE fixture:** subsidiary **1**; accounts **245** (Product Revenue, acctnumber **41100**),
327
+ **248** (COGS Product, **51100**), **215** (Clearing - CSS — ignored, neither revenue nor
328
+ cost); set a `reversalDate` so NetSuite auto-creates the reversal. Classification is now by
329
+ **account number** (allowlist), so any clearing/balance line lands outside both lists and is
330
+ ignored automatically.
331
+ - **Discriminating classification test — `test/@dave/verify_je_classification.php`:** posts one
332
+ balanced JE `+10/+20/+30` to `41100/41300/41500`, `−5/−7` to `51100/51200`, **plus `+100` to
333
+ the excluded sub-account `41300.01`**, plus a clearing balance line → local `Forecast.Sales`
334
+ aggregates to **revenue=60.00, profit=48.00** (NOT 160 — proving `41300.01` is excluded and the
335
+ exact-number rule is in force).
307
336
 
308
337
  ### Expected local revenue signs / row outcomes
309
338
  - **invoice** → `+` (a **$0** invoice still writes **one** row, revenue `0.00` — the engine does
@@ -382,6 +411,19 @@ record is deleted in NetSuite.)
382
411
  - The cron's sign handling is not portable here — see Sign convention.
383
412
 
384
413
  ## Change history
414
+ - 2026-06-26 — **Changed JE revenue/cost classification from `accttype` buckets to an EXACT
415
+ account-NUMBER allowlist** (sales-team-defined): `JE_REVENUE_ACCOUNT_NUMBERS =
416
+ {41100,41300,41500}`, `JE_COST_ACCOUNT_NUMBERS = {51100,51200}`; everything else ignored.
417
+ `accountNumbers()` (replaced `accountTypes()`) resolves each account's `acctnumber` via
418
+ SuiteQL, cached per run; match is exact-string, **never prefix** — sub-accounts are excluded
419
+ by design to dodge real chart-of-accounts traps (`41300.04` "Service Costs-Agent" is
420
+ `accttype` Income; `41300.02/.03` are `accttype` Bank). Resolved map: 41100→245, 41300→339,
421
+ 41500→355, 51100→248, 51200→249. Verified with a discriminating balanced-JE test
422
+ (`test/@dave/verify_je_classification.php` → revenue=60.00/profit=48.00, proving `41300.01`
423
+ excluded); read-only resolver `test/@dave/probe_je_accounts.php`. Future one-liner: add
424
+ `41300.01`/`51200.01` if Agent sub-accounts should count. Also clarified the e2e harness
425
+ update step exercises **only `tranDate`** (a tracked column) — not revenue/customer/salesRep/
426
+ item or line add/remove. (dfranks)
385
427
  - 2026-06-26 — **Documented the full e2e testing playbook + harness inventory to run cold**
386
428
  (6-step create→verify→update→verify→delete→verify lifecycle; per-type harness action sets;
387
429
  the two execution modes — WEBHOOK vs LOCAL-ONLY `sync` — fixtures, expected signs, prod
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.219",
3
+ "version": "1.0.220",
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",