diff --git a/lib/business/business-base.mjs b/lib/business/business-base.mjs index b45d41b..208ca93 100644 --- a/lib/business/business-base.mjs +++ b/lib/business/business-base.mjs @@ -19,6 +19,12 @@ const filterFields = { ModifiedByUser: "Modified_" } +const OperationMode = { + load: 'load', + list: 'list', + lookupList: 'lookupList', +}; + const dateTypeFields = ["date", "dateTime"]; const IsDeletedColumn = "IsDeleted"; @@ -254,17 +260,33 @@ class BusinessBase { return BusinessBase.businessObject.sql.createRequest(this.logger); } - createWhere({ alias = "Main", filterDeleted = true } = {}) { + /** + * Generates a WHERE clause object based on the current business object's configuration and optional parameters. + * @param {Object} [options] + * @param {string} [options.alias="Main"] - The alias to use for table references in the WHERE clause. + * @param {boolean} [options.isStandard] - When true, apply soft-delete filtering (IsDeleted = 0) if enabled. + * @returns {Promise} A WHERE clause object that can be passed to sql.addParameters({ forWhere: true }). + */ + async createWhere({ alias = "Main", ...options } = {}) { const where = {}; if (this.clientBased && this.user.scopeId) { where[`${alias}.ClientId`] = this.user.scopeId; } - if (filterDeleted && this.softDelete !== false) { + if (options.isStandard && this.softDelete !== false) { where[`${alias}.IsDeleted`] = 0; } + if (typeof this.customizeWhere === 'function') { + await this.customizeWhere({ where, alias, ...options }); + } return where; } + /** + * customizeWhere - Optional hook for customizing the WHERE clause in SQL queries. + * @param {Object} param0 - An object containing the current WHERE clause, alias, and additional options. + * @returns {Promise} A promise that resolves when the customization is complete. + */ + pluralize(str) { return str + 's'; } @@ -278,7 +300,7 @@ class BusinessBase { let query = this.getSelectStatement(); - const where = await this.createWhere({ filterDeleted: this.standardTable }); + const where = await this.createWhere({ isStandard: this.standardTable, operationMode: OperationMode.load }); where[keyField] = id; const request = this.createRequest(); @@ -620,8 +642,20 @@ class BusinessBase { return await sql.insertUpdate({ tableName: this.getTableName(), keyField, id, json: values, update: true, logger: this.logger }); } - getListStatement() { - return this.listStatement || (this.standardTable && this.useView !== false ? `SELECT * FROM vw${this.getTableName()}List Main` : this.getSelectStatement()); + async getListStatement(listParameters) { + const listStatement = this.listStatement || (this.standardTable && this.useView !== false ? `SELECT * FROM vw${this.getTableName()}List Main` : this.getSelectStatement()); + const isStandard = this.standardTable === true && listStatement.indexOf("vw") === -1; + return { listStatement, isStandard }; + } + + normalizeListStatement(result) { + if(typeof result === 'string') { + return { + listStatement: result, + isStandard: this.standardTable === true && result.indexOf("vw") === -1 + } + } + return result; } async lookupList({ scopeId }) { @@ -634,8 +668,20 @@ class BusinessBase { } const sql = BusinessBase.businessObject.sql; - let listStatement = this.getListStatement(); - const isStandard = this.standardTable === true && listStatement.indexOf("vw") === -1; + let { listStatement, isStandard } = this.normalizeListStatement( + await this.getListStatement({ + scopeId, + operationMode: OperationMode.lookupList, + keyField, + lookupSortOrder, + defaultSortOrder, + displayField, + clientBased, + lookupListStatement, + tableName, + sort + }) + ); const labelField = displayField || sort || this.sort; if (!labelField) { this.logger.error('No displayField or sort field defined for lookupList label.'); @@ -644,7 +690,7 @@ class BusinessBase { listStatement = listStatement.replace(/^.+ FROM/i, `SELECT [${keyField}] value, [${labelField}] label FROM `); let query = listStatement; - const where = await this.createWhere({ filterDeleted: isStandard, tableName }); + const where = await this.createWhere({ isStandard, tableName, operationMode: OperationMode.lookupList }); if (!clientBased && scopeId) { where.ScopeId = scopeId; } @@ -660,7 +706,23 @@ class BusinessBase { return result.recordset; } - async list({ start = 0, limit = 100, sort, filter, groupBy, include, exclude, returnCount = true }) { + /** + * addAdditionalColumns - Optional hook for adding custom JOINs and columns to the list SQL statement. + * @param {Object} param0 - An object containing the current SQL statement, request, and additional parameters for context. + * @returns {Promise} An object that can include a modified listStatement and an array of additionalColumns to be added to the SELECT clause. + */ + + /** + * customizeList - Optional hook for customizing the list results + * @param {Object} param0 - An object containing the current SQL statement, request, and additional parameters for context. + * @returns {Promise} A promise that resolves when the customization is complete. + */ + + /** + * List records with optional hooks for extensibility + * Supports hooks: customizeWhere, addAdditionalColumns,customizeList + */ + async list({ start = 0, limit = 100, sort, filter, groupBy, include, exclude, returnCount = true, ...options }) { sort = sort || this.defaultSortOrder; const request = this.createRequest(); const { keyField } = this; @@ -669,9 +731,31 @@ class BusinessBase { let totalStatement = "SELECT COUNT(1) AS TotalCount"; const { relations = [] } = this; - let listStatement = this.getListStatement(); - const isStandard = this.standardTable === true && listStatement.indexOf("vw") === -1; + + const hookParameters = { + ...options, + sql, + request, + keyField, + start, + limit, + sort, + filter, + groupBy, + include, + exclude, + returnCount, + relations, + operationMode: OperationMode.list + }; + + let { listStatement, isStandard } = this.normalizeListStatement(await this.getListStatement(hookParameters)); const isDataFromView = listStatement.indexOf("vw") > -1; + + hookParameters.listStatement = listStatement; + hookParameters.isStandard = isStandard; + hookParameters.isDataFromView = isDataFromView; + const additionalColumns = []; if (isStandard) { listStatement += '\r\n LEFT OUTER JOIN (SELECT UserId Created_UserId, UserName as CreatedByUser FROM Security_User) Created_ ON Created_.Created_UserId = Main.CreatedByUserId' @@ -697,12 +781,27 @@ class BusinessBase { } } + + // Hook: addAdditionalColumns - Allow adding custom JOINs and columns + if (typeof this.addAdditionalColumns === 'function' ) { + const result = await this.addAdditionalColumns(hookParameters); + + if (result) { + listStatement = result.listStatement || listStatement; + if (result.additionalColumns && result.additionalColumns.length > 0) { + additionalColumns.push(...result.additionalColumns); + } + } + } + if (additionalColumns.length > 0) { listStatement = listStatement.replace(/ from /i, ', ' + additionalColumns.join(', ') + ' FROM '); } - let query = listStatement; - const where = this.createWhere({ filterDeleted: isStandard }); + hookParameters.listStatement = listStatement; + const where = await this.createWhere(hookParameters); + let query = hookParameters.listStatement; + if (typeof include === 'string') { include = include.split(',').map(item => Number(item)); } @@ -782,25 +881,27 @@ class BusinessBase { const result = await request.query(query); - if (returnCount) { - let recordCount; + const listResult = { + records: result.recordset + }; + if (returnCount) { if (limit > 0) { - recordCount = result.recordsets[1][0].TotalCount; + listResult.recordCount = result.recordsets[1][0].TotalCount; } else { - recordCount = result.rowsAffected[0]; + listResult.recordCount = result.rowsAffected[0]; } + } - return { - records: result.recordset, - recordCount - } - } else { - return { - records: result.recordset - } + hookParameters.listResult = listResult; + + // Hook: customizeList - Allow result post-processing + if (typeof this.customizeList === 'function') { + await this.customizeList(hookParameters); } + return hookParameters.listResult; + } static async handleMultiSelectValues({ multiSelectValues, multiSelectColumns, getTableName, keyField, id, user, sql, isUpdate, softDelete }) { @@ -883,6 +984,6 @@ const classMap = { } }; -export { RelationshipTypes, BusinessBase, classMap }; +export { RelationshipTypes, BusinessBase, classMap, OperationMode }; export default BusinessBase; \ No newline at end of file