Merge branch 'mig/15.0/mrp_bom_add' into '15.0'

WIP: mig/15.0/mrp_bom_add into 15.0

See merge request hibou-io/hibou-odoo/suite!1425
This commit is contained in:
Jared Kipe
2022-06-01 19:35:29 +00:00
8 changed files with 302 additions and 0 deletions

1
mrp_bom_add/__init__.py Executable file
View File

@@ -0,0 +1 @@
from . import wizard

27
mrp_bom_add/__manifest__.py Executable file
View File

@@ -0,0 +1,27 @@
{
'name': 'BoM Mass Add',
'author': 'Hibou Corp. <hello@hibou.io>',
'category': 'Hidden',
'version': '15.0.1.0.0',
'description':
"""
Bill of Materials Mass Component Adder
======================================
Helper to add all variants of a Product to a BoM filtered by the attributes on that product.
Adds a button under BoM components named "Add Bulk". This lets you select a product template
and add one BoM line for every variant of that product.
Optionally you can choose to only add the variants that could be selected by the attributes
on the produced product itself. You can also replace all of the lines for the slected product
to allow you to quickly re-configure qty or operation for bulk products.
""",
'depends': [
'mrp',
],
'auto_install': False,
'data': [
'security/ir.model.access.csv',
'wizard/mrp_bom_add_views.xml',
],
}

View File

@@ -0,0 +1,2 @@
"id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
"access_mrp_bom_add","access mrp.bom.add","mrp_bom_add.model_mrp_bom_add","base.group_user",1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_mrp_bom_add access mrp.bom.add mrp_bom_add.model_mrp_bom_add base.group_user 1 1 1 1

View File

@@ -0,0 +1 @@
from . import test_mrp_bom_add

View File

@@ -0,0 +1,137 @@
from odoo.tests.common import TransactionCase
class TestMRPBOMAdd(TransactionCase):
def setUp(self):
super(TestMRPBOMAdd, self).setUp()
self.attr1 = self.env['product.attribute'].create({
'name': 'Test Attr1',
'create_variant': 'always',
'display_type': 'radio',
})
self.attr1_val1 = self.env['product.attribute.value'].create({
'attribute_id': self.attr1.id,
'name': 'Test Attr1 Val1'
})
self.attr1_val2 = self.env['product.attribute.value'].create({
'attribute_id': self.attr1.id,
'name': 'Test Attr2 Val1'
})
self.attr2 = self.env['product.attribute'].create({
'name': 'Test Attr2',
'create_variant': 'always',
'display_type': 'radio',
})
self.attr2_val1 = self.env['product.attribute.value'].create({
'attribute_id': self.attr2.id,
'name': 'Test Attr2 Val1'
})
self.attr2_val2 = self.env['product.attribute.value'].create({
'attribute_id': self.attr2.id,
'name': 'Test Attr2 Val2'
})
# 2 variant product
self.manufacture = self.env['product.template'].create({
'name': 'Test Manufactured',
'attribute_line_ids': [
(0, 0, {
'attribute_id': self.attr1.id,
'value_ids': [(4, self.attr1_val1.id, 0)],
}),
(0, 0, {
'attribute_id': self.attr2.id,
'value_ids': [(4, self.attr2_val1.id, 0), (4, self.attr2_val2.id, 0)],
}),
]
})
# 4 variant component
self.component = self.env['product.template'].create({
'name': 'Test Component',
'attribute_line_ids': [
(0, 0, {
'attribute_id': self.attr1.id,
'value_ids': [(4, self.attr1_val1.id, 0), (4, self.attr1_val2.id, 0)],
}),
(0, 0, {
'attribute_id': self.attr2.id,
'value_ids': [(4, self.attr2_val1.id, 0), (4, self.attr2_val2.id, 0)],
}),
]
})
self.bom = self.env['mrp.bom'].create({
'product_tmpl_id': self.manufacture.id,
'product_qty': 1.0,
'product_uom_id': self.manufacture.uom_id.id,
})
def test_internals(self):
# Ensure BoM is empty
self.assertEqual(len(self.bom.bom_line_ids), 0)
wizard = self.env['mrp.bom.add'].create({
'bom_id': self.bom.id,
})
wizard.product_tmpl_id = self.component
self.assertEqual(wizard._compute_attribute_value_ids(),
self.component.mapped('attribute_line_ids.value_ids'))
self.assertEqual(wizard._compute_product_ids(), self.component.product_variant_ids)
wizard.limit_possible = True
self.assertEqual(wizard._compute_attribute_value_ids(),
self.manufacture.mapped('attribute_line_ids.value_ids'))
def test_main(self):
# Ensure BoM is empty
self.assertEqual(len(self.bom.bom_line_ids), 0)
wizard = self.env['mrp.bom.add'].create({
'bom_id': self.bom.id,
})
self.assertEqual(wizard.product_variant_count, 0)
wizard.product_tmpl_id = self.component
self.assertEqual(wizard.product_variant_count, 4)
wizard.add_variants()
self.assertEqual(len(self.bom.bom_line_ids), 4)
def test_limited(self):
# Ensure BoM is empty
self.assertEqual(len(self.bom.bom_line_ids), 0)
wizard = self.env['mrp.bom.add'].create({
'bom_id': self.bom.id,
})
self.assertEqual(wizard.product_variant_count, 0)
wizard.limit_possible = True
wizard.product_tmpl_id = self.component
self.assertEqual(wizard.product_variant_count, 2)
wizard.add_variants()
self.assertEqual(len(self.bom.bom_line_ids), 2)
def test_replace(self):
# Ensure BoM is empty
self.assertEqual(len(self.bom.bom_line_ids), 0)
wizard = self.env['mrp.bom.add'].create({
'bom_id': self.bom.id,
})
wizard.product_tmpl_id = self.component
self.assertEqual(wizard.product_variant_count, 4)
wizard.add_variants()
self.assertEqual(len(self.bom.bom_line_ids), 4)
# Additive
wizard.add_variants()
self.assertEqual(len(self.bom.bom_line_ids), 8)
# remove those 8 lines when adding new ones
wizard.replace_existing = True
wizard.add_variants()
self.assertEqual(len(self.bom.bom_line_ids), 4)

View File

@@ -0,0 +1 @@
from . import mrp_bom_add

View File

@@ -0,0 +1,83 @@
from odoo import api, fields, models
class ProgramOutputView(models.TransientModel):
_name = 'mrp.bom.add'
_description = 'BoM Add Wizard'
bom_id = fields.Many2one('mrp.bom', string='Bill of Materials')
bom_product_tmpl_id = fields.Many2one('product.template', string='BoM Product', related='bom_id.product_tmpl_id')
product_tmpl_id = fields.Many2one('product.template', string='Product')
product_variant_count = fields.Integer(string='Variant Count',
compute='_compute_variant_count')
limit_possible = fields.Boolean(string='Limit to possible variants',
help='Only add variants that can be selected by BoM Product')
replace_existing = fields.Boolean(string='Replace existing BoM lines for this template.')
existing_line_count = fields.Integer(string='Existing Lines',
help='Remove any existing lines for this Product Template.',
compute='_compute_variant_count')
product_qty = fields.Float(string='Quantity to Consume', default=1.0)
product_uom_id = fields.Many2one('uom.uom', string='Consume Unit of Measure')
@api.depends('product_tmpl_id', 'limit_possible')
def _compute_variant_count(self):
self.ensure_one()
if not self.product_tmpl_id or not self.bom_product_tmpl_id:
self.product_variant_count = 0
self.existing_line_count = 0
else:
products = self._compute_product_ids()
lines = self._compute_existing_line_ids()
self.product_variant_count = len(products)
self.existing_line_count = len(lines)
@api.onchange('product_tmpl_id')
def _onchange_default_product_uom(self):
if self.product_tmpl_id:
self.product_uom_id = self.product_tmpl_id.uom_id
def _compute_attribute_value_ids(self):
attr_val_ids = self.env['product.attribute.value']
other_val_ids = self.product_tmpl_id.mapped('attribute_line_ids.value_ids')
if not self.limit_possible:
return other_val_ids
main_product_value_ids = self.bom_product_tmpl_id.mapped('attribute_line_ids.value_ids')
return main_product_value_ids.filtered(lambda v: v in other_val_ids)
def _compute_product_ids(self):
values = self._compute_attribute_value_ids()
products = self.env['product.product']
for p in self.product_tmpl_id.product_variant_ids:
if not p.product_template_attribute_value_ids.mapped('product_attribute_value_id').filtered(lambda a: a not in values):
# This product's attribute values are in the values set.
products += p
return products
def _compute_existing_line_ids(self):
return self.bom_id.bom_line_ids.filtered(lambda l: l.product_id.product_tmpl_id == self.product_tmpl_id)
def add_variants(self):
if self.replace_existing:
lines = self._compute_existing_line_ids()
lines.unlink()
attribute_values = self._compute_attribute_value_ids()
products = self._compute_product_ids()
if not self.product_uom_id:
self.product_uom_id = self.product_tmpl_id.uom_id
lines = []
for p in products:
bom_product_template_attribute_values = self.bom_product_tmpl_id.attribute_line_ids\
.mapped('product_template_value_ids')
p_values = p.product_template_attribute_value_ids.mapped('product_attribute_value_id')
bom_product_template_attribute_value_ids = bom_product_template_attribute_values \
.filtered(lambda v: v.product_attribute_value_id in attribute_values and v.product_attribute_value_id in p_values)
lines.append((0, 0, {
'bom_id': self.bom_id.id,
'product_id': p.id,
'product_qty': self.product_qty,
'product_uom_id': self.product_uom_id.id,
'bom_product_template_attribute_value_ids': [(4, a.id, 0)
for a in bom_product_template_attribute_value_ids],
}))
self.bom_id.write({'bom_line_ids': lines})

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_mrp_bom_add_form_view" model="ir.ui.view">
<field name="name">mrp.bom.add.form</field>
<field name="model">mrp.bom.add</field>
<field name="arch" type="xml">
<form string="Add to Bill of Materials">
<group>
<group>
<field name="bom_id" invisible="1"/>
<field name="bom_product_tmpl_id" invisible="1"/>
<field name="product_tmpl_id"/>
<field name="limit_possible"/>
<field name="product_variant_count"/>
<field name="replace_existing"/>
<field name="existing_line_count"/>
</group>
<group>
<field name="product_qty"/>
<field name="product_uom_id"/>
</group>
</group>
<footer>
<button string="Add" class="btn-primary" type="object" name="add_variants"/>
</footer>
</form>
</field>
</record>
<record id="action_mrp_bom_add" model="ir.actions.act_window">
<field name="name">Add to Bill of Materials</field>
<field name="res_model">mrp.bom.add</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="view_mrp_bom_add_form_view"/>
<field name="target">new</field>
</record>
<record id="mrp_bom_form_view_inherit" model="ir.ui.view">
<field name="name">mrp.bom.form.inherit</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']" position="after">
<button string="Add Bulk" type="action" name="%(mrp_bom_add.action_mrp_bom_add)d" context="{'default_bom_id': id}"/>
</xpath>
</field>
</record>
</odoo>