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

@@ -4,3 +4,5 @@ from . import test_fp_job_milestone_cascade
from . import test_qty_received_propagation
from . import test_display_wo_name
from . import test_blocker_compute
from . import test_late_risk_ratio
from . import test_active_step_id

View File

@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc. — License OPL-1
"""Plan task P2.3 — fp.job.active_step_id compute."""
from odoo.tests.common import TransactionCase, tagged
@tagged('-at_install', 'post_install', 'fp_jobs')
class TestActiveStepId(TransactionCase):
def setUp(self):
super().setUp()
self.partner = self.env['res.partner'].create({'name': 'AS'})
self.product = self.env['product.product'].create({'name': 'AS'})
self.job = self.env['fp.job'].create({
'name': 'WH/JOB/AS',
'partner_id': self.partner.id,
'product_id': self.product.id,
'qty': 1,
})
def test_no_active_step(self):
self.env['fp.job.step'].create({
'job_id': self.job.id,
'name': 'S1',
'sequence': 10,
'state': 'ready',
})
self.job.invalidate_recordset(['active_step_id'])
self.assertFalse(self.job.active_step_id.id)
def test_single_in_progress_step(self):
s = self.env['fp.job.step'].create({
'job_id': self.job.id,
'name': 'S1',
'sequence': 10,
'state': 'in_progress',
})
self.job.invalidate_recordset(['active_step_id'])
self.assertEqual(self.job.active_step_id.id, s.id)
def test_multiple_in_progress_picks_lowest_sequence(self):
s1 = self.env['fp.job.step'].create({
'job_id': self.job.id,
'name': 'S1',
'sequence': 10,
'state': 'in_progress',
})
self.env['fp.job.step'].create({
'job_id': self.job.id,
'name': 'S2',
'sequence': 20,
'state': 'in_progress',
})
self.job.invalidate_recordset(['active_step_id'])
self.assertEqual(self.job.active_step_id.id, s1.id)

View File

@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc. — License OPL-1
"""Plan task P2.2 — fp.job.late_risk_ratio compute."""
from datetime import datetime, timedelta
from odoo.tests.common import TransactionCase, tagged
@tagged('-at_install', 'post_install', 'fp_jobs')
class TestLateRiskRatio(TransactionCase):
def setUp(self):
super().setUp()
self.partner = self.env['res.partner'].create({'name': 'LR'})
self.product = self.env['product.product'].create({'name': 'LR'})
def _make_job(self, deadline=None):
return self.env['fp.job'].create({
'name': 'WH/JOB/LR',
'partner_id': self.partner.id,
'product_id': self.product.id,
'qty': 1,
'date_deadline': deadline,
})
def test_no_deadline_zero(self):
job = self._make_job(deadline=False)
self.assertEqual(job.late_risk_ratio, 0.0)
def test_no_open_steps_zero(self):
job = self._make_job(deadline=datetime.now() + timedelta(hours=8))
self.assertEqual(job.late_risk_ratio, 0.0)
def test_ratio_above_one_when_overrun(self):
job = self._make_job(deadline=datetime.now() + timedelta(hours=2))
# One step planned for 240 min, only 120 min left → ratio ~ 2.0
self.env['fp.job.step'].create({
'job_id': job.id,
'name': 'Long step',
'sequence': 10,
'state': 'ready',
'duration_expected': 240,
})
job.invalidate_recordset(['late_risk_ratio'])
self.assertGreaterEqual(job.late_risk_ratio, 1.5)
def test_done_steps_dont_count_toward_remaining(self):
job = self._make_job(deadline=datetime.now() + timedelta(hours=4))
self.env['fp.job.step'].create({
'job_id': job.id,
'name': 'Done',
'sequence': 10,
'state': 'done',
'duration_expected': 999,
})
self.env['fp.job.step'].create({
'job_id': job.id,
'name': 'Tiny remaining',
'sequence': 20,
'state': 'ready',
'duration_expected': 30,
})
job.invalidate_recordset(['late_risk_ratio'])
# 30 min remaining vs 240 min to deadline → ratio ~ 0.125
self.assertLess(job.late_risk_ratio, 0.3)