AccountingNext/Schaad.Accounting.Db/Repositories/TransactionRepository.cs
Claudio Schaad 9f11f6490b PR A: correctness fixes and improvement plan
- Add IMPROVEMENT_PLAN.md with phased plan for follow-up work
- Consolidate DI registrations into a single AddAccounting() extension;
  drop the duplicate service registrations and the PdfParsingService
  self-registration
- TrySetYear: capture this.year before overwriting so rollback actually
  restores the previous value
- DummyFxService: check toCurrency (was checking fromCurrency twice)
- TransactionRepository.GetTransaction: return a copy instead of mutating
  the loaded entity, and guard against unknown ids
- ViewService.GetTransactionViewList(accountId): flip the sign on a copy
  rather than mutating the entity returned by the repository
- FileService.GetTransactionListCsv: same treatment; use a local
  signedValue instead of mutating trx.Value
- ProfitLossReport: use ClassIds.Income/Expenses instead of magic 3/4
- CLAUDE.md: correct the ClassIds documentation (1/2/3/4, not
  1000/2000/3000/4000)

SettingsService lifetime is intentionally left as Singleton for now;
making it Scoped requires persisting year/mandator selection across
page reloads first (tracked in the plan).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-02 20:37:03 +02:00

100 lines
No EOL
3.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Schaad.Accounting.Interfaces;
using Schaad.Accounting.Models;
namespace Schaad.Accounting.Repositories
{
public class TransactionRepository : BaseRepository, ITransactionRepository
{
private readonly IAccountRepository accountRepository;
private readonly string TRANSACTIONS = "Transactions.xml";
public TransactionRepository(ISettingsService settingsService, IAccountRepository accountRepository) : base(settingsService)
{
this.accountRepository = accountRepository;
}
/// <summary>
/// Get transaction list
/// </summary>
public List<Transaction> GetTransactionList()
{
var transactionList = Load<List<Transaction>>(TRANSACTIONS);
return transactionList ?? new List<Transaction>();
}
/// <summary>
/// Save a transaction (insert/update)
/// </summary>
public void SaveTransaction(Transaction transaction)
{
transaction.Value = Math.Abs(transaction.Value);
var originIsFx = accountRepository.GetAccount(transaction.OriginAccountId).IsFxAccount;
var targetIsFx = accountRepository.GetAccount(transaction.TargetAccountId).IsFxAccount;
var isFxAccount = originIsFx || targetIsFx;
// user enters amount in the foreign account's currency -> convert to CHF for storage
if (isFxAccount && transaction.FxRate != 0)
{
transaction.Value = transaction.Value * transaction.FxRate.Value;
}
if (!isFxAccount)
{
transaction.FxRate = null;
}
var transactionList = GetTransactionList();
var existingTransaction = transactionList.FirstOrDefault(a => a.Id == transaction.Id);
if (existingTransaction == null)
{
existingTransaction = new Transaction();
transactionList.Add(existingTransaction);
transaction.Id = Guid.NewGuid().ToString();
}
transaction.Copy(existingTransaction);
Save(transactionList, TRANSACTIONS);
}
/// <summary>
/// Get transaction
/// </summary>
public Transaction GetTransaction(string id)
{
var transactions = GetTransactionList();
var stored = transactions.FirstOrDefault(t => t.Id == id);
if (stored == null)
{
return null;
}
var result = new Transaction();
stored.Copy(result);
// value is stored in CHF -> convert back to foreign currency for display/editing
var isFxAccount = accountRepository.GetAccount(result.OriginAccountId).IsFxAccount
|| accountRepository.GetAccount(result.TargetAccountId).IsFxAccount;
if (isFxAccount && result.FxRate != 0)
{
result.Value = result.Value / result.FxRate.Value;
}
return result;
}
/// <summary>
/// Delete transaction
/// </summary>
public void DeleteTransaction(string id)
{
var transactions = GetTransactionList();
transactions.RemoveAll(t => t.Id == id);
Save(transactions, TRANSACTIONS);
}
}
}