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
- Core Principle
- Terminology
- Data Model
- Business Logic
- UI/UX Design
- Edge Cases
- Technical Integration
- 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:
intervalis 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 fromperiod_start+interval - NO
interval_type- read fromcontribution_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:
- Get
member.contribution_start_dateandmember.contribution_type - Calculate first period based on
contribution_start_date - Generate all periods from start to today (or
left_atif present) - Skip existing periods
- Set
amountto currentcontribution_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:
- Check: New contribution type has same interval
- If yes: Set
member.contribution_type_id - Future unpaid periods: Delete and regenerate with new amount
- 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