fix(pieces) : empêche EntityNotFoundException sur Piece orpheline + UX prévention delete

- Migration FK CASCADE/SET NULL pour toutes les FK vers pieces.id (miroir
  de la fix Composant) + cleanup des orphelins existants avec audit log
- Helper ensurePieceExists() qui catch EntityNotFoundException dans
  MachineStructureController et CustomFieldValueController
- Script SQL standalone scripts/cleanup_orphan_piece_refs.sql pour
  nettoyer la prod sans attendre la migration
- Affiche les machines (avec leur site) utilisant la pièce avant la
  confirmation de suppression
This commit is contained in:
Matthieu
2026-05-28 10:08:28 +02:00
parent d1b170d87f
commit 003e419a93
6 changed files with 611 additions and 9 deletions
+17 -2
View File
@@ -167,7 +167,7 @@ import { usePieces } from '~/composables/usePieces'
import { usePieceTypes } from '~/composables/usePieceTypes'
import { useDataTable } from '~/composables/useDataTable'
import DocumentThumbnail from '~/components/DocumentThumbnail.vue'
import { resolveDeleteImpact, buildDeleteMessage } from '~/shared/utils/deleteImpactUtils'
import { buildDeleteMessageWithUsage, type UsageInfo } from '~/shared/utils/deleteImpactUtils'
import { resolvePrimaryDocument, resolvePreviewAlt, resolveSupplierNames, buildSuppliersDisplay } from '~/shared/utils/catalogDisplayUtils'
import { formatFrenchDate } from '~/utils/date'
@@ -249,10 +249,25 @@ const buildPieceSuppliersDisplay = (piece: Record<string, any>) =>
buildSuppliersDisplay(resolveSupplierNames(piece, 'product'))
const { confirm } = useConfirm()
const api = useApi()
const handleDeletePiece = async (piece: Record<string, any>) => {
const pieceName = piece?.name || 'cette pièce'
const message = buildDeleteMessage(pieceName, resolveDeleteImpact(piece))
let usage: UsageInfo = {}
try {
const result = await api.get(`/pieces/${piece.id}/used-in`)
if (result.success && result.data) {
usage = {
machines: result.data.machines ?? [],
composants: result.data.composants ?? [],
}
}
} catch (error) {
console.warn('Impossible de récupérer les usages de la pièce avant suppression :', error)
}
const message = buildDeleteMessageWithUsage(pieceName, 'Cette pièce', usage)
const confirmed = await confirm({ title: 'Supprimer la pièce', message, dangerous: true })
if (!confirmed) return
await deletePiece(piece.id)