108 lines
4.5 KiB
C#
108 lines
4.5 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 SeasonalTariffValidationRule(IDistributorRepository distributorRepository) : IValidationRule
|
|
{
|
|
private readonly IDistributorRepository _distributorRepository = distributorRepository;
|
|
private const string RULE_NAME = "Seasonal Tariff Validation";
|
|
private const string TUSD_ERROR = "TUSD amount with seasonal adjustment doesn't match the expected value";
|
|
private const string TE_ERROR = "TE amount with seasonal adjustment doesn't match the expected value";
|
|
private const string SEASON_ERROR = "Incorrect season applied for the billing period";
|
|
private const decimal TOLERANCE = 0.01m;
|
|
public int Priority => 4; // Run after reactive energy validation
|
|
|
|
public async Task<ValidationResult> ValidateAsync(
|
|
BillComplianceRequest request,
|
|
DistributorInformation distributorInfo,
|
|
Client clientInfo)
|
|
{
|
|
if (!ShouldApplySeasonalTariff(request))
|
|
{
|
|
return new ValidationResult
|
|
{
|
|
IsValid = true,
|
|
RuleName = RULE_NAME,
|
|
Message = string.Empty
|
|
};
|
|
}
|
|
|
|
var seasonalInfo = await _distributorRepository.GetSeasonalTariffInformationAsync(
|
|
request.DistributorName,
|
|
request.ConsumerGroup,
|
|
request.Month) ?? throw new InvalidOperationException("Seasonal tariff information not found");
|
|
|
|
var tariffInfo = await _distributorRepository.GetTariffInformationAsync(
|
|
request.DistributorName,
|
|
request.Month) ?? throw new InvalidOperationException("Tariff information not found");
|
|
|
|
var isValid = true;
|
|
var message = new StringBuilder();
|
|
|
|
var billingPeriod = DateOnly.ParseExact(request.Month, "MM/yyyy");
|
|
var isDrySeason = IsInDrySeason(billingPeriod, seasonalInfo);
|
|
var seasonalMultiplier = isDrySeason ?
|
|
seasonalInfo.DrySeasonMultiplier :
|
|
seasonalInfo.WetSeasonMultiplier;
|
|
|
|
if (request.AppliedSeason != (isDrySeason ? "DRY" : "WET"))
|
|
{
|
|
isValid = false;
|
|
message.AppendLine($"{SEASON_ERROR}. Expected: {(isDrySeason ? "DRY" : "WET")}, Found: {request.AppliedSeason}");
|
|
}
|
|
|
|
// Validate TUSD with seasonal adjustment
|
|
var expectedTUSD = request.ConsumptionAmount * tariffInfo.TUSDValue * seasonalMultiplier;
|
|
if (Math.Abs(request.SeasonalTUSDAmount - expectedTUSD) > TOLERANCE)
|
|
{
|
|
isValid = false;
|
|
message.AppendLine($"{TUSD_ERROR}. Expected: {expectedTUSD:F2}, Found: {request.SeasonalTUSDAmount:F2}");
|
|
}
|
|
|
|
// Validate TE with seasonal adjustment
|
|
var expectedTE = request.ConsumptionAmount * tariffInfo.TEValue * seasonalMultiplier;
|
|
if (Math.Abs(request.SeasonalTEAmount - expectedTE) > TOLERANCE)
|
|
{
|
|
isValid = false;
|
|
message.AppendLine($"{TE_ERROR}. Expected: {expectedTE:F2}, Found: {request.SeasonalTEAmount:F2}");
|
|
}
|
|
|
|
return new ValidationResult
|
|
{
|
|
IsValid = isValid,
|
|
RuleName = RULE_NAME,
|
|
Message = message.ToString().TrimEnd()
|
|
};
|
|
}
|
|
|
|
private static bool ShouldApplySeasonalTariff(BillComplianceRequest request)
|
|
{
|
|
// According to ANEEL, seasonal tariffs typically apply to irrigation/rural consumers
|
|
return request.ConsumerGroup.ToUpper() switch
|
|
{
|
|
"RURAL" => true,
|
|
"IRRIGATION" => true,
|
|
"AQUACULTURE" => true,
|
|
_ => false
|
|
};
|
|
}
|
|
|
|
private static bool IsInDrySeason(DateOnly date, SeasonalTariffInformation seasonalInfo)
|
|
{
|
|
// Handle year wrap-around case
|
|
if (seasonalInfo.DrySeasonStart.Month > seasonalInfo.DrySeasonEnd.Month)
|
|
{
|
|
return date.Month >= seasonalInfo.DrySeasonStart.Month ||
|
|
date.Month <= seasonalInfo.DrySeasonEnd.Month;
|
|
}
|
|
|
|
return date.Month >= seasonalInfo.DrySeasonStart.Month &&
|
|
date.Month <= seasonalInfo.DrySeasonEnd.Month;
|
|
}
|
|
}
|
|
} |