MemberLive: confirm_delete_all_cycles via Ash.destroy, reduce current_actor

- Delete each cycle with Ash.destroy(actor:) so policies apply; add do_delete_all_cycles/5.
- Use positive can? check; remove duplicate current_actor(socket) in change_membership_fee_type.
This commit is contained in:
Moritz 2026-02-04 00:34:00 +01:00
parent e799f0271c
commit 182d34fe58

View file

@ -457,7 +457,7 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|> assign(:cycles, []) |> assign(:cycles, [])
|> assign( |> assign(
:available_fee_types, :available_fee_types,
get_available_fee_types(updated_member, current_actor(socket)) get_available_fee_types(updated_member, actor)
) )
|> assign(:interval_warning, nil) |> assign(:interval_warning, nil)
|> put_flash(:info, gettext("Membership fee type removed"))} |> put_flash(:info, gettext("Membership fee type removed"))}
@ -488,13 +488,9 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
if interval_warning do if interval_warning do
{:noreply, assign(socket, :interval_warning, interval_warning)} {:noreply, assign(socket, :interval_warning, interval_warning)}
else else
actor = current_actor(socket)
case update_member_fee_type(member, fee_type_id, actor) do case update_member_fee_type(member, fee_type_id, actor) do
{:ok, updated_member} -> {:ok, updated_member} ->
# Reload member with cycles # Reload member with cycles
actor = current_actor(socket)
updated_member = updated_member =
updated_member updated_member
|> Ash.load!( |> Ash.load!(
@ -520,7 +516,7 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
|> assign(:cycles, cycles) |> assign(:cycles, cycles)
|> assign( |> assign(
:available_fee_types, :available_fee_types,
get_available_fee_types(updated_member, current_actor(socket)) get_available_fee_types(updated_member, actor)
) )
|> assign(:interval_warning, nil) |> assign(:interval_warning, nil)
|> put_flash(:info, gettext("Membership fee type updated. Cycles regenerated."))} |> put_flash(:info, gettext("Membership fee type updated. Cycles regenerated."))}
@ -730,61 +726,31 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
confirmation = String.trim(String.downcase(socket.assigns.delete_all_confirmation)) confirmation = String.trim(String.downcase(socket.assigns.delete_all_confirmation))
expected = String.downcase(gettext("Yes")) expected = String.downcase(gettext("Yes"))
if confirmation != expected do if confirmation == expected do
member = socket.assigns.member
actor = current_actor(socket)
cycles = socket.assigns.cycles
reset_modal = fn s ->
s
|> assign(:deleting_all_cycles, false)
|> assign(:delete_all_confirmation, "")
end
if can?(actor, :destroy, MembershipFeeCycle) do
do_delete_all_cycles(socket, member, actor, cycles, reset_modal)
else
{:noreply,
socket
|> reset_modal.()
|> put_flash(:error, format_error(%Ash.Error.Forbidden{}))}
end
else
{:noreply, {:noreply,
socket socket
|> assign(:deleting_all_cycles, false) |> assign(:deleting_all_cycles, false)
|> assign(:delete_all_confirmation, "") |> assign(:delete_all_confirmation, "")
|> put_flash(:error, gettext("Confirmation text does not match"))} |> put_flash(:error, gettext("Confirmation text does not match"))}
else
member = socket.assigns.member
# Delete all cycles atomically using Ecto query
import Ecto.Query
deleted_count =
Mv.Repo.delete_all(
from c in Mv.MembershipFees.MembershipFeeCycle,
where: c.member_id == ^member.id
)
if deleted_count > 0 do
# Reload member to get updated cycles
actor = current_actor(socket)
updated_member =
member
|> Ash.load!(
[
:membership_fee_type,
membership_fee_cycles: [:membership_fee_type]
],
actor: actor
)
updated_cycles =
Enum.sort_by(
updated_member.membership_fee_cycles || [],
& &1.cycle_start,
{:desc, Date}
)
send(self(), {:member_updated, updated_member})
{:noreply,
socket
|> assign(:member, updated_member)
|> assign(:cycles, updated_cycles)
|> assign(:deleting_all_cycles, false)
|> assign(:delete_all_confirmation, "")
|> put_flash(:info, gettext("All cycles deleted"))}
else
{:noreply,
socket
|> assign(:deleting_all_cycles, false)
|> assign(:delete_all_confirmation, "")
|> put_flash(:info, gettext("No cycles to delete"))}
end
end end
end end
@ -903,6 +869,55 @@ defmodule MvWeb.MemberLive.Show.MembershipFeesComponent do
# Helper functions # Helper functions
defp do_delete_all_cycles(socket, member, actor, cycles, reset_modal) do
result =
Enum.reduce_while(cycles, {:ok, 0}, fn cycle, {:ok, count} ->
case Ash.destroy(cycle, domain: MembershipFees, actor: actor) do
:ok -> {:cont, {:ok, count + 1}}
{:ok, _} -> {:cont, {:ok, count + 1}}
{:error, error} -> {:halt, {:error, error}}
end
end)
case result do
{:ok, deleted_count} when deleted_count > 0 ->
updated_member =
member
|> Ash.load!(
[:membership_fee_type, membership_fee_cycles: [:membership_fee_type]],
actor: actor
)
updated_cycles =
Enum.sort_by(
updated_member.membership_fee_cycles || [],
& &1.cycle_start,
{:desc, Date}
)
send(self(), {:member_updated, updated_member})
{:noreply,
socket
|> assign(:member, updated_member)
|> assign(:cycles, updated_cycles)
|> reset_modal.()
|> put_flash(:info, gettext("All cycles deleted"))}
{:ok, _} ->
{:noreply,
socket
|> reset_modal.()
|> put_flash(:info, gettext("No cycles to delete"))}
{:error, error} ->
{:noreply,
socket
|> reset_modal.()
|> put_flash(:error, format_error(error))}
end
end
defp get_available_fee_types(member, actor) do defp get_available_fee_types(member, actor) do
all_types = all_types =
MembershipFeeType MembershipFeeType