diff --git a/account_banking_mandate_sale_contact/README.rst b/account_banking_mandate_sale_contact/README.rst new file mode 100644 index 000000000..0a66b1f0e --- /dev/null +++ b/account_banking_mandate_sale_contact/README.rst @@ -0,0 +1,104 @@ +==================================== +Account Banking Mandate Sale Contact +==================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:7cfbc94c4688e616b2efe836942f2a7d58ae4b7c7fecd870882b8d4b2942a810 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fbank--payment-lightgray.png?logo=github + :target: https://github.com/OCA/bank-payment/tree/16.0/account_banking_mandate_sale_contact + :alt: OCA/bank-payment +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/bank-payment-16-0/bank-payment-16-0-account_banking_mandate_sale_contact + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/bank-payment&target_branch=16.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module combines the functionality of account_banking_mandate_sale +with account_banking_mandate_contact and to allows you to add a specific +contact mandate to sale orders. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To configure this module, you need to: + +#. Go to Settings/Sales/Invoicing and select the Default Mandates +option. This allows you to choose if you want the mandate of the sale +partner, invoice address or delivery address. + +#. If you want to specifically change the default mandate for a +customer, you can go to the "Sales & Purchase" tab of his contact form. + +Usage +===== + +For selecting the mandate at contact level: + +#. Go to *Invoicing > Customers > Customers*. #. Open or create one +contact. #. On the "Sales & Purchase" page, fill *Contact Mandate*. + +Then, when you select a payment mode that requires mandate on a sale +order, Odoo will choose the mandate selected at contact level. That +mandate will be copied from the sale order to the invoice. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* Alberto Martínez + +Contributors +------------ + +- ``Sygel ``\ \_: + + - Alberto Martínez + +Maintainers +----------- + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/bank-payment `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/account_banking_mandate_sale_contact/__init__.py b/account_banking_mandate_sale_contact/__init__.py new file mode 100644 index 000000000..31660d6a9 --- /dev/null +++ b/account_banking_mandate_sale_contact/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import models diff --git a/account_banking_mandate_sale_contact/__manifest__.py b/account_banking_mandate_sale_contact/__manifest__.py new file mode 100644 index 000000000..3ea2f6ac0 --- /dev/null +++ b/account_banking_mandate_sale_contact/__manifest__.py @@ -0,0 +1,15 @@ +# Copyright 2024 Alberto Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Account Banking Mandate Sale Contact", + "summary": "Add a specific contact mandate to sale orders", + "version": "16.0.1.0.0", + "category": "Banking addons", + "website": "https://github.com/OCA/bank-payment", + "author": "Alberto Martínez, Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": ["account_banking_mandate_contact", "account_banking_mandate_sale"], + "data": ["views/res_config_settings.xml", "views/res_partner.xml"], +} diff --git a/account_banking_mandate_sale_contact/models/__init__.py b/account_banking_mandate_sale_contact/models/__init__.py new file mode 100644 index 000000000..d9c0dc324 --- /dev/null +++ b/account_banking_mandate_sale_contact/models/__init__.py @@ -0,0 +1,6 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import res_company +from . import res_config_settings +from . import res_partner +from . import sale_order diff --git a/account_banking_mandate_sale_contact/models/res_company.py b/account_banking_mandate_sale_contact/models/res_company.py new file mode 100644 index 000000000..261512d37 --- /dev/null +++ b/account_banking_mandate_sale_contact/models/res_company.py @@ -0,0 +1,31 @@ +# Copyright 2024 Alberto Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResCompany(models.Model): + _inherit = "res.company" + + sale_default_mandate_contact = fields.Selection( + selection=[ + ("partner_id", "Customer Mandate"), + ("commercial_partner_id", "Commercial Customer Mandate"), + ("partner_invoice_id", "Invoice Address Mandate"), + ("partner_shipping_id", "Delivery Address Mandate"), + ], + string="Default Sale Mandate Contact", + default="partner_id", + help="The contact of this company in which odoo will search for the mandate on sales\n" + "- Customer Mandate: Odoo will look the mandate in the sale partner," + " whether is an individual or the company\n" + "- Commercial Customer Mandate: Odoo will look the mandate in the" + " sale partner company\n" + "- Invoice Address Mandate: Odoo will look the mandate in the" + " sale invoice address\n" + "- Delivery Address Mandate: Odoo will look the mandate in the" + " sale delivery address\n" + "- False: Odoo will use the first mandate he founds for the partner company." + " Odoo will also use this option if no default mandate is found in the" + " partner of the above options", + ) diff --git a/account_banking_mandate_sale_contact/models/res_config_settings.py b/account_banking_mandate_sale_contact/models/res_config_settings.py new file mode 100644 index 000000000..a900fea6f --- /dev/null +++ b/account_banking_mandate_sale_contact/models/res_config_settings.py @@ -0,0 +1,14 @@ +# Copyright 2024 Alberto Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + sale_default_mandate_contact = fields.Selection( + string="Default Sale Mandate Contact", + related="company_id.sale_default_mandate_contact", + readonly=False, + ) diff --git a/account_banking_mandate_sale_contact/models/res_partner.py b/account_banking_mandate_sale_contact/models/res_partner.py new file mode 100644 index 000000000..6e00d76ea --- /dev/null +++ b/account_banking_mandate_sale_contact/models/res_partner.py @@ -0,0 +1,36 @@ +# Copyright 2024 Alberto Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class ResPartner(models.Model): + _inherit = "res.partner" + + sale_default_mandate_contact = fields.Selection( + selection=[ + ("partner_id", "Customer Mandate"), + ("commercial_partner_id", "Commercial Customer Mandate"), + ("partner_invoice_id", "Invoice Address Mandate"), + ("partner_shipping_id", "Delivery Address Mandate"), + ], + string="Default Sale Mandate Contact", + help="The partner of the sales in which odoo will search for the mandate\n" + "- Customer Mandate: Odoo will look the mandate in the sale partner," + " whether is an individual or the company\n" + "- Commercial Customer Mandate: Odoo will look the mandate in the" + " sale partner company\n" + "- Invoice Address Mandate: Odoo will look the mandate in the" + " sale invoice address\n" + "- Delivery Address Mandate: Odoo will look the mandate in the" + " sale delivery address\n" + "- False: Odoo will use the first mandate he founds for the partner company." + " Odoo will also use this option if no default mandate is found in the" + " partner of the above options", + ) + + @api.model + def _commercial_fields(self): + return super()._commercial_fields() + [ + "sale_default_mandate_contact", + ] diff --git a/account_banking_mandate_sale_contact/models/sale_order.py b/account_banking_mandate_sale_contact/models/sale_order.py new file mode 100644 index 000000000..d0a5980ed --- /dev/null +++ b/account_banking_mandate_sale_contact/models/sale_order.py @@ -0,0 +1,40 @@ +# Copyright 2024 Alberto Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, models + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + @api.depends( + "partner_id", "partner_invoice_id", "partner_shipping_id", "payment_mode_id" + ) + def _compute_mandate_id(self): + procesed_orders = self.browse() + for order in self: + if ( + order.partner_invoice_id + and order.payment_mode_id + and order.payment_mode_id.payment_method_id.mandate_required + ): + partner_mandate_config = ( + order.commercial_invoice_partner_id.sale_default_mandate_contact + or order.company_id.sale_default_mandate_contact + ) + if partner_mandate_config: + mandate = False + if partner_mandate_config == "partner_id": + mandate = order.partner_id.contact_mandate_id + if partner_mandate_config == "commercial_partner_id": + mandate = ( + order.partner_id.commercial_partner_id.contact_mandate_id + ) + elif partner_mandate_config == "partner_invoice_id": + mandate = order.partner_invoice_id.contact_mandate_id + elif partner_mandate_config == "partner_shipping_id": + mandate = order.partner_shipping_id.contact_mandate_id + if mandate: + order.mandate_id = mandate + procesed_orders |= order + return super(SaleOrder, self - procesed_orders)._compute_mandate_id() diff --git a/account_banking_mandate_sale_contact/readme/CONFIGURE.md b/account_banking_mandate_sale_contact/readme/CONFIGURE.md new file mode 100644 index 000000000..e0bc5adb3 --- /dev/null +++ b/account_banking_mandate_sale_contact/readme/CONFIGURE.md @@ -0,0 +1,7 @@ +To configure this module, you need to: + +#. Go to Settings/Sales/Invoicing and select the Default Mandates option. + This allows you to choose if you want the mandate of the sale partner, + invoice address or delivery address. + +#. If you want to specifically change the default mandate for a customer, you can go to the "Sales & Purchase" tab of his contact form. diff --git a/account_banking_mandate_sale_contact/readme/CONTRIBUTORS.md b/account_banking_mandate_sale_contact/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..736ea050a --- /dev/null +++ b/account_banking_mandate_sale_contact/readme/CONTRIBUTORS.md @@ -0,0 +1,3 @@ +* `Sygel `_: + + * Alberto Martínez \ No newline at end of file diff --git a/account_banking_mandate_sale_contact/readme/DESCRIPTION.md b/account_banking_mandate_sale_contact/readme/DESCRIPTION.md new file mode 100644 index 000000000..ff1a091d2 --- /dev/null +++ b/account_banking_mandate_sale_contact/readme/DESCRIPTION.md @@ -0,0 +1,3 @@ +This module combines the functionality of account_banking_mandate_sale +with account_banking_mandate_contact and to allows you to +add a specific contact mandate to sale orders. diff --git a/account_banking_mandate_sale_contact/readme/USAGE.md b/account_banking_mandate_sale_contact/readme/USAGE.md new file mode 100644 index 000000000..03880ef79 --- /dev/null +++ b/account_banking_mandate_sale_contact/readme/USAGE.md @@ -0,0 +1,8 @@ +For selecting the mandate at contact level: + +#. Go to *Invoicing > Customers > Customers*. +#. Open or create one contact. +#. On the "Sales & Purchase" page, fill *Contact Mandate*. + +Then, when you select a payment mode that requires mandate on a sale order, Odoo will +choose the mandate selected at contact level. That mandate will be copied from the sale order to the invoice. diff --git a/account_banking_mandate_sale_contact/static/description/icon.png b/account_banking_mandate_sale_contact/static/description/icon.png new file mode 100644 index 000000000..207fb7ad5 Binary files /dev/null and b/account_banking_mandate_sale_contact/static/description/icon.png differ diff --git a/account_banking_mandate_sale_contact/static/description/index.html b/account_banking_mandate_sale_contact/static/description/index.html new file mode 100644 index 000000000..e76257473 --- /dev/null +++ b/account_banking_mandate_sale_contact/static/description/index.html @@ -0,0 +1,446 @@ + + + + + + +Account Banking Mandate Sale Contact + + + +
+

Account Banking Mandate Sale Contact

+ + +

Beta License: AGPL-3 OCA/bank-payment Translate me on Weblate Try me on Runboat

+

This module combines the functionality of account_banking_mandate_sale +with account_banking_mandate_contact and to allows you to add a specific +contact mandate to sale orders.

+

Table of contents

+ +
+

Configuration

+

To configure this module, you need to:

+

#. Go to Settings/Sales/Invoicing and select the Default Mandates +option. This allows you to choose if you want the mandate of the sale +partner, invoice address or delivery address.

+

#. If you want to specifically change the default mandate for a +customer, you can go to the “Sales & Purchase” tab of his contact form.

+
+
+

Usage

+

For selecting the mandate at contact level:

+

#. Go to Invoicing > Customers > Customers. #. Open or create one +contact. #. On the “Sales & Purchase” page, fill Contact Mandate.

+

Then, when you select a payment mode that requires mandate on a sale +order, Odoo will choose the mandate selected at contact level. That +mandate will be copied from the sale order to the invoice.

+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Alberto Martínez
  • +
+
+
+

Contributors

+
    +
  • Sygel <https://www.sygel.es>_:
      +
    • Alberto Martínez
    • +
    +
  • +
+
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/bank-payment project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/account_banking_mandate_sale_contact/tests/__init__.py b/account_banking_mandate_sale_contact/tests/__init__.py new file mode 100644 index 000000000..bde316600 --- /dev/null +++ b/account_banking_mandate_sale_contact/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from . import test_account_banking_mandate_sale_contact diff --git a/account_banking_mandate_sale_contact/tests/test_account_banking_mandate_sale_contact.py b/account_banking_mandate_sale_contact/tests/test_account_banking_mandate_sale_contact.py new file mode 100644 index 000000000..048db6ea5 --- /dev/null +++ b/account_banking_mandate_sale_contact/tests/test_account_banking_mandate_sale_contact.py @@ -0,0 +1,154 @@ +# Copyright 2024 Alberto Martínez +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from unittest.mock import patch + +from odoo import fields +from odoo.tests.common import Form, TransactionCase + +from odoo.addons.account.models.account_payment_method import AccountPaymentMethod + + +class TestAccountBankingMandateSaleContact(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.partner_company = cls.env["res.partner"].create( + { + "name": "Test Partner Company", + "company_type": "company", + } + ) + cls.partner_invoice = cls.env["res.partner"].create( + { + "name": "Test Partner Invoice Address", + "company_type": "person", + "type": "invoice", + "parent_id": cls.partner_company.id, + } + ) + cls.partner_delivery = cls.env["res.partner"].create( + { + "name": "Test Partner Delivery Address", + "company_type": "person", + "type": "delivery", + "parent_id": cls.partner_company.id, + } + ) + + cls.partner_bank = cls._create_res_partner_bank( + cls.partner_company, "Test Bank" + ) + cls.mandate_first = cls._create_mandate(cls.partner_bank, "Test Mandate") + cls.mandate_company = cls._create_mandate( + cls.partner_bank, "Test Company Mandate" + ) + cls.mandate_invoice = cls._create_mandate( + cls.partner_bank, "Test Invoice Mandate" + ) + cls.mandate_delivery = cls._create_mandate( + cls.partner_bank, "Test Delivery Mandate" + ) + cls.payment_method = cls._create_payment_method( + { + "name": "Test Payment Method", + "code": "test_payment_method", + "payment_type": "inbound", + "bank_account_required": True, + "mandate_required": True, + } + ) + cls.journal_bank = cls.env["account.journal"].create( + {"name": "Test Journal", "type": "bank", "code": "bank"} + ) + payment_form = Form(cls.env["account.payment.mode"]) + payment_form.name = "Test Payment Mode" + payment_form.payment_method_id = cls.payment_method + payment_form.bank_account_link = "fixed" + payment_form.fixed_journal_id = cls.journal_bank + payment_form.payment_order_ok = True + cls.payment_mode = payment_form.save() + cls.partner_company.update( + { + "customer_payment_mode_id": cls.payment_mode.id, + "contact_mandate_id": cls.mandate_company.id, + } + ) + cls.partner_invoice.contact_mandate_id = cls.mandate_invoice + cls.partner_delivery.contact_mandate_id = cls.mandate_delivery + + @classmethod + def _create_res_partner_bank(cls, partner_id, acc_number): + res_partner_bank_form = Form(cls.env["res.partner.bank"]) + res_partner_bank_form.partner_id = partner_id + res_partner_bank_form.acc_number = acc_number + return res_partner_bank_form.save() + + @classmethod + def _create_mandate(cls, partner_bank, scheme): + mandate_form = Form(cls.env["account.banking.mandate"]) + mandate_form.partner_bank_id = partner_bank + mandate_form.signature_date = fields.Date.from_string("2021-01-01") + mandate = mandate_form.save() + mandate.validate() + return mandate + + @classmethod + def _create_payment_method(cls, payment_method_vals): + method_get_payment_method_information = ( + AccountPaymentMethod._get_payment_method_information + ) + + def _get_payment_method_information(cls): + res = method_get_payment_method_information(cls) + res[payment_method_vals["code"]] = { + "mode": "multi", + "domain": [("type", "=", "bank")], + } + return res + + with patch.object( + AccountPaymentMethod, + "_get_payment_method_information", + _get_payment_method_information, + ): + return cls.env["account.payment.method"].create(payment_method_vals) + + def test_sale_mandate(self): + """Tests the computed sale mandate with the default company configuration""" + sale_form = Form(self.env["sale.order"].with_context()) + sale_form.partner_id = self.partner_company + sale = sale_form.save() + self.assertEqual(sale.mandate_id, self.mandate_company) + + def test_sale_mandate_before(self): + """Tests the default sale mendate before this module, the first mandate found""" + self.env.user.company_id.sale_default_mandate_contact = False + sale_form = Form(self.env["sale.order"].with_context()) + sale_form.partner_id = self.partner_company + sale = sale_form.save() + self.assertEqual(sale.mandate_id, self.mandate_first) + + def test_sale_mandate_invoice_address(self): + """Tests the computed sale mendate with a config based on invoice address""" + self.partner_company.sale_default_mandate_contact = "partner_invoice_id" + sale_form = Form(self.env["sale.order"].with_context()) + sale_form.partner_id = self.partner_company + sale = sale_form.save() + self.assertEqual(sale.mandate_id, self.mandate_invoice) + + def test_sale_mandate_delivery_address(self): + """Tests the computed sale mendate with a config based on delivery address""" + self.partner_company.sale_default_mandate_contact = "partner_shipping_id" + sale_form = Form(self.env["sale.order"].with_context()) + sale_form.partner_id = self.partner_company + sale = sale_form.save() + self.assertEqual(sale.mandate_id, self.mandate_delivery) + + def test_sale_mandate_commercial_partner(self): + """Tests the computed sale mendate with a config based on delivery address""" + self.partner_company.sale_default_mandate_contact = "commercial_partner_id" + sale_form = Form(self.env["sale.order"].with_context()) + sale_form.partner_id = self.partner_invoice + sale = sale_form.save() + self.assertEqual(sale.mandate_id, self.mandate_company) diff --git a/account_banking_mandate_sale_contact/views/res_config_settings.xml b/account_banking_mandate_sale_contact/views/res_config_settings.xml new file mode 100644 index 000000000..bf41713ce --- /dev/null +++ b/account_banking_mandate_sale_contact/views/res_config_settings.xml @@ -0,0 +1,26 @@ + + + + + settings.view.form.sale.warning + res.config.settings + + + +
+
+
+ Default Mandates +
+ The partner of the sales in which odoo will search for the mandate +
+
+ +
+
+
+ + + + diff --git a/account_banking_mandate_sale_contact/views/res_partner.xml b/account_banking_mandate_sale_contact/views/res_partner.xml new file mode 100644 index 000000000..bc4577942 --- /dev/null +++ b/account_banking_mandate_sale_contact/views/res_partner.xml @@ -0,0 +1,21 @@ + + + + + res.partner + + + + + + + + + diff --git a/setup/account_banking_mandate_sale_contact/odoo/addons/account_banking_mandate_sale_contact b/setup/account_banking_mandate_sale_contact/odoo/addons/account_banking_mandate_sale_contact new file mode 120000 index 000000000..776a7585e --- /dev/null +++ b/setup/account_banking_mandate_sale_contact/odoo/addons/account_banking_mandate_sale_contact @@ -0,0 +1 @@ +../../../../account_banking_mandate_sale_contact \ No newline at end of file diff --git a/setup/account_banking_mandate_sale_contact/setup.py b/setup/account_banking_mandate_sale_contact/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/account_banking_mandate_sale_contact/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)