# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) # Part of the Fusion Plating product family. from odoo import api, fields, models class FpDelivery(models.Model): """Scheduled delivery of finished parts back to a customer. Lifecycle: draft → scheduled → en_route → delivered → refused → returned → cancelled A delivery references a job by soft reference (`job_ref`) because the job module is not yet built. Once the job module ships, this field can be deprecated in favour of a proper Many2one without migration. """ _name = 'fusion.plating.delivery' _description = 'Fusion Plating — Delivery' _inherit = ['mail.thread', 'mail.activity.mixin'] _order = 'scheduled_date desc, id desc' name = fields.Char( string='Reference', required=True, copy=False, default=lambda self: self._default_name(), tracking=True, ) partner_id = fields.Many2one( 'res.partner', string='Customer', required=True, tracking=True, ) delivery_address_id = fields.Many2one( 'res.partner', string='Delivery Address', help='Leave blank to use the customer default address.', ) contact_name = fields.Char( string='Contact Name', ) contact_phone = fields.Char( string='Contact Phone', ) job_ref = fields.Char( string='Job Reference', help='Soft reference to the job this delivery belongs to. ' 'Will become a Many2one once the job module ships.', tracking=True, ) scheduled_date = fields.Datetime( string='Scheduled Date', tracking=True, ) assigned_driver_id = fields.Many2one( 'hr.employee', string='Assigned Driver', tracking=True, domain=[('x_fc_is_driver', '=', True)], ) vehicle_id = fields.Many2one( 'fusion.plating.vehicle', string='Vehicle', tracking=True, ) source_facility_id = fields.Many2one( 'fusion.plating.facility', string='Source Facility', tracking=True, ) company_id = fields.Many2one( 'res.company', related='source_facility_id.company_id', store=True, readonly=True, ) tdg_required = fields.Boolean( string='TDG Required', tracking=True, ) coc_attachment_id = fields.Many2one( 'ir.attachment', string='Certificate of Conformance', ) packing_list_attachment_id = fields.Many2one( 'ir.attachment', string='Packing List', ) state = fields.Selection( [ ('draft', 'Draft'), ('scheduled', 'Scheduled'), ('en_route', 'En Route'), ('delivered', 'Delivered'), ('refused', 'Refused'), ('returned', 'Returned'), ('cancelled', 'Cancelled'), ], string='Status', default='draft', required=True, tracking=True, ) delivered_at = fields.Datetime( string='Delivered At', readonly=True, ) pod_id = fields.Many2one( 'fusion.plating.proof.of.delivery', string='Proof of Delivery', copy=False, ) notes = fields.Html( string='Notes', ) custody_event_ids = fields.One2many( 'fusion.plating.chain.of.custody', 'delivery_id', string='Custody Events', ) custody_event_count = fields.Integer( compute='_compute_custody_count', ) @api.model def _default_name(self): seq = self.env['ir.sequence'].next_by_code('fusion.plating.delivery') return seq or '/' def _compute_custody_count(self): for rec in self: rec.custody_event_count = len(rec.custody_event_ids) def _log_custody_event(self, event_type, from_party=None, to_party=None): self.ensure_one() self.env['fusion.plating.chain.of.custody'].create({ 'event_datetime': fields.Datetime.now(), 'event_type': event_type, 'from_party': from_party or '', 'to_party': to_party or '', 'delivery_id': self.id, 'facility_id': self.source_facility_id.id, 'recorded_by_id': self.env.user.id, }) # ========================================================================== # Actions # ========================================================================== def action_schedule(self): self.write({'state': 'scheduled'}) def action_start_route(self): for rec in self: rec.write({'state': 'en_route'}) rec._log_custody_event( 'loaded_on_vehicle', from_party=(rec.source_facility_id.display_name or 'Facility'), to_party=(rec.assigned_driver_id.display_name or rec.vehicle_id.display_name or 'Driver'), ) def action_mark_delivered(self): for rec in self: rec.write({ 'state': 'delivered', 'delivered_at': fields.Datetime.now(), }) rec._log_custody_event( 'delivered_to_customer', from_party=(rec.assigned_driver_id.display_name or rec.vehicle_id.display_name or 'Driver'), to_party=rec.partner_id.display_name, ) def action_mark_refused(self): self.write({'state': 'refused'}) def action_mark_returned(self): self.write({'state': 'returned'}) def action_cancel(self): self.write({'state': 'cancelled'}) def action_reset_to_draft(self): self.write({'state': 'draft'}) def action_create_pod(self): """Create a blank POD record for this delivery and open it.""" self.ensure_one() pod = self.env['fusion.plating.proof.of.delivery'].create({ 'delivery_id': self.id, 'delivered_at': fields.Datetime.now(), }) self.pod_id = pod.id return { 'type': 'ir.actions.act_window', 'res_model': 'fusion.plating.proof.of.delivery', 'res_id': pod.id, 'view_mode': 'form', 'target': 'current', }