# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) """Phase C — extend fusion.shipment with dimension fields. fusion_shipping's native model has `weight` but no length/width/height. The plating workflow needs all four captured at receiving time so the shipment record carries everything the carrier API would want. Added here (not in fusion_shipping) to keep the upstream module untouched. """ import logging from odoo import api, fields, models _logger = logging.getLogger(__name__) class FusionShipment(models.Model): _inherit = 'fusion.shipment' x_fc_length = fields.Float(string='Length', digits=(10, 2)) x_fc_width = fields.Float(string='Width', digits=(10, 2)) x_fc_height = fields.Float(string='Height', digits=(10, 2)) x_fc_dim_uom = fields.Selection( [('in', 'in'), ('cm', 'cm')], string='Dim UoM', default='in', ) x_fc_weight_uom = fields.Selection( [('lb', 'lb'), ('kg', 'kg')], string='Weight UoM', default='lb', ) # Multi-piece label storage. label_attachment_id remains the # primary (first box) for backward-compat; this M2M holds the full # set so the operator can download any box's label individually. x_fc_label_attachment_ids = fields.Many2many( 'ir.attachment', 'fusion_shipment_label_attachment_rel', 'shipment_id', 'attachment_id', string='All Labels', copy=False, ) # Phase C — resolved carrier tracking URL with the tracking number # substituted into the carrier.tracking_url template. Used by the # shipment_labeled email template and any other place that needs a # working clickable tracking link. Single source of truth so both # email + portal stay consistent. x_fc_tracking_url = fields.Char( string='Tracking URL (resolved)', compute='_compute_x_fc_tracking_url', help='carrier.tracking_url with replaced ' 'by tracking_number. Empty when the carrier has no URL ' 'template or there is no tracking number yet.', ) @api.depends('carrier_id.tracking_url', 'tracking_number') def _compute_x_fc_tracking_url(self): for rec in self: tpl = (rec.carrier_id.tracking_url or '') if rec.carrier_id else '' tn = rec.tracking_number or '' if not tpl or not tn: rec.x_fc_tracking_url = '' continue placeholder = '' if placeholder in tpl: rec.x_fc_tracking_url = tpl.replace(placeholder, tn) else: rec.x_fc_tracking_url = tpl + tn def write(self, vals): """Sync the carrier tracking number + label to the customer portal job whenever they land on the shipment. The portal_job currently shows `delivery.name` as 'tracking' — wrong; the customer wants the carrier's actual tracking number so the clickable link goes to FedEx/UPS/etc.""" res = super().write(vals) sync_keys = {'tracking_number', 'label_attachment_id', 'status'} if not sync_keys & set(vals.keys()): return res for ship in self: try: ship._fp_sync_to_portal_job() except Exception as e: _logger.warning( 'Shipment %s: portal-job sync failed: %s', ship.name, e, ) return res def _fp_sync_to_portal_job(self): """Walk shipment → SO → fp.job → fusion.plating.portal.job and push the carrier tracking number + label + delivery's packing slip onto the customer-facing record. """ self.ensure_one() if not self.sale_order_id: return Job = self.env.get('fp.job') if Job is None: return jobs = Job.sudo().search( [('sale_order_id', '=', self.sale_order_id.id)], ) if not jobs: return for job in jobs: portal = job.portal_job_id if not portal: continue vals = {} if self.tracking_number and portal.tracking_ref != self.tracking_number: vals['tracking_ref'] = self.tracking_number # Packing slip lives on the linked fp.delivery, not the # shipment. Walk it lazily here so a packing-slip generated # earlier on the delivery also lands on the portal job. delivery = job.delivery_id if (delivery and 'packing_list_attachment_id' in delivery._fields and delivery.packing_list_attachment_id and portal.packing_list_attachment_id != delivery.packing_list_attachment_id): vals['packing_list_attachment_id'] = ( delivery.packing_list_attachment_id.id ) # Once a tracking number exists, the parts have been picked # by the carrier (or are about to be) — advance the portal # state to 'shipped' so the customer sees their order is # on its way. The 'delivered' status flips when FedEx # tracking reports the delivery. if self.tracking_number and portal.state in ( 'received', 'in_progress', 'ready_to_ship', ): vals['state'] = 'shipped' if vals: portal.sudo().write(vals)