wip: machine create skeleton links
This commit is contained in:
@@ -400,6 +400,7 @@ import DocumentUpload from '~/components/DocumentUpload.vue'
|
||||
import DocumentPreviewModal from '~/components/DocumentPreviewModal.vue'
|
||||
import { useComponentTypes } from '~/composables/useComponentTypes'
|
||||
import { useComposants } from '~/composables/useComposants'
|
||||
import { usePieceTypes } from '~/composables/usePieceTypes'
|
||||
import { useCustomFields } from '~/composables/useCustomFields'
|
||||
import { useApi } from '~/composables/useApi'
|
||||
import { useToast } from '~/composables/useToast'
|
||||
@@ -434,6 +435,7 @@ const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { get } = useApi()
|
||||
const { componentTypes, loadComponentTypes } = useComponentTypes()
|
||||
const { pieceTypes, loadPieceTypes } = usePieceTypes()
|
||||
const { updateComposant } = useComposants()
|
||||
const { ensureConstructeurs } = useConstructeurs()
|
||||
const { upsertCustomFieldValue, updateCustomFieldValue, getCustomFieldValuesByEntity } = useCustomFields()
|
||||
@@ -500,6 +502,16 @@ const documentPreviewSrc = (document: any) => {
|
||||
}
|
||||
return document.path
|
||||
}
|
||||
|
||||
const fetchedPieceTypeMap = ref<Record<string, string>>({})
|
||||
const pieceTypeLabelMap = computed(() => ({
|
||||
...Object.fromEntries(
|
||||
(pieceTypes.value || [])
|
||||
.filter((type: any) => type?.id)
|
||||
.map((type: any) => [type.id, type.name || type.code || '']),
|
||||
),
|
||||
...fetchedPieceTypeMap.value,
|
||||
}))
|
||||
const documentThumbnailClass = (document: any) => {
|
||||
if (shouldInlinePdf(document) || (isImageDocument(document) && document?.path)) {
|
||||
return 'h-24 w-20'
|
||||
@@ -1023,6 +1035,8 @@ const resolvePieceLabel = (piece: Record<string, any>) => {
|
||||
parts.push(piece.typePiece.name)
|
||||
} else if (piece.typePieceLabel) {
|
||||
parts.push(piece.typePieceLabel)
|
||||
} else if (piece.typePieceId && pieceTypeLabelMap.value[piece.typePieceId]) {
|
||||
parts.push(pieceTypeLabelMap.value[piece.typePieceId])
|
||||
} else if (piece.typePiece?.code) {
|
||||
parts.push(`Famille ${piece.typePiece.code}`)
|
||||
} else if (piece.familyCode) {
|
||||
@@ -1033,6 +1047,42 @@ const resolvePieceLabel = (piece: Record<string, any>) => {
|
||||
return parts.length ? parts.join(' • ') : 'Pièce'
|
||||
}
|
||||
|
||||
const fetchPieceTypeNames = async (ids: string[]) => {
|
||||
const missing = ids.filter((id) => id && !pieceTypeLabelMap.value[id])
|
||||
if (!missing.length) {
|
||||
return
|
||||
}
|
||||
const results = await Promise.allSettled(
|
||||
missing.map((id) => get(`/model_types/${id}`)),
|
||||
)
|
||||
const next = { ...fetchedPieceTypeMap.value }
|
||||
results.forEach((result, index) => {
|
||||
if (result.status !== 'fulfilled') {
|
||||
return
|
||||
}
|
||||
const data = result.value?.data
|
||||
const name = data?.name || data?.code
|
||||
if (name) {
|
||||
next[missing[index]] = name
|
||||
}
|
||||
})
|
||||
fetchedPieceTypeMap.value = next
|
||||
}
|
||||
|
||||
watch(
|
||||
selectedTypeStructure,
|
||||
(structure) => {
|
||||
const ids = getStructurePieces(structure)
|
||||
.map((piece: any) => piece?.typePieceId)
|
||||
.filter((id: any): id is string => typeof id === 'string' && id.trim().length > 0)
|
||||
if (!ids.length) {
|
||||
return
|
||||
}
|
||||
fetchPieceTypeNames(Array.from(new Set(ids))).catch(() => {})
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const resolveSubcomponentLabel = (node: Record<string, any>) => {
|
||||
const parts: string[] = []
|
||||
if (node.alias) {
|
||||
@@ -1158,7 +1208,7 @@ const saveCustomFieldValues = async (updatedComponent: any) => {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.allSettled([loadComponentTypes(), fetchComponent()])
|
||||
await Promise.allSettled([loadComponentTypes(), loadPieceTypes(), fetchComponent()])
|
||||
loading.value = false
|
||||
if (component.value?.id) {
|
||||
await refreshDocuments()
|
||||
|
||||
@@ -355,6 +355,7 @@ import { usePieces } from '~/composables/usePieces'
|
||||
import { usePieceTypes } from '~/composables/usePieceTypes'
|
||||
import { useProducts } from '~/composables/useProducts'
|
||||
import { useProductTypes } from '~/composables/useProductTypes'
|
||||
import { useApi } from '~/composables/useApi'
|
||||
import { useToast } from '~/composables/useToast'
|
||||
import { useCustomFields } from '~/composables/useCustomFields'
|
||||
import { useDocuments } from '~/composables/useDocuments'
|
||||
@@ -375,6 +376,7 @@ interface ComponentCatalogType extends ModelType {
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { get } = useApi()
|
||||
|
||||
const { componentTypes, loadComponentTypes, loadingComponentTypes } = useComponentTypes()
|
||||
const { pieceTypes, loadPieceTypes } = usePieceTypes()
|
||||
@@ -418,13 +420,15 @@ const structureDataLoading = computed(
|
||||
() => piecesLoading.value || componentsLoading.value || productsLoading.value,
|
||||
)
|
||||
|
||||
const pieceTypeLabelMap = computed(() =>
|
||||
Object.fromEntries(
|
||||
const fetchedPieceTypeMap = ref<Record<string, string>>({})
|
||||
const pieceTypeLabelMap = computed(() => ({
|
||||
...Object.fromEntries(
|
||||
(pieceTypes.value || [])
|
||||
.filter((type: any) => type?.id)
|
||||
.map((type: any) => [type.id, type.name || type.code || '']),
|
||||
),
|
||||
)
|
||||
...fetchedPieceTypeMap.value,
|
||||
}))
|
||||
const productTypeLabelMap = computed(() =>
|
||||
Object.fromEntries(
|
||||
(productTypes.value || [])
|
||||
@@ -804,6 +808,8 @@ const resolvePieceLabel = (piece: Record<string, any>) => {
|
||||
parts.push(piece.typePiece.name)
|
||||
} else if (piece.typePieceLabel) {
|
||||
parts.push(piece.typePieceLabel)
|
||||
} else if (piece.typePieceId && pieceTypeLabelMap.value[piece.typePieceId]) {
|
||||
parts.push(pieceTypeLabelMap.value[piece.typePieceId])
|
||||
} else if (piece.typePiece?.code) {
|
||||
parts.push(`Famille ${piece.typePiece.code}`)
|
||||
} else if (piece.familyCode) {
|
||||
@@ -814,6 +820,42 @@ const resolvePieceLabel = (piece: Record<string, any>) => {
|
||||
return parts.length ? parts.join(' • ') : 'Pièce'
|
||||
}
|
||||
|
||||
const fetchPieceTypeNames = async (ids: string[]) => {
|
||||
const missing = ids.filter((id) => id && !pieceTypeLabelMap.value[id])
|
||||
if (!missing.length) {
|
||||
return
|
||||
}
|
||||
const results = await Promise.allSettled(
|
||||
missing.map((id) => get(`/model_types/${id}`)),
|
||||
)
|
||||
const next = { ...fetchedPieceTypeMap.value }
|
||||
results.forEach((result, index) => {
|
||||
if (result.status !== 'fulfilled') {
|
||||
return
|
||||
}
|
||||
const data = result.value?.data
|
||||
const name = data?.name || data?.code
|
||||
if (name) {
|
||||
next[missing[index]] = name
|
||||
}
|
||||
})
|
||||
fetchedPieceTypeMap.value = next
|
||||
}
|
||||
|
||||
watch(
|
||||
selectedTypeStructure,
|
||||
(structure) => {
|
||||
const ids = getStructurePieces(structure)
|
||||
.map((piece: any) => piece?.typePieceId)
|
||||
.filter((id: any): id is string => typeof id === 'string' && id.trim().length > 0)
|
||||
if (!ids.length) {
|
||||
return
|
||||
}
|
||||
fetchPieceTypeNames(Array.from(new Set(ids))).catch(() => {})
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
const resolveProductLabel = (product: Record<string, any>) => {
|
||||
const parts: string[] = []
|
||||
if (product.role) {
|
||||
|
||||
@@ -273,18 +273,19 @@
|
||||
</label>
|
||||
<SearchSelect
|
||||
:model-value="entry.pieceId || ''"
|
||||
:options="getPieceOptions(requirement, entry)"
|
||||
:loading="piecesLoading"
|
||||
:options="getPieceOptions(requirement, entry, entryIndex)"
|
||||
:loading="piecesLoading || pieceLoadingByKey[getPieceKey(requirement, entryIndex)]"
|
||||
size="sm"
|
||||
placeholder="Rechercher une pièce…"
|
||||
empty-text="Aucune pièce disponible"
|
||||
:option-label="pieceOptionLabel"
|
||||
:option-description="pieceOptionDescription"
|
||||
@search="(term) => fetchPieceOptions(requirement, entryIndex, term)"
|
||||
@update:modelValue="setPieceRequirementPiece(requirement, entryIndex, $event || '')"
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
v-if="getPieceOptions(requirement, entry).length === 0"
|
||||
v-if="getPieceOptions(requirement, entry, entryIndex).length === 0"
|
||||
class="text-xs text-error"
|
||||
>
|
||||
Aucune pièce disponible pour cette famille.
|
||||
@@ -743,6 +744,7 @@ 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 { sanitizeDefinitionOverrides } from '~/shared/modelUtils'
|
||||
import SearchSelect from '~/components/common/SearchSelect.vue'
|
||||
@@ -754,12 +756,13 @@ import IconLucideAlertTriangle from '~icons/lucide/alert-triangle'
|
||||
import IconLucideCheckCircle2 from '~icons/lucide/check-circle-2'
|
||||
import IconLucideCircle from '~icons/lucide/circle'
|
||||
|
||||
const { createMachine, createMachineFromType } = useMachines()
|
||||
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()
|
||||
|
||||
const submitting = ref(false)
|
||||
@@ -842,6 +845,85 @@ const productById = computed(() => {
|
||||
return map
|
||||
})
|
||||
|
||||
const pieceOptionsByKey = ref({})
|
||||
const pieceLoadingByKey = ref({})
|
||||
|
||||
const extractCollection = (payload) => {
|
||||
if (Array.isArray(payload)) {
|
||||
return payload
|
||||
}
|
||||
if (Array.isArray(payload?.member)) {
|
||||
return payload.member
|
||||
}
|
||||
if (Array.isArray(payload?.['hydra:member'])) {
|
||||
return payload['hydra:member']
|
||||
}
|
||||
if (Array.isArray(payload?.data)) {
|
||||
return payload.data
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
const getPieceKey = (requirement, entryIndex) => `${requirement?.id || 'req'}:${entryIndex}`
|
||||
|
||||
const findPieceInCachedOptions = (id) => {
|
||||
if (!id) {
|
||||
return null
|
||||
}
|
||||
const buckets = Object.values(pieceOptionsByKey.value || {})
|
||||
for (const bucket of buckets) {
|
||||
if (!Array.isArray(bucket)) {
|
||||
continue
|
||||
}
|
||||
const found = bucket.find((piece) => piece?.id === id)
|
||||
if (found) {
|
||||
return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const cachePieceIfMissing = (piece) => {
|
||||
if (!piece?.id) {
|
||||
return
|
||||
}
|
||||
if (pieceById.value.has(piece.id)) {
|
||||
return
|
||||
}
|
||||
const current = Array.isArray(pieces.value) ? pieces.value : []
|
||||
pieces.value = [...current, piece]
|
||||
}
|
||||
|
||||
const fetchPieceOptions = async (requirement, entryIndex, term = '') => {
|
||||
const key = getPieceKey(requirement, entryIndex)
|
||||
if (pieceLoadingByKey.value[key]) {
|
||||
return
|
||||
}
|
||||
|
||||
const requirementTypeId = requirement?.typePieceId || requirement?.typePiece?.id || null
|
||||
const params = new URLSearchParams()
|
||||
params.set('itemsPerPage', '50')
|
||||
if (term && term.trim()) {
|
||||
params.set('name', term.trim())
|
||||
}
|
||||
if (requirementTypeId) {
|
||||
params.set('typePiece', `/api/model_types/${requirementTypeId}`)
|
||||
}
|
||||
|
||||
pieceLoadingByKey.value = { ...pieceLoadingByKey.value, [key]: true }
|
||||
try {
|
||||
const result = await get(`/pieces?${params.toString()}`)
|
||||
if (result.success) {
|
||||
pieceOptionsByKey.value = {
|
||||
...pieceOptionsByKey.value,
|
||||
[key]: extractCollection(result.data)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
pieceLoadingByKey.value = { ...pieceLoadingByKey.value, [key]: false }
|
||||
}
|
||||
}
|
||||
|
||||
const isPlainObject = value => value !== null && typeof value === 'object' && !Array.isArray(value)
|
||||
|
||||
const toTrimmedString = (value) => {
|
||||
@@ -1077,7 +1159,12 @@ const getComponentOptions = (requirement, currentEntry) => {
|
||||
})
|
||||
}
|
||||
|
||||
const getPieceOptions = (requirement, currentEntry) => {
|
||||
const getPieceOptions = (requirement, currentEntry, entryIndex) => {
|
||||
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) => id && (!currentEntry || id !== currentEntry.pieceId)),
|
||||
@@ -1241,8 +1328,11 @@ const setPieceRequirementPiece = (requirement, index, pieceId) => {
|
||||
if (!entry) return
|
||||
entry.pieceId = pieceId || null
|
||||
if (pieceId) {
|
||||
const piece = findPieceById(pieceId)
|
||||
const piece = findPieceById(pieceId) || findPieceInCachedOptions(pieceId)
|
||||
entry.typePieceId = piece?.typePieceId || requirement?.typePieceId || null
|
||||
if (piece) {
|
||||
cachePieceIfMissing(piece)
|
||||
}
|
||||
} else {
|
||||
entry.typePieceId = requirement?.typePieceId || null
|
||||
}
|
||||
@@ -1259,7 +1349,7 @@ const findPieceById = (id) => {
|
||||
if (!id) {
|
||||
return null
|
||||
}
|
||||
return pieceById.value.get(id) || null
|
||||
return pieceById.value.get(id) || findPieceInCachedOptions(id) || null
|
||||
}
|
||||
|
||||
const findProductById = (id) => {
|
||||
@@ -1519,6 +1609,7 @@ const addPieceSelectionEntry = (requirement) => {
|
||||
...entries,
|
||||
createPieceSelectionEntry(requirement),
|
||||
]
|
||||
fetchPieceOptions(requirement, entries.length).catch(() => {})
|
||||
}
|
||||
|
||||
const removePieceSelectionEntry = (requirementId, index) => {
|
||||
@@ -2096,6 +2187,9 @@ const initializeRequirementSelections = (type) => {
|
||||
const initialCount = Math.max(min, requirement.required ? 1 : 0)
|
||||
if (initialCount > 0) {
|
||||
pieceRequirementSelections[requirement.id] = Array.from({ length: initialCount }, () => createPieceSelectionEntry(requirement))
|
||||
pieceRequirementSelections[requirement.id].forEach((_, index) => {
|
||||
fetchPieceOptions(requirement, index).catch(() => {})
|
||||
})
|
||||
} else {
|
||||
pieceRequirementSelections[requirement.id] = []
|
||||
}
|
||||
@@ -2158,22 +2252,22 @@ const finalizeMachineCreation = async () => {
|
||||
productLinks = validationResult.productLinks
|
||||
}
|
||||
|
||||
const payload = {
|
||||
...baseMachineData,
|
||||
...(hasRequirements
|
||||
? {
|
||||
componentLinks,
|
||||
pieceLinks,
|
||||
productLinks
|
||||
}
|
||||
: {})
|
||||
}
|
||||
|
||||
const result = hasRequirements
|
||||
? await createMachine(payload)
|
||||
? await createMachine(baseMachineData)
|
||||
: await createMachineFromType(baseMachineData, type)
|
||||
|
||||
if (result.success) {
|
||||
if (hasRequirements && result.data?.id) {
|
||||
const skeletonResult = await reconfigureSkeleton(result.data.id, {
|
||||
componentLinks,
|
||||
pieceLinks,
|
||||
productLinks,
|
||||
})
|
||||
if (!skeletonResult.success) {
|
||||
toast.showError(skeletonResult.error || 'Impossible d\'enregistrer les pièces/composants')
|
||||
return
|
||||
}
|
||||
}
|
||||
newMachine.name = ''
|
||||
newMachine.siteId = ''
|
||||
newMachine.typeMachineId = ''
|
||||
|
||||
Reference in New Issue
Block a user