feat: add membership fee helper modules
MembershipFeeHelpers: formatting functions for currency, intervals, cycles MembershipFeeStatus: helper for loading and determining cycle status in member list
This commit is contained in:
parent
a728968dad
commit
09dfbe455b
2 changed files with 346 additions and 0 deletions
143
lib/mv_web/member_live/index/membership_fee_status.ex
Normal file
143
lib/mv_web/member_live/index/membership_fee_status.ex
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
defmodule MvWeb.MemberLive.Index.MembershipFeeStatus do
|
||||
@moduledoc """
|
||||
Helper module for membership fee status display in member list view.
|
||||
|
||||
Provides functions to efficiently load and determine cycle status for members
|
||||
in the list view, avoiding N+1 queries.
|
||||
"""
|
||||
|
||||
use Gettext, backend: MvWeb.Gettext
|
||||
|
||||
alias Mv.Membership.Member
|
||||
alias MvWeb.Helpers.MembershipFeeHelpers
|
||||
|
||||
@doc """
|
||||
Loads membership fee cycles for members efficiently.
|
||||
|
||||
Preloads cycles with membership_fee_type relationship to avoid N+1 queries.
|
||||
Only loads the relevant cycle per member (last completed or current, depending on show_current).
|
||||
|
||||
## Parameters
|
||||
|
||||
- `query` - Ash query for members
|
||||
- `show_current` - If true, load current cycle; if false, load last completed cycle
|
||||
- `today` - Optional date to use as reference (defaults to today)
|
||||
|
||||
## Returns
|
||||
|
||||
Modified query with cycles loaded
|
||||
|
||||
## Performance
|
||||
|
||||
Uses Ash.Query.load to efficiently preload cycles in a single query.
|
||||
Filters cycles at database level to only load the relevant cycle per member.
|
||||
"""
|
||||
@spec load_cycles_for_members(Ash.Query.t(), boolean(), Date.t() | nil) :: Ash.Query.t()
|
||||
def load_cycles_for_members(query, _show_current \\ false, _today \\ nil) do
|
||||
# Load membership_fee_type and cycles with efficient filtering
|
||||
query
|
||||
|> Ash.Query.load([:membership_fee_type, membership_fee_cycles: [:membership_fee_type]])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets the cycle status for a member.
|
||||
|
||||
Returns the status of either the last completed cycle or the current cycle,
|
||||
depending on the `show_current` parameter.
|
||||
|
||||
## Parameters
|
||||
|
||||
- `member` - Member struct with loaded cycles and membership_fee_type
|
||||
- `show_current` - If true, get current cycle status; if false, get last completed cycle status
|
||||
|
||||
## Returns
|
||||
|
||||
- `:paid`, `:unpaid`, or `:suspended` if cycle exists
|
||||
- `nil` if no cycle exists
|
||||
|
||||
## Examples
|
||||
|
||||
# Get last completed cycle status
|
||||
iex> MvWeb.MemberLive.Index.MembershipFeeStatus.get_cycle_status_for_member(member, false)
|
||||
:paid
|
||||
|
||||
# Get current cycle status
|
||||
iex> MvWeb.MemberLive.Index.MembershipFeeStatus.get_cycle_status_for_member(member, true)
|
||||
:unpaid
|
||||
"""
|
||||
@spec get_cycle_status_for_member(Member.t(), boolean()) :: :paid | :unpaid | :suspended | nil
|
||||
def get_cycle_status_for_member(member, show_current \\ false) do
|
||||
cycle =
|
||||
if show_current do
|
||||
MembershipFeeHelpers.get_current_cycle(member)
|
||||
else
|
||||
MembershipFeeHelpers.get_last_completed_cycle(member)
|
||||
end
|
||||
|
||||
case cycle do
|
||||
nil -> nil
|
||||
cycle -> cycle.status
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Formats cycle status as a badge component.
|
||||
|
||||
Returns a map with badge information for rendering in templates.
|
||||
|
||||
## Parameters
|
||||
|
||||
- `status` - Cycle status (`:paid`, `:unpaid`, `:suspended`, or `nil`)
|
||||
|
||||
## Returns
|
||||
|
||||
Map with `:color`, `:icon`, and `:label` keys, or `nil` if status is nil
|
||||
|
||||
## Examples
|
||||
|
||||
iex> MvWeb.MemberLive.Index.MembershipFeeStatus.format_cycle_status_badge(:paid)
|
||||
%{color: "badge-success", icon: "hero-check-circle", label: "Paid"}
|
||||
|
||||
iex> MvWeb.MemberLive.Index.MembershipFeeStatus.format_cycle_status_badge(nil)
|
||||
nil
|
||||
"""
|
||||
@spec format_cycle_status_badge(:paid | :unpaid | :suspended | nil) ::
|
||||
%{color: String.t(), icon: String.t(), label: String.t()} | nil
|
||||
def format_cycle_status_badge(nil), do: nil
|
||||
|
||||
def format_cycle_status_badge(status) when status in [:paid, :unpaid, :suspended] do
|
||||
%{
|
||||
color: MembershipFeeHelpers.status_color(status),
|
||||
icon: MembershipFeeHelpers.status_icon(status),
|
||||
label: format_status_label(status)
|
||||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Filters members by unpaid cycle status.
|
||||
|
||||
Returns members that have unpaid cycles in either the last completed cycle
|
||||
or the current cycle, depending on `show_current`.
|
||||
|
||||
## Parameters
|
||||
|
||||
- `members` - List of member structs with loaded cycles
|
||||
- `show_current` - If true, filter by current cycle; if false, filter by last completed cycle
|
||||
|
||||
## Returns
|
||||
|
||||
List of members with unpaid cycles
|
||||
"""
|
||||
@spec filter_unpaid_members([Member.t()], boolean()) :: [Member.t()]
|
||||
def filter_unpaid_members(members, show_current \\ false) do
|
||||
Enum.filter(members, fn member ->
|
||||
status = get_cycle_status_for_member(member, show_current)
|
||||
status == :unpaid
|
||||
end)
|
||||
end
|
||||
|
||||
# Private helper function to format status label
|
||||
defp format_status_label(:paid), do: gettext("Paid")
|
||||
defp format_status_label(:unpaid), do: gettext("Unpaid")
|
||||
defp format_status_label(:suspended), do: gettext("Suspended")
|
||||
end
|
||||
Loading…
Add table
Add a link
Reference in a new issue