- Replace unused amount_str variable with normalized_str - Ensure consistent variable naming throughout function
218 lines
6.4 KiB
Elixir
218 lines
6.4 KiB
Elixir
defmodule MvWeb.Helpers.MembershipFeeHelpers do
|
|
@moduledoc """
|
|
Helper functions for membership fee UI components.
|
|
|
|
Provides formatting and utility functions for displaying membership fee
|
|
information in LiveViews and templates.
|
|
"""
|
|
|
|
use Gettext, backend: MvWeb.Gettext
|
|
|
|
alias Mv.MembershipFees.CalendarCycles
|
|
alias Mv.MembershipFees.MembershipFeeCycle
|
|
alias Mv.Membership.Member
|
|
|
|
@doc """
|
|
Formats a decimal amount as currency string.
|
|
|
|
## Examples
|
|
|
|
iex> MvWeb.Helpers.MembershipFeeHelpers.format_currency(Decimal.new("60.00"))
|
|
"60,00 €"
|
|
|
|
iex> MvWeb.Helpers.MembershipFeeHelpers.format_currency(Decimal.new("5.5"))
|
|
"5,50 €"
|
|
"""
|
|
@spec format_currency(Decimal.t()) :: String.t()
|
|
def format_currency(%Decimal{} = amount) do
|
|
# Use German format: comma as decimal separator, always 2 decimal places
|
|
# Normalize to 2 decimal places
|
|
normalized = Decimal.round(amount, 2)
|
|
normalized_str = Decimal.to_string(normalized, :normal)
|
|
normalized_str = String.replace(normalized_str, ".", ",")
|
|
# Ensure 2 decimal places
|
|
case String.split(normalized_str, ",") do
|
|
[int_part, dec_part] when byte_size(dec_part) == 1 ->
|
|
"#{int_part},#{dec_part}0 €"
|
|
|
|
[int_part, dec_part] when byte_size(dec_part) == 2 ->
|
|
"#{int_part},#{dec_part} €"
|
|
|
|
[int_part] ->
|
|
"#{int_part},00 €"
|
|
|
|
_ ->
|
|
"#{normalized_str} €"
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Formats an interval atom as a translated string.
|
|
|
|
## Examples
|
|
|
|
iex> MvWeb.Helpers.MembershipFeeHelpers.format_interval(:monthly)
|
|
"Monthly"
|
|
|
|
iex> MvWeb.Helpers.MembershipFeeHelpers.format_interval(:yearly)
|
|
"Yearly"
|
|
"""
|
|
@spec format_interval(:monthly | :quarterly | :half_yearly | :yearly) :: String.t()
|
|
def format_interval(:monthly), do: gettext("Monthly")
|
|
def format_interval(:quarterly), do: gettext("Quarterly")
|
|
def format_interval(:half_yearly), do: gettext("Half-yearly")
|
|
def format_interval(:yearly), do: gettext("Yearly")
|
|
|
|
@doc """
|
|
Formats a cycle date range as a string.
|
|
|
|
Calculates the cycle end date from cycle_start and interval, then formats
|
|
both dates in European format (dd.mm.yyyy).
|
|
|
|
## Examples
|
|
|
|
iex> cycle_start = ~D[2024-01-01]
|
|
iex> MvWeb.Helpers.MembershipFeeHelpers.format_cycle_range(cycle_start, :yearly)
|
|
"01.01.2024 - 31.12.2024"
|
|
|
|
iex> cycle_start = ~D[2024-03-01]
|
|
iex> MvWeb.Helpers.MembershipFeeHelpers.format_cycle_range(cycle_start, :monthly)
|
|
"01.03.2024 - 31.03.2024"
|
|
"""
|
|
@spec format_cycle_range(Date.t(), :monthly | :quarterly | :half_yearly | :yearly) :: String.t()
|
|
def format_cycle_range(cycle_start, interval) do
|
|
cycle_end = CalendarCycles.calculate_cycle_end(cycle_start, interval)
|
|
start_str = format_date(cycle_start)
|
|
end_str = format_date(cycle_end)
|
|
"#{start_str} - #{end_str}"
|
|
end
|
|
|
|
@doc """
|
|
Gets the last completed cycle for a member.
|
|
|
|
Returns the cycle that was most recently completed (ended before today).
|
|
Returns `nil` if no completed cycles exist.
|
|
|
|
## Parameters
|
|
|
|
- `member` - Member struct with loaded membership_fee_cycles and membership_fee_type
|
|
- `today` - Optional date to use as reference (defaults to today)
|
|
|
|
## Returns
|
|
|
|
- `%MembershipFeeCycle{}` if found
|
|
- `nil` if no completed cycle exists
|
|
|
|
## Examples
|
|
|
|
# Member with cycles from 2023 and 2024, today is 2025-01-15
|
|
iex> cycle = MvWeb.Helpers.MembershipFeeHelpers.get_last_completed_cycle(member)
|
|
# => %MembershipFeeCycle{cycle_start: ~D[2024-01-01], ...}
|
|
"""
|
|
@spec get_last_completed_cycle(Member.t(), Date.t() | nil) :: MembershipFeeCycle.t() | nil
|
|
def get_last_completed_cycle(member, today \\ nil)
|
|
|
|
def get_last_completed_cycle(%Member{} = member, today) do
|
|
today = today || Date.utc_today()
|
|
|
|
case member.membership_fee_type do
|
|
nil ->
|
|
nil
|
|
|
|
fee_type ->
|
|
cycles = member.membership_fee_cycles || []
|
|
|
|
cycles
|
|
|> Enum.filter(fn cycle ->
|
|
CalendarCycles.last_completed_cycle?(cycle.cycle_start, fee_type.interval, today)
|
|
end)
|
|
|> List.first()
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Gets the current cycle for a member.
|
|
|
|
Returns the cycle that contains today's date.
|
|
Returns `nil` if no current cycle exists.
|
|
|
|
## Parameters
|
|
|
|
- `member` - Member struct with loaded membership_fee_cycles and membership_fee_type
|
|
- `today` - Optional date to use as reference (defaults to today)
|
|
|
|
## Returns
|
|
|
|
- `%MembershipFeeCycle{}` if found
|
|
- `nil` if no current cycle exists
|
|
|
|
## Examples
|
|
|
|
# Member with cycles, today is 2024-06-15 (within Q2 2024)
|
|
iex> cycle = MvWeb.Helpers.MembershipFeeHelpers.get_current_cycle(member)
|
|
# => %MembershipFeeCycle{cycle_start: ~D[2024-04-01], ...}
|
|
"""
|
|
@spec get_current_cycle(Member.t(), Date.t() | nil) :: MembershipFeeCycle.t() | nil
|
|
def get_current_cycle(member, today \\ nil)
|
|
|
|
def get_current_cycle(%Member{} = member, today) do
|
|
today = today || Date.utc_today()
|
|
|
|
case member.membership_fee_type do
|
|
nil ->
|
|
nil
|
|
|
|
fee_type ->
|
|
cycles = member.membership_fee_cycles || []
|
|
|
|
cycles
|
|
|> Enum.filter(fn cycle ->
|
|
CalendarCycles.current_cycle?(cycle.cycle_start, fee_type.interval, today)
|
|
end)
|
|
|> List.first()
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Gets the CSS color class for a status badge.
|
|
|
|
## Examples
|
|
|
|
iex> MvWeb.Helpers.MembershipFeeHelpers.status_color(:paid)
|
|
"badge-success"
|
|
|
|
iex> MvWeb.Helpers.MembershipFeeHelpers.status_color(:unpaid)
|
|
"badge-error"
|
|
|
|
iex> MvWeb.Helpers.MembershipFeeHelpers.status_color(:suspended)
|
|
"badge-ghost"
|
|
"""
|
|
@spec status_color(:paid | :unpaid | :suspended) :: String.t()
|
|
def status_color(:paid), do: "badge-success"
|
|
def status_color(:unpaid), do: "badge-error"
|
|
def status_color(:suspended), do: "badge-ghost"
|
|
|
|
@doc """
|
|
Gets the icon name for a status.
|
|
|
|
## Examples
|
|
|
|
iex> MvWeb.Helpers.MembershipFeeHelpers.status_icon(:paid)
|
|
"hero-check-circle"
|
|
|
|
iex> MvWeb.Helpers.MembershipFeeHelpers.status_icon(:unpaid)
|
|
"hero-x-circle"
|
|
|
|
iex> MvWeb.Helpers.MembershipFeeHelpers.status_icon(:suspended)
|
|
"hero-pause-circle"
|
|
"""
|
|
@spec status_icon(:paid | :unpaid | :suspended) :: String.t()
|
|
def status_icon(:paid), do: "hero-check-circle"
|
|
def status_icon(:unpaid), do: "hero-x-circle"
|
|
def status_icon(:suspended), do: "hero-pause-circle"
|
|
|
|
# Private helper function for date formatting
|
|
defp format_date(%Date{} = date) do
|
|
Calendar.strftime(date, "%d.%m.%Y")
|
|
end
|
|
end
|