Files
Inventory/app/composables/useMachines.ts
Matthieu 67af3c9c46 feat: add API optimizations, cache invalidation and comprehensive test suite
- Add abort controllers and request deduplication to composables
- Add entity type cache invalidation on create/update/delete flows
- Add 179 new tests (utilities, services, composables, components)
- Fix Vue runtime warnings in structure editors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 14:19:08 +01:00

268 lines
8.7 KiB
TypeScript

import { ref } from 'vue'
import { useToast } from './useToast'
import { useApi, type ApiResponse } from './useApi'
import { buildConstructeurRequestPayload } from '~/shared/constructeurUtils'
import { extractRelationId, normalizeRelationIds } from '~/shared/apiRelations'
import { extractCollection } from '~/shared/utils/apiHelpers'
export interface Machine {
id: string
name?: string
siteId?: string | null
typeMachineId?: string | null
componentLinks?: unknown[]
pieceLinks?: unknown[]
[key: string]: unknown
}
const machines = ref<Machine[]>([])
const loading = ref(false)
const loaded = ref(false)
const resolveLinkCollection = (source: Record<string, unknown>, keys: string[]): unknown[] | undefined => {
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: unknown): Machine | null => {
if (!payload || typeof payload !== 'object') {
return null
}
const raw = payload as Record<string, unknown>
const container = raw.machine && typeof raw.machine === 'object'
? raw.machine as Record<string, unknown>
: raw
const normalized: Record<string, unknown> = { ...container }
if (!normalized.siteId) {
const siteId = extractRelationId(container.site)
if (siteId) {
normalized.siteId = siteId
}
}
if (!normalized.typeMachineId) {
const typeMachineId = extractRelationId(container.typeMachine)
if (typeMachineId) {
normalized.typeMachineId = typeMachineId
}
}
const componentLinks = resolveLinkCollection(raw, ['componentLinks', 'machineComponentLinks']) ??
resolveLinkCollection(container, ['componentLinks', 'machineComponentLinks']) ??
[]
const pieceLinks = resolveLinkCollection(raw, ['pieceLinks', 'machinePieceLinks']) ??
resolveLinkCollection(container, ['pieceLinks', 'machinePieceLinks']) ??
[]
normalized.componentLinks = componentLinks
normalized.pieceLinks = pieceLinks
return normalized as Machine
}
export function useMachines() {
const { showSuccess, showError } = useToast()
const { get, post, patch, delete: del } = useApi()
const loadMachines = async (options: { force?: boolean } = {}): Promise<void> => {
if (!options.force && loaded.value) return
loading.value = true
try {
const result = await get('/machines')
if (result.success) {
const machineList = extractCollection(result.data)
const normalized = machineList
.map((item) => normalizeMachineResponse(item))
.filter((item): item is Machine => item !== null)
machines.value = normalized
loaded.value = true
}
} catch (error) {
console.error('Erreur lors du chargement des machines:', error)
} finally {
loading.value = false
}
}
const createMachine = async (machineData: Partial<Machine>): Promise<ApiResponse> => {
loading.value = true
try {
const normalizedPayload = normalizeRelationIds(buildConstructeurRequestPayload(machineData))
const result = await post('/machines', normalizedPayload)
if (result.success) {
const createdMachine = normalizeMachineResponse(result.data) ||
normalizeMachineResponse((result.data as Record<string, unknown>)?.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) {
console.error('Erreur lors de la création de la machine:', error)
return { success: false, error: (error as Error).message }
} finally {
loading.value = false
}
}
const createMachineFromType = async (machineData: Partial<Machine>, typeMachine: { id: string }): Promise<ApiResponse> => {
const machineWithStructure = {
...machineData,
typeMachineId: typeMachine.id,
}
return await createMachine(machineWithStructure)
}
const updateMachineData = async (id: string, machineData: Partial<Machine>): Promise<ApiResponse> => {
loading.value = true
try {
const normalizedPayload = normalizeRelationIds(buildConstructeurRequestPayload(machineData))
const result = await patch(`/machines/${id}`, normalizedPayload)
if (result.success) {
const updatedMachine = normalizeMachineResponse(result.data) ||
normalizeMachineResponse((result.data as Record<string, unknown>)?.machine) ||
null
const index = machines.value.findIndex((machine) => machine.id === id)
if (index !== -1 && updatedMachine) {
machines.value[index] = {
...machines.value[index],
...updatedMachine,
}
}
showSuccess(`Machine "${updatedMachine?.name || machineData.name || ''}" mise à jour avec succès`)
}
return result
} catch (error) {
console.error('Erreur lors de la mise à jour de la machine:', error)
return { success: false, error: (error as Error).message }
} finally {
loading.value = false
}
}
const reconfigureSkeleton = async (machineId: string, payload: unknown): Promise<ApiResponse> => {
if (!machineId) {
return { success: false, error: 'Identifiant de machine manquant' }
}
loading.value = true
try {
const result = await patch(`/machines/${machineId}/skeleton`, payload)
if (result.success) {
const index = machines.value.findIndex((machine) => machine.id === machineId)
if (index !== -1) {
const updatedMachine = normalizeMachineResponse(result.data) ||
normalizeMachineResponse((result.data as Record<string, unknown>)?.machine) ||
machines.value[index]
machines.value[index] = {
...machines.value[index],
...updatedMachine,
} as Machine
}
showSuccess('Structure de la machine mise à jour avec succès')
}
return result
} catch (error) {
console.error('Erreur lors de la reconfiguration du squelette de la machine:', error)
return { success: false, error: (error as Error).message }
} finally {
loading.value = false
}
}
const addMissingCustomFields = async (machineId: string, { showToast: shouldShowToast = true } = {}): Promise<ApiResponse> => {
if (!machineId) {
const error = 'Identifiant de machine manquant'
if (shouldShowToast) {
showError(error)
}
return { success: false, error }
}
try {
const result = await post(`/machines/${machineId}/add-custom-fields`)
if (result.success) {
if (shouldShowToast) {
showSuccess('Champs personnalisés complétés avec succès')
}
} else if (shouldShowToast && result.error) {
showError(result.error)
}
return result
} catch (error) {
console.error('Erreur lors de l\'ajout des champs personnalisés manquants:', error)
if (shouldShowToast) {
showError('Erreur lors de la complétion des champs personnalisés')
}
return { success: false, error: (error as Error).message }
}
}
const deleteMachine = async (id: string): Promise<ApiResponse> => {
loading.value = true
try {
const result = await del(`/machines/${id}`)
if (result.success) {
const deletedMachine = machines.value.find((machine) => machine.id === id)
machines.value = machines.value.filter((machine) => machine.id !== id)
showSuccess(`Machine "${deletedMachine?.name || 'inconnu'}" supprimée avec succès`)
}
return result
} catch (error) {
console.error('Erreur lors de la suppression de la machine:', error)
return { success: false, error: (error as Error).message }
} finally {
loading.value = false
}
}
const getMachineById = (id: string): Machine | undefined => {
return machines.value.find((machine) => machine.id === id)
}
const getMachinesBySite = (siteId: string): Machine[] => {
return machines.value.filter((machine) => machine.siteId === siteId)
}
const getMachinesByType = (typeMachineId: string): Machine[] => {
return machines.value.filter((machine) => machine.typeMachineId === typeMachineId)
}
const getMachines = (): Machine[] => machines.value
const isLoading = (): boolean => loading.value
return {
machines,
loading,
loadMachines,
createMachine,
createMachineFromType,
updateMachine: updateMachineData,
reconfigureSkeleton,
deleteMachine,
getMachineById,
getMachinesBySite,
getMachinesByType,
getMachines,
isLoading,
addMissingCustomFields,
}
}