# -*- 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), )