# Fusion Accounting - Debit Note Creation # Copyright (C) 2026 Nexa Systems Inc. (https://nexasystems.ca) # Original implementation for the Fusion Accounting module. # # Extends account.move with the ability to create debit notes from # existing invoices. A debit note copies the invoice lines with # reversed sign and links back to the original document. import logging from odoo import api, fields, models, Command, _ from odoo.exceptions import UserError _logger = logging.getLogger(__name__) class FusionDebitNote(models.Model): """Extends account.move with debit note creation from invoices. A *debit note* is an additional charge issued to a customer or received from a vendor. Unlike a credit note (which reduces the amount owed), a debit note increases it. This implementation: - Copies all product lines from the source invoice - Creates a new invoice of the same type (not reversed) - Links the debit note back to the original document """ _inherit = 'account.move' # ===================================================================== # Fields # ===================================================================== fusion_debit_note_origin_id = fields.Many2one( comodel_name='account.move', string="Debit Note Origin", copy=False, readonly=True, index=True, help="The original invoice from which this debit note was created.", ) fusion_debit_note_ids = fields.One2many( comodel_name='account.move', inverse_name='fusion_debit_note_origin_id', string="Debit Notes", copy=False, readonly=True, help="Debit notes created from this invoice.", ) fusion_debit_note_count = fields.Integer( string="Debit Note Count", compute='_compute_debit_note_count', ) # ===================================================================== # Computed Fields # ===================================================================== @api.depends('fusion_debit_note_ids') def _compute_debit_note_count(self): for move in self: move.fusion_debit_note_count = len(move.fusion_debit_note_ids) # ===================================================================== # Debit Note Creation # ===================================================================== def action_create_debit_note(self): """Create a debit note from the current invoice. The debit note is a new invoice document with the same type as the original. All product lines are copied. The amounts remain positive (this is an additional charge, not a reversal). Supported source types: - Customer Invoice (out_invoice) → Customer Debit Note (out_invoice) - Vendor Bill (in_invoice) → Vendor Debit Note (in_invoice) :returns: action dict to open the newly created debit note :raises UserError: if the move type is unsupported """ self.ensure_one() if self.move_type not in ('out_invoice', 'in_invoice'): raise UserError(_( "Debit notes can only be created from customer invoices " "or vendor bills." )) if self.state == 'draft': raise UserError(_( "Please confirm the invoice before creating a debit note." )) # Build line values from original invoice line_vals = [] for line in self.invoice_line_ids.filtered( lambda l: l.display_type == 'product' ): line_vals.append(Command.create({ 'name': _("Debit Note: %s", line.name or ''), 'product_id': line.product_id.id if line.product_id else False, 'product_uom_id': line.product_uom_id.id if line.product_uom_id else False, 'quantity': line.quantity, 'price_unit': line.price_unit, 'discount': line.discount, 'tax_ids': [Command.set(line.tax_ids.ids)], 'analytic_distribution': line.analytic_distribution, 'account_id': line.account_id.id, })) # Copy section and note lines for context for line in self.invoice_line_ids.filtered( lambda l: l.display_type in ('line_section', 'line_note') ): line_vals.append(Command.create({ 'display_type': line.display_type, 'name': line.name, 'sequence': line.sequence, })) if not line_vals: raise UserError(_( "The invoice has no lines to copy for the debit note." )) debit_note_vals = { 'move_type': self.move_type, 'partner_id': self.partner_id.id, 'journal_id': self.journal_id.id, 'currency_id': self.currency_id.id, 'company_id': self.company_id.id, 'invoice_date': fields.Date.context_today(self), 'ref': _("DN: %s", self.name), 'narration': _("Debit Note for %s", self.name), 'fiscal_position_id': self.fiscal_position_id.id if self.fiscal_position_id else False, 'invoice_payment_term_id': self.invoice_payment_term_id.id if self.invoice_payment_term_id else False, 'fusion_debit_note_origin_id': self.id, 'invoice_line_ids': line_vals, } debit_note = self.env['account.move'].create(debit_note_vals) _logger.info( "Fusion Debit Note: created %s (id=%s) from %s (id=%s)", debit_note.name, debit_note.id, self.name, self.id, ) # Return action to view the debit note if self.move_type == 'out_invoice': action_ref = 'account.action_move_out_invoice_type' else: action_ref = 'account.action_move_in_invoice_type' return { 'type': 'ir.actions.act_window', 'res_model': 'account.move', 'res_id': debit_note.id, 'view_mode': 'form', 'target': 'current', 'name': _("Debit Note"), } # ===================================================================== # View Related Debit Notes # ===================================================================== def action_view_debit_notes(self): """Open the list of debit notes created from this invoice.""" self.ensure_one() debit_notes = self.fusion_debit_note_ids if len(debit_notes) == 1: return { 'type': 'ir.actions.act_window', 'res_model': 'account.move', 'res_id': debit_notes.id, 'view_mode': 'form', 'target': 'current', 'name': _("Debit Note"), } return { 'type': 'ir.actions.act_window', 'res_model': 'account.move', 'domain': [('id', 'in', debit_notes.ids)], 'view_mode': 'list,form', 'target': 'current', 'name': _("Debit Notes"), }