From f0028bc837bc93e355d5bda7be1223142b485718 Mon Sep 17 00:00:00 2001 From: Nicolas Martinelli Date: Tue, 14 Aug 2018 12:43:06 +0200 Subject: [PATCH] [FIX] mrp: raw materials and multi-location - Create a BOM for Product P with the following component: Product C, 5.0 units - Make some stock for C: 2 units in location 1 3 units in location 2 - Create a MO for P, reserve. When trying to produce an error arise: > Cannot set the done quantity from this stock move, work directly with > the move lines. If the user tries to unlock the MO in order to set the done quantity on the move lines, the same error will arise when trying to produce. In the specific scope of the production, we set the quantity done on the move line instead of directly on the move, to avoid this limitation. opw-1871241 Co-authored-by: Arnold Moyaux --- addons/mrp/tests/test_order.py | 39 ++++++++++++++++++++++++ addons/mrp/wizard/mrp_product_produce.py | 38 ++++++++++++++++++++++- 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/addons/mrp/tests/test_order.py b/addons/mrp/tests/test_order.py index 56b87b7e61d..656156a9cde 100644 --- a/addons/mrp/tests/test_order.py +++ b/addons/mrp/tests/test_order.py @@ -578,3 +578,42 @@ class TestMrpOrder(TestMrpCommon): 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. """ + 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(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_produce = self.env['mrp.product.produce'].with_context({ + 'active_id': mo.id, + 'active_ids': [mo.id], + }).create({ + 'product_qty': 1.0, + }) + product_produce.do_produce() + + ml_p1 = mo.move_raw_ids.filtered(lambda x: x.product_id == p1).mapped('move_line_ids') + self.assertEqual(len(ml_p1), 3) + for ml in ml_p1: + self.assertIn(ml.qty_done, [1.0, 2.0, 3.0], 'Quantity done should be 1.0, 2.0 or 3.0') + self.assertEqual(sum(ml_p1.mapped('qty_done')), 6.0, 'Total qty consumed 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.") diff --git a/addons/mrp/wizard/mrp_product_produce.py b/addons/mrp/wizard/mrp_product_produce.py index d854e667822..077c62f04b1 100644 --- a/addons/mrp/wizard/mrp_product_produce.py +++ b/addons/mrp/wizard/mrp_product_produce.py @@ -100,8 +100,10 @@ class MrpProductProduce(models.TransientModel): if self.product_id.tracking != 'none': qty_to_add = float_round(quantity * move.unit_factor, precision_rounding=rounding) move._generate_consumed_move_line(qty_to_add, self.lot_id) - else: + elif len(move._get_move_lines()) < 2: move.quantity_done += float_round(quantity * move.unit_factor, precision_rounding=rounding) + else: + self._set_quantity_done(move, quantity * move.unit_factor) for move in self.production_id.move_finished_ids: if move.product_id.tracking == 'none' and move.state not in ('done', 'cancel'): rounding = move.product_uom.rounding @@ -118,6 +120,40 @@ class MrpProductProduce(models.TransientModel): }) return {'type': 'ir.actions.act_window_close'} + def _set_quantity_done(self, move, qty): + """ + Set the given quantity as quantity done on the move through the move lines. The method is + able to handle move lines with a different UoM than the move (but honestly, this would be + looking for trouble...). + @param move: stock.move + @param qty: quantity in the UoM of move.product_uom + """ + for ml in move.move_line_ids: + # Convert move line qty into move uom + ml_qty = ml.product_uom_qty - ml.qty_done + if ml.product_uom_id != move.product_uom: + ml_qty = ml.product_uom_id._compute_quantity(ml_qty, move.product_uom, round=False) + + taken_qty = min(qty, ml_qty) + # Convert taken qty into move line uom + if ml.product_uom_id != move.product_uom: + taken_qty = move.product_uom._compute_quantity(ml_qty, ml.product_uom_id, round=False) + + # Assign qty_done and explicitly round to make sure there is no inconsistency between + # ml.qty_done and qty. + taken_qty = float_round(taken_qty, precision_rounding=ml.product_uom_id.rounding) + ml.qty_done += taken_qty + if ml.product_uom_id != move.product_uom: + taken_qty = ml.product_uom_id._compute_quantity(ml_qty, move.product_uom, round=False) + qty -= taken_qty + + if float_compare(qty, 0.0, precision_rounding=move.product_uom.rounding) <= 0: + break + if float_compare(qty, 0.0, precision_rounding=move.product_uom.rounding) > 0: + vals = move._prepare_move_line_vals(quantity=0) + vals['qty_done'] = qty + ml = self.env['stock.move.line'].create(vals) + @api.multi def check_finished_move_lots(self): produce_move = self.production_id.move_finished_ids.filtered(lambda x: x.product_id == self.product_id and x.state not in ('done', 'cancel'))