using GB5Shared.DTO.Framework.Login; using GoodBooks.PAY.PAYDAL.CustomCode.Loyalty; using GoodBooks.PAY.PAYDAL.DTO.Loyalty; using Microsoft.Extensions.Logging; namespace GoodBooks.PAY.PAYBLL.Loyalty { // ───────────────────────────────────────────────────────────────────────── // LoyaltyEarnCfgBLL // // Earn rule priority engine: // RuleType 5 = ByProduct — matches on ReferenceId = productId // RuleType 4 = ByCategory — matches on ReferenceId = categoryId // RuleType 3 = ByGateway — matches on ReferenceId = gatewayId // RuleType 2 = ByTier — matches on ReferenceId = tierId // RuleType 1 = Global — always matches (no ReferenceId check) // // Earliest match after ordering by RuleType DESC wins. // DAL returns rules ordered by RULETYPE DESC — first match is highest priority. // ───────────────────────────────────────────────────────────────────────── public class LoyaltyEarnCfgBLL : ILoyaltyEarnCfgBLL { private readonly ILoyaltyEarnCfgDAL _earnCfgDal; private readonly ILogger _logger; public LoyaltyEarnCfgBLL(ILoyaltyEarnCfgDAL earnCfgDal, ILogger logger) { _earnCfgDal = earnCfgDal; _logger = logger; } public async Task> GetEarnCfgsByProgram( int programId, LoginDTO login, CancellationToken ct) { try { return await _earnCfgDal.GetEarnCfgsByProgram(programId, login).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "GetEarnCfgsByProgram failed for ProgramId {ProgramId}", programId); throw; } } /// /// Pure in-memory priority resolution — no I/O. /// Caller must supply rules already filtered by date range (GetEarnCfgsByProgram handles this). /// Rules must be ordered by RuleType DESC (DAL guarantee). /// public LoyaltyEarnCfgDTO? ResolveEarnRule( IEnumerable rules, int? productId, int? categoryId, int? gatewayId, int? tierId) { if (rules is null) return null; foreach (var rule in rules) // already ordered by RuleType DESC { bool match = rule.RuleType switch { 5 => productId.HasValue && rule.ReferenceId == productId, // ByProduct 4 => categoryId.HasValue && rule.ReferenceId == categoryId, // ByCategory 3 => gatewayId.HasValue && rule.ReferenceId == gatewayId, // ByGateway 2 => tierId.HasValue && rule.ReferenceId == tierId, // ByTier 1 => true, // Global _ => false }; if (match) return rule; } return null; } /// /// Points = Floor(netPayableAmt × rule.EarnPoints × tierMultiplier). /// tierMultiplier = tier.EarnMultiplier when tiering is active; 1.0 otherwise. /// public decimal CalculateEarnPoints(decimal netPayableAmt, LoyaltyEarnCfgDTO earnRule, LoyaltyTierDTO? tier) { decimal multiplier = tier?.EarnMultiplier > 0m ? tier.EarnMultiplier : 1.0m; return Math.Floor(netPayableAmt * earnRule.EarnPoints * multiplier); } } }