feat(fp.job): button_mark_shipped + milestone cascade integration (Task 5)

button_mark_shipped: manual transition awaiting_ship → done. Does
not re-run the bake/qty/QC gates — those passed at the in_progress
→ awaiting_cert/ship transition. Just the 'yes, shipped' stamp.

Milestone cascade (_compute_next_milestone_action) extended to
recognize the two new states:
  - awaiting_cert → 'issue_certs' button
  - awaiting_ship → 'mark_shipped' button
Legacy state='done' branch preserved for historical jobs.

action_advance_next_milestone now dispatches 'mark_shipped' via
_action_mark_shipped_dispatch which routes:
  awaiting_ship → button_mark_shipped (new path)
  done + active delivery → _action_mark_active_delivery_delivered
    (legacy, unchanged)

View: 'Mark Shipped' milestone button gated on Manager/Owner groups.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-25 09:26:03 -04:00
parent 72f0f182a6
commit 4930a89970
2 changed files with 67 additions and 9 deletions

View File

@@ -599,17 +599,33 @@ class FpJob(models.Model):
job.next_milestone_action = False
job.next_milestone_label = ''
continue
if job.state != 'done':
# New state machine (spec 2026-05-25). The auto-advance
# helper normally fires button_finish post-super, so we
# rarely see state='in_progress' here. When we do (e.g.
# historical jobs caught mid-migration, or jobs whose
# cert/delivery infra failed mid-transition), surface
# mark_done as a manual fallback.
if job.state == 'in_progress':
job.next_milestone_action = 'mark_done'
elif job._fp_has_draft_required_certs():
elif job.state == 'awaiting_cert':
job.next_milestone_action = 'issue_certs'
elif (not job.delivery_id
or job.delivery_id.state == 'draft'):
job.next_milestone_action = 'schedule_delivery'
elif job.delivery_id.state in ('scheduled', 'in_transit'):
elif job.state == 'awaiting_ship':
job.next_milestone_action = 'mark_shipped'
elif job.state == 'done':
# Legacy path — historical jobs that closed before the
# new state machine landed. Preserve the old cascade
# so their milestone buttons keep working.
if job._fp_has_draft_required_certs():
job.next_milestone_action = 'issue_certs'
elif (not job.delivery_id
or job.delivery_id.state == 'draft'):
job.next_milestone_action = 'schedule_delivery'
elif job.delivery_id.state in ('scheduled', 'in_transit'):
job.next_milestone_action = 'mark_shipped'
else:
job.next_milestone_action = 'closed'
else:
job.next_milestone_action = 'closed'
job.next_milestone_action = False
job.next_milestone_label = labels.get(
job.next_milestone_action, ''
)
@@ -646,7 +662,10 @@ class FpJob(models.Model):
'mark_done': self.button_mark_done,
'issue_certs': self._action_open_draft_certs,
'schedule_delivery': self._action_open_draft_delivery,
'mark_shipped': self._action_mark_active_delivery_delivered,
# Spec 2026-05-25: dispatch between the new state-machine
# path (state=awaiting_ship → button_mark_shipped) and the
# legacy delivery path (state=done + scheduled delivery).
'mark_shipped': self._action_mark_shipped_dispatch,
}
fn = action_map.get(self.next_milestone_action)
if not fn:
@@ -734,6 +753,18 @@ class FpJob(models.Model):
) % self.delivery_id.name)
return True
def _action_mark_shipped_dispatch(self):
"""Dispatch the milestone-cascade 'Mark Shipped' button to the
right handler based on job state. Spec 2026-05-25:
- awaiting_ship → button_mark_shipped (new state machine)
- done + active delivery → _action_mark_active_delivery_delivered
(legacy historical path)
"""
self.ensure_one()
if self.state == 'awaiting_ship':
return self.button_mark_shipped()
return self._action_mark_active_delivery_delivered()
@api.depends(
'sale_order_id', 'delivery_id', 'portal_job_id', 'step_ids',
'step_ids.time_log_ids', 'origin', 'partner_id',
@@ -1997,6 +2028,32 @@ class FpJob(models.Model):
job._fp_fire_notification('job_complete')
return True
def button_mark_shipped(self):
"""Manual transition awaiting_ship → done. Operator-facing
button on the job form; restricted to Manager / Owner via
groups= on the view button.
Does NOT re-run the bake/qty/QC gates — those passed when the
job first transitioned out of in_progress. This is just the
"yes, shipped" stamp.
Future hook: delivery.action_mark_delivered will call this
automatically — out of scope for this iteration (spec 2026-05-25).
"""
for job in self:
if job.state != 'awaiting_ship':
raise UserError(_(
'Job %s cannot be marked Shipped — state is "%s" '
'(expected "awaiting_ship").'
) % (job.name, job.state))
job.state = 'done'
job.date_finished = fields.Datetime.now()
job._fp_fire_notification('job_shipped')
job.message_post(body=_(
'Marked shipped by %s.'
) % self.env.user.name)
return True
# ------------------------------------------------------------------
# Notifications dispatch (Phase 4)
#

View File

@@ -68,7 +68,8 @@
string="Mark Shipped"
class="btn-success"
icon="fa-paper-plane"
invisible="next_milestone_action != 'mark_shipped'"/>
invisible="next_milestone_action != 'mark_shipped'"
groups="fusion_plating.group_fp_manager,fusion_plating.group_fp_owner"/>
<field name="all_steps_terminal" invisible="1"/>
<field name="next_milestone_action" invisible="1"/>
<button name="action_print_sticker" type="object"