details: https://code.tryton.org/tryton/commit/5b605ac1dc43
branch: default
user: Cédric Krier <[email protected]>
date: Wed Dec 31 15:57:40 2025 +0100
description:
Warn against creating an overpayment
Closes #14457
diffstat:
modules/account_payment/CHANGELOG | 1 +
modules/account_payment/account.py | 65 ++++-
modules/account_payment/message.xml | 3 +
modules/account_payment/tests/scenario_account_payment_overpay.rst | 101
++++++++++
4 files changed, 153 insertions(+), 17 deletions(-)
diffs (218 lines):
diff -r d9af1c8b229d -r 5b605ac1dc43 modules/account_payment/CHANGELOG
--- a/modules/account_payment/CHANGELOG Tue Dec 30 17:41:36 2025 +0100
+++ b/modules/account_payment/CHANGELOG Wed Dec 31 15:57:40 2025 +0100
@@ -1,3 +1,4 @@
+* Warn against creating an overpayment
Version 7.8.0 - 2025-12-15
--------------------------
diff -r d9af1c8b229d -r 5b605ac1dc43 modules/account_payment/account.py
--- a/modules/account_payment/account.py Tue Dec 30 17:41:36 2025 +0100
+++ b/modules/account_payment/account.py Wed Dec 31 15:57:40 2025 +0100
@@ -24,7 +24,7 @@
from trytond.wizard import (
Button, StateAction, StateTransition, StateView, Wizard)
-from .exceptions import BlockedWarning, GroupWarning
+from .exceptions import BlockedWarning, GroupWarning, OverpayWarning
from .payment import KINDS
@@ -412,9 +412,12 @@
def default_start(self, fields):
pool = Pool()
+ Currency = pool.get('currency.currency')
+ Lang = pool.get('ir.lang')
Line = pool.get('account.move.line')
Warning = pool.get('res.user.warning')
+ lang = Lang.get()
reverse = {'receivable': 'payable', 'payable': 'receivable'}
companies = {}
lines = self.records
@@ -442,22 +445,50 @@
('move_state', '=', 'posted'),
])
for party in parties:
- party_lines = [l for l in others if l.party == party]
- if not party_lines:
- continue
- lines = [l for l in types[kind]['lines']
- if l.party == party]
- warning_name = Warning.format(
- '%s:%s' % (reverse[kind], party), lines)
- if Warning.check(warning_name):
- names = ', '.join(l.rec_name for l in lines[:5])
- if len(lines) > 5:
- names += '...'
- raise GroupWarning(warning_name,
- gettext('account_payment.msg_pay_line_group',
- names=names,
- party=party.rec_name,
- line=party_lines[0].rec_name))
+ lines = [
+ l for l in types[kind]['lines'] if l.party == party]
+
+ if party_lines := [l for l in others if l.party == party]:
+ warning_name = Warning.format(
+ '%s:%s' % (reverse[kind], party), lines)
+ if Warning.check(warning_name):
+ names = ', '.join(l.rec_name for l in lines[:5])
+ if len(lines) > 5:
+ names += '...'
+ raise GroupWarning(warning_name,
+ gettext('account_payment.msg_pay_line_group',
+ names=names,
+ party=party.rec_name,
+ line=party_lines[0].rec_name))
+
+ payment_amount = 0
+ for line in lines:
+ payment_amount += Currency.compute(
+ line.payment_currency, line.payment_amount,
+ company.currency).copy_sign(
+ line.debit - line.credit)
+ payment_amount_sign = payment_amount.as_tuple().sign
+ amount = getattr(party, kind)
+ amount_sign = amount.as_tuple().sign
+ if (abs(payment_amount) > abs(amount)
+ and (payment_amount_sign == amount_sign
+ or payment_amount.is_zero()
+ or amount.is_zero())):
+ warning_name = Warning.format(
+ '%s:%s' % (kind, party), lines)
+ if Warning.check(warning_name):
+ names = ', '.join(
+ l.rec_name for l in lines[:5])
+ if len(lines) > 5:
+ names += '...'
+ raise OverpayWarning(warning_name,
+ gettext('account_payment.msg_pay_line_overpay',
+ names=names,
+ party=party.rec_name,
+ payment_amount=lang.currency(
+ payment_amount, company.currency),
+ amount=lang.currency(
+ amount, company.currency)))
return {}
def _get_journals(self):
diff -r d9af1c8b229d -r 5b605ac1dc43 modules/account_payment/message.xml
--- a/modules/account_payment/message.xml Tue Dec 30 17:41:36 2025 +0100
+++ b/modules/account_payment/message.xml Wed Dec 31 15:57:40 2025 +0100
@@ -24,6 +24,9 @@
<record model="ir.message" id="msg_pay_line_group">
<field name="text">The lines "%(names)s" for %(party)s could be
grouped with the line "%(line)s".</field>
</record>
+ <record model="ir.message" id="msg_pay_line_overpay">
+ <field name="text">The payment of %(payment_amount)s for lines
"%(names)s" exceeds the amount for %(party)s, which is %(amount)s.</field>
+ </record>
<record model="ir.message" id="msg_move_cancel_payments">
<field name="text">The moves "%(moves)s" contain lines with
payments, you may want to cancel them before cancelling.</field>
</record>
diff -r d9af1c8b229d -r 5b605ac1dc43
modules/account_payment/tests/scenario_account_payment_overpay.rst
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/modules/account_payment/tests/scenario_account_payment_overpay.rst
Wed Dec 31 15:57:40 2025 +0100
@@ -0,0 +1,101 @@
+================================
+Account Payment Overpay Scenario
+================================
+
+Imports::
+
+ >>> import datetime as dt
+ >>> from decimal import Decimal
+
+ >>> from proteus import Model, Wizard
+ >>> from trytond.modules.account.tests.tools import (
+ ... create_chart, create_fiscalyear, get_accounts)
+ >>> from trytond.modules.company.tests.tools import create_company
+ >>> from trytond.tests.tools import activate_modules
+
+ >>> today = dt.date.today()
+
+Activate modules::
+
+ >>> config = activate_modules('account_payment', create_company,
create_chart)
+
+ >>> Journal = Model.get('account.journal')
+ >>> Move = Model.get('account.move')
+ >>> Party = Model.get('party.party')
+ >>> Payment = Model.get('account.payment')
+ >>> PaymentJournal = Model.get('account.payment.journal')
+
+Create fiscal year::
+
+ >>> fiscalyear = create_fiscalyear()
+ >>> fiscalyear.click('create_period')
+
+Get accounts::
+
+ >>> accounts = get_accounts()
+
+ >>> expense_journal, = Journal.find([('code', '=', 'EXP')])
+ >>> cash_journal, = Journal.find([('code', '=', 'CASH')])
+
+Create parties::
+
+ >>> supplier = Party(name="Supplier")
+ >>> supplier.save()
+
+Create payable line::
+
+ >>> move = Move()
+ >>> move.journal = expense_journal
+ >>> line = move.lines.new(
+ ... account=accounts['payable'], party=supplier, maturity_date=today,
+ ... credit=Decimal('100.00'))
+ >>> line = move.lines.new(
+ ... account=accounts['expense'],
+ ... debit=Decimal('100.00'))
+ >>> move.click('post')
+ >>> move.state
+ 'posted'
+
+Make partial payment::
+
+ >>> payment_move = Move()
+ >>> payment_move.journal = cash_journal
+ >>> _ = payment_move.lines.new(
+ ... account=accounts['payable'], party=supplier,
+ ... debit=Decimal('50.00'))
+ >>> _ = payment_move.lines.new(
+ ... account=accounts['cash'],
+ ... credit=Decimal('50.00'))
+ >>> payment_move.click('post')
+ >>> payment_move.state
+ 'posted'
+
+Try to overpay the line::
+
+ >>> line, = [l for l in move.lines if l.account == accounts['payable']]
+ >>> pay_line = Wizard('account.move.line.pay', [line])
+ Traceback (most recent call last):
+ ...
+ OverpayWarning: ...
+
+Make full payment::
+
+ >>> payment_move2 = Move()
+ >>> payment_move2.journal = cash_journal
+ >>> _ = payment_move2.lines.new(
+ ... account=accounts['payable'], party=supplier,
+ ... debit=Decimal('50.00'))
+ >>> _ = payment_move2.lines.new(
+ ... account=accounts['cash'],
+ ... credit=Decimal('50.00'))
+ >>> payment_move2.click('post')
+ >>> payment_move2.state
+ 'posted'
+
+Try to overpay the line::
+
+ >>> line, = [l for l in move.lines if l.account == accounts['payable']]
+ >>> pay_line = Wizard('account.move.line.pay', [line])
+ Traceback (most recent call last):
+ ...
+ OverpayWarning: ...