Member Fee Type in overview and exports, fix column visibility from URL #442
3 changed files with 101 additions and 34 deletions
|
|
@ -16,7 +16,7 @@ defmodule Mv.Membership.MemberExport do
|
||||||
alias MvWeb.MemberLive.Index.MembershipFeeStatus
|
alias MvWeb.MemberLive.Index.MembershipFeeStatus
|
||||||
|
|
||||||
@member_fields_allowlist (Mv.Constants.member_fields() |> Enum.map(&Atom.to_string/1)) ++
|
@member_fields_allowlist (Mv.Constants.member_fields() |> Enum.map(&Atom.to_string/1)) ++
|
||||||
["membership_fee_status", "groups"]
|
["membership_fee_type", "membership_fee_status", "groups"]
|
||||||
@computed_export_fields ["membership_fee_status"]
|
@computed_export_fields ["membership_fee_status"]
|
||||||
@computed_insert_after "membership_fee_start_date"
|
@computed_insert_after "membership_fee_start_date"
|
||||||
@custom_field_prefix Mv.Constants.custom_field_prefix()
|
@custom_field_prefix Mv.Constants.custom_field_prefix()
|
||||||
|
|
@ -326,10 +326,10 @@ defmodule Mv.Membership.MemberExport do
|
||||||
# Separate groups from other fields (groups is handled as a special field, not a member field)
|
# Separate groups from other fields (groups is handled as a special field, not a member field)
|
||||||
groups_field = if "groups" in member_fields, do: ["groups"], else: []
|
groups_field = if "groups" in member_fields, do: ["groups"], else: []
|
||||||
|
|
||||||
# final member_fields list (used for column specs order): table order + computed inserted + groups
|
# final member_fields list (used for column specs order): table order + fee type + computed + groups
|
||||||
ordered_member_fields =
|
ordered_member_fields =
|
||||||
selectable_member_fields
|
selectable_member_fields
|
||||||
|> insert_computed_fields_like_table(computed_fields)
|
|> insert_fee_type_and_computed_fields_like_table(computed_fields, member_fields)
|
||||||
|> then(fn fields -> fields ++ groups_field end)
|
|> then(fn fields -> fields ++ groups_field end)
|
||||||
|
|
||||||
%{
|
%{
|
||||||
|
|
@ -420,27 +420,44 @@ defmodule Mv.Membership.MemberExport do
|
||||||
table_order |> Enum.filter(&(&1 in fields))
|
table_order |> Enum.filter(&(&1 in fields))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp insert_computed_fields_like_table(db_fields_ordered, computed_fields) do
|
defp insert_fee_type_and_computed_fields_like_table(
|
||||||
# Insert membership_fee_status right after membership_fee_start_date (if both selected),
|
db_fields_ordered,
|
||||||
# otherwise append at the end of DB fields.
|
computed_fields,
|
||||||
|
member_fields
|
||||||
|
) do
|
||||||
computed_fields = computed_fields || []
|
computed_fields = computed_fields || []
|
||||||
|
member_fields = member_fields || []
|
||||||
|
|
||||||
db_with_insert =
|
db_with_insert =
|
||||||
Enum.flat_map(db_fields_ordered, fn f ->
|
Enum.flat_map(db_fields_ordered, fn f ->
|
||||||
if f == @computed_insert_after and "membership_fee_status" in computed_fields do
|
expand_field_with_computed(f, member_fields, computed_fields)
|
||||||
[f, "membership_fee_status"]
|
|
||||||
else
|
|
||||||
[f]
|
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
remaining =
|
remaining = Enum.reject(computed_fields, &(&1 in db_with_insert))
|
||||||
computed_fields
|
|
||||||
|> Enum.reject(&(&1 in db_with_insert))
|
|
||||||
|
|
||||||
db_with_insert ++ remaining
|
db_with_insert ++ remaining
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Insert membership_fee_type and membership_fee_status after membership_fee_start_date (table order).
|
||||||
|
defp expand_field_with_computed(f, member_fields, computed_fields) do
|
||||||
|
if f == @computed_insert_after do
|
||||||
|
extra = []
|
||||||
|
|
||||||
|
extra =
|
||||||
|
if "membership_fee_type" in member_fields,
|
||||||
|
do: extra ++ ["membership_fee_type"],
|
||||||
|
else: extra
|
||||||
|
|
||||||
|
extra =
|
||||||
|
if "membership_fee_status" in computed_fields,
|
||||||
|
do: extra ++ ["membership_fee_status"],
|
||||||
|
else: extra
|
||||||
|
|
||||||
|
[f] ++ extra
|
||||||
|
else
|
||||||
|
[f]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp normalize_computed_fields(fields) when is_list(fields) do
|
defp normalize_computed_fields(fields) when is_list(fields) do
|
||||||
fields
|
fields
|
||||||
|> Enum.filter(&is_binary/1)
|
|> Enum.filter(&is_binary/1)
|
||||||
|
|
|
||||||
|
|
@ -133,6 +133,7 @@ defmodule Mv.Membership.MemberExport.Build do
|
||||||
"membership_fee_status" in parsed.member_fields
|
"membership_fee_status" in parsed.member_fields
|
||||||
|
|
||||||
need_groups = "groups" in parsed.member_fields
|
need_groups = "groups" in parsed.member_fields
|
||||||
|
need_membership_fee_type = "membership_fee_type" in parsed.member_fields
|
||||||
|
|
||||||
query =
|
query =
|
||||||
Member
|
Member
|
||||||
|
|
@ -141,6 +142,7 @@ defmodule Mv.Membership.MemberExport.Build do
|
||||||
|> load_custom_field_values_query(custom_field_ids_union)
|
|> load_custom_field_values_query(custom_field_ids_union)
|
||||||
|> maybe_load_cycles(need_cycles, parsed.show_current_cycle)
|
|> maybe_load_cycles(need_cycles, parsed.show_current_cycle)
|
||||||
|> maybe_load_groups(need_groups)
|
|> maybe_load_groups(need_groups)
|
||||||
|
|> maybe_load_membership_fee_type(need_membership_fee_type)
|
||||||
|
|
||||||
query =
|
query =
|
||||||
if parsed.selected_ids != [] do
|
if parsed.selected_ids != [] do
|
||||||
|
|
@ -196,8 +198,11 @@ defmodule Mv.Membership.MemberExport.Build do
|
||||||
defp sort_members_in_memory(members, field, order) when is_binary(field) do
|
defp sort_members_in_memory(members, field, order) when is_binary(field) do
|
||||||
field_atom = String.to_existing_atom(field)
|
field_atom = String.to_existing_atom(field)
|
||||||
|
|
||||||
if field_atom in Mv.Constants.member_fields() do
|
if field_atom in Mv.Constants.member_fields() or field_atom == :membership_fee_type do
|
||||||
sort_by_field(members, field_atom, order)
|
sort_field =
|
||||||
|
if field_atom == :membership_fee_type, do: :membership_fee_type_id, else: field_atom
|
||||||
|
|
||||||
|
sort_by_field(members, sort_field, order)
|
||||||
else
|
else
|
||||||
members
|
members
|
||||||
end
|
end
|
||||||
|
|
@ -245,26 +250,39 @@ defmodule Mv.Membership.MemberExport.Build do
|
||||||
|
|
||||||
defp maybe_sort(query, field, order) when is_binary(field) do
|
defp maybe_sort(query, field, order) when is_binary(field) do
|
||||||
cond do
|
cond do
|
||||||
field == "groups" ->
|
field == "groups" -> {query, true}
|
||||||
# Groups sort → in-memory nach dem Read (wie Tabelle)
|
field == "membership_fee_type" -> apply_fee_type_sort(query, order)
|
||||||
{query, true}
|
custom_field_sort?(field) -> {query, true}
|
||||||
|
true -> apply_standard_member_sort(query, field, order)
|
||||||
custom_field_sort?(field) ->
|
|
||||||
{query, true}
|
|
||||||
|
|
||||||
true ->
|
|
||||||
field_atom = String.to_existing_atom(field)
|
|
||||||
|
|
||||||
if field_atom in (Mv.Constants.member_fields() -- [:notes]) do
|
|
||||||
{Ash.Query.sort(query, [{field_atom, String.to_existing_atom(order)}]), false}
|
|
||||||
else
|
|
||||||
{query, false}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
rescue
|
rescue
|
||||||
ArgumentError -> {query, false}
|
ArgumentError -> {query, false}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp apply_fee_type_sort(query, order) do
|
||||||
|
order_atom = if order == "desc", do: :desc, else: :asc
|
||||||
|
{Ash.Query.sort(query, membership_fee_type_id: order_atom), false}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp apply_standard_member_sort(query, field, order) do
|
||||||
|
field_atom = String.to_existing_atom(field)
|
||||||
|
|
||||||
|
sortable =
|
||||||
|
field_atom in (Mv.Constants.member_fields() -- [:notes]) or
|
||||||
|
field_atom == :membership_fee_type
|
||||||
|
|
||||||
|
if sortable do
|
||||||
|
order_atom = if order == "desc", do: :desc, else: :asc
|
||||||
|
|
||||||
|
sort_field =
|
||||||
|
if field_atom == :membership_fee_type, do: :membership_fee_type_id, else: field_atom
|
||||||
|
|
||||||
|
{Ash.Query.sort(query, [{sort_field, order_atom}]), false}
|
||||||
|
else
|
||||||
|
{query, false}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp sort_members_by_custom_field(members, _field, _order, _custom_fields) when members == [],
|
defp sort_members_by_custom_field(members, _field, _order, _custom_fields) when members == [],
|
||||||
do: []
|
do: []
|
||||||
|
|
||||||
|
|
@ -344,6 +362,12 @@ defmodule Mv.Membership.MemberExport.Build do
|
||||||
Ash.Query.load(query, groups: [:id, :name])
|
Ash.Query.load(query, groups: [:id, :name])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp maybe_load_membership_fee_type(query, false), do: query
|
||||||
|
|
||||||
|
defp maybe_load_membership_fee_type(query, true) do
|
||||||
|
Ash.Query.load(query, membership_fee_type: [:id, :name])
|
||||||
|
end
|
||||||
|
|
||||||
defp apply_cycle_status_filter(members, nil, _show_current), do: members
|
defp apply_cycle_status_filter(members, nil, _show_current), do: members
|
||||||
|
|
||||||
defp apply_cycle_status_filter(members, status, show_current) when status in [:paid, :unpaid] do
|
defp apply_cycle_status_filter(members, status, show_current) when status in [:paid, :unpaid] do
|
||||||
|
|
@ -393,6 +417,19 @@ defmodule Mv.Membership.MemberExport.Build do
|
||||||
}
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
membership_fee_type_col =
|
||||||
|
if "membership_fee_type" in parsed.member_fields do
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
key: :membership_fee_type,
|
||||||
|
kind: :membership_fee_type,
|
||||||
|
label: label_fn.(:membership_fee_type)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
groups_col =
|
groups_col =
|
||||||
if "groups" in parsed.member_fields do
|
if "groups" in parsed.member_fields do
|
||||||
[
|
[
|
||||||
|
|
@ -424,7 +461,8 @@ defmodule Mv.Membership.MemberExport.Build do
|
||||||
end)
|
end)
|
||||||
|> Enum.reject(&is_nil/1)
|
|> Enum.reject(&is_nil/1)
|
||||||
|
|
||||||
member_cols ++ computed_cols ++ groups_col ++ custom_cols
|
# Table order: ... membership_fee_start_date, membership_fee_type, membership_fee_status, groups, custom
|
||||||
|
member_cols ++ membership_fee_type_col ++ computed_cols ++ groups_col ++ custom_cols
|
||||||
end
|
end
|
||||||
|
|
||||||
defp build_rows(members, columns, custom_fields_by_id) do
|
defp build_rows(members, columns, custom_fields_by_id) do
|
||||||
|
|
@ -454,6 +492,17 @@ defmodule Mv.Membership.MemberExport.Build do
|
||||||
if is_binary(value), do: value, else: ""
|
if is_binary(value), do: value, else: ""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp cell_value(
|
||||||
|
member,
|
||||||
|
%{kind: :membership_fee_type, key: :membership_fee_type},
|
||||||
|
_custom_fields_by_id
|
||||||
|
) do
|
||||||
|
case Map.get(member, :membership_fee_type) do
|
||||||
|
%{name: name} when is_binary(name) -> name
|
||||||
|
_ -> ""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp cell_value(member, %{kind: :groups, key: :groups}, _custom_fields_by_id) do
|
defp cell_value(member, %{kind: :groups, key: :groups}, _custom_fields_by_id) do
|
||||||
groups = Map.get(member, :groups) || []
|
groups = Map.get(member, :groups) || []
|
||||||
format_groups(groups)
|
format_groups(groups)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,8 @@ defmodule MvWeb.MemberPdfExportController do
|
||||||
@invalid_json_message "invalid JSON"
|
@invalid_json_message "invalid JSON"
|
||||||
@export_failed_message "Failed to generate PDF export"
|
@export_failed_message "Failed to generate PDF export"
|
||||||
|
|
||||||
@allowed_member_field_strings Mv.Constants.member_fields() |> Enum.map(&Atom.to_string/1)
|
@allowed_member_field_strings (Mv.Constants.member_fields() |> Enum.map(&Atom.to_string/1)) ++
|
||||||
|
["membership_fee_type", "groups"]
|
||||||
|
|
||||||
def export(conn, %{"payload" => payload}) when is_binary(payload) do
|
def export(conn, %{"payload" => payload}) when is_binary(payload) do
|
||||||
actor = current_actor(conn)
|
actor = current_actor(conn)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue