Files
Odoo-Modules/fusion_plating/docs/superpowers/specs/2026-06-03-racking-multi-rack-wo-grouping-design.md
gsinghpal acd1fc9f8f docs(fusion_plating): racking multi-rack + WO grouping design spec & Phase 1 plan
Approved design for splitting a WO's parts across multiple racks + grouping
multiple WOs on one rack, plus the Phase 1 implementation plan (split +
independent movement). Phases 2 (grouping + Station screen) and 3 (Plant
Kanban rollup) are noted for follow-up plans.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-03 08:37:18 -04:00

11 KiB
Raw Blame History

Multi-Rack Splitting + Work-Order Grouping at Racking — Design

Date: 2026-06-03 Status: Approved (design sign-off 2026-06-03) Modules touched: fusion_plating (core: rack-load models), fusion_plating_jobs (movement / partial-order integration), fusion_plating_shopfloor (UI surfaces + controllers), fusion_plating_reports (rack travel ticket reuse)

1. Problem / Goal

At the Racking step, operators load a job's parts onto physical racks before plating. Today a step links to exactly one rack (fp.job.step.rack_id, single Many2one) and there is no model for partial parts-per-rack or multiple work orders sharing a rack. Operators need to:

  1. Split a job across multiple racks. Default: all parts on one rack. An "+ Add Rack" button divides the quantity equally (100 → 50/50 → 34/33/33 → 25×4…). The operator can then manually override any individual rack's quantity.
  2. Move racks independently through the rest of the line (Plating → Baking → De-Racking) — partial-order flow, but rack-aware. The operator chooses which rack(s) advance.
  3. Group multiple work orders on one rack when they run the identical recipe + spec (any customer), for line efficiency — e.g. WO-A (20 ENP parts) + WO-B (10 ENP parts) on one rack, processed together, then separated at De-Racking.

2. Locked Decisions (from brainstorm 2026-06-03)

# Decision
D1 Rack movement = independent, operator's choice. Each rack is its own trackable unit; it can move ahead on its own, or the operator can move several at once.
D2 Grouping eligibility = identical process + spec. Only WOs with the same resolved recipe AND same coating spec / thickness target may share a rack. Different customers are allowed. Mismatched recipe/spec is blocked.
D3 Two UI surfaces. (a) A per-WO Racking panel on the Job Workspace (the split case). (b) A dedicated Racking Station shop-floor screen listing all WOs at Racking, with split controls and cross-WO grouping. Both drive the same model + endpoints.
D4 Division remainder goes to the first rack(s): base = total // N, the first total % N racks get base + 1. Total always equals the parts available.
D5 Capacity = soft warning. Each rack shows assigned / capacity; over-capacity is an amber warning, never a hard block.
D6 Plant Kanban = one card per job with a small rack rollup ("3 racks · 1 Baking, 2 Plating"). The job card sits in the column of its least-advanced rack-load (a WO isn't "done" until every rack clears). Per-rack detail lives on the Racking screen / a card drill-down — NOT as separate board cards.

3. Data Model

3.1 fp.rack.load (new, in fusion_plating)

"Parts loaded on one physical rack." First-class, moves through the workflow independently.

Field Type Notes
name Char Sequence RACKLOAD/YYYY/NNNN
rack_id Many2one fusion.plating.rack The physical rack
line_ids One2many fp.rack.load.line (inverse load_id) Per-WO allocation (1 line = single WO; 2+ = grouped)
qty_total Integer (compute, stored) sum(line_ids.qty)
recipe_id Many2one (recipe ref) The shared recipe (all lines must match) — for grouping eligibility + display
spec_key Char (compute, stored) Normalised spec/thickness signature used to enforce D2 grouping
current_step_id Many2one fp.job.step The step the rack-load is parked at (drives independent position)
current_area_kind Char (compute, stored) From current_step_id.area_kind — for the Plant Kanban column
state Selection loadingloadedrunningunracked (→ cancelled)
tag_ids Many2many fp.rack.tag Reuse existing rack tags (Rush / Hold for QC)
company_id Many2one Standard
chatter mail.thread Audit

Constraints: a rack-load's line_ids must all share recipe_id + spec_key (D2); qty_total must be ≥ 1; rack_id unique among non-unracked loads (a physical rack holds one active load at a time).

3.2 fp.rack.load.line (new, in fusion_plating)

Field Type Notes
load_id Many2one fp.rack.load, required, ondelete cascade
job_id Many2one fp.job, required The work order whose parts are on this rack
qty Integer, required Parts of this job on this rack
part_catalog_id Many2one (related from job) Display
recipe_id / spec_key related/compute from job Used to enforce D2

3.3 Job ↔ rack-load relationships (on fp.job, in fusion_plating_jobs)

  • rack_load_line_ids (One2many to fp.rack.load.line) — all loads carrying this job's parts.
  • qty_racked (compute) = sum of this job's load-line qtys — how many of the job's parts are on racks.
  • qty_unracked (compute) = qty_at_racking_step qty_racked — parts not yet assigned to a rack (the "Unassigned" counter).

4. Division Math (the "+ Add Rack" behaviour)

  • Default state: 1 rack-load, line.qty = full racking quantity.
  • + Add Rack → create one more rack-load and re-divide equally across all current loads (D4): base = total // N; first total % N loads get base + 1. This overwrites all line qtys (the simple behaviour: "add 4th rack → divide by 4").
  • Divide Equally button → same as above without adding a rack (re-balance current N).
  • Manual qty edit on a rack → updates that load's line qty; the Unassigned: N counter recomputes (total Σ assigned). Manual edits persist until the next Add Rack / Divide Equally. Sum may not exceed total (validation). Sum < total is allowed (operator may rack in waves) and shown as Unassigned.
  • Remove Rack → only when its load hasn't moved past Racking; its qty returns to Unassigned.

5. Independent Movement + Partial-Order Integration

  • Movement reuses the existing move log fp.job.step.move. When a rack-load advances from step A → B, create one move row per line (per job): from_step_id, to_step_id, qty_moved = line.qty, rack_id = load.rack_id, transfer_type = 'step'. This keeps the existing qty_at_step partial-order compute correct and rack-aware.
  • The rack-load's current_step_id is set to the destination on commit (explicit position for the independent-movement UI), and state flips loaded → running.
  • The operator can move one load or select several to move together (D1). Reuse / extend the existing Move Rack tablet dialog (move_rack_dialog.js + /fp/tablet/move_rack/*) so a rack-load moves as a unit; the multi-select batch move is a thin wrapper.
  • De-Racking = unrack. When a rack-load reaches the De-Racking step and is unracked: set state = unracked, free the physical rack (rack.racking_state = 'empty'), and each line's qty returns to its own job's downstream flow (inspection → cert → shipping). Grouped WOs separate cleanly here — each job continues with its own parts/qty.

6. Work-Order Grouping (D2)

  • On the Racking Station screen, eligible WOs at Racking (same recipe_id + spec_key, any customer) can be pulled onto a shared rack-load → adds a fp.rack.load.line for the second job.
  • Eligibility is enforced server-side: adding a line whose job's recipe/spec differs from the load's is rejected with a clear message.
  • A grouped rack-load moves as one unit (§5); at De-Racking each line returns to its job (§5).

7. UI Surfaces

7.1 Job Workspace → Racking panel (per-WO) — fusion_plating_shopfloor

  • Appears on the Job Workspace when the WO is at the Racking step (mirrors the existing Receiving card pattern).
  • Shows: total parts, Unassigned: N, a list of rack-loads each with [rack picker] [qty input] [assigned/capacity bar] [remove], + Add Rack and Divide Equally buttons.
  • Split / qty-edit only (single WO). Grouping is not done here.

7.2 Racking Station screen (new) — fusion_plating_shopfloor

  • New OWL client action + menu under Shop Floor.
  • Lists all WOs currently at the Racking step (grouped by recipe/spec for grouping visibility).
  • Per-WO split controls (same as 7.1) plus "Combine onto rack" to pull an eligible WO onto another's rack-load.
  • Shows rack capacity bars + over-capacity warnings.

7.3 Shared controller endpoints — fusion_plating_shopfloor/controllers

  • /fp/racking/load (GET context for a WO or the station)
  • /fp/racking/add_rack / divide_equally / set_qty / remove_rack
  • /fp/racking/assign_rack (pick/scan the physical rack for a load — reuse /rack/list_empty + /rack/scan_qr)
  • /fp/racking/group (add an eligible WO's line to a load) / ungroup
  • /fp/racking/move (advance one or more rack-loads to the next step — wraps the move-log writes) All run as request.env.user (the technician) reusing existing rack/move ACLs.

8. Plant Kanban Representation (D6)

  • One card per job. Card column = area of the job's least-advanced rack-load (min over rack_load_line_ids.load_id.current_area_kind by column sequence), falling back to today's active_step_id.area_kind when the job has no rack-loads.
  • Card shows a compact rack rollup chip ("3 racks · 1 Baking, 2 Plating"). Tapping the chip / card opens a per-rack drill-down (or routes to the Racking screen).
  • No new board columns; no per-rack board cards.

9. Phasing (single spec, built in order)

  1. Phase 1 — Split + independent movement. fp.rack.load + fp.rack.load.line, division math, move-log integration, De-Racking unrack, Job Workspace Racking panel. Single-WO only (one line per load).
  2. Phase 2 — WO grouping + Racking Station screen. Multi-line loads, eligibility enforcement, the dedicated cross-WO surface.
  3. Phase 3 — Plant Kanban rollup + drill-down.

10. Integration Points / Reuse

  • fusion.plating.rack (capacity, racking_state, tags) — reused; rack-load references it.
  • fp.job.step.move / qty_at_step partial-order compute — reused, now rack-aware.
  • move_rack_dialog.js + /fp/tablet/move_rack/* + /rack/list_empty + /rack/scan_qr — reused/extended.
  • Rack Travel Ticket PDF (report_fp_rack_travel) — reused (print a load's ticket).
  • _fp_is_racking_step / racking inspection gate — unchanged; rack-loads are created at the racking step.

11. Edge Cases / Rules

  • Sum of load qtys may be < total (rack in waves); the remainder shows as Unassigned and can be racked later.
  • A load can't be removed/edited once it has moved past Racking.
  • One physical rack = one active (non-unracked) load at a time.
  • Over-capacity = soft amber warning only.
  • Cancelling a job cascades its load lines; a load with no remaining lines is cancelled.
  • Migration: existing single fp.job.step.rack_id assignments are left as-is (legacy); new flow uses rack-loads. No destructive backfill.

12. Out of Scope (this spec)

  • Auto-suggesting which WOs to group (operator-driven only).
  • Rack capacity planning/optimisation.
  • Changing the De-Racking inspection model.
  • Reworking the legacy rack_id-on-step flow (kept for back-compat).