diff --git a/merge_request779.diff b/merge_request779.diff new file mode 100644 index 0000000..ef6a4c8 --- /dev/null +++ b/merge_request779.diff @@ -0,0 +1,233 @@ +diff --git a/tryton/modules/account/exceptions.py b/tryton/modules/account/exceptions.py +index 1742ee2f59..583546888f 100644 +--- a/tryton/modules/account/exceptions.py ++++ b/tryton/modules/account/exceptions.py +@@ -99,3 +99,15 @@ class GroupLineError(UserError): + + class RescheduleLineError(UserError): + pass ++ ++ ++class GroupLineWarning(UserWarning): ++ pass ++ ++ ++class RescheduleLineWarning(UserWarning): ++ pass ++ ++ ++class DelegateLineWarning(UserWarning): ++ pass +diff --git a/tryton/modules/account_payment/__init__.py b/tryton/modules/account_payment/__init__.py +index 52f6be034a..a59a560059 100644 +--- a/tryton/modules/account_payment/__init__.py ++++ b/tryton/modules/account_payment/__init__.py +@@ -34,6 +34,9 @@ def register(): + payment.ProcessPayment, + account.CreateDirectDebit, + account.PayLine, ++ account.MoveCancel, ++ account.MoveLineGroup, ++ account.MoveLineReschedule, + party.Replace, + party.Erase, + module='account_payment', type_='wizard') +diff --git a/tryton/modules/account_payment/account.py b/tryton/modules/account_payment/account.py +index e6acb794cf..112c8e1f2c 100644 +--- a/tryton/modules/account_payment/account.py ++++ b/tryton/modules/account_payment/account.py +@@ -11,6 +11,9 @@ from sql.functions import Abs + from trytond import backend + from trytond.i18n import gettext + from trytond.model import ModelSQL, ModelView, fields ++from trytond.modules.account.exceptions import ( ++ CancelWarning, DelegateLineWarning, GroupLineWarning, ++ RescheduleLineWarning) + from trytond.modules.company.model import CompanyValueMixin + from trytond.modules.currency.fields import Monetary + from trytond.pool import Pool, PoolMeta +@@ -503,6 +506,80 @@ class ConfigurationPaymentGroupSequence(ModelSQL, CompanyValueMixin): + return None + + ++class MoveCancel(metaclass=PoolMeta): ++ __name__ = 'account.move.cancel' ++ ++ def transition_cancel(self): ++ pool = Pool() ++ Warning = pool.get('res.user.warning') ++ moves_w_payments = [] ++ for move in self.records: ++ for line in move.lines: ++ if any(p.state != 'failed' for p in line.payments): ++ moves_w_payments.append(move) ++ break ++ if moves_w_payments: ++ names = ', '.join( ++ m.rec_name for m in moves_w_payments[:5]) ++ if len(moves_w_payments) > 5: ++ names += '...' ++ key = Warning.format('cancel_payments', moves_w_payments) ++ if Warning.check(key): ++ raise CancelWarning( ++ key, gettext( ++ 'account_payment.msg_move_cancel_payments', ++ moves=names)) ++ return super().transition_cancel() ++ ++ ++class MoveLineGroup(metaclass=PoolMeta): ++ __name__ = 'account.move.line.group' ++ ++ def do_group(self, action): ++ pool = Pool() ++ Warning = pool.get('res.user.warning') ++ lines_w_payments = [] ++ for line in self.records: ++ if any(p.state != 'failed' for p in line.payments): ++ lines_w_payments.append(line) ++ if lines_w_payments: ++ names = ', '.join( ++ m.rec_name for m in lines_w_payments[:5]) ++ if len(lines_w_payments) > 5: ++ names += '...' ++ key = Warning.format('group_payments', lines_w_payments) ++ if Warning.check(key): ++ raise GroupLineWarning( ++ key, gettext( ++ 'account_payment.msg_move_line_group_payments', ++ lines=names)) ++ return super().do_group(action) ++ ++ ++class MoveLineReschedule(metaclass=PoolMeta): ++ __name__ = 'account.move.line.reschedule' ++ ++ def do_reschedule(self, action): ++ pool = Pool() ++ Warning = pool.get('res.user.warning') ++ lines_w_payments = [] ++ for line in self.records: ++ if any(p.state != 'failed' for p in line.payments): ++ lines_w_payments.append(line) ++ if lines_w_payments: ++ names = ', '.join( ++ m.rec_name for m in lines_w_payments[:5]) ++ if len(lines_w_payments) > 5: ++ names += '...' ++ key = Warning.format('reschedule_payments', lines_w_payments) ++ if Warning.check(key): ++ raise RescheduleLineWarning( ++ key, gettext( ++ 'account_payment.msg_move_line_reschedule_payments', ++ lines=names)) ++ return super().do_reschedule(action) ++ ++ + class Invoice(metaclass=PoolMeta): + __name__ = 'account.invoice' + +diff --git a/tryton/modules/account_payment/exceptions.py b/tryton/modules/account_payment/exceptions.py +index 3403af63cd..de61c32ae9 100644 +--- a/tryton/modules/account_payment/exceptions.py ++++ b/tryton/modules/account_payment/exceptions.py +@@ -13,6 +13,10 @@ class OverpayWarning(UserWarning): + pass + + ++class ReconciledWarning(UserWarning): ++ pass ++ ++ + class PaymentValidationError(ValidationError): + pass + +diff --git a/tryton/modules/account_payment/message.xml b/tryton/modules/account_payment/message.xml +index 69fabc95fe..d4cc514c84 100644 +--- a/tryton/modules/account_payment/message.xml ++++ b/tryton/modules/account_payment/message.xml +@@ -9,6 +9,9 @@ this repository contains the full copyright notices and license terms. --> + + Payment "%(payment)s" overpays line "%(line)s". + ++ ++ The line "%(line)s" of payment "%(payment)s" is already reconciled. ++ + + You cannot erase party "%(party)s" while they have pending payments with company "%(company)s". + +@@ -18,5 +21,14 @@ this repository contains the full copyright notices and license terms. --> + + The lines "%(names)s" for %(party)s could be grouped with the line "%(line)s". + ++ ++ The moves "%(moves)s" contain lines with payments, you may want to cancel them before cancelling. ++ ++ ++ The lines "%(lines)s" have payments, you may want to cancel them before grouping. ++ ++ ++ The lines "%(lines)s" have payments, you may want to cancel them before rescheduling. ++ + + +diff --git a/tryton/modules/account_payment/payment.py b/tryton/modules/account_payment/payment.py +index 61dceb3205..52de371dc7 100644 +--- a/tryton/modules/account_payment/payment.py ++++ b/tryton/modules/account_payment/payment.py +@@ -22,7 +22,7 @@ from trytond.tools import ( + from trytond.transaction import Transaction + from trytond.wizard import Button, StateAction, StateView, Wizard + +-from .exceptions import OverpayWarning ++from .exceptions import OverpayWarning, ReconciledWarning + + KINDS = [ + ('payable', 'Payable'), +@@ -561,14 +561,14 @@ class Payment(Workflow, ModelSQL, ModelView): + @Workflow.transition('submitted') + @set_employee('submitted_by') + def submit(cls, payments): +- pass ++ cls._check_reconciled(payments) + + @classmethod + @ModelView.button + @Workflow.transition('approved') + @set_employee('approved_by') + def approve(cls, payments): +- pass ++ cls._check_reconciled(payments) + + @classmethod + @Workflow.transition('processing') +@@ -595,6 +595,8 @@ class Payment(Workflow, ModelSQL, ModelView): + @Workflow.transition('processing') + def proceed(cls, payments): + assert all(p.group for p in payments) ++ cls._check_reconciled( ++ [p for p in payments if p.state not in {'succeeded', 'failed'}]) + + @classmethod + @ModelView.button +@@ -610,6 +612,20 @@ class Payment(Workflow, ModelSQL, ModelView): + def fail(cls, payments): + pass + ++ @classmethod ++ def _check_reconciled(cls, payments): ++ pool = Pool() ++ Warning = pool.get('res.user.warning') ++ for payment in payments: ++ if payment.line and payment.line.reconciliation: ++ key = Warning.format('submit_reconciled', [payment]) ++ if Warning.check(key): ++ raise ReconciledWarning( ++ key, gettext( ++ 'account_payment.msg_payment_reconciled', ++ payment=payment.rec_name, ++ line=payment.line.rec_name)) ++ + + class ProcessPaymentStart(ModelView): + 'Process Payment' diff --git a/series b/series index 3960ceb..faebef4 100644 --- a/series +++ b/series @@ -51,3 +51,5 @@ issue12216.diff # [stock] Handle evaluation error of cost price in cost price re issue12480.diff # [trytond] Support GIN index with btree_gin PostgreSQL extension issue12497.diff # [purchase] Use the last 10 purchases to set default currency, payment term and invoice method + +merge_request779.diff # [account_payment] Warn when submitting, approving or proceeding payment with reconciled line