Files
enterprise-addons/mrp_workorder/tests/test_workorder.py
T

738 lines
33 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import unittest
from odoo import Command, fields
from odoo.addons.mrp_workorder.tests.common import TestMrpWorkorderCommon
from odoo.addons.base.tests.common import HttpCase
from odoo.tests import Form, tagged
from odoo.tools import mute_logger
from odoo.exceptions import UserError
from datetime import timedelta
from freezegun import freeze_time
import logging
_logger = logging.getLogger(__name__)
class TestWorkOrder(TestMrpWorkorderCommon):
@classmethod
def setUpClass(cls):
super(TestWorkOrder, cls).setUpClass()
cls.env.ref('base.group_user').write({'implied_ids': [
Command.link(cls.env.ref('mrp.group_mrp_routings').id),
Command.link(cls.env.ref('stock.group_production_lot').id),
]})
# Products and lots
cls.submarine_pod = cls.env['product.product'].create({
'name': 'Submarine pod',
'is_storable': True,
'tracking': 'serial'})
cls.sp1 = cls.env['stock.lot'].create({
'product_id': cls.submarine_pod.id,
'name': 'sp1',
})
cls.sp2 = cls.env['stock.lot'].create({
'product_id': cls.submarine_pod.id,
'name': 'sp2',
})
cls.sp3 = cls.env['stock.lot'].create({
'product_id': cls.submarine_pod.id,
'name': 'sp3',
})
cls.elon_musk = cls.env['product.product'].create({
'name': 'Elon Musk',
'is_storable': True,
'tracking': 'serial'})
cls.elon1 = cls.env['stock.lot'].create({
'product_id': cls.elon_musk.id,
'name': 'elon1',
})
cls.elon2 = cls.env['stock.lot'].create({
'product_id': cls.elon_musk.id,
'name': 'elon2',
})
cls.elon3 = cls.env['stock.lot'].create({
'product_id': cls.elon_musk.id,
'name': 'elon3',
})
cls.metal_cylinder = cls.env['product.product'].create({
'name': 'Metal cylinder',
'is_storable': True,
'tracking': 'lot'})
cls.mc1 = cls.env['stock.lot'].create({
'product_id': cls.metal_cylinder.id,
'name': 'mc1',
})
cls.trapped_child = cls.env['product.product'].create({
'name': 'Trapped child',
'is_storable': True,
'tracking': 'none'})
# Bill of material
cls.bom_submarine = cls.env['mrp.bom'].create({
'product_tmpl_id': cls.submarine_pod.product_tmpl_id.id,
'product_qty': 1.0,
'operation_ids': [
(0, 0, {'name': 'Cutting Machine', 'workcenter_id': cls.workcenter_1.id, 'time_cycle': 12, 'sequence': 1}),
(0, 0, {'name': 'Weld Machine', 'workcenter_id': cls.workcenter_1.id, 'time_cycle': 18, 'sequence': 2}),
]})
cls.env['mrp.bom.line'].create({
'product_id': cls.elon_musk.id,
'product_qty': 1.0,
'bom_id': cls.bom_submarine.id,
'operation_id': cls.bom_submarine.operation_ids[1].id})
cls.env['mrp.bom.line'].create({
'product_id': cls.trapped_child.id,
'product_qty': 12.0,
'bom_id': cls.bom_submarine.id})
cls.env['mrp.bom.line'].create({
'product_id': cls.metal_cylinder.id,
'product_qty': 2.0,
'bom_id': cls.bom_submarine.id,
'operation_id': cls.bom_submarine.operation_ids[0].id})
cls.operation_4 = cls.env['mrp.routing.workcenter'].create({
'name': 'Rescue operation',
'workcenter_id': cls.workcenter_1.id,
'bom_id': cls.bom_submarine.id,
'time_cycle': 13,
'sequence': 2})
#cls.mrp_routing_0 = cls.env['mrp.routing'].create({
# 'name': 'Primary Assembly',
#})
#cls.mrp_routing_1 = cls.env['mrp.routing'].create({
# 'name': 'Secondary Assembly',
#})
cls.mrp_workcenter_1 = cls.env['mrp.workcenter'].create({
'name': 'Drill Station 1',
'resource_calendar_id': cls.env.ref('resource.resource_calendar_std').id,
})
cls.mrp_workcenter_3 = cls.env['mrp.workcenter'].create({
'name': 'Assembly Line 1',
'resource_calendar_id': cls.env.ref('resource.resource_calendar_std').id,
})
# --------------
# .mrp_routing_0
# --------------
# cls.mrp_routing_workcenter_0 = cls.env['mrp.routing.workcenter'].create({
# #'routing_id': cls.mrp_routing_0.id,
# 'workcenter_id': cls.mrp_workcenter_3.id,
# 'name': 'Manual Assembly',
# 'time_cycle': 60,
# })
#
# # -------------
# # mpr_routing_1
# # -------------
# cls.mrp_routing_workcenter_1 = cls.env['mrp.routing.workcenter'].create({
# #'routing_id': cls.mrp_routing_1.id,
# 'workcenter_id': cls.mrp_workcenter_3.id,
# 'name': 'Long time assembly',
# 'time_cycle': 180,
# 'sequence': 15,
# })
# cls.mrp_routing_workcenter_3 = cls.env['mrp.routing.workcenter'].create({
# #'routing_id': cls.mrp_routing_1.id,
# 'workcenter_id': cls.mrp_workcenter_3.id,
# 'name': 'Testing',
# 'time_cycle': 60,
# 'sequence': 10,
# })
# cls.mrp_routing_workcenter_4 = cls.env['mrp.routing.workcenter'].create({
# #'routing_id': cls.mrp_routing_1.id,
# 'workcenter_id': cls.mrp_workcenter_1.id,
# 'name': 'Packing',
# 'time_cycle': 30,
# 'sequence': 5,
# })
# Update quantities
Quant = cls.env['stock.quant']
Quant._update_available_quantity(cls.elon_musk, cls.stock_location, 1.0, lot_id=cls.elon1)
Quant._update_available_quantity(cls.elon_musk, cls.stock_location, 1.0, lot_id=cls.elon2)
Quant._update_available_quantity(cls.elon_musk, cls.stock_location, 1.0, lot_id=cls.elon3)
Quant._update_available_quantity(cls.metal_cylinder, cls.stock_location, 6.0, lot_id=cls.mc1)
Quant._update_available_quantity(cls.trapped_child, cls.stock_location, 36.0)
def test_assign_1(self):
unit = self.uom_unit.id
custom_laptop = self.env['product.product'].create({
'name': 'Drawer',
'is_storable': True,
'uom_id': unit,
})
custom_laptop.tracking = 'none'
product_charger = self.env['product.product'].create({
'name': 'Charger',
'is_storable': True,
'tracking': 'lot',
'uom_id': unit,
})
product_keybord = self.env['product.product'].create({
'name': 'Usb Keybord',
'is_storable': True,
'uom_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,
#'routing_id': self.mrp_routing_0.id,
# inlined mrp_routing0
'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
})],
'operation_ids': [(0, 0, {
'workcenter_id': self.mrp_workcenter_3.id,
'name': 'Manual Assembly',
'time_cycle': 60,
})]
})
production_form = Form(self.env['mrp.production'])
production_form.product_id = custom_laptop
production_form.bom_id = bom_custom_laptop
production_form.product_qty = 2
production = production_form.save()
production.action_confirm()
production.button_plan()
workorder = production.workorder_ids
self.assertTrue(workorder)
self.env['stock.quant']._update_available_quantity(product_charger, self.stock_location, 5)
self.env['stock.quant']._update_available_quantity(product_keybord, self.stock_location, 5)
production.action_assign()
def test_flexible_consumption_2(self):
""" Production with a flexible consumption
Check that consuming different quantities than planned doensn't trigger
any error"""
self.bom_submarine.consumption = 'flexible'
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = self.submarine_pod
mo_form.bom_id = self.bom_submarine
mo_form.product_qty = 1
mo = mo_form.save()
mo.action_confirm()
mo.action_assign()
mo.button_plan()
sorted_workorder_ids = mo.workorder_ids.sorted()
wo = sorted_workorder_ids[0]
wo.button_start()
wo.finished_lot_ids = self.sp1
self.assertEqual(wo.move_raw_ids.move_line_ids[0].lot_id, self.mc1, 'The suggested lot is wrong')
wo.move_raw_ids.move_line_ids[0].quantity = 1
wo.move_raw_ids.picked = True
wo.do_finish()
wo = sorted_workorder_ids[1]
wo.button_start()
self.assertEqual(wo.finished_lot_ids, self.sp1, 'The suggested final product is wrong')
self.assertEqual(wo.move_raw_ids.move_line_ids[0].lot_id, self.elon1, 'The suggested lot is wrong')
wo.move_raw_ids.move_line_ids[0].quantity = 1
wo.move_raw_ids.picked = True
wo.move_raw_ids.move_line_ids[0].copy({'lot_id': self.elon2.id, 'quantity': 1})
wo.do_finish()
wo = sorted_workorder_ids[2]
wo.button_start()
self.assertEqual(wo.finished_lot_ids, self.sp1, 'The suggested final product is wrong')
wo.do_finish()
mo.move_raw_ids.filtered(lambda m: not m.operation_id).picked = True
mo.button_mark_done()
move_1 = mo.move_raw_ids.filtered(lambda move: move.product_id == self.metal_cylinder and move.state == 'done')
self.assertEqual(sum(move_1.mapped('quantity')), 1, 'Only one cylinder was consumed')
move_2 = mo.move_raw_ids.filtered(lambda move: move.product_id == self.elon_musk and move.state == 'done')
self.assertEqual(sum(move_2.mapped('quantity')), 2, '2 Elon Musk was consumed')
move_3 = mo.move_raw_ids.filtered(lambda move: move.product_id == self.trapped_child and move.state == 'done')
self.assertEqual(sum(move_3.mapped('quantity')), 12, '12 child was consumed')
self.assertEqual(mo.state, 'done', 'Final state of the MO should be "done"')
def test_workorder_1(self):
# get the computer sc234 demo data
prod = self.submarine_pod
bom = self.bom_submarine
mrp_order_form = Form(self.env['mrp.production'])
mrp_order_form.product_id = self.submarine_pod
mrp_order_form.product_qty = 1
production = mrp_order_form.save()
# plan the work orders
production.button_plan()
def test_suggested_lot_in_multi_step(self):
"""Suggest the assigned lot in multi step system."""
self.env['quality.point'].create({
'product_ids': [Command.link(self.submarine_pod.id)],
'picking_type_ids': [Command.link(self.picking_type_manu.id)],
'operation_id': self.bom_submarine.operation_ids[0].id,
'test_type_id': self.env.ref('mrp_workorder.test_type_register_consumed_materials').id,
'component_id': self.elon_musk.id,
})
self.warehouse_1.manufacture_steps = 'pbm'
self.submarine_pod.tracking = 'none'
self.bom_submarine.bom_line_ids.filtered(lambda l: l.product_id.id != self.elon_musk.id).unlink()
self.bom_submarine.operation_ids[1:].unlink()
mrp_order_form = Form(self.env['mrp.production'])
mrp_order_form.product_id = self.submarine_pod
mrp_order_form.product_qty = 1
production = mrp_order_form.save()
production.action_confirm()
production.button_plan()
production.picking_ids.action_assign()
production.picking_ids.move_line_ids.lot_id = self.elon2
production.picking_ids.button_validate()
wo = production.workorder_ids
wo.button_start()
self.assertEqual(production.move_raw_ids.move_line_ids.lot_id, self.elon2, "Lot should be assigned.")
self.assertEqual(wo.current_quality_check_id.lot_ids, self.elon2, "Lot should be set in the step")
def test_step_by_product_variant(self):
who_attr = self.env['product.attribute'].create({'name': 'Who?'})
a1 = self.env['product.attribute.value'].create({'name': 'V0hFCg==', 'attribute_id': who_attr.id})
a2 = self.env['product.attribute.value'].create({'name': 'QVJN', 'attribute_id': who_attr.id})
a3 = self.env['product.attribute.value'].create({'name': 'UllW', 'attribute_id': who_attr.id})
product_who = self.env['product.template'].create({
'name': 'Odoo staff',
'attribute_line_ids': [(0, 0, {
'attribute_id': who_attr.id,
'value_ids': [(6, 0, [a1.id, a2.id, a3.id])],
})]
})
product_a1 = product_who.product_variant_ids.filtered(lambda v: a1 in v.product_template_attribute_value_ids.product_attribute_value_id)
product_a2 = product_who.product_variant_ids.filtered(lambda v: a2 in v.product_template_attribute_value_ids.product_attribute_value_id)
product_a3 = product_who.product_variant_ids.filtered(lambda v: a3 in v.product_template_attribute_value_ids.product_attribute_value_id)
bom_who = self.env['mrp.bom'].create({
'code': 'To be ready',
'product_tmpl_id': product_who.id,
'product_qty': 1.0,
'bom_line_ids': [
(0, 0, {'product_id': self.trapped_child.id, 'product_qty': 1}),
],
'operation_ids': [
(0, 0, {'name': 'Be ready', 'workcenter_id': self.workcenter_1.id, 'time_cycle': 60, 'sequence': 1}),
]
})
manufacture_picking_type = self.env['stock.picking.type'].search([('code', '=', 'mrp_operation')], limit=1)
p1 = self.env['quality.point'].create({
'product_ids': [Command.link(product_a1.id)],
'picking_type_ids': [Command.link(manufacture_picking_type.id)],
'operation_id': bom_who.operation_ids[0].id,
'test_type_id': self.env.ref('quality.test_type_instructions').id,
'note': 'Installing VIM (pcs xi ipzth adi du ixbt)',
})
# Check that the operation is removed when the picking type is updated and none have the mrp_operation code.
self.assertTrue(p1.operation_id)
p1.picking_type_ids = self.env['stock.picking.type'].search([('code', '=', 'internal'), ('company_id', '=', self.env.company.id)], limit=1)
self.assertFalse(p1.operation_id)
# reset manufacture_picking_type
p1.picking_type_ids = manufacture_picking_type
p1.operation_id = bom_who.operation_ids[0].id,
self.assertTrue(p1.operation_id)
p2 = self.env['quality.point'].create({
'product_ids': [Command.link(product_a2.id)],
'picking_type_ids': [Command.link(self.env['stock.picking.type'].search([('code', '=', 'mrp_operation')], limit=1).id)],
'operation_id': bom_who.operation_ids[0].id,
'test_type_id': self.env.ref('quality.test_type_instructions').id,
'note': 'Taking lot of coffee with UElN',
})
mrp_order_form = Form(self.env['mrp.production'])
mrp_order_form.product_id = product_a1
production_a1 = mrp_order_form.save()
mrp_order_form = Form(self.env['mrp.production'])
mrp_order_form.product_id = product_a2
production_a2 = mrp_order_form.save()
mrp_order_form = Form(self.env['mrp.production'])
mrp_order_form.product_id = product_a3
production_a3 = mrp_order_form.save()
self.assertEqual(production_a1.bom_id, bom_who)
self.assertEqual(production_a2.bom_id, bom_who)
self.assertEqual(production_a3.bom_id, bom_who)
production_a1.action_confirm()
(production_a2 | production_a3).action_confirm()
self.assertEqual(len(production_a1.workorder_ids.check_ids), 1)
self.assertEqual(production_a1.workorder_ids.check_ids.point_id, p1)
self.assertEqual(len(production_a2.workorder_ids.check_ids), 1)
self.assertEqual(production_a2.workorder_ids.check_ids.point_id, p2)
self.assertEqual(len(production_a3.workorder_ids.check_ids), 0)
def test_add_workorder_into_a_backorder(self):
""" Checks a new workorder can be created and processed into a backorder."""
(self.submarine_pod | self.elon_musk | self.metal_cylinder).tracking = 'none'
self.bom_submarine.consumption = 'flexible'
def process_workorder(workorders, qty, next_and_finish=False):
for wo in workorders:
wo.button_start()
wo_form = Form(wo, view='mrp_workorder.mrp_workorder_view_form_tablet')
wo_form.qty_producing = qty
wo = wo_form.save()
if next_and_finish:
if wo.current_quality_check_id:
wo.current_quality_check_id._next()
wo.do_finish()
# Creates a MO with 2 WO.
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = self.submarine_pod
mo_form.product_qty = 10
mo = mo_form.save()
mo.action_confirm()
mo.button_plan()
# Processes partially the workorders.
process_workorder(mo.workorder_ids.sorted(), 2)
# Marks the MO as done and creates a backorder.
Form.from_action(self.env, mo.button_mark_done()).save().action_backorder()
backorder = mo.production_group_id.production_ids[-1]
self.assertEqual(mo.state, 'done')
self.assertEqual(backorder.state, 'confirmed')
self.assertEqual(len(backorder.workorder_ids), 3)
self.assertEqual(backorder.workorder_ids.mapped('qty_production'), [8.0, 8.0, 8.0])
# Adds a new WO in the backorder.
mo_form = Form(backorder)
with mo_form.workorder_ids.new() as wo_line:
wo_line.name = "OP-SP"
wo_line.workcenter_id = self.workcenter_1
mo_form.save()
# Again, processes partially the workorders.
process_workorder(backorder.workorder_ids.sorted(), 5, True)
def test_backorder_with_reserved_qty_in_sublocation(self):
"""
Let's produce a MO based on a BoM with a storable component C and a
workorder. There are some C available in a sublocation SL. The user
reserves the needed quantities, then processes a part of the MO and
creates a backorder. On the backorder, the production should be
pre-completed: the qty_producing should be set and the consumed quantity
of C should come from SL
"""
location = self.shelf_1
compo = self.bom_4.bom_line_ids.product_id
compo.is_storable = True
self.env['stock.quant']._update_available_quantity(compo, location, 3)
mo_form = Form(self.env['mrp.production'])
mo_form.bom_id = self.bom_4
mo_form.product_qty = 3
mo = mo_form.save()
mo.action_confirm()
mo.action_assign()
self.assertEqual(mo.move_raw_ids.move_line_ids.quantity, 3)
self.assertEqual(mo.move_raw_ids.move_line_ids.location_id, location)
with Form(mo) as mo_form:
mo_form.qty_producing = 1
action = mo.button_mark_done()
backorder_form = Form.from_action(self.env, action)
backorder_form.save().action_backorder()
backorder = mo.production_group_id.production_ids[1]
self.assertEqual(mo.move_raw_ids.move_line_ids.quantity, 1)
self.assertEqual(mo.move_raw_ids.move_line_ids.location_id, location)
self.assertEqual(backorder.move_raw_ids.move_line_ids.quantity, 2)
self.assertEqual(backorder.move_raw_ids.move_line_ids.location_id, location)
with Form(backorder) as bo_form:
bo_form.qty_producing = 2
self.assertEqual(backorder.move_raw_ids.move_line_ids.quantity, 2)
self.assertEqual(backorder.move_raw_ids.move_line_ids.location_id, location)
backorder.button_mark_done()
self.assertEqual(backorder.state, 'done')
self.assertEqual(backorder.move_raw_ids.state, 'done')
self.assertEqual(backorder.move_raw_ids.move_line_ids.quantity, 2)
self.assertEqual(backorder.move_raw_ids.move_line_ids.location_id, location)
def test_split_mo_finished_wo_transition(self):
""" Check that if WOs are done out of order, then backordered/split WOs are not
started when they should not be started
"""
simple_bom = self.env['mrp.bom'].create({
'product_tmpl_id': self.product_1.product_tmpl_id.id,
'product_qty': 1.0,
'operation_ids': [
(0, 0, {'name': 'OP1', 'workcenter_id': self.workcenter_1.id, 'time_cycle': 12, 'sequence': 1}),
(0, 0, {'name': 'OP2', 'workcenter_id': self.workcenter_2.id, 'time_cycle': 12, 'sequence': 1}),
]})
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = self.product_1
mo_form.bom_id = simple_bom
mo_form.product_qty = 2
mo = mo_form.save()
mo.action_confirm()
action = mo.action_split()
wizard = Form.from_action(self.env, action)
wizard.max_batch_size = 1
action = wizard.save().action_split()
# Should have 2 mos w/ 2 wos each
self.assertEqual(len(mo.production_group_id.production_ids), 2)
mo2 = mo.production_group_id.production_ids[1]
self.assertEqual(len(mo.workorder_ids), 2)
self.assertEqual(len(mo2.workorder_ids), 2)
self.assertEqual(mo.state, 'confirmed')
self.assertEqual(mo2.state, 'confirmed')
wo1_1, wo1_2 = mo.workorder_ids.sorted()
wo2_1, wo2_2 = mo2.workorder_ids.sorted()
self.assertEqual(wo1_1.state, 'ready')
self.assertEqual(wo1_2.state, 'blocked')
self.assertEqual(wo2_1.state, 'ready')
self.assertEqual(wo2_2.state, 'blocked')
wo1_1.qty_producing = 1
wo1_1.do_finish()
self.assertEqual(wo1_1.state, 'done')
self.assertEqual(wo1_2.state, 'ready')
self.assertEqual(wo2_1.state, 'ready', "Completion of first MO's WO no longer auto-start second MO's first WO")
self.assertEqual(wo2_2.state, 'blocked')
wo1_2.do_finish()
self.assertEqual(wo1_1.state, 'done')
self.assertEqual(wo1_2.state, 'done')
self.assertEqual(wo2_1.state, 'ready')
self.assertEqual(wo2_2.state, 'blocked')
self.assertEqual(mo.state, 'to_close')
def test_workorder_tracked_final_product(self):
"""
Suppose that you have an MO for a prodcut tracked by SN with two operations.
Check that you can mark the operations as done without registering an SN.
"""
tracked_product = self.env['product.product'].create({
'name': 'Final product',
'is_storable': True,
'tracking': 'serial',
})
comp_1, comp_2 = self.product_1, self.product_2
(comp_1 | comp_2).is_storable = True
self.env['stock.quant']._update_available_quantity(comp_1, self.stock_location, 5)
self.env['stock.quant']._update_available_quantity(comp_2, self.stock_location, 5)
bom = self.env['mrp.bom'].create({
'product_tmpl_id': tracked_product.product_tmpl_id.id,
'product_qty': 1.0,
'operation_ids': [
Command.create({'name': 'OP1', 'workcenter_id': self.workcenter_1.id, 'time_cycle': 12, 'sequence': 1}),
Command.create({'name': 'OP2', 'workcenter_id': self.workcenter_2.id, 'time_cycle': 12, 'sequence': 1}),
],
'bom_line_ids': [
Command.create({'product_id': comp_1.id, 'product_qty': 1}),
Command.create({'product_id': comp_2.id, 'product_qty': 1}),
],
})
bom.bom_line_ids[0].operation_id = bom.operation_ids[0]
bom.bom_line_ids[1].operation_id = bom.operation_ids[1]
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = self.product_1
mo_form.bom_id = bom
mo_form.product_qty = 1
mo = mo_form.save()
mo.action_confirm()
# Operation 1 & 2: consume components and mark as done
raw_move_1, raw_move_2 = mo.move_raw_ids
operation_1, operation_2 = mo.workorder_ids
operation_1.button_start()
operation_1.qty_producing = 1.0
raw_move_1.move_line_ids[0].quantity = 1.0
operation_1.do_finish()
self.assertRecordValues(operation_1, [{'qty_produced': 1.0, 'finished_lot_ids': [], 'state': 'done'}])
operation_2.button_start()
operation_2.qty_producing = 1.0
raw_move_2.move_line_ids[0].quantity = 1.0
operation_2.do_finish()
self.assertRecordValues(operation_2, [{'qty_produced': 1.0, 'finished_lot_ids': [], 'state': 'done'}])
self.assertEqual(mo.state, 'to_close')
# Try to finish the production without assigning an SN
mo.move_raw_ids.filtered(lambda m: not m.operation_id).picked = True
mo.button_mark_done()
self.assertEqual(operation_1.finished_lot_ids, mo.lot_producing_ids)
self.assertEqual(operation_2.finished_lot_ids, mo.lot_producing_ids)
self.assertEqual(mo.state, 'done')
@freeze_time('2025-10-01')
def test_workorder_duration_inverse_time_ids_consistency(self):
"""
Verify that the inverse method of the workorder duration field correctly
mirrors the logic of its compute method.
Specifically, the duration is computed as the total interval duration,
counting overlapping time_ids only once. This ensures that the inverse
method does not accidentally unlink any time_ids when updating durations.
"""
now = fields.Datetime.now()
mo = self.env['mrp.production'].create({
'product_qty': 1,
'product_id': self.product_1.id,
'workorder_ids': [Command.create({
'name': 'workorder_1',
'workcenter_id': self.workcenter_1.id,
'time_ids': [
Command.create({
'workcenter_id': self.workcenter_1.id,
'date_start': now - timedelta(minutes=30),
'date_end': now,
'loss_id': self.ref('mrp.block_reason4'),
}),
Command.create({
'workcenter_id': self.workcenter_1.id,
'date_start': now - timedelta(minutes=20),
'date_end': now - timedelta(minutes=5),
'loss_id': self.ref('mrp.block_reason4'),
}),
],
})],
})
wo = mo.workorder_ids
wo.duration = wo.duration
self.assertEqual(wo.duration, 30.0)
self.assertRecordValues(wo.time_ids, [{'duration': 30.0}, {'duration': 15.0}])
def test_workorder_timelog_for_assigned_employee(self):
"""This test ensures that when a work order is assigned to an employee (not the current/logged-in user)
and is marked as done by the current user, the time log for workorder is correctly recorded
under the assigned employee instead of the current user.
"""
# Create employees
assigned_employee, current_employee = self.env['hr.employee'].create([
{'name': 'Assigned Employee'},
{'name': 'Current User Employee', 'user_id': self.env.user.id},
])
# Create and confirm a Manufacturing Order with workorders.
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = self.submarine_pod
mo_form.bom_id = self.bom_submarine
mo = mo_form.save()
mo.action_confirm()
workorder_1, workorder_2 = mo.workorder_ids[:2]
# Assign a different employee to the workorder_1 and keep workorder_2 unassigned.
workorder_1.employee_assigned_ids = assigned_employee
# mark the workorders as done.
mo.workorder_ids.action_mark_as_done()
# The time log should be assigned to the employee assigned in the workorder_1,
# and to the current user in workorder_2.
self.assertEqual(workorder_1.time_ids[0].employee_id, assigned_employee)
self.assertEqual(workorder_2.time_ids[0].employee_id, current_employee)
def test_update_bom_update_operation_workorder(self):
"""
When an operation is edited from the BOM and action_update_bom is called on a
confirmed MO, the workorder related to that operation must be updated
"""
final_product = self.env['product.product'].create({'name': 'Final Product', 'is_storable': True})
component_1 = self.env['product.product'].create({'name': 'Component 1', 'is_storable': True})
component_2 = self.env['product.product'].create({'name': 'Component 2', 'is_storable': True})
bom = self.env['mrp.bom'].create({
'product_tmpl_id': final_product.product_tmpl_id.id,
'product_qty': 1.0,
'bom_line_ids': [
Command.create({'product_id': component_1.id, 'product_qty': 1}),
Command.create({'product_id': component_2.id, 'product_qty': 1}),
],
'operation_ids': [
Command.create({'name': 'Operation 1', 'workcenter_id': self.workcenter_1.id}),
Command.create({'name': 'Operation 2', 'workcenter_id': self.workcenter_1.id}),
],
})
operation_to_update = bom.operation_ids[0]
mo = self.env['mrp.production'].create({
'product_id': final_product.id,
'bom_id': bom.id,
'product_qty': 1.0,
})
mo.action_confirm()
self.assertEqual(mo.state, 'confirmed')
instruction = self.env['quality.point'].create({
'product_ids': [Command.link(final_product.id)],
'operation_id': operation_to_update.id,
'picking_type_ids': [Command.link(mo.picking_type_id.id)],
'test_type_id': self.ref('mrp_workorder.test_type_register_consumed_materials'),
'component_id': component_1.id
})
operation_to_update.quality_point_ids = instruction
mo.action_update_bom()
self.assertRecordValues(mo.workorder_ids, [
{'operation_id': operation_to_update.id, 'quality_point_ids': instruction.ids},
{'operation_id': bom.operation_ids.ids[1], 'quality_point_ids': []},
])
@tagged("post_install", "-at_install")
class TestShopFloor(HttpCase, TestMrpWorkorderCommon):
@unittest.skip # TODO: tour needs to be updated.
def test_access_shop_floor_with_multicompany(self):
"""
test the flow when we have multicompany situation and
we want to access shop floor from a company after switching
from the other one.
"""
company1 = self.env['res.company'].create({'name': 'Test Company'})
user_admin = self.env.ref('base.user_admin')
user_admin.write({
'company_ids': [Command.link(company1.id)],
'group_ids': [Command.link(self.env.ref('mrp.group_mrp_routings').id)],
})
submarine_pod = self.env['product.product'].with_company(company1).with_user(user_admin).create({
'name': 'Submarine pod',
'is_storable': True,
'tracking': 'serial'})
workcenter_2 = self.env['mrp.workcenter'].with_company(company1).with_user(user_admin).create({
'name': 'Nuclear Workcenter',
'time_start': 10,
'time_stop': 5,
'time_efficiency': 80,
})
bom_submarine = self.env['mrp.bom'].with_company(company1).with_user(user_admin).create({
'product_tmpl_id': submarine_pod.product_tmpl_id.id,
'product_qty': 1.0,
'operation_ids': [
(0, 0, {'name': 'Cutting Machine', 'workcenter_id': workcenter_2.id,
'time_cycle': 12, 'sequence': 1}),
]})
mo_form = Form(self.env['mrp.production'].with_company(
company1).with_user(user_admin))
mo_form.product_id = submarine_pod
mo_form.bom_id = bom_submarine
mo_form.product_qty = 1
mo = mo_form.save()
mo.action_confirm()
mo.action_assign()
mo.button_plan()
self.start_tour(
"/", 'test_access_shop_floor_with_multicompany', login="admin")