Files
tryton/modules/account_payment_clearing/payment.py
T
Cédric Krier 696e8095cc Try to post only clearing move of journals from the contextual company
Closes #12453
(grafted from b71b06a871a7fb67a202f1ae242a04185078c614)

--HG--
branch : 6.0
2023-08-05 00:27:11 +02:00

296 lines
10 KiB
Python

# 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 functools import wraps
from trytond.pool import PoolMeta, Pool
from trytond.model import ModelView, Workflow, fields
from trytond.pyson import Eval, Bool
from trytond.transaction import Transaction
from trytond.wizard import Wizard, StateView, StateTransition, Button
class Journal(metaclass=PoolMeta):
__name__ = 'account.payment.journal'
clearing_account = fields.Many2One('account.account', 'Clearing Account',
domain=[
('type', '!=', None),
('closed', '!=', True),
('party_required', '=', False),
],
states={
'required': Bool(Eval('clearing_journal')),
},
depends=['clearing_journal'])
clearing_journal = fields.Many2One('account.journal', 'Clearing Journal',
states={
'required': Bool(Eval('clearing_account')),
},
depends=['clearing_account'])
clearing_posting_delay = fields.TimeDelta(
"Clearing Posting Delay",
help="Post automatically the clearing moves after the delay.\n"
"Leave empty for no posting.")
@classmethod
def cron_post_clearing_moves(cls, date=None):
pool = Pool()
Date = pool.get('ir.date')
Move = pool.get('account.move')
if date is None:
date = Date.today()
moves = []
journals = cls.search([
('company', '=', Transaction().context.get('company')),
('clearing_posting_delay', '!=', None),
])
for journal in journals:
move_date = date - journal.clearing_posting_delay
moves.extend(Move.search([
('date', '<=', move_date),
('origin.journal.id', '=', journal.id,
'account.payment'),
('state', '=', 'draft'),
('company', '=', journal.company.id),
]))
Move.post(moves)
def cancel_clearing_move(func):
@wraps(func)
def wrapper(cls, payments, *args, **kwargs):
pool = Pool()
Move = pool.get('account.move')
Line = pool.get('account.move.line')
Reconciliation = pool.get('account.move.reconciliation')
func(cls, payments, *args, **kwargs)
to_delete = []
to_reconcile = defaultdict(lambda: defaultdict(list))
to_unreconcile = []
for payment in payments:
if payment.clearing_move:
if payment.clearing_move.state == 'draft':
to_delete.append(payment.clearing_move)
for line in payment.clearing_move.lines:
if line.reconciliation:
to_unreconcile.append(line.reconciliation)
else:
cancel_move = payment.clearing_move.cancel()
for line in (payment.clearing_move.lines
+ cancel_move.lines):
if line.reconciliation:
to_unreconcile.append(line.reconciliation)
if line.account.reconcile:
to_reconcile[payment.party][line.account].append(
line)
# Remove clearing_move before delete
# in case reconciliation triggers use it.
cls.write(payments, {'clearing_move': None})
if to_unreconcile:
Reconciliation.delete(to_unreconcile)
if to_delete:
Move.delete(to_delete)
for party in to_reconcile:
for lines in to_reconcile[party].values():
Line.reconcile(lines)
return wrapper
class Payment(metaclass=PoolMeta):
__name__ = 'account.payment'
account = fields.Many2One(
'account.account', "Account", ondelete='RESTRICT',
domain=[
('closed', '!=', True),
('company', '=', Eval('company', -1)),
('type.statement', 'in', ['balance', 'off-balance']),
['OR',
('second_currency', '=', Eval('currency', None)),
[
('company.currency', '=', Eval('currency', None)),
('second_currency', '=', None),
],
],
],
states={
'readonly': Eval('state') != 'draft',
'invisible': Bool(Eval('line')),
},
depends=['company', 'currency', 'state', 'line'],
help="Define the account to use for clearing move.")
clearing_move = fields.Many2One('account.move', 'Clearing Move',
readonly=True)
@classmethod
def __setup__(cls):
super(Payment, cls).__setup__()
line_invisible = Bool(Eval('account'))
if 'invisible' in cls.line.states:
cls.line.states['invisible'] &= line_invisible
else:
cls.line.states['invisible'] = line_invisible
cls._buttons.update({
'succeed_wizard': cls._buttons['succeed'],
})
@classmethod
@ModelView.button_action('account_payment_clearing.wizard_succeed')
def succeed_wizard(cls, payments):
pass
@classmethod
@ModelView.button
@Workflow.transition('succeeded')
def succeed(cls, payments):
pool = Pool()
Move = pool.get('account.move')
Line = pool.get('account.move.line')
super(Payment, cls).succeed(payments)
moves = []
for payment in payments:
move = payment.create_clearing_move(
date=Transaction().context.get('clearing_date'))
if move:
moves.append(move)
if moves:
Move.save(moves)
cls.write(*sum((([m.origin], {'clearing_move': m.id})
for m in moves), ()))
to_reconcile = []
for payment in payments:
if (payment.line
and not payment.line.reconciliation
and payment.clearing_move):
lines = [l for l in payment.clearing_move.lines
if l.account == payment.line.account] + [payment.line]
if not sum(l.debit - l.credit for l in lines):
to_reconcile.append(lines)
Line.reconcile(*to_reconcile)
@property
def clearing_account(self):
if self.line:
return self.line.account
elif self.account:
return self.account
@property
def clearing_party(self):
if self.line:
return self.line.party
else:
return self.party
def create_clearing_move(self, date=None):
pool = Pool()
Move = pool.get('account.move')
Line = pool.get('account.move.line')
Currency = pool.get('currency.currency')
Period = pool.get('account.period')
Date = pool.get('ir.date')
if not self.clearing_account:
return
if (not self.journal.clearing_account
or not self.journal.clearing_journal):
return
if self.clearing_move:
return self.clearing_move
if date is None:
date = Date.today()
period = Period.find(self.company.id, date=date)
local_currency = self.journal.currency == self.company.currency
if not local_currency:
with Transaction().set_context(date=self.date):
local_amount = Currency.compute(
self.journal.currency, self.amount, self.company.currency)
else:
local_amount = self.amount
move = Move(journal=self.journal.clearing_journal, origin=self,
date=date, period=period, company=self.company)
line = Line()
if self.kind == 'payable':
line.debit, line.credit = local_amount, 0
else:
line.debit, line.credit = 0, local_amount
line.account = self.clearing_account
if not local_currency:
line.amount_second_currency = self.amount.copy_sign(
line.debit - line.credit)
line.second_currency = self.journal.currency
line.party = (self.clearing_party
if line.account.party_required else None)
counterpart = Line()
if self.kind == 'payable':
counterpart.debit, counterpart.credit = 0, local_amount
else:
counterpart.debit, counterpart.credit = local_amount, 0
counterpart.account = self.journal.clearing_account
if not local_currency:
counterpart.amount_second_currency = self.amount.copy_sign(
counterpart.debit - counterpart.credit)
counterpart.second_currency = self.journal.currency
move.lines = (line, counterpart)
return move
@classmethod
@Workflow.transition('processing')
@cancel_clearing_move
def proceed(cls, payments):
super().proceed(payments)
@classmethod
@ModelView.button
@Workflow.transition('failed')
@cancel_clearing_move
def fail(cls, payments):
super(Payment, cls).fail(payments)
@classmethod
def copy(cls, payments, default=None):
if default is None:
default = {}
else:
default = default.copy()
default.setdefault('clearing_move')
return super(Payment, cls).copy(payments, default=default)
class Succeed(Wizard):
"Succeed Payment"
__name__ = 'account.payment.succeed'
start = StateView('account.payment.succeed.start',
'account_payment_clearing.succeed_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Succeed', 'succeed', 'tryton-ok', default=True),
])
succeed = StateTransition()
def transition_succeed(self):
with Transaction().set_context(clearing_date=self.start.date):
self.model.succeed(self.records)
return 'end'
class SucceedStart(ModelView):
"Succeed Payment"
__name__ = 'account.payment.succeed.start'
date = fields.Date("Date", required=True)
@classmethod
def default_date(cls):
pool = Pool()
Date = pool.get('ir.date')
return Date.today()