diff --git a/fusion-plating/fusion_plating_invoicing/models/account_move.py b/fusion-plating/fusion_plating_invoicing/models/account_move.py index 0c8fe912..e3624563 100644 --- a/fusion-plating/fusion_plating_invoicing/models/account_move.py +++ b/fusion-plating/fusion_plating_invoicing/models/account_move.py @@ -2,4 +2,27 @@ # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) # Part of the Fusion Plating product family. -# Placeholder — implemented in a later task. + +from odoo import models, _ +from odoo.exceptions import UserError + + +class AccountMove(models.Model): + _inherit = 'account.move' + + def action_post(self): + """Check account hold before posting invoices.""" + for move in self: + if move.move_type in ('out_invoice', 'out_refund') and move.partner_id: + if move.partner_id.x_fc_account_hold: + is_manager = self.env.user.has_group( + 'fusion_plating.group_fusion_plating_manager' + ) + 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.' + ) % (move.partner_id.name, + move.partner_id.x_fc_account_hold_reason or 'No reason specified')) + return super().action_post() diff --git a/fusion-plating/fusion_plating_invoicing/models/sale_order.py b/fusion-plating/fusion_plating_invoicing/models/sale_order.py index 0c8fe912..dcd52878 100644 --- a/fusion-plating/fusion_plating_invoicing/models/sale_order.py +++ b/fusion-plating/fusion_plating_invoicing/models/sale_order.py @@ -2,4 +2,105 @@ # Copyright 2026 Nexa Systems Inc. # License OPL-1 (Odoo Proprietary License v1.0) # Part of the Fusion Plating product family. -# Placeholder — implemented in a later task. + +import logging + +from odoo import api, fields, models, _ +from odoo.exceptions import UserError + +_logger = logging.getLogger(__name__) + + +class SaleOrder(models.Model): + _inherit = 'sale.order' + + @api.onchange('partner_id') + def _onchange_partner_id_invoice_strategy(self): + """Auto-fill invoice strategy from customer defaults.""" + if self.partner_id: + default = self.env['fp.invoice.strategy.default'].search( + [('partner_id', '=', self.partner_id.id)], limit=1, + ) + if default: + self.x_fc_invoice_strategy = default.default_strategy + self.x_fc_deposit_percent = default.default_deposit_percent + if default.payment_term_id: + self.payment_term_id = default.payment_term_id + + def action_confirm(self): + """Override to check account hold and trigger invoice strategy.""" + for order in self: + # --- Account hold check --- + if order.partner_id.x_fc_account_hold: + is_manager = self.env.user.has_group( + 'fusion_plating.group_fusion_plating_manager' + ) + if not is_manager: + raise UserError(_( + 'Cannot confirm — customer "%s" is on account hold.\n' + 'Reason: %s\n\n' + 'Contact a manager to override.' + ) % (order.partner_id.name, + order.partner_id.x_fc_account_hold_reason or 'No reason specified')) + else: + # Manager gets a warning in chatter but can proceed + order.message_post( + body=_( + 'Warning: Customer "%s" is on account hold (reason: %s). ' + 'Order confirmed by manager override.' + ) % (order.partner_id.name, + order.partner_id.x_fc_account_hold_reason or 'N/A'), + ) + + res = super().action_confirm() + + # --- Invoice strategy automation --- + for order in self: + strategy = order.x_fc_invoice_strategy + if not strategy: + continue + + if strategy == 'deposit' and order.x_fc_deposit_percent: + order._create_deposit_invoice() + elif strategy == 'cod_prepay': + order._create_full_invoice() + + return res + + def _create_deposit_invoice(self): + """Create a deposit (down payment) invoice for the deposit percentage.""" + self.ensure_one() + percent = self.x_fc_deposit_percent + if not percent or percent <= 0: + return + + try: + # Use Odoo's standard down payment mechanism + wizard = self.env['sale.advance.payment.inv'].create({ + 'advance_payment_method': 'percentage', + 'amount': percent, + }) + wizard.with_context(active_ids=self.ids, active_model='sale.order').create_invoices() + self.message_post( + body=_('Deposit invoice (%.0f%%) created automatically — strategy: Deposit.') % percent, + ) + except Exception as e: + _logger.warning('Failed to create deposit invoice for SO %s: %s', self.name, e) + self.message_post( + body=_('Failed to auto-create deposit invoice: %s. Create manually.') % str(e), + ) + + def _create_full_invoice(self): + """Create a full invoice immediately (COD/Prepay strategy).""" + self.ensure_one() + try: + invoices = self._create_invoices() + if invoices: + self.message_post( + body=_('Full invoice created automatically — strategy: COD / Prepay.'), + ) + except Exception as e: + _logger.warning('Failed to create COD invoice for SO %s: %s', self.name, e) + self.message_post( + body=_('Failed to auto-create invoice: %s. Create manually.') % str(e), + ) diff --git a/fusion-plating/fusion_plating_invoicing/views/fp_invoicing_menu.xml b/fusion-plating/fusion_plating_invoicing/views/fp_invoicing_menu.xml index 3859f8d4..801c0211 100644 --- a/fusion-plating/fusion_plating_invoicing/views/fp_invoicing_menu.xml +++ b/fusion-plating/fusion_plating_invoicing/views/fp_invoicing_menu.xml @@ -5,4 +5,33 @@ Part of the Fusion Plating product family. --> + + + + Account Holds + res.partner + list,form + [('x_fc_account_hold', '=', True)] + + + No customers on account hold + + + + + + + + + diff --git a/fusion-plating/fusion_plating_invoicing/views/sale_order_views.xml b/fusion-plating/fusion_plating_invoicing/views/sale_order_views.xml index 3859f8d4..a9c9ad9d 100644 --- a/fusion-plating/fusion_plating_invoicing/views/sale_order_views.xml +++ b/fusion-plating/fusion_plating_invoicing/views/sale_order_views.xml @@ -5,4 +5,21 @@ Part of the Fusion Plating product family. --> + + + + sale.order.form.fp.hold.banner + sale.order + + + + + Account Hold — This customer is on account hold. + SO confirmation, invoicing, and shipping are blocked for non-managers. + + + + +
+ No customers on account hold +