using General_Ledger_Service.DTOs.Reports; using General_Ledger_Service.Infra; using General_Ledger_Service.Interfaces.Repositories; using General_Ledger_Service.Models; using Microsoft.EntityFrameworkCore; namespace General_Ledger_Service.Repositories { public class JournalEntryRepository(AppDBContext context) : IJournalEntryRepository { private readonly AppDBContext _context = context; public async Task GetJournalEntryByJournalNoAsync(string journalNo) => await _context.JournalEntries .Include(j => j.Lines) .FirstOrDefaultAsync(j => j.JournalNo == journalNo); public async Task GetJournalEntryBySourceReferenceAsync(string sourceModule, string reference) => await _context.JournalEntries .Include(j => j.Lines) .FirstOrDefaultAsync(j => j.SourceModule == sourceModule && j.Reference == reference); public async Task CountJournalEntriesForYearAsync(int year) => await _context.JournalEntries.CountAsync(j => j.JournalNo.StartsWith($"JV-{year}-")); public Task AddJournalEntryAsync(JournalEntry entry) => RepositoryHelpers.AddAsync(_context.JournalEntries, entry); public async Task AddJournalEntryLinesAsync(IEnumerable lines) => await _context.JournalEntryLines.AddRangeAsync(lines); public async Task> GetGeneralLedgerLinesAsync(long accountId, DateOnly periodStart, DateOnly periodEnd) { var treeRows = await _context.Accounts.AsNoTracking() .Select(a => new RecursiveTreeQueries.TreeRow { Id = a.AccountId, Code = a.AccountCode, Name = a.AccountName, ParentId = a.ParentAccountId }) .ToListAsync(); var accountIds = RecursiveTreeQueries.GetDescendantIds(treeRows, accountId).ToList(); var lines = _context.JournalEntryLines .Where(l => l.Journal.IsPosted && accountIds.Contains(l.AccountId) && l.Journal.EntryDate >= periodStart && l.Journal.EntryDate <= periodEnd); return await BuildGeneralLedgerLinesAsync(lines); } public async Task> GetGeneralLedgerLinesForAllAccountsAsync(DateOnly periodStart, DateOnly periodEnd) { var lines = _context.JournalEntryLines .Where(l => l.Journal.IsPosted && l.Account.IsPostable && l.Journal.EntryDate >= periodStart && l.Journal.EntryDate <= periodEnd); return await BuildGeneralLedgerLinesAsync(lines); } // Orders by account code, then entry date/journal (the running balance restarts for each // account and accumulates in that same date/journal order), then fetches the rows and // computes the running balance in memory. private static async Task> BuildGeneralLedgerLinesAsync(IQueryable lines) { var rows = await lines .OrderBy(l => l.Account.AccountCode) .ThenBy(l => l.Journal.EntryDate) .ThenBy(l => l.JournalId) .ThenBy(l => l.LineId) .Select(l => new { l.AccountId, l.Journal.EntryDate, l.Journal.JournalNo, l.Account.AccountCode, l.Account.AccountName, l.Journal.Narration, l.DebitAmount, l.CreditAmount }) .ToListAsync(); var runningBalances = new Dictionary(); var result = new List(rows.Count); foreach (var row in rows) { runningBalances.TryGetValue(row.AccountId, out var previous); var running = previous + row.DebitAmount - row.CreditAmount; runningBalances[row.AccountId] = running; result.Add(new GeneralLedgerLineDto { EntryDate = row.EntryDate, JournalNo = row.JournalNo, AccountCode = row.AccountCode, AccountName = row.AccountName, Narration = row.Narration, DebitAmount = row.DebitAmount, CreditAmount = row.CreditAmount, RunningBalance = running }); } return result; } public Task AddJournalCorrectionAsync(JournalCorrection correction) => RepositoryHelpers.AddAsync(_context.JournalCorrections, correction); } }