First In First Out (FIFO) fifo Last In First Out (LIFO) lifo Closest Location closest Correct inconsistencies for reservation code MoveLines = env['stock.move.line'].sudo() Quants = env['stock.quant'].sudo() tracked_fields = ['product_id', 'location_id', 'lot_id', 'package_id', 'owner_id'] impacted_quants, impacted_move_lines = set(), set() all_errors = {} # Compute rounding precision for each UOM uom_roundings = { u['id']: u['rounding'] for u in env['uom.uom'].sudo().with_context(active_test=False).search_read([], ['rounding']) } products_rounding = { p['id']: uom_roundings[p['uom_id'][0]] for p in env['product.product'].sudo().with_context(active_test=False).search_read([], ['uom_id']) } # Move that bypass reservations ignore_moves = env['stock.move'].sudo().search([('state', 'not in', ['draft', 'cancel', 'done'])]) ignore_moves = ignore_moves.filtered(lambda m: m._should_bypass_reservation()).ids # Get move_lines with incorrect reservation (negative or invalid on state) incorrect_lines_grouped = MoveLines.read_group( [ '|', ('reserved_qty', '<', 0), '&', ('reserved_qty', '!=', 0), '|', ('state', 'in', ['done', 'draft', 'cancel']), ('move_id', '=', False), ], tracked_fields + ['ids:array_agg(id)', 'reserved_qty:sum'], tracked_fields, lazy=False, ) for lines_grp in incorrect_lines_grouped: rd = products_rounding[lines_grp['product_id'][0]] if float_compare(0, lines_grp['reserved_qty'], precision_rounding=rd) != 0: impacted_move_lines.update(lines_grp['ids']) # Get key to match between quants and sml def get_key(res): return res['product_id'], res['location_id'], res['lot_id'], res['package_id'], res['owner_id'] # Create a python dictionary containing all quants with reserved quantities in the following format: # (product_id, location_id, lot_id, package_id, owner_id): (id, reserved_quantity, quantity, rounding) all_quants = { get_key(q): (q['id'], q['reserved_quantity'], q['quantity'], products_rounding[q['product_id'][0]]) for q in Quants.search_read([('reserved_quantity', '!=', 0)], tracked_fields + ['reserved_quantity', 'quantity']) } # Get all move_lines with reserved quantities all_grouped_move_lines = MoveLines.read_group( [ ('move_id', 'not in', ignore_moves), ('state', 'not in', ['done', 'draft', 'cancel']), ('reserved_qty', '>', 0), ('move_id', '!=', False), ], tracked_fields + ['ids:array_agg(id)', 'reserved_qty:sum'], tracked_fields, lazy=False, ) def check_quant(quant_key, quant_val=None, lines=None): if quant_val is None and lines is None: return if quant_val is None: quant_val = (None, 0, 0, products_rounding[quant_key[0][0]]) if lines is None: lines = {'ids': [], 'reserved_qty': 0} quant_id, quant_res, quant_qty, rounding = quant_val err = False # Quant reserved must be inferior or equal to the Quant quantity (Before Odoo 17) err |= float_compare(quant_qty, quant_res, precision_rounding=rounding) == -1 # Quant reserved must be higher or equal to 0 err |= float_compare(0, quant_res, precision_rounding=rounding) == 1 # Quant reserved must be equal to Move reserved err |= float_compare(lines['reserved_qty'], quant_res, precision_rounding=rounding) != 0 if err: impacted_move_lines.update(lines['ids']) if quant_id: impacted_quants.add(quant_id) # Check errors on Move Lines and Quants for lines_grp in all_grouped_move_lines: sq_key = get_key(lines_grp) check_quant(sq_key, quant_val=all_quants.pop(sq_key, None), lines=lines_grp) # Quants with reservation without move lines reserved on it for sq_key, sq_val in all_quants.items(): check_quant(sq_key, quant_val=sq_val, lines=None) if impacted_quants: Quants.browse(impacted_quants).write({'reserved_quantity': 0}) if impacted_move_lines: lines = MoveLines.browse(impacted_move_lines) lines.with_context(bypass_reservation_update=True).write({'reserved_uom_qty': 0}) lines.move_id._recompute_state() if impacted_quants or impacted_move_lines: report = "Reserved quantity set to 0 for the following: \n- stock.quant: {}\n- stock.move.line: {}".format( impacted_quants, impacted_move_lines ) log(report, level="debug") action = {'type': 'ir.actions.client', 'tag': 'reload'} Physical Locations view Partners view 1 Virtual Locations view 1 Vendors supplier Customers customer Inter-company transit transit Replenish on Order (MTO) False 5 property_stock_supplier property_stock_customer WH