# Plan: Issue #45 — Unit tests for FeeCalculationService

## Summary
Create comprehensive PHPUnit tests for `FeeCalculationService` covering all 13 test cases specified in the issue: single member fees, family discount tiers, frozen rates, Erziehungsberechtigter exclusion, manual overrides during batch, mixed family states, error handling for missing rules, and cross-year rule changes.

## Implementation Steps

### Step 1: Create phpunit.xml configuration
- Add `phpunit.xml` at project root with standard Nextcloud app test configuration
- Configure autoloading to use composer PSR-4 paths
- Set test suite directory to `tests/Unit/`

### Step 2: Create test helper for building mock Members/Rules
- Create a helper trait or base class that provides factory methods:
  - `createMember(id, rolle, status, familyId, geburtsdatum, frozenFeeRate)`
  - `createFeeRule(yearFrom, baseRate, familyRulesJson, inactiveRule)`
  - `createFeeRecord(memberId, year, amount, manuellAngepasst)`

### Step 3: Create FeeCalculationServiceTest.php
Write isolated test methods for each case in the issue:

1. `testSingleMemberNoFamily` — member without family_id → base_rate
2. `testSingleChildInFamily` — 1 active child → family rule "1" rate
3. `testTwoChildrenInFamily` — 2 active children → correct per-child amounts per position
4. `testThreeOrMoreChildrenInFamily` — 3+ children → "3+" catch-all rule applied, additional children at that rate
5. `testInactiveMemberWithFrozenRate` — inactive member with frozen_fee_rate → frozen rate returned
6. `testErziehungsberechtigterExcluded` — rolle=erziehungsberechtigter → fee = 0.00
7. `testManualOverrideSkippedDuringBatch` — manuell_angepasst records not overwritten in batchCalculate
8. `testMixedFamilyActiveInactiveChildren` — only active children counted for position-based discount
9. `testMemberBecomesInactiveMidYear` — rate frozen at current value
10. `testNoFeeRuleForYear` — DoesNotExistException thrown
11. `testRuleChangeBetweenYears` — correct rule applied per year
12. `testEmptyFamilyNoChildren` — no fees generated (base rate for adults in empty family)
13. `testMemberWithoutFamilyWithOverrideBanking` — base rate

### Step 4: Ensure all tests use mocked mappers
- Mock `FeeRuleMapper`, `FeeRecordMapper`, `MemberMapper`
- Mock `IUserSession` and `LoggerInterface`
- Each test creates its own data set, no shared state

## Acceptance Criteria Checklist
- [ ] AC1: `FeeCalculationServiceTest.php` exists in `tests/Unit/`
- [ ] AC2: All 13 test cases from the issue are implemented
- [ ] AC3: Tests use mock data (mocked mappers), isolated per test
- [ ] AC4: Tests assert exact amounts and rule snapshots
- [ ] AC5: Tests cover both `calculateFee()` and `batchCalculate()` methods
- [ ] AC6: phpunit.xml configuration is present
