[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 <arm@odoo.com>
This commit is contained in:
Nicolas Martinelli
2018-08-17 13:21:28 +02:00
committed by Nicolas Martinelli
co-authored by Arnold Moyaux
parent 412c471e78
commit f0028bc837
2 changed files with 76 additions and 1 deletions
+39
View File
@@ -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.")
+37 -1
View File
@@ -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'))