feat(jobs): order-level ship-readiness helpers
_fp_order_ship_state + _fp_mark_order_shipped enforce spec D4 ship-together: the order ships only when every active job on it is awaiting_ship/done. Shared by the tablet shipping endpoints and /fp/workspace/load. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -2184,6 +2184,72 @@ class FpJob(models.Model):
|
||||
) % self.env.user.name)
|
||||
return True
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Order-level ship readiness (tablet receiving+shipping, 2026-05-29)
|
||||
#
|
||||
# An order can split into several jobs (one per part/recipe) but has
|
||||
# ONE outbound shipment (the physical boxes). Spec D4 = "ship
|
||||
# together": the order can ship only when EVERY active job on it is
|
||||
# awaiting_ship or done, with at least one awaiting_ship to act on.
|
||||
# Both the tablet endpoints and /fp/workspace/load read this.
|
||||
# ------------------------------------------------------------------
|
||||
def _fp_order_ship_state(self):
|
||||
"""Return ship-readiness for the whole order this job belongs to.
|
||||
|
||||
{ready, not_ready:[{wo_name, state_label}], awaiting_ship_jobs,
|
||||
order_jobs, order_receiving}
|
||||
Runs in the caller's env: call on a sudo job for display, on a
|
||||
user job (the tablet tech) when you want real write attribution.
|
||||
"""
|
||||
self.ensure_one()
|
||||
empty_job = self.browse()
|
||||
empty_rcv = (self.env['fp.receiving'].browse()
|
||||
if 'fp.receiving' in self.env else empty_job)
|
||||
so = self.sale_order_id
|
||||
if not so:
|
||||
return {'ready': False, 'not_ready': [],
|
||||
'awaiting_ship_jobs': empty_job, 'order_jobs': self,
|
||||
'order_receiving': empty_rcv}
|
||||
jobs = self.search([
|
||||
('sale_order_id', '=', so.id),
|
||||
('state', '!=', 'cancelled'),
|
||||
])
|
||||
not_ready = jobs.filtered(lambda j: j.state not in ('awaiting_ship', 'done'))
|
||||
awaiting = jobs.filtered(lambda j: j.state == 'awaiting_ship')
|
||||
ready = bool(jobs) and not not_ready and bool(awaiting)
|
||||
state_sel = dict(self._fields['state'].selection)
|
||||
rcv = empty_rcv
|
||||
if 'fp.receiving' in self.env:
|
||||
rcv = self.env['fp.receiving'].search(
|
||||
[('sale_order_id', '=', so.id)], order='id desc', limit=1)
|
||||
return {
|
||||
'ready': ready,
|
||||
'not_ready': [{'wo_name': j.display_wo_name,
|
||||
'state_label': state_sel.get(j.state, j.state)}
|
||||
for j in not_ready],
|
||||
'awaiting_ship_jobs': awaiting,
|
||||
'order_jobs': jobs,
|
||||
'order_receiving': rcv,
|
||||
}
|
||||
|
||||
def _fp_mark_order_shipped(self):
|
||||
"""Mark every awaiting_ship job on the order as shipped (done).
|
||||
|
||||
Gated on _fp_order_ship_state['ready']; raises UserError naming
|
||||
the unfinished jobs otherwise. Returns the recordset marked.
|
||||
"""
|
||||
self.ensure_one()
|
||||
info = self._fp_order_ship_state()
|
||||
if not info['ready']:
|
||||
names = ', '.join(n['wo_name'] for n in info['not_ready']) or _('none')
|
||||
raise UserError(_(
|
||||
'Cannot ship yet — these jobs on the order are not '
|
||||
'finished: %s'
|
||||
) % names)
|
||||
awaiting = info['awaiting_ship_jobs']
|
||||
awaiting.button_mark_shipped()
|
||||
return awaiting
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Notifications dispatch (Phase 4)
|
||||
#
|
||||
|
||||
Reference in New Issue
Block a user