feat(fusion_plating): box-level tracking (fp.box) + thermal job-sticker redesign
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>
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
# 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/<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 band `WORK ORDER <wo> | 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/<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.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/<id>`). 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/<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.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.
|
||||
Reference in New Issue
Block a user