summaryrefslogtreecommitdiff
path: root/addons/mrp/tests/test_unbuild.py
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/mrp/tests/test_unbuild.py
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/mrp/tests/test_unbuild.py')
-rw-r--r--addons/mrp/tests/test_unbuild.py599
1 files changed, 599 insertions, 0 deletions
diff --git a/addons/mrp/tests/test_unbuild.py b/addons/mrp/tests/test_unbuild.py
new file mode 100644
index 00000000..159b4c3d
--- /dev/null
+++ b/addons/mrp/tests/test_unbuild.py
@@ -0,0 +1,599 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo.tests import Form
+from odoo.addons.mrp.tests.common import TestMrpCommon
+from odoo.exceptions import UserError
+
+
+class TestUnbuild(TestMrpCommon):
+ def setUp(self):
+ super(TestUnbuild, self).setUp()
+ self.stock_location = self.env.ref('stock.stock_location_stock')
+ self.env.ref('base.group_user').write({
+ 'implied_ids': [(4, self.env.ref('stock.group_production_lot').id)]
+ })
+
+ def test_unbuild_standart(self):
+ """ This test creates a MO and then creates 3 unbuild
+ orders for the final product. None of the products for this
+ test are tracked. It checks the stock state after each order
+ and ensure it is correct.
+ """
+ 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()
+
+ mo_form = Form(mo)
+ mo_form.qty_producing = 5.0
+ mo = mo_form.save()
+ mo.button_mark_done()
+ self.assertEqual(mo.state, 'done', "Production order should be in done state.")
+
+ # Check quantity in stock before unbuild.
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location), 5, 'You should have the 5 final product in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location), 80, 'You should have 80 products in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location), 0, 'You should have consumed all the 5 product in stock')
+
+ # ---------------------------------------------------
+ # unbuild
+ # ---------------------------------------------------
+
+ x = Form(self.env['mrp.unbuild'])
+ x.product_id = p_final
+ x.bom_id = bom
+ x.product_qty = 3
+ x.save().action_unbuild()
+
+
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location), 2, 'You should have consumed 3 final product in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location), 92, 'You should have 80 products in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location), 3, 'You should have consumed all the 5 product in stock')
+
+ x = Form(self.env['mrp.unbuild'])
+ x.product_id = p_final
+ x.bom_id = bom
+ x.product_qty = 2
+ x.save().action_unbuild()
+
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location), 0, 'You should have 0 finalproduct in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location), 100, 'You should have 80 products in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location), 5, 'You should have consumed all the 5 product in stock')
+
+ x = Form(self.env['mrp.unbuild'])
+ x.product_id = p_final
+ x.bom_id = bom
+ x.product_qty = 5
+ x.save().action_unbuild()
+
+ # Check quantity in stock after last unbuild.
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location, allow_negative=True), -5, 'You should have negative quantity for final product in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location), 120, 'You should have 80 products in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location), 10, 'You should have consumed all the 5 product in stock')
+
+ def test_unbuild_with_final_lot(self):
+ """ This test creates a MO and then creates 3 unbuild
+ orders for the final product. Only the final product is tracked
+ by lot. It checks the stock state after each order
+ and ensure it is correct.
+ """
+ mo, bom, p_final, p1, p2 = self.generate_mo(tracking_final='lot')
+ self.assertEqual(len(mo), 1, 'MO should have been created')
+
+ 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_location, 100)
+ self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5)
+ mo.action_assign()
+
+ mo_form = Form(mo)
+ mo_form.qty_producing = 5.0
+ mo_form.lot_producing_id = lot
+ mo = mo_form.save()
+
+ mo.button_mark_done()
+ self.assertEqual(mo.state, 'done', "Production order should be in done state.")
+
+ # Check quantity in stock before unbuild.
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location, lot_id=lot), 5, 'You should have the 5 final product in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location), 80, 'You should have 80 products in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location), 0, 'You should have consumed all the 5 product in stock')
+
+ # ---------------------------------------------------
+ # unbuild
+ # ---------------------------------------------------
+
+ # This should fail since we do not choose a lot to unbuild for final product.
+ with self.assertRaises(AssertionError):
+ x = Form(self.env['mrp.unbuild'])
+ x.product_id = p_final
+ x.bom_id = bom
+ x.product_qty = 3
+ unbuild_order = x.save()
+
+ x = Form(self.env['mrp.unbuild'])
+ x.product_id = p_final
+ x.bom_id = bom
+ x.product_qty = 3
+ x.lot_id = lot
+ x.save().action_unbuild()
+
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location, lot_id=lot), 2, 'You should have consumed 3 final product in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location), 92, 'You should have 80 products in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location), 3, 'You should have consumed all the 5 product in stock')
+
+ x = Form(self.env['mrp.unbuild'])
+ x.product_id = p_final
+ x.bom_id = bom
+ x.product_qty = 2
+ x.lot_id = lot
+ x.save().action_unbuild()
+
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location, lot_id=lot), 0, 'You should have 0 finalproduct in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location), 100, 'You should have 80 products in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location), 5, 'You should have consumed all the 5 product in stock')
+
+ x = Form(self.env['mrp.unbuild'])
+ x.product_id = p_final
+ x.bom_id = bom
+ x.product_qty = 5
+ x.lot_id = lot
+ x.save().action_unbuild()
+
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location, lot_id=lot, allow_negative=True), -5, 'You should have negative quantity for final product in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location), 120, 'You should have 80 products in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location), 10, 'You should have consumed all the 5 product in stock')
+
+ def test_unbuild_with_comnsumed_lot(self):
+ """ This test creates a MO and then creates 3 unbuild
+ orders for the final product. Only once of the two consumed
+ product is tracked by lot. It checks the stock state after each
+ order and ensure it is correct.
+ """
+ mo, bom, p_final, p1, p2 = self.generate_mo(tracking_base_1='lot')
+ self.assertEqual(len(mo), 1, 'MO should have been created')
+
+ lot = self.env['stock.production.lot'].create({
+ 'name': 'lot1',
+ 'product_id': p1.id,
+ 'company_id': self.env.company.id,
+ })
+
+ self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 100, lot_id=lot)
+ self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5)
+ mo.action_assign()
+ for ml in mo.move_raw_ids.mapped('move_line_ids'):
+ if ml.product_id.tracking != 'none':
+ self.assertEqual(ml.lot_id, lot, 'Wrong reserved lot.')
+
+ # FIXME sle: behavior change
+ mo_form = Form(mo)
+ mo_form.qty_producing = 5.0
+ mo = mo_form.save()
+ details_operation_form = Form(mo.move_raw_ids[1], view=self.env.ref('stock.view_stock_move_operations'))
+ with details_operation_form.move_line_ids.edit(0) as ml:
+ ml.lot_id = lot
+ ml.qty_done = 20
+ details_operation_form.save()
+
+ mo.button_mark_done()
+ self.assertEqual(mo.state, 'done', "Production order should be in done state.")
+ # Check quantity in stock before unbuild.
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location), 5, 'You should have the 5 final product in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location, lot_id=lot), 80, 'You should have 80 products in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location), 0, 'You should have consumed all the 5 product in stock')
+
+ # ---------------------------------------------------
+ # unbuild
+ # ---------------------------------------------------
+
+ x = Form(self.env['mrp.unbuild'])
+ x.product_id = p_final
+ x.bom_id = bom
+ x.product_qty = 3
+ unbuild_order = x.save()
+
+ # This should fail since we do not provide the MO that we wanted to unbuild. (without MO we do not know which consumed lot we have to restore)
+ with self.assertRaises(UserError):
+ unbuild_order.action_unbuild()
+
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location), 5, 'You should have consumed 3 final product in stock')
+
+ unbuild_order.mo_id = mo.id
+ unbuild_order.action_unbuild()
+
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location), 2, 'You should have consumed 3 final product in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location, lot_id=lot), 92, 'You should have 92 products in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location), 3, 'You should have consumed all the 5 product in stock')
+
+ x = Form(self.env['mrp.unbuild'])
+ x.product_id = p_final
+ x.bom_id = bom
+ x.mo_id = mo
+ x.product_qty = 2
+ x.save().action_unbuild()
+
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location), 0, 'You should have 0 finalproduct in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location, lot_id=lot), 100, 'You should have 80 products in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location), 5, 'You should have consumed all the 5 product in stock')
+
+ x = Form(self.env['mrp.unbuild'])
+ x.product_id = p_final
+ x.bom_id = bom
+ x.mo_id = mo
+ x.product_qty = 5
+ x.save().action_unbuild()
+
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location, allow_negative=True), -5, 'You should have negative quantity for final product in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location, lot_id=lot), 120, 'You should have 80 products in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location), 10, 'You should have consumed all the 5 product in stock')
+
+ def test_unbuild_with_everything_tracked(self):
+ """ This test creates a MO and then creates 3 unbuild
+ orders for the final product. All the products for this
+ test are tracked. It checks the stock state after each order
+ and ensure it is correct.
+ """
+ mo, bom, p_final, p1, p2 = self.generate_mo(tracking_final='lot', tracking_base_2='lot', tracking_base_1='lot')
+ self.assertEqual(len(mo), 1, 'MO should have been created')
+
+ lot_final = self.env['stock.production.lot'].create({
+ 'name': 'lot_final',
+ 'product_id': p_final.id,
+ 'company_id': self.env.company.id,
+ })
+ lot_1 = self.env['stock.production.lot'].create({
+ 'name': 'lot_consumed_1',
+ 'product_id': p1.id,
+ 'company_id': self.env.company.id,
+ })
+ lot_2 = self.env['stock.production.lot'].create({
+ 'name': 'lot_consumed_2',
+ 'product_id': p2.id,
+ 'company_id': self.env.company.id,
+ })
+
+ self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 100, lot_id=lot_1)
+ self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5, lot_id=lot_2)
+ mo.action_assign()
+
+ # FIXME sle: behavior change
+ mo_form = Form(mo)
+ mo_form.qty_producing = 5.0
+ mo_form.lot_producing_id = lot_final
+ mo = mo_form.save()
+ details_operation_form = Form(mo.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations'))
+ with details_operation_form.move_line_ids.edit(0) as ml:
+ ml.qty_done = 5
+ details_operation_form.save()
+ details_operation_form = Form(mo.move_raw_ids[1], view=self.env.ref('stock.view_stock_move_operations'))
+ with details_operation_form.move_line_ids.edit(0) as ml:
+ ml.qty_done = 20
+ details_operation_form.save()
+
+ mo.button_mark_done()
+ self.assertEqual(mo.state, 'done', "Production order should be in done state.")
+ # Check quantity in stock before unbuild.
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location, lot_id=lot_final), 5, 'You should have the 5 final product in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location, lot_id=lot_1), 80, 'You should have 80 products in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location, lot_id=lot_2), 0, 'You should have consumed all the 5 product in stock')
+
+ # ---------------------------------------------------
+ # unbuild
+ # ---------------------------------------------------
+
+ x = Form(self.env['mrp.unbuild'])
+ with self.assertRaises(AssertionError):
+ x.product_id = p_final
+ x.bom_id = bom
+ x.product_qty = 3
+ x.save()
+
+ with self.assertRaises(AssertionError):
+ x.product_id = p_final
+ x.bom_id = bom
+ x.product_qty = 3
+ x.save()
+
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location, lot_id=lot_final), 5, 'You should have consumed 3 final product in stock')
+
+ with self.assertRaises(AssertionError):
+ x.product_id = p_final
+ x.bom_id = bom
+ x.mo_id = mo
+ x.product_qty = 3
+ x.save()
+
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location, lot_id=lot_final), 5, 'You should have consumed 3 final product in stock')
+
+ x = Form(self.env['mrp.unbuild'])
+ x.product_id = p_final
+ x.bom_id = bom
+ x.mo_id = mo
+ x.product_qty = 3
+ x.lot_id = lot_final
+ x.save().action_unbuild()
+
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location, lot_id=lot_final), 2, 'You should have consumed 3 final product in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location, lot_id=lot_1), 92, 'You should have 92 products in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location, lot_id=lot_2), 3, 'You should have consumed all the 5 product in stock')
+
+ x = Form(self.env['mrp.unbuild'])
+ x.product_id = p_final
+ x.bom_id = bom
+ x.mo_id = mo
+ x.product_qty = 2
+ x.lot_id = lot_final
+ x.save().action_unbuild()
+
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location, lot_id=lot_final), 0, 'You should have 0 finalproduct in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location, lot_id=lot_1), 100, 'You should have 80 products in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location, lot_id=lot_2), 5, 'You should have consumed all the 5 product in stock')
+
+ x = Form(self.env['mrp.unbuild'])
+ x.product_id = p_final
+ x.bom_id = bom
+ x.mo_id = mo
+ x.product_qty = 5
+ x.lot_id = lot_final
+ x.save().action_unbuild()
+
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location, lot_id=lot_final, allow_negative=True), -5, 'You should have negative quantity for final product in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location, lot_id=lot_1), 120, 'You should have 80 products in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location, lot_id=lot_2), 10, 'You should have consumed all the 5 product in stock')
+
+ def test_unbuild_with_duplicate_move(self):
+ """ This test creates a MO from 3 different lot on a consumed product (p2).
+ The unbuild order should revert the correct quantity for each specific lot.
+ """
+ mo, bom, p_final, p1, p2 = self.generate_mo(tracking_final='none', tracking_base_2='lot', tracking_base_1='none')
+ self.assertEqual(len(mo), 1, 'MO should have been created')
+
+ lot_1 = self.env['stock.production.lot'].create({
+ 'name': 'lot_1',
+ 'product_id': p2.id,
+ 'company_id': self.env.company.id,
+ })
+ lot_2 = self.env['stock.production.lot'].create({
+ 'name': 'lot_2',
+ 'product_id': p2.id,
+ 'company_id': self.env.company.id,
+ })
+ lot_3 = self.env['stock.production.lot'].create({
+ 'name': 'lot_3',
+ 'product_id': p2.id,
+ 'company_id': self.env.company.id,
+ })
+ self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 100)
+ self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 1, lot_id=lot_1)
+ self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 3, lot_id=lot_2)
+ self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 2, lot_id=lot_3)
+ mo.action_assign()
+
+ mo_form = Form(mo)
+ mo_form.qty_producing = 5.0
+ mo = mo_form.save()
+ details_operation_form = Form(mo.move_raw_ids.filtered(lambda ml: ml.product_id == p2), view=self.env.ref('stock.view_stock_move_operations'))
+ with details_operation_form.move_line_ids.edit(0) as ml:
+ ml.qty_done = ml.product_uom_qty
+ with details_operation_form.move_line_ids.edit(1) as ml:
+ ml.qty_done = ml.product_uom_qty
+ with details_operation_form.move_line_ids.edit(2) as ml:
+ ml.qty_done = ml.product_uom_qty
+ details_operation_form.save()
+
+ mo.button_mark_done()
+ self.assertEqual(mo.state, 'done', "Production order should be in done state.")
+ # Check quantity in stock before unbuild.
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location), 5, 'You should have the 5 final product in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location), 80, 'You should have 80 products in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location, lot_id=lot_1), 0, 'You should have consumed all the 1 product for lot 1 in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location, lot_id=lot_2), 0, 'You should have consumed all the 3 product for lot 2 in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location, lot_id=lot_3), 1, 'You should have consumed only 1 product for lot3 in stock')
+
+ x = Form(self.env['mrp.unbuild'])
+ x.product_id = p_final
+ x.bom_id = bom
+ x.mo_id = mo
+ x.product_qty = 5
+ x.save().action_unbuild()
+
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p_final, self.stock_location), 0, 'You should have no more final product in stock after unbuild')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p1, self.stock_location), 100, 'You should have 80 products in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location, lot_id=lot_1), 1, 'You should have get your product with lot 1 in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location, lot_id=lot_2), 3, 'You should have the 3 basic product for lot 2 in stock')
+ self.assertEqual(self.env['stock.quant']._get_available_quantity(p2, self.stock_location, lot_id=lot_3), 2, 'You should have get one product back for lot 3')
+
+ def test_production_links_with_non_tracked_lots(self):
+ """ This test produces an MO in two times and checks that the move lines are linked in a correct way
+ """
+ mo, bom, p_final, p1, p2 = self.generate_mo(tracking_final='lot', tracking_base_1='none', tracking_base_2='lot')
+ # Young Tom
+ # \ Botox - 4 - p1
+ # \ Old Tom - 1 - p2
+ lot_1 = self.env['stock.production.lot'].create({
+ 'name': 'lot_1',
+ 'product_id': p2.id,
+ 'company_id': self.env.company.id,
+ })
+
+ self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 3, lot_id=lot_1)
+ lot_finished_1 = self.env['stock.production.lot'].create({
+ 'name': 'lot_finished_1',
+ 'product_id': p_final.id,
+ 'company_id': self.env.company.id,
+ })
+
+ self.assertEqual(mo.product_qty, 5)
+ mo_form = Form(mo)
+ mo_form.qty_producing = 3.0
+ mo_form.lot_producing_id = lot_finished_1
+ mo = mo_form.save()
+ self.assertEqual(mo.move_raw_ids[1].quantity_done, 12)
+ details_operation_form = Form(mo.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations'))
+ with details_operation_form.move_line_ids.new() as ml:
+ ml.qty_done = 3
+ ml.lot_id = lot_1
+ details_operation_form.save()
+ action = mo.button_mark_done()
+ backorder = Form(self.env[action['res_model']].with_context(**action['context']))
+ backorder.save().action_backorder()
+
+ lot_2 = self.env['stock.production.lot'].create({
+ 'name': 'lot_2',
+ 'product_id': p2.id,
+ 'company_id': self.env.company.id,
+ })
+
+ self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 4, lot_id=lot_2)
+ lot_finished_2 = self.env['stock.production.lot'].create({
+ 'name': 'lot_finished_2',
+ 'product_id': p_final.id,
+ 'company_id': self.env.company.id,
+ })
+
+ mo = mo.procurement_group_id.mrp_production_ids[1]
+ # FIXME sle: issue in backorder?
+ mo.move_raw_ids.move_line_ids.unlink()
+ self.assertEqual(mo.product_qty, 2)
+ mo_form = Form(mo)
+ mo_form.qty_producing = 2
+ mo_form.lot_producing_id = lot_finished_2
+ mo = mo_form.save()
+ details_operation_form = Form(mo.move_raw_ids[0], view=self.env.ref('stock.view_stock_move_operations'))
+ with details_operation_form.move_line_ids.new() as ml:
+ ml.qty_done = 2
+ ml.lot_id = lot_2
+ details_operation_form.save()
+ action = mo.button_mark_done()
+
+ mo1 = mo.procurement_group_id.mrp_production_ids[0]
+ ml = mo1.finished_move_line_ids[0].consume_line_ids.filtered(lambda m: m.product_id == p1 and lot_finished_1 in m.produce_line_ids.lot_id)
+ self.assertEqual(sum(ml.mapped('qty_done')), 12.0, 'Should have consumed 12 for the first lot')
+ ml = mo.finished_move_line_ids[0].consume_line_ids.filtered(lambda m: m.product_id == p1 and lot_finished_2 in m.produce_line_ids.lot_id)
+ self.assertEqual(sum(ml.mapped('qty_done')), 8.0, 'Should have consumed 8 for the second lot')
+
+ def test_unbuild_with_routes(self):
+ """ This test creates a MO of a stockable product (Table). A new route for rule QC/Unbuild -> Stock
+ is created with Warehouse -> True.
+ The unbuild order should revert the consumed components into QC/Unbuild location for quality check
+ and then a picking should be generated for transferring components from QC/Unbuild location to stock.
+ """
+ StockQuant = self.env['stock.quant']
+ ProductObj = self.env['product.product']
+ # Create new QC/Unbuild location
+ warehouse = self.env.ref('stock.warehouse0')
+ unbuild_location = self.env['stock.location'].create({
+ 'name': 'QC/Unbuild',
+ 'usage': 'internal',
+ 'location_id': warehouse.view_location_id.id
+ })
+
+ # Create a product route containing a stock rule that will move product from QC/Unbuild location to stock
+ product_route = self.env['stock.location.route'].create({
+ 'name': 'QC/Unbuild -> Stock',
+ 'warehouse_selectable': True,
+ 'warehouse_ids': [(4, warehouse.id)],
+ 'rule_ids': [(0, 0, {
+ 'name': 'Send Matrial QC/Unbuild -> Stock',
+ 'action': 'push',
+ 'picking_type_id': self.ref('stock.picking_type_internal'),
+ 'location_src_id': unbuild_location.id,
+ 'location_id': self.stock_location.id,
+ })],
+ })
+
+ # Create a stockable product and its components
+ finshed_product = ProductObj.create({
+ 'name': 'Table',
+ 'type': 'product',
+ })
+ component1 = ProductObj.create({
+ 'name': 'Table head',
+ 'type': 'product',
+ })
+ component2 = ProductObj.create({
+ 'name': 'Table stand',
+ 'type': 'product',
+ })
+
+ # Create bom and add components
+ bom = self.env['mrp.bom'].create({
+ 'product_id': finshed_product.id,
+ 'product_tmpl_id': finshed_product.product_tmpl_id.id,
+ 'product_uom_id': self.uom_unit.id,
+ 'product_qty': 1.0,
+ 'type': 'normal',
+ 'bom_line_ids': [
+ (0, 0, {'product_id': component1.id, 'product_qty': 1}),
+ (0, 0, {'product_id': component2.id, 'product_qty': 1})
+ ]})
+
+ # Set on hand quantity
+ StockQuant._update_available_quantity(component1, self.stock_location, 1)
+ StockQuant._update_available_quantity(component2, self.stock_location, 1)
+
+ # Create mo
+ mo_form = Form(self.env['mrp.production'])
+ mo_form.product_id = finshed_product
+ mo_form.bom_id = bom
+ mo_form.product_uom_id = finshed_product.uom_id
+ mo_form.product_qty = 1.0
+ mo = mo_form.save()
+ self.assertEqual(len(mo), 1, 'MO should have been created')
+ mo.action_confirm()
+ mo.action_assign()
+
+ # Produce the final product
+ mo_form = Form(mo)
+ mo_form.qty_producing = 1.0
+ produce_wizard = mo_form.save()
+
+ mo.button_mark_done()
+ self.assertEqual(mo.state, 'done', "Production order should be in done state.")
+
+ # Check quantity in stock before unbuild
+ self.assertEqual(StockQuant._get_available_quantity(finshed_product, self.stock_location), 1, 'Table should be available in stock')
+ self.assertEqual(StockQuant._get_available_quantity(component1, self.stock_location), 0, 'Table head should not be available in stock')
+ self.assertEqual(StockQuant._get_available_quantity(component2, self.stock_location), 0, 'Table stand should not be available in stock')
+
+ # ---------------------------------------------------
+ # Unbuild
+ # ---------------------------------------------------
+
+ # Create an unbuild order of the finished product and set the destination loacation = QC/Unbuild
+ x = Form(self.env['mrp.unbuild'])
+ x.product_id = finshed_product
+ x.bom_id = bom
+ x.mo_id = mo
+ x.product_qty = 1
+ x.location_id = self.stock_location
+ x.location_dest_id = unbuild_location
+ x.save().action_unbuild()
+
+ # Check the available quantity of components and final product in stock
+ self.assertEqual(StockQuant._get_available_quantity(finshed_product, self.stock_location), 0, 'Table should not be available in stock as it is unbuild')
+ self.assertEqual(StockQuant._get_available_quantity(component1, self.stock_location), 0, 'Table head should not be available in stock as it is in QC/Unbuild location')
+ self.assertEqual(StockQuant._get_available_quantity(component2, self.stock_location), 0, 'Table stand should not be available in stock as it is in QC/Unbuild location')
+
+ # Find new generated picking
+ picking = self.env['stock.picking'].search([('product_id', 'in', [component1.id, component2.id])])
+ self.assertEqual(picking.location_id.id, unbuild_location.id, 'Wrong source location in picking')
+ self.assertEqual(picking.location_dest_id.id, self.stock_location.id, 'Wrong destination location in picking')
+
+ # Transfer it
+ for ml in picking.move_ids_without_package:
+ ml.quantity_done = 1
+ picking._action_done()
+
+ # Check the available quantity of components and final product in stock
+ self.assertEqual(StockQuant._get_available_quantity(finshed_product, self.stock_location), 0, 'Table should not be available in stock')
+ self.assertEqual(StockQuant._get_available_quantity(component1, self.stock_location), 1, 'Table head should be available in stock as the picking is transferred')
+ self.assertEqual(StockQuant._get_available_quantity(component2, self.stock_location), 1, 'Table stand should be available in stock as the picking is transferred')