notebook vb
This commit is contained in:
@@ -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 |
|
||||||
@@ -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()
|
||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user