details:   https://code.tryton.org/tryton/commit/7feb597bd64a
branch:    default
user:      Cédric Krier <[email protected]>
date:      Mon Feb 16 12:01:18 2026 +0100
description:
        Add direct debit as instrument for invoice payment means
diffstat:

 modules/account_payment/CHANGELOG                                              
  |   1 +
 modules/account_payment/account.py                                             
  |  46 +++++++
 modules/account_payment/message.xml                                            
  |   3 +
 modules/account_payment/party.py                                               
  |   7 +
 
modules/account_payment/tests/scenario_account_payment_invoice_payment_means.rst
 |  63 ++++++++++
 modules/account_payment/tryton.cfg                                             
  |   1 +
 modules/account_payment_sepa/account.py                                        
  |  18 ++
 modules/account_payment_sepa/message.xml                                       
  |   3 +
 modules/account_payment_sepa/setup.py                                          
  |   5 +-
 modules/account_payment_sepa/tests/test_module.py                              
  |   3 +-
 modules/account_payment_sepa/tryton.cfg                                        
  |   5 +
 11 files changed, 153 insertions(+), 2 deletions(-)

diffs (270 lines):

diff -r f2c5d67fe779 -r 7feb597bd64a modules/account_payment/CHANGELOG
--- a/modules/account_payment/CHANGELOG Mon Feb 16 12:32:35 2026 +0100
+++ b/modules/account_payment/CHANGELOG Mon Feb 16 12:01:18 2026 +0100
@@ -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 -r f2c5d67fe779 -r 7feb597bd64a modules/account_payment/account.py
--- a/modules/account_payment/account.py        Mon Feb 16 12:32:35 2026 +0100
+++ b/modules/account_payment/account.py        Mon Feb 16 12:01:18 2026 +0100
@@ -820,6 +820,52 @@
                         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 -r f2c5d67fe779 -r 7feb597bd64a modules/account_payment/message.xml
--- a/modules/account_payment/message.xml       Mon Feb 16 12:32:35 2026 +0100
+++ b/modules/account_payment/message.xml       Mon Feb 16 12:01:18 2026 +0100
@@ -3,6 +3,9 @@
 this repository contains the full copyright notices and license terms. -->
 <tryton>
     <data grouped="1">
+        <record model="ir.message" id="msg_invoice_payment_mean_direct_debit">
+            <field name="text">Direct debit</field>
+        </record>
         <record model="ir.message" id="msg_payment_delete_draft">
             <field name="text">To delete payment "%(payment)s" you must reset 
it to draft state.</field>
         </record>
diff -r f2c5d67fe779 -r 7feb597bd64a modules/account_payment/party.py
--- a/modules/account_payment/party.py  Mon Feb 16 12:32:35 2026 +0100
+++ b/modules/account_payment/party.py  Mon Feb 16 12:01:18 2026 +0100
@@ -124,6 +124,13 @@
             '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 -r f2c5d67fe779 -r 7feb597bd64a 
modules/account_payment/tests/scenario_account_payment_invoice_payment_means.rst
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ 
b/modules/account_payment/tests/scenario_account_payment_invoice_payment_means.rst
  Mon Feb 16 12:01:18 2026 +0100
@@ -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 -r f2c5d67fe779 -r 7feb597bd64a modules/account_payment/tryton.cfg
--- a/modules/account_payment/tryton.cfg        Mon Feb 16 12:32:35 2026 +0100
+++ b/modules/account_payment/tryton.cfg        Mon Feb 16 12:01:18 2026 +0100
@@ -51,6 +51,7 @@
 [register account_invoice]
 model:
     account.Invoice
+    account.InvoicePaymentMean
     payment.Payment_Invoice
 
 [register account_statement]
diff -r f2c5d67fe779 -r 7feb597bd64a modules/account_payment_sepa/account.py
--- a/modules/account_payment_sepa/account.py   Mon Feb 16 12:32:35 2026 +0100
+++ b/modules/account_payment_sepa/account.py   Mon Feb 16 12:01:18 2026 +0100
@@ -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 @@
                     '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 -r f2c5d67fe779 -r 7feb597bd64a modules/account_payment_sepa/message.xml
--- a/modules/account_payment_sepa/message.xml  Mon Feb 16 12:32:35 2026 +0100
+++ b/modules/account_payment_sepa/message.xml  Mon Feb 16 12:01:18 2026 +0100
@@ -3,6 +3,9 @@
 this repository contains the full copyright notices and license terms. -->
 <tryton>
     <data grouped="1">
+        <record model="ir.message" id="msg_invoice_payment_mean_direct_debit">
+            <field name="text">SEPA direct debit using mandate %(mandate)s on 
%(account_number)s</field>
+        </record>
         <record model="ir.message" id="msg_payment_process_no_mandate">
             <field name="text">To process payment "%(payment)s" you must 
define a SEPA mandate for it.</field>
         </record>
diff -r f2c5d67fe779 -r 7feb597bd64a modules/account_payment_sepa/setup.py
--- a/modules/account_payment_sepa/setup.py     Mon Feb 16 12:32:35 2026 +0100
+++ b/modules/account_payment_sepa/setup.py     Mon Feb 16 12:01:18 2026 +0100
@@ -50,7 +50,10 @@
         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 -r f2c5d67fe779 -r 7feb597bd64a 
modules/account_payment_sepa/tests/test_module.py
--- a/modules/account_payment_sepa/tests/test_module.py Mon Feb 16 12:32:35 
2026 +0100
+++ b/modules/account_payment_sepa/tests/test_module.py Mon Feb 16 12:01:18 
2026 +0100
@@ -154,6 +154,7 @@
         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 @@
             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 -r f2c5d67fe779 -r 7feb597bd64a modules/account_payment_sepa/tryton.cfg
--- a/modules/account_payment_sepa/tryton.cfg   Mon Feb 16 12:32:35 2026 +0100
+++ b/modules/account_payment_sepa/tryton.cfg   Mon Feb 16 12:01:18 2026 +0100
@@ -8,6 +8,7 @@
     bank
     party
 extras_depend:
+    account_invoice
     account_payment_clearing
 xml:
     payment.xml
@@ -32,3 +33,7 @@
 report:
     payment.MandateReport
     payment.MessageReport
+
+[register account_invoice]
+model:
+    account.InvoicePaymentMean

Reply via email to