toga-ai 1.0.580 → 1.0.582
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/dbchanges/INDEX.md +1 -1
- package/knowledge/1.0/apps/dbchanges/workflows/authoring-and-shipping-sql-files.md +43 -2
- package/knowledge/2.0/apps/dbchanges2/INDEX.md +1 -1
- package/knowledge/2.0/apps/dbchanges2/features/surface-layer-schema.md +25 -1
- package/knowledge/2.0/apps/toga25-supply/INDEX.md +1 -1
- package/knowledge/2.0/apps/toga25-supply/features/record-modals-and-nested-tables.md +20 -1
- package/knowledge/clients/nycdoe/INDEX.md +1 -1
- package/knowledge/clients/nycdoe/features/servicenow-integration.md +62 -1
- package/package.json +1 -1
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
|
-
| [Authoring & Shipping a 1.0 dbchanges SQL File](workflows/authoring-and-shipping-sql-files.md) | `dbchanges` is the **1.0** (legacy/V1) schema-and-data change repository — the 1.0 sibling of 2.0's `dbchanges2`. | dbchanges/index.php, dbchanges/Core/, worker/crons/infrastructure/execute_dbchanges.php, worker/.ebextensions/030_dbchanges.config |
|
|
5
|
+
| [Authoring & Shipping a 1.0 dbchanges SQL File](workflows/authoring-and-shipping-sql-files.md) | `dbchanges` is the **1.0** (legacy/V1) schema-and-data change repository — the 1.0 sibling of 2.0's `dbchanges2`. | dbchanges/index.php, dbchanges/Core/, dbchanges/TOGaDeskSupport/, worker/crons/infrastructure/execute_dbchanges.php, worker/.ebextensions/030_dbchanges.config |
|
|
@@ -6,11 +6,12 @@ project: Database Changes
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-08-14
|
|
10
10
|
owners: [sking]
|
|
11
11
|
files:
|
|
12
12
|
- dbchanges/index.php
|
|
13
13
|
- dbchanges/Core/
|
|
14
|
+
- dbchanges/TOGaDeskSupport/
|
|
14
15
|
- worker/crons/infrastructure/execute_dbchanges.php
|
|
15
16
|
- worker/.ebextensions/030_dbchanges.config
|
|
16
17
|
related:
|
|
@@ -97,6 +98,27 @@ WHERE t.id IN (<ids>)
|
|
|
97
98
|
) AS preflight) = <expected-row-count>;
|
|
98
99
|
```
|
|
99
100
|
|
|
101
|
+
## One top-level folder = one DATABASE (and the silent-skip trap)
|
|
102
|
+
|
|
103
|
+
The repo's top level is a list of **database names**, not modules: `Core`, `TOGaDeskSupport`,
|
|
104
|
+
`Common`, `Logs`, `Bridge_NetSuite`, … The runner walks every top-level directory and treats the
|
|
105
|
+
**folder name as the database to connect to** (`index.php` ~L55–60), then — for each folder — scans
|
|
106
|
+
`config.<env>.ini` and uses the **first `[database*]` group it finds** (`~L76–80`; in
|
|
107
|
+
`config.prod.ini` that is `[database_main]`) for hostname/username/password. So *all* folders are
|
|
108
|
+
attempted with **one** set of credentials against one host, with only the DB name varying.
|
|
109
|
+
|
|
110
|
+
Consequence: **a folder whose database is not reachable from that host is skipped silently.**
|
|
111
|
+
The failure path is a bare `echo "Skipping <db> Database... unable to connect"` (`~L189`) — no
|
|
112
|
+
`Logs.Errors` row, no `exit()`, no `_dbchanges` bookkeeping. Nothing retries it, and nothing tells
|
|
113
|
+
you. A change that spans two databases on two different clusters (e.g. `Core` on **db_core** and
|
|
114
|
+
`TOGaDeskSupport` on **db_togadesk**) therefore needs **two separate runs**, one per cluster, and
|
|
115
|
+
an applier pointed at only one of them **half-applies the change and reports success**.
|
|
116
|
+
|
|
117
|
+
**Rule: a multi-database change is not done until you have verified each database independently.**
|
|
118
|
+
Verify **per-pair** (does *this* key have *this* value?), not by a `COUNT(*) ... IS NOT NULL` total —
|
|
119
|
+
a per-count check passes on the cluster that applied and tells you nothing about the one that
|
|
120
|
+
didn't.
|
|
121
|
+
|
|
100
122
|
## Which branches actually auto-apply
|
|
101
123
|
|
|
102
124
|
| Path | Trigger | Timing |
|
|
@@ -121,7 +143,11 @@ The repo ships `config.alpha/beta/demo/hotfix/prod/stage/test/worker.ini`, there
|
|
|
121
143
|
is a no-op rather than a wrong write.
|
|
122
144
|
5. Open a PR into `_production` (or the target `_<env>` branch).
|
|
123
145
|
6. After merge: `_<env>` branches apply within ~2 min; **`_production` waits for the next worker
|
|
124
|
-
deploy** — verify with a read-only query afterwards rather than assuming.
|
|
146
|
+
deploy** — verify with a read-only query afterwards rather than assuming. **Merging the PR
|
|
147
|
+
applies nothing by itself** — `dbchanges` has no CI, no workflow, no runner of its own; every
|
|
148
|
+
application is driven from the worker tier.
|
|
149
|
+
7. If the change spans more than one top-level (database) folder, verify **each database
|
|
150
|
+
separately** — see the silent-skip trap above.
|
|
125
151
|
|
|
126
152
|
## Gotchas
|
|
127
153
|
|
|
@@ -130,6 +156,14 @@ The repo ships `config.alpha/beta/demo/hotfix/prod/stage/test/worker.ini`, there
|
|
|
130
156
|
local one.
|
|
131
157
|
- **No `DELIMITER`, no stored routines, no explicit `COMMIT`.** See above.
|
|
132
158
|
- **Error 1093** is why the preflight subquery must be wrapped in a derived table.
|
|
159
|
+
- **A merged PR is not an applied change.** Confirmed live 2026-08-13 (PR dbchanges#265): after the
|
|
160
|
+
merge into `_production`, both target tables were still 100% unchanged. Never tell a customer or a
|
|
161
|
+
ticket "applied" off a merge — always re-read the data.
|
|
162
|
+
- **A cross-database change half-applies without warning.** Same incident: the `TOGaDeskSupport`
|
|
163
|
+
file landed and the `Core` file did not, leaving `assets.tag` 87/87 correct while
|
|
164
|
+
`Core.AdvanceShippingNoticeUnits.assetTag` was 87/87 still empty — a state that *looks* fixed in
|
|
165
|
+
the UI. Cause is the one-credential-set / folder-is-a-database model above. Verify every database
|
|
166
|
+
in the change, per-pair.
|
|
133
167
|
- **Retry semantics are a feature**: because the data change and the `_dbchanges` row share a
|
|
134
168
|
transaction, a failing file re-runs next cycle. Do not "help" it with a manual COMMIT.
|
|
135
169
|
- **⚠ SECURITY — hardcoded GitHub token.** `worker/crons/infrastructure/execute_dbchanges.php`
|
|
@@ -140,6 +174,13 @@ The repo ships `config.alpha/beta/demo/hotfix/prod/stage/test/worker.ini`, there
|
|
|
140
174
|
argv string. No credential value is recorded here by design.
|
|
141
175
|
|
|
142
176
|
## Change history
|
|
177
|
+
- 2026-08-14 — Documented that each **top-level folder is a database name** and that the runner uses
|
|
178
|
+
the **first `[database*]` config group** for every folder, so a folder on another cluster is
|
|
179
|
+
**silently skipped** (bare echo, no `Logs.Errors`, no `exit()`, no retry). Confirmed live during
|
|
180
|
+
the DOE asset-tag backfill (PR dbchanges#265): merging the PR applied nothing, and then a
|
|
181
|
+
`TOGaDeskSupport`-only run left `Core` untouched — a half-fix that looked complete in the UI.
|
|
182
|
+
Added the rule that a multi-database change needs one run per cluster and **per-pair**
|
|
183
|
+
verification, not a non-null count. (sking)
|
|
143
184
|
- 2026-07-29 — Repo onboarded to the KB (1.0 `dbchanges`, framework core sibling of `dbchanges2`).
|
|
144
185
|
Documented the `index.php` `";\n"` split + `mysqli_query()` model and the comment-only-chunk /
|
|
145
186
|
error-1065 queue abort, the per-file transaction & retry semantics, the derived-table preflight
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [Database Changes (dbchanges2) Repository Architecture](architecture.md) | `dbchanges2` is the **schema-migration / SQL change-set repository** for the entire 2.0 platform. | Core/, Client/, Client_<Tenant>/, Logs/, Logs_Client/, _modules/ |
|
|
6
|
-
| [Surface Layer Schema (UI presentation/config tables)](features/surface-layer-schema.md) | The persistent schema for the platform-wide **Surface** UI presentation/configuration layer (see the `_underscore` [surface-resolver](../../_underscore/features | dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client_Compass/2026-06-25d - SalesOrderSurfaceClientSeed.sql, _underscore/Model/Client/ThemeToken.php, toga25-supply/src/themeConfig.json, dbchanges2/Core/2026-06-25a - SurfaceCoreTables.sql, dbchanges2/Core/2026-06-25b - SurfaceRecordsAndFields.sql, dbchanges2/Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql, dbchanges2/Core/2026-06-29a - ItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql, dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.sql, dbchanges2/Core/2026-06-29d - VendorItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29e - InventorySurfaceSeed.sql, dbchanges2/Core/2026-06-30a - SurfaceMetaGroupAndSalesOrderSections.sql, dbchanges2/Client/2026-06-30a - SurfaceMetaGroupAcl.sql, dbchanges2/Client_Compass/2026-06-30a - SalesOrderDisplaySectionManagerOverrides.sql, dbchanges2/Client_CompassCanada/2026-06-30a - SalesOrderSurfaceManagerOverrides.sql, dbchanges2/Client_Quad/2026-06-30a - SalesOrderSurfaceClientOverrides.sql, dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client/2026-06-25b - SurfaceClientSeed.sql, dbchanges2/Client/2026-06-25c - SurfaceClientAcl.sql, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Core/2026-07-17h - Update - ClearApprovalsFilterButtonConfig.sql, dbchanges2/Client_Compass/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_Compass/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Client_CompassCanada/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_CompassCanada/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Client_Quad/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_Quad/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Core/2026-07-20a - Update - HideAdminNotesSectionByDefault.sql, dbchanges2/Core/2026-07-20b - Update - NotesSectionFieldElements.sql, dbchanges2/Client_Compass/2026-07-20a - AdminNotesSectionVisibilityOverride.sql, dbchanges2/Client_Compass/2026-07-20b - NotesSectionFieldsOverride.sql, dbchanges2/Client_CompassCanada/2026-07-20a - AdminNotesSectionVisibilityOverride.sql, dbchanges2/Client_CompassCanada/2026-07-20b - NotesSectionFieldsOverride.sql, dbchanges2/Client_Quad/2026-07-20a - NotesSectionFieldsOverride.sql, dbchanges2/Core/2026-07-20c - Update - VendorItemsToggleSurfaceSeed.sql, dbchanges2/Client_Compass/2026-07-20c - ItemRecordEditButtonEnable.sql, dbchanges2/Client_Compass/2026-07-20d - ItemRecordVendorItemsEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20c - ItemRecordEditButtonEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20d - ItemRecordVendorItemsEnable.sql, dbchanges2/Core/2026-07-20e - RestoreApproveDenyRowActions.sql, dbchanges2/Client_Compass/2026-07-20e - RowActionsApprovalWorkflowAdminEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20e - RowActionsApprovalWorkflowAdminEnable.sql, dbchanges2/Core/2026-07-17 - README - RUN ORDER.md, dbchanges2/Client_Compass/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_Compass/2026-07-21b - SalesOrderApprovalsGateEnable.sql, dbchanges2/Client_CompassCanada/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_CompassCanada/2026-07-21b - SalesOrderApprovalsGateEnable.sql, dbchanges2/Client_Quad/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_Quad/2026-07-21b - SalesOrderApproveDisabledTooltipTranslation.sql, dbchanges2/Core/2026-07-21a - SalesOrderDecisionSummarySurfaceSeed.sql, dbchanges2/Core/2026-07-21b - SalesOrderDecisionActionSurfaceSeed.sql, dbchanges2/Client_Quad/2026-07-21c - SalesOrderDecisionSummaryOverride.sql, dbchanges2/Client_Compass/2026-07-21c - SalesOrderDecisionSummaryTotalConcat.sql, dbchanges2/Client_CompassCanada/2026-07-21c - SalesOrderDecisionSummaryTotalConcat.sql, dbchanges2/Core/2026-07-23a - PoNumberDetailFieldValueKey.sql |
|
|
6
|
+
| [Surface Layer Schema (UI presentation/config tables)](features/surface-layer-schema.md) | The persistent schema for the platform-wide **Surface** UI presentation/configuration layer (see the `_underscore` [surface-resolver](../../_underscore/features | dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client_Compass/2026-06-25d - SalesOrderSurfaceClientSeed.sql, _underscore/Model/Client/ThemeToken.php, toga25-supply/src/themeConfig.json, dbchanges2/Core/2026-06-25a - SurfaceCoreTables.sql, dbchanges2/Core/2026-06-25b - SurfaceRecordsAndFields.sql, dbchanges2/Core/2026-06-25c - SalesOrderLoginSurfaceSeed.sql, dbchanges2/Core/2026-06-29a - ItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29b - SurfaceMetaPublicReadAcl.sql, dbchanges2/Client/2026-06-29c - SurfaceRecordScriptAcl.sql, dbchanges2/Core/2026-06-29c - SurfaceDebugPhpMethodFix.sql, dbchanges2/Core/2026-06-29d - VendorItemsSurfaceSeed.sql, dbchanges2/Core/2026-06-29e - InventorySurfaceSeed.sql, dbchanges2/Core/2026-06-30a - SurfaceMetaGroupAndSalesOrderSections.sql, dbchanges2/Client/2026-06-30a - SurfaceMetaGroupAcl.sql, dbchanges2/Client_Compass/2026-06-30a - SalesOrderDisplaySectionManagerOverrides.sql, dbchanges2/Client_CompassCanada/2026-06-30a - SalesOrderSurfaceManagerOverrides.sql, dbchanges2/Client_Quad/2026-06-30a - SalesOrderSurfaceClientOverrides.sql, dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql, dbchanges2/Client/2026-06-25b - SurfaceClientSeed.sql, dbchanges2/Client/2026-06-25c - SurfaceClientAcl.sql, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Core/2026-07-17h - Update - ClearApprovalsFilterButtonConfig.sql, dbchanges2/Client_Compass/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_Compass/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Client_CompassCanada/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_CompassCanada/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Client_Quad/2026-07-17a - SalesOrderApprovalActionsOverride.sql, dbchanges2/Client_Quad/2026-07-17b - SalesOrderApprovalsFilterButtonOverride.sql, dbchanges2/Core/2026-07-20a - Update - HideAdminNotesSectionByDefault.sql, dbchanges2/Core/2026-07-20b - Update - NotesSectionFieldElements.sql, dbchanges2/Client_Compass/2026-07-20a - AdminNotesSectionVisibilityOverride.sql, dbchanges2/Client_Compass/2026-07-20b - NotesSectionFieldsOverride.sql, dbchanges2/Client_CompassCanada/2026-07-20a - AdminNotesSectionVisibilityOverride.sql, dbchanges2/Client_CompassCanada/2026-07-20b - NotesSectionFieldsOverride.sql, dbchanges2/Client_Quad/2026-07-20a - NotesSectionFieldsOverride.sql, dbchanges2/Core/2026-07-20c - Update - VendorItemsToggleSurfaceSeed.sql, dbchanges2/Client_Compass/2026-07-20c - ItemRecordEditButtonEnable.sql, dbchanges2/Client_Compass/2026-07-20d - ItemRecordVendorItemsEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20c - ItemRecordEditButtonEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20d - ItemRecordVendorItemsEnable.sql, dbchanges2/Core/2026-07-20e - RestoreApproveDenyRowActions.sql, dbchanges2/Client_Compass/2026-07-20e - RowActionsApprovalWorkflowAdminEnable.sql, dbchanges2/Client_CompassCanada/2026-07-20e - RowActionsApprovalWorkflowAdminEnable.sql, dbchanges2/Core/2026-07-17 - README - RUN ORDER.md, dbchanges2/Client_Compass/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_Compass/2026-07-21b - SalesOrderApprovalsGateEnable.sql, dbchanges2/Client_CompassCanada/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_CompassCanada/2026-07-21b - SalesOrderApprovalsGateEnable.sql, dbchanges2/Client_Quad/2026-07-21a - SalesOrderApproveEnabledRuleOverride.sql, dbchanges2/Client_Quad/2026-07-21b - SalesOrderApproveDisabledTooltipTranslation.sql, dbchanges2/Core/2026-07-21a - SalesOrderDecisionSummarySurfaceSeed.sql, dbchanges2/Core/2026-07-21b - SalesOrderDecisionActionSurfaceSeed.sql, dbchanges2/Client_Quad/2026-07-21c - SalesOrderDecisionSummaryOverride.sql, dbchanges2/Client_Compass/2026-07-21c - SalesOrderDecisionSummaryTotalConcat.sql, dbchanges2/Client_CompassCanada/2026-07-21c - SalesOrderDecisionSummaryTotalConcat.sql, dbchanges2/Core/2026-07-23a - PoNumberDetailFieldValueKey.sql, dbchanges2/Core/2026-07-29a - SalesOrderApprovalDetailsSurfaceSeed.sql, dbchanges2/Core/2026-08-05b - ApprovalDetailsShippingMethodConcatCharge.sql |
|
|
7
7
|
| [2.0 New-Client Onboarding (manual process)](workflows/client-onboarding.md) | > **A local browser wizard now automates this.** Steps 2–9 below (create DBs, generate Core/API > inserts, append to `Clients_Db.txt`) — plus the dbchanges2 bla | Client/, Client_<Tenant>/, Core/, Logs_Client/ |
|
|
8
8
|
| [Auditing a client DB that drifted from its models (partially applied module migration)](workflows/client-schema-drift-audit.md) | A recurring 2.0 failure mode: **one client's database drifts from what the PHP models declare**, usually because a `_modules/<module>/` migration was applied to | dbchanges2/Client_Growrk/2026-05-28.sql, dbchanges2/Client_Growrk/2026-08-10c - GrowrkServiceRequestCustomFieldsCatchUp.sql, dbchanges2/Client_Growrk/2026-08-10d - GrowrkServiceRequestTypeAndDispositionSeeds.sql, dbchanges2/_modules/netsuite/2026-07-10a - UnitInventoryFields.sql, dbchanges2/Client_Growrk/2026-08-10 - GrowrkUnitInventoryFieldsCatchUp.sql, dbchanges2/Client_Growrk/2026-08-10b - GrowrkUnitItemDescriptionAcl.sql, dbchanges2/Client_Growrk/_modules.txt |
|
|
9
9
|
| [Local vs prod MySQL config parity — why “it passed locally” is not evidence](workflows/local-vs-prod-mysql-config-parity.md) | Several migration failures that look like "prod-only bugs" are actually **per-machine MySQL server-configuration differences**. | |
|
|
@@ -6,7 +6,7 @@ project: Database Changes
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-08-14
|
|
10
10
|
owners: [jcardinal, apeterson]
|
|
11
11
|
files:
|
|
12
12
|
- dbchanges2/Client/2026-06-25a - SurfaceClientTables.sql
|
|
@@ -66,6 +66,8 @@ files:
|
|
|
66
66
|
- dbchanges2/Client_Compass/2026-07-21c - SalesOrderDecisionSummaryTotalConcat.sql
|
|
67
67
|
- dbchanges2/Client_CompassCanada/2026-07-21c - SalesOrderDecisionSummaryTotalConcat.sql
|
|
68
68
|
- dbchanges2/Core/2026-07-23a - PoNumberDetailFieldValueKey.sql
|
|
69
|
+
- dbchanges2/Core/2026-07-29a - SalesOrderApprovalDetailsSurfaceSeed.sql
|
|
70
|
+
- dbchanges2/Core/2026-08-05b - ApprovalDetailsShippingMethodConcatCharge.sql
|
|
69
71
|
related:
|
|
70
72
|
- ../../_underscore/features/surface-resolver.md
|
|
71
73
|
---
|
|
@@ -512,6 +514,19 @@ rule resumes.
|
|
|
512
514
|
widening for the rule attributes); and for the `/surfaces/meta` endpoint to resolve at all, the ACL
|
|
513
515
|
dispatch grants (`Client/2026-06-25c`, `2026-06-29c`, `2026-06-30a`). Skipping the table/ENUM
|
|
514
516
|
bring-up makes the `2026-07-17a/b` override inserts fail or silently coerce.
|
|
517
|
+
- **A `SurfaceElements` seed guard keyed on `config.valueKey` is NOT durably idempotent.** If a
|
|
518
|
+
later migration mutates that same valueKey, the guard goes blind and a re-run inserts a
|
|
519
|
+
**duplicate** element (same sortOrder/label/valueKey, different uuid/id) — the field then renders
|
|
520
|
+
twice. Proven case: `Core/2026-07-29a - SalesOrderApprovalDetailsSurfaceSeed.sql` inserts the
|
|
521
|
+
sales-order-approval-details "Shipping Method" element guarded by
|
|
522
|
+
`NOT EXISTS (... config.valueKey = 'shippingMethod.display')`; `Core/2026-08-05b -
|
|
523
|
+
ApprovalDetailsShippingMethodConcatCharge.sql` later UPDATEs that valueKey to `shippingMethod.name`
|
|
524
|
+
(+ `isConcatenated` charge). After that mutation, re-running `07-29a` sees no `.display` element and
|
|
525
|
+
inserts a **second** shipping-method element; re-running `08-05b` then converts the dup to `.name`
|
|
526
|
+
too. **Rule:** key idempotency guards on a **stable** identifier (`labelMessageId` / the message
|
|
527
|
+
key) that display-tweak migrations don't touch — never on `config.valueKey`. (Cleanup here was a
|
|
528
|
+
one-off manual DELETE of the higher-id dup on the developer's local DB — local operator error from
|
|
529
|
+
running seeds twice, deliberately not committed to dbchanges2.)
|
|
515
530
|
- **"Enter PO Details" (element 32) has a DUAL visibility gate.** Its rendering is gated by BOTH the
|
|
516
531
|
element-level `isVisible` flag AND a `config`-embedded `"isVisible":false` (migrated from Quad's
|
|
517
532
|
legacy field JSON, alongside `isEnabled`/`isComplete`). Flipping the element-level `IS_VISIBLE=1`
|
|
@@ -521,6 +536,15 @@ rule resumes.
|
|
|
521
536
|
override is added). **Open follow-up.**
|
|
522
537
|
|
|
523
538
|
## Change history
|
|
539
|
+
- 2026-08-14 — Recorded a durable idempotency-guard gotcha (below): a `SurfaceElements` seed
|
|
540
|
+
guard keyed on `config.valueKey` is defeated by any later migration that mutates that same
|
|
541
|
+
valueKey, causing duplicate element inserts on re-run. Concrete case: `Core/2026-07-29a`
|
|
542
|
+
guards the approval-details "Shipping Method" element on `config.valueKey='shippingMethod.display'`,
|
|
543
|
+
and `Core/2026-08-05b` later UPDATEs it to `shippingMethod.name`; re-running `07-29a` then no
|
|
544
|
+
longer sees a `.display` element and inserts a second one. Cleanup was a one-off manual DELETE on
|
|
545
|
+
the developer's local DB (local operator error from re-running seeds), deliberately **not** added
|
|
546
|
+
to dbchanges2. Rule: guard seed elements on a **stable** identifier (labelMessageId / message key),
|
|
547
|
+
not on `config.valueKey` which display-tweak migrations commonly change. (apeterson)
|
|
524
548
|
- 2026-07-31 — **Closed the "audit the cross-DB surface seeds" open item** (TRUE-80487): swept all
|
|
525
549
|
505 non-`HISTORIC` `.sql` files and found **24** cross-tenant references (13 in `Client/`,
|
|
526
550
|
including the `BLANK_CLIENT_DATABASE` new-client template; 3 in `Client_Rate/`; 2 in `Core/`
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
| [Column Visibility (URL-driven show/hide columns)](features/column-visibility.md) | A "Columns" header button that opens a modal listing every column from the table meta, lets the user show/hide columns, adjusts the table live, and persists the | toga25-supply/src/components/ColumnVisibilityModal/, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableData.tsx |
|
|
10
10
|
| [Force Logout on Deployment (useDeploymentGuard)](features/force-logout-on-deployment.md) | On large deployments the backend bumps the Core parameter `META_LAST_REFRESH_DATETIME`. | toga25-supply/src/hooks/useDeploymentGuard.tsx, toga25-supply/src/App.tsx |
|
|
11
11
|
| [Meta-Driven Page & Table Setup](features/meta-driven-table-data.md) | A page in this app is **meta-driven end to end**: the page view model fetches *page meta* (labels, sections, ACL) and *table meta* (the columns/fields + table s | toga25-supply/src/pages/SalesOrders/viewModel/useSalesOrdersPageViewModel.tsx, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrdersTableState.ts, toga25-supply/src/hooks/useTablePageMeta.ts, toga-blox-npm/dist/hooks/useFetchPageMeta.d.ts, toga-blox-npm/dist/hooks/useFetchTablePageMeta.d.ts, toga-blox-npm/dist/hooks/useAssignTableFieldLabels.d.ts, toga-blox-npm/dist/components/Table/hooks/useTableData.d.ts |
|
|
12
|
-
| [Record Modals & Nested Tables](features/record-modals-and-nested-tables.md) | The repo's family of modal + nested-table patterns layered over toga-blox `TableRecordModal` and `PrimaryTable*Layout`. | toga25-supply/src/layout/ItemRecordModalLayout/, toga25-supply/src/layout/SalesOrderRecordModalLayout/, toga25-supply/src/layout/SalesOrderItemsTableLayout/, toga25-supply/src/layout/ItemFulfillmentModal/, toga25-supply/src/layout/GenericNestedTables/GenericNestedTables.tsx, toga25-supply/src/layout/GenericNestedTables/GenericTableLayout.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/DEFAULT/inventoryGroupings.json, toga25-supply/src/hooks/useTableCellInteractions.ts, toga25-supply/src/hooks/useServerTableUrlState.ts |
|
|
12
|
+
| [Record Modals & Nested Tables](features/record-modals-and-nested-tables.md) | The repo's family of modal + nested-table patterns layered over toga-blox `TableRecordModal` and `PrimaryTable*Layout`. | toga25-supply/src/layout/ItemRecordModalLayout/, toga25-supply/src/layout/SalesOrderRecordModalLayout/, toga25-supply/src/layout/SalesOrderItemsTableLayout/, toga25-supply/src/layout/ItemFulfillmentModal/, toga25-supply/src/layout/GenericNestedTables/GenericNestedTables.tsx, toga25-supply/src/layout/GenericNestedTables/GenericTableLayout.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/DEFAULT/inventoryGroupings.json, toga25-supply/src/hooks/useTableCellInteractions.ts, toga25-supply/src/hooks/useServerTableUrlState.ts, toga25-supply/src/layout/RecordApprovalModal/helpers/handleFormatApprovalWorkflowPayload.ts, toga25-supply/src/layout/RecordApprovalModal/api/approvalDecisionsApi.ts |
|
|
13
13
|
| [Surface Frontend (DB-driven UI consumption, src/surface/)](features/surface-frontend.md) | The frontend consumer of the platform-wide Surface layer — DB-driven UI config fetched from `GET /v2/surfaces/meta?slug=<slug>` instead of statically-imported J | toga25-supply/src/surface/applyColSpan.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/sectionRenderers.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getVisibleSections.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderApprovalSummaryGrid.tsx, toga25-supply/src/surface/useFetchSurfaceMeta.ts, toga25-supply/src/pages/SalesOrders/helpers/surfaceBundleToTenantFields.ts, toga25-supply/src/pages/SalesOrders/helpers/buildPatchedTenantFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/viewModel/useSalesOrderRecordModalLayoutModel.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/SalesOrderView.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/layoutComponents/SalesOrderSummaryGrid.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/getDetailSections.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderNotesSection.tsx, toga25-supply/src/pages/SalesOrders/helpers/cleanOrder.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/helpers/index.ts, toga25-supply/src/surface/evaluateSurfaceRule.ts, toga25-supply/src/surface/resolveElementState.ts, toga25-supply/src/pages/SalesOrders/helpers/evaluateEnableRule.ts, toga25-supply/src/pages/SalesOrders/hooks/useSalesOrderRowRecordState.ts, toga25-supply/src/surface/actionRegistry.ts, toga25-supply/src/surface/componentRegistry.tsx, toga25-supply/src/surface/SurfaceActionBar.tsx, toga25-supply/src/surface/SurfaceSection.tsx, toga25-supply/src/surface/resolve.ts, toga25-supply/src/surface/types.ts, toga25-supply/src/surface/index.ts, toga25-supply/src/pages/Login/LoginPage.tsx, toga25-supply/src/pages/SalesOrders/SalesOrders.tsx, toga25-supply/src/pages/SalesOrders/view/SurfaceRowActions.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderRecordModalLayout/view/sections/SalesOrderTopBar.tsx, toga25-supply/src/pages/Items/ItemsPage.tsx, toga25-supply/src/pages/Items/viewModel/useItemsPageViewModel.tsx, toga25-supply/src/pages/VendorItems/VendorItemsPage.tsx, toga25-supply/src/pages/VendorItems/viewModel/useVendorItemsPageViewModel.tsx, toga25-supply/src/pages/Inventory/Inventory.tsx, toga25-supply/src/pages/Inventory/viewModel/useInventoryPageViewModel.tsx, toga25-supply/src/pages/Inventory/viewModel/FIELDS/index.ts, toga25-supply/src/fieldsConfig/index.ts, toga25-supply/src/layout/ItemRecordModalLayout/helpers/surfaceBundleToItemFields.ts, toga25-supply/src/layout/ItemRecordModalLayout/helpers/index.ts, toga25-supply/src/layout/ItemRecordModalLayout/viewModel/useItemRecordModalViewModel.tsx, toga25-supply/src/layout/ItemRecordModalLayout/ItemRecordModalLayout.tsx, toga25-supply/src/layout/ItemRecordModalLayout/components/ItemRecordView.tsx, toga25-supply/src/surface/useStatusColors.ts, toga25-supply/src/surface/SurfaceHeader.tsx, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/helpers/surfaceBundlesToDecisionFields.ts, toga25-supply/src/pages/SalesOrders/view/SalesOrderApprovalModalsLayout/viewModel/useApprovalModalViewModel.tsx |
|
|
14
14
|
| [AWS Amplify Multi-Environment Deployment](workflows/amplify-deployment.md) | How `toga25-supply` deploys to **all** of its environments on AWS Amplify from a **single shared `amplify.yml`**. | toga25-supply/amplify.yml, toga25-supply/src/api/api.ts, toga25-supply/src/hooks/useAuthenticationFlow.ts, toga25-supply/vite.config.ts, toga25-supply/package.json |
|
|
15
15
|
| [Cypress Testing Harness (component + e2e)](workflows/cypress-testing.md) | The Cypress test harness for the `toga25-supply` frontend, bootstrapped from scratch (`cypress` was already a dependency but there was no config, no `cypress/` | toga25-supply/cypress.config.ts, toga25-supply/cypress/tsconfig.json, toga25-supply/cypress/support/component.tsx, toga25-supply/cypress/support/component-index.html, toga25-supply/cypress/support/e2e.ts, toga25-supply/cypress/support/commands.ts, toga25-supply/cypress/support/fixtures.ts, toga25-supply/cypress/support/mocks/useApprovalModalViewModel.ts, toga25-supply/cypress/component/SalesOrderApprovalModalsLayout.cy.tsx, toga25-supply/cypress/component/RecordApprovalModalLayout.cy.tsx, toga25-supply/cypress/component/EnterPoNumberModal.cy.tsx, toga25-supply/cypress/e2e/salesOrderApproval.cy.ts |
|
|
@@ -6,7 +6,7 @@ project: TOGa 2.5 Supply
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
9
|
+
updated: 2026-08-14
|
|
10
10
|
owners: [apeterson]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-supply/src/layout/ItemRecordModalLayout/
|
|
@@ -19,6 +19,8 @@ files:
|
|
|
19
19
|
- toga25-supply/src/pages/Inventory/viewModel/FIELDS/DEFAULT/inventoryGroupings.json
|
|
20
20
|
- toga25-supply/src/hooks/useTableCellInteractions.ts
|
|
21
21
|
- toga25-supply/src/hooks/useServerTableUrlState.ts
|
|
22
|
+
- toga25-supply/src/layout/RecordApprovalModal/helpers/handleFormatApprovalWorkflowPayload.ts
|
|
23
|
+
- toga25-supply/src/layout/RecordApprovalModal/api/approvalDecisionsApi.ts
|
|
22
24
|
related:
|
|
23
25
|
- ../architecture.md
|
|
24
26
|
- meta-driven-table-data.md
|
|
@@ -189,8 +191,25 @@ Scaffold per the `ItemRecordModalLayout` pattern (`src/layout/ItemRecordModalLay
|
|
|
189
191
|
for stages whose `note` / `assignedTo` / `decision` actually changed, and chooses verb by
|
|
190
192
|
`stage.ApprovalDecisions.uuid` — `null` → POST `/approval-decisions`, otherwise PUT
|
|
191
193
|
`/approval-decisions/{uuid}`.
|
|
194
|
+
- **Clearing a stage's assignee is a DELETE, not a dropped field.** The assignee is stored
|
|
195
|
+
**on** the `ApprovalDecisions` record, so to *unassign* a pending stage you must emit a
|
|
196
|
+
**DELETE `/approval-decisions/{uuid}`** — not a PUT with an empty value. The earlier bug: the
|
|
197
|
+
payload builder only added the assignee field when the new value was truthy
|
|
198
|
+
(`if (didAssignedToChanged && formAssignedToUuid)`), so clearing it produced **no** backend
|
|
199
|
+
change and the old assignment survived. Fix (`handleFormatApprovalWorkflowPayload`): emit a
|
|
200
|
+
DELETE item **only** when all three hold — the assignee actually changed to empty, the stage
|
|
201
|
+
has **no decision yet** (`isApproved` null/undefined; a decided/denied/approved or pre-approved
|
|
202
|
+
stage is excluded), and an existing `ApprovalDecisions.uuid` exists to delete.
|
|
203
|
+
`updateApprovalWorkflow` (`approvalDecisionsApi.ts`) gained a `method === "DELETE"` branch
|
|
204
|
+
calling `apiDelete(route)`.
|
|
192
205
|
|
|
193
206
|
## Change history
|
|
207
|
+
- 2026-08-14 — Fixed unassigning an approval-workflow stage: clearing the assignee now emits a
|
|
208
|
+
DELETE `/approval-decisions/{uuid}` (the assignment lives on the `ApprovalDecisions` record)
|
|
209
|
+
instead of silently dropping the field. Gated to fire only for a still-undecided stage
|
|
210
|
+
(`isApproved` null) with an existing decision uuid; `updateApprovalWorkflow` gained a
|
|
211
|
+
`DELETE` branch (`apiDelete`). Added the gotcha + the two RecordApprovalModal files.
|
|
212
|
+
(apeterson)
|
|
194
213
|
- 2026-07-20 — Pattern 5: added `buildSwapTriggerColumns(triggerSwap)` and `getModalUuid(row)` to
|
|
195
214
|
`TableLevel` so a swap level's "drill to child tier" (a dedicated "See units" chevron) and
|
|
196
215
|
"open this record" (plain row click → `renderModal`) are decoupled, and a record modal can open a
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
| Doc | Framework | Summary | Files |
|
|
4
4
|
|-----|-----------|---------|-------|
|
|
5
5
|
| [NYCDOE Ticket Hold-Status Sync (ServiceNow ⇄ TOGaDesk)](features/hold-status-sync.md) | 1.0 | DOE ticket **hold** status must round-trip between ServiceNow (SNOW) and TOGaDesk and **stay held** — holds are SLA-bearing in both systems. | worker/crons/sync/nycdoe/send_ticket_updates.php, worker/crons/sync/nycdoe/process_tickets.php, worker/crons/sync/nycdoe/send_request_item_updates.php, library/app/model/togadesk/repairorder.php, library/app/api/nycdoev2.php, togadesk/desk/includes/classes/class.repair.php |
|
|
6
|
-
| [NYCDOE ServiceNow / ASN Integration](features/servicenow-integration.md) | 1.0 | The NYCDOE/ServiceNow integration mirrors DOE's ServiceNow tickets (Incidents + RITMs) into local tables, turns vendor shipment notices into NetSuite Sales Orde | worker/crons/sync/nycdoe/import_asn.php, worker/crons/sync/nycdoe/import_inc.php, worker/crons/sync/nycdoe/legacy_import_asn.php, worker/crons/sync/nycdoe/legacy_process_asn_queue.php, worker/crons/sync/nycdoe/process_tickets.php, worker/crons/sync/nycdoe/1_send_asn_to_netsuite.php, worker/crons/sync/nycdoe/2_send_serials_to_netsuite.php, worker/crons/sync/nycdoe/3_create_installation_ticket.php, worker/crons/sync/nycdoe/test_multi_po_receipt_resolution.php, worker/crons/sync/nycdoe/send_ticket_updates.php, worker/crons/sync/nycdoe/send_request_item_updates.php, worker/crons/sync/nycdoe/send_nycdoe_proof_of_delivery.php, worker/crons/sync/nycdoe/sync_nycdoe_locations.php, worker/crons/sync/nycdoe/receive_edi_purchase_orders.php, worker/crons/sync/nycdoe/send_edi_open_invoices.php, worker/crons/notifications/nycdoe/, worker/schedules/cron.worker.sync.json, worker/schedules/cron.worker.notification.json, library/app/api/nycdoe.php, library/app/api/nycdoev2.php, library/app/asnprocessor/manufacturer.php, library/app/asnprocessor/apple.php, library/app/asnprocessor/lenovo.php, library/app/asnprocessor/lexmark.php, library/app/asnprocessor/acer.php, library/app/edi.php, library/app/netsuite.php, dbchanges/Core/SK/ |
|
|
6
|
+
| [NYCDOE ServiceNow / ASN Integration](features/servicenow-integration.md) | 1.0 | The NYCDOE/ServiceNow integration mirrors DOE's ServiceNow tickets (Incidents + RITMs) into local tables, turns vendor shipment notices into NetSuite Sales Orde | worker/crons/sync/nycdoe/import_asn.php, worker/crons/sync/nycdoe/import_inc.php, worker/crons/sync/nycdoe/legacy_import_asn.php, worker/crons/sync/nycdoe/legacy_process_asn_queue.php, worker/crons/sync/nycdoe/process_tickets.php, worker/crons/sync/nycdoe/1_send_asn_to_netsuite.php, worker/crons/sync/nycdoe/2_send_serials_to_netsuite.php, worker/crons/sync/nycdoe/3_create_installation_ticket.php, worker/crons/sync/nycdoe/test_multi_po_receipt_resolution.php, worker/crons/sync/nycdoe/send_ticket_updates.php, worker/crons/sync/nycdoe/send_request_item_updates.php, worker/crons/sync/nycdoe/send_nycdoe_proof_of_delivery.php, worker/crons/sync/nycdoe/sync_nycdoe_locations.php, worker/crons/sync/nycdoe/receive_edi_purchase_orders.php, worker/crons/sync/nycdoe/send_edi_open_invoices.php, worker/crons/notifications/nycdoe/, worker/schedules/cron.worker.sync.json, worker/schedules/cron.worker.notification.json, library/app/api/nycdoe.php, library/app/api/nycdoev2.php, library/app/nycdoe.php, library/app/asnprocessor/manufacturer.php, library/app/asnprocessor/apple.php, library/app/asnprocessor/lenovo.php, library/app/asnprocessor/lexmark.php, library/app/asnprocessor/acer.php, library/app/edi.php, library/app/netsuite.php, dbchanges/Core/SK/, dbchanges/TOGaDeskSupport/SK/ |
|
|
7
7
|
| [New York City Department of Education](profile.md) | 1.0 | NYC DOE (New York City Department of Education) is a TOGA client whose entire integration runs in the **1.0 worker tier** (~30 cron scripts under `worker/crons/ | |
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: nycdoe
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-08-
|
|
9
|
+
updated: 2026-08-14
|
|
10
10
|
owners: [mhammontree, sking]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/sync/nycdoe/import_asn.php
|
|
@@ -29,6 +29,7 @@ files:
|
|
|
29
29
|
- worker/schedules/cron.worker.notification.json
|
|
30
30
|
- library/app/api/nycdoe.php
|
|
31
31
|
- library/app/api/nycdoev2.php
|
|
32
|
+
- library/app/nycdoe.php
|
|
32
33
|
- library/app/asnprocessor/manufacturer.php
|
|
33
34
|
- library/app/asnprocessor/apple.php
|
|
34
35
|
- library/app/asnprocessor/lenovo.php
|
|
@@ -37,6 +38,7 @@ files:
|
|
|
37
38
|
- library/app/edi.php
|
|
38
39
|
- library/app/netsuite.php
|
|
39
40
|
- dbchanges/Core/SK/
|
|
41
|
+
- dbchanges/TOGaDeskSupport/SK/
|
|
40
42
|
related:
|
|
41
43
|
- ../profile.md
|
|
42
44
|
- hold-status-sync.md
|
|
@@ -250,10 +252,29 @@ Vendor SFTP ───(legacy_import_asn.php, ser+non-ser)─┘ [UNIQUE ded
|
|
|
250
252
|
item/unit lookup keys (built from the truncated DB value, compared against the full
|
|
251
253
|
in-memory string), and two long descriptions sharing their first 64 characters collapse
|
|
252
254
|
into the same item.
|
|
255
|
+
- **It is also the root cause of the DOE missing-asset-tag class** (traced 2026-08-13). Because
|
|
256
|
+
the shift is +1, `assetTag ← $cols[23]` picks up the **serial**, and the **real asset-tag
|
|
257
|
+
column `$cols[24]` is never read at all** — so no unit from an off-layout file can ever get a
|
|
258
|
+
correct tag. Further collateral on the same rows: `workPhone` holds a date and `shipZipCode`
|
|
259
|
+
holds a person's name. Treat any Lenovo missing-tag report as this bug until proven otherwise,
|
|
260
|
+
and fix the parser rather than only backfilling.
|
|
253
261
|
- **Remediation is MANUAL data repair — there is no automated recovery.** Correct
|
|
254
262
|
`Core.AdvanceShippingNoticeItems.partNumber` on the affected items to the real NetSuite
|
|
255
263
|
OEM SKU (replacing the description the malformed file supplied); the pipeline then
|
|
256
264
|
proceeds normally. Done this way for ASN `26720` on 2026-08-11.
|
|
265
|
+
- **Asset-tag repair is a SEPARATE, TWO-STORE backfill** — see the derived-`assets.tag` gotcha
|
|
266
|
+
below. Generate the SQL **mechanically** from the customer's serial→tag CSV (never hand-
|
|
267
|
+
transcribe), match serials through `App_NYCDOE::serialKey()` (the leading-`S` gotcha below),
|
|
268
|
+
scope the statements to the ASN/MSO, and fill **only still-empty** tags so the file is
|
|
269
|
+
re-runnable. Done this way for ASN `26720` / PO `S202648137` / MSO `329924` (87 units) on
|
|
270
|
+
2026-08-13, PR dbchanges#265.
|
|
271
|
+
- **Known remaining damage from this defect** (each needs its own customer serial→tag list):
|
|
272
|
+
ASN `26641` / PO `S202668066` — **162 of 363** units still tagless; ASN `26706` /
|
|
273
|
+
PO `WR270003276` — **1 of 7**.
|
|
274
|
+
- **A fixture-based regression test is owed with the parser fix**: assert that serial, quantity
|
|
275
|
+
and assetTag each land in the right field for **both** layouts (do not detect by column count
|
|
276
|
+
alone). ⚠ Do not quote constants out of `asnprocessor/lenovo.php` into a doc or ticket — that
|
|
277
|
+
file contains hardcoded FTP credentials.
|
|
257
278
|
- **Recommended future code fixes (proposed, NOT approved, NOT implemented):** map columns
|
|
258
279
|
by **header name** and hard-reject an unknown layout with a loud alert; stop building the
|
|
259
280
|
dedupe key from a field that can be a constant; widen
|
|
@@ -266,6 +287,30 @@ Vendor SFTP ───(legacy_import_asn.php, ser+non-ser)─┘ [UNIQUE ded
|
|
|
266
287
|
`worker/crons/toga2/**` against the toga2 `AdvanceShippingNoticeItemUnits` schema, while
|
|
267
288
|
the DOE path is `Core.AdvanceShippingNoticeUnits` via `asnprocessor/lenovo.php`.
|
|
268
289
|
|
|
290
|
+
- **⚠ `TOGaDeskSupport.assets.tag` is DERIVED from `Core.AdvanceShippingNoticeUnits.assetTag` — an
|
|
291
|
+
asset-tag repair MUST touch BOTH stores.** The TOGa Desk UI renders `assets.tag`, but that column
|
|
292
|
+
is *populated from* the ASN unit's `assetTag` at install-ticket creation
|
|
293
|
+
(`3_create_installation_ticket.php` ~L150). Fixing only `assets.tag` makes the UI look correct
|
|
294
|
+
while the ASN record stays blank, so every ASN-driven consumer — reports, NetSuite / ServiceNow
|
|
295
|
+
transmissions, proof of delivery — still sees no tag. This is the trap that makes a half-fix look
|
|
296
|
+
like a full fix, and it compounds with the `dbchanges` **cross-database silent skip**: `Core/` and
|
|
297
|
+
`TOGaDeskSupport/` are different databases on different clusters, so the repair is **two separate
|
|
298
|
+
runs** and an applier that picks up one directory reports success on a 50% fix (confirmed live
|
|
299
|
+
2026-08-13: `assets.tag` 87/87 correct while `Core…Units.assetTag` was 87/87 empty). Always
|
|
300
|
+
verify **per-pair** (serial ↔ tag) in **both** stores, never a non-null count. See the
|
|
301
|
+
[1.0 dbchanges authoring workflow](../../../1.0/apps/dbchanges/workflows/authoring-and-shipping-sql-files.md).
|
|
302
|
+
|
|
303
|
+
- **The leading `S` on Lenovo/Lexmark serials is stripped ON PURPOSE — do not "correct" it.**
|
|
304
|
+
Customer and vendor lists show ThinkPad serials as `SA00HZ20` while the DB stores `A00HZ20`: the
|
|
305
|
+
importer deliberately strips **one** leading capital `S` for Lenovo and Lexmark
|
|
306
|
+
(`legacy_process_asn_queue.php` ~L269-274). This is intended normalization, **not corruption** —
|
|
307
|
+
re-adding the `S` to stored serials would break the dedupe lookups that rely on the stripped form.
|
|
308
|
+
- Always compare via **`App_NYCDOE::serialKey()`** (`library/app/nycdoe.php` ~L51 =
|
|
309
|
+
`normalizeSerial()` then `stripLeadingSerialS()`), never a raw string compare.
|
|
310
|
+
- **Never** `REPLACE(serial,'S','')` — that strips *every* `S`, not just the leading one.
|
|
311
|
+
- Concretely: 25 of the 87 serials in the 2026-08-13 asset-tag backfill would have silently
|
|
312
|
+
failed to match without this.
|
|
313
|
+
|
|
269
314
|
- **The "Outstanding Sales Orders - PO Reconciliation Required" email is FLEET-WIDE, not a
|
|
270
315
|
per-ASN alert** (`2_send_serials_to_netsuite.php:270-357`). It runs at the end of every
|
|
271
316
|
5-minute run and re-queries **all** `AdvanceShippingNoticeItems` with
|
|
@@ -518,6 +563,22 @@ use the toga DB MCP + `Logs.API` instead of running prod code locally.
|
|
|
518
563
|
of the consumer query; `php -l` every touched file.
|
|
519
564
|
|
|
520
565
|
## Change history
|
|
566
|
+
- 2026-08-14 — Tied the DOE **missing-asset-tag** class to the existing "Lenovo Off-Layout ASN File"
|
|
567
|
+
bug: the +1 shift puts the serial in `assetTag` and means the real asset-tag column (`$cols[24]`)
|
|
568
|
+
is **never read**, so no unit from an off-layout file can get a tag (still **open** — needs the
|
|
569
|
+
parser fix plus a fixture regression test covering both layouts). Backfilled 87 tags for ASN
|
|
570
|
+
`26720` / PO `S202648137` / MSO `329924` (client 16, P.S. 235 Lenox School) in **both** stores
|
|
571
|
+
(`dbchanges/Core/SK/2026-08-13-backfill-asn-asset-tags-S202648137.sql`,
|
|
572
|
+
`dbchanges/TOGaDeskSupport/SK/2026-08-13-backfill-asset-tags-S202648137.sql`, PR dbchanges#265,
|
|
573
|
+
branch `fix/backfill-asset-tags-s202648137` → `_production`); SQL generated mechanically from the
|
|
574
|
+
customer serial→tag CSV, 87/87 verified against the live order before generation and round-trip
|
|
575
|
+
verified byte-identical, scoped to the ASN/MSO and filling only still-empty tags, so re-runnable.
|
|
576
|
+
Documented that `TOGaDeskSupport.assets.tag` is **derived** from
|
|
577
|
+
`Core.AdvanceShippingNoticeUnits.assetTag` (both must be repaired; confirmed live half-fix where
|
|
578
|
+
the UI was 87/87 right and the ASN 87/87 empty) and that the leading `S` on Lenovo/Lexmark serials
|
|
579
|
+
is stripped **on purpose** (compare via `App_NYCDOE::serialKey()`; 25/87 depended on it).
|
|
580
|
+
Remaining damage from the same defect: ASN `26641`/PO `S202668066` (162/363) and ASN `26706`/PO
|
|
581
|
+
`WR270003276` (1/7). No code changes in `worker`/`library`/`togadesk`. (sking)
|
|
521
582
|
- 2026-08-11 — Investigated (read-only, **no code change**) ASN `26720` / PO `S202648137` never completing: root-caused the **"Lenovo Off-Layout ASN File"** bug — `asnprocessor/lenovo.php` maps columns by COUNT with no header validation, so the 31-column `DOE-Open-Lenovo-*.csv` shifts every field +1, making `serialNumber` the constant `"1"`, which collapses 87 source lines into 3 `completed` queue rows and loses units with no error/alert. Documented the diagnostic fingerprint, the 13-row systemic blast radius, the `varchar(64)` partNumber truncation amplifier, and the **manual** partNumber data repair as the only remediation. Also documented that the "Outstanding Sales Orders - PO Reconciliation Required" email is fleet-wide (and its count inflated), ruled out the Quad ASN work, and left the ASN `26720` items `54672`/`54673` correct-serial units as UNRESOLVED. (mhammontree)
|
|
522
583
|
- 2026-08-06 — Named and cross-referenced the umbrella failure pattern **"Pre-ASN Manual
|
|
523
584
|
Receipt Mismatch"** (aka "Shipment Received Notice") tying together the existing
|
package/package.json
CHANGED