feat(logistics): auto-generate packing slip on local delivery dispatch
fusion.plating.delivery already had packing_list_attachment_id + a viewer action, but nothing populated it — shipping a local delivery produced no packing slip. Add _fp_generate_packing_slip(): renders fusion_plating_reports.action_report_fp_packing_slip_portrait and stores it on packing_list_attachment_id. Hooked into action_start_route (the dispatch / loaded-on-vehicle moment, so it travels with the goods) and as a generate-if-missing catch-all on action_mark_delivered. Idempotent (skips deliveries that already have one unless force=True) and best-effort (a report glitch logs + continues, never blocks shipping). Report action resolved at runtime so logistics keeps no hard dep on fusion_plating_reports. Deployed + verified on entech (12.8KB PDF for DLV-30097, rolled back). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,11 +3,16 @@
|
|||||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||||
# Part of the Fusion Plating product family.
|
# Part of the Fusion Plating product family.
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import logging
|
||||||
|
|
||||||
from markupsafe import Markup
|
from markupsafe import Markup
|
||||||
|
|
||||||
from odoo import _, api, fields, models
|
from odoo import _, api, fields, models
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class FpDelivery(models.Model):
|
class FpDelivery(models.Model):
|
||||||
"""Scheduled delivery of finished parts back to a customer.
|
"""Scheduled delivery of finished parts back to a customer.
|
||||||
@@ -480,6 +485,51 @@ class FpDelivery(models.Model):
|
|||||||
or rec.vehicle_id.display_name
|
or rec.vehicle_id.display_name
|
||||||
or 'Driver'),
|
or 'Driver'),
|
||||||
)
|
)
|
||||||
|
# Packing slip travels with the shipment — render + attach it on
|
||||||
|
# dispatch so the driver/customer get it and it's on the record.
|
||||||
|
self._fp_generate_packing_slip()
|
||||||
|
|
||||||
|
def _fp_generate_packing_slip(self, force=False):
|
||||||
|
"""Render each delivery's packing slip and store it on
|
||||||
|
packing_list_attachment_id so it ships with the goods.
|
||||||
|
|
||||||
|
Fired on dispatch (action_start_route) and as a catch-all on
|
||||||
|
action_mark_delivered. Idempotent + best-effort: skips deliveries
|
||||||
|
that already carry a slip (unless force=True) and never raises —
|
||||||
|
a report glitch must not block shipping. The report action is
|
||||||
|
resolved at runtime so this module needs no hard dependency on
|
||||||
|
fusion_plating_reports.
|
||||||
|
"""
|
||||||
|
report_xmlid = (
|
||||||
|
'fusion_plating_reports.action_report_fp_packing_slip_portrait'
|
||||||
|
)
|
||||||
|
report = self.env.ref(report_xmlid, raise_if_not_found=False)
|
||||||
|
if not report:
|
||||||
|
return
|
||||||
|
for rec in self:
|
||||||
|
if 'packing_list_attachment_id' not in rec._fields:
|
||||||
|
return
|
||||||
|
if rec.packing_list_attachment_id and not force:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
report_model = self.env['ir.actions.report'].sudo()
|
||||||
|
pdf_bytes, _fmt = report_model._render_qweb_pdf(
|
||||||
|
report_xmlid, res_ids=rec.ids)
|
||||||
|
att = self.env['ir.attachment'].sudo().create({
|
||||||
|
'name': _('Packing Slip - %s.pdf') % rec.display_name,
|
||||||
|
'type': 'binary',
|
||||||
|
'datas': base64.b64encode(pdf_bytes),
|
||||||
|
'mimetype': 'application/pdf',
|
||||||
|
'res_model': rec._name,
|
||||||
|
'res_id': rec.id,
|
||||||
|
})
|
||||||
|
rec.packing_list_attachment_id = att.id
|
||||||
|
rec.message_post(
|
||||||
|
body=_('Packing slip generated and attached.'))
|
||||||
|
except Exception as exc:
|
||||||
|
_logger.warning(
|
||||||
|
'Packing slip render failed for delivery %s: %s',
|
||||||
|
rec.display_name, exc)
|
||||||
|
|
||||||
def action_mark_delivered(self):
|
def action_mark_delivered(self):
|
||||||
"""Block "delivered" until a Proof of Delivery exists.
|
"""Block "delivered" until a Proof of Delivery exists.
|
||||||
@@ -511,6 +561,9 @@ class FpDelivery(models.Model):
|
|||||||
# Sub 8 — box-parity warning. Non-blocking; just posts to
|
# Sub 8 — box-parity warning. Non-blocking; just posts to
|
||||||
# chatter so the shipping supervisor sees it on the record.
|
# chatter so the shipping supervisor sees it on the record.
|
||||||
rec._fp_check_box_parity()
|
rec._fp_check_box_parity()
|
||||||
|
# Catch-all: ensure a slip exists even if dispatch was skipped
|
||||||
|
# (generate-if-missing — won't overwrite the dispatch-time one).
|
||||||
|
self._fp_generate_packing_slip()
|
||||||
|
|
||||||
def _fp_check_box_parity(self):
|
def _fp_check_box_parity(self):
|
||||||
"""Compare this delivery's boxes-out count to the boxes-in count
|
"""Compare this delivery's boxes-out count to the boxes-in count
|
||||||
|
|||||||
Reference in New Issue
Block a user