Idempotent post-migrate that moves mid-flight in_progress jobs whose recipe steps are all terminal into the appropriate new state: - draft cert exists → awaiting_cert - no cert required → awaiting_ship done jobs left alone (historically completed, already shipped). Card_state + mini_timeline_json recomputed for affected rows so the plant kanban renders correctly on first page load. Version bump 19.0.10.31.0 → 19.0.11.0.0 triggers the migration on -u. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
92 lines
3.1 KiB
Python
92 lines
3.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright 2026 Nexa Systems Inc.
|
|
# License OPL-1 (Odoo Proprietary License v1.0)
|
|
"""Backfill new awaiting_cert / awaiting_ship states for mid-flight jobs.
|
|
|
|
Spec: docs/superpowers/specs/2026-05-25-post-shop-cert-shipping-job-states-design.md
|
|
|
|
Rules:
|
|
- in_progress + all steps terminal + draft cert exists → awaiting_cert
|
|
- in_progress + all steps terminal + no cert required → awaiting_ship
|
|
- done jobs LEFT ALONE — historically completed (already shipped)
|
|
|
|
Idempotent: re-running on a fresh upgrade is a no-op because no
|
|
in_progress job will match the all-terminal predicate after the first
|
|
run. Pass 1 and Pass 2 are mutually exclusive (the cert-existence
|
|
sub-queries are inverses).
|
|
"""
|
|
import logging
|
|
|
|
from odoo import api, SUPERUSER_ID
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
def migrate(cr, version):
|
|
"""Post-migrate entrypoint — called by Odoo after the module's
|
|
XML/Python loads on -u of fusion_plating_jobs."""
|
|
|
|
# ---- Pass 1: in_progress + all-terminal + draft cert → awaiting_cert
|
|
cr.execute("""
|
|
UPDATE fp_job
|
|
SET state = 'awaiting_cert'
|
|
WHERE id IN (
|
|
SELECT j.id
|
|
FROM fp_job j
|
|
JOIN fp_job_step s ON s.job_id = j.id
|
|
WHERE j.state = 'in_progress'
|
|
GROUP BY j.id
|
|
HAVING count(*) FILTER (
|
|
WHERE s.state NOT IN ('done','skipped','cancelled')
|
|
) = 0
|
|
)
|
|
AND EXISTS (
|
|
SELECT 1 FROM fp_certificate c
|
|
WHERE c.x_fc_job_id = fp_job.id AND c.state = 'draft'
|
|
);
|
|
""")
|
|
n_cert = cr.rowcount
|
|
_logger.info(
|
|
"post-migrate 19.0.11.0.0: %d jobs migrated to awaiting_cert", n_cert,
|
|
)
|
|
|
|
# ---- Pass 2: in_progress + all-terminal + no cert → awaiting_ship
|
|
cr.execute("""
|
|
UPDATE fp_job
|
|
SET state = 'awaiting_ship'
|
|
WHERE id IN (
|
|
SELECT j.id
|
|
FROM fp_job j
|
|
JOIN fp_job_step s ON s.job_id = j.id
|
|
WHERE j.state = 'in_progress'
|
|
GROUP BY j.id
|
|
HAVING count(*) FILTER (
|
|
WHERE s.state NOT IN ('done','skipped','cancelled')
|
|
) = 0
|
|
)
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM fp_certificate c
|
|
WHERE c.x_fc_job_id = fp_job.id
|
|
AND c.state IN ('draft', 'issued')
|
|
);
|
|
""")
|
|
n_ship = cr.rowcount
|
|
_logger.info(
|
|
"post-migrate 19.0.11.0.0: %d jobs migrated to awaiting_ship", n_ship,
|
|
)
|
|
|
|
# ---- Card_state recompute for affected rows (stored compute) ----
|
|
if n_cert or n_ship:
|
|
env = api.Environment(cr, SUPERUSER_ID, {})
|
|
affected = env['fp.job'].search([
|
|
('state', 'in', ('awaiting_cert', 'awaiting_ship')),
|
|
])
|
|
# Bust cache then read-to-recompute via @api.depends.
|
|
affected.invalidate_recordset(['card_state', 'mini_timeline_json'])
|
|
affected.mapped('card_state')
|
|
affected.mapped('mini_timeline_json')
|
|
_logger.info(
|
|
"post-migrate 19.0.11.0.0: card_state recomputed on %d jobs",
|
|
len(affected),
|
|
)
|