feat(fusion_plating_jobs): fp.job.step blocker_kind/reason/jump_target computes
Plan task P1.2. Reuses _fp_should_block_predecessors so the new compute stays in sync with the existing can_start logic. Drives the OWL GateViz component on the tablet — "Can't start yet — Waiting on Step N: X". Future work: extend with explicit branches for contract_review / parts_not_received / racking_required / manager_input as those gate models mature. Tests not run locally (no fusion_plating mount in odoo-modsdev). Verify on entech: -u fusion_plating_jobs --test-tags fp_jobs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -85,6 +85,72 @@ class FpJobStep(models.Model):
|
||||
)
|
||||
step.can_start = not bool(blocking)
|
||||
|
||||
# Gate visualizer — drives the OWL GateViz component on the tablet.
|
||||
# Returns kind of blocker + human reason + optional (model, id) jump
|
||||
# target. Reuses _fp_should_block_predecessors so this stays in sync
|
||||
# with can_start as a single source of truth.
|
||||
blocker_kind = fields.Selection(
|
||||
[
|
||||
('none', 'Not blocked'),
|
||||
('predecessor', 'Waiting on predecessor'),
|
||||
('contract_review', 'Contract review pending'),
|
||||
('parts_not_received', 'Parts not received'),
|
||||
('racking_required', 'Racking inspection required'),
|
||||
('manager_input', 'Manager input required'),
|
||||
('other', 'Other'),
|
||||
],
|
||||
compute='_compute_blocker',
|
||||
string='Blocker Kind',
|
||||
)
|
||||
blocker_reason = fields.Char(
|
||||
compute='_compute_blocker',
|
||||
string='Blocker Reason',
|
||||
help='Human-readable explanation surfaced in the GateViz block.',
|
||||
)
|
||||
blocker_jump_target_model = fields.Char(compute='_compute_blocker')
|
||||
blocker_jump_target_id = fields.Integer(compute='_compute_blocker')
|
||||
|
||||
@api.depends(
|
||||
'state', 'sequence', 'parallel_start', 'requires_predecessor_done',
|
||||
'job_id.enforce_sequential',
|
||||
'job_id.step_ids.state', 'job_id.step_ids.sequence',
|
||||
)
|
||||
def _compute_blocker(self):
|
||||
for step in self:
|
||||
# Terminal/in-progress states are never "blocked"
|
||||
if step.state in ('done', 'skipped', 'cancelled', 'in_progress'):
|
||||
step.blocker_kind = 'none'
|
||||
step.blocker_reason = ''
|
||||
step.blocker_jump_target_model = False
|
||||
step.blocker_jump_target_id = 0
|
||||
continue
|
||||
|
||||
# Predecessor gate — same policy as _compute_can_start
|
||||
if step._fp_should_block_predecessors():
|
||||
earlier_open = step.job_id.step_ids.filtered(lambda x: (
|
||||
x.id != step.id
|
||||
and x.sequence < step.sequence
|
||||
and x.state not in ('done', 'skipped', 'cancelled')
|
||||
))
|
||||
if earlier_open:
|
||||
first_blocker = earlier_open.sorted('sequence')[0]
|
||||
step.blocker_kind = 'predecessor'
|
||||
seq_disp = (first_blocker.sequence or 0) // 10
|
||||
step.blocker_reason = (
|
||||
f'Waiting on Step {seq_disp}: {first_blocker.name}'
|
||||
)
|
||||
step.blocker_jump_target_model = 'fp.job.step'
|
||||
step.blocker_jump_target_id = first_blocker.id
|
||||
continue
|
||||
|
||||
# Future: extend with explicit checks for contract_review /
|
||||
# parts_not_received / racking_required / manager_input as
|
||||
# those gate models mature. For now, default to 'none'.
|
||||
step.blocker_kind = 'none'
|
||||
step.blocker_reason = ''
|
||||
step.blocker_jump_target_model = False
|
||||
step.blocker_jump_target_id = 0
|
||||
|
||||
# NOTE: the actual button_start override lives further down (~line
|
||||
# 876) where it merges Sub 13 predecessor gate + Policy B Contract
|
||||
# Review auto-open + Sub 8 Racking auto-open + the receiving soft
|
||||
|
||||
Reference in New Issue
Block a user