""" 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 pymysql import pymysql.cursors from requests import Session from requests_pkcs12 import Pkcs12Adapter from Knihovny.najdi_dropbox import get_dropbox_root from Knihovny.medicus_db import get_medicus_connection from Knihovny.mysql_db import connect_mysql 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) # ── Adresáře ───────────────────────────────────────────────────────────────── XML_DIR = Path(get_dropbox_root()) / "Ordinace" / "Dokumentace_ke_zpracování" / "Zúčtovací zprávy" / "NačteníPředpisuWithClaude" / "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 AND r.STORNO = 'F' 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 = connect_mysql(database="medicus", cursorclass=pymysql.cursors.DictCursor) 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 = get_medicus_connection() 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()