WIP - Basic adding and deleting field
This commit is contained in:
@@ -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();
|
||||||
|
|
||||||
return knex('field_definitions').where({ id }).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 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) {
|
for (const layout of pageLayouts) {
|
||||||
const layoutConfig = layout.layout_config ? JSON.parse(layout.layout_config) : { fields: [] };
|
// Handle JSON column that might already be parsed
|
||||||
|
let layoutConfig;
|
||||||
|
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
|
// Filter out any field references for this field
|
||||||
if (layoutConfig.fields) {
|
if (layoutConfig.fields) {
|
||||||
layoutConfig.fields = layoutConfig.fields.filter(
|
layoutConfig.fields = layoutConfig.fields.filter(
|
||||||
(f: any) => f.fieldId !== field.id,
|
(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(
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user