mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-07-01 11:14:52 +02:00
fix: tolerate invalid member dates in views
This commit is contained in:
@@ -16,6 +16,7 @@
|
|||||||
"Datumseingabe und -anzeige an das Systemformat angepasst; gespeichert wird weiterhin portabel im ISO-Format.",
|
"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.",
|
"Eine ribbonweite Mitgliederliste mit direktem Zugriff auf alle Akten ergänzt.",
|
||||||
"Texthintergründe der Dashboard-Karten an die Kartenflächen angeglichen.",
|
"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.",
|
"Hausmeister um konfigurierbare Geburtstags- und Mitgliedsjubiläumsmeldungen erweitert.",
|
||||||
"Statusänderungen werden mit altem und neuem Klartextwert in der Mitgliederchronik protokolliert.",
|
"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.",
|
"Fensterposition, normaler Fensterzustand und Maximierung werden gespeichert; der Splash startet auf dem zuletzt verwendeten Monitor.",
|
||||||
|
|||||||
@@ -79,9 +79,13 @@ def normalize_date_input(value: str, field_name: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def format_date_for_display(value: str) -> str:
|
def format_date_for_display(value: str) -> str:
|
||||||
if not value.strip():
|
text = value.strip()
|
||||||
|
if not text:
|
||||||
return ""
|
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 ""
|
return parsed.strftime(system_date_pattern()) if parsed else ""
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,12 @@ import calendar
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from datetime import date, timedelta
|
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.domain.models import HousekeeperFinding
|
||||||
from ccma.services.intervals import AnniversaryInterval, parse_anniversary_intervals
|
from ccma.services.intervals import AnniversaryInterval, parse_anniversary_intervals
|
||||||
from ccma.storage.repository import MemberRepository
|
from ccma.storage.repository import MemberRepository
|
||||||
@@ -48,6 +53,23 @@ class Housekeeper:
|
|||||||
current_date = today or date.today()
|
current_date = today or date.today()
|
||||||
findings: list[HousekeeperFinding] = []
|
findings: list[HousekeeperFinding] = []
|
||||||
for member in self.repository.list_members():
|
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 {
|
if member.status in {
|
||||||
"active",
|
"active",
|
||||||
"suspended_contribution",
|
"suspended_contribution",
|
||||||
|
|||||||
@@ -36,6 +36,10 @@ def test_date_display_uses_system_pattern(monkeypatch) -> None:
|
|||||||
assert format_date_for_display("2024-02-29") == "2024-02-29"
|
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:
|
def test_birth_date_checks_future_and_plausibility() -> None:
|
||||||
today = date(2026, 6, 21)
|
today = date(2026, 6, 21)
|
||||||
assert validate_birth_date("2000-06-22", today=today) == date(2000, 6, 22)
|
assert validate_birth_date("2000-06-22", today=today) == date(2000, 6, 22)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import json
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
|
||||||
from ccma.domain.models import ContributionData
|
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",
|
"Anniversary2 Member hat heute 1-jähriges Mitgliedsjubiläum",
|
||||||
"Anniversary3 Member hat in 1 Tag 10-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
|
||||||
|
|||||||
Reference in New Issue
Block a user