ORDER-FORMS.md — the form-by-form reading of the order flow
One section per form, filled as the owner walks it. Each decision line is written in his words where the wording matters, and is what a row must satisfy. Anything still open is listed at the end of the section, never assumed.
Resume rule: a row touching one of these forms reads its section first.
1. Fabric SO — /orders/new/trading
OrderEntryForm with fixedType="SO", plus the ops completion form on the
order record (/orders/[id] ?/complete) which fills what sales leaves blank.
Creation writes the order, its coverage apportionments and any auto production
order in one transaction.
D1. Quantity is metres only (2026-10-30)
A fabric SO is booked in metres, always. A job-work order may be booked in
metres or beams. The two forms are separate forms and stay separate — a
job-work field never appears on the sale form. The code is already right
(qtyUnit==='beams' && !isJW is refused; SO is forced to mtrs).
D2. Tolerance is ±5% of the quantity (2026-10-30)
- The tolerance is a percentage of the sale quantity, not an absolute metre figure. Default 5%, stored on the order.
- Dispatch is checked against the ±5% band. Inside the band it proceeds.
- Beyond the band the order needs an amendment, and that amendment needs owner approval — not a note, not a silent over-ship.
- The existing absolute
diffMtrstays on the job-work side; the SO tolerance is separate.
D3. Apportionment is assigned within the band; the gap is the user's (2026-10-30)
Three separate quantities, all inside ±5%:
- Apportionment at booking — PO / free stock / market fabric lines are assigned within the band. The plan is not forced to equal the quantity exactly.
- Dispatch — cumulative dispatch is checked against the same band.
- The gap between (1) and (2) is settled by the user, never the system.
- Dispatched more than apportioned → the system asks the user to apportion more, from a PO or from fabric, at his choice.
- Dispatched less than apportioned → the excess is released back.
- Nothing is auto-drawn and nothing is re-priced.
D4. A dispatch never blocks (2026-10-30)
If a dispatch exceeds what is apportioned, the dispatch saves and raises an item on the pending board. The gate is never held while costing catches up.
D5. One price for every grade that ships; X never ships (2026-10-30)
- A, B, C and R all command the same ₹/m. There is no grade-wise price and
the SO keeps its single
rate. - X never goes to the buyer. It does not count toward the sale quantity and does not count toward the ±5% tolerance — a lot that yields X is a production shortfall, not permission to dispatch short.
- X is sold, if at all, as its own separate (damaged) sale at its own price.
D6. Delivery is staged inside a fixed window (2026-10-30)
- An SO fixes a start date and an end date. There is no per-stage schedule of dated quantities.
- Deliveries happen as the required lots get ready inside that window — the rhythm is production-driven, bounded by the two dates.
- The buyer takes delivery in dispatch lots (say 5,000 or 6,000 m a shipment), not in one final drop. That lot size is a hint shown on the dispatch form, left to the dispatcher — the system does not enforce a minimum or a multiple.
- The stated quantity (D1) plus its ±5% (D2) is measured cumulatively across the whole window, not per lot.
D7. The consignee may be someone other than the buyer, and may be decided later (2026-10-30)
- The goods can be delivered to a consignee that is not the buyer — a depot, the broker's godown, a transporter's hub.
- The consignee is a party master link — not free text — carrying the new
Otherscategory in the party master (added for this). - The consignee may not be known when the SO is booked, so the SO carries it as optional with an explicit decide at fulfilment state.
- Dispatch requires a consignee. If the SO left it open, the dispatch form is where it must be resolved.
Worked (C381, 2026-10-31). The order stores one nullable party link
(consignee_party_id); a blank link is the decide-at-fulfilment state and
every surface prints those words until it is resolved. The receiving categories
are Others (added for this), Godown and Transport — the depot, the
broker's godown and the transporter's hub D7 names — and the party must be
active. Booking offers the picker but never requires it; a consignee named at
booking is read-only for planning; the planning card may resolve an open one;
dispatch refuses to save without one, stores it on the slip, and writes it back
onto the order as an append-only amendment when the sale left it open.
D8. The broker is a party id (2026-10-30)
The broker is stored as a party link (broker_id, category Broker) — not
a typed name that merely matches party.name. A rename must never orphan a past
order, and the brokerage statement keys on the id.
Worked (C382, 2026-10-31). order.broker_id is an additive nullable FK;
broker_name stays on the row as the name the booking recorded (history only)
and DIRECT keeps its sentinel with no link. The picker lists Broker masters
only and is valued by id, and both surfaces read one rule (brokerPickError):
a master's id wins, a name a master still carries is accepted for a pre-id
booking, and anything else is refused. An SO inherits the quote's broker_id;
the record resolves its broker by id, so a renamed master still names and
reaches the same broker on every past order.
D9. Payment terms are stored as numbers, not a sentence (2026-10-30)
The order stores the CD% and the payment days on the row at booking, taken from the chosen dhara slab, alongside the dhara link. Operations confirms them rather than re-keying, and ageing / rate-list read the numbers — never a parsed label.
D10. A sale must carry a positive rate (2026-10-30)
The SO cannot be saved without a rate greater than zero — for every role, owner included. Coverage and margin therefore always have a real price to work against; ₹0 is not a legitimate sale.
D11. Freight and insurance are terms on the SO (2026-10-30)
- A sale can be ex-godown (buyer arranges) or delivered (we arrange). The choice is a term on the SO, not an assumption.
- Insurance follows the same term — it is stated with the delivery term, not left implicit.
D12. Freight/insurance are either absorbed or recovered — the SO says which (2026-10-30)
When the term is delivered, the freight and insurance may be inside the ₹/m (our cost, absorbed) or billed on top to the buyer (recovered). Both happen, so the SO states the recovery basis rather than the system assuming one. Default: freight is not included in the rate and is borne by the buyer.
D13. The end date is the promise (2026-10-30)
due_days ("we must deliver in days") and promised_days (sales promise) both
go. The SO's two dates are both promises: the start date is the promise for
the first delivery, the end date is the promise for completion. One source
of truth, two dates.
D14. Quality is found by name or alias, and the alias is recorded (2026-10-30)
- The quality picker searches both the quality name and the master's alias (the picker already does this).
- When a quality is selected, its alias is recorded on the order.
- A user who is not coming from a lead or a quote can type the alias (or the quality) to find and select the quality; the typed alias rides the order.
D15. Picks 1–200; quantity in multiples of 1,000 (2026-10-30)
- Picks is picks per inch (64, 100 …), bounded 1 to 200.
- The weaving formula stays as it is — ₹/m = picks × ₹/pick ÷ 100 — because that is the market custom: the per-pick rate already carries the unit. It must not be "corrected" to a picks-per-metre conversion.
- Quantity is entered in multiples of 1,000 metres (5,000; 6,000; 12,000).
D16. Three stages on one record, with two gates (2026-10-30)
The order is one record with three stages — Sales → Planning → PPC — and each role sees its own stage open, the others as read-only summary. Not three forms: one document, one number, one append-only trail.
- Sales (creates it): pick the quote, quantity ordered, tolerance, remark. Absolute essentials only.
- Planning (same record): buyer's order no, send-as, metres per roll/rolls, consignee or decide at fulfilment. Planning does not confirm the delivery window — it is sales' and stands as set.
- PPC / procurement (same record): the cost cover (apportionment within the ±5% band).
Gates: Sales + PPC = ready to produce (yarn booking, sizing may start). Planning = before weaving starts. Planning is not a gate on readiness.
D17. Freight/insurance are priced in the quote (2026-10-30)
Freight and insurance are quote fields — factored into the quoted rate and carried to the SO. The SO never asks them again. (Same for anything else the quote already carries: buyer, broker, quality, rate, picks, payment dharma, delivery window.)
D18. What the quote carries, the SO inherits — read-only (2026-10-31)
The quote is where the commercial terms are settled; the sale order reads them back and never asks for them again (D17).
- Brokerage % and pass-on to buyer % — both 0–100, blank means none. A percentage outside the band is refused on the form and on the action.
- Delivery term (freight/insurance) is a choice, not free text:
ex-mill(buyer collects),fob,cif(freight + insurance inside),ddp(delivered, duty paid). - Freight recovery is a choice:
on-top— excluded and billed on top, the buyer bears it (the default) — orin-rate(inside the quoted rate). - On a quote-linked SO the four show read-only (brokerage, pass-on, delivery term, freight recovery), the save takes them from the quote even if a posted value disagrees, and the form does not demand brokerage/pass-on from sales either. A quote that states none leaves them blank — the SO never invents a commercial term.
D19. The ordered quantity is free of the quote's estimate (2026-10-30)
The SO's quantity ordered may be more or less than the quote's estQty with no
restriction — the estimate is a planning figure, the order is the commitment.
D20. PPC covers the whole order before production; tolerance is sales' (2026-10-30)
- Cost cover must cover the whole order (within the band) before the order is ready to produce. Partial cover does not start production.
- The tolerance is 5% by default and sales may change it on the SO.
D21. A damaged (X) sale is exempt from the quote (2026-10-30)
X cloth never goes to the buyer; it is sold separately as a damaged lot, and that sale is exempt from the compulsory quote — it is not a normal sale.
D22. A damaged sale is its own small form (2026-10-30)
The damaged (X) sale is a separate small form — buyer, quality/lot, metres, scrap rate, source SO — not the sale form with a skipped step.
D23. The rate is prefilled from the quote and can be negotiated on the SO (2026-10-30)
- The SO inherits the quote's rate, but sales may change it on the SO — the final negotiated price wins. The quote stays as quoted (it is frozen once the SO exists); the SO carries the agreed rate.
- No owner approval is required for an SO. A sales-booked order goes straight to PPC's cost cover.
D24. The new-order screen is the quote table; a wrong pick resets clean (2026-10-30)
- "New sale order" opens the pending-quotes table (party · quality · qty · rate · validity · days open), not a blank form. One tap on a row opens the SO already filled. A Create SO shortcut stays inside the quote screen.
- Back returns to the table with the choice cleared.
- Changing the quote resets the form and re-inherits everything from the new quote (quantity from its estimate, rate from it, tolerance back to 5%, remark cleared). Nothing typed against the old quote is carried over.
- After save the quote is locked (one SO per quote); a wrong quote is corrected by cancelling and re-booking.
D25. The sales stage is four focus steps, gesture-first (2026-10-30)
- Four steps only — Quantity → Rate → Tolerance → Remark — each a full focus entry (never a compact multi-field sheet: coins and wheels need the width).
- The quote facts stay pinned at the top of every step, rate included.
- Each field uses the gesture that fits it, and suggestion coins are context-based — drawn from the quote, this buyer/quality's history and recent entries, not a fixed list.
| Step | Gesture | Coins |
|---|---|---|
| Quantity | wheel in 1,000 m steps, ± for 100 / 1,000 | quote estimate · last qty for this buyer · last for this quality · last three entered |
| Rate | numeric keypad, ± ₹0.25 | the quote's rate (marked from quote) · last rate for this quality · last for this buyer |
| Tolerance | chips 3 / 5 / 7 / 10, default 5 | this buyer's last two tolerances |
| Remark | dictation mic + text | past remarks for this buyer |
The rate is shown in the pinned strip on every step.
D26. One profitability path (2026-10-30)
Reports → Order profitability moves to the new cost engine (cost legs +
apportionment). The legacy order_cost path — the "Yarn cost basis" field, the
whole-booking pin, recordCostBasis — is deleted. It double-counted a split
booking and has no job left.
D27. Each role works from its own board (2026-10-30)
Sales, Planning and PPC each land on a dedicated pending board (built on the existing queue engine), showing only their stage's incomplete orders with an age. The Orders list keeps a "planning pending" filter for lookups.
D28. Sales uploads the buyer's order image at SO creation (2026-10-30)
The image of the buyer's order is attached by sales while creating the SO — not by planning. It is optional: a phone-call order can still be booked. When sales did not attach it, planning uploads it while filling the planning fields. Planning then verifies against that image (photo pinned in the top half of the page) and corrects the prefilled fields. Planning may add further pages.
Queued (2026-10-31)
| Row | Decision |
|---|---|
| C374 | D17/D18 — quote carries brokerage, pass-on, freight/insurance + recovery |
| C375 | D23/D24 — a normal SO must come from a quote, rate editable, no SO approval |
| C376 | D24 — the new-order screen opens on the pending-quotes table |
| C377 | D25 — four focus steps, pinned quote strip, gesture + context coins |
| C378 | D2/D6/D13/D20 — tolerance % and the two delivery dates |
| C379 | D2/D4/D5/D6 — dispatch band, X excluded, lot-size hint |
| C380 | D3/D4/D20 — PPC cover within the band, pending item, release |
| C381 | D7 — consignee party (Others), decide at fulfilment |
| C382 | D8 — broker as a party id |
| C383 | D9 — payment terms stored as numbers |
| C384 | D10/D15 — rate > 0, picks 1–200 per inch, qty ×1000, no duplicate buyer PO |
| C385 | Errors made visible (banner + jump to the field) |
| C386 | D16/D27 — three-stage rail, gates, per-role boards |
| C387 | D28 — planning card with the buyer's order image pinned |
| C388 | D14 — alias recorded on quality selection, typeable |
| C389 | D26 — retire the legacy order_cost path and report |
| C390 | D21/D22 — damaged (X) sale as its own small form |
| C391 | Draft autosave snapshots only the fields this form shows |
| C392 | Ops completion stops editing rate, cash discount, city (sales/planning own them) |
| C393 | Split the 3,460-line order form by stage |
Deferred
- PPC stage screen design (the cover UI) — walked later, beside the job-work lane.
- Quote form walk (its own section once the SO is done).
D29 — the sales SO screen is a strip plus four fields, not four sections (2026-10-31)
Owner: "do we need to show 4 sections — why not a general 4-field list as there are only 4 fields", and on the consignee: "does not appear here. It appears in planning."
The screen, in order:
| # | Thing | Kind | Rule / source |
|---|---|---|---|
| — | Quote strip | read-only | quote no · buyer · quality · quoted delivery · payment · quoted rate (quoteStripFacts) — the quote's own figures, kept beside the booked ones |
| 1 | Quantity | field | metres, multiple of 1,000, prefilled from the quote estimate, free of it (D19) |
| 2 | Delivery start | field | date, the promise for the first delivery, prefilled from the quote, editable (D30) |
| 3 | Delivery end | field | date, the promise for completion, prefilled from the quote, editable (D30) |
| 4 | Rate | field | ₹/m, greater than 0 (D10/D23), prefilled from the quote, editable |
| 5 | Tolerance | field | 0–25 %, default 5, chips 3/5/7/10 (D2/D20) |
| 6 | Remark | field | free text, past remarks for this buyer offered (D25) |
No sections on an SO. The four-section grouping (Order & source · Party, quality & rate · Quantity & delivery · Terms & papers) stays for the job-work and production forms, where the user really fills fifteen-plus fields. On an SO every section field is already a quote fact (read-only), a planning field, or PPC's — so the grouping is noise and creates a second chronology beside the wizard.
Absent from this screen, and where it lives instead: buyer's order no, cut size and rolls → planning; consignee → planning (owner, 2026-10-31; dispatch still refuses without one); coverage/apportionment → PPC; brokerage %, pass-on %, delivery term, freight recovery → carried read-only from the quote; the damaged-kind branch → its own form.
The new-order screen shows the pending-quotes table only: quote no column added, horizontal scroll on a phone, and no "Create SO" button — a quote is the only door (C375). The table row opens this form already filled.
Machine-readable copy: docs/contracts/so-form.json.
D30 — the delivery promise is set on the order, not inherited blind (2026-10-31)
Owner: "the promise start date and end date needs to be editable and finalised at the time of order creation as sometimes the quote is not finalised the same day and the promise date changes."
The quote's delivery window is therefore a starting value, never the final word: both dates are prefilled from the quote and both are editable and required on the sales screen (fields 2 and 3 of D29). The strip keeps the quoted window as context, exactly as it keeps the quoted rate beside the booked one, so the seller can see what was promised then and book what is promised now.
Validation on both surfaces: a start not before the booking date, an end not before the start, both required at booking. After creation the promise is final: changing it is an amendment event with the usual approval, never a silent edit (append-only UX).
Consequence for the rest of the flow: planning does not re-promise the window (D16 — planning does not confirm the delivery window); dispatch and the tolerance band read the dates booked here. docs/contracts/so-form.json carries them as fields 2 and 3.
D31 - the cost cover is its own page, four doors, one rule (2026-11-04)
Owner: "we can assign ready stock, which can be done right now; we can create a new fabric purchase order; we can create a new production order; we can only [use the] unassigned or unproduced part of an existing production order; or a combination of these... we need [a] dedicated cost coverage page with these things instead of having a section in sale order."
So the PPC stage's cover card on /orders/<id> is no longer a form. It keeps
the reading (band, covered, gap, cost, ready/not) and one button; the work
happens on /orders/<id>/cover, which already knows the sale order and
never asks which one:
| Door | Source | What it does |
|---|---|---|
| 1. Ready stock | pool |
assigns free godown metres of the order's quality, right now |
| 2. Buy fabric | market |
creates the purchase order and assigns it, in one step |
| 3. Running PO | po |
takes the unassigned, unproduced metres of an open order |
| 4. New PO | new_po |
raises a production order (planned to the next 100 m) and assigns it |
Any combination, in any order. Each save is one line and the page re-reads the gap after it, so a worker can take 300 m from stock now, buy 5,000 m next, and raise a new order for the rest. A partial cover is legal; the order is simply not ready to produce until the whole quantity (± tolerance, D20) is covered.
One rule for all four doors (addCoverLine): the sale order is live, the
metres are greater than zero, they are free on that source, and they never take
the sale past its quantity. The doors cannot disagree with each other — and a
blank metres box means "all this source can give, up to the gap", so the common
case is one tap.
Three quantities decide what "free" means (pure functions in
src/lib/coverQty.ts, each with its own test):
- Ready stock = the pool bucket, minus what another sale order already holds from it. A pool hold writes no pool-out line, so without this netting two orders could be promised the same cloth.
- A running PO = its plan, minus what is already apportioned, minus what has already come in as cloth (that cloth sits in the produced pool, and offering both would count it twice).
- A cost-less running PO is still a source — held provisional, its cost following the order as its legs/actuals are updated later (PLANNING §739).
A running order of a different quality is not offered: the list is filtered to the sale's own quality name (the two stock orders left on this database are this week's, each already pricing its own sale order — none is this sale's cloth).
A purchase assigned to a sale order never enters the free pool (owner,
2026-11-04): its metres are earmarked to that cover and the hold carries
purchase_order_id. Only an unassigned purchase becomes free stock, at entry,
received or not — the yarn earmark rule (C364) applied to fabric.
No schema change beyond cost_apportionment.pool_key + purchase_order_id
(0060); no field moved, so no contract change; so-form.json's absent list
already sends coverage to PPC.