Files
Inventory_frontend/app/composables/useMachineCreatePage.ts
Matthieu e1594cab76 refactor(machine): decompose create page into composable + 5 components (F1.2)
Extract useMachineCreatePage composable and 5 preview/selector components
from machines/new.vue, reducing it from 1231 to 196 LOC.

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

459 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Machine creation page orchestration composable.
*
* Consolidates entity lookup maps, option filters, label helpers,
* template wrappers, and the finalization logic that were previously
* inlined in pages/machines/new.vue.
*/
import { ref, reactive, computed, watch, onMounted } from 'vue'
import { useMachines } from '~/composables/useMachines'
import { useSites } from '~/composables/useSites'
import { useMachineTypesApi } from '~/composables/useMachineTypesApi'
import { useComposants } from '~/composables/useComposants'
import { usePieces } from '~/composables/usePieces'
import { useProducts } from '~/composables/useProducts'
import { useApi } from '~/composables/useApi'
import { useToast } from '~/composables/useToast'
import { useMachineCreateSelections } from '~/composables/useMachineCreateSelections'
import {
useMachineCreatePreview,
validateRequirementSelections as _validateRequirementSelections,
resolveComponentRequirementTypeLabel as _resolveComponentRequirementTypeLabel,
resolvePieceRequirementTypeLabel as _resolvePieceRequirementTypeLabel,
} from '~/composables/useMachineCreatePreview'
import {
getComponentMachineAssignments,
getPieceMachineAssignments,
getPieceComponentAssignments,
formatAssignmentList,
} from '~/shared/utils/assignmentUtils'
export function useMachineCreatePage() {
// ---------------------------------------------------------------------------
// Composable calls
// ---------------------------------------------------------------------------
const { createMachine, createMachineFromType, reconfigureSkeleton } = useMachines()
const { sites, loadSites } = useSites()
const { machineTypes, loadMachineTypes, loading: machineTypesLoading } = useMachineTypesApi()
const { composants, loadComposants, loading: composantsLoading } = useComposants()
const { pieces, loadPieces, loading: piecesLoading } = usePieces()
const { products, loadProducts, loading: productsLoading } = useProducts()
const { get } = useApi()
const toast = useToast()
// ---------------------------------------------------------------------------
// Local state
// ---------------------------------------------------------------------------
const submitting = ref(false)
const newMachine = reactive({
name: '',
siteId: '',
typeMachineId: '',
reference: '',
})
const selectedMachineType = computed(() => {
if (!newMachine.typeMachineId) return null
return (machineTypes as any).value.find((type: any) => type.id === newMachine.typeMachineId) || null
})
// ---------------------------------------------------------------------------
// Entity lookup maps
// ---------------------------------------------------------------------------
const componentById = computed(() => {
const map = new Map()
;((composants as any).value || []).forEach((component: any) => {
if (component?.id) map.set(component.id, component)
})
return map
})
const pieceById = computed(() => {
const map = new Map()
;((pieces as any).value || []).forEach((piece: any) => {
if (piece?.id) map.set(piece.id, piece)
})
return map
})
const componentInventory = computed(() => (composants as any).value || [])
const pieceInventory = computed(() => (pieces as any).value || [])
const productInventory = computed(() => (products as any).value || [])
const productById = computed(() => {
const map = new Map()
;(productInventory.value || []).forEach((product: any) => {
if (product?.id) map.set(product.id, product)
})
return map
})
// ---------------------------------------------------------------------------
// Entity finders
// ---------------------------------------------------------------------------
const findComponentById = (id: string) => {
if (!id) return null
return componentById.value.get(id) || null
}
const findPieceById = (id: string): any => {
if (!id) return null
return pieceById.value.get(id) || findPieceInCachedOptions(id) || null
}
const findProductById = (id: string) => {
if (!id) return null
return productById.value.get(id) || null
}
// ---------------------------------------------------------------------------
// Selection state (from composable)
// ---------------------------------------------------------------------------
const {
pieceOptionsByKey,
pieceLoadingByKey,
selectedPieceIds,
getPieceKey,
findPieceInCachedOptions,
fetchPieceOptions,
getComponentRequirementEntries,
getPieceRequirementEntries,
getProductRequirementEntries,
addComponentSelectionEntry,
removeComponentSelectionEntry,
addPieceSelectionEntry,
removePieceSelectionEntry,
addProductSelectionEntry,
removeProductSelectionEntry,
setComponentRequirementComponent,
setPieceRequirementPiece,
setProductRequirementProduct: _setProductRequirementProduct,
clearRequirementSelections,
initializeRequirementSelections,
} = useMachineCreateSelections({
findComponentById,
findPieceById,
pieces: pieces as any,
get: get as any,
toast,
})
// ---------------------------------------------------------------------------
// Preview / validation (from composable)
// ---------------------------------------------------------------------------
const { machinePreview, blockingPreviewIssues, canCreateMachine } = useMachineCreatePreview({
newMachine,
sites: sites as any,
selectedMachineType,
findComponentById,
findPieceById,
findProductById,
getComponentRequirementEntries,
getPieceRequirementEntries,
getProductRequirementEntries,
})
// ---------------------------------------------------------------------------
// Template wrappers
// ---------------------------------------------------------------------------
const resolveComponentRequirementTypeLabel = (requirement: any, entry: any) =>
_resolveComponentRequirementTypeLabel(requirement, entry, findComponentById)
const resolvePieceRequirementTypeLabel = (requirement: any, entry: any) =>
_resolvePieceRequirementTypeLabel(requirement, entry, findPieceById)
const setProductRequirementProduct = (requirement: any, index: number, productId: string) =>
_setProductRequirementProduct(requirement, index, productId, findProductById)
const validateRequirementSelections = (type: any) =>
_validateRequirementSelections(type, {
newMachine,
sites: sites as any,
selectedMachineType,
findComponentById,
findPieceById,
findProductById,
getComponentRequirementEntries,
getPieceRequirementEntries,
getProductRequirementEntries,
})
// ---------------------------------------------------------------------------
// Machine type helpers
// ---------------------------------------------------------------------------
const machineTypeLabel = (type: any) => {
if (!type) return ''
return type.name || 'Type de machine'
}
const machineTypeDescription = (type: any) => {
if (!type) return ''
const parts: string[] = []
if (type.category) parts.push(`Catégorie : ${type.category}`)
const componentCount = type.componentRequirements?.length ?? 0
const pieceCount = type.pieceRequirements?.length ?? 0
const productCount = type.productRequirements?.length ?? 0
parts.push(
`${componentCount} composant(s)`,
`${pieceCount} pièce(s)`,
`${productCount} produit(s)`,
)
return parts.join(' • ')
}
// ---------------------------------------------------------------------------
// Option filters
// ---------------------------------------------------------------------------
const getComponentOptions = (requirement: any, currentEntry: any) => {
const requirementTypeId = requirement?.typeComposantId || requirement?.typeComposant?.id || null
return componentInventory.value.filter((component: any) => {
if (!component?.id) return false
if (requirementTypeId && component.typeComposantId !== requirementTypeId) {
return currentEntry?.composantId === component.id
}
return true
})
}
const getPieceOptions = (requirement: any, currentEntry: any, entryIndex: number) => {
const key = getPieceKey(requirement, entryIndex)
const cached = pieceOptionsByKey.value[key]
if (cached) return cached
const requirementTypeId = requirement?.typePieceId || requirement?.typePiece?.id || null
const usedIds = new Set(
selectedPieceIds.value.filter((id: any) => id && (!currentEntry || id !== currentEntry.pieceId)),
)
return pieceInventory.value.filter((piece: any) => {
if (requirementTypeId && piece.typePieceId !== requirementTypeId) return false
if (!piece.id) return false
if (currentEntry?.pieceId === piece.id) return true
return !usedIds.has(piece.id)
})
}
const getProductOptions = (requirement: any) => {
const requirementTypeId = requirement?.typeProductId || requirement?.typeProduct?.id || null
return productInventory.value.filter((product: any) => {
if (!product?.id) return false
if (!requirementTypeId) return true
const productTypeId = product.typeProductId || product.typeProduct?.id || null
return productTypeId === requirementTypeId
})
}
// ---------------------------------------------------------------------------
// Option label / description helpers
// ---------------------------------------------------------------------------
const componentOptionLabel = (component: any) => component?.name || 'Composant'
const componentOptionDescription = (component: any) => {
if (!component) return ''
const parts: string[] = []
if (component.reference) parts.push(`Réf. ${component.reference}`)
const constructeurName = component.constructeur?.name || component.constructeurName
if (constructeurName) parts.push(constructeurName)
const machineAssignments = getComponentMachineAssignments(component)
if (machineAssignments.length) parts.push(`Machines: ${formatAssignmentList(machineAssignments)}`)
const productTypeName = component.product?.typeProduct?.name
const productLabel = component.product?.name || component.product?.reference
if (productTypeName || productLabel) parts.push(`Produit: ${productTypeName || productLabel}`)
return parts.join(' • ')
}
const pieceOptionLabel = (piece: any) => piece?.name || 'Pièce'
const pieceOptionDescription = (piece: any) => {
if (!piece) return ''
const parts: string[] = []
if (piece.reference) parts.push(`Réf. ${piece.reference}`)
const constructeurName = piece.constructeur?.name || piece.constructeurName
if (constructeurName) parts.push(constructeurName)
const machineAssignments = getPieceMachineAssignments(piece)
if (machineAssignments.length) parts.push(`Machines: ${formatAssignmentList(machineAssignments)}`)
const componentAssignments = getPieceComponentAssignments(piece)
if (componentAssignments.length) parts.push(`Composants: ${formatAssignmentList(componentAssignments)}`)
const productTypeName = piece.product?.typeProduct?.name
const productLabel = piece.product?.name || piece.product?.reference
if (productTypeName || productLabel) parts.push(`Produit: ${productTypeName || productLabel}`)
return parts.join(' • ')
}
// ---------------------------------------------------------------------------
// Machine creation
// ---------------------------------------------------------------------------
const finalizeMachineCreation = async () => {
if (submitting.value) return
const type = selectedMachineType.value
if (!type) {
toast.showError('Merci de sélectionner un type de machine')
return
}
if (!canCreateMachine.value) {
toast.showError('Compléter les informations obligatoires avant de créer la machine')
return
}
submitting.value = true
try {
const baseMachineData = {
name: newMachine.name,
siteId: newMachine.siteId,
reference: newMachine.reference,
typeMachineId: type.id,
}
const hasRequirements =
(type.componentRequirements?.length || 0) > 0 ||
(type.pieceRequirements?.length || 0) > 0 ||
(type.productRequirements?.length || 0) > 0
let componentLinks: any[] = []
let pieceLinks: any[] = []
let productLinks: any[] = []
if (hasRequirements) {
const validationResult = validateRequirementSelections(type)
if (!validationResult.valid) {
toast.showError(validationResult.error as string)
return
}
componentLinks = validationResult.componentLinks as any[]
pieceLinks = validationResult.pieceLinks as any[]
productLinks = validationResult.productLinks as any[]
}
const result: any = hasRequirements
? await createMachine(baseMachineData as any)
: await createMachineFromType(baseMachineData as any, type)
if (result.success) {
if (hasRequirements && result.data?.id) {
const skeletonResult: any = await reconfigureSkeleton(result.data.id, {
componentLinks,
pieceLinks,
productLinks,
} as any)
if (!skeletonResult.success) {
toast.showError(skeletonResult.error || 'Impossible d\'enregistrer les pièces/composants')
return
}
}
newMachine.name = ''
newMachine.siteId = ''
newMachine.typeMachineId = ''
newMachine.reference = ''
clearRequirementSelections()
await navigateTo('/machines')
} else if (result.error) {
toast.showError(`Impossible de créer la machine: ${result.error}`)
}
} catch (error: any) {
toast.showError(`Erreur lors de la création: ${error.message}`)
} finally {
submitting.value = false
}
}
// ---------------------------------------------------------------------------
// Watchers & lifecycle
// ---------------------------------------------------------------------------
watch(
() => newMachine.typeMachineId,
(typeId) => {
clearRequirementSelections()
if (!typeId) return
const type = (machineTypes as any).value.find((item: any) => item.id === typeId)
if (!type) return
initializeRequirementSelections(type)
},
)
onMounted(async () => {
await Promise.all([
loadSites(),
loadMachineTypes(),
loadComposants(),
loadPieces(),
loadProducts(),
])
})
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
return {
// State
submitting,
newMachine,
sites,
machineTypes,
machineTypesLoading,
composantsLoading,
piecesLoading,
productsLoading,
selectedMachineType,
// Selection state
pieceLoadingByKey,
getPieceKey,
fetchPieceOptions,
getComponentRequirementEntries,
getPieceRequirementEntries,
getProductRequirementEntries,
addComponentSelectionEntry,
removeComponentSelectionEntry,
addPieceSelectionEntry,
removePieceSelectionEntry,
addProductSelectionEntry,
removeProductSelectionEntry,
setComponentRequirementComponent,
setPieceRequirementPiece,
setProductRequirementProduct,
// Preview
machinePreview,
blockingPreviewIssues,
canCreateMachine,
// Entity finders
findComponentById,
findPieceById,
findProductById,
// Options
getComponentOptions,
getPieceOptions,
getProductOptions,
// Label helpers
machineTypeLabel,
machineTypeDescription,
componentOptionLabel,
componentOptionDescription,
pieceOptionLabel,
pieceOptionDescription,
// Type label resolvers
resolveComponentRequirementTypeLabel,
resolvePieceRequirementTypeLabel,
// Actions
finalizeMachineCreation,
}
}