Files
Mitgliederverwaltung/docs/requirements.md
T
shahondin1624 fbd3b1cb30 Initial scaffold for Mitgliederverwaltung Nextcloud app
Directory structure, minimal skeleton files (info.xml, Application.php,
routes.php, templates, Vue entry point), and requirements doc.
No implementation yet.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 10:56:26 +02:00

30 KiB
Raw Blame History

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.

Target scale: 50200 members, 310 admin users
Database: MySQL/MariaDB (shared with Nextcloud instance)
Tech stack: PHP backend (Nextcloud App Framework), Vue.js 3 frontend
App ID: mitgliederverwaltung
Security posture: High — coded to latest security standards, thoroughly tested and pentested. Banking data encrypted at rest.


2. Functional Requirements

2.1 Member Management (Mitgliederverwaltung)

Each member record contains:

Field Type Notes
Vorname, Nachname text required
Geburtsdatum date required; age auto-calculated
Geschlecht enum optional
Rolle enum Mitglied or Erziehungsberechtigter
Stufe enum Wölflinge, Pfadfinder, Rover (configurable list)
Eintritt date required; for Mitgliedsdauer calculation
Austritt date nullable; if set → inactive member
Status enum aktiv, inaktiv, gelöscht (soft-deleted)
Allergien text freeform, visible on reports
Notizen richtext general notes
Zusätzliche Notizen richtext secondary notes (e.g. medical, behavioral)
E-Mail text optional, multiple allowed
Telefon text E.164 format enforced (+49...), multiple allowed
Adressen structured[] multiple addresses allowed (e.g. separated parents). Fields: Label, Straße, PLZ, Ort, Land
Bankverbindung relation → Family banking (shared) or individual override
Mitgliederordner file link link to folder in Nextcloud Files (configurable path pattern)
Mitgliederantrag file link specific file within member folder

Derived/computed fields:

  • Alter — calculated from Geburtsdatum, displayed everywhere, updated daily
  • Mitgliedsdauer — years and months since Eintritt. Used to trigger Jubiläum milestones (see 2.8)
  • Beitragsstatus — current year paid/unpaid

Erziehungsberechtigter (non-paying parent) role:

  • Linked to family, contact details stored (phone, email, address)
  • Does NOT get a birthday calendar event
  • Does NOT appear in fee calculations
  • DOES appear in contact lists and family views
  • DOES get a Nextcloud Contact entry

2.2 Family Management (Familienverwaltung)

Families group members who share banking details and optionally addresses.

Field Type Notes
Familienname text e.g. "Familie Müller"
Bankverbindung structured Kontoinhaber, IBAN (validated + encrypted), BIC, Kreditinstitut
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 (JanuaryDecember)
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:

{
  "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)

Fee records:

  • One record per member per year
  • Fields: year, calculated amount, rule applied (snapshot), paid (yes/no), payment date, notes
  • Flag: manuell_angepasst — if an admin overrides the calculated fee, it is never auto-overwritten
  • Batch action: "Beiträge für Jahr X berechnen" generates/updates all records (skips manually adjusted ones)
  • When a member becomes inactive: store frozen_rate on their record (the rate they'll continue paying)

2.5 Search, Filtering & Saved Queries

Global search bar: Full-text search across Vorname, Nachname, Familienname, Adressen, Notizen, Allergien

Filter/query system — granular and saveable:

Admins can build complex queries using a visual query builder:

  • Fields: any member/family field (Alter, Stufe, Status, Eintritt, Austritt, Beitragsstatus, Mitgliedsdauer, Rolle, Adresse.Ort, etc.)
  • Operators: =, , <, >, <=, >=, enthält, beginnt mit, ist leer, ist nicht leer
  • Logic: AND / OR grouping with nesting
  • Examples:
    • "Alle aktiven Mitglieder unter 14 Jahren" → Status = aktiv AND Alter < 14
    • "Unbezahlte Beiträge 2026" → Beitragsstatus(2026) = unbezahlt
    • "Rover ohne E-Mail" → Stufe = Rover AND E-Mail ist leer
    • "Mitglieder aus PLZ 8xxxx" → Adresse.PLZ beginnt mit 8

Saved queries:

  • Each query can be saved with a user-defined name (e.g. "Minderjährige aktive Mitglieder")
  • Saved queries appear as quick-access buttons/list in the sidebar
  • Saved queries are shared across all admins (with appropriate permission level)
  • Sort order is part of the saved query

Preset quick filters:

  • "Aktive Mitglieder"
  • "Inaktive Mitglieder"
  • "Unbezahlte Beiträge"
  • "Geburtstage diesen Monat"

2.6 Reports & Exports

All reports available as PDF and CSV.

Report Content
Mitgliederliste all or filtered members with key fields
Beitragsliste per year: who owes what, who has paid, totals
Stufenliste members grouped by Stufe
Allergieliste all members with allergies (for camps/events)
Geburtstagsliste upcoming birthdays
Kontaktliste addresses + phone numbers, optionally per Stufe
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:
Permission level Can do
Kein Zugriff App not visible
Lesezugriff View members, search, generate reports (no banking details)
Stufenzugriff Read/write for members in assigned Stufe(n) only
Vollzugriff Full CRUD on all data
Admin Vollzugriff + app settings, fee rules, user permissions, import
  • Banking details (IBAN etc.) are a separate visibility flag — can be hidden even from Vollzugriff users
  • Injury records: visible to all users with Lesezugriff or above
  • Permission management UI in app settings (Admin only)

4.2 Audit Log

Full audit trail stored in a dedicated DB table:

Field Content
Zeitpunkt timestamp
Benutzer Nextcloud user who made the change
Aktion create / update / delete / soft-delete
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)
  • Security-specific tests: injection attempts, permission bypass, CSRF
  • Manual pentest before production deployment (or use automated tools like OWASP ZAP)

Dependency management:

  • Pin all PHP and JS dependencies to exact versions
  • Regular dependency audit (Composer audit, npm audit)

4.4 Data Validation

  • IBAN validation (checksum + country format)
  • Phone numbers: E.164 format enforced (+49...), validated on input with helpful formatting hints
  • Date plausibility (birthday not in future, not >120 years ago; Eintritt not before birthday)
  • Required field enforcement on save
  • Duplicate detection: warn when creating a member with matching Vorname + Nachname + Geburtsdatum

4.5 Soft Deletion & Data Retention

When a member is soft-deleted:

  • Retained: Vorname, Nachname, Geburtsdatum, Eintritt, Austritt, Mitgliedsdauer, Stufe history, Lager attendance, fee history
  • Hard-deleted: Banking details (IBAN, Kontoinhaber, BIC), Allergien, Notizen, Zusätzliche Notizen, Adressen, Telefon, E-Mail
  • Calendar: birthday event removed
  • Contacts: VCard removed
  • Files: member folder is NOT deleted (manual cleanup)
  • Soft-deleted members are hidden from normal views but visible in an "Archiv" view (Admin only)
  • A "DSGVO-Löschung" action performs full hard-delete including retained fields (irreversible, audit-logged)

4.6 DSGVO / Data Privacy

  • Auskunftsrecht: Export all stored data for a single member as encrypted PDF/JSON
  • Recht auf Löschung: Full hard-delete action (see 4.5)
  • Einwilligung: Optional date field per member for date of data processing consent
  • Datenschutzhinweis: Configurable text shown during member creation (informational)

5. Architecture Hints

5.1 Nextcloud App Structure

mitgliederverwaltung/
├── appinfo/
│   ├── info.xml              ← app metadata, NC 28+ constraint
│   └── routes.php            ← API route definitions
├── lib/
│   ├── AppInfo/
│   │   └── Application.php   ← app bootstrap, DI container
│   ├── Controller/
│   │   ├── MemberController.php
│   │   ├── FamilyController.php
│   │   ├── FeeController.php
│   │   ├── LagerController.php
│   │   ├── InjuryController.php
│   │   ├── ReportController.php
│   │   ├── ImportController.php
│   │   ├── QueryController.php      ← saved queries CRUD
│   │   └── SettingsController.php
│   ├── Db/
│   │   ├── Member.php / MemberMapper.php
│   │   ├── Family.php / FamilyMapper.php
│   │   ├── FeeRecord.php / FeeRecordMapper.php
│   │   ├── FeeRule.php / FeeRuleMapper.php
│   │   ├── Lager.php / LagerMapper.php
│   │   ├── LagerTeilnehmer.php / LagerTeilnehmerMapper.php
│   │   ├── Injury.php / InjuryMapper.php
│   │   ├── SavedQuery.php / SavedQueryMapper.php
│   │   ├── StufeHistory.php / StufeHistoryMapper.php
│   │   ├── AuditLog.php / AuditLogMapper.php
│   │   └── Permission.php / PermissionMapper.php
│   ├── Service/
│   │   ├── MemberService.php
│   │   ├── FamilyService.php
│   │   ├── FeeCalculationService.php  ← fee rules engine
│   │   ├── CalendarSyncService.php
│   │   ├── ContactsSyncService.php
│   │   ├── FileLinkService.php
│   │   ├── LagerService.php
│   │   ├── InjuryService.php
│   │   ├── ReportService.php
│   │   ├── ImportService.php
│   │   ├── QueryService.php           ← query builder → SQL
│   │   ├── AuditService.php
│   │   ├── PermissionService.php
│   │   ├── EncryptionService.php      ← wraps ICrypto for field-level encryption
│   │   └── MilestoneService.php       ← Jubiläen detection
│   └── Migration/
│       └── Version000001Date*.php
├── src/                       ← Vue.js 3 frontend
│   ├── main.js
│   ├── App.vue
│   ├── router.js
│   ├── stores/                ← Pinia
│   │   ├── members.js
│   │   ├── families.js
│   │   ├── fees.js
│   │   ├── lager.js
│   │   └── queries.js
│   ├── views/
│   │   ├── MemberList.vue
│   │   ├── MemberDetail.vue   ← tabbed: Persönlich, Familie, Beitrag, Lager, Verletzungen, Dateien, Verlauf
│   │   ├── FamilyList.vue
│   │   ├── FamilyDetail.vue
│   │   ├── FeeOverview.vue
│   │   ├── LagerList.vue
│   │   ├── LagerDetail.vue
│   │   ├── Reports.vue
│   │   ├── Import.vue
│   │   ├── QueryBuilder.vue
│   │   ├── Milestones.vue
│   │   ├── AuditLog.vue
│   │   └── Settings.vue
│   └── components/
│       ├── MemberForm.vue
│       ├── FamilyForm.vue
│       ├── SearchBar.vue
│       ├── QueryBuilder.vue   ← visual AND/OR filter builder
│       ├── LagerForm.vue
│       ├── InjuryForm.vue
│       ├── PhoneInput.vue     ← E.164 formatter
│       └── FileExplorer.vue   ← inline NC file browser
├── templates/
│   └── index.php
├── tests/
│   ├── Unit/
│   │   ├── FeeCalculationServiceTest.php
│   │   ├── PermissionServiceTest.php
│   │   └── ...
│   └── Integration/
│       ├── MemberControllerTest.php
│       └── ...
├── css/
└── img/

5.2 Database Schema (Key Tables)

-- Members
oc_mv_members (
  id, vorname, nachname, geburtsdatum, geschlecht,
  rolle ENUM('mitglied', 'erziehungsberechtigter'),
  stufe_id, eintritt, austritt,
  status ENUM('aktiv', 'inaktiv', 'geloescht'),
  allergien_encrypted, notizen, zusatz_notizen,
  family_id FK, frozen_fee_rate,
  calendar_event_uri, contact_vcard_uri,
  created_at, updated_at, deleted_at
)

-- Addresses (multiple per member)
oc_mv_addresses (
  id, member_id FK, label, strasse, plz, ort, land, is_primary
)

-- Phone numbers (multiple per member, E.164)
oc_mv_phones (
  id, member_id FK, label, number_e164
)

-- Emails (multiple per member)
oc_mv_emails (
  id, member_id FK, label, email
)

-- Families
oc_mv_families (
  id, name, kontoinhaber_encrypted, iban_encrypted, bic, kreditinstitut,
  created_at, updated_at
)

-- Fee rules (JSON config, versioned)
oc_mv_fee_rules (
  id, year_from, base_rate, family_rules_json, inactive_rule,
  created_at, created_by
)

-- Fee records
oc_mv_fee_records (
  id, member_id FK, year, amount, rule_snapshot_json,
  manuell_angepasst, paid, payment_date, notes,
  created_at, updated_at
)

-- Stufen
oc_mv_stufen (
  id, name, sort_order, age_range_min, age_range_max, color
)

-- Stufe history
oc_mv_stufe_history (
  id, member_id FK, stufe_id FK, changed_at, changed_by
)

-- Lager
oc_mv_lager (
  id, name, startdatum, enddatum, ort, beschreibung,
  files_folder_path, created_at, updated_at
)

-- Lager ↔ Stufen (m:n)
oc_mv_lager_stufen (
  lager_id FK, stufe_id FK
)

-- Lager ↔ Members (m:n)
oc_mv_lager_teilnehmer (
  lager_id FK, member_id FK, rolle TEXT -- e.g. 'Teilnehmer', 'Leitung'
)

-- Lager files
oc_mv_lager_files (
  id, lager_id FK, file_path, label -- e.g. 'Kostenabrechnung', 'Zeitplan'
)

-- Injuries
oc_mv_injuries (
  id, datum, member_id FK, lager_id FK NULL,
  activity_name, beschreibung, created_by, created_at
)

-- Injury involved members (m:n)
oc_mv_injury_involved (
  injury_id FK, member_id FK
)

-- Saved queries
oc_mv_saved_queries (
  id, name, query_json, sort_json, created_by, created_at
)

-- Permissions
oc_mv_permissions (
  id, nc_user_id, level ENUM('none','read','stufe','full','admin'),
  allowed_stufen_json, can_see_banking BOOLEAN
)

-- Audit log (append-only)
oc_mv_audit_log (
  id, zeitpunkt, nc_user_id, aktion, entitaet, entitaet_id,
  feld, alter_wert, neuer_wert
)

5.3 Key Technical Decisions

Calendar/Contacts sync strategy:

  • Don't sync on every save — queue sync jobs instead
  • A TimedJob (every 5 min) processes the queue
  • A nightly full-sync catches drift
  • Store CalDAV event URI and CardDAV vCard URI on member records for efficient updates

Fee calculation engine:

  • Fee rules stored in oc_mv_fee_rules with a year_from field (rules apply from that year until a newer rule exists)
  • Calculation is a pure function: (member, family, rules_for_year) → fee_amount
  • Inactive members: use their frozen_fee_rate instead of current rules
  • Batch action: iterates all active+paying members, skips manuell_angepasst records
  • Rule snapshots: each fee_record stores the rule that was applied as JSON (immutable audit trail)

Saved query engine:

  • Queries stored as JSON AST: { "and": [{ "field": "alter", "op": "<", "value": 14 }, { "field": "status", "op": "=", "value": "aktiv" }] }
  • QueryService translates AST → Doctrine DBAL QueryBuilder conditions
  • Validate field names against a whitelist to prevent injection
  • Support nested AND/OR groups

File linking:

  • Store file/folder paths (relative to base path), not file IDs
  • Validate existence when displaying; show warning if missing
  • Auto-create member subfolder on member creation (configurable)

Encryption:

  • Dedicated EncryptionService wrapping ICrypto
  • Encrypts: IBAN, Kontoinhaber, Allergien
  • Decrypt only when rendering for a user with appropriate permission
  • Audit log shows [verschlüsselt] for encrypted field changes

Soft deletion:

  • deleted_at timestamp + status = 'geloescht'
  • On soft-delete: hard-delete banking, allergies, notes, addresses, phones, emails
  • Retain: name, dates, Stufe history, Lager attendance, fee records
  • Full DSGVO delete: remove everything including the retained fields

5.4 Frontend Architecture

  • Vue 3 + Composition API
  • Pinia for state management
  • @nextcloud/vue component library (NcButton, NcModal, NcTextField, NcSelect, NcDateTimePicker, NcAppNavigation, NcAppContent, etc.)
  • @nextcloud/axios for API calls (handles CSRF)
  • @nextcloud/router for URL generation

Key UI flows:

  1. Member list — table with global search + saved query sidebar. Click row → detail view
  2. Member detail — tabbed: Persönlich, Familie, Beitrag, Lager, Verletzungen, Dateien, Verlauf
  3. Family view — family card with all linked members, banking details
  4. Fee overview — year selector, table with amounts/paid status, export
  5. Lager list — past and upcoming, filterable by year/Stufe. Click → detail with participants, files
  6. Query builder — visual filter builder with AND/OR nesting, save/load queries
  7. Reports — choose type, apply filters, preview, download PDF/CSV (with encryption prompt for sensitive data)
  8. Import wizard — upload → column mapping → preview → confirm
  9. Milestones — upcoming Jubiläen list
  10. Settings — Stufen, fee rules (DSL editor), file paths, user permissions
  11. Audit log — searchable/filterable log table (Admin only)

Build in this order for a usable system at each stage:

  1. Skeleton — NC app scaffold, empty Vue app loads in Nextcloud, DB migrations run
  2. Members CRUD — create/read/update members with polished form UI, table view
  3. Search & basic filter — global search, preset quick filters
  4. Families — family CRUD, link members, shared banking (encrypted)
  5. Stufen — configurable list, assign to members, history tracking
  6. Permissions — per-user access control, banking visibility flag
  7. Audit log — full change tracking, viewer UI
  8. Fee engine — configurable rules (DSL), batch calculation, overview UI
  9. Reports — PDF + CSV generation, encrypted exports
  10. Calendar sync — birthday events for Mitglieder (not Erziehungsberechtigte)
  11. Contacts sync — address book for all active members
  12. Files integration — member folders, configurable paths, inline viewer
  13. Saved queries — visual query builder, save/load/share
  14. Lagertracking — Lager CRUD, participants, files, per-member history
  15. Injury tracking — log entries, per-member and per-Lager views
  16. Import wizard — CSV/Excel import with mapping and duplicate detection
  17. Milestones — Jubiläen detection and view
  18. Soft deletion & DSGVO — soft-delete logic, data export, hard-delete
  19. Stufe suggestions — advisory notifications based on age ranges
  20. Security hardening — pentest, dependency audit, rate limiting, final review

5.6 Useful Nextcloud APIs

Need API
DB queries \OCP\AppFramework\Db\QBMapper
DB migrations \OCP\Migration\SimpleMigrationStep
Current user \OCP\IUserSession
Background jobs \OCP\BackgroundJob\TimedJob
Calendar \OCA\DAV\CalDAV\CalDavBackend
Contacts \OCA\DAV\CardDAV\CardDavBackend
Files \OCP\Files\IRootFolder
Encryption \OCP\Security\ICrypto
Logging \Psr\Log\LoggerInterface
App config \OCP\IConfig
CSRF @NoCSRFRequired annotation

Docs: https://docs.nextcloud.com/server/latest/developer_manual/
@nextcloud/vue: https://nextcloud-vue-components.netlify.app/

5.7 Resolved Decisions

# Question Decision
1 Family discount rules Configurable via DSL/rule table
2 Proration / refund on exit No proration, no refund
3 App name mitgliederverwaltung
4 Stufe auto-advance Manual only, with advisory suggestions
5 Multiple Vereine One Verein per NC instance
6 Notifications Not for now
7 Backup Standard NC/DB backup; exports encrypted
8 Phone format E.164 enforced (+49...)
9 Multiple addresses Yes, multiple per member
10 Archiving Soft-delete: retain name/dates, hard-delete sensitive data