fix: Prevent floating-point inaccuracies in MO raw material quantities during BOM explosion and correct existing noise with a cleanup script.
This commit is contained in:
parent
4fd8c57af3
commit
e5923de362
@ -12,36 +12,32 @@ try:
|
||||
uid = common.authenticate(selected_db, username, api_key, {})
|
||||
models = xmlrpc.client.ServerProxy('{}/xmlrpc/2/object'.format(url), context=context)
|
||||
|
||||
# Search for all draft or confirmed MOs to recalculate quantities based on true BOM ratios.
|
||||
# Search for all draft or confirmed MOs.
|
||||
mo_ids = models.execute_kw(selected_db, uid, api_key, 'mrp.production', 'search', [[('state', 'in', ['draft', 'confirmed'])]])
|
||||
|
||||
print(f"Found {len(mo_ids)} active MOs.")
|
||||
|
||||
for mo_id in mo_ids:
|
||||
mo = models.execute_kw(selected_db, uid, api_key, 'mrp.production', 'read', [[mo_id]], {'fields': ['name', 'product_qty', 'bom_id', 'move_raw_ids']})[0]
|
||||
if not mo.get('bom_id'):
|
||||
mo = models.execute_kw(selected_db, uid, api_key, 'mrp.production', 'read', [[mo_id]], {'fields': ['name', 'product_qty', 'move_raw_ids']})[0]
|
||||
|
||||
move_ids = mo.get('move_raw_ids', [])
|
||||
if not move_ids:
|
||||
continue
|
||||
|
||||
print(f"Checking MO {mo['name']}...")
|
||||
bom = models.execute_kw(selected_db, uid, api_key, 'mrp.bom', 'read', [[mo['bom_id'][0]]], {'fields': ['product_qty', 'bom_line_ids']})[0]
|
||||
bom_qty = bom['product_qty']
|
||||
|
||||
# Read BOM lines to get exact ratios
|
||||
bom_lines = models.execute_kw(selected_db, uid, api_key, 'mrp.bom.line', 'read', [bom['bom_line_ids']], {'fields': ['product_qty']})
|
||||
bom_ratios = {bl['id']: bl['product_qty'] / bom_qty for bl in bom_lines}
|
||||
|
||||
moves = models.execute_kw(selected_db, uid, api_key, 'stock.move', 'read', [mo['move_raw_ids']], {'fields': ['bom_line_id', 'product_uom_qty']})
|
||||
moves = models.execute_kw(selected_db, uid, api_key, 'stock.move', 'read', [move_ids], {'fields': ['product_uom_qty']})
|
||||
|
||||
for move in moves:
|
||||
if move.get('bom_line_id'):
|
||||
bom_line_id = move['bom_line_id'][0]
|
||||
if bom_line_id in bom_ratios:
|
||||
correct_qty = bom_ratios[bom_line_id] * mo['product_qty']
|
||||
current_qty = move.get('product_uom_qty') or 0.0
|
||||
current_qty = move.get('product_uom_qty') or 0.0
|
||||
|
||||
if abs(correct_qty - current_qty) > 0.0001:
|
||||
print(f" - Fixing move {move['id']}: qty {current_qty} -> exact integer {correct_qty}")
|
||||
models.execute_kw(selected_db, uid, api_key, 'stock.move', 'write', [[move['id']], {'product_uom_qty': correct_qty}])
|
||||
# Detect +0.001 to +0.009 floating point noise
|
||||
# by comparing it against its cleanly rounded 2-decimal version.
|
||||
clean_qty = round(current_qty, 2)
|
||||
fraction_diff = abs(current_qty - clean_qty)
|
||||
|
||||
if 0.0001 < fraction_diff < 0.01:
|
||||
print(f" - Fixing move {move['id']}: qty {current_qty} -> clean {clean_qty}")
|
||||
models.execute_kw(selected_db, uid, api_key, 'stock.move', 'write', [[move['id']], {'product_uom_qty': clean_qty}])
|
||||
|
||||
print("Finished checking MOs.")
|
||||
|
||||
|
||||
@ -4,6 +4,42 @@ from odoo.tools import float_compare
|
||||
class MrpProduction(models.Model):
|
||||
_inherit = 'mrp.production'
|
||||
|
||||
def _get_moves_raw_values(self):
|
||||
moves = super()._get_moves_raw_values()
|
||||
|
||||
# Odoo's internal mrp.bom.explode uses rounding_method='UP'.
|
||||
# A tiny floating point inaccuracy in UoM conversion (e.g. 1.00000000005)
|
||||
# rounding UP with precision 0.001 results in +0.001 to all components.
|
||||
# We calculate the clean integer factor here and overwrite the moved quantities
|
||||
for production in self:
|
||||
if not production.bom_id:
|
||||
continue
|
||||
|
||||
raw_factor = production.product_uom_id._compute_quantity(production.product_qty, production.bom_id.product_uom_id, round=False) / production.bom_id.product_qty
|
||||
# Truncate the floating point noise. 8 decimals is safe.
|
||||
clean_factor = round(raw_factor, 8)
|
||||
|
||||
for move_vals in moves:
|
||||
# If this move belongs to this production and is tied to a BOM line
|
||||
if move_vals.get('raw_material_production_id') == production.id and move_vals.get('bom_line_id'):
|
||||
bom_line = production.env['mrp.bom.line'].browse(move_vals['bom_line_id'])
|
||||
|
||||
# Instead of Odoo's noisy exploded qty, use the clean factor
|
||||
line_quantity = clean_factor * bom_line.product_qty
|
||||
|
||||
# It's important to use float_round rather than just python round
|
||||
# so we respect the UOM rounding method (UP), but on the CLEANED number!
|
||||
from odoo.tools import float_round
|
||||
clean_qty = float_round(line_quantity, precision_rounding=bom_line.product_uom_id.rounding, rounding_method='UP')
|
||||
|
||||
move_vals['product_uom_qty'] = clean_qty
|
||||
|
||||
return moves
|
||||
|
||||
def _update_raw_moves(self, factor):
|
||||
# Prevent floating point noise during UPDATE quantity
|
||||
clean_factor = round(factor, 8)
|
||||
return super()._update_raw_moves(clean_factor)
|
||||
|
||||
packaging_id = fields.Many2one('mrp.packaging', string='Packaging', domain="[('product_tmpl_id', '=', product_tmpl_id)]", check_company=True)
|
||||
packaging_qty = fields.Float('Quantity Packaging', compute='_compute_packaging_qty', inverse='_inverse_packaging_qty', store=True, readonly=False)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user