854 lines
27 KiB
TypeScript
854 lines
27 KiB
TypeScript
import {
|
|
createEmptyComponentModelStructure,
|
|
type ComponentModelCustomFieldType,
|
|
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<string, unknown> => {
|
|
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 toStringArray = (input: unknown): string[] | undefined => {
|
|
if (!Array.isArray(input)) {
|
|
return undefined
|
|
}
|
|
const parsed = input
|
|
.map((value) => {
|
|
if (typeof value === 'string') {
|
|
return value.trim()
|
|
}
|
|
if (value === null || value === undefined) {
|
|
return ''
|
|
}
|
|
return String(value).trim()
|
|
})
|
|
.filter((value) => value.length > 0)
|
|
return parsed.length ? parsed : undefined
|
|
}
|
|
|
|
const extractFieldValueObject = (field: any): Record<string, any> => {
|
|
if (isPlainObject(field?.value)) {
|
|
return field.value as Record<string, any>
|
|
}
|
|
return {}
|
|
}
|
|
|
|
const sanitizeCustomFields = (fields: any[]): ComponentModelCustomField[] => {
|
|
if (!Array.isArray(fields)) {
|
|
return []
|
|
}
|
|
|
|
return fields
|
|
.map((field) => {
|
|
const rawName =
|
|
typeof field?.name === 'string'
|
|
? field.name
|
|
: typeof field?.key === 'string'
|
|
? field.key
|
|
: ''
|
|
const name = rawName.trim()
|
|
if (!name) {
|
|
return null
|
|
}
|
|
|
|
const valueObject = extractFieldValueObject(field)
|
|
|
|
const candidateType =
|
|
typeof field?.type === 'string' && field.type
|
|
? field.type
|
|
: typeof valueObject?.type === 'string'
|
|
? valueObject.type
|
|
: ''
|
|
const allowedTypes: ComponentModelCustomFieldType[] = ['text', 'number', 'select', 'boolean', 'date']
|
|
const type = allowedTypes.includes(candidateType as ComponentModelCustomFieldType)
|
|
? (candidateType as ComponentModelCustomFieldType)
|
|
: 'text'
|
|
|
|
const required =
|
|
typeof valueObject?.required === 'boolean' ? valueObject.required : !!field?.required
|
|
|
|
let options: string[] | undefined
|
|
if (type === 'select') {
|
|
options =
|
|
toStringArray(valueObject?.options) ||
|
|
toStringArray((valueObject as any)?.choices) ||
|
|
toStringArray(field?.options)
|
|
|
|
if (!options && typeof field?.optionsText === 'string') {
|
|
const parsedFromText = field.optionsText
|
|
.split(/\r?\n/)
|
|
.map((option) => option.trim())
|
|
.filter((option) => option.length > 0)
|
|
options = parsedFromText.length ? parsedFromText : undefined
|
|
}
|
|
}
|
|
|
|
const result: ComponentModelCustomField = { name, type, required }
|
|
if (options) {
|
|
result.options = options
|
|
}
|
|
const defaultCandidate =
|
|
field?.defaultValue ?? valueObject?.defaultValue ?? field?.value ?? field?.default ?? null
|
|
const resolvedDefault = (() => {
|
|
if (defaultCandidate === undefined || defaultCandidate === null) {
|
|
return undefined
|
|
}
|
|
if (typeof defaultCandidate === 'object') {
|
|
if (defaultCandidate === null) {
|
|
return undefined
|
|
}
|
|
if ('defaultValue' in (defaultCandidate as Record<string, any>)) {
|
|
return (defaultCandidate as Record<string, any>).defaultValue
|
|
}
|
|
if ('value' in (defaultCandidate as Record<string, any>)) {
|
|
return (defaultCandidate as Record<string, any>).value
|
|
}
|
|
return undefined
|
|
}
|
|
return defaultCandidate
|
|
})()
|
|
if (resolvedDefault !== undefined && resolvedDefault !== null && resolvedDefault !== '') {
|
|
result.defaultValue = String(resolvedDefault)
|
|
}
|
|
const id = typeof field?.id === 'string' ? field.id : undefined
|
|
if (id) {
|
|
result.id = id
|
|
}
|
|
const customFieldId = typeof field?.customFieldId === 'string' ? field.customFieldId : undefined
|
|
if (customFieldId) {
|
|
result.customFieldId = customFieldId
|
|
}
|
|
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
|
|
|
|
const rawFamilyCode = typeof piece?.familyCode === 'string'
|
|
? piece.familyCode.trim()
|
|
: typeof piece?.typePiece?.code === 'string'
|
|
? piece.typePiece.code.trim()
|
|
: ''
|
|
const familyCode = rawFamilyCode.length > 0 ? rawFamilyCode : undefined
|
|
|
|
const rawRole = typeof piece?.role === 'string' ? piece.role.trim() : ''
|
|
const role = rawRole.length > 0 ? rawRole : undefined
|
|
|
|
if (!typePieceId && !typePieceLabel && !reference && !familyCode) {
|
|
return null
|
|
}
|
|
|
|
const result: ComponentModelPiece = {}
|
|
if (role) {
|
|
result.role = role
|
|
}
|
|
if (familyCode) {
|
|
result.familyCode = familyCode
|
|
}
|
|
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 normalizeStructureForEditor = (input: any): ComponentModelStructure => {
|
|
const source = cloneStructure(input)
|
|
|
|
const sanitizedCustomFields = sanitizeCustomFields(source.customFields)
|
|
const customFields = sanitizedCustomFields.map((field) => {
|
|
const options = Array.isArray(field.options) ? [...field.options] : []
|
|
const optionsText = options.length ? options.join('\n') : ''
|
|
const defaultValue =
|
|
field.defaultValue !== undefined && field.defaultValue !== null && field.defaultValue !== ''
|
|
? String(field.defaultValue)
|
|
: null
|
|
const copy: ComponentModelCustomField = {
|
|
name: field.name,
|
|
type: field.type,
|
|
required: field.required,
|
|
options,
|
|
defaultValue,
|
|
optionsText,
|
|
id: field.id,
|
|
customFieldId: field.customFieldId,
|
|
}
|
|
return copy
|
|
})
|
|
|
|
const result: ComponentModelStructure = {
|
|
customFields: customFields as ComponentModelCustomField[],
|
|
pieces: sanitizePieces(source.pieces),
|
|
subcomponents: hydrateSubcomponents(source.subcomponents),
|
|
}
|
|
|
|
if (typeof source.typeComposantId === 'string' && source.typeComposantId.length > 0) {
|
|
result.typeComposantId = source.typeComposantId
|
|
}
|
|
if (typeof source.typeComposantLabel === 'string' && source.typeComposantLabel.length > 0) {
|
|
result.typeComposantLabel = source.typeComposantLabel
|
|
}
|
|
if (typeof source.modelId === 'string' && source.modelId.length > 0) {
|
|
result.modelId = source.modelId
|
|
}
|
|
if (typeof source.familyCode === 'string' && source.familyCode.length > 0) {
|
|
result.familyCode = source.familyCode
|
|
}
|
|
if (typeof source.alias === 'string' && source.alias.length > 0) {
|
|
result.alias = source.alias
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
export const normalizeStructureForSave = (input: any): any => {
|
|
const source = cloneStructure(input)
|
|
|
|
const sanitizedCustomFields = sanitizeCustomFields(source.customFields)
|
|
const backendCustomFields = sanitizedCustomFields.map((field) => {
|
|
const value: Record<string, any> = {
|
|
type: field.type,
|
|
required: !!field.required,
|
|
}
|
|
if (field.options && field.options.length) {
|
|
value.options = field.options
|
|
}
|
|
if (field.defaultValue !== undefined && field.defaultValue !== null && field.defaultValue !== '') {
|
|
value.defaultValue = field.defaultValue
|
|
}
|
|
const payload: Record<string, any> = {
|
|
key: field.name,
|
|
value,
|
|
}
|
|
if (field.id) {
|
|
payload.id = field.id
|
|
}
|
|
if (field.customFieldId) {
|
|
payload.customFieldId = field.customFieldId
|
|
}
|
|
return payload
|
|
}) as any
|
|
|
|
const backendPieces = sanitizePieces(source.pieces).map((piece) => {
|
|
const payload: Record<string, any> = {}
|
|
if ((piece as any).familyCode) {
|
|
payload.familyCode = (piece as any).familyCode
|
|
}
|
|
if (piece.typePieceId) {
|
|
payload.typePieceId = piece.typePieceId
|
|
}
|
|
if (piece.typePieceLabel) {
|
|
payload.typePieceLabel = piece.typePieceLabel
|
|
}
|
|
if (piece.reference) {
|
|
payload.reference = piece.reference
|
|
}
|
|
return payload
|
|
}) as any
|
|
|
|
const mapSubcomponentForSave = (subcomponent: ComponentModelStructureNode): any => {
|
|
const payload: Record<string, any> = {}
|
|
if (subcomponent.typeComposantId) {
|
|
payload.typeComposantId = subcomponent.typeComposantId
|
|
}
|
|
if (subcomponent.modelId) {
|
|
payload.modelId = subcomponent.modelId
|
|
}
|
|
if (subcomponent.familyCode) {
|
|
payload.familyCode = subcomponent.familyCode
|
|
}
|
|
if (subcomponent.alias) {
|
|
payload.alias = subcomponent.alias
|
|
}
|
|
if (Array.isArray(subcomponent.subcomponents) && subcomponent.subcomponents.length) {
|
|
payload.subcomponents = subcomponent.subcomponents.map(mapSubcomponentForSave)
|
|
}
|
|
return payload
|
|
}
|
|
|
|
const backendSubcomponents = sanitizeSubcomponents(source.subcomponents).map(mapSubcomponentForSave) as any
|
|
|
|
const result: ComponentModelStructure = {
|
|
customFields: backendCustomFields,
|
|
pieces: backendPieces,
|
|
subcomponents: backendSubcomponents,
|
|
}
|
|
|
|
if (typeof source.typeComposantId === 'string' && source.typeComposantId.length > 0) {
|
|
(result as any).typeComposantId = source.typeComposantId
|
|
}
|
|
if (typeof source.typeComposantLabel === 'string' && source.typeComposantLabel.length > 0) {
|
|
(result as any).typeComposantLabel = source.typeComposantLabel
|
|
}
|
|
if (typeof source.modelId === 'string' && source.modelId.length > 0) {
|
|
(result as any).modelId = source.modelId
|
|
}
|
|
if (typeof source.familyCode === 'string' && source.familyCode.length > 0) {
|
|
(result as any).familyCode = source.familyCode
|
|
}
|
|
if (typeof source.alias === 'string' && source.alias.length > 0) {
|
|
(result as any).alias = source.alias
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
const hydrateCustomFields = (fields: any[]): any[] => {
|
|
if (!Array.isArray(fields)) {
|
|
return []
|
|
}
|
|
|
|
return fields.map((field) => {
|
|
const valueObject = extractFieldValueObject(field)
|
|
const name = typeof field?.name === 'string'
|
|
? field.name
|
|
: typeof field?.key === 'string'
|
|
? field.key
|
|
: ''
|
|
|
|
const candidateType =
|
|
typeof field?.type === 'string' && field.type
|
|
? field.type
|
|
: typeof valueObject?.type === 'string'
|
|
? valueObject.type
|
|
: ''
|
|
const allowedTypes: ComponentModelCustomFieldType[] = ['text', 'number', 'select', 'boolean', 'date']
|
|
const type = allowedTypes.includes(candidateType as ComponentModelCustomFieldType)
|
|
? (candidateType as ComponentModelCustomFieldType)
|
|
: 'text'
|
|
|
|
const required =
|
|
typeof field?.required === 'boolean'
|
|
? field.required
|
|
: typeof valueObject?.required === 'boolean'
|
|
? valueObject.required
|
|
: false
|
|
|
|
const options =
|
|
toStringArray(field?.options) ||
|
|
toStringArray(valueObject?.options) ||
|
|
toStringArray((valueObject as any)?.choices) ||
|
|
[]
|
|
|
|
const optionsText = typeof field?.optionsText === 'string'
|
|
? field.optionsText
|
|
: options.length
|
|
? options.join('\n')
|
|
: ''
|
|
|
|
const defaultCandidate =
|
|
field?.defaultValue ?? valueObject?.defaultValue ?? field?.value ?? field?.default ?? null
|
|
const resolvedDefault = (() => {
|
|
if (defaultCandidate === undefined || defaultCandidate === null) {
|
|
return undefined
|
|
}
|
|
if (typeof defaultCandidate === 'object') {
|
|
if (defaultCandidate === null) {
|
|
return undefined
|
|
}
|
|
if ('defaultValue' in (defaultCandidate as Record<string, any>)) {
|
|
return (defaultCandidate as Record<string, any>).defaultValue
|
|
}
|
|
if ('value' in (defaultCandidate as Record<string, any>)) {
|
|
return (defaultCandidate as Record<string, any>).value
|
|
}
|
|
return undefined
|
|
}
|
|
return defaultCandidate
|
|
})()
|
|
const defaultValue =
|
|
resolvedDefault !== undefined && resolvedDefault !== null && resolvedDefault !== ''
|
|
? String(resolvedDefault)
|
|
: ''
|
|
|
|
const id = typeof field?.id === 'string' ? field.id : undefined
|
|
const customFieldId = typeof field?.customFieldId === 'string' ? field.customFieldId : undefined
|
|
|
|
return {
|
|
name,
|
|
type,
|
|
required,
|
|
options,
|
|
optionsText,
|
|
defaultValue,
|
|
id,
|
|
customFieldId,
|
|
}
|
|
})
|
|
}
|
|
|
|
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 ?? '',
|
|
familyCode: piece?.familyCode ?? piece?.typePiece?.code ?? '',
|
|
role: piece?.role ?? '',
|
|
}))
|
|
}
|
|
|
|
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 mapComponentCustomFields = (fields: any[]) => {
|
|
if (!Array.isArray(fields)) {
|
|
return []
|
|
}
|
|
return hydrateCustomFields(fields).map((field) => {
|
|
const defaultValue =
|
|
field?.defaultValue !== undefined && field?.defaultValue !== null && field?.defaultValue !== ''
|
|
? field.defaultValue
|
|
: null
|
|
return {
|
|
name: typeof field?.name === 'string' ? field.name : '',
|
|
type: field?.type ?? 'text',
|
|
required: !!field?.required,
|
|
options: Array.isArray(field?.options) ? field.options : [],
|
|
optionsText: typeof field?.optionsText === 'string' ? field.optionsText : '',
|
|
defaultValue,
|
|
id: typeof (field as any)?.id === 'string' ? (field as any).id : undefined,
|
|
customFieldId:
|
|
typeof (field as any)?.customFieldId === 'string'
|
|
? (field as any).customFieldId
|
|
: undefined,
|
|
}
|
|
})
|
|
}
|
|
|
|
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 ?? '',
|
|
familyCode: piece?.familyCode ?? piece?.typePiece?.code ?? '',
|
|
role: piece?.role ?? '',
|
|
}))
|
|
}
|
|
|
|
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 normalizeStructureForEditor(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<string, unknown>)) {
|
|
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: typeof field?.optionsText === 'string'
|
|
? field.optionsText
|
|
: Array.isArray(field?.options)
|
|
? field.options.join('\n')
|
|
: '',
|
|
}))
|
|
}
|
|
|
|
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)`
|
|
}
|