AccountingNext/CLAUDE.md
Claudio Schaad 8926fb1e97 PR G: test project with xxxTestShould / DoThisWhenThat / Shouldly
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>
2026-07-02 21:25:48 +02:00

92 lines
3.9 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Commands
```bash
# Build
dotnet build
# Run (dev server at http://localhost:5225)
cd Schaad.Accounting.UI && dotnet run
# Test
dotnet test
# Publish
dotnet publish -c Release
```
Tests live in `Schaad.Accounting.Tests` (xUnit + NSubstitute + Shouldly). There is no lint command.
## Architecture
**AccountingNext** is a Swiss personal accounting app built with ASP.NET Core 9 + Blazor (interactive server-side rendering) and Microsoft Fluent UI. Data is stored as XML files on disk — no database.
### Projects
| Project | Role |
|---|---|
| `Schaad.Accounting.UI` | Blazor web app — pages, dialogs, layout |
| `Schaad.Accounting.Services` | Business logic — balances, FX conversion, file import, charts |
| `Schaad.Accounting.Db` | Repository layer — XML serialization/deserialization |
| `Schaad.Accounting.Common` | Shared models, DTOs, interfaces |
### Data flow
```
Blazor Pages/Dialogs (UI)
→ Services (ViewService, FileService, ChartService)
→ Repositories (AccountRepository, TransactionRepository, …)
→ XML files: {DataPath}/{year}/{mandator}/Data/*.xml
```
Data is multi-year and multi-mandator. Each combination has its own directory under `DataPath` (configured in `appsettings.Development.json`). When a new year is opened, the previous year's data is copied as the starting point.
### Key services
- **ViewService** — aggregates account balances, applies FX rates, builds datasets for the UI
- **FileService** — imports bank statements (MT940, CAMT053 via external `Schaad.Finance.dll`), parses PDFs, manages backups
- **ChartService** — produces data for Plotly charts (assets, spendings over time)
- **SettingsService** — manages the active year, mandator, and file paths
### UI structure
Pages live in `Schaad.Accounting.UI/Components/Pages/`. Each page typically has a companion `Dialogs/` subfolder with Fluent UI dialog components for CRUD operations. The app is hardcoded to the `de-CH` culture.
### Key external dependencies
- `Microsoft.FluentUI.AspNetCore.Components` — UI components (FluentDataGrid, FluentDialog, etc.)
- `Plotly.Blazor` — charts
- `FreeSpire.PDF` — PDF parsing for bank statement imports
- `Schaad.Finance.dll` / `Schaad.Finance.Api.dll` — proprietary DLLs for MT940/CAMT053 parsing and FX rate lookups (FixerIo API key in `appsettings.Development.json`)
### Domain constants
`ClassIds` in `Schaad.Accounting.Common` defines the Swiss accounting chart-of-accounts classes: Activa=1, Passiva=2, Income=3, Expenses=4. These are the leading digit of an account number (accounts are 4-digit; `Account.Class = Number / 1000`).
## Testing conventions
Tests live in `Schaad.Accounting.Tests` and use **xUnit** + **NSubstitute** (mocks) + **Shouldly** (assertions).
- **File and class name**: `<Subject>TestShould` — one file per subject under test. Examples: `ViewServiceTestShould.cs`, `TransactionRepositoryTestShould.cs`, `FormattingTestShould.cs`.
- **Test method name**: `DoThisWhenThat` — describes the behavior first, then the condition. Read together the class + method form a sentence:
- `ViewServiceTestShould.SumActivaAndPassivaTotalsSeparatelyWhenGettingBalanceView`
- `TransactionRepositoryTestShould.ReturnNullWhenGettingUnknownTransactionId`
- **Assertions**: use Shouldly (`value.ShouldBe(expected)`, `list.ShouldBeEmpty()`, `x.ShouldBeNull()`, `list.ShouldContain(...)`, `first.ShouldBeSameAs(second)`, etc.). Do **not** use xUnit `Assert.*`.
Example:
```csharp
public class FormattingTestShould
{
[Fact]
public void RoundToTwoDecimalsWhenValueHasMorePrecision()
{
1.234m.ToFormattedString().ShouldBe("1.23");
}
}
```
Repository tests that hit real XML I/O use a temp directory via `Path.GetTempPath()` and clean up in `IDisposable.Dispose`. Pure service tests use NSubstitute mocks for every dependency and don't touch the filesystem.