using System; using System.Threading.Tasks; using System.Text; using System.Linq; using Compliance.Domain.Models; using Compliance.DTOs; using Compliance.Infrastructure.Repositories; namespace Compliance.Services.ValidationRules { public class AdditionalChargeValidationRule(IDistributorRepository distributorRepository) : IValidationRule { private readonly IDistributorRepository _distributorRepository = distributorRepository; private const string RULE_NAME = "Additional Charge Validation"; private const string AMOUNT_ERROR = "Additional charge amount doesn't match the expected value"; private const string MISSING_CHARGE_ERROR = "Mandatory charge not applied"; private const string INVALID_CHARGE_ERROR = "Invalid charge type applied"; private const string JUSTIFICATION_ERROR = "Missing justification for additional charge"; private const string TOTAL_ERROR = "Total additional charges exceed maximum allowed percentage"; private const decimal TOLERANCE = 0.01m; public int Priority => 8; // Run after subsidy validation public async Task ValidateAsync( BillComplianceRequest request, DistributorInformation distributorInfo, Client clientInfo) { var chargeInfo = await _distributorRepository.GetAdditionalChargeInformationAsync( request.DistributorName, request.Month) ?? throw new InvalidOperationException("Additional charge information not found"); var isValid = true; var message = new StringBuilder(); // Validate mandatory charges foreach (var mandatoryCharge in chargeInfo.MandatoryCharges) { if (!request.AdditionalCharges.Any(c => c.Type.Equals(mandatoryCharge, StringComparison.OrdinalIgnoreCase))) { isValid = false; message.AppendLine($"{MISSING_CHARGE_ERROR}: {mandatoryCharge}"); } } // Calculate bill base for percentage validation var billBase = CalculateBillBase(request); var totalCharges = 0m; // Validate each charge foreach (var charge in request.AdditionalCharges) { // Validate charge type if (!chargeInfo.ChargeRates.ContainsKey(charge.Type.ToLower())) { isValid = false; message.AppendLine($"{INVALID_CHARGE_ERROR}: {charge.Type}"); continue; } // Check for exemptions if (chargeInfo.ConsumerGroupExemptions.Contains($"{charge.Type.ToLower()}:{request.ConsumerGroup.ToLower()}")) { if (charge.Amount != 0) { isValid = false; message.AppendLine($"Consumer group exempt from charge: {charge.Type}"); } continue; } // Validate justification if (chargeInfo.RequireJustification && string.IsNullOrWhiteSpace(charge.Justification)) { isValid = false; message.AppendLine($"{JUSTIFICATION_ERROR}: {charge.Type}"); } // Validate amount var expectedAmount = billBase * (chargeInfo.ChargeRates[charge.Type.ToLower()] / 100m); if (Math.Abs(charge.Amount - expectedAmount) > TOLERANCE) { isValid = false; message.AppendLine($"{AMOUNT_ERROR} for {charge.Type}. Expected: {expectedAmount:F2}, Found: {charge.Amount:F2}"); } totalCharges += charge.Amount; } // Validate total charges percentage var maxAllowed = billBase * (chargeInfo.MaximumTotalPercentage / 100m); if (totalCharges > maxAllowed) { isValid = false; message.AppendLine($"{TOTAL_ERROR}. Maximum: {maxAllowed:F2}, Total: {totalCharges:F2}"); } return new ValidationResult { IsValid = isValid, RuleName = RULE_NAME, Message = message.ToString().TrimEnd() }; } private static decimal CalculateBillBase(BillComplianceRequest request) { return request.TUSDAmount + request.TEAmount + request.PowerFactorAdjustment + request.FlagAmount + request.ICMSAmount + request.MunicipalTaxAmount; } } }