padding acc

This commit is contained in:
2026-04-26 14:35:07 +02:00
parent 9544418897
commit fbfa73110f
11 changed files with 590 additions and 14 deletions

View File

@@ -15,6 +15,180 @@ from trytond.modules.purchase.purchase import (
class Invoice(metaclass=PoolMeta):
__name__ = 'account.invoice'
def do_lot_invoicing(self):
super().do_lot_invoicing()
self._create_sale_padding_moves()
@classmethod
def _post(cls, invoices):
pool = Pool()
Move = pool.get('account.move')
super()._post(invoices)
padding_moves = []
for invoice in invoices:
padding_moves.extend(invoice._create_sale_padding_moves())
padding_moves.extend([
move for move in (invoice.additional_moves or [])
if move.description in {
invoice._get_sale_padding_move_description(False),
invoice._get_sale_padding_move_description(True),
}
and move.state != 'posted'
])
if padding_moves:
cls.save(invoices)
Move.post([m for m in padding_moves if m.state != 'posted'])
def _get_sale_padding_accounts(self):
Configuration = Pool().get('account.configuration')
config = Configuration(1)
sale_account = config.get_multivalue(
'default_sale_padding_account', company=self.company.id)
accrual_account = config.get_multivalue(
'default_accrual_padding_account', company=self.company.id)
if not sale_account or not accrual_account:
raise UserError(
'Default Sale Padding and Default Accrual Padding '
'accounts must be configured.')
return sale_account, accrual_account
def _has_sale_padding_move(self, reversal=False):
description = self._get_sale_padding_move_description(reversal)
return any(
move.description == description
for move in (self.additional_moves or []))
@staticmethod
def _get_sale_padding_move_description(reversal=False):
if reversal:
return 'Sale padding reversal'
return 'Sale padding accrual'
def _get_padding_company_amount(self, invoice_line, padding):
Currency = Pool().get('currency.currency')
invoice = invoice_line.invoice
amount = Decimal(str(padding or 0)) * Decimal(
str(invoice_line.unit_price or 0))
amount = invoice.currency.round(amount)
if invoice.currency == invoice.company.currency:
return amount, amount
if invoice.rate:
company_amount = invoice.company.currency.round(
amount / invoice.rate)
else:
with Transaction().set_context(date=invoice.currency_date):
company_amount = Currency.compute(
invoice.currency, amount, invoice.company.currency)
return amount, company_amount
def _get_sale_padding_entries(self):
if self.type != 'out':
return []
entries = []
for line in self.lines or []:
if getattr(line, 'type', None) != 'line':
continue
lot = getattr(line, 'lot', None)
if not lot:
continue
padding = Decimal(str(
getattr(lot, 'sale_invoice_padding', 0) or 0))
if padding <= 0:
continue
if self.reference == 'Provisional' and line.description == 'Pro forma':
entries.append((lot, line, padding, False))
elif self.reference == 'Final' and line.description == 'Final':
provisional_line = getattr(lot, 'sale_invoice_line_prov', None)
if provisional_line:
entries.append((lot, provisional_line, padding, True))
return entries
def _get_sale_padding_move_lines(self, entries):
MoveLine = Pool().get('account.move.line')
sale_account, accrual_account = self._get_sale_padding_accounts()
move_lines = []
for lot, invoice_line, padding, reversal in entries:
padding_amount, company_amount = self._get_padding_company_amount(
invoice_line, padding)
if not company_amount:
continue
sale_line = MoveLine()
accrual_line = MoveLine()
for move_line in (sale_line, accrual_line):
move_line.lot = lot
move_line.origin = invoice_line
if move_line.account and move_line.account.party_required:
move_line.party = self.party
if not reversal:
sale_line.account = sale_account
sale_line.debit = company_amount
sale_line.credit = Decimal(0)
accrual_line.account = accrual_account
accrual_line.debit = Decimal(0)
accrual_line.credit = company_amount
else:
accrual_line.account = accrual_account
accrual_line.debit = company_amount
accrual_line.credit = Decimal(0)
sale_line.account = sale_account
sale_line.debit = Decimal(0)
sale_line.credit = company_amount
if self.currency != self.company.currency:
sale_line.second_currency = self.currency
accrual_line.second_currency = self.currency
sale_line.amount_second_currency = padding_amount.copy_sign(
sale_line.debit - sale_line.credit)
accrual_line.amount_second_currency = padding_amount.copy_sign(
accrual_line.debit - accrual_line.credit)
if sale_line.account.party_required:
sale_line.party = self.party
if accrual_line.account.party_required:
accrual_line.party = self.party
move_lines.extend([sale_line, accrual_line])
return move_lines
def _create_sale_padding_moves(self):
pool = Pool()
Move = pool.get('account.move')
Period = pool.get('account.period')
Date = pool.get('ir.date')
entries = self._get_sale_padding_entries()
if not entries:
return []
reversal = entries[0][3]
if self._has_sale_padding_move(reversal):
return []
move_lines = self._get_sale_padding_move_lines(entries)
if not move_lines:
return []
with Transaction().set_context(company=self.company.id):
today = Date.today()
accounting_date = self.accounting_date or self.invoice_date or today
period = Period.find(self.company, date=accounting_date)
move = Move()
move.journal = self.journal
move.period = period
move.date = accounting_date
move.origin = self
move.company = self.company
move.description = self._get_sale_padding_move_description(reversal)
move.lines = move_lines
Move.save([move])
self.additional_moves = tuple(self.additional_moves or ()) + (move,)
return [move]
@staticmethod
def _format_report_number(value, digits='0.0000', keep_trailing_decimal=False,
strip_trailing_zeros=True):