New IFxConverter.ConvertToChf(amount, fromCurrency) and FxConverter
wrap the vendor IFxService and the FixerIo API key so callers stop
threading the key through every conversion. ViewService drops
IFxService and ISettingsService from its constructor and takes
IFxConverter instead; GetAccountViewList and GetBalanceSheetView no
longer read settingsService.GetSettings() per method.
FxConverterTestShould locks in the target-currency + API-key routing.
40 tests total, all passing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
BaseRepository takes an ILogger via its constructor and logs the
failing file path and exception in Save's catch block before
rethrowing. Every concrete repository takes ILogger<ThisRepo> and
passes it up so failures are categorised by repository type.
Pulls Microsoft.Extensions.Logging.Abstractions into the Db project.
TransactionRepositoryTestShould and AccountRepositoryTestShould pass
NullLogger<T>.Instance.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
IViewService, IFileService, and IChartService move from
Schaad.Accounting.Common/Interfaces/ to
Schaad.Accounting.Services/Interfaces/. Namespaces are unchanged, so
no consumer needs a using update. Common's vendor <Reference
Include="Schaad.Finance.Api"> can be dropped, matching Common's
documented role as the shared models/DTOs/interfaces layer.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
FileService.ImportAccountStatementFile now emits Information on the
file being processed and on each account's import count, Warning on
mandator-mismatch skips, and Error on vendor-parser failures. UI-facing
MessageDataset behaviour is unchanged.
Pulls Microsoft.Extensions.Logging.Abstractions into the Services
project; FileServiceTestShould uses NullLogger<FileService>.Instance.
Rest of item 18 (repository logging, MatchOpenBankTransactions
summary) tracked as a follow-up.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>