using GB5Shared.DaprCache; using GB5Shared.DTO.Framework.Login; using GB5Shared.EventLogPublish; using GB5Shared.GenerateAutoNumber; using GB5Shared.QueryExecutor; using GB5Shared.Resource.Response; using GoodBooks.PAY.PAYDAL.CustomCode.Loyalty; using GoodBooks.PAY.PAYDAL.DTO.Loyalty; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using static GB5Shared.GB5Constant.Constant; namespace GoodBooks.PAY.PAYBLL.Loyalty { // ───────────────────────────────────────────────────────────────────────── // LoyaltyProgramBLL // // SaveLoyaltyProgram handles header + tiers + earn rules in ONE transaction // (mirrors SupplyGroupBLL pattern). // // AutoNumber batch allocation: // isNew: one GetAutoNumber call for header ID + tier IDs // update: separate per-tier GetAutoNumber calls (inside tx) for new tiers // // EntityConstant: OBJECTMLOYALTYPROGRAM = -1382000006 // ───────────────────────────────────────────────────────────────────────── public class LoyaltyProgramBLL : ILoyaltyProgramBLL { private readonly ILoyaltyProgramDAL _programDal; private readonly ILoyaltyTierDAL _tierDal; private readonly ILoyaltyEarnCfgDAL _earnCfgDal; private readonly AutoNumber _autoNumber; private readonly EventLogPublish _eventLogPublish; private readonly KeyInvalidate _keyInvalidate; private readonly IQueryExecutor _queryExecutor; private readonly ILogger _logger; public LoyaltyProgramBLL( ILoyaltyProgramDAL programDal, ILoyaltyTierDAL tierDal, ILoyaltyEarnCfgDAL earnCfgDal, AutoNumber autoNumber, EventLogPublish eventLogPublish, KeyInvalidate keyInvalidate, IQueryExecutor queryExecutor, ILogger logger) { _programDal = programDal; _tierDal = tierDal; _earnCfgDal = earnCfgDal; _autoNumber = autoNumber; _eventLogPublish = eventLogPublish; _keyInvalidate = keyInvalidate; _queryExecutor = queryExecutor; _logger = logger; } // ── GetLoyaltyProgram ────────────────────────────────────────────────── public async Task GetLoyaltyProgram(int programId, LoginDTO login, CancellationToken ct) { try { return await _programDal.GetLoyaltyProgram(programId, login).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "GetLoyaltyProgram failed for ProgramId {ProgramId}", programId); throw; } } // ── GetLoyaltyProgramList ───────────────────────────────────────────── public async Task GetLoyaltyProgramList(int pageOffset, int pageSize, LoginDTO login, CancellationToken ct) { try { return await _programDal.GetLoyaltyProgramList(pageOffset, pageSize, login).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "GetLoyaltyProgramList failed for TenantId {TenantId}", login.ClientId); throw; } } // ── SaveLoyaltyProgram ──────────────────────────────────────────────── // // Transaction scope covers header + all tiers + all earn configs. // On update: existing tiers are deleted and re-inserted (fresh IDs). // On insert: batch auto-number covers header ID + all tier IDs in one call. public async Task SaveLoyaltyProgram(LoyaltyProgramDTO dto, LoginDTO login, CancellationToken ct) { try { bool isNew = dto.LoyaltyProgramId == 0; var tiers = dto.LoyaltyTierArray ?? new List(); var earnCfgs = dto.LoyaltyEarnCfgArray ?? new List(); if (isNew) { // Batch auto-number: 1 header ID + N tier IDs int batchCount = 1 + tiers.Count; var autoNum = await _autoNumber.GetAutoNumber(batchCount, "LOYALTYPROGRAM", login) .ConfigureAwait(false); dto.LoyaltyProgramId = autoNum.StartNumber; dto.CreatedById = login.UserId; dto.CreatedOn = DateTime.UtcNow; // Pre-assign sequential tier IDs from the batch for (int i = 0; i < tiers.Count; i++) { tiers[i].LoyaltyTierId = autoNum.StartNumber + 1 + i; tiers[i].LoyaltyProgramId = dto.LoyaltyProgramId; // Note: LoyaltyTierDTO has no TenantId field — tenant is inherited from program } } dto.ModifiedById = login.UserId; dto.ModifiedOn = DateTime.UtcNow; dto.TenantId = login.ClientId; var tx = await _queryExecutor.BeginTransactionAsync(login).ConfigureAwait(false); try { // Header if (isNew) await _programDal.InsertLoyaltyProgram(dto, login, tx).ConfigureAwait(false); else { await _programDal.UpdateLoyaltyProgram(dto, login, tx).ConfigureAwait(false); // Delete existing tiers — re-insert below with fresh IDs await _programDal.DeleteLoyaltyTiersByProgram(dto.LoyaltyProgramId, login, tx) .ConfigureAwait(false); } // Tiers foreach (var tier in tiers) { if (!isNew) { // Update path: each tier needs a fresh auto-number inside the tx var tierNum = await _autoNumber.GetAutoNumber(1, "LOYALTYTIER", login) .ConfigureAwait(false); tier.LoyaltyTierId = tierNum.StartNumber; tier.LoyaltyProgramId = dto.LoyaltyProgramId; // Note: LoyaltyTierDTO has no TenantId field } await _tierDal.InsertLoyaltyTier(tier, login, tx).ConfigureAwait(false); } // Earn configs foreach (var earnCfg in earnCfgs) { earnCfg.LoyaltyProgramId = dto.LoyaltyProgramId; earnCfg.TenantId = login.ClientId; if (earnCfg.LoyaltyEarnCfgId == 0) { var earnNum = await _autoNumber.GetAutoNumber(1, "LOYALTYEARNCFG", login) .ConfigureAwait(false); earnCfg.LoyaltyEarnCfgId = earnNum.StartNumber; earnCfg.CreatedById = login.UserId; earnCfg.CreatedOn = DateTime.UtcNow; await _earnCfgDal.InsertLoyaltyEarnCfg(earnCfg, login, tx).ConfigureAwait(false); } else { earnCfg.ModifiedById = login.UserId; earnCfg.ModifiedOn = DateTime.UtcNow; await _earnCfgDal.UpdateLoyaltyEarnCfg(earnCfg, login, tx).ConfigureAwait(false); } } await _queryExecutor.CommitAsync(tx).ConfigureAwait(false); } catch { await _queryExecutor.RollbackAsync(tx).ConfigureAwait(false); throw; } // Cache invalidation string cacheKey = new CacheKeyGeneration().KeyGeneration( dto.LoyaltyProgramId.ToString(), EntityConstant.OBJECTMLOYALTYPROGRAM, CacheKeyLevel.CLIENT_LEVEL, login); _keyInvalidate.AllInvalidateCache(cacheKey); // Event log (fire-and-forget) try { await _eventLogPublish.PublishEventLogAsync( isNew ? "Loyalty Program Created" : "Loyalty Program Updated", new { dto.LoyaltyProgramId, dto.ProgramCode, dto.ProgramName }, isNew ? EventTypeConstant.SAVELOAYLTYPROGRAMEVENTTYPEID : EventTypeConstant.UPDATELOAYLTYPROGRAMEVENTTYPEID, 0, login).ConfigureAwait(false); } catch (Exception ex) { _logger.LogWarning(ex, "Event log failed for LoyaltyProgram {ProgramId}", dto.LoyaltyProgramId); } return isNew ? $"{SuccessResponse.SaveSuccessMessage} {dto.LoyaltyProgramId}" : SuccessResponse.UpdateSuccess; } catch (Exception ex) { _logger.LogError(ex, "SaveLoyaltyProgram failed for ProgramId {ProgramId}", dto.LoyaltyProgramId); throw; } } } }