feat: add membership fee status to columns and dropdown

This commit is contained in:
carla 2026-02-09 13:34:38 +01:00
parent 36e57b24be
commit e1266944b1
7 changed files with 725 additions and 514 deletions

View file

@ -16,8 +16,9 @@ defmodule Mv.Membership.MemberExport do
alias MvWeb.MemberLive.Index.MembershipFeeStatus
@member_fields_allowlist (Mv.Constants.member_fields() |> Enum.map(&Atom.to_string/1)) ++
["membership_fee_status", "payment_status"]
@computed_export_fields ["membership_fee_status", "payment_status"]
["membership_fee_status"]
@computed_export_fields ["membership_fee_status"]
@computed_insert_after "membership_fee_start_date"
@custom_field_prefix Mv.Constants.custom_field_prefix()
@domain_member_field_strings Mv.Constants.member_fields() |> Enum.map(&Atom.to_string/1)
@ -75,9 +76,16 @@ defmodule Mv.Membership.MemberExport do
if f in parsed.selectable_member_fields do
%{kind: :member_field, key: f}
else
%{kind: :computed, key: String.to_existing_atom(f)}
# only allow known computed export fields to avoid crashing on unknown atoms
if f in @computed_export_fields do
%{kind: :computed, key: String.to_existing_atom(f)}
else
# ignore unknown non-selectable fields defensively
nil
end
end
end)
|> Enum.reject(&is_nil/1)
custom_specs =
parsed.custom_field_ids
@ -96,7 +104,8 @@ defmodule Mv.Membership.MemberExport do
need_cycles =
parsed.show_current_cycle or parsed.cycle_status_filter != nil or
parsed.computed_fields != []
parsed.computed_fields != [] or
"membership_fee_status" in parsed.member_fields
query =
Member
@ -143,6 +152,9 @@ defmodule Mv.Membership.MemberExport do
members
end
# Calculate membership_fee_status for computed fields
members = add_computed_fields(members, parsed.computed_fields, parsed.show_current_cycle)
{:ok, members}
{:error, %Ash.Error.Forbidden{}} ->
@ -241,6 +253,19 @@ defmodule Mv.Membership.MemberExport do
defp apply_cycle_status_filter(members, _status, _show_current), do: members
defp add_computed_fields(members, computed_fields, show_current_cycle) do
computed_fields = computed_fields || []
if "membership_fee_status" in computed_fields do
Enum.map(members, fn member ->
status = MembershipFeeStatus.get_cycle_status_for_member(member, show_current_cycle)
Map.put(member, :membership_fee_status, status) # <= Atom rein
end)
else
members
end
end
# Called by controller to build parsed map from raw params (kept here so controller stays thin)
@doc """
Parses and validates export params (from JSON payload).
@ -251,12 +276,31 @@ defmodule Mv.Membership.MemberExport do
"""
@spec parse_params(map()) :: map()
def parse_params(params) do
member_fields = filter_allowed_member_fields(extract_list(params, "member_fields"))
{selectable_member_fields, computed_fields} = split_member_fields(member_fields)
# DB fields come from "member_fields"
raw_member_fields = extract_list(params, "member_fields")
member_fields = filter_allowed_member_fields(raw_member_fields)
# computed fields can come from "computed_fields" (new payload) OR legacy inclusion in member_fields
computed_fields =
(extract_list(params, "computed_fields") ++ member_fields)
|> normalize_computed_fields()
|> Enum.filter(&(&1 in @computed_export_fields))
|> Enum.uniq()
# selectable DB fields: only real domain member fields, ordered like the table
selectable_member_fields =
member_fields
|> Enum.filter(&(&1 in @domain_member_field_strings))
|> order_member_fields_like_table()
# final member_fields list (used for column specs order): table order + computed inserted
ordered_member_fields =
selectable_member_fields
|> insert_computed_fields_like_table(computed_fields)
%{
selected_ids: filter_valid_uuids(extract_list(params, "selected_ids")),
member_fields: member_fields,
member_fields: ordered_member_fields,
selectable_member_fields: selectable_member_fields,
computed_fields: computed_fields,
custom_field_ids: filter_valid_uuids(extract_list(params, "custom_field_ids")),
@ -269,12 +313,6 @@ defmodule Mv.Membership.MemberExport do
}
end
defp split_member_fields(member_fields) do
selectable = Enum.filter(member_fields, fn f -> f in @domain_member_field_strings end)
computed = Enum.filter(member_fields, fn f -> f in @computed_export_fields end)
{selectable, computed}
end
defp extract_boolean(params, key) do
case Map.get(params, key) do
true -> true
@ -341,4 +379,41 @@ defmodule Mv.Membership.MemberExport do
end)
|> Enum.uniq()
end
defp order_member_fields_like_table(fields) when is_list(fields) do
table_order = Mv.Constants.member_fields() |> Enum.map(&Atom.to_string/1)
table_order |> Enum.filter(&(&1 in fields))
end
defp insert_computed_fields_like_table(db_fields_ordered, computed_fields) do
# Insert membership_fee_status right after membership_fee_start_date (if both selected),
# otherwise append at the end of DB fields.
computed_fields = computed_fields || []
db_with_insert =
Enum.flat_map(db_fields_ordered, fn f ->
if f == @computed_insert_after and "membership_fee_status" in computed_fields do
[f, "membership_fee_status"]
else
[f]
end
end)
remaining =
computed_fields
|> Enum.reject(&(&1 in db_with_insert))
db_with_insert ++ remaining
end
defp normalize_computed_fields(fields) when is_list(fields) do
fields
|> Enum.filter(&is_binary/1)
|> Enum.map(fn
"payment_status" -> "membership_fee_status"
other -> other
end)
end
defp normalize_computed_fields(_), do: []
end