diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 97eb136..f9a9b3c 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -6,6 +6,7 @@ alias Mv.Membership alias Mv.Accounts alias Mv.MembershipFees.MembershipFeeType +alias Mv.MembershipFees.CycleGenerator # Create example membership fee types for fee_type_attrs <- [ @@ -131,7 +132,10 @@ Accounts.create_user!(%{email: "admin@mv.local"}, upsert?: true, upsert_identity all_fee_types = MembershipFeeType |> Ash.read!() |> Enum.to_list() # Create sample members for testing - use upsert to prevent duplicates -# Assign each member to a fee type using round-robin distribution +# Member 1: Hans - All cycles paid +# Member 2: Greta - All cycles unpaid +# Member 3: Friedrich - Mixed cycles (paid, unpaid, suspended) +# Member 4: Marianne - No membership fee type member_attrs_list = [ %{ first_name: "Hans", @@ -142,7 +146,9 @@ member_attrs_list = [ city: "München", street: "Hauptstraße", house_number: "42", - postal_code: "80331" + postal_code: "80331", + membership_fee_type_id: Enum.at(all_fee_types, 0).id, + cycle_status: :all_paid }, %{ first_name: "Greta", @@ -154,7 +160,9 @@ member_attrs_list = [ street: "Lindenstraße", house_number: "17", postal_code: "20095", - notes: "Interessiert an Fortgeschrittenen-Kursen" + notes: "Interessiert an Fortgeschrittenen-Kursen", + membership_fee_type_id: Enum.at(all_fee_types, 1).id, + cycle_status: :all_unpaid }, %{ first_name: "Friedrich", @@ -164,7 +172,9 @@ member_attrs_list = [ phone_number: "+49301122334", city: "Berlin", street: "Kastanienallee", - house_number: "8" + house_number: "8", + membership_fee_type_id: Enum.at(all_fee_types, 2).id, + cycle_status: :mixed }, %{ first_name: "Marianne", @@ -175,21 +185,74 @@ member_attrs_list = [ city: "Berlin", street: "Kastanienallee", house_number: "8" + # No membership_fee_type_id - member without fee type } ] -# Assign fee types to members using round-robin -Enum.with_index(member_attrs_list) -|> Enum.each(fn {member_attrs, index} -> - # Round-robin assignment: cycle through fee types - fee_type = Enum.at(all_fee_types, rem(index, length(all_fee_types))) - member_attrs_with_fee_type = Map.put(member_attrs, :membership_fee_type_id, fee_type.id) +# Create members and generate cycles +Enum.each(member_attrs_list, fn member_attrs -> + cycle_status = Map.get(member_attrs, :cycle_status) + member_attrs_without_status = Map.delete(member_attrs, :cycle_status) # Use upsert to prevent duplicates based on email - Membership.create_member!(member_attrs_with_fee_type, - upsert?: true, - upsert_identity: :unique_email - ) + member = + Membership.create_member!(member_attrs_without_status, + upsert?: true, + upsert_identity: :unique_email + ) + + # Generate cycles if member has a fee type + if member.membership_fee_type_id do + # Load member with cycles to check if they already exist + member_with_cycles = + member + |> Ash.load!(:membership_fee_cycles) + + # Only generate if no cycles exist yet (to avoid duplicates on re-run) + cycles = + 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) + + new_cycles + else + # Use existing cycles + member_with_cycles.membership_fee_cycles + end + + # Set cycle statuses based on member type + if cycle_status do + cycles + |> Enum.sort_by(& &1.cycle_start, Date) + |> Enum.with_index() + |> Enum.each(fn {cycle, index} -> + status = + case cycle_status do + :all_paid -> + :paid + + :all_unpaid -> + :unpaid + + :mixed -> + # Mix: first paid, second unpaid, third suspended, then repeat + case rem(index, 3) do + 0 -> :paid + 1 -> :unpaid + 2 -> :suspended + end + end + + # Only update if status is different + if cycle.status != status do + cycle + |> Ash.Changeset.for_update(:update, %{status: status}) + |> Ash.update!() + end + end) + end + end end) # Create additional users for user-member linking examples @@ -250,26 +313,64 @@ Enum.with_index(linked_members) # Round-robin assignment: continue cycling through fee types # Start from where previous members ended - fee_type_index = rem(length(member_attrs_list) + index, length(all_fee_types)) + 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) # Check if user already has a 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}), - 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, - upsert?: true, - upsert_identity: :unique_email - ) + 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}), + 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, + upsert?: true, + upsert_identity: :unique_email + ) + end + + # Generate cycles for linked members + if member.membership_fee_type_id do + # Load member with cycles to check if they already exist + member_with_cycles = + member + |> Ash.load!(:membership_fee_cycles) + + # Only generate if no cycles exist yet (to avoid duplicates on re-run) + cycles = + 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) + + new_cycles + else + # Use existing cycles + member_with_cycles.membership_fee_cycles + end + + # Set some cycles to paid for linked members (mixed status) + cycles + |> Enum.sort_by(& &1.cycle_start, Date) + |> Enum.with_index() + |> Enum.each(fn {cycle, index} -> + # Every other cycle is paid, rest unpaid + status = if rem(index, 2) == 0, do: :paid, else: :unpaid + + # Only update if status is different + if cycle.status != status do + cycle + |> Ash.Changeset.for_update(:update, %{status: status}) + |> Ash.update!() + end + end) end end) diff --git a/test/seeds_test.exs b/test/seeds_test.exs index 8075078..d72618e 100644 --- a/test/seeds_test.exs +++ b/test/seeds_test.exs @@ -43,18 +43,19 @@ defmodule Mv.SeedsTest do "CustomFields count should remain same after re-running seeds" end - test "all members have membership fee type assigned" do + test "at least one member has no membership fee type assigned" do # Run the seeds script assert Code.eval_file("priv/repo/seeds.exs") # Get all members {:ok, members} = Ash.read(Mv.Membership.Member) - # All members should have a membership_fee_type_id - Enum.each(members, fn member -> - assert member.membership_fee_type_id != nil, - "Member #{member.first_name} #{member.last_name} should have a membership fee type assigned" - end) + # At least one member should have no membership_fee_type_id + members_without_fee_type = + Enum.filter(members, fn member -> member.membership_fee_type_id == nil end) + + assert length(members_without_fee_type) > 0, + "At least one member should have no membership fee type assigned" end test "each membership fee type has at least one member" do @@ -65,9 +66,10 @@ defmodule Mv.SeedsTest do {:ok, fee_types} = Ash.read(Mv.MembershipFees.MembershipFeeType) {:ok, members} = Ash.read(Mv.Membership.Member) - # Group members by fee type + # Group members by fee type (excluding nil) members_by_fee_type = members + |> Enum.filter(&(&1.membership_fee_type_id != nil)) |> Enum.group_by(& &1.membership_fee_type_id) # Each fee type should have at least one member @@ -78,5 +80,38 @@ defmodule Mv.SeedsTest do "Membership fee type #{fee_type.name} should have at least one member assigned" end) end + + test "members with fee types have cycles with various statuses" do + # Run the seeds script + assert Code.eval_file("priv/repo/seeds.exs") + + # Get all members with fee types + {:ok, members} = Ash.read(Mv.Membership.Member) + + members_with_fee_types = + members + |> Enum.filter(&(&1.membership_fee_type_id != nil)) + + # At least one member should have cycles + assert length(members_with_fee_types) > 0, + "At least one member should have a membership fee type" + + # Check that cycles exist and have various statuses + all_cycle_statuses = + members_with_fee_types + |> Enum.flat_map(fn member -> + Mv.MembershipFees.MembershipFeeCycle + |> Ash.Query.filter(member_id == ^member.id) + |> Ash.read!() + end) + |> Enum.map(& &1.status) + + # At least one cycle should be paid + assert :paid in all_cycle_statuses, "At least one cycle should be paid" + # At least one cycle should be unpaid + assert :unpaid in all_cycle_statuses, "At least one cycle should be unpaid" + # At least one cycle should be suspended + assert :suspended in all_cycle_statuses, "At least one cycle should be suspended" + end end end