527 lines
14 KiB
Markdown
527 lines
14 KiB
Markdown
# 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](./membership-fee-implementation-plan.md) (created after concept iterations)
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Core Principle](#core-principle)
|
|
2. [Terminology](#terminology)
|
|
3. [Data Model](#data-model)
|
|
4. [Business Logic](#business-logic)
|
|
5. [UI/UX Design](#uiux-design)
|
|
6. [Edge Cases](#edge-cases)
|
|
7. [Technical Integration](#technical-integration)
|
|
8. [Implementation Scope](#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:**
|
|
|
|
- `interval` is **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 from `cycle_start` + `interval`
|
|
- **NO** `interval_type` - read from `membership_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:**
|
|
|
|
1. Get `member.membership_fee_start_date` and member's membership fee type
|
|
2. Calculate first cycle based on `membership_fee_start_date`
|
|
3. Generate all cycles from start to today (or `left_at` if present)
|
|
4. Skip existing cycles
|
|
5. Set `amount` to 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:**
|
|
|
|
1. Check: New membership fee type has same interval
|
|
2. If yes: Set `member.membership_fee_type_id`
|
|
3. Future **unpaid** cycles: Delete and regenerate with new amount
|
|
4. 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
|