Prevent to select reconciled payment or group on draft statement line

issue11175
review381781002
This commit is contained in:
Cédric Krier committed 2022-04-11 20:51:41 +02:00
1 parent 03148cc871
commit bb398854c0
9 files changed
+164 -3

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
* Prevent to select reconciled payment or group on draft statement line
* Reconcile payment clearing line with all statement lines
* Use reference field for statement line relation
* Add support for Python 3.10
+1
View File
@@ -12,6 +12,7 @@ def register():
payment.Group,
payment.SucceedStart,
account.Move,
account.MoveReconciliation,
ir.Cron,
module='account_payment_clearing', type_='model')
Pool.register(
+46 -1
View File
@@ -1,6 +1,7 @@
# 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 trytond.pool import PoolMeta
from trytond.pool import Pool, PoolMeta
from trytond.tools import grouped_slice
class Move(metaclass=PoolMeta):
@@ -10,3 +11,47 @@ class Move(metaclass=PoolMeta):
def _get_origin(cls):
models = super(Move, cls)._get_origin()
return models + ['account.payment']
def _payments_to_update(reconciliations):
pool = Pool()
Payment = pool.get('account.payment')
Reconciliation = pool.get('account.move.reconciliation')
moves = set()
others = set()
for reconciliation in reconciliations:
for line in reconciliation.lines:
moves.add(line.move)
others.update(line.reconciliations_delegated)
payments = set()
for sub_moves in grouped_slice(moves):
payments.update(Payment.search([
('clearing_move', 'in', [m.id for m in sub_moves]),
], order=[]))
if others:
payments.update(_payments_to_update(Reconciliation.browse(others)))
return payments
class MoveReconciliation(metaclass=PoolMeta):
__name__ = 'account.move.reconciliation'
@classmethod
def create(cls, vlist):
pool = Pool()
Payment = pool.get('account.payment')
reconciliations = super().create(vlist)
Payment.__queue__.update_reconciled(
list(_payments_to_update(reconciliations)))
return reconciliations
@classmethod
def delete(cls, reconciliations):
pool = Pool()
Payment = pool.get('account.payment')
payments = _payments_to_update(reconciliations)
super().delete(reconciliations)
Payment.__queue__.update_reconciled(list(payments))
+66
View File
@@ -3,9 +3,13 @@
from collections import defaultdict
from functools import wraps
from sql.aggregate import BoolAnd, Min
from trytond import backend
from trytond.model import ModelView, Workflow, fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Bool, Eval
from trytond.tools import grouped_slice, reduce_ids
from trytond.transaction import Transaction
from trytond.wizard import Button, StateTransition, StateView, Wizard
@@ -94,6 +98,7 @@ def cancel_clearing_move(func):
for party in to_reconcile:
for lines in to_reconcile[party].values():
Line.reconcile(lines)
cls.update_reconciled(payments)
return wrapper
@@ -120,6 +125,12 @@ class Payment(metaclass=PoolMeta):
help="Define the account to use for clearing move.")
clearing_move = fields.Many2One('account.move', 'Clearing Move',
readonly=True)
clearing_reconciled = fields.Boolean(
"Clearing Reconciled", select=True,
states={
'invisible': ~Eval('clearing_move'),
},
help="Checked if clearing line is reconciled.")
@property
def amount_line_paid(self):
@@ -190,6 +201,7 @@ class Payment(metaclass=PoolMeta):
to_reconcile.append(lines)
Line.reconcile(*to_reconcile)
cls.reconcile_clearing(payments)
cls.update_reconciled(payments)
@property
def clearing_account(self):
@@ -276,6 +288,16 @@ class Payment(metaclass=PoolMeta):
def fail(cls, payments):
super(Payment, cls).fail(payments)
@classmethod
def update_reconciled(cls, payments):
for payment in payments:
if payment.clearing_move:
payment.clearing_reconciled = all(
l.reconciliation for l in payment.clearing_lines)
else:
payment.clearing_reconciled = False
cls.save(payments)
@classmethod
def reconcile_clearing(cls, payments):
pool = Pool()
@@ -317,6 +339,50 @@ class Payment(metaclass=PoolMeta):
class Group(metaclass=PoolMeta):
__name__ = 'account.payment.group'
clearing_reconciled = fields.Function(fields.Boolean(
"Clearing Reconciled",
help="All payments in the group are reconciled."),
'get_reconciled', searcher='search_reconciled')
@classmethod
def get_reconciled(cls, groups, name):
pool = Pool()
Payment = pool.get('account.payment')
payment = Payment.__table__()
cursor = Transaction().connection.cursor()
result = defaultdict()
if backend.name == 'sqlite':
column = Min(payment.clearing_reconciled)
else:
column = BoolAnd(payment.clearing_reconciled)
for sub_groups in grouped_slice(groups):
cursor.execute(*payment.select(
payment.group, column,
where=reduce_ids(payment.group, sub_groups),
group_by=payment.group))
result.update(cursor)
return result
@classmethod
def search_reconciled(cls, name, clause):
pool = Pool()
Payment = pool.get('account.payment')
payment = Payment.__table__()
_, operator, value = clause
Operator = fields.SQL_OPERATORS[operator]
if backend.name == 'sqlite':
column = Min(payment.clearing_reconciled)
else:
column = BoolAnd(payment.clearing_reconciled)
query = payment.select(
payment.group,
having=Operator(column, value),
group_by=payment.group)
return [('id', 'in', query)]
@classmethod
def reconcile_clearing(cls, groups):
pool = Pool()
+6
View File
@@ -22,6 +22,12 @@ this repository contains the full copyright notices and license terms. -->
<field name="name">payment_list</field>
</record>
<record model="ir.ui.view" id="payment_group_view_list">
<field name="model">account.payment.group</field>
<field name="inherit" ref="account_payment.payment_group_view_list"/>
<field name="name">payment_group_list</field>
</record>
<record model="ir.action.wizard" id="wizard_succeed">
<field name="name">Succeed Payments</field>
<field name="wiz_name">account.payment.succeed</field>
+11 -2
View File
@@ -4,7 +4,7 @@ from collections import defaultdict
from trytond.model import fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
from trytond.pyson import Eval, If
from trytond.transaction import Transaction
@@ -98,9 +98,18 @@ class StatementLine(metaclass=PoolMeta):
@classmethod
def __setup__(cls):
super(StatementLine, cls).__setup__()
cls.related_to.domain['account.payment'] = [
cls.related_to.domain.get('account.payment', []),
If(Eval('statement_state') == 'draft',
('clearing_reconciled', '!=', True),
()),
]
cls.related_to.domain['account.payment.group'] = [
('company', '=', Eval('company', -1)),
('currency', '=', Eval('currency', -1))
('currency', '=', Eval('currency', -1)),
If(Eval('statement_state') == 'draft',
('clearing_reconciled', '!=', True),
()),
]
@classmethod
@@ -119,6 +119,8 @@ Succeed payment::
True
>>> payment.clearing_move.state
'draft'
>>> bool(payment.clearing_reconciled)
False
>>> payable.reload()
>>> payable.balance
Decimal('-20.00')
@@ -133,6 +135,8 @@ Fail payment::
>>> payment.state
'failed'
>>> payment.clearing_move
>>> bool(payment.clearing_reconciled)
False
>>> payment.line.reconciliation
>>> payable.reload()
>>> payable.balance
@@ -214,6 +218,8 @@ Fail payment with posted clearing::
>>> payment.state
'failed'
>>> payment.clearing_move
>>> bool(payment.clearing_reconciled)
False
>>> payment.line.reconciliation
>>> clearing_move.reload()
>>> line, = [l for l in clearing_move.lines
@@ -284,6 +290,20 @@ Validate statement::
>>> bank_clearing.reload()
>>> bank_clearing.balance
Decimal('0.00')
>>> payment.reload()
>>> bool(payment.clearing_reconciled)
True
>>> bool(payment.group.clearing_reconciled)
True
Unreconcile payment clearing to allow reimbursement::
>>> move_line.reconciliation.delete()
>>> payment.reload()
>>> bool(payment.clearing_reconciled)
False
>>> bool(payment.group.clearing_reconciled)
False
Create a statement that reimburse the payment group::
@@ -460,6 +480,8 @@ Validate statement and check the payment is confirmed::
>>> payment.reload()
>>> payment.state
'succeeded'
>>> bool(payment.clearing_reconciled)
True
>>> debit_line, = [l for l in payment.clearing_move.lines if l.debit > 0]
>>> debit_line.debit
Decimal('50.00')
+8
View File
@@ -0,0 +1,8 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//field[@name='payment_complete']" position="after">
<field name="clearing_reconciled"/>
</xpath>
</data>
+3
View File
@@ -2,6 +2,9 @@
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//field[@name='state']" position="after">
<field name="clearing_reconciled"/>
</xpath>
<xpath expr="//button[@name='succeed']" position="replace_attributes">
<button name="succeed_wizard"/>
</xpath>