fix(fusion_plating): wet_process passthrough + per-clone unlink safety

Two follow-up fixes caught during the entech deploy of recipe cleanup:

1. RESOLVER_KIND_TO_ACTIVE_KIND was missing a self-pass entry for
   'wet_process'. The new aliases added in 19.0.21.3.0 (Chemical
   Conversion, Trivalent Chromate Conversion, Strip Process - AL,
   Plug The Threaded Holes via mask) directly return 'wet_process'
   from the resolver — without the passthrough they didn't translate
   to any active kind and stayed as 'other'. Added 'wet_process':
   'wet_process' so the migration's Phase 2 backfill catches them.

2. Migration 19.0.10.26.0 Phase 3 was using batch unlink
   (clone_recipes.unlink()) which tripped a PostgreSQL FK cascade
   ordering bug on entech ("insert or update on parent_id violates
   FK ..." during the CASCADE chain). Rewrote Phase 3 to delete one
   clone at a time with SAVEPOINT per clone — slower but immune to
   the batching bug, and one failed clone doesn't roll back the
   whole transaction.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-24 18:08:35 -04:00
parent 9a2975b154
commit e1fedf7231
2 changed files with 47 additions and 16 deletions

View File

@@ -362,6 +362,12 @@ RESOLVER_KIND_TO_ACTIVE_KIND = {
'strike': 'wet_process',
'dry': 'wet_process',
'wbf_test': 'wet_process',
'wet_process': 'wet_process', # the alias added in 19.0.21.3.0
# for "Strip Process - AL", "Chemical
# Conversion", "Trivalent Chromate
# Conversion" maps DIRECTLY to
# 'wet_process' — this passthrough
# entry lets those land correctly.
# 1:1 mappings (kind exists and is active)
'contract_review': 'contract_review',
'mask': 'mask',

View File

@@ -153,30 +153,55 @@ def migrate(cr, version):
# Identify by name pattern. The configurator names clones
# "BASE_NAME - PART_NUMBER Rev X" with an em-dash separator
# (U+2014). No base recipe uses em-dash in its name.
#
# IMPORTANT: delete one-clone-at-a-time with savepoint per clone.
# Batch unlink (clone_recipes.unlink()) tripped a PostgreSQL FK
# cascade ordering bug on entech (insert-or-update on parent_id
# during the cascade chain). Per-clone unlink with intermediate
# cleanup avoids that path entirely and lets one bad clone fail
# without rolling back the others.
clone_recipes = Node.search([
('node_type', '=', 'recipe'),
('name', 'ilike', '%%'),
])
if clone_recipes:
clone_names = [c.name for c in clone_recipes]
_logger.info(
'[recipe-cleanup] Phase 3: deleting %s clone recipes: %s',
len(clone_recipes),
', '.join(clone_names[:10])
+ (' ...' if len(clone_names) > 10 else ''),
)
clone_recipes.unlink()
_logger.info(
'[recipe-cleanup] Phase 3: deleted %s clone recipes '
'(CASCADE removed their child nodes; FK SET NULL applied '
'to historical fp.job + fp.job.step references)',
len(clone_recipes),
)
else:
if not clone_recipes:
_logger.info(
'[recipe-cleanup] Phase 3: no clone recipes found '
'(already deleted on a prior run, or none exist)'
)
else:
_logger.info(
'[recipe-cleanup] Phase 3: deleting %s clone recipes one '
'at a time (per-clone savepoint)', len(clone_recipes),
)
deleted = 0
failed = []
for clone in clone_recipes:
cid, cname = clone.id, clone.name
cr.execute('SAVEPOINT delete_clone')
try:
clone.unlink()
cr.execute('RELEASE SAVEPOINT delete_clone')
deleted += 1
except Exception as e:
cr.execute('ROLLBACK TO SAVEPOINT delete_clone')
failed.append((cid, cname, type(e).__name__, str(e)[:120]))
_logger.warning(
'[recipe-cleanup] Phase 3: failed to delete '
'clone %s ("%s"): %s — continuing',
cid, cname, type(e).__name__,
)
_logger.info(
'[recipe-cleanup] Phase 3: deleted %s/%s clones '
'(%s failures retained for manual review)',
deleted, len(clone_recipes), len(failed),
)
if failed:
for cid, cname, errtype, errmsg in failed:
_logger.warning(
'[recipe-cleanup] Phase 3 leftover: id=%s name=%r '
'err=%s: %s', cid, cname, errtype, errmsg,
)
# ============================================================
# Phase 4 - Recompute area_kind on all fp.job.step rows