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:
@@ -12,6 +12,7 @@
|
||||
@edit-piece="$emit('edit-piece', $event)"
|
||||
@custom-field-update="$emit('custom-field-update', $event)"
|
||||
@delete="$emit('delete')"
|
||||
@fill-entity="(linkId, typeId) => $emit('fill-entity', linkId, typeId)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -43,5 +44,5 @@ defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
defineEmits(['update', 'edit-piece', 'custom-field-update', 'delete'])
|
||||
defineEmits(['update', 'edit-piece', 'custom-field-update', 'delete', 'fill-entity'])
|
||||
</script>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
/>
|
||||
|
||||
<!-- Component Header -->
|
||||
<div class="flex items-center gap-3 p-3 bg-base-200 rounded-lg cursor-pointer" @click="toggleCollapse">
|
||||
<div class="flex items-center gap-3 p-3 rounded-lg cursor-pointer" :class="component.pendingEntity ? 'bg-error/10 border border-error' : 'bg-base-200'" @click="toggleCollapse">
|
||||
<IconLucideChevronRight
|
||||
class="w-4 h-4 shrink-0 transition-transform text-base-content/50"
|
||||
:class="{ 'rotate-90': !isCollapsed }"
|
||||
@@ -22,9 +22,18 @@
|
||||
/>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<h3 class="text-sm font-semibold text-base-content truncate">
|
||||
<h3 class="text-sm font-semibold truncate" :class="component.pendingEntity ? 'text-error' : 'text-base-content'">
|
||||
{{ component.name }}
|
||||
</h3>
|
||||
<button
|
||||
v-if="component.pendingEntity"
|
||||
type="button"
|
||||
class="badge badge-error badge-sm cursor-pointer hover:badge-outline transition-colors"
|
||||
title="Cliquer pour associer un item"
|
||||
@click.stop="$emit('fill-entity', component.linkId, component.modelTypeId)"
|
||||
>
|
||||
À remplir
|
||||
</button>
|
||||
<span v-if="component.reference" class="badge badge-outline badge-xs">{{ component.reference }}</span>
|
||||
<span v-if="component.prix" class="badge badge-primary badge-xs">{{ component.prix }}€</span>
|
||||
</div>
|
||||
@@ -54,7 +63,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Expanded content -->
|
||||
<div v-show="!isCollapsed" class="mt-3 space-y-4 pl-7">
|
||||
<div v-show="!isCollapsed && !component.pendingEntity" class="mt-3 space-y-4 pl-7">
|
||||
<!-- Info fields -->
|
||||
<div v-if="isEditMode" class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<div class="form-control">
|
||||
@@ -241,6 +250,7 @@
|
||||
@update="updatePiece"
|
||||
@edit="editPiece"
|
||||
@custom-field-update="updatePieceCustomField"
|
||||
@fill-entity="(linkId, typeId) => $emit('fill-entity', linkId, typeId)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -276,6 +286,7 @@
|
||||
@update="$emit('update', $event)"
|
||||
@edit-piece="$emit('edit-piece', $event)"
|
||||
@custom-field-update="$emit('custom-field-update', $event)"
|
||||
@fill-entity="(linkId, typeId) => $emit('fill-entity', linkId, typeId)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -317,7 +328,7 @@ const props = defineProps({
|
||||
toggleToken: { type: Number, default: 0 },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update', 'edit-piece', 'custom-field-update', 'delete'])
|
||||
const emit = defineEmits(['update', 'edit-piece', 'custom-field-update', 'delete', 'fill-entity'])
|
||||
|
||||
// --- Shared composables ---
|
||||
const {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
/>
|
||||
|
||||
<!-- Piece Header (collapsible, same pattern as ComponentItem) -->
|
||||
<div class="flex items-start justify-between p-4 rounded-lg" :class="piece._emptySlot ? 'bg-error/10 border border-error' : 'bg-base-200'">
|
||||
<div class="flex items-start justify-between p-4 rounded-lg" :class="piece._emptySlot || piece.pendingEntity ? 'bg-error/10 border border-error' : 'bg-base-200'">
|
||||
<div class="flex items-start gap-3 flex-1 min-w-0">
|
||||
<button
|
||||
type="button"
|
||||
@@ -28,9 +28,18 @@
|
||||
<span class="sr-only">{{ isCollapsed ? 'Déplier' : 'Replier' }} la pièce</span>
|
||||
</button>
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-lg font-semibold" :class="{ 'text-error': piece._emptySlot }">
|
||||
<h3 class="text-lg font-semibold" :class="{ 'text-error': piece._emptySlot || piece.pendingEntity }">
|
||||
{{ pieceData.name }}
|
||||
<span v-if="piece._emptySlot" class="text-sm font-semibold text-error ml-1">— manquant</span>
|
||||
<button
|
||||
v-if="piece.pendingEntity"
|
||||
type="button"
|
||||
class="badge badge-error badge-sm cursor-pointer hover:badge-outline transition-colors ml-1"
|
||||
title="Cliquer pour associer un item"
|
||||
@click.stop="$emit('fill-entity', piece.linkId, piece.modelTypeId)"
|
||||
>
|
||||
À remplir
|
||||
</button>
|
||||
<span
|
||||
v-if="displayQuantity > 1"
|
||||
class="text-sm font-normal text-base-content/60 ml-1"
|
||||
@@ -77,7 +86,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-show="!isCollapsed" class="space-y-4">
|
||||
<div v-show="!isCollapsed && !piece.pendingEntity" class="space-y-4">
|
||||
<div class="p-4 bg-base-100 border border-base-200 rounded-lg">
|
||||
<div class="space-y-2 text-sm">
|
||||
<div v-if="isEditMode" class="form-control">
|
||||
@@ -308,7 +317,7 @@ const props = defineProps({
|
||||
toggleToken: { type: Number, default: 0 },
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update', 'edit', 'custom-field-update', 'delete'])
|
||||
const emit = defineEmits(['update', 'edit', 'custom-field-update', 'delete', 'fill-entity'])
|
||||
|
||||
// --- Local reactive data for editing ---
|
||||
const pieceData = reactive({
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
:toggle-token="collapseToggleToken"
|
||||
@edit-piece="$emit('edit-piece', $event)"
|
||||
@delete="$emit('remove-component', component.linkId || component.id)"
|
||||
@fill-entity="(linkId, typeId) => $emit('fill-entity', linkId, typeId)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,5 +69,6 @@ defineEmits<{
|
||||
'custom-field-update': [fieldUpdate: any]
|
||||
'add-component': []
|
||||
'remove-component': [linkId: string]
|
||||
'fill-entity': [linkId: string, modelTypeId: string]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
@update="$emit('update-piece', $event)"
|
||||
@edit="$emit('edit-piece', $event)"
|
||||
@delete="$emit('remove-piece', piece.linkId || piece.id)"
|
||||
@fill-entity="(linkId, typeId) => $emit('fill-entity', linkId, typeId)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,5 +68,6 @@ defineEmits<{
|
||||
'edit-piece': [piece: any]
|
||||
'add-piece': []
|
||||
'remove-piece': [linkId: string]
|
||||
'fill-entity': [linkId: string, modelTypeId: string]
|
||||
}>()
|
||||
</script>
|
||||
|
||||
@@ -23,14 +23,24 @@
|
||||
<div v-if="products.length" class="space-y-3">
|
||||
<div
|
||||
v-for="product in products"
|
||||
:key="product.id || product.name"
|
||||
class="rounded border border-base-200 bg-base-200/60 p-3 text-sm space-y-2"
|
||||
:key="product.id || product.linkId || product.name"
|
||||
class="rounded border p-3 text-sm space-y-2"
|
||||
:class="product.pendingEntity ? 'border-error bg-error/10' : 'border-base-200 bg-base-200/60'"
|
||||
>
|
||||
<div class="flex items-center justify-between flex-wrap gap-2">
|
||||
<p class="font-semibold text-base-content">
|
||||
<p class="font-semibold" :class="product.pendingEntity ? 'text-error' : 'text-base-content'">
|
||||
{{ product.name }}
|
||||
</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
v-if="product.pendingEntity"
|
||||
type="button"
|
||||
class="badge badge-error badge-sm cursor-pointer hover:badge-outline transition-colors"
|
||||
title="Cliquer pour associer un item"
|
||||
@click="$emit('fill-entity', (product.linkId || product.id) as string, product.modelTypeId as string)"
|
||||
>
|
||||
À remplir
|
||||
</button>
|
||||
<span v-if="product.groupLabel" class="badge badge-ghost badge-sm">
|
||||
{{ product.groupLabel }}
|
||||
</span>
|
||||
@@ -141,6 +151,9 @@ defineProps<{
|
||||
supplierLabel?: string | null
|
||||
priceLabel?: string | null
|
||||
groupLabel?: string
|
||||
pendingEntity?: boolean
|
||||
modelTypeId?: string | null
|
||||
modelType?: string | null
|
||||
documents?: Array<{
|
||||
id?: string
|
||||
name?: string
|
||||
@@ -156,6 +169,7 @@ defineProps<{
|
||||
defineEmits<{
|
||||
'add-product': []
|
||||
'remove-product': [linkId: string]
|
||||
'fill-entity': [linkId: string, modelTypeId: string]
|
||||
}>()
|
||||
|
||||
const previewDocument = ref<any>(null)
|
||||
|
||||
@@ -193,6 +193,10 @@ export function useMachineDetailData(machineId: string) {
|
||||
removePieceLink,
|
||||
addProductLink,
|
||||
removeProductLink,
|
||||
addComponentLinkCategoryOnly,
|
||||
addPieceLinkCategoryOnly,
|
||||
addProductLinkCategoryOnly,
|
||||
fillEntityLink,
|
||||
} = hierarchy
|
||||
|
||||
// Keep the product links proxy in sync with the hierarchy's machineProductLinks
|
||||
@@ -511,6 +515,8 @@ export function useMachineDetailData(machineId: string) {
|
||||
loadMachineData, loadInitialData,
|
||||
addComponentLink, removeComponentLink, addPieceLink, removePieceLink,
|
||||
addProductLink, removeProductLink, reloadMachineStructure,
|
||||
addComponentLinkCategoryOnly, addPieceLinkCategoryOnly,
|
||||
addProductLinkCategoryOnly, fillEntityLink,
|
||||
|
||||
// External
|
||||
constructeurs, loadProducts, updateMachineStructure, toast,
|
||||
|
||||
@@ -39,7 +39,7 @@ export function useMachineDetailHierarchy(deps: MachineDetailHierarchyDeps) {
|
||||
syncMachineCustomFields,
|
||||
} = deps
|
||||
|
||||
const { get, post: apiPost, delete: apiDel } = useApi()
|
||||
const { get, post: apiPost, delete: apiDel, patch: apiPatch } = useApi()
|
||||
const toast = useToast()
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -263,6 +263,69 @@ export function useMachineDetailHierarchy(deps: MachineDetailHierarchyDeps) {
|
||||
return result
|
||||
}
|
||||
|
||||
const addComponentLinkCategoryOnly = async (modelTypeId: string) => {
|
||||
const result: any = await apiPost('/machine_component_links', {
|
||||
machine: `/api/machines/${machineId}`,
|
||||
modelType: `/api/model_types/${modelTypeId}`,
|
||||
})
|
||||
if (result.success) {
|
||||
toast.showSuccess('Catégorie ajoutée — item à remplir')
|
||||
await reloadMachineStructure()
|
||||
} else {
|
||||
toast.showError('Erreur lors de l\'ajout')
|
||||
}
|
||||
}
|
||||
|
||||
const addPieceLinkCategoryOnly = async (modelTypeId: string) => {
|
||||
const result: any = await apiPost('/machine_piece_links', {
|
||||
machine: `/api/machines/${machineId}`,
|
||||
modelType: `/api/model_types/${modelTypeId}`,
|
||||
})
|
||||
if (result.success) {
|
||||
toast.showSuccess('Catégorie ajoutée — item à remplir')
|
||||
await reloadMachineStructure()
|
||||
} else {
|
||||
toast.showError('Erreur lors de l\'ajout')
|
||||
}
|
||||
}
|
||||
|
||||
const addProductLinkCategoryOnly = async (modelTypeId: string) => {
|
||||
const result: any = await apiPost('/machine_product_links', {
|
||||
machine: `/api/machines/${machineId}`,
|
||||
modelType: `/api/model_types/${modelTypeId}`,
|
||||
})
|
||||
if (result.success) {
|
||||
toast.showSuccess('Catégorie ajoutée — item à remplir')
|
||||
await reloadMachineStructure()
|
||||
} else {
|
||||
toast.showError('Erreur lors de l\'ajout')
|
||||
}
|
||||
}
|
||||
|
||||
const fillEntityLink = async (linkId: string, entityId: string, entityKind: string) => {
|
||||
let endpoint = ''
|
||||
let payload: Record<string, string> = {}
|
||||
|
||||
if (entityKind === 'component') {
|
||||
endpoint = `/machine_component_links/${linkId}`
|
||||
payload = { composant: `/api/composants/${entityId}` }
|
||||
} else if (entityKind === 'piece') {
|
||||
endpoint = `/machine_piece_links/${linkId}`
|
||||
payload = { piece: `/api/pieces/${entityId}` }
|
||||
} else {
|
||||
endpoint = `/machine_product_links/${linkId}`
|
||||
payload = { product: `/api/products/${entityId}` }
|
||||
}
|
||||
|
||||
const result: any = await apiPatch(endpoint, payload)
|
||||
if (result.success) {
|
||||
toast.showSuccess('Item associé avec succès')
|
||||
await reloadMachineStructure()
|
||||
} else {
|
||||
toast.showError('Erreur lors de l\'association')
|
||||
}
|
||||
}
|
||||
|
||||
const removeProductLink = async (linkId: string) => {
|
||||
const result: any = await apiDel(`/machine_product_links/${linkId}`)
|
||||
if (result.success) {
|
||||
@@ -301,6 +364,10 @@ export function useMachineDetailHierarchy(deps: MachineDetailHierarchyDeps) {
|
||||
addPieceLink,
|
||||
removePieceLink,
|
||||
addProductLink,
|
||||
addComponentLinkCategoryOnly,
|
||||
addPieceLinkCategoryOnly,
|
||||
addProductLinkCategoryOnly,
|
||||
fillEntityLink,
|
||||
removeProductLink,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ export function useMachineDetailProducts(deps: MachineDetailProductsDeps) {
|
||||
return {
|
||||
id: (resolved?.id as string) || productId || null,
|
||||
linkId: (link.id as string) || (typeof link['@id'] === 'string' ? link['@id'].split('/').pop() : null) || null,
|
||||
name: (resolved?.name as string) || 'Produit inconnu',
|
||||
name: (resolved?.name as string) || (link.modelType as AnyRecord)?.name as string || 'Produit inconnu',
|
||||
reference: (resolved?.reference as string) || null,
|
||||
supplierLabel: resolvedConstructeurs.length
|
||||
? resolvedConstructeurs.map((c) => c.name).filter(Boolean).join(', ') || null
|
||||
@@ -111,6 +111,9 @@ export function useMachineDetailProducts(deps: MachineDetailProductsDeps) {
|
||||
priceLabel: resolved ? getProductPriceLabel(resolved) : null,
|
||||
groupLabel: ((resolved?.typeProduct as AnyRecord)?.name as string) || '',
|
||||
documents: productId ? (productDocumentsMap.value.get(productId) || []) : [],
|
||||
pendingEntity: (link.pendingEntity as boolean) || false,
|
||||
modelTypeId: (link.modelTypeId as string) || null,
|
||||
modelType: (link.modelType as string) || null,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
:is-edit-mode="d.isEditMode.value"
|
||||
@add-product="openAddModal('product')"
|
||||
@remove-product="async (id) => { await d.removeProductLink(id); refreshVersions() }"
|
||||
@fill-entity="(linkId, typeId) => handleFillEntity(linkId, 'product', typeId)"
|
||||
/>
|
||||
|
||||
<!-- Components Section -->
|
||||
@@ -115,6 +116,7 @@
|
||||
@custom-field-update="d.updatePieceCustomField"
|
||||
@add-component="openAddModal('component')"
|
||||
@remove-component="async (id) => { await d.removeComponentLink(id); refreshVersions() }"
|
||||
@fill-entity="(linkId, typeId) => handleFillEntity(linkId, 'component', typeId)"
|
||||
/>
|
||||
|
||||
<!-- Machine Pieces Section -->
|
||||
@@ -129,6 +131,7 @@
|
||||
@custom-field-update="d.updatePieceCustomField"
|
||||
@add-piece="openAddModal('piece')"
|
||||
@remove-piece="async (id) => { await d.removePieceLink(id); refreshVersions() }"
|
||||
@fill-entity="(linkId, typeId) => handleFillEntity(linkId, 'piece', typeId)"
|
||||
@toggle-collapse="d.toggleAllPieces"
|
||||
/>
|
||||
|
||||
@@ -136,7 +139,8 @@
|
||||
<AddEntityToMachineModal
|
||||
:open="addModalOpen"
|
||||
:entity-kind="addModalKind"
|
||||
@close="addModalOpen = false"
|
||||
:prefill-type-id="fillTypeId"
|
||||
@close="addModalOpen = false; fillLinkId = ''; fillTypeId = ''"
|
||||
@confirm="handleAddEntity"
|
||||
/>
|
||||
|
||||
@@ -277,6 +281,8 @@ const historyFieldLabels = {
|
||||
|
||||
const addModalOpen = ref(false)
|
||||
const addModalKind = ref('component')
|
||||
const fillLinkId = ref('')
|
||||
const fillTypeId = ref('')
|
||||
|
||||
const openAddModal = (kind) => {
|
||||
addModalKind.value = kind
|
||||
@@ -288,17 +294,40 @@ const handleRemoveConstructeurLink = (constructeurId) => {
|
||||
d.handleMachineConstructeurChange(ids)
|
||||
}
|
||||
|
||||
const handleAddEntity = async (entityId) => {
|
||||
if (addModalKind.value === 'component') {
|
||||
await d.addComponentLink(entityId)
|
||||
} else if (addModalKind.value === 'piece') {
|
||||
await d.addPieceLink(entityId)
|
||||
const handleAddEntity = async (payload) => {
|
||||
const { entityId, modelTypeId } = payload
|
||||
|
||||
if (fillLinkId.value) {
|
||||
await d.fillEntityLink(fillLinkId.value, entityId, addModalKind.value)
|
||||
fillLinkId.value = ''
|
||||
fillTypeId.value = ''
|
||||
} else if (entityId) {
|
||||
if (addModalKind.value === 'component') {
|
||||
await d.addComponentLink(entityId)
|
||||
} else if (addModalKind.value === 'piece') {
|
||||
await d.addPieceLink(entityId)
|
||||
} else {
|
||||
await d.addProductLink(entityId)
|
||||
}
|
||||
} else {
|
||||
await d.addProductLink(entityId)
|
||||
if (addModalKind.value === 'component') {
|
||||
await d.addComponentLinkCategoryOnly(modelTypeId)
|
||||
} else if (addModalKind.value === 'piece') {
|
||||
await d.addPieceLinkCategoryOnly(modelTypeId)
|
||||
} else {
|
||||
await d.addProductLinkCategoryOnly(modelTypeId)
|
||||
}
|
||||
}
|
||||
refreshVersions()
|
||||
}
|
||||
|
||||
const handleFillEntity = (linkId, entityKind, modelTypeId) => {
|
||||
fillLinkId.value = linkId
|
||||
fillTypeId.value = modelTypeId
|
||||
addModalKind.value = entityKind
|
||||
addModalOpen.value = true
|
||||
}
|
||||
|
||||
const machineViewTitle = computed(() => {
|
||||
return d.isEditMode.value ? 'Modification de la machine' : 'Détails de la machine'
|
||||
})
|
||||
|
||||
94
migrations/Version20260403_CategoryOnlyLinks.php
Normal file
94
migrations/Version20260403_CategoryOnlyLinks.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20260403_CategoryOnlyLinks extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Allow category-only machine links: make entity FKs nullable, add modelType FK to link tables';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// 1. Make entity FK columns nullable
|
||||
$this->addSql('ALTER TABLE machine_component_links ALTER COLUMN composantid DROP NOT NULL');
|
||||
$this->addSql('ALTER TABLE machine_piece_links ALTER COLUMN pieceid DROP NOT NULL');
|
||||
$this->addSql('ALTER TABLE machine_product_links ALTER COLUMN productid DROP NOT NULL');
|
||||
|
||||
// 2. Add modeltypeid column to all 3 tables
|
||||
$this->addSql('ALTER TABLE machine_component_links ADD COLUMN IF NOT EXISTS modeltypeid VARCHAR(36) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE machine_piece_links ADD COLUMN IF NOT EXISTS modeltypeid VARCHAR(36) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE machine_product_links ADD COLUMN IF NOT EXISTS modeltypeid VARCHAR(36) DEFAULT NULL');
|
||||
|
||||
// 3. Add FK constraints from modeltypeid to model_types(id) ON DELETE SET NULL
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'fk_machine_component_links_modeltype' AND table_name = 'machine_component_links'
|
||||
) THEN
|
||||
ALTER TABLE machine_component_links ADD CONSTRAINT fk_machine_component_links_modeltype
|
||||
FOREIGN KEY (modeltypeid) REFERENCES model_types(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'fk_machine_piece_links_modeltype' AND table_name = 'machine_piece_links'
|
||||
) THEN
|
||||
ALTER TABLE machine_piece_links ADD CONSTRAINT fk_machine_piece_links_modeltype
|
||||
FOREIGN KEY (modeltypeid) REFERENCES model_types(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
SQL);
|
||||
|
||||
$this->addSql(<<<'SQL'
|
||||
DO $$ BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.table_constraints
|
||||
WHERE constraint_name = 'fk_machine_product_links_modeltype' AND table_name = 'machine_product_links'
|
||||
) THEN
|
||||
ALTER TABLE machine_product_links ADD CONSTRAINT fk_machine_product_links_modeltype
|
||||
FOREIGN KEY (modeltypeid) REFERENCES model_types(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
SQL);
|
||||
|
||||
// 4. Add indexes on modeltypeid
|
||||
$this->addSql('CREATE INDEX IF NOT EXISTS idx_machine_component_links_modeltypeid ON machine_component_links (modeltypeid)');
|
||||
$this->addSql('CREATE INDEX IF NOT EXISTS idx_machine_piece_links_modeltypeid ON machine_piece_links (modeltypeid)');
|
||||
$this->addSql('CREATE INDEX IF NOT EXISTS idx_machine_product_links_modeltypeid ON machine_product_links (modeltypeid)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// Drop indexes
|
||||
$this->addSql('DROP INDEX IF EXISTS idx_machine_component_links_modeltypeid');
|
||||
$this->addSql('DROP INDEX IF EXISTS idx_machine_piece_links_modeltypeid');
|
||||
$this->addSql('DROP INDEX IF EXISTS idx_machine_product_links_modeltypeid');
|
||||
|
||||
// Drop FK constraints
|
||||
$this->addSql('ALTER TABLE machine_component_links DROP CONSTRAINT IF EXISTS fk_machine_component_links_modeltype');
|
||||
$this->addSql('ALTER TABLE machine_piece_links DROP CONSTRAINT IF EXISTS fk_machine_piece_links_modeltype');
|
||||
$this->addSql('ALTER TABLE machine_product_links DROP CONSTRAINT IF EXISTS fk_machine_product_links_modeltype');
|
||||
|
||||
// Drop modeltypeid columns
|
||||
$this->addSql('ALTER TABLE machine_component_links DROP COLUMN IF EXISTS modeltypeid');
|
||||
$this->addSql('ALTER TABLE machine_piece_links DROP COLUMN IF EXISTS modeltypeid');
|
||||
$this->addSql('ALTER TABLE machine_product_links DROP COLUMN IF EXISTS modeltypeid');
|
||||
|
||||
// Restore NOT NULL on entity FK columns
|
||||
$this->addSql('ALTER TABLE machine_component_links ALTER COLUMN composantid SET NOT NULL');
|
||||
$this->addSql('ALTER TABLE machine_piece_links ALTER COLUMN pieceid SET NOT NULL');
|
||||
$this->addSql('ALTER TABLE machine_product_links ALTER COLUMN productid SET NOT NULL');
|
||||
}
|
||||
}
|
||||
@@ -623,17 +623,21 @@ class MachineStructureController extends AbstractController
|
||||
{
|
||||
return array_map(function (MachineComponentLink $link): array {
|
||||
$composant = $link->getComposant();
|
||||
$modelType = $link->getModelType();
|
||||
$parentLink = $link->getParentLink();
|
||||
|
||||
return [
|
||||
'id' => $link->getId(),
|
||||
'linkId' => $link->getId(),
|
||||
'machineId' => $link->getMachine()->getId(),
|
||||
'composantId' => $composant->getId(),
|
||||
'composant' => $this->normalizeComposant($composant),
|
||||
'composantId' => $composant?->getId(),
|
||||
'composant' => $composant ? $this->normalizeComposant($composant) : null,
|
||||
'modelTypeId' => $modelType?->getId(),
|
||||
'modelType' => $modelType ? $this->normalizeModelType($modelType) : null,
|
||||
'pendingEntity' => null === $composant,
|
||||
'parentLinkId' => $parentLink?->getId(),
|
||||
'parentComponentLinkId' => $parentLink?->getId(),
|
||||
'parentComponentId' => $parentLink?->getComposant()->getId(),
|
||||
'parentComponentId' => $parentLink?->getComposant()?->getId(),
|
||||
'overrides' => $this->normalizeOverrides($link),
|
||||
'childLinks' => [],
|
||||
'pieceLinks' => [],
|
||||
@@ -645,19 +649,23 @@ class MachineStructureController extends AbstractController
|
||||
{
|
||||
return array_map(function (MachinePieceLink $link): array {
|
||||
$piece = $link->getPiece();
|
||||
$modelType = $link->getModelType();
|
||||
$parentLink = $link->getParentLink();
|
||||
|
||||
return [
|
||||
'id' => $link->getId(),
|
||||
'linkId' => $link->getId(),
|
||||
'machineId' => $link->getMachine()->getId(),
|
||||
'pieceId' => $piece->getId(),
|
||||
'piece' => $this->normalizePiece($piece),
|
||||
'pieceId' => $piece?->getId(),
|
||||
'piece' => $piece ? $this->normalizePiece($piece) : null,
|
||||
'modelTypeId' => $modelType?->getId(),
|
||||
'modelType' => $modelType ? $this->normalizeModelType($modelType) : null,
|
||||
'pendingEntity' => null === $piece,
|
||||
'parentLinkId' => $parentLink?->getId(),
|
||||
'parentComponentLinkId' => $parentLink?->getId(),
|
||||
'parentComponentId' => $parentLink?->getComposant()->getId(),
|
||||
'parentComponentId' => $parentLink?->getComposant()?->getId(),
|
||||
'overrides' => $this->normalizeOverrides($link),
|
||||
'quantity' => $this->resolvePieceQuantity($link),
|
||||
'quantity' => $piece ? $this->resolvePieceQuantity($link) : 1,
|
||||
];
|
||||
}, $links);
|
||||
}
|
||||
@@ -665,13 +673,16 @@ class MachineStructureController extends AbstractController
|
||||
private function resolvePieceQuantity(MachinePieceLink $link): int
|
||||
{
|
||||
$parentLink = $link->getParentLink();
|
||||
$piece = $link->getPiece();
|
||||
|
||||
if (!$parentLink) {
|
||||
if (!$parentLink || !$piece) {
|
||||
return $link->getQuantity();
|
||||
}
|
||||
|
||||
$composant = $parentLink->getComposant();
|
||||
$piece = $link->getPiece();
|
||||
if (!$composant) {
|
||||
return $link->getQuantity();
|
||||
}
|
||||
|
||||
foreach ($composant->getPieceSlots() as $slot) {
|
||||
if ($slot->getSelectedPiece()?->getId() === $piece->getId()) {
|
||||
@@ -685,14 +696,18 @@ class MachineStructureController extends AbstractController
|
||||
private function normalizeProductLinks(array $links): array
|
||||
{
|
||||
return array_map(function (MachineProductLink $link): array {
|
||||
$product = $link->getProduct();
|
||||
$product = $link->getProduct();
|
||||
$modelType = $link->getModelType();
|
||||
|
||||
return [
|
||||
'id' => $link->getId(),
|
||||
'linkId' => $link->getId(),
|
||||
'machineId' => $link->getMachine()->getId(),
|
||||
'productId' => $product->getId(),
|
||||
'product' => $this->normalizeProduct($product),
|
||||
'productId' => $product?->getId(),
|
||||
'product' => $product ? $this->normalizeProduct($product) : null,
|
||||
'modelTypeId' => $modelType?->getId(),
|
||||
'modelType' => $modelType ? $this->normalizeModelType($modelType) : null,
|
||||
'pendingEntity' => null === $product,
|
||||
'parentLinkId' => $link->getParentLink()?->getId(),
|
||||
'parentComponentLinkId' => $link->getParentComponentLink()?->getId(),
|
||||
'parentPieceLinkId' => $link->getParentPieceLink()?->getId(),
|
||||
|
||||
@@ -46,8 +46,12 @@ class MachineComponentLink
|
||||
private Machine $machine;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: Composant::class, inversedBy: 'machineLinks')]
|
||||
#[ORM\JoinColumn(name: 'composantId', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
|
||||
private Composant $composant;
|
||||
#[ORM\JoinColumn(name: 'composantId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
|
||||
private ?Composant $composant = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: ModelType::class)]
|
||||
#[ORM\JoinColumn(name: 'modelTypeId', referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
|
||||
private ?ModelType $modelType = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: MachineComponentLink::class, inversedBy: 'childLinks')]
|
||||
#[ORM\JoinColumn(name: 'parentLinkId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
|
||||
@@ -107,18 +111,30 @@ class MachineComponentLink
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getComposant(): Composant
|
||||
public function getComposant(): ?Composant
|
||||
{
|
||||
return $this->composant;
|
||||
}
|
||||
|
||||
public function setComposant(Composant $composant): static
|
||||
public function setComposant(?Composant $composant): static
|
||||
{
|
||||
$this->composant = $composant;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getModelType(): ?ModelType
|
||||
{
|
||||
return $this->modelType;
|
||||
}
|
||||
|
||||
public function setModelType(?ModelType $modelType): static
|
||||
{
|
||||
$this->modelType = $modelType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getParentLink(): ?MachineComponentLink
|
||||
{
|
||||
return $this->parentLink;
|
||||
|
||||
@@ -47,8 +47,12 @@ class MachinePieceLink
|
||||
private Machine $machine;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: Piece::class, inversedBy: 'machineLinks')]
|
||||
#[ORM\JoinColumn(name: 'pieceId', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
|
||||
private Piece $piece;
|
||||
#[ORM\JoinColumn(name: 'pieceId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
|
||||
private ?Piece $piece = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: ModelType::class)]
|
||||
#[ORM\JoinColumn(name: 'modelTypeId', referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
|
||||
private ?ModelType $modelType = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: MachineComponentLink::class, inversedBy: 'pieceLinks')]
|
||||
#[ORM\JoinColumn(name: 'parentLinkId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
|
||||
@@ -98,18 +102,30 @@ class MachinePieceLink
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPiece(): Piece
|
||||
public function getPiece(): ?Piece
|
||||
{
|
||||
return $this->piece;
|
||||
}
|
||||
|
||||
public function setPiece(Piece $piece): static
|
||||
public function setPiece(?Piece $piece): static
|
||||
{
|
||||
$this->piece = $piece;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getModelType(): ?ModelType
|
||||
{
|
||||
return $this->modelType;
|
||||
}
|
||||
|
||||
public function setModelType(?ModelType $modelType): static
|
||||
{
|
||||
$this->modelType = $modelType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getParentLink(): ?MachineComponentLink
|
||||
{
|
||||
return $this->parentLink;
|
||||
|
||||
@@ -46,8 +46,12 @@ class MachineProductLink
|
||||
private Machine $machine;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: Product::class, inversedBy: 'machineLinks')]
|
||||
#[ORM\JoinColumn(name: 'productId', referencedColumnName: 'id', nullable: false, onDelete: 'CASCADE')]
|
||||
private Product $product;
|
||||
#[ORM\JoinColumn(name: 'productId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
|
||||
private ?Product $product = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: ModelType::class)]
|
||||
#[ORM\JoinColumn(name: 'modelTypeId', referencedColumnName: 'id', nullable: true, onDelete: 'SET NULL')]
|
||||
private ?ModelType $modelType = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: MachineProductLink::class, inversedBy: 'childLinks')]
|
||||
#[ORM\JoinColumn(name: 'parentLinkId', referencedColumnName: 'id', nullable: true, onDelete: 'CASCADE')]
|
||||
@@ -92,18 +96,30 @@ class MachineProductLink
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getProduct(): Product
|
||||
public function getProduct(): ?Product
|
||||
{
|
||||
return $this->product;
|
||||
}
|
||||
|
||||
public function setProduct(Product $product): static
|
||||
public function setProduct(?Product $product): static
|
||||
{
|
||||
$this->product = $product;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getModelType(): ?ModelType
|
||||
{
|
||||
return $this->modelType;
|
||||
}
|
||||
|
||||
public function setModelType(?ModelType $modelType): static
|
||||
{
|
||||
$this->modelType = $modelType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getParentLink(): ?MachineProductLink
|
||||
{
|
||||
return $this->parentLink;
|
||||
|
||||
@@ -74,18 +74,20 @@ final class MachineAuditSubscriber extends AbstractAuditSubscriber
|
||||
foreach ($entity->getComponentLinks() as $link) {
|
||||
$componentLinks[] = [
|
||||
'id' => $link->getId(),
|
||||
'composantId' => $link->getComposant()->getId(),
|
||||
'composantName' => $link->getComposant()->getName(),
|
||||
'composantId' => $link->getComposant()?->getId(),
|
||||
'composantName' => $link->getComposant()?->getName(),
|
||||
'modelTypeId' => $link->getModelType()?->getId(),
|
||||
];
|
||||
}
|
||||
|
||||
$pieceLinks = [];
|
||||
foreach ($entity->getPieceLinks() as $link) {
|
||||
$pieceLinks[] = [
|
||||
'id' => $link->getId(),
|
||||
'pieceId' => $link->getPiece()->getId(),
|
||||
'pieceName' => $link->getPiece()->getName(),
|
||||
'quantity' => $link->getQuantity(),
|
||||
'id' => $link->getId(),
|
||||
'pieceId' => $link->getPiece()?->getId(),
|
||||
'pieceName' => $link->getPiece()?->getName(),
|
||||
'quantity' => $link->getQuantity(),
|
||||
'modelTypeId' => $link->getModelType()?->getId(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -93,8 +95,9 @@ final class MachineAuditSubscriber extends AbstractAuditSubscriber
|
||||
foreach ($entity->getProductLinks() as $link) {
|
||||
$productLinks[] = [
|
||||
'id' => $link->getId(),
|
||||
'productId' => $link->getProduct()->getId(),
|
||||
'productName' => $link->getProduct()->getName(),
|
||||
'productId' => $link->getProduct()?->getId(),
|
||||
'productName' => $link->getProduct()?->getName(),
|
||||
'modelTypeId' => $link->getModelType()?->getId(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -187,8 +190,8 @@ final class MachineAuditSubscriber extends AbstractAuditSubscriber
|
||||
'machine' => $entity->getMachine(),
|
||||
'diffKey' => $action.'Component',
|
||||
'diffValue' => [
|
||||
'id' => $entity->getComposant()->getId(),
|
||||
'name' => $entity->getComposant()->getName(),
|
||||
'id' => $entity->getComposant()?->getId() ?? $entity->getModelType()?->getId(),
|
||||
'name' => $entity->getComposant()?->getName() ?? $entity->getModelType()?->getName() ?? 'Catégorie seule',
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -198,8 +201,8 @@ final class MachineAuditSubscriber extends AbstractAuditSubscriber
|
||||
'machine' => $entity->getMachine(),
|
||||
'diffKey' => $action.'Piece',
|
||||
'diffValue' => [
|
||||
'id' => $entity->getPiece()->getId(),
|
||||
'name' => $entity->getPiece()->getName(),
|
||||
'id' => $entity->getPiece()?->getId() ?? $entity->getModelType()?->getId(),
|
||||
'name' => $entity->getPiece()?->getName() ?? $entity->getModelType()?->getName() ?? 'Catégorie seule',
|
||||
],
|
||||
];
|
||||
}
|
||||
@@ -209,8 +212,8 @@ final class MachineAuditSubscriber extends AbstractAuditSubscriber
|
||||
'machine' => $entity->getMachine(),
|
||||
'diffKey' => $action.'Product',
|
||||
'diffValue' => [
|
||||
'id' => $entity->getProduct()->getId(),
|
||||
'name' => $entity->getProduct()->getName(),
|
||||
'id' => $entity->getProduct()?->getId() ?? $entity->getModelType()?->getId(),
|
||||
'name' => $entity->getProduct()?->getName() ?? $entity->getModelType()?->getName() ?? 'Catégorie seule',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -164,11 +164,11 @@ class MachineStructureTool
|
||||
'id' => $link->getId(),
|
||||
'linkId' => $link->getId(),
|
||||
'machineId' => $link->getMachine()->getId(),
|
||||
'composantId' => $composant->getId(),
|
||||
'composant' => $this->normalizeComposant($composant),
|
||||
'composantId' => $composant?->getId(),
|
||||
'composant' => $composant ? $this->normalizeComposant($composant) : null,
|
||||
'parentLinkId' => $parentLink?->getId(),
|
||||
'parentComponentLinkId' => $parentLink?->getId(),
|
||||
'parentComponentId' => $parentLink?->getComposant()->getId(),
|
||||
'parentComponentId' => $parentLink?->getComposant()?->getId(),
|
||||
'overrides' => $this->normalizeOverrides($link),
|
||||
'childLinks' => [],
|
||||
'pieceLinks' => [],
|
||||
@@ -189,13 +189,13 @@ class MachineStructureTool
|
||||
'id' => $link->getId(),
|
||||
'linkId' => $link->getId(),
|
||||
'machineId' => $link->getMachine()->getId(),
|
||||
'pieceId' => $piece->getId(),
|
||||
'piece' => $this->normalizePiece($piece),
|
||||
'pieceId' => $piece?->getId(),
|
||||
'piece' => $piece ? $this->normalizePiece($piece) : null,
|
||||
'parentLinkId' => $parentLink?->getId(),
|
||||
'parentComponentLinkId' => $parentLink?->getId(),
|
||||
'parentComponentId' => $parentLink?->getComposant()->getId(),
|
||||
'parentComponentId' => $parentLink?->getComposant()?->getId(),
|
||||
'overrides' => $this->normalizeOverrides($link),
|
||||
'quantity' => $this->resolvePieceQuantity($link),
|
||||
'quantity' => $piece ? $this->resolvePieceQuantity($link) : 1,
|
||||
];
|
||||
}, $links);
|
||||
}
|
||||
|
||||
@@ -869,22 +869,25 @@ final class EntityVersionService
|
||||
$snapshot['componentLinks'] = [];
|
||||
foreach ($entity->getComponentLinks() as $link) {
|
||||
$snapshot['componentLinks'][] = [
|
||||
'id' => $link->getId(), 'composantId' => $link->getComposant()->getId(),
|
||||
'composantName' => $link->getComposant()->getName(),
|
||||
'id' => $link->getId(), 'composantId' => $link->getComposant()?->getId(),
|
||||
'composantName' => $link->getComposant()?->getName(),
|
||||
'modelTypeId' => $link->getModelType()?->getId(),
|
||||
];
|
||||
}
|
||||
$snapshot['pieceLinks'] = [];
|
||||
foreach ($entity->getPieceLinks() as $link) {
|
||||
$snapshot['pieceLinks'][] = [
|
||||
'id' => $link->getId(), 'pieceId' => $link->getPiece()->getId(),
|
||||
'pieceName' => $link->getPiece()->getName(), 'quantity' => $link->getQuantity(),
|
||||
'id' => $link->getId(), 'pieceId' => $link->getPiece()?->getId(),
|
||||
'pieceName' => $link->getPiece()?->getName(), 'quantity' => $link->getQuantity(),
|
||||
'modelTypeId' => $link->getModelType()?->getId(),
|
||||
];
|
||||
}
|
||||
$snapshot['productLinks'] = [];
|
||||
foreach ($entity->getProductLinks() as $link) {
|
||||
$snapshot['productLinks'][] = [
|
||||
'id' => $link->getId(), 'productId' => $link->getProduct()->getId(),
|
||||
'productName' => $link->getProduct()->getName(),
|
||||
'id' => $link->getId(), 'productId' => $link->getProduct()?->getId(),
|
||||
'productName' => $link->getProduct()?->getName(),
|
||||
'modelTypeId' => $link->getModelType()?->getId(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user