import { createEmptyComponentModelStructure, type ComponentModelCustomField, type ComponentModelPiece, type ComponentModelStructure, type ComponentModelStructureNode, type PieceModelCustomField, type PieceModelStructure, type PieceModelStructureEditorField, type PieceModelStructureForEditor, createEmptyPieceModelStructure, } from './types/inventory' export const isPlainObject = (value: unknown): value is Record => { return value !== null && typeof value === 'object' && !Array.isArray(value) } export interface ModelStructurePreview { customFields: number pieces: number subcomponents: number } export const defaultStructure = (): ComponentModelStructure => ({ ...createEmptyComponentModelStructure(), }) const ensureStructureShape = (input: any): ComponentModelStructure => { const base = createEmptyComponentModelStructure() if (!isPlainObject(input)) { return base } const clone: ComponentModelStructure = { ...base, customFields: Array.isArray((input as any).customFields) ? (input as any).customFields : [], pieces: Array.isArray((input as any).pieces) ? (input as any).pieces : [], subcomponents: Array.isArray((input as any).subcomponents) ? (input as any).subcomponents : Array.isArray((input as any).subComponents) ? (input as any).subComponents : [], typeComposantId: typeof (input as any).typeComposantId === 'string' ? (input as any).typeComposantId : undefined, typeComposantLabel: typeof (input as any).typeComposantLabel === 'string' ? (input as any).typeComposantLabel : undefined, modelId: typeof (input as any).modelId === 'string' ? (input as any).modelId : undefined, familyCode: typeof (input as any).familyCode === 'string' ? (input as any).familyCode : undefined, alias: typeof (input as any).alias === 'string' ? (input as any).alias : undefined, } return clone } export const cloneStructure = (input: any): ComponentModelStructure => { try { const cloned = JSON.parse(JSON.stringify(input ?? defaultStructure())) return ensureStructureShape(cloned) } catch (error) { return defaultStructure() } } const sanitizeCustomFields = (fields: any[]): ComponentModelCustomField[] => { if (!Array.isArray(fields)) { return [] } return fields .map((field) => { const name = typeof field?.name === 'string' ? field.name.trim() : '' if (!name) { return null } const type = typeof field?.type === 'string' && field.type ? field.type : 'text' const required = !!field?.required let options: string[] | undefined if (type === 'select') { const rawOptions = typeof field?.optionsText === 'string' ? field.optionsText : Array.isArray(field?.options) ? field.options.join('\n') : '' const parsed = rawOptions .split(/\r?\n/) .map((option) => option.trim()) .filter((option) => option.length > 0) options = parsed.length > 0 ? parsed : undefined } const result: ComponentModelCustomField = { name, type, required } if (options) { result.options = options } return result }) .filter((field): field is ComponentModelCustomField => !!field) } const sanitizePieces = (pieces: any[]): ComponentModelPiece[] => { if (!Array.isArray(pieces)) { return [] } return pieces .map((piece) => { const rawTypePieceId = typeof piece?.typePieceId === 'string' ? piece.typePieceId.trim() : typeof piece?.typePiece?.id === 'string' ? piece.typePiece.id.trim() : '' const typePieceId = rawTypePieceId.length > 0 ? rawTypePieceId : undefined const rawTypePieceLabel = typeof piece?.typePieceLabel === 'string' ? piece.typePieceLabel.trim() : typeof piece?.typePiece?.name === 'string' ? piece.typePiece.name.trim() : '' const typePieceLabel = rawTypePieceLabel.length > 0 ? rawTypePieceLabel : undefined const reference = typeof piece?.reference === 'string' && piece.reference.trim().length > 0 ? piece.reference.trim() : undefined if (!typePieceId && !typePieceLabel && !reference) { return null } const result: ComponentModelPiece = {} if (reference !== undefined) { result.reference = reference } if (typePieceId) { result.typePieceId = typePieceId } if (typePieceLabel) { result.typePieceLabel = typePieceLabel } return result }) .filter((piece): piece is ComponentModelPiece => !!piece) } const sanitizeSubcomponents = (components: any[]): ComponentModelStructureNode[] => { if (!Array.isArray(components)) { return [] } return components .map((component) => { const rawTypeComposantId = typeof component?.typeComposantId === 'string' ? component.typeComposantId.trim() : typeof component?.typeComposant?.id === 'string' ? component.typeComposant.id.trim() : '' const typeComposantId = rawTypeComposantId.length > 0 ? rawTypeComposantId : undefined const modelId = typeof component?.modelId === 'string' && component.modelId.trim().length > 0 ? component.modelId.trim() : undefined const familyCode = typeof component?.familyCode === 'string' && component.familyCode.trim().length > 0 ? component.familyCode.trim() : undefined const alias = typeof component?.alias === 'string' && component.alias.trim().length > 0 ? component.alias.trim() : undefined if (!typeComposantId && !modelId && !familyCode) { return null } const result: ComponentModelStructureNode = { subcomponents: sanitizeSubcomponents( Array.isArray(component?.subcomponents) ? component.subcomponents : component?.subComponents, ), } if (typeComposantId) { result.typeComposantId = typeComposantId } const typeComposantLabel = typeof component?.typeComposantLabel === 'string' ? component.typeComposantLabel.trim() : typeof component?.typeComposant?.name === 'string' ? component.typeComposant.name.trim() : '' if (typeComposantLabel) { result.typeComposantLabel = typeComposantLabel } if (modelId) { result.modelId = modelId } if (familyCode) { result.familyCode = familyCode } if (alias) { result.alias = alias } return result }) .filter((component): component is ComponentModelStructureNode => !!component) } export const normalizeStructureForSave = (input: any): ComponentModelStructure => { const source = cloneStructure(input) return { customFields: sanitizeCustomFields(source.customFields), pieces: sanitizePieces(source.pieces), subcomponents: sanitizeSubcomponents(source.subcomponents), typeComposantId: source.typeComposantId, typeComposantLabel: source.typeComposantLabel, modelId: source.modelId, familyCode: source.familyCode, alias: source.alias, } } const hydrateCustomFields = (fields: any[]): any[] => { if (!Array.isArray(fields)) { return [] } return fields.map((field) => ({ name: field?.name ?? '', type: field?.type ?? 'text', required: !!field?.required, options: Array.isArray(field?.options) ? field.options : [], optionsText: Array.isArray(field?.options) ? field.options.join('\n') : (field?.optionsText ?? ''), })) } const hydratePieces = (pieces: any[]): ComponentModelPiece[] => { if (!Array.isArray(pieces)) { return [] } return pieces.map((piece) => ({ typePieceId: piece?.typePieceId ?? piece?.typePiece?.id ?? '', typePieceLabel: piece?.typePieceLabel ?? piece?.typePiece?.name ?? '', reference: piece?.reference ?? '', })) } const hydrateSubcomponents = (components: any[]): ComponentModelStructureNode[] => { if (!Array.isArray(components)) { return [] } return components.map((component) => ({ typeComposantId: component?.typeComposantId ?? component?.typeComposant?.id ?? '', typeComposantLabel: component?.typeComposantLabel ?? component?.typeComposant?.name ?? '', modelId: component?.modelId ?? '', familyCode: component?.familyCode ?? component?.typeComposant?.code ?? '', alias: component?.alias ?? component?.name ?? '', subcomponents: hydrateSubcomponents( Array.isArray(component?.subcomponents) ? component.subcomponents : component?.subComponents, ), })) } export const hydrateStructureForEditor = (input: any): ComponentModelStructure => { const source = cloneStructure(input) return { customFields: hydrateCustomFields(source.customFields), pieces: hydratePieces(source.pieces), subcomponents: hydrateSubcomponents( Array.isArray(source.subcomponents) ? source.subcomponents : (source as any).subComponents, ), typeComposantId: source.typeComposantId ?? '', typeComposantLabel: source.typeComposantLabel ?? '', modelId: source.modelId ?? '', familyCode: source.familyCode ?? '', alias: source.alias ?? '', } } const toOptionsText = (field: any) => { if (typeof field?.optionsText === 'string') { return field.optionsText } if (Array.isArray(field?.options)) { return field.options.join('\n') } return '' } const mapComponentCustomFields = (fields: any[]) => { if (!Array.isArray(fields)) { return [] } return fields.map((field) => ({ name: field?.name ?? '', type: field?.type ?? 'text', required: !!field?.required, options: Array.isArray(field?.options) ? field.options : [], optionsText: toOptionsText(field), })) } const mapComponentPieces = (pieces: any[]): ComponentModelPiece[] => { if (!Array.isArray(pieces)) { return [] } return pieces.map((piece) => ({ reference: piece?.reference ?? '', typePieceId: piece?.typePieceId ?? piece?.typePiece?.id ?? '', typePieceLabel: piece?.typePieceLabel ?? piece?.typePiece?.name ?? '', })) } const mapSubcomponents = (components: any[]): ComponentModelStructureNode[] => { if (!Array.isArray(components)) { return [] } return components.map((component) => ({ typeComposantId: component?.typeComposantId ?? component?.typeComposant?.id ?? '', typeComposantLabel: component?.typeComposantLabel ?? component?.typeComposant?.name ?? '', modelId: component?.modelId ?? '', familyCode: component?.familyCode ?? component?.typeComposant?.code ?? '', alias: component?.alias ?? component?.name ?? '', subcomponents: mapSubcomponents( Array.isArray(component?.subcomponents) ? component.subcomponents : component?.subComponents, ), })) } export const extractStructureFromComponent = (component: any) => { if (!component) { return defaultStructure() } const raw = { customFields: mapComponentCustomFields(component.customFields), pieces: mapComponentPieces(component.pieces), subcomponents: mapSubcomponents( Array.isArray(component?.subcomponents) ? component.subcomponents : component?.subComponents, ), typeComposantId: component?.typeComposantId ?? component?.typeComposant?.id ?? '', typeComposantLabel: component?.typeComposant?.name ?? component?.typeComposantLabel ?? '', modelId: component?.modelId ?? '', familyCode: component?.familyCode ?? component?.typeComposant?.code ?? '', alias: component?.alias ?? component?.name ?? '', } return normalizeStructureForSave(raw) } export const computeStructureStats = (structure: any): ModelStructurePreview => { if (!structure || typeof structure !== 'object') { return { customFields: 0, pieces: 0, subcomponents: 0 } } return { customFields: Array.isArray(structure.customFields) ? structure.customFields.length : 0, pieces: Array.isArray(structure.pieces) ? structure.pieces.length : 0, subcomponents: Array.isArray(structure.subcomponents) ? structure.subcomponents.length : Array.isArray(structure.subComponents) ? structure.subComponents.length : 0, } } export const formatStructurePreview = (structure: any) => { const stats = computeStructureStats(structure) if (!stats.customFields && !stats.pieces && !stats.subcomponents) { return 'Structure vide' } const segments: string[] = [] if (stats.customFields) segments.push(`${stats.customFields} champ(s)`) if (stats.pieces) segments.push(`${stats.pieces} pièce(s)`) if (stats.subcomponents) segments.push(`${stats.subcomponents} sous-composant(s)`) return segments.join(' • ') } export interface DefinitionOverridePayload { name?: string reference?: string constructeurId?: string | null prix?: number } export const sanitizeDefinitionOverrides = (definition: any): DefinitionOverridePayload | null => { if (!definition || typeof definition !== 'object') { return null } const payload: DefinitionOverridePayload = {} if (typeof definition.name === 'string') { const name = definition.name.trim() if (name.length > 0) { payload.name = name } } if (typeof definition.reference === 'string') { const reference = definition.reference.trim() if (reference.length > 0) { payload.reference = reference } } if (definition.constructeurId !== undefined && definition.constructeurId !== null && definition.constructeurId !== '') { payload.constructeurId = definition.constructeurId } if (definition.prix !== undefined && definition.prix !== null && definition.prix !== '') { const parsed = Number(definition.prix) if (!Number.isNaN(parsed)) { payload.prix = parsed } } return Object.keys(payload).length ? payload : null } export const defaultPieceStructure = (): PieceModelStructure => ({ ...createEmptyPieceModelStructure(), }) const ensurePieceStructureShape = (input: any): PieceModelStructure => { const base = createEmptyPieceModelStructure() if (!isPlainObject(input)) { return base } const clone: PieceModelStructure = { ...base, customFields: Array.isArray((input as any).customFields) ? (input as any).customFields : [], } for (const [key, value] of Object.entries(input as Record)) { if (key === 'customFields') { continue } clone[key] = value } return clone } export const clonePieceStructure = (input: any): PieceModelStructure => { try { const cloned = JSON.parse(JSON.stringify(input ?? defaultPieceStructure())) return ensurePieceStructureShape(cloned) } catch (error) { return defaultPieceStructure() } } const sanitizePieceCustomFields = (fields: any[]): PieceModelCustomField[] => { if (!Array.isArray(fields)) { return [] } return fields .map((field) => { const name = typeof field?.name === 'string' ? field.name.trim() : '' if (!name) { return null } const type = typeof field?.type === 'string' && field.type ? field.type : 'text' const required = !!field?.required let options: string[] | undefined if (type === 'select') { const rawOptions = typeof field?.optionsText === 'string' ? field.optionsText : Array.isArray(field?.options) ? field.options.join('\n') : '' const parsed = rawOptions .split(/\r?\n/) .map((option) => option.trim()) .filter((option) => option.length > 0) options = parsed.length > 0 ? parsed : undefined } const result: PieceModelCustomField = { name, type, required } if (options) { result.options = options } return result }) .filter((field): field is PieceModelCustomField => !!field) } export const normalizePieceStructureForSave = (input: any): PieceModelStructure => { const source = clonePieceStructure(input) return { ...Object.fromEntries( Object.entries(source).filter(([key]) => key !== 'customFields'), ), customFields: sanitizePieceCustomFields(source.customFields), } } const hydratePieceCustomFields = (fields: any[]): PieceModelStructureEditorField[] => { if (!Array.isArray(fields)) { return [] } return fields.map((field) => ({ name: field?.name ?? '', type: field?.type ?? 'text', required: !!field?.required, options: Array.isArray(field?.options) ? field.options : undefined, optionsText: Array.isArray(field?.options) ? field.options.join('\n') : (field?.optionsText ?? ''), })) } export const hydratePieceStructureForEditor = (input: any): PieceModelStructureForEditor => { const source = clonePieceStructure(input) const payload: PieceModelStructureForEditor = { ...Object.fromEntries( Object.entries(source).filter(([key]) => key !== 'customFields'), ), customFields: hydratePieceCustomFields(source.customFields), } return payload } export const formatPieceStructurePreview = (structure: any) => { if (!structure || typeof structure !== 'object') { return 'Aucun champ personnalisé' } const customFields = Array.isArray((structure as any).customFields) ? (structure as any).customFields.length : 0 if (!customFields) { return 'Aucun champ personnalisé' } return `${customFields} champ(s) personnalisé(s)` }