101 lines
3.8 KiB
C#
101 lines
3.8 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 ICMSValidationRule(IDistributorRepository distributorRepository) : IValidationRule
|
|
{
|
|
private readonly IDistributorRepository _distributorRepository = distributorRepository;
|
|
private const string RULE_NAME = "ICMS Validation";
|
|
private const string AMOUNT_ERROR = "ICMS amount doesn't match the expected value";
|
|
private const string EXEMPT_ERROR = "ICMS charged to exempt consumer";
|
|
private const string BASE_ERROR = "ICMS base calculation is incorrect";
|
|
private const decimal TOLERANCE = 0.01m;
|
|
public int Priority => 6; // Run after public lighting validation
|
|
|
|
public async Task<ValidationResult> ValidateAsync(
|
|
BillComplianceRequest request,
|
|
DistributorInformation distributorInfo,
|
|
Client clientInfo)
|
|
{
|
|
var icmsInfo = await _distributorRepository.GetICMSInformationAsync(
|
|
request.DistributorName,
|
|
request.ConsumerGroup,
|
|
request.Month) ?? throw new InvalidOperationException("ICMS information not found");
|
|
|
|
var isValid = true;
|
|
var message = new StringBuilder();
|
|
|
|
// Check if consumer is exempt from ICMS
|
|
if (icmsInfo.ExemptGroups.Contains(request.ConsumerGroup.ToLower()))
|
|
{
|
|
if (request.ICMSAmount != 0)
|
|
{
|
|
isValid = false;
|
|
message.AppendLine(EXEMPT_ERROR);
|
|
}
|
|
return new ValidationResult
|
|
{
|
|
IsValid = isValid,
|
|
RuleName = RULE_NAME,
|
|
Message = message.ToString().TrimEnd()
|
|
};
|
|
}
|
|
|
|
// Get applicable rate
|
|
var rate = icmsInfo.GroupRates.TryGetValue(request.ConsumerGroup.ToLower(), out var groupRate)
|
|
? groupRate
|
|
: icmsInfo.BaseRate;
|
|
|
|
// Calculate base and expected ICMS
|
|
var baseAmount = CalculateICMSBase(request, icmsInfo.IncludesTaxInBase);
|
|
var expectedICMS = CalculateICMSAmount(baseAmount, rate, icmsInfo.IncludesTaxInBase);
|
|
|
|
if (Math.Abs(request.ICMSBase - baseAmount) > TOLERANCE)
|
|
{
|
|
isValid = false;
|
|
message.AppendLine($"{BASE_ERROR}. Expected: {baseAmount:F2}, Found: {request.ICMSBase:F2}");
|
|
}
|
|
|
|
if (Math.Abs(request.ICMSAmount - expectedICMS) > TOLERANCE)
|
|
{
|
|
isValid = false;
|
|
message.AppendLine($"{AMOUNT_ERROR}. Expected: {expectedICMS:F2}, Found: {request.ICMSAmount:F2}");
|
|
}
|
|
|
|
return new ValidationResult
|
|
{
|
|
IsValid = isValid,
|
|
RuleName = RULE_NAME,
|
|
Message = message.ToString().TrimEnd()
|
|
};
|
|
}
|
|
|
|
private static decimal CalculateICMSBase(BillComplianceRequest request, bool includesTaxInBase)
|
|
{
|
|
// Sum all components that are part of ICMS base
|
|
return request.TUSDAmount +
|
|
request.TEAmount +
|
|
request.PowerFactorAdjustment +
|
|
request.FlagAmount;
|
|
}
|
|
|
|
private static decimal CalculateICMSAmount(decimal baseAmount, decimal rate, bool includesTaxInBase)
|
|
{
|
|
rate /= 100m; // Convert percentage to decimal
|
|
|
|
if (!includesTaxInBase)
|
|
{
|
|
// Regular calculation ("por fora")
|
|
return baseAmount * rate;
|
|
}
|
|
|
|
// "Por dentro" calculation where ICMS is included in its own base
|
|
return baseAmount * rate / (1 - rate);
|
|
}
|
|
}
|
|
} |