feat(fusion_plating_jobs): late_risk_ratio + active_step_id computes on fp.job

Plan tasks P2.2 + P2.3 batched (both small additive computes on fp.job;
local tests not run between them — entech verifies).

  late_risk_ratio  — stored Float, remaining_planned / minutes_to_deadline.
                     Drives the Manager At-Risk view (Phase 4).
                     Recomputes on step state, duration, deadline changes.

  active_step_id   — non-stored Many2one. Currently in_progress step
                     (lowest sequence if multiple — defensive).
                     Drives JobWorkspace landing focus.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-22 22:01:58 -04:00
parent 06dc6a62b9
commit c76eb94724
4 changed files with 173 additions and 0 deletions

View File

@@ -121,6 +121,57 @@ class FpJob(models.Model):
tail = raw.rsplit('/', 1)[-1]
job.display_wo_name = f'WO # {tail}'
# Phase 2 — At-Risk view + Workspace landing focus.
late_risk_ratio = fields.Float(
compute='_compute_late_risk_ratio',
store=True,
string='Late-risk Ratio',
help='remaining_planned_minutes / minutes_to_deadline. '
'>1.0 means the job will be late if nothing changes. '
'Drives the At-Risk view on the manager dashboard.',
)
active_step_id = fields.Many2one(
'fp.job.step',
compute='_compute_active_step_id',
string='Active Step',
help='Currently in-progress step (lowest sequence if multiple — '
'defensive). Drives JobWorkspace landing focus.',
)
@api.depends(
'date_deadline',
'step_ids.state',
'step_ids.duration_expected',
)
def _compute_late_risk_ratio(self):
from datetime import datetime
for job in self:
if not job.date_deadline:
job.late_risk_ratio = 0.0
continue
open_steps = job.step_ids.filtered(
lambda s: s.state not in ('done', 'skipped', 'cancelled')
)
remaining_planned = sum(open_steps.mapped('duration_expected') or [0])
if remaining_planned <= 0:
job.late_risk_ratio = 0.0
continue
now = datetime.now()
# date_deadline is naive UTC in Odoo; compare directly
minutes_to_deadline = max(
1.0,
(job.date_deadline - now).total_seconds() / 60.0,
)
job.late_risk_ratio = remaining_planned / minutes_to_deadline
@api.depends('step_ids.state', 'step_ids.sequence')
def _compute_active_step_id(self):
for job in self:
active = job.step_ids.filtered(
lambda s: s.state == 'in_progress'
).sorted('sequence')
job.active_step_id = active[:1].id if active else False
# ------------------------------------------------------------------
# Sub 14 — Configurable workflow state (status bar milestone)
# ------------------------------------------------------------------