90 lines
3.3 KiB
Python
90 lines
3.3 KiB
Python
# -*- 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,
|
|
)
|