115 lines
4.7 KiB
C#
115 lines
4.7 KiB
C#
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<ValidationResult> 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;
|
|
}
|
|
}
|
|
} |