diff --git a/src/ccma/assets/CHANGELOG.json b/src/ccma/assets/CHANGELOG.json index e77d964..b2db78d 100644 --- a/src/ccma/assets/CHANGELOG.json +++ b/src/ccma/assets/CHANGELOG.json @@ -3,6 +3,7 @@ "version": "0.2.0", "date": "upcoming", "changes": [ + "Mitgliedsanträge und Mitgliederakten enthalten jetzt ein separates Feld für das Antragsdatum.", "Eigentum und aktueller Gewahrsam von Assets werden getrennt dokumentiert; neben Vereinseigentum kann nun auch Eigentum von Mitgliedern oder externen Personen zentral erfasst werden.", "Mitgliedseigentum wird per UUID mit der Mitgliedsakte verknüpft und erscheint dort gemeinsam mit den vom Verein erhaltenen Assets in getrennt verständlichen Ansichten.", "Standort, Übergabedatum, Zustand und Wert eines Assets können für Übergabe- und Versicherungsnachweise festgehalten werden; Eigentumsänderungen erscheinen zusätzlich in der Chronik.", diff --git a/src/ccma/domain/models.py b/src/ccma/domain/models.py index b8d50b6..3d34405 100644 --- a/src/ccma/domain/models.py +++ b/src/ccma/domain/models.py @@ -95,6 +95,7 @@ class Member: mandate_active: bool = False mandate_revoked_at: str = "" status: str = "application" + application_date: str = "" accepted_at: str = "" membership_started_at: str = "" membership_ended_at: str = "" @@ -141,6 +142,7 @@ class Member: }, "membership": { "status": self.status, + "application_date": self.application_date, "accepted_at": self.accepted_at, "started_at": self.membership_started_at, "ended_at": self.membership_ended_at, @@ -185,6 +187,7 @@ class Member: mandate_active=bool(banking.get("mandate_active", False)), mandate_revoked_at=str(banking.get("mandate_revoked_at", "")), status=str(membership.get("status", "application")), + application_date=str(membership.get("application_date", "")), accepted_at=str(membership.get("accepted_at", "")), membership_started_at=str(membership.get("started_at", "")), membership_ended_at=str(membership.get("ended_at", "")), diff --git a/src/ccma/storage/repository.py b/src/ccma/storage/repository.py index 4bc1c3d..9ac247b 100644 --- a/src/ccma/storage/repository.py +++ b/src/ccma/storage/repository.py @@ -295,6 +295,7 @@ class MemberRepository: email: str = "", phone: str = "", birth_date: str = "", + application_date: str = "", member_number: str = "", ) -> Member: with self._member_number_lock(): @@ -305,6 +306,7 @@ class MemberRepository: email=email, phone=phone, birth_date=birth_date, + application_date=application_date, member_number=member_number, ) @@ -317,12 +319,14 @@ class MemberRepository: email: str, phone: str, birth_date: str, + application_date: str, member_number: str, ) -> Member: if not first_name.strip() or not last_name.strip(): raise RepositoryError("Vorname und Nachname sind erforderlich.") try: birth_date = normalize_date_input(birth_date, "Geburtsdatum") + application_date = normalize_date_input(application_date, "Antragsdatum") validate_member_dates(birth_date=birth_date) except DateValidationError as exc: raise RepositoryError(str(exc)) from exc @@ -349,6 +353,7 @@ class MemberRepository: email=email.strip(), phone=phone.strip(), birth_date=birth_date, + application_date=application_date, ) write_json_atomic(directory / "member.json", member.to_dict()) write_json_atomic(directory / "contributions.json", ContributionData().to_dict()) @@ -365,6 +370,7 @@ class MemberRepository: existing = self.get_member(member.member_id) try: member.birth_date = normalize_date_input(member.birth_date, "Geburtsdatum") + member.application_date = normalize_date_input(member.application_date, "Antragsdatum") member.accepted_at = normalize_date_input(member.accepted_at, "Aufnahmebeschluss") member.membership_started_at = normalize_date_input(member.membership_started_at, "Mitglied seit") member.membership_ended_at = normalize_date_input(member.membership_ended_at, "Austrittsdatum") @@ -1892,6 +1898,7 @@ class MemberRepository: "email": "E-Mail-Adresse", "phone": "Telefonnummer", "birth_date": "Geburtsdatum", + "application_date": "Antragsdatum", "membership_ended_at": "Austrittsdatum", "status": "Status", "payment_frequency": "Zahlungsweise", diff --git a/src/ccma/ui/dialogs.py b/src/ccma/ui/dialogs.py index fa74a5a..c2c5ae4 100644 --- a/src/ccma/ui/dialogs.py +++ b/src/ccma/ui/dialogs.py @@ -41,6 +41,7 @@ class NewMemberDialog(tk.Toplevel): "email", "phone", "birth_date", + "application_date", "member_number", ) } @@ -63,6 +64,7 @@ class NewMemberDialog(tk.Toplevel): ("E-Mail-Adresse", "email"), ("Telefonnummer", "phone"), (f"Geburtsdatum ({date_input_hint()})", "birth_date"), + (f"Antragsdatum ({date_input_hint()})", "application_date"), ] if self.number_policy["mode"] == "manual": fields.append(("Mitgliedsnummer *", "member_number")) diff --git a/src/ccma/ui/member_tab.py b/src/ccma/ui/member_tab.py index 288b310..41b3658 100644 --- a/src/ccma/ui/member_tab.py +++ b/src/ccma/ui/member_tab.py @@ -170,6 +170,7 @@ class MemberTab(ttk.Frame): ("E-Mail-Adresse", "email"), ("Telefonnummer", "phone"), (f"Geburtsdatum ({date_input_hint()})", "birth_date"), + (f"Antragsdatum ({date_input_hint()})", "application_date"), (f"Aufnahmebeschluss ({date_input_hint()})", "accepted_at"), (f"Mitglied seit ({date_input_hint()})", "membership_started_at"), (f"Austrittsdatum ({date_input_hint()})", "membership_ended_at"), @@ -475,6 +476,7 @@ class MemberTab(ttk.Frame): ) date_fields = { "birth_date", + "application_date", "accepted_at", "membership_started_at", "membership_ended_at", diff --git a/tests/test_repository.py b/tests/test_repository.py index f574f0a..29ad2e9 100644 --- a/tests/test_repository.py +++ b/tests/test_repository.py @@ -25,6 +25,7 @@ def test_repository_creates_transparent_member_record(tmp_path) -> None: nickname="Enchantress", email="ada@example.org", birth_date="1990-12-10", + application_date="30.07.2026", member_number="0042", ) @@ -38,6 +39,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["membership"]["application_date"] == "2026-07-30" assert raw["schema_version"] == 1 assert raw["membership"]["ended_at"] == "" assert raw["contribution_profile"]["payment_frequency"] == "semiannual" @@ -144,6 +146,18 @@ def test_repository_accepts_local_date_input_and_rejects_invalid_dates(tmp_path) repository.create_member(first_name="Invalid", last_name="Date", birth_date="31.02.2000") +def test_existing_member_without_application_date_stays_empty(tmp_path) -> None: + repository = MemberRepository(tmp_path) + repository.initialize() + member = repository.create_member(first_name="Legacy", last_name="Member") + path = repository.members_root / member.member_id / "member.json" + raw = json.loads(path.read_text(encoding="utf-8")) + raw["membership"].pop("application_date") + path.write_text(json.dumps(raw), encoding="utf-8") + + assert repository.get_member(member.member_id).application_date == "" + + def test_repository_reports_empty_contributions_file(tmp_path) -> None: repository = MemberRepository(tmp_path) repository.initialize()