using System; using System.Threading.Tasks; using System.Text; using Compliance.Domain.Models; using Compliance.DTOs; using Compliance.Infrastructure.Repositories; namespace Compliance.Services.ValidationRules { public class PaymentTermsValidationRule(IDistributorRepository distributorRepository) : IValidationRule { private readonly IDistributorRepository _distributorRepository = distributorRepository; private const string RULE_NAME = "Payment Terms Validation"; private const string DUE_DATE_ERROR = "Due date doesn't meet minimum required days"; private const string LATE_FEE_ERROR = "Late payment fee calculation is incorrect"; private const string INTEREST_ERROR = "Interest calculation is incorrect"; private const string PARTIAL_ERROR = "Partial payment not allowed for this consumer"; private const decimal TOLERANCE = 0.01m; public int Priority => 9; // Run after additional charges validation public async Task ValidateAsync( BillComplianceRequest request, DistributorInformation distributorInfo, Client clientInfo) { var termsInfo = await _distributorRepository.GetPaymentTermsInformationAsync( request.DistributorName, request.ConsumerGroup, request.Month) ?? throw new InvalidOperationException("Payment terms information not found"); var isValid = true; var message = new StringBuilder(); // Get applicable minimum due days var minimumDueDays = termsInfo.GroupSpecificDueDays.TryGetValue( request.ConsumerGroup.ToLower(), out var specialDays) ? specialDays : termsInfo.MinimumDueDays; // Validate due date var daysUntilDue = (request.DueDate - request.IssueDate).Days; if (daysUntilDue < minimumDueDays) { isValid = false; message.AppendLine($"{DUE_DATE_ERROR}. Minimum: {minimumDueDays}, Found: {daysUntilDue}"); } // Validate late payment charges if applicable if (request.PaymentDate > request.DueDate && !termsInfo.ExemptFromLateCharges.Contains(request.ConsumerGroup.ToLower())) { // Validate late payment fee var expectedLateFee = CalculateLateFee(request.BillTotalBeforeLateCharges, termsInfo.LatePaymentFeePercentage); if (Math.Abs(request.LatePaymentFee - expectedLateFee) > TOLERANCE) { isValid = false; message.AppendLine($"{LATE_FEE_ERROR}. Expected: {expectedLateFee:F2}, Found: {request.LatePaymentFee:F2}"); } // Validate interest var expectedInterest = CalculateInterest( request.BillTotalBeforeLateCharges, request.DueDate, request.PaymentDate, termsInfo.MonthlyInterestRate); if (Math.Abs(request.InterestAmount - expectedInterest) > TOLERANCE) { isValid = false; message.AppendLine($"{INTEREST_ERROR}. Expected: {expectedInterest:F2}, Found: {request.InterestAmount:F2}"); } } // Validate partial payment if applicable if (request.IsPartialPayment && !termsInfo.AllowPartialPayments) { isValid = false; message.AppendLine(PARTIAL_ERROR); } return new ValidationResult { IsValid = isValid, RuleName = RULE_NAME, Message = message.ToString().TrimEnd() }; } private static decimal CalculateLateFee(decimal amount, decimal feePercentage) { return amount * (feePercentage / 100m); } private static decimal CalculateInterest( decimal amount, DateTime dueDate, DateTime paymentDate, decimal monthlyRate) { var monthsLate = ((paymentDate.Year - dueDate.Year) * 12) + paymentDate.Month - dueDate.Month + (paymentDate.Day >= dueDate.Day ? 0 : -1); if (monthsLate <= 0) return 0; return amount * (monthlyRate / 100m) * monthsLate; } } }