1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
|
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.addons.mrp.tests.common import TestMrpCommon
from odoo.tests import Form
from odoo import Command
class TestMrpSerialMassProduce(TestMrpCommon):
def test_smp_serial(self):
"""Create a MO for a product not tracked by serial number.
The smp wizard should not open.
"""
mo = self.generate_mo()[0]
self.assertEqual(mo.state, 'confirmed')
res = mo.action_mass_produce()
self.assertFalse(res)
def test_smp_produce_all(self):
"""Create a MO for a product tracked by serial number.
Open the smp wizard, generate all serial numbers to produce all quantities.
"""
mo = self.generate_mo(tracking_final='serial')[0]
count = mo.product_qty
# Make some stock and reserve
for product in mo.move_raw_ids.product_id:
self.env['stock.quant'].with_context(inventory_mode=True).create({
'product_id': product.id,
'inventory_quantity': 100,
'location_id': mo.location_src_id.id,
})._apply_inventory()
mo.action_assign()
# Open the wizard
action = mo.action_mass_produce()
wizard_form = Form(self.env['mrp.batch.produce'].with_context(**action['context']))
# Let the wizard generate all serial numbers
wizard_form.lot_name = "sn#1"
wizard_form.lot_qty = count
wizard = wizard_form.save()
wizard.action_generate_production_text()
wizard.action_prepare()
# Initial MO should have a backorder-sequenced name and be in to_close state
self.assertTrue("-001" in mo.name)
self.assertEqual(mo.state, "to_close")
# Each generated serial number should have its own mo
self.assertEqual(len(mo.procurement_group_id.mrp_production_ids), count)
# Check generated serial numbers
self.assertEqual(mo.procurement_group_id.mrp_production_ids.lot_producing_id.mapped('name'), ["sn#1", "sn#2", "sn#3", "sn#4", "sn#5"])
def test_smp_produce_all_but_one(self):
"""Create a MO for a product tracked by serial number.
Open the smp wizard, generate all but one serial numbers and create a back order.
"""
mo = self.generate_mo(tracking_final='serial')[0]
count = mo.product_qty
# Make some stock and reserve
for product in mo.move_raw_ids.product_id:
self.env['stock.quant'].with_context(inventory_mode=True).create({
'product_id': product.id,
'inventory_quantity': 100,
'location_id': mo.location_src_id.id,
})._apply_inventory()
mo.action_assign()
action = mo.action_mass_produce()
wizard = Form(self.env['mrp.batch.produce'].with_context(**action['context']))
wizard.lot_name = "sn#1"
wizard.lot_qty = count - 1
wizard = wizard.save()
wizard.action_generate_production_text()
wizard.action_prepare()
# Last MO in sequence is the backorder
bo = mo.procurement_group_id.mrp_production_ids[-1]
self.assertEqual(bo.backorder_sequence, count)
self.assertEqual(bo.state, "confirmed")
def test_smp_produce_complex(self):
"""Create a MO for a product tracked by serial number
and with complex components (serial and multiple lots).
Open the smp wizard, generate all serial numbers to produce all quantities.
Check lot splitting.
"""
mo, dummy, dummy, product_to_use_1, product_to_use_2 = self.generate_mo(tracking_final='serial', tracking_base_1='lot', tracking_base_2='serial', qty_final=3, qty_base_1=2, qty_base_2=1)
count = mo.product_qty
# Make some stock and reserve
for _ in range(2): # 2 lots of 3 to satisfy the need and check lot splitting
lot = self.env['stock.lot'].create({
'product_id': product_to_use_1.id,
})
self.env['stock.quant'].with_context(inventory_mode=True).create({
'product_id': product_to_use_1.id,
'inventory_quantity': 3,
'location_id': mo.location_src_id.id,
'lot_id': lot.id,
})._apply_inventory()
for _ in range(3): # 3 serial numbers
lot = self.env['stock.lot'].create({
'product_id': product_to_use_2.id,
})
self.env['stock.quant'].with_context(inventory_mode=True).create({
'product_id': product_to_use_2.id,
'inventory_quantity': 1,
'location_id': mo.location_src_id.id,
'lot_id': lot.id,
})._apply_inventory()
mo.action_assign()
# Open the wizard
action = mo.action_mass_produce()
wizard = Form(self.env['mrp.batch.produce'].with_context(**action['context']))
# Let the wizard generate all serial numbers
wizard.lot_name = "sn#1"
wizard.lot_qty = count
wizard = wizard.save()
wizard.action_generate_production_text()
wizard.action_prepare()
# 1st & 2nd MO in sequence should have only 1 move lines (1 lot) for product_to_use_1 (2nd in bom)
self.assertEqual(mo.procurement_group_id.mrp_production_ids[0].move_raw_ids[1].move_lines_count, 1)
self.assertEqual(mo.procurement_group_id.mrp_production_ids[1].move_raw_ids[1].move_lines_count, 1)
# 3rd MO should have 2 move lines (2 different lots) for product_to_use_1
self.assertEqual(mo.procurement_group_id.mrp_production_ids[2].move_raw_ids[1].move_lines_count, 2)
# Verify mark as done
mos = mo.procurement_group_id.mrp_production_ids
mos.button_mark_done()
self.assertRecordValues(mos.lot_producing_id, [
{'product_qty': 1},
{'product_qty': 1},
{'product_qty': 1},
])
self.assertRecordValues(mos.move_finished_ids, [
{'picked': True},
{'picked': True},
{'picked': True},
])
self.assertRecordValues(mos.move_raw_ids, [
{'picked': True},
{'picked': True},
{'picked': True},
{'picked': True},
{'picked': True},
{'picked': True},
])
def test_mass_produce_with_tracked_product(self):
"""
Check that we can mass produce a tracked product.
"""
tracked_product = self.env['product.product'].create({
'name': 'Tracked Product',
'is_storable': True,
'tracking': 'serial',
})
component = self.env['product.product'].create({
'name': 'Component',
'is_storable': True,
})
byproduct = self.env['product.product'].create({
'name': 'Byproduct',
'is_storable': True,
})
# create a BoM
bom = self.env['mrp.bom'].create({
'product_tmpl_id': tracked_product.product_tmpl_id.id,
'product_qty': 1,
'bom_line_ids': [Command.create({
'product_id': component.id,
'product_qty': 1,
})],
'byproduct_ids': [Command.create({
'product_id': byproduct.id,
'product_qty': 1,
})],
})
sn_1 = self.env['stock.lot'].create({
'name': 'SN1',
'product_id': tracked_product.id,
})
sn_2 = self.env['stock.lot'].create({
'name': 'SN2',
'product_id': tracked_product.id,
})
self.env['stock.quant']._update_available_quantity(tracked_product, self.stock_location_14, 1, lot_id=sn_1)
self.env['stock.quant']._update_available_quantity(tracked_product, self.stock_location_14, 1, lot_id=sn_2)
self.env['stock.quant']._update_available_quantity(component, self.stock_location_14, 10)
# create an MO to use the tracked product available in stock
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = self.product_1
mo_form.product_qty = 2
mo_form.product_uom_id = component.uom_id
# use tracked as component
with mo_form.move_raw_ids.new() as move:
move.name = tracked_product.name
move.product_id = tracked_product
move.product_uom_qty = 2
move.product_uom = tracked_product.uom_id
mo = mo_form.save()
mo.action_confirm()
mo.action_assign()
mo.qty_producing = 2
mo.move_raw_ids.move_line_ids.write({'quantity': 1})
mo.button_mark_done()
self.assertEqual(mo.state, 'done')
# create a Mo to produce 2 units of tracked product
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = tracked_product
mo_form.bom_id = bom
mo_form.product_qty = 2
mo_form.product_uom_id = tracked_product.uom_id
mo = mo_form.save()
mo.action_confirm()
self.assertEqual(mo.state, 'confirmed')
mo.action_assign()
# Open the wizard
action = mo.action_mass_produce()
wizard = Form(self.env['mrp.batch.produce'].with_context(**action['context']))
# Let the wizard generate all serial numbers
wizard.lot_name = "sn#3"
wizard.lot_qty = 2
wizard = wizard.save()
wizard.action_generate_production_text()
# Reload the wizard to apply generated serial numbers
wizard.action_prepare()
# Initial MO should have a backorder-sequenced name and be in to_close state
self.assertTrue("-001" in mo.name)
self.assertEqual(mo.state, "to_close")
# Each generated serial number should have its own mo
self.assertEqual(len(mo.procurement_group_id.mrp_production_ids), 2)
# Check generated serial numbers
self.assertEqual(mo.procurement_group_id.mrp_production_ids.lot_producing_id.mapped('name'), ["sn#3", "sn#4"])
#check byproduct quantity
self.assertEqual(mo.procurement_group_id.mrp_production_ids.move_byproduct_ids.mapped('quantity'), [1, 1])
# check the component quantity
self.assertEqual(mo.procurement_group_id.mrp_production_ids.move_raw_ids.mapped('quantity'), [1, 1])
# Mark the MOs as done
mo.procurement_group_id.mrp_production_ids.move_raw_ids.picked = True
mo.procurement_group_id.mrp_production_ids.button_mark_done()
self.assertEqual(mo.procurement_group_id.mrp_production_ids.mapped('state'), ['done', 'done'])
def test_mass_produce_with_tracked_product_2(self):
"""
Check that the components are correclty consumed during mass
production of a tracked product.
"""
mo = self.generate_mo(tracking_final='serial', qty_final=3, qty_base_1=1, qty_base_2=1)[0]
comp1, comp2 = mo.move_raw_ids.mapped('product_id')
self.env['stock.quant']._update_available_quantity(comp1, mo.warehouse_id.lot_stock_id, 5)
# Generate an SN using the action_generate_serial
mo.action_generate_serial()
# In the end mass produce the SN's
action = mo.action_mass_produce()
wizard = Form(self.env['mrp.batch.produce'].with_context(**action['context']))
# Let the wizard generate all serial numbers
wizard.lot_name = "sn#5"
wizard.lot_qty = 3
wizard = wizard.save()
wizard.action_generate_production_text()
# Reload the wizard to apply generated serial numbers
wizard.action_prepare()
# Initial MO should have a backorder-sequenced name and be in to_close state
self.assertIn("-001", mo.name)
self.assertEqual(mo.state, "to_close")
# Each generated serial number should have its own mo
self.assertEqual(mo.procurement_group_id.mrp_production_ids.lot_producing_id.mapped('name'), ["sn#5", "sn#6", "sn#7"])
# check the component quantity
self.assertRecordValues(mo.procurement_group_id.mrp_production_ids.move_raw_ids, [
{'quantity': 1.0, 'picked': True},
{'quantity': 1.0, 'picked': True},
{'quantity': 1.0, 'picked': True},
{'quantity': 1.0, 'picked': True},
{'quantity': 1.0, 'picked': True},
{'quantity': 1.0, 'picked': True},
])
mo.procurement_group_id.mrp_production_ids.button_mark_done()
self.assertEqual(mo.procurement_group_id.mrp_production_ids.mapped('state'), ['done', 'done', 'done'])
self.assertEqual(comp1.qty_available, 2.0)
self.assertEqual(comp2.qty_available, -3.0)
def test_smp_produce_with_consumable_component(self):
"""Create a MO for a product tracked by serial number with a consumable component.
Open the smp wizard, You should be able to generate all serial numbers.
BoM:
- 1x final product (tracked by serial number)
components:
- 2 x (storable)
- 4 x (consumable)
- Create a MO with 12 final products to produce.
- update the component quantity to 100
"""
self.bom_1.product_id.uom_id = self.ref('uom.product_uom_unit')
self.bom_1.product_id.tracking = 'serial'
self.bom_1.product_qty = 1
mo_form = Form(self.env['mrp.production'])
mo_form.bom_id = self.bom_1
mo_form.product_qty = 12
mo = mo_form.save()
self.assertEqual(mo.move_raw_ids.mapped(lambda l: l.product_qty), [24, 48])
self.assertEqual(mo.move_raw_ids[1].product_id.type, 'consu', 'The second component should be consumable')
mo.move_raw_ids[1].product_uom_qty = 100
# Make some stock and reserve for storable component
self.env['stock.quant'].with_context(inventory_mode=True).create({
'product_id': mo.move_raw_ids[0].product_id.id,
'inventory_quantity': 24,
'location_id': mo.location_src_id.id,
})._apply_inventory()
mo.action_confirm()
self.assertEqual(mo.state, 'confirmed')
# Open the wizard
action = mo.button_mark_done()
wizard = Form(self.env['mrp.batch.produce'].with_context(**action['context']))
# Let the wizard generate all serial numbers
wizard.lot_name = "sn#1"
wizard.lot_qty = mo.product_qty
wizard = wizard.save()
wizard.action_generate_production_text()
wizard.action_prepare()
# Initial MO should have a backorder-sequenced name and be in to_close state
self.assertTrue("-001" in mo.name)
self.assertEqual(mo.state, "to_close")
# Each generated serial number should have its own mo
self.assertEqual(len(mo.procurement_group_id.mrp_production_ids), 12)
def test_smp_two_steps(self):
"""Create a MO for a product tracked by lot and with a component untracked and tracked by lot.
As the smp wizard should not open even if in two steps
"""
self.env['res.config.settings'].write({
'group_stock_adv_location': True,
})
self.env.ref('stock.warehouse0').manufacture_steps = 'pbm'
mo = self.generate_mo(tracking_final='lot', tracking_base_1='lot')[0]
# Make some stock and reserve
for product in mo.move_raw_ids.product_id:
self.env['stock.quant'].with_context(inventory_mode=True).create({
'product_id': product.id,
'inventory_quantity': 100,
'location_id': mo.location_src_id.id,
})._apply_inventory()
mo.action_assign()
action = mo.action_mass_produce()
self.assertEqual(action, None)
|