← Main Menu All manuals Operator Guide for this screen IM2

Truck Pick — Technical Spec

Truck Pick (handheld) + Truck Pick Full Screen (PC)
Technical Spec Ver 1 Updated 2026-09-20 Matches the live screen

1. Routes

app/ro_pick.py, reuses Pick Item's session tables and save/lookup endpoints unchanged. Only the find step and the post dispatch are new.

MethodPathPurposePermission
GET/ro/pick-truck?sku=&saved=Handheld (ro_pick_truck.html)signed in, Save gated by can_write
GET/ro/pick-truck-full?sku=&saved=Desktop (ro_pick_truck_full.html)same
POST/ro/pick-truck/findResolve job (RO/FP/customer text or qb_customer_id+qb_customer_name) AND truck_customer_id; stamps both onto the ro_pick rowrequire_write
GET/ro/trucks/suggest?q=Truck # typeahead — SA Vehicles sub-jobs onlysigned in
POST/ro/pick-item/save, /save-batch, GET /ro/pick-item/lookupShared, unchangedrequire_write / signed in
POST/ro/pick/leave, /ro/pick/{pick_id}/classShared, unchanged (redirect_to whitelist includes /ro/pick-truck[-full])require_write

Truck picks are always no-RO (ro_pick.order_no IS NULL) — pick-truck/find never opens an RO session's truck fields; if job_number resolves to an RO, get_or_open_pick is used and the truck columns are stamped onto that RO-backed pick row too (allowed, but not the expected path).

2. Data model

ro_pick gained two nullable columns (migration run 2026-09-20, no DDL file — ad hoc ALTER TABLE via the RO_DSN connection):

``sql ALTER TABLE ro_pick ADD COLUMN truck_customer_id text; ALTER TABLE ro_pick ADD COLUMN truck_customer_name text; ``

A pick is a "Truck pick" purely by truck_customer_id IS NOT NULL — no separate type/flag column. load_session() in ro_post.py already SELECT p.* so both columns are available for free.

Trucks are QuickBooks sub-jobs under customer SA Vehicles (Id 100000431), seeded 2026-09-20 with the 11 active vehicles; Notes carries the description ("2026 Ford F-250"). search_trucks() / resolve_truck() in ro_pick.py match on FullyQualifiedName LIKE 'SA Vehicles:%' — QuickBooks' query language rejects filtering on ParentRef directly (QueryValidationError), same constraint search_qb_jobs() already worked around for the general job typeahead. resolve_truck() re-checks the client-supplied id server-side before it is trusted (never assume the client's typeahead selection wasn't tampered with).

3. Posting (ro_post.py::post_truck)

Dispatched from post_pick_to_qb() in ro_pick.py, which reads truck_customer_id off the pick row before choosing post() (normal picks) vs post_truck().

Because the truck's "stock" is never really on hand, every touched line becomes a mirrored pair of $0 QuickBooks documents instead of one document against a single customer:

Line qtyTruck-side documentJob-side document
> 0 (issued)CreditMemo on truck_customer_idInvoice on qb_customer_id
< 0 (return)Invoice on truck_customer_idInvoice→CreditMemo swap: CreditMemo on qb_customer_id

Posting order per pair is truck-side document first. If the job-side document then fails, the truck-side document is deleted via qbo.delete_doc() before the error is raised — mirrors the existing CM-before-invoice rollback rule in post() (Dave, 2026-09-06) so nothing is ever left half-posted across two customers. ro_txn rows are tagged source='Workbench-Truck' so truck movements are distinguishable from ordinary pick movements in the ledger; the unique index (qb_doc_type, qb_doc_id, sku) is respected because every document has its own qb_doc_id.

zeroed/surplus handling is unchanged from post() (truck picks have no ro_detail row to update in practice, since they are always no-RO, but the code path is identical for parity).

4. Review screen

_pending_pick_rows() and the posted-status query in picks_review_screen() both select p.truck_customer_name; ro_picks.html renders a small "TRUCK <name>" tag ahead of the job name when it is set.

5. Truck-tech menu

Static page /var/www/im2docs/truck.html, served by nginx at https://logistics.solalt.com/truck (location = /truck added to /etc/nginx/sites-enabled/im2, alongside the existing /handheld alias). Lists only Truck Pick and Rush Material Request — deliberately not merged into the warehouse handheld.html (Dave, 2026-09-20: "The techs in trucks will need their own menu with just this truck pick and the rush material request options").

6. Verified 2026-09-20

search_trucks/resolve_truck tested live against QuickBooks (11 trucks resolve, bogus id rejected). post_truck(dry_run=True) tested against a temporary ro_pick/ro_pick_line fixture (inserted and deleted directly in the ro database, never posted to QuickBooks) — correctly classified 1 issued line. End-to-end /ro/pick-truck/find → /ro/pick-truck tested authenticated as dcantin, confirmed the job bar reads "Truck HT6 · SA:Admin" and the ro_pick row carries both customer ids; test session deleted afterward (no lines were saved). Live QuickBooks posting itself (post_truck without dry_run) has NOT been exercised against production — first real Truck pick should be watched end-to-end before relying on it unattended.