Skip to content

[ADD] modular_type: support for manufacturing orders from sales orders #899

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: 18.0
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions modular_types/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models
from . import wizards
19 changes: 19 additions & 0 deletions modular_types/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
'name': 'Sales Modular Products',
'version': '1.0',
'summary': 'Automate creation of modular product lines on sales orders.',
'author': 'ksoz',
'depends': ['sale_management', 'product', 'mrp'],
'license': 'LGPL-3',
'data': [
'security/ir.model.access.csv',
'wizards/modular_wizard_views.xml',
'views/modular_type_views.xml',
'views/product_template_views.xml',
'views/mrp_bom_views.xml',
'views/sale_order_view.xml',
'views/mrp_production_view.xml',
],
'installable': True,
'application': False,
}
5 changes: 5 additions & 0 deletions modular_types/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from . import modular_type_config
from . import product_template
from . import mrp_bom
from . import sale_order
from . import mrp
15 changes: 15 additions & 0 deletions modular_types/models/modular_type_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from odoo import models, fields


class ModularTypeConfig(models.Model):
_name = 'modular.type.config'
_description = 'Configuration for Modular Types'

name = fields.Char(string='Modular Type Name', required=True)
default_quantity = fields.Float(string='Default Quantity', default=1.0, required=True)
component_product_id = fields.Many2one(
'product.product',
string='Component Product',
required=True,
help="The product that represents this modular component on the sales order line."
)
38 changes: 38 additions & 0 deletions modular_types/models/mrp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from odoo import api, models, fields


class StockMove(models.Model):
_inherit = 'stock.move'

modular_type_id = fields.Many2one(
'modular.type.config',
string="Module Type",
readonly=True
)


class MrpProduction(models.Model):
_inherit = 'mrp.production'

@api.model_create_multi
def create(self, vals_list):
custom_values_map = {}
for i, vals in enumerate(vals_list):
if 'custom_modular_values' in vals:
custom_values_map[i] = vals.pop('custom_modular_values')

mos = super().create(vals_list)

for i, mo in enumerate(mos):
custom_values = custom_values_map.get(i)
if custom_values and mo.move_raw_ids:
for custom_val in custom_values:
move_to_update = mo.move_raw_ids.filtered(
lambda m: m.product_id.id == custom_val['component_product_id']
)
if move_to_update:
move_to_update.write({
'product_uom_qty': custom_val['quantity'],
'modular_type_id': custom_val['modular_type_id'],
})
return mos
11 changes: 11 additions & 0 deletions modular_types/models/mrp_bom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from odoo import models, fields


class MrpBomLine(models.Model):
_inherit = 'mrp.bom.line'

modular_type_id = fields.Many2one(
'modular.type.config',
string='Modular Type',
help="The modular type configuration for this BOM line."
)
12 changes: 12 additions & 0 deletions modular_types/models/product_template.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from odoo import models, fields


class ProductTemplate(models.Model):
_inherit = 'product.template'

modular_type_ids = fields.Many2many(
'modular.type.config',
'component_product_id',
string='Modular Types',
help="Select the modular types applicable to this product template."
)
75 changes: 75 additions & 0 deletions modular_types/models/sale_order.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
from odoo import api, models, fields


class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'

has_modular_type = fields.Boolean(
string='Has Modular Type',
compute='_compute_has_modular_type',
help="Technical field to check if the product's BoM has modular components."
)
source_modular_type_id = fields.Many2one(
'modular.type.config',
string="Source Modular Type",
readonly=True,
copy=False
)

@api.depends('product_id')
def _compute_has_modular_type(self):
if not self.product_id:
self.has_modular_type = False
return

boms = self.env['mrp.bom']._bom_find(
self.product_id,
company_id=self.company_id.id,
bom_type='normal'
)
for line in self:
line.has_modular_type = False
bom = boms.get(line.product_id)
if bom:
if any(bom_line.modular_type_id for bom_line in bom.bom_line_ids):
line.has_modular_type = True

def action_configure_modular_line(self):
self.ensure_one()

boms_dict = self.env['mrp.bom']._bom_find(
self.product_id,
company_id=self.company_id.id,
bom_type='normal'
)
bom = boms_dict.get(self.product_id)

if not bom:
return True

wizard_lines_vals = []
for bom_line in bom.bom_line_ids:
if bom_line.modular_type_id:
wizard_lines_vals.append((0, 0, {
'modular_type_id': bom_line.modular_type_id.id,
'component_product_id': bom_line.product_id.id,
'default_quantity': bom_line.product_qty * self.product_uom_qty,
'new_quantity': bom_line.product_qty * self.product_uom_qty,
}))

if not wizard_lines_vals:
return True

wizard = self.env['modular.line.generator.wizard'].create({
'sale_order_id': self.order_id.id,
'wizard_line_ids': wizard_lines_vals,
})

return {
'name': 'Set Modular Type Values',
'type': 'ir.actions.act_window',
'res_model': 'modular.line.generator.wizard',
'view_mode': 'form',
'res_id': wizard.id,
'target': 'new',
}
4 changes: 4 additions & 0 deletions modular_types/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_modular_type_config_user,modular.type.config.user,model_modular_type_config,base.group_user,1,1,1,1
access_modular_line_generator_wizard,access_modular_line_generator_wizard,modular_types.model_modular_line_generator_wizard,base.group_user,1,1,1,1
access_modular_line_generator_wizard_line,access_modular_line_generator_wizard_line,modular_types.model_modular_line_generator_wizard_line,base.group_user,1,1,1,1
48 changes: 48 additions & 0 deletions modular_types/views/modular_type_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_modular_type_config_form" model="ir.ui.view">
<field name="name">modular.type.config.form</field>
<field name="model">modular.type.config</field>
<field name="arch" type="xml">
<form string="Modular Type Configuration">
<sheet>
<group>
<group>
<field name="name"/>
<field name="component_product_id"/>
</group>
<group>
<field name="default_quantity"/>
</group>
</group>
</sheet>
</form>
</field>
</record>

<record id="view_modular_type_list" model="ir.ui.view">
<field name="name">view.modular_type_list</field>
<field name="model">modular.type.config</field>
<field name="arch" type="xml">
<list string="Modular Types">
<field name="name"/>
<field name="component_product_id"/>
<field name="default_quantity"/>
</list>
</field>
</record>

<record id="action_modular_type_config" model="ir.actions.act_window">
<field name="name">Modular Types</field>
<field name="res_model">modular.type.config</field>
<field name="view_mode">list,form</field>
</record>

<menuitem id="menu_modular_type_config"
name="Modular Types"
parent="sale.menu_sale_config"
action="action_modular_type_config"
sequence="25"/>
</data>
</odoo>
14 changes: 14 additions & 0 deletions modular_types/views/mrp_bom_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<odoo>
<data>
<record id="mrp_bom_form_view_modular" model="ir.ui.view">
<field name="name">mrp.bom.form.modular</field>
<field name="model">mrp.bom</field>
<field name="inherit_id" ref="mrp.mrp_bom_form_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='bom_line_ids']/list/field[@name='product_id']" position="after">
<field name="modular_type_id"/>
</xpath>
</field>
</record>
</data>
</odoo>
15 changes: 15 additions & 0 deletions modular_types/views/mrp_production_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<data>
<record id="mrp_production_form_view_modular" model="ir.ui.view">
<field name="name">mrp.production.form.modular</field>
<field name="model">mrp.production</field>
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='move_raw_ids']/list/field[@name='product_id']" position="after">
<field name="modular_type_id"/>
</xpath>
</field>
</record>
</data>
</odoo>
13 changes: 13 additions & 0 deletions modular_types/views/product_template_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="product_template_form_view_inherit" model="ir.ui.view">
<field name="name">product.template.form.inherit</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='service_tracking']" position="after">
<field name="modular_type_ids" widget="many2many_tags" options="{'no_create': True}" />
</xpath>
</field>
</record>
</odoo>
21 changes: 21 additions & 0 deletions modular_types/views/sale_order_view.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version='1.0' encoding='utf-8'?>
<odoo>
<data>
<record id="sale_order_form_modular_line_button" model="ir.ui.view">
<field name="name">sale.order.form.modular.line.button</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='order_line']/list//field[@name='product_template_id']" position="after">
<field name="has_modular_type" invisible="1"/>
<button name="action_configure_modular_line"
type="object"
icon="fa-flask"
title="Configure modular components"
invisible="(not has_modular_type)"
help="Configure modular components"/>
</xpath>
</field>
</record>
</data>
</odoo>
1 change: 1 addition & 0 deletions modular_types/wizards/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from . import modular_wizard
57 changes: 57 additions & 0 deletions modular_types/wizards/modular_wizard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from odoo import models, fields


class ModularLineGeneratorWizard(models.TransientModel):
_name = 'modular.line.generator.wizard'
_description = 'Wizard to Generate Modular Lines'

sale_order_id = fields.Many2one('sale.order', string="Sales Order", readonly=True)
sale_order_line_id = fields.Many2one('sale.order.line', string="Sales Order Line")
wizard_line_ids = fields.One2many(
'modular.line.generator.wizard.line',
'wizard_id',
string='Modular Lines'
)

def action_apply(self):
self.ensure_one()
so_line = self.sale_order_line_id or self.env['sale.order.line'].browse(
self.env.context.get('active_id')
)
if not so_line:
return

so_line.modular_value_ids.unlink()
vals_list = []
for line in self.wizard_line_ids:
vals_list.append({
'sale_order_line_id': so_line.id,
'modular_type_id': line.modular_type_id.id,
'component_product_id': line.component_product_id.id,
'quantity': line.new_quantity,
})
self.env['sale.order.line.modular.value'].create(vals_list)
return {'type': 'ir.actions.act_window_close'}

def action_confirm(self):
for line in self.wizard_line_ids:
if line.new_quantity > 0:
self.env['sale.order.line'].create({
'order_id': self.sale_order_id.id,
'product_id': line.component_product_id.id,
'product_uom_qty': line.new_quantity,
'price_unit': 0,
'source_modular_type_id': line.modular_type_id.id,
})
return {'type': 'ir.actions.act_window_close'}


class ModularLineGeneratorWizardLine(models.TransientModel):
_name = 'modular.line.generator.wizard.line'
_description = 'Line for Modular Line Generator Wizard'

wizard_id = fields.Many2one('modular.line.generator.wizard', required=True, ondelete='cascade')
modular_type_id = fields.Many2one('modular.type.config', string="Module Type", readonly=True)
component_product_id = fields.Many2one('product.product', string="Component", readonly=True)
default_quantity = fields.Float(string="BoM Qty", readonly=True)
new_quantity = fields.Float(string="Quantity", help="The quantity to add to the Sales Order.")
27 changes: 27 additions & 0 deletions modular_types/wizards/modular_wizard_views.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<odoo>
<data>
<record id="view_modular_line_generator_wizard_form" model="ir.ui.view">
<field name="name">modular.line.generator.wizard.form</field>
<field name="model">modular.line.generator.wizard</field>
<field name="arch" type="xml">
<form string="Set Modular Type Values">
<p>
Review and adjust the quantities for the modular components below.
</p>
<field name="wizard_line_ids" nolabel="1">
<list editable="bottom">
<field name="modular_type_id"/>
<field name="component_product_id" string="Component"/>
<field name="default_quantity"/>
<field name="new_quantity"/>
</list>
</field>
<footer>
<button name="action_confirm" string="Add" type="object" class="btn-primary"/>
<button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
</data>
</odoo>