diff --git a/module_types/__init__.py b/module_types/__init__.py
new file mode 100644
index 0000000000..9b4296142f
--- /dev/null
+++ b/module_types/__init__.py
@@ -0,0 +1,2 @@
+from . import models
+from . import wizard
diff --git a/module_types/__manifest__.py b/module_types/__manifest__.py
new file mode 100644
index 0000000000..4e4d49c137
--- /dev/null
+++ b/module_types/__manifest__.py
@@ -0,0 +1,17 @@
+{
+ 'name': 'Product Module type',
+ 'version': '1.0',
+ 'category': 'Sales/Manufacturing',
+ 'summary': 'Enable Modular types feature for Manufacturing orders',
+ 'depends': ['sale_mrp', 'sale_management'],
+ 'data': [
+ 'security/ir.model.access.csv',
+ 'wizard/sale_module_types_wizard.xml',
+ 'views/product_template_views.xml',
+ 'views/mrp_bom_views.xml',
+ 'views/sales_order_views.xml',
+ 'views/stock_move_views.xml'
+ ],
+ 'installable': True,
+ 'license': 'LGPL-3'
+}
diff --git a/module_types/models/__init__.py b/module_types/models/__init__.py
new file mode 100644
index 0000000000..59c891dbe7
--- /dev/null
+++ b/module_types/models/__init__.py
@@ -0,0 +1,6 @@
+from . import module_types
+from . import product_template
+from . import mrp_bom
+from . import sales_order_line
+from . import sales_order
+from . import stock_move
diff --git a/module_types/models/module_types.py b/module_types/models/module_types.py
new file mode 100644
index 0000000000..dbfe760b2e
--- /dev/null
+++ b/module_types/models/module_types.py
@@ -0,0 +1,9 @@
+from odoo import fields, models
+
+
+class ModuleTypes(models.Model):
+ _name = "module.types"
+ _description = "Module Types"
+
+ name = fields.Char("Module type name", required=True)
+ value = fields.Integer("Module type value", default=0)
diff --git a/module_types/models/mrp_bom.py b/module_types/models/mrp_bom.py
new file mode 100644
index 0000000000..4d71cae248
--- /dev/null
+++ b/module_types/models/mrp_bom.py
@@ -0,0 +1,42 @@
+from odoo import models, fields, api
+
+
+class MrpBom(models.Model):
+ _inherit = 'mrp.bom'
+
+ module_type_ids = fields.Many2many(
+ 'module.types',
+ string="Available Module Types",
+ related='product_tmpl_id.module_types',
+ readonly=True
+ )
+
+ @api.onchange('product_tmpl_id')
+ def _onchange_parent_product(self):
+ if self.bom_line_ids:
+ for line in self.bom_line_ids:
+ line.module_types_id = False
+
+
+class MrpBomLine(models.Model):
+ _inherit = 'mrp.bom.line'
+
+ module_types_id = fields.Many2one(
+ 'module.types',
+ string="Module Type",
+ domain="[('id', 'in', available_module_type_ids)] or []"
+ )
+
+ available_module_type_ids = fields.Many2many(
+ 'module.types',
+ compute='_compute_available_module_type_ids',
+ store=False
+ )
+
+ @api.depends('bom_id.product_tmpl_id')
+ def _compute_available_module_type_ids(self):
+ for line in self:
+ if line.bom_id.product_tmpl_id:
+ line.available_module_type_ids = line.bom_id.product_tmpl_id.module_types
+ else:
+ line.available_module_type_ids = False
diff --git a/module_types/models/product_template.py b/module_types/models/product_template.py
new file mode 100644
index 0000000000..68cda028fa
--- /dev/null
+++ b/module_types/models/product_template.py
@@ -0,0 +1,7 @@
+from odoo import fields, models
+
+
+class ProductTemplate(models.Model):
+ _inherit = "product.template"
+
+ module_types = fields.Many2many("module.types", string="Module types")
diff --git a/module_types/models/sales_order.py b/module_types/models/sales_order.py
new file mode 100644
index 0000000000..43f32409e4
--- /dev/null
+++ b/module_types/models/sales_order.py
@@ -0,0 +1,14 @@
+from odoo import models
+
+
+class SaleOrder(models.Model):
+ _inherit = 'sale.order'
+
+ def action_confirm(self):
+ res = super().action_confirm()
+ for production in self.mrp_production_ids:
+ selected_order_line = self.order_line.filtered(lambda line: line.product_template_id == production.product_tmpl_id and line.product_uom_qty == production.product_qty)
+ for stock_line in production.move_raw_ids.filtered(lambda component: component.module_type_id):
+ have_module_types = bool(selected_order_line.product_template_id.module_types)
+ stock_line.product_uom_qty *= stock_line.module_type_id.value if have_module_types else 0
+ return res
diff --git a/module_types/models/sales_order_line.py b/module_types/models/sales_order_line.py
new file mode 100644
index 0000000000..c2db635e26
--- /dev/null
+++ b/module_types/models/sales_order_line.py
@@ -0,0 +1,14 @@
+from odoo import api, fields, models
+
+
+class SaleOrderLine(models.Model):
+ _inherit = "sale.order.line"
+
+ have_module_types = fields.Integer(
+ compute="_compute_module_types_count"
+ )
+
+ @api.depends('product_template_id.module_types')
+ def _compute_module_types_count(self):
+ for line in self:
+ line.have_module_types = bool(line.product_id.module_types)
diff --git a/module_types/models/stock_move.py b/module_types/models/stock_move.py
new file mode 100644
index 0000000000..c7fb05b37c
--- /dev/null
+++ b/module_types/models/stock_move.py
@@ -0,0 +1,15 @@
+from odoo import api, fields, models
+
+
+class StockMove(models.Model):
+ _inherit = 'stock.move'
+
+ module_type_id = fields.Many2one(
+ comodel_name='module.types',
+ compute="_compute_modular_type_id",
+ string='Modular Type')
+
+ @api.depends('production_id.bom_id.bom_line_ids')
+ def _compute_modular_type_id(self):
+ for line in self:
+ line.module_type_id = line.raw_material_production_id.bom_id.bom_line_ids.filtered(lambda bom_line: bom_line.product_id == line.product_id).module_types_id
diff --git a/module_types/security/ir.model.access.csv b/module_types/security/ir.model.access.csv
new file mode 100644
index 0000000000..df97d8aa35
--- /dev/null
+++ b/module_types/security/ir.model.access.csv
@@ -0,0 +1,4 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+module_types.access_module_types,access_module_types,module_types.model_module_types,base.group_user,1,1,1,1
+module_types.access_sale_module_type_wizard,access_sale_module_type_wizard,module_types.model_sale_module_type_wizard,base.group_user,1,1,1,1
+module_types.access_sale_module_type_wizard_line,access_sale_module_type_wizard_line,module_types.model_sale_module_type_wizard_line,base.group_user,1,1,1,1
\ No newline at end of file
diff --git a/module_types/views/mrp_bom_views.xml b/module_types/views/mrp_bom_views.xml
new file mode 100644
index 0000000000..1d0def89a8
--- /dev/null
+++ b/module_types/views/mrp_bom_views.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ res.mrp.bom.form.inherit.module_types
+ mrp.bom
+
+
+
+
+
+
+
+
+
diff --git a/module_types/views/product_template_views.xml b/module_types/views/product_template_views.xml
new file mode 100644
index 0000000000..a9e4741e0e
--- /dev/null
+++ b/module_types/views/product_template_views.xml
@@ -0,0 +1,15 @@
+
+
+
+
+ res.product.form.inherit.module_types
+ product.template
+
+
+
+
+
+
+
+
+
diff --git a/module_types/views/sales_order_views.xml b/module_types/views/sales_order_views.xml
new file mode 100644
index 0000000000..3f34e88d08
--- /dev/null
+++ b/module_types/views/sales_order_views.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ sale.order.line.form.module.button
+ sale.order
+
+
+
+
+
+
+
+
+
diff --git a/module_types/views/stock_move_views.xml b/module_types/views/stock_move_views.xml
new file mode 100644
index 0000000000..4dc6620707
--- /dev/null
+++ b/module_types/views/stock_move_views.xml
@@ -0,0 +1,13 @@
+
+
+
+ mrp.production.form.view.inherit.modular.types
+ mrp.production
+
+
+
+
+
+
+
+
diff --git a/module_types/wizard/__init__.py b/module_types/wizard/__init__.py
new file mode 100644
index 0000000000..ece030cf64
--- /dev/null
+++ b/module_types/wizard/__init__.py
@@ -0,0 +1 @@
+from . import sale_module_types_wizard
diff --git a/module_types/wizard/sale_module_types_wizard.py b/module_types/wizard/sale_module_types_wizard.py
new file mode 100644
index 0000000000..c5aca3e2a2
--- /dev/null
+++ b/module_types/wizard/sale_module_types_wizard.py
@@ -0,0 +1,43 @@
+from odoo import Command, api, fields, models
+
+
+class SaleModuleTypeWizard(models.TransientModel):
+ _name = "sale.module.type.wizard"
+ _description = "Wizard to set Module Types for Sales Order Line"
+
+ wizard_lines = fields.One2many(
+ comodel_name='sale.module.type.wizard.line',
+ inverse_name='wizard_id',
+ string='Modular Wizard Lines'
+ )
+
+ @api.model
+ def default_get(self, fields_list):
+ res = super().default_get(fields_list)
+ sales_order_line = self.env['sale.order.line'].browse(self.env.context.get('active_id'))
+ if sales_order_line.product_template_id:
+ res.update({
+ 'wizard_lines': [
+ Command.create({
+ 'module_type_id': modular_type.id,
+ 'value': modular_type.value
+ })
+ for modular_type in sales_order_line.product_template_id.module_types
+ ],
+ })
+ return res
+
+ def action_confirm(self):
+ for lines in self.wizard_lines:
+ lines.module_type_id.write({
+ 'value': lines.value
+ })
+
+
+class SaleModuleTypeWizardLine(models.TransientModel):
+ _name = "sale.module.type.wizard.line"
+ _description = "Wizard Lines for Module Types"
+
+ wizard_id = fields.Many2one('sale.module.type.wizard')
+ module_type_id = fields.Many2one('module.types', string="Module Type")
+ value = fields.Integer()
diff --git a/module_types/wizard/sale_module_types_wizard.xml b/module_types/wizard/sale_module_types_wizard.xml
new file mode 100644
index 0000000000..cbb548ef1a
--- /dev/null
+++ b/module_types/wizard/sale_module_types_wizard.xml
@@ -0,0 +1,30 @@
+
+
+
+
+ sale.module.type.wizard.form
+ sale.module.type.wizard
+
+
+
+
+
+
+ Set Modular Types Values
+ sale.module.type.wizard
+ form
+ new
+
+
+