RO Inbox — Detail
RO Inbox — Detail — Technical Spec
Route: GET /ro/inbox/{inbox_id} in app/ro_import.py.
Data
Reads one ro_inbox row plus all its ro_inbox_line rows, ordered by line_no. No writes happen on page load. A missing inbox_id renders the same template with not_found=True and HTTP 404.
Line columns rendered: line_no, line_type, sku, description, qty, uom, change_type/qty_prior (as New line / Dropped from source (was N) / Qty A → B), would_hold (row highlighted) and hold_reason. The Type dropdown and the three action buttons render only when can_write and the row is non-terminal. [Ver 2, 2026-09-06]
Actions on this screen
Same three POST routes as the list screen (/ro/inbox/{id}/hold, /ro/inbox/{id}/reject, /ro/inbox/{id}/accept), plus POST /ro/inbox/{id}/type (ro_type form field) for setting the operator-chosen type inline. See docs/tech/ro_inbox.md for the full contract of each.
Accept
POST /ro/inbox/{id}/accept:
- 400 if
ro_typeis not set, or the row is alreadyAccepted/Rejected. - Calls
promote_inbox_to_ro(inbox_id, user)(implemented inro_import.pysince 2026-09-05), outside any transaction this route holds, in its own transaction: on success it returns anorder_nohaving writtenro_header+ro_detailin full; on failure it raises having written nothing. If the source job already produced an RO it refreshes that one viarefresh_ro()and returns its existingorder_no— so a "successful Accept" does not always mean a new order number. Seedocs/tech/ro_inbox.md. - On success:
ro_inbox.status = 'Accepted',order_noset,ro_auditrow written, JSON{"ok": true, "order_no": N}returned. The template redirects the browser to/ro/{order_no}?imported=1. - On the stub raising:
ro_inbox.status = 'Error',parse_errorset to the exception text,ro_auditrow written, HTTP 502 with the error message. The row is untouched otherwise — Accept can be retried once whatever's wrong is fixed.
Review-screen header edit + pre-RO line fix (2026-09-07)
DDL: skills/requirements_orders/scripts/ro_inbox_review_2026-09-07.sql (applied live). New ro_inbox columns: qb_customer_id, qb_customer_name, class_ref, date_required, priority_code, link_system, link_order_no, link_label. ro_inbox.ro_type CHECK widened to match ro_header (Roofing/Administration were offered by the class list but refused on save). ro_detail.line_type CHECK now allows SURPLUS.
Routes (app/ro_import.py):
POST /ro/inbox/{id}/header— one save for the whole header; validates class against the RO classes setting, priority against R/1-5, and the source order number throughcheck_source_link(). Writes onero_auditrow per changed field (action='inbox_edit_header',note='inbox_id=<id>').GET /ro/inbox/link-check?system=&order_no=— FP is a livero_fp.find_joblookup (returns the FP record id, which is stored onro_inbox.fp_job_id); BT is checked against thebt_documentcache, because the app itself has no browser.GET /ro/inbox/suggest-date?ro_type=—po.add_working_days(today, lt_* setting).POST /ro/inbox/{id}/line/{line_no}— sets sku/qty on a staged line, clearswould_holdwhen the line is complete,drop=1deletes it. SKU is validated against IM2 before it is accepted.
promote_inbox_to_ro() now carries date_required (header value, else the class lead time), priority_code, class_ref, qb_customer_id/name, entry_source, and the link_system/link_order_no pair as the RO's source system/id. Header date_required seeds every ro_detail.date_required.
Refresh-vs-new fix: the prior-RO lookup matched on source_id alone, so a second BOM on the same FP job refreshed the first BOM's RO instead of creating a second RO. It now matches source_id AND bom_name, per the v3.4 design rule.
Review screen: surplus, BT free text, Fix-now autosave (2026-09-07)
ro_inbox_line.line_typeCHECK widened toINV | NONINV | SURPLUS(scripts/ro_inbox_surplus_2026-09-07.sql).POST /ro/inbox/{id}/line/{line_no}takessurplus=1: NONINV only, qty required (falls back to the line's own qty), setsline_type='SURPLUS', clearswould_hold/hold_reason, auditsinbox_line_surplus.promote_inbox_to_roalready passesline_typethrough and now keeps the SKU on a SURPLUS line.check_source_linkfor BT no longer fails on a cache miss: BT jobs have no order number (Dave, 2026-09-07), so any text is accepted,unmatched:trueflags the no-cache-hit case and the label isBT <text>. FP is unchanged (live lookup).- Lost-qty defect:
fixLine()only ran on the row's Save button, so a qty typed and left in the box was discarded and Accept promoted the line with qty 0 (inbox 22 → RO 799025: noinbox_line_fixaudit row exists, which is how this was diagnosed). Fix-now inputs now mark their row dirty on input, save onchange, andinboxAction('accept')callsflushAll()beforesaveHeader().fixLinegained aquietflag and returns a boolean so the flush can abort Accept on failure. - Accept's missing-class error message says "Class" (the field was renamed on 09-07).