From c14664ab71bd1eb647234141db2a72a281791f84 Mon Sep 17 00:00:00 2001 From: patsh-odoo Date: Wed, 27 May 2026 10:19:54 +0530 Subject: [PATCH] [ADD] sale_update: update global discount when order lines change Added a module that fixes issue of updating global discount when order lines changes, it enables updating discount lines whenever order lines are removed or order lines are updated and removes discount lines when no order line is left. Earlier once global discount is applied it was not changed whenever user changes order lines or even removes them. project - [PSIM] INTERNSHIP ONBOARDING task - [sale_management] : Discount value should get update when updating orderlines --- sale_update/__init__.py | 2 + sale_update/__manifest__.py | 7 + sale_update/models/__init__.py | 3 + sale_update/models/sale_order.py | 7 + .../models/sale_order_discount_record.py | 9 + sale_update/models/sale_order_line.py | 177 ++++++++++++++++++ sale_update/security/ir.model.access.csv | 2 + sale_update/wizard/__init__.py | 1 + sale_update/wizard/sale_order_discount.py | 23 +++ 9 files changed, 231 insertions(+) create mode 100644 sale_update/__init__.py create mode 100644 sale_update/__manifest__.py create mode 100644 sale_update/models/__init__.py create mode 100644 sale_update/models/sale_order.py create mode 100644 sale_update/models/sale_order_discount_record.py create mode 100644 sale_update/models/sale_order_line.py create mode 100644 sale_update/security/ir.model.access.csv create mode 100644 sale_update/wizard/__init__.py create mode 100644 sale_update/wizard/sale_order_discount.py diff --git a/sale_update/__init__.py b/sale_update/__init__.py new file mode 100644 index 00000000000..9b4296142f4 --- /dev/null +++ b/sale_update/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard diff --git a/sale_update/__manifest__.py b/sale_update/__manifest__.py new file mode 100644 index 00000000000..7bced659a8b --- /dev/null +++ b/sale_update/__manifest__.py @@ -0,0 +1,7 @@ +{ + "name": "sale_update", + "depends": ["sale_management"], + "author": "shrey patel", + "license": "LGPL-3", + "data": ["security/ir.model.access.csv"], +} diff --git a/sale_update/models/__init__.py b/sale_update/models/__init__.py new file mode 100644 index 00000000000..e783b5a7284 --- /dev/null +++ b/sale_update/models/__init__.py @@ -0,0 +1,3 @@ +from . import sale_order_line +from . import sale_order_discount_record +from . import sale_order diff --git a/sale_update/models/sale_order.py b/sale_update/models/sale_order.py new file mode 100644 index 00000000000..28b8c7bc85b --- /dev/null +++ b/sale_update/models/sale_order.py @@ -0,0 +1,7 @@ +from odoo import fields, models + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + discount_record_id = fields.Many2one("sale.order.discount.record") diff --git a/sale_update/models/sale_order_discount_record.py b/sale_update/models/sale_order_discount_record.py new file mode 100644 index 00000000000..62be1d246f8 --- /dev/null +++ b/sale_update/models/sale_order_discount_record.py @@ -0,0 +1,9 @@ +from odoo import models, fields + + +class SaleOrderDiscountRecord(models.Model): + _name = "sale.order.discount.record" + + order_id = fields.Many2one("sale.order") + discount_percentage = fields.Float() + discount_id = fields.Integer() diff --git a/sale_update/models/sale_order_line.py b/sale_update/models/sale_order_line.py new file mode 100644 index 00000000000..6f44dd94910 --- /dev/null +++ b/sale_update/models/sale_order_line.py @@ -0,0 +1,177 @@ +from odoo import models, Command, api +from collections import defaultdict +from odoo.tools.float_utils import float_repr + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + def write(self, values): + super().write(values) + if ( + self.order_id.discount_record_id + and self.product_id != self.company_id.sale_discount_product_id + ): + trigger_fields = {"price_unit", "product_uom_qty", "tax_ids", "discount"} + if not any(key in values for key in trigger_fields): + return + discount_product = self.company_id.sale_discount_product_id + amount = self.order_id.discount_record_id.discount_percentage * 100.0 + order = self.order_id + AccountTax = self.env["account.tax"] + discount_lines = self.order_id.order_line.filtered_domain( + [("product_id", "=", discount_product)] + ) + order_lines = ( + order.order_line.filtered(lambda x: not x.display_type) - discount_lines + ) + base_lines = [ + line._prepare_base_line_for_taxes_computation() for line in order_lines + ] + AccountTax._add_tax_details_in_base_lines(base_lines, order.company_id) + AccountTax._round_base_lines_tax_details(base_lines, order.company_id) + + def grouping_function(base_line): + return {"product_id": discount_product} + + global_discount_base_lines = AccountTax._prepare_global_discount_lines( + base_lines=base_lines, + company=self.company_id, + amount_type="percent", + amount=amount, + computation_key=f"global_discount,{self.order_id.discount_record_id.discount_id}", + grouping_function=grouping_function, + ) + update_commands = [Command.unlink(line.id) for line in discount_lines] + update_commands += [ + Command.create(values) + for values in self._prepare_global_discount_so_lines( + global_discount_base_lines + ) + ] + self.order_id.with_context(is_global_discount_recompute=True).write( + { + "discount_record_id": self.order_id.discount_record_id, + "order_line": update_commands, + } + ) + + @api.ondelete(at_uninstall=False) + def _unlink_sale_order_line(self): + if self.env.context.get("is_global_discount_recompute"): + return + all_global_discount_lines = self.order_id.order_line.filtered_domain( + [("product_id", "=", self.company_id.sale_discount_product_id)] + ) + global_discount_lines_to_delete = self.filtered_domain( + [("product_id", "=", self.company_id.sale_discount_product_id)] + ) + if all_global_discount_lines == global_discount_lines_to_delete: + self.order_id.write({"discount_record_id": False}) + return + if global_discount_lines_to_delete == self: + return + self -= global_discount_lines_to_delete + lines_grouped_by_tax_ids = self._group_lines_by_tax() + for group in lines_grouped_by_tax_ids: + self._recompute_specific_tax_discount( + group, global_discount_lines_to_delete + ) + + def _recompute_specific_tax_discount(self, lines, global_discount_lines_to_delete): + discount_product = self.company_id.sale_discount_product_id + target_discount_line = self.order_id.order_line.filtered_domain( + [("product_id", "=", discount_product), ("tax_ids", "=", lines.tax_ids)] + ) + if target_discount_line in global_discount_lines_to_delete: + return + source_lines = self.order_id.order_line.filtered_domain( + [ + ("product_id", "!=", discount_product), + ("tax_ids", "=", target_discount_line.tax_ids), + ] + ) + if lines == source_lines: + target_discount_line.unlink() + return + discount_percentage = self.order_id.discount_record_id.discount_percentage * 100 + source_lines -= lines + base_lines = [ + line._prepare_base_line_for_taxes_computation() for line in source_lines + ] + AccountTax = self.env["account.tax"] + AccountTax._add_tax_details_in_base_lines(base_lines, self.company_id) + AccountTax._round_base_lines_tax_details(base_lines, self.company_id) + new_data = self.env["account.tax"]._prepare_global_discount_lines( + base_lines=base_lines, + company=self.company_id, + amount_type="percent", + amount=discount_percentage, + computation_key=f"global_discount,{self.order_id.discount_record_id.discount_id}", + grouping_function=lambda l: {"product_id": discount_product}, + ) + target_discount_line.write( + self._prepare_global_discount_so_lines(new_data, target_discount_line.name)[ + 0 + ] + ) + + def _prepare_global_discount_so_lines(self, base_lines, name=None): + AccountTax = self.env["account.tax"] + so_line_values_list = [] + for base_line in base_lines: + if not name: + AccountTax = self.env["account.tax"] + discount_dp = self.env["decimal.precision"].precision_get("Discount") + has_multiple_tax_combinations = ( + len( + { + base_line["tax_ids"] + for base_line in base_lines + if base_line["tax_ids"] + } + ) + > 1 + ) + if has_multiple_tax_combinations: + name = self.env._( + "Discount %(percent)s%%" + "- On products with the following taxes %(taxes)s", + percent=float_repr( + self.order_id.discount_record_id.discount_percentage + * 100.0, + discount_dp, + ), + taxes=", ".join(base_line["tax_ids"].mapped("name")), + ) + else: + name = self.env._( + "Discount %(percent)s%%", + percent=float_repr( + self.order_id.discount_record_id.discount_percentage + * 100.0, + discount_dp, + ), + ) + so_line_values_list.append( + { + "name": name, + "product_id": base_line["product_id"].id, + "price_unit": base_line["price_unit"], + "technical_price_unit": 0, + "product_uom_qty": base_line["quantity"], + "tax_ids": [Command.set(base_line["tax_ids"].ids)], + "extra_tax_data": AccountTax._export_base_line_extra_tax_data( + base_line + ), + "sequence": 999, + } + ) + return so_line_values_list + + def _group_lines_by_tax(self): + grouped_map = defaultdict(lambda: self.env["sale.order.line"]) + for line in self: + tax_key = tuple(sorted(line.tax_ids.ids)) + grouped_map[tax_key] |= line + return list(grouped_map.values()) diff --git a/sale_update/security/ir.model.access.csv b/sale_update/security/ir.model.access.csv new file mode 100644 index 00000000000..acb49d0c3f4 --- /dev/null +++ b/sale_update/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 +sale_update.access_sale_order_discount_record,access_sale_order_discount_record,sale_update.model_sale_order_discount_record,base.group_user,1,1,1,1 diff --git a/sale_update/wizard/__init__.py b/sale_update/wizard/__init__.py new file mode 100644 index 00000000000..d156570a8be --- /dev/null +++ b/sale_update/wizard/__init__.py @@ -0,0 +1 @@ +from . import sale_order_discount diff --git a/sale_update/wizard/sale_order_discount.py b/sale_update/wizard/sale_order_discount.py new file mode 100644 index 00000000000..13771b2b624 --- /dev/null +++ b/sale_update/wizard/sale_order_discount.py @@ -0,0 +1,23 @@ +from odoo import models + + +class SaleOrderDiscount(models.TransientModel): + _inherit = "sale.order.discount" + + def action_apply_discount(self): + self.ensure_one() + record = self.with_company(self.company_id) + if record.discount_type == "sol_discount": + record.sale_order_id.order_line.write( + {"discount": record.discount_percentage * 100} + ) + else: + record.sale_order_id.discount_record_id = record.env[ + "sale.order.discount.record" + ].create( + { + "discount_id": record.id, + "discount_percentage": record.discount_percentage, + } + ) + record._create_discount_lines()