- Remove TypeEdit*, TypeInfoDisplay, MachineSkeletonSummary, MachineCreatePreview components - Remove machine-skeleton pages and type pages - Remove useMachineTypesApi, useMachineSkeletonEditor, useMachineCreateSelections composables - Add AddEntityToMachineModal for direct entity linking - Update machine detail/create pages for direct custom fields - Fix SearchSelect, category display, and ipartial search filters Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1411 lines
48 KiB
TypeScript
1411 lines
48 KiB
TypeScript
/**
|
|
* Machine detail page — core state & business logic.
|
|
*
|
|
* Extracted from pages/machine/[id].vue (F1.1).
|
|
* Manages reactive state, data loading, transforms, updates,
|
|
* document management and custom field logic.
|
|
*/
|
|
|
|
import { ref, computed, watch } from 'vue'
|
|
import { useMachines } from '~/composables/useMachines'
|
|
import { useComposants } from '~/composables/useComposants'
|
|
import { usePieces } from '~/composables/usePieces'
|
|
import { useComponentTypes } from '~/composables/useComponentTypes'
|
|
import { usePieceTypes } from '~/composables/usePieceTypes'
|
|
import { useCustomFields } from '~/composables/useCustomFields'
|
|
import { useApi } from '~/composables/useApi'
|
|
import { useToast } from '~/composables/useToast'
|
|
import { useDocuments } from '~/composables/useDocuments'
|
|
import { useConstructeurs } from '~/composables/useConstructeurs'
|
|
import { useProducts } from '~/composables/useProducts'
|
|
import { useMachinePrint } from '~/composables/useMachinePrint'
|
|
import { getFileIcon } from '~/utils/fileIcons'
|
|
import { normalizeStructureForEditor } from '~/shared/modelUtils'
|
|
import {
|
|
resolveConstructeurs,
|
|
uniqueConstructeurIds,
|
|
formatConstructeurContact as formatConstructeurContactSummary,
|
|
} from '~/shared/constructeurUtils'
|
|
import {
|
|
formatCustomFieldValue,
|
|
shouldDisplayCustomField,
|
|
normalizeExistingCustomFieldDefinitions,
|
|
normalizeCustomFieldValueEntry,
|
|
mergeCustomFieldValuesWithDefinitions,
|
|
dedupeCustomFieldEntries,
|
|
summarizeCustomFields,
|
|
} from '~/shared/utils/customFieldUtils'
|
|
import {
|
|
resolveIdentifier,
|
|
resolveProductReference as _resolveProductReference,
|
|
getProductDisplay as _getProductDisplay,
|
|
getProductSuppliersLabel,
|
|
getProductPriceLabel,
|
|
extractParentLinkIdentifiers,
|
|
} from '~/shared/utils/productDisplayUtils'
|
|
import {
|
|
buildMachineHierarchyFromLinks,
|
|
resolveLinkArray,
|
|
} from '~/composables/useMachineHierarchy'
|
|
import { canPreviewDocument, isImageDocument, isPdfDocument } from '~/utils/documentPreview'
|
|
import {
|
|
formatSize,
|
|
shouldInlinePdf,
|
|
documentPreviewSrc,
|
|
documentThumbnailClass,
|
|
documentIcon,
|
|
downloadDocument as downloadDocumentHelper,
|
|
} from '~/shared/utils/documentDisplayUtils'
|
|
|
|
type AnyRecord = Record<string, unknown>
|
|
|
|
export function useMachineDetailData(machineId: string) {
|
|
// ---------------------------------------------------------------------------
|
|
// External composables
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const {
|
|
updateMachine: updateMachineApi,
|
|
updateStructure: updateMachineStructure,
|
|
} = useMachines()
|
|
const { updateComposant: updateComposantApi } = useComposants()
|
|
const { updatePiece: updatePieceApi } = usePieces()
|
|
const { componentTypes, loadComponentTypes } = useComponentTypes()
|
|
const { pieceTypes, loadPieceTypes } = usePieceTypes()
|
|
const { products, loadProducts } = useProducts()
|
|
const {
|
|
upsertCustomFieldValue,
|
|
updateCustomFieldValue: updateCustomFieldValueApi,
|
|
} = useCustomFields()
|
|
const { get, post: apiPost, delete: apiDel } = useApi()
|
|
const {
|
|
uploadDocuments,
|
|
deleteDocument,
|
|
loadDocumentsByMachine,
|
|
loadDocumentsByProduct,
|
|
} = useDocuments()
|
|
const toast = useToast()
|
|
const { constructeurs, loadConstructeurs } = useConstructeurs()
|
|
|
|
const {
|
|
printModalOpen,
|
|
printSelection,
|
|
ensurePrintSelectionEntries: _ensurePrintEntries,
|
|
setAllPrintSelection: _setAllPrint,
|
|
openPrintModal: _openPrintModal,
|
|
closePrintModal,
|
|
handlePrintConfirm: _handlePrintConfirm,
|
|
} = useMachinePrint()
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Core state
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const loading = ref(true)
|
|
const machine = ref<AnyRecord | null>(null)
|
|
const components = ref<AnyRecord[]>([])
|
|
const pieces = ref<AnyRecord[]>([])
|
|
const machineComponentLinks = ref<AnyRecord[]>([])
|
|
const machinePieceLinks = ref<AnyRecord[]>([])
|
|
const machineProductLinks = ref<AnyRecord[]>([])
|
|
const productDocumentsMap = ref<Map<string, AnyRecord[]>>(new Map())
|
|
const printAreaRef = ref<HTMLElement | null>(null)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Machine fields
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const machineName = ref('')
|
|
const machineReference = ref('')
|
|
const machineConstructeurIds = ref<string[]>([])
|
|
|
|
const machineConstructeurId = computed({
|
|
get: () => machineConstructeurIds.value[0] || null,
|
|
set: (value: string | null) => {
|
|
machineConstructeurIds.value = value ? [value] : []
|
|
},
|
|
})
|
|
|
|
const machineConstructeursDisplay = computed(() => {
|
|
const ids = uniqueConstructeurIds(
|
|
machineConstructeurIds.value,
|
|
(machine.value as AnyRecord)?.constructeurIds,
|
|
(machine.value as AnyRecord)?.constructeurs,
|
|
(machine.value as AnyRecord)?.constructeur,
|
|
)
|
|
return resolveConstructeurs(
|
|
ids,
|
|
Array.isArray((machine.value as AnyRecord)?.constructeurs)
|
|
? ((machine.value as AnyRecord).constructeurs as any[])
|
|
: [],
|
|
(machine.value as AnyRecord)?.constructeur
|
|
? [(machine.value as AnyRecord).constructeur as any]
|
|
: [],
|
|
constructeurs.value as any,
|
|
) as any[]
|
|
})
|
|
|
|
const machineConstructeurContact = computed(() =>
|
|
machineConstructeursDisplay.value
|
|
.map((c: any) => formatConstructeurContactSummary(c))
|
|
.filter(Boolean)
|
|
.join(' • '),
|
|
)
|
|
|
|
const hasMachineConstructeur = computed(
|
|
() => machineConstructeursDisplay.value.length > 0,
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// UI state
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const machineDocumentFiles = ref<File[]>([])
|
|
const machineDocumentsUploading = ref(false)
|
|
const machineDocumentsLoaded = ref(false)
|
|
const machineCustomFields = ref<AnyRecord[]>([])
|
|
const previewDocument = ref<AnyRecord | null>(null)
|
|
const previewVisible = ref(false)
|
|
|
|
const isEditMode = ref(false)
|
|
const debug = ref(false)
|
|
|
|
const componentsCollapsed = ref(true)
|
|
const collapseToggleToken = ref(0)
|
|
|
|
const piecesCollapsed = ref(true)
|
|
const pieceCollapseToggleToken = ref(0)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Product helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const componentTypeOptions = computed(() => componentTypes.value || [])
|
|
const pieceTypeOptions = computed(() => pieceTypes.value || [])
|
|
|
|
const componentTypeLabelMap = computed(() => {
|
|
const map = new Map<string, string>()
|
|
componentTypeOptions.value.forEach((type) => {
|
|
if (type?.id) map.set(type.id as string, (type.name as string) || '')
|
|
})
|
|
return map
|
|
})
|
|
|
|
const pieceTypeLabelMap = computed(() => {
|
|
const map = new Map<string, string>()
|
|
pieceTypeOptions.value.forEach((type) => {
|
|
if (type?.id) map.set(type.id as string, (type.name as string) || '')
|
|
})
|
|
return map
|
|
})
|
|
|
|
const productInventory = computed(() => products.value || [])
|
|
|
|
const productById = computed(() => {
|
|
const map = new Map<string, AnyRecord>()
|
|
;(productInventory.value as AnyRecord[]).forEach((product: AnyRecord) => {
|
|
if (product?.id) map.set(product.id as string, product)
|
|
})
|
|
return map
|
|
})
|
|
|
|
const findProductById = (productId: string | null | undefined): AnyRecord | null => {
|
|
if (!productId) return null
|
|
return productById.value.get(productId) || null
|
|
}
|
|
|
|
const resolveProductReference = (source: AnyRecord) =>
|
|
_resolveProductReference(source, findProductById as any)
|
|
const getProductDisplay = (source: AnyRecord) =>
|
|
_getProductDisplay(source, findProductById as any)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const isPlainObject = (value: unknown): boolean =>
|
|
Object.prototype.toString.call(value) === '[object Object]'
|
|
|
|
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)
|
|
}
|
|
|
|
const collectConstructeurs = (...sources: unknown[]): AnyRecord[] => {
|
|
const ids = uniqueConstructeurIds(...sources)
|
|
if (!ids.length) return []
|
|
const pools = sources
|
|
.flatMap((source) => {
|
|
if (Array.isArray(source)) return [source]
|
|
if (source && typeof source === 'object' && (source as AnyRecord).id) return [[source]]
|
|
return []
|
|
})
|
|
.filter(Boolean) as AnyRecord[][]
|
|
return resolveConstructeurs(ids, ...(pools as any[])) as any[]
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Transform functions
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const getStructureCustomFields = (structure: unknown): AnyRecord[] => {
|
|
if (!structure || typeof structure !== 'object') return []
|
|
const normalized = normalizeStructureForEditor(structure as any) as any
|
|
return Array.isArray(normalized?.customFields)
|
|
? (normalized.customFields as AnyRecord[])
|
|
: []
|
|
}
|
|
|
|
const transformCustomFields = (piecesData: AnyRecord[]): AnyRecord[] => {
|
|
return (piecesData || []).map((piece) => {
|
|
const typePiece = (piece.typePiece as AnyRecord) || {}
|
|
|
|
const normalizeStructureDefs = (structure: unknown) =>
|
|
structure ? normalizeStructureForEditor(structure as AnyRecord) : null
|
|
|
|
const normalizedStructureDefs = [
|
|
normalizeStructureDefs((piece.definition as AnyRecord)?.structure),
|
|
normalizeStructureDefs((piece.typePiece as AnyRecord)?.structure),
|
|
normalizeStructureDefs(typePiece.structure),
|
|
]
|
|
|
|
const valueEntries = [
|
|
...(Array.isArray(piece.customFieldValues) ? piece.customFieldValues : []),
|
|
...(Array.isArray(piece.customFields)
|
|
? (piece.customFields as AnyRecord[])
|
|
.map(normalizeCustomFieldValueEntry)
|
|
.filter((e) => e !== null)
|
|
: []),
|
|
...(Array.isArray(typePiece.customFieldValues)
|
|
? (typePiece.customFieldValues as AnyRecord[])
|
|
.map(normalizeCustomFieldValueEntry)
|
|
.filter((e) => e !== null)
|
|
: []),
|
|
]
|
|
|
|
const customFields = dedupeCustomFieldEntries(
|
|
mergeCustomFieldValuesWithDefinitions(
|
|
valueEntries,
|
|
normalizeExistingCustomFieldDefinitions(piece.customFields),
|
|
normalizeExistingCustomFieldDefinitions((piece.definition as AnyRecord)?.customFields),
|
|
normalizeExistingCustomFieldDefinitions((piece.typePiece as AnyRecord)?.customFields),
|
|
normalizeExistingCustomFieldDefinitions(typePiece.customFields),
|
|
...normalizedStructureDefs.map((def) => getStructureCustomFields(def)),
|
|
),
|
|
)
|
|
|
|
const constructeurIds = uniqueConstructeurIds(
|
|
piece.constructeurs,
|
|
piece.constructeurIds,
|
|
piece.constructeurId,
|
|
piece.constructeur,
|
|
(piece.originalPiece as AnyRecord)?.constructeurs,
|
|
(piece.originalPiece as AnyRecord)?.constructeurIds,
|
|
(piece.originalPiece as AnyRecord)?.constructeurId,
|
|
(piece.originalPiece as AnyRecord)?.constructeur,
|
|
)
|
|
|
|
const { product: resolvedProduct, productId: resolvedProductId } =
|
|
resolveProductReference(piece)
|
|
|
|
const constructeursList = resolveConstructeurs(
|
|
constructeurIds,
|
|
Array.isArray(piece.constructeurs) ? (piece.constructeurs as any[]) : [],
|
|
piece.constructeur ? [piece.constructeur as any] : [],
|
|
Array.isArray((piece.originalPiece as AnyRecord)?.constructeurs)
|
|
? ((piece.originalPiece as AnyRecord).constructeurs as any[])
|
|
: [],
|
|
(piece.originalPiece as AnyRecord)?.constructeur
|
|
? [(piece.originalPiece as AnyRecord).constructeur as any]
|
|
: [],
|
|
constructeurs.value as any,
|
|
) as any[]
|
|
|
|
const normalizedPiece = {
|
|
...piece,
|
|
product: resolvedProduct || piece.product || null,
|
|
productId: resolvedProductId || piece.productId || (piece.product as AnyRecord)?.id || null,
|
|
}
|
|
const productDisplay = getProductDisplay(normalizedPiece)
|
|
|
|
return {
|
|
...normalizedPiece,
|
|
customFields,
|
|
documents: piece.documents || [],
|
|
constructeurs: constructeursList,
|
|
constructeur: constructeursList[0] || piece.constructeur || null,
|
|
constructeurIds,
|
|
constructeurId: constructeurIds[0] || null,
|
|
typePieceId:
|
|
piece.typePieceId ||
|
|
(piece.typePiece as AnyRecord)?.id ||
|
|
null,
|
|
__productDisplay: productDisplay,
|
|
}
|
|
})
|
|
}
|
|
|
|
const transformComponentCustomFields = (componentsData: AnyRecord[]): AnyRecord[] => {
|
|
const normalizeStructureDefs = (structure: unknown) =>
|
|
structure ? normalizeStructureForEditor(structure as AnyRecord) : null
|
|
|
|
return (componentsData || []).map((component) => {
|
|
const type = (component.typeComposant as AnyRecord) || {}
|
|
|
|
const normalizedStructureDefs = [
|
|
normalizeStructureDefs((component.definition as AnyRecord)?.structure),
|
|
normalizeStructureDefs((component.typeComposant as AnyRecord)?.structure),
|
|
normalizeStructureDefs(type.structure),
|
|
]
|
|
|
|
const actualComponent = (component.originalComposant as AnyRecord) || component
|
|
|
|
const valueEntries = [
|
|
...(Array.isArray(component.customFieldValues) ? component.customFieldValues : []),
|
|
...(Array.isArray(component.customFields)
|
|
? (component.customFields as AnyRecord[])
|
|
.map(normalizeCustomFieldValueEntry)
|
|
.filter((e) => e !== null)
|
|
: []),
|
|
...(Array.isArray(actualComponent?.customFields)
|
|
? (actualComponent.customFields as AnyRecord[])
|
|
.map(normalizeCustomFieldValueEntry)
|
|
.filter((e) => e !== null)
|
|
: []),
|
|
]
|
|
|
|
const customFields = dedupeCustomFieldEntries(
|
|
mergeCustomFieldValuesWithDefinitions(
|
|
valueEntries,
|
|
normalizeExistingCustomFieldDefinitions(component.customFields),
|
|
normalizeExistingCustomFieldDefinitions((component.definition as AnyRecord)?.customFields),
|
|
normalizeExistingCustomFieldDefinitions((component.typeComposant as AnyRecord)?.customFields),
|
|
normalizeExistingCustomFieldDefinitions(type.customFields),
|
|
normalizeExistingCustomFieldDefinitions(actualComponent?.customFields),
|
|
...normalizedStructureDefs.map((def) => getStructureCustomFields(def)),
|
|
),
|
|
)
|
|
|
|
const piecesTransformed = component.pieces
|
|
? transformCustomFields(component.pieces as AnyRecord[]).map((p) => ({
|
|
...p,
|
|
parentComponentName: component.name,
|
|
}))
|
|
: []
|
|
|
|
const subComponents = component.sousComposants
|
|
? transformComponentCustomFields(component.sousComposants as AnyRecord[])
|
|
: []
|
|
|
|
const constructeurIds = uniqueConstructeurIds(
|
|
component.constructeurs,
|
|
component.constructeurIds,
|
|
component.constructeurId,
|
|
component.constructeur,
|
|
actualComponent?.constructeurs,
|
|
actualComponent?.constructeurIds,
|
|
actualComponent?.constructeurId,
|
|
actualComponent?.constructeur,
|
|
)
|
|
|
|
const constructeursList = resolveConstructeurs(
|
|
constructeurIds,
|
|
Array.isArray(component.constructeurs) ? (component.constructeurs as any[]) : [],
|
|
component.constructeur ? [component.constructeur as any] : [],
|
|
Array.isArray(actualComponent?.constructeurs)
|
|
? (actualComponent.constructeurs as any[])
|
|
: [],
|
|
actualComponent?.constructeur ? [actualComponent.constructeur as any] : [],
|
|
constructeurs.value as any,
|
|
) as any[]
|
|
|
|
const { product: resolvedProduct, productId: resolvedProductId } =
|
|
resolveProductReference(component)
|
|
const normalizedComponent = {
|
|
...component,
|
|
product: resolvedProduct || component.product || null,
|
|
productId:
|
|
resolvedProductId || component.productId || (component.product as AnyRecord)?.id || null,
|
|
}
|
|
const productDisplay = getProductDisplay(normalizedComponent)
|
|
|
|
return {
|
|
...normalizedComponent,
|
|
customFields,
|
|
pieces: piecesTransformed,
|
|
subComponents,
|
|
documents: component.documents || [],
|
|
constructeurs: constructeursList,
|
|
constructeur: constructeursList[0] || component.constructeur || null,
|
|
constructeurIds,
|
|
constructeurId: constructeurIds[0] || null,
|
|
typeComposantId:
|
|
component.typeComposantId ||
|
|
(component.typeComposant as AnyRecord)?.id ||
|
|
null,
|
|
__productDisplay: productDisplay,
|
|
}
|
|
})
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 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,
|
|
)
|
|
|
|
components.value = transformComponentCustomFields(hierarchy as AnyRecord[])
|
|
pieces.value = transformCustomFields(machineLevelPieces as AnyRecord[])
|
|
|
|
return true
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Computed values
|
|
// ---------------------------------------------------------------------------
|
|
|
|
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
|
|
})
|
|
})
|
|
|
|
const machineDirectProducts = computed(() => {
|
|
return machineProductLinks.value.map((link) => {
|
|
const productObj = link.product as AnyRecord | string | null
|
|
let resolved: AnyRecord | null = null
|
|
let productId: string | null = null
|
|
|
|
if (typeof productObj === 'string') {
|
|
productId = productObj.split('/').pop() || null
|
|
resolved = productId ? findProductById(productId) : null
|
|
} else if (productObj && typeof productObj === 'object') {
|
|
productId = (productObj as AnyRecord)?.id as string | null
|
|
// Prefer the embedded product from the structure endpoint — it has richer
|
|
// data (typeProduct as object, supplierPrice, constructeurs) than the
|
|
// global products cache which may store typeProduct as an IRI string.
|
|
const cached = productId ? findProductById(productId) : null
|
|
resolved = productObj as AnyRecord
|
|
if (cached) {
|
|
// Merge: use embedded as base, overlay any non-null cached fields
|
|
resolved = { ...resolved, ...Object.fromEntries(
|
|
Object.entries(cached as AnyRecord).filter(([, v]) => v != null && v !== ''),
|
|
) }
|
|
// But always prefer the embedded typeProduct when it's an object
|
|
if (productObj.typeProduct && typeof productObj.typeProduct === 'object') {
|
|
resolved.typeProduct = productObj.typeProduct
|
|
}
|
|
}
|
|
}
|
|
|
|
const constructeurIds = uniqueConstructeurIds(
|
|
resolved?.constructeurs,
|
|
resolved?.constructeurIds,
|
|
)
|
|
const resolvedConstructeurs = resolveConstructeurs(
|
|
constructeurIds,
|
|
resolved?.constructeurs as any[] || [],
|
|
constructeurs.value,
|
|
)
|
|
|
|
return {
|
|
id: (resolved?.id as string) || productId || null,
|
|
linkId: (link.id as string) || (typeof link['@id'] === 'string' ? link['@id'].split('/').pop() : null) || null,
|
|
name: (resolved?.name as string) || 'Produit inconnu',
|
|
reference: (resolved?.reference as string) || null,
|
|
supplierLabel: resolvedConstructeurs.length
|
|
? resolvedConstructeurs.map((c) => c.name).filter(Boolean).join(', ') || null
|
|
: getProductSuppliersLabel(resolved),
|
|
priceLabel: resolved ? getProductPriceLabel(resolved) : null,
|
|
groupLabel: ((resolved?.typeProduct as AnyRecord)?.name as string) || '',
|
|
documents: productId ? (productDocumentsMap.value.get(productId) || []) : [],
|
|
}
|
|
})
|
|
})
|
|
|
|
const loadProductDocuments = async () => {
|
|
const productIds = machineProductLinks.value
|
|
.map((link) => {
|
|
const p = link.product as AnyRecord | string | null
|
|
if (typeof p === 'string') return p.split('/').pop() || null
|
|
return (p as AnyRecord)?.id as string | null
|
|
})
|
|
.filter((id): id is string => !!id)
|
|
|
|
const results = await Promise.allSettled(
|
|
productIds.map(async (id) => {
|
|
const result: any = await loadDocumentsByProduct(id, { updateStore: false })
|
|
if (result.success && Array.isArray(result.data)) {
|
|
return { id, docs: result.data as AnyRecord[] }
|
|
}
|
|
return { id, docs: [] }
|
|
}),
|
|
)
|
|
|
|
const map = new Map<string, AnyRecord[]>()
|
|
results.forEach((r) => {
|
|
if (r.status === 'fulfilled' && r.value.docs.length) {
|
|
map.set(r.value.id, r.value.docs)
|
|
}
|
|
})
|
|
productDocumentsMap.value = map
|
|
}
|
|
|
|
const machineDocumentsList = computed(
|
|
() => ((machine.value as AnyRecord)?.documents as AnyRecord[]) || [],
|
|
)
|
|
|
|
const visibleMachineCustomFields = computed(() => {
|
|
const fields = Array.isArray(machineCustomFields.value) ? machineCustomFields.value : []
|
|
if (isEditMode.value) return fields
|
|
return fields.filter((field) => shouldDisplayCustomField(field))
|
|
})
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Machine field methods
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const initMachineFields = () => {
|
|
if (machine.value) {
|
|
machineName.value = (machine.value.name as string) || ''
|
|
machineReference.value = (machine.value.reference as string) || ''
|
|
machineConstructeurIds.value = uniqueConstructeurIds(
|
|
machine.value.constructeurIds,
|
|
machine.value.constructeurs,
|
|
machine.value.constructeur,
|
|
)
|
|
}
|
|
}
|
|
|
|
const getMachineFieldId = (fieldName: string): string => {
|
|
return machine.value ? `machine-${fieldName}-${machine.value.id}` : `machine-${fieldName}`
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Custom field methods
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const syncMachineCustomFields = () => {
|
|
if (!machine.value) {
|
|
machineCustomFields.value = []
|
|
return
|
|
}
|
|
const valueEntries = [
|
|
...(Array.isArray(machine.value.customFieldValues) ? machine.value.customFieldValues : []),
|
|
...(Array.isArray(machine.value.customFields)
|
|
? (machine.value.customFields as AnyRecord[])
|
|
.map(normalizeCustomFieldValueEntry)
|
|
.filter((e) => e !== null)
|
|
: []),
|
|
]
|
|
const merged = dedupeCustomFieldEntries(
|
|
mergeCustomFieldValuesWithDefinitions(
|
|
valueEntries,
|
|
normalizeExistingCustomFieldDefinitions(machine.value.customFields),
|
|
),
|
|
).map((field: AnyRecord) => ({ ...field, readOnly: false }))
|
|
machineCustomFields.value = merged
|
|
}
|
|
|
|
const setMachineCustomFieldValue = (field: AnyRecord, value: unknown) => {
|
|
if (!field) return
|
|
field.value = value
|
|
if (field.customFieldValueId && (machine.value as AnyRecord)?.customFieldValues) {
|
|
const stored = ((machine.value as AnyRecord).customFieldValues as AnyRecord[]).find(
|
|
(fv) => fv.id === field.customFieldValueId,
|
|
)
|
|
if (stored) stored.value = value
|
|
}
|
|
}
|
|
|
|
const updateMachineCustomField = async (field: AnyRecord) => {
|
|
if (!machine.value || !field) return
|
|
|
|
const { id: customFieldId, customFieldValueId } = field
|
|
const fieldLabel = (field.name as string) || 'Champ personnalisé'
|
|
|
|
try {
|
|
if (customFieldValueId) {
|
|
const result: any = await updateCustomFieldValueApi(customFieldValueId as string, {
|
|
value: field.value ?? '',
|
|
} as any)
|
|
if (result.success) {
|
|
toast.showSuccess(`Champ "${fieldLabel}" de la machine mis à jour avec succès`)
|
|
syncMachineCustomFields()
|
|
} else {
|
|
toast.showError(`Erreur lors de la mise à jour du champ "${fieldLabel}"`)
|
|
}
|
|
return
|
|
}
|
|
if (!customFieldId) {
|
|
toast.showError(
|
|
'Impossible de mettre à jour ce champ personnalisé (identifiant manquant).',
|
|
)
|
|
return
|
|
}
|
|
const result: any = await upsertCustomFieldValue(
|
|
customFieldId as string,
|
|
'machine',
|
|
machine.value.id as string,
|
|
field.value ?? '',
|
|
)
|
|
if (result.success) {
|
|
const createdValue = result.data as AnyRecord
|
|
toast.showSuccess(`Champ "${fieldLabel}" de la machine mis à jour avec succès`)
|
|
if (createdValue?.id) {
|
|
if (!createdValue.customField) {
|
|
createdValue.customField = {
|
|
id: customFieldId,
|
|
name: field.name,
|
|
type: field.type,
|
|
required: field.required,
|
|
options: field.options,
|
|
}
|
|
}
|
|
field.customFieldValueId = createdValue.id
|
|
field.readOnly = false
|
|
const existingValues = Array.isArray(machine.value.customFieldValues)
|
|
? (machine.value.customFieldValues as AnyRecord[]).filter(
|
|
(item) => item.id !== createdValue.id,
|
|
)
|
|
: []
|
|
machine.value.customFieldValues = [...existingValues, createdValue]
|
|
}
|
|
syncMachineCustomFields()
|
|
} else {
|
|
toast.showError(`Erreur lors de la mise à jour du champ "${fieldLabel}"`)
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur lors de la mise à jour du champ personnalisé de la machine:', error)
|
|
toast.showError(`Erreur lors de la mise à jour du champ "${fieldLabel}"`)
|
|
}
|
|
}
|
|
|
|
const updatePieceCustomField = async (fieldUpdate: AnyRecord) => {
|
|
try {
|
|
const result: any = await upsertCustomFieldValue(
|
|
fieldUpdate.fieldId as string,
|
|
'piece',
|
|
fieldUpdate.pieceId as string,
|
|
fieldUpdate.value,
|
|
)
|
|
if (result.success) {
|
|
toast.showSuccess('Champ personnalisé mis à jour avec succès')
|
|
} else {
|
|
toast.showError('Erreur lors de la mise à jour du champ personnalisé')
|
|
}
|
|
} catch (error) {
|
|
toast.showError('Erreur lors de la mise à jour du champ personnalisé')
|
|
console.error('Erreur lors de la mise à jour du champ personnalisé:', error)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Document methods
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const refreshMachineDocuments = async () => {
|
|
if (!machine.value?.id) return
|
|
const result: any = await loadDocumentsByMachine(machine.value.id as string, { updateStore: false })
|
|
if (result.success && machine.value) {
|
|
machine.value.documents = result.data || []
|
|
machineDocumentsLoaded.value = true
|
|
}
|
|
}
|
|
|
|
const handleMachineFilesAdded = async (files: File[]) => {
|
|
if (!files.length || !machine.value?.id) return
|
|
machineDocumentsUploading.value = true
|
|
try {
|
|
const result: any = await uploadDocuments(
|
|
{ files, context: { machineId: machine.value.id } } as any,
|
|
{ updateStore: false },
|
|
)
|
|
if (result.success && machine.value) {
|
|
const newDocs = (result.data as AnyRecord[]) || []
|
|
machine.value.documents = [
|
|
...newDocs,
|
|
...((machine.value.documents as AnyRecord[]) || []),
|
|
]
|
|
machineDocumentFiles.value = []
|
|
}
|
|
} finally {
|
|
machineDocumentsUploading.value = false
|
|
}
|
|
}
|
|
|
|
const removeMachineDocument = async (documentId: string) => {
|
|
if (!documentId) return
|
|
const result: any = await deleteDocument(documentId, { updateStore: false })
|
|
if (result.success && machine.value) {
|
|
machine.value.documents = ((machine.value.documents as AnyRecord[]) || []).filter(
|
|
(doc) => doc.id !== documentId,
|
|
)
|
|
}
|
|
}
|
|
|
|
const openPreview = (doc: AnyRecord) => {
|
|
if (!canPreviewDocument(doc)) return
|
|
previewDocument.value = doc
|
|
previewVisible.value = true
|
|
}
|
|
|
|
const closePreview = () => {
|
|
previewVisible.value = false
|
|
previewDocument.value = null
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Update methods
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const updateMachineInfo = async () => {
|
|
if (!machine.value) return
|
|
try {
|
|
const cIds = uniqueConstructeurIds(machineConstructeurIds.value)
|
|
machineConstructeurIds.value = cIds
|
|
|
|
const result: any = await updateMachineApi(machine.value.id as string, {
|
|
name: machineName.value,
|
|
reference: machineReference.value,
|
|
constructeurIds: cIds,
|
|
} as any)
|
|
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.documents || [],
|
|
customFieldValues: machinePayload.customFieldValues || machine.value.customFieldValues || [],
|
|
}
|
|
machineConstructeurIds.value = uniqueConstructeurIds(
|
|
machine.value!.constructeurIds,
|
|
machine.value!.constructeurs,
|
|
machine.value!.constructeur,
|
|
)
|
|
const linksApplied = applyMachineLinks(result.data)
|
|
if (linksApplied && machine.value) {
|
|
machine.value.componentLinks = machineComponentLinks.value
|
|
machine.value.pieceLinks = machinePieceLinks.value
|
|
}
|
|
loadProductDocuments().catch(() => {})
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur lors de la mise à jour de la machine:', error)
|
|
}
|
|
}
|
|
|
|
const updateComponent = async (updatedComponent: AnyRecord) => {
|
|
try {
|
|
const cIds = uniqueConstructeurIds(
|
|
updatedComponent.constructeurIds,
|
|
updatedComponent.constructeurId,
|
|
updatedComponent.constructeur,
|
|
)
|
|
const productId = updatedComponent.productId
|
|
? String(updatedComponent.productId)
|
|
: null
|
|
const prix =
|
|
updatedComponent.prix !== null &&
|
|
updatedComponent.prix !== undefined &&
|
|
String(updatedComponent.prix).trim() !== ''
|
|
? Number(updatedComponent.prix)
|
|
: null
|
|
|
|
const result: any = await updateComposantApi(updatedComponent.id as string, {
|
|
name: updatedComponent.name,
|
|
reference: updatedComponent.reference,
|
|
constructeurIds: cIds,
|
|
prix: Number.isNaN(prix) ? null : prix,
|
|
productId,
|
|
} as any)
|
|
if (result.success) {
|
|
const transformed = transformComponentCustomFields([result.data])[0]
|
|
Object.assign(updatedComponent, transformed)
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur lors de la mise à jour du composant:', error)
|
|
}
|
|
}
|
|
|
|
const updatePieceFromComponent = async (updatedPiece: AnyRecord) => {
|
|
try {
|
|
const cIds = uniqueConstructeurIds(
|
|
updatedPiece.constructeurIds,
|
|
updatedPiece.constructeurId,
|
|
updatedPiece.constructeur,
|
|
)
|
|
const productId = updatedPiece.productId ? String(updatedPiece.productId) : null
|
|
const prix =
|
|
updatedPiece.prix !== null &&
|
|
updatedPiece.prix !== undefined &&
|
|
String(updatedPiece.prix).trim() !== ''
|
|
? Number(updatedPiece.prix)
|
|
: null
|
|
|
|
const result: any = await updatePieceApi(updatedPiece.id as string, {
|
|
name: updatedPiece.name,
|
|
reference: updatedPiece.reference,
|
|
constructeurIds: cIds,
|
|
prix: Number.isNaN(prix) ? null : prix,
|
|
productId,
|
|
} as any)
|
|
if (result.success) {
|
|
const transformed = transformCustomFields([result.data])[0]
|
|
Object.assign(updatedPiece, transformed)
|
|
if (updatedPiece.customFields) {
|
|
for (const field of updatedPiece.customFields as AnyRecord[]) {
|
|
if (field.value !== undefined) {
|
|
await upsertCustomFieldValue(
|
|
field.id as string,
|
|
'piece',
|
|
updatedPiece.id as string,
|
|
field.value,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur lors de la mise à jour de la pièce:', error)
|
|
}
|
|
}
|
|
|
|
const updatePieceInfo = async (updatedPiece: AnyRecord) => {
|
|
try {
|
|
const cIds = uniqueConstructeurIds(
|
|
updatedPiece.constructeurIds,
|
|
updatedPiece.constructeurId,
|
|
updatedPiece.constructeur,
|
|
)
|
|
const productId = updatedPiece.productId ? String(updatedPiece.productId) : null
|
|
const prix =
|
|
updatedPiece.prix !== null &&
|
|
updatedPiece.prix !== undefined &&
|
|
String(updatedPiece.prix).trim() !== ''
|
|
? Number(updatedPiece.prix)
|
|
: null
|
|
|
|
const result: any = await updatePieceApi(updatedPiece.id as string, {
|
|
name: updatedPiece.name,
|
|
reference: updatedPiece.reference,
|
|
constructeurIds: cIds,
|
|
prix: Number.isNaN(prix) ? null : prix,
|
|
productId,
|
|
} as any)
|
|
if (result.success) {
|
|
const transformed = transformCustomFields([result.data])[0]
|
|
Object.assign(updatedPiece, transformed)
|
|
}
|
|
} catch (error) {
|
|
console.error('Erreur lors de la mise à jour de la pièce:', error)
|
|
}
|
|
}
|
|
|
|
const handleMachineConstructeurChange = async (value: unknown) => {
|
|
machineConstructeurIds.value = uniqueConstructeurIds(value)
|
|
await updateMachineInfo()
|
|
}
|
|
|
|
const editComponent = () => {
|
|
toast.showInfo('La modification des composants sera bientôt disponible')
|
|
}
|
|
|
|
const editPiece = () => {
|
|
toast.showInfo('La modification des pièces sera bientôt disponible')
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// UI methods
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const toggleEditMode = () => {
|
|
isEditMode.value = !isEditMode.value
|
|
debug.value = !debug.value
|
|
if (isEditMode.value && !machineDocumentsLoaded.value) {
|
|
refreshMachineDocuments()
|
|
}
|
|
}
|
|
|
|
const toggleAllComponents = () => {
|
|
componentsCollapsed.value = !componentsCollapsed.value
|
|
collapseToggleToken.value += 1
|
|
}
|
|
|
|
const collapseAllComponents = () => {
|
|
componentsCollapsed.value = true
|
|
collapseToggleToken.value += 1
|
|
}
|
|
|
|
const toggleAllPieces = () => {
|
|
piecesCollapsed.value = !piecesCollapsed.value
|
|
pieceCollapseToggleToken.value += 1
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Print wrappers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const ensurePrintSelectionEntries = () =>
|
|
_ensurePrintEntries(components.value, machinePieces.value)
|
|
const setAllPrintSelection = (value: boolean) =>
|
|
_setAllPrint(value, components.value, machinePieces.value)
|
|
const openPrintModal = () =>
|
|
_openPrintModal(components.value, machinePieces.value)
|
|
const handlePrintConfirm = () =>
|
|
_handlePrintConfirm(
|
|
machine.value as any,
|
|
machineName.value,
|
|
machineReference.value,
|
|
machinePieces.value as any,
|
|
components.value as any,
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Structure link management
|
|
// ---------------------------------------------------------------------------
|
|
|
|
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()
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Data loading
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const loadMachineData = async () => {
|
|
loading.value = true
|
|
try {
|
|
const machineResult: any = await get(`/machines/${machineId}/structure`)
|
|
|
|
if (!machineResult.success) {
|
|
console.error('Machine non trouvée:', machineId, machineResult.error)
|
|
machine.value = null
|
|
components.value = []
|
|
pieces.value = []
|
|
return
|
|
}
|
|
|
|
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 as AnyRecord[])?.length)
|
|
syncMachineCustomFields()
|
|
initMachineFields()
|
|
|
|
// Start document loading early (independent of products/links)
|
|
const documentPromise = !machineDocumentsLoaded.value
|
|
? refreshMachineDocuments()
|
|
: Promise.resolve()
|
|
|
|
if (!(productInventory.value as AnyRecord[]).length) {
|
|
try {
|
|
await loadProducts()
|
|
} catch (error) {
|
|
console.error('Erreur lors du chargement des produits:', error)
|
|
}
|
|
}
|
|
|
|
const linksApplied = applyMachineLinks(machineResult.data)
|
|
|
|
if (machine.value) {
|
|
machine.value.componentLinks = machineComponentLinks.value
|
|
machine.value.pieceLinks = machinePieceLinks.value
|
|
machine.value.productLinks = machineProductLinks.value
|
|
}
|
|
|
|
if (!linksApplied) {
|
|
components.value = transformComponentCustomFields(machinePayload.components || [])
|
|
pieces.value = transformCustomFields(machinePayload.pieces || [])
|
|
machineProductLinks.value = Array.isArray(machinePayload.productLinks)
|
|
? machinePayload.productLinks
|
|
: []
|
|
}
|
|
|
|
if (machine.value) {
|
|
machine.value.productLinks = machineProductLinks.value
|
|
}
|
|
|
|
collapseAllComponents()
|
|
|
|
// Load product documents in background
|
|
loadProductDocuments().catch(() => {})
|
|
|
|
// Wait for documents if still loading
|
|
await documentPromise
|
|
} catch (error) {
|
|
console.error('Erreur lors du chargement des données:', error)
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const loadInitialData = (): Promise<unknown[]> => {
|
|
return Promise.all([
|
|
loadConstructeurs(),
|
|
loadComponentTypes(),
|
|
loadPieceTypes(),
|
|
])
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Watchers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
watch(() => (machine.value as AnyRecord)?.customFieldValues, () => syncMachineCustomFields(), { deep: true })
|
|
watch(() => (machine.value as AnyRecord)?.customFields, () => syncMachineCustomFields(), { deep: true })
|
|
watch(
|
|
() => [components.value.length, machinePieces.value.length],
|
|
() => ensurePrintSelectionEntries(),
|
|
{ immediate: true },
|
|
)
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Public API
|
|
// ---------------------------------------------------------------------------
|
|
|
|
return {
|
|
// State
|
|
loading,
|
|
machine,
|
|
components,
|
|
pieces,
|
|
machineComponentLinks,
|
|
machinePieceLinks,
|
|
machineProductLinks,
|
|
printAreaRef,
|
|
|
|
// Machine fields
|
|
machineName,
|
|
machineReference,
|
|
machineConstructeurIds,
|
|
machineConstructeurId,
|
|
machineConstructeursDisplay,
|
|
machineConstructeurContact,
|
|
hasMachineConstructeur,
|
|
|
|
// UI state
|
|
machineDocumentFiles,
|
|
machineDocumentsUploading,
|
|
machineDocumentsLoaded,
|
|
machineCustomFields,
|
|
previewDocument,
|
|
previewVisible,
|
|
isEditMode,
|
|
debug,
|
|
componentsCollapsed,
|
|
collapseToggleToken,
|
|
piecesCollapsed,
|
|
pieceCollapseToggleToken,
|
|
|
|
// Computed
|
|
componentTypeOptions,
|
|
pieceTypeOptions,
|
|
componentTypeLabelMap,
|
|
pieceTypeLabelMap,
|
|
productInventory,
|
|
productById,
|
|
flattenedComponents,
|
|
machinePieces,
|
|
machineDirectProducts,
|
|
machineDocumentsList,
|
|
visibleMachineCustomFields,
|
|
|
|
// Product helpers
|
|
findProductById,
|
|
resolveProductReference,
|
|
getProductDisplay,
|
|
|
|
// Helpers
|
|
isPlainObject,
|
|
flattenComponents,
|
|
findComponentById,
|
|
findPieceById,
|
|
collectConstructeurs,
|
|
|
|
// Transform
|
|
transformCustomFields,
|
|
transformComponentCustomFields,
|
|
|
|
// Hierarchy
|
|
applyMachineLinks,
|
|
|
|
// Machine fields methods
|
|
initMachineFields,
|
|
getMachineFieldId,
|
|
|
|
// Custom fields
|
|
syncMachineCustomFields,
|
|
setMachineCustomFieldValue,
|
|
updateMachineCustomField,
|
|
updatePieceCustomField,
|
|
|
|
// Documents
|
|
refreshMachineDocuments,
|
|
handleMachineFilesAdded,
|
|
removeMachineDocument,
|
|
openPreview,
|
|
closePreview,
|
|
|
|
// Updates
|
|
updateMachineInfo,
|
|
updateComponent,
|
|
updatePieceFromComponent,
|
|
updatePieceInfo,
|
|
handleMachineConstructeurChange,
|
|
editComponent,
|
|
editPiece,
|
|
|
|
// UI methods
|
|
toggleEditMode,
|
|
toggleAllComponents,
|
|
collapseAllComponents,
|
|
toggleAllPieces,
|
|
|
|
// Print
|
|
printModalOpen,
|
|
printSelection,
|
|
ensurePrintSelectionEntries,
|
|
setAllPrintSelection,
|
|
openPrintModal,
|
|
closePrintModal,
|
|
handlePrintConfirm,
|
|
|
|
// Loading
|
|
loadMachineData,
|
|
loadInitialData,
|
|
|
|
// Structure link management
|
|
addComponentLink,
|
|
removeComponentLink,
|
|
addPieceLink,
|
|
removePieceLink,
|
|
addProductLink,
|
|
removeProductLink,
|
|
reloadMachineStructure,
|
|
|
|
// External
|
|
constructeurs,
|
|
loadProducts,
|
|
updateMachineStructure,
|
|
toast,
|
|
|
|
// Re-exports for template
|
|
formatCustomFieldValue,
|
|
summarizeCustomFields,
|
|
formatConstructeurContactSummary,
|
|
formatSize,
|
|
shouldInlinePdf,
|
|
documentPreviewSrc,
|
|
documentThumbnailClass,
|
|
documentIcon,
|
|
downloadDocument: downloadDocumentHelper,
|
|
canPreviewDocument,
|
|
isImageDocument,
|
|
isPdfDocument,
|
|
getFileIcon,
|
|
resolveIdentifier,
|
|
extractParentLinkIdentifiers,
|
|
}
|
|
}
|