using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using GB5Shared.GOP.GBQueryExecutor; using GB5Shared.GOP.GBQueryExecutor.DTOs; using GB5Shared.DTO.Framework.Criteria; using GB5Shared.DTO.Framework.Login; using GB5Shared.EntityHandler; using GB5Shared.QueryExecutor; using GB5Shared.Resource.Response; using GB5Shared.Validation; using Microsoft.Extensions.Logging; using static GB5Shared.GB5Constant.Constant; namespace GB5Shared.GOP.GBQueryExecutor { // ============================================================ // GBConfiguredQueryBLL — CRUD management for MGBCONFIGUREDQUERY. // // Design notes: // • CONFIGUREDQUERYID is a live IDENTITY column (found via direct inspection -- // unlike sibling GOP config tables, this one does NOT take an AutoNumber-assigned // PK). Save lets the DAL insert and read the identity value back via OUTPUT INSERTED. // • ValidateConfiguredQuery fetches the record then delegates // to IGBQueryExecutor.ValidateAsync (which checks all 6 rules // and marks the row IsValidated = 1 on success). // • SaveConfiguredQuery routes through BaseEntityAppService.ExecuteSaveAsync // (same convention as QualifierBLL) so a configured-query save gets the same // qualifier-validate/workflow-check/event-publish pipeline as every other // GOP entity save, instead of a bare DAL call with no transaction at all. // ============================================================ public class GBConfiguredQueryBLL : IGBConfiguredQueryBLL { private readonly IGBQueryExecutorDAL _Dal; private readonly IGBQueryExecutor _Executor; private readonly IValidation _Validation; private readonly IQueryExecutor _QueryExecutor; private readonly BaseEntityAppService _EntitySvc; private readonly ILogger _Logger; public GBConfiguredQueryBLL( IGBQueryExecutorDAL dal, IGBQueryExecutor executor, IValidation validation, IQueryExecutor queryExecutor, BaseEntityAppService entitySvc, ILogger logger) { _Dal = dal; _Executor = executor; _Validation = validation; _QueryExecutor = queryExecutor; _EntitySvc = entitySvc; _Logger = logger; } // ── Save (create or update) ─────────────────────────────── public async Task SaveConfiguredQuery( GBConfiguredQueryDTO dto, LoginDTO loginDTO, CancellationToken ct = default) { _Validation.NotEmpty(dto.QueryCode, nameof(dto.QueryCode)); _Validation.NotEmpty(dto.QueryName, nameof(dto.QueryName)); _Validation.NotEmpty(dto.SqlTemplate, nameof(dto.SqlTemplate)); bool isNew = dto.ConfiguredQueryId == 0; var trans = await _QueryExecutor.BeginTransactionAsync(loginDTO); try { await _EntitySvc.ExecuteSaveAsync( EntityConstant.GBCONFIGUREDQUERY, -1, // SAVEGBCONFIGUREDQUERYEVENTTYPEID — not yet in compiled GB5Shared.dll dto, loginDTO, async tx => { if (isNew) dto.ConfiguredQueryId = await _Dal.SaveConfiguredQuery(dto, loginDTO, tx, ct); else await _Dal.UpdateConfiguredQuery(dto, loginDTO, tx, ct); return dto.ConfiguredQueryId; }, null, BIZTRANSACTIONCLASSCONSTANT.GBCONFIGUREDQUERY, BizTransactionConstant.GBCONFIGUREDQUERY, trans, isNewEntity: isNew); await _QueryExecutor.CommitAsync(trans); } catch { await _QueryExecutor.RollbackAsync(trans); throw; } _Logger.LogInformation( "GBConfiguredQueryBLL: query {QueryCode} (Id={Id}) {Action} by user {UserId}", dto.QueryCode, dto.ConfiguredQueryId, isNew ? "created" : "updated", loginDTO.UserId); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {dto.ConfiguredQueryId}" : $"{SuccessResponse.UpdateSuccessMessage} {dto.ConfiguredQueryId}"; } // ── Read ───────────────────────────────────────────────── public async Task GetConfiguredQuery( int configuredQueryId, LoginDTO loginDTO, CancellationToken ct = default) { return await _Dal.GetConfiguredQueryById(configuredQueryId, loginDTO); } public async Task> GetConfiguredQueryList( LoginDTO loginDTO, CancellationToken ct = default) { return await _Dal.GetConfiguredQueryList(loginDTO, ct); } // ── Delete ─────────────────────────────────────────────── public async Task DeleteConfiguredQuery( int configuredQueryId, LoginDTO loginDTO, CancellationToken ct = default) { if (configuredQueryId <= 0) throw new ArgumentException("ConfiguredQueryId is required."); await _Dal.DeleteConfiguredQuery(configuredQueryId, loginDTO, ct); _Logger.LogInformation( "GBConfiguredQueryBLL: query Id={Id} soft-deleted by user {UserId}", configuredQueryId, loginDTO.UserId); return SuccessResponse.DeleteSuccessMessage; } public async Task> GetSelectListIntentProfiles( CriteriaDTO criteriaDTO, LoginDTO loginDTO, CancellationToken ct = default) { return await _Dal.GetSelectListIntentProfiles(criteriaDTO, loginDTO, ct); } public async Task> GetSelectListConfiguredQuery( CriteriaDTO criteriaDTO, LoginDTO loginDTO, CancellationToken ct = default) { return await _Dal.GetSelectListConfiguredQuery(criteriaDTO, loginDTO, ct); } // ── Validate ───────────────────────────────────────────── public async Task ValidateConfiguredQuery( int configuredQueryId, LoginDTO loginDTO, CancellationToken ct = default) { if (configuredQueryId <= 0) throw new ArgumentException("ConfiguredQueryId is required."); var record = await _Dal.GetConfiguredQueryById(configuredQueryId, loginDTO); if (record is null) throw new ArgumentException( $"Configured query id={configuredQueryId} not found."); var result = await _Executor.ValidateAsync(record.QueryCode, loginDTO, ct); if (result.IsValid) { _Logger.LogInformation( "GBConfiguredQueryBLL: query {QueryCode} (Id={Id}) validated OK by user {UserId}", record.QueryCode, configuredQueryId, loginDTO.UserId); return $"Query '{record.QueryCode}' validated successfully."; } var errorList = string.Join(" | ", result.Errors); _Logger.LogWarning( "GBConfiguredQueryBLL: query {QueryCode} (Id={Id}) validation failed: {Errors}", record.QueryCode, configuredQueryId, errorList); throw new InvalidOperationException( $"Validation failed for '{record.QueryCode}': {errorList}"); } } }