← All manuals Operator Guide for this screen IM2

New Requirements Order

Create a Requirements Order
Technical Spec Ver 2 Updated 2026-09-06 Matches the live screen

Create a Requirements Order — Technical Spec

Screen key ro_new; banner/tile name Create a Requirements Order (base.html screen_names).

Routes in app/ro_import.py: GET /ro/new, GET /ro/new/item/{sku}, GET /ro/new/item-search?q=, GET /ro/new/job-search?q=, POST /ro/new. Level2+ only on every one of them, the whole screen and not just Save (require_write(), WRITE_ROLES = admin/level1/level2).

### ⚠ Template/server drift — verify before trusting this page [Ver 2, 2026-09-06] The deployed app/templates/ro_new.html is newer than the deployed app/ro_import.py (checked 2026-09-06 against the live box). The template's header field is Class (<select id="roClass"> populated from a classes context variable), it calls GET /ro/new/job-class, and it posts ajax=1 expecting a JSON reply. None of those three exist server-side yet: - manual_ro_form() passes ro_types only — no classes — so the Class dropdown renders with the (choose) option and nothing else until that context is added; - there is no /ro/new/job-class route, so the class-from-job lookup 404s and the hint falls through to "No class on that job yet — choose one."; - POST /ro/new still returns RedirectResponse(303), so the template's res.json() parse fails and the green "saved" panel renders RO undefined saved.

>

The server contract documented below is what ro_import.py actually does today. The parent thread owns deploying the matching ro_import.py (class list from im2.setting_classes(), a job-class route, and an ajax JSON response); this doc must be re-checked the moment that lands.

Class list (the 2026-09-04/05 change)

The four RO classes are not a ro_class table. They live in app_settings under the ro_classes key (im2.py SETTING_DEFS, rendered as a textarea on Settings, one Class = QuickBooks class id per line; default Administration = 1202535, Commercial = 1202520, Residential = 1202519, Service = 1202540). im2.setting_classes() parses it into [{"name","id"}], sorted case-insensitively, de-duplicated by name; a line with no id is still offered but can carry no class onto a QuickBooks posting. Classes not on that list are ignored. Separately, ro_header.ro_type still has its own CHECK (Residential|Commercial|Service|Truck) and RO_TYPES is that list — Type and Class are different fields, and POST /ro/new currently validates the posted value against RO_TYPES, not against the class list.

SKU lookup / search

GET /ro/new/item/{sku}lookup_item(sku): exact, case-insensitive, whitespace-tolerant match on IM2 items where archived_at is null, returning sku, item_name, description_po, uom, qty_on_hand, primary_bin, else 404. GET /ro/new/item-search?q=search_items(): ILIKE across sku/item_name/description_po, min 2 characters, exact-SKU match first, limit 10. Both are best-effort: a failure returns an empty result set (or 502 on the single-SKU route), never a 500.

Job search

GET /ro/new/job-search?q=search_jobs(): FieldPulse (search param on /v2.5/job, customer name attached via withCustomfieldsAndRelatedData) and QuickBooks (ro_pick.search_qb_jobs) run in parallel in a ThreadPoolExecutor, each cached for 120 s (_JOB_SEARCH_TTL, _cached(), errors never cached) — Dave 2026-09-06 called the uncached serial version "very slow". BuilderTrend is deliberately not searched (a live browser session, ~30 s). Result rows: {source: FP|QB, id, fp_job_id, name, detail}.

Save (POST /ro/new)

Form fields: job_name, bom_name, ro_type, date_required, notes, plus index-aligned repeated sku / description / qty / uom / line_type. (The live template additionally posts class_ref, job_source, job_source_id, qb_customer_id and ajax — all currently ignored by the handler.)

  1. Validates job_name, bom_name non-blank and ro_type in RO_TYPES (400 otherwise); date_required parses to None if blank or unparseable (never defaulted — Ver 3 ruling C.2).
  2. Skips rows with neither SKU nor description; a non-numeric qty is a 400 naming the line number; blank qty becomes 0. line_type anything but NONINV normalises to INV.
  3. Refuses (400) an INV line with no SKU, and a NONINV line with neither SKU nor description.
  4. Inserts one ro_header (source_system/source_id left NULL) then one ro_detail per line with origin='Manual', qty_source=qty, description falling back to the item master's description_po/item_name/the SKU/"(no description)".
  5. A SKU that does not resolve → line saved as NONINV, typed SKU moved into src_ref (never discarded), plus an open ro_issue (issue_type='SKU not in QB') so it lands on Issues to Resolve with the import-parser issues. A raise from lookup_item is treated exactly like "not found".
  6. One ro_audit row (action='manual_ro_create', note "created manually, N line(s)"), then 303 redirect to /ro/{order_no}?created=1 (which shows the green "created manually" banner on the Requirements Order screen).

Not implemented