152 lines
6.1 KiB
C#
152 lines
6.1 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 GroupSpecificRulesValidationRule(IDistributorRepository distributorRepository) : IValidationRule
|
|
{
|
|
private readonly IDistributorRepository _distributorRepository = distributorRepository;
|
|
private const string RULE_NAME = "Group-Specific Rules Validation";
|
|
|
|
// Art. 297-299 - Rural and Irrigation Requirements
|
|
private const decimal MIN_IRRIGATION_DISCOUNT = 0.10m;
|
|
private const decimal MAX_IRRIGATION_DISCOUNT = 0.90m;
|
|
private const string SCHEDULE_ERROR = "Irrigation schedule must be documented for irrigation consumers";
|
|
private const string SEASON_ERROR = "Invalid seasonal period configuration";
|
|
private const string DISCOUNT_ERROR = "Invalid irrigation discount";
|
|
private const string ACTIVITY_ERROR = "Activity-specific discount not properly documented";
|
|
|
|
public int Priority => 3; // Run after basic and consumption validation
|
|
|
|
public async Task<ValidationResult> ValidateAsync(
|
|
BillComplianceRequest request,
|
|
DistributorInformation distributorInfo,
|
|
Client clientInfo)
|
|
{
|
|
var groupRules = await _distributorRepository.GetGroupSpecificRulesInfoAsync(
|
|
request.ConsumerGroup,
|
|
request.Subgroup,
|
|
request.Month) ?? throw new InvalidOperationException("Group rules not found");
|
|
|
|
var errors = new List<string>();
|
|
|
|
ValidateRuralClassification(request, groupRules, errors);
|
|
ValidateIrrigationRules(request, groupRules, errors);
|
|
ValidateSeasonalRules(request, groupRules, errors);
|
|
ValidateActivityDiscounts(request, groupRules, errors);
|
|
|
|
return errors.Count == 0
|
|
? ValidationResult.Success(RULE_NAME)
|
|
: ValidationResult.Failure(RULE_NAME, errors);
|
|
}
|
|
|
|
private static void ValidateRuralClassification(
|
|
BillComplianceRequest request,
|
|
GroupSpecificRulesInfo rules,
|
|
List<string> errors)
|
|
{
|
|
if (rules.IsRural)
|
|
{
|
|
// Art. 297 - Rural consumer validation
|
|
if (!rules.SpecialConditions.Any())
|
|
{
|
|
errors.Add("Rural classification requires documented special conditions");
|
|
}
|
|
|
|
// Validate activity-specific requirements
|
|
foreach (var condition in rules.SpecialConditions)
|
|
{
|
|
if (!rules.ActivityDiscounts.ContainsKey(condition))
|
|
{
|
|
errors.Add($"Missing discount configuration for rural activity: {condition}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void ValidateIrrigationRules(
|
|
BillComplianceRequest request,
|
|
GroupSpecificRulesInfo rules,
|
|
List<string> errors)
|
|
{
|
|
if (rules.IsIrrigation)
|
|
{
|
|
// Art. 298 - Irrigation requirements
|
|
if (string.IsNullOrEmpty(rules.IrrigationSchedule))
|
|
{
|
|
errors.Add(SCHEDULE_ERROR);
|
|
}
|
|
|
|
if (!rules.IrrigationDiscount.HasValue)
|
|
{
|
|
errors.Add(DISCOUNT_ERROR);
|
|
}
|
|
else if (rules.IrrigationDiscount < MIN_IRRIGATION_DISCOUNT ||
|
|
rules.IrrigationDiscount > MAX_IRRIGATION_DISCOUNT)
|
|
{
|
|
errors.Add($"Irrigation discount ({rules.IrrigationDiscount:P}) outside allowed range " +
|
|
$"({MIN_IRRIGATION_DISCOUNT:P}-{MAX_IRRIGATION_DISCOUNT:P})");
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void ValidateSeasonalRules(
|
|
BillComplianceRequest request,
|
|
GroupSpecificRulesInfo rules,
|
|
List<string> errors)
|
|
{
|
|
// Art. 299 - Seasonal variations
|
|
if (!string.IsNullOrEmpty(rules.Season))
|
|
{
|
|
if (rules.SeasonStartDate == default || rules.SeasonEndDate == default)
|
|
{
|
|
errors.Add(SEASON_ERROR);
|
|
}
|
|
|
|
var currentDate = request.CurrentReadingDate;
|
|
if (currentDate >= rules.SeasonStartDate && currentDate <= rules.SeasonEndDate)
|
|
{
|
|
if (rules.SeasonalMultiplier <= 0)
|
|
{
|
|
errors.Add("Invalid seasonal multiplier");
|
|
}
|
|
|
|
// Validate if seasonal multiplier was correctly applied
|
|
var expectedAmount = request.ConsumptionAmount * rules.SeasonalMultiplier;
|
|
if (Math.Abs(expectedAmount - request.BillTotalBeforeTaxes) > 0.01m)
|
|
{
|
|
errors.Add("Seasonal multiplier not correctly applied to consumption amount");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void ValidateActivityDiscounts(
|
|
BillComplianceRequest request,
|
|
GroupSpecificRulesInfo rules,
|
|
List<string> errors)
|
|
{
|
|
foreach (var (activity, discount) in rules.ActivityDiscounts)
|
|
{
|
|
if (discount <= 0 || discount > 1)
|
|
{
|
|
errors.Add($"{ACTIVITY_ERROR}: Invalid discount percentage for {activity}");
|
|
}
|
|
|
|
// If this activity applies to the current bill
|
|
if (request.ApplicableActivities.Contains(activity))
|
|
{
|
|
var expectedDiscount = request.BillTotalBeforeTaxes * discount;
|
|
if (Math.Abs(expectedDiscount - request.ActivityDiscountAmount) > 0.01m)
|
|
{
|
|
errors.Add($"Activity discount for {activity} not correctly applied");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |