changeset 9d1a39ee17e5 in modules/account_payment_sepa:default
details: 
https://hg.tryton.org/modules/account_payment_sepa?cmd=changeset&node=9d1a39ee17e5
description:
        Improve setting mandate sequence type

        We cache the result for each mandate and convert FRST to RCUR the next 
time
        the same mandate is used.
        We use a SQL query in a Function field to compute if it is the first 
payment
        for a mandate.

        issue9797
        review331081002
diffstat:

 payment.py |  56 ++++++++++++++++++++++++++++++++++++++++++++------------
 1 files changed, 44 insertions(+), 12 deletions(-)

diffs (100 lines):

diff -r 99a864afc2b9 -r 9d1a39ee17e5 payment.py
--- a/payment.py        Sun Oct 03 13:48:43 2021 +0200
+++ b/payment.py        Sun Oct 10 22:13:51 2021 +0200
@@ -9,7 +9,7 @@
 import genshi
 import genshi.template
 from lxml import etree
-from sql import Literal
+from sql import Literal, Null
 
 from trytond.config import config
 from trytond.i18n import gettext
@@ -207,20 +207,28 @@
     def process_sepa(self):
         pool = Pool()
         Payment = pool.get('account.payment')
+        Mandate = pool.get('account.payment.sepa.mandate')
         if self.kind == 'receivable':
-            mandates = Payment.get_sepa_mandates(self.payments)
-            for payment, mandate in zip(self.payments, mandates):
+            payments = list(self.payments)
+            mandates = Payment.get_sepa_mandates(payments)
+            Mandate.lock(mandates)
+            sequence_types = {}
+            for payment, mandate in zip(payments, mandates):
                 if not mandate:
                     raise ProcessError(
                         gettext('account_payment_sepa'
                             '.msg_payment_process_no_mandate',
                             payment=payment.rec_name))
-                # Write one by one because mandate.sequence_type must be
-                # recomputed each time
-                Payment.write([payment], {
-                        'sepa_mandate': mandate,
-                        'sepa_mandate_sequence_type': mandate.sequence_type,
-                        })
+                sequence_type = sequence_types.get(mandate)
+                if not sequence_type:
+                    sequence_type = mandate.sequence_type
+                    if sequence_type == 'FRST':
+                        sequence_types[mandate] = 'RCUR'
+                    else:
+                        sequence_types[mandate] = sequence_type
+                payment.sepa_mandate = mandate
+                payment.sepa_mandate_sequence_type = sequence_type
+            Payment.save(payments)
         else:
             for payment in self.payments:
                 if not payment.sepa_bank_account_number:
@@ -483,6 +491,8 @@
     payments = fields.One2Many('account.payment', 'sepa_mandate', 'Payments')
     has_payments = fields.Function(fields.Boolean('Has Payments'),
         'get_has_payments')
+    is_first_payment = fields.Function(
+        fields.Boolean("Is First Payment"), 'get_is_first_payment')
 
     @classmethod
     def __setup__(cls):
@@ -641,9 +651,7 @@
     def sequence_type(self):
         if self.type == 'one-off':
             return 'OOFF'
-        elif not self.sequence_type_rcur and (not self.payments
-                or all(not p.sepa_mandate_sequence_type for p in self.payments)
-                or all(p.rejected for p in self.payments)):
+        elif not self.sequence_type_rcur and self.is_first_payment:
             return 'FRST'
         # TODO manage FNAL
         else:
@@ -667,6 +675,30 @@
         return has_payments
 
     @classmethod
+    def get_is_first_payment(cls, mandates, name):
+        pool = Pool()
+        Payment = pool.get('account.payment')
+        payment = Payment.__table__()
+        cursor = Transaction().connection.cursor()
+
+        is_first = dict.fromkeys([m.id for m in mandates], True)
+        for sub_ids in grouped_slice(mandates):
+            red_sql = reduce_ids(payment.sepa_mandate, sub_ids)
+            cursor.execute(*payment.select(
+                    payment.sepa_mandate, Literal(False),
+                    where=red_sql
+                    & (payment.sepa_mandate_sequence_type != Null)
+                    & ~(  # Same as property rejected
+                        (payment.state == 'failed')
+                        & ((payment.sepa_return_reason_code != Null)
+                            | (payment.sepa_return_reason_code != ''))
+                        & (payment.sepa_return_reason_information
+                            == '/RTYP/RJCT')),
+                    group_by=payment.sepa_mandate))
+            is_first.update(cursor)
+        return is_first
+
+    @classmethod
     @ModelView.button
     @Workflow.transition('draft')
     def draft(cls, mandates):

Reply via email to