The sanitize layer was reconstructing the field object without machineContextOnly, causing it to always be false in the save payload regardless of checkbox state. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
321 lines
10 KiB
TypeScript
321 lines
10 KiB
TypeScript
import type {
|
|
ComponentModelCustomField,
|
|
ComponentModelCustomFieldType,
|
|
ComponentModelPiece,
|
|
ComponentModelProduct,
|
|
ComponentModelStructureNode,
|
|
} from '../types/inventory'
|
|
// Inline helper to avoid circular dependency with componentStructure.ts
|
|
const isPlainObject = (value: unknown): value is Record<string, unknown> => {
|
|
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
}
|
|
|
|
export 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
|
|
}
|
|
|
|
export const extractFieldValueObject = (field: any): Record<string, any> => {
|
|
if (isPlainObject(field?.value)) {
|
|
return field.value as Record<string, any>
|
|
}
|
|
return {}
|
|
}
|
|
|
|
export const sanitizeCustomFields = (fields: any[]): ComponentModelCustomField[] => {
|
|
if (!Array.isArray(fields)) {
|
|
return []
|
|
}
|
|
|
|
return fields
|
|
.map((field, index) => {
|
|
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: string) => option.trim())
|
|
.filter((option: string) => option.length > 0)
|
|
options = parsedFromText.length ? parsedFromText : undefined
|
|
}
|
|
}
|
|
|
|
const machineContextOnly =
|
|
typeof valueObject?.machineContextOnly === 'boolean' ? valueObject.machineContextOnly : !!field?.machineContextOnly
|
|
|
|
const result: ComponentModelCustomField = { name, type, required, machineContextOnly }
|
|
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
|
|
}
|
|
const orderIndex = typeof field?.orderIndex === 'number' ? field.orderIndex : index
|
|
result.orderIndex = orderIndex
|
|
return result
|
|
})
|
|
.filter((field): field is ComponentModelCustomField => !!field)
|
|
}
|
|
|
|
export 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
|
|
|
|
const quantity = typeof piece?.quantity === 'number' && piece.quantity >= 1 ? piece.quantity : 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
|
|
}
|
|
if (quantity !== undefined) {
|
|
result.quantity = quantity
|
|
}
|
|
return result
|
|
})
|
|
.filter((piece): piece is ComponentModelPiece => !!piece)
|
|
}
|
|
|
|
export const sanitizeProducts = (products: any[]): ComponentModelProduct[] => {
|
|
if (!Array.isArray(products)) {
|
|
return []
|
|
}
|
|
|
|
return products
|
|
.map((product) => {
|
|
const rawTypeProductId = typeof product?.typeProductId === 'string'
|
|
? product.typeProductId.trim()
|
|
: typeof product?.typeProduct?.id === 'string'
|
|
? product.typeProduct.id.trim()
|
|
: ''
|
|
const typeProductId = rawTypeProductId.length > 0 ? rawTypeProductId : undefined
|
|
|
|
const rawTypeProductLabel = typeof product?.typeProductLabel === 'string'
|
|
? product.typeProductLabel.trim()
|
|
: typeof product?.typeProduct?.name === 'string'
|
|
? product.typeProduct.name.trim()
|
|
: ''
|
|
const typeProductLabel = rawTypeProductLabel.length > 0 ? rawTypeProductLabel : undefined
|
|
|
|
const reference = typeof product?.reference === 'string' && product.reference.trim().length > 0
|
|
? product.reference.trim()
|
|
: undefined
|
|
|
|
const rawFamilyCode = typeof product?.familyCode === 'string'
|
|
? product.familyCode.trim()
|
|
: typeof product?.typeProduct?.code === 'string'
|
|
? product.typeProduct.code.trim()
|
|
: ''
|
|
const familyCode = rawFamilyCode.length > 0 ? rawFamilyCode : undefined
|
|
|
|
const rawRole = typeof product?.role === 'string' ? product.role.trim() : ''
|
|
const role = rawRole.length > 0 ? rawRole : undefined
|
|
|
|
if (!typeProductId && !typeProductLabel && !reference && !familyCode) {
|
|
return null
|
|
}
|
|
|
|
const result: ComponentModelProduct = {}
|
|
if (role) {
|
|
result.role = role
|
|
}
|
|
if (familyCode) {
|
|
result.familyCode = familyCode
|
|
}
|
|
if (reference !== undefined) {
|
|
result.reference = reference
|
|
}
|
|
if (typeProductId) {
|
|
result.typeProductId = typeProductId
|
|
}
|
|
if (typeProductLabel) {
|
|
result.typeProductLabel = typeProductLabel
|
|
}
|
|
return result
|
|
})
|
|
.filter((product): product is ComponentModelProduct => !!product)
|
|
}
|
|
|
|
export 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)
|
|
}
|