diff --git a/modules/account_payment/CHANGELOG b/modules/account_payment/CHANGELOG index 5bc2378f46..4122bc708b 100644 --- a/modules/account_payment/CHANGELOG +++ b/modules/account_payment/CHANGELOG @@ -1,3 +1,4 @@ +* Add direct debit as instrument for invoice payment means * Warn against creating an overpayment Version 7.8.0 - 2025-12-15 diff --git a/modules/account_payment/account.py b/modules/account_payment/account.py index cdf923982f..e579199398 100644 --- a/modules/account_payment/account.py +++ b/modules/account_payment/account.py @@ -820,6 +820,52 @@ class Invoice(metaclass=PoolMeta): amounts[invoice.id] -= payment_amount return amounts + @classmethod + def set_payment_means(cls, invoices): + pool = Pool() + PaymentMean = pool.get('account.invoice.payment.mean') + Reception = pool.get('party.party.reception_direct_debit') + payment_means = [] + for invoice in invoices: + if invoice.type == 'out' and not invoice.payment_means: + pattern = Reception.get_pattern_for_invoice(invoice) + for reception in invoice.party.reception_direct_debits: + if reception.match(pattern): + payment_mean = PaymentMean.from_reception_direct_debit( + reception) + payment_mean.invoice = invoice + payment_means.append(payment_mean) + PaymentMean.save(payment_means) + super().set_payment_means(invoices) + + +class InvoicePaymentMean(metaclass=PoolMeta): + __name__ = 'account.invoice.payment.mean' + + @classmethod + def __setup__(cls): + super().__setup__() + cls.instrument.domain['party.party.reception_direct_debit'] = [ + ('party', 'in', Eval('payers', [])), + ('company', '=', Eval('company', -1)), + ] + + @classmethod + def _get_instruments(cls): + yield from super()._get_instruments() + yield 'party.party.reception_direct_debit' + + @classmethod + def from_reception_direct_debit(cls, reception): + return cls(instrument=reception) + + def get_rec_name(self, name): + name = super().get_rec_name(name) + if self.instrument.__name__ == 'party.party.reception_direct_debit': + name = gettext( + 'account_payment.msg_invoice_payment_mean_direct_debit') + return name + class Statement(metaclass=PoolMeta): __name__ = 'account.statement' diff --git a/modules/account_payment/message.xml b/modules/account_payment/message.xml index a59affb6a4..8b0c80fec9 100644 --- a/modules/account_payment/message.xml +++ b/modules/account_payment/message.xml @@ -3,6 +3,9 @@ this repository contains the full copyright notices and license terms. --> + + Direct debit + To delete payment "%(payment)s" you must reset it to draft state. diff --git a/modules/account_payment/party.py b/modules/account_payment/party.py index bc47f0143d..32b7911b47 100644 --- a/modules/account_payment/party.py +++ b/modules/account_payment/party.py @@ -124,6 +124,13 @@ class PartyReceptionDirectDebit( 'type': type, } + @classmethod + def get_pattern_for_invoice(cls, invoice): + return { + 'company': invoice.company.id, + 'currency': invoice.currency.id, + } + def get_payments(self, line=None, amount=None, date=None): pool = Pool() Date = pool.get('ir.date') diff --git a/modules/account_payment/tests/scenario_account_payment_invoice_payment_means.rst b/modules/account_payment/tests/scenario_account_payment_invoice_payment_means.rst new file mode 100644 index 0000000000..848004e351 --- /dev/null +++ b/modules/account_payment/tests/scenario_account_payment_invoice_payment_means.rst @@ -0,0 +1,63 @@ +============================== +Invoice Payment Means Scenario +============================== + +Imports:: + + >>> from proteus import Model + >>> from trytond.modules.account.tests.tools import ( + ... create_chart, create_fiscalyear, get_accounts) + >>> from trytond.modules.account_invoice.tests.tools import ( + ... set_fiscalyear_invoice_sequences) + >>> from trytond.modules.company.tests.tools import create_company, get_company + >>> from trytond.tests.tools import activate_modules, assertEqual, assertFalse + +Activate modules:: + + >>> config = activate_modules( + ... ['account_payment', 'account_invoice'], create_company, create_chart) + + >>> Invoice = Model.get('account.invoice') + >>> Party = Model.get('party.party') + >>> PaymentJournal = Model.get('account.payment.journal') + + >>> company = get_company() + +Create fiscal year:: + + >>> fiscalyear = set_fiscalyear_invoice_sequences(create_fiscalyear()) + >>> fiscalyear.click('create_period') + +Get accounts:: + + >>> accounts = get_accounts() + +Create payment journal:: + + >>> payment_journal = PaymentJournal(name="Manual", process_method='manual') + >>> payment_journal.save() + +Create party:: + + >>> customer = Party(name="Customer") + >>> _ = customer.reception_direct_debits.new(journal=payment_journal) + >>> customer.save() + +Create invoice without payment means:: + + >>> invoice = Invoice(type='out') + >>> invoice.party = customer + >>> assertFalse(invoice.payment_means) + >>> invoice.click('validate_invoice') + >>> payment_mean, = invoice.payment_means + >>> assertEqual(payment_mean.instrument, customer.reception_direct_debits[0]) + +Create invoice with payment means:: + + >>> invoice = Invoice(type='out') + >>> invoice.party = customer + >>> payment_mean = invoice.payment_means.new() + >>> payment_mean.instrument = customer.reception_direct_debits[0] + >>> invoice.click('post') + >>> payment_mean, = invoice.payment_means + >>> assertEqual(payment_mean.instrument, customer.reception_direct_debits[0]) diff --git a/modules/account_payment/tryton.cfg b/modules/account_payment/tryton.cfg index a3cf7fd8c2..ee9f973e44 100644 --- a/modules/account_payment/tryton.cfg +++ b/modules/account_payment/tryton.cfg @@ -51,6 +51,7 @@ model: [register account_invoice] model: account.Invoice + account.InvoicePaymentMean payment.Payment_Invoice [register account_statement] diff --git a/modules/account_payment_sepa/account.py b/modules/account_payment_sepa/account.py index 3cbbdce6b3..e8f580e37a 100644 --- a/modules/account_payment_sepa/account.py +++ b/modules/account_payment_sepa/account.py @@ -1,5 +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.i18n import gettext from trytond.model import ModelSQL, fields from trytond.modules.company.model import CompanyValueMixin from trytond.pool import PoolMeta @@ -27,3 +29,19 @@ class ConfigurationSepaMandateSequence(ModelSQL, CompanyValueMixin): 'account_payment_sepa', 'sequence_type_mandate')), ('company', 'in', [Eval('company', -1), None]), ]) + + +class InvoicePaymentMean(metaclass=PoolMeta): + __name__ = 'account.invoice.payment.mean' + + def get_rec_name(self, name): + name = super().get_rec_name(name) + if (self.instrument.__name__ == 'party.party.reception_direct_debit' + and self.instrument.journal.process_method == 'sepa' + and self.instrument.sepa_mandate): + mandate = self.instrument.sepa_mandate + name = gettext( + 'account_payment_sepa.msg_invoice_payment_mean_direct_debit', + mandate=mandate.identification, + account_number=mandate.account_number.number) + return name diff --git a/modules/account_payment_sepa/message.xml b/modules/account_payment_sepa/message.xml index 87658ffff8..4911445237 100644 --- a/modules/account_payment_sepa/message.xml +++ b/modules/account_payment_sepa/message.xml @@ -3,6 +3,9 @@ this repository contains the full copyright notices and license terms. --> + + SEPA direct debit using mandate %(mandate)s on %(account_number)s + To process payment "%(payment)s" you must define a SEPA mandate for it. diff --git a/modules/account_payment_sepa/setup.py b/modules/account_payment_sepa/setup.py index d4137207e7..b6db65b19f 100755 --- a/modules/account_payment_sepa/setup.py +++ b/modules/account_payment_sepa/setup.py @@ -50,7 +50,10 @@ for dep in info.get('depends', []): requires.append(get_require_version('trytond_%s' % dep)) requires.append(get_require_version('trytond')) -tests_require = [get_require_version('proteus')] +tests_require = [ + get_require_version('proteus'), + get_require_version('trytond_account_invoice'), + ] setup(name=name, version=version, diff --git a/modules/account_payment_sepa/tests/test_module.py b/modules/account_payment_sepa/tests/test_module.py index 5cc00f63cd..ebf29a7c6c 100644 --- a/modules/account_payment_sepa/tests/test_module.py +++ b/modules/account_payment_sepa/tests/test_module.py @@ -154,6 +154,7 @@ class AccountPaymentSepaTestCase( PartyCheckReplaceMixin, CompanyTestMixin, ModuleTestCase): 'Test Account Payment SEPA module' module = 'account_payment_sepa' + extras = ['account_invoice'] @with_transaction() def test_pain001_001_03(self): @@ -279,7 +280,7 @@ class AccountPaymentSepaTestCase( party = Party( bank_accounts_used=[bank_account]) payment = Payment( - kind='payable', party=party, + kind='payable', party=party, line=None, sepa_payable_bank_account_number=None) self.assertEqual(id(payment.sepa_bank_account_number), diff --git a/modules/account_payment_sepa/tryton.cfg b/modules/account_payment_sepa/tryton.cfg index e8ec97f3c3..d8132a2f78 100644 --- a/modules/account_payment_sepa/tryton.cfg +++ b/modules/account_payment_sepa/tryton.cfg @@ -8,6 +8,7 @@ depends: bank party extras_depend: + account_invoice account_payment_clearing xml: payment.xml @@ -32,3 +33,7 @@ wizard: report: payment.MandateReport payment.MessageReport + +[register account_invoice] +model: + account.InvoicePaymentMean