527 lines
14 KiB
Markdown
527 lines
14 KiB
Markdown
# 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](./contributions-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 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/unpaid/suspended"
|
|
- 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: (Low Prio)**
|
|
|
|
```
|
|
⚠ 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/unpaid/suspended] (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
|