diff --git a/test/membership/member_cycle_calculations_test.exs b/test/membership/member_cycle_calculations_test.exs index 9be1272..860d784 100644 --- a/test/membership/member_cycle_calculations_test.exs +++ b/test/membership/member_cycle_calculations_test.exs @@ -4,30 +4,15 @@ defmodule Mv.Membership.MemberCycleCalculationsTest do """ use Mv.DataCase, async: true + import Mv.Fixtures, only: [create_fee_type: 2, create_cycle: 4] + alias Mv.MembershipFees.CalendarCycles - alias Mv.MembershipFees.MembershipFeeCycle - alias Mv.MembershipFees.MembershipFeeType setup do system_actor = Mv.Helpers.SystemActor.get_system_actor() %{actor: system_actor} end - # Helper to create a membership fee type - defp create_fee_type(attrs, actor) do - default_attrs = %{ - name: "Test Fee Type #{System.unique_integer([:positive])}", - amount: Decimal.new("50.00"), - interval: :yearly - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeType - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: actor) - end - # Helper to create a member defp create_member(attrs, actor) do default_attrs = %{ @@ -41,23 +26,6 @@ defmodule Mv.Membership.MemberCycleCalculationsTest do member end - # Helper to create a cycle - defp create_cycle(member, fee_type, attrs, actor) do - default_attrs = %{ - cycle_start: ~D[2024-01-01], - amount: Decimal.new("50.00"), - member_id: member.id, - membership_fee_type_id: fee_type.id, - status: :unpaid - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeCycle - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: actor) - end - describe "current_cycle_status" do test "returns status of current cycle for member with active cycle", %{actor: actor} do fee_type = create_fee_type(%{interval: :yearly}, actor) diff --git a/test/membership/member_type_change_integration_test.exs b/test/membership/member_type_change_integration_test.exs index 35b3137..a2baf78 100644 --- a/test/membership/member_type_change_integration_test.exs +++ b/test/membership/member_type_change_integration_test.exs @@ -4,9 +4,10 @@ defmodule Mv.Membership.MemberTypeChangeIntegrationTest do """ use Mv.DataCase, async: true + import Mv.Fixtures, only: [create_fee_type: 2, create_cycle: 4] + alias Mv.MembershipFees.CalendarCycles alias Mv.MembershipFees.MembershipFeeCycle - alias Mv.MembershipFees.MembershipFeeType require Ash.Query @@ -15,21 +16,6 @@ defmodule Mv.Membership.MemberTypeChangeIntegrationTest do %{actor: system_actor} end - # Helper to create a membership fee type - defp create_fee_type(attrs, actor) do - default_attrs = %{ - name: "Test Fee Type #{System.unique_integer([:positive])}", - amount: Decimal.new("50.00"), - interval: :yearly - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeType - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: actor) - end - # Helper to create a member defp create_member(attrs, actor) do default_attrs = %{ @@ -44,23 +30,6 @@ defmodule Mv.Membership.MemberTypeChangeIntegrationTest do member end - # Helper to create a cycle - defp create_cycle(member, fee_type, attrs, actor) do - default_attrs = %{ - cycle_start: ~D[2024-01-01], - amount: Decimal.new("50.00"), - member_id: member.id, - membership_fee_type_id: fee_type.id, - status: :unpaid - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeCycle - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: actor) - end - describe "type change cycle regeneration" do test "future unpaid cycles are regenerated with new amount", %{actor: actor} do today = Date.utc_today() diff --git a/test/membership_fees/changes/validate_same_interval_test.exs b/test/membership_fees/changes/validate_same_interval_test.exs index 31c2847..21e0a55 100644 --- a/test/membership_fees/changes/validate_same_interval_test.exs +++ b/test/membership_fees/changes/validate_same_interval_test.exs @@ -4,29 +4,15 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do """ use Mv.DataCase, async: true + import Mv.Fixtures, only: [create_fee_type: 2] + alias Mv.MembershipFees.Changes.ValidateSameInterval - alias Mv.MembershipFees.MembershipFeeType setup do system_actor = Mv.Helpers.SystemActor.get_system_actor() %{actor: system_actor} end - # Helper to create a membership fee type - defp create_fee_type(attrs, actor) do - default_attrs = %{ - name: "Test Fee Type #{System.unique_integer([:positive])}", - amount: Decimal.new("50.00"), - interval: :yearly - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeType - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: actor) - end - # Helper to create a member defp create_member(attrs, actor) do default_attrs = %{ diff --git a/test/membership_fees/member_cycle_integration_test.exs b/test/membership_fees/member_cycle_integration_test.exs index 76f4d08..c6a1486 100644 --- a/test/membership_fees/member_cycle_integration_test.exs +++ b/test/membership_fees/member_cycle_integration_test.exs @@ -4,8 +4,9 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do """ use Mv.DataCase, async: false + import Mv.Fixtures, only: [create_fee_type: 2] + alias Mv.MembershipFees.MembershipFeeCycle - alias Mv.MembershipFees.MembershipFeeType require Ash.Query @@ -14,21 +15,6 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do %{actor: system_actor} end - # Helper to create a membership fee type - defp create_fee_type(attrs, actor) do - default_attrs = %{ - name: "Test Fee Type #{System.unique_integer([:positive])}", - amount: Decimal.new("50.00"), - interval: :yearly - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeType - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: actor) - end - # Helper to set up settings defp setup_settings(include_joining_cycle, actor) do {:ok, settings} = Mv.Membership.get_settings() diff --git a/test/membership_fees/membership_fee_cycle_test.exs b/test/membership_fees/membership_fee_cycle_test.exs index 8770134..f4f89af 100644 --- a/test/membership_fees/membership_fee_cycle_test.exs +++ b/test/membership_fees/membership_fee_cycle_test.exs @@ -4,29 +4,15 @@ defmodule Mv.MembershipFees.MembershipFeeCycleTest do """ use Mv.DataCase, async: true + import Mv.Fixtures, only: [create_fee_type: 2, create_cycle: 4] + alias Mv.MembershipFees.MembershipFeeCycle - alias Mv.MembershipFees.MembershipFeeType setup do system_actor = Mv.Helpers.SystemActor.get_system_actor() %{actor: system_actor} end - # Helper to create a membership fee type - defp create_fee_type(attrs, actor) do - default_attrs = %{ - name: "Test Fee Type #{System.unique_integer([:positive])}", - amount: Decimal.new("50.00"), - interval: :yearly - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeType - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: actor) - end - # Helper to create a member defp create_member(attrs, actor) do default_attrs = %{ @@ -40,22 +26,6 @@ defmodule Mv.MembershipFees.MembershipFeeCycleTest do member end - # Helper to create a cycle - defp create_cycle(member, fee_type, attrs, actor) do - default_attrs = %{ - cycle_start: ~D[2024-01-01], - amount: Decimal.new("50.00"), - member_id: member.id, - membership_fee_type_id: fee_type.id - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeCycle - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: actor) - end - describe "status defaults" do test "status defaults to :unpaid when creating a cycle", %{actor: actor} do fee_type = create_fee_type(%{interval: :yearly}, actor) diff --git a/test/membership_fees/membership_fee_type_integration_test.exs b/test/membership_fees/membership_fee_type_integration_test.exs index f834094..86ef284 100644 --- a/test/membership_fees/membership_fee_type_integration_test.exs +++ b/test/membership_fees/membership_fee_type_integration_test.exs @@ -4,6 +4,8 @@ defmodule Mv.MembershipFees.MembershipFeeTypeIntegrationTest do """ use Mv.DataCase, async: false + import Mv.Fixtures, only: [create_fee_type: 2] + alias Mv.Membership.Member alias Mv.MembershipFees.MembershipFeeCycle alias Mv.MembershipFees.MembershipFeeType @@ -15,21 +17,6 @@ defmodule Mv.MembershipFees.MembershipFeeTypeIntegrationTest do %{actor: system_actor} end - # Helper to create a membership fee type - defp create_fee_type(attrs, actor) do - default_attrs = %{ - name: "Test Fee Type #{System.unique_integer([:positive])}", - amount: Decimal.new("50.00"), - interval: :yearly - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeType - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: actor) - end - describe "admin can create membership fee type" do test "creates type with all fields", %{actor: actor} do attrs = %{ diff --git a/test/mv/membership_fees/cycle_generator_edge_cases_test.exs b/test/mv/membership_fees/cycle_generator_edge_cases_test.exs index a9e3316..eb9da26 100644 --- a/test/mv/membership_fees/cycle_generator_edge_cases_test.exs +++ b/test/mv/membership_fees/cycle_generator_edge_cases_test.exs @@ -12,9 +12,10 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do """ use Mv.DataCase, async: false + import Mv.Fixtures, only: [create_fee_type: 2] + alias Mv.MembershipFees.CycleGenerator alias Mv.MembershipFees.MembershipFeeCycle - alias Mv.MembershipFees.MembershipFeeType require Ash.Query @@ -23,21 +24,6 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do %{actor: system_actor} end - # Helper to create a membership fee type - defp create_fee_type(attrs, actor) do - default_attrs = %{ - name: "Test Fee Type #{System.unique_integer([:positive])}", - amount: Decimal.new("50.00"), - interval: :yearly - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeType - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: actor) - end - # Helper to create a member. Note: If membership_fee_type_id is provided, # cycles will be auto-generated during creation in test environment. defp create_member(attrs, actor) do diff --git a/test/mv/membership_fees/cycle_generator_test.exs b/test/mv/membership_fees/cycle_generator_test.exs index f193903..a715ca9 100644 --- a/test/mv/membership_fees/cycle_generator_test.exs +++ b/test/mv/membership_fees/cycle_generator_test.exs @@ -4,9 +4,10 @@ defmodule Mv.MembershipFees.CycleGeneratorTest do """ use Mv.DataCase, async: false + import Mv.Fixtures, only: [create_fee_type: 2] + alias Mv.MembershipFees.CycleGenerator alias Mv.MembershipFees.MembershipFeeCycle - alias Mv.MembershipFees.MembershipFeeType require Ash.Query @@ -15,21 +16,6 @@ defmodule Mv.MembershipFees.CycleGeneratorTest do %{actor: system_actor} end - # Helper to create a membership fee type - defp create_fee_type(attrs, actor) do - default_attrs = %{ - name: "Test Fee Type #{System.unique_integer([:positive])}", - amount: Decimal.new("50.00"), - interval: :yearly - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeType - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: actor) - end - # Helper to create a member without triggering cycle generation defp create_member_without_cycles(attrs, actor) do default_attrs = %{ diff --git a/test/mv/membership_fees/membership_fee_cycle_policies_test.exs b/test/mv/membership_fees/membership_fee_cycle_policies_test.exs index cf7e889..e3e37ba 100644 --- a/test/mv/membership_fees/membership_fee_cycle_policies_test.exs +++ b/test/mv/membership_fees/membership_fee_cycle_policies_test.exs @@ -8,6 +8,8 @@ defmodule Mv.MembershipFees.MembershipFeeCyclePoliciesTest do """ use Mv.DataCase, async: false + import Mv.Fixtures, only: [create_fee_type: 1, create_cycle: 3] + alias Mv.Membership alias Mv.MembershipFees @@ -32,41 +34,15 @@ defmodule Mv.MembershipFees.MembershipFeeCyclePoliciesTest do member end - defp create_fee_type_fixture do - admin = Mv.Fixtures.user_with_role_fixture("admin") - - {:ok, fee_type} = - MembershipFees.create_membership_fee_type( - %{ - name: "Test Fee #{System.unique_integer([:positive])}", - amount: Decimal.new("10.00"), - interval: :yearly, - description: "Test" - }, - actor: admin - ) - - fee_type + defp fee_type_fixture do + create_fee_type(%{amount: Decimal.new("10.00"), description: "Test"}) end - defp create_cycle_fixture do - admin = Mv.Fixtures.user_with_role_fixture("admin") - member = create_member_fixture() - fee_type = create_fee_type_fixture() - - {:ok, cycle} = - MembershipFees.create_membership_fee_cycle( - %{ - member_id: member.id, - membership_fee_type_id: fee_type.id, - cycle_start: Date.utc_today(), - amount: Decimal.new("10.00"), - status: :unpaid - }, - actor: admin - ) - - cycle + defp cycle_fixture do + create_cycle(create_member_fixture(), fee_type_fixture(), %{ + cycle_start: Date.utc_today(), + amount: Decimal.new("10.00") + }) end describe "own_data permission set" do @@ -74,7 +50,7 @@ defmodule Mv.MembershipFees.MembershipFeeCyclePoliciesTest do user = Mv.Fixtures.user_with_role_fixture("own_data") linked_member = create_member_fixture() other_member = create_member_fixture() - fee_type = create_fee_type_fixture() + fee_type = fee_type_fixture() admin = Mv.Fixtures.user_with_role_fixture("admin") user = @@ -130,7 +106,7 @@ defmodule Mv.MembershipFees.MembershipFeeCyclePoliciesTest do describe "read_only permission set" do setup %{actor: actor} do user = Mv.Fixtures.user_with_role_fixture("read_only") - cycle = create_cycle_fixture() + cycle = cycle_fixture() %{actor: actor, user: user, cycle: cycle} end @@ -156,7 +132,7 @@ defmodule Mv.MembershipFees.MembershipFeeCyclePoliciesTest do test "cannot create cycle (returns forbidden)", %{user: user, actor: _actor} do member = create_member_fixture() - fee_type = create_fee_type_fixture() + fee_type = fee_type_fixture() assert {:error, %Ash.Error.Forbidden{}} = MembershipFees.create_membership_fee_cycle( @@ -180,7 +156,7 @@ defmodule Mv.MembershipFees.MembershipFeeCyclePoliciesTest do describe "normal_user permission set" do setup %{actor: actor} do user = Mv.Fixtures.user_with_role_fixture("normal_user") - cycle = create_cycle_fixture() + cycle = cycle_fixture() %{actor: actor, user: user, cycle: cycle} end @@ -210,7 +186,7 @@ defmodule Mv.MembershipFees.MembershipFeeCyclePoliciesTest do test "can create cycle", %{user: user, actor: _actor} do member = create_member_fixture() - fee_type = create_fee_type_fixture() + fee_type = fee_type_fixture() assert {:ok, created} = MembershipFees.create_membership_fee_cycle( @@ -235,7 +211,7 @@ defmodule Mv.MembershipFees.MembershipFeeCyclePoliciesTest do describe "admin permission set" do setup %{actor: actor} do user = Mv.Fixtures.user_with_role_fixture("admin") - cycle = create_cycle_fixture() + cycle = cycle_fixture() %{actor: actor, user: user, cycle: cycle} end @@ -270,7 +246,7 @@ defmodule Mv.MembershipFees.MembershipFeeCyclePoliciesTest do test "can create cycle", %{user: user, actor: _actor} do member = create_member_fixture() - fee_type = create_fee_type_fixture() + fee_type = fee_type_fixture() assert {:ok, created} = MembershipFees.create_membership_fee_cycle( diff --git a/test/mv/statistics_test.exs b/test/mv/statistics_test.exs index 6b72ffb..e016a10 100644 --- a/test/mv/statistics_test.exs +++ b/test/mv/statistics_test.exs @@ -6,11 +6,11 @@ defmodule Mv.StatisticsTest do require Ash.Query import Ash.Expr + import Mv.Fixtures, only: [create_fee_type: 2] alias Mv.Membership.Member alias Mv.MembershipFees alias Mv.MembershipFees.MembershipFeeCycle - alias Mv.MembershipFees.MembershipFeeType alias Mv.Statistics setup do @@ -18,22 +18,6 @@ defmodule Mv.StatisticsTest do %{actor: actor} end - defp create_fee_type(actor, attrs) do - MembershipFeeType - |> Ash.Changeset.for_create( - :create, - Map.merge( - %{ - name: "Test Fee #{System.unique_integer([:positive])}", - amount: Decimal.new("50.00"), - interval: :yearly - }, - attrs - ) - ) - |> Ash.create!(actor: actor) - end - describe "first_join_year/1" do test "returns the year of the earliest join_date", %{actor: actor} do Mv.Fixtures.member_fixture(%{join_date: ~D[2019-03-15]}) @@ -131,7 +115,7 @@ defmodule Mv.StatisticsTest do end test "returns totals by status for cycles in that year", %{actor: actor} do - fee_type = create_fee_type(actor, %{amount: Decimal.new("50.00")}) + fee_type = create_fee_type(%{amount: Decimal.new("50.00")}, actor) # Creating members with fee type triggers cycle generation (2020..today). We use 2024 cycles. _member1 = @@ -171,8 +155,8 @@ defmodule Mv.StatisticsTest do test "when fee_type_id is passed in opts, returns only cycles of that fee type", %{ actor: actor } do - fee_type_a = create_fee_type(actor, %{amount: Decimal.new("30.00")}) - fee_type_b = create_fee_type(actor, %{amount: Decimal.new("70.00")}) + fee_type_a = create_fee_type(%{amount: Decimal.new("30.00")}, actor) + fee_type_b = create_fee_type(%{amount: Decimal.new("70.00")}, actor) _m1 = Mv.Fixtures.member_fixture(%{ @@ -207,7 +191,7 @@ defmodule Mv.StatisticsTest do end test "returns sum of amount for all unpaid cycles", %{actor: actor} do - fee_type = create_fee_type(actor, %{amount: Decimal.new("50.00")}) + fee_type = create_fee_type(%{amount: Decimal.new("50.00")}, actor) _member = Mv.Fixtures.member_fixture(%{ diff --git a/test/mv_web/live/membership_fee_type_live/form_test.exs b/test/mv_web/live/membership_fee_type_live/form_test.exs index a836c4d..998fb65 100644 --- a/test/mv_web/live/membership_fee_type_live/form_test.exs +++ b/test/mv_web/live/membership_fee_type_live/form_test.exs @@ -5,6 +5,7 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do use MvWeb.ConnCase, async: false import Phoenix.LiveViewTest + import Mv.Fixtures, only: [create_fee_type: 1] alias Mv.MembershipFees.MembershipFeeType @@ -17,23 +18,6 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do %{conn: authenticated_conn, user: user} end - # Helper to create a membership fee type - defp create_fee_type(attrs) do - system_actor = Mv.Helpers.SystemActor.get_system_actor() - - default_attrs = %{ - name: "Test Fee Type #{System.unique_integer([:positive])}", - amount: Decimal.new("50.00"), - interval: :yearly - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeType - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: system_actor) - end - # Helper to create a member defp create_member(attrs) do system_actor = Mv.Helpers.SystemActor.get_system_actor() diff --git a/test/mv_web/live/membership_fee_type_live/index_test.exs b/test/mv_web/live/membership_fee_type_live/index_test.exs index c65341f..8c99d90 100644 --- a/test/mv_web/live/membership_fee_type_live/index_test.exs +++ b/test/mv_web/live/membership_fee_type_live/index_test.exs @@ -5,6 +5,7 @@ defmodule MvWeb.MembershipFeeTypeLive.IndexTest do use MvWeb.ConnCase, async: false import Phoenix.LiveViewTest + import Mv.Fixtures, only: [create_fee_type: 2] alias Mv.MembershipFees.MembershipFeeType @@ -13,22 +14,6 @@ defmodule MvWeb.MembershipFeeTypeLive.IndexTest do # Use global setup from ConnCase which provides admin user with role # No custom setup needed - # Helper to create a membership fee type - # Uses admin_user to test permissions (UI-/Permissions-nah) - defp create_fee_type(attrs, admin_user) do - default_attrs = %{ - name: "Test Fee Type #{System.unique_integer([:positive])}", - amount: Decimal.new("50.00"), - interval: :yearly - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeType - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: admin_user) - end - # Helper to create a member. Requires actor (e.g. admin_user from setup); no fallback so # missing-actor bugs are not masked in tests. defp create_member(attrs, actor) do diff --git a/test/mv_web/member_live/form_membership_fee_type_test.exs b/test/mv_web/member_live/form_membership_fee_type_test.exs index 5382dfc..01812df 100644 --- a/test/mv_web/member_live/form_membership_fee_type_test.exs +++ b/test/mv_web/member_live/form_membership_fee_type_test.exs @@ -5,27 +5,10 @@ defmodule MvWeb.MemberLive.FormMembershipFeeTypeTest do use MvWeb.ConnCase, async: false import Phoenix.LiveViewTest - - alias Mv.MembershipFees.MembershipFeeType + import Mv.Fixtures, only: [create_fee_type: 2] require Ash.Query - # Helper to create a membership fee type - # Uses admin_user to test permissions (UI-/Permissions-nah) - defp create_fee_type(attrs, admin_user) do - default_attrs = %{ - name: "Test Fee Type #{System.unique_integer([:positive])}", - amount: Decimal.new("50.00"), - interval: :yearly - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeType - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: admin_user) - end - # Helper to create a member # Uses admin_user to test permissions (UI-/Permissions-nah) defp create_member(attrs, admin_user) do diff --git a/test/mv_web/member_live/index/membership_fee_status_test.exs b/test/mv_web/member_live/index/membership_fee_status_test.exs index 5d96e68..8412f61 100644 --- a/test/mv_web/member_live/index/membership_fee_status_test.exs +++ b/test/mv_web/member_live/index/membership_fee_status_test.exs @@ -4,30 +4,13 @@ defmodule MvWeb.MemberLive.Index.MembershipFeeStatusTest do """ use Mv.DataCase, async: false + import Mv.Fixtures, only: [create_fee_type: 1, create_cycle: 3] + alias Mv.Membership.Member - alias Mv.MembershipFees.MembershipFeeCycle - alias Mv.MembershipFees.MembershipFeeType alias MvWeb.MemberLive.Index.MembershipFeeStatus require Ash.Query - # Helper to create a membership fee type - defp create_fee_type(attrs) do - system_actor = Mv.Helpers.SystemActor.get_system_actor() - - default_attrs = %{ - name: "Test Fee Type #{System.unique_integer([:positive])}", - amount: Decimal.new("50.00"), - interval: :yearly - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeType - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: system_actor) - end - # Helper to create a member defp create_member(attrs) do system_actor = Mv.Helpers.SystemActor.get_system_actor() @@ -43,27 +26,6 @@ defmodule MvWeb.MemberLive.Index.MembershipFeeStatusTest do member end - # Helper to create a cycle - # Note: Does not delete existing cycles - tests should manage their own test data - # If cleanup is needed, it should be done in setup or explicitly in the test - defp create_cycle(member, fee_type, attrs) do - system_actor = Mv.Helpers.SystemActor.get_system_actor() - - default_attrs = %{ - cycle_start: ~D[2023-01-01], - amount: Decimal.new("50.00"), - member_id: member.id, - membership_fee_type_id: fee_type.id, - status: :unpaid - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeCycle - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: system_actor) - end - describe "load_cycles_for_members/2" do test "efficiently loads cycles for members" do fee_type = create_fee_type(%{interval: :yearly}) diff --git a/test/mv_web/member_live/index_membership_fee_status_test.exs b/test/mv_web/member_live/index_membership_fee_status_test.exs index ce6bee8..2efe055 100644 --- a/test/mv_web/member_live/index_membership_fee_status_test.exs +++ b/test/mv_web/member_live/index_membership_fee_status_test.exs @@ -5,29 +5,12 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do use MvWeb.ConnCase, async: false import Phoenix.LiveViewTest + import Mv.Fixtures, only: [create_fee_type: 1, create_cycle: 3] alias Mv.MembershipFees.MembershipFeeCycle - alias Mv.MembershipFees.MembershipFeeType require Ash.Query - # Helper to create a membership fee type - defp create_fee_type(attrs) do - system_actor = Mv.Helpers.SystemActor.get_system_actor() - - default_attrs = %{ - name: "Test Fee Type #{System.unique_integer([:positive])}", - amount: Decimal.new("50.00"), - interval: :yearly - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeType - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: system_actor) - end - # Helper to create a member defp create_member(attrs) do system_actor = Mv.Helpers.SystemActor.get_system_actor() @@ -43,38 +26,16 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do member end - # Helper to create a cycle - defp create_cycle(member, fee_type, attrs) do - system_actor = Mv.Helpers.SystemActor.get_system_actor() - - # Delete any auto-generated cycles first to avoid conflicts - existing_cycles = - MembershipFeeCycle - |> Ash.Query.filter(member_id == ^member.id) - |> Ash.read!(actor: system_actor) - - Enum.each(existing_cycles, fn cycle -> Ash.destroy!(cycle, actor: system_actor) end) - - default_attrs = %{ - cycle_start: ~D[2023-01-01], - amount: Decimal.new("50.00"), - member_id: member.id, - membership_fee_type_id: fee_type.id, - status: :unpaid - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeCycle - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: system_actor) - end - describe "status column display" do test "shows status column in member list", %{conn: conn} do fee_type = create_fee_type(%{interval: :yearly}) member = create_member(%{membership_fee_type_id: fee_type.id}) - create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :paid}) + + create_cycle(member, fee_type, %{ + cycle_start: ~D[2023-01-01], + status: :paid, + replace_existing: true + }) {:ok, _view, html} = live(conn, "/members") @@ -85,8 +46,18 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do test "shows last completed cycle status by default", %{conn: conn} do fee_type = create_fee_type(%{interval: :yearly}) member = create_member(%{membership_fee_type_id: fee_type.id}) - create_cycle(member, fee_type, %{cycle_start: ~D[2022-01-01], status: :paid}) - create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid}) + + create_cycle(member, fee_type, %{ + cycle_start: ~D[2022-01-01], + status: :paid, + replace_existing: true + }) + + create_cycle(member, fee_type, %{ + cycle_start: ~D[2023-01-01], + status: :unpaid, + replace_existing: true + }) {:ok, view, _html} = live(conn, "/members") @@ -102,8 +73,17 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do today = Date.utc_today() current_year_start = %{today | month: 1, day: 1} - create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :paid}) - create_cycle(member, fee_type, %{cycle_start: current_year_start, status: :suspended}) + create_cycle(member, fee_type, %{ + cycle_start: ~D[2023-01-01], + status: :paid, + replace_existing: true + }) + + create_cycle(member, fee_type, %{ + cycle_start: current_year_start, + status: :suspended, + replace_existing: true + }) {:ok, view, _html} = live(conn, "/members") @@ -120,7 +100,12 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do test "shows correct color coding for paid status", %{conn: conn} do fee_type = create_fee_type(%{interval: :yearly}) member = create_member(%{membership_fee_type_id: fee_type.id}) - create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :paid}) + + create_cycle(member, fee_type, %{ + cycle_start: ~D[2023-01-01], + status: :paid, + replace_existing: true + }) {:ok, view, _html} = live(conn, "/members") @@ -131,7 +116,12 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do test "shows correct color coding for unpaid status", %{conn: conn} do fee_type = create_fee_type(%{interval: :yearly}) member = create_member(%{membership_fee_type_id: fee_type.id}) - create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid}) + + create_cycle(member, fee_type, %{ + cycle_start: ~D[2023-01-01], + status: :unpaid, + replace_existing: true + }) {:ok, view, _html} = live(conn, "/members") @@ -142,7 +132,12 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do test "shows correct color coding for suspended status", %{conn: conn} do fee_type = create_fee_type(%{interval: :yearly}) member = create_member(%{membership_fee_type_id: fee_type.id}) - create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :suspended}) + + create_cycle(member, fee_type, %{ + cycle_start: ~D[2023-01-01], + status: :suspended, + replace_existing: true + }) {:ok, view, _html} = live(conn, "/members") @@ -169,11 +164,21 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do # Member with unpaid last cycle member1 = create_member(%{first_name: "UnpaidMember", membership_fee_type_id: fee_type.id}) - create_cycle(member1, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid}) + + create_cycle(member1, fee_type, %{ + cycle_start: ~D[2023-01-01], + status: :unpaid, + replace_existing: true + }) # Member with paid last cycle member2 = create_member(%{first_name: "PaidMember", membership_fee_type_id: fee_type.id}) - create_cycle(member2, fee_type, %{cycle_start: ~D[2023-01-01], status: :paid}) + + create_cycle(member2, fee_type, %{ + cycle_start: ~D[2023-01-01], + status: :paid, + replace_existing: true + }) system_actor = Mv.Helpers.SystemActor.get_system_actor() @@ -205,11 +210,21 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do # Member with unpaid current cycle member1 = create_member(%{first_name: "UnpaidCurrent", membership_fee_type_id: fee_type.id}) - create_cycle(member1, fee_type, %{cycle_start: current_year_start, status: :unpaid}) + + create_cycle(member1, fee_type, %{ + cycle_start: current_year_start, + status: :unpaid, + replace_existing: true + }) # Member with paid current cycle member2 = create_member(%{first_name: "PaidCurrent", membership_fee_type_id: fee_type.id}) - create_cycle(member2, fee_type, %{cycle_start: current_year_start, status: :paid}) + + create_cycle(member2, fee_type, %{ + cycle_start: current_year_start, + status: :paid, + replace_existing: true + }) system_actor = Mv.Helpers.SystemActor.get_system_actor() @@ -243,7 +258,12 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do # Create multiple members with cycles Enum.each(1..5, fn _ -> member = create_member(%{membership_fee_type_id: fee_type.id}) - create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :paid}) + + create_cycle(member, fee_type, %{ + cycle_start: ~D[2023-01-01], + status: :paid, + replace_existing: true + }) end) {:ok, _view, html} = live(conn, "/members") diff --git a/test/mv_web/member_live/index_test.exs b/test/mv_web/member_live/index_test.exs index 0cde8fd..be61c9c 100644 --- a/test/mv_web/member_live/index_test.exs +++ b/test/mv_web/member_live/index_test.exs @@ -7,49 +7,9 @@ defmodule MvWeb.MemberLive.IndexTest do alias Mv.Membership alias Mv.Membership.CustomField alias Mv.Membership.CustomFieldValue - alias Mv.MembershipFees.MembershipFeeCycle - alias Mv.MembershipFees.MembershipFeeType alias MvWeb.MemberLive.Index, as: MemberIndex - # Helper to create a membership fee type (shared across all tests) - defp create_fee_type(attrs, actor) do - default_attrs = %{ - name: "Test Fee Type #{System.unique_integer([:positive])}", - amount: Decimal.new("50.00"), - interval: :yearly - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeType - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: actor) - end - - # Helper to create a cycle (shared across all tests) - defp create_cycle(member, fee_type, attrs, actor) do - # Delete any auto-generated cycles first to avoid conflicts - existing_cycles = - MembershipFeeCycle - |> Ash.Query.filter(member_id == ^member.id) - |> Ash.read!(actor: actor) - - Enum.each(existing_cycles, fn cycle -> Ash.destroy!(cycle, actor: actor) end) - - default_attrs = %{ - cycle_start: ~D[2023-01-01], - amount: Decimal.new("50.00"), - member_id: member.id, - membership_fee_type_id: fee_type.id, - status: :unpaid - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeCycle - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: actor) - end + import Mv.Fixtures, only: [create_fee_type: 2, create_cycle: 4] describe "desktop layout: scroll container and sticky table header" do @describetag :ui @@ -1110,7 +1070,7 @@ defmodule MvWeb.MemberLive.IndexTest do create_cycle( paid_member, fee_type, - %{cycle_start: last_year_start, status: :paid}, + %{cycle_start: last_year_start, status: :paid, replace_existing: true}, system_actor ) @@ -1127,7 +1087,7 @@ defmodule MvWeb.MemberLive.IndexTest do create_cycle( unpaid_member, fee_type, - %{cycle_start: last_year_start, status: :unpaid}, + %{cycle_start: last_year_start, status: :unpaid, replace_existing: true}, system_actor ) @@ -1157,7 +1117,7 @@ defmodule MvWeb.MemberLive.IndexTest do create_cycle( paid_member, fee_type, - %{cycle_start: last_year_start, status: :paid}, + %{cycle_start: last_year_start, status: :paid, replace_existing: true}, system_actor ) @@ -1174,7 +1134,7 @@ defmodule MvWeb.MemberLive.IndexTest do create_cycle( unpaid_member, fee_type, - %{cycle_start: last_year_start, status: :unpaid}, + %{cycle_start: last_year_start, status: :unpaid, replace_existing: true}, system_actor ) @@ -1204,7 +1164,7 @@ defmodule MvWeb.MemberLive.IndexTest do create_cycle( paid_member, fee_type, - %{cycle_start: current_year_start, status: :paid}, + %{cycle_start: current_year_start, status: :paid, replace_existing: true}, system_actor ) @@ -1221,7 +1181,7 @@ defmodule MvWeb.MemberLive.IndexTest do create_cycle( unpaid_member, fee_type, - %{cycle_start: current_year_start, status: :unpaid}, + %{cycle_start: current_year_start, status: :unpaid, replace_existing: true}, system_actor ) @@ -1251,7 +1211,7 @@ defmodule MvWeb.MemberLive.IndexTest do create_cycle( paid_member, fee_type, - %{cycle_start: current_year_start, status: :paid}, + %{cycle_start: current_year_start, status: :paid, replace_existing: true}, system_actor ) @@ -1268,7 +1228,7 @@ defmodule MvWeb.MemberLive.IndexTest do create_cycle( unpaid_member, fee_type, - %{cycle_start: current_year_start, status: :unpaid}, + %{cycle_start: current_year_start, status: :unpaid, replace_existing: true}, system_actor ) @@ -2196,7 +2156,7 @@ defmodule MvWeb.MemberLive.IndexTest do create_cycle( member_paid_true, fee_type, - %{cycle_start: last_year_start, status: :paid}, + %{cycle_start: last_year_start, status: :paid, replace_existing: true}, system_actor ) @@ -2224,7 +2184,7 @@ defmodule MvWeb.MemberLive.IndexTest do create_cycle( member_unpaid_true, fee_type, - %{cycle_start: last_year_start, status: :unpaid}, + %{cycle_start: last_year_start, status: :unpaid, replace_existing: true}, system_actor ) diff --git a/test/mv_web/member_live/membership_fee_integration_test.exs b/test/mv_web/member_live/membership_fee_integration_test.exs index ac60220..16f1064 100644 --- a/test/mv_web/member_live/membership_fee_integration_test.exs +++ b/test/mv_web/member_live/membership_fee_integration_test.exs @@ -5,29 +5,12 @@ defmodule MvWeb.MemberLive.MembershipFeeIntegrationTest do use MvWeb.ConnCase, async: false import Phoenix.LiveViewTest + import Mv.Fixtures, only: [create_fee_type: 1] alias Mv.MembershipFees.MembershipFeeCycle - alias Mv.MembershipFees.MembershipFeeType require Ash.Query - # Helper to create a membership fee type - defp create_fee_type(attrs) do - system_actor = Mv.Helpers.SystemActor.get_system_actor() - - default_attrs = %{ - name: "Test Fee Type #{System.unique_integer([:positive])}", - amount: Decimal.new("50.00"), - interval: :yearly - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeType - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: system_actor) - end - # Helper to create a member defp create_member(attrs) do system_actor = Mv.Helpers.SystemActor.get_system_actor() diff --git a/test/mv_web/member_live/show_membership_fees_test.exs b/test/mv_web/member_live/show_membership_fees_test.exs index 394d743..9c1b73f 100644 --- a/test/mv_web/member_live/show_membership_fees_test.exs +++ b/test/mv_web/member_live/show_membership_fees_test.exs @@ -5,56 +5,12 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do use MvWeb.ConnCase, async: false import Phoenix.LiveViewTest + import Mv.Fixtures, only: [create_fee_type: 1, create_cycle: 3] alias Mv.MembershipFees.MembershipFeeCycle - alias Mv.MembershipFees.MembershipFeeType require Ash.Query - # Helper to create a membership fee type - defp create_fee_type(attrs) do - system_actor = Mv.Helpers.SystemActor.get_system_actor() - - default_attrs = %{ - name: "Test Fee Type #{System.unique_integer([:positive])}", - amount: Decimal.new("50.00"), - interval: :yearly - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeType - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: system_actor) - end - - # Helper to create a cycle - defp create_cycle(member, fee_type, attrs) do - system_actor = Mv.Helpers.SystemActor.get_system_actor() - - # Delete any auto-generated cycles first to avoid conflicts - existing_cycles = - MembershipFeeCycle - |> Ash.Query.filter(member_id == ^member.id) - |> Ash.read!(actor: system_actor) - - Enum.each(existing_cycles, fn cycle -> Ash.destroy!(cycle, actor: system_actor) end) - - default_attrs = %{ - cycle_start: ~D[2023-01-01], - amount: Decimal.new("50.00"), - member_id: member.id, - membership_fee_type_id: fee_type.id, - status: :unpaid - } - - attrs = Map.merge(default_attrs, attrs) - - MembershipFeeCycle - |> Ash.Changeset.for_create(:create, attrs) - |> Ash.create!(actor: system_actor) - end - describe "cycle-regeneration control tooltip (ยง3.5 icon/tooltip audit)" do test "the regenerate_cycles control carries a tooltip and accessible label", %{conn: conn} do fee_type = create_fee_type(%{interval: :yearly}) @@ -76,8 +32,19 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do fee_type = create_fee_type(%{interval: :yearly}) member = Mv.Fixtures.member_fixture(%{membership_fee_type_id: fee_type.id}) - _cycle1 = create_cycle(member, fee_type, %{cycle_start: ~D[2022-01-01], status: :paid}) - _cycle2 = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid}) + _cycle1 = + create_cycle(member, fee_type, %{ + cycle_start: ~D[2022-01-01], + status: :paid, + replace_existing: true + }) + + _cycle2 = + create_cycle(member, fee_type, %{ + cycle_start: ~D[2023-01-01], + status: :unpaid, + replace_existing: true + }) {:ok, view, _html} = live(conn, "/members/#{member.id}") @@ -101,7 +68,8 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do create_cycle(member, fee_type, %{ cycle_start: ~D[2023-01-01], amount: Decimal.new("60.00"), - status: :paid + status: :paid, + replace_existing: true }) {:ok, view, _html} = live(conn, "/members/#{member.id}") @@ -158,7 +126,12 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do fee_type = create_fee_type(%{interval: :yearly}) member = Mv.Fixtures.member_fixture(%{membership_fee_type_id: fee_type.id}) - cycle = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid}) + cycle = + create_cycle(member, fee_type, %{ + cycle_start: ~D[2023-01-01], + status: :unpaid, + replace_existing: true + }) {:ok, view, _html} = live(conn, "/members/#{member.id}") @@ -189,7 +162,12 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do fee_type = create_fee_type(%{interval: :yearly}) member = Mv.Fixtures.member_fixture(%{membership_fee_type_id: fee_type.id}) - cycle = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid}) + cycle = + create_cycle(member, fee_type, %{ + cycle_start: ~D[2023-01-01], + status: :unpaid, + replace_existing: true + }) {:ok, view, _html} = live(conn, "/members/#{member.id}") @@ -220,7 +198,12 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do fee_type = create_fee_type(%{interval: :yearly}) member = Mv.Fixtures.member_fixture(%{membership_fee_type_id: fee_type.id}) - cycle = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :paid}) + cycle = + create_cycle(member, fee_type, %{ + cycle_start: ~D[2023-01-01], + status: :paid, + replace_existing: true + }) {:ok, view, _html} = live(conn, "/members/#{member.id}") @@ -316,7 +299,13 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do } do fee_type = create_fee_type(%{interval: :yearly}) member = Mv.Fixtures.member_fixture(%{membership_fee_type_id: fee_type.id}) - _cycle = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid}) + + _cycle = + create_cycle(member, fee_type, %{ + cycle_start: ~D[2023-01-01], + status: :unpaid, + replace_existing: true + }) {:ok, view, _html} = live(conn, "/members/#{member.id}") @@ -335,7 +324,13 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do } do fee_type = create_fee_type(%{interval: :yearly}) member = Mv.Fixtures.member_fixture(%{membership_fee_type_id: fee_type.id}) - cycle = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid}) + + cycle = + create_cycle(member, fee_type, %{ + cycle_start: ~D[2023-01-01], + status: :unpaid, + replace_existing: true + }) {:ok, view, _html} = live(conn, "/members/#{member.id}") @@ -361,7 +356,13 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do # This test verifies that Ash.destroy(cycle, actor: read_only_user) returns Forbidden. fee_type = create_fee_type(%{interval: :yearly}) member = Mv.Fixtures.member_fixture(%{membership_fee_type_id: fee_type.id}) - cycle = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid}) + + cycle = + create_cycle(member, fee_type, %{ + cycle_start: ~D[2023-01-01], + status: :unpaid, + replace_existing: true + }) assert {:error, %Ash.Error.Forbidden{}} = Ash.destroy(cycle, domain: Mv.MembershipFees, actor: read_only_user) @@ -389,8 +390,20 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do fee_type = create_fee_type(%{interval: :yearly}) member = Mv.Fixtures.member_fixture(%{membership_fee_type_id: fee_type.id}) - _c1 = create_cycle(member, fee_type, %{cycle_start: ~D[2022-01-01], status: :paid}) - _c2 = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid}) + + _c1 = + create_cycle(member, fee_type, %{ + cycle_start: ~D[2022-01-01], + status: :paid, + replace_existing: true + }) + + _c2 = + create_cycle(member, fee_type, %{ + cycle_start: ~D[2023-01-01], + status: :unpaid, + replace_existing: true + }) {:ok, view, _html} = live(conn, "/members/#{member.id}") diff --git a/test/support/fixtures.ex b/test/support/fixtures.ex index 73bf12a..e022600 100644 --- a/test/support/fixtures.ex +++ b/test/support/fixtures.ex @@ -335,4 +335,82 @@ defmodule Mv.Fixtures do {:ok, request} = Membership.confirm_join_request(token, actor: nil) request end + + @doc """ + Creates a membership fee type with default or custom attributes. + + Defaults: a unique `name`, `amount` 50.00, `interval` :yearly. + + ## Parameters + - `attrs` - Map of attributes to override defaults (e.g. `%{interval: :monthly}`). + - `actor` - the authorization actor; defaults to the system actor when omitted/nil. + + ## Returns + - MembershipFeeType struct. + + ## Examples + + iex> create_fee_type(%{interval: :monthly}) + %Mv.MembershipFees.MembershipFeeType{interval: :monthly, ...} + + iex> create_fee_type(%{amount: Decimal.new("10.00")}, admin) + %Mv.MembershipFees.MembershipFeeType{...} + """ + def create_fee_type(attrs \\ %{}, actor \\ nil) do + actor = actor || SystemActor.get_system_actor() + + default_attrs = %{ + name: "Test Fee Type #{System.unique_integer([:positive])}", + amount: Decimal.new("50.00"), + interval: :yearly + } + + Mv.MembershipFees.MembershipFeeType + |> Ash.Changeset.for_create(:create, Map.merge(default_attrs, attrs)) + |> Ash.create!(actor: actor) + end + + @doc """ + Creates a membership fee cycle for the given member and fee type. + + Defaults: `cycle_start` ~D[2024-01-01], `amount` 50.00, `status` :unpaid, + with `member_id`/`membership_fee_type_id` derived from the passed structs. + + ## Parameters + - `member` - the Member struct the cycle belongs to. + - `fee_type` - the MembershipFeeType struct the cycle references. + - `attrs` - Map overriding the cycle defaults (e.g. `%{cycle_start: ~D[2023-01-01], status: :paid}`). + A reserved `:replace_existing` key (truthy) deletes any pre-existing cycles for the member + before creating the new one (used where auto-generated cycles would otherwise conflict); + it is stripped from the attrs and never passed to the create action. Defaults to absent/false. + - `actor` - the authorization actor; defaults to the system actor when omitted/nil. + + ## Returns + - MembershipFeeCycle struct. + """ + def create_cycle(member, fee_type, attrs \\ %{}, actor \\ nil) do + actor = actor || SystemActor.get_system_actor() + {replace_existing, attrs} = Map.pop(attrs, :replace_existing, false) + + if replace_existing do + require Ash.Query + + Mv.MembershipFees.MembershipFeeCycle + |> Ash.Query.filter(member_id == ^member.id) + |> Ash.read!(actor: actor) + |> Enum.each(&Ash.destroy!(&1, actor: actor)) + end + + default_attrs = %{ + cycle_start: ~D[2024-01-01], + amount: Decimal.new("50.00"), + member_id: member.id, + membership_fee_type_id: fee_type.id, + status: :unpaid + } + + Mv.MembershipFees.MembershipFeeCycle + |> Ash.Changeset.for_create(:create, Map.merge(default_attrs, attrs)) + |> Ash.create!(actor: actor) + end end