Public release from ruodoo-project: 19.0 - 2026-05-31 21:19:12 UTC
This commit is contained in:
151
account_move_templates_invoice/models/account_move.py
Normal file
151
account_move_templates_invoice/models/account_move.py
Normal file
@ -0,0 +1,151 @@
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = ['account.move', 'account.move.template.mixin']
|
||||
|
||||
move_ids = fields.Many2many(
|
||||
comodel_name='account.move',
|
||||
relation='account_move_template_invoice_move_rel',
|
||||
column1='document_id',
|
||||
column2='move_id',
|
||||
string='Проводки по шаблону',
|
||||
)
|
||||
|
||||
journal_template_id = fields.Many2one(
|
||||
comodel_name='account.move.template',
|
||||
string='Шаблон типовой операции',
|
||||
default=lambda self: self._default_journal_template(),
|
||||
)
|
||||
|
||||
def _default_journal_template(self):
|
||||
use = self.env['ir.config_parameter'].sudo().get_param(
|
||||
'account_move_templates_invoice.use_journal_templates_for_invoices'
|
||||
)
|
||||
if use:
|
||||
return self.env['account.move.template'].search([], limit=1, order='id asc')
|
||||
return False
|
||||
|
||||
def get_move_line_amounts(self):
|
||||
"""Return invoice total as the base amount for the wizard."""
|
||||
self.ensure_one()
|
||||
return [{'name': self.name or '/', 'amount': self.amount_total}]
|
||||
|
||||
def _replace_lines_from_template(self, move):
|
||||
"""
|
||||
Replace ALL lines (product + payment_term) with template lines
|
||||
plus one explicit payment_term line with date_maturity.
|
||||
|
||||
We pass 'balance' explicitly in vals so _get_protected_vals protects
|
||||
it from being overwritten by _sync_invoice.
|
||||
"""
|
||||
_logger.info("TEMPLATE DEBUG: _replace_lines_from_template START for move %s", move.id)
|
||||
template = move.journal_template_id
|
||||
_logger.info("TEMPLATE DEBUG: template = %s", template)
|
||||
base_amount = move.amount_untaxed or move.amount_total
|
||||
_logger.info("TEMPLATE DEBUG: base_amount = %s", base_amount)
|
||||
|
||||
# Determine receivable/payable account
|
||||
partner = move.commercial_partner_id.with_company(move.company_id)
|
||||
if move.is_sale_document(include_receipts=True):
|
||||
term_account = partner.property_account_receivable_id
|
||||
else:
|
||||
term_account = partner.property_account_payable_id
|
||||
_logger.info("TEMPLATE DEBUG: term_account = %s", term_account)
|
||||
|
||||
# due date: use invoice_date_due, invoice_date, or today
|
||||
due_date = (
|
||||
move.invoice_date_due
|
||||
or move.invoice_date
|
||||
or fields.Date.context_today(move)
|
||||
)
|
||||
_logger.info("TEMPLATE DEBUG: due_date = %s", due_date)
|
||||
|
||||
# Remove only payment_term lines, keep product lines from invoice_line_ids
|
||||
_logger.info("TEMPLATE DEBUG: Removing existing payment_term lines")
|
||||
lines_to_remove = move.line_ids.filtered(lambda l: l.display_type == 'payment_term')
|
||||
_logger.info("TEMPLATE DEBUG: Lines to remove: %s", [(l.id, l.account_id.code, l.display_type) for l in lines_to_remove])
|
||||
lines_to_remove.with_context(
|
||||
dynamic_unlink=True,
|
||||
check_move_validity=False,
|
||||
skip_invoice_sync=True,
|
||||
).unlink()
|
||||
_logger.info("TEMPLATE DEBUG: payment_term lines removed")
|
||||
|
||||
AML = self.env['account.move.line']
|
||||
ctx = dict(check_move_validity=False, skip_invoice_sync=True)
|
||||
|
||||
# 1. Process template lines based on line_type
|
||||
_logger.info("TEMPLATE DEBUG: Processing template lines")
|
||||
|
||||
# Get existing product lines from invoice_line_ids
|
||||
product_lines = move.line_ids.filtered(lambda l: l.display_type == 'product')
|
||||
_logger.info("TEMPLATE DEBUG: Found %d product lines", len(product_lines))
|
||||
|
||||
# Separate template lines by type
|
||||
template_product_lines = template.line_ids.filtered(lambda l: l.line_type == 'product')
|
||||
template_payment_lines = template.line_ids.filtered(lambda l: l.line_type == 'payment')
|
||||
|
||||
_logger.info("TEMPLATE DEBUG: Template has %d product lines and %d payment lines",
|
||||
len(template_product_lines), len(template_payment_lines))
|
||||
|
||||
# Update existing product lines with accounts from template (line_type='product')
|
||||
for idx, product_line in enumerate(product_lines):
|
||||
if idx < len(template_product_lines):
|
||||
tpl_line = template_product_lines[idx]
|
||||
_logger.info("TEMPLATE DEBUG: Updating product line %d: old account=%s, new account=%s",
|
||||
product_line.id, product_line.account_id.code, tpl_line.account_id.code)
|
||||
product_line.with_context(**ctx).write({'account_id': tpl_line.account_id.id})
|
||||
|
||||
# Create payment lines from template (line_type='payment')
|
||||
_logger.info("TEMPLATE DEBUG: Creating payment lines from template")
|
||||
for tpl_line in template_payment_lines:
|
||||
line_amount = base_amount * tpl_line.percent / 100.0
|
||||
balance = line_amount if tpl_line.move_type == 'debit' else -line_amount
|
||||
_logger.info("TEMPLATE DEBUG: Creating payment line: account=%s, balance=%s",
|
||||
tpl_line.account_id.id, balance)
|
||||
|
||||
vals = {
|
||||
'move_id': move.id,
|
||||
'account_id': tpl_line.account_id.id,
|
||||
'name': template.name,
|
||||
'balance': balance,
|
||||
'amount_currency': balance,
|
||||
'date_maturity': due_date,
|
||||
'display_type': 'payment_term',
|
||||
}
|
||||
|
||||
AML.with_context(**ctx).create(vals)
|
||||
_logger.info("TEMPLATE DEBUG: Template lines processed")
|
||||
|
||||
_logger.info("TEMPLATE DEBUG: _replace_lines_from_template END")
|
||||
|
||||
def action_post(self):
|
||||
_logger.info("TEMPLATE DEBUG: action_post called for moves: %s", self.ids)
|
||||
use_templates = self.env['ir.config_parameter'].sudo().get_param(
|
||||
'account_move_templates_invoice.use_journal_templates_for_invoices'
|
||||
)
|
||||
_logger.info("TEMPLATE DEBUG: use_templates = %s", use_templates)
|
||||
|
||||
if use_templates:
|
||||
for move in self.filtered(
|
||||
lambda m: m.move_type in ('out_invoice', 'in_invoice')
|
||||
):
|
||||
_logger.info("TEMPLATE DEBUG: Processing move %s, template: %s", move.id, move.journal_template_id)
|
||||
if not move.journal_template_id:
|
||||
raise UserError(
|
||||
'Выберите шаблон типовой операции перед проведением.'
|
||||
)
|
||||
try:
|
||||
_logger.info("TEMPLATE DEBUG: Calling _replace_lines_from_template")
|
||||
self._replace_lines_from_template(move)
|
||||
_logger.info("TEMPLATE DEBUG: _replace_lines_from_template completed")
|
||||
except Exception as e:
|
||||
_logger.error("TEMPLATE DEBUG: Error in _replace_lines_from_template: %s", e, exc_info=True)
|
||||
raise
|
||||
|
||||
return super().action_post()
|
||||
Reference in New Issue
Block a user