changeset 1f9dc29c1458 in modules/account_payment:default
details: 
https://hg.tryton.org/modules/account_payment?cmd=changeset&node=1f9dc29c1458
description:
        Add wizard to create direct debit for configured parties

        issue10772
        review379071002
diffstat:

 CHANGELOG                                       |    1 +
 __init__.py                                     |    3 +
 account.py                                      |   74 ++++++++++++++++
 account.xml                                     |   21 ++++
 doc/index.rst                                   |    3 +
 party.py                                        |   97 +++++++++++++++++++++-
 party.xml                                       |   40 +++++++++
 tests/scenario_account_payment_direct_debit.rst |  106 ++++++++++++++++++++++++
 tests/test_account_payment.py                   |    5 +
 view/move_line_create_direct_debit_form.xml     |   10 ++
 view/party_form.xml                             |    9 +-
 view/party_reception_direct_debit_form.xml      |   14 +++
 view/party_reception_direct_debit_list.xml      |    9 ++
 13 files changed, 386 insertions(+), 6 deletions(-)

diffs (531 lines):

diff -r 69b733325048 -r 1f9dc29c1458 CHANGELOG
--- a/CHANGELOG Mon Oct 11 18:22:19 2021 +0200
+++ b/CHANGELOG Mon Oct 11 18:35:27 2021 +0200
@@ -1,3 +1,4 @@
+* Add wizard to create direct debit for configured parties
 * Add succeed button for payment groups
 
 Version 6.0.0 - 2021-05-03
diff -r 69b733325048 -r 1f9dc29c1458 __init__.py
--- a/__init__.py       Mon Oct 11 18:22:19 2021 +0200
+++ b/__init__.py       Mon Oct 11 18:35:27 2021 +0200
@@ -13,18 +13,21 @@
         payment.Payment,
         account.MoveLine,
         payment.ProcessPaymentStart,
+        account.CreateDirectDebitStart,
         account.PayLineStart,
         account.PayLineAskJournal,
         account.Configuration,
         account.ConfigurationPaymentGroupSequence,
         party.Party,
         party.PartyPaymentDirectDebit,
+        party.PartyReceptionDirectDebit,
         module='account_payment', type_='model')
     Pool.register(
         account.Invoice,
         module='account_payment', type_='model', depends=['account_invoice'])
     Pool.register(
         payment.ProcessPayment,
+        account.CreateDirectDebit,
         account.PayLine,
         party.Replace,
         party.Erase,
diff -r 69b733325048 -r 1f9dc29c1458 account.py
--- a/account.py        Mon Oct 11 18:22:19 2021 +0200
+++ b/account.py        Mon Oct 11 18:35:27 2021 +0200
@@ -185,6 +185,80 @@
                 'payment_blocked': False,
                 })
 
+    @classmethod
+    def _pay_direct_debit_domain(cls, date):
+        return [
+            ['OR',
+                ('account.type.receivable', '=', True),
+                ('account.type.payable', '=', True),
+                ],
+            ('party', '!=', None),
+            ('reconciliation', '=', None),
+            ('payment_amount', '!=', 0),
+            ('move_state', '=', 'posted'),
+            ['OR',
+                ('debit', '>', 0),
+                ('credit', '<', 0),
+                ],
+            ('maturity_date', '<=', date),
+            ]
+
+    @classmethod
+    def pay_direct_debit(cls, date=None):
+        pool = Pool()
+        Date = pool.get('ir.date')
+        Payment = pool.get('account.payment')
+        Reception = pool.get('party.party.reception_direct_debit')
+        if date is None:
+            date = Date.today()
+        with Transaction().set_context(_check_access=True):
+            lines = cls.search(cls._pay_direct_debit_domain(date))
+
+        payments = []
+        for line in lines:
+            if not line.payment_amount:
+                # SQLite fails to search for payment_amount != 0
+                continue
+            pattern = Reception.get_pattern(line)
+            for reception in line.party.reception_direct_debits:
+                if reception.match(pattern):
+                    payments.extend(reception.get_payments(line))
+        Payment.save(payments)
+        return payments
+
+
+class CreateDirectDebit(Wizard):
+    "Create Direct Debit"
+    __name__ = 'account.move.line.create_direct_debit'
+
+    start = StateView('account.move.line.create_direct_debit.start',
+        'account_payment.move_line_create_direct_debit_view_form', [
+            Button("Cancel", 'end', 'tryton-cancel'),
+            Button("Create", 'create_', 'tryton-ok', default=True),
+            ])
+    create_ = StateAction('account_payment.act_payment_form')
+
+    def do_create_(self, action):
+        pool = Pool()
+        Line = pool.get('account.move.line')
+        payments = Line.pay_direct_debit(date=self.start.date)
+        return action, {
+            'res_id': [p.id for p in payments],
+            }
+
+
+class CreateDirectDebitStart(ModelView):
+    "Create Direct Debit"
+    __name__ = 'account.move.line.create_direct_debit.start'
+
+    date = fields.Date(
+        "Date", required=True,
+        help="Create direct debit for lines due up to this date.")
+
+    @classmethod
+    def default_date(cls):
+        return Pool().get('ir.date').today()
+
 
 class PayLineStart(ModelView):
     "Pay Line"
diff -r 69b733325048 -r 1f9dc29c1458 account.xml
--- a/account.xml       Mon Oct 11 18:22:19 2021 +0200
+++ b/account.xml       Mon Oct 11 18:35:27 2021 +0200
@@ -73,6 +73,27 @@
             <field name="perm_delete" eval="False"/>
         </record>
 
+        <record model="ir.action.wizard" id="wizard_create_direct_debit">
+            <field name="name">Create Direct Debit</field>
+            <field 
name="wiz_name">account.move.line.create_direct_debit</field>
+        </record>
+        <menuitem
+            parent="menu_payments"
+            action="wizard_create_direct_debit"
+            sequence="40"
+            id="menu_create_direct_debit"/>
+
+        <record model="ir.action-res.group" 
id="wizard_create_direct_debit-group_payment">
+            <field name="action" ref="wizard_create_direct_debit"/>
+            <field name="group" ref="group_payment"/>
+        </record>
+
+        <record model="ir.ui.view" 
id="move_line_create_direct_debit_view_form">
+            <field 
name="model">account.move.line.create_direct_debit.start</field>
+            <field name="type">form</field>
+            <field name="name">move_line_create_direct_debit_form</field>
+        </record>
+
         <record model="ir.ui.view" id="move_line_pay_start_view_form">
             <field name="model">account.move.line.pay.start</field>
             <field name="type">form</field>
diff -r 69b733325048 -r 1f9dc29c1458 doc/index.rst
--- a/doc/index.rst     Mon Oct 11 18:22:19 2021 +0200
+++ b/doc/index.rst     Mon Oct 11 18:35:27 2021 +0200
@@ -24,6 +24,9 @@
 using the `Pay Lines` action. The amount is computed from the debit/credit
 deducing existing payments for this line.
 
+Direct debit payments can be created from the wizard `Create Direct Debit` for
+all the parties that are configured with at least one *Direct Debits* record.
+
 The payment can be in one of this states:
 
 * Draft
diff -r 69b733325048 -r 1f9dc29c1458 party.py
--- a/party.py  Mon Oct 11 18:22:19 2021 +0200
+++ b/party.py  Mon Oct 11 18:35:27 2021 +0200
@@ -2,7 +2,8 @@
 # this repository contains the full copyright notices and license terms.
 from trytond.i18n import gettext
 from trytond.pool import PoolMeta, Pool
-from trytond.model import ModelSQL, fields
+from trytond.model import (
+    ModelSQL, ModelView, sequence_ordered, MatchMixin, fields)
 from trytond.modules.company.model import CompanyValueMixin
 from trytond.modules.party.exceptions import EraseError
 from trytond.pyson import Eval
@@ -17,11 +18,23 @@
     payment_direct_debit = fields.MultiValue(payment_direct_debit)
     payment_direct_debits = fields.One2Many(
         'party.party.payment_direct_debit', 'party', "Direct Debits")
+    reception_direct_debits = fields.One2Many(
+        'party.party.reception_direct_debit', 'party', "Direct Debits",
+        help="Fill to debit automatically the customer.")
 
     @classmethod
     def default_payment_direct_debit(cls, **pattern):
         return False
 
+    @classmethod
+    def copy(cls, parties, default=None):
+        if default is None:
+            default = {}
+        else:
+            default = default.copy()
+        default.setdefault('reception_direct_debits')
+        return super().copy(parties, default=default)
+
 
 class PartyPaymentDirectDebit(ModelSQL, CompanyValueMixin):
     "Party Payment Direct Debit"
@@ -35,6 +48,87 @@
     payment_direct_debit = payment_direct_debit
 
 
+class PartyReceptionDirectDebit(
+        sequence_ordered(), MatchMixin, ModelView, ModelSQL):
+    "Party Reception Direct Debit"
+    __name__ = 'party.party.reception_direct_debit'
+
+    party = fields.Many2One(
+        'party.party', "Party", ondelete='CASCADE', select=True,
+        context={
+            'company': Eval('company', -1),
+            },
+        depends=['company'])
+    journal = fields.Many2One(
+        'account.payment.journal', "Journal", required=True)
+    company = fields.Function(
+        fields.Many2One('company.company', "Company"),
+        'on_change_with_company', searcher='search_company')
+    currency = fields.Function(
+        fields.Many2One('currency.currency', "Currency"),
+        'on_change_with_currency')
+    process_method = fields.Function(
+        fields.Selection('get_process_methods', "Process Method"),
+        'on_change_with_process_method')
+
+    @fields.depends('journal')
+    def on_change_with_company(self, name=None):
+        if self.journal:
+            return self.journal.company.id
+
+    @classmethod
+    def search_company(cls, name, clause):
+        nested = clause[0].lstrip(name)
+        return [('journal.' + name + nested,) + tuple(clause[1:])]
+
+    @fields.depends('journal')
+    def on_change_with_currency(self, name=None):
+        if self.journal:
+            return self.journal.currency.id
+
+    @classmethod
+    def get_process_methods(cls):
+        pool = Pool()
+        Journal = pool.get('account.payment.journal')
+        name = 'process_method'
+        return Journal.fields_get([name])[name]['selection']
+
+    @fields.depends('journal')
+    def on_change_with_process_method(self, name=None):
+        if self.journal:
+            return self.journal.process_method
+
+    @classmethod
+    def get_pattern(cls, line):
+        return {
+            'company': line.company.id,
+            'currency': line.payment_currency.id,
+            }
+
+    def get_payments(self, line):
+        pool = Pool()
+        Date = pool.get('ir.date')
+        today = Date.today()
+        for date, amount in self._compute(
+                line.maturity_date or today, line.payment_amount):
+            yield self._get_payment(line, date, amount)
+
+    def _compute(self, date, amount):
+        yield date, amount
+
+    def _get_payment(self, line, date, amount):
+        pool = Pool()
+        Payment = pool.get('account.payment')
+        return Payment(
+            company=line.company,
+            journal=self.journal,
+            kind='receivable',
+            party=line.party,
+            date=date,
+            amount=amount,
+            line=line)
+
+
 class Replace(metaclass=PoolMeta):
     __name__ = 'party.replace'
 
@@ -42,6 +136,7 @@
     def fields_to_replace(cls):
         return super().fields_to_replace() + [
             ('account.payment', 'party'),
+            ('party.party.reception_direct_debit', 'party'),
             ]
 
 
diff -r 69b733325048 -r 1f9dc29c1458 party.xml
--- a/party.xml Mon Oct 11 18:22:19 2021 +0200
+++ b/party.xml Mon Oct 11 18:35:27 2021 +0200
@@ -8,5 +8,45 @@
             <field name="inherit" ref="party.party_view_form"/>
             <field name="name">party_form</field>
         </record>
+
+        <record model="ir.ui.view" id="party_reception_direct_debit_view_form">
+            <field name="model">party.party.reception_direct_debit</field>
+            <field name="type">form</field>
+            <field name="name">party_reception_direct_debit_form</field>
+        </record>
+
+        <record model="ir.ui.view" id="party_reception_direct_debit_view_list">
+            <field name="model">party.party.reception_direct_debit</field>
+            <field name="type">tree</field>
+            <field name="name">party_reception_direct_debit_list</field>
+        </record>
+
+        <record model="ir.rule.group" 
id="rule_group_party_reception_direct_debit_companies">
+            <field name="name">User in companies</field>
+            <field name="model" search="[('model', '=', 
'party.party.reception_direct_debit')]"/>
+            <field name="global_p" eval="True"/>
+        </record>
+        <record model="ir.rule" 
id="rule_party_reception_direct_debit_companies">
+            <field name="domain"
+                eval="[('company', 'in', Eval('companies', []))]"
+                pyson="1"/>
+            <field name="rule_group" 
ref="rule_group_party_reception_direct_debit_companies"/>
+        </record>
+
+        <record model="ir.model.access" 
id="access_party_reception_direct_debit">
+            <field name="model" search="[('model', '=', 
'party.party.reception_direct_debit')]"/>
+            <field name="perm_read" eval="False"/>
+            <field name="perm_write" eval="False"/>
+            <field name="perm_create" eval="False"/>
+            <field name="perm_delete" eval="False"/>
+        </record>
+        <record model="ir.model.access" 
id="access_party_reception_direct_debit_payment">
+            <field name="model" search="[('model', '=', 
'party.party.reception_direct_debit')]"/>
+            <field name="group" ref="group_payment"/>
+            <field name="perm_read" eval="True"/>
+            <field name="perm_write" eval="True"/>
+            <field name="perm_create" eval="True"/>
+            <field name="perm_delete" eval="True"/>
+        </record>
     </data>
 </tryton>
diff -r 69b733325048 -r 1f9dc29c1458 
tests/scenario_account_payment_direct_debit.rst
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/scenario_account_payment_direct_debit.rst   Mon Oct 11 18:35:27 
2021 +0200
@@ -0,0 +1,106 @@
+=============================
+Payment Direct Debit Scenario
+=============================
+
+Imports::
+
+    >>> import datetime as dt
+    >>> from decimal import Decimal
+
+    >>> from proteus import Model, Wizard
+    >>> from trytond.tests.tools import activate_modules
+    >>> from trytond.modules.company.tests.tools import (
+    ...     create_company, get_company)
+    >>> from trytond.modules.account.tests.tools import (
+    ...     create_fiscalyear, create_chart, get_accounts)
+
+    >>> today = dt.date.today()
+    >>> tomorrow = today + dt.timedelta(days=1)
+
+Activate modules::
+
+    >>> config = activate_modules('account_payment')
+
+    >>> 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 company::
+
+    >>> _ = create_company()
+    >>> company = get_company()
+
+Create fiscal year::
+
+    >>> fiscalyear = create_fiscalyear(company)
+    >>> fiscalyear.click('create_period')
+
+Create chart of accounts::
+
+    >>> _ = create_chart(company)
+    >>> accounts = get_accounts(company)
+
+    >>> revnue_journal, = Journal.find([('code', '=', 'REV')])
+
+Create payment journal::
+
+    >>> payment_journal = PaymentJournal(
+    ...     name="Manual", process_method='manual')
+    >>> payment_journal.save()
+
+Create parties::
+
+    >>> customer1 = Party(name="Customer 1")
+    >>> _ = customer1.reception_direct_debits.new(journal=payment_journal)
+    >>> customer1.save()
+    >>> customer2 = Party(name="Customer 2")
+    >>> customer2.save()
+
+Create receivable moves::
+
+    >>> move = Move()
+    >>> move.journal = revnue_journal
+    >>> line = move.lines.new(
+    ...     account=accounts['receivable'], party=customer1,
+    ...     debit=Decimal('100.00'), maturity_date=tomorrow)
+    >>> line = move.lines.new(
+    ...     account=accounts['revenue'],
+    ...     credit=Decimal('100.00'))
+    >>> move.click('post')
+
+    >>> move = Move()
+    >>> move.journal = revnue_journal
+    >>> line = move.lines.new(
+    ...     account=accounts['receivable'], party=customer2,
+    ...     debit=Decimal('200.00'), maturity_date=tomorrow)
+    >>> line = move.lines.new(
+    ...     account=accounts['revenue'],
+    ...     credit=Decimal('200.00'))
+    >>> move.click('post')
+
+Create direct debit::
+
+    >>> create_direct_debit = Wizard('account.move.line.create_direct_debit')
+    >>> create_direct_debit.form.date = tomorrow + dt.timedelta(days=1)
+    >>> create_direct_debit.execute('create_')
+
+    >>> payment, = Payment.find([])
+    >>> payment.amount
+    Decimal('100.00')
+    >>> payment.party == customer1
+    True
+    >>> payment.date == tomorrow
+    True
+    >>> payment.journal == payment_journal
+    True
+
+Re-run create direct debit does nothing::
+
+    >>> create_direct_debit = Wizard('account.move.line.create_direct_debit')
+    >>> create_direct_debit.form.date = tomorrow + dt.timedelta(days=1)
+    >>> create_direct_debit.execute('create_')
+
+    >>> Payment.find([]) == [payment]
+    True
diff -r 69b733325048 -r 1f9dc29c1458 tests/test_account_payment.py
--- a/tests/test_account_payment.py     Mon Oct 11 18:22:19 2021 +0200
+++ b/tests/test_account_payment.py     Mon Oct 11 18:35:27 2021 +0200
@@ -31,6 +31,11 @@
             checker=doctest_checker,
             optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
     suite.addTests(doctest.DocFileSuite(
+            'scenario_account_payment_direct_debit.rst',
+            tearDown=doctest_teardown, encoding='utf-8',
+            checker=doctest_checker,
+            optionflags=doctest.REPORT_ONLY_FIRST_FAILURE))
+    suite.addTests(doctest.DocFileSuite(
             'scenario_account_payment_planning.rst',
             tearDown=doctest_teardown, encoding='utf-8',
             checker=doctest_checker,
diff -r 69b733325048 -r 1f9dc29c1458 view/move_line_create_direct_debit_form.xml
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/view/move_line_create_direct_debit_form.xml       Mon Oct 11 18:35:27 
2021 +0200
@@ -0,0 +1,10 @@
+<?xml version="1.0"?>
+<!-- This file is part of Tryton.  The COPYRIGHT file at the top level of
+this repository contains the full copyright notices and license terms. -->
+<form col="2">
+    <image name="tryton-question" xexpand="0" xfill="0"/>
+    <group col="2" xexpand="1" id="create_date">
+        <label string="Create Direct Debit for date" id="create"/>
+        <field name="date"/>
+    </group>
+</form>
diff -r 69b733325048 -r 1f9dc29c1458 view/party_form.xml
--- a/view/party_form.xml       Mon Oct 11 18:22:19 2021 +0200
+++ b/view/party_form.xml       Mon Oct 11 18:35:27 2021 +0200
@@ -2,10 +2,9 @@
 <!-- This file is part of Tryton.  The COPYRIGHT file at the top level of
 this repository contains the full copyright notices and license terms. -->
 <data>
-    <xpath expr="//page[@id='accounting']/field[@name='account_payable']"
-        position="after">
-        <label id="empty" colspan="2"/>
-        <label name="payment_direct_debit"/>
-        <field name="payment_direct_debit"/>
+    <xpath expr="//page[@id='accounting']/field[@name='account_payable']" 
position="after">
+        <field name="reception_direct_debits" colspan="2" height="200" 
yexpand="0"/>
+        <label name="payment_direct_debit" yalign="0"/>
+        <field name="payment_direct_debit" yalign="0"/>
     </xpath>
 </data>
diff -r 69b733325048 -r 1f9dc29c1458 view/party_reception_direct_debit_form.xml
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/view/party_reception_direct_debit_form.xml        Mon Oct 11 18:35:27 
2021 +0200
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<!-- This file is part of Tryton.  The COPYRIGHT file at the top level of
+this repository contains the full copyright notices and license terms. -->
+<form>
+    <label name="party"/>
+    <field name="party"/>
+    <label name="sequence"/>
+    <field name="sequence"/>
+
+    <label name="journal"/>
+    <field name="journal"/>
+    <label name="currency"/>
+    <field name="currency"/>
+</form>
diff -r 69b733325048 -r 1f9dc29c1458 view/party_reception_direct_debit_list.xml
--- /dev/null   Thu Jan 01 00:00:00 1970 +0000
+++ b/view/party_reception_direct_debit_list.xml        Mon Oct 11 18:35:27 
2021 +0200
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<!-- This file is part of Tryton.  The COPYRIGHT file at the top level of
+this repository contains the full copyright notices and license terms. -->
+<tree sequence="sequence">
+    <field name="party" expand="2"/>
+    <field name="company" expand="1"/>
+    <field name="journal" expand="1"/>
+    <field name="currency"/>
+</tree>

Reply via email to