334 lines
8.6 KiB
TypeScript
334 lines
8.6 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { FieldDefinition } from '../models/field-definition.model';
|
|
|
|
export interface FieldConfigDTO {
|
|
id: string;
|
|
apiName: string;
|
|
label: string;
|
|
type: string;
|
|
placeholder?: string;
|
|
helpText?: string;
|
|
defaultValue?: any;
|
|
isRequired?: boolean;
|
|
isReadOnly?: boolean;
|
|
showOnList?: boolean;
|
|
showOnDetail?: boolean;
|
|
showOnEdit?: boolean;
|
|
sortable?: boolean;
|
|
options?: Array<{ label: string; value: any }>;
|
|
rows?: number;
|
|
min?: number;
|
|
max?: number;
|
|
step?: number;
|
|
accept?: string;
|
|
relationObject?: string;
|
|
relationDisplayField?: string;
|
|
format?: string;
|
|
prefix?: string;
|
|
suffix?: string;
|
|
validationRules?: Array<{
|
|
type: string;
|
|
value?: any;
|
|
message?: string;
|
|
}>;
|
|
dependsOn?: string[];
|
|
computedValue?: string;
|
|
}
|
|
|
|
export interface ObjectDefinitionDTO {
|
|
id: string;
|
|
apiName: string;
|
|
label: string;
|
|
pluralLabel?: string;
|
|
description?: string;
|
|
isSystem: boolean;
|
|
fields: FieldConfigDTO[];
|
|
relatedLists?: Array<{
|
|
title: string;
|
|
relationName: string;
|
|
objectApiName: string;
|
|
fields: FieldConfigDTO[];
|
|
canCreate?: boolean;
|
|
createRoute?: string;
|
|
}>;
|
|
}
|
|
|
|
@Injectable()
|
|
export class FieldMapperService {
|
|
/**
|
|
* Convert a field definition from the database to a frontend-friendly FieldConfig
|
|
*/
|
|
mapFieldToDTO(field: any): FieldConfigDTO {
|
|
// Parse ui_metadata if it's a JSON string or object
|
|
let uiMetadata: any = {};
|
|
const metadataField = field.ui_metadata || field.uiMetadata;
|
|
if (metadataField) {
|
|
if (typeof metadataField === 'string') {
|
|
try {
|
|
uiMetadata = JSON.parse(metadataField);
|
|
} catch (e) {
|
|
uiMetadata = {};
|
|
}
|
|
} else {
|
|
uiMetadata = metadataField;
|
|
}
|
|
}
|
|
|
|
const frontendType = this.mapFieldType(field.type);
|
|
const isLookupField = frontendType === 'belongsTo' || field.type.toLowerCase().includes('lookup');
|
|
|
|
return {
|
|
id: field.id,
|
|
apiName: field.apiName,
|
|
label: field.label,
|
|
type: frontendType,
|
|
|
|
// Display properties
|
|
placeholder: uiMetadata.placeholder || field.description,
|
|
helpText: uiMetadata.helpText || field.description,
|
|
defaultValue: field.defaultValue,
|
|
|
|
// Validation
|
|
isRequired: field.isRequired || false,
|
|
isReadOnly: field.isSystem || uiMetadata.isReadOnly || false,
|
|
|
|
// View visibility
|
|
showOnList: uiMetadata.showOnList !== false,
|
|
showOnDetail: uiMetadata.showOnDetail !== false,
|
|
showOnEdit: uiMetadata.showOnEdit !== false && !field.isSystem,
|
|
sortable: uiMetadata.sortable !== false,
|
|
|
|
// Field type specific options
|
|
options: uiMetadata.options,
|
|
rows: uiMetadata.rows,
|
|
min: uiMetadata.min,
|
|
max: uiMetadata.max,
|
|
step: uiMetadata.step,
|
|
accept: uiMetadata.accept,
|
|
relationObject: field.referenceObject,
|
|
// For lookup fields, provide default display field if not specified
|
|
relationDisplayField: isLookupField
|
|
? (uiMetadata.relationDisplayField || 'name')
|
|
: uiMetadata.relationDisplayField,
|
|
|
|
// Formatting
|
|
format: uiMetadata.format,
|
|
prefix: uiMetadata.prefix,
|
|
suffix: uiMetadata.suffix,
|
|
|
|
// Validation rules
|
|
validationRules: this.buildValidationRules(field, uiMetadata),
|
|
|
|
// Advanced
|
|
dependsOn: uiMetadata.dependsOn,
|
|
computedValue: uiMetadata.computedValue,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Map database field type to frontend FieldType enum
|
|
*/
|
|
private mapFieldType(dbType: string): string {
|
|
const typeMap: Record<string, string> = {
|
|
'string': 'text',
|
|
'text': 'textarea',
|
|
'integer': 'number',
|
|
'decimal': 'number',
|
|
'boolean': 'boolean',
|
|
'date': 'date',
|
|
'datetime': 'datetime',
|
|
'time': 'time',
|
|
'email': 'email',
|
|
'url': 'url',
|
|
'phone': 'text',
|
|
'picklist': 'select',
|
|
'multipicklist': 'multiSelect',
|
|
'lookup': 'belongsTo',
|
|
'master-detail': 'belongsTo',
|
|
'currency': 'currency',
|
|
'percent': 'number',
|
|
'textarea': 'textarea',
|
|
'richtext': 'markdown',
|
|
'file': 'file',
|
|
'image': 'image',
|
|
'json': 'json',
|
|
};
|
|
|
|
return typeMap[dbType.toLowerCase()] || 'text';
|
|
}
|
|
|
|
/**
|
|
* Build validation rules array
|
|
*/
|
|
private buildValidationRules(field: any, uiMetadata: any): Array<any> {
|
|
const rules = uiMetadata.validationRules || [];
|
|
|
|
// Add required rule if field is required and not already in rules
|
|
if (field.isRequired && !rules.some(r => r.type === 'required')) {
|
|
rules.unshift({
|
|
type: 'required',
|
|
message: `${field.label} is required`,
|
|
});
|
|
}
|
|
|
|
// Add length validation for string fields
|
|
if (field.length && field.type === 'string') {
|
|
rules.push({
|
|
type: 'max',
|
|
value: field.length,
|
|
message: `${field.label} must not exceed ${field.length} characters`,
|
|
});
|
|
}
|
|
|
|
// Add email validation
|
|
if (field.type === 'email' && !rules.some(r => r.type === 'email')) {
|
|
rules.push({
|
|
type: 'email',
|
|
message: `${field.label} must be a valid email address`,
|
|
});
|
|
}
|
|
|
|
// Add URL validation
|
|
if (field.type === 'url' && !rules.some(r => r.type === 'url')) {
|
|
rules.push({
|
|
type: 'url',
|
|
message: `${field.label} must be a valid URL`,
|
|
});
|
|
}
|
|
|
|
return rules;
|
|
}
|
|
|
|
/**
|
|
* Convert object definition with fields to DTO
|
|
*/
|
|
mapObjectDefinitionToDTO(objectDef: any): ObjectDefinitionDTO {
|
|
return {
|
|
id: objectDef.id,
|
|
apiName: objectDef.apiName,
|
|
label: objectDef.label,
|
|
pluralLabel: objectDef.pluralLabel,
|
|
description: objectDef.description,
|
|
isSystem: objectDef.isSystem || false,
|
|
fields: (objectDef.fields || [])
|
|
.filter((f: any) => f.isActive !== false)
|
|
.sort((a: any, b: any) => (a.displayOrder || 0) - (b.displayOrder || 0))
|
|
.map((f: any) => this.mapFieldToDTO(f)),
|
|
relatedLists: (objectDef.relatedLists || []).map((list: any) => ({
|
|
title: list.title,
|
|
relationName: list.relationName,
|
|
objectApiName: list.objectApiName,
|
|
fields: (list.fields || [])
|
|
.filter((f: any) => f.isActive !== false)
|
|
.map((f: any) => this.mapFieldToDTO(f))
|
|
.filter((f: any) => f.showOnList !== false),
|
|
canCreate: list.canCreate,
|
|
createRoute: list.createRoute,
|
|
})),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Generate default UI metadata for a field type
|
|
*/
|
|
generateDefaultUIMetadata(fieldType: string): any {
|
|
const defaults: Record<string, any> = {
|
|
text: {
|
|
showOnList: true,
|
|
showOnDetail: true,
|
|
showOnEdit: true,
|
|
sortable: true,
|
|
},
|
|
textarea: {
|
|
showOnList: false,
|
|
showOnDetail: true,
|
|
showOnEdit: true,
|
|
sortable: false,
|
|
rows: 4,
|
|
},
|
|
number: {
|
|
showOnList: true,
|
|
showOnDetail: true,
|
|
showOnEdit: true,
|
|
sortable: true,
|
|
},
|
|
currency: {
|
|
showOnList: true,
|
|
showOnDetail: true,
|
|
showOnEdit: true,
|
|
sortable: true,
|
|
prefix: '$',
|
|
step: 0.01,
|
|
},
|
|
boolean: {
|
|
showOnList: true,
|
|
showOnDetail: true,
|
|
showOnEdit: true,
|
|
sortable: true,
|
|
},
|
|
date: {
|
|
showOnList: true,
|
|
showOnDetail: true,
|
|
showOnEdit: true,
|
|
sortable: true,
|
|
format: 'yyyy-MM-dd',
|
|
},
|
|
datetime: {
|
|
showOnList: false,
|
|
showOnDetail: true,
|
|
showOnEdit: true,
|
|
sortable: true,
|
|
format: 'yyyy-MM-dd HH:mm:ss',
|
|
},
|
|
email: {
|
|
showOnList: true,
|
|
showOnDetail: true,
|
|
showOnEdit: true,
|
|
sortable: true,
|
|
validationRules: [{ type: 'email' }],
|
|
},
|
|
url: {
|
|
showOnList: false,
|
|
showOnDetail: true,
|
|
showOnEdit: true,
|
|
sortable: false,
|
|
validationRules: [{ type: 'url' }],
|
|
},
|
|
select: {
|
|
showOnList: true,
|
|
showOnDetail: true,
|
|
showOnEdit: true,
|
|
sortable: true,
|
|
options: [],
|
|
},
|
|
multiSelect: {
|
|
showOnList: false,
|
|
showOnDetail: true,
|
|
showOnEdit: true,
|
|
sortable: false,
|
|
options: [],
|
|
},
|
|
image: {
|
|
showOnList: false,
|
|
showOnDetail: true,
|
|
showOnEdit: true,
|
|
sortable: false,
|
|
accept: 'image/*',
|
|
},
|
|
file: {
|
|
showOnList: false,
|
|
showOnDetail: true,
|
|
showOnEdit: true,
|
|
sortable: false,
|
|
},
|
|
};
|
|
|
|
return defaults[fieldType] || {
|
|
showOnList: true,
|
|
showOnDetail: true,
|
|
showOnEdit: true,
|
|
sortable: true,
|
|
};
|
|
}
|
|
}
|