Distribution_GL Initialization
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
using System.Text.Json;
|
||||
using General_Ledger_Service.DTOs.Common;
|
||||
using General_Ledger_Service.DTOs.FiscalCalendar;
|
||||
using General_Ledger_Service.Enums;
|
||||
using General_Ledger_Service.Exceptions;
|
||||
using General_Ledger_Service.Helpers;
|
||||
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 FiscalCalendarService(
|
||||
IFiscalCalendarRepository fiscalCalendarRepository,
|
||||
IAuditLogRepository auditLogRepository,
|
||||
IUnitOfWork unitOfWork) : IFiscalCalendarService
|
||||
{
|
||||
private readonly IFiscalCalendarRepository _fiscalCalendarRepository = fiscalCalendarRepository;
|
||||
private readonly IAuditLogRepository _auditLogRepository = auditLogRepository;
|
||||
private readonly IUnitOfWork _unitOfWork = unitOfWork;
|
||||
|
||||
public async Task<ApiResponse> CreateFiscalYearAsync(CreateFiscalYearRequest request)
|
||||
{
|
||||
if (request.EndDate <= request.StartDate)
|
||||
{
|
||||
throw new BadRequestException("End date must be after start date");
|
||||
}
|
||||
|
||||
if (await _fiscalCalendarRepository.HasOverlappingFiscalYearRangeAsync(request.StartDate, request.EndDate))
|
||||
{
|
||||
throw new BadRequestException("Fiscal year date range overlaps with an existing fiscal year");
|
||||
}
|
||||
|
||||
var fiscalYear = new FiscalYear
|
||||
{
|
||||
YearName = request.YearName,
|
||||
StartDate = request.StartDate,
|
||||
EndDate = request.EndDate,
|
||||
IsClosed = false
|
||||
};
|
||||
|
||||
var generatedPeriods = new List<AccountingPeriod>();
|
||||
|
||||
await _unitOfWork.BeginAsync();
|
||||
try
|
||||
{
|
||||
await _fiscalCalendarRepository.AddFiscalYearAsync(fiscalYear);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
|
||||
if (request.AutoGeneratePeriods)
|
||||
{
|
||||
generatedPeriods = await BuildPeriodsAsync(fiscalYear, request.PeriodCount ?? 12);
|
||||
await _fiscalCalendarRepository.AddPeriodRangeAsync(generatedPeriods);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
}
|
||||
|
||||
await _auditLogRepository.AddAsync(new AuditLog
|
||||
{
|
||||
Category = AuditCategory.Ledger,
|
||||
EntityName = "FiscalYear",
|
||||
EntityId = fiscalYear.FiscalYearId.ToString(),
|
||||
Action = AuditAction.Create,
|
||||
Description = $"Fiscal year {fiscalYear.YearName} created",
|
||||
PerformedBy = null
|
||||
});
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
|
||||
await _unitOfWork.CommitAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
await _unitOfWork.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
|
||||
return ResponseHelpers.Created("Fiscal year created successfully", new CreateFiscalYearResponse
|
||||
{
|
||||
FiscalYear = fiscalYear,
|
||||
GeneratedPeriods = generatedPeriods
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<ApiResponse> GeneratePeriodsAsync(int fiscalYearId, GeneratePeriodsRequest request)
|
||||
{
|
||||
var fiscalYear = await _fiscalCalendarRepository.GetFiscalYearByIdAsync(fiscalYearId)
|
||||
?? throw new NotFoundException("Fiscal year not found");
|
||||
|
||||
var periods = await BuildPeriodsAsync(fiscalYear, request.PeriodCount ?? 12);
|
||||
|
||||
await _unitOfWork.BeginAsync();
|
||||
try
|
||||
{
|
||||
await _fiscalCalendarRepository.AddPeriodRangeAsync(periods);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
|
||||
await _auditLogRepository.AddAsync(new AuditLog
|
||||
{
|
||||
Category = AuditCategory.Ledger,
|
||||
EntityName = "AccountingPeriod",
|
||||
EntityId = fiscalYearId.ToString(),
|
||||
Action = AuditAction.Create,
|
||||
Description = $"{periods.Count} period(s) generated for fiscal year {fiscalYearId}",
|
||||
PerformedBy = null
|
||||
});
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
|
||||
await _unitOfWork.CommitAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
await _unitOfWork.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
|
||||
return ResponseHelpers.Created("Periods generated successfully", periods);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse> LockPeriodAsync(int periodId, Dictionary<string, JsonElement> payload)
|
||||
{
|
||||
var rawAction = DataExtractor.GetRequiredString(payload, "action");
|
||||
var reason = DataExtractor.GetRequiredString(payload, "reason");
|
||||
|
||||
if (!Enum.TryParse<PeriodLockAction>(rawAction, ignoreCase: true, out var action))
|
||||
{
|
||||
throw new BadRequestException(
|
||||
$"Invalid action '{rawAction}'. Must be one of: {string.Join(", ", Enum.GetNames<PeriodLockAction>())}.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(reason))
|
||||
{
|
||||
throw new BadRequestException("Reason is required");
|
||||
}
|
||||
|
||||
var period = await _fiscalCalendarRepository.GetPeriodByIdAsync(periodId)
|
||||
?? throw new NotFoundException("Period not found");
|
||||
|
||||
if (period.Status == PeriodStatus.Closed && action != PeriodLockAction.Unlock)
|
||||
{
|
||||
throw new BadRequestException("Period is closed; only Unlock is allowed to reopen it");
|
||||
}
|
||||
|
||||
(period.Status, period.LockGl) = action switch
|
||||
{
|
||||
PeriodLockAction.Lock => (PeriodStatus.Locked, true),
|
||||
PeriodLockAction.Unlock => (PeriodStatus.Open, false),
|
||||
PeriodLockAction.SoftClose => (PeriodStatus.SoftClose, false),
|
||||
PeriodLockAction.Close => (PeriodStatus.Closed, true),
|
||||
_ => throw new BadRequestException($"Invalid action '{rawAction}'.")
|
||||
};
|
||||
period.LockReason = reason;
|
||||
|
||||
var log = new PeriodLockLog
|
||||
{
|
||||
PeriodId = periodId,
|
||||
Action = action,
|
||||
ModuleLocked = "GL",
|
||||
Reason = reason
|
||||
};
|
||||
|
||||
var auditAction = action switch
|
||||
{
|
||||
PeriodLockAction.Lock => AuditAction.Lock,
|
||||
PeriodLockAction.Unlock => AuditAction.Unlock,
|
||||
PeriodLockAction.SoftClose => AuditAction.SoftClose,
|
||||
PeriodLockAction.Close => AuditAction.Close,
|
||||
_ => throw new BadRequestException($"Invalid action '{rawAction}'.")
|
||||
};
|
||||
|
||||
await _unitOfWork.BeginAsync();
|
||||
try
|
||||
{
|
||||
await _fiscalCalendarRepository.AddPeriodLockLogAsync(log);
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
|
||||
await _auditLogRepository.AddAsync(new AuditLog
|
||||
{
|
||||
Category = AuditCategory.Ledger,
|
||||
EntityName = "AccountingPeriod",
|
||||
EntityId = periodId.ToString(),
|
||||
Action = auditAction,
|
||||
Description = $"Period {periodId} action {action}",
|
||||
PerformedBy = null
|
||||
});
|
||||
await _unitOfWork.SaveChangesAsync();
|
||||
|
||||
await _unitOfWork.CommitAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
await _unitOfWork.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
|
||||
return ResponseHelpers.Ok("Period lock status updated successfully", new LockPeriodResponse
|
||||
{
|
||||
Period = period,
|
||||
LockLog = log
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<ApiResponse> GetPeriodByDateAsync(DateOnly date)
|
||||
{
|
||||
var period = await _fiscalCalendarRepository.GetPeriodByDateAsync(date)
|
||||
?? throw new NotFoundException("Period not found");
|
||||
|
||||
return ResponseHelpers.Ok("Period retrieved successfully", period);
|
||||
}
|
||||
|
||||
private async Task<List<AccountingPeriod>> BuildPeriodsAsync(FiscalYear fiscalYear, int periodCount)
|
||||
{
|
||||
if (periodCount <= 0)
|
||||
{
|
||||
throw new BadRequestException("Period count must be greater than zero");
|
||||
}
|
||||
|
||||
var existingPeriods = await _fiscalCalendarRepository.GetPeriodsByFiscalYearIdAsync(fiscalYear.FiscalYearId);
|
||||
var nextPeriodNumber = existingPeriods.Count > 0 ? existingPeriods[^1].PeriodNumber + 1 : 1;
|
||||
var cursor = existingPeriods.Count > 0 ? existingPeriods[^1].EndDate.AddDays(1) : fiscalYear.StartDate;
|
||||
|
||||
var periods = new List<AccountingPeriod>();
|
||||
for (var i = 0; i < periodCount; i++)
|
||||
{
|
||||
var periodStart = cursor;
|
||||
var periodEnd = periodStart.AddMonths(1).AddDays(-1);
|
||||
|
||||
periods.Add(new AccountingPeriod
|
||||
{
|
||||
FiscalYearId = fiscalYear.FiscalYearId,
|
||||
PeriodNumber = (short)(nextPeriodNumber + i),
|
||||
StartDate = periodStart,
|
||||
EndDate = periodEnd,
|
||||
Status = PeriodStatus.Open,
|
||||
LockGl = false
|
||||
});
|
||||
|
||||
cursor = periodEnd.AddDays(1);
|
||||
}
|
||||
|
||||
if (periods[^1].EndDate > fiscalYear.EndDate)
|
||||
{
|
||||
throw new BadRequestException("Requested period count exceeds the fiscal year's date range");
|
||||
}
|
||||
|
||||
return periods;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user