# Sprint 10 Test Plan — Smart Payment Import

**Date:** 2026-06-15
**Author:** Patrick Plate / Lumen (Architect)
**Status:** Draft v1
**Basis:** cannamanage-sprint10-plan.md
**Sprint Goal:** Bank statement import with auto-matching for member payment reconciliation

---

## Test Overview

| ID | Description | Type | Class/Component | Status |
|----|------------|------|-----------------|--------|
| T-01 | MT940 parsing — standard Sparkasse file | Unit | Mt940ParserTest | ⬜ |
| T-02 | MT940 parsing — multi-line Verwendungszweck | Unit | Mt940ParserTest | ⬜ |
| T-03 | MT940 parsing — debit/credit identification | Unit | Mt940ParserTest | ⬜ |
| T-04 | MT940 parsing — German amount format (comma decimal) | Unit | Mt940ParserTest | ⬜ |
| T-05 | MT940 parsing — multi-statement file (multiple accounts) | Unit | Mt940ParserTest | ⬜ |
| T-06 | MT940 parsing — malformed input (graceful error) | Unit | Mt940ParserTest | ⬜ |
| T-07 | CAMT.053 parsing — standard XML file | Unit | Camt053ParserTest | ⬜ |
| T-08 | CAMT.053 parsing — multiple entries extraction | Unit | Camt053ParserTest | ⬜ |
| T-09 | CAMT.053 parsing — namespace v2 and v8 support | Unit | Camt053ParserTest | ⬜ |
| T-10 | CAMT.053 parsing — counterparty IBAN + name extraction | Unit | Camt053ParserTest | ⬜ |
| T-11 | CAMT.053 parsing — invalid XML (error handling) | Unit | Camt053ParserTest | ⬜ |
| T-12 | CSV parsing — semicolon delimiter, ISO-8859-1 | Unit | CsvBankParserTest | ⬜ |
| T-13 | CSV parsing — German number format (1.234,56) | Unit | CsvBankParserTest | ⬜ |
| T-14 | CSV parsing — configurable column mapping | Unit | CsvBankParserTest | ⬜ |
| T-15 | CSV parsing — skip header rows | Unit | CsvBankParserTest | ⬜ |
| T-16 | CSV parsing — UTF-8 encoding variant | Unit | CsvBankParserTest | ⬜ |
| T-17 | CSV parsing — empty/malformed rows skipped | Unit | CsvBankParserTest | ⬜ |
| T-18 | Format detection — MT940 by content signature | Unit | BankStatementParserServiceTest | ⬜ |
| T-19 | Format detection — CAMT.053 by XML namespace | Unit | BankStatementParserServiceTest | ⬜ |
| T-20 | Format detection — CSV by file extension | Unit | BankStatementParserServiceTest | ⬜ |
| T-21 | Format detection — unrecognized format throws exception | Unit | BankStatementParserServiceTest | ⬜ |
| T-22 | Matching — exact amount match scores 100 | Unit | PaymentMatchingServiceTest | ⬜ |
| T-23 | Matching — amount within 5% scores 80 | Unit | PaymentMatchingServiceTest | ⬜ |
| T-24 | Matching — member number in Verwendungszweck (M-0042) | Unit | PaymentMatchingServiceTest | ⬜ |
| T-25 | Matching — full member name in reference text | Unit | PaymentMatchingServiceTest | ⬜ |
| T-26 | Matching — last name only (partial name match) | Unit | PaymentMatchingServiceTest | ⬜ |
| T-27 | Matching — IBAN exact match | Unit | PaymentMatchingServiceTest | ⬜ |
| T-28 | Matching — confidence ≥90% → MATCHED status | Unit | PaymentMatchingServiceTest | ⬜ |
| T-29 | Matching — confidence 60-89% → SUGGESTED status | Unit | PaymentMatchingServiceTest | ⬜ |
| T-30 | Matching — confidence <60% → UNMATCHED status | Unit | PaymentMatchingServiceTest | ⬜ |
| T-31 | Matching — conflict: multiple members match → SUGGESTED | Unit | PaymentMatchingServiceTest | ⬜ |
| T-32 | Matching — negative amount (debit) skipped from matching | Unit | PaymentMatchingServiceTest | ⬜ |
| T-33 | Matching — no active fee assignment → no match | Unit | PaymentMatchingServiceTest | ⬜ |
| T-34 | Import service — upload and parse creates session | Integration | BankImportServiceTest | ⬜ |
| T-35 | Import service — run matching updates transactions | Integration | BankImportServiceTest | ⬜ |
| T-36 | Import service — confirm match creates Payment + LedgerEntry | Integration | BankImportServiceTest | ⬜ |
| T-37 | Import service — bulk confirm all matched | Integration | BankImportServiceTest | ⬜ |
| T-38 | Import service — skip transaction sets status | Integration | BankImportServiceTest | ⬜ |
| T-39 | Import service — manual assign with explicit member | Integration | BankImportServiceTest | ⬜ |
| T-40 | Import service — categorize as expense creates LedgerEntry | Integration | BankImportServiceTest | ⬜ |
| T-41 | Import service — complete session updates status | Integration | BankImportServiceTest | ⬜ |
| T-42 | Tier enforcement — Starter: CSV only | Unit | TierLimitServiceTest | ⬜ |
| T-43 | Tier enforcement — Starter: max 1 import/month | Unit | TierLimitServiceTest | ⬜ |
| T-44 | Tier enforcement — Pro: all formats allowed | Unit | TierLimitServiceTest | ⬜ |
| T-45 | Tier enforcement — Pro: max 3 CSV templates | Unit | TierLimitServiceTest | ⬜ |
| T-46 | Tier enforcement — Enterprise: auto-confirm enabled | Unit | TierLimitServiceTest | ⬜ |
| T-47 | IBAN validation — valid German IBAN (DE) | Unit | IbanValidatorTest | ⬜ |
| T-48 | IBAN validation — invalid checksum rejected | Unit | IbanValidatorTest | ⬜ |
| T-49 | IBAN validation — wrong length rejected | Unit | IbanValidatorTest | ⬜ |
| T-50 | IBAN validation — international IBAN formats | Unit | IbanValidatorTest | ⬜ |
| T-51 | REST API — upload file returns session | Integration | BankImportControllerTest | ⬜ |
| T-52 | REST API — upload exceeds 10MB → 413 error | Integration | BankImportControllerTest | ⬜ |
| T-53 | REST API — unauthorized user → 403 | Integration | BankImportControllerTest | ⬜ |
| T-54 | REST API — confirm match endpoint | Integration | BankImportControllerTest | ⬜ |
| T-55 | REST API — list sessions paginated | Integration | BankImportControllerTest | ⬜ |
| T-56 | REST API — CSV mapping CRUD | Integration | BankImportControllerTest | ⬜ |
| T-57 | REST API — tenant isolation (club A cannot see club B sessions) | Integration | BankImportControllerTest | ⬜ |
| T-58 | Member IBAN — store with consent verification | Integration | MemberControllerTest | ⬜ |
| T-59 | Member IBAN — reject without consent | Integration | MemberControllerTest | ⬜ |
| T-60 | Flyway migration — V30 applies cleanly | Integration | FlywayMigrationTest | ⬜ |
| T-61 | Flyway migration — V31 applies cleanly | Integration | FlywayMigrationTest | ⬜ |
| T-62 | Flyway migration — V32 applies cleanly | Integration | FlywayMigrationTest | ⬜ |
| T-63 | End-to-end — MT940 upload → match → confirm → payment created | E2E | BankImportE2ETest | ⬜ |
| T-64 | End-to-end — CSV with custom mapping → successful import | E2E | BankImportE2ETest | ⬜ |
| T-65 | End-to-end — mixed matches (auto + manual + skip) | E2E | BankImportE2ETest | ⬜ |
| T-66 | Frontend — upload wizard renders all 4 steps | E2E | Playwright | ⬜ |
| T-67 | Frontend — CSV column mapping interaction | E2E | Playwright | ⬜ |
| T-68 | Frontend — match review table filtering | E2E | Playwright | ⬜ |
| T-69 | Frontend — bulk confirm action | E2E | Playwright | ⬜ |
| T-70 | Frontend — import history table | E2E | Playwright | ⬜ |

Status: ⬜ Pending | ✅ Passed | ❌ Failed | ⏭️ Skipped

---

## Test Cases — Detailed

### T-01: MT940 parsing — standard Sparkasse file

**Type:** Unit
**Class:** `cannamanage-service/src/test/java/de/cannamanage/service/bankimport/Mt940ParserTest.java`
**Method:** `testParseSparkasseFile()`

**Preconditions:**
- MT940 test fixture file in `src/test/resources/fixtures/mt940/sparkasse-standard.sta`

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | Standard Sparkasse MT940 with 5 transactions | ParseResult with 5 transactions, correct amounts/dates |
| b | Opening balance `:60F:C260601EUR1234,56` | openingBalanceCents = 123456 |
| c | Credit transaction `:61:2506150615CR50,00` | amountCents = 5000, bookingDate = 2025-06-15 |
| d | Account IBAN from `:25:` field | accountIban extracted correctly |

**Postconditions:**
- All transactions have non-null bookingDate and amountCents

---

### T-02: MT940 parsing — multi-line Verwendungszweck

**Type:** Unit
**Class:** `Mt940ParserTest.java`
**Method:** `testMultiLineReference()`

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | `:86:` spanning 3 lines with `SVWZ+` | referenceText contains full concatenated text |
| b | `:86:` with `EREF+`, `KREF+`, `MREF+` sub-fields | Sub-fields parsed, SVWZ extracted as reference |
| c | `:86:` with `DEBT+DE89370400440532013000` | counterpartyIban = "DE89370400440532013000" |

---

### T-03: MT940 parsing — debit/credit identification

**Type:** Unit
**Class:** `Mt940ParserTest.java`
**Method:** `testDebitCreditIdentification()`

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | `:61:` with `CR` indicator | amountCents > 0 (positive/incoming) |
| b | `:61:` with `D` indicator | amountCents < 0 (negative/outgoing) |
| c | `:61:` with `RC` (reversal credit) | amountCents > 0 |
| d | `:61:` with `RD` (reversal debit) | amountCents < 0 |

---

### T-04: MT940 parsing — German amount format

**Type:** Unit
**Class:** `Mt940ParserTest.java`
**Method:** `testGermanAmountParsing()`

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | `50,00` | 5000 cents |
| b | `1234,56` | 123456 cents |
| c | `0,01` | 1 cent |
| d | `99999,99` | 9999999 cents |

---

### T-05: MT940 parsing — multi-statement file

**Type:** Unit
**Class:** `Mt940ParserTest.java`
**Method:** `testMultiStatementFile()`

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | File with 2 statements (2 different accounts) | Parse both, return transactions from all |
| b | Filter by specific account IBAN | Only matching account's transactions returned |

---

### T-06: MT940 parsing — malformed input

**Type:** Unit
**Class:** `Mt940ParserTest.java`
**Method:** `testMalformedInput()`

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | Empty file | ParseResult with empty list + warning |
| b | Missing `:60F:` (no opening balance) | Parse continues, openingBalanceCents = null |
| c | Corrupted amount field | Transaction skipped, warning added |
| d | Binary file (not text) | Exception or empty result with error warning |

---

### T-07: CAMT.053 parsing — standard XML file

**Type:** Unit
**Class:** `cannamanage-service/src/test/java/de/cannamanage/service/bankimport/Camt053ParserTest.java`
**Method:** `testParseStandardCamt053()`

**Preconditions:**
- CAMT.053 test fixture in `src/test/resources/fixtures/camt053/standard.xml`

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | Standard CAMT.053 with 3 entries | ParseResult with 3 transactions |
| b | `<Amt Ccy="EUR">50.00</Amt>` | amountCents = 5000 |
| c | `<CdtDbtInd>CRDT</CdtDbtInd>` | amountCents positive |
| d | `<BookgDt><Dt>2025-06-15</Dt></BookgDt>` | bookingDate = 2025-06-15 |

---

### T-08: CAMT.053 parsing — multiple entries

**Type:** Unit
**Class:** `Camt053ParserTest.java`
**Method:** `testMultipleEntries()`

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | 10 `<Ntry>` elements | 10 ParsedTransaction objects |
| b | Mix of CRDT and DBIT | Correct positive/negative amounts |

---

### T-09: CAMT.053 parsing — namespace versions

**Type:** Unit
**Class:** `Camt053ParserTest.java`
**Method:** `testNamespaceVersions()`

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | Namespace `camt.053.001.02` | Parsed successfully |
| b | Namespace `camt.053.001.08` | Parsed successfully |
| c | Unknown namespace `camt.054.001.02` | canParse returns false or warning |

---

### T-10: CAMT.053 parsing — counterparty extraction

**Type:** Unit
**Class:** `Camt053ParserTest.java`
**Method:** `testCounterpartyExtraction()`

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | `<Dbtr><Nm>Max Mustermann</Nm></Dbtr>` | counterpartyName = "Max Mustermann" |
| b | `<DbtrAcct><Id><IBAN>DE12...</IBAN></Id></DbtrAcct>` | counterpartyIban = "DE12..." |
| c | `<Ustrd>M-0042 Beitrag</Ustrd>` | referenceText = "M-0042 Beitrag" |

---

### T-11: CAMT.053 parsing — invalid XML

**Type:** Unit
**Class:** `Camt053ParserTest.java`
**Method:** `testInvalidXml()`

**Scenarios:**

| # | Input | Expected Result |
|---|-------|----------------|
| a | Malformed XML (unclosed tag) | Exception with descriptive message |
| b | Valid XML but wrong schema (not CAMT) | Empty result or format detection rejects |

---

### T-12 through T-17: CSV parsing tests

**Type:** Unit
**Class:** `cannamanage-service/src/test/java/de/cannamanage/service/bankimport/CsvBankParserTest.java`

Test fixtures:
- `src/test/resources/fixtures/csv/sparkasse.csv` (semicolon, ISO-8859-1, dd.MM.yyyy)
- `src/test/resources/fixtures/csv/ing-diba.csv` (semicolon, ISO-8859-1, different columns)
- `src/test/resources/fixtures/csv/commerzbank.csv` (semicolon, UTF-8)
- `src/test/resources/fixtures/csv/malformed.csv` (bad rows)

Key scenarios:
- Semicolon delimiter parsing
- German number format: `"1.234,56"` → 123456 cents
- Configurable column positions via CsvColumnMapping
- Header row skipping (1 or 2 rows)
- UTF-8 encoding support
- Empty/malformed rows produce warnings, don't abort

---

### T-22 through T-33: Matching algorithm tests

**Type:** Unit
**Class:** `cannamanage-service/src/test/java/de/cannamanage/service/bankimport/PaymentMatchingServiceTest.java`

**Test data setup:**
```java
// Members:
// - Member "M-0042" (Max Mustermann, IBAN DE89370400440532013000, fee 50€/month)
// - Member "M-0043" (Anna Schmidt, no IBAN, fee 75€/quarter)
// - Member "M-0044" (Peter Müller, IBAN DE12345678901234567890, fee 50€/month)
```

**Key test scenarios:**

| Test | Transaction | Expected Match | Confidence | Rationale |
|------|-------------|---------------|------------|-----------|
| T-22 | 50.00€, ref "M-0042 Beitrag" | M-0042 | ≥95% | Exact amount + member number |
| T-23 | 51.00€, ref "M-0042 Beitrag" | M-0042 | ~85% | Amount within 5% + member number |
| T-24 | 50.00€, ref "M-0042 Juni" | M-0042 | ≥90% | Exact amount + member number |
| T-25 | 50.00€, ref "Max Mustermann Beitrag" | M-0042 | ≥80% | Amount + full name |
| T-26 | 50.00€, ref "Mustermann Mitgliedsbeitrag" | M-0042 | ~75% | Amount + last name |
| T-27 | 50.00€, ref "Überweisung", IBAN DE89... | M-0042 | ≥85% | Amount + IBAN |
| T-28 | 50.00€, ref "M-0042", IBAN DE89... | M-0042 | ≥95% (MATCHED) | All criteria hit |
| T-29 | 50.00€, ref "Beitrag", no IBAN | M-0042 or M-0044 | 60-89% (SUGGESTED) | Amount matches two members |
| T-30 | 123.45€, ref "random text" | None | <60% (UNMATCHED) | No criteria match |
| T-31 | 50.00€, ref "Beitrag", no IBAN match | Conflict | SUGGESTED | Two 50€ members, can't distinguish |
| T-32 | -50.00€ (debit) | Skipped | N/A | Negative = outgoing, not a payment |
| T-33 | 50.00€ from member with no fee assignment | None | <60% | No expected payment to match against |

---

### T-34 through T-41: Import service integration tests

**Type:** Integration (Spring Boot Test with H2)
**Class:** `cannamanage-service/src/test/java/de/cannamanage/service/bankimport/BankImportServiceTest.java`

**Setup:** `@SpringBootTest` with `@Transactional`, test fixtures loaded via Flyway

| Test | Scenario | Verification |
|------|----------|-------------|
| T-34 | Upload MT940 file | Session created, status=PENDING, transactions persisted |
| T-35 | Run matching on session | Transactions updated with matchStatus + confidence |
| T-36 | Confirm a MATCHED transaction | Payment entity created, LedgerEntry(INCOME) created, tx status=CONFIRMED |
| T-37 | Bulk confirm 5 MATCHED transactions | 5 Payments created, session.confirmedCount = 5 |
| T-38 | Skip a transaction | tx status=SKIPPED, skipReason set |
| T-39 | Manual assign to different member | Payment created with specified memberId |
| T-40 | Categorize debit as RENT expense | LedgerEntry(EXPENSE, RENT) created |
| T-41 | Complete session | status=COMPLETED, completedAt set |

**Postconditions for T-36:**
- `Payment` exists with correct memberId, amountCents, paymentMethod=BANK_TRANSFER
- `LedgerEntry` exists with transactionType=INCOME, relatedPaymentId set
- `BankTransaction.matchedPaymentId` set to new Payment ID
- Audit event BANK_PAYMENT_CONFIRMED logged

---

### T-42 through T-46: Tier enforcement tests

**Type:** Unit
**Class:** `cannamanage-service/src/test/java/de/cannamanage/service/TierLimitServiceTest.java`

| Test | Tier | Action | Expected |
|------|------|--------|----------|
| T-42 | STARTER | Import MT940 | TierLimitExceededException |
| T-43 | STARTER | 2nd import in same month | TierLimitExceededException |
| T-44 | PRO | Import MT940 | Allowed |
| T-45 | PRO | Create 4th CSV mapping | TierLimitExceededException |
| T-46 | ENTERPRISE | Check autoConfirmAllowed | returns true |

---

### T-47 through T-50: IBAN validation tests

**Type:** Unit
**Class:** `cannamanage-service/src/test/java/de/cannamanage/service/bankimport/IbanValidatorTest.java`

| Test | Input | Expected |
|------|-------|----------|
| T-47 | `DE89370400440532013000` | valid = true |
| T-48 | `DE00370400440532013000` (bad checksum) | valid = false |
| T-49 | `DE893704004` (too short) | valid = false |
| T-50 | `GB29NWBK60161331926819` (UK IBAN) | valid = true |

---

### T-51 through T-57: REST API integration tests

**Type:** Integration (MockMvc)
**Class:** `cannamanage-api/src/test/java/de/cannamanage/api/controller/BankImportControllerTest.java`

| Test | Request | Expected Response |
|------|---------|------------------|
| T-51 | POST /api/finance/import/upload (multipart) | 201, session JSON with transaction count |
| T-52 | POST /api/finance/import/upload (15MB file) | 413 Payload Too Large |
| T-53 | POST /api/finance/import/upload (no FINANCE_IMPORT permission) | 403 Forbidden |
| T-54 | POST /api/finance/import/transactions/{id}/confirm | 200, updated transaction JSON |
| T-55 | GET /api/finance/import/sessions?page=0&size=10 | 200, paginated session list |
| T-56 | POST + GET + PUT + DELETE /api/finance/import/csv-mappings | Full CRUD cycle |
| T-57 | GET /api/finance/import/sessions (as different club) | 200, empty list (tenant isolation) |

---

### T-58 through T-59: Member IBAN consent tests

**Type:** Integration
**Class:** `cannamanage-api/src/test/java/de/cannamanage/api/controller/MemberControllerTest.java`

| Test | Precondition | Request | Expected |
|------|-------------|---------|----------|
| T-58 | BANK_DATA consent granted | PATCH /api/members/{id}/iban | 200, IBAN stored |
| T-59 | No BANK_DATA consent | PATCH /api/members/{id}/iban | 400, "Consent required" |

---

### T-60 through T-62: Flyway migration tests

**Type:** Integration
**Class:** `cannamanage-api/src/test/java/de/cannamanage/api/FlywayMigrationTest.java`

Verify all migrations V30-V32 apply cleanly on a fresh H2 database without errors.

---

### T-63 through T-65: End-to-end backend tests

**Type:** E2E (Spring Boot integration with full context)
**Class:** `cannamanage-api/src/test/java/de/cannamanage/api/BankImportE2ETest.java`

| Test | Flow | Verification |
|------|------|-------------|
| T-63 | Upload MT940 → detect → parse → match → confirm all → complete | Payments + LedgerEntries created, session COMPLETED |
| T-64 | Upload CSV → create mapping → parse with mapping → match → confirm | Custom mapping applied, correct column extraction |
| T-65 | Upload → match (3 auto, 2 suggested, 1 unmatched) → confirm 3 → assign 2 → skip 1 → complete | All statuses correct, counts accurate |

---

### T-66 through T-70: Frontend E2E tests (Playwright)

**Type:** E2E (Playwright)
**File:** `cannamanage-frontend/e2e/bank-import.spec.ts`

| Test | Scenario | Verification |
|------|----------|-------------|
| T-66 | Navigate to /finance/import, verify 4-step wizard structure | Steps visible, upload zone rendered |
| T-67 | Upload CSV → column mapping dropdowns → assign columns → proceed | Mapping applied, preview table shown |
| T-68 | Match review table → filter by status → verify correct rows | Filter tabs work, row counts match |
| T-69 | Click "Alle bestätigen" → success toast → counts update | Confirmed count increases, green rows disappear |
| T-70 | Navigate to import history → past sessions listed | Table with filename, date, status columns |

---

## Test Data (Fixtures)

### MT940 Test File (`sparkasse-standard.sta`)

```
:20:STARTUMSE
:25:20050550/7654321
:28C:00000/001
:60F:C260601EUR1234,56
:61:2506150615CR50,00NMSCNONREF
:86:SVWZ+M-0042 Mitgliedsbeitrag Juni 2025
EREF+NOTPROVIDED
DEBT+DE89370400440532013000
:61:2506150615CR75,00NMSCNONREF
:86:SVWZ+Anna Schmidt Quartalsbeitrag
DEBT+DE55500105175898765432
:61:2506150615DR120,00NMSCNONREF
:86:SVWZ+Miete Vereinsraum Juni
CRED+DE44100800000123456789
:61:2506150615CR50,00NMSCNONREF
:86:SVWZ+Beitrag
DEBT+DE12345678901234567890
:61:2506150615CR25,00NMSCNONREF
:86:SVWZ+Spende Vereinsfest
DEBT+DE99876543210987654321
:62F:C260615EUR1239,56
```

### CAMT.053 Test File (`standard.xml`)

```xml
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.02">
  <BkToCstmrStmt>
    <Stmt>
      <Id>2025-06-15-001</Id>
      <Acct><Id><IBAN>DE20050550007654321</IBAN></Id></Acct>
      <Bal><!-- balances --></Bal>
      <Ntry>
        <Amt Ccy="EUR">50.00</Amt>
        <CdtDbtInd>CRDT</CdtDbtInd>
        <BookgDt><Dt>2025-06-15</Dt></BookgDt>
        <ValDt><Dt>2025-06-15</Dt></ValDt>
        <NtryDtls><TxDtls>
          <RmtInf><Ustrd>M-0042 Mitgliedsbeitrag Juni 2025</Ustrd></RmtInf>
          <RltdPties>
            <Dbtr><Nm>Max Mustermann</Nm></Dbtr>
            <DbtrAcct><Id><IBAN>DE89370400440532013000</IBAN></Id></DbtrAcct>
          </RltdPties>
        </TxDtls></NtryDtls>
      </Ntry>
      <!-- more entries... -->
    </Stmt>
  </BkToCstmrStmt>
</Document>
```

### CSV Test File (`sparkasse.csv`)

```csv
"Auftragskonto";"Buchungstag";"Valutadatum";"Buchungstext";"Verwendungszweck";"Beguenstigter/Zahlungspflichtiger";"Kontonummer";"BLZ";"Betrag";"Waehrung"
"DE20050550007654321";"15.06.2025";"15.06.2025";"GUTSCHR";"M-0042 Mitgliedsbeitrag Juni 2025";"Max Mustermann";"DE89370400440532013000";"37040044";"50,00";"EUR"
"DE20050550007654321";"15.06.2025";"15.06.2025";"GUTSCHR";"Anna Schmidt Quartalsbeitrag";"Anna Schmidt";"DE55500105175898765432";"50010517";"75,00";"EUR"
```

---

## Test Coverage Matrix

| Component | Unit | Integration | E2E | Total |
|-----------|------|-------------|-----|-------|
| Mt940Parser | 6 | 0 | 1 | 7 |
| Camt053Parser | 5 | 0 | 0 | 5 |
| CsvBankParser | 6 | 0 | 1 | 7 |
| BankStatementParserService | 4 | 0 | 0 | 4 |
| PaymentMatchingService | 12 | 0 | 0 | 12 |
| BankImportService | 0 | 8 | 3 | 11 |
| TierLimitService | 5 | 0 | 0 | 5 |
| IbanValidator | 4 | 0 | 0 | 4 |
| BankImportController | 0 | 7 | 0 | 7 |
| MemberController (IBAN) | 0 | 2 | 0 | 2 |
| Flyway Migrations | 0 | 3 | 0 | 3 |
| Frontend (Playwright) | 0 | 0 | 5 | 5 |
| **Total** | **42** | **20** | **10** | **70** |

---

## Traceability Matrix

| Requirement | Plan Step | Test Case(s) |
|-------------|-----------|-------------|
| MT940 format parsing | Phase 2, Step 2.2 | T-01 through T-06 |
| CAMT.053 format parsing | Phase 2, Step 2.3 | T-07 through T-11 |
| CSV format parsing with configurable columns | Phase 2, Step 2.4 | T-12 through T-17 |
| Auto-format detection | Phase 2, Step 2.5 | T-18 through T-21 |
| Weighted matching algorithm | Phase 3, Step 3.1 | T-22 through T-33 |
| Import session lifecycle | Phase 3, Step 3.2 | T-34 through T-41 |
| Tier-based restrictions | Phase 3, Step 3.3 | T-42 through T-46 |
| IBAN validation + consent | Phase 4, Step 4.4 | T-47 through T-50, T-58, T-59 |
| REST API endpoints | Phase 4, Step 4.1 | T-51 through T-57 |
| Database migrations | Phase 1, Step 1.1 | T-60 through T-62 |
| Full import flow | All phases | T-63 through T-65 |
| Frontend wizard UX | Phase 5 | T-66 through T-70 |
| Tenant isolation | Phase 4, Step 4.3 | T-57 |
| GDPR consent for IBAN | Phase 1, Step 1.2 (ConsentType) | T-58, T-59 |
| File size limit (10MB) | Phase 4, Step 4.3 | T-52 |
| Permission enforcement | Phase 4, Step 4.3 | T-53 |
