diff --git a/addons/mrp/models/mrp_bom.py b/addons/mrp/models/mrp_bom.py index 3feb7b498be..6ca6f8bbf4c 100644 --- a/addons/mrp/models/mrp_bom.py +++ b/addons/mrp/models/mrp_bom.py @@ -4,6 +4,7 @@ from odoo import api, fields, models, _ from odoo.addons import decimal_precision as dp from odoo.exceptions import UserError, ValidationError +from odoo.tools import float_round class MrpBom(models.Model): @@ -138,6 +139,10 @@ class MrpBom(models.Model): templates_done |= current_line.product_id.product_tmpl_id boms_done.append((bom, {'qty': converted_line_quantity, 'product': current_product, 'original_qty': quantity, 'parent_line': current_line})) else: + # We round up here because the user expects that if he has to consume a little more, the whole UOM unit + # should be consumed. + rounding = current_line.product_uom_id.rounding + line_quantity = float_round(line_quantity, precision_rounding=rounding, rounding_method='UP') lines_done.append((current_line, {'qty': line_quantity, 'product': current_product, 'original_qty': quantity, 'parent_line': parent_line})) return boms_done, lines_done diff --git a/addons/mrp/models/mrp_workorder.py b/addons/mrp/models/mrp_workorder.py index fc0da1ecdab..23cd851b780 100644 --- a/addons/mrp/models/mrp_workorder.py +++ b/addons/mrp/models/mrp_workorder.py @@ -6,7 +6,7 @@ from dateutil.relativedelta import relativedelta from odoo import api, fields, models, _ from odoo.exceptions import UserError -from odoo.tools import float_compare +from odoo.tools import float_compare, float_round from odoo.addons import decimal_precision as dp @@ -275,7 +275,8 @@ class MrpWorkorder(models.Model): raw_moves = self.move_raw_ids.filtered(lambda x: (x.has_tracking == 'none') and (x.state not in ('done', 'cancel')) and x.bom_line_id) for move in raw_moves: if move.unit_factor: - move.quantity_done += self.qty_producing * move.unit_factor + rounding = move.product_uom.rounding + move.quantity_done += float_round(self.qty_producing * move.unit_factor, precision_rounding=rounding) # Transfer quantities from temporary to final move lots or make them final for move_lot in self.active_move_lot_ids: diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 39f190ece58..1d67c226bca 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -3,7 +3,7 @@ from odoo import api, exceptions, fields, models, _ from odoo.exceptions import UserError -from odoo.tools import float_compare +from odoo.tools import float_compare, float_round from odoo.addons import decimal_precision as dp @@ -225,8 +225,14 @@ class StockMove(models.Model): moves_to_unreserve = self.env['stock.move'] # Create extra moves where necessary for move in moves: + # Here, the `quantity_done` was already rounded to the product UOM by the `do_produce` wizard. However, + # it is possible that the user changed the value before posting the inventory by a value that should be + # rounded according to the move's UOM. In this specific case, we chose to round up the value, because it + # is what is expected by the user (if i consumed/produced a little more, the whole UOM unit should be + # consumed/produced and the moves are split correctly). rounding = move.product_uom.rounding - if float_compare(move.quantity_done, 0.0, precision_rounding=rounding) <= 0: + move.quantity_done = float_round(move.quantity_done, precision_rounding=rounding, rounding_method ='UP') + if move.quantity_done <= 0: continue moves_todo |= move moves_todo |= move._create_extra_move() diff --git a/addons/mrp/tests/test_order.py b/addons/mrp/tests/test_order.py index 64a42fbb136..0623a892e52 100644 --- a/addons/mrp/tests/test_order.py +++ b/addons/mrp/tests/test_order.py @@ -427,3 +427,35 @@ class TestMrpOrder(TestMrpCommon): # check the consumed quants of the newly produced quant self.assertEquals(sum(second_move.quant_ids.mapped('consumed_quant_ids').mapped('qty')), 2) + + def test_rounding(self): + """ 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, + '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 = self.env['mrp.production'].create({'name': 'MO efficiency test', + 'product_id': self.product_6.id, + 'product_qty': 20, + 'bom_id': bom_eff.id, + 'product_uom_id': self.product_6.uom_id.id,}) + #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, + 'active_ids': [production.id], + }).create({ + 'product_qty': 8, + }) + 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') diff --git a/addons/mrp/wizard/mrp_product_produce.py b/addons/mrp/wizard/mrp_product_produce.py index 3ac2b1bf7c3..b636fc15ec7 100644 --- a/addons/mrp/wizard/mrp_product_produce.py +++ b/addons/mrp/wizard/mrp_product_produce.py @@ -6,7 +6,7 @@ from datetime import datetime from odoo import api, fields, models, _ from odoo.addons import decimal_precision as dp from odoo.exceptions import UserError -from odoo.tools import float_compare +from odoo.tools import float_compare, float_round class MrpProductProduce(models.TransientModel): _name = "mrp.product.produce" @@ -80,13 +80,16 @@ class MrpProductProduce(models.TransientModel): raise UserError(_('You should at least produce some quantity')) for move in moves.filtered(lambda x: x.product_id.tracking == 'none' and x.state not in ('done', 'cancel')): if move.unit_factor: - move.quantity_done_store += quantity * move.unit_factor + rounding = move.product_uom.rounding + move.quantity_done_store += float_round(quantity * move.unit_factor, precision_rounding=rounding) moves = self.production_id.move_finished_ids.filtered(lambda x: x.product_id.tracking == 'none' and x.state not in ('done', 'cancel')) for move in moves: + rounding = move.product_uom.rounding if move.product_id.id == self.production_id.product_id.id: - move.quantity_done_store += quantity + move.quantity_done_store += float_round(quantity, precision_rounding=rounding) elif move.unit_factor: - move.quantity_done_store += quantity * move.unit_factor + # byproducts handling + move.quantity_done_store += float_round(quantity * move.unit_factor, precision_rounding=rounding) self.check_finished_move_lots() if self.production_id.state == 'confirmed': self.production_id.write({