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:
@@ -1984,6 +1984,11 @@ class FpJob(models.Model):
|
|||||||
"Job %s requires QC. A new check has been created — "
|
"Job %s requires QC. A new check has been created — "
|
||||||
"complete it before marking the job Done."
|
"complete it before marking the job Done."
|
||||||
) % job.name)
|
) % 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.state = 'done'
|
||||||
job.date_finished = fields.Datetime.now()
|
job.date_finished = fields.Datetime.now()
|
||||||
if not skip_side_effects:
|
if not skip_side_effects:
|
||||||
@@ -2153,6 +2158,30 @@ class FpJob(models.Model):
|
|||||||
# regresses awaiting_ship → awaiting_cert. All helpers are
|
# regresses awaiting_ship → awaiting_cert. All helpers are
|
||||||
# idempotent — safe to call from any hook.
|
# 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):
|
def _fp_check_advance_post_shop(self):
|
||||||
"""Auto-advance in_progress jobs whose recipe steps are all
|
"""Auto-advance in_progress jobs whose recipe steps are all
|
||||||
terminal. Called from fp.job.step.button_finish post-super().
|
terminal. Called from fp.job.step.button_finish post-super().
|
||||||
|
|||||||
@@ -1311,8 +1311,36 @@ class FpJobStep(models.Model):
|
|||||||
self._fp_check_contract_review_complete()
|
self._fp_check_contract_review_complete()
|
||||||
self._fp_check_receiving_gate()
|
self._fp_check_receiving_gate()
|
||||||
|
|
||||||
|
# ----- Post-shop gate (spec 2026-05-25 D12) ---------------------
|
||||||
|
# When finishing the LAST open step on an in_progress job, run
|
||||||
|
# the bake/qty/QC gates that used to live in button_mark_done.
|
||||||
|
# Failure raises UserError on THIS click — operator fixes
|
||||||
|
# (qty, bake, QC) and retries the finish. Without this the
|
||||||
|
# auto-advance helper would silently fail with no error path.
|
||||||
|
for step in self:
|
||||||
|
if step.state not in ('in_progress', 'paused', 'ready'):
|
||||||
|
continue
|
||||||
|
job = step.job_id
|
||||||
|
if not job or job.state != 'in_progress':
|
||||||
|
continue
|
||||||
|
# Would this finish leave every step terminal?
|
||||||
|
siblings_open = job.step_ids.filtered(
|
||||||
|
lambda s: s.id != step.id
|
||||||
|
and s.state not in ('done', 'skipped', 'cancelled')
|
||||||
|
)
|
||||||
|
if siblings_open:
|
||||||
|
continue # not the last open step — skip the gates
|
||||||
|
job._fp_check_finish_gates()
|
||||||
|
|
||||||
result = super().button_finish()
|
result = super().button_finish()
|
||||||
|
|
||||||
|
# ----- Post-shop auto-advance (spec 2026-05-25) -----------------
|
||||||
|
# After super().button_finish flips step state to done, ask the
|
||||||
|
# job to check whether ALL steps are now terminal. If so the
|
||||||
|
# helper auto-advances state to awaiting_cert / awaiting_ship.
|
||||||
|
for job in self.mapped('job_id'):
|
||||||
|
job._fp_check_advance_post_shop()
|
||||||
|
|
||||||
# ----- Post-finish side effects --------------------------------
|
# ----- Post-finish side effects --------------------------------
|
||||||
BW = self.env['fusion.plating.bake.window']
|
BW = self.env['fusion.plating.bake.window']
|
||||||
Bath = self.env['fusion.plating.bath']
|
Bath = self.env['fusion.plating.bath']
|
||||||
|
|||||||
Reference in New Issue
Block a user