712 lines
21 KiB
Markdown
712 lines
21 KiB
Markdown
# Membership Fees - Technical Architecture
|
|
|
|
**Project:** Mila - Membership Management System
|
|
**Feature:** Membership Fee Management
|
|
**Version:** 1.0
|
|
**Last Updated:** 2025-11-27
|
|
**Status:** Architecture Design - Ready for Implementation
|
|
|
|
---
|
|
|
|
## Purpose
|
|
|
|
This document defines the technical architecture for the Membership Fees system. It focuses on architectural decisions, patterns, module structure, and integration points **without** concrete implementation details.
|
|
|
|
**Related Documents:**
|
|
|
|
- [membership-fee-overview.md](./membership-fee-overview.md) - Business logic and requirements
|
|
- [database-schema-readme.md](./database-schema-readme.md) - Database documentation
|
|
- [database_schema.dbml](./database_schema.dbml) - Database schema definition
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Architecture Principles](#architecture-principles)
|
|
2. [Domain Structure](#domain-structure)
|
|
3. [Data Architecture](#data-architecture)
|
|
4. [Business Logic Architecture](#business-logic-architecture)
|
|
5. [Integration Points](#integration-points)
|
|
6. [Acceptance Criteria](#acceptance-criteria)
|
|
7. [Testing Strategy](#testing-strategy)
|
|
8. [Security Considerations](#security-considerations)
|
|
9. [Performance Considerations](#performance-considerations)
|
|
|
|
---
|
|
|
|
## Architecture Principles
|
|
|
|
### Core Design Decisions
|
|
|
|
1. **Single Responsibility:**
|
|
- Each module has one clear responsibility
|
|
- Cycle generation separated from status management
|
|
- Calendar logic isolated in dedicated module
|
|
|
|
2. **No Redundancy:**
|
|
- No `cycle_end` field (calculated from `cycle_start` + `interval`)
|
|
- No `interval_type` field (read from `membership_fee_type.interval`)
|
|
- Eliminates data inconsistencies
|
|
|
|
3. **Immutability Where Important:**
|
|
- `membership_fee_type.interval` cannot be changed after creation
|
|
- Prevents complex migration scenarios
|
|
- Enforced via Ash change validation
|
|
|
|
4. **Historical Accuracy:**
|
|
- `amount` stored per cycle for audit trail
|
|
- Enables tracking of membership fee changes over time
|
|
- Old cycles retain original amounts
|
|
|
|
5. **Calendar-Based Cycles:**
|
|
- All cycles aligned to calendar boundaries
|
|
- Simplifies date calculations
|
|
- Predictable cycle generation
|
|
|
|
---
|
|
|
|
## Domain Structure
|
|
|
|
### Ash Domain: `Mv.MembershipFees`
|
|
|
|
**Purpose:** Encapsulates all membership fee-related resources and logic
|
|
|
|
**Resources:**
|
|
|
|
- `MembershipFeeType` - Membership fee type definitions (admin-managed)
|
|
- `MembershipFeeCycle` - Individual membership fee cycles per member
|
|
|
|
**Extensions:**
|
|
|
|
- Member resource extended with membership fee fields
|
|
|
|
### Module Organization
|
|
|
|
```
|
|
lib/
|
|
├── membership_fees/
|
|
│ ├── membership_fees.ex # Ash domain definition
|
|
│ ├── membership_fee_type.ex # MembershipFeeType resource
|
|
│ ├── membership_fee_cycle.ex # MembershipFeeCycle resource
|
|
│ └── changes/
|
|
│ ├── prevent_interval_change.ex # Validates interval immutability
|
|
│ ├── set_membership_fee_start_date.ex # Auto-sets start date
|
|
│ └── validate_same_interval.ex # Validates interval match on type change
|
|
├── mv/
|
|
│ └── membership_fees/
|
|
│ ├── cycle_generator.ex # Cycle generation algorithm
|
|
│ └── calendar_cycles.ex # Calendar cycle calculations
|
|
└── membership/
|
|
└── member.ex # Extended with membership fee relationships
|
|
```
|
|
|
|
### Separation of Concerns
|
|
|
|
**Domain Layer (Ash Resources):**
|
|
|
|
- Data validation
|
|
- Relationship management
|
|
- Policy enforcement
|
|
- Action definitions
|
|
|
|
**Business Logic Layer (`Mv.MembershipFees`):**
|
|
|
|
- Cycle generation algorithm
|
|
- Calendar calculations
|
|
- Date boundary handling
|
|
- Status transitions
|
|
|
|
**UI Layer (LiveView):**
|
|
|
|
- User interaction
|
|
- Display logic
|
|
- Authorization checks
|
|
- Form handling
|
|
|
|
---
|
|
|
|
## Data Architecture
|
|
|
|
### Database Schema Extensions
|
|
|
|
**See:** [database-schema-readme.md](./database-schema-readme.md) and [database_schema.dbml](./database_schema.dbml) for complete schema documentation.
|
|
|
|
### New Tables
|
|
|
|
1. **`membership_fee_types`**
|
|
- Purpose: Define membership fee types with fixed intervals
|
|
- Key Constraint: `interval` field immutable after creation
|
|
- Relationships: has_many members, has_many membership_fee_cycles
|
|
|
|
2. **`membership_fee_cycles`**
|
|
- Purpose: Individual membership fee cycles for members
|
|
- Key Design: NO `cycle_end` or `interval_type` fields (calculated)
|
|
- Relationships: belongs_to member, belongs_to membership_fee_type
|
|
- Composite uniqueness: One cycle per member per cycle_start
|
|
|
|
### Member Table Extensions
|
|
|
|
**Fields Added:**
|
|
|
|
- `membership_fee_type_id` (FK, NOT NULL with default from settings)
|
|
- `membership_fee_start_date` (Date, nullable)
|
|
|
|
**Existing Fields Used:**
|
|
|
|
- `joined_at` - For calculating membership fee start
|
|
- `left_at` - For limiting cycle generation
|
|
- These fields must remain member fields and should not be replaced by custom fields in the future
|
|
|
|
### Settings Integration
|
|
|
|
**Global Settings:**
|
|
|
|
- `membership_fees.include_joining_cycle` (Boolean)
|
|
- `membership_fees.default_membership_fee_type_id` (UUID)
|
|
|
|
**Storage:** Existing settings mechanism (TBD: dedicated table or configuration resource)
|
|
|
|
### Foreign Key Behaviors
|
|
|
|
| Relationship | On Delete | Rationale |
|
|
|--------------|-----------|-----------|
|
|
| `membership_fee_cycles.member_id → members.id` | CASCADE | Remove membership fee cycles when member deleted |
|
|
| `membership_fee_cycles.membership_fee_type_id → membership_fee_types.id` | RESTRICT | Prevent membership fee type deletion if cycles exist |
|
|
| `members.membership_fee_type_id → membership_fee_types.id` | RESTRICT | Prevent membership fee type deletion if assigned to members |
|
|
|
|
---
|
|
|
|
## Business Logic Architecture
|
|
|
|
### Cycle Generation System
|
|
|
|
**Component:** `Mv.MembershipFees.CycleGenerator`
|
|
|
|
**Responsibilities:**
|
|
|
|
- Calculate which cycles should exist for a member
|
|
- Generate missing cycles
|
|
- Respect membership_fee_start_date and left_at boundaries
|
|
- Skip existing cycles (idempotent)
|
|
|
|
**Triggers:**
|
|
|
|
1. Member membership fee type assigned (via Ash change)
|
|
2. Member created with membership fee type (via Ash change)
|
|
3. Scheduled job runs (daily/weekly cron)
|
|
4. Admin manual regeneration (UI action)
|
|
|
|
**Algorithm Steps:**
|
|
|
|
1. Retrieve member with membership fee type and dates
|
|
2. Determine first cycle start (based on membership_fee_start_date)
|
|
3. Calculate all cycle starts from first to today (or left_at)
|
|
4. Query existing cycles for member
|
|
5. Generate missing cycles with current membership fee type's amount
|
|
6. Insert new cycles (batch operation)
|
|
|
|
**Edge Case Handling:**
|
|
|
|
- If membership_fee_start_date is NULL: Calculate from joined_at + global setting
|
|
- If left_at is set: Stop generation at left_at
|
|
- If membership fee type changes: Handled separately by regeneration logic
|
|
|
|
### Calendar Cycle Calculations
|
|
|
|
**Component:** `Mv.MembershipFees.CalendarCycles`
|
|
|
|
**Responsibilities:**
|
|
|
|
- Calculate cycle boundaries based on interval type
|
|
- Determine current cycle
|
|
- Determine last completed cycle
|
|
- Calculate cycle_end from cycle_start + interval
|
|
|
|
**Functions (high-level):**
|
|
|
|
- `calculate_cycle_start/3` - Given date and interval, find cycle start
|
|
- `calculate_cycle_end/2` - Given cycle_start and interval, calculate end
|
|
- `next_cycle_start/2` - Given cycle_start and interval, find next
|
|
- `is_current_cycle?/2` - Check if cycle contains today
|
|
- `is_last_completed_cycle?/2` - Check if cycle just ended
|
|
|
|
**Interval Logic:**
|
|
|
|
- **Monthly:** Start = 1st of month, End = last day of month
|
|
- **Quarterly:** Start = 1st of quarter (Jan/Apr/Jul/Oct), End = last day of quarter
|
|
- **Half-yearly:** Start = 1st of half (Jan/Jul), End = last day of half
|
|
- **Yearly:** Start = Jan 1st, End = Dec 31st
|
|
|
|
### Status Management
|
|
|
|
**Component:** Ash actions on `MembershipFeeCycle`
|
|
|
|
**Status Transitions:**
|
|
|
|
- Simple state machine: unpaid ↔ paid ↔ suspended
|
|
- No complex validation (all transitions allowed)
|
|
- Permissions checked via Ash policies
|
|
|
|
**Actions Required:**
|
|
|
|
- `mark_as_paid` - Set status to :paid
|
|
- `mark_as_suspended` - Set status to :suspended
|
|
- `mark_as_unpaid` - Set status to :unpaid (error correction)
|
|
|
|
**Bulk Operations:**
|
|
|
|
- `bulk_mark_as_paid` - Mark multiple cycles as paid (efficiency)
|
|
- low priority, can be a future issue
|
|
|
|
### Membership Fee Type Change Handling
|
|
|
|
**Component:** Ash change on `Member.membership_fee_type_id`
|
|
|
|
**Validation:**
|
|
|
|
- Check if new type has same interval as old type
|
|
- If different: Reject change (MVP constraint)
|
|
- If same: Allow change
|
|
|
|
**Side Effects on Allowed Change:**
|
|
|
|
1. Keep all existing cycles unchanged
|
|
2. Find future unpaid cycles
|
|
3. Delete future unpaid cycles
|
|
4. Regenerate cycles with new membership_fee_type_id and amount
|
|
|
|
**Implementation Pattern:**
|
|
|
|
- Use Ash change module to validate
|
|
- Use after_action hook to trigger regeneration
|
|
- Use transaction to ensure atomicity
|
|
|
|
---
|
|
|
|
## Integration Points
|
|
|
|
### Member Resource Integration
|
|
|
|
**Extension Points:**
|
|
|
|
1. Add fields via migration
|
|
2. Add relationships (belongs_to, has_many)
|
|
3. Add calculations (current_cycle_status, overdue_count)
|
|
4. Add changes (auto-set membership_fee_start_date, validate interval)
|
|
|
|
**Backward Compatibility:**
|
|
|
|
- New fields nullable or with defaults
|
|
- Existing members get default membership fee type from settings
|
|
- No breaking changes to existing member functionality
|
|
|
|
### Settings System Integration
|
|
|
|
**Requirements:**
|
|
|
|
- Store two global settings
|
|
- Provide UI for admin to modify
|
|
- Default values if not set
|
|
- Validation (e.g., default membership fee type must exist)
|
|
|
|
**Access Pattern:**
|
|
|
|
- Read settings during cycle generation
|
|
- Read settings during member creation
|
|
- Write settings only via admin UI
|
|
|
|
### Permission System Integration
|
|
|
|
**See:** [roles-and-permissions-architecture.md](./roles-and-permissions-architecture.md)
|
|
|
|
**Required Permissions:**
|
|
|
|
- `MembershipFeeType.create/update/destroy` - Admin only
|
|
- `MembershipFeeType.read` - Admin, Treasurer, Board
|
|
- `MembershipFeeCycle.update` (status changes) - Admin, Treasurer
|
|
- `MembershipFeeCycle.read` - Admin, Treasurer, Board, Own member
|
|
|
|
**Policy Patterns:**
|
|
|
|
- Use existing HasPermission check
|
|
- Leverage existing roles (Admin, Kassenwart)
|
|
- Member can read own cycles (linked via member_id)
|
|
|
|
### LiveView Integration
|
|
|
|
**New LiveViews Required:**
|
|
|
|
1. MembershipFeeType index/form (admin)
|
|
2. MembershipFeeCycle table component (member detail view)
|
|
3. Settings form section (admin)
|
|
4. Member list column (membership fee status)
|
|
|
|
**Existing LiveViews to Extend:**
|
|
|
|
- Member detail view: Add membership fees section
|
|
- Member list view: Add status column
|
|
- Settings page: Add membership fees section
|
|
|
|
**Authorization Helpers:**
|
|
|
|
- Use existing `can?/3` helper for UI conditionals
|
|
- Check permissions before showing actions
|
|
|
|
---
|
|
|
|
## Acceptance Criteria
|
|
|
|
### MembershipFeeType Resource
|
|
|
|
**AC-MFT-1:** Admin can create membership fee type with name, amount, interval, description
|
|
**AC-MFT-2:** Interval field is immutable after creation (validation error on change attempt)
|
|
**AC-MFT-3:** Admin can update name, amount, description (but not interval)
|
|
**AC-MFT-4:** Cannot delete membership fee type if assigned to members
|
|
**AC-MFT-5:** Cannot delete membership fee type if cycles exist referencing it
|
|
**AC-MFT-6:** Interval must be one of: monthly, quarterly, half_yearly, yearly
|
|
|
|
### MembershipFeeCycle Resource
|
|
|
|
**AC-MFC-1:** Cycle has cycle_start, status, amount, notes, member_id, membership_fee_type_id
|
|
**AC-MFC-2:** cycle_end is calculated, not stored
|
|
**AC-MFC-3:** Status defaults to :unpaid
|
|
**AC-MFC-4:** One cycle per member per cycle_start (uniqueness constraint)
|
|
**AC-MFC-5:** Amount is set at generation time from membership_fee_type.amount
|
|
**AC-MFC-6:** Cycles cascade delete when member deleted
|
|
**AC-MFC-7:** Admin/Treasurer can change status
|
|
**AC-MFC-8:** Member can read own cycles
|
|
|
|
### Member Extensions
|
|
|
|
**AC-M-1:** Member has membership_fee_type_id field (NOT NULL with default)
|
|
**AC-M-2:** Member has membership_fee_start_date field (nullable)
|
|
**AC-M-3:** New members get default membership fee type from global setting
|
|
**AC-M-4:** membership_fee_start_date auto-set based on joined_at and global setting
|
|
**AC-M-5:** Admin can manually override membership_fee_start_date
|
|
**AC-M-6:** Cannot change to membership fee type with different interval (MVP)
|
|
|
|
### Cycle Generation
|
|
|
|
**AC-CG-1:** Cycles generated when member gets membership fee type
|
|
**AC-CG-2:** Cycles generated when member created (via change hook)
|
|
**AC-CG-3:** Scheduled job generates missing cycles daily
|
|
**AC-CG-4:** Generation respects membership_fee_start_date
|
|
**AC-CG-5:** Generation stops at left_at if member exited
|
|
**AC-CG-6:** Generation is idempotent (skips existing cycles)
|
|
**AC-CG-7:** Cycles align to calendar boundaries (1st of month/quarter/half/year)
|
|
**AC-CG-8:** Amount comes from membership_fee_type at generation time
|
|
|
|
### Calendar Logic
|
|
|
|
**AC-CL-1:** Monthly cycles: 1st to last day of month
|
|
**AC-CL-2:** Quarterly cycles: 1st of Jan/Apr/Jul/Oct to last day of quarter
|
|
**AC-CL-3:** Half-yearly cycles: 1st of Jan/Jul to last day of half
|
|
**AC-CL-4:** Yearly cycles: Jan 1 to Dec 31
|
|
**AC-CL-5:** cycle_end calculated correctly for all interval types
|
|
**AC-CL-6:** Current cycle determined correctly based on today's date
|
|
**AC-CL-7:** Last completed cycle determined correctly
|
|
|
|
### Membership Fee Type Change
|
|
|
|
**AC-TC-1:** Can change to type with same interval
|
|
**AC-TC-2:** Cannot change to type with different interval (error message)
|
|
**AC-TC-3:** On allowed change: future unpaid cycles regenerated
|
|
**AC-TC-4:** On allowed change: paid/suspended cycles unchanged
|
|
**AC-TC-5:** On allowed change: amount updated to new type's amount
|
|
**AC-TC-6:** Change is atomic (transaction)
|
|
|
|
### Settings
|
|
|
|
**AC-S-1:** Global setting: include_joining_cycle (boolean, default true)
|
|
**AC-S-2:** Global setting: default_membership_fee_type_id (UUID, required)
|
|
**AC-S-3:** Admin can modify settings via UI
|
|
**AC-S-4:** Settings validated (e.g., default membership fee type must exist)
|
|
**AC-S-5:** Settings applied to new members immediately
|
|
|
|
### UI - Member List
|
|
|
|
**AC-UI-ML-1:** New column shows membership fee status
|
|
**AC-UI-ML-2:** Default: Shows last completed cycle status
|
|
**AC-UI-ML-3:** Optional: Toggle to show current cycle status
|
|
**AC-UI-ML-4:** Color coding: green (paid), red (unpaid), gray (suspended)
|
|
**AC-UI-ML-5:** Filter: Unpaid in last cycle
|
|
**AC-UI-ML-6:** Filter: Unpaid in current cycle
|
|
|
|
### UI - Member Detail
|
|
|
|
**AC-UI-MD-1:** Membership fees section shows all cycles
|
|
**AC-UI-MD-2:** Table columns: Cycle, Interval, Amount, Status, Actions
|
|
**AC-UI-MD-3:** Checkbox per cycle for bulk marking (low prio)
|
|
**AC-UI-MD-4:** "Mark selected as paid" button
|
|
**AC-UI-MD-5:** Dropdown to change membership fee type (same interval only)
|
|
**AC-UI-MD-6:** Warning if different interval selected
|
|
**AC-UI-MD-7:** Only show actions if user has permission
|
|
|
|
### UI - Membership Fee Types Admin
|
|
|
|
**AC-UI-CTA-1:** List all membership fee types
|
|
**AC-UI-CTA-2:** Show: Name, Amount, Interval, Member count
|
|
**AC-UI-CTA-3:** Create new membership fee type form
|
|
**AC-UI-CTA-4:** Edit form: Name, Amount, Description editable
|
|
**AC-UI-CTA-5:** Edit form: Interval grayed out (not editable)
|
|
**AC-UI-CTA-6:** Warning on amount change (explain impact)
|
|
**AC-UI-CTA-7:** Cannot delete if members assigned
|
|
**AC-UI-CTA-8:** Only admin can access
|
|
|
|
### UI - Settings Admin
|
|
|
|
**AC-UI-SA-1:** Membership fees section in settings
|
|
**AC-UI-SA-2:** Dropdown to select default membership fee type
|
|
**AC-UI-SA-3:** Checkbox: Include joining cycle
|
|
**AC-UI-SA-4:** Explanatory text with examples
|
|
**AC-UI-SA-5:** Save button with validation
|
|
|
|
---
|
|
|
|
## Testing Strategy
|
|
|
|
### Unit Testing
|
|
|
|
**Cycle Generator Tests:**
|
|
|
|
- Correct cycle_start calculation for all interval types
|
|
- Correct cycle count from start to end date
|
|
- Respects membership_fee_start_date boundary
|
|
- Respects left_at boundary
|
|
- Skips existing cycles (idempotent)
|
|
- Handles edge dates (year boundaries, leap years)
|
|
|
|
**Calendar Cycles Tests:**
|
|
|
|
- Cycle boundaries correct for all intervals
|
|
- cycle_end calculation correct
|
|
- Current cycle detection
|
|
- Last completed cycle detection
|
|
- Next cycle calculation
|
|
|
|
**Validation Tests:**
|
|
|
|
- Interval immutability enforced
|
|
- Same interval validation on type change
|
|
- Status transitions allowed
|
|
- Uniqueness constraints enforced
|
|
|
|
### Integration Testing
|
|
|
|
**Cycle Generation Flow:**
|
|
|
|
- Member creation triggers generation
|
|
- Type assignment triggers generation
|
|
- Type change regenerates future cycles
|
|
- Scheduled job generates missing cycles
|
|
- Left member stops generation
|
|
|
|
**Status Management Flow:**
|
|
|
|
- Mark single cycle as paid
|
|
- Bulk mark multiple cycles (low prio)
|
|
- Status transitions work
|
|
- Permissions enforced
|
|
|
|
**Membership Fee Type Management:**
|
|
|
|
- Create type
|
|
- Update amount (regeneration triggered)
|
|
- Cannot update interval
|
|
- Cannot delete if in use
|
|
|
|
### LiveView Testing
|
|
|
|
**Member List:**
|
|
|
|
- Status column displays correctly
|
|
- Toggle between last/current works
|
|
- Filters work correctly
|
|
- Color coding applied
|
|
|
|
**Member Detail:**
|
|
|
|
- Cycles table displays all cycles
|
|
- Checkboxes work
|
|
- Bulk marking works (low prio)
|
|
- Membership fee type change validation works
|
|
- Actions only shown with permission
|
|
|
|
**Admin UI:**
|
|
|
|
- Type CRUD works
|
|
- Settings save correctly
|
|
- Validations display errors
|
|
- Only authorized users can access
|
|
|
|
### Edge Case Testing
|
|
|
|
**Interval Change Attempt:**
|
|
|
|
- Error message displayed
|
|
- No data modified
|
|
- User can cancel/choose different type
|
|
|
|
**Exit with Unpaid:**
|
|
|
|
- Warning shown
|
|
- Option to suspend offered
|
|
- Exit completes correctly
|
|
|
|
**Amount Change:**
|
|
|
|
- Warning displayed
|
|
- Only future unpaid regenerated
|
|
- Historical cycles unchanged
|
|
|
|
**Date Boundaries:**
|
|
|
|
- Today = cycle start handled
|
|
- Today = cycle end handled
|
|
- Leap year handled
|
|
|
|
### Performance Testing
|
|
|
|
**Cycle Generation:**
|
|
|
|
- Generate 10 years of monthly cycles: < 100ms
|
|
- Generate for 1000 members: < 5 seconds
|
|
- Idempotent check efficient (no full scan)
|
|
|
|
**Member List Query:**
|
|
|
|
- With status column: < 200ms for 1000 members
|
|
- Filters applied efficiently
|
|
- No N+1 queries
|
|
|
|
---
|
|
|
|
## Security Considerations
|
|
|
|
### Authorization
|
|
|
|
**Permissions Required:**
|
|
|
|
- Membership fee type management: Admin only
|
|
- Membership fee cycle status changes: Admin + Treasurer
|
|
- View all cycles: Admin + Treasurer + Board
|
|
- View own cycles: All authenticated users
|
|
|
|
**Policy Enforcement:**
|
|
|
|
- All actions protected by Ash policies
|
|
- UI shows/hides based on permissions
|
|
- Backend validates permissions (never trust UI alone)
|
|
|
|
### Data Integrity
|
|
|
|
**Validation Layers:**
|
|
|
|
1. Database constraints (NOT NULL, UNIQUE, CHECK)
|
|
2. Ash validations (business rules)
|
|
3. UI validations (user experience)
|
|
|
|
**Immutability Protection:**
|
|
|
|
- Interval change prevented at multiple layers
|
|
- Cycle amounts immutable (audit trail)
|
|
- Settings changes logged (future)
|
|
|
|
### Audit Trail
|
|
|
|
**Tracked Information:**
|
|
|
|
- Cycle status changes (who, when) - future enhancement
|
|
- Membership fee type amount changes (implicit via cycle amounts)
|
|
|
|
---
|
|
|
|
## Performance Considerations
|
|
|
|
### Database Indexes
|
|
|
|
**Required Indexes:**
|
|
|
|
- `membership_fee_cycles(member_id)` - For member cycle lookups
|
|
- `membership_fee_cycles(membership_fee_type_id)` - For type queries
|
|
- `membership_fee_cycles(status)` - For unpaid filters
|
|
- `membership_fee_cycles(cycle_start)` - For date range queries
|
|
- `membership_fee_cycles(member_id, cycle_start)` - Composite unique index
|
|
- `members(membership_fee_type_id)` - For type membership count
|
|
|
|
### Query Optimization
|
|
|
|
**Preloading:**
|
|
|
|
- Load membership_fee_type with cycles (avoid N+1)
|
|
- Load cycles when displaying member detail
|
|
- Use Ash's load for efficient preloading
|
|
|
|
**Calculated Fields:**
|
|
|
|
- cycle_end calculated on-demand (not stored)
|
|
- current_cycle_status calculated when needed
|
|
- Use Ash calculations for lazy evaluation
|
|
|
|
**Pagination:**
|
|
|
|
- Cycle list paginated if > 50 cycles
|
|
- Member list already paginated
|
|
|
|
### Caching Strategy
|
|
|
|
**No caching needed in MVP:**
|
|
|
|
- Membership fee types rarely change
|
|
- Cycle queries are fast
|
|
- Settings read infrequently
|
|
|
|
**Future caching if needed:**
|
|
|
|
- Cache settings in application memory
|
|
- Cache membership fee types list
|
|
- Invalidate on change
|
|
|
|
### Scheduled Job Performance
|
|
|
|
**Cycle Generation Job:**
|
|
|
|
- Run daily or weekly (not hourly)
|
|
- Batch members (process 100 at a time)
|
|
- Skip members with no changes
|
|
- Log failures for retry
|
|
|
|
---
|
|
|
|
## Future Enhancements
|
|
|
|
### Phase 2: Interval Change Support
|
|
|
|
**Architecture Changes:**
|
|
|
|
- Add logic to handle cycle overlaps
|
|
- Calculate prorata amounts if needed
|
|
- More complex validation
|
|
- Migration path for existing cycles
|
|
|
|
### Phase 3: Payment Details
|
|
|
|
**Architecture Changes:**
|
|
|
|
- Add PaymentTransaction resource
|
|
- Link transactions to cycles
|
|
- Support multiple payments per cycle
|
|
- Reconciliation logic
|
|
|
|
### Phase 4: vereinfacht.digital Integration
|
|
|
|
**Architecture Changes:**
|
|
|
|
- External API client module
|
|
- Webhook handling for transactions
|
|
- Automatic matching logic
|
|
- Manual review interface
|
|
|
|
---
|
|
|
|
**End of Architecture Document**
|