toga-ai 1.0.790 → 1.0.792
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/features/calculated-sql-fields.md +52 -1
- package/knowledge/2.0/apps/api2/features/nested-fk-acl-embedding.md +41 -4
- package/knowledge/2.0/apps/dbchanges2/INDEX.md +1 -0
- package/knowledge/2.0/apps/dbchanges2/workflows/deleting-contacts-from-a-client-database.md +87 -0
- package/knowledge/2.0/apps/toga25-supply/features/transfer-orders-page.md +50 -3
- package/knowledge/2.0/apps/worker2/features/netsuite-transferorder-outbound-push.md +87 -3
- package/knowledge/INDEX.md +1 -1
- package/knowledge/clients/nychh/INDEX.md +1 -0
- package/knowledge/clients/nychh/features/location-shipping-addresses.md +150 -0
- package/knowledge/clients/nychh/features/netsuite-transfer-order-import.md +14 -1
- package/knowledge/clients/nychh/features/transfer-order-netsuite-push.md +51 -1
- package/knowledge/clients/nychh/profile.md +21 -4
- package/package.json +1 -1
|
@@ -6,13 +6,14 @@ project: _Underscore
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-10
|
|
10
10
|
owners: [snaredla, jcardinal, bala]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model.php
|
|
13
13
|
- _underscore/Model/Client/PurchaseOrder.php
|
|
14
14
|
- _underscore/Model/Client/SalesOrder.php
|
|
15
15
|
- _underscore/Model/Client/ServiceRequest.php
|
|
16
|
+
- _underscore/Model/Client/Location.php
|
|
16
17
|
- _underscore/Model/Elite/SalesOrder.php
|
|
17
18
|
- _underscore/Model/Compass/SalesOrder.php
|
|
18
19
|
- _underscore/Model/Compass/Canada/SalesOrder.php
|
|
@@ -30,6 +31,8 @@ related:
|
|
|
30
31
|
- ./model-magic-field-access.md
|
|
31
32
|
- ./model-save-parent-cascade-stored-field-deadlock.md
|
|
32
33
|
- ./sales-order-purchase-order-bridge-direction.md
|
|
34
|
+
- ../../../../clients/nychh/features/location-shipping-addresses.md
|
|
35
|
+
- ../../toga25-supply/features/transfer-orders-page.md
|
|
33
36
|
---
|
|
34
37
|
|
|
35
38
|
## Summary
|
|
@@ -95,6 +98,45 @@ Verified 2026-08-26 on `_Model_Quad_Item::_unitPrice`, which returns a `GROUP_CO
|
|
|
95
98
|
`'CODE amount'` pairs across a Quad item's currencies (see
|
|
96
99
|
[Quad multi-currency item pricing](../../../../clients/quad/features/multi-currency-item-pricing.md)).
|
|
97
100
|
|
|
101
|
+
## ⚠ A calculated field must return the HUMAN value, not the FK id (`_addressState`, 2026-09-10)
|
|
102
|
+
|
|
103
|
+
`_Model_Client_Location` exposes the location's address as calculated fields
|
|
104
|
+
(`_addressLine1/_addressLine2/_addressCity/_addressState/_addressZip`). `_addressState` selected
|
|
105
|
+
**`Addresses.stateId`**, so every consumer rendered the internal id: `Bronx, 33 10451-5504` instead
|
|
106
|
+
of `Bronx, NY 10451-5504`. A calculated field is a **display** field — resolve the lookup inside the
|
|
107
|
+
subquery rather than handing the caller an id it cannot translate.
|
|
108
|
+
|
|
109
|
+
The fix joins `States` inside the existing subquery (`Model/Client/Location.php`, ~L66):
|
|
110
|
+
|
|
111
|
+
```php
|
|
112
|
+
LEFT JOIN States ON States.id = Addresses.stateId
|
|
113
|
+
... SELECT States.code ...
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**`LEFT JOIN`, not `INNER`** — `Addresses.stateId` is nullable, and an inner join would drop the
|
|
117
|
+
whole address line for a state-less address. This matches the existing pattern in
|
|
118
|
+
`_underscore/Model/Rate/SalesOrder.php`.
|
|
119
|
+
|
|
120
|
+
### The blast-radius checklist for changing a shared `_Model_Client_*` calculated field
|
|
121
|
+
|
|
122
|
+
This is a **shared core model**: one edit ships to every tenant at once, and a calculated field has
|
|
123
|
+
no compile-time consumers, so the only way to know who breaks is to look. What was checked before
|
|
124
|
+
this one-line change, and the shape to reuse:
|
|
125
|
+
|
|
126
|
+
1. **Does every client schema support the new join?** All **36** `Client_*` prod schemas have both a
|
|
127
|
+
`States` and a `Locations` table, so no tenant errors on the join.
|
|
128
|
+
2. **Who reads the field, and do they DISPLAY it or DECIDE on it?** Only two frontends request
|
|
129
|
+
`_addressState` and both only print it (`toga25-supply` TransferOrders hooks, and
|
|
130
|
+
`toga2-supply/src/pages/Inventory/api/InventoryApi.tsx` ~L560).
|
|
131
|
+
3. **Does any metadata treat it as an id?** `Core.RecordFields` id **73** is `_addressState`, with
|
|
132
|
+
**0** rows in `SurfaceElements` and `DefaultRecordFieldSettings` and one ACL read row — nothing
|
|
133
|
+
configures it as an id-based filter, so nothing depended on getting a number.
|
|
134
|
+
4. **Run the new subquery against production before committing.** The edited expression returned
|
|
135
|
+
`NY` for all 24 NYCHH locations; `php -l` alone proves nothing about a SQL string.
|
|
136
|
+
|
|
137
|
+
> **Status 2026-09-10: written, `php -l` clean, UNCOMMITTED on `_production` and NOT deployed.**
|
|
138
|
+
> Production still renders the state id until it ships.
|
|
139
|
+
|
|
98
140
|
## Exposing a calculated field to the API - a client `CustomRecordFields` row is enough
|
|
99
141
|
|
|
100
142
|
**Declaring the field on the model does not make it requestable.** A client whose DB has no
|
|
@@ -211,6 +253,15 @@ characters — that is the standard of evidence for touching a field that runs o
|
|
|
211
253
|
[save-cascade stored-field deadlocks](./model-save-parent-cascade-stored-field-deadlock.md).
|
|
212
254
|
|
|
213
255
|
## Change history
|
|
256
|
+
- 2026-09-10 — **Fixed `_Model_Client_Location::_addressState()` to return the state CODE, not
|
|
257
|
+
`Addresses.stateId`** (addresses rendered as `Bronx, 33 10451-5504`). Added
|
|
258
|
+
`LEFT JOIN States ON States.id = Addresses.stateId` inside the subquery and selected `States.code`
|
|
259
|
+
— LEFT because `stateId` is nullable, matching `_underscore/Model/Rate/SalesOrder.php`. Recorded
|
|
260
|
+
the **blast-radius checklist** used before touching a shared calculated field: all 36 prod
|
|
261
|
+
`Client_*` schemas have `States` + `Locations`; only two frontends read the field and both only
|
|
262
|
+
display it; `Core.RecordFields` 73 has no `SurfaceElements`/`DefaultRecordFieldSettings` rows, so
|
|
263
|
+
no filter relied on the numeric value; and the new subquery was run against prod (returned `NY`
|
|
264
|
+
for all 24 NYCHH locations). **Uncommitted / not deployed** as of this date. (bala)
|
|
214
265
|
- 2026-09-01 — Added `_shipTo` to the base `_Model_Client_PurchaseOrder` (`public $_shipTo =
|
|
215
266
|
self::FIELD_SQL;` + static `_shipTo($table = self::TABLE)` returning a correlated subquery
|
|
216
267
|
`SELECT Addresses.addressee FROM Addresses WHERE Addresses.id = $table.shipToAddressId`),
|
|
@@ -6,14 +6,17 @@ project: API
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-
|
|
10
|
-
owners: [apeterson]
|
|
9
|
+
updated: 2026-09-10
|
|
10
|
+
owners: [apeterson, bala]
|
|
11
11
|
files:
|
|
12
12
|
- api2/Component/Api/V2/V2.php
|
|
13
13
|
- dbchanges2/Client_Compass/2026-07-23b - PurchaseOrdersRecordReadAcl.sql
|
|
14
|
+
- dbchanges2/Client_Nychh/2026-09-09d - ApiRoleReadLocationAddress.sql
|
|
14
15
|
related:
|
|
15
16
|
- cross-client-data-retrieval.md
|
|
16
17
|
- tableview-apiwhereclause-row-filtering.md
|
|
18
|
+
- v2-rest-query-contract.md
|
|
19
|
+
- ../../worker2/features/netsuite-transferorder-outbound-push.md
|
|
17
20
|
---
|
|
18
21
|
|
|
19
22
|
## What it is
|
|
@@ -59,6 +62,33 @@ a machine/API caller on role 3 sees it fine.
|
|
|
59
62
|
ids), `NOT EXISTS`-guarded, wrapping any self-referencing subquery in a derived table to avoid MySQL
|
|
60
63
|
error 1093. See `dbchanges2/Client_Compass/2026-07-23b - PurchaseOrdersRecordReadAcl.sql`.
|
|
61
64
|
|
|
65
|
+
## ⚠ Deepening a `depth` expansion is an ACL change — check EVERY record it crosses
|
|
66
|
+
|
|
67
|
+
The same silent drop bites when nothing about the ACL changed and only the **read shape** did.
|
|
68
|
+
Raising `depth` pulls in records the old read never touched, and each new record needs its own
|
|
69
|
+
`AclRecordPermissions` row **for the calling role** or that whole branch comes back empty — 200,
|
|
70
|
+
`isSuccess: true`, no message.
|
|
71
|
+
|
|
72
|
+
**Rule: before you raise a depth, list the records the new expansion crosses and confirm an ACL row
|
|
73
|
+
exists for the role the caller actually authenticates as.** Service callers are the usual victims,
|
|
74
|
+
because a developer testing in a browser holds different roles.
|
|
75
|
+
|
|
76
|
+
**Confirmed case (2026-09-09, NYCHH transfer-order → NetSuite push).** Expanding the ship-to address
|
|
77
|
+
from `order → destinationLocation` out to `… → primaryLocationAddress → address → state → country`
|
|
78
|
+
crossed Core records **69 “Location addresses”**, **12 “States”** and **23 “Countries”**. The push
|
|
79
|
+
reads `/transfer-orders` as role **3 “API”** (authenticating with the `Client_Nychh.Apis` row named
|
|
80
|
+
`Agilant`), and `Client_Nychh.AclRecordPermissions` had **no roleId 3 row for any of the three** —
|
|
81
|
+
only roleId **1 “Base”**. **Roles are looked up per `roleId` directly in
|
|
82
|
+
`api2/Component/Api/V2/V2.php`; there is no Base-role inheritance**, so a role-1 grant does nothing
|
|
83
|
+
for a role-3 caller. `Client_Compass` already carried role 3 on all three, which is why the identical
|
|
84
|
+
read shape works there. Fixed with `Client_Nychh/2026-09-09d - ApiRoleReadLocationAddress.sql`
|
|
85
|
+
(read-only role-3 rows for 69, 12, 23, `NOT EXISTS`-guarded).
|
|
86
|
+
|
|
87
|
+
**Why this one was expensive:** without those rows the deeper read returns the same thing as the
|
|
88
|
+
shallow read, so the code fix that motivated the depth change would have shipped as a **silent
|
|
89
|
+
no-op** and looked like a code bug. Mechanics of that push are in
|
|
90
|
+
[transfer-order → NetSuite push](../../worker2/features/netsuite-transferorder-outbound-push.md).
|
|
91
|
+
|
|
62
92
|
## Diagnostic tell
|
|
63
93
|
|
|
64
94
|
A top-level GET of the child record **works** but the **nested embed of the same record under a
|
|
@@ -67,6 +97,15 @@ record's `AclRecordPermissions` for the caller's role — not the parent's grant
|
|
|
67
97
|
permissions.
|
|
68
98
|
|
|
69
99
|
## Change history
|
|
100
|
+
- 2026-09-09 — Added the **depth-expansion** face of this bug: raising `depth` crosses records the old
|
|
101
|
+
read never touched, and each one needs an `AclRecordPermissions` row **for the calling role** or the
|
|
102
|
+
branch is dropped silently. Confirmed on the NYCHH transfer-order → NetSuite push, where expanding
|
|
103
|
+
the ship-to address to depth 6 crossed Core records **69 Location addresses / 12 States / 23
|
|
104
|
+
Countries** and `Client_Nychh` had **no roleId 3 (“API”) row on any of them** — only roleId 1
|
|
105
|
+
(“Base”), which does not inherit, roles being resolved per `roleId` in `V2.php`. `Client_Compass`
|
|
106
|
+
already had role 3 on all three, which is why the same shape worked there. Fixed by
|
|
107
|
+
`Client_Nychh/2026-09-09d`; the durable rule is to audit ACL on every record a new expansion crosses
|
|
108
|
+
before assuming the deeper read is broken. (bala)
|
|
70
109
|
- 2026-07-23 — Documented that V2 nested-FK embedding (`getFullModelData`) re-checks the CHILD
|
|
71
110
|
record's own `AclRecordPermissions` and SILENTLY drops the nested object (200, isSuccess true, no
|
|
72
111
|
message) when no grant exists — needing only an ACL row, not a logic-group expression. Fixed the
|
|
@@ -75,5 +114,3 @@ permissions.
|
|
|
75
114
|
embedded fine; mirrored Quad's role-1 grant (`Client_Compass/2026-07-23b`). Confirmed this is the
|
|
76
115
|
SAME role-3-vs-role-1 mis-seed pattern already seen on CompassCanada (record 275) — not
|
|
77
116
|
CompassCanada-only. (apeterson)
|
|
78
|
-
</content>
|
|
79
|
-
</invoke>
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
| [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 |
|
|
8
8
|
| [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 |
|
|
9
9
|
| [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 |
|
|
10
|
+
| [Deleting Contacts rows from a client database (the FK order, and the self-referencing trap)](workflows/deleting-contacts-from-a-client-database.md) | Clearing test `Contacts` out of a 2.0 client database is **not** a one-line `DELETE`. |
|
|
10
11
|
| [Framework branch running AHEAD of schema — environment-wide 1054/EO-1 after a deploy](workflows/framework-branch-ahead-of-schema.md) | A **third** kind of 2.0 schema drift, distinct from the two already documented: nobody's database went backwards — **the code went forwards**. |
|
|
11
12
|
| [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**. |
|
|
12
13
|
| [Repairing non-prod metadata drift (works in prod, broken in beta/dev-sandbox)](workflows/nonprod-metadata-drift-repair.md) | Almost all 2.0 platform behavior is **metadata** — `Core.Records`/`RecordFields`, `Core.RecordScripts`, `Core.ApiPayloadInterceptors`, and per-client `Acl*` row |
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Deleting Contacts rows from a client database (the FK order, and the self-referencing trap)"
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: dbchanges2
|
|
5
|
+
project: Database Changes
|
|
6
|
+
client: shared
|
|
7
|
+
type: workflow
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-09-10
|
|
10
|
+
owners: [bala]
|
|
11
|
+
files:
|
|
12
|
+
- dbchanges2/Client_Nychh/2026-09-09a - DeleteTestContacts.sql
|
|
13
|
+
- dbchanges2/Client_Nychh/2026-09-09b - InsertDeliveryContacts.sql
|
|
14
|
+
related:
|
|
15
|
+
- ../architecture.md
|
|
16
|
+
- ../features/rerunnable-additive-inserts.md
|
|
17
|
+
- ../../../clients/nychh/features/location-shipping-addresses.md
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Summary
|
|
21
|
+
|
|
22
|
+
Clearing test `Contacts` out of a 2.0 client database is **not** a one-line `DELETE`. Nothing
|
|
23
|
+
cascades, and `Contacts` points **back at its own children**, so the obvious order fails with
|
|
24
|
+
`SQL Error [1451] ... Contacts_ibfk_1`. Verified on `Client_Nychh` 2026-09-09 while replacing 12
|
|
25
|
+
test contacts with 19 real delivery contacts.
|
|
26
|
+
|
|
27
|
+
**The two facts that cost the time:**
|
|
28
|
+
|
|
29
|
+
1. **No foreign key referencing `Contacts` cascades.** 24 of them are `NO ACTION` and 5 are
|
|
30
|
+
`RESTRICT`, so **every child row must be cleared by hand**.
|
|
31
|
+
2. **`Contacts` holds FKs pointing DOWN at its own children** — `Contacts_ibfk_1` →
|
|
32
|
+
`ContactEmailAddresses`, `Contacts_ibfk_2` → `ContactAddresses`, `Contacts_ibfk_3` →
|
|
33
|
+
`ContactPhoneNumbers`, through `primaryContactEmailAddressId` / `primaryContactAddressId` /
|
|
34
|
+
`primaryContactPhoneNumberId`. So the parent must **release** its children before they can be
|
|
35
|
+
deleted. Delete the email rows first and you get error 1451 on `Contacts_ibfk_1`.
|
|
36
|
+
|
|
37
|
+
## Steps
|
|
38
|
+
|
|
39
|
+
1. **Unlink the business rows that reference the contact.** For transfer orders that is
|
|
40
|
+
`UPDATE TransferOrders SET contactId = NULL WHERE contactId IN (…)`. Find the full set with
|
|
41
|
+
`information_schema.KEY_COLUMN_USAGE` against `REFERENCED_TABLE_NAME = 'Contacts'` — do not
|
|
42
|
+
assume the list.
|
|
43
|
+
2. **NULL the three `primaryContact*Id` columns on `Contacts` first** (`primaryContactEmailAddressId`,
|
|
44
|
+
`primaryContactAddressId`, `primaryContactPhoneNumberId`). This is the step that is easy to miss.
|
|
45
|
+
3. **Delete `Contacts_Locations`** for those contacts.
|
|
46
|
+
4. **Delete `ContactEmailAddresses`, then `ContactPhoneNumbers`, then `ContactAddresses`.**
|
|
47
|
+
5. **Delete `Contacts_Vendors`** — `Contacts_Vendors_ibfk_2` blocks the delete even when the table
|
|
48
|
+
holds no matching rows for these contacts.
|
|
49
|
+
6. **Delete `Contacts`.**
|
|
50
|
+
7. **`COMMIT`.**
|
|
51
|
+
8. **Only then run `ALTER TABLE Contacts AUTO_INCREMENT = 1`.** `ALTER TABLE` performs an **implicit
|
|
52
|
+
commit**, so putting it inside the transaction ends the transaction early and the rollback safety
|
|
53
|
+
net is gone before the deletes are checked.
|
|
54
|
+
|
|
55
|
+
## Re-seeding contacts — the shape that works
|
|
56
|
+
|
|
57
|
+
Per person: one `Contacts` row, one `ContactEmailAddresses` row (`name = 'Work'`) set as the
|
|
58
|
+
contact's `primaryContactEmailAddressId`, and one `Contacts_Locations` row with `type = 'SHIPPING'`
|
|
59
|
+
per facility. **One contact can hold several `Contacts_Locations` rows** — the same person covering
|
|
60
|
+
two sites is one contact with two links, not two contacts.
|
|
61
|
+
|
|
62
|
+
- **`uuid` must be a real UUID4 literal in the file**, never MySQL `UUID()` (the standing 2.0 rule).
|
|
63
|
+
- **`contactTypeId` is nullable and usually should stay NULL** — a client's `ContactTypes` table is
|
|
64
|
+
commonly empty (it is on `Client_Nychh`), and inventing a type row to fill the column is worse
|
|
65
|
+
than leaving it null.
|
|
66
|
+
|
|
67
|
+
## Gotchas
|
|
68
|
+
|
|
69
|
+
- **⚠ `SQL Error [1451] ... Contacts_ibfk_1` means the PARENT still points at the child.** It reads
|
|
70
|
+
like a normal child-blocks-parent error and it is the opposite. NULL the `primaryContact*Id`
|
|
71
|
+
columns, then retry.
|
|
72
|
+
- **⚠ `ALTER TABLE` after `COMMIT`, always.** Same trap as any implicit-commit statement inside a
|
|
73
|
+
migration transaction.
|
|
74
|
+
- **⚠ Do not assume the child list.** 29 constraints reference `Contacts` on a full client schema;
|
|
75
|
+
query `information_schema` for the database you are actually in, because client schemas drift
|
|
76
|
+
(see [client schema drift audit](./client-schema-drift-audit.md)).
|
|
77
|
+
|
|
78
|
+
## Change history
|
|
79
|
+
|
|
80
|
+
- 2026-09-09 — Wrote up the delete order after hitting `Contacts_ibfk_1` on `Client_Nychh` while
|
|
81
|
+
swapping 12 test contacts for 19 real delivery contacts. Recorded that **no FK on `Contacts`
|
|
82
|
+
cascades** (24 `NO ACTION`, 5 `RESTRICT`), that `Contacts` points **back** at
|
|
83
|
+
`ContactEmailAddresses` / `ContactAddresses` / `ContactPhoneNumbers` through its three
|
|
84
|
+
`primaryContact*Id` columns so those must be NULLed **before** the child rows are deleted, that
|
|
85
|
+
`Contacts_Vendors` blocks the delete regardless of content, and that
|
|
86
|
+
`ALTER TABLE … AUTO_INCREMENT = 1` must sit **after** `COMMIT` because it commits on its own.
|
|
87
|
+
(bala)
|
|
@@ -6,7 +6,7 @@ project: TOGa 2.5 Supply
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-10
|
|
10
10
|
owners: [apeterson, bala]
|
|
11
11
|
files:
|
|
12
12
|
- toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useCreateTransferOrder.ts
|
|
@@ -73,6 +73,7 @@ related:
|
|
|
73
73
|
- ../../toga-blox/features/advanced-select.md
|
|
74
74
|
- ../../toga-blox/features/primary-table-templates.md
|
|
75
75
|
- ../../../standards/frontend.md
|
|
76
|
+
- ../../../../clients/nychh/features/location-shipping-addresses.md
|
|
76
77
|
---
|
|
77
78
|
|
|
78
79
|
## Summary
|
|
@@ -482,8 +483,26 @@ Verified on NYCHH 2026-09-01: exactly **1 of 31** active locations sets `allowRe
|
|
|
482
483
|
**the same location** that sets `allowSending = 1` — the source warehouse. Filtering targets on
|
|
483
484
|
`allowReceiving` therefore offers exactly one option: **the one place you cannot transfer to.**
|
|
484
485
|
|
|
485
|
-
The dropdown
|
|
486
|
-
|
|
486
|
+
**The dropdown filters on `Locations.locationTypeId` instead (2026-09-10).**
|
|
487
|
+
`hooks/useTargetLocationOptions.ts` now sends a second condition in the same `where` `and` array:
|
|
488
|
+
|
|
489
|
+
```ts
|
|
490
|
+
where: { and: [
|
|
491
|
+
{ "Locations.isActive": { "=": "1" } },
|
|
492
|
+
{ "Locations.locationTypeId": { "=": "1" } },
|
|
493
|
+
] }
|
|
494
|
+
// serializes to where=(Locations.isActive:eq:1,AND,Locations.locationTypeId:eq:1)
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
Before this it asked for every active location, so all **31** NYCHH active rows were offered,
|
|
498
|
+
including the **7** internal/warehouse rows (Health & Hospitals, Health & Hospitals Processing, the
|
|
499
|
+
New York warehouse, Bay S-A-01-A, Chicago, New York, New York HQ) — one of which is the **source
|
|
500
|
+
warehouse the transfer ships FROM**. Verified on prod `Client_Nychh`: `locationTypeId` **1 = 24
|
|
501
|
+
shipping locations** (hospitals/clinics), **2 = 7 warehouse/office rows**. So the location TYPE is
|
|
502
|
+
the filter that actually works here, and `allowReceiving` is not. Live in production
|
|
503
|
+
(commit `eb12055`, branch `_production`).
|
|
504
|
+
|
|
505
|
+
Treat `allowReceiving` as a column nobody populates until someone proves otherwise per tenant.
|
|
487
506
|
|
|
488
507
|
Related data-shape facts found the same day:
|
|
489
508
|
|
|
@@ -611,6 +630,14 @@ Wider platform finding from building them:
|
|
|
611
630
|
- **Flat-list picker with debounced search.** The breadcrumb path is assembled from
|
|
612
631
|
`parentLocation.uuid` **with a cycle guard** — a self- or mutually-parented row would otherwise
|
|
613
632
|
loop forever.
|
|
633
|
+
- **Only shipping locations are listed** — the `where` carries `Locations.locationTypeId = "1"`
|
|
634
|
+
alongside `isActive`; see [the `allowReceiving` section](#-allowreceiving-is-unmaintained--do-not-filter-the-target-dropdown-on-it).
|
|
635
|
+
- **A location's own address IS the transfer's delivery address.** `TransferOrders` has no ship-to
|
|
636
|
+
column (only `originLocationId` / `destinationLocationId`), unlike `SalesOrders`, which carries
|
|
637
|
+
its own `shipToAddressId` per order. So a wrong `Locations` address is invisible on the sales-order
|
|
638
|
+
screens and shows up **only here** — exactly how NYCHH's 24 shipping locations were found all
|
|
639
|
+
rendering one head-office billing address:
|
|
640
|
+
[NYCHH location shipping addresses](../../../../clients/nychh/features/location-shipping-addresses.md).
|
|
614
641
|
- **Target Contact options come from `Contacts` through the `Contacts_Locations` junction**, scoped
|
|
615
642
|
to the selected location.
|
|
616
643
|
- **`Locations` deliberately does NOT get a contact FK.** The junction is the right place, and it is
|
|
@@ -885,6 +912,14 @@ deliberate, separate exception — see [surface-frontend](./surface-frontend.md)
|
|
|
885
912
|
|
|
886
913
|
## Gotchas
|
|
887
914
|
|
|
915
|
+
- **⚠ Never filter the target-location dropdown on `allowReceiving`, and never leave it
|
|
916
|
+
unfiltered either** — filter on `Locations.locationTypeId = "1"` (shipping). Unfiltered, the list
|
|
917
|
+
offers warehouses including the source of the transfer.
|
|
918
|
+
- **⚠ `_addressState` returned the state's internal ID, not its code**, so a location address
|
|
919
|
+
rendered as `Bronx, 33 10451-5504`. Fixed in `_Model_Client_Location` by joining `States` and
|
|
920
|
+
selecting `States.code`; see
|
|
921
|
+
[FIELD_SQL calculated fields](../../_underscore/features/calculated-sql-fields.md). **Written but
|
|
922
|
+
NOT deployed as of 2026-09-10**, so the id still shows in production.
|
|
888
923
|
- **⚠ An empty dropdown with no request in the network tab is a numeric `where` value or a
|
|
889
924
|
non-zero `staleTime`** — see the section above. Neither logs anything.
|
|
890
925
|
- **⚠ An ungranted `uuid` field 403s the WHOLE request** — `uuid` is V2's `IDENTIFIER_FIELD` and is
|
|
@@ -958,6 +993,18 @@ deliberate, separate exception — see [surface-frontend](./surface-frontend.md)
|
|
|
958
993
|
earlier migration without names, so a name-based lookup on them finds nothing.
|
|
959
994
|
|
|
960
995
|
## Change history
|
|
996
|
+
- 2026-09-10 — **Target Location dropdown now lists only shipping locations.**
|
|
997
|
+
`useTargetLocationOptions.ts` gained a second `where` condition,
|
|
998
|
+
`{ "Locations.locationTypeId": { "=": "1" } }`, so the serialized query is
|
|
999
|
+
`where=(Locations.isActive:eq:1,AND,Locations.locationTypeId:eq:1)`. It previously fetched every
|
|
1000
|
+
active location, which put all **31** NYCHH rows in the list including the **7**
|
|
1001
|
+
warehouse/office rows — one being the source warehouse the transfer ships from. Prod
|
|
1002
|
+
`Client_Nychh` splits **24 shipping / 7 warehouse**, and `allowReceiving` remains unusable
|
|
1003
|
+
(1 of 31, and it is the source). Committed and live in prod (`eb12055`, `_production`). Also
|
|
1004
|
+
recorded that a transfer's delivery address comes from the LOCATION record (no ship-to column on
|
|
1005
|
+
`TransferOrders`), which is why NYCHH's shared head-office address only broke this screen —
|
|
1006
|
+
see [NYCHH location shipping addresses](../../../../clients/nychh/features/location-shipping-addresses.md).
|
|
1007
|
+
(bala)
|
|
961
1008
|
- 2026-09-04 — **Virtualized the item picker, made the create POST set an opening stage, and lit up
|
|
962
1009
|
the status badge.** (1) `TransferItemPickerTable` now renders through `@tanstack/react-virtual`
|
|
963
1010
|
with the offsets as **spacer `<tr>`s inside the real `<table>`**, so `table-layout: fixed`, the
|
|
@@ -6,16 +6,18 @@ project: Worker
|
|
|
6
6
|
client: shared
|
|
7
7
|
type: feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-10
|
|
10
10
|
owners: ["bala"]
|
|
11
11
|
files:
|
|
12
12
|
- worker2/Worker/Netsuite/SalesOrder.php
|
|
13
13
|
- _underscore/Model/Client/TransferOrder.php
|
|
14
14
|
- dbchanges2/Client_Nychh/2026-09-02a - TransferOrderNetsuitePushInterceptor.sql
|
|
15
|
+
- dbchanges2/Client_Nychh/2026-09-09d - ApiRoleReadLocationAddress.sql
|
|
15
16
|
- test/@Bala/tests/netsuite_transferorder_payload_tests.php
|
|
16
17
|
- test/@Bala/tests/transferorder_interceptor_tests.php
|
|
17
18
|
related:
|
|
18
19
|
- ./netsuite-salesorder-outbound-push.md
|
|
20
|
+
- ../../api2/features/nested-fk-acl-embedding.md
|
|
19
21
|
- ./creating-worker-actions.md
|
|
20
22
|
- ../../api2/features/api-payload-interceptors.md
|
|
21
23
|
- ../../api2/features/v2-rest-query-contract.md
|
|
@@ -45,6 +47,10 @@ re-read this doc if you last saw it on 2026-09-02: **the NetSuite customer is th
|
|
|
45
47
|
destination location**, and `description` is now sent as the NetSuite **`memo`**. Committed as
|
|
46
48
|
worker2 **`cbafd25`** on `_production` and deployed.
|
|
47
49
|
|
|
50
|
+
**A third correction is written but NOT yet deployed (2026-09-09): the push was sending no ship-to
|
|
51
|
+
address at all**, so NetSuite used the customer's default site on every order. That fix and the
|
|
52
|
+
depth-6 read it needs are below.
|
|
53
|
+
|
|
48
54
|
## Key files / entry points
|
|
49
55
|
|
|
50
56
|
| Action | Method | Purpose |
|
|
@@ -89,8 +95,8 @@ additive precedence change safe to reason about instead of hope.
|
|
|
89
95
|
|
|
90
96
|
## What is actually sent to NetSuite
|
|
91
97
|
|
|
92
|
-
Nothing beyond this list. A transfer order has no shipping cost, ship method, terms, subsidiary
|
|
93
|
-
|
|
98
|
+
Nothing beyond this list. A transfer order has no shipping cost, ship method, terms, subsidiary or
|
|
99
|
+
status columns, so none are sent.
|
|
94
100
|
|
|
95
101
|
| NetSuite field | Source |
|
|
96
102
|
|---|---|
|
|
@@ -98,6 +104,7 @@ shipping address or status columns, so none are sent.
|
|
|
98
104
|
| `tranDate` | `dateOrder`, date only; **omitted** when null |
|
|
99
105
|
| `otherRefNum` | the PO number (the developer's explicit requirement) |
|
|
100
106
|
| `memo` | `TransferOrders.description` — the NYCHH custom form renders it as **"ORDER DESCRIPTION"** |
|
|
107
|
+
| `shippingAddress` | the **destination** Location's `primaryLocationAddress.address` (added 2026-09-09, not yet deployed). The key is **omitted** when the destination has no address, so NetSuite keeps its own default instead of getting an empty one. |
|
|
101
108
|
| `externalId` | `toga-to-<transfer order uuid>` |
|
|
102
109
|
| line `item` / `quantity` | each transfer-order item |
|
|
103
110
|
| line `rate` | **0** — this is the zero-value part |
|
|
@@ -147,6 +154,62 @@ showed the transfer order's `description`.
|
|
|
147
154
|
destination whose customer id already equals the client's own (**30205** and **36443**), so the old
|
|
148
155
|
and new rules produce the same id for them.
|
|
149
156
|
|
|
157
|
+
|
|
158
|
+
### ⚠ The ship-to address was NEVER sent — and it needs a depth-6 read (fixed 2026-09-09, not deployed)
|
|
159
|
+
|
|
160
|
+
`buildSalesOrderShapeFromTransferOrder()` never set a `shipToAddress` key, and the `shippingAddress`
|
|
161
|
+
block in `buildNetSuiteOrder()` (~L922) only runs when that key exists. So **every transfer order
|
|
162
|
+
went to NetSuite with no address**, and NetSuite substituted the **customer's default shipping
|
|
163
|
+
address**. Because the customer is now always the client (previous section), that default is one
|
|
164
|
+
head-office site for **every** destination.
|
|
165
|
+
|
|
166
|
+
Live evidence: NetSuite order **7467799** was for **North Central Bronx** in supply and shipped to
|
|
167
|
+
*“1400 Pelham Parkway South, Bronx”* (**Jacobi**); SO **289187** (Woodhull) shows the same address.
|
|
168
|
+
Both are the default on NetSuite customer **28908**. Grepped the whole file — there was no other
|
|
169
|
+
place sending an address for a transfer order.
|
|
170
|
+
|
|
171
|
+
**Fix:** new private `resolveTransferOrderShipToAddress(object $destinationLocation): ?object` reads
|
|
172
|
+
`$destinationLocation->primaryLocationAddress->address`, and the shape sets `shipToAddress` only when
|
|
173
|
+
it is non-null.
|
|
174
|
+
|
|
175
|
+
> **Durable rule:** a transfer order's NetSuite customer is the client's *whole account*, so the
|
|
176
|
+
> customer default can never be the right ship-to. The address must come from the **destination
|
|
177
|
+
> Location**. This is the same “ships-where ≠ who-is-billed” split as the customer fix, one field later.
|
|
178
|
+
|
|
179
|
+
#### The address sits TWO levels deeper than a sales order's
|
|
180
|
+
|
|
181
|
+
| Order | Path to the country | Depth needed |
|
|
182
|
+
|---|---|---|
|
|
183
|
+
| sales order | `order → shipToAddress → state → country` | **4** (`TOGA_FETCH_DEPTH`, unchanged) |
|
|
184
|
+
| transfer order | `order → destinationLocation → primaryLocationAddress → address → state → country` | **6** |
|
|
185
|
+
|
|
186
|
+
At depth 4 the address comes back **missing with no error at all** — the max-depth omission rule in
|
|
187
|
+
[V2 REST query contract](../../api2/features/v2-rest-query-contract.md). The order is read in **two**
|
|
188
|
+
places, so **both** depths were raised:
|
|
189
|
+
|
|
190
|
+
- `_Model_Client_TransferOrder::NETSUITE_READ_DEPTH` **4 → 6** — the `postPost` read done before
|
|
191
|
+
queueing. The order travels with the job, so this is the read that matters in practice.
|
|
192
|
+
- new `TOGA_FETCH_DEPTH__TRANSFER_ORDER = 6` in the worker, chosen by the existing `$isTransferOrder`
|
|
193
|
+
flag in `fetchSalesOrder()` — the fallback path when no order is carried. **Sales orders still read
|
|
194
|
+
at 4.**
|
|
195
|
+
|
|
196
|
+
Note this contradicts nothing above: `NETSUITE_READ_DEPTH` stays a class constant (not INI) for the
|
|
197
|
+
same reason — it is a fact about the read shape, and the shape just got deeper.
|
|
198
|
+
|
|
199
|
+
#### ⚠ Raising a depth is an ACL change, not just a number
|
|
200
|
+
|
|
201
|
+
The new branch crosses three records the old read never touched — Core records **69 Location
|
|
202
|
+
addresses**, **12 States**, **23 Countries** — and the push reads `/transfer-orders` as role **3
|
|
203
|
+
“API”**. `Client_Nychh` had **no role-3 `AclRecordPermissions` rows for any of the three**, so the
|
|
204
|
+
expanded branch would have come back empty, `resolveTransferOrderShipToAddress()` would have returned
|
|
205
|
+
null, and the code fix would have been a **silent no-op**. Fixed by
|
|
206
|
+
`dbchanges2/Client_Nychh/2026-09-09d - ApiRoleReadLocationAddress.sql`.
|
|
207
|
+
|
|
208
|
+
> **Durable rule: when you deepen a `depth` expansion on any api2 read, check
|
|
209
|
+
> `AclRecordPermissions` for the CALLING role on EVERY record the new expansion crosses.** A missing
|
|
210
|
+
> row drops the branch silently instead of erroring — see
|
|
211
|
+
> [nested FK ACL embedding](../../api2/features/nested-fk-acl-embedding.md).
|
|
212
|
+
|
|
150
213
|
### `description` → `memo` cannot leak into a plain sales order
|
|
151
214
|
|
|
152
215
|
`memo` is sent only from the transfer-order shape. There is **no `memo` RecordField and no `memo`
|
|
@@ -300,6 +363,12 @@ Stub detail for anyone extending it: `_Model_True` is stubbed with **only** the
|
|
|
300
363
|
`postPost` reads **zero lines** and the push aborts — the trigger would then have to move. See
|
|
301
364
|
[Transfer Orders page](../../toga25-supply/features/transfer-orders-page.md).
|
|
302
365
|
- **dev-sandbox metadata is incomplete** (record 325 + its RecordFields + both inherent-child rows).
|
|
366
|
+
- **The ship-to fix is written, `php -l` clean, but NOT deployed and NOT committed** (worker2 +
|
|
367
|
+
`_underscore`). The two NYCHH SQL files it depends on **have** been run on prod.
|
|
368
|
+
- **Undecided: should the delivery contact go into the NetSuite `attention` field?** NetSuite ship-to
|
|
369
|
+
addresses carry a person on the first line (order **289193** shows *Frederick Roberts*, the Bellevue
|
|
370
|
+
delivery contact). `TransferOrders.contactId` exists as an FK on `_Model_Client_TransferOrder` but
|
|
371
|
+
**nothing in worker2 or toga2-supply reads it** — nothing is wired, and nothing was decided.
|
|
303
372
|
- **Undecided: disable the customer name-search fallback for transfer orders?**
|
|
304
373
|
`resolveNetSuiteCustomerId()` falls back to a SuiteQL search by company **name** when the
|
|
305
374
|
destination has no customer id, and NYCHH's `Health & Hospitals` vs `NYC Health + Hospitals` are
|
|
@@ -328,6 +397,21 @@ Stub detail for anyone extending it: `_Model_True` is stubbed with **only** the
|
|
|
328
397
|
|
|
329
398
|
## Change history
|
|
330
399
|
|
|
400
|
+
- 2026-09-09 — **The push had never sent a shipping address at all.**
|
|
401
|
+
`buildSalesOrderShapeFromTransferOrder()` set no `shipToAddress` key, so the `shippingAddress` block
|
|
402
|
+
in `buildNetSuiteOrder()` never ran and NetSuite substituted the **customer's default** site on every
|
|
403
|
+
order — NetSuite order **7467799** (North Central Bronx) and SO **289187** (Woodhull) both shipped to
|
|
404
|
+
Jacobi's *1400 Pelham Parkway South*, the default on customer 28908. Added
|
|
405
|
+
`resolveTransferOrderShipToAddress()` reading `destinationLocation->primaryLocationAddress->address`,
|
|
406
|
+
set on the shape only when non-null so a destination with no address still lets NetSuite use its own
|
|
407
|
+
default. Discovered the address sits **two levels deeper than a sales order's** (order → destination
|
|
408
|
+
→ primaryLocationAddress → address → state → country = **6**, versus 4), and at depth 4 it returns
|
|
409
|
+
**missing with no error**: raised `_Model_Client_TransferOrder::NETSUITE_READ_DEPTH` 4 → 6 and added
|
|
410
|
+
`TOGA_FETCH_DEPTH__TRANSFER_ORDER = 6` for the fallback fetch, sales orders unchanged at 4. Also
|
|
411
|
+
recorded the durable rule that **deepening a depth is an ACL change** — the new branch crosses Core
|
|
412
|
+
records 69/12/23 and `Client_Nychh` had no role-3 grant on any of them, which would have made the
|
|
413
|
+
whole fix a silent no-op (fixed by `Client_Nychh/2026-09-09d`). Code written and `php -l` clean but
|
|
414
|
+
**not deployed and not committed**. (bala)
|
|
331
415
|
- 2026-09-08 — **Went live, and fixed the customer.** `buildSalesOrderShapeFromTransferOrder()` was
|
|
332
416
|
sending the **destination Location** as the NetSuite customer, which put SO **7462031** on
|
|
333
417
|
sub-customer **33674** (CHS) instead of **28908**; it only ever looked correct because almost every
|
package/knowledge/INDEX.md
CHANGED
|
@@ -21,7 +21,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
21
21
|
- **_underscore** (_Underscore) _(framework core)_ — 82 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
|
|
22
22
|
- **worker2** (Worker) — 68 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
|
|
23
23
|
- **api2** (API) — 25 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
|
|
24
|
-
- **dbchanges2** (Database Changes) _(framework core)_ —
|
|
24
|
+
- **dbchanges2** (Database Changes) _(framework core)_ — 17 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
25
25
|
- **toga2-supply** (TOGa Supply) — 9 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
|
|
26
26
|
- **saml** (SAML SSO Gateway) — 6 doc(s) → [2.0/apps/saml/INDEX.md](2.0/apps/saml/INDEX.md)
|
|
27
27
|
- **toga2-view** (TOGa View Frontend) — 12 doc(s) → [2.0/apps/toga2-view/INDEX.md](2.0/apps/toga2-view/INDEX.md)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
| Doc | Framework | Summary |
|
|
4
4
|
|-----|-----------|---------|
|
|
5
|
+
| [NYCHH transfer-order destinations — location shipping addresses + delivery contacts](features/location-shipping-addresses.md) | 2.0 | Every one of NYCHH's **24 shipping locations** (`Locations.locationTypeId = 1`) resolved to the same address — **50 Water St., 3rd Floor, New York, NY 10004**, |
|
|
5
6
|
| [NYCHH inventory-adjustment → item-fulfillment linking (direct FK columns, REST)](features/netsuite-inventory-adjustment-fulfillment-link.md) | 1.0 | For NYCHH stock adjustments, the NetSuite **inventory adjustment** carries a link to the **Item Fulfillment** it corrects, on the multi-select body custom field |
|
|
6
7
|
| [NYCHH NetSuite → TransferOrders import (stocking-flag routing, PO bridges, origin location)](features/netsuite-transfer-order-import.md) | 1.0 | NYCHH's NetSuite **transfer orders are represented as NetSuite sales orders** (same as the GroWrk pattern), so the shared NetSuite → TOGa Supply importer (`App_ |
|
|
7
8
|
| [NYCHH PO links are UPSTREAM — the downstream SO→PO route returns empty](features/po-number-upstream-direction.md) | 2.0 | NYCHH's sales orders are created **from the customer's purchase order**, so their SO↔PO links live in the **upstream** table `PurchaseOrders_SalesOrders` (route |
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "NYCHH transfer-order destinations — location shipping addresses + delivery contacts"
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: dbchanges2
|
|
5
|
+
project: Database Changes
|
|
6
|
+
client: nychh
|
|
7
|
+
type: client-feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-09-10
|
|
10
|
+
owners: [bala]
|
|
11
|
+
files:
|
|
12
|
+
- dbchanges2/Client_Nychh/2026-09-09c - UpdateLocationShippingAddresses.sql
|
|
13
|
+
- dbchanges2/Client_Nychh/2026-09-09a - DeleteTestContacts.sql
|
|
14
|
+
- dbchanges2/Client_Nychh/2026-09-09b - InsertDeliveryContacts.sql
|
|
15
|
+
- _underscore/Model/Client/Location.php
|
|
16
|
+
- toga25-supply/src/pages/TransferOrders/view/CreateTransferOrderModal/hooks/useTargetLocationOptions.ts
|
|
17
|
+
related:
|
|
18
|
+
- ../profile.md
|
|
19
|
+
- ./netsuite-transfer-order-import.md
|
|
20
|
+
- ./transfer-order-netsuite-push.md
|
|
21
|
+
- ../../../2.0/apps/toga25-supply/features/transfer-orders-page.md
|
|
22
|
+
- ../../../2.0/apps/_underscore/features/calculated-sql-fields.md
|
|
23
|
+
- ../../../2.0/apps/dbchanges2/workflows/deleting-contacts-from-a-client-database.md
|
|
24
|
+
- ../../../2.0/apps/worker2/features/netsuite-transferorder-outbound-push.md
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Summary
|
|
28
|
+
|
|
29
|
+
Every one of NYCHH's **24 shipping locations** (`Locations.locationTypeId = 1`) resolved to the same
|
|
30
|
+
address — **50 Water St., 3rd Floor, New York, NY 10004**, attention *"Accounts Payable OTPS"*. That
|
|
31
|
+
is the head-office **Accounts Payable BILLING** address, not a delivery address, so the Create
|
|
32
|
+
Transfer Order target-location list showed one identical address 24 times and a transfer had no real
|
|
33
|
+
delivery address. Fixed 2026-09-09 by backfilling each location's own **NetSuite default shipping
|
|
34
|
+
address**.
|
|
35
|
+
|
|
36
|
+
The load-bearing fact behind the whole issue: **for NYCHH a `Locations` address only affects transfer
|
|
37
|
+
orders.** `SalesOrders` carries its own `shipToAddressId` per order and those already held the real
|
|
38
|
+
hospital addresses (462 1st Ave, 451 Clarkson Ave, 100 N Portland Ave, …). `TransferOrders` has **no
|
|
39
|
+
ship-to column at all** — only `originLocationId` / `destinationLocationId` — so a transfer's
|
|
40
|
+
delivery address is read off the location record. That is why a wrong location address was invisible
|
|
41
|
+
everywhere except the transfer screen.
|
|
42
|
+
|
|
43
|
+
## How it works — where a location's shipping address comes from
|
|
44
|
+
|
|
45
|
+
- Each `Locations` row points at its own `Addresses` row through `Locations_Addresses`. The rows
|
|
46
|
+
**existed** (`Addresses` ids **261–303**); they simply all held the same values, so this was a
|
|
47
|
+
data problem, not a missing-topology problem.
|
|
48
|
+
- The source of truth is the location's **NetSuite customer** record:
|
|
49
|
+
`Locations.c_netsuiteInternalCustomerId` → `customer` → `customerAddressbook` with
|
|
50
|
+
**`defaultshipping = 'T'`**. (On a NYCHH transfer order the destination location is the NetSuite
|
|
51
|
+
*customer* and the origin is the NetSuite *warehouse* — see
|
|
52
|
+
[transfer-order NetSuite push](./transfer-order-netsuite-push.md).)
|
|
53
|
+
- `attention` belongs on a **bill-to**, not a ship-to. The backfill cleared it, because it carried
|
|
54
|
+
*"Accounts Payable"* on rows now used as delivery addresses.
|
|
55
|
+
|
|
56
|
+
## The migration — `Client_Nychh/2026-09-09c - UpdateLocationShippingAddresses.sql`
|
|
57
|
+
|
|
58
|
+
1. **UPDATE 23 existing `Addresses` rows in place** with the NetSuite default-shipping values.
|
|
59
|
+
2. **INSERT one new row** for location **2 "NYC Health + Hospitals"** and repoint
|
|
60
|
+
`Locations_Addresses.id = 24` at it, because that location was **sharing `Addresses.id = 1` with
|
|
61
|
+
location 1 (Lincoln)** — updating the shared row would have silently changed Lincoln too.
|
|
62
|
+
3. Clear `attention`.
|
|
63
|
+
|
|
64
|
+
**Why overwriting in place was safe:** verified that **no** `SalesOrders`, `PurchaseOrders`,
|
|
65
|
+
`ItemFulfillments`, `AdvanceShippingNotices`, `ServiceRequests` or `Customers` row pointed at any of
|
|
66
|
+
those `Addresses` ids. Do that check before reusing an `Addresses` row anywhere — an `Addresses` row
|
|
67
|
+
is shareable, so an in-place UPDATE is a multi-record edit until proven otherwise.
|
|
68
|
+
|
|
69
|
+
**Applied and verified in production 2026-09-09.** All 24 locations now carry their own street, city
|
|
70
|
+
and zip: **22 distinct addresses across 24 locations**, and both repeats are real per NetSuite
|
|
71
|
+
(Harlem + Renaissance at 506 Lenox Ave; Jacobi + NYC Health + Hospitals at 1400 Pelham Parkway
|
|
72
|
+
South).
|
|
73
|
+
|
|
74
|
+
## Delivery contacts — 12 test rows replaced with 19 real people (2026-09-09)
|
|
75
|
+
|
|
76
|
+
The same Create Transfer Order screen picks a **Target Contact** from `Contacts` through the
|
|
77
|
+
**`Contacts_Locations`** junction, scoped to the chosen location. `Client_Nychh` held **12 test
|
|
78
|
+
contacts**; they were deleted and **19 real NYCHH delivery contacts** inserted (ids **1–19**, **18**
|
|
79
|
+
location links) by `2026-09-09a - DeleteTestContacts.sql` and `2026-09-09b - InsertDeliveryContacts.sql`.
|
|
80
|
+
|
|
81
|
+
- Shape per person: one `Contacts` row, one `ContactEmailAddresses` row (`name = 'Work'`) set as
|
|
82
|
+
`primaryContactEmailAddressId`, and a `Contacts_Locations` row with `type = 'SHIPPING'`.
|
|
83
|
+
- **Omar Moharem covers two facilities** (Dr. Susan Smith McKinney and Kings County) — **one** contact
|
|
84
|
+
with **two** `Contacts_Locations` rows, not two contacts. That is why 19 contacts produce 18 links
|
|
85
|
+
alongside the two people below who have none.
|
|
86
|
+
- `Client_Nychh.ContactTypes` is **empty**, so `contactTypeId` is left NULL.
|
|
87
|
+
- **Deleting the old rows was the hard part** — nothing cascades and `Contacts` points back at its own
|
|
88
|
+
children. The full order is in
|
|
89
|
+
[deleting Contacts from a client database](../../../2.0/apps/dbchanges2/workflows/deleting-contacts-from-a-client-database.md).
|
|
90
|
+
|
|
91
|
+
## Open questions / known bad data
|
|
92
|
+
|
|
93
|
+
- **🚩 OPEN — two contacts have no location link.** **Erald Velmishi** (Central Office) and **Harry
|
|
94
|
+
Ho** (Community Care) got no `Contacts_Locations` row because **neither facility exists in
|
|
95
|
+
`Locations`**. They will not appear in the Target Contact dropdown anywhere until the locations are
|
|
96
|
+
created.
|
|
97
|
+
- **🚩 OPEN — nothing reads the chosen contact.** `TransferOrders.contactId` is a real FK on
|
|
98
|
+
`_Model_Client_TransferOrder`, but **no code in worker2 or toga25-supply reads it**. NetSuite
|
|
99
|
+
ship-to addresses do carry a person on the first line (order **289193** shows *Frederick Roberts*,
|
|
100
|
+
the Bellevue delivery contact), so wiring the contact into the NetSuite `attention` field is the
|
|
101
|
+
obvious next step — **undecided, not implemented**. See
|
|
102
|
+
[transfer-order → NetSuite push](../../../2.0/apps/worker2/features/netsuite-transferorder-outbound-push.md).
|
|
103
|
+
|
|
104
|
+
- **🚩 OPEN — is location 2 "NYC Health + Hospitals" a real delivery place?** It is the destination
|
|
105
|
+
on **1,832 of 1,939** NYCHH transfer orders (next highest is Coney Island at 27), so it behaves as
|
|
106
|
+
a **catch-all destination**. But every one of those 1,832 has `createdByUserId` **NULL** — not one
|
|
107
|
+
was created by a person on the Create Transfer Order screen. They come from the automated NetSuite
|
|
108
|
+
import (277 on 2026-08-29, 1,528 on 2026-08-30, then 1–3/day). Its address was set to NetSuite's
|
|
109
|
+
value (**1400 Pelham Parkway South, Bronx NY 10461**) at the developer's direction, but that is
|
|
110
|
+
**Jacobi Medical Center's** address. Still with the project manager: is this a real receiving
|
|
111
|
+
place, or should the import pick the actual hospital instead? **Do not treat its address as
|
|
112
|
+
settled**, and see [the NetSuite transfer-order import](./netsuite-transfer-order-import.md).
|
|
113
|
+
- **⚠ Two locations carry NetSuite customer ids that no longer exist.** **Bellevue Hospital Center →
|
|
114
|
+
29275** and **Jacobi Medical Center → 31917**; neither id is in the NetSuite `customer` table, so
|
|
115
|
+
their addresses came from other sources rather than a `defaultshipping` row. The ids need
|
|
116
|
+
correcting; until then any NetSuite lookup keyed on them returns nothing.
|
|
117
|
+
|
|
118
|
+
## Gotchas
|
|
119
|
+
|
|
120
|
+
- **⚠ A location's address surfaces ONLY on transfer orders for this client.** Sales orders carry
|
|
121
|
+
their own `shipToAddressId`, so "the address is fine on the order screens" says nothing about the
|
|
122
|
+
location record.
|
|
123
|
+
- **⚠ An `Addresses` row can be shared by several locations** (ids 1 and 2 shared `Addresses.id = 1`
|
|
124
|
+
here). Always check before an in-place UPDATE; insert + repoint the junction instead.
|
|
125
|
+
- **⚠ `attention = "Accounts Payable OTPS"` is the tell that a row is a BILL-TO** being used as a
|
|
126
|
+
ship-to. If a whole tenant's locations share one address, suspect the head-office AP address.
|
|
127
|
+
- **⚠ The state renders as a number until `_addressState` ships.** The fixed
|
|
128
|
+
`_Model_Client_Location::_addressState()` (join `States`, select `States.code`) is written but
|
|
129
|
+
**not deployed**, so production still shows `Bronx, 33 10451-5504`. See
|
|
130
|
+
[FIELD_SQL calculated fields](../../../2.0/apps/_underscore/features/calculated-sql-fields.md).
|
|
131
|
+
|
|
132
|
+
## Change history
|
|
133
|
+
|
|
134
|
+
- 2026-09-09 — Replaced the **12 test contacts** with **19 real delivery contacts** (ids 1–19, 18
|
|
135
|
+
`Contacts_Locations` `SHIPPING` links; Omar Moharem covers two facilities on one contact), each with
|
|
136
|
+
a `'Work'` `ContactEmailAddresses` row as `primaryContactEmailAddressId` and `contactTypeId` NULL
|
|
137
|
+
because `ContactTypes` is empty. The delete needed a specific FK order —
|
|
138
|
+
[written up separately](../../../2.0/apps/dbchanges2/workflows/deleting-contacts-from-a-client-database.md).
|
|
139
|
+
Left open: Erald Velmishi and Harry Ho have no location link (their facilities do not exist in
|
|
140
|
+
`Locations`), and `TransferOrders.contactId` is still read by nothing. (bala)
|
|
141
|
+
- 2026-09-10 — Recorded the backfill and the surrounding facts. All **24** `locationTypeId = 1`
|
|
142
|
+
locations shared the head-office AP billing address (50 Water St), so
|
|
143
|
+
`Client_Nychh/2026-09-09c - UpdateLocationShippingAddresses.sql` pulled each location's NetSuite
|
|
144
|
+
`defaultshipping` address, UPDATEd 23 `Addresses` rows in place, INSERTed one for location 2 (it
|
|
145
|
+
was sharing `Addresses.id = 1` with Lincoln) and cleared `attention`. Applied and verified in
|
|
146
|
+
prod: 22 distinct addresses over 24 locations. Also captured that a location address drives
|
|
147
|
+
**transfer orders only** (`TransferOrders` has no ship-to column), the open question about
|
|
148
|
+
catch-all destination location 2 (1,832 of 1,939 transfers, all `createdByUserId` NULL, address
|
|
149
|
+
currently Jacobi's), and the two dead NetSuite customer ids (Bellevue 29275, Jacobi 31917).
|
|
150
|
+
(bala)
|
|
@@ -6,7 +6,7 @@ project: Library
|
|
|
6
6
|
client: nychh
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-10
|
|
10
10
|
owners: [jcardinal, bala, rgirish]
|
|
11
11
|
files:
|
|
12
12
|
- library/app/api/toga2.php
|
|
@@ -29,6 +29,7 @@ related:
|
|
|
29
29
|
- ./transfer-order-inventory-quantities.md
|
|
30
30
|
- ./transfer-order-netsuite-push.md
|
|
31
31
|
- ../../../2.0/apps/worker2/features/netsuite-transferorder-outbound-push.md
|
|
32
|
+
- ./location-shipping-addresses.md
|
|
32
33
|
- ../profile.md
|
|
33
34
|
---
|
|
34
35
|
|
|
@@ -286,6 +287,13 @@ item receipts replays about two years unless it is rewound forward first.
|
|
|
286
287
|
|
|
287
288
|
## Gotchas / known issues
|
|
288
289
|
|
|
290
|
+
- **🚩 The import sends almost everything to catch-all destination location 2 "NYC Health +
|
|
291
|
+
Hospitals" — 1,832 of 1,939 transfer orders**, next highest Coney Island at 27, and **every one
|
|
292
|
+
has `createdByUserId` NULL** (277 on 2026-08-29, 1,528 on 2026-08-30, then 1–3/day). Whether that
|
|
293
|
+
location is a real receiving place or the import should resolve the actual hospital is **open with
|
|
294
|
+
the PM**; its address is currently Jacobi's. Do not assume the destination is correct:
|
|
295
|
+
[NYCHH location shipping addresses](./location-shipping-addresses.md).
|
|
296
|
+
|
|
289
297
|
- **Real-TrnfrOrd item receipts are disabled — see the DEFERRED section above.** A receipt whose
|
|
290
298
|
`createdFrom` is a real Transfer Order is skipped by design; do not read that as the importer being
|
|
291
299
|
broken.
|
|
@@ -307,6 +315,11 @@ item receipts replays about two years unless it is rewound forward first.
|
|
|
307
315
|
|
|
308
316
|
## Change history
|
|
309
317
|
|
|
318
|
+
- 2026-09-10 - Recorded that the import concentrates **1,832 of 1,939** transfer orders on catch-all
|
|
319
|
+
destination **location 2**, all with `createdByUserId` NULL (so none came from the Create Transfer
|
|
320
|
+
Order screen), and that whether it should resolve the real hospital instead is still open with the
|
|
321
|
+
PM. Cross-linked the location-address backfill. (bala)
|
|
322
|
+
|
|
310
323
|
- 2026-09-08 - **Closed transfer orders never reached the Closed stage** - the `status === 'Closed'`
|
|
311
324
|
`continue` in `common_sync_togasupply.php` (~L746) runs before the `isTransferOrder()` branch, so
|
|
312
325
|
`syncTransferOrderFromNetsuite`'s `case 'Closed'` was dead code and **18 NYCHH transfer orders sat
|
|
@@ -6,18 +6,21 @@ project: _Underscore
|
|
|
6
6
|
client: nychh
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-09-
|
|
9
|
+
updated: 2026-09-10
|
|
10
10
|
owners: ["bala"]
|
|
11
11
|
files:
|
|
12
12
|
- _underscore/Model/Client/TransferOrder.php
|
|
13
13
|
- worker2/Worker/Netsuite/SalesOrder.php
|
|
14
14
|
- dbchanges2/Client_Nychh/2026-09-02a - TransferOrderNetsuitePushInterceptor.sql
|
|
15
|
+
- dbchanges2/Client_Nychh/2026-09-09d - ApiRoleReadLocationAddress.sql
|
|
15
16
|
- test/@Bala/tests/netsuite_transferorder_payload_tests.php
|
|
16
17
|
related:
|
|
17
18
|
- ../../../2.0/apps/worker2/features/netsuite-transferorder-outbound-push.md
|
|
18
19
|
- ../../../2.0/apps/api2/features/api-payload-interceptors.md
|
|
19
20
|
- ../../../2.0/apps/dbchanges2/workflows/nonprod-metadata-drift-repair.md
|
|
21
|
+
- ../../../2.0/apps/api2/features/nested-fk-acl-embedding.md
|
|
20
22
|
- ./netsuite-transfer-order-import.md
|
|
23
|
+
- ./location-shipping-addresses.md
|
|
21
24
|
- ./po-number-upstream-direction.md
|
|
22
25
|
- ../profile.md
|
|
23
26
|
---
|
|
@@ -91,6 +94,42 @@ from `TransferOrders.description` (now sent as NetSuite `memo`, a NYCHH custom-f
|
|
|
91
94
|
So the location-role table above describes **which column a location carries**, not what gets sent
|
|
92
95
|
as `entity`. Destination is still what resolves the *ship-to*; it is no longer the customer.
|
|
93
96
|
|
|
97
|
+
## ⚠ `Client_Nychh` was missing the role-3 ACL rows the ship-to address needs (fixed 2026-09-09)
|
|
98
|
+
|
|
99
|
+
The push now sends a ship-to address taken from the destination Location, which needs a **depth-6**
|
|
100
|
+
read (`order → destinationLocation → primaryLocationAddress → address → state → country`) — mechanics
|
|
101
|
+
in [the shared push doc](../../../2.0/apps/worker2/features/netsuite-transferorder-outbound-push.md).
|
|
102
|
+
**That deeper read was dead on arrival for NYCHH.**
|
|
103
|
+
|
|
104
|
+
The push reads `/transfer-orders` as **roleId 3 “API”**, authenticating with the `Client_Nychh.Apis`
|
|
105
|
+
row named **`Agilant`**. `Client_Nychh.AclRecordPermissions` had **no roleId 3 row** for Core records
|
|
106
|
+
**69 “Location addresses”**, **12 “States”** or **23 “Countries”** — only roleId **1 “Base”**. Roles
|
|
107
|
+
are resolved per `roleId` in `api2/Component/Api/V2/V2.php`; **there is no Base-role inheritance**, so
|
|
108
|
+
role 1 grants nothing to a role-3 caller. `Client_Compass` already carried role 3 on all three, which
|
|
109
|
+
is why the identical read shape works there.
|
|
110
|
+
|
|
111
|
+
**Without those rows the expanded address never comes back, the new helper returns null, and no
|
|
112
|
+
address is sent — exactly the broken behaviour the code fix was meant to end.**
|
|
113
|
+
|
|
114
|
+
Fixed by `dbchanges2/Client_Nychh/2026-09-09d - ApiRoleReadLocationAddress.sql` (read-only roleId 3
|
|
115
|
+
rows for records 69, 12, 23, `NOT EXISTS`-guarded). **Run on prod and verified: role 3 now has
|
|
116
|
+
`allowRead` on records 11, 12, 13, 23, 69.**
|
|
117
|
+
|
|
118
|
+
> Generalised on [nested FK ACL embedding](../../../2.0/apps/api2/features/nested-fk-acl-embedding.md):
|
|
119
|
+
> deepening a `depth` is an ACL change, and the branch is dropped **silently**.
|
|
120
|
+
|
|
121
|
+
## Where the ship-to address comes from — and the location-2 problem
|
|
122
|
+
|
|
123
|
+
The address is the **destination Location's** `primaryLocationAddress.address`. Until 2026-09-09 all
|
|
124
|
+
**24** NYCHH shipping locations shared the head-office Accounts Payable billing address, so even a
|
|
125
|
+
correct push would have shipped everything to 50 Water Street; that backfill and its open questions
|
|
126
|
+
are in [location shipping addresses + delivery contacts](./location-shipping-addresses.md).
|
|
127
|
+
|
|
128
|
+
**Still open and still wrong for the push:** `Locations.id = 2` “NYC Health + Hospitals” carries
|
|
129
|
+
`Addresses.id 32536` = **1400 Pelham Parkway South**, which is **Jacobi's** address, not a real
|
|
130
|
+
delivery site for that location — and location 2 is the destination on the large majority of NYCHH
|
|
131
|
+
transfer orders. Any transfer order to location 2 will ship to Jacobi.
|
|
132
|
+
|
|
94
133
|
## Interceptor registration — record 312, and the deploy order matters
|
|
95
134
|
|
|
96
135
|
`dbchanges2/Client_Nychh/2026-09-02a - TransferOrderNetsuitePushInterceptor.sql` inserts into
|
|
@@ -125,6 +164,17 @@ switch that turns the feature on for NYCHH and nobody else.
|
|
|
125
164
|
|
|
126
165
|
## Change history
|
|
127
166
|
|
|
167
|
+
- 2026-09-09 — **The push had never sent a shipping address**, so NetSuite used customer 28908's
|
|
168
|
+
default site on every order: NetSuite order **7467799** was for North Central Bronx yet shipped to
|
|
169
|
+
Jacobi's *1400 Pelham Parkway South*, and SO **289187** (Woodhull) shows the same. The fix reads the
|
|
170
|
+
destination Location's `primaryLocationAddress.address`, which needs a **depth-6** read — and that
|
|
171
|
+
read was **dead on arrival for NYCHH**: `Client_Nychh.AclRecordPermissions` had **no roleId 3
|
|
172
|
+
(“API”) row** for Core records **69 Location addresses / 12 States / 23 Countries**, only roleId 1
|
|
173
|
+
(“Base”), which does not inherit. `Client_Compass` already had role 3 on all three. Added them with
|
|
174
|
+
`2026-09-09d - ApiRoleReadLocationAddress.sql` — **run on prod, role 3 now reads records 11, 12, 13,
|
|
175
|
+
23, 69.** The worker2 / `_underscore` code side is written and `php -l` clean but **not deployed and
|
|
176
|
+
not committed**. Also noted that location **2** still holds Jacobi's address, so transfers to the
|
|
177
|
+
catch-all destination will ship to the wrong hospital. (bala)
|
|
128
178
|
- 2026-09-08 — **First real order pushed (SO 289187 / id 7463537), and the customer was wrong until
|
|
129
179
|
the same day.** The destination location was being sent as the NetSuite customer, which put SO
|
|
130
180
|
**7462031** on sub-customer **33674** (CHS) instead of **28908** — `Locations` 1–26 are "Shipping"
|
|
@@ -18,7 +18,7 @@ project: _Underscore
|
|
|
18
18
|
client: nychh
|
|
19
19
|
type: profile
|
|
20
20
|
status: active
|
|
21
|
-
updated: 2026-09-
|
|
21
|
+
updated: 2026-09-10
|
|
22
22
|
owners: ["jcardinal", "apeterson", "bala", "akhokhani"]
|
|
23
23
|
files:
|
|
24
24
|
- dbchanges2/Client_Nychh/2026-09-02a - TransferOrderNetsuitePushInterceptor.sql
|
|
@@ -32,7 +32,10 @@ files:
|
|
|
32
32
|
- dbchanges2/Client/2026-08-28b - TransferOrderItemsTimestamps.sql
|
|
33
33
|
- dbchanges2/Client/2026-09-01a - PurchaseOrderItemQtyFieldsApiRoleRead.sql
|
|
34
34
|
- dbchanges2/Client_Nychh/2026-09-02a - TalosAssistantEnable.sql
|
|
35
|
+
- dbchanges2/Client_Nychh/2026-09-09c - UpdateLocationShippingAddresses.sql
|
|
35
36
|
related:
|
|
37
|
+
- ./features/location-shipping-addresses.md
|
|
38
|
+
- ../../2.0/apps/dbchanges2/workflows/deleting-contacts-from-a-client-database.md
|
|
36
39
|
- ../../2.0/apps/_underscore/features/tracking-number-bridges.md
|
|
37
40
|
- ../../2.0/apps/_underscore/features/tableview-joins.md
|
|
38
41
|
- ./features/po-number-upstream-direction.md
|
|
@@ -44,6 +47,7 @@ related:
|
|
|
44
47
|
- ./features/talos-assistant-enablement.md
|
|
45
48
|
- ../../2.0/apps/_underscore/features/sales-order-purchase-order-bridge-direction.md
|
|
46
49
|
- ../../2.0/apps/toga25-supply/features/transfer-orders-page.md
|
|
50
|
+
- ./features/location-shipping-addresses.md
|
|
47
51
|
---
|
|
48
52
|
|
|
49
53
|
## Summary
|
|
@@ -58,6 +62,17 @@ table views. Client-specific DB change-sets live in `dbchanges2/Client_Nychh/`.
|
|
|
58
62
|
run the NetSuite→TOGa Supply sync and the asset-tag verification/backfill diagnostics.
|
|
59
63
|
|
|
60
64
|
## Key features (this client)
|
|
65
|
+
- **All 24 shipping locations shared ONE address until 2026-09-09 — the head-office Accounts
|
|
66
|
+
Payable bill-to (50 Water St).** Each location did have its own `Addresses` row (261-303); the
|
|
67
|
+
rows just held identical values, and locations 1 + 2 shared `Addresses.id = 1`.
|
|
68
|
+
`Client_Nychh/2026-09-09c - UpdateLocationShippingAddresses.sql` backfilled each from its NetSuite
|
|
69
|
+
customer's `defaultshipping` address (applied + verified in prod: 22 distinct addresses over 24
|
|
70
|
+
locations). It only ever showed on the **transfer-order** screens, because `TransferOrders` has no
|
|
71
|
+
ship-to column while `SalesOrders` carries its own `shipToAddressId`. 🚩 Still open: catch-all
|
|
72
|
+
destination **location 2 "NYC Health + Hospitals"** (1,832 of 1,939 transfers, all
|
|
73
|
+
`createdByUserId` NULL) currently holds **Jacobi's** address, and Bellevue/Jacobi carry NetSuite
|
|
74
|
+
customer ids (29275 / 31917) that no longer exist. See
|
|
75
|
+
[NYCHH location shipping addresses](./features/location-shipping-addresses.md).
|
|
61
76
|
- 🚨 **sandbox-client (`Core` + `Client_Nychh`) was reset from PRODUCTION again on 2026-09-04** —
|
|
62
77
|
the second reset in two days (the first, 2026-09-03, was over the stale `purchase-orders`
|
|
63
78
|
TableView). Every "verified on <date>" sandbox-client claim in this profile dated **before
|
|
@@ -267,9 +282,11 @@ table views. Client-specific DB change-sets live in `dbchanges2/Client_Nychh/`.
|
|
|
267
282
|
Location is the one active `allowSending = 1` location, read-only from open; all lines on a
|
|
268
283
|
transfer must share one PO. `Locations.allowReceiving` is **unmaintained** for this tenant (1 of 31
|
|
269
284
|
active locations, and it is the source warehouse), so the target dropdown lists all active
|
|
270
|
-
locations minus the source.
|
|
271
|
-
|
|
272
|
-
|
|
285
|
+
locations minus the source. The populated contact join is **`Contacts_Locations`** (record 76),
|
|
286
|
+
not `Locations_Contacts`; contacts were sparse until 2026-09-09, when 12 test rows were replaced by
|
|
287
|
+
**19 real delivery contacts** (18 location links). See
|
|
288
|
+
[Transfer Orders page](../../2.0/apps/toga25-supply/features/transfer-orders-page.md) and
|
|
289
|
+
[location shipping addresses + delivery contacts](./features/location-shipping-addresses.md).
|
|
273
290
|
- **Inventory-adjustment → item-fulfillment linking** — imports NetSuite `custbody_stock_adjustment`
|
|
274
291
|
(adjustment→fulfillment) into two all-client bridges for adjustment→fulfillment→SO→PO traceability;
|
|
275
292
|
gated NYCHH-only, best-effort, with on-demand fulfillment import. Go-live toggle
|
package/package.json
CHANGED