toga-ai 1.0.199 → 1.0.201
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/apps/worker/features/forecast2-netsuite-reconciliation.md +26 -0
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/rate/INDEX.md +1 -0
- package/knowledge/clients/rate/features/netsuite-cashsale-export.md +92 -0
- package/knowledge/clients/rate/profile.md +2 -1
- package/package.json +1 -1
|
@@ -68,6 +68,12 @@ by reconciling a chosen tranDate range directly against NetSuite.
|
|
|
68
68
|
still go to stderr.
|
|
69
69
|
- **`--verbose`** was trimmed — removed per-line MATCH/EXCLUDE/NOOP and the always-on per-SO summary;
|
|
70
70
|
the per-SO header now prints lazily, once, only when the order has an INSERT/UPDATE/DELETE.
|
|
71
|
+
- **Step 4 per-statement SQL audit (added 2026-06-25).** The Step 4 results block now lists, one line
|
|
72
|
+
per statement, `<NetSuite SO internal id> <SQL>` — the exact statement that **was run** (live) or
|
|
73
|
+
**would be run** (`--dry-run`), so you can audit a dry-run before applying or trace an unexpected
|
|
74
|
+
insert/update/delete back to its source order. Previously `emit()` executed-or-counted each write but
|
|
75
|
+
discarded the SQL string, leaving only aggregate counts. The listing inherits the existing `--quiet`
|
|
76
|
+
gating (prints only on a changed pass) and the `--dry-run` header wording ("would run" vs "applied").
|
|
71
77
|
- `loop_trueup_open_orders.php [--prod] [--dry-run] [--verbose] [--quiet] [--sleep=N] [--days-back=N]`
|
|
72
78
|
— continuous runner that recomputes a rolling `[today−N .. today]` window each pass and re-invokes
|
|
73
79
|
`trueup_open_orders.php --by-lastmodified` until Ctrl-C. Uses `PHP_BINARY` + `passthru` with
|
|
@@ -113,6 +119,18 @@ by reconciling a chosen tranDate range directly against NetSuite.
|
|
|
113
119
|
**classify a drift set as whole-missing vs. present-but-different**, and to scope a
|
|
114
120
|
`Logs.Webhook`/`WorkerJobs` search window from the lastmodified timestamps.
|
|
115
121
|
|
|
122
|
+
- **`trueup_open_orders` SQL-audit collector.** A global `$sqlLog` (alongside `$opCount`) collects
|
|
123
|
+
`['soid'=>.., 'sql'=>..]` for every write **in both live and dry-run modes**; `emit($sql, $db, $soId=null)`
|
|
124
|
+
records first, then executes only when not `--dry-run`. Every main-loop `emit()` passes the in-scope
|
|
125
|
+
NetSuite SO internal id (`$nsSoId`) — closed-order DELETE, both Classifications INSERT/UPDATE, the
|
|
126
|
+
OpenOrderItems UPDATE/INSERT, and the per-order DELETE flush. The Step 3b stale-cleanup DELETE is batched
|
|
127
|
+
by local row id across many SOs, so it keeps the default null `$soId` and is labelled `stale` (the full
|
|
128
|
+
stale SO id set is printed once above the listing). Each statement is whitespace-collapsed
|
|
129
|
+
(`preg_replace('/\s+/', ' ', trim(...))`) so multi-line built SQL renders on one line, and the listing is
|
|
130
|
+
appended to the existing `$resultLines` array (inheriting `--quiet` gating + `--dry-run` wording). Memory
|
|
131
|
+
stays bounded under the existing 1G `memory_limit`. (This corrected a prior docblock claim that `$sqls`
|
|
132
|
+
was never accumulated globally.)
|
|
133
|
+
|
|
116
134
|
## Data model
|
|
117
135
|
|
|
118
136
|
`Forecast.Sales`, `Forecast.OpenOrderItems` on the **core2** cluster
|
|
@@ -253,6 +271,14 @@ None — Forecast2 is a single shared dataset.
|
|
|
253
271
|
|
|
254
272
|
## Change history
|
|
255
273
|
|
|
274
|
+
- 2026-06-25 — **`trueup_open_orders` Step 4 now lists the exact per-statement SQL** (`<NS SO id> <SQL>`,
|
|
275
|
+
one line per statement) for both live and `--dry-run` passes, so a dry-run is auditable and any
|
|
276
|
+
unexpected insert/update/delete is traceable to its source order. Implemented via a global `$sqlLog`
|
|
277
|
+
collector + `emit($sql,$db,$soId)` (records always, executes only when not dry-run); each main-loop emit
|
|
278
|
+
passes `$nsSoId`, and the batched Step 3b stale-cleanup DELETE is labelled `stale`. Inherits existing
|
|
279
|
+
`--quiet` gating and `--dry-run` wording; whitespace-collapsed to one line per statement. Corrects the
|
|
280
|
+
prior "`$sqls` never accumulated globally" docblock. `php -l` clean (PHP 7.2); not executed live/dry-run
|
|
281
|
+
this session. (dfranks)
|
|
256
282
|
- 2026-06-25 — **`trueup_open_orders` gained `--by-lastmodified` / `--quiet` / leaner `--verbose`; added
|
|
257
283
|
`loop_trueup_open_orders.php` as the stopgap for the real-time billed-order removal gap.**
|
|
258
284
|
`--by-lastmodified` windows both Step 1 and Step 3b on `lastmodifieddate` (ET TIMESTAMP bounds) and
|
package/knowledge/INDEX.md
CHANGED
|
@@ -15,7 +15,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
15
15
|
|
|
16
16
|
## 2.0 framework
|
|
17
17
|
|
|
18
|
-
- **_underscore** (_Underscore) _(framework core)_ —
|
|
18
|
+
- **_underscore** (_Underscore) _(framework core)_ — 15 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
19
19
|
- **worker2** (Worker) — 15 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
20
20
|
- **api2** (API) — 6 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
21
21
|
- **dbchanges2** (Database Changes) _(framework core)_ — 2 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
| Doc | Framework | Summary | Files |
|
|
4
4
|
|-----|-----------|---------|-------|
|
|
5
5
|
| [Rate Monthly Reconciliation Report](features/monthly-reconciliation-report.md) | 1.0 | A monthly cron that emails an Excel reconciliation report covering all Rate subscription sales orders and their linked PayPal payments for the prior calendar mo | worker/crons/notifications/reports/rate/send_monthly_rate_purchases_report.php, worker/schedules/cron.worker.notification.json |
|
|
6
|
+
| [Rate SalesOrder → NetSuite CashSale Export (postPost)](features/netsuite-cashsale-export.md) | 2.0 | Rate sells home-warranty / home-tech-support products. | _underscore/Model/Rate/SalesOrder.php, _underscore/Model/Rate/Item.php |
|
|
6
7
|
| [Rate SAML SSO](features/saml-sso.md) | 2.0 | Rate uses Azure AD as its IdP (`login.rate.com`). | _underscore/Model/Rate/ClientAuthentication.php, saml/Controller/Index.php, toga2-view/src/hooks/useAuthenticationFlow.ts |
|
|
7
8
|
| [Service Card Entitlement Display](features/service-card-entitlements.md) | 2.0 | Rate's home and services pages display one service card per purchased entitlement. | src/components/ServiceCard/ServiceCard.tsx, src/components/ServiceCard/index.ts, src/hooks/useBundleServices.ts, src/pages/Home/api/homeApi.ts, src/pages/Home/view/HomePage.tsx, src/pages/Home/viewModels/useHomePageViewModel.ts, src/pages/Services/view/ServicesPage.tsx, src/pages/Services/viewModels/useServicePageViewModel.ts |
|
|
8
9
|
| [Rate](profile.md) | 2.0 | Rate is a mortgage/lending client. | |
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Rate SalesOrder → NetSuite CashSale Export (postPost)"
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: _underscore
|
|
5
|
+
project: _Underscore
|
|
6
|
+
client: rate
|
|
7
|
+
type: client-feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-25
|
|
10
|
+
owners: ["rgirish"]
|
|
11
|
+
files:
|
|
12
|
+
- _underscore/Model/Rate/SalesOrder.php
|
|
13
|
+
- _underscore/Model/Rate/Item.php
|
|
14
|
+
related:
|
|
15
|
+
- 2.0/apps/_underscore/features/netsuite-rest-client.md
|
|
16
|
+
- clients/rate/profile.md
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Summary
|
|
20
|
+
|
|
21
|
+
Rate sells home-warranty / home-tech-support products. When a Rate SalesOrder is created,
|
|
22
|
+
the `_Model_Rate_SalesOrder::postPost` interceptor builds and writes a NetSuite **CashSale**
|
|
23
|
+
(not a SalesOrder). This doc covers two pieces of that export added 2026-06-25: passing the
|
|
24
|
+
order's ship-to (property) address onto the CashSale, and resolving each line item's NetSuite
|
|
25
|
+
item internalId from a dedicated column with a partNumber fallback.
|
|
26
|
+
|
|
27
|
+
This is Rate's override of the shared NetSuite write path documented in
|
|
28
|
+
`2.0/apps/_underscore/features/netsuite-rest-client.md`.
|
|
29
|
+
|
|
30
|
+
## Key files / entry points
|
|
31
|
+
|
|
32
|
+
| File | Role |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `_underscore/Model/Rate/SalesOrder.php` | `postPost` interceptor — builds the NetSuite CashSale (address + line items) |
|
|
35
|
+
| `_underscore/Model/Rate/Item.php` | `_Model_Rate_Item` — declares `c_netsuiteInternalItemId` so the column is API-exposed and UI-settable |
|
|
36
|
+
|
|
37
|
+
## How it works
|
|
38
|
+
|
|
39
|
+
### Ship-to address → CashSale `shippingAddress`
|
|
40
|
+
The `postPost` SQL query was expanded to `LEFT JOIN Addresses` (via
|
|
41
|
+
`SalesOrders.shipToAddressId`) and `States`, selecting `line1`, `line2`, `city`, the
|
|
42
|
+
state code, and `zip`. The address is captured from the first result row and set as a
|
|
43
|
+
top-level `shippingAddress` on the CashSale — an `Address` object with `override=true`
|
|
44
|
+
and `country='_unitedStates'`.
|
|
45
|
+
|
|
46
|
+
This fires for **every** order that has a `shipToAddressId` set — it is **not** restricted
|
|
47
|
+
to the Whole Home Warranty SKU (1429124), because `shippingAddress` is a top-level field on
|
|
48
|
+
the CashSale, not a per-line concern. (The business driver was Whole Home Warranty orders
|
|
49
|
+
needing the property address in NetSuite so ops / account teams can determine regional
|
|
50
|
+
pricing, but the field applies to all orders.)
|
|
51
|
+
|
|
52
|
+
### Line item NetSuite internalId resolution
|
|
53
|
+
Each line item's NetSuite `RecordRef` internalId is now resolved as:
|
|
54
|
+
|
|
55
|
+
1. `Items.c_netsuiteInternalItemId` when populated, else
|
|
56
|
+
2. the hard-coded `partNumber` integer (1429122 / 1429123 / 1429124) as a fallback.
|
|
57
|
+
|
|
58
|
+
The query was updated to `SELECT Items.c_netsuiteInternalItemId` alongside `partNumber`.
|
|
59
|
+
The fallback exists because ops has not yet populated `c_netsuiteInternalItemId`.
|
|
60
|
+
|
|
61
|
+
### `c_netsuiteInternalItemId` field declaration
|
|
62
|
+
`Client_Rate.Items` has a `c_netsuiteInternalItemId INT` column that was not declared in the
|
|
63
|
+
ORM. It is now declared as `FIELD_INTEGER` on `_Model_Rate_Item`, so the column is exposed
|
|
64
|
+
through the API and can be set via the UI.
|
|
65
|
+
|
|
66
|
+
## Production data state (as of 2026-06-25)
|
|
67
|
+
|
|
68
|
+
- **All 39 Rate SalesOrders in `Client_Rate.SalesOrders` have `shipToAddressId = NULL`.**
|
|
69
|
+
The new code path only populates `shippingAddress` once the frontend starts setting
|
|
70
|
+
`shipToAddressId` on order creation. For the historical backfill export, the address was
|
|
71
|
+
pulled from the contact's `primaryContactAddressId → ContactAddresses → Addresses` join
|
|
72
|
+
instead — a one-off, not the going-forward path.
|
|
73
|
+
- **`Items.c_netsuiteInternalItemId` is NULL for all three Rate SKUs** (partNumbers
|
|
74
|
+
1429122, 1429123, 1429124). Until ops populates it, the partNumber-integer fallback is the
|
|
75
|
+
active internalId path.
|
|
76
|
+
|
|
77
|
+
## Gotchas / known issues
|
|
78
|
+
|
|
79
|
+
- **`shippingAddress` only flows when `shipToAddressId` is set.** No live order has it set
|
|
80
|
+
yet (all 39 are NULL), so the new address path is effectively dormant until the frontend
|
|
81
|
+
populates the field on creation. Don't assume the CashSale carries an address from current
|
|
82
|
+
prod data.
|
|
83
|
+
- **internalId fallback is partNumber, not the NS internalId.** Until ops backfills
|
|
84
|
+
`c_netsuiteInternalItemId`, CashSale lines reference the partNumber integer as the NS item
|
|
85
|
+
internalId. These happen to coincide for Rate today, but the clean path is the column.
|
|
86
|
+
- **Historical backfill used a different address source** (contact primary address), not
|
|
87
|
+
`shipToAddressId` — do not confuse the one-off backfill join with the going-forward order
|
|
88
|
+
path.
|
|
89
|
+
|
|
90
|
+
## Change history
|
|
91
|
+
|
|
92
|
+
- 2026-06-25 — Added ship-to address (`LEFT JOIN Addresses`/`States`) to the CashSale as a top-level `shippingAddress` (override=true, US); resolved line-item NS internalId from `c_netsuiteInternalItemId` with partNumber fallback; declared `c_netsuiteInternalItemId` on `_Model_Rate_Item`. Confirmed all 39 prod orders have NULL `shipToAddressId` and all 3 SKUs have NULL `c_netsuiteInternalItemId` (rgirish)
|
|
@@ -10,12 +10,13 @@ project: SAML SSO Gateway
|
|
|
10
10
|
client: rate
|
|
11
11
|
type: profile
|
|
12
12
|
status: active
|
|
13
|
-
updated: 2026-06-
|
|
13
|
+
updated: 2026-06-25
|
|
14
14
|
owners: ["rgirish", "bala"]
|
|
15
15
|
files: []
|
|
16
16
|
related:
|
|
17
17
|
- clients/rate/features/saml-sso.md
|
|
18
18
|
- clients/rate/features/monthly-reconciliation-report.md
|
|
19
|
+
- clients/rate/features/netsuite-cashsale-export.md
|
|
19
20
|
---
|
|
20
21
|
|
|
21
22
|
## Summary
|
package/package.json
CHANGED