Member/Setting/validations: domain, actor, and seeds

- setting.ex: domain/authorize for default_membership_fee_type_id check
- validate_same_interval: require membership_fee_type (no None)
- set_membership_fee_start_date: domain/actor for fee type lookup
- Validations: domain/authorize for cross-resource checks
- helpers.ex, email_sync change, seeds.exs actor/authorize fixes
- Update related tests
This commit is contained in:
Moritz 2026-02-03 23:52:16 +01:00
parent 5889683854
commit 5ed41555e9
13 changed files with 118 additions and 55 deletions

View file

@ -31,12 +31,12 @@ defmodule Mv.MembershipFees.Changes.SetMembershipFeeStartDate do
alias Mv.MembershipFees.CalendarCycles
@impl true
def change(changeset, _opts, _context) do
def change(changeset, _opts, context) do
# Only calculate if membership_fee_start_date is not already set
if has_start_date?(changeset) do
changeset
else
calculate_and_set_start_date(changeset)
calculate_and_set_start_date(changeset, context)
end
end
@ -56,10 +56,13 @@ defmodule Mv.MembershipFees.Changes.SetMembershipFeeStartDate do
end
end
defp calculate_and_set_start_date(changeset) do
defp calculate_and_set_start_date(changeset, context) do
actor = Map.get(context || %{}, :actor)
opts = if actor, do: [actor: actor], else: []
with {:ok, join_date} <- get_join_date(changeset),
{:ok, membership_fee_type_id} <- get_membership_fee_type_id(changeset),
{:ok, interval} <- get_interval(membership_fee_type_id),
{:ok, interval} <- get_interval(membership_fee_type_id, opts),
{:ok, include_joining_cycle} <- get_include_joining_cycle() do
start_date = calculate_start_date(join_date, interval, include_joining_cycle)
Ash.Changeset.force_change_attribute(changeset, :membership_fee_start_date, start_date)
@ -118,8 +121,8 @@ defmodule Mv.MembershipFees.Changes.SetMembershipFeeStartDate do
end
end
defp get_interval(membership_fee_type_id) do
case Ash.get(Mv.MembershipFees.MembershipFeeType, membership_fee_type_id) do
defp get_interval(membership_fee_type_id, opts) do
case Ash.get(Mv.MembershipFees.MembershipFeeType, membership_fee_type_id, opts) do
{:ok, %{interval: interval}} -> {:ok, interval}
{:error, _} -> {:error, :membership_fee_type_not_found}
end

View file

@ -19,9 +19,9 @@ defmodule Mv.MembershipFees.Changes.ValidateSameInterval do
use Ash.Resource.Change
@impl true
def change(changeset, _opts, _context) do
def change(changeset, _opts, context) do
if changing_membership_fee_type?(changeset) do
validate_interval_match(changeset)
validate_interval_match(changeset, context)
else
changeset
end
@ -33,9 +33,10 @@ defmodule Mv.MembershipFees.Changes.ValidateSameInterval do
end
# Validate that the new type has the same interval as the current type
defp validate_interval_match(changeset) do
defp validate_interval_match(changeset, context) do
current_type_id = get_current_type_id(changeset)
new_type_id = get_new_type_id(changeset)
actor = Map.get(context || %{}, :actor)
cond do
# If no current type, allow any change (first assignment)
@ -48,13 +49,13 @@ defmodule Mv.MembershipFees.Changes.ValidateSameInterval do
# Both types exist - validate intervals match
true ->
validate_intervals_match(changeset, current_type_id, new_type_id)
validate_intervals_match(changeset, current_type_id, new_type_id, actor)
end
end
# Validates that intervals match when both types exist
defp validate_intervals_match(changeset, current_type_id, new_type_id) do
case get_intervals(current_type_id, new_type_id) do
defp validate_intervals_match(changeset, current_type_id, new_type_id, actor) do
case get_intervals(current_type_id, new_type_id, actor) do
{:ok, current_interval, new_interval} ->
if current_interval == new_interval do
changeset
@ -85,11 +86,16 @@ defmodule Mv.MembershipFees.Changes.ValidateSameInterval do
end
end
# Get intervals for both types
defp get_intervals(current_type_id, new_type_id) do
# Get intervals for both types (actor required for authorization when resource has policies)
defp get_intervals(current_type_id, new_type_id, actor) do
alias Mv.MembershipFees.MembershipFeeType
case {Ash.get(MembershipFeeType, current_type_id), Ash.get(MembershipFeeType, new_type_id)} do
opts = if actor, do: [actor: actor], else: []
case {
Ash.get(MembershipFeeType, current_type_id, opts),
Ash.get(MembershipFeeType, new_type_id, opts)
} do
{{:ok, current_type}, {:ok, new_type}} ->
{:ok, current_type.interval, new_type.interval}