172 lines
6.8 KiB
C#
172 lines
6.8 KiB
C#
using General_Ledger_Service.DTOs.Common;
|
|
using General_Ledger_Service.DTOs.JournalEntries;
|
|
using General_Ledger_Service.DTOs.RecurringTemplates;
|
|
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 RecurringTemplateService(
|
|
IRecurringJournalTemplateRepository templateRepository,
|
|
IAccountRepository accountRepository,
|
|
IFiscalCalendarRepository fiscalCalendarRepository,
|
|
IJournalEntryService journalEntryService,
|
|
IAuditLogRepository auditLogRepository,
|
|
IUnitOfWork unitOfWork) : IRecurringTemplateService
|
|
{
|
|
private readonly IRecurringJournalTemplateRepository _templateRepository = templateRepository;
|
|
private readonly IAccountRepository _accountRepository = accountRepository;
|
|
private readonly IFiscalCalendarRepository _fiscalCalendarRepository = fiscalCalendarRepository;
|
|
private readonly IJournalEntryService _journalEntryService = journalEntryService;
|
|
private readonly IAuditLogRepository _auditLogRepository = auditLogRepository;
|
|
private readonly IUnitOfWork _unitOfWork = unitOfWork;
|
|
|
|
public async Task<ApiResponse> CreateAsync(CreateRecurringTemplateRequest request)
|
|
{
|
|
if (!Enum.TryParse<RecurrencePattern>(request.RecurrencePattern, ignoreCase: true, out var recurrencePattern))
|
|
{
|
|
throw new BadRequestException(
|
|
$"Invalid recurrencePattern '{request.RecurrencePattern}'. Must be one of: {string.Join(", ", Enum.GetNames<RecurrencePattern>())}.");
|
|
}
|
|
|
|
if (request.Lines.Count == 0)
|
|
{
|
|
throw new BadRequestException("Recurring template must have at least one line");
|
|
}
|
|
|
|
var totalDebit = request.Lines.Sum(l => l.DebitAmount);
|
|
var totalCredit = request.Lines.Sum(l => l.CreditAmount);
|
|
if (totalDebit != totalCredit)
|
|
{
|
|
throw new BadRequestException("Recurring template lines do not balance");
|
|
}
|
|
|
|
var lines = new List<RecurringJournalTemplateLine>();
|
|
foreach (var lineRequest in request.Lines)
|
|
{
|
|
var account = await _accountRepository.GetByCodeAsync(lineRequest.AccountCode)
|
|
?? throw new NotFoundException($"Account {lineRequest.AccountCode} not found");
|
|
|
|
if (!account.IsPostable)
|
|
{
|
|
throw new BadRequestException($"Account {account.AccountCode} is not postable");
|
|
}
|
|
|
|
lines.Add(new RecurringJournalTemplateLine
|
|
{
|
|
AccountId = account.AccountId,
|
|
DebitAmount = lineRequest.DebitAmount,
|
|
CreditAmount = lineRequest.CreditAmount
|
|
});
|
|
}
|
|
|
|
var template = new RecurringJournalTemplate
|
|
{
|
|
TemplateName = request.TemplateName,
|
|
RecurrencePattern = recurrencePattern,
|
|
NextRunDate = request.NextRunDate,
|
|
EndDate = request.EndDate,
|
|
IsActive = true,
|
|
Lines = lines
|
|
};
|
|
|
|
await _unitOfWork.BeginAsync();
|
|
try
|
|
{
|
|
await _templateRepository.AddAsync(template);
|
|
await _unitOfWork.SaveChangesAsync();
|
|
|
|
await _auditLogRepository.AddAsync(new AuditLog
|
|
{
|
|
Category = AuditCategory.Administrative,
|
|
EntityName = "RecurringJournalTemplate",
|
|
EntityId = template.TemplateId.ToString(),
|
|
Action = AuditAction.Create,
|
|
Description = $"Recurring template {template.TemplateName} created",
|
|
PerformedBy = null
|
|
});
|
|
await _unitOfWork.SaveChangesAsync();
|
|
|
|
await _unitOfWork.CommitAsync();
|
|
}
|
|
catch
|
|
{
|
|
await _unitOfWork.RollbackAsync();
|
|
throw;
|
|
}
|
|
|
|
return ResponseHelpers.Created("Recurring template created successfully", template);
|
|
}
|
|
|
|
public async Task<ApiResponse> GenerateAsync(long templateId)
|
|
{
|
|
var template = await _templateRepository.GetByIdAsync(templateId)
|
|
?? throw new NotFoundException("Recurring template not found");
|
|
|
|
if (!template.IsActive)
|
|
{
|
|
throw new BadRequestException("Recurring template is not active");
|
|
}
|
|
|
|
var today = DateOnly.FromDateTime(DateTime.UtcNow);
|
|
if (template.NextRunDate > today)
|
|
{
|
|
throw new BadRequestException("Recurring template is not yet due to run");
|
|
}
|
|
|
|
var period = await _fiscalCalendarRepository.GetPeriodByDateAsync(today)
|
|
?? throw new NotFoundException("No accounting period covers today's date");
|
|
|
|
var lines = new List<JournalEntryLineRequest>();
|
|
foreach (var line in template.Lines)
|
|
{
|
|
var account = await _accountRepository.GetByIdAsync(line.AccountId);
|
|
lines.Add(new JournalEntryLineRequest
|
|
{
|
|
AccountCode = account!.AccountCode,
|
|
DebitAmount = line.DebitAmount,
|
|
CreditAmount = line.CreditAmount
|
|
});
|
|
}
|
|
|
|
var journalRequest = new CreateJournalEntryRequest
|
|
{
|
|
PeriodId = period.PeriodId,
|
|
EntryDate = today,
|
|
JournalType = "RECURRING",
|
|
Reference = template.TemplateName,
|
|
Narration = $"Generated from recurring template {template.TemplateName}",
|
|
Lines = lines
|
|
};
|
|
|
|
var journalResponse = await _journalEntryService.CreateAsync(journalRequest);
|
|
|
|
template.NextRunDate = template.RecurrencePattern switch
|
|
{
|
|
RecurrencePattern.Monthly => template.NextRunDate.AddMonths(1),
|
|
RecurrencePattern.Quarterly => template.NextRunDate.AddMonths(3),
|
|
RecurrencePattern.Annual => template.NextRunDate.AddYears(1),
|
|
_ => template.NextRunDate
|
|
};
|
|
|
|
await _unitOfWork.BeginAsync();
|
|
try
|
|
{
|
|
await _unitOfWork.SaveChangesAsync();
|
|
await _unitOfWork.CommitAsync();
|
|
}
|
|
catch
|
|
{
|
|
await _unitOfWork.RollbackAsync();
|
|
throw;
|
|
}
|
|
|
|
return ResponseHelpers.Created("Recurring journal entry generated successfully", journalResponse.Data);
|
|
}
|
|
}
|
|
}
|