Membership Fee 6 - UI Components & LiveViews closes #280 #304
2 changed files with 59 additions and 10 deletions
|
|
@ -242,6 +242,63 @@ defmodule Mv.Membership.Member do
|
||||||
{:ok, member}
|
{:ok, member}
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
# Trigger cycle regeneration when join_date or exit_date changes
|
||||||
|
# Regenerates cycles based on new dates
|
||||||
|
# Note: Cycle generation runs synchronously in test environment, asynchronously in production
|
||||||
|
# CycleGenerator uses advisory locks and transactions internally to prevent race conditions
|
||||||
|
change after_action(fn changeset, member, _context ->
|
||||||
|
join_date_changed = Ash.Changeset.changing_attribute?(changeset, :join_date)
|
||||||
|
exit_date_changed = Ash.Changeset.changing_attribute?(changeset, :exit_date)
|
||||||
|
|
||||||
|
if (join_date_changed || exit_date_changed) && member.membership_fee_type_id do
|
||||||
|
if Application.get_env(:mv, :sql_sandbox, false) do
|
||||||
|
# Run synchronously in test environment for DB sandbox compatibility
|
||||||
|
# Use skip_lock?: true to avoid nested transactions (after_action runs within action transaction)
|
||||||
|
# Return notifications to Ash so they are sent after commit
|
||||||
|
case Mv.MembershipFees.CycleGenerator.generate_cycles_for_member(
|
||||||
|
member.id,
|
||||||
|
today: Date.utc_today(),
|
||||||
|
skip_lock?: true
|
||||||
|
) do
|
||||||
|
{:ok, _cycles, notifications} ->
|
||||||
|
{:ok, member, notifications}
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
Logger.warning(
|
||||||
|
"Failed to regenerate cycles for member #{member.id}: #{inspect(reason)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
{:ok, member}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
# Run asynchronously in other environments
|
||||||
|
# Send notifications explicitly since they cannot be returned via after_action
|
||||||
|
Task.start(fn ->
|
||||||
|
case Mv.MembershipFees.CycleGenerator.generate_cycles_for_member(member.id) do
|
||||||
|
{:ok, _cycles, notifications} ->
|
||||||
|
# Send notifications manually for async case
|
||||||
|
if Enum.any?(notifications) do
|
||||||
|
Ash.Notifier.notify(notifications)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
Logger.warning(
|
||||||
|
"Failed to regenerate cycles for member #{member.id}: #{inspect(reason)}"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, member}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
{:ok, member}
|
||||||
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Action to handle fuzzy search on specific fields
|
# Action to handle fuzzy search on specific fields
|
||||||
|
|
@ -395,11 +452,6 @@ defmodule Mv.Membership.Member do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Join date not in the future
|
|
||||||
validate compare(:join_date, less_than_or_equal_to: &Date.utc_today/0),
|
|
||||||
where: [present(:join_date)],
|
|
||||||
message: "cannot be in the future"
|
|
||||||
|
|
||||||
# Exit date not before join date
|
# Exit date not before join date
|
||||||
validate compare(:exit_date, greater_than: :join_date),
|
validate compare(:exit_date, greater_than: :join_date),
|
||||||
where: [present([:join_date, :exit_date])],
|
where: [present([:join_date, :exit_date])],
|
||||||
|
|
|
||||||
|
|
@ -58,12 +58,9 @@ defmodule Mv.Membership.MemberTest do
|
||||||
assert {:ok, _member} = Membership.create_member(attrs2)
|
assert {:ok, _member} = Membership.create_member(attrs2)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Join date is optional but must not be in the future" do
|
test "Join date can be in the future" do
|
||||||
attrs = Map.put(@valid_attrs, :join_date, Date.utc_today() |> Date.add(1))
|
attrs = Map.put(@valid_attrs, :join_date, Date.utc_today() |> Date.add(1))
|
||||||
assert {:error, %Ash.Error.Invalid{errors: errors}} = Membership.create_member(attrs)
|
assert {:ok, _member} = Membership.create_member(attrs)
|
||||||
assert error_message(errors, :join_date) =~ "cannot be in the future"
|
|
||||||
attrs2 = Map.delete(@valid_attrs, :join_date)
|
|
||||||
assert {:ok, _member} = Membership.create_member(attrs2)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Exit date is optional but must not be before join date if both are specified" do
|
test "Exit date is optional but must not be before join date if both are specified" do
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue