changes
This commit is contained in:
@@ -263,6 +263,83 @@ class FpPortalJob(models.Model):
|
||||
walk(mo.x_fc_recipe_id, 0)
|
||||
return result
|
||||
|
||||
# ==========================================================================
|
||||
# State recompute — single source of truth derived from upstream models
|
||||
# ==========================================================================
|
||||
# The portal state should ALWAYS reflect the real shop-floor state of the
|
||||
# linked fp.job(s), the outbound shipment(s), and the customer invoice.
|
||||
# Earlier paths wrote state directly from each event hook (tracking number
|
||||
# arrived → 'shipped'; invoice posted → 'complete') which drifted out of
|
||||
# sync the moment any of those events fired before the job was actually
|
||||
# done — e.g. a FedEx label booked early would promote portal state to
|
||||
# 'shipped' even though the WO was still in 'confirmed'. The helper below
|
||||
# is the new single source of truth; the hooks now delegate to it.
|
||||
def _fp_recompute_portal_state(self):
|
||||
"""Derive portal state from fp.job + shipment + invoice and write
|
||||
it back if it differs. Safe to call from any sync hook; only
|
||||
writes when the target state actually changes."""
|
||||
Job = self.env.get('fp.job')
|
||||
if Job is None:
|
||||
return
|
||||
for portal in self:
|
||||
jobs = Job.sudo().search([('portal_job_id', '=', portal.id)])
|
||||
if not jobs:
|
||||
# No linked job — leave manual edits alone.
|
||||
continue
|
||||
|
||||
all_done = all(j.state == 'done' for j in jobs)
|
||||
any_in_progress = any(
|
||||
j.state in ('in_progress', 'done') for j in jobs
|
||||
)
|
||||
|
||||
# Walk SO → fp.receiving → fusion.shipment for shipment status.
|
||||
ship_delivered = False
|
||||
ship_in_transit = False
|
||||
for j in jobs:
|
||||
so = j.sale_order_id
|
||||
if not so or 'x_fc_receiving_ids' not in so._fields:
|
||||
continue
|
||||
for recv in so.x_fc_receiving_ids:
|
||||
ship = (
|
||||
recv.x_fc_outbound_shipment_id
|
||||
if 'x_fc_outbound_shipment_id' in recv._fields
|
||||
else False
|
||||
)
|
||||
if not ship:
|
||||
continue
|
||||
if ship.status == 'delivered':
|
||||
ship_delivered = True
|
||||
elif ship.status == 'shipped':
|
||||
ship_in_transit = True
|
||||
|
||||
# Invoice signal — any posted customer invoice on the SO.
|
||||
invoiced = False
|
||||
for j in jobs:
|
||||
so = j.sale_order_id
|
||||
if not so:
|
||||
continue
|
||||
if any(
|
||||
m.state == 'posted' and m.move_type in ('out_invoice', 'out_refund')
|
||||
for m in so.invoice_ids
|
||||
):
|
||||
invoiced = True
|
||||
break
|
||||
|
||||
# Resolve target state.
|
||||
if all_done and invoiced and ship_delivered:
|
||||
target = 'complete'
|
||||
elif all_done and (ship_delivered or ship_in_transit):
|
||||
target = 'shipped'
|
||||
elif all_done:
|
||||
target = 'ready_to_ship'
|
||||
elif any_in_progress:
|
||||
target = 'in_progress'
|
||||
else:
|
||||
target = 'received'
|
||||
|
||||
if portal.state != target:
|
||||
portal.sudo().write({'state': target})
|
||||
|
||||
# ==========================================================================
|
||||
# Per-stage timestamp snapshots
|
||||
# ==========================================================================
|
||||
|
||||
Reference in New Issue
Block a user