""" Stažení detailu receptů (NacistPredpis) z eRecept SÚKL. Logika přeskakování: - Recept je v recept_doklad se stav_terminal = 1 → přeskočit (vydaný / zrušený) - Recept není v recept_doklad → stáhnout (nový) - Recept je v recept_doklad se stav_terminal = 0 → stáhnout znovu (dosud nevyzvednutý) Spuštění: python 10_StahnoutXML.py # všechny od 2025-01-01 python 10_StahnoutXML.py --od 2026-01-01 python 10_StahnoutXML.py --limit 50 # testování XML odpovědi se ukládají do xml_archive/YYYY-MM-DD/ERP_KOD.xml """ import sys import time import uuid from datetime import datetime, timezone, date from pathlib import Path import random import fdb import pymysql import pymysql.cursors from requests import Session from requests_pkcs12 import Pkcs12Adapter if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(errors="replace") # ── Konfigurace eRecept ────────────────────────────────────────────────────── PFX_FILE = Path(__file__).parent.parent / "AMBSUKL214235369G_31DEC2024.pfx" PFX_PASS = "Vlado7309208104++" API_USER = "e08c89c6-2b1a-4eba-8ed9-4e3e63618379" API_PASS = "Buzalka@Vladimir2025" UZIVATEL = "E08C89C6-2B1A-4EBA-8ED9-4E3E63618379" PRACOVISTE = "00214235367" ENDPOINT = "https://lekar-soap.erecept.sukl.cz/cuer/Lekar" NAMESPACE = "http://www.sukl.cz/erp/201704" PAUZA_MIN = 4 # sekund mezi voláními API (minimum) PAUZA_MAX = 6 # sekund mezi voláními API (maximum) # ── Konfigurace Firebird ───────────────────────────────────────────────────── FB_DSN = r'localhost:c:\medicus 3\data\medicus.fdb' FB_USER = 'SYSDBA' FB_PASS = 'masterkey' FB_CHARSET = 'win1250' # ── Konfigurace MySQL ──────────────────────────────────────────────────────── DB = dict( host = "192.168.1.76", user = "root", password = "Vlado9674+", database = "medicus", charset = "utf8mb4", cursorclass = pymysql.cursors.DictCursor, ) # ── Adresáře ───────────────────────────────────────────────────────────────── XML_DIR = Path(__file__).parent / "xml_archive" # ── Parametry spuštění (uprav zde) ─────────────────────────────────────────── DATUM_OD = "2025-01-01" # recepty od tohoto data LIMIT = None # max počet receptů ke stažení; None = bez omezení # ───────────────────────────────────────────────────────────────────────────── def nacti_terminal_set(mysql_conn): """ Vrátí set ERP kódů, které jsou již terminální (vydané / zrušené / expirované). Jeden dotaz na začátku — pak jen O(1) lookup v Pythonu. """ with mysql_conn.cursor() as cur: cur.execute("SELECT id_dokladu FROM recept_doklad WHERE stav_terminal = 1") return {row["id_dokladu"] for row in cur.fetchall()} def nacti_erp_kody(fb_conn, datum_od, limit=None): """ Načte unikátní ERP kódy z Firebirdu (recept_epodani.erp) od datum_od. Vrací list tuplů: (datum, lek, dop, idpac, prijmeni, jmeno, erp_kod) """ if limit: sql = f"SELECT FIRST {int(limit)}" else: sql = "SELECT" sql += """ r.datum, r.lek, r.dop, r.idpac, TRIM(kar.prijmeni) AS prijmeni, TRIM(kar.jmeno) AS jmeno, ep.erp FROM recept r JOIN recept_epodani ep ON r.id_epodani = ep.id JOIN kar ON r.idpac = kar.idpac WHERE r.datum >= ? AND ep.erp IS NOT NULL ORDER BY r.datum DESC """ cur = fb_conn.cursor() cur.execute(sql, [datum_od]) rows = cur.fetchall() cur.close() # deduplikace dle ERP kódu — jeden recept může mít více léků (řádků) seen = set() unique = [] for row in rows: erp = row[6] if erp not in seen: seen.add(erp) unique.append(row) return unique def volej_nacist_predpis(sess, erp_kod): """Zavolá NacistPredpis SOAP a vrátí (status_code, response_text).""" id_zpravy = str(uuid.uuid4()) odeslano = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S+00:00") soap_body = ( '' '' '' f'' f'' f'' f'{UZIVATEL}' f'{PRACOVISTE}' f'' f'' f'{erp_kod}' f'' f'' f'' f'{id_zpravy}' f'202501A' f'{odeslano}' f'MEDICUS_____' f'' f'' '' '' ) headers = { "Content-Type": 'text/xml; charset="UTF-8"', "SOAPAction": '"NacistPredpis"', "User-Agent": "Medicus", } resp = sess.post(ENDPOINT, data=soap_body.encode("utf-8"), headers=headers, timeout=15) return resp.status_code, resp.text def main(): datum_od = DATUM_OD limit = LIMIT dnes = date.today().isoformat() out_dir = XML_DIR / dnes out_dir.mkdir(parents=True, exist_ok=True) # ── 1. Načti terminální sadu z MySQL ───────────────────────────────────── print("Připojuji MySQL...") mysql = pymysql.connect(**DB) terminal = nacti_terminal_set(mysql) mysql.close() print(f" Terminálních receptů v DB: {len(terminal)}\n") # ── 2. Načti ERP kódy z Firebirdu ──────────────────────────────────────── print("Připojuji Firebird...") fb = fdb.connect(dsn=FB_DSN, user=FB_USER, password=FB_PASS, charset=FB_CHARSET) rows = nacti_erp_kody(fb, datum_od, limit) fb.close() print(f" Unikátních ERP kódů v Medicusu (od {datum_od}): {len(rows)}\n") if not rows: print("Žádné recepty k zpracování.") return # ── 3. Filtruj — přeskoč terminální ────────────────────────────────────── ke_stazeni = [r for r in rows if r[6] not in terminal] preskoceno = len(rows) - len(ke_stazeni) print(f" Přeskočeno (terminální): {preskoceno}") print(f" Ke stažení: {len(ke_stazeni)}\n") if not ke_stazeni: print("Vše je již staženo a terminální. Hotovo.") return # ── 4. SOAP session ─────────────────────────────────────────────────────── sess = Session() sess.mount("https://", Pkcs12Adapter(pkcs12_filename=PFX_FILE, pkcs12_password=PFX_PASS)) sess.auth = (API_USER, API_PASS) ok = 0 chyby = 0 for i, row in enumerate(ke_stazeni, 1): datum_rec, lek, dop, idpac, prijmeni, jmeno, erp_kod = row lek_str = f"{lek} {dop}".strip() if dop else str(lek or "").strip() label = f"{prijmeni} {jmeno}".strip() print(f"[{i:4d}/{len(ke_stazeni)}] {label:30s} {erp_kod} ", end="", flush=True) try: status, text = volej_nacist_predpis(sess, erp_kod) je_chyba = status != 200 or "" in text if not je_chyba: xml_file = out_dir / f"{erp_kod}.xml" xml_file.write_text(text, encoding="utf-8") print(f"OK {len(text.encode()) / 1024:5.1f} KB {lek_str[:40]}") ok += 1 else: chyba_short = text[:120].replace("\n", " ") print(f"CHYBA HTTP {status} {chyba_short}") xml_file = out_dir / f"{erp_kod}_CHYBA.xml" xml_file.write_text(text, encoding="utf-8") chyby += 1 except Exception as e: print(f"EXCEPTION {e}") chyby += 1 if i < len(ke_stazeni): time.sleep(random.uniform(PAUZA_MIN, PAUZA_MAX)) print(f"\nHotovo: {ok} OK, {chyby} chyb, {preskoceno} přeskočeno") print(f"XML: {out_dir}") if __name__ == "__main__": main()