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 CreateTaxCodeAsync(CreateTaxCodeRequest request) { if (request.EffectiveFrom == default) { throw new BadRequestException("effectiveFrom is required"); } if (!Enum.TryParse(request.CalculationBasis, ignoreCase: true, out var calculationBasis)) { throw new BadRequestException( $"Invalid calculationBasis '{request.CalculationBasis}'. Must be one of: {string.Join(", ", Enum.GetNames())}."); } if (!Enum.TryParse(request.AppliesTo, ignoreCase: true, out var appliesTo)) { throw new BadRequestException( $"Invalid appliesTo '{request.AppliesTo}'. Must be one of: {string.Join(", ", Enum.GetNames())}."); } 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 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 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 ListTaxTypesAsync() { var taxTypes = await _taxRepository.GetAllTaxTypesAsync(); return ResponseHelpers.Ok("Tax types retrieved successfully", taxTypes); } public async Task ListTaxCodesAsync() { var taxCodes = await _taxRepository.GetAllTaxCodesAsync(); return ResponseHelpers.Ok("Tax codes retrieved successfully", taxCodes); } } }