Files
Inventory/app/shared/utils/structureAssignmentHelpers.ts
2026-03-12 12:08:43 +01:00

259 lines
7.8 KiB
TypeScript

/**
* 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<string, any>) {
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<string, any> => {
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<string, any> = {
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
}