Box registry: new fp.box model (fusion_plating_receiving), one record per received box, auto-created when a receiving is marked Counted (idempotent _fp_sync_boxes — grows/shrinks with box_count_in, never touches an advanced box). Status received -> racked -> in_process -> packed -> shipped, per-box scannable QR (/fp/box/<id> controller). Backfill migration for receivings counted before tracking shipped. Boxes list/kanban/form + receiving smart button. Job stickers redesigned (thermal label, 6x4 in / 152x102mm, mm layout @ paperformat dpi=96 so mm maps 1:1 in wkhtmltopdf — see rule 14): - Internal Job Sticker = Layout A, ONE per job (shop notes from x_fc_internal_description, job QR). - External Job Sticker = Layout B, ONE per fp.box (BOX n/N, per-box QR, factory company logo, customer-facing notes). Dynamic MASK badge (x_fc_masking_enabled) + BAKE block (x_fc_bake_instructions), length-tiered notes font. Display logic in fp.job._fp_sticker_data(). Also retains the SO/WO box-sticker MemoryError fix in report_fp_wo_sticker.xml (per-box loop sourced from fp.receiving.box_count_in + 100-label safety cap). Verified live on entech: 111 boxes backfilled (31 receivings), External renders one page per box, Internal one per job, scan endpoint 303->login. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.4 KiB
Box-Level Tracking + Job Sticker Redesign — Design Spec
Date: 2026-06-03 Status: Approved (brainstormed with client), implementation in progress.
Summary
Two coupled deliverables:
- 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).
- Box-level tracking: a new
fp.boxregistry, 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/<id>. |
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 bandWORK ORDER <wo> | BOX n / N→MASK/BAKEflags → per-box QR →Part#→Customer→PO/Qty→Due/Thk. - Right column:
BAKEblock →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/<wo-or-recv>/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 <base_url>/fp/box/<id>. |
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/<int:box_id>→ resolves the box, shows its job / status, allows advancing state (received→…→shipped). Tie into the existing shopfloor scan wedge (request.env.userattribution — notablet_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'sfp.boxrecords → one label per box (Layout B), each with itsbox_number/box_count+ per-box QR (/fp/box/<id>). Replaces the currentrange(box_count_in)loop inreport_fp_wo_sticker_inner. When a job has nofp.boxrows 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/<id>), 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.receivingandfp.jobforms.
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 infusion_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.