diff --git a/product_kit/__init__.py b/product_kit/__init__.py
new file mode 100644
index 00000000000..9b4296142f4
--- /dev/null
+++ b/product_kit/__init__.py
@@ -0,0 +1,2 @@
+from . import models
+from . import wizard
diff --git a/product_kit/__manifest__.py b/product_kit/__manifest__.py
new file mode 100644
index 00000000000..1717618781e
--- /dev/null
+++ b/product_kit/__manifest__.py
@@ -0,0 +1,15 @@
+{
+ 'name': 'Product Kit',
+ 'Category': 'Sales/Product Kit',
+ 'license': 'LGPL-3',
+ 'installable': True,
+ 'depends': ['sale_management', 'stock'],
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'wizard/wizard_product_kit_views.xml',
+ 'views/product_template_views.xml',
+ 'views/sale_order_views.xml',
+ 'views/sale_portal_template.xml',
+ 'views/report_invoice.xml',
+ ]
+}
diff --git a/product_kit/models/__init__.py b/product_kit/models/__init__.py
new file mode 100644
index 00000000000..8f2f8c0cbc1
--- /dev/null
+++ b/product_kit/models/__init__.py
@@ -0,0 +1,3 @@
+from . import product_template
+from . import sale_order_line
+from . import sale_order
diff --git a/product_kit/models/product_template.py b/product_kit/models/product_template.py
new file mode 100644
index 00000000000..128e4038a60
--- /dev/null
+++ b/product_kit/models/product_template.py
@@ -0,0 +1,8 @@
+from odoo import fields, models
+
+
+class ProductTemplate(models.Model):
+ _inherit = 'product.template'
+
+ kit = fields.Boolean(string="has kit ?", default=False, help="To enable new product type")
+ subproduct = fields.Many2many(comodel_name="product.product", string="Sub Product", help="Select subproduct if kit is enabled")
diff --git a/product_kit/models/sale_order.py b/product_kit/models/sale_order.py
new file mode 100644
index 00000000000..17045c59066
--- /dev/null
+++ b/product_kit/models/sale_order.py
@@ -0,0 +1,7 @@
+from odoo import fields, models
+
+
+class SaleOrder(models.Model):
+ _inherit = 'sale.order'
+
+ print_in_report = fields.Boolean(string="Print in report ?", default=True)
diff --git a/product_kit/models/sale_order_line.py b/product_kit/models/sale_order_line.py
new file mode 100644
index 00000000000..e33e12da083
--- /dev/null
+++ b/product_kit/models/sale_order_line.py
@@ -0,0 +1,55 @@
+from odoo import Command, fields, models
+
+
+class SaleOrderLine(models.Model):
+ _inherit = 'sale.order.line'
+
+ has_kit = fields.Boolean(related="product_template_id.kit", store=True, help="Check product has kit enable or not")
+ is_kit = fields.Boolean(string="Is kit", default=False, help="Distinguish main product and subproduct. If true it is sub product")
+
+ def open_kit_wizard(self):
+ for record in self:
+ sale_order_line_id = record.id
+ sale_order_id = record.order_id.id
+
+ existing_lines = []
+
+ # Fetching existing order lines from sale order
+ existing_lines = self.env['sale.order.line'].search([
+ ('linked_line_id', '=', sale_order_line_id),
+ ('order_id', '=', sale_order_id)
+ ])
+
+ # Creating dictionary with product id as key and quantity as value from existing order line
+ existing_dict = {
+ line.product_id.id: line.product_uom_qty for line in existing_lines
+ }
+ product_commands = []
+
+ # Looping through all the subproduct of kit enabled product
+ for product in record.product_template_id.subproduct:
+ qty = existing_dict.get(product.id, 1) # Fetching quantity of product if not found return 1
+ # Creating command to create new linked record
+ product_commands.append(
+ Command.create({
+ "product_id": product.id,
+ "quantity": qty
+ })
+ )
+
+ # Populating wizard models
+ wizard = self.env['wizard.product.kit'].create({
+ "product_ids": product_commands
+ })
+
+ return {
+ 'type': 'ir.actions.act_window',
+ 'name': 'Product Kit',
+ 'res_model': 'wizard.product.kit',
+ 'view_mode': 'form',
+ 'target': 'new',
+ "res_id": wizard.id,
+ "context": {
+ "default_sale_order_id": record.order_id.id,
+ }
+ }
diff --git a/product_kit/security/ir.model.access.csv b/product_kit/security/ir.model.access.csv
new file mode 100644
index 00000000000..9fc358ea369
--- /dev/null
+++ b/product_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_wizard_product_kit,access_wizard_product_kit,model_wizard_product_kit,base.group_user,1,1,1,0
+access_wizard_product_kit_add,access_wizard_product_kit_add,model_wizard_product_kit_add,base.group_user,1,1,1,0
diff --git a/product_kit/views/product_template_views.xml b/product_kit/views/product_template_views.xml
new file mode 100644
index 00000000000..f4118f4b440
--- /dev/null
+++ b/product_kit/views/product_template_views.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ product.template.form.inherit.product.kit
+ product.template
+
+
+
+
+
+
+
+
+
+
diff --git a/product_kit/views/report_invoice.xml b/product_kit/views/report_invoice.xml
new file mode 100644
index 00000000000..f4ba17ca9b9
--- /dev/null
+++ b/product_kit/views/report_invoice.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ (line.sale_line_ids.order_id.print_in_report) or
+ (not line.sale_line_ids.filtered('is_kit'))
+
+
+
+
+
diff --git a/product_kit/views/sale_order_views.xml b/product_kit/views/sale_order_views.xml
new file mode 100644
index 00000000000..33fd96a2d9d
--- /dev/null
+++ b/product_kit/views/sale_order_views.xml
@@ -0,0 +1,41 @@
+
+
+
+ view.sale.form.order.line.inherit
+ sale.order
+
+
+
+
+
+
+
+
+
+
+
+ is_kit
+
+
+
+ is_kit
+
+
+ is_kit
+
+
+ is_kit
+
+
+ is_kit
+
+
+ is_kit
+
+
+ is_kit
+
+
+
+
+
diff --git a/product_kit/views/sale_portal_template.xml b/product_kit/views/sale_portal_template.xml
new file mode 100644
index 00000000000..5a032effcb1
--- /dev/null
+++ b/product_kit/views/sale_portal_template.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+ (sale_order.print_in_report and line.is_kit) or (not line.is_kit)
+
+
+
+
diff --git a/product_kit/wizard/__init__.py b/product_kit/wizard/__init__.py
new file mode 100644
index 00000000000..c7f145bc1b4
--- /dev/null
+++ b/product_kit/wizard/__init__.py
@@ -0,0 +1 @@
+from . import wizard_product_kit
diff --git a/product_kit/wizard/wizard_product_kit.py b/product_kit/wizard/wizard_product_kit.py
new file mode 100644
index 00000000000..1ea31a44c9a
--- /dev/null
+++ b/product_kit/wizard/wizard_product_kit.py
@@ -0,0 +1,78 @@
+from odoo import api, fields, models
+from odoo.exceptions import UserError
+
+
+class WizardProductKit(models.TransientModel):
+ _name = 'wizard.product.kit'
+ _description = "Wizard to add product kit"
+
+ # sale_order_id = fields.Many2one(comodel_name="sale.order")
+ product_ids = fields.One2many(
+ comodel_name="wizard.product.kit.add",
+ inverse_name="kit_id",
+ string="Product"
+ )
+
+ def add_product_kits(self):
+ sale_order_id = self.env.context.get("default_sale_order_id")
+ main_order_line_id = self.env.context.get("active_id")
+
+ existing_line = self.env['sale.order.line'].search([
+ ('linked_line_id', '=', main_order_line_id),
+ ('order_id', '=', sale_order_id)
+ ])
+ existing_product_ids = existing_line.mapped('product_id.id')
+
+ kits_price = 0
+ for product in self.product_ids:
+ # if product already in sale order line only update quantity and price
+ if product.product_id.id in existing_product_ids:
+ for line in existing_line:
+ if line.product_id.id == product.product_id.id:
+ line.write({
+ "product_uom_qty": product.quantity,
+ "price_unit": 0
+ })
+ # If not found create new line
+ else:
+ new_sale_order_line = self.env['sale.order.line'].create({
+ "product_id": product.product_id.id,
+ "order_id": sale_order_id,
+ "product_uom_qty": product.quantity,
+ "linked_line_id": main_order_line_id,
+ "price_unit": 0,
+ "is_kit": True,
+ })
+ if not new_sale_order_line:
+ raise UserError("something went wrong!!!")
+
+ kits_price += product.price
+
+ # Updating main order line price
+ order_line = self.env['sale.order.line'].browse(main_order_line_id)
+ order_line.write({
+ "price_subtotal": (order_line.product_uom_qty * order_line.product_id.list_price) + kits_price
+ })
+
+ return {"type": "ir.actions.act_window_close"}
+
+
+class WizardProductKitAdd(models.TransientModel):
+ _name = 'wizard.product.kit.add'
+ _description = 'Wizard to add product kit list'
+
+ product_id = fields.Many2one(
+ comodel_name="product.product",
+ string="Name",
+ )
+ quantity = fields.Float(string='Quantity', default=1.0)
+ price = fields.Float(string='Price', compute="_compute_price")
+ kit_id = fields.Many2one(
+ comodel_name="wizard.product.kit",
+ string="Kit Name"
+ )
+
+ @api.depends("quantity")
+ def _compute_price(self):
+ for record in self:
+ record.price = record.quantity * record.product_id.list_price
diff --git a/product_kit/wizard/wizard_product_kit_views.xml b/product_kit/wizard/wizard_product_kit_views.xml
new file mode 100644
index 00000000000..e58624d084e
--- /dev/null
+++ b/product_kit/wizard/wizard_product_kit_views.xml
@@ -0,0 +1,26 @@
+
+
+
+
+ Product Kit Wizard
+ wizard.product.kit
+
+
+
+
+
+