/** * Machine detail — product display sub-composable. * * Handles product resolution, display helpers, supplier info, * and machine-level direct product links. */ import { computed } from 'vue' import { useProducts } from '~/composables/useProducts' import { resolveProductReference as _resolveProductReference, getProductDisplay as _getProductDisplay, getProductSuppliersLabel, getProductPriceLabel, } from '~/shared/utils/productDisplayUtils' import { resolveConstructeurs, uniqueConstructeurIds, } from '~/shared/constructeurUtils' type AnyRecord = Record interface MachineDetailProductsDeps { machineProductLinks: Ref productDocumentsMap: Ref> constructeurs: Ref } export function useMachineDetailProducts(deps: MachineDetailProductsDeps) { const { machineProductLinks, productDocumentsMap, constructeurs } = deps const { products, loadProducts } = useProducts() // --------------------------------------------------------------------------- // Computed // --------------------------------------------------------------------------- const productInventory = computed(() => products.value || []) const productById = computed(() => { const map = new Map() ;(productInventory.value as AnyRecord[]).forEach((product: AnyRecord) => { if (product?.id) map.set(product.id as string, product) }) return map }) // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- 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) // --------------------------------------------------------------------------- // Machine direct products // --------------------------------------------------------------------------- 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 cIds = uniqueConstructeurIds( resolved?.constructeurs, resolved?.constructeurIds, ) const resolvedConstructeurs = resolveConstructeurs( cIds, resolved?.constructeurs as any[] || [], constructeurs.value as any, ) 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) || (link.modelType as AnyRecord)?.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) || []) : [], pendingEntity: (link.pendingEntity as boolean) || false, modelTypeId: (link.modelTypeId as string) || null, modelType: (link.modelType as string) || null, } }) }) return { // Computed productInventory, productById, machineDirectProducts, // Helpers findProductById, resolveProductReference, getProductDisplay, // Loading loadProducts, } }