From 44bdf7dcda99d71cd3d476a8e2ee51e2bebce611 Mon Sep 17 00:00:00 2001 From: tshe-odoo Date: Wed, 24 Sep 2025 18:51:37 +0530 Subject: [PATCH 1/3] [ADD] sales_products_kit: add functionality to sell products as a kit in Sales - Added a new "Is Kit" button to the product page for kit selection. - If the product is a kit, sub-products can be selected in the order line. - Introduced a wizard to adjust the quantity and price of sub-products. - On confirmation, sub-products are added to the order line with price set to 0, and costs are moved to the parent product. - Sub-products are automatically deleted if the parent product is removed. - Added a "Print in Report?" boolean to control visibility of sub-products in reports and customer preview page. Task Id :- 5098017 --- sales_products_kit/__init__.py | 2 + sales_products_kit/__manifest__.py | 21 ++++ sales_products_kit/models/__init__.py | 3 + sales_products_kit/models/product_template.py | 8 ++ sales_products_kit/models/sale_order.py | 7 ++ sales_products_kit/models/sale_order_line.py | 51 ++++++++++ sales_products_kit/report/invoice_report.xml | 10 ++ .../report/sale_order_report.xml | 8 ++ .../security/ir.model.access.csv | 3 + sales_products_kit/views/product_view.xml | 15 +++ sales_products_kit/views/sale_order_view.xml | 45 +++++++++ .../views/sale_portal_templates.xml | 8 ++ sales_products_kit/wizard/__init__.py | 2 + sales_products_kit/wizard/sub_product_line.py | 15 +++ .../wizard/sub_product_wizard.py | 96 +++++++++++++++++++ .../wizard/sub_product_wizard.xml | 26 +++++ 16 files changed, 320 insertions(+) create mode 100644 sales_products_kit/__init__.py create mode 100644 sales_products_kit/__manifest__.py create mode 100644 sales_products_kit/models/__init__.py create mode 100644 sales_products_kit/models/product_template.py create mode 100644 sales_products_kit/models/sale_order.py create mode 100644 sales_products_kit/models/sale_order_line.py create mode 100644 sales_products_kit/report/invoice_report.xml create mode 100644 sales_products_kit/report/sale_order_report.xml create mode 100644 sales_products_kit/security/ir.model.access.csv create mode 100644 sales_products_kit/views/product_view.xml create mode 100644 sales_products_kit/views/sale_order_view.xml create mode 100644 sales_products_kit/views/sale_portal_templates.xml create mode 100644 sales_products_kit/wizard/__init__.py create mode 100644 sales_products_kit/wizard/sub_product_line.py create mode 100644 sales_products_kit/wizard/sub_product_wizard.py create mode 100644 sales_products_kit/wizard/sub_product_wizard.xml diff --git a/sales_products_kit/__init__.py b/sales_products_kit/__init__.py new file mode 100644 index 00000000000..9b4296142f4 --- /dev/null +++ b/sales_products_kit/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizard diff --git a/sales_products_kit/__manifest__.py b/sales_products_kit/__manifest__.py new file mode 100644 index 00000000000..62826bbda9c --- /dev/null +++ b/sales_products_kit/__manifest__.py @@ -0,0 +1,21 @@ +{ + "name": "Sales - Product_Kit", + "version": "1.0", + "description": """ + This custom module adds a function to Odoo to sell products as a Kit, but not using a BOM or the Manufacturing Module. + """, + "category": "Sales/Sales", + "depends": ["sale_management"], + "data": [ + "security/ir.model.access.csv", + "wizard/sub_product_wizard.xml", + "views/product_view.xml", + "views/sale_order_view.xml", + "report/sale_order_report.xml", + "views/sale_portal_templates.xml", + "report/invoice_report.xml", + ], + "installable": True, + "auto_install": False, + "license": "LGPL-3", +} diff --git a/sales_products_kit/models/__init__.py b/sales_products_kit/models/__init__.py new file mode 100644 index 00000000000..8f2f8c0cbc1 --- /dev/null +++ b/sales_products_kit/models/__init__.py @@ -0,0 +1,3 @@ +from . import product_template +from . import sale_order_line +from . import sale_order diff --git a/sales_products_kit/models/product_template.py b/sales_products_kit/models/product_template.py new file mode 100644 index 00000000000..3051e74b5b8 --- /dev/null +++ b/sales_products_kit/models/product_template.py @@ -0,0 +1,8 @@ +from odoo import fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + is_kit = fields.Boolean() + sub_product_ids = fields.Many2many("product.product", string="Sub Product") diff --git a/sales_products_kit/models/sale_order.py b/sales_products_kit/models/sale_order.py new file mode 100644 index 00000000000..191177cae3c --- /dev/null +++ b/sales_products_kit/models/sale_order.py @@ -0,0 +1,7 @@ +from odoo import models, fields + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + print_in_report = fields.Boolean(string="Print in Report ?") diff --git a/sales_products_kit/models/sale_order_line.py b/sales_products_kit/models/sale_order_line.py new file mode 100644 index 00000000000..fd41a542790 --- /dev/null +++ b/sales_products_kit/models/sale_order_line.py @@ -0,0 +1,51 @@ +from odoo import models, fields, api +from odoo.exceptions import UserError + + +class SaleOrderLine(models.Model): + _inherit = "sale.order.line" + + is_kit = fields.Boolean(related="product_template_id.is_kit") + parent_line_id = fields.Many2one("sale.order.line", ondelete="cascade") + + @api.ondelete(at_uninstall=False) + def _ondelete_sale_order_line(self): + if not self.parent_line_id: + for line in self: + sub_product_lines = self.env["sale.order.line"].search( + [("parent_line_id", "=", line.id)] + ) + sub_product_lines.with_context(allow_child_unlink=True).unlink() + else: + if not self.env.context.get("allow_child_unlink"): + raise UserError( + "You cannot delete a child line directly. Please delete the parent line instead." + ) + + def write(self, vals): + if "product_uom_qty" in vals and not self.parent_line_id: + for line in self: + sub_product_lines = self.env["sale.order.line"].search( + [("parent_line_id", "=", line.id)] + ) + old_qty = line.product_uom_qty + for sub_line in sub_product_lines: + if sub_line: + if old_qty != 0: + qty = sub_line.product_uom_qty / old_qty + new_qty = vals["product_uom_qty"] * qty + sub_line.update({"product_uom_qty": new_qty}) + else: + new_qty = vals["product_uom_qty"] * sub_line.product_uom_qty + sub_line.update({"product_uom_qty": new_qty}) + return super().write(vals) + + def action_subproduct(self): + return { + "type": "ir.actions.act_window", + "name": f"Product: {self.product_id.name}", + "res_model": "sub.product.wizard", + "view_mode": "form", + "target": "new", + "context": {"default_sale_order_line_id": self.id}, + } diff --git a/sales_products_kit/report/invoice_report.xml b/sales_products_kit/report/invoice_report.xml new file mode 100644 index 00000000000..60b58cc6ec5 --- /dev/null +++ b/sales_products_kit/report/invoice_report.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/sales_products_kit/report/sale_order_report.xml b/sales_products_kit/report/sale_order_report.xml new file mode 100644 index 00000000000..a3f242f5350 --- /dev/null +++ b/sales_products_kit/report/sale_order_report.xml @@ -0,0 +1,8 @@ + + + + diff --git a/sales_products_kit/security/ir.model.access.csv b/sales_products_kit/security/ir.model.access.csv new file mode 100644 index 00000000000..c57a11b5dbd --- /dev/null +++ b/sales_products_kit/security/ir.model.access.csv @@ -0,0 +1,3 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_sub_product_wizard,access_sub_product_wizard,model_sub_product_wizard,base.group_user,1,1,1,1 +access_sub_product_line,access_sub_product_line,model_sub_product_line,base.group_user,1,1,1,1 diff --git a/sales_products_kit/views/product_view.xml b/sales_products_kit/views/product_view.xml new file mode 100644 index 00000000000..9addc0aa6ad --- /dev/null +++ b/sales_products_kit/views/product_view.xml @@ -0,0 +1,15 @@ + + + + + product.template.form.view.inherit.sales_products_kit + product.template + + + + + + + + + diff --git a/sales_products_kit/views/sale_order_view.xml b/sales_products_kit/views/sale_order_view.xml new file mode 100644 index 00000000000..548dfa53d1d --- /dev/null +++ b/sales_products_kit/views/sale_order_view.xml @@ -0,0 +1,45 @@ + + + + + sale.order.form.inherit + sale.order + + + + + + +