Create PO — Technical Spec
1. Files
app/po_create.py (router, /po prefix, mounted in im2.py after po_rel). templates/po_create.html. Screen key po_create is in screen_names and po_tile_screens (base.html). The chip is in _tiles.html po_tiles. The menu card is in its own row under Purchasing in menu.html.
2. Routes (all require action po_create; defaults admin/level1/level2)
| Method | Path | Purpose |
|---|---|---|
| GET | /po/create | screen + last 10 rows of po_create_log |
| POST | /po/create/parse | JSON {text} → {header, lines, edits, unparsed} |
| GET | /po/create/vendor-search, item-search, item/{sku}, job-search, job-class | typeaheads; job-class = ro_import.class_for_job('QB', id) |
| POST | /po/create/submit | validate → write one QB PurchaseOrder |
3. Speech
Speech recognition runs in the browser (Web Speech API, Chrome/Edge, en-US, continuous). No audio reaches the server.
4. Parser
Deterministic: number words → digits; header keywords (vendor/supplier, date/issue date/dated, job/project/customer, class, memo/note); lines split at "next"/"then"/sentence/", <n>"/" and <n>". Line = qty, optional UOM word, price ("at/@/for $x", "total" ÷ qty), and the remaining text. Matching:
- Vendor: qb_vendor, fuzzy. ok when the top score ≥0.9 and leads by 0.05.
- Item: IM2 active items with a qb_item_id; exact SKU or mfr part no = 1.0, compact part no inside the name = 0.94, plus token and difflib scores. ok when ≥0.9 and leads by 0.04. If no confident match, the line becomes a Non-SKU Material line with candidates.
- Job: ro_pick.search_qb_jobs.
- Keyword lines: subcontract → 2831, rental → 4294, permit → 4273, shipping/freight → 4067.
5. Write
Number from po_rel._next_po_number (shared 60000 series). The po_create_log row (ro DB, unique token) is inserted as Pending before the POST, then set to Created or Failed. A Created token returns the existing PO; a Pending token blocks a retry; a Failed token retries under the same number. Payload follows po_rel: APAccount 588, NotBillable, tax NON, ShipAddr from po_ship_to, PrivateNote = memo + tag. INV lines: ItemRef = items.qb_item_id, no ClassRef, CustomerRef only if the job_on_inventory box is ticked. NONINV lines: CustomerRef + ClassRef required. Not gated by allow_auto_po (Dave: straight to QB). QB PO lines reach qb_po_line through the normal PO sync.