feat(jobs+shopfloor): live-step priority chain + done-job filter
Fix the Shop Floor plant kanban so cards land in the right column: - fp.job._compute_active_step_id walks priority chain (in_progress > paused > ready > pending), not just in_progress - fp.job._compute_card_state edge case respects job.state='done' (no more bogus 'contract_review' label on done jobs) - fp.job.step._compute_area_kind reads kind.area_kind directly; legacy _STEP_KIND_TO_AREA dict removed (50+ lines deleted) - /fp/landing/plant_kanban filters out done/cancelled jobs from the live board Migration 19.0.10.25.0 backfills template metadata (codes, descriptions, icons, kind_id) on 30 unfinished library templates and repoints recipe nodes for 6 unambiguous name patterns (Blasting -> blast, Ready For X -> gating, De-Masking -> demask, Scheduling -> gating, Nickel Strip -> wet_process, Pre-Meas/Check Sulfamate -> inspect). Battle test bt_s24_between_steps.py covers between-step routing, paused step lifecycle, and done-job board filter. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -257,13 +257,21 @@ class FpJob(models.Model):
|
||||
def _compute_card_state(self):
|
||||
"""Dispatch matching spec §6.2 / §9.3 explicit precedence list."""
|
||||
for job in self:
|
||||
# Edge: no active step (all pending or all done)
|
||||
# Edge: no live step (all steps done OR no steps at all).
|
||||
# - job.state='done' → 'done' (defensive — done jobs are
|
||||
# filtered off the Shop Floor board upstream, but the
|
||||
# field still needs a value).
|
||||
# - confirmed + parts not yet received → 'no_parts'.
|
||||
# - else → 'ready' (job awaiting work, no steps yet OR
|
||||
# recipe not assigned).
|
||||
if not job.active_step_id:
|
||||
if (job.state == 'confirmed'
|
||||
if job.state == 'done':
|
||||
job.card_state = 'done'
|
||||
elif (job.state == 'confirmed'
|
||||
and job._fp_inbound_not_received()):
|
||||
job.card_state = 'no_parts'
|
||||
else:
|
||||
job.card_state = 'contract_review'
|
||||
job.card_state = 'ready'
|
||||
continue
|
||||
|
||||
step = job.active_step_id
|
||||
@@ -384,11 +392,32 @@ class FpJob(models.Model):
|
||||
|
||||
@api.depends('step_ids.state', 'step_ids.sequence')
|
||||
def _compute_active_step_id(self):
|
||||
"""Pick the "live" step — first match by priority then sequence.
|
||||
|
||||
Priority order:
|
||||
in_progress > paused > ready > first pending
|
||||
|
||||
in_progress is the most informative (someone is actively
|
||||
working on it). paused means someone was working and stopped —
|
||||
the card belongs at that station so the next operator can
|
||||
pick it up. ready is the next-up step waiting for an operator.
|
||||
The first pending after a done step is the "next gate" —
|
||||
where the card visually waits between steps.
|
||||
|
||||
Returns False only when every step is `done` (job finished)
|
||||
or when there are no steps at all (recipe not assigned).
|
||||
|
||||
See spec 2026-05-24-shopfloor-live-step-fix-design.md Change 1.
|
||||
"""
|
||||
PRIORITY_STATES = ('in_progress', 'paused', 'ready', 'pending')
|
||||
for job in self:
|
||||
active = job.step_ids.filtered(
|
||||
lambda s: s.state == 'in_progress'
|
||||
).sorted('sequence')
|
||||
job.active_step_id = active[:1].id if active else False
|
||||
ordered = job.step_ids.sorted('sequence')
|
||||
live = job.env['fp.job.step']
|
||||
for state in PRIORITY_STATES:
|
||||
live = ordered.filtered(lambda s: s.state == state)
|
||||
if live:
|
||||
break
|
||||
job.active_step_id = live[:1].id if live else False
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Sub 14 — Configurable workflow state (status bar milestone)
|
||||
|
||||
@@ -17,60 +17,11 @@ from odoo.exceptions import UserError
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Mapping from fp.process.node.default_kind → fp.work.centre.area_kind.
|
||||
# Used as fallback by fp.job.step.area_kind compute when the step has no
|
||||
# work_centre_id or its work_centre has no area_kind set. Authoritative
|
||||
# source per the plant-view spec §4.2.
|
||||
# 2026-05-23 — Shop Floor plant-view redesign.
|
||||
_STEP_KIND_TO_AREA = {
|
||||
# Receiving (admin / pre-physical-work)
|
||||
'receiving': 'receiving',
|
||||
'incoming_inspection': 'receiving',
|
||||
'contract_review': 'receiving',
|
||||
'gating': 'receiving',
|
||||
'ready_for_processing': 'receiving',
|
||||
# Masking
|
||||
'masking': 'masking',
|
||||
# Blasting
|
||||
'blasting': 'blasting',
|
||||
'bead_blast': 'blasting',
|
||||
'media_blast': 'blasting',
|
||||
# Racking
|
||||
'racking': 'racking',
|
||||
# Plating (everything wet — rolled up into one column per spec D3)
|
||||
'soak_clean': 'plating',
|
||||
'electroclean': 'plating',
|
||||
'acid_dip': 'plating',
|
||||
'etch': 'plating',
|
||||
'desmut': 'plating',
|
||||
'zincate': 'plating',
|
||||
'rinse': 'plating',
|
||||
'water_break_test': 'plating',
|
||||
'activation': 'plating',
|
||||
'e_nickel_plate': 'plating',
|
||||
'chrome': 'plating',
|
||||
'anodize': 'plating',
|
||||
'black_oxide': 'plating',
|
||||
'drying': 'plating',
|
||||
# Baking
|
||||
'bake': 'baking',
|
||||
'oven_bake': 'baking',
|
||||
'post_bake_relief': 'baking',
|
||||
# De-Racking (folds in de-masking per spec D4)
|
||||
'de_rack': 'de_racking',
|
||||
'de_mask': 'de_racking',
|
||||
'unrack': 'de_racking',
|
||||
# Final inspection (post-plate inspection / FAIR / thickness QC)
|
||||
'inspection': 'inspection',
|
||||
'final_inspection': 'inspection',
|
||||
'post_plate_inspection':'inspection',
|
||||
'thickness_qc': 'inspection',
|
||||
'fair': 'inspection',
|
||||
'dimensional_check': 'inspection',
|
||||
# Shipping
|
||||
'shipping': 'shipping',
|
||||
'pack_ship': 'shipping',
|
||||
}
|
||||
# 2026-05-24 — Shop Floor live-step fix (19.0.10.24.0):
|
||||
# The legacy `_STEP_KIND_TO_AREA` dict that lived here was removed.
|
||||
# fp.step.kind now self-declares its area_kind, so the kind taxonomy
|
||||
# IS the source of truth for Shop Floor column routing.
|
||||
# See docs/superpowers/specs/2026-05-24-shopfloor-live-step-fix-design.md.
|
||||
|
||||
|
||||
class FpJobStep(models.Model):
|
||||
@@ -169,21 +120,40 @@ class FpJobStep(models.Model):
|
||||
store=True,
|
||||
index=True,
|
||||
help='Which Shop Floor column this step belongs to. Resolved as: '
|
||||
'(1) work_centre.area_kind if set; else (2) fallback to '
|
||||
'_STEP_KIND_TO_AREA[recipe_node.default_kind]; else (3) the '
|
||||
'safe catch-all "plating". Drives plant-view kanban grouping.',
|
||||
'(1) work_centre.area_kind if set; else (2) the area_kind on '
|
||||
'recipe_node.kind_id; else (3) the safe catch-all "plating". '
|
||||
'Drives plant-view kanban grouping.',
|
||||
)
|
||||
|
||||
@api.depends('work_centre_id.area_kind', 'recipe_node_id.default_kind')
|
||||
@api.depends(
|
||||
'work_centre_id.area_kind',
|
||||
'recipe_node_id.kind_id.area_kind',
|
||||
)
|
||||
def _compute_area_kind(self):
|
||||
"""Resolve the plant-view column this step belongs in.
|
||||
|
||||
Priority chain:
|
||||
1. work_centre.area_kind (explicit operator setup wins)
|
||||
2. recipe_node.kind_id.area_kind (kind taxonomy authoritative)
|
||||
3. catch-all 'plating' (data integrity issue if we land here)
|
||||
|
||||
The legacy _STEP_KIND_TO_AREA dict was removed — fp.step.kind
|
||||
now self-declares its area_kind, so the kind taxonomy IS the
|
||||
source of truth. See spec
|
||||
2026-05-24-shopfloor-live-step-fix-design.md Change 6.
|
||||
"""
|
||||
for step in self:
|
||||
# 1. Explicit work_centre wins
|
||||
if step.work_centre_id and step.work_centre_id.area_kind:
|
||||
step.area_kind = step.work_centre_id.area_kind
|
||||
continue
|
||||
kind = step.recipe_node_id.default_kind if step.recipe_node_id else False
|
||||
if kind and kind in _STEP_KIND_TO_AREA:
|
||||
step.area_kind = _STEP_KIND_TO_AREA[kind]
|
||||
# 2. Kind taxonomy
|
||||
node = step.recipe_node_id
|
||||
if node and node.kind_id and node.kind_id.area_kind:
|
||||
step.area_kind = node.kind_id.area_kind
|
||||
continue
|
||||
# 3. Catch-all — only reached for orphaned steps (no
|
||||
# work_centre AND no recipe_node).
|
||||
step.area_kind = 'plating'
|
||||
|
||||
last_activity_at = fields.Datetime(
|
||||
|
||||
Reference in New Issue
Block a user