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:
gsinghpal
2026-05-22 22:03:20 -04:00
parent c76eb94724
commit c06d3d442a
4 changed files with 154 additions and 0 deletions

View File

@@ -151,6 +151,62 @@ class FpJobStep(models.Model):
step.blocker_jump_target_model = False
step.blocker_jump_target_id = 0
# ==================================================================
# Shop-Floor auto-pause cron (Phase 2 — tablet redesign)
# ==================================================================
@api.model
def _cron_autopause_stale_steps(self):
"""Flip in_progress steps idle > threshold to paused.
Threshold read from ir.config_parameter
fp.shopfloor.autopause_threshold_hours (default 8.0)
Recipes can opt out per node via
fusion.plating.process.node.long_running (Phase 2 — P2.1)
Fixes the 411-hour ghost timer that bit us on the original tablet
when an operator started a step and never tapped Finish. Posts an
audit chatter entry on the step so the operator can see what
happened when they resume.
"""
from datetime import timedelta
threshold = float(
self.env['ir.config_parameter'].sudo()
.get_param('fp.shopfloor.autopause_threshold_hours', 8)
)
deadline = fields.Datetime.now() - timedelta(hours=threshold)
domain = [
('state', '=', 'in_progress'),
('date_started', '<', deadline),
'|',
('recipe_node_id', '=', False),
('recipe_node_id.long_running', '=', False),
]
stale = self.search(domain)
paused = 0
for step in stale:
try:
step.button_pause()
step.message_post(body=Markup(
"<b>Auto-paused</b> after %.1fh idle. "
"Resume from the tablet when work continues."
) % threshold)
_logger.info(
"Auto-paused step %s (%s) after %.1fh idle",
step.id, step.name, threshold,
)
paused += 1
except Exception:
_logger.exception(
"Auto-pause failed for step %s — skipping", step.id,
)
if paused:
_logger.info(
"_cron_autopause_stale_steps: paused %d step(s) "
"(threshold %.1fh)", paused, threshold,
)
return paused
# 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