14 KiB
Membership Fees - Overview
Project: Mila - Membership Management System
Feature: Membership Fee Management
Version: 1.0
Last Updated: 2025-11-27
Status: Concept - Ready for Review
Purpose
This document provides a comprehensive overview of the Membership Fees system. It covers business logic, data model, UI/UX design, and technical architecture in a concise, bullet-point format.
For detailed implementation: See membership-fee-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 cycle-based (Month/Quarter/Half-Year/Year)
Terminology
German ↔ English
Core Entities:
- Beitragsart ↔ Membership Fee Type
- Beitragszyklus ↔ Membership Fee Cycle
- Mitgliedsbeitrag ↔ Membership Fee
Status:
- bezahlt ↔ paid
- unbezahlt ↔ unpaid
- ausgesetzt ↔ suspended / waived
Intervals (Frequenz / Payment Frequency):
- monatlich ↔ monthly
- quartalsweise ↔ quarterly
- halbjährlich ↔ half-yearly / semi-annually
- jährlich ↔ yearly / annually
UI Elements:
- "Letzter Zyklus" ↔ "Last Cycle" (e.g., 2023 when in 2024)
- "Aktueller Zyklus" ↔ "Current Cycle" (e.g., 2024)
- "Als bezahlt markieren" ↔ "Mark as paid"
- "Aussetzen" ↔ "Suspend" / "Waive"
Data Model
Membership Fee Type (MembershipFeeType)
- id (UUID)
- name (String) - e.g., "Regular", "Reduced", "Student"
- amount (Decimal) - Membership fee 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 cycles regenerated with new amount
Membership Fee Cycle (MembershipFeeCycle)
- id (UUID)
- member_id (FK → members.id)
- membership_fee_type_id (FK → membership_fee_types.id)
- cycle_start (Date) - Calendar cycle start (01.01., 01.04., 01.07., 01.10., etc.)
- status (Enum) - :unpaid (default), :paid, :suspended
- amount (Decimal) - Membership fee amount at generation time (history when type changes)
- notes (Text, optional) - Admin notes
- timestamps
Important:
- NO
cycle_end- calculated fromcycle_start+interval - NO
interval_type- read frommembership_fee_type.interval - Avoids redundancy and inconsistencies!
Calendar Cycle 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)
- membership_fee_type_id (FK → membership_fee_types.id, NOT NULL, default from settings)
- membership_fee_start_date (Date, nullable) - When to start generating membership fees
- left_at (Date, nullable) - Exit date (existing)
Logic for membership_fee_start_date:
- Auto-set based on global setting
include_joining_cycle - If
include_joining_cycle = true: First day of joining month/quarter/year - If
include_joining_cycle = false: First day of NEXT cycle after joining - Can be manually overridden by admin
NO include_joining_cycle field on Member - unnecessary due to membership_fee_start_date!
Global Settings
key: "membership_fees.include_joining_cycle"
value: Boolean (Default: true)
key: "membership_fees.default_membership_fee_type_id"
value: UUID (Required) - Default membership fee type for new members
Meaning include_joining_cycle:
true: Joining cycle is included (member pays from joining cycle)false: Only from next full cycle after joining
Meaning of default membership fee type setting:
- Every new member automatically gets this membership fee type
- Must be configured in admin settings
- Prevents: Members without membership fee type
Business Logic
Cycle Generation
Triggers:
- Member gets membership fee type assigned (also during member creation)
- New cycle begins (Cron job daily/weekly)
- Admin requests manual regeneration
Algorithm:
- Get
member.membership_fee_start_dateand member's membership fee type - Calculate first cycle based on
membership_fee_start_date - Generate all cycles from start to today (or
left_atif present) - Skip existing cycles
- Set
amountto current membership fee type's amount
Example (Yearly):
Joining date: 15.03.2023
include_joining_cycle: true
→ membership_fee_start_date: 01.01.2023
Generated cycles:
- 01.01.2023 - 31.12.2023 (joining cycle)
- 01.01.2024 - 31.12.2024
- 01.01.2025 - 31.12.2025 (current year)
Example (Quarterly):
Joining date: 15.03.2023
include_joining_cycle: false
→ membership_fee_start_date: 01.04.2023
Generated cycles:
- 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
Membership Fee Type Change
MVP - Same Cycle Only:
- Member can only choose membership fee type with same cycle
- Example: From "Regular (yearly)" to "Reduced (yearly)" ✓
- Example: From "Regular (yearly)" to "Reduced (monthly)" ✗
Logic on Change:
- Check: New membership fee type has same interval
- If yes: Set
member.membership_fee_type_id - Future unpaid cycles: Delete and regenerate with new amount
- Paid/suspended cycles: Remain unchanged (historical amount)
Future - Different Intervals:
- Enable interval switching (e.g., yearly → monthly)
- More complex logic for cycle overlaps
- Needs additional validation
Member Exit
Logic:
- Cycles only generated until
member.left_at - Existing cycles remain visible
- Unpaid exit cycle can be marked as "suspended"
Example:
Exit: 15.08.2024
Yearly cycle: 01.01.2024 - 31.12.2024
→ Cycle 2024 is shown (Status: unpaid)
→ Admin can set to "suspended"
→ No cycles for 2025+ generated
UI/UX Design
Member List View
New Column: "Membership Fee Status"
Default Display (Last Cycle):
- Shows status of last completed cycle
- Example in 2024: Shows membership fee for 2023
- Color coding:
- Green: paid ✓
- Red: unpaid ✗
- Gray: suspended ⊘
Optional: Show Current Cycle
- Toggle: "Show current cycle" (2024)
- Admin decides what to display
Filters:
- "Unpaid membership fees in last cycle"
- "Unpaid membership fees in current cycle"
Member Detail View
Section: "Membership Fees"
Membership Fee Type Assignment:
┌─────────────────────────────────────┐
│ Membership Fee Type: [Dropdown] │
│ ⚠ Only types with same interval │
│ can be selected │
└─────────────────────────────────────┘
Cycle Table:
┌───────────────┬──────────┬────────┬──────────┬─────────┐
│ Cycle │ 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/unpaid/suspended"
- Bulk action for multiple cycles
Admin: Membership Fee 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 cycles will be generated with 65 €
- Already paid cycles remain with old amount
[Cancel] [Confirm]
Admin: Settings
Membership Fee Configuration:
Default Membership Fee Type: [Dropdown: Membership Fee Types]
Selected: "Regular (60 €, Yearly)"
This membership fee type is automatically assigned to all new members.
Can be changed individually per member.
---
☐ Include joining cycle
When active:
Members pay from the cycle of their joining.
Example (Yearly):
Joining: 15.03.2023
→ Pays from 2023
When inactive:
Members pay from the next full cycle.
Example (Yearly):
Joining: 15.03.2023
→ Pays from 2024
Edge Cases
1. Membership Fee Type Change with Different Interval
MVP: Blocked (only same interval allowed)
UI:
Error: Interval change not possible
Current membership fee type: "Regular (Yearly)"
Selected membership fee type: "Student (Monthly)"
Changing the interval is currently not possible.
Please select a membership fee type with interval "Yearly".
[OK]
Future:
- Allow interval switching
- Calculate overlaps
- Generate new cycles without duplicates
2. Exit with Unpaid Membership Fees
Scenario:
Member exits: 15.08.2024
Yearly cycle 2024: unpaid
UI Notice on Exit: (Low Prio)
⚠ Unpaid membership fees present
This member has 1 unpaid cycle(s):
- 2024: 60 € (unpaid)
Do you want to continue?
[ ] Mark membership fee as "suspended"
[Cancel] [Confirm Exit]
3. Multiple Unpaid Cycles
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/unpaid/suspended] (2 selected)
4. Amount Changes
Scenario:
2023: Regular = 50 €
2024: Regular = 60 € (increase)
Result:
- Cycle 2023: Saved with 50 € (history)
- Cycle 2024: Generated with 60 € (current)
- Both cycles show correct historical amount
5. Date Boundaries
Problem: What if today = 01.01.2025?
Solution:
- Current cycle (2025) is generated
- Status: unpaid (open)
- Shown in overview
Implementation Scope
MVP (Phase 1)
Included:
- ✓ Membership fee types (CRUD)
- ✓ Automatic cycle generation
- ✓ Status management (paid/unpaid/suspended)
- ✓ Member overview with membership fee status
- ✓ Cycle view per member
- ✓ Quick checkbox marking
- ✓ Bulk actions
- ✓ Amount history
- ✓ Same-interval type change
- ✓ Default membership fee type
- ✓ Joining cycle 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 cycles
- Manual vereinfacht.digital links per member
- Extended filter options
Phase 3:
- Automated vereinfacht.digital integration
- Automatic payment matching
- SEPA integration
- Advanced reports