/** * Pure helper functions for building, validating and serializing * component structure assignment trees. * * Extracted from useComponentCreate composable to keep file sizes manageable. */ import type { StructureAssignmentNode } from '~/components/ComponentStructureAssignmentNode.vue' import type { ComponentModelPiece, ComponentModelProduct, ComponentModelStructure, ComponentModelStructureNode, } from '~/shared/types/inventory' // --------------------------------------------------------------------------- // Extraction helpers // --------------------------------------------------------------------------- export function extractSubcomponents( definition: ComponentModelStructure | ComponentModelStructureNode | null | undefined, ): ComponentModelStructureNode[] { if (!definition || typeof definition !== 'object') { return [] } const raw = Array.isArray((definition as any).subcomponents) ? (definition as any).subcomponents : Array.isArray((definition as any).subComponents) ? (definition as any).subComponents : [] return raw.filter( (item: unknown): item is ComponentModelStructureNode => !!item && typeof item === 'object', ) } export function extractPiecesFromNode( definition: ComponentModelStructure | ComponentModelStructureNode | null | undefined, ): ComponentModelPiece[] { if (!definition || typeof definition !== 'object') { return [] } const raw = Array.isArray((definition as any).pieces) ? (definition as any).pieces : [] return raw.filter( (item: unknown): item is ComponentModelPiece => !!item && typeof item === 'object', ) } export function extractProductsFromNode( definition: ComponentModelStructure | ComponentModelStructureNode | null | undefined, ): ComponentModelProduct[] { if (!definition || typeof definition !== 'object') { return [] } const raw = Array.isArray((definition as any).products) ? (definition as any).products : [] return raw.filter( (item: unknown): item is ComponentModelProduct => !!item && typeof item === 'object', ) } // --------------------------------------------------------------------------- // Assignment tree building // --------------------------------------------------------------------------- export function buildAssignmentNode( definition: ComponentModelStructureNode | ComponentModelStructure, path: string, ): StructureAssignmentNode { const pieces = extractPiecesFromNode(definition).map((piece, index) => ({ path: `${path}:piece-${index}`, definition: piece, selectedPieceId: '', })) const products = extractProductsFromNode(definition).map((product, index) => ({ path: `${path}:product-${index}`, definition: product, selectedProductId: '', })) const subcomponents = extractSubcomponents(definition).map( (child, index) => buildAssignmentNode(child, `${path}:sub-${index}`), ) return { path, definition, selectedComponentId: '', pieces, products, subcomponents, } } export function initializeStructureAssignments( structure: ComponentModelStructure | null, ): StructureAssignmentNode | null { if (!structure || typeof structure !== 'object') { return null } return buildAssignmentNode(structure, 'root') } // --------------------------------------------------------------------------- // Validation // --------------------------------------------------------------------------- export function hasAssignments(node: StructureAssignmentNode | null): boolean { if (!node) { return false } if (node.pieces.length > 0 || node.products.length > 0 || node.subcomponents.length > 0) { return true } return node.subcomponents.some((child) => hasAssignments(child)) } export function isAssignmentNodeComplete( node: StructureAssignmentNode, isRootNode = false, ): boolean { const piecesComplete = node.pieces.every( (piece) => !!piece.selectedPieceId && piece.selectedPieceId.length > 0, ) const productsComplete = node.products.every( (product) => !!product.selectedProductId && product.selectedProductId.length > 0, ) const subcomponentsComplete = node.subcomponents.every( (child) => !!child.selectedComponentId && child.selectedComponentId.length > 0 && isAssignmentNodeComplete(child, false), ) return ( piecesComplete && productsComplete && subcomponentsComplete && (isRootNode || !!node.selectedComponentId) ) } // --------------------------------------------------------------------------- // Serialization // --------------------------------------------------------------------------- export function stripNullish(input: Record) { return Object.fromEntries( Object.entries(input).filter( ([, value]) => value !== null && value !== undefined && value !== '', ), ) } export function sanitizeStructureDefinition( definition: ComponentModelStructureNode, ) { return stripNullish({ alias: definition.alias ?? null, typeComposantId: definition.typeComposantId ?? null, typeComposantLabel: definition.typeComposantLabel ?? null, modelId: definition.modelId ?? null, familyCode: (definition as any).familyCode ?? null, }) } export function sanitizePieceDefinition(definition: ComponentModelPiece) { return stripNullish({ role: (definition as any).role ?? null, typePieceId: definition.typePieceId ?? null, typePieceLabel: definition.typePieceLabel ?? null, reference: definition.reference ?? null, familyCode: (definition as any).familyCode ?? null, quantity: typeof (definition as any).quantity === 'number' ? (definition as any).quantity : null, }) } export function sanitizeProductDefinition(definition: ComponentModelProduct) { return stripNullish({ role: (definition as any).role ?? null, typeProductId: definition.typeProductId ?? null, typeProductLabel: (definition as any).typeProductLabel ?? null, reference: (definition as any).reference ?? null, familyCode: (definition as any).familyCode ?? null, }) } export function serializeStructureAssignments( root: StructureAssignmentNode | null, ) { if (!root) { return null } const serializeNode = ( assignment: StructureAssignmentNode, isRootNode = false, ): Record => { const serializedPieces = assignment.pieces .filter((piece) => !!piece.selectedPieceId) .map((piece) => stripNullish({ path: piece.path, definition: sanitizePieceDefinition(piece.definition), selectedPieceId: piece.selectedPieceId, }), ) const serializedProducts = assignment.products .filter((product) => !!product.selectedProductId) .map((product) => stripNullish({ path: product.path, definition: sanitizeProductDefinition(product.definition), selectedProductId: product.selectedProductId, }), ) const serializedSubcomponents = assignment.subcomponents .map((child) => serializeNode(child, false)) .filter((child) => Object.keys(child).length > 0) const base: Record = { path: assignment.path, definition: sanitizeStructureDefinition(assignment.definition), } if (!isRootNode) { base.selectedComponentId = assignment.selectedComponentId } if (serializedPieces.length) { base.pieces = serializedPieces } if (serializedProducts.length) { base.products = serializedProducts } if (serializedSubcomponents.length) { base.subcomponents = serializedSubcomponents } return stripNullish(base) } const serializedRoot = serializeNode(root, true) if ( (!serializedRoot.pieces || serializedRoot.pieces.length === 0) && (!serializedRoot.products || serializedRoot.products.length === 0) && (!serializedRoot.subcomponents || serializedRoot.subcomponents.length === 0) ) { return null } return serializedRoot }