diff --git a/src/ccma/assets/CHANGELOG.json b/src/ccma/assets/CHANGELOG.json index 4d52fac..4914c4f 100644 --- a/src/ccma/assets/CHANGELOG.json +++ b/src/ccma/assets/CHANGELOG.json @@ -16,6 +16,7 @@ "Datumseingabe und -anzeige an das Systemformat angepasst; gespeichert wird weiterhin portabel im ISO-Format.", "Eine ribbonweite Mitgliederliste mit direktem Zugriff auf alle Akten ergänzt.", "Texthintergründe der Dashboard-Karten an die Kartenflächen angeglichen.", + "Mitgliederlisten bleiben bei fehlerhaften Datumswerten bedienbar; der Hausmeister meldet die betroffene Akte zur Korrektur.", "Hausmeister um konfigurierbare Geburtstags- und Mitgliedsjubiläumsmeldungen erweitert.", "Statusänderungen werden mit altem und neuem Klartextwert in der Mitgliederchronik protokolliert.", "Fensterposition, normaler Fensterzustand und Maximierung werden gespeichert; der Splash startet auf dem zuletzt verwendeten Monitor.", diff --git a/src/ccma/domain/dates.py b/src/ccma/domain/dates.py index 97097cc..4592014 100644 --- a/src/ccma/domain/dates.py +++ b/src/ccma/domain/dates.py @@ -79,9 +79,13 @@ def normalize_date_input(value: str, field_name: str) -> str: def format_date_for_display(value: str) -> str: - if not value.strip(): + text = value.strip() + if not text: return "" - parsed = parse_iso_date(value, "Datum") + try: + parsed = parse_iso_date(text, "Datum") + except DateValidationError: + return text return parsed.strftime(system_date_pattern()) if parsed else "" diff --git a/src/ccma/services/housekeeper.py b/src/ccma/services/housekeeper.py index 105719f..46126bd 100644 --- a/src/ccma/services/housekeeper.py +++ b/src/ccma/services/housekeeper.py @@ -4,7 +4,12 @@ import calendar from dataclasses import dataclass, field from datetime import date, timedelta -from ccma.domain.dates import DateValidationError, parse_iso_date, validate_birth_date +from ccma.domain.dates import ( + DateValidationError, + parse_iso_date, + validate_birth_date, + validate_member_dates, +) from ccma.domain.models import HousekeeperFinding from ccma.services.intervals import AnniversaryInterval, parse_anniversary_intervals from ccma.storage.repository import MemberRepository @@ -48,6 +53,23 @@ class Housekeeper: current_date = today or date.today() findings: list[HousekeeperFinding] = [] for member in self.repository.list_members(): + try: + validate_member_dates( + birth_date=member.birth_date, + accepted_at=member.accepted_at, + membership_started_at=member.membership_started_at, + today=current_date, + ) + except DateValidationError as exc: + findings.append( + HousekeeperFinding( + severity="error", + member_id=member.member_id, + code="invalid_member_dates", + title=f"{member.display_name}: Ungültige Datumsangabe", + detail=str(exc), + ) + ) if member.status in { "active", "suspended_contribution", diff --git a/tests/test_dates.py b/tests/test_dates.py index fea9d34..f0ad752 100644 --- a/tests/test_dates.py +++ b/tests/test_dates.py @@ -36,6 +36,10 @@ def test_date_display_uses_system_pattern(monkeypatch) -> None: assert format_date_for_display("2024-02-29") == "2024-02-29" +def test_invalid_date_remains_visible_for_correction() -> None: + assert format_date_for_display("31/12/2000") == "31/12/2000" + + def test_birth_date_checks_future_and_plausibility() -> None: today = date(2026, 6, 21) assert validate_birth_date("2000-06-22", today=today) == date(2000, 6, 22) diff --git a/tests/test_housekeeper.py b/tests/test_housekeeper.py index e6b548b..a5d300b 100644 --- a/tests/test_housekeeper.py +++ b/tests/test_housekeeper.py @@ -1,3 +1,4 @@ +import json from datetime import date from ccma.domain.models import ContributionData @@ -91,3 +92,20 @@ def test_housekeeper_reports_day_month_and_year_anniversaries(tmp_path) -> None: "Anniversary2 Member hat heute 1-jähriges Mitgliedsjubiläum", "Anniversary3 Member hat in 1 Tag 10-jähriges Mitgliedsjubiläum", } + + +def test_housekeeper_reports_invalid_member_dates(tmp_path) -> None: + repository = MemberRepository(tmp_path) + repository.initialize() + member = repository.create_member(first_name="Broken", last_name="Date") + member_path = repository.members_root / member.member_id / "member.json" + raw = json.loads(member_path.read_text(encoding="utf-8")) + raw["person"]["birth_date"] = "31/12/2000" + member_path.write_text(json.dumps(raw), encoding="utf-8") + + findings = Housekeeper(repository).run(today=date(2026, 6, 21)) + invalid = [finding for finding in findings if finding.code == "invalid_member_dates"] + + assert len(invalid) == 1 + assert invalid[0].member_id == member.member_id + assert "Geburtsdatum" in invalid[0].detail