From e6bbf566ca9ef350e3bb15109e8c2a6008050ce2 Mon Sep 17 00:00:00 2001 From: gsinghpal Date: Thu, 4 Jun 2026 14:49:30 -0400 Subject: [PATCH] feat(logistics): auto-generate packing slip on local delivery dispatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../models/fp_delivery.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/fusion_plating/fusion_plating_logistics/models/fp_delivery.py b/fusion_plating/fusion_plating_logistics/models/fp_delivery.py index cbc14fbb..c4a7149d 100644 --- a/fusion_plating/fusion_plating_logistics/models/fp_delivery.py +++ b/fusion_plating/fusion_plating_logistics/models/fp_delivery.py @@ -3,11 +3,16 @@ # License OPL-1 (Odoo Proprietary License v1.0) # Part of the Fusion Plating product family. +import base64 +import logging + from markupsafe import Markup from odoo import _, api, fields, models from odoo.exceptions import UserError +_logger = logging.getLogger(__name__) + class FpDelivery(models.Model): """Scheduled delivery of finished parts back to a customer. @@ -480,6 +485,51 @@ class FpDelivery(models.Model): or rec.vehicle_id.display_name 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): """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 # chatter so the shipping supervisor sees it on the record. 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): """Compare this delivery's boxes-out count to the boxes-in count