toga-ai 1.0.951 → 1.0.952
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/2.0/apps/toga-blox/architecture.md +3 -3
- package/knowledge/2.0/apps/toga-blox/features/blox-authoring-defects.md +19 -1
- package/knowledge/2.0/apps/toga-blox/features/environment-badge.md +4 -2
- package/knowledge/2.0/apps/toga-blox/features/table.md +3 -2
- package/knowledge/2.0/apps/toga25-blox/INDEX.md +1 -1
- package/knowledge/2.0/apps/toga25-blox/architecture.md +8 -11
- package/knowledge/2.0/standards/frontend.md +5 -2
- package/knowledge/clients/nychh/features/transfer-order-notification-emails.md +63 -10
- package/knowledge/clients/nychh/profile.md +1 -0
- package/package.json +1 -1
|
@@ -6,8 +6,8 @@ project: TOGa Blox
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: [apeterson]
|
|
9
|
+
updated: 2026-10-05
|
|
10
|
+
owners: [apeterson, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- toga-blox/package.json
|
|
13
13
|
- toga-blox/src/components/index.ts
|
|
@@ -29,7 +29,7 @@ Shared React+TS component/template/API library (`@agilant/toga-blox`) reused by
|
|
|
29
29
|
|
|
30
30
|
`toga-blox` (npm package **`@agilant/toga-blox`**) is the shared React + TypeScript component / template / utility library reused across the 2.0 frontends: `toga2-view`, `toga2-supply`, `toga25-supply`, `toga2-commerce`, `toga2-hub`. Not a PHP framework repo — no `_underscore` backend. Filed under `2.0/` because every consumer is a 2.0 app, but structurally a standalone front-end package (`dependsOn: []`).
|
|
31
31
|
|
|
32
|
-
**
|
|
32
|
+
**Legacy — fixes only.** No new components and no Storybook work here. [toga25-blox](../toga25-blox/architecture.md) (`@toga/blox`, source-shared) replaces this library for **all** apps, eventually: new platform apps first, then these consumers. When an app moves, rewrite onto toga25-blox pieces — do not port this code wholesale. New shared UI goes to toga25-blox.
|
|
33
33
|
|
|
34
34
|
Ships: presentational + data-aware **components** (`src/components/`), **templates** wiring components into ready screens (`src/templates/`, e.g. `PrimaryTable`, `Login`, `AddNotes`), a thin **API client** over axios + React Query (`src/api/`, `src/reactQuery/`), and **utilities** (`src/utils/`). Builds with `tsc` to `dist/` (not bundled), develops via Storybook, tests with Vitest.
|
|
35
35
|
|
|
@@ -6,7 +6,7 @@ project: TOGa Blox
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-10-05
|
|
10
10
|
owners: [apeterson, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- toga-blox/src/components/BaseButton
|
|
@@ -74,6 +74,21 @@ Defect 4 is about swapping variants; this is the shape of the same problem for a
|
|
|
74
74
|
- **Consumer symptom.** One page toolbar renders its buttons at three different heights, and the developer cannot fix it from `themeConfig.json` — the number they need is not exposed. Nothing in the theme layer hints geometry is off-limits, so time is lost hunting the app's own CSS.
|
|
75
75
|
- **Fix direction.** Same as defect 5: convert both to `var(--baseButton-height, <existing literal>)` (and give `.primaryBtn` the same base height), non-breaking because the fallback preserves today's look. Until then a consumer must own the chrome: `toga25-supply` built its own `ToolbarButton` rather than retheme tokens shared by ~all buttons in the app — see [toolbar button chrome](../../toga25-supply/features/toolbar-button-chrome.md).
|
|
76
76
|
|
|
77
|
+
### Tooling defects (found 2026-09-28, Storybook/Docker audit)
|
|
78
|
+
|
|
79
|
+
**7. Storybook addons in `dependencies` — they install into every consumer**
|
|
80
|
+
- **Mechanism.** `@storybook/addon-docs`, `@storybook/addon-viewport`, `storybook-addon-react-router-v6` sit in `dependencies`, not `devDependencies`.
|
|
81
|
+
- **Consumer symptom.** Every app installing `@agilant/toga-blox` pulls Storybook packages it never uses (install weight, peer noise).
|
|
82
|
+
- **Fix direction.** Move them to `devDependencies`. Non-breaking.
|
|
83
|
+
|
|
84
|
+
**8. Storybook (7.6.24) is local-only and renders unstyled**
|
|
85
|
+
- Never deployed. Defines no host tokens (frontend §13e), so components render unstyled. Many exported components and all 3 templates have no story. `addon-onboarding` is configured but not installed.
|
|
86
|
+
- **Fix direction.** None planned — Storybook investment goes to [toga25-blox](../../toga25-blox/architecture.md), not here. To verify a CSS change, see the Storybook gotcha in [table](table.md).
|
|
87
|
+
|
|
88
|
+
**9. Docker files call scripts that do not exist**
|
|
89
|
+
- `docker-compose` runs `npm run storybook` (the script is `npm run blox`); the `Dockerfile` `CMD` runs `npm run start` (no such script). Neither container works as written.
|
|
90
|
+
- **Fix direction.** Point them at real scripts, or delete them.
|
|
91
|
+
|
|
77
92
|
### Already-known authoring items (carried over from §22)
|
|
78
93
|
|
|
79
94
|
Long-standing, previously recorded only as consumer-side advice:
|
|
@@ -89,6 +104,9 @@ Long-standing, previously recorded only as consumer-side advice:
|
|
|
89
104
|
- **Do not "fix" these in an app.** Working around them in a consumer is §22's job and is legitimate; changing app code to *imitate* the defect is not.
|
|
90
105
|
- **A fix here is a fleet-wide change.** blox is unbundled and consumed by ~40 call sites; anything touching auth or `localStorage` keys changes live session behavior on the next channel head a consumer picks up — and `toga2-commerce` picks up the channel head **automatically at build time**, with no PR. Sequence auth fixes deliberately.
|
|
91
106
|
|
|
107
|
+
## Change history
|
|
108
|
+
- 2026-10-05 — added tooling defects 7–9 (Storybook addons in `dependencies`, unstyled local-only Storybook, broken Docker scripts) (tcox)
|
|
109
|
+
|
|
92
110
|
## Related
|
|
93
111
|
|
|
94
112
|
- [frontend standard §22](../../../standards/frontend.md) — the app-side workarounds this backlog is the counterpart to.
|
|
@@ -6,8 +6,8 @@ project: TOGa Blox
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: [apeterson]
|
|
9
|
+
updated: 2026-10-05
|
|
10
|
+
owners: [apeterson, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- toga-blox/src/components/EnvironmentBadge/EnvironmentBadge.tsx
|
|
13
13
|
- toga-blox/src/components/EnvironmentBadge/EnvironmentBadge.css
|
|
@@ -64,7 +64,9 @@ Classifies `VITE_API` into `production` | `local` | `unknown` (plus the existing
|
|
|
64
64
|
- **A CSS class typo silently unstyles the badge.** `.env-localost` (missing `h`) shipped for a long time; the localhost badge rendered as plain text and nobody noticed. Class names must match the endpoint strings the classifier returns — add both together.
|
|
65
65
|
- **Testing `import.meta.env`:** use `vi.stubEnv("MODE", …)` / `vi.stubEnv("VITE_API", …)`. To set the hostname without navigating jsdom, redefine `window.location` with `Object.defineProperty`.
|
|
66
66
|
- **Consumers import it in ways that matter.** A barrel `import { EnvironmentBadge } from "@agilant/toga-blox"` drags in unrelated peers at build time; older published versions exported it as **default only**. See [toga2-commerce Amplify build & deploy](../../toga2-commerce/workflows/amplify-build-and-deploy.md).
|
|
67
|
+
- **toga2-supply does NOT use this badge.** It mounts its own copy (`toga2-supply/src/components/layout/Header/EnvironmentBadge.tsx` + `src/utils/getEndpointFromEnvironment.ts`), written because older blox mis-labelled unknown API hosts. A change to the hide rule or classifier here does not reach toga2-supply — change both, or move toga2-supply back to blox's badge.
|
|
67
68
|
- Do not remove the badge from an app to "clean up" — it is a deliberate QA tool.
|
|
68
69
|
|
|
69
70
|
## Change history
|
|
71
|
+
- 2026-10-05 — noted toga2-supply's app-local copy of the badge + classifier (tcox)
|
|
70
72
|
- 2026-09-23 — Hostname now overrides the hide rule (`isNonProductionHost`); endpoint classifier matches `production` explicitly and adds `local`/`unknown`; fixed `.env-localost` typo, added `.env-local`/`.env-unknown`; 24 tests added (apeterson)
|
|
@@ -6,7 +6,7 @@ project: TOGa Blox
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-10-05
|
|
10
10
|
owners: [apeterson, tcox, bala]
|
|
11
11
|
files:
|
|
12
12
|
- toga-blox/src/components/Table/hooks/useFetchTablePageMeta.ts
|
|
@@ -121,7 +121,7 @@ This exists because an unscoped `GET /{recordRoute}` returns *every* row of a lo
|
|
|
121
121
|
- **A filter popover's "Clear" must call `onClear()` — resetting local state only leaves the table filtered.** `HeaderFilterMultiselect.handleClear` did just `setSelectedOptions([])`, so the URL param (owned by the consuming app) and the applied filter survived: the popover showed nothing selected while the table stayed filtered. Fixed 2026-09-28 — it now calls `onClear()` and closes, **guarded by `if (filterValue)`**. The guard matters: `onClear` routes through `PrimaryTableHeaderCell` → `table.options.onColumnFiltersChange` → toga25-supply `useServerTableUrlState`, which resets to **page 1 on any filter change** — so an unguarded Clear with nothing applied would bounce the user off their page.
|
|
122
122
|
- **⚠ OPEN — `HeaderFilterRange.handleClear` has the same bug** (~line 162): it resets `singleValue`/`minValue`/`maxValue` but never calls `onClear()` and never closes the popover. Same fix shape as the multi-select. Not fixed as of 2026-09-28.
|
|
123
123
|
- **⚠ OPEN — `SortIcon` has no `aria-*` at all.** Sort direction and position are conveyed by shape only — no `aria-label`, no `aria-sort`. Pre-existing; needs its own ticket.
|
|
124
|
-
- **A unit test cannot prove a CSS change — verify in blox's own Storybook.** JSDOM applies no real layout, so a Vitest test can only assert the modifier class is present; it can never show the triangle actually flipped or that the digit stayed upright. Add a co-located `*.stories.tsx` rendering the states side by side, run `npm run blox` (port 6006), and look at it.
|
|
124
|
+
- **A unit test cannot prove a CSS change — verify in blox's own Storybook.** JSDOM applies no real layout, so a Vitest test can only assert the modifier class is present; it can never show the triangle actually flipped or that the digit stayed upright. Add a co-located `*.stories.tsx` rendering the states side by side, run `npm run blox` (port 6006), and look at it. **⚠ blox's Storybook defines no host tokens** (frontend §13e), so components render unstyled there — set the `--*` tokens the component reads in the story/decorator, or check the change in a consuming app.
|
|
125
125
|
- **⚠ OPEN — the multi-select filter matches by SUBSTRING, not exactly.** `HeaderFilterMultiselect` comma-joins the picked slugs and `src/api/tableData/getDataTableData.ts` turns that into `where: { or: [ { field: { contains: value } }, … ] }`. Picking "Jon Smith" also matches "Jon Smithson" — it over-includes rows.
|
|
126
126
|
- **⚠ OPEN — an option value containing a comma silently returns zero rows.** It splits into two bogus filter terms, with no error. Both bugs are pre-existing and affect **every** multi-select column including Order Status; both fix in one place — send a list / exact-equals operator instead of a comma-joined `contains`. Not fixed as of 2026-09-15.
|
|
127
127
|
- **⚠ `useTableData` APPENDS `.uuid` to `additionalData.slug` — unless the slug is on a hardcoded allow-list.** The list is exactly `catalogId`, `customerId`, `Items.catalogId`, `SalesOrderItems.uuid`, `SalesOrders.uuid` (verified in `@agilant/toga-blox` **1.1.2-production.144**, the version toga25-supply pins). Consequences for anyone writing a filter slug into config or a `TableLevel` callback:
|
|
@@ -144,6 +144,7 @@ This exists because an unscoped `GET /{recordRoute}` returns *every* row of a lo
|
|
|
144
144
|
- **Table/surface meta is cached with `Infinity` staleTime/gcTime** (`useFetchTablePageMeta`), so after a DB metadata change (`TableViewFields`, `Core.RecordFields`) a **hard reload** is required to see it. And the app consumes toga-blox's built **`dist/`**, not `src/` — editing blox source requires `npm run build` before it takes effect at runtime.
|
|
145
145
|
|
|
146
146
|
## Change history
|
|
147
|
+
- 2026-10-05 — Storybook verify gotcha: blox's Storybook has no host tokens, components render unstyled (tcox)
|
|
147
148
|
- 2026-09-28 — multi-select filter "Clear" now actually clears (calls `onClear`, page-1 guard); sort badge now points down for `desc` via CSS rotation (apeterson)
|
|
148
149
|
|
|
149
150
|
## Related
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
| Doc | Summary |
|
|
4
4
|
|-----|---------|
|
|
5
|
-
| [TOGa Blox 2.5 — Architecture](architecture.md) | Shared React+TS SOURCE library for the new platform apps (
|
|
5
|
+
| [TOGa Blox 2.5 — Architecture](architecture.md) | Shared React+TS SOURCE library for the new platform apps (new platform apps first; replaces toga-blox for all apps over time); read the ADR for full detail, not |
|
|
@@ -6,27 +6,23 @@ project: TOGa Blox 2.5
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: architecture
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: [jcardinal]
|
|
9
|
+
updated: 2026-10-05
|
|
10
|
+
owners: [jcardinal, tcox]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-blox/src
|
|
13
|
-
related:
|
|
13
|
+
related:
|
|
14
|
+
- ../toga-blox/architecture.md
|
|
14
15
|
---
|
|
15
|
-
Shared React+TS SOURCE library for the new platform apps (
|
|
16
|
+
Shared React+TS SOURCE library for the new platform apps (new platform apps first; replaces toga-blox for all apps over time); read the ADR for full detail, not this stub.
|
|
16
17
|
|
|
17
18
|
## Summary
|
|
18
19
|
|
|
19
|
-
`toga25-blox` is a fresh, **source-shared** (git, not npm) React+TS library that replaces
|
|
20
|
-
`toga-blox` for **new-platform apps only** (toga25-desk, toga25-iq, toga2-template, future
|
|
21
|
-
apps). Apps import blox **source** from a sibling clone — `@toga/blox/<entry>` resolves via
|
|
22
|
-
Vite alias to `../toga25-blox/src/<entry>/index.ts`. No build, no publish, no npm version.
|
|
23
|
-
Deploy clones `toga25-blox` on the app's own branch at build time. Old npm `toga-blox` stays
|
|
24
|
-
on `toga25-supply` / `toga2-commerce` / `toga2-view` / `toga2-hub` — do not port those apps to
|
|
25
|
-
this repo. Full decision + rejected alternatives + consequences: `docs/decisions/0001-platform-frontend-architecture.md` in the toga25-blox repo — read that for anything beyond these rules.
|
|
20
|
+
`toga25-blox` is a fresh, **source-shared** (git, not npm) React+TS library that replaces `toga-blox` for **all apps, eventually** (decided 2026-09-30). New platform apps (toga25-desk, toga25-iq, toga2-template, future apps) come first. Old apps (toga25-supply, toga2-supply, toga2-commerce, toga2-view, toga2-hub, BDR, talos, togatech) move over later. Apps import blox **source** from a sibling clone — `@toga/blox/<entry>` resolves via Vite alias to `../toga25-blox/src/<entry>/index.ts`. No build, no publish, no npm version. Deploy clones `toga25-blox` on the app's own branch at build time. Old `@agilant/toga-blox` is fixes-only until each app moves. Full decision + rejected alternatives + consequences: `docs/decisions/0001-platform-frontend-architecture.md` in the toga25-blox repo — read that for anything beyond these rules.
|
|
26
21
|
|
|
27
22
|
**Critical rules:**
|
|
28
23
|
- **Reuse order:** an existing `toga25-blox` piece → a piece already built in another
|
|
29
24
|
new-platform app (promote it to blox) → only then build new. Never duplicate.
|
|
25
|
+
- **Moving an old app: rewrite onto toga25-blox pieces — never port old blox code wholesale.** Old blox has 3 table engines and 3 select stacks; copying them carries that over.
|
|
30
26
|
- **Import boundary:** apps may import only `@toga/blox/<entry>` (the public entry barrel) —
|
|
31
27
|
lint blocks deeper paths. Blox never imports app code.
|
|
32
28
|
- **Layer order (nothing imports upward):** `lib` < `core` < `tenancy` < `theme` < `surface`
|
|
@@ -44,4 +40,5 @@ this repo. Full decision + rejected alternatives + consequences: `docs/decisions
|
|
|
44
40
|
## Related
|
|
45
41
|
|
|
46
42
|
- ADR: `docs/decisions/0001-platform-frontend-architecture.md` (toga25-blox repo).
|
|
43
|
+
- Component viewer: Storybook (`npm run storybook`, port 6006). Setup and open questions are in `docs/storybook.md` in the toga25-blox repo (prototype on branch TRUE-82044, awaiting Jeff's review).
|
|
47
44
|
- Old npm lib for legacy 2.0 apps: [toga-blox architecture](../toga-blox/architecture.md).
|
|
@@ -5,8 +5,8 @@ project: _Underscore
|
|
|
5
5
|
client: shared
|
|
6
6
|
type: standard
|
|
7
7
|
status: active
|
|
8
|
-
updated: 2026-10-
|
|
9
|
-
owners: [jcardinal, apeterson]
|
|
8
|
+
updated: 2026-10-05
|
|
9
|
+
owners: [jcardinal, apeterson, tcox]
|
|
10
10
|
files: []
|
|
11
11
|
related:
|
|
12
12
|
- ../apps/toga25-supply/architecture.md
|
|
@@ -479,6 +479,8 @@ so restoration cannot resurrect a cleared cache.
|
|
|
479
479
|
`@agilant/toga-blox` is the front-end shared core — the React analog of `_underscore` / `library`
|
|
480
480
|
on the back end. A consuming app **MUST** meet every requirement below.
|
|
481
481
|
|
|
482
|
+
`@agilant/toga-blox` is **legacy — fixes only**. Build new shared components in `toga25-blox` (`@toga/blox`), which replaces it for all apps over time. See [toga25-blox architecture](../apps/toga25-blox/architecture.md). These rules apply until an app has moved.
|
|
483
|
+
|
|
482
484
|
**(a) Pin an exact published version — never a caret.** Some published versions ship an **empty
|
|
483
485
|
`dist`** and break the build; a caret can float you onto one. Both apps pin exact versions. Also
|
|
484
486
|
audit for **pin drift** between branches. A caret on a **prerelease** is inert (npm matches only the
|
|
@@ -637,6 +639,7 @@ The payoff: copy and icons change as a **data edit**, with no front-end deploy.
|
|
|
637
639
|
- **Build a new generic capability app-LOCAL first; promote into blox only after a 2nd consumer
|
|
638
640
|
validates the contract.** Hardening a shared contract from a single call site (n=1) bakes in that
|
|
639
641
|
app's accidents.
|
|
642
|
+
- **Promote into `toga25-blox`, not `@agilant/toga-blox`** (legacy, fixes only). toga25-blox has its own reuse order — see its architecture doc.
|
|
640
643
|
- **Mark intent with a per-module header** so a future reader knows which side a module belongs on:
|
|
641
644
|
|
|
642
645
|
```ts
|
|
@@ -16,6 +16,7 @@ files:
|
|
|
16
16
|
- dbchanges2/Client_Nychh/2026-09-30a - TransferOrderPackedAssetTagException.sql
|
|
17
17
|
- dbchanges2/Client_Nychh/2026-09-15b - TransferOrderEmailGoLiveRecipient.sql
|
|
18
18
|
- dbchanges2/Core/2026-09-16a - NychhTransferOrderEmailsCron.sql
|
|
19
|
+
- dbchanges2/Client_Nychh/2026-10-05a - TransferOrderShippedEmailOnTrackingCutover.sql
|
|
19
20
|
related:
|
|
20
21
|
- ./transfer-order-netsuite-push.md
|
|
21
22
|
- ../../../2.0/apps/_underscore/features/units-for-items-for-purchase-orders.md
|
|
@@ -42,7 +43,7 @@ holds the two queued entry points, the backstop cron, every query and the HTML b
|
|
|
42
43
|
`_underscore` per-client models only declare the `c_` marker fields and queue the job — they build
|
|
43
44
|
nothing. That is deliberate: app-level over core, and a slow send must not sit inside an api2 save.
|
|
44
45
|
|
|
45
|
-
|
|
46
|
+
Rules that carry most of the risk:
|
|
46
47
|
|
|
47
48
|
1. **Submitted fires on `postPut`, not `postPost`** — on the POST the order still holds the
|
|
48
49
|
`TA…` autoincrement placeholder, not the NetSuite number.
|
|
@@ -51,6 +52,9 @@ Three rules carry most of the risk:
|
|
|
51
52
|
3. **A failed send throws — it is never swallowed**, so nothing is ever marked sent that did not go.
|
|
52
53
|
4. **Packed (and shipped) are HELD while a line is short of asset tags** — ops is warned instead, and
|
|
53
54
|
the customer email sends itself once the tags land. See below.
|
|
55
|
+
5. **Shipped fires on the first TRACKING NUMBER, never on the NetSuite shipped stage** (since
|
|
56
|
+
2026-10-05). The warehouse often marks a fulfillment shipped only after delivery, so the stage is
|
|
57
|
+
unreliable. No tracking → no Shipped email, even at stage shipped. See below.
|
|
54
58
|
|
|
55
59
|
Recipient is `DevTeam@togatech.com` while testing. `2026-09-15b` swaps it to
|
|
56
60
|
`EndUserDeviceLifecycle@nychhc.org` — run that file only after sign-off.
|
|
@@ -61,7 +65,10 @@ Recipient is `DevTeam@togatech.com` while testing. `2026-09-15b` swaps it to
|
|
|
61
65
|
- `SendSubmitted(string $transferOrderUuid)` — queued from the TransferOrder `postPut`.
|
|
62
66
|
- `SendFulfillment(string $itemFulfillmentUuid)` — queued from the ItemFulfillment hooks.
|
|
63
67
|
- `SendPending(bool $dryRun = true)` — the `*/5` backstop cron.
|
|
64
|
-
- `resolveStagesStillToAnnounce()` — which
|
|
68
|
+
- `resolveStagesStillToAnnounce(stageName, stageAlreadyEmailed, hasTrackingNumber)` — which
|
|
69
|
+
stages are still owed (see *Shipped = tracking number*).
|
|
70
|
+
- `buildHasTrackingNumberSql()` — the one "has tracking" test, shared by
|
|
71
|
+
`readItemFulfillmentForEmail()` (`hasTrackingNumber` column) and the `SendPending` sweep.
|
|
65
72
|
- `readLinesMissingAssetTags()` / `sendPackedExceptionEmail()` — the asset-tag hold.
|
|
66
73
|
- `initialize()` registers the client DB before any query runs.
|
|
67
74
|
- Builders: `buildItemRowsHtml()`, `buildTrackingSectionHtml()`, `buildAssetTagTableHtml()`,
|
|
@@ -122,13 +129,11 @@ they must stay in step: the Submitted guard, the order read, and both `SendPendi
|
|
|
122
129
|
2. **Packed / Shipped** — the ItemFulfillment `postPost` / `postPut` queue `SendFulfillment` with
|
|
123
130
|
the fulfillment uuid. The stage reached is recorded on `c_transferOrderEmailStageSent`, with
|
|
124
131
|
`c_dtTransferOrderEmailSent` as the timestamp, so a fulfillment cannot re-announce a stage.
|
|
125
|
-
`resolveStagesStillToAnnounce()` decides what is owed
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
skipped
|
|
129
|
-
|
|
130
|
-
Packed. The NYCHH NetSuite sync is a **daily** cron, so a fulfillment landing fully shipped is
|
|
131
|
-
normal, not an edge case — see
|
|
132
|
+
`resolveStagesStillToAnnounce()` decides what is owed (table in *Shipped = tracking number*).
|
|
133
|
+
When packed was never announced and shipped is now owed it returns **`[packed, shipped]`**, both
|
|
134
|
+
sent in that order in one run — the **asset-tag check lives on packed**, so packed must never be
|
|
135
|
+
skipped (transfer order **291773** once got Submitted + Shipped, no Packed). The NYCHH NetSuite
|
|
136
|
+
sync is a **daily** cron, so a fulfillment landing fully shipped/tracked is normal — see
|
|
132
137
|
[the per-client sync](../../../1.0/apps/worker/features/netsuite-togasupply-per-client-sync.md).
|
|
133
138
|
3. The **backstop cron** sweeps anything the hooks missed (below).
|
|
134
139
|
4. All three paths share the same markers, so nothing sends twice.
|
|
@@ -152,6 +157,48 @@ PUT follows.** Fulfillment **2704** in production: header row at 20:33:09, lines
|
|
|
152
157
|
seconds ago with no lines yet is left for the next run instead of being mailed empty.
|
|
153
158
|
- It also picks up any send that threw (the marker was never written).
|
|
154
159
|
- Caps at **25 of each kind per run** (`MAXIMUM_EMAILS_PER_RUN`).
|
|
160
|
+
- **It is the real sender of Shipped** — see below.
|
|
161
|
+
|
|
162
|
+
## ⚠ Shipped = tracking number, not the NetSuite stage (2026-10-05)
|
|
163
|
+
|
|
164
|
+
"Has tracking" = `ItemFulfillments_TrackingNumbers` **INNER JOIN** `TrackingNumbers` on
|
|
165
|
+
`trackingNumberId` with `TRIM(number) <> ''` — a bridge row can hold only a
|
|
166
|
+
`returnTrackingNumberId`. Built once in `buildHasTrackingNumberSql()`; never re-type it.
|
|
167
|
+
|
|
168
|
+
`resolveStagesStillToAnnounce()`, first match wins:
|
|
169
|
+
|
|
170
|
+
| Marker (`c_transferOrderEmailStageSent`) | Tracking | Stage | Owed |
|
|
171
|
+
|---|---|---|---|
|
|
172
|
+
| `shipped` | any | any | nothing |
|
|
173
|
+
| `packed` | yes | any | `[shipped]` |
|
|
174
|
+
| empty | yes | any (incl. picked — tracking means boxed) | `[packed, shipped]` |
|
|
175
|
+
| empty | no | packed / shipped | `[packed]` |
|
|
176
|
+
| else | | | nothing |
|
|
177
|
+
|
|
178
|
+
The asset-tag hold runs whenever packed is owed and blocks both.
|
|
179
|
+
|
|
180
|
+
- **⚠ `SendPending` WHERE must match that table:** marker NULL or `<> 'shipped'` AND (has tracking OR
|
|
181
|
+
(stage IN packed/shipped AND `COALESCE(marker,'') = ''`)). Drift → rows picked then skipped, eating
|
|
182
|
+
`LIMIT 25`.
|
|
183
|
+
- **Why the cron, not the hook, sends Shipped:** library `syncItemFulfillmentFromNetsuite`
|
|
184
|
+
(`library/app/api/toga2.php`) PUTs the stage first (~L7270), then units/asset tags, and writes
|
|
185
|
+
package tracking **last** (~L8046). The ItemFulfillment `postPut` job runs before tracking exists.
|
|
186
|
+
(Also removes the old race where Shipped read stale tracking/tags.) Tracking imports at picked,
|
|
187
|
+
packed or shipped alike.
|
|
188
|
+
- **Once per fulfillment.** Business rule = 1 tracking number per fulfillment; prod has violations
|
|
189
|
+
(IF 329047 has 2; IFs 329944/330013 share 1). Still sends once — the marker gates it.
|
|
190
|
+
- **Lag:** adding tracking in NetSuite bumps the IF `lastmodifieddate`, so the sync re-reads it, but
|
|
191
|
+
only on the NYCHH sync interval (daily). E.g. IF 330377 (NS 7566823) still Packed (status B) in
|
|
192
|
+
NetSuite, tracking imported 2026-10-05 11:56.
|
|
193
|
+
- **Only NYCHH emails off the NetSuite shipped stage** (checked 2026-10-05): Compass in-transit emails
|
|
194
|
+
are per tracking number from ASN data; Prudential "order shipped" is a transmission; Elite moves to
|
|
195
|
+
shipped when tracking lands.
|
|
196
|
+
- **Cutover:** `Client_Nychh/2026-10-05a` stamps `c_transferOrderEmailStageSent='shipped'` on supply
|
|
197
|
+
fulfillments that already had tracking (`c_dtTransferOrderEmailSent` untouched — no email sent), so
|
|
198
|
+
old fulfillments got no late Shipped. Re-runnable; matched 2 on prod (IF 330377/TO 291765, IF
|
|
199
|
+
330400/TO 291965). **⚠ Deploy order:** the old code re-sends Packed for a row stamped `shipped`
|
|
200
|
+
whose stage is packed, so: pause `Core.CronJobs` id 78 (`isActive=0`) → run SQL → deploy worker2 →
|
|
201
|
+
re-enable id 78.
|
|
155
202
|
|
|
156
203
|
## ⚠ Missing asset tags HOLD the packed email (and warn ops instead)
|
|
157
204
|
|
|
@@ -257,7 +304,8 @@ Cost of the split, stated so nobody is surprised later:
|
|
|
257
304
|
**1,203** have one number and **292** have two or more, **up to 25**.
|
|
258
305
|
- **Many shipped fulfillments have no tracking at all** — 1,484 of 2,479 measured 2026-09-17, and
|
|
259
306
|
322 of the rest have no carrier on the number. `buildTrackingSectionHtml()` drops the whole block
|
|
260
|
-
when there is no number and drops the button when there is no URL
|
|
307
|
+
when there is no number and drops the button when there is no URL (since 2026-10-05 Shipped needs a
|
|
308
|
+
number, so this now matters only for the Packed email). Placeholders are
|
|
261
309
|
**`{trackingIntroSuffix}`** and **`{trackingSection}`**.
|
|
262
310
|
- **Gmail clips a message past about 102,400 bytes.** `MAXIMUM_EMAIL_BODY_BYTES = 80000` budgets the
|
|
263
311
|
**item block only**; the remainder is summarised as *"and N more"*. 80,000 (not the earlier
|
|
@@ -348,6 +396,11 @@ loaded out of the dbchanges2 file so an unresolved `{placeholder}` fails a test.
|
|
|
348
396
|
unescaped values and both detail-box defects were all found there, not in production.
|
|
349
397
|
|
|
350
398
|
## Change history
|
|
399
|
+
- 2026-10-05 — **Shipped now fires on the first tracking number, not the NetSuite shipped stage**
|
|
400
|
+
(warehouse marks shipped after delivery). Shared `buildHasTrackingNumberSql()`, new
|
|
401
|
+
`resolveStagesStillToAnnounce()` table, matching `SendPending` WHERE; removed dead
|
|
402
|
+
`resolveFulfillmentEmailTemplateUuid()`. Cutover SQL `2026-10-05a` (2 prod rows). Deployed + verified
|
|
403
|
+
on prod. (jcardinal)
|
|
351
404
|
- 2026-10-05 — **The asset-tag hold now only counts missing units on `SERIALIZED` items**
|
|
352
405
|
(TRUE-82111). `readLinesMissingAssetTags()` treated "fewer linked units than quantity" as missing
|
|
353
406
|
tags for every item, so accessories were reported short: first prod exception was `AC785544`, a
|
|
@@ -66,6 +66,7 @@ TOGA 2.0 client on `_underscore`, prod schema `Client_Nychh`. Supply-chain / ful
|
|
|
66
66
|
- **Worker tiers:** `worker2` (2.0) runs the NYCHH asset-tag backfill; `worker`/`library` (1.0) run the NetSuite→TOGa Supply sync and the asset-tag verification/backfill diagnostics.
|
|
67
67
|
|
|
68
68
|
## Key features (this client)
|
|
69
|
+
- **Transfer-order "Order Shipped" email fires on the first tracking number, not the NetSuite shipped stage (2026-10-05, prod).** Warehouse marks shipped after delivery. NYCHH is the only client emailing off the NS shipped stage. See [TO notification emails](./features/transfer-order-notification-emails.md).
|
|
69
70
|
- **Prod NetSuite feeds (2026-10-02):** only ITEM_FULFILLMENTS + INVENTORY_ADJUSTMENTS run (worker `511a7fd9`, 2026-09-30), cursors rewound to 2022/2023.
|
|
70
71
|
- **TO destination + own ship-to (2026-10-02, library `8f974d93`, _underscore `6839dd3e`).** NetSuite-entered TOs landed on catch-all Location 2 (text match failed on Queens hyphens + multi-address sites). Destination now = mapped NetSuite address-book entry → text match → Location 2; TOs own `shipToAddressId`; Location auto-create uses default shipping. Mapping: 61 entries → 18 sites. See [TO import](./features/netsuite-transfer-order-import.md), [address-book mapping](../../1.0/apps/worker/workflows/netsuite-addressbook-location-mapping.md).
|
|
71
72
|
- **TO↔customer-PO line matching fixed (2026-10-02, `1bc4de52`).** Old matcher picked unstocked duplicate PO lines and created empty PO lines; now ranks stocked lines and allows reuse. 161 TOs re-processed; 86 bad links over 62 TOs remained at last measure (re-process result pending). See [reconcile](../../1.0/apps/library/features/transfer-order-purchase-order-link-reconcile.md).
|
package/package.json
CHANGED