AccountingNext/Schaad.Accounting.Db/Repositories/AccountRepository.cs
Claudio Schaad 3a770af016 PR J: log repository save failures via ILogger
BaseRepository takes an ILogger via its constructor and logs the
failing file path and exception in Save's catch block before
rethrowing. Every concrete repository takes ILogger<ThisRepo> and
passes it up so failures are categorised by repository type.

Pulls Microsoft.Extensions.Logging.Abstractions into the Db project.
TransactionRepositoryTestShould and AccountRepositoryTestShould pass
NullLogger<T>.Instance.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-02 21:47:55 +02:00

116 lines
No EOL
3.7 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Logging;
using Schaad.Accounting.Interfaces;
using Schaad.Accounting.Models;
namespace Schaad.Accounting.Repositories
{
public class AccountRepository : BaseRepository, IAccountRepository
{
private readonly string ACCOUNTS = "Accounts.xml";
public AccountRepository(ISettingsService settingsService, RepositoryCache cache, ILogger<AccountRepository> logger)
: base(settingsService, cache, logger)
{
EnsureAccountsFile();
}
/// <summary>
/// Load accounts
/// </summary>
public List<Account> GetAccountList() => LoadList<Account>(ACCOUNTS);
/// <summary>
/// Save an account (insert/update)
/// </summary>
public void SaveAccount(Account account)
{
var accounts = GetAccountList();
var existingAccount = accounts.FirstOrDefault(a => a.Id == account.Id);
if (existingAccount == null)
{
existingAccount = new Account();
accounts.Add(existingAccount);
account.Id = Guid.NewGuid().ToString();
}
account.Copy(existingAccount);
Save(accounts, ACCOUNTS);
}
/// <summary>
/// Get account
/// </summary>
public Account GetAccount(string id)
{
var accounts = GetAccountList();
return accounts.FirstOrDefault(t => t.Id == id);
}
/// <summary>
/// Delete account
/// </summary>
public void DeleteAccount(string id)
{
var accounts = GetAccountList();
accounts.RemoveAll(t => t.Id == id);
Save(accounts, ACCOUNTS);
}
/// <summary>
/// Get account
/// </summary>
public Account GetAccountForBankAccountNumber(string bankAccountNumber)
{
var accounts = GetAccountList();
return accounts.SingleOrDefault(t => t.BankAccountNumber == bankAccountNumber);
}
public void SaveBankAccountBalance(string bankAccountNumber, decimal accountBalance)
{
var account = GetAccountForBankAccountNumber(bankAccountNumber);
account.LastBankBalance = accountBalance;
SaveAccount(account);
}
private void EnsureAccountsFile()
{
string filePath = Path.Combine(settingsService.GetDbPath(), ACCOUNTS);
if (File.Exists(filePath))
{
return;
}
var lastYearFile = Path.Combine(settingsService.GetLastYearDbPath(), ACCOUNTS);
if (File.Exists(lastYearFile))
{
File.Copy(lastYearFile, filePath);
}
// Year rollover: seed start balances from the previous year's closing values.
// Bank accounts start at the last known bank balance; cash accounts start at zero.
var accounts = GetAccountList();
var modified = false;
foreach (var account in accounts.Where(a => a.Class == ClassIds.Activa))
{
if (account.LastBankBalance > 0)
{
account.StartBalance = account.LastBankBalance;
modified = true;
}
else if (account.Number < 1010)
{
account.StartBalance = 0;
modified = true;
}
}
if (modified)
{
Save(accounts, ACCOUNTS);
}
}
}
}