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

@@ -49,6 +49,12 @@
/>
</div>
<div v-if="selectedTypeName && !selectedEntityId && !loadingEntities" class="bg-warning/10 border border-warning rounded-lg p-3 mb-4">
<p class="text-sm text-warning font-medium">
Aucun item sélectionné — la catégorie sera ajoutée avec le statut "À remplir".
</p>
</div>
<!-- Summary of selection -->
<div v-if="selectedEntitySummary" class="bg-base-200 rounded-lg p-3 mb-4">
<p class="text-sm font-medium">{{ selectedEntitySummary.name }}</p>
@@ -64,10 +70,10 @@
<button
type="button"
class="btn btn-primary"
:disabled="!selectedEntityId"
:disabled="!selectedTypeId"
@click="handleConfirm"
>
Ajouter
{{ selectedEntityId ? 'Ajouter' : 'Ajouter (catégorie seule)' }}
</button>
</div>
</div>
@@ -90,11 +96,12 @@ type EntityKind = 'component' | 'piece' | 'product'
const props = defineProps<{
open: boolean
entityKind: EntityKind
prefillTypeId?: string
}>()
const emit = defineEmits<{
close: []
confirm: [entityId: string]
confirm: [payload: { entityId?: string; modelTypeId: string; modelTypeName: string }]
}>()
const selectedTypeId = ref('')
@@ -166,6 +173,10 @@ watch(() => props.open, async (isOpen) => {
if (props.entityKind === 'component') await loadComponentTypes()
else if (props.entityKind === 'piece') await loadPieceTypes()
else await loadProductTypes()
if (props.prefillTypeId) {
selectedTypeId.value = props.prefillTypeId
}
})
// Load entities when type changes
@@ -222,8 +233,12 @@ const handleClose = () => {
}
const handleConfirm = () => {
if (!selectedEntityId.value) return
emit('confirm', selectedEntityId.value)
if (!selectedTypeId.value) return
emit('confirm', {
entityId: selectedEntityId.value || undefined,
modelTypeId: selectedTypeId.value,
modelTypeName: selectedTypeName.value,
})
resetState()
emit('close')
}