← All manuals Operator Guide for this screen IM2

RO Inbox

RO Inbox
Technical Spec Ver 2 Updated 2026-09-06 Matches the live screen

RO Inbox — Technical Spec

Router: app/ro_import.py, mounted at /ro before ro_router (see the im2.py mount patch — this ordering is load-bearing: ro_router's /{order_no:int} and /{screen:str} catch-alls would otherwise swallow /ro/inbox).

Data

Both tables must be applied via create_inbox_tables.sql before this router is deployed — they do not exist in the schema this package shipped against.

Routes

MethodPathNotes
GET/ro/inboxList screen. status query param: Awaiting Review (default) / Held / Rejected / All.
POST/ro/inbox/importsource (FP\BT\XLSX), source_id, optional bom_ref, optional file. See "Import flow" below.
GET/ro/inbox/{inbox_id}Detail screen (Screen B).
POST/ro/inbox/{inbox_id}/typero_type — sets the operator-chosen RO type before Accept.
POST/ro/inbox/{inbox_id}/holdnote (required).
POST/ro/inbox/{inbox_id}/rejectnote (required).
POST/ro/inbox/{inbox_id}/acceptCalls promote_inbox_to_ro(). Requires ro_type already set.

All POST routes require level2+ (ro_import.can_write / require_write — a fixed role check, admin/level1/level2, per Ver 3 addendum ruling C.3, not a role_action_perms lookup).

Import flow (stage_source_job)

No longer a stub [Ver 2, 2026-09-06]: stage_source_job() now dispatches into app/ro_fp.py (stage_fp_job(), stage_bt_job(), stage_upload()); lookup_item() / search_items() query the IM2 items table directly; promote_inbox_to_ro() is implemented in this file. The contract below still holds — every caller treats a raise as "nothing was written".

One entry point covers all three sources:

  1. stage_source_job("BT", source_id, user) (no bom_ref) → {"choices": [...]}, nothing staged yet.
  2. stage_source_job("BT", source_id, user, bom_ref=<picked>){"inbox_id": N}.

A raise from stage_source_job at any point means nothing was written — the route returns {"ok": false, "error": ...} with HTTP 502, and the operator can just retry.

Accept: promote or refresh (promote_inbox_to_ro) [Ver 2, 2026-09-06]

Dave, 2026-09-05: a re-import must refresh the RO the source job already produced, not create a second one. promote_inbox_to_ro() first looks for ro_header.source_id = ro_inbox.source_id (newest first); if found it hands off to refresh_ro() and returns that existing order_no. refresh_ro():

On the fresh path it inserts ro_header (job_name falling back to customer_name / FP <id>, bom_name falling back to source_ref / Inbox <id>, carrying fp_material_list_id, fp_job_id, fp_job_cuid, source_system, source_id, note) then one ro_detail per ro_inbox_line, mapping would_hold to line_status='HOLD' and raising a ro_issue ('SKU not in QB' when the reason mentions QB, else 'no qty'). ro_header_job_bom_uniq (job_name, bom_name) makes a duplicate Accept fail cleanly.

A held line is non-blocking; the rest of the order picks, posts and orders normally. Once the last Open issue on a line is closed, ro.sync_line_hold() clears the HOLD by itself (Dave, 2026-09-05).

Status machine

Awaiting Review / Held / Error → (Accept) → Accepted, or → (Reject) → Rejected. Accepted and Rejected are terminal — every action route 400s on a terminal row. One exception, added 2026-09-05: POST /ro/{order_no}/delete (admin only, in ro.py) sets any ro_inbox row pointing at that RO back to status='Awaiting Review' with order_no, reviewed_by and reviewed_at cleared and a note — a deleted RO's source job returns to this queue.

Transaction rules (unchanged)

See the docstrings on stage_source_job() and promote_inbox_to_ro() in app/ro_import.py: a raise from either must never leave a half-written row. POST /ro/inbox/{id}/accept still updates ro_inbox in its own follow-up transaction, and on a raise sets status='Error' + parse_error and returns 502.

Shared table sort (2026-09-07)

Column sorting is one implementation in templates/base.html (Dave, 2026-09-07: "all screens that have tables like this need sorts on the appropriate columns"). A table opts in with class="sorttable"; every thead th becomes sortable except those with class="nosort"/class="toggle", an empty heading, or a checkbox in the heading. Client-side only, over the rows already rendered. Details: tech/table_sorting.md.