mirror of
https://github.com/OCA/manufacture.git
synced 2025-01-28 16:37:15 +02:00
369 lines
13 KiB
Python
369 lines
13 KiB
Python
# -*- coding: utf-8 -*-
|
|
# © 2016 Cyril Gaudin (Camptocamp)
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
|
|
from openerp import exceptions
|
|
|
|
from openerp.exceptions import UserError
|
|
from openerp.tests import TransactionCase
|
|
|
|
|
|
class TestBom(TransactionCase):
|
|
|
|
def setUp(self):
|
|
super(TestBom, self).setUp()
|
|
|
|
self.product_model = self.env['product.product']
|
|
self.bom_model = self.env['mrp.bom']
|
|
self.bom_line_model = self.env['mrp.bom.line']
|
|
self.mrp_production_model = self.env['mrp.production']
|
|
self.config_param_model = self.env['ir.config_parameter']
|
|
|
|
self.unit_uom = self.browse_ref('product.product_uom_unit')
|
|
self.dozen_uom = self.browse_ref('product.product_uom_dozen')
|
|
|
|
def check_result_and_load_entity(self, model_name, result, context=None):
|
|
entity_id = result.pop('res_id')
|
|
self.assertEqual({
|
|
'type': 'ir.actions.act_window',
|
|
'view_type': 'form',
|
|
'view_mode': 'form',
|
|
'res_model': model_name,
|
|
'target': 'current',
|
|
'context': context or self.env.context,
|
|
}, result)
|
|
|
|
return self.env[model_name].browse(entity_id)
|
|
|
|
def create_bom(self, product, qty=1, uom=None,
|
|
phantom=False, components=None):
|
|
bom = self.bom_model.create({
|
|
'product_tmpl_id': product.product_tmpl_id.id,
|
|
'product_id': product.id,
|
|
'product_qty': qty,
|
|
'product_uom': self.unit_uom.id if uom is None else uom.id,
|
|
'type': 'phantom' if phantom else 'normal',
|
|
})
|
|
|
|
if components:
|
|
for component in components:
|
|
self.create_bom_line(bom, component)
|
|
return bom
|
|
|
|
def create_bom_line(self, bom, component, qty=1, uom=None):
|
|
self.bom_line_model.create({
|
|
'bom_id': bom.id,
|
|
'product_id': component.id,
|
|
'product_qty': qty,
|
|
'product_uom': self.unit_uom.id if uom is None else uom.id,
|
|
})
|
|
|
|
def test_dismantling_no_components(self):
|
|
p1 = self.product_model.create({'name': 'Test P1'})
|
|
p2 = self.product_model.create({'name': 'Test P1'})
|
|
|
|
p1_bom = self.create_bom(p1)
|
|
|
|
with self.assertRaises(exceptions.UserError):
|
|
p1_bom.create_dismantling_bom()
|
|
|
|
self.create_bom_line(p1_bom, p2)
|
|
p1_bom.create_dismantling_bom()
|
|
|
|
def test_dismantling_on_dismantling_bom(self):
|
|
p1 = self.product_model.create({'name': 'Test P1'})
|
|
p2 = self.product_model.create({'name': 'Test P1'})
|
|
|
|
p1_bom = self.create_bom(p1, components=[p2])
|
|
p1_bom.write({
|
|
'dismantling': True,
|
|
'dismantled_product_id': p2.id
|
|
})
|
|
|
|
with self.assertRaises(exceptions.UserError):
|
|
p1_bom.create_dismantling_bom()
|
|
|
|
def test_dismantling_bom_no_product_id__multiple_vaiants(self):
|
|
p1 = self.product_model.create({'name': 'Test P1'})
|
|
p1_var = self.product_model.create({
|
|
'product_tmpl_id': p1.product_tmpl_id.id,
|
|
})
|
|
p2 = self.product_model.create({'name': 'Test P2'})
|
|
|
|
# P1 BoM: Need one P2
|
|
p1_bom = self.bom_model.create({
|
|
'product_tmpl_id': p1.product_tmpl_id.id,
|
|
'product_id': None,
|
|
})
|
|
self.create_bom_line(p1_bom, p2)
|
|
|
|
# No variant specified (and template has multiple variants)
|
|
with self.assertRaises(exceptions.UserError):
|
|
p1_bom.create_dismantling_bom()
|
|
|
|
# Variant specified
|
|
p1_bom.product_id = p1_var
|
|
result = p1_bom.create_dismantling_bom()
|
|
self.check_result_and_load_entity('mrp.bom', result)
|
|
|
|
def test_dismantling_bom_no_product_id(self):
|
|
# Same tests but BoM only have a product_tmpl_id, no product_id
|
|
# (Seems to be the standard case)
|
|
|
|
p1 = self.product_model.create({'name': 'Test P1'})
|
|
p2 = self.product_model.create({'name': 'Test P2'})
|
|
|
|
# P1 BoM: Need one P2
|
|
p1_bom = self.bom_model.create({
|
|
'product_tmpl_id': p1.product_tmpl_id.id,
|
|
'product_id': None,
|
|
})
|
|
self.create_bom_line(p1_bom, p2)
|
|
|
|
result = p1_bom.create_dismantling_bom()
|
|
|
|
dmtl_bom = self.check_result_and_load_entity('mrp.bom', result)
|
|
self.assertEqual(p2.id, dmtl_bom.product_id.id)
|
|
self.assertEqual(True, dmtl_bom.dismantling)
|
|
self.assertEqual(p1.id, dmtl_bom.dismantled_product_id.id)
|
|
self.assertEqual(p2.product_tmpl_id.id, dmtl_bom.product_tmpl_id.id)
|
|
|
|
# Consume p1
|
|
self.assertEqual(1, len(dmtl_bom.bom_line_ids))
|
|
self.assertEqual(p1.id, dmtl_bom.bom_line_ids[0].product_id.id)
|
|
|
|
def test_dismantling_simple_case(self):
|
|
p1 = self.product_model.create({'name': 'Test P1'})
|
|
p2 = self.product_model.create({'name': 'Test P2'})
|
|
p3 = self.product_model.create({'name': 'Test P3'})
|
|
|
|
# P1 BoM: Need one P2 and one P3
|
|
p1_bom = self.create_bom(p1, components=[p2, p3])
|
|
|
|
result = p1_bom.create_dismantling_bom()
|
|
|
|
dmtl_bom = self.check_result_and_load_entity('mrp.bom', result)
|
|
self.assertEqual(p2.id, dmtl_bom.product_id.id)
|
|
self.assertEqual(p2.product_tmpl_id.id, dmtl_bom.product_tmpl_id.id)
|
|
self.assertEqual(True, dmtl_bom.dismantling)
|
|
self.assertEqual(p1.id, dmtl_bom.dismantled_product_id.id)
|
|
|
|
# Consume p1
|
|
self.assertEqual(1, len(dmtl_bom.bom_line_ids))
|
|
self.assertEqual(p1.id, dmtl_bom.bom_line_ids[0].product_id.id)
|
|
|
|
# P3 in by-products
|
|
self.assertEqual(1, len(dmtl_bom.sub_products))
|
|
self.assertEqual(p3.id, dmtl_bom.sub_products[0].product_id.id)
|
|
|
|
def test_phantom_bom(self):
|
|
p1 = self.product_model.create({'name': 'Test P1'})
|
|
p2 = self.product_model.create({'name': 'Test P2'})
|
|
sub_p2 = self.product_model.create({'name': 'Test sub P2'})
|
|
p3 = self.product_model.create({'name': 'Test P3'})
|
|
sub_p3 = self.product_model.create({'name': 'Test sub P3'})
|
|
|
|
# P1 BoM: Need one P2 and one P3
|
|
# P2 has a phantom BoM which need one sub P2
|
|
# P3 has a normal Bom which need one sub p3
|
|
p1_bom = self.create_bom(p1, components=[p2, p3])
|
|
self.create_bom(p2, phantom=True, components=[sub_p2])
|
|
self.create_bom(p3, components=[sub_p3])
|
|
|
|
result = p1_bom.create_dismantling_bom()
|
|
|
|
dmtl_bom = self.check_result_and_load_entity('mrp.bom', result)
|
|
self.assertEqual(sub_p2.id, dmtl_bom.product_id.id)
|
|
self.assertEqual(sub_p2.product_tmpl_id.id,
|
|
dmtl_bom.product_tmpl_id.id)
|
|
self.assertEqual(True, dmtl_bom.dismantling)
|
|
self.assertEqual(p1.id, dmtl_bom.dismantled_product_id.id)
|
|
|
|
# Consume p1self.assertEqual(1, len(dmtl_bom.bom_line_ids))
|
|
self.assertEqual(p1.id, dmtl_bom.bom_line_ids[0].product_id.id)
|
|
|
|
# Sub P3 in by-products
|
|
self.assertEqual(1, len(dmtl_bom.sub_products))
|
|
self.assertEqual(p3.id, dmtl_bom.sub_products[0].product_id.id)
|
|
|
|
def test_multi_unit_components(self):
|
|
p1 = self.product_model.create({'name': 'Test P1'})
|
|
p2 = self.product_model.create({'name': 'Test P2'})
|
|
p3 = self.product_model.create({'name': 'Test P3'})
|
|
p4 = self.product_model.create({'name': 'Test P4'})
|
|
|
|
# P1 BoM (produced 1 dozen): Needs 2 P2, 4 P3 and 2 dozen P4
|
|
# P2 has a phantom BoM which need one Dozen of P3
|
|
#
|
|
# => Dismantling BoM:
|
|
# Product: P3 (produced 28 unit)
|
|
# Component: 1 dozen P1
|
|
|
|
p1_bom = self.create_bom(p1, qty=1, uom=self.dozen_uom)
|
|
self.create_bom_line(p1_bom, p2, qty=2)
|
|
self.create_bom_line(p1_bom, p3, qty=4)
|
|
self.create_bom_line(p1_bom, p4, qty=2, uom=self.dozen_uom)
|
|
|
|
p2_bom = self.create_bom(p2, phantom=True)
|
|
self.create_bom_line(p2_bom, p3, qty=1, uom=self.dozen_uom)
|
|
|
|
result = p1_bom.create_dismantling_bom()
|
|
|
|
dmtl_bom = self.check_result_and_load_entity('mrp.bom', result)
|
|
self.assertEqual(p3.id, dmtl_bom.product_id.id)
|
|
self.assertEqual(28, dmtl_bom.product_qty)
|
|
self.assertEqual(self.unit_uom, dmtl_bom.product_uom)
|
|
self.assertEqual(True, dmtl_bom.dismantling)
|
|
self.assertEqual(p1.id, dmtl_bom.dismantled_product_id.id)
|
|
|
|
# Consume 1 dozen p1
|
|
self.assertEqual(1, len(dmtl_bom.bom_line_ids))
|
|
|
|
dmtl_bom_line = dmtl_bom.bom_line_ids[0]
|
|
self.assertEqual(p1.id, dmtl_bom_line.product_id.id)
|
|
self.assertEqual(1, dmtl_bom_line.product_qty)
|
|
self.assertEqual(self.dozen_uom, dmtl_bom_line.product_uom)
|
|
|
|
# Byproducts
|
|
self.assertEqual(1, len(dmtl_bom.sub_products))
|
|
|
|
dmtl_sub_product = dmtl_bom.sub_products[0]
|
|
self.assertEqual(p4.id, dmtl_sub_product.product_id.id)
|
|
self.assertEqual(24, dmtl_sub_product.product_qty)
|
|
self.assertEqual(self.unit_uom, dmtl_sub_product.product_uom)
|
|
|
|
def test_create_mrp_production(self):
|
|
p1 = self.product_model.create({'name': 'Test P1'})
|
|
p2 = self.product_model.create({'name': 'Test P2'})
|
|
bom = self.create_bom(p1, qty=2, uom=self.dozen_uom, components=[p2])
|
|
|
|
self.assertEqual(
|
|
0,
|
|
self.mrp_production_model.search_count([('bom_id', '=', bom.id)])
|
|
)
|
|
|
|
result = bom.create_mrp_production()
|
|
mrp_prod = self.check_result_and_load_entity('mrp.production', result)
|
|
|
|
self.assertEqual(bom, mrp_prod.bom_id)
|
|
self.assertEqual(p1, mrp_prod.product_id)
|
|
self.assertEqual(2, mrp_prod.product_qty)
|
|
self.assertEqual(2, mrp_prod.product_qty)
|
|
self.assertEqual(self.dozen_uom, mrp_prod.product_uom)
|
|
|
|
def test_action_create_dismantling_bom(self):
|
|
# Set component automatically choosen.
|
|
self.config_param_model.set_param(
|
|
'mrp.bom.dismantling.product_choice', False
|
|
)
|
|
|
|
p1 = self.product_model.create({'name': 'Test P1'})
|
|
p2 = self.product_model.create({'name': 'Test P2'})
|
|
|
|
p1_bom = self.create_bom(p1, components=[p2])
|
|
|
|
dismantled_p2_domain = [
|
|
('product_id', '=', p2.id),
|
|
('dismantling', '=', True),
|
|
]
|
|
|
|
# Non dismantling bom
|
|
self.assertEqual(0, self.bom_model.search_count(dismantled_p2_domain))
|
|
|
|
result = p1_bom.action_create_dismantling_bom()
|
|
self.assertEqual(1, self.bom_model.search_count(dismantled_p2_domain))
|
|
|
|
dmtl_bom = self.check_result_and_load_entity('mrp.bom', result)
|
|
self.assertEqual(p2.id, dmtl_bom.product_id.id)
|
|
self.assertEqual(True, dmtl_bom.dismantling)
|
|
|
|
# Component must be choose by user
|
|
self.config_param_model.set_param(
|
|
'mrp.bom.dismantling.product_choice', '1'
|
|
)
|
|
|
|
result = p1_bom.action_create_dismantling_bom()
|
|
|
|
# No new dismantling bom created
|
|
self.assertEqual(1, self.bom_model.search_count(dismantled_p2_domain))
|
|
|
|
# Response opened wizard
|
|
self.assertEqual({
|
|
'type': 'ir.actions.act_window',
|
|
'name': 'Choose main compoment',
|
|
'view_type': 'form',
|
|
'view_mode': 'form',
|
|
'res_model': 'mrp.bom.dismantling_product_choice',
|
|
'target': 'new',
|
|
'context': self.env.context
|
|
}, result)
|
|
|
|
def test_res_config(self):
|
|
# Coverage test for res_config methods
|
|
self.config_param_model.set_param(
|
|
'mrp.bom.dismantling.product_choice', None
|
|
)
|
|
|
|
mrp_config = self.env['mrp.config.settings'].create({
|
|
# Bypass default_get bug: https://github.com/odoo/odoo/pull/10373
|
|
'group_product_variant': 0
|
|
})
|
|
self.assertEqual(
|
|
False, mrp_config.read(
|
|
['dismantling_product_choice']
|
|
)[0]['dismantling_product_choice']
|
|
)
|
|
|
|
mrp_config.write({'dismantling_product_choice': 1})
|
|
mrp_config.execute()
|
|
|
|
self.assertEqual('1', self.config_param_model.get_param(
|
|
'mrp.bom.dismantling.product_choice'
|
|
))
|
|
|
|
def test_product_choice_wizard(self):
|
|
wizard_model = self.env['mrp.bom.dismantling_product_choice']
|
|
|
|
p1 = self.product_model.create({'name': 'Test P1'})
|
|
p2 = self.product_model.create({'name': 'Test P2'})
|
|
p3 = self.product_model.create({'name': 'Test P3'})
|
|
|
|
bom = self.create_bom(p1)
|
|
|
|
# No active ID
|
|
with self.assertRaises(KeyError):
|
|
wizard_model.create({})
|
|
|
|
# Cannot really test full workflow => call methods manually.
|
|
wizard = wizard_model.with_context(active_id=bom.id).new({})
|
|
self.assertEqual(bom, wizard._get_bom_id())
|
|
|
|
wizard.bom_id = bom
|
|
|
|
# No component
|
|
with self.assertRaises(UserError):
|
|
wizard.on_change_bom_id()
|
|
|
|
self.create_bom_line(bom, p2)
|
|
self.create_bom_line(bom, p3)
|
|
|
|
bom.refresh()
|
|
wizard.bom_id = bom
|
|
result = wizard.on_change_bom_id()
|
|
self.assertEqual({
|
|
'domain': {
|
|
'component_id': [('id', 'in', [p2.id, p3.id])],
|
|
}
|
|
}, result)
|
|
|
|
wizard.component_id = p3
|
|
wizard.write({})
|
|
result = wizard.create_bom()
|
|
|
|
# Dismantling BOM main product is P3
|
|
dmtl_bom = self.check_result_and_load_entity(
|
|
'mrp.bom', result, context={'active_id': bom.id}
|
|
)
|
|
self.assertEqual(p3.id, dmtl_bom.product_id.id)
|
|
self.assertEqual(True, dmtl_bom.dismantling)
|