flectra/addons/mrp/tests/test_order.py

581 lines
28 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, timedelta
from odoo.fields import Datetime as Dt
from odoo.addons.mrp.tests.common import TestMrpCommon
class TestMrpOrder(TestMrpCommon):
def test_access_rights_manager(self):
man_order = self.env['mrp.production'].sudo(self.user_mrp_manager).create({
'name': 'Stick-0',
'product_id': self.product_4.id,
'product_uom_id': self.product_4.uom_id.id,
'product_qty': 5.0,
'bom_id': self.bom_1.id,
'location_src_id': self.location_1.id,
'location_dest_id': self.warehouse_1.wh_output_stock_loc_id.id,
})
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):
man_order = self.env['mrp.production'].sudo(self.user_mrp_user).create({
'name': 'Stick-0',
'product_id': self.product_4.id,
'product_uom_id': self.product_4.uom_id.id,
'product_qty': 5.0,
'bom_id': self.bom_1.id,
'location_src_id': self.location_1.id,
'location_dest_id': self.warehouse_1.wh_output_stock_loc_id.id,
})
man_order.action_cancel()
self.assertEqual(man_order.state, 'cancel', "Production order should be in cancel state.")
man_order.unlink()
def test_basic(self):
""" Basic order test: no routing (thus no workorders), no lot """
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_done()
test_date_planned = datetime.now() - timedelta(days=1)
test_quantity = 2.0
self.bom_1.routing_id = False
man_order = self.env['mrp.production'].sudo(self.user_mrp_user).create({
'name': 'Stick-0',
'product_id': self.product_4.id,
'product_uom_id': self.product_4.uom_id.id,
'product_qty': test_quantity,
'bom_id': self.bom_1.id,
'date_planned_start': test_date_planned,
'location_src_id': self.location_1.id,
'location_dest_id': self.warehouse_1.wh_output_stock_loc_id.id,
})
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, Dt.to_string(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, Dt.to_string(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)
# waste some material, create a scrap
# scrap = self.env['stock.scrap'].with_context(
# active_model='mrp.production', active_id=man_order.id
# ).create({})
# scrap = self.env['stock.scrap'].create({
# 'production_id': man_order.id,
# 'product_id': first_move.product_id.id,
# 'product_uom_id': first_move.product_uom.id,
# 'scrap_qty': 5.0,
# })
# check created scrap
# procurements = self.env['procurement.order'].search([('move_dest_id', 'in', man_order.move_raw_ids.ids)])
# print procurements
# procurements = self.env['procurement.order'].search([('production_id', '=', man_order.id)])
# print procurements
# for proc in self.env['procurement.order'].browse(procurements):
# date_planned = self.mrp_production_test1.date_planned
# if proc.product_id.type not in ('product', 'consu'):
# continue
# if proc.product_id.id == order_line.product_id.id:
# self.assertEqual(proc.date_planned, date_planned, "Planned date does not correspond")
# # procurement state should be `confirmed` at this stage, except if procurement_jit is installed, in which
# # case it could already be in `running` or `exception` state (not enough stock)
# expected_states = ('confirmed', 'running', 'exception')
# self.assertEqual(proc.state in expected_states, 'Procurement state is `%s` for %s, expected one of %s' % (proc.state, proc.product_id.name, expected_states))
# Change production quantity
qty_wizard = self.env['change.production.qty'].create({
'mo_id': man_order.id,
'product_qty': 3.0,
})
# qty_wizard.change_prod_qty()
# # I check qty after changed in production order.
# #self.assertEqual(self.mrp_production_test1.product_qty, 3, "Qty is not changed in order.")
# move = self.mrp_production_test1.move_finished_ids[0]
# self.assertEqual(move.product_qty, self.mrp_production_test1.product_qty, "Qty is not changed in move line.")
# # I run scheduler.
# self.env['procurement.order'].run_scheduler()
# # The production order is Waiting Goods, will force production which should set consume lines as available
# self.mrp_production_test1.button_plan()
# # I check that production order in ready state after forcing production.
# #self.assertEqual(self.mrp_production_test1.availability, 'assigned', 'Production order availability should be set as available')
# produce product
produce_wizard = self.env['mrp.product.produce'].sudo(self.user_mrp_user).with_context({
'active_id': man_order.id,
'active_ids': [man_order.id],
}).create({
'product_qty': 1.0,
})
produce_wizard.do_produce()
# man_order.button_mark_done()
man_order.button_mark_done()
self.assertEqual(man_order.state, 'done', "Production order should be in done state.")
def test_explode_from_order(self):
#
# bom3 produces 2 Dozen of Doors (p6), aka 24
# To produce 24 Units of Doors (p6)
# - 2 Units of Tools (p5) -> need 4
# - 8 Dozen of Sticks (p4) -> need 16
# - 12 Units of Wood (p2) -> need 24
# bom2 produces 1 Unit of Sticks (p4)
# To produce 1 Unit of Sticks (p4)
# - 2 Dozen of Sticks (p4) -> need 8
# - 3 Dozen of Stones (p3) -> need 12
# Update capacity, start time, stop time, and time efficiency.
# ------------------------------------------------------------
self.workcenter_1.write({'capacity': 1, 'time_start': 0, 'time_stop': 0, 'time_efficiency': 100})
# Set manual time cycle 20 and 10.
# --------------------------------
self.operation_1.write({'time_cycle_manual': 20})
(self.operation_2 | self.operation_3).write({'time_cycle_manual': 10})
man_order = self.env['mrp.production'].create({
'name': 'MO-Test',
'product_id': self.product_6.id,
'product_uom_id': self.product_6.uom_id.id,
'product_qty': 48,
'bom_id': self.bom_3.id,
})
# reset quantities
self.product_1.type = "product"
self.env['stock.change.product.qty'].create({
'product_id': self.product_1.id,
'new_quantity': 0.0,
'location_id': self.warehouse_1.lot_stock_id.id,
}).change_product_qty()
(self.product_2 | self.product_4).write({
'tracking': 'none',
})
# assign consume material
man_order.action_assign()
self.assertEqual(man_order.availability, 'waiting', "Production order should be in waiting state.")
# check consume materials of manufacturing order
self.assertEqual(len(man_order.move_raw_ids), 4, "Consume material lines are not generated proper.")
product_2_consume_moves = man_order.move_raw_ids.filtered(lambda x: x.product_id == self.product_2)
product_3_consume_moves = man_order.move_raw_ids.filtered(lambda x: x.product_id == self.product_3)
product_4_consume_moves = man_order.move_raw_ids.filtered(lambda x: x.product_id == self.product_4)
product_5_consume_moves = man_order.move_raw_ids.filtered(lambda x: x.product_id == self.product_5)
consume_qty_2 = product_2_consume_moves.product_uom_qty
self.assertEqual(consume_qty_2, 24.0, "Consume material quantity of Wood should be 24 instead of %s" % str(consume_qty_2))
consume_qty_3 = product_3_consume_moves.product_uom_qty
self.assertEqual(consume_qty_3, 12.0, "Consume material quantity of Stone should be 12 instead of %s" % str(consume_qty_3))
self.assertEqual(len(product_4_consume_moves), 2, "Consume move are not generated proper.")
for consume_moves in product_4_consume_moves:
consume_qty_4 = consume_moves.product_uom_qty
self.assertIn(consume_qty_4, [8.0, 16.0], "Consume material quantity of Stick should be 8 or 16 instead of %s" % str(consume_qty_4))
self.assertFalse(product_5_consume_moves, "Move should not create for phantom bom")
# create required lots
lot_product_2 = self.env['stock.production.lot'].create({'product_id': self.product_2.id})
lot_product_4 = self.env['stock.production.lot'].create({'product_id': self.product_4.id})
# refuel stock
inventory = self.env['stock.inventory'].create({
'name': 'Inventory For Product C',
'filter': 'partial',
'line_ids': [(0, 0, {
'product_id': self.product_2.id,
'product_uom_id': self.product_2.uom_id.id,
'product_qty': 30,
'prod_lot_id': lot_product_2.id,
'location_id': self.ref('stock.stock_location_14')
}), (0, 0, {
'product_id': self.product_3.id,
'product_uom_id': self.product_3.uom_id.id,
'product_qty': 60,
'location_id': self.ref('stock.stock_location_14')
}), (0, 0, {
'product_id': self.product_4.id,
'product_uom_id': self.product_4.uom_id.id,
'product_qty': 60,
'prod_lot_id': lot_product_4.id,
'location_id': self.ref('stock.stock_location_14')
})]
})
inventory.action_start()
inventory.action_done()
# re-assign consume material
man_order.action_assign()
# Check production order status after assign.
self.assertEqual(man_order.availability, 'assigned', "Production order should be in assigned state.")
# Plan production order.
man_order.button_plan()
# check workorders
# - main bom: Door: 2 operations
# operation 1: Cutting
# operation 2: Welding, waiting for the previous one
# - kit bom: Stone Tool: 1 operation
# operation 1: Gift Wrapping
workorders = man_order.workorder_ids
kit_wo = man_order.workorder_ids.filtered(lambda wo: wo.operation_id == self.operation_1)
door_wo_1 = man_order.workorder_ids.filtered(lambda wo: wo.operation_id == self.operation_2)
door_wo_2 = man_order.workorder_ids.filtered(lambda wo: wo.operation_id == self.operation_3)
for workorder in workorders:
self.assertEqual(workorder.workcenter_id, self.workcenter_1, "Workcenter does not match.")
self.assertEqual(kit_wo.state, 'ready', "Workorder should be in ready state.")
self.assertEqual(door_wo_1.state, 'ready', "Workorder should be in ready state.")
self.assertEqual(door_wo_2.state, 'pending', "Workorder should be in pending state.")
self.assertEqual(kit_wo.duration_expected, 80, "Workorder duration should be 80 instead of %s." % str(kit_wo.duration_expected))
self.assertEqual(door_wo_1.duration_expected, 20, "Workorder duration should be 20 instead of %s." % str(door_wo_1.duration_expected))
self.assertEqual(door_wo_2.duration_expected, 20, "Workorder duration should be 20 instead of %s." % str(door_wo_2.duration_expected))
# subbom: kit for stone tools
kit_wo.button_start()
finished_lot = self.env['stock.production.lot'].create({'product_id': man_order.product_id.id})
kit_wo.write({
'final_lot_id': finished_lot.id,
'qty_producing': 48
})
kit_wo.record_production()
self.assertEqual(kit_wo.state, 'done', "Workorder should be in done state.")
# first operation of main bom
finished_lot = self.env['stock.production.lot'].create({'product_id': man_order.product_id.id})
door_wo_1.write({
'final_lot_id': finished_lot.id,
'qty_producing': 48
})
door_wo_1.record_production()
self.assertEqual(door_wo_1.state, 'done', "Workorder should be in done state.")
# second operation of main bom
self.assertEqual(door_wo_2.state, 'ready', "Workorder should be in ready state.")
door_wo_2.record_production()
self.assertEqual(door_wo_2.state, 'done', "Workorder should be in done state.")
def test_production_avialability(self):
"""
Test availability of production order.
"""
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()
production_2 = self.env['mrp.production'].create({
'name': 'MO-Test001',
'product_id': self.product_6.id,
'product_qty': 5.0,
'bom_id': self.bom_3.id,
'product_uom_id': self.product_6.uom_id.id,
})
production_2.action_assign()
# check sub product availability state is waiting
self.assertEqual(production_2.availability, 'waiting', '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.availability, 'partially_available', '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.availability, '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',
'location_id': self.warehouse_1.wh_input_stock_loc_id.id,})
self.bom_3.routing_id = routing.id
production = self.env['mrp.production'].create({'name': 'MO test',
'product_id': self.product_6.id,
'product_qty': 3,
'bom_id': self.bom_3.id,
'product_uom_id': self.product_6.uom_id.id,})
self.assertEqual(production.routing_id.id, False, 'The routing field should be empty on the mo')
self.assertEqual(production.move_raw_ids[0].location_id.id, self.warehouse_1.wh_input_stock_loc_id.id, 'Raw moves start location should have altered.')
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("product.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})
bom_custom_laptop = 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')
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_done()
# create a mo for this bom
mo_custom_laptop = self.env['mrp.production'].create({
'product_id': custom_laptop.id,
'product_qty': 2,
'product_uom_id': unit,
'bom_id': bom_custom_laptop.id
})
mo_custom_laptop.action_assign()
self.assertEqual(mo_custom_laptop.availability, 'assigned')
# produce one item, call `post_inventory`
context = {"active_ids": [mo_custom_laptop.id], "active_id": mo_custom_laptop.id}
custom_laptop_produce = self.env['mrp.product.produce'].with_context(context).create({'product_qty': 1.00})
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')
second_move = mo_custom_laptop.move_finished_ids.filtered(lambda mo: mo.state == 'confirmed')
# produce the second item, call `post_inventory`
context = {"active_ids": [mo_custom_laptop.id], "active_id": mo_custom_laptop.id}
custom_laptop_produce = self.env['mrp.product.produce'].with_context(context).create({'product_qty': 1.00})
custom_laptop_produce.do_produce()
mo_custom_laptop.post_inventory()
def test_rounding(self):
""" In previous versions we had rounding and efficiency fields. We check if we can still do the same, but with only the rounding on the UoM"""
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 = self.env['mrp.production'].create({'name': 'MO efficiency test',
'product_id': self.product_6.id,
'product_qty': 20,
'bom_id': bom_eff.id,
'product_uom_id': self.product_6.uom_id.id,})
#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_wizard = self.env['mrp.product.produce'].with_context({
'active_id': production.id,
'active_ids': [production.id],
}).create({
'product_qty': 8,
})
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):
""" Check that no produce line are created when the consumed products are not tracked """
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()
product_produce = self.env['mrp.product.produce'].with_context({
'active_id': mo.id,
'active_ids': [mo.id],
}).create({})
self.assertEqual(len(product_produce.produce_line_ids), 0, 'You should not have any produce lines since the consumed products are not tracked.')
def test_product_produce_2(self):
""" Check that line are created when the consumed products are
tracked by serial and the lot proposed are correct. """
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()
product_produce = self.env['mrp.product.produce'].with_context({
'active_id': mo.id,
'active_ids': [mo.id],
}).create({})
self.assertEqual(len(product_produce.produce_line_ids), 2, 'You should have 2 produce lines. One for each serial to consume')
product_produce.product_qty = 1
produce_line_1 = product_produce.produce_line_ids[0]
produce_line_1.qty_done = 1
remaining_lot = (lot_p1_1 | lot_p1_2) - produce_line_1.lot_id
product_produce.do_produce()
product_produce = self.env['mrp.product.produce'].with_context({
'active_id': mo.id,
'active_ids': [mo.id],
}).create({})
self.assertEqual(len(product_produce.produce_line_ids), 1, 'You should have 1 produce lines since one has already be consumed.')
self.assertEqual(product_produce.produce_line_ids[0].lot_id, remaining_lot, 'Wrong lot proposed.')
def test_product_produce_3(self):
""" Check that line are created when the consumed products are
tracked by serial and the lot proposed are correct. """
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')
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()
product_produce = self.env['mrp.product.produce'].with_context({
'active_id': mo.id,
'active_ids': [mo.id],
}).create({
'product_qty': 1.0,
'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.produce_line_ids), 3, 'You should have 3 produce lines. lot 1 shelf_1, lot 1 shelf_2, lot2')
for produce_line in product_produce.produce_line_ids:
produce_line.qty_done = produce_line.qty_to_consume + 1
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.")