From 4e5016738d09d64ac0fefed7b3b1d394326db7eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Taymans?= Date: Thu, 28 Nov 2024 15:14:13 +0100 Subject: [PATCH] [ADD] website_sale_restrict_sepa_dd --- .../odoo/addons/website_sale_restrict_sepa_dd | 1 + setup/website_sale_restrict_sepa_dd/setup.py | 6 + website_sale_restrict_sepa_dd/README.rst | 70 +++ website_sale_restrict_sepa_dd/__init__.py | 4 + website_sale_restrict_sepa_dd/__manifest__.py | 26 ++ .../models/__init__.py | 6 + .../models/payment_provider.py | 52 +++ .../models/product_template.py | 37 ++ .../models/sale_order.py | 89 ++++ .../readme/CONTRIBUTORS.rst | 3 + .../readme/DESCRIPTION.rst | 1 + .../static/description/index.html | 421 ++++++++++++++++++ .../static/src/js/payment_form.js | 83 ++++ .../views/product_views.xml | 16 + .../views/templates.xml | 22 + 15 files changed, 837 insertions(+) create mode 120000 setup/website_sale_restrict_sepa_dd/odoo/addons/website_sale_restrict_sepa_dd create mode 100644 setup/website_sale_restrict_sepa_dd/setup.py create mode 100644 website_sale_restrict_sepa_dd/README.rst create mode 100644 website_sale_restrict_sepa_dd/__init__.py create mode 100644 website_sale_restrict_sepa_dd/__manifest__.py create mode 100644 website_sale_restrict_sepa_dd/models/__init__.py create mode 100644 website_sale_restrict_sepa_dd/models/payment_provider.py create mode 100644 website_sale_restrict_sepa_dd/models/product_template.py create mode 100644 website_sale_restrict_sepa_dd/models/sale_order.py create mode 100644 website_sale_restrict_sepa_dd/readme/CONTRIBUTORS.rst create mode 100644 website_sale_restrict_sepa_dd/readme/DESCRIPTION.rst create mode 100644 website_sale_restrict_sepa_dd/static/description/index.html create mode 100644 website_sale_restrict_sepa_dd/static/src/js/payment_form.js create mode 100644 website_sale_restrict_sepa_dd/views/product_views.xml create mode 100644 website_sale_restrict_sepa_dd/views/templates.xml diff --git a/setup/website_sale_restrict_sepa_dd/odoo/addons/website_sale_restrict_sepa_dd b/setup/website_sale_restrict_sepa_dd/odoo/addons/website_sale_restrict_sepa_dd new file mode 120000 index 000000000..495bb16b8 --- /dev/null +++ b/setup/website_sale_restrict_sepa_dd/odoo/addons/website_sale_restrict_sepa_dd @@ -0,0 +1 @@ +../../../../website_sale_restrict_sepa_dd \ No newline at end of file diff --git a/setup/website_sale_restrict_sepa_dd/setup.py b/setup/website_sale_restrict_sepa_dd/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/website_sale_restrict_sepa_dd/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/website_sale_restrict_sepa_dd/README.rst b/website_sale_restrict_sepa_dd/README.rst new file mode 100644 index 000000000..3600d4148 --- /dev/null +++ b/website_sale_restrict_sepa_dd/README.rst @@ -0,0 +1,70 @@ +================================== +Restrict SEPA Direct Debit Payment +================================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:d5514507c207937042ea64484cf364dd8c11232d97806933a51e12dcf7c534f9 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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-coopiteasy%2Faddons-lightgray.png?logo=github + :target: https://github.com/coopiteasy/addons/tree/16.0/website_sale_restrict_sepa_dd + :alt: coopiteasy/addons + +|badge1| |badge2| |badge3| + +Form to order subscription product + +**Table of contents** + +.. contents:: + :local: + +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 +~~~~~~~ + +* Coop IT Easy SC + +Contributors +~~~~~~~~~~~~ + +* `Coop IT Easy SC `_: + + * Rémy Taymans + +Maintainers +~~~~~~~~~~~ + +.. |maintainer-remytms| image:: https://github.com/remytms.png?size=40px + :target: https://github.com/remytms + :alt: remytms + +Current maintainer: + +|maintainer-remytms| + +This module is part of the `coopiteasy/addons `_ project on GitHub. + +You are welcome to contribute. diff --git a/website_sale_restrict_sepa_dd/__init__.py b/website_sale_restrict_sepa_dd/__init__.py new file mode 100644 index 000000000..7fbcfccdd --- /dev/null +++ b/website_sale_restrict_sepa_dd/__init__.py @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: 2024 Coop IT Easy SC +# +# SPDX-License-Identifier: AGPL-3.0-or-later +from . import models diff --git a/website_sale_restrict_sepa_dd/__manifest__.py b/website_sale_restrict_sepa_dd/__manifest__.py new file mode 100644 index 000000000..46a9177ef --- /dev/null +++ b/website_sale_restrict_sepa_dd/__manifest__.py @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: 2024 Coop IT Easy SC +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +{ + "name": "Restrict SEPA Direct Debit Payment", + "summary": """ + Restrict payment by SEPA Direct Debit for some products.""", + "version": "16.0.1.0.0", + "category": "Website", + "website": "https://github.com/coopiteasy/addons", + "author": "Coop IT Easy SC", + "maintainers": ["remytms"], + "license": "AGPL-3", + "application": False, + "depends": [ + "website_sale_sepa_dd_payment", + ], + "excludes": [], + "data": [ + "views/product_views.xml", + "views/templates.xml", + ], + "demo": [], + "qweb": [], +} diff --git a/website_sale_restrict_sepa_dd/models/__init__.py b/website_sale_restrict_sepa_dd/models/__init__.py new file mode 100644 index 000000000..7e3148687 --- /dev/null +++ b/website_sale_restrict_sepa_dd/models/__init__.py @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2024 Coop IT Easy SC +# +# SPDX-License-Identifier: AGPL-3.0-or-later +from . import product_template +from . import payment_provider +from . import sale_order diff --git a/website_sale_restrict_sepa_dd/models/payment_provider.py b/website_sale_restrict_sepa_dd/models/payment_provider.py new file mode 100644 index 000000000..61aa8157e --- /dev/null +++ b/website_sale_restrict_sepa_dd/models/payment_provider.py @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: 2024 Coop IT Easy SC +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +from odoo import api, models + + +class PaymentProvider(models.Model): + _inherit = "payment.provider" + + @api.model + def _get_compatible_providers( + self, *args, sale_order_id=None, website_id=None, **kwargs + ): + """Override of payment to exclude SEPA Direct Debit if product + does not allow it. + + :param int sale_order_id: The sale order to be paid, if any, as + a `sale.order` id + :param int website_id: The provided website, as a `website` id + :return: The compatible providers + :rtype: recordset of `payment.provider` + """ + compatible_providers = super()._get_compatible_providers( + *args, sale_order_id=sale_order_id, website_id=website_id, **kwargs + ) + order = self.env["sale.order"].browse(sale_order_id).exists() + # Allow SEPA Direct Debit only if all products allow SEPA DD payment + allow_sepa_dd_payment = all( + order.order_line.mapped("product_id").mapped("allow_sepa_dd_payment") + ) + # Allow only SEPA Direct Debit if at least one product must be payed + # by SEPA Direct Debit + only_sepa_dd_payment = any( + order.order_line.mapped("product_id").mapped("only_sepa_dd_payment") + ) + if not allow_sepa_dd_payment and not only_sepa_dd_payment: + # Exclude sepa_dd + compatible_providers = compatible_providers.filtered( + lambda p: p.code != "custom" or p.custom_mode != "sepa_dd" + ) + elif allow_sepa_dd_payment and only_sepa_dd_payment: + # Get only sepa_dd + compatible_providers = compatible_providers.filtered( + lambda p: p.code == "custom" and p.custom_mode == "sepa_dd" + ) + elif not allow_sepa_dd_payment and only_sepa_dd_payment: + # Product are not compatible between each other. No payement + # provider can be used. + compatible_providers = self.env["payment.provider"] + + return compatible_providers diff --git a/website_sale_restrict_sepa_dd/models/product_template.py b/website_sale_restrict_sepa_dd/models/product_template.py new file mode 100644 index 000000000..03170a561 --- /dev/null +++ b/website_sale_restrict_sepa_dd/models/product_template.py @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: 2024 Coop IT Easy SC +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class SaleOrder(models.Model): + _inherit = "product.template" + + allow_sepa_dd_payment = fields.Boolean(string="Allow SEPA Direct Debit Payment") + only_sepa_dd_payment = fields.Boolean(string="Only SEPA Direct Debit Payment") + + @api.constrains("allow_sepa_dd_payment", "only_sepa_dd_payment") + def _check_allow_sepa_dd_payment(self): + """Ensure that allow_sepa_dd_payment is set if + only_sepa_dd_payment is set. + """ + for product in self: + if product.only_sepa_dd_payment and not product.allow_sepa_dd_payment: + raise ValidationError( + _( + "Allow SEPA Direct Debit payment for the product " + "in ordre to set Only SEPA Direct Debit payment." + ) + ) + + @api.onchange("only_sepa_dd_payment") + def _onchange_only_sepa_dd_payment(self): + if self.only_sepa_dd_payment: + self.allow_sepa_dd_payment = True + + @api.onchange("allow_sepa_dd_payment") + def _onchange_allow_sepa_dd_payment(self): + if not self.allow_sepa_dd_payment: + self.only_sepa_dd_payment = False diff --git a/website_sale_restrict_sepa_dd/models/sale_order.py b/website_sale_restrict_sepa_dd/models/sale_order.py new file mode 100644 index 000000000..35ae221f5 --- /dev/null +++ b/website_sale_restrict_sepa_dd/models/sale_order.py @@ -0,0 +1,89 @@ +# SPDX-FileCopyrightText: 2024 Coop IT Easy SC +# +# SPDX-License-Identifier: AGPL-3.0-or-later + + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + allow_sepa_dd_payment = fields.Boolean(compute="_compute_allow_sepa_dd_payment") + only_sepa_dd_payment = fields.Boolean(compute="_compute_only_sepa_dd_payment") + + @api.constrains("order_line") + def _check_compatible_product(self): + """Prevent incompatible product to be added to the same sale order""" + for order in self: + only_sepa_dd_payment = any( + order.order_line.mapped("product_id").mapped("only_sepa_dd_payment") + ) + allow_sepa_dd_payment = all( + order.order_line.mapped("product_id").mapped("allow_sepa_dd_payment") + ) + if only_sepa_dd_payment and not allow_sepa_dd_payment: + raise ValidationError( + _( + "Cannot add product that does not allow SEPA " + "Direct Debit with products that allow only SEPA " + "Direct Debit payment." + ) + ) + + @api.depends("order_line") + def _compute_allow_sepa_dd_payment(self): + for order in self: + allow_sepa_dd_payment = all( + order.order_line.mapped("product_id").mapped("allow_sepa_dd_payment") + ) + order.allow_sepa_dd_payment = allow_sepa_dd_payment + + @api.depends("order_line") + def _compute_only_sepa_dd_payment(self): + for order in self: + order.only_sepa_dd_payment = any( + order.order_line.mapped("product_id").mapped("only_sepa_dd_payment") + ) + + def _cart_update(self, product_id, line_id=None, add_qty=0, set_qty=0, **kwargs): + """Prevent incompatible product to be added to the cart.""" + self.ensure_one() + product = self.env["product.product"].browse(product_id).exists() + warning = "" + if product: + only_sepa_dd_product = self.order_line.mapped("product_id").filtered( + lambda p: p.only_sepa_dd_payment + ) + allow_sepa_dd_payment = all( + self.order_line.mapped("product_id").mapped("allow_sepa_dd_payment") + ) + if only_sepa_dd_product and not product.allow_sepa_dd_payment: + # This product cannot be added + add_qty, set_qty = None, 0 + warning = _( + f"Product {product.name} cannot be added to cart " + "because product(s) : " + f"{', '.join(p.name for p in only_sepa_dd_product)} " + "must be payed by SEPA Direct Debit and product " + f"{product.name} cannot be payed by such method." + ) + elif not allow_sepa_dd_payment and product.only_sepa_dd_payment: + add_qty, set_qty = None, 0 + warning = _( + f"Product {product.name} cannot be added to cart " + "because other products in the cart does not allow " + "SEPA Direct Debit as payment method and " + f"{product.name} must be payed with such a method." + ) + + values = super()._cart_update( + product_id=product_id, + line_id=line_id, + add_qty=add_qty, + set_qty=set_qty, + **kwargs, + ) + values["warning"] = warning + return values diff --git a/website_sale_restrict_sepa_dd/readme/CONTRIBUTORS.rst b/website_sale_restrict_sepa_dd/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..31498d266 --- /dev/null +++ b/website_sale_restrict_sepa_dd/readme/CONTRIBUTORS.rst @@ -0,0 +1,3 @@ +* `Coop IT Easy SC `_: + + * Rémy Taymans diff --git a/website_sale_restrict_sepa_dd/readme/DESCRIPTION.rst b/website_sale_restrict_sepa_dd/readme/DESCRIPTION.rst new file mode 100644 index 000000000..9cd7a7503 --- /dev/null +++ b/website_sale_restrict_sepa_dd/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +Form to order subscription product diff --git a/website_sale_restrict_sepa_dd/static/description/index.html b/website_sale_restrict_sepa_dd/static/description/index.html new file mode 100644 index 000000000..a2774acac --- /dev/null +++ b/website_sale_restrict_sepa_dd/static/description/index.html @@ -0,0 +1,421 @@ + + + + + +Restrict SEPA Direct Debit Payment + + + +
+

Restrict SEPA Direct Debit Payment

+ + +

Beta License: AGPL-3 coopiteasy/addons

+

Form to order subscription product

+

Table of contents

+ +
+

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

+
    +
  • Coop IT Easy SC
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

Current maintainer:

+

remytms

+

This module is part of the coopiteasy/addons project on GitHub.

+

You are welcome to contribute.

+
+
+
+ + diff --git a/website_sale_restrict_sepa_dd/static/src/js/payment_form.js b/website_sale_restrict_sepa_dd/static/src/js/payment_form.js new file mode 100644 index 000000000..c50a413e2 --- /dev/null +++ b/website_sale_restrict_sepa_dd/static/src/js/payment_form.js @@ -0,0 +1,83 @@ +odoo.define("website_sale_sepa_dd_payment.payment_form", (require) => { + "use strict"; + + const checkoutForm = require("payment.checkout_form"); + const manageForm = require("payment.manage_form"); + + const sepaDDMixin = { + /** + * Return all relevant inline form inputs + * + * @private + * @param {Number} providerId - The id of the selected provider + * @returns {Object} - An object mapping the name of inline form inputs + * to their DOM element + */ + _getInlineFormInputs: function (providerId) { + return { + sepa_dd_iban: document.getElementById(`o_sepa_dd_iban_${providerId}`), + sepa_dd_accept: document.getElementById( + `o_sepa_dd_accept_${providerId}` + ), + }; + }, + + /** + * Return values to pass to the default transaction validation + * route + * + * @private + * @param {String} code - The code of the payment option's provider + * @param {Number} paymentOptionId - The id of the selected provider + * @param {String} flow - The online payment flow of the transaction + * @returns {Object} - The params for the default transaction route + */ + _prepareTransactionRouteParams: function (code, paymentOptionId, flow) { + const values = this._super(code, paymentOptionId, flow); + const inputs = this._getInlineFormInputs(paymentOptionId); + values.sepa_dd_iban = inputs.sepa_dd_iban.value; + values.sepa_dd_accept = inputs.sepa_dd_accept.value; + return values; + }, + + /** + * Ensure that inputs for the form are valid (more validation + * are done in python by the backend). + * + * @override method from payment.payment_form_mixin + * @private + * @param {String} code - The code of the payment option's provider + * @param {Number} paymentOptionId - The id of the payment option handling the transaction + * @param {String} flow - The online payment flow of the transaction + * @returns {Promise} + */ + _processPayment: function (code, paymentOptionId, flow) { + if (code !== "custom" || flow === "token") { + return this._super(...arguments); + } + + if (!this._validateFormInputs(paymentOptionId)) { + this._enableButton(); + $("body").unblock(); + return Promise.resolve(); + } + + return this._super(...arguments); + }, + + /** + * Checks that all payment inputs adhere to the DOM validation constraints. + * + * @private + * @param {Number} providerId - The id of the selected provider + * @returns {Boolean} - Whether all elements pass the validation constraints + */ + _validateFormInputs: function (providerId) { + const inputs = Object.values(this._getInlineFormInputs(providerId)); + return inputs.every((element) => element.reportValidity()); + }, + }; + + checkoutForm.include(sepaDDMixin); + manageForm.include(sepaDDMixin); +}); diff --git a/website_sale_restrict_sepa_dd/views/product_views.xml b/website_sale_restrict_sepa_dd/views/product_views.xml new file mode 100644 index 000000000..55b83f4cd --- /dev/null +++ b/website_sale_restrict_sepa_dd/views/product_views.xml @@ -0,0 +1,16 @@ + + + + + product.template.common.allow_sepa_dd_payment.form + product.template + + + + + + + + + + diff --git a/website_sale_restrict_sepa_dd/views/templates.xml b/website_sale_restrict_sepa_dd/views/templates.xml new file mode 100644 index 000000000..9f22585d2 --- /dev/null +++ b/website_sale_restrict_sepa_dd/views/templates.xml @@ -0,0 +1,22 @@ + + + + + +