From c30130a3d6d3b76d179b51229a779a564047e978 Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Wed, 12 Feb 2025 12:10:09 -0500 Subject: [PATCH] new module sale_mandatory_customer_reference and small fix to delivery_carrier_partner_account --- .../models/res_partner.py | 11 +- sale_mandatory_customer_reference/__init__.py | 2 + .../__manifest__.py | 38 ++++++ .../controllers/__init__.py | 1 + .../controllers/portal.py | 61 ++++++++++ .../models/__init__.py | 2 + .../models/res_config_settings.py | 12 ++ .../models/sale_order.py | 26 ++++ .../static/src/js/portal_sale.js | 99 +++++++++++++++ .../static/src/xml/portal_sale.xml | 20 ++++ .../tests/__init__.py | 1 + .../tests/test_sale_order.py | 113 ++++++++++++++++++ .../views/portal_templates.xml | 29 +++++ .../views/res_config_settings_views.xml | 15 +++ 14 files changed, 427 insertions(+), 3 deletions(-) create mode 100644 sale_mandatory_customer_reference/__init__.py create mode 100644 sale_mandatory_customer_reference/__manifest__.py create mode 100644 sale_mandatory_customer_reference/controllers/__init__.py create mode 100644 sale_mandatory_customer_reference/controllers/portal.py create mode 100644 sale_mandatory_customer_reference/models/__init__.py create mode 100644 sale_mandatory_customer_reference/models/res_config_settings.py create mode 100644 sale_mandatory_customer_reference/models/sale_order.py create mode 100644 sale_mandatory_customer_reference/static/src/js/portal_sale.js create mode 100644 sale_mandatory_customer_reference/static/src/xml/portal_sale.xml create mode 100644 sale_mandatory_customer_reference/tests/__init__.py create mode 100644 sale_mandatory_customer_reference/tests/test_sale_order.py create mode 100644 sale_mandatory_customer_reference/views/portal_templates.xml create mode 100644 sale_mandatory_customer_reference/views/res_config_settings_views.xml diff --git a/delivery_carrier_partner_account/models/res_partner.py b/delivery_carrier_partner_account/models/res_partner.py index 8168634..92459ff 100644 --- a/delivery_carrier_partner_account/models/res_partner.py +++ b/delivery_carrier_partner_account/models/res_partner.py @@ -24,6 +24,9 @@ class Partner(models.Model): res = super().write(vals) if update_default_carrier and self.carrier_account_ids: self.default_carrier_account_id = self.carrier_account_ids[0] + self.property_delivery_carrier_id = ( + self.default_carrier_account_id.delivery_carrier_id + ) return res @api.model_create_multi @@ -41,9 +44,11 @@ class Partner(models.Model): ) if own_accounts: return own_accounts[0] - commercial_patner_accounts = self.commercial_partner_id.carrier_account_ids.filtered( - lambda account: account.delivery_carrier_id == carrier + commercial_patner_accounts = ( + self.commercial_partner_id.carrier_account_ids.filtered( + lambda account: account.delivery_carrier_id == carrier + ) ) if commercial_patner_accounts: return commercial_patner_accounts[0] - return self.env["delivery.carrier.account"] \ No newline at end of file + return self.env["delivery.carrier.account"] diff --git a/sale_mandatory_customer_reference/__init__.py b/sale_mandatory_customer_reference/__init__.py new file mode 100644 index 0000000..f7209b1 --- /dev/null +++ b/sale_mandatory_customer_reference/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import controllers diff --git a/sale_mandatory_customer_reference/__manifest__.py b/sale_mandatory_customer_reference/__manifest__.py new file mode 100644 index 0000000..c2dde90 --- /dev/null +++ b/sale_mandatory_customer_reference/__manifest__.py @@ -0,0 +1,38 @@ +{ + "name": "Mandatory Customer Reference on Sales Orders", + "version": "18.0.1.0.0", + "category": "Sales", + "summary": "Enforce customer reference on sales orders with portal integration", + "description": """ +This module enforces the requirement of a customer reference (purchase order number) on sales orders before confirmation. +It provides the following features: + +* Prevents confirmation of sales orders without a customer reference +* Allows customers to set their reference number through the portal +* Automatically sets reference to "Credit Card" for online payments +* Integrates with both backend and portal interfaces +* Compatible with e-commerce flows +""", + "author": "Bemade Inc", + "website": "https://bemade.org", + "depends": [ + "sale", # For portal templates and base functionality + "portal", # For portal features + "payment", # For payment integration + "website", # For frontend assets and portal templates + "website_sale", # For sale portal features + ], + "data": [ + "views/res_config_settings_views.xml", + "views/portal_templates.xml", + ], + "assets": { + "web.assets_frontend": [ + "sale_mandatory_customer_reference/static/src/js/portal_sale.js", + ], + }, + "license": "LGPL-3", + "installable": True, + "application": False, + "auto_install": False, +} diff --git a/sale_mandatory_customer_reference/controllers/__init__.py b/sale_mandatory_customer_reference/controllers/__init__.py new file mode 100644 index 0000000..8c3feb6 --- /dev/null +++ b/sale_mandatory_customer_reference/controllers/__init__.py @@ -0,0 +1 @@ +from . import portal diff --git a/sale_mandatory_customer_reference/controllers/portal.py b/sale_mandatory_customer_reference/controllers/portal.py new file mode 100644 index 0000000..7e25cf9 --- /dev/null +++ b/sale_mandatory_customer_reference/controllers/portal.py @@ -0,0 +1,61 @@ +from odoo import http +from odoo.http import request +from odoo.addons.sale.controllers.portal import CustomerPortal +from odoo.addons.portal.controllers.portal import pager as portal_pager + + +class CustomerPortalInherit(CustomerPortal): + + def _prepare_quotations_domain(self, partner): + domain = super()._prepare_quotations_domain(partner) + return domain + + def _prepare_sale_portal_rendering_values(self, order, **kwargs): + values = super()._prepare_sale_portal_rendering_values(order, **kwargs) + values["enforce_customer_reference"] = ( + request.env["ir.config_parameter"] + .sudo() + .get_param( + "sale_mandatory_customer_reference.enforce_customer_reference", False + ) + ) + return values + + @http.route( + ["/my/orders//update_reference"], + type="json", + auth="public", + website=True, + ) + def portal_update_sale_reference( + self, order_id, reference, access_token=None, **kw + ): + try: + order = request.env["sale.order"].browse(order_id) + if not order.exists(): + return {"error": "Order not found"} + + # Try user access first + try: + # These will raise if access denied + order.check_access_rights("write") + order.check_access_rule("write") + except Exception: + # If user access fails, try token access + if access_token: + order = request.env["sale.order"].sudo().browse(order_id) + try: + order.check_access_token(access_token) + except Exception: + return {"error": "Access Denied"} + else: + return {"error": "Access Denied"} + + if order.state not in ("draft", "sent"): + return {"error": "Order cannot be modified in its current state"} + + order.write({"client_order_ref": reference}) + return {"success": True} + + except Exception as e: + return {"error": str(e)} diff --git a/sale_mandatory_customer_reference/models/__init__.py b/sale_mandatory_customer_reference/models/__init__.py new file mode 100644 index 0000000..fc3553c --- /dev/null +++ b/sale_mandatory_customer_reference/models/__init__.py @@ -0,0 +1,2 @@ +from . import res_config_settings +from . import sale_order diff --git a/sale_mandatory_customer_reference/models/res_config_settings.py b/sale_mandatory_customer_reference/models/res_config_settings.py new file mode 100644 index 0000000..f17144b --- /dev/null +++ b/sale_mandatory_customer_reference/models/res_config_settings.py @@ -0,0 +1,12 @@ +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + enforce_customer_reference = fields.Boolean( + string="Require Customer Reference on Sales Orders", + config_parameter='sale_mandatory_customer_reference.enforce_customer_reference', + help="When enabled, sales orders cannot be confirmed without a customer reference " + "(except for online payments where it will be set to 'Credit Card')." + ) diff --git a/sale_mandatory_customer_reference/models/sale_order.py b/sale_mandatory_customer_reference/models/sale_order.py new file mode 100644 index 0000000..38864bc --- /dev/null +++ b/sale_mandatory_customer_reference/models/sale_order.py @@ -0,0 +1,26 @@ +from odoo import models, _ +from odoo.exceptions import ValidationError + + +class SaleOrder(models.Model): + _inherit = 'sale.order' + + def _get_enforce_customer_reference(self): + """Get the configuration parameter for customer reference enforcement.""" + return self.env['ir.config_parameter'].sudo().get_param( + 'sale_mandatory_customer_reference.enforce_customer_reference', False) + + def action_confirm(self): + """Override to check for customer reference before confirmation.""" + for order in self: + if order._get_enforce_customer_reference(): + # For online payments with successful transactions, set reference to "Credit Card" if not set + successful_tx = order.transaction_ids.filtered(lambda tx: tx.state == 'done') + if successful_tx and not order.client_order_ref: + order.client_order_ref = "Credit Card" + elif not order.client_order_ref: + raise ValidationError(_( + "Customer reference (PO Number) is required before confirming this order. " + "Please set the customer reference field." + )) + return super().action_confirm() diff --git a/sale_mandatory_customer_reference/static/src/js/portal_sale.js b/sale_mandatory_customer_reference/static/src/js/portal_sale.js new file mode 100644 index 0000000..7e871db --- /dev/null +++ b/sale_mandatory_customer_reference/static/src/js/portal_sale.js @@ -0,0 +1,99 @@ +/** @odoo-module **/ + +import publicWidget from "@web/legacy/js/public/public_widget"; +import { _t } from "@web/core/l10n/translation"; +import { rpc } from "@web/core/network/rpc"; + +publicWidget.registry.SalePortalReference = publicWidget.Widget.extend({ + selector: '.o_portal_sale_reference', + events: { + 'change input[name="client_order_ref"]': '_onReferenceChange', + }, + + /** + * @override + */ + start: function () { + this.orderId = this.el.dataset.orderId; + this.accessToken = this.el.dataset.accessToken; + return this._super.apply(this, arguments); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Show a notification message + * @private + */ + _showNotification(message, type = 'success') { + const alertClass = type === 'success' ? 'alert-success' : 'alert-danger'; + const $notification = $('
', { + class: `alert ${alertClass} alert-dismissible fade show`, + role: 'alert', + text: message + }).append($('