Allow deleting claims, creating standalone payments, and add a donations tab

Claims could previously only be cancelled (stornieren), which blocks once a
payment is allocated. Add a hard delete that releases any linked
payments/credits back to being unallocated instead of destroying them.

Payments could only be created from within a claim, forcing immediate
allocation. Add a bare payment creation flow in the Zahlungen tab so
incoming transfers can be logged first and allocated later.

Add a per-member Spenden tab (donations, backed by a new donations list on
ContributionData) so amounts paid beyond the membership fee can be tracked
and existing free payments allocated to them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Marcel Peterkau
2026-08-14 21:02:51 +02:00
co-authored by Claude Sonnet 5
parent a61ea3cb57
commit 42fb4c4224
8 changed files with 960 additions and 2 deletions
+21 -1
View File
@@ -92,7 +92,9 @@ class ClaimTab(ttk.Frame):
self.edit_button = ttk.Button(footer, text="Forderung bearbeiten", command=self._edit_claim)
self.edit_button.grid(row=0, column=1, sticky="e", padx=(0, 8))
self.cancel_button = ttk.Button(footer, text="Forderung stornieren", command=self._cancel_claim)
self.cancel_button.grid(row=0, column=2, sticky="e")
self.cancel_button.grid(row=0, column=2, sticky="e", padx=(0, 8))
self.delete_button = ttk.Button(footer, text="Forderung löschen", command=self._delete_claim)
self.delete_button.grid(row=0, column=3, sticky="e")
def _build_ledger(self) -> None:
ledger = ttk.Frame(self, padding=12)
@@ -503,6 +505,24 @@ class ClaimTab(ttk.Frame):
return
self._changed()
def _delete_claim(self) -> None:
allocated = allocated_total(self.data, self.claim_id)
detail = "Diese Forderung wirklich endgültig löschen? Das kann nicht rückgängig gemacht werden."
if allocated:
detail += (
f"\n\nZugeordnete Zahlungen/Gutschriften in Höhe von {money_text(allocated)} EUR "
"werden dabei gelöst und stehen danach wieder frei zur Verfügung."
)
if not messagebox.askyesno("Forderung löschen", detail, parent=self):
return
try:
self.repository.delete_claim(self.member_id, self.claim_id)
except RepositoryError as exc:
messagebox.showerror("Löschen fehlgeschlagen", str(exc), parent=self)
return
self.on_changed()
self.on_close()
def _edit_claim(self) -> None:
ClaimEditDialog(
self,