fix(fusion_plating_jobs): gating steps fall forward to next stage's column
A "Ready for X" gating step (fp.step.kind code='gating') maps to area_kind='receiving' in the taxonomy. For a MID-recipe gate (e.g. "Ready for processing" between Racking and Plating) that snapped the job's card back to the far-left Receiving column when work advanced into it — the job looked like it vanished from the board. _compute_area_kind now detects gating via the stable kind code and resolves a gating step's column to the NEXT non-gating step's area (so "Ready for processing" shows in Plating), keeping cards flowing left→right. Falls back to the last real stage for a trailing gate. Non-gating steps unchanged. Helpers: _fp_is_gating_step / _fp_raw_area_kind (no recursion) / _fp_resolve_area_kind. area_kind is a stored compute — recomputed all 537 live steps on entech. Verified: WO-30061 "Ready for processing" area receiving→plating, card now renders in the Plating column. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -630,8 +630,27 @@ De-Racking → Final inspection → Shipping`
|
|||||||
Columns are first-class — they always render in this exact order, never
|
Columns are first-class — they always render in this exact order, never
|
||||||
reorder, never collapse when empty. Driven by `fp.work.centre.area_kind`
|
reorder, never collapse when empty. Driven by `fp.work.centre.area_kind`
|
||||||
Selection (added 2026-05-23). Each `fp.job.step.area_kind` is computed
|
Selection (added 2026-05-23). Each `fp.job.step.area_kind` is computed
|
||||||
(stored) from `work_centre.area_kind` with a fallback to a step-kind
|
(stored) in `_compute_area_kind` (`fusion_plating_jobs/models/fp_job_step.py`):
|
||||||
dispatch table (`_STEP_KIND_TO_AREA` in `fusion_plating_jobs/models/fp_job_step.py`).
|
`work_centre.area_kind` → else `recipe_node.kind_id.area_kind` (the
|
||||||
|
`fp.step.kind` taxonomy is authoritative; the legacy `_STEP_KIND_TO_AREA`
|
||||||
|
dict is gone) → else catch-all `'plating'`.
|
||||||
|
|
||||||
|
**Gating/"Ready for X" marker steps fall FORWARD (fixed 2026-06-02).** The
|
||||||
|
`fp.step.kind` named *Gating* has `code='gating'` **and `area_kind='receiving'`**.
|
||||||
|
A gating step is a non-physical "ready for the next stage" marker, so
|
||||||
|
mapping it to Receiving made a *mid-recipe* gate snap the job's card back
|
||||||
|
to the first column (Racking → "Ready for processing" jumped to Receiving,
|
||||||
|
so the job looked like it vanished). `_compute_area_kind` therefore detects
|
||||||
|
a gating step via the **stable `kind_id.code == 'gating'`** (never the
|
||||||
|
display name) and resolves its column to the **next non-gating step's** raw
|
||||||
|
area (so "Ready for processing" before plating shows in the **Plating**
|
||||||
|
column); if nothing real follows, it falls back to the last real stage.
|
||||||
|
Helpers: `_fp_is_gating_step`, `_fp_raw_area_kind` (own work_centre/kind
|
||||||
|
only — no look-ahead, avoids recursion), `_fp_resolve_area_kind`. **NB:**
|
||||||
|
`area_kind` is a STORED compute, so after changing this logic you must
|
||||||
|
force-recompute existing rows (`env['fp.job.step'].search([])._compute_area_kind()`
|
||||||
|
+ `flush_recordset(['area_kind'])` + commit) — a `-u`/restart alone leaves
|
||||||
|
old values stale.
|
||||||
|
|
||||||
**Spec D3:** all wet-line steps (Soak Clean, Electroclean, Acid Dip,
|
**Spec D3:** all wet-line steps (Soak Clean, Electroclean, Acid Dip,
|
||||||
Etch, Desmut, Zincate, Rinse, E-Nickel, Chrome, Anodize, Black Oxide,
|
Etch, Desmut, Zincate, Rinse, E-Nickel, Chrome, Anodize, Black Oxide,
|
||||||
|
|||||||
@@ -157,33 +157,71 @@ class FpJobStep(models.Model):
|
|||||||
@api.depends(
|
@api.depends(
|
||||||
'work_centre_id.area_kind',
|
'work_centre_id.area_kind',
|
||||||
'recipe_node_id.kind_id.area_kind',
|
'recipe_node_id.kind_id.area_kind',
|
||||||
|
'recipe_node_id.kind_id.code',
|
||||||
|
'sequence',
|
||||||
|
'job_id.step_ids.sequence',
|
||||||
|
'job_id.step_ids.work_centre_id.area_kind',
|
||||||
|
'job_id.step_ids.recipe_node_id.kind_id.area_kind',
|
||||||
|
'job_id.step_ids.recipe_node_id.kind_id.code',
|
||||||
)
|
)
|
||||||
def _compute_area_kind(self):
|
def _compute_area_kind(self):
|
||||||
"""Resolve the plant-view column this step belongs in.
|
"""Resolve the plant-view column this step belongs in.
|
||||||
|
|
||||||
Priority chain:
|
Priority chain (non-gating steps):
|
||||||
1. work_centre.area_kind (explicit operator setup wins)
|
1. work_centre.area_kind (explicit operator setup wins)
|
||||||
2. recipe_node.kind_id.area_kind (kind taxonomy authoritative)
|
2. recipe_node.kind_id.area_kind (kind taxonomy authoritative)
|
||||||
3. catch-all 'plating' (data integrity issue if we land here)
|
3. catch-all 'plating' (data integrity issue if we land here)
|
||||||
|
|
||||||
The legacy _STEP_KIND_TO_AREA dict was removed — fp.step.kind
|
Gating/marker steps (kind `code == 'gating'` — the "Ready for X"
|
||||||
now self-declares its area_kind, so the kind taxonomy IS the
|
steps) have NO physical location; the taxonomy maps them to
|
||||||
source of truth. See spec
|
'receiving', which made a mid-recipe gate snap the job's card back
|
||||||
2026-05-24-shopfloor-live-step-fix-design.md Change 6.
|
to the first column (Racking -> "Ready for processing" jumped to
|
||||||
|
Receiving, so the job looked like it vanished — 2026-06-02). A
|
||||||
|
gating step now FALLS FORWARD to the next non-gating step's column
|
||||||
|
(it's "ready for [that stage]"), keeping the card moving
|
||||||
|
left->right. If nothing real follows, it falls back to the last
|
||||||
|
real stage.
|
||||||
"""
|
"""
|
||||||
for step in self:
|
for step in self:
|
||||||
# 1. Explicit work_centre wins
|
step.area_kind = step._fp_resolve_area_kind()
|
||||||
if step.work_centre_id and step.work_centre_id.area_kind:
|
|
||||||
step.area_kind = step.work_centre_id.area_kind
|
def _fp_raw_area_kind(self):
|
||||||
continue
|
"""Area from this step's OWN work_centre / kind only — no look-ahead
|
||||||
# 2. Kind taxonomy
|
and no dependence on the computed `area_kind` field (so the gating
|
||||||
node = step.recipe_node_id
|
fall-forward below can't recurse)."""
|
||||||
if node and node.kind_id and node.kind_id.area_kind:
|
self.ensure_one()
|
||||||
step.area_kind = node.kind_id.area_kind
|
if self.work_centre_id and self.work_centre_id.area_kind:
|
||||||
continue
|
return self.work_centre_id.area_kind
|
||||||
# 3. Catch-all — only reached for orphaned steps (no
|
node = self.recipe_node_id
|
||||||
# work_centre AND no recipe_node).
|
if node and node.kind_id and node.kind_id.area_kind:
|
||||||
step.area_kind = 'plating'
|
return node.kind_id.area_kind
|
||||||
|
return 'plating'
|
||||||
|
|
||||||
|
def _fp_is_gating_step(self):
|
||||||
|
"""True for a 'Ready for X' marker step (no physical location).
|
||||||
|
Detected via the STABLE kind code, never the display name."""
|
||||||
|
self.ensure_one()
|
||||||
|
node = self.recipe_node_id
|
||||||
|
return bool(node and node.kind_id and node.kind_id.code == 'gating')
|
||||||
|
|
||||||
|
def _fp_resolve_area_kind(self):
|
||||||
|
"""Column for this step: its own raw area, EXCEPT a gating marker
|
||||||
|
falls forward to the next non-gating step's column."""
|
||||||
|
self.ensure_one()
|
||||||
|
if not self._fp_is_gating_step():
|
||||||
|
return self._fp_raw_area_kind()
|
||||||
|
siblings = self.job_id.step_ids
|
||||||
|
later = siblings.filtered(
|
||||||
|
lambda s: s.sequence > self.sequence and not s._fp_is_gating_step()
|
||||||
|
).sorted('sequence')
|
||||||
|
if later:
|
||||||
|
return later[0]._fp_raw_area_kind()
|
||||||
|
earlier = siblings.filtered(
|
||||||
|
lambda s: s.sequence < self.sequence and not s._fp_is_gating_step()
|
||||||
|
).sorted('sequence')
|
||||||
|
if earlier:
|
||||||
|
return earlier[-1]._fp_raw_area_kind()
|
||||||
|
return self._fp_raw_area_kind()
|
||||||
|
|
||||||
last_activity_at = fields.Datetime(
|
last_activity_at = fields.Datetime(
|
||||||
string='Last Activity',
|
string='Last Activity',
|
||||||
|
|||||||
Reference in New Issue
Block a user