Adds legacy_mrp_production_id (Integer index) on fp.job and
legacy_mrp_workorder_id on fp.job.step. Used as the idempotency
key during cutover migration.
Three scripts under fusion_plating_jobs/scripts/:
- audit_pre_migration.py — counts and data-quality concerns BEFORE
- migrate_to_fp_jobs.py — copies MO->fp.job, WO->fp.job.step, time
logs, rebinds cross-refs (batches,
holds, certs, readings, portals,
inspections, deliveries). Idempotent.
- audit_post_migration.py — counts and verifies AFTER
Migration is run manually from \`odoo shell\` at cutover (not as
auto post-migration hook, for safety). README explains usage.
Tests verify the legacy id fields exist and the migration script
files are well-formed Python.
Manifest 19.0.1.9.0 -> 19.0.2.0.0.
Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
117 lines
3.9 KiB
Python
117 lines
3.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2026 Nexa Systems Inc.
|
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
|
#
|
|
# Pre-migration audit. Reports row counts and data-quality concerns
|
|
# before running migrate_to_fp_jobs.py. Read-only — does NOT modify data.
|
|
#
|
|
# Run from `odoo shell` where `env` is in scope. See ./README.md.
|
|
|
|
import logging
|
|
|
|
_logger = logging.getLogger('fp_jobs_migration')
|
|
|
|
|
|
def run(env):
|
|
"""Print a snapshot of what migrate_to_fp_jobs.py would touch.
|
|
|
|
All queries are SELECT-only. Safe to run on production at any time.
|
|
"""
|
|
cr = env.cr
|
|
print('=== Pre-migration audit ===')
|
|
|
|
# Core MRP counts
|
|
cr.execute("SELECT COUNT(*) FROM mrp_production")
|
|
mo_total = cr.fetchone()[0]
|
|
print('mrp.production total:', mo_total)
|
|
|
|
cr.execute("SELECT state, COUNT(*) FROM mrp_production GROUP BY state ORDER BY 1")
|
|
print('mrp.production by state:', cr.fetchall())
|
|
|
|
cr.execute("SELECT COUNT(*) FROM mrp_workorder")
|
|
wo_total = cr.fetchone()[0]
|
|
print('mrp.workorder total:', wo_total)
|
|
|
|
cr.execute("SELECT state, COUNT(*) FROM mrp_workorder GROUP BY state ORDER BY 1")
|
|
print('mrp.workorder by state:', cr.fetchall())
|
|
|
|
# Already migrated?
|
|
cr.execute("SELECT COUNT(*) FROM fp_job")
|
|
job_total = cr.fetchone()[0]
|
|
print('fp.job already exists:', job_total)
|
|
|
|
cr.execute("SELECT COUNT(*) FROM fp_job_step")
|
|
step_total = cr.fetchone()[0]
|
|
print('fp.job.step already exists:', step_total)
|
|
|
|
# Data quality
|
|
if 'x_fc_recipe_id' in env['mrp.production']._fields:
|
|
cr.execute(
|
|
"SELECT COUNT(*) FROM mrp_production WHERE x_fc_recipe_id IS NULL"
|
|
)
|
|
no_recipe = cr.fetchone()[0]
|
|
print('MOs without x_fc_recipe_id:', no_recipe)
|
|
|
|
cr.execute(
|
|
"SELECT COUNT(*) FROM mrp_workorder WHERE workcenter_id IS NULL"
|
|
)
|
|
no_wc = cr.fetchone()[0]
|
|
print('WOs without workcenter_id:', no_wc)
|
|
|
|
# Dependent records — check by model registry (truthful even when
|
|
# the schema names differ from defaults).
|
|
if 'fp.quality.hold' in env:
|
|
cr.execute(
|
|
"SELECT COUNT(*) FROM fp_quality_hold WHERE production_id IS NOT NULL"
|
|
)
|
|
print('fp.quality.hold rows with production_id:', cr.fetchone()[0])
|
|
if 'fp.certificate' in env:
|
|
cr.execute(
|
|
"SELECT COUNT(*) FROM fp_certificate WHERE production_id IS NOT NULL"
|
|
)
|
|
print('fp.certificate rows with production_id:', cr.fetchone()[0])
|
|
if 'fp.thickness.reading' in env:
|
|
cr.execute(
|
|
"SELECT COUNT(*) FROM fp_thickness_reading WHERE production_id IS NOT NULL"
|
|
)
|
|
print(
|
|
'fp.thickness.reading rows with production_id:',
|
|
cr.fetchone()[0],
|
|
)
|
|
if 'fusion.plating.batch' in env:
|
|
cr.execute(
|
|
"SELECT COUNT(*) FROM fusion_plating_batch WHERE workorder_id IS NOT NULL"
|
|
)
|
|
print(
|
|
'fusion.plating.batch rows with workorder_id:',
|
|
cr.fetchone()[0],
|
|
)
|
|
if 'fusion.plating.portal.job' in env:
|
|
cr.execute("SELECT COUNT(*) FROM fusion_plating_portal_job")
|
|
print('fusion.plating.portal.job total:', cr.fetchone()[0])
|
|
if 'fp.racking.inspection' in env:
|
|
cr.execute(
|
|
"SELECT COUNT(*) FROM fp_racking_inspection WHERE production_id IS NOT NULL"
|
|
)
|
|
print(
|
|
'fp.racking.inspection rows with production_id:',
|
|
cr.fetchone()[0],
|
|
)
|
|
if 'fusion.plating.delivery' in env:
|
|
cr.execute(
|
|
"SELECT COUNT(*) FROM fusion_plating_delivery WHERE job_ref IS NOT NULL"
|
|
)
|
|
print(
|
|
'fusion.plating.delivery rows with job_ref:',
|
|
cr.fetchone()[0],
|
|
)
|
|
|
|
print('=== End pre-migration audit ===')
|
|
|
|
|
|
# Run when the script is exec'd from odoo shell (env is in scope).
|
|
try:
|
|
run(env) # noqa: F821 — `env` is provided by odoo shell
|
|
except NameError:
|
|
print('This script expects to run inside `odoo shell` where `env` is defined.')
|