toga-ai 1.0.445 → 1.0.447
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/_underscore/INDEX.md +2 -2
- package/knowledge/2.0/apps/_underscore/features/acl-permission-chain.md +32 -1
- package/knowledge/2.0/apps/_underscore/workflows/local-db-refresh-from-beta.md +14 -2
- package/knowledge/2.0/apps/_underscore/workflows/running-a-2.0-app-locally.md +15 -1
- package/knowledge/2.0/apps/toga2-supply/INDEX.md +1 -1
- package/knowledge/2.0/apps/toga2-supply/features/fulfill-and-ship.md +77 -21
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [Proposed — git-sourced base+overlay JSON authoring for the Surface layer](architecture/surface-authoring-proposal.md) | A **proposal / handoff recommendation** (not implemented) that the Surface layer's *authoring* model move off hand-authored SQL against the `SurfaceOverrides` E | _underscore/Model/Core/Surface.php, _underscore/Model/Client/SurfaceOverride.php |
|
|
6
6
|
| [_underscore Framework Architecture](architecture.md) | `_underscore` is the shared PHP backend framework for **all 2.0 applications**. | _underscore/_underscore.php, _underscore/Loader.php, _underscore/Framework.php, _underscore/Model.php, _underscore/Database.php, _underscore/Query.php, _underscore/Route.php, _underscore/Component.php |
|
|
7
|
-
| [ACL Permission Chain (Record & Field Authorization)](features/acl-permission-chain.md) | Authorization in the 2.0 API is **metadata-driven**: whether a role may Create/Read/Update/Delete a record is decided by rows across **four linked tables**, not | api2/Component/Api/V2/V2.php, _underscore/Model/Core/Page.php, _underscore/Model/Client/TrackingNumber.php, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Client/2026-06-23b - ItemTranslationsAcl.sql, dbchanges2/Client/2026-07-02c - TrackingNumberNeedsReturnLabelFieldPermission.sql |
|
|
7
|
+
| [ACL Permission Chain (Record & Field Authorization)](features/acl-permission-chain.md) | Authorization in the 2.0 API is **metadata-driven**: whether a role may Create/Read/Update/Delete a record is decided by rows across **four linked tables**, not | api2/Component/Api/V2/V2.php, _underscore/Model/Core/Page.php, _underscore/Model/Client/TrackingNumber.php, dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql, dbchanges2/Client/2026-06-23b - ItemTranslationsAcl.sql, dbchanges2/Client/2026-07-02c - TrackingNumberNeedsReturnLabelFieldPermission.sql, dbchanges2/Client/2026-07-22c - TrackingNumberMeasureIdsFieldPermission.sql |
|
|
8
8
|
| [Address Validation (carrier waterfall + validateAddress scripted endpoint)](features/address-validation.md) | `_Model_Client_Address::validateAddress` verifies a US address against a **carrier waterfall (USPS → FedEx → UPS)** and returns a single canonical, carrier-norm | _underscore/Model/Client/Address.php |
|
|
9
9
|
| [_ApiRequest ENCODE__JSON now sends Content-Type: application/json](features/apirequest-json-content-type.md) | `_ApiRequest::execute()`'s `ENCODE__JSON` branch json-encoded the request body but never set a `Content-Type` header. | _underscore/ApiRequest.php |
|
|
10
10
|
| [Assortment Name Translation (AssortmentTranslations sidecar)](features/assortment-name-translation.md) | Serves Assortment (product-grouping) **names** in multiple languages by adding a per-language **sidecar** table `AssortmentTranslations`, reusing the platform's | _underscore/Model/Client/AssortmentTranslation.php, dbchanges2/Client/2026-06-26a - AssortmentTranslations.sql, dbchanges2/Core/2026-06-26a - AssortmentTranslationsRecord.sql, dbchanges2/Client/2026-06-26b - AssortmentTranslationsAcl.sql |
|
|
@@ -30,4 +30,4 @@
|
|
|
30
30
|
| [Tracking-Number Bridge Migration (ASN / Item Fulfillment / Item Receipt)](features/tracking-number-bridges.md) | Shipment tracking numbers used to live as **scalar FK columns** (`trackingNumberId`, `returnTrackingNumberId`) directly on the lowest-level "unit"/"item" tables | api2/Component/Api/V2/V2.php, _underscore/Model/Client/AdvanceShippingNoticeItemUnit.php, _underscore/Model/Client/AdvanceShippingNoticeItemUnits/TrackingNumber.php, _underscore/Model/Client/ItemFulfillmentItemUnits/TrackingNumber.php, _underscore/Model/Client/ItemFulfillment.php, _underscore/Model/Prudential/AdvanceShippingNotice.php, _underscore/Model/Compass/AdvanceShippingNotice.php, _underscore/Trait/Netsuite/ItemFulfillment.php, api2/Component/Api/Cxml/Cxml.php, dbchanges2/Client/2026-06-10 - TrackingNumberBridges.sql, dbchanges2/Core/2026-06-10 - TrackingNumberBridges.sql, dbchanges2/Client_Prudential/2026-06-15 - ItemFulfillmentTrackingNumberAclLogicGroups.sql, dbchanges2/Client_Quad/2026-06-18a - ItemFulfillmentItemReceiptTrackingNumberAclLogicGroups.sql, dbchanges2/Client_Nychh/2026-06-19b - ItemFulfillmentItemReceiptTrackingNumberAclLogicGroups.sql, dbchanges2/Client_Growrk/2026-07-13a - ItemFulfillmentItemReceiptTrackingNumberAclLogicGroups.sql, dbchanges2/Client_Nychh/2026-06-19a - FixItemFulfillmentTableViewTrackingAndRoot.sql, dbchanges2/Client_Quad/2026-06-19a - FixItemFulfillmentTableViewTrackingAndRoot.sql |
|
|
31
31
|
| [Units for Items for Purchase Orders — Data Structure](features/units-for-items-for-purchase-orders.md) | Describes how unit (serialized inventory) data is linked to sales-order and purchase-order line items behind the `units-for-items-for-purchase-orders` TableView | |
|
|
32
32
|
| [Refreshing a Local Dev Database from Beta (dev-sandbox)](workflows/local-db-refresh-from-beta.md) | How to reset a local 2.0 dev database from the **beta / dev-sandbox** environment: dump each schema (`Core`, `Client_<Id>`, `Logs_<Id>`, …) from the beta host, | api2/Config/, _underscore/Loader.php, _underscore/Model/Client/BundleTranslation.php, api2/Component/Api/V2/V2.php, toga25-supply/sync_compasscanada_schema.sql |
|
|
33
|
-
| [Running a 2.0 App Locally (browser, end-to-end via api2)](workflows/running-a-2.0-app-locally.md) | The full dependency chain required to run a 2.0 client app **through the browser**, end-to-end, against a **local `api2`** (e.g. | _underscore/Environment.php, _underscore/Config.php, _underscore/Database.php, _underscore/Route.php, api2/Component/Api/V2/V2.php, api2/index.php, api2/.htaccess |
|
|
33
|
+
| [Running a 2.0 App Locally (browser, end-to-end via api2)](workflows/running-a-2.0-app-locally.md) | The full dependency chain required to run a 2.0 client app **through the browser**, end-to-end, against a **local `api2`** (e.g. | _underscore/Environment.php, _underscore/Config.php, _underscore/Database.php, _underscore/Route.php, api2/Component/Api/V2/V2.php, api2/Controller/Index.php, api2/index.php, api2/.htaccess |
|
|
@@ -6,7 +6,7 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-27
|
|
10
10
|
owners: ["jcardinal", "mhammontree", "tcox"]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
@@ -15,6 +15,7 @@ files:
|
|
|
15
15
|
- dbchanges2/Client/2026-06-03- BLANK_CLIENT_DATABASE.sql
|
|
16
16
|
- dbchanges2/Client/2026-06-23b - ItemTranslationsAcl.sql
|
|
17
17
|
- dbchanges2/Client/2026-07-02c - TrackingNumberNeedsReturnLabelFieldPermission.sql
|
|
18
|
+
- dbchanges2/Client/2026-07-22c - TrackingNumberMeasureIdsFieldPermission.sql
|
|
18
19
|
---
|
|
19
20
|
|
|
20
21
|
## Summary
|
|
@@ -53,6 +54,16 @@ writable). Without it the field is rejected on write **and** is unreadable:
|
|
|
53
54
|
fully registered (`Core.RecordFields` row present, so no `EV-8`) yet still `EZ-2` because it has
|
|
54
55
|
no `AclFieldPermissions` row — **registering a field and granting access to it are two separate
|
|
55
56
|
steps**, and shipping the field registration without the grant is a recurring launch blocker.
|
|
57
|
+
- **⚠ The V2 GET response's OUTPUT field list is itself BUILT from `AclFieldPermissions`, so a field
|
|
58
|
+
with no grant is often SILENTLY OMITTED from the response body — not surfaced as an `EZ-2` error.**
|
|
59
|
+
`_Component_Api_V2::getAclFieldPermissions()` (`api2/Component/Api/V2/V2.php` ~L5795) resolves the
|
|
60
|
+
caller's readable/writable set, and the output serializers (`foreach ($aclFieldPermissions as $field
|
|
61
|
+
=> $isWritable)` output loops, ~L3181 / L6082 / L6302) iterate **only** that set — a `RecordField`
|
|
62
|
+
with no grant row for the caller's role never enters the list, so it just isn't in the JSON (200,
|
|
63
|
+
no `messages[]`). This is **beyond the known EV-9 (write) rule: `AclFieldPermissions` gates READ
|
|
64
|
+
VISIBILITY too.** Concrete symptom (TRUE-79191): `Measures.measureType` was registered but ungranted,
|
|
65
|
+
so it came back absent and the frontend unit dropdowns were empty. When a field renders blank with no
|
|
66
|
+
error, check for a missing `AclFieldPermissions` grant before suspecting the selector or the data.
|
|
56
67
|
- **`AclFieldPermissions` is `UNIQUE (recordFieldId, roleId)`** (Client blank DDL) — guard inserts
|
|
57
68
|
with `NOT EXISTS`. For a **read-only, server-written** field (one populated by a `postPost`
|
|
58
69
|
interceptor rather than the API caller), grant `isWritable = 0`.
|
|
@@ -168,6 +179,16 @@ rejects the write with a **specific code** (see the
|
|
|
168
179
|
writable sibling field** on the same record — pick a sibling that actually has explicit
|
|
169
180
|
`AclFieldPermissions` rows (e.g. on `TrackingNumbers`, `containsBattery` has them, whereas
|
|
170
181
|
`requiresSignature` had none), and resolve `roleId` by subselect.
|
|
182
|
+
**⚠ When the grant FANS OUT across ALL client DBs, mirror a STABLE, universally-present sibling —
|
|
183
|
+
not a recently-added one.** A migration that copies a sibling's grants only works on a client where
|
|
184
|
+
that sibling *itself* has grants. Mirror a long-established field (e.g. the FK `shippingMethodId`)
|
|
185
|
+
whose `AclFieldPermissions` rows exist in **every** client DB. Mirroring a newly-added field
|
|
186
|
+
**fails silently** on clients where that field's own grants haven't propagated: TRUE-79191's
|
|
187
|
+
measure-id grant first mirrored `needsReturnLabel` and ended up ungranted on `Client_True`
|
|
188
|
+
(verified: `Client_True` had `shippingMethodId` grants but **no** `needsReturnLabel` grant) → the
|
|
189
|
+
new field was EV-9 on write / omitted on read there. Because the clone reads each client's **own**
|
|
190
|
+
sibling rows, it also correctly adapts to that client's role set (Growrk role 1; Compass roles
|
|
191
|
+
1,3,4) — another reason to resolve by subselect, never hardcode.
|
|
171
192
|
4. **Generated model-class declaration** (`_underscore/Model/Client/<Name>.php`) — the field must
|
|
172
193
|
be declared on the **generated** PHP model class, e.g.
|
|
173
194
|
`public $needsReturnLabel = self::FIELD_BOOLEAN;`. **Without it the API returns `EO-1`**
|
|
@@ -206,6 +227,16 @@ the field.
|
|
|
206
227
|
|
|
207
228
|
## Change history
|
|
208
229
|
|
|
230
|
+
- **2026-07-22** — TRUE-79191: documented that the V2 GET **OUTPUT** field list is itself built from
|
|
231
|
+
`AclFieldPermissions` (`getAclFieldPermissions` + the output `foreach` loops in
|
|
232
|
+
`api2/.../V2.php`), so an **ungranted field is SILENTLY OMITTED** from the response (200, no
|
|
233
|
+
`messages[]`) — read visibility is gated, not only writes (beyond the EV-9 write rule). Symptom:
|
|
234
|
+
`Measures.measureType` absent → empty unit dropdowns. Also added the **cross-client fan-out rule**:
|
|
235
|
+
a grant migration that clones a sibling must mirror a **stable, universally-present** sibling
|
|
236
|
+
(e.g. `shippingMethodId`), not a recently-added one — mirroring `needsReturnLabel` left the new
|
|
237
|
+
measure-id field ungranted on `Client_True` (which had `shippingMethodId` grants but no
|
|
238
|
+
`needsReturnLabel` grant); cloning each client's own rows also adapts to its role set
|
|
239
|
+
(Growrk role 1; Compass 1,3,4). (mhammontree)
|
|
209
240
|
- **2026-07-23** — TRUE-79533: noted that a **read-only field only needs a grant on the role(s) that
|
|
210
241
|
actually read it** — mirror a sibling field of the same kind the readers already consume rather than
|
|
211
242
|
granting every role. The standard FK `serviceAddressId` mirrored `saleItemId`'s reader, **role 1** in
|
|
@@ -6,8 +6,8 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
10
|
-
owners: ["apeterson"]
|
|
9
|
+
updated: 2026-07-27
|
|
10
|
+
owners: ["apeterson", "mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Config/
|
|
13
13
|
- _underscore/Loader.php
|
|
@@ -87,6 +87,14 @@ Order matters — dump first so a failed dump never wipes a local schema it can'
|
|
|
87
87
|
`Client_CompassCanada` selected as the active DB in your client — a standalone `USE` line run
|
|
88
88
|
as a separate statement does **not** carry over, and the tables land in the wrong schema. See
|
|
89
89
|
[tracking-number-bridges](../features/tracking-number-bridges.md) for the bridge tables.
|
|
90
|
+
- **⚠ Pulling updated models from prod WITHOUT applying the matching `dbchanges2` migrations locally
|
|
91
|
+
→ `1054 Unknown column`.** The mirror image of the missing-model 500 above: if you `git pull`
|
|
92
|
+
`_underscore` models that reference a column a **newer migration** adds, but your local client DB
|
|
93
|
+
hasn't run that migration, queries fail with `1054 Unknown column`. Concrete case:
|
|
94
|
+
`Items.isFulfillable` (added by the 2026-07-17 migration) — the pulled Item model selected the
|
|
95
|
+
column before the local schema had it. **Fix:** apply the pending `dbchanges2` migrations in date
|
|
96
|
+
order, or reset the local client DB(s) from a beta snapshot (the steps above). Keep local
|
|
97
|
+
`_underscore` **and** the local schema on the same migration point.
|
|
90
98
|
- **The logs-DB write trap still applies.** A reset only refreshes the schemas you loop over; if
|
|
91
99
|
you skip `Logs_<Id>` the first request 500s (`Unknown database 'logs_<id>'`) on the logging
|
|
92
100
|
write. See [per-client DB connections](../features/per-client-database-connections.md).
|
|
@@ -97,6 +105,10 @@ Order matters — dump first so a failed dump never wipes a local schema it can'
|
|
|
97
105
|
|
|
98
106
|
## Change history
|
|
99
107
|
|
|
108
|
+
- 2026-07-27 — TRUE-79191: added the reverse-drift gotcha — pulling updated `_underscore` models from
|
|
109
|
+
prod **without** applying the matching `dbchanges2` migrations locally throws `1054 Unknown column`
|
|
110
|
+
(e.g. `Items.isFulfillable` from the 2026-07-17 migration); apply pending migrations or reset the
|
|
111
|
+
local client DB from a beta snapshot. (mhammontree)
|
|
100
112
|
- 2026-07-09 — Initial capture: non-locking `mysqldump` recipe (beta `admin` lacks `RELOAD`), the
|
|
101
113
|
VPN-only beta host, and the two recurring post-reset side-effects — a missing `_Model_Client_*`
|
|
102
114
|
autoload 500 (pull the model file, no cache) and the CompassCanada 17-table drift
|
|
@@ -6,7 +6,7 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: workflow
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-27
|
|
10
10
|
owners: ["mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Environment.php
|
|
@@ -14,6 +14,7 @@ files:
|
|
|
14
14
|
- _underscore/Database.php
|
|
15
15
|
- _underscore/Route.php
|
|
16
16
|
- api2/Component/Api/V2/V2.php
|
|
17
|
+
- api2/Controller/Index.php
|
|
17
18
|
- api2/index.php
|
|
18
19
|
- api2/.htaccess
|
|
19
20
|
related:
|
|
@@ -95,6 +96,15 @@ Checked in `V2.php` (~lines 485–800):
|
|
|
95
96
|
|
|
96
97
|
## Gotchas / known issues
|
|
97
98
|
|
|
99
|
+
- **⚠ On a locked-down Windows dev box, PCRE JIT crashes EVERY api2 route with a 500.** During api2
|
|
100
|
+
bootstrap (`Controller/Index.php`), `Sentry\init()` runs a `preg_split()` that fails with
|
|
101
|
+
**`preg_split(): Allocation of JIT memory failed, PCRE JIT will be disabled`** — on a hardened
|
|
102
|
+
Windows Enterprise machine the OS refuses the executable-memory (W^X) allocation the PCRE JIT needs.
|
|
103
|
+
The `_underscore` error handler **escalates that warning to a fatal**, so **every V2 route returns
|
|
104
|
+
500**, masquerading as a code bug. **Fix (local only): set `pcre.jit=0` in `php.ini` and restart
|
|
105
|
+
Apache.** Nothing to ship — beta/prod PHP is permitted to allocate executable memory, so the JIT
|
|
106
|
+
works there and there is no code change.
|
|
107
|
+
|
|
98
108
|
- **The silent `Route.php:525` error** — `"Failed to determine how to render view for route
|
|
99
109
|
'/v2/...'"` (thrown at `_underscore/Route.php:525`) means the API handler returned **no**
|
|
100
110
|
response, so `_Route` fell through to view rendering. It is thrown during bootstrap
|
|
@@ -118,6 +128,10 @@ Checked in `V2.php` (~lines 485–800):
|
|
|
118
128
|
|
|
119
129
|
## Change history
|
|
120
130
|
|
|
131
|
+
- 2026-07-27 — TRUE-79191: added the **PCRE JIT** gotcha — on a locked-down Windows Enterprise dev
|
|
132
|
+
box `Sentry\init()` (`api2/Controller/Index.php`) hits `preg_split(): Allocation of JIT memory
|
|
133
|
+
failed`, which the `_underscore` error handler escalates to a fatal, 500ing **every** V2 route;
|
|
134
|
+
fix is `pcre.jit=0` in `php.ini` + Apache restart (local only — nothing to ship). (mhammontree)
|
|
121
135
|
- 2026-07-09 — Documented the full local browser-run dependency chain (Apache `ENVIRONMENT`,
|
|
122
136
|
`dev-*`→`dev` slug collapse, Core-driven client-DB registration, `/v2/auth/login` browser gate,
|
|
123
137
|
the `Route.php:525` silent-fallthrough gotcha, and the stale-Core "missing model file" fatal),
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
5
|
| [TOGa Supply (toga2-supply) Architecture](architecture.md) | `toga2-supply` is the **React + Vite frontend** for TOGa Supply — warehouse fulfillment tooling (shipment selection, fulfill & ship against carrier APIs, NetSui | toga2-supply/src/api/toga.ts, toga2-supply/src/pages/ShipmentItems/view/ShipmentItemsPage.tsx, toga2-supply/src/pages/EditShipment/view/EditShipmentPage.tsx, toga2-supply/src/pages/EditShipment/api/UpdateShipmentApi.ts, toga2-supply/src/pages/Shipments/view/components/ShipmentsCardTableForm/ShipmentsCardTableForm.tsx |
|
|
6
|
-
| [Fulfill & Ship](features/fulfill-and-ship.md) | Fulfill & Ship lets a warehouse user select sales-order line items, enter serials, pick a carrier/method, and in one action: create the Item Fulfillment records | toga2-supply/src/components/ui/Tables/BasicTable/BasicTable.tsx, toga2-supply/src/components/ui/Tables/types.ts, toga2-supply/src/components/ui/GoogleMapsLink.tsx, toga2-supply/src/pages/ShipmentItems/view/forms/ShipmentItemsTable.tsx, toga2-supply/src/pages/ShipmentItems/api/ShipmentItemsApi.ts, toga2-supply/src/pages/ShipmentItems/types.ts, toga2-supply/src/pages/EditShipment/viewModel/FIELDS/RETURNLABELFIELDS.json, toga2-supply/src/pages/EditShipment/view/components/forms/EditShipmentForm.tsx, toga2-supply/src/components/ui/BaseInput/UnitSelect.tsx, toga2-supply/src/pages/EditShipment/view/modals/SerialNumbersModal.tsx, toga2-supply/src/pages/ShipmentItems/view/ShipmentItemsPage.tsx, toga2-supply/src/pages/EditShipment/view/EditShipmentPage.tsx, toga2-supply/src/pages/EditShipment/view/components/forms/EditShipmentForm.tsx, toga2-supply/src/pages/EditShipment/view/components/SelectedShipmentItemsTable.tsx, toga2-supply/src/pages/EditShipment/view/helpers/ShipmentDetailsForm/renderEditShipmentFormInput.tsx, toga2-supply/src/pages/EditShipment/viewModel/FIELDS/DUMMYUPDATESHIPMENTFIELDS.json, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/renderEditShipmentFormInput.tsx, toga2-supply/tailwind.config.cjs, toga2-supply/src/pages/EditShipment/view/modals/ReturnShippingModal.tsx, toga2-supply/src/styles/index.scss, toga2-supply/src/components/ui/BaseInput/BaseInput.tsx, toga2-supply/src/pages/EditShipment/api/UpdateShipmentApi.ts, toga2-supply/src/pages/EditShipment/viewModel/signatureTypes.ts, toga2-supply/src/pages/EditShipment/view/modals/SelectReturnAddressModal.tsx, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/formatShipmentData.ts, toga2-supply/src/pages/EditShipment/types.ts, toga2-supply/src/pages/Shipments/view/ShipmentsPage.tsx, toga2-supply/src/pages/Shipments/view/components/ShipmentsCardTableForm/ShipmentsCardTableForm.tsx, toga2-supply/src/pages/Shipments/api/ShipmentsApi.ts, toga2-supply/src/pages/Shipments/types.ts, toga2-supply/src/pages/FulfilledShipments/view/FulfilledShipmentsPage.tsx, toga2-supply/src/components/ui/CardTable/CardTable.tsx, toga2-supply/src/components/ui/CardTable/types.ts, toga2-supply/src/assets/pen-line.svg, _underscore/Model/Client/ItemFulfillment.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Component/Library/Carriers/Ups/Ups.php |
|
|
6
|
+
| [Fulfill & Ship](features/fulfill-and-ship.md) | Fulfill & Ship lets a warehouse user select sales-order line items, enter serials, pick a carrier/method, and in one action: create the Item Fulfillment records | _underscore/Model/Client/Measure.php, _underscore/Model/Client/TrackingNumber.php, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/getEditShipmentFormOptions.ts, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/validateFormOnSubmit.ts, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/checkDimensions.ts, toga2-supply/src/components/ui/Tables/BasicTable/BasicTable.tsx, toga2-supply/src/components/ui/Tables/types.ts, toga2-supply/src/components/ui/GoogleMapsLink.tsx, toga2-supply/src/pages/ShipmentItems/view/forms/ShipmentItemsTable.tsx, toga2-supply/src/pages/ShipmentItems/api/ShipmentItemsApi.ts, toga2-supply/src/pages/ShipmentItems/types.ts, toga2-supply/src/pages/EditShipment/viewModel/FIELDS/RETURNLABELFIELDS.json, toga2-supply/src/pages/EditShipment/view/components/forms/EditShipmentForm.tsx, toga2-supply/src/components/ui/BaseInput/UnitSelect.tsx, toga2-supply/src/pages/EditShipment/view/modals/SerialNumbersModal.tsx, toga2-supply/src/pages/ShipmentItems/view/ShipmentItemsPage.tsx, toga2-supply/src/pages/EditShipment/view/EditShipmentPage.tsx, toga2-supply/src/pages/EditShipment/view/components/forms/EditShipmentForm.tsx, toga2-supply/src/pages/EditShipment/view/components/SelectedShipmentItemsTable.tsx, toga2-supply/src/pages/EditShipment/view/helpers/ShipmentDetailsForm/renderEditShipmentFormInput.tsx, toga2-supply/src/pages/EditShipment/viewModel/FIELDS/DUMMYUPDATESHIPMENTFIELDS.json, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/renderEditShipmentFormInput.tsx, toga2-supply/tailwind.config.cjs, toga2-supply/src/pages/EditShipment/view/modals/ReturnShippingModal.tsx, toga2-supply/src/styles/index.scss, toga2-supply/src/components/ui/BaseInput/BaseInput.tsx, toga2-supply/src/pages/EditShipment/api/UpdateShipmentApi.ts, toga2-supply/src/pages/EditShipment/viewModel/signatureTypes.ts, toga2-supply/src/pages/EditShipment/view/modals/SelectReturnAddressModal.tsx, toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/formatShipmentData.ts, toga2-supply/src/pages/EditShipment/types.ts, toga2-supply/src/pages/Shipments/view/ShipmentsPage.tsx, toga2-supply/src/pages/Shipments/view/components/ShipmentsCardTableForm/ShipmentsCardTableForm.tsx, toga2-supply/src/pages/Shipments/api/ShipmentsApi.ts, toga2-supply/src/pages/Shipments/types.ts, toga2-supply/src/pages/FulfilledShipments/view/FulfilledShipmentsPage.tsx, toga2-supply/src/components/ui/CardTable/CardTable.tsx, toga2-supply/src/components/ui/CardTable/types.ts, toga2-supply/src/assets/pen-line.svg, _underscore/Model/Client/ItemFulfillment.php, _underscore/Trait/Netsuite/ItemFulfillment.php, _underscore/Component/Library/Carriers/Ups/Ups.php |
|
|
7
7
|
| [AWS Amplify Build & Deploy (non-prod environments)](workflows/amplify-build-and-deploy.md) | How `toga2-supply` (React + Vite) builds and deploys on **AWS Amplify**. | toga2-supply/amplify.yml, toga2-supply/.gitattributes, toga2-supply/.github/workflows/sync-stage-environments.yml, toga2-supply/.env.qc-security |
|
|
@@ -6,9 +6,14 @@ project: TOGa Supply
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-27
|
|
10
10
|
owners: [mhammontree]
|
|
11
11
|
files:
|
|
12
|
+
- _underscore/Model/Client/Measure.php
|
|
13
|
+
- _underscore/Model/Client/TrackingNumber.php
|
|
14
|
+
- toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/getEditShipmentFormOptions.ts
|
|
15
|
+
- toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/validateFormOnSubmit.ts
|
|
16
|
+
- toga2-supply/src/pages/EditShipment/helpers/ShipmentDetailsForm/checkDimensions.ts
|
|
12
17
|
- toga2-supply/src/components/ui/Tables/BasicTable/BasicTable.tsx
|
|
13
18
|
- toga2-supply/src/components/ui/Tables/types.ts
|
|
14
19
|
- toga2-supply/src/components/ui/GoogleMapsLink.tsx
|
|
@@ -302,26 +307,41 @@ existing IF), `fulfillShipment` + `generateReturnLabel` (label), `saveShipmentTo
|
|
|
302
307
|
(push-back). New pieces needed: the punch-out entry carrying an IF id, and a **label-only mode**
|
|
303
308
|
(skip the create step). This is a separate feature/ticket — recorded as planned direction, not done.
|
|
304
309
|
|
|
305
|
-
##
|
|
306
|
-
|
|
307
|
-
**
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
**`
|
|
317
|
-
- **
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
310
|
+
## Package Units of Measure (weight + dimensions) — IMPLEMENTED (TRUE-79191, 2026-07-22)
|
|
311
|
+
|
|
312
|
+
The package **weight** and **dimensions** on the Update Info / Edit Shipment form now have **real
|
|
313
|
+
unit selectors** (base **Pound** / **Inch**), replacing the earlier visual-only placeholder. The
|
|
314
|
+
chosen units **persist on the tracking number** and **rehydrate on reopen**. Verified end-to-end
|
|
315
|
+
(the saved `TrackingNumber` carries the selected measure ids from the frontend create payload).
|
|
316
|
+
Commit `e91f7029d`. **Supersedes the earlier design** (VOLUME and COUNT dropped entirely;
|
|
317
|
+
`dimensionMeasureId` → `lengthMeasureId`; `weightMeasureId` added).
|
|
318
|
+
|
|
319
|
+
- **Data model — ZERO new tables (design confirmed with product owner Jeff).** Reuse the existing
|
|
320
|
+
client **`Measures`** table (`id`/`uuid`/`slug`/`name`/`symbol`) and add two columns:
|
|
321
|
+
**`measureType ENUM('LENGTH','WEIGHT')`** and **`conversionFactorToBase DECIMAL(18,9)`**.
|
|
322
|
+
- **Base units seeded into every client:** **Inch** (LENGTH, factor `1`) and **Pound** (WEIGHT,
|
|
323
|
+
factor `1`). **Convention: `value_in_base = value * conversionFactorToBase`.** Metric (cm/kg) is an
|
|
324
|
+
**opt-in per-client seed** added later; the base always stays Inch/Pound. Carrier constraint is the
|
|
325
|
+
reason a base/conversion exists at all — FedEx/UPS ship APIs accept only IN/CM + LB/KG.
|
|
326
|
+
- **`TrackingNumbers` gains two FKs** (hard DB constraints → `Measures`): **`lengthMeasureId`** (one
|
|
327
|
+
unit governs length/width/height) and **`weightMeasureId`**. Existing rows are **backfilled to
|
|
328
|
+
Inch/Pound** (one-time migration; **not** re-run-safe).
|
|
329
|
+
- **Models (`_underscore`):** `_Model_Client_Measure` gains `measureType` (`FIELD_CHAR`) +
|
|
330
|
+
`conversionFactorToBase` (`FIELD_DECIMAL`); `_Model_Client_TrackingNumber` gains
|
|
331
|
+
`lengthMeasureId`/`weightMeasureId` (`FIELD_FOREIGNKEY` → `_Model_Client_Measure`).
|
|
332
|
+
- **api2 needs NO code.** The new fields ride the metadata-driven V2 engine — exposed purely via Core
|
|
333
|
+
`RecordFields` + Client `AclFieldPermissions`. **The `AclFieldPermissions` grant is mandatory for
|
|
334
|
+
READ, not only write** — see the acl-permission-chain doc's field-registration recipe and the
|
|
335
|
+
read-visibility rule (a registered field with no grant is silently omitted from the response, which
|
|
336
|
+
presented here as empty unit dropdowns).
|
|
337
|
+
- **Frontend:** two react-hook-form selects fed by **`GET /v2/measures`**, split client-side by
|
|
338
|
+
`measureType`. **The FK is WRITTEN as a nested relation OBJECT `{uuid}`** (`lengthMeasure` /
|
|
339
|
+
`weightMeasure`), exactly like the sibling `shippingCarrier` — **NOT a scalar id**; read
|
|
340
|
+
field-selectors request `lengthMeasure`/`weightMeasure`. (Same whole-object react-select rule as
|
|
341
|
+
every other select in these forms — see that gotcha.)
|
|
342
|
+
- **Migrations (`dbchanges2`):** `Core/2026-07-22a - MeasureUomRecordFields.sql`,
|
|
343
|
+
`Client/2026-07-22a - MeasureUomColumns.sql`, `Client/2026-07-22b - TrackingNumberMeasureIds.sql`,
|
|
344
|
+
`Client/2026-07-22c - TrackingNumberMeasureIdsFieldPermission.sql`.
|
|
325
345
|
|
|
326
346
|
## Sources — meeting notes / decision origins
|
|
327
347
|
|
|
@@ -598,6 +618,24 @@ the call 403s and returns no label.
|
|
|
598
618
|
programmatic `setValue` for a user-meaningful prefill must pass `{ shouldDirty: true }` or the form
|
|
599
619
|
still reads "clean" (also affects the mint dirty-border above). File:
|
|
600
620
|
`EditShipment/view/modals/ReturnShippingModal.tsx`.
|
|
621
|
+
- **⚠ A submit handler must branch on a validation helper's RETURN value, NOT on the same-tick
|
|
622
|
+
`errors` snapshot after `setError`.** Same RHF-formState-lag family as the `isDirty` gotcha above:
|
|
623
|
+
the destructured `errors` object is one render **behind** `setError`/`clearErrors`, so a helper that
|
|
624
|
+
sets field errors and a caller that then reads `Object.keys(errors)` on the **same tick** see a
|
|
625
|
+
stale (empty) `errors` — the FIRST submit sailed past validation straight to the API even with
|
|
626
|
+
invalid fields. **Two rules for any toga2-supply form doing manual RHF validation on submit:**
|
|
627
|
+
(1) the helper must **RETURN a fresh boolean/result** and the caller must branch on that return
|
|
628
|
+
value, never on the same-tick `errors` snapshot; (2) the helper must **`clearErrors` on the
|
|
629
|
+
now-valid path**, not only `setError` on the invalid path. *Concrete failure:* `checkDimensions`
|
|
630
|
+
had branches for every partial L/W/H combination and for all-absent but **none for all-present**,
|
|
631
|
+
so once a partial submit flagged a dimension, completing all three never cleared it and submit
|
|
632
|
+
stayed blocked in RHF state **permanently**. Fix: `validateFormOnSubmit.ts` and `checkDimensions.ts`
|
|
633
|
+
now return a boolean (`hasErrors`) and clear errors on the valid state; `EditShipmentForm.tsx`
|
|
634
|
+
branches on the returned result. (This validation now also runs in **edit mode** — detail/address
|
|
635
|
+
fields were previously skipped on the edit save path.) Files:
|
|
636
|
+
`EditShipment/helpers/ShipmentDetailsForm/validateFormOnSubmit.ts`,
|
|
637
|
+
`EditShipment/helpers/ShipmentDetailsForm/checkDimensions.ts`,
|
|
638
|
+
`EditShipment/view/components/forms/EditShipmentForm.tsx`.
|
|
601
639
|
- **⚠ api2 V2 field SELECTORS are dot-paths of REAL DB column names — a wrong name silently returns
|
|
602
640
|
nothing.** V2 responses select fields by dot-path (e.g.
|
|
603
641
|
`"itemFulfillmentItems.salesOrderItem.item.skuNumber"`). The real `Items` columns are **`skuNumber`
|
|
@@ -671,6 +709,17 @@ not the base `_Model_Client_ItemFulfillment`. Tested with GroWrk; UPS support wa
|
|
|
671
709
|
for Compass and is not yet in prod.
|
|
672
710
|
|
|
673
711
|
## Change history
|
|
712
|
+
- 2026-07-22 — TRUE-79191 (commit `e91f7029d`, full-stack): **IMPLEMENTED package Units of Measure** —
|
|
713
|
+
the package weight + dimensions now have real Pound/Inch unit selectors that persist on the
|
|
714
|
+
`TrackingNumber` and rehydrate on reopen (verified end-to-end). ZERO new tables: reused the client
|
|
715
|
+
`Measures` table + `_Model_Client_Measure` with `measureType ENUM('LENGTH','WEIGHT')` +
|
|
716
|
+
`conversionFactorToBase DECIMAL(18,9)` (base Inch/Pound factor 1, `value_in_base = value *
|
|
717
|
+
conversionFactorToBase`, metric opt-in later); `TrackingNumbers` gained `lengthMeasureId` (governs
|
|
718
|
+
L/W/H) + `weightMeasureId` FKs (existing rows backfilled Inch/Pound, one-time). **VOLUME + COUNT
|
|
719
|
+
dropped** and `dimensionMeasureId` → `lengthMeasureId` vs the earlier design. api2 needed NO code
|
|
720
|
+
(metadata-driven: Core `RecordFields` + Client `AclFieldPermissions`); the FK is written as a nested
|
|
721
|
+
relation object `{uuid}` like `shippingCarrier`. Migrations `Core/2026-07-22a`, `Client/2026-07-22a
|
|
722
|
+
/b/c`. (mhammontree)
|
|
674
723
|
- 2026-07-21 — TRUE-79191 (commit `651e9d9d0`, toga2-supply UI fixes + UoM design): fixed the shared
|
|
675
724
|
**`GoogleMapsLink`** mapping the recipient instead of the address (excluded `addressee`; read
|
|
676
725
|
`street || line1` not `line1`; killed an always-true empty-part filter and a `replace`+`encodeURIComponent`
|
|
@@ -769,6 +818,13 @@ for Compass and is not yet in prod.
|
|
|
769
818
|
the form via `getValueFromPath`'s unguarded `.split`). Remaining Figma tweaks (Dimensions in./cm
|
|
770
819
|
dropdown + gaps, Item Count right-align, return-label row gaps, shipment-card indicator top gap) were
|
|
771
820
|
cosmetic — no doc change. (mhammontree)
|
|
821
|
+
- 2026-07-27 — TRUE-79191 (CodeRabbit review pass): fixed the Edit Shipment form's **first-submit
|
|
822
|
+
validation bypass** — `validateFormOnSubmit`/`checkDimensions` now RETURN a boolean and the submit
|
|
823
|
+
handler branches on that instead of the stale same-tick `errors` snapshot; `checkDimensions` now
|
|
824
|
+
clears errors on the all-present dimensions path (previously had no all-present branch, so a partial
|
|
825
|
+
submit could block submit permanently). Edit mode now runs the same detail/address validation
|
|
826
|
+
(previously skipped) and `updateShipment` returns step-identifying failures. Recorded as a reusable
|
|
827
|
+
RHF-validation gotcha. (mhammontree)
|
|
772
828
|
- 2026-07-14 — TRUE-79191 (commit `724d17c1a`): fixed **Signature Type** not saving/rehydrating —
|
|
773
829
|
documented the reusable **whole-object react-select** convention (`{uuid,name}`; map on `.uuid`,
|
|
774
830
|
rehydrate the object) as a gotcha; the string comparison `data.signatureType === "Required"`
|
package/package.json
CHANGED