From e7ff0973972425ad261f0210c53928799f018c8a Mon Sep 17 00:00:00 2001 From: Claudio Schaad Date: Fri, 3 Jul 2026 17:56:29 +0200 Subject: [PATCH] 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 --- IMPROVEMENT_PLAN.md | 1 + .../Datasets/Reports/BalanceDataset.cs | 27 ++++---------- .../Datasets/Reports/BalanceSheetDataset.cs | 35 +++++-------------- Schaad.Accounting.Services/ViewService.cs | 26 ++++++-------- 4 files changed, 28 insertions(+), 61 deletions(-) diff --git a/IMPROVEMENT_PLAN.md b/IMPROVEMENT_PLAN.md index 7a02c89..1115fa2 100644 --- a/IMPROVEMENT_PLAN.md +++ b/IMPROVEMENT_PLAN.md @@ -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` 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. diff --git a/Schaad.Accounting.Common/Datasets/Reports/BalanceDataset.cs b/Schaad.Accounting.Common/Datasets/Reports/BalanceDataset.cs index d18d265..3af5d1e 100644 --- a/Schaad.Accounting.Common/Datasets/Reports/BalanceDataset.cs +++ b/Schaad.Accounting.Common/Datasets/Reports/BalanceDataset.cs @@ -1,23 +1,10 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Schaad.Accounting.Datasets.Reports { - public class BalanceDataset - { - public List ActivaAccountList { get; set; } - - public List PassivaAccountList { get; set; } - - public decimal TotalActivaCHF { get; set; } - - public decimal TotalPassivaCHF { get; set; } - - public BalanceDataset(List activaAccountList, List passivaAccountList, decimal totalActivaCHF, decimal totalPassivaCHF) - { - ActivaAccountList = activaAccountList; - PassivaAccountList = passivaAccountList; - TotalActivaCHF = totalActivaCHF; - TotalPassivaCHF = totalPassivaCHF; - } - } -} \ No newline at end of file + public record BalanceDataset( + List ActivaAccountList, + List PassivaAccountList, + decimal TotalActivaCHF, + decimal TotalPassivaCHF); +} diff --git a/Schaad.Accounting.Common/Datasets/Reports/BalanceSheetDataset.cs b/Schaad.Accounting.Common/Datasets/Reports/BalanceSheetDataset.cs index b4945cf..e7bf8e6 100644 --- a/Schaad.Accounting.Common/Datasets/Reports/BalanceSheetDataset.cs +++ b/Schaad.Accounting.Common/Datasets/Reports/BalanceSheetDataset.cs @@ -1,29 +1,12 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Schaad.Accounting.Datasets.Reports { - public class BalanceSheetDataset - { - public List ActivaAccountList { get; } - - public List IncomeAccountList { get; } - - public List ExpensesAccountList { get; } - - public decimal ProfitCHF { get; } - - public decimal LossCHF { get; } - - public int Year { get; } - - public BalanceSheetDataset(List activaAccountList, List incomeAccountList, List expensesAccountList, decimal profitCHF, decimal lossCHF, int year) - { - ActivaAccountList = activaAccountList; - IncomeAccountList = incomeAccountList; - ExpensesAccountList = expensesAccountList; - ProfitCHF = profitCHF; - LossCHF = lossCHF; - Year = year; - } - } -} \ No newline at end of file + public record BalanceSheetDataset( + List ActivaAccountList, + List IncomeAccountList, + List ExpensesAccountList, + decimal ProfitCHF, + decimal LossCHF, + int Year); +} diff --git a/Schaad.Accounting.Services/ViewService.cs b/Schaad.Accounting.Services/ViewService.cs index 2bff47a..7b41005 100644 --- a/Schaad.Accounting.Services/ViewService.cs +++ b/Schaad.Accounting.Services/ViewService.cs @@ -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; }