Repository: OCA/donation (language: python)

Issue:
Dificulties to create mandate from donation - customer´s bank accounts not selectable
When you have to create a mandate from the donation, you get a mandate form. The first parameter is bank account, when you browse on it you don't get any record, you cannot select an existing bank account, it doesn´t recognize the customer´s bank accounts. 



Discussion / hints:

Fix patch:
diff --git a/donation_direct_debit/README.rst b/donation_direct_debit/README.rst
new file mode 100644
index 000000000..bb535d3eb
--- /dev/null
+++ b/donation_direct_debit/README.rst
@@ -0,0 +1,1 @@
+Will be re-generated from the readme subdir
diff --git a/donation_direct_debit/__init__.py b/donation_direct_debit/__init__.py
new file mode 100644
index 000000000..0650744f6
--- /dev/null
+++ b/donation_direct_debit/__init__.py
@@ -0,0 +1,1 @@
+from . import models
diff --git a/donation_direct_debit/__manifest__.py b/donation_direct_debit/__manifest__.py
new file mode 100644
index 000000000..0eeffae3d
--- /dev/null
+++ b/donation_direct_debit/__manifest__.py
@@ -0,0 +1,21 @@
+# Copyright 2015-2021 Akretion France (http://www.akretion.com/)
+# @author: Alexis de Lattre <alexis.delattre@akretion.com>
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+
+{
+    "name": "Donation Direct Debit",
+    "version": "14.0.1.0.0",
+    "category": "Accounting",
+    "license": "AGPL-3",
+    "summary": "Auto-generate direct debit order on donation validation",
+    "author": "Akretion, Odoo Community Association (OCA)",
+    "maintainers": ["alexis-via"],
+    "website": "https://github.com/OCA/donation",
+    "depends": ["account_banking_sepa_direct_debit", "donation"],
+    "data": [
+        "views/donation.xml",
+    ],
+    "demo": ["demo/donation_demo.xml"],
+    "installable": True,
+}
diff --git a/donation_direct_debit/demo/donation_demo.xml b/donation_direct_debit/demo/donation_demo.xml
new file mode 100644
index 000000000..415ee4bf0
--- /dev/null
+++ b/donation_direct_debit/demo/donation_demo.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!--
+  © 2015-2016 Akretion France (Alexis de Lattre <alexis.delattre@akretion.com>)
+  License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+-->
+<odoo noupdate="1">
+
+
+<record id="donor2_iban" model="res.partner.bank">
+    <field name="acc_number">FR93 1277 1277 1277 1277 1277 127</field>
+    <field name="partner_id" ref="donation_base.donor2" />
+    <field name="bank_id" ref="account_payment_mode.bank_la_banque_postale" />
+</record>
+
+<record id="donor2_mandate" model="account.banking.mandate">
+    <field name="partner_bank_id" ref="donor2_iban" />
+    <field name="type">recurrent</field>
+    <field name="recurrent_sequence_type">first</field>
+    <field name="signature_date" eval="time.strftime('%Y-01-01')" />
+    <field name="state">valid</field>
+</record>
+
+<record id="donor4" model="res.partner">
+    <field name="name">Bella Andalouse</field>
+    <field name="donor_rank" eval="1" />
+    <field name="street">42 avenue de la République</field>
+    <field name="zip">50550</field>
+    <field name="city">Saint-Vaast la Hougue</field>
+    <field name="country_id" ref="base.fr" />
+    <field name="email">bella.andalouse@yahoo.example.com</field>
+    <field name="tax_receipt_option">annual</field>
+</record>
+
+<record id="donor4_iban" model="res.partner.bank">
+    <field name="acc_number">FR50 4266 4266 4266 4266 4266 426</field>
+    <field name="partner_id" ref="donor4" />
+    <field name="bank_id" ref="account_payment_mode.bank_la_banque_postale" />
+</record>
+
+<record id="donor4_mandate" model="account.banking.mandate">
+    <field name="partner_bank_id" ref="donor4_iban" />
+    <field name="type">recurrent</field>
+    <field name="recurrent_sequence_type">first</field>
+    <field name="signature_date" eval="time.strftime('%Y-%m-01')" />
+    <field name="state">valid</field>
+</record>
+
+<record id="donation6" model="donation.donation">
+    <field name="currency_id" ref="base.EUR" />
+    <field name="check_total">150</field>
+    <field name="partner_id" ref="donor4" />
+    <field name="donation_date" eval="time.strftime('%Y-%m-01')" />
+    <field name="payment_ref">Don prelev SEPA</field>
+    <field name="campaign_id" ref="donation.prospecting_origin" />
+    <field name="mandate_id" ref="donor4_mandate" />
+    <field
+            name="payment_mode_id"
+            ref="account_banking_sepa_direct_debit.payment_mode_inbound_sepa_dd1"
+        />
+    <field name="tax_receipt_option">annual</field>
+</record>
+
+<record id="donation6_l1" model="donation.line">
+    <field name="donation_id" ref="donation6" />
+    <field name="product_id" ref="donation_base.product_product_donation" />
+    <field name="quantity">1</field>
+    <field name="unit_price">150</field>
+</record>
+
+
+</odoo>
diff --git a/donation_direct_debit/i18n/de.po b/donation_direct_debit/i18n/de.po
new file mode 100644
index 000000000..fcd024184
--- /dev/null
+++ b/donation_direct_debit/i18n/de.po
@@ -0,0 +1,48 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+#	* donation_direct_debit
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 10.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: Automatically generated\n"
+"Language-Team: none\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+
+#. module: donation_direct_debit
+#: code:addons/donation_direct_debit/donation.py:75
+#, python-format
+msgid "A new draft direct debit order %s has been automatically created"
+msgstr ""
+
+#. module: donation_direct_debit
+#: code:addons/donation_direct_debit/donation.py:84
+#, python-format
+msgid "A new payment line has been automatically added to the existing draft direct debit order %s"
+msgstr ""
+
+#. module: donation_direct_debit
+#: model:ir.model,name:donation_direct_debit.model_donation_donation
+msgid "Donation"
+msgstr ""
+
+#. module: donation_direct_debit
+#: model:ir.model.fields,field_description:donation_direct_debit.field_donation_donation_mandate_id
+msgid "Mandate"
+msgstr ""
+
+#. module: donation_direct_debit
+#: model:ir.model.fields,field_description:donation_direct_debit.field_donation_donation_payment_mode_id
+msgid "Payment Mode"
+msgstr ""
+
+#. module: donation_direct_debit
+#: code:addons/donation_direct_debit/donation.py:101
+#, python-format
+msgid "You cannot cancel a donation which is linked to a payment line in a direct debit order. Remove it from the following direct debit order: %s."
+msgstr ""
diff --git a/donation_direct_debit/i18n/donation_direct_debit.pot b/donation_direct_debit/i18n/donation_direct_debit.pot
new file mode 100644
index 000000000..06553cda7
--- /dev/null
+++ b/donation_direct_debit/i18n/donation_direct_debit.pot
@@ -0,0 +1,48 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+#	* donation_direct_debit
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 10.0\n"
+"Report-Msgid-Bugs-To: \n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: donation_direct_debit
+#: code:addons/donation_direct_debit/donation.py:75
+#, python-format
+msgid "A new draft direct debit order %s has been automatically created"
+msgstr ""
+
+#. module: donation_direct_debit
+#: code:addons/donation_direct_debit/donation.py:84
+#, python-format
+msgid "A new payment line has been automatically added to the existing draft direct debit order %s"
+msgstr ""
+
+#. module: donation_direct_debit
+#: model:ir.model,name:donation_direct_debit.model_donation_donation
+msgid "Donation"
+msgstr ""
+
+#. module: donation_direct_debit
+#: model:ir.model.fields,field_description:donation_direct_debit.field_donation_donation_mandate_id
+msgid "Mandate"
+msgstr ""
+
+#. module: donation_direct_debit
+#: model:ir.model.fields,field_description:donation_direct_debit.field_donation_donation_payment_mode_id
+msgid "Payment Mode"
+msgstr ""
+
+#. module: donation_direct_debit
+#: code:addons/donation_direct_debit/donation.py:101
+#, python-format
+msgid "You cannot cancel a donation which is linked to a payment line in a direct debit order. Remove it from the following direct debit order: %s."
+msgstr ""
+
diff --git a/donation_direct_debit/i18n/fr.po b/donation_direct_debit/i18n/fr.po
new file mode 100644
index 000000000..c68386a26
--- /dev/null
+++ b/donation_direct_debit/i18n/fr.po
@@ -0,0 +1,64 @@
+# Translation of Odoo Server.
+# This file contains the translation of the following modules:
+# 	* donation_direct_debit
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Odoo Server 8.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-05-29 23:01+0000\n"
+"PO-Revision-Date: 2015-05-29 23:01+0000\n"
+"Last-Translator: <>\n"
+"Language-Team: \n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: \n"
+"Plural-Forms: \n"
+
+#. module: donation_direct_debit
+#: code:addons/donation_direct_debit/donation.py:75
+#, python-format
+msgid "A new draft direct debit order %s has been automatically created"
+msgstr "Un nouvel ordre de prélèvement brouillon %s a été créé automatiquement"
+
+#. module: donation_direct_debit
+#: code:addons/donation_direct_debit/donation.py:84
+#, python-format
+msgid ""
+"A new payment line has been automatically added to the existing draft direct "
+"debit order %s"
+msgstr ""
+"Une nouvelle ligne de paiement a été automatiquement ajoutée à l'ordre de "
+"prélèvement brouillon existant %s"
+
+#. module: donation_direct_debit
+#: model:ir.model,name:donation_direct_debit.model_donation_donation
+msgid "Donation"
+msgstr "Don"
+
+#. module: donation_direct_debit
+#: model:ir.model.fields,field_description:donation_direct_debit.field_donation_donation_mandate_id
+msgid "Mandate"
+msgstr "Mandat"
+
+#. module: donation_direct_debit
+#: model:ir.model.fields,field_description:donation_direct_debit.field_donation_donation_payment_mode_id
+msgid "Payment Mode"
+msgstr ""
+
+#. module: donation_direct_debit
+#: code:addons/donation_direct_debit/donation.py:101
+#, python-format
+msgid ""
+"You cannot cancel a donation which is linked to a payment line in a direct "
+"debit order. Remove it from the following direct debit order: %s."
+msgstr ""
+"Vous ne pouvez pas annuler un don lié à une ligne de paiement d'un ordre de "
+"prélèvement. Vous devez la supprimer de l'ordre de prélèvement %s."
+
+#~ msgid "No valid mandate found for donor %s"
+#~ msgstr "Aucun mandat valide trouvé pour le donateur %s"
+
+#~ msgid "There are 2 payment modes with the same bank journal!"
+#~ msgstr "Il y a 2 modes de paiements liés au même journal de banque !"
diff --git a/donation_direct_debit/models/__init__.py b/donation_direct_debit/models/__init__.py
new file mode 100644
index 000000000..b1847a5ab
--- /dev/null
+++ b/donation_direct_debit/models/__init__.py
@@ -0,0 +1,1 @@
+from . import donation
diff --git a/donation_direct_debit/models/donation.py b/donation_direct_debit/models/donation.py
new file mode 100644
index 000000000..fb6469ac5
--- /dev/null
+++ b/donation_direct_debit/models/donation.py
@@ -0,0 +1,130 @@
+# Copyright 2015-2021 Akretion France (http://www.akretion.com/)
+# @author: Alexis de Lattre <alexis.delattre@akretion.com>
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from odoo import _, api, fields, models
+from odoo.exceptions import UserError
+
+
+class DonationDonation(models.Model):
+    _inherit = "donation.donation"
+
+    mandate_id = fields.Many2one(
+        "account.banking.mandate",
+        string="Mandate",
+        states={"done": [("readonly", True)]},
+        tracking=True,
+        check_company=True,
+        ondelete="restrict",
+        domain="[('state', '=', 'valid'), "
+        "('partner_id', '=', commercial_partner_id), "
+        "('company_id', '=', company_id)]",
+    )
+    mandate_required = fields.Boolean(
+        related="payment_mode_id.payment_method_id.mandate_required", readonly=True
+    )
+
+    @api.onchange("payment_mode_id")
+    def donation_partner_direct_debit_change(self):
+        if (
+            self.partner_id
+            and self.payment_mode_id
+            and self.payment_mode_id.payment_method_id.mandate_required
+            and not self.mandate_id
+        ):
+            mandate = self.env["account.banking.mandate"].search(
+                [
+                    ("state", "=", "valid"),
+                    ("partner_id", "=", self.commercial_partner_id.id),
+                ],
+                limit=1,
+            )
+            if mandate:
+                self.mandate_id = mandate
+
+    def _prepare_donation_move(self):
+        vals = super()._prepare_donation_move()
+        vals.update(
+            {
+                "mandate_id": self.mandate_id.id or False,
+                "payment_mode_id": self.payment_mode_id.id,
+            }
+        )
+        return vals
+
+    def _prepare_payment_order(self):
+        self.ensure_one()
+        vals = {"payment_mode_id": self.payment_mode_id.id}
+        return vals
+
+    def validate(self):
+        """Create Direct debit payment order on donation validation or update
+        an existing draft Direct Debit pay order"""
+        res = super().validate()
+        apoo = self.env["account.payment.order"].sudo()
+        for donation in self:
+            if (
+                donation.payment_mode_id
+                and donation.payment_mode_id.payment_type == "inbound"
+                and donation.payment_mode_id.payment_order_ok
+                and donation.move_id
+            ):
+                payorders = apoo.search(
+                    [
+                        ("state", "=", "draft"),
+                        ("company_id", "=", donation.company_id.id),
+                        ("payment_mode_id", "=", donation.payment_mode_id.id),
+                    ]
+                )
+                msg = False
+                if payorders:
+                    payorder = payorders[0]
+                else:
+                    payorder_vals = donation._prepare_payment_order()
+                    payorder = apoo.create(payorder_vals)
+                    msg = _(
+                        "A new draft direct debit order "
+                        "<a href=# data-oe-model=account.payment.order "
+                        "data-oe-id=%d>%s</a> has been automatically created"
+                    ) % (payorder.id, payorder.name)
+                # add payment line
+                payment_account_id = (
+                    donation.payment_mode_id.fixed_journal_id.payment_debit_account_id.id
+                )
+                for mline in donation.move_id.line_ids:
+                    if mline.account_id.id == payment_account_id:
+                        mline.sudo().create_payment_line_from_move_line(payorder)
+                        break
+                if not msg:
+                    msg = _(
+                        "A new payment line has been automatically added "
+                        "to the existing draft direct debit order "
+                        "<a href=# data-oe-model=account.payment.order "
+                        "data-oe-id=%d>%s</a>."
+                    ) % (payorder.id, payorder.name)
+                donation.message_post(body=msg)
+        return res
+
+    def done2cancel(self):
+        for donation in self:
+            if donation.move_id:
+                donation_mv_line_ids = [line.id for line in donation.move_id.line_ids]
+                if donation_mv_line_ids:
+                    plines = self.env["account.payment.line"].search(
+                        [
+                            ("move_line_id", "in", donation_mv_line_ids),
+                            ("company_id", "=", donation.company_id.id),
+                            ("state", "in", ("draft", "open")),
+                        ]
+                    )
+                    if plines:
+                        raise UserError(
+                            _(
+                                "You cannot cancel a donation "
+                                "which is linked to a payment line in a "
+                                "direct debit order. Remove it from the "
+                                "following direct debit order: %s."
+                            )
+                            % plines[0].order_id.display_name
+                        )
+        return super().done2cancel()
diff --git a/donation_direct_debit/readme/CONFIGURE.rst b/donation_direct_debit/readme/CONFIGURE.rst
new file mode 100644
index 000000000..d07c7114d
--- /dev/null
+++ b/donation_direct_debit/readme/CONFIGURE.rst
@@ -0,0 +1,1 @@
+In the menu *Invoicing > Configuration > Management > Payment Modes*, make sure that you have a payment mode for SEPA Direct Debit that has the donation option active.
diff --git a/donation_direct_debit/readme/CONTRIBUTORS.rst b/donation_direct_debit/readme/CONTRIBUTORS.rst
new file mode 100644
index 000000000..ff65d68ce
--- /dev/null
+++ b/donation_direct_debit/readme/CONTRIBUTORS.rst
@@ -0,0 +1,1 @@
+* Alexis de Lattre <alexis.delattre@akretion.com>
diff --git a/donation_direct_debit/readme/DESCRIPTION.rst b/donation_direct_debit/readme/DESCRIPTION.rst
new file mode 100644
index 000000000..f690311c9
--- /dev/null
+++ b/donation_direct_debit/readme/DESCRIPTION.rst
@@ -0,0 +1,7 @@
+With this module, when you validate a donation that has a payment method linked to a SEPA direct debit payment mode:
+
+* if a draft direct debit order for SEPA Direct Debit already exists, a new payment line linked to the donation is added to it,
+
+* otherwise, a new SEPA direct debit order is created and a payment line linked to the donation is added to it.
+
+A message is added in the chatter of the donation with a link to the payment order.
diff --git a/donation_direct_debit/readme/USAGE.rst b/donation_direct_debit/readme/USAGE.rst
new file mode 100644
index 000000000..3a3cdbdde
--- /dev/null
+++ b/donation_direct_debit/readme/USAGE.rst
@@ -0,0 +1,5 @@
+On the donor (i.e. partner) form, create a bank account (IBAN) and create a related mandate. Validate the mandate.
+
+Create a new donation for that donor. Select the SEPA direct debit payment mode: a new required field *Mandate* will appear: select the mandate of the donor. Create the donation line(s).
+
+Validate the donation. In the chatter of the donation, you will see a message about the debit order that has been created or updated with a link to it. You can then click on the link and finalize the debit order.
diff --git a/donation_direct_debit/static/description/icon.png b/donation_direct_debit/static/description/icon.png
new file mode 100644
index 000000000..e519e6f54
Binary files /dev/null and b/donation_direct_debit/static/description/icon.png differ
diff --git a/donation_direct_debit/views/donation.xml b/donation_direct_debit/views/donation.xml
new file mode 100644
index 000000000..b1523d535
--- /dev/null
+++ b/donation_direct_debit/views/donation.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!--
+  Copyright 2015-2021 Akretion France (http://www.akretion.com/)
+  @author: Alexis de Lattre <alexis.delattre@akretion.com>
+  License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+-->
+<odoo>
+
+
+<record id="donation_form" model="ir.ui.view">
+    <field name="name">mandate.donation.form</field>
+    <field name="model">donation.donation</field>
+    <field name="inherit_id" ref="donation.donation_form" />
+    <field name="arch" type="xml">
+        <field name="payment_mode_id" position="after">
+                <field
+                    name="mandate_id"
+                    options="{'no_create_edit': True}"
+                    attrs="{'required': [('mandate_required', '=', True)], 'invisible': [('mandate_required', '=', False)]}"
+                />
+                <field name="mandate_required" invisible="1" />
+        </field>
+    </field>
+</record>
+
+
+</odoo>
diff --git a/setup/donation_direct_debit/odoo/addons/donation_direct_debit b/setup/donation_direct_debit/odoo/addons/donation_direct_debit
new file mode 120000
index 000000000..f2dcfaea7
--- /dev/null
+++ b/setup/donation_direct_debit/odoo/addons/donation_direct_debit
@@ -0,0 +1,1 @@
+../../../../donation_direct_debit
\ No newline at end of file
diff --git a/setup/donation_direct_debit/setup.py b/setup/donation_direct_debit/setup.py
new file mode 100644
index 000000000..28c57bb64
--- /dev/null
+++ b/setup/donation_direct_debit/setup.py
@@ -0,0 +1,6 @@
+import setuptools
+
+setuptools.setup(
+    setup_requires=['setuptools-odoo'],
+    odoo_addon=True,
+)


Test patch:
diff --git a/donation_direct_debit/tests/__init__.py b/donation_direct_debit/tests/__init__.py
new file mode 100644
index 000000000..280cc6f93
--- /dev/null
+++ b/donation_direct_debit/tests/__init__.py
@@ -0,0 +1,1 @@
+from . import test_direct_debit
diff --git a/donation_direct_debit/tests/test_direct_debit.py b/donation_direct_debit/tests/test_direct_debit.py
new file mode 100644
index 000000000..1120d001b
--- /dev/null
+++ b/donation_direct_debit/tests/test_direct_debit.py
@@ -0,0 +1,49 @@
+# Copyright 2016-2021 Akretion France (http://www.akretion.com/)
+# @author: Alexis de Lattre <alexis.delattre@akretion.com>
+# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
+
+from odoo.tests.common import TransactionCase
+
+
+class TestDirectDebit(TransactionCase):
+    def test_direct_debit(self):
+        donation = self.env.ref("donation_direct_debit.donation6")
+        dd_payment_mode = self.env.ref(
+            "account_banking_sepa_direct_debit.payment_mode_inbound_sepa_dd1"
+        )
+        bank_journal = self.env["account.journal"].create(
+            {
+                "type": "bank",
+                "name": "Bank account test",
+            }
+        )
+        dd_payment_mode.write(
+            {
+                "donation": True,
+                "bank_account_link": "fixed",
+                "fixed_journal_id": bank_journal.id,
+            }
+        )
+        donation.validate()
+        self.assertEqual(donation.state, "done")
+        self.assertTrue(donation.move_id)
+        self.assertEqual(donation.mandate_id, donation.move_id.mandate_id)
+        self.assertEqual(donation.payment_mode_id, donation.move_id.payment_mode_id)
+        paylines = self.env["account.payment.line"].search(
+            [
+                ("communication", "=", donation.payment_ref),
+                ("partner_id", "=", donation.commercial_partner_id.id),
+            ]
+        )
+        self.assertEqual(len(paylines), 1)
+        payline = paylines[0]
+        self.assertFalse(
+            donation.currency_id.compare_amounts(
+                payline.amount_currency, donation.check_total
+            )
+        )
+        self.assertEqual(payline.currency_id, donation.currency_id)
+        self.assertTrue(payline.move_line_id in donation.move_id.line_ids)
+        self.assertEqual(payline.partner_bank_id, donation.mandate_id.partner_bank_id)
+        self.assertEqual(payline.mandate_id, donation.mandate_id)
+        self.assertEqual(payline.order_id.state, "draft")