diff --git a/docs/superpowers/specs/2026-10-26-inbox-3-matching-design.md b/docs/superpowers/specs/2026-10-26-inbox-3-matching-design.md new file mode 100644 index 0000000..731e376 --- /dev/null +++ b/docs/superpowers/specs/2026-10-26-inbox-3-matching-design.md @@ -0,0 +1,190 @@ +# Inbox 3 — delivery-note matching screen + +Date: 2026-10-26 · Status: draft for approval + +## Why + +We need to know, for every purchase order, exactly what was delivered and how much. Today's +matching screen gets in the way: + +- Two sources of suggestions disagree. The rule matcher (`match-po-item.ts`, the + `product_matched` check) and the AI allocation preview (`reconciliation_preview`) are both + shown, and they often contradict each other. On slip 0007075669 the AI matched all 7 lines + while the rule check said 6 matched "no PO line". +- The line picker (`ProductDecisions` in `document-detail-view.tsx`) lists every PO line + with no quantities, and lets you pick a PO line but not say how many arrived, in what unit, + or at what price. +- Units that differ between slip and PO (roll vs m, roll vs kg, 5 kg bag) cannot be + resolved on screen. The AI turned "2 rolls" into 60 m and flagged a huge excess. +- A slip stays on the screen as one block until every line is sorted, even when most lines + are plainly right. + +## What it is + +A new screen, **Inbox 3**, for the office to test on real delivery notes. The site keeps +using the current inbox until we are happy. Then we switch site over. + +The capture flow does not change: someone takes a photo of the slip in the app and it comes +in. Inbox 3 changes what happens after that. + +## How it behaves + +1. **Auto-match first.** When a slip arrives, the system matches every line it is sure of. + Each line that passes all checks goes straight to **Ready to confirm**. Only lines that + still need help stay in Inbox 3. +2. **Slip-first table, one numbered row per slip line** (1, 2, 3…). Columns: + - **On the slip:** the description, plus the slip's own qty × price = amount. + - **Goes to PO line:** a searchable dropdown containing only this PO's lines (type "bail" + to narrow), plus "Not on this PO". It is pre-filled with the single best suggestion. + - **Received:** the quantity, editable. + - **Unit:** a text box with suggestions as you type (each, roll, m, m², m³, kg, 5 kg + bag, ton, length, box, pack). It is never limited to a list. When the unit differs from + the PO line's unit, a box appears: "1 roll = __ m on the PO". When it is filled in, + quantity, value and "left on PO" recalculate. + - **Price:** editable. It starts at the PO rate, with the slip's price shown beside it. + When they differ, the line is flagged and the user picks one or types their own. + - **Value:** received × price, ex VAT. + - **Left on PO:** ordered − (earlier receipts + this line), in PO units. +3. **Money totals** at the bottom: the value of the remaining lines, plus the whole slip ex + VAT against what the PO would charge for the same items. +4. **Slip and PO pictures on the right**: slip above, PO below, with the current line + highlighted where possible. On narrow screens, a switch between Both / Slip / PO. +5. **The assistant** is a text box beside the pictures. You talk to it by line number ("fix + line 5", "lines 3 and 4 are one item, 60 m total, per metre"). It can change anything on a + line: quantity, unit, unit conversion, price, which PO line, and combining several slip + lines into one PO line. It can only choose PO lines that are on this PO. It never posts. + The furthest it goes is Ready to confirm. +6. **Finished lines leave the screen.** When a line is complete (saved by the user, or fixed + by the assistant and passing checks), it moves to Ready to confirm. A banner says how many + lines have gone ("5 lines sent to Ready to confirm · show"). +7. **Ready to confirm** groups the ready lines by slip. **Both site and office can confirm** + (David, 2026-10-26): + - In Inbox 3, admin and office users press Confirm. + - The same ready lines also appear in site's normal Ready to post, where the site manager + presses Confirm delivery as today. + + Either one posts the ready lines of that slip, never the unresolved ones. Whoever confirms + first wins; the other sees the lines as posted. A slip is complete when all its lines are + posted. + + This means the existing inbox's Ready to post and Confirm delivery must handle a slip where + only some lines are ready. They list and post just the ready lines, instead of requiring + the whole document to be ready (`deliveryReady` / `prepareDelivery` today). This is the + second deliberate change to the existing inbox. + +## Goals + +- One suggestion per line, from one source, with quantity, unit, price and value visible. +- Any slip line can be fixed in place, by hand or by asking the assistant. +- Lines move to Ready to confirm one at a time, as soon as they are right. +- Slip-vs-PO unit and price differences are resolved explicitly and remembered. + +## Non-goals + +- Redesigning the site's current inbox screen or the photo capture flow. Only two safety and + readiness changes reach it: the supplier guard, and posting only the ready lines. +- Agents (the assistant, Foreman, MCP tools) posting. Only a person presses Confirm: site + manager, admin or office user. +- New OCR or page-grouping work. + +## Anti-goals (a failure even if it "works") + +- The assistant links a line to anything that is not a line on this PO, or invents an item. +- A price or unit conversion is applied silently. Every change from the PO rate or PO unit + carries a reason and shows on the line. +- A line disappears from Inbox 3 without arriving in Ready to confirm. +- A slip is linked to a PO from a different supplier without being stopped. This happened + live: Chamberlain slips sat on Avidan and Raumix POs. + +## Decided by David + +- Slip-first layout (mockup A, then v2). +- Numbered lines; quantity, unit and price all editable; a money value on every line. +- Slip and PO pictures on the same screen. +- A searchable PO-line dropdown limited to this PO. +- An assistant with full edit rights on the lines, stopping at Ready to confirm. +- Completed lines move to Ready to confirm individually; only unresolved lines stay. +- Built as a separate Inbox 3, tested by us first, then rolled out to site. +- During the test, both site (from the normal Ready to post) and office (from Inbox 3) can + confirm ready lines. +- The price on each line is editable. + +## Claude's calls (check these) + +- **Who sees Inbox 3 during testing:** admin and office_user only, not site_user. This + needs a new role check, because financial pages currently let site managers in + (`access.ts`, `path-access.ts`). +- **Price default:** the PO rate. The slip price is shown alongside, and any difference + above R0.50 per unit or 1% flags the line. +- **The unit box** is free text plus suggestions. A "1 X = n PO-units" factor is required + when the unit differs from the PO's. The factor is remembered per supplier + product + (extends `remembered-match.ts`, which today refuses unit overrides). +- **Suggestions come from one source:** the AI allocation. The rule matcher stays as a + guard. If it finds a dimension conflict (e.g. 600 vs 1050 pipe), the AI suggestion is + shown as "check", not ready. +- **Line numbers** in the UI and assistant are 1-based and map to `lineIndex` (0-based). +- **Supplier guard:** a slip whose supplier does not match the PO's supplier is blocked with + a clear message, in Inbox 3 and the existing inbox. This is the one deliberate change to the + existing inbox: it is a safety check, not a screen change. Supplier aliases and merged + suppliers count as a match. The user can override from the line with a reason. The guard + applies whenever a note is re-checked, so existing wrong links show up too. +- **Auto-match ("sure of")** puts a line straight into Ready to confirm only when all of + these hold: + - the AI allocation and the rule guard agree on the PO line; + - `prepareLineBooking` passes; + - the unit equals the PO unit, or is converted by a remembered factor (the factor is shown + on the line); + - the price is within tolerance. + + It runs where the AI preview runs today, on arrival. Queued notes are re-run once when + Inbox 3 ships. +- **Partial slips:** these use the existing per-line posting. The first posted line creates + the delivery note, the slip completes when all its lines are posted, and unposted lines + remain visible in both inboxes. Editing or reversing a posted line stays as today (correct + receipt). No new document state is added. +- **Memory:** only unit factors are remembered. Price differences are not; each one is + decided on its line. +- **Combined lines** are one row labelled with all their numbers ("3+4"). They are ready and + posted together, as one `kit`/`converted` allocation. Quantity and price are entered once, + in PO units. +- **Price tolerance:** a line is flagged if it differs from the PO rate by more than R0.50 + per PO unit OR more than 1%, compared after unit conversion. Every change away from the PO + rate or PO unit needs a free-text reason. The assistant writes its own reason. +- **Assistant safety:** its edits go through the same validation as manual edits + (`prepareLineBooking`). Chat and changes are logged per note in `dn_review_events`, along + with who asked. + +## Built on what exists + +- The list model and tabs from `src/lib/inbox-two/model.ts` and `inbox-two-view.tsx`. +- The page viewer (`PageViewer`) and the two-column workspace from + `inbox-document-workspace.tsx`. +- The assistant: route `api/dn-batches/[id]/notes/[noteId]/assistant`, `review.ts`, + `review-schema.ts` and the MiniMax model. The schema already supports qty/unit `edits`, + `purchaseOrderItemId` and `kit` combining. It needs to add **price** (`netAmount` / unit + price, today forced to `usePoRate`) and a **unit factor** on converted lines. The server + must reject any `purchaseOrderItemId` that is not on the note's PO. +- Per-line validation and posting: `prepareLineBooking` (`line-booking.ts`) and + `postLineWithinLease` (`post-line.ts`). These already support per-line posting, + `unitOverride`, `netAmount` and `usePoRate`. +- Readiness: `deliveryReady` is whole-document today. Inbox 3 needs a **per-line ready + state**: a line that passes `prepareLineBooking` is "ready" even while other lines on the + same slip are not. + +## Left to the builder + +- How per-line ready state is stored (on the allocation or in a separate field), and how + the Ready to confirm view reads it. +- Highlighting the current line on the slip and PO images (best effort; there are no page + coordinates today). +- Whether Inbox 3 is a new route (`/dashboard/financial/delivery-notes/inbox-3`) or a mode + of the inbox-2 document page. A new route is preferred so site never sees it by accident. +- Exact styling, using the existing shadcn components. + +## Success + +- The 14 notes in today's queue can each be cleared in Inbox 3 without the old screen or + agent tools. +- No line can reach Ready to confirm with a PO line from another PO, an unexplained unit + change, or an unexplained price. +- After a week of office use, David decides whether to switch site over.