# -*- 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, _ from odoo.exceptions import UserError class AccountMove(models.Model): _inherit = 'account.move' # Mirrors the SO-side related field. See sale_order.py for the # rationale (dotted refs in view modifiers are fragile + hold lives # on the commercial partner). x_fc_partner_account_hold = fields.Boolean( string='Customer on Account Hold', related='partner_id.commercial_partner_id.x_fc_account_hold', store=True, readonly=True, ) @api.model_create_multi def create(self, vals_list): """Auto-inherit payment terms + customer PO# at creation time. Two defensive defaults so newly-created invoices come out compliant out of the box: 1. **invoice_payment_term_id** — pulled from the customer's property_payment_term_id (Net-30, COD, etc.). Without this the due date silently becomes "immediate", wrong for B2B. 2. **ref** (customer reference / PO#) — pulled from the source sale order's client_order_ref or x_fc_po_number. Customer AP teams reject invoices that don't quote their PO# back. We already populate this on the SO confirm path, but a manually-created invoice would miss it without this default. """ Partner = self.env['res.partner'] SO = self.env['sale.order'] for vals in vals_list: if vals.get('move_type') in ('out_invoice', 'out_refund'): if not vals.get('invoice_payment_term_id') and vals.get('partner_id'): partner = Partner.browse(vals['partner_id']) if partner.property_payment_term_id: vals['invoice_payment_term_id'] = partner.property_payment_term_id.id # Defensive PO#: invoice_origin links to the SO; pull the # customer ref from there if the caller didn't pass one. if not vals.get('ref') and vals.get('invoice_origin'): so = SO.search([('name', '=', vals['invoice_origin'])], limit=1) if so: vals['ref'] = ( so.client_order_ref or (so.x_fc_po_number if 'x_fc_po_number' in so._fields else False) or False ) return super().create(vals_list) def action_post(self): """Block post when: • customer is on account hold (existing rule), or • the invoice has no payment term (auto-fill missed it AND partner had no default — accountant must pick one). """ for move in self: if move.move_type in ('out_invoice', 'out_refund') and move.partner_id: hold_partner = move.partner_id.commercial_partner_id if hold_partner.x_fc_account_hold: is_manager = self.env['res.partner']._fp_user_can_override_account_hold() if not is_manager: raise UserError(_( 'Cannot post invoice — customer "%s" is on account hold.\n' 'Reason: %s\n\n' 'Contact a manager to override.' ) % (hold_partner.name, hold_partner.x_fc_account_hold_reason or 'No reason specified')) if not move.invoice_payment_term_id: raise UserError(_( 'Cannot post invoice "%s" — no payment terms set.\n\n' 'Pick payment terms (Net-30, COD, etc.) on the invoice, ' 'or set a default on the customer "%s" so future ' 'invoices inherit it automatically.' ) % (move.name or move.display_name, move.partner_id.name)) return super().action_post()