Multiple changes
- Excel from network folder - multiple input Excel via dowpdown, - free creation date, - Output folder path tooltip
This commit is contained in:
1 parent
6e9d865a9d
commit
9d0390f8fd
4 files changed
+273
-47
No files matched your search
+2
-1
@@ -31,5 +31,6 @@
|
||||
"pdf_pauschale_prefix": "pauschale_5",
|
||||
"pauschale_present_value": "1",
|
||||
"pdf_use_display_values": true,
|
||||
"pdf_need_appearances": false
|
||||
"pdf_need_appearances": false,
|
||||
"excel_network_source": "J:\\Fachbereiche\\BO\\Produktionsschule\\Maßnahmeunterlagen\\30_Teilnehmende\\01_Anwesenheit\\Anwesenheitsliste\\Anwesenheitsliste 2026.xlsx"
|
||||
}
|
||||
@@ -3,13 +3,15 @@
|
||||
# Build-Artefakte: build/ (temporaer, nicht committen)
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import threading
|
||||
from datetime import date
|
||||
from datetime import date, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import customtkinter as ctk
|
||||
import tkinter as tk
|
||||
from tkinter import messagebox, filedialog
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
@@ -42,13 +44,42 @@ def _runtime_dir() -> Path:
|
||||
return Path(__file__).parent
|
||||
|
||||
|
||||
def _scan_input_folder() -> Optional[Path]:
|
||||
"""Return the single .xlsx in input/, or None if 0 or >1 found."""
|
||||
def _input_dir() -> Path:
|
||||
"""Return the input directory and ensure it exists."""
|
||||
folder = _runtime_dir() / "input"
|
||||
if not folder.exists():
|
||||
return None
|
||||
files = list(folder.glob("*.xlsx"))
|
||||
return files[0] if len(files) == 1 else None
|
||||
folder.mkdir(parents=True, exist_ok=True)
|
||||
return folder
|
||||
|
||||
|
||||
def _list_input_files() -> List[Path]:
|
||||
"""Return all Excel files from input/ sorted by name."""
|
||||
return sorted(_input_dir().glob("*.xlsx"), key=lambda path: path.name.lower())
|
||||
|
||||
|
||||
def _copy_excel_to_input(source: Path) -> Path:
|
||||
"""Copy a selected Excel file into input/ and return the local file path."""
|
||||
target = _input_dir() / source.name
|
||||
if source.resolve() != target.resolve():
|
||||
shutil.copy2(source, target)
|
||||
return target
|
||||
|
||||
|
||||
def _sync_network_excel(config: Dict[str, Any]) -> tuple[Optional[Path], str]:
|
||||
"""Copy a newer Excel file from the configured network path into input/."""
|
||||
source_value = str(config.get("excel_network_source", "")).strip()
|
||||
if not source_value:
|
||||
return None, "Kein Netzlaufwerk-Pfad konfiguriert."
|
||||
|
||||
source = Path(source_value)
|
||||
if not source.exists():
|
||||
return None, f"Netzlaufwerk-Datei nicht gefunden: {source.name}"
|
||||
|
||||
target = _input_dir() / source.name
|
||||
if not target.exists() or source.stat().st_mtime > target.stat().st_mtime:
|
||||
shutil.copy2(source, target)
|
||||
return target, f"Excel-Datei aktualisiert: {source.name}"
|
||||
|
||||
return target, f"Lokale Excel-Datei ist aktuell: {source.name}"
|
||||
|
||||
|
||||
def _scan_template_folder() -> Optional[Path]:
|
||||
@@ -79,6 +110,26 @@ def _build_month_keys(config: Dict[str, Any]) -> List[str]:
|
||||
return keys
|
||||
|
||||
|
||||
def _truncate_middle(text: str, max_length: int = 48) -> str:
|
||||
"""Shorten long text by replacing the middle with '...' ."""
|
||||
if len(text) <= max_length:
|
||||
return text
|
||||
keep = max_length - 3
|
||||
left = keep // 2
|
||||
right = keep - left
|
||||
return f"{text[:left]}...{text[-right:]}"
|
||||
|
||||
|
||||
def _format_creation_date(value: date) -> str:
|
||||
"""Format a creation date for the GUI and PDF preview."""
|
||||
return value.strftime("%d.%m.%Y")
|
||||
|
||||
|
||||
def _parse_creation_date(value: str) -> date:
|
||||
"""Parse a German-style date from the GUI entry."""
|
||||
return datetime.strptime(value.strip(), "%d.%m.%Y").date()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Startup: load config once
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -117,6 +168,10 @@ class AnwesenheitApp(ctk.CTk):
|
||||
self._excel_path: Optional[Path] = None
|
||||
self._template_path: Optional[Path] = None
|
||||
self._out_dir: Optional[Path] = None
|
||||
self._input_files: List[Path] = []
|
||||
self._excel_name_to_path: Dict[str, Path] = {}
|
||||
self._tooltip_window: Optional[ctk.CTkToplevel] = None
|
||||
self._tooltip_label: Optional[tk.Label] = None
|
||||
|
||||
# Build UI sections
|
||||
self._build_input_section()
|
||||
@@ -124,8 +179,8 @@ class AnwesenheitApp(ctk.CTk):
|
||||
self._build_preview_section()
|
||||
self._build_log_section()
|
||||
|
||||
# Auto-scan after event loop starts
|
||||
self.after(100, self._scan_and_validate)
|
||||
# Auto-sync after event loop starts
|
||||
self.after(100, self._start_initial_sync)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# UI builders
|
||||
@@ -146,8 +201,16 @@ class AnwesenheitApp(ctk.CTk):
|
||||
frame, text="\U0001f4c2", width=BTN_W, height=28,
|
||||
command=self._browse_excel,
|
||||
).grid(row=0, column=1, padx=(0, 6), pady=6)
|
||||
self._lbl_excel = ctk.CTkLabel(frame, text="Suche\u2026", anchor="w")
|
||||
self._lbl_excel.grid(row=0, column=2, padx=(0, 12), pady=6, sticky="ew")
|
||||
self._excel_var = ctk.StringVar(value="")
|
||||
self._excel_menu = ctk.CTkOptionMenu(
|
||||
frame,
|
||||
values=["Keine Excel-Datei gefunden"],
|
||||
variable=self._excel_var,
|
||||
command=lambda _: self._on_excel_changed(),
|
||||
width=320,
|
||||
state="disabled",
|
||||
)
|
||||
self._excel_menu.grid(row=0, column=2, padx=(0, 12), pady=6, sticky="w")
|
||||
|
||||
# Row 1: Template
|
||||
ctk.CTkLabel(frame, text="Template:", width=110, anchor="w").grid(
|
||||
@@ -174,22 +237,46 @@ class AnwesenheitApp(ctk.CTk):
|
||||
)
|
||||
self._month_menu.grid(row=2, column=2, padx=(0, 12), pady=6, sticky="w")
|
||||
|
||||
# Row 3: Output dir
|
||||
# Row 3: Creation date
|
||||
ctk.CTkLabel(frame, text="Erstellungsdatum:", width=110, anchor="w").grid(
|
||||
row=3, column=0, padx=(12, 4), pady=6, sticky="w"
|
||||
)
|
||||
self._creation_date_var = ctk.StringVar(value=_format_creation_date(date.today()))
|
||||
self._entry_creation_date = ctk.CTkEntry(
|
||||
frame,
|
||||
textvariable=self._creation_date_var,
|
||||
width=150,
|
||||
)
|
||||
self._entry_creation_date.grid(row=3, column=2, padx=(0, 12), pady=6, sticky="w")
|
||||
self._entry_creation_date.bind("<FocusOut>", lambda _: self._on_creation_date_changed())
|
||||
self._entry_creation_date.bind("<Return>", lambda _: self._on_creation_date_changed())
|
||||
|
||||
# Row 4: Output dir
|
||||
out_label = str((_runtime_dir() / _CONFIG.get("output_dir", "output")).resolve())
|
||||
ctk.CTkLabel(frame, text="Ausgabe:", width=110, anchor="w").grid(
|
||||
row=3, column=0, padx=(12, 4), pady=6, sticky="w"
|
||||
row=4, column=0, padx=(12, 4), pady=6, sticky="w"
|
||||
)
|
||||
ctk.CTkButton(
|
||||
frame, text="\U0001f4c2", width=BTN_W, height=28,
|
||||
command=self._open_output_folder,
|
||||
).grid(row=3, column=1, padx=(0, 6), pady=6)
|
||||
ctk.CTkLabel(frame, text=out_label, anchor="w").grid(
|
||||
row=3, column=2, padx=(0, 12), pady=6, sticky="ew"
|
||||
).grid(row=4, column=1, padx=(0, 6), pady=6)
|
||||
self._lbl_output_path = ctk.CTkLabel(
|
||||
frame, text=_truncate_middle(out_label), anchor="w"
|
||||
)
|
||||
self._lbl_output_path.grid(
|
||||
row=4, column=2, padx=(0, 12), pady=6, sticky="ew"
|
||||
)
|
||||
self._lbl_output_path.bind(
|
||||
"<Enter>", lambda event: self._show_tooltip(event, out_label)
|
||||
)
|
||||
self._lbl_output_path.bind("<Leave>", lambda _: self._hide_tooltip())
|
||||
self._lbl_output_path.bind(
|
||||
"<Motion>", lambda event: self._move_tooltip(event, out_label)
|
||||
)
|
||||
|
||||
# Row 4: Output warning + "Ordner leeren" link (hidden when 0)
|
||||
# Row 5: Output warning + "Ordner leeren" link (hidden when 0)
|
||||
warning_row = ctk.CTkFrame(frame, fg_color="transparent")
|
||||
warning_row.grid(row=4, column=2, padx=(0, 12), pady=(0, 6), sticky="ew")
|
||||
warning_row.grid(row=5, column=2, padx=(0, 12), pady=(0, 6), sticky="ew")
|
||||
self._lbl_warning = ctk.CTkLabel(
|
||||
warning_row, text="", anchor="w", text_color="orange"
|
||||
)
|
||||
@@ -201,6 +288,13 @@ class AnwesenheitApp(ctk.CTk):
|
||||
self._lbl_clear_link.pack(side="left", padx=(6, 0))
|
||||
self._lbl_clear_link.bind("<Button-1>", lambda _: self._confirm_clear_output())
|
||||
|
||||
self._lbl_download_status = ctk.CTkLabel(
|
||||
frame, text="", anchor="w", text_color="#60A5FA"
|
||||
)
|
||||
self._lbl_download_status.grid(
|
||||
row=6, column=2, padx=(0, 12), pady=(0, 8), sticky="ew"
|
||||
)
|
||||
|
||||
def _build_button_section(self) -> None:
|
||||
frame = ctk.CTkFrame(self)
|
||||
frame.pack(fill="x", padx=16, pady=(8, 0))
|
||||
@@ -228,7 +322,6 @@ class AnwesenheitApp(ctk.CTk):
|
||||
("Monat:", "_pv_monat"),
|
||||
("Antragsnummer:", "_pv_antrag"),
|
||||
("Ort:", "_pv_ort"),
|
||||
("Erstelldatum:", "_pv_datum"),
|
||||
("Teilnehmer:", "_pv_teilnehmer"),
|
||||
("Fehler:", "_pv_fehler"),
|
||||
]
|
||||
@@ -290,6 +383,51 @@ class AnwesenheitApp(ctk.CTk):
|
||||
self._log_box.delete("1.0", "end")
|
||||
self._log_box.configure(state="disabled")
|
||||
|
||||
def _show_tooltip(self, event, text: str) -> None:
|
||||
if self._tooltip_window is None:
|
||||
self._tooltip_window = tk.Toplevel(self)
|
||||
self._tooltip_window.wm_overrideredirect(True)
|
||||
self._tooltip_window.attributes("-topmost", True)
|
||||
self._tooltip_label = tk.Label(
|
||||
self._tooltip_window,
|
||||
background="#F8FAFC",
|
||||
foreground="#111827",
|
||||
relief="solid",
|
||||
borderwidth=1,
|
||||
padx=10,
|
||||
pady=6,
|
||||
font=("Segoe UI", 9),
|
||||
)
|
||||
self._tooltip_label.pack()
|
||||
|
||||
if self._tooltip_label is not None:
|
||||
self._tooltip_label.configure(text=text)
|
||||
|
||||
self._tooltip_window.geometry(f"+{event.x_root + 14}+{event.y_root + 10}")
|
||||
|
||||
def _move_tooltip(self, event, text: str) -> None:
|
||||
if self._tooltip_window is None:
|
||||
self._show_tooltip(event, text)
|
||||
return
|
||||
self._tooltip_window.geometry(f"+{event.x_root + 14}+{event.y_root + 10}")
|
||||
|
||||
def _hide_tooltip(self) -> None:
|
||||
if self._tooltip_window is not None:
|
||||
self._tooltip_window.destroy()
|
||||
self._tooltip_window = None
|
||||
self._tooltip_label = None
|
||||
|
||||
def _set_excel_loading_state(self, text: str) -> None:
|
||||
self._excel_menu.configure(values=[text], state="disabled")
|
||||
self._excel_var.set(text)
|
||||
|
||||
def _set_download_status(self, text: str, color: str = "#60A5FA") -> None:
|
||||
self._lbl_download_status.configure(text=text, text_color=color)
|
||||
|
||||
def _get_creation_date(self) -> date:
|
||||
"""Return the parsed creation date from the GUI entry."""
|
||||
return _parse_creation_date(self._creation_date_var.get())
|
||||
|
||||
def _on_log_click(self, event) -> None:
|
||||
tb = self._log_box._textbox
|
||||
idx = tb.index(f"@{event.x},{event.y}")
|
||||
@@ -328,7 +466,7 @@ class AnwesenheitApp(ctk.CTk):
|
||||
def _clear_preview(self) -> None:
|
||||
for attr in (
|
||||
"_pv_datei", "_pv_jahr", "_pv_monat", "_pv_antrag",
|
||||
"_pv_ort", "_pv_datum", "_pv_teilnehmer", "_pv_fehler",
|
||||
"_pv_ort", "_pv_teilnehmer", "_pv_fehler",
|
||||
):
|
||||
getattr(self, attr).configure(text="–", text_color=("gray10", "gray90"))
|
||||
|
||||
@@ -377,13 +515,15 @@ class AnwesenheitApp(ctk.CTk):
|
||||
)
|
||||
if not path:
|
||||
return
|
||||
self._excel_path = Path(path)
|
||||
self._lbl_excel.configure(
|
||||
text=self._excel_path.name, text_color=("gray10", "gray90")
|
||||
)
|
||||
self._excel_path = _copy_excel_to_input(Path(path))
|
||||
self._refresh_excel_dropdown(selected=self._excel_path)
|
||||
self._btn_generate.configure(state="disabled")
|
||||
self._clear_preview()
|
||||
self._clear_log()
|
||||
self._set_download_status(
|
||||
f"Excel-Datei nach input kopiert: {self._excel_path.name}",
|
||||
"#22C55E",
|
||||
)
|
||||
if self._template_path and self._month_var.get():
|
||||
threading.Thread(target=self._run_validate, daemon=True).start()
|
||||
|
||||
@@ -404,6 +544,73 @@ class AnwesenheitApp(ctk.CTk):
|
||||
if self._excel_path and self._month_var.get():
|
||||
threading.Thread(target=self._run_validate, daemon=True).start()
|
||||
|
||||
def _refresh_excel_dropdown(self, selected: Optional[Path] = None) -> None:
|
||||
self._input_files = _list_input_files()
|
||||
self._excel_name_to_path = {path.name: path for path in self._input_files}
|
||||
|
||||
if not self._input_files:
|
||||
self._excel_path = None
|
||||
self._excel_menu.configure(values=["Keine Excel-Datei gefunden"], state="disabled")
|
||||
self._excel_var.set("Keine Excel-Datei gefunden")
|
||||
return
|
||||
|
||||
values = [path.name for path in self._input_files]
|
||||
self._excel_menu.configure(values=values, state="normal")
|
||||
|
||||
if selected and selected.name in self._excel_name_to_path:
|
||||
selected_name = selected.name
|
||||
elif self._excel_var.get() in self._excel_name_to_path:
|
||||
selected_name = self._excel_var.get()
|
||||
else:
|
||||
selected_name = values[0]
|
||||
|
||||
self._excel_var.set(selected_name)
|
||||
self._excel_path = self._excel_name_to_path[selected_name]
|
||||
|
||||
def _start_initial_sync(self) -> None:
|
||||
self._set_excel_loading_state("Lade Excel vom Netzlaufwerk ...")
|
||||
self._set_download_status("Datei vom Netzlaufwerk wird geprueft ...")
|
||||
threading.Thread(target=self._run_initial_sync, daemon=True).start()
|
||||
|
||||
def _run_initial_sync(self) -> None:
|
||||
try:
|
||||
synced_path, status = _sync_network_excel(_CONFIG)
|
||||
self.after(0, self._finish_initial_sync, synced_path, status, None)
|
||||
except Exception as exc:
|
||||
self.after(0, self._finish_initial_sync, None, "", str(exc))
|
||||
|
||||
def _finish_initial_sync(
|
||||
self,
|
||||
synced_path: Optional[Path],
|
||||
status: str,
|
||||
error: Optional[str],
|
||||
) -> None:
|
||||
if error:
|
||||
self._set_download_status(
|
||||
f"Netzlaufwerk-Pruefung fehlgeschlagen: {error}",
|
||||
"orange",
|
||||
)
|
||||
else:
|
||||
color = "#22C55E" if synced_path else "orange"
|
||||
self._set_download_status(status, color)
|
||||
self._scan_and_validate(selected_excel=synced_path)
|
||||
|
||||
def _on_excel_changed(self) -> None:
|
||||
selected = self._excel_name_to_path.get(self._excel_var.get())
|
||||
self._excel_path = selected
|
||||
self._btn_generate.configure(state="disabled")
|
||||
self._clear_preview()
|
||||
self._clear_log()
|
||||
if self._excel_path and self._template_path and self._month_var.get():
|
||||
threading.Thread(target=self._run_validate, daemon=True).start()
|
||||
|
||||
def _on_creation_date_changed(self) -> None:
|
||||
self._btn_generate.configure(state="disabled")
|
||||
self._clear_preview()
|
||||
self._clear_log()
|
||||
if self._excel_path and self._template_path and self._month_var.get():
|
||||
threading.Thread(target=self._run_validate, daemon=True).start()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Event handlers
|
||||
# ------------------------------------------------------------------
|
||||
@@ -414,28 +621,12 @@ class AnwesenheitApp(ctk.CTk):
|
||||
self._clear_log()
|
||||
self._scan_and_validate()
|
||||
|
||||
def _scan_and_validate(self) -> None:
|
||||
def _scan_and_validate(self, selected_excel: Optional[Path] = None) -> None:
|
||||
if _CONFIG_ERROR:
|
||||
self._log(f"[FEHLER] config.json: {_CONFIG_ERROR}")
|
||||
return
|
||||
|
||||
# Scan input/
|
||||
self._excel_path = _scan_input_folder()
|
||||
input_folder = _runtime_dir() / "input"
|
||||
if self._excel_path:
|
||||
self._lbl_excel.configure(
|
||||
text=self._excel_path.name, text_color=("gray10", "gray90")
|
||||
)
|
||||
elif not input_folder.exists() or not list(input_folder.glob("*.xlsx")):
|
||||
self._lbl_excel.configure(
|
||||
text="Keine .xlsx in input/ gefunden", text_color="red"
|
||||
)
|
||||
else:
|
||||
n = len(list(input_folder.glob("*.xlsx")))
|
||||
self._lbl_excel.configure(
|
||||
text=f"Mehrere Dateien gefunden ({n}) — nur eine erlaubt",
|
||||
text_color="red",
|
||||
)
|
||||
self._refresh_excel_dropdown(selected=selected_excel)
|
||||
|
||||
# Scan template/
|
||||
self._template_path = _scan_template_folder()
|
||||
@@ -461,6 +652,8 @@ class AnwesenheitApp(ctk.CTk):
|
||||
# Start validation thread if all inputs are ready
|
||||
if self._excel_path and self._template_path and self._month_var.get():
|
||||
threading.Thread(target=self._run_validate, daemon=True).start()
|
||||
else:
|
||||
self._btn_generate.configure(state="disabled")
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Validation (background thread)
|
||||
@@ -474,6 +667,7 @@ class AnwesenheitApp(ctk.CTk):
|
||||
"year": 0,
|
||||
"persons": [],
|
||||
"month": month,
|
||||
"creation_date": None,
|
||||
"exception": None,
|
||||
}
|
||||
|
||||
@@ -494,6 +688,13 @@ class AnwesenheitApp(ctk.CTk):
|
||||
except Exception as exc:
|
||||
result["errors"].append(f"Teilnehmer nicht lesbar: {exc}")
|
||||
|
||||
try:
|
||||
result["creation_date"] = self._get_creation_date()
|
||||
except ValueError:
|
||||
result["errors"].append(
|
||||
"Erstellungsdatum ungueltig. Bitte Format DD.MM.YYYY verwenden."
|
||||
)
|
||||
|
||||
self.after(0, self._on_validate_done, result)
|
||||
|
||||
def _on_validate_done(self, result: Dict[str, Any]) -> None:
|
||||
@@ -506,6 +707,7 @@ class AnwesenheitApp(ctk.CTk):
|
||||
year: int = result["year"]
|
||||
persons = result["persons"]
|
||||
month: str = result["month"]
|
||||
creation_date_value = result["creation_date"]
|
||||
|
||||
month_num = (_CONFIG.get("month_mapping") or {}).get(month, "??")
|
||||
default_color = ("gray10", "gray90")
|
||||
@@ -526,9 +728,6 @@ class AnwesenheitApp(ctk.CTk):
|
||||
self._pv_ort.configure(
|
||||
text=_CONFIG.get("ort", "–"), text_color=default_color
|
||||
)
|
||||
self._pv_datum.configure(
|
||||
text=date.today().strftime("%d.%m.%Y"), text_color=default_color
|
||||
)
|
||||
self._pv_teilnehmer.configure(
|
||||
text=str(len(persons)), text_color=default_color
|
||||
)
|
||||
@@ -550,6 +749,17 @@ class AnwesenheitApp(ctk.CTk):
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _start_generate(self) -> None:
|
||||
try:
|
||||
self._get_creation_date()
|
||||
except ValueError:
|
||||
messagebox.showerror(
|
||||
"Ung\u00fcltiges Datum",
|
||||
"Bitte ein Erstellungsdatum im Format DD.MM.YYYY eingeben.",
|
||||
parent=self,
|
||||
)
|
||||
self._entry_creation_date.focus_set()
|
||||
return
|
||||
|
||||
count = _count_output_pdfs(_CONFIG)
|
||||
if count > 0:
|
||||
answer = messagebox.askyesnocancel(
|
||||
@@ -581,6 +791,7 @@ class AnwesenheitApp(ctk.CTk):
|
||||
try:
|
||||
year = extract_year_from_filename(excel_file, _CONFIG)
|
||||
persons = extract_persons(excel_file, month, _CONFIG)
|
||||
creation_date_value = self._get_creation_date()
|
||||
total = len(persons)
|
||||
|
||||
out_dir = _runtime_dir() / _CONFIG.get("output_dir", "output")
|
||||
@@ -594,7 +805,13 @@ class AnwesenheitApp(ctk.CTk):
|
||||
)
|
||||
output_filename = f"{person.filename_safe_name}.pdf"
|
||||
output_path = out_dir / output_filename
|
||||
fill_pdf(attendance, self._template_path, output_path, _CONFIG)
|
||||
fill_pdf(
|
||||
attendance,
|
||||
self._template_path,
|
||||
output_path,
|
||||
_CONFIG,
|
||||
creation_date=creation_date_value,
|
||||
)
|
||||
self.after(0, self._log_ok, f"[OK] {output_filename} ({i}/{total})")
|
||||
success_count += 1
|
||||
except Exception as exc:
|
||||
|
||||
@@ -90,6 +90,10 @@
|
||||
"type": "boolean",
|
||||
"description": "Steuert AcroForm /NeedAppearances beim Schreiben der PDF. false wird fuer stabile PyMuPDF-Render bevorzugt (Footer/Buttons sichtbar).",
|
||||
"default": false
|
||||
},
|
||||
"excel_network_source": {
|
||||
"type": "string",
|
||||
"description": "Optionaler SMB- oder Dateipfad zur Excel-Datei, die beim GUI-Start nach input/ kopiert und bei Bedarf aktualisiert wird"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,10 @@ def load_config(config_path: Path | None = None) -> Dict[str, Any]:
|
||||
|
||||
# Optional settings with backward-compatible defaults
|
||||
data.setdefault("pdf_need_appearances", False)
|
||||
data.setdefault(
|
||||
"excel_network_source",
|
||||
r"J:\Fachbereiche\BO\Produktionsschule\Maßnahmeunterlagen\30_Teilnehmende\01_Anwesenheit\Anwesenheitsliste\Anwesenheitsliste 2026.xlsx",
|
||||
)
|
||||
|
||||
_config_cache = data
|
||||
_config_path_cache = config_path
|
||||
|
||||
Reference in new issue
Block a user