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:
@@ -14,6 +14,77 @@ export const buildDeleteMessage = (entityName: string, impacts: string[]): strin
|
||||
if (impacts.length) {
|
||||
lines.push(`Cela supprimera également :\n• ${impacts.join('\n• ')}`)
|
||||
}
|
||||
lines.push('Cette action est irréversible.')
|
||||
return lines.join('\n\n')
|
||||
}
|
||||
|
||||
interface UsedInMachine {
|
||||
id: string
|
||||
name: string | null
|
||||
site?: { id: string; name: string | null } | null
|
||||
}
|
||||
|
||||
interface UsedInEntity {
|
||||
id: string
|
||||
name: string | null
|
||||
}
|
||||
|
||||
export interface UsageInfo {
|
||||
machines?: UsedInMachine[]
|
||||
composants?: UsedInEntity[]
|
||||
pieces?: UsedInEntity[]
|
||||
}
|
||||
|
||||
const formatMachineLine = (m: UsedInMachine): string => {
|
||||
const name = m.name?.trim() || '(sans nom)'
|
||||
const siteName = m.site?.name?.trim()
|
||||
return siteName ? `${name} (${siteName})` : name
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a delete-confirmation message that lists the machines (and other
|
||||
* entities) currently using the item. The user sees exactly what will be
|
||||
* detached before they confirm the deletion.
|
||||
*/
|
||||
export const buildDeleteMessageWithUsage = (
|
||||
entityName: string,
|
||||
entityLabel: string,
|
||||
usage: UsageInfo,
|
||||
): string => {
|
||||
const machines = usage.machines ?? []
|
||||
const composants = usage.composants ?? []
|
||||
const pieces = usage.pieces ?? []
|
||||
|
||||
const lines = [`Voulez-vous vraiment supprimer « ${entityName} » ?`]
|
||||
|
||||
if (machines.length > 0) {
|
||||
const header = machines.length === 1
|
||||
? `${entityLabel} est actuellement utilisée par 1 machine :`
|
||||
: `${entityLabel} est actuellement utilisée par ${machines.length} machines :`
|
||||
const bullets = machines.map((m) => `• ${formatMachineLine(m)}`).join('\n')
|
||||
lines.push(`${header}\n${bullets}\n\nLa supprimer la retirera de ${machines.length === 1 ? 'cette machine' : 'ces machines'}.`)
|
||||
}
|
||||
|
||||
if (composants.length > 0) {
|
||||
const header = composants.length === 1
|
||||
? 'Elle est également référencée par 1 composant :'
|
||||
: `Elle est également référencée par ${composants.length} composants :`
|
||||
const bullets = composants
|
||||
.map((c) => `• ${c.name?.trim() || '(sans nom)'}`)
|
||||
.join('\n')
|
||||
lines.push(`${header}\n${bullets}`)
|
||||
}
|
||||
|
||||
if (pieces.length > 0) {
|
||||
const header = pieces.length === 1
|
||||
? 'Elle est également utilisée par 1 pièce :'
|
||||
: `Elle est également utilisée par ${pieces.length} pièces :`
|
||||
const bullets = pieces
|
||||
.map((p) => `• ${p.name?.trim() || '(sans nom)'}`)
|
||||
.join('\n')
|
||||
lines.push(`${header}\n${bullets}`)
|
||||
}
|
||||
|
||||
lines.push('Cette action est irréversible.')
|
||||
return lines.join('\n\n')
|
||||
}
|
||||
Reference in New Issue
Block a user