From 1f860fea38eee32046882c6121d887dc7c4ff33e Mon Sep 17 00:00:00 2001 From: Arnold Moyaux Date: Fri, 15 Dec 2017 13:19:04 +0100 Subject: [PATCH] [FIX] mrp: generate move_line with same lot Usecase to reproduce: - Set a product with the same lot in different locations - Create a MO that will uses theses products and that require more than one location - Produce it. -> Traceback Expected singleton: stock.move.line It happens because product produce only expect one move line by lot. Howeever it could exist more than one move line if the product for a specific lot are shared among multiple under location. This commit try to fill the move line with the correct quantity one by one and add the extra in a new move line. --- addons/mrp/models/stock_move.py | 19 ++++++--- addons/mrp/tests/test_order.py | 72 ++++++++++++++++++++++++++++++--- 2 files changed, 80 insertions(+), 11 deletions(-) diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 863b863c42c..97ae8d2458c 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -190,12 +190,18 @@ class StockMove(models.Model): def _generate_consumed_move_line(self, qty_to_add, final_lot, lot=False): if lot: - ml = self.move_line_ids.filtered(lambda ml: ml.lot_id == lot and not ml.lot_produced_id) + move_lines = self.move_line_ids.filtered(lambda ml: ml.lot_id == lot and not ml.lot_produced_id) else: - ml = self.move_line_ids.filtered(lambda ml: not ml.lot_id and not ml.lot_produced_id) - if ml: - new_quantity_done = (ml.qty_done + qty_to_add) - if new_quantity_done >= ml.product_uom_qty: + move_lines = self.move_line_ids.filtered(lambda ml: not ml.lot_id and not ml.lot_produced_id) + for ml in move_lines: + rounding = ml.product_uom_id.rounding + if float_compare(qty_to_add, 0, precision_rounding=rounding) <= 0: + break + quantity_to_process = min(qty_to_add, ml.product_uom_qty - ml.qty_done) + qty_to_add -= quantity_to_process + + new_quantity_done = (ml.qty_done + quantity_to_process) + if float_compare(new_quantity_done, ml.product_uom_qty, precision_rounding=rounding) >= 0: ml.write({'qty_done': new_quantity_done, 'lot_produced_id': final_lot.id}) else: new_qty_reserved = ml.product_uom_qty - new_quantity_done @@ -204,7 +210,8 @@ class StockMove(models.Model): 'lot_produced_id': final_lot.id} ml.copy(default=default) ml.with_context(bypass_reservation_update=True).write({'product_uom_qty': new_qty_reserved, 'qty_done': 0}) - else: + + if float_compare(qty_to_add, 0, precision_rounding=self.product_uom.rounding) > 0: vals = { 'move_id': self.id, 'product_id': self.product_id.id, diff --git a/addons/mrp/tests/test_order.py b/addons/mrp/tests/test_order.py index 296ae2fbe44..56b87b7e61d 100644 --- a/addons/mrp/tests/test_order.py +++ b/addons/mrp/tests/test_order.py @@ -276,7 +276,7 @@ class TestMrpOrder(TestMrpCommon): 'final_lot_id': finished_lot.id, 'qty_producing': 48 }) - + kit_wo.record_production() self.assertEqual(kit_wo.state, 'done', "Workorder should be in done state.") @@ -431,9 +431,9 @@ class TestMrpOrder(TestMrpCommon): """ In previous versions we had rounding and efficiency fields. We check if we can still do the same, but with only the rounding on the UoM""" self.product_6.uom_id.rounding = 1.0 bom_eff = self.env['mrp.bom'].create({'product_id': self.product_6.id, - 'product_tmpl_id': self.product_6.product_tmpl_id.id, - 'product_qty': 1, - 'product_uom_id': self.product_6.uom_id.id, + '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}), @@ -447,7 +447,7 @@ class TestMrpOrder(TestMrpCommon): #Check the production order has the right quantities self.assertEqual(production.move_raw_ids[0].product_qty, 41, 'The quantity should be rounded up') self.assertEqual(production.move_raw_ids[1].product_qty, 84, 'The quantity should be rounded up') - + # produce product produce_wizard = self.env['mrp.product.produce'].with_context({ 'active_id': production.id, @@ -516,3 +516,65 @@ class TestMrpOrder(TestMrpCommon): }).create({}) self.assertEqual(len(product_produce.produce_line_ids), 1, 'You should have 1 produce lines since one has already be consumed.') self.assertEqual(product_produce.produce_line_ids[0].lot_id, remaining_lot, 'Wrong lot proposed.') + + def test_product_produce_3(self): + """ Check that line are created when the consumed products are + tracked by serial and the lot proposed are correct. """ + self.stock_location = self.env.ref('stock.stock_location_stock') + self.stock_shelf_1 = self.env.ref('stock.stock_location_components') + self.stock_shelf_2 = self.env.ref('stock.stock_location_14') + mo, _, p_final, p1, p2 = self.generate_mo(tracking_base_1='lot', qty_base_1=10, qty_final=1) + self.assertEqual(len(mo), 1, 'MO should have been created') + + first_lot_for_p1 = self.env['stock.production.lot'].create({ + 'name': 'lot1', + 'product_id': p1.id, + }) + second_lot_for_p1 = self.env['stock.production.lot'].create({ + 'name': 'lot2', + 'product_id': p1.id, + }) + + final_product_lot = self.env['stock.production.lot'].create({ + 'name': 'lot1', + 'product_id': p_final.id, + }) + + self.env['stock.quant']._update_available_quantity(p1, self.stock_shelf_1, 3, lot_id=first_lot_for_p1) + self.env['stock.quant']._update_available_quantity(p1, self.stock_shelf_2, 3, lot_id=first_lot_for_p1) + self.env['stock.quant']._update_available_quantity(p1, self.stock_location, 8, lot_id=second_lot_for_p1) + self.env['stock.quant']._update_available_quantity(p2, self.stock_location, 5) + + mo.action_assign() + product_produce = self.env['mrp.product.produce'].with_context({ + 'active_id': mo.id, + 'active_ids': [mo.id], + }).create({ + 'product_qty': 1.0, + 'lot_id': final_product_lot.id, + }) + # product 1 lot 1 shelf1 + # product 1 lot 1 shelf2 + # product 1 lot 2 + self.assertEqual(len(product_produce.produce_line_ids), 3, 'You should have 3 produce lines. lot 1 shelf_1, lot 1 shelf_2, lot2') + + for produce_line in product_produce.produce_line_ids: + produce_line.qty_done = produce_line.qty_to_consume + 1 + product_produce.do_produce() + + move_1 = mo.move_raw_ids.filtered(lambda m: m.product_id == p1) + # qty_done/product_uom_qty lot + # 3/3 lot 1 shelf 1 + # 1/1 lot 1 shelf 2 + # 2/2 lot 1 shelf 2 + # 2/0 lot 1 other + # 5/4 lot 2 + ml_to_shelf_1 = move_1.move_line_ids.filtered(lambda ml: ml.lot_id == first_lot_for_p1 and ml.location_id == self.stock_shelf_1) + ml_to_shelf_2 = move_1.move_line_ids.filtered(lambda ml: ml.lot_id == first_lot_for_p1 and ml.location_id == self.stock_shelf_2) + + self.assertEqual(sum(ml_to_shelf_1.mapped('qty_done')), 3.0, '3 units should be took from shelf1 as reserved.') + self.assertEqual(sum(ml_to_shelf_2.mapped('qty_done')), 3.0, '3 units should be took from shelf2 as reserved.') + self.assertEqual(move_1.quantity_done, 13, 'You should have used the tem units.') + + mo.button_mark_done() + self.assertEqual(mo.state, 'done', "Production order should be in done state.")