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:
2026-03-30 07:42:46 +02:00
parent 8497223ebb
commit 4f586f4b57
9 changed files with 1175 additions and 1 deletions
+219 -1
View File
@@ -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'))