from odoo import models, fields, api, _ class StockPicking(models.Model): _inherit = 'stock.picking' fusion_shipment_count = fields.Integer( string='Shipments', compute='_compute_fusion_shipment_count', ) fusion_shipping_label_attachment_id = fields.Many2one( 'ir.attachment', string='Shipping Label', compute='_compute_fusion_shipping_docs', help='Most recent shipping label generated for this delivery ' '(any carrier).', ) fusion_commercial_invoice_attachment_id = fields.Many2one( 'ir.attachment', string='Commercial Invoice', compute='_compute_fusion_shipping_docs', help='Most recent commercial (customs) invoice generated for this ' 'delivery (any carrier).', ) def _compute_fusion_shipment_count(self): Shipment = self.env['fusion.shipment'] for picking in self: picking.fusion_shipment_count = Shipment.search_count( [('picking_id', '=', picking.id)] ) @api.depends('message_ids.attachment_ids') def _compute_fusion_shipping_docs(self): """Find the latest shipping label + commercial invoice that the carrier posted to this delivery's chatter, so they can be opened from a smart button. Naming is carrier-agnostic (see the carriers' send_shipping): - labels contain 'Label' (Label-, LabelUPS, LabelShipping-...) - invoices contain 'CommercialInvoice' (UPS/Canada Post) or start with 'ShippingDoc-' (FedEx/DHL, via _get_delivery_doc_prefix). """ Attachment = self.env['ir.attachment'] for picking in self: invoice = label = Attachment if isinstance(picking.id, int): atts = Attachment.search( [('res_model', '=', 'stock.picking'), ('res_id', '=', picking.id)], order='id desc') for att in atts: name = att.name or '' norm = name.lower().replace(' ', '').replace('-', '') if 'commercialinvoice' in norm or name.startswith('ShippingDoc'): invoice = invoice or att elif 'label' in name.lower() and 'return' not in name.lower(): label = label or att if invoice and label: break picking.fusion_commercial_invoice_attachment_id = invoice.id picking.fusion_shipping_label_attachment_id = label.id def _fusion_open_attachment(self, attachment): """Open a shipping document for the operator. Routes PDFs through ir.attachment.action_fusion_preview (preview dialog) when fusion_pdf_preview is installed, else opens the file in a new tab. Mirrors fusion.shipment._action_open_attachment. See CLAUDE.md "PDF Preview". """ self.ensure_one() if not attachment: return False if hasattr(attachment, 'action_fusion_preview'): return attachment.action_fusion_preview( title=attachment.name or _('Shipping Document'), model_name=self._name, record_ids=self.id, ) return { 'type': 'ir.actions.act_url', 'url': '/web/content/%s?download=false' % attachment.id, 'target': 'new', } def action_view_fusion_shipping_label(self): return self._fusion_open_attachment( self.fusion_shipping_label_attachment_id) def action_view_fusion_commercial_invoice(self): return self._fusion_open_attachment( self.fusion_commercial_invoice_attachment_id) def action_view_fusion_shipments(self): self.ensure_one() shipments = self.env['fusion.shipment'].search( [('picking_id', '=', self.id)] ) action = { 'type': 'ir.actions.act_window', 'name': 'Shipments', 'res_model': 'fusion.shipment', 'view_mode': 'list,form', 'domain': [('picking_id', '=', self.id)], } if len(shipments) == 1: action['view_mode'] = 'form' action['res_id'] = shipments.id return action