toga-ai 1.0.234 → 1.0.236

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.
@@ -99,6 +99,19 @@ Before creating a new class in a 1.0 repo:
99
99
  3. Follow the naming convention exactly, including the type segment.
100
100
  4. If you are adding a class to the `library` core repo, be aware it affects all 1.0 apps — review `knowledge/1.0/apps/library/architecture.md` first.
101
101
 
102
+ ## Schema migrations — always in `dbchanges`
103
+
104
+ **Application repos (`worker`, `togadesk`, `togaview`, `tools`, and any other 1.0 app)
105
+ contain no standalone `.sql` migration or schema files.** All schema changes for any
106
+ 1.0 database — table creation, `ALTER` statements, index changes, seed data — go in the
107
+ **`dbchanges`** repo.
108
+
109
+ This rule applies regardless of which application repo the feature lives in. Even if you
110
+ are adding a column that only `worker` reads, the `.sql` file goes in `dbchanges`.
111
+
112
+ Never create a `.sql` file inside a 1.0 application repo. The SQL that belongs in those
113
+ repos is only query strings embedded in PHP code — not standalone migration files.
114
+
102
115
  ## Error handling in controllers
103
116
 
104
117
  Controllers must not let exceptions bubble up to the framework unhandled. Catch specific exceptions, log them, and return an appropriate response.
@@ -8,7 +8,7 @@
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
10
  | [Record-Changed Event Publishing (_Event::publish to SQS)](features/event-publish-sqs.md) | `_Event::publish()` (in `_underscore/Event.php`) is the PHP side of the real-time event pipeline. | _underscore/Event.php |
11
- | [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
+ | [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/probe_je_shape.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 |
12
12
  | [_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 |
13
13
  | [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 |
14
14
  | [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 |
@@ -6,7 +6,7 @@ project: _Underscore
6
6
  client: shared
7
7
  type: feature
8
8
  status: active
9
- updated: 2026-06-26
9
+ updated: 2026-06-29
10
10
  owners: [dfranks]
11
11
  files:
12
12
  - _underscore/Component/Forecast/SaleImport/SaleImport.php
@@ -28,6 +28,7 @@ files:
28
28
  - test/@dave/test_fetchrecord_routes.php
29
29
  - test/@dave/verify_je_classification.php
30
30
  - test/@dave/probe_je_accounts.php
31
+ - test/@dave/probe_je_shape.php
31
32
  - test/@dave/NetSuite/api-message-queue/ue_api_msg_queue_enqueue.js
32
33
  - test/@dave/NetSuite/api-message-queue/dev_ue_api_msg_queue_enqueue.js
33
34
  related:
@@ -161,11 +162,27 @@ location, memo). Handler `_Worker_Netsuite_JournalEntry`: `post`/`put` → `sync
161
162
  item-line upsert/reconcile machinery (`syncLines`/`guardedInsert`/`deleteRows`).
162
163
 
163
164
  **Mapping model (durable design):**
164
- - A JE line carries **no native item and no salesRep** (confirmed against the live account:
165
- 0 JEs carry an item on a revenue/cost line). The fields the business rule needs **do not
166
- exist in NetSuite yet** — they are stubbed behind constants `JE_LINE_ITEM_FIELD` /
167
- `JE_LINE_SALESREP_FIELD` (currently **null**), so until the fields are added every kept
168
- line groups under `(null, null)` into **one aggregate row**.
165
+ - **Sales rep is now LIVE on JE lines** as the custom column **`custcol_sales_rep_line`**
166
+ (`JE_LINE_SALESREP_FIELD = 'custcol_sales_rep_line'`, wired 2026-06-29 after live Apr–May
167
+ 2026 probing). The value is a NetSuite **employee REFERENCE object** `{links, id, refName}`;
168
+ `.id` is the employee internalId (a **string** in JSON). `journalLineLookup()` resolves it
169
+ via `lookupId('Employees','netsuiteInternalId', .id)` → local `salesRepEmployeeId`. The
170
+ per-run **account-number allowlist isolates exactly the lines that carry the rep** — offset/
171
+ balancing lines omit class/entity/`custcol_sales_rep_line`, so they group to null and are
172
+ dropped. No schema/migration (`salesRepEmployeeId` already on `Forecast.Sales`).
173
+ - **JE lines carry NO item field at all — item ingestion from NetSuite is not possible.**
174
+ Confirmed across multiple Apr–May 2026 JEs: the full union of line keys is
175
+ `{account, class, cleared, debit, credit, custcol_sales_rep_line, entity, line, memo, location}`.
176
+ There is **no native `item` and no custom item column**. `JE_LINE_ITEM_FIELD` stays **null
177
+ by design**; kept lines now group **per-salesRep with a null item**. The dimension that
178
+ exists *instead of* item is **`entity`** (the customer ref). Probe tool:
179
+ `test/@dave/probe_je_shape.php` (dumps line column shapes + custom columns).
180
+ - **No salesRep self-heal (unlike the item path).** `journalLineLookup()` for Employees has
181
+ **no `syncEmployee()` fallback** (the item path self-heals via
182
+ `_Worker_Netsuite_Item::syncItem()`). A rep missing from `Forecast.Employees` silently
183
+ resolves to **null** and re-aggregates under null. It relies on the **supporting-records
184
+ pull** keeping `Forecast.Employees` current. (Sample coverage was complete — rep IDs
185
+ 482, 569, 685, 30954, 34877 all present in prod `Forecast.Employees`.)
169
186
  - Lines hitting a **revenue** or **cost** GL account are grouped by **(salesRep, item)**:
170
187
  `revenue = Σ(credit − debit)` over revenue lines, `cost = Σ(debit − credit)` over cost
171
188
  lines, `profit = revenue − cost`.
@@ -411,6 +428,21 @@ record is deleted in NetSuite.)
411
428
  - The cron's sign handling is not portable here — see Sign convention.
412
429
 
413
430
  ## Change history
431
+ - 2026-06-29 — **Wired JE sales-rep ingestion live** (TRUE-79862): set
432
+ `JE_LINE_SALESREP_FIELD = 'custcol_sales_rep_line'` after live Apr–May 2026 probing
433
+ confirmed the rep is now present on JE lines as that custom column — a NetSuite employee
434
+ **reference** object `{links,id,refName}` whose `.id` (string) resolves via
435
+ `lookupId('Employees','netsuiteInternalId',.id)`. Kept lines now group per-salesRep with a
436
+ null item. One-line change, no schema/migration (`salesRepEmployeeId` already exists).
437
+ **Confirmed JE lines carry NO item field** (native or custom) — line key union is
438
+ `{account,class,cleared,debit,credit,custcol_sales_rep_line,entity,line,memo,location}`; the
439
+ dimension *instead of* item is `entity` (customer ref), so `JE_LINE_ITEM_FIELD` stays null
440
+ by design (item ingestion from NetSuite is not possible). **Gotcha: JE salesRep lookup has
441
+ no self-heal** (the item path does via `syncItem()`) — a rep missing from
442
+ `Forecast.Employees` resolves to null and aggregates under null; relies on the
443
+ supporting-records pull. Still dormant in prod (JE webhook fires only once the AMQ enqueuer
444
+ deploys on the Journal Entry record type). Probe tool: `test/@dave/probe_je_shape.php`.
445
+ (dfranks)
414
446
  - 2026-06-26 — **Changed JE revenue/cost classification from `accttype` buckets to an EXACT
415
447
  account-NUMBER allowlist** (sales-team-defined): `JE_REVENUE_ACCOUNT_NUMBERS =
416
448
  {41100,41300,41500}`, `JE_COST_ACCOUNT_NUMBERS = {51100,51200}`; everything else ignored.
@@ -39,6 +39,13 @@ processes background jobs. It's a `_underscore` 2.0 app (`index.php` is just
39
39
  | S3 `agilant-worker-fallback` | Failed webhook payloads (`webhook-fallback/{uuid}.json`, 7-day lifecycle). |
40
40
  | API Gateway `WorkerWebhookIngestion` | `webhook.togahub.com` → Lambda. Lambda Function URLs are blocked by an AWS Org SCP, so API Gateway is the only public entry point. |
41
41
 
42
+ ## Critical repo rule — no SQL migration files
43
+
44
+ **This repo contains PHP and Python only. Never create `.sql` schema-migration files here.**
45
+ All table creation, `ALTER` statements, index changes, and seed data — including for
46
+ `Core.WorkerJobs` and `Core.CronJobs` — go in the `dbchanges2` repo using its
47
+ `YYYY-MM-DD<letter> - <Description>.sql` naming convention.
48
+
42
49
  ## MySQL tables
43
50
 
44
51
  **`Core.WorkerJobs`** — unified execution record. Key columns: `uuid`,
@@ -22,6 +22,14 @@ related:
22
22
 
23
23
  ## Database/SQL
24
24
 
25
+ ### Migration files belong in `dbchanges2`
26
+
27
+ SQL query strings embedded in PHP code (SELECTs, INSERTs, UPDATEs) live in this repo as
28
+ part of normal PHP files. **Standalone `.sql` migration files do not.** Any schema change
29
+ — CREATE TABLE, ALTER TABLE, new index, seed rows — must be placed in the `dbchanges2`
30
+ repo, not here. See `2.0/standards/framework-rules.md` § Schema migrations for naming
31
+ conventions.
32
+
25
33
  ### Table Design
26
34
 
27
35
  #### **Order of Fields**
@@ -86,6 +86,22 @@ return [
86
86
  ];
87
87
  ```
88
88
 
89
+ ## Schema migrations — always in `dbchanges2`
90
+
91
+ **Application repos (`worker2`, `api2`, `_underscore`) contain no standalone `.sql`
92
+ migration or schema files.** All schema changes for any 2.0 database — table creation,
93
+ `ALTER` statements, index changes, seed data — go in the **`dbchanges2`** repo.
94
+
95
+ File naming in `dbchanges2`: `YYYY-MM-DD<letter> - <Description>.sql`
96
+ (e.g. `2026-06-30a - Add WorkerJobs failureCode column.sql`).
97
+
98
+ This rule applies regardless of which application repo the feature lives in. Even if you
99
+ are adding a column that only `worker2` reads, the `.sql` file goes in `dbchanges2`.
100
+
101
+ Never create a `.sql` file inside `worker2`, `api2`, `_underscore`, or any other 2.0
102
+ application repo. The SQL that belongs in those repos is only query strings embedded in
103
+ PHP code — not standalone migration files.
104
+
89
105
  ## Checking dependencies before touching shared code
90
106
 
91
107
  `dependsOn` in `knowledge/registry.json` means a repo extends or depends on another repo's classes. Before modifying a class in a dependency repo (e.g. `_underscore` core):
@@ -16,7 +16,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
16
16
 
17
17
  ## 2.0 framework
18
18
 
19
- - **_underscore** (_Underscore) _(framework core)_ — 18 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
19
+ - **_underscore** (_Underscore) _(framework core)_ — 19 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
20
20
  - **worker2** (Worker) — 22 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
21
21
  - **api2** (API) — 7 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
22
22
  - **dbchanges2** (Database Changes) _(framework core)_ — 3 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.234",
3
+ "version": "1.0.236",
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",