feat(accounts): preview and confirm cross-year balance corrections (U12)

This commit is contained in:
2026-11-02 19:28:30 +01:00
parent 4d0d5c8d88
commit 45fe327840
13 changed files with 158 additions and 57 deletions
@@ -93,15 +93,19 @@ public class AccountService : IAccountService
return new AccountSummaryDto(account.Id, account.Name, account.CarryoverBalance, totalBalance, account.CreatedUtc);
}
public async Task<AccountSummaryDto> CreateAccountAsync(string name)
public Task<AccountSummaryDto> CreateAccountAsync(string name) => CreateAccountAsync(name, 0m);
public async Task<AccountSummaryDto> CreateAccountAsync(string name, decimal initialBalance)
{
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Bitte einen Kontonamen eingeben.");
if (decimal.Round(initialBalance, 2) != initialBalance) throw new ArgumentException("Ungültiger Anfangsbestand.");
using var lease = await DatabaseLease.AcquireAsync(_location);
await using var db = await _dbFactory.CreateDbContextAsync();
var account = new Account { Name = name };
var account = new Account { Name = name.Trim(), CarryoverBalance = initialBalance };
db.Accounts.Add(account);
await db.SaveChangesAsync();
return new AccountSummaryDto(account.Id, account.Name, 0m, 0m, account.CreatedUtc);
return new AccountSummaryDto(account.Id, account.Name, initialBalance, initialBalance, account.CreatedUtc);
}
public async Task RenameAccountAsync(int accountId, string newName)
@@ -148,6 +152,22 @@ public class AccountService : IAccountService
await db.SaveChangesAsync();
}
public async Task CorrectCarryoverAsync(int accountId, decimal value, int? year, decimal expectedCarryover, decimal expectedInitial)
{
if (decimal.Round(value, 2) != value) throw new ArgumentException("Ungültiger Bestand.");
using var lease = await DatabaseLease.AcquireAsync(_location);
await using var db = await _dbFactory.CreateDbContextAsync();
await using var transaction = await db.Database.BeginTransactionAsync();
var account = await db.Accounts.FindAsync(accountId) ?? throw new InvalidOperationException("Konto nicht mehr vorhanden.");
var movement = year.HasValue ? 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) : 0m;
if (account.CarryoverBalance != expectedInitial || account.CarryoverBalance + movement != expectedCarryover)
throw new InvalidOperationException("Der Bestand wurde inzwischen geändert. Bitte abbrechen und die Korrektur mit aktuellen Werten erneut öffnen.");
account.CarryoverBalance = value - movement;
await db.SaveChangesAsync();
await transaction.CommitAsync();
}
public async Task DeleteAccountAsync(int accountId)
{
using var lease = await DatabaseLease.AcquireAsync(_location);