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:
|
||||
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í
|
||||
# =====================
|
||||
@@ -269,4 +487,4 @@ for i, row in enumerate(fak_rows, start=2):
|
||||
conn.close()
|
||||
wb.save(output_path)
|
||||
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
|
||||
- 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é!
|
||||
|
||||
Dávkové soubory (KDAVKA, FDAVKA) jsou uloženy v **CP852** (DOS Latin-2, prahistorické kódování).
|
||||
|
||||
Reference in New Issue
Block a user