[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:
committed by
Simon Lejeune
parent
3f232a6022
commit
347f140fe6
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user