diff --git a/MedicusWithClaudePN/PN.md b/MedicusWithClaudePN/PN.md new file mode 100644 index 0000000..2b75ba2 --- /dev/null +++ b/MedicusWithClaudePN/PN.md @@ -0,0 +1,321 @@ +# MedicusWithClaudePN – Pracovní neschopnosti + +## Účel + +Report aktivních pracovních neschopností pro MUDr. Buzalkovou Michaelu. +Generuje PDF a odesílá na výchozí tiskárnu. + +## Spuštění + +```bash +# Test – otevře PDF v prohlížeči, netiskne: +python pn_report.py --no-print + +# Ostrý provoz – vytiskne rovnou na výchozí tiskárnu: +python pn_report.py +``` + +## Požadavky + +```bash +pip install reportlab pywin32 +``` + +## SQL dotaz – aktivní PN + +Zachycen přes Firebird trace přímo z Medicusu (přesná kopie logiky aplikace), +doplněn o podotaz na poslední 14denní potvrzení z tabulky HPN. + +```sql +SELECT + nes.id, + nes.idpac, + TRIM(kar.prijmeni) || ', ' || TRIM(kar.jmeno) AS jmeno, + kar.rodcis, + nes.zacnes, + nes.konnes, + nes.diagno, + COALESCE(nes.ecn, nes.cisnes) AS cisnes, + (SELECT MAX(h.datum) FROM hpn h + WHERE h.idnes = nes.id AND h.typ = '2' AND h.storno = 'F') AS posl_potvrzeni +FROM nes, kar +WHERE nes.zacnes <= current_date + AND nes.konnes IS NULL + AND nes.idpac = kar.idpac + AND nes.pracne = 'A' + AND nes.storno <> 'T' + AND ( + NOT EXISTS (SELECT id FROM nesd WHERE nesd.idnes = nes.id) + OR (SELECT FIRST 1 kam FROM nesd WHERE nesd.idnes = nes.id + ORDER BY nesd.datum DESC, nesd.id DESC) = 'N' + ) +ORDER BY kar.prijmeni ASC, kar.jmeno ASC +``` + +### Klíčové podmínky + +| Podmínka | Význam | +|---|---| +| `pracne = 'A'` | Pouze pracovní neschopnosti (ne jiné typy) | +| `storno <> 'T'` | Vyřazení stornovaných záznamů | +| `zacnes <= current_date` | PN již začala | +| `konnes IS NULL` | PN dosud neskončila – datum konce nemůže být v budoucnosti (pravidlo ČSSZ), aktivní PN má vždy `konnes = NULL` | +| `nesd` subquery | PN nebyla předána dál – poslední záznam `Kam = 'N'` = stále u pacienta | +| `COALESCE(ecn, cisnes)` | Použije ECN (elektronické), jinak starší CISNES | + +## Sloupce reportu + +| Sloupec | Zdroj | Poznámka | +|---|---|---| +| # | – | Pořadové číslo | +| Příjmení a jméno | KAR | | +| Rod. číslo | KAR | | +| Začátek PN | NES.ZACNES | | +| Dnů | výpočet | Počet dní od začátku PN do dnes | +| Diagnóza | NES.DIAGNO | | +| Posl. potvrzení | HPN (TYP='2') | Datum posledního 14denního potvrzení | +| Dní od potvr. | výpočet | Červeně pokud > 14 dní | + +## Tabulka HPN – typy podání + +| TYP | Význam | +|---|---| +| `H` | Hlášení neschopnosti (vznik PN) | +| `1` | První zpráva | +| `P` | **Průběžná zpráva = 14denní potvrzení trvání PN** | +| `2` | Neznámý typ (2033 záznamů v DB, ale ne pro průběžná potvrzení) | +| `C`, `Y`, `Z` | Vzácné typy (jednotky záznamů) | + +Vazba: `HPN.IDNES → NES.ID` + +--- + +## Jak Medicus zobrazuje PN daného pacienta (zachyceno z trace) + +### 1. Seznam všech PN pacienta + +```sql +SELECT + ID, IDPAC, DATNES, CISNES, PODNIK, ADRESA, PROFES, ZACNES, + KONNES, PRACNE, PRICINA, DIAGNO, KONDIA, PREDAN, IDUZI, + VYSTAVIL, DATUKONNES, IDODD, IDPRAC, STORNO, + DATVYCHOD, DATVYCHDO, VYCH1OD, VYCH1DO, VYCH2OD, VYCH2DO, VYCH3OD, VYCH3DO, + DATOSETRENI, DATPRINAV, OMLUVENKA, RODCISNES, + DatNastUstPece, DatUkonUstPece, ICPE, ECN, EPODANI, + ADR_OBEC, ADR_CP, ADR_CO, ADR_DOD, ADR_PSC, ADR_STAT, + ZAM_ADRESA, DATUKON_OSSZ, ZAMDRUH, STATDPNKOD, + SOUHLAS_SSZKOD, SOUHLAS_SSZNAZ, SOUHLAS_DATUM, + DGZMENA, CIZI, OSSZ, DUVOD_UKONCENI, UKON_OSSZ, PORUS_REZIMU, + UKON_OSSZNAZ, ADR_ZMENA, coalesce(ECN, CISNES) as CISLO, + ADR_ZMENA_DO, POTVRZENI_VYDANO, DATNAR, SPRAVCE_POJ, + ZAM_OBEC, ZAM_CO, ZAM_CP, ZAM_DOD, ZAM_PSC, ZAM_STAT, ZAM_CCSZ_ID, ZAM_CSSZ_VARSYM, + VYCHINDIVIDUAL, VERZE_DPN, LEKAR_VYSTAVIL, LEKAR_VYSTAVIL_ICPE, + USEDATNAR, KONTAKT_TEL, KONTAKT_EMAIL, + case when KONTAKT_TEL is not NULL then 'S' + when KONTAKT_EMAIL is not null then 'E' + else NULL end as NOTIFIKACE, + coalesce(KONTAKT_TEL, KONTAKT_EMAIL) as NOTIFIKACE_KONTAKT +FROM NES +WHERE IDPAC = ? +ORDER BY DATNES ASC, ID ASC +``` + +### 2. Formuláře eNeschopenky pro vybranou PN (záložka "Formuláře eNeschopenky") + +Zobrazuje pouze TYP `H`, `1`, `2` (ne `P` = propuštění). +Pouze záznamy s vazbou na HISTDOC (`IDHISTDOC IS NOT NULL`). + +```sql +SELECT + HD.ID AS HISTDOCID, + H.TYP AS HISTDOCTYP, -- H=hlášení, 1=první zpráva, 2=průběžná/potvrzení + HD.DATUM AS DATZAD, -- Datum vystavení (z HISTDOC) + H.DATUM AS DATPOD, -- Datum podání (z HPN) + H.STAV, + H.ODBAVENO, + HD.TYP +FROM HPN H +JOIN HISTDOC HD ON H.IDHISTDOC = HD.ID +WHERE H.IDNES = ? +ORDER BY HD.DATUM ASC +``` + +### 3. Rychlý přehled formulářů (bez HISTDOC) + +Používá se pro zjištění stavu – vrací všechny záznamy TYP `H`, `1`, `2`: + +```sql +SELECT * FROM HPN +WHERE IDNES = ? + AND STORNO = 'F' + AND TYP IN ('1', '2', 'H') +ORDER BY DATUM DESC, CAS DESC, ID DESC +``` + +### 4. TFHpnHistorie – formulář "Historie HPN" (acHpnHistorie) + +Kompletní přehled všech HPN záznamů pro vybranou PN. Spouští se akcí `acHpnHistorie`. + +```sql +select h.ID, h.IDNES, h.IDPODANI, h.TYP, h.DATA, h.DATUM, h.CAS, h.ODPOVED, + h.IDPRAC, h.IDUZI, h.UPRAVENO, h.OPRAVA_ID, h.STAV, h.STORNO, + h.POR_CISLO, h.ID_CHYBY, h.OSSZ, + n.CisNes, n.Ecn, coalesce(n.CisNes, n.Ecn) as Cislo, n.IdPac, + k.Prijmeni, k.Jmeno, k.Titul, coalesce(n.RODCISNES, k.RODCIS) as RODCIS, + u.Zkratka, hp.Odeslano, hp.CorelationId, + (select first 1 h2.ID from HPN h2 where h2.OPRAVA_ID = h.ID) as IdOpravy, + h.verze_dpn, + n.SPRAVCE_POJ, + n.ADRESA as ULICE, n.ADR_CP, n.ADR_CO, n.ADR_DOD, n.ADR_OBEC, n.ADR_PSC, n.ADR_STAT, + n.PODNIK, n.PROFES, n.ZAM_ADRESA, n.ZAM_CP, n.ZAM_CO, n.ZAM_OBEC, n.ZAM_PSC, n.ZAM_STAT, + n.ZACNES, n.DIAGNO, n.DATNES, n.PRICINA, + n.KONNES, n.KONDIA, n.DATUKONNES, + n.DATVYCHOD, n.VYCH1OD, n.VYCH1DO, n.VYCH2OD, n.VYCH2DO, + n.PRICINA, n.ICPE, n.VYCH3OD, n.VYCH3DO, n.KONTAKT_TEL, + h.IDHISTDOC, h.ODBAVENO, + IIF((H.IDHISTDOC is not null), + (select HD.TYP from HISTDOC HD where HD.ID = H.IDHISTDOC), null) as HISTDOCTYP +from HPN h + left join NES n on (n.id = h.idnes) + left join KAR k on (k.IdPac = n.IdPac) + left join UZIVATEL u on (u.IdUzi = h.IdUzi) + left join HPN_PODANI hp on (hp.ID = h.IdPodani) +where h.IdNes = ? +ORDER BY h.Datum ASC, h.Cas ASC, h.Id ASC +``` + +### 5. Kontrola čekajících podání (PN s neodeslanými HPN záznamy) + +```sql +select nes.id from nes +where nes.idpac = ? + and nes.storno = 'F' + and nes.epodani = 'T' + and nes.icpe = ? + and coalesce(nes.verze_dpn, '') not in ('', 'p', 'o') + and exists ( + select 1 from hpn + where hpn.idnes = nes.id + and hpn.storno = 'F' + and hpn.typ in ('1', '2', 'H') + and hpn.idpodani is null -- dosud neodesláno + and hpn.stav <> 99 + and hpn.stav <> 10 + ) +``` + +### 6. Kontrola potvrzení vydaného tento měsíc (POTVRZENI_VYDANO) + +Medicus kontroluje zda pro daného pacienta existuje PN aktivní alespoň 10 dní, +u které ještě nebylo vydáno potvrzení v aktuálním měsíci: + +```sql +select first 1 ZACNES, CISNES, ID, POTVRZENI_VYDANO +from NES +where (IDPAC = ?) + and (? >= ZACNES + 10) -- PN trvá alespoň 10 dní + and ((KONNES is NULL) or (KONNES > ?)) + and (STORNO = 'F') + and ( + extract(month from POTVRZENI_VYDANO) || extract(year from POTVRZENI_VYDANO) + = + extract(month from cast(? as date)) || extract(year from cast(? as date)) + ) +``` + +### 7. Vyhledání HPN záznamu podle ICPE v XML datech + +Číslo `11031812` (ICPE lékaře) se hledá přímo v obsahu XML blobu `HPN.DATA`. +Medicus takto identifikuje konkrétní HPN záznam při opravě nebo ověření stavu: + +```sql +-- Neodeslané nebo čekající záznamy: +select h.id from HPN h +left join HPN h2 on (h2.OPRAVA_ID = h.ID) +where (h.storno = 'F') + and ((h.stav in (0,1)) or (h.stav is NULL)) + and (h2.OPRAVA_ID is null) + and (H.DATA containing '11031812') -- hledá ICPE v XML obsahu + and (h.IDNES = ?) + +-- Úspěšně odeslané záznamy (stav=1): +select h.id from HPN h +left join HPN h2 on (h2.OPRAVA_ID = h.ID) +where (h.storno = 'F') + and (h.stav = 1) + and h2.OPRAVA_ID is null + and (H.DATA containing '11031812') + and h.IDNES = ? +``` + +### 8. Předání/Převzetí (záložka "Předání/Převzetí") + +```sql +SELECT ID, IDNES, KAMODKUD, DATUM, KAM, ICZ, ICPE, ICO, JMENO_LEKARE +FROM NESD +WHERE IDNES = ? +ORDER BY DATUM ASC, ID ASC +``` + +### Poznámky + +- **HISTDOC** – každé odeslání formuláře vytváří záznam v HISTDOC; HPN bez IDHISTDOC se v UI nezobrazí +- **HPN.STAV** – stav podání (1 = odesláno/přijato) +- **HPN.ODBAVENO** – příznak zpracování (`'F'` = ne, `'T'` = ano) +- HPN záznamy TYP='2' (průběžná potvrzení) **nemají IDHISTDOC** – JOIN s HISTDOC by je odfiltroval. Pro datum posledního potvrzení v reportu proto používáme prostý MAX bez JOINu. HISTDOC mají pouze TYP='P' (ukončení PN). + +--- + +## Vazby tabulky NES (zjištěno z DB) + +### Formální FK constrainty + +| Směr | Vazba | Popis | +|---|---|---| +| NES → KAR | `NES.IDPAC → KAR.IDPAC` | Každá neschopenka patří pacientovi v kartotéce | +| HPN → NES | `HPN.IDNES → NES.ID` | Formuláře/hlášení HPN odkazují na konkrétní neschopenku | + +### Logické vazby (bez FK constraintu) + +| Tabulka | Pole | Poznámka | +|---|---|---| +| NESD | `NESD.IDNES → NES.ID` | Předání/převzetí PN – vazba jen kódem, ne constraintem | +| HISTDOC | `HPN.IDHISTDOC → HISTDOC.ID` | Dokumenty k formulářům – vazba přes HPN | + +### Indexy na NES + +| Index | Unique | Pole | +|---|---|---| +| PK_NES | Ano | ID | +| FK_NES_KAR | Ne | IDPAC | +| NES_POTVRZENI_VYDANO | Ne | POTVRZENI_VYDANO | + +### Poznámka + +Medicus obecně používá minimum DB constraintů – většina vazeb je řešena aplikačním kódem. +`NESD` a `HISTDOC` nemají formální FK na `NES`, přesto jsou klíčové pro zobrazení PN v UI. + +--- + +## Červené zvýraznění + +Sloupce "Posl. potvrzení" a "Dní od potvr." jsou červeně zvýrazněny pokud: +- Od posledního potvrzení uplynulo více než 14 dní, nebo +- PN nemá žádné potvrzení a trvá déle než 14 dní (zobrazí se s `(!)`) + +## Tisk + +Skript používá `win32api.ShellExecute` s příkazem `'print'` – odešle PDF +na výchozí tiskárnu Windows. + +## Automatizace + +Plánované spouštění každé pondělí a pátek ráno přes Windows Task Scheduler +– zatím nenastaveno, připravit až bude skript stabilní. + +## Soubory + +| Soubor | Obsah | +|---|---| +| `pn_report.py` | Hlavní skript – DB dotaz, generování PDF, tisk | +| `PN.md` | Tento soubor – dokumentace | diff --git a/MedicusWithClaudePN/pn_report.py b/MedicusWithClaudePN/pn_report.py new file mode 100644 index 0000000..9e95aa0 --- /dev/null +++ b/MedicusWithClaudePN/pn_report.py @@ -0,0 +1,320 @@ +""" +pn_report.py – Report aktivních pracovních neschopností +Generuje PDF a posílá na výchozí tiskárnu. + +Spuštění: + python pn_report.py # vytvoří PDF a vytiskne + python pn_report.py --no-print # jen vytvoří PDF (pro testování) + +Požadavky: + pip install reportlab pywin32 +""" + +import fdb +import sys +import os +import tempfile +from datetime import date, datetime + +from reportlab.lib.pagesizes import A4 +from reportlab.lib import colors +from reportlab.lib.units import cm +from reportlab.lib.styles import getSampleStyleSheet +from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer +from reportlab.pdfbase import pdfmetrics +from reportlab.pdfbase.ttfonts import TTFont + +# --------------------------------------------------------------------------- +# Konfigurace +# --------------------------------------------------------------------------- + +DB_DSN = r'localhost:c:\medicus 3\data\medicus.fdb' +DB_USER = 'SYSDBA' +DB_PASS = 'masterkey' +DB_CHARSET = 'win1250' + +# Pokud chcete soubor uložit trvale, nastavte výstupní adresář: +OUTPUT_DIR = None # None = dočasný soubor, smaže se po tisku +#OUTPUT_DIR = r'u:\Dropbox\!!!Days\Downloads Z230' + +# --------------------------------------------------------------------------- +# Dotaz – aktivní PN (konnes IS NULL nebo v budoucnosti, storno='F') +# --------------------------------------------------------------------------- + +SQL = """ + SELECT + nes.id AS idnes, + nes.idpac, + TRIM(kar.prijmeni) || ', ' || TRIM(kar.jmeno) AS jmeno, + kar.rodcis, + nes.zacnes, + nes.konnes, + nes.diagno, + COALESCE(nes.ecn, nes.cisnes) AS cisnes, + (SELECT MAX(h.datum) FROM hpn h + WHERE h.idnes = nes.id AND h.typ = 'P' AND h.storno = 'F') AS posl_potvrzeni + FROM nes, kar + WHERE nes.zacnes <= current_date + AND nes.konnes IS NULL + AND nes.idpac = kar.idpac + AND nes.pracne = 'A' + AND nes.storno <> 'T' + AND ( + NOT EXISTS (SELECT id FROM nesd WHERE nesd.idnes = nes.id) + OR (SELECT FIRST 1 kam FROM nesd WHERE nesd.idnes = nes.id + ORDER BY nesd.datum DESC, nesd.id DESC) = 'N' + ) + ORDER BY kar.prijmeni ASC, kar.jmeno ASC +""" + +# --------------------------------------------------------------------------- +# Pomocné funkce +# --------------------------------------------------------------------------- + +def fmt_date(val): + """Datum → DD.MM.YYYY nebo prázdný řetězec.""" + if val is None: + return '' + if isinstance(val, (date, datetime)): + return val.strftime('%d.%m.%Y') + return str(val) + +def fmt_str(val): + if val is None: + return '' + return str(val).strip() + +def delka_pn(zacnes, konnes): + """Počet dnů PN (od začátku do dnes / do konce).""" + if zacnes is None: + return '' + end = konnes if konnes else date.today() + if isinstance(zacnes, datetime): + zacnes = zacnes.date() + if isinstance(end, datetime): + end = end.date() + if isinstance(zacnes, date) and isinstance(end, date): + days = (end - zacnes).days + 1 + return str(days) + return '' + +# --------------------------------------------------------------------------- +# Načtení fontu s českou diakritikou +# --------------------------------------------------------------------------- + +def register_font(): + """ + Zkusí zaregistrovat DejaVuSans (umí win1250 znaky). + Fallback: Helvetica (bez diakritiky – nouzové řešení). + """ + font_paths = [ + r'C:\Windows\Fonts\DejaVuSans.ttf', + r'C:\Windows\Fonts\arial.ttf', + r'C:\Windows\Fonts\segoeui.ttf', + ] + for path in font_paths: + if os.path.exists(path): + name = os.path.splitext(os.path.basename(path))[0] + try: + pdfmetrics.registerFont(TTFont(name, path)) + pdfmetrics.registerFont(TTFont(name + '-Bold', + path.replace('.ttf', 'bd.ttf') if 'arial' in path.lower() + else path.replace('.ttf', '-Bold.ttf') + if os.path.exists(path.replace('.ttf', '-Bold.ttf')) + else path + )) + return name, name + '-Bold' + except Exception: + continue + return 'Helvetica', 'Helvetica-Bold' + +# --------------------------------------------------------------------------- +# Generování PDF +# --------------------------------------------------------------------------- + +def build_pdf(rows, output_path, font_name, font_bold): + today_str = date.today().strftime('%d.%m.%Y') + weekday_cs = ['pondělí', 'úterý', 'středa', 'čtvrtek', 'pátek', 'sobota', 'neděle'] + weekday = weekday_cs[date.today().weekday()] + + doc = SimpleDocTemplate( + output_path, + pagesize=A4, + topMargin=1.5*cm, + bottomMargin=1.5*cm, + leftMargin=1.5*cm, + rightMargin=1.5*cm, + ) + + styles = getSampleStyleSheet() + + def para(text, size=10, bold=False, align='LEFT', color=colors.black): + fn = font_bold if bold else font_name + al = {'LEFT': 0, 'CENTER': 1, 'RIGHT': 2}.get(align, 0) + from reportlab.platypus import Paragraph as P + from reportlab.lib.styles import ParagraphStyle + st = ParagraphStyle('x', fontName=fn, fontSize=size, + textColor=color, alignment=al, leading=size*1.3) + return P(text, st) + + story = [] + + # Záhlaví + story.append(para('MUDr. Buzalková Michaela – ordinace praktického lékaře', + size=9, color=colors.grey)) + story.append(para(f'Aktivní pracovní neschopnosti', + size=16, bold=True)) + story.append(para(f'Vytištěno: {weekday} {today_str} | Počet záznamů: {len(rows)}', + size=9, color=colors.grey)) + story.append(Spacer(1, 0.4*cm)) + + if not rows: + story.append(para('Žádné aktivní pracovní neschopnosti.', size=12)) + doc.build(story) + return + + # Záhlaví tabulky + headers = ['#', 'Příjmení a jméno', 'Rod. číslo', 'Začátek PN', + 'Dnů', 'Diagnóza', 'Posl. potvrzení', 'Dní od potvr.'] + + col_widths = [0.7*cm, 5.5*cm, 2.8*cm, 2.4*cm, 1.4*cm, 2.2*cm, 3.0*cm, 2.2*cm] + + table_data = [headers] + overdue_rows = [] # indexy řádků kde je potvrzení po splatnosti + + for idx, row in enumerate(rows, start=1): + idnes, idpac, jmeno, rodcis, zacnes, konnes, diagno, cisnes, posl_potvrzeni = row + + # Počet dní od posledního potvrzení + if posl_potvrzeni is not None: + pp = posl_potvrzeni.date() if isinstance(posl_potvrzeni, datetime) else posl_potvrzeni + dni_od = (date.today() - pp).days + dni_od_str = str(dni_od) + if dni_od > 14: + overdue_rows.append(idx + 1) # +1 kvůli záhlaví + else: + # Žádné potvrzení – počítáme od začátku PN + zac = zacnes.date() if isinstance(zacnes, datetime) else zacnes + if zac: + dni_od = (date.today() - zac).days + dni_od_str = str(dni_od) + ' (!)' + if dni_od > 14: + overdue_rows.append(idx + 1) + else: + dni_od_str = '—' + + table_data.append([ + str(idx), + fmt_str(jmeno), + fmt_str(rodcis), + fmt_date(zacnes), + delka_pn(zacnes, konnes), + fmt_str(diagno), + fmt_date(posl_potvrzeni) if posl_potvrzeni else '—', + dni_od_str, + ]) + + tbl = Table(table_data, colWidths=col_widths, repeatRows=1) + + style = TableStyle([ + # Záhlaví + ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#2F5496')), + ('TEXTCOLOR', (0, 0), (-1, 0), colors.white), + ('FONTNAME', (0, 0), (-1, 0), font_bold), + ('FONTSIZE', (0, 0), (-1, 0), 8), + ('ALIGN', (0, 0), (-1, 0), 'CENTER'), + ('BOTTOMPADDING',(0, 0), (-1, 0), 5), + ('TOPPADDING', (0, 0), (-1, 0), 5), + # Data + ('FONTNAME', (0, 1), (-1, -1), font_name), + ('FONTSIZE', (0, 1), (-1, -1), 8), + ('ALIGN', (0, 1), (0, -1), 'CENTER'), # # + ('ALIGN', (5, 1), (5, -1), 'RIGHT'), # Dnů + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('TOPPADDING', (0, 1), (-1, -1), 3), + ('BOTTOMPADDING',(0, 1), (-1, -1), 3), + # Mřížka + ('GRID', (0, 0), (-1, -1), 0.3, colors.HexColor('#AAAAAA')), + ('LINEBELOW', (0, 0), (-1, 0), 1, colors.HexColor('#2F5496')), + # Zebra + *[('BACKGROUND', (0, i), (-1, i), colors.HexColor('#DCE6F1')) + for i in range(2, len(table_data), 2)], + # Červené zvýraznění – potvrzení po splatnosti (> 14 dní) + *[('BACKGROUND', (6, i), (7, i), colors.HexColor('#F4CCCC')) + for i in overdue_rows], + *[('TEXTCOLOR', (6, i), (7, i), colors.HexColor('#CC0000')) + for i in overdue_rows], + *[('FONTNAME', (6, i), (7, i), font_bold) + for i in overdue_rows], + ]) + tbl.setStyle(style) + story.append(tbl) + + # Patička + story.append(Spacer(1, 0.5*cm)) + story.append(para(f'--- konec reportu ({len(rows)} záznamů) ---', + size=8, color=colors.grey, align='CENTER')) + + doc.build(story) + +# --------------------------------------------------------------------------- +# Tisk +# --------------------------------------------------------------------------- + +def print_pdf(path): + """Pošle PDF na výchozí tiskárnu přes Windows ShellExecute.""" + try: + import win32api + win32api.ShellExecute(0, 'print', path, None, '.', 0) + print(f'Odesláno na tiskárnu: {path}') + except ImportError: + # Fallback – otevře soubor v PDF prohlížeči (ruční tisk) + print('pywin32 není nainstalován, otevírám PDF...') + os.startfile(path) + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +def main(): + no_print = '--no-print' in sys.argv + + # Připojení k DB + print('Připojuji se k DB...') + conn = fdb.connect(dsn=DB_DSN, user=DB_USER, password=DB_PASS, charset=DB_CHARSET) + cur = conn.cursor() + + print('Načítám aktivní PN...') + cur.execute(SQL) + rows = cur.fetchall() + conn.close() + print(f'Nalezeno {len(rows)} aktivních PN.') + + # Font + font_name, font_bold = register_font() + print(f'Font: {font_name}') + + # Výstupní soubor + if OUTPUT_DIR: + os.makedirs(OUTPUT_DIR, exist_ok=True) + out_path = os.path.join(OUTPUT_DIR, + date.today().strftime('%Y-%m-%d') + '_pn_report.pdf') + else: + fd, out_path = tempfile.mkstemp(suffix='_pn_report.pdf') + os.close(fd) + + print(f'Generuji PDF: {out_path}') + build_pdf(rows, out_path, font_name, font_bold) + print('PDF hotovo.') + + if no_print: + print('(tisk přeskočen – --no-print)') + # Otevřeme pro náhled + os.startfile(out_path) + else: + print_pdf(out_path) + # Dočasný soubor necháme – tiskárna ho potřebuje přečíst + # Windows ho smaže sám po zpracování tisku (temp adresář) + +if __name__ == '__main__': + main() diff --git a/MedicusWithClaudePN/test_pn.py b/MedicusWithClaudePN/test_pn.py new file mode 100644 index 0000000..f4e6cfa --- /dev/null +++ b/MedicusWithClaudePN/test_pn.py @@ -0,0 +1,28 @@ +import fdb, sys +sys.stdout.reconfigure(encoding='utf-8') +conn = fdb.connect(dsn=r'localhost:c:\medicus 3\data\medicus.fdb', user='SYSDBA', password='masterkey', charset='win1250') +cur = conn.cursor() + +sql = """ + SELECT nes.id, TRIM(kar.prijmeni) || ', ' || TRIM(kar.jmeno) AS jmeno, + nes.zacnes, + (SELECT MAX(h.datum) FROM hpn h + WHERE h.idnes = nes.id AND h.typ = 'P' AND h.storno = 'F') AS posl_potvrzeni + FROM nes, kar + WHERE nes.zacnes <= current_date + AND nes.konnes IS NULL + AND nes.idpac = kar.idpac + AND nes.pracne = 'A' + AND nes.storno <> 'T' + AND ( + NOT EXISTS (SELECT id FROM nesd WHERE nesd.idnes = nes.id) + OR (SELECT FIRST 1 kam FROM nesd WHERE nesd.idnes = nes.id + ORDER BY nesd.datum DESC, nesd.id DESC) = 'N' + ) + ORDER BY kar.prijmeni ASC +""" + +cur.execute(sql) +for row in cur.fetchall(): + print(row) +conn.close()