193 lines
7.0 KiB
Python
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"),
|
|
}
|