# Box-Level Tracking + Job Sticker Redesign — Design Spec Date: 2026-06-03 Status: Approved (brainstormed with client), implementation in progress. ## Summary Two coupled deliverables: 1. **Job sticker redesign** (thermal-label-friendly, 6×4 in / 152×102 mm): - **Internal Job Sticker → Layout A** (stacked: identity band + full-width instructions), printed **one per job**. - **External Job Sticker → Layout B** (left identity rail + tall instructions column), printed **one per box**, carrying the **box identity** (BOX n/N) and a **per-box QR**. Shows the **factory logo** (`env.company.logo`). 2. **Box-level tracking**: a new `fp.box` registry, one record per received box, auto-created at receiving, with a status workflow and per-box scannable QR. ## Decisions (locked with client) | Q | Decision | |---|---| | Label size | Keep 6×4 in (152×102 mm). | | Redesign goals | Readability/scan-speed + thermal print quality (no grey fills — solid-black bands + knockout white text; thick rules; bold sans). | | Masking on label | **MASK badge** (on/off flag) when `sale.order.line.x_fc_masking_enabled` is true. No detail text. | | Baking on label | **BAKE block** showing `sale.order.line.x_fc_bake_instructions` text, only when present. Also a BAKE flag for at-a-glance. | | Notes source | Internal = `x_fc_internal_description`; External = SO line `name` (customer-facing). | | Long notes | Notes-dominant zone, **length-tiered font shrink** to keep to **one label**, clip with "…see traveller" only in the extreme. | | Factory logo | On **External only** (header), from `env.company.logo` → `logo_web` → company partner image. Internal stays clean. Thermal caveat: prefer a mono/high-contrast logo. | | Box tracking depth | **Box registry** — per-box record, status, scannable QR. (Not box-contents.) | | Internal copies | **One per job.** | | External copies | **One per box.** | | Box QR | **Per-box** — encodes `/fp/box/`. | ## Label layouts (approved mockups) Both labels: outer 0.9 mm border, `overflow:hidden` single-page guard, dynamic blocks render only when their field has content. **Layout A (Internal, per job):** full-width stacked rows — `[logo | WO# band + INTERNAL tag | QR]` → `Part# + MASK/BAKE flags` → one-line field strip `Customer · PO · Qty · Due · Thk` → `BAKE` block → `NOTES` (full width, `x_fc_internal_description`, length-tiered, bottom padding). **Layout B (External, per box):** absolute two-column — - Left rail (50 mm): `logo` → black band `WORK ORDER | BOX n / N` → `MASK/BAKE` flags → per-box QR → `Part#` → `Customer` → `PO/Qty` → `Due/Thk`. - Right column: `BAKE` block → `NOTES` (customer description, length-tiered). - Full-height divider (rail `border-right`). CUSTOMER copy. Reference mockups (Chrome-rendered, true 6×4): `~/Downloads/fusion_sticker_concepts/Sticker-A-Internal-LongNotes.*`, `Sticker-B-External.*`. Final proof renders through entech wkhtmltopdf. ## `fp.box` model (fusion_plating_receiving) | Field | Type | Notes | |---|---|---| | `name` | Char | Sequence, e.g. `BOX//01`. | | `box_number` | Integer | n (1..N). | | `box_count` | Integer | N (related/snapshot of receiving `box_count_in`). | | `receiving_id` | M2O `fp.receiving` | Origin. ondelete cascade. | | `sale_order_id` | M2O `sale.order` | Related from receiving. | | `job_id` | M2O `fp.job` | Resolved (single-job SO = that job; multi-job = first/SO-level, see edge cases). | | `partner_id` | M2O `res.partner` | Related (customer). | | `state` | Selection | `received → racked → in_process → packed → shipped` (+ `lost`/`cancelled`). | | `qr` | Binary/compute | Encodes `/fp/box/`. | | `location_note` | Char | Optional free text "where is it now". | | `scan_event_ids` | (phase 2) | Per-scan log — deferred. | Constraints: `(receiving_id, box_number)` unique. Append-only-ish; state advances. ## Auto-create at receiving When `fp.receiving.box_count_in = N` is set and the receiving is confirmed (state hook — reuse the existing box-count chatter point at `fp_receiving.py:~1191`), create/sync N `fp.box` rows (1..N), linked to the receiving + resolved job. **Idempotent**: changing N adds/removes trailing rows (never renumbers existing tracked boxes). Manager can regenerate. ## Scanning - Controller route `/fp/box/` → resolves the box, shows its job / status, allows advancing state (received→…→shipped). Tie into the existing shopfloor scan wedge (`request.env.user` attribution — no `tablet_tech_id`). - **Reconciliation**: helper flags a receiving/job whose boxes haven't all reached `shipped` (so none are lost — matches the "ship back in the same boxes" Sub-8 rule). ## Label binding - **External job sticker** (`fusion_plating_jobs.report_fp_job_sticker_template`): iterate the job's `fp.box` records → **one label per box** (Layout B), each with its `box_number/box_count` + per-box QR (`/fp/box/`). Replaces the current `range(box_count_in)` loop in `report_fp_wo_sticker_inner`. When a job has no `fp.box` rows yet, fall back to a single label (BOX 1/1). - **Internal job sticker** (`report_fp_job_sticker_internal_template`): **one per job** (Layout A), job QR (`/fp/job/`), no box loop. - Shared inner keeps the 100-label hard safety cap (defense-in-depth from the WO-30072 OOM fix). ## UI - Boxes list + kanban (group by `state`) under **Operations**; form with state buttons + scan QR. - Smart buttons: box count on `fp.receiving` and `fp.job` forms. ## Module placement - Model + auto-create + views/menu/ACL → `fusion_plating_receiving`. - Scan controller → `fusion_plating_receiving` (or shopfloor). - Label templates → `fusion_plating_jobs` (job stickers) + shared inner in `fusion_plating_reports`. ## Edge cases / open - **Multi-job SO** (one SO line → multiple jobs via serial/thickness grouping): boxes are physical (per shipment/receiving). MVP links a box to the SO's primary job; the external sticker prints the SO's boxes. Revisit if a real multi-job-per-box case appears. - **Box ↔ part for multi-part SO**: out of MVP (registry, not contents). - Per-box qty/contents = future "registry + contents" upgrade. ## Deploy / verify entech (LXC 111 / pve-worker5), `-u fusion_plating_receiving fusion_plating_jobs fusion_plating_reports` with the revert-on-failure guard. Verify: render both stickers for a real job through wkhtmltopdf; confirm auto-create on a test receiving; scan a box id.