Add Schaad.Accounting.Tests (xUnit + NSubstitute + Shouldly, net9.0)
wired into Accounting.sln with a direct Reference to
Schaad.Finance.Api.dll so it flows into the test binary.
Naming convention: file/class is <Subject>TestShould, each test method
is DoThisWhenThat. Reads as a sentence:
"ViewService test should sum activa and passiva totals separately when
getting balance view". All assertions use Shouldly (.ShouldBe,
.ShouldBeNull, .ShouldContain, ...) rather than xUnit Assert.*.
38 tests across seven files:
- ViewServiceTestShould: balance math (activa/passiva totals, per-
account balance from start balance + credits - debits, FX conversion
to CHF) and bank-transaction auto-matching (booking-rule text,
value-matching preference, same-accounts-last-month fallback,
open-transaction filter).
- TransactionRepositoryTestShould: FX round-trip against a temp XML
directory, unknown-id -> null, and the mutation-on-read regression
from PR A (a second Get on the same FX transaction used to divide
by FxRate again).
- FormattingTestShould: Swiss thousands separator, two-decimal
rounding, culture independence.
- RepositoryCacheTestShould: loader called once, per-key isolation,
invalidation forces reload, case-insensitive keys.
- AccountRepositoryTestShould: id assignment, in-place update,
currency defaulting, delete, bank-account lookup, bank-balance
update, and constructor re-run against an existing file.
- ChartServiceTestShould: honours settingsService.GetYear() without
mutating it (locks in PR E), account vs sub-class grouping
heuristic, skips empty accounts.
- FileServiceTestShould: CSV header + running balance for debit and
credit lines, ordering by BookingDate/ValueDate/Value, no in-place
value mutation (locks in PR A).
CLAUDE.md gains a `dotnet test` line. IMPROVEMENT_PLAN.md notes that
this landed as three PRs (G, H, I) and was squashed on request.
Also add `*.DotSettings.user` to .gitignore so Rider's per-user
solution settings don't get accidentally staged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eliminate O(N*M) linear scans in view assembly.
GetTransactionViewList / GetBookingRuleViewList: precompute
accountsById and use dictionary lookups instead of accountList.Single
per row. GetAccountViewList: group transactions by
TargetAccountId/OriginAccountId once; per-account balance is now O(k)
instead of a full scan. Fold GetBalanceInAccountCurrency and
GetCurrentBalanceInCHF into a single ComputeBalance helper so the
account-currency balance is computed once per account instead of
twice.
Defer item 19 (async I/O): after PR D each XML file is loaded at most
once per circuit, and this app is single-user local Blazor Server, so
async would be ~40 files of churn for negligible benefit.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Remove the settings-mutating year-hopping in GetAccountExpensesPerMonth
that discarded the user's header year selection and silently forced
the Spendings-over-time chart back to DateTime.Now.Year. The chart
now honours settingsService.GetYear(), skips accounts with no
transactions instead of returning null, and no longer depends on
IAccountRepository or ISubclassRepository.
Also reorder the plan: async becomes PR F, tests PR G.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Consolidate decimal.ToFormattedString into Schaad.Accounting.Formatting
- Rename EnsureFileExisits -> EnsureFileExists and
SplitPredefiniton* -> SplitPredefinition* (class, interface, files, DI reg)
- AccountRepository: single-save year-rollover start-balance seed
- Drop legacy .NET Standard packages redundant on net9.0 and the stale
Interfaces\Extensions\ folder entry
- Set culture via RequestLocalizationOptions middleware
- Plan: item 9 (unit of work) split off into a dedicated PR D
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- 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>