feat: add application date to members

This commit is contained in:
Marcel Peterkau
2026-07-30 19:35:48 +02:00
parent e3f0a1df86
commit 7a0cdbc04e
6 changed files with 29 additions and 0 deletions
+1
View File
@@ -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.",
+3
View File
@@ -88,6 +88,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 = ""
@@ -134,6 +135,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,
@@ -178,6 +180,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", "")),
+7
View File
@@ -294,6 +294,7 @@ class MemberRepository:
email: str = "",
phone: str = "",
birth_date: str = "",
application_date: str = "",
member_number: str = "",
) -> Member:
with self._member_number_lock():
@@ -304,6 +305,7 @@ class MemberRepository:
email=email,
phone=phone,
birth_date=birth_date,
application_date=application_date,
member_number=member_number,
)
@@ -316,12 +318,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
@@ -348,6 +352,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())
@@ -364,6 +369,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")
@@ -1889,6 +1895,7 @@ class MemberRepository:
"email": "E-Mail-Adresse",
"phone": "Telefonnummer",
"birth_date": "Geburtsdatum",
"application_date": "Antragsdatum",
"membership_ended_at": "Austrittsdatum",
"status": "Status",
"payment_frequency": "Zahlungsweise",
+2
View File
@@ -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"))
+2
View File
@@ -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"),
@@ -464,6 +465,7 @@ class MemberTab(ttk.Frame):
)
date_fields = {
"birth_date",
"application_date",
"accepted_at",
"membership_started_at",
"membership_ended_at",
+14
View File
@@ -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["content_hash"]
@@ -121,6 +123,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()