diff --git a/new_product_type/__init__.py b/new_product_type/__init__.py
new file mode 100644
index 00000000000..9b4296142f4
--- /dev/null
+++ b/new_product_type/__init__.py
@@ -0,0 +1,2 @@
+from . import models
+from . import wizard
diff --git a/new_product_type/__manifest__.py b/new_product_type/__manifest__.py
new file mode 100644
index 00000000000..f52f5756308
--- /dev/null
+++ b/new_product_type/__manifest__.py
@@ -0,0 +1,17 @@
+{
+ "name": "New Product Type",
+ "version": "1.0",
+ 'author': "habar",
+ "depends": ["sale", "product", 'account'],
+ "data": [
+ 'security/ir.model.access.csv',
+ "report/sale_order_portal_report.xml",
+ "report/sale_order_report.xml",
+ "views/product_kit_wizard_views.xml",
+ "views/product_views.xml",
+ "report/invoice_report.xml",
+ "views/sale_order_line_views.xml",
+ ],
+ "installable": True,
+ 'license': 'LGPL-3',
+}
diff --git a/new_product_type/models/__init__.py b/new_product_type/models/__init__.py
new file mode 100644
index 00000000000..fbe8e5e1443
--- /dev/null
+++ b/new_product_type/models/__init__.py
@@ -0,0 +1,4 @@
+from . import product_template
+from . import sale_order_line
+from . import sale_order
+from . import account_move
diff --git a/new_product_type/models/account_move.py b/new_product_type/models/account_move.py
new file mode 100644
index 00000000000..de869dd41d7
--- /dev/null
+++ b/new_product_type/models/account_move.py
@@ -0,0 +1,15 @@
+from odoo import models
+
+
+class AccountMoveLine(models.Model):
+ _inherit = "account.move.line"
+
+ def show_in_report(self):
+ self.ensure_one()
+
+ sale_line = self.sale_line_ids[:1]
+
+ return (
+ not sale_line.is_kit_product
+ or sale_line.order_id.print_in_report
+ )
diff --git a/new_product_type/models/product_template.py b/new_product_type/models/product_template.py
new file mode 100644
index 00000000000..c7a5541261a
--- /dev/null
+++ b/new_product_type/models/product_template.py
@@ -0,0 +1,15 @@
+from odoo import api, fields, models
+from odoo.exceptions import ValidationError
+
+
+class ProductTemplate(models.Model):
+ _inherit = "product.template"
+
+ is_kit = fields.Boolean()
+ sub_product = fields.Many2many("product.product")
+
+ @api.constrains("sub_product")
+ def _check_no_self_product_reference(self):
+ for record in self:
+ if record.product_variant_id in record.sub_product:
+ raise ValidationError("A product cannot be added as a sub-product in its own kit.")
diff --git a/new_product_type/models/sale_order.py b/new_product_type/models/sale_order.py
new file mode 100644
index 00000000000..2e48c082fe9
--- /dev/null
+++ b/new_product_type/models/sale_order.py
@@ -0,0 +1,9 @@
+from odoo import fields, models
+
+
+class SaleOrder(models.Model):
+ _inherit = "sale.order"
+
+ print_in_report = fields.Boolean(
+ string="Print Kit Products"
+ )
diff --git a/new_product_type/models/sale_order_line.py b/new_product_type/models/sale_order_line.py
new file mode 100644
index 00000000000..9692e61413a
--- /dev/null
+++ b/new_product_type/models/sale_order_line.py
@@ -0,0 +1,39 @@
+from odoo import _, api, fields, models
+from odoo.exceptions import UserError
+
+
+class SaleOrderLine(models.Model):
+ _inherit = "sale.order.line"
+
+ is_kit = fields.Boolean(
+ related="product_id.product_tmpl_id.is_kit",
+ store=True
+ )
+ is_kit_product = fields.Boolean()
+ kit_parent_line_id = fields.Many2one("sale.order.line", ondelete="cascade")
+ extra_price = fields.Float(default=0.0)
+
+ @api.ondelete(at_uninstall=False)
+ def _check_kit_product_restriction(self):
+ for line in self:
+ if line.is_kit_product:
+ raise UserError(_("You cannot delete a kit sub product directly."))
+
+ def show_in_report(self):
+ self.ensure_one()
+ return (
+ not self.is_kit_product
+ or self.order_id.print_in_report
+ )
+
+ def action_open_kit_wizard(self):
+ return {
+ "type": "ir.actions.act_window",
+ "name": "Configure Kit",
+ "res_model": "product.kit.wizard",
+ "view_mode": "form",
+ "target": "new",
+ "context": {
+ "active_id": self.id
+ },
+ }
diff --git a/new_product_type/report/invoice_report.xml b/new_product_type/report/invoice_report.xml
new file mode 100644
index 00000000000..c09d9f8915c
--- /dev/null
+++ b/new_product_type/report/invoice_report.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+ o._get_move_lines_to_report().filtered(
+ lambda l: l.show_in_report()
+ )
+
+
+
+
diff --git a/new_product_type/report/sale_order_portal_report.xml b/new_product_type/report/sale_order_portal_report.xml
new file mode 100644
index 00000000000..00a5ac89e06
--- /dev/null
+++ b/new_product_type/report/sale_order_portal_report.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+ sale_order._get_order_lines_to_report().filtered(
+ lambda l: l.show_in_report()
+ )
+
+
+
+
diff --git a/new_product_type/report/sale_order_report.xml b/new_product_type/report/sale_order_report.xml
new file mode 100644
index 00000000000..3f793a7b4a3
--- /dev/null
+++ b/new_product_type/report/sale_order_report.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+ doc._get_order_lines_to_report().filtered(
+ lambda l: l.show_in_report()
+ )
+
+
+
+
diff --git a/new_product_type/security/ir.model.access.csv b/new_product_type/security/ir.model.access.csv
new file mode 100644
index 00000000000..1a7a4f167f2
--- /dev/null
+++ b/new_product_type/security/ir.model.access.csv
@@ -0,0 +1,3 @@
+id,name,model_id:id,group_id:id,perm_read,perm_create,perm_write,perm_unlink
+access_product_kit_wizard,access_product_kit_wizard,model_product_kit_wizard,base.group_user,1,1,1,1
+access_product_kit_wizard_line,access_product_kit_wizard_line,model_product_kit_wizard_line,base.group_user,1,1,1,1
diff --git a/new_product_type/views/product_kit_wizard_views.xml b/new_product_type/views/product_kit_wizard_views.xml
new file mode 100644
index 00000000000..61da1b4b60f
--- /dev/null
+++ b/new_product_type/views/product_kit_wizard_views.xml
@@ -0,0 +1,29 @@
+
+
+
+ product.kit.wizard.form
+ product.kit.wizard
+
+
+
+
+
diff --git a/new_product_type/views/product_views.xml b/new_product_type/views/product_views.xml
new file mode 100644
index 00000000000..542f7836845
--- /dev/null
+++ b/new_product_type/views/product_views.xml
@@ -0,0 +1,17 @@
+
+
+
+ product.template.view.form
+ product.template
+
+
+
+
+
+
+
+
+
+
+
diff --git a/new_product_type/views/sale_order_line_views.xml b/new_product_type/views/sale_order_line_views.xml
new file mode 100644
index 00000000000..d0ba5c40477
--- /dev/null
+++ b/new_product_type/views/sale_order_line_views.xml
@@ -0,0 +1,38 @@
+
+
+
+ sale.order.line.form.view
+ sale.order
+
+
+
+ is_kit_product
+
+
+
+
+
+ is_kit_product
+
+
+ is_kit_product
+
+
+ is_kit_product
+
+
+ is_kit_product
+
+
+ is_kit_product
+
+
+
+
+
+
+
diff --git a/new_product_type/wizard/__init__.py b/new_product_type/wizard/__init__.py
new file mode 100644
index 00000000000..b99680c2dd5
--- /dev/null
+++ b/new_product_type/wizard/__init__.py
@@ -0,0 +1,2 @@
+from . import product_kit_wizard
+from . import product_kit_wizard_line
diff --git a/new_product_type/wizard/product_kit_wizard.py b/new_product_type/wizard/product_kit_wizard.py
new file mode 100644
index 00000000000..7920a7d267a
--- /dev/null
+++ b/new_product_type/wizard/product_kit_wizard.py
@@ -0,0 +1,103 @@
+from odoo import api, fields, models
+
+
+class ProductKitWizard(models.TransientModel):
+ _name = "product.kit.wizard"
+ _description = "Kit Sub Product Wizard"
+
+ sale_line_id = fields.Many2one("sale.order.line")
+ main_product_id = fields.Many2one(
+ "product.product",
+ string="Product"
+ )
+ line_ids = fields.One2many(
+ "product.kit.wizard.line",
+ "wizard_id",
+ string="Sub Products"
+ )
+
+ @api.model
+ def default_get(self, fields_list):
+ res = super().default_get(fields_list)
+
+ sale_line = self.env["sale.order.line"].browse(
+ self.env.context.get("active_id")
+ )
+
+ if not sale_line.product_id:
+ return res
+
+ order = sale_line.order_id
+ product = sale_line.product_id
+
+ lines = []
+
+ for sub_product in product.product_tmpl_id.sub_product:
+
+ existing_line = order.order_line.filtered(
+ lambda l:
+ l.product_id == sub_product
+ and l.kit_parent_line_id == sale_line
+ )[:1]
+
+ lines.append(
+ (0, 0, {
+ "product_id": sub_product.id,
+ "quantity": (
+ existing_line.product_uom_qty
+ if existing_line else 1.0
+ ),
+ "price": (
+ existing_line.extra_price
+ if existing_line else sub_product.lst_price
+ ),
+ })
+ )
+
+ res.update({
+ "sale_line_id": sale_line.id,
+ "main_product_id": product.id,
+ "line_ids": lines,
+ })
+
+ return res
+
+ def action_confirm(self):
+ self.ensure_one()
+ order = self.sale_line_id.order_id
+ parent_line = self.sale_line_id
+ total_price = parent_line.product_id.lst_price
+
+ has_extra_price = 'extra_price' in self.env['sale.order.line']._fields
+
+ for line in self.line_ids:
+ existing_line = order.order_line.filtered(
+ lambda l: l.product_id == line.product_id and l.kit_parent_line_id == parent_line
+ )[:1]
+
+ vals = {
+ "name": line.product_id.display_name,
+ "product_uom_qty": line.quantity,
+ "price_unit": 0.0,
+ "sequence": parent_line.sequence + 1,
+ }
+
+ if has_extra_price:
+ vals["extra_price"] = line.price
+
+ if existing_line:
+ existing_line.write(vals)
+ else:
+ vals.update({
+ "order_id": order.id,
+ "product_id": line.product_id.id,
+ "is_kit_product": True,
+ "kit_parent_line_id": parent_line.id,
+ })
+ self.env["sale.order.line"].create(vals)
+
+ total_price += line.quantity * line.price
+
+ parent_line.write({"price_unit": total_price})
+
+ return {"type": "ir.actions.act_window_close"}
diff --git a/new_product_type/wizard/product_kit_wizard_line.py b/new_product_type/wizard/product_kit_wizard_line.py
new file mode 100644
index 00000000000..03e07f4be45
--- /dev/null
+++ b/new_product_type/wizard/product_kit_wizard_line.py
@@ -0,0 +1,24 @@
+from odoo import api, fields, models
+
+
+class ProductKitWizardLine(models.TransientModel):
+ _name = "product.kit.wizard.line"
+ _description = "Kit Wizard Line"
+
+ wizard_id = fields.Many2one("product.kit.wizard")
+ product_id = fields.Many2one(
+ "product.product",
+ readonly=True
+ )
+ quantity = fields.Float(default=1.0)
+ price = fields.Float()
+ price_subtotal = fields.Float(
+ string="Total Price",
+ compute="_compute_price_subtotal",
+ store=True
+ )
+
+ @api.depends("quantity", "price")
+ def _compute_price_subtotal(self):
+ for line in self:
+ line.price_subtotal = line.quantity * line.price