feat(fusion_plating_jobs): auto-pause cron for stale in-progress steps
Plan tasks P2.4 + P2.5 batched.
Adds _cron_autopause_stale_steps method on fp.job.step + 30-min cron
registration. Flips in_progress steps idle > threshold to paused with
a chatter audit ("Auto-paused after Nh idle. Resume from the tablet
when work continues.").
Threshold from ir.config_parameter:
fp.shopfloor.autopause_threshold_hours (default 8.0)
Recipe nodes opt out via fusion.plating.process.node.long_running
(added in P2.1) — useful for 24h bakes and multi-shift soaks.
Fixes the 411-hour ghost timer that motivated the redesign. Doesn't
replace the existing nudge crons — those still notify the supervisor;
this one actually pauses the timer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,3 +6,4 @@ from . import test_display_wo_name
|
||||
from . import test_blocker_compute
|
||||
from . import test_late_risk_ratio
|
||||
from . import test_active_step_id
|
||||
from . import test_autopause_cron
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc. — License OPL-1
|
||||
"""Plan task P2.4 — _cron_autopause_stale_steps method."""
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from odoo.tests.common import TransactionCase, tagged
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install', 'fp_jobs')
|
||||
class TestAutopauseCron(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.partner = self.env['res.partner'].create({'name': 'AP'})
|
||||
self.product = self.env['product.product'].create({'name': 'AP'})
|
||||
self.job = self.env['fp.job'].create({
|
||||
'name': 'WH/JOB/AP',
|
||||
'partner_id': self.partner.id,
|
||||
'product_id': self.product.id,
|
||||
'qty': 1,
|
||||
})
|
||||
|
||||
def test_stale_step_flips_to_paused(self):
|
||||
step = self.env['fp.job.step'].create({
|
||||
'job_id': self.job.id,
|
||||
'name': 'Stale',
|
||||
'sequence': 10,
|
||||
'state': 'in_progress',
|
||||
'date_started': datetime.now() - timedelta(hours=10),
|
||||
})
|
||||
paused = self.env['fp.job.step']._cron_autopause_stale_steps()
|
||||
self.assertGreaterEqual(paused, 1)
|
||||
step.invalidate_recordset(['state'])
|
||||
self.assertEqual(step.state, 'paused')
|
||||
|
||||
def test_fresh_step_unchanged(self):
|
||||
step = self.env['fp.job.step'].create({
|
||||
'job_id': self.job.id,
|
||||
'name': 'Fresh',
|
||||
'sequence': 10,
|
||||
'state': 'in_progress',
|
||||
'date_started': datetime.now() - timedelta(hours=2),
|
||||
})
|
||||
self.env['fp.job.step']._cron_autopause_stale_steps()
|
||||
step.invalidate_recordset(['state'])
|
||||
self.assertEqual(step.state, 'in_progress')
|
||||
|
||||
def test_long_running_node_exempt(self):
|
||||
node = self.env['fusion.plating.process.node'].create({
|
||||
'name': 'Long bake',
|
||||
'long_running': True,
|
||||
'node_type': 'operation',
|
||||
})
|
||||
step = self.env['fp.job.step'].create({
|
||||
'job_id': self.job.id,
|
||||
'name': 'Long',
|
||||
'sequence': 10,
|
||||
'state': 'in_progress',
|
||||
'date_started': datetime.now() - timedelta(hours=20),
|
||||
'recipe_node_id': node.id,
|
||||
})
|
||||
self.env['fp.job.step']._cron_autopause_stale_steps()
|
||||
step.invalidate_recordset(['state'])
|
||||
self.assertEqual(step.state, 'in_progress')
|
||||
|
||||
def test_threshold_config_parameter_respected(self):
|
||||
self.env['ir.config_parameter'].sudo().set_param(
|
||||
'fp.shopfloor.autopause_threshold_hours', '24',
|
||||
)
|
||||
step = self.env['fp.job.step'].create({
|
||||
'job_id': self.job.id,
|
||||
'name': 'Within 24h',
|
||||
'sequence': 10,
|
||||
'state': 'in_progress',
|
||||
'date_started': datetime.now() - timedelta(hours=10),
|
||||
})
|
||||
self.env['fp.job.step']._cron_autopause_stale_steps()
|
||||
step.invalidate_recordset(['state'])
|
||||
# 10h < 24h → still in_progress
|
||||
self.assertEqual(step.state, 'in_progress')
|
||||
Reference in New Issue
Block a user