@@ -1,3 +1,4 @@
|
||||
* Warn against creating an overpayment
|
||||
|
||||
Version 7.8.0 - 2025-12-15
|
||||
--------------------------
|
||||
|
||||
@@ -24,7 +24,7 @@ from trytond.transaction import Transaction, check_access, without_check_access
|
||||
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 @@ class PayLine(Wizard):
|
||||
|
||||
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 @@ class PayLine(Wizard):
|
||||
('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):
|
||||
|
||||
@@ -24,6 +24,9 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<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>
|
||||
|
||||
@@ -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: ...
|
||||
Reference in New Issue
Block a user