From b330c03bac6ca67363e8d849977d1984f14a7c66 Mon Sep 17 00:00:00 2001 From: times-odoo Date: Fri, 29 May 2026 17:48:23 +0530 Subject: [PATCH] [ADD] purchase: add global discount wizard for purchase orders Introduced a transient wizard to apply an order-wide discount to purchase order lines based on an amount or percentage. Rationale: Purchasing teams need a fast, error-free way to distribute global vendor discounts across all order lines. Manually calculating line-level deductions is slow and prone to discrepancy. This wizard allows users to enter a fixed cash value or a total percentage, automatically deriving the relative values and updating all linked line items in bulk. Technical choices: - Created the 'purchase.order.discount' TransientModel to capture input without database bloat. - Implemented an '@api.onchange' trigger utilizing a clean helper method to compute proportional percentage values dynamically when an amount is specified. - Wrapped execution contexts using 'with_company(self.company_id)' to ensure correct multi-company record updating behavior. - Injected an execution button cleanly under the 'order_line' field in the inherited purchase order view using precise XPath positioning. Task Reference: 6253108 --- purchase_global_discount/__init__.py | 2 + purchase_global_discount/__manifest__.py | 15 ++++++ purchase_global_discount/models/__init__.py | 1 + .../models/purchase_order.py | 16 +++++++ .../security/ir.model.access.csv | 2 + .../views/purchase_views.xml | 21 +++++++++ purchase_global_discount/wizard/__init__.py | 1 + .../wizard/purchase_order_discount.py | 33 +++++++++++++ .../wizard/purchase_order_discount_views.xml | 47 +++++++++++++++++++ 9 files changed, 138 insertions(+) create mode 100644 purchase_global_discount/__init__.py create mode 100644 purchase_global_discount/__manifest__.py create mode 100644 purchase_global_discount/models/__init__.py create mode 100644 purchase_global_discount/models/purchase_order.py create mode 100644 purchase_global_discount/security/ir.model.access.csv create mode 100644 purchase_global_discount/views/purchase_views.xml create mode 100644 purchase_global_discount/wizard/__init__.py create mode 100644 purchase_global_discount/wizard/purchase_order_discount.py create mode 100644 purchase_global_discount/wizard/purchase_order_discount_views.xml diff --git a/purchase_global_discount/__init__.py b/purchase_global_discount/__init__.py new file mode 100644 index 00000000000..9b4296142f4 --- /dev/null +++ b/purchase_global_discount/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard diff --git a/purchase_global_discount/__manifest__.py b/purchase_global_discount/__manifest__.py new file mode 100644 index 00000000000..c7a07b67704 --- /dev/null +++ b/purchase_global_discount/__manifest__.py @@ -0,0 +1,15 @@ +{ + 'name': 'Purchase Order Global Discount', + 'version': "1.0", + 'category': "Supply Chain/Purchase", + 'description': "Creates a Discount Option in Purchase Order", + 'depends': ['purchase'], + 'data': [ + 'security/ir.model.access.csv', + 'wizard/purchase_order_discount_views.xml', + 'views/purchase_views.xml', + ], + 'auto_install': True, + 'author': "times", + 'license': "LGPL-3", +} diff --git a/purchase_global_discount/models/__init__.py b/purchase_global_discount/models/__init__.py new file mode 100644 index 00000000000..9f03530643d --- /dev/null +++ b/purchase_global_discount/models/__init__.py @@ -0,0 +1 @@ +from . import purchase_order diff --git a/purchase_global_discount/models/purchase_order.py b/purchase_global_discount/models/purchase_order.py new file mode 100644 index 00000000000..277b8aded5b --- /dev/null +++ b/purchase_global_discount/models/purchase_order.py @@ -0,0 +1,16 @@ +from odoo import _, api, models + + +class PurchaseOrder(models.Model): + _inherit = 'purchase.order' + + @api.readonly + def action_open_discount_wizard(self): + self.ensure_one() + return { + 'name': _("Discount"), + 'type': 'ir.actions.act_window', + 'res_model': 'purchase.order.discount', + 'view_mode': 'form', + 'target': 'new', + } diff --git a/purchase_global_discount/security/ir.model.access.csv b/purchase_global_discount/security/ir.model.access.csv new file mode 100644 index 00000000000..68963adfe79 --- /dev/null +++ b/purchase_global_discount/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_purchase_order_discount,access_purchase_order_discount,model_purchase_order_discount,purchase.group_purchase_manager,1,1,1,0 diff --git a/purchase_global_discount/views/purchase_views.xml b/purchase_global_discount/views/purchase_views.xml new file mode 100644 index 00000000000..16017478104 --- /dev/null +++ b/purchase_global_discount/views/purchase_views.xml @@ -0,0 +1,21 @@ + + + + purchase.order.form.discount + purchase.order + + + +
+
+
+
+
+
\ No newline at end of file diff --git a/purchase_global_discount/wizard/__init__.py b/purchase_global_discount/wizard/__init__.py new file mode 100644 index 00000000000..510f03e2878 --- /dev/null +++ b/purchase_global_discount/wizard/__init__.py @@ -0,0 +1 @@ +from . import purchase_order_discount diff --git a/purchase_global_discount/wizard/purchase_order_discount.py b/purchase_global_discount/wizard/purchase_order_discount.py new file mode 100644 index 00000000000..22b51e36268 --- /dev/null +++ b/purchase_global_discount/wizard/purchase_order_discount.py @@ -0,0 +1,33 @@ +from odoo import api, fields, models + + +class PurchaseOrderDiscount(models.TransientModel): + _name = "purchase.order.discount" + _description = "Discount Wizard" + + purchase_order_id = fields.Many2one( + 'purchase.order', default=lambda self: self.env.context.get('active_id'), required=True) + company_id = fields.Many2one(related='purchase_order_id.company_id') + currency_id = fields.Many2one(related='purchase_order_id.currency_id') + discount_amount = fields.Monetary(string="Amount") + discount_percentage = fields.Float(string="Percentage") + discount_type = fields.Selection([ + ('amount', "Amount (Total)"), + ('percentage', "Percentage"), + ], default='percentage') + + def _get_original_total(self): + return sum( + line.price_unit * line.product_qty + for line in self.purchase_order_id.order_line + ) + + @api.onchange('discount_amount') + def _onchange_discount_amount(self): + original_total = self._get_original_total() + if original_total: + self.discount_percentage = (self.discount_amount / original_total) + + def action_apply_discount(self): + self.ensure_one() + self.purchase_order_id.order_line.write({'discount': self.discount_percentage * 100}) diff --git a/purchase_global_discount/wizard/purchase_order_discount_views.xml b/purchase_global_discount/wizard/purchase_order_discount_views.xml new file mode 100644 index 00000000000..9b554bd3cc7 --- /dev/null +++ b/purchase_global_discount/wizard/purchase_order_discount_views.xml @@ -0,0 +1,47 @@ + + + + + purchase.order.discount.form + purchase.order.discount + +
+ + + + +
+
+ + +
+
+ +
+
+ +
+
+
+ +
+
+
+ +