changes
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2026 Nexa Systems Inc.
|
||||
"""Name-match existing fp_receiving.carrier_name → x_fc_carrier_id.
|
||||
|
||||
Phase A of the shipping integration replaces the free-text carrier
|
||||
field with a Many2one to delivery.carrier. Existing records (16 on
|
||||
entech at write time) have free-text values like "FedEx", "Purolator"
|
||||
in carrier_name. This migration walks them and populates the new M2O
|
||||
when a unique case-insensitive name match exists.
|
||||
|
||||
delivery.carrier.name is jsonb (translatable) in Odoo 19 — match
|
||||
strips to the en_US translation. Ambiguous values stay as text in
|
||||
carrier_name for the operator to pick manually.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def migrate(cr, version):
|
||||
# Skip if the field doesn't exist yet (defensive — the column is
|
||||
# added by the registry update that runs before post-migrate).
|
||||
cr.execute("""
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'fp_receiving'
|
||||
AND column_name = 'x_fc_carrier_id'
|
||||
""")
|
||||
if not cr.fetchone():
|
||||
_logger.warning('x_fc_carrier_id column not present — skip.')
|
||||
return
|
||||
|
||||
cr.execute("""
|
||||
UPDATE fp_receiving r
|
||||
SET x_fc_carrier_id = dc.id
|
||||
FROM delivery_carrier dc
|
||||
WHERE r.carrier_name IS NOT NULL
|
||||
AND r.carrier_name <> ''
|
||||
AND r.x_fc_carrier_id IS NULL
|
||||
AND LOWER(TRIM(r.carrier_name)) =
|
||||
LOWER(TRIM((dc.name->>'en_US')))
|
||||
""")
|
||||
matched = cr.rowcount
|
||||
_logger.info(
|
||||
'Receiving carrier migration: matched %d record(s) by name.',
|
||||
matched,
|
||||
)
|
||||
@@ -0,0 +1,89 @@
|
||||
# -*- 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,
|
||||
)
|
||||
Reference in New Issue
Block a user