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:
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user