#!/usr/bin/env python3 # -*- coding: utf-8 -*- from requests_pkcs12 import Pkcs12Adapter import requests import uuid from datetime import date class VZPB2BClient: def __init__(self, env: str, pfx_path: str, pfx_password: str, icz: str = "00000000", dic: str = "00000000"): # Normalize environment name env = env.lower().strip() if env in ("prod", "production", "live", "real"): self.env = "prod" elif env in ("simu", "simulace", "test", "testing"): self.env = "simu" else: raise ValueError(f"Unknown environment '{env}'. Use 'simu' or 'prod'.") self.pfx_path = pfx_path self.pfx_password = pfx_password self.icz = icz self.dic = dic # Prepare mTLS session session = requests.Session() session.mount( "https://", Pkcs12Adapter(pkcs12_filename=pfx_path, pkcs12_password=pfx_password) ) self.session = session # -------------------------------------------------------------- # URL BUILDER # -------------------------------------------------------------- def _build_endpoint(self, service_name: str) -> str: """ SIMU: https://simu.b2b.vzp.cz/B2BProxy/HttpProxy/SIMU?sluzba=SIMU PROD: https://prod.b2b.vzp.cz/B2BProxy/HttpProxy/ """ if self.env == "simu": simu_service = f"SIMU{service_name}" return ( f"https://simu.b2b.vzp.cz/B2BProxy/HttpProxy/" f"{simu_service}?sluzba={simu_service}" ) # Production return ( f"https://prod.b2b.vzp.cz/B2BProxy/HttpProxy/{service_name}" ) # -------------------------------------------------------------- # SOAP HEADER BUILDER # -------------------------------------------------------------- def _header(self) -> str: idZpravy = uuid.uuid4().hex[:12] # must be alphanumeric, max 12 chars return f""" {idZpravy} {self.icz} {self.dic} """ # -------------------------------------------------------------- # OVERPRUKAZ — EHIC CHECK # -------------------------------------------------------------- def over_prukaz_pojistence(self, cislo_prukazu: str, k_datu: str = None) -> str: """ Calls OverPrukazPojistenceB2B (SIMU or PROD depending on env). Returns raw XML string. """ service = "OverPrukazPojistenceB2B" endpoint = self._build_endpoint(service) if not k_datu: k_datu = date.today().isoformat() soap = f""" {self._header()} {cislo_prukazu} {k_datu} """ headers = {"Content-Type": "text/xml; charset=utf-8"} print(f"Calling: {endpoint}") response = self.session.post( endpoint, data=soap.encode("utf-8"), headers=headers, timeout=30 ) print("HTTP:", response.status_code) return response.text def stav_pojisteni(self, rc: str, k_datu: str = None, prijmeni: str = None): """ Calls stavPojisteniB2B (SIMU or PROD). """ service = "stavPojisteniB2B" endpoint = self._build_endpoint(service) if not k_datu: k_datu = date.today().isoformat() prijmeni_xml = f"{prijmeni}" if prijmeni else "" soap = f""" {self._header()} {rc} {prijmeni_xml} {k_datu} """ headers = { "Content-Type": "text/xml; charset=utf-8", "SOAPAction": "process" } print(f"Calling: {endpoint}") resp = self.session.post(endpoint, data=soap.encode("utf-8"), headers=headers, timeout=30) print("HTTP:", resp.status_code) return resp.text def registrace_lekare(self, rc: str, k_datu: str = None, odbornosti: list = None) -> str: """ Calls RegistracePojistencePZSB2B — vrátí registrující lékaře pojištěnce. odbornosti: seznam kódů, např. ["001","002","014"]. None = bez filtru (vrátí vše). """ service = "RegistracePojistencePZSB2B" endpoint = self._build_endpoint(service) if not k_datu: k_datu = date.today().isoformat() ns = "http://xmlns.gemsystem.cz/B2B/RegistracePojistencePZSB2B/1" odb_xml = "" if odbornosti: kody = "".join(f"{k}" for k in odbornosti) odb_xml = f"{kody}" soap = f""" {rc} {k_datu} {odb_xml} """ resp = self.session.post( endpoint, data=soap.encode("utf-8"), headers={"Content-Type": "text/xml; charset=utf-8", "SOAPAction": "process"}, timeout=30, ) return resp.text def parse_registrace_lekare(self, xml_text: str) -> list[dict]: """ Parsuje odpověď RegistracePojistencePZSB2B. Vrátí seznam diktů — jeden na odbornost (i prázdné = ma_lekare=False). """ import xml.etree.ElementTree as ET NS = { "soap": "http://schemas.xmlsoap.org/soap/envelope/", "rp": "http://xmlns.gemsystem.cz/B2B/RegistracePojistencePZSB2B/1", } root = ET.fromstring(xml_text) stav_el = root.find(".//rp:stavVyrizeniPozadavku", NS) stav_vyrizeni = stav_el.text.strip() if stav_el is not None and stav_el.text else None # Jen vnější elementy (ty s ICZ), ne vnořené subelementy results = [] for it in root.findall(".//rp:seznamOdbornosti/rp:odbornost", NS): def g(tag): el = it.find(f"rp:{tag}", NS) return el.text.strip() if el is not None and el.text else None odb = it.find("rp:odbornost", NS) if odb is not None: # Záznam s lékařem kod = odb.find("rp:kod", NS) naz = odb.find("rp:nazev", NS) poj = it.find("rp:zdravotniPojistovna", NS) results.append({ "ma_lekare": True, "kod_odbornosti": kod.text.strip() if kod is not None and kod.text else None, "nazev_odbornosti": naz.text.strip() if naz is not None and naz.text else None, "ICZ": g("ICZ"), "ICP": g("ICP"), "nazev_lekare": g("nazevICP"), "nazev_zzz": g("nazevSZZ"), "poj_kod": poj.find("rp:kod", NS).text.strip() if poj is not None and poj.find("rp:kod", NS) is not None else None, "poj_zkratka": poj.find("rp:zkratka", NS).text.strip() if poj is not None and poj.find("rp:zkratka", NS) is not None else None, "datum_registrace": g("datumRegistrace"), "datum_zahajeni": g("datumZahajeni"), "datum_ukonceni": g("datumUkonceni"), "stav_vyrizeni": stav_vyrizeni, }) else: # Prázdný placeholder — pacient nemá lékaře v této odbornosti # (VZP vrací element bez ICZ/ICP — ignorujeme, zaznamená skript sám) pass return results def parse_stav_pojisteni(self, xml_text: str): """ Parses stavPojisteniB2B SOAP response into a Python dict. Returned structure: { "stavVyrizeni": int, "stav": str | None, "kodPojistovny": str | None, "nazevPojistovny": str | None, "pojisteniKod": str | None } """ import xml.etree.ElementTree as ET NS = { "soap": "http://schemas.xmlsoap.org/soap/envelope/", "vzp": "http://xmlns.gemsystem.cz/stavPojisteniB2B" } root = ET.fromstring(xml_text) # ---- Extract status ---- stav_vyr = root.find(".//vzp:stavVyrizeniPozadavku", NS) stav_vyr = int(stav_vyr.text.strip()) if stav_vyr is not None else None # ---- If no stavPojisteni element present (e.g. 0 or some errors) ---- node_stav = root.find(".//vzp:stavPojisteni", NS) if node_stav is None: return { "stavVyrizeni": stav_vyr, "stav": None, "kodPojistovny": None, "nazevPojistovny": None, "pojisteniKod": None, } def get(tag): el = node_stav.find(f"vzp:{tag}", NS) return el.text.strip() if el is not None and el.text else None return { "stavVyrizeni": stav_vyr, "stav": get("stav"), "kodPojistovny": get("kodPojistovny"), "nazevPojistovny": get("nazevPojistovny"), "pojisteniKod": get("pojisteniKod"), }