Commit graph

5 commits

Author SHA1 Message Date
1c5227ab9a PR U-12: report primitives + print CSS
Extract ReportHeader, ReportDivider, ReportSectionHeader, and
ReportFooter under Components/Pages/Reports/ and adopt them across
BalanceReport, BalanceSheetReport, ProfitLossReport, and DetailReport.
Row shapes are unchanged; the primitives just fold the divider hr
rows (30+ occurrences), section headers (8), and header/footer (4
each). ProfitLossReport / DetailReport also drop leftover
`m.Class == 3/4` magic numbers for ClassIds. Print CSS gains
`table.report tr { page-break-inside: avoid; }` so section headers
don't land at the bottom of a page alone. Net -88 lines across the
four report razor files.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-07 08:15:00 +02:00
58ca05f83e PR U-5: shared Loading component
Add Components/Loading.razor and swap the ad-hoc `<p>Lädt…</p>` guard
visuals for it across the four reports, three edit dialogs, and three
chart pages (the last three now show the placeholder instead of a
blank page while waiting for view data). Kept as a pure visual so
each call site changes from `<p>Lädt…</p>` to `<Loading/>` with no
other churn.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 09:44:23 +02:00
f9084dc074 PR P: full async I/O top to bottom (item 19)
Every repository, service, Blazor page/dialog, and test now uses
async/await. Single atomic diff; the codebase does not compile in
intermediate states.

- BaseRepository: LoadListAsync/LoadAsync/SaveAsync return Task<T>;
  per-file locks use SemaphoreSlim so waiters can await; Save
  serialises to a MemoryStream sync (XmlSerializer has no async
  form), then File.WriteAllBytesAsync + sync File.Move.
- RepositoryCache.GetOrLoadAsync takes a Func<Task<List<T>>>.
- All 7 repository interfaces + implementations async.
- All service interfaces + implementations async (except vendor
  IFxService and stateless IFxConverter / SettingsService).
- Every Blazor OnInitializedAsync switches to await base.
- Test suite fully async, 42 tests pass.

AccountRepository.EnsureAccountsFile keeps two .GetAwaiter().GetResult()
bridges because it runs from the constructor.

Null-render guard follow-up (folded in):
Blazor now renders the component once with fields at their initial
values while OnInitializedAsync awaits — so fields declared `= null!`
are actually null on that first render and things like
`accounts.GroupBy(...)` throw ArgumentNullException. Fixed across
Transactions, BalanceReport, BalanceSheetReport, ProfitLossReport,
DetailReport, Assets, Spendings, SpendingsOverTime, TransactionDialog,
TransactionSplitDialog, and BookingRuleDialog:
- Collection fields initialise to [] so first-render loops are empty.
- Single-object data fields become nullable; the razor wraps
  consumption in `@if (field is null) { <p>Lädt…</p> return; }`.
- <PlotlyChart> guarded behind a null check on config/layout/data so
  Plotly.Blazor's @bind doesn't see nulls.
- Header/footer strings initialise to "" instead of null!.

Architectural hygiene on a single-user local Blazor Server app: the
observed win is one File.ReadAllBytesAsync and one
File.WriteAllBytesAsync per Load/Save, and after PR D each file is
loaded at most once per SignalR circuit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-03 18:30:44 +02:00
9f11f6490b PR A: correctness fixes and improvement plan
- 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>
2026-07-02 20:37:03 +02:00
ad75c47709 Add new blazor app 2025-05-04 20:00:42 +02:00