← All manuals Operator Guide for this screen IM2

RO Maintenance & Attachments

Requirements Order
Technical Spec Ver 2 Updated 2026-09-07 Matches the live screen

RO Maintenance & Attachments — Technical Spec

All routes in app/ro.py, extending /ro/{order_no:int}. Level2+ for the inline edits and attachments (ro.require_write — the file's existing level3-view-only rule); the two routes added 2026-09-05 have their own, stricter gates (see below). Template ro_order.html, screen key ro_order, banner Requirements Order.

order_detail() accepts line_filter (LINE_FILTERS: all / open / adjusted / noninv / cancelled; anything else falls back to all) and the one-shot banner flags ?imported=1 (from Accept) and ?created=1 (from Create a Requirements Order). It also passes held_count, imports (import history), header_attachments and per-line attachments.

Inline edits

RouteFieldsNotes
POST /{order_no}/line/{line_key}/adjustqty_adj, adj_note (optional)Writes ro_detail.qty_adj + qty_source_at_adj/adj_by/adj_at/adj_note. Never writes qty_source or qty_required (GENERATED). Also writes one ro_bom_audit row (match_basis='Manual Adjustment') and one ro_audit row.
POST /{order_no}/date_requireddate_required (blank clears it)Writes ro_header.date_required + ro_audit. No default is ever applied server-side (Ver 3 ruling C.2).

adj_note is optional per Ver 3 ruling C.1 — the original brief's "mandatory adj_note" is superseded.

Routes added 2026-09-05 [Ver 2, 2026-09-06]

RouteGateNotes
POST /{order_no}/priorityrequire_action(user, "rush_priority_set") — admin/level1 by defaultWrites ro_header.priority_code = exactly 'R' or NULL, 400 on anything else; no-op returns changed: false; one ro_audit row (rush_priority_set/rush_priority_clear). Called from the PO Workbench demand drill-down, not from this screen. FP-sourced 1–5 priorities stay read-only app-wide.
POST /{order_no}/deleteuser["role"] == "admin" (hard-coded, not a perms key)400 if sum(ro_detail.qty_issued) > 0 or any ro_pick row exists ("cancel it instead"). Then: sets any ro_inbox row for this order back to status='Awaiting Review' (order_no / reviewed_by / reviewed_at cleared, note written); deletes ro_issue, ro_bom_audit, ro_attachment, ro_detail for the order; update ro_audit set order_no = NULL; deletes ro_header; inserts one ro_audit row (action='ro_delete', order_no NULL) naming the job/BOM and line count. The button renders only for user.role == 'admin'.

Held-line badge

order_detail() now also counts ro_detail rows with line_status = 'HOLD' for the order and passes held_count to the template; the badge links to /ro/issues/{order_no} (Screen C's per-order drill-down).

Attachments

Table: ro_attachment (create_inbox_tables.sql). line_key IS NULL = header document.

RouteNotes
POST /{order_no}/attachmentsMultipart, one file per call (file, optional line_key). 25 MB cap, any content type, filename sanitised (_safe_filename) before it touches disk. Writes the file under RO_ATTACH_DIR (env var, default /var/lib/im2/ro_attachments) as {order_no}/{uuid}_{name}, written to disk before the DB row so a failed insert never leaves a dangling reference to a missing file.
POST /{order_no}/attachments/{attachment_id}/removeSets deleted_at. Never deletes the file — same no-physical-delete rule as everywhere else in this schema.
GET /{order_no}/attachments/{attachment_id}/downloadAvailable to every role — only Remove is level2+. Streams the file with its original filename.

order_detail() loads all active attachments for the order in one query (fetch_attachments()) and splits them into header_attachments and a line_key → [attachments] map, attached to each line as line["attachments"] before rendering — no per-line lazy fetch, unlike the PO-links/movement/refresh History panel (order sizes here are small enough that eager loading is simpler and just as fast).

Manual PO reference (added Ver 1, 2026-09-06)

POST /ro/{order_no}/line/{line_key}/po_ref and POST /ro/{order_no}/line/{line_key}/po_ref/{po_link_id}/remove in app/ro.py. Dave, 2026-09-06: "They can run the buy report, enter the PO manually. They need to be able to enter the pieces that come back to the RO manually."

RO number, line sorting, per-line issue flags (2026-09-07)

order_detail() loads open issues grouped by line_key and stamps open_issues / issue_types on each line, plus open_issue_count for the header. Line sorting is client-side and moves each tr.line-row together with its hidden #expand-{line_key} row; only columns 1–8 (Line # … Req Date) are sortable.

Status was reviewed against live data on 2026-09-07 and is behaving as the views define it (ro_detail_v.line_state, ro_header_v.ro_status) — every current RO reads Open because nothing has been picked or PO'd yet. The one gap worth knowing: an RO whose only exception is a HOLD line still rolls up to "Open", which is why the issue badge exists.

Ver 2 - 2026-09-07 - header Class

Ver 2 — 2026-09-07 — header Class

Ver 3 - 2026-09-07 - PO reference removal opened up

remove_po_ref() no longer rejects link_source <> 'Manual entry' (Dave: "I need to also be able to remove one"). Any ro_po_link row on the line can be deleted; the audit row records the source it was removed from in new_value, and the response carries may_resync=True for QB-sourced links, which the confirm dialog surfaces. Deleting a link changes workbench supply for that line immediately (see tech/po_workbench Ver 3).