@memberjunction/codegen-lib 3.3.0 → 4.0.0
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/README.md +56 -1
- package/dist/Angular/angular-codegen.d.ts +1 -1
- package/dist/Angular/angular-codegen.d.ts.map +1 -1
- package/dist/Angular/angular-codegen.js +80 -136
- package/dist/Angular/angular-codegen.js.map +1 -1
- package/dist/Angular/entity-data-grid-related-entity-component.d.ts +1 -1
- package/dist/Angular/entity-data-grid-related-entity-component.js +6 -9
- package/dist/Angular/entity-data-grid-related-entity-component.js.map +1 -1
- package/dist/Angular/join-grid-related-entity-component.d.ts +1 -1
- package/dist/Angular/join-grid-related-entity-component.js +8 -36
- package/dist/Angular/join-grid-related-entity-component.js.map +1 -1
- package/dist/Angular/related-entity-components.js +13 -79
- package/dist/Angular/related-entity-components.js.map +1 -1
- package/dist/Angular/timeline-related-entity-component.d.ts +1 -1
- package/dist/Angular/timeline-related-entity-component.js +14 -38
- package/dist/Angular/timeline-related-entity-component.js.map +1 -1
- package/dist/Angular/user-view-grid-related-entity-component.d.ts +43 -0
- package/dist/Angular/user-view-grid-related-entity-component.d.ts.map +1 -0
- package/dist/Angular/user-view-grid-related-entity-component.js +85 -0
- package/dist/Angular/user-view-grid-related-entity-component.js.map +1 -0
- package/dist/Config/config.d.ts +79 -0
- package/dist/Config/config.d.ts.map +1 -1
- package/dist/Config/config.js +174 -172
- package/dist/Config/config.js.map +1 -1
- package/dist/Config/db-connection.d.ts +1 -1
- package/dist/Config/db-connection.d.ts.map +1 -1
- package/dist/Config/db-connection.js +6 -33
- package/dist/Config/db-connection.js.map +1 -1
- package/dist/Database/dbSchema.js +28 -35
- package/dist/Database/dbSchema.js.map +1 -1
- package/dist/Database/manage-metadata.d.ts +25 -9
- package/dist/Database/manage-metadata.d.ts.map +1 -1
- package/dist/Database/manage-metadata.js +438 -316
- package/dist/Database/manage-metadata.js.map +1 -1
- package/dist/Database/reorder-columns.d.ts +1 -1
- package/dist/Database/reorder-columns.d.ts.map +1 -1
- package/dist/Database/reorder-columns.js +1 -5
- package/dist/Database/reorder-columns.js.map +1 -1
- package/dist/Database/sql.d.ts +1 -1
- package/dist/Database/sql.d.ts.map +1 -1
- package/dist/Database/sql.js +67 -98
- package/dist/Database/sql.js.map +1 -1
- package/dist/Database/sql_codegen.d.ts +5 -2
- package/dist/Database/sql_codegen.d.ts.map +1 -1
- package/dist/Database/sql_codegen.js +344 -268
- package/dist/Database/sql_codegen.js.map +1 -1
- package/dist/Manifest/GenerateClassRegistrationsManifest.d.ts +110 -0
- package/dist/Manifest/GenerateClassRegistrationsManifest.d.ts.map +1 -0
- package/dist/Manifest/GenerateClassRegistrationsManifest.js +632 -0
- package/dist/Manifest/GenerateClassRegistrationsManifest.js.map +1 -0
- package/dist/Misc/action_subclasses_codegen.d.ts.map +1 -1
- package/dist/Misc/action_subclasses_codegen.js +15 -26
- package/dist/Misc/action_subclasses_codegen.js.map +1 -1
- package/dist/Misc/advanced_generation.d.ts +1 -1
- package/dist/Misc/advanced_generation.js +34 -40
- package/dist/Misc/advanced_generation.js.map +1 -1
- package/dist/Misc/createNewUser.d.ts +1 -1
- package/dist/Misc/createNewUser.js +22 -26
- package/dist/Misc/createNewUser.js.map +1 -1
- package/dist/Misc/entity_subclasses_codegen.d.ts +2 -2
- package/dist/Misc/entity_subclasses_codegen.d.ts.map +1 -1
- package/dist/Misc/entity_subclasses_codegen.js +33 -40
- package/dist/Misc/entity_subclasses_codegen.js.map +1 -1
- package/dist/Misc/graphql_server_codegen.js +36 -41
- package/dist/Misc/graphql_server_codegen.js.map +1 -1
- package/dist/Misc/runCommand.d.ts +1 -1
- package/dist/Misc/runCommand.js +13 -20
- package/dist/Misc/runCommand.js.map +1 -1
- package/dist/Misc/sql_logging.d.ts +7 -1
- package/dist/Misc/sql_logging.d.ts.map +1 -1
- package/dist/Misc/sql_logging.js +40 -53
- package/dist/Misc/sql_logging.js.map +1 -1
- package/dist/Misc/status_logging.js +45 -60
- package/dist/Misc/status_logging.js.map +1 -1
- package/dist/Misc/system_integrity.d.ts +1 -1
- package/dist/Misc/system_integrity.d.ts.map +1 -1
- package/dist/Misc/system_integrity.js +12 -16
- package/dist/Misc/system_integrity.js.map +1 -1
- package/dist/Misc/temp_batch_file.js +15 -22
- package/dist/Misc/temp_batch_file.js.map +1 -1
- package/dist/Misc/util.d.ts.map +1 -1
- package/dist/Misc/util.js +17 -28
- package/dist/Misc/util.js.map +1 -1
- package/dist/index.d.ts +21 -20
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -40
- package/dist/index.js.map +1 -1
- package/dist/runCodeGen.d.ts +1 -0
- package/dist/runCodeGen.d.ts.map +1 -1
- package/dist/runCodeGen.js +151 -178
- package/dist/runCodeGen.js.map +1 -1
- package/package.json +24 -21
|
@@ -1,165 +1,170 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const sql = __importStar(require("mssql"));
|
|
31
|
-
const config_1 = require("../Config/config");
|
|
32
|
-
const core_1 = require("@memberjunction/core");
|
|
33
|
-
const status_logging_1 = require("../Misc/status_logging");
|
|
34
|
-
const sql_1 = require("./sql");
|
|
35
|
-
const advanced_generation_1 = require("../Misc/advanced_generation");
|
|
36
|
-
const global_1 = require("@memberjunction/global");
|
|
37
|
-
const uuid_1 = require("uuid");
|
|
38
|
-
const fs = __importStar(require("fs"));
|
|
39
|
-
const path_1 = __importDefault(require("path"));
|
|
40
|
-
const sql_logging_1 = require("../Misc/sql_logging");
|
|
41
|
-
class ValidatorResult {
|
|
42
|
-
entityName = "";
|
|
43
|
-
fieldName;
|
|
44
|
-
sourceCheckConstraint = "";
|
|
45
|
-
functionText = "";
|
|
46
|
-
functionName = "";
|
|
47
|
-
functionDescription = "";
|
|
48
|
-
/**
|
|
49
|
-
* The ID value in the Generated Codes entity that was created for this validator.
|
|
50
|
-
*/
|
|
51
|
-
generatedCodeId = "";
|
|
52
|
-
/**
|
|
53
|
-
* The ID for the AI Model that was used to generate the code
|
|
54
|
-
*/
|
|
55
|
-
aiModelID = "";
|
|
56
|
-
wasGenerated = true;
|
|
57
|
-
success = false;
|
|
1
|
+
import sql from 'mssql';
|
|
2
|
+
import { configInfo, currentWorkingDirectory, getSettingValue, mj_core_schema, outputDir } from '../Config/config.js';
|
|
3
|
+
import { CodeNameFromString, EntityInfo, ExtractActualDefaultValue, LogError, LogStatus, Metadata, SeverityType } from "@memberjunction/core";
|
|
4
|
+
import { logError, logMessage, logStatus } from "../Misc/status_logging.js";
|
|
5
|
+
import { SQLUtilityBase } from "./sql.js";
|
|
6
|
+
import { AdvancedGeneration } from "../Misc/advanced_generation.js";
|
|
7
|
+
import { convertCamelCaseToHaveSpaces, generatePluralName, MJGlobal, stripTrailingChars } from "@memberjunction/global";
|
|
8
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { SQLLogging } from "../Misc/sql_logging.js";
|
|
12
|
+
export class ValidatorResult {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.entityName = "";
|
|
15
|
+
this.sourceCheckConstraint = "";
|
|
16
|
+
this.functionText = "";
|
|
17
|
+
this.functionName = "";
|
|
18
|
+
this.functionDescription = "";
|
|
19
|
+
/**
|
|
20
|
+
* The ID value in the Generated Codes entity that was created for this validator.
|
|
21
|
+
*/
|
|
22
|
+
this.generatedCodeId = "";
|
|
23
|
+
/**
|
|
24
|
+
* The ID for the AI Model that was used to generate the code
|
|
25
|
+
*/
|
|
26
|
+
this.aiModelID = "";
|
|
27
|
+
this.wasGenerated = true;
|
|
28
|
+
this.success = false;
|
|
29
|
+
}
|
|
58
30
|
}
|
|
59
|
-
exports.ValidatorResult = ValidatorResult;
|
|
60
31
|
/**
|
|
61
32
|
* Base class for managing metadata within the CodeGen system. This class can be sub-classed to extend/override base class functionality. Make sure to use the RegisterClass decorator from the @memberjunction/global package
|
|
62
33
|
* to properly register your subclass with a priority of 1+ to ensure it gets instantiated.
|
|
63
34
|
*/
|
|
64
|
-
class ManageMetadataBase {
|
|
65
|
-
|
|
35
|
+
export class ManageMetadataBase {
|
|
36
|
+
constructor() {
|
|
37
|
+
this._sqlUtilityObject = MJGlobal.Instance.ClassFactory.CreateInstance(SQLUtilityBase);
|
|
38
|
+
}
|
|
66
39
|
get SQLUtilityObject() {
|
|
67
40
|
return this._sqlUtilityObject;
|
|
68
41
|
}
|
|
69
|
-
static _newEntityList = [];
|
|
42
|
+
static { this._newEntityList = []; }
|
|
70
43
|
/**
|
|
71
44
|
* Globally scoped list of entities that have been created during the metadata management process.
|
|
72
45
|
*/
|
|
73
46
|
static get newEntityList() {
|
|
74
47
|
return this._newEntityList;
|
|
75
48
|
}
|
|
76
|
-
static _modifiedEntityList = [];
|
|
49
|
+
static { this._modifiedEntityList = []; }
|
|
77
50
|
/**
|
|
78
51
|
* Globally scoped list of entities that have been modified during the metadata management process.
|
|
79
52
|
*/
|
|
80
53
|
static get modifiedEntityList() {
|
|
81
54
|
return this._modifiedEntityList;
|
|
82
55
|
}
|
|
83
|
-
static _generatedValidators = [];
|
|
56
|
+
static { this._generatedValidators = []; }
|
|
84
57
|
/**
|
|
85
58
|
* Globally scoped list of validators that have been generated during the metadata management process.
|
|
86
59
|
*/
|
|
87
60
|
static get generatedValidators() {
|
|
88
61
|
return this._generatedValidators;
|
|
89
62
|
}
|
|
63
|
+
static { this._softPKFKConfigCache = null; }
|
|
64
|
+
static { this._softPKFKConfigPath = ''; }
|
|
65
|
+
/**
|
|
66
|
+
* Loads and caches the soft PK/FK configuration from the additionalSchemaInfo file.
|
|
67
|
+
* The file is only loaded once per session to avoid repeated I/O.
|
|
68
|
+
*/
|
|
69
|
+
static getSoftPKFKConfig() {
|
|
70
|
+
// Return cached config if path hasn't changed
|
|
71
|
+
const configPath = configInfo.additionalSchemaInfo
|
|
72
|
+
? path.join(currentWorkingDirectory, configInfo.additionalSchemaInfo)
|
|
73
|
+
: '';
|
|
74
|
+
if (this._softPKFKConfigCache !== null && this._softPKFKConfigPath === configPath) {
|
|
75
|
+
return this._softPKFKConfigCache;
|
|
76
|
+
}
|
|
77
|
+
// Cache miss or path changed - reload from disk
|
|
78
|
+
if (!configPath || !fs.existsSync(configPath)) {
|
|
79
|
+
this._softPKFKConfigCache = null;
|
|
80
|
+
this._softPKFKConfigPath = configPath;
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const configContent = fs.readFileSync(configPath, 'utf-8');
|
|
85
|
+
this._softPKFKConfigCache = JSON.parse(configContent);
|
|
86
|
+
this._softPKFKConfigPath = configPath;
|
|
87
|
+
return this._softPKFKConfigCache;
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
this._softPKFKConfigCache = null;
|
|
91
|
+
this._softPKFKConfigPath = configPath;
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
90
95
|
/**
|
|
91
96
|
* Primary function to manage metadata within the CodeGen system. This function will call a series of sub-functions to manage the metadata.
|
|
92
97
|
* @param pool - the ConnectionPool object to use for querying and updating the database
|
|
93
98
|
* @returns
|
|
94
99
|
*/
|
|
95
100
|
async manageMetadata(pool, currentUser) {
|
|
96
|
-
const md = new
|
|
97
|
-
const excludeSchemas =
|
|
101
|
+
const md = new Metadata();
|
|
102
|
+
const excludeSchemas = configInfo.excludeSchemas ? configInfo.excludeSchemas : [];
|
|
98
103
|
let bSuccess = true;
|
|
99
104
|
let start = new Date();
|
|
100
|
-
|
|
105
|
+
logStatus(' Creating new entities...');
|
|
101
106
|
if (!await this.createNewEntities(pool, currentUser)) {
|
|
102
|
-
|
|
107
|
+
logError(' Error creating new entities');
|
|
103
108
|
bSuccess = false;
|
|
104
109
|
}
|
|
105
|
-
|
|
110
|
+
logStatus(` > Created new entities in ${(new Date().getTime() - start.getTime()) / 1000} seconds`);
|
|
106
111
|
start = new Date();
|
|
107
|
-
|
|
112
|
+
logStatus(' Updating existing entities...');
|
|
108
113
|
if (!await this.updateExistingEntitiesFromSchema(pool, excludeSchemas)) {
|
|
109
|
-
|
|
114
|
+
logError(' Error updating existing entities');
|
|
110
115
|
bSuccess = false;
|
|
111
116
|
}
|
|
112
|
-
|
|
117
|
+
logStatus(` > Updated existing entities in ${(new Date().getTime() - start.getTime()) / 1000} seconds`);
|
|
113
118
|
start = new Date();
|
|
114
|
-
|
|
119
|
+
logStatus(' Scanning for tables that were deleted where entity metadata still exists...');
|
|
115
120
|
if (!await this.checkAndRemoveMetadataForDeletedTables(pool, excludeSchemas)) {
|
|
116
|
-
|
|
121
|
+
logError(' Error removing metadata for tables that were removed');
|
|
117
122
|
bSuccess = false;
|
|
118
123
|
}
|
|
119
|
-
|
|
124
|
+
logStatus(` > Removed metadata for deleted tables in ${(new Date().getTime() - start.getTime()) / 1000} seconds`);
|
|
120
125
|
start = new Date();
|
|
121
|
-
|
|
122
|
-
const sqlUtility =
|
|
123
|
-
const adminSchema =
|
|
124
|
-
const schemasToExclude =
|
|
126
|
+
logStatus(' Recompiling base views...');
|
|
127
|
+
const sqlUtility = MJGlobal.Instance.ClassFactory.CreateInstance(SQLUtilityBase);
|
|
128
|
+
const adminSchema = getSettingValue('mj_core_schema', '__mj');
|
|
129
|
+
const schemasToExclude = getSettingValue('recompile_mj_views', true)
|
|
125
130
|
? excludeSchemas.filter((s) => s !== adminSchema)
|
|
126
131
|
: excludeSchemas;
|
|
127
132
|
if (!await sqlUtility.recompileAllBaseViews(pool, schemasToExclude, true, ManageMetadataBase._newEntityList /*exclude the newly created entities from the above step the first time we run as those views don't exist yet*/)) {
|
|
128
|
-
|
|
133
|
+
logMessage(' Warning: Non-Fatal error recompiling base views', SeverityType.Warning, false);
|
|
129
134
|
// many times the former versions of base views will NOT succesfully recompile, so don't consider that scenario to be a
|
|
130
135
|
// failure for this entire function
|
|
131
136
|
}
|
|
132
|
-
|
|
137
|
+
logStatus(` > Recompiled base views in ${(new Date().getTime() - start.getTime()) / 1000} seconds`);
|
|
133
138
|
start = new Date();
|
|
134
|
-
|
|
139
|
+
logStatus(' Managing entity fields...');
|
|
135
140
|
// note that we skip Advanced Generation here because we do it again later when the manageSQLScriptsAndExecution occurs in SQLCodeGen class
|
|
136
141
|
if (!await this.manageEntityFields(pool, excludeSchemas, false, false, currentUser, true)) {
|
|
137
|
-
|
|
142
|
+
logError(' Error managing entity fields');
|
|
138
143
|
bSuccess = false;
|
|
139
144
|
}
|
|
140
|
-
|
|
145
|
+
logStatus(` > Managed entity fields in ${(new Date().getTime() - start.getTime()) / 1000} seconds`);
|
|
141
146
|
start = new Date();
|
|
142
|
-
|
|
147
|
+
logStatus(' Managing entity relationships...');
|
|
143
148
|
if (!await this.manageEntityRelationships(pool, excludeSchemas, md)) {
|
|
144
|
-
|
|
149
|
+
logError(' Error managing entity relationships');
|
|
145
150
|
bSuccess = false;
|
|
146
151
|
}
|
|
147
|
-
|
|
152
|
+
logStatus(` > Managed entity relationships in ${(new Date().getTime() - start.getTime()) / 1000} seconds`);
|
|
148
153
|
if (ManageMetadataBase.newEntityList.length > 0) {
|
|
149
154
|
await this.generateNewEntityDescriptions(pool, md, currentUser); // don't pass excludeSchemas becuase by definition this is the NEW entities we created
|
|
150
155
|
}
|
|
151
156
|
const veResult = await this.manageVirtualEntities(pool);
|
|
152
157
|
if (!veResult.success) {
|
|
153
|
-
|
|
158
|
+
logError(' Error managing virtual entities');
|
|
154
159
|
bSuccess = false;
|
|
155
160
|
}
|
|
156
161
|
start = new Date();
|
|
157
|
-
|
|
162
|
+
logStatus(' Syncing schema info from database...');
|
|
158
163
|
if (!await this.updateSchemaInfoFromDatabase(pool, excludeSchemas)) {
|
|
159
|
-
|
|
164
|
+
logError(' Error syncing schema info');
|
|
160
165
|
bSuccess = false;
|
|
161
166
|
}
|
|
162
|
-
|
|
167
|
+
logStatus(` > Synced schema info in ${(new Date().getTime() - start.getTime()) / 1000} seconds`);
|
|
163
168
|
return bSuccess;
|
|
164
169
|
}
|
|
165
170
|
async manageVirtualEntities(pool) {
|
|
@@ -167,7 +172,7 @@ class ManageMetadataBase {
|
|
|
167
172
|
// virtual entities are records defined in the entity metadata and do NOT define a distinct base table
|
|
168
173
|
// but they do specify a base view. We DO NOT generate a base view for a virtual entity, we simply use it to figure
|
|
169
174
|
// out the fields that should be in the entity definition and add/update/delete the entity definition to match what's in the view when this runs
|
|
170
|
-
const sql = `SELECT * FROM [${
|
|
175
|
+
const sql = `SELECT * FROM [${mj_core_schema()}].vwEntities WHERE VirtualEntity = 1`;
|
|
171
176
|
const virtualEntitiesResult = await pool.request().query(sql);
|
|
172
177
|
const virtualEntities = virtualEntitiesResult.recordset;
|
|
173
178
|
let anyUpdates = false;
|
|
@@ -177,7 +182,7 @@ class ManageMetadataBase {
|
|
|
177
182
|
const { success, updatedEntity } = await this.manageSingleVirtualEntity(pool, ve);
|
|
178
183
|
anyUpdates = anyUpdates || updatedEntity;
|
|
179
184
|
if (!success) {
|
|
180
|
-
|
|
185
|
+
logError(` Error managing virtual entity ${ve.Name}`);
|
|
181
186
|
bSuccess = false;
|
|
182
187
|
}
|
|
183
188
|
}
|
|
@@ -208,7 +213,7 @@ class ManageMetadataBase {
|
|
|
208
213
|
if (veFields && veFields.length > 0) {
|
|
209
214
|
// we have 1+ fields, now loop through them and process each one
|
|
210
215
|
// first though, remove any fields that are no longer in the view
|
|
211
|
-
const md = new
|
|
216
|
+
const md = new Metadata();
|
|
212
217
|
const entity = md.EntityByName(virtualEntity.Name);
|
|
213
218
|
if (entity) {
|
|
214
219
|
const removeList = [];
|
|
@@ -217,7 +222,7 @@ class ManageMetadataBase {
|
|
|
217
222
|
removeList.push(f.ID);
|
|
218
223
|
}
|
|
219
224
|
if (removeList.length > 0) {
|
|
220
|
-
const sqlRemove = `DELETE FROM [${
|
|
225
|
+
const sqlRemove = `DELETE FROM [${mj_core_schema()}].EntityField WHERE ID IN (${removeList.map(removeId => `'${removeId}'`).join(',')})`;
|
|
221
226
|
// this removes the fields that shouldn't be there anymore
|
|
222
227
|
await this.LogSQLAndExecute(pool, sqlRemove, `SQL text to remove fields from entity ${virtualEntity.Name}`);
|
|
223
228
|
bUpdated = true;
|
|
@@ -231,7 +236,7 @@ class ManageMetadataBase {
|
|
|
231
236
|
const { success, updatedField } = await this.manageSingleVirtualEntityField(pool, virtualEntity, vef, i + 1, !hasPkey && i === 0);
|
|
232
237
|
bUpdated = bUpdated || updatedField;
|
|
233
238
|
if (!success) {
|
|
234
|
-
|
|
239
|
+
logError(`Error managing virtual entity field ${vef.FieldName} for virtual entity ${virtualEntity.Name}`);
|
|
235
240
|
bSuccess = false;
|
|
236
241
|
}
|
|
237
242
|
}
|
|
@@ -239,13 +244,13 @@ class ManageMetadataBase {
|
|
|
239
244
|
}
|
|
240
245
|
if (bUpdated) {
|
|
241
246
|
// finally make sure we update the UpdatedAt field for the entity if we made changes to its fields
|
|
242
|
-
const sqlUpdate = `UPDATE [${
|
|
247
|
+
const sqlUpdate = `UPDATE [${mj_core_schema()}].Entity SET [${EntityInfo.UpdatedAtFieldName}]=GETUTCDATE() WHERE ID='${virtualEntity.ID}'`;
|
|
243
248
|
await this.LogSQLAndExecute(pool, sqlUpdate, `SQL text to update virtual entity updated date for ${virtualEntity.Name}`);
|
|
244
249
|
}
|
|
245
250
|
return { success: bSuccess, updatedEntity: bUpdated };
|
|
246
251
|
}
|
|
247
252
|
catch (e) {
|
|
248
|
-
|
|
253
|
+
logError(e);
|
|
249
254
|
return { success: false, updatedEntity: bUpdated };
|
|
250
255
|
}
|
|
251
256
|
}
|
|
@@ -253,7 +258,7 @@ class ManageMetadataBase {
|
|
|
253
258
|
// this protected checks to see if the field exists in the entity definition, and if not, adds it
|
|
254
259
|
// if it exist it updates the entity field to match the view's data type and nullability attributes
|
|
255
260
|
// first, get the entity definition
|
|
256
|
-
const md = new
|
|
261
|
+
const md = new Metadata();
|
|
257
262
|
const entity = md.EntityByName(virtualEntity.Name);
|
|
258
263
|
let newEntityFieldUUID = null;
|
|
259
264
|
let didUpdate = false;
|
|
@@ -270,7 +275,7 @@ class ManageMetadataBase {
|
|
|
270
275
|
field.Sequence !== fieldSequence) {
|
|
271
276
|
// the field needs to be updated, so update it
|
|
272
277
|
const sqlUpdate = `UPDATE
|
|
273
|
-
[${
|
|
278
|
+
[${mj_core_schema()}].EntityField
|
|
274
279
|
SET
|
|
275
280
|
Sequence=${fieldSequence},
|
|
276
281
|
Type='${veField.Type}',
|
|
@@ -288,7 +293,7 @@ class ManageMetadataBase {
|
|
|
288
293
|
else {
|
|
289
294
|
// this means that we do NOT have a match so the field does not exist in the entity definition, so we need to add it
|
|
290
295
|
newEntityFieldUUID = this.createNewUUID();
|
|
291
|
-
const sqlAdd = `INSERT INTO [${
|
|
296
|
+
const sqlAdd = `INSERT INTO [${mj_core_schema()}].EntityField (
|
|
292
297
|
ID, EntityID, Name, Type, AllowsNull,
|
|
293
298
|
Length, Precision, Scale,
|
|
294
299
|
Sequence, IsPrimaryKey, IsUnique )
|
|
@@ -338,16 +343,16 @@ class ManageMetadataBase {
|
|
|
338
343
|
try {
|
|
339
344
|
// STEP 1 - search for all foreign keys in the vwEntityFields view, we use the RelatedEntityID field to determine our FKs
|
|
340
345
|
const sSQL = `SELECT *
|
|
341
|
-
FROM ${
|
|
346
|
+
FROM ${mj_core_schema()}.vwEntityFields
|
|
342
347
|
WHERE
|
|
343
348
|
RelatedEntityID IS NOT NULL AND
|
|
344
349
|
IsVirtual = 0 AND
|
|
345
|
-
EntityID NOT IN (SELECT ID FROM ${
|
|
350
|
+
EntityID NOT IN (SELECT ID FROM ${mj_core_schema()}.Entity WHERE SchemaName IN (${excludeSchemas.map(s => `'${s}'`).join(',')}))
|
|
346
351
|
ORDER BY RelatedEntityID`;
|
|
347
352
|
const entityFieldsResult = await pool.request().query(sSQL);
|
|
348
353
|
const entityFields = entityFieldsResult.recordset;
|
|
349
354
|
// Get the relationship counts for each entity
|
|
350
|
-
const sSQLRelationshipCount = `SELECT EntityID, COUNT(*) AS Count FROM ${
|
|
355
|
+
const sSQLRelationshipCount = `SELECT EntityID, COUNT(*) AS Count FROM ${mj_core_schema()}.EntityRelationship GROUP BY EntityID`;
|
|
351
356
|
const relationshipCountsResult = await pool.request().query(sSQLRelationshipCount);
|
|
352
357
|
const relationshipCounts = relationshipCountsResult.recordset;
|
|
353
358
|
const relationshipCountMap = new Map();
|
|
@@ -355,7 +360,7 @@ class ManageMetadataBase {
|
|
|
355
360
|
relationshipCountMap.set(rc.EntityID, rc.Count);
|
|
356
361
|
}
|
|
357
362
|
// get all relationships in one query for performance improvement
|
|
358
|
-
const sSQLRelationship = `SELECT * FROM ${
|
|
363
|
+
const sSQLRelationship = `SELECT * FROM ${mj_core_schema()}.EntityRelationship`;
|
|
359
364
|
const allRelationshipsResult = await pool.request().query(sSQLRelationship);
|
|
360
365
|
const allRelationships = allRelationshipsResult.recordset;
|
|
361
366
|
// Function to process a batch of entity fields
|
|
@@ -374,11 +379,11 @@ class ManageMetadataBase {
|
|
|
374
379
|
batchSQL += `
|
|
375
380
|
IF NOT EXISTS (
|
|
376
381
|
SELECT 1
|
|
377
|
-
FROM [${
|
|
382
|
+
FROM [${mj_core_schema()}].EntityRelationship
|
|
378
383
|
WHERE ID = '${newEntityRelationshipUUID}'
|
|
379
384
|
)
|
|
380
385
|
BEGIN
|
|
381
|
-
INSERT INTO ${
|
|
386
|
+
INSERT INTO ${mj_core_schema()}.EntityRelationship (ID, EntityID, RelatedEntityID, RelatedEntityJoinField, Type, BundleInAPI, DisplayInForm, DisplayName, Sequence)
|
|
382
387
|
VALUES ('${newEntityRelationshipUUID}', '${f.RelatedEntityID}', '${f.EntityID}', '${f.Name}', 'One To Many', 1, 1, '${e.Name}', ${sequence});
|
|
383
388
|
END
|
|
384
389
|
`;
|
|
@@ -398,7 +403,7 @@ class ManageMetadataBase {
|
|
|
398
403
|
return true;
|
|
399
404
|
}
|
|
400
405
|
catch (e) {
|
|
401
|
-
|
|
406
|
+
logError(e);
|
|
402
407
|
return false;
|
|
403
408
|
}
|
|
404
409
|
}
|
|
@@ -410,7 +415,7 @@ class ManageMetadataBase {
|
|
|
410
415
|
*/
|
|
411
416
|
async checkAndRemoveMetadataForDeletedTables(pool, excludeSchemas) {
|
|
412
417
|
try {
|
|
413
|
-
const sql = `SELECT * FROM ${
|
|
418
|
+
const sql = `SELECT * FROM ${mj_core_schema()}.vwEntitiesWithMissingBaseTables WHERE VirtualEntity=0`;
|
|
414
419
|
const entitiesResult = await pool.request().query(sql);
|
|
415
420
|
const entities = entitiesResult.recordset;
|
|
416
421
|
if (entities && entities.length > 0) {
|
|
@@ -421,7 +426,7 @@ class ManageMetadataBase {
|
|
|
421
426
|
try {
|
|
422
427
|
const sqlDelete = `__mj.spDeleteEntityWithCoreDependencies @EntityID='${e.ID}'`;
|
|
423
428
|
await this.LogSQLAndExecute(pool, sqlDelete, `SQL text to remove entity ${e.Name}`);
|
|
424
|
-
|
|
429
|
+
logStatus(` > Removed metadata for table ${e.SchemaName}.${e.BaseTable}`);
|
|
425
430
|
// next up we need to remove the spCreate, spDelete, spUpdate, BaseView, and FullTextSearchFunction, if provided.
|
|
426
431
|
// We only remoe these artifcacts when they are generated which is info we have in the BaseViewGenerated, spCreateGenerated, etc. fields
|
|
427
432
|
await this.checkDropSQLObject(pool, e.BaseViewGenerated, 'view', e.SchemaName, e.BaseView);
|
|
@@ -431,17 +436,17 @@ class ManageMetadataBase {
|
|
|
431
436
|
await this.checkDropSQLObject(pool, e.FullTextSearchFunctionGenerated, 'function', e.SchemaName, e.FullTextSearchFunction);
|
|
432
437
|
}
|
|
433
438
|
catch (ex) {
|
|
434
|
-
|
|
439
|
+
logError(`Error removing metadata for entity ${ex.Name}, error: ${ex}`);
|
|
435
440
|
}
|
|
436
441
|
}
|
|
437
442
|
// if we get here we now need to refresh our metadata object
|
|
438
|
-
const md = new
|
|
443
|
+
const md = new Metadata();
|
|
439
444
|
await md.Refresh();
|
|
440
445
|
}
|
|
441
446
|
return true;
|
|
442
447
|
}
|
|
443
448
|
catch (e) {
|
|
444
|
-
|
|
449
|
+
logError(e);
|
|
445
450
|
return false;
|
|
446
451
|
}
|
|
447
452
|
}
|
|
@@ -455,23 +460,23 @@ class ManageMetadataBase {
|
|
|
455
460
|
const sqlDelete = `IF OBJECT_ID('[${schemaName}].[${name}]', '${objectTypeCode}') IS NOT NULL\n DROP ${upperType} [${schemaName}].[${name}]`;
|
|
456
461
|
await this.LogSQLAndExecute(pool, sqlDelete, `SQL text to remove ${type} ${schemaName}.${name}`);
|
|
457
462
|
// next up, we need to clean up the cache of saved DB objects that may exist for this entity in the appropriate sub-directory.
|
|
458
|
-
const sqlOutputDir =
|
|
463
|
+
const sqlOutputDir = outputDir('SQL', true);
|
|
459
464
|
if (sqlOutputDir) {
|
|
460
465
|
// now do the same thing for the /schema directory within the provided directory
|
|
461
466
|
const fType = type === 'procedure' ? 'sp' : type === 'view' ? 'view' : 'full_text_search_function';
|
|
462
|
-
const filePath =
|
|
463
|
-
const filePathPermissions =
|
|
467
|
+
const filePath = path.join(sqlOutputDir, this.SQLUtilityObject.getDBObjectFileName(fType, schemaName, name, false, true));
|
|
468
|
+
const filePathPermissions = path.join(sqlOutputDir, this.SQLUtilityObject.getDBObjectFileName(fType, schemaName, name, true, true));
|
|
464
469
|
// if the files exist, delete them
|
|
465
470
|
if (fs.existsSync(filePath))
|
|
466
471
|
fs.unlinkSync(filePath);
|
|
467
472
|
if (fs.existsSync(filePathPermissions))
|
|
468
473
|
fs.unlinkSync(filePathPermissions);
|
|
469
474
|
}
|
|
470
|
-
|
|
475
|
+
logStatus(` > Removed ${type} ${schemaName}.${name}`);
|
|
471
476
|
}
|
|
472
477
|
}
|
|
473
478
|
catch (e) {
|
|
474
|
-
|
|
479
|
+
logError(` > Error removing ${type} ${schemaName}.${name}, error: ${e}`);
|
|
475
480
|
}
|
|
476
481
|
}
|
|
477
482
|
/**
|
|
@@ -497,66 +502,72 @@ class ManageMetadataBase {
|
|
|
497
502
|
if (!skipCreatedAtUpdatedAtDeletedAtFieldValidation) {
|
|
498
503
|
if (!await this.ensureCreatedAtUpdatedAtFieldsExist(pool, excludeSchemas) ||
|
|
499
504
|
!await this.ensureDeletedAtFieldsExist(pool, excludeSchemas)) {
|
|
500
|
-
|
|
505
|
+
logError(`Error ensuring ${EntityInfo.CreatedAtFieldName}, ${EntityInfo.UpdatedAtFieldName} and ${EntityInfo.DeletedAtFieldName} fields exist`);
|
|
501
506
|
bSuccess = false;
|
|
502
507
|
}
|
|
503
|
-
|
|
508
|
+
logStatus(` Ensured ${EntityInfo.CreatedAtFieldName}/${EntityInfo.UpdatedAtFieldName}/${EntityInfo.DeletedAtFieldName} fields exist in ${(new Date().getTime() - startTime.getTime()) / 1000} seconds`);
|
|
504
509
|
}
|
|
505
510
|
const step1StartTime = new Date();
|
|
506
511
|
if (!await this.deleteUnneededEntityFields(pool, excludeSchemas)) {
|
|
507
|
-
|
|
512
|
+
logError('Error deleting unneeded entity fields');
|
|
508
513
|
bSuccess = false;
|
|
509
514
|
}
|
|
510
|
-
|
|
515
|
+
logStatus(` Deleted unneeded entity fields in ${(new Date().getTime() - step1StartTime.getTime()) / 1000} seconds`);
|
|
511
516
|
// AN: 14-June-2025 - See note below about the new order of these steps, this must
|
|
512
517
|
// happen before we update existing entity fields from schema.
|
|
513
518
|
const step2StartTime = new Date();
|
|
514
519
|
if (!await this.createNewEntityFieldsFromSchema(pool)) { // has its own internal filtering for exclude schema/table so don't pass in
|
|
515
|
-
|
|
520
|
+
logError('Error creating new entity fields from schema');
|
|
516
521
|
bSuccess = false;
|
|
517
522
|
}
|
|
518
|
-
|
|
523
|
+
logStatus(` Created new entity fields from schema in ${(new Date().getTime() - step2StartTime.getTime()) / 1000} seconds`);
|
|
519
524
|
// AN: 14-June-2025 - we are now running this AFTER we create new entity fields from schema
|
|
520
525
|
// which results in the same pattern of behavior as migrations where we first create new fields
|
|
521
526
|
// with VERY HIGH sequence numbers (e.g. 100,000 above what they will be approx) and then
|
|
522
527
|
// we align them properly in sequential order from 1+ via this method below.
|
|
523
528
|
const step3StartTime = new Date();
|
|
524
529
|
if (!await this.updateExistingEntityFieldsFromSchema(pool, excludeSchemas)) {
|
|
525
|
-
|
|
530
|
+
logError('Error updating existing entity fields from schema');
|
|
526
531
|
bSuccess = false;
|
|
527
532
|
}
|
|
528
|
-
|
|
533
|
+
logStatus(` Updated existing entity fields from schema in ${(new Date().getTime() - step3StartTime.getTime()) / 1000} seconds`);
|
|
534
|
+
// Apply soft PK/FK configuration if config file exists
|
|
535
|
+
const stepConfigStartTime = new Date();
|
|
536
|
+
if (!await this.applySoftPKFKConfig(pool)) {
|
|
537
|
+
logError('Error applying soft PK/FK configuration');
|
|
538
|
+
}
|
|
539
|
+
logStatus(` Applied soft PK/FK configuration in ${(new Date().getTime() - stepConfigStartTime.getTime()) / 1000} seconds`);
|
|
529
540
|
const step4StartTime = new Date();
|
|
530
541
|
if (!await this.setDefaultColumnWidthWhereNeeded(pool, excludeSchemas)) {
|
|
531
|
-
|
|
542
|
+
logError('Error setting default column width where needed');
|
|
532
543
|
bSuccess = false;
|
|
533
544
|
}
|
|
534
|
-
|
|
545
|
+
logStatus(` Set default column width where needed in ${(new Date().getTime() - step4StartTime.getTime()) / 1000} seconds`);
|
|
535
546
|
const step5StartTime = new Date();
|
|
536
547
|
if (!await this.updateEntityFieldDisplayNameWhereNull(pool, excludeSchemas)) {
|
|
537
|
-
|
|
548
|
+
logError('Error updating entity field display name where null');
|
|
538
549
|
bSuccess = false;
|
|
539
550
|
}
|
|
540
|
-
|
|
551
|
+
logStatus(` Updated entity field display name where null in ${(new Date().getTime() - step5StartTime.getTime()) / 1000} seconds`);
|
|
541
552
|
if (!skipEntityFieldValues) {
|
|
542
553
|
const step6StartTime = new Date();
|
|
543
|
-
|
|
554
|
+
logStatus(` Starting to manage entity field values...`);
|
|
544
555
|
if (!await this.manageEntityFieldValuesAndValidatorFunctions(pool, excludeSchemas, currentUser, false)) {
|
|
545
|
-
|
|
556
|
+
logError('Error managing entity field values');
|
|
546
557
|
bSuccess = false;
|
|
547
558
|
}
|
|
548
|
-
|
|
559
|
+
logStatus(` Managed entity field values in ${(new Date().getTime() - step6StartTime.getTime()) / 1000} seconds`);
|
|
549
560
|
}
|
|
550
561
|
// Advanced Generation - Smart field identification and form layout
|
|
551
562
|
if (!skipAdvancedGeneration) {
|
|
552
563
|
const step7StartTime = new Date();
|
|
553
564
|
if (!await this.applyAdvancedGeneration(pool, excludeSchemas, currentUser)) {
|
|
554
|
-
|
|
565
|
+
logError('Error applying advanced generation features');
|
|
555
566
|
// Don't fail the entire process - advanced generation is optional
|
|
556
567
|
}
|
|
557
|
-
|
|
568
|
+
logStatus(` Applied advanced generation features in ${(new Date().getTime() - step7StartTime.getTime()) / 1000} seconds`);
|
|
558
569
|
}
|
|
559
|
-
|
|
570
|
+
logStatus(` Total time to manage entity fields: ${(new Date().getTime() - startTime.getTime()) / 1000} seconds`);
|
|
560
571
|
return bSuccess;
|
|
561
572
|
}
|
|
562
573
|
/**
|
|
@@ -567,7 +578,7 @@ class ManageMetadataBase {
|
|
|
567
578
|
const sqlEntities = `SELECT
|
|
568
579
|
*
|
|
569
580
|
FROM
|
|
570
|
-
[${
|
|
581
|
+
[${mj_core_schema()}].vwEntities
|
|
571
582
|
WHERE
|
|
572
583
|
VirtualEntity=0 AND
|
|
573
584
|
DeleteType='Soft' AND
|
|
@@ -582,21 +593,102 @@ class ManageMetadataBase {
|
|
|
582
593
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
583
594
|
WHERE
|
|
584
595
|
${entities.map((e) => `(TABLE_SCHEMA='${e.SchemaName}' AND TABLE_NAME='${e.BaseTable}')`).join(' OR ')}
|
|
585
|
-
AND COLUMN_NAME='${
|
|
596
|
+
AND COLUMN_NAME='${EntityInfo.DeletedAtFieldName}'`;
|
|
586
597
|
const resultResult = await pool.request().query(sql);
|
|
587
598
|
const result = resultResult.recordset;
|
|
588
599
|
for (const e of entities) {
|
|
589
600
|
const eResult = result.filter((r) => r.TABLE_NAME === e.BaseTable && r.TABLE_SCHEMA === e.SchemaName); // get just the fields for this entity
|
|
590
|
-
const deletedAt = eResult.find((r) => r.COLUMN_NAME.trim().toLowerCase() ===
|
|
601
|
+
const deletedAt = eResult.find((r) => r.COLUMN_NAME.trim().toLowerCase() === EntityInfo.DeletedAtFieldName.trim().toLowerCase());
|
|
591
602
|
// now, if we have the fields, we need to check the default value and update if necessary
|
|
592
|
-
const fieldResult = await this.ensureSpecialDateFieldExistsAndHasCorrectDefaultValue(pool, e,
|
|
603
|
+
const fieldResult = await this.ensureSpecialDateFieldExistsAndHasCorrectDefaultValue(pool, e, EntityInfo.DeletedAtFieldName, deletedAt, true);
|
|
593
604
|
overallResult = overallResult && fieldResult;
|
|
594
605
|
}
|
|
595
606
|
}
|
|
596
607
|
return overallResult;
|
|
597
608
|
}
|
|
598
609
|
catch (e) {
|
|
599
|
-
|
|
610
|
+
logError(e);
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Applies soft PK/FK configuration from a JSON file specified in mj.config.cjs (additionalSchemaInfo property).
|
|
616
|
+
* For soft PKs: Sets BOTH IsPrimaryKey=1 AND IsSoftPrimaryKey=1 (IsPrimaryKey is source of truth, IsSoftPrimaryKey protects from schema sync).
|
|
617
|
+
* For soft FKs: Sets RelatedEntityID/RelatedEntityFieldName + IsSoftForeignKey=1 (RelatedEntityID is source of truth, IsSoftForeignKey protects from schema sync).
|
|
618
|
+
* All UPDATE statements are logged to migration files via LogSQLAndExecute() for CI/CD traceability.
|
|
619
|
+
*/
|
|
620
|
+
async applySoftPKFKConfig(pool) {
|
|
621
|
+
// Check if additionalSchemaInfo is configured in mj.config.cjs
|
|
622
|
+
if (!configInfo.additionalSchemaInfo) {
|
|
623
|
+
// No additional schema info configured - this is fine, it's optional
|
|
624
|
+
return true;
|
|
625
|
+
}
|
|
626
|
+
const configPath = path.join(currentWorkingDirectory, configInfo.additionalSchemaInfo);
|
|
627
|
+
if (!fs.existsSync(configPath)) {
|
|
628
|
+
logStatus(` ⚠️ additionalSchemaInfo configured but file not found: ${configPath}`);
|
|
629
|
+
return true;
|
|
630
|
+
}
|
|
631
|
+
try {
|
|
632
|
+
logStatus(` Found ${configInfo.additionalSchemaInfo}, applying soft PK/FK configuration...`);
|
|
633
|
+
const config = ManageMetadataBase.getSoftPKFKConfig();
|
|
634
|
+
let totalPKs = 0;
|
|
635
|
+
let totalFKs = 0;
|
|
636
|
+
const schema = mj_core_schema();
|
|
637
|
+
for (const table of config.tables || []) {
|
|
638
|
+
// Look up entity ID (SELECT query - no need to log to migration file)
|
|
639
|
+
const entityLookupSQL = `SELECT ID FROM [${schema}].[Entity] WHERE SchemaName = '${table.schemaName}' AND BaseTable = '${table.tableName}'`;
|
|
640
|
+
const entityResult = await pool.request().query(entityLookupSQL);
|
|
641
|
+
if (entityResult.recordset.length === 0) {
|
|
642
|
+
logStatus(` ⚠️ Entity not found for ${table.schemaName}.${table.tableName} - skipping`);
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
const entityId = entityResult.recordset[0].ID;
|
|
646
|
+
// Process primary keys - set BOTH IsPrimaryKey = 1 AND IsSoftPrimaryKey = 1
|
|
647
|
+
// IsPrimaryKey is the source of truth, IsSoftPrimaryKey protects it from schema sync
|
|
648
|
+
if (table.primaryKeys && table.primaryKeys.length > 0) {
|
|
649
|
+
for (const pk of table.primaryKeys) {
|
|
650
|
+
const sSQL = `UPDATE [${schema}].[EntityField]
|
|
651
|
+
SET ${EntityInfo.UpdatedAtFieldName}=GETUTCDATE(),
|
|
652
|
+
[IsPrimaryKey] = 1,
|
|
653
|
+
[IsSoftPrimaryKey] = 1
|
|
654
|
+
WHERE [EntityID] = '${entityId}' AND [Name] = '${pk.fieldName}'`;
|
|
655
|
+
const result = await this.LogSQLAndExecute(pool, sSQL, `Set soft PK for ${table.schemaName}.${table.tableName}.${pk.fieldName}`);
|
|
656
|
+
if (result !== null) {
|
|
657
|
+
logStatus(` ✓ Set IsPrimaryKey=1, IsSoftPrimaryKey=1 for ${table.tableName}.${pk.fieldName}`);
|
|
658
|
+
totalPKs++;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
// Process foreign keys - set RelatedEntityID, RelatedEntityFieldName, and IsSoftForeignKey = 1
|
|
663
|
+
if (table.foreignKeys && table.foreignKeys.length > 0) {
|
|
664
|
+
for (const fk of table.foreignKeys) {
|
|
665
|
+
// Look up related entity ID (SELECT query - no need to log to migration file)
|
|
666
|
+
const relatedLookupSQL = `SELECT ID FROM [${schema}].[Entity] WHERE SchemaName = '${fk.relatedSchema}' AND BaseTable = '${fk.relatedTable}'`;
|
|
667
|
+
const relatedEntityResult = await pool.request().query(relatedLookupSQL);
|
|
668
|
+
if (relatedEntityResult.recordset.length === 0) {
|
|
669
|
+
logStatus(` ⚠️ Related entity not found for ${fk.relatedSchema}.${fk.relatedTable} - skipping FK ${fk.fieldName}`);
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
const relatedEntityId = relatedEntityResult.recordset[0].ID;
|
|
673
|
+
const sSQL = `UPDATE [${schema}].[EntityField]
|
|
674
|
+
SET ${EntityInfo.UpdatedAtFieldName}=GETUTCDATE(),
|
|
675
|
+
[RelatedEntityID] = '${relatedEntityId}',
|
|
676
|
+
[RelatedEntityFieldName] = '${fk.relatedField}',
|
|
677
|
+
[IsSoftForeignKey] = 1
|
|
678
|
+
WHERE [EntityID] = '${entityId}' AND [Name] = '${fk.fieldName}'`;
|
|
679
|
+
const result = await this.LogSQLAndExecute(pool, sSQL, `Set soft FK for ${table.schemaName}.${table.tableName}.${fk.fieldName} → ${fk.relatedTable}.${fk.relatedField}`);
|
|
680
|
+
if (result !== null) {
|
|
681
|
+
logStatus(` ✓ Set soft FK for ${table.tableName}.${fk.fieldName} → ${fk.relatedTable}.${fk.relatedField}`);
|
|
682
|
+
totalFKs++;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
logStatus(` Applied ${totalPKs} soft PK(s) and ${totalFKs} soft FK(s) from configuration`);
|
|
688
|
+
return true;
|
|
689
|
+
}
|
|
690
|
+
catch (e) {
|
|
691
|
+
logError(`Error applying soft PK/FK configuration: ${e}`);
|
|
600
692
|
return false;
|
|
601
693
|
}
|
|
602
694
|
}
|
|
@@ -611,7 +703,7 @@ class ManageMetadataBase {
|
|
|
611
703
|
const sqlEntities = `SELECT
|
|
612
704
|
*
|
|
613
705
|
FROM
|
|
614
|
-
[${
|
|
706
|
+
[${mj_core_schema()}].vwEntities
|
|
615
707
|
WHERE
|
|
616
708
|
VirtualEntity = 0 AND
|
|
617
709
|
TrackRecordChanges = 1 AND
|
|
@@ -627,24 +719,24 @@ class ManageMetadataBase {
|
|
|
627
719
|
FROM INFORMATION_SCHEMA.COLUMNS
|
|
628
720
|
WHERE
|
|
629
721
|
${entities.map((e) => `(TABLE_SCHEMA='${e.SchemaName}' AND TABLE_NAME='${e.BaseTable}')`).join(' OR ')}
|
|
630
|
-
AND COLUMN_NAME IN ('${
|
|
722
|
+
AND COLUMN_NAME IN ('${EntityInfo.CreatedAtFieldName}','${EntityInfo.UpdatedAtFieldName}')`;
|
|
631
723
|
const resultResult = await pool.request().query(sqlCreatedUpdated);
|
|
632
724
|
const result = resultResult.recordset;
|
|
633
725
|
for (const e of entities) {
|
|
634
726
|
// result has both created at and updated at fields, so filter on the result for each and do what we need to based on that
|
|
635
727
|
const eResult = result.filter((r) => r.TABLE_NAME === e.BaseTable && r.TABLE_SCHEMA === e.SchemaName); // get just the fields for this entity
|
|
636
|
-
const createdAt = eResult.find((r) => r.COLUMN_NAME.trim().toLowerCase() ===
|
|
637
|
-
const updatedAt = eResult.find((r) => r.COLUMN_NAME.trim().toLowerCase() ===
|
|
728
|
+
const createdAt = eResult.find((r) => r.COLUMN_NAME.trim().toLowerCase() === EntityInfo.CreatedAtFieldName.trim().toLowerCase());
|
|
729
|
+
const updatedAt = eResult.find((r) => r.COLUMN_NAME.trim().toLowerCase() === EntityInfo.UpdatedAtFieldName.trim().toLowerCase());
|
|
638
730
|
// now, if we have the fields, we need to check the default value and update if necessary
|
|
639
|
-
const fieldResult = await this.ensureSpecialDateFieldExistsAndHasCorrectDefaultValue(pool, e,
|
|
640
|
-
await this.ensureSpecialDateFieldExistsAndHasCorrectDefaultValue(pool, e,
|
|
731
|
+
const fieldResult = await this.ensureSpecialDateFieldExistsAndHasCorrectDefaultValue(pool, e, EntityInfo.CreatedAtFieldName, createdAt, false) &&
|
|
732
|
+
await this.ensureSpecialDateFieldExistsAndHasCorrectDefaultValue(pool, e, EntityInfo.UpdatedAtFieldName, updatedAt, false);
|
|
641
733
|
overallResult = overallResult && fieldResult;
|
|
642
734
|
}
|
|
643
735
|
}
|
|
644
736
|
return overallResult;
|
|
645
737
|
}
|
|
646
738
|
catch (e) {
|
|
647
|
-
|
|
739
|
+
logError(e);
|
|
648
740
|
return false;
|
|
649
741
|
}
|
|
650
742
|
}
|
|
@@ -680,7 +772,7 @@ class ManageMetadataBase {
|
|
|
680
772
|
// field that is NOT NULL
|
|
681
773
|
if (!allowNull) {
|
|
682
774
|
const defaultValue = currentFieldData.COLUMN_DEFAULT;
|
|
683
|
-
const realDefaultValue =
|
|
775
|
+
const realDefaultValue = ExtractActualDefaultValue(defaultValue);
|
|
684
776
|
if (!realDefaultValue || realDefaultValue.trim().toLowerCase() !== 'getutcdate()') {
|
|
685
777
|
await this.dropAndCreateDefaultConstraintForSpecialDateField(pool, entity, fieldName);
|
|
686
778
|
}
|
|
@@ -691,7 +783,7 @@ class ManageMetadataBase {
|
|
|
691
783
|
return true;
|
|
692
784
|
}
|
|
693
785
|
catch (e) {
|
|
694
|
-
|
|
786
|
+
logError(e);
|
|
695
787
|
return false;
|
|
696
788
|
}
|
|
697
789
|
}
|
|
@@ -700,11 +792,11 @@ class ManageMetadataBase {
|
|
|
700
792
|
*/
|
|
701
793
|
async createDefaultConstraintForSpecialDateField(pool, entity, fieldName) {
|
|
702
794
|
try {
|
|
703
|
-
const sqlAddDefaultConstraint = `ALTER TABLE [${entity.SchemaName}].[${entity.BaseTable}] ADD CONSTRAINT DF_${entity.SchemaName}_${
|
|
795
|
+
const sqlAddDefaultConstraint = `ALTER TABLE [${entity.SchemaName}].[${entity.BaseTable}] ADD CONSTRAINT DF_${entity.SchemaName}_${CodeNameFromString(entity.BaseTable)}_${fieldName} DEFAULT GETUTCDATE() FOR [${fieldName}]`;
|
|
704
796
|
await this.LogSQLAndExecute(pool, sqlAddDefaultConstraint, `SQL text to add default constraint for special date field ${fieldName} in entity ${entity.SchemaName}.${entity.BaseTable}`);
|
|
705
797
|
}
|
|
706
798
|
catch (e) {
|
|
707
|
-
|
|
799
|
+
logError(e);
|
|
708
800
|
}
|
|
709
801
|
}
|
|
710
802
|
/**
|
|
@@ -748,7 +840,7 @@ class ManageMetadataBase {
|
|
|
748
840
|
await this.LogSQLAndExecute(pool, sqlDropDefaultConstraint, `SQL text to drop default existing default constraints in entity ${entity.SchemaName}.${entity.BaseTable}`);
|
|
749
841
|
}
|
|
750
842
|
catch (e) {
|
|
751
|
-
|
|
843
|
+
logError(e);
|
|
752
844
|
}
|
|
753
845
|
}
|
|
754
846
|
/**
|
|
@@ -760,18 +852,18 @@ class ManageMetadataBase {
|
|
|
760
852
|
*/
|
|
761
853
|
async generateNewEntityDescriptions(pool, md, currentUser) {
|
|
762
854
|
// for the list of new entities, go through and attempt to generate new entity descriptions
|
|
763
|
-
const ag = new
|
|
855
|
+
const ag = new AdvancedGeneration();
|
|
764
856
|
if (ag.featureEnabled('EntityDescriptions')) {
|
|
765
857
|
// we have the feature enabled, so let's loop through the new entities and generate descriptions for them
|
|
766
858
|
for (let e of ManageMetadataBase.newEntityList) {
|
|
767
|
-
const dataResult = await pool.request().query(`SELECT * FROM [${
|
|
859
|
+
const dataResult = await pool.request().query(`SELECT * FROM [${mj_core_schema()}].vwEntities WHERE Name = '${e}'`);
|
|
768
860
|
const data = dataResult.recordset;
|
|
769
|
-
const fieldsResult = await pool.request().query(`SELECT * FROM [${
|
|
861
|
+
const fieldsResult = await pool.request().query(`SELECT * FROM [${mj_core_schema()}].vwEntityFields WHERE EntityID='${data[0].ID}'`);
|
|
770
862
|
const fields = fieldsResult.recordset;
|
|
771
863
|
// Use new API to generate entity description
|
|
772
864
|
const result = await ag.generateEntityDescription(e, data[0].BaseTable, fields.map((f) => ({ Name: f.Name, Type: f.Type, IsNullable: f.AllowsNull, Description: f.Description })), currentUser);
|
|
773
865
|
if (result?.entityDescription && result.entityDescription.length > 0) {
|
|
774
|
-
const sSQL = `UPDATE [${
|
|
866
|
+
const sSQL = `UPDATE [${mj_core_schema()}].Entity SET Description = '${result.entityDescription}' WHERE Name = '${e}'`;
|
|
775
867
|
await this.LogSQLAndExecute(pool, sSQL, `SQL text to update entity description for entity ${e}`);
|
|
776
868
|
}
|
|
777
869
|
else {
|
|
@@ -793,9 +885,9 @@ class ManageMetadataBase {
|
|
|
793
885
|
const sql = `SELECT
|
|
794
886
|
ef.ID, ef.Name
|
|
795
887
|
FROM
|
|
796
|
-
[${
|
|
888
|
+
[${mj_core_schema()}].vwEntityFields ef
|
|
797
889
|
INNER JOIN
|
|
798
|
-
[${
|
|
890
|
+
[${mj_core_schema()}].vwEntities e
|
|
799
891
|
ON
|
|
800
892
|
ef.EntityID = e.ID
|
|
801
893
|
WHERE
|
|
@@ -808,16 +900,16 @@ class ManageMetadataBase {
|
|
|
808
900
|
const fields = fieldsResult.recordset;
|
|
809
901
|
if (fields && fields.length > 0)
|
|
810
902
|
for (const field of fields) {
|
|
811
|
-
const sDisplayName =
|
|
903
|
+
const sDisplayName = stripTrailingChars(convertCamelCaseToHaveSpaces(field.Name), 'ID', true).trim();
|
|
812
904
|
if (sDisplayName.length > 0 && sDisplayName.toLowerCase().trim() !== field.Name.toLowerCase().trim()) {
|
|
813
|
-
const sSQL = `UPDATE [${
|
|
905
|
+
const sSQL = `UPDATE [${mj_core_schema()}].EntityField SET ${EntityInfo.UpdatedAtFieldName}=GETUTCDATE(), DisplayName = '${sDisplayName}' WHERE ID = '${field.ID}'`;
|
|
814
906
|
await this.LogSQLAndExecute(pool, sSQL, `SQL text to update display name for field ${field.Name}`);
|
|
815
907
|
}
|
|
816
908
|
}
|
|
817
909
|
return true;
|
|
818
910
|
}
|
|
819
911
|
catch (e) {
|
|
820
|
-
|
|
912
|
+
logError(e);
|
|
821
913
|
return false;
|
|
822
914
|
}
|
|
823
915
|
}
|
|
@@ -831,12 +923,12 @@ class ManageMetadataBase {
|
|
|
831
923
|
*/
|
|
832
924
|
async setDefaultColumnWidthWhereNeeded(pool, excludeSchemas) {
|
|
833
925
|
try {
|
|
834
|
-
const sSQL = `EXEC ${
|
|
926
|
+
const sSQL = `EXEC ${mj_core_schema()}.spSetDefaultColumnWidthWhereNeeded @ExcludedSchemaNames='${excludeSchemas.join(',')}'`;
|
|
835
927
|
await this.LogSQLAndExecute(pool, sSQL, `SQL text to set default column width where needed`, true);
|
|
836
928
|
return true;
|
|
837
929
|
}
|
|
838
930
|
catch (e) {
|
|
839
|
-
|
|
931
|
+
logError(e);
|
|
840
932
|
return false;
|
|
841
933
|
}
|
|
842
934
|
}
|
|
@@ -858,7 +950,7 @@ class ManageMetadataBase {
|
|
|
858
950
|
EntityID,
|
|
859
951
|
ISNULL(MAX(Sequence), 0) AS MaxSequence
|
|
860
952
|
FROM
|
|
861
|
-
[${
|
|
953
|
+
[${mj_core_schema()}].EntityField
|
|
862
954
|
GROUP BY
|
|
863
955
|
EntityID
|
|
864
956
|
),
|
|
@@ -877,9 +969,9 @@ NumberedRows AS (
|
|
|
877
969
|
sf.AllowsNull,
|
|
878
970
|
sf.DefaultValue,
|
|
879
971
|
sf.AutoIncrement,
|
|
880
|
-
IIF(sf.IsVirtual = 1, 0, IIF(sf.FieldName = '${
|
|
881
|
-
sf.FieldName = '${
|
|
882
|
-
sf.FieldName = '${
|
|
972
|
+
IIF(sf.IsVirtual = 1, 0, IIF(sf.FieldName = '${EntityInfo.CreatedAtFieldName}' OR
|
|
973
|
+
sf.FieldName = '${EntityInfo.UpdatedAtFieldName}' OR
|
|
974
|
+
sf.FieldName = '${EntityInfo.DeletedAtFieldName}' OR
|
|
883
975
|
pk.ColumnName IS NOT NULL, 0, 1)) AllowUpdateAPI,
|
|
884
976
|
sf.IsVirtual,
|
|
885
977
|
e.RelationshipDefaultDisplayType,
|
|
@@ -901,34 +993,34 @@ NumberedRows AS (
|
|
|
901
993
|
END,
|
|
902
994
|
ROW_NUMBER() OVER (PARTITION BY sf.EntityID, sf.FieldName ORDER BY (SELECT NULL)) AS rn
|
|
903
995
|
FROM
|
|
904
|
-
[${
|
|
996
|
+
[${mj_core_schema()}].vwSQLColumnsAndEntityFields sf
|
|
905
997
|
LEFT OUTER JOIN
|
|
906
998
|
MaxSequences ms
|
|
907
999
|
ON
|
|
908
1000
|
sf.EntityID = ms.EntityID
|
|
909
1001
|
LEFT OUTER JOIN
|
|
910
|
-
[${
|
|
1002
|
+
[${mj_core_schema()}].Entity e
|
|
911
1003
|
ON
|
|
912
1004
|
sf.EntityID = e.ID
|
|
913
1005
|
LEFT OUTER JOIN
|
|
914
|
-
[${
|
|
1006
|
+
[${mj_core_schema()}].vwForeignKeys fk
|
|
915
1007
|
ON
|
|
916
1008
|
sf.FieldName = fk.[column] AND
|
|
917
1009
|
e.BaseTable = fk.[table] AND
|
|
918
1010
|
e.SchemaName = fk.[schema_name]
|
|
919
1011
|
LEFT OUTER JOIN
|
|
920
|
-
[${
|
|
1012
|
+
[${mj_core_schema()}].Entity re -- Related Entity
|
|
921
1013
|
ON
|
|
922
1014
|
re.BaseTable = fk.referenced_table AND
|
|
923
1015
|
re.SchemaName = fk.[referenced_schema]
|
|
924
1016
|
LEFT OUTER JOIN
|
|
925
|
-
[${
|
|
1017
|
+
[${mj_core_schema()}].vwTablePrimaryKeys pk
|
|
926
1018
|
ON
|
|
927
1019
|
e.BaseTable = pk.TableName AND
|
|
928
1020
|
sf.FieldName = pk.ColumnName AND
|
|
929
1021
|
e.SchemaName = pk.SchemaName
|
|
930
1022
|
LEFT OUTER JOIN
|
|
931
|
-
[${
|
|
1023
|
+
[${mj_core_schema()}].vwTableUniqueKeys uk
|
|
932
1024
|
ON
|
|
933
1025
|
e.BaseTable = uk.TableName AND
|
|
934
1026
|
sf.FieldName = uk.ColumnName AND
|
|
@@ -969,22 +1061,22 @@ NumberedRows AS (
|
|
|
969
1061
|
const isPrimaryKey = n.FieldName?.trim().toLowerCase() === 'id';
|
|
970
1062
|
const isForeignKey = n.RelatedEntityID && n.RelatedEntityID.length > 0; // Foreign keys have RelatedEntityID set
|
|
971
1063
|
const isNameField = n.FieldName?.trim().toLowerCase() === 'name' || n.IsNameField;
|
|
972
|
-
const isEarlySequence = n.Sequence <=
|
|
1064
|
+
const isEarlySequence = n.Sequence <= configInfo.newEntityDefaults?.IncludeFirstNFieldsAsDefaultInView;
|
|
973
1065
|
const bDefaultInView = (isNameField || isEarlySequence) && !isPrimaryKey && !isForeignKey;
|
|
974
1066
|
const escapedDescription = n.Description ? `'${n.Description.replace(/'/g, "''")}'` : 'NULL';
|
|
975
1067
|
let fieldDisplayName = '';
|
|
976
1068
|
switch (n.FieldName.trim().toLowerCase()) {
|
|
977
|
-
case
|
|
1069
|
+
case EntityInfo.CreatedAtFieldName.trim().toLowerCase():
|
|
978
1070
|
fieldDisplayName = "Created At";
|
|
979
1071
|
break;
|
|
980
|
-
case
|
|
1072
|
+
case EntityInfo.UpdatedAtFieldName.trim().toLowerCase():
|
|
981
1073
|
fieldDisplayName = "Updated At";
|
|
982
1074
|
break;
|
|
983
|
-
case
|
|
1075
|
+
case EntityInfo.DeletedAtFieldName.trim().toLowerCase():
|
|
984
1076
|
fieldDisplayName = "Deleted At";
|
|
985
1077
|
break;
|
|
986
1078
|
default:
|
|
987
|
-
fieldDisplayName =
|
|
1079
|
+
fieldDisplayName = convertCamelCaseToHaveSpaces(n.FieldName).trim();
|
|
988
1080
|
break;
|
|
989
1081
|
}
|
|
990
1082
|
const parsedDefaultValue = this.parseDefaultValue(n.DefaultValue);
|
|
@@ -993,13 +1085,13 @@ NumberedRows AS (
|
|
|
993
1085
|
// in the above we are setting quotedDefaultValue to NULL if the parsed default value is an empty string or the string 'NULL' (case insensitive)
|
|
994
1086
|
return `
|
|
995
1087
|
IF NOT EXISTS (
|
|
996
|
-
SELECT 1 FROM [${
|
|
1088
|
+
SELECT 1 FROM [${mj_core_schema()}].EntityField
|
|
997
1089
|
WHERE ID = '${newEntityFieldUUID}' OR
|
|
998
1090
|
(EntityID = '${n.EntityID}' AND Name = '${n.FieldName}')
|
|
999
1091
|
-- check to make sure we're not inserting a duplicate entity field metadata record
|
|
1000
1092
|
)
|
|
1001
1093
|
BEGIN
|
|
1002
|
-
INSERT INTO [${
|
|
1094
|
+
INSERT INTO [${mj_core_schema()}].EntityField
|
|
1003
1095
|
(
|
|
1004
1096
|
ID,
|
|
1005
1097
|
EntityID,
|
|
@@ -1100,7 +1192,7 @@ NumberedRows AS (
|
|
|
1100
1192
|
}
|
|
1101
1193
|
catch (e) {
|
|
1102
1194
|
// this is here so we can catch the error for debug. We want the transaction to die
|
|
1103
|
-
|
|
1195
|
+
logError(`Error inserting new entity field. SQL: \n${sSQLInsert}`);
|
|
1104
1196
|
throw e;
|
|
1105
1197
|
}
|
|
1106
1198
|
}
|
|
@@ -1118,7 +1210,7 @@ NumberedRows AS (
|
|
|
1118
1210
|
return true;
|
|
1119
1211
|
}
|
|
1120
1212
|
catch (e) {
|
|
1121
|
-
|
|
1213
|
+
logError(e);
|
|
1122
1214
|
return false;
|
|
1123
1215
|
}
|
|
1124
1216
|
}
|
|
@@ -1131,20 +1223,20 @@ NumberedRows AS (
|
|
|
1131
1223
|
*/
|
|
1132
1224
|
async updateEntityFieldRelatedEntityNameFieldMap(pool, entityFieldID, relatedEntityNameFieldMap) {
|
|
1133
1225
|
try {
|
|
1134
|
-
const sSQL = `EXEC [${
|
|
1226
|
+
const sSQL = `EXEC [${mj_core_schema()}].spUpdateEntityFieldRelatedEntityNameFieldMap
|
|
1135
1227
|
@EntityFieldID='${entityFieldID}',
|
|
1136
1228
|
@RelatedEntityNameFieldMap='${relatedEntityNameFieldMap}'`;
|
|
1137
1229
|
await this.LogSQLAndExecute(pool, sSQL, `SQL text to update entity field related entity name field map for entity field ID ${entityFieldID}`);
|
|
1138
1230
|
return true;
|
|
1139
1231
|
}
|
|
1140
1232
|
catch (e) {
|
|
1141
|
-
|
|
1233
|
+
logError(e);
|
|
1142
1234
|
return false;
|
|
1143
1235
|
}
|
|
1144
1236
|
}
|
|
1145
1237
|
async updateExistingEntitiesFromSchema(pool, excludeSchemas) {
|
|
1146
1238
|
try {
|
|
1147
|
-
const sSQL = `EXEC [${
|
|
1239
|
+
const sSQL = `EXEC [${mj_core_schema()}].spUpdateExistingEntitiesFromSchema @ExcludedSchemaNames='${excludeSchemas.join(',')}'`;
|
|
1148
1240
|
const result = await this.LogSQLAndExecute(pool, sSQL, `SQL text to update existing entities from schema`, true);
|
|
1149
1241
|
// result contains the updated entities, and there is a property of each row called Name which has the entity name that was modified
|
|
1150
1242
|
// add these to the modified entity list if they're not already in there
|
|
@@ -1154,7 +1246,7 @@ NumberedRows AS (
|
|
|
1154
1246
|
return true;
|
|
1155
1247
|
}
|
|
1156
1248
|
catch (e) {
|
|
1157
|
-
|
|
1249
|
+
logError(e);
|
|
1158
1250
|
return false;
|
|
1159
1251
|
}
|
|
1160
1252
|
}
|
|
@@ -1169,7 +1261,7 @@ NumberedRows AS (
|
|
|
1169
1261
|
}
|
|
1170
1262
|
async updateExistingEntityFieldsFromSchema(pool, excludeSchemas) {
|
|
1171
1263
|
try {
|
|
1172
|
-
const sSQL = `EXEC [${
|
|
1264
|
+
const sSQL = `EXEC [${mj_core_schema()}].spUpdateExistingEntityFieldsFromSchema @ExcludedSchemaNames='${excludeSchemas.join(',')}'`;
|
|
1173
1265
|
const result = await this.LogSQLAndExecute(pool, sSQL, `SQL text to update existing entity fields from schema`, true);
|
|
1174
1266
|
// result contains the updated entity fields
|
|
1175
1267
|
// there is a field in there called EntityName. Get a distinct list of entity names from this and add them
|
|
@@ -1180,7 +1272,7 @@ NumberedRows AS (
|
|
|
1180
1272
|
return true;
|
|
1181
1273
|
}
|
|
1182
1274
|
catch (e) {
|
|
1183
|
-
|
|
1275
|
+
logError(e);
|
|
1184
1276
|
return false;
|
|
1185
1277
|
}
|
|
1186
1278
|
}
|
|
@@ -1194,21 +1286,21 @@ NumberedRows AS (
|
|
|
1194
1286
|
*/
|
|
1195
1287
|
async updateSchemaInfoFromDatabase(pool, excludeSchemas) {
|
|
1196
1288
|
try {
|
|
1197
|
-
const sSQL = `EXEC [${
|
|
1289
|
+
const sSQL = `EXEC [${mj_core_schema()}].spUpdateSchemaInfoFromDatabase @ExcludedSchemaNames='${excludeSchemas.join(',')}'`;
|
|
1198
1290
|
const result = await this.LogSQLAndExecute(pool, sSQL, `SQL text to sync schema info from database schemas`, true);
|
|
1199
1291
|
if (result && result.length > 0) {
|
|
1200
|
-
|
|
1292
|
+
logStatus(` > Updated/created ${result.length} SchemaInfo records`);
|
|
1201
1293
|
}
|
|
1202
1294
|
return true;
|
|
1203
1295
|
}
|
|
1204
1296
|
catch (e) {
|
|
1205
|
-
|
|
1297
|
+
logError(e);
|
|
1206
1298
|
return false;
|
|
1207
1299
|
}
|
|
1208
1300
|
}
|
|
1209
1301
|
async deleteUnneededEntityFields(pool, excludeSchemas) {
|
|
1210
1302
|
try {
|
|
1211
|
-
const sSQL = `EXEC [${
|
|
1303
|
+
const sSQL = `EXEC [${mj_core_schema()}].spDeleteUnneededEntityFields @ExcludedSchemaNames='${excludeSchemas.join(',')}'`;
|
|
1212
1304
|
const result = await this.LogSQLAndExecute(pool, sSQL, `SQL text to delete unneeded entity fields`, true);
|
|
1213
1305
|
// result contains the DELETED entity fields
|
|
1214
1306
|
// there is a field in there called Entity. Get a distinct list of entity names from this and add them
|
|
@@ -1219,7 +1311,7 @@ NumberedRows AS (
|
|
|
1219
1311
|
return true;
|
|
1220
1312
|
}
|
|
1221
1313
|
catch (e) {
|
|
1222
|
-
|
|
1314
|
+
logError(e);
|
|
1223
1315
|
return false;
|
|
1224
1316
|
}
|
|
1225
1317
|
}
|
|
@@ -1230,13 +1322,13 @@ NumberedRows AS (
|
|
|
1230
1322
|
// for the field and sync that up with the EntityFieldValue table. If it is not a simple series of OR statements, we will not be able to parse it and we'll
|
|
1231
1323
|
// just ignore it.
|
|
1232
1324
|
const filter = excludeSchemas && excludeSchemas.length > 0 ? ` WHERE SchemaName NOT IN (${excludeSchemas.map(s => `'${s}'`).join(',')})` : '';
|
|
1233
|
-
const sSQL = `SELECT * FROM [${
|
|
1325
|
+
const sSQL = `SELECT * FROM [${mj_core_schema()}].vwEntityFieldsWithCheckConstraints${filter}`;
|
|
1234
1326
|
const resultResult = await pool.request().query(sSQL);
|
|
1235
1327
|
const result = resultResult.recordset;
|
|
1236
|
-
const efvSQL = `SELECT * FROM [${
|
|
1328
|
+
const efvSQL = `SELECT * FROM [${mj_core_schema()}].EntityFieldValue`;
|
|
1237
1329
|
const allEntityFieldValuesResult = await pool.request().query(efvSQL);
|
|
1238
1330
|
const allEntityFieldValues = allEntityFieldValuesResult.recordset;
|
|
1239
|
-
const efSQL = `SELECT * FROM [${
|
|
1331
|
+
const efSQL = `SELECT * FROM [${mj_core_schema()}].vwEntityFields ORDER BY EntityID, Sequence`;
|
|
1240
1332
|
const allEntityFieldsResult = await pool.request().query(efSQL);
|
|
1241
1333
|
const allEntityFields = allEntityFieldsResult.recordset;
|
|
1242
1334
|
const generationPromises = [];
|
|
@@ -1258,11 +1350,11 @@ NumberedRows AS (
|
|
|
1258
1350
|
await this.syncEntityFieldValues(pool, r.EntityFieldID, parsedValues, allEntityFieldValues);
|
|
1259
1351
|
// finally, make sure the ValueListType column within the EntityField table is set to "List" because for check constraints we only allow the values specified in the list.
|
|
1260
1352
|
// check to see if the ValueListType is already set to "List", if not, update it
|
|
1261
|
-
const sSQLCheck = `SELECT ValueListType FROM [${
|
|
1353
|
+
const sSQLCheck = `SELECT ValueListType FROM [${mj_core_schema()}].EntityField WHERE ID='${r.EntityFieldID}'`;
|
|
1262
1354
|
const checkResultResult = await pool.request().query(sSQLCheck);
|
|
1263
1355
|
const checkResult = checkResultResult.recordset;
|
|
1264
1356
|
if (checkResult && checkResult.length > 0 && checkResult[0].ValueListType.trim().toLowerCase() !== 'list') {
|
|
1265
|
-
const sSQL = `UPDATE [${
|
|
1357
|
+
const sSQL = `UPDATE [${mj_core_schema()}].EntityField SET ValueListType='List' WHERE ID='${r.EntityFieldID}'`;
|
|
1266
1358
|
await this.LogSQLAndExecute(pool, sSQL, `SQL text to update ValueListType for entity field ID ${r.EntityFieldID}`);
|
|
1267
1359
|
}
|
|
1268
1360
|
}
|
|
@@ -1273,8 +1365,8 @@ NumberedRows AS (
|
|
|
1273
1365
|
else {
|
|
1274
1366
|
// if we get here that means we don't have a simple condition in the check constraint that the RegEx could parse. If Advanced Generation is enabled, we will
|
|
1275
1367
|
// attempt to use an LLM to do things fancier now
|
|
1276
|
-
if (
|
|
1277
|
-
|
|
1368
|
+
if (configInfo.advancedGeneration?.enableAdvancedGeneration &&
|
|
1369
|
+
configInfo.advancedGeneration?.features.find(f => f.name === 'ParseCheckConstraints' && f.enabled)) {
|
|
1278
1370
|
// the user has the feature turned on, let's generate a description of the constraint and then build a Validate function for the constraint
|
|
1279
1371
|
// run this in parallel
|
|
1280
1372
|
generationPromises.push(this.runValidationGeneration(r, allEntityFields, !skipDBUpdate, currentUser));
|
|
@@ -1284,8 +1376,8 @@ NumberedRows AS (
|
|
|
1284
1376
|
}
|
|
1285
1377
|
// now for the table level constraints run the process for advanced generation
|
|
1286
1378
|
for (const r of tableLevelResults) {
|
|
1287
|
-
if (
|
|
1288
|
-
|
|
1379
|
+
if (configInfo.advancedGeneration?.enableAdvancedGeneration &&
|
|
1380
|
+
configInfo.advancedGeneration?.features.find(f => f.name === 'ParseCheckConstraints' && f.enabled)) {
|
|
1289
1381
|
// the user has the feature turned on, let's generate a description of the constraint and then build a Validate function for the constraint
|
|
1290
1382
|
// run this in parallel
|
|
1291
1383
|
generationPromises.push(this.runValidationGeneration(r, allEntityFields, !skipDBUpdate, currentUser));
|
|
@@ -1296,7 +1388,7 @@ NumberedRows AS (
|
|
|
1296
1388
|
return true;
|
|
1297
1389
|
}
|
|
1298
1390
|
catch (e) {
|
|
1299
|
-
|
|
1391
|
+
logError(e);
|
|
1300
1392
|
return false;
|
|
1301
1393
|
}
|
|
1302
1394
|
}
|
|
@@ -1311,7 +1403,7 @@ NumberedRows AS (
|
|
|
1311
1403
|
return await this.manageEntityFieldValuesAndValidatorFunctions(pool, [], currentUser, true);
|
|
1312
1404
|
}
|
|
1313
1405
|
catch (e) {
|
|
1314
|
-
|
|
1406
|
+
logError(e);
|
|
1315
1407
|
return false;
|
|
1316
1408
|
}
|
|
1317
1409
|
}
|
|
@@ -1357,9 +1449,9 @@ NumberedRows AS (
|
|
|
1357
1449
|
}
|
|
1358
1450
|
}
|
|
1359
1451
|
try {
|
|
1360
|
-
if (generateNewCode &&
|
|
1452
|
+
if (generateNewCode && configInfo.advancedGeneration?.enableAdvancedGeneration && configInfo.advancedGeneration?.features.find(f => f.name === 'ParseCheckConstraints' && f.enabled)) {
|
|
1361
1453
|
// feature is enabled, so let's call the AI to generate a function for us
|
|
1362
|
-
const ag = new
|
|
1454
|
+
const ag = new AdvancedGeneration();
|
|
1363
1455
|
const entityFieldListInfo = allEntityFields.filter(item => item.Entity.trim().toLowerCase() === data.EntityName.trim().toLowerCase()).map(item => ` * ${item.Name} - ${item.Type}${item.AllowsNull ? ' (nullable)' : ' (not null)'}`).join('\n');
|
|
1364
1456
|
// Use new API to parse check constraint
|
|
1365
1457
|
const result = await ag.parseCheckConstraint(constraintDefinition, entityFieldListInfo, generatedValidationFunctionName, currentUser);
|
|
@@ -1372,12 +1464,12 @@ NumberedRows AS (
|
|
|
1372
1464
|
returnResult.success = true;
|
|
1373
1465
|
}
|
|
1374
1466
|
else {
|
|
1375
|
-
|
|
1467
|
+
logError(`Error generating field validator function from check constraint for entity ${entityName} and field ${fieldName}. LLM returned invalid result.`);
|
|
1376
1468
|
}
|
|
1377
1469
|
}
|
|
1378
1470
|
}
|
|
1379
1471
|
catch (e) {
|
|
1380
|
-
|
|
1472
|
+
logError(e);
|
|
1381
1473
|
}
|
|
1382
1474
|
finally {
|
|
1383
1475
|
return returnResult;
|
|
@@ -1396,7 +1488,7 @@ NumberedRows AS (
|
|
|
1396
1488
|
for (const ev of existingValues) {
|
|
1397
1489
|
if (!possibleValues.find(v => v === ev.Value)) {
|
|
1398
1490
|
// delete the value from the database
|
|
1399
|
-
const sSQLDelete = `DELETE FROM [${
|
|
1491
|
+
const sSQLDelete = `DELETE FROM [${mj_core_schema()}].EntityFieldValue WHERE ID='${ev.ID}'`;
|
|
1400
1492
|
await this.LogSQLAndExecute(ds, sSQLDelete, `SQL text to delete entity field value ID ${ev.ID}`);
|
|
1401
1493
|
numRemoved++;
|
|
1402
1494
|
}
|
|
@@ -1406,9 +1498,9 @@ NumberedRows AS (
|
|
|
1406
1498
|
for (const v of possibleValues) {
|
|
1407
1499
|
if (!existingValues.find((ev) => ev.Value === v)) {
|
|
1408
1500
|
// Generate a UUID for this new EntityFieldValue record
|
|
1409
|
-
const newId = (
|
|
1501
|
+
const newId = uuidv4();
|
|
1410
1502
|
// add the value to the database with explicit ID
|
|
1411
|
-
const sSQLInsert = `INSERT INTO [${
|
|
1503
|
+
const sSQLInsert = `INSERT INTO [${mj_core_schema()}].EntityFieldValue
|
|
1412
1504
|
(ID, EntityFieldID, Sequence, Value, Code)
|
|
1413
1505
|
VALUES
|
|
1414
1506
|
('${newId}', '${entityFieldID}', ${1 + possibleValues.indexOf(v)}, '${v}', '${v}')`;
|
|
@@ -1422,7 +1514,7 @@ NumberedRows AS (
|
|
|
1422
1514
|
const ev = existingValues.find((ev) => ev.Value === v);
|
|
1423
1515
|
if (ev && ev.Sequence !== 1 + possibleValues.indexOf(v)) {
|
|
1424
1516
|
// update the sequence to match the order in the possible values list, if it doesn't already match
|
|
1425
|
-
const sSQLUpdate = `UPDATE [${
|
|
1517
|
+
const sSQLUpdate = `UPDATE [${mj_core_schema()}].EntityFieldValue SET Sequence=${1 + possibleValues.indexOf(v)} WHERE ID='${ev.ID}'`;
|
|
1426
1518
|
await this.LogSQLAndExecute(ds, sSQLUpdate, `SQL text to update entity field value sequence`);
|
|
1427
1519
|
numUpdated++;
|
|
1428
1520
|
}
|
|
@@ -1436,7 +1528,7 @@ NumberedRows AS (
|
|
|
1436
1528
|
return true;
|
|
1437
1529
|
}
|
|
1438
1530
|
catch (e) {
|
|
1439
|
-
|
|
1531
|
+
logError(e);
|
|
1440
1532
|
return false;
|
|
1441
1533
|
}
|
|
1442
1534
|
}
|
|
@@ -1495,9 +1587,9 @@ NumberedRows AS (
|
|
|
1495
1587
|
createExcludeTablesAndSchemasFilter(fieldPrefix) {
|
|
1496
1588
|
let sExcludeTables = '';
|
|
1497
1589
|
let sExcludeSchemas = '';
|
|
1498
|
-
if (
|
|
1499
|
-
for (let i = 0; i <
|
|
1500
|
-
const t =
|
|
1590
|
+
if (configInfo.excludeTables) {
|
|
1591
|
+
for (let i = 0; i < configInfo.excludeTables.length; ++i) {
|
|
1592
|
+
const t = configInfo.excludeTables[i];
|
|
1501
1593
|
sExcludeTables += (sExcludeTables.length > 0 ? ' AND ' : '') +
|
|
1502
1594
|
(t.schema.indexOf('%') > -1 ? ` NOT ( ${fieldPrefix}SchemaName LIKE '${t.schema}'` :
|
|
1503
1595
|
` NOT ( ${fieldPrefix}SchemaName = '${t.schema}'`);
|
|
@@ -1505,9 +1597,9 @@ NumberedRows AS (
|
|
|
1505
1597
|
` AND ${fieldPrefix}TableName = '${t.table}') `);
|
|
1506
1598
|
}
|
|
1507
1599
|
}
|
|
1508
|
-
if (
|
|
1509
|
-
for (let i = 0; i <
|
|
1510
|
-
const s =
|
|
1600
|
+
if (configInfo.excludeSchemas) {
|
|
1601
|
+
for (let i = 0; i < configInfo.excludeSchemas.length; ++i) {
|
|
1602
|
+
const s = configInfo.excludeSchemas[i];
|
|
1511
1603
|
sExcludeSchemas += (sExcludeSchemas.length > 0 ? ' AND ' : '') +
|
|
1512
1604
|
(s.indexOf('%') > -1 ? `${fieldPrefix}SchemaName NOT LIKE '${s}'` : `${fieldPrefix}SchemaName <> '${s}'`);
|
|
1513
1605
|
}
|
|
@@ -1519,11 +1611,11 @@ NumberedRows AS (
|
|
|
1519
1611
|
}
|
|
1520
1612
|
async createNewEntities(pool, currentUser) {
|
|
1521
1613
|
try {
|
|
1522
|
-
const sSQL = `SELECT * FROM [${
|
|
1614
|
+
const sSQL = `SELECT * FROM [${mj_core_schema()}].vwSQLTablesAndEntities WHERE EntityID IS NULL ` + this.createExcludeTablesAndSchemasFilter('');
|
|
1523
1615
|
const newEntitiesResult = await pool.request().query(sSQL);
|
|
1524
1616
|
const newEntities = newEntitiesResult.recordset;
|
|
1525
1617
|
if (newEntities && newEntities.length > 0) {
|
|
1526
|
-
const md = new
|
|
1618
|
+
const md = new Metadata();
|
|
1527
1619
|
const transaction = new sql.Transaction(pool);
|
|
1528
1620
|
await transaction.begin();
|
|
1529
1621
|
try {
|
|
@@ -1540,14 +1632,14 @@ NumberedRows AS (
|
|
|
1540
1632
|
}
|
|
1541
1633
|
if (ManageMetadataBase.newEntityList.length > 0) {
|
|
1542
1634
|
// only do this if we actually created new entities
|
|
1543
|
-
|
|
1635
|
+
LogStatus(` Done creating entities, refreshing metadata to reflect new entities...`);
|
|
1544
1636
|
await md.Refresh(); // refresh now since we've added some new entities
|
|
1545
1637
|
}
|
|
1546
1638
|
}
|
|
1547
1639
|
return true; // if we get here, we succeeded
|
|
1548
1640
|
}
|
|
1549
1641
|
catch (e) {
|
|
1550
|
-
|
|
1642
|
+
LogError(e);
|
|
1551
1643
|
return false;
|
|
1552
1644
|
}
|
|
1553
1645
|
}
|
|
@@ -1556,11 +1648,16 @@ NumberedRows AS (
|
|
|
1556
1648
|
// criteria:
|
|
1557
1649
|
// 1) entity has a field that is a primary key
|
|
1558
1650
|
// validate all of these factors by getting the sql from SQL Server and check the result, if failure, shouldCreate=false and generate validation message, otherwise return empty validation message and true for shouldCreate.
|
|
1559
|
-
const query = `EXEC ${
|
|
1651
|
+
const query = `EXEC ${Metadata.Provider.ConfigData.MJCoreSchemaName}.spGetPrimaryKeyForTable @TableName='${newEntity.TableName}', @SchemaName='${newEntity.SchemaName}'`;
|
|
1560
1652
|
try {
|
|
1561
1653
|
const resultResult = await ds.request().query(query);
|
|
1562
1654
|
const result = resultResult.recordset;
|
|
1563
1655
|
if (result.length === 0) {
|
|
1656
|
+
// No database PK constraint found - check if there's a soft PK defined in config
|
|
1657
|
+
if (this.hasSoftPrimaryKeyInConfig(newEntity.SchemaName, newEntity.TableName)) {
|
|
1658
|
+
logStatus(` ✓ No database PK for ${newEntity.SchemaName}.${newEntity.TableName}, but soft PK found in config - allowing entity creation`);
|
|
1659
|
+
return { shouldCreate: true, validationMessage: '' };
|
|
1660
|
+
}
|
|
1564
1661
|
return { shouldCreate: false, validationMessage: "No primary key found" };
|
|
1565
1662
|
}
|
|
1566
1663
|
return { shouldCreate: true, validationMessage: '' };
|
|
@@ -1571,8 +1668,40 @@ NumberedRows AS (
|
|
|
1571
1668
|
return { shouldCreate: false, validationMessage: errorMsg };
|
|
1572
1669
|
}
|
|
1573
1670
|
}
|
|
1671
|
+
/**
|
|
1672
|
+
* Checks if a table has a soft primary key defined in the additionalSchemaInfo JSON file (configured in mj.config.cjs)
|
|
1673
|
+
*/
|
|
1674
|
+
hasSoftPrimaryKeyInConfig(schemaName, tableName) {
|
|
1675
|
+
// Check if additionalSchemaInfo is configured
|
|
1676
|
+
if (!configInfo.additionalSchemaInfo) {
|
|
1677
|
+
return false;
|
|
1678
|
+
}
|
|
1679
|
+
const configPath = path.join(currentWorkingDirectory, configInfo.additionalSchemaInfo);
|
|
1680
|
+
if (!fs.existsSync(configPath)) {
|
|
1681
|
+
logStatus(` [Soft PK Check] Config file not found at: ${configPath}`);
|
|
1682
|
+
return false;
|
|
1683
|
+
}
|
|
1684
|
+
try {
|
|
1685
|
+
const config = ManageMetadataBase.getSoftPKFKConfig();
|
|
1686
|
+
if (!config || !config.tables) {
|
|
1687
|
+
logStatus(` [Soft PK Check] Config file found but no tables array`);
|
|
1688
|
+
return false;
|
|
1689
|
+
}
|
|
1690
|
+
const tableConfig = config.tables.find((t) => t.schemaName?.toLowerCase() === schemaName?.toLowerCase() &&
|
|
1691
|
+
t.tableName?.toLowerCase() === tableName?.toLowerCase());
|
|
1692
|
+
const found = Boolean(tableConfig?.primaryKeys && tableConfig.primaryKeys.length > 0);
|
|
1693
|
+
if (!found) {
|
|
1694
|
+
logStatus(` [Soft PK Check] No config found for ${schemaName}.${tableName} (config has ${config.tables.length} tables)`);
|
|
1695
|
+
}
|
|
1696
|
+
return found;
|
|
1697
|
+
}
|
|
1698
|
+
catch (e) {
|
|
1699
|
+
logStatus(` [Soft PK Check] Error reading config: ${e}`);
|
|
1700
|
+
return false;
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1574
1703
|
async createNewEntityName(newEntity, currentUser) {
|
|
1575
|
-
const ag = new
|
|
1704
|
+
const ag = new AdvancedGeneration();
|
|
1576
1705
|
if (ag.featureEnabled('EntityNames')) {
|
|
1577
1706
|
return this.newEntityNameWithAdvancedGeneration(ag, newEntity, currentUser);
|
|
1578
1707
|
}
|
|
@@ -1612,8 +1741,8 @@ NumberedRows AS (
|
|
|
1612
1741
|
}
|
|
1613
1742
|
}
|
|
1614
1743
|
simpleNewEntityName(schemaName, tableName) {
|
|
1615
|
-
const convertedTableName =
|
|
1616
|
-
const pluralName =
|
|
1744
|
+
const convertedTableName = convertCamelCaseToHaveSpaces(tableName);
|
|
1745
|
+
const pluralName = generatePluralName(convertedTableName, { capitalizeFirstLetterOnly: true });
|
|
1617
1746
|
return this.markupEntityName(schemaName, pluralName);
|
|
1618
1747
|
}
|
|
1619
1748
|
/**
|
|
@@ -1633,18 +1762,18 @@ NumberedRows AS (
|
|
|
1633
1762
|
}
|
|
1634
1763
|
}
|
|
1635
1764
|
getNewEntityNameRule(schemaName) {
|
|
1636
|
-
const rule =
|
|
1765
|
+
const rule = configInfo.newEntityDefaults?.NameRulesBySchema?.find(r => {
|
|
1637
1766
|
let schemaNameToUse = r.SchemaName;
|
|
1638
1767
|
if (schemaNameToUse?.trim().toLowerCase() === '${mj_core_schema}') {
|
|
1639
1768
|
// markup for this is to be replaced with the mj_core_schema() config
|
|
1640
|
-
schemaNameToUse =
|
|
1769
|
+
schemaNameToUse = mj_core_schema();
|
|
1641
1770
|
}
|
|
1642
1771
|
return schemaNameToUse.trim().toLowerCase() === schemaName.trim().toLowerCase();
|
|
1643
1772
|
});
|
|
1644
1773
|
return rule;
|
|
1645
1774
|
}
|
|
1646
1775
|
createNewUUID() {
|
|
1647
|
-
return (
|
|
1776
|
+
return uuidv4();
|
|
1648
1777
|
}
|
|
1649
1778
|
async createNewEntity(pool, newEntity, md, currentUser) {
|
|
1650
1779
|
try {
|
|
@@ -1660,7 +1789,7 @@ NumberedRows AS (
|
|
|
1660
1789
|
// the generated name is already in place, so we need another name
|
|
1661
1790
|
suffix = '__' + newEntity.SchemaName;
|
|
1662
1791
|
newEntityName = newEntityName + suffix;
|
|
1663
|
-
|
|
1792
|
+
LogError(` >>>> WARNING: Entity name already exists, so using ${newEntityName} instead. If you did not intend for this, please rename the ${newEntity.SchemaName}.${newEntity.TableName} table in the database.`);
|
|
1664
1793
|
}
|
|
1665
1794
|
const isNewSchema = await this.isSchemaNew(pool, newEntity.SchemaName);
|
|
1666
1795
|
const newEntityID = this.createNewUUID();
|
|
@@ -1672,7 +1801,7 @@ NumberedRows AS (
|
|
|
1672
1801
|
// next, check if this entity is in a schema that is new (e.g. no other entities have been added to this schema yet), if so and if
|
|
1673
1802
|
// our config option is set to create new applications from new schemas, then create a new application for this schema
|
|
1674
1803
|
let apps;
|
|
1675
|
-
if (isNewSchema &&
|
|
1804
|
+
if (isNewSchema && configInfo.newSchemaDefaults.CreateNewApplicationWithSchemaName) {
|
|
1676
1805
|
// new schema and config option is to create a new application from the schema name so do that
|
|
1677
1806
|
// check to see if the app already exists
|
|
1678
1807
|
apps = await this.getApplicationIDForSchema(pool, newEntity.SchemaName);
|
|
@@ -1684,7 +1813,7 @@ NumberedRows AS (
|
|
|
1684
1813
|
apps = [newAppID];
|
|
1685
1814
|
}
|
|
1686
1815
|
else {
|
|
1687
|
-
|
|
1816
|
+
LogError(` >>>> ERROR: Unable to create new application for schema ${newEntity.SchemaName}`);
|
|
1688
1817
|
}
|
|
1689
1818
|
await md.Refresh(); // refresh now since we've added a new application, not super efficient to do this for each new application but that won't happen super
|
|
1690
1819
|
// often so not a huge deal, would be more efficient do this in batch after all new apps are created but that would be an over optimization IMO
|
|
@@ -1695,12 +1824,12 @@ NumberedRows AS (
|
|
|
1695
1824
|
apps = await this.getApplicationIDForSchema(pool, newEntity.SchemaName);
|
|
1696
1825
|
}
|
|
1697
1826
|
if (apps && apps.length > 0) {
|
|
1698
|
-
if (
|
|
1827
|
+
if (configInfo.newEntityDefaults.AddToApplicationWithSchemaName) {
|
|
1699
1828
|
// only do this if the configuration setting is set to add new entities to applications for schema names
|
|
1700
1829
|
for (const appUUID of apps) {
|
|
1701
|
-
const sSQLInsertApplicationEntity = `INSERT INTO ${
|
|
1830
|
+
const sSQLInsertApplicationEntity = `INSERT INTO ${mj_core_schema()}.ApplicationEntity
|
|
1702
1831
|
(ApplicationID, EntityID, Sequence) VALUES
|
|
1703
|
-
('${appUUID}', '${newEntityID}', (SELECT ISNULL(MAX(Sequence),0)+1 FROM ${
|
|
1832
|
+
('${appUUID}', '${newEntityID}', (SELECT ISNULL(MAX(Sequence),0)+1 FROM ${mj_core_schema()}.ApplicationEntity WHERE ApplicationID = '${appUUID}'))`;
|
|
1704
1833
|
await this.LogSQLAndExecute(pool, sSQLInsertApplicationEntity, `SQL generated to add new entity ${newEntityName} to application ID: '${appUUID}'`);
|
|
1705
1834
|
}
|
|
1706
1835
|
}
|
|
@@ -1710,91 +1839,85 @@ NumberedRows AS (
|
|
|
1710
1839
|
}
|
|
1711
1840
|
else {
|
|
1712
1841
|
// this is an error condition, we should have an application for this schema, if we don't, log an error, non fatal, but should be logged
|
|
1713
|
-
|
|
1842
|
+
LogError(` >>>> ERROR: Unable to add new entity ${newEntityName} to an application because an Application record for schema ${newEntity.SchemaName} does not exist.`);
|
|
1714
1843
|
}
|
|
1715
1844
|
// next up, we need to check if we're configured to add permissions for new entities, and if so, add them
|
|
1716
|
-
if (
|
|
1845
|
+
if (configInfo.newEntityDefaults.PermissionDefaults && configInfo.newEntityDefaults.PermissionDefaults.AutoAddPermissionsForNewEntities) {
|
|
1717
1846
|
// we are asked to add permissions for new entities, so do that by looping through the permissions and adding them
|
|
1718
|
-
const permissions =
|
|
1847
|
+
const permissions = configInfo.newEntityDefaults.PermissionDefaults.Permissions;
|
|
1719
1848
|
for (const p of permissions) {
|
|
1720
1849
|
const RoleID = md.Roles.find(r => r.Name.trim().toLowerCase() === p.RoleName.trim().toLowerCase())?.ID;
|
|
1721
1850
|
if (RoleID) {
|
|
1722
|
-
const sSQLInsertPermission = `INSERT INTO ${
|
|
1851
|
+
const sSQLInsertPermission = `INSERT INTO ${mj_core_schema()}.EntityPermission
|
|
1723
1852
|
(EntityID, RoleID, CanRead, CanCreate, CanUpdate, CanDelete) VALUES
|
|
1724
1853
|
('${newEntityID}', '${RoleID}', ${p.CanRead ? 1 : 0}, ${p.CanCreate ? 1 : 0}, ${p.CanUpdate ? 1 : 0}, ${p.CanDelete ? 1 : 0})`;
|
|
1725
1854
|
await this.LogSQLAndExecute(pool, sSQLInsertPermission, `SQL generated to add new permission for entity ${newEntityName} for role ${p.RoleName}`);
|
|
1726
1855
|
}
|
|
1727
1856
|
else
|
|
1728
|
-
|
|
1857
|
+
LogError(` >>>> ERROR: Unable to find Role ID for role ${p.RoleName} to add permissions for new entity ${newEntityName}`);
|
|
1729
1858
|
}
|
|
1730
1859
|
}
|
|
1731
|
-
|
|
1860
|
+
LogStatus(` Created new entity ${newEntityName} for table ${newEntity.SchemaName}.${newEntity.TableName}`);
|
|
1732
1861
|
}
|
|
1733
1862
|
else {
|
|
1734
|
-
|
|
1863
|
+
LogStatus(` Skipping new entity ${newEntity.TableName} because it doesn't qualify to be created. Reason: ${validationMessage}`);
|
|
1735
1864
|
return;
|
|
1736
1865
|
}
|
|
1737
1866
|
}
|
|
1738
1867
|
catch (e) {
|
|
1739
|
-
|
|
1868
|
+
LogError(`Failed to create new entity ${newEntity?.TableName}`);
|
|
1740
1869
|
}
|
|
1741
1870
|
}
|
|
1742
1871
|
async isSchemaNew(pool, schemaName) {
|
|
1743
1872
|
// check to see if there are any entities in the db with this schema name
|
|
1744
|
-
const sSQL = `SELECT COUNT(*) AS Count FROM [${
|
|
1873
|
+
const sSQL = `SELECT COUNT(*) AS Count FROM [${mj_core_schema()}].Entity WHERE SchemaName = '${schemaName}'`;
|
|
1745
1874
|
const resultResult = await pool.request().query(sSQL);
|
|
1746
1875
|
const result = resultResult.recordset;
|
|
1747
1876
|
return result && result.length > 0 ? result[0].Count === 0 : true;
|
|
1748
1877
|
}
|
|
1749
1878
|
/**
|
|
1750
|
-
* Creates a new application using
|
|
1751
|
-
*
|
|
1752
|
-
* - Auto-generation of Path from Name (via ApplicationEntityServerEntity)
|
|
1753
|
-
* - Any other server-side business logic
|
|
1879
|
+
* Creates a new application using direct SQL INSERT to ensure it's captured in SQL logging.
|
|
1880
|
+
* The Path field is auto-generated from Name using the same slug logic as ApplicationEntityServerEntity.
|
|
1754
1881
|
*
|
|
1755
|
-
* @param pool SQL connection pool
|
|
1882
|
+
* @param pool SQL connection pool
|
|
1756
1883
|
* @param appID Pre-generated UUID for the application
|
|
1757
1884
|
* @param appName Name of the application
|
|
1758
1885
|
* @param schemaName Schema name for SchemaAutoAddNewEntities
|
|
1759
|
-
* @param currentUser Current user for entity operations
|
|
1886
|
+
* @param currentUser Current user for entity operations (unused but kept for signature compatibility)
|
|
1760
1887
|
* @returns The application ID if successful, null otherwise
|
|
1761
1888
|
*/
|
|
1762
1889
|
async createNewApplication(pool, appID, appName, schemaName, currentUser) {
|
|
1763
1890
|
try {
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
|
-
const errorMsg = app.LatestResult ? JSON.stringify(app.LatestResult) : 'Unknown error';
|
|
1780
|
-
(0, core_1.LogError)(`Failed to save new application ${appName}: ${errorMsg}`);
|
|
1781
|
-
return null;
|
|
1782
|
-
}
|
|
1891
|
+
// Generate Path from Name using slug conversion:
|
|
1892
|
+
// 1. Convert to lowercase
|
|
1893
|
+
// 2. Replace spaces with hyphens
|
|
1894
|
+
// 3. Remove special characters (keep only alphanumeric and hyphens)
|
|
1895
|
+
const path = appName
|
|
1896
|
+
.toLowerCase()
|
|
1897
|
+
.replace(/\s+/g, '-') // spaces to hyphens
|
|
1898
|
+
.replace(/[^a-z0-9-]/g, '') // remove special chars
|
|
1899
|
+
.replace(/-+/g, '-') // collapse multiple hyphens
|
|
1900
|
+
.replace(/^-|-$/g, ''); // trim hyphens from start/end
|
|
1901
|
+
const sSQL = `INSERT INTO [${mj_core_schema()}].Application (ID, Name, Description, SchemaAutoAddNewEntities, Path, AutoUpdatePath)
|
|
1902
|
+
VALUES ('${appID}', '${appName}', 'Generated for schema', '${schemaName}', '${path}', 1)`;
|
|
1903
|
+
await this.LogSQLAndExecute(pool, sSQL, `SQL generated to create new application ${appName}`);
|
|
1904
|
+
LogStatus(`Created new application ${appName} with Path: ${path}`);
|
|
1905
|
+
return appID;
|
|
1783
1906
|
}
|
|
1784
1907
|
catch (e) {
|
|
1785
|
-
|
|
1908
|
+
LogError(`Failed to create new application ${appName} for schema ${schemaName}`, null, e);
|
|
1786
1909
|
return null;
|
|
1787
1910
|
}
|
|
1788
1911
|
}
|
|
1789
1912
|
async applicationExists(pool, applicationName) {
|
|
1790
|
-
const sSQL = `SELECT ID FROM [${
|
|
1913
|
+
const sSQL = `SELECT ID FROM [${mj_core_schema()}].Application WHERE Name = '${applicationName}'`;
|
|
1791
1914
|
const resultResult = await pool.request().query(sSQL);
|
|
1792
1915
|
const result = resultResult.recordset;
|
|
1793
1916
|
return result && result.length > 0 ? result[0].ID.length > 0 : false;
|
|
1794
1917
|
}
|
|
1795
1918
|
async getApplicationIDForSchema(pool, schemaName) {
|
|
1796
1919
|
// get all the apps each time from DB as we might be adding, don't use Metadata here for that reason
|
|
1797
|
-
const sSQL = `SELECT ID, Name, SchemaAutoAddNewEntities FROM [${
|
|
1920
|
+
const sSQL = `SELECT ID, Name, SchemaAutoAddNewEntities FROM [${mj_core_schema()}].vwApplications`;
|
|
1798
1921
|
const resultResult = await pool.request().query(sSQL);
|
|
1799
1922
|
const result = resultResult.recordset;
|
|
1800
1923
|
if (!result || result.length === 0) {
|
|
@@ -1814,10 +1937,10 @@ NumberedRows AS (
|
|
|
1814
1937
|
}
|
|
1815
1938
|
}
|
|
1816
1939
|
createNewEntityInsertSQL(newEntityUUID, newEntityName, newEntity, newEntitySuffix, newEntityDisplayName) {
|
|
1817
|
-
const newEntityDefaults =
|
|
1940
|
+
const newEntityDefaults = configInfo.newEntityDefaults;
|
|
1818
1941
|
const newEntityDescriptionEscaped = newEntity.Description ? `'${newEntity.Description.replace(/'/g, "''")}` : null;
|
|
1819
1942
|
const sSQLInsert = `
|
|
1820
|
-
INSERT INTO [${
|
|
1943
|
+
INSERT INTO [${mj_core_schema()}].Entity (
|
|
1821
1944
|
ID,
|
|
1822
1945
|
Name,
|
|
1823
1946
|
DisplayName,
|
|
@@ -1844,7 +1967,7 @@ NumberedRows AS (
|
|
|
1844
1967
|
${newEntityDescriptionEscaped ? newEntityDescriptionEscaped : 'NULL' /*if no description, then null*/},
|
|
1845
1968
|
${newEntitySuffix && newEntitySuffix.length > 0 ? `'${newEntitySuffix}'` : 'NULL'},
|
|
1846
1969
|
'${newEntity.TableName}',
|
|
1847
|
-
'vw${
|
|
1970
|
+
'vw${generatePluralName(newEntity.TableName, { capitalizeFirstLetterOnly: true }) + (newEntitySuffix && newEntitySuffix.length > 0 ? newEntitySuffix : '')}',
|
|
1848
1971
|
'${newEntity.SchemaName}',
|
|
1849
1972
|
1,
|
|
1850
1973
|
${newEntityDefaults.AllowUserSearchAPI === undefined ? 1 : newEntityDefaults.AllowUserSearchAPI ? 1 : 0}
|
|
@@ -1865,7 +1988,7 @@ NumberedRows AS (
|
|
|
1865
1988
|
*/
|
|
1866
1989
|
async applyAdvancedGeneration(pool, excludeSchemas, currentUser) {
|
|
1867
1990
|
try {
|
|
1868
|
-
const ag = new
|
|
1991
|
+
const ag = new AdvancedGeneration();
|
|
1869
1992
|
if (!ag.enabled) {
|
|
1870
1993
|
return true;
|
|
1871
1994
|
}
|
|
@@ -1874,13 +1997,13 @@ NumberedRows AS (
|
|
|
1874
1997
|
// Otherwise, only process new or modified entities
|
|
1875
1998
|
let entitiesToProcess = [];
|
|
1876
1999
|
let whereClause = '';
|
|
1877
|
-
if (
|
|
2000
|
+
if (configInfo.forceRegeneration?.enabled) {
|
|
1878
2001
|
// Force regeneration mode - process all entities (or filtered by entityWhereClause)
|
|
1879
|
-
|
|
2002
|
+
logStatus(` Force regeneration enabled - processing all entities...`);
|
|
1880
2003
|
whereClause = 'e.VirtualEntity = 0';
|
|
1881
|
-
if (
|
|
1882
|
-
whereClause += ` AND (${
|
|
1883
|
-
|
|
2004
|
+
if (configInfo.forceRegeneration.entityWhereClause && configInfo.forceRegeneration.entityWhereClause.trim().length > 0) {
|
|
2005
|
+
whereClause += ` AND (${configInfo.forceRegeneration.entityWhereClause})`;
|
|
2006
|
+
logStatus(` Filtered by: ${configInfo.forceRegeneration.entityWhereClause}`);
|
|
1884
2007
|
}
|
|
1885
2008
|
whereClause += ` AND e.SchemaName NOT IN (${excludeSchemas.map(s => `'${s}'`).join(',')})`;
|
|
1886
2009
|
}
|
|
@@ -1896,7 +2019,7 @@ NumberedRows AS (
|
|
|
1896
2019
|
if (entitiesToProcess.length === 0) {
|
|
1897
2020
|
return true;
|
|
1898
2021
|
}
|
|
1899
|
-
|
|
2022
|
+
logStatus(` Advanced Generation enabled, processing ${entitiesToProcess.length} entities...`);
|
|
1900
2023
|
whereClause = `e.VirtualEntity = 0 AND e.Name IN (${entitiesToProcess.map(name => `'${name}'`).join(',')}) AND e.SchemaName NOT IN (${excludeSchemas.map(s => `'${s}'`).join(',')})`;
|
|
1901
2024
|
}
|
|
1902
2025
|
// Get entity details for entities that need processing
|
|
@@ -1908,7 +2031,7 @@ NumberedRows AS (
|
|
|
1908
2031
|
e.SchemaName,
|
|
1909
2032
|
e.BaseTable
|
|
1910
2033
|
FROM
|
|
1911
|
-
[${
|
|
2034
|
+
[${mj_core_schema()}].vwEntities e
|
|
1912
2035
|
WHERE
|
|
1913
2036
|
${whereClause}
|
|
1914
2037
|
ORDER BY
|
|
@@ -1941,7 +2064,7 @@ NumberedRows AS (
|
|
|
1941
2064
|
ef.EntityIDFieldName,
|
|
1942
2065
|
ef.RelatedEntity
|
|
1943
2066
|
FROM
|
|
1944
|
-
[${
|
|
2067
|
+
[${mj_core_schema()}].vwEntityFields ef
|
|
1945
2068
|
WHERE
|
|
1946
2069
|
ef.EntityID IN (${entityIds})
|
|
1947
2070
|
ORDER BY
|
|
@@ -1957,7 +2080,7 @@ NumberedRows AS (
|
|
|
1957
2080
|
es.Name,
|
|
1958
2081
|
es.Value
|
|
1959
2082
|
FROM
|
|
1960
|
-
[${
|
|
2083
|
+
[${mj_core_schema()}].EntitySetting es
|
|
1961
2084
|
WHERE
|
|
1962
2085
|
es.EntityID IN (${entityIds})
|
|
1963
2086
|
AND es.Name IN ('FieldCategoryIcons', 'FieldCategoryInfo')
|
|
@@ -1980,7 +2103,7 @@ NumberedRows AS (
|
|
|
1980
2103
|
return await this.processEntitiesBatched(pool, entities, allFields, ag, currentUser);
|
|
1981
2104
|
}
|
|
1982
2105
|
catch (error) {
|
|
1983
|
-
|
|
2106
|
+
logError(`Advanced Generation failed: ${error}`);
|
|
1984
2107
|
return false;
|
|
1985
2108
|
}
|
|
1986
2109
|
}
|
|
@@ -2008,12 +2131,12 @@ NumberedRows AS (
|
|
|
2008
2131
|
}
|
|
2009
2132
|
else {
|
|
2010
2133
|
errorCount++;
|
|
2011
|
-
|
|
2134
|
+
logError(` Error processing entity: ${result.reason}`);
|
|
2012
2135
|
}
|
|
2013
2136
|
}
|
|
2014
|
-
|
|
2137
|
+
logStatus(` Progress: ${processedCount}/${entities.length} entities processed`);
|
|
2015
2138
|
}
|
|
2016
|
-
|
|
2139
|
+
logStatus(` Advanced Generation complete: ${processedCount} entities processed, ${errorCount} errors`);
|
|
2017
2140
|
return errorCount === 0;
|
|
2018
2141
|
}
|
|
2019
2142
|
/**
|
|
@@ -2053,7 +2176,7 @@ NumberedRows AS (
|
|
|
2053
2176
|
}, currentUser, isNewEntity);
|
|
2054
2177
|
if (layoutAnalysis) {
|
|
2055
2178
|
await this.applyFormLayout(pool, entity.ID, fields, layoutAnalysis, isNewEntity);
|
|
2056
|
-
|
|
2179
|
+
logStatus(` Applied form layout for ${entity.Name}`);
|
|
2057
2180
|
}
|
|
2058
2181
|
}
|
|
2059
2182
|
}
|
|
@@ -2066,26 +2189,26 @@ NumberedRows AS (
|
|
|
2066
2189
|
const nameField = fields.find(f => f.Name === result.nameField);
|
|
2067
2190
|
if (nameField && nameField.AutoUpdateIsNameField && nameField.ID) {
|
|
2068
2191
|
sqlStatements.push(`
|
|
2069
|
-
UPDATE [${
|
|
2192
|
+
UPDATE [${mj_core_schema()}].EntityField
|
|
2070
2193
|
SET IsNameField = 1
|
|
2071
2194
|
WHERE ID = '${nameField.ID}'
|
|
2072
2195
|
AND AutoUpdateIsNameField = 1
|
|
2073
2196
|
`);
|
|
2074
2197
|
}
|
|
2075
2198
|
else if (!nameField) {
|
|
2076
|
-
|
|
2199
|
+
logError(`Smart field identification returned invalid nameField: '${result.nameField}' not found in entity fields`);
|
|
2077
2200
|
}
|
|
2078
2201
|
// Find all default in view fields (one or more)
|
|
2079
2202
|
const defaultInViewFields = fields.filter(f => result.defaultInView.includes(f.Name) && f.AutoUpdateDefaultInView && f.ID);
|
|
2080
2203
|
// Warn about any fields that weren't found
|
|
2081
2204
|
const missingFields = result.defaultInView.filter(name => !fields.some(f => f.Name === name));
|
|
2082
2205
|
if (missingFields.length > 0) {
|
|
2083
|
-
|
|
2206
|
+
logError(`Smart field identification returned invalid defaultInView fields: ${missingFields.join(', ')} not found in entity`);
|
|
2084
2207
|
}
|
|
2085
2208
|
// Build update statements for all default in view fields
|
|
2086
2209
|
for (const field of defaultInViewFields) {
|
|
2087
2210
|
sqlStatements.push(`
|
|
2088
|
-
UPDATE [${
|
|
2211
|
+
UPDATE [${mj_core_schema()}].EntityField
|
|
2089
2212
|
SET DefaultInView = 1
|
|
2090
2213
|
WHERE ID = '${field.ID}'
|
|
2091
2214
|
AND AutoUpdateDefaultInView = 1
|
|
@@ -2097,12 +2220,12 @@ NumberedRows AS (
|
|
|
2097
2220
|
// Warn about any fields that weren't found
|
|
2098
2221
|
const missingSearchableFields = result.searchableFields.filter(name => !fields.some(f => f.Name === name));
|
|
2099
2222
|
if (missingSearchableFields.length > 0) {
|
|
2100
|
-
|
|
2223
|
+
logError(`Smart field identification returned invalid searchableFields: ${missingSearchableFields.join(', ')} not found in entity`);
|
|
2101
2224
|
}
|
|
2102
2225
|
// Build update statements for all searchable fields
|
|
2103
2226
|
for (const field of searchableFields) {
|
|
2104
2227
|
sqlStatements.push(`
|
|
2105
|
-
UPDATE [${
|
|
2228
|
+
UPDATE [${mj_core_schema()}].EntityField
|
|
2106
2229
|
SET IncludeInUserSearchAPI = 1
|
|
2107
2230
|
WHERE ID = '${field.ID}'
|
|
2108
2231
|
AND AutoUpdateIncludeInUserSearchAPI = 1
|
|
@@ -2153,7 +2276,7 @@ NumberedRows AS (
|
|
|
2153
2276
|
if (fieldHasExistingCategory && categoryIsNew) {
|
|
2154
2277
|
// LLM is trying to move an existing field to a brand new category
|
|
2155
2278
|
// This could be an attempt to rename a category - reject it
|
|
2156
|
-
|
|
2279
|
+
logStatus(` Rejected category change for field '${field.Name}': cannot move from existing category '${field.Category}' to new category '${category}'. Keeping original category.`);
|
|
2157
2280
|
category = field.Category; // Keep the original category
|
|
2158
2281
|
}
|
|
2159
2282
|
// Build SET clause with all available metadata
|
|
@@ -2175,14 +2298,14 @@ NumberedRows AS (
|
|
|
2175
2298
|
const codeType = fieldCategory.codeType === null ? 'NULL' : `'${fieldCategory.codeType.replace(/'/g, "''")}'`;
|
|
2176
2299
|
setClauses.push(`CodeType = ${codeType}`);
|
|
2177
2300
|
}
|
|
2178
|
-
const updateSQL = `UPDATE [${
|
|
2301
|
+
const updateSQL = `UPDATE [${mj_core_schema()}].EntityField
|
|
2179
2302
|
SET ${setClauses.join(',\n ')}
|
|
2180
2303
|
WHERE ID = '${field.ID}'
|
|
2181
2304
|
AND AutoUpdateCategory = 1`;
|
|
2182
2305
|
sqlStatements.push(updateSQL);
|
|
2183
2306
|
}
|
|
2184
2307
|
else if (!field) {
|
|
2185
|
-
|
|
2308
|
+
logError(`Form layout generation returned invalid fieldName: '${fieldCategory.fieldName}' not found in entity`);
|
|
2186
2309
|
}
|
|
2187
2310
|
}
|
|
2188
2311
|
// Execute all field updates in one batch
|
|
@@ -2194,7 +2317,7 @@ NumberedRows AS (
|
|
|
2194
2317
|
if (result.entityIcon && result.entityIcon.trim().length > 0) {
|
|
2195
2318
|
// Check if entity already has an icon
|
|
2196
2319
|
const checkEntitySQL = `
|
|
2197
|
-
SELECT Icon FROM [${
|
|
2320
|
+
SELECT Icon FROM [${mj_core_schema()}].Entity
|
|
2198
2321
|
WHERE ID = '${entityId}'
|
|
2199
2322
|
`;
|
|
2200
2323
|
const entityCheck = await pool.request().query(checkEntitySQL);
|
|
@@ -2204,13 +2327,13 @@ NumberedRows AS (
|
|
|
2204
2327
|
if (!currentIcon || currentIcon.trim().length === 0) {
|
|
2205
2328
|
const escapedIcon = result.entityIcon.replace(/'/g, "''");
|
|
2206
2329
|
const updateEntitySQL = `
|
|
2207
|
-
UPDATE [${
|
|
2330
|
+
UPDATE [${mj_core_schema()}].Entity
|
|
2208
2331
|
SET Icon = '${escapedIcon}',
|
|
2209
2332
|
__mj_UpdatedAt = GETUTCDATE()
|
|
2210
2333
|
WHERE ID = '${entityId}'
|
|
2211
2334
|
`;
|
|
2212
2335
|
await this.LogSQLAndExecute(pool, updateEntitySQL, `Set entity icon to ${result.entityIcon}`, false);
|
|
2213
|
-
|
|
2336
|
+
logStatus(` ✓ Set entity icon: ${result.entityIcon}`);
|
|
2214
2337
|
}
|
|
2215
2338
|
}
|
|
2216
2339
|
}
|
|
@@ -2224,14 +2347,14 @@ NumberedRows AS (
|
|
|
2224
2347
|
const infoJSON = JSON.stringify(categoryInfoToStore).replace(/'/g, "''");
|
|
2225
2348
|
// First check if new format setting already exists
|
|
2226
2349
|
const checkNewSQL = `
|
|
2227
|
-
SELECT ID FROM [${
|
|
2350
|
+
SELECT ID FROM [${mj_core_schema()}].EntitySetting
|
|
2228
2351
|
WHERE EntityID = '${entityId}' AND Name = 'FieldCategoryInfo'
|
|
2229
2352
|
`;
|
|
2230
2353
|
const existingNew = await pool.request().query(checkNewSQL);
|
|
2231
2354
|
if (existingNew.recordset.length > 0) {
|
|
2232
2355
|
// Update existing setting
|
|
2233
2356
|
const updateSQL = `
|
|
2234
|
-
UPDATE [${
|
|
2357
|
+
UPDATE [${mj_core_schema()}].EntitySetting
|
|
2235
2358
|
SET Value = '${infoJSON}',
|
|
2236
2359
|
__mj_UpdatedAt = GETUTCDATE()
|
|
2237
2360
|
WHERE EntityID = '${entityId}' AND Name = 'FieldCategoryInfo'
|
|
@@ -2240,9 +2363,9 @@ NumberedRows AS (
|
|
|
2240
2363
|
}
|
|
2241
2364
|
else {
|
|
2242
2365
|
// Insert new setting
|
|
2243
|
-
const newId = (
|
|
2366
|
+
const newId = uuidv4();
|
|
2244
2367
|
const insertSQL = `
|
|
2245
|
-
INSERT INTO [${
|
|
2368
|
+
INSERT INTO [${mj_core_schema()}].EntitySetting (ID, EntityID, Name, Value, __mj_CreatedAt, __mj_UpdatedAt)
|
|
2246
2369
|
VALUES ('${newId}', '${entityId}', 'FieldCategoryInfo', '${infoJSON}', GETUTCDATE(), GETUTCDATE())
|
|
2247
2370
|
`;
|
|
2248
2371
|
await this.LogSQLAndExecute(pool, insertSQL, `Insert FieldCategoryInfo setting for entity`, false);
|
|
@@ -2257,13 +2380,13 @@ NumberedRows AS (
|
|
|
2257
2380
|
}
|
|
2258
2381
|
const iconsJSON = JSON.stringify(iconsOnly).replace(/'/g, "''");
|
|
2259
2382
|
const checkLegacySQL = `
|
|
2260
|
-
SELECT ID FROM [${
|
|
2383
|
+
SELECT ID FROM [${mj_core_schema()}].EntitySetting
|
|
2261
2384
|
WHERE EntityID = '${entityId}' AND Name = 'FieldCategoryIcons'
|
|
2262
2385
|
`;
|
|
2263
2386
|
const existingLegacy = await pool.request().query(checkLegacySQL);
|
|
2264
2387
|
if (existingLegacy.recordset.length > 0) {
|
|
2265
2388
|
const updateSQL = `
|
|
2266
|
-
UPDATE [${
|
|
2389
|
+
UPDATE [${mj_core_schema()}].EntitySetting
|
|
2267
2390
|
SET Value = '${iconsJSON}',
|
|
2268
2391
|
__mj_UpdatedAt = GETUTCDATE()
|
|
2269
2392
|
WHERE EntityID = '${entityId}' AND Name = 'FieldCategoryIcons'
|
|
@@ -2271,9 +2394,9 @@ NumberedRows AS (
|
|
|
2271
2394
|
await this.LogSQLAndExecute(pool, updateSQL, `Update FieldCategoryIcons setting for entity (legacy format)`, false);
|
|
2272
2395
|
}
|
|
2273
2396
|
else {
|
|
2274
|
-
const newId = (
|
|
2397
|
+
const newId = uuidv4();
|
|
2275
2398
|
const insertSQL = `
|
|
2276
|
-
INSERT INTO [${
|
|
2399
|
+
INSERT INTO [${mj_core_schema()}].EntitySetting (ID, EntityID, Name, Value, __mj_CreatedAt, __mj_UpdatedAt)
|
|
2277
2400
|
VALUES ('${newId}', '${entityId}', 'FieldCategoryIcons', '${iconsJSON}', GETUTCDATE(), GETUTCDATE())
|
|
2278
2401
|
`;
|
|
2279
2402
|
await this.LogSQLAndExecute(pool, insertSQL, `Insert FieldCategoryIcons setting for entity (legacy format)`, false);
|
|
@@ -2285,14 +2408,14 @@ NumberedRows AS (
|
|
|
2285
2408
|
const defaultForNewUser = result.entityImportance.defaultForNewUser ? 1 : 0;
|
|
2286
2409
|
// Update all ApplicationEntity records for this entity
|
|
2287
2410
|
const updateAppEntitySQL = `
|
|
2288
|
-
UPDATE [${
|
|
2411
|
+
UPDATE [${mj_core_schema()}].ApplicationEntity
|
|
2289
2412
|
SET DefaultForNewUser = ${defaultForNewUser},
|
|
2290
2413
|
__mj_UpdatedAt = GETUTCDATE()
|
|
2291
2414
|
WHERE EntityID = '${entityId}'
|
|
2292
2415
|
`;
|
|
2293
2416
|
await this.LogSQLAndExecute(pool, updateAppEntitySQL, `Set DefaultForNewUser=${defaultForNewUser} for NEW entity based on AI analysis (category: ${result.entityImportance.entityCategory}, confidence: ${result.entityImportance.confidence})`, false);
|
|
2294
|
-
|
|
2295
|
-
|
|
2417
|
+
logStatus(` ✓ Entity importance (NEW Entity): ${result.entityImportance.entityCategory} (defaultForNewUser: ${result.entityImportance.defaultForNewUser}, confidence: ${result.entityImportance.confidence})`);
|
|
2418
|
+
logStatus(` Reasoning: ${result.entityImportance.reasoning}`);
|
|
2296
2419
|
}
|
|
2297
2420
|
}
|
|
2298
2421
|
/**
|
|
@@ -2306,8 +2429,7 @@ NumberedRows AS (
|
|
|
2306
2429
|
* @returns - The result of the query execution.
|
|
2307
2430
|
*/
|
|
2308
2431
|
async LogSQLAndExecute(pool, query, description, isRecurringScript = false) {
|
|
2309
|
-
return await
|
|
2432
|
+
return await SQLLogging.LogSQLAndExecute(pool, query, description, isRecurringScript);
|
|
2310
2433
|
}
|
|
2311
2434
|
}
|
|
2312
|
-
exports.ManageMetadataBase = ManageMetadataBase;
|
|
2313
2435
|
//# sourceMappingURL=manage-metadata.js.map
|