@memberjunction/codegen-lib 5.2.0 → 5.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Config/config.d.ts.map +1 -1
- package/dist/Config/config.js +11 -6
- package/dist/Config/config.js.map +1 -1
- package/dist/Database/manage-metadata.d.ts +12 -4
- package/dist/Database/manage-metadata.d.ts.map +1 -1
- package/dist/Database/manage-metadata.js +230 -153
- package/dist/Database/manage-metadata.js.map +1 -1
- package/dist/Database/sql_codegen.d.ts +24 -1
- package/dist/Database/sql_codegen.d.ts.map +1 -1
- package/dist/Database/sql_codegen.js +107 -24
- package/dist/Database/sql_codegen.js.map +1 -1
- package/dist/Misc/entity_subclasses_codegen.d.ts +6 -0
- package/dist/Misc/entity_subclasses_codegen.d.ts.map +1 -1
- package/dist/Misc/entity_subclasses_codegen.js +20 -7
- package/dist/Misc/entity_subclasses_codegen.js.map +1 -1
- package/package.json +15 -15
|
@@ -809,7 +809,7 @@ export class ManageMetadataBase {
|
|
|
809
809
|
// Load VE EntityField rows from DB (we need the ID and auto-update flags)
|
|
810
810
|
const schema = mj_core_schema();
|
|
811
811
|
const fieldsSQL = `
|
|
812
|
-
SELECT ID, Name, Category, AutoUpdateCategory, AutoUpdateDisplayName
|
|
812
|
+
SELECT ID, Name, Category, AutoUpdateCategory, AutoUpdateDisplayName, GeneratedFormSection, DisplayName, ExtendedType, CodeType
|
|
813
813
|
FROM [${schema}].EntityField
|
|
814
814
|
WHERE EntityID = '${entity.ID}'
|
|
815
815
|
`;
|
|
@@ -830,7 +830,7 @@ export class ManageMetadataBase {
|
|
|
830
830
|
if (fieldCategories.length === 0)
|
|
831
831
|
return false;
|
|
832
832
|
const existingCategories = this.buildExistingCategorySet(dbFields);
|
|
833
|
-
await this.applyFieldCategories(pool, entity
|
|
833
|
+
await this.applyFieldCategories(pool, entity, dbFields, fieldCategories, existingCategories);
|
|
834
834
|
// Apply entity icon if provided
|
|
835
835
|
if (result.entityIcon) {
|
|
836
836
|
await this.applyEntityIcon(pool, entity.ID, result.entityIcon);
|
|
@@ -1800,13 +1800,34 @@ export class ManageMetadataBase {
|
|
|
1800
1800
|
* @returns {string} - The SQL statement to retrieve pending entity fields.
|
|
1801
1801
|
*/
|
|
1802
1802
|
getPendingEntityFieldsSELECTSQL() {
|
|
1803
|
-
const
|
|
1803
|
+
const schema = mj_core_schema();
|
|
1804
|
+
const sSQL = `
|
|
1805
|
+
-- Materialize system DMV views into temp tables so SQL Server gets real statistics
|
|
1806
|
+
-- instead of expanding nested view-on-view joins with bad cardinality estimates
|
|
1807
|
+
-- Drop first in case a prior run on this connection left them behind
|
|
1808
|
+
IF OBJECT_ID('tempdb..#__mj__CodeGen__vwForeignKeys') IS NOT NULL DROP TABLE #__mj__CodeGen__vwForeignKeys;
|
|
1809
|
+
IF OBJECT_ID('tempdb..#__mj__CodeGen__vwTablePrimaryKeys') IS NOT NULL DROP TABLE #__mj__CodeGen__vwTablePrimaryKeys;
|
|
1810
|
+
IF OBJECT_ID('tempdb..#__mj__CodeGen__vwTableUniqueKeys') IS NOT NULL DROP TABLE #__mj__CodeGen__vwTableUniqueKeys;
|
|
1811
|
+
|
|
1812
|
+
SELECT [column], [table], [schema_name], referenced_table, referenced_column, [referenced_schema]
|
|
1813
|
+
INTO #__mj__CodeGen__vwForeignKeys
|
|
1814
|
+
FROM [${schema}].vwForeignKeys;
|
|
1815
|
+
|
|
1816
|
+
SELECT TableName, ColumnName, SchemaName
|
|
1817
|
+
INTO #__mj__CodeGen__vwTablePrimaryKeys
|
|
1818
|
+
FROM [${schema}].vwTablePrimaryKeys;
|
|
1819
|
+
|
|
1820
|
+
SELECT TableName, ColumnName, SchemaName
|
|
1821
|
+
INTO #__mj__CodeGen__vwTableUniqueKeys
|
|
1822
|
+
FROM [${schema}].vwTableUniqueKeys;
|
|
1823
|
+
|
|
1824
|
+
WITH MaxSequences AS (
|
|
1804
1825
|
-- Calculate the maximum existing sequence for each entity to avoid collisions
|
|
1805
1826
|
SELECT
|
|
1806
1827
|
EntityID,
|
|
1807
1828
|
ISNULL(MAX(Sequence), 0) AS MaxSequence
|
|
1808
1829
|
FROM
|
|
1809
|
-
[${
|
|
1830
|
+
[${schema}].EntityField
|
|
1810
1831
|
GROUP BY
|
|
1811
1832
|
EntityID
|
|
1812
1833
|
),
|
|
@@ -1849,61 +1870,50 @@ NumberedRows AS (
|
|
|
1849
1870
|
END,
|
|
1850
1871
|
ROW_NUMBER() OVER (PARTITION BY sf.EntityID, sf.FieldName ORDER BY (SELECT NULL)) AS rn
|
|
1851
1872
|
FROM
|
|
1852
|
-
[${
|
|
1873
|
+
[${schema}].vwSQLColumnsAndEntityFields sf
|
|
1853
1874
|
LEFT OUTER JOIN
|
|
1854
1875
|
MaxSequences ms
|
|
1855
1876
|
ON
|
|
1856
1877
|
sf.EntityID = ms.EntityID
|
|
1857
1878
|
LEFT OUTER JOIN
|
|
1858
|
-
[${
|
|
1879
|
+
[${schema}].Entity e
|
|
1859
1880
|
ON
|
|
1860
1881
|
sf.EntityID = e.ID
|
|
1861
1882
|
LEFT OUTER JOIN
|
|
1862
|
-
|
|
1883
|
+
#__mj__CodeGen__vwForeignKeys fk
|
|
1863
1884
|
ON
|
|
1864
1885
|
sf.FieldName = fk.[column] AND
|
|
1865
1886
|
e.BaseTable = fk.[table] AND
|
|
1866
1887
|
e.SchemaName = fk.[schema_name]
|
|
1867
1888
|
LEFT OUTER JOIN
|
|
1868
|
-
[${
|
|
1889
|
+
[${schema}].Entity re -- Related Entity
|
|
1869
1890
|
ON
|
|
1870
1891
|
re.BaseTable = fk.referenced_table AND
|
|
1871
1892
|
re.SchemaName = fk.[referenced_schema]
|
|
1872
1893
|
LEFT OUTER JOIN
|
|
1873
|
-
|
|
1894
|
+
#__mj__CodeGen__vwTablePrimaryKeys pk
|
|
1874
1895
|
ON
|
|
1875
1896
|
e.BaseTable = pk.TableName AND
|
|
1876
1897
|
sf.FieldName = pk.ColumnName AND
|
|
1877
1898
|
e.SchemaName = pk.SchemaName
|
|
1878
1899
|
LEFT OUTER JOIN
|
|
1879
|
-
|
|
1900
|
+
#__mj__CodeGen__vwTableUniqueKeys uk
|
|
1880
1901
|
ON
|
|
1881
1902
|
e.BaseTable = uk.TableName AND
|
|
1882
1903
|
sf.FieldName = uk.ColumnName AND
|
|
1883
1904
|
e.SchemaName = uk.SchemaName
|
|
1884
1905
|
WHERE
|
|
1885
1906
|
EntityFieldID IS NULL -- only where we have NOT YET CREATED EntityField records\n${this.createExcludeTablesAndSchemasFilter('sf.')}
|
|
1886
|
-
),
|
|
1887
|
-
FilteredRows AS ( -- filter rows to only include rn=1 OR where we have rows where the to/from fkey is the same so long as the field name <> the same
|
|
1888
|
-
SELECT *
|
|
1889
|
-
FROM NumberedRows
|
|
1890
|
-
WHERE rn = 1
|
|
1891
|
-
UNION ALL
|
|
1892
|
-
SELECT nr.*
|
|
1893
|
-
FROM NumberedRows nr
|
|
1894
|
-
WHERE rn <> 1
|
|
1895
|
-
AND NOT EXISTS (
|
|
1896
|
-
SELECT 1
|
|
1897
|
-
FROM NumberedRows nr1
|
|
1898
|
-
WHERE nr1.rn = 1
|
|
1899
|
-
AND nr1.EntityID = nr.EntityID
|
|
1900
|
-
AND nr1.FieldName = nr.FieldName
|
|
1901
|
-
)
|
|
1902
1907
|
)
|
|
1903
1908
|
SELECT *
|
|
1904
|
-
FROM
|
|
1909
|
+
FROM NumberedRows
|
|
1910
|
+
WHERE rn = 1
|
|
1905
1911
|
ORDER BY EntityID, Sequence;
|
|
1906
|
-
|
|
1912
|
+
|
|
1913
|
+
DROP TABLE #__mj__CodeGen__vwForeignKeys;
|
|
1914
|
+
DROP TABLE #__mj__CodeGen__vwTablePrimaryKeys;
|
|
1915
|
+
DROP TABLE #__mj__CodeGen__vwTableUniqueKeys;
|
|
1916
|
+
`;
|
|
1907
1917
|
return sSQL;
|
|
1908
1918
|
}
|
|
1909
1919
|
/**
|
|
@@ -2031,38 +2041,40 @@ NumberedRows AS (
|
|
|
2031
2041
|
const sSQL = this.getPendingEntityFieldsSELECTSQL();
|
|
2032
2042
|
const newEntityFieldsResult = await pool.request().query(sSQL);
|
|
2033
2043
|
const newEntityFields = newEntityFieldsResult.recordset;
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2044
|
+
if (newEntityFields.length > 0) {
|
|
2045
|
+
const transaction = new sql.Transaction(pool);
|
|
2046
|
+
await transaction.begin();
|
|
2047
|
+
try {
|
|
2048
|
+
// wrap in a transaction so we get all of it or none of it
|
|
2049
|
+
for (let i = 0; i < newEntityFields.length; ++i) {
|
|
2050
|
+
const n = newEntityFields[i];
|
|
2051
|
+
if (n.EntityID !== null && n.EntityID !== undefined && n.EntityID.length > 0) {
|
|
2052
|
+
// need to check for null entity id = that is because the above query can return candidate Entity Fields but the entities may not have been created if the entities
|
|
2053
|
+
// that would have been created violate rules - such as not having an ID column, etc.
|
|
2054
|
+
const newEntityFieldUUID = this.createNewUUID();
|
|
2055
|
+
const sSQLInsert = this.getPendingEntityFieldINSERTSQL(newEntityFieldUUID, n);
|
|
2056
|
+
try {
|
|
2057
|
+
await this.LogSQLAndExecute(pool, sSQLInsert, `SQL text to insert new entity field`);
|
|
2058
|
+
// if we get here, we're okay, otherwise we have an exception, which we want as it blows up transaction
|
|
2059
|
+
}
|
|
2060
|
+
catch (e) {
|
|
2061
|
+
// this is here so we can catch the error for debug. We want the transaction to die
|
|
2062
|
+
logError(`Error inserting new entity field. SQL: \n${sSQLInsert}`);
|
|
2063
|
+
throw e;
|
|
2064
|
+
}
|
|
2053
2065
|
}
|
|
2054
2066
|
}
|
|
2067
|
+
await transaction.commit();
|
|
2055
2068
|
}
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2069
|
+
catch (e) {
|
|
2070
|
+
await transaction.rollback();
|
|
2071
|
+
throw e;
|
|
2072
|
+
}
|
|
2073
|
+
// if we get here now send a distinct list of the entities that had new fields to the modified entity list
|
|
2074
|
+
// column in the resultset is called EntityName, we dont have to dedupe them here because the method below
|
|
2075
|
+
// will do that for us
|
|
2076
|
+
ManageMetadataBase.addNewEntitiesToModifiedList(newEntityFields.map((f) => f.EntityName));
|
|
2061
2077
|
}
|
|
2062
|
-
// if we get here now send a distinct list of the entities that had new fields to the modified entity list
|
|
2063
|
-
// column in the resultset is called EntityName, we dont have to dedupe them here because the method below
|
|
2064
|
-
// will do that for us
|
|
2065
|
-
ManageMetadataBase.addNewEntitiesToModifiedList(newEntityFields.map((f) => f.EntityName));
|
|
2066
2078
|
return true;
|
|
2067
2079
|
}
|
|
2068
2080
|
catch (e) {
|
|
@@ -2559,7 +2571,6 @@ NumberedRows AS (
|
|
|
2559
2571
|
* Checks if a table has a soft primary key defined in the additionalSchemaInfo JSON file (configured in mj.config.cjs)
|
|
2560
2572
|
*/
|
|
2561
2573
|
hasSoftPrimaryKeyInConfig(schemaName, tableName) {
|
|
2562
|
-
// Check if additionalSchemaInfo is configured
|
|
2563
2574
|
if (!configInfo.additionalSchemaInfo) {
|
|
2564
2575
|
return false;
|
|
2565
2576
|
}
|
|
@@ -2570,15 +2581,20 @@ NumberedRows AS (
|
|
|
2570
2581
|
}
|
|
2571
2582
|
try {
|
|
2572
2583
|
const config = ManageMetadataBase.getSoftPKFKConfig();
|
|
2573
|
-
if (!config
|
|
2574
|
-
logStatus(` [Soft PK Check] Config file found but
|
|
2584
|
+
if (!config) {
|
|
2585
|
+
logStatus(` [Soft PK Check] Config file found but could not be parsed`);
|
|
2575
2586
|
return false;
|
|
2576
2587
|
}
|
|
2577
|
-
const
|
|
2578
|
-
|
|
2579
|
-
|
|
2588
|
+
const tables = this.extractTablesFromConfig(config);
|
|
2589
|
+
if (tables.length === 0) {
|
|
2590
|
+
logStatus(` [Soft PK Check] Config file found but no tables defined`);
|
|
2591
|
+
return false;
|
|
2592
|
+
}
|
|
2593
|
+
const tableConfig = tables.find((t) => t.SchemaName.toLowerCase() === schemaName?.toLowerCase() &&
|
|
2594
|
+
t.TableName.toLowerCase() === tableName?.toLowerCase());
|
|
2595
|
+
const found = Boolean(tableConfig?.PrimaryKey && tableConfig.PrimaryKey.length > 0);
|
|
2580
2596
|
if (!found) {
|
|
2581
|
-
logStatus(` [Soft PK Check] No config found for ${schemaName}.${tableName} (config has ${
|
|
2597
|
+
logStatus(` [Soft PK Check] No config found for ${schemaName}.${tableName} (config has ${tables.length} tables)`);
|
|
2582
2598
|
}
|
|
2583
2599
|
return found;
|
|
2584
2600
|
}
|
|
@@ -3068,7 +3084,10 @@ NumberedRows AS (
|
|
|
3068
3084
|
ef.EntityIDFieldName,
|
|
3069
3085
|
ef.RelatedEntity,
|
|
3070
3086
|
ef.IsVirtual,
|
|
3071
|
-
ef.AllowUpdateAPI
|
|
3087
|
+
ef.AllowUpdateAPI,
|
|
3088
|
+
ef.IsNameField,
|
|
3089
|
+
ef.DefaultInView,
|
|
3090
|
+
ef.IncludeInUserSearchAPI
|
|
3072
3091
|
FROM
|
|
3073
3092
|
[${mj_core_schema()}].vwEntityFields ef
|
|
3074
3093
|
WHERE
|
|
@@ -3154,40 +3173,45 @@ NumberedRows AS (
|
|
|
3154
3173
|
* @param currentUser User context
|
|
3155
3174
|
*/
|
|
3156
3175
|
async processEntityAdvancedGeneration(pool, entity, allFields, ag, currentUser) {
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3176
|
+
try {
|
|
3177
|
+
// Filter fields for this entity (client-side filtering)
|
|
3178
|
+
const fields = allFields.filter((f) => f.EntityID === entity.ID);
|
|
3179
|
+
// Determine if this is a new entity (for DefaultForNewUser decision)
|
|
3180
|
+
const isNewEntity = ManageMetadataBase.newEntityList.includes(entity.Name);
|
|
3181
|
+
// Smart Field Identification
|
|
3182
|
+
// Only run if at least one field allows auto-update for any of the smart field properties
|
|
3183
|
+
if (fields.some((f) => f.AutoUpdateIsNameField || f.AutoUpdateDefaultInView || f.AutoUpdateIncludeInUserSearchAPI)) {
|
|
3184
|
+
const fieldAnalysis = await ag.identifyFields({
|
|
3185
|
+
Name: entity.Name,
|
|
3186
|
+
Description: entity.Description,
|
|
3187
|
+
Fields: fields
|
|
3188
|
+
}, currentUser);
|
|
3189
|
+
if (fieldAnalysis) {
|
|
3190
|
+
await this.applySmartFieldIdentification(pool, entity.ID, fields, fieldAnalysis);
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
// Form Layout Generation
|
|
3194
|
+
// Only run if at least one field allows auto-update
|
|
3195
|
+
const needsCategoryGeneration = fields.some((f) => f.AutoUpdateCategory && (!f.Category || f.Category.trim() === ''));
|
|
3196
|
+
if (needsCategoryGeneration) {
|
|
3197
|
+
// Build IS-A parent chain context if this entity has a parent
|
|
3198
|
+
const parentChainContext = this.buildParentChainContext(entity, fields);
|
|
3199
|
+
const layoutAnalysis = await ag.generateFormLayout({
|
|
3200
|
+
Name: entity.Name,
|
|
3201
|
+
Description: entity.Description,
|
|
3202
|
+
SchemaName: entity.SchemaName,
|
|
3203
|
+
Settings: entity.Settings,
|
|
3204
|
+
Fields: fields,
|
|
3205
|
+
...parentChainContext
|
|
3206
|
+
}, currentUser, isNewEntity);
|
|
3207
|
+
if (layoutAnalysis) {
|
|
3208
|
+
await this.applyFormLayout(pool, entity, fields, layoutAnalysis, isNewEntity);
|
|
3209
|
+
logStatus(` Applied form layout for ${entity.Name}`);
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
catch (ex) {
|
|
3214
|
+
logError('Error Processing Entity Advanced Generation', ex);
|
|
3191
3215
|
}
|
|
3192
3216
|
}
|
|
3193
3217
|
/**
|
|
@@ -3258,7 +3282,7 @@ NumberedRows AS (
|
|
|
3258
3282
|
const sqlStatements = [];
|
|
3259
3283
|
// Find the name field (exactly one)
|
|
3260
3284
|
const nameField = fields.find(f => f.Name === result.nameField);
|
|
3261
|
-
if (nameField && nameField.AutoUpdateIsNameField && nameField.ID) {
|
|
3285
|
+
if (nameField && nameField.AutoUpdateIsNameField && nameField.ID && !nameField.IsNameField /*don't waste SQL to set the value if IsNameField already set */) {
|
|
3262
3286
|
sqlStatements.push(`
|
|
3263
3287
|
UPDATE [${mj_core_schema()}].EntityField
|
|
3264
3288
|
SET IsNameField = 1
|
|
@@ -3278,12 +3302,15 @@ NumberedRows AS (
|
|
|
3278
3302
|
}
|
|
3279
3303
|
// Build update statements for all default in view fields
|
|
3280
3304
|
for (const field of defaultInViewFields) {
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3305
|
+
if (!field.DefaultInView) {
|
|
3306
|
+
// only set these when DefaultInView not already on, otherwise wasteful
|
|
3307
|
+
sqlStatements.push(`
|
|
3308
|
+
UPDATE [${mj_core_schema()}].EntityField
|
|
3309
|
+
SET DefaultInView = 1
|
|
3310
|
+
WHERE ID = '${field.ID}'
|
|
3311
|
+
AND AutoUpdateDefaultInView = 1
|
|
3312
|
+
`);
|
|
3313
|
+
}
|
|
3287
3314
|
}
|
|
3288
3315
|
// Find all searchable fields (one or more) - for IncludeInUserSearchAPI
|
|
3289
3316
|
if (result.searchableFields && result.searchableFields.length > 0) {
|
|
@@ -3295,18 +3322,26 @@ NumberedRows AS (
|
|
|
3295
3322
|
}
|
|
3296
3323
|
// Build update statements for all searchable fields
|
|
3297
3324
|
for (const field of searchableFields) {
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3325
|
+
if (!field.IncludeInUserSearchAPI) {
|
|
3326
|
+
// only set this if IncludeInUserSearchAPI isn't already set
|
|
3327
|
+
sqlStatements.push(`
|
|
3328
|
+
UPDATE [${mj_core_schema()}].EntityField
|
|
3329
|
+
SET IncludeInUserSearchAPI = 1
|
|
3330
|
+
WHERE ID = '${field.ID}'
|
|
3331
|
+
AND AutoUpdateIncludeInUserSearchAPI = 1
|
|
3332
|
+
`);
|
|
3333
|
+
}
|
|
3304
3334
|
}
|
|
3305
3335
|
}
|
|
3306
3336
|
// Execute all updates in one batch
|
|
3307
3337
|
if (sqlStatements.length > 0) {
|
|
3308
3338
|
const combinedSQL = sqlStatements.join('\n');
|
|
3309
|
-
|
|
3339
|
+
try {
|
|
3340
|
+
await this.LogSQLAndExecute(pool, combinedSQL, `Set field properties for entity`, false);
|
|
3341
|
+
}
|
|
3342
|
+
catch (ex) {
|
|
3343
|
+
logError('Error executing combined smart field SQL: ', ex);
|
|
3344
|
+
}
|
|
3310
3345
|
}
|
|
3311
3346
|
}
|
|
3312
3347
|
/**
|
|
@@ -3318,21 +3353,21 @@ NumberedRows AS (
|
|
|
3318
3353
|
* @param result Form layout result from LLM
|
|
3319
3354
|
* @param isNewEntity If true, apply entityImportance; if false, skip it
|
|
3320
3355
|
*/
|
|
3321
|
-
async applyFormLayout(pool,
|
|
3356
|
+
async applyFormLayout(pool, entity, fields, result, isNewEntity = false) {
|
|
3322
3357
|
const existingCategories = this.buildExistingCategorySet(fields);
|
|
3323
|
-
await this.applyFieldCategories(pool,
|
|
3358
|
+
await this.applyFieldCategories(pool, entity, fields, result.fieldCategories, existingCategories);
|
|
3324
3359
|
if (result.entityIcon) {
|
|
3325
|
-
await this.applyEntityIcon(pool,
|
|
3360
|
+
await this.applyEntityIcon(pool, entity.ID, result.entityIcon);
|
|
3326
3361
|
}
|
|
3327
3362
|
// Resolve categoryInfo from new or legacy format
|
|
3328
3363
|
const categoryInfoToStore = result.categoryInfo ||
|
|
3329
3364
|
(result.categoryIcons ?
|
|
3330
3365
|
Object.fromEntries(Object.entries(result.categoryIcons).map(([cat, icon]) => [cat, { icon, description: '' }])) : null);
|
|
3331
3366
|
if (categoryInfoToStore) {
|
|
3332
|
-
await this.applyCategoryInfoSettings(pool,
|
|
3367
|
+
await this.applyCategoryInfoSettings(pool, entity.ID, categoryInfoToStore);
|
|
3333
3368
|
}
|
|
3334
3369
|
if (isNewEntity && result.entityImportance) {
|
|
3335
|
-
await this.applyEntityImportance(pool,
|
|
3370
|
+
await this.applyEntityImportance(pool, entity.ID, result.entityImportance);
|
|
3336
3371
|
}
|
|
3337
3372
|
}
|
|
3338
3373
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -3357,7 +3392,7 @@ NumberedRows AS (
|
|
|
3357
3392
|
* Enforces stability rules: fields with existing categories cannot move to NEW categories.
|
|
3358
3393
|
* All SQL updates are batched into a single execution for performance.
|
|
3359
3394
|
*/
|
|
3360
|
-
async applyFieldCategories(pool,
|
|
3395
|
+
async applyFieldCategories(pool, entity, fields, fieldCategories, existingCategories) {
|
|
3361
3396
|
const sqlStatements = [];
|
|
3362
3397
|
for (const fieldCategory of fieldCategories) {
|
|
3363
3398
|
const field = fields.find(f => f.Name === fieldCategory.fieldName);
|
|
@@ -3374,32 +3409,44 @@ NumberedRows AS (
|
|
|
3374
3409
|
logStatus(` Rejected category change for field '${field.Name}': cannot move from existing category '${field.Category}' to new category '${category}'. Keeping original category.`);
|
|
3375
3410
|
category = field.Category;
|
|
3376
3411
|
}
|
|
3377
|
-
const setClauses = [
|
|
3378
|
-
|
|
3379
|
-
`
|
|
3380
|
-
|
|
3381
|
-
if (
|
|
3412
|
+
const setClauses = [];
|
|
3413
|
+
if (field.Category !== category) {
|
|
3414
|
+
setClauses.push(`Category = '${category.replace(/'/g, "''")}'`);
|
|
3415
|
+
}
|
|
3416
|
+
if (field.GeneratedFormSection !== 'Category') {
|
|
3417
|
+
setClauses.push(`GeneratedFormSection = 'Category'`);
|
|
3418
|
+
}
|
|
3419
|
+
if (fieldCategory.displayName && field.AutoUpdateDisplayName && field.DisplayName !== fieldCategory.displayName) {
|
|
3382
3420
|
setClauses.push(`DisplayName = '${fieldCategory.displayName.replace(/'/g, "''")}'`);
|
|
3383
3421
|
}
|
|
3384
|
-
if (fieldCategory.extendedType !== undefined) {
|
|
3422
|
+
if (fieldCategory.extendedType !== undefined && field.ExtendedType !== fieldCategory.extendedType) {
|
|
3385
3423
|
const extendedType = fieldCategory.extendedType === null ? 'NULL' : `'${String(fieldCategory.extendedType).replace(/'/g, "''")}'`;
|
|
3386
3424
|
setClauses.push(`ExtendedType = ${extendedType}`);
|
|
3387
3425
|
}
|
|
3388
|
-
if (fieldCategory.codeType !== undefined) {
|
|
3426
|
+
if (fieldCategory.codeType !== undefined && field.CodeType !== fieldCategory.codeType) {
|
|
3389
3427
|
const codeType = fieldCategory.codeType === null ? 'NULL' : `'${String(fieldCategory.codeType).replace(/'/g, "''")}'`;
|
|
3390
3428
|
setClauses.push(`CodeType = ${codeType}`);
|
|
3391
3429
|
}
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3430
|
+
if (setClauses.length > 0) {
|
|
3431
|
+
// only generate an UPDATE if we have 1+ set clause
|
|
3432
|
+
sqlStatements.push(`\n-- UPDATE Entity Field Category Info ${entity.Name}.${field.Name} \nUPDATE [${mj_core_schema()}].EntityField
|
|
3433
|
+
SET
|
|
3434
|
+
${setClauses.join(',\n ')}
|
|
3435
|
+
WHERE
|
|
3436
|
+
ID = '${field.ID}' AND AutoUpdateCategory = 1`);
|
|
3437
|
+
}
|
|
3396
3438
|
}
|
|
3397
3439
|
else if (!field) {
|
|
3398
3440
|
logError(`Form layout returned invalid fieldName: '${fieldCategory.fieldName}' not found in entity`);
|
|
3399
3441
|
}
|
|
3400
3442
|
}
|
|
3401
3443
|
if (sqlStatements.length > 0) {
|
|
3402
|
-
|
|
3444
|
+
try {
|
|
3445
|
+
await this.LogSQLAndExecute(pool, sqlStatements.join('\n'), `Set categories for ${sqlStatements.length} fields`, false);
|
|
3446
|
+
}
|
|
3447
|
+
catch (ex) {
|
|
3448
|
+
logError('Error Applying Field Categories', ex);
|
|
3449
|
+
}
|
|
3403
3450
|
}
|
|
3404
3451
|
}
|
|
3405
3452
|
/**
|
|
@@ -3419,8 +3466,13 @@ NumberedRows AS (
|
|
|
3419
3466
|
SET Icon = '${escapedIcon}', __mj_UpdatedAt = GETUTCDATE()
|
|
3420
3467
|
WHERE ID = '${entityId}'
|
|
3421
3468
|
`;
|
|
3422
|
-
|
|
3423
|
-
|
|
3469
|
+
try {
|
|
3470
|
+
await this.LogSQLAndExecute(pool, updateSQL, `Set entity icon to ${entityIcon}`, false);
|
|
3471
|
+
logStatus(` Set entity icon: ${entityIcon}`);
|
|
3472
|
+
}
|
|
3473
|
+
catch (ex) {
|
|
3474
|
+
logError('Error Applying Entity Icon', ex);
|
|
3475
|
+
}
|
|
3424
3476
|
}
|
|
3425
3477
|
}
|
|
3426
3478
|
}
|
|
@@ -3435,18 +3487,28 @@ NumberedRows AS (
|
|
|
3435
3487
|
const checkNewSQL = `SELECT ID FROM [${mj_core_schema()}].EntitySetting WHERE EntityID = '${entityId}' AND Name = 'FieldCategoryInfo'`;
|
|
3436
3488
|
const existingNew = await pool.request().query(checkNewSQL);
|
|
3437
3489
|
if (existingNew.recordset.length > 0) {
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3490
|
+
try {
|
|
3491
|
+
await this.LogSQLAndExecute(pool, `
|
|
3492
|
+
UPDATE [${mj_core_schema()}].EntitySetting
|
|
3493
|
+
SET Value = '${infoJSON}', __mj_UpdatedAt = GETUTCDATE()
|
|
3494
|
+
WHERE EntityID = '${entityId}' AND Name = 'FieldCategoryInfo'
|
|
3495
|
+
`, `Update FieldCategoryInfo setting for entity`, false);
|
|
3496
|
+
}
|
|
3497
|
+
catch (ex) {
|
|
3498
|
+
logError('Error Applying Category Info Settings: Part 1', ex);
|
|
3499
|
+
}
|
|
3443
3500
|
}
|
|
3444
3501
|
else {
|
|
3445
3502
|
const newId = uuidv4();
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3503
|
+
try {
|
|
3504
|
+
await this.LogSQLAndExecute(pool, `
|
|
3505
|
+
INSERT INTO [${mj_core_schema()}].EntitySetting (ID, EntityID, Name, Value, __mj_CreatedAt, __mj_UpdatedAt)
|
|
3506
|
+
VALUES ('${newId}', '${entityId}', 'FieldCategoryInfo', '${infoJSON}', GETUTCDATE(), GETUTCDATE())
|
|
3507
|
+
`, `Insert FieldCategoryInfo setting for entity`, false);
|
|
3508
|
+
}
|
|
3509
|
+
catch (ex) {
|
|
3510
|
+
logError('Error Applying Category Info Settings: Part 2', ex);
|
|
3511
|
+
}
|
|
3450
3512
|
}
|
|
3451
3513
|
// Also upsert legacy FieldCategoryIcons for backwards compatibility
|
|
3452
3514
|
const iconsOnly = {};
|
|
@@ -3459,18 +3521,28 @@ NumberedRows AS (
|
|
|
3459
3521
|
const checkLegacySQL = `SELECT ID FROM [${mj_core_schema()}].EntitySetting WHERE EntityID = '${entityId}' AND Name = 'FieldCategoryIcons'`;
|
|
3460
3522
|
const existingLegacy = await pool.request().query(checkLegacySQL);
|
|
3461
3523
|
if (existingLegacy.recordset.length > 0) {
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3524
|
+
try {
|
|
3525
|
+
await this.LogSQLAndExecute(pool, `
|
|
3526
|
+
UPDATE [${mj_core_schema()}].EntitySetting
|
|
3527
|
+
SET Value = '${iconsJSON}', __mj_UpdatedAt = GETUTCDATE()
|
|
3528
|
+
WHERE EntityID = '${entityId}' AND Name = 'FieldCategoryIcons'
|
|
3529
|
+
`, `Update FieldCategoryIcons setting (legacy)`, false);
|
|
3530
|
+
}
|
|
3531
|
+
catch (ex) {
|
|
3532
|
+
logError('Error Applying Category Info Settings: Part 3', ex);
|
|
3533
|
+
}
|
|
3467
3534
|
}
|
|
3468
3535
|
else {
|
|
3469
3536
|
const newId = uuidv4();
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3537
|
+
try {
|
|
3538
|
+
await this.LogSQLAndExecute(pool, `
|
|
3539
|
+
INSERT INTO [${mj_core_schema()}].EntitySetting (ID, EntityID, Name, Value, __mj_CreatedAt, __mj_UpdatedAt)
|
|
3540
|
+
VALUES ('${newId}', '${entityId}', 'FieldCategoryIcons', '${iconsJSON}', GETUTCDATE(), GETUTCDATE())
|
|
3541
|
+
`, `Insert FieldCategoryIcons setting (legacy)`, false);
|
|
3542
|
+
}
|
|
3543
|
+
catch (ex) {
|
|
3544
|
+
logError('Error Applying Category Info Settings: Part 4', ex);
|
|
3545
|
+
}
|
|
3474
3546
|
}
|
|
3475
3547
|
}
|
|
3476
3548
|
/**
|
|
@@ -3484,9 +3556,14 @@ NumberedRows AS (
|
|
|
3484
3556
|
SET DefaultForNewUser = ${defaultForNewUser}, __mj_UpdatedAt = GETUTCDATE()
|
|
3485
3557
|
WHERE EntityID = '${entityId}'
|
|
3486
3558
|
`;
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3559
|
+
try {
|
|
3560
|
+
await this.LogSQLAndExecute(pool, updateSQL, `Set DefaultForNewUser=${defaultForNewUser} for NEW entity (category: ${importance.entityCategory}, confidence: ${importance.confidence})`, false);
|
|
3561
|
+
logStatus(` Entity importance (NEW Entity): ${importance.entityCategory} (defaultForNewUser: ${importance.defaultForNewUser}, confidence: ${importance.confidence})`);
|
|
3562
|
+
logStatus(` Reasoning: ${importance.reasoning}`);
|
|
3563
|
+
}
|
|
3564
|
+
catch (ex) {
|
|
3565
|
+
logError('Error Applying Entity Importance', ex);
|
|
3566
|
+
}
|
|
3490
3567
|
}
|
|
3491
3568
|
/**
|
|
3492
3569
|
* Executes the given SQL query using the given ConnectionPool object.
|