Distribution_GL Initialization
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
using General_Ledger_Service.DTOs.Common;
|
||||
using General_Ledger_Service.DTOs.Tax;
|
||||
using General_Ledger_Service.Enums;
|
||||
using General_Ledger_Service.Exceptions;
|
||||
using General_Ledger_Service.Infra.UoW;
|
||||
using General_Ledger_Service.Interfaces.Repositories;
|
||||
using General_Ledger_Service.Interfaces.Services;
|
||||
using General_Ledger_Service.Models;
|
||||
|
||||
namespace General_Ledger_Service.Services
|
||||
{
|
||||
public class TaxService(
|
||||
ITaxRepository taxRepository,
|
||||
IAccountRepository accountRepository,
|
||||
IAuditLogRepository auditLogRepository,
|
||||
IUnitOfWork unitOfWork) : ITaxService
|
||||
{
|
||||
private readonly ITaxRepository _taxRepository = taxRepository;
|
||||
private readonly IAccountRepository _accountRepository = accountRepository;
|
||||
private readonly IAuditLogRepository _auditLogRepository = auditLogRepository;
|
||||
private readonly IUnitOfWork _unitOfWork = unitOfWork;
|
||||
|
||||
public async Task<ApiResponse> CreateTaxCodeAsync(CreateTaxCodeRequest request)
|
||||
{
|
||||
if (request.EffectiveFrom == default)
|
||||
{
|
||||
throw new BadRequestException("effectiveFrom is required");
|
||||
}
|
||||
|
||||
if (!Enum.TryParse<TaxCalculationBasis>(request.CalculationBasis, ignoreCase: true, out var calculationBasis))
|
||||
{
|
||||
throw new BadRequestException(
|
||||
$"Invalid calculationBasis '{request.CalculationBasis}'. Must be one of: {string.Join(", ", Enum.GetNames<TaxCalculationBasis>())}.");
|
||||
}
|
||||
|
||||
if (!Enum.TryParse<TaxAppliesTo>(request.AppliesTo, ignoreCase: true, out var appliesTo))
|
||||
{
|
||||
throw new BadRequestException(
|
||||
$"Invalid appliesTo '{request.AppliesTo}'. Must be one of: {string.Join(", ", Enum.GetNames<TaxAppliesTo>())}.");
|
||||
}
|
||||
|
||||
var taxType = await _taxRepository.GetTaxTypeByCodeAsync(request.TaxTypeCode)
|
||||
?? throw new NotFoundException("Tax type not found");
|
||||
|
||||
long? outputTaxAccountId = null;
|
||||
if (!string.IsNullOrWhiteSpace(request.OutputTaxAccountCode))
|
||||
{
|
||||
var outputAccount = await _accountRepository.GetByCodeAsync(request.OutputTaxAccountCode)
|
||||
?? throw new NotFoundException("Output tax account not found");
|
||||
outputTaxAccountId = outputAccount.AccountId;
|
||||
}
|
||||
|
||||
long? inputTaxAccountId = null;
|
||||
if (!string.IsNullOrWhiteSpace(request.InputTaxAccountCode))
|
||||
{
|
||||
var inputAccount = await _accountRepository.GetByCodeAsync(request.InputTaxAccountCode)
|
||||
?? throw new NotFoundException("Input tax account not found");
|
||||
inputTaxAccountId = inputAccount.AccountId;
|
||||
}
|
||||
|
||||
var code = await GenerateTaxCodeAsync(taxType);
|
||||
|
||||
var taxCode = new TaxCode
|
||||
{
|
||||
TaxTypeId = taxType.TaxTypeId,
|
||||
Code = code,
|
||||
Name = request.Name,
|
||||
RatePercent = request.RatePercent,
|
||||
CalculationBasis = calculationBasis,
|
||||
AppliesTo = appliesTo,
|
||||
OutputTaxAccountId = outputTaxAccountId,
|
||||
InputTaxAccountId = inputTaxAccountId,
|
||||
EffectiveFrom = request.EffectiveFrom,
|
||||
EffectiveTo = request.EffectiveTo,
|
||||
IsActive = true
|
||||
};
|
||||
|
||||
await _unitOfWork.BeginAsync();
|
||||
try
|
||||
{
|
||||
await _taxRepository.AddTaxCodeAsync(taxCode);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
|
||||
await _auditLogRepository.AddAsync(new AuditLog
|
||||
{
|
||||
Category = AuditCategory.Ledger,
|
||||
EntityName = "TaxCode",
|
||||
EntityId = taxCode.TaxCodeId.ToString(),
|
||||
Action = AuditAction.Create,
|
||||
Description = $"Tax code {taxCode.Code} created",
|
||||
PerformedBy = null
|
||||
});
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
|
||||
await _unitOfWork.CommitAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
await _unitOfWork.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
|
||||
return ResponseHelpers.Created("Tax code created successfully", taxCode);
|
||||
}
|
||||
|
||||
private async Task<string> GenerateTaxCodeAsync(TaxType taxType)
|
||||
{
|
||||
// Probes candidates directly rather than trusting a count (same fix as
|
||||
// AccountService.GenerateAccountCodeAsync) so a pre-existing, manually-coded tax code
|
||||
// can never collide with a generated one.
|
||||
for (var seq = 1; seq <= 999; seq++)
|
||||
{
|
||||
var code = $"{taxType.Code}-{seq:D3}";
|
||||
if (await _taxRepository.GetByCodeAsync(code) is null)
|
||||
{
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
throw new BadRequestException("Maximum number of tax codes for this tax type has been reached.");
|
||||
}
|
||||
|
||||
public async Task<ApiResponse> CalculateTaxAsync(CalculateTaxRequest request)
|
||||
{
|
||||
var taxCode = await _taxRepository.GetEffectiveTaxCodeByCodeAsync(request.TaxCode, request.TxnDate)
|
||||
?? throw new NotFoundException("Tax code not found or not effective for the given date");
|
||||
|
||||
var taxAmount = taxCode.CalculationBasis switch
|
||||
{
|
||||
TaxCalculationBasis.Exclusive => request.Amount * (taxCode.RatePercent / 100),
|
||||
TaxCalculationBasis.Inclusive => request.Amount - (request.Amount / (1 + (taxCode.RatePercent / 100))),
|
||||
_ => throw new BadRequestException($"Unsupported calculationBasis '{taxCode.CalculationBasis}'.")
|
||||
};
|
||||
|
||||
return ResponseHelpers.Ok("Tax calculated successfully", new CalculateTaxResponse
|
||||
{
|
||||
TaxCodeId = taxCode.TaxCodeId,
|
||||
Code = taxCode.Code,
|
||||
RatePercent = taxCode.RatePercent,
|
||||
LineTotal = request.Amount,
|
||||
TaxAmount = taxAmount
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<ApiResponse> ListTaxTypesAsync()
|
||||
{
|
||||
var taxTypes = await _taxRepository.GetAllTaxTypesAsync();
|
||||
return ResponseHelpers.Ok("Tax types retrieved successfully", taxTypes);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse> ListTaxCodesAsync()
|
||||
{
|
||||
var taxCodes = await _taxRepository.GetAllTaxCodesAsync();
|
||||
return ResponseHelpers.Ok("Tax codes retrieved successfully", taxCodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user