/** * Machine detail — hierarchy & link management sub-composable. * * Handles machine hierarchy building, component/piece tree resolution, * flatten helpers, find-by-id utilities, and structure link CRUD. */ import { ref, computed } from 'vue' import { useApi } from '~/composables/useApi' import { useToast } from '~/composables/useToast' import { resolveIdentifier, } from '~/shared/utils/productDisplayUtils' import { buildMachineHierarchyFromLinks, resolveLinkArray, } from '~/composables/useMachineHierarchy' type AnyRecord = Record interface MachineDetailHierarchyDeps { machineId: string machine: Ref constructeurs: Ref findProductById: (id: string | null | undefined) => AnyRecord | null transformComponentCustomFields: (data: AnyRecord[]) => AnyRecord[] transformCustomFields: (data: AnyRecord[]) => AnyRecord[] syncMachineCustomFields: () => void } export function useMachineDetailHierarchy(deps: MachineDetailHierarchyDeps) { const { machineId, machine, constructeurs, findProductById, transformComponentCustomFields, transformCustomFields, syncMachineCustomFields, } = deps const { get, post: apiPost, delete: apiDel } = useApi() const toast = useToast() // --------------------------------------------------------------------------- // State // --------------------------------------------------------------------------- const components = ref([]) const pieces = ref([]) const machineComponentLinks = ref([]) const machinePieceLinks = ref([]) const machineProductLinks = ref([]) // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- const flattenComponents = (list: AnyRecord[] = []): AnyRecord[] => { const result: AnyRecord[] = [] const traverse = (items: AnyRecord[]) => { items.forEach((item) => { result.push(item) if (Array.isArray(item.subComponents) && item.subComponents.length) { traverse(item.subComponents as AnyRecord[]) } }) } traverse(list) return result } const findComponentById = (items: AnyRecord[] | undefined, id: string): AnyRecord | null => { for (const item of items || []) { if (item.id === id) return item const found = findComponentById(item.subComponents as AnyRecord[] | undefined, id) if (found) return found } return null } const findPieceById = (pieceId: string): AnyRecord | null => { const direct = pieces.value.find((p) => p.id === pieceId) if (direct) return direct const searchInComponents = (items: AnyRecord[]): AnyRecord | null => { for (const item of items || []) { const match = ((item.pieces as AnyRecord[]) || []).find((p) => p.id === pieceId) if (match) return match const nested = searchInComponents((item.subComponents as AnyRecord[]) || []) if (nested) return nested } return null } return searchInComponents(components.value) } // --------------------------------------------------------------------------- // Hierarchy & links // --------------------------------------------------------------------------- const applyMachineLinks = (source: AnyRecord): boolean => { const container = (source?.machine as AnyRecord) ?? null const componentLinksData = resolveLinkArray(source, ['componentLinks', 'machineComponentLinks']) ?? resolveLinkArray(container, ['componentLinks', 'machineComponentLinks']) const pieceLinksData = resolveLinkArray(source, ['pieceLinks', 'machinePieceLinks']) ?? resolveLinkArray(container, ['pieceLinks', 'machinePieceLinks']) const productLinksData = resolveLinkArray(source, ['productLinks', 'machineProductLinks']) ?? resolveLinkArray(container, ['productLinks', 'machineProductLinks']) if (componentLinksData === null && pieceLinksData === null && productLinksData === null) { return false } const normalizedComponentLinks = (componentLinksData ?? []) as AnyRecord[] const normalizedPieceLinks = (pieceLinksData ?? []) as AnyRecord[] const normalizedProductLinks = (productLinksData ?? []) as AnyRecord[] machineComponentLinks.value = normalizedComponentLinks machinePieceLinks.value = normalizedPieceLinks machineProductLinks.value = normalizedProductLinks const { components: hierarchy, machinePieces: machineLevelPieces } = buildMachineHierarchyFromLinks( normalizedComponentLinks, normalizedPieceLinks, findProductById as any, constructeurs.value as any, ) components.value = transformComponentCustomFields(hierarchy as AnyRecord[]) pieces.value = transformCustomFields(machineLevelPieces as AnyRecord[]) return true } // --------------------------------------------------------------------------- // Computed // --------------------------------------------------------------------------- const flattenedComponents = computed(() => flattenComponents(components.value)) const machinePieces = computed(() => { return pieces.value.filter((piece) => { const parentLinkId = resolveIdentifier( piece.parentComponentLinkId, (piece.machinePieceLink as AnyRecord)?.parentComponentLinkId, piece.parentLinkId, ) if (parentLinkId) return false return !piece.composantId }) }) // --------------------------------------------------------------------------- // Structure reload // --------------------------------------------------------------------------- const reloadMachineStructure = async () => { const result: any = await get(`/machines/${machineId}/structure`) if (result.success) { 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 as AnyRecord)?.documents || [], customFieldValues: machinePayload.customFieldValues || (machine.value as AnyRecord)?.customFieldValues || [], } const linksApplied = applyMachineLinks(result.data) if (linksApplied && machine.value) { machine.value.componentLinks = machineComponentLinks.value machine.value.pieceLinks = machinePieceLinks.value machine.value.productLinks = machineProductLinks.value } syncMachineCustomFields() } } } // --------------------------------------------------------------------------- // Structure link CRUD // --------------------------------------------------------------------------- const addComponentLink = async (composantId: string) => { const result: any = await apiPost('/machine_component_links', { machine: `/api/machines/${machineId}`, composant: `/api/composants/${composantId}`, }) if (result.success) { toast.showSuccess('Composant ajouté à la machine') await reloadMachineStructure() } else { toast.showError('Erreur lors de l\'ajout du composant') } return result } const removeComponentLink = async (linkId: string) => { const result: any = await apiDel(`/machine_component_links/${linkId}`) if (result.success) { toast.showSuccess('Composant retiré de la machine') await reloadMachineStructure() } else { toast.showError('Erreur lors de la suppression du composant') } return result } const addPieceLink = async (pieceId: string, parentComponentLinkId?: string) => { const payload: any = { machine: `/api/machines/${machineId}`, piece: `/api/pieces/${pieceId}`, } if (parentComponentLinkId) { payload.parentLink = `/api/machine_component_links/${parentComponentLinkId}` } const result: any = await apiPost('/machine_piece_links', payload) if (result.success) { toast.showSuccess('Pièce ajoutée à la machine') await reloadMachineStructure() } else { toast.showError('Erreur lors de l\'ajout de la pièce') } return result } const removePieceLink = async (linkId: string) => { const result: any = await apiDel(`/machine_piece_links/${linkId}`) if (result.success) { toast.showSuccess('Pièce retirée de la machine') await reloadMachineStructure() } else { toast.showError('Erreur lors de la suppression de la pièce') } return result } const addProductLink = async (productId: string, parentComponentLinkId?: string, parentPieceLinkId?: string) => { const payload: any = { machine: `/api/machines/${machineId}`, product: `/api/products/${productId}`, } if (parentComponentLinkId) { payload.parentComponentLink = `/api/machine_component_links/${parentComponentLinkId}` } if (parentPieceLinkId) { payload.parentPieceLink = `/api/machine_piece_links/${parentPieceLinkId}` } const result: any = await apiPost('/machine_product_links', payload) if (result.success) { toast.showSuccess('Produit ajouté à la machine') await reloadMachineStructure() } else { toast.showError('Erreur lors de l\'ajout du produit') } return result } const removeProductLink = async (linkId: string) => { const result: any = await apiDel(`/machine_product_links/${linkId}`) if (result.success) { toast.showSuccess('Produit retiré de la machine') await reloadMachineStructure() } else { toast.showError('Erreur lors de la suppression du produit') } return result } return { // State components, pieces, machineComponentLinks, machinePieceLinks, machineProductLinks, // Computed flattenedComponents, machinePieces, // Helpers flattenComponents, findComponentById, findPieceById, // Hierarchy applyMachineLinks, // Structure link management reloadMachineStructure, addComponentLink, removeComponentLink, addPieceLink, removePieceLink, addProductLink, removeProductLink, } }