diff --git a/mrp_bom_add/__init__.py b/mrp_bom_add/__init__.py new file mode 100755 index 00000000..40272379 --- /dev/null +++ b/mrp_bom_add/__init__.py @@ -0,0 +1 @@ +from . import wizard diff --git a/mrp_bom_add/__manifest__.py b/mrp_bom_add/__manifest__.py new file mode 100755 index 00000000..5c79f1aa --- /dev/null +++ b/mrp_bom_add/__manifest__.py @@ -0,0 +1,27 @@ +{ + 'name': 'BoM Mass Add', + 'author': 'Hibou Corp. ', + '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', + ], +} diff --git a/mrp_bom_add/security/ir.model.access.csv b/mrp_bom_add/security/ir.model.access.csv new file mode 100644 index 00000000..bf7d46d3 --- /dev/null +++ b/mrp_bom_add/security/ir.model.access.csv @@ -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 diff --git a/mrp_bom_add/tests/__init__.py b/mrp_bom_add/tests/__init__.py new file mode 100644 index 00000000..334c6444 --- /dev/null +++ b/mrp_bom_add/tests/__init__.py @@ -0,0 +1 @@ +from . import test_mrp_bom_add diff --git a/mrp_bom_add/tests/test_mrp_bom_add.py b/mrp_bom_add/tests/test_mrp_bom_add.py new file mode 100644 index 00000000..631862ff --- /dev/null +++ b/mrp_bom_add/tests/test_mrp_bom_add.py @@ -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) diff --git a/mrp_bom_add/wizard/__init__.py b/mrp_bom_add/wizard/__init__.py new file mode 100644 index 00000000..f578190b --- /dev/null +++ b/mrp_bom_add/wizard/__init__.py @@ -0,0 +1 @@ +from . import mrp_bom_add diff --git a/mrp_bom_add/wizard/mrp_bom_add.py b/mrp_bom_add/wizard/mrp_bom_add.py new file mode 100644 index 00000000..a7743cda --- /dev/null +++ b/mrp_bom_add/wizard/mrp_bom_add.py @@ -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}) diff --git a/mrp_bom_add/wizard/mrp_bom_add_views.xml b/mrp_bom_add/wizard/mrp_bom_add_views.xml new file mode 100644 index 00000000..123d090a --- /dev/null +++ b/mrp_bom_add/wizard/mrp_bom_add_views.xml @@ -0,0 +1,50 @@ + + + + + mrp.bom.add.form + mrp.bom.add + +
+ + + + + + + + + + + + + + + +
+
+
+
+
+ + + Add to Bill of Materials + mrp.bom.add + tree,form + + new + + + + mrp.bom.form.inherit + mrp.bom + + + +