mirror of
https://git.hiabuto.net/C3MA/CCMA.git
synced 2026-07-01 11:14:52 +02:00
556 lines
24 KiB
Python
556 lines
24 KiB
Python
import tkinter as tk
|
|
from collections.abc import Callable
|
|
from tkinter import messagebox, ttk
|
|
|
|
from datetime import date
|
|
|
|
from ccma.domain.dates import age_label, date_input_hint
|
|
from ccma.domain.models import Asset, Member
|
|
from ccma.storage.repository import MemberRepository, RepositoryError
|
|
|
|
|
|
def _activate_modal_window(window: tk.Toplevel, focus_widget: tk.Widget | None = None) -> None:
|
|
try:
|
|
window.update_idletasks()
|
|
window.deiconify()
|
|
window.lift()
|
|
window.focus_force()
|
|
if focus_widget is not None and focus_widget.winfo_exists():
|
|
focus_widget.focus_set()
|
|
window.grab_set()
|
|
window.attributes("-topmost", True)
|
|
window.after_idle(lambda: window.winfo_exists() and window.attributes("-topmost", False))
|
|
except tk.TclError:
|
|
return
|
|
|
|
|
|
class NewMemberDialog(tk.Toplevel):
|
|
def __init__(self, master: tk.Misc, repository: MemberRepository, on_created: Callable[[Member], None]):
|
|
super().__init__(master)
|
|
self.repository = repository
|
|
self.on_created = on_created
|
|
self.title("Neue Mitgliederakte")
|
|
self.transient(master.winfo_toplevel())
|
|
self.resizable(False, False)
|
|
self.number_policy = repository.get_member_number_policy()
|
|
self.variables = {
|
|
name: tk.StringVar()
|
|
for name in ("first_name", "last_name", "nickname", "email", "phone", "birth_date", "member_number")
|
|
}
|
|
self._build_ui()
|
|
self.bind("<Escape>", lambda _event: self.destroy())
|
|
self.bind("<Return>", lambda _event: self._create())
|
|
self.after_idle(self._activate_modal)
|
|
self.after_idle(self._focus_first)
|
|
|
|
def _activate_modal(self) -> None:
|
|
_activate_modal_window(self, self.entries.get("first_name"))
|
|
|
|
def _build_ui(self) -> None:
|
|
frame = ttk.Frame(self, padding=18)
|
|
frame.pack(fill="both", expand=True)
|
|
fields = [
|
|
("Vorname *", "first_name"),
|
|
("Nachname *", "last_name"),
|
|
("Nickname", "nickname"),
|
|
("E-Mail-Adresse", "email"),
|
|
("Telefonnummer", "phone"),
|
|
(f"Geburtsdatum ({date_input_hint()})", "birth_date"),
|
|
]
|
|
if self.number_policy["mode"] == "manual":
|
|
fields.append(("Mitgliedsnummer *", "member_number"))
|
|
self.entries: dict[str, ttk.Entry] = {}
|
|
for row, (label, key) in enumerate(fields):
|
|
ttk.Label(frame, text=label).grid(row=row, column=0, sticky="w", pady=5, padx=(0, 12))
|
|
if key == "birth_date":
|
|
birth_row = ttk.Frame(frame)
|
|
birth_row.grid(row=row, column=1, sticky="ew", pady=5)
|
|
birth_row.columnconfigure(0, weight=1)
|
|
entry = ttk.Entry(birth_row, textvariable=self.variables[key], width=24)
|
|
entry.grid(row=0, column=0, sticky="ew")
|
|
self.birth_age_var = tk.StringVar(value="Alter: —")
|
|
ttk.Label(birth_row, textvariable=self.birth_age_var, style="Mono.TLabel").grid(
|
|
row=0, column=1, sticky="w", padx=(10, 0)
|
|
)
|
|
self.variables[key].trace_add(
|
|
"write",
|
|
lambda *_args: self.birth_age_var.set(age_label(self.variables["birth_date"].get())),
|
|
)
|
|
else:
|
|
entry = ttk.Entry(frame, textvariable=self.variables[key], width=38)
|
|
entry.grid(row=row, column=1, sticky="ew", pady=5)
|
|
self.entries[key] = entry
|
|
button_row = len(fields)
|
|
if self.number_policy["mode"] == "automatic":
|
|
preview = self.repository.preview_member_number(self.number_policy["pattern"])
|
|
ttk.Label(frame, text="Mitgliedsnummer").grid(
|
|
row=button_row, column=0, sticky="w", pady=5, padx=(0, 12)
|
|
)
|
|
ttk.Label(frame, text=f"Automatisch: {preview}", style="TimelineHeader.TLabel").grid(
|
|
row=button_row, column=1, sticky="w", pady=5
|
|
)
|
|
button_row += 1
|
|
buttons = ttk.Frame(frame)
|
|
buttons.grid(row=button_row, column=0, columnspan=2, sticky="e", pady=(16, 0))
|
|
ttk.Button(buttons, text="Abbrechen", command=self.destroy).pack(side="left", padx=(0, 8))
|
|
ttk.Button(buttons, text="Akte anlegen", style="Accent.TButton", command=self._create).pack(
|
|
side="left"
|
|
)
|
|
|
|
def _focus_first(self) -> None:
|
|
self.entries["first_name"].focus_set()
|
|
|
|
def _create(self) -> None:
|
|
try:
|
|
member = self.repository.create_member(
|
|
**{key: variable.get() for key, variable in self.variables.items()}
|
|
)
|
|
except RepositoryError as exc:
|
|
messagebox.showerror("Akte konnte nicht angelegt werden", str(exc), parent=self)
|
|
return
|
|
self.destroy()
|
|
self.on_created(member)
|
|
|
|
|
|
class NewAssetDialog(tk.Toplevel):
|
|
def __init__(self, master: tk.Misc, repository: MemberRepository, on_created: Callable[[Asset], None]):
|
|
super().__init__(master)
|
|
self.repository = repository
|
|
self.on_created = on_created
|
|
self.title("Neues Asset")
|
|
self.transient(master.winfo_toplevel())
|
|
self.resizable(False, False)
|
|
self.variables = {
|
|
name: tk.StringVar()
|
|
for name in ("label", "category", "inventory_number", "serial_number", "deposit_amount_default")
|
|
}
|
|
self._build_ui()
|
|
self.bind("<Escape>", lambda _event: self.destroy())
|
|
self.bind("<Return>", lambda _event: self._create())
|
|
self.after_idle(self._activate_modal)
|
|
|
|
def _build_ui(self) -> None:
|
|
frame = ttk.Frame(self, padding=18)
|
|
frame.pack(fill="both", expand=True)
|
|
fields = [
|
|
("Bezeichnung *", "label"),
|
|
("Kategorie", "category"),
|
|
("Inventarnummer", "inventory_number"),
|
|
("Seriennummer", "serial_number"),
|
|
("Kaution (EUR)", "deposit_amount_default"),
|
|
]
|
|
self.entries: dict[str, ttk.Entry] = {}
|
|
for row, (label, key) in enumerate(fields):
|
|
ttk.Label(frame, text=label).grid(row=row, column=0, sticky="w", pady=5, padx=(0, 12))
|
|
entry = ttk.Entry(frame, textvariable=self.variables[key], width=38)
|
|
entry.grid(row=row, column=1, sticky="ew", pady=5)
|
|
self.entries[key] = entry
|
|
ttk.Label(frame, text="Interne Notiz").grid(row=len(fields), column=0, sticky="nw", pady=5, padx=(0, 12))
|
|
self.notes_text = tk.Text(frame, width=38, height=5, wrap="word")
|
|
self.notes_text.grid(row=len(fields), column=1, sticky="ew", pady=5)
|
|
buttons = ttk.Frame(frame)
|
|
buttons.grid(row=len(fields) + 1, column=0, columnspan=2, sticky="e", pady=(16, 0))
|
|
ttk.Button(buttons, text="Abbrechen", command=self.destroy).pack(side="left", padx=(0, 8))
|
|
ttk.Button(buttons, text="Asset anlegen", style="Accent.TButton", command=self._create).pack(side="left")
|
|
self.after_idle(lambda: self.entries["label"].focus_set())
|
|
|
|
def _activate_modal(self) -> None:
|
|
_activate_modal_window(self, self.entries.get("label"))
|
|
|
|
def _create(self) -> None:
|
|
try:
|
|
asset = self.repository.create_asset(
|
|
**{key: variable.get() for key, variable in self.variables.items()},
|
|
notes=self.notes_text.get("1.0", "end-1c"),
|
|
)
|
|
except RepositoryError as exc:
|
|
messagebox.showerror("Asset konnte nicht angelegt werden", str(exc), parent=self)
|
|
return
|
|
self.destroy()
|
|
self.on_created(asset)
|
|
|
|
|
|
class EditAssetDialog(tk.Toplevel):
|
|
def __init__(self, master: tk.Misc, repository: MemberRepository, asset_id: str, on_saved: Callable[[Asset], None]):
|
|
super().__init__(master)
|
|
self.repository = repository
|
|
self.asset_id = asset_id
|
|
self.on_saved = on_saved
|
|
self.asset = repository.get_asset(asset_id)
|
|
self.title("Asset bearbeiten")
|
|
self.transient(master.winfo_toplevel())
|
|
self.resizable(False, False)
|
|
self.variables = {
|
|
"label": tk.StringVar(value=self.asset.label),
|
|
"category": tk.StringVar(value=self.asset.category),
|
|
"inventory_number": tk.StringVar(value=self.asset.inventory_number),
|
|
"serial_number": tk.StringVar(value=self.asset.serial_number),
|
|
"deposit_amount_default": tk.StringVar(value=self.asset.deposit_amount_default),
|
|
"status": tk.StringVar(value=self.asset.status),
|
|
}
|
|
self._build_ui()
|
|
self.bind("<Escape>", lambda _event: self.destroy())
|
|
self.bind("<Return>", lambda _event: self._save())
|
|
self.after_idle(self._activate_modal)
|
|
|
|
def _build_ui(self) -> None:
|
|
frame = ttk.Frame(self, padding=18)
|
|
frame.pack(fill="both", expand=True)
|
|
issued = bool(self.asset.current_holder_member_id)
|
|
fields = [
|
|
("Bezeichnung *", "label"),
|
|
("Kategorie", "category"),
|
|
("Inventarnummer", "inventory_number"),
|
|
("Seriennummer", "serial_number"),
|
|
("Kaution (EUR)", "deposit_amount_default"),
|
|
]
|
|
self.entries: dict[str, ttk.Entry] = {}
|
|
for row, (label, key) in enumerate(fields):
|
|
ttk.Label(frame, text=label).grid(row=row, column=0, sticky="w", pady=5, padx=(0, 12))
|
|
state = "readonly" if key == "deposit_amount_default" and issued else "normal"
|
|
entry = ttk.Entry(frame, textvariable=self.variables[key], width=38, state=state)
|
|
entry.grid(row=row, column=1, sticky="ew", pady=5)
|
|
self.entries[key] = entry
|
|
ttk.Label(frame, text="Status").grid(row=len(fields), column=0, sticky="w", pady=5, padx=(0, 12))
|
|
status_values = [value for key, value in (
|
|
("available", "VERFUEGBAR"),
|
|
("lost", "VERLOREN"),
|
|
("retired", "AUSGEMUSTERT"),
|
|
)]
|
|
self.status_map = {
|
|
"VERFUEGBAR": "available",
|
|
"VERLOREN": "lost",
|
|
"AUSGEMUSTERT": "retired",
|
|
}
|
|
self.status_var = tk.StringVar(
|
|
value={
|
|
"available": "VERFUEGBAR",
|
|
"lost": "VERLOREN",
|
|
"retired": "AUSGEMUSTERT",
|
|
}.get(self.asset.status, "VERFUEGBAR")
|
|
)
|
|
self.status_box = ttk.Combobox(
|
|
frame,
|
|
textvariable=self.status_var,
|
|
values=status_values,
|
|
state="readonly" if not issued else "disabled",
|
|
width=35,
|
|
)
|
|
self.status_box.grid(row=len(fields), column=1, sticky="ew", pady=5)
|
|
note_row = len(fields) + 1
|
|
ttk.Label(frame, text="Interne Notiz").grid(row=note_row, column=0, sticky="nw", pady=5, padx=(0, 12))
|
|
self.notes_text = tk.Text(frame, width=38, height=5, wrap="word")
|
|
self.notes_text.grid(row=note_row, column=1, sticky="ew", pady=5)
|
|
self.notes_text.insert("1.0", self.asset.notes)
|
|
info_row = note_row + 1
|
|
info_text = (
|
|
"Kaution kann nur geändert werden, wenn das Asset nicht ausgegeben ist."
|
|
if issued
|
|
else "Status kann hier auf verfuegbar, verloren oder ausgemustert gesetzt werden."
|
|
)
|
|
ttk.Label(frame, text=info_text, style="Mono.TLabel").grid(
|
|
row=info_row, column=0, columnspan=2, sticky="w", pady=(4, 0)
|
|
)
|
|
buttons = ttk.Frame(frame)
|
|
buttons.grid(row=info_row + 1, column=0, columnspan=2, sticky="e", pady=(16, 0))
|
|
ttk.Button(buttons, text="Abbrechen", command=self.destroy).pack(side="left", padx=(0, 8))
|
|
ttk.Button(buttons, text="Speichern", style="Accent.TButton", command=self._save).pack(side="left")
|
|
self.after_idle(lambda: self.entries["label"].focus_set())
|
|
|
|
def _activate_modal(self) -> None:
|
|
_activate_modal_window(self, self.entries.get("label"))
|
|
|
|
def _save(self) -> None:
|
|
self.asset.label = self.variables["label"].get()
|
|
self.asset.category = self.variables["category"].get()
|
|
self.asset.inventory_number = self.variables["inventory_number"].get()
|
|
self.asset.serial_number = self.variables["serial_number"].get()
|
|
self.asset.notes = self.notes_text.get("1.0", "end-1c")
|
|
if not self.asset.current_holder_member_id:
|
|
self.asset.deposit_amount_default = self.variables["deposit_amount_default"].get()
|
|
self.asset.status = self.status_map.get(self.status_var.get(), self.asset.status)
|
|
try:
|
|
self.repository.save_asset(self.asset)
|
|
except RepositoryError as exc:
|
|
messagebox.showerror("Asset konnte nicht gespeichert werden", str(exc), parent=self)
|
|
return
|
|
self.destroy()
|
|
self.on_saved(self.asset)
|
|
|
|
|
|
class IssueAssetDialog(tk.Toplevel):
|
|
def __init__(
|
|
self,
|
|
master: tk.Misc,
|
|
repository: MemberRepository,
|
|
asset_id: str,
|
|
on_assigned: Callable[[Asset], None],
|
|
*,
|
|
preselected_member_id: str = "",
|
|
):
|
|
super().__init__(master)
|
|
self.repository = repository
|
|
self.asset_id = asset_id
|
|
self.on_assigned = on_assigned
|
|
self.preselected_member_id = preselected_member_id
|
|
self.title("Asset ausgeben")
|
|
self.transient(master.winfo_toplevel())
|
|
self.resizable(True, True)
|
|
self.search_var = tk.StringVar()
|
|
self.members = self.repository.list_members()
|
|
self._build_ui()
|
|
self.bind("<Escape>", lambda _event: self.destroy())
|
|
self.bind("<Return>", lambda _event: self._assign())
|
|
self.after_idle(self._activate_modal)
|
|
|
|
def _build_ui(self) -> None:
|
|
frame = ttk.Frame(self, padding=18)
|
|
frame.pack(fill="both", expand=True)
|
|
frame.columnconfigure(1, weight=1)
|
|
frame.rowconfigure(3, weight=1)
|
|
asset = self.repository.get_asset(self.asset_id)
|
|
ttk.Label(frame, text="Asset").grid(row=0, column=0, sticky="w", pady=5, padx=(0, 12))
|
|
ttk.Label(frame, text=asset.label, style="TimelineHeader.TLabel").grid(row=0, column=1, sticky="w", pady=5)
|
|
ttk.Label(frame, text="Suche").grid(row=1, column=0, sticky="w", pady=5, padx=(0, 12))
|
|
self.search_entry = ttk.Entry(frame, textvariable=self.search_var, width=42)
|
|
self.search_entry.grid(row=1, column=1, sticky="ew", pady=5)
|
|
self.search_var.trace_add("write", lambda *_args: self._render_members())
|
|
ttk.Label(frame, text="Mitglied").grid(row=2, column=0, sticky="nw", pady=5, padx=(0, 12))
|
|
self.member_tree = ttk.Treeview(
|
|
frame,
|
|
columns=("number", "name", "email"),
|
|
show="headings",
|
|
height=10,
|
|
)
|
|
for key, title, width in (
|
|
("number", "Nummer", 120),
|
|
("name", "Mitglied", 220),
|
|
("email", "E-Mail", 220),
|
|
):
|
|
self.member_tree.heading(key, text=title)
|
|
self.member_tree.column(key, width=width, anchor="w")
|
|
self.member_tree.grid(row=3, column=0, columnspan=2, sticky="nsew", pady=5)
|
|
self.member_tree.bind("<Double-1>", lambda _event: self._assign())
|
|
self.member_tree.bind("<Return>", lambda _event: self._assign())
|
|
self.member_tree.bind("<<TreeviewSelect>>", lambda _event: self._sync_selected_member_label())
|
|
self.selected_member_var = tk.StringVar(value="Kein Mitglied ausgewählt.")
|
|
ttk.Label(frame, textvariable=self.selected_member_var, style="Mono.TLabel").grid(
|
|
row=4, column=0, columnspan=2, sticky="w", pady=(4, 0)
|
|
)
|
|
buttons = ttk.Frame(frame)
|
|
buttons.grid(row=5, column=0, columnspan=2, sticky="e", pady=(16, 0))
|
|
ttk.Button(buttons, text="Abbrechen", command=self.destroy).pack(side="left", padx=(0, 8))
|
|
ttk.Button(buttons, text="Ausgeben", style="Accent.TButton", command=self._assign).pack(side="left")
|
|
self._render_members()
|
|
self.after_idle(self.search_entry.focus_set)
|
|
|
|
def _activate_modal(self) -> None:
|
|
_activate_modal_window(self, self.search_entry)
|
|
|
|
def _assign(self) -> None:
|
|
selected = self.member_tree.selection()
|
|
member_id = selected[0] if selected else ""
|
|
if not member_id:
|
|
messagebox.showerror("Ausgabe fehlgeschlagen", "Bitte ein Mitglied auswählen.", parent=self)
|
|
return
|
|
try:
|
|
asset = self.repository.assign_asset(self.asset_id, member_id)
|
|
except RepositoryError as exc:
|
|
messagebox.showerror("Ausgabe fehlgeschlagen", str(exc), parent=self)
|
|
return
|
|
self.destroy()
|
|
self.on_assigned(asset)
|
|
|
|
def _render_members(self) -> None:
|
|
self.member_tree.delete(*self.member_tree.get_children())
|
|
query = self.search_var.get().strip().casefold()
|
|
filtered = [
|
|
member
|
|
for member in self.members
|
|
if not query or query in self._member_search_text(member)
|
|
]
|
|
for member in filtered:
|
|
self.member_tree.insert(
|
|
"",
|
|
"end",
|
|
iid=member.member_id,
|
|
values=(member.member_number, member.display_name, member.email),
|
|
)
|
|
selected_id = self.preselected_member_id if self.preselected_member_id else ""
|
|
if filtered:
|
|
target_id = selected_id if selected_id and any(member.member_id == selected_id for member in filtered) else filtered[0].member_id
|
|
self.member_tree.selection_set(target_id)
|
|
self.member_tree.focus(target_id)
|
|
self.member_tree.see(target_id)
|
|
self._sync_selected_member_label()
|
|
|
|
def _sync_selected_member_label(self) -> None:
|
|
selected = self.member_tree.selection()
|
|
if not selected:
|
|
self.selected_member_var.set("Kein Mitglied ausgewählt.")
|
|
return
|
|
member = next((item for item in self.members if item.member_id == selected[0]), None)
|
|
if member is None:
|
|
self.selected_member_var.set("Kein Mitglied ausgewählt.")
|
|
return
|
|
self.selected_member_var.set(f"Ausgewählt: {self._member_label(member)}")
|
|
|
|
@staticmethod
|
|
def _member_search_text(member: Member) -> str:
|
|
return " ".join(
|
|
value.casefold()
|
|
for value in (
|
|
member.member_number,
|
|
member.first_name,
|
|
member.last_name,
|
|
member.nickname,
|
|
member.display_name,
|
|
member.email,
|
|
)
|
|
if value
|
|
)
|
|
|
|
@staticmethod
|
|
def _member_label(member: Member) -> str:
|
|
prefix = member.member_number or member.member_id
|
|
name = member.display_name or member.member_id
|
|
return f"{prefix} · {name}"
|
|
|
|
|
|
class AssetClaimDialog(tk.Toplevel):
|
|
def __init__(
|
|
self,
|
|
master: tk.Misc,
|
|
repository: MemberRepository,
|
|
asset_id: str,
|
|
member_id: str,
|
|
*,
|
|
preset_title: str,
|
|
preset_amount: str,
|
|
preset_description: str,
|
|
claim_type: str,
|
|
on_created: Callable[[str], None],
|
|
):
|
|
super().__init__(master)
|
|
self.repository = repository
|
|
self.asset_id = asset_id
|
|
self.member_id = member_id
|
|
self.claim_type = claim_type
|
|
self.on_created = on_created
|
|
self.title("Forderung aus Asset anlegen")
|
|
self.transient(master.winfo_toplevel())
|
|
self.resizable(False, False)
|
|
self.variables = {
|
|
"title": tk.StringVar(value=preset_title),
|
|
"amount": tk.StringVar(value=preset_amount),
|
|
"due_date": tk.StringVar(value=date.today().isoformat()),
|
|
}
|
|
self.preset_description = preset_description
|
|
self._build_ui()
|
|
self.bind("<Escape>", lambda _event: self.destroy())
|
|
self.bind("<Return>", lambda _event: self._create())
|
|
self.after_idle(self._activate_modal)
|
|
|
|
def _build_ui(self) -> None:
|
|
frame = ttk.Frame(self, padding=18)
|
|
frame.pack(fill="both", expand=True)
|
|
asset = self.repository.get_asset(self.asset_id)
|
|
member = self.repository.get_member(self.member_id)
|
|
ttk.Label(frame, text="Asset").grid(row=0, column=0, sticky="w", pady=5, padx=(0, 12))
|
|
ttk.Label(frame, text=asset.label, style="TimelineHeader.TLabel").grid(row=0, column=1, sticky="w", pady=5)
|
|
ttk.Label(frame, text="Mitglied").grid(row=1, column=0, sticky="w", pady=5, padx=(0, 12))
|
|
ttk.Label(frame, text=f"{member.member_number or member.member_id} · {member.display_name}").grid(
|
|
row=1, column=1, sticky="w", pady=5
|
|
)
|
|
row_offset = 2
|
|
for index, (label, key) in enumerate(
|
|
(
|
|
("Titel *", "title"),
|
|
("Betrag (EUR) *", "amount"),
|
|
(f"Fällig am ({date_input_hint()}) *", "due_date"),
|
|
)
|
|
):
|
|
ttk.Label(frame, text=label).grid(row=row_offset + index, column=0, sticky="w", pady=5, padx=(0, 12))
|
|
ttk.Entry(frame, textvariable=self.variables[key], width=42).grid(
|
|
row=row_offset + index, column=1, sticky="ew", pady=5
|
|
)
|
|
ttk.Label(frame, text="Beschreibung").grid(row=row_offset + 3, column=0, sticky="nw", pady=5, padx=(0, 12))
|
|
self.description_text = tk.Text(frame, width=42, height=5, wrap="word")
|
|
self.description_text.grid(row=row_offset + 3, column=1, sticky="ew", pady=5)
|
|
self.description_text.insert("1.0", self.preset_description)
|
|
info = (
|
|
"Negative Betraege werden als Gutschrift dokumentiert. "
|
|
"Die Auszahlung selbst wird im aktuellen CCMA-Stand nicht als eigener Workflow modelliert."
|
|
)
|
|
ttk.Label(frame, text=info, style="Mono.TLabel").grid(
|
|
row=row_offset + 4, column=0, columnspan=2, sticky="w", pady=(4, 0)
|
|
)
|
|
buttons = ttk.Frame(frame)
|
|
buttons.grid(row=row_offset + 5, column=0, columnspan=2, sticky="e", pady=(16, 0))
|
|
ttk.Button(buttons, text="Abbrechen", command=self.destroy).pack(side="left", padx=(0, 8))
|
|
ttk.Button(buttons, text="Forderung anlegen", style="Accent.TButton", command=self._create).pack(side="left")
|
|
self.after_idle(lambda: frame.focus_set())
|
|
|
|
def _activate_modal(self) -> None:
|
|
_activate_modal_window(self)
|
|
|
|
def _create(self) -> None:
|
|
try:
|
|
result = self.repository.create_manual_claim(
|
|
self.member_id,
|
|
title=self.variables["title"].get(),
|
|
amount=self.variables["amount"].get(),
|
|
due_date=self.variables["due_date"].get(),
|
|
description=self.description_text.get("1.0", "end-1c"),
|
|
claim_type=self.claim_type,
|
|
references={"asset_id": self.asset_id},
|
|
)
|
|
except RepositoryError as exc:
|
|
messagebox.showerror("Forderung konnte nicht angelegt werden", str(exc), parent=self)
|
|
return
|
|
self.destroy()
|
|
self.on_created(str(result["claim"]["claim_id"]))
|
|
|
|
|
|
class IntegrityWarningDialog(tk.Toplevel):
|
|
def __init__(
|
|
self,
|
|
master: tk.Misc,
|
|
*,
|
|
title: str,
|
|
warnings: list[str],
|
|
on_confirm: Callable[[], None],
|
|
):
|
|
super().__init__(master)
|
|
self.on_confirm = on_confirm
|
|
self.title(title)
|
|
self.transient(master.winfo_toplevel())
|
|
self.resizable(False, False)
|
|
self.warnings = warnings
|
|
self._build_ui()
|
|
self.bind("<Escape>", lambda _event: self.destroy())
|
|
self.after_idle(self._activate_modal)
|
|
|
|
def _build_ui(self) -> None:
|
|
frame = ttk.Frame(self, padding=18)
|
|
frame.pack(fill="both", expand=True)
|
|
message = (
|
|
"ACHTUNG: Die zugehörigen JSON-Dateien wurden vermutlich extern geändert.\n\n"
|
|
"Haben Sie alle Daten geprüft und soll der Hash jetzt aktualisiert werden?"
|
|
)
|
|
ttk.Label(frame, text=message, justify="left").grid(row=0, column=0, sticky="w")
|
|
ttk.Label(frame, text="\n".join(f"• {item}" for item in self.warnings), style="Warning.TLabel", justify="left").grid(
|
|
row=1, column=0, sticky="w", pady=(12, 0)
|
|
)
|
|
buttons = ttk.Frame(frame)
|
|
buttons.grid(row=2, column=0, sticky="e", pady=(18, 0))
|
|
ttk.Button(buttons, text="Nein", command=self.destroy).pack(side="left", padx=(0, 8))
|
|
ttk.Button(buttons, text="Ja, bestätigen", style="Accent.TButton", command=self._confirm).pack(side="left")
|
|
|
|
def _activate_modal(self) -> None:
|
|
_activate_modal_window(self)
|
|
|
|
def _confirm(self) -> None:
|
|
self.destroy()
|
|
self.on_confirm()
|