WIP - Basic adding and deleting field

This commit is contained in:
Francisco Gaona
2026-01-06 10:01:02 +01:00
parent 8e4690c9c9
commit 6c73eb1658
3 changed files with 128 additions and 27 deletions

View File

@@ -271,6 +271,10 @@ export class ObjectService {
relationObject?: string; relationObject?: string;
relationDisplayField?: string; relationDisplayField?: string;
defaultValue?: string; defaultValue?: string;
length?: number;
precision?: number;
scale?: number;
uiMetadata?: any;
}, },
) { ) {
const resolvedTenantId = await this.tenantDbService.resolveTenantId(tenantId); const resolvedTenantId = await this.tenantDbService.resolveTenantId(tenantId);
@@ -283,8 +287,11 @@ export class ObjectService {
// Use relationObject if provided (alias for referenceObject) // Use relationObject if provided (alias for referenceObject)
const referenceObject = data.referenceObject || data.relationObject; const referenceObject = data.referenceObject || data.relationObject;
// Generate UUID in Node.js instead of using MySQL UUID() function
const fieldId = require('crypto').randomUUID();
const fieldData: any = { const fieldData: any = {
id: knex.raw('(UUID())'), id: fieldId,
objectDefinitionId: obj.id, objectDefinitionId: obj.id,
apiName: data.apiName, apiName: data.apiName,
label: data.label, label: data.label,
@@ -294,20 +301,41 @@ export class ObjectService {
isUnique: data.isUnique ?? false, isUnique: data.isUnique ?? false,
referenceObject: referenceObject, referenceObject: referenceObject,
defaultValue: data.defaultValue, defaultValue: data.defaultValue,
length: data.length,
precision: data.precision,
scale: data.scale,
created_at: knex.fn.now(), created_at: knex.fn.now(),
updated_at: knex.fn.now(), updated_at: knex.fn.now(),
}; };
// Store relationDisplayField in UI metadata if provided // Merge UI metadata
const uiMetadata: any = {};
if (data.relationDisplayField) { if (data.relationDisplayField) {
fieldData.ui_metadata = JSON.stringify({ uiMetadata.relationDisplayField = data.relationDisplayField;
relationDisplayField: data.relationDisplayField, }
}); if (data.uiMetadata) {
Object.assign(uiMetadata, data.uiMetadata);
}
if (Object.keys(uiMetadata).length > 0) {
fieldData.ui_metadata = JSON.stringify(uiMetadata);
} }
const [id] = await knex('field_definitions').insert(fieldData); await knex('field_definitions').insert(fieldData);
const createdField = await knex('field_definitions').where({ id: fieldId }).first();
// Add the column to the physical table
const schemaManagementService = new SchemaManagementService();
try {
await schemaManagementService.addFieldToTable(knex, objectApiName, createdField);
this.logger.log(`Added column ${data.apiName} to table for object ${objectApiName}`);
} catch (error) {
// If column creation fails, delete the field definition to maintain consistency
this.logger.error(`Failed to add column ${data.apiName}: ${error.message}`);
await knex('field_definitions').where({ id: fieldId }).delete();
throw new Error(`Failed to create field column: ${error.message}`);
}
return knex('field_definitions').where({ id }).first(); return createdField;
} }
// Helper to get table name from object definition // Helper to get table name from object definition
@@ -874,26 +902,39 @@ export class ObjectService {
} }
// Clean up page layouts - remove field references from layoutConfig // Clean up page layouts - remove field references from layoutConfig
const pageLayouts = await knex('page_layouts') try {
.where({ objectDefinitionId: objectDef.id }); const pageLayouts = await knex('page_layouts')
.where({ object_id: objectDef.id });
for (const layout of pageLayouts) {
const layoutConfig = layout.layout_config ? JSON.parse(layout.layout_config) : { fields: [] };
// Filter out any field references for this field for (const layout of pageLayouts) {
if (layoutConfig.fields) { // Handle JSON column that might already be parsed
layoutConfig.fields = layoutConfig.fields.filter( let layoutConfig;
(f: any) => f.fieldId !== field.id, if (layout.layout_config) {
); layoutConfig = typeof layout.layout_config === 'string'
? JSON.parse(layout.layout_config)
: layout.layout_config;
} else {
layoutConfig = { fields: [] };
}
// Filter out any field references for this field
if (layoutConfig.fields) {
layoutConfig.fields = layoutConfig.fields.filter(
(f: any) => f.fieldId !== field.id,
);
}
// Update the page layout
await knex('page_layouts')
.where({ id: layout.id })
.update({
layout_config: JSON.stringify(layoutConfig),
updated_at: knex.fn.now(),
});
} }
} catch (error) {
// Update the page layout // If page layouts table doesn't exist or query fails, log but continue
await knex('page_layouts') this.logger.warn(`Could not update page layouts for field deletion: ${error.message}`);
.where({ id: layout.id })
.update({
layout_config: JSON.stringify(layoutConfig),
updated_at: knex.fn.now(),
});
} }
// Clean up dependsOn references in other fields // Clean up dependsOn references in other fields
@@ -902,7 +943,15 @@ export class ObjectService {
.whereNot({ id: field.id }); .whereNot({ id: field.id });
for (const otherField of otherFields) { for (const otherField of otherFields) {
const metadata = otherField.ui_metadata ? JSON.parse(otherField.ui_metadata) : {}; // Handle JSON column that might already be parsed
let metadata;
if (otherField.ui_metadata) {
metadata = typeof otherField.ui_metadata === 'string'
? JSON.parse(otherField.ui_metadata)
: otherField.ui_metadata;
} else {
metadata = {};
}
if (metadata.dependsOn && Array.isArray(metadata.dependsOn)) { if (metadata.dependsOn && Array.isArray(metadata.dependsOn)) {
metadata.dependsOn = metadata.dependsOn.filter( metadata.dependsOn = metadata.dependsOn.filter(

View File

@@ -125,15 +125,30 @@ export class SchemaManagementService {
let column: Knex.ColumnBuilder; let column: Knex.ColumnBuilder;
switch (field.type) { switch (field.type) {
// Text types
case 'String': case 'String':
case 'TEXT':
case 'EMAIL':
case 'PHONE':
case 'URL':
column = table.string(columnName, field.length || 255); column = table.string(columnName, field.length || 255);
break; break;
case 'Text': case 'Text':
case 'LONG_TEXT':
column = table.text(columnName); column = table.text(columnName);
break; break;
case 'PICKLIST':
case 'MULTI_PICKLIST':
column = table.string(columnName, 255);
break;
// Numeric types
case 'Number': case 'Number':
case 'NUMBER':
case 'CURRENCY':
case 'PERCENT':
if (field.scale && field.scale > 0) { if (field.scale && field.scale > 0) {
column = table.decimal( column = table.decimal(
columnName, columnName,
@@ -146,18 +161,28 @@ export class SchemaManagementService {
break; break;
case 'Boolean': case 'Boolean':
case 'BOOLEAN':
column = table.boolean(columnName).defaultTo(false); column = table.boolean(columnName).defaultTo(false);
break; break;
// Date types
case 'Date': case 'Date':
case 'DATE':
column = table.date(columnName); column = table.date(columnName);
break; break;
case 'DateTime': case 'DateTime':
case 'DATE_TIME':
column = table.datetime(columnName); column = table.datetime(columnName);
break; break;
case 'TIME':
column = table.time(columnName);
break;
// Relationship types
case 'Reference': case 'Reference':
case 'LOOKUP':
column = table.uuid(columnName); column = table.uuid(columnName);
if (field.referenceObject) { if (field.referenceObject) {
const refTableName = this.getTableName(field.referenceObject); const refTableName = this.getTableName(field.referenceObject);
@@ -165,19 +190,30 @@ export class SchemaManagementService {
} }
break; break;
// Email (legacy)
case 'Email': case 'Email':
column = table.string(columnName, 255); column = table.string(columnName, 255);
break; break;
// Phone (legacy)
case 'Phone': case 'Phone':
column = table.string(columnName, 50); column = table.string(columnName, 50);
break; break;
// Url (legacy)
case 'Url': case 'Url':
column = table.string(columnName, 255); column = table.string(columnName, 255);
break; break;
// File types
case 'FILE':
case 'IMAGE':
column = table.text(columnName); // Store file path or URL
break;
// JSON
case 'Json': case 'Json':
case 'JSON':
column = table.json(columnName); column = table.json(columnName);
break; break;

View File

@@ -560,13 +560,29 @@ const saveField = async () => {
defaultValue: fieldForm.value.defaultValue, defaultValue: fieldForm.value.defaultValue,
} }
// Extract type-specific database fields
const typeAttrs = fieldForm.value.typeAttributes || {}
// For text fields
if (fieldForm.value.type === 'text' && typeAttrs.maxLength) {
payload.length = typeAttrs.maxLength
}
// For number and currency fields
if ((fieldForm.value.type === 'number' || fieldForm.value.type === 'currency') && typeAttrs.scale !== undefined) {
payload.scale = typeAttrs.scale
if (typeAttrs.scale > 0) {
payload.precision = 10 // Default precision for decimals
}
}
// Merge UI metadata // Merge UI metadata
const uiMetadata: any = { const uiMetadata: any = {
placeholder: fieldForm.value.placeholder, placeholder: fieldForm.value.placeholder,
helpText: fieldForm.value.helpText, helpText: fieldForm.value.helpText,
} }
// Add type-specific attributes // Add type-specific attributes to UI metadata
if (fieldForm.value.typeAttributes) { if (fieldForm.value.typeAttributes) {
Object.assign(uiMetadata, fieldForm.value.typeAttributes) Object.assign(uiMetadata, fieldForm.value.typeAttributes)
} }