Pass actor through CycleGenerator so seeds can use admin
- get_actor(opts): use opts[:actor] or system actor - load_member, do_generate_cycles, create_cycles pass opts - Seeds pass admin_user_with_role for Ash.load! and cycle updates
This commit is contained in:
parent
6e309622a0
commit
a263cb4954
2 changed files with 34 additions and 28 deletions
|
|
@ -87,7 +87,7 @@ defmodule Mv.MembershipFees.CycleGenerator do
|
||||||
def generate_cycles_for_member(member_or_id, opts \\ [])
|
def generate_cycles_for_member(member_or_id, opts \\ [])
|
||||||
|
|
||||||
def generate_cycles_for_member(member_id, opts) when is_binary(member_id) do
|
def generate_cycles_for_member(member_id, opts) when is_binary(member_id) do
|
||||||
case load_member(member_id) do
|
case load_member(member_id, opts) do
|
||||||
{:ok, member} -> generate_cycles_for_member(member, opts)
|
{:ok, member} -> generate_cycles_for_member(member, opts)
|
||||||
{:error, reason} -> {:error, reason}
|
{:error, reason} -> {:error, reason}
|
||||||
end
|
end
|
||||||
|
|
@ -97,25 +97,25 @@ defmodule Mv.MembershipFees.CycleGenerator do
|
||||||
today = Keyword.get(opts, :today, Date.utc_today())
|
today = Keyword.get(opts, :today, Date.utc_today())
|
||||||
skip_lock? = Keyword.get(opts, :skip_lock?, false)
|
skip_lock? = Keyword.get(opts, :skip_lock?, false)
|
||||||
|
|
||||||
do_generate_cycles_with_lock(member, today, skip_lock?)
|
do_generate_cycles_with_lock(member, today, skip_lock?, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Generate cycles with lock handling
|
# Generate cycles with lock handling
|
||||||
# Returns {:ok, cycles, notifications} - notifications are never sent here,
|
# Returns {:ok, cycles, notifications} - notifications are never sent here,
|
||||||
# they should be returned to the caller (e.g., via after_action hook)
|
# they should be returned to the caller (e.g., via after_action hook)
|
||||||
defp do_generate_cycles_with_lock(member, today, true = _skip_lock?) do
|
defp do_generate_cycles_with_lock(member, today, true = _skip_lock?, opts) do
|
||||||
# Lock already set by caller (e.g., regenerate_cycles_on_type_change)
|
# Lock already set by caller (e.g., regenerate_cycles_on_type_change or seeds)
|
||||||
# Just generate cycles without additional locking
|
# Just generate cycles without additional locking
|
||||||
do_generate_cycles(member, today)
|
do_generate_cycles(member, today, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_generate_cycles_with_lock(member, today, false) do
|
defp do_generate_cycles_with_lock(member, today, false, opts) do
|
||||||
lock_key = :erlang.phash2(member.id)
|
lock_key = :erlang.phash2(member.id)
|
||||||
|
|
||||||
Repo.transaction(fn ->
|
Repo.transaction(fn ->
|
||||||
Ecto.Adapters.SQL.query!(Repo, "SELECT pg_advisory_xact_lock($1)", [lock_key])
|
Ecto.Adapters.SQL.query!(Repo, "SELECT pg_advisory_xact_lock($1)", [lock_key])
|
||||||
|
|
||||||
case do_generate_cycles(member, today) do
|
case do_generate_cycles(member, today, opts) do
|
||||||
{:ok, cycles, notifications} ->
|
{:ok, cycles, notifications} ->
|
||||||
# Return cycles and notifications - do NOT send notifications here
|
# Return cycles and notifications - do NOT send notifications here
|
||||||
# They will be sent by the caller (e.g., via after_action hook)
|
# They will be sent by the caller (e.g., via after_action hook)
|
||||||
|
|
@ -235,25 +235,33 @@ defmodule Mv.MembershipFees.CycleGenerator do
|
||||||
|
|
||||||
# Private functions
|
# Private functions
|
||||||
|
|
||||||
defp load_member(member_id) do
|
# Use actor from opts when provided (e.g. seeds pass admin); otherwise system actor
|
||||||
system_actor = SystemActor.get_system_actor()
|
defp get_actor(opts) do
|
||||||
opts = Helpers.ash_actor_opts(system_actor)
|
case Keyword.get(opts, :actor) do
|
||||||
|
nil -> SystemActor.get_system_actor()
|
||||||
|
actor -> actor
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp load_member(member_id, opts) do
|
||||||
|
actor = get_actor(opts)
|
||||||
|
read_opts = Helpers.ash_actor_opts(actor)
|
||||||
|
|
||||||
query =
|
query =
|
||||||
Member
|
Member
|
||||||
|> Ash.Query.filter(id == ^member_id)
|
|> Ash.Query.filter(id == ^member_id)
|
||||||
|> Ash.Query.load([:membership_fee_type, :membership_fee_cycles])
|
|> Ash.Query.load([:membership_fee_type, :membership_fee_cycles])
|
||||||
|
|
||||||
case Ash.read_one(query, opts) do
|
case Ash.read_one(query, read_opts) do
|
||||||
{:ok, nil} -> {:error, :member_not_found}
|
{:ok, nil} -> {:error, :member_not_found}
|
||||||
{:ok, member} -> {:ok, member}
|
{:ok, member} -> {:ok, member}
|
||||||
{:error, reason} -> {:error, reason}
|
{:error, reason} -> {:error, reason}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp do_generate_cycles(member, today) do
|
defp do_generate_cycles(member, today, opts) do
|
||||||
# Reload member with relationships to ensure fresh data
|
# Reload member with relationships to ensure fresh data
|
||||||
case load_member(member.id) do
|
case load_member(member.id, opts) do
|
||||||
{:ok, member} ->
|
{:ok, member} ->
|
||||||
cond do
|
cond do
|
||||||
is_nil(member.membership_fee_type_id) ->
|
is_nil(member.membership_fee_type_id) ->
|
||||||
|
|
@ -263,7 +271,7 @@ defmodule Mv.MembershipFees.CycleGenerator do
|
||||||
{:error, :no_join_date}
|
{:error, :no_join_date}
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
generate_missing_cycles(member, today)
|
generate_missing_cycles(member, today, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
{:error, reason} ->
|
{:error, reason} ->
|
||||||
|
|
@ -271,7 +279,7 @@ defmodule Mv.MembershipFees.CycleGenerator do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp generate_missing_cycles(member, today) do
|
defp generate_missing_cycles(member, today, opts) do
|
||||||
fee_type = member.membership_fee_type
|
fee_type = member.membership_fee_type
|
||||||
interval = fee_type.interval
|
interval = fee_type.interval
|
||||||
amount = fee_type.amount
|
amount = fee_type.amount
|
||||||
|
|
@ -287,7 +295,7 @@ defmodule Mv.MembershipFees.CycleGenerator do
|
||||||
# Only generate if start_date <= end_date
|
# Only generate if start_date <= end_date
|
||||||
if start_date && Date.compare(start_date, end_date) != :gt do
|
if start_date && Date.compare(start_date, end_date) != :gt do
|
||||||
cycle_starts = generate_cycle_starts(start_date, end_date, interval)
|
cycle_starts = generate_cycle_starts(start_date, end_date, interval)
|
||||||
create_cycles(cycle_starts, member.id, fee_type.id, amount)
|
create_cycles(cycle_starts, member.id, fee_type.id, amount, opts)
|
||||||
else
|
else
|
||||||
{:ok, [], []}
|
{:ok, [], []}
|
||||||
end
|
end
|
||||||
|
|
@ -382,9 +390,9 @@ defmodule Mv.MembershipFees.CycleGenerator do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp create_cycles(cycle_starts, member_id, fee_type_id, amount) do
|
defp create_cycles(cycle_starts, member_id, fee_type_id, amount, opts) do
|
||||||
system_actor = SystemActor.get_system_actor()
|
actor = get_actor(opts)
|
||||||
opts = Helpers.ash_actor_opts(system_actor)
|
create_opts = Helpers.ash_actor_opts(actor)
|
||||||
|
|
||||||
# Always use return_notifications?: true to collect notifications
|
# Always use return_notifications?: true to collect notifications
|
||||||
# Notifications will be returned to the caller, who is responsible for
|
# Notifications will be returned to the caller, who is responsible for
|
||||||
|
|
@ -400,7 +408,7 @@ defmodule Mv.MembershipFees.CycleGenerator do
|
||||||
}
|
}
|
||||||
|
|
||||||
handle_cycle_creation_result(
|
handle_cycle_creation_result(
|
||||||
Ash.create(MembershipFeeCycle, attrs, [return_notifications?: true] ++ opts),
|
Ash.create(MembershipFeeCycle, attrs, [return_notifications?: true] ++ create_opts),
|
||||||
cycle_start
|
cycle_start
|
||||||
)
|
)
|
||||||
end)
|
end)
|
||||||
|
|
|
||||||
|
|
@ -379,10 +379,9 @@ Enum.each(member_attrs_list, fn member_attrs ->
|
||||||
|
|
||||||
# Generate cycles if member has a fee type
|
# Generate cycles if member has a fee type
|
||||||
if final_member.membership_fee_type_id do
|
if final_member.membership_fee_type_id do
|
||||||
# Load member with cycles to check if they already exist
|
# Load member with cycles to check if they already exist (actor required for auth)
|
||||||
member_with_cycles =
|
member_with_cycles =
|
||||||
final_member
|
Ash.load!(final_member, :membership_fee_cycles, actor: admin_user_with_role)
|
||||||
|> Ash.load!(:membership_fee_cycles)
|
|
||||||
|
|
||||||
# Only generate if no cycles exist yet (to avoid duplicates on re-run)
|
# Only generate if no cycles exist yet (to avoid duplicates on re-run)
|
||||||
cycles =
|
cycles =
|
||||||
|
|
@ -427,7 +426,7 @@ Enum.each(member_attrs_list, fn member_attrs ->
|
||||||
if cycle.status != status do
|
if cycle.status != status do
|
||||||
cycle
|
cycle
|
||||||
|> Ash.Changeset.for_update(:update, %{status: status})
|
|> Ash.Changeset.for_update(:update, %{status: status})
|
||||||
|> Ash.update!(actor: admin_user_with_role)
|
|> Ash.update!(actor: admin_user_with_role, domain: Mv.MembershipFees)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
@ -542,10 +541,9 @@ Enum.with_index(linked_members)
|
||||||
|
|
||||||
# Generate cycles for linked members
|
# Generate cycles for linked members
|
||||||
if final_member.membership_fee_type_id do
|
if final_member.membership_fee_type_id do
|
||||||
# Load member with cycles to check if they already exist
|
# Load member with cycles to check if they already exist (actor required for auth)
|
||||||
member_with_cycles =
|
member_with_cycles =
|
||||||
final_member
|
Ash.load!(final_member, :membership_fee_cycles, actor: admin_user_with_role)
|
||||||
|> Ash.load!(:membership_fee_cycles)
|
|
||||||
|
|
||||||
# Only generate if no cycles exist yet (to avoid duplicates on re-run)
|
# Only generate if no cycles exist yet (to avoid duplicates on re-run)
|
||||||
cycles =
|
cycles =
|
||||||
|
|
@ -575,7 +573,7 @@ Enum.with_index(linked_members)
|
||||||
if cycle.status != status do
|
if cycle.status != status do
|
||||||
cycle
|
cycle
|
||||||
|> Ash.Changeset.for_update(:update, %{status: status})
|
|> Ash.Changeset.for_update(:update, %{status: status})
|
||||||
|> Ash.update!()
|
|> Ash.update!(actor: admin_user_with_role, domain: Mv.MembershipFees)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue