Initial commit
This commit is contained in:
192
Fusion Accounting/models/debit_note.py
Normal file
192
Fusion Accounting/models/debit_note.py
Normal file
@@ -0,0 +1,192 @@
|
||||
# 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"),
|
||||
}
|
||||
Reference in New Issue
Block a user