diff --git a/modular_types/__init__.py b/modular_types/__init__.py
new file mode 100644
index 00000000000..9b4296142f4
--- /dev/null
+++ b/modular_types/__init__.py
@@ -0,0 +1,2 @@
+from . import models
+from . import wizard
diff --git a/modular_types/__manifest__.py b/modular_types/__manifest__.py
new file mode 100644
index 00000000000..b56c80ed40a
--- /dev/null
+++ b/modular_types/__manifest__.py
@@ -0,0 +1,22 @@
+{
+ "name": "Modular Types",
+ "version": "1.0",
+ "category": "Manufacturing",
+ "author": "Abhi Bhingradiya(abbhi)",
+ "description": """
+ This module allows adding modular types to products and BoM lines.
+ In Sales Orders, users can set values for these modular types, which
+ will then be used to multiply the quantities in the resulting Manufacturing Orders.
+ """,
+ "depends": ["sale_management", "mrp", "sale_mrp"],
+ "data": [
+ "security/ir.model.access.csv",
+ "views/modular_type_views.xml",
+ "views/product_views.xml",
+ "views/mrp_bom_views.xml",
+ "views/sale_order_views.xml",
+ "wizard/set_modular_values_views.xml",
+ "views/mrp_production_views.xml"
+ ],
+ "license": "LGPL-3",
+}
diff --git a/modular_types/models/__init__.py b/modular_types/models/__init__.py
new file mode 100644
index 00000000000..bad6cfbc5dd
--- /dev/null
+++ b/modular_types/models/__init__.py
@@ -0,0 +1,7 @@
+from . import modular_type
+from . import product
+from . import mrp_bom_line
+from . import sale_order_line
+from . import mrp_production
+from . import stock_move
+from . import sale_order_line_modular_values
diff --git a/modular_types/models/modular_type.py b/modular_types/models/modular_type.py
new file mode 100644
index 00000000000..8dc438fb27b
--- /dev/null
+++ b/modular_types/models/modular_type.py
@@ -0,0 +1,8 @@
+from odoo import fields, models
+
+
+class ModularType(models.Model):
+ _name = "modular.type"
+ _description = "Modular Type"
+
+ name = fields.Char(string="Name", required=True)
diff --git a/modular_types/models/mrp_bom_line.py b/modular_types/models/mrp_bom_line.py
new file mode 100644
index 00000000000..570ea69b542
--- /dev/null
+++ b/modular_types/models/mrp_bom_line.py
@@ -0,0 +1,7 @@
+from odoo import fields, models
+
+
+class MrpBomLine(models.Model):
+ _inherit = "mrp.bom.line"
+
+ modular_type_id = fields.Many2one("modular.type", string="Module Type")
diff --git a/modular_types/models/mrp_production.py b/modular_types/models/mrp_production.py
new file mode 100644
index 00000000000..3c5c4ee5404
--- /dev/null
+++ b/modular_types/models/mrp_production.py
@@ -0,0 +1,42 @@
+from odoo import models
+
+
+class MrpProduction(models.Model):
+ _inherit = "mrp.production"
+
+ def _get_moves_raw_values(self):
+ res = super()._get_moves_raw_values()
+ for production in self:
+ if (
+ not production.sale_line_id
+ or not production.sale_line_id.modular_value_ids
+ ):
+ continue
+
+ factors = {
+ val.modular_type_id.id: val.value
+ for val in production.sale_line_id.modular_value_ids
+ }
+
+ for move_vals in res:
+ if move_vals.get("raw_material_production_id") == production.id:
+ bom_line_id = move_vals.get("bom_line_id")
+ if bom_line_id:
+ bom_line = self.env["mrp.bom.line"].browse(bom_line_id)
+ if (
+ bom_line.modular_type_id
+ and bom_line.modular_type_id.id in factors
+ ):
+ factor = factors[bom_line.modular_type_id.id]
+ move_vals["product_uom_qty"] *= factor
+ return res
+
+ def _get_move_raw_values(
+ self, product, product_uom_qty, product_uom, operation_id=False, bom_line=False
+ ):
+ res = super()._get_move_raw_values(
+ product, product_uom_qty, product_uom, operation_id, bom_line
+ )
+ if bom_line and bom_line.modular_type_id:
+ res["modular_type_id"] = bom_line.modular_type_id.id
+ return res
diff --git a/modular_types/models/product.py b/modular_types/models/product.py
new file mode 100644
index 00000000000..6c34e3e2094
--- /dev/null
+++ b/modular_types/models/product.py
@@ -0,0 +1,7 @@
+from odoo import fields, models
+
+
+class ProductTemplate(models.Model):
+ _inherit = "product.template"
+
+ modular_type_ids = fields.Many2many("modular.type", string="Module Types")
diff --git a/modular_types/models/sale_order_line.py b/modular_types/models/sale_order_line.py
new file mode 100644
index 00000000000..a74828c33b5
--- /dev/null
+++ b/modular_types/models/sale_order_line.py
@@ -0,0 +1,43 @@
+from odoo import api, fields, models
+
+
+class SaleOrderLine(models.Model):
+ _inherit = "sale.order.line"
+
+ modular_value_ids = fields.One2many(
+ "sale.order.line.modular.value", "sale_line_id", string="Modular Values"
+ )
+
+ has_modular_types = fields.Boolean(compute="_compute_has_modular_types", store=True)
+
+ @api.depends("product_id", "product_id.modular_type_ids")
+ def _compute_has_modular_types(self):
+ for line in self:
+ line.has_modular_types = bool(line.product_id.modular_type_ids)
+
+ def action_open_modular_values_wizard(self):
+ self.ensure_one()
+ return {
+ "name": "Set modular type values",
+ "type": "ir.actions.act_window",
+ "res_model": "set.modular.values.wizard",
+ "view_mode": "form",
+ "target": "new",
+ "context": {
+ "default_sale_line_id": self.id,
+ "default_line_ids": [
+ (
+ 0,
+ 0,
+ {
+ "modular_type_id": m_type.id,
+ "value": self.modular_value_ids.filtered(
+ lambda v: v.modular_type_id == m_type
+ )[:1].value
+ or 1.0,
+ },
+ )
+ for m_type in self.product_id.modular_type_ids
+ ],
+ },
+ }
diff --git a/modular_types/models/sale_order_line_modular_values.py b/modular_types/models/sale_order_line_modular_values.py
new file mode 100644
index 00000000000..623596bc66b
--- /dev/null
+++ b/modular_types/models/sale_order_line_modular_values.py
@@ -0,0 +1,14 @@
+from odoo import fields, models
+
+
+class SaleOrderLineModularValue(models.Model):
+ _name = "sale.order.line.modular.value"
+ _description = "Sale Order Line Modular Value"
+
+ sale_line_id = fields.Many2one(
+ "sale.order.line", string="Sale Line", ondelete="cascade"
+ )
+ modular_type_id = fields.Many2one(
+ "modular.type", string="Modular Type", required=True
+ )
+ value = fields.Float(string="Value", default=1.0)
diff --git a/modular_types/models/stock_move.py b/modular_types/models/stock_move.py
new file mode 100644
index 00000000000..6f643024cc4
--- /dev/null
+++ b/modular_types/models/stock_move.py
@@ -0,0 +1,7 @@
+from odoo import fields, models
+
+
+class StockMove(models.Model):
+ _inherit = "stock.move"
+
+ modular_type_id = fields.Many2one("modular.type", string="Module Type")
diff --git a/modular_types/security/ir.model.access.csv b/modular_types/security/ir.model.access.csv
new file mode 100644
index 00000000000..6813b0b61f5
--- /dev/null
+++ b/modular_types/security/ir.model.access.csv
@@ -0,0 +1,5 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_modular_type,modular_type,model_modular_type,base.group_user,1,1,1,1
+access_sale_order_line_modular_value,sale_order_line_modular_value,model_sale_order_line_modular_value,base.group_user,1,1,1,1
+access_set_modular_values_wizard,set_modular_values_wizard,model_set_modular_values_wizard,base.group_user,1,1,1,1
+access_set_modular_values_line_wizard,set_modular_values_line_wizard,model_set_modular_values_line_wizard,base.group_user,1,1,1,1
diff --git a/modular_types/views/modular_type_views.xml b/modular_types/views/modular_type_views.xml
new file mode 100644
index 00000000000..34d8519844d
--- /dev/null
+++ b/modular_types/views/modular_type_views.xml
@@ -0,0 +1,19 @@
+
+
+ modular.type.list
+ modular.type
+
+
+
+
+
+
+
+
+ Modular Types
+ modular.type
+ list,form
+
+
+
+
diff --git a/modular_types/views/mrp_bom_views.xml b/modular_types/views/mrp_bom_views.xml
new file mode 100644
index 00000000000..8fc14a99066
--- /dev/null
+++ b/modular_types/views/mrp_bom_views.xml
@@ -0,0 +1,12 @@
+
+
+ mrp.bom.form.inherit
+ mrp.bom
+
+
+
+
+
+
+
+
diff --git a/modular_types/views/mrp_production_views.xml b/modular_types/views/mrp_production_views.xml
new file mode 100644
index 00000000000..7cbde183b55
--- /dev/null
+++ b/modular_types/views/mrp_production_views.xml
@@ -0,0 +1,12 @@
+
+
+ mrp.production.form.inherit.modular
+ mrp.production
+
+
+
+
+
+
+
+
diff --git a/modular_types/views/product_views.xml b/modular_types/views/product_views.xml
new file mode 100644
index 00000000000..f57ecacb0f9
--- /dev/null
+++ b/modular_types/views/product_views.xml
@@ -0,0 +1,12 @@
+
+
+ product.template.form.inherit
+ product.template
+
+
+
+
+
+
+
+
diff --git a/modular_types/views/sale_order_views.xml b/modular_types/views/sale_order_views.xml
new file mode 100644
index 00000000000..49ec7bc4ab4
--- /dev/null
+++ b/modular_types/views/sale_order_views.xml
@@ -0,0 +1,12 @@
+
+
+ sale.order.form.inherit
+ sale.order
+
+
+
+
+
+
+
+
diff --git a/modular_types/wizard/__init__.py b/modular_types/wizard/__init__.py
new file mode 100644
index 00000000000..f400a5687e3
--- /dev/null
+++ b/modular_types/wizard/__init__.py
@@ -0,0 +1,2 @@
+from . import set_modular_values_wizard
+from . import set_modular_values_line_wizard
diff --git a/modular_types/wizard/set_modular_values_line_wizard.py b/modular_types/wizard/set_modular_values_line_wizard.py
new file mode 100644
index 00000000000..723c47c1c42
--- /dev/null
+++ b/modular_types/wizard/set_modular_values_line_wizard.py
@@ -0,0 +1,10 @@
+from odoo import fields, models
+
+
+class SetModularValuesLineWizard(models.TransientModel):
+ _name = "set.modular.values.line.wizard"
+ _description = "Set Modular Type Value Line"
+
+ wizard_id = fields.Many2one("set.modular.values.wizard", string="Wizard")
+ modular_type_id = fields.Many2one("modular.type", string="Modular Type")
+ value = fields.Float(string="Value", default=1.0)
diff --git a/modular_types/wizard/set_modular_values_views.xml b/modular_types/wizard/set_modular_values_views.xml
new file mode 100644
index 00000000000..b493317f889
--- /dev/null
+++ b/modular_types/wizard/set_modular_values_views.xml
@@ -0,0 +1,20 @@
+
+
+ set.modular.values.wizard.form
+ set.modular.values.wizard
+
+
+
+
+
diff --git a/modular_types/wizard/set_modular_values_wizard.py b/modular_types/wizard/set_modular_values_wizard.py
new file mode 100644
index 00000000000..9d4bc0aa3d3
--- /dev/null
+++ b/modular_types/wizard/set_modular_values_wizard.py
@@ -0,0 +1,26 @@
+from odoo import fields, models
+
+
+class SetModularValuesWizard(models.TransientModel):
+ _name = "set.modular.values.wizard"
+ _description = "Set Modular Type Values"
+
+ sale_line_id = fields.Many2one("sale.order.line", string="Sale Line")
+ line_ids = fields.One2many(
+ "set.modular.values.line.wizard", "wizard_id", string="Values"
+ )
+
+ def action_confirm(self):
+ self.ensure_one()
+ self.sale_line_id.modular_value_ids.unlink()
+ vals = []
+ for line in self.line_ids:
+ vals.append(
+ (
+ 0,
+ 0,
+ {"modular_type_id": line.modular_type_id.id, "value": line.value},
+ )
+ )
+ self.sale_line_id.write({"modular_value_ids": vals})
+ return {"type": "ir.actions.act_window_close"}