mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-07-02 11:40:13 +02:00
Update member UI and related app changes
This commit is contained in:
@@ -16,6 +16,8 @@ def test_paths_and_automation_settings_round_trip(tmp_path, monkeypatch) -> None
|
||||
anniversary_days_before=21,
|
||||
anniversary_days_after=5,
|
||||
anniversary_intervals="30D;2M;1Y;10Y",
|
||||
retroactive_claims=True,
|
||||
optional_member_fields=("email", "phone", "nickname"),
|
||||
window_geometry="1200x800-1800+40",
|
||||
window_state="maximized",
|
||||
monitor_bounds=(-1920, 0, 1920, 1080),
|
||||
@@ -28,6 +30,8 @@ def test_paths_and_automation_settings_round_trip(tmp_path, monkeypatch) -> None
|
||||
assert raw["schema_version"] == 1
|
||||
assert raw["monitor_bounds"] == [-1920, 0, 1920, 1080]
|
||||
assert raw["splash_minimum_seconds"] == 0
|
||||
assert raw["retroactive_claims"] is True
|
||||
assert raw["optional_member_fields"] == ["nickname", "email", "phone"]
|
||||
|
||||
|
||||
def test_splash_minimum_defaults_to_five_and_is_clamped(tmp_path, monkeypatch) -> None:
|
||||
|
||||
@@ -113,3 +113,28 @@ def test_housekeeper_reports_invalid_member_dates(tmp_path) -> None:
|
||||
assert len(invalid) == 1
|
||||
assert invalid[0].member_id == member.member_id
|
||||
assert "Geburtsdatum" in invalid[0].detail
|
||||
|
||||
|
||||
def test_housekeeper_can_treat_selected_member_fields_as_optional(tmp_path) -> None:
|
||||
repository = MemberRepository(tmp_path)
|
||||
repository.initialize()
|
||||
member = repository.create_member(first_name="Optional", last_name="Fields")
|
||||
member.status = "active"
|
||||
repository.save_member(member)
|
||||
settings = HousekeeperSettings.from_values(
|
||||
birthday_days_before=0,
|
||||
birthday_days_after=0,
|
||||
anniversary_days_before=0,
|
||||
anniversary_days_after=0,
|
||||
anniversary_intervals="1Y",
|
||||
optional_member_fields=("nickname", "email", "phone", "birth_date"),
|
||||
)
|
||||
|
||||
findings = Housekeeper(repository, settings).run(today=date(2026, 6, 21))
|
||||
|
||||
codes = {finding.code for finding in findings}
|
||||
assert "missing_birth_date" not in codes
|
||||
assert "missing_member_field:nickname" not in codes
|
||||
assert "missing_member_field:email" not in codes
|
||||
assert "missing_member_field:phone" not in codes
|
||||
assert "missing_member_field:street" in codes
|
||||
|
||||
@@ -18,6 +18,7 @@ def test_repository_creates_transparent_member_record(tmp_path) -> None:
|
||||
member = repository.create_member(
|
||||
first_name="Ada",
|
||||
last_name="Lovelace",
|
||||
nickname="Enchantress",
|
||||
email="ada@example.org",
|
||||
birth_date="1990-12-10",
|
||||
member_number="0042",
|
||||
@@ -32,6 +33,7 @@ def test_repository_creates_transparent_member_record(tmp_path) -> None:
|
||||
|
||||
raw = json.loads((member_dir / "member.json").read_text(encoding="utf-8"))
|
||||
assert raw["person"]["first_name"] == "Ada"
|
||||
assert raw["person"]["nickname"] == "Enchantress"
|
||||
assert raw["schema_version"] == 1
|
||||
|
||||
|
||||
@@ -41,12 +43,13 @@ def test_search_matches_name_email_number_and_german_birth_date(tmp_path) -> Non
|
||||
member = repository.create_member(
|
||||
first_name="Jörg",
|
||||
last_name="Müller",
|
||||
nickname="Jogi",
|
||||
email="joerg.mueller@example.org",
|
||||
birth_date="1990-04-23",
|
||||
member_number="C3-007",
|
||||
)
|
||||
|
||||
for query in ("Jorg Muller", "mueller@example.org", "C3-007", "23.04.1990"):
|
||||
for query in ("Jorg Muller", "Jogi", "mueller@example.org", "C3-007", "23.04.1990"):
|
||||
assert [result.member_id for result in repository.search(query)] == [member.member_id]
|
||||
|
||||
|
||||
@@ -138,6 +141,7 @@ def test_automatic_member_numbers_are_sequential_and_preview_does_not_consume(tm
|
||||
first = repository.create_member(first_name="First", last_name="Member")
|
||||
second = repository.create_member(first_name="Second", last_name="Member")
|
||||
|
||||
|
||||
assert first.member_number == "CCMA-0001"
|
||||
assert second.member_number == "CCMA-0002"
|
||||
assert repository.preview_member_number() == "CCMA-0003"
|
||||
|
||||
@@ -91,6 +91,84 @@ def test_housekeeper_claim_actions_are_idempotent(tmp_path) -> None:
|
||||
assert state["last_completed_run"] == "2026-04-15:000002"
|
||||
|
||||
|
||||
def test_housekeeper_creates_membership_claims_retroactively_since_entry(tmp_path) -> None:
|
||||
repository = MemberRepository(tmp_path)
|
||||
repository.initialize()
|
||||
member = repository.create_member(first_name="Retro", last_name="Claims", birth_date="1990-01-01")
|
||||
member.status = "active"
|
||||
member.accepted_at = "2024-04-15"
|
||||
member.membership_started_at = "2024-04-15"
|
||||
repository.save_member(member)
|
||||
|
||||
settings = housekeeper_module.HousekeeperSettings.from_values(
|
||||
birthday_days_before=0,
|
||||
birthday_days_after=0,
|
||||
anniversary_days_before=0,
|
||||
anniversary_days_after=0,
|
||||
anniversary_intervals="1Y",
|
||||
retroactive_claims=True,
|
||||
)
|
||||
|
||||
Housekeeper(repository, settings).run(today=date(2026, 6, 21))
|
||||
|
||||
claims = repository.get_contributions(member.member_id).claims
|
||||
claims_by_key = {claim["claim_key"]: claim for claim in claims}
|
||||
|
||||
assert set(claims_by_key) == {
|
||||
"admission-fee",
|
||||
"membership-fee:2024:annual",
|
||||
"membership-fee:2025:annual",
|
||||
"membership-fee:2026:annual",
|
||||
}
|
||||
assert claims_by_key["membership-fee:2024:annual"]["amount"] == "150.00"
|
||||
|
||||
|
||||
def test_housekeeper_uses_pre_2022_contribution_amounts_for_legacy_years(tmp_path) -> None:
|
||||
repository = MemberRepository(tmp_path)
|
||||
repository.initialize()
|
||||
member = repository.create_member(first_name="Legacy", last_name="Rates", birth_date="1990-01-01")
|
||||
member.status = "active"
|
||||
member.accepted_at = "2021-04-15"
|
||||
member.membership_started_at = "2021-04-15"
|
||||
repository.save_member(member)
|
||||
|
||||
settings = housekeeper_module.HousekeeperSettings.from_values(
|
||||
birthday_days_before=0,
|
||||
birthday_days_after=0,
|
||||
anniversary_days_before=0,
|
||||
anniversary_days_after=0,
|
||||
anniversary_intervals="1Y",
|
||||
retroactive_claims=True,
|
||||
)
|
||||
|
||||
Housekeeper(repository, settings).run(today=date(2022, 6, 21))
|
||||
|
||||
claims = repository.get_contributions(member.member_id).claims
|
||||
claims_by_key = {claim["claim_key"]: claim for claim in claims}
|
||||
|
||||
assert claims_by_key["membership-fee:2021:annual"]["amount"] == "90.00"
|
||||
assert claims_by_key["membership-fee:2022:annual"]["amount"] == "150.00"
|
||||
|
||||
|
||||
def test_housekeeper_does_not_create_retroactive_membership_claims_by_default(tmp_path) -> None:
|
||||
repository = MemberRepository(tmp_path)
|
||||
repository.initialize()
|
||||
member = repository.create_member(first_name="Current", last_name="Only", birth_date="1990-01-01")
|
||||
member.status = "active"
|
||||
member.accepted_at = "2024-04-15"
|
||||
member.membership_started_at = "2024-04-15"
|
||||
repository.save_member(member)
|
||||
|
||||
Housekeeper(repository).run(today=date(2026, 6, 21))
|
||||
|
||||
claim_keys = {claim["claim_key"] for claim in repository.get_contributions(member.member_id).claims}
|
||||
|
||||
assert claim_keys == {
|
||||
"admission-fee",
|
||||
"membership-fee:2026:annual",
|
||||
}
|
||||
|
||||
|
||||
def test_housekeeper_resolves_tasks_not_seen_in_current_run(tmp_path) -> None:
|
||||
repository = MemberRepository(tmp_path)
|
||||
repository.initialize()
|
||||
|
||||
@@ -96,3 +96,46 @@ def test_german_ui_labels_round_trip_to_english_storage_keys() -> None:
|
||||
assert storage_key(CLAIM_ITEM_TYPE_LABELS, "Dienstleistung") == "service"
|
||||
assert display_label(MEMBERSHIP_STATUS_LABELS, "active") == "AKTIV"
|
||||
assert storage_key(MEMBERSHIP_STATUS_LABELS, "EHRENMITGLIED") == "honorary"
|
||||
|
||||
|
||||
def test_member_table_filter_only_keeps_selected_status() -> None:
|
||||
from ccma.domain.models import Member
|
||||
from ccma.ui.work_tabs import _filter_members, _selected_status_filter
|
||||
|
||||
members = [
|
||||
Member("1", "0001", "Ada", "Lovelace", status="active"),
|
||||
Member("2", "0002", "Grace", "Hopper", status="application"),
|
||||
Member("3", "0003", "Linus", "Example", status="active"),
|
||||
]
|
||||
|
||||
assert _selected_status_filter("Alle") == "all"
|
||||
assert _selected_status_filter("AKTIV") == "active"
|
||||
assert [member.member_id for member in _filter_members(members, "active")] == ["1", "3"]
|
||||
assert [member.member_id for member in _filter_members(members, "all")] == ["1", "2", "3"]
|
||||
|
||||
|
||||
def test_member_table_sort_uses_display_values() -> None:
|
||||
from ccma.domain.models import Member
|
||||
from ccma.ui.work_tabs import _sort_members
|
||||
|
||||
members = [
|
||||
Member("1", "0002", "Grace", "Hopper", status="application"),
|
||||
Member("2", "0001", "Ada", "Lovelace", status="active"),
|
||||
Member("3", "0003", "Linus", "Example", status="honorary"),
|
||||
]
|
||||
|
||||
assert [member.member_id for member in _sort_members(members, "number", False)] == ["2", "1", "3"]
|
||||
assert [member.member_id for member in _sort_members(members, "first_name", False)] == ["2", "1", "3"]
|
||||
assert [member.member_id for member in _sort_members(members, "last_name", False)] == ["3", "1", "2"]
|
||||
assert [member.member_id for member in _sort_members(members, "status", False)] == ["2", "1", "3"]
|
||||
|
||||
|
||||
def test_claim_table_sort_uses_due_date_by_raw_value() -> None:
|
||||
from ccma.domain.models import ContributionData
|
||||
from ccma.ui.member_tab import _claim_sort_value
|
||||
|
||||
data = ContributionData()
|
||||
older = {"title": "Alt", "due_date": "2024-01-31", "amount": "75.00"}
|
||||
newer = {"title": "Neu", "due_date": "2025-07-31", "amount": "50.00"}
|
||||
|
||||
assert _claim_sort_value(data, older, "due") < _claim_sort_value(data, newer, "due")
|
||||
|
||||
Reference in New Issue
Block a user