[FIX] mrp: rounding issues and efficiency replacement

Before, when you would put a quantity in a BoM line that was not rounded
to its UoM (e.g. use 0.3 piece with rounding 1.0) it would not round this
quantity in the MO also (when exploding the BoM), and the user could also
enter a not rounded quantity himself for the quantity produced or
consumed.

The moves were split with the exact quantities, but the moves were validated
taking into account rounded quantities. This resulted in either moving 2
pieces instead of 1 (entering 0.7 or 1 piece) or raising an error telling
you can not process moves with 0 quantity (0.3 piece).

As product_efficiency and product_rounding was removed from the BoM/BoM
line in v10, if the rounding is correctly handled, the feature could work
like in v9, by e.g. putting 1.03 in the bom line (97% efficiency) and
rounding up when exploding the BoM (e.g. producing 10 pieces would put
it to 11).

When "producing", we round the produced qty to the product uom (it does
not make sense to produce 1.32 if your uom rounding is 1.0). The user
might now change the produced quantity, that's why we round-up when
validating the move: indeed, if the user slightly increase the produced
value (below the uom unit), it actually means he produced more.

This way, instead of not rounding anything and doing rounding behind the
scenes, leading to errors, we round everything and the user sees the
result directly.
This commit is contained in:
Josse Colpaert
2017-01-27 19:22:02 +01:00
committed by Simon Lejeune
parent 3f232a6022
commit 347f140fe6
5 changed files with 55 additions and 8 deletions
+5
View File
@@ -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
+3 -2
View File
@@ -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:
+8 -2
View File
@@ -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()
+32
View File
@@ -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')
+7 -4
View File
@@ -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({