using System.Text.Json; using Dapr.Client; using GB5Shared.DaprCache; using GB5Shared.DTO.Framework.Login; using GB5Shared.GenerateAutoNumber; using GB5Shared.QueryExecutor; using GB5Shared.Resource.Response; using IDMSDAL.DTOs; using IDMSDAL.Interfaces; using IDMSBLL.Interfaces; using static GB5Shared.GB5Constant.Constant; using GB5Shared.EntityHandler; namespace IDMSBLL.Implementations; public class EngagementBLL : IEngagementBLL { private readonly IEngagementDAL _EngagementDAL; private readonly AutoNumber _AutoNumber; private readonly IQueryExecutor _QueryExecutor; private readonly KeyInvalidate _KeyInvalidate; private readonly BaseEntityAppService _BaseEntityAppService; private readonly DaprClient _DaprClient; private static readonly IReadOnlyDictionary _domainSets = new Dictionary { [1] = ["D01", "D03", "D05", "D10", "D11", "D12"], [2] = ["D01", "D03", "D04", "D05", "D06", "D07", "D08", "D09", "D10", "D11", "D12"], [3] = ["D01", "D02", "D03", "D04", "D05", "D06", "D07", "D08", "D09", "D10", "D11", "D12"], [4] = ["D01", "D02", "D03", "D04", "D05", "D06", "D07", "D08", "D09", "D10", "D11", "D12"], }; public EngagementBLL( IEngagementDAL engagementDAL, AutoNumber autoNumber, IQueryExecutor queryExecutor, KeyInvalidate keyInvalidate, BaseEntityAppService baseEntityAppService, DaprClient daprClient) { _EngagementDAL = engagementDAL; _AutoNumber = autoNumber; _QueryExecutor = queryExecutor; _KeyInvalidate = keyInvalidate; _BaseEntityAppService = baseEntityAppService; _DaprClient = daprClient; } // ── Reads ───────────────────────────────────────────────────────────────── public async Task GetEngagementAsync( int engagementId, LoginDTO login, CancellationToken ct) { var engagement = await _EngagementDAL.GetEngagementAsync(engagementId, login, ct) .ConfigureAwait(false); if (engagement is null) return null; var modules = await _EngagementDAL.GetEngagementModulesAsync(engagementId, login, ct) .ConfigureAwait(false); engagement.Modules = modules.ToList(); return engagement; } public async Task GetEngagementDashboardAsync( int engagementId, LoginDTO login, CancellationToken ct) { return await _EngagementDAL.GetEngagementDashboardAsync(engagementId, login, ct) .ConfigureAwait(false); } // ── Writes ──────────────────────────────────────────────────────────────── public async Task SaveEngagementAsync( EngagementDTO dto, LoginDTO login, CancellationToken ct) { if (dto is null) throw new ArgumentNullException(nameof(dto)); if (string.IsNullOrWhiteSpace(dto.EngagementName)) throw new ArgumentException("EngagementName is required.", nameof(dto)); if (dto.EngagementType == 0) throw new ArgumentException("EngagementType is required.", nameof(dto)); bool isNew = dto.EngagementId == 0; if (isNew) { var auto = await _AutoNumber.GetNumberAsync(1, AUTONUMBERCONSTANT.IDMSENGAGEMENT, login) .ConfigureAwait(false); dto.EngagementId = auto.StartNumber; dto.Status = 1; // Draft } var Trans = await _QueryExecutor.BeginTransactionAsync(login).ConfigureAwait(false); try { await _BaseEntityAppService.ExecuteSaveAsync( EntityConstant.OBJECTIDMSENGAGEMENT, isNew ? EventTypeConstant.SAVEIDMSENGAGEMENTEVENTTYPEID : EventTypeConstant.UPDATEIDMSENGAGEMENTEVENTTYPEID, dto, login, async tx => { if (isNew) await _EngagementDAL.SaveEngagementAsync(dto, login, tx, ct).ConfigureAwait(false); else await _EngagementDAL.UpdateEngagementAsync(dto, login, tx, ct).ConfigureAwait(false); return dto.EngagementId; }, null, BIZTRANSACTIONCLASSCONSTANT.IDMSENGAGEMENT, BizTransactionConstant.IDMSENGAGEMENT, Trans).ConfigureAwait(false); await _QueryExecutor.CommitAsync(Trans).ConfigureAwait(false); var cacheKey = new CacheKeyGeneration().KeyGeneration( dto.EngagementId, EntityConstant.OBJECTIDMSENGAGEMENT, CacheKeyLevel.CLIENT_LEVEL, login); await _KeyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); return isNew ? SuccessResponse.SaveSuccess : SuccessResponse.UpdateSuccess; } catch (Exception) { await _QueryExecutor.RollbackAsync(Trans).ConfigureAwait(false); throw; } } public async Task ActivateEngagementAsync( int engagementId, LoginDTO login, CancellationToken ct) { var engagement = await _EngagementDAL.GetEngagementAsync(engagementId, login, ct) .ConfigureAwait(false) ?? throw new InvalidOperationException($"Engagement {engagementId} not found."); if (engagement.Status != 1) throw new InvalidOperationException("Only Draft engagements can be activated."); if (!_domainSets.TryGetValue(engagement.EngagementType, out var domains)) domains = _domainSets[2]; // Standard fallback engagement.Status = 2; engagement.ActiveDomains = JsonSerializer.Serialize(domains); var Trans = await _QueryExecutor.BeginTransactionAsync(login).ConfigureAwait(false); try { await _BaseEntityAppService.ExecuteSaveAsync( EntityConstant.OBJECTIDMSENGAGEMENT, EventTypeConstant.ACTIVATEIDMSENGAGEMENTEVENTTYPEID, engagement, login, async tx => { await _EngagementDAL.ActivateEngagementAsync( engagementId, engagement.ActiveDomains, login, tx, ct).ConfigureAwait(false); return engagementId; }, null, BIZTRANSACTIONCLASSCONSTANT.IDMSENGAGEMENT, BizTransactionConstant.IDMSENGAGEMENT, Trans).ConfigureAwait(false); await _QueryExecutor.CommitAsync(Trans).ConfigureAwait(false); await PublishSeedingEventsAsync(engagementId, login.ClientId, domains, ct) .ConfigureAwait(false); var cacheKey = new CacheKeyGeneration().KeyGeneration( engagementId, EntityConstant.OBJECTIDMSENGAGEMENT, CacheKeyLevel.CLIENT_LEVEL, login); await _KeyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); return SuccessResponse.UpdateSuccess; } catch (Exception) { await _QueryExecutor.RollbackAsync(Trans).ConfigureAwait(false); throw; } } public async Task DeclareGoLiveAsync( int engagementId, DateTime goLiveOn, LoginDTO login, CancellationToken ct) { var engagement = await _EngagementDAL.GetEngagementAsync(engagementId, login, ct) .ConfigureAwait(false) ?? throw new InvalidOperationException($"Engagement {engagementId} not found."); if (engagement.Status != 2) throw new InvalidOperationException("Only Active engagements can declare go-live."); var Trans = await _QueryExecutor.BeginTransactionAsync(login).ConfigureAwait(false); try { await _BaseEntityAppService.ExecuteSaveAsync( EntityConstant.OBJECTIDMSENGAGEMENT, EventTypeConstant.GOLIVEIDMSENGAGEMENTEVENTTYPEID, engagement, login, async tx => { await _EngagementDAL.DeclareGoLiveAsync( engagementId, goLiveOn, login, tx, ct).ConfigureAwait(false); return engagementId; }, null, BIZTRANSACTIONCLASSCONSTANT.IDMSENGAGEMENT, BizTransactionConstant.IDMSENGAGEMENT, Trans).ConfigureAwait(false); await _QueryExecutor.CommitAsync(Trans).ConfigureAwait(false); await _DaprClient.PublishEventAsync( "pubsub", "idms.golive.declared", new { EngagementId = engagementId, GoLiveOn = goLiveOn, TenantId = login.ClientId }, ct).ConfigureAwait(false); var cacheKey = new CacheKeyGeneration().KeyGeneration( engagementId, EntityConstant.OBJECTIDMSENGAGEMENT, CacheKeyLevel.CLIENT_LEVEL, login); await _KeyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); return SuccessResponse.UpdateSuccess; } catch (Exception) { await _QueryExecutor.RollbackAsync(Trans).ConfigureAwait(false); throw; } } // ── Private helpers ─────────────────────────────────────────────────────── private async Task PublishSeedingEventsAsync( int engagementId, int tenantId, string[] domains, CancellationToken ct) { await _DaprClient.PublishEventAsync( "pubsub", "idms.engagement.activated", new { EngagementId = engagementId, TenantId = tenantId, Domains = domains }, ct).ConfigureAwait(false); foreach (var domain in domains) { await _DaprClient.PublishEventAsync( "pubsub", "idms.engagement.seeding.requested", new { EngagementId = engagementId, TenantId = tenantId, DomainCode = domain }, ct).ConfigureAwait(false); } } }