using GB5Shared.DTO.Framework.Login; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using Microsoft.Extensions.Logging; using TMSDAL.CustomCode.KpiPosting; namespace TMSBLL.KpiPosting { // Bypasses AnalyticsBLL.KPIEvaluationBLL entirely -- that engine only evaluates // MKPI.ACTUALVALUEEXPRESSION strings against the FFINANCE BI catalog (hardcoded // BICatalogId=2). TMS's 11 KPIs are computed directly against TMS's own tables here and // written straight into the shared TKPIVALUE/MKPISTATUS tables, using the exact same // delete-then-insert-per-(Period,OU) shape KPIEvaluationBLL.PostKPIValues established -- // just with a TMS-scoped delete (see TmsKpiPostingQB.DELETE_EXISTING_VALUES) so this job can // never touch another module's posted KPI rows. public class TmsKpiPostingBLL : ITmsKpiPostingBLL { private readonly ITmsKpiPostingDAL _dal; private readonly ILogger _logger; public TmsKpiPostingBLL(ITmsKpiPostingDAL dal, ILogger logger) { _dal = dal; _logger = logger; } public async Task PostAllTmsKpis(LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("validate-tms-kpi-posting", new { login.ClientId }); var period = await _dal.ResolveCurrentMonthlyPeriod(login, ct).ConfigureAwait(false); if (period is null) { _logger.LogWarning("TmsKpiPostingBLL: no MONTHLY MGBPERIODDETAIL row covers today for ClientId {ClientId} -- skipping.", login.ClientId); return null; } var (periodDetailId, fromDate, toDate) = period.Value; const int ouId = TmsKpiIds.WholeOrgOuId; GB5Trace.Step("compute-tms-kpi-actuals", new { login.ClientId, periodDetailId }); var actuals = new Dictionary { [TmsKpiIds.ComplianceCoveragePct] = await _dal.GetComplianceCoveragePct(toDate, login, ct).ConfigureAwait(false), [TmsKpiIds.NominationApprovalTatDays] = await _dal.GetNominationApprovalTatDays(fromDate, toDate, login, ct).ConfigureAwait(false), [TmsKpiIds.CostPerTrainedEmployee] = await _dal.GetCostPerTrainedEmployee(fromDate, toDate, login, ct).ConfigureAwait(false), [TmsKpiIds.VenueUtilizationPct] = await _dal.GetVenueUtilizationPct(fromDate, toDate, login, ct).ConfigureAwait(false), [TmsKpiIds.PassRatePct] = await _dal.GetPassRatePct(fromDate, toDate, login, ct).ConfigureAwait(false), [TmsKpiIds.FirstAttemptPassRatePct] = await _dal.GetFirstAttemptPassRatePct(fromDate, toDate, login, ct).ConfigureAwait(false), [TmsKpiIds.SkillUpgradeApprovalRatePct] = await _dal.GetSkillUpgradeApprovalRatePct(fromDate, toDate, login, ct).ConfigureAwait(false), [TmsKpiIds.CertRenewalCompliancePct] = await _dal.GetCertRenewalCompliancePct(toDate, login, ct).ConfigureAwait(false), [TmsKpiIds.ExpiringCertificates30D] = await _dal.GetExpiringCertificates30D(login, ct).ConfigureAwait(false), [TmsKpiIds.FeedbackResponseRatePct] = await _dal.GetFeedbackResponseRatePct(fromDate, toDate, login, ct).ConfigureAwait(false), [TmsKpiIds.LearningGainPct] = await _dal.GetLearningGainPct(fromDate, toDate, login, ct).ConfigureAwait(false), }; GB5Trace.Step("delete-existing-tms-kpi-values", new { login.ClientId, periodDetailId }); await _dal.DeleteExistingValues(periodDetailId, ouId, TmsKpiIds.All.ToList(), login, ct).ConfigureAwait(false); var previousPeriodDetailId = await _dal.GetPreviousMonthlyPeriodDetailId(periodDetailId, login, ct).ConfigureAwait(false); GB5Trace.Step("post-tms-kpi-values", new { login.ClientId, periodDetailId, count = actuals.Count }); var posted = 0; foreach (var (kpiId, actual) in actuals) { var bands = await _dal.GetStatusBands(kpiId, login, ct).ConfigureAwait(false); var statusId = actual is null ? -1 : bands.FirstOrDefault(b => actual.Value >= b.VALUEFROM && actual.Value <= b.VALUETO)?.KPISTATUSID ?? -1; byte? trend = null; if (actual is not null && previousPeriodDetailId is not null) { var previousActual = await _dal.GetPreviousActual(kpiId, previousPeriodDetailId.Value, ouId, login, ct).ConfigureAwait(false); if (previousActual is not null) trend = actual.Value > previousActual.Value ? (byte)1 : actual.Value < previousActual.Value ? (byte)2 : (byte)0; } await _dal.InsertValue(kpiId, periodDetailId, ouId, actual, trend, statusId, login, ct).ConfigureAwait(false); posted++; } return $"{SuccessResponse.SaveSuccessMessage} {posted} TMS KPI value(s) posted for ClientId {login.ClientId}, PeriodDetailId {periodDetailId}."; } catch (Exception ex) { GB5Trace.MarkFailed("post-tms-kpi-values-failed", ex); _logger.LogError(ex, "TmsKpiPostingBLL.PostAllTmsKpis failed for ClientId {ClientId}", login.ClientId); throw; } } } }