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:
@@ -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()
|
||||
Reference in New Issue
Block a user