feat(fusion_plating): partial order handling on the shop floor
Operators can now see and advance a job's parts across multiple stages
at once (e.g. 10 Masking / 20 Plating / 20 Baking on one 50-part job).
Tracking model C (fluid per-stage quantities + existing hold/scrap/
rework records for exceptions); board option 2 (a card per occupied
stage); wait-to-reconverge close. Additive only — no new model, no
migration, no change to the close/cert/ship lifecycle.
Board (fusion_plating_shopfloor/controllers/plant_kanban.py):
- One card PER (job, stage), composite key "{job_id}:{area}". Unsplit
jobs render exactly as before. _job_presences/_render_presence;
primary presence keeps full job card_state, secondary presences
derive state from their focus step.
Card (plant_card.js/.xml/.scss):
- "20 of 50 here" badge; tap opens the workspace focused on that
stage's step (focus_step_id, already accepted by the workspace).
Move + light-up (move_controller.py, fusion_plating_jobs/fp_job_step.py):
- Availability/pre-fill now from qty_at_step (step had no qty_done/
qty_scrapped fields — the old read was always 0, dead path).
- Forward move auto-flips destination pending->ready (no auto-start;
labour timer stays explicit) and auto-finishes a drained source
(best-effort). Predecessor gate is qty-aware: a step with real
arrived parts is startable regardless of upstream completion
(_fp_has_real_incoming, single source of truth for can_start /
blocker / button_start / move blockers).
Operator advance (job_workspace.js):
- "Send -> <next>" action on in_progress/paused steps opens the slimmed
Move dialog (qty steppers, no keyboard; advanced fields collapsed).
Was only wired into the deprecated shopfloor_tablet before.
Close (fp_job.py):
- button_mark_done counts move-based scrap (_fp_scrapped_via_moves) into
qty_scrapped and derives qty_done = qty - scrapped (was blindly
= job.qty, over-counting). Reconciliation gate unchanged.
Static-validated: pyflakes (py), lxml parse (xml), node --check (js).
Dynamic tests + browser check need an installed env (entech/trial) —
plating modules can't install on the local Community DB.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2074,11 +2074,27 @@ class FpJob(models.Model):
|
||||
# the operator reconciles by hand. Mirrors the receiving
|
||||
# `_update_job_qty_received` pattern: server fills the
|
||||
# obvious default, operator owns the edge cases.
|
||||
if (not job.qty_done and not job.qty_scrapped
|
||||
# Partial-order handling (2026-06-02): surface scrap that
|
||||
# was recorded through the Move log (transfer_type='scrap')
|
||||
# into qty_scrapped, so the reconciliation + cert qty stay
|
||||
# honest even when scrap was done from the tablet Move
|
||||
# dialog rather than the qty_scrapped field. Only when the
|
||||
# field hasn't been set by hand.
|
||||
scrap_moves = job._fp_scrapped_via_moves()
|
||||
if scrap_moves and not job.qty_scrapped:
|
||||
job.qty_scrapped = scrap_moves
|
||||
# Clean-close auto-fill: derive the good (done) count from
|
||||
# what physically came in minus scrap, instead of blindly
|
||||
# assuming the whole order completed (which over-counts when
|
||||
# parts were scrapped mid-line). Skips when the operator
|
||||
# already typed qty_done, or when visual rejects make the
|
||||
# split non-obvious — then the gate below makes them
|
||||
# reconcile by hand.
|
||||
if (not job.qty_done
|
||||
and not (job.qty_visual_inspection_rejects or 0)
|
||||
and job.qty_received
|
||||
and abs(job.qty_received - job.qty) < 0.0001):
|
||||
job.qty_done = job.qty
|
||||
job.qty_done = job.qty - (job.qty_scrapped or 0)
|
||||
accounted = (job.qty_done or 0) + (job.qty_scrapped or 0)
|
||||
if abs(accounted - job.qty) > 0.0001:
|
||||
raise UserError(_(
|
||||
@@ -2439,6 +2455,19 @@ class FpJob(models.Model):
|
||||
fp_skip_step_gate=True,
|
||||
).button_mark_done()
|
||||
|
||||
def _fp_scrapped_via_moves(self):
|
||||
"""Total parts scrapped through the Move log (transfer_type=
|
||||
'scrap') for this job. Lets button_mark_done's reconciliation
|
||||
count scrap done via the tablet Move dialog, not just the
|
||||
qty_scrapped field (partial-order handling, 2026-06-02)."""
|
||||
self.ensure_one()
|
||||
Move = self.env['fp.job.step.move']
|
||||
moves = Move.sudo().search([
|
||||
('job_id', '=', self.id),
|
||||
('transfer_type', '=', 'scrap'),
|
||||
])
|
||||
return int(sum(m.qty_moved or 0 for m in moves))
|
||||
|
||||
def _fp_check_advance_post_shop(self):
|
||||
"""Auto-advance in_progress jobs whose recipe steps are all
|
||||
terminal. Called from fp.job.step.button_finish post-super().
|
||||
|
||||
Reference in New Issue
Block a user