Distribution_GL Initialization

This commit is contained in:
2026-10-20 13:07:07 +05:30
commit 3f16e0cb44
194 changed files with 35659 additions and 0 deletions
+248
View File
@@ -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;
}
}
}