feat(overview): add point-in-time, suspended, OR-group and count-range filters
This commit is contained in:
parent
c547ca19af
commit
c0d1550401
23 changed files with 1116 additions and 117 deletions
|
|
@ -59,6 +59,7 @@ defmodule MvWeb.MemberLive.Index do
|
|||
alias MvWeb.MemberLive.Index.MembershipFeeStatus
|
||||
alias MvWeb.MemberLive.Index.OverviewQuery
|
||||
alias MvWeb.MemberLive.Index.PaymentAging
|
||||
alias MvWeb.MemberLive.Index.Stichtag
|
||||
alias MvWeb.MemberLive.Index.ViewSettings
|
||||
|
||||
require Ash.Query
|
||||
|
|
@ -177,6 +178,8 @@ defmodule MvWeb.MemberLive.Index do
|
|||
|> assign_new(:sort_order, fn -> :asc end)
|
||||
|> assign(:payment_filter, nil)
|
||||
|> assign(:payment_period, PaymentAging.default_period())
|
||||
|> assign(:suspended, false)
|
||||
|> assign(:stichtag, nil)
|
||||
|> assign(:group_filters, %{})
|
||||
|> assign(:groups, groups)
|
||||
|> assign(:fee_type_filters, %{})
|
||||
|
|
@ -679,6 +682,8 @@ defmodule MvWeb.MemberLive.Index do
|
|||
|> maybe_update_sort(params)
|
||||
|> maybe_update_payment_filter(params)
|
||||
|> maybe_update_payment_period(params)
|
||||
|> maybe_update_suspended(params)
|
||||
|> maybe_update_stichtag(params)
|
||||
|> maybe_update_group_filters(params)
|
||||
|> maybe_update_fee_type_filters(params)
|
||||
|> maybe_update_boolean_filters(params)
|
||||
|
|
@ -728,6 +733,8 @@ defmodule MvWeb.MemberLive.Index do
|
|||
socket.assigns.sort_order,
|
||||
socket.assigns.payment_filter,
|
||||
socket.assigns.payment_period,
|
||||
socket.assigns[:suspended],
|
||||
socket.assigns[:stichtag],
|
||||
socket.assigns[:group_filters],
|
||||
socket.assigns[:fee_type_filters],
|
||||
socket.assigns.boolean_custom_field_filters,
|
||||
|
|
@ -883,10 +890,19 @@ defmodule MvWeb.MemberLive.Index do
|
|||
base_params = add_group_filters(base_params, opts.group_filters || %{})
|
||||
base_params = add_fee_type_filters(base_params, opts.fee_type_filters || %{})
|
||||
base_params = add_payment_params(base_params, opts.payment_filter, opts.payment_period)
|
||||
base_params = add_status_params(base_params, opts[:suspended], opts[:stichtag])
|
||||
base_params = add_boolean_filters(base_params, opts.boolean_filters || %{})
|
||||
add_date_filters(base_params, opts.date_filters)
|
||||
end
|
||||
|
||||
# Suspended-status flag and Stichtag date serialize additively into the flat
|
||||
# URL contract (suspended=1 / stichtag=<iso>), omitted when inactive (§2.1).
|
||||
defp add_status_params(params, suspended, stichtag) do
|
||||
params
|
||||
|> Map.merge(PaymentAging.suspended_to_params(suspended))
|
||||
|> Map.merge(Stichtag.to_params(stichtag))
|
||||
end
|
||||
|
||||
# Period-scoped payment model (§3.3): the payment-count filter and the active
|
||||
# period both serialize into the flat URL contract (pay_filter/pay_from/pay_to).
|
||||
defp add_payment_params(params, payment_filter, payment_period) do
|
||||
|
|
@ -906,6 +922,8 @@ defmodule MvWeb.MemberLive.Index do
|
|||
sort_order: socket.assigns.sort_order,
|
||||
payment_filter: socket.assigns.payment_filter,
|
||||
payment_period: socket.assigns.payment_period,
|
||||
suspended: socket.assigns[:suspended] || false,
|
||||
stichtag: socket.assigns[:stichtag],
|
||||
group_filters: socket.assigns[:group_filters] || %{},
|
||||
boolean_filters: socket.assigns.boolean_custom_field_filters || %{},
|
||||
fee_type_filters: socket.assigns[:fee_type_filters] || %{},
|
||||
|
|
@ -1366,6 +1384,8 @@ defmodule MvWeb.MemberLive.Index do
|
|||
date_custom_fields: socket.assigns[:date_custom_fields],
|
||||
payment_filter: socket.assigns.payment_filter,
|
||||
payment_period: socket.assigns.payment_period,
|
||||
suspended: socket.assigns[:suspended] || false,
|
||||
stichtag: socket.assigns[:stichtag],
|
||||
sort_field: socket.assigns.sort_field,
|
||||
sort_order: socket.assigns.sort_order,
|
||||
custom_fields: socket.assigns.all_custom_fields
|
||||
|
|
@ -1448,7 +1468,7 @@ defmodule MvWeb.MemberLive.Index do
|
|||
valid_fields = Mv.Constants.member_fields() -- non_sortable_fields
|
||||
|
||||
field in valid_fields or custom_field_sort?(field) or
|
||||
field in [:groups, :membership_fee_type, :name, :address]
|
||||
field in [:groups, :membership_fee_type, :name, :address, :payment]
|
||||
end
|
||||
|
||||
defp valid_sort_field_db_or_custom?(field) when is_binary(field) do
|
||||
|
|
@ -1458,6 +1478,7 @@ defmodule MvWeb.MemberLive.Index do
|
|||
field == "membership_fee_type" -> :membership_fee_type
|
||||
field == "name" -> :name
|
||||
field == "address" -> :address
|
||||
field == "payment" -> :payment
|
||||
true -> safe_member_field_atom_only(field)
|
||||
end
|
||||
|
||||
|
|
@ -1507,9 +1528,10 @@ defmodule MvWeb.MemberLive.Index do
|
|||
defp determine_field(default, nil), do: default
|
||||
|
||||
# Computed/pseudo fields that are nonetheless sortable (they resolve to DB
|
||||
# sort keys in OverviewQuery): groups aggregate and the composite Name/Address
|
||||
# columns. Other computed fields (e.g. membership_fee_status) are not sortable.
|
||||
@sortable_computed_fields [:groups, :name, :address]
|
||||
# sort keys in OverviewQuery): groups aggregate, the composite Name/Address
|
||||
# columns, and the period-scoped payment (unpaid-cycle-count) column. Other
|
||||
# computed fields (e.g. membership_fee_status) are not sortable.
|
||||
@sortable_computed_fields [:groups, :name, :address, :payment]
|
||||
|
||||
defp determine_field(default, sf) when is_binary(sf) do
|
||||
sortable_strings = Enum.map(@sortable_computed_fields, &Atom.to_string/1)
|
||||
|
|
@ -1578,6 +1600,15 @@ defmodule MvWeb.MemberLive.Index do
|
|||
defp maybe_update_payment_period(socket, params),
|
||||
do: assign(socket, :payment_period, PaymentAging.parse_period(params))
|
||||
|
||||
# Suspended-status filter (§1.23) and point-in-time Stichtag (§1.24/§2.6):
|
||||
# both decoded straight from the URL so `handle_params` remains the single
|
||||
# source of truth. Absent params fall back to no filter.
|
||||
defp maybe_update_suspended(socket, params),
|
||||
do: assign(socket, :suspended, PaymentAging.parse_suspended(params))
|
||||
|
||||
defp maybe_update_stichtag(socket, params),
|
||||
do: assign(socket, :stichtag, Stichtag.parse(params))
|
||||
|
||||
defp maybe_update_group_filters(socket, params) when is_map(params) do
|
||||
prefix = @group_filter_prefix
|
||||
prefix_len = String.length(prefix)
|
||||
|
|
@ -1836,17 +1867,16 @@ defmodule MvWeb.MemberLive.Index do
|
|||
all-outstanding default (both bounds nil) reads just "Payment"; a bounded
|
||||
period appends the range so the scope is always legible.
|
||||
"""
|
||||
def payment_column_label(%{from: nil, to: nil}), do: gettext("Fees")
|
||||
def payment_column_label(_period), do: gettext("Fees")
|
||||
|
||||
def payment_column_label(%{from: from, to: to}) do
|
||||
range = "#{payment_bound(from)}–#{payment_bound(to)}"
|
||||
gettext("Fees · %{range}", range: range)
|
||||
end
|
||||
|
||||
def payment_column_label(_), do: gettext("Fees")
|
||||
|
||||
defp payment_bound(%Date{} = d), do: Date.to_iso8601(d)
|
||||
defp payment_bound(_), do: "…"
|
||||
@doc """
|
||||
Whether a payment period is actively scoped (at least one bound set), used to
|
||||
decide whether the compact period indicator badge renders (§1.28). The
|
||||
all-outstanding default (both bounds nil) shows no badge.
|
||||
"""
|
||||
def payment_period_active?(%{from: nil, to: nil}), do: false
|
||||
def payment_period_active?(%{from: _, to: _}), do: true
|
||||
def payment_period_active?(_), do: false
|
||||
|
||||
defp update_selection_assigns(socket) do
|
||||
selected_members = socket.assigns.selected_members
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue