Membership Fee 6 - UI Components & LiveViews closes #280 #304

Open
moritz wants to merge 65 commits from feature/280_membership_fee_ui into main
Showing only changes of commit a03056e6ae - Show all commits

View file

@ -129,7 +129,12 @@ Accounts.create_user!(%{email: "admin@mv.local"}, upsert?: true, upsert_identity
|> Ash.update!()
# Load all membership fee types for assignment
all_fee_types = MembershipFeeType |> Ash.read!() |> Enum.to_list()
# Sort by name to ensure deterministic order
all_fee_types =
MembershipFeeType
|> Ash.Query.sort(name: :asc)
|> Ash.read!()
|> Enum.to_list()
# Create sample members for testing - use upsert to prevent duplicates
# Member 1: Hans - All cycles paid
@ -195,17 +200,32 @@ Enum.each(member_attrs_list, fn member_attrs ->
member_attrs_without_status = Map.delete(member_attrs, :cycle_status)
# Use upsert to prevent duplicates based on email
# First create/update member without membership_fee_type_id to avoid overwriting existing assignments
member_attrs_without_fee_type = Map.delete(member_attrs_without_status, :membership_fee_type_id)
member =
Membership.create_member!(member_attrs_without_status,
Membership.create_member!(member_attrs_without_fee_type,
upsert?: true,
upsert_identity: :unique_email
)
# Only set membership_fee_type_id if member doesn't have one yet (idempotent)
final_member =
if is_nil(member.membership_fee_type_id) and Map.has_key?(member_attrs_without_status, :membership_fee_type_id) do
member
|> Ash.Changeset.for_update(:update_member, %{
membership_fee_type_id: member_attrs_without_status.membership_fee_type_id
})
|> Ash.update!()
else
member
end
# Generate cycles if member has a fee type
if member.membership_fee_type_id do
if final_member.membership_fee_type_id do
# Load member with cycles to check if they already exist
member_with_cycles =
member
final_member
|> Ash.load!(:membership_fee_cycles)
# Only generate if no cycles exist yet (to avoid duplicates on re-run)
@ -213,7 +233,7 @@ Enum.each(member_attrs_list, fn member_attrs ->
if Enum.empty?(member_with_cycles.membership_fee_cycles) do
# Generate cycles
{:ok, new_cycles, _notifications} =
CycleGenerator.generate_cycles_for_member(member.id, skip_lock?: true)
CycleGenerator.generate_cycles_for_member(final_member.id, skip_lock?: true)
new_cycles
else
@ -311,36 +331,47 @@ Enum.with_index(linked_members)
user = member_attrs.user
member_attrs_without_user = Map.delete(member_attrs, :user)
# Round-robin assignment: continue cycling through fee types
# Start from where previous members ended
fee_type_index = rem(3 + index, length(all_fee_types))
fee_type = Enum.at(all_fee_types, fee_type_index)
member_attrs_with_fee_type =
Map.put(member_attrs_without_user, :membership_fee_type_id, fee_type.id)
# Use upsert to prevent duplicates based on email
# First create/update member without membership_fee_type_id to avoid overwriting existing assignments
member_attrs_without_fee_type = Map.delete(member_attrs_without_user, :membership_fee_type_id)
# Check if user already has a member
member =
if user.member_id == nil do
# User is free, create member and link - use upsert to prevent duplicates
Membership.create_member!(
Map.put(member_attrs_with_fee_type, :user, %{id: user.id}),
Map.put(member_attrs_without_fee_type, :user, %{id: user.id}),
upsert?: true,
upsert_identity: :unique_email
)
else
# User already has a member, just create the member without linking - use upsert to prevent duplicates
Membership.create_member!(member_attrs_with_fee_type,
Membership.create_member!(member_attrs_without_fee_type,
upsert?: true,
upsert_identity: :unique_email
)
end
# Only set membership_fee_type_id if member doesn't have one yet (idempotent)
final_member =
if is_nil(member.membership_fee_type_id) do
# Assign deterministically using round-robin
# Start from where previous members ended (3 members before this)
fee_type_index = rem(3 + index, length(all_fee_types))
fee_type = Enum.at(all_fee_types, fee_type_index)
member
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|> Ash.update!()
else
member
end
# Generate cycles for linked members
if member.membership_fee_type_id do
if final_member.membership_fee_type_id do
# Load member with cycles to check if they already exist
member_with_cycles =
member
final_member
|> Ash.load!(:membership_fee_cycles)
# Only generate if no cycles exist yet (to avoid duplicates on re-run)
@ -348,7 +379,7 @@ Enum.with_index(linked_members)
if Enum.empty?(member_with_cycles.membership_fee_cycles) do
# Generate cycles
{:ok, new_cycles, _notifications} =
CycleGenerator.generate_cycles_for_member(member.id, skip_lock?: true)
CycleGenerator.generate_cycles_for_member(final_member.id, skip_lock?: true)
new_cycles
else