# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo.tests import Form from datetime import datetime, timedelta from odoo.fields import Datetime as Dt from odoo.exceptions import UserError from odoo.addons.mrp.tests.common import TestMrpCommon class TestMrpOrder(TestMrpCommon): def test_access_rights_manager(self): """ Checks an MRP manager can create, confirm and cancel a manufacturing order. """ man_order_form = Form(self.env['mrp.production'].sudo(self.user_mrp_manager)) man_order_form.product_id = self.product_4 man_order_form.product_qty = 5.0 man_order_form.bom_id = self.bom_1 man_order_form.location_src_id = self.location_1 man_order_form.location_dest_id = self.warehouse_1.wh_output_stock_loc_id man_order = man_order_form.save() man_order.action_confirm() man_order.action_cancel() self.assertEqual(man_order.state, 'cancel', "Production order should be in cancel state.") man_order.unlink() def test_access_rights_user(self): """ Checks an MRP user can create, confirm and cancel a manufacturing order. """ man_order_form = Form(self.env['mrp.production'].sudo(self.user_mrp_user)) man_order_form.product_id = self.product_4 man_order_form.product_qty = 5.0 man_order_form.bom_id = self.bom_1 man_order_form.location_src_id = self.location_1 man_order_form.location_dest_id = self.warehouse_1.wh_output_stock_loc_id man_order = man_order_form.save() man_order.action_confirm() man_order.action_cancel() self.assertEqual(man_order.state, 'cancel', "Production order should be in cancel state.") man_order.unlink() def test_basic(self): """ Checks a basic manufacturing order: no routing (thus no workorders), no lot and consume strictly what's needed. """ self.product_1.type = 'product' self.product_2.type = 'product' inventory = self.env['stock.inventory'].create({ 'name': 'Initial inventory', 'filter': 'partial', 'line_ids': [(0, 0, { 'product_id': self.product_1.id, 'product_uom_id': self.product_1.uom_id.id, 'product_qty': 500, 'location_id': self.warehouse_1.lot_stock_id.id }), (0, 0, { 'product_id': self.product_2.id, 'product_uom_id': self.product_2.uom_id.id, 'product_qty': 500, 'location_id': self.warehouse_1.lot_stock_id.id })] }) inventory.action_validate() test_date_planned = Dt.now() - timedelta(days=1) test_quantity = 2.0 self.bom_1.routing_id = False man_order_form = Form(self.env['mrp.production'].sudo(self.user_mrp_user)) man_order_form.product_id = self.product_4 man_order_form.bom_id = self.bom_1 man_order_form.product_uom_id = self.product_4.uom_id man_order_form.product_qty = test_quantity man_order_form.date_planned_start = test_date_planned man_order_form.location_src_id = self.location_1 man_order_form.location_dest_id = self.warehouse_1.wh_output_stock_loc_id man_order = man_order_form.save() self.assertEqual(man_order.state, 'draft', "Production order should be in draft state.") man_order.action_confirm() self.assertEqual(man_order.state, 'confirmed', "Production order should be in confirmed state.") # check production move production_move = man_order.move_finished_ids self.assertEqual(production_move.date, test_date_planned) self.assertEqual(production_move.product_id, self.product_4) self.assertEqual(production_move.product_uom, man_order.product_uom_id) self.assertEqual(production_move.product_qty, man_order.product_qty) self.assertEqual(production_move.location_id, self.product_4.property_stock_production) self.assertEqual(production_move.location_dest_id, man_order.location_dest_id) # check consumption moves for move in man_order.move_raw_ids: self.assertEqual(move.date, test_date_planned) first_move = man_order.move_raw_ids.filtered(lambda move: move.product_id == self.product_2) self.assertEqual(first_move.product_qty, test_quantity / self.bom_1.product_qty * self.product_4.uom_id.factor_inv * 2) first_move = man_order.move_raw_ids.filtered(lambda move: move.product_id == self.product_1) self.assertEqual(first_move.product_qty, test_quantity / self.bom_1.product_qty * self.product_4.uom_id.factor_inv * 4) # produce product produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': man_order.id, 'active_ids': [man_order.id], })) produce_form.qty_producing = 1.0 produce_wizard = produce_form.save() produce_wizard.do_produce() man_order.button_mark_done() self.assertEqual(man_order.state, 'done', "Production order should be in done state.") def test_production_avialability(self): """ Checks the availability of a production order through mutliple calls to `action_assign`. """ self.bom_3.bom_line_ids.filtered(lambda x: x.product_id == self.product_5).unlink() self.bom_3.bom_line_ids.filtered(lambda x: x.product_id == self.product_4).unlink() self.bom_3.ready_to_produce = 'all_available' production_form = Form(self.env['mrp.production']) production_form.product_id = self.product_6 production_form.bom_id = self.bom_3 production_form.product_qty = 5.0 production_form.product_uom_id = self.product_6.uom_id production_2 = production_form.save() production_2.action_confirm() production_2.action_assign() # check sub product availability state is waiting self.assertEqual(production_2.reservation_state, 'confirmed', 'Production order should be availability for waiting state') # Update Inventory inventory_wizard = self.env['stock.change.product.qty'].create({ 'product_id': self.product_2.id, 'new_quantity': 2.0, }) inventory_wizard.change_product_qty() production_2.action_assign() # check sub product availability state is partially available self.assertEqual(production_2.reservation_state, 'confirmed', 'Production order should be availability for partially available state') # Update Inventory inventory_wizard = self.env['stock.change.product.qty'].create({ 'product_id': self.product_2.id, 'new_quantity': 5.0, }) inventory_wizard.change_product_qty() production_2.action_assign() # check sub product availability state is assigned self.assertEqual(production_2.reservation_state, 'assigned', 'Production order should be availability for assigned state') def test_empty_routing(self): """ Check what happens when you work with an empty routing""" routing = self.env['mrp.routing'].create({'name': 'Routing without operations'}) self.bom_3.routing_id = routing.id production_form = Form(self.env['mrp.production']) production_form.product_id = self.product_6 production = production_form.save() self.assertEqual(production.routing_id.id, False, 'The routing field should be empty on the mo') def test_multiple_post_inventory(self): """ Check the consumed quants of the produced quants when intermediate calls to `post_inventory` during a MO.""" # create a bom for `custom_laptop` with components that aren't tracked unit = self.ref("uom.product_uom_unit") custom_laptop = self.env.ref("product.product_product_27") custom_laptop.tracking = 'none' product_charger = self.env['product.product'].create({ 'name': 'Charger', 'type': 'product', 'uom_id': unit, 'uom_po_id': unit}) product_keybord = self.env['product.product'].create({ 'name': 'Usb Keybord', 'type': 'product', 'uom_id': unit, 'uom_po_id': unit}) self.env['mrp.bom'].create({ 'product_tmpl_id': custom_laptop.product_tmpl_id.id, 'product_qty': 1, 'product_uom_id': unit, 'bom_line_ids': [(0, 0, { 'product_id': product_charger.id, 'product_qty': 1, 'product_uom_id': unit }), (0, 0, { 'product_id': product_keybord.id, 'product_qty': 1, 'product_uom_id': unit })] }) # put the needed products in stock source_location_id = self.ref('stock.stock_location_14') quant_before = custom_laptop.qty_available inventory = self.env['stock.inventory'].create({ 'name': 'Inventory Product Table', 'filter': 'partial', 'line_ids': [(0, 0, { 'product_id': product_charger.id, 'product_uom_id': product_charger.uom_id.id, 'product_qty': 2, 'location_id': source_location_id }), (0, 0, { 'product_id': product_keybord.id, 'product_uom_id': product_keybord.uom_id.id, 'product_qty': 2, 'location_id': source_location_id })] }) inventory.action_validate() # create a mo for this bom mo_custom_laptop_form = Form(self.env['mrp.production']) mo_custom_laptop_form.product_id = custom_laptop mo_custom_laptop_form.product_qty = 2 mo_custom_laptop = mo_custom_laptop_form.save() mo_custom_laptop.action_confirm() mo_custom_laptop.action_assign() self.assertEqual(mo_custom_laptop.reservation_state, 'assigned') # produce one item, call `post_inventory` context = {"active_ids": [mo_custom_laptop.id], "active_id": mo_custom_laptop.id} produce_form = Form(self.env['mrp.product.produce'].with_context(context)) produce_form.qty_producing = 1.00 custom_laptop_produce = produce_form.save() custom_laptop_produce.do_produce() mo_custom_laptop.post_inventory() # check the consumed quants of the produced quant first_move = mo_custom_laptop.move_finished_ids.filtered(lambda mo: mo.state == 'done') quant_after1 = custom_laptop.qty_available self.assertEqual(first_move.quantity_done, 1, "Order already produce 1 product") self.assertEqual(quant_after1 - quant_before, 1, "1 product available after production") second_move = mo_custom_laptop.move_finished_ids.filtered(lambda mo: mo.state == 'confirmed') self.assertEqual(second_move.quantity_done, 0, "There is still one product to pruduce") # produce the second item, call `post_inventory` context = {"active_ids": [mo_custom_laptop.id], "active_id": mo_custom_laptop.id} produce_form = Form(self.env['mrp.product.produce'].with_context(context)) produce_form.qty_producing = 1.00 custom_laptop_produce = produce_form.save() custom_laptop_produce.do_produce() mo_custom_laptop.post_inventory() self.assertEqual(second_move.quantity_done, 1, "Order produce the second product") quant_after2 = custom_laptop.qty_available self.assertEqual(quant_after2 - quant_before, 2, "2 products available after production") def test_rounding(self): """ Checks we round up when bringing goods to produce and round half-up when producing. This implementation allows to implement an efficiency notion (see rev 347f140fe63612ee05e). """ self.product_6.uom_id.rounding = 1.0 bom_eff = self.env['mrp.bom'].create({ 'product_id': self.product_6.id, 'product_tmpl_id': self.product_6.product_tmpl_id.id, 'product_qty': 1, 'product_uom_id': self.product_6.uom_id.id, 'type': 'normal', 'bom_line_ids': [ (0, 0, {'product_id': self.product_2.id, 'product_qty': 2.03}), (0, 0, {'product_id': self.product_8.id, 'product_qty': 4.16}) ] }) production_form = Form(self.env['mrp.production']) production_form.product_id = self.product_6 production_form.bom_id = bom_eff production_form.product_qty = 20 production_form.product_uom_id = self.product_6.uom_id production = production_form.save() production.action_confirm() #Check the production order has the right quantities self.assertEqual(production.move_raw_ids[0].product_qty, 41, 'The quantity should be rounded up') self.assertEqual(production.move_raw_ids[1].product_qty, 84, 'The quantity should be rounded up') # produce product produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': production.id, 'active_ids': [production.id], })) produce_form.qty_producing = 8 produce_wizard = produce_form.save() produce_wizard.do_produce() self.assertEqual(production.move_raw_ids[0].quantity_done, 16, 'Should use half-up rounding when producing') self.assertEqual(production.move_raw_ids[1].quantity_done, 34, 'Should use half-up rounding when producing') def test_product_produce_1(self): """ Checks the production wizard contains lines even for untracked products. """ self.stock_location = self.env.ref('stock.stock_location_stock') mo, bom, p_final, p1, p2 = self.generate_mo() self.assertEqual(len(mo), 1, 'MO should have been created') self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 100) self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5) mo.action_assign() produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], })) # change the quantity done in one line produce_form.workorder_line_ids._records[0]['qty_done'] = 1 # change the quantity producing produce_form.qty_producing = 3 # check than all quantities are update correctly line1 = produce_form.workorder_line_ids._records[0] line2 = produce_form.workorder_line_ids._records[1] self.assertEqual(line1['qty_to_consume'], 3, "Wrong quantity to consume") self.assertEqual(line1['qty_done'], 3, "Wrong quantity done") self.assertEqual(line2['qty_to_consume'], 12, "Wrong quantity to consume") self.assertEqual(line2['qty_done'], 12, "Wrong quantity done") product_produce = produce_form.save() self.assertEqual(len(product_produce.workorder_line_ids), 2, 'You should have produce lines even the consumed products are not tracked.') product_produce.do_produce() def test_product_produce_2(self): """ Checks that, for a BOM where one of the components is tracked by serial number and the other is not tracked, when creating a manufacturing order for two finished products and reserving, the produce wizards proposes the corrects lines when producing one at a time. """ self.stock_location = self.env.ref('stock.stock_location_stock') mo, bom, p_final, p1, p2 = self.generate_mo(tracking_base_1='serial', qty_base_1=1, qty_final=2) self.assertEqual(len(mo), 1, 'MO should have been created') lot_p1_1 = self.env['stock.production.lot'].create({ 'name': 'lot1', 'product_id': p1.id, }) lot_p1_2 = self.env['stock.production.lot'].create({ 'name': 'lot2', 'product_id': p1.id, }) self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 1, lot_id=lot_p1_1) self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 1, lot_id=lot_p1_2) self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5) mo.action_assign() produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], })) self.assertEqual(len(produce_form.workorder_line_ids), 3, 'You should have 3 produce lines. One for each serial to consume and for the untracked product.') produce_form.qty_producing = 1 # get the proposed lot consumed_lots = self.env['stock.production.lot'] for workorder_line in produce_form.workorder_line_ids._records: if workorder_line['product_id'] == p1.id: consumed_lots |= self.env['stock.production.lot'].browse(workorder_line['lot_id']) consumed_lots.ensure_one() product_produce = produce_form.save() product_produce.do_produce() remaining_lot = (lot_p1_1 | lot_p1_2) - consumed_lots remaining_lot.ensure_one() produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], })) product_produce = produce_form.save() self.assertEqual(len(product_produce.workorder_line_ids), 2, 'You should have 2 produce lines left.') for line in product_produce.workorder_line_ids.filtered(lambda x: x.lot_id): self.assertEqual(line.lot_id, remaining_lot, 'Wrong lot proposed.') def test_product_produce_3(self): """ Checks that, for a BOM where one of the components is tracked by lot and the other is not tracked, when creating a manufacturing order for 1 finished product and reserving, the produce wizard proposes the corrects lines. Then, checks the generated move lines when over consuming. """ # FIXME: some asserts on the quants after overproducing would be nice self.stock_location = self.env.ref('stock.stock_location_stock') self.stock_shelf_1 = self.env.ref('stock.stock_location_components') self.stock_shelf_2 = self.env.ref('stock.stock_location_14') mo, _, p_final, p1, p2 = self.generate_mo(tracking_base_1='lot', qty_base_1=10, qty_final=1) self.assertEqual(len(mo), 1, 'MO should have been created') mo.bom_id.consumption = 'flexible' # Because we will over consume. first_lot_for_p1 = self.env['stock.production.lot'].create({ 'name': 'lot1', 'product_id': p1.id, }) second_lot_for_p1 = self.env['stock.production.lot'].create({ 'name': 'lot2', 'product_id': p1.id, }) final_product_lot = self.env['stock.production.lot'].create({ 'name': 'lot1', 'product_id': p_final.id, }) self.env['stock.quant']._update_available_quantity(p1, self.stock_shelf_1, 3, lot_id=first_lot_for_p1) self.env['stock.quant']._update_available_quantity(p1, self.stock_shelf_2, 3, lot_id=first_lot_for_p1) self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 8, lot_id=second_lot_for_p1) self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5) mo.action_assign() produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], })) produce_form.qty_producing = 1.0 for i in range(len(produce_form.workorder_line_ids)): with produce_form.workorder_line_ids.edit(i) as line: line.qty_done += 1 product_produce = produce_form.save() product_produce.final_lot_id = final_product_lot.id # product 1 lot 1 shelf1 # product 1 lot 1 shelf2 # product 1 lot 2 self.assertEqual(len(product_produce.workorder_line_ids), 4, 'You should have 4 produce lines. lot 1 shelf_1, lot 1 shelf_2, lot2 and for product which have tracking None') product_produce.do_produce() move_1 = mo.move_raw_ids.filtered(lambda m: m.product_id == p1) # qty_done/product_uom_qty lot # 3/3 lot 1 shelf 1 # 1/1 lot 1 shelf 2 # 2/2 lot 1 shelf 2 # 2/0 lot 1 other # 5/4 lot 2 ml_to_shelf_1 = move_1.move_line_ids.filtered(lambda ml: ml.lot_id == first_lot_for_p1 and ml.location_id == self.stock_shelf_1) ml_to_shelf_2 = move_1.move_line_ids.filtered(lambda ml: ml.lot_id == first_lot_for_p1 and ml.location_id == self.stock_shelf_2) self.assertEqual(sum(ml_to_shelf_1.mapped('qty_done')), 3.0, '3 units should be took from shelf1 as reserved.') self.assertEqual(sum(ml_to_shelf_2.mapped('qty_done')), 3.0, '3 units should be took from shelf2 as reserved.') self.assertEqual(move_1.quantity_done, 13, 'You should have used the tem units.') mo.button_mark_done() self.assertEqual(mo.state, 'done', "Production order should be in done state.") def test_product_produce_4(self): """ Possibility to produce with a given raw material in multiple locations. """ # FIXME sle: how is it possible to consume before producing in the interface? self.stock_location = self.env.ref('stock.stock_location_stock') self.stock_shelf_1 = self.env.ref('stock.stock_location_components') self.stock_shelf_2 = self.env.ref('stock.stock_location_14') mo, _, p_final, p1, p2 = self.generate_mo(qty_final=1, qty_base_1=5) self.env['stock.quant']._update_available_quantity(p1, self.stock_shelf_1, 2) self.env['stock.quant']._update_available_quantity(p1, self.stock_shelf_2, 3) self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 1) mo.action_assign() ml_p1 = mo.move_raw_ids.filtered(lambda x: x.product_id == p1).mapped('move_line_ids') ml_p2 = mo.move_raw_ids.filtered(lambda x: x.product_id == p2).mapped('move_line_ids') self.assertEqual(len(ml_p1), 2) self.assertEqual(len(ml_p2), 1) # Add some quantity already done to force an extra move line to be created ml_p1[0].qty_done = 1.0 # Produce baby! product_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], })) product_produce = product_form.save() product_produce.do_produce() m_p1 = mo.move_raw_ids.filtered(lambda x: x.product_id == p1) ml_p1 = m_p1.mapped('move_line_ids') self.assertEqual(len(ml_p1), 3) self.assertEqual(sorted(ml_p1.mapped('qty_done')), [1.0, 2.0, 3.0], 'Quantity done should be 1.0, 2.0 or 3.0') self.assertEqual(m_p1.quantity_done, 6.0, 'Total qty done should be 6.0') self.assertEqual(sum(ml_p1.mapped('product_uom_qty')), 5.0, 'Total qty reserved should be 5.0') mo.button_mark_done() self.assertEqual(mo.state, 'done', "Production order should be in done state.") def test_update_quantity_1(self): """ Build 5 final products with different consumed lots, then edit the finished quantity and update the Manufacturing order quantity. Then check if the produced quantity do not change and it is possible to close the MO. """ self.stock_location = self.env.ref('stock.stock_location_stock') mo, bom, p_final, p1, p2 = self.generate_mo(tracking_base_1='lot') self.assertEqual(len(mo), 1, 'MO should have been created') lot_1 = self.env['stock.production.lot'].create({ 'name': 'lot1', 'product_id': p1.id, }) lot_2 = self.env['stock.production.lot'].create({ 'name': 'lot2', 'product_id': p1.id, }) self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 10, lot_id=lot_1) self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 10, lot_id=lot_2) self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5) mo.action_assign() produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], })) produce_wizard = produce_form.save() produce_wizard.do_produce() mo.move_finished_ids.move_line_ids.qty_done -= 1 update_quantity_wizard = self.env['change.production.qty'].create({ 'mo_id': mo.id, 'product_qty': 4, }) update_quantity_wizard.change_prod_qty() self.assertEqual(mo.move_raw_ids.filtered(lambda m: m.product_id == p1).quantity_done, 20, 'Update the produce quantity should not impact already produced quantity.') mo.button_mark_done() def test_product_produce_6(self): """ Checks that, for a BOM with two components, when creating a manufacturing order for one finished products and without reserving, the produce wizards proposes the corrects lines even if we change the quantity to produce multiple times. """ self.stock_location = self.env.ref('stock.stock_location_stock') mo, bom, p_final, p1, p2 = self.generate_mo(qty_final=1) self.assertEqual(len(mo), 1, 'MO should have been created') self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 4) self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 1) mo.bom_id.consumption = 'flexible' # Because we'll over-consume with a product not defined in the BOM mo.action_assign() produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], })) produce_form.qty_producing = 3 self.assertEqual(len(produce_form.workorder_line_ids._records), 4, 'Update the produce quantity should change the components quantity.') self.assertEqual(sum([x['qty_done'] for x in produce_form.workorder_line_ids._records]), 15, 'Update the produce quantity should change the components quantity.') self.assertEqual(sum([x['qty_reserved'] for x in produce_form.workorder_line_ids._records]), 5, 'Update the produce quantity should not change the components reserved quantity.') produce_form.qty_producing = 4 self.assertEqual(len(produce_form.workorder_line_ids._records), 4, 'Update the produce quantity should change the components quantity.') self.assertEqual(sum([x['qty_done'] for x in produce_form.workorder_line_ids._records]), 20, 'Update the produce quantity should change the components quantity.') self.assertEqual(sum([x['qty_reserved'] for x in produce_form.workorder_line_ids._records]), 5, 'Update the produce quantity should not change the components reserved quantity.') produce_form.qty_producing = 1 self.assertEqual(len(produce_form.workorder_line_ids._records), 2, 'Update the produce quantity should change the components quantity.') self.assertEqual(sum([x['qty_done'] for x in produce_form.workorder_line_ids._records]), 5, 'Update the produce quantity should change the components quantity.') self.assertEqual(sum([x['qty_reserved'] for x in produce_form.workorder_line_ids._records]), 5, 'Update the produce quantity should not change the components reserved quantity.') # try adding another product that doesn't belong to the BoM with produce_form.workorder_line_ids.new() as line: line.product_id = self.product_4 line.qty_done = 1 produce_wizard = produce_form.save() produce_wizard.do_produce() def test_product_produce_7(self): """ Add components in 2 differents sub location. Do not reserve the MO and checks that the move line created takes stock from location that contains needed raw materials. """ mo, bom, p_final, p1, p2 = self.generate_mo(qty_final=2) self.assertEqual(len(mo), 1, 'MO should have been created') self.stock_location = self.env.ref('stock.stock_location_stock') self.stock_shelf_1 = self.env.ref('stock.stock_location_components') self.stock_shelf_2 = self.env.ref('stock.stock_location_14') self.env['stock.quant']._update_available_quantity(p1, self.stock_shelf_1, 3) self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 3) self.env['stock.quant']._update_available_quantity(p1, self.stock_shelf_2, 2) self.env['stock.quant']._update_available_quantity(p2, self.stock_shelf_1, 1) self.env['stock.quant']._update_available_quantity(p2, self.stock_shelf_2, 1) produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], })) produce_form.qty_producing = 1 produce_wizard = produce_form.save() self.assertEqual(len(produce_wizard.workorder_line_ids), 2) produce_wizard.do_produce() produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], })) produce_form.qty_producing = 1 produce_wizard = produce_form.save() self.assertEqual(len(produce_wizard.workorder_line_ids), 2) produce_wizard.do_produce() mo.button_mark_done() mo_move_line_p1 = mo.move_raw_ids[1].move_line_ids self.assertEqual(sum(mo_move_line_p1.filtered(lambda ml: ml.location_id == self.stock_location).mapped('qty_done')), 3) self.assertEqual(sum(mo_move_line_p1.filtered(lambda ml: ml.location_id == self.stock_shelf_1).mapped('qty_done')), 3) self.assertEqual(sum(mo_move_line_p1.filtered(lambda ml: ml.location_id == self.stock_shelf_2).mapped('qty_done')), 2) self.assertEqual(sum(mo.move_finished_ids.move_line_ids.mapped('qty_done')), 2) self.assertEqual(self.env['stock.quant']._gather(p1, self.stock_location, strict=True).quantity, 0) self.assertEqual(self.env['stock.quant']._gather(p1, self.stock_shelf_1, strict=True).quantity, 0) self.assertEqual(self.env['stock.quant']._gather(p1, self.stock_shelf_2, strict=True).quantity, 0) self.assertEqual(self.env['stock.quant']._gather(p2, self.stock_shelf_1, strict=True).quantity, 0) self.assertEqual(self.env['stock.quant']._gather(p2, self.stock_shelf_2, strict=True).quantity, 0) self.assertEqual(self.env['stock.quant']._gather(p_final, self.stock_location, strict=True).quantity, 2) def test_product_produce_8(self): """ Produce more than reserved and planned. Check that produce wizard only propose one line for product not reserved. """ mo, bom, p_final, p1, p2 = self.generate_mo(qty_final=2) self.assertEqual(len(mo), 1, 'MO should have been created') self.stock_location = self.env.ref('stock.stock_location_stock') self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 5) self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 2) mo.action_assign() produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], })) produce_form.qty_producing = 1 produce_wizard = produce_form.save() self.assertEqual(len(produce_wizard.workorder_line_ids), 2) self.assertEqual(produce_wizard.workorder_line_ids.filtered(lambda l: l.product_id == p1).qty_reserved, 4) self.assertEqual(produce_wizard.workorder_line_ids.filtered(lambda l: l.product_id == p2).qty_reserved, 1) produce_wizard.do_produce() produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], })) produce_form.qty_producing = 1 produce_wizard = produce_form.save() # p1 1 1 1 # p1 3 0 3 # p2 1 1 1 self.assertEqual(len(produce_wizard.workorder_line_ids), 3) self.assertEqual(sum(produce_wizard.workorder_line_ids.filtered(lambda l: l.product_id == p1).mapped('qty_reserved')), 1) self.assertEqual(produce_wizard.workorder_line_ids.filtered(lambda l: l.product_id == p1 and l.qty_reserved).qty_to_consume, 1) self.assertEqual(produce_wizard.workorder_line_ids.filtered(lambda l: l.product_id == p1 and not l.qty_reserved).qty_to_consume, 3) self.assertEqual(produce_wizard.workorder_line_ids.filtered(lambda l: l.product_id == p2).qty_reserved, 1) with Form(produce_wizard) as produce_form: produce_form.qty_producing = 2 # p1 1 1 1 # p1 7 0 7 # p2 1 1 1 # p2 1 0 1 self.assertEqual(len(produce_wizard.workorder_line_ids), 4) self.assertEqual(sum(produce_wizard.workorder_line_ids.filtered(lambda l: l.product_id == p1).mapped('qty_reserved')), 1) self.assertEqual(produce_wizard.workorder_line_ids.filtered(lambda l: l.product_id == p1 and l.qty_reserved).qty_to_consume, 1) self.assertEqual(produce_wizard.workorder_line_ids.filtered(lambda l: l.product_id == p1 and not l.qty_reserved).qty_to_consume, 7) self.assertEqual(sum(produce_wizard.workorder_line_ids.filtered(lambda l: l.product_id == p2).mapped('qty_reserved')), 1) produce_wizard.do_produce() mo.button_mark_done() self.assertEqual(self.env['stock.quant']._gather(p1, self.stock_location, strict=True).quantity, -7) self.assertEqual(self.env['stock.quant']._gather(p2, self.stock_location, strict=True).quantity, -1) self.assertEqual(self.env['stock.quant']._gather(p_final, self.stock_location, strict=True).quantity, 3) def test_product_produce_9(self): """ Checks the constraints of a strict BOM without tracking when playing around in the produce wizard. """ self.stock_location = self.env.ref('stock.stock_location_stock') mo, bom, p_final, p1, p2 = self.generate_mo() self.assertEqual(len(mo), 1, 'MO should have been created') self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 100) self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5) mo.action_assign() produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], })) with self.assertRaises(UserError): # try adding another line for a bom product to increase the quantity produce_form.qty_producing = 1 with produce_form.workorder_line_ids.new() as line: line.product_id = p1 line.qty_done = 1 product_produce = produce_form.save() product_produce.do_produce() with self.assertRaises(UserError): # Try updating qty_done product_produce = produce_form.save() product_produce.workorder_line_ids[0].qty_done += 1 product_produce.do_produce() with self.assertRaises(UserError): # try adding another product produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], })) produce_form.qty_producing = 1 with produce_form.workorder_line_ids.new() as line: line.product_id = self.product_4 line.qty_done = 1 product_produce = produce_form.save() product_produce.do_produce() # try adding another line for a bom product but the total quantity is good produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], })) produce_form.qty_producing = 1 with produce_form.workorder_line_ids.new() as line: line.product_id = p1 line.qty_done = 1 product_produce = produce_form.save() product_produce.workorder_line_ids[1].qty_done -= 1 product_produce.do_produce() def test_product_produce_uom(self): """ Produce a finished product tracked by serial number. Set another UoM on the bom. The produce wizard should keep the UoM of the product (unit) and quantity = 1.""" plastic_laminate = self.env.ref('mrp.product_product_plastic_laminate') bom = self.env.ref('mrp.mrp_bom_plastic_laminate') dozen = self.env.ref('uom.product_uom_dozen') unit = self.env.ref('uom.product_uom_unit') plastic_laminate.tracking = 'serial' mo_form = Form(self.env['mrp.production']) mo_form.product_id = plastic_laminate mo_form.bom_id = bom mo_form.product_uom_id = dozen mo_form.product_qty = 1 mo = mo_form.save() final_product_lot = self.env['stock.production.lot'].create({ 'name': 'lot1', 'product_id': plastic_laminate.id, }) mo.action_confirm() mo.action_assign() self.assertEqual(mo.move_raw_ids.product_qty, 12, '12 units should be reserved.') # produce product produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], })) produce_form.final_lot_id = final_product_lot product_produce = produce_form.save() self.assertEqual(product_produce.qty_producing, 1) self.assertEqual(product_produce.product_uom_id, unit, 'Should be 1 unit since the tracking is serial.') product_produce.final_lot_id = final_product_lot.id product_produce.do_produce() move_line_raw = mo.move_raw_ids.mapped('move_line_ids').filtered(lambda m: m.qty_done) self.assertEqual(move_line_raw.qty_done, 1) self.assertEqual(move_line_raw.product_uom_id, unit, 'Should be 1 unit since the tracking is serial.') move_line_finished = mo.move_finished_ids.mapped('move_line_ids').filtered(lambda m: m.qty_done) self.assertEqual(move_line_finished.qty_done, 1) self.assertEqual(move_line_finished.product_uom_id, unit, 'Should be 1 unit since the tracking is serial.') def test_product_produce_uom_2(self): """ Create a bom with a serial tracked component and a pair UoM (2 x unit). The produce wizard should create 2 line with quantity = 1 and UoM = unit for this component. """ unit = self.env.ref("uom.product_uom_unit") categ_unit_id = self.env.ref('uom.product_uom_categ_unit') paire = self.env['uom.uom'].create({ 'name': 'Paire', 'factor_inv': 2, 'uom_type': 'bigger', 'rounding': 0.001, 'category_id': categ_unit_id.id }) binocular = self.env['product.product'].create({ 'name': 'Binocular', 'type': 'product', 'uom_id': unit.id, 'uom_po_id': unit.id }) nocular = self.env['product.product'].create({ 'name': 'Nocular', 'type': 'product', 'tracking': 'serial', 'uom_id': unit.id, 'uom_po_id': unit.id }) bom_binocular = self.env['mrp.bom'].create({ 'product_tmpl_id': binocular.product_tmpl_id.id, 'product_qty': 1, 'product_uom_id': unit.id, 'bom_line_ids': [(0, 0, { 'product_id': nocular.id, 'product_qty': 1, 'product_uom_id': paire.id })] }) mo_form = Form(self.env['mrp.production']) mo_form.product_id = binocular mo_form.bom_id = bom_binocular mo_form.product_uom_id = unit mo_form.product_qty = 1 mo = mo_form.save() mo.action_confirm() self.assertEqual(mo.move_raw_ids.product_uom_qty, 1, 'Quantity should be 1.') self.assertEqual(mo.move_raw_ids.product_uom, paire, 'Move UoM should be "Paire".') # produce product produce_form = Form(self.env['mrp.product.produce'].with_context({ 'active_id': mo.id, 'active_ids': [mo.id], })) product_produce = produce_form.save() self.assertEqual(product_produce.qty_producing, 1) self.assertEqual(len(product_produce.workorder_line_ids), 2, 'Should be 2 lines since the component tracking is serial and quantity 2.') self.assertEqual(product_produce.workorder_line_ids[0].qty_to_consume, 1, 'Should be 1 unit since the tracking is serial and quantity 2.') self.assertEqual(product_produce.workorder_line_ids[0].product_uom_id, unit, 'Should be the product uom so "unit"') self.assertEqual(product_produce.workorder_line_ids[1].qty_to_consume, 1, 'Should be 1 unit since the tracking is serial and quantity 2.') self.assertEqual(product_produce.workorder_line_ids[1].product_uom_id, unit, 'should be the product uom so "unit"')