Files
odoo_source/addons/mrp/tests/test_backorder.py
T

405 lines
20 KiB
Python

# -*- 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.tests.common import TransactionCase
class TestMrpProductionBackorder(TestMrpCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.stock_location = cls.env.ref('stock.stock_location_stock')
warehouse_form = Form(cls.env['stock.warehouse'])
warehouse_form.name = 'Test Warehouse'
warehouse_form.code = 'TWH'
cls.warehouse = warehouse_form.save()
def test_no_tracking_1(self):
"""Create a MO for 4 product. Produce 4. The backorder button should
not appear and hitting mark as done should not open the backorder wizard.
The name of the MO should be MO/001.
"""
mo = self.generate_mo(qty_final=4)[0]
mo_form = Form(mo)
mo_form.qty_producing = 4
mo = mo_form.save()
# No backorder is proposed
self.assertTrue(mo.button_mark_done())
self.assertEqual(mo._get_quantity_to_backorder(), 0)
self.assertTrue("-001" not in mo.name)
def test_no_tracking_2(self):
"""Create a MO for 4 product. Produce 1. The backorder button should
appear and hitting mark as done should open the backorder wizard. In the backorder
wizard, choose to do the backorder. A new MO for 3 self.untracked_bom should be
created.
The sequence of the first MO should be MO/001-01, the sequence of the second MO
should be MO/001-02.
Check that all MO are reachable through the procurement group.
"""
production, _, _, product_to_use_1, _ = self.generate_mo(qty_final=4, qty_base_1=3)
self.assertEqual(production.state, 'confirmed')
self.assertEqual(production.reserve_visible, True)
# Make some stock and reserve
for product in production.move_raw_ids.product_id:
self.env['stock.quant'].with_context(inventory_mode=True).create({
'product_id': product.id,
'inventory_quantity': 100,
'location_id': production.location_src_id.id,
})._apply_inventory()
production.action_assign()
self.assertEqual(production.state, 'confirmed')
self.assertEqual(production.reserve_visible, False)
mo_form = Form(production)
mo_form.qty_producing = 1
production = mo_form.save()
action = production.button_mark_done()
backorder = Form(self.env['mrp.production.backorder'].with_context(**action['context']))
backorder.save().action_backorder()
# Two related MO to the procurement group
self.assertEqual(len(production.procurement_group_id.mrp_production_ids), 2)
# Check MO backorder
mo_backorder = production.procurement_group_id.mrp_production_ids[-1]
self.assertEqual(mo_backorder.product_id.id, production.product_id.id)
self.assertEqual(mo_backorder.product_qty, 3)
self.assertEqual(sum(mo_backorder.move_raw_ids.filtered(lambda m: m.product_id.id == product_to_use_1.id).mapped("product_uom_qty")), 9)
self.assertEqual(mo_backorder.reserve_visible, False) # the reservation of the first MO should've been moved here
def test_no_tracking_pbm_1(self):
"""Create a MO for 4 product. Produce 1. The backorder button should
appear and hitting mark as done should open the backorder wizard. In the backorder
wizard, choose to do the backorder. A new MO for 3 self.untracked_bom should be
created.
The sequence of the first MO should be MO/001-01, the sequence of the second MO
should be MO/001-02.
Check that all MO are reachable through the procurement group.
"""
with Form(self.warehouse) as warehouse:
warehouse.manufacture_steps = 'pbm'
production, _, product_to_build, product_to_use_1, product_to_use_2 = self.generate_mo(qty_base_1=4, qty_final=4, picking_type_id=self.warehouse.manu_type_id)
move_raw_ids = production.move_raw_ids
self.assertEqual(len(move_raw_ids), 2)
self.assertEqual(set(move_raw_ids.mapped("product_id")), {product_to_use_1, product_to_use_2})
pbm_move = move_raw_ids.move_orig_ids
self.assertEqual(len(pbm_move), 2)
self.assertEqual(set(pbm_move.mapped("product_id")), {product_to_use_1, product_to_use_2})
self.assertFalse(pbm_move.move_orig_ids)
mo_form = Form(production)
mo_form.qty_producing = 1
production = mo_form.save()
self.assertEqual(sum(pbm_move.filtered(lambda m: m.product_id.id == product_to_use_1.id).mapped("product_qty")), 16)
self.assertEqual(sum(pbm_move.filtered(lambda m: m.product_id.id == product_to_use_2.id).mapped("product_qty")), 4)
action = production.button_mark_done()
backorder = Form(self.env['mrp.production.backorder'].with_context(**action['context']))
backorder.save().action_backorder()
mo_backorder = production.procurement_group_id.mrp_production_ids[-1]
self.assertEqual(mo_backorder.delivery_count, 1)
pbm_move |= mo_backorder.move_raw_ids.move_orig_ids
# Check that quantity is correct
self.assertEqual(sum(pbm_move.filtered(lambda m: m.product_id.id == product_to_use_1.id).mapped("product_qty")), 16)
self.assertEqual(sum(pbm_move.filtered(lambda m: m.product_id.id == product_to_use_2.id).mapped("product_qty")), 4)
self.assertFalse(pbm_move.move_orig_ids)
def test_no_tracking_pbm_sam_1(self):
"""Create a MO for 4 product. Produce 1. The backorder button should
appear and hitting mark as done should open the backorder wizard. In the backorder
wizard, choose to do the backorder. A new MO for 3 self.untracked_bom should be
created.
The sequence of the first MO should be MO/001-01, the sequence of the second MO
should be MO/001-02.
Check that all MO are reachable through the procurement group.
"""
with Form(self.warehouse) as warehouse:
warehouse.manufacture_steps = 'pbm_sam'
production, _, product_to_build, product_to_use_1, product_to_use_2 = self.generate_mo(qty_base_1=4, qty_final=4, picking_type_id=self.warehouse.manu_type_id)
move_raw_ids = production.move_raw_ids
self.assertEqual(len(move_raw_ids), 2)
self.assertEqual(set(move_raw_ids.mapped("product_id")), {product_to_use_1, product_to_use_2})
pbm_move = move_raw_ids.move_orig_ids
self.assertEqual(len(pbm_move), 2)
self.assertEqual(set(pbm_move.mapped("product_id")), {product_to_use_1, product_to_use_2})
self.assertFalse(pbm_move.move_orig_ids)
self.assertEqual(sum(pbm_move.filtered(lambda m: m.product_id.id == product_to_use_1.id).mapped("product_qty")), 16)
self.assertEqual(sum(pbm_move.filtered(lambda m: m.product_id.id == product_to_use_2.id).mapped("product_qty")), 4)
sam_move = production.move_finished_ids.move_dest_ids
self.assertEqual(len(sam_move), 1)
self.assertEqual(sam_move.product_id.id, product_to_build.id)
self.assertEqual(sum(sam_move.mapped("product_qty")), 4)
mo_form = Form(production)
mo_form.qty_producing = 1
production = mo_form.save()
action = production.button_mark_done()
backorder = Form(self.env['mrp.production.backorder'].with_context(**action['context']))
backorder.save().action_backorder()
mo_backorder = production.procurement_group_id.mrp_production_ids[-1]
self.assertEqual(mo_backorder.delivery_count, 2)
pbm_move |= mo_backorder.move_raw_ids.move_orig_ids
self.assertEqual(sum(pbm_move.filtered(lambda m: m.product_id.id == product_to_use_1.id).mapped("product_qty")), 16)
self.assertEqual(sum(pbm_move.filtered(lambda m: m.product_id.id == product_to_use_2.id).mapped("product_qty")), 4)
sam_move |= mo_backorder.move_finished_ids.move_orig_ids
self.assertEqual(sum(sam_move.mapped("product_qty")), 4)
def test_tracking_backorder_series_lot_1(self):
""" Create a MO of 4 tracked products. all component is tracked by lots
Produce one by one with one bakorder for each until end.
"""
nb_product_todo = 4
production, _, p_final, p1, p2 = self.generate_mo(qty_final=nb_product_todo, tracking_final='lot', tracking_base_1='lot', tracking_base_2='lot')
lot_final = self.env['stock.lot'].create({
'name': 'lot_final',
'product_id': p_final.id,
'company_id': self.env.company.id,
})
lot_1 = self.env['stock.lot'].create({
'name': 'lot_consumed_1',
'product_id': p1.id,
'company_id': self.env.company.id,
})
lot_2 = self.env['stock.lot'].create({
'name': 'lot_consumed_2',
'product_id': p2.id,
'company_id': self.env.company.id,
})
self.env['stock.quant']._update_available_quantity(p1, self.stock_location, nb_product_todo*4, lot_id=lot_1)
self.env['stock.quant']._update_available_quantity(p2, self.stock_location, nb_product_todo, lot_id=lot_2)
production.action_assign()
active_production = production
for i in range(nb_product_todo):
details_operation_form = Form(active_production.move_raw_ids.filtered(lambda m: m.product_id == p1), view=self.env.ref('stock.view_stock_move_operations'))
with details_operation_form.move_line_ids.edit(0) as ml:
ml.qty_done = 4
ml.lot_id = lot_1
details_operation_form.save()
details_operation_form = Form(active_production.move_raw_ids.filtered(lambda m: m.product_id == p2), view=self.env.ref('stock.view_stock_move_operations'))
with details_operation_form.move_line_ids.edit(0) as ml:
ml.qty_done = 1
ml.lot_id = lot_2
details_operation_form.save()
production_form = Form(active_production)
production_form.qty_producing = 1
production_form.lot_producing_id = lot_final
active_production = production_form.save()
active_production.button_mark_done()
if i + 1 != nb_product_todo: # If last MO, don't make a backorder
action = active_production.button_mark_done()
backorder = Form(self.env['mrp.production.backorder'].with_context(**action['context']))
backorder.save().action_backorder()
active_production = active_production.procurement_group_id.mrp_production_ids[-1]
self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location, lot_id=lot_final), nb_product_todo, f'You should have the {nb_product_todo} final product in stock')
self.assertEqual(len(production.procurement_group_id.mrp_production_ids), nb_product_todo)
def test_tracking_backorder_series_serial_1(self):
""" Create a MO of 4 tracked products (serial) with pbm_sam.
all component is tracked by serial
Produce one by one with one bakorder for each until end.
"""
nb_product_todo = 4
production, _, p_final, p1, p2 = self.generate_mo(qty_final=nb_product_todo, tracking_final='serial', tracking_base_1='serial', tracking_base_2='serial', qty_base_1=1)
serials_final, serials_p1, serials_p2 = [], [], []
for i in range(nb_product_todo):
serials_final.append(self.env['stock.lot'].create({
'name': f'lot_final_{i}',
'product_id': p_final.id,
'company_id': self.env.company.id,
}))
serials_p1.append(self.env['stock.lot'].create({
'name': f'lot_consumed_1_{i}',
'product_id': p1.id,
'company_id': self.env.company.id,
}))
serials_p2.append(self.env['stock.lot'].create({
'name': f'lot_consumed_2_{i}',
'product_id': p2.id,
'company_id': self.env.company.id,
}))
self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 1, lot_id=serials_p1[-1])
self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 1, lot_id=serials_p2[-1])
production.action_assign()
active_production = production
for i in range(nb_product_todo):
details_operation_form = Form(active_production.move_raw_ids.filtered(lambda m: m.product_id == p1), view=self.env.ref('stock.view_stock_move_operations'))
with details_operation_form.move_line_ids.edit(0) as ml:
ml.qty_done = 1
ml.lot_id = serials_p1[i]
details_operation_form.save()
details_operation_form = Form(active_production.move_raw_ids.filtered(lambda m: m.product_id == p2), view=self.env.ref('stock.view_stock_move_operations'))
with details_operation_form.move_line_ids.edit(0) as ml:
ml.qty_done = 1
ml.lot_id = serials_p2[i]
details_operation_form.save()
production_form = Form(active_production)
production_form.qty_producing = 1
production_form.lot_producing_id = serials_final[i]
active_production = production_form.save()
active_production.button_mark_done()
if i + 1 != nb_product_todo: # If last MO, don't make a backorder
action = active_production.button_mark_done()
backorder = Form(self.env['mrp.production.backorder'].with_context(**action['context']))
backorder.save().action_backorder()
active_production = active_production.procurement_group_id.mrp_production_ids[-1]
self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location), nb_product_todo, f'You should have the {nb_product_todo} final product in stock')
self.assertEqual(len(production.procurement_group_id.mrp_production_ids), nb_product_todo)
def test_tracking_backorder_immediate_production_serial_1(self):
""" Create a MO to build 2 of a SN tracked product.
Build both the starting MO and its backorder as immediate productions
(i.e. Mark As Done without setting SN/filling any quantities)
"""
mo, _, p_final, p1, p2 = self.generate_mo(qty_final=2, tracking_final='serial', qty_base_1=2, qty_base_2=2)
self.env['stock.quant']._update_available_quantity(p1, self.stock_location_components, 2.0)
self.env['stock.quant']._update_available_quantity(p2, self.stock_location_components, 2.0)
mo.action_assign()
res_dict = mo.button_mark_done()
self.assertEqual(res_dict.get('res_model'), 'mrp.immediate.production')
immediate_wizard = Form(self.env[res_dict['res_model']].with_context(res_dict['context'])).save()
res_dict = immediate_wizard.process()
self.assertEqual(res_dict.get('res_model'), 'mrp.production.backorder')
backorder_wizard = Form(self.env[res_dict['res_model']].with_context(res_dict['context']))
# backorder should automatically open
action = backorder_wizard.save().action_backorder()
self.assertEqual(action.get('res_model'), 'mrp.production')
backorder_mo_form = Form(self.env[action['res_model']].with_context(action['context']).browse(action['res_id']))
backorder_mo = backorder_mo_form.save()
res_dict = backorder_mo.button_mark_done()
self.assertEqual(res_dict.get('res_model'), 'mrp.immediate.production')
immediate_wizard = Form(self.env[res_dict['res_model']].with_context(res_dict['context'])).save()
immediate_wizard.process()
self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location), 2, "Incorrect number of final product produced.")
self.assertEqual(len(self.env['stock.lot'].search([('product_id', '=', p_final.id)])), 2, "Serial Numbers were not correctly produced.")
def test_backorder_name(self):
def produce_one(mo):
mo_form = Form(mo)
mo_form.qty_producing = 1
mo = mo_form.save()
action = mo.button_mark_done()
backorder = Form(self.env['mrp.production.backorder'].with_context(**action['context']))
backorder.save().action_backorder()
return mo.procurement_group_id.mrp_production_ids[-1]
default_picking_type_id = self.env['mrp.production']._get_default_picking_type()
default_picking_type = self.env['stock.picking.type'].browse(default_picking_type_id)
mo_sequence = default_picking_type.sequence_id
mo_sequence.prefix = "WH-MO-"
initial_mo_name = mo_sequence.prefix + str(mo_sequence.number_next_actual).zfill(mo_sequence.padding)
production = self.generate_mo(qty_final=5)[0]
self.assertEqual(production.name, initial_mo_name)
backorder = produce_one(production)
self.assertEqual(production.name, initial_mo_name + "-001")
self.assertEqual(backorder.name, initial_mo_name + "-002")
backorder.backorder_sequence = 998
for seq in [998, 999, 1000]:
new_backorder = produce_one(backorder)
self.assertEqual(backorder.name, initial_mo_name + "-" + str(seq))
self.assertEqual(new_backorder.name, initial_mo_name + "-" + str(seq + 1))
backorder = new_backorder
def test_backorder_name_without_procurement_group(self):
production = self.generate_mo(qty_final=5)[0]
mo_form = Form(production)
mo_form.qty_producing = 1
mo = mo_form.save()
# Remove pg to trigger fallback on backorder name
mo.procurement_group_id = False
action = mo.button_mark_done()
backorder_form = Form(self.env['mrp.production.backorder'].with_context(**action['context']))
backorder_form.save().action_backorder()
# The pg is back
self.assertTrue(production.procurement_group_id)
backorder_ids = production.procurement_group_id.mrp_production_ids[1]
self.assertEqual(production.name.split('-')[0], backorder_ids.name.split('-')[0])
self.assertEqual(int(production.name.split('-')[1]) + 1, int(backorder_ids.name.split('-')[1]))
class TestMrpWorkorderBackorder(TransactionCase):
@classmethod
def setUpClass(cls):
super(TestMrpWorkorderBackorder, cls).setUpClass()
cls.uom_unit = cls.env['uom.uom'].search([
('category_id', '=', cls.env.ref('uom.product_uom_categ_unit').id),
('uom_type', '=', 'reference')
], limit=1)
cls.finished1 = cls.env['product.product'].create({
'name': 'finished1',
'type': 'product',
})
cls.compfinished1 = cls.env['product.product'].create({
'name': 'compfinished1',
'type': 'product',
})
cls.compfinished2 = cls.env['product.product'].create({
'name': 'compfinished2',
'type': 'product',
})
cls.workcenter1 = cls.env['mrp.workcenter'].create({
'name': 'workcenter1',
})
cls.workcenter2 = cls.env['mrp.workcenter'].create({
'name': 'workcenter2',
})
cls.bom_finished1 = cls.env['mrp.bom'].create({
'product_id': cls.finished1.id,
'product_tmpl_id': cls.finished1.product_tmpl_id.id,
'product_uom_id': cls.uom_unit.id,
'product_qty': 1,
'consumption': 'flexible',
'type': 'normal',
'bom_line_ids': [
(0, 0, {'product_id': cls.compfinished1.id, 'product_qty': 1}),
(0, 0, {'product_id': cls.compfinished2.id, 'product_qty': 1}),
],
'operation_ids': [
(0, 0, {'sequence': 1, 'name': 'finished operation 1', 'workcenter_id': cls.workcenter1.id}),
(0, 0, {'sequence': 2, 'name': 'finished operation 2', 'workcenter_id': cls.workcenter2.id}),
],
})
cls.bom_finished1.bom_line_ids[0].operation_id = cls.bom_finished1.operation_ids[0].id
cls.bom_finished1.bom_line_ids[1].operation_id = cls.bom_finished1.operation_ids[1].id