using GB5Shared.DaprCache; using GB5Shared.DTO.Framework.AutoNumber; using GB5Shared.DTO.Framework.Login; using GB5Shared.EntityHandler; using GB5Shared.GenerateAutoNumber; using GB5Shared.QueryExecutor; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using Microsoft.Extensions.Logging; using MMDAL.CustomCode.Heat; using MMDAL.DTO.Heat; using Newtonsoft.Json; using static GB5Shared.GB5Constant.Constant; namespace MMBLL.Heat { public class HeatBLL : IHeatBLL { private readonly IHeatDAL _dal; private readonly AutoNumber _autoNumber; private readonly IQueryExecutor _queryExecutor; private readonly KeyInvalidate _keyInvalidate; private readonly BaseEntityAppService _baseEntityAppService; private readonly ILogger _logger; public HeatBLL( IHeatDAL dal, AutoNumber autoNumber, IQueryExecutor queryExecutor, KeyInvalidate keyInvalidate, BaseEntityAppService baseEntityAppService, ILogger logger) { _dal = dal; _autoNumber = autoNumber; _queryExecutor = queryExecutor; _keyInvalidate = keyInvalidate; _baseEntityAppService = baseEntityAppService; _logger = logger; } // ── Get ─────────────────────────────────────────────────────────────── public async Task GetHeat(int heatId, LoginDTO login, CancellationToken ct) { var header = await _dal.GetHeat(heatId, login, ct).ConfigureAwait(false); if (header is null) return JsonConvert.SerializeObject(null); header.Details = (await _dal.GetHeatDetails(heatId, login, ct).ConfigureAwait(false)) .ToList(); header.Laddles = (await _dal.GetHeatLaddles(heatId, login, ct).ConfigureAwait(false)) .ToList(); return JsonConvert.SerializeObject(header); } public async Task GetHeatList( int ouId, DateTime? fromDate, DateTime? toDate, int firstNumber, int maxResult, LoginDTO login, CancellationToken ct) { var (items, total) = await _dal .GetHeatList(ouId, fromDate, toDate, firstNumber, maxResult, login, ct) .ConfigureAwait(false); return JsonConvert.SerializeObject(new { Items = items, TotalCount = total }); } // ── Save ────────────────────────────────────────────────────────────── public async Task SaveHeat(HeatSaveDTO dto, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("validate-heat", new { dto.HeatId }); if (dto.OUId <= 0) throw new ArgumentException("OUId is required."); if (dto.PeriodId <= 0) throw new ArgumentException("PeriodId is required."); bool isNew = dto.HeatId == 0; if (isNew) { var hNum = await _autoNumber .GetNumberAsync(1, AUTONUMBERCONSTANT.HEAT, login) .ConfigureAwait(false); dto.HeatId = hNum.StartNumber; } int detailCount = dto.Details.Count; if (detailCount > 0) { var detailNum = await _autoNumber .GetNumberAsync(detailCount, AUTONUMBERCONSTANT.HEATDETAIL, login) .ConfigureAwait(false); int nextDetailId = detailNum.StartNumber; for (short i = 0; i < dto.Details.Count; i++) { dto.Details[i].HeatDetailId = nextDetailId++; dto.Details[i].HeatId = dto.HeatId; dto.Details[i].SlNo = (short)(i + 1); dto.Details[i].TenantId = login.ClientId; } } // Assign IDs and order to laddles for (short i = 0; i < dto.Laddles.Count; i++) { dto.Laddles[i].HeatId = dto.HeatId; dto.Laddles[i].SlNo = (short)(i + 1); dto.Laddles[i].TenantId = login.ClientId; } dto.TenantId = login.ClientId; GB5Trace.Step("save-heat", new { dto.HeatId, isNew }); await _baseEntityAppService.ExecuteSaveAsync( EntityConstant.OBJECTHEAT, EventTypeConstant.SAVEHEATEVENTTYPEID, dto, login, async tx => { if (isNew) await _dal.SaveHeatHeader(dto, login, tx, ct).ConfigureAwait(false); else { await _dal.DeleteHeatDetails(dto.HeatId, login, tx, ct).ConfigureAwait(false); await _dal.DeleteHeatLaddles(dto.HeatId, login, tx, ct).ConfigureAwait(false); await _dal.UpdateHeatHeader(dto, login, tx, ct).ConfigureAwait(false); } await _dal.BulkSaveHeatDetails(dto.Details, login.ClientId, login, tx, ct) .ConfigureAwait(false); await _dal.BulkSaveHeatLaddles(dto.Laddles, login.ClientId, login, tx, ct) .ConfigureAwait(false); return dto.HeatId; }, new Dictionary { { "HeatId", dto.HeatId } }, isNewEntity: isNew); GB5Trace.Step("event-publish", new { EventTypeConstant.SAVEHEATEVENTTYPEID }); var cacheKey = new CacheKeyGeneration() .KeyGeneration(dto.HeatId, EntityConstant.OBJECTHEAT, CacheKeyLevel.CLIENT_LEVEL, login); await _keyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {dto.HeatId}" : SuccessResponse.UpdateSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("save-heat-failed", ex); _logger.LogError(ex, "SaveHeat failed for HeatId {HeatId}", dto.HeatId); throw; } } // ── Delete ──────────────────────────────────────────────────────────── public async Task DeleteHeat(int heatId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("delete-heat", new { heatId }); var tx = await _queryExecutor.BeginTransactionAsync(login); try { await _dal.DeleteHeatDetails(heatId, login, tx, ct).ConfigureAwait(false); await _dal.DeleteHeatLaddles(heatId, login, tx, ct).ConfigureAwait(false); await _dal.SoftDeleteHeat(heatId, login, tx, ct).ConfigureAwait(false); await _queryExecutor.CommitAsync(tx); } catch { await _queryExecutor.RollbackAsync(tx); throw; } GB5Trace.Step("event-publish", new { EventTypeConstant.DELETEHEATEVENTTYPEID }); var cacheKey = new CacheKeyGeneration() .KeyGeneration(heatId, EntityConstant.OBJECTHEAT, CacheKeyLevel.CLIENT_LEVEL, login); await _keyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("delete-heat-failed", ex); _logger.LogError(ex, "DeleteHeat failed for HeatId {HeatId}", heatId); throw; } } } }