Files
medicus/PNWithClaude/003_aktivni_PN_seznam.py
2026-03-25 17:32:12 +01:00

209 lines
7.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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