Add asset records, claims, and credit workflows

This commit is contained in:
Marcel Peterkau
2026-06-26 23:03:06 +02:00
parent 30b6d253b2
commit d1dab793a6
11 changed files with 2060 additions and 10 deletions
+157
View File
@@ -14,6 +14,7 @@ from ccma.domain.contributions import (
claim_items,
claim_status,
claim_total,
credit_allocated_total,
decimal_value,
money_text,
payment_allocated_total,
@@ -125,6 +126,8 @@ class ClaimTab(ttk.Frame):
self.ledger.tag_configure("position", background="#234d70", foreground="#ffffff")
self.ledger.tag_configure("payment-group", background="#237a3b", foreground="#ffffff")
self.ledger.tag_configure("payment", background="#285b3b", foreground="#ffffff")
self.ledger.tag_configure("credit-group", background="#8b3d88", foreground="#ffffff")
self.ledger.tag_configure("credit", background="#5e2f5b", foreground="#ffffff")
self.ledger.tag_configure("reminder-group", background="#b85f00", foreground="#ffffff")
self.ledger.tag_configure("reminder", background="#70451f", foreground="#ffffff")
self.ledger.bind("<<TreeviewSelect>>", lambda _event: self._update_reminder_buttons())
@@ -137,6 +140,10 @@ class ClaimTab(ttk.Frame):
side="left", padx=(0, 8)
)
ttk.Button(buttons, text="Zahlung erfassen", command=self._record_payment).pack(side="left")
ttk.Button(buttons, text="Vorhandene Gutschrift zuordnen", command=self._allocate_credit).pack(
side="left", padx=(8, 8)
)
ttk.Button(buttons, text="Gutschrift erfassen", command=self._record_credit).pack(side="left")
ttk.Separator(buttons, orient="vertical").pack(side="left", fill="y", padx=10)
self.discard_reminder_button = ttk.Button(
buttons, text="Entwurf verwerfen", command=self._discard_reminder, state="disabled"
@@ -227,6 +234,8 @@ class ClaimTab(ttk.Frame):
)
for allocation in allocations:
payment = payment_by_id.get(str(allocation.get("payment_id", "")), {})
if not payment:
continue
payment_total = str(payment.get("amount", ""))
gnucash_id = str(payment.get("gnucash_transaction_id", ""))
self.ledger.insert(
@@ -246,6 +255,43 @@ class ClaimTab(ttk.Frame):
tags=("payment",),
)
credits_by_id = {str(item.get("credit_id")): item for item in self.data.credits}
credit_allocations = [
allocation
for allocation in self.data.allocations
if str(allocation.get("claim_id", "")) == self.claim_id and str(allocation.get("credit_id", ""))
]
credit_group = self.ledger.insert(
"",
"end",
iid="group:credits",
text=f"Gutschriften ({len(credit_allocations)})",
values=("", "", "", "", f"{money_text(sum((decimal_value(item.get('amount', '0')) for item in credit_allocations), Decimal('0')))} EUR", "", ""),
tags=("credit-group",),
open=True,
)
for allocation in credit_allocations:
credit = credits_by_id.get(str(allocation.get("credit_id", "")), {})
if not credit:
continue
credit_total = str(credit.get("amount", ""))
self.ledger.insert(
credit_group,
"end",
iid=f"credit-allocation:{allocation.get('allocation_id', '')}",
text="Gutschrift",
values=(
format_date_for_display(str(credit.get("date", ""))),
credit.get("reference", ""),
"",
"",
f"{allocation.get('amount', '')} EUR",
f"Gutschrift gesamt: {credit_total} EUR",
"",
),
tags=("credit",),
)
reminders = [
reminder
for reminder in self.data.reminders
@@ -309,6 +355,26 @@ class ClaimTab(ttk.Frame):
self._changed,
)
def _record_credit(self) -> None:
CreditDialog(
self,
self.repository,
self.member_id,
self.claim_id,
claim_balance(self.data, self.claim),
self._changed,
)
def _allocate_credit(self) -> None:
AllocateCreditDialog(
self,
self.repository,
self.member_id,
self.claim_id,
claim_balance(self.data, self.claim),
self._changed,
)
def _add_reminder(self) -> None:
ReminderDialog(self, self.repository, self.member_id, self.claim_id, self._changed)
@@ -508,6 +574,45 @@ class PaymentDialog(_Dialog):
self.on_saved()
class CreditDialog(_Dialog):
def __init__(self, master, repository, member_id, claim_id, balance: Decimal, on_saved):
super().__init__(master, "Gutschrift erfassen", on_saved)
self.repository, self.member_id, self.claim_id = repository, member_id, claim_id
initial = money_text(max(-balance, Decimal("0")))
self.variables = {
"date": tk.StringVar(value=format_date_for_display(date.today().isoformat())),
"amount": tk.StringVar(value=initial),
"allocation": tk.StringVar(value=initial),
"reference": tk.StringVar(),
}
fields = (
(f"Gutschriftsdatum ({date_input_hint()})", "date"),
("Gutschriftsbetrag", "amount"),
("Dieser Gutschrift zuordnen", "allocation"),
("Referenz", "reference"),
)
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=38).grid(row=row, column=1, pady=5)
self._buttons(len(fields), self._save)
def _save(self):
try:
self.repository.record_credit(
self.member_id,
self.claim_id,
credit_date=self.variables["date"].get(),
amount=self.variables["amount"].get(),
allocation_amount=self.variables["allocation"].get(),
reference=self.variables["reference"].get(),
)
except RepositoryError as exc:
messagebox.showerror("Gutschrift konnte nicht gespeichert werden", str(exc), parent=self)
return
self.destroy()
self.on_saved()
class AllocatePaymentDialog(_Dialog):
def __init__(self, master, repository, member_id, claim_id, balance: Decimal, on_saved):
super().__init__(master, "Vorhandene Zahlung zuordnen", on_saved)
@@ -560,6 +665,58 @@ class AllocatePaymentDialog(_Dialog):
self.on_saved()
class AllocateCreditDialog(_Dialog):
def __init__(self, master, repository, member_id, claim_id, balance: Decimal, on_saved):
super().__init__(master, "Vorhandene Gutschrift zuordnen", on_saved)
self.repository, self.member_id, self.claim_id = repository, member_id, claim_id
data = repository.get_contributions(member_id)
self.credit_by_label = {}
for credit in data.credits:
credit_id = str(credit.get("credit_id", ""))
available = decimal_value(credit.get("amount", "0")) - credit_allocated_total(data, credit_id)
if available <= 0:
continue
label = (
f"{credit.get('date', '')} · {money_text(available)} EUR frei · "
f"{credit.get('reference', '')}"
)
self.credit_by_label[label] = (credit_id, available)
self.credit_var = tk.StringVar()
self.amount_var = tk.StringVar(value=money_text(max(-balance, Decimal("0"))))
ttk.Label(self.frame, text="Gutschrift").grid(row=0, column=0, sticky="w", pady=5, padx=(0, 12))
combo = ttk.Combobox(
self.frame,
textvariable=self.credit_var,
values=list(self.credit_by_label),
state="readonly",
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)
combo.bind("<<ComboboxSelected>>", lambda _event: self._select(balance))
self._buttons(2, self._save)
def _select(self, balance):
_credit_id, available = self.credit_by_label[self.credit_var.get()]
self.amount_var.set(money_text(min(available, max(-balance, Decimal("0")))))
def _save(self):
selected = self.credit_by_label.get(self.credit_var.get())
if not selected:
messagebox.showerror("Gutschrift auswählen", "Bitte eine Gutschrift auswählen.", parent=self)
return
try:
self.repository.allocate_credit(
self.member_id, self.claim_id, credit_id=selected[0], amount=self.amount_var.get()
)
except RepositoryError as exc:
messagebox.showerror("Zuordnung fehlgeschlagen", str(exc), parent=self)
return
self.destroy()
self.on_saved()
class ReminderDialog(_Dialog):
def __init__(self, master, repository, member_id, claim_id, on_saved):
super().__init__(master, "Mahnung vorbereiten", on_saved)