feat(overview): add period-scoped payment filter and field-picker descriptor

This commit is contained in:
Simon 2026-07-10 16:27:15 +02:00
parent 95ef3177ac
commit 7d1a71b1fd
12 changed files with 654 additions and 0 deletions

View file

@ -5,6 +5,7 @@ defmodule MvWeb.MemberLive.IndexPaymentPeriodTest do
backs the badge tooltip (unpaid cycles for the period; suspended shown apart).
"""
use Mv.DataCase, async: false
use ExUnitProperties
import Mv.Fixtures, only: [create_fee_type: 2, member_fixture_with_actor: 2, create_cycle: 4]
@ -39,6 +40,58 @@ defmodule MvWeb.MemberLive.IndexPaymentPeriodTest do
end
end
describe "payment-count filter codec" do
test "round-trips fully_paid and has-unpaid thresholds" do
for state <- [:fully_paid, {:has_unpaid, 1}, {:has_unpaid, 2}, {:has_unpaid, 3}] do
params = PaymentAging.filter_to_params(state)
assert PaymentAging.parse_filter_params(params) == state
end
end
test "nil filter serializes to no params and unknown values parse to nil" do
assert PaymentAging.filter_to_params(nil) == %{}
assert PaymentAging.parse_filter_params(%{}) == nil
assert PaymentAging.parse_filter_params(%{"pay_filter" => "bogus"}) == nil
end
test "serializes to stable string tokens" do
assert PaymentAging.filter_to_params(:fully_paid) == %{"pay_filter" => "fully_paid"}
assert PaymentAging.filter_to_params({:has_unpaid, 2}) == %{"pay_filter" => "unpaid_2"}
end
end
describe "payment URL round-trip (§2.1, payment keys)" do
@dates [nil, ~D[2020-01-01], ~D[2024-06-30], ~D[2025-12-31]]
@filters [nil, :fully_paid, {:has_unpaid, 1}, {:has_unpaid, 2}, {:has_unpaid, 3}]
property "decode∘encode is canonical and idempotent for period + payment filter" do
check all(
from <- StreamData.member_of(@dates),
to <- StreamData.member_of(@dates),
filter <- StreamData.member_of(@filters)
) do
period = %{from: from, to: to}
params =
period
|> PaymentAging.to_params()
|> Map.merge(PaymentAging.filter_to_params(filter))
assert PaymentAging.parse_period(params) == period
assert PaymentAging.parse_filter_params(params) == filter
# Idempotence: re-encoding the decoded state yields the same params.
reencoded =
params
|> PaymentAging.parse_period()
|> PaymentAging.to_params()
|> Map.merge(PaymentAging.filter_to_params(PaymentAging.parse_filter_params(params)))
assert reencoded == params
end
end
end
describe "badge/1" do
test "0 renders as Paid (success)" do
badge = PaymentAging.badge(0)