""" 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()