feat(fp.job.step): wrap button_finish with gate + advance (Task 4)

Pre-super: when finishing the last open step on an in_progress job,
run the bake/qty/QC gates from button_mark_done so failures surface
as UserError on the click (per spec D12). Without this the
auto-advance would silently fail with no error path.

Post-super: trigger _fp_check_advance_post_shop so the state
auto-advances cleanly (in_progress → awaiting_cert / awaiting_ship).

Added _fp_check_finish_gates helper on fp.job and a
fp_check_gates_only context flag honored by button_mark_done so the
gate logic is single-sourced (DRY).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-25 09:22:33 -04:00
parent 5173554281
commit 72f0f182a6
2 changed files with 57 additions and 0 deletions

View File

@@ -1984,6 +1984,11 @@ class FpJob(models.Model):
"Job %s requires QC. A new check has been created — "
"complete it before marking the job Done."
) % job.name)
# When called as a gate-check from fp.job.step.button_finish
# (per spec 2026-05-25 D12), exit BEFORE flipping state —
# the post-shop advance helper handles the actual transition.
if self.env.context.get('fp_check_gates_only'):
continue
job.state = 'done'
job.date_finished = fields.Datetime.now()
if not skip_side_effects:
@@ -2153,6 +2158,30 @@ class FpJob(models.Model):
# regresses awaiting_ship → awaiting_cert. All helpers are
# idempotent — safe to call from any hook.
# ==================================================================
def _fp_check_finish_gates(self):
"""Run the bake-window / qty-reconciliation / QC gates that used
to live in button_mark_done. Called from
fp.job.step.button_finish when the operator is finishing the
LAST open step on the job (spec D12).
Raises UserError on failure — operator stays on the step, fixes
the issue, retries the click. Manager bypass via the same
context flags as button_mark_done (fp_skip_bake_gate,
fp_skip_qty_reconcile, fp_skip_qc_gate).
The trick: re-uses button_mark_done's gate logic but short-
circuits BEFORE the state flip via the fp_check_gates_only
context flag (honoured in button_mark_done below).
"""
self.ensure_one()
# Pass through with context flag; button_mark_done will run all
# its gates and then exit before flipping state. The actual
# state transition (in_progress → awaiting_cert/ship) is owned
# by _fp_check_advance_post_shop running AFTER super().button_finish.
self.with_context(
fp_check_gates_only=True,
).button_mark_done()
def _fp_check_advance_post_shop(self):
"""Auto-advance in_progress jobs whose recipe steps are all
terminal. Called from fp.job.step.button_finish post-super().