Transfer Order Automation — Daily Overflow → Sydney Replenishment
Sydney takes customer orders it cannot fill from its own stock, while the spare stock sits in the Overflow warehouse. Once a day at close of business this automation reads what Sydney is short, finds that stock in the Overflow bins, and raises the Transfer Orders that move it — so a planner no longer reconciles spreadsheets by hand.
System: Transfer Order Automation (Asana "[Bonded WH/Overflow] TO Automation", BE-3412)
Source repository (read-only):
lifeinteriors/transfer-order-automation-s10Source of truth for intent: the Asana task — Definitions, rules BR-01…BR-13, Examples 1–4
Per the access policy, the logic is recorded here, not changed in the
source repository, and no source code is stored here — only described and linked. See
CONVENTIONS.md for how entries are structured.
Readings (per CONVENTIONS.md §8): this entry is served as three tabs — Business logic (readable with zero system access), Developer (inputs, processing, edge cases, defects) and Diagram. Everything above the switcher shows in all three.
⚠️ Status:
draft— the logic below is not deployed. Production reads the three saved searches and stops there. The allocation engine, the amend path and the shortfall alert all live on an unmerged branch. This entry describes intended, working-in-sandbox behaviour, not deployed behaviour — which is why it isdraftand notactive(CONVENTIONS.md §9). The developer reading has the detail.
Rules & IDs
Six rules, each with a stable anchor in the business reading, so links survive heading edits (CONVENTIONS.md §5).
| Rule ID | Rule | Reading |
|---|---|---|
LI-BL-INV-001 |
What gets replenished, and how much | Business logic |
LI-BL-INV-002 |
Whole bins, closest fit | Business logic |
LI-BL-INV-003 |
Overflow before Bonded | Business logic |
LI-BL-INV-004 |
Move what there is, flag the rest | Business logic |
LI-BL-FUL-004 |
Add to an open order before raising a new one | Business logic |
LI-BL-FRT-002 |
One shipment, one truckload | Business logic |
Rule IDs are domain-coded, so
FUL(fulfilment) andFRT(freight) IDs appear here even though the file's primarydomainis inventory (CONVENTIONS.md §4).
| What | Name in the system | |
|---|---|---|
| Lambda repositoryGitHub repository | lifeinteriors/transfer-order-automation-s10 @ 807826a — transfer-order-automation-s10 | open at 807826a |
| Saved searchNetSuite saved search | customsearch7895 — SCRIPT | TO AUTOMATION | 1/3 | Sydney Backorder Demand | open search 7895 |
| Saved searchNetSuite saved search | customsearch7898 — SCRIPT | TO AUTOMATION | 2/3 | Overflow Bin Availability | open search 7898 |
| Saved searchNetSuite saved search | customsearch7900 — SCRIPT | TO AUTOMATION | 3/3 | Active Transfer Order | open search 7900 |
The NetSuite links use the account-neutral host, which redirects a signed-in user to this account. Ids and names are exact, so a search stays findable by name even if a link does not resolve.
Three readings of the same rules. Nothing is duplicated between them. Printing gives you the business reading followed by the diagrams.
Written for the business — no field IDs, no script names, no system jargon.
What it does. Every evening the system looks at what Sydney is short, finds that stock in the Overflow warehouse, and raises the paperwork to truck it over — flagging anything it can't cover.
Why it exists. Sydney can't fulfil orders it doesn't have stock for, and the stock is often already in the business, just in the wrong warehouse. Done by hand it means a planner reconciling reports daily; it gets skipped, and customers wait for stock the company already owns.
When it runs. Once daily, at close of business, automatically. Nobody triggers it.
Who it affects. The inventory planner (who receives the shortfall alerts), the Overflow and Sydney warehouse teams (who pick and receive against the Transfer Orders), and — indirectly — every customer waiting on a backordered item.
How it decides.
- What Sydney is short of today, item by item — that number is the demand, and nothing else.
- Where that stock sits. Overflow is drained first; the bonded warehouse in Vietnam covers only what Overflow cannot, because it is six weeks away rather than one.
- Which bins to take it from — whole bins, closest fit. Sydney usually receives more than it was short, and that surplus is intended.
- What it cannot cover at all. That difference is emailed to the planner rather than quietly dropped.
- Where the movement goes: onto an open Transfer Order that is still amendable if one exists, otherwise onto a new one.
- How much fits. One Transfer Order is one truckload, so a load past the cap splits.
Outcomes. A Transfer Order is raised · an existing open order is added to · a shortfall is emailed to the planner · nothing happens, because the stock is already on its way.
What gets replenished, and how much LI-BL-INV-001
Every item Sydney is short of is a candidate, with no exclusions. The quantity to move is what is still outstanding after anything already on its way is taken into account.
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | Sydney is short of an item | It is a candidate to move from Overflow | That is the whole purpose |
| 2 | The shortage is zero or less | Nothing happens for that item | There is nothing to send |
| 3 | The stock is already on an open Transfer Order | It is not moved again | Otherwise the same stock is ordered every night |
Whole bins, closest fit LI-BL-INV-002
The system works in quantities, never in bin locations. It picks the tightest-fitting bin and moves all of it.
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | One bin holds enough to cover the shortage | The smallest bin that still covers it is chosen | Leaves the big bulk bins intact for larger jobs later |
| 2 | A bin is chosen | The whole bin moves, not just the shortfall | Emptying a bin is cleaner for the warehouse, and the extra stock in Sydney is accepted on purpose |
| 3 | No single bin covers the shortage | Whole bins are combined, biggest first, until covered | Fewest bins disturbed |
| 4 | Any bin is chosen | The bin is never named on the Transfer Order | The warehouse picks the physical bin when it picks the stock |
Overflow before Bonded LI-BL-INV-003
Two warehouses can supply Sydney, and they are days versus weeks apart. They are drained in a fixed order and never travel together.
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | The item is in Overflow | Overflow is used first | It is a local truck movement, about a week |
| 2 | Overflow can't cover it all and Bonded can | Bonded covers only the remainder | Bonded is Vietnam — roughly six weeks |
| 3 | Both warehouses supply the same order | Each raises its own Transfer Order | One shipment cannot come from two warehouses |
Move what there is, flag the rest LI-BL-INV-004
The automation never waits for perfect coverage and never silently skips an item.
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | The warehouses together can't cover the shortage | Everything available is moved anyway | Partial is better than nothing |
| 2 | Anything is left uncovered | The planner is emailed the gap | A human has to source it another way |
| 3 | The run is otherwise clean | The shortfall email is still sent | It is an operational alert, not an error report |
Add to an open order before raising a new one LI-BL-FUL-004
Fewer, fuller shipments. Existing orders are added to wherever they have room, and nothing already booked is ever taken back.
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | An open order already carries this item and has room | The quantity is added to that line | Keeps an item on one shipment instead of scattering it |
| 2 | An open order has room | It is added to the tightest-fitting one | Fewer shipments, fewer trucks |
| 3 | It would take three or more open orders to fit | A new order is raised instead | Splitting a line across three shipments helps nobody |
| 4 | Demand later drops, because an order was cancelled | The Transfer Order is left alone | Stock is never clawed back mid-flight |
| 5 | The team has already handled an order | It is locked — nothing is added | A shipment being picked must not change underneath the warehouse |
One shipment, one truckload LI-BL-FRT-002
A Transfer Order is one shipment, and one shipment is one truck.
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | Everything fits one truckload | One Transfer Order | A shipment is a truck |
| 2 | A single item's volume exceeds a truckload | It is split across the fewest new orders that will hold it | It physically will not fit |
| 3 | Adding to an open order would overfill it | The addition is refused | A truck that cannot be loaded is worse than a second truck |
What people get wrong
- "The system tells the warehouse which bin to pick from." It does not. The bin is never written onto the Transfer Order — only how many. The warehouse picks the physical bin at fulfilment.
- "Sydney gets exactly what it was short." It usually gets more. Whole bins move, so if Sydney is short 2 and the bin holds 4, all 4 travel. That surplus is intentional, not an error.
- "A shortfall email means the run failed." It didn't. The stock that was available has been moved; the email is only about the remainder.
- "If it's on a Transfer Order, it's on its way." An order sits awaiting approval until someone approves it. Until then it is a plan, not a movement.
- "Bonded stock arrives with the rest." Bonded is Vietnam — six weeks, not one. An order split across both warehouses arrives as two very different shipments.
Manual steps
| Step | Who | When | What happens if skipped |
|---|---|---|---|
| Act on the shortfall email | Inventory Planner | Whenever a run can't fully cover an item | The gap persists silently; the customer keeps waiting and nothing escalates |
| Clear the approval marker once the order is handled | Fulfilment team | After dealing with a Transfer Order | The automation keeps treating it as open and may keep adding lines to it |
| Deploy the NetSuite-side scripts | Backend | Whenever the write script changes | The automation calls an old script; new fields and guards silently don't apply |
Field registry
Every field this automation reads or writes, by the name it carries in the system — what it means, and what changes when it changes. Field ids are in the developer reading.
Inputs — what it reads
Values the automation looks at to make its decision. Change one of these and the decision changes.
| Field | What it means | What it changes | Status |
|---|---|---|---|
| Qty demand from sales order | How many units Sydney is short across its open sales orders. | This is the quantity moved. Nothing else sets it. | needs confirming |
| Item internal ID | Which product the shortage is for. | Identifies which product is short. If it does not resolve, the item is reported as a total shortfall (defect D1). | needs confirming |
| Qty available from Bin | How many units are sitting in one Overflow bin. | Raw on-hand in one Overflow bin. Caps how much can move from that bin. | needs confirming |
| Qty allocated from sales order | Recorded label says allocated to SALES orders, but the automation uses this value as stock already on open TRANSFER orders. Unresolved — see defect D2. | Subtracted from bin stock so stock already on an open order is not moved twice. If it is missing it defaults to zero and the same stock moves nightly (defect D2). | needs confirming |
| CBM from Item record | The volume of one unit of the item. | Volume of one unit. Multiplied by quantity to test the 15 CBM truck cap. | needs confirming |
| Bin location | Which warehouse and bin the stock is sitting in. | Decides which warehouse supplies the line, and therefore the lead time — 7 days from Overflow, 42 from Bonded. | needs confirming |
| Transfer Order Approval | Whether the team has dealt with this transfer order yet. | Only orders still awaiting approval can be added to. Anything else is locked. | needs confirming |
| Total CBMTransfer Order | Total volume of everything on the order. | Recalculated when lines are added, and the addition is refused if it would exceed one truckload. | confirmed |
Outputs — what it writes
Values the automation puts back. Everything downstream of this rule reads them, so a wrong value here travels.
| Field | What it means | What it changes | Status |
|---|---|---|---|
| LocationTransfer Order | The warehouse the stock is taken from. | Overflow or Bonded. Never mixed on one order, and it sets the lead time. | confirmed |
| To LocationTransfer Order | The warehouse the stock is sent to. | Always Sydney. Not checked when picking an order to add to, which is defect D3. | confirmed |
| Transfer Order ApprovalTransfer Order | Whether the team has dealt with this transfer order yet. | While it reads Pending Approval the automation may still add lines. Once the team changes it, the order is locked and further additions are refused. | confirmed |
| Override Lead Time (Days)Transfer Order | How long the stock is expected to take to arrive. | Sets the expected due date — 7 days from Overflow, 42 from Bonded. | confirmed |
| DateTransfer Order | The date the transfer order is raised. | Must be sent US-format or the order fails to save. | confirmed |
| Total CBMTransfer Order | Total volume of everything on the order. | Recalculated when lines are added, and the addition is refused if it would exceed one truckload. | confirmed |
| Commitment ConfirmedTransfer Order (line) | Locks the stock to this order so it is not reallocated elsewhere. | Set on every line so the allocation engine does not release the stock. | confirmed |
Deployment state — what is actually running
What is deployed and what is documented are two different code bases.
| Branch | Contains | Deployed? |
|---|---|---|
main @ 807826a |
Step 1 (read) only. Steps 2 and 3 are stubs — the decision step returns an empty payload and the write step is commented out. | ✅ Yes — merging to main auto-deploys to the production Lambda alias |
BE-3412-APPLY-BUSINESS-LOGIC @ 47c0845 |
The entire allocation engine, the amend path, the shortfall alert, and the wired-up write step — 40 commits, unmerged | ❌ No |
Two consequences worth stating plainly:
- The production config cannot run steps 2–3 even if merged.
config/production.mjsstill carries placeholder script ids for the Create-TO and dropdown RESTlets. The three production saved-search ids are set; the RESTlet ids are not. - The NetSuite-side scripts are deployed by hand and their state is not tracked in the repo. The
branch adds fields to the write script that require a redeploy to take effect (see
TO-D5).
Inputs
What it reads, and where from.
Three NetSuite saved searches, fetched sequentially — they are too large to route through the internal request queue, so parallel fetching risks request-limit errors.
| Field / source | System | Type | Notes |
|---|---|---|---|
QtyDemand |
Saved search #1 7895 — Sydney backorder demand |
string | Taken as the true remaining demand |
item_id |
Saved search #1 | select | Item internal id. The committed capture spells this item_ID — see TO-D1 |
itemName |
Saved search #1 | string | Used only in the shortfall alert |
binAvailableQty |
Saved search #2 7898 — Overflow bin availability |
string | Raw per-bin on-hand, not net |
itemallocated |
Saved search #2 | string | Item-level total already on open TOs. Same value on every row for the item — read once, never summed. See TO-D2 |
itemCBM |
Saved search #2 | string | Per-unit volume; drives the cap check |
binLocation |
Saved search #2 | select | Source lane — 23 Overflow or 25 Bonded |
to_id, fromLocation, totalCBM |
Saved search #3 7900 — active transfer orders |
select / string | Amend candidates and their remaining room |
approvalStatus |
Saved search #3 | select | Read as text to test for "Pending Approval" |
Search 7898 could not be fetched live — the integration role lacks Bin record access. Its
committed capture was generated from an admin CSV export as a best-effort stand-in.
Processing — demand & sourcing
What it does with that, step by step.
Traces LI-BL-INV-001 and LI-BL-INV-002.
on daily schedule:
read search 1 (demand), search 2 (bins), search 3 (open TOs)
for each item in search 1:
net demand = QtyDemand as returned (no subtraction applied)
if net demand <= 0 -> skip the item
available(source) = sum of that source's bins - allocation charged to it
allocation is consumed Overflow-first, spilling into Bonded if it exceeds the Overflow pool
to source N units at one source:
if any single bin holds >= N -> take the SMALLEST such bin, whole
else repeat until covered or dry:
if a remaining bin covers the rest -> take the smallest such bin, whole
else -> take the largest remaining bin, whole
cap the total taken at available(source)
The original rule (BR-02) computed net = gross − coverage, coverage being the quantity on open
Transfer Orders. The code no longer does this — as of commit d68b33f ("NSSS is authoritative:
remove code-side TO netting"), search #1's QtyDemand is used directly. Open TOs are read only to
find amendable ones. Protection against re-ordering the same stock now rests entirely on the
itemallocated supply guard — see TO-D2.
Selection is by quantity only. Bin identity, receipt date and FIFO play no part, because the bin is never recorded on the Transfer Order (BR-13 supersedes the FIFO reading of BR-10). Verified against a real Transfer Order record, whose inventory-detail flag was not set.
The availability cap is applied after the whole-bin pick, so when availability is tighter than the bin, a partial bin moves. A deliberate safety guard, but a deviation from BR-04's literal "move the entire bin".
Processing — source lanes & lead time
What it does with that, step by step.
Traces LI-BL-INV-003.
| Source | Location id | Order tried | Lead time applied | Meaning |
|---|---|---|---|---|
| Overflow | 23 |
first | 7 days | Local truck movement |
| Bonded WH Vietnam | 25 |
second, remainder only | 42 days | 7 (to departure) + 28 (transit) + 7 (buffer) |
| Destination — Sydney | 11 |
— | — | Every TO's destination |
for each source, Overflow before Bonded:
if nothing left to cover -> stop
source as much as that lane's availability allows
emit a line tagged with that source
carry any remainder to the next lane
The lead time is written as the TO's lead-time override, which drives its expected due date.
Open: the 15 CBM cap is a local truck limit. A Bonded movement is a sea container and its real cap is unconfirmed — 15 is assumed for now, which would wrongly chop a Bonded shipment into 15 CBM pieces. Only bites once a Bonded-sourced item is backordered.
Processing — placement & the CBM cap
What it does with that, step by step.
Traces LI-BL-FUL-004 and LI-BL-FRT-002. Cap = 15 CBM, the Metro Transfer truck capacity.
It is a planning threshold only — no ship method is written to the Transfer Order.
for each line (already tagged with its source):
if line volume > cap:
units per TO = floor(cap / item CBM)
open a fresh TO per chunk until the quantity is exhausted
force the next same-source line onto a new TO
else:
eligible = open TOs that are Pending Approval, share this source, and have room
1. eligible TO already carrying this item -> add the quantity there
2. else one eligible TO with room -> add to the tightest fit
3. else two eligible TOs with room -> split across those two
4. else -> start a new TO
Amendability keys off the custom Transfer Order Approval marker, not the main order status. The
automation sets it to Pending Approval on every TO it creates; the team clears it once the order
is handled, which locks it. The write script refuses to amend a locked TO, returning TO_LOCKED.
Not implemented: BR-09 re-solve — replacing a previous selection for an item and releasing it back to the pool. The write path only appends and increments. "Never reduce" (BR-07) therefore holds; "re-solve replaces" does not.
Decision tree — one backordered item, from a shortage to a Transfer Order
The same logic as a path through the decisions, in the order they run.
Read the deployment state first. This tree describes the allocation engine on
BE-3412-APPLY-BUSINESS-LOGIC.maincarries step 1 only — the decision step returns an empty payload and the write step is commented out — so on the state this entry records, none of the branches below run in production.TO-D9then found that a commit onmainclaims to be that engine, without the code being re-verified. Until someone re-reads it, treat this tree as what the engine decides, not as what happened last night.
Four stages, in the order they run: how much · from where · which bins · which Transfer Order.
Every branch names the rule it implements, and where an open defect changes what actually happens
the branch says so and names it — the tree follows the code, not the spec, and TO-D7 records that
the two have drifted apart.
Stage 1 — how much does Sydney need? LI-BL-INV-001
each item in the Sydney backorder search
├─ net demand = QtyDemand, EXACTLY AS RETURNED
│ ⚠ TO-D1: the engine reads the item id as `item_id`; the committed capture
│ spells it `item_ID`. Every item then resolves to undefined, finds no bins
│ and is reported as a total shortfall — with no error anywhere. A run that
│ hits this is indistinguishable from "Overflow is empty"
│
│ the spec said net = gross − what is already on open Transfer Orders.
│ The code stopped doing that at `d68b33f` — the saved search is treated as
│ authoritative. Open TOs are now read ONLY to find amendable ones TO-D7
│
├─ net demand ≤ 0 ? ──► skip the item entirely
└─ net demand > 0 ──► go to stage 2
Nothing else prevents the same stock being transferred every night. With the netting gone, the only guard left is the
itemallocatedsupply figure — and it is absent from the committed capture of the bin search, so it has never been seen working.TO-D2
Stage 2 — which warehouse supplies it? LI-BL-INV-003
Two lanes, always in this order, and never on the same Transfer Order: one order carries one source and one lead time, and 7 days against 42 is not a detail.
├─ 1. OVERFLOW (location 23) ──► drain first · lead time 7 days
│ available = its bins − the allocation charged to it
│ allocation is consumed Overflow-first, spilling into Bonded
│ only once the Overflow pool is exhausted
│
├─ 2. BONDED WH VIETNAM (location 25) ──► the remainder only · lead time 42 days
│ 7 to departure + 28 in transit + 7 buffer
│
├─ nothing left to cover ? ──► stop; later lanes are not tried
└─ still short after both ? ──► move everything available anyway,
and email the planner the gap LI-BL-INV-004
Destination is always Sydney (location 11). The lead time is written as the Transfer Order's lead-time override, which is what drives its expected due date.
Stage 3 — which bins, and how many units? LI-BL-INV-002
Quantities only. The bin is never written onto the Transfer Order — the warehouse picks the
physical bin at fulfilment — so bin identity, receipt date and FIFO play no part in the choice.
This was verified against a real Transfer Order record, whose inventory-detail flag was not set;
TO-D8 records that main's README still says the opposite, so the README is the thing that is
wrong here.
to source N units at one lane
├─ any single bin holds ≥ N ?
│ └─ take the SMALLEST such bin, WHOLE LI-BL-INV-002
│ leaves the bulk bins intact for larger jobs later
└─ no single bin covers it — repeat until covered or dry:
├─ a remaining bin covers the rest ? ──► take the smallest such bin, whole
└─ otherwise ──► take the largest remaining bin, whole
then cap the total taken at that lane's availability
⚠ the cap is applied AFTER the whole-bin pick, so when availability is tighter
than the bin a PARTIAL bin moves. A deliberate safety guard, and a deviation
from the rule's literal "move the entire bin"
Sydney receives more than it was short, on purpose. Short 2 against a bin of 4 moves all 4. That surplus is intended and is not an error.
Stage 4 — which Transfer Order does the line go on? LI-BL-FUL-004, LI-BL-FRT-002
Cap = 15 CBM, one Metro Transfer truckload. It is a planning threshold only — no ship method is written. The over-cap split is tested first, because a line that cannot fit a truck can never be added to one.
each line, already tagged with its source
├─ line volume > 15 CBM ?
│ └─ units per TO = floor(15 / item CBM)
│ open a fresh TO per chunk until the quantity is exhausted, and
│ force the next same-source line onto a new TO
│ ⚠ TO-D4: a SINGLE unit bigger than 15 CBM makes that floor 0, and the
│ line is emitted on a Transfer Order over the cap, silently
│
└─ otherwise, look for an open Transfer Order to amend
eligible = Pending Approval · same source · has room
⚠ TO-D3: eligibility never checks the DESTINATION, so a TO heading
somewhere other than Sydney can be selected
⚠ TO-D5: "amendable" is decided in two places that do not agree
⚠ TO-D6: the engine auto-approves what it creates, which conflicts with
the amendability model it then relies on
├─ 1. an eligible TO already carries this item ──► add to THAT line
├─ 2. else one eligible TO with room ──► add to the tightest fit
├─ 3. else two eligible TOs with room ──► split across those two
└─ 4. else (or it would take three or more) ──► raise a NEW Transfer Order
Nothing is ever taken back. Demand dropping after a Transfer Order exists leaves its lines untouched, and re-solve — replacing an earlier selection and releasing it to the pool — is not implemented: the write path only appends and increments.
TO-D7A handled order is locked. Amendability keys off the custom Transfer Order Approval marker, not the order status. The automation sets it to Pending Approval on everything it creates; the team clears it once the order is handled, and the write script then refuses to amend it, returning
TO_LOCKED. An amend that would breach the cap returnsCBM_CAP_EXCEEDEDand writes nothing — that line falls to a new Transfer Order on the next run.
What a run leaves behind
| Outcome | Where it lands |
|---|---|
| A new Transfer Order | Source 23 or 25 — never mixed · destination 11 · lines of item and quantity only · lead-time override · approval marker · run date in US MM/DD/YYYY, because ISO dates fail the save |
| Lines added to an open Transfer Order | Same RESTlet, routed by TO id |
| A shortfall | SES email to the inventory planner — sent on a clean run too, because it is an operational alert, not an error report |
| Nothing | The stock is already on an open Transfer Order, or demand was zero |
A Transfer Order is a plan, not a movement. It sits awaiting approval until a person approves it. And CBM is deliberately not written on create — NetSuite derives both line and header CBM from the item record. The engine computes it only to size shipments against the cap.
Outputs & side effects
What it writes, and who reads it afterwards.
| Output | Written to | Downstream consumer |
|---|---|---|
| New Transfer Order (source, destination, lines, lead time) | NetSuite, via the create/amend RESTlet | Overflow pickers, Sydney receiving |
| Added / incremented lines on an existing TO | NetSuite, same RESTlet, routed by TO id | As above |
| Approval marker set to Pending Approval | NetSuite TO header | Fulfilment team's queue |
| Shortfall list (item, needed, moved, short) | SES email | Inventory Planner |
| Technical error summary | SES email | Devs |
Created TO headers carry: source location 23 or 25 (never mixed on one TO), destination 11,
order status A — NetSuite forces this on create regardless — the approval marker, the lead-time
override, and the run date in US MM/DD/YYYY format, since ISO dates fail the save. Lines carry
item and quantity only — no bin, no cartons, no units. CBM is deliberately not written on
create: NetSuite derives both line and header CBM from the item record. The engine still computes
CBM internally, purely to size shipments against the cap.
Worked examples
Real records carried end to end, so the logic can be checked against something that happened.
Traced against the reference captures committed in the repo, with the allocation engine from
47c0845 run over them read-only. Six backordered items, all sourced from Overflow, no open Sydney
TO to amend.
| Item | Short | Bin holds | Moved | Why |
|---|---|---|---|---|
Alaska Bar Stool 148542 |
2 | 4 | 4 | One bin covers → whole bin; surplus 2 accepted |
Ava Dining Chair 103065 |
3 | 4 | 4 | Whole bin; surplus 1 |
Blake Low Bench 207402 |
1 | 8 | 8 | Whole bin; surplus 7 — the clearest illustration of BR-04 |
Palermo Dining Chair 381793 |
7 | 4 | 4 | No bin covers 7, only one bin exists → move all, short 3 |
Rose 2.5 Seater 400289 |
1 | 1 | 1 | Exact fit |
Sorrento Dining Chair 372081 |
1 | 6 | 6 | Whole bin; surplus 5 |
Result: one Transfer Order — Overflow 23 → Sydney 11, six lines, 8.83089 CBM (well under
the cap, so no split), Pending Approval, 7-day lead time. Plus one shortfall alert: Palermo
needed 7, moved 4, short 3.
Note the shape of it: Sydney was short 15 units in total and receives 27, because five of the six lines move a whole bin. That is the rule working as designed, and it is the single behaviour most likely to surprise the warehouse.
Test cases
What to run to know the behaviour is intact. Expected values, never “should work”.
Acceptance tests for each rule.
Environment. The config auto-selects from the Lambda alias — $LATEST or a local
run resolves to staging, which points at the NetSuite sandbox, with no code change between
environments. Start read-only: the GET-NSSS harness fetches the three searches and prints row counts
and a sample row, and is safe against production. Then the full-engine dry run computes payloads
without writing. Only then the create/amend harnesses, which ⚠️ create and amend real Transfer
Orders in the sandbox. Verify in NetSuite: source and destination locations, order status,
approval marker, lead-time override and expected due date, one line per item with the right
quantity, header CBM derived by NetSuite, and no bin/inventory detail set.
UAT
What a person checks, by hand, before it is trusted.
How the business signs this off. The Result column below doubles as the sign-off sheet — tick each row against a sandbox run and record who ticked it, following the environment steps above.
| ID | Scenario / input | Expected outcome | Result |
|---|---|---|---|
| A1 | Scheduled event received | All three searches fetched sequentially; row counts logged | ☐ |
| A2 | Non-scheduled / HTTP-shaped event | Not treated as scheduled | ☐ |
| A3 | Item with demand of zero or less | Skipped — no line, no TO | ☐ |
| A4 | One search returns an error | Run continues with an empty set; error emailed — note it is indistinguishable from "no stock" | ☐ |
| A5 | Live search #1 column name | Item id resolves; no item reports an undefined id (D1) | ☐ |
| B1 | Short 2, bins of 4 and 8 available | Smallest covering bin chosen → move 4, not 8 | ☐ |
| B2 | Short 1, single bin of 8 | Whole bin moves → 8; surplus 7 accepted | ☐ |
| B3 | Short 7, bins of 4 and 4 | Both bins combined → 8 moved | ☐ |
| B4 | Short 7, only one bin of 4 | Move 4, shortfall 3 raised | ☐ |
| B5 | itemallocated equals the full bin quantity |
Nothing moves — stock already on an open TO | ☐ |
| B6 | itemallocated column absent from the response |
Must fail loudly, not default to zero (D2) | ☐ |
| C1 | Item in Overflow only | One TO from 23, 7-day lead time |
☐ |
| C2 | Item in both lanes, Overflow covers it | Bonded untouched | ☐ |
| C3 | Item in both, Overflow partially covers | Overflow drained first; remainder from Bonded on a separate TO, 42-day lead time | ☐ |
| C4 | Bonded-sourced line over 15 CBM | Currently split at 15 — confirm the container cap first | ☐ |
| D1 | Partial coverage | Available stock still transferred and planner emailed | ☐ |
| D2 | Shortfalls on an otherwise clean run | Alert still sent, independent of the error email | ☐ |
| D3 | Recipient list empty | No email; run still logs the shortfalls | ☐ |
| E1 | No eligible open TO | New TO created, Pending Approval, approval marker set | ☐ |
| E2 | Eligible TO already carries the item | Existing line incremented, no duplicate line | ☐ |
| E3 | Two eligible TOs, line fits across both | Split across exactly those two | ☐ |
| E4 | Line would need three or more TOs | New TO instead — never fragmented | ☐ |
| E5 | Overflow line and Bonded line in one run | Separate TOs — sources never mixed | ☐ |
| E6 | TO whose approval marker has been cleared | Amend rejected as TO_LOCKED; nothing written |
☐ |
| E7 | Pending-Approval TO destined somewhere other than Sydney | Must NOT be selected as an amend target (D3) | ☐ |
| E8 | Demand drops after a TO exists | Existing lines untouched — never reduced | ☐ |
| E9 | Auto-approved TO, marker still Pending Approval | Define and assert the expected behaviour (D6) | ☐ |
| F1 | Total under 15 CBM | Single TO, no split | ☐ |
| F2 | Line of ~45 CBM | Three new TOs, each under 15 | ☐ |
| F3 | Amend would push a TO over the cap | Rejected as CBM_CAP_EXCEEDED; nothing written |
☐ |
| F4 | Single unit larger than 15 CBM | Must raise an exception, not emit an over-cap TO (D4) | ☐ |
| G1 | Create payload | Lines carry item + quantity only — no bin, cartons or units | ☐ |
| G2 | Created TO read back | Line and header CBM auto-derived by NetSuite | ☐ |
| G3 | ISO-format transaction date | Save fails — dates must be US MM/DD/YYYY |
☐ |
| G4 | Expected due date | Equals transaction date + lead time for the source | ☐ |
| G5 | RESTlet reports a logical failure at HTTP 200 | Surfaced as an error, not counted as success | ☐ |
| G6 | Write script redeployed after a field change | Approval-marker guard actually in effect (D5) | ☐ |
UAT sign-off
- ☐ Ran the read-only dry run over live search data before any write
- ☐ Column names (A5) and
itemallocated(B6) verified against live responses - ☐ Bin sourcing (B) — closest-fit, whole-bin and multi-bin all behave as specified
- ☐ Source lanes (C) — Overflow drained first; lanes never share a TO
- ☐ Placement (E) — E7 destination check passes
- ☐ CBM cap (F) — including the oversized-unit case
- ☐ Write mechanics (G) — sandbox TO inspected field by field
- ☐ Sign-off: name & date ____________________
Edge cases
The inputs that sit at the boundary, and whether each is handled.
| Case | Behaviour | Handled? |
|---|---|---|
| Demand of zero or less | Item skipped entirely | ✅ |
| Item in both source lanes | Overflow drained first, Bonded takes the remainder, separate TOs | ✅ |
| Stock already fully allocated to open TOs | Nothing moves for that item | ✅ (only if itemallocated is populated — TO-D2) |
| Whole bin larger than remaining availability | Partial bin moves | ✅ (deviates from BR-04's wording) |
| Single unit larger than the 15 CBM cap | Emits a Transfer Order over the cap, silently | ❌ — TO-D4 |
| Amend target destined somewhere other than Sydney | Selected anyway | ❌ — TO-D3 |
| Demand drops after a TO exists | Lines untouched | ✅ |
| Re-solve — replacing an earlier selection | Not implemented; appends only | ❌ |
| A saved search errors | Returns an empty row set; run continues | ⚠️ indistinguishable from "no stock" |
Failure modes
What breaks it, how that shows, and how to recover.
| Failure | Symptom | Detection | Recovery |
|---|---|---|---|
| Bin search unreachable (role lacks Bin access) | Every item reports a total shortfall | Full-list shortfall email | Grant Bin record access to the integration role; re-run |
| Item-id column renamed | Zero TOs created; every item reported short | Same shortfall email — looks identical to "Overflow is empty" | Reconcile the column name; re-run (TO-D1) |
itemallocated missing from the response |
Same stock re-transferred nightly | Duplicate TOs across consecutive days | Treat a missing value as a hard failure, not zero (TO-D2) |
| Write script rejects an amend | TO_LOCKED in the run log |
Error email | Expected when the team has handled the TO; no action |
| Amend would exceed the cap | CBM_CAP_EXCEEDED; nothing written |
Error email | Line falls to a new TO on the next run |
| RESTlet returns a logical failure at HTTP 200 | Surfaced as an error, not counted as success | Error email | Read the stage field — it names the failing field or step |
Known limitations
Scope that was deliberately chosen. A limitation is a decision.
Deliberate scope, not mistakes — the misreadings in What people get wrong above are the business- facing version of the same list. Keeping them here stops them being re-raised as bugs.
| Limitation | Why it is this way | What to do instead |
|---|---|---|
| Sydney receives more than it was short | Whole bins move, so a shortfall of 2 against a bin of 4 moves all 4 | Nothing. The surplus is intended |
| The bin to pick from is never written onto the Transfer Order | Only the quantity is transferred; the warehouse assigns bins at fulfilment | Warehouse picks the physical bin |
| A Transfer Order is a plan, not a movement | It sits awaiting approval until a person approves it | The fulfilment team approves and then clears the approval marker |
| A shortfall email is not a failed run | Everything available was moved; the email covers only the remainder | The planner acts on the remainder |
| Bonded and Overflow never share a Transfer Order | One order carries one source and one lead time — 7 days versus 42 | Expect two shipments for a split item |
| Nothing is escalated automatically | The shortfall email is the only channel | The planner is the escalation |
Known defects
Where it does something other than what was decided. A defect is a mistake.
| Ref | Status | Defect | Rule |
|---|---|---|---|
TO-D1High |
Open | Saved-search column name mismatch — silently produces zero TOs. The engine reads the backorder item id as item_id; the committed reference capture returns it as item_ID. Running the branch engine over the committed captures unchanged, every one of the six items resolves to an undefined item, finds no bins, and is reported as a total shortfall — zero Transfer Orders created, and a shortfall email listing every backordered item. Correcting the key alone produces the correct result in the worked example above. |
needs confirming |
TO-D2High |
Open | Anti-double-order protection rests on one un-captured field. With demand netting removed, the only thing preventing the same stock being transferred every night is the supply guard's itemallocated field. That field is absent from the committed capture of the bin search, and the code defaults a missing value to zero — meaning "nothing is allocated", so the full bin becomes available again on the next run. |
needs confirming |
TO-D3High |
Open | Amend eligibility never checks the destination. The spec defines an eligible TO as "open TO, Overflow → Sydney, Pending Approval, with room". The code checks the source and the approval marker — never the destination. Any Pending-Approval TO sourced from Overflow qualifies, wherever it is going. | needs confirming |
TO-D4Medium |
Open | Oversized single unit escapes the CBM cap. In the over-cap split, units-per-TO is floored and then raised to a minimum of one. If one unit of an item exceeds 15 CBM, a Transfer Order is created over the cap, silently. Rare, but sofas and modular pieces are the plausible candidates. Flag it as an exception rather than emitting an over-cap TO. | needs confirming |
TO-D5Medium |
Open | Two competing definitions of "amendable". Amendability is decided in two places that read different fields: the engine picks amend targets by matching the text "Pending Approval" in the open-TO search's approval-status column, while the write script's guard checks the custbody_to_approval field equals 1. |
needs confirming |
TO-D6Medium |
Open | Auto-approve conflicts with the amendability model. Created TOs are immediately auto-approved to Pending Fulfilment, because that state commits the source stock and makes the searches self-correct. But BR-11 says a TO is amendable only while Pending Approval. The approval marker is still set to Pending Approval on those same TOs, so the guard would permit an amend to a TO that NetSuite has already locked. The repo carries a test harness written specifically to probe whether a Pending-Fulfilment TO can be amended — i.e. this was known to be unresolved. Confirm the intended end state: auto-approve for commitment, or stay amendable. It cannot be both. | needs confirming |
TO-D7Low |
Open | The spec and the code have drifted apart. The spec is the artefact a reviewer would reach for first, which makes the drift costlier than it looks. | needs confirming |
TO-D8Low |
Open | Merged docs contradict a verified finding. main's README and netsuite/README.md still state that bins/inventory-detail are required and that the save will fail without them, and the write-step comment gives bin management as the reason for choosing a RESTlet. The branch (and PR #2) record the opposite, verified against a real record: inventory detail is not set, and the warehouse assigns bins at fulfilment. Documentation-only, but it is the currently-merged version that a new reader sees. |
needs confirming |
TO-D9High |
Open | The unmerged branch appears to have been merged — this entry's deployment state is stale. Reading main for the script_updated date on 2026-08-21 returned HEAD 0ca95c7, dated 2026-08-17, whose message is "BE-3412: Transfer Order automation — allocation engine (create/amend, auto-approve, commit, shortfalls) (#3)". The Deployment state section above records BE-3412-APPLY-BUSINESS-LOGIC @ 47c0845 as unmerged and not deployed, and main @ 807826a as read-only step 1. |
needs confirming |
Findings from reading the repository at 807826a (main) and 47c0845 (branch). Each was
reproduced against the committed reference data unless noted.
TO-D1 · Saved-search column name mismatch — silently produces zero TOs 🔴
The engine reads the backorder item id as item_id; the committed reference capture returns it
as item_ID. Running the branch engine over the committed captures unchanged, every one of
the six items resolves to an undefined item, finds no bins, and is reported as a total shortfall —
zero Transfer Orders created, and a shortfall email listing every backordered item. Correcting the
key alone produces the correct result in the worked example above.
There is no error and no exception: the failure looks exactly like "Overflow is empty". Whether it bites in production depends on which spelling the live search returns — unverifiable from the repository, and the captures are the only committed evidence. Confirm the live column name, and make an unresolved item id a hard error rather than a shortfall.
TO-D2 · Anti-double-order protection rests on one un-captured field 🔴
With demand netting removed, the only thing preventing the same stock being transferred every night
is the supply guard's itemallocated field. That field is absent from the committed capture of
the bin search, and the code defaults a missing value to zero — meaning "nothing is
allocated", so the full bin becomes available again on the next run.
The confirmed label makes this worse, not better. The field's recorded label is "Qty allocated from sales order" — allocated to sales orders. The code uses the value as stock already on open transfer orders. Those are different quantities, and the supply guard is only correct under the second reading. If the label is right, the guard is subtracting the wrong number and the whole anti-double-order argument fails. Resolve which it is before this merges — this now blocks the branch, not just flags it.
Compounding it: the bin search could not be fetched live at all, and its capture was generated from a CSV. The field now carrying the entire idempotency guarantee has never been observed in a live response. Verify it exists and is populated before this is merged; treat its absence as a hard failure, not a zero.
TO-D3 · Amend eligibility never checks the destination 🔴
The spec defines an eligible TO as "open TO, Overflow → Sydney, Pending Approval, with room". The code checks the source and the approval marker — never the destination. Any Pending-Approval TO sourced from Overflow qualifies, wherever it is going.
Not hypothetical: the committed capture of the open-TO search contains a live Overflow TO whose destination is Gold Coast Showroom - Miami. Re-running the engine with that TO marked Pending Approval — the state the search was widened to surface — placed all six of Sydney's replenishment lines onto the Gold Coast shipment and created no Sydney TO at all.
Today it is masked, because the captured TO is Pending Fulfilment and so filtered out. Once the search reliably surfaces Pending-Approval rows, the mask is gone. Add an explicit destination check in the code; don't rely on the saved search to enforce it.
TO-D4 · Oversized single unit escapes the CBM cap 🟠
In the over-cap split, units-per-TO is floored and then raised to a minimum of one. If one unit of an item exceeds 15 CBM, a Transfer Order is created over the cap, silently. Rare, but sofas and modular pieces are the plausible candidates. Flag it as an exception rather than emitting an over-cap TO.
TO-D5 · Two competing definitions of "amendable" 🟠
Amendability is decided in two places that read different fields: the engine picks amend targets
by matching the text "Pending Approval" in the open-TO search's approval-status column, while the
write script's guard checks the custbody_to_approval field equals 1.
Confirming the labels narrows this: the search column and the custom field carry the same label,
Transfer Order Approval, so they are almost certainly two reads of one field rather than two
different fields. That removes the risk of them describing different things — but not the mismatch
in how they are read, since one matches display text and the other compares a stored value. They
can still disagree, and a disagreement is only discovered at write time as a TO_LOCKED failure. The guard also depends on a
manual redeploy — the spec flags "redeploy required" twice. Against an older deployed script the
guard simply doesn't exist, and any TO can be amended.
TO-D6 · Auto-approve conflicts with the amendability model 🟠
Created TOs are immediately auto-approved to Pending Fulfilment, because that state commits the source stock and makes the searches self-correct. But BR-11 says a TO is amendable only while Pending Approval. The approval marker is still set to Pending Approval on those same TOs, so the guard would permit an amend to a TO that NetSuite has already locked. The repo carries a test harness written specifically to probe whether a Pending-Fulfilment TO can be amended — i.e. this was known to be unresolved. Confirm the intended end state: auto-approve for commitment, or stay amendable. It cannot be both.
TO-D7 · The spec and the code have drifted apart 🟡
| Spec says | Code does |
|---|---|
| Net demand = gross − coverage (Definitions, BR-02, C3/C4) | Takes demand as-is; no netting |
binAvailableQty net-ness is an open question |
Decided: treated as raw, offset by itemallocated |
| Re-solve replaces the previous selection (BR-09, C14) | Append/increment only — never replaces |
| Engine must recognise its own TOs (C16) | Superseded — amends any Pending-Approval TO |
The spec is the artefact a reviewer would reach for first, which makes the drift costlier than it looks.
TO-D8 · Merged docs contradict a verified finding 🟡
main's README and netsuite/README.md still state that bins/inventory-detail are required and
that the save will fail without them, and the write-step comment gives bin management as the reason
for choosing a RESTlet. The branch (and PR #2) record the opposite, verified against a real record:
inventory detail is not set, and the warehouse assigns bins at fulfilment. Documentation-only,
but it is the currently-merged version that a new reader sees.
Other things to watch
- No test gate before production.
npm testexits non-zero by design, and the deploy workflow pushes to the production alias on merge tomainwith no test step. - Remaining room on an existing TO is trusted from the search's header CBM total. If that value is stale or absent, room is computed as the full cap and the TO can be over-filled.
- A failed search returns an empty row set rather than aborting, so an outage looks identical to "no stock in Overflow" — and produces a full-list shortfall email rather than an error.
- Bonded container cap unconfirmed — 15 CBM assumed.
TO-D9 · The unmerged branch appears to have been merged — this entry's deployment state is stale 🔴
Reading main for the script_updated date on 2026-08-21 returned HEAD 0ca95c7, dated
2026-08-17, whose message is "BE-3412: Transfer Order automation — allocation engine
(create/amend, auto-approve, commit, shortfalls) (#3)". The Deployment state section above
records BE-3412-APPLY-BUSINESS-LOGIC @ 47c0845 as unmerged and not deployed, and main @
807826a as read-only step 1.
Only the dates and the commit subject were read — no code was re-verified — so this entry has
not been changed to describe the merged state. Production wins over the entry: if the engine is
now live, everything from Deployment state down needs re-verifying at 0ca95c7, and the
production config's placeholder RESTlet ids (see above) become urgent rather than theoretical.
Re-verify before relying on the deployment state recorded here.
Source references (read-only)
The code and searches this reading was written from.
Do not edit these files — read-only per the access policy.
Deployed — main @ 807826a:
transfer-order-automation-s10/index.mjs— handler, error/shortfall globals, run-end reportingtransfer-order-automation-s10/controllers/app.mjs— orchestration (read → decide → write)transfer-order-automation-s10/controllers/netsuite.mjs— saved-search reads and RESTlet callstransfer-order-automation-s10/helpers/netsuite.mjs— token-based-auth client; account chosen entirely by the secrettransfer-order-automation-s10/config/{production,staging}.mjs— search ids, script ids, recipientstransfer-order-automation-s10/json/— captured saved-search reference data and payload contracts
Not deployed — BE-3412-APPLY-BUSINESS-LOGIC @ 47c0845:
transfer-order-automation-s10/controllers/businessLogic.mjs— the allocation enginetransfer-order-automation-s10/controllers/createTO.mjs— write seam, create vs amend routingtransfer-order-automation-s10/netsuite/createTransferOrder.js— create/amend script, approval guard, cap checktransfer-order-automation-s10/controllers/aws.mjs— shortfall alert alongside the error emailtransfer-order-automation-s10/docs/allocation-engine-spec.md— the rules as written (seeTO-D7)
The Asana task remains the source of truth for intent; where this entry and the task disagree, the task wins for intent and production wins for behaviour (CONVENTIONS.md §1).
Change history
What moved on this page, and when.
| Date | Change | Change request |
|---|---|---|
| 2026-09-28 | Added a decision tree in the skeleton's slot (CONVENTIONS §15, §16): four stages — how much, from where, which bins, which Transfer Order — in the order they run, each branch naming the rule it implements. All six rules and all nine defects are cited, and every ref was checked to resolve. It restates what this entry already documents, drawn from the three Processing sections, the source-lane table and the rules' own tables; nothing new is asserted. It opens on the deployment state, because that is the first thing that decides whether any of it is running: main carries step 1 only, the decision and write steps are stubs, and TO-D9 records a commit claiming to be the engine without the code having been re-verified. The tree follows the code rather than the spec, which TO-D7 records as having drifted from it. No logic change and last_reviewed is unchanged. |
— |
| 2026-09-28 | Corrected the defect register. The migration earlier today read the first table under ### Known defects and registered its four rows as TO-D1 to TO-D4 — but that table is the spec-versus-code evidence belonging to D7, and this entry's nine real defects are written as prose beneath it with their own anchors and about twelve references pointing at them. The register now carries those nine, TO-D1 to TO-D9, with the severity each already showed; the anchors and every reference moved into the namespace with them, and the spec-versus-code table is back in the body under TO-D7 where it belongs. No defect was reinterpreted and none was lost — the finding text is unchanged. |
— |
| 2026-09-28 | Developer sections reordered to the registry's skeleton (CONVENTIONS §15): Inputs · Processing · Decision tree · Outputs · Worked examples · Test cases · UAT · Edge cases · Failure modes · Known limitations · Known defects · Open questions · Source references · Change history. The entries had drifted into two house styles — four put the proof after the outputs, four put the problems there — so every shared heading sat at a different position depending on which entry you opened; Test cases alone appeared at five different ones. Every section moved whole and byte-identical: nothing inside any of them was touched, and no wording changed. ### Worked example was renamed to the plural the skeleton uses; its Deployment state section stays above Inputs, where scene-setting belongs. The one-line description the site now prints under each heading is generated from build.mjs, not written here, so it reads the same on every entry. npm run check warns on a wrong order and on a missing section. No logic change, and last_reviewed is unchanged. |
— |
| 2026-09-28 | The test_cases: register was added and left empty. This entry records its harness and its sandbox warning but no cases, and writing them would mean inventing the expected column against logic the entry itself records as diverging from its spec. npm run check now warns on the gap rather than leaving it invisible. No behaviour was re-documented and last_reviewed is unchanged. |
— |
| 2026-09-28 | Defects and open questions moved from body tables into the defects: and open_questions: front-matter registers, namespaced TO-: 4 defects (4 open). The build renders them back under the same headings, so the page reads as it did; the prose around those tables is unchanged. They now also appear on the generated DEFECTS and Open questions pages, which is the point — the same list was unreadable spread across nine entries in five different column shapes. Every existing ref is preserved. No behaviour was re-documented, no defect status was reinterpreted, and last_reviewed is unchanged. |
— |
| 2026-09-28 | System slugs eventbridge, ses and secrets-manager renamed aws-eventbridge, aws-ses and aws-secrets-manager. eventbridge here and aws-eventbridge in the courier tracking entry were the same AWS service counted twice across the registry — the systems view of the Integration Map drew two platforms where there is one. npm run check now warns on a slug outside the known list. Front-matter only — no logic change, and last_reviewed is unchanged. |
— |
| 2026-08-31 | Placed on the order journey: journey_stage: supply (Supply), the stage confirmed with the business. The Organisation Map now groups and orders entries by journey stage rather than by folder. Front-matter only — no logic change, and last_reviewed is unchanged. |
— |
| 2026-08-21 | Restructured to the current registry shape: the business reading now answers what / why / who / when / how it decides / outcomes in that order; the developer reading gains worked examples, test cases, UAT, known limitations as named sections. Front-matter: version removed (nobody kept it accurate), replaced by documented_on / documented_updated for this page and script_created / script_updated for the source; links: added so the saved searches and flows are one click away. Test cases & UAT split into two sections. New defect D9: reading main for the script date returned HEAD 0ca95c7 dated 2026-08-17, whose subject says the BE-3412 allocation-engine branch was merged — this entry still records it as unmerged and undeployed. Only the commit date and subject were read; no code was re-verified, so the deployment state above is left as it was and flagged instead. No logic change, and last_reviewed is unchanged. |
— |
| 2026-08-21 | Linked the entry to the glossary: 10 terms recorded in terms:, covering the warehouses, the Transfer Order and the shortfall alert. Vocabulary only — no logic change, and last_reviewed is unchanged. |
— |
| 2026-08-17 | Recorded the NetSuite field labels supplied by the project owner across all 14 fields, and drafted the business meanings from those labels plus the documented behaviour. 7 fields are fully confirmed; 7 keep confirmed: false because their meaning is a draft awaiting sign-off, not because the label is missing. Two findings came out of the labels: itemallocated is labelled as allocated to sales orders while the code treats it as stock on open transfer orders, now recorded against TO-D2 as blocking; and approvalStatus shares the Transfer Order Approval label with custbody_to_approval, which narrows TO-D5 to a read-style mismatch rather than two different fields. No logic change. |
— |
| 2026-08-17 | Added the field registry: a fields: block naming every field the automation reads or writes, rendered as a business-facing table (name, meaning, what it changes) and an id-keyed developer table. 14 fields recorded; 2 confirmed, 12 flagged needs confirming — the field ids and impacts are derived from the code, while the NetSuite labels and business meanings do not exist in the code and await confirmation (WORKING_AGREEMENT §4). No logic change. |
— |
| 2026-08-17 | Removed the owner / tech_owner role fields entirely — front-matter, metadata bar, validator and conventions. Named people are now the only ownership record. Attribution only, so last_reviewed is unchanged. |
— |
| 2026-08-17 | Recorded ownership: project_owner Kim Hoang Nguyen, developers Julian Ayoub — corroborated by GitHub, where Julian-Ayoub authored both merged PRs and the unmerged business-logic branch — and for_department Fulfilment Team. The role fields owner / tech_owner are unchanged. Attribution only, so last_reviewed is unchanged. |
— |
| 2026-08-17 | Restructured into the three readings — ## Business logic / ## Developer logic / ## Diagram — per the reader-theme handoff and the rewritten CONVENTIONS §8. The standalone Workflow Map page was folded in as this entry's Diagram reading, and its seven Mermaid diagrams became six hand-written HTML trees. Rule IDs, anchors and all findings are unchanged. No logic change. |
BE-3412 |
| 2026-08-17 | Initial documentation, from main (807826a) and the unmerged BE-3412-APPLY-BUSINESS-LOGIC branch (47c0845). Allocated LI-BL-INV-001..004, LI-BL-FUL-004, LI-BL-FRT-002. Recorded eight divergences/defects — three of them (D1, D2, D3) reproduced against the committed reference captures. Status draft: the logic described is not deployed. |
BE-3412 |
Field registry — ids
Inputs
| Field id | Field name | Record | Source | Type | Read / written | Used by |
|---|---|---|---|---|---|---|
QtyDemand |
Qty demand from sales order | — | Saved search 7895 | integer | read | What gets replenished, and how muchLI-BL-INV-001 |
item_id |
Item internal ID | — | Saved search 7895 | select | read | What gets replenished, and how muchLI-BL-INV-001 |
binAvailableQty |
Qty available from Bin | — | Saved search 7898 | integer | read | Whole bins, closest fitLI-BL-INV-002 |
itemallocated |
Qty allocated from sales order | — | Saved search 7898 | integer | read | What gets replenished, and how muchLI-BL-INV-001 Whole bins, closest fitLI-BL-INV-002 |
itemCBM |
CBM from Item record | — | Saved search 7898 | decimal | read | One shipment, one truckloadLI-BL-FRT-002 |
binLocation |
Bin location | — | Saved search 7898 | select | read | Overflow before BondedLI-BL-INV-003 |
approvalStatus |
Transfer Order Approval | — | Saved search 7900 | select | read | Add to an open order before raising a new oneLI-BL-FUL-004 |
custbody_total_cbm |
Total CBM | Transfer Order | NetSuite | decimal | read-written | One shipment, one truckloadLI-BL-FRT-002 |
Outputs
| Field id | Field name | Record | Source | Type | Read / written | Used by |
|---|---|---|---|---|---|---|
location |
Location | Transfer Order | NetSuite | select | written | Overflow before BondedLI-BL-INV-003 |
transferlocation |
To Location | Transfer Order | NetSuite | select | written | Add to an open order before raising a new oneLI-BL-FUL-004 |
custbody_to_approval |
Transfer Order Approval | Transfer Order | NetSuite | select | written | Add to an open order before raising a new oneLI-BL-FUL-004 |
overrideleadtimedays |
Override Lead Time (Days) | Transfer Order | NetSuite | integer | written | Overflow before BondedLI-BL-INV-003 |
trandate |
Date | Transfer Order | NetSuite | date | written | — |
custbody_total_cbm |
Total CBM | Transfer Order | NetSuite | decimal | read-written | One shipment, one truckloadLI-BL-FRT-002 |
commitmentfirm |
Commitment Confirmed | Transfer Order (line) | NetSuite | checkbox | written | — |
Hover any box to see what it means. Click to pin it. Each tree carries the rule ID it belongs to; the defects named D1–D3 are written up in the Developer reading.
The daily run
Two side channels run off this: uncovered demand emails the planner, and any failure emails the developers. Those four endpoints are everything a run produces.
One item, end to end LI-BL-INV-001
Choosing bins LI-BL-INV-002
Whole bins move. Short 1, bin holds 8 → all 8 travel. The bin is never written on the Transfer Order — only quantities. The warehouse picks the physical bin at fulfilment.
Two source lanes LI-BL-INV-003
LI-BL-FUL-004 & LI-BL-FRT-002 · From a line to a Transfer Order
Lines are only ever added — never removed, never reduced.
Writing to NetSuite
Lines carry item and quantity only — no bin, no cartons, no units. NetSuite derives the volume.
Where it breaks today
Three defects reproduced against the repository's own captured data, shown at the step each one sits on. All three are written up in the Developer reading.
Five more — including an oversized unit escaping the CBM cap, and two competing definitions of "amendable" — are in the Developer reading.