""" 003_aktivni_PN_seznam.py ======================== Vypíše seznam pacientů s aktivní pracovní neschopností (PN) k dnešnímu datu. SQL logika převzata přímo z Medicusu (report "Přehled práce neschopných pacientů"): - ZACNES <= dnes - (KONNES >= dnes) OR (KONNES IS NULL) - PRACNE = 'A' - STORNO <> 'T' Spouštění: tlačítkem z Medicusu nebo přímo pythonu. """ import sys, os, traceback _LOG = r"C:\Users\vlado\PycharmProjects\Medicus\PNWithClaude\003_error.log" def _log_exception(exc_type, exc_value, exc_tb): with open(_LOG, "w", encoding="utf-8") as f: traceback.print_exception(exc_type, exc_value, exc_tb, file=f) sys.excepthook = _log_exception import fdb import datetime import tkinter as tk from tkinter import ttk # ── Připojení ───────────────────────────────────────────────────────────────── DSN = r'localhost:c:\medicus 3\data\medicus.fdb' USER = 'SYSDBA' PASSWORD = 'masterkey' CHARSET = 'win1250' # ── SQL ─────────────────────────────────────────────────────────────────────── SQL = """ SELECT kar.PRIJMENI, kar.JMENO, kar.RODCIS, nes.ZACNES, nes.KONNES, nes.DIAGNO, COALESCE(nes.ECN, nes.CISNES) AS CISNES FROM NES nes JOIN KAR kar ON kar.IDPAC = nes.IDPAC WHERE nes.ZACNES <= ? AND (nes.KONNES >= ? OR nes.KONNES IS NULL) AND nes.PRACNE = 'A' AND nes.STORNO <> 'T' ORDER BY kar.PRIJMENI, kar.JMENO """ def nacti_data(): dnes = datetime.date.today() conn = fdb.connect(dsn=DSN, user=USER, password=PASSWORD, charset=CHARSET) try: cur = conn.cursor() cur.execute(SQL, (dnes, dnes)) rows = cur.fetchall() return dnes, rows finally: conn.close() def delka_PN(zacnes, konnes, dnes): """Počet dní PN (od začátku do dnes nebo do konce).""" do = konnes if konnes else dnes return (do - zacnes).days + 1 def zobraz_okno(dnes, rows): root = tk.Tk() root.title(f"Aktivní pracovní neschopnost – {dnes.strftime('%d.%m.%Y')}") w, h = 900, 550 root.update_idletasks() sw = root.winfo_screenwidth() sh = root.winfo_screenheight() x = (sw - w) // 2 y = (sh - h) // 2 root.geometry(f"{w}x{h}+{x}+{y}") # ── Záhlaví ─────────────────────────────────────────────────────────────── hlavicka = tk.Frame(root, bg="#2c5f8a", pady=8) hlavicka.pack(fill=tk.X) tk.Label( hlavicka, text=f"Pracovní neschopnost – aktivní k {dnes.strftime('%d.%m.%Y')} ({len(rows)} pacientů)", font=("Segoe UI", 13, "bold"), fg="white", bg="#2c5f8a" ).pack() # ── Tabulka ─────────────────────────────────────────────────────────────── cols = ("Příjmení", "Jméno", "Rodné číslo", "Začátek", "Konec", "Dní", "Diagnóza", "Č. neschopenky") frame = tk.Frame(root) frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) scroll_y = tk.Scrollbar(frame, orient=tk.VERTICAL) scroll_y.pack(side=tk.RIGHT, fill=tk.Y) scroll_x = tk.Scrollbar(frame, orient=tk.HORIZONTAL) scroll_x.pack(side=tk.BOTTOM, fill=tk.X) tree = ttk.Treeview( frame, columns=cols, show="headings", yscrollcommand=scroll_y.set, xscrollcommand=scroll_x.set ) scroll_y.config(command=tree.yview) scroll_x.config(command=tree.xview) # Šířky sloupců widths = [120, 100, 110, 85, 85, 50, 80, 150] for col, w in zip(cols, widths): tree.column(col, width=w, anchor=tk.W) # ── Řazení kliknutím na záhlaví ─────────────────────────────────────────── _sort_state = {} # col -> ascending True/False def sort_by(col): ascending = not _sort_state.get(col, False) _sort_state[col] = ascending # Index sloupce pro čtení hodnoty col_idx = cols.index(col) data = [(tree.set(k, col), k) for k in tree.get_children("")] # Číselné řazení pro "Dní" if col == "Dní": data.sort(key=lambda t: int(t[0]) if t[0].isdigit() else 0, reverse=not ascending) else: data.sort(key=lambda t: t[0].lower(), reverse=not ascending) for idx, (_, k) in enumerate(data): tree.move(k, "", idx) # Šipka v záhlaví for c in cols: arrow = (" ▲" if ascending else " ▼") if c == col else "" tree.heading(c, text=c + arrow, command=lambda c=c: sort_by(c)) # Obarvi znovu střídavě for idx, (_, k) in enumerate(data): current_tags = list(tree.item(k, "tags")) dlouha = "dlouha" in current_tags if dlouha: tree.item(k, tags=("dlouha",)) else: tree.item(k, tags=("even" if idx % 2 == 0 else "odd",)) for col in cols: tree.heading(col, text=col, command=lambda c=col: sort_by(c)) # Střídavé barvy řádků tree.tag_configure("even", background="#f0f4f8") tree.tag_configure("odd", background="white") tree.tag_configure("dlouha", foreground="#c0392b") # červeně > 90 dní for i, row in enumerate(rows): prijmeni, jmeno, rodcis, zacnes, konnes, diagno, cisnes = row dni = delka_PN(zacnes, konnes, dnes) konec_str = konnes.strftime("%d.%m.%Y") if konnes else "trvá" diagno_str = (diagno or "").strip() cisnes_str = (cisnes or "").strip() tag = "dlouha" if dni > 90 else ("even" if i % 2 == 0 else "odd") tree.insert("", tk.END, values=( prijmeni, jmeno, rodcis, zacnes.strftime("%d.%m.%Y"), konec_str, dni, diagno_str, cisnes_str, ), tags=(tag,)) tree.pack(fill=tk.BOTH, expand=True) # ── Výchozí řazení: Dní vzestupně ───────────────────────────────────────── sort_by("Dní") # ── Legenda + zavřít ────────────────────────────────────────────────────── spodek = tk.Frame(root, pady=6) spodek.pack(fill=tk.X) tk.Label( spodek, text="Červeně = PN delší než 90 dní", font=("Segoe UI", 9), fg="#c0392b" ).pack(side=tk.LEFT, padx=15) tk.Button( spodek, text="Zavřít", command=root.destroy, width=12, font=("Segoe UI", 10) ).pack(side=tk.RIGHT, padx=15) root.mainloop() # ── Hlavní tok ──────────────────────────────────────────────────────────────── if __name__ == "__main__": try: dnes, rows = nacti_data() zobraz_okno(dnes, rows) except Exception: with open(_LOG, "w", encoding="utf-8") as f: traceback.print_exc(file=f) raise