Files
Odoo-Modules/Fusion Accounting/models/debit_note.py
2026-02-22 01:22:18 -05:00

193 lines
7.0 KiB
Python

# 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"),
}