diff --git a/Insurance/SeznamPojistencu/zadej_davku.py b/Insurance/SeznamPojistencu/zadej_davku.py
new file mode 100644
index 0000000..20a3a96
--- /dev/null
+++ b/Insurance/SeznamPojistencu/zadej_davku.py
@@ -0,0 +1,167 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+import sys as _sys
+_sys.stdout.reconfigure(encoding="utf-8", errors="replace")
+_sys.stderr.reconfigure(encoding="utf-8", errors="replace")
+"""
+zadej_davku.py
+==============
+Odešle asynchronní požadavek na VZP B2B službu SeznamRegPojistencuB2B
+(seznam zakapitovaných/registrovaných pojištěnců za dané období).
+
+Použití:
+ python zadej_davku.py [mesic] [rok]
+ python zadej_davku.py 2 2025 # únor 2025
+ python zadej_davku.py # předchozí měsíc (výchozí)
+
+Výstup:
+ - korelační ID zprávy (pro pozdější spárování asynchronní odpovědi)
+ - stavVyrizeniPozadavku=2 znamená "přijato, VZP zpracovává"
+ - finální odpověď přijde asynchronně na AS2 endpoint partnera
+"""
+
+import sys
+import uuid
+import argparse
+from pathlib import Path
+from datetime import date, timedelta
+
+from requests_pkcs12 import Pkcs12Adapter
+import requests
+import xml.etree.ElementTree as ET
+
+# ── KONFIGURACE ───────────────────────────────────────────────────────────────
+
+PFX_PATH = Path(__file__).resolve().parent.parent / "Certificates" / "picka.pfx"
+PFX_PASSWORD = "Vlado7309208104+"
+
+ICZ = "09305000"
+ENV = "prod"
+
+NS_VZP = "http://xmlns.gemsystem.cz/SeznamRegPojistencuB2B"
+NS_COMMON = "http://xmlns.gemsystem.cz/CommonB2B"
+NS_SOAP = "http://schemas.xmlsoap.org/soap/envelope/"
+
+ENDPOINT_SIMU = "https://simu.b2b.vzp.cz/B2BProxy/HttpProxy/SIMUSeznamRegPojistencuB2B?sluzba=SIMUSeznamRegPojistencuB2B"
+ENDPOINT_PROD = "https://prod.b2b.vzp.cz/B2BProxy/HttpProxy/SeznamRegPojistencuB2B"
+
+# ── ARGUMENTY ────────────────────────────────────────────────────────────────
+
+parser = argparse.ArgumentParser(description="Požadavek na seznam registrovaných pojištěnců VZP")
+parser.add_argument("mesic", nargs="?", type=int, help="Měsíc (1-12)")
+parser.add_argument("rok", nargs="?", type=int, help="Rok (např. 2025)")
+parser.add_argument("--simu", action="store_true", help="Použít simulační prostředí")
+parser.add_argument("--pdf", action="store_true", help="Výstup jako PDF (výchozí: text/plain)")
+args = parser.parse_args()
+
+# Výchozí: předchozí měsíc
+if args.mesic and args.rok:
+ mesic, rok = args.mesic, args.rok
+else:
+ prvni_tohoto = date.today().replace(day=1)
+ predchozi = prvni_tohoto - timedelta(days=1)
+ mesic, rok = predchozi.month, predchozi.year
+
+format_vystupu = "application/pdf" if args.pdf else "text/plain"
+endpoint = ENDPOINT_SIMU if args.simu else ENDPOINT_PROD
+
+# ── KONTROLY ─────────────────────────────────────────────────────────────────
+
+if not PFX_PATH.exists():
+ print(f"CHYBA: certifikát nenalezen: {PFX_PATH}")
+ sys.exit(1)
+
+if not (1 <= mesic <= 12):
+ print(f"CHYBA: neplatný měsíc: {mesic}")
+ sys.exit(1)
+
+# ── SESTAVENÍ POŽADAVKU ───────────────────────────────────────────────────────
+
+id_zpravy = uuid.uuid4().hex[:12].upper()
+
+soap = f"""
+
+
+
+ {id_zpravy}
+
+ {ICZ}
+
+
+
+
+
+ {id_zpravy}
+ {ICZ}
+ zp
+
+
+ {mesic}
+ {rok}
+
+ {format_vystupu}
+
+
+
+
+"""
+
+# ── ODESLÁNÍ ─────────────────────────────────────────────────────────────────
+
+print(f"Období: {mesic:02d}/{rok}")
+print(f"Formát: {format_vystupu}")
+print(f"Prostředí: {'SIMU' if args.simu else 'PROD'}")
+print(f"IČZ: {ICZ}")
+print(f"ID zprávy: {id_zpravy}")
+print(f"Endpoint: {endpoint}")
+print()
+
+session = requests.Session()
+session.mount("https://", Pkcs12Adapter(
+ pkcs12_filename=str(PFX_PATH),
+ pkcs12_password=PFX_PASSWORD
+))
+
+try:
+ resp = session.post(
+ endpoint,
+ data=soap.encode("utf-8"),
+ headers={"Content-Type": "text/xml; charset=utf-8", "SOAPAction": "process"},
+ timeout=30
+ )
+except requests.RequestException as e:
+ print(f"CHYBA při spojení: {e}")
+ sys.exit(1)
+
+print(f"HTTP status: {resp.status_code}")
+print()
+
+# ── PARSOVÁNÍ ODPOVĚDI ────────────────────────────────────────────────────────
+
+if not resp.content:
+ print("→ Požadavek přijat (prázdná odpověď = asynchronní služba).")
+ print(f"→ VZP zpracuje a odešle výsledek na AS2 endpoint.")
+ print(f"→ ID zprávy pro spárování: {id_zpravy}")
+else:
+ try:
+ root = ET.fromstring(resp.text)
+ NS = {"soap": NS_SOAP, "vzp": NS_VZP}
+ korelace = root.find(".//vzp:korelaceZpravy", NS)
+ text_odp = root.find(".//vzp:textOdpovedi", NS)
+ stav = root.find(".//vzp:stavVyrizeniPozadavku", NS)
+
+ print(f"Korelace zprávy: {korelace.text if korelace is not None else '–'}")
+ print(f"Text odpovědi: {text_odp.text if text_odp is not None else '–'}")
+ print(f"Stav vyřízení: {stav.text if stav is not None else '–'}")
+
+ if stav is not None and stav.text == "2":
+ print()
+ print("→ VZP zpracovává — finální odpověď přijde asynchronně na AS2 endpoint.")
+ print(f"→ ID zprávy pro spárování: {id_zpravy}")
+
+ except ET.ParseError:
+ print("Nepodařilo se parsovat XML odpověď:")
+ print(resp.text)
diff --git a/Insurance/StahováníZpráv/111 VZP/04_prihlaseni_a_stahuj_nove.py b/Insurance/StahováníZpráv/111 VZP/04_prihlaseni_a_stahuj_nove.py
index 07ac8a9..b190f81 100644
--- a/Insurance/StahováníZpráv/111 VZP/04_prihlaseni_a_stahuj_nove.py
+++ b/Insurance/StahováníZpráv/111 VZP/04_prihlaseni_a_stahuj_nove.py
@@ -1,7 +1,7 @@
"""
-Přihlásí se na VZP Point a stáhne nové zprávy.
+Přihlásí se na VZP Point, stáhne nové zprávy a aktualizuje číselníky.
-Kombinuje 01_prihlaseni.py + 03_stahuj_nove.py do jednoho spuštění.
+Kombinuje 01_prihlaseni.py + 03_stahuj_nove.py + 01_stahni_ciselniky.py.
Přihlášení probíhá plně automaticky (Chrome auto-vybere certifikát).
POUŽITÍ:
@@ -13,23 +13,29 @@ import sys
import os
DIR = os.path.dirname(os.path.abspath(__file__))
+CISELNIKY_SCRIPT = os.path.abspath(
+ os.path.join(DIR, "..", "..", "..", "Recepty", "StahovánízVZPWithClaude", "01_stahni_ciselniky.py")
+)
def run(script: str) -> None:
result = subprocess.run(
- [sys.executable, os.path.join(DIR, script)],
+ [sys.executable, script],
check=False,
)
if result.returncode != 0:
- raise SystemExit(f"Skript {script} skončil s chybou (kód {result.returncode})")
+ raise SystemExit(f"Skript {os.path.basename(script)} skončil s chybou (kód {result.returncode})")
def main() -> None:
print("=== Přihlášení ===")
- run("01_prihlaseni.py")
+ run(os.path.join(DIR, "01_prihlaseni.py"))
print("\n=== Stahování nových zpráv ===")
- run("03_stahuj_nove.py")
+ run(os.path.join(DIR, "03_stahuj_nove.py"))
+
+ print("\n=== Stahování číselníků VZP ===")
+ run(CISELNIKY_SCRIPT)
if __name__ == "__main__":
diff --git a/Recepty/StahovánízVZPWithClaude/01_stahni_ciselniky.py b/Recepty/StahovánízVZPWithClaude/01_stahni_ciselniky.py
new file mode 100644
index 0000000..61610f7
--- /dev/null
+++ b/Recepty/StahovánízVZPWithClaude/01_stahni_ciselniky.py
@@ -0,0 +1,289 @@
+"""
+01 — Stáhni číselníky VZP z point.vzp.cz/Cms/Document (sekce „Číselníky").
+Soubory přejmenuje do formátu: YYYY-MM-DD NázevLinku (popis).zip
+Ověří co již existuje v cílovém adresáři a stáhne pouze chybějící soubory.
+Použití: python 01_stahni_ciselniky.py [--dry-run]
+"""
+
+import json
+import os
+import re
+import sys
+
+# Windows konzole - povol UTF-8 výstup
+if sys.stdout.encoding != "utf-8":
+ import io
+ sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "Knihovny"))
+from najdi_dropbox import get_dropbox_root
+
+TARGET_DIR = os.path.join(
+ get_dropbox_root(),
+ "Ordinace", "Dokumentace_ke_zpracování", "Zúčtovací zprávy", "Číselníky"
+)
+
+DRY_RUN = False # True = pouze zobraz co by se stáhlo, nic nestahuj
+
+CHROME_PROFILE = os.path.join(os.path.dirname(__file__), "chrome_profile")
+COOKIES_FILE = os.path.join(os.path.dirname(__file__), "vzp_cookies.json")
+
+# Issuer CN certifikátu v Windows store (CurrentUser\My)
+CERT_ISSUER_CN = "I.CA Public CA/RSA 06/2022"
+
+
+def _set_chrome_cert_policy() -> None:
+ """Nastaví Chrome politiku AutoSelectCertificateForUrls — Chrome vybere certifikát automaticky."""
+ import winreg
+ policy = json.dumps({
+ "pattern": "https://[*.]vzp.cz",
+ "filter": {"ISSUER": {"CN": CERT_ISSUER_CN}},
+ })
+ key_path = r"SOFTWARE\Policies\Google\Chrome\AutoSelectCertificateForUrls"
+ try:
+ key = winreg.CreateKey(winreg.HKEY_CURRENT_USER, key_path)
+ winreg.SetValueEx(key, "1", 0, winreg.REG_SZ, policy)
+ winreg.CloseKey(key)
+ print(f" Chrome politika nastavena (issuer: {CERT_ISSUER_CN})")
+ except Exception as e:
+ print(f" Varování: nelze nastavit Chrome politiku: {e}")
+
+
+def load_cookies(context) -> int:
+ if not os.path.exists(COOKIES_FILE):
+ return 0
+ try:
+ with open(COOKIES_FILE, "r", encoding="utf-8") as f:
+ cookies = json.load(f)
+ context.add_cookies(cookies)
+ return len(cookies)
+ except Exception as e:
+ print(f" Chyba při načítání cookies: {e}")
+ return 0
+
+
+def save_cookies(context) -> int:
+ try:
+ vzp = [c for c in context.cookies() if "vzp.cz" in c.get("domain", "")]
+ with open(COOKIES_FILE, "w", encoding="utf-8") as f:
+ json.dump(vzp, f, indent=2, ensure_ascii=False)
+ return len(vzp)
+ except Exception as e:
+ print(f" Chyba při ukládání cookies: {e}")
+ return 0
+
+
+def sanitize_filename(name: str) -> str:
+ """Odstraní znaky nepovolené ve Windows názvech souborů."""
+ return re.sub(r'[\\/:*?"<>|]', '_', name).strip()
+
+
+# True = přidat popis v závorce do názvu souboru
+SECTIONS = {
+ "Číselníky": True,
+ "Soubor platných IČP": False,
+}
+
+
+def extract_entries(page) -> list[dict]:
+ """
+ Projde tabulky sekcí definovaných v SECTIONS a vrátí seznam slovníků:
+ orig_fname — původní název souboru z atributu download
+ new_fname — nový název: YYYY-MM-DD Název [(popis)].zip
+ link — Playwright Locator pro kliknutí
+ """
+ entries = []
+ for section, include_description in SECTIONS.items():
+ rows = page.locator(f"h3:has-text('{section}') + table tbody tr").all()
+ entries.extend(_extract_rows(rows, include_description=include_description))
+ return entries
+
+
+def _extract_rows(rows: list, include_description: bool = True) -> list[dict]:
+ entries = []
+ i = 0
+ while i < len(rows):
+ row = rows[i]
+ dl_links = row.locator("a[download]").all()
+
+ if not dl_links:
+ i += 1
+ continue
+
+ # Datum platnosti z