first commit

This commit is contained in:
root
2026-03-14 09:42:12 +00:00
commit 0adbd20c2c
10991 changed files with 1646955 additions and 0 deletions

View File

@@ -0,0 +1,360 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from collections import defaultdict
from itertools import groupby
from sql import Literal, Null
from sql.operators import Equal
from trytond.i18n import gettext
from trytond.model import (
Exclude, ModelSQL, ModelView, Workflow, dualmethod, fields)
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
from trytond.tools import sortable_values
from trytond.transaction import Transaction
from .exceptions import ClosePeriodWarning
def _tax_group(tax):
while tax.parent:
tax = tax.parent
return tax.group
class FiscalYear(metaclass=PoolMeta):
__name__ = 'account.fiscalyear'
tax_group_on_cash_basis = fields.Many2Many(
'account.tax.group.cash', 'fiscalyear', 'tax_group',
"Tax Group On Cash Basis",
help="The tax group reported on cash basis for this fiscal year.")
class Period(metaclass=PoolMeta):
__name__ = 'account.period'
tax_group_on_cash_basis = fields.Many2Many(
'account.tax.group.cash', 'period', 'tax_group',
"Tax Group On Cash Basis",
help="The tax group reported on cash basis for this period.")
def is_on_cash_basis(self, tax):
if not tax:
return False
group = _tax_group(tax)
return (group in self.tax_group_on_cash_basis
or group in self.fiscalyear.tax_group_on_cash_basis)
@classmethod
@ModelView.button
@Workflow.transition('closed')
def close(cls, periods):
pool = Pool()
MoveLine = pool.get('account.move.line')
Warning = pool.get('res.user.warning')
super().close(periods)
for period in periods:
if (period.tax_group_on_cash_basis
or period.fiscalyear.tax_group_on_cash_basis):
move_lines = MoveLine.search([
('move.period', '=', period.id),
('reconciliation', '=', None),
('invoice_payment', '=', None),
['OR', [
('account.type.receivable', '=', True),
('credit', '>', 0),
], [
('account.type.payable', '=', True),
('debit', '<', 0),
],
],
])
if move_lines:
warning_name = Warning.format(
'period_close_line_payment', move_lines)
if Warning.check(warning_name):
raise ClosePeriodWarning(warning_name,
gettext('account_tax_cash'
'.msg_close_period_line_payment',
period=period.rec_name))
class TaxGroupCash(ModelSQL):
__name__ = 'account.tax.group.cash'
fiscalyear = fields.Many2One(
'account.fiscalyear', "Fiscal Year", ondelete='CASCADE')
period = fields.Many2One(
'account.period', "Period", ondelete='CASCADE')
party = fields.Many2One(
'party.party', "Party", ondelete='CASCADE')
tax_group = fields.Many2One(
'account.tax.group', "Tax Group", ondelete='CASCADE', required=True)
class Tax(metaclass=PoolMeta):
__name__ = 'account.tax'
@classmethod
def _amount_where(cls, tax_line, move_line, move):
context = Transaction().context
periods = context.get('periods', [])
where = super()._amount_where(tax_line, move_line, move)
return ((where
& (tax_line.on_cash_basis == Literal(False))
| (tax_line.on_cash_basis == Null))
| ((tax_line.period.in_(periods) if periods else Literal(False))
& (tax_line.on_cash_basis == Literal(True))))
@classmethod
def _amount_domain(cls):
context = Transaction().context
periods = context.get('periods', [])
domain = super()._amount_domain()
return ['OR',
[domain,
('on_cash_basis', '=', False),
],
[('period', 'in', periods),
('on_cash_basis', '=', True),
]]
class TaxLine(metaclass=PoolMeta):
__name__ = 'account.tax.line'
on_cash_basis = fields.Boolean("On Cash Basis")
period = fields.Many2One('account.period', "Period",
domain=[
('company', '=', Eval('company', -1)),
],
states={
'invisible': ~Eval('on_cash_basis', False),
})
@classmethod
def __setup__(cls):
super().__setup__()
t = cls.__table__()
cls._sql_constraints = [
('tax_type_move_line_cash_basis_no_period',
Exclude(
t,
(t.tax, Equal),
(t.type, Equal),
(t.move_line, Equal),
where=(t.on_cash_basis == Literal(True))
& (t.period == Null)),
'account_tax_cash.'
'msg_tax_type_move_line_cash_basis_no_period_unique'),
]
@classmethod
def default_on_cash_basis(cls):
return False
@property
def period_checked(self):
period = super().period_checked
if self.on_cash_basis:
period = self.period
return period
@classmethod
def group_cash_basis_key(cls, line):
return (
('tax', line.tax),
('type', line.type),
('move_line', line.move_line),
('on_cash_basis', line.on_cash_basis),
)
@classmethod
def update_cash_basis(cls, lines, ratio, period):
if not lines:
return
to_save = []
lines = cls.browse(sorted(
lines, key=sortable_values(cls.group_cash_basis_key)))
for key, lines in groupby(lines, key=cls.group_cash_basis_key):
key = dict(key)
if not key['on_cash_basis']:
continue
lines = list(lines)
company = lines[0].company
line_no_periods = [l for l in lines if not l.period]
if line_no_periods:
line_no_period, = line_no_periods
else:
line_no_period = None
total = sum(l.amount for l in lines)
amount = total * ratio - sum(l.amount for l in lines if l.period)
amount = company.currency.round(amount)
if amount:
if line_no_period and line_no_period.amount == amount:
line_no_period.period = period
else:
line = cls(**key, amount=amount)
if line_no_period:
line_no_period.amount -= line.amount
line.period = period
if line.amount:
to_save.append(line)
if line_no_period:
to_save.append(line_no_period)
cls.save(to_save)
class Move(metaclass=PoolMeta):
__name__ = 'account.move'
@dualmethod
@ModelView.button
def post(cls, moves):
pool = Pool()
TaxLine = pool.get('account.tax.line')
super().post(moves)
tax_lines = []
for move in moves:
period = move.period
for line in move.lines:
for tax_line in line.tax_lines:
if (not tax_line.on_cash_basis
and period.is_on_cash_basis(tax_line.tax)):
tax_lines.append(tax_line)
TaxLine.write(tax_lines, {
'on_cash_basis': True,
})
class Invoice(metaclass=PoolMeta):
__name__ = 'account.invoice'
tax_group_on_cash_basis = fields.Many2Many(
'account.invoice.tax.group.cash', 'invoice', 'tax_group',
"Tax Group On Cash Basis",
states={
'invisible': Eval('type') != 'in',
},
help="The tax group reported on cash basis for this invoice.")
@fields.depends('party', 'type', 'tax_group_on_cash_basis')
def on_change_party(self):
super().on_change_party()
if self.type == 'in' and self.party:
self.tax_group_on_cash_basis = (
self.party.supplier_tax_group_on_cash_basis)
else:
self.tax_group_on_cash_basis = []
def get_move(self):
move = super().get_move()
if self.tax_group_on_cash_basis:
for line in move.lines:
for tax_line in getattr(line, 'tax_lines', []):
tax = tax_line.tax
if tax and _tax_group(tax) in self.tax_group_on_cash_basis:
tax_line.on_cash_basis = True
return move
@property
def cash_paid_ratio(self):
amount = sum(l.debit - l.credit for l in self.lines_to_pay)
if self.state == 'paid':
ratio = 1
elif self.state == 'cancelled':
ratio = 0
else:
payment_amount = sum(
l.debit - l.credit for l in self.payment_lines
if not l.reconciliation)
payment_amount -= sum(
l.debit - l.credit for l in self.lines_to_pay
if l.reconciliation)
if amount:
ratio = abs(payment_amount / amount)
else:
ratio = 0
assert 0 <= ratio <= 1
return ratio
@classmethod
def on_modification(cls, mode, invoices, field_names=None):
super().on_modification(mode, invoices, field_names=field_names)
if mode == 'write' and 'payment_lines' in field_names:
cls._update_tax_cash_basis(invoices)
@classmethod
def process(cls, invoices):
super().process(invoices)
cls._update_tax_cash_basis(invoices)
@classmethod
@ModelView.button
@Workflow.transition('cancelled')
def cancel(cls, invoices):
super().cancel(invoices)
cls._update_tax_cash_basis(invoices)
@classmethod
def _update_tax_cash_basis(cls, invoices):
pool = Pool()
TaxLine = pool.get('account.tax.line')
Date = pool.get('ir.date')
Period = pool.get('account.period')
# Call update_cash_basis grouped per period and ratio only because
# group_cash_basis_key already group per move_line.
to_update = defaultdict(list)
periods = {}
for invoice in invoices:
if not invoice.move:
continue
if invoice.company not in periods:
with Transaction().set_context(company=invoice.company.id):
date = Transaction().context.get(
'payment_date', Date.today())
periods[invoice.company] = Period.find(
invoice.company, date=date)
period = periods[invoice.company]
ratio = invoice.cash_paid_ratio
for line in invoice.move.lines:
to_update[(period, ratio)].extend(line.tax_lines)
for (period, ratio), tax_lines in to_update.items():
TaxLine.update_cash_basis(tax_lines, ratio, period)
class InvoiceTax(metaclass=PoolMeta):
__name__ = 'account.invoice.tax'
@property
def on_cash_basis(self):
pool = Pool()
Period = pool.get('account.period')
if self.invoice and self.tax:
if self.tax.group in self.invoice.tax_group_on_cash_basis:
return True
if self.invoice.move:
period = self.invoice.move.period
else:
accounting_date = (
self.invoice.accounting_date or self.invoice.invoice_date)
period = Period.find(
self.invoice.company, date=accounting_date)
return period.is_on_cash_basis(self.tax)
class InvoiceTaxGroupCash(ModelSQL):
__name__ = 'account.invoice.tax.group.cash'
invoice = fields.Many2One(
'account.invoice', "Invoice", ondelete='CASCADE',
domain=[
('type', '=', 'in'),
])
tax_group = fields.Many2One(
'account.tax.group', "Tax Group", ondelete='CASCADE', required=True)