notebook vb

This commit is contained in:
2026-03-31 07:47:17 +02:00
parent 8497223ebb
commit 3141875629
3 changed files with 669 additions and 0 deletions
+321
View File
@@ -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 |
+320
View File
@@ -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()
+28
View File
@@ -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()