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.
- package/knowledge/1.0/standards/framework-rules.md +13 -0
- package/knowledge/2.0/apps/_underscore/INDEX.md +1 -1
- package/knowledge/2.0/apps/_underscore/features/forecast-sale-import.md +38 -6
- package/knowledge/2.0/apps/worker2/architecture.md +7 -0
- package/knowledge/2.0/standards/backend-php.md +8 -0
- package/knowledge/2.0/standards/framework-rules.md +16 -0
- package/knowledge/INDEX.md +1 -1
- package/package.json +1 -1
|
@@ -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-
|
|
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
|
-
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
`
|
|
168
|
-
|
|
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):
|
package/knowledge/INDEX.md
CHANGED
|
@@ -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)_ —
|
|
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