From 95c2a8268926cb3a5b001ae585c52aceb527b799 Mon Sep 17 00:00:00 2001 From: MatthieuTD <39524319+MatthieuTD@users.noreply.github.com> Date: Thu, 9 Oct 2025 09:21:40 +0200 Subject: [PATCH] Normalize machine link responses --- app/composables/useMachines.js | 83 +++- app/pages/machine/[id].vue | 709 ++++++++++++++++++++++++++------- app/pages/machines/new.vue | 452 +++++++++++++++++---- 3 files changed, 1028 insertions(+), 216 deletions(-) diff --git a/app/composables/useMachines.js b/app/composables/useMachines.js index ce97c0c..69a36f9 100644 --- a/app/composables/useMachines.js +++ b/app/composables/useMachines.js @@ -5,6 +5,45 @@ import { useApi } from './useApi' const machines = ref([]) const loading = ref(false) +const resolveLinkCollection = (source, keys) => { + if (!source || typeof source !== 'object') { + return undefined + } + + for (const key of keys) { + const value = source[key] + if (Array.isArray(value)) { + return value + } + } + + return undefined +} + +const normalizeMachineResponse = (payload) => { + if (!payload || typeof payload !== 'object') { + return null + } + + const container = payload.machine && typeof payload.machine === 'object' + ? payload.machine + : payload + + const normalized = { ...container } + + const componentLinks = resolveLinkCollection(payload, ['componentLinks', 'machineComponentLinks']) ?? + resolveLinkCollection(container, ['componentLinks', 'machineComponentLinks']) ?? + [] + const pieceLinks = resolveLinkCollection(payload, ['pieceLinks', 'machinePieceLinks']) ?? + resolveLinkCollection(container, ['pieceLinks', 'machinePieceLinks']) ?? + [] + + normalized.componentLinks = componentLinks + normalized.pieceLinks = pieceLinks + + return normalized +} + export function useMachines () { const { showSuccess, showError, showInfo } = useToast() const { get, post, patch, delete: del } = useApi() @@ -14,8 +53,18 @@ export function useMachines () { try { const result = await get('/machines') if (result.success) { - machines.value = result.data - showInfo(`Chargement de ${machines.value.length} machine(s) réussi`) + const machineList = Array.isArray(result.data) + ? result.data + : Array.isArray(result.data?.machines) + ? result.data.machines + : Array.isArray(result.data?.data) + ? result.data.data + : [] + const normalized = machineList + .map((item) => normalizeMachineResponse(item)) + .filter(Boolean) + machines.value = normalized + showInfo(`Chargement de ${normalized.length} machine(s) réussi`) } } catch (error) { console.error('Erreur lors du chargement des machines:', error) @@ -29,8 +78,14 @@ export function useMachines () { try { const result = await post('/machines', machineData) if (result.success) { - machines.value.push(result.data) - showSuccess(`Machine "${machineData.name}" créée avec succès`) + const createdMachine = normalizeMachineResponse(result.data) || + normalizeMachineResponse(result.data?.machine) || + null + if (createdMachine) { + machines.value.push(createdMachine) + } + const displayName = createdMachine?.name || machineData?.name || '' + showSuccess(`Machine "${displayName}" créée avec succès`) } return result } catch (error) { @@ -58,11 +113,17 @@ export function useMachines () { try { const result = await patch(`/machines/${id}`, machineData) if (result.success) { + const updatedMachine = normalizeMachineResponse(result.data) || + normalizeMachineResponse(result.data?.machine) || + null const index = machines.value.findIndex(machine => machine.id === id) - if (index !== -1) { - machines.value[index] = result.data + if (index !== -1 && updatedMachine) { + machines.value[index] = { + ...machines.value[index], + ...updatedMachine, + } } - showSuccess(`Machine "${result.data?.name || machineData.name || ''}" mise à jour avec succès`) + showSuccess(`Machine "${updatedMachine?.name || machineData.name || ''}" mise à jour avec succès`) } return result } catch (error) { @@ -84,7 +145,13 @@ export function useMachines () { if (result.success) { const index = machines.value.findIndex(machine => machine.id === machineId) if (index !== -1) { - machines.value[index] = result.data?.machine || result.data + const updatedMachine = normalizeMachineResponse(result.data) || + normalizeMachineResponse(result.data?.machine) || + machines.value[index] + machines.value[index] = { + ...machines.value[index], + ...(updatedMachine || {}), + } } showSuccess('Structure de la machine mise à jour avec succès') } diff --git a/app/pages/machine/[id].vue b/app/pages/machine/[id].vue index a62c26b..a7b0fa7 100644 --- a/app/pages/machine/[id].vue +++ b/app/pages/machine/[id].vue @@ -858,18 +858,17 @@ const { updateMachine: updateMachineApi, reconfigureSkeleton: reconfigureMachineSkeleton, } = useMachines() -const { - getComposantsByMachine, - updateComposant: updateComposantApi +const { + updateComposant: updateComposantApi } = useComposants() const { - getPiecesByMachine, updatePiece: updatePieceApi } = usePieces() const { componentTypes, loadComponentTypes } = useComponentTypes() const { pieceTypes, loadPieceTypes } = usePieceTypes() const { upsertCustomFieldValue, updateCustomFieldValue: updateCustomFieldValueApi } = useCustomFields() +const { get } = useApi() const { uploadDocuments, deleteDocument, @@ -884,6 +883,8 @@ const loading = ref(true) const machine = ref(null) const components = ref([]) const pieces = ref([]) +const machineComponentLinks = ref([]) +const machinePieceLinks = ref([]) const printAreaRef = ref(null) const { constructeurs, loadConstructeurs } = useConstructeurs() @@ -970,6 +971,67 @@ const pieceTypeLabelMap = computed(() => { return map }) +const isPlainObject = (value) => Object.prototype.toString.call(value) === '[object Object]' + +const resolveIdentifier = (...candidates) => { + for (const candidate of candidates) { + if (candidate !== undefined && candidate !== null && candidate !== '') { + return candidate + } + } + return null +} + +const extractParentLinkIdentifiers = (source) => { + if (!source || typeof source !== 'object') { + return {} + } + + const identifiers = {} + + const idKeys = [ + 'parentRequirementId', + 'parentComponentRequirementId', + 'parentPieceRequirementId', + 'parentMachineComponentRequirementId', + 'parentMachinePieceRequirementId', + 'parentLinkId', + 'parentComponentLinkId', + 'parentPieceLinkId', + 'parentComponentId', + 'parentPieceId', + ] + + idKeys.forEach((key) => { + if (Object.prototype.hasOwnProperty.call(source, key)) { + const value = source[key] + if (value !== undefined && value !== null && value !== '') { + identifiers[key] = value + } + } + }) + + const objectKeys = [ + 'parentRequirement', + 'parentComponentRequirement', + 'parentPieceRequirement', + 'parentMachineComponentRequirement', + 'parentMachinePieceRequirement', + ] + + objectKeys.forEach((key) => { + const value = source[key] + if (isPlainObject(value) && value.id !== undefined && value.id !== null && value.id !== '') { + const idKey = `${key}Id` + if (!Object.prototype.hasOwnProperty.call(identifiers, idKey)) { + identifiers[idKey] = value.id + } + } + }) + + return identifiers +} + const resolveComponentRequirementTypeLabel = (requirement, entry) => { const typeId = entry?.typeComposantId || requirement?.typeComposantId || null if (!typeId) { @@ -994,47 +1056,126 @@ const getPieceRequirementEntries = (requirementId) => { return pieceRequirementSelections[requirementId] || [] } -const createComponentSelectionEntry = (requirement, source = null) => ({ - typeComposantId: source?.typeMachineComponentRequirement?.typeComposantId - || source?.typeComposantId - || source?.typeComposant?.id - || requirement?.typeComposantId - || null, - definition: { - name: - source?.name - || source?.nom - || requirement?.typeComposant?.name - || '', - reference: source?.reference || '', - constructeurId: source?.constructeurId || source?.constructeur?.id || null, - prix: - source?.prix - ?? source?.price - ?? null, - }, -}) +const createComponentSelectionEntry = (requirement, source = null) => { + const link = source?.machineComponentLink || null -const createPieceSelectionEntry = (requirement, source = null) => ({ - typePieceId: source?.typeMachinePieceRequirement?.typePieceId - || source?.typePieceId - || source?.typePiece?.id - || requirement?.typePieceId - || null, - definition: { - name: - source?.name - || source?.nom - || requirement?.typePiece?.name - || '', - reference: source?.reference || '', - constructeurId: source?.constructeurId || source?.constructeur?.id || null, - prix: - source?.prix - ?? source?.price - ?? null, - }, -}) + const entry = { + linkId: resolveIdentifier(link?.id, source?.machineComponentLinkId, source?.linkId), + composantId: resolveIdentifier(source?.composantId, source?.componentId, source?.id), + parentLinkId: resolveIdentifier( + link?.parentLinkId, + link?.parentComponentLinkId, + source?.parentComponentLinkId, + source?.parentLinkId, + ), + parentRequirementId: resolveIdentifier( + link?.parentRequirementId, + source?.parentRequirementId, + requirement?.parentRequirementId, + ), + parentMachineComponentRequirementId: resolveIdentifier( + link?.parentMachineComponentRequirementId, + source?.parentMachineComponentRequirementId, + requirement?.parentMachineComponentRequirementId, + ), + parentMachinePieceRequirementId: resolveIdentifier( + link?.parentMachinePieceRequirementId, + source?.parentMachinePieceRequirementId, + requirement?.parentMachinePieceRequirementId, + ), + parentComponentId: resolveIdentifier(link?.parentComponentId, source?.parentComponentId), + parentPieceId: resolveIdentifier(link?.parentPieceId, source?.parentPieceId), + typeComposantId: + source?.typeMachineComponentRequirement?.typeComposantId + || source?.typeComposantId + || source?.typeComposant?.id + || requirement?.typeComposantId + || null, + definition: { + name: + source?.name + || source?.nom + || requirement?.typeComposant?.name + || '', + reference: source?.reference || '', + constructeurId: source?.constructeurId || source?.constructeur?.id || null, + prix: + source?.prix + ?? source?.price + ?? null, + }, + } + + if (link?.overrides && isPlainObject(link.overrides)) { + entry.definition = { + ...entry.definition, + ...link.overrides, + } + } + + return entry +} + +const createPieceSelectionEntry = (requirement, source = null) => { + const link = source?.machinePieceLink || null + + const entry = { + linkId: resolveIdentifier(link?.id, source?.machinePieceLinkId, source?.linkId), + pieceId: resolveIdentifier(source?.pieceId, source?.id), + parentLinkId: resolveIdentifier(link?.parentLinkId, source?.parentLinkId), + parentComponentLinkId: resolveIdentifier( + link?.parentComponentLinkId, + source?.parentComponentLinkId, + source?.machineComponentLinkId, + ), + parentRequirementId: resolveIdentifier( + link?.parentRequirementId, + source?.parentRequirementId, + requirement?.parentRequirementId, + ), + parentMachineComponentRequirementId: resolveIdentifier( + link?.parentMachineComponentRequirementId, + source?.parentMachineComponentRequirementId, + requirement?.parentMachineComponentRequirementId, + ), + parentMachinePieceRequirementId: resolveIdentifier( + link?.parentMachinePieceRequirementId, + source?.parentMachinePieceRequirementId, + requirement?.parentMachinePieceRequirementId, + ), + parentComponentId: resolveIdentifier(link?.parentComponentId, source?.parentComponentId, source?.composantId), + parentPieceId: resolveIdentifier(link?.parentPieceId, source?.parentPieceId), + composantId: resolveIdentifier(source?.composantId, link?.composantId, link?.componentId), + typePieceId: + source?.typeMachinePieceRequirement?.typePieceId + || source?.typePieceId + || source?.typePiece?.id + || requirement?.typePieceId + || null, + definition: { + name: + source?.name + || source?.nom + || requirement?.typePiece?.name + || '', + reference: source?.reference || '', + constructeurId: source?.constructeurId || source?.constructeur?.id || null, + prix: + source?.prix + ?? source?.price + ?? null, + }, + } + + if (link?.overrides && isPlainObject(link.overrides)) { + entry.definition = { + ...entry.definition, + ...link.overrides, + } + } + + return entry +} const resetSkeletonRequirementSelections = () => { Object.keys(componentRequirementSelections).forEach((key) => { @@ -1225,8 +1366,8 @@ const changeMachineView = async (view) => { const validateSkeletonSelections = (type) => { const errors = [] - const componentSelectionsPayload = [] - const pieceSelectionsPayload = [] + const componentLinksPayload = [] + const pieceLinksPayload = [] for (const requirement of type.componentRequirements || []) { const entries = getComponentRequirementEntries(requirement.id) @@ -1254,17 +1395,32 @@ const validateSkeletonSelections = (type) => { return } - const payload = { requirementId: requirement.id } - if (entry.typeComposantId && entry.typeComposantId !== requirement.typeComposantId) { - payload.typeComposantId = entry.typeComposantId + const payload = { + requirementId: requirement.id, + typeComposantId: resolvedTypeId, + } + + if (entry.linkId) { + payload.id = entry.linkId + payload.linkId = entry.linkId + } + + if (entry.composantId) { + payload.composantId = entry.composantId } const overrides = sanitizeDefinitionOverrides(entry.definition) if (overrides) { - payload.definition = overrides + payload.overrides = overrides } - componentSelectionsPayload.push(payload) + Object.assign( + payload, + extractParentLinkIdentifiers(requirement), + extractParentLinkIdentifiers(entry), + ) + + componentLinksPayload.push(payload) }) } @@ -1294,17 +1450,36 @@ const validateSkeletonSelections = (type) => { return } - const payload = { requirementId: requirement.id } - if (entry.typePieceId && entry.typePieceId !== requirement.typePieceId) { - payload.typePieceId = entry.typePieceId + const payload = { + requirementId: requirement.id, + typePieceId: resolvedTypeId, + } + + if (entry.linkId) { + payload.id = entry.linkId + payload.linkId = entry.linkId + } + + if (entry.pieceId) { + payload.pieceId = entry.pieceId + } + + if (entry.composantId) { + payload.composantId = entry.composantId } const overrides = sanitizeDefinitionOverrides(entry.definition) if (overrides) { - payload.definition = overrides + payload.overrides = overrides } - pieceSelectionsPayload.push(payload) + Object.assign( + payload, + extractParentLinkIdentifiers(requirement), + extractParentLinkIdentifiers(entry), + ) + + pieceLinksPayload.push(payload) }) } @@ -1314,8 +1489,8 @@ const validateSkeletonSelections = (type) => { return { valid: true, - componentSelections: componentSelectionsPayload, - pieceSelections: pieceSelectionsPayload, + componentLinks: componentLinksPayload, + pieceLinks: pieceLinksPayload, } } @@ -1333,6 +1508,16 @@ const applySkeletonReconfigurationResult = async (data) => { machineDocumentsLoaded.value = !!(machine.value.documents?.length) } + const linksApplied = applyMachineLinks(data) || applyMachineLinks(updatedMachine) + if (linksApplied) { + if (machine.value) { + machine.value.componentLinks = machineComponentLinks.value + machine.value.pieceLinks = machinePieceLinks.value + } + collapseAllComponents() + return + } + const newComponents = data.components ?? updatedMachine?.components ?? null if (Array.isArray(newComponents)) { components.value = transformComponentCustomFields(newComponents) @@ -1352,7 +1537,7 @@ const saveSkeletonConfiguration = async () => { } const type = machineType.value - let payload = { componentSelections: [], pieceSelections: [] } + let payload = { componentLinks: [], pieceLinks: [] } if (type && machineHasSkeletonRequirements.value) { const validation = validateSkeletonSelections(type) @@ -1361,8 +1546,8 @@ const saveSkeletonConfiguration = async () => { return } payload = { - componentSelections: validation.componentSelections, - pieceSelections: validation.pieceSelections, + componentLinks: validation.componentLinks, + pieceLinks: validation.pieceLinks, } } @@ -1436,8 +1621,17 @@ const getMachineFieldId = (fieldName) => { // Computed const machinePieces = computed(() => { - const filteredPieces = pieces.value.filter(piece => !piece.composantId) - console.log('machinePieces computed:', filteredPieces) + const filteredPieces = pieces.value.filter((piece) => { + const parentLinkId = resolveIdentifier( + piece.parentComponentLinkId, + piece.machinePieceLink?.parentComponentLinkId, + piece.parentLinkId, + ) + if (parentLinkId) { + return false + } + return !piece.composantId + }) return filteredPieces }) @@ -2081,96 +2275,319 @@ function mergeComponentTrees(existing = [], updates = []) { return merged } +const buildMachineHierarchyFromLinks = (componentLinks = [], pieceLinks = []) => { + const componentMap = new Map() + const componentRoots = [] + + componentLinks.forEach((link, index) => { + if (!isPlainObject(link)) { + return + } + + const baseComponent = isPlainObject(link.composant) + ? link.composant + : isPlainObject(link.component) + ? link.component + : isPlainObject(link.targetComponent) + ? link.targetComponent + : {} + + const linkId = resolveIdentifier(link.id, link.linkId, link.machineComponentLinkId) + + const node = { + ...baseComponent, + machineComponentLink: link, + machineComponentLinkId: linkId, + linkId, + componentLinkId: linkId, + composantId: resolveIdentifier( + baseComponent.composantId, + baseComponent.componentId, + link.composantId, + link.componentId, + baseComponent.id, + ), + parentComponentLinkId: resolveIdentifier( + link.parentComponentLinkId, + link.parentLinkId, + link.parentMachineComponentLinkId, + baseComponent.parentComponentLinkId, + baseComponent.parentLinkId, + ), + parentComposantId: resolveIdentifier( + baseComponent.parentComposantId, + link.parentComponentId, + ), + parentRequirementId: resolveIdentifier( + baseComponent.parentRequirementId, + link.parentRequirementId, + ), + parentMachineComponentRequirementId: resolveIdentifier( + baseComponent.parentMachineComponentRequirementId, + link.parentMachineComponentRequirementId, + ), + parentMachinePieceRequirementId: resolveIdentifier( + baseComponent.parentMachinePieceRequirementId, + link.parentMachinePieceRequirementId, + ), + typeMachineComponentRequirement: + link.requirement + || link.typeMachineComponentRequirement + || baseComponent.typeMachineComponentRequirement + || null, + typeMachineComponentRequirementId: resolveIdentifier( + link.requirementId, + link.typeMachineComponentRequirementId, + (link.requirement || link.typeMachineComponentRequirement)?.id, + baseComponent.typeMachineComponentRequirementId, + ), + definition: baseComponent.definition || {}, + pieces: [], + subComponents: [], + sousComposants: [], + } + + if (!node.id) { + node.id = resolveIdentifier( + baseComponent.id, + node.composantId, + link.composantId, + link.componentId, + `component-${index}`, + ) + } + + node.requirementId = node.typeMachineComponentRequirementId + node.machineComponentLinkOverrides = link.overrides || null + node.overrides = link.overrides || null + node.definitionOverrides = link.overrides || null + node.subcomponents = node.subComponents + + componentMap.set(node.machineComponentLinkId || node.id, node) + }) + + componentMap.forEach((node) => { + const parentLinkId = resolveIdentifier(node.parentComponentLinkId) + if (parentLinkId && componentMap.has(parentLinkId)) { + const parent = componentMap.get(parentLinkId) + parent.subComponents.push(node) + parent.sousComposants = parent.subComponents + parent.subcomponents = parent.subComponents + node.parentComposantId = resolveIdentifier( + node.parentComposantId, + parent.composantId, + parent.id, + ) + } else { + componentRoots.push(node) + } + }) + + const machinePieces = [] + + pieceLinks.forEach((link, index) => { + if (!isPlainObject(link)) { + return + } + + const basePiece = isPlainObject(link.piece) + ? link.piece + : isPlainObject(link.targetPiece) + ? link.targetPiece + : isPlainObject(link.pieceModel) + ? link.pieceModel + : {} + + const linkId = resolveIdentifier(link.id, link.linkId, link.machinePieceLinkId) + const parentComponentLinkId = resolveIdentifier( + link.parentComponentLinkId, + link.parentLinkId, + link.parentMachineComponentLinkId, + basePiece.parentComponentLinkId, + ) + + const pieceEntry = { + ...basePiece, + id: resolveIdentifier(basePiece.id, link.pieceId, linkId, `piece-${index}`), + pieceId: resolveIdentifier(basePiece.id, link.pieceId), + machinePieceLink: link, + machinePieceLinkId: linkId, + linkId, + parentComponentLinkId, + parentLinkId: resolveIdentifier( + link.parentLinkId, + link.parentMachinePieceLinkId, + basePiece.parentLinkId, + ), + parentPieceLinkId: resolveIdentifier( + link.parentPieceLinkId, + basePiece.parentPieceLinkId, + ), + parentPieceId: resolveIdentifier( + basePiece.parentPieceId, + link.parentPieceId, + ), + parentComponentId: resolveIdentifier( + basePiece.parentComponentId, + link.parentComponentId, + ), + composantId: resolveIdentifier( + basePiece.composantId, + basePiece.componentId, + link.composantId, + link.componentId, + ), + typeMachinePieceRequirement: + link.requirement + || link.typeMachinePieceRequirement + || basePiece.typeMachinePieceRequirement + || null, + } + + pieceEntry.typeMachinePieceRequirementId = resolveIdentifier( + link.requirementId, + link.typeMachinePieceRequirementId, + pieceEntry.typeMachinePieceRequirement?.id, + basePiece.typeMachinePieceRequirementId, + ) + pieceEntry.parentMachineComponentRequirementId = resolveIdentifier( + basePiece.parentMachineComponentRequirementId, + link.parentMachineComponentRequirementId, + ) + pieceEntry.parentMachinePieceRequirementId = resolveIdentifier( + basePiece.parentMachinePieceRequirementId, + link.parentMachinePieceRequirementId, + ) + pieceEntry.definition = basePiece.definition || {} + pieceEntry.overrides = link.overrides || null + if (!pieceEntry.name && link.overrides?.name) { + pieceEntry.name = link.overrides.name + } + + if (parentComponentLinkId && componentMap.has(parentComponentLinkId)) { + const parent = componentMap.get(parentComponentLinkId) + parent.pieces.push(pieceEntry) + pieceEntry.parentComponentName = parent.name || parent.nom || null + } else { + machinePieces.push(pieceEntry) + } + }) + + componentMap.forEach((node) => { + node.sousComposants = node.subComponents + node.subcomponents = node.subComponents + }) + + return { + components: componentRoots, + machinePieces, + } +} + +const resolveLinkArray = (source, keys) => { + if (!source || typeof source !== 'object') { + return null + } + for (const key of keys) { + const value = source[key] + if (Array.isArray(value)) { + return value + } + } + return null +} + +const applyMachineLinks = (source) => { + const container = source?.machine ?? null + const componentLinks = + resolveLinkArray(source, ['componentLinks', 'machineComponentLinks']) ?? + resolveLinkArray(container, ['componentLinks', 'machineComponentLinks']) + const pieceLinks = + resolveLinkArray(source, ['pieceLinks', 'machinePieceLinks']) ?? + resolveLinkArray(container, ['pieceLinks', 'machinePieceLinks']) + + if (componentLinks === null && pieceLinks === null) { + return false + } + + const normalizedComponentLinks = componentLinks ?? [] + const normalizedPieceLinks = pieceLinks ?? [] + + machineComponentLinks.value = normalizedComponentLinks + machinePieceLinks.value = normalizedPieceLinks + + const { components: hierarchy, machinePieces: machineLevelPieces } = + buildMachineHierarchyFromLinks(normalizedComponentLinks, normalizedPieceLinks) + + components.value = transformComponentCustomFields(hierarchy) + pieces.value = transformCustomFields(machineLevelPieces) + + return true +} + // Methods const loadMachineData = async () => { loading.value = true try { - console.log('Début du chargement des données pour machineId:', machineId) - - // Load specific machine directly from API - const { apiCall } = useApi() - console.log('Appel API pour machine:', machineId) - const machineResult = await apiCall(`/machines/${machineId}`, { method: 'GET' }) - console.log('Résultat machine complet:', machineResult) - console.log('machineResult.success:', machineResult.success) - console.log('machineResult.data:', machineResult.data) - console.log('machineResult.error:', machineResult.error) - console.log('Machine customFieldValues:', machineResult.data?.customFieldValues) - console.log('Nombre de champs personnalisés:', machineResult.data?.customFieldValues?.length) - - if (machineResult.success) { - machine.value = machineResult.data - machine.value.documents = machine.value.documents || [] - machine.value.customFieldValues = machine.value.customFieldValues || [] - machineDocumentsLoaded.value = !!(machine.value.documents?.length) - console.log('Machine trouvée et assignée:', machine.value) + const machineResult = await get(`/machines/${machineId}`) - syncMachineCustomFields() - } else { - console.error('Machine non trouvée:', machineId) - console.error('Erreur API:', machineResult.error) - loading.value = false + if (!machineResult.success) { + console.error('Machine non trouvée:', machineId, machineResult.error) + machine.value = null + components.value = [] + pieces.value = [] return } - - // Initialize machine fields + + const machinePayload = machineResult.data?.machine && typeof machineResult.data.machine === 'object' + ? machineResult.data.machine + : machineResult.data + + if (!machinePayload || typeof machinePayload !== 'object') { + console.error('Réponse machine invalide pour', machineId) + machine.value = null + components.value = [] + pieces.value = [] + return + } + + machine.value = { + ...machinePayload, + documents: machinePayload.documents || [], + customFieldValues: machinePayload.customFieldValues || [], + } + + machineDocumentsLoaded.value = machine.value.documents.length > 0 + syncMachineCustomFields() initMachineFields() - console.log('Champs machine initialisés') - console.log('Machine après initialisation:', machine.value) - console.log('Machine name:', machineName.value) - console.log('Machine reference:', machineReference.value) - - // Load components with hierarchy - console.log('Chargement des composants avec hiérarchie...') - const componentsResult = await apiCall(`/composants/hierarchy/${machineId}`, { method: 'GET' }) - console.log('Résultat composants:', componentsResult) - console.log('Structure des données reçues:', JSON.stringify(componentsResult.data, null, 2)) - if (componentsResult.success) { - console.log('Transformation des composants...') - components.value = transformComponentCustomFields(componentsResult.data) - console.log('Composants chargés:', components.value.length) - console.log('Composants transformés:', components.value) - collapseAllComponents() - - // Debug: afficher la hiérarchie - console.log('=== HIÉRARCHIE DES COMPOSANTS ===') - components.value.forEach(comp => { - console.log(`Composant: ${comp.name} (ID: ${comp.id}, Parent: ${comp.parentComposantId})`) - if (comp.subComponents && comp.subComponents.length > 0) { - console.log(` Sous-composants: ${comp.subComponents.map(sc => sc.name).join(', ')}`) - } - }) - console.log('=== FIN HIÉRARCHIE ===') - } else { - console.error('Erreur lors du chargement des composants:', componentsResult.error) + + const linksApplied = applyMachineLinks(machineResult.data) + + if (machine.value) { + machine.value.componentLinks = machineComponentLinks.value + machine.value.pieceLinks = machinePieceLinks.value } - - // Load pieces from machine response instead of separate API call - if (machine.value && machine.value.pieces) { - // Transformer les champs personnalisés - pieces.value = transformCustomFields(machine.value.pieces) - console.log('Pièces transformées:', pieces.value) - console.log('Pièces chargées:', pieces.value.length) - } else { - console.log('Aucune pièce trouvée dans la réponse de la machine') + + if (!linksApplied) { + components.value = transformComponentCustomFields(machinePayload.components || []) + pieces.value = transformCustomFields(machinePayload.pieces || []) } + collapseAllComponents() + if (!machineDocumentsLoaded.value) { await refreshMachineDocuments() } - - console.log('Chargement terminé avec succès') } catch (error) { console.error('Erreur lors du chargement des données:', error) } finally { loading.value = false - console.log('Loading terminé, loading.value =', loading.value) } } const updateMachineInfo = async () => { if (!machine.value) return - + try { const result = await updateMachineApi(machine.value.id, { name: machineName.value, @@ -2178,8 +2595,25 @@ const updateMachineInfo = async () => { constructeurId: machineConstructeurId.value || null }) if (result.success) { - machine.value = result.data - machineConstructeurId.value = result.data.constructeurId || result.data.constructeur?.id || null + const machinePayload = result.data?.machine && typeof result.data.machine === 'object' + ? result.data.machine + : result.data + + if (machinePayload && typeof machinePayload === 'object') { + machine.value = { + ...machine.value, + ...machinePayload, + documents: machinePayload.documents || machine.value.documents || [], + customFieldValues: machinePayload.customFieldValues || machine.value.customFieldValues || [], + } + machineConstructeurId.value = machine.value.constructeurId || machine.value.constructeur?.id || null + + const linksApplied = applyMachineLinks(result.data) + if (linksApplied && machine.value) { + machine.value.componentLinks = machineComponentLinks.value + machine.value.pieceLinks = machinePieceLinks.value + } + } } } catch (error) { console.error('Erreur lors de la mise à jour de la machine:', error) @@ -2348,14 +2782,12 @@ const updatePieceCustomField = async (fieldUpdate) => { } } -const editComponent = (component) => { - // TODO: Implement edit modal - console.log('Edit component:', component) +const editComponent = () => { + toast.showInfo('La modification des composants sera bientôt disponible') } -const editPiece = (piece) => { - // TODO: Implement edit modal - console.log('Edit piece:', piece) +const editPiece = () => { + toast.showInfo('La modification des pièces sera bientôt disponible') } const toggleEditMode = () => { @@ -2415,7 +2847,6 @@ onMounted(() => { const route = useRoute() if (route.query.edit === 'true') { isEditMode.value = true - console.log('Mode édition activé depuis l\'URL') } }) diff --git a/app/pages/machines/new.vue b/app/pages/machines/new.vue index 26c215b..e53cbb4 100644 --- a/app/pages/machines/new.vue +++ b/app/pages/machines/new.vue @@ -203,16 +203,18 @@ Constructeur : {{ findComponentById(entry.composantId)?.constructeur?.name || findComponentById(entry.composantId)?.constructeurName || "—" }} -
- Machine actuelle : - {{ findComponentById(entry.composantId)?.machine?.name || findComponentById(entry.composantId)?.machineId || "Non affecté" }} -
-
- La réaffectation détachera ce composant de sa machine actuelle lors de la création. -
+
+ Machines liées : + {{ formatAssignmentList(getComponentMachineAssignments(findComponentById(entry.composantId))) || 'Aucune' }} +
+
+ Ce composant est déjà lié à + {{ formatAssignmentList(getComponentMachineAssignments(findComponentById(entry.composantId))) }}. + La création ajoutera un nouveau lien. +
@@ -319,20 +321,20 @@ Constructeur : {{ findPieceById(entry.pieceId)?.constructeur?.name || findPieceById(entry.pieceId)?.constructeurName || "—" }} -
- Machine actuelle : - {{ findPieceById(entry.pieceId)?.machine?.name || findPieceById(entry.pieceId)?.machineId || "Non affecté" }} -
-
- Composant actuel : - {{ findPieceById(entry.pieceId)?.composant?.name || findPieceById(entry.pieceId)?.composantId || "Non affecté" }} -
-
- Cette pièce sera détachée de son affectation actuelle pendant la création. -
+
+ Machines liées : + {{ formatAssignmentList(getPieceMachineAssignments(findPieceById(entry.pieceId))) || 'Aucune' }} +
+
+ Composants liés : + {{ formatAssignmentList(getPieceComponentAssignments(findPieceById(entry.pieceId))) || 'Aucun' }} +
+
+ Cette pièce dispose déjà de liaisons existantes. La création ajoutera un nouveau lien. +
@@ -583,6 +585,7 @@ import { useMachineTypesApi } from '~/composables/useMachineTypesApi' import { useComposants } from '~/composables/useComposants' import { usePieces } from '~/composables/usePieces' import { useToast } from '~/composables/useToast' +import { sanitizeDefinitionOverrides } from '~/shared/modelUtils' import IconLucidePlus from '~icons/lucide/plus' import IconLucideX from '~icons/lucide/x' import IconLucideEye from '~icons/lucide/eye' @@ -639,6 +642,215 @@ const pieceById = computed(() => { const componentInventory = computed(() => composants.value || []) const pieceInventory = computed(() => pieces.value || []) +const isPlainObject = value => value !== null && typeof value === 'object' && !Array.isArray(value) + +const toTrimmedString = (value) => { + if (typeof value === 'string') { + const trimmed = value.trim() + return trimmed.length > 0 ? trimmed : null + } + if (typeof value === 'number' && Number.isFinite(value)) { + return String(value) + } + return null +} + +const dedupeAssignments = (assignments) => { + const seen = new Set() + return assignments.filter((assignment) => { + if (!assignment) { + return false + } + const id = assignment.id != null ? String(assignment.id) : '' + const name = assignment.name != null ? String(assignment.name) : '' + const key = `${id}::${name}` + if (!id && !name) { + return false + } + if (seen.has(key)) { + return false + } + seen.add(key) + return true + }) +} + +const normalizeMachineAssignment = (input) => { + if (!input) { + return null + } + if (typeof input === 'string') { + const name = toTrimmedString(input) + return name ? { id: name, name } : null + } + if (typeof input === 'number' && Number.isFinite(input)) { + const value = String(input) + return { id: value, name: value } + } + + const container = input.machine || input.machineData || input + if (!isPlainObject(container)) { + return null + } + + const id = container.id ?? input.machineId ?? input.id ?? null + const name = + container.name + || input.machineName + || container.label + || container.title + || (typeof id === 'string' ? id : null) + || (typeof id === 'number' ? String(id) : null) + + if (id == null && name == null) { + return null + } + + return { + id: id != null ? id : null, + name: name != null ? name : null, + } +} + +const collectMachineAssignments = (source) => { + if (!isPlainObject(source)) { + return [] + } + + const candidates = [ + source.machines, + source.machineLinks, + source.machineAssignments, + source.machinesAssignments, + source.linkedMachines, + ] + + const assignments = [] + + candidates.forEach((list) => { + if (Array.isArray(list)) { + list.forEach((item) => { + const normalized = normalizeMachineAssignment(item) + if (normalized) { + assignments.push(normalized) + } + }) + } + }) + + if (!assignments.length) { + const direct = normalizeMachineAssignment(source.machine) + if (direct) { + assignments.push(direct) + } + } + + if (!assignments.length) { + const idCandidate = source.machineId ?? source.machineID ?? null + const nameCandidate = source.machineName ?? null + const normalized = normalizeMachineAssignment(nameCandidate || idCandidate) + if (normalized) { + assignments.push(normalized) + } + } + + return dedupeAssignments(assignments) +} + +const normalizeComponentAssignment = (input) => { + if (!input) { + return null + } + if (typeof input === 'string') { + const value = toTrimmedString(input) + return value ? { id: value, name: value } : null + } + if (typeof input === 'number' && Number.isFinite(input)) { + const value = String(input) + return { id: value, name: value } + } + + const container = input.component || input.composant || input + if (!isPlainObject(container)) { + return null + } + + const id = container.id ?? input.componentId ?? input.composantId ?? input.id ?? null + const name = + container.name + || input.componentName + || input.composantName + || container.label + || (typeof id === 'string' ? id : null) + || (typeof id === 'number' ? String(id) : null) + + if (id == null && name == null) { + return null + } + + return { + id: id != null ? id : null, + name: name != null ? name : null, + } +} + +const collectComponentAssignments = (source) => { + if (!isPlainObject(source)) { + return [] + } + + const candidates = [ + source.components, + source.composants, + source.componentLinks, + source.linkedComponents, + ] + + const assignments = [] + + candidates.forEach((list) => { + if (Array.isArray(list)) { + list.forEach((item) => { + const normalized = normalizeComponentAssignment(item) + if (normalized) { + assignments.push(normalized) + } + }) + } + }) + + if (!assignments.length) { + const direct = normalizeComponentAssignment(source.component || source.composant) + if (direct) { + assignments.push(direct) + } + } + + if (!assignments.length) { + const idCandidate = source.componentId ?? source.composantId ?? null + const normalized = normalizeComponentAssignment(idCandidate) + if (normalized) { + assignments.push(normalized) + } + } + + return dedupeAssignments(assignments) +} + +const getComponentMachineAssignments = component => collectMachineAssignments(component || {}) +const getPieceMachineAssignments = piece => collectMachineAssignments(piece || {}) +const getPieceComponentAssignments = piece => collectComponentAssignments(piece || {}) + +const formatAssignmentList = (assignments) => { + if (!Array.isArray(assignments) || assignments.length === 0) { + return '' + } + return assignments + .map((assignment) => assignment?.name || assignment?.id) + .filter(Boolean) + .join(', ') +} + const selectedComponentIds = computed(() => { const ids = [] Object.values(componentRequirementSelections).forEach((entries) => { @@ -715,10 +927,9 @@ const formatComponentOption = (component) => { if (constructeurName) { parts.push(constructeurName) } - if (component.machine?.name) { - parts.push(`Machine: ${component.machine.name}`) - } else if (component.machineId) { - parts.push(`Machine: ${component.machineId}`) + const machineAssignments = getComponentMachineAssignments(component) + if (machineAssignments.length) { + parts.push(`Machines: ${formatAssignmentList(machineAssignments)}`) } return parts.join(' • ') } @@ -735,15 +946,13 @@ const formatPieceOption = (piece) => { if (constructeurName) { parts.push(constructeurName) } - if (piece.machine?.name) { - parts.push(`Machine: ${piece.machine.name}`) - } else if (piece.machineId) { - parts.push(`Machine: ${piece.machineId}`) + const machineAssignments = getPieceMachineAssignments(piece) + if (machineAssignments.length) { + parts.push(`Machines: ${formatAssignmentList(machineAssignments)}`) } - if (piece.composant?.name) { - parts.push(`Composant: ${piece.composant.name}`) - } else if (piece.composantId) { - parts.push(`Composant: ${piece.composantId}`) + const componentAssignments = getPieceComponentAssignments(piece) + if (componentAssignments.length) { + parts.push(`Composants: ${formatAssignmentList(componentAssignments)}`) } return parts.join(' • ') } @@ -877,10 +1086,58 @@ const removePieceSelectionEntry = (requirementId, index) => { pieceRequirementSelections[requirementId] = entries.filter((_, i) => i !== index) } +const extractParentIdentifiers = (source) => { + if (!isPlainObject(source)) { + return {} + } + + const identifiers = {} + + const idKeys = [ + 'parentRequirementId', + 'parentComponentRequirementId', + 'parentPieceRequirementId', + 'parentMachineComponentRequirementId', + 'parentMachinePieceRequirementId', + 'parentLinkId', + 'parentComponentId', + 'parentPieceId', + ] + + idKeys.forEach((key) => { + if (Object.prototype.hasOwnProperty.call(source, key)) { + const value = source[key] + if (value !== undefined && value !== null && value !== '') { + identifiers[key] = value + } + } + }) + + const objectKeys = [ + 'parentRequirement', + 'parentComponentRequirement', + 'parentPieceRequirement', + 'parentMachineComponentRequirement', + 'parentMachinePieceRequirement', + ] + + objectKeys.forEach((key) => { + const value = source[key] + if (isPlainObject(value) && value.id !== undefined && value.id !== null && value.id !== '') { + const idKey = `${key}Id` + if (!Object.prototype.hasOwnProperty.call(identifiers, idKey)) { + identifiers[idKey] = value.id + } + } + }) + + return identifiers +} + const validateRequirementSelections = (type) => { const errors = [] - const componentSelectionsPayload = [] - const pieceSelectionsPayload = [] + const componentLinksPayload = [] + const pieceLinksPayload = [] for (const requirement of type.componentRequirements || []) { const entries = getComponentRequirementEntries(requirement.id) @@ -907,16 +1164,6 @@ const validateRequirementSelections = (type) => { return } - if (component.machineId) { - errors.push(`Le composant "${component.name || component.id}" est déjà affecté à une machine.`) - return - } - - if (component.parentComposantId) { - errors.push(`Le composant "${component.name || component.id}" est déjà rattaché à un autre composant.`) - return - } - const requiredTypeId = requirement.typeComposantId || requirement.typeComposant?.id || null if ( requiredTypeId && @@ -927,10 +1174,19 @@ const validateRequirementSelections = (type) => { return } - componentSelectionsPayload.push({ + const payload = { requirementId: requirement.id, composantId: entry.composantId, - }) + } + + const overrides = sanitizeDefinitionOverrides(entry.definition) + if (overrides) { + payload.overrides = overrides + } + + Object.assign(payload, extractParentIdentifiers(requirement), extractParentIdentifiers(entry)) + + componentLinksPayload.push(payload) }) } @@ -959,11 +1215,6 @@ const validateRequirementSelections = (type) => { return } - if (piece.machineId || piece.composantId) { - errors.push(`La pièce "${piece.name || piece.id}" est déjà affectée.`) - return - } - const requiredTypeId = requirement.typePieceId || requirement.typePiece?.id || null if ( requiredTypeId && @@ -974,10 +1225,19 @@ const validateRequirementSelections = (type) => { return } - pieceSelectionsPayload.push({ + const payload = { requirementId: requirement.id, pieceId: entry.pieceId, - }) + } + + const overrides = sanitizeDefinitionOverrides(entry.definition) + if (overrides) { + payload.overrides = overrides + } + + Object.assign(payload, extractParentIdentifiers(requirement), extractParentIdentifiers(entry)) + + pieceLinksPayload.push(payload) }) } @@ -987,8 +1247,8 @@ const validateRequirementSelections = (type) => { return { valid: true, - componentSelections: componentSelectionsPayload, - pieceSelections: pieceSelectionsPayload, + componentLinks: componentLinksPayload, + pieceLinks: pieceLinksPayload, } } @@ -1059,6 +1319,11 @@ const machinePreview = computed(() => { if (constructeurName) { subtitleParts.push(constructeurName) } + const machineAssignments = selectedComponent ? getComponentMachineAssignments(selectedComponent) : [] + const assignmentLabel = formatAssignmentList(machineAssignments) + if (assignmentLabel) { + subtitleParts.push(`Liée à ${assignmentLabel}`) + } const status = entry.composantId ? 'complete' : 'pending' return { @@ -1066,6 +1331,8 @@ const machinePreview = computed(() => { status, title: displayName, subtitle: subtitleParts.join(' • ') || null, + assignmentLabel, + assignments: machineAssignments, } }) @@ -1086,9 +1353,22 @@ const machinePreview = computed(() => { issues.push({ message: 'Sélectionner un composant pour chaque entrée.', kind: 'error', anchor: `component-group-${requirement.id}` }) } - const status = issues.some(issue => issue.kind === 'error') + normalizedEntries.forEach((entrySummary) => { + if (entrySummary.assignmentLabel) { + issues.push({ + message: `Le composant "${entrySummary.title}" est déjà lié à ${entrySummary.assignmentLabel}.`, + kind: 'warning', + anchor: `component-group-${requirement.id}`, + }) + } + }) + + const hasErrors = issues.some(issue => issue.kind === 'error') + const hasWarnings = issues.some(issue => issue.kind === 'warning') || completed < entries.length + + const status = hasErrors ? 'error' - : completed < entries.length + : hasWarnings ? 'warning' : 'ready' @@ -1120,6 +1400,16 @@ const machinePreview = computed(() => { if (constructeurName) { subtitleParts.push(constructeurName) } + const machineAssignments = selectedPiece ? getPieceMachineAssignments(selectedPiece) : [] + const machineAssignmentLabel = formatAssignmentList(machineAssignments) + if (machineAssignmentLabel) { + subtitleParts.push(`Machines: ${machineAssignmentLabel}`) + } + const componentAssignments = selectedPiece ? getPieceComponentAssignments(selectedPiece) : [] + const componentAssignmentLabel = formatAssignmentList(componentAssignments) + if (componentAssignmentLabel) { + subtitleParts.push(`Composants: ${componentAssignmentLabel}`) + } const status = entry.pieceId ? 'complete' : 'pending' return { @@ -1127,6 +1417,10 @@ const machinePreview = computed(() => { status, title: displayName, subtitle: subtitleParts.join(' • ') || null, + machineAssignmentLabel, + componentAssignmentLabel, + machineAssignments, + componentAssignments, } }) @@ -1147,9 +1441,29 @@ const machinePreview = computed(() => { issues.push({ message: 'Sélectionner une pièce pour chaque entrée.', kind: 'error', anchor: `piece-group-${requirement.id}` }) } - const status = issues.some(issue => issue.kind === 'error') + normalizedEntries.forEach((entrySummary) => { + if (entrySummary.machineAssignmentLabel) { + issues.push({ + message: `La pièce "${entrySummary.title}" est déjà liée aux machines ${entrySummary.machineAssignmentLabel}.`, + kind: 'warning', + anchor: `piece-group-${requirement.id}`, + }) + } + if (entrySummary.componentAssignmentLabel) { + issues.push({ + message: `La pièce "${entrySummary.title}" est déjà rattachée aux composants ${entrySummary.componentAssignmentLabel}.`, + kind: 'warning', + anchor: `piece-group-${requirement.id}`, + }) + } + }) + + const hasErrors = issues.some(issue => issue.kind === 'error') + const hasWarnings = issues.some(issue => issue.kind === 'warning') || completed < entries.length + + const status = hasErrors ? 'error' - : completed < entries.length + : hasWarnings ? 'warning' : 'ready' @@ -1293,8 +1607,8 @@ const finalizeMachineCreation = async () => { const hasRequirements = (type.componentRequirements?.length || 0) > 0 || (type.pieceRequirements?.length || 0) > 0 - let componentSelections = [] - let pieceSelections = [] + let componentLinks = [] + let pieceLinks = [] if (hasRequirements) { const validationResult = validateRequirementSelections(type) @@ -1302,16 +1616,16 @@ const finalizeMachineCreation = async () => { toast.showError(validationResult.error) return } - componentSelections = validationResult.componentSelections - pieceSelections = validationResult.pieceSelections + componentLinks = validationResult.componentLinks + pieceLinks = validationResult.pieceLinks } const payload = { ...baseMachineData, ...(hasRequirements ? { - componentSelections, - pieceSelections + componentLinks, + pieceLinks } : {}) }