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()