Files
odoo_source/addons/mrp/tests/test_order.py
T
William Henrotin eb89927019 [FIX] stock: split move line in extra move
_action_done() creates extra stock move when quantity_done is superior
to initial demand. This extra move should be, after confirmation, either
merge into the original stock_move, either stay extra but the move lines
still linked to the first one should be shared accordingly to the quantity
done. The fix 561b3461a0 adapted the code
for MRP to split the move lines but neither those cases are applied in
MRP because the extra move has no picking_id field.

condition :
  ```if merge_into_self and extra_move.picking_id:```
will therefore be false as well as
  ```if not merge_into_self:```

To be sure the two extra move management cases are complementary, this
Commit keeps make sure the conditions are mutualy exclusive.

closes odoo/odoo#40230

Opw: 2087864
X-original-commit: 4e8ac38a6d67bcd5040af0b580bfa43da7841548
Signed-off-by: William Henrotin <Whenrow@users.noreply.github.com>
2019-11-13 18:30:08 +00:00

1344 lines
62 KiB
Python

# -*- 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'].with_user(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'].with_user(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',
'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_start()
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'].with_user(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
self.env['stock.quant'].with_context(inventory_mode=True).create({
'product_id': self.product_2.id,
'inventory_quantity': 2.0,
'location_id': self.stock_location_14.id
})
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
self.env['stock.quant'].with_context(inventory_mode=True).create({
'product_id': self.product_2.id,
'inventory_quantity': 5.0,
'location_id': self.stock_location_14.id
})
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_split_move_line(self):
""" Consume more component quantity than the initial demand.
It should create extra move and share the quantity between the two stock
moves """
mo, bom, p_final, p1, p2 = self.generate_mo(qty_base_1=10, qty_final=1, qty_base_2=1)
bom.consumption = 'flexible'
mo.action_assign()
produce_form = Form(self.env['mrp.product.produce'].with_context({
'active_id': mo.id,
'active_ids': [mo.id],
}))
for i in range(len(produce_form.raw_workorder_line_ids)):
with produce_form.raw_workorder_line_ids.edit(i) as line:
line.qty_done += 1
product_produce = produce_form.save()
product_produce.do_produce()
self.assertEqual(len(mo.move_raw_ids), 2)
self.assertEqual(len(mo.move_raw_ids.mapped('move_line_ids')), 2)
self.assertEqual(mo.move_raw_ids[0].move_line_ids.mapped('qty_done'), [2])
self.assertEqual(mo.move_raw_ids[1].move_line_ids.mapped('qty_done'), [11])
self.assertEqual(mo.move_raw_ids[0].quantity_done, 2)
self.assertEqual(mo.move_raw_ids[1].quantity_done, 11)
mo.button_mark_done()
self.assertEqual(len(mo.move_raw_ids), 4)
self.assertEqual(len(mo.move_raw_ids.mapped('move_line_ids')), 4)
self.assertEqual(mo.move_raw_ids.mapped('quantity_done'), [1, 10, 1, 1])
self.assertEqual(mo.move_raw_ids.mapped('move_line_ids.qty_done'), [1, 10, 1, 1])
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['product.product'].create({
'name': 'Drawer',
'type': 'product',
'uom_id': unit,
'uom_po_id': unit,
})
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.stock_location_14.id
quant_before = custom_laptop.qty_available
inventory = self.env['stock.inventory'].create({
'name': 'Inventory Product Table',
'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_start()
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_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,
'company_id': self.env.company.id,
})
lot_2 = self.env['stock.production.lot'].create({
'name': 'lot2',
'product_id': p1.id,
'company_id': self.env.company.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.')
self.assertEqual(mo.move_finished_ids.product_uom_qty, 4)
mo.button_mark_done()
def test_update_quantity_2(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(qty_final=3)
self.assertEqual(len(mo), 1, 'MO should have been created')
self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 20)
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 = 2
produce_wizard = produce_form.save()
produce_wizard.do_produce()
mo.post_inventory()
update_quantity_wizard = self.env['change.production.qty'].create({
'mo_id': mo.id,
'product_qty': 5,
})
update_quantity_wizard.change_prod_qty()
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.button_mark_done()
self.assertEqual(sum(mo.move_raw_ids.filtered(lambda m: m.product_id == p1).mapped('quantity_done')), 20)
self.assertEqual(sum(mo.move_finished_ids.mapped('quantity_done')), 5)
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.raw_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.raw_workorder_line_ids._records[0]
line2 = produce_form.raw_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.raw_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,
'company_id': self.env.company.id,
})
lot_p1_2 = self.env['stock.production.lot'].create({
'name': 'lot2',
'product_id': p1.id,
'company_id': self.env.company.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.raw_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.raw_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.raw_workorder_line_ids), 2, 'You should have 2 produce lines left.')
for line in product_produce.raw_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.stock_location_components
self.stock_shelf_2 = self.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,
'company_id': self.env.company.id,
})
second_lot_for_p1 = self.env['stock.production.lot'].create({
'name': 'lot2',
'product_id': p1.id,
'company_id': self.env.company.id,
})
final_product_lot = self.env['stock.production.lot'].create({
'name': 'lot1',
'product_id': p_final.id,
'company_id': self.env.company.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.raw_workorder_line_ids)):
with produce_form.raw_workorder_line_ids.edit(i) as line:
line.qty_done += 1
product_produce = produce_form.save()
product_produce.finished_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.raw_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.stock_location_components
self.stock_shelf_2 = self.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_product_produce_6(self):
""" Plan 5 finished products, reserve and produce 3. Post the current production.
Simulate an unlock and edit and, on the opened moves, set the consumed quantity
to 3. Now, try to update the quantity to produce to 3. It should fail since there
are consumed quantities. Unlock and edit, remove the consumed quantities and
update the quantity to produce to 3."""
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, 20)
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 = 3
produce_wizard = produce_form.save()
produce_wizard.do_produce()
mo.post_inventory()
self.assertEqual(len(mo.move_raw_ids), 4)
mo.move_raw_ids.filtered(lambda m: m.state != 'done')[0].quantity_done = 3
update_quantity_wizard = self.env['change.production.qty'].create({
'mo_id': mo.id,
'product_qty': 3,
})
mo.move_raw_ids.filtered(lambda m: m.state != 'done')[0].quantity_done = 0
update_quantity_wizard.change_prod_qty()
self.assertEqual(len(mo.move_raw_ids), 2)
mo.button_mark_done()
self.assertTrue(all(s == 'done' for s in mo.move_raw_ids.mapped('state')))
self.assertEqual(sum(mo.move_raw_ids.mapped('move_line_ids.product_uom_qty')), 0)
def test_product_produce_7(self):
""" Add components in 2 different 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.stock_location_components
self.stock_shelf_2 = self.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.raw_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.raw_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.raw_workorder_line_ids), 2)
self.assertEqual(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1).qty_reserved, 4)
self.assertEqual(produce_wizard.raw_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.raw_workorder_line_ids), 3)
self.assertEqual(sum(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1).mapped('qty_reserved')), 1)
self.assertEqual(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1 and l.qty_reserved).qty_to_consume, 1)
self.assertEqual(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1 and not l.qty_reserved).qty_to_consume, 3)
self.assertEqual(produce_wizard.raw_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.raw_workorder_line_ids), 4)
self.assertEqual(sum(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1).mapped('qty_reserved')), 1)
self.assertEqual(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1 and l.qty_reserved).qty_to_consume, 1)
self.assertEqual(produce_wizard.raw_workorder_line_ids.filtered(lambda l: l.product_id == p1 and not l.qty_reserved).qty_to_consume, 7)
self.assertEqual(sum(produce_wizard.raw_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.raw_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.raw_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.raw_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.raw_workorder_line_ids.new() as line:
line.product_id = p1
line.qty_done = 1
product_produce = produce_form.save()
product_produce.raw_workorder_line_ids[1].qty_done -= 1
product_produce.do_produce()
def test_product_produce_10(self):
""" Produce byproduct with serial, lot and not tracked.
byproduct1 serial 1.0
byproduct2 lot 2.0
byproduct3 none 1.0 dozen
Check qty producing update and moves finished values.
"""
dozen = self.env.ref('uom.product_uom_dozen')
self.byproduct1 = self.env['product.product'].create({
'name': 'Byproduct 1',
'type': 'product',
'tracking': 'serial'
})
self.serial_1 = self.env['stock.production.lot'].create({
'product_id': self.byproduct1.id,
'name': 'serial 1',
'company_id': self.env.company.id,
})
self.serial_2 = self.env['stock.production.lot'].create({
'product_id': self.byproduct1.id,
'name': 'serial 2',
'company_id': self.env.company.id,
})
self.byproduct2 = self.env['product.product'].create({
'name': 'Byproduct 2',
'type': 'product',
'tracking': 'lot',
})
self.lot_1 = self.env['stock.production.lot'].create({
'product_id': self.byproduct2.id,
'name': 'Lot 1',
'company_id': self.env.company.id,
})
self.lot_2 = self.env['stock.production.lot'].create({
'product_id': self.byproduct2.id,
'name': 'Lot 2',
'company_id': self.env.company.id,
})
self.byproduct3 = self.env['product.product'].create({
'name': 'Byproduct 3',
'type': 'product',
'tracking': 'none',
})
with Form(self.bom_1) as bom:
bom.product_qty = 1.0
with bom.byproduct_ids.new() as bp:
bp.product_id = self.byproduct1
bp.product_qty = 1.0
with bom.byproduct_ids.new() as bp:
bp.product_id = self.byproduct2
bp.product_qty = 2.0
with bom.byproduct_ids.new() as bp:
bp.product_id = self.byproduct3
bp.product_qty = 2.0
bp.product_uom_id = dozen
self.bom_1.routing_id = False
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = self.product_4
mo_form.bom_id = self.bom_1
mo_form.product_qty = 2
mo = mo_form.save()
mo.action_confirm()
produce_form = Form(self.env['mrp.product.produce'].with_context({
'active_id': mo.id,
'active_ids': [mo.id],
}))
self.assertEqual(len(produce_form.finished_workorder_line_ids), 4)
produce_wizard = produce_form.save()
wokorder_lines_byproduct_1 = produce_wizard.finished_workorder_line_ids.filtered(lambda l: l.product_id == self.byproduct1)
self.assertEqual(len(wokorder_lines_byproduct_1), 2)
self.assertEqual(wokorder_lines_byproduct_1.mapped('qty_to_consume'), [1.0, 1.0])
self.assertEqual(wokorder_lines_byproduct_1.mapped('qty_done'), [1.0, 1.0])
wokorder_lines_byproduct_2 = produce_wizard.finished_workorder_line_ids.filtered(lambda l: l.product_id == self.byproduct2)
self.assertEqual(len(wokorder_lines_byproduct_2), 1)
self.assertEqual(wokorder_lines_byproduct_2.qty_to_consume, 4.0)
self.assertEqual(wokorder_lines_byproduct_2.qty_done, 4.0)
wokorder_lines_byproduct_3 = produce_wizard.finished_workorder_line_ids.filtered(lambda l: l.product_id == self.byproduct3)
self.assertEqual(wokorder_lines_byproduct_3.qty_to_consume, 4.0)
self.assertEqual(wokorder_lines_byproduct_3.qty_done, 4.0)
self.assertEqual(wokorder_lines_byproduct_3.product_uom_id, dozen)
produce_form = Form(produce_wizard)
produce_form.qty_producing = 1.0
self.assertEqual(len(produce_form.finished_workorder_line_ids), 3)
produce_wizard = produce_form.save()
wokorder_lines_byproduct_1 = produce_wizard.finished_workorder_line_ids.filtered(lambda l: l.product_id == self.byproduct1)
self.assertEqual(len(wokorder_lines_byproduct_1), 1)
self.assertEqual(wokorder_lines_byproduct_1.qty_to_consume, 1.0)
self.assertEqual(wokorder_lines_byproduct_1.qty_done, 1.0)
wokorder_lines_byproduct_2 = produce_wizard.finished_workorder_line_ids.filtered(lambda l: l.product_id == self.byproduct2)
self.assertEqual(len(wokorder_lines_byproduct_2), 1)
self.assertEqual(wokorder_lines_byproduct_2.qty_to_consume, 2.0)
self.assertEqual(wokorder_lines_byproduct_2.qty_done, 2.0)
wokorder_lines_byproduct_3 = produce_wizard.finished_workorder_line_ids.filtered(lambda l: l.product_id == self.byproduct3)
self.assertEqual(wokorder_lines_byproduct_3.qty_to_consume, 2.0)
self.assertEqual(wokorder_lines_byproduct_3.qty_done, 2.0)
self.assertEqual(wokorder_lines_byproduct_3.product_uom_id, dozen)
produce_form = Form(produce_wizard)
wokorder_lines_byproduct_1.lot_id = self.serial_1
wokorder_lines_byproduct_2.lot_id = self.lot_1
produce_wizard = produce_form.save()
produce_wizard.do_produce()
produce_form = Form(self.env['mrp.product.produce'].with_context({
'active_id': mo.id,
'active_ids': [mo.id],
}))
self.assertEqual(produce_form.qty_producing, 1.0)
self.assertEqual(len(produce_form.finished_workorder_line_ids), 3)
produce_wizard = produce_form.save()
wokorder_lines_byproduct_1 = produce_wizard.finished_workorder_line_ids.filtered(lambda l: l.product_id == self.byproduct1)
self.assertEqual(len(wokorder_lines_byproduct_1), 1)
self.assertEqual(wokorder_lines_byproduct_1.qty_to_consume, 1.0)
self.assertEqual(wokorder_lines_byproduct_1.qty_done, 1.0)
wokorder_lines_byproduct_2 = produce_wizard.finished_workorder_line_ids.filtered(lambda l: l.product_id == self.byproduct2)
self.assertEqual(len(wokorder_lines_byproduct_2), 1)
self.assertEqual(wokorder_lines_byproduct_2.qty_to_consume, 2.0)
self.assertEqual(wokorder_lines_byproduct_2.qty_done, 2.0)
wokorder_lines_byproduct_3 = produce_wizard.finished_workorder_line_ids.filtered(lambda l: l.product_id == self.byproduct3)
self.assertEqual(wokorder_lines_byproduct_3.qty_to_consume, 2.0)
self.assertEqual(wokorder_lines_byproduct_3.qty_done, 2.0)
self.assertEqual(wokorder_lines_byproduct_3.product_uom_id, dozen)
produce_form = Form(produce_wizard)
wokorder_lines_byproduct_1.lot_id = self.serial_2
wokorder_lines_byproduct_2.lot_id = self.lot_2
wokorder_lines_byproduct_3.qty_done = 3.0
produce_wizard = produce_form.save()
produce_wizard.do_produce()
mo.button_mark_done()
move_lines_byproduct_1 = mo.move_finished_ids.filtered(lambda l: l.product_id == self.byproduct1).mapped('move_line_ids')
move_lines_byproduct_2 = mo.move_finished_ids.filtered(lambda l: l.product_id == self.byproduct2).mapped('move_line_ids')
move_lines_byproduct_3 = mo.move_finished_ids.filtered(lambda l: l.product_id == self.byproduct3).mapped('move_line_ids')
self.assertEqual(move_lines_byproduct_1.filtered(lambda ml: ml.lot_id == self.serial_1).qty_done, 1.0)
self.assertEqual(move_lines_byproduct_1.filtered(lambda ml: ml.lot_id == self.serial_2).qty_done, 1.0)
self.assertEqual(move_lines_byproduct_2.filtered(lambda ml: ml.lot_id == self.lot_1).qty_done, 2.0)
self.assertEqual(move_lines_byproduct_2.filtered(lambda ml: ml.lot_id == self.lot_2).qty_done, 2.0)
self.assertEqual(sum(move_lines_byproduct_3.mapped('qty_done')), 5.0)
self.assertEqual(move_lines_byproduct_3.mapped('product_uom_id'), dozen)
def test_product_produce_11(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.raw_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.raw_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.raw_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.raw_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.raw_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.raw_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.raw_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.raw_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.raw_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.raw_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_duplicate_1(self):
""" produce a finished product tracked by serial number 2 times with the
same SN. Check that an error is raised the second time"""
mo1, bom, p_final, p1, p2 = self.generate_mo(tracking_final='serial', qty_final=1, qty_base_1=1,)
produce_form = Form(self.env['mrp.product.produce'].with_context({
'active_id': mo1.id,
'active_ids': [mo1.id],
}))
product_produce = produce_form.save()
product_produce.action_generate_serial()
sn = product_produce.finished_lot_id
product_produce.do_produce()
mo1.button_mark_done()
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = p_final
mo_form.bom_id = bom
mo_form.product_qty = 1
mo2 = mo_form.save()
mo2.action_confirm()
produce_form = Form(self.env['mrp.product.produce'].with_context({
'active_id': mo2.id,
'active_ids': [mo2.id],
}))
produce_form.finished_lot_id = sn
product_produce = produce_form.save()
with self.assertRaises(UserError):
product_produce.do_produce()
def test_product_produce_duplicate_2(self):
""" produce a finished product with component tracked by serial number 2
times with the same SN. Check that an error is raised the second time"""
mo1, bom, p_final, p1, p2 = self.generate_mo(tracking_base_2='serial', qty_final=1, qty_base_1=1,)
sn = self.env['stock.production.lot'].create({
'name': 'sn used twice',
'product_id': p2.id,
'company_id': self.env.company.id,
})
produce_form = Form(self.env['mrp.product.produce'].with_context({
'active_id': mo1.id,
'active_ids': [mo1.id],
}))
with produce_form.raw_workorder_line_ids.edit(0) as line:
line.lot_id = sn
product_produce = produce_form.save()
product_produce.do_produce()
mo1.button_mark_done()
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = p_final
mo_form.bom_id = bom
mo_form.product_qty = 1
mo2 = mo_form.save()
mo2.action_confirm()
produce_form = Form(self.env['mrp.product.produce'].with_context({
'active_id': mo2.id,
'active_ids': [mo2.id],
}))
with produce_form.raw_workorder_line_ids.edit(0) as line:
line.lot_id = sn
product_produce = produce_form.save()
with self.assertRaises(UserError):
product_produce.do_produce()
def test_product_produce_duplicate_3(self):
""" produce a finished product with by-product tracked by serial number 2
times with the same SN. Check that an error is raised the second time"""
finished_product = self.env['product.product'].create({'name': 'finished product'})
byproduct = self.env['product.product'].create({'name': 'byproduct', 'tracking': 'serial'})
component = self.env['product.product'].create({'name': 'component'})
bom = self.env['mrp.bom'].create({
'product_id': finished_product.id,
'product_tmpl_id': finished_product.product_tmpl_id.id,
'product_uom_id': finished_product.uom_id.id,
'product_qty': 1.0,
'type': 'normal',
'bom_line_ids': [
(0, 0, {'product_id': component.id, 'product_qty': 1}),
],
'byproduct_ids': [
(0, 0, {'product_id': byproduct.id, 'product_qty': 1, 'product_uom_id': byproduct.uom_id.id})
]})
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = finished_product
mo_form.bom_id = bom
mo_form.product_qty = 1
mo = mo_form.save()
mo.action_confirm()
sn = self.env['stock.production.lot'].create({
'name': 'sn used twice',
'product_id': byproduct.id,
'company_id': self.env.company.id,
})
produce_form = Form(self.env['mrp.product.produce'].with_context({
'active_id': mo.id,
'active_ids': [mo.id],
}))
with produce_form.finished_workorder_line_ids.edit(0) as line:
line.lot_id = sn
product_produce = produce_form.save()
product_produce.do_produce()
mo.button_mark_done()
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = finished_product
mo_form.bom_id = bom
mo_form.product_qty = 1
mo2 = mo_form.save()
mo2.action_confirm()
produce_form = Form(self.env['mrp.product.produce'].with_context({
'active_id': mo2.id,
'active_ids': [mo2.id],
}))
with produce_form.finished_workorder_line_ids.edit(0) as line:
line.lot_id = sn
product_produce = produce_form.save()
with self.assertRaises(UserError):
product_produce.do_produce()
def test_product_produce_duplicate_4(self):
""" Consuming the same serial number two times should not give an error if
a repair order of the first production has been made before the second one"""
mo1, bom, p_final, p1, p2 = self.generate_mo(tracking_base_2='serial', qty_final=1, qty_base_1=1,)
sn = self.env['stock.production.lot'].create({
'name': 'sn used twice',
'product_id': p2.id,
'company_id': self.env.company.id,
})
produce_form = Form(self.env['mrp.product.produce'].with_context({
'active_id': mo1.id,
'active_ids': [mo1.id],
}))
with produce_form.raw_workorder_line_ids.edit(0) as line:
line.lot_id = sn
product_produce = produce_form.save()
product_produce.do_produce()
mo1.button_mark_done()
unbuild_form = Form(self.env['mrp.unbuild'])
unbuild_form.product_id = p_final
unbuild_form.bom_id = bom
unbuild_form.product_qty = 1
unbuild_form.mo_id = mo1
unbuild_order = unbuild_form.save()
unbuild_order.action_unbuild()
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = p_final
mo_form.bom_id = bom
mo_form.product_qty = 1
mo2 = mo_form.save()
mo2.action_confirm()
produce_form = Form(self.env['mrp.product.produce'].with_context({
'active_id': mo2.id,
'active_ids': [mo2.id],
}))
with produce_form.raw_workorder_line_ids.edit(0) as line:
line.lot_id = sn
product_produce = produce_form.save()
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."""
dozen = self.env.ref('uom.product_uom_dozen')
unit = self.env.ref('uom.product_uom_unit')
plastic_laminate = self.env['product.product'].create({
'name': 'Plastic Laminate',
'type': 'product',
'uom_id': unit.id,
'uom_po_id': unit.id,
'tracking': 'serial',
})
ply_veneer = self.env['product.product'].create({
'name': 'Ply Veneer',
'type': 'product',
'uom_id': unit.id,
'uom_po_id': unit.id,
})
routing = self.env['mrp.routing'].create({
'name': 'Secondary Assembly',
})
bom = self.env['mrp.bom'].create({
'product_tmpl_id': plastic_laminate.product_tmpl_id.id,
'product_uom_id': unit.id,
'sequence': 1,
'routing_id': routing.id,
'bom_line_ids': [(0, 0, {
'product_id': ply_veneer.id,
'product_qty': 1,
'product_uom_id': unit.id,
'sequence': 1,
})]
})
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,
'company_id': self.env.company.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.finished_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.finished_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.raw_workorder_line_ids), 2, 'Should be 2 lines since the component tracking is serial and quantity 2.')
self.assertEqual(product_produce.raw_workorder_line_ids[0].qty_to_consume, 1, 'Should be 1 unit since the tracking is serial and quantity 2.')
self.assertEqual(product_produce.raw_workorder_line_ids[0].product_uom_id, unit, 'Should be the product uom so "unit"')
self.assertEqual(product_produce.raw_workorder_line_ids[1].qty_to_consume, 1, 'Should be 1 unit since the tracking is serial and quantity 2.')
self.assertEqual(product_produce.raw_workorder_line_ids[1].product_uom_id, unit, 'should be the product uom so "unit"')
def test_product_type_service_1(self):
# Create finished product
finished_product = self.env['product.product'].create({
'name': 'Geyser',
'type': 'product',
})
# Create service type product
product_raw = self.env['product.product'].create({
'name': 'raw Geyser',
'type': 'service',
})
# Create bom for finish product
bom = self.env['mrp.bom'].create({
'product_id': finished_product.id,
'product_tmpl_id': finished_product.product_tmpl_id.id,
'product_uom_id': self.env.ref('uom.product_uom_unit').id,
'product_qty': 1.0,
'type': 'normal',
'bom_line_ids': [(5, 0), (0, 0, {'product_id': product_raw.id})]
})
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = finished_product
mo_form.bom_id = bom
mo_form.product_uom_id = self.env.ref('uom.product_uom_unit')
mo_form.product_qty = 1
mo = mo_form.save()
# Check Mo is created or not
self.assertTrue(mo, "Mo is created")