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:
parent
786fafefa5
commit
e7ff097397
4 changed files with 28 additions and 61 deletions
|
|
@ -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 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 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 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.
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,10 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Schaad.Accounting.Datasets.Reports
|
namespace Schaad.Accounting.Datasets.Reports
|
||||||
{
|
{
|
||||||
public class BalanceDataset
|
public record BalanceDataset(
|
||||||
{
|
List<AccountDataset> ActivaAccountList,
|
||||||
public List<AccountDataset> ActivaAccountList { get; set; }
|
List<AccountDataset> PassivaAccountList,
|
||||||
|
decimal TotalActivaCHF,
|
||||||
public List<AccountDataset> PassivaAccountList { get; set; }
|
decimal TotalPassivaCHF);
|
||||||
|
}
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,12 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Schaad.Accounting.Datasets.Reports
|
namespace Schaad.Accounting.Datasets.Reports
|
||||||
{
|
{
|
||||||
public class BalanceSheetDataset
|
public record BalanceSheetDataset(
|
||||||
{
|
List<AccountDataset> ActivaAccountList,
|
||||||
public List<AccountDataset> ActivaAccountList { get; }
|
List<AccountDataset> IncomeAccountList,
|
||||||
|
List<AccountDataset> ExpensesAccountList,
|
||||||
public List<AccountDataset> IncomeAccountList { get; }
|
decimal ProfitCHF,
|
||||||
|
decimal LossCHF,
|
||||||
public List<AccountDataset> ExpensesAccountList { get; }
|
int Year);
|
||||||
|
}
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -96,14 +96,11 @@ namespace Schaad.Accounting.Services
|
||||||
public BalanceDataset GetBalanceView()
|
public BalanceDataset GetBalanceView()
|
||||||
{
|
{
|
||||||
var accountList = GetAccountViewList();
|
var accountList = GetAccountViewList();
|
||||||
var balanceView = new BalanceDataset(
|
return new BalanceDataset(
|
||||||
activaAccountList: accountList.Where(m => m.Class == ClassIds.Activa).ToList(),
|
ActivaAccountList: accountList.Where(m => m.Class == ClassIds.Activa).ToList(),
|
||||||
passivaAccountList: accountList.Where(m => m.Class == ClassIds.Passiva).ToList(),
|
PassivaAccountList: accountList.Where(m => m.Class == ClassIds.Passiva).ToList(),
|
||||||
totalActivaCHF: Math.Abs(accountList.Where(m => m.Class == ClassIds.Activa).Sum(m => m.BalanceCHF)),
|
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))
|
TotalPassivaCHF: Math.Abs(accountList.Where(m => m.Class == ClassIds.Passiva).Sum(m => m.BalanceCHF)));
|
||||||
);
|
|
||||||
|
|
||||||
return balanceView;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BalanceSheetDataset GetBalanceSheetView(int year)
|
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 loss = Math.Abs(accountList.Where(m => m.Class == ClassIds.Expenses).Sum(m => m.BalanceCHF));
|
||||||
|
|
||||||
var balanceView = new BalanceSheetDataset(
|
var balanceView = new BalanceSheetDataset(
|
||||||
activaAccountList: accountList.Where(m => m.Class == ClassIds.Activa).ToList(),
|
ActivaAccountList: accountList.Where(m => m.Class == ClassIds.Activa).ToList(),
|
||||||
incomeAccountList: accountList.Where(m => m.Class == ClassIds.Income).ToList(),
|
IncomeAccountList: accountList.Where(m => m.Class == ClassIds.Income).ToList(),
|
||||||
expensesAccountList: accountList.Where(m => m.Class == ClassIds.Expenses).ToList(),
|
ExpensesAccountList: accountList.Where(m => m.Class == ClassIds.Expenses).ToList(),
|
||||||
profitCHF: profit,
|
ProfitCHF: profit,
|
||||||
lossCHF: loss,
|
LossCHF: loss,
|
||||||
year: year
|
Year: year);
|
||||||
);
|
|
||||||
|
|
||||||
return balanceView;
|
return balanceView;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue