This commit is contained in:
gsinghpal
2026-05-22 18:01:31 -04:00
parent d127e19b45
commit f661724c72
34 changed files with 1011 additions and 59 deletions

View File

@@ -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
# ==========================================================================