feat(machines) : allow category-only links on machine structure

Enable adding a component, piece, or product to a machine by selecting
only the category (ModelType) without a specific entity. The link
displays a red "À remplir" badge; clicking it reopens the modal
pre-filled with the category so the user can associate an item later.

Backend: entity FKs made nullable on the 3 link tables, modelType FK
added, controller/audit/version/MCP normalization adapted for null
entities.

Frontend: modal accepts category-only confirm, page handles fill mode,
hierarchy builder creates pending nodes, display components show
clickable badge with event propagation through the full hierarchy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-03 10:15:47 +02:00
parent 342ae37762
commit 1c3b566923
20 changed files with 452 additions and 77 deletions

View File

@@ -150,6 +150,30 @@ export const buildMachineHierarchyFromLinks = (
const createPieceNode = (link: AnyRecord, parentComponentName: string | null = null): AnyRecord | null => {
if (!link || typeof link !== 'object') return null
// Category-only link (no entity yet)
if (link.pendingEntity || (!link.piece && !link.pieceId)) {
const machinePieceLinkId = normalizePieceLinkId(link)
const mt = (link.modelType || null) as AnyRecord | null
return {
id: machinePieceLinkId || `pending-${link.id}`,
linkId: machinePieceLinkId,
name: mt?.name || 'Catégorie sans item',
reference: null,
prix: null,
pendingEntity: true,
modelTypeId: link.modelTypeId || mt?.id || null,
modelType: mt,
pieceId: null,
constructeurs: [],
documents: [],
customFields: [],
parentComponentLinkId: link.parentComponentLinkId || link.parentLinkId || null,
parentComponentName,
machinePieceLinkId,
quantity: 1,
}
}
const appliedPiece = (link.piece && typeof link.piece === 'object' ? link.piece : {}) as AnyRecord
const originalPiece = (link.originalPiece && typeof link.originalPiece === 'object' ? link.originalPiece : null) as AnyRecord | null
@@ -205,6 +229,35 @@ export const buildMachineHierarchyFromLinks = (
const createComponentNode = (link: AnyRecord): AnyRecord | null => {
if (!link || typeof link !== 'object') return null
// Category-only link (no entity yet)
if (link.pendingEntity || (!link.composant && !link.composantId)) {
const machineComponentLinkId = normalizeComponentLinkId(link)
const mt = (link.modelType || null) as AnyRecord | null
return {
id: machineComponentLinkId || `pending-${link.id}`,
linkId: machineComponentLinkId,
name: mt?.name || 'Catégorie sans item',
reference: null,
prix: null,
pendingEntity: true,
modelTypeId: link.modelTypeId || mt?.id || null,
modelType: mt,
composantId: null,
composant: null,
constructeurs: [],
documents: [],
customFields: [],
customFieldValues: [],
subComponents: [],
pieces: [],
overrides: null,
parentComponentLinkId: link.parentComponentLinkId || link.parentLinkId || null,
machineComponentLinkId,
childLinks: [],
pieceLinks: [],
}
}
const appliedComponent = (link.composant && typeof link.composant === 'object' ? link.composant : {}) as AnyRecord
const originalComponent = (link.originalComposant && typeof link.originalComposant === 'object' ? link.originalComposant : null) as AnyRecord | null