feat: add member address and SEPA data

This commit is contained in:
Marcel Peterkau
2026-06-21 22:14:45 +02:00
parent 0622a22794
commit 6c7bf63280
12 changed files with 362 additions and 10 deletions
+2 -1
View File
@@ -19,7 +19,7 @@ class NewMemberDialog(tk.Toplevel):
self.number_policy = repository.get_member_number_policy()
self.variables = {
name: tk.StringVar()
for name in ("first_name", "last_name", "email", "birth_date", "member_number")
for name in ("first_name", "last_name", "email", "phone", "birth_date", "member_number")
}
self._build_ui()
self.bind("<Escape>", lambda _event: self.destroy())
@@ -33,6 +33,7 @@ class NewMemberDialog(tk.Toplevel):
("Vorname *", "first_name"),
("Nachname *", "last_name"),
("E-Mail-Adresse", "email"),
("Telefonnummer", "phone"),
(f"Geburtsdatum ({date_input_hint()})", "birth_date"),
]
if self.number_policy["mode"] == "manual":
+58 -2
View File
@@ -83,9 +83,13 @@ class MemberTab(ttk.Frame):
notebook = ttk.Notebook(parent)
notebook.grid(row=0, column=0, sticky="nsew")
data_tab = ttk.Frame(notebook, padding=16)
address_tab = ttk.Frame(notebook, padding=16)
banking_tab = ttk.Frame(notebook, padding=16)
contribution_tab = ttk.Frame(notebook, padding=16)
documents_tab = ttk.Frame(notebook, padding=16)
notebook.add(data_tab, text="Stammdaten")
notebook.add(address_tab, text="Anschrift")
notebook.add(banking_tab, text="Bank / SEPA")
notebook.add(contribution_tab, text="Forderungen")
notebook.add(documents_tab, text="Dokumente")
@@ -94,6 +98,7 @@ class MemberTab(ttk.Frame):
("Vorname", "first_name"),
("Nachname", "last_name"),
("E-Mail-Adresse", "email"),
("Telefonnummer", "phone"),
(f"Geburtsdatum ({date_input_hint()})", "birth_date"),
(f"Aufnahmebeschluss ({date_input_hint()})", "accepted_at"),
(f"Mitglied seit ({date_input_hint()})", "membership_started_at"),
@@ -139,6 +144,50 @@ class MemberTab(ttk.Frame):
row=len(fields) + 2, column=1, sticky="e", pady=(18, 0)
)
address_fields = (
("Straße und Hausnummer", "street"),
("Adresszusatz", "address_addition"),
("Postleitzahl", "postal_code"),
("Ort", "city"),
("Land", "country"),
)
address_tab.columnconfigure(1, weight=1)
for row, (label, key) in enumerate(address_fields):
self.variables[key] = tk.StringVar()
ttk.Label(address_tab, text=label).grid(row=row, column=0, sticky="w", padx=(0, 12), pady=5)
ttk.Entry(address_tab, textvariable=self.variables[key], width=42).grid(
row=row, column=1, sticky="ew", pady=5
)
ttk.Button(
address_tab, text="Anschrift speichern", style="Accent.TButton", command=self._save
).grid(row=len(address_fields), column=1, sticky="e", pady=(18, 0))
banking_fields = (
("Kontoinhaber", "account_holder"),
("IBAN", "iban"),
("BIC", "bic"),
("Mandatsreferenz", "mandate_reference"),
(f"Mandat erteilt am ({date_input_hint()})", "mandate_signed_at"),
(f"Mandat widerrufen am ({date_input_hint()})", "mandate_revoked_at"),
)
banking_tab.columnconfigure(1, weight=1)
for row, (label, key) in enumerate(banking_fields):
self.variables[key] = tk.StringVar()
ttk.Label(banking_tab, text=label).grid(row=row, column=0, sticky="w", padx=(0, 12), pady=5)
ttk.Entry(banking_tab, textvariable=self.variables[key], width=42).grid(
row=row, column=1, sticky="ew", pady=5
)
self.variables["mandate_active"] = tk.BooleanVar()
ttk.Checkbutton(
banking_tab,
text="SEPA-Lastschriftmandat ist aktiv",
variable=self.variables["mandate_active"],
style="Switch",
).grid(row=len(banking_fields), column=0, columnspan=2, sticky="w", pady=(12, 5))
ttk.Button(
banking_tab, text="Bankdaten speichern", style="Accent.TButton", command=self._save
).grid(row=len(banking_fields) + 1, column=1, sticky="e", pady=(18, 0))
contribution_tab.columnconfigure(0, weight=1)
contribution_tab.rowconfigure(1, weight=1)
self.contribution_summary = tk.StringVar()
@@ -225,7 +274,13 @@ class MemberTab(ttk.Frame):
self.member = self.repository.get_member(self.member_id)
self.title_var.set(f"{self.member.member_number or ''} · {self.member.display_name}")
self.status_var.set(STATUS_LABELS.get(self.member.status, self.member.status.upper()))
date_fields = {"birth_date", "accepted_at", "membership_started_at"}
date_fields = {
"birth_date",
"accepted_at",
"membership_started_at",
"mandate_signed_at",
"mandate_revoked_at",
}
for key, variable in self.variables.items():
value = getattr(self.member, key)
if key == "status":
@@ -303,7 +358,8 @@ class MemberTab(ttk.Frame):
def _save(self) -> None:
for key, variable in self.variables.items():
value = variable.get().strip()
raw_value = variable.get()
value = raw_value.strip() if isinstance(raw_value, str) else raw_value
if key == "status":
value = storage_key(STATUS_LABELS, value)
setattr(self.member, key, value)
+37
View File
@@ -42,6 +42,16 @@ class OptionsDialog(tk.Toplevel):
self.manual_numbers_var = tk.BooleanVar(value=number_policy["mode"] == "manual")
self.number_pattern_var = tk.StringVar(value=number_policy["pattern"])
self.number_preview_var = tk.StringVar()
organization = repository.get_configuration().get("organization") or {}
if not isinstance(organization, dict):
organization = {}
self.organization_vars = {
key: tk.StringVar(value=str(organization.get(key, "")))
for key in (
"name", "street", "postal_code", "city", "country", "email", "phone",
"website", "iban", "bic", "creditor_id",
)
}
self.title("Optionen")
self.geometry("900x650")
self.minsize(760, 540)
@@ -65,16 +75,19 @@ class OptionsDialog(tk.Toplevel):
paths = ttk.Frame(notebook, padding=16)
appearance = ttk.Frame(notebook, padding=16)
member_numbers = ttk.Frame(notebook, padding=16)
organization = ttk.Frame(notebook, padding=16)
automation = ttk.Frame(notebook, padding=16)
changelog = ChangelogView(notebook)
notebook.add(paths, text="Pfade")
notebook.add(appearance, text="Darstellung")
notebook.add(member_numbers, text="Mitgliedsnummern")
notebook.add(organization, text="Verein / Absender")
notebook.add(automation, text="Hausmeister")
notebook.add(changelog, text="Changelog")
self._build_paths(paths)
self._build_appearance(appearance)
self._build_member_numbers(member_numbers)
self._build_organization(organization)
self._build_automation(automation)
buttons = ttk.Frame(root)
@@ -148,6 +161,27 @@ class OptionsDialog(tk.Toplevel):
style="Muted.TLabel",
).grid(row=1, column=1, sticky="w")
def _build_organization(self, parent: ttk.Frame) -> None:
parent.columnconfigure(1, weight=1)
fields = (
("Vereinsname", "name"),
("Straße und Hausnummer", "street"),
("Postleitzahl", "postal_code"),
("Ort", "city"),
("Land", "country"),
("E-Mail-Adresse", "email"),
("Telefonnummer", "phone"),
("Webseite", "website"),
("IBAN", "iban"),
("BIC", "bic"),
("SEPA-Gläubiger-ID", "creditor_id"),
)
for row, (label, key) in enumerate(fields):
ttk.Label(parent, text=label).grid(row=row, column=0, sticky="w", padx=(0, 12), pady=5)
ttk.Entry(parent, textvariable=self.organization_vars[key], width=48).grid(
row=row, column=1, sticky="ew", pady=5
)
def _build_automation(self, parent: ttk.Frame) -> None:
parent.columnconfigure(1, weight=1)
ttk.Checkbutton(
@@ -350,6 +384,9 @@ class OptionsDialog(tk.Toplevel):
try:
self.config_obj.save()
self.repository.save_member_number_policy(mode=number_mode, pattern=number_pattern)
self.repository.save_organization(
{key: variable.get() for key, variable in self.organization_vars.items()}
)
except (OSError, RepositoryError) as exc:
messagebox.showerror("Optionen konnten nicht gespeichert werden", str(exc), parent=self)
return