using AccountsDAL.CustomCode.Warehouse; using AccountsDAL.DTO.Warehouse; using GB5Shared.DTO.Framework.AutoNumber; using GB5Shared.DTO.Framework.Login; using GB5Shared.EventLogPublish; using GB5Shared.GenerateAutoNumber; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using GB5Shared.Validation; using Microsoft.Extensions.Logging; using static GB5Shared.GB5Constant.Constant; namespace AccountsBLL.Warehouse { public class WarehouseAdminBLL : IWarehouseAdminBLL { private readonly IWarehouseAdminDAL _dal; private readonly AutoNumber _autoNumber; private readonly IValidation _validation; private readonly EventLogPublish _eventLog; private readonly ILogger _logger; public WarehouseAdminBLL( IWarehouseAdminDAL dal, AutoNumber autoNumber, IValidation validation, EventLogPublish eventLog, ILogger logger) { _dal = dal; _autoNumber = autoNumber; _validation = validation; _eventLog = eventLog; _logger = logger; } // ─── Fact ───────────────────────────────────────────────────────────── public async Task GetWarehouseFactsAsync(LoginDTO login, CancellationToken ct) { try { return await _dal.GetWarehouseFactsAsync(login, ct).ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("get-warehouse-facts-failed", ex); _logger.LogError(ex, "GetWarehouseFacts failed"); throw; } } public async Task SaveWarehouseFactAsync(SaveWarehouseFactDTO dto, LoginDTO login, CancellationToken ct) { AutoNumberDTO? autoNumberDTO = null; try { GB5Trace.Step("validate-warehouse-fact", new { dto.FactId, dto.FactCode }); await _validation.NotEmpty(dto.FactCode, nameof(dto.FactCode)) .ConfigureAwait(false); await _validation.NotEmpty(dto.FactName, nameof(dto.FactName)) .ConfigureAwait(false); await _validation.NotEmpty(dto.FactTableName, nameof(dto.FactTableName)).ConfigureAwait(false); await _validation.NotEmpty(dto.DateDimColumn, nameof(dto.DateDimColumn)).ConfigureAwait(false); await _validation.NotEmpty(dto.OuDimColumn, nameof(dto.OuDimColumn)) .ConfigureAwait(false); var isNew = dto.FactId == 0; dto.ModifiedById = login.UserId; if (isNew) { autoNumberDTO = await _autoNumber.GetAutoNumber(1, "WAREHOUSEFACT", login).ConfigureAwait(false); dto.FactId = autoNumberDTO.StartNumber; dto.CreatedById = login.UserId; dto.TenantId = login.ClientId; } GB5Trace.Step("save-warehouse-fact", new { dto.FactId, isNew }); await _dal.SaveWarehouseFactAsync(dto, isNew, login, ct).ConfigureAwait(false); GB5Trace.Step("event-publish", new { EventTypeConstant.SAVEWAREHOUSEFACTEVENTTYPEID }); await _eventLog.PublishEventLogAsync( isNew ? "Warehouse Fact Created" : "Warehouse Fact Updated", dto, EventTypeConstant.SAVEWAREHOUSEFACTEVENTTYPEID, dto.FactId, login, ct: ct) .ConfigureAwait(false); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {dto.FactId}" : $"{SuccessResponse.UpdateSuccessMessage} {dto.FactId}"; } catch (Exception ex) { if (autoNumberDTO != null && dto.FactId != 0) await _autoNumber.RollbackAutoNumber("WAREHOUSEFACT", dto.FactId, login).ConfigureAwait(false); GB5Trace.MarkFailed("save-warehouse-fact-failed", ex); _logger.LogError(ex, "SaveWarehouseFact failed for FactCode {FactCode}", dto.FactCode); throw; } } public async Task DeleteWarehouseFactAsync(int factId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("delete-warehouse-fact", new { factId }); await _dal.DeleteWarehouseFactAsync(factId, login.UserId, login, ct).ConfigureAwait(false); GB5Trace.Step("event-publish", new { EventTypeConstant.DELETEWAREHOUSEFACTEVENTTYPEID }); await _eventLog.PublishEventLogAsync( "Warehouse Fact Deleted", new { FactId = factId }, EventTypeConstant.DELETEWAREHOUSEFACTEVENTTYPEID, factId, login, ct: ct) .ConfigureAwait(false); return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("delete-warehouse-fact-failed", ex); _logger.LogError(ex, "DeleteWarehouseFact failed for FactId {FactId}", factId); throw; } } // ─── Measure ────────────────────────────────────────────────────────── public async Task GetWarehouseMeasuresByFactAsync(int factId, LoginDTO login, CancellationToken ct) { try { return await _dal.GetWarehouseMeasuresByFactAsync(factId, login, ct).ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("get-warehouse-measures-failed", ex); _logger.LogError(ex, "GetWarehouseMeasures failed for FactId {FactId}", factId); throw; } } public async Task SaveWarehouseMeasureAsync(SaveWarehouseMeasureDTO dto, LoginDTO login, CancellationToken ct) { AutoNumberDTO? autoNumberDTO = null; try { GB5Trace.Step("validate-warehouse-measure", new { dto.MeasureId, dto.MeasureCode }); await _validation.NotEmpty(dto.MeasureCode, nameof(dto.MeasureCode)).ConfigureAwait(false); await _validation.NotEmpty(dto.MeasureName, nameof(dto.MeasureName)).ConfigureAwait(false); await _validation.NotEmpty(dto.ColumnName, nameof(dto.ColumnName)) .ConfigureAwait(false); if (dto.FactId == 0) throw new ArgumentException("FactId is required."); var isNew = dto.MeasureId == 0; dto.ModifiedById = login.UserId; if (isNew) { autoNumberDTO = await _autoNumber.GetAutoNumber(1, "WAREHOUSEMEASURE", login).ConfigureAwait(false); dto.MeasureId = autoNumberDTO.StartNumber; dto.CreatedById = login.UserId; } GB5Trace.Step("save-warehouse-measure", new { dto.MeasureId, isNew }); await _dal.SaveWarehouseMeasureAsync(dto, isNew, login, ct).ConfigureAwait(false); GB5Trace.Step("event-publish", new { EventTypeConstant.SAVEWAREHOUSEMEASUREEVENTTYPEID }); await _eventLog.PublishEventLogAsync( isNew ? "Warehouse Measure Created" : "Warehouse Measure Updated", dto, EventTypeConstant.SAVEWAREHOUSEMEASUREEVENTTYPEID, dto.MeasureId, login, ct: ct) .ConfigureAwait(false); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {dto.MeasureId}" : $"{SuccessResponse.UpdateSuccessMessage} {dto.MeasureId}"; } catch (Exception ex) { if (autoNumberDTO != null && dto.MeasureId != 0) await _autoNumber.RollbackAutoNumber("WAREHOUSEMEASURE", dto.MeasureId, login).ConfigureAwait(false); GB5Trace.MarkFailed("save-warehouse-measure-failed", ex); _logger.LogError(ex, "SaveWarehouseMeasure failed for MeasureCode {MeasureCode}", dto.MeasureCode); throw; } } public async Task DeleteWarehouseMeasureAsync(int measureId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("delete-warehouse-measure", new { measureId }); await _dal.DeleteWarehouseMeasureAsync(measureId, login.UserId, login, ct).ConfigureAwait(false); GB5Trace.Step("event-publish", new { EventTypeConstant.DELETEWAREHOUSEMEASUREEVENTTYPEID }); await _eventLog.PublishEventLogAsync( "Warehouse Measure Deleted", new { MeasureId = measureId }, EventTypeConstant.DELETEWAREHOUSEMEASUREEVENTTYPEID, measureId, login, ct: ct) .ConfigureAwait(false); return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("delete-warehouse-measure-failed", ex); _logger.LogError(ex, "DeleteWarehouseMeasure failed for MeasureId {MeasureId}", measureId); throw; } } // ─── Posting Rule ───────────────────────────────────────────────────── public async Task GetWarehousePostingRulesByFactAsync(int factId, LoginDTO login, CancellationToken ct) { try { return await _dal.GetWarehousePostingRulesByFactAsync(factId, login, ct).ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("get-warehouse-posting-rules-failed", ex); _logger.LogError(ex, "GetWarehousePostingRules failed for FactId {FactId}", factId); throw; } } public async Task SaveWarehousePostingRuleAsync(SaveWarehousePostingRuleDTO dto, LoginDTO login, CancellationToken ct) { AutoNumberDTO? autoNumberDTO = null; try { GB5Trace.Step("validate-warehouse-posting-rule", new { dto.PostingRuleId, dto.MeasureId }); if (dto.MeasureId == 0) throw new ArgumentException("MeasureId is required."); if (dto.SourceType == 0 && string.IsNullOrWhiteSpace(dto.NatureValues)) throw new ArgumentException("NatureValues is required for NatureBucketAggregation (SourceType=0)."); if (dto.SourceType == 1 && string.IsNullOrWhiteSpace(dto.FormulaExpression)) throw new ArgumentException("FormulaExpression is required for DerivedFormula (SourceType=1)."); var isNew = dto.PostingRuleId == 0; dto.ModifiedById = login.UserId; if (isNew) { autoNumberDTO = await _autoNumber.GetAutoNumber(1, "WAREHOUSEPOSTINGRULE", login).ConfigureAwait(false); dto.PostingRuleId = autoNumberDTO.StartNumber; dto.CreatedById = login.UserId; } GB5Trace.Step("save-warehouse-posting-rule", new { dto.PostingRuleId, isNew }); await _dal.SaveWarehousePostingRuleAsync(dto, isNew, login, ct).ConfigureAwait(false); GB5Trace.Step("event-publish", new { EventTypeConstant.SAVEWAREHOUSEPOSTINGRULEEVENTTYPEID }); await _eventLog.PublishEventLogAsync( isNew ? "Warehouse Posting Rule Created" : "Warehouse Posting Rule Updated", dto, EventTypeConstant.SAVEWAREHOUSEPOSTINGRULEEVENTTYPEID, dto.PostingRuleId, login, ct: ct) .ConfigureAwait(false); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {dto.PostingRuleId}" : $"{SuccessResponse.UpdateSuccessMessage} {dto.PostingRuleId}"; } catch (Exception ex) { if (autoNumberDTO != null && dto.PostingRuleId != 0) await _autoNumber.RollbackAutoNumber("WAREHOUSEPOSTINGRULE", dto.PostingRuleId, login).ConfigureAwait(false); GB5Trace.MarkFailed("save-warehouse-posting-rule-failed", ex); _logger.LogError(ex, "SaveWarehousePostingRule failed for MeasureId {MeasureId}", dto.MeasureId); throw; } } public async Task DeleteWarehousePostingRuleAsync(int postingRuleId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("delete-warehouse-posting-rule", new { postingRuleId }); await _dal.DeleteWarehousePostingRuleAsync(postingRuleId, login.UserId, login, ct).ConfigureAwait(false); GB5Trace.Step("event-publish", new { EventTypeConstant.DELETEWAREHOUSEPOSTINGRULEEVENTTYPEID }); await _eventLog.PublishEventLogAsync( "Warehouse Posting Rule Deleted", new { PostingRuleId = postingRuleId }, EventTypeConstant.DELETEWAREHOUSEPOSTINGRULEEVENTTYPEID, postingRuleId, login, ct: ct) .ConfigureAwait(false); return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("delete-warehouse-posting-rule-failed", ex); _logger.LogError(ex, "DeleteWarehousePostingRule failed for PostingRuleId {PostingRuleId}", postingRuleId); throw; } } // ─── DIMOU ETL ──────────────────────────────────────────────────────── public async Task PopulateDimOuAsync(LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("populate-dimou", new { login.ClientId }); var rowsAffected = await _dal.PopulateDimOuAsync(login, ct).ConfigureAwait(false); return $"DIMOU populated successfully. Rows affected: {rowsAffected}"; } catch (Exception ex) { GB5Trace.MarkFailed("populate-dimou-failed", ex); _logger.LogError(ex, "PopulateDimOU failed for ClientId {ClientId}", login.ClientId); throw; } } } }