feat: include inactive members in batch cycle generation

- Remove exit_date filter from generate_cycles_for_all_members query
- Inactive members now get cycles generated up to their exit_date
- Add tests for inactive member processing and exit_date boundary
- Document exit_date == cycle_start behavior (cycle still generated)
This commit is contained in:
Moritz 2025-12-12 16:21:36 +01:00
parent b693ab1e26
commit a99f56969d
2 changed files with 87 additions and 6 deletions

View file

@ -83,12 +83,18 @@ defmodule Mv.MembershipFees.CycleGenerator do
end
@doc """
Generates membership fee cycles for all active members.
Generates membership fee cycles for all members with a fee type assigned.
Active members are those who:
This includes both active and inactive (left) members. Inactive members
will have cycles generated up to their exit_date if they don't have cycles
for that period yet. This allows for catch-up generation of missing cycles.
Members processed are those who:
- Have a membership_fee_type assigned
- Have a join_date set
- Either have no exit_date or exit_date >= today
The exit_date boundary is respected during generation (not in the query),
so inactive members will get cycles up to their exit date.
## Parameters
@ -107,12 +113,13 @@ defmodule Mv.MembershipFees.CycleGenerator do
today = Keyword.get(opts, :today, Date.utc_today())
batch_size = Keyword.get(opts, :batch_size, 10)
# Query active members with fee type assigned
# Query ALL members with fee type assigned (including inactive/left members)
# The exit_date boundary is applied during cycle generation, not here.
# This allows catch-up generation for members who left but are missing cycles.
query =
Member
|> Ash.Query.filter(not is_nil(membership_fee_type_id))
|> Ash.Query.filter(not is_nil(join_date))
|> Ash.Query.filter(is_nil(exit_date) or exit_date >= ^today)
case Ash.read(query) do
{:ok, members} ->
@ -234,7 +241,11 @@ defmodule Mv.MembershipFees.CycleGenerator do
defp determine_end_date(member, today) do
if member.exit_date && Date.compare(member.exit_date, today) == :lt do
# Member has left - use the cycle that contains the exit date
# Member has left - use the exit date as boundary
# Note: If exit_date == cycle_start, the cycle IS still generated.
# This means the member is considered a member on the first day of that cycle.
# Example: exit_date = 2025-01-01, yearly interval
# -> The 2025 cycle (starting 2025-01-01) WILL be generated
member.exit_date
else
today