mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-07-01 19:26:53 +02:00
Add asset records, claims, and credit workflows
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user