fix(membership): add chronological sort key for custom :date fields

Custom :date values are real Date structs; sorting them by Erlang term
order compares day, then month, then year, so the member list ordered
them like day-first text instead of chronologically. Derive the sort key
from a single shared helper that maps a date to its Gregorian day count,
leaving the other value types at their already-correct natural order.
This commit is contained in:
Moritz 2026-06-15 16:10:14 +02:00
parent d6c322fd79
commit 1aaa0ece5d
3 changed files with 124 additions and 0 deletions

View file

@ -0,0 +1,30 @@
defmodule Mv.Membership.CustomFieldSort do
@moduledoc """
Derives a term-order-comparable sort key from a custom-field value.
Custom-field values are stored in an Ash `:union` attribute, so a value
arrives either as an `%Ash.Union{}` or as its already-unwrapped term. This
module is the single source of truth for turning such a value into a key that
`Enum.sort_by/2` can order correctly per `value_type`.
nil / empty handling is intentionally NOT this function's concern — the call
sites split present from absent values before sorting.
"""
@doc """
Returns a term-order-comparable sort key for `value`, given its `value_type`.
For every non-date type the natural unwrapped value is returned, so
`:integer` sorts numerically and `:string` / `:email` sort lexicographically.
"""
def sort_key(%Ash.Union{value: value, type: type}, _expected_type),
do: sort_key(value, type)
def sort_key(value, :string) when is_binary(value), do: value
def sort_key(value, :integer) when is_integer(value), do: value
def sort_key(value, :boolean) when is_boolean(value), do: value
# Gregorian day count is a chronological integer key (earlier date ⇒ smaller).
def sort_key(%Date{} = date, :date), do: Date.to_gregorian_days(date)
def sort_key(value, :email) when is_binary(value), do: value
def sort_key(value, _type), do: to_string(value)
end