PR O: convert BalanceDataset and BalanceSheetDataset to records

Both were constructor-initialised value carriers with read-only
consumers. Positional record syntax replaces the class + property +
ctor boilerplate; ViewService callers updated to PascalCase named
args to match the record-generated parameter names.

Rest of item 26 descoped: XmlSerializer needs mutable public
setters (value-equality on mutable data is a footgun) and has no
source generator without changing file format. See
IMPROVEMENT_PLAN.md for the full rationale.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Claudio Schaad 2026-07-03 17:56:29 +02:00
parent 786fafefa5
commit e7ff097397
4 changed files with 28 additions and 61 deletions

View file

@ -110,3 +110,4 @@ Analysis and phased plan produced 2026-07-02. See conversation history for full
- **PR L** — Phase 2 item 10: drop the hand-rolled `Copy(target)` methods from `Account`, `BookingRule`, `BookingText`, `SubClass`, and `SplitPredefinition`; keep only a `Transaction.Clone()` for defensive-copy needs in `GetTransaction` / `ViewService.WithDisplaySign`. Every `SaveXxx` now uses `FindIndex` → in-place replace (or `Add` for new entries), moving the two "hidden" defaults (`Account.Currency = "CHF"` when unset, `Transaction.BookingDate = ValueDate` when unset) into the corresponding `SaveXxx` method where they belong. Fixes a pre-existing bug: `Transaction.Copy` never copied `RelatedParty`, so update-saves silently dropped it. Added `PreserveRelatedPartyWhenRoundTrippingTransaction` and `DefaultBookingDateToValueDateWhenBookingDateIsUnset` as regression tests. 42 tests total.
- **PR M** — Phase 2 item 18 (final slice): add `ILogger<ViewService>` and log a match-count summary from `MatchOpenBankTransactions` (`"Matched {Matched} of {Total} open bank transactions"`). Completes item 18 — statement imports (PR H), repository save failures (PR J), and match runs now all emit structured logs. Also removes a dead `ISettingsService` injection from `Home.razor.cs` (declared with `[Inject]` but never used anywhere in the file).
- **PR N** — Small polish pass covering three long-noted rough edges from the original analysis: `ClassIds` becomes a `static class` (was instantiable); `Home.razor.cs` loses the redundant `loaded` guard in `OnInitializedAsync` (Blazor already runs that lifecycle hook once per component instance); the hardcoded `mandator = "Claudio Schaad"` in `SettingsService` moves to a `DefaultMandator` field on `SettingsDataset`, plumbed through `appsettings.Development.json`.
- **PR O** — Phase 5 item 26 (partial): convert `BalanceDataset` and `BalanceSheetDataset` to positional records. Both were constructor-initialised value carriers with read-only usage. The rest of item 26 was descoped: the XML-serialised domain models (`Account`, `Transaction`, ...) need mutable public setters for `XmlSerializer`, and value-equality on mutable data is a footgun (hash changes on mutation); source-generated XML serialisers do not exist without switching file formats. `DataSerie` was already a record; `MessageDataset` has a real mutation method (`Add`) and stays a class; `AccountDataset` / `TransactionDataset` / `BookingRuleDataset` inherit from the mutable domain models and can't cleanly become records without a bigger refactor.

View file

@ -1,23 +1,10 @@
using System.Collections.Generic;
using System.Collections.Generic;
namespace Schaad.Accounting.Datasets.Reports
{
public class BalanceDataset
{
public List<AccountDataset> ActivaAccountList { get; set; }
public List<AccountDataset> PassivaAccountList { get; set; }
public decimal TotalActivaCHF { get; set; }
public decimal TotalPassivaCHF { get; set; }
public BalanceDataset(List<AccountDataset> activaAccountList, List<AccountDataset> passivaAccountList, decimal totalActivaCHF, decimal totalPassivaCHF)
{
ActivaAccountList = activaAccountList;
PassivaAccountList = passivaAccountList;
TotalActivaCHF = totalActivaCHF;
TotalPassivaCHF = totalPassivaCHF;
}
}
public record BalanceDataset(
List<AccountDataset> ActivaAccountList,
List<AccountDataset> PassivaAccountList,
decimal TotalActivaCHF,
decimal TotalPassivaCHF);
}

View file

@ -1,29 +1,12 @@
using System.Collections.Generic;
using System.Collections.Generic;
namespace Schaad.Accounting.Datasets.Reports
{
public class BalanceSheetDataset
{
public List<AccountDataset> ActivaAccountList { get; }
public List<AccountDataset> IncomeAccountList { get; }
public List<AccountDataset> ExpensesAccountList { get; }
public decimal ProfitCHF { get; }
public decimal LossCHF { get; }
public int Year { get; }
public BalanceSheetDataset(List<AccountDataset> activaAccountList, List<AccountDataset> incomeAccountList, List<AccountDataset> expensesAccountList, decimal profitCHF, decimal lossCHF, int year)
{
ActivaAccountList = activaAccountList;
IncomeAccountList = incomeAccountList;
ExpensesAccountList = expensesAccountList;
ProfitCHF = profitCHF;
LossCHF = lossCHF;
Year = year;
}
}
public record BalanceSheetDataset(
List<AccountDataset> ActivaAccountList,
List<AccountDataset> IncomeAccountList,
List<AccountDataset> ExpensesAccountList,
decimal ProfitCHF,
decimal LossCHF,
int Year);
}

View file

@ -96,14 +96,11 @@ namespace Schaad.Accounting.Services
public BalanceDataset GetBalanceView()
{
var accountList = GetAccountViewList();
var balanceView = new BalanceDataset(
activaAccountList: accountList.Where(m => m.Class == ClassIds.Activa).ToList(),
passivaAccountList: accountList.Where(m => m.Class == ClassIds.Passiva).ToList(),
totalActivaCHF: Math.Abs(accountList.Where(m => m.Class == ClassIds.Activa).Sum(m => m.BalanceCHF)),
totalPassivaCHF: Math.Abs(accountList.Where(m => m.Class == ClassIds.Passiva).Sum(m => m.BalanceCHF))
);
return balanceView;
return new BalanceDataset(
ActivaAccountList: accountList.Where(m => m.Class == ClassIds.Activa).ToList(),
PassivaAccountList: accountList.Where(m => m.Class == ClassIds.Passiva).ToList(),
TotalActivaCHF: Math.Abs(accountList.Where(m => m.Class == ClassIds.Activa).Sum(m => m.BalanceCHF)),
TotalPassivaCHF: Math.Abs(accountList.Where(m => m.Class == ClassIds.Passiva).Sum(m => m.BalanceCHF)));
}
public BalanceSheetDataset GetBalanceSheetView(int year)
@ -115,13 +112,12 @@ namespace Schaad.Accounting.Services
var loss = Math.Abs(accountList.Where(m => m.Class == ClassIds.Expenses).Sum(m => m.BalanceCHF));
var balanceView = new BalanceSheetDataset(
activaAccountList: accountList.Where(m => m.Class == ClassIds.Activa).ToList(),
incomeAccountList: accountList.Where(m => m.Class == ClassIds.Income).ToList(),
expensesAccountList: accountList.Where(m => m.Class == ClassIds.Expenses).ToList(),
profitCHF: profit,
lossCHF: loss,
year: year
);
ActivaAccountList: accountList.Where(m => m.Class == ClassIds.Activa).ToList(),
IncomeAccountList: accountList.Where(m => m.Class == ClassIds.Income).ToList(),
ExpensesAccountList: accountList.Where(m => m.Class == ClassIds.Expenses).ToList(),
ProfitCHF: profit,
LossCHF: loss,
Year: year);
return balanceView;
}