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:
gsinghpal
2026-05-24 17:06:53 -04:00
parent 7b90f210b9
commit b06d28e7f6
7 changed files with 346 additions and 75 deletions

View File

@@ -0,0 +1,112 @@
# -*- coding: utf-8 -*-
"""Battle test S24 — Live step priority chain + board state filter.
Run end-to-end via odoo shell with stdin redirection:
ssh pve-worker5 "pct exec 111 -- bash -c 'su - odoo -s /bin/bash -c \\
\"/usr/bin/odoo shell -c /etc/odoo/odoo.conf -d admin --no-http\" \\
< /mnt/extra-addons/custom/fusion_plating_quality/scripts/bt_s24_between_steps.py'"
Asserts:
1. Job between steps (one done, next pending) has live active_step_id
pointing at the next pending step, NOT False.
2. Card column resolves to that pending step's area_kind, NOT receiving.
3. Paused steps still count as active.
4. state='done' jobs are excluded from the live-board search domain.
See docs/superpowers/specs/2026-05-24-shopfloor-live-step-fix-design.md.
"""
import logging
_logger = logging.getLogger(__name__)
def _resolve_card_area(job):
"""Mirror of plant_kanban._resolve_card_area for test purposes."""
if job.active_step_id and job.active_step_id.area_kind:
return job.active_step_id.area_kind
return 'receiving'
def run():
partner = env['res.partner'].search([('customer_rank', '>', 0)], limit=1)
if not partner:
raise AssertionError(
'No customer partner found — seed test data first'
)
recipe = env['fusion.plating.process.node'].search([
('node_type', '=', 'recipe'),
('child_ids', '!=', False),
], limit=1)
if not recipe:
raise AssertionError('No recipe found — seed test data first')
job = env['fp.job'].create({
'partner_id': partner.id,
'recipe_id': recipe.id,
'qty': 1,
})
job._fp_generate_steps_from_recipe()
steps = job.step_ids.sorted('sequence')
if len(steps) < 3:
raise AssertionError('Need at least 3 steps for the test')
# === Phase A — between-step assertion ===
s1 = steps[0]
s2 = steps[1]
s1.button_start()
s1.button_finish()
job.invalidate_recordset(['active_step_id', 'card_state'])
if job.active_step_id.id != s2.id:
raise AssertionError(
'Expected active_step_id = %s (next pending), got %s'
% (s2.id, job.active_step_id.id)
)
if _resolve_card_area(job) != s2.area_kind:
raise AssertionError(
'Card column should match s2.area_kind=%s, got %s'
% (s2.area_kind, _resolve_card_area(job))
)
_logger.info('[bt_s24] Phase A OK — between-step routing correct')
# === Phase B — paused step assertion ===
s2.button_start()
s2.button_pause('lunch break')
job.invalidate_recordset(['active_step_id', 'card_state'])
if job.active_step_id.id != s2.id:
raise AssertionError(
'Paused step should remain the live step, got %s'
% job.active_step_id.id
)
_logger.info('[bt_s24] Phase B OK — paused step stays live')
# === Phase C — done job filter ===
for s in steps:
if s.state != 'done':
if s.state == 'paused':
s.button_resume()
if s.state != 'in_progress':
s.button_start()
s.button_finish()
job.with_context(
fp_skip_step_gate=True,
fp_skip_qty_reconcile=True,
fp_skip_bake_gate=True,
).button_mark_done()
if job.state != 'done':
raise AssertionError('job did not transition to done')
jobs_on_board = env['fp.job'].search([
('state', 'in', ('confirmed', 'in_progress')),
])
if job.id in jobs_on_board.ids:
raise AssertionError(
'Done job %s should be filtered off board' % job.id
)
_logger.info('[bt_s24] Phase C OK — done jobs filtered off board')
_logger.info('[bt_s24] ALL ASSERTIONS PASSED')
run()