feat: add OpenDocument PDF templates

This commit is contained in:
Marcel Peterkau
2026-06-21 22:10:16 +02:00
parent b34135b34a
commit 0622a22794
14 changed files with 942 additions and 14 deletions
+92 -14
View File
@@ -1,10 +1,9 @@
from __future__ import annotations
import subprocess
import sys
import tkinter as tk
from collections.abc import Callable
from datetime import datetime
from pathlib import Path
from tkinter import messagebox, ttk
from ccma.domain.contributions import CLAIM_STATUS_LABELS, claim_status, claim_total, money_text
@@ -12,6 +11,8 @@ from ccma.domain.dates import age_label, date_input_hint, format_date_for_displa
from ccma.domain.models import MEMBERSHIP_STATUS_LABELS as STATUS_LABELS
from ccma.domain.models import Event
from ccma.storage.repository import MemberRepository, RepositoryError
from ccma.ui.document_dialog import DocumentTemplateDialog
from ccma.ui.file_open import open_path
from ccma.ui.labels import display_label, storage_key
@@ -161,11 +162,41 @@ class MemberTab(ttk.Frame):
documents_tab.columnconfigure(0, weight=1)
documents_tab.rowconfigure(1, weight=1)
ttk.Button(documents_tab, text="Dateiordner öffnen", command=self._open_files).grid(
row=0, column=0, sticky="w", pady=(0, 10)
document_buttons = ttk.Frame(documents_tab)
document_buttons.grid(row=0, column=0, sticky="ew", pady=(0, 10))
ttk.Button(
document_buttons,
text="Dokument aus Template",
style="Accent.TButton",
command=self._create_document,
).pack(side="left", padx=(0, 8))
ttk.Button(document_buttons, text="Dateiordner öffnen", command=self._open_files).pack(
side="left"
)
self.documents = tk.Listbox(documents_tab, borderwidth=0, highlightthickness=0)
self.documents = ttk.Treeview(
documents_tab,
columns=("name", "type", "modified", "size"),
show="headings",
)
for key, title, width in (
("name", "Dokument", 280),
("type", "Typ", 65),
("modified", "Geändert", 135),
("size", "Größe", 80),
):
self.documents.heading(key, text=title)
self.documents.column(key, width=width, anchor="w")
self.documents.grid(row=1, column=0, sticky="nsew")
document_scroll = ttk.Scrollbar(
documents_tab,
orient="vertical",
command=self.documents.yview,
)
document_scroll.grid(row=1, column=1, sticky="ns")
self.documents.configure(yscrollcommand=document_scroll.set)
self.documents.bind("<Double-1>", lambda _event: self._open_selected_document())
self.documents.bind("<Return>", lambda _event: self._open_selected_document())
self.document_paths: dict[str, Path] = {}
def _build_timeline(self, parent: ttk.Frame) -> None:
parent.columnconfigure(0, weight=1)
@@ -244,11 +275,31 @@ class MemberTab(ttk.Frame):
self.on_open_claim(self.member_id, selected[0])
def _refresh_documents(self) -> None:
self.documents.delete(0, "end")
self.documents.delete(*self.documents.get_children())
self.document_paths.clear()
root = self.repository.members_root / self.member_id / "files"
for path in sorted(root.rglob("*")):
for index, path in enumerate(sorted(root.rglob("*"))):
if path.is_file():
self.documents.insert("end", str(path.relative_to(root)))
item_id = f"document:{index}"
self.document_paths[item_id] = path
try:
stat = path.stat()
modified = datetime.fromtimestamp(stat.st_mtime).strftime("%d.%m.%Y %H:%M")
size = _file_size(stat.st_size)
except OSError:
modified = ""
size = ""
self.documents.insert(
"",
"end",
iid=item_id,
values=(
path.relative_to(root),
path.suffix.removeprefix(".").upper() or "DATEI",
modified,
size,
),
)
def _save(self) -> None:
for key, variable in self.variables.items():
@@ -280,12 +331,31 @@ class MemberTab(ttk.Frame):
def _open_files(self) -> None:
path = self.repository.members_root / self.member_id / "files"
if sys.platform == "win32":
subprocess.Popen(["explorer", str(path)])
elif sys.platform == "darwin":
subprocess.Popen(["open", str(path)])
else:
subprocess.Popen(["xdg-open", str(path)])
self._open_path(path)
def _open_selected_document(self) -> None:
selected = self.documents.selection()
if selected and selected[0] in self.document_paths:
self._open_path(self.document_paths[selected[0]])
def _open_path(self, path: Path) -> None:
try:
open_path(path)
except OSError as exc:
messagebox.showerror("Datei konnte nicht geöffnet werden", str(exc), parent=self)
def _create_document(self) -> None:
DocumentTemplateDialog(
self,
self.repository,
self.member_id,
self._document_generated,
)
def _document_generated(self, _path: Path) -> None:
self._refresh_documents()
self._refresh_events()
self.on_changed()
def _format_timestamp(event: Event) -> str:
@@ -299,3 +369,11 @@ def _event_label(event: Event) -> str:
if event.actor_type == "system":
return f"[AUTO] {event.summary}"
return event.summary
def _file_size(size: int) -> str:
if size < 1024:
return f"{size} B"
if size < 1024 * 1024:
return f"{size / 1024:.1f} KiB"
return f"{size / (1024 * 1024):.1f} MiB"