diff --git a/lib/membership/member.ex b/lib/membership/member.ex index 234fc6f..5e574d6 100644 --- a/lib/membership/member.ex +++ b/lib/membership/member.ex @@ -705,7 +705,6 @@ defmodule Mv.Membership.Member do # Deletes future unpaid cycles and regenerates them with the new type/amount # Uses advisory lock to prevent concurrent modifications defp regenerate_cycles_on_type_change(member) do - require Ash.Query alias Mv.Repo today = Date.utc_today() @@ -714,26 +713,39 @@ defmodule Mv.Membership.Member do # Use advisory lock to prevent concurrent deletion and regeneration # This ensures atomicity when multiple updates happen simultaneously if Repo.in_transaction?() do - # Already in transaction: use advisory lock directly - Ecto.Adapters.SQL.query!(Repo, "SELECT pg_advisory_xact_lock($1)", [lock_key]) - do_regenerate_cycles_on_type_change(member, today) + regenerate_cycles_in_transaction(member, today, lock_key) else - # Not in transaction: start new transaction with advisory lock - Repo.transaction(fn -> - Ecto.Adapters.SQL.query!(Repo, "SELECT pg_advisory_xact_lock($1)", [lock_key]) - - case do_regenerate_cycles_on_type_change(member, today) do - :ok -> :ok - {:error, reason} -> Repo.rollback(reason) - end - end) - |> case do - {:ok, result} -> result - {:error, reason} -> {:error, reason} - end + regenerate_cycles_new_transaction(member, today, lock_key) end end + # Already in transaction: use advisory lock directly + defp regenerate_cycles_in_transaction(member, today, lock_key) do + alias Mv.Repo + + Ecto.Adapters.SQL.query!(Repo, "SELECT pg_advisory_xact_lock($1)", [lock_key]) + do_regenerate_cycles_on_type_change(member, today) + end + + # Not in transaction: start new transaction with advisory lock + defp regenerate_cycles_new_transaction(member, today, lock_key) do + alias Mv.Repo + + Repo.transaction(fn -> + Ecto.Adapters.SQL.query!(Repo, "SELECT pg_advisory_xact_lock($1)", [lock_key]) + + case do_regenerate_cycles_on_type_change(member, today) do + :ok -> :ok + {:error, reason} -> Repo.rollback(reason) + end + end) + |> handle_transaction_result() + end + + # Handle transaction result + defp handle_transaction_result({:ok, result}), do: result + defp handle_transaction_result({:error, reason}), do: {:error, reason} + # Performs the actual cycle deletion and regeneration defp do_regenerate_cycles_on_type_change(member, today) do require Ash.Query