# -*- coding: utf-8 -*- # Copyright 2026 Nexa Systems Inc. """Backfill missing part metadata + received_qty on fp.receiving.line. A bug in fp.receiving auto-create (now fixed in fusion_plating_receiving/models/sale_order.py) read ``order.x_fc_part_catalog_id`` (the rarely-populated SO header field) instead of ``line.x_fc_part_catalog_id`` (the authoritative per-line field), leaving every auto-generated receiving line with an empty ``part_number`` and ``part_catalog_id``. Same auto-create also forgot to prefill ``received_qty``. This migration walks existing receiving records and rebuilds the line metadata from the linked SO's order lines via position-based zip — only when the receiving line count matches the SO line count (otherwise the mapping isn't safe and we leave the record alone for manual review). """ import logging _logger = logging.getLogger(__name__) def migrate(cr, version): # Find candidates: receiving lines with empty part_catalog_id AND # empty part_number, scoped to receivings with a linked SO. cr.execute(""" SELECT r.id AS receiving_id, r.sale_order_id AS so_id, array_agg(rl.id ORDER BY rl.id) AS line_ids FROM fp_receiving r JOIN fp_receiving_line rl ON rl.receiving_id = r.id WHERE r.sale_order_id IS NOT NULL AND (rl.part_catalog_id IS NULL AND (rl.part_number IS NULL OR rl.part_number = '')) GROUP BY r.id, r.sale_order_id """) candidates = cr.fetchall() if not candidates: _logger.info('Receiving line backfill: no candidates.') return fixed = 0 skipped = 0 for receiving_id, so_id, recv_line_ids in candidates: # Pull the SO's order lines in stable order. cr.execute(""" SELECT id, x_fc_part_catalog_id, product_uom_qty, name FROM sale_order_line WHERE order_id = %s ORDER BY sequence, id """, (so_id,)) so_lines = cr.fetchall() if len(so_lines) != len(recv_line_ids): # Mismatch — don't risk corrupting a non-trivial mapping. skipped += 1 continue # Receiving lines come ordered by id ascending (the create call # in sale_order.py emits them in order_line order, so id-order # = sequence-order on the SO side). for recv_line_id, (sol_id, part_id, qty, name) in zip( recv_line_ids, so_lines, ): part_number = '' if part_id: cr.execute( "SELECT part_number FROM fp_part_catalog WHERE id = %s", (part_id,), ) row = cr.fetchone() part_number = (row and row[0]) or '' cr.execute(""" UPDATE fp_receiving_line SET part_catalog_id = %s, part_number = %s, received_qty = COALESCE(NULLIF(received_qty, 0), %s) WHERE id = %s """, ( part_id or None, part_number, int(qty or 0), recv_line_id, )) fixed += 1 _logger.info( 'Receiving line backfill: fixed %d lines, skipped %d receivings ' '(line-count mismatch).', fixed, skipped, )