diff --git a/models/mrp_production.py b/models/mrp_production.py index 8f2f48b..e8a3c38 100644 --- a/models/mrp_production.py +++ b/models/mrp_production.py @@ -38,24 +38,25 @@ class MrpProduction(models.Model): return moves def _update_raw_moves(self, factor): - # Odoo core _update_raw_moves multiplies `old_qty * factor` and rounds UP. - # If factor is 0.9799118079... and old_qty is 71435, the math results in 70000.00000000004 - # Odoo's UP rounding elevates this dust to 70000.001! - # We must let Odoo do its thing, but then surgically clean the dust. + # We override this to ensure "To Consume" (product_uom_qty) is calculated from BOM line "truth" + # rather than multiplying an old (possibly noisy) quantity by a factor. res = super()._update_raw_moves(factor) clean_res = [] for move, old_qty, new_qty in res: - exact_qty = old_qty * factor - # Clean floating point microscopic dust (e.g. 70000.0000000004 -> 70000.0) - clean_exact_qty = round(exact_qty, 8) - - from odoo.tools import float_round - ideal_qty = float_round(clean_exact_qty, precision_rounding=move.product_uom.rounding, rounding_method='UP') - - if move.product_uom_qty != ideal_qty: - move.write({'product_uom_qty': ideal_qty}) - clean_res.append((move, old_qty, ideal_qty)) + if move.bom_line_id and self.bom_id: + # Recalculate the clean truth directly from BOM + clean_factor = round(self.product_uom_id._compute_quantity(self.product_qty, self.bom_id.product_uom_id, round=False) / self.bom_id.product_qty, 8) + line_quantity = round(clean_factor * move.bom_line_id.product_qty, 8) + + from odoo.tools import float_round + ideal_qty = float_round(line_quantity, precision_rounding=move.product_uom.rounding, rounding_method='UP') + + if move.product_uom_qty != ideal_qty: + move.write({'product_uom_qty': ideal_qty}) + clean_res.append((move, old_qty, ideal_qty)) + else: + clean_res.append((move, old_qty, new_qty)) return clean_res @@ -132,18 +133,38 @@ class MrpProduction(models.Model): def _clean_lingering_decimals(self): for production in self: + if not production.bom_id: + continue + + # Pre-calculate clean factor for this production + clean_factor = round(production.product_uom_id._compute_quantity(production.product_qty, production.bom_id.product_uom_id, round=False) / production.bom_id.product_qty, 8) + for move in production.move_raw_ids: - rounding = move.product_uom.rounding - if move.product_uom_qty: - # Clean quantities that moved away from clean decimals by exactly the rounding amount - # E.g. 60.030 instead of 60.000 where component increment is 0.010 - clean_qty = round(move.product_uom_qty, 2) - if 0.000001 < abs(move.product_uom_qty - clean_qty) < (rounding * 1.5): - move.product_uom_qty = clean_qty - for ml in move.move_line_ids: - if ml.quantity: + if not move.bom_line_id: + continue + + # Calculate what the quantity SHOULD be according to BOM and MO Qty + line_quantity = round(clean_factor * move.bom_line_id.product_qty, 8) + from odoo.tools import float_round + ideal_qty = float_round(line_quantity, precision_rounding=move.product_uom.rounding, rounding_method='UP') + + # 1. Fix "To Consume" (product_uom_qty) + if abs(move.product_uom_qty - ideal_qty) > 0.000001: + move.product_uom_qty = ideal_qty + + # 2. Fix "Consumed" (move_line_ids.quantity) + # If the line is FULLY consumed (e.g. qty_producing == product_qty), + # then move lines should match the ideal quantity. + if production.qty_producing == production.product_qty: + for ml in move.move_line_ids: + if abs(ml.quantity - ideal_qty) > 0.000001: + ml.quantity = ideal_qty + else: + # Otherwise just round to 2 decimals if it's "close enough" to a clean decimal + # but drifted by microscopic dust. + for ml in move.move_line_ids: clean_done = round(ml.quantity, 2) - if 0.000001 < abs(ml.quantity - clean_done) < (rounding * 1.5): + if 0.000001 < abs(ml.quantity - clean_done) < (move.product_uom.rounding * 1.5): ml.quantity = clean_done def _merge_finished_move_lines(self):