refactor: improve cycle generation code quality and documentation
All checks were successful
continuous-integration/drone/push Build is passing

- Remove Process.sleep calls from integration tests (tests run synchronously in SQL sandbox)
- Improve error handling: membership_fee_type_not_found now returns changeset error instead of just logging
- Clarify partial_failure documentation: successful_cycles are not persisted on rollback
- Update documentation: joined_at → join_date, left_at → exit_date
- Document PostgreSQL advisory locks per member (not whole table lock)
- Document gap handling: explicitly deleted cycles are not recreated
This commit is contained in:
Moritz 2025-12-12 17:41:22 +01:00
parent e6ac5d1ab1
commit 82897d5cd3
5 changed files with 53 additions and 51 deletions

View file

@ -120,7 +120,7 @@ This document provides a comprehensive overview of the Membership Fees system. I
```
- 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)
- exit_date (Date, nullable) - Exit date (existing)
```
**Logic for membership_fee_start_date:**
@ -167,16 +167,17 @@ value: UUID (Required) - Default membership fee type for new members
**Algorithm:**
Lock the whole cycle table for the duration of the algorithm
Use PostgreSQL advisory locks per member to prevent race conditions
1. Get `member.membership_fee_start_date` and member's membership fee type
2. Generate cycles until today (or `left_at` if present):
- If no cycle exists:
- Generate all cycles from `membership_fee_start_date`
- else:
- Generate all cycles from last existing cycle
- use the interval to generate the cycles
3. Set `amount` to current membership fee type's amount
2. Determine generation start point:
- If NO cycles exist: Start from `membership_fee_start_date`
- If cycles exist: Start from the cycle AFTER the last existing one
3. Generate cycles until today (or `exit_date` if present):
- Use the interval to generate the cycles
- **Note:** If cycles were explicitly deleted (gaps exist), they are NOT recreated.
The generator always continues from the cycle AFTER the last existing cycle.
4. Set `amount` to current membership fee type's amount
**Example (Yearly):**
@ -246,7 +247,7 @@ suspended → unpaid
**Logic:**
- Cycles only generated until `member.left_at`
- Cycles only generated until `member.exit_date`
- Existing cycles remain visible
- Unpaid exit cycle can be marked as "suspended"