Přidán list ED_PODANI + ED_PODANI_DATA do faktury_report.py; doplněny poznámky o eDávkách
- faktury_report.py: nový list ED_PODANI (ED_BOOKOFSUBMISSIONS) s přehledem podání pojišťovnám - faktury_report.py: nový list ED_PODANI_DATA s dekódovaným obsahem dávek (KDAVKA, REQUEST XML, odpovědi pojišťoven) - Opraveno kódování: KDAVKA=cp1250, REQUEST detekce BOM (utf-16/utf-8), SERVERRESPONSE/PROTOCOL=iso-8859-2 - Hyperlinky ED_PODANI ↔ ED_PODANI_DATA a Faktura → FAK - FakturaceADavky.md: dokumentace ED_* tabulek, portálů pojišťoven, formátů REQUEST XML - Průzkumné skripty: find_edavky_table, explore_hpn, explore_ed_bookofsubmissions, parse_trace_edavky aj. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys, io, base64, re
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||||
|
import fdb
|
||||||
|
|
||||||
|
conn = fdb.connect(
|
||||||
|
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||||
|
user='SYSDBA', password='masterkey', charset='win1250'
|
||||||
|
)
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
# Nacti poslednich 5 PORTAL zaznamu s DATA
|
||||||
|
cur.execute("""
|
||||||
|
SELECT FIRST 5 ID, ODESLANO, IDFAK, ID_PODANI, DATA
|
||||||
|
FROM PORTAL
|
||||||
|
WHERE DATA IS NOT NULL
|
||||||
|
ORDER BY ID DESC
|
||||||
|
""")
|
||||||
|
rows = cur.fetchall()
|
||||||
|
|
||||||
|
for row in rows:
|
||||||
|
pid, odeslano, idfak, id_podani, data = row
|
||||||
|
print(f"\n{'='*70}")
|
||||||
|
print(f"PORTAL.ID={pid} ODESLANO={odeslano} IDFAK={idfak} ID_PODANI={id_podani}")
|
||||||
|
|
||||||
|
if data is None:
|
||||||
|
print("DATA: NULL")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# data je string (win1250)
|
||||||
|
if isinstance(data, bytes):
|
||||||
|
data = data.decode('win1250', errors='replace')
|
||||||
|
|
||||||
|
# Parsuj XML obalku
|
||||||
|
print(f"DATA (prvnich 300 zn): {data[:300]}")
|
||||||
|
|
||||||
|
# Najdi BASE64 obsah uvnitr <Soubor ...>...</Soubor>
|
||||||
|
m = re.search(r'<Soubor[^>]*Format="BASE64"[^>]*>(.*?)</Soubor>', data, re.DOTALL)
|
||||||
|
if m:
|
||||||
|
b64_raw = m.group(1).strip()
|
||||||
|
# Dekoduj
|
||||||
|
try:
|
||||||
|
b64_clean = re.sub(r'\s+', '', b64_raw)
|
||||||
|
# Padding
|
||||||
|
b64_clean += '=' * (4 - len(b64_clean) % 4)
|
||||||
|
html_bytes = base64.b64decode(b64_clean)
|
||||||
|
html = html_bytes.decode('iso-8859-2', errors='replace')
|
||||||
|
|
||||||
|
# Extrahuj text
|
||||||
|
text = re.sub(r'<!--.*?-->', '', html, flags=re.DOTALL)
|
||||||
|
text = re.sub(r'<script[^>]*>.*?</script>', '', text, flags=re.DOTALL)
|
||||||
|
text = re.sub(r'<style[^>]*>.*?</style>', '', text, flags=re.DOTALL)
|
||||||
|
text = re.sub(r'<[^>]+>', ' ', text)
|
||||||
|
text = re.sub(r' ', ' ', text)
|
||||||
|
text = re.sub(r'&', '&', text)
|
||||||
|
text = re.sub(r'\s+', ' ', text).strip()
|
||||||
|
|
||||||
|
print(f"\nDEKÓDOVANÝ HTML protokol:")
|
||||||
|
print(text[:2000])
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Chyba dekódování: {e}")
|
||||||
|
else:
|
||||||
|
# Zkus plain text odpoved (ne BASE64)
|
||||||
|
m2 = re.search(r'<Soubor[^>]*>(.*?)</Soubor>', data, re.DOTALL)
|
||||||
|
if m2:
|
||||||
|
print(f"\nObsah Soubor (plain): {m2.group(1)[:500]}")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys, io
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||||
|
import fdb
|
||||||
|
|
||||||
|
conn = fdb.connect(
|
||||||
|
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||||
|
user='SYSDBA', password='masterkey', charset='win1250'
|
||||||
|
)
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
for table in ['ED_BOOKOFSUBMISSIONS', 'ED_BOOKOFSUBMISSIONATTACH', 'ED_MAILBOXMESSAGE', 'ED_STORAGE']:
|
||||||
|
print(f"\n{'='*70}")
|
||||||
|
print(f"TABULKA: {table}")
|
||||||
|
print('='*70)
|
||||||
|
|
||||||
|
cur.execute(f"""
|
||||||
|
SELECT rf.RDB$FIELD_NAME
|
||||||
|
FROM RDB$RELATION_FIELDS rf
|
||||||
|
WHERE rf.RDB$RELATION_NAME = '{table}'
|
||||||
|
ORDER BY rf.RDB$FIELD_POSITION
|
||||||
|
""")
|
||||||
|
cols = [r[0].strip() for r in cur.fetchall()]
|
||||||
|
print(f"Sloupce: {cols}")
|
||||||
|
|
||||||
|
cur.execute(f"SELECT COUNT(*) FROM {table}")
|
||||||
|
print(f"Počet: {cur.fetchone()[0]}")
|
||||||
|
|
||||||
|
# BLOB sloupce vynech
|
||||||
|
cur.execute(f"""
|
||||||
|
SELECT rf.RDB$FIELD_NAME
|
||||||
|
FROM RDB$RELATION_FIELDS rf
|
||||||
|
JOIN RDB$FIELDS f ON f.RDB$FIELD_NAME = rf.RDB$FIELD_SOURCE
|
||||||
|
WHERE rf.RDB$RELATION_NAME = '{table}'
|
||||||
|
AND f.RDB$FIELD_TYPE = 261
|
||||||
|
""")
|
||||||
|
blob_cols = {r[0].strip() for r in cur.fetchall()}
|
||||||
|
safe = [c for c in cols if c not in blob_cols]
|
||||||
|
|
||||||
|
# Najdi razeni
|
||||||
|
order_col = next((c for c in ['CREATEDATE','SENTDATE','CREATED','ODESLANO','DATUM','ID'] if c in cols), cols[0])
|
||||||
|
|
||||||
|
try:
|
||||||
|
cur.execute(f"SELECT FIRST 10 {', '.join(safe)} FROM {table} ORDER BY {order_col} DESC")
|
||||||
|
rows = cur.fetchall()
|
||||||
|
print(f"\nPosledních 10 (ORDER BY {order_col} DESC):")
|
||||||
|
for row in rows:
|
||||||
|
print(f" {dict(zip(safe, row))}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Chyba SELECT: {e}")
|
||||||
|
# Zkus bez order
|
||||||
|
try:
|
||||||
|
cur.execute(f"SELECT FIRST 5 {', '.join(safe)} FROM {table}")
|
||||||
|
rows = cur.fetchall()
|
||||||
|
for row in rows:
|
||||||
|
print(f" {dict(zip(safe, row))}")
|
||||||
|
except Exception as e2:
|
||||||
|
print(f"Chyba i bez ORDER: {e2}")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
print("\nHotovo.")
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys, io
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||||
|
import fdb
|
||||||
|
|
||||||
|
conn = fdb.connect(
|
||||||
|
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||||
|
user='SYSDBA', password='masterkey', charset='win1250'
|
||||||
|
)
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("1. Sloupce HPN")
|
||||||
|
print("=" * 60)
|
||||||
|
cur.execute("""
|
||||||
|
SELECT rf.RDB$FIELD_NAME
|
||||||
|
FROM RDB$RELATION_FIELDS rf
|
||||||
|
WHERE rf.RDB$RELATION_NAME = 'HPN'
|
||||||
|
ORDER BY rf.RDB$FIELD_POSITION
|
||||||
|
""")
|
||||||
|
col_names = [r[0].strip() for r in cur.fetchall()]
|
||||||
|
print(f" Sloupce: {col_names}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print("2. Počet záznamů a max datum")
|
||||||
|
print("=" * 60)
|
||||||
|
cur.execute("SELECT COUNT(*) FROM HPN")
|
||||||
|
print(f" Celkem: {cur.fetchone()[0]} záznamů")
|
||||||
|
|
||||||
|
# Najdi datumove sloupce
|
||||||
|
date_candidates = [c for c in col_names if any(x in c for x in ['DAT', 'ODE', 'VYT', 'CAS'])]
|
||||||
|
print(f" Datumové sloupce: {date_candidates}")
|
||||||
|
for dc in date_candidates[:3]:
|
||||||
|
try:
|
||||||
|
cur.execute(f"SELECT MIN({dc}), MAX({dc}) FROM HPN")
|
||||||
|
mn, mx = cur.fetchone()
|
||||||
|
print(f" {dc}: {mn} .. {mx}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" {dc}: {e}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print("3. Posledních 20 záznamů (bez BLOBů)")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Zjisti BLOB sloupce
|
||||||
|
cur.execute("""
|
||||||
|
SELECT rf.RDB$FIELD_NAME
|
||||||
|
FROM RDB$RELATION_FIELDS rf
|
||||||
|
JOIN RDB$FIELDS f ON f.RDB$FIELD_NAME = rf.RDB$FIELD_SOURCE
|
||||||
|
WHERE rf.RDB$RELATION_NAME = 'HPN'
|
||||||
|
AND f.RDB$FIELD_TYPE = 261
|
||||||
|
""")
|
||||||
|
blob_cols = {r[0].strip() for r in cur.fetchall()}
|
||||||
|
print(f" BLOB sloupce: {blob_cols}")
|
||||||
|
|
||||||
|
safe_cols = [c for c in col_names if c not in blob_cols]
|
||||||
|
|
||||||
|
# Zjisti razeni - zkus VYTVORENO nebo ODESLANO nebo ID
|
||||||
|
order_col = next((c for c in ['VYTVORENO', 'ODESLANO', 'DATUM', 'ID'] if c in col_names), col_names[0])
|
||||||
|
|
||||||
|
cur.execute(f"SELECT FIRST 20 {', '.join(safe_cols)} FROM HPN ORDER BY {order_col} DESC")
|
||||||
|
rows = cur.fetchall()
|
||||||
|
for row in rows:
|
||||||
|
d = dict(zip(safe_cols, row))
|
||||||
|
print(f" {d}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print("4. Záznamy od 2026-02-01")
|
||||||
|
print("=" * 60)
|
||||||
|
for dc in date_candidates[:2]:
|
||||||
|
try:
|
||||||
|
cur.execute(f"SELECT COUNT(*) FROM HPN WHERE {dc} >= '2026-02-01'")
|
||||||
|
cnt = cur.fetchone()[0]
|
||||||
|
if cnt > 0:
|
||||||
|
print(f" {dc} >= 2026-02-01: {cnt} záznamů")
|
||||||
|
cur.execute(f"SELECT FIRST 5 {', '.join(safe_cols)} FROM HPN WHERE {dc} >= '2026-02-01' ORDER BY {dc} DESC")
|
||||||
|
for row in cur.fetchall():
|
||||||
|
print(f" {dict(zip(safe_cols, row))}")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print(f" {dc}: {e}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print("5. HPN_NOTIFIKACE_DETAIL – sloupce a ukazka")
|
||||||
|
print("=" * 60)
|
||||||
|
cur.execute("""
|
||||||
|
SELECT rf.RDB$FIELD_NAME
|
||||||
|
FROM RDB$RELATION_FIELDS rf
|
||||||
|
WHERE rf.RDB$RELATION_NAME = 'HPN_NOTIFIKACE_DETAIL'
|
||||||
|
ORDER BY rf.RDB$FIELD_POSITION
|
||||||
|
""")
|
||||||
|
hnd_cols = [r[0].strip() for r in cur.fetchall()]
|
||||||
|
print(f" Sloupce: {hnd_cols}")
|
||||||
|
cur.execute("SELECT COUNT(*) FROM HPN_NOTIFIKACE_DETAIL")
|
||||||
|
print(f" Záznamy: {cur.fetchone()[0]}")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
print("\nHotovo.")
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys, io
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||||
|
import fdb
|
||||||
|
|
||||||
|
conn = fdb.connect(
|
||||||
|
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||||
|
user='SYSDBA', password='masterkey', charset='win1250'
|
||||||
|
)
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("1. Sloupce HPN_PODANI")
|
||||||
|
print("=" * 60)
|
||||||
|
cur.execute("""
|
||||||
|
SELECT rf.RDB$FIELD_NAME, rf.RDB$FIELD_POSITION
|
||||||
|
FROM RDB$RELATION_FIELDS rf
|
||||||
|
WHERE rf.RDB$RELATION_NAME = 'HPN_PODANI'
|
||||||
|
ORDER BY rf.RDB$FIELD_POSITION
|
||||||
|
""")
|
||||||
|
col_names = [r[0].strip() for r in cur.fetchall()]
|
||||||
|
print(f" Sloupce: {col_names}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print("2. Ukazka HPN_PODANI (bez BLOB ODPOVED)")
|
||||||
|
print("=" * 60)
|
||||||
|
safe_cols = [c for c in col_names if c != 'ODPOVED']
|
||||||
|
cur.execute(f"SELECT FIRST 10 {', '.join(safe_cols)} FROM HPN_PODANI ORDER BY ODESLANO DESC")
|
||||||
|
rows = cur.fetchall()
|
||||||
|
for row in rows:
|
||||||
|
print(dict(zip(safe_cols, row)))
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print("3. Hledej tabulky s POJ nebo ZP sloupcem + data od 2026-02")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Najdi vsechny tabulky ktere maji sloupec POJ nebo ZP
|
||||||
|
cur.execute("""
|
||||||
|
SELECT DISTINCT rf.RDB$RELATION_NAME
|
||||||
|
FROM RDB$RELATION_FIELDS rf
|
||||||
|
JOIN RDB$RELATIONS r ON r.RDB$RELATION_NAME = rf.RDB$RELATION_NAME
|
||||||
|
WHERE r.RDB$SYSTEM_FLAG = 0
|
||||||
|
AND (TRIM(rf.RDB$FIELD_NAME) = 'POJ' OR TRIM(rf.RDB$FIELD_NAME) = 'ZP')
|
||||||
|
ORDER BY rf.RDB$RELATION_NAME
|
||||||
|
""")
|
||||||
|
poj_tables = [r[0].strip() for r in cur.fetchall()]
|
||||||
|
print(f" Tabulky s POJ/ZP: {len(poj_tables)}")
|
||||||
|
|
||||||
|
# Z techto tabulek vyber ty ktere maji ODESLANO nebo DATUM a data od 2026-02
|
||||||
|
hits = []
|
||||||
|
for table in poj_tables:
|
||||||
|
cur.execute(f"""
|
||||||
|
SELECT rf.RDB$FIELD_NAME
|
||||||
|
FROM RDB$RELATION_FIELDS rf
|
||||||
|
WHERE rf.RDB$RELATION_NAME = '{table}'
|
||||||
|
AND (TRIM(rf.RDB$FIELD_NAME) LIKE '%ODESLAN%'
|
||||||
|
OR TRIM(rf.RDB$FIELD_NAME) LIKE '%VYTVOR%'
|
||||||
|
OR TRIM(rf.RDB$FIELD_NAME) = 'DATUM')
|
||||||
|
""")
|
||||||
|
date_cols = [r[0].strip() for r in cur.fetchall()]
|
||||||
|
for dc in date_cols:
|
||||||
|
try:
|
||||||
|
cur.execute(f"SELECT COUNT(*) FROM {table} WHERE {dc} >= '2026-02-01'")
|
||||||
|
cnt = cur.fetchone()[0]
|
||||||
|
if cnt > 0:
|
||||||
|
hits.append((table, dc, cnt))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
hits.sort(key=lambda x: -x[2])
|
||||||
|
for table, dc, cnt in hits:
|
||||||
|
print(f" {table:<40} {dc}: {cnt} od 2026-02")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print("4. Hledej tabulky s PODACI_CISLO nebo PODAC nebo ID_PODANI")
|
||||||
|
print("=" * 60)
|
||||||
|
cur.execute("""
|
||||||
|
SELECT DISTINCT rf.RDB$RELATION_NAME, rf.RDB$FIELD_NAME
|
||||||
|
FROM RDB$RELATION_FIELDS rf
|
||||||
|
JOIN RDB$RELATIONS r ON r.RDB$RELATION_NAME = rf.RDB$RELATION_NAME
|
||||||
|
WHERE r.RDB$SYSTEM_FLAG = 0
|
||||||
|
AND (TRIM(rf.RDB$FIELD_NAME) LIKE '%PODACI%'
|
||||||
|
OR TRIM(rf.RDB$FIELD_NAME) = 'ID_PODANI'
|
||||||
|
OR TRIM(rf.RDB$FIELD_NAME) LIKE '%PODANI%')
|
||||||
|
ORDER BY rf.RDB$RELATION_NAME
|
||||||
|
""")
|
||||||
|
for r in cur.fetchall():
|
||||||
|
print(f" {r[0].strip():<40} sloupec: {r[1].strip()}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print("5. PORTAL - co tam chybi od 2026-02?")
|
||||||
|
print("=" * 60)
|
||||||
|
cur.execute("""
|
||||||
|
SELECT FIRST 5 ID, IDFAK, ODESLANO, STAV, ID_PODANI, DAVKA_ROK, BB_DAVKA, BB_FAKTURA
|
||||||
|
FROM PORTAL
|
||||||
|
ORDER BY ODESLANO DESC
|
||||||
|
""")
|
||||||
|
for row in cur.fetchall():
|
||||||
|
print(f" {row}")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
print("\nHotovo.")
|
||||||
@@ -262,6 +262,224 @@ for i, row in enumerate(fak_rows, start=2):
|
|||||||
else:
|
else:
|
||||||
cell.value = ''
|
cell.value = ''
|
||||||
|
|
||||||
|
# =====================
|
||||||
|
# List 5 – ED_PODANI (ED_BOOKOFSUBMISSIONS)
|
||||||
|
# =====================
|
||||||
|
|
||||||
|
ws5 = wb.create_sheet('ED_PODANI')
|
||||||
|
|
||||||
|
REQUESTTYPE_MAP = {0: 'Registrační', 1: 'Výkonová'}
|
||||||
|
HICCODE_MAP = {
|
||||||
|
'111': 'VZP', '201': 'VoZP', '205': 'ČPZP',
|
||||||
|
'207': 'OZP', '209': 'ZPŠ', '211': 'ZPMV',
|
||||||
|
'212': '212', '213': '213', '217': '217', '228': '228', '333': '333',
|
||||||
|
}
|
||||||
|
|
||||||
|
cur.execute('''
|
||||||
|
SELECT
|
||||||
|
ID, CREATED, SENTDATE, HICCODE, REQUESTTYPE,
|
||||||
|
SUBMISSIONID, INVOICENUMBER,
|
||||||
|
PERIODFROM, PERIODTO, TOTALSUM,
|
||||||
|
STATE, CREATOR, HCPPERSONNAME, HCPCODE, UNIQUEID
|
||||||
|
FROM ED_BOOKOFSUBMISSIONS
|
||||||
|
ORDER BY ID DESC
|
||||||
|
''')
|
||||||
|
ed_cols_raw = [d[0] for d in cur.description]
|
||||||
|
ed_rows = cur.fetchall()
|
||||||
|
|
||||||
|
# Lidštější záhlaví
|
||||||
|
ed_headers = [
|
||||||
|
'ID', 'Vytvořeno', 'Odesláno', 'ZP', 'Typ podání',
|
||||||
|
'Podací č.', 'Faktura',
|
||||||
|
'Období od', 'Období do', 'Částka (Kč)',
|
||||||
|
'Stav', 'Autor', 'Lékař', 'IČZ', 'UUID',
|
||||||
|
]
|
||||||
|
ws5.append(ed_headers)
|
||||||
|
|
||||||
|
# Mapa CISFAK → řádek na listu FAK (pro hyperlink)
|
||||||
|
cisfak_to_fak_row = {}
|
||||||
|
for i, row in enumerate(fak_rows, start=2):
|
||||||
|
cisfak = row[1] # CISFAK je druhý sloupec z DB (index 1)
|
||||||
|
if cisfak and cisfak not in cisfak_to_fak_row:
|
||||||
|
cisfak_to_fak_row[cisfak] = i
|
||||||
|
|
||||||
|
for i, row in enumerate(ed_rows, start=2):
|
||||||
|
(eid, created, sentdate, hiccode, reqtype,
|
||||||
|
submid, invoicenum,
|
||||||
|
perfrom, perto, totalsum,
|
||||||
|
state, creator, hcpperson, hcpcode, uniqueid) = row
|
||||||
|
|
||||||
|
out = [
|
||||||
|
eid,
|
||||||
|
fmt(created),
|
||||||
|
fmt(sentdate),
|
||||||
|
f"{hiccode} {HICCODE_MAP.get(str(hiccode), '')}" if hiccode else '',
|
||||||
|
REQUESTTYPE_MAP.get(reqtype, str(reqtype)) if reqtype is not None else '',
|
||||||
|
fmt(submid),
|
||||||
|
fmt(invoicenum),
|
||||||
|
fmt(perfrom),
|
||||||
|
fmt(perto),
|
||||||
|
float(totalsum) if totalsum is not None else '',
|
||||||
|
fmt(state),
|
||||||
|
fmt(creator),
|
||||||
|
fmt(hcpperson),
|
||||||
|
fmt(hcpcode),
|
||||||
|
fmt(uniqueid),
|
||||||
|
]
|
||||||
|
ws5.append(out)
|
||||||
|
if i % 2 == 0:
|
||||||
|
for cell in ws5[i]:
|
||||||
|
cell.fill = ZEBRA_FILL
|
||||||
|
|
||||||
|
style_header(ws5)
|
||||||
|
ws5.freeze_panes = 'A2'
|
||||||
|
autofit(ws5)
|
||||||
|
|
||||||
|
# Hyperlink: Faktura (sloupec 7) → list FAK
|
||||||
|
for i, row in enumerate(ed_rows, start=2):
|
||||||
|
invoicenum = row[6] # INVOICENUMBER
|
||||||
|
if invoicenum and invoicenum in cisfak_to_fak_row:
|
||||||
|
cell = ws5.cell(row=i, column=7)
|
||||||
|
# +1 kvůli vloženému sloupci FAKDET na listu FAK
|
||||||
|
cell.hyperlink = f'#FAK!B{cisfak_to_fak_row[invoicenum]}'
|
||||||
|
cell.font = LINK_FONT
|
||||||
|
|
||||||
|
# =====================
|
||||||
|
# List 6 – ED_PODANI_DATA (BLOBy z ED_BOOKOFSUBMISSIONS)
|
||||||
|
# =====================
|
||||||
|
|
||||||
|
ws6 = wb.create_sheet('ED_PODANI_DATA')
|
||||||
|
|
||||||
|
cur.execute('''
|
||||||
|
SELECT ID, HICCODE, SENTDATE, SUBMISSIONID, INVOICENUMBER,
|
||||||
|
KDAVKACONTENT, REQUEST, SERVERRESPONSE, PROTOCOL
|
||||||
|
FROM ED_BOOKOFSUBMISSIONS
|
||||||
|
ORDER BY ID DESC
|
||||||
|
''')
|
||||||
|
ed_data_rows = cur.fetchall()
|
||||||
|
|
||||||
|
ed_data_headers = ['ID', 'ZP', 'Odesláno', 'Podací č.', 'Faktura',
|
||||||
|
'KDAVKA', 'REQUEST (XML)', 'SERVERRESPONSE', 'PROTOCOL']
|
||||||
|
ws6.append(ed_data_headers)
|
||||||
|
|
||||||
|
def decode_ed_blob(v, enc):
|
||||||
|
"""Dekóduj BLOB z ED_BOOKOFSUBMISSIONS."""
|
||||||
|
if v is None:
|
||||||
|
return ''
|
||||||
|
if hasattr(v, 'read'):
|
||||||
|
raw = v.read()
|
||||||
|
else:
|
||||||
|
raw = v
|
||||||
|
if not raw:
|
||||||
|
return ''
|
||||||
|
if isinstance(raw, str):
|
||||||
|
# fdb vrátil jako win1250 string – zrekonstruuj bytes
|
||||||
|
raw = raw.encode('cp1250', errors='replace')
|
||||||
|
try:
|
||||||
|
text = raw.decode(enc, errors='replace')
|
||||||
|
except Exception:
|
||||||
|
return repr(raw[:200])
|
||||||
|
# Odstraň prázdné řádky a sjednoť odřádkování
|
||||||
|
lines = [l for l in text.splitlines() if l.strip()]
|
||||||
|
return '\n'.join(lines)
|
||||||
|
|
||||||
|
for i, row in enumerate(ed_data_rows, start=2):
|
||||||
|
eid, hiccode, sentdate, submid, invoicenum, kdavka, request, serverresp, protocol = row
|
||||||
|
|
||||||
|
# KDAVKACONTENT – fdb vrací str přes win1250 spojení, použij přímo
|
||||||
|
if kdavka is None:
|
||||||
|
kdavka_txt = ''
|
||||||
|
else:
|
||||||
|
raw = kdavka.read() if hasattr(kdavka, 'read') else kdavka
|
||||||
|
if isinstance(raw, bytes):
|
||||||
|
raw = raw.encode('latin-1', errors='replace').decode('cp852', errors='replace')
|
||||||
|
lines = [l for l in raw.splitlines() if l.strip()]
|
||||||
|
kdavka_txt = '\n'.join(lines)
|
||||||
|
|
||||||
|
# REQUEST – XML; může být uložen jako UTF-16 binary nebo jako ASCII/UTF-8 string
|
||||||
|
request_txt = ''
|
||||||
|
if request is not None:
|
||||||
|
raw = request.read() if hasattr(request, 'read') else request
|
||||||
|
if isinstance(raw, str):
|
||||||
|
raw_b = raw.encode('latin-1', errors='replace')
|
||||||
|
else:
|
||||||
|
raw_b = raw
|
||||||
|
if raw_b:
|
||||||
|
# Detekce podle BOM – jedině tehdy jde o skutečné UTF-16 binární data
|
||||||
|
if raw_b[:2] in (b'\xff\xfe', b'\xfe\xff'):
|
||||||
|
request_txt = raw_b.decode('utf-16', errors='replace')
|
||||||
|
elif raw_b[:1] == b'<':
|
||||||
|
# ASCII/UTF-8 XML – fdb ho vrátil jako string, použij přímo
|
||||||
|
request_txt = raw_b.decode('utf-8', errors='replace')
|
||||||
|
else:
|
||||||
|
request_txt = raw_b.decode('cp1250', errors='replace')
|
||||||
|
lines = [l for l in request_txt.splitlines() if l.strip()]
|
||||||
|
request_txt = '\n'.join(lines)
|
||||||
|
|
||||||
|
# SERVERRESPONSE a PROTOCOL – latin-1 re-encoding, pak iso-8859-2
|
||||||
|
def decode_latin_blob(v, enc):
|
||||||
|
if v is None:
|
||||||
|
return ''
|
||||||
|
raw = v.read() if hasattr(v, 'read') else v
|
||||||
|
if not raw:
|
||||||
|
return ''
|
||||||
|
if isinstance(raw, str):
|
||||||
|
raw = raw.encode('latin-1', errors='replace')
|
||||||
|
text = raw.decode(enc, errors='replace')
|
||||||
|
lines = [l for l in text.splitlines() if l.strip()]
|
||||||
|
return '\n'.join(lines)
|
||||||
|
|
||||||
|
serverresp_txt = decode_latin_blob(serverresp, 'iso-8859-2')
|
||||||
|
protocol_txt = decode_latin_blob(protocol, 'iso-8859-2')
|
||||||
|
|
||||||
|
out = [
|
||||||
|
eid,
|
||||||
|
f"{hiccode} {HICCODE_MAP.get(str(hiccode), '')}" if hiccode else '',
|
||||||
|
fmt(sentdate),
|
||||||
|
fmt(submid),
|
||||||
|
fmt(invoicenum),
|
||||||
|
kdavka_txt,
|
||||||
|
request_txt,
|
||||||
|
serverresp_txt,
|
||||||
|
protocol_txt,
|
||||||
|
]
|
||||||
|
ws6.append(out)
|
||||||
|
if i % 2 == 0:
|
||||||
|
for cell in ws6[i]:
|
||||||
|
cell.fill = ZEBRA_FILL
|
||||||
|
for cell in ws6[i]:
|
||||||
|
cell.alignment = WRAP
|
||||||
|
ws6.row_dimensions[i].height = 80
|
||||||
|
|
||||||
|
style_header(ws6)
|
||||||
|
ws6.freeze_panes = 'A2'
|
||||||
|
|
||||||
|
# Šířky sloupců ED_PODANI_DATA
|
||||||
|
for col, width in zip(['A','B','C','D','E','F','G','H','I'],
|
||||||
|
[6, 10, 12, 16, 14, 80, 60, 60, 40]):
|
||||||
|
ws6.column_dimensions[col].width = width
|
||||||
|
|
||||||
|
# Hyperlinky ED_PODANI ↔ ED_PODANI_DATA (přes ID)
|
||||||
|
ed_id_to_ws5_row = {row[0]: i for i, row in enumerate(ed_rows, start=2)}
|
||||||
|
ed_id_to_ws6_row = {row[0]: i for i, row in enumerate(ed_data_rows, start=2)}
|
||||||
|
|
||||||
|
# ED_PODANI sloupec 1 (ID) → ED_PODANI_DATA
|
||||||
|
for i, row in enumerate(ed_rows, start=2):
|
||||||
|
eid = row[0]
|
||||||
|
if eid in ed_id_to_ws6_row:
|
||||||
|
cell = ws5.cell(row=i, column=1)
|
||||||
|
cell.hyperlink = f'#ED_PODANI_DATA!A{ed_id_to_ws6_row[eid]}'
|
||||||
|
cell.font = LINK_FONT
|
||||||
|
|
||||||
|
# ED_PODANI_DATA sloupec 1 (ID) → ED_PODANI
|
||||||
|
for i, row in enumerate(ed_data_rows, start=2):
|
||||||
|
eid = row[0]
|
||||||
|
if eid in ed_id_to_ws5_row:
|
||||||
|
cell = ws6.cell(row=i, column=1)
|
||||||
|
cell.hyperlink = f'#ED_PODANI!A{ed_id_to_ws5_row[eid]}'
|
||||||
|
cell.font = LINK_FONT
|
||||||
|
cell.alignment = WRAP
|
||||||
|
|
||||||
# =====================
|
# =====================
|
||||||
# Uložení
|
# Uložení
|
||||||
# =====================
|
# =====================
|
||||||
@@ -269,4 +487,4 @@ for i, row in enumerate(fak_rows, start=2):
|
|||||||
conn.close()
|
conn.close()
|
||||||
wb.save(output_path)
|
wb.save(output_path)
|
||||||
sys.stdout.buffer.write(f'Ulozeno: {output_path}\n'.encode('utf-8'))
|
sys.stdout.buffer.write(f'Ulozeno: {output_path}\n'.encode('utf-8'))
|
||||||
sys.stdout.buffer.write(f'FAK: {len(fak_rows)} radku, FAKDET: {len(det_rows)} radku, PORTAL: {len(portal_rows)} radku\n'.encode('utf-8'))
|
sys.stdout.buffer.write(f'FAK: {len(fak_rows)} radku, FAKDET: {len(det_rows)} radku, PORTAL: {len(portal_rows)} radku, ED_PODANI: {len(ed_rows)} radku\n'.encode('utf-8'))
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys, io
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||||
|
import fdb
|
||||||
|
|
||||||
|
conn = fdb.connect(
|
||||||
|
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||||
|
user='SYSDBA', password='masterkey', charset='win1250'
|
||||||
|
)
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
# Ze screenshotu vime:
|
||||||
|
# - sloupce: Vytvoreno, Odeslano, ZP, Stav, Zpravy, Podaci c., Faktura
|
||||||
|
# - ZP: 111, 201, 205, 207, 209, 211
|
||||||
|
# - Podaci c.: 58933293, 174804160, 26082877, D01F260218...
|
||||||
|
# - datum: 01.03.2026, 05.03.2026, 12.03.2026, 23.03.2026, 24.03.2026
|
||||||
|
# - typ: "Reg. listy" (bez faktury), vykonove davky (s fakturou)
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("1. EOCK_DAVKA - sloupce a ukazka")
|
||||||
|
print("=" * 60)
|
||||||
|
cur.execute("""
|
||||||
|
SELECT rf.RDB$FIELD_NAME
|
||||||
|
FROM RDB$RELATION_FIELDS rf
|
||||||
|
WHERE rf.RDB$RELATION_NAME = 'EOCK_DAVKA'
|
||||||
|
ORDER BY rf.RDB$FIELD_POSITION
|
||||||
|
""")
|
||||||
|
eock_cols = [r[0].strip() for r in cur.fetchall()]
|
||||||
|
print(f" Sloupce: {eock_cols}")
|
||||||
|
cur.execute("SELECT COUNT(*) FROM EOCK_DAVKA")
|
||||||
|
print(f" Pocet: {cur.fetchone()[0]}")
|
||||||
|
safe = [c for c in eock_cols if c not in ('DATA', 'DAVKA', 'ODPOVED')]
|
||||||
|
if safe:
|
||||||
|
cur.execute(f"SELECT FIRST 3 {', '.join(safe)} FROM EOCK_DAVKA ORDER BY ID DESC")
|
||||||
|
for r in cur.fetchall():
|
||||||
|
print(f" {dict(zip(safe, r))}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print("2. FAK - zaznamy z brezna 2026")
|
||||||
|
print("=" * 60)
|
||||||
|
cur.execute("""
|
||||||
|
SELECT ID, CISFAK, POJ, DATVYS, DATODE, OBDOB, CENA, DRUH, STAV
|
||||||
|
FROM FAK
|
||||||
|
WHERE DATVYS >= '2026-03-01' OR DATODE >= '2026-03-01'
|
||||||
|
ORDER BY ID DESC
|
||||||
|
""")
|
||||||
|
rows = cur.fetchall()
|
||||||
|
print(f" Celkem: {len(rows)}")
|
||||||
|
for r in rows:
|
||||||
|
print(f" ID={r[0]} CISFAK={r[1]} POJ={r[2]} DATVYS={r[3]} DATODE={r[4]} OBDOB={r[5]} CENA={r[6]} DRUH={r[7]} STAV={r[8]}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print("3. Hledej tabulky ktere maji sloupec VYTVORENO nebo PODACI")
|
||||||
|
print("=" * 60)
|
||||||
|
cur.execute("""
|
||||||
|
SELECT DISTINCT rf.RDB$RELATION_NAME, rf.RDB$FIELD_NAME
|
||||||
|
FROM RDB$RELATION_FIELDS rf
|
||||||
|
JOIN RDB$RELATIONS r ON r.RDB$RELATION_NAME = rf.RDB$RELATION_NAME
|
||||||
|
WHERE r.RDB$SYSTEM_FLAG = 0
|
||||||
|
AND (TRIM(rf.RDB$FIELD_NAME) LIKE '%VYTVOR%'
|
||||||
|
OR TRIM(rf.RDB$FIELD_NAME) LIKE '%PODACI%')
|
||||||
|
ORDER BY rf.RDB$RELATION_NAME
|
||||||
|
""")
|
||||||
|
for r in cur.fetchall():
|
||||||
|
print(f" {r[0].strip():<40} {r[1].strip()}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print("4. Hledej tabulky ktere maji POJ sloupec a zaznamy z brezna 2026")
|
||||||
|
print("=" * 60)
|
||||||
|
# Vsechny tabulky s POJ sloupcem
|
||||||
|
cur.execute("""
|
||||||
|
SELECT DISTINCT rf.RDB$RELATION_NAME
|
||||||
|
FROM RDB$RELATION_FIELDS rf
|
||||||
|
JOIN RDB$RELATIONS r ON r.RDB$RELATION_NAME = rf.RDB$RELATION_NAME
|
||||||
|
WHERE r.RDB$SYSTEM_FLAG = 0
|
||||||
|
AND TRIM(rf.RDB$FIELD_NAME) = 'POJ'
|
||||||
|
ORDER BY rf.RDB$RELATION_NAME
|
||||||
|
""")
|
||||||
|
poj_tables = [r[0].strip() for r in cur.fetchall()]
|
||||||
|
|
||||||
|
for table in poj_tables:
|
||||||
|
# Najdi vsechny datumove sloupce
|
||||||
|
cur.execute(f"""
|
||||||
|
SELECT rf.RDB$FIELD_NAME
|
||||||
|
FROM RDB$RELATION_FIELDS rf
|
||||||
|
WHERE rf.RDB$RELATION_NAME = '{table}'
|
||||||
|
AND TRIM(rf.RDB$FIELD_NAME) LIKE '%DAT%'
|
||||||
|
""")
|
||||||
|
date_cols = [r[0].strip() for r in cur.fetchall()]
|
||||||
|
for dc in date_cols:
|
||||||
|
try:
|
||||||
|
cur.execute(f"SELECT COUNT(*) FROM {table} WHERE {dc} >= '2026-03-01'")
|
||||||
|
cnt = cur.fetchone()[0]
|
||||||
|
if cnt > 0:
|
||||||
|
print(f" {table:<40} {dc}: {cnt} od 2026-03-01")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print("5. Prohledej vsechny tabulky - ktere maji ~15 zaznamu od 2026-03-01")
|
||||||
|
print(" (v eDavky screenshotu je ~15 radku z brezna)")
|
||||||
|
print("=" * 60)
|
||||||
|
cur.execute("""
|
||||||
|
SELECT RDB$RELATION_NAME FROM RDB$RELATIONS
|
||||||
|
WHERE RDB$SYSTEM_FLAG = 0
|
||||||
|
ORDER BY RDB$RELATION_NAME
|
||||||
|
""")
|
||||||
|
all_tables = [r[0].strip() for r in cur.fetchall()]
|
||||||
|
|
||||||
|
# Hledej tabulky s datumovym sloupcem a 10-50 zaznamy od 2026-03
|
||||||
|
hits = []
|
||||||
|
for table in all_tables:
|
||||||
|
cur.execute(f"""
|
||||||
|
SELECT rf.RDB$FIELD_NAME
|
||||||
|
FROM RDB$RELATION_FIELDS rf
|
||||||
|
WHERE rf.RDB$RELATION_NAME = '{table}'
|
||||||
|
AND (TRIM(rf.RDB$FIELD_NAME) IN ('ODESLANO', 'VYTVORENO', 'DATODE', 'DATVYS', 'DATUM_ODESLANI'))
|
||||||
|
""")
|
||||||
|
date_cols = [r[0].strip() for r in cur.fetchall()]
|
||||||
|
for dc in date_cols:
|
||||||
|
try:
|
||||||
|
cur.execute(f"SELECT COUNT(*) FROM {table} WHERE {dc} >= '2026-03-01'")
|
||||||
|
cnt = cur.fetchone()[0]
|
||||||
|
if 5 <= cnt <= 100:
|
||||||
|
hits.append((table, dc, cnt))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
hits.sort(key=lambda x: x[2])
|
||||||
|
for table, dc, cnt in hits:
|
||||||
|
# Vypis sloupce teto tabulky
|
||||||
|
cur.execute(f"""
|
||||||
|
SELECT rf.RDB$FIELD_NAME
|
||||||
|
FROM RDB$RELATION_FIELDS rf
|
||||||
|
WHERE rf.RDB$RELATION_NAME = '{table}'
|
||||||
|
ORDER BY rf.RDB$FIELD_POSITION
|
||||||
|
""")
|
||||||
|
cols = [r[0].strip() for r in cur.fetchall()]
|
||||||
|
print(f" {table:<40} {dc}: {cnt} | sloupce: {cols[:10]}")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
print("\nHotovo.")
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
Hledani tabulky pro eDavky / Kniha podani v Medicus DB.
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import io
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||||
|
import fdb
|
||||||
|
|
||||||
|
conn = fdb.connect(
|
||||||
|
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||||
|
user='SYSDBA', password='masterkey', charset='win1250'
|
||||||
|
)
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("1. Tabulky s relevantními názvy")
|
||||||
|
print("=" * 60)
|
||||||
|
cur.execute("""
|
||||||
|
SELECT RDB$RELATION_NAME
|
||||||
|
FROM RDB$RELATIONS
|
||||||
|
WHERE RDB$SYSTEM_FLAG = 0
|
||||||
|
AND (
|
||||||
|
TRIM(RDB$RELATION_NAME) LIKE '%DAV%'
|
||||||
|
OR TRIM(RDB$RELATION_NAME) LIKE '%PORTAL%'
|
||||||
|
OR TRIM(RDB$RELATION_NAME) LIKE '%PODANI%'
|
||||||
|
OR TRIM(RDB$RELATION_NAME) LIKE '%EDAVK%'
|
||||||
|
OR TRIM(RDB$RELATION_NAME) LIKE '%KNIHA%'
|
||||||
|
OR TRIM(RDB$RELATION_NAME) LIKE '%PODAC%'
|
||||||
|
OR TRIM(RDB$RELATION_NAME) LIKE '%ELEK%'
|
||||||
|
)
|
||||||
|
ORDER BY RDB$RELATION_NAME
|
||||||
|
""")
|
||||||
|
tables = [row[0].strip() for row in cur.fetchall()]
|
||||||
|
for t in tables:
|
||||||
|
print(f" {t}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print("2. Počty záznamů a max datum v relevantních tabulkách")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
for table in tables:
|
||||||
|
try:
|
||||||
|
# Zkus najít datumový sloupec
|
||||||
|
cur.execute(f"""
|
||||||
|
SELECT RDB$FIELD_NAME FROM RDB$RELATION_FIELDS
|
||||||
|
WHERE RDB$RELATION_NAME = '{table}'
|
||||||
|
ORDER BY RDB$FIELD_POSITION
|
||||||
|
""")
|
||||||
|
cols = [r[0].strip() for r in cur.fetchall()]
|
||||||
|
|
||||||
|
cur.execute(f"SELECT COUNT(*) FROM {table}")
|
||||||
|
count = cur.fetchone()[0]
|
||||||
|
|
||||||
|
# Najdi datum sloupec
|
||||||
|
date_col = None
|
||||||
|
for c in cols:
|
||||||
|
if any(x in c for x in ['DAT', 'ODE', 'VYT', 'CAS', 'TIME']):
|
||||||
|
date_col = c
|
||||||
|
break
|
||||||
|
|
||||||
|
if date_col:
|
||||||
|
try:
|
||||||
|
cur.execute(f"SELECT MAX({date_col}) FROM {table}")
|
||||||
|
max_date = cur.fetchone()[0]
|
||||||
|
print(f" {table:<30} {count:>6} záznamů max {date_col}={max_date}")
|
||||||
|
except Exception:
|
||||||
|
print(f" {table:<30} {count:>6} záznamů cols: {cols[:5]}")
|
||||||
|
else:
|
||||||
|
print(f" {table:<30} {count:>6} záznamů cols: {cols[:5]}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" {table:<30} chyba: {e}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print("3. Aktuální stav PORTAL")
|
||||||
|
print("=" * 60)
|
||||||
|
try:
|
||||||
|
cur.execute("SELECT COUNT(*), MAX(ODESLANO) FROM PORTAL")
|
||||||
|
cnt, mx = cur.fetchone()
|
||||||
|
print(f" PORTAL: {cnt} záznamů, max ODESLANO={mx}")
|
||||||
|
cur.execute("SELECT FIRST 5 ID, IDFAK, ODESLANO, STAV, ID_PODANI, DAVKA_ROK FROM PORTAL ORDER BY ID DESC")
|
||||||
|
for row in cur.fetchall():
|
||||||
|
print(f" {row}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" PORTAL chyba: {e}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("=" * 60)
|
||||||
|
print("4. Hledání tabulek s datem >= 2026-02-01 (čerstvá data)")
|
||||||
|
print("=" * 60)
|
||||||
|
# Projdi všechny tabulky a hledej ty které mají záznamy z 2026
|
||||||
|
suspicious = []
|
||||||
|
cur.execute("""
|
||||||
|
SELECT RDB$RELATION_NAME FROM RDB$RELATIONS
|
||||||
|
WHERE RDB$SYSTEM_FLAG = 0
|
||||||
|
ORDER BY RDB$RELATION_NAME
|
||||||
|
""")
|
||||||
|
all_tables = [r[0].strip() for r in cur.fetchall()]
|
||||||
|
|
||||||
|
for table in all_tables:
|
||||||
|
try:
|
||||||
|
# Najdi datum sloupce
|
||||||
|
cur.execute(f"""
|
||||||
|
SELECT RDB$FIELD_NAME FROM RDB$RELATION_FIELDS
|
||||||
|
WHERE RDB$RELATION_NAME = '{table}'
|
||||||
|
AND (
|
||||||
|
TRIM(RDB$FIELD_NAME) LIKE '%ODESLANO%'
|
||||||
|
OR TRIM(RDB$FIELD_NAME) LIKE '%VYTVORENO%'
|
||||||
|
OR TRIM(RDB$FIELD_NAME) LIKE '%DATUM_ODE%'
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
date_cols = [r[0].strip() for r in cur.fetchall()]
|
||||||
|
for dc in date_cols:
|
||||||
|
cur.execute(f"SELECT COUNT(*) FROM {table} WHERE {dc} >= '2026-02-01'")
|
||||||
|
cnt = cur.fetchone()[0]
|
||||||
|
if cnt > 0:
|
||||||
|
suspicious.append((table, dc, cnt))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for table, dc, cnt in suspicious:
|
||||||
|
print(f" {table:<35} {dc}: {cnt} záznamů od 2026-02-01")
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
print()
|
||||||
|
print("Hotovo.")
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import sys, io, re
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||||
|
|
||||||
|
TRACE_FILE = r'c:\Program Files\Firebird\Firebird_2_5_CGM\default_trace.log'
|
||||||
|
TIME_FROM = '2026-03-29T13:20:00'
|
||||||
|
TIME_TO = '2026-03-29T13:20:15'
|
||||||
|
|
||||||
|
# Hledame dotazy ktere pravdepodobne patri k eDavky oknu:
|
||||||
|
# - zminka o tabulkach s davkami/podanim na pojistovnu
|
||||||
|
# - sloupce jako ZP, PODACI, ODESLANO, STAV v kontextu davek
|
||||||
|
KEYWORDS = [
|
||||||
|
'EDAVKY', 'EDAVKA', 'DAVKY_POJ', 'REGISTR_POJ',
|
||||||
|
'PODACI_CISLO', 'PODACI',
|
||||||
|
'FAKDAV', 'BB_DAVKA', 'BB_FAKTURA',
|
||||||
|
'STAV_PODANI', 'ID_PODANI',
|
||||||
|
'DAVKA_ROK', 'DAVKA_DISK', 'DAVKA_CASTKA',
|
||||||
|
]
|
||||||
|
|
||||||
|
# Tabulky ktere NECHCEME (HPN = neschopenky, RECEPT = recepty, atd.)
|
||||||
|
EXCLUDE_TABLES = ['FROM HPN', 'FROM NES', 'FROM RECEPT', 'FROM CLICKDOC',
|
||||||
|
'FROM UZIVATEL', 'FROM ZARIZENI', 'FROM ODDEL', 'FROM PRACOVISTE']
|
||||||
|
|
||||||
|
print(f"Čtu trace: {TRACE_FILE}")
|
||||||
|
print(f"Časové okno: {TIME_FROM} .. {TIME_TO}")
|
||||||
|
print(f"Hledám klíčová slova: {KEYWORDS}")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
ts_re = re.compile(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}')
|
||||||
|
|
||||||
|
in_window = False
|
||||||
|
current_ts = ''
|
||||||
|
current_block = []
|
||||||
|
hits = []
|
||||||
|
all_selects_tables = {} # tabulka -> pocet vyskytu
|
||||||
|
|
||||||
|
def get_from_tables(text):
|
||||||
|
"""Extrahuj tabulky z FROM klauzule."""
|
||||||
|
tables = re.findall(r'\bFROM\s+([A-Z_][A-Z0-9_]*)', text, re.IGNORECASE)
|
||||||
|
tables += re.findall(r'\bJOIN\s+([A-Z_][A-Z0-9_]*)', text, re.IGNORECASE)
|
||||||
|
return [t.upper() for t in tables]
|
||||||
|
|
||||||
|
def process_block(ts, lines):
|
||||||
|
text = '\n'.join(lines)
|
||||||
|
if not re.search(r'\bSELECT\b', text, re.IGNORECASE):
|
||||||
|
return
|
||||||
|
|
||||||
|
text_up = text.upper()
|
||||||
|
|
||||||
|
# Spocitej tabulky
|
||||||
|
tables = get_from_tables(text_up)
|
||||||
|
for t in tables:
|
||||||
|
all_selects_tables[t] = all_selects_tables.get(t, 0) + 1
|
||||||
|
|
||||||
|
# Filtr - hledej klicova slova
|
||||||
|
for kw in KEYWORDS:
|
||||||
|
if kw in text_up:
|
||||||
|
hits.append((ts, kw, text))
|
||||||
|
return
|
||||||
|
|
||||||
|
# Alternativne: dotazy na PORTAL nebo FAK s datumem
|
||||||
|
if ('PORTAL' in text_up or 'FAKDAV' in text_up) and 'SELECT' in text_up:
|
||||||
|
# Vynech jednoduche dotazy
|
||||||
|
if len(text) > 100:
|
||||||
|
hits.append((ts, 'PORTAL/FAKDAV', text))
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(TRACE_FILE, 'r', encoding='utf-8', errors='replace') as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.rstrip('\n')
|
||||||
|
m = ts_re.match(line)
|
||||||
|
if m:
|
||||||
|
if current_block and in_window:
|
||||||
|
process_block(current_ts, current_block)
|
||||||
|
current_ts = line[:19]
|
||||||
|
current_block = [line]
|
||||||
|
in_window = (TIME_FROM <= current_ts <= TIME_TO)
|
||||||
|
else:
|
||||||
|
if in_window:
|
||||||
|
current_block.append(line)
|
||||||
|
if current_block and in_window:
|
||||||
|
process_block(current_ts, current_block)
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"CHYBA: soubor nenalezen: {TRACE_FILE}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"\nNejčastější tabulky v SELECT dotazech v okně (top 40):")
|
||||||
|
sorted_tables = sorted(all_selects_tables.items(), key=lambda x: -x[1])
|
||||||
|
for t, cnt in sorted_tables[:40]:
|
||||||
|
print(f" {t:<40} {cnt}x")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(f"{'='*70}")
|
||||||
|
print(f"Nalezeno relevantních bloků (klíčová slova): {len(hits)}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
for i, (ts, kw, text) in enumerate(hits):
|
||||||
|
print(f"{'='*70}")
|
||||||
|
print(f"[{i+1}] {ts} klíčové slovo: {kw}")
|
||||||
|
print(text[:4000])
|
||||||
|
print()
|
||||||
|
|
||||||
|
if not hits:
|
||||||
|
print("Žádné přesné shody. Zkusíme PREPARE_STATEMENT bloky s FROM tabulkami:")
|
||||||
|
print("(viz seznam tabulek nahoře - neobvyklé názvy mohou být klíč)")
|
||||||
@@ -235,6 +235,246 @@ P ... – preventivní prohlídka
|
|||||||
- Kapitace se v FAK.KAPITACE neukazuje (je 0), ale v FAKDET.CENAKAP ano – nutno ověřit
|
- Kapitace se v FAK.KAPITACE neukazuje (je 0), ale v FAKDET.CENAKAP ano – nutno ověřit
|
||||||
- PORTAL = registrační dávky, nesouvisí s fakturací, IDFAK bývá NULL
|
- PORTAL = registrační dávky, nesouvisí s fakturací, IDFAK bývá NULL
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## eDávky – elektronické odesílání dávek pojišťovnám (zjištěno 2026-03-29)
|
||||||
|
|
||||||
|
### Přehled tabulek
|
||||||
|
|
||||||
|
Modul eDávky v Medicusu používá tabulky s prefixem `ED_`:
|
||||||
|
|
||||||
|
| Tabulka | Záznamy | Popis |
|
||||||
|
|---|---|---|
|
||||||
|
| `ED_BOOKOFSUBMISSIONS` | 998 | Hlavní tabulka – Kniha podání (od 2016) |
|
||||||
|
| `ED_BOOKOFSUBMISSIONATTACH` | 0 | Přílohy k podáním (zatím nevyužito) |
|
||||||
|
| `ED_MAILBOXMESSAGE` | 0 | Schránka zpráv od pojišťoven (zatím prázdná) |
|
||||||
|
| `ED_STORAGE` | 5 | Konfigurace – certifikáty a přihlašovací údaje |
|
||||||
|
|
||||||
|
### Jak jsme tabulku našli
|
||||||
|
Přes Firebird trace log – při otevření okna eDávky v Medicusu se nejčastěji dotazuje
|
||||||
|
na `ED_BOOKOFSUBMISSIONS` (45x) a `ED_MAILBOXMESSAGE` (4990x).
|
||||||
|
|
||||||
|
### PORTAL vs ED_BOOKOFSUBMISSIONS
|
||||||
|
- **PORTAL** (180 záznamů, max 2026-01-27) = starý systém podávání dávek
|
||||||
|
- **ED_BOOKOFSUBMISSIONS** (998 záznamů, od 2016) = nový systém (přechod ~únor 2026)
|
||||||
|
- Nepřekrývají se – žádný záznam není v obou (různá ID_PODANI/SUBMISSIONID)
|
||||||
|
- PORTAL má data od 2014, ED_BOOKOFSUBMISSIONS od 2016 (oba systémy běžely paralelně)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ED_BOOKOFSUBMISSIONS – hlavní tabulka Knihy podání
|
||||||
|
|
||||||
|
**Sloupce:**
|
||||||
|
|
||||||
|
| Sloupec | Popis |
|
||||||
|
|---|---|
|
||||||
|
| `ID` | primární klíč |
|
||||||
|
| `CREATED` | datum vytvoření |
|
||||||
|
| `SENTDATE` | datum odeslání pojišťovně |
|
||||||
|
| `CREATOR` | jméno autora (např. "Buzalková Michaela MUDr.") |
|
||||||
|
| `HCPCODE` | IČZ ordinace (09305000) |
|
||||||
|
| `HCPPERSONNAME` | jméno lékaře |
|
||||||
|
| `HICCODE` | kód pojišťovny (111, 201, 205, 207, 209, 211...) |
|
||||||
|
| `INVOICENUMBER` | číslo faktury (např. 0000260020) – NULL pro reg. dávky |
|
||||||
|
| `PERIODFROM` / `PERIODTO` | období dávky |
|
||||||
|
| `REQUESTTYPE` | typ: **0** = registrační (Reg. listy), **1** = výkonová s fakturou |
|
||||||
|
| `STATE` | stav podání (0 = odesláno OK) |
|
||||||
|
| `SUBMISSIONID` | podací číslo přidělené pojišťovnou (např. 59135047) |
|
||||||
|
| `TOTALSUM` | celková částka (0 pro reg. dávky) |
|
||||||
|
| `UNIQUEID` | UUID záznamu |
|
||||||
|
| `USERDESCRIPTION` | popis (zobrazuje se ve sloupci "Zprávy") |
|
||||||
|
| `REQUEST` | XML žádosti odeslané pojišťovně (BLOB) |
|
||||||
|
| `SERVERRESPONSE` | odpověď pojišťovny (BLOB, bytes, kódování iso-8859-2) |
|
||||||
|
| `FDAVKACONTENT` | obsah FDAVKA (BLOB) |
|
||||||
|
| `KDAVKACONTENT` | obsah KDAVKA (BLOB) |
|
||||||
|
| `PROTOCOL` | protokol (BLOB) |
|
||||||
|
|
||||||
|
**Pojišťovny v datech:** 111 (VZP), 201 (VoZP), 205 (ČPZP), 207 (OZP), 209 (ZPŠ), 211 (ZPMV)
|
||||||
|
|
||||||
|
### Kódování pojišťoven (správné!)
|
||||||
|
- **111** = VZP (Všeobecná zdravotní pojišťovna)
|
||||||
|
- **201** = VoZP (Vojenská zdravotní pojišťovna)
|
||||||
|
- **205** = ČPZP (Česká průmyslová zdravotní pojišťovna)
|
||||||
|
- **207** = OZP (Oborová zdravotní pojišťovna)
|
||||||
|
- **209** = ZPŠ (Zdravotní pojišťovna Škoda)
|
||||||
|
- **211** = ZPMV (Zdravotní pojišťovna ministerstva vnitra)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ED_STORAGE – konfigurace certifikátů a přihlašovacích údajů
|
||||||
|
|
||||||
|
Sloupce: `ID`, `NAME`, `VALUEB` (BLOB XML), `IDUZI`
|
||||||
|
|
||||||
|
**Záznamy:**
|
||||||
|
- `ServerSettingsXml` (per uživatel, IDUZI=None/2/4/6) – XML s certifikáty pro každou pojišťovnu
|
||||||
|
- `LastMessagesDownloadTime` (IDUZI=None) – datum posledního stažení zpráv
|
||||||
|
|
||||||
|
**Struktura ServerSettingsXml:**
|
||||||
|
```xml
|
||||||
|
<Settings>
|
||||||
|
<PortalZP>
|
||||||
|
<Portal Code="201">
|
||||||
|
<SigningCertificate Issuer="..." SerialNumber="..." />
|
||||||
|
</Portal>
|
||||||
|
<!-- Code: 201, 205, 207, 209, 212, 213, 217, 228, 333 -->
|
||||||
|
</PortalZP>
|
||||||
|
<PortalZPMV>
|
||||||
|
<!-- Pro ZPMV (211): PIN, Password, Email uloženy v plaintextu! -->
|
||||||
|
<Portal Code="211" PIN="..." Password="..." Email="ordinace@buzalkova.cz" />
|
||||||
|
</PortalZPMV>
|
||||||
|
<PortalVZP>
|
||||||
|
<!-- Pro VZP (111): SigningCertificate + AuthenticationCertificate -->
|
||||||
|
<Portal Code="111" UseAlternativePortal="true">
|
||||||
|
<SigningCertificate Issuer="..." SerialNumber="..." />
|
||||||
|
<AuthenticationCertificate Issuer="..." SerialNumber="..." />
|
||||||
|
</Portal>
|
||||||
|
</PortalVZP>
|
||||||
|
</Settings>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Certifikát Buzalky Vladimíra (IDUZI=6):**
|
||||||
|
- Vydavatel: I.CA EU Qualified CA2/RSA 06/2022 (První certifikační autorita, a.s.)
|
||||||
|
- SerialNumber: `0247068517B0049E2E`
|
||||||
|
- Platí pro pojišťovny: 201, 205, 207, 209, 213, 217, 228
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Formát REQUEST XML – registrační dávka (REQUESTTYPE=0)
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="utf-16"?>
|
||||||
|
<SubmissionRequest>
|
||||||
|
<SubmissionType>RegistrationCards</SubmissionType>
|
||||||
|
<InvoiceType>None</InvoiceType>
|
||||||
|
<PeriodFrom>2026-03-01T00:00:00.000</PeriodFrom>
|
||||||
|
<PeriodTo>2026-03-31T00:00:00.000</PeriodTo>
|
||||||
|
<TotalSum>0</TotalSum>
|
||||||
|
<HcpCode>09305000</HcpCode> <!-- IČZ ordinace -->
|
||||||
|
<FICZ>09305000</FICZ>
|
||||||
|
<HcpOrgNum>68366370</HcpOrgNum> <!-- IČO -->
|
||||||
|
<HcpPersonName>MUDr. Buzalka Vladimír</HcpPersonName>
|
||||||
|
<HicCode>111</HicCode> <!-- kód pojišťovny -->
|
||||||
|
<HicName>Všeobecná zdravotní pojišťovna ČR</HicName>
|
||||||
|
<HicCity>0900</HicCity>
|
||||||
|
<FPOB>0900</FPOB>
|
||||||
|
<VDPOJ>1</VDPOJ>
|
||||||
|
<InvoiceItems>
|
||||||
|
<InvoiceItem>
|
||||||
|
<BatchNumber>8</BatchNumber>
|
||||||
|
<Type>DP80</Type> <!-- DP80 = registrační dávka -->
|
||||||
|
<Price>0.00</Price>
|
||||||
|
<Year>2026</Year>
|
||||||
|
<Month>3</Month>
|
||||||
|
<DBODY>0</DBODY>
|
||||||
|
<DUHR>2</DUHR>
|
||||||
|
</InvoiceItem>
|
||||||
|
</InvoiceItems>
|
||||||
|
</SubmissionRequest>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Formát REQUEST XML – výkonová dávka s fakturou (REQUESTTYPE=1)
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="utf-16"?>
|
||||||
|
<SubmissionRequest>
|
||||||
|
<SubmissionType>HealthCareBilling</SubmissionType>
|
||||||
|
<InvoiceNumber>0000260026</InvoiceNumber>
|
||||||
|
<InvoiceType>HealthCareInvoice</InvoiceType>
|
||||||
|
<DateOfIssue>2026-03-01T00:00:00.000</DateOfIssue>
|
||||||
|
<DateOfDispatch>2026-03-01T00:00:00.000</DateOfDispatch>
|
||||||
|
<DateOfMaturity>2026-03-31T00:00:00.000</DateOfMaturity>
|
||||||
|
<PeriodFrom>2025-12-01T00:00:00.000</PeriodFrom>
|
||||||
|
<PeriodTo>2025-12-31T00:00:00.000</PeriodTo>
|
||||||
|
<TotalSum>85</TotalSum>
|
||||||
|
<HcpCode>09305000</HcpCode>
|
||||||
|
<HcpOrgNum>68366370</HcpOrgNum>
|
||||||
|
<HcpAccountNumber>2800046620</HcpAccountNumber>
|
||||||
|
<HcpAccountPrefix>000000</HcpAccountPrefix>
|
||||||
|
<HcpAccountBankCode>2010</HcpAccountBankCode>
|
||||||
|
<HcpName>Praktický lékař pro dospělé</HcpName>
|
||||||
|
<HcpStreet>Lovosická 440/40</HcpStreet>
|
||||||
|
<HcpCity>Praha 9-Prosek</HcpCity>
|
||||||
|
<HcpZipCode>19000</HcpZipCode>
|
||||||
|
<HcpPersonName>MUDr. Buzalková Michaela</HcpPersonName>
|
||||||
|
<HicCode>207</HicCode>
|
||||||
|
<HicStreet>Ročkotova 1225/1</HicStreet>
|
||||||
|
<HicCity>Praha 4</HicCity>
|
||||||
|
<HicZipCode>140 21</HicZipCode>
|
||||||
|
<FPOB>0900</FPOB>
|
||||||
|
<VDPECE>51</VDPECE>
|
||||||
|
<InvoiceItems>
|
||||||
|
<InvoiceItem>
|
||||||
|
<BatchNumber>34</BatchNumber>
|
||||||
|
<Type>DP05</Type> <!-- DP05 = výkonová dávka -->
|
||||||
|
<Year>2025</Year>
|
||||||
|
<Month>12</Month>
|
||||||
|
<DBODY>85</DBODY>
|
||||||
|
<DUHR>1</DUHR>
|
||||||
|
</InvoiceItem>
|
||||||
|
</InvoiceItems>
|
||||||
|
</SubmissionRequest>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Poznámka k diakritice v REQUEST:** Medicus ukládá XML v UTF-16, ale česká diakritika v polích jako HcpPersonName, HicName apod. bývá uložena bez háčků/čárek (bug Medicusu při tvorbě XML). Toto není chyba dekódování.
|
||||||
|
|
||||||
|
### Formát SERVERRESPONSE (odpověď pojišťovny)
|
||||||
|
- Kódování: **iso-8859-2** (bytes)
|
||||||
|
- Struktura: XML `<Komunikace><Data ...><Soubor ...>text</Soubor></Data><Podpis>PKCS7</Podpis></Komunikace>`
|
||||||
|
- `PZP_IdPodani` = přidělené podací číslo
|
||||||
|
- `PZP_Chyba="0"` = bez chyby
|
||||||
|
- Odpověď je podepsána pojišťovnou (PKCS7)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Plán: skript pro automatické odeslání žádosti o seznam registrovaných
|
||||||
|
|
||||||
|
### Portály pojišťoven – 3 skupiny
|
||||||
|
|
||||||
|
#### Skupina 1 – Společný portál (201, 205, 207, 209, 213, 217, 228)
|
||||||
|
- Jeden společný portál pro všechny tyto pojišťovny
|
||||||
|
- Autentizace a podepisování: **kvalifikovaný certifikát I.CA EU Qualified CA2/RSA 06/2022**
|
||||||
|
- SerialNumber (Buzalka): `0247068517B0049E2E`
|
||||||
|
- Vydavatel: První certifikační autorita, a.s.
|
||||||
|
|
||||||
|
#### Skupina 2 – VZP (111) – vlastní portál
|
||||||
|
- VZP má **samostatný portál** (`UseAlternativePortal="true"` v konfiguraci)
|
||||||
|
- Podepisování: **komerční certifikát I.CA Public CA/RSA 06/2022** (nižší stupeň než kvalifikovaný)
|
||||||
|
- SerialNumber: `01DE0F46B713505F1F`
|
||||||
|
- Autentizace vůči portálu: **certifikát Komerční banky (DCS CA KB)**
|
||||||
|
- SerialNumber: `46E67A`
|
||||||
|
|
||||||
|
#### Skupina 3 – ZPMV (211) – samostatní exoti
|
||||||
|
- Žádný certifikát, přihlašování **heslem**
|
||||||
|
- Konfigurace: PIN, Password, Email (uloženo v plaintextu v ED_STORAGE)
|
||||||
|
- Email: `ordinace@buzalkova.cz`
|
||||||
|
|
||||||
|
### Formáty odpovědí v PORTAL.DATA (historické)
|
||||||
|
Různé pojišťovny vracely různé formáty:
|
||||||
|
- **ČPZP/ZPMV** (D01 portál): XML `<ekomunikace><statuscode>100</statuscode><idpodani>D01F...</idpodani>`
|
||||||
|
- **OZP a ostatní** (starý portál): XML `<Komunikace><Data ...><Soubor Format="BASE64">HTML protokol</Soubor></Data></Komunikace>`
|
||||||
|
- HTML protokol je v iso-8859-2, obsahuje tabulku s detaily dávky
|
||||||
|
|
||||||
|
### Kódování BLOBů v ED_BOOKOFSUBMISSIONS
|
||||||
|
|
||||||
|
| Sloupec | Kódování | Poznámka |
|
||||||
|
|---|---|---|
|
||||||
|
| `KDAVKACONTENT` | CP1250 | fdb vrací str přes win1250 spojení – použít přímo |
|
||||||
|
| `REQUEST` | ASCII/UTF-8 nebo UTF-16 | Detekovat podle BOM: `FF FE`/`FE FF` → utf-16, začíná `<` → utf-8 |
|
||||||
|
| `SERVERRESPONSE` | iso-8859-2 | latin-1 re-encoding → decode iso-8859-2 |
|
||||||
|
| `PROTOCOL` | iso-8859-2 | latin-1 re-encoding → decode iso-8859-2 |
|
||||||
|
|
||||||
|
**Důležité:** Pro re-encoding str→bytes vždy používat `latin-1` (ne `cp1250`), protože latin-1 zachová všechny bajty 0–255 beze změny včetně null bajtů. Pro utf-16 nikdy nezkoušet decode bez ověření BOM – bez BOM Python tiše vrátí čínské znaky místo chyby.
|
||||||
|
|
||||||
|
**Co potřebujeme ještě zjistit:**
|
||||||
|
- URL endpointů jednotlivých portálů (zachytit přes síťový trace)
|
||||||
|
- Jak přesně se REQUEST podepisuje certifikátem (PKCS12 / Windows certificate store)
|
||||||
|
|
||||||
|
**Co už máme:**
|
||||||
|
- Formát REQUEST XML (viz výše)
|
||||||
|
- Certifikáty a přihlašovací údaje z ED_STORAGE
|
||||||
|
- Strukturu odpovědi pojišťovny
|
||||||
|
- Rozdělení pojišťoven do 3 skupin podle způsobu přístupu
|
||||||
|
|
||||||
## Kódování KDAVKA/FDAVKA – důležité!
|
## Kódování KDAVKA/FDAVKA – důležité!
|
||||||
|
|
||||||
Dávkové soubory (KDAVKA, FDAVKA) jsou uloženy v **CP852** (DOS Latin-2, prahistorické kódování).
|
Dávkové soubory (KDAVKA, FDAVKA) jsou uloženy v **CP852** (DOS Latin-2, prahistorické kódování).
|
||||||
|
|||||||
Reference in New Issue
Block a user