<description><![CDATA[Verwaltung von Mitgliedern, Familien, Beitraegen, Lagern und mehr fuer Pfadfindervereine. Integriert sich in Nextcloud Kalender, Kontakte und Dateien.]]></description>
# Mitgliederverwaltung für Pfadfindervereine — Requirements & Architecture
**Version:** 2.0
**Status:** Draft
**Last updated:** 2026-04-07
## 1. Project Overview
A custom Nextcloud app (NC 28+) for managing scout group (Pfadfinderverein) members. Serves as the **single source of truth** for all member data, deeply integrated into the Nextcloud ecosystem (Calendar, Contacts, Files). German-language UI only. Designed for potential future open-sourcing but not prioritized.
| Ansprechpartner | relation | → one or more Erziehungsberechtigter members |
**Family ↔ Member link:**
- A member belongs to 0 or 1 family
- Role within family: `Kind` or `Erziehungsberechtigter`
- Members have their own address(es) — no shared family address (since separated parents need multiple addresses)
- Members can override the family banking with their own
**Active children count** — only active members with role `Kind` in the family. Drives fee discount calculation.
### 2.3 Stufen Management
- Configurable list of Stufen (default: **Wölflinge, Pfadfinder, Rover**)
- Each Stufe has: Name, typical age range (display only), optional color
- Members are assigned to exactly one Stufe at a time
- Stufe history tracked (when a member moved between Stufen, with date)
- **Suggested Stufe changes:** The app shows a notification/list when members reach the typical age boundary for the next Stufe. Stufenwechsel is always manual — the suggestion is advisory only.
### 2.4 Fee System (Beitragsverwaltung)
**Billing period:** Calendar year (January–December)
**Collection:** Manually, typically in January
**No partial payments:** Fees are always paid in full
**No proration:** Members who join mid-year pay the full amount for that year
**No refund on exit:** Members who leave mid-year do not get refunds
**Fee model — key complexity:**
- Fee amounts can change year to year (Vorstand decides a new rate)
- **Active members** pay the current rate
- **Inactive members** (who still pay) continue paying the rate that was current when they became inactive — they are not affected by fee increases
- **Erziehungsberechtigte** (non-paying parents) are excluded entirely
**Family discount — configurable DSL:**
The fee rules must be configurable to express logic like:
```
1 Kind in Familie → 60€
2 Kinder in Familie → 120€ (= 2 × 60€)
3+ Kinder in Familie → 120€ (3., 4., ... Kind kostenlos)
```
**DSL approach:** A small rule table in the settings, structured as:
| Anzahl aktive Kinder | Gesamtbeitrag | Notiz |
|---|---|---|
| 1 | 60.00 | Einzelkind |
| 2 | 120.00 | 2 Kinder |
| 3+ | 120.00 | Ab 3. Kind kostenlos |
This is stored as a JSON array in the app config. The fee engine reads this table and looks up the total family fee based on the count of active children, then divides appropriately (or assigns per-child amounts as configured).
**Alternative: per-member rule approach** for more flexibility:
```json
{
"base_rate":60.00,
"family_rules":[
{"child_number":1,"rate":60.00},
{"child_number":2,"rate":60.00},
{"child_number":"3+","rate":0.00}
],
"inactive_rule":"freeze_rate_at_exit"
}
```
The engine processes:
1. Get all active Kinder in the family, sorted by age (oldest first)
2. Assign each child the rate for their position (1st, 2nd, 3rd+)
3. For inactive members: look up their `frozen_rate` (stored when they became inactive)
| Bankverbindungen | for manual SEPA processing (restricted access) |
| Familienliste | families with their members |
| Lagerhistorie | which camps a member attended (see 2.9) |
| Verletzungsprotokoll | injury records for a given period or member (see 2.10) |
**Any saved query can be exported** as PDF or CSV directly.
**PDF generation:** Use a PHP PDF library (TCPDF or FPDF) server-side. Reports have a configurable header with Verein name/logo.
**CSV export:** Standard CSV with UTF-8 BOM for Excel compatibility.
**Encrypted export:** When exporting sensitive data (banking, full member details), the export file must be encrypted. Options: password-protected ZIP or GPG-encrypted file. The user is prompted for a password before download.
### 2.7 Data Import
- CSV/Excel import wizard for initial migration
- Column mapping UI: user maps source columns to member fields
- Preview before import with validation errors highlighted
- Support creating families from import data (e.g. grouping by shared Nachname + Adresse)
- Dry-run mode: show what would be created without committing
- Duplicate detection on import (name + birthday match)
### 2.8 Membership Milestones (Jubiläen)
- **Mitgliedsdauer** is calculated from Eintritt and displayed on member records
- The system flags upcoming milestones: **25, 50, 60, 70 Jahre** Mitgliedschaft (configurable list)
- A dedicated view/report: "Anstehende Jubiläen" shows members reaching a milestone in the current or upcoming year
- No automatic notifications for now — just a visible list/report
### 2.9 Lagertracking (Camp Management)
Each Lager (camp/event) is a standalone entity that can span multiple Stufen.
| Field | Type | Notes |
|---|---|---|
| Name | text | e.g. "Sommerlager Trupp 2026" |
| Startdatum | date | required |
| Enddatum | date | required |
| Ort | text | location/address |
| Stufen | enum[] | which Stufen participated (multi-select) |
| Teilnehmer | relation[] | → members who attended |
| Beschreibung | richtext | optional notes |
| Dateien | file links[] | links to files in Nextcloud (Kostenabrechnung, Einladung, Zeitplan, etc.) |
**Features:**
- CRUD for Lager with a form UI
- Add/remove members as Teilnehmer (with search/autocomplete)
- Link files from Nextcloud Files (from a configurable Lager base folder, or arbitrary paths)
- **Per-member camp history:** on a member's detail page, show all Lager they attended
- **Export:** "Lagerhistorie für [Mitglied]" — list of all camps attended with dates and roles
- **Lager overview:** list of all past and upcoming camps, filterable by year, Stufe
### 2.10 Injury Tracking (Verletzungsprotokoll)
Simple text-based injury log linked to members and optionally to a Lager.
| Field | Type | Notes |
|---|---|---|
| Datum | date | when the injury occurred |
| Betroffenes Mitglied | relation | → member who was injured |
| Beteiligte | relation[] | → other members involved (optional) |
| Lager/Aktivität | relation | → Lager (optional) or freetext activity name |
| Beschreibung | richtext | what happened, what was done |
| Erstellt von | auto | Nextcloud user who logged the entry |
**Features:**
- Log entries from member detail page or from Lager detail page
- View all injuries for a member, or all injuries for a Lager
- Exportable as report (PDF/CSV) for a date range or per member
- Audit-logged like all other data changes
---
## 3. Nextcloud Integrations
### 3.1 Calendar Integration (CalDAV)
- App creates/manages a dedicated calendar (e.g. "Pfadfinder Geburtstage")
- For each active member with role `Mitglied` (NOT Erziehungsberechtigter):
- A recurring yearly all-day event on their birthday
- Event title format: **"🎂 Vorname Nachname (*Geburtsjahr)"** — e.g. "🎂 Max Müller (*2012)"
- The birth year is static in the title; the calendar app itself shows which anniversary it is
- No current age in the title — it would require annual updates
- **Sync behavior:**
- Member created (role=Mitglied) → calendar event created
- Birthday changed → event updated
- Member deactivated (Austritt set) → event deleted
- Member reactivated → event re-created
- Role changed to/from Erziehungsberechtigter → event created/deleted accordingly
- Use Nextcloud's `\OCA\DAV\CalDAV\CalDavBackend` or the internal CalDAV API
### 3.2 Contacts Integration (CardDAV)
- App creates/manages a dedicated address book (e.g. "Pfadfinder Mitglieder")
- For each active member (including Erziehungsberechtigte): a VCard with name, address(es), phone, email, birthday
- **Sync behavior:** same pattern as calendar — create/update/delete on member changes
- Use Nextcloud's `\OCA\DAV\CardDAV\CardDavBackend`
### 3.3 Files Integration
- Each member is linked to a **folder** in Nextcloud Files
- **Configurable base path** in app settings (e.g. `/Verein/Mitglieder/`)
- **Configurable subfolder pattern** (e.g. `{Nachname}_{Vorname}/` or `{Nachname}, {Vorname}/`)
- The member folder can contain arbitrary files: Mitgliederantrag, medical docs, correspondence, etc.
- The app shows the folder contents inline on the member detail page and allows navigating to Nextcloud Files
- Optional: "Datei hochladen" button that uploads directly into the member's folder
- Optional: auto-create member subfolder on member creation (configurable toggle)
- **Lager files** follow a similar pattern: configurable Lager base path + subfolder per Lager
- Uses Nextcloud's `\OCP\Files\IRootFolder` API
---
## 4. Non-Functional Requirements
### 4.1 Access Control
- Uses Nextcloud's built-in user/group system for authentication
- App-level permission model, **configurable per Nextcloud user**:
| Entität | member / family / fee_record / lager / injury / setting / permission |
| Entität-ID | ID of affected record |
| Feld | which field changed (for updates) |
| Alter Wert | previous value (encrypted fields show "[verschlüsselt]") |
| Neuer Wert | new value (encrypted fields show "[verschlüsselt]") |
- Viewable by Admin users in the app
- Filterable by entity, user, date range
- Audit log entries are immutable — no editing or deletion
- Retention: keep indefinitely (configurable max age as future option)
### 4.3 Security Requirements
**This is a high-priority area. The app handles sensitive personal data (minors, banking, health/allergy info).**
**Data at rest:**
- IBAN and Kontoinhaber encrypted using `\OCP\Security\ICrypto`
- Consider encrypting Allergien and medical notes as well
- Soft-deleted member data: banking, allergies, and sensitive notes are **hard-deleted** on soft-delete. Only name, Eintritt, Austritt, and Mitgliedsdauer are retained.
**Data in transit:**
- All API endpoints require Nextcloud authentication (no anonymous access)
- CSRF protection on all state-changing endpoints
- API input validation and sanitization on every endpoint
**Application security:**
- Parameterized queries only (via QBMapper) — no raw SQL
- Output escaping in Vue templates (Vue's default behavior)
- Rate limiting on search/export endpoints to prevent data scraping
- No sensitive data in URLs or browser logs
- Session timeout follows Nextcloud's settings
**Export security:**
- Exports containing banking or sensitive data must be encrypted (password-protected ZIP or GPG)
- Export actions are audit-logged
**Testing requirements:**
- Unit tests for fee calculation engine, permission checks, validation logic
- Integration tests for API endpoints (auth, CRUD, edge cases)
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.