mitgliederverwaltung/docs/contributions-overview.md
2025-12-03 14:32:09 +01:00

14 KiB

Membership Contributions - Overview

Project: Mila - Membership Management System
Feature: Membership Contribution Management
Version: 1.0
Last Updated: 2025-11-27
Status: Concept - Ready for Review


Purpose

This document provides a comprehensive overview of the Membership Contributions system. It covers business logic, data model, UI/UX design, and technical architecture in a concise, bullet-point format.

For detailed implementation: See contributions-implementation-plan.md (created after concept iterations)


Table of Contents

  1. Core Principle
  2. Terminology
  3. Data Model
  4. Business Logic
  5. UI/UX Design
  6. Edge Cases
  7. Technical Integration
  8. Implementation Scope

Core Principle

Maximum Simplicity:

  • Minimal complexity
  • Clear data model without redundancies
  • Intuitive operation
  • Calendar period-based (Month/Quarter/Half-Year/Year)

Terminology

German ↔ English

Core Entities:

  • Beitragsart ↔ Contribution Type / Membership Fee Type
  • Beitragsintervall ↔ Contribution Period
  • Mitgliedsbeitrag ↔ Membership Fee / Contribution

Status:

  • bezahlt ↔ paid
  • unbezahlt ↔ unpaid
  • ausgesetzt ↔ suspended / waived

Intervals:

  • monatlich ↔ monthly
  • quartalsweise ↔ quarterly
  • halbjährlich ↔ half-yearly / semi-annually
  • jährlich ↔ yearly / annually

UI Elements:

  • "Letztes Intervall" ↔ "Last Period" (e.g., 2023 when in 2024)
  • "Aktuelles Intervall" ↔ "Current Period" (e.g., 2024)
  • "Als bezahlt markieren" ↔ "Mark as paid"
  • "Aussetzen" ↔ "Suspend" / "Waive"

Data Model

Contribution Type (ContributionType)

- id (UUID)
- name (String) - e.g., "Regular", "Reduced", "Student"
- amount (Decimal) - Contribution amount in Euro
- interval (Enum) - :monthly, :quarterly, :half_yearly, :yearly
- description (Text, optional)
- timestamps

Important:

  • interval is IMMUTABLE after creation!
  • Admin can only change name, amount, description
  • On change: Future unpaid periods regenerated with new amount

Contribution Period (ContributionPeriod)

- id (UUID)
- member_id (FK → members.id)
- contribution_type_id (FK → contribution_types.id)
- period_start (Date) - Calendar period start (01.01., 01.04., 01.07., 01.10., etc.)
- status (Enum) - :unpaid (default), :paid, :suspended
- amount (Decimal) - Amount at generation time (history when type changes)
- notes (Text, optional) - Admin notes
- timestamps

Important:

  • NO period_end - calculated from period_start + interval
  • NO interval_type - read from contribution_type.interval
  • Avoids redundancy and inconsistencies!

Calendar Period Logic:

  • Monthly: 01.01. - 31.01., 01.02. - 28./29.02., etc.
  • Quarterly: 01.01. - 31.03., 01.04. - 30.06., 01.07. - 30.09., 01.10. - 31.12.
  • Half-yearly: 01.01. - 30.06., 01.07. - 31.12.
  • Yearly: 01.01. - 31.12.

Member (Extensions)

- contribution_type_id (FK → contribution_types.id, NOT NULL, default from settings)
- contribution_start_date (Date, nullable) - When to start generating contributions
- left_at (Date, nullable) - Exit date (existing)

Logic for contribution_start_date:

  • Auto-set based on global setting include_joining_period
  • If include_joining_period = true: First day of joining month/quarter/year
  • If include_joining_period = false: First day of NEXT period after joining
  • Can be manually overridden by admin

NO include_joining_period field on Member - unnecessary due to contribution_start_date!

Global Settings

key: "contributions.include_joining_period" 
value: Boolean (Default: true)

key: "contributions.default_contribution_type_id"
value: UUID (Required) - Default contribution type for new members

Meaning include_joining_period:

  • true: Joining period is included (member pays from joining period)
  • false: Only from next full period after joining

Meaning default_contribution_type_id:

  • Every new member automatically gets this contribution type
  • Must be configured in admin settings
  • Prevents: Members without contribution type

Business Logic

Period Generation

Triggers:

  • Member gets contribution type assigned (also during member creation)
  • New period begins (Cron job daily/weekly)
  • Admin requests manual regeneration

Algorithm:

  1. Get member.contribution_start_date and member.contribution_type
  2. Calculate first period based on contribution_start_date
  3. Generate all periods from start to today (or left_at if present)
  4. Skip existing periods
  5. Set amount to current contribution_type.amount

Example (Yearly):

Joining date: 15.03.2023
include_joining_period: true
→ contribution_start_date: 01.01.2023

Generated periods:
- 01.01.2023 - 31.12.2023 (joining period)
- 01.01.2024 - 31.12.2024
- 01.01.2025 - 31.12.2025 (current year)

Example (Quarterly):

Joining date: 15.03.2023
include_joining_period: false
→ contribution_start_date: 01.04.2023

Generated periods:
- 01.04.2023 - 30.06.2023 (first full quarter)
- 01.07.2023 - 30.09.2023
- 01.10.2023 - 31.12.2023
- 01.01.2024 - 31.03.2024
- ...

Status Transitions

unpaid → paid
unpaid → suspended
paid → unpaid
suspended → paid
suspended → unpaid

Permissions:

  • Admin + Treasurer (Kassenwart) can change status
  • Uses existing permission system

Contribution Type Change

MVP - Same Interval Only:

  • Member can only choose contribution type with same interval
  • Example: From "Regular (yearly)" to "Reduced (yearly)" ✓
  • Example: From "Regular (yearly)" to "Reduced (monthly)" ✗

Logic on Change:

  1. Check: New contribution type has same interval
  2. If yes: Set member.contribution_type_id
  3. Future unpaid periods: Delete and regenerate with new amount
  4. Paid/suspended periods: Remain unchanged (historical amount)

Future - Different Intervals:

  • Enable interval switching (e.g., yearly → monthly)
  • More complex logic for period overlaps
  • Needs additional validation

Member Exit

Logic:

  • Periods only generated until member.left_at
  • Existing periods remain visible
  • Unpaid exit period can be marked as "suspended"

Example:

Exit: 15.08.2024
Yearly period: 01.01.2024 - 31.12.2024

→ Period 2024 is shown (Status: unpaid)
→ Admin can set to "suspended"
→ No periods for 2025+ generated

UI/UX Design

Member List View

New Column: "Contribution Status"

Default Display (Last Period):

  • Shows status of last completed period
  • Example in 2024: Shows contribution for 2023
  • Color coding:
    • Green: paid ✓
    • Red: unpaid ✗
    • Gray: suspended ⊘

Optional: Show Current Period

  • Toggle: "Show current period" (2024)
  • Admin decides what to display

Filters:

  • "Unpaid contributions in last period"
  • "Unpaid contributions in current period"

Member Detail View

Section: "Contributions"

Contribution Type Assignment:

┌─────────────────────────────────────┐
│ Contribution Type: [Dropdown]       │
│ ⚠ Only types with same interval    │
│   can be selected                   │
└─────────────────────────────────────┘

Period Table:

┌───────────────┬──────────┬────────┬──────────┬─────────┐
│ Period        │ Interval │ Amount │ Status   │ Action  │
├───────────────┼──────────┼────────┼──────────┼─────────┤
│ 01.01.2023-   │ Yearly   │ 50 €   │ ☑ Paid   │         │
│ 31.12.2023    │          │        │          │         │
├───────────────┼──────────┼────────┼──────────┼─────────┤
│ 01.01.2024-   │ Yearly   │ 60 €   │ ☐ Open   │ [Mark   │
│ 31.12.2024    │          │        │          │ as paid]│
├───────────────┼──────────┼────────┼──────────┼─────────┤
│ 01.01.2025-   │ Yearly   │ 60 €   │ ☐ Open   │ [Mark   │
│ 31.12.2025    │          │        │          │ as paid]│
└───────────────┴──────────┴────────┴──────────┴─────────┘

Legend: ☑ = paid | ☐ = unpaid | ⊘ = suspended

Quick Marking:

  • Checkbox in each row for fast marking
  • Button: "Mark selected as paid"
  • Bulk action for multiple periods

Admin: Contribution Types Management

List:

┌────────────┬──────────┬──────────┬────────────┬─────────┐
│ Name       │ Amount   │ Interval │ Members    │ Actions │
├────────────┼──────────┼──────────┼────────────┼─────────┤
│ Regular    │ 60 €     │ Yearly   │ 45         │ [Edit]  │
│ Reduced    │ 30 €     │ Yearly   │ 12         │ [Edit]  │
│ Student    │ 20 €     │ Monthly  │ 8          │ [Edit]  │
└────────────┴──────────┴──────────┴────────────┴─────────┘

Edit:

  • Name: ✓ editable
  • Amount: ✓ editable
  • Description: ✓ editable
  • Interval: ✗ NOT editable (grayed out)

Warning on Amount Change:

⚠ Change amount to 65 €?
  
Impact:
- 45 members affected
- Future unpaid periods will be generated with 65 €
- Already paid periods remain with old amount

[Cancel] [Confirm]

Admin: Settings

Contribution Configuration:

Default Contribution Type: [Dropdown: Contribution Types]

Selected: "Regular (60 €, Yearly)"

This contribution type is automatically assigned to all new members.
Can be changed individually per member.

---

☐ Include joining period

When active:
Members pay from the period of their joining.

Example (Yearly):
Joining: 15.03.2023
→ Pays from 2023

When inactive:
Members pay from the next full period.

Example (Yearly):
Joining: 15.03.2023
→ Pays from 2024

Edge Cases

1. Contribution Type Change with Different Interval

MVP: Blocked (only same interval allowed)

UI:

Error: Interval change not possible

Current contribution type: "Regular (Yearly)"
Selected contribution type: "Student (Monthly)"

Changing the interval is currently not possible.
Please select a contribution type with interval "Yearly".

[OK]

Future:

  • Allow interval switching
  • Calculate overlaps
  • Generate new periods without duplicates

2. Exit with Unpaid Contributions

Scenario:

Member exits: 15.08.2024
Yearly period 2024: unpaid

UI Notice on Exit:

⚠ Unpaid contributions present

This member has 1 unpaid period(s):
- 2024: 60 € (unpaid)

Do you want to continue?

[ ] Mark contribution as "suspended"
[Cancel] [Confirm Exit]

3. Multiple Unpaid Periods

Scenario: Member hasn't paid for 2 years

Display:

┌───────────────┬──────────┬────────┬──────────┬─────────┐
│ 2023          │ Yearly   │ 50 €   │ ☐ Open   │ [✓]     │
│ 2024          │ Yearly   │ 60 €   │ ☐ Open   │ [✓]     │
│ 2025          │ Yearly   │ 60 €   │ ☐ Open   │ [ ]     │
└───────────────┴──────────┴────────┴──────────┴─────────┘

[Mark selected as paid] (2 selected)

4. Amount Changes

Scenario:

2023: Regular = 50 €
2024: Regular = 60 € (increase)

Result:

  • Period 2023: Saved with 50 € (history)
  • Period 2024: Generated with 60 € (current)
  • Both periods show correct historical amount

5. Date Boundaries

Problem: What if today = 01.01.2025?

Solution:

  • Current period (2025) is generated
  • Status: unpaid (open)
  • Shown in overview

Implementation Scope

MVP (Phase 1)

Included:

  • ✓ Contribution types (CRUD)
  • ✓ Automatic period generation
  • ✓ Status management (paid/unpaid/suspended)
  • ✓ Member overview with contribution status
  • ✓ Period view per member
  • ✓ Quick checkbox marking
  • ✓ Bulk actions
  • ✓ Amount history
  • ✓ Same-interval type change
  • ✓ Default contribution type
  • ✓ Joining period configuration

NOT Included:

  • ✗ Interval change (only same interval)
  • ✗ Payment details (date, method)
  • ✗ Automatic integration (vereinfacht.digital)
  • ✗ Prorata calculation
  • ✗ Reports/statistics
  • ✗ Reminders/dunning (manual via filters)

Future Enhancements

Phase 2:

  • Payment details (date, amount, method)
  • Interval change for future unpaid periods
  • Manual vereinfacht.digital links per member
  • Extended filter options

Phase 3:

  • Automated vereinfacht.digital integration
  • Automatic payment matching
  • SEPA integration
  • Advanced reports