feat: implement automatic cycle generation for members
All checks were successful
continuous-integration/drone/push Build is passing

- Add CycleGenerator module with advisory lock mechanism
- Add SetMembershipFeeStartDate change for auto-calculation
- Extend Settings with include_joining_cycle and default_membership_fee_type_id
- Add scheduled job skeleton for future Oban integration
This commit is contained in:
Moritz 2025-12-11 21:16:47 +01:00
parent ecddf55331
commit 162d06da21
Signed by: moritz
GPG key ID: 1020A035E5DD0824
15 changed files with 2698 additions and 6 deletions

View file

@ -80,7 +80,7 @@ defmodule Mv.Membership.Member do
argument :user, :map, allow_nil?: true
# Accept member fields plus membership_fee_type_id (belongs_to FK)
accept @member_fields ++ [:membership_fee_type_id]
accept @member_fields ++ [:membership_fee_type_id, :membership_fee_start_date]
change manage_relationship(:custom_field_values, type: :create)
@ -101,6 +101,30 @@ defmodule Mv.Membership.Member do
change Mv.EmailSync.Changes.SyncUserEmailToMember do
where [changing(:user)]
end
# Auto-calculate membership_fee_start_date if not manually set
# Requires both join_date and membership_fee_type_id to be present
change Mv.MembershipFees.Changes.SetMembershipFeeStartDate
# Trigger cycle generation after member creation
# Only runs if membership_fee_type_id is set
# Note: Cycle generation runs asynchronously to not block the action,
# but in test environment it runs synchronously for DB sandbox compatibility
change after_action(fn _changeset, member, _context ->
if member.membership_fee_type_id && member.join_date do
if Application.get_env(:mv, :env) == :test do
# Run synchronously in test environment for DB sandbox compatibility
Mv.MembershipFees.CycleGenerator.generate_cycles_for_member(member.id)
else
# Run asynchronously in other environments
Task.start(fn ->
Mv.MembershipFees.CycleGenerator.generate_cycles_for_member(member.id)
end)
end
end
{:ok, member}
end)
end
update :update_member do
@ -114,7 +138,7 @@ defmodule Mv.Membership.Member do
argument :user, :map, allow_nil?: true
# Accept member fields plus membership_fee_type_id (belongs_to FK)
accept @member_fields ++ [:membership_fee_type_id]
accept @member_fields ++ [:membership_fee_type_id, :membership_fee_start_date]
change manage_relationship(:custom_field_values, on_match: :update, on_no_match: :create)
@ -141,6 +165,34 @@ defmodule Mv.Membership.Member do
change Mv.EmailSync.Changes.SyncUserEmailToMember do
where [changing(:user)]
end
# Auto-calculate membership_fee_start_date when membership_fee_type_id is set
# and membership_fee_start_date is not already set
change Mv.MembershipFees.Changes.SetMembershipFeeStartDate do
where [changing(:membership_fee_type_id)]
end
# Trigger cycle generation when membership_fee_type_id changes
# Note: Cycle generation runs asynchronously to not block the action,
# but in test environment it runs synchronously for DB sandbox compatibility
change after_action(fn changeset, member, _context ->
fee_type_changed =
Ash.Changeset.changing_attribute?(changeset, :membership_fee_type_id)
if fee_type_changed && member.membership_fee_type_id && member.join_date do
if Application.get_env(:mv, :env) == :test do
# Run synchronously in test environment for DB sandbox compatibility
Mv.MembershipFees.CycleGenerator.generate_cycles_for_member(member.id)
else
# Run asynchronously in other environments
Task.start(fn ->
Mv.MembershipFees.CycleGenerator.generate_cycles_for_member(member.id)
end)
end
end
{:ok, member}
end)
end
# Action to handle fuzzy search on specific fields