-- ============================================================================= -- cleanup_orphan_piece_refs.sql -- ============================================================================= -- Contexte : la suppression directe de rows dans `pieces` (bypass Doctrine / -- FK DB sans ON DELETE CASCADE) laisse des références orphelines dans plusieurs -- tables, ce qui fait planter l'API au chargement d'une Machine : -- Doctrine\ORM\EntityNotFoundException: Entity of type 'App\Entity\Piece' ... -- -- Ce script fait deux choses : -- 1. ÉTAPE 1 (toujours exécutée) : compte les références orphelines par table -- pour visualiser l'ampleur du problème. -- 2. ÉTAPE 2 (commentée par défaut) : insère un audit_log par row, puis -- DELETE / UPDATE SET NULL selon la sémantique attendue côté entité. -- Décommenter le bloc `BEGIN; ... COMMIT;` pour appliquer. -- -- Usage : -- # Dry-run (compte seulement) -- psql -h -U -d inventory -f scripts/cleanup_orphan_piece_refs.sql -- -- # Application : décommenter le bloc transactionnel en bas du fichier, -- # puis relancer la même commande. La transaction garantit l'atomicité. -- ============================================================================= -- ============================== ÉTAPE 1 : DRY-RUN ============================ \echo '' \echo '=== Orphelins par table (Pieces) ===' SELECT 'machine_piece_links' AS table_name, count(*) AS orphans FROM machine_piece_links WHERE pieceid IS NOT NULL AND pieceid NOT IN (SELECT id FROM pieces) UNION ALL SELECT 'composant_piece_slots', count(*) FROM composant_piece_slots WHERE selectedpieceid IS NOT NULL AND selectedpieceid NOT IN (SELECT id FROM pieces) UNION ALL SELECT 'piece_product_slots', count(*) FROM piece_product_slots WHERE pieceid NOT IN (SELECT id FROM pieces) UNION ALL SELECT 'documents', count(*) FROM documents WHERE pieceid IS NOT NULL AND pieceid NOT IN (SELECT id FROM pieces) UNION ALL SELECT 'custom_field_values', count(*) FROM custom_field_values WHERE pieceid IS NOT NULL AND pieceid NOT IN (SELECT id FROM pieces) UNION ALL SELECT 'piece_constructeur_links', count(*) FROM piece_constructeur_links WHERE pieceid NOT IN (SELECT id FROM pieces) UNION ALL SELECT 'piece_products', count(*) FROM piece_products WHERE piece_id NOT IN (SELECT id FROM pieces) ORDER BY table_name; \echo '' \echo '=> Pour appliquer le cleanup, décommenter le bloc BEGIN/COMMIT ci-dessous.' \echo '' -- ============================== ÉTAPE 2 : APPLY ============================= -- Décommenter ce bloc pour exécuter le cleanup. La transaction garantit -- l'atomicité : tout passe, ou rien (en cas d'erreur, ROLLBACK auto). -- -- BEGIN; -- -- -- 1. Audit log : snapshot des rows qui vont être supprimées (traçabilité prod). -- -- INSERT INTO audit_logs (id, entitytype, entityid, action, snapshot, actorprofileid, createdat) -- SELECT -- 'cl' || substring(md5(random()::text || clock_timestamp()::text), 1, 24), -- 'machine_piece_link', -- l.id, -- 'delete', -- json_build_object( -- 'id', l.id, -- 'machineId', l.machineid, -- 'pieceId', l.pieceid, -- 'note', 'Cleaned by cleanup_orphan_piece_refs.sql - referenced piece no longer existed' -- ), -- NULL, -- NOW() -- FROM machine_piece_links l -- WHERE l.pieceid IS NOT NULL -- AND l.pieceid NOT IN (SELECT id FROM pieces); -- -- INSERT INTO audit_logs (id, entitytype, entityid, action, snapshot, actorprofileid, createdat) -- SELECT -- 'cl' || substring(md5(random()::text || clock_timestamp()::text), 1, 24), -- 'piece_product_slot', -- s.id, -- 'delete', -- json_build_object( -- 'id', s.id, -- 'pieceId', s.pieceid, -- 'note', 'Cleaned by cleanup_orphan_piece_refs.sql - referenced piece no longer existed' -- ), -- NULL, -- NOW() -- FROM piece_product_slots s -- WHERE s.pieceid NOT IN (SELECT id FROM pieces); -- -- INSERT INTO audit_logs (id, entitytype, entityid, action, snapshot, actorprofileid, createdat) -- SELECT -- 'cl' || substring(md5(random()::text || clock_timestamp()::text), 1, 24), -- 'document', -- d.id, -- 'delete', -- json_build_object( -- 'id', d.id, -- 'name', d.name, -- 'filename', d.filename, -- 'pieceId', d.pieceid, -- 'note', 'Cleaned by cleanup_orphan_piece_refs.sql - referenced piece no longer existed' -- ), -- NULL, -- NOW() -- FROM documents d -- WHERE d.pieceid IS NOT NULL -- AND d.pieceid NOT IN (SELECT id FROM pieces); -- -- INSERT INTO audit_logs (id, entitytype, entityid, action, snapshot, actorprofileid, createdat) -- SELECT -- 'cl' || substring(md5(random()::text || clock_timestamp()::text), 1, 24), -- 'custom_field_value', -- v.id, -- 'delete', -- json_build_object( -- 'id', v.id, -- 'pieceId', v.pieceid, -- 'note', 'Cleaned by cleanup_orphan_piece_refs.sql - referenced piece no longer existed' -- ), -- NULL, -- NOW() -- FROM custom_field_values v -- WHERE v.pieceid IS NOT NULL -- AND v.pieceid NOT IN (SELECT id FROM pieces); -- -- INSERT INTO audit_logs (id, entitytype, entityid, action, snapshot, actorprofileid, createdat) -- SELECT -- 'cl' || substring(md5(random()::text || clock_timestamp()::text), 1, 24), -- 'piece_constructeur_link', -- l.id, -- 'delete', -- json_build_object( -- 'id', l.id, -- 'pieceId', l.pieceid, -- 'constructeurId', l.constructeurid, -- 'note', 'Cleaned by cleanup_orphan_piece_refs.sql - referenced piece no longer existed' -- ), -- NULL, -- NOW() -- FROM piece_constructeur_links l -- WHERE l.pieceid NOT IN (SELECT id FROM pieces); -- -- -- 2. Nettoyage des orphelins. -- -- DELETE FROM machine_piece_links -- WHERE pieceid IS NOT NULL -- AND pieceid NOT IN (SELECT id FROM pieces); -- -- UPDATE composant_piece_slots SET selectedpieceid = NULL -- WHERE selectedpieceid IS NOT NULL -- AND selectedpieceid NOT IN (SELECT id FROM pieces); -- -- DELETE FROM piece_product_slots -- WHERE pieceid NOT IN (SELECT id FROM pieces); -- -- DELETE FROM documents -- WHERE pieceid IS NOT NULL -- AND pieceid NOT IN (SELECT id FROM pieces); -- -- DELETE FROM custom_field_values -- WHERE pieceid IS NOT NULL -- AND pieceid NOT IN (SELECT id FROM pieces); -- -- DELETE FROM piece_constructeur_links -- WHERE pieceid NOT IN (SELECT id FROM pieces); -- -- DELETE FROM piece_products -- WHERE piece_id NOT IN (SELECT id FROM pieces); -- -- -- 3. Vérification post-cleanup : tout doit être à 0. -- SELECT 'machine_piece_links' AS table_name, count(*) AS remaining_orphans -- FROM machine_piece_links -- WHERE pieceid IS NOT NULL -- AND pieceid NOT IN (SELECT id FROM pieces) -- UNION ALL -- SELECT 'composant_piece_slots', count(*) -- FROM composant_piece_slots -- WHERE selectedpieceid IS NOT NULL -- AND selectedpieceid NOT IN (SELECT id FROM pieces) -- UNION ALL -- SELECT 'piece_product_slots', count(*) -- FROM piece_product_slots -- WHERE pieceid NOT IN (SELECT id FROM pieces) -- UNION ALL -- SELECT 'documents', count(*) -- FROM documents -- WHERE pieceid IS NOT NULL -- AND pieceid NOT IN (SELECT id FROM pieces) -- UNION ALL -- SELECT 'custom_field_values', count(*) -- FROM custom_field_values -- WHERE pieceid IS NOT NULL -- AND pieceid NOT IN (SELECT id FROM pieces) -- UNION ALL -- SELECT 'piece_constructeur_links', count(*) -- FROM piece_constructeur_links -- WHERE pieceid NOT IN (SELECT id FROM pieces) -- UNION ALL -- SELECT 'piece_products', count(*) -- FROM piece_products -- WHERE piece_id NOT IN (SELECT id FROM pieces) -- ORDER BY table_name; -- -- COMMIT;