using Duempelkas.App.Services; using Duempelkas.App.Services.Models; using Duempelkas.Domain.Entities; using Duempelkas.Domain.Enums; using Duempelkas.Infrastructure.Persistence; using Microsoft.EntityFrameworkCore; namespace Duempelkas.Infrastructure.Services; public class AccountService : IAccountService { private readonly IDbContextFactory _dbFactory; private readonly DatabaseLocation? _location; public AccountService(IDbContextFactory dbFactory, DatabaseLocation? location = null) { _dbFactory = dbFactory; _location = location; } public Task> GetAllAccountsAsync() { return GetAllAccountsAsync(null); } public async Task> GetAllAccountsAsync(int? year) { using var lease = await DatabaseLease.AcquireAsync(_location); await using var db = await _dbFactory.CreateDbContextAsync(); var accounts = await db.Accounts .OrderBy(a => a.Name) .ToListAsync(); var entries = db.Entries.Where(e => !e.IsDeleted); if (year.HasValue) { var movementBeforeYearByAccountId = await entries .Where(e => e.Date.Year < year.Value) .GroupBy(e => e.AccountId) .ToDictionaryAsync( g => g.Key, g => g.Sum(e => e.Type == EntryType.Income ? e.Amount : -e.Amount)); var movementInYearByAccountId = await entries .Where(e => e.Date.Year == year.Value) .GroupBy(e => e.AccountId) .ToDictionaryAsync( g => g.Key, g => g.Sum(e => e.Type == EntryType.Income ? e.Amount : -e.Amount)); return accounts .Select(account => { var carryoverForYear = account.CarryoverBalance + movementBeforeYearByAccountId.GetValueOrDefault(account.Id); var totalBalance = carryoverForYear + movementInYearByAccountId.GetValueOrDefault(account.Id); return new AccountSummaryDto(account.Id, account.Name, carryoverForYear, totalBalance, account.CreatedUtc); }) .ToList(); } var movementAllByAccountId = await entries .GroupBy(e => e.AccountId) .ToDictionaryAsync( g => g.Key, g => g.Sum(e => e.Type == EntryType.Income ? e.Amount : -e.Amount)); return accounts .Select(account => { var totalBalance = account.CarryoverBalance + movementAllByAccountId.GetValueOrDefault(account.Id); return new AccountSummaryDto(account.Id, account.Name, account.CarryoverBalance, totalBalance, account.CreatedUtc); }) .ToList(); } public async Task GetAccountAsync(int accountId) { using var lease = await DatabaseLease.AcquireAsync(_location); await using var db = await _dbFactory.CreateDbContextAsync(); var account = await db.Accounts .Include(a => a.Entries) .FirstOrDefaultAsync(a => a.Id == accountId) ?? throw new InvalidOperationException($"Account {accountId} not found."); var totalIncome = account.Entries.Where(e => !e.IsDeleted && e.Type == EntryType.Income).Sum(e => e.Amount); var totalExpense = account.Entries.Where(e => !e.IsDeleted && e.Type == EntryType.Expense).Sum(e => e.Amount); var totalBalance = account.CarryoverBalance + totalIncome - totalExpense; return new AccountSummaryDto(account.Id, account.Name, account.CarryoverBalance, totalBalance, account.CreatedUtc); } public async Task CreateAccountAsync(string name) { using var lease = await DatabaseLease.AcquireAsync(_location); await using var db = await _dbFactory.CreateDbContextAsync(); var account = new Account { Name = name }; db.Accounts.Add(account); await db.SaveChangesAsync(); return new AccountSummaryDto(account.Id, account.Name, 0m, 0m, account.CreatedUtc); } public async Task RenameAccountAsync(int accountId, string newName) { using var lease = await DatabaseLease.AcquireAsync(_location); await using var db = await _dbFactory.CreateDbContextAsync(); var account = await db.Accounts.FindAsync(accountId) ?? throw new InvalidOperationException($"Account {accountId} not found."); account.Name = newName; await db.SaveChangesAsync(); } public async Task UpdateCarryoverAsync(int accountId, decimal carryoverBalance) { using var lease = await DatabaseLease.AcquireAsync(_location); await using var db = await _dbFactory.CreateDbContextAsync(); var account = await db.Accounts.FindAsync(accountId) ?? throw new InvalidOperationException($"Account {accountId} not found."); account.CarryoverBalance = carryoverBalance; await db.SaveChangesAsync(); } public async Task UpdateCarryoverAsync(int accountId, decimal carryoverBalance, int? year) { if (!year.HasValue) { await UpdateCarryoverAsync(accountId, carryoverBalance); return; } using var lease = await DatabaseLease.AcquireAsync(_location); await using var db = await _dbFactory.CreateDbContextAsync(); var account = await db.Accounts.FindAsync(accountId) ?? throw new InvalidOperationException($"Account {accountId} not found."); var movementBeforeYear = await db.Entries .Where(e => e.AccountId == accountId && !e.IsDeleted && e.Date.Year < year.Value) .SumAsync(e => e.Type == EntryType.Income ? e.Amount : -e.Amount); account.CarryoverBalance = carryoverBalance - movementBeforeYear; await db.SaveChangesAsync(); } public async Task DeleteAccountAsync(int accountId) { using var lease = await DatabaseLease.AcquireAsync(_location); await using var db = await _dbFactory.CreateDbContextAsync(); var account = await db.Accounts .Include(a => a.Entries) .FirstOrDefaultAsync(a => a.Id == accountId) ?? throw new InvalidOperationException($"Account {accountId} not found."); var entryIds = account.Entries.Select(e => e.Id).ToList(); var transferLinks = await db.TransferLinks .Where(tl => entryIds.Contains(tl.SourceEntryId) || entryIds.Contains(tl.TargetEntryId)) .ToListAsync(); foreach (var link in transferLinks) { var otherEntryId = entryIds.Contains(link.SourceEntryId) ? link.TargetEntryId : link.SourceEntryId; var otherEntry = await db.Entries.FindAsync(otherEntryId); if (otherEntry != null) db.Entries.Remove(otherEntry); } db.TransferLinks.RemoveRange(transferLinks); db.Accounts.Remove(account); await db.SaveChangesAsync(); } }