feat(plant_kanban): post-shop states visible on board (Tasks 9-13)
Controller (plant_kanban.py):
- Widen domain: state IN (confirmed, in_progress, awaiting_cert,
awaiting_ship). Done jobs still drop off.
- _resolve_card_area: state=awaiting_cert → 'inspection' column,
state=awaiting_ship → 'shipping' column. State drives column
regardless of recipe shape.
- _state_chip: 🏷️ Awaiting CoC (amber) + 📦 Ready to ship (green).
- _SORT_PRIORITY: awaiting_cert=3.5, awaiting_ship=8.5.
- KPI dict: awaiting_cert + awaiting_ship counts.
- Filter clauses for the two new chips.
Model (fp_job.py):
- _compute_card_state handles new states in BOTH branches: the
no-active-step early return (where awaiting_cert/ship cards
land — all steps terminal) AND the per-step branch (defensive).
- _compute_mini_timeline_json: awaiting_cert paints inspection
dot 'current'; awaiting_ship paints shipping dot 'current'.
All earlier dots show 'done'.
SCSS (_plant_tokens.scss + _plant_card.scss):
- New tokens for amber (cert) + green (ship), light + dark variants
via the existing $o-webclient-color-scheme compile-time branch.
- .state-awaiting_cert / .state-awaiting_ship modifier classes
match the existing border-left pattern.
XML (plant_kanban.xml):
- Two new KPI tiles + two new filter chips wired to the state
filter clauses.
Manifest: fusion_plating_shopfloor 19.0.33.2.0 → 19.0.34.0.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -287,6 +287,13 @@ class FpJob(models.Model):
|
||||
if not job.active_step_id:
|
||||
if job.state == 'done':
|
||||
job.card_state = 'done'
|
||||
elif job.state == 'awaiting_cert':
|
||||
# Spec 2026-05-25 — state drives card_state for
|
||||
# post-shop jobs (active_step_id is False because
|
||||
# every step is terminal).
|
||||
job.card_state = 'awaiting_cert'
|
||||
elif job.state == 'awaiting_ship':
|
||||
job.card_state = 'awaiting_ship'
|
||||
elif (job.state == 'confirmed'
|
||||
and job._fp_inbound_not_received()):
|
||||
job.card_state = 'no_parts'
|
||||
@@ -327,6 +334,17 @@ class FpJob(models.Model):
|
||||
and step._fp_is_idle(threshold_hours=8)):
|
||||
job.card_state = 'idle_warning'
|
||||
continue
|
||||
# Rule 7.5 — awaiting_cert + awaiting_ship (spec 2026-05-25)
|
||||
# State drives card_state regardless of step state. Inserted
|
||||
# BEFORE the done rule because state='done' jobs are filtered
|
||||
# off the board upstream so the done rule is unreachable
|
||||
# from cards we'd actually render.
|
||||
if job.state == 'awaiting_cert':
|
||||
job.card_state = 'awaiting_cert'
|
||||
continue
|
||||
if job.state == 'awaiting_ship':
|
||||
job.card_state = 'awaiting_ship'
|
||||
continue
|
||||
# Rule 8 — done
|
||||
if step.area_kind == 'shipping' and job.state == 'done':
|
||||
job.card_state = 'done'
|
||||
@@ -356,10 +374,50 @@ class FpJob(models.Model):
|
||||
'step_ids.area_kind',
|
||||
'active_step_id',
|
||||
'card_state',
|
||||
'state',
|
||||
)
|
||||
def _compute_mini_timeline_json(self):
|
||||
"""9-element JSON array, one per Shop Floor column."""
|
||||
"""9-element JSON array, one per Shop Floor column.
|
||||
|
||||
For awaiting_cert / awaiting_ship (spec 2026-05-25): the
|
||||
Final-inspection or Shipping dot renders as 'current' with the
|
||||
state-named variant; all earlier dots render 'done'. Lets the
|
||||
QM see at a glance "this card has cleared the line, just
|
||||
waiting on paperwork/shipping".
|
||||
"""
|
||||
for job in self:
|
||||
# Post-shop state override (spec 2026-05-25): visually walk
|
||||
# the card across the two right-most columns even though
|
||||
# the recipe may not have steps with those area_kinds.
|
||||
if job.state == 'awaiting_cert':
|
||||
timeline = []
|
||||
for area in _COLUMN_SEQUENCE:
|
||||
if area == 'inspection':
|
||||
timeline.append({
|
||||
'area': area,
|
||||
'state': 'current',
|
||||
'variant': 'awaiting_cert',
|
||||
})
|
||||
elif area == 'shipping':
|
||||
timeline.append({'area': area, 'state': 'upcoming'})
|
||||
else:
|
||||
timeline.append({'area': area, 'state': 'done'})
|
||||
job.mini_timeline_json = json.dumps(timeline)
|
||||
continue
|
||||
if job.state == 'awaiting_ship':
|
||||
timeline = []
|
||||
for area in _COLUMN_SEQUENCE:
|
||||
if area == 'shipping':
|
||||
timeline.append({
|
||||
'area': area,
|
||||
'state': 'current',
|
||||
'variant': 'awaiting_ship',
|
||||
})
|
||||
else:
|
||||
timeline.append({'area': area, 'state': 'done'})
|
||||
job.mini_timeline_json = json.dumps(timeline)
|
||||
continue
|
||||
# Standard path — pre-existing logic.
|
||||
active_area = (job.active_step_id.area_kind
|
||||
if job.active_step_id else None)
|
||||
timeline = []
|
||||
|
||||
Reference in New Issue
Block a user