feat: extend inventory and administration workflows

This commit is contained in:
Marcel Peterkau
2026-07-22 22:26:02 +02:00
parent aeaaca1459
commit 070684d9bc
17 changed files with 1342 additions and 69 deletions
+97 -5
View File
@@ -88,8 +88,10 @@ class ClaimTab(ttk.Frame):
footer.columnconfigure(0, weight=1)
self.hold_button = ttk.Button(footer, text="Mahnsperre setzen", command=self._toggle_hold)
self.hold_button.grid(row=0, column=0, sticky="w")
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=1, sticky="e")
self.cancel_button.grid(row=0, column=2, sticky="e")
def _build_ledger(self) -> None:
ledger = ttk.Frame(self, padding=12)
@@ -185,6 +187,7 @@ class ClaimTab(ttk.Frame):
self.summary_vars["balance"].set(f"{money_text(balance)} EUR")
self.summary_vars["status"].set(CLAIM_STATUS_LABELS.get(status, status.upper()))
self.cancel_button.configure(state="disabled" if status == "cancelled" else "normal")
self.edit_button.configure(state="disabled" if status == "cancelled" else "normal")
self.hold_button.configure(text="Mahnsperre aufheben" if hold.get("active") else "Mahnsperre setzen")
self._render_ledger()
@@ -478,6 +481,16 @@ class ClaimTab(ttk.Frame):
return
self._changed()
def _edit_claim(self) -> None:
ClaimEditDialog(
self,
self.repository,
self.member_id,
self.claim_id,
self.claim,
self._changed,
)
def _changed(self) -> None:
self.refresh()
self.on_changed()
@@ -502,6 +515,68 @@ class _Dialog(tk.Toplevel):
ttk.Button(buttons, text="Speichern", style="Accent.TButton", command=command).pack(side="left")
class ClaimEditDialog(_Dialog):
def __init__(self, master, repository, member_id, claim_id, claim, on_saved):
super().__init__(master, "Forderung bearbeiten", on_saved)
self.repository = repository
self.member_id = member_id
self.claim_id = claim_id
items = claim_items(claim)
base_item = next(
(item for item in items if str(item.get("type", "")) == "base"),
items[0] if items else {},
)
self.variables = {
"title": tk.StringVar(value=str(claim.get("title", ""))),
"due_date": tk.StringVar(
value=format_date_for_display(str(claim.get("due_date", "")))
),
"base_amount": tk.StringVar(
value=str(base_item.get("amount", claim.get("amount", "")))
),
"description": tk.StringVar(value=str(base_item.get("description", ""))),
}
fields = (
("Titel", "title"),
(f"Fällig am ({date_input_hint()})", "due_date"),
("Grundbetrag", "base_amount"),
("Beschreibung der Grundposition", "description"),
)
for row, (label, key) in enumerate(fields):
ttk.Label(self.frame, text=label).grid(
row=row, column=0, sticky="w", pady=5, padx=(0, 12)
)
ttk.Entry(self.frame, textvariable=self.variables[key], width=52).grid(
row=row, column=1, sticky="ew", pady=5
)
ttk.Label(
self.frame,
text=(
"Der Grundbetrag ändert nur die Grundposition. Weitere Positionen, "
"zum Beispiel Mahngebühren, bleiben erhalten."
),
style="Mono.TLabel",
wraplength=520,
).grid(row=len(fields), column=0, columnspan=2, sticky="w", pady=(8, 0))
self._buttons(len(fields) + 1, self._save)
def _save(self) -> None:
try:
self.repository.update_claim(
self.member_id,
self.claim_id,
title=self.variables["title"].get(),
due_date=self.variables["due_date"].get(),
base_amount=self.variables["base_amount"].get(),
description=self.variables["description"].get(),
)
except RepositoryError as exc:
messagebox.showerror("Forderung konnte nicht gespeichert werden", str(exc), parent=self)
return
self.destroy()
self.on_saved()
class ItemDialog(_Dialog):
def __init__(self, master, repository, member_id, claim_id, on_saved):
super().__init__(master, "Forderungsposition hinzufügen", on_saved)
@@ -634,7 +709,11 @@ class AllocatePaymentDialog(_Dialog):
self.repository, self.member_id, self.claim_id = repository, member_id, claim_id
data = repository.get_contributions(member_id)
self.payment_by_label = {}
for payment in data.payments:
for payment in sorted(
data.payments,
key=lambda item: (str(item.get("date", "")), str(item.get("created_at", ""))),
reverse=True,
):
payment_id = str(payment.get("payment_id", ""))
available = decimal_value(payment.get("amount", "0")) - payment_allocated_total(data, payment_id)
if available <= 0:
@@ -655,10 +734,23 @@ class AllocatePaymentDialog(_Dialog):
width=60,
)
combo.grid(row=0, column=1, pady=5)
ttk.Label(self.frame, text="Betrag").grid(row=1, column=0, sticky="w", pady=5, padx=(0, 12))
ttk.Entry(self.frame, textvariable=self.amount_var).grid(row=1, column=1, sticky="ew", pady=5)
if not self.payment_by_label:
ttk.Label(
self.frame,
text="Für dieses Mitglied gibt es keine Zahlung mit freiem Restbetrag.",
style="Mono.TLabel",
).grid(row=1, column=0, columnspan=2, sticky="w", pady=(3, 5))
amount_row = 2
else:
amount_row = 1
ttk.Label(self.frame, text="Betrag").grid(
row=amount_row, column=0, sticky="w", pady=5, padx=(0, 12)
)
ttk.Entry(self.frame, textvariable=self.amount_var).grid(
row=amount_row, column=1, sticky="ew", pady=5
)
combo.bind("<<ComboboxSelected>>", lambda _event: self._select(balance))
self._buttons(2, self._save)
self._buttons(amount_row + 1, self._save)
def _select(self, balance):
_payment_id, available = self.payment_by_label[self.payment_var.get()]