Membership Fee 6 - UI Components & LiveViews closes #280 #304

Open
moritz wants to merge 65 commits from feature/280_membership_fee_ui into main
2 changed files with 43 additions and 9 deletions
Showing only changes of commit 3241dd7d96 - Show all commits

View file

@ -299,11 +299,15 @@ defmodule Mv.MembershipFees.CalendarCycles do
end
defp quarterly_cycle_end(cycle_start) do
case cycle_start.month do
1 -> Date.new!(cycle_start.year, 3, 31)
4 -> Date.new!(cycle_start.year, 6, 30)
7 -> Date.new!(cycle_start.year, 9, 30)
10 -> Date.new!(cycle_start.year, 12, 31)
# Ensure cycle_start is aligned to quarter boundary
# This handles cases where cycle_start might not be at the correct quarter start (e.g., month 12)
aligned_start = quarterly_cycle_start(cycle_start)
case aligned_start.month do
1 -> Date.new!(aligned_start.year, 3, 31)
4 -> Date.new!(aligned_start.year, 6, 30)
7 -> Date.new!(aligned_start.year, 9, 30)
10 -> Date.new!(aligned_start.year, 12, 31)
end
end
@ -313,9 +317,13 @@ defmodule Mv.MembershipFees.CalendarCycles do
end
defp half_yearly_cycle_end(cycle_start) do
case cycle_start.month do
1 -> Date.new!(cycle_start.year, 6, 30)
7 -> Date.new!(cycle_start.year, 12, 31)
# Ensure cycle_start is aligned to half-year boundary
# This handles cases where cycle_start might not be at the correct half-year start (e.g., month 10)
aligned_start = half_yearly_cycle_start(cycle_start)
case aligned_start.month do
1 -> Date.new!(aligned_start.year, 6, 30)
7 -> Date.new!(aligned_start.year, 12, 31)
end
end

View file

@ -386,18 +386,44 @@ defmodule Mv.MembershipFees.CycleGenerator do
{:ok, cycle} ->
{:ok, cycle, []}
{:error, %Ash.Error.Invalid{errors: [%Ash.Error.Changes.InvalidAttribute{private_vars: %{constraint: constraint, constraint_type: :unique}}]}} = error ->
# Cycle already exists (unique constraint violation) - skip it silently
# This makes the function idempotent and prevents errors on server restart
if constraint == "membership_fee_cycles_unique_cycle_per_member_index" do
{:skip, cycle_start}
else
{:error, {cycle_start, error}}
end
{:error, reason} ->
{:error, {cycle_start, reason}}
end
end)
{successes, errors} = Enum.split_with(results, &match?({:ok, _, _}, &1))
{successes, skips, errors} =
Enum.reduce(results, {[], [], []}, fn
{:ok, cycle, notifications}, {successes, skips, errors} ->
{[{:ok, cycle, notifications} | successes], skips, errors}
{:skip, cycle_start}, {successes, skips, errors} ->
{successes, [cycle_start | skips], errors}
{:error, error}, {successes, skips, errors} ->
{successes, skips, [error | errors]}
end)
all_notifications =
Enum.flat_map(successes, fn {:ok, _cycle, notifications} -> notifications end)
if Enum.empty?(errors) do
successful_cycles = Enum.map(successes, fn {:ok, cycle, _notifications} -> cycle end)
if Enum.any?(skips) do
Logger.debug(
"Skipped #{length(skips)} cycles that already exist for member #{member_id}"
)
end
{:ok, successful_cycles, all_notifications}
else
Logger.warning("Some cycles failed to create: #{inspect(errors)}")