From 0acd12360f297456a8e8608d1913105b389f8454 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 10 Jul 2026 16:27:16 +0200 Subject: [PATCH] refactor(overview): use English identifier as_of_date for point-in-time filter --- lib/mv/constants.ex | 10 ++-- .../add_filter_builder_component.ex | 46 +++++++++---------- lib/mv_web/live/member_live/index.ex | 38 +++++++-------- lib/mv_web/live/member_live/index.html.heex | 2 +- .../live/member_live/index/as_of_date.ex | 37 +++++++++++++++ .../live/member_live/index/overview_query.ex | 10 ++-- lib/mv_web/live/member_live/index/stichtag.ex | 36 --------------- .../member_live/index_active_former_test.exs | 16 +++---- ...exs => index_as_of_date_property_test.exs} | 8 ++-- .../index_filter_url_roundtrip_test.exs | 8 ++-- .../index_status_params_codec_test.exs | 14 +++--- 11 files changed, 113 insertions(+), 112 deletions(-) create mode 100644 lib/mv_web/live/member_live/index/as_of_date.ex delete mode 100644 lib/mv_web/live/member_live/index/stichtag.ex rename test/mv_web/member_live/{index_stichtag_property_test.exs => index_as_of_date_property_test.exs} (92%) diff --git a/lib/mv/constants.ex b/lib/mv/constants.ex index f60f77ad..a0607078 100644 --- a/lib/mv/constants.ex +++ b/lib/mv/constants.ex @@ -50,7 +50,7 @@ defmodule Mv.Constants do @suspended_param "suspended" - @stichtag_param "stichtag" + @as_of_date_param "as_of_date" @max_boolean_filters 50 @@ -240,16 +240,16 @@ defmodule Mv.Constants do def suspended_param, do: @suspended_param @doc """ - Returns the URL parameter name for the point-in-time membership ("Stichtag") + Returns the URL parameter name for the point-in-time "active on X" membership filter: keeps members who were active on the given ISO-8601 date X, i.e. `join_date <= X and (exit_date is nil or exit_date > X)`. ## Examples - iex> Mv.Constants.stichtag_param() - "stichtag" + iex> Mv.Constants.as_of_date_param() + "as_of_date" """ - def stichtag_param, do: @stichtag_param + def as_of_date_param, do: @as_of_date_param @doc """ Returns the maximum number of boolean custom field filters allowed per request. diff --git a/lib/mv_web/live/components/add_filter_builder_component.ex b/lib/mv_web/live/components/add_filter_builder_component.ex index fc1ee34b..4cc2c619 100644 --- a/lib/mv_web/live/components/add_filter_builder_component.ex +++ b/lib/mv_web/live/components/add_filter_builder_component.ex @@ -35,7 +35,7 @@ defmodule MvWeb.Components.AddFilterBuilderComponent do * `{:payment_filter_changed, nil | :fully_paid | {:has_unpaid, 1..3} | {:unpaid_range, pos_integer(), pos_integer() | nil}}` * `{:payment_period_changed, %{from: Date.t() | nil, to: Date.t() | nil}}` * `{:suspended_changed, boolean()}` - * `{:stichtag_changed, Date.t() | nil}` + * `{:as_of_date_changed, Date.t() | nil}` and `{:clear_all_filters}` for Clear all. """ @@ -91,7 +91,7 @@ defmodule MvWeb.Components.AddFilterBuilderComponent do |> assign(:payment_filter, assigns[:payment_filter]) |> assign(:payment_period, assigns[:payment_period] || PaymentAging.default_period()) |> assign(:suspended, assigns[:suspended] || false) - |> assign(:stichtag, assigns[:stichtag]) + |> assign(:as_of_date, assigns[:as_of_date]) end # -------------------------------------------------------------------------- @@ -461,23 +461,23 @@ defmodule MvWeb.Components.AddFilterBuilderComponent do <%!-- Separate paragraph (not part of the Active/Former button-join): the - point-in-time "active on X" Stichtag filter (§1.24). --%> -
+ point-in-time "active on X" as-of-date filter (§1.24). --%> +
<.input type="date" - name="stichtag" + name="as_of_date" label={gettext("Active on reference date")} class="input input-sm input-bordered" - value={date_iso(@stichtag)} - data-testid="stichtag-input" + value={date_iso(@as_of_date)} + data-testid="as-of-date-input" />
- <%!-- Third paragraph "Eigener Zeitraum": the same native From/To range the + <%!-- Third paragraph, a custom range: the same native From/To range the join-date builder uses, wired to the exit_date custom mode — members whose exit date falls within [from, to] (§1.24). A sibling form (not the - quick/Stichtag one) so committing a bound only touches the exit_date + quick/as-of-date one) so committing a bound only touches the exit_date slice and never re-reads the quick radios. --%>
- <%!-- Separate "Eigener Zeitraum" paragraph: two native + <%!-- Separate custom-range paragraph: two native controls side by side (§3.12 native-first — no cally). Committing a bound through the shared date dispatch applies the custom range; the browser's own date popup gives year-jump, manual entry and WCAG a11y for free. --%> @@ -722,7 +722,7 @@ defmodule MvWeb.Components.AddFilterBuilderComponent do def handle_event("commit", params, socket) do dispatch_payment(socket, params) dispatch_quick(socket, params) - dispatch_stichtag(socket, params) + dispatch_as_of_date(socket, params) dispatch_exit_range(socket, params) dispatch_prefix( @@ -824,14 +824,14 @@ defmodule MvWeb.Components.AddFilterBuilderComponent do end end - defp dispatch_stichtag(socket, params) do - if Map.has_key?(params, "stichtag") do - date = parse_date(Map.get(params, "stichtag")) - maybe_send(date != socket.assigns.stichtag, {:stichtag_changed, date}) + defp dispatch_as_of_date(socket, params) do + if Map.has_key?(params, "as_of_date") do + date = parse_date(Map.get(params, "as_of_date")) + maybe_send(date != socket.assigns.as_of_date, {:as_of_date_changed, date}) end end - # The exit-date "Eigener Zeitraum" range writes only the exit_date slice + # The exit-date custom range writes only the exit_date slice # (mode :custom + bounds), preserving join_date and custom-date entries — so # editing the exit range never wipes another active date filter. Committing an # empty range simply parks exit_date in :custom with nil bounds (no chip, no @@ -917,7 +917,7 @@ defmodule MvWeb.Components.AddFilterBuilderComponent do end defp remove_chip("suspended", _id, _socket), do: send(self(), {:suspended_changed, false}) - defp remove_chip("stichtag", _id, _socket), do: send(self(), {:stichtag_changed, nil}) + defp remove_chip("as_of_date", _id, _socket), do: send(self(), {:as_of_date_changed, nil}) defp remove_chip("group", id, _socket), do: send(self(), {:group_filter_changed, id, nil}) defp remove_chip("fee_type", id, _socket), do: send(self(), {:fee_type_filter_changed, id, nil}) @@ -947,7 +947,7 @@ defmodule MvWeb.Components.AddFilterBuilderComponent do fee_type_chips(assigns) ++ boolean_chips(assigns) ++ date_chips(assigns) ++ - stichtag_chip(assigns) ++ + as_of_date_chip(assigns) ++ payment_chips(assigns) ++ suspended_chip(assigns) end @@ -1117,18 +1117,18 @@ defmodule MvWeb.Components.AddFilterBuilderComponent do defp suspended_chip(_), do: [] - defp stichtag_chip(%{stichtag: %Date{} = date}) do + defp as_of_date_chip(%{as_of_date: %Date{} = date}) do [ %{ - kind: "stichtag", - id: "stichtag", + kind: "as_of_date", + id: "as_of_date", edit_key: "active_former", label: gettext("Active on %{date}", date: DateFormatter.format_date(date)) } ] end - defp stichtag_chip(_), do: [] + defp as_of_date_chip(_), do: [] defp unpaid_range_label(min, nil) when min <= 1, do: gettext("Open contributions") defp unpaid_range_label(min, nil), do: gettext("≥ %{n} unpaid", n: min) @@ -1235,7 +1235,7 @@ defmodule MvWeb.Components.AddFilterBuilderComponent do defp date_iso(_), do: nil # ISO string for a bound of the exit_date custom range, pre-filling the - # "Eigener Zeitraum" inputs only when the exit filter is actually in :custom + # custom-range inputs only when the exit filter is actually in :custom # mode (the quick active/former/all states carry no user range). defp exit_bound_iso(filters, bound) do case Map.get(filters, :exit_date, %{}) do diff --git a/lib/mv_web/live/member_live/index.ex b/lib/mv_web/live/member_live/index.ex index 2173025f..af0214e7 100644 --- a/lib/mv_web/live/member_live/index.ex +++ b/lib/mv_web/live/member_live/index.ex @@ -49,6 +49,7 @@ defmodule MvWeb.MemberLive.Index do alias Mv.MembershipFees alias Mv.MembershipFees.MembershipFeeType alias MvWeb.Helpers.DateFormatter + alias MvWeb.MemberLive.Index.AsOfDate alias MvWeb.MemberLive.Index.CustomFieldValueLookup alias MvWeb.MemberLive.Index.DateFilter alias MvWeb.MemberLive.Index.ExportPayload @@ -59,7 +60,6 @@ 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 @@ -179,7 +179,7 @@ defmodule MvWeb.MemberLive.Index do |> assign(:payment_filter, nil) |> assign(:payment_period, PaymentAging.default_period()) |> assign(:suspended, false) - |> assign(:stichtag, nil) + |> assign(:as_of_date, nil) |> assign(:group_filters, %{}) |> assign(:groups, groups) |> assign(:fee_type_filters, %{}) @@ -473,7 +473,7 @@ defmodule MvWeb.MemberLive.Index do payment_filter: nil, payment_period: PaymentAging.default_period(), suspended: false, - stichtag: nil, + as_of_date: nil, group_filters: %{}, fee_type_filters: %{}, boolean_filters: %{}, @@ -504,16 +504,16 @@ defmodule MvWeb.MemberLive.Index do end @impl true - def handle_info({:stichtag_changed, stichtag}, socket) do + def handle_info({:as_of_date_changed, as_of_date}, socket) do socket = socket - |> assign(:stichtag, stichtag) + |> assign(:as_of_date, as_of_date) |> load_members() |> scroll_list_to_top() |> update_selection_assigns() query_params = - build_query_params(opts_for_query_params(socket, %{stichtag: stichtag})) + build_query_params(opts_for_query_params(socket, %{as_of_date: as_of_date})) |> maybe_add_field_selection( socket.assigns[:user_field_selection], socket.assigns[:fields_in_url?] || false @@ -638,7 +638,7 @@ defmodule MvWeb.MemberLive.Index do |> assign(:boolean_custom_field_filters, Map.get(opts, :boolean_filters, %{})) |> assign(:date_filters, Map.get(opts, :date_filters, DateFilter.default())) |> assign(:suspended, Map.get(opts, :suspended, false)) - |> assign(:stichtag, Map.get(opts, :stichtag)) + |> assign(:as_of_date, Map.get(opts, :as_of_date)) |> load_members() |> scroll_list_to_top() |> update_selection_assigns() @@ -727,7 +727,7 @@ defmodule MvWeb.MemberLive.Index do |> maybe_update_payment_filter(params) |> maybe_update_payment_period(params) |> maybe_update_suspended(params) - |> maybe_update_stichtag(params) + |> maybe_update_as_of_date(params) |> maybe_update_group_filters(params) |> maybe_update_fee_type_filters(params) |> maybe_update_boolean_filters(params) @@ -778,7 +778,7 @@ defmodule MvWeb.MemberLive.Index do socket.assigns.payment_filter, socket.assigns.payment_period, socket.assigns[:suspended], - socket.assigns[:stichtag], + socket.assigns[:as_of_date], socket.assigns[:group_filters], socket.assigns[:fee_type_filters], socket.assigns.boolean_custom_field_filters, @@ -934,17 +934,17 @@ 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_status_params(base_params, opts[:suspended], opts[:as_of_date]) 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=), omitted when inactive (§2.1). - defp add_status_params(params, suspended, stichtag) do + # Suspended-status flag and as-of date serialize additively into the flat + # URL contract (suspended=1 / as_of_date=), omitted when inactive (§2.1). + defp add_status_params(params, suspended, as_of_date) do params |> Map.merge(PaymentAging.suspended_to_params(suspended)) - |> Map.merge(Stichtag.to_params(stichtag)) + |> Map.merge(AsOfDate.to_params(as_of_date)) end # Period-scoped payment model (§3.3): the payment-count filter and the active @@ -967,7 +967,7 @@ defmodule MvWeb.MemberLive.Index do payment_filter: socket.assigns.payment_filter, payment_period: socket.assigns.payment_period, suspended: socket.assigns[:suspended] || false, - stichtag: socket.assigns[:stichtag], + as_of_date: socket.assigns[:as_of_date], group_filters: socket.assigns[:group_filters] || %{}, boolean_filters: socket.assigns.boolean_custom_field_filters || %{}, fee_type_filters: socket.assigns[:fee_type_filters] || %{}, @@ -1429,7 +1429,7 @@ defmodule MvWeb.MemberLive.Index do payment_filter: socket.assigns.payment_filter, payment_period: socket.assigns.payment_period, suspended: socket.assigns[:suspended] || false, - stichtag: socket.assigns[:stichtag], + as_of_date: socket.assigns[:as_of_date], sort_field: socket.assigns.sort_field, sort_order: socket.assigns.sort_order, custom_fields: socket.assigns.all_custom_fields @@ -1644,14 +1644,14 @@ 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): + # Suspended-status filter (§1.23) and point-in-time as-of date (§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_as_of_date(socket, params), + do: assign(socket, :as_of_date, AsOfDate.parse(params)) defp maybe_update_group_filters(socket, params) when is_map(params) do prefix = @group_filter_prefix diff --git a/lib/mv_web/live/member_live/index.html.heex b/lib/mv_web/live/member_live/index.html.heex index 2a060282..e1de0fff 100644 --- a/lib/mv_web/live/member_live/index.html.heex +++ b/lib/mv_web/live/member_live/index.html.heex @@ -64,7 +64,7 @@ payment_filter={@payment_filter} payment_period={@payment_period} suspended={@suspended} - stichtag={@stichtag} + as_of_date={@as_of_date} />
diff --git a/lib/mv_web/live/member_live/index/as_of_date.ex b/lib/mv_web/live/member_live/index/as_of_date.ex new file mode 100644 index 00000000..6ec9d9a1 --- /dev/null +++ b/lib/mv_web/live/member_live/index/as_of_date.ex @@ -0,0 +1,37 @@ +defmodule MvWeb.MemberLive.Index.AsOfDate do + @moduledoc """ + URL codec for the point-in-time "active on X" membership filter (§1.24/§2.6). + + The as-of date is a single ISO-8601 date X carried in the flat URL contract + under the `as_of_date` key. When set, the overview keeps members who were + active on X (`join_date <= X and (exit_date is nil or exit_date > X)`); the + predicate itself lives in `OverviewQuery`. Absent or malformed values decode + to nil (no filter), and nil encodes to the empty map so a fresh URL stays + canonical. + """ + + @as_of_date_param Mv.Constants.as_of_date_param() + + @doc """ + Decodes the as-of date from a params map. Missing or malformed ISO-8601 + values yield nil. + """ + @spec parse(map()) :: Date.t() | nil + def parse(params) when is_map(params), do: parse_date(Map.get(params, @as_of_date_param)) + + @doc """ + Encodes an as-of date into URL params. nil yields the empty map. + """ + @spec to_params(Date.t() | nil) :: %{optional(String.t()) => String.t()} + def to_params(%Date{} = date), do: %{@as_of_date_param => Date.to_iso8601(date)} + def to_params(_), do: %{} + + defp parse_date(value) when is_binary(value) do + case Date.from_iso8601(String.trim(value)) do + {:ok, date} -> date + _ -> nil + end + end + + defp parse_date(_), do: nil +end diff --git a/lib/mv_web/live/member_live/index/overview_query.ex b/lib/mv_web/live/member_live/index/overview_query.ex index dc125d65..fa5559ca 100644 --- a/lib/mv_web/live/member_live/index/overview_query.ex +++ b/lib/mv_web/live/member_live/index/overview_query.ex @@ -46,7 +46,7 @@ defmodule MvWeb.MemberLive.Index.OverviewQuery do opts[:boolean_custom_fields] ) |> apply_date_filters(opts[:date_filters]) - |> apply_stichtag_filter(opts[:stichtag]) + |> apply_as_of_date_filter(opts[:as_of_date]) |> apply_custom_date_filters(opts[:date_filters], opts[:date_custom_fields]) |> apply_payment_filter(opts[:payment_filter], payment_period(opts)) |> apply_suspended_filter(opts[:suspended], payment_period(opts)) @@ -285,20 +285,20 @@ defmodule MvWeb.MemberLive.Index.OverviewQuery do defp apply_custom_date_filters(query, _filters, _custom_fields), do: query # --------------------------------------------------------------------------- - # Point-in-time membership ("Stichtag"): a member counts as a member on the + # Point-in-time membership (as-of date): a member counts as a member on the # reference date X iff it had joined by X and had not yet left as of X # (§1.24/§2.6). "Left as of X" means a non-nil exit_date at or before X, so an # exit_date strictly after X (or nil) still counts as a member on X. # --------------------------------------------------------------------------- - defp apply_stichtag_filter(query, %Date{} = x) do + defp apply_as_of_date_filter(query, %Date{} = x) do Ash.Query.filter( query, expr(join_date <= ^x and (is_nil(exit_date) or exit_date > ^x)) ) end - defp apply_stichtag_filter(query, _), do: query + defp apply_as_of_date_filter(query, _), do: query # --------------------------------------------------------------------------- # Sort (always append the unique id tie-breaker for keyset stability) @@ -327,7 +327,7 @@ defmodule MvWeb.MemberLive.Index.OverviewQuery do ]) end - # "Beiträge" (payment) column: sort DB-side by the period-scoped unpaid-cycle + # Fees (payment) column: sort DB-side by the period-scoped unpaid-cycle # count (§1.27). The count calculation takes the active period as arguments so # the sort key matches the displayed value; the id tie-breaker keeps keyset # pages stable across equal counts. diff --git a/lib/mv_web/live/member_live/index/stichtag.ex b/lib/mv_web/live/member_live/index/stichtag.ex deleted file mode 100644 index 647d609d..00000000 --- a/lib/mv_web/live/member_live/index/stichtag.ex +++ /dev/null @@ -1,36 +0,0 @@ -defmodule MvWeb.MemberLive.Index.Stichtag do - @moduledoc """ - URL codec for the point-in-time membership ("Stichtag") filter (§1.24/§2.6). - - The Stichtag is a single ISO-8601 date X carried in the flat URL contract - under the `stichtag` key. When set, the overview keeps members who were active - on X (`join_date <= X and (exit_date is nil or exit_date > X)`); the predicate - itself lives in `OverviewQuery`. Absent or malformed values decode to nil (no - filter), and nil encodes to the empty map so a fresh URL stays canonical. - """ - - @stichtag_param Mv.Constants.stichtag_param() - - @doc """ - Decodes the Stichtag date from a params map. Missing or malformed ISO-8601 - values yield nil. - """ - @spec parse(map()) :: Date.t() | nil - def parse(params) when is_map(params), do: parse_date(Map.get(params, @stichtag_param)) - - @doc """ - Encodes a Stichtag date into URL params. nil yields the empty map. - """ - @spec to_params(Date.t() | nil) :: %{optional(String.t()) => String.t()} - def to_params(%Date{} = date), do: %{@stichtag_param => Date.to_iso8601(date)} - def to_params(_), do: %{} - - defp parse_date(value) when is_binary(value) do - case Date.from_iso8601(String.trim(value)) do - {:ok, date} -> date - _ -> nil - end - end - - defp parse_date(_), do: nil -end diff --git a/test/mv_web/member_live/index_active_former_test.exs b/test/mv_web/member_live/index_active_former_test.exs index 4bb2c731..e959643d 100644 --- a/test/mv_web/member_live/index_active_former_test.exs +++ b/test/mv_web/member_live/index_active_former_test.exs @@ -62,16 +62,16 @@ defmodule MvWeb.MemberLive.IndexActiveFormerTest do assert has_element?(view, "#row-#{ctx.former.id}") end - describe "Stichtag paragraph (§1.24)" do + describe "as-of-date paragraph (§1.24)" do test "the active/former control carries a separate 'active on reference date' input", ctx do {:ok, view, _html} = live(ctx.conn, ~p"/members") pick_field(view, "active_former") - # The Stichtag input sits in its own paragraph, not inside the + # The as-of-date input sits in its own paragraph, not inside the # Active/Former button-join. - assert has_element?(view, "[data-testid='stichtag-paragraph'] input[name='stichtag']") - refute has_element?(view, ".join input[name='stichtag']") + assert has_element?(view, "[data-testid='as-of-date-paragraph'] input[name='as_of_date']") + refute has_element?(view, ".join input[name='as_of_date']") end test "setting a reference date activates the point-in-time membership filter", ctx do @@ -81,22 +81,22 @@ defmodule MvWeb.MemberLive.IndexActiveFormerTest do view |> element("[data-testid='value-control-active-former']") - |> render_change(%{"quick" => "active", "stichtag" => "2024-06-15"}) + |> render_change(%{"quick" => "active", "as_of_date" => "2024-06-15"}) _ = render(view) path = assert_patch(view) - assert path =~ "stichtag=2024-06-15" + assert path =~ "as_of_date=2024-06-15" end end - describe "Eigener Zeitraum exit-date range (§1.24)" do + describe "custom exit-date range (§1.24)" do test "the exit-date control carries a separate native From/To range paragraph", ctx do {:ok, view, _html} = live(ctx.conn, ~p"/members") pick_field(view, "active_former") # Third paragraph, same native two-input range control the join-date - # builder uses — separate from the Active/Former join and the Stichtag box. + # builder uses — separate from the Active/Former join and the as-of-date box. assert has_element?(view, "[data-testid='value-control-exit-range']") assert has_element?( diff --git a/test/mv_web/member_live/index_stichtag_property_test.exs b/test/mv_web/member_live/index_as_of_date_property_test.exs similarity index 92% rename from test/mv_web/member_live/index_stichtag_property_test.exs rename to test/mv_web/member_live/index_as_of_date_property_test.exs index 017d08fb..a30fef52 100644 --- a/test/mv_web/member_live/index_stichtag_property_test.exs +++ b/test/mv_web/member_live/index_as_of_date_property_test.exs @@ -1,8 +1,8 @@ -defmodule MvWeb.MemberLive.IndexStichtagPropertyTest do +defmodule MvWeb.MemberLive.IndexAsOfDatePropertyTest do @moduledoc """ §2.6 — Point-in-time membership ("member on the reference date X"). - For arbitrary (join_date, exit_date) pairs and reference dates X, the Stichtag + For arbitrary (join_date, exit_date) pairs and reference dates X, the as-of-date filter keeps a member iff `join_date <= X and (exit_date is nil or exit_date > X)`. Verified at the `OverviewQuery` layer against an in-memory oracle. """ @@ -42,7 +42,7 @@ defmodule MvWeb.MemberLive.IndexStichtagPropertyTest do end) end - property "Stichtag filter keeps exactly the members active on X" do + property "as-of-date filter keeps exactly the members active on X" do actor = Mv.Helpers.SystemActor.get_system_actor() check all( @@ -67,7 +67,7 @@ defmodule MvWeb.MemberLive.IndexStichtagPropertyTest do do: id actual = - %{stichtag: x} + %{as_of_date: x} |> OverviewQuery.build() |> Ash.read!(actor: actor) |> MapSet.new(& &1.id) diff --git a/test/mv_web/member_live/index_filter_url_roundtrip_test.exs b/test/mv_web/member_live/index_filter_url_roundtrip_test.exs index 8fe3405a..b01a9705 100644 --- a/test/mv_web/member_live/index_filter_url_roundtrip_test.exs +++ b/test/mv_web/member_live/index_filter_url_roundtrip_test.exs @@ -1,7 +1,7 @@ defmodule MvWeb.MemberLive.IndexFilterUrlRoundtripTest do @moduledoc """ §2.1 (extended) — the v2 filter keys survive the LiveView decode→encode round - trip. Loading `/members` with a Stichtag date, the suspended flag, and a + trip. Loading `/members` with an as-of date, the suspended flag, and a two-sided payment count range, then triggering an unrelated re-patch (a sort click), preserves every one of those keys in the pushed URL. """ @@ -21,15 +21,15 @@ defmodule MvWeb.MemberLive.IndexFilterUrlRoundtripTest do %{conn: conn_with_oidc_user(conn)} end - test "stichtag, suspended and payment count range persist through a re-patch", %{conn: conn} do - url = "/members?stichtag=2026-03-15&suspended=1&pay_filter=has_unpaid&pay_min=2&pay_max=4" + test "as-of date, suspended and payment count range persist through a re-patch", %{conn: conn} do + url = "/members?as_of_date=2026-03-15&suspended=1&pay_filter=has_unpaid&pay_min=2&pay_max=4" {:ok, view, _html} = live(conn, url) # Trigger an unrelated URL re-patch by sorting a stable column. view |> element("[data-testid='payment']") |> render_click() path = assert_patch(view) - assert path =~ "stichtag=2026-03-15" + assert path =~ "as_of_date=2026-03-15" assert path =~ "suspended=1" assert path =~ "pay_filter=has_unpaid" assert path =~ "pay_min=2" diff --git a/test/mv_web/member_live/index_status_params_codec_test.exs b/test/mv_web/member_live/index_status_params_codec_test.exs index b1438969..bf32962e 100644 --- a/test/mv_web/member_live/index_status_params_codec_test.exs +++ b/test/mv_web/member_live/index_status_params_codec_test.exs @@ -1,28 +1,28 @@ defmodule MvWeb.MemberLive.IndexStatusParamsCodecTest do @moduledoc """ §2.1 (extended) — URL codec round-trip for the new v2 filter keys: the - point-in-time Stichtag date and the suspended-status flag. Encoding a state to + point-in-time as-of date and the suspended-status flag. Encoding a state to params then decoding yields the canonical state, and the default (no filter) encodes to the empty map so a fresh URL is canonical. """ use ExUnit.Case, async: true + alias MvWeb.MemberLive.Index.AsOfDate alias MvWeb.MemberLive.Index.PaymentAging - alias MvWeb.MemberLive.Index.Stichtag - describe "Stichtag date" do + describe "as-of date" do test "a set date round-trips" do date = ~D[2026-03-15] - assert date |> Stichtag.to_params() |> Stichtag.parse() == date + assert date |> AsOfDate.to_params() |> AsOfDate.parse() == date end test "nil encodes to the empty map and decodes to nil" do - assert Stichtag.to_params(nil) == %{} - assert Stichtag.parse(%{}) == nil + assert AsOfDate.to_params(nil) == %{} + assert AsOfDate.parse(%{}) == nil end test "a malformed date param decodes to nil" do - assert Stichtag.parse(%{"stichtag" => "not-a-date"}) == nil + assert AsOfDate.parse(%{"as_of_date" => "not-a-date"}) == nil end end