PHASE 7: Document Management & Data Import
Trigger: "Execute Phase 7 as per masterplan."
Goal: Document upload/storage with S3/MinIO, PDF generation for contracts and invoices, and a complete data import framework with CSV parser, mapping engine, dry-run capability, and pre-defined templates for 5 competitor systems.
Prerequisite: Phase 6 is completed.
Step 7.0 — Technical Conception
Create doc/developer/conception/phase-7-conception.md.
(reference: Chapter 11 — Data Model, Chapter 15 — Migration Strategy)
Content: - Scope: membership-document (upload, storage, PDF), membership-import (CSV, mapping, templates) - Storage Architecture: S3-compatible API (MinIO for local dev, Hetzner Object Storage for prod) - PDF Pipeline: Open HTML to PDF engine, Thymeleaf template rendering, per-entity branding - Import Framework: CSV parsing (Apache Commons CSV), column mapping engine, validation pipeline, dry-run mode - Template Design: 5 competitor templates with pre-configured field mappings - Error Handling: Per-row validation errors, downloadable error report, partial import with skip-on-error - Security: ClamAV virus scanning on upload, file type whitelist, max file size - Design Chapter References: Ch11, Ch15
Result: Technical conception for Phase 7.
Step 7.1 — Document Entity and Storage
Create the membership-document module.
(reference: Chapter 11 — Data Model)
Approach:
- Create JPA entity:
Document
Document fields:
| Field | Type | Constraints | Description |
|---|---|---|---|
| id | Long | PK, generated | |
| entityId | Long | FK NOT NULL | Organization |
| name | String(255) | NOT NULL | Original filename |
| displayName | String(255) | User-friendly display name | |
| mimeType | String(100) | NOT NULL | MIME type (e.g., application/pdf) |
| sizeBytes | Long | NOT NULL | File size in bytes |
| storagePath | String(500) | NOT NULL | S3/MinIO path |
| linkedType | String(30) | NOT NULL | MEMBER / CONTRACT / ORGANIZATION / INVOICE / IMPORT |
| linkedId | Long | NOT NULL | ID of linked entity |
| category | String(30) | default 'OTHER' | CONTRACT_PDF / INVOICE_PDF / MEMBER_CARD / PHOTO / IMPORT_FILE / SEPA_XML / OTHER |
| generatedBy | String(30) | nullable | SYSTEM (auto-generated) / USER (uploaded) |
| uploadedBy | Long | FK nullable | User who uploaded (null if system-generated) |
| virusScanStatus | String(20) | default 'PENDING' | PENDING / CLEAN / INFECTED / SKIPPED |
| createdAt | Instant | auto | |
| version | Long | @Version | Optimistic locking |
2. Create StorageService (interface): |
|||
- upload(InputStream data, String path, String contentType) -> URL |
|||
- download(String path) -> InputStream |
|||
- delete(String path) |
|||
- getSignedUrl(String path, Duration expiry) -> String |
|||
3. Create MinioStorageService (S3-compatible implementation): |
|||
- Bucket: membership-documents |
|||
- Path convention: /{entityId}/{linkedType}/{linkedId}/{documentId}_{filename} |
|||
| - Configurable: endpoint, access key, secret key, bucket name | |||
4. Create DocumentService: |
|||
| - Upload with metadata, download with access control | |||
| - File type whitelist: PDF, PNG, JPG, JPEG, DOCX, XLSX, CSV (configurable) | |||
| - Max file size: 10 MB (configurable) | |||
| - Virus scanning: ClamAV integration (optional, toggleable) | |||
| - Link document to member, contract, or organization | |||
| 5. Create controller: | |||
- POST /api/document/upload (multipart) — Upload document |
|||
- GET /api/document/{id} — Get document metadata |
|||
- GET /api/document/{id}/download — Download file |
|||
- DELETE /api/document/{id} — Delete document |
|||
- GET /api/document/list?linkedType=MEMBER&linkedId={id} — List documents for entity |
|||
6. Create Flyway migration: V700__create_document.sql |
|||
7. Add MinIO to backend/docker-compose.yml (port 9000, console 9001) |
|||
8. Create tests: DocumentServiceTest, MinioStorageServiceTest |
Result: Document management with S3-compatible storage.
Step 7.2 — PDF Generation
(reference: Chapter 12 — Integration)
Approach:
- Add
openhtmltopdf-pdfboxdependency tomembership-document - Create
PdfGenerationService: -generateContractPdf(Contract contract)-> byte[] -generateInvoicePdf(Transaction transaction)-> byte[] -generateMemberCardPdf(Member member)-> byte[] - Create Thymeleaf templates in
membership-document/src/main/resources/templates/pdf/: -contract-template.html— Contract PDF:- Header: organization logo (left), organization name + address (right)
- Section 1: Member details (name, address, member number)
- Section 2: Membership details (plan name, price, billing cycle, start date, minimum term)
- Section 3: Terms and conditions (from CommunicationTemplate type AGB)
- Section 4: Cancellation policy (notice period, auto-renewal clause)
- Section 5: Signature area (digital signature data: name, date, IP address)
- Footer: organization contact info, "Generated by Membership One"
invoice-template.html— Invoice PDF:- Header: organization logo, name, address, USt-IdNr.
- Addressee: member name and address
- Invoice details: invoice number, invoice date, due date, payment method
- Line items table: position #, description, quantity, unit price, VAT rate, total
- Summary: subtotal (net), VAT breakdown per rate (7%, 19%), total (gross)
- Payment info: IBAN, BIC, bank name, mandate reference (if SEPA)
- Footer: Amtsgericht, HRB, Geschäftsführer
member-card-template.html— Member card (A6 landscape):- Front: organization logo, member photo, full name, member number, QR code
- Membership plan badge, valid until date
- Per-entity branding: logo, primary color, accent color, footer text loaded from Organization settings JSONB
- i18n: PDF rendered in member's preferred locale (from User.locale, falls back to entity locale)
- Page format: A4 portrait for contracts/invoices, A6 landscape for member cards. Fonts: Noto Sans (includes RTL glyphs for Arabic/Hebrew)
- Create controller:
-
GET /api/document/pdf/contract/{contractId}— Generate and download contract PDF -GET /api/document/pdf/invoice/{transactionId}— Generate and download invoice PDF - RabbitMQ integration:
document.generatequeue for async PDF generation of bulk documents - Create tests:
PdfGenerationServiceTest
Result: PDF generation for contracts, invoices, member cards with branding and i18n.
Step 7.3 — Document Controller and Admin UI
- Admin UI: document list per member/contract, upload drag-and-drop, preview (PDF/image inline)
- Member app: view own documents, download contracts/invoices
- Automatic document creation: contract PDF generated on purchase, invoice PDF on billing
Result: Document management integrated in admin and member UI.
Step 7.4 — Data Import Framework
Create the membership-import module.
(reference: Chapter 15 — Migration Strategy)
Approach:
- Create JPA entities:
ImportJob fields:
| Field | Type | Constraints | Description |
|---|---|---|---|
| id | Long | PK, generated | |
| entityId | Long | FK NOT NULL | Organization |
| templateId | Long | FK NOT NULL | Import template used |
| fileName | String(255) | NOT NULL | Original CSV filename |
| fileStoragePath | String(500) | NOT NULL | S3/MinIO path to uploaded file |
| status | String(20) | default 'PENDING' | PENDING / VALIDATING / PREVIEWING / EXECUTING / COMPLETED / FAILED |
| totalRows | Integer | default 0 | Total rows in CSV (excl. header) |
| validRows | Integer | default 0 | Rows passing validation |
| importedRows | Integer | default 0 | Successfully imported rows |
| errorRows | Integer | default 0 | Rows with validation errors |
| errorReportPath | String(500) | nullable | S3 path to error CSV |
| startedAt | Instant | nullable | Processing start time |
| completedAt | Instant | nullable | Processing end time |
| createdBy | Long | FK NOT NULL | User who initiated |
| createdAt | Instant | auto | |
| version | Long | @Version |
ImportTemplate fields:
| Field | Type | Constraints | Description |
|---|---|---|---|
| id | Long | PK, generated | |
| name | String(200) | NOT NULL | Template display name (e.g., "easyVerein Members") |
| sourceSystem | String(50) | NOT NULL | EASY_VEREIN / CLUB_DESK / MAGICLINE / SPORTSCLUB / GENERIC |
| targetEntity | String(50) | NOT NULL | MEMBER / MEMBER_AND_CONTRACT / MEMBER_AND_BANK_ACCOUNT |
| columnMappings | JSONB | NOT NULL | [{"sourceColumn": "Vorname", "targetField": "firstName", "transform": null}, ...] |
| validationRules | JSONB | default '[]' | [{"field": "email", "rule": "EMAIL_FORMAT"}, {"field": "iban", "rule": "IBAN_CHECKSUM"}, ...] |
| dateFormat | String(30) | default 'yyyy-MM-dd' | Date parsing format (e.g., dd.MM.yyyy for German CSVs) |
| delimiter | String(1) | default ',' | CSV delimiter (; common in DACH) |
| encoding | String(20) | default 'UTF-8' | File encoding |
| isSystemTemplate | boolean | default false | True = pre-defined (seed), false = user-created |
| createdAt | Instant | auto | |
| version | Long | @Version | |
2. Create CsvParserService: |
|||
| - Apache Commons CSV parser with configurable delimiter, quote char, encoding | |||
| - Header detection (first row), encoding auto-detection (UTF-8, ISO-8859-1, Windows-1252) | |||
| - Streaming: process file row-by-row (no full file in memory) | |||
3. Create MappingEngine: |
|||
- Column mapping: source column -> target field (from ImportTemplate.columnMappings) |
|||
| - Value transformations: date format conversion, status mapping, enum mapping | |||
| - Computed fields: full name = firstName + " " + lastName | |||
4. Create ValidationPipeline: |
|||
| - Required field checks | |||
| - Format validation: email, IBAN, date, phone (libphonenumber) | |||
| - Referential integrity: membership template exists, organization exists | |||
| - Duplicate detection: email, member number | |||
| - Per-row error collection (not fail-fast) | |||
5. Create ImportExecutor: |
|||
| - Dry-run mode: Validate all rows, report errors, create NO records | |||
| - Execute mode: After dry-run approval, create records in transaction batches (100 rows per transaction) | |||
- Progress tracking via ImportJob.importedRows (updated every batch) |
|||
| - Error report: CSV file with original row + error column | |||
6. RabbitMQ: import.process queue for async execution |
|||
| 7. Create controller: | |||
- POST /api/import/upload — Upload CSV file, create ImportJob |
|||
- POST /api/import/{jobId}/validate — Run dry-run validation |
|||
- GET /api/import/{jobId}/preview — Get validation results (errors, preview of first 10 rows) |
|||
- POST /api/import/{jobId}/execute — Execute import (after dry-run approval) |
|||
- GET /api/import/{jobId}/status — Get import progress |
|||
- GET /api/import/{jobId}/errors — Download error report CSV |
|||
- GET /api/import/templates — List available import templates |
|||
8. Create Flyway: V701__create_import.sql |
|||
9. Create tests: CsvParserServiceTest, MappingEngineTest, ValidationPipelineTest, ImportExecutorTest |
Result: Complete import framework with CSV parsing, mapping, validation, and dry-run.
Step 7.5 — Pre-Defined Import Templates
(reference: Chapter 15 — Migration Strategy)
Approach:
Create 5 competitor import templates as seed data:
| # | Source System | Target | Mapped Fields | Notes |
|---|---|---|---|---|
| 1 | easyVerein | Member + Contract | 15 fields: name, email, address, phone, birthdate, IBAN, memberNumber, joinDate, membershipType, status, notes, emergencyContact, gender, newsletter, customFields | Most common competitor in DACH |
| 2 | ClubDesk | Member + Contract | 12 fields: name, email, address, phone, memberType, entryDate, exitDate, bankAccount, memberNumber, notes, status, category | Popular in Switzerland |
| 3 | Magicline | Member + Contract + BankAccount | 20 fields: fullName, email, phone, street, zip, city, country, dateOfBirth, gender, iban, bic, contractName, startDate, endDate, monthlyFee, paymentMethod, status, barcode, photo, notes | Fitness-specific (largest competitor) |
| 4 | SPORTSCLUB | Member + Contract | 14 fields: firstName, lastName, email, mobile, address, birthDate, memberSince, membershipName, fee, paymentType, accountHolder, iban, bic, status | Sports club specific |
| 5 | Generic CSV | Member | User maps columns manually | Fallback for any system |
Each template includes: - Column mapping configuration (JSONB) - Value transformation rules (status codes, date formats) - Sample CSV for testing
Result: 5 ready-to-use import templates covering 80%+ of market.
Step 7.6 — Admin Import UI
(reference: Chapter 08 — User Journeys)
Approach:
-
Import Wizard (4 steps): - Step 1 — Upload: Drag-and-drop CSV, select import template (or Generic), upload - Step 2 — Mapping: If Generic template: column mapping UI (source column dropdown -> target field). If pre-defined: show mapping summary, allow overrides - Step 3 — Preview: Show dry-run results: total rows, valid rows (green), error rows (red with details). Download error report. "Fix and re-upload" or "Import valid rows only" options - Step 4 — Execute: Progress bar (X of Y rows), status updates, completion summary
-
Import history: list of past import jobs with status, row counts, download links
Result: User-friendly import wizard with mapping, preview, and progress tracking.
Step 7.7 — Phase 7 Documentation
- Update
doc/enduser/admin-manual.md(document management, data import sections) - Update
doc/enduser/consumer-manual.md(document access) - Update
doc/developer/backend-guide.md(document and import module architecture) - Update
doc/developer/api-reference.md(new endpoints)
Step 7.8 — Update Intranet
Run python doc/intranet/build.py.
Phase 7 — Quality Gate
| # | Check | Target |
|---|---|---|
| 1 | Conception | exists |
| 2 | Compilation + tests | 0 errors, 0 failures |
| 3 | Coverage | >= 70% |
| 4 | Document upload/download | works with access control |
| 5 | Contract PDF | generated with entity branding |
| 6 | Invoice PDF | includes VAT breakdown |
| 7 | CSV import dry-run | validates all 5 templates |
| 8 | CSV import execute | creates records correctly |
| 9 | Import error report | downloadable with row details |
| 10 | Documentation + intranet | updated |
| 11 | CLAUDE.md | updated |
Report: "Phase 7 completed."