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.Casting; using MMDAL.DTO.Casting; using Newtonsoft.Json; using static GB5Shared.GB5Constant.Constant; namespace MMBLL.Casting { public class CastingBLL : ICastingBLL { private readonly ICastingDAL _dal; private readonly AutoNumber _autoNumber; private readonly IQueryExecutor _queryExecutor; private readonly KeyInvalidate _keyInvalidate; private readonly BaseEntityAppService _baseEntityAppService; private readonly ILogger _logger; public CastingBLL( ICastingDAL 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 GetCasting(int castingId, LoginDTO login, CancellationToken ct) { var header = await _dal.GetCasting(castingId, login, ct).ConfigureAwait(false); if (header is null) return JsonConvert.SerializeObject(null); header.Details = (await _dal.GetCastingDetails(castingId, login, ct).ConfigureAwait(false)) .ToList(); return JsonConvert.SerializeObject(header); } public async Task GetCastingList( int ouId, DateTime? fromDate, DateTime? toDate, int firstNumber, int maxResult, LoginDTO login, CancellationToken ct) { var (items, total) = await _dal .GetCastingList(ouId, fromDate, toDate, firstNumber, maxResult, login, ct) .ConfigureAwait(false); return JsonConvert.SerializeObject(new { Items = items, TotalCount = total }); } // ── Save ────────────────────────────────────────────────────────────── public async Task SaveCasting(CastingSaveDTO dto, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("validate-casting", new { dto.CastingId }); if (dto.OUId <= 0) throw new ArgumentException("OUId is required."); if (dto.PeriodId <= 0) throw new ArgumentException("PeriodId is required."); bool isNew = dto.CastingId == 0; if (isNew) { var hNum = await _autoNumber .GetNumberAsync(1, AUTONUMBERCONSTANT.CASTING, login) .ConfigureAwait(false); dto.CastingId = hNum.StartNumber; } int detailCount = dto.Details.Count; if (detailCount > 0) { var detailNum = await _autoNumber .GetNumberAsync(detailCount, AUTONUMBERCONSTANT.CASTINGDETAIL, login) .ConfigureAwait(false); int nextDetailId = detailNum.StartNumber; for (short i = 0; i < dto.Details.Count; i++) { dto.Details[i].CastingDetailId = nextDetailId++; dto.Details[i].CastingId = dto.CastingId; dto.Details[i].SlNo = (short)(i + 1); dto.Details[i].TenantId = login.ClientId; } } dto.TenantId = login.ClientId; GB5Trace.Step("save-casting", new { dto.CastingId, isNew }); await _baseEntityAppService.ExecuteSaveAsync( EntityConstant.OBJECTCASTING, EventTypeConstant.SAVECASTINGEVENTTYPEID, dto, login, async tx => { if (isNew) await _dal.SaveCastingHeader(dto, login, tx, ct).ConfigureAwait(false); else { await _dal.DeleteCastingDetails(dto.CastingId, login, tx, ct).ConfigureAwait(false); await _dal.UpdateCastingHeader(dto, login, tx, ct).ConfigureAwait(false); } await _dal.BulkSaveCastingDetails(dto.Details, login.ClientId, login, tx, ct) .ConfigureAwait(false); return dto.CastingId; }, new Dictionary { { "CastingId", dto.CastingId } }, isNewEntity: isNew); GB5Trace.Step("event-publish", new { EventTypeConstant.SAVECASTINGEVENTTYPEID }); var cacheKey = new CacheKeyGeneration() .KeyGeneration(dto.CastingId, EntityConstant.OBJECTCASTING, CacheKeyLevel.CLIENT_LEVEL, login); await _keyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {dto.CastingId}" : SuccessResponse.UpdateSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("save-casting-failed", ex); _logger.LogError(ex, "SaveCasting failed for CastingId {CastingId}", dto.CastingId); throw; } } // ── Delete ──────────────────────────────────────────────────────────── public async Task DeleteCasting(int castingId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("delete-casting", new { castingId }); var tx = await _queryExecutor.BeginTransactionAsync(login); try { await _dal.DeleteCastingDetails(castingId, login, tx, ct).ConfigureAwait(false); await _dal.SoftDeleteCasting(castingId, login, tx, ct).ConfigureAwait(false); await _queryExecutor.CommitAsync(tx); } catch { await _queryExecutor.RollbackAsync(tx); throw; } GB5Trace.Step("event-publish", new { EventTypeConstant.DELETECASTINGEVENTTYPEID }); var cacheKey = new CacheKeyGeneration() .KeyGeneration(castingId, EntityConstant.OBJECTCASTING, CacheKeyLevel.CLIENT_LEVEL, login); await _keyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("delete-casting-failed", ex); _logger.LogError(ex, "DeleteCasting failed for CastingId {CastingId}", castingId); throw; } } } }