107 lines
4.4 KiB
C#
107 lines
4.4 KiB
C#
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<ValidationResult> 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;
|
|
}
|
|
}
|
|
} |