toga-ai 1.0.928 → 1.0.929
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/library/features/netsuite-suiteql-api-reference.md +25 -2
- package/knowledge/1.0/apps/tools/INDEX.md +1 -0
- package/knowledge/1.0/apps/tools/features/force-logout.md +46 -0
- package/knowledge/1.0/apps/tools/features/worker2-job-bridge.md +13 -2
- package/knowledge/1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md +28 -1
- package/knowledge/2.0/apps/toga2-commerce/features/force-logout-on-deployment.md +5 -0
- package/knowledge/2.0/apps/toga25-supply/features/force-logout-on-deployment.md +5 -1
- package/knowledge/INDEX.md +2 -2
- package/knowledge/clients/nychh/INDEX.md +2 -2
- package/knowledge/clients/nychh/features/transfer-order-notification-emails.md +80 -1
- package/knowledge/clients/nychh/profile.md +2 -2
- package/package.json +1 -1
|
@@ -6,8 +6,8 @@ project: Library
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: ["dfranks", "jcardinal", "kyalamarthi", "ajean", "rgirish"]
|
|
9
|
+
updated: 2026-10-01
|
|
10
|
+
owners: ["dfranks", "jcardinal", "kyalamarthi", "ajean", "rgirish", "bala"]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/api/netsuite/rest.php
|
|
13
13
|
- library/ssl/netsuite_ec_key.pem
|
|
@@ -106,6 +106,7 @@ Type codes (`transaction.type`):
|
|
|
106
106
|
|
|
107
107
|
Status codes (`transaction.status` letter):
|
|
108
108
|
**Sales Orders:** `A` Pending Approval · `B` Pending Fulfillment · `C` Cancelled · `D` Partially Fulfilled · `E` Pending Billing/Partially Fulfilled · `F` Pending Billing · `G` Billed · `H` Closed. **"Open" sales orders = `B`, `D`, `E`, `F`.** Note `listSalesOrders()` returns **all** statuses for a window (no server-side status filter) — callers must filter.
|
|
109
|
+
**Item Fulfillments (`ItemShip`):** `A` Picked · `B` Packed · `C` Shipped.
|
|
109
110
|
**Invoices:** `A` Open · `B` Paid In Full. **Credit Memos:** `A` Open · `B` Fully Applied. **Cash Sales:** `A` Unapproved Payment · `C` Deposited. **Cash Refunds:** `Y` (the only status; empty string in SOAP).
|
|
110
111
|
|
|
111
112
|
Header columns present (✓ confirmed in SuiteQL): `id`, `tranid`, `trandate`, `lastmodifieddate`, `type`, `status`, `entity`, `employee`, `leadsource`, `terms`, `memo`, `otherrefnum`, `foreigntotal`/`total` (both return the same value), `foreignamountunpaid` (AR balance — invoices only; NULL for SO/credit memo/cash sale), `duedate`, `shipmethod`, `opportunity`, `shippingaddress` (numeric id → join `transactionShippingAddress`), and custom fields (`CUSTBODY_END_CUSTOMER`, `CUSTBODY_STOCKING_ORDER`, …).
|
|
@@ -114,6 +115,25 @@ Header fields NOT in SuiteQL (✗ — require a per-id REST GET): `shippingCost`
|
|
|
114
115
|
|
|
115
116
|
Tracking numbers in SuiteQL (verified 2026-09-28): `transaction.trackingnumberlist` (on the IF) / `linkedtrackingnumberlist` (on the SO) return **`trackingnumber` record ids**, not the numbers — resolve with `SELECT id, trackingnumber FROM trackingnumber WHERE id IN (...)`. `trackingnumbermap` returned nothing. The REST per-id GET below also has them.
|
|
116
117
|
|
|
118
|
+
### Order → the transactions created FROM it: `nexttransactionlink`
|
|
119
|
+
|
|
120
|
+
**`createdfrom` is a `transactionline` column — SuiteQL rejects it on `transaction`.** There is no
|
|
121
|
+
header-level createdfrom to filter on. To find an order's fulfillments (or any child document), join
|
|
122
|
+
`nexttransactionlink`:
|
|
123
|
+
|
|
124
|
+
```sql
|
|
125
|
+
SELECT ntl.previousdoc, ntl.nextdoc, t.tranid, t.type, t.status
|
|
126
|
+
FROM nexttransactionlink ntl
|
|
127
|
+
JOIN transaction t ON t.id = ntl.nextdoc
|
|
128
|
+
WHERE ntl.previousdoc IN (<order internal ids>)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Pair it with the `ItemShip` status letters above to see how far each fulfillment got. This is how a
|
|
132
|
+
**NYCHH transfer order** is traced: `Client_Nychh.TransferOrders.c_netsuiteInternalSalesOrderId`
|
|
133
|
+
holds a NetSuite **sales order** internal id (a $0 SO *is* the transfer order — see
|
|
134
|
+
[the outbound push](../../../2.0/apps/worker2/features/netsuite-transferorder-outbound-push.md)), so
|
|
135
|
+
`previousdoc` is that id and the `ItemShip` rows that come back are its fulfillments.
|
|
136
|
+
|
|
117
137
|
### `transactionline` table
|
|
118
138
|
|
|
119
139
|
Key columns (confirmed present): `transaction`, `linesequencenumber`, `uniquekey`, `id`, `item`, `itemtype`, `mainline`, `quantity`, `foreignamount`, `rate`, `costestimate`, `class`, `memo`, `quantitybilled`, `quantitycommitted`, `quantitypicked`, `quantityshiprecv`, `createdfrom`, `entity` (line-level customer), `location`, `subsidiary`.
|
|
@@ -271,4 +291,7 @@ SalesOrd per-id GET counts by `lastmodifieddate` window: ~16,600 (90d) / ~61,900
|
|
|
271
291
|
- `../../worker/features/forecast2-netsuite-reconciliation.md` — the audit/trueup tooling that consumes this API.
|
|
272
292
|
|
|
273
293
|
## Change history
|
|
294
|
+
- 2026-10-01 — Added `nexttransactionlink` as the order → child-document join (`createdfrom` is a
|
|
295
|
+
`transactionline` column and is rejected on `transaction`), and the `ItemShip` status letters
|
|
296
|
+
(`A` picked / `B` packed / `C` shipped). Found tracing NYCHH transfer orders. (bala)
|
|
274
297
|
- 2026-09-30 — Gotcha: `inventorybalance.quantityavailable` ignores SO commitments; use `aggregateitemlocation`. (rgirish)
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
| [Tools — Developers Folder (UUID & Password Generators)](features/developer-tools.md) | Tools "Developers" folder — the Generate UUID and Generate Password tools (reference example of the one-tool-per-`mvc/<route>/get.php` pattern); open for the ge |
|
|
13
13
|
| [/emails Email Queue Review (Tools → 2.0 Logs_<Client>.Email)](features/email-queue-review.md) | Staff page to browse any 2.0 client's `Logs_<Client>.Email` queue and release/reject `HELD` review-hold emails. |
|
|
14
14
|
| [/errors Curation Console (Tools → shared Core Logs DB)](features/errors-curation-console.md) | Internal-only 1.0 Tools triage/curation screen (`/errors`) for the 2.0 Issue/Event error pipeline, reading the shared Core Logs DB via `db_toga2logs`; curates, |
|
|
15
|
+
| [Force Logout (tools /developers/force-logout → worker2 sets META_LAST_REFRESH_DATETIME)](features/force-logout.md) | Tools page that signs every 2.0 front-end user out after a big deploy by changing Core parameter `META_LAST_REFRESH_DATETIME` through a worker2 job; open to tri |
|
|
15
16
|
| [GitHub Audit (monthly code-churn report)](features/github-audit.md) | Internal monthly code-churn report at `/developers/github-audit` (lines added/removed across every `agilantsolutions` repo for one month); open for the two-phas |
|
|
16
17
|
| [Legacy Email Notifier (tools /email-migration/notify)](features/legacy-email-notifier.md) | SSO-gated admin tool at `/email-migration/notify` that emails employees a digest of mail still hitting their legacy domains (asks senders to switch to `@togatec |
|
|
17
18
|
| [Tools — Multi-Client SQL (per-client-DB SQL fan-out)](features/multi-client-sql.md) | Tools `/developers/multi-client-sql` — fans one SQL change-set across a live list of 2.0 client/log/archive/module DBs into a copy/download script (never execut |
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Force Logout (tools /developers/force-logout → worker2 sets META_LAST_REFRESH_DATETIME)
|
|
3
|
+
framework: "1.0"
|
|
4
|
+
repo: tools
|
|
5
|
+
project: Tools
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-10-01
|
|
10
|
+
owners: [bala]
|
|
11
|
+
files:
|
|
12
|
+
- tools/_/app/developers/forcelogout.php
|
|
13
|
+
- tools/mvc/developers/force-logout/get.php
|
|
14
|
+
- tools/mvc/developers/force-logout/post.php
|
|
15
|
+
- tools/_/app/nav.php
|
|
16
|
+
- worker2/Worker/Platform/ForceLogout.php
|
|
17
|
+
related:
|
|
18
|
+
- ./worker2-job-bridge.md
|
|
19
|
+
- ./developer-tools.md
|
|
20
|
+
- ../../../2.0/apps/toga2-commerce/features/force-logout-on-deployment.md
|
|
21
|
+
- ../../../2.0/apps/toga25-supply/features/force-logout-on-deployment.md
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
Tools page that signs every 2.0 front-end user out after a big deploy by changing Core parameter `META_LAST_REFRESH_DATETIME` through a worker2 job; open to trigger a force logout, or before touching `Core.Parameters` from code.
|
|
25
|
+
|
|
26
|
+
## Summary
|
|
27
|
+
|
|
28
|
+
Page `/developers/force-logout` (nav: Developers → Force Logout, persona **Development Team** only). Dev picks a date/time, presses Update. Replaces the old hand-written INSERT/UPDATE on `Core.Parameters` in prod. toga2-commerce and toga25-supply poll that key and log the user out on **any** change; the value itself does not matter (see the two FE docs in `related`).
|
|
29
|
+
|
|
30
|
+
## How it works
|
|
31
|
+
|
|
32
|
+
1. `post.php` actions: `load` (current value), `queue-update`, `job-status`.
|
|
33
|
+
2. `App_Developers_ForceLogout::queueLogoutTimeUpdate()` validates strict `Y-m-d H:i:s` and **refuses a value equal to the stored one** (no change = no logout). Logs via `error_log` with the user uuid (audit).
|
|
34
|
+
3. Queues `Platform/ForceLogout/SetLogoutTime` with `App_Worker::enqueue` (see [worker2-job-bridge](./worker2-job-bridge.md)).
|
|
35
|
+
4. Page polls `job-status` every 2s: reads `WorkerJobs.isSuccess/dtStarted/output` scoped to this action only; shows only the first line of a failure, file paths stripped.
|
|
36
|
+
5. worker2 `_Worker_Platform_ForceLogout::SetLogoutTime(value, requestedByUserUuid = '')` loads `_Model_Core_Parameter` by the hardcoded key, sets `value`, `save()`. Turns the query cache off first, then calls `_Database::transactionCommit(database: _underscore::DB_CORE)` explicitly.
|
|
37
|
+
|
|
38
|
+
## Gotchas
|
|
39
|
+
|
|
40
|
+
- **⚠ Never take the parameter key from input.** `Core.Parameters` also holds api2 secrets (`API_SECRET_ACCESS_TOKEN` etc). Key is a constant in both tools and worker2.
|
|
41
|
+
- **`createFromFormat` alone accepts `2026-02-31`** (rolls over). Validate with a round trip: `format()` of the parsed date must equal the input.
|
|
42
|
+
- **Works on prod tools only today.** Only `config.production.ini` has both `[worker]` (queue_url/queue_region) and `[database_toga2core]`. alpha has Core but no `[worker]`; no beta tools config has a worker queue.
|
|
43
|
+
- **Deploy worker2 before tools**, or the first job fails with an unknown class.
|
|
44
|
+
|
|
45
|
+
## Change history
|
|
46
|
+
- 2026-10-01 — Built the page + worker2 action, replacing hand-run SQL on Core.Parameters. TRUE-82414. (bala)
|
|
@@ -6,11 +6,12 @@ project: Tools
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: [mhammontree]
|
|
9
|
+
updated: 2026-10-01
|
|
10
|
+
owners: [mhammontree, bala]
|
|
11
11
|
files:
|
|
12
12
|
- tools/_/app/worker.php
|
|
13
13
|
related:
|
|
14
|
+
- ./force-logout.md
|
|
14
15
|
- ../../../clients/compass-usa/features/people-file-import-admin-page.md
|
|
15
16
|
- ./mvc-data-access-patterns.md
|
|
16
17
|
- ../../../2.0/apps/worker2/architecture.md
|
|
@@ -63,6 +64,16 @@ empty result — because the wrapper format is worker2's, not a contract.
|
|
|
63
64
|
`maxExecutionTime`. Never make a page wait for long work — poll, don't block.
|
|
64
65
|
- **Deploy worker2 first.** A Tools page that calls a new action is broken until the action
|
|
65
66
|
exists.
|
|
67
|
+
- **Most tools configs cannot enqueue.** Only `config.production.ini` has a `[worker]` section
|
|
68
|
+
(queue_url/queue_region); alpha and beta tools have none.
|
|
69
|
+
|
|
70
|
+
## Local testing (tools → worker2)
|
|
71
|
+
|
|
72
|
+
1. Tools picks `config.<ENVIRONMENT>.ini` from the `ENVIRONMENT` env var; `config.dev-local.ini` is git-ignored and needs `[worker]` + the DB sections.
|
|
73
|
+
2. Tools `composer install` fails on local PHP 8.1 (`symfony/filesystem` needs 8.2). Workaround: a git-ignored `tools/vendor/autoload.php` that requires worker2's vendor autoload.
|
|
74
|
+
3. The AWS SDK ignores the host in `QueueUrl`. Set `AWS_ENDPOINT_URL_SQS` to a local fake SQS that POSTs the message body to `http://worker2/` (Laragon vhost).
|
|
75
|
+
4. Dev-bypass login must use a `Client_True` email (e.g. `@togatech.com`, not `@goagilant.com`).
|
|
66
76
|
|
|
67
77
|
## Change history
|
|
78
|
+
- 2026-10-01 — Added the local tools→worker2 test setup and the prod-only `[worker]` config fact (TRUE-82414). (bala)
|
|
68
79
|
- 2026-09-23 — Created from TRUE-82250: documented the three `enqueue()` outcomes, the log-line-wrapped `output`, and the CTO decision that tools never speaks SFTP (phpseclib 1.x sha1-only KEX + credential duplication). (mhammontree)
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-10-01
|
|
10
10
|
owners: ["dfranks", "bala", "jcardinal", "mhammontree", "snaredla", "rgirish"]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/toga2/netsuite/common_sync_togasupply.php
|
|
@@ -106,6 +106,10 @@ The value is `<seconds>-<STATE>`. Three readings, and only the first is a real f
|
|
|
106
106
|
|
|
107
107
|
- **`1-RUNNING`** (window collapsed ÷3 to 1s) = throwing on **every** run = real freeze. Go read the
|
|
108
108
|
error (`Logs.Event` by clientId → file:line → `Logs_<Client>.Api` on the transactionId).
|
|
109
|
+
**⚠ Nothing alerts on the way down.** `common_sync_togasupply.php:675-681` shrinks the window
|
|
110
|
+
÷3 and the paired *"Interval is already at minimum but previous execution still failed to
|
|
111
|
+
finish"* throw is **commented out** (“so the script can continue”), so a phase degrades
|
|
112
|
+
864000 → … → **1 second** silently and never climbs back. Only the `Parameters` value shows it.
|
|
109
113
|
- **A large-window `*-RUNNING`** (e.g. `864000-RUNNING`, `288000-RUNNING`) = **usually** healthy
|
|
110
114
|
mid-run or a deliberate pause/backfill. **⚠ CORRECTED 2026-09-18 — this is NOT sufficient on its
|
|
111
115
|
own, and the old "NOT stuck, do not fix it" wording caused a real miss.** NYCHH showed
|
|
@@ -577,6 +581,23 @@ purchase orders, invoices and item receipts were disabled again on 2026-09-03**
|
|
|
577
581
|
is exactly why every NYCHH cursor stops at **2026-09-03**. Before concluding "the sync is broken",
|
|
578
582
|
check the launcher's toggles at the relevant commit.
|
|
579
583
|
|
|
584
|
+
### ⚠ NYCHH syncs ONCE A DAY — and its live state is in the CLIENT db, not Core
|
|
585
|
+
|
|
586
|
+
Two traps that cost a diagnosis, both NYCHH-specific:
|
|
587
|
+
|
|
588
|
+
- **The `*/5` schedule is not the real cadence.** `cron.worker.sync.json` registers
|
|
589
|
+
`sync_togasupply_hh.php` every 5 minutes like everyone else, but the wrapper calls
|
|
590
|
+
**`App_Framework::cronInitialization(86400)`** (“THIS CRON JOB IS INTENDED TO BE RUN EVERY DAY”),
|
|
591
|
+
so 287 of every 288 invocations exit immediately. Anything NetSuite ships after the morning run
|
|
592
|
+
waits until the next day. Account for that before calling a late import “broken” — and when
|
|
593
|
+
rewinding a cursor, run the wrapper by hand rather than waiting (the 10-day ceiling above means a
|
|
594
|
+
deep rewind would otherwise take weeks).
|
|
595
|
+
- **Read `Client_Nychh.Parameters`, never the `NETSUITE_HH_*` keys in `Core`.** Core still carries
|
|
596
|
+
legacy `NETSUITE_HH_*` rows whose last-sync dates sit in **May 2026**; they are **stale leftovers
|
|
597
|
+
that nothing writes**. The live cursors and execution modes are
|
|
598
|
+
`NETSUITE_LAST_SYNC_CURSOR_*` / `NETSUITE_EXECUTION_MODE_*` in the client DB (see *Data model*).
|
|
599
|
+
Reading the Core copies leads straight to a wrong diagnosis.
|
|
600
|
+
|
|
580
601
|
### Replaying one order means rewinding the cursor by hand
|
|
581
602
|
|
|
582
603
|
The cursor is a **seek** position (`<lastmodifieddate>|<internalId>`, see above), so an order whose
|
|
@@ -2044,6 +2065,12 @@ library (or vice versa) crashes GroWrk and Adyen on their next sync run.
|
|
|
2044
2065
|
[NetSuite Sync Alert Monitor](../../library/features/netsuite-sync-alert-monitor.md).
|
|
2045
2066
|
|
|
2046
2067
|
## Change history
|
|
2068
|
+
- 2026-10-01 — NYCHH diagnosis traps recorded: the wrapper is gated to **once a day** by
|
|
2069
|
+
`cronInitialization(86400)` despite its `*/5` schedule entry, and the live cursors/modes live in
|
|
2070
|
+
`Client_Nychh.Parameters` — the `NETSUITE_HH_*` keys in `Core` are stale (May 2026) leftovers.
|
|
2071
|
+
Also noted that the “interval already at minimum” throw is **commented out**
|
|
2072
|
+
(`common_sync_togasupply.php:675-681`), so a phase can decay to a 1-second window with no alert.
|
|
2073
|
+
(bala)
|
|
2047
2074
|
- 2026-09-30 — Closed-order rule v3: drop only when every line is closed and nothing was created from it ([doc](../../library/features/netsuite-closed-order-rule.md)). Sync never auto-creates warehouse Locations (`throwIfUnknownWarehouse`); unit location from the line, not `custitemnumber_location`. Added `process_netsuite_records.php` and the stuck-sync diagnosis path. (rgirish)
|
|
2048
2075
|
- 2026-09-24 — TO↔PO links: two-direction reconcile (vendor POs no longer upstream; retireStale moves downstream SO links to 326/330); fixed SO downstream line-link deletes on PO re-sync; new `process_netsuite_order.php` + `NETSUITE_SYNC_LOOKUPS_ONLY`. (jcardinal)
|
|
2049
2076
|
- 2026-09-22 — **The `Logs.Issue` 589 doom-loop's framework root cause is now fully explained and has
|
|
@@ -8,6 +8,7 @@ type: feature
|
|
|
8
8
|
status: active
|
|
9
9
|
updated: 2026-10-01
|
|
10
10
|
owners: [apeterson]
|
|
11
|
+
owners: [apeterson, bala]
|
|
11
12
|
files:
|
|
12
13
|
- toga2-commerce/src/hooks/useDeploymentGuard.tsx
|
|
13
14
|
- toga2-commerce/src/lib/deploymentVersion.ts
|
|
@@ -15,6 +16,7 @@ files:
|
|
|
15
16
|
- toga2-commerce/src/components/AuthLayout/AuthLayout.tsx
|
|
16
17
|
- toga2-commerce/src/lib/authStorage.ts
|
|
17
18
|
related:
|
|
19
|
+
- ../../../../1.0/apps/tools/features/force-logout.md
|
|
18
20
|
- ./sso-redirect-and-session-gating.md
|
|
19
21
|
- ./catalog-cache-freshness.md
|
|
20
22
|
- ../../toga25-supply/features/force-logout-on-deployment.md
|
|
@@ -32,6 +34,8 @@ Ported from toga25-supply (`force-logout-on-deployment`). Two commerce-only diff
|
|
|
32
34
|
|
|
33
35
|
Reached commerce `_production` only in merge `26e2fae7` (2026-09-30, TRUE-81991); supply had it earlier. That gap is why one parameter bump logged supply users out and commerce users not at all.
|
|
34
36
|
|
|
37
|
+
**Trigger:** change the value with tools `/developers/force-logout` ([force-logout](../../../../1.0/apps/tools/features/force-logout.md)), not hand SQL.
|
|
38
|
+
|
|
35
39
|
## How it works
|
|
36
40
|
|
|
37
41
|
1. One query on `META_LAST_REFRESH_DATETIME`, `enabled` only while authenticated, `staleTime: 0`, `refetchOnWindowFocus: true`, `refetchInterval` **5 minutes**.
|
|
@@ -76,4 +80,5 @@ To also fire on an in-app navigation: a `useLocation()` effect calling `refetch(
|
|
|
76
80
|
|
|
77
81
|
## Change history
|
|
78
82
|
- 2026-10-01 — Documented the exact refetch triggers (mount / tab-visibility / 5-min poll; **not** route change), the background-pause behavior, the first-bump no-op, the ignored `key=` param, and the commerce-vs-supply query config difference. Route-change refetch deferred. (apeterson)
|
|
83
|
+
- 2026-10-01 — Trigger is now the tools Force Logout page (TRUE-82414). (bala)
|
|
79
84
|
- 2026-09-25 — Built for commerce: moved out of `AuthContext` into `<DeploymentGuard />` so a toaster can be shown; added 5-min poll + focus refetch; baseline key added to `AUTH_STORAGE_KEYS`. TRUE-81991. (apeterson)
|
|
@@ -7,12 +7,13 @@ client: shared
|
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
9
|
updated: 2026-10-01
|
|
10
|
-
owners: [apeterson]
|
|
10
|
+
owners: [apeterson, bala]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-supply/src/hooks/useDeploymentGuard.tsx
|
|
13
13
|
- toga25-supply/src/App.tsx
|
|
14
14
|
related:
|
|
15
15
|
- ../../toga2-commerce/features/force-logout-on-deployment.md
|
|
16
|
+
- ../../../../1.0/apps/tools/features/force-logout.md
|
|
16
17
|
---
|
|
17
18
|
|
|
18
19
|
Force-signs-out every user when a deploy bumps a Core parameter, so session + caches reset to the new build; open when debugging surprise logouts or `useDeploymentGuard`.
|
|
@@ -21,6 +22,8 @@ Force-signs-out every user when a deploy bumps a Core parameter, so session + ca
|
|
|
21
22
|
|
|
22
23
|
On large deploys the backend bumps Core parameter `META_LAST_REFRESH_DATETIME`. The FE watches it and, when it changes mid-session, force-signs-out every logged-in user so session, `localStorage`, and React Query cache (including the persisted `supply-chain-query-cache`) reset cleanly. Implemented as a headless `<DeploymentGuard />` driven by `useDeploymentGuard`, mounted inside the `AuthProvider` / `ToasterProvider` tree in `App.tsx` — **above** `<AppRouter />`, so the hook cannot use `useLocation()`.
|
|
23
24
|
|
|
25
|
+
**Trigger:** change the value with tools `/developers/force-logout` ([force-logout](../../../../1.0/apps/tools/features/force-logout.md)), not hand SQL.
|
|
26
|
+
|
|
24
27
|
## How it works
|
|
25
28
|
|
|
26
29
|
1. **Poll the Core parameter.** TanStack `useQuery` reads `META_LAST_REFRESH_DATETIME` via `apiGet("/core-parameters", { fields: ["value"], key: "META_LAST_REFRESH_DATETIME" })`, reading `response.data.coreParameters` (tolerant of an array or a single object). Query: `enabled` only while authenticated, `staleTime: 0`, `refetchOnWindowFocus: true`, `refetchInterval` 5 minutes.
|
|
@@ -32,5 +35,6 @@ On large deploys the backend bumps Core parameter `META_LAST_REFRESH_DATETIME`.
|
|
|
32
35
|
**When it actually fires, the first-bump no-op, and the ignored `key=` param are identical here** — documented once in [commerce's force-logout doc](../../toga2-commerce/features/force-logout-on-deployment.md) (sections *What actually triggers a refetch* and *Gotchas*). Supply-only difference: `App.tsx` leaves `refetchOnWindowFocus` at React Query's default `true` app-wide, so every on-screen query refetches on tab visibility — not just this one. Sign-out timing is the same in both apps.
|
|
33
36
|
|
|
34
37
|
## Change history
|
|
38
|
+
- 2026-10-01 — Trigger is now the tools Force Logout page (TRUE-82414). (bala)
|
|
35
39
|
- 2026-10-01 — Recorded that the guard sits above the router (blocks `useLocation()`); moved the shared trigger/gotcha detail to the commerce doc rather than duplicating it. (apeterson)
|
|
36
40
|
- 2026-09-16 — Initial implementation. (apeterson)
|
package/knowledge/INDEX.md
CHANGED
|
@@ -15,7 +15,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
15
15
|
- **test** (Test) — 16 doc(s) → [1.0/apps/test/INDEX.md](1.0/apps/test/INDEX.md)
|
|
16
16
|
- **toga** (TOGa) — 3 doc(s) → [1.0/apps/toga/INDEX.md](1.0/apps/toga/INDEX.md)
|
|
17
17
|
- **store** (Store) — 3 doc(s) → [1.0/apps/store/INDEX.md](1.0/apps/store/INDEX.md)
|
|
18
|
-
- **tools** (Tools) —
|
|
18
|
+
- **tools** (Tools) — 24 doc(s) → [1.0/apps/tools/INDEX.md](1.0/apps/tools/INDEX.md)
|
|
19
19
|
|
|
20
20
|
## 2.0 framework
|
|
21
21
|
|
|
@@ -62,7 +62,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
62
62
|
- **ManageLife** (`managelife`) → [clients/managelife/INDEX.md](clients/managelife/INDEX.md)
|
|
63
63
|
- **Northwell Health** (`northwell`) → [clients/northwell/INDEX.md](clients/northwell/INDEX.md)
|
|
64
64
|
- **New York City Department of Education** (`nycdoe`) → [clients/nycdoe/INDEX.md](clients/nycdoe/INDEX.md)
|
|
65
|
-
- **NYC Health
|
|
65
|
+
- **NYC Health + Hospitals** (`nychh`) → [clients/nychh/INDEX.md](clients/nychh/INDEX.md)
|
|
66
66
|
- **Office Depot** (`office-depot`) → [clients/office-depot/INDEX.md](clients/office-depot/INDEX.md)
|
|
67
67
|
- **PC Matic B2B Client Profile** (`pcmaticb2b`) → [clients/pcmaticb2b/INDEX.md](clients/pcmaticb2b/INDEX.md)
|
|
68
68
|
- **Prudential Financial** (`prudential`) → [clients/prudential/INDEX.md](clients/prudential/INDEX.md)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Client: NYC Health
|
|
1
|
+
# Client: NYC Health + Hospitals `nychh`
|
|
2
2
|
|
|
3
3
|
| Doc | Framework | Summary |
|
|
4
4
|
|-----|-----------|---------|
|
|
@@ -10,4 +10,4 @@
|
|
|
10
10
|
| [NYCHH transfer-order 2.0 model — inventory quantities + V2 field enablement](features/transfer-order-inventory-quantities.md) | 2.0 | NYCHH 2.0 transfer-order model — two-branch inventory quantities + the model/ACL work that lets a TransferOrder POST; open when NYCHH `_qty*` reads 0 or a Trans |
|
|
11
11
|
| [NYCHH TransferOrder → NetSuite push (location roles, interceptor row 312, metadata state)](features/transfer-order-netsuite-push.md) | 2.0 | NYCHH is the **first and only** client on the outbound transfer-order push. |
|
|
12
12
|
| [NYCHH transfer-order notification emails (Submitted / Packed / Shipped)](features/transfer-order-notification-emails.md) | 2.0 | NYC Health + Hospitals (H+H) gets three notification emails for a transfer order raised in **TOGa Supply**: **Submitted**, **Packed** and **Shipped**. |
|
|
13
|
-
| [NYC Health
|
|
13
|
+
| [NYC Health + Hospitals](profile.md) | 2.0 | NYC Health & Hospitals (NYCHH) client profile — open for supply-chain / TransferOrder / NetSuite-sync work on prod schema `Client_Nychh`. |
|
|
@@ -6,13 +6,14 @@ project: Worker
|
|
|
6
6
|
client: nychh
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-10-01
|
|
10
10
|
owners: ["bala", "rgirish", "jcardinal"]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Client/Nychh/TransferOrderEmails.php
|
|
13
13
|
- _underscore/Model/Nychh/TransferOrder.php
|
|
14
14
|
- _underscore/Model/Nychh/ItemFulfillment.php
|
|
15
15
|
- dbchanges2/Client_Nychh/2026-09-15a - TransferOrderEmails.sql
|
|
16
|
+
- dbchanges2/Client_Nychh/2026-09-30a - TransferOrderPackedAssetTagException.sql
|
|
16
17
|
- dbchanges2/Client_Nychh/2026-09-15b - TransferOrderEmailGoLiveRecipient.sql
|
|
17
18
|
- dbchanges2/Core/2026-09-16a - NychhTransferOrderEmailsCron.sql
|
|
18
19
|
related:
|
|
@@ -47,6 +48,8 @@ Three rules carry most of the risk:
|
|
|
47
48
|
2. **Only orders raised in TOGa Supply may be announced.** `TransferOrders.createdByUserId` is the
|
|
48
49
|
discriminator; without it H+H would be mailed about every order the NetSuite sync ever imported.
|
|
49
50
|
3. **A failed send throws — it is never swallowed**, so nothing is ever marked sent that did not go.
|
|
51
|
+
4. **Packed (and shipped) are HELD while a line is short of asset tags** — ops is warned instead, and
|
|
52
|
+
the customer email sends itself once the tags land. See below.
|
|
50
53
|
|
|
51
54
|
Recipient is `DevTeam@togatech.com` while testing. `2026-09-15b` swaps it to
|
|
52
55
|
`EndUserDeviceLifecycle@nychhc.org` — run that file only after sign-off.
|
|
@@ -57,6 +60,8 @@ Recipient is `DevTeam@togatech.com` while testing. `2026-09-15b` swaps it to
|
|
|
57
60
|
- `SendSubmitted(string $transferOrderUuid)` — queued from the TransferOrder `postPut`.
|
|
58
61
|
- `SendFulfillment(string $itemFulfillmentUuid)` — queued from the ItemFulfillment hooks.
|
|
59
62
|
- `SendPending(bool $dryRun = true)` — the `*/5` backstop cron.
|
|
63
|
+
- `resolveStagesStillToAnnounce()` — which stages are still owed (see the late-shipped case).
|
|
64
|
+
- `readLinesMissingAssetTags()` / `sendPackedExceptionEmail()` — the asset-tag hold.
|
|
60
65
|
- `initialize()` registers the client DB before any query runs.
|
|
61
66
|
- Builders: `buildItemRowsHtml()`, `buildTrackingSectionHtml()`, `buildAssetTagTableHtml()`,
|
|
62
67
|
`escapeForEmailHtml()`; budget constant `MAXIMUM_EMAIL_BODY_BYTES`.
|
|
@@ -70,6 +75,8 @@ Recipient is `DevTeam@togatech.com` while testing. `2026-09-15b` swaps it to
|
|
|
70
75
|
- submitted `0f4a6c21-9d3e-4b87-a15c-6e2b8f70d914`
|
|
71
76
|
- packed `7b1d9e54-3c62-48af-b0d7-25f4a8c31e6b`
|
|
72
77
|
- shipped `c58f3a07-6b94-4d21-8e3f-914ad7b06c52`
|
|
78
|
+
- packed **exception** (internal warning, seeded by `2026-09-30a`)
|
|
79
|
+
`9d3b1e6a-47c2-4f85-b019-2ae85c7d4630`
|
|
73
80
|
- **`nychh_email_templates/*.html`** — ⚠ the HTML source of the three templates. **These files are
|
|
74
81
|
in no git repo** — they live only in the working folder. The dbchanges2 seed SQL is generated
|
|
75
82
|
*from* them, so the `EmailTemplates` rows in `2026-09-15a` are the only durable copy.
|
|
@@ -114,6 +121,14 @@ they must stay in step: the Submitted guard, the order read, and both `SendPendi
|
|
|
114
121
|
2. **Packed / Shipped** — the ItemFulfillment `postPost` / `postPut` queue `SendFulfillment` with
|
|
115
122
|
the fulfillment uuid. The stage reached is recorded on `c_transferOrderEmailStageSent`, with
|
|
116
123
|
`c_dtTransferOrderEmailSent` as the timestamp, so a fulfillment cannot re-announce a stage.
|
|
124
|
+
`resolveStagesStillToAnnounce()` decides what is owed: normally the one stage reached, but when a
|
|
125
|
+
fulfillment **arrives already shipped** and packed was never announced it returns
|
|
126
|
+
**`[packed, shipped]`** and both send in that order in one run. Before this the packed stage was
|
|
127
|
+
skipped outright — and **the asset-tag check lives on packed**, so a late shipped fulfillment
|
|
128
|
+
bypassed it completely. Real case: transfer order **291773** got Submitted and Shipped, never
|
|
129
|
+
Packed. The NYCHH NetSuite sync is a **daily** cron, so a fulfillment landing fully shipped is
|
|
130
|
+
normal, not an edge case — see
|
|
131
|
+
[the per-client sync](../../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md).
|
|
117
132
|
3. The **backstop cron** sweeps anything the hooks missed (below).
|
|
118
133
|
4. All three paths share the same markers, so nothing sends twice.
|
|
119
134
|
5. The worker renders the stored `EmailTemplates` body, fills `{orderItems}` and
|
|
@@ -137,6 +152,42 @@ PUT follows.** Fulfillment **2704** in production: header row at 20:33:09, lines
|
|
|
137
152
|
- It also picks up any send that threw (the marker was never written).
|
|
138
153
|
- Caps at **25 of each kind per run** (`MAXIMUM_EMAILS_PER_RUN`).
|
|
139
154
|
|
|
155
|
+
## ⚠ Missing asset tags HOLD the packed email (and warn ops instead)
|
|
156
|
+
|
|
157
|
+
Added 2026-09-30 (TRUE-82111). `SendFulfillment` runs `readLinesMissingAssetTags()` whenever
|
|
158
|
+
**packed** is one of the stages still owed. A fulfillment line counts as **short** when either:
|
|
159
|
+
|
|
160
|
+
- it has **fewer linked units than its quantity** (`unitsLinked < ItemFulfillmentItems.quantity`), or
|
|
161
|
+
- a linked unit has a **blank `Units.assetTag`**.
|
|
162
|
+
|
|
163
|
+
When any line is short, `SendFulfillment` **returns before writing
|
|
164
|
+
`c_transferOrderEmailStageSent`** — so packed stays pending and the backstop cron re-sends it by
|
|
165
|
+
itself the moment the tags arrive. **The hold covers shipped too**, because the early return happens
|
|
166
|
+
before the stage loop.
|
|
167
|
+
|
|
168
|
+
Instead, an **internal** warning goes out on template `9d3b1e6a-47c2-4f85-b019-2ae85c7d4630`
|
|
169
|
+
(`2026-09-30a`): **to `operations@togatech.com`, CC `HealthcareteamNYCHHC@togatech.com`** —
|
|
170
|
+
**never the client**. It lists one row per short line. Subject carries both numbers so it is
|
|
171
|
+
searchable:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
Action needed: asset tags missing on transfer order {transferOrderNumber}, fulfillment {itemFulfillmentNumber}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
- **It warns once per fulfillment, not every five minutes.** New custom field
|
|
178
|
+
**`c_dtTransferOrderPackedExceptionSent`** on `ItemFulfillments` (record **28**) is the marker;
|
|
179
|
+
`sendPackedExceptionEmail()` returns early when it is already set, and stamps it only after a
|
|
180
|
+
successful send.
|
|
181
|
+
- **⚠ Warned rows must still be SELECTED by the cron, just sorted last.**
|
|
182
|
+
`sendPendingFulfillmentEmails()` takes `LIMIT 25` per run, and a held fulfillment keeps
|
|
183
|
+
`c_transferOrderEmailStageSent` NULL forever, so 25 stuck rows would sit at the front of every run
|
|
184
|
+
and starve everyone else. The order is now
|
|
185
|
+
`ORDER BY (c_dtTransferOrderPackedExceptionSent IS NOT NULL) ASC, id ASC`. **Do not filter them
|
|
186
|
+
out** — that strands them permanently; they must still be picked up the run after the tags land.
|
|
187
|
+
|
|
188
|
+
See [the NYCHH asset-tag backfill](../../../2.0/apps/worker2/features/nychh-asset-tag-backfill.md)
|
|
189
|
+
for where the tags come from.
|
|
190
|
+
|
|
140
191
|
## Schema, registration and deploy order
|
|
141
192
|
|
|
142
193
|
`dbchanges2/Client_Nychh/2026-09-15a - TransferOrderEmails.sql` carries the whole feature:
|
|
@@ -145,6 +196,8 @@ PUT follows.** Fulfillment **2704** in production: header row at 20:33:09, lines
|
|
|
145
196
|
|---|---|
|
|
146
197
|
| `c_dtTransferOrderSubmittedEmailSent` | `TransferOrders` (record **312**) |
|
|
147
198
|
| `c_transferOrderEmailStageSent`, `c_dtTransferOrderEmailSent` | `ItemFulfillments` (record **28**) |
|
|
199
|
+
| `c_dtTransferOrderPackedExceptionSent` (`2026-09-30a`) | `ItemFulfillments` (record **28**) |
|
|
200
|
+
| The packed-exception `EmailTemplates` row + its TO/CC rows (`2026-09-30a`) | `Client_Nychh` |
|
|
148
201
|
| The three `EmailTemplates` rows + recipient rows | `Client_Nychh` |
|
|
149
202
|
| `CustomRecordFields` rows + `AclCustomFieldPermissions` grants (Base and API) | `Client_Nychh` |
|
|
150
203
|
| The record-312 **PUT** interceptor row | `ApiPayloadInterceptors` |
|
|
@@ -255,9 +308,22 @@ Cost of the split, stated so nobody is surprised later:
|
|
|
255
308
|
`Logs_Nychh.Email` without an inbox — that is how these were verified. See
|
|
256
309
|
[the email send pipeline](../../../2.0/apps/_underscore/features/email-send-pipeline.md).
|
|
257
310
|
- **The `nychh_email_templates/*.html` sources are not version-controlled** (see *Key files*).
|
|
311
|
+
- **⚠ An email that leaves the dev team never describes internal process.** The packed-exception
|
|
312
|
+
body first explained NetSuite, TOGa Supply, the sync and the five-minute retry; all of it was
|
|
313
|
+
removed on instruction. An email says **what happened, what to do, and that it is handled** —
|
|
314
|
+
nothing about which system, which cron, or how often we retry. Process detail belongs in code
|
|
315
|
+
comments and the dbchanges2 header. Applies to every template here, not just the exception one.
|
|
316
|
+
- **⚠ The ops / healthcare-team addresses have never actually been exercised.** On dev sandbox the
|
|
317
|
+
debug redirect rewrites every recipient, so the `operations@togatech.com` TO and the
|
|
318
|
+
`HealthcareteamNYCHHC@togatech.com` CC on the exception template are unproven until prod. Check the
|
|
319
|
+
first live exception email lands on both.
|
|
258
320
|
|
|
259
321
|
## Testing
|
|
260
322
|
|
|
323
|
+
**106 tests** as of 2026-09-30. Each fix is proven by **reverting it** and confirming the matching
|
|
324
|
+
tests fail. The five scenarios were then run end to end on **dev sandbox** against fulfillment
|
|
325
|
+
**898** / transfer order **286536**.
|
|
326
|
+
|
|
261
327
|
A local end-to-end harness (scratchpad, **not committed**) runs the **real** worker class against a
|
|
262
328
|
throwaway local schema built with `CREATE TABLE LIKE` from `Client_Nychh`, stubbing **only**
|
|
263
329
|
`_Query` / `_Database` / `_Model_Client_EmailTemplate`, and rendering the **real** template bodies
|
|
@@ -266,6 +332,19 @@ loaded out of the dbchanges2 file so an unresolved `{placeholder}` fails a test.
|
|
|
266
332
|
unescaped values and both detail-box defects were all found there, not in production.
|
|
267
333
|
|
|
268
334
|
## Change history
|
|
335
|
+
- 2026-10-01 — **Packed/shipped are now HELD when a fulfillment line is short of asset tags**
|
|
336
|
+
(TRUE-82111). A line is short when it has fewer linked units than its quantity **or** a linked unit
|
|
337
|
+
has a blank `assetTag`; `SendFulfillment` then returns without writing
|
|
338
|
+
`c_transferOrderEmailStageSent`, so the customer email sends itself once the tags land, and an
|
|
339
|
+
internal warning goes to `operations@togatech.com` (CC `HealthcareteamNYCHHC@togatech.com`) once
|
|
340
|
+
per fulfillment via new marker `c_dtTransferOrderPackedExceptionSent` and template
|
|
341
|
+
`9d3b1e6a-…` (`2026-09-30a`). Two fixes came with it: `resolveStagesStillToAnnounce()` now sends
|
|
342
|
+
**packed then shipped in one run** when a fulfillment arrives already shipped (transfer order
|
|
343
|
+
**291773** had Submitted + Shipped and no Packed — which also meant it skipped the asset-tag
|
|
344
|
+
check), and `sendPendingFulfillmentEmails()` sorts warned rows **last**
|
|
345
|
+
(`ORDER BY (c_dtTransferOrderPackedExceptionSent IS NOT NULL) ASC, id ASC`) so 25 held rows cannot
|
|
346
|
+
fill the `LIMIT 25` and starve every other fulfillment. Also recorded the rule that outbound emails
|
|
347
|
+
never describe internal process. (bala)
|
|
269
348
|
- 2026-09-24 — `TransferOrderEmails` reads PO numbers from upstream bridge 325 (column `purchaseOrderId` dropped). (jcardinal)
|
|
270
349
|
- 2026-09-23 — Submitted template put on review hold; missing REVIEW row → job failure note. (jcardinal)
|
|
271
350
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "NYC Health
|
|
2
|
+
title: "NYC Health + Hospitals"
|
|
3
3
|
framework: "2.0"
|
|
4
4
|
apps:
|
|
5
5
|
- _underscore
|
|
@@ -18,7 +18,7 @@ project: _Underscore
|
|
|
18
18
|
client: nychh
|
|
19
19
|
type: profile
|
|
20
20
|
status: active
|
|
21
|
-
updated: 2026-
|
|
21
|
+
updated: 2026-10-01
|
|
22
22
|
owners: ["jcardinal", "apeterson", "bala", "akhokhani"]
|
|
23
23
|
files:
|
|
24
24
|
- dbchanges2/Client_Nychh/2026-09-02a - TransferOrderNetsuitePushInterceptor.sql
|
package/package.json
CHANGED