Shipping Automation — Module 5: Courier Approval & Set Ship Date
System: Shipping Automation (Module 5 / Asana "Part 2")
Source repositories (read-only):
lifeinteriors/shipping-v6-nswo,lifeinteriors/shipping-v6-swoSource of truth for intent: Miro board "Module 2 V6.1 — Courier Approval & Set Ship Date"
Status: Documented from code + Miro board + live saved-search data + fulfilment schedule sheet.
This entry captures the business logic of the courier-approval automation. Per the
access policy, the logic is recorded here, not changed in the
source repositories, 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 (rules, edge cases, failure modes) and Diagram. Everything above the switcher shows in all three. The front-matter is the machine-readable layer that feeds
registry-index.json.
Rules & IDs
This file documents one integration made of four rules. Each has a stable anchor under its rule in the business reading, so links survive heading edits (CONVENTIONS.md §5).
| Rule ID | Rule | Where |
|---|---|---|
LI-BL-FUL-001 |
Which pile an order goes in | Business logic |
LI-BL-FUL-002 |
Orders that travel together | Business logic |
LI-BL-FRT-001 |
Picking the courier | Business logic |
LI-BL-FUL-003 |
Picking the day | Business logic |
Rule IDs are domain-coded, so
FRT(freight) andFUL(fulfilment) IDs both appear here even though the file's primarydomainis fulfilment (CONVENTIONS.md §4).
| What | Name in the system | |
|---|---|---|
| Flow 1 — Non-Ship-Whole-OrderGitHub repository | lifeinteriors/shipping-v6-nswo @ 0ed5ee9 — shipping-v6-nswo | open at 0ed5ee9 |
| Flow 2 — Ship-Whole-OrderGitHub repository | lifeinteriors/shipping-v6-swo @ 69029d7 — shipping-v6-swo | open at 69029d7 |
| Saved search — Flow 1 feedNetSuite saved search | customsearch7132 — SCRIPT | SHIPPING AUTOMATION 2 | COURIER APPROVAL | Ship Complete NO - V5 *live | open search 7132 |
| Saved search — Flow 2 feedNetSuite saved search | customsearch7136 — SCRIPT | SHIPPING AUTOMATION 2 | COURIER APPROVAL | Ship Complete YES - V5 *live | open search 7136 |
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. When a customer's order is paid and ready, the automation decides which courier should carry it and what date it ships, writes the approval back to NetSuite, and sets aside only the orders that don't fit the rules for a human to review.
Why it exists. Choosing a courier and a delivery day by hand, for every paid order, is slow and inconsistent — and each of the ways it goes wrong costs money. Part-delivering an order that was meant to travel together creates a redelivery. Booking a group that cannot ship complete wastes the truck slot. Arriving before the customer asked for is a failed delivery in the same way arriving late is. Deciding by rule makes the same call every time, and leaves a person to handle only the orders the rules genuinely cannot settle.
Who it affects. The fulfilment team feels it first: everything the automation holds lands in their review queue, and the courier schedule sheet it reads is theirs to maintain. The customer gets the delivery date it picks. Couriers and the Life truck runs get the volume it books against them.
When it runs. On a schedule, as two AWS Lambda functions: Flow 1 (nswo) runs first, then Flow 2 (swo). An order is eligible only when it is paid in full, not on hold, not already booked or approved or closed, carries no drop-ship items, holds only Standard or 2-Man goods, has no ship date already set in the past, and still has something to send. A send all together order also waits until every item across the linked orders is in stock. The run frequency is not recorded here — needs confirming.
How it decides.
- Which pile the order belongs in — sent as it becomes available, or held until the whole order can travel in one go. That decides which of the two flows handles it.
- What the customer paid for — Standard, 2-Man or Full Service — which fixes the list of couriers to try, in preferred order.
- Whether each courier in that list covers the delivery postcode. The first that does wins; if none do, the customer's checkout pick is used, and if there wasn't one, the order is held.
- Whether any item needs two people to handle it. One such item upgrades the whole order to 2-Man.
- The earliest sensible day, respecting the courier's notice period, the customer's requested date, and — for Life trucks — whether that region's truck still has room. NSW and ACT ship and deliver the same day; QLD and VIC go on the weekly linehaul.
- Anything the rules cannot settle is left for a person rather than guessed at.
It is delivered as two AWS Lambda functions with near-identical architecture, split by order type:
| Repository | Order type | Ship Complete | Service levels | Order handling |
|---|---|---|---|---|
shipping-v6-nswo |
Non-Ship-Whole-Order | No | Standard, Two Man | Each order processed independently |
shipping-v6-swo |
Ship-Whole-Order | Yes | Two Man, Full Service | Related orders merged and shipped together |
The two flows run as one system: Flow 1 (nswo) runs first, then Flow 2 (swo). An order can be promoted from Flow 1 to Flow 2.
In one line: the system looks at orders that are ready, works out the best courier and delivery day for each, and books it in automatically — asking a person to step in only when an order doesn't fit the rules.
Both repositories are read-only. Unlike the Celigo entries, this logic is under version
control, so drift is detectable: compare the commits in Where this runs above against each
repository's HEAD. That is why this entry carries no review_cycle — it does not need a clock
to know when it has gone stale.
The two piles of orders
- Send as it's ready — parts of the order can go out as stock lands (Ship Complete = No).
- Send all together — the customer's whole order must arrive in one go (Ship Complete = Yes).
An order can move from the first pile to the second if it's meant to travel with another order. The "send all together" pile is always handled second.
When an order is "ready" (all must be true)
- Paid in full (nothing owing)
- Not on hold
- Not already booked with a courier
- Not already approved or closed
- No drop-ship items
- Items are Standard or 2-Man goods
- No ship date already set in the past
- At least one item still to send
A "send all together" order also waits until every item across the linked orders is in stock.
Picking the courier
The paid service level (Standard / 2-Man / Full Service) sets the list of couriers to try, in preferred order. For each, the system checks whether it covers the delivery postcode; the first that covers it wins.
- If none cover it → use the courier the customer picked at checkout, unless that was "To Be Confirmed", in which case hold the order for review.
- If a Standard order contains an item needing 2-Man handling → upgrade the whole order to 2-Man.
Picking the day
Earliest sensible day, respecting the courier's notice period, the customer's requested date, and (for Life trucks) whether the truck for that region still has room that day.
- NSW / ACT (local): ship and deliver on the same day.
- QLD / VIC (interstate): goods travel by linehaul, so the ship date is set to the weekly linehaul day — Tuesday for Brisbane, Thursday for Melbourne.
- Other states on a Life truck: not supported — left for review.
Outcomes. Approved · Approved (upgraded to 2-Man) · Approved (customer's online pick) · Held for review · Waiting (missing stock — nothing is booked, and the order is retried next run).
Which pile an order goes in LI-BL-FUL-001
Every ready order is either sent as it becomes available, or held until the whole order can travel in one go.
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | Parts of the order can go out as stock lands | Flow 1 — send as it is ready | Stock in hand should not wait on stock that has not landed |
| 2 | The customer's whole order must arrive together | Flow 2 — send all together, handled second | Part-delivering a modular sofa creates a redelivery |
| 3 | A Flow 1 order is meant to travel with another order | Moved to Flow 2, picked up on the next run | Two linked orders must land on the same truck on the same day |
| 4 | Any item across the linked orders is not yet in stock | The whole group waits, nothing is booked | Booking a group that cannot ship complete wastes the slot |
Picking the courier LI-BL-FRT-001
The service level the customer paid for sets a list of couriers to try in preferred order. Each is checked against the delivery postcode; the first that covers it wins.
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | A courier in the list covers the postcode | That courier is approved | Preferred order runs cheapest-capable first |
| 2 | No courier covers the postcode, and the customer picked a real courier at checkout | The customer's pick is approved | An address we cannot service is still a promise made |
| 3 | No courier covers the postcode and the checkout pick was "to be confirmed" | Held for review | Nobody has chosen a carrier yet — a person must |
| 4 | A Standard order contains an item needing 2-Man handling | The whole order is upgraded to 2-Man | One heavy item makes the whole delivery a two-person job |
Picking the day LI-BL-FUL-003
The earliest sensible day that respects the courier's notice period, the customer's requested date, and — for Life trucks — whether the truck for that region still has room.
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | Delivery is in NSW or ACT on a Life truck | Ship and deliver on the same day | Local runs leave and arrive the same day |
| 2 | Delivery is in QLD on a Life truck | Ship on the weekly Brisbane linehaul day — Tuesday | Interstate goods travel by linehaul, which departs weekly |
| 3 | Delivery is in VIC on a Life truck | Ship on the weekly Melbourne linehaul day — Thursday | Same reason, different departure day |
| 4 | The customer asked for a later date than the earliest available | The customer's date becomes the floor | Arriving early is a failed delivery too |
| 5 | The order does not fit the truck's remaining capacity that day | The next day that fits is used | Cubic capacity is the hard limit on a truck run |
| 6 | Delivery is on a Life truck to any other state | Not supported — left for review | No Life truck route serves those states |
Planned change — not yet live. A revised date model works out the ship date first and derives the delivery date forward from it, dropping the fixed Tuesday/Thursday linehaul days. Until it ships, the rules above are what runs. The detail is in the developer reading.
Orders that travel together LI-BL-FUL-002
When one purchase is split across several orders, they are grouped and treated as one delivery.
| # | If… | Then… | Because |
|---|---|---|---|
| 1 | An order points at another order it must ship with | Both, and anything they chain to, form one group | A three-piece sofa across two orders is one delivery |
| 2 | The group is approved | The same courier and dates are written to every order in it | The orders must not drift apart afterwards |
| 3 | Orders in the group have different requested dates | The latest requested date across the group applies | Nothing in the group may arrive before the customer wants it |
| 4 | A linked order is not in the day's list | It is fetched anyway so the group is complete | A partial group would be booked on partial capacity |
Related rules
None recorded. The related field for this entry is empty — links are kept in both directions, so
the first cross-reference is added to both entries at once.
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 |
|---|---|---|---|
| Ship Together With This Sales OrderSales Order | Another sales order this one must be delivered with. | Links an order to another it must travel with. Present only in the swo search, which is what promotes an order from Flow 1 to Flow 2 and builds the group. | confirmed |
| Courier Service LevelSales Order | The delivery service the customer paid for — Standard, 2-Man or Full Service. | The level the customer paid for. Selects which list of couriers is tried, and in what order. | confirmed |
| Shipping PostcodeSales Order | The postcode the order is being delivered to. | Checked against each courier's coverage list. The first courier that covers it wins. | confirmed |
| Shipping StateSales Order | The state the order is being delivered to. | Decides local versus interstate. NSW and ACT ship and deliver the same day; QLD and VIC go by linehaul. | confirmed |
| Customer Requested Delivery Date OnwardsSales Order | The date the customer asked to receive the order on or after. | Sets a floor on the delivery date. Blank means the courier's notice period governs instead. For a merged group the latest date across the group applies. | confirmed |
| Default Shipping Method from Item recordInventory Item | How the item is normally sent — standard goods, or goods needing two people to handle. | The item mix decides routing, and a single 2-Man item upgrades the whole order. | confirmed |
| CBM from Item recordInventory Item | The volume of one unit of the item. | Multiplied by committed quantity to give the order's volume, which is checked against the truck's remaining capacity. | confirmed |
| Qty committed at SO line itemSales Order line | How many units on the order line are in stock and reserved for it. | A swo group only proceeds when every item across every linked order is committed. One short piece holds the whole group. | confirmed |
| Life Truck RegionSales Order | Which Life truck run covers the delivery address. | Picks which Life truck run the order belongs to, and therefore which day's capacity is checked. | confirmed |
| Ship ViaSales Order | The courier booked to carry the order. | Read as the customer's checkout pick when no courier covers the postcode, and written with the chosen courier on approval. | 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 |
|---|---|---|---|
| Ship ViaSales Order | The courier booked to carry the order. | Read as the customer's checkout pick when no courier covers the postcode, and written with the chosen courier on approval. | confirmed |
Two AWS Lambda functions of near-identical architecture, split by order type. Flow 1 (nswo) runs
first, then Flow 2 (swo). An order can be promoted from Flow 1 to Flow 2. Every section below
collapses on the site.
Entry gate & eligibility — LI-BL-FUL-001
Saved-search entry criteria ("Sales Orders NSSS")
An order only enters when all hold:
- Sales order balance owing = 0
- At least 1 item not shipped
- "Exported to Courier" = false
- "Hold Order" = false
- "Courier Approval" = false
- Orders / items not closed
- No item has a Drop Ship PO
- Item default shipping method is 2 Man Sensitive or Standard
- Ship Date not on/before today OR Ship Date empty & "Reason for service change" empty
Input data, flow routing & ship-together grouping — LI-BL-FUL-002
What it reads, and where from.
Each flow is fed by a NetSuite saved search returning one row per order line (rows sharing
SO_internalID belong to the same order).
| Saved search | ID | Feeds | Distinctive column |
|---|---|---|---|
| Courier Approval · Ship Complete No | 7132 | nswo | — |
| Courier Approval · Ship Complete Yes | 7136 | swo | adds SO_shipTogetherWithID |
SO_shipTogetherWithID (7136 only) is the internal ID of another SO this order must ship with — the
field mergeSalesOrders follows to build ship-together groups. Search 7132 omits it, which is why
nswo has no merge step.
Key columns: SO_postcode/SO_state (postcode + linehaul), SO_shipVia/SO_shipViaId (current
courier), SO_lifeRegion/SO_lifeTruckNumber (scheduling/CBM), SO_cxRequestedDate,
SO_courierServiceLevel (paid level), item_defaultShippingMethod (item mix), item_packageCBM
(× committed qty = CBM), item_qtyCommitted (drives the "all committed?" gate).
Ship-via reference (from live data): 3420 = TBC · 201100 = Life 2 Man · 247628 = Life Standard
· 253939 = Life Full Service · 112729 = TLC · 112728 = Pedemont · 17186 = AusPost ·
39973 = Allied · 2864 = Designer Transport.
Customer requested delivery date
Field: SO_cxRequestedDate — the date the customer asked to receive the order by. Present in
both saved searches, format DD/MM/YYYY, and may be blank. For a merged swo group the
automation uses maxCxRequestedDate — the latest requested date across all linked orders — so
the whole group lands on or after the latest customer ask.
How it's used: it sets a floor on the scheduled date. The Date Check base date is the SO committed date / today, or the CX requested date when that is later than the committed / PO dispatch date. The chosen ship/delivery date is then the first valid courier day on or after that floor. Blank = no customer preference → the courier buffer governs instead.
| Example | CX requested date | What happens |
|---|---|---|
S315316 (Life 2 Man, Syd R5) |
30/10/2026 | The 3-day buffer would allow early August, but the CX date is later, so the date becomes the first Syd R5 truck day on/after 30 Oct 2026. |
S308550 (Life Full, Syd R4) |
(blank) | No customer floor → the 3-day buffer governs → first Syd R4 truck day (Fri 7 Aug 2026 in the sample data). |
S310492 + S310562 (group) |
maxCxRequestedDate = 5/7/2026 |
Latest ask across the group is in the past, so the buffer governs the whole group. |
Ship together with this order
Field: SO_shipTogetherWithID — the internal ID of another sales order this order must be
delivered with. Present only in the 7136 (swo) search; blank means the order ships on its own.
It drives two mechanisms:
- Flow 1 (nswo): an order flagged to ship together with another SO has its Ship Complete set to
Yes, so it drops out of Flow 1 and is picked up by Flow 2 (swo) on the next run. (nswo's
search has no
SO_shipTogetherWithIDcolumn — this is the "Ships together with another SO?" gate.) - Flow 2 (swo):
mergeSalesOrdersfollowsSO_shipTogetherWithIDto build a group, which:- ships together —
updateGroupedSalesOrderswrites the same courier + dates to every linked SO; - only proceeds when every item across all linked SOs is committed (
allSOsCommitted) — otherwise the whole group waits; - uses group totals — total CBM across all items, and
maxCxRequestedDate; - pulls in a linked SO that isn't in the search results via SuiteQL (
ns.getSalesOrder).
- ships together —
Example (reciprocal group): a Float modular sofa split across two orders that reference each other:
| SO | internalID | shipTogetherWith | Item | Committed |
|---|---|---|---|---|
S301287 |
13815215 | 14132070 | Float 1.5 Seat Armless Piece | 1 / 1 |
S301287 |
13815215 | 14132070 | Float 4 Piece Modular Sofa | 1 / 1 |
S306157 |
14132070 | 13815215 | Float 3 Seat Armless Piece | 0 / 1 |
They merge into one group (VIC 3754, Full Service). Because the third piece is 0/1, allSOsCommitted
is false → the group waits, nothing is booked, and it retries next run once the piece is committed.
Data quirks:
- Self-reference: some rows set
SO_shipTogetherWithIDto their own internal ID (e.g.S311157,S312794) — a group of one; the merge logic tolerates it. - Chains: links can chain (A↔B, B↔C) — the group is the whole connected set of orders.
Processing — ship date, CBM capacity & linehaul (LI-BL-FUL-003)
What it does with that, step by step.
Base date = SO committed date / today, or the CX requested date when it is later than the SO
committed date or the PO dispatch date (SO_cxRequestedDate in nswo; maxCxRequestedDate across the
group in swo).
- Buffer: non-Life count business days; Life trucks may count weekend days when marked available in the schedule sheet.
- CBM: for Life trucks, capacity is checked against the schedule sheet per region (limited to the first few candidate dates); capacity is reserved on selection. Ordering resolved by which SO was placed first.
- Schedule/CBM loop: schedule check → if no available date, ignore order + error email; else CBM check → fits ⇒ approve, else find next available date and repeat.
- Life deliveries to states other than NSW/VIC/QLD are rejected (no partial update).
The above is the current behaviour. A revised date model is planned — see below.
Planned change — ship date, SDD & linehaul
Status: planned — not yet implemented in
shipping-v6-nswo/shipping-v6-swo. Documented here ahead of the code change; until it ships, the current logic above applies.
The revised model computes the ship date first and derives the delivery date forward, and it removes the fixed weekly linehaul weekday (Tue/Thu) anchoring.
| Courier | Ship date | Scheduled Delivery Date (SDD) | Line Haul Date |
|---|---|---|---|
| non-LIFE | today + 1 + buffer |
none | — |
| LIFE · NSW/ACT | today + 1 + buffer |
= Ship date (same day) | — |
| LIFE · VIC/QLD | today + 1 + buffer |
= Ship date + 1 + buffer | = Ship date |
What changes vs the current (OLD) logic:
| Current (OLD) | Planned (NEW) | |
|---|---|---|
| Primary date computed | SDD first (today + 1 + buffer) |
Ship date first (today + 1 + buffer) |
| LIFE · NSW/ACT | Ship date = SDD | SDD = Ship date (same result) |
| LIFE · VIC/QLD ship date | pulled back to the weekly linehaul weekday — QLD → Tuesday (SYD→BRIS), VIC → Thursday (SYD→MEL) | no weekday anchoring — ship date = today + 1 + buffer = Line Haul Date |
| LIFE · VIC/QLD delivery | = the computed base date (SDD) | pushed forward: SDD = Ship date + 1 + buffer |
When this ships, the Which day it ships diagram and the linehaul rows in Test cases (group F) should be updated to match.
Courier routing by service level — LI-BL-FRT-001
First courier whose postcode check passes wins (each courier has a whitelist CSV in S3).
| Service level | Flow(s) | Hierarchy (first valid postcode wins) |
|---|---|---|
| Full Service | swo | Life Full → DT Full. TLC auto-approved (no postcode check). |
| Two Man | nswo, swo | Life 2 Man → DT 2 Man → Pedemont → TT (TT has no postcode file → always fails). |
| Standard — Life items | nswo, swo | Life Standard → DT ATL. |
| Standard — AusPost | nswo | All-AusPost → AusPost. |
| Standard — other | nswo | All / at-least-one Standard → Allied. |
| Standard w/ a 2-Man item | nswo, swo | Upgrade to the Two Man hierarchy; reason code 21. |
When no courier in the hierarchy covers the postcode, the customer's checkout pick is approved
instead, under reason code 14 — see LI-BL-FRT-001 rows 2 and 3 for the rule, and SA-D9
for the mechanism swo now uses.
Approval reason codes
| Board | Meaning | Config key | Live value |
|---|---|---|---|
| 15 | Approved — Reviewed by the automation | N_RFSC_APPR |
15 |
| 21 | Approved — Automation upgraded due to premium courier item | N_RFSC_APPR_PREMITEM |
21 |
| 14 | Approved — Reselected via Front End Rate Card | N_RFSC_APPR_RESLECTED |
14 |
| 10 | Disapproved — Postcode not covered | N_RFSC_DIS_PCODENOTCOV |
5 |
⚠️ Board vs code mismatch: the disapprove note is labelled (10) on the Miro board, but every deployed config sets
N_RFSC_DIS_PCODENOTCOV = 5. Treat 5 as the live value; confirm in NetSuite.
Courier schedule sheet (fulfilment-owned)
A Google Sheet (single tab INTEGRATION SCHEUDULE) maintained by the fulfilment team. The
Courier Schedule microservice reads it; the automation only reads it.
Layout: columns A–H are per-courier/truck settings; every following column is one calendar day
with a TRUE/FALSE availability flag.
| Column | Meaning |
|---|---|
| COURIER | Australia Post, Pedemonts, Allied, Designer Transport, Amaze, TLC, Life |
| REGIONS | Life only — e.g. Syd R1 - East, Melb R4 - North East, Bris R3 - East |
| Netsuite Internal ID | Ship-via IDs this row covers (Life rows: 201100 = 2 Man, 253939 = Full Service, 247628… = Standard) |
| Detrack Truck# | Physical truck (Syd 01/02/03, Melb 01/02, Bris 01); a region can have several |
| TRUCK CBM CAPACITY | N/A for non-Life; 5–16 m³ for Life trucks |
| MAX DELIVERY STOPS | Life only — 3–18 |
| BUFFER DATE | Lead days: non-Life = 1; Life = 2–3 (Sydney) or 7 (Melbourne/Brisbane) |
| Note | ship date (non-Life) or Scheduled Delivery Date (Life) |
Two dedicated linehaul rows mark departure days (toggleable per week):
- LINEHAUL - VIC — every Thursday (SYD → MEL)
- LINEHAUL - QLD — every Tuesday (SYD → BRIS)
Rules baked into the sheet:
- Life availability sets the scheduled delivery date; other couriers set the ship date.
- Buffers exclude weekends unless the weekend day is selected (e.g. order Thursday, buffer 3 → Fri + Mon + Tue; if Saturday available → Fri + Sat + Mon).
- Tiebreak: prefer the lower-CBM truck — "all loaded first".
- One Life row's three NetSuite IDs serve Standard, 2 Man, and Full Service ship-vias at once.
Decision tree — one order, from the saved search to a booked date
The same logic as a path through the decisions, in the order they run.
Four decisions, taken in this order: does it enter · which pile · which courier · which day. Each stage assumes the one above it has already run — the courier decides which buffer and which schedule row the date stage reads, so reading the date rules without the courier gives the wrong answer.
Every branch names the rule it implements. The codes down the right margin are this entry's test
cases — prefix them with SA-TC- to find one (F4 is SA-TC-F4). Where an open defect changes
what actually happens, the branch says so and names it: the tree describes deployed behaviour,
not the board, which is the whole reason it is worth reading next to the tables above.
Stage 1 — does the order enter at all? LI-BL-FUL-001
Nothing here is code. The saved search is the gate, and an order failing any one of these is never seen by the automation — it is not held, not logged, and not counted anywhere.
sales order
├─ balance owing > 0 ? ──► never selected E1
├─ Hold Order = true ? ──► never selected E2
├─ Courier Approval already true,
│ or order / items closed ? ──► never selected E3
├─ every item has a Drop Ship PO ? ──► never selected E4
├─ no item still to ship ? ──► never selected
├─ an item is neither Standard
│ nor 2 Man Sensitive ? ──► never selected
├─ Ship Date on/before today AND a
│ reason for service change present ? ──► never selected E5
└─ all of the above pass ──► enters, at stage 2
A silent run and a quiet day look identical from here: if the search stops returning rows, nothing is processed and nothing errors. That is a recorded failure mode, not a defect.
Stage 2 — which pile, and does the group wait? LI-BL-FUL-001, LI-BL-FUL-002
The one people get wrong: a Flow 1 order that must travel with another order is not merged now. It is flagged Ship Complete = Yes and picked up by Flow 2 on the next run, so it is a run behind. Search 7132 has no ship-together column at all, which is why Flow 1 has no merge step.
order
├─ Ship Complete = No ──► Flow 1 · search 7132 (nswo)
│ ├─ ships together with another SO ?
│ │ └─ yes ──► set Ship Complete = Yes, stop.
│ │ Flow 2 collects it NEXT RUN F2
│ └─ no ──► process alone, go to stage 3 F1
│
└─ Ship Complete = Yes ──► Flow 2 · search 7136 (swo), always second
├─ follow SO_shipTogetherWithID to build the group
│ · a link that chains (A↔B, B↔C) pulls in the whole set
│ · a self-reference is a group of one, and is tolerated
│ · a linked SO missing from the search is fetched by SuiteQL F5
├─ EVERY item across EVERY order in the group committed ?
│ ├─ no ──► the whole group WAITS. Nothing is written,
│ │ nothing is booked, retried next run F4
│ └─ yes ──► one group from here on:
│ total CBM across all items,
│ maxCxRequestedDate across all orders F3
└─ go to stage 3
Stage 3 — which courier? LI-BL-FRT-001
The paid service level picks a hierarchy; the hierarchy is walked in order and the first courier
whose postcode whitelist covers the address wins. A missing whitelist is indistinguishable from a
genuine no-cover, which is what makes SA-D2 invisible in production.
paid service level
├─ Standard, but ≥1 item needs 2 Man handling ?
│ └─ UPGRADE the whole order to the Two Man hierarchy, reason 21 C9
│ (a margin loss by design — the customer paid Standard · SA-D4)
│
├─ Full Service ──► Life Full → DT Full
│ ⚠ SA-D8: the rules table and the courier diagram put
│ TLC here, auto-approved with no postcode check. In
│ swo TLC is tried in the TWO MAN branch instead, and
│ is auto-approved only when it is the front-end
│ pre-selection — otherwise the order falls through
├─ Two Man ──► Life 2 Man → DT 2 Man → Pedemont → TT C3 C4
│ ⚠ SA-D2: TT has no postcode file, so it can never win P3
│ ⚠ SA-D7: a PR titled "remove fallback to dt" did not
│ remove DT — DT 2 Man and DT Standard are both still
│ in nswo's hierarchy at that commit. Which fallback
│ it meant to drop is unestablished; the tree shows
│ what is deployed
├─ Standard, ≥1 Life item ──► Life Standard → DT ATL C5 C6
├─ Standard, all AusPost items ──► AusPost C7
└─ Standard, other ──► Allied C8
│
▼
walk the hierarchy, first postcode match wins
├─ one covers the postcode ? ──► APPROVED, reason 15 C1 C2
└─ none covers it ──► what did the customer pick at checkout?
├─ a real courier ──► approve the customer's pick, reason 14 P1
│ ⚠ SA-D9: swo no longer looks up the rate card;
│ it calls autoApproveFECourier instead
└─ "To Be Confirmed" ──► HELD FOR REVIEW, reason 5 P2
⚠ SA-D1: the board says 10, every deployed
config says 5. 5 is what runs
A 2 Man order whose committed items are all Standard_Auspost goes to manual review, not through this tree at all. That is deliberate scope, recorded as
SA-D3— caseX1.
Stage 4 — which day? LI-BL-FUL-003
Two independent things set the date and they are easy to confuse: the buffer says how soon, the customer's requested date says no sooner than. Whichever is later wins. Only then is capacity checked, and only for Life trucks.
approved courier
├─ not a Life truck ──► the sheet's buffer sets the SHIP date
│ (non-Life buffer = 1 day)
└─ a Life truck ──► the sheet's buffer sets the SCHEDULED DELIVERY date
(Sydney 2–3 days · Melbourne / Brisbane 7)
│
├─ 1. base date = SO committed date / today, OR the customer's
│ requested date when that is later than the committed date
│ or the PO dispatch date. The buffer runs from there, and
│ the answer is the first valid courier day on/after it S1 S4
│ · blank requested date ──► the buffer alone governs
│ · a merged group uses maxCxRequestedDate across the group
│ · buffer days skip weekends UNLESS the sheet marks that
│ weekend day available S5
│
├─ 2. state decides the shape of the answer
│ ├─ NSW or ACT ──► ship and deliver the SAME day L1
│ ├─ QLD ──► deliver on a Brisbane day; ship on the
│ │ weekly SYD→BRIS linehaul — TUESDAY L2
│ ├─ VIC ──► ship on the weekly SYD→MEL linehaul —
│ │ THURSDAY L3
│ └─ anything else on a Life truck
│ ──► REJECTED, left for review, no partial
│ update written L4
│
├─ 3. does the order's CBM fit that truck's remaining capacity?
│ ├─ no ──► take the next available date and repeat from 3 S2
│ └─ yes ──► reserve the capacity on selection
│ two trucks both fit ? take the LOWER-CBM truck S6
│ two orders compete ? the one placed FIRST wins
│
└─ 4. no date at all fits ──► order ignored, error email to fulfilment S3
A revised date model is planned and is not live. It would work the ship date out first and derive the delivery date forward from it, dropping the fixed Tuesday / Thursday linehaul days. Until it ships, stage 4 above is what runs. See Planned change in this reading.
What is written, and what is not — case X2 checks the code on every outcome
| Outcome | Reason code | Written back |
|---|---|---|
| Approved | 15 | Courier Approval, Ship Via, Ship Date, Scheduled Delivery Date, linehaul fields for QLD / VIC |
| Approved — upgraded to 2 Man | 21 | As above |
| Approved — customer's checkout pick | 14 | As above |
| Held for review — postcode not covered | 5 | The reason code only |
| Waiting — group not all committed | — | Nothing at all. Retried next run |
| No date available | — | Nothing at all. An error email to fulfilment |
Two mechanics sit outside the tree and change nothing it decides. A run nearing the ~14 minute Lambda limit re-invokes itself, up to three times, and the orders it had not reached are processed by the next invocation (
X3). If the Courier Schedule service is unavailable the automation falls back to a cached schedule, and terminates without writing if there is none (X4).
Outputs & side effects
What it writes, and who reads it afterwards.
What a run leaves behind on an approved order. The ids for most of these are not yet in the field
registry — only SO_shipVia is — so they are listed here by the name they carry on the NetSuite
Sales Order form. Confirming the ids is outstanding.
| Output | Written to | Downstream consumer |
|---|---|---|
| Courier Approval | Sales Order | Removes the order from the courier-approval saved search, so it is not reprocessed |
| Ship Via — the chosen courier | Sales Order (SO_shipVia) |
Fulfilment, DeTrack, the customer's despatch notification |
| RFSC reason code — 15 / 21 / 14 / 5 | Sales Order | Explains why the automation decided as it did; the audit trail for a disputed booking |
| Ship Date | Sales Order | The warehouse pick date, and the linehaul cut-off for QLD / VIC |
| Scheduled Delivery Date | Sales Order | What the customer is told, and what the Life truck run is planned against |
| Linehaul fields (QLD / VIC only) | Sales Order | The weekly SYD→BRIS / SYD→MEL departure the order rides on |
| Error email | Fulfilment mailbox | Raised when no available date can be found for an order |
| Nothing at all | — | A held or waiting order is left untouched, and is picked up again next run |
Worked examples
Real records carried end to end, so the logic can be checked against something that happened.
Real orders traced end-to-end (dates computed from a 31 Jul 2026 run against the schedule sheet).
A · Local 2-Man (nswo) — S315316
NSW 2112, Syd R5 - North West, paid 2 Man, 2 items (Standard-Life), CBM 4.8 m³, CX date 30/10/2026. → Life 2 Man; CBM 4.8 vs 6 m³ truck (tight fit); CX date 30/10 overrides the 3-day buffer; NSW = local so ship = delivery on the first Syd R5 truck day on/after 30 Oct 2026. Approved (15).
B · Local Full Service (swo) — S308550
NSW 2040, Syd R4 - West, paid Full Service, 1 item, CBM 1.39 m³, no CX date. → Life Full; CBM fits 16 m³; 3-day buffer → ship & deliver Fri 7 Aug 2026. Approved (15).
C · Interstate QLD linehaul (swo, merged) — S310492 + S310562
QLD 4173, Bris R3 - East, Full Service, both orders committed, group CBM 0.74 m³. → Life Full; fits 13 m³ truck; latest CX date (5/7/2026) is past so 7-day buffer applies; first Bris R3 truck day = deliver Tue 18 Aug 2026; QLD linehaul → ship Tue 11 Aug 2026 (SYD → BRIS), applied to both linked orders. Approved (15).
D · Merged group — waiting (swo) — S301287 + S306157
VIC 3754, Full Service, a Float modular sofa split across two linked orders; one piece 0/1 in stock.
→ allSOsCommitted = false → whole group skipped this run, retried next run when the last piece
is committed. Waiting — nothing booked.
Test cases
What to run to know the behaviour is intact. Expected values, never “should work”.
| Case ID | Layer | Preconditions | Input | Expected output | Rule ref |
|---|---|---|---|---|---|
SA-TC-E1 |
Entry & eligibility (saved-search gating) | SO balance owing > 0 | — | Not selected by the saved search — never processed | needs confirming |
SA-TC-E2 |
Entry & eligibility (saved-search gating) | "Hold Order" = true | — | Excluded — not processed | needs confirming |
SA-TC-E3 |
Entry & eligibility (saved-search gating) | "Courier Approval" already true, or order/items closed | — | Excluded — not reprocessed | needs confirming |
SA-TC-E4 |
Entry & eligibility (saved-search gating) | All items have a Drop Ship PO | — | Excluded (drop-ship items dropped; empty order skipped) | needs confirming |
SA-TC-E5 |
Entry & eligibility (saved-search gating) | Ship Date on/before today with a reason for service change present | — | Excluded — not re-dated | needs confirming |
SA-TC-F1 |
Flow routing (ship complete & grouping) | Ship Complete = No, not linked to another SO | — | Flow 1 (nswo, 7132) → proceeds to courier selection | needs confirming |
SA-TC-F2 |
Flow routing (ship complete & grouping) | Ship Complete = No, ships together with another SO | — | Flagged Ship Complete = Yes → picked up by Flow 2 next run | needs confirming |
SA-TC-F3 |
Flow routing (ship complete & grouping) | Ship Complete = Yes, every item across the group committed | — | Flow 2 (swo, 7136) → proceeds; group updated together | needs confirming |
SA-TC-F4 |
Flow routing (ship complete & grouping) | Ship Complete = Yes, one item 0/1 committed (e.g. S301287 + S306157) | — | Waiting — whole group skipped this run, no NetSuite update; retries next run | needs confirming |
SA-TC-F5 |
Flow routing (ship complete & grouping) | Merged group where a related SO isn't in the search results | — | Related SO fetched (SuiteQL); group assembled; maxCxRequestedDate & total CBM across all items |
needs confirming |
SA-TC-C1 |
Courier selection by service level | Full Service, Life postcode valid (e.g. S308550)Reason code 15 | — | Courier = Life Full; approved | needs confirming |
SA-TC-C2 |
Courier selection by service level | Full Service, Life invalid, DT validReason code 15 | — | Courier = DT Full; approved | needs confirming |
SA-TC-C3 |
Courier selection by service level | 2 Man, Life items, Life postcode valid (e.g. S315316)Reason code 15 | — | Courier = Life 2 Man; approved | needs confirming |
SA-TC-C4 |
Courier selection by service level | 2 Man, Life invalidReason code 15 | — | Falls through in order: DT 2 Man → Pedemont → TT | needs confirming |
SA-TC-C5 |
Courier selection by service level | Standard, ≥1 Life item, Life postcode validReason code 15 | — | Ship Via → Life Standard; approved | needs confirming |
SA-TC-C6 |
Courier selection by service level | Standard, Life invalid, DT ATL validReason code 15 | — | Ship Via → DT ATL; approved | needs confirming |
SA-TC-C7 |
Courier selection by service level | Standard, all items AusPostReason code 15 | — | Ship Via → AusPost; approved | needs confirming |
SA-TC-C8 |
Courier selection by service level | Standard, all / ≥1 Standard (non-AusPost, non-Life)Reason code 15 | — | Ship Via → Allied; approved | needs confirming |
SA-TC-C9 |
Courier selection by service level | Paid Standard, but ≥1 item needs 2 ManReason code 21 | — | Upgraded to Two Man hierarchy; approved as premium-item upgrade | needs confirming |
SA-TC-P1 |
Postcode validation & front-end fallback | No courier covers the postcode; front-end pick is a real courierReason code 14 | — | Approve the front-end rate-card courier | needs confirming |
SA-TC-P2 |
Postcode validation & front-end fallback | No courier covers the postcode; front-end pick = TBCReason code 5 | — | Disapprove — postcode not covered | needs confirming |
SA-TC-P3 |
Postcode validation & front-end fallback | TT courier (no postcode CSV) | — | Its postcode check always fails → next step | needs confirming |
SA-TC-S1 |
Scheduling, CBM & buffer | Life truck, CBM fits, no CX date (e.g. S308550, Syd R4) | — | First available Syd R4 truck day on/after the 3-day buffer (Fri 7 Aug 2026 in sample data) | needs confirming |
SA-TC-S2 |
Scheduling, CBM & buffer | Life truck, order CBM exceeds remaining capacity that day | — | Skips that day → next available date that fits | needs confirming |
SA-TC-S3 |
Scheduling, CBM & buffer | No available date fits | — | Order ignored + error email raised | needs confirming |
SA-TC-S4 |
Scheduling, CBM & buffer | CX requested date later than the buffer (e.g. S315316, CX 30/10) | — | First truck day on/after the CX requested date | needs confirming |
SA-TC-S5 |
Scheduling, CBM & buffer | Life truck with a weekend day marked available | — | Buffer counts that weekend day; otherwise weekends excluded | needs confirming |
SA-TC-S6 |
Scheduling, CBM & buffer | Two trucks/dates both qualify | — | Lower-CBM truck chosen first | needs confirming |
SA-TC-L1 |
Linehaul (Life couriers) | Life courier, ship state NSW or ACT | — | Ship Date = Scheduled Delivery Date (local, same day) | needs confirming |
SA-TC-L2 |
Linehaul (Life couriers) | Life courier, QLD (e.g. S310492 + S310562, Bris R3) | — | Deliver Tue 18 Aug; Ship Date = Tue 11 Aug; Route SYD → BRIS; applied to both linked SOs | needs confirming |
SA-TC-L3 |
Linehaul (Life couriers) | Life courier, VIC | — | Ship Date = Thursday before delivery; Route SYD → MEL | needs confirming |
SA-TC-L4 |
Linehaul (Life couriers) | Life courier, state not NSW/VIC/QLD (WA/SA/TAS) | — | Rejected — no partial update | needs confirming |
SA-TC-X1 |
Edge cases & resilience | 2 Man order where every committed item is Standard_Auspost | — | Sent to manual review (not auto-processed) | needs confirming |
SA-TC-X2 |
Edge cases & resilience | Any approval/disapproval | — | Correct RFSC reason code written (15 / 21 / 14 / 5) — confirm against config | needs confirming |
SA-TC-X3 |
Edge cases & resilience | Run approaches the ~14-min Lambda limit | — | Self-reinvokes async (retryAttempt+1, max 3); remaining orders processed next invocation; no duplicate updates | needs confirming |
SA-TC-X4 |
Edge cases & resilience | Courier Schedule service unavailable | — | Falls back to cached schedule; if none, terminates gracefully with an error email (no bad dates written) | needs confirming |
Scenario, input and expected outcome for each rule. Reason codes: 15 approved · 21 upgraded · 14 rate-card · 5 disapproved (postcode).
UAT
What a person checks, by hand, before it is trusted.
How the business signs this off. The Result column on every table below doubles as the sign-off sheet — tick it against a real run, and record who ticked it.
How to run UAT (staging)
- Environment: run on staging —
AWS_LAMBDA_FUNCTION_VERSION=$LATESTselects the staging config (sandbox NetSuite, staging courier API). - Dry-run first: swo exposes
N_QUEUER_DRY_RUN,NS_SAVED_SEARCH_DRY_RUN,COURIER_SCHEDULE_API_DRY_RUN; nswo hasAWS_RDS_IN_DRY_RUN+ a courier dry-run. Trace decisions without writing to NetSuite. - Seed: create a sandbox sales order that meets the entry criteria (paid, not on hold, not already approved, correct item shipping methods).
- Trigger: invoke the Lambda (manual kickoff /
test.index.mjs). - Verify in NetSuite: Courier Approval set, Ship Via updated, RFSC reason code, Ship Date + Scheduled Delivery Date (and linehaul fields for QLD/VIC). Check the logs; confirm no error email unless the case expects one.
Failure modes
What breaks it, how that shows, and how to recover.
| Failure | Symptom | Detection | Recovery |
|---|---|---|---|
| No available delivery date for an order | The order is skipped and an error email is raised | The error email, and the order staying in the saved search | Free capacity in the courier schedule sheet, or handle the order manually |
| A courier's postcode file is missing from S3 | That courier silently always fails its postcode check, and the order falls through to the next in the hierarchy — see SA-D2 for TT |
Not detected automatically: the outcome looks like a legitimate no-cover | Restore the CSV; re-run the order |
| The courier schedule sheet is wrong or unmaintained | Dates are booked against capacity that does not exist, or capacity that does exist is not used | Not detected automatically | Fulfilment owns the sheet and corrects it |
| A merged group never completes | The group waits every run and is never booked | The order ageing in the review queue | Complete the stock, or split the group manually |
| The saved search stops returning rows | Nothing is processed and nothing errors | Not detected automatically — a silent run looks like a quiet day | Re-check the search; confirm against LI-BL-FUL-001 eligibility |
These are read from the documented behaviour, not from an incident log. Detection is honestly recorded as "not detected automatically" wherever nothing watches for it.
Known limitations
Scope that was deliberately chosen. A limitation is a decision.
Deliberate scope, not mistakes. Keeping them here stops them being re-raised as defects.
| Limitation | Why it is this way | What to do instead |
|---|---|---|
| Life trucks serve NSW, ACT, QLD and VIC only | No Life truck route serves the other states | Those orders are left for review and booked manually |
| All-AusPost 2-Man orders go to manual review | By design — see SA-D3 |
Handle in the review queue |
| A Standard order containing one 2-Man item is upgraded whole | One heavy item makes the whole delivery a two-person job. The margin loss is real and is tracked as SA-D4 |
Nothing automatic; changing it is a commercial decision |
| The automation never picks a courier that covers nothing | An address we cannot service is still a promise made, so the customer's checkout pick stands | If the checkout pick was "to be confirmed", a person chooses |
| Run frequency is not recorded in this entry | Nobody has written it down | Needs confirming |
Known defects
Where it does something other than what was decided. A defect is a mistake.
| Ref | Status | Defect | Rule |
|---|---|---|---|
SA-D1 |
OpenOpen — needs confirming | Disapprove reason code disagrees. The board shows (10); config sets N_RFSC_DIS_PCODENOTCOV = 5. Confirm against NetSuite |
needs confirming |
SA-D2 |
OpenOpen | TT (Two Man) has no postcode CSV, so it always fails its postcode check in practice | needs confirming |
SA-D3 |
OpenBy design, worth watching | All-AusPost 2-Man orders are routed to manual review rather than auto-processed | needs confirming |
SA-D4 |
OpenOpen — commercial | Standard → 2-Man upgrade is a margin loss — the customer paid Standard. The front-end rate card is the noted pain point | needs confirming |
SA-D5 |
OpenCosmetic, but it misleads | The board is titled "Module 2" while the repos' package.json call it "Module 5". Same stage, different naming |
needs confirming |
SA-D6 |
OpenOpen | nswo config keys resolve to undefined. queryHelper/rds reference config.AWS_RDS_DBNAME, and several libs read config.AWS_REGION, but neither is defined in nswo config. swo defines AWS_REGION and sources the DB name from Secrets Manager |
needs confirming |
SA-D7 |
OpenOpen — intent versus deployed | PR #12 "remove fallback to dt" (nswo, 2026-07-21, 0ed5ee9) signals a DT fallback was removed, but at that commit DT 2 Man (DT_PRM) and DT Standard (DT_STD) are still present in nswo's courier hierarchy. Which DT fallback was meant to go? Until confirmed, this entry reflects the deployed behaviour |
needs confirming |
SA-D8 |
OpenOpen — this entry is wrong here, correct on the next pass | TLC is a Two Man courier in swo, not Full Service. The rules table and the courier diagram place TLC under Full Service as "auto-approved (no postcode check)". In swo at 69029d7, TLC is tried in the Two Man branch and is only auto-approved when it is the pre-selected front-end courier; otherwise the order falls through to Life 2 Man → DT 2 Man → Pedemont → TT |
needs confirming |
SA-D9 |
OpenResolved in code, entry updated | swo has retired the rate-card lookup. db.checkRateCard is commented out; swo now calls utils.autoApproveFECourier for the "no courier covers the postcode" fallback. The outcome — approve the front-end pick, reason 14 — is unchanged; only the mechanism moved |
needs confirming |
Where the deployed code and the documented or expected behaviour do not line up. Each carries a reference so it can be quoted in a ticket, the same way the Lead Time defects are. These describe code, never people.
Source references (read-only)
The code and searches this reading was written from.
Business logic lives in these files (do not edit — read-only per the access policy). The two
controllers/app.mjs files are the entries' pinned code_refs (front-matter) and were
re-verified at the commits below; the remaining files are the wider source map and were not
individually re-verified in this pass.
Verified this pass (main decision loop):
shipping-v6-nswo/controllers/app.mjs@0ed5ee9— nswo decision loop (verified 2026-08-04)shipping-v6-swo/controllers/app.mjs@69029d7— swo decision loop, grouping (verified 2026-08-04)
Wider source map (not re-verified this pass):
helper/courierScheduleClient.mjs— buffers, CBM, date selectionhelper/formatter.mjs— order formatting (swo addsmergeSalesOrders)controllers/ns.mjs— NetSuite writes (swo addsupdateGroupedSalesOrders)controllers/aws.mjs— postcode validation, S3, SEShelper/enums.mjs— couriers & service levelsconfig/{staging,production}.mjs— reason codes, saved-search IDs
Full architecture, repo differences, and file-by-file detail are captured in this entry itself, across the three readings — this registry is the self-contained reference.
Change history
What moved on this page, and when.
| Date | Change | Change request |
|---|---|---|
| 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. Its four entry-specific sections — the entry gate, the courier hierarchy, the reason codes and the schedule sheet — sit between Processing and the decision tree, which is where sections of an entry's own belong. 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 | Added a decision tree to the developer reading: four stages — does the order enter, which pile, which courier, which day — in the order they run, each branch naming the rule it implements and the test case that proves it. All 36 cases are cited. It restates what this entry already documents and asserts nothing new: it is drawn from the saved-search criteria, the routing and scheduling sections, the schedule sheet and the cases, and every rule, defect and case ref in it was checked to resolve. Where an open defect changes what actually happens the branch says so and follows the deployed behaviour rather than the board — SA-D8 (TLC is tried in the Two Man branch in swo, auto-approved only when it is the front-end pre-selection), SA-D7 (the PR titled "remove fallback to dt" left both DT couriers in nswo's hierarchy), and SA-D9, SA-D2, SA-D1, SA-D4, SA-D3. The planned date model is marked as not live, where reading it as current is the easiest mistake on this page. This entry carried no pseudocode at all, so its decision logic existed only as prose bullets, a diagram and a test table; it is the third entry to carry a tree, after lead-time flows 1 to 3. No logic change, no defect status reinterpreted, and last_reviewed is unchanged — a restatement is not a re-verification. |
— |
| 2026-09-28 | 36 test cases moved from the body table into the test_cases: front-matter register and restated in the shape a test pack uses — Case ID · Layer · Preconditions · Input · Expected output · Rule ref. Ids become SA-TC-<case>, which keeps every case id the table already used while making them unique across the registry. The build renders them back under ### Test cases, and they now also appear on the generated Test cases page, so a pack spanning two entries no longer has to be retyped. 36 name no rule — neither is inferred, and npm run check now counts them. No expected value was changed. Removing the #### G · Edge cases & resilience layer heading also revealed that this entry has no ### Edge cases section: the check had been matching that #### heading as if it were one. The check no longer accepts a #### in place of an ###, so the gap now warns. 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 SA-: 9 defects (9 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 slug s3 renamed aws-s3, matching the aws- prefix aws-lambda already used here. systems: drives the blast-radius queries and the systems view of the Integration Map, where an unprefixed slug reads as a separate platform. 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: pre-delivery (Pre-Delivery), 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-31 | Linked to LI-BL-FUL-018, the scope rule of the new Courier Tracking Status Update entry: the consignment this module approves a courier for is the one that entry then tracks. related only — kept bidirectional so the Integration Map draws the edge both ways. 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, and Known divergences & things to watch renamed Known defects; Outputs & side effects and Failure modes written for the first time, from the documented verification steps. script_updated 2026-07-21 read from GitHub — 0ed5ee9 (nswo) and 69029d7 (swo) are both still HEAD, so this entry is not behind its code. script_created is not recorded. No logic change, and last_reviewed is unchanged. |
— |
| 2026-08-21 | Aligned to the current registry structure: source links at the verified commits, record added to every field so the field map keys correctly, divergences given SA- references, change history unchanged in place. review_cycle removed — this entry is git-backed, so drift is detectable from the commit |
— |
| 2026-08-21 | Linked the entry to the glossary: 16 terms recorded in terms:, including Full Service and notice period, which this entry relies on and which are not defined anywhere. 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 10 fields, and drafted the business meanings from those labels plus the documented behaviour. One field is fully confirmed; the other nine keep confirmed: false because their meaning is a draft awaiting sign-off, not because the label is missing. Two supplied labels carried obvious typos and were corrected — ife Truck Region to Life Truck Region, and qty commited at So line item to Qty committed at SO line item. No logic change. |
— |
| 2026-08-17 | Added the field registry: a fields: block covering the ten fields the courier-approval flow reads and writes, rendered as a business-facing table and an id-keyed developer table. All ten are flagged needs confirming — the ids and impacts come from this entry's own developer reading, while the NetSuite labels and business meanings 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 (project_owner, developers, for_department) are now the only ownership record. Attribution only, so last_reviewed is unchanged. |
— |
| 2026-08-17 | Recorded ownership: project_owner Kim Hoang Nguyen, developers Yvonne Kotevski and Sushil Adhiraki, for_department Fulfilment Team. The role fields owner / tech_owner are unchanged — roles and people are kept side by side (WORKING_AGREEMENT §2). 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 nine Mermaid diagrams became five hand-written HTML trees, so diagrams now print, search and copy like text. Rule IDs, anchors, front-matter and all developer text are unchanged; stale §n cross-references were repointed at the new sections. This supersedes v1.2, whose Mermaid accessibility markup went with the Mermaid. No logic change, and last_reviewed stays 2026-08-04 — nothing was re-verified against production. |
— |
| 2026-08-17 | Diagram presentation pass: accTitle/accDescr on all ten Mermaid diagrams and labelled arrows. Superseded by v1.3, which removed Mermaid entirely. No logic change. |
— |
| 2026-08-04 | Removed the external Claude-artifact "interactive documentation" links (header + §13) — not durable, not team-accessible, and redundant with this self-contained registry page. Split the header metadata onto separate lines for readability. No logic change. | — |
| 2026-08-04 | Adopted the registry convention: added front-matter, rule IDs (LI-BL-FUL-001/002/003, LI-BL-FRT-001) with stable anchors, and re-verified both app.mjs decision loops against production HEAD (0ed5ee9 / 69029d7). Recorded three drift findings in §10 (PR #12 DT-fallback intent; TLC handled in swo's Two Man branch; swo rate-card retired for autoApproveFECourier). Content otherwise carried over from the pre-convention entry. |
— |
| 0.x | — | Pre-convention entry: documented from code + Miro board + live saved-search data + fulfilment schedule sheet. |
Field registry — ids
Inputs
| Field id | Field name | Record | Source | Type | Read / written | Used by |
|---|---|---|---|---|---|---|
SO_shipTogetherWithID |
Ship Together With This Sales Order | Sales Order | Saved search 7136 | select | read | Orders that travel togetherLI-BL-FUL-002 |
SO_courierServiceLevel |
Courier Service Level | Sales Order | Saved search 7132 / 7136 | select | read | Picking the courierLI-BL-FRT-001 |
SO_postcode |
Shipping Postcode | Sales Order | Saved search 7132 / 7136 | text | read | Picking the courierLI-BL-FRT-001 |
SO_state |
Shipping State | Sales Order | Saved search 7132 / 7136 | select | read | Picking the dayLI-BL-FUL-003 |
SO_cxRequestedDate |
Customer Requested Delivery Date Onwards | Sales Order | Saved search 7132 / 7136 | date | read | Picking the dayLI-BL-FUL-003 |
item_defaultShippingMethod |
Default Shipping Method from Item record | Inventory Item | Saved search 7132 / 7136 | select | read | Picking the courierLI-BL-FRT-001 |
item_packageCBM |
CBM from Item record | Inventory Item | Saved search 7132 / 7136 | decimal | read | Picking the dayLI-BL-FUL-003 |
item_qtyCommitted |
Qty committed at SO line item | Sales Order line | Saved search 7132 / 7136 | integer | read | Orders that travel togetherLI-BL-FUL-002 |
SO_lifeRegion |
Life Truck Region | Sales Order | Saved search 7132 / 7136 | select | read | Picking the dayLI-BL-FUL-003 |
SO_shipVia |
Ship Via | Sales Order | NetSuite | select | read-written | Picking the courierLI-BL-FRT-001 |
Outputs
| Field id | Field name | Record | Source | Type | Read / written | Used by |
|---|---|---|---|---|---|---|
SO_shipVia |
Ship Via | Sales Order | NetSuite | select | read-written | Picking the courierLI-BL-FRT-001 |
Hover any box to see what it means. Click to pin it. Every decision below is one of the four rules — the rule ID is on each tree.