notebookvb
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
# Claude Code
|
||||
.claude/
|
||||
|
||||
# Python
|
||||
.venv/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
# PyCharm
|
||||
.idea/
|
||||
*.iml
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import fitz # PyMuPDF
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# ===============================
|
||||
# 📁 FOLDER SETTINGS
|
||||
# ===============================
|
||||
input_folder = Path(r"u:\Dropbox\Ordinace\Dokumentace_ke_zpracování\A4 expand")
|
||||
output_suffix = "_A4scaled.pdf"
|
||||
|
||||
# A4 portrait size in points
|
||||
A4_WIDTH, A4_HEIGHT = 595, 842
|
||||
|
||||
# ===============================
|
||||
# ⚙️ FUNCTION
|
||||
# ===============================
|
||||
def normalize_to_A4_portrait(pdf_path: Path):
|
||||
output_path = pdf_path.with_name(pdf_path.stem + output_suffix)
|
||||
|
||||
# skip if already exists
|
||||
if output_path.exists():
|
||||
try:
|
||||
output_path.unlink()
|
||||
print(f"🧹 Deleted old: {output_path.name}")
|
||||
except PermissionError:
|
||||
print(f"⚠️ Skipped {output_path.name} (open elsewhere)")
|
||||
return
|
||||
|
||||
src = fitz.open(pdf_path)
|
||||
dst = fitz.open()
|
||||
changed = False
|
||||
|
||||
for i, page in enumerate(src, 1):
|
||||
rect = page.rect
|
||||
w, h = rect.width, rect.height
|
||||
|
||||
# check if already close to A4 size (within 2%)
|
||||
if abs(w - A4_WIDTH) < 12 and abs(h - A4_HEIGHT) < 17:
|
||||
# copy directly without change
|
||||
dst.insert_pdf(src, from_page=i - 1, to_page=i - 1)
|
||||
print(f"✅ {pdf_path.name} page {i}: already A4, copied as-is.")
|
||||
continue
|
||||
|
||||
changed = True
|
||||
scale = min(A4_WIDTH / w, A4_HEIGHT / h)
|
||||
new_w, new_h = w * scale, h * scale
|
||||
|
||||
# horizontal center, vertical TOP
|
||||
x_off = (A4_WIDTH - new_w) / 2
|
||||
y_off = 0 # 👈 place at top edge
|
||||
|
||||
new_page = dst.new_page(width=A4_WIDTH, height=A4_HEIGHT)
|
||||
new_page.show_pdf_page(
|
||||
fitz.Rect(x_off, y_off, x_off + new_w, y_off + new_h),
|
||||
src,
|
||||
page.number
|
||||
)
|
||||
|
||||
print(f"📄 {pdf_path.name} page {i}: {w:.0f}×{h:.0f} → {new_w:.0f}×{new_h:.0f} (top-aligned)")
|
||||
|
||||
dst.save(output_path)
|
||||
src.close()
|
||||
dst.close()
|
||||
|
||||
if changed:
|
||||
print(f"✅ Saved normalized A4 PDF: {output_path.name}\n")
|
||||
else:
|
||||
print(f"➡️ No change needed: {pdf_path.name}\n")
|
||||
|
||||
|
||||
# ===============================
|
||||
# 🚀 MAIN LOOP
|
||||
# ===============================
|
||||
for file in input_folder.glob("*.pdf"):
|
||||
normalize_to_A4_portrait(file)
|
||||
|
||||
print("\n🎉 All PDFs processed — scaled to A4 portrait, top-aligned.")
|
||||
@@ -0,0 +1,11 @@
|
||||
import fitz # PyMuPDF
|
||||
|
||||
pdf = r"u:\Dropbox\Ordinace\Dokumentace_ke_zpracování\A4 expand\0602054486 2025-07-02 [LZ ORL] [zalehnutí levého ucha, cerumen, vyčištění, OK].pdf"
|
||||
doc = fitz.open(pdf)
|
||||
|
||||
for i, page in enumerate(doc, 1):
|
||||
print(f"Page {i}")
|
||||
print(" MediaBox:", page.mediabox)
|
||||
print(" CropBox :", page.cropbox)
|
||||
print(" Rect :", page.rect)
|
||||
print()
|
||||
@@ -0,0 +1,86 @@
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
from EmailMessagingGraph import send_mail
|
||||
|
||||
# =========================
|
||||
# CONFIG – TEST PC
|
||||
# =========================
|
||||
|
||||
GBAK = r"C:\Program Files\Firebird\Firebird_2_5_CGM\bin\gbak.exe"
|
||||
DB = r"localhost/3050:Z:\Medicus 3\data\MEDICUS.FDB"
|
||||
BACKUP_DIR = Path(r"D:\medicusbackup")
|
||||
|
||||
FB_USER = "SYSDBA"
|
||||
FB_PASS = "masterkey"
|
||||
|
||||
MAIL_TO = "vladimir.buzalka@buzalka.cz"
|
||||
|
||||
# =========================
|
||||
# MAIN
|
||||
# =========================
|
||||
|
||||
def main():
|
||||
BACKUP_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
now = datetime.now()
|
||||
ts = now.strftime("%Y-%m-%d_%H-%M-%S")
|
||||
|
||||
fbk = BACKUP_DIR / f"MEDICUS_{ts}.fbk"
|
||||
log = BACKUP_DIR / f"MEDICUS_{ts}.log"
|
||||
|
||||
cmd = [
|
||||
GBAK,
|
||||
"-b",
|
||||
"-user", FB_USER,
|
||||
"-pas", FB_PASS,
|
||||
DB,
|
||||
str(fbk),
|
||||
"-v",
|
||||
]
|
||||
|
||||
try:
|
||||
with open(log, "w", encoding="utf-8") as f:
|
||||
subprocess.run(
|
||||
cmd,
|
||||
stdout=f,
|
||||
stderr=subprocess.STDOUT,
|
||||
check=True,
|
||||
)
|
||||
|
||||
send_mail(
|
||||
MAIL_TO,
|
||||
"✅ MEDICUS – záloha OK (test PC)",
|
||||
f"""Záloha MEDICUS proběhla úspěšně.
|
||||
|
||||
Čas: {now}
|
||||
Soubor: {fbk}
|
||||
Log: {log}
|
||||
""",
|
||||
)
|
||||
|
||||
except Exception:
|
||||
err = traceback.format_exc()
|
||||
|
||||
send_mail(
|
||||
MAIL_TO,
|
||||
"❌ MEDICUS – CHYBA ZÁLOHY (test PC)",
|
||||
f"""Při záloze MEDICUS došlo k chybě.
|
||||
|
||||
Čas: {now}
|
||||
|
||||
Chyba:
|
||||
{err}
|
||||
|
||||
Log:
|
||||
{log}
|
||||
""",
|
||||
)
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,23 @@
|
||||
from pathlib import Path
|
||||
import zipfile
|
||||
|
||||
# =========================
|
||||
# CONFIG
|
||||
# =========================
|
||||
|
||||
SRC = Path(r"D:\medicusbackup\MEDICUS_2026-01-24_17-07-44.fbk")
|
||||
DST = SRC.with_suffix(".zip")
|
||||
|
||||
# =========================
|
||||
# ZIP MAX COMPRESSION
|
||||
# =========================
|
||||
|
||||
with zipfile.ZipFile(
|
||||
DST,
|
||||
mode="w",
|
||||
compression=zipfile.ZIP_DEFLATED,
|
||||
compresslevel=9
|
||||
) as z:
|
||||
z.write(SRC, arcname=SRC.name)
|
||||
|
||||
print(f"ZIP created: {DST}")
|
||||
@@ -0,0 +1,39 @@
|
||||
from pathlib import Path
|
||||
import zipfile
|
||||
|
||||
SRC = Path(r"D:\medicusbackup\MEDICUS_2026-01-24_17-07-44.fbk")
|
||||
DST = SRC.with_suffix(".zip")
|
||||
|
||||
total = SRC.stat().st_size
|
||||
processed = 0
|
||||
chunk = 8 * 1024 * 1024 # 8 MB
|
||||
|
||||
with zipfile.ZipFile(
|
||||
DST,
|
||||
"w",
|
||||
compression=zipfile.ZIP_DEFLATED,
|
||||
compresslevel=9,
|
||||
) as zf:
|
||||
|
||||
zi = zipfile.ZipInfo(SRC.name)
|
||||
zi.compress_type = zipfile.ZIP_DEFLATED
|
||||
|
||||
# ⬇⬇⬇ DŮLEŽITÉ ⬇⬇⬇
|
||||
with zf.open(zi, "w", force_zip64=True) as z:
|
||||
with open(SRC, "rb") as f:
|
||||
while True:
|
||||
buf = f.read(chunk)
|
||||
if not buf:
|
||||
break
|
||||
|
||||
z.write(buf)
|
||||
processed += len(buf)
|
||||
|
||||
pct = processed * 100 / total
|
||||
print(
|
||||
f"\rZIP: {pct:6.2f}% ({processed//1024//1024} MB)",
|
||||
end="",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
print("\nHotovo.")
|
||||
@@ -0,0 +1,141 @@
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
import zipfile
|
||||
import zstandard as zstd
|
||||
|
||||
# ============================================================
|
||||
# CONFIG
|
||||
# ============================================================
|
||||
|
||||
SRC = Path(r"D:\medicusbackup\MEDICUS_2026-01-24_17-07-44.fbk")
|
||||
BASE = SRC.with_suffix("")
|
||||
CHUNK = 8 * 1024 * 1024 # 8 MB
|
||||
|
||||
SEVENZIP_EXE = r"c:\Program Files (x86)\7-Zip\7z.exe"
|
||||
|
||||
ORIG_SIZE = SRC.stat().st_size
|
||||
|
||||
|
||||
# ============================================================
|
||||
# ZIP (ZIP64 + progress)
|
||||
# ============================================================
|
||||
|
||||
def zip_test():
|
||||
dst = BASE.with_suffix(".zip")
|
||||
total = ORIG_SIZE
|
||||
processed = 0
|
||||
|
||||
start = time.time()
|
||||
|
||||
with zipfile.ZipFile(
|
||||
dst,
|
||||
"w",
|
||||
compression=zipfile.ZIP_DEFLATED,
|
||||
compresslevel=9,
|
||||
) as zf:
|
||||
|
||||
zi = zipfile.ZipInfo(SRC.name)
|
||||
zi.compress_type = zipfile.ZIP_DEFLATED
|
||||
|
||||
# ⬇⬇⬇ KLÍČOVÁ OPRAVA ⬇⬇⬇
|
||||
with zf.open(zi, "w", force_zip64=True) as z:
|
||||
with open(SRC, "rb") as f:
|
||||
while buf := f.read(CHUNK):
|
||||
z.write(buf)
|
||||
processed += len(buf)
|
||||
print(
|
||||
f"\rZIP {processed*100/total:6.2f}%",
|
||||
end="",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
print()
|
||||
return time.time() - start, dst.stat().st_size
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 7Z (CLI + progress)
|
||||
# ============================================================
|
||||
|
||||
def sevenzip_test():
|
||||
dst = BASE.with_suffix(".7z")
|
||||
|
||||
start = time.time()
|
||||
|
||||
proc = subprocess.Popen(
|
||||
[SEVENZIP_EXE, "a", "-t7z", "-mx=9", str(dst), str(SRC)],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
)
|
||||
|
||||
for line in proc.stdout:
|
||||
if "%" in line:
|
||||
print(f"\r7Z {line.strip():>6}", end="", flush=True)
|
||||
|
||||
proc.wait()
|
||||
print()
|
||||
|
||||
return time.time() - start, dst.stat().st_size
|
||||
|
||||
|
||||
# ============================================================
|
||||
# ZSTD (stream + progress)
|
||||
# ============================================================
|
||||
|
||||
def zstd_test():
|
||||
dst = BASE.with_suffix(".zst")
|
||||
total = ORIG_SIZE
|
||||
processed = 0
|
||||
|
||||
cctx = zstd.ZstdCompressor(level=19)
|
||||
start = time.time()
|
||||
|
||||
with open(SRC, "rb") as src, open(dst, "wb") as out:
|
||||
with cctx.stream_writer(out) as compressor:
|
||||
while buf := src.read(CHUNK):
|
||||
compressor.write(buf)
|
||||
processed += len(buf)
|
||||
print(
|
||||
f"\rZSTD {processed*100/total:6.2f}%",
|
||||
end="",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
print()
|
||||
return time.time() - start, dst.stat().st_size
|
||||
|
||||
|
||||
# ============================================================
|
||||
# MAIN
|
||||
# ============================================================
|
||||
|
||||
results = {}
|
||||
|
||||
print("\n--- ZIP ---")
|
||||
t, s = zip_test()
|
||||
results["ZIP"] = (t, s)
|
||||
|
||||
# print("\n--- 7Z ---")
|
||||
# t, s = sevenzip_test()
|
||||
# results["7Z"] = (t, s)
|
||||
#
|
||||
# print("\n--- ZSTD ---")
|
||||
# t, s = zstd_test()
|
||||
# results["ZSTD"] = (t, s)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# SUMMARY
|
||||
# ============================================================
|
||||
|
||||
print("\n=== SUMMARY ===")
|
||||
for name, (t, size) in results.items():
|
||||
ratio = 100 * (1 - size / ORIG_SIZE)
|
||||
print(
|
||||
f"{name:5s}: "
|
||||
f"{t:7.1f} s "
|
||||
f"{size/1024/1024:8.1f} MB "
|
||||
f"(-{ratio:5.1f} %)"
|
||||
)
|
||||
@@ -0,0 +1,161 @@
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import zipfile
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from EmailMessagingGraph import send_mail
|
||||
|
||||
# ============================================================
|
||||
# CONFIG
|
||||
# ============================================================
|
||||
|
||||
GBAK = r"C:\Program Files\Firebird\Firebird_2_5_CGM\bin\gbak.exe"
|
||||
DB = r"localhost/3050:Z:\Medicus 3\data\MEDICUS.FDB"
|
||||
BACKUP_DIR = Path(r"D:\medicusbackup")
|
||||
|
||||
FB_USER = "SYSDBA"
|
||||
FB_PASS = "masterkey"
|
||||
|
||||
MAIL_TO = "vladimir.buzalka@buzalka.cz"
|
||||
|
||||
CHUNK = 8 * 1024 * 1024 # 8 MB
|
||||
|
||||
|
||||
# ============================================================
|
||||
# MAIN
|
||||
# ============================================================
|
||||
|
||||
def main():
|
||||
BACKUP_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
now = datetime.now()
|
||||
ts = now.strftime("%Y-%m-%d_%H-%M-%S")
|
||||
|
||||
fbk = BACKUP_DIR / f"MEDICUS_{ts}.fbk"
|
||||
zipf = fbk.with_suffix(".zip")
|
||||
log = BACKUP_DIR / f"MEDICUS_{ts}.log"
|
||||
|
||||
report = []
|
||||
report.append(f"Čas spuštění: {now}")
|
||||
report.append(f"Databáze: {DB}")
|
||||
report.append("")
|
||||
|
||||
try:
|
||||
# ====================================================
|
||||
# 1) GBAK
|
||||
# ====================================================
|
||||
t0 = time.time()
|
||||
|
||||
cmd = [
|
||||
GBAK,
|
||||
"-b",
|
||||
"-user", FB_USER,
|
||||
"-pas", FB_PASS,
|
||||
DB,
|
||||
str(fbk),
|
||||
"-v",
|
||||
]
|
||||
|
||||
with open(log, "w", encoding="utf-8") as f:
|
||||
subprocess.run(
|
||||
cmd,
|
||||
stdout=f,
|
||||
stderr=subprocess.STDOUT,
|
||||
check=True,
|
||||
)
|
||||
|
||||
t_gbak = time.time() - t0
|
||||
fbk_size = fbk.stat().st_size
|
||||
|
||||
report.append("GBAK záloha: OK")
|
||||
report.append(f"FBK soubor: {fbk}")
|
||||
report.append(f"Velikost FBK: {fbk_size/1024/1024:.1f} MB")
|
||||
report.append(f"Čas GBAK: {t_gbak:.1f} s")
|
||||
report.append("")
|
||||
|
||||
# ====================================================
|
||||
# 2) ZIP
|
||||
# ====================================================
|
||||
t1 = time.time()
|
||||
processed = 0
|
||||
|
||||
with zipfile.ZipFile(
|
||||
zipf,
|
||||
"w",
|
||||
compression=zipfile.ZIP_DEFLATED,
|
||||
compresslevel=9,
|
||||
) as zf:
|
||||
|
||||
zi = zipfile.ZipInfo(fbk.name)
|
||||
zi.compress_type = zipfile.ZIP_DEFLATED
|
||||
|
||||
with zf.open(zi, "w", force_zip64=True) as z:
|
||||
with open(fbk, "rb") as src:
|
||||
while buf := src.read(CHUNK):
|
||||
z.write(buf)
|
||||
processed += len(buf)
|
||||
pct = processed * 100 / fbk_size
|
||||
print(
|
||||
f"\rZIP: {pct:6.2f}% "
|
||||
f"({processed//1024//1024} MB)",
|
||||
end="",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
print("\nZIP hotov.")
|
||||
|
||||
t_zip = time.time() - t1
|
||||
zip_size = zipf.stat().st_size
|
||||
|
||||
report.append("ZIP komprese: OK")
|
||||
report.append(f"ZIP soubor: {zipf}")
|
||||
report.append(f"Velikost ZIP: {zip_size/1024/1024:.1f} MB")
|
||||
report.append(
|
||||
f"Kompresní poměr: "
|
||||
f"{100 * (1 - zip_size / fbk_size):.1f} %"
|
||||
)
|
||||
report.append(f"Čas ZIP: {t_zip:.1f} s")
|
||||
report.append("")
|
||||
|
||||
# ====================================================
|
||||
# 3) DELETE FBK
|
||||
# ====================================================
|
||||
fbk.unlink()
|
||||
|
||||
report.append("FBK soubor byl po úspěšné kompresi smazán.")
|
||||
report.append("")
|
||||
report.append(f"LOG: {log}")
|
||||
|
||||
# ====================================================
|
||||
# MAIL OK
|
||||
# ====================================================
|
||||
send_mail(
|
||||
MAIL_TO,
|
||||
"✅ MEDICUS – záloha + ZIP OK",
|
||||
"\n".join(report),
|
||||
)
|
||||
|
||||
except Exception:
|
||||
err = traceback.format_exc()
|
||||
|
||||
send_mail(
|
||||
MAIL_TO,
|
||||
"❌ MEDICUS – chyba při záloze / ZIP",
|
||||
f"""Při záloze MEDICUS došlo k chybě.
|
||||
|
||||
Čas: {now}
|
||||
|
||||
Chyba:
|
||||
{err}
|
||||
|
||||
FBK (pokud existuje): {fbk}
|
||||
LOG: {log}
|
||||
""",
|
||||
)
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,161 @@
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import zipfile
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from EmailMessagingGraph import send_mail
|
||||
|
||||
# ============================================================
|
||||
# CONFIG
|
||||
# ============================================================
|
||||
|
||||
GBAK = r"C:\Program Files\Firebird\Firebird_2_5_CGM\bin\gbak.exe"
|
||||
DB = r"localhost/3050:Z:\Medicus 3\data\MEDICUS.FDB"
|
||||
BACKUP_DIR = Path(r"D:\medicusbackup")
|
||||
|
||||
FB_USER = "SYSDBA"
|
||||
FB_PASS = "masterkey"
|
||||
|
||||
MAIL_TO = "vladimir.buzalka@buzalka.cz"
|
||||
|
||||
CHUNK = 8 * 1024 * 1024 # 8 MB
|
||||
|
||||
|
||||
# ============================================================
|
||||
# MAIN
|
||||
# ============================================================
|
||||
|
||||
def main():
|
||||
BACKUP_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
now = datetime.now()
|
||||
ts = now.strftime("%Y-%m-%d_%H-%M-%S")
|
||||
|
||||
fbk = BACKUP_DIR / f"MEDICUS_{ts}.fbk"
|
||||
zipf = fbk.with_suffix(".zip")
|
||||
log = BACKUP_DIR / f"MEDICUS_{ts}.log"
|
||||
|
||||
report = []
|
||||
report.append(f"Čas spuštění: {now}")
|
||||
report.append(f"Databáze: {DB}")
|
||||
report.append("")
|
||||
|
||||
try:
|
||||
# ====================================================
|
||||
# 1) GBAK
|
||||
# ====================================================
|
||||
t0 = time.time()
|
||||
|
||||
cmd = [
|
||||
GBAK,
|
||||
"-b",
|
||||
"-user", FB_USER,
|
||||
"-pas", FB_PASS,
|
||||
DB,
|
||||
str(fbk),
|
||||
"-v",
|
||||
]
|
||||
|
||||
with open(log, "w", encoding="utf-8") as f:
|
||||
subprocess.run(
|
||||
cmd,
|
||||
stdout=f,
|
||||
stderr=subprocess.STDOUT,
|
||||
check=True,
|
||||
)
|
||||
|
||||
t_gbak = time.time() - t0
|
||||
fbk_size = fbk.stat().st_size
|
||||
|
||||
report.append("GBAK záloha: OK")
|
||||
report.append(f"FBK soubor: {fbk}")
|
||||
report.append(f"Velikost FBK: {fbk_size/1024/1024:.1f} MB")
|
||||
report.append(f"Čas GBAK: {t_gbak:.1f} s")
|
||||
report.append("")
|
||||
|
||||
# ====================================================
|
||||
# 2) ZIP
|
||||
# ====================================================
|
||||
t1 = time.time()
|
||||
processed = 0
|
||||
|
||||
with zipfile.ZipFile(
|
||||
zipf,
|
||||
"w",
|
||||
compression=zipfile.ZIP_DEFLATED,
|
||||
compresslevel=9,
|
||||
) as zf:
|
||||
|
||||
zi = zipfile.ZipInfo(fbk.name)
|
||||
zi.compress_type = zipfile.ZIP_DEFLATED
|
||||
|
||||
with zf.open(zi, "w", force_zip64=True) as z:
|
||||
with open(fbk, "rb") as src:
|
||||
while buf := src.read(CHUNK):
|
||||
z.write(buf)
|
||||
processed += len(buf)
|
||||
pct = processed * 100 / fbk_size
|
||||
print(
|
||||
f"\rZIP: {pct:6.2f}% "
|
||||
f"({processed//1024//1024} MB)",
|
||||
end="",
|
||||
flush=True,
|
||||
)
|
||||
|
||||
print("\nZIP hotov.")
|
||||
|
||||
t_zip = time.time() - t1
|
||||
zip_size = zipf.stat().st_size
|
||||
|
||||
report.append("ZIP komprese: OK")
|
||||
report.append(f"ZIP soubor: {zipf}")
|
||||
report.append(f"Velikost ZIP: {zip_size/1024/1024:.1f} MB")
|
||||
report.append(
|
||||
f"Kompresní poměr: "
|
||||
f"{100 * (1 - zip_size / fbk_size):.1f} %"
|
||||
)
|
||||
report.append(f"Čas ZIP: {t_zip:.1f} s")
|
||||
report.append("")
|
||||
|
||||
# ====================================================
|
||||
# 3) DELETE FBK
|
||||
# ====================================================
|
||||
fbk.unlink()
|
||||
|
||||
report.append("FBK soubor byl po úspěšné kompresi smazán.")
|
||||
report.append("")
|
||||
report.append(f"LOG: {log}")
|
||||
|
||||
# ====================================================
|
||||
# MAIL OK
|
||||
# ====================================================
|
||||
send_mail(
|
||||
MAIL_TO,
|
||||
"✅ MEDICUS – záloha + ZIP OK",
|
||||
"\n".join(report),
|
||||
)
|
||||
|
||||
except Exception:
|
||||
err = traceback.format_exc()
|
||||
|
||||
send_mail(
|
||||
MAIL_TO,
|
||||
"❌ MEDICUS – chyba při záloze / ZIP",
|
||||
f"""Při záloze MEDICUS došlo k chybě.
|
||||
|
||||
Čas: {now}
|
||||
|
||||
Chyba:
|
||||
{err}
|
||||
|
||||
FBK (pokud existuje): {fbk}
|
||||
LOG: {log}
|
||||
""",
|
||||
)
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,185 @@
|
||||
import subprocess
|
||||
import os
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import zipfile
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from EmailMessagingGraph import send_mail
|
||||
|
||||
# ============================================================
|
||||
# CONFIG
|
||||
# ============================================================
|
||||
|
||||
GBAK = r"C:\Program Files\Firebird\Firebird_2_5_CGM\bin\gbak.exe"
|
||||
FB_USER = "SYSDBA"
|
||||
FB_PASS = "masterkey"
|
||||
FB_PORT = "3050"
|
||||
|
||||
MAIN_DB = r"localhost/3050:C:\medicus 3\data\MEDICUS.FDB"
|
||||
EXT_DIR = Path(r"U:\externi")
|
||||
BACKUP_DIR = Path(r"U:\medicusbackup")
|
||||
|
||||
MAIL_TO = "vladimir.buzalka@buzalka.cz"
|
||||
|
||||
CHUNK = 8 * 1024 * 1024 # 8 MB
|
||||
|
||||
|
||||
# ============================================================
|
||||
# HELPERS
|
||||
# ============================================================
|
||||
|
||||
def gbak_and_zip(label: str, db_conn: str, fbk: Path, zipf: Path, log: Path) -> dict:
|
||||
"""
|
||||
Run gbak backup and ZIP the result.
|
||||
Returns a result dict.
|
||||
"""
|
||||
result = {
|
||||
"label": label,
|
||||
"ok": False,
|
||||
"fbk_size": 0,
|
||||
"zip_size": 0,
|
||||
"t_gbak": 0,
|
||||
"t_zip": 0,
|
||||
"error": None,
|
||||
}
|
||||
|
||||
# 1) GBAK
|
||||
print(f"GBAK: {label} ... ", end="", flush=True)
|
||||
t0 = time.time()
|
||||
cmd = [GBAK, "-b", "-user", FB_USER, "-pas", FB_PASS, db_conn, str(fbk), "-v"]
|
||||
with open(log, "w", encoding="utf-8") as f:
|
||||
subprocess.run(cmd, stdout=f, stderr=subprocess.STDOUT, check=True)
|
||||
result["t_gbak"] = time.time() - t0
|
||||
result["fbk_size"] = fbk.stat().st_size
|
||||
print(f"OK ({result['t_gbak']:.0f}s, {result['fbk_size']/1024/1024:.1f} MB)")
|
||||
|
||||
# 2) ZIP
|
||||
t1 = time.time()
|
||||
processed = 0
|
||||
fbk_size = result["fbk_size"]
|
||||
with zipfile.ZipFile(zipf, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
|
||||
zi = zipfile.ZipInfo(fbk.name)
|
||||
zi.compress_type = zipfile.ZIP_DEFLATED
|
||||
with zf.open(zi, "w", force_zip64=True) as z:
|
||||
with open(fbk, "rb") as src:
|
||||
while buf := src.read(CHUNK):
|
||||
z.write(buf)
|
||||
processed += len(buf)
|
||||
pct = processed * 100 / fbk_size
|
||||
print(f"\r ZIP {label}: {pct:6.2f}%", end="", flush=True)
|
||||
print()
|
||||
result["t_zip"] = time.time() - t1
|
||||
result["zip_size"] = zipf.stat().st_size
|
||||
|
||||
# 3) Delete FBK + LOG
|
||||
fbk.unlink()
|
||||
log.unlink()
|
||||
|
||||
result["ok"] = True
|
||||
return result
|
||||
|
||||
|
||||
def format_result(r: dict) -> str:
|
||||
ratio = 100 * (1 - r["zip_size"] / r["fbk_size"]) if r["fbk_size"] else 0
|
||||
return (
|
||||
f" {r['label']}: "
|
||||
f"FBK {r['fbk_size']/1024/1024:.1f} MB → "
|
||||
f"ZIP {r['zip_size']/1024/1024:.1f} MB "
|
||||
f"({ratio:.0f}% komprese, "
|
||||
f"gbak {r['t_gbak']:.0f}s, zip {r['t_zip']:.0f}s)"
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# MAIN
|
||||
# ============================================================
|
||||
|
||||
def main():
|
||||
BACKUP_DIR.mkdir(parents=True, exist_ok=True)
|
||||
now = datetime.now()
|
||||
ts = now.strftime("%Y-%m-%d_%H-%M-%S")
|
||||
|
||||
backed_up = []
|
||||
errors = []
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 1) Hlavní DB – MEDICUS.FDB
|
||||
# ----------------------------------------------------------
|
||||
fbk = BACKUP_DIR / f"MEDICUS_{ts}.fbk"
|
||||
zipf = BACKUP_DIR / f"MEDICUS_{ts}.zip"
|
||||
log = BACKUP_DIR / f"MEDICUS_{ts}.log"
|
||||
try:
|
||||
r = gbak_and_zip("MEDICUS", MAIN_DB, fbk, zipf, log)
|
||||
backed_up.append(r)
|
||||
except Exception:
|
||||
errors.append({"label": "MEDICUS", "error": traceback.format_exc()})
|
||||
for f in (fbk, log):
|
||||
if f.exists():
|
||||
f.unlink()
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 2) Externí DB – MEDICUS_FILES_*.fdb
|
||||
# ----------------------------------------------------------
|
||||
fdb_all = sorted(
|
||||
set(EXT_DIR.glob("MEDICUS_FILES_*.fdb")) | set(EXT_DIR.glob("MEDICUS_FILES_*.FDB")),
|
||||
key=lambda p: p.name.lower(),
|
||||
)
|
||||
|
||||
for fdb in fdb_all:
|
||||
name = fdb.stem
|
||||
fbk = BACKUP_DIR / f"{name}_{ts}.fbk"
|
||||
zipf = BACKUP_DIR / f"{name}_{ts}.zip"
|
||||
log = BACKUP_DIR / f"{name}_{ts}.log"
|
||||
db_conn = f"localhost/{FB_PORT}:{fdb}"
|
||||
try:
|
||||
r = gbak_and_zip(name, db_conn, fbk, zipf, log)
|
||||
backed_up.append(r)
|
||||
except Exception:
|
||||
errors.append({"label": name, "error": traceback.format_exc()})
|
||||
for f in (fbk, log):
|
||||
if f.exists():
|
||||
f.unlink()
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Report
|
||||
# ----------------------------------------------------------
|
||||
total = 1 + len(fdb_all)
|
||||
report = [
|
||||
f"Backup Medicus – {now.strftime('%d.%m.%Y %H:%M')}",
|
||||
f"Celkem DB: {total} | OK: {len(backed_up)} | Chyby: {len(errors)}",
|
||||
f"Výstupní adresář: {BACKUP_DIR}",
|
||||
"",
|
||||
]
|
||||
|
||||
if backed_up:
|
||||
report.append("--- Zálohováno ---")
|
||||
total_zip = sum(r["zip_size"] for r in backed_up)
|
||||
for r in backed_up:
|
||||
report.append(format_result(r))
|
||||
report.append(f" Celková velikost ZIP: {total_zip/1024/1024:.1f} MB")
|
||||
report.append("")
|
||||
|
||||
if errors:
|
||||
report.append("--- CHYBY ---")
|
||||
for e in errors:
|
||||
report.append(f" {e['label']}:\n{e['error']}")
|
||||
report.append("")
|
||||
|
||||
has_errors = bool(errors)
|
||||
subject = (
|
||||
f"{'X' if has_errors else 'OK'} MEDICUS backup "
|
||||
f"{len(backed_up)}/{total}"
|
||||
+ (f" – {len(errors)} chyb" if has_errors else "")
|
||||
)
|
||||
|
||||
send_mail(MAIL_TO, subject, "\n".join(report))
|
||||
print("\n" + "\n".join(report))
|
||||
|
||||
if errors:
|
||||
raise RuntimeError(f"{len(errors)} backup(s) failed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,185 @@
|
||||
import subprocess
|
||||
import os
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import zipfile
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from EmailMessagingGraph import send_mail
|
||||
|
||||
# ============================================================
|
||||
# CONFIG
|
||||
# ============================================================
|
||||
|
||||
GBAK = r"C:\Program Files\Firebird\Firebird_2_5_CGM\bin\gbak.exe"
|
||||
FB_USER = "SYSDBA"
|
||||
FB_PASS = "masterkey"
|
||||
FB_PORT = "3050"
|
||||
|
||||
MAIN_DB = r"localhost/3050:C:\medicus 3\data\MEDICUS.FDB"
|
||||
EXT_DIR = Path(r"U:\externi")
|
||||
BACKUP_DIR = Path(r"U:\medicusbackup")
|
||||
|
||||
MAIL_TO = "vladimir.buzalka@buzalka.cz"
|
||||
|
||||
CHUNK = 8 * 1024 * 1024 # 8 MB
|
||||
|
||||
|
||||
# ============================================================
|
||||
# HELPERS
|
||||
# ============================================================
|
||||
|
||||
def gbak_and_zip(label: str, db_conn: str, fbk: Path, zipf: Path, log: Path) -> dict:
|
||||
"""
|
||||
Run gbak backup and ZIP the result.
|
||||
Returns a result dict.
|
||||
"""
|
||||
result = {
|
||||
"label": label,
|
||||
"ok": False,
|
||||
"fbk_size": 0,
|
||||
"zip_size": 0,
|
||||
"t_gbak": 0,
|
||||
"t_zip": 0,
|
||||
"error": None,
|
||||
}
|
||||
|
||||
# 1) GBAK
|
||||
print(f"GBAK: {label} ... ", end="", flush=True)
|
||||
t0 = time.time()
|
||||
cmd = [GBAK, "-b", "-user", FB_USER, "-pas", FB_PASS, db_conn, str(fbk), "-v"]
|
||||
with open(log, "w", encoding="utf-8") as f:
|
||||
subprocess.run(cmd, stdout=f, stderr=subprocess.STDOUT, check=True)
|
||||
result["t_gbak"] = time.time() - t0
|
||||
result["fbk_size"] = fbk.stat().st_size
|
||||
print(f"OK ({result['t_gbak']:.0f}s, {result['fbk_size']/1024/1024:.1f} MB)")
|
||||
|
||||
# 2) ZIP
|
||||
t1 = time.time()
|
||||
processed = 0
|
||||
fbk_size = result["fbk_size"]
|
||||
with zipfile.ZipFile(zipf, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
|
||||
zi = zipfile.ZipInfo(fbk.name)
|
||||
zi.compress_type = zipfile.ZIP_DEFLATED
|
||||
with zf.open(zi, "w", force_zip64=True) as z:
|
||||
with open(fbk, "rb") as src:
|
||||
while buf := src.read(CHUNK):
|
||||
z.write(buf)
|
||||
processed += len(buf)
|
||||
pct = processed * 100 / fbk_size
|
||||
print(f"\r ZIP {label}: {pct:6.2f}%", end="", flush=True)
|
||||
print()
|
||||
result["t_zip"] = time.time() - t1
|
||||
result["zip_size"] = zipf.stat().st_size
|
||||
|
||||
# 3) Delete FBK + LOG
|
||||
fbk.unlink()
|
||||
log.unlink()
|
||||
|
||||
result["ok"] = True
|
||||
return result
|
||||
|
||||
|
||||
def format_result(r: dict) -> str:
|
||||
ratio = 100 * (1 - r["zip_size"] / r["fbk_size"]) if r["fbk_size"] else 0
|
||||
return (
|
||||
f" {r['label']}: "
|
||||
f"FBK {r['fbk_size']/1024/1024:.1f} MB → "
|
||||
f"ZIP {r['zip_size']/1024/1024:.1f} MB "
|
||||
f"({ratio:.0f}% komprese, "
|
||||
f"gbak {r['t_gbak']:.0f}s, zip {r['t_zip']:.0f}s)"
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# MAIN
|
||||
# ============================================================
|
||||
|
||||
def main():
|
||||
BACKUP_DIR.mkdir(parents=True, exist_ok=True)
|
||||
now = datetime.now()
|
||||
ts = now.strftime("%Y-%m-%d_%H-%M-%S")
|
||||
|
||||
backed_up = []
|
||||
errors = []
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 1) Hlavní DB – MEDICUS.FDB
|
||||
# ----------------------------------------------------------
|
||||
fbk = BACKUP_DIR / f"MEDICUS_{ts}.fbk"
|
||||
zipf = BACKUP_DIR / f"MEDICUS_{ts}.zip"
|
||||
log = BACKUP_DIR / f"MEDICUS_{ts}.log"
|
||||
try:
|
||||
r = gbak_and_zip("MEDICUS", MAIN_DB, fbk, zipf, log)
|
||||
backed_up.append(r)
|
||||
except Exception:
|
||||
errors.append({"label": "MEDICUS", "error": traceback.format_exc()})
|
||||
for f in (fbk, log):
|
||||
if f.exists():
|
||||
f.unlink()
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 2) Externí DB – MEDICUS_FILES_*.fdb
|
||||
# ----------------------------------------------------------
|
||||
fdb_all = sorted(
|
||||
set(EXT_DIR.glob("MEDICUS_FILES_*.fdb")) | set(EXT_DIR.glob("MEDICUS_FILES_*.FDB")),
|
||||
key=lambda p: p.name.lower(),
|
||||
)
|
||||
|
||||
for fdb in fdb_all:
|
||||
name = fdb.stem
|
||||
fbk = BACKUP_DIR / f"{name}_{ts}.fbk"
|
||||
zipf = BACKUP_DIR / f"{name}_{ts}.zip"
|
||||
log = BACKUP_DIR / f"{name}_{ts}.log"
|
||||
db_conn = f"localhost/{FB_PORT}:{fdb}"
|
||||
try:
|
||||
r = gbak_and_zip(name, db_conn, fbk, zipf, log)
|
||||
backed_up.append(r)
|
||||
except Exception:
|
||||
errors.append({"label": name, "error": traceback.format_exc()})
|
||||
for f in (fbk, log):
|
||||
if f.exists():
|
||||
f.unlink()
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Report
|
||||
# ----------------------------------------------------------
|
||||
total = 1 + len(fdb_all)
|
||||
report = [
|
||||
f"Backup Medicus – {now.strftime('%d.%m.%Y %H:%M')}",
|
||||
f"Celkem DB: {total} | OK: {len(backed_up)} | Chyby: {len(errors)}",
|
||||
f"Výstupní adresář: {BACKUP_DIR}",
|
||||
"",
|
||||
]
|
||||
|
||||
if backed_up:
|
||||
report.append("--- Zálohováno ---")
|
||||
total_zip = sum(r["zip_size"] for r in backed_up)
|
||||
for r in backed_up:
|
||||
report.append(format_result(r))
|
||||
report.append(f" Celková velikost ZIP: {total_zip/1024/1024:.1f} MB")
|
||||
report.append("")
|
||||
|
||||
if errors:
|
||||
report.append("--- CHYBY ---")
|
||||
for e in errors:
|
||||
report.append(f" {e['label']}:\n{e['error']}")
|
||||
report.append("")
|
||||
|
||||
has_errors = bool(errors)
|
||||
subject = (
|
||||
f"{'X' if has_errors else 'OK'} MEDICUS backup "
|
||||
f"{len(backed_up)}/{total}"
|
||||
+ (f" – {len(errors)} chyb" if has_errors else "")
|
||||
)
|
||||
|
||||
send_mail(MAIL_TO, subject, "\n".join(report))
|
||||
print("\n" + "\n".join(report))
|
||||
|
||||
if errors:
|
||||
raise RuntimeError(f"{len(errors)} backup(s) failed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,235 @@
|
||||
import subprocess
|
||||
import os
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import zipfile
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from EmailMessagingGraph import send_mail
|
||||
|
||||
# ============================================================
|
||||
# CONFIG
|
||||
# ============================================================
|
||||
|
||||
GBAK = r"C:\Program Files\Firebird\Firebird_2_5_CGM\bin\gbak.exe"
|
||||
FB_USER = "SYSDBA"
|
||||
FB_PASS = "masterkey"
|
||||
FB_PORT = "3050"
|
||||
|
||||
MAIN_DB = r"localhost/3050:C:\medicus 3\data\MEDICUS.FDB"
|
||||
EXT_DIR = Path(r"c:\medicusext")
|
||||
BACKUP_DIR = Path(r"U:\medicusbackup")
|
||||
|
||||
MAIL_TO = "vladimir.buzalka@buzalka.cz"
|
||||
|
||||
CHUNK = 8 * 1024 * 1024 # 8 MB
|
||||
|
||||
|
||||
# ============================================================
|
||||
# HELPERS
|
||||
# ============================================================
|
||||
|
||||
def run_gbak(label: str, db_conn: str, fbk: Path, log: Path) -> dict:
|
||||
"""Run gbak, return result dict (without zip info)."""
|
||||
result = {
|
||||
"label": label,
|
||||
"ok": False,
|
||||
"fbk": fbk,
|
||||
"fbk_size": 0,
|
||||
"zip_size": 0,
|
||||
"t_gbak": 0,
|
||||
"t_zip": 0,
|
||||
"error": None,
|
||||
}
|
||||
print(f"GBAK: {label} ... ", end="", flush=True)
|
||||
t0 = time.time()
|
||||
cmd = [GBAK, "-b", "-user", FB_USER, "-pas", FB_PASS, db_conn, str(fbk), "-v"]
|
||||
with open(log, "w", encoding="utf-8") as f:
|
||||
subprocess.run(cmd, stdout=f, stderr=subprocess.STDOUT, check=True)
|
||||
result["t_gbak"] = time.time() - t0
|
||||
result["fbk_size"] = fbk.stat().st_size
|
||||
print(f"OK ({result['t_gbak']:.0f}s, {result['fbk_size']/1024/1024:.1f} MB)")
|
||||
result["ok"] = True
|
||||
return result
|
||||
|
||||
|
||||
def zip_single(label: str, fbk: Path, zipf: Path) -> tuple[int, float]:
|
||||
"""ZIP one FBK into its own ZIP. Returns (zip_size, t_zip)."""
|
||||
t1 = time.time()
|
||||
processed = 0
|
||||
fbk_size = fbk.stat().st_size
|
||||
with zipfile.ZipFile(zipf, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
|
||||
zi = zipfile.ZipInfo(fbk.name)
|
||||
zi.compress_type = zipfile.ZIP_DEFLATED
|
||||
with zf.open(zi, "w", force_zip64=True) as z:
|
||||
with open(fbk, "rb") as src:
|
||||
while buf := src.read(CHUNK):
|
||||
z.write(buf)
|
||||
processed += len(buf)
|
||||
pct = processed * 100 / fbk_size
|
||||
print(f"\r ZIP {label}: {pct:6.2f}%", end="", flush=True)
|
||||
print()
|
||||
return zipf.stat().st_size, time.time() - t1
|
||||
|
||||
|
||||
def zip_multiple(fbk_results: list[dict], zipf: Path) -> tuple[int, float]:
|
||||
"""ZIP multiple FBK files into one ZIP. Returns (zip_size, t_zip)."""
|
||||
t1 = time.time()
|
||||
total_fbk_size = sum(r["fbk_size"] for r in fbk_results)
|
||||
total_processed = 0
|
||||
with zipfile.ZipFile(zipf, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=9) as zf:
|
||||
for r in fbk_results:
|
||||
fbk = r["fbk"]
|
||||
zi = zipfile.ZipInfo(fbk.name)
|
||||
zi.compress_type = zipfile.ZIP_DEFLATED
|
||||
with zf.open(zi, "w", force_zip64=True) as z:
|
||||
with open(fbk, "rb") as src:
|
||||
while buf := src.read(CHUNK):
|
||||
z.write(buf)
|
||||
total_processed += len(buf)
|
||||
pct = total_processed * 100 / total_fbk_size
|
||||
print(f"\r ZIP {fbk.name}: {pct:6.2f}%", end="", flush=True)
|
||||
print()
|
||||
return zipf.stat().st_size, time.time() - t1
|
||||
|
||||
|
||||
def format_result(r: dict) -> str:
|
||||
ratio = 100 * (1 - r["zip_size"] / r["fbk_size"]) if r["fbk_size"] else 0
|
||||
return (
|
||||
f" {r['label']}: "
|
||||
f"FBK {r['fbk_size']/1024/1024:.1f} MB → "
|
||||
f"ZIP {r['zip_size']/1024/1024:.1f} MB "
|
||||
f"({ratio:.0f}% komprese, "
|
||||
f"gbak {r['t_gbak']:.0f}s, zip {r['t_zip']:.0f}s)"
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# MAIN
|
||||
# ============================================================
|
||||
|
||||
def main():
|
||||
BACKUP_DIR.mkdir(parents=True, exist_ok=True)
|
||||
now = datetime.now()
|
||||
ts = now.strftime("%Y-%m-%d_%H-%M-%S")
|
||||
|
||||
backed_up = []
|
||||
errors = []
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 1) Hlavní DB – MEDICUS.FDB → vlastní ZIP
|
||||
# ----------------------------------------------------------
|
||||
fbk = BACKUP_DIR / f"MEDICUS_{ts}.fbk"
|
||||
zipf = BACKUP_DIR / f"MEDICUS_{ts}.zip"
|
||||
log = BACKUP_DIR / f"MEDICUS_{ts}.log"
|
||||
try:
|
||||
r = run_gbak("MEDICUS", MAIN_DB, fbk, log)
|
||||
log.unlink()
|
||||
zip_size, t_zip = zip_single("MEDICUS", fbk, zipf)
|
||||
fbk.unlink()
|
||||
r["zip_size"] = zip_size
|
||||
r["t_zip"] = t_zip
|
||||
backed_up.append(r)
|
||||
except Exception:
|
||||
errors.append({"label": "MEDICUS", "fbk_size": 0, "zip_size": 0, "t_gbak": 0, "t_zip": 0, "error": traceback.format_exc()})
|
||||
for f in (fbk, log):
|
||||
if f.exists():
|
||||
f.unlink()
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# 2) Externí DB – MEDICUS_FILES_*.fdb → všechny do jednoho ZIP
|
||||
# ----------------------------------------------------------
|
||||
fdb_all = sorted(
|
||||
set(EXT_DIR.glob("MEDICUS_FILES_*.fdb")) | set(EXT_DIR.glob("MEDICUS_FILES_*.FDB")),
|
||||
key=lambda p: p.name.lower(),
|
||||
)
|
||||
|
||||
ext_results = []
|
||||
for fdb in fdb_all:
|
||||
name = fdb.stem
|
||||
fbk = BACKUP_DIR / f"{name}_{ts}.fbk"
|
||||
log = BACKUP_DIR / f"{name}_{ts}.log"
|
||||
db_conn = f"localhost/{FB_PORT}:{fdb}"
|
||||
try:
|
||||
r = run_gbak(name, db_conn, fbk, log)
|
||||
log.unlink()
|
||||
ext_results.append(r)
|
||||
except Exception:
|
||||
errors.append({"label": name, "fbk_size": 0, "zip_size": 0, "t_gbak": 0, "t_zip": 0, "error": traceback.format_exc()})
|
||||
for f in (fbk, log):
|
||||
if f.exists():
|
||||
f.unlink()
|
||||
|
||||
# ZIP všechny externí FBK do jednoho souboru
|
||||
if ext_results:
|
||||
ext_zip = BACKUP_DIR / f"MEDICUS_FILES_{ts}.zip"
|
||||
print(f"\nZIP externích DB → {ext_zip.name}")
|
||||
try:
|
||||
zip_size, t_zip = zip_multiple(ext_results, ext_zip)
|
||||
for r in ext_results:
|
||||
r["zip_size"] = zip_size # sdílená velikost výsledného ZIPu
|
||||
r["t_zip"] = t_zip
|
||||
r["fbk"].unlink()
|
||||
backed_up.append(r)
|
||||
except Exception:
|
||||
errors.append({"label": "MEDICUS_FILES (zip)", "fbk_size": 0, "zip_size": 0, "t_gbak": 0, "t_zip": 0, "error": traceback.format_exc()})
|
||||
for r in ext_results:
|
||||
if r["fbk"].exists():
|
||||
r["fbk"].unlink()
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Report
|
||||
# ----------------------------------------------------------
|
||||
total = 1 + len(fdb_all)
|
||||
report = [
|
||||
f"Backup Medicus – {now.strftime('%d.%m.%Y %H:%M')}",
|
||||
f"Celkem DB: {total} | OK: {len(backed_up)} | Chyby: {len(errors)}",
|
||||
f"Výstupní adresář: {BACKUP_DIR}",
|
||||
"",
|
||||
]
|
||||
|
||||
if backed_up:
|
||||
report.append("--- Zálohováno ---")
|
||||
# Hlavní DB
|
||||
main_results = [r for r in backed_up if r["label"] == "MEDICUS"]
|
||||
ext_backed = [r for r in backed_up if r["label"] != "MEDICUS"]
|
||||
for r in main_results:
|
||||
report.append(format_result(r))
|
||||
if ext_backed:
|
||||
total_ext_fbk = sum(r["fbk_size"] for r in ext_backed)
|
||||
ext_zip_size = ext_backed[0]["zip_size"] if ext_backed else 0
|
||||
ratio = 100 * (1 - ext_zip_size / total_ext_fbk) if total_ext_fbk else 0
|
||||
report.append(f" Externí DB ({len(ext_backed)} souborů):")
|
||||
for r in ext_backed:
|
||||
report.append(f" {r['label']}: FBK {r['fbk_size']/1024/1024:.1f} MB (gbak {r['t_gbak']:.0f}s)")
|
||||
report.append(
|
||||
f" → společný ZIP: {ext_zip_size/1024/1024:.1f} MB "
|
||||
f"({ratio:.0f}% komprese, zip {ext_backed[0]['t_zip']:.0f}s)"
|
||||
)
|
||||
total_zip = sum(r["zip_size"] for r in main_results) + (ext_backed[0]["zip_size"] if ext_backed else 0)
|
||||
report.append(f" Celková velikost ZIP: {total_zip/1024/1024:.1f} MB")
|
||||
report.append("")
|
||||
|
||||
if errors:
|
||||
report.append("--- CHYBY ---")
|
||||
for e in errors:
|
||||
report.append(f" {e['label']}:\n{e['error']}")
|
||||
report.append("")
|
||||
|
||||
has_errors = bool(errors)
|
||||
subject = (
|
||||
f"{'X' if has_errors else 'OK'} MEDICUS backup "
|
||||
f"{len(backed_up)}/{total}"
|
||||
+ (f" – {len(errors)} chyb" if has_errors else "")
|
||||
)
|
||||
|
||||
send_mail(MAIL_TO, subject, "\n".join(report))
|
||||
print("\n" + "\n".join(report))
|
||||
|
||||
if errors:
|
||||
raise RuntimeError(f"{len(errors)} backup(s) failed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,172 @@
|
||||
import subprocess
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import zipfile
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from EmailMessagingGraph import send_mail
|
||||
|
||||
# ============================================================
|
||||
# CONFIG
|
||||
# ============================================================
|
||||
|
||||
GBAK = r"C:\Program Files\Firebird\Firebird_2_5_CGM\bin\gbak.exe"
|
||||
FB_USER = "SYSDBA"
|
||||
FB_PASS = "masterkey"
|
||||
FB_PORT = "3050"
|
||||
|
||||
SRC_DIR = Path(r"c:\medicusext")
|
||||
BACKUP_DIR = Path(r"U:\medicusbackup")
|
||||
|
||||
MAIL_TO = "vladimir.buzalka@buzalka.cz"
|
||||
|
||||
CHUNK = 8 * 1024 * 1024 # 8 MB
|
||||
|
||||
|
||||
# ============================================================
|
||||
# MAIN
|
||||
# ============================================================
|
||||
|
||||
def main():
|
||||
BACKUP_DIR.mkdir(parents=True, exist_ok=True)
|
||||
now = datetime.now()
|
||||
ts = now.strftime("%Y-%m-%d_%H-%M-%S")
|
||||
|
||||
# Find all FDB files (case-insensitive)
|
||||
fdb_files = sorted(SRC_DIR.glob("MEDICUS_FILES_*.fdb"))
|
||||
fdb_upper = sorted(SRC_DIR.glob("MEDICUS_FILES_*.FDB"))
|
||||
fdb_all = sorted(
|
||||
set(fdb_files + fdb_upper),
|
||||
key=lambda p: p.name.lower(),
|
||||
)
|
||||
|
||||
backed_up = []
|
||||
errors = []
|
||||
|
||||
for fdb in fdb_all:
|
||||
name = fdb.stem
|
||||
fbk = BACKUP_DIR / f"{name}_{ts}.fbk"
|
||||
zipf = BACKUP_DIR / f"{name}_{ts}.zip"
|
||||
log = BACKUP_DIR / f"{name}_{ts}.log"
|
||||
|
||||
result = {
|
||||
"file": fdb.name,
|
||||
"ok": False,
|
||||
"fbk_size": 0,
|
||||
"zip_size": 0,
|
||||
"t_gbak": 0,
|
||||
"t_zip": 0,
|
||||
"error": None,
|
||||
}
|
||||
|
||||
try:
|
||||
# 1) GBAK
|
||||
print(f"GBAK: {fdb.name} ... ", end="", flush=True)
|
||||
t0 = time.time()
|
||||
db_conn = f"localhost/{FB_PORT}:{fdb}"
|
||||
cmd = [
|
||||
GBAK, "-b",
|
||||
"-user", FB_USER,
|
||||
"-pas", FB_PASS,
|
||||
db_conn, str(fbk),
|
||||
"-v",
|
||||
]
|
||||
with open(log, "w", encoding="utf-8") as f:
|
||||
subprocess.run(
|
||||
cmd, stdout=f, stderr=subprocess.STDOUT, check=True,
|
||||
)
|
||||
result["t_gbak"] = time.time() - t0
|
||||
result["fbk_size"] = fbk.stat().st_size
|
||||
print(f"OK ({result['t_gbak']:.0f}s)")
|
||||
|
||||
# 2) ZIP
|
||||
t1 = time.time()
|
||||
processed = 0
|
||||
fbk_size = result["fbk_size"]
|
||||
|
||||
with zipfile.ZipFile(
|
||||
zipf, "w",
|
||||
compression=zipfile.ZIP_DEFLATED,
|
||||
compresslevel=9,
|
||||
) as zf:
|
||||
zi = zipfile.ZipInfo(fbk.name)
|
||||
zi.compress_type = zipfile.ZIP_DEFLATED
|
||||
with zf.open(zi, "w", force_zip64=True) as z:
|
||||
with open(fbk, "rb") as src:
|
||||
while buf := src.read(CHUNK):
|
||||
z.write(buf)
|
||||
processed += len(buf)
|
||||
pct = processed * 100 / fbk_size
|
||||
print(
|
||||
f"\r ZIP {name}: {pct:6.2f}%",
|
||||
end="", flush=True,
|
||||
)
|
||||
print()
|
||||
|
||||
result["t_zip"] = time.time() - t1
|
||||
result["zip_size"] = zipf.stat().st_size
|
||||
|
||||
# 3) DELETE FBK + LOG
|
||||
fbk.unlink()
|
||||
log.unlink()
|
||||
|
||||
result["ok"] = True
|
||||
backed_up.append(result)
|
||||
|
||||
except Exception:
|
||||
result["error"] = traceback.format_exc()
|
||||
errors.append(result)
|
||||
for f in (fbk, log):
|
||||
if f.exists():
|
||||
f.unlink()
|
||||
|
||||
# Build report
|
||||
report = []
|
||||
report.append(f"Backup externi DB - {now.strftime('%d.%m.%Y %H:%M')}")
|
||||
report.append(f"Celkem souboru: {len(fdb_all)}")
|
||||
report.append(f"Zalohovano: {len(backed_up)}")
|
||||
report.append(f"Chyby: {len(errors)}")
|
||||
report.append("")
|
||||
|
||||
if backed_up:
|
||||
report.append("--- Backed up ---")
|
||||
total_zip = 0
|
||||
for r in backed_up:
|
||||
total_zip += r["zip_size"]
|
||||
report.append(
|
||||
f" {r['file']}: "
|
||||
f"FBK {r['fbk_size']/1024/1024:.1f} MB -> "
|
||||
f"ZIP {r['zip_size']/1024/1024:.1f} MB "
|
||||
f"(gbak {r['t_gbak']:.0f}s, zip {r['t_zip']:.0f}s)"
|
||||
)
|
||||
report.append(f" Total ZIP: {total_zip / 1024 / 1024:.1f} MB")
|
||||
report.append("")
|
||||
|
||||
if errors:
|
||||
report.append("--- ERRORS ---")
|
||||
for r in errors:
|
||||
report.append(f" {r['file']}: {r['error']}")
|
||||
report.append("")
|
||||
|
||||
# Send email
|
||||
has_errors = len(errors) > 0
|
||||
subject = (
|
||||
f"{'X' if has_errors else 'OK'} "
|
||||
f"MEDICUS externi DB - "
|
||||
f"backup {len(backed_up)}/{len(fdb_all)}"
|
||||
f"{f', {len(errors)} errors' if has_errors else ''}"
|
||||
)
|
||||
|
||||
send_mail(MAIL_TO, subject, "\n".join(report))
|
||||
|
||||
print("\n" + "\n".join(report))
|
||||
|
||||
if errors:
|
||||
raise RuntimeError(f"{len(errors)} backup(s) failed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,172 @@
|
||||
import subprocess
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import zipfile
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from EmailMessagingGraph import send_mail
|
||||
|
||||
# ============================================================
|
||||
# CONFIG
|
||||
# ============================================================
|
||||
|
||||
GBAK = r"C:\Program Files\Firebird\Firebird_2_5_CGM\bin\gbak.exe"
|
||||
FB_USER = "SYSDBA"
|
||||
FB_PASS = "masterkey"
|
||||
FB_PORT = "3050"
|
||||
|
||||
SRC_DIR = Path(r"c:\medicusext")
|
||||
BACKUP_DIR = Path(r"U:\medicusbackup")
|
||||
|
||||
MAIL_TO = "vladimir.buzalka@buzalka.cz"
|
||||
|
||||
CHUNK = 8 * 1024 * 1024 # 8 MB
|
||||
|
||||
|
||||
# ============================================================
|
||||
# MAIN
|
||||
# ============================================================
|
||||
|
||||
def main():
|
||||
BACKUP_DIR.mkdir(parents=True, exist_ok=True)
|
||||
now = datetime.now()
|
||||
ts = now.strftime("%Y-%m-%d_%H-%M-%S")
|
||||
|
||||
# Find all FDB files (case-insensitive)
|
||||
fdb_files = sorted(SRC_DIR.glob("MEDICUS_FILES_*.fdb"))
|
||||
fdb_upper = sorted(SRC_DIR.glob("MEDICUS_FILES_*.FDB"))
|
||||
fdb_all = sorted(
|
||||
set(fdb_files + fdb_upper),
|
||||
key=lambda p: p.name.lower(),
|
||||
)
|
||||
|
||||
backed_up = []
|
||||
errors = []
|
||||
|
||||
for fdb in fdb_all:
|
||||
name = fdb.stem
|
||||
fbk = BACKUP_DIR / f"{name}_{ts}.fbk"
|
||||
zipf = BACKUP_DIR / f"{name}_{ts}.zip"
|
||||
log = BACKUP_DIR / f"{name}_{ts}.log"
|
||||
|
||||
result = {
|
||||
"file": fdb.name,
|
||||
"ok": False,
|
||||
"fbk_size": 0,
|
||||
"zip_size": 0,
|
||||
"t_gbak": 0,
|
||||
"t_zip": 0,
|
||||
"error": None,
|
||||
}
|
||||
|
||||
try:
|
||||
# 1) GBAK
|
||||
print(f"GBAK: {fdb.name} ... ", end="", flush=True)
|
||||
t0 = time.time()
|
||||
db_conn = f"localhost/{FB_PORT}:{fdb}"
|
||||
cmd = [
|
||||
GBAK, "-b",
|
||||
"-user", FB_USER,
|
||||
"-pas", FB_PASS,
|
||||
db_conn, str(fbk),
|
||||
"-v",
|
||||
]
|
||||
with open(log, "w", encoding="utf-8") as f:
|
||||
subprocess.run(
|
||||
cmd, stdout=f, stderr=subprocess.STDOUT, check=True,
|
||||
)
|
||||
result["t_gbak"] = time.time() - t0
|
||||
result["fbk_size"] = fbk.stat().st_size
|
||||
print(f"OK ({result['t_gbak']:.0f}s)")
|
||||
|
||||
# 2) ZIP
|
||||
t1 = time.time()
|
||||
processed = 0
|
||||
fbk_size = result["fbk_size"]
|
||||
|
||||
with zipfile.ZipFile(
|
||||
zipf, "w",
|
||||
compression=zipfile.ZIP_DEFLATED,
|
||||
compresslevel=9,
|
||||
) as zf:
|
||||
zi = zipfile.ZipInfo(fbk.name)
|
||||
zi.compress_type = zipfile.ZIP_DEFLATED
|
||||
with zf.open(zi, "w", force_zip64=True) as z:
|
||||
with open(fbk, "rb") as src:
|
||||
while buf := src.read(CHUNK):
|
||||
z.write(buf)
|
||||
processed += len(buf)
|
||||
pct = processed * 100 / fbk_size
|
||||
print(
|
||||
f"\r ZIP {name}: {pct:6.2f}%",
|
||||
end="", flush=True,
|
||||
)
|
||||
print()
|
||||
|
||||
result["t_zip"] = time.time() - t1
|
||||
result["zip_size"] = zipf.stat().st_size
|
||||
|
||||
# 3) DELETE FBK + LOG
|
||||
fbk.unlink()
|
||||
log.unlink()
|
||||
|
||||
result["ok"] = True
|
||||
backed_up.append(result)
|
||||
|
||||
except Exception:
|
||||
result["error"] = traceback.format_exc()
|
||||
errors.append(result)
|
||||
for f in (fbk, log):
|
||||
if f.exists():
|
||||
f.unlink()
|
||||
|
||||
# Build report
|
||||
report = []
|
||||
report.append(f"Backup externi DB - {now.strftime('%d.%m.%Y %H:%M')}")
|
||||
report.append(f"Celkem souboru: {len(fdb_all)}")
|
||||
report.append(f"Zalohovano: {len(backed_up)}")
|
||||
report.append(f"Chyby: {len(errors)}")
|
||||
report.append("")
|
||||
|
||||
if backed_up:
|
||||
report.append("--- Backed up ---")
|
||||
total_zip = 0
|
||||
for r in backed_up:
|
||||
total_zip += r["zip_size"]
|
||||
report.append(
|
||||
f" {r['file']}: "
|
||||
f"FBK {r['fbk_size']/1024/1024:.1f} MB -> "
|
||||
f"ZIP {r['zip_size']/1024/1024:.1f} MB "
|
||||
f"(gbak {r['t_gbak']:.0f}s, zip {r['t_zip']:.0f}s)"
|
||||
)
|
||||
report.append(f" Total ZIP: {total_zip / 1024 / 1024:.1f} MB")
|
||||
report.append("")
|
||||
|
||||
if errors:
|
||||
report.append("--- ERRORS ---")
|
||||
for r in errors:
|
||||
report.append(f" {r['file']}: {r['error']}")
|
||||
report.append("")
|
||||
|
||||
# Send email
|
||||
has_errors = len(errors) > 0
|
||||
subject = (
|
||||
f"{'X' if has_errors else 'OK'} "
|
||||
f"MEDICUS externi DB - "
|
||||
f"backup {len(backed_up)}/{len(fdb_all)}"
|
||||
f"{f', {len(errors)} errors' if has_errors else ''}"
|
||||
)
|
||||
|
||||
send_mail(MAIL_TO, subject, "\n".join(report))
|
||||
|
||||
print("\n" + "\n".join(report))
|
||||
|
||||
if errors:
|
||||
raise RuntimeError(f"{len(errors)} backup(s) failed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,189 @@
|
||||
import subprocess
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import zipfile
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from EmailMessagingGraph import send_mail
|
||||
|
||||
# ============================================================
|
||||
# CONFIG
|
||||
# ============================================================
|
||||
|
||||
GBAK = r"C:\Program Files\Firebird\Firebird_2_5_CGM\bin\gbak.exe"
|
||||
FB_USER = "SYSDBA"
|
||||
FB_PASS = "masterkey"
|
||||
FB_PORT = "3050"
|
||||
|
||||
SRC_DIR = Path(r"c:\medicusext")
|
||||
BACKUP_DIR = Path(r"U:\medicusbackup")
|
||||
|
||||
MAIL_TO = "vladimir.buzalka@buzalka.cz"
|
||||
|
||||
CHUNK = 8 * 1024 * 1024 # 8 MB
|
||||
|
||||
|
||||
# ============================================================
|
||||
# MAIN
|
||||
# ============================================================
|
||||
|
||||
def main():
|
||||
BACKUP_DIR.mkdir(parents=True, exist_ok=True)
|
||||
now = datetime.now()
|
||||
ts = now.strftime("%Y-%m-%d_%H-%M-%S")
|
||||
|
||||
# Find all FDB files (case-insensitive)
|
||||
fdb_files = sorted(SRC_DIR.glob("MEDICUS_FILES_*.fdb"))
|
||||
fdb_upper = sorted(SRC_DIR.glob("MEDICUS_FILES_*.FDB"))
|
||||
fdb_all = sorted(
|
||||
set(fdb_files + fdb_upper),
|
||||
key=lambda p: p.name.lower(),
|
||||
)
|
||||
|
||||
backed_up = []
|
||||
errors = []
|
||||
fbk_paths = [] # FBK files to be zipped together
|
||||
|
||||
# --------------------------------------------------------
|
||||
# 1) GBAK all databases
|
||||
# --------------------------------------------------------
|
||||
for fdb in fdb_all:
|
||||
name = fdb.stem
|
||||
fbk = BACKUP_DIR / f"{name}_{ts}.fbk"
|
||||
log = BACKUP_DIR / f"{name}_{ts}.log"
|
||||
|
||||
result = {
|
||||
"file": fdb.name,
|
||||
"ok": False,
|
||||
"fbk_size": 0,
|
||||
"zip_size": 0,
|
||||
"t_gbak": 0,
|
||||
"t_zip": 0,
|
||||
"error": None,
|
||||
}
|
||||
|
||||
try:
|
||||
print(f"GBAK: {fdb.name} ... ", end="", flush=True)
|
||||
t0 = time.time()
|
||||
db_conn = f"localhost/{FB_PORT}:{fdb}"
|
||||
cmd = [
|
||||
GBAK, "-b",
|
||||
"-user", FB_USER,
|
||||
"-pas", FB_PASS,
|
||||
db_conn, str(fbk),
|
||||
"-v",
|
||||
]
|
||||
with open(log, "w", encoding="utf-8") as f:
|
||||
subprocess.run(
|
||||
cmd, stdout=f, stderr=subprocess.STDOUT, check=True,
|
||||
)
|
||||
result["t_gbak"] = time.time() - t0
|
||||
result["fbk_size"] = fbk.stat().st_size
|
||||
print(f"OK ({result['t_gbak']:.0f}s)")
|
||||
|
||||
# Delete log, keep FBK for zipping
|
||||
log.unlink()
|
||||
|
||||
result["ok"] = True
|
||||
fbk_paths.append((fbk, result))
|
||||
backed_up.append(result)
|
||||
|
||||
except Exception:
|
||||
result["error"] = traceback.format_exc()
|
||||
errors.append(result)
|
||||
for f in (fbk, log):
|
||||
if f.exists():
|
||||
f.unlink()
|
||||
|
||||
# --------------------------------------------------------
|
||||
# 2) ZIP all FBK files into one archive
|
||||
# --------------------------------------------------------
|
||||
total_zip_size = 0
|
||||
if fbk_paths:
|
||||
zip_path = BACKUP_DIR / f"MEDICUS_FILES_{ts}.zip"
|
||||
print(f"\nZIP: {zip_path.name}")
|
||||
t_zip_start = time.time()
|
||||
|
||||
# Calculate total size for progress
|
||||
total_fbk_size = sum(fbk.stat().st_size for fbk, _ in fbk_paths)
|
||||
total_processed = 0
|
||||
|
||||
with zipfile.ZipFile(
|
||||
zip_path, "w",
|
||||
compression=zipfile.ZIP_DEFLATED,
|
||||
compresslevel=9,
|
||||
) as zf:
|
||||
for fbk, result in fbk_paths:
|
||||
zi = zipfile.ZipInfo(fbk.name)
|
||||
zi.compress_type = zipfile.ZIP_DEFLATED
|
||||
with zf.open(zi, "w", force_zip64=True) as z:
|
||||
with open(fbk, "rb") as src:
|
||||
while buf := src.read(CHUNK):
|
||||
z.write(buf)
|
||||
total_processed += len(buf)
|
||||
pct = total_processed * 100 / total_fbk_size
|
||||
print(
|
||||
f"\r {fbk.name}: {pct:6.2f}%",
|
||||
end="", flush=True,
|
||||
)
|
||||
print()
|
||||
|
||||
t_zip_total = time.time() - t_zip_start
|
||||
total_zip_size = zip_path.stat().st_size
|
||||
print(f"ZIP OK ({t_zip_total:.0f}s, {total_zip_size/1024/1024:.1f} MB)")
|
||||
|
||||
# Fill zip_size into each result and delete FBK files
|
||||
for fbk, result in fbk_paths:
|
||||
result["zip_size"] = total_zip_size
|
||||
fbk.unlink()
|
||||
|
||||
# --------------------------------------------------------
|
||||
# Build report
|
||||
# --------------------------------------------------------
|
||||
report = []
|
||||
report.append(f"Backup externi DB - {now.strftime('%d.%m.%Y %H:%M')}")
|
||||
report.append(f"Celkem souboru: {len(fdb_all)}")
|
||||
report.append(f"Zalohovano: {len(backed_up)}")
|
||||
report.append(f"Chyby: {len(errors)}")
|
||||
report.append("")
|
||||
|
||||
if backed_up:
|
||||
report.append("--- Backed up ---")
|
||||
total_fbk_mb = sum(r["fbk_size"] for r in backed_up) / 1024 / 1024
|
||||
for r in backed_up:
|
||||
report.append(
|
||||
f" {r['file']}: "
|
||||
f"FBK {r['fbk_size']/1024/1024:.1f} MB "
|
||||
f"(gbak {r['t_gbak']:.0f}s)"
|
||||
)
|
||||
report.append(f" Total FBK: {total_fbk_mb:.1f} MB -> ZIP: {total_zip_size/1024/1024:.1f} MB")
|
||||
report.append("")
|
||||
|
||||
if errors:
|
||||
report.append("--- ERRORS ---")
|
||||
for r in errors:
|
||||
report.append(f" {r['file']}: {r['error']}")
|
||||
report.append("")
|
||||
|
||||
# Send email
|
||||
has_errors = len(errors) > 0
|
||||
subject = (
|
||||
f"{'X' if has_errors else 'OK'} "
|
||||
f"MEDICUS externi DB - "
|
||||
f"backup {len(backed_up)}/{len(fdb_all)}"
|
||||
f"{f', {len(errors)} errors' if has_errors else ''}"
|
||||
)
|
||||
|
||||
send_mail(MAIL_TO, subject, "\n".join(report))
|
||||
|
||||
print("\n" + "\n".join(report))
|
||||
|
||||
if errors:
|
||||
raise RuntimeError(f"{len(errors)} backup(s) failed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
Upravená verze skriptu, prosím upravte si cesty, toto vše vložit do nějakého cmd souboru:
|
||||
editují se pouze následující sekce:
|
||||
rem /zalozeni slozek do kterych se ma zalohovat kdyz neexistuji/
|
||||
rem /nastaveni serveru a portu a cest k db/
|
||||
rem /zaloha/ - v případě že je soubor gbak umístěn jinde, je jiná cesta k Firebirdu
|
||||
rem/odkud kam kopirovat/
|
||||
@echo off
|
||||
rem /nacteni aktualniho datumu a casu/
|
||||
For /f "tokens=1-3 delims=/." %%a in ("%DATE%") do (set mydate=%%c-%%b-%%a)
|
||||
For /f "tokens=1-2 delims=/:" %%a in ("%TIME%") do (set mytime=%%a-%%b)
|
||||
@echo on
|
||||
rem /zalozeni slozek do kterych se ma zalohovat kdyz neexistuji/
|
||||
if not exist "C:\zalohy\MEDICUS" mkdir "C:\zalohy\MEDICUS"
|
||||
if not exist "C:\backup\MEDICUS" mkdir "C:\backup\MEDICUS"
|
||||
rem /nastaveni serveru a portu a cest k db/
|
||||
set ufdb="localhost/3050:c:\Medicus\Medicus Komfort\data\sestavy.fdb"
|
||||
rem /nastaveni cesty pro umisteni zalohy/
|
||||
set ufbk="C:\zalohy\MEDICUS\MEDICUS3_%mydate%%mytime%.FBK" -y "C:\zalohy\MEDICUS\log%mydate%_%mytime%.txt"
|
||||
rem /zaloha/
|
||||
"C:\Program Files (x86)\Firebird\Firebird_2_5\bin\gbak" -b -user SYSDBA -pas masterke %ufdb% %ufbk% -v
|
||||
rem/odkud kam kopirovat/
|
||||
set odkud="C:\zalohy\MEDICUS*.*"
|
||||
set kam="C:\backup\MEDICUS"
|
||||
SET drv1=%odkud:~1,1%
|
||||
SET drv2=%kam:~1,1%
|
||||
if %drv1%==%drv2% goto :move
|
||||
if not %drv1%==%drv2% goto :copy
|
||||
:move
|
||||
rem /presunuti do jineho umisteni/
|
||||
move /Y %odkud% %kam%
|
||||
goto :exit
|
||||
:copy
|
||||
rem /kopiruj a smaz/
|
||||
copy /Y %odkud% %kam%
|
||||
del /Q %odkud%
|
||||
goto :exit
|
||||
:exit
|
||||
timeout /T 5
|
||||
exit
|
||||
Zajímavé odkazy rovněž zde:
|
||||
https://docplayer.cz/665150-Databaze-firebird-zalohovani-obnoveni-roman-fic-1-29-14-databaze.html
|
||||
http://www.epos.cz/obis4wiki/firebird:zalohovani_databazi_-_gbak
|
||||
Pohodový den přeje
|
||||
Mgr. Josef Kauc
|
||||
KONZULTANT MEDICUS
|
||||
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
EmailMessagingGraph.py
|
||||
----------------------
|
||||
Private Microsoft Graph mail sender
|
||||
Application permissions, shared mailbox
|
||||
"""
|
||||
|
||||
import msal
|
||||
import requests
|
||||
from functools import lru_cache
|
||||
from typing import Union, List
|
||||
|
||||
|
||||
# =========================
|
||||
# PRIVATE CONFIG (ONLY YOU)
|
||||
# =========================
|
||||
TENANT_ID = "7d269944-37a4-43a1-8140-c7517dc426e9"
|
||||
CLIENT_ID = "4b222bfd-78c9-4239-a53f-43006b3ed07f"
|
||||
CLIENT_SECRET = "Txg8Q~MjhocuopxsJyJBhPmDfMxZ2r5WpTFj1dfk"
|
||||
SENDER = "reports@buzalka.cz"
|
||||
|
||||
|
||||
AUTHORITY = f"https://login.microsoftonline.com/{TENANT_ID}"
|
||||
SCOPE = ["https://graph.microsoft.com/.default"]
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _get_token() -> str:
|
||||
app = msal.ConfidentialClientApplication(
|
||||
CLIENT_ID,
|
||||
authority=AUTHORITY,
|
||||
client_credential=CLIENT_SECRET,
|
||||
)
|
||||
|
||||
token = app.acquire_token_for_client(scopes=SCOPE)
|
||||
|
||||
if "access_token" not in token:
|
||||
raise RuntimeError(f"Graph auth failed: {token}")
|
||||
|
||||
return token["access_token"]
|
||||
|
||||
|
||||
def send_mail(
|
||||
to: Union[str, List[str]],
|
||||
subject: str,
|
||||
body: str,
|
||||
*,
|
||||
html: bool = False,
|
||||
):
|
||||
"""
|
||||
Send email via Microsoft Graph.
|
||||
|
||||
:param to: email or list of emails
|
||||
:param subject: subject
|
||||
:param body: email body
|
||||
:param html: True = HTML, False = plain text
|
||||
"""
|
||||
|
||||
if isinstance(to, str):
|
||||
to = [to]
|
||||
|
||||
payload = {
|
||||
"message": {
|
||||
"subject": subject,
|
||||
"body": {
|
||||
"contentType": "HTML" if html else "Text",
|
||||
"content": body,
|
||||
},
|
||||
"toRecipients": [
|
||||
{"emailAddress": {"address": addr}} for addr in to
|
||||
],
|
||||
},
|
||||
"saveToSentItems": "true",
|
||||
}
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {_get_token()}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
r = requests.post(
|
||||
f"https://graph.microsoft.com/v1.0/users/{SENDER}/sendMail",
|
||||
headers=headers,
|
||||
json=payload,
|
||||
timeout=30,
|
||||
)
|
||||
|
||||
if r.status_code != 202:
|
||||
raise RuntimeError(
|
||||
f"sendMail failed [{r.status_code}]: {r.text}"
|
||||
)
|
||||
@@ -0,0 +1,51 @@
|
||||
# Medicus + Claude – kontext projektu
|
||||
|
||||
## Přečti si prosím tyto soubory na začátku každé konverzace
|
||||
|
||||
1. `MedicusWithClaude/CLAUDE_NOTES.md` – hlavní poznámky: DB připojení, klíčové tabulky, RTF formát, import pipeline
|
||||
2. `MedicusWithClaudeSelects/SELECTS.md` – SQL dotazy (registrovaní pacienti atd.)
|
||||
3. `MedicusWithClaudeSelects/FakturaceADavky.md` – tabulky FAK, FAKDET, FAKDAV, PORTAL, kódování dávek
|
||||
|
||||
## O projektu
|
||||
|
||||
Firebird DB lékařského SW **Medicus** pro praktického lékaře. Lékařka je **MUDr. Buzalková Michaela** (IDUZI=4). Vladimír Buzalka (IDUZI=6) je manžel a správce systému – s ním probíhají tyto konverzace.
|
||||
|
||||
## Připojení k DB
|
||||
|
||||
```python
|
||||
import fdb
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
```
|
||||
|
||||
## Počítač "reporter"
|
||||
|
||||
Na tomto stroji běží pravidelná automatická tvorba reportů. Připojení k DB funguje stejně jako výše (ostrá DB, čerstvá data). Hlavní report skript: `MedicusWithClaudeFaktury/faktury_report.py`
|
||||
|
||||
### Co report dělá
|
||||
- Generuje Excel s listy: **FAK**, **FAKDET**, **PORTAL**, **PORTAL_DATA**
|
||||
- Ukládá do `u:\Dropbox\!!!Days\Downloads Z230\`
|
||||
- Název souboru: `YYYY-MM-DD_HH-MM-SS_faktury.xlsx`
|
||||
- Maže předchozí verze ze stejné složky
|
||||
- Řazení: nejnovější záznamy nahoře
|
||||
- Hyperlinky: FAK → FAKDET, PORTAL ↔ PORTAL_DATA
|
||||
|
||||
### Důležité – kódování dávek
|
||||
KDAVKA/FDAVKA jsou v **CP852** (DOS). fdb je vrací jako string dekódovaný win1250 – nutno re-enkódovat:
|
||||
```python
|
||||
spravny_text = s.encode('cp1250', errors='replace').decode('cp852', errors='replace')
|
||||
```
|
||||
|
||||
## Klíčové adresáře
|
||||
|
||||
| Adresář | Obsah |
|
||||
|---|---|
|
||||
| `MedicusWithClaude/` | průzkumné skripty, import pipeline (s03soubory.py) |
|
||||
| `MedicusWithClaudeSelects/` | SQL dotazy, dokumentace tabulek |
|
||||
| `MedicusWithClaudeFaktury/` | fakturační reporty |
|
||||
|
||||
## Co chceme na reporteru nastavit
|
||||
|
||||
Pravidelná automatická tvorba reportu `faktury_report.py` – např. každý den ráno, aby byl vždy čerstvý Excel s fakturami v Dropboxu.
|
||||
@@ -0,0 +1,226 @@
|
||||
"""
|
||||
msg_to_clipboard.py
|
||||
Převede Outlook .msg email (i vlákno odpovědí) do formátu:
|
||||
DD.MM.YYYY HH:MM od Jméno: text
|
||||
a zkopíruje do schránky.
|
||||
|
||||
Použití:
|
||||
python msg_to_clipboard.py "cesta/k/souboru.msg"
|
||||
nebo spustit bez argumentu -> otevře dialog pro výběr souboru
|
||||
"""
|
||||
|
||||
import sys
|
||||
import re
|
||||
import tkinter as tk
|
||||
from tkinter import filedialog, messagebox
|
||||
import extract_msg
|
||||
from bs4 import BeautifulSoup
|
||||
from dateutil import parser as dateparser
|
||||
|
||||
|
||||
def decode_html(html_bytes: bytes) -> str:
|
||||
"""Dekóduje HTML bytes – zkouší UTF-8 přísně (selže na špatných sekvencích),
|
||||
pak záložní windows-1250 a latin-1."""
|
||||
for enc in ("utf-8", "windows-1250", "latin-1"):
|
||||
try:
|
||||
return html_bytes.decode(enc) # utf-8 je přísné – selže pro non-UTF-8
|
||||
except (UnicodeDecodeError, LookupError):
|
||||
continue
|
||||
return html_bytes.decode("utf-8", errors="replace")
|
||||
|
||||
|
||||
def clean_text(text: str) -> str:
|
||||
"""Smaže zbytečné bílé znaky a nbsp, vrátí jeden řádek."""
|
||||
text = text.replace("\xa0", " ")
|
||||
text = re.sub(r"\s+", " ", text)
|
||||
return text.strip()
|
||||
|
||||
|
||||
def is_separator(element) -> bool:
|
||||
"""Vrátí True pokud je element Outlook oddělovač citace (border-top div)."""
|
||||
if getattr(element, "name", None) != "div":
|
||||
return False
|
||||
inner = element.find("div", style=lambda s: s and "border-top" in s)
|
||||
return inner is not None
|
||||
|
||||
|
||||
def parse_separator(element) -> tuple[str, str | None]:
|
||||
"""Z oddělovače vytáhne jméno odesílatele a řetězec data."""
|
||||
text = element.get_text(separator="\n")
|
||||
from_match = re.search(r"From:\s*(.+?)(?:\n|$)", text)
|
||||
sent_match = re.search(r"Sent:\s*(.+?)(?:\n|$)", text)
|
||||
|
||||
sender_raw = from_match.group(1).strip() if from_match else "Neznámý"
|
||||
# "Jméno Příjmení <email>" → "Jméno Příjmení"
|
||||
name_only = re.match(r"^(.+?)\s*<", sender_raw)
|
||||
sender = name_only.group(1).strip() if name_only else sender_raw
|
||||
|
||||
sent_str = sent_match.group(1).strip() if sent_match else None
|
||||
return sender, sent_str
|
||||
|
||||
|
||||
def parse_date(sent_str: str | None, fallback=None):
|
||||
if not sent_str:
|
||||
return fallback
|
||||
try:
|
||||
return dateparser.parse(sent_str)
|
||||
except Exception:
|
||||
return fallback
|
||||
|
||||
|
||||
def extract_sections(soup, main_date, main_sender: str) -> list[dict]:
|
||||
"""
|
||||
Projde HTML tělo a rozdělí ho na sekce (každá zpráva ve vláknu).
|
||||
Vrátí seznam diktů: {'sender', 'date', 'text'} seřazených od nejstaršího.
|
||||
"""
|
||||
body_div = soup.find("div", class_="WordSection1") or soup.body
|
||||
if not body_div:
|
||||
return []
|
||||
|
||||
sections = []
|
||||
current = {"sender": main_sender, "date": main_date, "parts": []}
|
||||
|
||||
for child in body_div.children:
|
||||
if is_separator(child):
|
||||
# Ulož aktuální sekci a začni novou
|
||||
text = clean_text(" ".join(current["parts"]))
|
||||
sections.append({
|
||||
"sender": current["sender"],
|
||||
"date": current["date"],
|
||||
"text": text,
|
||||
})
|
||||
sender, sent_str = parse_separator(child)
|
||||
current = {
|
||||
"sender": sender,
|
||||
"date": parse_date(sent_str),
|
||||
"parts": [],
|
||||
}
|
||||
elif hasattr(child, "get_text"):
|
||||
t = clean_text(child.get_text(separator=" "))
|
||||
if t:
|
||||
current["parts"].append(t)
|
||||
|
||||
# Poslední sekce
|
||||
text = clean_text(" ".join(current["parts"]))
|
||||
sections.append({
|
||||
"sender": current["sender"],
|
||||
"date": current["date"],
|
||||
"text": text,
|
||||
})
|
||||
|
||||
# Otočit – v e-mailu je nejnovější nahoře, chceme chronologicky
|
||||
sections.reverse()
|
||||
return sections
|
||||
|
||||
|
||||
def parse_msg(msg_path: str) -> list[dict]:
|
||||
msg = extract_msg.openMsg(msg_path)
|
||||
|
||||
main_date = msg.date
|
||||
# Sender: preferuj display name před celým polem
|
||||
main_sender = msg.sender or "Neznámý"
|
||||
name_only = re.match(r"^(.+?)\s*<", main_sender)
|
||||
if name_only:
|
||||
main_sender = name_only.group(1).strip()
|
||||
|
||||
if msg.htmlBody:
|
||||
html_text = decode_html(msg.htmlBody)
|
||||
soup = BeautifulSoup(html_text, "html.parser")
|
||||
return extract_sections(soup, main_date, main_sender)
|
||||
|
||||
# Fallback – prostý text
|
||||
return [{
|
||||
"sender": main_sender,
|
||||
"date": main_date,
|
||||
"text": clean_text((msg.body or "").replace("\r\n", " ")),
|
||||
}]
|
||||
|
||||
|
||||
def format_sections(sections: list[dict]) -> str:
|
||||
lines = []
|
||||
for s in sections:
|
||||
dt = s["date"]
|
||||
if dt:
|
||||
date_str = dt.strftime("%d.%m.%Y %H:%M")
|
||||
else:
|
||||
date_str = "??"
|
||||
lines.append(f"{date_str} od {s['sender']}: {s['text']}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def copy_to_clipboard(text: str):
|
||||
root = tk.Tk()
|
||||
root.withdraw()
|
||||
root.clipboard_clear()
|
||||
root.clipboard_append(text)
|
||||
root.update()
|
||||
root.after(2000, root.destroy)
|
||||
root.mainloop()
|
||||
|
||||
|
||||
def show_result(text: str):
|
||||
"""Zobrazí výsledek v malém okně a zkopíruje do schránky."""
|
||||
root = tk.Tk()
|
||||
root.title("Email → schránka")
|
||||
root.resizable(True, True)
|
||||
|
||||
frame = tk.Frame(root, padx=10, pady=10)
|
||||
frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
label = tk.Label(frame, text="Zkopírováno do schránky. Náhled:", anchor="w")
|
||||
label.pack(fill=tk.X)
|
||||
|
||||
text_widget = tk.Text(frame, wrap=tk.WORD, height=12, width=80)
|
||||
text_widget.insert(tk.END, text)
|
||||
text_widget.config(state=tk.DISABLED)
|
||||
text_widget.pack(fill=tk.BOTH, expand=True, pady=(4, 0))
|
||||
|
||||
scrollbar = tk.Scrollbar(frame, command=text_widget.yview)
|
||||
text_widget.config(yscrollcommand=scrollbar.set)
|
||||
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
|
||||
def copy_again():
|
||||
root.clipboard_clear()
|
||||
root.clipboard_append(text)
|
||||
root.update()
|
||||
|
||||
btn_frame = tk.Frame(frame)
|
||||
btn_frame.pack(fill=tk.X, pady=(8, 0))
|
||||
tk.Button(btn_frame, text="Kopírovat znovu", command=copy_again).pack(side=tk.LEFT)
|
||||
tk.Button(btn_frame, text="Zavřít", command=root.destroy).pack(side=tk.RIGHT)
|
||||
|
||||
# Auto-copy on open
|
||||
root.clipboard_clear()
|
||||
root.clipboard_append(text)
|
||||
root.update()
|
||||
|
||||
root.mainloop()
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) >= 2:
|
||||
msg_path = sys.argv[1]
|
||||
else:
|
||||
root = tk.Tk()
|
||||
root.withdraw()
|
||||
msg_path = filedialog.askopenfilename(
|
||||
title="Vyberte email (.msg)",
|
||||
filetypes=[("Outlook Email", "*.msg"), ("Všechny soubory", "*.*")],
|
||||
)
|
||||
root.destroy()
|
||||
if not msg_path:
|
||||
return
|
||||
|
||||
try:
|
||||
sections = parse_msg(msg_path)
|
||||
result = format_sections(sections)
|
||||
except Exception as exc:
|
||||
messagebox.showerror("Chyba", f"Nepodařilo se zpracovat email:\n{exc}")
|
||||
return
|
||||
|
||||
print(result)
|
||||
show_result(result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
:: Přetáhněte .msg soubor na tento soubor, nebo spusťte bez argumentu pro výběr souboru
|
||||
python "%~dp0msg_to_clipboard.py" %1
|
||||
@@ -0,0 +1,2 @@
|
||||
06.03.2026 11:19 od pavel kraus: Dobrý den, přeposílám dokument z Fakultní nemocnice Královské Vinohrady Kardiologická klinika, dejte mi prosím vědět, kdy k vám mám můj tchán Antonín Mareček, r.č. 370701406 přijít. Děkuji, Kraus
|
||||
08.03.2026 17:14 od Ordinace MUDr. Michaela Buzalková: Dobrý den, To záleží na termínu, kdy je TAVI plánováno. Nevidím ho nikde ve zprávě. Hezký den Vladimír Buzalka
|
||||
@@ -0,0 +1,500 @@
|
||||
# MedicusWithClaude – poznámky pro Clauda
|
||||
|
||||
## Stav projektu
|
||||
- Zahájeno: 2026-03-13
|
||||
- Cíl: průzkum Firebird DB Medicus → analýzy a reporty
|
||||
- Zatím: úspěšně čteme i zapisujeme do DB, rozumíme RTF formátu
|
||||
- **2026-03-17**: Obnovena session – Claude si přečetl poznámky, připraven pokračovat
|
||||
- **2026-03-18**: Obnovena session – Claude si přečetl poznámky, připraven pokračovat. Průběžně zapisuje do tohoto souboru.
|
||||
- **2026-03-20**: Obnovena session – merge logika z `test_import_single.py` integrována do `s03soubory.py`. Import pipeline kompletní.
|
||||
- **2026-04-01**: Opravena chyba `BlobReader invalid BLOB handle` v `dekurz_report.py` – viz níže.
|
||||
|
||||
## Bezpečnost
|
||||
- Pracujeme na **místní kopii** – poškození DB nevadí, obnova = 5 minut
|
||||
- Lze bez obav experimentovat, zapisovat testovací data atd.
|
||||
- Testovací záznamy nemusíme mazat
|
||||
|
||||
## Připojení k DB
|
||||
```python
|
||||
import fdb
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA',
|
||||
password='masterkey',
|
||||
charset='win1250'
|
||||
)
|
||||
```
|
||||
- fdb verze 2.0.4, Python na PATH (prostý `python` příkaz)
|
||||
- Firebird instalace: `C:\Program Files\Firebird\Firebird_2_5_CGM\bin\`
|
||||
- Jedná se o **HQbird** (IBSurgeon enhanced Firebird) – port **3070** (ne standardní 3050)
|
||||
- fbtracemgr trace nefungoval spolehlivě – doporučeno použít **FBScanner** (stejný výrobce)
|
||||
|
||||
## O Medicusu
|
||||
- Lékařský software pro praktického lékaře
|
||||
- Vyvíjen od ~roku 2000, organicky rostl přidáváním funkcí
|
||||
- Firebird 2.5 – nikdy nepřejdou na nový (desetitisíce instalací, 993 tabulek)
|
||||
- Žádné zabezpečení DB – vše čitelné přes SYSDBA/masterkey
|
||||
- Charset: win1250
|
||||
|
||||
## DB fakta
|
||||
- 993 tabulek celkem
|
||||
- Charset: win1250
|
||||
|
||||
## Klíčové tabulky
|
||||
|
||||
### KAR – Kartotéka (základní kámen Medicusu)
|
||||
- 6319 pacientů – všichni kdo kdy prošli ordinací
|
||||
- Primární klíč: **IDPAC** (interní ID, používá se jako FK v ostatních tabulkách)
|
||||
- Identifikace pacienta v ČR/pojišťovna: **RODCIS** (rodné číslo, bez lomítka, např. 7309208104)
|
||||
- **PRIJMENI**, **JMENO**, TITUL, TITULZA, DATNAR, POHLAVI
|
||||
- **POJ** – pojišťovna (3 znaky, např. 111 = VZP)
|
||||
- Adresy: TRV* (trvalé), PRE* (přechodné)
|
||||
- **VYRAZEN** – příznak vyřazeného pacienta
|
||||
- HLAVDGN – hlavní diagnóza
|
||||
|
||||
### DEKURS – záznamy z návštěv
|
||||
- 172 068 záznamů
|
||||
- **IDPAC** → vazba na KAR
|
||||
- **DATUM** (date), **CAS** (time) – kdy
|
||||
- **DEKURS** – text záznamu (BLOB, RTF nebo holý text)
|
||||
- **DGN1** – hlavní diagnóza (5 znaků, např. 'Z000 '), VDGN1-4 – vedlejší
|
||||
- **IDPRAC** – který pracovník
|
||||
- IDODD, IDUZI – oddělení/uživatel (u Buzalky = 2, 2, 6)
|
||||
- IDSKUPINA, IDTYP – volitelné (NULL OK)
|
||||
|
||||
## Základní schéma
|
||||
```
|
||||
KAR (kartotéka) ←IDPAC→ DEKURS (záznamy z návštěv)
|
||||
```
|
||||
|
||||
## Testovací pacient (uživatel)
|
||||
- **IDPAC = 9742** – Buzalka Vladimír, RC 7309208104, nar. 20.9.1973
|
||||
- IDODD=2, IDPRAC=2, IDUZI=6 (použít při INSERT do DEKURS)
|
||||
|
||||
## RTF formát v DEKURS
|
||||
|
||||
### Základní struktura
|
||||
```
|
||||
{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029
|
||||
{\info{\bookmarks "popis","Typ:ID",číslo}} ← metadata Medicusu
|
||||
{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f2...Courier New;}{\f5...Symbol;}}
|
||||
{\colortbl ;\red0\green0\blue255;\red255\green255\blue0;\red0\green128\blue0;\red192\green192\blue192;}
|
||||
{\stylesheet{styly...}}
|
||||
\uc1\pard\s10\plain... ← tělo záznamu
|
||||
}
|
||||
```
|
||||
|
||||
### Barvy (\cfX)
|
||||
- `\cf1` = modrá (odkazy)
|
||||
- `\cf2` = žlutá (highlight)
|
||||
- `\cf3` = zelená
|
||||
- `\cf4` = šedá (pozadí buněk tabulky `\clcbpat4`)
|
||||
|
||||
### Velikost písma (\fsXX = XX/2 bodů)
|
||||
- `\fs18` = 9pt (tabulky)
|
||||
- `\fs20` = 10pt (normální)
|
||||
- `\fs24` = 12pt atd.
|
||||
|
||||
### Řezy a formátování
|
||||
- `\b` = tučné, `\i` = kurzíva, `\ul` = podtržené
|
||||
- `\ql` = vlevo, `\qr` = vpravo, `\qc` = střed
|
||||
|
||||
### Styly (stylesheet)
|
||||
- `cs15/cs16` = Normální
|
||||
- `cs20` = Vkládaný text
|
||||
- `cs21` = Záhlaví (italic)
|
||||
- `cs22/cs23` = Odkaz (underline, modrá)
|
||||
- `s8/s10` = Vlevo (paragraph styl)
|
||||
|
||||
### Záložky (bookmarks v {\info})
|
||||
- Klikatelné odkazy v Medicusu
|
||||
- Formát: `"popis","Typ:ID",číslo`
|
||||
- Typy: `Files:ID`, `MEDLAB:ID`, `Lab:ID`, `Ock:ID`, `VykA:ID`
|
||||
|
||||
### České znaky
|
||||
- RTF hex escape v win1250: `\'e1`=á, `\'9e`=ž, `\'fd`=ý atd.
|
||||
|
||||
### Tabulky
|
||||
- Plně funkční RTF tabulky (`\trowd`, `\cell`, `\cellx...`)
|
||||
- Používají se např. pro laboratorní výsledky (viz ID=243082, 50KB)
|
||||
|
||||
### Holý text (starší záznamy)
|
||||
- Některé starší záznamy jsou prostý text bez RTF obálky
|
||||
- Nutno detekovat: začíná na `{\\rtf` = RTF, jinak plain text
|
||||
|
||||
### Příklad minimálního RTF záznamu
|
||||
```
|
||||
{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029
|
||||
{\fonttbl{\f0\fnil\fcharset238 Arial;}}
|
||||
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
\pard\plain\f0\fs20 Text záznamu zde\par
|
||||
}
|
||||
```
|
||||
|
||||
## INSERT do DEKURS – ověřený postup
|
||||
```python
|
||||
import fdb, datetime
|
||||
|
||||
conn = fdb.connect(...)
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute('SELECT MAX(ID) FROM DEKURS')
|
||||
next_id = cur.fetchone()[0] + 1
|
||||
|
||||
text = r'{\rtf1\ansi\ansicpg1250...\par' + '\n}' # raw string pro RTF!
|
||||
|
||||
cur.execute("""
|
||||
INSERT INTO DEKURS (ID, IDPAC, IDODD, IDPRAC, IDUZI, DATUM, CAS, DEKURS, DGN1)
|
||||
VALUES (?, ?, 2, 2, 6, ?, ?, ?, ?)
|
||||
""", (next_id, idpac, datum, cas, text, dgn1))
|
||||
|
||||
conn.commit()
|
||||
```
|
||||
- RTF string musí být **raw string** (r'...') kvůli zpětným lomítkům
|
||||
- DGN1 = 5 znaků s mezerou: `'Z000 '`
|
||||
- DATUM = datetime.date(...), CAS = datetime.time(...)
|
||||
|
||||
## Soubory v projektu
|
||||
- `explore_db.py` – výpis všech tabulek a jejich sloupců
|
||||
- `inspect_table.py` – detailní info o konkrétní tabulce (arg: název tabulky)
|
||||
- `sample_dekurs.py` – ukázky nejstarších a nejnovějších záznamů
|
||||
- `sample_dekurs2.py` – celý text jednoho záznamu
|
||||
- `sample_rtf.py` – nejdelší záznamy pacienta (dle délky RTF)
|
||||
- `analyze_rtf.py` – analýza RTF tagů v záznamu
|
||||
- `insert_test.py` – testovací INSERT do DEKURS
|
||||
- `CLAUDE_NOTES.md` – tento soubor
|
||||
|
||||
## Export FILES do externích DB (zjištěno 2026-03-17 přes FBScanner)
|
||||
|
||||
### Mechanismus přesunu
|
||||
Medicus vytváří **měsíční** externí Firebird databáze ve formátu `MEDICUS_FILES_YYYYMM.fdb`.
|
||||
|
||||
**Postup exportu:**
|
||||
1. SELECT dat z FILES po 10 záznamech: `select first 10 ID, BODY, DATUM from FILES where ID > X ORDER BY ID ASC`
|
||||
2. Binární data (skeny) se zapíší do externího `.fdb` souboru
|
||||
3. V hlavní DB se BODY přepíše referenčním číslem (ID v externím souboru): `update FILES set BODY = 379 where ID = 10009`
|
||||
4. Do EXTERNI_DB se zapíše lokace: `insert into EXTERNI_DB (DBNAME, SERVER, PATH, HESLO) values ('DB202501', 'localhost/3053', 'u:\MEDICUS_FILES_202501.fdb', '<encrypted>')`
|
||||
|
||||
### Výsledek
|
||||
- Hlavní DB: FILES.BODY obsahuje jen malé číslo (referenci) místo MB blobu
|
||||
- Skenované soubory jsou fyzicky v `u:\MEDICUS_FILES_YYYYMM.fdb`
|
||||
- Medicus načítá soubory přes EXTERNI_DB tabulku (SERVER + PATH + HESLO)
|
||||
- Hesla v EXTERNI_DB jsou šifrovaná (base64 AES)
|
||||
|
||||
### FBScanner
|
||||
- Nainstalován, funguje jako proxy: Medicus → [3050] FBScanner → [3053] Firebird
|
||||
- Community Edition 2.5 – stačí pro monitoring
|
||||
- Export proveden na záloze (ne produkční DB)
|
||||
|
||||
## Přímý zápis do externí DB – dokončeno (2026-03-17)
|
||||
|
||||
### Nový soubor: `funkce_ext.py`
|
||||
Náhrada za `funkce.zapis_file()`. Ukládá PDF přímo do měsíční externí FDB
|
||||
místo do hlavní medicus.fdb.
|
||||
|
||||
**Funkce:** `zapis_file_ext(vstupconnection, idpac, cesta, souborname, prvnizavorka, soubordate, souborfiledate, poznamka, ext_base_path=r'u:\\')`
|
||||
- Parametry shodné s `funkce.zapis_file()` → jen prohodit import
|
||||
- Vrací `fileid` stejně jako původní funkce
|
||||
|
||||
**Co dělá:**
|
||||
1. Z `soubordate` odvodí `YYYYMM` → `DBNAME = 'DB202603'`
|
||||
2. Načte PDF jako bytes
|
||||
3. Vyhledá EXTERNI_DB pro daný měsíc:
|
||||
- Nalezeno → připojí se k existující FDB (masterkey)
|
||||
- Nenalezeno → vytvoří novou `MEDICUS_FILES_YYYYMM.fdb`, zaregistruje do EXTERNI_DB
|
||||
4. Vygeneruje `UID = uuid.uuid4().hex` (32 znaků)
|
||||
5. Zapíše do DATA tabulky: `UID, DATA (blob), DATASIZE, CHUNK=0`
|
||||
6. Sestaví 48bajtovou BODY referenci
|
||||
7. Získá `fileid` z Gen_Files
|
||||
8. Vloží do FILES s BODY = reference (ne blob)
|
||||
|
||||
### Úprava `s03soubory.py`
|
||||
- Přidán import `funkce_ext`
|
||||
- Volání `funkce.zapis_file(...)` nahrazeno `funkce_ext.zapis_file_ext(...)`
|
||||
|
||||
### BODY reference – binární formát (48 B)
|
||||
```
|
||||
magic 4 B = b'\xee\xbb\xaa\x0b'
|
||||
uid 32 B = UUID4 hex (ASCII, 32 znaků)
|
||||
dblen 4 B = len(DBNAME) jako little-endian uint32
|
||||
dbname N B = DBNAME ASCII (typicky 'DB202603', 8 B)
|
||||
```
|
||||
Celkem typicky: 4 + 32 + 4 + 8 = **48 bajtů**
|
||||
|
||||
### Struktura DATA tabulky (externe DB)
|
||||
```
|
||||
UID VARCHAR(32) NOT NULL
|
||||
DATA BLOB SUB_TYPE 0
|
||||
DATASIZE INTEGER
|
||||
CHUNK INTEGER
|
||||
```
|
||||
Generator: `GEN_UID` (Medicus ho asi nepoužívá přímo, UID je UUID string)
|
||||
|
||||
### EXTERNI_DB tabulka (hlavní DB)
|
||||
```
|
||||
DBNAME např. 'DB202603'
|
||||
SERVER např. 'localhost/3053' (FBScanner proxy)
|
||||
PATH např. 'u:\MEDICUS_FILES_202603.fdb'
|
||||
HESLO šifrované heslo (Medicus AES) – zkopírujeme z existující položky
|
||||
```
|
||||
Při vytváření nové DB: HESLO zkopírujeme z libovolné existující EXTERNI_DB položky
|
||||
(všechny používají masterkey, jen jinak zašifrované – ale stejný klíč).
|
||||
|
||||
### Poznámka k HESLO šifrování
|
||||
Hesla v EXTERNI_DB jsou šifrovaná Medicusem. Při vytváření nové DB proto
|
||||
zkopírujeme HESLO z existující položky (stejné heslo = stejný šifrovaný text).
|
||||
Medicus pak dokáže novou DB otevřít standardně. Pokud by to nefungovalo,
|
||||
lze heslo zadat manuálně přes správce Medicusu.
|
||||
|
||||
## Stav 2026-03-18 – nastudováno po obnově session
|
||||
|
||||
### Co bylo dokončeno (včera večer)
|
||||
- `funkce_ext.zapis_file_ext()` je **otestována a funkční**
|
||||
- `s03soubory.py` volá `zapis_file_ext` místo původní `funkce.zapis_file`
|
||||
- Celý import pipeline funguje end-to-end
|
||||
|
||||
### Celý import pipeline (s03soubory.py)
|
||||
1. Scanuje složku `u:\NextcloudOrdinace\Dokumentace_ke_zpracování`
|
||||
2. Ověří formát názvu souboru: `RC YYYY-MM-DD Jmeno, Jmeno. [prvnizavorka] [druhazavorka].pdf`
|
||||
3. Ověří rodné číslo v KAR tabulce
|
||||
4. Seskupí soubory podle RC (jeden pacient = jeden dekurs)
|
||||
5. Pro každý PDF zavolá `funkce_ext.zapis_file_ext()` → zapíše do externí DB
|
||||
6. Přesune soubor do `u:\NextcloudOrdinace\Dokumentace_zpracovaná`
|
||||
7. Sestaví RTF se záložkami (klikatelné odkazy na soubory v Medicusu)
|
||||
8. Zapíše dekurs do DEKURS tabulky
|
||||
|
||||
### Klíčové soubory
|
||||
- `funkce_ext.py` – zápis PDF do měsíční externí FDB (náhrada za funkce.zapis_file)
|
||||
- `funkce.py` – původní funkce (zapis_file, zapis_dekurs, get_files_id, get_idpac, convert_to1250)
|
||||
- `s03soubory.py` – hlavní import script (spouštět na Windows)
|
||||
- `s04_presun_externi_db.py` – utility: přesun ext DB souborů do u:\externi\ a update PATH v EXTERNI_DB
|
||||
- `test_ext_db4.py` – ověření BODY bajty z FILES tabulky (hex dump)
|
||||
|
||||
### Formát názvu souboru pro import
|
||||
```
|
||||
RC YYYY-MM-DD Prijmeni, Jmeno. [prvnizavorka] [druhazavorka].pdf
|
||||
např: 7309208104 2026-03-17 Buzalka, Vladimír. [EKG] [normální nález].pdf
|
||||
```
|
||||
|
||||
## Oprava RTF klikacích odkazů (2026-03-18)
|
||||
|
||||
### Problém
|
||||
`s03soubory.py` generoval RTF s `\cs22` pro styl odkazu, ale Medicus vyžaduje `\cs32`.
|
||||
Druhý+ bookmark neměl link styling vůbec. `poradi` se nikdy neinkrementoval.
|
||||
|
||||
### Zjištěno analýzou Medicusem vytvořeného DEKURS (ID=263480, AhojClaude.pdf)
|
||||
Správný RTF formát klikacího odkazu:
|
||||
```
|
||||
{\*\cs32\f0\ul\fs20\cf1 Odkaz;} ← stylesheet: cs32, NE cs22
|
||||
\uc1\pard\s10\plain\cs20\f0\i\fs20 Přílohy: {\*\bkmkstart 0}\plain\cs32\f0\ul\fs20\cf1 TEXT{\*\bkmkend 0}\par
|
||||
```
|
||||
- "Přílohy:" je inline s prvním odkazem na stejném `\pard` řádku
|
||||
- Každý další odkaz: `\pard\s10{\*\bkmkstart N}\plain\cs32\f0\ul\fs20\cf1 TEXT{\*\bkmkend N}\par`
|
||||
|
||||
### Opraveno v s03soubory.py + test_import_3files.py
|
||||
1. `\cs22` → `\cs32` v stylesheet i těle RTF
|
||||
2. Header `Vložené přílohy:` je na **samostatném** `\pard` řádku (RTF: `Vlo\'9een\'e9 p\'f8\'edlohy:`)
|
||||
3. Všechny bookmarky mají stejný formát: `\pard\s10{\*\bkmkstart N}\plain\cs32\f0\ul\fs20\cf1 TEXT{\*\bkmkend N}\par`
|
||||
4. Přidáno `poradi += 1` (bug: poradi se nikdy neinkrementoval)
|
||||
5. Odstraněna proměnná `prvnibookmark` – již nepotřebná
|
||||
|
||||
### RTF kódování českých znaků (win1250)
|
||||
- ž = `\'9e`, é = `\'e9`, ř = `\'f8`, í = `\'ed`, á = `\'e1`
|
||||
- "Vložené přílohy:" → `Vlo\'9een\'e9 p\'f8\'edlohy:`
|
||||
|
||||
## Merge do existujícího dekurzu (2026-03-18)
|
||||
|
||||
### Rozhodovací logika (3 případy)
|
||||
1. Poslední dekurs pacienta je z dnešního dne A má `Vložené přílohy:`
|
||||
→ **přidat soubor DO existující sekce** (`pridat_do_sekce_prilohy`)
|
||||
2. Poslední dekurs je z dnešního dne, ale sekci nemá
|
||||
→ **prepend nové sekce na začátek** (`merge_rtf_prepend`)
|
||||
3. Poslední dekurs je z jiného dne / neexistuje
|
||||
→ **nový dekurs pro dnešek**
|
||||
- Funkce: `najdi_posledni_dekurs_dnes(conn, idpac, datum_vlozeni)`
|
||||
|
||||
### pridat_do_sekce_prilohy (přidání do existující sekce)
|
||||
1. Spočítat `"Files:"` v `{\info{\bookmarks}}` = N → nový bkmkstart = N
|
||||
2. Posunout všechny bkmkstart/bkmkend ≥ N o +1 (uvolnit index N)
|
||||
3. Vložit nový `\pard` před `\pard\s10\plain\cs15\f0\fs20 \par` (konec sekce)
|
||||
4. Vložit bookmark na pozici N do `{\info{\bookmarks}}`
|
||||
|
||||
### merge_rtf_prepend (prepend nové sekce)
|
||||
1. Posunout existující `\bkmkstart N` / `\bkmkend N` o počet nových souborů
|
||||
2. Přidat naše bookmarky NA ZAČÁTEK `{\info{\bookmarks ...}}`
|
||||
3. Vložit naše `\pard` tělo PŘED první `\uc1\pard` existujícího těla
|
||||
|
||||
### Detekce sekce
|
||||
- Přítomnost: `PRILOHY_HEADER = r"Vlo\'9een\'e9 p\'f8\'edlohy:"` – hledáme v RTF
|
||||
- Konec sekce: `PRILOHY_CLOSING = r'\pard\s10\plain\cs15\f0\fs20 \par'`
|
||||
|
||||
### Skripty
|
||||
- `test_import_merge.py` – 3 soubory → merge do dnešního dekurzu (bez sekce přílohy)
|
||||
- `test_import_single.py` – 1 soubor → merge DO existující sekce přílohy
|
||||
- `test_import_3files.py` – 3 soubory → vždy nový dekurs (bez merge logiky, starší)
|
||||
|
||||
### Stav testování (2026-03-18)
|
||||
- `test_import_3files.py` ✅ ověřeno – klikací odkazy fungují, "Vložené přílohy:" správně
|
||||
- `test_import_merge.py` ✅ připraveno – merge 3 souborů do dnešního dekurzu (bez sekce)
|
||||
- `test_import_single.py` ✅ připraveno – přidání 1 souboru DO existující sekce přílohy
|
||||
|
||||
### Stav s03soubory.py (2026-03-20) – KOMPLETNÍ ✅
|
||||
- Merge logika plně integrována z `test_import_single.py`
|
||||
- Přidány konstanty `PRILOHY_HEADER`, `PRILOHY_CLOSING`
|
||||
- Přidány funkce: `najdi_posledni_dekurs_dnes()`, `ma_sekci_prilohy()`, `pridat_do_sekce_prilohy()`, `merge_rtf_prepend()`
|
||||
- RTF šablona přesunuta do konstanty `RTF_TEMPLATE`
|
||||
- Odstraněn dead code: `prvnibookmark`, staré šablony, nepoužívané `convert_to1250`
|
||||
- `pridat_do_sekce_prilohy()` podporuje **více souborů najednou** (oproti test_import_single.py, kde byl jen 1)
|
||||
- `idpac` se bere z prvního záznamu skupiny (čistěji než z `row` po skončení loopu)
|
||||
|
||||
### Rozhodovací logika s03soubory.py – 3 případy
|
||||
1. Dnešní dekurs **má** sekci `Vložené přílohy` → soubory přidány **dovnitř** sekce (`pridat_do_sekce_prilohy`)
|
||||
2. Dnešní dekurs **nemá** sekci příloh → nová sekce vložena **na začátek** (`merge_rtf_prepend`)
|
||||
3. Žádný dnešní dekurs → **nový** záznam (INSERT)
|
||||
|
||||
### Stav testovacích skriptů
|
||||
- `test_import_3files.py` ✅ ověřeno – klikací odkazy fungují
|
||||
- `test_import_merge.py` ✅ otestováno – merge 3 souborů (jen 2 případy, bez detekce sekce)
|
||||
- `test_import_single.py` ✅ otestováno – všechny 3 případy, základ pro s03soubory.py
|
||||
|
||||
## Oprava BlobReader v dekurz_report.py (2026-04-01)
|
||||
|
||||
### Chyba
|
||||
```
|
||||
Exception ignored while calling deallocator <function BlobReader.__del__>:
|
||||
fdb.fbcore.DatabaseError: BlobReader.close/isc_close_blob: invalid BLOB handle (-901)
|
||||
```
|
||||
Skript fungoval správně (exit code 0, Excel uložen), ale garbage collector házel warningy.
|
||||
|
||||
### Příčina
|
||||
`fdb` vrací BLOB sloupce jako `BlobReader` objekty. Po `conn.close()` se při GC pokoušely
|
||||
zavřít handle, který již neexistoval.
|
||||
|
||||
### Oprava
|
||||
Explicitně zavřít `BlobReader` hned po `.read()`, ještě za živa spojení:
|
||||
|
||||
```python
|
||||
# PŘED (špatně):
|
||||
rtf = dekurs_blob.read() if hasattr(dekurs_blob, 'read') else (dekurs_blob or '')
|
||||
|
||||
# PO (správně):
|
||||
if hasattr(dekurs_blob, 'read'):
|
||||
rtf = dekurs_blob.read()
|
||||
dekurs_blob.close()
|
||||
else:
|
||||
rtf = dekurs_blob or ''
|
||||
```
|
||||
|
||||
### Obecné pravidlo
|
||||
Kdykoli čteme BLOB z fdb, vždy `.close()` po `.read()` – jinak GC po `conn.close()` hlásí chybu.
|
||||
|
||||
---
|
||||
|
||||
## Další postup (nápady)
|
||||
- Otestovat `s03soubory.py` na Windows se skutečnými soubory (všechny 3 případy)
|
||||
- Napsat `rtf_to_text()` pro extrakci čistého textu z dekurzů
|
||||
- Prozkoumat tabulky: LECH/LECD (léky?), POU (poukazy?), AMBULEKY (výkony?)
|
||||
- První report – domluvit s uživatelem co chce vidět
|
||||
|
||||
---
|
||||
|
||||
## KAR.POZNAMKA – automatické tagy (2026-04-09)
|
||||
|
||||
Pole `POZNAMKA` v tabulce KAR je **BLOB SUB_TYPE 1** (text blob, win1250) –
|
||||
Medicus ho renderuje jako RTF stejně jako DEKURS. Lze psát i prostý text.
|
||||
|
||||
### Konvence automatických tagů
|
||||
Automaticky zapisované informace se ukládají na **začátek** POZNAMKA ve formátu:
|
||||
```
|
||||
[[klic:hodnota1, hodnota2]] #zapsáno DD-MM-YYYY HH:MM#
|
||||
```
|
||||
- Oddělovače `[[` a `]]` se v lékařských poznámkách přirozeně nevyskytují
|
||||
- Tag lze programově najít a nahradit regexem
|
||||
- Ruční text lékaře/sestry pod tagem zůstává nedotčen
|
||||
|
||||
### Implementovaný tag: prev_prohlidka
|
||||
```
|
||||
[[prev_prohlidka:15-05-2024 01022, 15-04-2026]] #zapsáno 09-04-2026 14:30#
|
||||
```
|
||||
- `15-05-2024` = datum poslední PP
|
||||
- `01022` = kód výkonu (01022 = dospělí, 01021 = starší varianta)
|
||||
- `15-04-2026` = nejdřívější možný termín příští PP (23 měsíců od poslední)
|
||||
- Skript: `MedicusWithClaudeTest/prev_prohlidka.py`
|
||||
|
||||
---
|
||||
|
||||
## Tabulka VZPARC – výkonové dávky pojišťovnám (zjištěno 2026-04-09)
|
||||
|
||||
Zachyceno přes Firebird trace log při exportu dávek z Medicusu.
|
||||
|
||||
### Struktura
|
||||
- **1954 záznamů**, data od 2007 (s DAVKA blobem od 2013)
|
||||
- Klíčové sloupce: `ID`, `ROK`, `DRUH`, `DATUMOD`, `DATUMDO`, `DAVKA` (blob), `ICP`, `ODB`, `IDICZ`
|
||||
- Vazba na ICZ přes `IDICZ`
|
||||
|
||||
### Typy dávek (DRUH)
|
||||
| DRUH | Popis |
|
||||
|---|---|
|
||||
| `98` | Výkonová dávka – obsahuje výkony vykázané pojišťovně (DP98 formát) |
|
||||
| `05` | Doplňková dávka (DP05) |
|
||||
| `80` | Registrační dávka – přihlášení/odhlášení pacientů (DP80) |
|
||||
| `36` | Jiný typ |
|
||||
|
||||
### DAVKA blob – formát (CP852)
|
||||
Stejný formát jako KDAVKA v PORTAL, dekódování identické:
|
||||
```python
|
||||
text = raw.encode('cp1250', errors='replace').decode('cp852', errors='replace')
|
||||
```
|
||||
|
||||
Struktura DP98 dávky:
|
||||
```
|
||||
DP98... – hlavička (IČP, rok, měsíc, disk, počet případů, částka)
|
||||
A ... – ambulantní případ: RC pacienta na pozici 34, délka 10
|
||||
V ... – výkon: V + DDMM + YYYY + KOD(5) + ...
|
||||
Z ... – ZULP
|
||||
L ... – lék
|
||||
```
|
||||
|
||||
### Parsování RC z A řádku
|
||||
```python
|
||||
aktualni_rc = line[34:44].strip() # pevná pozice, délka 10
|
||||
```
|
||||
|
||||
### Parsování výkonu z V řádku
|
||||
```python
|
||||
dd = int(line[1:3])
|
||||
mm = int(line[3:5])
|
||||
yyyy = int(line[5:9])
|
||||
kod = line[9:14].strip() # 5znakový kód výkonu
|
||||
```
|
||||
|
||||
### Poznámka: PORTAL vs VZPARC
|
||||
- **PORTAL** – starší tabulka, KDAVKA blob, data jen do 2015
|
||||
- **VZPARC** – správná tabulka pro výkonové dávky, data od 2013 do dnes
|
||||
- Pro hledání výkonů per pacient vždy používat **VZPARC**
|
||||
|
||||
---
|
||||
|
||||
## Skript prev_prohlidka.py (MedicusWithClaudeTest/)
|
||||
|
||||
Účel: najde datum poslední preventivní prohlídky (PP) pro každého registrovaného
|
||||
pacienta a zapíše/aktualizuje tag v KAR.POZNAMKA.
|
||||
|
||||
### Co dělá
|
||||
1. Načte registrované pacienty (přesný dotaz přes REGISTR + ICP, IČP=09305001)
|
||||
2. Projde všechny VZPARC DRUH=98 dávky, hledá výkony `01022` nebo `01021`
|
||||
3. Pro každého pacienta zachová jen **nejnovější** datum PP
|
||||
4. Zapíše/přepíše tag `[[prev_prohlidka:...]]` na začátek KAR.POZNAMKA
|
||||
|
||||
### Výsledek posledního spuštění (2026-04-09)
|
||||
- Registrovaných pacientů: 1621
|
||||
- Dávek VZPARC DRUH=98: 778
|
||||
- Pacientů s nalezenou PP: 1289
|
||||
- Zapsáno do KAR: 1120
|
||||
- Nenalezeno (odregistrovaní nebo bez PP): 169
|
||||
|
||||
### Závislosti
|
||||
```
|
||||
pip install python-dateutil
|
||||
```
|
||||
(fdb již musí být nainstalováno)
|
||||
@@ -0,0 +1,37 @@
|
||||
@echo off
|
||||
:: Firebird SQL trace pro Medicus
|
||||
:: Zachytí všechny SQL příkazy při exportu souborů do externích DB
|
||||
::
|
||||
:: INSTRUKCE:
|
||||
:: 1. Spusť tento soubor jako Administrator (pravé tlačítko → Spustit jako správce)
|
||||
:: 2. Nechej okno otevřené
|
||||
:: 3. V Medicusu proveď export souborů do externích DB
|
||||
:: 4. Vrať se sem a stiskni Ctrl+C pro zastavení
|
||||
:: 5. Log najdeš v medicus_trace_output.txt
|
||||
|
||||
echo.
|
||||
echo === MEDICUS SQL TRACE ===
|
||||
echo Hledam Firebird fbtracemgr...
|
||||
echo.
|
||||
|
||||
set FB_BIN=C:\Program Files\Firebird\Firebird_2_5_CGM\bin
|
||||
|
||||
if not exist "%FB_BIN%\fbtracemgr.exe" (
|
||||
echo CHYBA: fbtracemgr.exe nenalezen v %FB_BIN%
|
||||
echo Zkontroluj cestu a uprav tento soubor.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Nalezen: %FB_BIN%\fbtracemgr.exe
|
||||
echo.
|
||||
echo POZOR: Nyni PROVEĎ export v Medicusu, pak se vrat a stiskni Ctrl+C
|
||||
echo Výstup se zapisuje do: medicus_trace_output.txt
|
||||
echo.
|
||||
|
||||
powershell -Command "& '%FB_BIN%\fbtracemgr.exe' -se localhost:service_mgr -user SYSDBA -password masterkey -start -config '%~dp0medicus_trace.conf' | Tee-Object -FilePath '%~dp0medicus_trace_output.txt'"
|
||||
|
||||
echo.
|
||||
echo === TRACE ZASTAVEN ===
|
||||
echo Výsledky jsou v medicus_trace_output.txt
|
||||
pause
|
||||
@@ -0,0 +1,34 @@
|
||||
import fdb, re
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
cur.execute('SELECT DEKURS FROM DEKURS WHERE ID=243082')
|
||||
text = cur.fetchone()[0]
|
||||
text = text.read() if hasattr(text, 'read') else text
|
||||
text = text.decode('windows-1250', errors='replace') if isinstance(text, bytes) else text
|
||||
|
||||
# colortbl
|
||||
ct = re.search(r'\\colortbl[^}]+\}', text)
|
||||
if ct: print('COLORTBL:', ct.group(0))
|
||||
|
||||
# stylesheet
|
||||
ss = re.search(r'\\stylesheet\{.+?\}(?=\n)', text, re.DOTALL)
|
||||
if ss: print('\nSTYLESHEET:', ss.group(0)[:600])
|
||||
|
||||
# Najdi různé styly použité v textu
|
||||
print('\n--- Použité RTF tagy (unikátní) ---')
|
||||
tags = re.findall(r'\\[a-z]+\d*', text)
|
||||
from collections import Counter
|
||||
for tag, count in Counter(tags).most_common(40):
|
||||
print(f" {tag:<20} {count}x")
|
||||
|
||||
# Ukázka tabulky
|
||||
tbl_start = text.find(r'\trowd')
|
||||
if tbl_start > 0:
|
||||
print('\n--- Začátek tabulky ---')
|
||||
print(text[tbl_start:tbl_start+300])
|
||||
|
||||
conn.close()
|
||||
@@ -0,0 +1,42 @@
|
||||
"""check_last_dekurs.py – zobrazí posledních 5 záznamů v DEKURS + FILES pro pacienta 9742"""
|
||||
import fdb
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='WIN1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
print("=== Posledních 5 záznamů DEKURS pro Buzalka (idpac=9742) ===")
|
||||
cur.execute("""
|
||||
SELECT id, datum, cas, OCTET_LENGTH(dekurs) as delka
|
||||
FROM dekurs WHERE idpac = 9742
|
||||
ORDER BY id DESC ROWS 5
|
||||
""")
|
||||
for row in cur.fetchall():
|
||||
print(f" ID={row[0]} datum={row[1]} cas={row[2]} délka={row[3]} B")
|
||||
|
||||
print()
|
||||
print("=== Obsah posledního DEKURS záznamu ===")
|
||||
cur.execute("""
|
||||
SELECT dekurs FROM dekurs WHERE idpac = 9742
|
||||
ORDER BY id DESC ROWS 1
|
||||
""")
|
||||
row = cur.fetchone()
|
||||
if row:
|
||||
text = row[0]
|
||||
if isinstance(text, bytes):
|
||||
text = text.decode('cp1250', errors='replace')
|
||||
print(text)
|
||||
|
||||
print()
|
||||
print("=== Posledních 5 záznamů FILES pro Buzalka (idpac=9742) ===")
|
||||
cur.execute("""
|
||||
SELECT id, filename, datum, OCTET_LENGTH(body) as bodylen
|
||||
FROM files WHERE idpac = 9742
|
||||
ORDER BY id DESC ROWS 5
|
||||
""")
|
||||
for row in cur.fetchall():
|
||||
print(f" ID={row[0]} filename={row[1]} datum={row[2]} body={row[3]} B")
|
||||
|
||||
conn.close()
|
||||
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
Pomocný skript pro Clauda – spusť na Windows: python db_query.py
|
||||
Výsledky zapíše do db_query_result.txt
|
||||
"""
|
||||
import fdb
|
||||
import json
|
||||
import sys
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA',
|
||||
password='masterkey',
|
||||
charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
results = {}
|
||||
|
||||
# 1. Tabulky s klíčovými slovy (soubory, dokumenty)
|
||||
cur.execute("""
|
||||
SELECT RDB$RELATION_NAME
|
||||
FROM RDB$RELATIONS
|
||||
WHERE RDB$SYSTEM_FLAG = 0 AND RDB$VIEW_BLR IS NULL
|
||||
ORDER BY RDB$RELATION_NAME
|
||||
""")
|
||||
all_tables = [row[0].strip() for row in cur.fetchall()]
|
||||
|
||||
keywords = ['FILE', 'SOUB', 'DOC', 'SCAN', 'OBRAZ', 'IMG', 'ATTACH',
|
||||
'EXTERN', 'PRILOHA', 'ZPRAV', 'ARCH', 'PRIL', 'FOTO']
|
||||
matches = [t for t in all_tables if any(k in t.upper() for k in keywords)]
|
||||
results['tables_matching'] = matches
|
||||
results['total_tables'] = len(all_tables)
|
||||
|
||||
# 2. Podrobnosti o nalezených tabulkách (sloupce + počet řádků)
|
||||
table_details = {}
|
||||
for t in matches:
|
||||
try:
|
||||
cur.execute(f"SELECT COUNT(*) FROM {t}")
|
||||
count = cur.fetchone()[0]
|
||||
except:
|
||||
count = "error"
|
||||
|
||||
cur.execute("""
|
||||
SELECT r.RDB$FIELD_NAME, f.RDB$FIELD_TYPE, f.RDB$FIELD_LENGTH
|
||||
FROM RDB$RELATION_FIELDS r
|
||||
JOIN RDB$FIELDS f ON f.RDB$FIELD_NAME = r.RDB$FIELD_SOURCE
|
||||
WHERE r.RDB$RELATION_NAME = ?
|
||||
ORDER BY r.RDB$FIELD_POSITION
|
||||
""", (t,))
|
||||
cols = [(row[0].strip(), row[1], row[2]) for row in cur.fetchall()]
|
||||
table_details[t] = {"count": count, "columns": cols}
|
||||
|
||||
results['table_details'] = table_details
|
||||
|
||||
# 3. Uložit výsledky
|
||||
output = []
|
||||
output.append(f"=== VÝSLEDKY DB PRŮZKUMU ===\n")
|
||||
output.append(f"Celkem tabulek v DB: {results['total_tables']}\n")
|
||||
output.append(f"\nTabulky obsahující klíčová slova (soubory/dokumenty):\n")
|
||||
for t in results['tables_matching']:
|
||||
d = results['table_details'].get(t, {})
|
||||
output.append(f"\n ** {t} ** (řádků: {d.get('count', '?')})")
|
||||
for col in d.get('columns', []):
|
||||
output.append(f" - {col[0]} (type={col[1]}, len={col[2]})")
|
||||
|
||||
with open('db_query_result.txt', 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(output))
|
||||
|
||||
print("Hotovo! Výsledky jsou v db_query_result.txt")
|
||||
conn.close()
|
||||
@@ -0,0 +1,75 @@
|
||||
"""
|
||||
Podrobný průzkum FILES a EXTERNI_DB – spusť na Windows: python db_query2.py
|
||||
"""
|
||||
import fdb
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA',
|
||||
password='masterkey',
|
||||
charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
out = []
|
||||
|
||||
# 1. Obsah EXTERNI_DB
|
||||
out.append("=== EXTERNI_DB (konfigurace externích databází) ===")
|
||||
cur.execute("SELECT DBNAME, PATH, SERVER, HESLO FROM EXTERNI_DB")
|
||||
for row in cur.fetchall():
|
||||
out.append(f" DBNAME={row[0]} PATH={row[1]} SERVER={row[2]} HESLO={row[3]}")
|
||||
|
||||
# 2. FILES – přehled po rocích
|
||||
out.append("\n=== FILES – počty záznamů po rocích ===")
|
||||
cur.execute("""
|
||||
SELECT EXTRACT(YEAR FROM DATUM) as ROK, COUNT(*) as POCET,
|
||||
SUM(OCTET_LENGTH(BODY)) as CELKEM_BYTU
|
||||
FROM FILES
|
||||
GROUP BY EXTRACT(YEAR FROM DATUM)
|
||||
ORDER BY ROK
|
||||
""")
|
||||
total_bytes = 0
|
||||
for row in cur.fetchall():
|
||||
rok, pocet, bytu = row
|
||||
mb = (bytu or 0) / 1024 / 1024
|
||||
total_bytes += (bytu or 0)
|
||||
out.append(f" Rok {int(rok) if rok else '?'}: {pocet} souborů, {mb:.1f} MB")
|
||||
out.append(f" CELKEM: {total_bytes/1024/1024:.1f} MB v BLOB datech FILES")
|
||||
|
||||
# 3. FILES – ukázka typů souborů
|
||||
out.append("\n=== FILES – typy souborů (dle přípony FILENAME) ===")
|
||||
cur.execute("""
|
||||
SELECT LOWER(SUBSTRING(FILENAME FROM CHAR_LENGTH(FILENAME) - 3)) as EXT,
|
||||
COUNT(*) as POCET
|
||||
FROM FILES
|
||||
WHERE FILENAME IS NOT NULL
|
||||
GROUP BY 1
|
||||
ORDER BY 2 DESC
|
||||
""")
|
||||
for row in cur.fetchall():
|
||||
out.append(f" {row[0]} : {row[1]} souborů")
|
||||
|
||||
# 4. FILES – ukázka prvních 5 záznamů (bez BODY)
|
||||
out.append("\n=== FILES – ukázka 5 záznamů (bez binárních dat) ===")
|
||||
cur.execute("""
|
||||
SELECT ID, IDPAC, FILENAME, DATUM, OCTET_LENGTH(BODY) as VELIKOST,
|
||||
POZNAMKA, IDDOCTYP
|
||||
FROM FILES
|
||||
ORDER BY DATUM DESC
|
||||
ROWS 5
|
||||
""")
|
||||
for row in cur.fetchall():
|
||||
out.append(f" ID={row[0]} IDPAC={row[1]} soubor={row[2]} datum={row[3]}"
|
||||
f" velikost={row[4]}B pozn={row[5]} typ={row[6]}")
|
||||
|
||||
# 5. DOCTYP – typy dokumentů
|
||||
out.append("\n=== DOCTYP – číselník typů dokumentů ===")
|
||||
cur.execute("SELECT ID, NAZEV, SYSID FROM DOCTYP ORDER BY ID")
|
||||
for row in cur.fetchall():
|
||||
out.append(f" ID={row[0]} nazev={row[1]} sysid={row[2]}")
|
||||
|
||||
result = '\n'.join(out)
|
||||
print(result)
|
||||
with open('db_query2_result.txt', 'w', encoding='utf-8') as f:
|
||||
f.write(result)
|
||||
print("\nUloženo do db_query2_result.txt")
|
||||
conn.close()
|
||||
@@ -0,0 +1,532 @@
|
||||
=== VÝSLEDKY DB PRŮZKUMU ===
|
||||
|
||||
Celkem tabulek v DB: 993
|
||||
|
||||
|
||||
Tabulky obsahující klíčová slova (soubory/dokumenty):
|
||||
|
||||
|
||||
** CHAT_ZPRAVY ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- IDADRESAR (type=8, len=4)
|
||||
- ODCHOZI (type=14, len=1)
|
||||
- ODESLANO (type=35, len=8)
|
||||
- TEXT (type=261, len=8)
|
||||
- OTHER_ID (type=8, len=4)
|
||||
- PRECTENO (type=35, len=8)
|
||||
- SENT (type=35, len=8)
|
||||
|
||||
** CLICKDOC ** (řádků: 2)
|
||||
- KEY (type=37, len=100)
|
||||
- VALUE (type=37, len=100)
|
||||
|
||||
** CLICKDOC_CINNOSTI ** (řádků: 0)
|
||||
- IDCLICKDOC (type=8, len=4)
|
||||
- IDCINNOSTI (type=8, len=4)
|
||||
|
||||
** CLICKDOC_KAR ** (řádků: 0)
|
||||
- IDCLICKDOC (type=8, len=4)
|
||||
- IDPAC (type=8, len=4)
|
||||
|
||||
** CLICKDOC_OBJ ** (řádků: 0)
|
||||
- IDCLICKDOC (type=8, len=4)
|
||||
- IDOBJ (type=8, len=4)
|
||||
- REMOTECREATE (type=14, len=1)
|
||||
- REMOTECHANGE (type=14, len=1)
|
||||
- APPROVAL (type=7, len=2)
|
||||
- SEND_REMINDER_SMS (type=14, len=1)
|
||||
|
||||
** CLICKDOC_UZIVATEL ** (řádků: 0)
|
||||
- IDCLICKDOC (type=8, len=4)
|
||||
- IDUZI (type=8, len=4)
|
||||
|
||||
** DEKIMG ** (řádků: 8)
|
||||
- ID (type=8, len=4)
|
||||
- HASH (type=37, len=32)
|
||||
- REFCOUNT (type=8, len=4)
|
||||
- DATA (type=261, len=8)
|
||||
|
||||
** DEKIMGREF ** (řádků: 7)
|
||||
- ID (type=8, len=4)
|
||||
- IDPAC (type=8, len=4)
|
||||
- IDDEKURS (type=8, len=4)
|
||||
- IDDEKIMG (type=8, len=4)
|
||||
|
||||
** DOCLIST ** (řádků: 461429)
|
||||
- ID (type=8, len=4)
|
||||
- DATUM (type=12, len=4)
|
||||
- TABULKA (type=37, len=20)
|
||||
- IDREC (type=8, len=4)
|
||||
- TYP (type=7, len=2)
|
||||
- POPIS (type=37, len=40)
|
||||
- IDPAC (type=8, len=4)
|
||||
- IDODDEL (type=8, len=4)
|
||||
|
||||
** DOCTYP ** (řádků: 27)
|
||||
- ID (type=8, len=4)
|
||||
- NAZEV (type=37, len=32)
|
||||
- SYSID (type=8, len=4)
|
||||
|
||||
** DOMPECEARCHIV ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- CREATED (type=35, len=8)
|
||||
- DATA (type=261, len=8)
|
||||
|
||||
** ED_BOOKOFSUBMISSIONATTACH ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- BOOK_OF_SUBMISSION_ID (type=8, len=4)
|
||||
- ORDER (type=8, len=4)
|
||||
- HAS_ALREADY_BEEN_SENT (type=14, len=1)
|
||||
- NAME (type=37, len=255)
|
||||
- CONTENT (type=261, len=8)
|
||||
|
||||
** EET_ZPRAVARECORD ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- UUID_ZPRAVY (type=14, len=16)
|
||||
- ID_TRZBA (type=8, len=4)
|
||||
- GUID_TRZBA (type=14, len=16)
|
||||
- DAT_ODESL (type=35, len=8)
|
||||
- CERTTHUMBPRINT (type=37, len=64)
|
||||
- REQUEST (type=261, len=8)
|
||||
- RESPONSE (type=261, len=8)
|
||||
- RESP_TYPE (type=8, len=4)
|
||||
- RESP_UUID_ZPRAVY (type=37, len=50)
|
||||
- RESP_DAT_PRIJ (type=35, len=8)
|
||||
- RESP_DAT_ODMIT (type=35, len=8)
|
||||
- RESP_BKP (type=37, len=44)
|
||||
- RESP_FIK (type=37, len=39)
|
||||
- RESP_KOD (type=8, len=4)
|
||||
- RESP_CHYBA (type=37, len=100)
|
||||
- PRVNI_ZASLANI (type=7, len=2)
|
||||
- RESP_TEST (type=7, len=2)
|
||||
- RESP_EXCEPTIONMESSAGE (type=261, len=8)
|
||||
|
||||
** ES_KONZULTACE_FILE ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- ID_KONZULTACE (type=8, len=4)
|
||||
- CAS (type=35, len=8)
|
||||
- NAME (type=37, len=256)
|
||||
- CONTENT (type=261, len=8)
|
||||
- MIME (type=37, len=100)
|
||||
- PATIENT_DOC (type=14, len=1)
|
||||
- PORADI (type=8, len=4)
|
||||
|
||||
** EXTERNI_DB ** (řádků: 1)
|
||||
- DBNAME (type=37, len=12)
|
||||
- PATH (type=37, len=120)
|
||||
- SERVER (type=37, len=32)
|
||||
- HESLO (type=37, len=64)
|
||||
|
||||
** FILES ** (řádků: 10305)
|
||||
- ID (type=8, len=4)
|
||||
- IDPAC (type=8, len=4)
|
||||
- DOCID (type=8, len=4)
|
||||
- TYP (type=8, len=4)
|
||||
- FILENAME (type=37, len=254)
|
||||
- BODY (type=261, len=8)
|
||||
- DATUM (type=12, len=4)
|
||||
- IDDOCTYP (type=8, len=4)
|
||||
- IDPRAC (type=8, len=4)
|
||||
- IDUZI (type=8, len=4)
|
||||
- POZNAMKA (type=37, len=100)
|
||||
- DATSOUBORU (type=35, len=8)
|
||||
- ID_EDOKUMENT (type=8, len=4)
|
||||
- EXT_ID (type=37, len=36)
|
||||
|
||||
** FNUSA_ATTACHMENT ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- DATA (type=261, len=8)
|
||||
- ATTACH_INT_TYP (type=37, len=1)
|
||||
- ATTACH_INT_ID (type=8, len=4)
|
||||
- LAST_UPDATED (type=35, len=8)
|
||||
|
||||
** GRARCH ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- NAME (type=37, len=16)
|
||||
- PATH (type=37, len=150)
|
||||
|
||||
** GRDOC ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- IDNEM (type=37, len=60)
|
||||
- IDPAC (type=8, len=4)
|
||||
- SKUPINA (type=8, len=4)
|
||||
- DATUM (type=12, len=4)
|
||||
- PREVIEW (type=261, len=8)
|
||||
- PICT (type=261, len=8)
|
||||
- LOCATION (type=37, len=16)
|
||||
|
||||
** HISTDOC ** (řádků: 32250)
|
||||
- ID (type=8, len=4)
|
||||
- TYP (type=37, len=7)
|
||||
- DATUM (type=12, len=4)
|
||||
- DATA (type=261, len=8)
|
||||
- CENA (type=16, len=8)
|
||||
- REPPOZN (type=37, len=60)
|
||||
- REPDATUM (type=12, len=4)
|
||||
- IDPACI (type=8, len=4)
|
||||
- IDZAR (type=8, len=4)
|
||||
- IDODD (type=8, len=4)
|
||||
- IDPRAC (type=8, len=4)
|
||||
- IDUZIV (type=8, len=4)
|
||||
- IDLEK (type=8, len=4)
|
||||
- IDZARPR (type=8, len=4)
|
||||
- IDODDPR (type=8, len=4)
|
||||
- IDPRACPR (type=8, len=4)
|
||||
- IDLEKPR (type=8, len=4)
|
||||
- STAV (type=14, len=1)
|
||||
- REPSTAV (type=14, len=1)
|
||||
- IDSABLONPRAC (type=37, len=55)
|
||||
- REPTEXT (type=261, len=8)
|
||||
- TYPCENY (type=14, len=1)
|
||||
- CENAPACIENT (type=16, len=8)
|
||||
- IDDOKLAD (type=8, len=4)
|
||||
- POZNAMKA (type=37, len=100)
|
||||
- PRINTED (type=14, len=1)
|
||||
- PRIJALJINE (type=37, len=150)
|
||||
- BMKDATA (type=261, len=8)
|
||||
- PORCISLO (type=8, len=4)
|
||||
- DRUHPOJ (type=8, len=4)
|
||||
- IDLEKZPR (type=8, len=4)
|
||||
- IDHLAV (type=8, len=4)
|
||||
- CREATED (type=35, len=8)
|
||||
- IDSOUHLASPACSABL (type=8, len=4)
|
||||
- REPIDUZIVINS (type=8, len=4)
|
||||
- REPIDUZIVUPD (type=8, len=4)
|
||||
- CGMNUMBER_ODESILATEL (type=37, len=12)
|
||||
- CGMNUMBER_PRIJEMCE (type=37, len=12)
|
||||
- CLICKBOX_ATT_ID (type=8, len=4)
|
||||
- CLICKBOX_MAILBOX (type=37, len=255)
|
||||
- ID_EDOKUMENT (type=8, len=4)
|
||||
|
||||
** HISTDOCLAB ** (řádků: 27185)
|
||||
- ID (type=8, len=4)
|
||||
- IDHISTDOC (type=8, len=4)
|
||||
- IDMETOD (type=8, len=4)
|
||||
- POZN (type=261, len=8)
|
||||
- IDPLADET (type=8, len=4)
|
||||
|
||||
** HISTDOCLABPARAMS ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- IDHISTDOC (type=8, len=4)
|
||||
- IDHISTDOCLAB (type=8, len=4)
|
||||
- IDPARAM (type=8, len=4)
|
||||
- TYP (type=14, len=1)
|
||||
- TEXT (type=14, len=8)
|
||||
|
||||
** HISTDOCSABL ** (řádků: 7)
|
||||
- ID (type=8, len=4)
|
||||
- TYP (type=37, len=7)
|
||||
- DATA (type=261, len=8)
|
||||
- IDZAR (type=8, len=4)
|
||||
- IDODD (type=8, len=4)
|
||||
- IDPRAC (type=8, len=4)
|
||||
- IDUZIV (type=8, len=4)
|
||||
- IDLEK (type=8, len=4)
|
||||
- IDZARPR (type=8, len=4)
|
||||
- IDODDPR (type=8, len=4)
|
||||
- IDPRACPR (type=8, len=4)
|
||||
- IDLEKPR (type=8, len=4)
|
||||
- STAV (type=14, len=1)
|
||||
- IDSABLONPRAC (type=37, len=55)
|
||||
- NAZEV (type=37, len=48)
|
||||
- REPSTAV (type=14, len=1)
|
||||
- CENA (type=37, len=20)
|
||||
- DOPLATEK (type=37, len=20)
|
||||
- USEPRAC (type=8, len=4)
|
||||
- USEUZIV (type=8, len=4)
|
||||
|
||||
** HISTDOCSABLLAB ** (řádků: 19)
|
||||
- ID (type=8, len=4)
|
||||
- IDHISTDOC (type=8, len=4)
|
||||
- IDMETOD (type=8, len=4)
|
||||
|
||||
** HISTDOCVYK ** (řádků: 1868)
|
||||
- IDHDVYK (type=8, len=4)
|
||||
- ID (type=8, len=4)
|
||||
- KOD (type=14, len=7)
|
||||
- POCET (type=16, len=8)
|
||||
- CENA (type=16, len=8)
|
||||
- CENADOPL (type=16, len=8)
|
||||
- POJ (type=14, len=3)
|
||||
- ICZ (type=14, len=8)
|
||||
- ICP (type=14, len=8)
|
||||
- DATUM (type=12, len=4)
|
||||
- REV (type=14, len=1)
|
||||
- TYP (type=7, len=2)
|
||||
- NOT_CENA_PRUMER (type=14, len=1)
|
||||
- VARSYM (type=37, len=20)
|
||||
- SKUPINA (type=14, len=2)
|
||||
|
||||
** HISTDOC_DEF ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- KOD (type=37, len=5)
|
||||
- NAZEV (type=37, len=40)
|
||||
- IDUZI (type=8, len=4)
|
||||
- DATA (type=261, len=8)
|
||||
- SKUPINA (type=8, len=4)
|
||||
|
||||
** HISTDOC_EPOUKAZ ** (řádků: 849)
|
||||
- IDHISTDOC (type=8, len=4)
|
||||
- ID_DOKLADU (type=14, len=9)
|
||||
- IDUZI (type=8, len=4)
|
||||
- IDPRAC (type=8, len=4)
|
||||
- ODESLANO (type=35, len=8)
|
||||
- CHYBA (type=14, len=1)
|
||||
- SCHVALENI (type=14, len=1)
|
||||
- MODIFIED (type=14, len=1)
|
||||
- ODESLANO_LAST (type=35, len=8)
|
||||
- ID_ZP (type=37, len=36)
|
||||
- ID_DOKLADU_VYDEJ (type=37, len=9)
|
||||
- ID_ZP_VYDEJ (type=37, len=36)
|
||||
- VYDANO (type=35, len=8)
|
||||
|
||||
** HISTDOC_EPOUKAZ_ZMENY ** (řádků: 3)
|
||||
- ID_LEKAR (type=37, len=36)
|
||||
- LAST_DATE (type=12, len=4)
|
||||
|
||||
** HISTDOC_EPRILOHY ** (řádků: 17)
|
||||
- ID (type=8, len=4)
|
||||
- IDHISTDOC (type=8, len=4)
|
||||
- NAZEV (type=37, len=255)
|
||||
- POPIS (type=37, len=255)
|
||||
- TYP (type=37, len=7)
|
||||
- SOUBOR (type=261, len=8)
|
||||
- ID_PRILOHY (type=37, len=36)
|
||||
|
||||
** HISTDOC_EZADANKA ** (řádků: 2195)
|
||||
- ID (type=8, len=4)
|
||||
- IDHISTDOC (type=8, len=4)
|
||||
- TYP (type=37, len=10)
|
||||
- DATUMVYS (type=12, len=4)
|
||||
- CASVYS (type=13, len=4)
|
||||
- CISLO (type=37, len=30)
|
||||
- POZNAMKA (type=261, len=8)
|
||||
- HOTOVO (type=14, len=1)
|
||||
- IDZAD (type=8, len=4)
|
||||
- VYSTEXT (type=261, len=8)
|
||||
- DATUMEXP (type=12, len=4)
|
||||
- CASEXP (type=13, len=4)
|
||||
- SABLONY (type=261, len=8)
|
||||
- PRINTED (type=35, len=8)
|
||||
- SENDED (type=35, len=8)
|
||||
- TEMPDAT (type=35, len=8)
|
||||
- SEZNAMMETOD (type=261, len=8)
|
||||
- SEZNAMSABLON (type=261, len=8)
|
||||
- DIAG1 (type=37, len=10)
|
||||
- DIAG2 (type=37, len=10)
|
||||
- DIAG3 (type=37, len=10)
|
||||
- STATIM (type=14, len=1)
|
||||
- ODEBRANO (type=35, len=8)
|
||||
- BEZODBERU (type=14, len=1)
|
||||
- EXPORTOVAT (type=35, len=8)
|
||||
- NAH1 (type=37, len=10)
|
||||
- NAH2 (type=37, len=10)
|
||||
- ODEBRAL (type=37, len=30)
|
||||
- POCETZKUM (type=8, len=4)
|
||||
- OS_NAZEV (type=37, len=100)
|
||||
- OS_ID (type=37, len=20)
|
||||
- PARAMETRY (type=261, len=8)
|
||||
- POZNAMKY (type=261, len=8)
|
||||
- STRINGPARAMETRY (type=261, len=8)
|
||||
- VZOREK1 (type=37, len=25)
|
||||
- VZOREK2 (type=37, len=25)
|
||||
- VZOREK3 (type=37, len=25)
|
||||
- VZOREK4 (type=37, len=25)
|
||||
- PLATCE (type=37, len=30)
|
||||
- OSVOBOZENDPH (type=14, len=1)
|
||||
- PLATCEPOZNAMKA (type=37, len=100)
|
||||
- KATALOGALIAS (type=37, len=20)
|
||||
- KATALOGNAZEV (type=37, len=100)
|
||||
- PRIMARNIVZORKY (type=37, len=256)
|
||||
|
||||
** HISTDOC_SCHVALENI ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- IDHISTDOC (type=8, len=4)
|
||||
- IDVZPARC (type=8, len=4)
|
||||
- PRILOHA (type=261, len=8)
|
||||
- ODESLANO (type=14, len=1)
|
||||
|
||||
** HOSP_ZPRAVY ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- DATUM (type=12, len=4)
|
||||
- CAS (type=13, len=4)
|
||||
- ZPRAVA (type=261, len=8)
|
||||
- IDUZI (type=8, len=4)
|
||||
- IDPAC (type=8, len=4)
|
||||
- ID_EDOKUMENT (type=8, len=4)
|
||||
- IDHOSP (type=8, len=4)
|
||||
|
||||
** IZIPDOC ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- IDPAC (type=8, len=4)
|
||||
- DATUM (type=12, len=4)
|
||||
- STATUS (type=8, len=4)
|
||||
- IDENT (type=8, len=4)
|
||||
- ZAPIS (type=261, len=8)
|
||||
- DEKID (type=8, len=4)
|
||||
- DGN (type=14, len=5)
|
||||
- CAS (type=13, len=4)
|
||||
- IDZAR (type=8, len=4)
|
||||
- IDODD (type=8, len=4)
|
||||
- IDPRAC (type=8, len=4)
|
||||
- AUTOR (type=8, len=4)
|
||||
- LOCKED (type=14, len=1)
|
||||
- TYP (type=14, len=1)
|
||||
- VDGN1 (type=14, len=5)
|
||||
- VDGN2 (type=14, len=5)
|
||||
- VDGN3 (type=14, len=5)
|
||||
- VDGN4 (type=14, len=5)
|
||||
|
||||
** IZIPFILES ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- DOCID (type=8, len=4)
|
||||
- TYP (type=8, len=4)
|
||||
- FILENAME (type=37, len=50)
|
||||
- BODY (type=261, len=8)
|
||||
- DATUM (type=12, len=4)
|
||||
|
||||
** LEKZPRAVY ** (řádků: 13748)
|
||||
- ID (type=8, len=4)
|
||||
- IDPAC (type=8, len=4)
|
||||
- DEKURSID (type=8, len=4)
|
||||
- DATUM (type=12, len=4)
|
||||
- TEXT (type=261, len=8)
|
||||
- IDUZI (type=8, len=4)
|
||||
- IDNOSVYK (type=8, len=4)
|
||||
- TYP (type=14, len=1)
|
||||
- ODESILATEL (type=37, len=254)
|
||||
- DEKURS_COPYED (type=35, len=8)
|
||||
- EISPUBLIC (type=14, len=1)
|
||||
- PRIJEMCE (type=37, len=254)
|
||||
- IDPRAC (type=8, len=4)
|
||||
- SIGNATURE (type=261, len=8)
|
||||
- SIGNATURE_INFO (type=261, len=8)
|
||||
- IDCERTIFICATE (type=8, len=4)
|
||||
- TST (type=261, len=8)
|
||||
- NAZEV (type=37, len=80)
|
||||
- ID_EDOKUMENT (type=8, len=4)
|
||||
- PRIJATO (type=14, len=1)
|
||||
- JMENOLEK (type=37, len=50)
|
||||
- ICP (type=14, len=8)
|
||||
- ES_STAV (type=37, len=1)
|
||||
- PR_TYP (type=14, len=1)
|
||||
- PR_ID (type=8, len=4)
|
||||
- STORNO (type=14, len=1)
|
||||
- UID (type=37, len=16)
|
||||
- PUVODNI (type=8, len=4)
|
||||
- CAS (type=13, len=4)
|
||||
- DATAKT (type=35, len=8)
|
||||
- ODALIAS (type=37, len=8)
|
||||
- PDF (type=261, len=8)
|
||||
- PREPIS (type=8, len=4)
|
||||
- OZNACENI_O (type=37, len=50)
|
||||
- STAV (type=14, len=1)
|
||||
- CLICKBOX_ATT_ID (type=8, len=4)
|
||||
- CGMNUMBER_ODESILATEL (type=37, len=12)
|
||||
- CLICKBOX_MAILBOX (type=37, len=255)
|
||||
- CGMNUMBER_PRIJEMCE (type=37, len=12)
|
||||
- CGMNUMBER (type=37, len=12)
|
||||
|
||||
** LEKZPRAVY_PR ** (řádků: 134)
|
||||
- ID (type=8, len=4)
|
||||
- LEKZPRID (type=8, len=4)
|
||||
- PRID (type=8, len=4)
|
||||
- IDUZI (type=8, len=4)
|
||||
- TYP (type=14, len=1)
|
||||
- EXT (type=14, len=4)
|
||||
|
||||
** LEKZPRAVY_SEND ** (řádků: 0)
|
||||
- LEKZPRID (type=8, len=4)
|
||||
- KDY (type=35, len=8)
|
||||
- KOMU (type=37, len=80)
|
||||
|
||||
** MEDINETIN_ATTACHMENT ** (řádků: 120)
|
||||
- ID (type=8, len=4)
|
||||
- ID_MEDINETIN (type=8, len=4)
|
||||
- FILE_NAME (type=37, len=254)
|
||||
- FILE_EXT (type=37, len=5)
|
||||
- FILE_TYPE (type=8, len=4)
|
||||
- DATA (type=261, len=8)
|
||||
- ID_FILES (type=8, len=4)
|
||||
- ZPRACOVANO (type=14, len=1)
|
||||
|
||||
** MEDINETOUT_ATTACHMENT ** (řádků: 2236)
|
||||
- ID (type=8, len=4)
|
||||
- ID_MEDINETOUT (type=8, len=4)
|
||||
- FILE_ID (type=37, len=36)
|
||||
- FILE_NAME (type=37, len=254)
|
||||
- FILE_EXT (type=37, len=5)
|
||||
- FILE_TYPE (type=8, len=4)
|
||||
- DATA (type=261, len=8)
|
||||
- ATTACH_INT_TYP (type=37, len=1)
|
||||
- ATTACH_INT_ID (type=8, len=4)
|
||||
|
||||
** OSE_ZPRAVY ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- IDPAC (type=8, len=4)
|
||||
- DATUM (type=35, len=8)
|
||||
- UZI (type=37, len=3)
|
||||
- DATA (type=261, len=8)
|
||||
- SIGNATURE (type=261, len=8)
|
||||
- SIGNATURE_INFO (type=261, len=8)
|
||||
- IDCERTIFICATE (type=8, len=4)
|
||||
- STORNO (type=35, len=8)
|
||||
- STORNOBY (type=14, len=3)
|
||||
|
||||
** SOC_FINPRIJEM_HISTDOC ** (řádků: 0)
|
||||
- ID_SFH (type=8, len=4)
|
||||
- ID_SFP (type=8, len=4)
|
||||
- ID_HDC (type=8, len=4)
|
||||
|
||||
** ZPRAVY ** (řádků: 26)
|
||||
- ID (type=8, len=4)
|
||||
- IDZPRAVY (type=8, len=4)
|
||||
- IDOD (type=8, len=4)
|
||||
- IDKOMUZAR (type=8, len=4)
|
||||
- IDKOMUODD (type=8, len=4)
|
||||
- IDKOMUPRAC (type=8, len=4)
|
||||
- IDKOMUUZIV (type=8, len=4)
|
||||
- PREDMET (type=37, len=100)
|
||||
- TEXT (type=261, len=8)
|
||||
- DATUM (type=12, len=4)
|
||||
- CAS (type=13, len=4)
|
||||
- URGENTNI (type=8, len=4)
|
||||
- PRECTENA (type=8, len=4)
|
||||
- DATUMPRECT (type=12, len=4)
|
||||
- CASPRECT (type=13, len=4)
|
||||
- SMAZANA (type=8, len=4)
|
||||
- OZNAMOPRECT (type=8, len=4)
|
||||
- TYP (type=8, len=4)
|
||||
- KOMU (type=261, len=8)
|
||||
- PLATNOST (type=35, len=8)
|
||||
- LZE_ODPOVED (type=14, len=1)
|
||||
- ID_EXT (type=8, len=4)
|
||||
|
||||
** ZPRAVY_ANKETY ** (řádků: 0)
|
||||
- ID (type=8, len=4)
|
||||
- IDUZI (type=8, len=4)
|
||||
- IDPRIJEMCE (type=8, len=4)
|
||||
- ID_EXT (type=8, len=4)
|
||||
- DATA (type=261, len=8)
|
||||
- DATUM (type=12, len=4)
|
||||
- ODESLANO (type=14, len=1)
|
||||
|
||||
** ZPRAVY_EXT ** (řádků: 18)
|
||||
- ID (type=8, len=4)
|
||||
- ID_EXT (type=8, len=4)
|
||||
- ID_ODESILATEL (type=8, len=4)
|
||||
- TYP (type=37, len=1)
|
||||
- PRIORITA (type=37, len=1)
|
||||
- DATUM (type=35, len=8)
|
||||
- PREDMET (type=37, len=254)
|
||||
- DATA (type=261, len=8)
|
||||
- STAV (type=37, len=1)
|
||||
- AKCE (type=37, len=20)
|
||||
- PRIJEMCE (type=37, len=3)
|
||||
- ODESLAL (type=37, len=20)
|
||||
- ODB (type=37, len=3)
|
||||
|
||||
** ZPRAVY_LOCK ** (řádků: 1)
|
||||
- IDUZI (type=8, len=4)
|
||||
- BEGINTIME (type=35, len=8)
|
||||
- ENDTIME (type=35, len=8)
|
||||
@@ -0,0 +1,45 @@
|
||||
"""
|
||||
Průzkum Medicus databáze – výpis tabulek a jejich sloupců.
|
||||
"""
|
||||
import fdb
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA',
|
||||
password='masterkey',
|
||||
charset='win1250'
|
||||
)
|
||||
|
||||
cur = conn.cursor()
|
||||
|
||||
# Všechny uživatelské tabulky
|
||||
cur.execute("""
|
||||
SELECT rdb$relation_name
|
||||
FROM rdb$relations
|
||||
WHERE rdb$view_blr IS NULL
|
||||
AND (rdb$system_flag IS NULL OR rdb$system_flag = 0)
|
||||
ORDER BY rdb$relation_name
|
||||
""")
|
||||
tables = [row[0].strip() for row in cur.fetchall()]
|
||||
|
||||
print(f"Počet tabulek: {len(tables)}\n")
|
||||
print("=== SEZNAM TABULEK ===")
|
||||
for t in tables:
|
||||
print(f" {t}")
|
||||
|
||||
print("\n=== SLOUPCE KAŽDÉ TABULKY ===")
|
||||
for table in tables:
|
||||
cur.execute("""
|
||||
SELECT rf.rdb$field_name, f.rdb$field_type, f.rdb$field_length
|
||||
FROM rdb$relation_fields rf
|
||||
JOIN rdb$fields f ON rf.rdb$field_source = f.rdb$field_name
|
||||
WHERE rf.rdb$relation_name = ?
|
||||
ORDER BY rf.rdb$field_position
|
||||
""", (table,))
|
||||
cols = cur.fetchall()
|
||||
col_names = [c[0].strip() for c in cols]
|
||||
print(f"\n{table}")
|
||||
print(" " + ", ".join(col_names))
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
@@ -0,0 +1,114 @@
|
||||
import os,fdb,datetime
|
||||
|
||||
# conn = fdb.connect(
|
||||
# dsn=r'localhost:u:\medicus 3\data\medicus.fdb', # Database path
|
||||
# user='SYSDBA', # Username
|
||||
# password="masterkey", # Password,
|
||||
# charset="win1250")
|
||||
# cur = conn.cursor()
|
||||
|
||||
def zapis_file(vstupconnection,idpac,cesta,souborname,prvnizavorka,soubordate,souborfiledate,poznamka):
|
||||
import funkce
|
||||
cur=vstupconnection.cursor()
|
||||
fileid = funkce.get_files_id(vstupconnection)
|
||||
with open(os.path.join(cesta,souborname), 'rb') as f:
|
||||
daticka = f.read()
|
||||
query = "insert into files (id,iduzi,iddoctyp,typ,idpac,filename,body,datum,datsouboru,poznamka) values(?,?,?,?,?,?,?,?,?,?)"
|
||||
cur.execute(query,(fileid,6,1,1,idpac,prvnizavorka+".pdf",daticka,soubordate,souborfiledate,poznamka[:99]))
|
||||
vstupconnection.commit()
|
||||
return fileid
|
||||
def zapis_dekurs(vstupconnection, idpac, idodd, iduzi, idprac, idfile, filename, text, datumzpravy,
|
||||
datumsouboru):
|
||||
import funkce
|
||||
dekursid = funkce.get_dekurs_id(vstupconnection)
|
||||
cur = vstupconnection.cursor()
|
||||
print("Funkce zapis_dekurs hlasí OK")
|
||||
print("idpac", idpac)
|
||||
print("idodd", idodd)
|
||||
print("iduzi", iduzi)
|
||||
print("idfile", idfile)
|
||||
print("filename", filename)
|
||||
print("text", text)
|
||||
print("datumzpravy", datumzpravy)
|
||||
print("datumsouboru", datumsouboru)
|
||||
print("dekursid", dekursid)
|
||||
|
||||
# rtf = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks "BOOKMARKNAME","Files:FILEID",9}}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
|
||||
# {\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
# {\stylesheet{\s0\fi0\li0\ql\ri0\sb0\sa0 Norm\'e1ln\'ed;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs32\f0\ul\fs20\cf1 Odkaz;}}
|
||||
# \uc1\pard\s0\plain\cs20\f0\i\fs20 P\'f8\'edlohy: {\*\bkmkstart 0}\plain\cs32\f0\ul\fs20\cf1 BOOKMARKNAME{\*\bkmkend 0}\par
|
||||
# \pard\s0\plain\cs15\f0\fs20 \par
|
||||
# }
|
||||
# """
|
||||
rtf = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks "BOOKMARKNAME","Files:FILEID",9}}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
|
||||
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
{\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs22\f0\ul\fs20\cf1 Odkaz;}}
|
||||
\uc1\pard\s10\plain\cs20\f0\i\fs20 P\'f8\'edlohy:\par
|
||||
\pard\s10{\*\bkmkstart 0}\plain\cs22\f0\ul\fs20\cf1 BOOKMARKNAME{\*\bkmkend 0}\par
|
||||
\pard\s10\plain\cs15\f0\fs20 \par
|
||||
}
|
||||
|
||||
"""
|
||||
# id idpac filename body docid typ datum iddoctyp poznamka idpac=2 iduzi=2 datsouboru id_edokument ext_id
|
||||
encodedbookmark = funkce.convert_to1250(filename)
|
||||
print("Encodedbookmark", encodedbookmark)
|
||||
rtf = rtf.replace("BOOKMARKNAME", encodedbookmark)
|
||||
rtf = rtf.replace("FILEID", str(idfile))
|
||||
rtf = rtf.replace("TEXTENTER", text)
|
||||
datumzapisu = datetime.datetime.now().date()
|
||||
caszapisu = datetime.datetime.now().time()
|
||||
print("Datumzapisu", datumzapisu)
|
||||
print("Caszapisu", caszapisu)
|
||||
print("RTF", rtf)
|
||||
cur.execute("insert into dekurs (id,idpac,idodd,iduzi,idprac,datum,cas,dekurs) values(?,?,?,?,?,?,?,?)",
|
||||
(dekursid, idpac, idodd, iduzi, idprac, datumzapisu, caszapisu, rtf))
|
||||
vstupconnection.commit()
|
||||
|
||||
|
||||
def convert_to1250(retezec):
|
||||
retezec=retezec.encode("cp1250")
|
||||
retezec=str(retezec)[2:]
|
||||
retezec = retezec[:-1]
|
||||
retezec=retezec.replace(r"\x",r"\'")
|
||||
return retezec
|
||||
|
||||
|
||||
# x=convert_to1250("Příloha")
|
||||
# print(x,len(x))
|
||||
|
||||
def get_dekurs_id(connection):
|
||||
try:
|
||||
query = "SELECT GEN_ID(Gen_Dekurs, 1) FROM RDB$DATABASE"
|
||||
cur = connection.cursor()
|
||||
cur.execute(query)
|
||||
newid=cur.fetchone()[0]
|
||||
print("Funkce GET_DEKURS_ID přiřadila nové ID:",newid)
|
||||
return(newid)
|
||||
except:
|
||||
print("Funkce GET_DEKURS_ID nepřiřadila nové ID")
|
||||
return(None)
|
||||
|
||||
def get_files_id(connection):
|
||||
try:
|
||||
query = "SELECT GEN_ID(Gen_Files, 1) FROM RDB$DATABASE"
|
||||
cur=connection.cursor()
|
||||
cur.execute(query)
|
||||
newid=cur.fetchone()[0]
|
||||
print(newid)
|
||||
return(newid)
|
||||
except:
|
||||
return(None)
|
||||
|
||||
def get_idpac(rodnecislo,connection):
|
||||
try:
|
||||
query = "SELECT idpac,prijmeni FROM kar where rodcis=?"
|
||||
cur = connection.cursor()
|
||||
cur.execute(query,(rodnecislo,))
|
||||
tmp_nacteno=cur.fetchone()
|
||||
tmp_id = tmp_nacteno[0]
|
||||
tmp_jmeno=tmp_nacteno[1]
|
||||
print(f"Pacient s rodným číslem {rodnecislo} má ID {tmp_id} a jméno {tmp_jmeno}")
|
||||
return (tmp_id)
|
||||
except:
|
||||
return(None)
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
"""funkce_ext.py – zápis PDF souborů přímo do měsíční externí Firebird DB
|
||||
místo do hlavní medicus.fdb – spouštět na Windows
|
||||
|
||||
Náhrada za funkce.zapis_file() v s03soubory.py.
|
||||
Binární data souboru se uloží do MEDICUS_FILES_YYYYMM.fdb (DATA tabulka),
|
||||
do hlavní FILES.BODY se vloží 48bajtová reference.
|
||||
|
||||
Formát FILES.BODY reference (48 B):
|
||||
magic 4 B = b'\\xee\\xbb\\xaa\\x0b'
|
||||
uid 32 B = UUID4 hex (ASCII, 32 znaků)
|
||||
dblen 4 B = délka DBNAME jako little-endian uint32
|
||||
dbname N B = DBNAME ASCII (např. 'DB202603', 8 B)
|
||||
"""
|
||||
|
||||
import os
|
||||
import struct
|
||||
import uuid
|
||||
import fdb
|
||||
|
||||
|
||||
# Magic bajty identifikující referenci na externí DB
|
||||
MAGIC = b'\xee\xbb\xaa\x0b'
|
||||
|
||||
|
||||
# ─── Pomocné funkce ──────────────────────────────────────────────────────────
|
||||
|
||||
def make_body_ref(uid: str, dbname: str) -> bytes:
|
||||
"""Sestaví 48bajtovou binární referenci pro FILES.BODY."""
|
||||
uid_bytes = uid.encode('ascii') # 32 B
|
||||
dbname_bytes = dbname.encode('ascii') # typicky 8 B ('DB202603')
|
||||
return MAGIC + uid_bytes + struct.pack('<I', len(dbname_bytes)) + dbname_bytes
|
||||
|
||||
|
||||
def _get_ext_server(vstupconnection):
|
||||
"""Zjistí SERVER z existující EXTERNI_DB (např. 'localhost/3053')."""
|
||||
cur = vstupconnection.cursor()
|
||||
cur.execute(
|
||||
"SELECT SERVER FROM EXTERNI_DB WHERE DBNAME LIKE 'DB%' ORDER BY DBNAME DESC ROWS 1"
|
||||
)
|
||||
row = cur.fetchone()
|
||||
return row[0] if row else 'localhost/3053'
|
||||
|
||||
|
||||
def _get_ext_heslo(vstupconnection):
|
||||
"""Přečte šifrované heslo z existující EXTERNI_DB.
|
||||
Všechny externí DB mají stejné heslo (masterkey, jen zašifrované Medicusem),
|
||||
takže stačí vzít heslo z libovolné existující položky."""
|
||||
cur = vstupconnection.cursor()
|
||||
cur.execute(
|
||||
"SELECT HESLO FROM EXTERNI_DB WHERE DBNAME LIKE 'DB%' ORDER BY DBNAME DESC ROWS 1"
|
||||
)
|
||||
row = cur.fetchone()
|
||||
return row[0] if row else 'masterkey'
|
||||
|
||||
|
||||
def _vytvor_externi_db(ext_path):
|
||||
"""Vytvoří novou prázdnou externí Firebird DB s tabulkou DATA a generátorem GEN_UID.
|
||||
Schéma odpovídá tomu, co vytváří Medicus sám při prvním exportu daného měsíce."""
|
||||
print(f" Vytvářím novou ext DB: {ext_path}")
|
||||
conn = fdb.create_database(
|
||||
dsn=f'localhost:{ext_path}',
|
||||
user='SYSDBA',
|
||||
password='masterkey',
|
||||
charset='WIN1250',
|
||||
page_size=16384
|
||||
)
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
CREATE TABLE DATA (
|
||||
UID VARCHAR(32) NOT NULL,
|
||||
DATA BLOB SUB_TYPE 0,
|
||||
DATASIZE INTEGER,
|
||||
CHUNK INTEGER
|
||||
)
|
||||
""")
|
||||
cur.execute("CREATE GENERATOR GEN_UID")
|
||||
conn.commit()
|
||||
print(f" Nová ext DB vytvořena: {ext_path}")
|
||||
return conn
|
||||
|
||||
|
||||
# ─── Hlavní funkce ───────────────────────────────────────────────────────────
|
||||
|
||||
def zapis_file_ext(vstupconnection, idpac, cesta, souborname, prvnizavorka,
|
||||
soubordate, souborfiledate, poznamka,
|
||||
ext_base_path=r'u:\externi'):
|
||||
"""Zapíše PDF soubor do měsíční externí DB a vrátí fileid.
|
||||
|
||||
Parametry jsou shodné s funkce.zapis_file() – stačí prohodit import.
|
||||
|
||||
Postup:
|
||||
1. Určí měsíc z soubordate → DBNAME (např. 'DB202603')
|
||||
2. Načte binární data PDF
|
||||
3. Najde nebo vytvoří měsíční MEDICUS_FILES_YYYYMM.fdb
|
||||
4. Zapíše data do DATA tabulky pod novým UID (UUID4 hex)
|
||||
5. Sestaví 48bajtovou BODY referenci
|
||||
6. Získá nové FILES ID z Gen_Files
|
||||
7. Vloží záznam do hlavní FILES tabulky (BODY = reference, ne BLOB)
|
||||
8. Vrátí fileid (shodné s funkce.zapis_file())
|
||||
"""
|
||||
import funkce # get_files_id ze stávajícího funkce.py
|
||||
|
||||
# 1. Měsíc a DBNAME
|
||||
yyyymm = soubordate.strftime('%Y%m')
|
||||
dbname = f'DB{yyyymm}'
|
||||
print(f" DBNAME: {dbname}")
|
||||
|
||||
# 2. Binární data PDF
|
||||
soubor_cesta = os.path.join(cesta, souborname)
|
||||
with open(soubor_cesta, 'rb') as f:
|
||||
data = f.read()
|
||||
print(f" Načten soubor: {souborname} ({len(data):,} B)")
|
||||
|
||||
# 3. Připojení k externí DB
|
||||
cur = vstupconnection.cursor()
|
||||
cur.execute(
|
||||
"SELECT SERVER, PATH FROM EXTERNI_DB WHERE DBNAME = ?", (dbname,)
|
||||
)
|
||||
row = cur.fetchone()
|
||||
|
||||
if row:
|
||||
# Existující měsíční DB – připojíme se
|
||||
ext_server_raw, ext_path = row[0], row[1]
|
||||
# SERVER je "localhost/3053" (FBScanner proxy) – bereme jen hostname
|
||||
ext_server = ext_server_raw.split('/')[0]
|
||||
print(f" Existující ext DB: {dbname} → {ext_path}")
|
||||
conn_ext = fdb.connect(
|
||||
dsn=f'{ext_server}:{ext_path}',
|
||||
user='SYSDBA',
|
||||
password='masterkey',
|
||||
charset='WIN1250'
|
||||
)
|
||||
else:
|
||||
# Nová měsíční DB – vytvoříme a zaregistrujeme
|
||||
ext_filename = f'MEDICUS_FILES_{yyyymm}.fdb'
|
||||
ext_path = os.path.join(ext_base_path, ext_filename)
|
||||
conn_ext = _vytvor_externi_db(ext_path)
|
||||
|
||||
# Zaregistrovat do EXTERNI_DB (heslo zkopírujeme z existující položky)
|
||||
ext_server = _get_ext_server(vstupconnection)
|
||||
heslo = _get_ext_heslo(vstupconnection)
|
||||
cur.execute(
|
||||
"INSERT INTO EXTERNI_DB (DBNAME, SERVER, PATH, HESLO) VALUES (?, ?, ?, ?)",
|
||||
(dbname, ext_server, ext_path, heslo)
|
||||
)
|
||||
vstupconnection.commit()
|
||||
print(f" Registrována v EXTERNI_DB: {dbname}, server={ext_server}")
|
||||
|
||||
# 4. Zápis do DATA tabulky
|
||||
uid = uuid.uuid4().hex # 32 hex znaků, vždy unikátní
|
||||
cur_ext = conn_ext.cursor()
|
||||
cur_ext.execute(
|
||||
"INSERT INTO DATA (UID, DATA, DATASIZE, CHUNK) VALUES (?, ?, ?, ?)",
|
||||
(uid, data, len(data), 0)
|
||||
)
|
||||
conn_ext.commit()
|
||||
conn_ext.close()
|
||||
print(f" Zapsáno do ext DB: UID={uid}")
|
||||
|
||||
# 5. BODY reference (48 B)
|
||||
body_ref = make_body_ref(uid, dbname)
|
||||
print(f" BODY ref: {body_ref.hex()}")
|
||||
|
||||
# 6. Nové FILES ID
|
||||
fileid = funkce.get_files_id(vstupconnection)
|
||||
if fileid is None:
|
||||
raise RuntimeError("Nepodařilo se získat nové FILES ID (Gen_Files selhal)!")
|
||||
|
||||
# 7. INSERT do hlavní FILES tabulky
|
||||
cur.execute(
|
||||
"INSERT INTO FILES "
|
||||
"(id, iduzi, iddoctyp, typ, idpac, filename, body, datum, datsouboru, poznamka) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(fileid, 6, 1, 1, idpac,
|
||||
prvnizavorka + '.pdf',
|
||||
body_ref,
|
||||
soubordate, souborfiledate,
|
||||
poznamka[:99])
|
||||
)
|
||||
vstupconnection.commit()
|
||||
print(f" FILES záznam vytvořen: ID={fileid}, idpac={idpac}, "
|
||||
f"soubor={prvnizavorka}.pdf")
|
||||
|
||||
return fileid
|
||||
@@ -0,0 +1,23 @@
|
||||
import fdb
|
||||
import datetime
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute('SELECT MAX(ID) FROM DEKURS')
|
||||
next_id = cur.fetchone()[0] + 1
|
||||
print('Next ID:', next_id)
|
||||
|
||||
text = r'{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\fonttbl{\f0\fnil\fcharset238 Arial;}}{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}' + '\n' + r'\pard\plain\f0\fs20 Test Claude AI - automaticky vlo\u382en\'fd z\'e1znam 13.03.2026\par' + '\n}'
|
||||
|
||||
cur.execute("""
|
||||
INSERT INTO DEKURS (ID, IDPAC, IDODD, IDPRAC, IDUZI, DATUM, CAS, DEKURS, DGN1)
|
||||
VALUES (?, 9742, 2, 2, 6, ?, ?, ?, 'Z000 ')
|
||||
""", (next_id, datetime.date(2026, 3, 13), datetime.time(12, 0, 0), text))
|
||||
|
||||
conn.commit()
|
||||
print('Vloženo OK, ID:', next_id)
|
||||
conn.close()
|
||||
@@ -0,0 +1,28 @@
|
||||
import fdb
|
||||
import sys
|
||||
|
||||
table = sys.argv[1] if len(sys.argv) > 1 else 'DEKURS'
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute("""
|
||||
SELECT rf.rdb$field_name, f.rdb$field_type, f.rdb$field_length
|
||||
FROM rdb$relation_fields rf
|
||||
JOIN rdb$fields f ON rf.rdb$field_source = f.rdb$field_name
|
||||
WHERE rf.rdb$relation_name = ?
|
||||
ORDER BY rf.rdb$field_position
|
||||
""", (table,))
|
||||
cols = cur.fetchall()
|
||||
print(f"=== {table} – sloupce ===")
|
||||
for c in cols:
|
||||
print(f" {c[0].strip():<30} typ: {c[1]} délka: {c[2]}")
|
||||
|
||||
cur.execute(f'SELECT COUNT(*) FROM {table}')
|
||||
print(f"\nPočet záznamů: {cur.fetchone()[0]}")
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
@@ -0,0 +1,16 @@
|
||||
# Firebird trace konfigurace – zachytí SQL příkazy Medicusu
|
||||
# Soubor: medicus_trace.conf
|
||||
|
||||
<database c:\\medicus 3\\data\\medicus.fdb>
|
||||
enabled true
|
||||
log_connections false
|
||||
log_transactions false
|
||||
log_statement_prepare false
|
||||
log_statement_start false
|
||||
log_statement_finish true
|
||||
log_procedure_finish false
|
||||
print_plan false
|
||||
print_perf false
|
||||
time_threshold 0
|
||||
max_sql_length 32768
|
||||
</database>
|
||||
Binary file not shown.
@@ -0,0 +1,22 @@
|
||||
"""read_last_dekurs.py – jednorázový skript: vypíše obsah posledního DEKURS záznamu pro Buzalka"""
|
||||
import fdb
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute("""
|
||||
SELECT FIRST 1 ID, DATUM, CAS, CHAR_LENGTH(DEKURS), DEKURS
|
||||
FROM DEKURS
|
||||
WHERE IDPAC = 9742
|
||||
ORDER BY ID DESC
|
||||
""")
|
||||
row = cur.fetchone()
|
||||
idek, datum, cas, delka, obsah = row
|
||||
|
||||
print(f"ID={idek} datum={datum} cas={cas} délka={delka} B")
|
||||
print("=" * 60)
|
||||
print(obsah)
|
||||
conn.close()
|
||||
@@ -0,0 +1,323 @@
|
||||
import os, shutil, fdb, time
|
||||
import re, datetime, funkce, funkce_ext
|
||||
|
||||
# Connect to the Firebird database
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA',
|
||||
password="masterkey",
|
||||
charset="win1250")
|
||||
|
||||
cesta = r"u:\testimport"
|
||||
cestazpracovana = r"u:\testimportzpracovana"
|
||||
|
||||
# Konstanty pro detekci sekce Vložené přílohy (RTF kódování win1250)
|
||||
PRILOHY_HEADER = r"Vlo\'9een\'e9 p\'f8\'edlohy:"
|
||||
PRILOHY_CLOSING = r'\pard\s10\plain\cs15\f0\fs20 \par'
|
||||
|
||||
# ─── Helper funkce ────────────────────────────────────────────────────────────
|
||||
|
||||
def restore_files_for_import(retezec):
|
||||
drop = r"u:\Dropbox\!!!Days\Downloads Z230\Dokumentace"
|
||||
next = r"u:\NextcloudOrdinace\Dokumentace_ke_zpracování"
|
||||
if not os.path.exists(drop):
|
||||
print(f"The directory '{drop}' does not exist.")
|
||||
return
|
||||
for item in os.listdir(drop):
|
||||
item_path = os.path.join(drop, item)
|
||||
if os.path.isfile(item_path) or os.path.islink(item_path):
|
||||
os.unlink(item_path)
|
||||
print(f"Deleted file: {item_path}")
|
||||
elif os.path.isdir(item_path):
|
||||
shutil.rmtree(item_path)
|
||||
print(f"Deleted directory: {item_path}")
|
||||
for item in os.listdir(next):
|
||||
item_path = os.path.join(next, item)
|
||||
if os.path.isfile(item_path) and item_path.endswith(".pdf") and retezec in item_path:
|
||||
shutil.copy(item_path, os.path.join(drop, item))
|
||||
print(f"Copied file: {item_path}")
|
||||
|
||||
|
||||
def kontrola_rc(rc, connection):
|
||||
cur = connection.cursor()
|
||||
cur.execute("select count(*),idpac from kar where rodcis=? group by idpac", (rc,))
|
||||
row = cur.fetchone()
|
||||
if row:
|
||||
return row[1]
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def kontrola_struktury(souborname, connection):
|
||||
if souborname.endswith('.pdf'):
|
||||
pattern = re.compile(r'(^\d{9,10}) (\d{4}-\d{2}-\d{2}) (\w+, \w.+?) \[(.+?)\] \[(.*?)\]')
|
||||
match = pattern.search(souborname)
|
||||
vpohode = True
|
||||
if match and len(match.groups()) == 5:
|
||||
datum = match.group(2)
|
||||
try:
|
||||
datetime.datetime.strptime(datum, "%Y-%m-%d").date()
|
||||
except:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
cur = connection.cursor()
|
||||
cur.execute("select count(*) from kar where rodcis=?", (match.group(1),))
|
||||
row = cur.fetchone()[0]
|
||||
if row != 1:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
else:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
else:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
return vpohode
|
||||
|
||||
|
||||
def vrat_info_o_souboru(souborname, connection):
|
||||
pattern = re.compile(r'(^\d{9,10}) (\d{4}-\d{2}-\d{2}) (\w+, \w.+?) \[(.+?)\] \[(.*?)\]')
|
||||
match = pattern.search(souborname)
|
||||
rc = match.group(1)
|
||||
datum = datetime.datetime.strptime(match.group(2), "%Y-%m-%d").date()
|
||||
jmeno = match.group(3)
|
||||
prvnizavorka = match.group(4)
|
||||
druhazavorka = match.group(5)
|
||||
cur = connection.cursor()
|
||||
cur.execute("select idpac from kar where rodcis=?", (rc,))
|
||||
idpac = cur.fetchone()[0]
|
||||
datumsouboru = datetime.datetime.fromtimestamp(os.path.getctime(os.path.join(cesta, souborname)))
|
||||
return (rc, idpac, datum, jmeno, prvnizavorka, druhazavorka, souborname, datumsouboru)
|
||||
|
||||
|
||||
def prejmenuj_chybny_soubor(souborname, cesta):
|
||||
if souborname[0] != "♥":
|
||||
soubornovy = "♥" + souborname
|
||||
os.rename(os.path.join(cesta, souborname), os.path.join(cesta, soubornovy))
|
||||
|
||||
|
||||
def najdi_posledni_dekurs_dnes(conn, idpac, datum_vlozeni):
|
||||
"""Vrátí (id, rtf) posledního dekurzu pacienta pokud je z dnešního dne, jinak None."""
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
SELECT FIRST 1 ID, DATUM, DEKURS FROM DEKURS
|
||||
WHERE IDPAC = ?
|
||||
ORDER BY ID DESC
|
||||
""", (idpac,))
|
||||
row = cur.fetchone()
|
||||
if row is None:
|
||||
return None
|
||||
dekurs_id, dekurs_datum, dekurs_rtf = row
|
||||
print(f" Poslední dekurs: ID={dekurs_id}, datum={dekurs_datum}")
|
||||
if dekurs_datum == datum_vlozeni:
|
||||
print(f" → dnešní den ({datum_vlozeni}) ✓")
|
||||
return (dekurs_id, dekurs_rtf)
|
||||
else:
|
||||
print(f" → jiný den ({dekurs_datum} ≠ {datum_vlozeni}), vytvoříme nový")
|
||||
return None
|
||||
|
||||
|
||||
def ma_sekci_prilohy(rtf):
|
||||
return PRILOHY_HEADER in rtf
|
||||
|
||||
|
||||
def pridat_do_sekce_prilohy(rtf, bookmark_list, filenameforbookmark_list):
|
||||
"""Přidá více souborů do EXISTUJÍCÍ sekce 'Vložené přílohy'.
|
||||
|
||||
Postup:
|
||||
1. Spočítá počet Files: odkazů = N → nové indexy začínají od N
|
||||
2. Vloží nové \\pard řádky před uzavírací prázdný řádek sekce
|
||||
3. Přidá nové bookmarky na konec {\\info{\\bookmarks ...}}
|
||||
"""
|
||||
# 1. Počet existujících Files: odkazů
|
||||
bkm_match = re.search(r'\{\\info\{\\bookmarks ([^}]*)\}\}', rtf)
|
||||
if bkm_match:
|
||||
bkm_entries = [e for e in bkm_match.group(1).split(';') if e.strip()]
|
||||
n_files = sum(1 for e in bkm_entries if '"Files:' in e)
|
||||
else:
|
||||
n_files = 0
|
||||
print(f" Počet existujících Files odkazů: {n_files}, přidávám {len(bookmark_list)} nových")
|
||||
|
||||
# 2. Vložit nové \pard řádky před PRILOHY_CLOSING
|
||||
prilohy_pos = rtf.find(PRILOHY_HEADER)
|
||||
closing_pos = rtf.find(PRILOHY_CLOSING, prilohy_pos)
|
||||
if closing_pos == -1:
|
||||
raise RuntimeError("Nenalezen uzavírací řádek sekce Vložené přílohy!")
|
||||
|
||||
new_pards = ''
|
||||
for i, fname in enumerate(filenameforbookmark_list):
|
||||
idx = n_files + i
|
||||
new_pards += (r'\pard\s10{\*\bkmkstart ' + str(idx) + r'}'
|
||||
r'\plain\cs32\f0\ul\fs20\cf1 ' + fname
|
||||
+ r'{\*\bkmkend ' + str(idx) + r'}\par' + '\n')
|
||||
|
||||
rtf = rtf[:closing_pos] + new_pards + rtf[closing_pos:]
|
||||
|
||||
# 3. Přidat nové bookmarky na konec {\info{\bookmarks ...}}
|
||||
def append_bookmarks(m):
|
||||
entries = [e for e in m.group(1).split(';') if e.strip()]
|
||||
entries.extend(bookmark_list)
|
||||
return '{\\info{\\bookmarks ' + ';'.join(entries) + '}}'
|
||||
|
||||
rtf = re.sub(r'\{\\info\{\\bookmarks ([^}]*)\}\}', append_bookmarks, rtf)
|
||||
return rtf
|
||||
|
||||
|
||||
def merge_rtf_prepend(existing_rtf, new_bkm_list, new_body_pards, n_new):
|
||||
"""Vloží novou sekci příloh na ZAČÁTEK stávajícího dekurzu (sekce tam ještě není)."""
|
||||
rtf = existing_rtf
|
||||
rtf = re.sub(r'\\bkmkstart (\d+)',
|
||||
lambda m: '\\bkmkstart ' + str(int(m.group(1)) + n_new), rtf)
|
||||
rtf = re.sub(r'\\bkmkend (\d+)',
|
||||
lambda m: '\\bkmkend ' + str(int(m.group(1)) + n_new), rtf)
|
||||
|
||||
new_bkm_str = ';'.join(new_bkm_list)
|
||||
|
||||
def merge_bkm(m):
|
||||
existing = m.group(1).strip()
|
||||
combined = new_bkm_str + (';' + existing if existing else '')
|
||||
return '{\\info{\\bookmarks ' + combined + '}}'
|
||||
|
||||
if re.search(r'\{\\info\{\\bookmarks', rtf):
|
||||
rtf = re.sub(r'\{\\info\{\\bookmarks ([^}]*)\}\}', merge_bkm, rtf)
|
||||
else:
|
||||
rtf = re.sub(r'(\\deflang\d+)',
|
||||
r'\1{\\info{\\bookmarks ' + new_bkm_str + '}}', rtf, count=1)
|
||||
|
||||
match = re.search(r'\\uc1\\pard', rtf)
|
||||
if match:
|
||||
pos = match.start()
|
||||
rtf = rtf[:pos] + new_body_pards + '\n' + rtf[pos:]
|
||||
return rtf
|
||||
|
||||
|
||||
# Šablona RTF pro nový dekurs
|
||||
RTF_TEMPLATE = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks BOOKMARKNAMES}}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
|
||||
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
{\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs32\f0\ul\fs20\cf1 Odkaz;}}
|
||||
BOOKMARKSTEXT
|
||||
\pard\s10\plain\cs15\f0\fs20 \par
|
||||
}"""
|
||||
|
||||
# ─── Hlavní tělo skriptu ──────────────────────────────────────────────────────
|
||||
|
||||
info = []
|
||||
for soubor in os.listdir(cesta):
|
||||
if os.path.isfile(os.path.join(cesta, soubor)):
|
||||
print(soubor)
|
||||
if kontrola_struktury(soubor, conn):
|
||||
info.append(vrat_info_o_souboru(soubor, conn))
|
||||
else:
|
||||
prejmenuj_chybny_soubor(soubor, cesta)
|
||||
|
||||
info = sorted(info, key=lambda x: (x[0], x[1]))
|
||||
print(info)
|
||||
|
||||
skupiny = {}
|
||||
for row in info:
|
||||
skupiny[row[0]] = []
|
||||
for row in info:
|
||||
skupiny[row[0]].append(row)
|
||||
|
||||
for key in skupiny.keys():
|
||||
print(f"\n{'='*60}")
|
||||
print(f"RC: {key}, souborů: {len(skupiny[key])}")
|
||||
|
||||
cislo = 9
|
||||
poradi = 0
|
||||
bookmark_list = []
|
||||
filenameforbookmark_list = []
|
||||
bookmarks_body = ''
|
||||
idpac = skupiny[key][0][1]
|
||||
|
||||
# ── Krok 1: vložit každý soubor do ext DB + přesunout do zpracovaných ────
|
||||
for row in skupiny[key]:
|
||||
fileid = funkce_ext.zapis_file_ext(
|
||||
vstupconnection=conn, idpac=row[1],
|
||||
cesta=cesta, souborname=row[6], prvnizavorka=row[4],
|
||||
soubordate=row[2], souborfiledate=row[7], poznamka=row[5])
|
||||
print(f" → FILES.ID = {fileid} ({row[6]})")
|
||||
|
||||
# Přesun souboru do zpracovaných
|
||||
for attempt in range(3):
|
||||
try:
|
||||
dest = os.path.join(cestazpracovana, row[6])
|
||||
if not os.path.exists(dest):
|
||||
shutil.move(os.path.join(cesta, row[6]), dest)
|
||||
else:
|
||||
ts = datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S")
|
||||
shutil.move(os.path.join(cesta, row[6]),
|
||||
os.path.join(cestazpracovana, row[6][:-4] + " " + ts + ".pdf"))
|
||||
print(" Přesun OK!")
|
||||
break
|
||||
except Exception as e:
|
||||
print(f" Attempt {attempt + 1} failed: {e}")
|
||||
if attempt < 2:
|
||||
print(" Retrying in 5 seconds...")
|
||||
time.sleep(5)
|
||||
else:
|
||||
print(" Max retries reached. Command failed.")
|
||||
|
||||
filenameforbookmark = row[2].strftime('%Y-%m-%d') + ' ' + row[4] + ': ' + row[5]
|
||||
bookmark_list.append('"' + filenameforbookmark + '","Files:' + str(fileid) + '",' + str(cislo))
|
||||
filenameforbookmark_list.append(filenameforbookmark)
|
||||
cislo += 7
|
||||
|
||||
bookmarks_body += (r'\pard\s10{\*\bkmkstart ' + str(poradi) + r'}'
|
||||
r'\plain\cs32\f0\ul\fs20\cf1 ' + filenameforbookmark
|
||||
+ r'{\*\bkmkend ' + str(poradi) + r'}\par')
|
||||
poradi += 1
|
||||
|
||||
# ── Krok 2: sestavit tělo nové sekce příloh ───────────────────────────────
|
||||
new_body = (r'\uc1\pard\s10\plain\cs20\f0\i\fs20 Vlo\'9een\'e9 p\'f8\'edlohy:\par' + '\n'
|
||||
+ bookmarks_body + '\n'
|
||||
+ r'\pard\s10\plain\cs15\f0\fs20 \par')
|
||||
|
||||
# ── Krok 3: rozhodovací logika (3 případy) ────────────────────────────────
|
||||
datumzapisu = datetime.datetime.now().date()
|
||||
caszapisu = datetime.datetime.now().time()
|
||||
cur = conn.cursor()
|
||||
|
||||
print(f"\n>>> Hledám poslední dekurs pro IDPAC={idpac}...")
|
||||
existujici = najdi_posledni_dekurs_dnes(conn, idpac, datumzapisu)
|
||||
|
||||
if existujici:
|
||||
dekurs_id, existing_rtf = existujici
|
||||
|
||||
if ma_sekci_prilohy(existing_rtf):
|
||||
# Případ 1: dnešní dekurs má sekci příloh → přidáme soubory dovnitř
|
||||
print(f"\n>>> Sekce 'Vložené přílohy' nalezena v DEKURS ID={dekurs_id}")
|
||||
print(">>> Přidávám soubory DO existující sekce...")
|
||||
merged_rtf = pridat_do_sekce_prilohy(existing_rtf, bookmark_list, filenameforbookmark_list)
|
||||
else:
|
||||
# Případ 2: dnešní dekurs existuje, ale sekci příloh nemá → prepend
|
||||
print(f"\n>>> DEKURS ID={dekurs_id} nemá sekci příloh → vkládám sekci na začátek...")
|
||||
merged_rtf = merge_rtf_prepend(existing_rtf, bookmark_list, new_body, len(skupiny[key]))
|
||||
|
||||
print("\n=== Výsledný RTF ===")
|
||||
print(merged_rtf)
|
||||
cur.execute("UPDATE DEKURS SET DEKURS = ? WHERE ID = ?", (merged_rtf, dekurs_id))
|
||||
conn.commit()
|
||||
print(f"\n>>> UPDATE DEKURS ID={dekurs_id} – hotovo!")
|
||||
|
||||
else:
|
||||
# Případ 3: žádný dnešní dekurs → vytvoříme nový
|
||||
print(f"\n>>> Žádný dekurs pro dnešek → vytvářím nový...")
|
||||
bookmark_str = ';'.join(bookmark_list)
|
||||
rtf = RTF_TEMPLATE.replace('BOOKMARKNAMES', bookmark_str)
|
||||
rtf = rtf.replace('BOOKMARKSTEXT', new_body)
|
||||
|
||||
print("\n=== Výsledný RTF ===")
|
||||
print(rtf)
|
||||
|
||||
dekursid = funkce.get_dekurs_id(conn)
|
||||
cur.execute(
|
||||
"INSERT INTO DEKURS (id, iduzi, idprac, idodd, idpac, datum, cas, dekurs)"
|
||||
" VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(dekursid, 6, 2, 2, idpac, datumzapisu, caszapisu, rtf)
|
||||
)
|
||||
conn.commit()
|
||||
print(f"\n>>> Nový DEKURS ID={dekursid}")
|
||||
|
||||
print("\n=== HOTOVO ===")
|
||||
conn.close()
|
||||
@@ -0,0 +1,397 @@
|
||||
import os, shutil, fdb, time, threading
|
||||
import re, datetime, funkce, funkce_ext
|
||||
|
||||
# Connect to the Firebird database
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA',
|
||||
password="masterkey",
|
||||
charset="win1250")
|
||||
|
||||
cesta = r"u:\testimport"
|
||||
cestazpracovana = r"u:\testimportzpracovana"
|
||||
|
||||
# Konstanty pro detekci sekce Vložené přílohy (RTF kódování win1250)
|
||||
PRILOHY_HEADER = r"Vlo\'9een\'e9 p\'f8\'edlohy:"
|
||||
PRILOHY_CLOSING = r'\pard\s10\plain\cs15\f0\fs20 \par'
|
||||
|
||||
# ─── Helper funkce ────────────────────────────────────────────────────────────
|
||||
|
||||
def restore_files_for_import(retezec):
|
||||
drop = r"u:\Dropbox\!!!Days\Downloads Z230\Dokumentace"
|
||||
next = r"u:\NextcloudOrdinace\Dokumentace_ke_zpracování"
|
||||
if not os.path.exists(drop):
|
||||
print(f"The directory '{drop}' does not exist.")
|
||||
return
|
||||
for item in os.listdir(drop):
|
||||
item_path = os.path.join(drop, item)
|
||||
if os.path.isfile(item_path) or os.path.islink(item_path):
|
||||
os.unlink(item_path)
|
||||
print(f"Deleted file: {item_path}")
|
||||
elif os.path.isdir(item_path):
|
||||
shutil.rmtree(item_path)
|
||||
print(f"Deleted directory: {item_path}")
|
||||
for item in os.listdir(next):
|
||||
item_path = os.path.join(next, item)
|
||||
if os.path.isfile(item_path) and item_path.endswith(".pdf") and retezec in item_path:
|
||||
shutil.copy(item_path, os.path.join(drop, item))
|
||||
print(f"Copied file: {item_path}")
|
||||
|
||||
|
||||
def kontrola_rc(rc, connection):
|
||||
cur = connection.cursor()
|
||||
cur.execute("select count(*),idpac from kar where rodcis=? group by idpac", (rc,))
|
||||
row = cur.fetchone()
|
||||
if row:
|
||||
return row[1]
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def kontrola_struktury(souborname, connection):
|
||||
if souborname.endswith('.pdf'):
|
||||
pattern = re.compile(r'(^\d{9,10}) (\d{4}-\d{2}-\d{2}) (\w+, \w.+?) \[(.+?)\] \[(.*?)\]')
|
||||
match = pattern.search(souborname)
|
||||
vpohode = True
|
||||
if match and len(match.groups()) == 5:
|
||||
datum = match.group(2)
|
||||
try:
|
||||
datetime.datetime.strptime(datum, "%Y-%m-%d").date()
|
||||
except:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
cur = connection.cursor()
|
||||
cur.execute("select count(*) from kar where rodcis=?", (match.group(1),))
|
||||
row = cur.fetchone()[0]
|
||||
if row != 1:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
else:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
else:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
return vpohode
|
||||
|
||||
|
||||
def vrat_info_o_souboru(souborname, connection):
|
||||
pattern = re.compile(r'(^\d{9,10}) (\d{4}-\d{2}-\d{2}) (\w+, \w.+?) \[(.+?)\] \[(.*?)\]')
|
||||
match = pattern.search(souborname)
|
||||
rc = match.group(1)
|
||||
datum = datetime.datetime.strptime(match.group(2), "%Y-%m-%d").date()
|
||||
jmeno = match.group(3)
|
||||
prvnizavorka = match.group(4)
|
||||
druhazavorka = match.group(5)
|
||||
cur = connection.cursor()
|
||||
cur.execute("select idpac from kar where rodcis=?", (rc,))
|
||||
idpac = cur.fetchone()[0]
|
||||
datumsouboru = datetime.datetime.fromtimestamp(os.path.getctime(os.path.join(cesta, souborname)))
|
||||
return (rc, idpac, datum, jmeno, prvnizavorka, druhazavorka, souborname, datumsouboru)
|
||||
|
||||
|
||||
def prejmenuj_chybny_soubor(souborname, cesta):
|
||||
if souborname[0] != "♥":
|
||||
soubornovy = "♥" + souborname
|
||||
os.rename(os.path.join(cesta, souborname), os.path.join(cesta, soubornovy))
|
||||
|
||||
|
||||
def _pokus_o_zamek(dekurs_id, vysledek):
|
||||
"""Běží ve vlákně: pokusí se zamknout dekurz přes separátní spojení.
|
||||
Výsledek zapíše do slovníku vysledek: {'ok': True} nebo {'chyba': str}.
|
||||
Pokud vlákno stále běží po uplynutí timeoutu → záznam je zamčený.
|
||||
"""
|
||||
conn_t = None
|
||||
try:
|
||||
conn_t = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur_t = conn_t.cursor()
|
||||
cur_t.execute(
|
||||
"SELECT ID FROM DEKURS WHERE ID = ? FOR UPDATE WITH LOCK",
|
||||
(dekurs_id,)
|
||||
)
|
||||
cur_t.fetchone()
|
||||
conn_t.rollback() # Uvolni zámek – sloužil jen k ověření
|
||||
vysledek['ok'] = True
|
||||
except Exception as e:
|
||||
vysledek['chyba'] = str(e)
|
||||
finally:
|
||||
if conn_t:
|
||||
try:
|
||||
conn_t.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def zkus_zamknout_dnesni_dekurs(conn, idpac, datum_vlozeni, timeout_sec=2):
|
||||
"""Zjistí zda existuje dnešní dekurz a ověří že není zamčený.
|
||||
|
||||
Vrátí:
|
||||
(id, rtf) – dnešní dekurz existuje a není zamčený
|
||||
None – žádný dnešní dekurz (bude se dělat INSERT, zámek není potřeba)
|
||||
|
||||
Vyhodí RuntimeError pokud je záznam zamčený jiným uživatelem (Medicus ho má otevřený).
|
||||
|
||||
Poznámka: NOWAIT transakci fdb neumí spolehlivě nastavit, proto spustíme
|
||||
pokus o zámek ve vlákně s timeoutem. Pokud vlákno do timeout_sec sekund
|
||||
neskončí, záznam je zamčený a přeskočíme celou skupinu.
|
||||
"""
|
||||
cur = conn.cursor()
|
||||
|
||||
# Krok 1: přečti ID, datum a obsah posledního dekurzu (běžný SELECT)
|
||||
cur.execute("""
|
||||
SELECT FIRST 1 ID, DATUM, DEKURS FROM DEKURS
|
||||
WHERE IDPAC = ?
|
||||
ORDER BY ID DESC
|
||||
""", (idpac,))
|
||||
row = cur.fetchone()
|
||||
if row is None:
|
||||
print(f" Žádný dekurz pro pacienta IDPAC={idpac}")
|
||||
return None
|
||||
|
||||
dekurs_id, dekurs_datum, dekurs_rtf = row
|
||||
print(f" Poslední dekurs: ID={dekurs_id}, datum={dekurs_datum}")
|
||||
|
||||
if dekurs_datum != datum_vlozeni:
|
||||
print(f" → jiný den ({dekurs_datum} ≠ {datum_vlozeni}), vytvoříme nový (INSERT)")
|
||||
return None
|
||||
|
||||
# Krok 2: ověř přes vlákno s timeoutem zda záznam není zamčený
|
||||
print(f" → dnešní den ({datum_vlozeni}) ✓ – ověřuji zámek (timeout {timeout_sec}s)...")
|
||||
vysledek = {}
|
||||
t = threading.Thread(target=_pokus_o_zamek, args=(dekurs_id, vysledek), daemon=True)
|
||||
t.start()
|
||||
t.join(timeout=timeout_sec)
|
||||
|
||||
if t.is_alive():
|
||||
# Vlákno stále čeká na zámek = záznam drží Medicus
|
||||
raise RuntimeError(f"DEKURZ ID={dekurs_id} je zamčený (Medicus má záznam otevřený)")
|
||||
|
||||
if 'chyba' in vysledek:
|
||||
raise fdb.DatabaseError(vysledek['chyba'])
|
||||
|
||||
print(f" → záznam volný, pokračuji se zápisem")
|
||||
return (dekurs_id, dekurs_rtf)
|
||||
|
||||
|
||||
def ma_sekci_prilohy(rtf):
|
||||
return PRILOHY_HEADER in rtf
|
||||
|
||||
|
||||
def pridat_do_sekce_prilohy(rtf, bookmark_list, filenameforbookmark_list):
|
||||
"""Přidá více souborů do EXISTUJÍCÍ sekce 'Vložené přílohy'.
|
||||
|
||||
Postup:
|
||||
1. Spočítá počet Files: odkazů = N → nové indexy začínají od N
|
||||
2. Vloží nové \\pard řádky před uzavírací prázdný řádek sekce
|
||||
3. Přidá nové bookmarky na konec {\\info{\\bookmarks ...}}
|
||||
"""
|
||||
# 1. Počet existujících Files: odkazů
|
||||
bkm_match = re.search(r'\{\\info\{\\bookmarks ([^}]*)\}\}', rtf)
|
||||
if bkm_match:
|
||||
bkm_entries = [e for e in bkm_match.group(1).split(';') if e.strip()]
|
||||
n_files = sum(1 for e in bkm_entries if '"Files:' in e)
|
||||
else:
|
||||
n_files = 0
|
||||
print(f" Počet existujících Files odkazů: {n_files}, přidávám {len(bookmark_list)} nových")
|
||||
|
||||
# 2. Vložit nové \pard řádky před PRILOHY_CLOSING
|
||||
prilohy_pos = rtf.find(PRILOHY_HEADER)
|
||||
closing_pos = rtf.find(PRILOHY_CLOSING, prilohy_pos)
|
||||
if closing_pos == -1:
|
||||
raise RuntimeError("Nenalezen uzavírací řádek sekce Vložené přílohy!")
|
||||
|
||||
new_pards = ''
|
||||
for i, fname in enumerate(filenameforbookmark_list):
|
||||
idx = n_files + i
|
||||
new_pards += (r'\pard\s10{\*\bkmkstart ' + str(idx) + r'}'
|
||||
r'\plain\cs32\f0\ul\fs20\cf1 ' + fname
|
||||
+ r'{\*\bkmkend ' + str(idx) + r'}\par' + '\n')
|
||||
|
||||
rtf = rtf[:closing_pos] + new_pards + rtf[closing_pos:]
|
||||
|
||||
# 3. Přidat nové bookmarky na konec {\info{\bookmarks ...}}
|
||||
def append_bookmarks(m):
|
||||
entries = [e for e in m.group(1).split(';') if e.strip()]
|
||||
entries.extend(bookmark_list)
|
||||
return '{\\info{\\bookmarks ' + ';'.join(entries) + '}}'
|
||||
|
||||
rtf = re.sub(r'\{\\info\{\\bookmarks ([^}]*)\}\}', append_bookmarks, rtf)
|
||||
return rtf
|
||||
|
||||
|
||||
def merge_rtf_prepend(existing_rtf, new_bkm_list, new_body_pards, n_new):
|
||||
"""Vloží novou sekci příloh na ZAČÁTEK stávajícího dekurzu (sekce tam ještě není)."""
|
||||
rtf = existing_rtf
|
||||
rtf = re.sub(r'\\bkmkstart (\d+)',
|
||||
lambda m: '\\bkmkstart ' + str(int(m.group(1)) + n_new), rtf)
|
||||
rtf = re.sub(r'\\bkmkend (\d+)',
|
||||
lambda m: '\\bkmkend ' + str(int(m.group(1)) + n_new), rtf)
|
||||
|
||||
new_bkm_str = ';'.join(new_bkm_list)
|
||||
|
||||
def merge_bkm(m):
|
||||
existing = m.group(1).strip()
|
||||
combined = new_bkm_str + (';' + existing if existing else '')
|
||||
return '{\\info{\\bookmarks ' + combined + '}}'
|
||||
|
||||
if re.search(r'\{\\info\{\\bookmarks', rtf):
|
||||
rtf = re.sub(r'\{\\info\{\\bookmarks ([^}]*)\}\}', merge_bkm, rtf)
|
||||
else:
|
||||
rtf = re.sub(r'(\\deflang\d+)',
|
||||
r'\1{\\info{\\bookmarks ' + new_bkm_str + '}}', rtf, count=1)
|
||||
|
||||
match = re.search(r'\\uc1\\pard', rtf)
|
||||
if match:
|
||||
pos = match.start()
|
||||
rtf = rtf[:pos] + new_body_pards + '\n' + rtf[pos:]
|
||||
return rtf
|
||||
|
||||
|
||||
# Šablona RTF pro nový dekurs
|
||||
RTF_TEMPLATE = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks BOOKMARKNAMES}}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
|
||||
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
{\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs32\f0\ul\fs20\cf1 Odkaz;}}
|
||||
BOOKMARKSTEXT
|
||||
\pard\s10\plain\cs15\f0\fs20 \par
|
||||
}"""
|
||||
|
||||
# ─── Hlavní tělo skriptu ──────────────────────────────────────────────────────
|
||||
|
||||
info = []
|
||||
for soubor in os.listdir(cesta):
|
||||
if os.path.isfile(os.path.join(cesta, soubor)):
|
||||
print(soubor)
|
||||
if kontrola_struktury(soubor, conn):
|
||||
info.append(vrat_info_o_souboru(soubor, conn))
|
||||
else:
|
||||
prejmenuj_chybny_soubor(soubor, cesta)
|
||||
|
||||
info = sorted(info, key=lambda x: (x[0], x[1]))
|
||||
print(info)
|
||||
|
||||
skupiny = {}
|
||||
for row in info:
|
||||
skupiny[row[0]] = []
|
||||
for row in info:
|
||||
skupiny[row[0]].append(row)
|
||||
|
||||
for key in skupiny.keys():
|
||||
print(f"\n{'='*60}")
|
||||
print(f"RC: {key}, souborů: {len(skupiny[key])}")
|
||||
|
||||
idpac = skupiny[key][0][1]
|
||||
datumzapisu = datetime.datetime.now().date()
|
||||
caszapisu = datetime.datetime.now().time()
|
||||
|
||||
# ── PRE-CHECK: zkus zamknout dnešní dekurz PŘED zpracováním souborů ──────
|
||||
print(f"\n>>> Kontrola zámku dekurzu pro IDPAC={idpac}...")
|
||||
try:
|
||||
existujici = zkus_zamknout_dnesni_dekurs(conn, idpac, datumzapisu)
|
||||
except RuntimeError as e:
|
||||
# Vlákno nepřišlo do timeoutu = záznam drží Medicus
|
||||
print(f"\n!!! DEKURZ ZAMČEN – soubory skupiny RC={key} přeskočeny.")
|
||||
print(" Spusťte skript znovu až bude záznam volný.")
|
||||
continue
|
||||
except fdb.DatabaseError as e:
|
||||
chyba = str(e).lower()
|
||||
if 'deadlock' in chyba or 'lock conflict' in chyba or 'update conflict' in chyba:
|
||||
print(f"\n!!! DEKURZ ZAMČEN (DB konflikt) – soubory skupiny RC={key} přeskočeny.")
|
||||
print(" Spusťte skript znovu až bude záznam volný.")
|
||||
continue
|
||||
raise # jiná DB chyba – propaguj dál
|
||||
|
||||
cislo = 9
|
||||
poradi = 0
|
||||
bookmark_list = []
|
||||
filenameforbookmark_list = []
|
||||
bookmarks_body = ''
|
||||
|
||||
# ── Krok 1: vložit každý soubor do ext DB + přesunout do zpracovaných ────
|
||||
for row in skupiny[key]:
|
||||
fileid = funkce_ext.zapis_file_ext(
|
||||
vstupconnection=conn, idpac=row[1],
|
||||
cesta=cesta, souborname=row[6], prvnizavorka=row[4],
|
||||
soubordate=row[2], souborfiledate=row[7], poznamka=row[5])
|
||||
print(f" → FILES.ID = {fileid} ({row[6]})")
|
||||
|
||||
# Přesun souboru do zpracovaných
|
||||
for attempt in range(3):
|
||||
try:
|
||||
dest = os.path.join(cestazpracovana, row[6])
|
||||
if not os.path.exists(dest):
|
||||
shutil.move(os.path.join(cesta, row[6]), dest)
|
||||
else:
|
||||
ts = datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S")
|
||||
shutil.move(os.path.join(cesta, row[6]),
|
||||
os.path.join(cestazpracovana, row[6][:-4] + " " + ts + ".pdf"))
|
||||
print(" Přesun OK!")
|
||||
break
|
||||
except Exception as e:
|
||||
print(f" Attempt {attempt + 1} failed: {e}")
|
||||
if attempt < 2:
|
||||
print(" Retrying in 5 seconds...")
|
||||
time.sleep(5)
|
||||
else:
|
||||
print(" Max retries reached. Command failed.")
|
||||
|
||||
filenameforbookmark = row[2].strftime('%Y-%m-%d') + ' ' + row[4] + ': ' + row[5]
|
||||
bookmark_list.append('"' + filenameforbookmark + '","Files:' + str(fileid) + '",' + str(cislo))
|
||||
filenameforbookmark_list.append(filenameforbookmark)
|
||||
cislo += 7
|
||||
|
||||
bookmarks_body += (r'\pard\s10{\*\bkmkstart ' + str(poradi) + r'}'
|
||||
r'\plain\cs32\f0\ul\fs20\cf1 ' + filenameforbookmark
|
||||
+ r'{\*\bkmkend ' + str(poradi) + r'}\par')
|
||||
poradi += 1
|
||||
|
||||
# ── Krok 2: sestavit tělo nové sekce příloh ───────────────────────────────
|
||||
new_body = (r'\uc1\pard\s10\plain\cs20\f0\i\fs20 Vlo\'9een\'e9 p\'f8\'edlohy:\par' + '\n'
|
||||
+ bookmarks_body + '\n'
|
||||
+ r'\pard\s10\plain\cs15\f0\fs20 \par')
|
||||
|
||||
# ── Krok 3: rozhodovací logika (3 případy) ────────────────────────────────
|
||||
cur = conn.cursor()
|
||||
|
||||
if existujici:
|
||||
dekurs_id, existing_rtf = existujici
|
||||
|
||||
if ma_sekci_prilohy(existing_rtf):
|
||||
# Případ 1: dnešní dekurz má sekci příloh → přidáme soubory dovnitř
|
||||
print(f"\n>>> Sekce 'Vložené přílohy' nalezena v DEKURS ID={dekurs_id}")
|
||||
print(">>> Přidávám soubory DO existující sekce...")
|
||||
merged_rtf = pridat_do_sekce_prilohy(existing_rtf, bookmark_list, filenameforbookmark_list)
|
||||
else:
|
||||
# Případ 2: dnešní dekurz existuje, ale sekci příloh nemá → prepend
|
||||
print(f"\n>>> DEKURS ID={dekurs_id} nemá sekci příloh → vkládám sekci na začátek...")
|
||||
merged_rtf = merge_rtf_prepend(existing_rtf, bookmark_list, new_body, len(skupiny[key]))
|
||||
|
||||
print("\n=== Výsledný RTF ===")
|
||||
print(merged_rtf)
|
||||
cur.execute("UPDATE DEKURS SET DEKURS = ? WHERE ID = ?", (merged_rtf, dekurs_id))
|
||||
conn.commit()
|
||||
print(f"\n>>> UPDATE DEKURS ID={dekurs_id} – hotovo!")
|
||||
|
||||
else:
|
||||
# Případ 3: žádný dnešní dekurz → vytvoříme nový
|
||||
print(f"\n>>> Žádný dekurs pro dnešek → vytvářím nový...")
|
||||
bookmark_str = ';'.join(bookmark_list)
|
||||
rtf = RTF_TEMPLATE.replace('BOOKMARKNAMES', bookmark_str)
|
||||
rtf = rtf.replace('BOOKMARKSTEXT', new_body)
|
||||
|
||||
print("\n=== Výsledný RTF ===")
|
||||
print(rtf)
|
||||
|
||||
dekursid = funkce.get_dekurs_id(conn)
|
||||
cur.execute(
|
||||
"INSERT INTO DEKURS (id, iduzi, idprac, idodd, idpac, datum, cas, dekurs)"
|
||||
" VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(dekursid, 6, 2, 2, idpac, datumzapisu, caszapisu, rtf)
|
||||
)
|
||||
conn.commit()
|
||||
print(f"\n>>> Nový DEKURS ID={dekursid}")
|
||||
|
||||
print("\n=== HOTOVO ===")
|
||||
conn.close()
|
||||
@@ -0,0 +1,397 @@
|
||||
import os, shutil, fdb, time, threading
|
||||
import re, datetime, funkce, funkce_ext
|
||||
|
||||
# Connect to the Firebird database
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA',
|
||||
password="masterkey",
|
||||
charset="win1250")
|
||||
|
||||
cesta = r"u:\testimport"
|
||||
cestazpracovana = r"u:\testimportzpracovana"
|
||||
|
||||
# Konstanty pro detekci sekce Vložené přílohy (RTF kódování win1250)
|
||||
PRILOHY_HEADER = r"Vlo\'9een\'e9 p\'f8\'edlohy:"
|
||||
PRILOHY_CLOSING = r'\pard\s10\plain\cs15\f0\fs20 \par'
|
||||
|
||||
# ─── Helper funkce ────────────────────────────────────────────────────────────
|
||||
|
||||
def restore_files_for_import(retezec):
|
||||
drop = r"u:\Dropbox\!!!Days\Downloads Z230\Dokumentace"
|
||||
next = r"u:\NextcloudOrdinace\Dokumentace_ke_zpracování"
|
||||
if not os.path.exists(drop):
|
||||
print(f"The directory '{drop}' does not exist.")
|
||||
return
|
||||
for item in os.listdir(drop):
|
||||
item_path = os.path.join(drop, item)
|
||||
if os.path.isfile(item_path) or os.path.islink(item_path):
|
||||
os.unlink(item_path)
|
||||
print(f"Deleted file: {item_path}")
|
||||
elif os.path.isdir(item_path):
|
||||
shutil.rmtree(item_path)
|
||||
print(f"Deleted directory: {item_path}")
|
||||
for item in os.listdir(next):
|
||||
item_path = os.path.join(next, item)
|
||||
if os.path.isfile(item_path) and item_path.endswith(".pdf") and retezec in item_path:
|
||||
shutil.copy(item_path, os.path.join(drop, item))
|
||||
print(f"Copied file: {item_path}")
|
||||
|
||||
|
||||
def kontrola_rc(rc, connection):
|
||||
cur = connection.cursor()
|
||||
cur.execute("select count(*),idpac from kar where rodcis=? group by idpac", (rc,))
|
||||
row = cur.fetchone()
|
||||
if row:
|
||||
return row[1]
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def kontrola_struktury(souborname, connection):
|
||||
if souborname.endswith('.pdf'):
|
||||
pattern = re.compile(r'(^\d{9,10}) (\d{4}-\d{2}-\d{2}) (\w+, \w.+?) \[(.+?)\] \[(.*?)\]')
|
||||
match = pattern.search(souborname)
|
||||
vpohode = True
|
||||
if match and len(match.groups()) == 5:
|
||||
datum = match.group(2)
|
||||
try:
|
||||
datetime.datetime.strptime(datum, "%Y-%m-%d").date()
|
||||
except:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
cur = connection.cursor()
|
||||
cur.execute("select count(*) from kar where rodcis=?", (match.group(1),))
|
||||
row = cur.fetchone()[0]
|
||||
if row != 1:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
else:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
else:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
return vpohode
|
||||
|
||||
|
||||
def vrat_info_o_souboru(souborname, connection):
|
||||
pattern = re.compile(r'(^\d{9,10}) (\d{4}-\d{2}-\d{2}) (\w+, \w.+?) \[(.+?)\] \[(.*?)\]')
|
||||
match = pattern.search(souborname)
|
||||
rc = match.group(1)
|
||||
datum = datetime.datetime.strptime(match.group(2), "%Y-%m-%d").date()
|
||||
jmeno = match.group(3)
|
||||
prvnizavorka = match.group(4)
|
||||
druhazavorka = match.group(5)
|
||||
cur = connection.cursor()
|
||||
cur.execute("select idpac from kar where rodcis=?", (rc,))
|
||||
idpac = cur.fetchone()[0]
|
||||
datumsouboru = datetime.datetime.fromtimestamp(os.path.getctime(os.path.join(cesta, souborname)))
|
||||
return (rc, idpac, datum, jmeno, prvnizavorka, druhazavorka, souborname, datumsouboru)
|
||||
|
||||
|
||||
def prejmenuj_chybny_soubor(souborname, cesta):
|
||||
if souborname[0] != "♥":
|
||||
soubornovy = "♥" + souborname
|
||||
os.rename(os.path.join(cesta, souborname), os.path.join(cesta, soubornovy))
|
||||
|
||||
|
||||
def _pokus_o_zamek(dekurs_id, vysledek):
|
||||
"""Běží ve vlákně: pokusí se zamknout dekurz přes separátní spojení.
|
||||
Výsledek zapíše do slovníku vysledek: {'ok': True} nebo {'chyba': str}.
|
||||
Pokud vlákno stále běží po uplynutí timeoutu → záznam je zamčený.
|
||||
"""
|
||||
conn_t = None
|
||||
try:
|
||||
conn_t = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur_t = conn_t.cursor()
|
||||
cur_t.execute(
|
||||
"SELECT ID FROM DEKURS WHERE ID = ? FOR UPDATE WITH LOCK",
|
||||
(dekurs_id,)
|
||||
)
|
||||
cur_t.fetchone()
|
||||
conn_t.rollback() # Uvolni zámek – sloužil jen k ověření
|
||||
vysledek['ok'] = True
|
||||
except Exception as e:
|
||||
vysledek['chyba'] = str(e)
|
||||
finally:
|
||||
if conn_t:
|
||||
try:
|
||||
conn_t.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def zkus_zamknout_dnesni_dekurs(conn, idpac, datum_vlozeni, timeout_sec=2):
|
||||
"""Zjistí zda existuje dnešní dekurz a ověří že není zamčený.
|
||||
|
||||
Vrátí:
|
||||
(id, rtf) – dnešní dekurz existuje a není zamčený
|
||||
None – žádný dnešní dekurz (bude se dělat INSERT, zámek není potřeba)
|
||||
|
||||
Vyhodí RuntimeError pokud je záznam zamčený jiným uživatelem (Medicus ho má otevřený).
|
||||
|
||||
Poznámka: NOWAIT transakci fdb neumí spolehlivě nastavit, proto spustíme
|
||||
pokus o zámek ve vlákně s timeoutem. Pokud vlákno do timeout_sec sekund
|
||||
neskončí, záznam je zamčený a přeskočíme celou skupinu.
|
||||
"""
|
||||
cur = conn.cursor()
|
||||
|
||||
# Krok 1: přečti ID, datum a obsah posledního dekurzu (běžný SELECT)
|
||||
cur.execute("""
|
||||
SELECT FIRST 1 ID, DATUM, DEKURS FROM DEKURS
|
||||
WHERE IDPAC = ?
|
||||
ORDER BY ID DESC
|
||||
""", (idpac,))
|
||||
row = cur.fetchone()
|
||||
if row is None:
|
||||
print(f" Žádný dekurz pro pacienta IDPAC={idpac}")
|
||||
return None
|
||||
|
||||
dekurs_id, dekurs_datum, dekurs_rtf = row
|
||||
print(f" Poslední dekurs: ID={dekurs_id}, datum={dekurs_datum}")
|
||||
|
||||
if dekurs_datum != datum_vlozeni:
|
||||
print(f" → jiný den ({dekurs_datum} ≠ {datum_vlozeni}), vytvoříme nový (INSERT)")
|
||||
return None
|
||||
|
||||
# Krok 2: ověř přes vlákno s timeoutem zda záznam není zamčený
|
||||
print(f" → dnešní den ({datum_vlozeni}) ✓ – ověřuji zámek (timeout {timeout_sec}s)...")
|
||||
vysledek = {}
|
||||
t = threading.Thread(target=_pokus_o_zamek, args=(dekurs_id, vysledek), daemon=True)
|
||||
t.start()
|
||||
t.join(timeout=timeout_sec)
|
||||
|
||||
if t.is_alive():
|
||||
# Vlákno stále čeká na zámek = záznam drží Medicus
|
||||
raise RuntimeError(f"DEKURZ ID={dekurs_id} je zamčený (Medicus má záznam otevřený)")
|
||||
|
||||
if 'chyba' in vysledek:
|
||||
raise fdb.DatabaseError(vysledek['chyba'])
|
||||
|
||||
print(f" → záznam volný, pokračuji se zápisem")
|
||||
return (dekurs_id, dekurs_rtf)
|
||||
|
||||
|
||||
def ma_sekci_prilohy(rtf):
|
||||
return PRILOHY_HEADER in rtf
|
||||
|
||||
|
||||
def pridat_do_sekce_prilohy(rtf, bookmark_list, filenameforbookmark_list):
|
||||
"""Přidá více souborů do EXISTUJÍCÍ sekce 'Vložené přílohy'.
|
||||
|
||||
Postup:
|
||||
1. Spočítá počet Files: odkazů = N → nové indexy začínají od N
|
||||
2. Vloží nové \\pard řádky před uzavírací prázdný řádek sekce
|
||||
3. Přidá nové bookmarky na konec {\\info{\\bookmarks ...}}
|
||||
"""
|
||||
# 1. Počet existujících Files: odkazů
|
||||
bkm_match = re.search(r'\{\\info\{\\bookmarks ([^}]*)\}\}', rtf)
|
||||
if bkm_match:
|
||||
bkm_entries = [e for e in bkm_match.group(1).split(';') if e.strip()]
|
||||
n_files = sum(1 for e in bkm_entries if '"Files:' in e)
|
||||
else:
|
||||
n_files = 0
|
||||
print(f" Počet existujících Files odkazů: {n_files}, přidávám {len(bookmark_list)} nových")
|
||||
|
||||
# 2. Vložit nové \pard řádky před PRILOHY_CLOSING
|
||||
prilohy_pos = rtf.find(PRILOHY_HEADER)
|
||||
closing_pos = rtf.find(PRILOHY_CLOSING, prilohy_pos)
|
||||
if closing_pos == -1:
|
||||
raise RuntimeError("Nenalezen uzavírací řádek sekce Vložené přílohy!")
|
||||
|
||||
new_pards = ''
|
||||
for i, fname in enumerate(filenameforbookmark_list):
|
||||
idx = n_files + i
|
||||
new_pards += (r'\pard\s10{\*\bkmkstart ' + str(idx) + r'}'
|
||||
r'\plain\cs32\f0\ul\fs20\cf1 ' + fname
|
||||
+ r'{\*\bkmkend ' + str(idx) + r'}\par' + '\n')
|
||||
|
||||
rtf = rtf[:closing_pos] + new_pards + rtf[closing_pos:]
|
||||
|
||||
# 3. Přidat nové bookmarky na konec {\info{\bookmarks ...}}
|
||||
def append_bookmarks(m):
|
||||
entries = [e for e in m.group(1).split(';') if e.strip()]
|
||||
entries.extend(bookmark_list)
|
||||
return '{\\info{\\bookmarks ' + ';'.join(entries) + '}}'
|
||||
|
||||
rtf = re.sub(r'\{\\info\{\\bookmarks ([^}]*)\}\}', append_bookmarks, rtf)
|
||||
return rtf
|
||||
|
||||
|
||||
def merge_rtf_prepend(existing_rtf, new_bkm_list, new_body_pards, n_new):
|
||||
"""Vloží novou sekci příloh na ZAČÁTEK stávajícího dekurzu (sekce tam ještě není)."""
|
||||
rtf = existing_rtf
|
||||
rtf = re.sub(r'\\bkmkstart (\d+)',
|
||||
lambda m: '\\bkmkstart ' + str(int(m.group(1)) + n_new), rtf)
|
||||
rtf = re.sub(r'\\bkmkend (\d+)',
|
||||
lambda m: '\\bkmkend ' + str(int(m.group(1)) + n_new), rtf)
|
||||
|
||||
new_bkm_str = ';'.join(new_bkm_list)
|
||||
|
||||
def merge_bkm(m):
|
||||
existing = m.group(1).strip()
|
||||
combined = new_bkm_str + (';' + existing if existing else '')
|
||||
return '{\\info{\\bookmarks ' + combined + '}}'
|
||||
|
||||
if re.search(r'\{\\info\{\\bookmarks', rtf):
|
||||
rtf = re.sub(r'\{\\info\{\\bookmarks ([^}]*)\}\}', merge_bkm, rtf)
|
||||
else:
|
||||
rtf = re.sub(r'(\\deflang\d+)',
|
||||
r'\1{\\info{\\bookmarks ' + new_bkm_str + '}}', rtf, count=1)
|
||||
|
||||
match = re.search(r'\\uc1\\pard', rtf)
|
||||
if match:
|
||||
pos = match.start()
|
||||
rtf = rtf[:pos] + new_body_pards + '\n' + rtf[pos:]
|
||||
return rtf
|
||||
|
||||
|
||||
# Šablona RTF pro nový dekurs
|
||||
RTF_TEMPLATE = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks BOOKMARKNAMES}}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
|
||||
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
{\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs32\f0\ul\fs20\cf1 Odkaz;}}
|
||||
BOOKMARKSTEXT
|
||||
\pard\s10\plain\cs15\f0\fs20 \par
|
||||
}"""
|
||||
|
||||
# ─── Hlavní tělo skriptu ──────────────────────────────────────────────────────
|
||||
|
||||
info = []
|
||||
for soubor in os.listdir(cesta):
|
||||
if os.path.isfile(os.path.join(cesta, soubor)):
|
||||
print(soubor)
|
||||
if kontrola_struktury(soubor, conn):
|
||||
info.append(vrat_info_o_souboru(soubor, conn))
|
||||
else:
|
||||
prejmenuj_chybny_soubor(soubor, cesta)
|
||||
|
||||
info = sorted(info, key=lambda x: (x[0], x[1]))
|
||||
print(info)
|
||||
|
||||
skupiny = {}
|
||||
for row in info:
|
||||
skupiny[row[0]] = []
|
||||
for row in info:
|
||||
skupiny[row[0]].append(row)
|
||||
|
||||
for key in skupiny.keys():
|
||||
print(f"\n{'='*60}")
|
||||
print(f"RC: {key}, souborů: {len(skupiny[key])}")
|
||||
|
||||
idpac = skupiny[key][0][1]
|
||||
datumzapisu = datetime.datetime.now().date()
|
||||
caszapisu = datetime.datetime.now().time()
|
||||
|
||||
# ── PRE-CHECK: zkus zamknout dnešní dekurz PŘED zpracováním souborů ──────
|
||||
print(f"\n>>> Kontrola zámku dekurzu pro IDPAC={idpac}...")
|
||||
try:
|
||||
existujici = zkus_zamknout_dnesni_dekurs(conn, idpac, datumzapisu)
|
||||
except RuntimeError as e:
|
||||
# Vlákno nepřišlo do timeoutu = záznam drží Medicus
|
||||
print(f"\n!!! DEKURZ ZAMČEN – soubory skupiny RC={key} přeskočeny.")
|
||||
print(" Spusťte skript znovu až bude záznam volný.")
|
||||
continue
|
||||
except fdb.DatabaseError as e:
|
||||
chyba = str(e).lower()
|
||||
if 'deadlock' in chyba or 'lock conflict' in chyba or 'update conflict' in chyba:
|
||||
print(f"\n!!! DEKURZ ZAMČEN (DB konflikt) – soubory skupiny RC={key} přeskočeny.")
|
||||
print(" Spusťte skript znovu až bude záznam volný.")
|
||||
continue
|
||||
raise # jiná DB chyba – propaguj dál
|
||||
|
||||
cislo = 9
|
||||
poradi = 0
|
||||
bookmark_list = []
|
||||
filenameforbookmark_list = []
|
||||
bookmarks_body = ''
|
||||
|
||||
# ── Krok 1: vložit každý soubor do ext DB + přesunout do zpracovaných ────
|
||||
for row in skupiny[key]:
|
||||
fileid = funkce_ext.zapis_file_ext(
|
||||
vstupconnection=conn, idpac=row[1],
|
||||
cesta=cesta, souborname=row[6], prvnizavorka=row[4],
|
||||
soubordate=row[2], souborfiledate=row[7], poznamka=row[5])
|
||||
print(f" → FILES.ID = {fileid} ({row[6]})")
|
||||
|
||||
# Přesun souboru do zpracovaných
|
||||
for attempt in range(3):
|
||||
try:
|
||||
dest = os.path.join(cestazpracovana, row[6])
|
||||
if not os.path.exists(dest):
|
||||
shutil.move(os.path.join(cesta, row[6]), dest)
|
||||
else:
|
||||
ts = datetime.datetime.now().strftime("%Y-%m-%d %H-%M-%S")
|
||||
shutil.move(os.path.join(cesta, row[6]),
|
||||
os.path.join(cestazpracovana, row[6][:-4] + " " + ts + ".pdf"))
|
||||
print(" Přesun OK!")
|
||||
break
|
||||
except Exception as e:
|
||||
print(f" Attempt {attempt + 1} failed: {e}")
|
||||
if attempt < 2:
|
||||
print(" Retrying in 5 seconds...")
|
||||
time.sleep(5)
|
||||
else:
|
||||
print(" Max retries reached. Command failed.")
|
||||
|
||||
filenameforbookmark = row[2].strftime('%Y-%m-%d') + ' ' + row[4] + ': ' + row[5]
|
||||
bookmark_list.append('"' + filenameforbookmark + '","Files:' + str(fileid) + '",' + str(cislo))
|
||||
filenameforbookmark_list.append(filenameforbookmark)
|
||||
cislo += 7
|
||||
|
||||
bookmarks_body += (r'\pard\s10{\*\bkmkstart ' + str(poradi) + r'}'
|
||||
r'\plain\cs32\f0\ul\fs20\cf1 ' + filenameforbookmark
|
||||
+ r'{\*\bkmkend ' + str(poradi) + r'}\par')
|
||||
poradi += 1
|
||||
|
||||
# ── Krok 2: sestavit tělo nové sekce příloh ───────────────────────────────
|
||||
new_body = (r'\uc1\pard\s10\plain\cs20\f0\i\fs20 Vlo\'9een\'e9 p\'f8\'edlohy:\par' + '\n'
|
||||
+ bookmarks_body + '\n'
|
||||
+ r'\pard\s10\plain\cs15\f0\fs20 \par')
|
||||
|
||||
# ── Krok 3: rozhodovací logika (3 případy) ────────────────────────────────
|
||||
cur = conn.cursor()
|
||||
|
||||
if existujici:
|
||||
dekurs_id, existing_rtf = existujici
|
||||
|
||||
if ma_sekci_prilohy(existing_rtf):
|
||||
# Případ 1: dnešní dekurz má sekci příloh → přidáme soubory dovnitř
|
||||
print(f"\n>>> Sekce 'Vložené přílohy' nalezena v DEKURS ID={dekurs_id}")
|
||||
print(">>> Přidávám soubory DO existující sekce...")
|
||||
merged_rtf = pridat_do_sekce_prilohy(existing_rtf, bookmark_list, filenameforbookmark_list)
|
||||
else:
|
||||
# Případ 2: dnešní dekurz existuje, ale sekci příloh nemá → prepend
|
||||
print(f"\n>>> DEKURS ID={dekurs_id} nemá sekci příloh → vkládám sekci na začátek...")
|
||||
merged_rtf = merge_rtf_prepend(existing_rtf, bookmark_list, new_body, len(skupiny[key]))
|
||||
|
||||
print("\n=== Výsledný RTF ===")
|
||||
print(merged_rtf)
|
||||
cur.execute("UPDATE DEKURS SET DEKURS = ? WHERE ID = ?", (merged_rtf, dekurs_id))
|
||||
conn.commit()
|
||||
print(f"\n>>> UPDATE DEKURS ID={dekurs_id} – hotovo!")
|
||||
|
||||
else:
|
||||
# Případ 3: žádný dnešní dekurz → vytvoříme nový
|
||||
print(f"\n>>> Žádný dekurs pro dnešek → vytvářím nový...")
|
||||
bookmark_str = ';'.join(bookmark_list)
|
||||
rtf = RTF_TEMPLATE.replace('BOOKMARKNAMES', bookmark_str)
|
||||
rtf = rtf.replace('BOOKMARKSTEXT', new_body)
|
||||
|
||||
print("\n=== Výsledný RTF ===")
|
||||
print(rtf)
|
||||
|
||||
dekursid = funkce.get_dekurs_id(conn)
|
||||
cur.execute(
|
||||
"INSERT INTO DEKURS (id, iduzi, idprac, idodd, idpac, datum, cas, dekurs)"
|
||||
" VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
(dekursid, 6, 2, 2, idpac, datumzapisu, caszapisu, rtf)
|
||||
)
|
||||
conn.commit()
|
||||
print(f"\n>>> Nový DEKURS ID={dekursid}")
|
||||
|
||||
print("\n=== HOTOVO ===")
|
||||
conn.close()
|
||||
@@ -0,0 +1,226 @@
|
||||
# s03soubory_01_FINAL.py – dokumentace
|
||||
|
||||
**Finální verze importního skriptu pro vkládání PDF dokumentů do dekurzů Medicusu.**
|
||||
|
||||
Datum finalizace: 2026-04-04
|
||||
Autor: Vladimír Buzalka + Claude (Anthropic)
|
||||
|
||||
---
|
||||
|
||||
## Co skript dělá
|
||||
|
||||
Zpracuje PDF soubory v určené složce (`cesta`) a pro každý soubor:
|
||||
1. Ověří správnost názvu souboru (formát, RC, datum)
|
||||
2. Zkontroluje, zda cílový dekurz není zamčený v Medicusu
|
||||
3. Zapíše soubor do externí databáze souborů (tabulka FILES)
|
||||
4. Přesune soubor do složky zpracovaných
|
||||
5. Vloží odkaz (bookmark) do dekurzu pacienta jako RTF záznam
|
||||
|
||||
---
|
||||
|
||||
## Adresáře
|
||||
|
||||
| Proměnná | Cesta (testovací) | Popis |
|
||||
|---|---|---|
|
||||
| `cesta` | `u:\testimport` | Vstupní složka – sem patří soubory ke zpracování |
|
||||
| `cestazpracovana` | `u:\testimportzpracovana` | Cílová složka – sem se přesouvají zpracované soubory |
|
||||
|
||||
> V produkci tyto cesty nahradit skutečnými složkami (Nextcloud/Dropbox).
|
||||
|
||||
---
|
||||
|
||||
## Formát názvu souboru
|
||||
|
||||
Každý PDF soubor musí mít název ve tvaru:
|
||||
|
||||
```
|
||||
RC YYYY-MM-DD Příjmení, Jméno [typ dokumentu] [poznámka].pdf
|
||||
```
|
||||
|
||||
**Příklad:**
|
||||
```
|
||||
7309208104 2020-10-16 Buzalka, Vladimír [LZ ortopedie] [VAS LS páteře, obstřik].pdf
|
||||
```
|
||||
|
||||
| Část | Popis |
|
||||
|---|---|
|
||||
| `RC` | Rodné číslo pacienta (9 nebo 10 číslic) – musí existovat v tabulce KAR |
|
||||
| `YYYY-MM-DD` | Datum dokumentu |
|
||||
| `Příjmení, Jméno` | Jméno pacienta (jen pro čitelnost, nepoužívá se k vyhledání) |
|
||||
| `[typ dokumentu]` | První závorka – druh nálezu (LZ ortopedie, EKG, Lab. nález…) |
|
||||
| `[poznámka]` | Druhá závorka – krátký popis obsahu (může být prázdná `[]`) |
|
||||
|
||||
**Chybný soubor** (špatný název, RC nenalezeno v DB) je přejmenován přidáním prefixu `♥`:
|
||||
```
|
||||
♥chybny soubor.pdf
|
||||
```
|
||||
Skript ho přeskočí a nechá v složce pro ruční opravu.
|
||||
|
||||
---
|
||||
|
||||
## Databázové tabulky
|
||||
|
||||
| Tabulka | DB | Popis |
|
||||
|---|---|---|
|
||||
| `KAR` | Medicus (`medicus.fdb`) | Kartotéka pacientů – lookup RC → IDPAC |
|
||||
| `DEKURS` | Medicus (`medicus.fdb`) | Dekurzy – čtení a zápis RTF záznamu |
|
||||
| `FILES` | Externí DB (`MEDICUS_FILES_YYYYMM.fdb`) | Binární uložení PDF souborů |
|
||||
|
||||
---
|
||||
|
||||
## Klíčová novinka oproti s03soubory.py – ochrana před zamčeným dekurzem
|
||||
|
||||
### Problém
|
||||
Medicus drží **exkluzivní zámek** (Firebird row lock) na záznamu tabulky DEKURS po celou dobu, kdy má lékařka pacienta otevřeného. Kdyby skript provedl `UPDATE DEKURS` do zamčeného záznamu, přepsal by lékařčiny neuložené změny.
|
||||
|
||||
### Řešení – detekce zámku pomocí vlákna s timeoutem
|
||||
|
||||
Firebird neumí NOWAIT nastavit per-statement v SQL (syntaxe `FOR UPDATE WITH LOCK NOWAIT` není platná). Nastavení NOWAIT je vlastnost transakce, nikoliv dotazu. Knihovna `fdb` navíc toto nastavení spolehlivě nepodporuje.
|
||||
|
||||
**Zvolené řešení:** spuštění pokusu o zámek ve vedlejším vlákně s timeoutem 2 sekundy.
|
||||
|
||||
```
|
||||
hlavní vlákno vedlejší vlákno (_pokus_o_zamek)
|
||||
───────────────── ─────────────────────────────────
|
||||
t.start() ──────► fdb.connect() [nové spojení]
|
||||
t.join(timeout=2s) SELECT ... FOR UPDATE WITH LOCK
|
||||
├── záznam volný → fetchone() → rollback() → konec
|
||||
└── záznam zamčený → čeká (blokuje)...
|
||||
─────────────────
|
||||
po 2 sekundách:
|
||||
t.is_alive()?
|
||||
ANO → záznam zamčený → RuntimeError → přeskoč skupinu
|
||||
NE → záznam volný → pokračuj se zápisem
|
||||
```
|
||||
|
||||
### Důležitý detail – pořadí operací
|
||||
|
||||
**Kontrola zámku probíhá PŘED zápisem do FILES a PŘED přesunem souboru.**
|
||||
Kdyby se pořadí obrátilo, mohlo by dojít k situaci:
|
||||
- soubor zapsán do FILES ✓
|
||||
- soubor přesunut do zpracovaných ✓
|
||||
- dekurz zamčen → UPDATE selže
|
||||
- soubor je pryč ze vstupní složky, ale odkaz v dekurzu chybí
|
||||
|
||||
Správné pořadí:
|
||||
```
|
||||
1. Zkontroluj zámek dekurzu (NOWAIT)
|
||||
└── zamčeno → přeskoč (soubory zůstanou v cesta)
|
||||
2. Zapiš soubory do ext. DB (FILES)
|
||||
3. Přesuň soubory do zpracovaných
|
||||
4. Sestav RTF
|
||||
5. UPDATE nebo INSERT do DEKURS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Logika vkládání do dekurzu – 3 případy
|
||||
|
||||
Po úspěšné kontrole zámku skript rozhodne, co s dekurzem udělat:
|
||||
|
||||
```
|
||||
Existuje dnešní dekurz pro pacienta?
|
||||
│
|
||||
├── ANO → obsahuje sekci "Vložené přílohy"?
|
||||
│ ├── ANO → Případ 1: přidá nové soubory DOVNITŘ sekce
|
||||
│ └── NE → Případ 2: vloží celou sekci na ZAČÁTEK dekurzu (prepend)
|
||||
│
|
||||
└── NE → Případ 3: vytvoří NOVÝ dekurz ze šablony RTF_TEMPLATE
|
||||
```
|
||||
|
||||
### Případ 1 – `pridat_do_sekce_prilohy()`
|
||||
|
||||
Dnešní dekurz **existuje a má** sekci „Vložené přílohy".
|
||||
|
||||
- Spočítá kolik odkazů (Files:) už sekce obsahuje → nové indexy bookmarků navazují
|
||||
- Nové `\pard` řádky vloží **před** uzavírací prázdný řádek sekce (`PRILOHY_CLOSING`)
|
||||
- Nové bookmarky přidá na **konec** `{\info{\bookmarks ...}}`
|
||||
- Provede `UPDATE DEKURS SET DEKURS = ? WHERE ID = ?`
|
||||
|
||||
### Případ 2 – `merge_rtf_prepend()`
|
||||
|
||||
Dnešní dekurz **existuje, ale nemá** sekci příloh (lékařka do něj napsala text).
|
||||
|
||||
- Přečísluje existující bookmarky (posunutí o počet nových souborů)
|
||||
- Novou sekci „Vložené přílohy" vloží **na začátek** těla RTF (před `\uc1\pard`)
|
||||
- Nové bookmarky předřadí před existující v `{\info{\bookmarks ...}}`
|
||||
- Lékařčin text zůstane zachován, jen se posune níž
|
||||
- Provede `UPDATE DEKURS SET DEKURS = ? WHERE ID = ?`
|
||||
|
||||
### Případ 3 – nový INSERT
|
||||
|
||||
Pro dnešní datum **neexistuje žádný dekurz**.
|
||||
|
||||
- Vyplní `RTF_TEMPLATE` (bookmarky + tělo sekce příloh)
|
||||
- Provede `INSERT INTO DEKURS (id, iduzi, idprac, idodd, idpac, datum, cas, dekurs)`
|
||||
- `iduzi=6` (Vladimír Buzalka), `idprac=2`, `idodd=2`
|
||||
|
||||
---
|
||||
|
||||
## RTF formát dekurzu
|
||||
|
||||
### Struktura bookmarku
|
||||
Každý přiložený soubor je reprezentován jako:
|
||||
1. **Bookmark entry** v `{\info{\bookmarks ...}}`:
|
||||
```
|
||||
"2020-10-16 LZ ortopedie: VAS LS páteře, obstřik","Files:21923",9
|
||||
```
|
||||
- `"popis"` – zobrazený text odkazu
|
||||
- `"Files:ID"` – odkaz na záznam v tabulce FILES (slouží Medicusu k načtení souboru)
|
||||
- `9` – číslo fontu/stylu (od 9, každý další +7)
|
||||
|
||||
2. **Vizuální řádek** v těle RTF:
|
||||
```rtf
|
||||
\pard\s10{\*\bkmkstart 0}\plain\cs32\f0\ul\fs20\cf1 2020-10-16 LZ ortopedie: VAS LS páteře, obstřik{\*\bkmkend 0}\par
|
||||
```
|
||||
- `\bkmkstart N` / `\bkmkend N` – index bookmarku (0, 1, 2…)
|
||||
- `\cs32\ul\cf1` – styl „Odkaz" (modrý podtržený text)
|
||||
|
||||
### Konstanty pro detekci sekce příloh (win1250 RTF escape)
|
||||
```python
|
||||
PRILOHY_HEADER = r"Vlo\'9een\'e9 p\'f8\'edlohy:" # "Vložené přílohy:"
|
||||
PRILOHY_CLOSING = r'\pard\s10\plain\cs15\f0\fs20 \par' # uzavírací prázdný řádek
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Funkce – přehled
|
||||
|
||||
| Funkce | Popis |
|
||||
|---|---|
|
||||
| `restore_files_for_import(retezec)` | Debug utilita – vrátí soubory z Nextcloudu zpět do Dropboxu. Nepoužívá se v produkci. |
|
||||
| `kontrola_rc(rc, connection)` | Ověří zda RC existuje v KAR, vrátí IDPAC nebo False. |
|
||||
| `kontrola_struktury(souborname, connection)` | Ověří formát názvu souboru a existenci RC v DB. |
|
||||
| `vrat_info_o_souboru(souborname, connection)` | Parsuje název souboru, dohledá IDPAC, vrátí tuple s metadaty. |
|
||||
| `prejmenuj_chybny_soubor(souborname, cesta)` | Přidá prefix `♥` k chybnému souboru. |
|
||||
| `_pokus_o_zamek(dekurs_id, vysledek)` | Interní – běží ve vlákně, zkouší zamknout dekurz. |
|
||||
| `zkus_zamknout_dnesni_dekurs(conn, idpac, datum_vlozeni, timeout_sec=2)` | Zjistí zda dnešní dekurz existuje a není zamčený. Vyhodí RuntimeError pokud je zamčený. |
|
||||
| `ma_sekci_prilohy(rtf)` | Vrátí True pokud RTF obsahuje sekci „Vložené přílohy". |
|
||||
| `pridat_do_sekce_prilohy(rtf, bookmark_list, filenameforbookmark_list)` | Případ 1 – přidá soubory do existující sekce příloh. |
|
||||
| `merge_rtf_prepend(existing_rtf, new_bkm_list, new_body_pards, n_new)` | Případ 2 – vloží sekci příloh na začátek existujícího dekurzu. |
|
||||
|
||||
---
|
||||
|
||||
## Ošetření chyb
|
||||
|
||||
| Situace | Chování |
|
||||
|---|---|
|
||||
| Soubor má chybný název | Přejmenován na `♥soubor.pdf`, přeskočen |
|
||||
| RC nenalezeno v KAR | Přejmenován na `♥soubor.pdf`, přeskočen |
|
||||
| Dekurz zamčený (timeout vlákna) | Skupina přeskočena, soubory zůstanou v `cesta` |
|
||||
| DB konflikt při zamykání (-913 deadlock) | Skupina přeskočena, soubory zůstanou v `cesta` |
|
||||
| Přesun souboru selže | 3 pokusy s 5s pauzou, poté varování |
|
||||
| Jiná DB chyba | Výjimka se propaguje, skript havaruje |
|
||||
|
||||
---
|
||||
|
||||
## Vývoj a testování
|
||||
|
||||
| Verze | Soubor | Co přibilo |
|
||||
|---|---|---|
|
||||
| Prototyp | `test_import_FINAL.py` | Ruční zadání IDPAC a DATUM, ověření RTF logiky |
|
||||
| v1 | `s03soubory.py` | Automatický parsing RC z názvu, dávkování po skupinách |
|
||||
| **v1 FINAL** | `s03soubory_01_FINAL.py` | Ochrana před zamčeným dekurzem (threading + timeout) |
|
||||
|
||||
### Jak byl objeven problém se zámky
|
||||
Experimentem bylo ověřeno, že Medicus drží Firebird row lock na záznamu DEKURS po celou dobu, kdy má lékařka pacienta otevřeného (`SELECT FIRST 1 ... FOR UPDATE WITH LOCK` z Pythonu čekalo dokud lékařka neuložila). NOWAIT nelze nastavit přes SQL syntaxi ani spolehlivě přes fdb TPB bajty, proto bylo zvoleno řešení přes vlákno s timeoutem.
|
||||
@@ -0,0 +1,108 @@
|
||||
"""s04_presun_externi_db.py – Přesun externích DB souborů z u:\ do u:\externi\
|
||||
|
||||
Spustit na Windows s ZAVŘENÝM Medicusem!
|
||||
|
||||
Co dělá:
|
||||
1. Připojí se k hlavní medicus.fdb
|
||||
2. Načte všechny záznamy z EXTERNI_DB
|
||||
3. Pro každý záznam zkopíruje FDB soubor z původní lokace do u:\externi\
|
||||
4. Aktualizuje EXTERNI_DB.PATH na novou lokaci
|
||||
5. Vytiskne přehled výsledků
|
||||
|
||||
Po spuštění zkus v Medicusu otevřít přílohu pacienta – mělo by to fungovat.
|
||||
Pokud ne, zálohuj si databázi a spusť znovu (skript je idempotentní).
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import fdb
|
||||
|
||||
# ── Konfigurace ──────────────────────────────────────────────────────────────
|
||||
MAIN_DB = r'c:\medicus 3\data\medicus.fdb'
|
||||
CÍL_SLOŽKA = r'u:\externi'
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def main():
|
||||
os.makedirs(CÍL_SLOŽKA, exist_ok=True)
|
||||
print(f"Cílová složka: {CÍL_SLOŽKA}")
|
||||
print()
|
||||
|
||||
print("Připojuji se k hlavní DB...")
|
||||
conn = fdb.connect(
|
||||
dsn=f'localhost:{MAIN_DB}',
|
||||
user='SYSDBA',
|
||||
password='masterkey',
|
||||
charset='WIN1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
# Načti všechny záznamy z EXTERNI_DB
|
||||
cur.execute("SELECT DBNAME, SERVER, PATH, HESLO FROM EXTERNI_DB ORDER BY DBNAME")
|
||||
zaznamy = cur.fetchall()
|
||||
print(f"Nalezeno {len(zaznamy)} záznamů v EXTERNI_DB:")
|
||||
print()
|
||||
|
||||
ok = 0
|
||||
preskoceno = 0
|
||||
chyba = 0
|
||||
|
||||
for dbname, server, path_puvodni, heslo in zaznamy:
|
||||
print(f" [{dbname}] {path_puvodni}")
|
||||
|
||||
# Název souboru (jen basename, bez cesty)
|
||||
basename = os.path.basename(path_puvodni)
|
||||
# Normalize: Medicus někdy ukládá s malým .fdb, někdy velkým .FDB
|
||||
path_cil = os.path.join(CÍL_SLOŽKA, basename)
|
||||
|
||||
# Zkontroluj, jestli zdrojový soubor existuje
|
||||
# (zkus obě varianty přípony)
|
||||
path_src = None
|
||||
for kandidat in [path_puvodni,
|
||||
path_puvodni.replace('.fdb', '.FDB'),
|
||||
path_puvodni.replace('.FDB', '.fdb')]:
|
||||
if os.path.isfile(kandidat):
|
||||
path_src = kandidat
|
||||
break
|
||||
|
||||
if path_src is None:
|
||||
print(f" ⚠ ZDROJOVÝ SOUBOR NENALEZEN: {path_puvodni} – přeskakuji")
|
||||
chyba += 1
|
||||
continue
|
||||
|
||||
# Pokud je soubor už v cíli, přeskoč kopírování
|
||||
if os.path.isfile(path_cil):
|
||||
velikost = os.path.getsize(path_cil)
|
||||
print(f" → Soubor již existuje v cíli ({velikost:,} B), přeskakuji kopírování")
|
||||
else:
|
||||
velikost = os.path.getsize(path_src)
|
||||
print(f" → Kopíruji ({velikost:,} B)...", end=' ', flush=True)
|
||||
shutil.copy2(path_src, path_cil)
|
||||
print("OK")
|
||||
|
||||
# Aktualizuj PATH v EXTERNI_DB pokud se liší
|
||||
if path_puvodni != path_cil:
|
||||
cur.execute(
|
||||
"UPDATE EXTERNI_DB SET PATH = ? WHERE DBNAME = ?",
|
||||
(path_cil, dbname)
|
||||
)
|
||||
print(f" → EXTERNI_DB.PATH aktualizováno: {path_cil}")
|
||||
else:
|
||||
print(f" → PATH již ukazuje na cíl, není třeba měnit")
|
||||
preskoceno += 1
|
||||
|
||||
ok += 1
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
print(f"Hotovo! Zpracováno: {ok}, přeskočeno: {preskoceno}, chyb: {chyba}")
|
||||
print()
|
||||
if chyba > 0:
|
||||
print("⚠ Některé soubory nebyly nalezeny – viz výpis výše.")
|
||||
print(" Pokud jsou to staré DB které již neexistují, nevadí to.")
|
||||
print("Nyní spusť Medicus a zkus otevřít přílohu pacienta.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,37 @@
|
||||
import fdb
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
print("=== NEJSTARŠÍ záznamy ===")
|
||||
cur.execute("""
|
||||
SELECT FIRST 5 ID, DATUM, DEKURS
|
||||
FROM DEKURS
|
||||
WHERE DEKURS IS NOT NULL
|
||||
ORDER BY DATUM ASC
|
||||
""")
|
||||
for row in cur.fetchall():
|
||||
text = row[2].read() if hasattr(row[2], 'read') else row[2]
|
||||
if isinstance(text, bytes):
|
||||
text = text.decode('windows-1250', errors='replace')
|
||||
print(f"\n--- ID={row[0]} DATUM={row[1]} ---")
|
||||
print(repr(text[:300]))
|
||||
|
||||
print("\n\n=== NEJNOVĚJŠÍ záznamy ===")
|
||||
cur.execute("""
|
||||
SELECT FIRST 5 ID, DATUM, DEKURS
|
||||
FROM DEKURS
|
||||
WHERE DEKURS IS NOT NULL
|
||||
ORDER BY DATUM DESC
|
||||
""")
|
||||
for row in cur.fetchall():
|
||||
text = row[2].read() if hasattr(row[2], 'read') else row[2]
|
||||
if isinstance(text, bytes):
|
||||
text = text.decode('windows-1250', errors='replace')
|
||||
print(f"\n--- ID={row[0]} DATUM={row[1]} ---")
|
||||
print(repr(text[:300]))
|
||||
|
||||
conn.close()
|
||||
@@ -0,0 +1,25 @@
|
||||
import fdb
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute("""
|
||||
SELECT FIRST 1 ID, DATUM, DEKURS
|
||||
FROM DEKURS
|
||||
WHERE DEKURS IS NOT NULL
|
||||
ORDER BY DATUM DESC
|
||||
""")
|
||||
row = cur.fetchone()
|
||||
text = row[2].read() if hasattr(row[2], 'read') else row[2]
|
||||
if isinstance(text, bytes):
|
||||
text = text.decode('windows-1250', errors='replace')
|
||||
|
||||
print(f"ID={row[0]} DATUM={row[1]}")
|
||||
print(f"Délka: {len(text)} znaků")
|
||||
print("\n--- CELÝ TEXT ---")
|
||||
print(text)
|
||||
|
||||
conn.close()
|
||||
@@ -0,0 +1,35 @@
|
||||
import fdb
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
# Načti všechny záznamy pacienta a zjisti délku v Pythonu
|
||||
cur.execute("""
|
||||
SELECT ID, DATUM, DEKURS
|
||||
FROM DEKURS
|
||||
WHERE IDPAC = 9742 AND DEKURS IS NOT NULL
|
||||
ORDER BY DATUM DESC
|
||||
""")
|
||||
rows = cur.fetchall()
|
||||
|
||||
def read_blob(val):
|
||||
text = val.read() if hasattr(val, 'read') else val
|
||||
if isinstance(text, bytes):
|
||||
text = text.decode('windows-1250', errors='replace')
|
||||
return text
|
||||
|
||||
# Seřaď podle délky
|
||||
data = [(r[0], r[1], read_blob(r[2])) for r in rows]
|
||||
data.sort(key=lambda x: len(x[2]), reverse=True)
|
||||
|
||||
print("Top 10 nejdelších záznamů:")
|
||||
for d in data[:10]:
|
||||
print(f" ID={d[0]} datum={d[1]} délka={len(d[2])}")
|
||||
|
||||
print("\n--- Nejdelší záznam (celý RTF) ---")
|
||||
print(data[0][2])
|
||||
|
||||
conn.close()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
"""Test připojení k externí DB a prozkoumání její struktury - spusť na Windows"""
|
||||
import fdb, os, glob
|
||||
|
||||
# Najde první existující externí FDB soubor
|
||||
soubory = glob.glob(r'u:\MEDICUS_FILES_*.fdb')
|
||||
if not soubory:
|
||||
soubory = glob.glob(r'u:\externi\MEDICUS_FILES_*.fdb')
|
||||
|
||||
if not soubory:
|
||||
print("Žádné externí FDB soubory nenalezeny!")
|
||||
exit()
|
||||
|
||||
soubor = sorted(soubory)[-1] # vezme nejnovější
|
||||
print(f"Zkouším: {soubor}")
|
||||
|
||||
# Test 1: masterkey
|
||||
try:
|
||||
conn = fdb.connect(dsn=f'localhost:{soubor}', user='SYSDBA', password='masterkey', charset='win1250')
|
||||
print("✓ Připojení s masterkey FUNGUJE!")
|
||||
|
||||
cur = conn.cursor()
|
||||
|
||||
# Jaké tabulky jsou v externí DB?
|
||||
cur.execute("SELECT RDB$RELATION_NAME FROM RDB$RELATIONS WHERE RDB$SYSTEM_FLAG=0 AND RDB$VIEW_BLR IS NULL")
|
||||
tabulky = [r[0].strip() for r in cur.fetchall()]
|
||||
print(f"Tabulky v externí DB: {tabulky}")
|
||||
|
||||
# Kolik záznamů v FILES?
|
||||
if 'FILES' in tabulky:
|
||||
cur.execute("SELECT COUNT(*), MIN(ID), MAX(ID) FROM FILES")
|
||||
row = cur.fetchone()
|
||||
print(f"FILES: {row[0]} záznamů, ID od {row[1]} do {row[2]}")
|
||||
|
||||
cur.execute("SELECT ID, IDPAC, FILENAME, DATUM, OCTET_LENGTH(BODY) FROM FILES ORDER BY ID ROWS 3")
|
||||
for r in cur.fetchall():
|
||||
print(f" ID={r[0]} IDPAC={r[1]} FILE={r[2]} DATUM={r[3]} BODY={r[4]}B")
|
||||
|
||||
# Zjistit generátor
|
||||
cur.execute("SELECT RDB$GENERATOR_NAME, RDB$GENERATOR_ID FROM RDB$GENERATORS WHERE RDB$SYSTEM_FLAG=0")
|
||||
for r in cur.fetchall():
|
||||
print(f"Generátor: {r[0].strip()} = {r[1]}")
|
||||
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f"✗ masterkey NEFUNGUJE: {e}")
|
||||
|
||||
# Test s jiným heslem - zkus prázdné
|
||||
try:
|
||||
conn = fdb.connect(dsn=f'localhost:{soubor}', user='SYSDBA', password='', charset='win1250')
|
||||
print("✓ Připojení s prázdným heslem FUNGUJE!")
|
||||
conn.close()
|
||||
except Exception as e2:
|
||||
print(f"✗ Prázdné heslo také nefunguje: {e2}")
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Prozkoumá strukturu tabulky DATA v externí DB - spusť na Windows"""
|
||||
import fdb, glob
|
||||
|
||||
soubory = glob.glob(r'u:\MEDICUS_FILES_*.fdb') + glob.glob(r'u:\MEDICUS_FILES_*.FDB')
|
||||
if not soubory:
|
||||
soubory = glob.glob(r'u:\externi\MEDICUS_FILES_*.fdb') + glob.glob(r'u:\externi\MEDICUS_FILES_*.FDB')
|
||||
|
||||
soubor = sorted(soubory)[-1]
|
||||
print(f"Soubor: {soubor}\n")
|
||||
|
||||
conn = fdb.connect(dsn=f'localhost:{soubor}', user='SYSDBA', password='masterkey', charset='win1250')
|
||||
cur = conn.cursor()
|
||||
|
||||
# Sloupce tabulky DATA
|
||||
print("=== Sloupce tabulky DATA ===")
|
||||
cur.execute("""
|
||||
SELECT r.RDB$FIELD_NAME, f.RDB$FIELD_TYPE, f.RDB$FIELD_LENGTH, f.RDB$SEGMENT_LENGTH
|
||||
FROM RDB$RELATION_FIELDS r
|
||||
JOIN RDB$FIELDS f ON f.RDB$FIELD_NAME = r.RDB$FIELD_SOURCE
|
||||
WHERE r.RDB$RELATION_NAME = 'DATA'
|
||||
ORDER BY r.RDB$FIELD_POSITION
|
||||
""")
|
||||
for row in cur.fetchall():
|
||||
typ = {7:'SMALLINT',8:'INTEGER',12:'DATE',13:'TIME',14:'CHAR',16:'INT64',35:'TIMESTAMP',37:'VARCHAR',261:'BLOB'}.get(row[1], str(row[1]))
|
||||
print(f" {row[0].strip():30} {typ} len={row[2]}")
|
||||
|
||||
# Generátory
|
||||
print("\n=== Generátory ===")
|
||||
cur.execute("SELECT RDB$GENERATOR_NAME, RDB$GENERATOR_ID FROM RDB$GENERATORS WHERE RDB$SYSTEM_FLAG=0")
|
||||
for row in cur.fetchall():
|
||||
print(f" {row[0].strip()} = {row[1]}")
|
||||
|
||||
# Počet záznamů a ukázka
|
||||
print("\n=== Počet záznamů ===")
|
||||
cur.execute("SELECT COUNT(*) FROM DATA")
|
||||
print(f" {cur.fetchone()[0]} záznamů")
|
||||
|
||||
print("\n=== Ukázka 3 záznamů (bez BLOB) ===")
|
||||
cur.execute("""
|
||||
SELECT ID, OCTET_LENGTH(BODY) as VELIKOST
|
||||
FROM DATA ORDER BY ID ROWS 3
|
||||
""")
|
||||
for row in cur.fetchall():
|
||||
print(f" ID={row[0]} BODY={row[1]}B")
|
||||
|
||||
conn.close()
|
||||
@@ -0,0 +1,47 @@
|
||||
"""Zjistí propojení mezi hlavní DB a externí DB - spusť na Windows"""
|
||||
import fdb, glob
|
||||
|
||||
# Hlavní DB
|
||||
conn_main = fdb.connect(dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250')
|
||||
cur = conn_main.cursor()
|
||||
|
||||
# Najít exportované záznamy v FILES (BODY není velký BLOB)
|
||||
print("=== FILES záznamy po exportu (malý BODY = reference na externí DB) ===")
|
||||
cur.execute("""
|
||||
SELECT ID, IDPAC, FILENAME, DATUM, EXT_ID, DOCID,
|
||||
OCTET_LENGTH(BODY) as BODY_LEN,
|
||||
CAST(BODY AS VARCHAR(100)) as BODY_TEXT
|
||||
FROM FILES
|
||||
WHERE OCTET_LENGTH(BODY) < 100
|
||||
ORDER BY ID DESC ROWS 5
|
||||
""")
|
||||
for r in cur.fetchall():
|
||||
print(f" ID={r[0]} IDPAC={r[1]} FILE={r[2]}")
|
||||
print(f" DATUM={r[3]} EXT_ID={r[4]} DOCID={r[5]}")
|
||||
print(f" BODY_LEN={r[6]} BODY_TEXT='{r[7]}'")
|
||||
|
||||
# Ukázka záznamu s plným BODY (ještě ne exportovaný)
|
||||
print("\n=== FILES záznamy PŘED exportem (velký BODY = binary data) ===")
|
||||
cur.execute("""
|
||||
SELECT ID, FILENAME, DATUM, EXT_ID, OCTET_LENGTH(BODY) as BODY_LEN
|
||||
FROM FILES
|
||||
WHERE OCTET_LENGTH(BODY) > 1000
|
||||
ORDER BY ID DESC ROWS 3
|
||||
""")
|
||||
for r in cur.fetchall():
|
||||
print(f" ID={r[0]} FILE={r[1]} DATUM={r[2]} EXT_ID={r[3]} BODY={r[4]}B")
|
||||
|
||||
conn_main.close()
|
||||
|
||||
# Porovnat s externí DB
|
||||
print("\n=== DATA záznamy v externí DB ===")
|
||||
soubory = glob.glob(r'u:\MEDICUS_FILES_*.fdb') + glob.glob(r'u:\MEDICUS_FILES_*.FDB')
|
||||
soubor = sorted(soubory)[0] # vezmeme nejstarší
|
||||
print(f"Soubor: {soubor}")
|
||||
conn_ext = fdb.connect(dsn=f'localhost:{soubor}', user='SYSDBA', password='masterkey', charset='win1250')
|
||||
cur_ext = conn_ext.cursor()
|
||||
cur_ext.execute("SELECT UID, DATASIZE, CHUNK FROM DATA ORDER BY CHUNK ROWS 5")
|
||||
for r in cur_ext.fetchall():
|
||||
print(f" UID='{r[0]}' DATASIZE={r[1]} CHUNK={r[2]}")
|
||||
conn_ext.close()
|
||||
@@ -0,0 +1,20 @@
|
||||
"""Čte přesné bajty BODY z exportovaného záznamu FILES - spusť na Windows"""
|
||||
import fdb
|
||||
|
||||
conn = fdb.connect(dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250')
|
||||
cur = conn.cursor()
|
||||
|
||||
# Vezmi jeden exportovaný záznam
|
||||
cur.execute("SELECT ID, BODY FROM FILES WHERE ID = 10487")
|
||||
row = cur.fetchone()
|
||||
fileid = row[0]
|
||||
body_bytes = row[1] # fdb vrací BLOB jako bytes přímo
|
||||
|
||||
print(f"FILE ID: {fileid}")
|
||||
print(f"BODY délka: {len(body_bytes)} bajtů")
|
||||
print(f"BODY hex: {body_bytes.hex()}")
|
||||
print(f"BODY repr: {repr(body_bytes)}")
|
||||
print(f"BODY jako string (latin1): {body_bytes.decode('latin1')}")
|
||||
|
||||
conn.close()
|
||||
@@ -0,0 +1,108 @@
|
||||
"""test_import_3files.py – jednorázový test: 3 soubory jednoho pacienta → 1 dekurs s klikacími odkazy
|
||||
Testuje opravenou RTF logiku (cs32, Přílohy: inline, poradi++)
|
||||
Spustit na Windows.
|
||||
"""
|
||||
import datetime
|
||||
import os
|
||||
import fdb
|
||||
import funkce
|
||||
import funkce_ext
|
||||
|
||||
CESTA = r'u:\\'
|
||||
IDPAC = 9742 # Buzalka Vladimír, RC 7309208104
|
||||
|
||||
SOUBORY = [
|
||||
{
|
||||
'souborname': '7309208104 2026-03-18 Buzalka, Vladimír [vyšetření] [ahoj Claude copy1].pdf',
|
||||
'prvnizavorka': 'vyšetření',
|
||||
'druhazavorka': 'ahoj Claude copy1',
|
||||
'datum': datetime.date(2026, 3, 18),
|
||||
},
|
||||
{
|
||||
'souborname': '7309208104 2026-03-18 Buzalka, Vladimír [vyšetření] [ahoj Claude copy2].pdf',
|
||||
'prvnizavorka': 'vyšetření',
|
||||
'druhazavorka': 'ahoj Claude copy2',
|
||||
'datum': datetime.date(2026, 3, 18),
|
||||
},
|
||||
{
|
||||
'souborname': '7309208104 2026-03-18 Buzalka, Vladimír [vyšetření] [ahoj Claude].pdf',
|
||||
'prvnizavorka': 'vyšetření',
|
||||
'druhazavorka': 'ahoj Claude',
|
||||
'datum': datetime.date(2026, 3, 18),
|
||||
},
|
||||
]
|
||||
|
||||
# ── Připojení ─────────────────────────────────────────────────────────────────
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='WIN1250'
|
||||
)
|
||||
|
||||
# ── Krok 1: vložit každý soubor do ext DB ─────────────────────────────────────
|
||||
bookmark = ''
|
||||
bookmarks = ''
|
||||
cislo = 9
|
||||
poradi = 0
|
||||
|
||||
for s in SOUBORY:
|
||||
cesta_souboru = os.path.join(CESTA, s['souborname'])
|
||||
datumsouboru = datetime.datetime.fromtimestamp(os.path.getmtime(cesta_souboru))
|
||||
|
||||
print(f"\n>>> Zpracovávám: {s['souborname']}")
|
||||
fileid = funkce_ext.zapis_file_ext(
|
||||
vstupconnection = conn,
|
||||
idpac = IDPAC,
|
||||
cesta = CESTA,
|
||||
souborname = s['souborname'],
|
||||
prvnizavorka = s['prvnizavorka'],
|
||||
soubordate = s['datum'],
|
||||
souborfiledate = datumsouboru,
|
||||
poznamka = s['druhazavorka'],
|
||||
)
|
||||
print(f" → FILES.ID = {fileid}")
|
||||
|
||||
# {\info{\bookmarks ...}} sekce
|
||||
filenameforbookmark = s['datum'].strftime('%Y-%m-%d') + ' ' + s['prvnizavorka'] + ': ' + s['druhazavorka']
|
||||
bookmark += '"' + filenameforbookmark + '","Files:' + str(fileid) + '",' + str(cislo) + ';'
|
||||
cislo += 7
|
||||
|
||||
# tělo RTF – klikatelné záložky (cs32, všechny stejný formát)
|
||||
bookmarks += (r'\pard\s10{\*\bkmkstart ' + str(poradi) + r'}'
|
||||
r'\plain\cs32\f0\ul\fs20\cf1 ' + filenameforbookmark +
|
||||
r'{\*\bkmkend ' + str(poradi) + r'}\par')
|
||||
poradi += 1
|
||||
|
||||
bookmark = bookmark[:-1] # odstranit poslední ;
|
||||
|
||||
# ── Krok 2: sestavit RTF ──────────────────────────────────────────────────────
|
||||
rtf = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks BOOKMARKNAMES}}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
|
||||
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
{\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs32\f0\ul\fs20\cf1 Odkaz;}}
|
||||
\uc1\pard\s10\plain\cs20\f0\i\fs20 Vlo\'9een\'e9 p\'f8\'edlohy:\par
|
||||
BOOKMARKSTEXT
|
||||
\pard\s10\plain\cs15\f0\fs20 \par
|
||||
}"""
|
||||
|
||||
rtf = rtf.replace('BOOKMARKNAMES', bookmark)
|
||||
rtf = rtf.replace('BOOKMARKSTEXT', bookmarks)
|
||||
|
||||
print('\n=== Výsledný RTF ===')
|
||||
print(rtf)
|
||||
|
||||
# ── Krok 3: zapsat dekurs ─────────────────────────────────────────────────────
|
||||
dekursid = funkce.get_dekurs_id(conn)
|
||||
datumzapisu = datetime.datetime.now().date()
|
||||
caszapisu = datetime.datetime.now().time()
|
||||
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
"INSERT INTO DEKURS (id, iduzi, idprac, idodd, idpac, datum, cas, dekurs)"
|
||||
" VALUES (?,?,?,?,?,?,?,?)",
|
||||
(dekursid, 6, 2, 2, IDPAC, datumzapisu, caszapisu, rtf)
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
print(f'\n=== HOTOVO ===')
|
||||
print(f'DEKURS.ID = {dekursid}')
|
||||
print('Otevři Medicus → karta Buzalka Vladimír → najdi dnešní záznam → klikej na odkazy!')
|
||||
@@ -0,0 +1,238 @@
|
||||
"""test_import_single.py – vloží 1 soubor do dekurzu s rozšířenou logikou:
|
||||
|
||||
1. Poslední dekurs pacienta je z dnešního dne A má sekci 'Vložené přílohy'
|
||||
→ soubor se přidá DO té sekce (ne nová sekce)
|
||||
2. Poslední dekurs je z dnešního dne, ale sekci 'Vložené přílohy' nemá
|
||||
→ prepend nové sekce na začátek
|
||||
3. Poslední dekurs je z jiného dne / neexistuje
|
||||
→ nový dekurs pro dnešek
|
||||
|
||||
Spustit na Windows.
|
||||
"""
|
||||
import datetime, os, re, fdb
|
||||
import funkce, funkce_ext
|
||||
|
||||
CESTA = r'u:\\'
|
||||
IDPAC = 9742
|
||||
DATUM = datetime.date(2026, 3, 18)
|
||||
|
||||
SOUBORY = [
|
||||
{'souborname': '7309208104 2026-03-18 Buzalka, Vladimír [vyšetření] [ahoj Claude - zařazení].pdf',
|
||||
'prvnizavorka': 'vyšetření',
|
||||
'druhazavorka': 'ahoj Claude - zařazení',
|
||||
'datum': DATUM},
|
||||
]
|
||||
|
||||
# Vzor pro detekci sekce Vložené přílohy (RTF kódování win1250)
|
||||
PRILOHY_HEADER = r"Vlo\'9een\'e9 p\'f8\'edlohy:"
|
||||
PRILOHY_CLOSING = r'\pard\s10\plain\cs15\f0\fs20 \par'
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def najdi_posledni_dekurs_dnes(conn, idpac, datum_vlozeni):
|
||||
"""Vrátí (id, rtf) posledního dekurzu pacienta pokud je z dnešního dne."""
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
SELECT FIRST 1 ID, DATUM, DEKURS FROM DEKURS
|
||||
WHERE IDPAC = ?
|
||||
ORDER BY ID DESC
|
||||
""", (idpac,))
|
||||
row = cur.fetchone()
|
||||
if row is None:
|
||||
return None
|
||||
dekurs_id, dekurs_datum, dekurs_rtf = row
|
||||
print(f" Poslední dekurs: ID={dekurs_id}, datum={dekurs_datum}")
|
||||
if dekurs_datum == datum_vlozeni:
|
||||
print(f" → dnešní den ({datum_vlozeni}) ✓")
|
||||
return (dekurs_id, dekurs_rtf)
|
||||
else:
|
||||
print(f" → jiný den ({dekurs_datum} ≠ {datum_vlozeni}), vytvoříme nový")
|
||||
return None
|
||||
|
||||
|
||||
def ma_sekci_prilohy(rtf):
|
||||
return PRILOHY_HEADER in rtf
|
||||
|
||||
|
||||
def pridat_do_sekce_prilohy(rtf, new_bkm_entry, filenameforbookmark):
|
||||
"""Přidá soubor do EXISTUJÍCÍ sekce 'Vložené přílohy'.
|
||||
|
||||
Postup:
|
||||
1. Spočítá počet Files: odkazů = N → nový index = N
|
||||
2. Posune bkmkstart/bkmkend >= N o +1 (uvolní místo pro nový)
|
||||
3. Vloží nový \pard před uzavírací prázdný řádek sekce
|
||||
4. Vloží bookmark na pozici N do {\info{\bookmarks ...}}
|
||||
"""
|
||||
# 1. Počet existujících Files: odkazů v bookmarks listu
|
||||
bkm_match = re.search(r'\{\\info\{\\bookmarks ([^}]*)\}\}', rtf)
|
||||
if bkm_match:
|
||||
bkm_entries = [e for e in bkm_match.group(1).split(';') if e.strip()]
|
||||
n_files = sum(1 for e in bkm_entries if '"Files:' in e)
|
||||
else:
|
||||
bkm_entries = []
|
||||
n_files = 0
|
||||
|
||||
new_idx = n_files
|
||||
print(f" Počet existujících Files odkazů: {n_files} → nový bkmkstart={new_idx}")
|
||||
|
||||
# 2. Posunout bkmkstart/bkmkend >= n_files o +1
|
||||
rtf = re.sub(r'\\bkmkstart (\d+)',
|
||||
lambda m: '\\bkmkstart ' + (
|
||||
str(int(m.group(1)) + 1) if int(m.group(1)) >= n_files
|
||||
else m.group(1)),
|
||||
rtf)
|
||||
rtf = re.sub(r'\\bkmkend (\d+)',
|
||||
lambda m: '\\bkmkend ' + (
|
||||
str(int(m.group(1)) + 1) if int(m.group(1)) >= n_files
|
||||
else m.group(1)),
|
||||
rtf)
|
||||
|
||||
# 3. Najít uzavírající prázdný řádek sekce (první výskyt po hlavičce)
|
||||
prilohy_pos = rtf.find(PRILOHY_HEADER)
|
||||
closing_pos = rtf.find(PRILOHY_CLOSING, prilohy_pos)
|
||||
if closing_pos == -1:
|
||||
raise RuntimeError("Nenalezen uzavírací řádek sekce Vložené přílohy!")
|
||||
|
||||
new_pard = (r'\pard\s10{\*\bkmkstart ' + str(new_idx) + r'}'
|
||||
r'\plain\cs32\f0\ul\fs20\cf1 ' + filenameforbookmark
|
||||
+ r'{\*\bkmkend ' + str(new_idx) + r'}\par')
|
||||
|
||||
rtf = rtf[:closing_pos] + new_pard + '\n' + rtf[closing_pos:]
|
||||
|
||||
# 4. Vložit bookmark na pozici n_files do {\info{\bookmarks}}
|
||||
def insert_bookmark(m):
|
||||
entries = [e for e in m.group(1).split(';') if e.strip()]
|
||||
entries.insert(n_files, new_bkm_entry)
|
||||
return '{\\info{\\bookmarks ' + ';'.join(entries) + '}}'
|
||||
|
||||
rtf = re.sub(r'\{\\info\{\\bookmarks ([^}]*)\}\}', insert_bookmark, rtf)
|
||||
return rtf
|
||||
|
||||
|
||||
def merge_rtf_prepend(existing_rtf, new_bkm_list, new_body_pards, n_new):
|
||||
"""Vloží nový obsah na ZAČÁTEK stávajícího dekurzu (žádná existující sekce)."""
|
||||
rtf = existing_rtf
|
||||
rtf = re.sub(r'\\bkmkstart (\d+)',
|
||||
lambda m: '\\bkmkstart ' + str(int(m.group(1)) + n_new), rtf)
|
||||
rtf = re.sub(r'\\bkmkend (\d+)',
|
||||
lambda m: '\\bkmkend ' + str(int(m.group(1)) + n_new), rtf)
|
||||
|
||||
new_bkm_str = ';'.join(new_bkm_list)
|
||||
|
||||
def merge_bkm(m):
|
||||
existing = m.group(1).strip()
|
||||
combined = new_bkm_str + (';' + existing if existing else '')
|
||||
return '{\\info{\\bookmarks ' + combined + '}}'
|
||||
|
||||
if re.search(r'\{\\info\{\\bookmarks', rtf):
|
||||
rtf = re.sub(r'\{\\info\{\\bookmarks ([^}]*)\}\}', merge_bkm, rtf)
|
||||
else:
|
||||
rtf = re.sub(r'(\\deflang\d+)',
|
||||
r'\1{\\info{\\bookmarks ' + new_bkm_str + '}}', rtf, count=1)
|
||||
|
||||
match = re.search(r'\\uc1\\pard', rtf)
|
||||
if match:
|
||||
pos = match.start()
|
||||
rtf = rtf[:pos] + new_body_pards + '\n' + rtf[pos:]
|
||||
return rtf
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
conn = fdb.connect(dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='WIN1250')
|
||||
|
||||
# ── Krok 1: vložit soubory do ext DB ─────────────────────────────────────────
|
||||
bookmark_list = []
|
||||
bookmarks_body = ''
|
||||
cislo = 9
|
||||
poradi = 0
|
||||
|
||||
for s in SOUBORY:
|
||||
cesta_souboru = os.path.join(CESTA, s['souborname'])
|
||||
datumsouboru = datetime.datetime.fromtimestamp(os.path.getmtime(cesta_souboru))
|
||||
|
||||
print(f"\n>>> Zpracovávám: {s['souborname']}")
|
||||
fileid = funkce_ext.zapis_file_ext(
|
||||
vstupconnection=conn, idpac=IDPAC,
|
||||
cesta=CESTA, souborname=s['souborname'],
|
||||
prvnizavorka=s['prvnizavorka'],
|
||||
soubordate=s['datum'], souborfiledate=datumsouboru,
|
||||
poznamka=s['druhazavorka'],
|
||||
)
|
||||
print(f" → FILES.ID = {fileid}")
|
||||
|
||||
filenameforbookmark = (s['datum'].strftime('%Y-%m-%d') + ' '
|
||||
+ s['prvnizavorka'] + ': ' + s['druhazavorka'])
|
||||
bookmark_list.append('"' + filenameforbookmark + '","Files:' + str(fileid) + '",' + str(cislo))
|
||||
cislo += 7
|
||||
|
||||
bookmarks_body += (r'\pard\s10{\*\bkmkstart ' + str(poradi) + r'}'
|
||||
r'\plain\cs32\f0\ul\fs20\cf1 ' + filenameforbookmark
|
||||
+ r'{\*\bkmkend ' + str(poradi) + r'}\par')
|
||||
poradi += 1
|
||||
|
||||
new_body = (r'\uc1\pard\s10\plain\cs20\f0\i\fs20 Vlo\'9een\'e9 p\'f8\'edlohy:\par' + '\n'
|
||||
+ bookmarks_body + '\n'
|
||||
+ r'\pard\s10\plain\cs15\f0\fs20 \par')
|
||||
|
||||
# ── Krok 2: rozhodovací logika ────────────────────────────────────────────────
|
||||
print(f"\n>>> Hledám poslední dekurs pro IDPAC={IDPAC}...")
|
||||
existujici = najdi_posledni_dekurs_dnes(conn, IDPAC, DATUM)
|
||||
cur = conn.cursor()
|
||||
now = datetime.datetime.now()
|
||||
|
||||
if existujici:
|
||||
dekurs_id, existing_rtf = existujici
|
||||
|
||||
if ma_sekci_prilohy(existing_rtf):
|
||||
# ── Případ 1: dnešní dekurs s existující sekcí → přidáme do ní ──────
|
||||
print(f"\n>>> Sekce 'Vložené přílohy' nalezena v DEKURS ID={dekurs_id}")
|
||||
print(">>> Přidávám soubor DO existující sekce...")
|
||||
filenameforbookmark = (SOUBORY[0]['datum'].strftime('%Y-%m-%d') + ' '
|
||||
+ SOUBORY[0]['prvnizavorka'] + ': ' + SOUBORY[0]['druhazavorka'])
|
||||
merged_rtf = pridat_do_sekce_prilohy(
|
||||
existing_rtf,
|
||||
bookmark_list[0],
|
||||
filenameforbookmark
|
||||
)
|
||||
else:
|
||||
# ── Případ 2: dnešní dekurs bez sekce → prepend ───────────────────────
|
||||
print(f"\n>>> DEKURS ID={dekurs_id} nemá sekci příloh → prepend nové sekce")
|
||||
merged_rtf = merge_rtf_prepend(existing_rtf, bookmark_list, new_body, len(SOUBORY))
|
||||
|
||||
print("\n=== Výsledný RTF ===")
|
||||
print(merged_rtf)
|
||||
|
||||
cur.execute("UPDATE DEKURS SET DEKURS = ? WHERE ID = ?", (merged_rtf, dekurs_id))
|
||||
conn.commit()
|
||||
print(f"\n>>> UPDATE DEKURS ID={dekurs_id} – hotovo!")
|
||||
|
||||
else:
|
||||
# ── Případ 3: žádný dnešní dekurs → nový ─────────────────────────────────
|
||||
print(f"\n>>> Žádný dekurs pro {DATUM} → vytvářím nový...")
|
||||
bookmark_str = ';'.join(bookmark_list)
|
||||
rtf = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks BOOKMARKNAMES}}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
|
||||
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
{\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs32\f0\ul\fs20\cf1 Odkaz;}}
|
||||
BOOKMARKSTEXT
|
||||
\pard\s10\plain\cs15\f0\fs20 \par
|
||||
}"""
|
||||
rtf = rtf.replace('BOOKMARKNAMES', bookmark_str)
|
||||
rtf = rtf.replace('BOOKMARKSTEXT', new_body)
|
||||
|
||||
print("\n=== Výsledný RTF ===")
|
||||
print(rtf)
|
||||
|
||||
dekursid = funkce.get_dekurs_id(conn)
|
||||
cur.execute(
|
||||
"INSERT INTO DEKURS (id, iduzi, idprac, idodd, idpac, datum, cas, dekurs)"
|
||||
" VALUES (?,?,?,?,?,?,?,?)",
|
||||
(dekursid, 6, 2, 2, IDPAC, now.date(), now.time(), rtf)
|
||||
)
|
||||
conn.commit()
|
||||
print(f"\n>>> Nový DEKURS ID={dekursid}")
|
||||
|
||||
conn.close()
|
||||
print("\n=== HOTOVO ===")
|
||||
print("Otevři Medicus → karta Buzalka → zkontroluj dekurs!")
|
||||
@@ -0,0 +1,156 @@
|
||||
# test_import_FINAL.py – detailní dokumentace
|
||||
|
||||
## Co skript dělá
|
||||
|
||||
Importuje PDF soubory (lékařské zprávy) do Medicus DB. Konkrétně:
|
||||
|
||||
1. Uloží fyzický soubor do **externí Firebird DB** (tabulka FILES)
|
||||
2. Vloží nebo aktualizuje **dekurs pacienta** (tabulka DEKURS) s klikacím RTF odkazem na soubor
|
||||
|
||||
---
|
||||
|
||||
## Vstupní data (konfigurace nahoře)
|
||||
|
||||
```python
|
||||
CESTA = r'u:\\' # adresář se zdrojovými PDF soubory
|
||||
IDPAC = 9742 # ID pacienta v DB
|
||||
DATUM = datetime.date(2026, 3, 18) # datum zprávy (ne dnešek!)
|
||||
|
||||
SOUBORY = [
|
||||
{
|
||||
'souborname': 'název souboru.pdf',
|
||||
'prvnizavorka': 'typ zprávy', # např. "vyšetření"
|
||||
'druhazavorka': 'poznámka', # volný text
|
||||
'datum': DATUM,
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
Pozor: `DATUM` je datum zprávy (ne dnešek). Podle tohoto data se hledá existující dekurs.
|
||||
|
||||
---
|
||||
|
||||
## Rozhodovací logika – 3 scénáře
|
||||
|
||||
```
|
||||
Poslední dekurs pacienta
|
||||
│
|
||||
├─ z JINÉHO dne / neexistuje
|
||||
│ └─→ SCÉNÁŘ 3: vytvoří nový dekurs
|
||||
│
|
||||
└─ z DNEŠNÍHO dne (= DATUM)
|
||||
│
|
||||
├─ MÁ sekci "Vložené přílohy"
|
||||
│ └─→ SCÉNÁŘ 1: přidá odkaz DO existující sekce
|
||||
│
|
||||
└─ NEMÁ sekci "Vložené přílohy"
|
||||
└─→ SCÉNÁŘ 2: prepend nové sekce na začátek
|
||||
```
|
||||
|
||||
Klíčová funkce pro detekci: `ma_sekci_prilohy(rtf)` – hledá RTF string `Vlo\'9een\'e9 p\'f8\'edlohy:` (= „Vložené přílohy:" zakódováno win1250).
|
||||
|
||||
---
|
||||
|
||||
## Krok 1 – uložení souboru do ext DB
|
||||
|
||||
Volá `funkce_ext.zapis_file_ext(...)` pro každý soubor. Vrátí `fileid` (ID záznamu v tabulce FILES).
|
||||
|
||||
Z každého souboru se postaví:
|
||||
- **bookmark entry** pro `{\info{\bookmarks ...}}` blok RTF:
|
||||
`"2026-03-18 vyšetření: poznámka","Files:1234",9`
|
||||
- **RTF pard** (klikací odkaz) pro tělo dekurzu:
|
||||
`\pard\s10{\*\bkmkstart 0}\plain\cs32\f0\ul\fs20\cf1 2026-03-18 vyšetření: poznámka{\*\bkmkend 0}\par`
|
||||
|
||||
Číslo `cislo` začíná na 9 a roste po 7 (interní Medicus konvence). Index `poradi` (bkmkstart) začíná na 0 a roste po 1.
|
||||
|
||||
---
|
||||
|
||||
## Krok 2 – práce s dekurzem
|
||||
|
||||
### Scénář 1: přidání DO existující sekce (`pridat_do_sekce_prilohy`)
|
||||
|
||||
Situace: dnešní dekurs již má blok „Vložené přílohy" s nějakými odkazy.
|
||||
|
||||
Postup:
|
||||
1. Spočítá počet existujících `Files:` odkazů v `{\info{\bookmarks}}` → to je index nového (`new_idx`)
|
||||
2. Posune všechny `\bkmkstart N` / `\bkmkend N` kde `N >= new_idx` o +1 (uvolní místo)
|
||||
3. Vloží nový `\pard` řádek **před** uzavírací `\pard\s10\plain\cs15\f0\fs20 \par` sekce
|
||||
4. Vloží nový bookmark na pozici `new_idx` v `{\info{\bookmarks}}`
|
||||
|
||||
Výsledek: soubor se přidá na konec existujícího seznamu příloh, indexy zůstanou konzistentní.
|
||||
|
||||
### Scénář 2: prepend nové sekce (`merge_rtf_prepend`)
|
||||
|
||||
Situace: dnešní dekurs existuje, ale ještě nemá blok příloh.
|
||||
|
||||
Postup:
|
||||
1. Posune všechny existující `\bkmkstart N` / `\bkmkend N` o +n_new (počet nových souborů)
|
||||
2. Přidá nové bookmarky **na začátek** `{\info{\bookmarks}}` bloku
|
||||
- Pokud `{\info{\bookmarks}}` neexistuje, vloží ho za `\deflang1029`
|
||||
3. Vloží nové tělo (záhlaví „Vložené přílohy:" + řádky s odkazy) **před** první `\uc1\pard` těla stávajícího dekurzu
|
||||
|
||||
Výsledek: sekce příloh je viditelně nahoře, stávající text dekurzu zůstane pod ní.
|
||||
|
||||
### Scénář 3: nový dekurs
|
||||
|
||||
Situace: žádný dnešní dekurs neexistuje.
|
||||
|
||||
Sestaví RTF šablonu s:
|
||||
- `{\info{\bookmarks ...}}` – všechny bookmarky
|
||||
- záhlaví „Vložené přílohy:" + klikací řádky
|
||||
- uzavírací prázdný řádek
|
||||
|
||||
Vloží jako nový řádek do tabulky DEKURS s `iduzi=6, idprac=2, idodd=2` (Vladimír Buzalka, ordinace).
|
||||
|
||||
---
|
||||
|
||||
## RTF formát dekurzu
|
||||
|
||||
```rtf
|
||||
{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029
|
||||
{\info{\bookmarks "2026-03-18 vyšetření: poznámka","Files:1234",9}}
|
||||
{\fonttbl{\f0\fnil\fcharset238 Arial;} ...}
|
||||
{\colortbl ;\red0\green0\blue255; ...}
|
||||
{\stylesheet ... {\*\cs32\f0\ul\fs20\cf1 Odkaz;}}
|
||||
|
||||
\uc1\pard\s10\plain\cs20\f0\i\fs20 Vložené přílohy:\par
|
||||
\pard\s10{\*\bkmkstart 0}\plain\cs32\f0\ul\fs20\cf1 2026-03-18 vyšetření: poznámka{\*\bkmkend 0}\par
|
||||
\pard\s10\plain\cs15\f0\fs20 \par
|
||||
}
|
||||
```
|
||||
|
||||
- **cs20** = kurzíva (záhlaví sekce)
|
||||
- **cs32** = podtržený modrý text (klikací odkaz)
|
||||
- **cs15** = normální text
|
||||
- `\cf1` = modrá barva (první v colortbl)
|
||||
|
||||
---
|
||||
|
||||
## Závislosti
|
||||
|
||||
| Import | Odkud | Co dělá |
|
||||
|--------|-------|---------|
|
||||
| `funkce_ext.zapis_file_ext` | `funkce_ext.py` | Uloží soubor do ext DB (tabulka FILES), vrátí fileid |
|
||||
| `funkce.get_dekurs_id` | `funkce.py` | Vrátí nové ID pro INSERT do tabulky DEKURS |
|
||||
| `fdb` | pip | Připojení k Firebird DB |
|
||||
|
||||
---
|
||||
|
||||
## Tabulky v DB
|
||||
|
||||
| Tabulka | DB | Popis |
|
||||
|---------|----|-------|
|
||||
| `DEKURS` | hlavní (`medicus.fdb`) | Záznamy dekurzu, pole `DEKURS` obsahuje RTF text |
|
||||
| `FILES` | ext DB (`MEDICUS_FILES_*.fdb`) | Binární obsah souborů |
|
||||
|
||||
---
|
||||
|
||||
## Jak spustit
|
||||
|
||||
Skript se spouští jednorázově na Windows stroji s přístupem k Firebird DB. Před spuštěním:
|
||||
1. Upravit `SOUBORY` – seznam PDF souborů ke zpracování
|
||||
2. Zkontrolovat `IDPAC`, `DATUM`, `CESTA`
|
||||
3. Ověřit, že PDF soubory fyzicky existují na `CESTA`
|
||||
|
||||
Po spuštění ověřit v Medicus: karta pacienta → záložka Dekurzy → kliknout na odkaz.
|
||||
@@ -0,0 +1,47 @@
|
||||
"""test_import_april2026.py – jednorázový testovací import do DB202604
|
||||
|
||||
Spustit na Windows (Medicus může být spuštěný, jen ne na kartě tohoto pacienta).
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import fdb
|
||||
import funkce_ext
|
||||
|
||||
SOUBOR_CESTA = r'u:\\'
|
||||
SOUBOR_NAZEV = '7309208104 Buzalka, Vladimír 2026-04-01 [testovaci zprava] [všechno OK].pdf'
|
||||
|
||||
IDPAC = 9742 # Buzalka Vladimír (RC 7309208104)
|
||||
PRVNI_ZAVORKA = 'testovaci zprava'
|
||||
POZNAMKA = 'všechno OK'
|
||||
DATUM_ZPRAVY = datetime.date(2026, 4, 1)
|
||||
DATUM_SOUBORU = datetime.datetime.fromtimestamp(
|
||||
os.path.getmtime(os.path.join(SOUBOR_CESTA, SOUBOR_NAZEV)))
|
||||
|
||||
print(f"Soubor: {os.path.join(SOUBOR_CESTA, SOUBOR_NAZEV)}")
|
||||
print(f"Datum zprávy: {DATUM_ZPRAVY}")
|
||||
print(f"Datum souboru:{DATUM_SOUBORU}")
|
||||
print()
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA',
|
||||
password='masterkey',
|
||||
charset='WIN1250'
|
||||
)
|
||||
|
||||
fileid = funkce_ext.zapis_file_ext(
|
||||
vstupconnection=conn,
|
||||
idpac=IDPAC,
|
||||
cesta=SOUBOR_CESTA,
|
||||
souborname=SOUBOR_NAZEV,
|
||||
prvnizavorka=PRVNI_ZAVORKA,
|
||||
soubordate=DATUM_ZPRAVY,
|
||||
souborfiledate=DATUM_SOUBORU,
|
||||
poznamka=POZNAMKA,
|
||||
)
|
||||
|
||||
conn.close()
|
||||
print()
|
||||
print(f"Hotovo! FILES.ID = {fileid}")
|
||||
print("Otevři Medicus, přejdi na kartu Buzalka Vladimír a zkontroluj záložku Soubory.")
|
||||
@@ -0,0 +1,182 @@
|
||||
"""test_import_merge.py – vloží přílohy do dekurzu daného dne.
|
||||
Pokud dekurs pro daný den existuje → vloží přílohy NAHORU před stávající text.
|
||||
Pokud neexistuje → vytvoří nový dekurs.
|
||||
Spustit na Windows.
|
||||
"""
|
||||
import datetime, os, re, fdb
|
||||
import funkce, funkce_ext
|
||||
|
||||
CESTA = r'u:\\'
|
||||
IDPAC = 9742 # Buzalka Vladimír
|
||||
DATUM = datetime.date(2026, 3, 18)
|
||||
|
||||
SOUBORY = [
|
||||
{'souborname': '7309208104 2026-03-18 Buzalka, Vladimír [vyšetření] [ahoj Claude copy1].pdf',
|
||||
'prvnizavorka': 'vyšetření', 'druhazavorka': 'ahoj Claude copy1', 'datum': DATUM},
|
||||
{'souborname': '7309208104 2026-03-18 Buzalka, Vladimír [vyšetření] [ahoj Claude copy2].pdf',
|
||||
'prvnizavorka': 'vyšetření', 'druhazavorka': 'ahoj Claude copy2', 'datum': DATUM},
|
||||
{'souborname': '7309208104 2026-03-18 Buzalka, Vladimír [vyšetření] [ahoj Claude].pdf',
|
||||
'prvnizavorka': 'vyšetření', 'druhazavorka': 'ahoj Claude', 'datum': DATUM},
|
||||
]
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
def najdi_posledni_dekurs_dnes(conn, idpac, datum_vlozeni):
|
||||
"""Najde POSLEDNÍ dekurs pacienta (jakýkoli datum) a vrátí (id, rtf)
|
||||
pouze pokud je ze stejného dne jako datum_vlozeni. Jinak vrátí None.
|
||||
|
||||
Logika: zajímá nás jen poslední zápis – pokud je z dnešního dne,
|
||||
vložíme přílohy do něj. Pokud je starší, zakládáme nový dnešní záznam.
|
||||
"""
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
SELECT FIRST 1 ID, DATUM, DEKURS FROM DEKURS
|
||||
WHERE IDPAC = ?
|
||||
ORDER BY ID DESC
|
||||
""", (idpac,))
|
||||
row = cur.fetchone()
|
||||
if row is None:
|
||||
return None
|
||||
dekurs_id, dekurs_datum, dekurs_rtf = row
|
||||
print(f" Poslední dekurs: ID={dekurs_id}, datum={dekurs_datum}")
|
||||
if dekurs_datum == datum_vlozeni:
|
||||
print(f" → shoduje se s dneškem ({datum_vlozeni}), budeme mergovat")
|
||||
return (dekurs_id, dekurs_rtf)
|
||||
else:
|
||||
print(f" → jiný den ({dekurs_datum} ≠ {datum_vlozeni}), vytvoříme nový")
|
||||
return None
|
||||
|
||||
|
||||
def merge_rtf_prepend(existing_rtf, new_bkm_list, new_body_pards, n_new):
|
||||
"""Vloží nový obsah (přílohy) na ZAČÁTEK stávajícího dekurzu.
|
||||
|
||||
existing_rtf – stávající RTF string z DB
|
||||
new_bkm_list – list stringů typu '"popis","Files:123",9'
|
||||
new_body_pards – RTF string: header \par + \pard řádky s odkazy
|
||||
n_new – počet nových bkmkstart indexů (= počet souborů)
|
||||
"""
|
||||
rtf = existing_rtf
|
||||
|
||||
# 1. Posunout stávající bkmkstart/bkmkend indexy o n_new,
|
||||
# aby nedošlo ke kolizi s našimi novými (0, 1, 2 …)
|
||||
rtf = re.sub(r'\\bkmkstart (\d+)',
|
||||
lambda m: '\\bkmkstart ' + str(int(m.group(1)) + n_new), rtf)
|
||||
rtf = re.sub(r'\\bkmkend (\d+)',
|
||||
lambda m: '\\bkmkend ' + str(int(m.group(1)) + n_new), rtf)
|
||||
|
||||
# 2. Přidat naše bookmarky na ZAČÁTEK {\info{\bookmarks ...}}
|
||||
new_bkm_str = ';'.join(new_bkm_list)
|
||||
|
||||
def merge_bkm(m):
|
||||
existing = m.group(1).strip()
|
||||
combined = new_bkm_str + (';' + existing if existing else '')
|
||||
return '{\\info{\\bookmarks ' + combined + '}}'
|
||||
|
||||
if re.search(r'\{\\info\{\\bookmarks', rtf):
|
||||
rtf = re.sub(r'\{\\info\{\\bookmarks ([^}]*)\}\}', merge_bkm, rtf)
|
||||
else:
|
||||
# žádný {\info} blok – vložíme za \deflang...
|
||||
rtf = re.sub(r'(\\deflang\d+)',
|
||||
r'\1{\\info{\\bookmarks ' + new_bkm_str + '}}', rtf, count=1)
|
||||
|
||||
# 3. Vložit naše tělo před první \uc1\pard těla stávajícího dekurzu
|
||||
match = re.search(r'\\uc1\\pard', rtf)
|
||||
if match:
|
||||
pos = rtf.index(r'\uc1\pard', match.start())
|
||||
rtf = rtf[:pos] + new_body_pards + '\n' + rtf[pos:]
|
||||
else:
|
||||
# fallback – připojit před poslední }
|
||||
rtf = rtf.rstrip()
|
||||
if rtf.endswith('}'):
|
||||
rtf = rtf[:-1] + new_body_pards + '\n}'
|
||||
|
||||
return rtf
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
conn = fdb.connect(dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='WIN1250')
|
||||
|
||||
# ── Krok 1: vložit soubory do ext DB ─────────────────────────────────────────
|
||||
bookmark_list = []
|
||||
bookmarks_body = ''
|
||||
cislo = 9
|
||||
poradi = 0
|
||||
|
||||
for s in SOUBORY:
|
||||
cesta_souboru = os.path.join(CESTA, s['souborname'])
|
||||
datumsouboru = datetime.datetime.fromtimestamp(os.path.getmtime(cesta_souboru))
|
||||
|
||||
print(f"\n>>> Zpracovávám: {s['souborname']}")
|
||||
fileid = funkce_ext.zapis_file_ext(
|
||||
vstupconnection=conn, idpac=IDPAC,
|
||||
cesta=CESTA, souborname=s['souborname'],
|
||||
prvnizavorka=s['prvnizavorka'],
|
||||
soubordate=s['datum'], souborfiledate=datumsouboru,
|
||||
poznamka=s['druhazavorka'],
|
||||
)
|
||||
print(f" → FILES.ID = {fileid}")
|
||||
|
||||
filenameforbookmark = (s['datum'].strftime('%Y-%m-%d') + ' '
|
||||
+ s['prvnizavorka'] + ': ' + s['druhazavorka'])
|
||||
bookmark_list.append('"' + filenameforbookmark + '","Files:' + str(fileid) + '",' + str(cislo))
|
||||
cislo += 7
|
||||
|
||||
bookmarks_body += (r'\pard\s10{\*\bkmkstart ' + str(poradi) + r'}'
|
||||
r'\plain\cs32\f0\ul\fs20\cf1 ' + filenameforbookmark
|
||||
+ r'{\*\bkmkend ' + str(poradi) + r'}\par')
|
||||
poradi += 1
|
||||
|
||||
# Tělo přílohy (záhlaví + odkaz řádky + prázdný řádek)
|
||||
new_body = (r'\uc1\pard\s10\plain\cs20\f0\i\fs20 Vlo\'9een\'e9 p\'f8\'edlohy:\par' + '\n'
|
||||
+ bookmarks_body + '\n'
|
||||
+ r'\pard\s10\plain\cs15\f0\fs20 \par')
|
||||
|
||||
# ── Krok 2: existující dekurs pro tento den? ──────────────────────────────────
|
||||
print(f"\n>>> Hledám poslední dekurs pro IDPAC={IDPAC}...")
|
||||
existujici = najdi_posledni_dekurs_dnes(conn, IDPAC, DATUM)
|
||||
cur = conn.cursor()
|
||||
now = datetime.datetime.now()
|
||||
|
||||
if existujici:
|
||||
dekurs_id, existing_rtf = existujici
|
||||
print(f"\n>>> Nalezen existující dekurs pro {DATUM}: ID={dekurs_id}")
|
||||
print(">>> Vkládám přílohy na začátek...")
|
||||
|
||||
merged_rtf = merge_rtf_prepend(existing_rtf, bookmark_list, new_body, len(SOUBORY))
|
||||
|
||||
print("\n=== Výsledný RTF ===")
|
||||
print(merged_rtf)
|
||||
|
||||
cur.execute("UPDATE DEKURS SET DEKURS = ? WHERE ID = ?", (merged_rtf, dekurs_id))
|
||||
conn.commit()
|
||||
print(f"\n>>> UPDATE DEKURS ID={dekurs_id} – hotovo!")
|
||||
|
||||
else:
|
||||
print(f"\n>>> Žádný dekurs pro {DATUM} nenalezen – vytvářím nový...")
|
||||
|
||||
bookmark_str = ';'.join(bookmark_list)
|
||||
rtf = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks BOOKMARKNAMES}}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
|
||||
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
{\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs32\f0\ul\fs20\cf1 Odkaz;}}
|
||||
BOOKMARKSTEXT
|
||||
\pard\s10\plain\cs15\f0\fs20 \par
|
||||
}"""
|
||||
rtf = rtf.replace('BOOKMARKNAMES', bookmark_str)
|
||||
rtf = rtf.replace('BOOKMARKSTEXT', new_body)
|
||||
|
||||
print("\n=== Výsledný RTF ===")
|
||||
print(rtf)
|
||||
|
||||
dekursid = funkce.get_dekurs_id(conn)
|
||||
cur.execute(
|
||||
"INSERT INTO DEKURS (id, iduzi, idprac, idodd, idpac, datum, cas, dekurs)"
|
||||
" VALUES (?,?,?,?,?,?,?,?)",
|
||||
(dekursid, 6, 2, 2, IDPAC, now.date(), now.time(), rtf)
|
||||
)
|
||||
conn.commit()
|
||||
print(f"\n>>> Nový DEKURS ID={dekursid}")
|
||||
|
||||
conn.close()
|
||||
print("\n=== HOTOVO ===")
|
||||
print("Otevři Medicus → karta Buzalka → zkontroluj dekurs!")
|
||||
@@ -0,0 +1,140 @@
|
||||
# Dekurzy report – dokumentace
|
||||
|
||||
## Co report dělá
|
||||
|
||||
Generuje Excel soubor s přehledem všech dekurzů z ordinace MUDr. Buzalkové za zadané období.
|
||||
Hlavní list **Dekurz** zobrazuje každý dekurz jako jeden řádek. Čísla v sloupcích jsou klikatelné hyperlinkové zkratky, které přeskočí na příslušný detailní list (Recepty, Výkony, Soubory apod.).
|
||||
|
||||
---
|
||||
|
||||
## Spuštění
|
||||
|
||||
```
|
||||
python dekurz_report.py
|
||||
```
|
||||
|
||||
**Vstupní parametry** (nastavit přímo v souboru):
|
||||
|
||||
| Proměnná | Výchozí hodnota | Popis |
|
||||
|---|---|---|
|
||||
| `DATUM_OD` | `2025-01-01` | Začátek období |
|
||||
| `DATUM_DO` | dnešní datum | Konec období (automaticky) |
|
||||
| `VYSTUPNI_ADRESAR` | `u:\Dropbox\Ordinace\Reporty` | Kam se ukládá |
|
||||
| `NAZEV_REPORTU` | `Dekurzy` | Část názvu souboru |
|
||||
|
||||
**Výstupní soubor:** `YYYY-MM-DD HH-MM-SS Dekurzy.xlsx`
|
||||
Starý soubor se stejným názvem je automaticky smazán.
|
||||
|
||||
---
|
||||
|
||||
## Zdroj dat
|
||||
|
||||
Databáze Firebird: `localhost:c:\medicus 3\data\medicus.fdb`
|
||||
Připojení: SYSDBA / masterkey, charset win1250
|
||||
|
||||
### Hlavní dotaz
|
||||
|
||||
```sql
|
||||
SELECT d.DATUM, d.CAS, u.ZKRATKA, k.PRIJMENI, k.JMENO, k.RODCIS, k.POJ, d.DEKURS
|
||||
FROM DEKURS d
|
||||
JOIN KAR k ON k.IDPAC = d.IDPAC
|
||||
LEFT JOIN UZIVATEL u ON u.IDUZI = d.IDUZI
|
||||
WHERE d.DATUM >= '2025-01-01' AND d.DATUM <= dnes
|
||||
ORDER BY d.DATUM DESC, d.CAS DESC, k.PRIJMENI, k.JMENO
|
||||
```
|
||||
|
||||
Řazení: nejnovější záznamy nahoře.
|
||||
|
||||
---
|
||||
|
||||
## Jak funguje parsování RTF bookmarků
|
||||
|
||||
Každý dekurz je uložen jako RTF blob ve sloupci `DEKURS.DEKURS`.
|
||||
Medicus do RTF hlavičky zapisuje **bookmarky** – hypertextové odkazy na propojené záznamy:
|
||||
|
||||
```
|
||||
{\info{\bookmarks "ATORIS","Rec:322528",17;"01543","VykA:189603",8}}
|
||||
```
|
||||
|
||||
Formát: `"název","TYP:ID",číslo_stylu`
|
||||
|
||||
Skript parsuje regex `"([^"]+)","([A-Za-z]+):(\d+)"` a extrahuje typ a ID záznamu.
|
||||
|
||||
### Typy bookmarků a jejich tabulky
|
||||
|
||||
| Bookmark | List v Excelu | Tabulka v DB | PK | Zobrazované sloupce |
|
||||
|---|---|---|---|---|
|
||||
| `Rec` | Recepty | `RECEPT` | `ID` | LEK, DSIG (lék, dávkování) |
|
||||
| `VykA` | Výkony | `DOKLADD` | `ID` | KOD, DDGN (kód výkonu, diagnóza) |
|
||||
| `Files` | Soubory | `FILES` | `ID` | FILENAME, DATUM |
|
||||
| `MEDLAB` | MedLab | `HISTDOC` | `ID` | DATUM, TYP (žádanka do laboratoře) |
|
||||
| `Lab` | Lab | `LABVH` | `IDVH` | DATUM, CISLO (výsledky laboratoře) |
|
||||
| `Ock` | Očkování | `OCKZAZ` | `ID` | DATUM, LATKA (vakcína) |
|
||||
| `Nes` | Neschop. | `NES` | `ID` | ZACNES, KONNES (od – do) |
|
||||
| `Lec` | Léčiva | `LECD` | `ID` | KOD, DATOSE (léčivo podané v ordinaci) |
|
||||
| `SpecVys` | SpecVys | `SPECVYS` | `IDSPECVYS` | TYP, DATUM (Tonotrack, holter…) |
|
||||
| `PlaPac` | Platby | `PLA` | `IDPLA` | DATUM, CENA, DOKLAD |
|
||||
| ostatní | Ostatní | – | – | TYP, ID, Název (formuláře, poukazy…) |
|
||||
|
||||
**Ostatní typy** (méně časté):
|
||||
`ORTOPE` – ePoukaz na ortopedickou pomůcku
|
||||
`ZDRINF` – Žádost o předání zdravotních informací
|
||||
`PROHLAS` – Prohlášení
|
||||
`POTDPN` – Potvrzení DPN
|
||||
`MOTORVO` – Posudek motorového vozidla
|
||||
`LAZPEC` – Lázně
|
||||
`VypZdrD` – Výpis ze zdravotní dokumentace
|
||||
`VYMLIST` – Výměnný list
|
||||
`PouRTG` – Poukaz RTG
|
||||
`ZPUPRN` – Způsobilost k práci/řízení
|
||||
`EPOSMRO` – ePosudek MRO
|
||||
`ZNESUP` – Potvrzení neschopnosti uchazeče o zaměstnání
|
||||
|
||||
> Všechny výše uvedené jdou do listu **Ostatní** s uvedením typu, ID a názvu.
|
||||
|
||||
---
|
||||
|
||||
## Struktura Excel souboru
|
||||
|
||||
### List Dekurz (hlavní)
|
||||
|
||||
| Sloupec | Zdroj | Popis |
|
||||
|---|---|---|
|
||||
| Datum | `DEKURS.DATUM` | Datum dekurzu |
|
||||
| Čas | `DEKURS.CAS` | Čas (HH:MM) |
|
||||
| Lékař | `UZIVATEL.ZKRATKA` | MBU / VBU / ISE |
|
||||
| Jméno | `KAR.PRIJMENI + JMENO` | Formát: `Příjmení, I.` |
|
||||
| Rodné číslo | `KAR.RODCIS` | |
|
||||
| Pojišťovna | `KAR.POJ` | Kód pojišťovny (111, 201…) |
|
||||
| Rec … PlaPac | RTF bookmark | Počet záznamů – **klikací hyperlink** na detailní list |
|
||||
| Ostatní | RTF bookmark | Počet ostatních typů – hyperlink na list Ostatní |
|
||||
|
||||
### Detailní listy
|
||||
|
||||
Každý list má:
|
||||
- Záhlaví s vlastní barevnou kombinací
|
||||
- Sloupce: Datum, Jméno + specifické sloupce dle typu
|
||||
- Střídání bílých a barevných řádků
|
||||
- Tenké šedé ohraničení všech buněk
|
||||
- Zmrazený první řádek
|
||||
- Autofiltr
|
||||
|
||||
---
|
||||
|
||||
## Technické poznámky
|
||||
|
||||
- Firebird limit `IN (...)` je 1500 hodnot – dotazy na detaily se automaticky dělí do dávek po 1000
|
||||
- RTF blob je čten přes `blob.read()` nebo přímo jako string
|
||||
- Jméno pacienta: `Příjmení, I.` (iniciála prvního písmene jména)
|
||||
- Chybová hláška `BlobReader.close: invalid BLOB handle` je neškodná – GC uzavírá handlery po odpojení DB
|
||||
|
||||
---
|
||||
|
||||
## Scheduled Task
|
||||
|
||||
Spouštěcí příkaz:
|
||||
```
|
||||
python "C:\Users\vlado\PycharmProjects\Medicus\MedicusWithClaudeDekurz\dekurz_report.py"
|
||||
```
|
||||
|
||||
Doporučené spouštění: každý den ráno (např. 6:00), aby byl vždy čerstvý soubor v Dropboxu.
|
||||
@@ -0,0 +1,352 @@
|
||||
import sys, io, re, os, glob
|
||||
from datetime import date, datetime, timedelta
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
import fdb
|
||||
import openpyxl
|
||||
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
||||
|
||||
VYSTUPNI_ADRESAR = r'u:\Dropbox\Ordinace\Reporty'
|
||||
NAZEV_REPORTU = 'Dekurzy_TEST'
|
||||
DATUM_OD = (date.today() - timedelta(days=100)).strftime('%Y-%m-%d')
|
||||
DATUM_DO = date.today().strftime('%Y-%m-%d')
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute(f"""
|
||||
SELECT d.DATUM, d.CAS, u.ZKRATKA, k.PRIJMENI, k.JMENO, k.RODCIS, k.POJ, d.DEKURS
|
||||
FROM DEKURS d
|
||||
JOIN KAR k ON k.IDPAC = d.IDPAC
|
||||
LEFT JOIN UZIVATEL u ON u.IDUZI = d.IDUZI
|
||||
WHERE d.DATUM >= '{DATUM_OD}' AND d.DATUM <= '{DATUM_DO}'
|
||||
ORDER BY d.DATUM DESC, d.CAS DESC, k.PRIJMENI, k.JMENO
|
||||
""")
|
||||
raw_rows = cur.fetchall()
|
||||
|
||||
TOP_TYPY = ['VykA', 'Rec', 'Files', 'MEDLAB', 'Lab', 'Ock', 'Nes', 'Lec', 'SpecVys', 'PlaPac']
|
||||
|
||||
def rtf_na_text(rtf):
|
||||
"""Jednoduchý převod RTF na čistý text."""
|
||||
# Odstraň info blok (bookmarky apod.)
|
||||
text = re.sub(r'\{\\info.*?\}', '', rtf, flags=re.DOTALL)
|
||||
# Odstraň ostatní skupiny v {} rekurzivně (fonty, barvy apod.)
|
||||
for _ in range(6):
|
||||
text = re.sub(r'\{[^{}]*\}', '', text)
|
||||
# Nový řádek za \par \line
|
||||
text = re.sub(r'\\par\b\s*', '\n', text)
|
||||
text = re.sub(r'\\line\b\s*', '\n', text)
|
||||
# Dekóduj RTF hex escape sekvence (\'xx) jako cp1250 → správná čeština
|
||||
def decode_hex(m):
|
||||
try:
|
||||
return bytes.fromhex(m.group(1)).decode('cp1250')
|
||||
except Exception:
|
||||
return ''
|
||||
text = re.sub(r"\\'([0-9a-fA-F]{2})", decode_hex, text)
|
||||
# Odstraň ostatní RTF příkazy
|
||||
text = re.sub(r'\\[a-zA-Z]+\-?[0-9]*\s?', '', text)
|
||||
text = re.sub(r'[{}\\]', '', text)
|
||||
# Vyčisti prázdné řádky a whitespace
|
||||
lines = [l.strip() for l in text.splitlines()]
|
||||
lines = [l for l in lines if l]
|
||||
return '\n'.join(lines)
|
||||
|
||||
# Parse dekurzů
|
||||
rows = []
|
||||
for datum, cas, zkratka, prijmeni, jmeno, rodcis, poj, dekurs_blob in raw_rows:
|
||||
rtf = dekurs_blob.read() if hasattr(dekurs_blob, 'read') else (dekurs_blob or '')
|
||||
pocty = {}
|
||||
ids_by_typ = {t: [] for t in TOP_TYPY}
|
||||
ids_ostatni = []
|
||||
for nazev, typ, rid in re.findall(r'"([^"]+)","([A-Za-z]+):(\d+)"', rtf):
|
||||
pocty[typ] = pocty.get(typ, 0) + 1
|
||||
if typ in ids_by_typ:
|
||||
ids_by_typ[typ].append(int(rid))
|
||||
else:
|
||||
ids_ostatni.append((typ, int(rid), nazev))
|
||||
top = [pocty.get(t, 0) for t in TOP_TYPY]
|
||||
ostatni = sum(v for k, v in pocty.items() if k not in TOP_TYPY)
|
||||
iniciala = jmeno[0] + '.' if jmeno and jmeno.strip() else ''
|
||||
jmeno_cel = f"{prijmeni.strip()}, {iniciala}" if prijmeni else iniciala
|
||||
text_dekurzu = rtf_na_text(rtf)
|
||||
rows.append((datum, cas, zkratka, jmeno_cel, rodcis, poj, top, ostatni, ids_by_typ, ids_ostatni, text_dekurzu))
|
||||
|
||||
# ── Načtení detailů z DB ────────────────────────────────────────────────────
|
||||
def fetch_details(cur, table, pk, id_col, fields, ids):
|
||||
if not ids:
|
||||
return {}
|
||||
result = {}
|
||||
batch_size = 1000
|
||||
for i in range(0, len(ids), batch_size):
|
||||
batch = ids[i:i+batch_size]
|
||||
ph = ','.join('?' * len(batch))
|
||||
cur.execute(f"SELECT {pk}, {','.join(fields)} FROM {table} WHERE {id_col} IN ({ph})", batch)
|
||||
for row in cur.fetchall():
|
||||
result[row[0]] = row[1:]
|
||||
return result
|
||||
|
||||
def get_ids(rows, typ):
|
||||
return list({rid for *_, ids_by_typ, _, _text in rows for rid in ids_by_typ[typ]})
|
||||
|
||||
rec_det = fetch_details(cur, 'RECEPT', 'ID', 'ID', ['LEK','DSIG'], get_ids(rows,'Rec'))
|
||||
vyka_det = fetch_details(cur, 'DOKLADD', 'ID', 'ID', ['KOD','DDGN','BODY'], get_ids(rows,'VykA'))
|
||||
files_det = fetch_details(cur, 'FILES', 'ID', 'ID', ['FILENAME','DATUM'], get_ids(rows,'Files'))
|
||||
medlab_det = fetch_details(cur, 'HISTDOC', 'ID', 'ID', ['DATUM','TYP'], get_ids(rows,'MEDLAB'))
|
||||
lab_det = fetch_details(cur, 'LABVH', 'IDVH', 'IDVH', ['DATUM','CISLO'], get_ids(rows,'Lab'))
|
||||
ock_det = fetch_details(cur, 'OCKZAZ', 'ID', 'ID', ['DATUM','LATKA'], get_ids(rows,'Ock'))
|
||||
nes_det = fetch_details(cur, 'NES', 'ID', 'ID', ['ZACNES','KONNES'], get_ids(rows,'Nes'))
|
||||
lec_det = fetch_details(cur, 'LECD', 'ID', 'ID', ['KOD','DATOSE'], get_ids(rows,'Lec'))
|
||||
spec_det = fetch_details(cur, 'SPECVYS', 'IDSPECVYS','IDSPECVYS',['TYP','DATUM'], get_ids(rows,'SpecVys'))
|
||||
pla_det = fetch_details(cur, 'PLA', 'IDPLA', 'IDPLA', ['DATUM','CENA','DOKLAD'], get_ids(rows,'PlaPac'))
|
||||
|
||||
conn.close()
|
||||
print(f"Načteno {len(rows)} dekurzů (období: {DATUM_OD} – {DATUM_DO})")
|
||||
|
||||
# ── Styly ──────────────────────────────────────────────────────────────────
|
||||
tenka_cara = Side(style='thin', color='AAAAAA')
|
||||
ohraniceni = Border(left=tenka_cara, right=tenka_cara, top=tenka_cara, bottom=tenka_cara)
|
||||
hl_font = Font(bold=True, color="FFFFFF")
|
||||
hl_fill = PatternFill("solid", fgColor="2E75B6")
|
||||
r_fill = [PatternFill("solid", fgColor="FFFFFF"), PatternFill("solid", fgColor="DCE6F1")]
|
||||
|
||||
BARVY_LISTU = {
|
||||
'Recepty': ('1F6B33', 'E2EFDA'),
|
||||
'Výkony': ('2E4057', 'D6E4F0'),
|
||||
'Soubory': ('7B3F00', 'FAE5D3'),
|
||||
'Žádanky': ('4A235A', 'F5EEF8'),
|
||||
'Lab výsl.': ('145A32', 'D5F5E3'),
|
||||
'Očkování': ('7E5109', 'FDEBD0'),
|
||||
'Neschop.': ('922B21', 'FADBD8'),
|
||||
'Léčiva': ('1A5276', 'D6EAF8'),
|
||||
'SpecVys': ('0B5345', 'D1F2EB'),
|
||||
'Platby': ('4D5656', 'EAECEE'),
|
||||
'Ostatní': ('2C3E50', 'EBF5FB'),
|
||||
}
|
||||
|
||||
def zapis_hlavicku(ws, sloupce, sirky, barva_hex):
|
||||
hl_fill_l = PatternFill("solid", fgColor=barva_hex)
|
||||
for col, (nazev, sirka) in enumerate(zip(sloupce, sirky), start=1):
|
||||
cell = ws.cell(row=1, column=col, value=nazev)
|
||||
cell.font = hl_font
|
||||
cell.fill = hl_fill_l
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
cell.border = ohraniceni
|
||||
ws.column_dimensions[cell.column_letter].width = sirka
|
||||
|
||||
def zapis_radek(ws, row_i, hodnoty, zarovnani, barva_hex):
|
||||
fill = PatternFill("solid", fgColor="FFFFFF") if row_i % 2 == 0 \
|
||||
else PatternFill("solid", fgColor=barva_hex)
|
||||
for col_i, (val, align) in enumerate(zip(hodnoty, zarovnani), start=1):
|
||||
cell = ws.cell(row=row_i, column=col_i, value=val)
|
||||
cell.fill = fill
|
||||
cell.border = ohraniceni
|
||||
cell.alignment = Alignment(horizontal=align)
|
||||
if col_i == 1 and isinstance(val, __import__('datetime').date):
|
||||
cell.number_format = 'DD.MM.YYYY'
|
||||
|
||||
def hyperlink_cell(ws, row_i, col_i, cil_list, cil_radek, text, barva_hex):
|
||||
fill = PatternFill("solid", fgColor="FFFFFF") if row_i % 2 == 0 \
|
||||
else PatternFill("solid", fgColor=barva_hex)
|
||||
cell = ws.cell(row=row_i, column=col_i)
|
||||
cell.value = str(text)
|
||||
# Název listu s mezerou musí být v apostrofech
|
||||
sheet_ref = f"'{cil_list}'" if ' ' in cil_list else cil_list
|
||||
cell.hyperlink = f'#{sheet_ref}!A{cil_radek}'
|
||||
cell.font = Font(color="0000FF", underline='single')
|
||||
cell.fill = fill
|
||||
cell.border = ohraniceni
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
|
||||
# ── Workbook ───────────────────────────────────────────────────────────────
|
||||
wb = openpyxl.Workbook()
|
||||
|
||||
LISTY = [
|
||||
('Recepty', 'Rec', rec_det, ['Datum','Jméno','Recept','Dávkování'], [12,25,25,12], None),
|
||||
('Výkony', 'VykA', vyka_det, ['Datum','Jméno','Kód výkonu','Diagnóza','Body'], [12,25,14,10,8], None),
|
||||
('Soubory', 'Files', files_det, ['Datum','Jméno','Soubor','Datum souboru'], [12,25,35,14], None),
|
||||
('Žádanky', 'MEDLAB', medlab_det, ['Datum','Jméno','Typ'], [12,25,15], None),
|
||||
('Lab výsl.', 'Lab', lab_det, ['Datum','Jméno','Číslo'], [12,25,20], None),
|
||||
('Očkování', 'Ock', ock_det, ['Datum','Jméno','Datum očkování','Vakcína'], [12,25,14,30], None),
|
||||
('Neschop.', 'Nes', nes_det, ['Datum','Jméno','Od','Do'], [12,25,12,12], None),
|
||||
('Léčiva', 'Lec', lec_det, ['Datum','Jméno','Kód','Datum výkonu'], [12,25,12,14], None),
|
||||
('SpecVys', 'SpecVys', spec_det, ['Datum','Jméno','Typ vyšetření','Datum vyšetření'], [12,25,25,14], None),
|
||||
('Platby', 'PlaPac', pla_det, ['Datum','Jméno','Datum platby','Částka','Doklad'], [12,25,14,12,15], None),
|
||||
('Ostatní', None, None, ['Datum','Jméno','Typ','ID','Název'], [12,25,12,10,30], None),
|
||||
]
|
||||
|
||||
ws_d = wb.active
|
||||
ws_d.title = "Dekurz"
|
||||
|
||||
# List "Text dekurzu" hned za Dekurzem
|
||||
ws_text = wb.create_sheet("Text dekurzu")
|
||||
|
||||
ws_listy = {}
|
||||
for nazev, *_ in LISTY:
|
||||
ws_listy[nazev] = wb.create_sheet(nazev)
|
||||
|
||||
for nazev, typ, det, sloupce, sirky, _ in LISTY:
|
||||
barva_hl, _ = BARVY_LISTU[nazev]
|
||||
zapis_hlavicku(ws_listy[nazev], sloupce, sirky, barva_hl)
|
||||
ws_listy[nazev].freeze_panes = 'A2'
|
||||
|
||||
# Záhlaví listu "Text dekurzu"
|
||||
zapis_hlavicku(ws_text,
|
||||
['Datum', 'Čas', 'Lékař', 'Jméno', 'Rodné číslo', 'Text dekurzu'],
|
||||
[12, 8, 8, 25, 14, 100],
|
||||
'2E75B6')
|
||||
ws_text.freeze_panes = 'A2'
|
||||
row_ptr_text = 2
|
||||
|
||||
# Zobrazované názvy sloupců pro TOP_TYPY (stejné pořadí)
|
||||
NAZVY_TYPY = ['VykA', 'Rec', 'Files', 'Žádanky', 'Lab výsl.', 'Ock', 'Nes', 'Lec', 'SpecVys', 'PlaPac']
|
||||
|
||||
# Záhlaví Dekurz – sloupce A–R (bez textu)
|
||||
nazvy_d = ['Datum', 'Čas', 'Lékař', 'Jméno', 'Rodné číslo', 'Pojišťovna'] + ['HodVyk'] + NAZVY_TYPY + ['Ostatní']
|
||||
sirky_d = [12, 8, 8, 25, 14, 12 ] + [10] + [38, 8, 8, 8, 8, 8, 8, 8, 8, 8] + [8]
|
||||
zapis_hlavicku(ws_d, nazvy_d, sirky_d, '2E75B6')
|
||||
ws_d.freeze_panes = 'A2'
|
||||
ws_d.auto_filter.ref = f"A1:R{len(rows)+1}"
|
||||
|
||||
row_ptr = {nazev: 2 for nazev, *_ in LISTY}
|
||||
|
||||
# ── Plnění dat ─────────────────────────────────────────────────────────────
|
||||
def get_det_hodnoty(typ, rid, datum, jmeno_cel):
|
||||
if typ == 'Rec':
|
||||
d = rec_det.get(rid, ('', ''))
|
||||
return [datum, jmeno_cel, d[0] or '', d[1] or '']
|
||||
elif typ == 'VykA':
|
||||
d = vyka_det.get(rid, ('', '', 0))
|
||||
return [datum, jmeno_cel, d[0] or '', (d[1] or '').strip(), d[2] or 0]
|
||||
elif typ == 'Files':
|
||||
d = files_det.get(rid, ('', ''))
|
||||
return [datum, jmeno_cel, d[0] or '', d[1]]
|
||||
elif typ == 'MEDLAB':
|
||||
d = medlab_det.get(rid, ('', ''))
|
||||
return [datum, jmeno_cel, d[1] or '']
|
||||
elif typ == 'Lab':
|
||||
d = lab_det.get(rid, ('', ''))
|
||||
return [datum, jmeno_cel, d[1] or '']
|
||||
elif typ == 'Ock':
|
||||
d = ock_det.get(rid, ('', ''))
|
||||
return [datum, jmeno_cel, d[0], d[1] or '']
|
||||
elif typ == 'Nes':
|
||||
d = nes_det.get(rid, ('', ''))
|
||||
return [datum, jmeno_cel, d[0], d[1]]
|
||||
elif typ == 'Lec':
|
||||
d = lec_det.get(rid, ('', ''))
|
||||
return [datum, jmeno_cel, d[0] or '', d[1]]
|
||||
elif typ == 'SpecVys':
|
||||
d = spec_det.get(rid, ('', ''))
|
||||
return [datum, jmeno_cel, d[0] or '', d[1]]
|
||||
elif typ == 'PlaPac':
|
||||
d = pla_det.get(rid, ('', '', ''))
|
||||
return [datum, jmeno_cel, d[0], d[1], d[2] or '']
|
||||
return []
|
||||
|
||||
ZAROVNANI = {
|
||||
'Recepty': ['left','left','left','center'],
|
||||
'Výkony': ['left','left','center','center','right'],
|
||||
'Soubory': ['left','left','left','left'],
|
||||
'MedLab': ['left','left','center'],
|
||||
'Lab': ['left','left','center'],
|
||||
'Očkování': ['left','left','left','left'],
|
||||
'Neschop.': ['left','left','left','left'],
|
||||
'Léčiva': ['left','left','center','left'],
|
||||
'SpecVys': ['left','left','left','left'],
|
||||
'Platby': ['left','left','left','right','center'],
|
||||
'Ostatní': ['left','left','center','center','left'],
|
||||
}
|
||||
|
||||
for row_i, (datum, cas, zkratka, jmeno_cel, rodcis, poj, top, ostatni, ids_by_typ, ids_ostatni, text_dekurzu) in enumerate(rows, start=2):
|
||||
fill_d = r_fill[row_i % 2]
|
||||
for col_i in range(1, len(nazvy_d) + 1):
|
||||
ws_d.cell(row=row_i, column=col_i).fill = fill_d
|
||||
ws_d.cell(row=row_i, column=col_i).border = ohraniceni
|
||||
|
||||
ws_d.cell(row=row_i, column=1, value=datum).number_format = 'DD.MM.YYYY'
|
||||
ws_d.cell(row=row_i, column=2, value=str(cas)[:5] if cas else '').alignment = Alignment(horizontal='center')
|
||||
ws_d.cell(row=row_i, column=3, value=zkratka or '').alignment = Alignment(horizontal='center')
|
||||
ws_d.cell(row=row_i, column=4, value=jmeno_cel)
|
||||
# Sloupec 5 – Rodné číslo jako hyperlink na list "Text dekurzu"
|
||||
hyperlink_cell(ws_d, row_i, 5, 'Text dekurzu', row_ptr_text, rodcis or '', 'DCE6F1')
|
||||
ws_d.cell(row=row_i, column=6, value=poj or '').alignment = Alignment(horizontal='center')
|
||||
|
||||
# Sloupec G – HodVyk (součet bodů všech výkonů dekurzu)
|
||||
hodvyk = sum((vyka_det.get(rid, ('', '', 0))[2] or 0) for rid in ids_by_typ['VykA'])
|
||||
cell_hv = ws_d.cell(row=row_i, column=7, value=hodvyk if hodvyk else '')
|
||||
cell_hv.alignment = Alignment(horizontal='center')
|
||||
|
||||
for col_off, (typ, pocet) in enumerate(zip(TOP_TYPY, top)):
|
||||
col_i = 8 + col_off
|
||||
if pocet == 0:
|
||||
continue
|
||||
nazev_listu = next((n for n, t, *_ in LISTY if t == typ), None)
|
||||
if nazev_listu and ids_by_typ[typ]:
|
||||
_, barva_ll = BARVY_LISTU[nazev_listu]
|
||||
# Pro výkony (VykA) přidej kódy do závorky
|
||||
if typ == 'VykA':
|
||||
kody = [str(vyka_det.get(rid, ('',))[0] or '').strip() for rid in ids_by_typ[typ]]
|
||||
kody_str = ', '.join(k for k in kody if k)
|
||||
display_text = f"{pocet} ({kody_str})" if kody_str else pocet
|
||||
else:
|
||||
display_text = pocet
|
||||
hyperlink_cell(ws_d, row_i, col_i, nazev_listu, row_ptr[nazev_listu], display_text, barva_ll[1:] if len(barva_ll) > 6 else 'DCE6F1')
|
||||
ws_det = ws_listy[nazev_listu]
|
||||
barva_hl, barva_r = BARVY_LISTU[nazev_listu]
|
||||
for rid in ids_by_typ[typ]:
|
||||
hodnoty = get_det_hodnoty(typ, rid, datum, jmeno_cel)
|
||||
zarovnani_l = ZAROVNANI.get(nazev_listu, ['left']*10)
|
||||
zapis_radek(ws_det, row_ptr[nazev_listu], hodnoty, zarovnani_l, barva_r)
|
||||
row_ptr[nazev_listu] += 1
|
||||
else:
|
||||
ws_d.cell(row=row_i, column=col_i, value=pocet).alignment = Alignment(horizontal='center')
|
||||
|
||||
if ostatni:
|
||||
ws_det = ws_listy['Ostatní']
|
||||
barva_hl, barva_r = BARVY_LISTU['Ostatní']
|
||||
hyperlink_cell(ws_d, row_i, 18, 'Ostatní', row_ptr['Ostatní'], ostatni, barva_r)
|
||||
for typ, rid, nazev in ids_ostatni:
|
||||
zapis_radek(ws_det, row_ptr['Ostatní'],
|
||||
[datum, jmeno_cel, typ, rid, nazev],
|
||||
ZAROVNANI['Ostatní'], barva_r)
|
||||
row_ptr['Ostatní'] += 1
|
||||
|
||||
# Zápis do listu "Text dekurzu"
|
||||
fill_t = r_fill[row_ptr_text % 2]
|
||||
for col_i in range(1, 7):
|
||||
ws_text.cell(row=row_ptr_text, column=col_i).fill = fill_t
|
||||
ws_text.cell(row=row_ptr_text, column=col_i).border = ohraniceni
|
||||
c1 = ws_text.cell(row=row_ptr_text, column=1, value=datum)
|
||||
c1.number_format = 'DD.MM.YYYY'
|
||||
c1.alignment = Alignment(horizontal='left', vertical='center')
|
||||
ws_text.cell(row=row_ptr_text, column=2, value=str(cas)[:5] if cas else '').alignment = Alignment(horizontal='center', vertical='center')
|
||||
ws_text.cell(row=row_ptr_text, column=3, value=zkratka or '').alignment = Alignment(horizontal='center', vertical='center')
|
||||
ws_text.cell(row=row_ptr_text, column=4, value=jmeno_cel).alignment = Alignment(horizontal='left', vertical='center')
|
||||
ws_text.cell(row=row_ptr_text, column=5, value=rodcis or '').alignment = Alignment(horizontal='left', vertical='center')
|
||||
cell_txt = ws_text.cell(row=row_ptr_text, column=6, value=text_dekurzu)
|
||||
cell_txt.alignment = Alignment(horizontal='left', vertical='top', wrap_text=True)
|
||||
row_ptr_text += 1
|
||||
|
||||
# Autofiltr na detailních listech
|
||||
for nazev, *_ in LISTY:
|
||||
ws = ws_listy[nazev]
|
||||
max_col = ws.max_column
|
||||
max_row = ws.max_row
|
||||
if max_row > 1:
|
||||
ws.auto_filter.ref = f"A1:{ws.cell(row=1, column=max_col).column_letter}{max_row}"
|
||||
|
||||
# Smazat starý TEST report
|
||||
for stary in glob.glob(os.path.join(VYSTUPNI_ADRESAR, f'* {NAZEV_REPORTU}.xlsx')):
|
||||
os.remove(stary)
|
||||
print(f"Smazán: {stary}")
|
||||
|
||||
# Uložit nový
|
||||
os.makedirs(VYSTUPNI_ADRESAR, exist_ok=True)
|
||||
casova_znacka = datetime.now().strftime('%Y-%m-%d %H-%M-%S')
|
||||
vystup = os.path.join(VYSTUPNI_ADRESAR, f'{casova_znacka} {NAZEV_REPORTU}.xlsx')
|
||||
wb.save(vystup)
|
||||
print(f"Uloženo: {vystup}")
|
||||
for nazev, *_ in LISTY:
|
||||
print(f" {nazev}: {row_ptr[nazev]-2} řádků")
|
||||
@@ -0,0 +1,357 @@
|
||||
import sys, io, re, os, glob
|
||||
from datetime import date, datetime
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
import fdb
|
||||
import openpyxl
|
||||
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
||||
|
||||
VYSTUPNI_ADRESAR = r'z:\Dropbox\Ordinace\Reporty'
|
||||
NAZEV_REPORTU = 'Dekurzy'
|
||||
DATUM_OD = '2025-01-01'
|
||||
DATUM_DO = date.today().strftime('%Y-%m-%d')
|
||||
|
||||
|
||||
conn = fdb.connect(
|
||||
host='192.168.1.10', database=r'm:\MEDICUS\data\medicus.FDB',
|
||||
user='sysdba', password='masterkey',charset='WIN1250')
|
||||
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute(f"""
|
||||
SELECT d.DATUM, d.CAS, u.ZKRATKA, k.PRIJMENI, k.JMENO, k.RODCIS, k.POJ, d.DEKURS
|
||||
FROM DEKURS d
|
||||
JOIN KAR k ON k.IDPAC = d.IDPAC
|
||||
LEFT JOIN UZIVATEL u ON u.IDUZI = d.IDUZI
|
||||
WHERE d.DATUM >= '{DATUM_OD}' AND d.DATUM <= '{DATUM_DO}'
|
||||
ORDER BY d.DATUM DESC, d.CAS DESC, k.PRIJMENI, k.JMENO
|
||||
""")
|
||||
raw_rows = cur.fetchall()
|
||||
|
||||
TOP_TYPY = ['VykA', 'Rec', 'Files', 'MEDLAB', 'Lab', 'Ock', 'Nes', 'Lec', 'SpecVys', 'PlaPac']
|
||||
|
||||
def rtf_na_text(rtf):
|
||||
"""Jednoduchý převod RTF na čistý text."""
|
||||
# Odstraň info blok (bookmarky apod.)
|
||||
text = re.sub(r'\{\\info.*?\}', '', rtf, flags=re.DOTALL)
|
||||
# Odstraň ostatní skupiny v {} rekurzivně (fonty, barvy apod.)
|
||||
for _ in range(6):
|
||||
text = re.sub(r'\{[^{}]*\}', '', text)
|
||||
# Nový řádek za \par \line
|
||||
text = re.sub(r'\\par\b\s*', '\n', text)
|
||||
text = re.sub(r'\\line\b\s*', '\n', text)
|
||||
# Dekóduj RTF hex escape sekvence (\'xx) jako cp1250 → správná čeština
|
||||
def decode_hex(m):
|
||||
try:
|
||||
return bytes.fromhex(m.group(1)).decode('cp1250')
|
||||
except Exception:
|
||||
return ''
|
||||
text = re.sub(r"\\'([0-9a-fA-F]{2})", decode_hex, text)
|
||||
# Odstraň ostatní RTF příkazy
|
||||
text = re.sub(r'\\[a-zA-Z]+\-?[0-9]*\s?', '', text)
|
||||
text = re.sub(r'[{}\\]', '', text)
|
||||
# Vyčisti prázdné řádky a whitespace
|
||||
lines = [l.strip() for l in text.splitlines()]
|
||||
lines = [l for l in lines if l]
|
||||
return '\n'.join(lines)
|
||||
|
||||
# Parse dekurzů
|
||||
rows = []
|
||||
for datum, cas, zkratka, prijmeni, jmeno, rodcis, poj, dekurs_blob in raw_rows:
|
||||
if hasattr(dekurs_blob, 'read'):
|
||||
rtf = dekurs_blob.read()
|
||||
dekurs_blob.close()
|
||||
else:
|
||||
rtf = dekurs_blob or ''
|
||||
pocty = {}
|
||||
ids_by_typ = {t: [] for t in TOP_TYPY}
|
||||
ids_ostatni = []
|
||||
for nazev, typ, rid in re.findall(r'"([^"]+)","([A-Za-z]+):(\d+)"', rtf):
|
||||
pocty[typ] = pocty.get(typ, 0) + 1
|
||||
if typ in ids_by_typ:
|
||||
ids_by_typ[typ].append(int(rid))
|
||||
else:
|
||||
ids_ostatni.append((typ, int(rid), nazev))
|
||||
top = [pocty.get(t, 0) for t in TOP_TYPY]
|
||||
ostatni = sum(v for k, v in pocty.items() if k not in TOP_TYPY)
|
||||
iniciala = jmeno[0] + '.' if jmeno and jmeno.strip() else ''
|
||||
jmeno_cel = f"{prijmeni.strip()}, {iniciala}" if prijmeni else iniciala
|
||||
text_dekurzu = rtf_na_text(rtf)
|
||||
rows.append((datum, cas, zkratka, jmeno_cel, rodcis, poj, top, ostatni, ids_by_typ, ids_ostatni, text_dekurzu))
|
||||
|
||||
# ── Načtení detailů z DB ────────────────────────────────────────────────────
|
||||
def fetch_details(cur, table, pk, id_col, fields, ids):
|
||||
if not ids:
|
||||
return {}
|
||||
result = {}
|
||||
batch_size = 1000
|
||||
for i in range(0, len(ids), batch_size):
|
||||
batch = ids[i:i+batch_size]
|
||||
ph = ','.join('?' * len(batch))
|
||||
cur.execute(f"SELECT {pk}, {','.join(fields)} FROM {table} WHERE {id_col} IN ({ph})", batch)
|
||||
for row in cur.fetchall():
|
||||
result[row[0]] = row[1:]
|
||||
return result
|
||||
|
||||
def get_ids(rows, typ):
|
||||
return list({rid for *_, ids_by_typ, _, _text in rows for rid in ids_by_typ[typ]})
|
||||
|
||||
rec_det = fetch_details(cur, 'RECEPT', 'ID', 'ID', ['LEK','DSIG'], get_ids(rows,'Rec'))
|
||||
vyka_det = fetch_details(cur, 'DOKLADD', 'ID', 'ID', ['KOD','DDGN','BODY'], get_ids(rows,'VykA'))
|
||||
files_det = fetch_details(cur, 'FILES', 'ID', 'ID', ['FILENAME','DATUM'], get_ids(rows,'Files'))
|
||||
medlab_det = fetch_details(cur, 'HISTDOC', 'ID', 'ID', ['DATUM','TYP'], get_ids(rows,'MEDLAB'))
|
||||
lab_det = fetch_details(cur, 'LABVH', 'IDVH', 'IDVH', ['DATUM','CISLO'], get_ids(rows,'Lab'))
|
||||
ock_det = fetch_details(cur, 'OCKZAZ', 'ID', 'ID', ['DATUM','LATKA'], get_ids(rows,'Ock'))
|
||||
nes_det = fetch_details(cur, 'NES', 'ID', 'ID', ['ZACNES','KONNES'], get_ids(rows,'Nes'))
|
||||
lec_det = fetch_details(cur, 'LECD', 'ID', 'ID', ['KOD','DATOSE'], get_ids(rows,'Lec'))
|
||||
spec_det = fetch_details(cur, 'SPECVYS', 'IDSPECVYS','IDSPECVYS',['TYP','DATUM'], get_ids(rows,'SpecVys'))
|
||||
pla_det = fetch_details(cur, 'PLA', 'IDPLA', 'IDPLA', ['DATUM','CENA','DOKLAD'], get_ids(rows,'PlaPac'))
|
||||
|
||||
conn.close()
|
||||
print(f"Načteno {len(rows)} dekurzů")
|
||||
|
||||
# ── Styly ──────────────────────────────────────────────────────────────────
|
||||
tenka_cara = Side(style='thin', color='AAAAAA')
|
||||
ohraniceni = Border(left=tenka_cara, right=tenka_cara, top=tenka_cara, bottom=tenka_cara)
|
||||
hl_font = Font(bold=True, color="FFFFFF")
|
||||
hl_fill = PatternFill("solid", fgColor="2E75B6")
|
||||
r_fill = [PatternFill("solid", fgColor="FFFFFF"), PatternFill("solid", fgColor="DCE6F1")]
|
||||
|
||||
BARVY_LISTU = {
|
||||
'Recepty': ('1F6B33', 'E2EFDA'),
|
||||
'Výkony': ('2E4057', 'D6E4F0'),
|
||||
'Soubory': ('7B3F00', 'FAE5D3'),
|
||||
'Žádanky': ('4A235A', 'F5EEF8'),
|
||||
'Lab výsl.': ('145A32', 'D5F5E3'),
|
||||
'Očkování': ('7E5109', 'FDEBD0'),
|
||||
'Neschop.': ('922B21', 'FADBD8'),
|
||||
'Léčiva': ('1A5276', 'D6EAF8'),
|
||||
'SpecVys': ('0B5345', 'D1F2EB'),
|
||||
'Platby': ('4D5656', 'EAECEE'),
|
||||
'Ostatní': ('2C3E50', 'EBF5FB'),
|
||||
}
|
||||
|
||||
def zapis_hlavicku(ws, sloupce, sirky, barva_hex):
|
||||
hl_fill_l = PatternFill("solid", fgColor=barva_hex)
|
||||
for col, (nazev, sirka) in enumerate(zip(sloupce, sirky), start=1):
|
||||
cell = ws.cell(row=1, column=col, value=nazev)
|
||||
cell.font = hl_font
|
||||
cell.fill = hl_fill_l
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
cell.border = ohraniceni
|
||||
ws.column_dimensions[cell.column_letter].width = sirka
|
||||
|
||||
def zapis_radek(ws, row_i, hodnoty, zarovnani, barva_hex):
|
||||
fill = PatternFill("solid", fgColor="FFFFFF") if row_i % 2 == 0 \
|
||||
else PatternFill("solid", fgColor=barva_hex)
|
||||
for col_i, (val, align) in enumerate(zip(hodnoty, zarovnani), start=1):
|
||||
cell = ws.cell(row=row_i, column=col_i, value=val)
|
||||
cell.fill = fill
|
||||
cell.border = ohraniceni
|
||||
cell.alignment = Alignment(horizontal=align)
|
||||
if col_i == 1 and isinstance(val, __import__('datetime').date):
|
||||
cell.number_format = 'DD.MM.YYYY'
|
||||
|
||||
def hyperlink_cell(ws, row_i, col_i, cil_list, cil_radek, text, barva_hex):
|
||||
fill = PatternFill("solid", fgColor="FFFFFF") if row_i % 2 == 0 \
|
||||
else PatternFill("solid", fgColor=barva_hex)
|
||||
cell = ws.cell(row=row_i, column=col_i)
|
||||
cell.value = str(text)
|
||||
# Název listu s mezerou musí být v apostrofech
|
||||
sheet_ref = f"'{cil_list}'" if ' ' in cil_list else cil_list
|
||||
cell.hyperlink = f'#{sheet_ref}!A{cil_radek}'
|
||||
cell.font = Font(color="0000FF", underline='single')
|
||||
cell.fill = fill
|
||||
cell.border = ohraniceni
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
|
||||
# ── Workbook ───────────────────────────────────────────────────────────────
|
||||
wb = openpyxl.Workbook()
|
||||
|
||||
LISTY = [
|
||||
('Recepty', 'Rec', rec_det, ['Datum','Jméno','Recept','Dávkování'], [12,25,25,12], None),
|
||||
('Výkony', 'VykA', vyka_det, ['Datum','Jméno','Kód výkonu','Diagnóza','Body'], [12,25,14,10,8], None),
|
||||
('Soubory', 'Files', files_det, ['Datum','Jméno','Soubor','Datum souboru'], [12,25,35,14], None),
|
||||
('Žádanky', 'MEDLAB', medlab_det, ['Datum','Jméno','Typ'], [12,25,15], None),
|
||||
('Lab výsl.', 'Lab', lab_det, ['Datum','Jméno','Číslo'], [12,25,20], None),
|
||||
('Očkování', 'Ock', ock_det, ['Datum','Jméno','Datum očkování','Vakcína'], [12,25,14,30], None),
|
||||
('Neschop.', 'Nes', nes_det, ['Datum','Jméno','Od','Do'], [12,25,12,12], None),
|
||||
('Léčiva', 'Lec', lec_det, ['Datum','Jméno','Kód','Datum výkonu'], [12,25,12,14], None),
|
||||
('SpecVys', 'SpecVys', spec_det, ['Datum','Jméno','Typ vyšetření','Datum vyšetření'], [12,25,25,14], None),
|
||||
('Platby', 'PlaPac', pla_det, ['Datum','Jméno','Datum platby','Částka','Doklad'], [12,25,14,12,15], None),
|
||||
('Ostatní', None, None, ['Datum','Jméno','Typ','ID','Název'], [12,25,12,10,30], None),
|
||||
]
|
||||
|
||||
ws_d = wb.active
|
||||
ws_d.title = "Dekurz"
|
||||
|
||||
# List "Text dekurzu" hned za Dekurzem
|
||||
ws_text = wb.create_sheet("Text dekurzu")
|
||||
|
||||
ws_listy = {}
|
||||
for nazev, *_ in LISTY:
|
||||
ws_listy[nazev] = wb.create_sheet(nazev)
|
||||
|
||||
for nazev, typ, det, sloupce, sirky, _ in LISTY:
|
||||
barva_hl, _ = BARVY_LISTU[nazev]
|
||||
zapis_hlavicku(ws_listy[nazev], sloupce, sirky, barva_hl)
|
||||
ws_listy[nazev].freeze_panes = 'A2'
|
||||
|
||||
# Záhlaví listu "Text dekurzu"
|
||||
zapis_hlavicku(ws_text,
|
||||
['Datum', 'Čas', 'Lékař', 'Jméno', 'Rodné číslo', 'Text dekurzu'],
|
||||
[12, 8, 8, 25, 14, 100],
|
||||
'2E75B6')
|
||||
ws_text.freeze_panes = 'A2'
|
||||
row_ptr_text = 2
|
||||
|
||||
# Zobrazované názvy sloupců pro TOP_TYPY (stejné pořadí)
|
||||
NAZVY_TYPY = ['VykA', 'Rec', 'Files', 'Žádanky', 'Lab výsl.', 'Ock', 'Nes', 'Lec', 'SpecVys', 'PlaPac']
|
||||
|
||||
# Záhlaví Dekurz – sloupce A–R
|
||||
nazvy_d = ['Datum', 'Čas', 'Lékař', 'Jméno', 'Rodné číslo', 'Pojišťovna'] + ['HodVyk'] + NAZVY_TYPY + ['Ostatní']
|
||||
sirky_d = [12, 8, 8, 25, 14, 12 ] + [10] + [38, 8, 8, 8, 8, 8, 8, 8, 8, 8] + [8]
|
||||
zapis_hlavicku(ws_d, nazvy_d, sirky_d, '2E75B6')
|
||||
ws_d.freeze_panes = 'A2'
|
||||
ws_d.auto_filter.ref = f"A1:R{len(rows)+1}"
|
||||
|
||||
row_ptr = {nazev: 2 for nazev, *_ in LISTY}
|
||||
|
||||
# ── Plnění dat ─────────────────────────────────────────────────────────────
|
||||
def get_det_hodnoty(typ, rid, datum, jmeno_cel):
|
||||
if typ == 'Rec':
|
||||
d = rec_det.get(rid, ('', ''))
|
||||
return [datum, jmeno_cel, d[0] or '', d[1] or '']
|
||||
elif typ == 'VykA':
|
||||
d = vyka_det.get(rid, ('', '', 0))
|
||||
return [datum, jmeno_cel, d[0] or '', (d[1] or '').strip(), d[2] or 0]
|
||||
elif typ == 'Files':
|
||||
d = files_det.get(rid, ('', ''))
|
||||
return [datum, jmeno_cel, d[0] or '', d[1]]
|
||||
elif typ == 'MEDLAB':
|
||||
d = medlab_det.get(rid, ('', ''))
|
||||
return [datum, jmeno_cel, d[1] or '']
|
||||
elif typ == 'Lab':
|
||||
d = lab_det.get(rid, ('', ''))
|
||||
return [datum, jmeno_cel, d[1] or '']
|
||||
elif typ == 'Ock':
|
||||
d = ock_det.get(rid, ('', ''))
|
||||
return [datum, jmeno_cel, d[0], d[1] or '']
|
||||
elif typ == 'Nes':
|
||||
d = nes_det.get(rid, ('', ''))
|
||||
return [datum, jmeno_cel, d[0], d[1]]
|
||||
elif typ == 'Lec':
|
||||
d = lec_det.get(rid, ('', ''))
|
||||
return [datum, jmeno_cel, d[0] or '', d[1]]
|
||||
elif typ == 'SpecVys':
|
||||
d = spec_det.get(rid, ('', ''))
|
||||
return [datum, jmeno_cel, d[0] or '', d[1]]
|
||||
elif typ == 'PlaPac':
|
||||
d = pla_det.get(rid, ('', '', ''))
|
||||
return [datum, jmeno_cel, d[0], d[1], d[2] or '']
|
||||
return []
|
||||
|
||||
ZAROVNANI = {
|
||||
'Recepty': ['left','left','left','center'],
|
||||
'Výkony': ['left','left','center','center','right'],
|
||||
'Soubory': ['left','left','left','left'],
|
||||
'Žádanky': ['left','left','center'],
|
||||
'Lab výsl.': ['left','left','center'],
|
||||
'Očkování': ['left','left','left','left'],
|
||||
'Neschop.': ['left','left','left','left'],
|
||||
'Léčiva': ['left','left','center','left'],
|
||||
'SpecVys': ['left','left','left','left'],
|
||||
'Platby': ['left','left','left','right','center'],
|
||||
'Ostatní': ['left','left','center','center','left'],
|
||||
}
|
||||
|
||||
for row_i, (datum, cas, zkratka, jmeno_cel, rodcis, poj, top, ostatni, ids_by_typ, ids_ostatni, text_dekurzu) in enumerate(rows, start=2):
|
||||
fill_d = r_fill[row_i % 2]
|
||||
for col_i in range(1, len(nazvy_d) + 1):
|
||||
ws_d.cell(row=row_i, column=col_i).fill = fill_d
|
||||
ws_d.cell(row=row_i, column=col_i).border = ohraniceni
|
||||
|
||||
ws_d.cell(row=row_i, column=1, value=datum).number_format = 'DD.MM.YYYY'
|
||||
ws_d.cell(row=row_i, column=2, value=str(cas)[:5] if cas else '').alignment = Alignment(horizontal='center')
|
||||
ws_d.cell(row=row_i, column=3, value=zkratka or '').alignment = Alignment(horizontal='center')
|
||||
ws_d.cell(row=row_i, column=4, value=jmeno_cel)
|
||||
# Sloupec 5 – Rodné číslo jako hyperlink na list "Text dekurzu"
|
||||
hyperlink_cell(ws_d, row_i, 5, 'Text dekurzu', row_ptr_text, rodcis or '', 'DCE6F1')
|
||||
ws_d.cell(row=row_i, column=6, value=poj or '').alignment = Alignment(horizontal='center')
|
||||
|
||||
# Sloupec G – HodVyk (součet bodů všech výkonů dekurzu)
|
||||
hodvyk = sum((vyka_det.get(rid, ('', '', 0))[2] or 0) for rid in ids_by_typ['VykA'])
|
||||
cell_hv = ws_d.cell(row=row_i, column=7, value=hodvyk if hodvyk else '')
|
||||
cell_hv.alignment = Alignment(horizontal='center')
|
||||
|
||||
for col_off, (typ, pocet) in enumerate(zip(TOP_TYPY, top)):
|
||||
col_i = 8 + col_off
|
||||
if pocet == 0:
|
||||
continue
|
||||
nazev_listu = next((n for n, t, *_ in LISTY if t == typ), None)
|
||||
if nazev_listu and ids_by_typ[typ]:
|
||||
_, barva_ll = BARVY_LISTU[nazev_listu]
|
||||
# Pro výkony (VykA) přidej kódy do závorky
|
||||
if typ == 'VykA':
|
||||
kody = [str(vyka_det.get(rid, ('',))[0] or '').strip() for rid in ids_by_typ[typ]]
|
||||
kody_str = ', '.join(k for k in kody if k)
|
||||
display_text = f"{pocet} ({kody_str})" if kody_str else pocet
|
||||
else:
|
||||
display_text = pocet
|
||||
hyperlink_cell(ws_d, row_i, col_i, nazev_listu, row_ptr[nazev_listu], display_text, barva_ll[1:] if len(barva_ll) > 6 else 'DCE6F1')
|
||||
ws_det = ws_listy[nazev_listu]
|
||||
barva_hl, barva_r = BARVY_LISTU[nazev_listu]
|
||||
for rid in ids_by_typ[typ]:
|
||||
hodnoty = get_det_hodnoty(typ, rid, datum, jmeno_cel)
|
||||
zarovnani_l = ZAROVNANI.get(nazev_listu, ['left']*10)
|
||||
zapis_radek(ws_det, row_ptr[nazev_listu], hodnoty, zarovnani_l, barva_r)
|
||||
row_ptr[nazev_listu] += 1
|
||||
else:
|
||||
ws_d.cell(row=row_i, column=col_i, value=pocet).alignment = Alignment(horizontal='center')
|
||||
|
||||
if ostatni:
|
||||
ws_det = ws_listy['Ostatní']
|
||||
barva_hl, barva_r = BARVY_LISTU['Ostatní']
|
||||
hyperlink_cell(ws_d, row_i, 18, 'Ostatní', row_ptr['Ostatní'], ostatni, barva_r)
|
||||
for typ, rid, nazev in ids_ostatni:
|
||||
zapis_radek(ws_det, row_ptr['Ostatní'],
|
||||
[datum, jmeno_cel, typ, rid, nazev],
|
||||
ZAROVNANI['Ostatní'], barva_r)
|
||||
row_ptr['Ostatní'] += 1
|
||||
|
||||
# Zápis do listu "Text dekurzu"
|
||||
fill_t = r_fill[row_ptr_text % 2]
|
||||
for col_i in range(1, 7):
|
||||
ws_text.cell(row=row_ptr_text, column=col_i).fill = fill_t
|
||||
ws_text.cell(row=row_ptr_text, column=col_i).border = ohraniceni
|
||||
c1 = ws_text.cell(row=row_ptr_text, column=1, value=datum)
|
||||
c1.number_format = 'DD.MM.YYYY'
|
||||
c1.alignment = Alignment(horizontal='left', vertical='center')
|
||||
ws_text.cell(row=row_ptr_text, column=2, value=str(cas)[:5] if cas else '').alignment = Alignment(horizontal='center', vertical='center')
|
||||
ws_text.cell(row=row_ptr_text, column=3, value=zkratka or '').alignment = Alignment(horizontal='center', vertical='center')
|
||||
ws_text.cell(row=row_ptr_text, column=4, value=jmeno_cel).alignment = Alignment(horizontal='left', vertical='center')
|
||||
ws_text.cell(row=row_ptr_text, column=5, value=rodcis or '').alignment = Alignment(horizontal='left', vertical='center')
|
||||
cell_txt = ws_text.cell(row=row_ptr_text, column=6, value=text_dekurzu)
|
||||
cell_txt.alignment = Alignment(horizontal='left', vertical='top', wrap_text=True)
|
||||
row_ptr_text += 1
|
||||
|
||||
# Autofiltr na detailních listech
|
||||
for nazev, *_ in LISTY:
|
||||
ws = ws_listy[nazev]
|
||||
max_col = ws.max_column
|
||||
max_row = ws.max_row
|
||||
if max_row > 1:
|
||||
ws.auto_filter.ref = f"A1:{ws.cell(row=1, column=max_col).column_letter}{max_row}"
|
||||
|
||||
# Smazat starý report
|
||||
for stary in glob.glob(os.path.join(VYSTUPNI_ADRESAR, f'* {NAZEV_REPORTU}.xlsx')):
|
||||
os.remove(stary)
|
||||
print(f"Smazán: {stary}")
|
||||
|
||||
# Uložit nový
|
||||
os.makedirs(VYSTUPNI_ADRESAR, exist_ok=True)
|
||||
casova_znacka = datetime.now().strftime('%Y-%m-%d %H-%M-%S')
|
||||
vystup = os.path.join(VYSTUPNI_ADRESAR, f'{casova_znacka} {NAZEV_REPORTU}.xlsx')
|
||||
wb.save(vystup)
|
||||
print(f"Uloženo: {vystup}")
|
||||
for nazev, *_ in LISTY:
|
||||
print(f" {nazev}: {row_ptr[nazev]-2} řádků")
|
||||
@@ -0,0 +1,274 @@
|
||||
import firebirdsql as fdb
|
||||
import openpyxl
|
||||
from openpyxl.styles import Font, PatternFill, Alignment
|
||||
from openpyxl.utils import get_column_letter
|
||||
from datetime import datetime
|
||||
import os
|
||||
import sys
|
||||
|
||||
# --- Připojení ---
|
||||
conn = fdb.connect(
|
||||
host='192.168.1.10',
|
||||
port=3050,
|
||||
database=r'm:\Medicus\data\MEDICUS.FDB',
|
||||
user='SYSDBA', password='masterkey', charset='WIN1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
# --- Výstupní soubor ---
|
||||
output_dir = r'z:\Dropbox\Ordinace\Reporty'
|
||||
now = datetime.now()
|
||||
filename = now.strftime('%Y-%m-%d %H-%M-%S') + ' faktury.xlsx'
|
||||
output_path = os.path.join(output_dir, filename)
|
||||
|
||||
# --- Smazání předchozích verzí ---
|
||||
for f in os.listdir(output_dir):
|
||||
if f.endswith(' faktury.xlsx'):
|
||||
os.remove(os.path.join(output_dir, f))
|
||||
|
||||
wb = openpyxl.Workbook()
|
||||
|
||||
# =====================
|
||||
# Pomocné funkce
|
||||
# =====================
|
||||
|
||||
HEADER_FILL = PatternFill('solid', fgColor='2F5496')
|
||||
HEADER_FONT = Font(bold=True, color='FFFFFF')
|
||||
LINK_FONT = Font(color='0563C1', underline='single')
|
||||
ZEBRA_FILL = PatternFill('solid', fgColor='DCE6F1')
|
||||
|
||||
def style_header(ws):
|
||||
for cell in ws[1]:
|
||||
cell.fill = HEADER_FILL
|
||||
cell.font = HEADER_FONT
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
|
||||
def autofit(ws):
|
||||
for col in ws.columns:
|
||||
max_len = max((len(str(cell.value)) if cell.value is not None else 0) for cell in col)
|
||||
ws.column_dimensions[get_column_letter(col[0].column)].width = min(max_len + 2, 40)
|
||||
|
||||
def fmt(val):
|
||||
if val is None:
|
||||
return ''
|
||||
return val
|
||||
|
||||
# =====================
|
||||
# List 1 – FAK
|
||||
# =====================
|
||||
|
||||
ws1 = wb.active
|
||||
ws1.title = 'FAK'
|
||||
|
||||
cur.execute('''
|
||||
SELECT
|
||||
ID, CISFAK, POJ, DATUMOD, DATUMDO, DATVYS, DATODE,
|
||||
VYKONY, KAPITACE, ZALOHA, CENA, ZAPLACENO, ZUM, HOSPAUSAL,
|
||||
PROPLACENO, SPLAT, DRUH, TYP, ROK, OBDOB,
|
||||
NAZFAK, POZFAK, OBDFAK,
|
||||
ICO, BANKA, UCET,
|
||||
ODJMENO, ODULICE, ODMISTO, ODPSC,
|
||||
PLNAZEV, PLULICE, PLMISTO, PLPSC,
|
||||
ICZ, ICZ1, IDICZ, PORCISLO, DRUHPOJ, POZNAMKA
|
||||
FROM FAK
|
||||
ORDER BY ID DESC
|
||||
''')
|
||||
fak_cols = [d[0] for d in cur.description]
|
||||
fak_rows = cur.fetchall()
|
||||
|
||||
ws1.append(fak_cols)
|
||||
for i, row in enumerate(fak_rows, start=2):
|
||||
ws1.append([fmt(v) for v in row])
|
||||
if i % 2 == 0:
|
||||
for cell in ws1[i]:
|
||||
cell.fill = ZEBRA_FILL
|
||||
|
||||
style_header(ws1)
|
||||
ws1.freeze_panes = 'A2'
|
||||
autofit(ws1)
|
||||
|
||||
# =====================
|
||||
# List 2 – FAKDET
|
||||
# =====================
|
||||
|
||||
ws2 = wb.create_sheet('FAKDET')
|
||||
|
||||
cur.execute('''
|
||||
SELECT
|
||||
fd.ID, fd.IDFAK,
|
||||
f.CISFAK, f.POJ, f.DATUMOD, f.DATUMDO, f.ROK,
|
||||
fd.ICP, fd.ODB, fd.IDUZI,
|
||||
u.PRIJMENI, u.JMENO,
|
||||
fd.CENAVYK, fd.CENALEC, fd.CENAKAP
|
||||
FROM FAKDET fd
|
||||
JOIN FAK f ON f.ID = fd.IDFAK
|
||||
LEFT JOIN UZIVATEL u ON u.IDUZI = fd.IDUZI
|
||||
ORDER BY fd.IDFAK DESC, fd.ID DESC
|
||||
''')
|
||||
det_cols = [d[0] for d in cur.description]
|
||||
det_rows = cur.fetchall()
|
||||
|
||||
ws2.append(det_cols)
|
||||
for i, row in enumerate(det_rows, start=2):
|
||||
ws2.append([fmt(v) for v in row])
|
||||
if i % 2 == 0:
|
||||
for cell in ws2[i]:
|
||||
cell.fill = ZEBRA_FILL
|
||||
|
||||
style_header(ws2)
|
||||
ws2.freeze_panes = 'A2'
|
||||
autofit(ws2)
|
||||
|
||||
# =====================
|
||||
# List 3 – PORTAL (krátké sloupce)
|
||||
# =====================
|
||||
|
||||
ws3 = wb.create_sheet('PORTAL')
|
||||
|
||||
cur.execute('''
|
||||
SELECT ID, IDFAK, ODESLANO, CHYBA, STAV, ID_PODANI, IDPODANI, IDCERT,
|
||||
DAVKA_ROK, DAVKA_DISK, DAVKA_IDICZ, DAVKA_DATUMOD, DAVKA_DATUMDO,
|
||||
DAVKA_CASTKA, BB_DAVKA, BB_FAKTURA
|
||||
FROM PORTAL
|
||||
ORDER BY ID DESC
|
||||
''')
|
||||
portal_cols = [d[0] for d in cur.description]
|
||||
portal_rows = cur.fetchall()
|
||||
|
||||
ws3.append(portal_cols)
|
||||
for i, row in enumerate(portal_rows, start=2):
|
||||
ws3.append([fmt(v) for v in row])
|
||||
if i % 2 == 0:
|
||||
for cell in ws3[i]:
|
||||
cell.fill = ZEBRA_FILL
|
||||
|
||||
style_header(ws3)
|
||||
ws3.freeze_panes = 'A2'
|
||||
autofit(ws3)
|
||||
|
||||
# =====================
|
||||
# List 4 – PORTAL_DATA (BLOBy)
|
||||
# =====================
|
||||
|
||||
ws4 = wb.create_sheet('PORTAL_DATA')
|
||||
|
||||
cur.execute('''
|
||||
SELECT ID, ID_PODANI, DAVKA_ROK, DAVKA_DISK, DAVKA_DATUMOD, DAVKA_DATUMDO,
|
||||
KDAVKA, FDAVKA, DATA
|
||||
FROM PORTAL
|
||||
ORDER BY ID DESC
|
||||
''')
|
||||
pdata_rows = cur.fetchall()
|
||||
|
||||
pdata_cols = ['ID', 'ID_PODANI', 'DAVKA_ROK', 'DAVKA_DISK', 'DAVKA_DATUMOD', 'DAVKA_DATUMDO',
|
||||
'KDAVKA', 'FDAVKA', 'DATA']
|
||||
ws4.append(pdata_cols)
|
||||
|
||||
WRAP = Alignment(wrap_text=True, vertical='top')
|
||||
|
||||
# Indexy BLOB sloupců: KDAVKA=6, FDAVKA=7, DATA=8
|
||||
# DATA je XML v win1250, KDAVKA/FDAVKA jsou latin2
|
||||
BLOB_ENCODINGS = {6: 'cp852', 7: 'cp852', 8: 'cp1250'}
|
||||
|
||||
def decode_blob(v, enc):
|
||||
if hasattr(v, 'read'):
|
||||
raw = v.read()
|
||||
else:
|
||||
raw = v
|
||||
if not raw:
|
||||
return ''
|
||||
if isinstance(raw, str):
|
||||
# fdb dekódoval jako win1250 – vrátíme zpět na bytes, pak dekódujeme správně
|
||||
raw = raw.encode('cp1250', errors='replace')
|
||||
return raw.decode(enc, errors='replace')
|
||||
|
||||
for i, row in enumerate(pdata_rows, start=2):
|
||||
out = []
|
||||
for col_idx, v in enumerate(row):
|
||||
if col_idx in BLOB_ENCODINGS:
|
||||
enc = BLOB_ENCODINGS[col_idx]
|
||||
out.append(decode_blob(v, enc))
|
||||
else:
|
||||
out.append(fmt(v))
|
||||
ws4.append(out)
|
||||
if i % 2 == 0:
|
||||
for cell in ws4[i]:
|
||||
cell.fill = ZEBRA_FILL
|
||||
for cell in ws4[i]:
|
||||
cell.alignment = WRAP
|
||||
ws4.row_dimensions[i].height = 80
|
||||
|
||||
style_header(ws4)
|
||||
ws4.freeze_panes = 'A2'
|
||||
|
||||
# Šířky sloupců PORTAL_DATA
|
||||
for col, width in zip(['A','B','C','D','E','F','G','H','I'], [6, 26, 8, 6, 12, 12, 80, 20, 60]):
|
||||
ws4.column_dimensions[col].width = width
|
||||
|
||||
# =====================
|
||||
# Hyperlinky PORTAL ↔ PORTAL_DATA
|
||||
# =====================
|
||||
|
||||
# Mapa: portal_id → řádek v každém listu
|
||||
portal_id_to_ws3_row = {}
|
||||
for i, row in enumerate(portal_rows, start=2):
|
||||
portal_id_to_ws3_row[row[0]] = i # row[0] = ID
|
||||
|
||||
portal_id_to_ws4_row = {}
|
||||
for i, row in enumerate(pdata_rows, start=2):
|
||||
portal_id_to_ws4_row[row[0]] = i # row[0] = ID
|
||||
|
||||
# PORTAL → PORTAL_DATA (sloupec A = ID)
|
||||
for i, row in enumerate(portal_rows, start=2):
|
||||
pid = row[0]
|
||||
cell = ws3.cell(row=i, column=1)
|
||||
if pid in portal_id_to_ws4_row:
|
||||
cell.hyperlink = f'#PORTAL_DATA!A{portal_id_to_ws4_row[pid]}'
|
||||
cell.font = LINK_FONT
|
||||
|
||||
# PORTAL_DATA → PORTAL (sloupec A = ID)
|
||||
for i, row in enumerate(pdata_rows, start=2):
|
||||
pid = row[0]
|
||||
cell = ws4.cell(row=i, column=1)
|
||||
if pid in portal_id_to_ws3_row:
|
||||
cell.hyperlink = f'#PORTAL!A{portal_id_to_ws3_row[pid]}'
|
||||
cell.font = LINK_FONT
|
||||
cell.alignment = WRAP
|
||||
|
||||
# =====================
|
||||
# Hyperlinky FAK → FAKDET
|
||||
# =====================
|
||||
|
||||
# Mapa: IDFAK → první řádek na listu FAKDET (řádek 1 = záhlaví, data od 2)
|
||||
idfak_to_row = {}
|
||||
for i, row in enumerate(det_rows, start=2):
|
||||
idfak = row[1] # IDFAK je druhý sloupec
|
||||
if idfak not in idfak_to_row:
|
||||
idfak_to_row[idfak] = i
|
||||
|
||||
# Přidat sloupec "→FAKDET" jako první sloupec na listu FAK
|
||||
ws1.insert_cols(1)
|
||||
ws1.cell(row=1, column=1, value='FAKDET').fill = HEADER_FILL
|
||||
ws1.cell(row=1, column=1).font = HEADER_FONT
|
||||
ws1.cell(row=1, column=1).alignment = Alignment(horizontal='center')
|
||||
ws1.column_dimensions['A'].width = 9
|
||||
|
||||
for i, row in enumerate(fak_rows, start=2):
|
||||
fak_id = row[0] # ID je první sloupec z DB
|
||||
cell = ws1.cell(row=i, column=1)
|
||||
if fak_id in idfak_to_row:
|
||||
target_row = idfak_to_row[fak_id]
|
||||
cell.value = '>> det'
|
||||
cell.hyperlink = f'#FAKDET!A{target_row}'
|
||||
cell.font = LINK_FONT
|
||||
else:
|
||||
cell.value = ''
|
||||
|
||||
# =====================
|
||||
# Uložení
|
||||
# =====================
|
||||
|
||||
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'))
|
||||
@@ -0,0 +1,281 @@
|
||||
import os,shutil,fdb,time, socket
|
||||
import re,datetime,funkce
|
||||
|
||||
|
||||
def get_medicus_connection():
|
||||
"""
|
||||
Connect to Firebird 'medicus.fdb' depending on computer name.
|
||||
Returns fdb.Connection or raises RuntimeError if unknown or connection fails.
|
||||
"""
|
||||
computer_name = socket.gethostname().upper()
|
||||
try:
|
||||
if computer_name == "Z230":
|
||||
print("Computer name is Z230")
|
||||
cesta = r"u:\dropboxtest\Ordinace\Dokumentace_ke_zpracování"
|
||||
cestazpracovana = r"u:\Dropboxtest\Ordinace\Dokumentace_zpracovaná"
|
||||
return fdb.connect(dsn=r"localhost:c:\medicus 3\data\medicus.fdb", user="SYSDBA", password="masterkey", charset="win1250"),cesta,cestazpracovana
|
||||
elif computer_name == "LEKAR":
|
||||
print("Computer name is LEKAR")
|
||||
cesta = r"z:\dropbox\Ordinace\Dokumentace_ke_zpracování"
|
||||
cestazpracovana = r"z:\Dropbox\Ordinace\Dokumentace_zpracovaná"
|
||||
return fdb.connect(dsn=r"localhost:m:\medicus\data\medicus.fdb", user="SYSDBA", password="masterkey", charset="win1250"),cesta,cestazpracovana
|
||||
elif computer_name in ("SESTRA", "POHODA"):
|
||||
print("Computer name is SESTRA or POHODA")
|
||||
cesta = r"z:\dropbox\Ordinace\Dokumentace_ke_zpracování"
|
||||
cestazpracovana = r"z:\Dropbox\Ordinace\Dokumentace_zpracovaná"
|
||||
return fdb.connect(dsn=r"192.168.1.10:m:\medicus\data\medicus.fdb", user="SYSDBA", password="masterkey", charset="win1250"),cesta,cestazpracovana
|
||||
else:
|
||||
raise RuntimeError(f"❌ Unknown computer name: {computer_name}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error connecting to Medicus on {computer_name}: {e}")
|
||||
raise
|
||||
|
||||
#toto volání současně nadefinuje cesty do dropboxu
|
||||
conn,cesta,cestazpracovana=get_medicus_connection()
|
||||
|
||||
|
||||
def is_encodable_win1250(text: str) -> bool:
|
||||
try:
|
||||
text.encode("cp1250")
|
||||
return True
|
||||
except UnicodeEncodeError:
|
||||
return False
|
||||
|
||||
def make_win1250_safe(text: str) -> str:
|
||||
return text.encode("cp1250", errors="replace").decode("cp1250").replace("?", "_")
|
||||
|
||||
def restore_files_for_import(retezec):
|
||||
drop=r"u:\Dropbox\!!!Days\Downloads Z230\Dokumentace"
|
||||
next=r"u:\NextcloudOrdinace\Dokumentace_ke_zpracování"
|
||||
|
||||
# Check if the directory exists
|
||||
if not os.path.exists(drop):
|
||||
print(f"The directory '{drop}' does not exist.")
|
||||
return
|
||||
|
||||
# Iterate over all files and subdirectories in the directory
|
||||
for item in os.listdir(drop):
|
||||
item_path = os.path.join(drop, item)
|
||||
|
||||
# If it's a file or a symbolic link, delete it
|
||||
if os.path.isfile(item_path) or os.path.islink(item_path):
|
||||
os.unlink(item_path)
|
||||
print(f"Deleted file: {item_path}")
|
||||
|
||||
# If it's a directory, delete it recursively
|
||||
elif os.path.isdir(item_path):
|
||||
shutil.rmtree(item_path)
|
||||
print(f"Deleted directory: {item_path}")
|
||||
|
||||
for item in os.listdir(next):
|
||||
item_path = os.path.join(next, item)
|
||||
# If it's a file finished with PDF, copy it
|
||||
if os.path.isfile(item_path) and item_path.endswith(".pdf") and retezec in item_path:
|
||||
shutil.copy(item_path,os.path.join(drop,item))
|
||||
print(f"Copied file: {item_path}")
|
||||
|
||||
|
||||
def kontrola_rc(rc,connection):
|
||||
cur = connection.cursor()
|
||||
cur.execute("select count(*),idpac from kar where rodcis=? group by idpac",(rc,))
|
||||
row = cur.fetchone()
|
||||
if row:
|
||||
return row[1]
|
||||
else:
|
||||
return False
|
||||
|
||||
def kontrola_struktury(souborname,connection):
|
||||
if souborname.endswith('.pdf'):
|
||||
#kontrola struktury
|
||||
pattern=re.compile(r'(^\d{9,10}) (\d{4}-\d{2}-\d{2}) (\w+, \w.+?) \[(.+?)\] \[(.*?)\]')
|
||||
match=pattern.search(souborname)
|
||||
# print(souborname)
|
||||
vpohode=True
|
||||
if match and len(match.groups())==5:
|
||||
datum=match.group(2)
|
||||
try:
|
||||
datum_object = datetime.datetime.strptime(datum,"%Y-%m-%d").date()
|
||||
# print(datum_object)
|
||||
except:
|
||||
vpohode=False
|
||||
return vpohode
|
||||
cur = connection.cursor()
|
||||
cur.execute("select count(*) from kar where rodcis=?", (match.group(1),))
|
||||
row = cur.fetchone()[0]
|
||||
if row!=1:
|
||||
vpohode = False
|
||||
return vpohode
|
||||
else:
|
||||
vpohode=False
|
||||
return vpohode
|
||||
else:
|
||||
vpohode=False
|
||||
return vpohode
|
||||
return vpohode
|
||||
|
||||
def vrat_info_o_souboru(souborname, connection):
|
||||
pattern = re.compile(r'(^\d{9,10}) (\d{4}-\d{2}-\d{2}) (\w+, \w.+?) \[(.+?)\] \[(.*?)\]')
|
||||
match = pattern.search(souborname)
|
||||
rc = match.group(1)
|
||||
datum = datetime.datetime.strptime(match.group(2), "%Y-%m-%d").date()
|
||||
jmeno = match.group(3)
|
||||
prvnizavorka = match.group(4)
|
||||
druhazavorka = match.group(5)
|
||||
cur=connection.cursor()
|
||||
cur.execute("select idpac from kar where rodcis=?",(rc,))
|
||||
idpac = cur.fetchone()[0]
|
||||
datumsouboru = datetime.datetime.fromtimestamp(os.path.getctime(os.path.join(cesta,souborname)))
|
||||
return (rc,idpac,datum,jmeno,prvnizavorka,druhazavorka,souborname,datumsouboru)
|
||||
|
||||
def prejmenuj_chybny_soubor(souborname,cesta):
|
||||
if souborname[0]!="♥":
|
||||
soubornovy = "♥" + souborname
|
||||
os.rename(os.path.join(cesta,souborname),os.path.join(cesta,soubornovy))
|
||||
|
||||
|
||||
|
||||
# print(kontrola_struktury(ss))
|
||||
# info=vrat_info_o_souboru(ss)
|
||||
# print(kontrola_rc(info[0],conn))
|
||||
|
||||
# restore_files_for_import("")
|
||||
# restore_files_for_import("346204097")
|
||||
|
||||
info=[]
|
||||
for soubor in os.listdir(cesta):
|
||||
plna_cesta = os.path.join(cesta, soubor)
|
||||
if not os.path.isfile(plna_cesta):
|
||||
continue # skip folders or anything that’s not a regular fil
|
||||
|
||||
if not is_encodable_win1250(soubor):
|
||||
safe_name = make_win1250_safe(soubor)
|
||||
novy_plna_cesta = os.path.join(cesta, safe_name)
|
||||
|
||||
print(f"⚠️ Renaming invalid filename:\n {soubor} → {safe_name}")
|
||||
os.rename(plna_cesta, novy_plna_cesta)
|
||||
|
||||
# Update variable for later processing
|
||||
soubor = safe_name
|
||||
plna_cesta = novy_plna_cesta
|
||||
|
||||
print(soubor)
|
||||
if kontrola_struktury(soubor,conn):
|
||||
info.append(vrat_info_o_souboru(soubor,conn))
|
||||
# os.remove(os.path.join(cesta,soubor))
|
||||
else:
|
||||
prejmenuj_chybny_soubor(soubor,cesta)
|
||||
|
||||
info = sorted(info, key=lambda x: (x[0], x[1]))
|
||||
print(info)
|
||||
|
||||
skupiny={}
|
||||
for row in info:
|
||||
skupiny[row[0]]=[]
|
||||
for row in info:
|
||||
skupiny[row[0]].append(row)
|
||||
# print(skupiny)
|
||||
|
||||
# rtf = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks BOOKMARKNAMES }}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
|
||||
# {\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
# {\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs22\f0\ul\fs20\cf1 Odkaz;}}
|
||||
# \uc1\pard\s10\plain\cs20\f0\i\fs20 P\'f8\'edlohy:\par
|
||||
# \pard\s10{\*\bkmkstart 0}\plain\cs22\f0\ul\fs20\cf1 BOOKMARKNAMESTEXT{\*\bkmkend 0}\par
|
||||
# \pard\s10\plain\cs15\f0\fs20 \par
|
||||
# }"""
|
||||
|
||||
rtf = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks BOOKMARKNAMES }}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
|
||||
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
{\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs22\f0\ul\fs20\cf1 Odkaz;}}
|
||||
\uc1\pard\s10\plain\cs20\f0\i\fs20 P\'f8\'edlohy:\par
|
||||
BOOKMARKSTEXT
|
||||
\pard\s10\plain\cs15\f0\fs20 \par
|
||||
}"""
|
||||
|
||||
for key in skupiny.keys():
|
||||
rtf = r"""{\rtf1\ansi\ansicpg1250\uc1\deff0\deflang1029{\info{\bookmarks BOOKMARKNAMES }}{\fonttbl{\f0\fnil\fcharset238 Arial;}{\f5\fnil\fcharset238 Symbol;}}
|
||||
{\colortbl ;\red0\green0\blue255;\red0\green128\blue0;\red0\green0\blue0;}
|
||||
{\stylesheet{\s10\fi0\li0\ql\ri0\sb0\sa0 Vlevo;}{\*\cs15\f0\fs20 Norm\'e1ln\'ed;}{\*\cs20\f0\i\fs20 Z\'e1hlav\'ed;}{\*\cs22\f0\ul\fs20\cf1 Odkaz;}}
|
||||
\uc1\pard\s10\plain\cs20\f0\i\fs20 Vlo\'9eena skenovan\'e1 dokumentace:\par
|
||||
BOOKMARKSTEXT
|
||||
\pard\s10\plain\cs15\f0\fs20\par
|
||||
}"""
|
||||
|
||||
|
||||
# if key=="8257300425": #346204097
|
||||
if True:
|
||||
prvnibookmark=True
|
||||
print(key,len(skupiny[key]))
|
||||
cislo=9
|
||||
poradi=0
|
||||
bookmark=""
|
||||
bookmarks=""
|
||||
for row in skupiny[key]:
|
||||
# print(row)
|
||||
pacid=row[1]
|
||||
filename=row[6]
|
||||
fileid=funkce.zapis_file(vstupconnection=conn, idpac=row[1],
|
||||
cesta=cesta, souborname=row[6], prvnizavorka=row[4],
|
||||
soubordate=row[2], souborfiledate=row[7], poznamka=row[5])
|
||||
|
||||
for attempt in range(3):
|
||||
try:
|
||||
# Replace this with the command that might raise an error
|
||||
if not os.path.exists(os.path.join(cestazpracovana,row[6])):
|
||||
shutil.move(os.path.join(cesta,row[6]), os.path.join(cestazpracovana,row[6]))
|
||||
print("Command succeeded!")
|
||||
break # Exit the loop if the command succeeds
|
||||
else:
|
||||
now = datetime.datetime.now()
|
||||
datetime_string = now.strftime("%Y-%m-%d %H-%M-%S")
|
||||
print(os.path.join(cestazpracovana,row[6][:-4]+" "+datetime_string+".pdf"))
|
||||
shutil.move(os.path.join(cesta,row[6]),os.path.join(cestazpracovana,row[6][:-4]+" "+datetime_string+".pdf"))
|
||||
print("Command succeeded!")
|
||||
break # Exit the loop if the command succeeds
|
||||
except Exception as e:
|
||||
print(f"Attempt {attempt + 1} failed: {e}")
|
||||
if attempt < 3 - 1:
|
||||
print(f"Retrying in {5} seconds...")
|
||||
time.sleep(5)
|
||||
else:
|
||||
print("Max retries reached. Command failed.")
|
||||
|
||||
|
||||
filename=funkce.convert_to1250(filename)
|
||||
print("Encodedfilename", filename)
|
||||
filenameforbookmark=row[2].strftime('%Y-%m-%d')+" "+row[4]+": "+row[5]
|
||||
bookmark=bookmark+'"'+filenameforbookmark+'","Files:'+str(fileid)+'",'+str(cislo)+";"
|
||||
cislo+=7
|
||||
# print(bookmark)
|
||||
if prvnibookmark:
|
||||
bookmarks=bookmarks+r'\pard\s10{\*\bkmkstart '+str(poradi)+r"}\plain\cs22\f0\ul\fs20\cf1 "+filenameforbookmark+r"{\*\bkmkend "+str(poradi)+r"}\par"
|
||||
prvnibookmark=False
|
||||
else:
|
||||
bookmarks=bookmarks+r'\pard\s10{\*\bkmkstart '+str(poradi)+r"}" + filenameforbookmark + r"{\*\bkmkend " + str(poradi) + r"}\par"
|
||||
bookmark=bookmark[:-1]
|
||||
# bookmarks=bookmarks[:-2]
|
||||
print(bookmark)
|
||||
print(bookmarks)
|
||||
|
||||
rtf = rtf.replace("BOOKMARKNAMES", bookmark)
|
||||
rtf=rtf.replace("BOOKMARKSTEXT",bookmarks)
|
||||
print(rtf)
|
||||
dekursid = funkce.get_dekurs_id(conn)
|
||||
datumzapisu = datetime.datetime.now().date()
|
||||
caszapisu = datetime.datetime.now().time()
|
||||
cur=conn.cursor()
|
||||
cur.execute("insert into dekurs (id,iduzi,idprac,idodd,idpac,datum,cas,dekurs)"
|
||||
" values(?,?,?,?,?,?,?,?)",
|
||||
(dekursid,6,2,2, row[1],datumzapisu,caszapisu, rtf))
|
||||
conn.commit()
|
||||
# rtf = rtf.replace("FILEID", str(idfile))
|
||||
#Zde zapisujeme soubor
|
||||
# fileid=funkce.zapis_file(conn,row[1],cesta,row[6],row[4],row[2],row[7],row[5])
|
||||
# zapis_dekurs(vstupconnection, idpac, idodd, iduzi, idprac, idfile, filename, text, datumzpravy,datumsouboru)
|
||||
# return (rc, idpac, datum, jmeno, prvnizavorka, druhazavorka, souborname, datumsouboru)
|
||||
|
||||
# Zde zapisujeme dekurs
|
||||
# text=row[2].strftime("%Y-%m-%d")+" "+row[4].strip()+": "+row[5].strip()
|
||||
# funkce.zapis_dekurs(conn, row[1], 2, 6, 2, fileid, text, text, row[7], row[2])
|
||||
# os.remove(os.path.join(cesta, soubor))
|
||||
|
||||
@@ -0,0 +1,379 @@
|
||||
import os
|
||||
import fdb
|
||||
import csv,time,pandas as pd
|
||||
import openpyxl
|
||||
|
||||
|
||||
PathToSaveCSV=r"z:\Dropbox\Ordinace\Reporty"
|
||||
timestr = time.strftime("%Y-%m-%d %H-%M-%S ")
|
||||
CSVname="Pacienti.xlsx"
|
||||
|
||||
# ================= DELETE OLD REPORTS (KEEP TODAY) ==================
|
||||
from datetime import datetime
|
||||
|
||||
today = datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
for fname in os.listdir(PathToSaveCSV):
|
||||
if fname.endswith("Pacienti.xlsx"):
|
||||
file_date = fname[:10] # first 10 chars = YYYY-MM-DD
|
||||
if file_date != today: # delete only older files
|
||||
try:
|
||||
os.remove(os.path.join(PathToSaveCSV, fname))
|
||||
print(f"🗑️ Deleted old report: {fname}")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Could not delete {fname}: {e}")
|
||||
|
||||
|
||||
con = fdb.connect(
|
||||
host='192.168.1.10', database=r'm:\MEDICUS\data\medicus.FDB',
|
||||
user='sysdba', password='masterkey',charset='WIN1250')
|
||||
|
||||
#Server=192.168.1.10
|
||||
#Path=M:\Medicus\Data\Medicus.fdb
|
||||
|
||||
# Create a Cursor object that operates in the context of Connection con:
|
||||
cur = con.cursor()
|
||||
|
||||
# import openpyxl module
|
||||
import openpyxl
|
||||
import xlwings as xw
|
||||
wb = openpyxl.Workbook()
|
||||
sheet = wb.active
|
||||
# wb.save("sample.xlsx")
|
||||
|
||||
|
||||
#Načtení očkování registrovaných pacientů
|
||||
cur.execute("select rodcis,prijmeni,jmeno,ockzaz.datum,kodmz,ockzaz.poznamka,latka,nazev,expire from registr join kar on registr.idpac=kar.idpac join ockzaz on registr.idpac=ockzaz.idpac where datum_zruseni is null and kar.vyrazen!='A' and kar.rodcis is not null and idicp!=0 order by ockzaz.datum desc")
|
||||
nacteno=cur.fetchall()
|
||||
print(len(nacteno))
|
||||
|
||||
sheet.title="Očkování"
|
||||
sheet.append(["Rodne cislo","Prijmeni","Jmeno","Datum ockovani","Kod MZ","Sarze","Latka","Nazev","Expirace"])
|
||||
#nacteno jsou ockovani
|
||||
for row in nacteno:
|
||||
sheet.append(row)
|
||||
wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname))
|
||||
|
||||
|
||||
|
||||
|
||||
#Načtení registrovaných pacientů
|
||||
cur.execute("select rodcis,prijmeni,jmeno,datum_registrace,registr.idpac,poj from registr join kar on registr.idpac=kar.idpac where kar.vyrazen!='A' and kar.rodcis is not null and idicp!=0 and datum_zruseni is null")
|
||||
nacteno=cur.fetchall()
|
||||
print(len(nacteno))
|
||||
|
||||
wb.create_sheet('Registrovani',0)
|
||||
sheet=wb['Registrovani']
|
||||
|
||||
sheet.append(["Rodne cislo","Prijmeni","Jmeno","Datum registrace","ID pacienta","Pojistovna"])
|
||||
#nacteno jsou registrovani
|
||||
for row in nacteno:
|
||||
sheet.append(row)
|
||||
wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname))
|
||||
|
||||
|
||||
#Načtení receptů
|
||||
cur.execute("""select
|
||||
kar.rodcis,
|
||||
TRIM(kar.prijmeni) ||' '|| substring(kar.jmeno from 1 for 1) ||'.' as jmeno,
|
||||
recept.datum,
|
||||
TRIM(recept.lek) ||' '|| trim(recept.dop) as lek,
|
||||
recept.expori AS Poc,
|
||||
CASE
|
||||
WHEN recept.opakovani is null THEN 1
|
||||
ELSE recept.opakovani
|
||||
END AS OP,
|
||||
recept.uhrada,
|
||||
recept.dsig,
|
||||
recept.NOTIFIKACE_KONTAKT as notifikace,
|
||||
recept_epodani.erp,
|
||||
recept_epodani.vystavitel_jmeno,
|
||||
recept.atc,
|
||||
recept.CENAPOJ,
|
||||
recept.cenapac
|
||||
from recept LEFT Join RECEPT_EPODANI on recept.id_epodani=recept_epodani.id
|
||||
LEFT join kar on recept.idpac=kar.idpac
|
||||
order by datum desc,erp desc"""
|
||||
)
|
||||
nacteno=cur.fetchall()
|
||||
print(len(nacteno))
|
||||
|
||||
wb.create_sheet('Recepty',0)
|
||||
sheet=wb['Recepty']
|
||||
|
||||
sheet.title="Recepty"
|
||||
sheet.append(["Rodné číslo","Jméno","Datum vystavení","Název leku","Poč.","Op.","Úhr.","Da signa","Notifikace","eRECEPT","Vystavil","ATC","Cena pojišťovna","Cena pacient"])
|
||||
#nacteno jsou ockovani
|
||||
for row in nacteno:
|
||||
try:
|
||||
sheet.append(row)
|
||||
except:
|
||||
continue
|
||||
|
||||
wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname))
|
||||
|
||||
#Načtení vykony vsech
|
||||
cur.execute("select dokladd.rodcis,TRIM(prijmeni) ||', '|| TRIM(jmeno),dokladd.datose,dokladd.kod,dokladd.pocvyk,dokladd.ddgn,dokladd.body,vykony.naz "
|
||||
"from kar join dokladd on kar.rodcis=dokladd.rodcis join vykony on dokladd.kod=vykony.kod where (datose>=vykony.platiod and datose<=vykony.platido) OR (datose>=vykony.platiod and vykony.platido is null) order by dokladd.datose desc,dokladd.rodcis")
|
||||
|
||||
wb.create_sheet('Vykony',0)
|
||||
sheet=wb['Vykony']
|
||||
|
||||
nacteno=cur.fetchall()
|
||||
print(len(nacteno))
|
||||
|
||||
sheet.append(["Rodne cislo","Jmeno","Datum vykonu","Kod","Pocet","Dg.","Body","Nazev"])
|
||||
#nacteno jsou ockovani
|
||||
for row in nacteno:
|
||||
sheet.append(row)
|
||||
wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname))
|
||||
|
||||
#Načtení neschopenek
|
||||
|
||||
import datetime
|
||||
def pocet_dni(zacnes,konnes,pracne):
|
||||
dnes=datetime.date.today()
|
||||
if pracne=='A':
|
||||
return (dnes-zacnes).days
|
||||
if pracne=='N' and zacnes is not None and konnes is not None and zacnes<=konnes:
|
||||
return (konnes-zacnes).days
|
||||
else:
|
||||
return "NA"
|
||||
|
||||
cur.execute("select nes.idpac, "
|
||||
"kar.rodcis, "
|
||||
"TRIM(prijmeni) ||', '|| TRIM(jmeno), "
|
||||
"nes.datnes, "
|
||||
"nes.ecn, "
|
||||
"nes.zacnes, "
|
||||
"nes.pracne, "
|
||||
"nes.konnes, "
|
||||
"nes.diagno, "
|
||||
"nes.kondia, "
|
||||
"nes.updated "
|
||||
"from nes "
|
||||
"left join kar on nes.idpac=kar.idpac where nes.datnes<=current_date "
|
||||
"order by datnes desc")
|
||||
|
||||
|
||||
tmpnacteno_vse=[]
|
||||
nacteno_vse=cur.fetchall()
|
||||
|
||||
cur.execute("select nes.idpac, "
|
||||
"kar.rodcis, "
|
||||
"TRIM(prijmeni) ||', '|| TRIM(jmeno), "
|
||||
"nes.datnes, "
|
||||
"nes.ecn, "
|
||||
"nes.zacnes, "
|
||||
"nes.pracne, "
|
||||
"nes.konnes, "
|
||||
"nes.diagno, "
|
||||
"nes.kondia, "
|
||||
"nes.updated "
|
||||
"from nes "
|
||||
"left join kar on nes.idpac=kar.idpac where nes.datnes<=current_date and pracne='A'"
|
||||
"order by datnes desc")
|
||||
|
||||
tmpnacteno_aktivni=[]
|
||||
nacteno_aktivni=cur.fetchall()
|
||||
|
||||
for row in nacteno_vse:
|
||||
tmpnacteno_vse.append((row[0],row[1],row[2],row[3],row[4],row[5],row[6],row[7],pocet_dni(row[5],row[7],row[6]),row[8],row[9],row[10]))
|
||||
|
||||
for row in nacteno_aktivni:
|
||||
(tmpnacteno_aktivni.append((row[0],row[1],row[2],row[3],row[4],row[5],row[6],row[7],pocet_dni(row[5],row[7],row[6]),row[8],row[9],row[10])))
|
||||
|
||||
wb.create_sheet('Neschopenky všechny',0)
|
||||
sheet=wb["Neschopenky všechny"]
|
||||
sheet.append(["ID pac","Rodne cislo","Jmeno","Datum neschopenky","Číslo neschopenky","Zacatek","Aktivní?","Konec","Pocet dni","Diagnoza zacatel","Diagnoza konec","Aktualizovano"])
|
||||
for row in tmpnacteno_vse:
|
||||
sheet.append(row)
|
||||
|
||||
wb.create_sheet('Neschopenky aktivní',0)
|
||||
sheet=wb["Neschopenky aktivní"]
|
||||
sheet.append(["ID pac","Rodne cislo","Jmeno","Datum neschopenky","Číslo neschopenky","Zacatek","Aktivní?","Konec","Pocet dni","Diagnoza zacatel","Diagnoza konec","Aktualizovano"])
|
||||
for row in tmpnacteno_aktivni:
|
||||
sheet.append(row)
|
||||
|
||||
#Načtení preventivni prohlidky
|
||||
cur.execute("select all dokladd.rodcis,TRIM(prijmeni) ||', '|| TRIM(jmeno),dokladd.datose,dokladd.kod,vykony.naz,dokladd.ddgn,dokladd.body "
|
||||
"from dokladd left join kar on dokladd.rodcis=kar.rodcis join vykony on dokladd.kod=vykony.kod where "
|
||||
"((datose>=vykony.platiod and datose<=vykony.platido) OR (datose>=vykony.platiod and vykony.platido is null)) and (dokladd.kod=1022 or dokladd.kod=1021) "
|
||||
"order by datose desc")
|
||||
|
||||
wb.create_sheet('Preventivni prohlidky',0)
|
||||
sheet=wb['Preventivni prohlidky']
|
||||
|
||||
|
||||
nacteno=cur.fetchall()
|
||||
print(len(nacteno))
|
||||
|
||||
sheet.append(["Rodne cislo","Jmeno","Datum","Kod","Název","Dg.","Body"])
|
||||
|
||||
for row in nacteno:
|
||||
sheet.append(row)
|
||||
wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname))
|
||||
|
||||
#Nacteni INR
|
||||
cur.execute("select all dokladd.rodcis,TRIM(prijmeni) ||', '|| TRIM(jmeno),dokladd.datose,dokladd.kod,vykony.naz,dokladd.ddgn,dokladd.body "
|
||||
"from dokladd left join kar on dokladd.rodcis=kar.rodcis join vykony on dokladd.kod=vykony.kod where "
|
||||
"((datose>=vykony.platiod and datose<=vykony.platido) OR (datose>=vykony.platiod and vykony.platido is null)) and (dokladd.kod=01443) "
|
||||
"order by datose desc")
|
||||
|
||||
wb.create_sheet('INR',0)
|
||||
sheet=wb['INR']
|
||||
|
||||
nacteno=cur.fetchall()
|
||||
print(len(nacteno))
|
||||
|
||||
sheet.append(["Rodne cislo","Jmeno","Datum","Kod","Název","Dg.","Body"])
|
||||
|
||||
for row in nacteno:
|
||||
sheet.append(row)
|
||||
wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname))
|
||||
|
||||
#Nacteni CRP
|
||||
cur.execute("select all dokladd.rodcis,TRIM(prijmeni) ||', '|| TRIM(jmeno),dokladd.datose,dokladd.kod,vykony.naz,dokladd.ddgn,dokladd.body "
|
||||
"from dokladd left join kar on dokladd.rodcis=kar.rodcis join vykony on dokladd.kod=vykony.kod where "
|
||||
"((datose>=vykony.platiod and datose<=vykony.platido) OR (datose>=vykony.platiod and vykony.platido is null)) and (dokladd.kod=02230 or dokladd.kod=09111) "
|
||||
"order by datose desc,dokladd.rodcis,dokladd.kod")
|
||||
|
||||
wb.create_sheet('CRP',0)
|
||||
sheet=wb['CRP']
|
||||
|
||||
nacteno=cur.fetchall()
|
||||
print(len(nacteno))
|
||||
|
||||
sheet.append(["Rodne cislo","Jmeno","Datum","Kod","Název","Dg.","Body"])
|
||||
|
||||
for row in nacteno:
|
||||
sheet.append(row)
|
||||
wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname))
|
||||
|
||||
|
||||
#Nacteni Holter
|
||||
cur.execute("select all dokladd.rodcis,TRIM(prijmeni) ||', '|| TRIM(jmeno),dokladd.datose,dokladd.kod,vykony.naz,dokladd.ddgn,dokladd.body "
|
||||
"from dokladd left join kar on dokladd.rodcis=kar.rodcis join vykony on dokladd.kod=vykony.kod where "
|
||||
"((datose>=vykony.platiod and datose<=vykony.platido) OR (datose>=vykony.platiod and vykony.platido is null)) and (dokladd.kod=17129) "
|
||||
"order by datose desc,dokladd.rodcis,dokladd.kod")
|
||||
|
||||
wb.create_sheet('Holter',0)
|
||||
sheet=wb['Holter']
|
||||
|
||||
nacteno=cur.fetchall()
|
||||
print(len(nacteno))
|
||||
|
||||
sheet.append(["Rodne cislo","Jmeno","Datum","Kod","Název","Dg.","Body"])
|
||||
|
||||
for row in nacteno:
|
||||
sheet.append(row)
|
||||
wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname))
|
||||
|
||||
#Nacteni prostata
|
||||
cur.execute("select all dokladd.rodcis,TRIM(prijmeni) ||', '|| TRIM(jmeno),dokladd.datose,dokladd.kod,vykony.naz,dokladd.ddgn,dokladd.body "
|
||||
"from dokladd left join kar on dokladd.rodcis=kar.rodcis join vykony on dokladd.kod=vykony.kod where "
|
||||
"((datose>=vykony.platiod and datose<=vykony.platido) OR (datose>=vykony.platiod and vykony.platido is null)) and (dokladd.kod=01130 or dokladd.kod=01131 or dokladd.kod=01132 or dokladd.kod=01133 or dokladd.kod=01134) "
|
||||
"order by datose desc,dokladd.rodcis,dokladd.kod")
|
||||
|
||||
wb.create_sheet('Prostata',0)
|
||||
sheet=wb['Prostata']
|
||||
|
||||
nacteno=cur.fetchall()
|
||||
print(len(nacteno))
|
||||
|
||||
sheet.append(["Rodne cislo","Jmeno","Datum","Kod","Název","Dg.","Body"])
|
||||
|
||||
for row in nacteno:
|
||||
sheet.append(row)
|
||||
wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname))
|
||||
|
||||
#Nacteni TOKS
|
||||
cur.execute("select all dokladd.rodcis,TRIM(prijmeni) ||', '|| TRIM(jmeno),dokladd.datose,dokladd.kod,vykony.naz,dokladd.ddgn,dokladd.body "
|
||||
"from dokladd left join kar on dokladd.rodcis=kar.rodcis join vykony on dokladd.kod=vykony.kod where "
|
||||
"((datose>=vykony.platiod and datose<=vykony.platido) OR (datose>=vykony.platiod and vykony.platido is null)) and "
|
||||
"(dokladd.kod=15118 or dokladd.kod=15119 or dokladd.kod=15120 or dokladd.kod=15121) "
|
||||
"order by datose desc,dokladd.rodcis,dokladd.kod")
|
||||
|
||||
wb.create_sheet('TOKS',0)
|
||||
sheet=wb['TOKS']
|
||||
|
||||
nacteno=cur.fetchall()
|
||||
print(len(nacteno))
|
||||
|
||||
sheet.append(["Rodne cislo","Jmeno","Datum","Kod","Název","Dg.","Body"])
|
||||
|
||||
for row in nacteno:
|
||||
sheet.append(row)
|
||||
wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname))
|
||||
|
||||
#Nacteni COVID
|
||||
cur.execute("select all dokladd.rodcis,TRIM(prijmeni) ||', '|| TRIM(jmeno),dokladd.datose,dokladd.kod,vykony.naz,dokladd.ddgn,dokladd.body "
|
||||
"from dokladd left join kar on dokladd.rodcis=kar.rodcis join vykony on dokladd.kod=vykony.kod where "
|
||||
"((datose>=vykony.platiod and datose<=vykony.platido) OR (datose>=vykony.platiod and vykony.platido is null)) and "
|
||||
"(dokladd.kod=01306) "
|
||||
"order by datose desc,dokladd.rodcis,dokladd.kod")
|
||||
|
||||
wb.create_sheet('COVID',0)
|
||||
sheet=wb['COVID']
|
||||
|
||||
nacteno=cur.fetchall()
|
||||
print(len(nacteno))
|
||||
|
||||
sheet.append(["Rodne cislo","Jmeno","Datum","Kod","Název","Dg.","Body"])
|
||||
|
||||
for row in nacteno:
|
||||
sheet.append(row)
|
||||
wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname))
|
||||
|
||||
#Nacteni Streptest
|
||||
cur.execute("select all dokladd.rodcis,TRIM(prijmeni) ||', '|| TRIM(jmeno),dokladd.datose,dokladd.kod,vykony.naz,dokladd.ddgn,dokladd.body "
|
||||
"from dokladd left join kar on dokladd.rodcis=kar.rodcis join vykony on dokladd.kod=vykony.kod where "
|
||||
"((datose>=vykony.platiod and datose<=vykony.platido) OR (datose>=vykony.platiod and vykony.platido is null)) and "
|
||||
"(dokladd.kod=02220) "
|
||||
"order by datose desc,dokladd.rodcis,dokladd.kod")
|
||||
|
||||
wb.create_sheet('Streptest',0)
|
||||
sheet=wb['Streptest']
|
||||
|
||||
nacteno=cur.fetchall()
|
||||
print(len(nacteno))
|
||||
|
||||
sheet.append(["Rodne cislo","Jmeno","Datum","Kod","Název","Dg.","Body"])
|
||||
|
||||
for row in nacteno:
|
||||
sheet.append(row)
|
||||
|
||||
|
||||
# autofilter
|
||||
for ws in wb.worksheets:
|
||||
# Get the maximum number of rows and columns
|
||||
max_row = ws.max_row
|
||||
max_column = ws.max_column
|
||||
ws.auto_filter.ref = f"A1:{openpyxl.utils.get_column_letter(max_column)}{max_row}"
|
||||
# ws.auto_filter.ref = ws.dimensions
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
wb.save(os.path.join(PathToSaveCSV ,timestr+CSVname))
|
||||
|
||||
|
||||
# Tento modul je pouze na autofit jednotlivych sloupcu na vsech listech workbooku
|
||||
file = os.path.join(PathToSaveCSV ,timestr+CSVname)
|
||||
with xw.App(visible=False) as app:
|
||||
wb = xw.Book(file)
|
||||
for sheet in range(len(wb.sheets)):
|
||||
ws = wb.sheets[sheet]
|
||||
ws.autofit()
|
||||
|
||||
# centrování receptů
|
||||
sheet = wb.sheets['Recepty']
|
||||
for sloupec in ["C:C", "E:E", "F:F", "G:G", "I:I", "M:M", "N:N"]:
|
||||
sheet.range(sloupec).api.HorizontalAlignment = 3 # 3 = Center
|
||||
|
||||
|
||||
wb.save()
|
||||
wb.close()
|
||||
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
# MedicusWithClaudeKomplexniReport – CLAUDE_NOTES
|
||||
|
||||
## Co skript dělá
|
||||
|
||||
`komplexni_report.py` generuje komplexní Excel přehled ordinace.
|
||||
Soubor se ukládá do `u:\Dropbox\!!!Days\Downloads Z230\YYYY-MM-DD_HH-MM-SS_Pacienti.xlsx`.
|
||||
Předchozí verze (`*Pacienti.xlsx`) se před zápisem automaticky smažou.
|
||||
|
||||
## Spuštění
|
||||
|
||||
```
|
||||
C:\Python\python.exe komplexni_report.py
|
||||
```
|
||||
|
||||
Trvá cca **10 minut** (kvůli xlwings autofit přes celý Excel).
|
||||
Spouští se automaticky v noci → nevadí.
|
||||
|
||||
## Připojení k DB
|
||||
|
||||
```python
|
||||
fdb.connect(
|
||||
host='localhost',
|
||||
database=r'c:\MEDICUS 3\data\medicus.FDB',
|
||||
user='sysdba', password='masterkey', charset='WIN1250'
|
||||
)
|
||||
```
|
||||
|
||||
## Závislosti
|
||||
|
||||
```
|
||||
pip install fdb openpyxl xlwings extract-msg beautifulsoup4 python-dateutil
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Listy v Excelu (pořadí)
|
||||
|
||||
| List | Zdroj | Popis |
|
||||
|---|---|---|
|
||||
| `Registrovani` | registr + kar | Aktivní registrovaní pacienti |
|
||||
| `Očkování` | ockzaz + registr + kar | Záznamy o očkování registrovaných |
|
||||
| `Recepty` | recept + recept_epodani + kar | Všechny recepty, eRECEPT čísla, ceny |
|
||||
| `Vykony` | dokladd + kar + vykony | Všechny výkony (s platným číselníkem) |
|
||||
| `Neschopenky všechny` | nes + kar | Všechny neschopenky |
|
||||
| `Neschopenky aktivní` | nes + kar | Pouze aktivní (pracne='A') |
|
||||
| `Preventivni prohlidky` | dokladd + vykony | Kódy 1021, 1022 |
|
||||
| `INR` | dokladd + vykony | Kód 1443 |
|
||||
| `CRP` | dokladd + vykony | Kódy 2230, 9111 |
|
||||
| `Holter` | dokladd + vykony | Kód 17129 |
|
||||
| `Prostata` | dokladd + vykony | Kódy 1130–1134 |
|
||||
| `TOKS` | dokladd + vykony | Kódy 15118–15121 |
|
||||
| `COVID` | dokladd + vykony | Kód 1306 |
|
||||
| `Streptest` | dokladd + vykony | Kód 2220 |
|
||||
| `Posudky řidičák` | HISTDOC (TYP=MOTORVO) + KAR | Ruční posudky k řízení MV |
|
||||
| `ePosudky registr` | HISTDOC (TYP=EPOSMRO) + HISTDOC_EPOSUDEK + KAR | Elektronická podání do centrálního registru |
|
||||
|
||||
---
|
||||
|
||||
## Pomocné funkce
|
||||
|
||||
### `sanitize(val)`
|
||||
Opraví znaky neplatné pro Excel:
|
||||
- `µ` → `u`
|
||||
- řídící znaky (ord < 32, kromě tab/LF/CR) → `_`
|
||||
- náhradní znaky Unicode (0xFFFE, 0xFFFF, surrogáty) → `_`
|
||||
|
||||
Použito ve všech listech kde hrozí problematická data z DB.
|
||||
|
||||
### `fmt(val)`
|
||||
Vrátí `''` pro None, jinak zavolá `sanitize()`.
|
||||
|
||||
### `add_vykony_sheet(sheet_name, kody)`
|
||||
Helper pro listy s výkony. Přijme název listu a seznam kódů výkonů.
|
||||
SQL: `dokladd JOIN kar JOIN vykony WHERE kod IN (...) AND platnost kódu platí`.
|
||||
Řazení: datum DESC, rodcis, kod.
|
||||
|
||||
### `pocet_dni(zacnes, konnes, pracne)`
|
||||
Výpočet délky neschopenky:
|
||||
- `pracne='A'` (aktivní) → dny od začátku do dnes
|
||||
- `pracne='N'` → dny od začátku do konce
|
||||
- jinak → `"NA"`
|
||||
|
||||
### `parse_data(data_str)`
|
||||
Parsuje `key=value` text z pole `HISTDOC.DATA` do slovníku.
|
||||
Každý řádek = jeden klíč/hodnota oddělené `=`.
|
||||
|
||||
### `parse_date(val)`
|
||||
Převede formát `D:DD.MM.YYYY` (jak ho ukládá Medicus) na `datetime.date`.
|
||||
|
||||
### `style_header(ws)` / `autofit_ws(ws)`
|
||||
Styl záhlaví (modrý fill, bílý tučný text, centrování) a šířky sloupců (max 50 znaků).
|
||||
Používají se jen na listech s posudky (ostatní listy řeší xlwings).
|
||||
|
||||
---
|
||||
|
||||
## Listy s posudky – detail
|
||||
|
||||
### `Posudky řidičák` (MOTORVO)
|
||||
|
||||
Data jsou uložena v `HISTDOC.DATA` jako `key=value` text.
|
||||
Parsovaná pole:
|
||||
|
||||
| Sloupec | Zdroj v DATA |
|
||||
|---|---|
|
||||
| PorCislo | `PorCislo` nebo `HISTDOC.PORCISLO` |
|
||||
| DatumVyd | `DatumVyd` (formát `D:DD.MM.YYYY`) |
|
||||
| DatKonec | `DatKonec` (formát `D:DD.MM.YYYY`) |
|
||||
| DruhProh | `DruhProh` |
|
||||
| Posouzeni | odvozeno z `Posouzeni`, `Posouzeni2`, `ZpusobPodminka` |
|
||||
| ZpusobPodminka | `ZpusobPodminka` |
|
||||
| SkupinaPodminka | `SkupinaPodminka` |
|
||||
| Skupiny | `ZpusobJe` |
|
||||
|
||||
**Logika Posouzeni:**
|
||||
- `Posouzeni2 = T` → `nezpůsobilý`
|
||||
- `ZpusobPodminka = B:1` → `způsobilý s podmínkou`
|
||||
- `Posouzeni = T` → `způsobilý`
|
||||
|
||||
**Sloupec ePosudek:**
|
||||
`ANO` pokud existuje záznam v HISTDOC s `TYP='EPOSMRO'` pro stejného pacienta (IDPACI) a stejné datum.
|
||||
Párování: `(IDPACI, DATUM)` – přímá FK vazba mezi MOTORVO a EPOSMRO neexistuje.
|
||||
Buňka s ANO je zelená (fill + font).
|
||||
|
||||
**Zebra pruhování:** liché řádky bílé, sudé světle modré (`DCE6F1`).
|
||||
|
||||
### `ePosudky registr` (EPOSMRO)
|
||||
|
||||
Elektronická podání do centrálního registru způsobilosti.
|
||||
Stát tuto funkci zavedl přibližně od aktualizace Medicusu (03/2026).
|
||||
|
||||
Data parsovaná z `HISTDOC.DATA`:
|
||||
|
||||
| Sloupec | Zdroj v DATA |
|
||||
|---|---|
|
||||
| DatumVyd | `DatumVystaveni` |
|
||||
| DatKonec | `PlatnostDo` |
|
||||
| DruhProhlidky | `DruhProhlidkyNazev` |
|
||||
| DruhPosudku | `DruhPosudkuNazev` |
|
||||
| Vysledek | `VysledekNazev` |
|
||||
| StavPosudku | `StavPosudkuNazev` |
|
||||
| TypAkce | `TypAkceNazev` |
|
||||
|
||||
Stavová pole z `HISTDOC_EPOSUDEK`:
|
||||
- `ID_PODANI` – ID podání do registru
|
||||
- `ODESLANO` – timestamp odeslání
|
||||
- `STATUS_ODESL` – stav odpovědi z registru (`O` = odesláno)
|
||||
|
||||
**Zneplatnění:** `StavPosudku = zneplatneny` = lékař aktivně odvolal způsobilost
|
||||
(např. pacient prodělal mrtvici, epileptický záchvat atp.).
|
||||
Zneplatnění je samostatný EPOSMRO záznam, ne modifikace původního.
|
||||
|
||||
---
|
||||
|
||||
## xlwings – závěrečný krok
|
||||
|
||||
Po `wb.save()` se soubor otevře přes xlwings (vyžaduje plný Excel):
|
||||
1. `sheet.autofit()` na všech listech – správné šířky sloupců
|
||||
2. Na listu `Recepty`: centrování sloupců C, E, F, G, I, M, N
|
||||
3. `wb_xw.save()` + zavření
|
||||
|
||||
xlwings je nutný pro spolehlivý autofit (openpyxl ho neumí přesně).
|
||||
Trvá ~10 minut, spouští se v noci.
|
||||
|
||||
---
|
||||
|
||||
## Pořadí zpracování (pro debugování)
|
||||
|
||||
```
|
||||
DB connect
|
||||
→ smazání starých souborů
|
||||
→ SQL dotazy (Registrovani, Očkování, Recepty, Výkony, Neschopenky)
|
||||
→ add_vykony_sheet × 8
|
||||
→ MOTORVO + EPOSMRO listy (s parsováním DATA)
|
||||
→ autofilter na všech listech
|
||||
→ con.close() + wb.save()
|
||||
→ xlwings autofit + centrování
|
||||
→ Hotovo.
|
||||
```
|
||||
|
||||
Print výstup v konzoli ukazuje počty řádků každého listu – užitečné pro kontrolu.
|
||||
|
||||
---
|
||||
|
||||
## Rozšíření v budoucnu
|
||||
|
||||
- Přidat další typy posudků (pracovní, vstupní, sportovní...) ze `VS_POSUDKY`
|
||||
- Případně sledovat stav podání EPOSMRO v čase (datum odeslání vs. datum posudku)
|
||||
- Automatické spouštění přes Windows Task Scheduler (jako `faktury_report.py`)
|
||||
@@ -0,0 +1,410 @@
|
||||
import os
|
||||
import time
|
||||
import fdb
|
||||
import openpyxl
|
||||
import xlwings as xw
|
||||
from datetime import datetime, date
|
||||
from openpyxl.utils import get_column_letter
|
||||
from openpyxl.styles import Font, PatternFill, Alignment
|
||||
|
||||
# --- Konfigurace ---
|
||||
PathToSaveCSV = r"u:\Dropbox\!!!Days\Downloads Z230"
|
||||
timestr = time.strftime("%Y-%m-%d_%H-%M-%S_")
|
||||
output_path = os.path.join(PathToSaveCSV, timestr + "Pacienti.xlsx")
|
||||
|
||||
# --- Smazání předchozích verzí ---
|
||||
for fname in os.listdir(PathToSaveCSV):
|
||||
if fname.endswith("Pacienti.xlsx"):
|
||||
try:
|
||||
os.remove(os.path.join(PathToSaveCSV, fname))
|
||||
except Exception as e:
|
||||
print(f"Nelze smazat {fname}: {e}")
|
||||
|
||||
# --- Připojení k DB ---
|
||||
con = fdb.connect(
|
||||
host='localhost', database=r'c:\MEDICUS 3\data\medicus.FDB',
|
||||
user='sysdba', password='masterkey', charset='WIN1250'
|
||||
)
|
||||
cur = con.cursor()
|
||||
|
||||
wb = openpyxl.Workbook()
|
||||
|
||||
# =====================
|
||||
# Pomocné funkce
|
||||
# =====================
|
||||
|
||||
# Styly pro posudky
|
||||
HEADER_FILL = PatternFill('solid', fgColor='2F5496')
|
||||
HEADER_FONT = Font(bold=True, color='FFFFFF')
|
||||
ZEBRA_FILL = PatternFill('solid', fgColor='DCE6F1')
|
||||
GREEN_FILL = PatternFill('solid', fgColor='C6EFCE')
|
||||
GREEN_FONT = Font(bold=True, color='276221')
|
||||
|
||||
def style_header(ws):
|
||||
for cell in ws[1]:
|
||||
cell.fill = HEADER_FILL
|
||||
cell.font = HEADER_FONT
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
|
||||
def autofit_ws(ws):
|
||||
for col in ws.columns:
|
||||
max_len = max((len(str(cell.value)) if cell.value is not None else 0) for cell in col)
|
||||
ws.column_dimensions[get_column_letter(col[0].column)].width = min(max_len + 2, 50)
|
||||
|
||||
def sanitize(val):
|
||||
"""Nahradí znaky neplatné pro Excel: µ → u, ostatní → _"""
|
||||
if not isinstance(val, str):
|
||||
return val
|
||||
result = []
|
||||
for ch in val:
|
||||
if ch == 'µ':
|
||||
result.append('u')
|
||||
elif ord(ch) < 32 and ch not in '\t\n\r':
|
||||
result.append('_')
|
||||
elif ord(ch) in (0xFFFE, 0xFFFF) or 0xD800 <= ord(ch) <= 0xDFFF:
|
||||
result.append('_')
|
||||
else:
|
||||
result.append(ch)
|
||||
return ''.join(result)
|
||||
|
||||
def fmt(val):
|
||||
return '' if val is None else sanitize(val)
|
||||
|
||||
def parse_data(data_str):
|
||||
"""Parsuje key=value text z HISTDOC.DATA do slovníku."""
|
||||
result = {}
|
||||
if not data_str:
|
||||
return result
|
||||
for line in data_str.splitlines():
|
||||
if '=' in line:
|
||||
key, _, val = line.partition('=')
|
||||
result[key.strip()] = val.strip()
|
||||
return result
|
||||
|
||||
def parse_date(val):
|
||||
"""Převede 'D:DD.MM.YYYY' na datetime.date."""
|
||||
if val and val.startswith('D:'):
|
||||
try:
|
||||
return datetime.strptime(val[2:], '%d.%m.%Y').date()
|
||||
except ValueError:
|
||||
return val
|
||||
return val
|
||||
|
||||
VYKONY_CONDITION = """
|
||||
(datose >= vykony.platiod AND datose <= vykony.platido)
|
||||
OR (datose >= vykony.platiod AND vykony.platido IS NULL)
|
||||
"""
|
||||
VYKONY_HEADERS = ["Rodne cislo", "Jmeno", "Datum vykonu", "Kod", "Název", "Dg.", "Body"]
|
||||
|
||||
def add_vykony_sheet(sheet_name, kody):
|
||||
"""Přidá list s výkony filtrovanými podle seznamu kódů."""
|
||||
kod_list = ", ".join(str(k) for k in kody)
|
||||
cur.execute(f"""
|
||||
SELECT dokladd.rodcis,
|
||||
TRIM(prijmeni) || ', ' || TRIM(jmeno),
|
||||
dokladd.datose, dokladd.kod, vykony.naz, dokladd.ddgn, dokladd.body
|
||||
FROM dokladd
|
||||
LEFT JOIN kar ON dokladd.rodcis = kar.rodcis
|
||||
JOIN vykony ON dokladd.kod = vykony.kod
|
||||
WHERE ({VYKONY_CONDITION})
|
||||
AND dokladd.kod IN ({kod_list})
|
||||
ORDER BY datose DESC, dokladd.rodcis, dokladd.kod
|
||||
""")
|
||||
rows = cur.fetchall()
|
||||
print(f"{sheet_name}: {len(rows)}")
|
||||
ws = wb.create_sheet(sheet_name)
|
||||
ws.append(VYKONY_HEADERS)
|
||||
for row in rows:
|
||||
ws.append(list(row))
|
||||
|
||||
# =====================
|
||||
# List: Registrovaní
|
||||
# =====================
|
||||
cur.execute("""
|
||||
SELECT rodcis, prijmeni, jmeno, datum_registrace, registr.idpac, poj
|
||||
FROM registr
|
||||
JOIN kar ON registr.idpac = kar.idpac
|
||||
WHERE kar.vyrazen != 'A'
|
||||
AND kar.rodcis IS NOT NULL
|
||||
AND idicp != 0
|
||||
AND datum_zruseni IS NULL
|
||||
""")
|
||||
rows = cur.fetchall()
|
||||
print(f"Registrovaní: {len(rows)}")
|
||||
ws = wb.active
|
||||
ws.title = 'Registrovani'
|
||||
ws.append(["Rodne cislo", "Prijmeni", "Jmeno", "Datum registrace", "ID pacienta", "Pojistovna"])
|
||||
for row in rows:
|
||||
ws.append(list(row))
|
||||
|
||||
# =====================
|
||||
# List: Očkování
|
||||
# =====================
|
||||
cur.execute("""
|
||||
SELECT rodcis, prijmeni, jmeno, ockzaz.datum, kodmz, ockzaz.poznamka, latka, nazev, expire
|
||||
FROM registr
|
||||
JOIN kar ON registr.idpac = kar.idpac
|
||||
JOIN ockzaz ON registr.idpac = ockzaz.idpac
|
||||
WHERE datum_zruseni IS NULL
|
||||
AND kar.vyrazen != 'A'
|
||||
AND kar.rodcis IS NOT NULL
|
||||
AND idicp != 0
|
||||
ORDER BY ockzaz.datum DESC
|
||||
""")
|
||||
rows = cur.fetchall()
|
||||
print(f"Očkování: {len(rows)}")
|
||||
ws = wb.create_sheet("Očkování")
|
||||
ws.append(["Rodne cislo", "Prijmeni", "Jmeno", "Datum ockovani", "Kod MZ", "Sarze", "Latka", "Nazev", "Expirace"])
|
||||
for row in rows:
|
||||
ws.append(list(row))
|
||||
|
||||
# =====================
|
||||
# List: Recepty
|
||||
# =====================
|
||||
cur.execute("""
|
||||
SELECT kar.rodcis,
|
||||
TRIM(kar.prijmeni) || ' ' || SUBSTRING(kar.jmeno FROM 1 FOR 1) || '.' AS jmeno,
|
||||
recept.datum,
|
||||
TRIM(recept.lek) || ' ' || TRIM(recept.dop) AS lek,
|
||||
recept.expori AS Poc,
|
||||
CASE WHEN recept.opakovani IS NULL THEN 1 ELSE recept.opakovani END AS OP,
|
||||
recept.uhrada,
|
||||
recept.dsig,
|
||||
recept.NOTIFIKACE_KONTAKT AS notifikace,
|
||||
recept_epodani.erp,
|
||||
recept_epodani.vystavitel_jmeno,
|
||||
recept.atc,
|
||||
recept.CENAPOJ,
|
||||
recept.cenapac
|
||||
FROM recept
|
||||
LEFT JOIN RECEPT_EPODANI ON recept.id_epodani = recept_epodani.id
|
||||
LEFT JOIN kar ON recept.idpac = kar.idpac
|
||||
ORDER BY datum DESC, erp DESC
|
||||
""")
|
||||
rows = cur.fetchall()
|
||||
print(f"Recepty: {len(rows)}")
|
||||
ws = wb.create_sheet("Recepty")
|
||||
ws.append(["Rodné číslo", "Jméno", "Datum vystavení", "Název leku", "Poč.", "Op.", "Úhr.",
|
||||
"Da signa", "Notifikace", "eRECEPT", "Vystavil", "ATC", "Cena pojišťovna", "Cena pacient"])
|
||||
for row in rows:
|
||||
ws.append([sanitize(v) if isinstance(v, str) else v for v in row])
|
||||
|
||||
# =====================
|
||||
# List: Výkony všechny
|
||||
# =====================
|
||||
cur.execute(f"""
|
||||
SELECT dokladd.rodcis,
|
||||
TRIM(prijmeni) || ', ' || TRIM(jmeno),
|
||||
dokladd.datose, dokladd.kod, dokladd.pocvyk, dokladd.ddgn, dokladd.body, vykony.naz
|
||||
FROM kar
|
||||
JOIN dokladd ON kar.rodcis = dokladd.rodcis
|
||||
JOIN vykony ON dokladd.kod = vykony.kod
|
||||
WHERE {VYKONY_CONDITION}
|
||||
ORDER BY dokladd.datose DESC, dokladd.rodcis
|
||||
""")
|
||||
rows = cur.fetchall()
|
||||
print(f"Výkony: {len(rows)}")
|
||||
ws = wb.create_sheet("Vykony")
|
||||
ws.append(["Rodne cislo", "Jmeno", "Datum vykonu", "Kod", "Pocet", "Dg.", "Body", "Nazev"])
|
||||
for row in rows:
|
||||
ws.append(list(row))
|
||||
|
||||
# =====================
|
||||
# Listy: Neschopenky
|
||||
# =====================
|
||||
def pocet_dni(zacnes, konnes, pracne):
|
||||
dnes = date.today()
|
||||
if pracne == 'A':
|
||||
return (dnes - zacnes).days if zacnes else "NA"
|
||||
if pracne == 'N' and zacnes and konnes and zacnes <= konnes:
|
||||
return (konnes - zacnes).days
|
||||
return "NA"
|
||||
|
||||
def nes_row(r):
|
||||
return (r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7],
|
||||
pocet_dni(r[5], r[7], r[6]), r[8], r[9], r[10])
|
||||
|
||||
NES_HEADERS = ["ID pac", "Rodne cislo", "Jmeno", "Datum neschopenky", "Číslo neschopenky",
|
||||
"Zacatek", "Aktivní?", "Konec", "Pocet dni", "Diagnoza zacatel", "Diagnoza konec", "Aktualizovano"]
|
||||
|
||||
cur.execute("""
|
||||
SELECT nes.idpac, kar.rodcis,
|
||||
TRIM(prijmeni) || ', ' || TRIM(jmeno),
|
||||
nes.datnes, nes.ecn, nes.zacnes, nes.pracne, nes.konnes,
|
||||
nes.diagno, nes.kondia, nes.updated
|
||||
FROM nes
|
||||
LEFT JOIN kar ON nes.idpac = kar.idpac
|
||||
WHERE nes.datnes <= CURRENT_DATE
|
||||
ORDER BY datnes DESC
|
||||
""")
|
||||
vse = cur.fetchall()
|
||||
aktivni = [r for r in vse if r[6] == 'A']
|
||||
print(f"Neschopenky: {len(vse)} celkem, {len(aktivni)} aktivních")
|
||||
|
||||
ws = wb.create_sheet("Neschopenky všechny")
|
||||
ws.append(NES_HEADERS)
|
||||
for r in vse:
|
||||
ws.append(list(nes_row(r)))
|
||||
|
||||
ws = wb.create_sheet("Neschopenky aktivní")
|
||||
ws.append(NES_HEADERS)
|
||||
for r in aktivni:
|
||||
ws.append(list(nes_row(r)))
|
||||
|
||||
# =====================
|
||||
# Výkonové listy – jednotlivé typy výkonů
|
||||
# =====================
|
||||
add_vykony_sheet('Preventivni prohlidky', [1022, 1021])
|
||||
add_vykony_sheet('INR', [1443])
|
||||
add_vykony_sheet('CRP', [2230, 9111])
|
||||
add_vykony_sheet('Holter', [17129])
|
||||
add_vykony_sheet('Prostata', [1130, 1131, 1132, 1133, 1134])
|
||||
add_vykony_sheet('TOKS', [15118, 15119, 15120, 15121])
|
||||
add_vykony_sheet('COVID', [1306])
|
||||
add_vykony_sheet('Streptest', [2220])
|
||||
|
||||
# =====================
|
||||
# List: Posudky řidičák – MOTORVO (ruční)
|
||||
# =====================
|
||||
cur.execute("SELECT IDPACI, DATUM FROM HISTDOC WHERE TYP = 'EPOSMRO'")
|
||||
eposmro_keys = set((r[0], r[1]) for r in cur.fetchall())
|
||||
|
||||
cur.execute("""
|
||||
SELECT h.ID, h.DATUM, h.IDPACI,
|
||||
k.PRIJMENI, k.JMENO, k.RODCIS,
|
||||
h.DATA, h.PORCISLO, h.STAV, h.PRINTED, h.IDUZIV, h.CREATED
|
||||
FROM HISTDOC h
|
||||
JOIN KAR k ON k.IDPAC = h.IDPACI
|
||||
WHERE h.TYP = 'MOTORVO'
|
||||
ORDER BY h.ID DESC
|
||||
""")
|
||||
motorvo_rows = cur.fetchall()
|
||||
print(f"MOTORVO: {len(motorvo_rows)}")
|
||||
|
||||
motorvo_headers = [
|
||||
'ID', 'DATUM', 'IDPACI', 'PRIJMENI', 'JMENO', 'RODCIS',
|
||||
'PorCislo', 'DatumVyd', 'DatKonec', 'DruhProh',
|
||||
'Posouzeni', 'ZpusobPodminka', 'SkupinaPodminka', 'Skupiny',
|
||||
'ePosudek', 'STAV', 'PRINTED', 'IDUZIV', 'CREATED'
|
||||
]
|
||||
ws = wb.create_sheet("Posudky řidičák")
|
||||
ws.append(motorvo_headers)
|
||||
|
||||
epos_col_idx = motorvo_headers.index('ePosudek') + 1
|
||||
|
||||
for i, row in enumerate(motorvo_rows, start=2):
|
||||
(hid, datum, idpac, prijmeni, jmeno, rodcis,
|
||||
data_blob, porcislo, stav, printed, iduziv, created) = row
|
||||
data = parse_data(data_blob)
|
||||
|
||||
if data.get('Posouzeni2') == 'T':
|
||||
posouzeni = 'nezpůsobilý'
|
||||
elif data.get('ZpusobPodminka') == 'B:1':
|
||||
posouzeni = 'způsobilý s podmínkou'
|
||||
elif data.get('Posouzeni') == 'T':
|
||||
posouzeni = 'způsobilý'
|
||||
else:
|
||||
posouzeni = ''
|
||||
|
||||
ws.append([
|
||||
hid, fmt(datum), idpac, fmt(prijmeni), fmt(jmeno), fmt(rodcis),
|
||||
fmt(porcislo or data.get('PorCislo', '')),
|
||||
parse_date(data.get('DatumVyd', '')),
|
||||
parse_date(data.get('DatKonec', '')),
|
||||
fmt(data.get('DruhProh', '')),
|
||||
posouzeni,
|
||||
fmt(data.get('ZpusobPodminka', '')),
|
||||
fmt(data.get('SkupinaPodminka', '')),
|
||||
fmt(data.get('ZpusobJe', '')),
|
||||
'ANO' if (idpac, datum) in eposmro_keys else 'NE',
|
||||
fmt(stav), fmt(printed), fmt(iduziv), fmt(created),
|
||||
])
|
||||
|
||||
if i % 2 == 0:
|
||||
for cell in ws[i]:
|
||||
cell.fill = ZEBRA_FILL
|
||||
cell = ws.cell(row=i, column=epos_col_idx)
|
||||
if cell.value == 'ANO':
|
||||
cell.fill = GREEN_FILL
|
||||
cell.font = GREEN_FONT
|
||||
|
||||
style_header(ws)
|
||||
ws.freeze_panes = 'A2'
|
||||
autofit_ws(ws)
|
||||
|
||||
# =====================
|
||||
# List: Posudky řidičák – EPOSMRO (elektronická podání)
|
||||
# =====================
|
||||
cur.execute("""
|
||||
SELECT h.ID, h.DATUM, h.IDPACI,
|
||||
k.PRIJMENI, k.JMENO, k.RODCIS,
|
||||
h.DATA, h.STAV, h.CREATED,
|
||||
e.ID_PODANI, e.ODESLANO, e.STATUS
|
||||
FROM HISTDOC h
|
||||
JOIN KAR k ON k.IDPAC = h.IDPACI
|
||||
LEFT JOIN HISTDOC_EPOSUDEK e ON e.ID_HISTDOC = h.ID
|
||||
WHERE h.TYP = 'EPOSMRO'
|
||||
ORDER BY h.ID DESC
|
||||
""")
|
||||
epos_rows = cur.fetchall()
|
||||
print(f"EPOSMRO: {len(epos_rows)}")
|
||||
|
||||
ws = wb.create_sheet("ePosudky registr")
|
||||
ws.append([
|
||||
'ID', 'DATUM', 'IDPACI', 'PRIJMENI', 'JMENO', 'RODCIS',
|
||||
'DatumVyd', 'DatKonec', 'DruhProhlidky', 'DruhPosudku',
|
||||
'Vysledek', 'StavPosudku', 'TypAkce',
|
||||
'STAV', 'CREATED', 'ID_PODANI', 'ODESLANO', 'STATUS_ODESL'
|
||||
])
|
||||
|
||||
for i, row in enumerate(epos_rows, start=2):
|
||||
(hid, datum, idpac, prijmeni, jmeno, rodcis,
|
||||
data_blob, stav, created, id_podani, odeslano, status_odesl) = row
|
||||
data = parse_data(data_blob)
|
||||
|
||||
ws.append([
|
||||
hid, fmt(datum), idpac, fmt(prijmeni), fmt(jmeno), fmt(rodcis),
|
||||
parse_date(data.get('DatumVystaveni', '')),
|
||||
parse_date(data.get('PlatnostDo', '')),
|
||||
fmt(data.get('DruhProhlidkyNazev', '')),
|
||||
fmt(data.get('DruhPosudkuNazev', '')),
|
||||
fmt(data.get('VysledekNazev', '')),
|
||||
fmt(data.get('StavPosudkuNazev', '')),
|
||||
fmt(data.get('TypAkceNazev', '')),
|
||||
fmt(stav), fmt(created), fmt(id_podani), fmt(odeslano), fmt(status_odesl),
|
||||
])
|
||||
|
||||
if i % 2 == 0:
|
||||
for cell in ws[i]:
|
||||
cell.fill = ZEBRA_FILL
|
||||
|
||||
style_header(ws)
|
||||
ws.freeze_panes = 'A2'
|
||||
autofit_ws(ws)
|
||||
|
||||
# =====================
|
||||
# Autofilter na všech listech
|
||||
# =====================
|
||||
for ws in wb.worksheets:
|
||||
ws.auto_filter.ref = f"A1:{get_column_letter(ws.max_column)}{ws.max_row}"
|
||||
|
||||
# =====================
|
||||
# Uložení
|
||||
# =====================
|
||||
con.close()
|
||||
wb.save(output_path)
|
||||
print(f"Uloženo: {output_path}")
|
||||
|
||||
# =====================
|
||||
# xlwings: autofit + centrování Recepty
|
||||
# =====================
|
||||
with xw.App(visible=False) as app:
|
||||
wb_xw = xw.Book(output_path)
|
||||
for sheet in wb_xw.sheets:
|
||||
sheet.autofit()
|
||||
for sloupec in ["C:C", "E:E", "F:F", "G:G", "I:I", "M:M", "N:N"]:
|
||||
wb_xw.sheets['Recepty'].range(sloupec).api.HorizontalAlignment = 3
|
||||
wb_xw.save()
|
||||
wb_xw.close()
|
||||
|
||||
print("Hotovo.")
|
||||
@@ -0,0 +1,321 @@
|
||||
# MedicusWithClaudePN – Pracovní neschopnosti
|
||||
|
||||
## Účel
|
||||
|
||||
Report aktivních pracovních neschopností pro MUDr. Buzalkovou Michaelu.
|
||||
Generuje PDF a odesílá na výchozí tiskárnu.
|
||||
|
||||
## Spuštění
|
||||
|
||||
```bash
|
||||
# Test – otevře PDF v prohlížeči, netiskne:
|
||||
python pn_report.py --no-print
|
||||
|
||||
# Ostrý provoz – vytiskne rovnou na výchozí tiskárnu:
|
||||
python pn_report.py
|
||||
```
|
||||
|
||||
## Požadavky
|
||||
|
||||
```bash
|
||||
pip install reportlab pywin32
|
||||
```
|
||||
|
||||
## SQL dotaz – aktivní PN
|
||||
|
||||
Zachycen přes Firebird trace přímo z Medicusu (přesná kopie logiky aplikace),
|
||||
doplněn o podotaz na poslední 14denní potvrzení z tabulky HPN.
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
nes.id,
|
||||
nes.idpac,
|
||||
TRIM(kar.prijmeni) || ', ' || TRIM(kar.jmeno) AS jmeno,
|
||||
kar.rodcis,
|
||||
nes.zacnes,
|
||||
nes.konnes,
|
||||
nes.diagno,
|
||||
COALESCE(nes.ecn, nes.cisnes) AS cisnes,
|
||||
(SELECT MAX(h.datum) FROM hpn h
|
||||
WHERE h.idnes = nes.id AND h.typ = '2' AND h.storno = 'F') AS posl_potvrzeni
|
||||
FROM nes, kar
|
||||
WHERE nes.zacnes <= current_date
|
||||
AND nes.konnes IS NULL
|
||||
AND nes.idpac = kar.idpac
|
||||
AND nes.pracne = 'A'
|
||||
AND nes.storno <> 'T'
|
||||
AND (
|
||||
NOT EXISTS (SELECT id FROM nesd WHERE nesd.idnes = nes.id)
|
||||
OR (SELECT FIRST 1 kam FROM nesd WHERE nesd.idnes = nes.id
|
||||
ORDER BY nesd.datum DESC, nesd.id DESC) = 'N'
|
||||
)
|
||||
ORDER BY kar.prijmeni ASC, kar.jmeno ASC
|
||||
```
|
||||
|
||||
### Klíčové podmínky
|
||||
|
||||
| Podmínka | Význam |
|
||||
|---|---|
|
||||
| `pracne = 'A'` | Pouze pracovní neschopnosti (ne jiné typy) |
|
||||
| `storno <> 'T'` | Vyřazení stornovaných záznamů |
|
||||
| `zacnes <= current_date` | PN již začala |
|
||||
| `konnes IS NULL` | PN dosud neskončila – datum konce nemůže být v budoucnosti (pravidlo ČSSZ), aktivní PN má vždy `konnes = NULL` |
|
||||
| `nesd` subquery | PN nebyla předána dál – poslední záznam `Kam = 'N'` = stále u pacienta |
|
||||
| `COALESCE(ecn, cisnes)` | Použije ECN (elektronické), jinak starší CISNES |
|
||||
|
||||
## Sloupce reportu
|
||||
|
||||
| Sloupec | Zdroj | Poznámka |
|
||||
|---|---|---|
|
||||
| # | – | Pořadové číslo |
|
||||
| Příjmení a jméno | KAR | |
|
||||
| Rod. číslo | KAR | |
|
||||
| Začátek PN | NES.ZACNES | |
|
||||
| Dnů | výpočet | Počet dní od začátku PN do dnes |
|
||||
| Diagnóza | NES.DIAGNO | |
|
||||
| Posl. potvrzení | HPN (TYP='2') | Datum posledního 14denního potvrzení |
|
||||
| Dní od potvr. | výpočet | Červeně pokud > 14 dní |
|
||||
|
||||
## Tabulka HPN – typy podání
|
||||
|
||||
| TYP | Význam |
|
||||
|---|---|
|
||||
| `H` | Hlášení neschopnosti (vznik PN) |
|
||||
| `1` | První zpráva |
|
||||
| `P` | **Průběžná zpráva = 14denní potvrzení trvání PN** |
|
||||
| `2` | Neznámý typ (2033 záznamů v DB, ale ne pro průběžná potvrzení) |
|
||||
| `C`, `Y`, `Z` | Vzácné typy (jednotky záznamů) |
|
||||
|
||||
Vazba: `HPN.IDNES → NES.ID`
|
||||
|
||||
---
|
||||
|
||||
## Jak Medicus zobrazuje PN daného pacienta (zachyceno z trace)
|
||||
|
||||
### 1. Seznam všech PN pacienta
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
ID, IDPAC, DATNES, CISNES, PODNIK, ADRESA, PROFES, ZACNES,
|
||||
KONNES, PRACNE, PRICINA, DIAGNO, KONDIA, PREDAN, IDUZI,
|
||||
VYSTAVIL, DATUKONNES, IDODD, IDPRAC, STORNO,
|
||||
DATVYCHOD, DATVYCHDO, VYCH1OD, VYCH1DO, VYCH2OD, VYCH2DO, VYCH3OD, VYCH3DO,
|
||||
DATOSETRENI, DATPRINAV, OMLUVENKA, RODCISNES,
|
||||
DatNastUstPece, DatUkonUstPece, ICPE, ECN, EPODANI,
|
||||
ADR_OBEC, ADR_CP, ADR_CO, ADR_DOD, ADR_PSC, ADR_STAT,
|
||||
ZAM_ADRESA, DATUKON_OSSZ, ZAMDRUH, STATDPNKOD,
|
||||
SOUHLAS_SSZKOD, SOUHLAS_SSZNAZ, SOUHLAS_DATUM,
|
||||
DGZMENA, CIZI, OSSZ, DUVOD_UKONCENI, UKON_OSSZ, PORUS_REZIMU,
|
||||
UKON_OSSZNAZ, ADR_ZMENA, coalesce(ECN, CISNES) as CISLO,
|
||||
ADR_ZMENA_DO, POTVRZENI_VYDANO, DATNAR, SPRAVCE_POJ,
|
||||
ZAM_OBEC, ZAM_CO, ZAM_CP, ZAM_DOD, ZAM_PSC, ZAM_STAT, ZAM_CCSZ_ID, ZAM_CSSZ_VARSYM,
|
||||
VYCHINDIVIDUAL, VERZE_DPN, LEKAR_VYSTAVIL, LEKAR_VYSTAVIL_ICPE,
|
||||
USEDATNAR, KONTAKT_TEL, KONTAKT_EMAIL,
|
||||
case when KONTAKT_TEL is not NULL then 'S'
|
||||
when KONTAKT_EMAIL is not null then 'E'
|
||||
else NULL end as NOTIFIKACE,
|
||||
coalesce(KONTAKT_TEL, KONTAKT_EMAIL) as NOTIFIKACE_KONTAKT
|
||||
FROM NES
|
||||
WHERE IDPAC = ?
|
||||
ORDER BY DATNES ASC, ID ASC
|
||||
```
|
||||
|
||||
### 2. Formuláře eNeschopenky pro vybranou PN (záložka "Formuláře eNeschopenky")
|
||||
|
||||
Zobrazuje pouze TYP `H`, `1`, `2` (ne `P` = propuštění).
|
||||
Pouze záznamy s vazbou na HISTDOC (`IDHISTDOC IS NOT NULL`).
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
HD.ID AS HISTDOCID,
|
||||
H.TYP AS HISTDOCTYP, -- H=hlášení, 1=první zpráva, 2=průběžná/potvrzení
|
||||
HD.DATUM AS DATZAD, -- Datum vystavení (z HISTDOC)
|
||||
H.DATUM AS DATPOD, -- Datum podání (z HPN)
|
||||
H.STAV,
|
||||
H.ODBAVENO,
|
||||
HD.TYP
|
||||
FROM HPN H
|
||||
JOIN HISTDOC HD ON H.IDHISTDOC = HD.ID
|
||||
WHERE H.IDNES = ?
|
||||
ORDER BY HD.DATUM ASC
|
||||
```
|
||||
|
||||
### 3. Rychlý přehled formulářů (bez HISTDOC)
|
||||
|
||||
Používá se pro zjištění stavu – vrací všechny záznamy TYP `H`, `1`, `2`:
|
||||
|
||||
```sql
|
||||
SELECT * FROM HPN
|
||||
WHERE IDNES = ?
|
||||
AND STORNO = 'F'
|
||||
AND TYP IN ('1', '2', 'H')
|
||||
ORDER BY DATUM DESC, CAS DESC, ID DESC
|
||||
```
|
||||
|
||||
### 4. TFHpnHistorie – formulář "Historie HPN" (acHpnHistorie)
|
||||
|
||||
Kompletní přehled všech HPN záznamů pro vybranou PN. Spouští se akcí `acHpnHistorie`.
|
||||
|
||||
```sql
|
||||
select h.ID, h.IDNES, h.IDPODANI, h.TYP, h.DATA, h.DATUM, h.CAS, h.ODPOVED,
|
||||
h.IDPRAC, h.IDUZI, h.UPRAVENO, h.OPRAVA_ID, h.STAV, h.STORNO,
|
||||
h.POR_CISLO, h.ID_CHYBY, h.OSSZ,
|
||||
n.CisNes, n.Ecn, coalesce(n.CisNes, n.Ecn) as Cislo, n.IdPac,
|
||||
k.Prijmeni, k.Jmeno, k.Titul, coalesce(n.RODCISNES, k.RODCIS) as RODCIS,
|
||||
u.Zkratka, hp.Odeslano, hp.CorelationId,
|
||||
(select first 1 h2.ID from HPN h2 where h2.OPRAVA_ID = h.ID) as IdOpravy,
|
||||
h.verze_dpn,
|
||||
n.SPRAVCE_POJ,
|
||||
n.ADRESA as ULICE, n.ADR_CP, n.ADR_CO, n.ADR_DOD, n.ADR_OBEC, n.ADR_PSC, n.ADR_STAT,
|
||||
n.PODNIK, n.PROFES, n.ZAM_ADRESA, n.ZAM_CP, n.ZAM_CO, n.ZAM_OBEC, n.ZAM_PSC, n.ZAM_STAT,
|
||||
n.ZACNES, n.DIAGNO, n.DATNES, n.PRICINA,
|
||||
n.KONNES, n.KONDIA, n.DATUKONNES,
|
||||
n.DATVYCHOD, n.VYCH1OD, n.VYCH1DO, n.VYCH2OD, n.VYCH2DO,
|
||||
n.PRICINA, n.ICPE, n.VYCH3OD, n.VYCH3DO, n.KONTAKT_TEL,
|
||||
h.IDHISTDOC, h.ODBAVENO,
|
||||
IIF((H.IDHISTDOC is not null),
|
||||
(select HD.TYP from HISTDOC HD where HD.ID = H.IDHISTDOC), null) as HISTDOCTYP
|
||||
from HPN h
|
||||
left join NES n on (n.id = h.idnes)
|
||||
left join KAR k on (k.IdPac = n.IdPac)
|
||||
left join UZIVATEL u on (u.IdUzi = h.IdUzi)
|
||||
left join HPN_PODANI hp on (hp.ID = h.IdPodani)
|
||||
where h.IdNes = ?
|
||||
ORDER BY h.Datum ASC, h.Cas ASC, h.Id ASC
|
||||
```
|
||||
|
||||
### 5. Kontrola čekajících podání (PN s neodeslanými HPN záznamy)
|
||||
|
||||
```sql
|
||||
select nes.id from nes
|
||||
where nes.idpac = ?
|
||||
and nes.storno = 'F'
|
||||
and nes.epodani = 'T'
|
||||
and nes.icpe = ?
|
||||
and coalesce(nes.verze_dpn, '') not in ('', 'p', 'o')
|
||||
and exists (
|
||||
select 1 from hpn
|
||||
where hpn.idnes = nes.id
|
||||
and hpn.storno = 'F'
|
||||
and hpn.typ in ('1', '2', 'H')
|
||||
and hpn.idpodani is null -- dosud neodesláno
|
||||
and hpn.stav <> 99
|
||||
and hpn.stav <> 10
|
||||
)
|
||||
```
|
||||
|
||||
### 6. Kontrola potvrzení vydaného tento měsíc (POTVRZENI_VYDANO)
|
||||
|
||||
Medicus kontroluje zda pro daného pacienta existuje PN aktivní alespoň 10 dní,
|
||||
u které ještě nebylo vydáno potvrzení v aktuálním měsíci:
|
||||
|
||||
```sql
|
||||
select first 1 ZACNES, CISNES, ID, POTVRZENI_VYDANO
|
||||
from NES
|
||||
where (IDPAC = ?)
|
||||
and (? >= ZACNES + 10) -- PN trvá alespoň 10 dní
|
||||
and ((KONNES is NULL) or (KONNES > ?))
|
||||
and (STORNO = 'F')
|
||||
and (
|
||||
extract(month from POTVRZENI_VYDANO) || extract(year from POTVRZENI_VYDANO)
|
||||
=
|
||||
extract(month from cast(? as date)) || extract(year from cast(? as date))
|
||||
)
|
||||
```
|
||||
|
||||
### 7. Vyhledání HPN záznamu podle ICPE v XML datech
|
||||
|
||||
Číslo `11031812` (ICPE lékaře) se hledá přímo v obsahu XML blobu `HPN.DATA`.
|
||||
Medicus takto identifikuje konkrétní HPN záznam při opravě nebo ověření stavu:
|
||||
|
||||
```sql
|
||||
-- Neodeslané nebo čekající záznamy:
|
||||
select h.id from HPN h
|
||||
left join HPN h2 on (h2.OPRAVA_ID = h.ID)
|
||||
where (h.storno = 'F')
|
||||
and ((h.stav in (0,1)) or (h.stav is NULL))
|
||||
and (h2.OPRAVA_ID is null)
|
||||
and (H.DATA containing '11031812') -- hledá ICPE v XML obsahu
|
||||
and (h.IDNES = ?)
|
||||
|
||||
-- Úspěšně odeslané záznamy (stav=1):
|
||||
select h.id from HPN h
|
||||
left join HPN h2 on (h2.OPRAVA_ID = h.ID)
|
||||
where (h.storno = 'F')
|
||||
and (h.stav = 1)
|
||||
and h2.OPRAVA_ID is null
|
||||
and (H.DATA containing '11031812')
|
||||
and h.IDNES = ?
|
||||
```
|
||||
|
||||
### 8. Předání/Převzetí (záložka "Předání/Převzetí")
|
||||
|
||||
```sql
|
||||
SELECT ID, IDNES, KAMODKUD, DATUM, KAM, ICZ, ICPE, ICO, JMENO_LEKARE
|
||||
FROM NESD
|
||||
WHERE IDNES = ?
|
||||
ORDER BY DATUM ASC, ID ASC
|
||||
```
|
||||
|
||||
### Poznámky
|
||||
|
||||
- **HISTDOC** – každé odeslání formuláře vytváří záznam v HISTDOC; HPN bez IDHISTDOC se v UI nezobrazí
|
||||
- **HPN.STAV** – stav podání (1 = odesláno/přijato)
|
||||
- **HPN.ODBAVENO** – příznak zpracování (`'F'` = ne, `'T'` = ano)
|
||||
- HPN záznamy TYP='2' (průběžná potvrzení) **nemají IDHISTDOC** – JOIN s HISTDOC by je odfiltroval. Pro datum posledního potvrzení v reportu proto používáme prostý MAX bez JOINu. HISTDOC mají pouze TYP='P' (ukončení PN).
|
||||
|
||||
---
|
||||
|
||||
## Vazby tabulky NES (zjištěno z DB)
|
||||
|
||||
### Formální FK constrainty
|
||||
|
||||
| Směr | Vazba | Popis |
|
||||
|---|---|---|
|
||||
| NES → KAR | `NES.IDPAC → KAR.IDPAC` | Každá neschopenka patří pacientovi v kartotéce |
|
||||
| HPN → NES | `HPN.IDNES → NES.ID` | Formuláře/hlášení HPN odkazují na konkrétní neschopenku |
|
||||
|
||||
### Logické vazby (bez FK constraintu)
|
||||
|
||||
| Tabulka | Pole | Poznámka |
|
||||
|---|---|---|
|
||||
| NESD | `NESD.IDNES → NES.ID` | Předání/převzetí PN – vazba jen kódem, ne constraintem |
|
||||
| HISTDOC | `HPN.IDHISTDOC → HISTDOC.ID` | Dokumenty k formulářům – vazba přes HPN |
|
||||
|
||||
### Indexy na NES
|
||||
|
||||
| Index | Unique | Pole |
|
||||
|---|---|---|
|
||||
| PK_NES | Ano | ID |
|
||||
| FK_NES_KAR | Ne | IDPAC |
|
||||
| NES_POTVRZENI_VYDANO | Ne | POTVRZENI_VYDANO |
|
||||
|
||||
### Poznámka
|
||||
|
||||
Medicus obecně používá minimum DB constraintů – většina vazeb je řešena aplikačním kódem.
|
||||
`NESD` a `HISTDOC` nemají formální FK na `NES`, přesto jsou klíčové pro zobrazení PN v UI.
|
||||
|
||||
---
|
||||
|
||||
## Červené zvýraznění
|
||||
|
||||
Sloupce "Posl. potvrzení" a "Dní od potvr." jsou červeně zvýrazněny pokud:
|
||||
- Od posledního potvrzení uplynulo více než 14 dní, nebo
|
||||
- PN nemá žádné potvrzení a trvá déle než 14 dní (zobrazí se s `(!)`)
|
||||
|
||||
## Tisk
|
||||
|
||||
Skript používá `win32api.ShellExecute` s příkazem `'print'` – odešle PDF
|
||||
na výchozí tiskárnu Windows.
|
||||
|
||||
## Automatizace
|
||||
|
||||
Plánované spouštění každé pondělí a pátek ráno přes Windows Task Scheduler
|
||||
– zatím nenastaveno, připravit až bude skript stabilní.
|
||||
|
||||
## Soubory
|
||||
|
||||
| Soubor | Obsah |
|
||||
|---|---|
|
||||
| `pn_report.py` | Hlavní skript – DB dotaz, generování PDF, tisk |
|
||||
| `PN.md` | Tento soubor – dokumentace |
|
||||
@@ -0,0 +1,320 @@
|
||||
"""
|
||||
pn_report.py – Report aktivních pracovních neschopností
|
||||
Generuje PDF a posílá na výchozí tiskárnu.
|
||||
|
||||
Spuštění:
|
||||
python pn_report.py # vytvoří PDF a vytiskne
|
||||
python pn_report.py --no-print # jen vytvoří PDF (pro testování)
|
||||
|
||||
Požadavky:
|
||||
pip install reportlab pywin32
|
||||
"""
|
||||
|
||||
import fdb
|
||||
import sys
|
||||
import os
|
||||
import tempfile
|
||||
from datetime import date, datetime
|
||||
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.units import cm
|
||||
from reportlab.lib.styles import getSampleStyleSheet
|
||||
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Konfigurace
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
DB_DSN = r'localhost:c:\medicus 3\data\medicus.fdb'
|
||||
DB_USER = 'SYSDBA'
|
||||
DB_PASS = 'masterkey'
|
||||
DB_CHARSET = 'win1250'
|
||||
|
||||
# Pokud chcete soubor uložit trvale, nastavte výstupní adresář:
|
||||
OUTPUT_DIR = None # None = dočasný soubor, smaže se po tisku
|
||||
#OUTPUT_DIR = r'u:\Dropbox\!!!Days\Downloads Z230'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dotaz – aktivní PN (konnes IS NULL nebo v budoucnosti, storno='F')
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
SQL = """
|
||||
SELECT
|
||||
nes.id AS idnes,
|
||||
nes.idpac,
|
||||
TRIM(kar.prijmeni) || ', ' || TRIM(kar.jmeno) AS jmeno,
|
||||
kar.rodcis,
|
||||
nes.zacnes,
|
||||
nes.konnes,
|
||||
nes.diagno,
|
||||
COALESCE(nes.ecn, nes.cisnes) AS cisnes,
|
||||
(SELECT MAX(h.datum) FROM hpn h
|
||||
WHERE h.idnes = nes.id AND h.typ = 'P' AND h.storno = 'F') AS posl_potvrzeni
|
||||
FROM nes, kar
|
||||
WHERE nes.zacnes <= current_date
|
||||
AND nes.konnes IS NULL
|
||||
AND nes.idpac = kar.idpac
|
||||
AND nes.pracne = 'A'
|
||||
AND nes.storno <> 'T'
|
||||
AND (
|
||||
NOT EXISTS (SELECT id FROM nesd WHERE nesd.idnes = nes.id)
|
||||
OR (SELECT FIRST 1 kam FROM nesd WHERE nesd.idnes = nes.id
|
||||
ORDER BY nesd.datum DESC, nesd.id DESC) = 'N'
|
||||
)
|
||||
ORDER BY kar.prijmeni ASC, kar.jmeno ASC
|
||||
"""
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Pomocné funkce
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def fmt_date(val):
|
||||
"""Datum → DD.MM.YYYY nebo prázdný řetězec."""
|
||||
if val is None:
|
||||
return ''
|
||||
if isinstance(val, (date, datetime)):
|
||||
return val.strftime('%d.%m.%Y')
|
||||
return str(val)
|
||||
|
||||
def fmt_str(val):
|
||||
if val is None:
|
||||
return ''
|
||||
return str(val).strip()
|
||||
|
||||
def delka_pn(zacnes, konnes):
|
||||
"""Počet dnů PN (od začátku do dnes / do konce)."""
|
||||
if zacnes is None:
|
||||
return ''
|
||||
end = konnes if konnes else date.today()
|
||||
if isinstance(zacnes, datetime):
|
||||
zacnes = zacnes.date()
|
||||
if isinstance(end, datetime):
|
||||
end = end.date()
|
||||
if isinstance(zacnes, date) and isinstance(end, date):
|
||||
days = (end - zacnes).days + 1
|
||||
return str(days)
|
||||
return ''
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Načtení fontu s českou diakritikou
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def register_font():
|
||||
"""
|
||||
Zkusí zaregistrovat DejaVuSans (umí win1250 znaky).
|
||||
Fallback: Helvetica (bez diakritiky – nouzové řešení).
|
||||
"""
|
||||
font_paths = [
|
||||
r'C:\Windows\Fonts\DejaVuSans.ttf',
|
||||
r'C:\Windows\Fonts\arial.ttf',
|
||||
r'C:\Windows\Fonts\segoeui.ttf',
|
||||
]
|
||||
for path in font_paths:
|
||||
if os.path.exists(path):
|
||||
name = os.path.splitext(os.path.basename(path))[0]
|
||||
try:
|
||||
pdfmetrics.registerFont(TTFont(name, path))
|
||||
pdfmetrics.registerFont(TTFont(name + '-Bold',
|
||||
path.replace('.ttf', 'bd.ttf') if 'arial' in path.lower()
|
||||
else path.replace('.ttf', '-Bold.ttf')
|
||||
if os.path.exists(path.replace('.ttf', '-Bold.ttf'))
|
||||
else path
|
||||
))
|
||||
return name, name + '-Bold'
|
||||
except Exception:
|
||||
continue
|
||||
return 'Helvetica', 'Helvetica-Bold'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Generování PDF
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def build_pdf(rows, output_path, font_name, font_bold):
|
||||
today_str = date.today().strftime('%d.%m.%Y')
|
||||
weekday_cs = ['pondělí', 'úterý', 'středa', 'čtvrtek', 'pátek', 'sobota', 'neděle']
|
||||
weekday = weekday_cs[date.today().weekday()]
|
||||
|
||||
doc = SimpleDocTemplate(
|
||||
output_path,
|
||||
pagesize=A4,
|
||||
topMargin=1.5*cm,
|
||||
bottomMargin=1.5*cm,
|
||||
leftMargin=1.5*cm,
|
||||
rightMargin=1.5*cm,
|
||||
)
|
||||
|
||||
styles = getSampleStyleSheet()
|
||||
|
||||
def para(text, size=10, bold=False, align='LEFT', color=colors.black):
|
||||
fn = font_bold if bold else font_name
|
||||
al = {'LEFT': 0, 'CENTER': 1, 'RIGHT': 2}.get(align, 0)
|
||||
from reportlab.platypus import Paragraph as P
|
||||
from reportlab.lib.styles import ParagraphStyle
|
||||
st = ParagraphStyle('x', fontName=fn, fontSize=size,
|
||||
textColor=color, alignment=al, leading=size*1.3)
|
||||
return P(text, st)
|
||||
|
||||
story = []
|
||||
|
||||
# Záhlaví
|
||||
story.append(para('MUDr. Buzalková Michaela – ordinace praktického lékaře',
|
||||
size=9, color=colors.grey))
|
||||
story.append(para(f'Aktivní pracovní neschopnosti',
|
||||
size=16, bold=True))
|
||||
story.append(para(f'Vytištěno: {weekday} {today_str} | Počet záznamů: {len(rows)}',
|
||||
size=9, color=colors.grey))
|
||||
story.append(Spacer(1, 0.4*cm))
|
||||
|
||||
if not rows:
|
||||
story.append(para('Žádné aktivní pracovní neschopnosti.', size=12))
|
||||
doc.build(story)
|
||||
return
|
||||
|
||||
# Záhlaví tabulky
|
||||
headers = ['#', 'Příjmení a jméno', 'Rod. číslo', 'Začátek PN',
|
||||
'Dnů', 'Diagnóza', 'Posl. potvrzení', 'Dní od potvr.']
|
||||
|
||||
col_widths = [0.7*cm, 5.5*cm, 2.8*cm, 2.4*cm, 1.4*cm, 2.2*cm, 3.0*cm, 2.2*cm]
|
||||
|
||||
table_data = [headers]
|
||||
overdue_rows = [] # indexy řádků kde je potvrzení po splatnosti
|
||||
|
||||
for idx, row in enumerate(rows, start=1):
|
||||
idnes, idpac, jmeno, rodcis, zacnes, konnes, diagno, cisnes, posl_potvrzeni = row
|
||||
|
||||
# Počet dní od posledního potvrzení
|
||||
if posl_potvrzeni is not None:
|
||||
pp = posl_potvrzeni.date() if isinstance(posl_potvrzeni, datetime) else posl_potvrzeni
|
||||
dni_od = (date.today() - pp).days
|
||||
dni_od_str = str(dni_od)
|
||||
if dni_od > 14:
|
||||
overdue_rows.append(idx + 1) # +1 kvůli záhlaví
|
||||
else:
|
||||
# Žádné potvrzení – počítáme od začátku PN
|
||||
zac = zacnes.date() if isinstance(zacnes, datetime) else zacnes
|
||||
if zac:
|
||||
dni_od = (date.today() - zac).days
|
||||
dni_od_str = str(dni_od) + ' (!)'
|
||||
if dni_od > 14:
|
||||
overdue_rows.append(idx + 1)
|
||||
else:
|
||||
dni_od_str = '—'
|
||||
|
||||
table_data.append([
|
||||
str(idx),
|
||||
fmt_str(jmeno),
|
||||
fmt_str(rodcis),
|
||||
fmt_date(zacnes),
|
||||
delka_pn(zacnes, konnes),
|
||||
fmt_str(diagno),
|
||||
fmt_date(posl_potvrzeni) if posl_potvrzeni else '—',
|
||||
dni_od_str,
|
||||
])
|
||||
|
||||
tbl = Table(table_data, colWidths=col_widths, repeatRows=1)
|
||||
|
||||
style = TableStyle([
|
||||
# Záhlaví
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#2F5496')),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
||||
('FONTNAME', (0, 0), (-1, 0), font_bold),
|
||||
('FONTSIZE', (0, 0), (-1, 0), 8),
|
||||
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
|
||||
('BOTTOMPADDING',(0, 0), (-1, 0), 5),
|
||||
('TOPPADDING', (0, 0), (-1, 0), 5),
|
||||
# Data
|
||||
('FONTNAME', (0, 1), (-1, -1), font_name),
|
||||
('FONTSIZE', (0, 1), (-1, -1), 8),
|
||||
('ALIGN', (0, 1), (0, -1), 'CENTER'), # #
|
||||
('ALIGN', (5, 1), (5, -1), 'RIGHT'), # Dnů
|
||||
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
||||
('TOPPADDING', (0, 1), (-1, -1), 3),
|
||||
('BOTTOMPADDING',(0, 1), (-1, -1), 3),
|
||||
# Mřížka
|
||||
('GRID', (0, 0), (-1, -1), 0.3, colors.HexColor('#AAAAAA')),
|
||||
('LINEBELOW', (0, 0), (-1, 0), 1, colors.HexColor('#2F5496')),
|
||||
# Zebra
|
||||
*[('BACKGROUND', (0, i), (-1, i), colors.HexColor('#DCE6F1'))
|
||||
for i in range(2, len(table_data), 2)],
|
||||
# Červené zvýraznění – potvrzení po splatnosti (> 14 dní)
|
||||
*[('BACKGROUND', (6, i), (7, i), colors.HexColor('#F4CCCC'))
|
||||
for i in overdue_rows],
|
||||
*[('TEXTCOLOR', (6, i), (7, i), colors.HexColor('#CC0000'))
|
||||
for i in overdue_rows],
|
||||
*[('FONTNAME', (6, i), (7, i), font_bold)
|
||||
for i in overdue_rows],
|
||||
])
|
||||
tbl.setStyle(style)
|
||||
story.append(tbl)
|
||||
|
||||
# Patička
|
||||
story.append(Spacer(1, 0.5*cm))
|
||||
story.append(para(f'--- konec reportu ({len(rows)} záznamů) ---',
|
||||
size=8, color=colors.grey, align='CENTER'))
|
||||
|
||||
doc.build(story)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tisk
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def print_pdf(path):
|
||||
"""Pošle PDF na výchozí tiskárnu přes Windows ShellExecute."""
|
||||
try:
|
||||
import win32api
|
||||
win32api.ShellExecute(0, 'print', path, None, '.', 0)
|
||||
print(f'Odesláno na tiskárnu: {path}')
|
||||
except ImportError:
|
||||
# Fallback – otevře soubor v PDF prohlížeči (ruční tisk)
|
||||
print('pywin32 není nainstalován, otevírám PDF...')
|
||||
os.startfile(path)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def main():
|
||||
no_print = '--no-print' in sys.argv
|
||||
|
||||
# Připojení k DB
|
||||
print('Připojuji se k DB...')
|
||||
conn = fdb.connect(dsn=DB_DSN, user=DB_USER, password=DB_PASS, charset=DB_CHARSET)
|
||||
cur = conn.cursor()
|
||||
|
||||
print('Načítám aktivní PN...')
|
||||
cur.execute(SQL)
|
||||
rows = cur.fetchall()
|
||||
conn.close()
|
||||
print(f'Nalezeno {len(rows)} aktivních PN.')
|
||||
|
||||
# Font
|
||||
font_name, font_bold = register_font()
|
||||
print(f'Font: {font_name}')
|
||||
|
||||
# Výstupní soubor
|
||||
if OUTPUT_DIR:
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
out_path = os.path.join(OUTPUT_DIR,
|
||||
date.today().strftime('%Y-%m-%d') + '_pn_report.pdf')
|
||||
else:
|
||||
fd, out_path = tempfile.mkstemp(suffix='_pn_report.pdf')
|
||||
os.close(fd)
|
||||
|
||||
print(f'Generuji PDF: {out_path}')
|
||||
build_pdf(rows, out_path, font_name, font_bold)
|
||||
print('PDF hotovo.')
|
||||
|
||||
if no_print:
|
||||
print('(tisk přeskočen – --no-print)')
|
||||
# Otevřeme pro náhled
|
||||
os.startfile(out_path)
|
||||
else:
|
||||
print_pdf(out_path)
|
||||
# Dočasný soubor necháme – tiskárna ho potřebuje přečíst
|
||||
# Windows ho smaže sám po zpracování tisku (temp adresář)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,28 @@
|
||||
import fdb, sys
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
conn = fdb.connect(dsn=r'localhost:c:\medicus 3\data\medicus.fdb', user='SYSDBA', password='masterkey', charset='win1250')
|
||||
cur = conn.cursor()
|
||||
|
||||
sql = """
|
||||
SELECT nes.id, TRIM(kar.prijmeni) || ', ' || TRIM(kar.jmeno) AS jmeno,
|
||||
nes.zacnes,
|
||||
(SELECT MAX(h.datum) FROM hpn h
|
||||
WHERE h.idnes = nes.id AND h.typ = 'P' AND h.storno = 'F') AS posl_potvrzeni
|
||||
FROM nes, kar
|
||||
WHERE nes.zacnes <= current_date
|
||||
AND nes.konnes IS NULL
|
||||
AND nes.idpac = kar.idpac
|
||||
AND nes.pracne = 'A'
|
||||
AND nes.storno <> 'T'
|
||||
AND (
|
||||
NOT EXISTS (SELECT id FROM nesd WHERE nesd.idnes = nes.id)
|
||||
OR (SELECT FIRST 1 kam FROM nesd WHERE nesd.idnes = nes.id
|
||||
ORDER BY nesd.datum DESC, nesd.id DESC) = 'N'
|
||||
)
|
||||
ORDER BY kar.prijmeni ASC
|
||||
"""
|
||||
|
||||
cur.execute(sql)
|
||||
for row in cur.fetchall():
|
||||
print(row)
|
||||
conn.close()
|
||||
Binary file not shown.
@@ -0,0 +1,117 @@
|
||||
import sys, io
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
import fdb
|
||||
import openpyxl
|
||||
from openpyxl.styles import Font, PatternFill, Alignment
|
||||
from datetime import date, timedelta
|
||||
import os
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
zacatek = date(2025, 1, 1)
|
||||
konec = date.today()
|
||||
dny = []
|
||||
d = zacatek
|
||||
while d <= konec:
|
||||
dny.append(d)
|
||||
d += timedelta(days=1)
|
||||
|
||||
print(f"Počítám {len(dny)} dní ({zacatek} – {konec})...")
|
||||
|
||||
vysledky = []
|
||||
for i, den in enumerate(dny):
|
||||
# Počet registrovaných
|
||||
cur.execute(f"""
|
||||
SELECT COUNT(*) FROM KAR
|
||||
WHERE vyrazen = 'N'
|
||||
AND EXISTS (
|
||||
SELECT id FROM registr r
|
||||
JOIN icp i ON r.idicp = i.idicp
|
||||
WHERE r.idpac = kar.idpac
|
||||
AND r.datum <= '{den}'
|
||||
AND (r.datum_zruseni IS NULL OR r.datum_zruseni >= '{den}')
|
||||
AND r.priznak IN ('V','D','A')
|
||||
AND i.icp = '09305001'
|
||||
AND i.odb = '001'
|
||||
)
|
||||
""")
|
||||
pocet = cur.fetchone()[0]
|
||||
|
||||
# Zaregistrovaní tento den
|
||||
cur.execute(f"""
|
||||
SELECT k.RODCIS, k.PRIJMENI, k.JMENO
|
||||
FROM REGISTR r JOIN KAR k ON k.IDPAC = r.IDPAC
|
||||
WHERE r.datum = '{den}'
|
||||
AND r.priznak IN ('V','D','A')
|
||||
ORDER BY k.PRIJMENI, k.JMENO
|
||||
""")
|
||||
zaregistrovani = [f"{row[0]} {row[1].strip()} {row[2]}" for row in cur.fetchall()]
|
||||
|
||||
# Odregistrovaní tento den
|
||||
cur.execute(f"""
|
||||
SELECT k.RODCIS, k.PRIJMENI, k.JMENO
|
||||
FROM REGISTR r JOIN KAR k ON k.IDPAC = r.IDPAC
|
||||
WHERE r.datum_zruseni = '{den}'
|
||||
ORDER BY k.PRIJMENI, k.JMENO
|
||||
""")
|
||||
odregistrovani = [f"{row[0]} {row[1].strip()} {row[2]}" for row in cur.fetchall()]
|
||||
|
||||
vysledky.append((den, pocet, zaregistrovani, odregistrovani))
|
||||
if (i + 1) % 30 == 0:
|
||||
print(f" {i+1}/{len(dny)}: {den} → {pocet}")
|
||||
|
||||
conn.close()
|
||||
|
||||
# Excel
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "Registrace"
|
||||
|
||||
hlavicka_font = Font(bold=True, color="FFFFFF")
|
||||
hlavicka_fill = PatternFill("solid", fgColor="2E75B6")
|
||||
ws.column_dimensions['A'].width = 14
|
||||
ws.column_dimensions['B'].width = 14
|
||||
ws.column_dimensions['C'].width = 10
|
||||
ws.column_dimensions['D'].width = 45
|
||||
ws.column_dimensions['E'].width = 45
|
||||
|
||||
for col, nazev in enumerate(['Datum', 'Registrovaných', 'Změna', 'Zaregistrováno', 'Odregistrováno'], start=1):
|
||||
cell = ws.cell(row=1, column=col, value=nazev)
|
||||
cell.font = hlavicka_font
|
||||
cell.fill = hlavicka_fill
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
|
||||
predchozi = None
|
||||
for row_i, (den, pocet, zaregistrovani, odregistrovani) in enumerate(vysledky, start=2):
|
||||
ws.cell(row=row_i, column=1, value=den).number_format = 'DD.MM.YYYY'
|
||||
ws.cell(row=row_i, column=2, value=pocet).alignment = Alignment(horizontal='center')
|
||||
|
||||
if predchozi is not None:
|
||||
zmena = pocet - predchozi
|
||||
cell = ws.cell(row=row_i, column=3, value=zmena)
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
if zmena > 0:
|
||||
cell.font = Font(color="00AA00", bold=True)
|
||||
elif zmena < 0:
|
||||
cell.font = Font(color="CC0000", bold=True)
|
||||
predchozi = pocet
|
||||
|
||||
if zaregistrovani:
|
||||
cell = ws.cell(row=row_i, column=4, value="\n".join(zaregistrovani))
|
||||
cell.alignment = Alignment(wrap_text=True, vertical='top')
|
||||
cell.font = Font(color="00AA00")
|
||||
|
||||
if odregistrovani:
|
||||
cell = ws.cell(row=row_i, column=5, value="\n".join(odregistrovani))
|
||||
cell.alignment = Alignment(wrap_text=True, vertical='top')
|
||||
cell.font = Font(color="CC0000")
|
||||
|
||||
ws.freeze_panes = 'A2'
|
||||
|
||||
vystup = os.path.join(os.path.dirname(__file__), 'registrace_2025_dnes.xlsx')
|
||||
wb.save(vystup)
|
||||
print(f"\nUloženo: {vystup}")
|
||||
@@ -0,0 +1,143 @@
|
||||
# MedicusWithClaudePosudek – poznámky pro Clauda
|
||||
|
||||
## O co jde
|
||||
|
||||
Lékařské posudky vystavované MUDr. Buzalkovou. Prozatím řešíme posudky k řízení motorových vozidel.
|
||||
|
||||
Nový zákon ukládá povinnost odesílat posudky k řízení do **centrálního registru** – tuto funkci Medicus přidal v aktualizaci z konce března 2026.
|
||||
|
||||
---
|
||||
|
||||
## Tabulky
|
||||
|
||||
### HISTDOC – hlavní tabulka pro všechny posudky
|
||||
|
||||
Všechny posudky jsou záznamy v `HISTDOC`, lišící se hodnotou sloupce `TYP`.
|
||||
|
||||
Klíčové sloupce:
|
||||
| Sloupec | Popis |
|
||||
|---|---|
|
||||
| `ID` | primární klíč |
|
||||
| `TYP` | typ dokumentu (viz níže) |
|
||||
| `DATUM` | datum vystavení posudku |
|
||||
| `IDPACI` | FK → KAR.IDPAC (pacient) |
|
||||
| `DATA` | obsah posudku – text ve formátu key=value (viz níže) |
|
||||
| `PORCISLO` | pořadové číslo posudku (= PorCislo v DATA) |
|
||||
| `STAV` | stav záznamu (Z = zavřeno) |
|
||||
| `PRINTED` | T/F – byl vytištěn |
|
||||
| `IDUZIV` | FK → UZIVATEL.IDUZI – kdo vystavil (4 = MUDr. Buzalková) |
|
||||
| `CREATED` | timestamp vytvoření záznamu |
|
||||
|
||||
**Vazba:** žádná přímá vazba na jiné tabulky (vyšetření, dekurz apod.) – posudek je svébytný dokument.
|
||||
|
||||
### TYP hodnoty relevantní pro posudky řidičů
|
||||
|
||||
| TYP | Popis | Počet (k 2026-03-31) |
|
||||
|---|---|---|
|
||||
| `MOTORVO` | ruční posudek k řízení motorových vozidel | 1530 |
|
||||
| `EPOSMRO` | elektronické podání posudku do centrálního registru | 2 |
|
||||
|
||||
Ostatní typy posudků v HISTDOC (pro referenci):
|
||||
- `ZBROJPR`, `ZBROJP2` – zbrojní průkaz
|
||||
- `ZPUPRN` – způsobilost pro práci
|
||||
- `ZDRSTA3`–`ZDRSTA5`, `ZDRSTAV`, `ZDRINF` – zdravotní stav (různé varianty)
|
||||
- ... (celkem desítky typů)
|
||||
|
||||
### HISTDOC_EPOSUDEK – evidence odeslání do registru
|
||||
|
||||
Doplňková tabulka k EPOSMRO záznamům v HISTDOC.
|
||||
|
||||
| Sloupec | Popis |
|
||||
|---|---|
|
||||
| `ID_HISTDOC` | FK → HISTDOC.ID (záznam EPOSMRO) |
|
||||
| `ID_PODANI` | UUID přidělené centrálním registrem |
|
||||
| `ODESLANO` | timestamp odeslání |
|
||||
| `STATUS` | O = odesláno |
|
||||
| `VERZE` | verze záznamu (base64 interní hodnota) |
|
||||
|
||||
### VS_POSUDKY – prázdná, zatím nepoužívaná
|
||||
|
||||
Sloupce: ID, IDPAC, DATA (BLOB), DATUM, POSTYPE. Pravděpodobně připravena pro budoucí využití.
|
||||
|
||||
---
|
||||
|
||||
## Workflow: ruční posudek → elektronické podání
|
||||
|
||||
1. Lékař v Medicusu vyplní posudek → vznikne `HISTDOC` TYP=`MOTORVO`
|
||||
2. Medicus automaticky odešle do centrálního registru → vznikne `HISTDOC` TYP=`EPOSMRO` + záznam v `HISTDOC_EPOSUDEK`
|
||||
3. Oba záznamy mají stejné `IDPACI` + `DATUM` → podle toho je párujeme
|
||||
|
||||
Příklad (pacient Vráček, 30.3.2026):
|
||||
- HISTDOC ID=34743, TYP=MOTORVO, CREATED=13:12
|
||||
- HISTDOC ID=34746, TYP=EPOSMRO, CREATED=13:21
|
||||
- HISTDOC_EPOSUDEK: STATUS=O, ODESLANO=13:21
|
||||
|
||||
---
|
||||
|
||||
## Formát DATA (key=value) – MOTORVO
|
||||
|
||||
```
|
||||
JmenoPac=Radomil Vráček
|
||||
DatNar=D:27.03.1956
|
||||
Prukaz=207069669 ← číslo řidičského průkazu
|
||||
DatKonec=D:30.03.2028 ← platnost posudku do
|
||||
DatumVyd=D:30.03.2026 ← datum vydání
|
||||
Bydliste=K Šafránce 507/16, 19000 Praha 9-Střížkov
|
||||
DruhProh=periodická ← druh prohlídky
|
||||
Posouzeni=T ← T = způsobilý (F = nezpůsobilý?)
|
||||
Posouzeni2=F ← T = nezpůsobilý (druhá volba)
|
||||
ZpusobJe=B:0 ← skupiny bez podmínky
|
||||
ZpusobPodminka=B:1 ← B:1 = má podmínku
|
||||
SkupinaPodminka=sk. B brýle
|
||||
PorCislo=2600037
|
||||
KonecDleZakona=D
|
||||
DatumPrevzeti=D:30.03.2026
|
||||
```
|
||||
|
||||
**Výsledek posouzení** (kombinace Posouzeni + Posouzeni2 + ZpusobPodminka):
|
||||
- `Posouzeni=T` + `Posouzeni2=F` + `ZpusobPodminka=B:0` → způsobilý
|
||||
- `Posouzeni=T` + `Posouzeni2=F` + `ZpusobPodminka=B:1` → způsobilý s podmínkou
|
||||
- `Posouzeni=T` + `Posouzeni2=T` → nezpůsobilý
|
||||
|
||||
## Formát DATA (key=value) – EPOSMRO
|
||||
|
||||
```
|
||||
Lekar=MUDr. Michaela Buzalková
|
||||
KRZPID=130153584 ← ID lékaře v registru
|
||||
ICO=68366370
|
||||
ICP=09305001
|
||||
Pacient=Radomil Vráček
|
||||
RID=8705636888 ← číslo řidičáku
|
||||
DatumNarozeni=D:27.03.1956
|
||||
StavPosudkuKodVerze=zneplatneny|1.0.0
|
||||
StavPosudkuNazev=Zneplatněný ← stav posudku v registru
|
||||
TypAkceNazev=vytvoření
|
||||
TypAkceKodVerze=akce_ro_1|1.0.0
|
||||
DruhProhlidkyNazev=pravidelná
|
||||
DruhProhlidkyKodVerze=Pravidelna|1.0.0
|
||||
DruhPosudkuNazev=řidičské oprávnění pro seniory
|
||||
DruhPosudkuKodVerze=SenioriRo|1.0.0
|
||||
SkupinaZadatelRidicNazev=skupina 1
|
||||
SkupinyRidicskehoOpravneniSeznam=B
|
||||
HarmonizovaneNarodniKody=$:~HNK1:011:01.01 Brýle5:01.012:HK1:B0: ← kódy omezení (brýle)
|
||||
VysledekKodVerze=ZpusobilySPodminkou|1.0.0
|
||||
VysledekNazev=způsobilý s podmínkou
|
||||
DatumVystaveni=D:30.03.2026
|
||||
PlatnostDo=D:30.03.2028
|
||||
```
|
||||
|
||||
**StavPosudku = "Zneplatněný"** neznamená chybu – jde o akci, kdy lékař odvolá způsobilost pacienta (např. po mrtvici, epileptickém záchvatu apod.). Medicus pak odešle do registru zneplatnění existujícího posudku.
|
||||
|
||||
---
|
||||
|
||||
## Soubory v projektu
|
||||
|
||||
- `posudky_report.py` – generuje Excel s listy MOTORVO a EPOSMRO
|
||||
- `CLAUDE_NOTES.md` – tento soubor
|
||||
|
||||
## Report (posudky_report.py)
|
||||
|
||||
- Výstup: `u:\Dropbox\!!!Days\Downloads Z230\YYYY-MM-DD_HH-MM-SS_Přehled posudků řidičák.xlsx`
|
||||
- Maže předchozí verzi před zápisem nové
|
||||
- List MOTORVO: 1530 záznamů, sloupec `ePosudek` = ANO (zeleně) / NE podle toho, zda byl odeslán ePosudek (párování IDPACI + DATUM)
|
||||
- List EPOSMRO: 2 záznamy, detail elektronického podání
|
||||
@@ -0,0 +1,237 @@
|
||||
import fdb
|
||||
import openpyxl
|
||||
from openpyxl.styles import Font, PatternFill, Alignment
|
||||
from openpyxl.utils import get_column_letter
|
||||
from datetime import datetime
|
||||
import os
|
||||
import sys
|
||||
|
||||
# --- Připojení ---
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
# --- Výstupní soubor ---
|
||||
output_dir = r'u:\Dropbox\!!!Days\Downloads Z230'
|
||||
now = datetime.now()
|
||||
filename = now.strftime('%Y-%m-%d_%H-%M-%S') + '_Přehled posudků řidičák.xlsx'
|
||||
output_path = os.path.join(output_dir, filename)
|
||||
|
||||
# --- Smazání předchozích verzí ---
|
||||
for f in os.listdir(output_dir):
|
||||
if f.endswith('_Přehled posudků řidičák.xlsx'):
|
||||
os.remove(os.path.join(output_dir, f))
|
||||
|
||||
wb = openpyxl.Workbook()
|
||||
|
||||
# =====================
|
||||
# Pomocné funkce
|
||||
# =====================
|
||||
|
||||
HEADER_FILL = PatternFill('solid', fgColor='2F5496')
|
||||
HEADER_FONT = Font(bold=True, color='FFFFFF')
|
||||
ZEBRA_FILL = PatternFill('solid', fgColor='DCE6F1')
|
||||
GREEN_FILL = PatternFill('solid', fgColor='C6EFCE')
|
||||
GREEN_FONT = Font(bold=True, color='276221')
|
||||
|
||||
def style_header(ws):
|
||||
for cell in ws[1]:
|
||||
cell.fill = HEADER_FILL
|
||||
cell.font = HEADER_FONT
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
|
||||
def autofit(ws):
|
||||
for col in ws.columns:
|
||||
max_len = max((len(str(cell.value)) if cell.value is not None else 0) for cell in col)
|
||||
ws.column_dimensions[get_column_letter(col[0].column)].width = min(max_len + 2, 50)
|
||||
|
||||
def fmt(val):
|
||||
if val is None:
|
||||
return ''
|
||||
return val
|
||||
|
||||
def parse_data(data_str):
|
||||
"""Parsuje key=value text z HISTDOC.DATA do slovníku."""
|
||||
result = {}
|
||||
if not data_str:
|
||||
return result
|
||||
for line in data_str.splitlines():
|
||||
if '=' in line:
|
||||
key, _, val = line.partition('=')
|
||||
result[key.strip()] = val.strip()
|
||||
return result
|
||||
|
||||
def parse_date(val):
|
||||
"""Převede 'D:DD.MM.YYYY' na datetime.date, nebo vrátí původní hodnotu."""
|
||||
if val and val.startswith('D:'):
|
||||
try:
|
||||
return datetime.strptime(val[2:], '%d.%m.%Y').date()
|
||||
except ValueError:
|
||||
return val
|
||||
return val
|
||||
|
||||
# =====================
|
||||
# List 1 – MOTORVO (ruční posudky k řízení)
|
||||
# =====================
|
||||
|
||||
ws1 = wb.active
|
||||
ws1.title = 'MOTORVO'
|
||||
|
||||
# Množina (IDPACI, DATUM) kde existuje EPOSMRO
|
||||
cur.execute("""
|
||||
SELECT IDPACI, DATUM FROM HISTDOC WHERE TYP = 'EPOSMRO'
|
||||
""")
|
||||
eposmro_keys = set((r[0], r[1]) for r in cur.fetchall())
|
||||
|
||||
cur.execute("""
|
||||
SELECT h.ID, h.DATUM, h.IDPACI,
|
||||
k.PRIJMENI, k.JMENO, k.RODCIS,
|
||||
h.DATA, h.PORCISLO, h.STAV, h.PRINTED, h.IDUZIV, h.CREATED
|
||||
FROM HISTDOC h
|
||||
JOIN KAR k ON k.IDPAC = h.IDPACI
|
||||
WHERE h.TYP = 'MOTORVO'
|
||||
ORDER BY h.ID DESC
|
||||
""")
|
||||
raw_rows = cur.fetchall()
|
||||
|
||||
headers = [
|
||||
'ID', 'DATUM', 'IDPACI', 'PRIJMENI', 'JMENO', 'RODCIS',
|
||||
'PorCislo', 'DatumVyd', 'DatKonec', 'DruhProh',
|
||||
'Posouzeni', 'ZpusobPodminka', 'SkupinaPodminka',
|
||||
'Skupiny',
|
||||
'ePosudek',
|
||||
'STAV', 'PRINTED', 'IDUZIV', 'CREATED'
|
||||
]
|
||||
ws1.append(headers)
|
||||
|
||||
for i, row in enumerate(raw_rows, start=2):
|
||||
(hid, datum, idpac, prijmeni, jmeno, rodcis,
|
||||
data_blob, porcislo, stav, printed, iduziv, created) = row
|
||||
|
||||
data = parse_data(data_blob)
|
||||
|
||||
posouzeni = ''
|
||||
if data.get('Posouzeni') == 'T':
|
||||
if data.get('Posouzeni2') == 'T':
|
||||
posouzeni = 'nezpůsobilý'
|
||||
elif data.get('ZpusobPodminka') == 'B:1':
|
||||
posouzeni = 'způsobilý s podmínkou'
|
||||
else:
|
||||
posouzeni = 'způsobilý'
|
||||
|
||||
skupiny = data.get('SkupinyRidicskehoOpravneniSeznam', '')
|
||||
if not skupiny:
|
||||
# MOTORVO nemá SkupinyRidicskehoOpravneniSeznam, zkusíme ZpusobJe
|
||||
skupiny = data.get('ZpusobJe', '')
|
||||
|
||||
ws1.append([
|
||||
hid,
|
||||
fmt(datum),
|
||||
idpac,
|
||||
fmt(prijmeni),
|
||||
fmt(jmeno),
|
||||
fmt(rodcis),
|
||||
fmt(porcislo or data.get('PorCislo', '')),
|
||||
parse_date(data.get('DatumVyd', '')),
|
||||
parse_date(data.get('DatKonec', '')),
|
||||
fmt(data.get('DruhProh', '')),
|
||||
posouzeni,
|
||||
fmt(data.get('ZpusobPodminka', '')),
|
||||
fmt(data.get('SkupinaPodminka', '')),
|
||||
fmt(skupiny),
|
||||
'ANO' if (idpac, datum) in eposmro_keys else 'NE',
|
||||
fmt(stav),
|
||||
fmt(printed),
|
||||
fmt(iduziv),
|
||||
fmt(created),
|
||||
])
|
||||
|
||||
if i % 2 == 0:
|
||||
for cell in ws1[i]:
|
||||
cell.fill = ZEBRA_FILL
|
||||
|
||||
# Sloupec ePosudek – zvýraznit ANO zeleně
|
||||
epos_col = headers.index('ePosudek') + 1
|
||||
cell = ws1.cell(row=i, column=epos_col)
|
||||
if cell.value == 'ANO':
|
||||
cell.fill = GREEN_FILL
|
||||
cell.font = GREEN_FONT
|
||||
|
||||
style_header(ws1)
|
||||
ws1.freeze_panes = 'A2'
|
||||
autofit(ws1)
|
||||
|
||||
# =====================
|
||||
# List 2 – EPOSMRO (elektronická podání do registru)
|
||||
# =====================
|
||||
|
||||
ws2 = wb.create_sheet('EPOSMRO')
|
||||
|
||||
cur.execute("""
|
||||
SELECT h.ID, h.DATUM, h.IDPACI,
|
||||
k.PRIJMENI, k.JMENO, k.RODCIS,
|
||||
h.DATA, h.STAV, h.CREATED,
|
||||
e.ID_PODANI, e.ODESLANO, e.STATUS
|
||||
FROM HISTDOC h
|
||||
JOIN KAR k ON k.IDPAC = h.IDPACI
|
||||
LEFT JOIN HISTDOC_EPOSUDEK e ON e.ID_HISTDOC = h.ID
|
||||
WHERE h.TYP = 'EPOSMRO'
|
||||
ORDER BY h.ID DESC
|
||||
""")
|
||||
epos_rows = cur.fetchall()
|
||||
|
||||
headers2 = [
|
||||
'ID', 'DATUM', 'IDPACI', 'PRIJMENI', 'JMENO', 'RODCIS',
|
||||
'DatumVyd', 'DatKonec', 'DruhProhlidky', 'DruhPosudku',
|
||||
'Vysledek', 'StavPosudku', 'TypAkce',
|
||||
'STAV', 'CREATED',
|
||||
'ID_PODANI', 'ODESLANO', 'STATUS_ODESL'
|
||||
]
|
||||
ws2.append(headers2)
|
||||
|
||||
for i, row in enumerate(epos_rows, start=2):
|
||||
(hid, datum, idpac, prijmeni, jmeno, rodcis,
|
||||
data_blob, stav, created,
|
||||
id_podani, odeslano, status_odesl) = row
|
||||
|
||||
data = parse_data(data_blob)
|
||||
|
||||
ws2.append([
|
||||
hid,
|
||||
fmt(datum),
|
||||
idpac,
|
||||
fmt(prijmeni),
|
||||
fmt(jmeno),
|
||||
fmt(rodcis),
|
||||
parse_date(data.get('DatumVystaveni', '')),
|
||||
parse_date(data.get('PlatnostDo', '')),
|
||||
fmt(data.get('DruhProhlidkyNazev', '')),
|
||||
fmt(data.get('DruhPosudkuNazev', '')),
|
||||
fmt(data.get('VysledekNazev', '')),
|
||||
fmt(data.get('StavPosudkuNazev', '')),
|
||||
fmt(data.get('TypAkceNazev', '')),
|
||||
fmt(stav),
|
||||
fmt(created),
|
||||
fmt(id_podani),
|
||||
fmt(odeslano),
|
||||
fmt(status_odesl),
|
||||
])
|
||||
|
||||
if i % 2 == 0:
|
||||
for cell in ws2[i]:
|
||||
cell.fill = ZEBRA_FILL
|
||||
|
||||
style_header(ws2)
|
||||
ws2.freeze_panes = 'A2'
|
||||
autofit(ws2)
|
||||
|
||||
# =====================
|
||||
# Uložení
|
||||
# =====================
|
||||
|
||||
conn.close()
|
||||
wb.save(output_path)
|
||||
sys.stdout.buffer.write(f'Ulozeno: {output_path}\n'.encode('utf-8'))
|
||||
sys.stdout.buffer.write(f'MOTORVO: {len(raw_rows)} radku, EPOSMRO: {len(epos_rows)} radku\n'.encode('utf-8'))
|
||||
@@ -0,0 +1,252 @@
|
||||
# Fakturace a dávky – poznámky pro Clauda
|
||||
|
||||
## Přehled tabulek
|
||||
|
||||
### FAK – faktury pojišťovnám
|
||||
- **844 záznamů** (k 2026-03-28)
|
||||
- Primární klíč: `ID`
|
||||
|
||||
| Sloupec | Popis |
|
||||
|---|---|
|
||||
| `ID` | primární klíč |
|
||||
| `CISFAK` | číslo faktury (10 znaků) |
|
||||
| `POJ` | pojišťovna (3 znaky, např. 111=VZP, 205=ČPZP, 207=OZP) |
|
||||
| `ICZ`, `ICZ1` | IČZ ordinace |
|
||||
| `IDICZ` | FK → ICZ.IDICZ (jen jedna ordinace, neřešit) |
|
||||
| `PORCISLO` | pořadové číslo |
|
||||
| `DATUMOD`, `DATUMDO` | období faktury (od–do) |
|
||||
| `DATVYS` | datum vystavení |
|
||||
| `DATODE` | datum odeslání |
|
||||
| `OBDOB` | období (5 znaků, např. "20260") |
|
||||
| `VYKONY` | částka za výkony (Kč) |
|
||||
| `KAPITACE` | kapitační platba (Kč) |
|
||||
| `ZALOHA` | záloha |
|
||||
| `CENA` | celková cena faktury (Kč) |
|
||||
| `DRUH` | druh faktury (např. "konečná") |
|
||||
| `TYP` | typ (1 znak) |
|
||||
| `ROK` | rok |
|
||||
| `SPLAT` | datum splatnosti |
|
||||
| `PROPLACENO` | datum proplacení (NULL = nezaplaceno) |
|
||||
| `ZAPLACENO` | zaplacená částka |
|
||||
| `ZUM` | ? |
|
||||
| `HOSPAUSAL` | hospitalizační/paušální? |
|
||||
| `FDAVKA` | BLOB – odkaz na dávku |
|
||||
| `KAPDETAIL`, `AGRDETAIL` | BLOBy – detaily kapitace/agregace |
|
||||
| `NAZFAK`, `POZFAK`, `OBDFAK` | název, poznámka, období faktury (texty) |
|
||||
| `ICO`, `BANKA`, `UCET` | IČO, banka, účet |
|
||||
| `ODJMENO/ULICE/MISTO/PSC` | adresa odesílatele |
|
||||
| `PLNAZEV/ULICE/MISTO/PSC` | adresa plátce (pojišťovny) |
|
||||
| `DRUHPOJ` | druh pojištění |
|
||||
|
||||
---
|
||||
|
||||
### FAKDET – finanční detail faktury
|
||||
- **1021 záznamů**
|
||||
- Vazba: `IDFAK` → FAK.ID
|
||||
- Typicky 1–2 řádky na fakturu (breakdown podle lékaře/IDUZI)
|
||||
|
||||
| Sloupec | Popis |
|
||||
|---|---|
|
||||
| `ID` | primární klíč |
|
||||
| `IDFAK` | FK → FAK.ID |
|
||||
| `ICP` | IČP ordinace (např. 09305001) |
|
||||
| `ODB` | odbornost (001 = praktický lékař) |
|
||||
| `IDUZI` | FK → UZIVATEL.IDUZI – který lékař |
|
||||
| `CENAVYK` | výkony (Kč) |
|
||||
| `CENALEC` | léky (Kč) |
|
||||
| `CENAKAP` | kapitace (Kč) |
|
||||
|
||||
**Poznámka:** IDUZI=0 = systémový záznam (kapitace bez konkrétního lékaře).
|
||||
|
||||
---
|
||||
|
||||
### FAKDAV – dávky zahrnuté ve faktuře
|
||||
- **1296 záznamů**
|
||||
- Vazba: `IDFAK` → FAK.ID
|
||||
|
||||
| Sloupec | Popis |
|
||||
|---|---|
|
||||
| `ID` | primární klíč |
|
||||
| `IDFAK` | FK → FAK.ID |
|
||||
| `CISDAV` | číslo dávky |
|
||||
| `DRUH` | druh dávky |
|
||||
| `CENA` | celková cena dávky |
|
||||
| `CENAVYK` | výkony |
|
||||
| `CENALEC` | léky |
|
||||
| `CENAPAU` | paušál |
|
||||
| `ODB` | odbornost |
|
||||
| `ICP` | IČP |
|
||||
|
||||
---
|
||||
|
||||
### PORTAL – elektronické podání pojišťovně
|
||||
- **180 záznamů**
|
||||
- Vazba: `IDFAK` → FAK.ID
|
||||
|
||||
| Sloupec | Popis |
|
||||
|---|---|
|
||||
| `ID` | primární klíč |
|
||||
| `IDFAK` | FK → FAK.ID |
|
||||
| `ODESLANO` | timestamp odeslání |
|
||||
| `DATA`, `KDAVKA`, `FDAVKA` | BLOBy – pravděpodobně XML dávky |
|
||||
| `CHYBA` | příznak chyby (1 znak) |
|
||||
| `STAV` | stav podání (smallint) |
|
||||
| `ID_PODANI` | ID podání u pojišťovny (32 znaků, UUID) |
|
||||
| `IDPODANI` | interní ID podání |
|
||||
| `IDCERT` | certifikát použitý pro podání |
|
||||
| `DAVKA_ROK`, `DAVKA_DISK`, `DAVKA_IDICZ` | metadata dávky |
|
||||
| `BB_DAVKA`, `BB_FAKTURA` | ? |
|
||||
| `DAVKA_DATUMOD`, `DAVKA_DATUMDO` | období dávky |
|
||||
| `DAVKA_CASTKA` | částka dávky |
|
||||
|
||||
---
|
||||
|
||||
## Schéma vazeb
|
||||
|
||||
```
|
||||
FAK (faktura)
|
||||
├── FAKDET (IDFAK → FAK.ID) – finanční breakdown podle lékaře
|
||||
├── FAKDAV (IDFAK → FAK.ID) – seznam dávek ve faktuře
|
||||
├── PORTAL (IDFAK → FAK.ID) – viz níže, IDFAK bývá NULL
|
||||
└── ICZ (IDICZ → ICZ.IDICZ) – ordinace (jen jedna, neřešit)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## UZIVATEL – lékaři a uživatelé
|
||||
|
||||
| IDUZI | Příjmení | Jméno | Zkratka | Aktivní |
|
||||
|---|---|---|---|---|
|
||||
| 2 | Jourová | Jana | JOU | F (neaktivní) |
|
||||
| 3 | Sestra | — | SES | F (neaktivní) |
|
||||
| 4 | Buzalková | Michaela | MBU | T – **lékařka, manželka** |
|
||||
| 5 | Ševčíková | Ivana | ISE | T |
|
||||
| 6 | Buzalka | Vladimír | VBU | T – **uživatel (já)** |
|
||||
|
||||
---
|
||||
|
||||
## Ukázkové záznamy
|
||||
|
||||
### FAK ID=856 (poslední)
|
||||
- Faktura č. 260026, pojišťovna **205 (ČPZP)**, prosinec 2025
|
||||
- Vystavena: 2026-03-12, druh: konečná
|
||||
- VYKONY: 2 528,99 Kč, KAPITACE: 0, CENA: **2 528,99 Kč**
|
||||
- ZAPLACENO: NULL, PROPLACENO: NULL
|
||||
|
||||
FAKDET k FAK ID=856:
|
||||
| ID | IDUZI | CENAVYK | CENALEC | CENAKAP |
|
||||
|---|---|---|---|---|
|
||||
| 1034 | 0 (systém) | 0 | 0 | 1 902,54 |
|
||||
| 1035 | 4 (Buzalková) | 2 528,99 | 0 | 0 |
|
||||
|
||||
### FAK ID=855 (předposlední)
|
||||
- Faktura č. 260026, pojišťovna **207 (OZP)**, prosinec 2025
|
||||
- Vystavena: 2026-03-01, druh: konečná
|
||||
- VYKONY: 85 Kč, KAPITACE: 0, CENA: **85 Kč**
|
||||
- ZAPLACENO: NULL, PROPLACENO: NULL
|
||||
|
||||
FAKDET k FAK ID=855:
|
||||
| ID | IDUZI | CENAVYK | CENALEC | CENAKAP |
|
||||
|---|---|---|---|---|
|
||||
| 1033 | 6 (Buzalka Vladimír) | 85 | 0 | 0 |
|
||||
|
||||
---
|
||||
|
||||
## PORTAL – upřesnění (zjištěno 2026-03-28)
|
||||
|
||||
PORTAL **nesouvisí s fakturací**. Je to samostatná agenda – evidence **registračních dávek** odeslaných pojišťovně.
|
||||
|
||||
### Co PORTAL skutečně je
|
||||
- Medicus přes PORTAL odesílá pojišťovně dávky s přihlášením/odhlášením pacientů k lékaři (MUDr. Buzalková jako praktický lékař)
|
||||
- Bez peněz – čistě administrativní agenda registrací
|
||||
- **IDFAK = NULL** u všech registračních podání (není vazba na fakturu)
|
||||
|
||||
### Klíčové sloupce
|
||||
| Sloupec | Popis |
|
||||
|---|---|
|
||||
| `KDAVKA` | odeslaná dávka – pevný formát (hlavička + řádky I = pacienti, RC, datum) |
|
||||
| `DATA` | odpověď pojišťovny jako XML (`<statuscode>100</statuscode>` = OK) |
|
||||
| `ID_PODANI` | ID přidělené pojišťovnou (např. `D01F260118593316.D01`) |
|
||||
| `DAVKA_DISK` | číslo dávky v roce (sekvenční) |
|
||||
| `DAVKA_ROK` | rok dávky |
|
||||
| `DAVKA_DATUMOD/DO` | období registrací v dávce |
|
||||
| `CHYBA` | F = bez chyby |
|
||||
|
||||
### Ukázka KDAVKA (hlavička dávky)
|
||||
```
|
||||
DP80093050000900202506 15 1 0 0.00180:6.2.46
|
||||
H21109305001 179202506001
|
||||
I 1Příjmení Jméno RC datum_registrace
|
||||
I 2...
|
||||
```
|
||||
|
||||
### Ukázka DATA (odpověď pojišťovny)
|
||||
```xml
|
||||
<ekomunikace timestamp="20260127 063105">
|
||||
<session id="ff59b436-...">
|
||||
<statusline>Přijetí registrací proběhlo úspěšně</statusline>
|
||||
<statuscode>100</statuscode>
|
||||
<idpodani>D01F260118593316.D01</idpodani>
|
||||
</session>
|
||||
</ekomunikace>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PORTAL – dva typy dávek
|
||||
|
||||
### Typ 1 – Registrační dávka (DP80)
|
||||
- KDAVKA začíná `DP80`
|
||||
- **IDFAK = NULL** – bez vazby na fakturu
|
||||
- Bez peněz (`DAVKA_CASTKA` = NULL)
|
||||
- Přihlašuje/odhlašuje pacienty k lékaři (MUDr. Buzalková)
|
||||
- Řádky `I` = pacient (RC, datum registrace)
|
||||
|
||||
### Typ 2 – Výkonová dávka (DP98)
|
||||
- KDAVKA začíná `DP98`
|
||||
- **IDFAK = ID faktury** – napojeno na FAK
|
||||
- Má peníze (`DAVKA_CASTKA`, např. 15 452,91 Kč)
|
||||
- `STAV = 10` (registrační mají NULL)
|
||||
- Obsahuje výkony vykázané pojišťovně za dané období
|
||||
|
||||
**Struktura výkonové dávky (KDAVKA):**
|
||||
```
|
||||
DP98... – hlavička dávky (IČP, rok, měsíc, disk, počet případů, částka)
|
||||
A ... – ambulantní případ (RC pacienta, diagnóza)
|
||||
V ... – výkon (datum, kód výkonu, body)
|
||||
Z ... – ZULP (zvlášť účtovaný léčivý přípravek)
|
||||
L ... – lékový řádek (kód, množství, cena)
|
||||
DP05... – druhá část dávky (prevence / jiný typ)
|
||||
P ... – preventivní prohlídka
|
||||
```
|
||||
|
||||
**Odpověď pojišťovny pro výkonovou dávku:**
|
||||
```xml
|
||||
<statusline>Přijetí faktury proběhlo úspěšně</statusline>
|
||||
<statuscode>100</statuscode>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Poznámky
|
||||
- Faktury jsou vystavovány zvlášť pro každou pojišťovnu
|
||||
- FAKDET má řádky zvlášť pro každého lékaře (IDUZI)
|
||||
- IDUZI=0 v FAKDET = systémový záznam pro kapitaci
|
||||
- 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
|
||||
|
||||
## 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í).
|
||||
|
||||
fdb je čte přes connection charset win1250 a vrací je jako Python `str` (špatně dekódované).
|
||||
|
||||
**Správný postup dekódování:**
|
||||
```python
|
||||
# fdb vrátil string dekódovaný jako win1250 – musíme to zvrátit
|
||||
raw_bytes = s.encode('cp1250', errors='replace')
|
||||
spravny_text = raw_bytes.decode('cp852', errors='replace')
|
||||
```
|
||||
|
||||
- **KDAVKA, FDAVKA** → cp852
|
||||
- **DATA** (XML odpověď pojišťovny) → cp1250 (win1250), fdb dekóduje správně
|
||||
@@ -0,0 +1,316 @@
|
||||
# MedicusWithClaudeSelects – SQL dotazy
|
||||
|
||||
## Registrovaní pacienti
|
||||
|
||||
Přesný select který Medicus používá pro záložku **Registrovaní** (zachycen přes FBScanner, dotaz č. 143).
|
||||
|
||||
### Počet registrovaných pacientů
|
||||
|
||||
```sql
|
||||
SELECT COUNT(*) FROM KAR
|
||||
WHERE (vyrazen = 'N')
|
||||
AND EXISTS (
|
||||
SELECT id FROM registr r
|
||||
JOIN icp i ON r.idicp = i.idicp
|
||||
WHERE r.idpac = kar.idpac
|
||||
AND (r.datum <= '2026-03-20')
|
||||
AND (r.datum_zruseni IS NULL OR r.datum_zruseni >= '2026-03-20')
|
||||
AND (r.priznak IN ('V','D','A'))
|
||||
AND (i.icp = '09305001')
|
||||
AND (i.odb = '001')
|
||||
)
|
||||
```
|
||||
|
||||
Vrátí: **1618 pacientů** (ověřit na Windows).
|
||||
|
||||
### Podmínky registrace – vysvětlení
|
||||
|
||||
- `vyrazen = 'N'` – pacient není vyřazen z kartotéky
|
||||
- `r.datum <= dnes` – registrace již začala
|
||||
- `r.datum_zruseni IS NULL OR r.datum_zruseni >= dnes` – registrace dosud platí
|
||||
- `r.priznak IN ('V','D','A')` – aktivní příznak (ne 'Z' = zrušen, ne 'N')
|
||||
- `i.icp = '09305001'` – IČP naší ordinace
|
||||
- `i.odb = '001'` – odbornost praktický lékař
|
||||
|
||||
### Skript pro Python
|
||||
|
||||
Viz `count_registrovani.py` v této složce – spustit na Windows.
|
||||
|
||||
### Plný select Medicusu (seznam pacientů s metadaty)
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
KAR.DATNAR,
|
||||
KAR.IDPAC,
|
||||
KAR.INFORMACE,
|
||||
KAR.INFORMACE_COL,
|
||||
KAR.JMENO,
|
||||
KAR.POHLAVI,
|
||||
GPP.POJ,
|
||||
KAR.POZNAMKA,
|
||||
KAR.PRIJMENI,
|
||||
KAR.PRIJMENI_UP,
|
||||
(SELECT DATUM_REGISTRACE FROM SP_GETREGDAT(kar.IDPAC)) AS REGDATUM,
|
||||
KAR.REGISTROVAL,
|
||||
(SELECT PRIZNAK FROM SP_GETREGDAT(kar.IDPAC)) AS REGPRIZNAK,
|
||||
KAR.RODCIS,
|
||||
KAR.ROZENA,
|
||||
KAR.TITUL,
|
||||
KAR.TITULZA,
|
||||
KAR.TRVOBEC,
|
||||
KAR.TRVPSC,
|
||||
KAR.TRVULICE,
|
||||
KAR.VYRAZEN
|
||||
FROM KAR
|
||||
LEFT JOIN GETPACPOJ(KAR.IDPAC, '2026-03-20') GPP ON GPP.IDPAC = KAR.IDPAC
|
||||
WHERE (vyrazen = 'N')
|
||||
AND EXISTS (
|
||||
SELECT id FROM registr r
|
||||
JOIN icp i ON r.idicp = i.idicp
|
||||
WHERE r.idpac = kar.idpac
|
||||
AND (r.datum <= '2026-03-20')
|
||||
AND (r.datum_zruseni IS NULL OR r.datum_zruseni >= '2026-03-20')
|
||||
AND (r.priznak IN ('V','D','A'))
|
||||
AND (i.icp = '09305001')
|
||||
AND (i.odb = '001')
|
||||
)
|
||||
ORDER BY KAR.PRIJMENI_UP ASC, KAR.RODCIS ASC
|
||||
```
|
||||
|
||||
Poznámka: `GETPACPOJ` a `SP_GETREGDAT` jsou uložené procedury Medicusu –
|
||||
fungují v kontextu Firebird připojení přes SYSDBA/masterkey.
|
||||
|
||||
---
|
||||
|
||||
## Panel pacienta – hlavní UNION dotaz
|
||||
|
||||
Zachycen přes Firebird AuditTrace (`default_trace.log`) při otevření karty pacienta
|
||||
(IDPAC=9733, datum 28.03.2026). Dotaz vrací **28 UNION částí** – každá část má pevnou
|
||||
strukturu sloupců: `ID, VAR1, VAR2, DATE1, DATE2, TIME1, INT1, TEXT1, NUM1, NUM2`.
|
||||
|
||||
Parametry při volání:
|
||||
- `:IDPAC` – ID pacienta (např. 9733)
|
||||
- `:RODCIS` – rodné číslo pacienta (např. '0308020152')
|
||||
- `:DATUM` – dnešní datum ve formátu YYYY-MM-DD (např. '2026-03-28')
|
||||
- `:DATUM_CZ` – dnešní datum ve formátu DD.MM.YYYY (např. '28.03.2026')
|
||||
- `:ROK` – aktuální rok (např. '2026')
|
||||
|
||||
### Mapování UNION části → položka panelu Medicusu
|
||||
|
||||
| ID (UNION část) | Tabulka | Položka v panelu Medicusu |
|
||||
|------------------|--------------------|--------------------------------------------------|
|
||||
| `BalickyPac` | BALICKYPAC | Balíčky pacienta (aktivní/budoucí) |
|
||||
| `Dluh` | PLA / PLADET | Dluh (nezaplacené faktury po splatnosti) |
|
||||
| `SouhlasPac` | HISTDOC / SOUHLASPACSABL | Souhlasy pacienta |
|
||||
| `sCenaVykZUM` | DOKLADD / LECD | Cena výkonů v aktuálním roce (ZUM) |
|
||||
| `Registrl` | KAR | Kdo registroval pacienta |
|
||||
| `OseLekPrak` | KARUZIV_SEL | Ošetřující lékař (odb. 001/002) |
|
||||
| `SledLek` | SLEDLEK | Sledující lékař (specialista) |
|
||||
| `HistDoc` | HISTDOC | Historie dokumentů (posledních 10) |
|
||||
| `LastSms` | SMS | Datum poslední odeslané SMS |
|
||||
| `PozadLekar` | DOKLADH | Požadující lékař (EICZ) z posledního dokladu |
|
||||
| `Prilohy` | FILES | Přílohy pacienta (posledních 10) |
|
||||
| `Objednavky` | OBJOBJ | Objednávky od dnešního dne |
|
||||
| `OseLek` | KARUZIV_SEL | Všichni lékaři přiřazení ke kartě |
|
||||
| `PeProhlidky` | PREH / PREINIH | Preventivní a examinační prohlídky |
|
||||
| `Medikace` | MEDIKACE | Aktuální medikace (platná k dnešku) |
|
||||
| `NextDispenz` | DISPAC / DISSKU | Příští dispenzarizace (s termínem) |
|
||||
| `Dispenz` | DISPAC / DISSKU | Dispenzarizace – všechny záznamy |
|
||||
| `Prohlidky` | PREPRI / PREINIH | Preventivní prohlídky – záznamy |
|
||||
| `NextOck` | OCKPRI / KLK | Příští očkování (plánované) |
|
||||
| `LastVykon` | DOKLADD / DOKLADH | Poslední výkon (kód + datum) |
|
||||
| `LastDekurs` | DEKURS | Poslední dekurz (datum) |
|
||||
| `Karta` | KAR | Informace a poznámka z karty |
|
||||
| `Saldo` | PACIENT_SALDO(SP) | Saldo pacienta (funkce) |
|
||||
| `Anamneza` | ANAMNEZA | Anamnéza + krevní skupina |
|
||||
| `Ockovani` | OCKZAZ | Očkovací záznamy (max 20, seskupeno po látce) |
|
||||
| `NeschopenOd` | NES | Aktuální neschopenka (od – do) |
|
||||
| `Alergie` | ANAMNEZA | Alergie (z posledního záznamu anamnézy) |
|
||||
| `Pojistovna` | ICP / ICZ | IDICP naší ordinace pro pojišťovnu pacienta |
|
||||
|
||||
### SQL dotaz (parametrizovaný)
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
cast('BalickyPac' as varchar(11)) as ID, substring(cast(BPAC.KOD as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(BPAC.CENPASMO as VARCHAR(70)) from 1 for 30) as VAR2, cast(BPAC.DATUMOD as DATE) as DATE1, cast(BPAC.DATUMDO as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from BALICKYPAC BPAC left join SP_BALICKYPAC_PRIZNAK(BPAC.ID, ':DATUM_CZ') PRI on 1 = 1 where BPAC.IDPAC = :IDPAC and PRI.PRIZNAK in ('A', 'B')
|
||||
|
||||
UNION SELECT
|
||||
cast('Dluh' as varchar(11)) as ID, substring(cast(P.MENA as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast((SUM(P.CENA - P.SLEVAC) - (SUM(P.PLATBA) + SUM((COALESCE((select SUM(case ZD.TYP when 'R' then ZD.CELKEM else -ZD.CELKEM end) from PLADET ZD where ZD.IDPLA = P.IDPLA and (ZD.TYP <> P.DOKLADTYP) and (ZD.TYP <> '|') and ((ZD.CENA < 0) or (ZD.TYP = 'R'))), 0))))) as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 FROM PLA P WHERE (P.IDPAC = :IDPAC) AND (P.DOKLADTYP = 'F') AND (P.STORNO IS NULL) AND (P.NENISALDO = 'F') AND ((P.SPLATNOST IS NULL) OR (P.SPLATNOST < ':DATUM')) AND (P.VALID = 'F') GROUP BY P.MENA
|
||||
|
||||
UNION SELECT
|
||||
cast('SouhlasPac' as varchar(11)) as ID, substring(cast(case when S.NAZEV is null then case when H.TYP = 'ZSOUPOS' then 'Souhlas/Nesouhlas s poskytnutím zdravotních služeb nezletilému' when H.TYP = 'ZSOUPOZ' then 'Souhlas zákonného zástupce nezletilého pacienta staršího 15ti let' when H.TYP = 'ZSOUPO2' then 'Nesouhlas s poskytnutím zdravotních služeb - povinné očkování' when H.TYP = 'ZPOSIN2' then 'Určení osoby oprávněné dle zákona o zdravotních službách' when H.TYP = 'OdmPece' then 'Prohlášení o odmítnutí zdravotní péče pacientem - Negativní revers' end else S.NAZEV end as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(H.DATUM as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from HISTDOC H left join SOUHLASPACSABL S on H.IDSOUHLASPACSABL = S.ID where H.TYP in ('IndSou', 'ZSOUPOS', 'ZSOUPOZ', 'ZSOUPO2', 'ZPOSIN2', 'OdmPece') and H.IDPACI = :IDPAC
|
||||
|
||||
UNION SELECT
|
||||
cast('sCenaVykZUM' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(coalesce(sum(cenabod),0) + coalesce(sum(cenamat),0) as NUMERIC(15,2)) as NUM1, cast((select coalesce(sum(cena),0) from LECD d where d.RODCIS = ':RODCIS' and extract(year from d.DATOSE) = ':ROK' and ((d.KAT is null) or (d.KAT <> 'N')) and exists (select h.IDLEC from LECH h where h.IDLEC = d.IDLEC and h.POJ = '207' and h.ICZ in ('09305001'))) as NUMERIC(15,2)) as NUM2 from DOKLADD d where d.RODCIS = ':RODCIS' and extract(year from d.DATOSE) = ':ROK' and ((d.KAT is null) or (d.KAT <> 'N' and d.KAT <> 'K' and d.KAT <> 'A')) and exists (select h.IDHLAV from DOKLADH h where h.IDHLAV = d.IDHLAV and h.POJ = '207' and h.ICZ in ('09305001'))
|
||||
|
||||
UNION SELECT
|
||||
cast('Registrl' as varchar(11)) as ID, substring(cast(REGISTROVAL as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from KAR where IDPAC = :IDPAC
|
||||
|
||||
UNION SELECT
|
||||
cast('OseLekPrak' as varchar(11)) as ID, substring(cast(F_CONCAT(PRIJMENI, F_CONCAT(JMENO, TITUL, ', '), ' ') as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(TITUL2 as VARCHAR(70)) from 1 for 30) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(CAST(ODBORN as INTEGER) as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 FROM KARUZIV_SEL(:IDPAC, 'T') WHERE ODBORN in ('001', '002')
|
||||
|
||||
UNION SELECT
|
||||
cast('SledLek' as varchar(11)) as ID, substring(cast(KOD as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(LEK as VARCHAR(70)) from 1 for 30) as VAR2, cast(DATUM as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from SLEDLEK where IDPAC = :IDPAC and DATUM <= ':DATUM'
|
||||
|
||||
UNION SELECT
|
||||
first 10 cast('HistDoc' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(DATUM as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from HISTDOC where IDPACI = :IDPAC and STAV is NULL and IDZARPR = 2 and IDODDPR = 2 and IDPRACPR = 2
|
||||
|
||||
UNION SELECT
|
||||
first 1 cast('LastSms' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(MAX(SENDTIME) as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from SMS where PACID = :IDPAC and SENDTIME is not NULL and not(STATUS in (100,1000))
|
||||
|
||||
UNION SELECT
|
||||
first 1 cast('PozadLekar' as varchar(11)) as ID, substring(cast(H.EICZ as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(H.EODZ as VARCHAR(70)) from 1 for 30) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from DOKLADH H where H.IDHLAV = (select first 1 I.IDHLAV from DOKLADH I where I.RODCIS = ':RODCIS' and I.EICZ is not NULL order by I.IDHLAV desc)
|
||||
|
||||
UNION SELECT
|
||||
first 10 cast('Prilohy' as varchar(11)) as ID, substring(cast(FILENAME as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from FILES where IDPAC = :IDPAC
|
||||
|
||||
UNION SELECT
|
||||
first 10 cast('Objednavky' as varchar(11)) as ID, substring(cast(F_CONCAT(U.PRIJMENI, F_CONCAT(U.JMENO, U.TITUL, ', '), ' ') as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(PRAC as VARCHAR(70)) from 1 for 30) as VAR2, cast(DATUM as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(CAS as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from OBJOBJ O join PRACOVISTE P on (P.ID = O.IDPRAC) join UZIVATEL U on (U.IDUZI = O.IDUZI) where IDPAC = :IDPAC and DATUM >= ':DATUM_CZ'
|
||||
|
||||
UNION SELECT
|
||||
cast('OseLek' as varchar(11)) as ID, substring(cast(F_CONCAT(PRIJMENI, F_CONCAT(JMENO, TITUL, ', '), ' ') as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(TITUL2 as VARCHAR(70)) from 1 for 30) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 FROM KARUZIV_SEL(:IDPAC, 'T')
|
||||
|
||||
UNION SELECT
|
||||
cast('PeProhlidky' as varchar(11)) as ID, substring(cast(NAZEV as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(DATUM as DATE) as DATE1, cast(TERMIN as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from PREH join PREINIH on (PREH.IDPREINI = PREINIH.IDPREINI) where IDPAC = :IDPAC
|
||||
|
||||
UNION SELECT
|
||||
cast('Medikace' as varchar(11)) as ID, substring(cast(NAZ as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(PLATI_OD as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from MEDIKACE where IDPAC = :IDPAC and PLATI_OD <= ':DATUM_CZ' and (PLATI_DO >= ':DATUM_CZ' or PLATI_DO is NULL)
|
||||
|
||||
UNION SELECT
|
||||
cast('NextDispenz' as varchar(11)) as ID, substring(cast(NAZEV as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(SKUPINA as VARCHAR(70)) from 1 for 30) as VAR2, cast(PRISTI as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from DISPAC join DISSKU on (DISSKU.IDDIS = DISPAC.IDDIS) where IDPAC = :IDPAC and PRISTI is not NULL
|
||||
|
||||
UNION SELECT
|
||||
cast('Dispenz' as varchar(11)) as ID, substring(cast(NAZEV as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(SKUPINA as VARCHAR(70)) from 1 for 30) as VAR2, cast(DATZAR as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from DISPAC join DISSKU on (DISSKU.IDDIS = DISPAC.IDDIS) where IDPAC = :IDPAC
|
||||
|
||||
UNION SELECT
|
||||
cast('Prohlidky' as varchar(11)) as ID, substring(cast(NAZEV as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(DATUM as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from PREPRI join PREINIH on (PREPRI.IDPREINI = PREINIH.IDPREINI) where IDPAC = :IDPAC and datum is not null
|
||||
|
||||
UNION SELECT
|
||||
cast('NextOck' as varchar(11)) as ID, substring(cast(coalesce(NAZ,ZKRATKA) as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(DATUMD as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from OCKPRI o left join KLK k on o.ZKRATKA = k.KOD where IDPAC = :IDPAC
|
||||
|
||||
UNION SELECT
|
||||
first 1 cast('LastVykon' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, substring(cast(D.KOD as VARCHAR(70)) from 1 for 30) as VAR2, cast(D.DATOSE as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from DOKLADD D where D.ID = (select first 1 dd.id from dokladd dd join dokladh dh on (dh.idhlav = dd.idhlav) where dd.rodcis = ':RODCIS' and (dh.hodb = '001' or dh.hodb is null) order by dd.datose desc)
|
||||
|
||||
UNION SELECT
|
||||
first 1 cast('LastDekurs' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(MAX(DATUM) as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from DEKURS where IDPAC = :IDPAC and (IDPRAC = 2 or IDPRAC = -1)
|
||||
|
||||
UNION SELECT
|
||||
first 1 cast('Karta' as varchar(11)) as ID, substring(cast(INFORMACE as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(CIZINEC as VARCHAR(70)) from 1 for 30) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(INFORMACE_COL as INTEGER) as INT1, POZNAMKA as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from KAR where IDPAC = :IDPAC
|
||||
|
||||
UNION SELECT
|
||||
first 1 cast('Saldo' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(SALDO as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from PACIENT_SALDO(:IDPAC, 1, 0, 0)
|
||||
|
||||
UNION SELECT
|
||||
first 1 cast('Anamneza' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, substring(cast(KREVSKUP as VARCHAR(70)) from 1 for 30) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, ANAMNEZA as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from ANAMNEZA where ID = (select first 1 ID from ANAMNEZA where IDPAC = :IDPAC order by DATUM DESC, ID desc)
|
||||
|
||||
UNION SELECT
|
||||
first 20 cast('Ockovani' as varchar(11)) as ID, substring(cast(ockzaz.LATKA as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(ockzaz.ZKRATKA as VARCHAR(70)) from 1 for 30) as VAR2, cast(max(ockzaz.DATUM) as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from ockzaz where ockzaz.idpac = :IDPAC group by ockzaz.ZKRATKA, ockzaz.LATKA
|
||||
|
||||
UNION SELECT
|
||||
first 1 cast('NeschopenOd' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(MAX(ZACNES) as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from NES where (IDPAC = :IDPAC) and (ZACNES <= ':DATUM_CZ') and ((KONNES is NULL) or (KONNES > ':DATUM_CZ')) and (STORNO = 'F')
|
||||
|
||||
UNION SELECT
|
||||
first 1 cast('Alergie' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, ALERGIE as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from ANAMNEZA where IDPAC = :IDPAC and ID = (select first 1 ID from ANAMNEZA where IDPAC = :IDPAC and DATUM <= ':DATUM_CZ' order by DATUM desc, ID desc)
|
||||
|
||||
UNION SELECT
|
||||
first 1 cast('Pojistovna' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(P.IDICP as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from ICP P join ICZ Z on (Z.IDICZ = P.IDICZ) where Z.POJ = '207' and P.ODB = '001'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ošetřující lékař pacienta
|
||||
|
||||
### Kde je uložen
|
||||
|
||||
Ošetřující lékař není přímo v tabulce `KAR`. Je uložen v tabulce **`KARUZIV`** a čte se
|
||||
přes stored procedure **`KARUZIV_SEL`**.
|
||||
|
||||
### Tabulka KARUZIV
|
||||
|
||||
Vazba pacient → lékař. Jeden pacient může mít více záznamů (více lékařů/pracovišť).
|
||||
|
||||
| Sloupec | Popis |
|
||||
|------------|-------|
|
||||
| `IDPAC` | FK na KAR – pacient |
|
||||
| `IDLEKAR` | FK na LEKARI – externí lékař (specialista, cizí ordinace) |
|
||||
| `IDUZI` | FK na UZIVATEL – interní uživatel Medicusu (vlastní lékař) |
|
||||
| `IDPRAC` | FK na PRACOVISTE – pracoviště |
|
||||
| `IDODD` | FK na ODDEL – oddělení |
|
||||
| `AUTOMAT` | `'F'` = ručně přiřazen, `'T'` = automaticky |
|
||||
|
||||
Pokud je vyplněn `IDLEKAR` → jde se do tabulky `LEKARI` (cizí lékaři).
|
||||
Pokud je vyplněn `IDUZI` → jde se do tabulky `UZIVATEL` (lékaři v Medicusu).
|
||||
|
||||
### Tabulka REGISTR
|
||||
|
||||
Druhý zdroj – registrace pacienta u lékaře/pojišťovny.
|
||||
|
||||
| Sloupec | Popis |
|
||||
|------------------|-------|
|
||||
| `IDPAC` | FK na KAR |
|
||||
| `IDICP` | FK na ICP – identifikace pracoviště/pojišťovny |
|
||||
| `IDUZI` | FK na UZIVATEL – lékař (nepovinný, dohledává se přes ICP) |
|
||||
| `DATUM` | Datum začátku registrace |
|
||||
| `DATUM_ZRUSENI` | Datum zrušení (NULL = stále platná) |
|
||||
| `PRIZNAK` | `'V'`/`'D'`/`'A'` = aktivní; `'Z'`/`'N'` = zrušená/neaktivní |
|
||||
|
||||
### Stored procedure KARUZIV_SEL(IIDPAC, INCL_AUTOMAT)
|
||||
|
||||
Parametry:
|
||||
- `IIDPAC` – IDPAC pacienta
|
||||
- `INCL_AUTOMAT` – `'T'` = vrátit i automaticky přiřazené, `'F'` = jen ruční
|
||||
|
||||
Vrací sloupce: `ID, IDPAC, IDODD, ODD, IDUZI, IDPRAC, IDLEKAR, AUTOMAT, TITUL, PRIJMENI, JMENO, TITUL2, ODBORN`
|
||||
|
||||
**Logika (3 průchody):**
|
||||
|
||||
1. `KARUZIV` kde `AUTOMAT = 'F'` – ručně přiřazení lékaři
|
||||
2. `KARUZIV` kde `AUTOMAT = 'T'` – automaticky přiřazení (jen pokud `INCL_AUTOMAT = 'T'`)
|
||||
3. `REGISTR` – aktivní registrace (datum platný, `PRIZNAK` ≠ `'Z'`/`'N'`, nezrušená)
|
||||
- přes `IDICP` → `ICP` → `PRACOVISTE` → `PRACUZIV` → `UZIVATEL`
|
||||
|
||||
### Použití v panelu pacienta (UNION dotaz)
|
||||
|
||||
```sql
|
||||
-- Ošetřující lékař praktický (odbornost 001 nebo 002)
|
||||
SELECT ... FROM KARUZIV_SEL(:IDPAC, 'T') WHERE ODBORN in ('001', '002')
|
||||
-- → UNION část ID = 'OseLekPrak'
|
||||
|
||||
-- Všichni lékaři přiřazení ke kartě
|
||||
SELECT ... FROM KARUZIV_SEL(:IDPAC, 'T')
|
||||
-- → UNION část ID = 'OseLek'
|
||||
```
|
||||
|
||||
### Zapojené tabulky (přehled)
|
||||
|
||||
```
|
||||
KAR
|
||||
└── KARUZIV ──► LEKARI (externí lékaři, specialisté)
|
||||
└► UZIVATEL (interní lékaři v Medicusu)
|
||||
└► PRACOVISTE (pracoviště / odbornost)
|
||||
└► ODDEL (oddělení)
|
||||
└── REGISTR ──► ICP (identifikace pracoviště)
|
||||
└► PRACOVISTE ──► PRACUZIV ──► UZIVATEL
|
||||
```
|
||||
|
||||
### Barevné rozlišení v GUI Medicusu
|
||||
|
||||
- **Černá** = záznam pochází z `KARUZIV` (explicitně přiřazený ošetřující lékař, `IDUZI` vyplněno)
|
||||
- **Červená** = záznam pochází z `REGISTR` (registrující lékař – SP vrací `ID = 0 - REGISTR.ID`)
|
||||
- **Červená (ext.)** = záznam z `KARUZIV` kde je vyplněno `IDLEKAR` (externí lékař z tabulky `LEKARI`)
|
||||
|
||||
### Duplikát ošetřujícího lékaře – known issue
|
||||
|
||||
`KARUZIV_SEL` prochází **vždy oba zdroje** (KARUZIV i REGISTR) bez ohledu na to, zda už byl lékař nalezen. Pokud má pacient záznam v KARUZIV (černá) i v REGISTR (červená) se stejným lékařem, zobrazí se **dvakrát**.
|
||||
|
||||
Příčina: SP neobsahuje podmínku „přeskoč REGISTR, pokud KARUZIV již vrátil výsledky".
|
||||
|
||||
**Stav ordinace Buzalková (duben 2026):**
|
||||
- Všech 1620 registrovaných pacientů má v `KARUZIV` záznam IDUZI=4 (Michaela, černá)
|
||||
- 1537 pacientů má v `REGISTR` IDUZI=4 (Michaela, červená) → duplikát
|
||||
- Chování je konzistentní, ale GUI zobrazuje oba řádky – čeká se na vyjádření supportu Medicusu
|
||||
|
||||
**Možná řešení (zatím neaplikováno):**
|
||||
- A) Smazat KARUZIV záznamy → zůstane jen červená z REGISTR (jeden řádek)
|
||||
- B) Nastavit REGISTR.IDUZI zpět na NULL → REGISTR path hledá přes PRACOVISTE (najde Michalu jako první NOSVYK='A') → duplikát stále, ale přes jiný lookup
|
||||
- C) Řešení přes support Medicusu
|
||||
@@ -0,0 +1,83 @@
|
||||
SELECT
|
||||
cast('BalickyPac' as varchar(11)) as ID, substring(cast(BPAC.KOD as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(BPAC.CENPASMO as VARCHAR(70)) from 1 for 30) as VAR2, cast(BPAC.DATUMOD as DATE) as DATE1, cast(BPAC.DATUMDO as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from BALICKYPAC BPAC left join SP_BALICKYPAC_PRIZNAK(BPAC.ID, ':DATUM_CZ') PRI on 1 = 1 where BPAC.IDPAC = :IDPAC and PRI.PRIZNAK in ('A', 'B')
|
||||
|
||||
UNION SELECT
|
||||
cast('Dluh' as varchar(11)) as ID, substring(cast(P.MENA as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast((SUM(P.CENA - P.SLEVAC) - (SUM(P.PLATBA) + SUM((COALESCE((select SUM(case ZD.TYP when 'R' then ZD.CELKEM else -ZD.CELKEM end) from PLADET ZD where ZD.IDPLA = P.IDPLA and (ZD.TYP <> P.DOKLADTYP) and (ZD.TYP <> '|') and ((ZD.CENA < 0) or (ZD.TYP = 'R'))), 0))))) as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 FROM PLA P WHERE (P.IDPAC = :IDPAC) AND (P.DOKLADTYP = 'F') AND (P.STORNO IS NULL) AND (P.NENISALDO = 'F') AND ((P.SPLATNOST IS NULL) OR (P.SPLATNOST < ':DATUM')) AND (P.VALID = 'F') GROUP BY P.MENA
|
||||
|
||||
UNION SELECT
|
||||
cast('SouhlasPac' as varchar(11)) as ID, substring(cast(case when S.NAZEV is null then case when H.TYP = 'ZSOUPOS' then 'Souhlas/Nesouhlas s poskytnutím zdravotních služeb nezletilému' when H.TYP = 'ZSOUPOZ' then 'Souhlas zákonného zástupce nezletilého pacienta staršího 15ti let' when H.TYP = 'ZSOUPO2' then 'Nesouhlas s poskytnutím zdravotních služeb - povinné oèkování' when H.TYP = 'ZPOSIN2' then 'Urèení osoby oprávnìné dle zákona o zdravotních službách' when H.TYP = 'OdmPece' then 'Prohlášení o odmítnutí zdravotní péèe pacientem - Negativní revers' end else S.NAZEV end as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(H.DATUM as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from HISTDOC H left join SOUHLASPACSABL S on H.IDSOUHLASPACSABL = S.ID where H.TYP in ('IndSou', 'ZSOUPOS', 'ZSOUPOZ', 'ZSOUPO2', 'ZPOSIN2', 'OdmPece') and H.IDPACI = :IDPAC
|
||||
|
||||
UNION SELECT
|
||||
cast('sCenaVykZUM' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(coalesce(sum(cenabod),0) + coalesce(sum(cenamat),0) as NUMERIC(15,2)) as NUM1, cast((select coalesce(sum(cena),0) from LECD d where d.RODCIS = ':RODCIS' and extract(year from d.DATOSE) = ':ROK' and ((d.KAT is null) or (d.KAT <> 'N')) and exists (select h.IDLEC from LECH h where h.IDLEC = d.IDLEC and h.POJ = '207' and h.ICZ in ('09305001'))) as NUMERIC(15,2)) as NUM2 from DOKLADD d where d.RODCIS = ':RODCIS' and extract(year from d.DATOSE) = ':ROK' and ((d.KAT is null) or (d.KAT <> 'N' and d.KAT <> 'K' and d.KAT <> 'A')) and exists (select h.IDHLAV from DOKLADH h where h.IDHLAV = d.IDHLAV and h.POJ = '207' and h.ICZ in ('09305001'))
|
||||
|
||||
UNION SELECT
|
||||
cast('Registrl' as varchar(11)) as ID, substring(cast(REGISTROVAL as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from KAR where IDPAC = :IDPAC
|
||||
|
||||
UNION SELECT
|
||||
cast('OseLekPrak' as varchar(11)) as ID, substring(cast(F_CONCAT(PRIJMENI, F_CONCAT(JMENO, TITUL, ', '), ' ') as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(TITUL2 as VARCHAR(70)) from 1 for 30) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(CAST(ODBORN as INTEGER) as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 FROM KARUZIV_SEL(9733, 'T') WHERE ODBORN in ('001', '002')
|
||||
|
||||
UNION SELECT
|
||||
cast('SledLek' as varchar(11)) as ID, substring(cast(KOD as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(LEK as VARCHAR(70)) from 1 for 30) as VAR2, cast(DATUM as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from SLEDLEK where IDPAC = :IDPAC and DATUM <= ':DATUM'
|
||||
|
||||
UNION SELECT
|
||||
first 10 cast('HistDoc' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(DATUM as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from HISTDOC where IDPACI = :IDPAC and STAV is NULL and IDZARPR = 2 and IDODDPR = 2 and IDPRACPR = 2
|
||||
|
||||
UNION SELECT
|
||||
first 1 cast('LastSms' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(MAX(SENDTIME) as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from SMS where PACID = :IDPAC and SENDTIME is not NULL and not(STATUS in (100,1000))
|
||||
|
||||
UNION SELECT
|
||||
first 1 cast('PozadLekar' as varchar(11)) as ID, substring(cast(H.EICZ as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(H.EODZ as VARCHAR(70)) from 1 for 30) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from DOKLADH H where H.IDHLAV = (select first 1 I.IDHLAV from DOKLADH I where I.RODCIS = ':RODCIS' and I.EICZ is not NULL order by I.IDHLAV desc)
|
||||
|
||||
UNION SELECT
|
||||
first 10 cast('Prilohy' as varchar(11)) as ID, substring(cast(FILENAME as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from FILES where IDPAC = :IDPAC
|
||||
|
||||
UNION SELECT
|
||||
first 10 cast('Objednavky' as varchar(11)) as ID, substring(cast(F_CONCAT(U.PRIJMENI, F_CONCAT(U.JMENO, U.TITUL, ', '), ' ') as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(PRAC as VARCHAR(70)) from 1 for 30) as VAR2, cast(DATUM as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(CAS as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from OBJOBJ O join PRACOVISTE P on (P.ID = O.IDPRAC) join UZIVATEL U on (U.IDUZI = O.IDUZI) where IDPAC = :IDPAC and DATUM >= ':DATUM_CZ'
|
||||
|
||||
UNION SELECT
|
||||
cast('OseLek' as varchar(11)) as ID, substring(cast(F_CONCAT(PRIJMENI, F_CONCAT(JMENO, TITUL, ', '), ' ') as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(TITUL2 as VARCHAR(70)) from 1 for 30) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 FROM KARUZIV_SEL(9733, 'T')
|
||||
|
||||
UNION SELECT
|
||||
cast('PeProhlidky' as varchar(11)) as ID, substring(cast(NAZEV as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(DATUM as DATE) as DATE1, cast(TERMIN as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from PREH join PREINIH on (PREH.IDPREINI = PREINIH.IDPREINI) where IDPAC = :IDPAC
|
||||
|
||||
UNION SELECT
|
||||
cast('Medikace' as varchar(11)) as ID, substring(cast(NAZ as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(PLATI_OD as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from MEDIKACE where IDPAC = :IDPAC and PLATI_OD <= ':DATUM_CZ' and (PLATI_DO >= ':DATUM_CZ' or PLATI_DO is NULL)
|
||||
|
||||
UNION SELECT
|
||||
cast('NextDispenz' as varchar(11)) as ID, substring(cast(NAZEV as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(SKUPINA as VARCHAR(70)) from 1 for 30) as VAR2, cast(PRISTI as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from DISPAC join DISSKU on (DISSKU.IDDIS = DISPAC.IDDIS) where IDPAC = :IDPAC and PRISTI is not NULL
|
||||
|
||||
UNION SELECT
|
||||
cast('Dispenz' as varchar(11)) as ID, substring(cast(NAZEV as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(SKUPINA as VARCHAR(70)) from 1 for 30) as VAR2, cast(DATZAR as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from DISPAC join DISSKU on (DISSKU.IDDIS = DISPAC.IDDIS) where IDPAC = :IDPAC
|
||||
|
||||
UNION SELECT
|
||||
cast('Prohlidky' as varchar(11)) as ID, substring(cast(NAZEV as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(DATUM as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from PREPRI join PREINIH on (PREPRI.IDPREINI = PREINIH.IDPREINI) where IDPAC = :IDPAC and datum is not null
|
||||
|
||||
UNION SELECT
|
||||
cast('NextOck' as varchar(11)) as ID, substring(cast(coalesce(NAZ,ZKRATKA) as VARCHAR(254)) from 1 for 250) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(DATUMD as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from OCKPRI o left join KLK k on o.ZKRATKA = k.KOD where IDPAC = :IDPAC
|
||||
|
||||
UNION SELECT
|
||||
first 1 cast('LastVykon' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, substring(cast(D.KOD as VARCHAR(70)) from 1 for 30) as VAR2, cast(D.DATOSE as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from DOKLADD D where D.ID = (select first 1 dd.id from dokladd dd join dokladh dh on (dh.idhlav = dd.idhlav) where dd.rodcis = ':RODCIS' and (dh.hodb = '001' or dh.hodb is null) order by dd.datose desc)
|
||||
|
||||
UNION SELECT
|
||||
first 1 cast('LastDekurs' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(MAX(DATUM) as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from DEKURS where IDPAC = :IDPAC and (IDPRAC = 2 or IDPRAC = -1)
|
||||
|
||||
UNION SELECT
|
||||
first 1 cast('Karta' as varchar(11)) as ID, substring(cast(INFORMACE as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(CIZINEC as VARCHAR(70)) from 1 for 30) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(INFORMACE_COL as INTEGER) as INT1, POZNAMKA as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from KAR where IDPAC = :IDPAC
|
||||
|
||||
UNION SELECT
|
||||
first 1 cast('Saldo' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(SALDO as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from PACIENT_SALDO(9733, 1, 0, 0)
|
||||
|
||||
UNION SELECT
|
||||
first 1 cast('Anamneza' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, substring(cast(KREVSKUP as VARCHAR(70)) from 1 for 30) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, ANAMNEZA as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from ANAMNEZA where ID = (select first 1 ID from ANAMNEZA where IDPAC=9733 order by DATUM DESC, ID desc)
|
||||
|
||||
UNION SELECT
|
||||
first 20 cast('Ockovani' as varchar(11)) as ID, substring(cast(ockzaz.LATKA as VARCHAR(254)) from 1 for 250) as VAR1, substring(cast(ockzaz.ZKRATKA as VARCHAR(70)) from 1 for 30) as VAR2, cast(max(ockzaz.DATUM) as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from ockzaz where ockzaz.idpac = :IDPAC group by ockzaz.ZKRATKA, ockzaz.LATKA
|
||||
|
||||
UNION SELECT
|
||||
first 1 cast('NeschopenOd' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(MAX(ZACNES) as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from NES where (IDPAC = :IDPAC) and (ZACNES <= ':DATUM_CZ') and ((KONNES is NULL) or (KONNES > ':DATUM_CZ')) and (STORNO = 'F')
|
||||
|
||||
UNION SELECT
|
||||
first 1 cast('Alergie' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(NULL as INTEGER) as INT1, ALERGIE as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from ANAMNEZA where IDPAC = :IDPAC and ID = (select first 1 ID from ANAMNEZA where IDPAC = :IDPAC and DATUM <= ':DATUM_CZ' order by DATUM desc, ID desc)
|
||||
|
||||
UNION SELECT
|
||||
first 1 cast('Pojistovna' as varchar(11)) as ID, cast(NULL as VARCHAR(254)) as VAR1, cast(NULL as VARCHAR(70)) as VAR2, cast(NULL as DATE) as DATE1, cast(NULL as DATE) as DATE2, cast(NULL as TIMESTAMP) as TIME1, cast(P.IDICP as INTEGER) as INT1, NULL as TEXT1, cast(NULL as NUMERIC(15,2)) as NUM1, cast(NULL as NUMERIC(15,2)) as NUM2 from ICP P join ICZ Z on (Z.IDICZ = P.IDICZ) where Z.POJ = '207' and P.ODB = '001'
|
||||
@@ -0,0 +1,27 @@
|
||||
import fdb, datetime
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250')
|
||||
|
||||
cur = conn.cursor()
|
||||
dnes = datetime.date.today().isoformat()
|
||||
|
||||
cur.execute("""
|
||||
SELECT COUNT(*) FROM KAR
|
||||
WHERE (vyrazen = 'N')
|
||||
AND EXISTS (
|
||||
SELECT id FROM registr r
|
||||
JOIN icp i ON r.idicp = i.idicp
|
||||
WHERE r.idpac = kar.idpac
|
||||
AND (r.datum <= ?)
|
||||
AND (r.datum_zruseni IS NULL OR r.datum_zruseni >= ?)
|
||||
AND (r.priznak IN ('V','D','A'))
|
||||
AND (i.icp = '09305001')
|
||||
AND (i.odb = '001')
|
||||
)
|
||||
""", (dnes, dnes))
|
||||
|
||||
pocet = cur.fetchone()[0]
|
||||
print(f'Registrovaných pacientů: {pocet}')
|
||||
conn.close()
|
||||
@@ -0,0 +1,78 @@
|
||||
"""db_bridge_vm.py – VM strana souborového mostu k Medicusu.
|
||||
|
||||
Použití z Linuxu:
|
||||
from db_bridge_vm import query
|
||||
rows, columns = query("SELECT COUNT(*) FROM KAR")
|
||||
print(columns, rows)
|
||||
"""
|
||||
import json, time, os, uuid
|
||||
|
||||
BRIDGE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
REQUEST = os.path.join(BRIDGE_DIR, 'query_request.json')
|
||||
RESPONSE = os.path.join(BRIDGE_DIR, 'query_response.json')
|
||||
|
||||
TIMEOUT_SEC = 30
|
||||
POLL_SEC = 0.3
|
||||
|
||||
|
||||
def query(sql, params=None, timeout=TIMEOUT_SEC):
|
||||
"""Pošle SQL dotaz přes souborový most a vrátí (rows, columns).
|
||||
|
||||
Raises:
|
||||
TimeoutError – watchdog neodpověděl do timeout sekund
|
||||
RuntimeError – Firebird vrátil chybu
|
||||
"""
|
||||
# Smaž případnou starou response
|
||||
if os.path.exists(RESPONSE):
|
||||
os.remove(RESPONSE)
|
||||
|
||||
req_id = uuid.uuid4().hex
|
||||
req = {'id': req_id, 'sql': sql, 'params': params or []}
|
||||
|
||||
with open(REQUEST, 'w', encoding='utf-8') as f:
|
||||
json.dump(req, f, ensure_ascii=False)
|
||||
|
||||
# Čekej na odpověď
|
||||
waited = 0.0
|
||||
while waited < timeout:
|
||||
time.sleep(POLL_SEC)
|
||||
waited += POLL_SEC
|
||||
if os.path.exists(RESPONSE):
|
||||
with open(RESPONSE, 'r', encoding='utf-8') as f:
|
||||
resp = json.load(f)
|
||||
os.remove(RESPONSE)
|
||||
if resp.get('status') == 'error':
|
||||
raise RuntimeError(f"DB chyba: {resp.get('error')}")
|
||||
return resp.get('rows', []), resp.get('columns', [])
|
||||
|
||||
# Timeout – smaž request aby watchdog nezpracoval zastaralý dotaz
|
||||
if os.path.exists(REQUEST):
|
||||
os.remove(REQUEST)
|
||||
raise TimeoutError(f'Watchdog neodpověděl do {timeout}s – běží db_bridge_windows.py?')
|
||||
|
||||
|
||||
def query_print(sql, params=None):
|
||||
"""Spustí dotaz a vypíše výsledek přehledně."""
|
||||
rows, cols = query(sql, params)
|
||||
if not cols:
|
||||
print('(žádné sloupce)')
|
||||
return rows, cols
|
||||
col_w = [max(len(str(c)), max((len(str(r[i])) for r in rows), default=0))
|
||||
for i, c in enumerate(cols)]
|
||||
sep = '+' + '+'.join('-' * (w + 2) for w in col_w) + '+'
|
||||
fmt = '|' + '|'.join(f' {{:<{w}}} ' for w in col_w) + '|'
|
||||
print(sep)
|
||||
print(fmt.format(*cols))
|
||||
print(sep)
|
||||
for row in rows:
|
||||
print(fmt.format(*[str(v) if v is not None else 'NULL' for v in row]))
|
||||
print(sep)
|
||||
print(f'{len(rows)} řádků')
|
||||
return rows, cols
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Rychlý test
|
||||
print('Testuji spojení...')
|
||||
rows, cols = query('SELECT COUNT(*) AS POCET FROM KAR')
|
||||
print(f'OK – pacientů v KAR: {rows[0][0]}')
|
||||
@@ -0,0 +1,90 @@
|
||||
"""db_bridge_windows.py – Windows watchdog pro dotazy z Linux VM.
|
||||
|
||||
Spusť jednou na Windows:
|
||||
python db_bridge_windows.py
|
||||
|
||||
Skript sleduje soubor query_request.json ve stejné složce.
|
||||
Jakmile ho najde, spustí SQL dotaz proti Medicusu a zapíše výsledek
|
||||
do query_response.json. Pak čeká na další dotaz.
|
||||
|
||||
Ukonči: Ctrl+C
|
||||
"""
|
||||
import fdb, json, time, os, traceback, datetime
|
||||
|
||||
# ── Konfigurace ───────────────────────────────────────────────────────────────
|
||||
DSN = r'localhost:c:\medicus 3\data\medicus.fdb'
|
||||
USER = 'SYSDBA'
|
||||
PASSWORD = 'masterkey'
|
||||
CHARSET = 'win1250'
|
||||
|
||||
BRIDGE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
REQUEST = os.path.join(BRIDGE_DIR, 'query_request.json')
|
||||
RESPONSE = os.path.join(BRIDGE_DIR, 'query_response.json')
|
||||
POLL_SEC = 0.5
|
||||
|
||||
# ── Pomocné funkce ────────────────────────────────────────────────────────────
|
||||
|
||||
def serialize(val):
|
||||
"""Převede Python hodnoty na JSON-serializovatelné typy."""
|
||||
if isinstance(val, (datetime.date, datetime.datetime)):
|
||||
return val.isoformat()
|
||||
if isinstance(val, datetime.time):
|
||||
return val.isoformat()
|
||||
if isinstance(val, bytes):
|
||||
return f'<bytes len={len(val)}>'
|
||||
return val
|
||||
|
||||
|
||||
def run_query(sql, params=None):
|
||||
conn = fdb.connect(dsn=DSN, user=USER, password=PASSWORD, charset=CHARSET)
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute(sql, params or [])
|
||||
columns = [d[0] for d in cur.description] if cur.description else []
|
||||
rows = [[serialize(v) for v in row] for row in cur.fetchall()]
|
||||
return {'status': 'ok', 'columns': columns, 'rows': rows, 'error': None}
|
||||
except Exception as e:
|
||||
return {'status': 'error', 'columns': [], 'rows': [], 'error': str(e)}
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
# ── Hlavní smyčka ─────────────────────────────────────────────────────────────
|
||||
print(f'DB Bridge spuštěn. Sleduju: {REQUEST}')
|
||||
print('Ukončení: Ctrl+C\n')
|
||||
|
||||
while True:
|
||||
try:
|
||||
if os.path.exists(REQUEST):
|
||||
print(f'[{datetime.datetime.now().strftime("%H:%M:%S")}] Přijat dotaz...')
|
||||
|
||||
with open(REQUEST, 'r', encoding='utf-8') as f:
|
||||
req = json.load(f)
|
||||
|
||||
os.remove(REQUEST)
|
||||
|
||||
sql = req.get('sql', '')
|
||||
params = req.get('params', [])
|
||||
req_id = req.get('id', '')
|
||||
|
||||
result = run_query(sql, params)
|
||||
result['id'] = req_id
|
||||
result['sql'] = sql
|
||||
|
||||
with open(RESPONSE, 'w', encoding='utf-8') as f:
|
||||
json.dump(result, f, ensure_ascii=False, indent=2)
|
||||
|
||||
if result['status'] == 'ok':
|
||||
print(f' → OK, {len(result["rows"])} řádků')
|
||||
else:
|
||||
print(f' → CHYBA: {result["error"]}')
|
||||
|
||||
time.sleep(POLL_SEC)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print('\nDB Bridge ukončen.')
|
||||
break
|
||||
except Exception as e:
|
||||
print(f'Neočekávaná chyba: {e}')
|
||||
traceback.print_exc()
|
||||
time.sleep(2)
|
||||
@@ -0,0 +1,30 @@
|
||||
"""get_kar_sortby_idlist.py – přečte definici stored procedure KAR_SORTBY_IDLIST z Firebirdu.
|
||||
Spustit na Windows.
|
||||
"""
|
||||
import fdb
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250')
|
||||
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
SELECT RDB$PROCEDURE_SOURCE FROM RDB$PROCEDURES
|
||||
WHERE RDB$PROCEDURE_NAME = 'KAR_SORTBY_IDLIST'
|
||||
""")
|
||||
row = cur.fetchone()
|
||||
if row and row[0]:
|
||||
print(row[0])
|
||||
else:
|
||||
# Možná je to funkce (FUNCTION), ne procedure
|
||||
cur.execute("""
|
||||
SELECT RDB$FUNCTION_SOURCE FROM RDB$FUNCTIONS
|
||||
WHERE RDB$FUNCTION_NAME = 'KAR_SORTBY_IDLIST'
|
||||
""")
|
||||
row = cur.fetchone()
|
||||
if row and row[0]:
|
||||
print(row[0])
|
||||
else:
|
||||
print("Nenalezeno ani jako PROCEDURE ani jako FUNCTION.")
|
||||
|
||||
conn.close()
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,106 @@
|
||||
"""
|
||||
precti_trace.py – přehledné SQL dotazy z Firebird audit trace logu
|
||||
Čte od konce (nejnovější nahoře).
|
||||
|
||||
Použití:
|
||||
python precti_trace.py # posledních 50 dotazů
|
||||
python precti_trace.py 100 # posledních 100
|
||||
python precti_trace.py 50 SELECT # filtr – jen SELECT dotazy
|
||||
"""
|
||||
|
||||
import re, sys, io
|
||||
|
||||
LOG_PATH = r'C:\Program Files\Firebird\Firebird_2_5_CGM\default_trace.log'
|
||||
SEPARATOR = '-' * 80
|
||||
|
||||
TS_RE = re.compile(r'^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+)\s+\([\w:]+\)\s+(\w+)')
|
||||
ATT_RE = re.compile(r'\(ATT_\d+,\s*([\w]+):')
|
||||
SQL_S_RE = re.compile(r'^-{70,}$')
|
||||
SQL_E_RE = re.compile(r'^\^{70,}$')
|
||||
|
||||
# ── Parametry ──────────────────────────────────────────────────────────────
|
||||
limit = int(sys.argv[1]) if len(sys.argv) > 1 else 50
|
||||
filtr = sys.argv[2].upper() if len(sys.argv) > 2 else ''
|
||||
|
||||
# ── Čtení logu ─────────────────────────────────────────────────────────────
|
||||
with open(LOG_PATH, 'r', encoding='cp1252', errors='replace') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
# ── Rozděl log na bloky podle timestamp řádků ──────────────────────────────
|
||||
# Každý blok = od jednoho timestamp řádku do dalšího
|
||||
blocks = [] # (start_line_idx, ts, event, block_lines[])
|
||||
i = 0
|
||||
n = len(lines)
|
||||
|
||||
while i < n:
|
||||
m = TS_RE.match(lines[i].rstrip())
|
||||
if m:
|
||||
ts = m.group(1)
|
||||
event = m.group(2)
|
||||
start = i
|
||||
i += 1
|
||||
block = []
|
||||
while i < n and not TS_RE.match(lines[i].rstrip()):
|
||||
block.append(lines[i].rstrip())
|
||||
i += 1
|
||||
blocks.append((ts, event, block))
|
||||
else:
|
||||
i += 1
|
||||
|
||||
# ── Extrahuj SQL z PREPARE_STATEMENT bloků ─────────────────────────────────
|
||||
results = []
|
||||
|
||||
for ts, event, block in blocks:
|
||||
if event != 'PREPARE_STATEMENT':
|
||||
continue
|
||||
|
||||
# Uživatel
|
||||
user = '?'
|
||||
for line in block:
|
||||
att = ATT_RE.search(line)
|
||||
if att:
|
||||
user = att.group(1)
|
||||
break
|
||||
|
||||
# SQL mezi --- a ^^^
|
||||
sql_lines = []
|
||||
plan_lines = []
|
||||
in_sql = False
|
||||
after_sql = False
|
||||
|
||||
for line in block:
|
||||
if SQL_S_RE.match(line):
|
||||
in_sql = True
|
||||
after_sql = False
|
||||
continue
|
||||
if SQL_E_RE.match(line):
|
||||
in_sql = False
|
||||
after_sql = True
|
||||
continue
|
||||
if in_sql:
|
||||
sql_lines.append(line)
|
||||
elif after_sql and line.startswith('PLAN'):
|
||||
plan_lines.append(line)
|
||||
|
||||
sql = '\n'.join(sql_lines).strip()
|
||||
plan = ' '.join(plan_lines).strip()
|
||||
|
||||
if sql:
|
||||
results.append((ts, user, sql, plan))
|
||||
|
||||
# ── Výstup – nejnovější nahoře ─────────────────────────────────────────────
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||
|
||||
filtered = [r for r in results if filtr in r[2].upper()] if filtr else results
|
||||
filtered = list(reversed(filtered))[:limit]
|
||||
|
||||
print(f'Firebird trace – posledních {len(filtered)} dotazu'
|
||||
+ (f' [filtr: {filtr}]' if filtr else ''))
|
||||
print(SEPARATOR)
|
||||
|
||||
for ts, user, sql, plan in filtered:
|
||||
print(f'[{ts}] {user}')
|
||||
print(sql)
|
||||
if plan:
|
||||
print(f' --> {plan}')
|
||||
print(SEPARATOR)
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"status": "ok",
|
||||
"columns": [
|
||||
"ID",
|
||||
"DATUM",
|
||||
"CAS",
|
||||
"UZIVATEL",
|
||||
"IDPAC",
|
||||
"TABULKA",
|
||||
"IDREC",
|
||||
"AKCE",
|
||||
"DETAIL"
|
||||
],
|
||||
"rows": [
|
||||
[
|
||||
2882053,
|
||||
"2026-03-20",
|
||||
"17:43:50.523000",
|
||||
"VBU ",
|
||||
3234,
|
||||
10000,
|
||||
3234,
|
||||
"V",
|
||||
null
|
||||
],
|
||||
[
|
||||
2882052,
|
||||
"2026-03-20",
|
||||
"17:43:46.435000",
|
||||
"VBU ",
|
||||
3234,
|
||||
10000,
|
||||
3234,
|
||||
"V",
|
||||
null
|
||||
],
|
||||
[
|
||||
2882051,
|
||||
"2026-03-20",
|
||||
"17:24:19.777000",
|
||||
"VBU ",
|
||||
4757,
|
||||
10000,
|
||||
4757,
|
||||
"V",
|
||||
null
|
||||
],
|
||||
[
|
||||
2882050,
|
||||
"2026-03-20",
|
||||
"17:24:15.866000",
|
||||
"VBU ",
|
||||
3234,
|
||||
10000,
|
||||
3234,
|
||||
"V",
|
||||
null
|
||||
],
|
||||
[
|
||||
2882049,
|
||||
"2026-03-20",
|
||||
"07:11:41.434000",
|
||||
"VBU ",
|
||||
4757,
|
||||
10000,
|
||||
4757,
|
||||
"V",
|
||||
null
|
||||
]
|
||||
],
|
||||
"error": null,
|
||||
"id": "11859d529a784bedb653030ba60131de",
|
||||
"sql": "SELECT FIRST 5 * FROM LOG ORDER BY ID DESC"
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
# trace_report.py – Excel report z Firebird audit trace logu
|
||||
# Ulozi do u:\Dropbox\!!!Days\Downloads Z230\, smaze predchozi verzi.
|
||||
|
||||
import re, sys, io, os, openpyxl
|
||||
from openpyxl.styles import Font, PatternFill, Alignment
|
||||
from openpyxl.utils import get_column_letter
|
||||
from datetime import datetime
|
||||
|
||||
LOG_PATH = r'C:\Program Files\Firebird\Firebird_2_5_CGM\default_trace.log'
|
||||
OUTPUT_DIR = r'u:\Dropbox\!!!Days\Downloads Z230'
|
||||
|
||||
TS_RE = re.compile(r'^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+)\s+\([\w:]+\)\s+(\w+)')
|
||||
ATT_RE = re.compile(r'\(ATT_\d+,\s*([\w]+):')
|
||||
SQL_S_RE = re.compile(r'^-{70,}$')
|
||||
SQL_E_RE = re.compile(r'^\^{70,}$')
|
||||
|
||||
HEADER_FILL = PatternFill('solid', fgColor='2F5496')
|
||||
HEADER_FONT = Font(bold=True, color='FFFFFF')
|
||||
ZEBRA_FILL = PatternFill('solid', fgColor='DCE6F1')
|
||||
WRAP_TOP = Alignment(wrap_text=True, vertical='top')
|
||||
|
||||
# ── Smazání předchozích verzí ───────────────────────────────────────────────
|
||||
for f in os.listdir(OUTPUT_DIR):
|
||||
if f.endswith('_trace.xlsx'):
|
||||
os.remove(os.path.join(OUTPUT_DIR, f))
|
||||
|
||||
# ── Výstupní soubor ─────────────────────────────────────────────────────────
|
||||
now = datetime.now()
|
||||
filename = now.strftime('%Y-%m-%d_%H-%M-%S') + '_trace.xlsx'
|
||||
out_path = os.path.join(OUTPUT_DIR, filename)
|
||||
|
||||
# ── Čtení logu ─────────────────────────────────────────────────────────────
|
||||
with open(LOG_PATH, 'r', encoding='cp1252', errors='replace') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
# ── Rozděl na bloky ─────────────────────────────────────────────────────────
|
||||
blocks = []
|
||||
i, n = 0, len(lines)
|
||||
|
||||
while i < n:
|
||||
m = TS_RE.match(lines[i].rstrip())
|
||||
if m:
|
||||
ts, event = m.group(1), m.group(2)
|
||||
i += 1
|
||||
block = []
|
||||
while i < n and not TS_RE.match(lines[i].rstrip()):
|
||||
block.append(lines[i].rstrip())
|
||||
i += 1
|
||||
blocks.append((ts, event, block))
|
||||
else:
|
||||
i += 1
|
||||
|
||||
# ── Extrahuj SQL z PREPARE_STATEMENT bloků ─────────────────────────────────
|
||||
results = []
|
||||
|
||||
for ts, event, block in blocks:
|
||||
if event != 'PREPARE_STATEMENT':
|
||||
continue
|
||||
|
||||
user = '?'
|
||||
for line in block:
|
||||
att = ATT_RE.search(line)
|
||||
if att:
|
||||
user = att.group(1)
|
||||
break
|
||||
|
||||
sql_lines, plan_lines = [], []
|
||||
in_sql, after_sql = False, False
|
||||
|
||||
for line in block:
|
||||
if SQL_S_RE.match(line):
|
||||
in_sql, after_sql = True, False
|
||||
continue
|
||||
if SQL_E_RE.match(line):
|
||||
in_sql, after_sql = False, True
|
||||
continue
|
||||
if in_sql:
|
||||
sql_lines.append(line)
|
||||
elif after_sql and line.startswith('PLAN'):
|
||||
plan_lines.append(line)
|
||||
|
||||
sql = '\n'.join(sql_lines).strip()
|
||||
plan = ' '.join(plan_lines).strip()
|
||||
|
||||
if sql:
|
||||
# Detekuj typ dotazu
|
||||
first = sql.lstrip().upper()
|
||||
if first.startswith('SELECT'):
|
||||
typ = 'SELECT'
|
||||
elif first.startswith('INSERT'):
|
||||
typ = 'INSERT'
|
||||
elif first.startswith('UPDATE'):
|
||||
typ = 'UPDATE'
|
||||
elif first.startswith('DELETE'):
|
||||
typ = 'DELETE'
|
||||
elif first.startswith('EXECUTE'):
|
||||
typ = 'EXECUTE'
|
||||
else:
|
||||
typ = 'OTHER'
|
||||
|
||||
results.append((ts, user, typ, sql, plan))
|
||||
|
||||
# Nejnovější nahoře
|
||||
results = list(reversed(results))
|
||||
|
||||
# ── Excel ───────────────────────────────────────────────────────────────────
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
ws.title = 'TRACE'
|
||||
|
||||
cols = ['CAS', 'UZIVATEL', 'TYP', 'SQL', 'PLAN']
|
||||
ws.append(cols)
|
||||
|
||||
for i, (ts, user, typ, sql, plan) in enumerate(results, start=2):
|
||||
ws.append([ts, user, typ, sql, plan])
|
||||
if i % 2 == 0:
|
||||
for cell in ws[i]:
|
||||
cell.fill = ZEBRA_FILL
|
||||
for cell in ws[i]:
|
||||
cell.alignment = WRAP_TOP
|
||||
|
||||
# Záhlaví
|
||||
for cell in ws[1]:
|
||||
cell.fill = HEADER_FILL
|
||||
cell.font = HEADER_FONT
|
||||
cell.alignment = Alignment(horizontal='center')
|
||||
|
||||
# Šířky sloupců
|
||||
for col, width in zip(['A','B','C','D','E'], [22, 12, 10, 80, 60]):
|
||||
ws.column_dimensions[col].width = width
|
||||
|
||||
# Výška řádků – SQL může být víceřádkový
|
||||
for row in ws.iter_rows(min_row=2):
|
||||
lines_count = max(row[3].value.count('\n') + 1 if row[3].value else 1, 1)
|
||||
ws.row_dimensions[row[0].row].height = min(lines_count * 15, 120)
|
||||
|
||||
ws.freeze_panes = 'A2'
|
||||
|
||||
# Autofiltr
|
||||
ws.auto_filter.ref = f'A1:E{len(results)+1}'
|
||||
|
||||
wb.save(out_path)
|
||||
sys.stdout.buffer.write(f'Ulozeno: {out_path}\n'.encode('utf-8'))
|
||||
sys.stdout.buffer.write(f'Dotazu: {len(results)}\n'.encode('utf-8'))
|
||||
@@ -0,0 +1,155 @@
|
||||
"""
|
||||
Najde datum poslední preventivní prohlídky (výkon 01022 nebo 01021)
|
||||
pro každého registrovaného pacienta z VZPARC DRUH=98 (výkonové dávky pojišťovnám).
|
||||
Výsledek zapíše do KAR.POZNAMKA ve formátu:
|
||||
[[prev_prohlidka:YYYY-MM-DD 01022, YYYY-MM-DD]]
|
||||
kde první datum je datum PP a druhé je nejdřívější možný termín příští (23 měsíců).
|
||||
"""
|
||||
|
||||
import fdb
|
||||
import re
|
||||
import sys
|
||||
from datetime import date
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
sys.stdout = open(sys.stdout.fileno(), mode='w', encoding='utf-8', buffering=1)
|
||||
|
||||
KODY_PP = {'01022', '01021'}
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA', password='masterkey', charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
# --- 1. Načti registrované pacienty (RC → IDPAC, aktuální POZNAMKA) ---
|
||||
dnes = date.today().strftime('%Y-%m-%d')
|
||||
cur.execute(f"""
|
||||
SELECT KAR.IDPAC, KAR.RODCIS, KAR.PRIJMENI, KAR.JMENO, KAR.POZNAMKA
|
||||
FROM KAR
|
||||
WHERE KAR.VYRAZEN = 'N'
|
||||
AND EXISTS (
|
||||
SELECT r.ID FROM REGISTR r
|
||||
JOIN ICP i ON r.IDICP = i.IDICP
|
||||
WHERE r.IDPAC = KAR.IDPAC
|
||||
AND r.DATUM <= '{dnes}'
|
||||
AND (r.DATUM_ZRUSENI IS NULL OR r.DATUM_ZRUSENI >= '{dnes}')
|
||||
AND r.PRIZNAK IN ('V','D','A')
|
||||
AND i.ICP = '09305001'
|
||||
AND i.ODB = '001'
|
||||
)
|
||||
""")
|
||||
pacienti = {} # RODCIS -> {'idpac', 'prijmeni', 'jmeno', 'poznamka'}
|
||||
for row in cur.fetchall():
|
||||
idpac, rodcis, prijmeni, jmeno, poznamka = row
|
||||
if rodcis:
|
||||
pacienti[rodcis.strip()] = {
|
||||
'idpac': idpac,
|
||||
'prijmeni': prijmeni,
|
||||
'jmeno': jmeno,
|
||||
'poznamka': poznamka or '',
|
||||
}
|
||||
print(f"Registrovaných pacientů: {len(pacienti)}")
|
||||
|
||||
# --- 2. Projdi všechny VZPARC DRUH=98 dávky a hledej výkony 01022/01021 ---
|
||||
# Výsledek: RC -> (nejnovejsi_datum, kod)
|
||||
vysledky = {} # RC -> (nejnovejsi_datum, kod)
|
||||
|
||||
cur.execute("""
|
||||
SELECT DAVKA FROM VZPARC
|
||||
WHERE DRUH = '98' AND DAVKA IS NOT NULL
|
||||
""")
|
||||
|
||||
davky = cur.fetchall()
|
||||
print(f"VZPARC DRUH=98 blobů celkem: {len(davky)}")
|
||||
|
||||
for (kdavka_raw,) in davky:
|
||||
if not kdavka_raw:
|
||||
continue
|
||||
# Dekódování CP852
|
||||
try:
|
||||
text = kdavka_raw.encode('cp1250', errors='replace').decode('cp852', errors='replace')
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if not text.startswith('DP98'):
|
||||
continue
|
||||
|
||||
aktualni_rc = None
|
||||
for line in text.splitlines():
|
||||
if not line:
|
||||
continue
|
||||
typ = line[0]
|
||||
|
||||
if typ == 'A':
|
||||
# RC je na pevné pozici 34, délka 10
|
||||
if len(line) >= 44:
|
||||
aktualni_rc = line[34:44].strip()
|
||||
else:
|
||||
aktualni_rc = None
|
||||
|
||||
elif typ == 'V' and aktualni_rc:
|
||||
# V + DDMMYYYY + KOD(5)
|
||||
if len(line) < 14:
|
||||
continue
|
||||
try:
|
||||
dd = int(line[1:3])
|
||||
mm = int(line[3:5])
|
||||
yyyy = int(line[5:9])
|
||||
kod = line[9:14].strip()
|
||||
except (ValueError, IndexError):
|
||||
continue
|
||||
|
||||
if kod not in KODY_PP:
|
||||
continue
|
||||
|
||||
try:
|
||||
datum = date(yyyy, mm, dd)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
# Ulož jen nejnovější datum
|
||||
if aktualni_rc not in vysledky or datum > vysledky[aktualni_rc][0]:
|
||||
vysledky[aktualni_rc] = (datum, kod)
|
||||
|
||||
print(f"Pacientů s nalezenou PP (01022/01021): {len(vysledky)}")
|
||||
|
||||
# --- 3. Zapiš do KAR.POZNAMKA ---
|
||||
TAG_RE = re.compile(r'\[\[prev_prohlidka:[^\]]*\]\]\s*#zaps[^#]*#')
|
||||
TAG_FORMAT = '[[prev_prohlidka:{pp_datum} {kod}, {pristi_datum}]] #zapsáno {zapsano}#'
|
||||
|
||||
zapsano = 0
|
||||
preskoceno = 0
|
||||
nenalezeno = 0
|
||||
|
||||
from datetime import datetime
|
||||
ted = datetime.now().strftime('%d-%m-%Y %H:%M')
|
||||
|
||||
for rc, (datum_pp, kod) in sorted(vysledky.items()):
|
||||
if rc not in pacienti:
|
||||
nenalezeno += 1
|
||||
continue
|
||||
|
||||
pac = pacienti[rc]
|
||||
pristi = datum_pp + relativedelta(months=23)
|
||||
tag = TAG_FORMAT.format(
|
||||
pp_datum=datum_pp.strftime('%d-%m-%Y'),
|
||||
kod=kod,
|
||||
pristi_datum=pristi.strftime('%d-%m-%Y'),
|
||||
zapsano=ted,
|
||||
)
|
||||
|
||||
stara = pac['poznamka']
|
||||
# Odstraň starý tag a vlož nový na začátek
|
||||
nova = TAG_RE.sub('', stara).lstrip('\n')
|
||||
nova = tag + ('\n' + nova if nova else '')
|
||||
|
||||
cur.execute(
|
||||
"UPDATE KAR SET POZNAMKA=? WHERE IDPAC=?",
|
||||
(nova, pac['idpac'])
|
||||
)
|
||||
zapsano += 1
|
||||
|
||||
conn.commit()
|
||||
print(f"Zapsáno: {zapsano}, nenalezeno v KAR: {nenalezeno}, přeskočeno: {preskoceno}")
|
||||
conn.close()
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Find the latest Medicus backup (.fbk or .zip) in the given folder.
|
||||
If the newest backup is a ZIP, extract the .fbk into Z:\Medicus 3\restore.
|
||||
Before extraction, the restore folder is fully cleared.
|
||||
Prints the full path to the resulting .fbk file (for batch usage).
|
||||
"""
|
||||
|
||||
import re
|
||||
import zipfile
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
# ========================================
|
||||
# 🔧 CONFIGURATION
|
||||
# ========================================
|
||||
BACKUP_DIR = Path(r"G:\OnedriveOrdinace\OneDrive\MedicusBackup") # ✅ your real backup location
|
||||
RESTORE_DIR = Path(r"Z:\\Medicus 3\\restore") # destination folder for .fbk
|
||||
RESTORE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# ========================================
|
||||
# 🧹 1) Clean restore folder
|
||||
# ========================================
|
||||
print(f"🧹 Cleaning folder: {RESTORE_DIR}")
|
||||
for item in RESTORE_DIR.iterdir():
|
||||
try:
|
||||
if item.is_file() or item.is_symlink():
|
||||
item.unlink()
|
||||
elif item.is_dir():
|
||||
shutil.rmtree(item)
|
||||
except Exception as e:
|
||||
print(f"⚠️ Cannot delete {item}: {e}")
|
||||
|
||||
# ========================================
|
||||
# 🔍 2) Find newest backup file
|
||||
# ========================================
|
||||
pattern = re.compile(r"Medicus_(\d{6})_(\d{4})\.(fbk|zip)$", re.IGNORECASE)
|
||||
candidates = []
|
||||
|
||||
for f in BACKUP_DIR.iterdir():
|
||||
m = pattern.match(f.name)
|
||||
if m:
|
||||
yymmdd, hhmm, ext = m.groups()
|
||||
ts = datetime.strptime("20" + yymmdd + hhmm, "%Y%m%d%H%M")
|
||||
candidates.append((ts, f))
|
||||
|
||||
if not candidates:
|
||||
raise SystemExit("❌ No Medicus backup files found in folder!")
|
||||
|
||||
candidates.sort(key=lambda x: x[0], reverse=True)
|
||||
latest_time, latest_file = candidates[0]
|
||||
|
||||
print(f"🕒 Latest backup: {latest_file.name} ({latest_time})")
|
||||
|
||||
# ========================================
|
||||
# 📦 3) Extract or use directly
|
||||
# ========================================
|
||||
if latest_file.suffix.lower() == ".zip":
|
||||
with zipfile.ZipFile(latest_file, "r") as zf:
|
||||
fbk_files = [n for n in zf.namelist() if n.lower().endswith(".fbk")]
|
||||
if not fbk_files:
|
||||
raise SystemExit("❌ ZIP file does not contain any .fbk!")
|
||||
fbk_name = Path(fbk_files[0]).name
|
||||
out_path = RESTORE_DIR / fbk_name
|
||||
print(f"📂 Extracting {fbk_name} → {out_path}")
|
||||
zf.extract(fbk_files[0], RESTORE_DIR)
|
||||
final_fbk = out_path
|
||||
else:
|
||||
out_path = RESTORE_DIR / latest_file.name
|
||||
print(f"📋 Copying {latest_file} → {out_path}")
|
||||
shutil.copy2(latest_file, out_path)
|
||||
final_fbk = out_path
|
||||
|
||||
# ========================================
|
||||
# ✅ 4) Output final .fbk path for the batch file
|
||||
# ========================================
|
||||
print(final_fbk)
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Force-kill all running Medicus processes instantly.
|
||||
No graceful wait, no exceptions if already closed.
|
||||
"""
|
||||
|
||||
import psutil
|
||||
|
||||
TARGETS = ["Medicus.exe", "MedicusServer.exe", "MedicusUpdater.exe"]
|
||||
|
||||
for proc in psutil.process_iter(["name", "pid"]):
|
||||
try:
|
||||
name = proc.info["name"]
|
||||
if name and name.lower() in [t.lower() for t in TARGETS]:
|
||||
print(f"💀 Killing {name} (PID {proc.pid})")
|
||||
proc.kill()
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
continue
|
||||
|
||||
print("✅ All Medicus processes terminated.")
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Step 2: Restart Firebird service (FirebirdServerCGM)
|
||||
Forces all connections to close before database restore.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
SERVICE_NAME = "FirebirdServerCGM"
|
||||
|
||||
def run_cmd(cmd):
|
||||
"""Run a command and return (success, output)."""
|
||||
try:
|
||||
result = subprocess.run(cmd, capture_output=True, text=True, shell=True)
|
||||
return result.returncode == 0, result.stdout.strip() + result.stderr.strip()
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
print(f"🔧 Restarting service: {SERVICE_NAME}")
|
||||
|
||||
# --- Stop service
|
||||
ok, out = run_cmd(f'net stop "{SERVICE_NAME}"')
|
||||
if ok:
|
||||
print(f"🛑 Service {SERVICE_NAME} stopped.")
|
||||
else:
|
||||
print(f"⚠️ Failed to stop service (it may already be stopped):\n{out}")
|
||||
|
||||
# --- Small delay
|
||||
time.sleep(5)
|
||||
|
||||
# --- Start service again
|
||||
ok, out = run_cmd(f'net start "{SERVICE_NAME}"')
|
||||
if ok:
|
||||
print(f"▶️ Service {SERVICE_NAME} started.")
|
||||
else:
|
||||
print(f"❌ Failed to start service:\n{out}")
|
||||
|
||||
print("✅ Firebird restart sequence complete.")
|
||||
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Simplified Medicus Firebird database restore
|
||||
✅ Works locally with Firebird service running
|
||||
✅ Handles spaces in paths
|
||||
✅ Restores newest .fbk backup from restore folder
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
# =========================================
|
||||
# 🔧 CONFIGURATION
|
||||
# =========================================
|
||||
FIREBIRD_GBAK = Path(r"C:\Program Files\Firebird\Firebird_2_5_CGM\bin\gbak.exe")
|
||||
RESTORE_DIR = Path(r"Z:\Medicus 3\restore")
|
||||
TARGET_DB = Path(r"Z:\Medicus 3\data\MEDICUS.FDB")
|
||||
USER = "SYSDBA"
|
||||
PASSWORD = "masterkey"
|
||||
|
||||
# =========================================
|
||||
# 🔍 Find newest .fbk file
|
||||
# =========================================
|
||||
fbk_files = list(RESTORE_DIR.glob("*.fbk"))
|
||||
if not fbk_files:
|
||||
raise SystemExit(f"❌ No .fbk files found in {RESTORE_DIR}")
|
||||
|
||||
latest_fbk = max(fbk_files, key=lambda f: f.stat().st_mtime)
|
||||
print(f"🕒 Latest FBK: {latest_fbk.name}")
|
||||
|
||||
# =========================================
|
||||
# 🧩 Build gbak command (direct restore)
|
||||
# =========================================
|
||||
cmd = f'"{FIREBIRD_GBAK}" -rep "{latest_fbk}" "{TARGET_DB}" -user {USER} -pas {PASSWORD}'
|
||||
print("\n🧩 Running restore command:")
|
||||
print(cmd)
|
||||
|
||||
# =========================================
|
||||
# ▶️ Execute restore
|
||||
# =========================================
|
||||
result = subprocess.run(cmd, shell=True, text=True, capture_output=True)
|
||||
|
||||
if result.returncode == 0:
|
||||
print("✅ Medicus database successfully restored!")
|
||||
else:
|
||||
print("❌ Restore failed:")
|
||||
print(result.stderr.strip() or result.stdout.strip())
|
||||
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Nightly Medicus automatic restore
|
||||
---------------------------------
|
||||
Workflow:
|
||||
1️⃣ Kill all Medicus processes
|
||||
2️⃣ Restart Firebird service (to release all open connections)
|
||||
3️⃣ Clean restore folder
|
||||
4️⃣ Find and extract newest Medicus backup (.fbk or .zip)
|
||||
5️⃣ Restore database using gbak (Firebird service kept running)
|
||||
6️⃣ Restart Firebird again to refresh state
|
||||
7️⃣ Log all actions with timestamps
|
||||
"""
|
||||
|
||||
import os
|
||||
import psutil
|
||||
import shutil
|
||||
import zipfile
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import time
|
||||
|
||||
# =========================================
|
||||
# 🔧 CONFIGURATION
|
||||
# =========================================
|
||||
FIREBIRD_GBAK = Path(r"C:\Program Files\Firebird\Firebird_2_5_CGM\bin\gbak.exe")
|
||||
BACKUP_DIR = Path(r"G:\OnedriveOrdinace\OneDrive\MedicusBackup")
|
||||
RESTORE_DIR = Path(r"Z:\Medicus 3\restore")
|
||||
TARGET_DB = Path(r"Z:\Medicus 3\data\MEDICUS.FDB")
|
||||
SERVICE_NAME = "FirebirdServerCGM"
|
||||
USER = "SYSDBA"
|
||||
PASSWORD = "masterkey"
|
||||
|
||||
LOG_FILE = RESTORE_DIR / f"nightly_restore_{datetime.now():%Y%m%d_%H%M%S}.log"
|
||||
|
||||
# =========================================
|
||||
# 🪓 1) Kill Medicus processes
|
||||
# =========================================
|
||||
def kill_medicus():
|
||||
targets = ["Medicus.exe", "MedicusServer.exe", "MedicusUpdater.exe"]
|
||||
with open(LOG_FILE, "w", encoding="utf-8") as log:
|
||||
log.write(f"==== NIGHTLY RESTORE START {datetime.now():%Y-%m-%d %H:%M:%S} ====\n")
|
||||
for proc in psutil.process_iter(["name", "pid"]):
|
||||
try:
|
||||
if proc.info["name"] and proc.info["name"].lower() in [t.lower() for t in targets]:
|
||||
log.write(f"Killing {proc.info['name']} (PID {proc.pid})\n")
|
||||
proc.kill()
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
continue
|
||||
log.write("All Medicus processes terminated.\n")
|
||||
|
||||
# =========================================
|
||||
# ⚙️ 2) Firebird service control
|
||||
# =========================================
|
||||
def service_cmd(action):
|
||||
cmd = f'net {action} "{SERVICE_NAME}"'
|
||||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
|
||||
return result.returncode == 0, result.stdout + result.stderr
|
||||
|
||||
def restart_firebird(label):
|
||||
ok, out = service_cmd("stop")
|
||||
with open(LOG_FILE, "a", encoding="utf-8") as log:
|
||||
log.write(f"\n--- Restart Firebird ({label}) ---\n")
|
||||
log.write(f"Stopping {SERVICE_NAME}...\n{out}\n")
|
||||
time.sleep(5)
|
||||
ok, out = service_cmd("start")
|
||||
with open(LOG_FILE, "a", encoding="utf-8") as log:
|
||||
log.write(f"Starting {SERVICE_NAME}...\n{out}\n")
|
||||
|
||||
# =========================================
|
||||
# 🧹 3) Clean restore folder
|
||||
# =========================================
|
||||
def clean_restore_folder():
|
||||
for item in RESTORE_DIR.glob("*"):
|
||||
try:
|
||||
if item.is_file():
|
||||
item.unlink()
|
||||
elif item.is_dir():
|
||||
shutil.rmtree(item)
|
||||
except Exception as e:
|
||||
with open(LOG_FILE, "a", encoding="utf-8") as log:
|
||||
log.write(f"⚠️ Could not delete {item}: {e}\n")
|
||||
|
||||
# =========================================
|
||||
# 💾 4) Find newest backup and prepare .fbk
|
||||
# =========================================
|
||||
def prepare_backup():
|
||||
backups = list(BACKUP_DIR.glob("Medicus_*.fbk")) + list(BACKUP_DIR.glob("Medicus_*.zip"))
|
||||
if not backups:
|
||||
raise SystemExit(f"❌ No Medicus backups found in {BACKUP_DIR}")
|
||||
|
||||
latest = max(backups, key=lambda f: f.stat().st_mtime)
|
||||
fbk_path = RESTORE_DIR / (latest.stem + ".fbk")
|
||||
|
||||
with open(LOG_FILE, "a", encoding="utf-8") as log:
|
||||
log.write(f"\nUsing backup: {latest}\n")
|
||||
|
||||
if latest.suffix.lower() == ".fbk":
|
||||
shutil.copy2(latest, fbk_path)
|
||||
else:
|
||||
with zipfile.ZipFile(latest, "r") as zf:
|
||||
fbk_files = [n for n in zf.namelist() if n.lower().endswith(".fbk")]
|
||||
if not fbk_files:
|
||||
raise SystemExit("❌ ZIP file does not contain .fbk!")
|
||||
zf.extract(fbk_files[0], RESTORE_DIR)
|
||||
extracted = RESTORE_DIR / fbk_files[0]
|
||||
if extracted != fbk_path:
|
||||
shutil.move(extracted, fbk_path)
|
||||
|
||||
return fbk_path
|
||||
|
||||
# =========================================
|
||||
# 🧩 5) Restore database using gbak
|
||||
# =========================================
|
||||
def restore_database(fbk_path):
|
||||
cmd = f'"{FIREBIRD_GBAK}" -rep "{fbk_path}" "{TARGET_DB}" -user {USER} -pas {PASSWORD}'
|
||||
with open(LOG_FILE, "a", encoding="utf-8") as log:
|
||||
log.write(f"\nRunning restore command:\n{cmd}\n")
|
||||
|
||||
result = subprocess.run(cmd, shell=True, text=True, capture_output=True)
|
||||
|
||||
with open(LOG_FILE, "a", encoding="utf-8") as log:
|
||||
log.write(result.stdout)
|
||||
log.write(result.stderr)
|
||||
log.write(f"Return code: {result.returncode}\n")
|
||||
|
||||
if result.returncode == 0:
|
||||
print("✅ Medicus database successfully restored!")
|
||||
else:
|
||||
print("❌ Restore failed! Check log for details.")
|
||||
|
||||
# =========================================
|
||||
# 🚀 MAIN WORKFLOW
|
||||
# =========================================
|
||||
if __name__ == "__main__":
|
||||
kill_medicus()
|
||||
restart_firebird("before restore")
|
||||
clean_restore_folder()
|
||||
fbk = prepare_backup()
|
||||
restore_database(fbk)
|
||||
restart_firebird("after restore")
|
||||
|
||||
with open(LOG_FILE, "a", encoding="utf-8") as log:
|
||||
log.write(f"\n==== RESTORE COMPLETED {datetime.now():%Y-%m-%d %H:%M:%S} ====\n")
|
||||
|
||||
print(f"📝 Full log saved to {LOG_FILE}")
|
||||
@@ -0,0 +1,38 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
|
||||
|
||||
a = Analysis(
|
||||
['05 FullCodeNightlyRestore.py'],
|
||||
pathex=[],
|
||||
binaries=[],
|
||||
datas=[],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
hooksconfig={},
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
noarchive=False,
|
||||
optimize=0,
|
||||
)
|
||||
pyz = PYZ(a.pure)
|
||||
|
||||
exe = EXE(
|
||||
pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.datas,
|
||||
[],
|
||||
name='05 FullCodeNightlyRestore',
|
||||
debug=False,
|
||||
bootloader_ignore_signals=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
upx_exclude=[],
|
||||
runtime_tmpdir=None,
|
||||
console=True,
|
||||
disable_windowed_traceback=False,
|
||||
argv_emulation=False,
|
||||
target_arch=None,
|
||||
codesign_identity=None,
|
||||
entitlements_file=None,
|
||||
)
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,865 @@
|
||||
(['C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22\\05 '
|
||||
'FullCodeNightlyRestore.py'],
|
||||
['C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22'],
|
||||
[],
|
||||
[('C:\\Users\\vlado\\PycharmProjects\\medicus\\.venv\\Lib\\site-packages\\_pyinstaller_hooks_contrib\\stdhooks',
|
||||
-1000),
|
||||
('C:\\Users\\vlado\\PycharmProjects\\medicus\\.venv\\Lib\\site-packages\\_pyinstaller_hooks_contrib',
|
||||
-1000)],
|
||||
{},
|
||||
[],
|
||||
[],
|
||||
False,
|
||||
{},
|
||||
0,
|
||||
[],
|
||||
[],
|
||||
'3.12.0 (tags/v3.12.0:0fb18b0, Oct 2 2023, 13:03:39) [MSC v.1935 64 bit '
|
||||
'(AMD64)]',
|
||||
[('pyi_rth_inspect',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\.venv\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py',
|
||||
'PYSOURCE'),
|
||||
('05 FullCodeNightlyRestore',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\NighMedicusRestoreTW22\\05 '
|
||||
'FullCodeNightlyRestore.py',
|
||||
'PYSOURCE')],
|
||||
[('inspect',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\inspect.py',
|
||||
'PYMODULE'),
|
||||
('importlib',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('importlib._bootstrap_external',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\_bootstrap_external.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('typing',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\typing.py',
|
||||
'PYMODULE'),
|
||||
('importlib.abc',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\abc.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources.abc',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\abc.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources._legacy',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\_legacy.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources._common',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\_common.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources._adapters',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\_adapters.py',
|
||||
'PYMODULE'),
|
||||
('tempfile',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tempfile.py',
|
||||
'PYMODULE'),
|
||||
('random',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\random.py',
|
||||
'PYMODULE'),
|
||||
('statistics',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\statistics.py',
|
||||
'PYMODULE'),
|
||||
('decimal',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\decimal.py',
|
||||
'PYMODULE'),
|
||||
('_pydecimal',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_pydecimal.py',
|
||||
'PYMODULE'),
|
||||
('contextvars',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\contextvars.py',
|
||||
'PYMODULE'),
|
||||
('fractions',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\fractions.py',
|
||||
'PYMODULE'),
|
||||
('numbers',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\numbers.py',
|
||||
'PYMODULE'),
|
||||
('hashlib',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\hashlib.py',
|
||||
'PYMODULE'),
|
||||
('logging',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\logging\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('pickle',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\pickle.py',
|
||||
'PYMODULE'),
|
||||
('pprint',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\pprint.py',
|
||||
'PYMODULE'),
|
||||
('dataclasses',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\dataclasses.py',
|
||||
'PYMODULE'),
|
||||
('copy',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\copy.py',
|
||||
'PYMODULE'),
|
||||
('_compat_pickle',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_compat_pickle.py',
|
||||
'PYMODULE'),
|
||||
('struct',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\struct.py',
|
||||
'PYMODULE'),
|
||||
('threading',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\threading.py',
|
||||
'PYMODULE'),
|
||||
('_threading_local',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_threading_local.py',
|
||||
'PYMODULE'),
|
||||
('string',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\string.py',
|
||||
'PYMODULE'),
|
||||
('bisect',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\bisect.py',
|
||||
'PYMODULE'),
|
||||
('importlib._abc',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\_abc.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._itertools',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\_itertools.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._functools',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\_functools.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._collections',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\_collections.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._meta',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\_meta.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._adapters',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\_adapters.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._text',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\_text.py',
|
||||
'PYMODULE'),
|
||||
('email.message',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\message.py',
|
||||
'PYMODULE'),
|
||||
('email.policy',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\policy.py',
|
||||
'PYMODULE'),
|
||||
('email.contentmanager',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\contentmanager.py',
|
||||
'PYMODULE'),
|
||||
('email.quoprimime',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\quoprimime.py',
|
||||
'PYMODULE'),
|
||||
('email.headerregistry',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\headerregistry.py',
|
||||
'PYMODULE'),
|
||||
('email._header_value_parser',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\_header_value_parser.py',
|
||||
'PYMODULE'),
|
||||
('urllib',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\urllib\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('email.iterators',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\iterators.py',
|
||||
'PYMODULE'),
|
||||
('email.generator',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\generator.py',
|
||||
'PYMODULE'),
|
||||
('email._encoded_words',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\_encoded_words.py',
|
||||
'PYMODULE'),
|
||||
('base64',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\base64.py',
|
||||
'PYMODULE'),
|
||||
('getopt',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\getopt.py',
|
||||
'PYMODULE'),
|
||||
('gettext',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\gettext.py',
|
||||
'PYMODULE'),
|
||||
('email.charset',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\charset.py',
|
||||
'PYMODULE'),
|
||||
('email.encoders',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\encoders.py',
|
||||
'PYMODULE'),
|
||||
('email.base64mime',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\base64mime.py',
|
||||
'PYMODULE'),
|
||||
('email._policybase',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\_policybase.py',
|
||||
'PYMODULE'),
|
||||
('email.header',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\header.py',
|
||||
'PYMODULE'),
|
||||
('email.errors',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\errors.py',
|
||||
'PYMODULE'),
|
||||
('email.utils',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\utils.py',
|
||||
'PYMODULE'),
|
||||
('email._parseaddr',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\_parseaddr.py',
|
||||
'PYMODULE'),
|
||||
('calendar',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\calendar.py',
|
||||
'PYMODULE'),
|
||||
('urllib.parse',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\urllib\\parse.py',
|
||||
'PYMODULE'),
|
||||
('ipaddress',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\ipaddress.py',
|
||||
'PYMODULE'),
|
||||
('socket',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\socket.py',
|
||||
'PYMODULE'),
|
||||
('selectors',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\selectors.py',
|
||||
'PYMODULE'),
|
||||
('quopri',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\quopri.py',
|
||||
'PYMODULE'),
|
||||
('contextlib',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\contextlib.py',
|
||||
'PYMODULE'),
|
||||
('textwrap',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\textwrap.py',
|
||||
'PYMODULE'),
|
||||
('email',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('email.parser',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\parser.py',
|
||||
'PYMODULE'),
|
||||
('email.feedparser',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\feedparser.py',
|
||||
'PYMODULE'),
|
||||
('csv',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\csv.py',
|
||||
'PYMODULE'),
|
||||
('importlib.readers',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\readers.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources.readers',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\readers.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources._itertools',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\_itertools.py',
|
||||
'PYMODULE'),
|
||||
('importlib._bootstrap',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\_bootstrap.py',
|
||||
'PYMODULE'),
|
||||
('argparse',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\argparse.py',
|
||||
'PYMODULE'),
|
||||
('token',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\token.py',
|
||||
'PYMODULE'),
|
||||
('tokenize',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tokenize.py',
|
||||
'PYMODULE'),
|
||||
('importlib.machinery',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\machinery.py',
|
||||
'PYMODULE'),
|
||||
('dis',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\dis.py',
|
||||
'PYMODULE'),
|
||||
('opcode',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\opcode.py',
|
||||
'PYMODULE'),
|
||||
('ast',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\ast.py',
|
||||
'PYMODULE'),
|
||||
('tracemalloc',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tracemalloc.py',
|
||||
'PYMODULE'),
|
||||
('fnmatch',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\fnmatch.py',
|
||||
'PYMODULE'),
|
||||
('_py_abc',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_py_abc.py',
|
||||
'PYMODULE'),
|
||||
('stringprep',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\stringprep.py',
|
||||
'PYMODULE'),
|
||||
('bz2',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\bz2.py',
|
||||
'PYMODULE'),
|
||||
('_compression',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_compression.py',
|
||||
'PYMODULE'),
|
||||
('_strptime',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_strptime.py',
|
||||
'PYMODULE'),
|
||||
('datetime',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\datetime.py',
|
||||
'PYMODULE'),
|
||||
('_pydatetime',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_pydatetime.py',
|
||||
'PYMODULE'),
|
||||
('pathlib',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\pathlib.py',
|
||||
'PYMODULE'),
|
||||
('subprocess',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\subprocess.py',
|
||||
'PYMODULE'),
|
||||
('signal',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\signal.py',
|
||||
'PYMODULE'),
|
||||
('zipfile',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\zipfile\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('zipfile.__main__',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\zipfile\\__main__.py',
|
||||
'PYMODULE'),
|
||||
('zipfile._path',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\zipfile\\_path\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('zipfile._path.glob',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\zipfile\\_path\\glob.py',
|
||||
'PYMODULE'),
|
||||
('py_compile',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\py_compile.py',
|
||||
'PYMODULE'),
|
||||
('lzma',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\lzma.py',
|
||||
'PYMODULE'),
|
||||
('importlib.util',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\util.py',
|
||||
'PYMODULE'),
|
||||
('shutil',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\shutil.py',
|
||||
'PYMODULE'),
|
||||
('tarfile',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tarfile.py',
|
||||
'PYMODULE'),
|
||||
('gzip',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\gzip.py',
|
||||
'PYMODULE'),
|
||||
('psutil',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\.venv\\Lib\\site-packages\\psutil\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('psutil._pswindows',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\.venv\\Lib\\site-packages\\psutil\\_pswindows.py',
|
||||
'PYMODULE'),
|
||||
('psutil._common',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\.venv\\Lib\\site-packages\\psutil\\_common.py',
|
||||
'PYMODULE'),
|
||||
('ctypes',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\ctypes\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('ctypes._endian',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\ctypes\\_endian.py',
|
||||
'PYMODULE')],
|
||||
[('python312.dll',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\python312.dll',
|
||||
'BINARY'),
|
||||
('_decimal.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_decimal.pyd',
|
||||
'EXTENSION'),
|
||||
('_hashlib.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_hashlib.pyd',
|
||||
'EXTENSION'),
|
||||
('unicodedata.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\unicodedata.pyd',
|
||||
'EXTENSION'),
|
||||
('select.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\select.pyd',
|
||||
'EXTENSION'),
|
||||
('_socket.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_socket.pyd',
|
||||
'EXTENSION'),
|
||||
('_bz2.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_bz2.pyd',
|
||||
'EXTENSION'),
|
||||
('_lzma.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_lzma.pyd',
|
||||
'EXTENSION'),
|
||||
('psutil\\_psutil_windows.pyd',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\.venv\\Lib\\site-packages\\psutil\\_psutil_windows.pyd',
|
||||
'EXTENSION'),
|
||||
('_ctypes.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_ctypes.pyd',
|
||||
'EXTENSION'),
|
||||
('VCRUNTIME140.dll',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\VCRUNTIME140.dll',
|
||||
'BINARY'),
|
||||
('libcrypto-3.dll',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\libcrypto-3.dll',
|
||||
'BINARY'),
|
||||
('python3.dll',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\python3.dll',
|
||||
'BINARY'),
|
||||
('libffi-8.dll',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\libffi-8.dll',
|
||||
'BINARY')],
|
||||
[],
|
||||
[],
|
||||
[('base_library.zip',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22\\build\\05 '
|
||||
'FullCodeNightlyRestore\\base_library.zip',
|
||||
'DATA')],
|
||||
[('re._parser',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\re\\_parser.py',
|
||||
'PYMODULE'),
|
||||
('re._constants',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\re\\_constants.py',
|
||||
'PYMODULE'),
|
||||
('re._compiler',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\re\\_compiler.py',
|
||||
'PYMODULE'),
|
||||
('re._casefix',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\re\\_casefix.py',
|
||||
'PYMODULE'),
|
||||
('re',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\re\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('sre_compile',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\sre_compile.py',
|
||||
'PYMODULE'),
|
||||
('warnings',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\warnings.py',
|
||||
'PYMODULE'),
|
||||
('reprlib',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\reprlib.py',
|
||||
'PYMODULE'),
|
||||
('heapq',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\heapq.py',
|
||||
'PYMODULE'),
|
||||
('traceback',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\traceback.py',
|
||||
'PYMODULE'),
|
||||
('weakref',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\weakref.py',
|
||||
'PYMODULE'),
|
||||
('linecache',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\linecache.py',
|
||||
'PYMODULE'),
|
||||
('copyreg',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\copyreg.py',
|
||||
'PYMODULE'),
|
||||
('sre_constants',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\sre_constants.py',
|
||||
'PYMODULE'),
|
||||
('collections.abc',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\collections\\abc.py',
|
||||
'PYMODULE'),
|
||||
('collections',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\collections\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('abc',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\abc.py',
|
||||
'PYMODULE'),
|
||||
('operator',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\operator.py',
|
||||
'PYMODULE'),
|
||||
('_collections_abc',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_collections_abc.py',
|
||||
'PYMODULE'),
|
||||
('genericpath',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\genericpath.py',
|
||||
'PYMODULE'),
|
||||
('stat',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\stat.py',
|
||||
'PYMODULE'),
|
||||
('functools',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\functools.py',
|
||||
'PYMODULE'),
|
||||
('locale',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\locale.py',
|
||||
'PYMODULE'),
|
||||
('codecs',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\codecs.py',
|
||||
'PYMODULE'),
|
||||
('sre_parse',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\sre_parse.py',
|
||||
'PYMODULE'),
|
||||
('encodings.zlib_codec',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\zlib_codec.py',
|
||||
'PYMODULE'),
|
||||
('encodings.uu_codec',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\uu_codec.py',
|
||||
'PYMODULE'),
|
||||
('encodings.utf_8_sig',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\utf_8_sig.py',
|
||||
'PYMODULE'),
|
||||
('encodings.utf_8',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\utf_8.py',
|
||||
'PYMODULE'),
|
||||
('encodings.utf_7',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\utf_7.py',
|
||||
'PYMODULE'),
|
||||
('encodings.utf_32_le',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\utf_32_le.py',
|
||||
'PYMODULE'),
|
||||
('encodings.utf_32_be',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\utf_32_be.py',
|
||||
'PYMODULE'),
|
||||
('encodings.utf_32',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\utf_32.py',
|
||||
'PYMODULE'),
|
||||
('encodings.utf_16_le',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\utf_16_le.py',
|
||||
'PYMODULE'),
|
||||
('encodings.utf_16_be',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\utf_16_be.py',
|
||||
'PYMODULE'),
|
||||
('encodings.utf_16',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\utf_16.py',
|
||||
'PYMODULE'),
|
||||
('encodings.unicode_escape',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\unicode_escape.py',
|
||||
'PYMODULE'),
|
||||
('encodings.undefined',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\undefined.py',
|
||||
'PYMODULE'),
|
||||
('encodings.tis_620',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\tis_620.py',
|
||||
'PYMODULE'),
|
||||
('encodings.shift_jisx0213',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\shift_jisx0213.py',
|
||||
'PYMODULE'),
|
||||
('encodings.shift_jis_2004',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\shift_jis_2004.py',
|
||||
'PYMODULE'),
|
||||
('encodings.shift_jis',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\shift_jis.py',
|
||||
'PYMODULE'),
|
||||
('encodings.rot_13',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\rot_13.py',
|
||||
'PYMODULE'),
|
||||
('encodings.raw_unicode_escape',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\raw_unicode_escape.py',
|
||||
'PYMODULE'),
|
||||
('encodings.quopri_codec',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\quopri_codec.py',
|
||||
'PYMODULE'),
|
||||
('encodings.punycode',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\punycode.py',
|
||||
'PYMODULE'),
|
||||
('encodings.ptcp154',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\ptcp154.py',
|
||||
'PYMODULE'),
|
||||
('encodings.palmos',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\palmos.py',
|
||||
'PYMODULE'),
|
||||
('encodings.oem',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\oem.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mbcs',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\mbcs.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mac_turkish',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\mac_turkish.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mac_romanian',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\mac_romanian.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mac_roman',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\mac_roman.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mac_latin2',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\mac_latin2.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mac_iceland',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\mac_iceland.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mac_greek',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\mac_greek.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mac_farsi',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\mac_farsi.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mac_cyrillic',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\mac_cyrillic.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mac_croatian',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\mac_croatian.py',
|
||||
'PYMODULE'),
|
||||
('encodings.mac_arabic',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\mac_arabic.py',
|
||||
'PYMODULE'),
|
||||
('encodings.latin_1',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\latin_1.py',
|
||||
'PYMODULE'),
|
||||
('encodings.kz1048',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\kz1048.py',
|
||||
'PYMODULE'),
|
||||
('encodings.koi8_u',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\koi8_u.py',
|
||||
'PYMODULE'),
|
||||
('encodings.koi8_t',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\koi8_t.py',
|
||||
'PYMODULE'),
|
||||
('encodings.koi8_r',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\koi8_r.py',
|
||||
'PYMODULE'),
|
||||
('encodings.johab',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\johab.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_9',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso8859_9.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_8',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso8859_8.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_7',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso8859_7.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_6',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso8859_6.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_5',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso8859_5.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_4',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso8859_4.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_3',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso8859_3.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_2',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso8859_2.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_16',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso8859_16.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_15',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso8859_15.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_14',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso8859_14.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_13',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso8859_13.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_11',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso8859_11.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_10',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso8859_10.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso8859_1',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso8859_1.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso2022_kr',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso2022_kr.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso2022_jp_ext',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso2022_jp_ext.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso2022_jp_3',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso2022_jp_3.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso2022_jp_2004',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso2022_jp_2004.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso2022_jp_2',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso2022_jp_2.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso2022_jp_1',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso2022_jp_1.py',
|
||||
'PYMODULE'),
|
||||
('encodings.iso2022_jp',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\iso2022_jp.py',
|
||||
'PYMODULE'),
|
||||
('encodings.idna',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\idna.py',
|
||||
'PYMODULE'),
|
||||
('encodings.hz',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\hz.py',
|
||||
'PYMODULE'),
|
||||
('encodings.hp_roman8',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\hp_roman8.py',
|
||||
'PYMODULE'),
|
||||
('encodings.hex_codec',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\hex_codec.py',
|
||||
'PYMODULE'),
|
||||
('encodings.gbk',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\gbk.py',
|
||||
'PYMODULE'),
|
||||
('encodings.gb2312',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\gb2312.py',
|
||||
'PYMODULE'),
|
||||
('encodings.gb18030',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\gb18030.py',
|
||||
'PYMODULE'),
|
||||
('encodings.euc_kr',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\euc_kr.py',
|
||||
'PYMODULE'),
|
||||
('encodings.euc_jp',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\euc_jp.py',
|
||||
'PYMODULE'),
|
||||
('encodings.euc_jisx0213',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\euc_jisx0213.py',
|
||||
'PYMODULE'),
|
||||
('encodings.euc_jis_2004',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\euc_jis_2004.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp950',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp950.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp949',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp949.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp932',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp932.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp875',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp875.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp874',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp874.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp869',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp869.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp866',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp866.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp865',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp865.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp864',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp864.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp863',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp863.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp862',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp862.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp861',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp861.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp860',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp860.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp858',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp858.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp857',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp857.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp856',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp856.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp855',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp855.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp852',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp852.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp850',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp850.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp775',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp775.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp737',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp737.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp720',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp720.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp500',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp500.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp437',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp437.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp424',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp424.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp273',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp273.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1258',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp1258.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1257',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp1257.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1256',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp1256.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1255',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp1255.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1254',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp1254.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1253',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp1253.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1252',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp1252.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1251',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp1251.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1250',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp1250.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1140',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp1140.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1125',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp1125.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1026',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp1026.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp1006',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp1006.py',
|
||||
'PYMODULE'),
|
||||
('encodings.cp037',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\cp037.py',
|
||||
'PYMODULE'),
|
||||
('encodings.charmap',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\charmap.py',
|
||||
'PYMODULE'),
|
||||
('encodings.bz2_codec',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\bz2_codec.py',
|
||||
'PYMODULE'),
|
||||
('encodings.big5hkscs',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\big5hkscs.py',
|
||||
'PYMODULE'),
|
||||
('encodings.big5',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\big5.py',
|
||||
'PYMODULE'),
|
||||
('encodings.base64_codec',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\base64_codec.py',
|
||||
'PYMODULE'),
|
||||
('encodings.ascii',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\ascii.py',
|
||||
'PYMODULE'),
|
||||
('encodings.aliases',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\aliases.py',
|
||||
'PYMODULE'),
|
||||
('encodings',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\encodings\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('keyword',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\keyword.py',
|
||||
'PYMODULE'),
|
||||
('posixpath',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\posixpath.py',
|
||||
'PYMODULE'),
|
||||
('types',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\types.py',
|
||||
'PYMODULE'),
|
||||
('ntpath',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\ntpath.py',
|
||||
'PYMODULE'),
|
||||
('io',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\io.py',
|
||||
'PYMODULE'),
|
||||
('enum',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\enum.py',
|
||||
'PYMODULE'),
|
||||
('_weakrefset',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_weakrefset.py',
|
||||
'PYMODULE'),
|
||||
('os',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\os.py',
|
||||
'PYMODULE')])
|
||||
@@ -0,0 +1,123 @@
|
||||
('C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22\\dist\\05 '
|
||||
'FullCodeNightlyRestore.exe',
|
||||
True,
|
||||
False,
|
||||
False,
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\.venv\\Lib\\site-packages\\PyInstaller\\bootloader\\images\\icon-console.ico',
|
||||
None,
|
||||
False,
|
||||
False,
|
||||
b'<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n<assembly xmlns='
|
||||
b'"urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">\n <trustInfo x'
|
||||
b'mlns="urn:schemas-microsoft-com:asm.v3">\n <security>\n <requested'
|
||||
b'Privileges>\n <requestedExecutionLevel level="asInvoker" uiAccess='
|
||||
b'"false"/>\n </requestedPrivileges>\n </security>\n </trustInfo>\n '
|
||||
b'<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">\n <'
|
||||
b'application>\n <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f'
|
||||
b'0}"/>\n <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>\n '
|
||||
b' <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>\n <s'
|
||||
b'upportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>\n <supporte'
|
||||
b'dOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>\n </application>\n <'
|
||||
b'/compatibility>\n <application xmlns="urn:schemas-microsoft-com:asm.v3">'
|
||||
b'\n <windowsSettings>\n <longPathAware xmlns="http://schemas.micros'
|
||||
b'oft.com/SMI/2016/WindowsSettings">true</longPathAware>\n </windowsSett'
|
||||
b'ings>\n </application>\n <dependency>\n <dependentAssembly>\n <ass'
|
||||
b'emblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version='
|
||||
b'"6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" langua'
|
||||
b'ge="*"/>\n </dependentAssembly>\n </dependency>\n</assembly>',
|
||||
True,
|
||||
False,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22\\build\\05 '
|
||||
'FullCodeNightlyRestore\\05 FullCodeNightlyRestore.pkg',
|
||||
[('pyi-contents-directory _internal', '', 'OPTION'),
|
||||
('PYZ-00.pyz',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22\\build\\05 '
|
||||
'FullCodeNightlyRestore\\PYZ-00.pyz',
|
||||
'PYZ'),
|
||||
('struct',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22\\build\\05 '
|
||||
'FullCodeNightlyRestore\\localpycs\\struct.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod01_archive',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22\\build\\05 '
|
||||
'FullCodeNightlyRestore\\localpycs\\pyimod01_archive.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod02_importers',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22\\build\\05 '
|
||||
'FullCodeNightlyRestore\\localpycs\\pyimod02_importers.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod03_ctypes',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22\\build\\05 '
|
||||
'FullCodeNightlyRestore\\localpycs\\pyimod03_ctypes.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod04_pywin32',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22\\build\\05 '
|
||||
'FullCodeNightlyRestore\\localpycs\\pyimod04_pywin32.pyc',
|
||||
'PYMODULE'),
|
||||
('pyiboot01_bootstrap',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\.venv\\Lib\\site-packages\\PyInstaller\\loader\\pyiboot01_bootstrap.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_inspect',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\.venv\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py',
|
||||
'PYSOURCE'),
|
||||
('05 FullCodeNightlyRestore',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\NighMedicusRestoreTW22\\05 '
|
||||
'FullCodeNightlyRestore.py',
|
||||
'PYSOURCE'),
|
||||
('python312.dll',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\python312.dll',
|
||||
'BINARY'),
|
||||
('_decimal.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_decimal.pyd',
|
||||
'EXTENSION'),
|
||||
('_hashlib.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_hashlib.pyd',
|
||||
'EXTENSION'),
|
||||
('unicodedata.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\unicodedata.pyd',
|
||||
'EXTENSION'),
|
||||
('select.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\select.pyd',
|
||||
'EXTENSION'),
|
||||
('_socket.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_socket.pyd',
|
||||
'EXTENSION'),
|
||||
('_bz2.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_bz2.pyd',
|
||||
'EXTENSION'),
|
||||
('_lzma.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_lzma.pyd',
|
||||
'EXTENSION'),
|
||||
('psutil\\_psutil_windows.pyd',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\.venv\\Lib\\site-packages\\psutil\\_psutil_windows.pyd',
|
||||
'EXTENSION'),
|
||||
('_ctypes.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_ctypes.pyd',
|
||||
'EXTENSION'),
|
||||
('VCRUNTIME140.dll',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\VCRUNTIME140.dll',
|
||||
'BINARY'),
|
||||
('libcrypto-3.dll',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\libcrypto-3.dll',
|
||||
'BINARY'),
|
||||
('python3.dll',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\python3.dll',
|
||||
'BINARY'),
|
||||
('libffi-8.dll',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\libffi-8.dll',
|
||||
'BINARY'),
|
||||
('base_library.zip',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22\\build\\05 '
|
||||
'FullCodeNightlyRestore\\base_library.zip',
|
||||
'DATA')],
|
||||
[],
|
||||
False,
|
||||
False,
|
||||
1762244851,
|
||||
[('run.exe',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\.venv\\Lib\\site-packages\\PyInstaller\\bootloader\\Windows-64bit-intel\\run.exe',
|
||||
'EXECUTABLE')],
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\python312.dll')
|
||||
@@ -0,0 +1,100 @@
|
||||
('C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22\\build\\05 '
|
||||
'FullCodeNightlyRestore\\05 FullCodeNightlyRestore.pkg',
|
||||
{'BINARY': True,
|
||||
'DATA': True,
|
||||
'EXECUTABLE': True,
|
||||
'EXTENSION': True,
|
||||
'PYMODULE': True,
|
||||
'PYSOURCE': True,
|
||||
'PYZ': False,
|
||||
'SPLASH': True,
|
||||
'SYMLINK': False},
|
||||
[('pyi-contents-directory _internal', '', 'OPTION'),
|
||||
('PYZ-00.pyz',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22\\build\\05 '
|
||||
'FullCodeNightlyRestore\\PYZ-00.pyz',
|
||||
'PYZ'),
|
||||
('struct',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22\\build\\05 '
|
||||
'FullCodeNightlyRestore\\localpycs\\struct.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod01_archive',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22\\build\\05 '
|
||||
'FullCodeNightlyRestore\\localpycs\\pyimod01_archive.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod02_importers',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22\\build\\05 '
|
||||
'FullCodeNightlyRestore\\localpycs\\pyimod02_importers.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod03_ctypes',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22\\build\\05 '
|
||||
'FullCodeNightlyRestore\\localpycs\\pyimod03_ctypes.pyc',
|
||||
'PYMODULE'),
|
||||
('pyimod04_pywin32',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22\\build\\05 '
|
||||
'FullCodeNightlyRestore\\localpycs\\pyimod04_pywin32.pyc',
|
||||
'PYMODULE'),
|
||||
('pyiboot01_bootstrap',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\.venv\\Lib\\site-packages\\PyInstaller\\loader\\pyiboot01_bootstrap.py',
|
||||
'PYSOURCE'),
|
||||
('pyi_rth_inspect',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\.venv\\Lib\\site-packages\\PyInstaller\\hooks\\rthooks\\pyi_rth_inspect.py',
|
||||
'PYSOURCE'),
|
||||
('05 FullCodeNightlyRestore',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\NighMedicusRestoreTW22\\05 '
|
||||
'FullCodeNightlyRestore.py',
|
||||
'PYSOURCE'),
|
||||
('python312.dll',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\python312.dll',
|
||||
'BINARY'),
|
||||
('_decimal.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_decimal.pyd',
|
||||
'EXTENSION'),
|
||||
('_hashlib.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_hashlib.pyd',
|
||||
'EXTENSION'),
|
||||
('unicodedata.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\unicodedata.pyd',
|
||||
'EXTENSION'),
|
||||
('select.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\select.pyd',
|
||||
'EXTENSION'),
|
||||
('_socket.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_socket.pyd',
|
||||
'EXTENSION'),
|
||||
('_bz2.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_bz2.pyd',
|
||||
'EXTENSION'),
|
||||
('_lzma.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_lzma.pyd',
|
||||
'EXTENSION'),
|
||||
('psutil\\_psutil_windows.pyd',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\.venv\\Lib\\site-packages\\psutil\\_psutil_windows.pyd',
|
||||
'EXTENSION'),
|
||||
('_ctypes.pyd',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\_ctypes.pyd',
|
||||
'EXTENSION'),
|
||||
('VCRUNTIME140.dll',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\VCRUNTIME140.dll',
|
||||
'BINARY'),
|
||||
('libcrypto-3.dll',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\libcrypto-3.dll',
|
||||
'BINARY'),
|
||||
('python3.dll',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\python3.dll',
|
||||
'BINARY'),
|
||||
('libffi-8.dll',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\DLLs\\libffi-8.dll',
|
||||
'BINARY'),
|
||||
('base_library.zip',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22\\build\\05 '
|
||||
'FullCodeNightlyRestore\\base_library.zip',
|
||||
'DATA')],
|
||||
'python312.dll',
|
||||
False,
|
||||
False,
|
||||
False,
|
||||
[],
|
||||
None,
|
||||
None,
|
||||
None)
|
||||
Binary file not shown.
@@ -0,0 +1,326 @@
|
||||
('C:\\Users\\vlado\\PycharmProjects\\medicus\\nighmedicusrestoretw22\\build\\05 '
|
||||
'FullCodeNightlyRestore\\PYZ-00.pyz',
|
||||
[('_compat_pickle',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_compat_pickle.py',
|
||||
'PYMODULE'),
|
||||
('_compression',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_compression.py',
|
||||
'PYMODULE'),
|
||||
('_py_abc',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_py_abc.py',
|
||||
'PYMODULE'),
|
||||
('_pydatetime',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_pydatetime.py',
|
||||
'PYMODULE'),
|
||||
('_pydecimal',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_pydecimal.py',
|
||||
'PYMODULE'),
|
||||
('_strptime',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_strptime.py',
|
||||
'PYMODULE'),
|
||||
('_threading_local',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\_threading_local.py',
|
||||
'PYMODULE'),
|
||||
('argparse',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\argparse.py',
|
||||
'PYMODULE'),
|
||||
('ast',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\ast.py',
|
||||
'PYMODULE'),
|
||||
('base64',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\base64.py',
|
||||
'PYMODULE'),
|
||||
('bisect',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\bisect.py',
|
||||
'PYMODULE'),
|
||||
('bz2',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\bz2.py',
|
||||
'PYMODULE'),
|
||||
('calendar',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\calendar.py',
|
||||
'PYMODULE'),
|
||||
('contextlib',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\contextlib.py',
|
||||
'PYMODULE'),
|
||||
('contextvars',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\contextvars.py',
|
||||
'PYMODULE'),
|
||||
('copy',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\copy.py',
|
||||
'PYMODULE'),
|
||||
('csv',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\csv.py',
|
||||
'PYMODULE'),
|
||||
('ctypes',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\ctypes\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('ctypes._endian',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\ctypes\\_endian.py',
|
||||
'PYMODULE'),
|
||||
('dataclasses',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\dataclasses.py',
|
||||
'PYMODULE'),
|
||||
('datetime',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\datetime.py',
|
||||
'PYMODULE'),
|
||||
('decimal',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\decimal.py',
|
||||
'PYMODULE'),
|
||||
('dis',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\dis.py',
|
||||
'PYMODULE'),
|
||||
('email',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('email._encoded_words',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\_encoded_words.py',
|
||||
'PYMODULE'),
|
||||
('email._header_value_parser',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\_header_value_parser.py',
|
||||
'PYMODULE'),
|
||||
('email._parseaddr',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\_parseaddr.py',
|
||||
'PYMODULE'),
|
||||
('email._policybase',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\_policybase.py',
|
||||
'PYMODULE'),
|
||||
('email.base64mime',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\base64mime.py',
|
||||
'PYMODULE'),
|
||||
('email.charset',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\charset.py',
|
||||
'PYMODULE'),
|
||||
('email.contentmanager',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\contentmanager.py',
|
||||
'PYMODULE'),
|
||||
('email.encoders',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\encoders.py',
|
||||
'PYMODULE'),
|
||||
('email.errors',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\errors.py',
|
||||
'PYMODULE'),
|
||||
('email.feedparser',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\feedparser.py',
|
||||
'PYMODULE'),
|
||||
('email.generator',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\generator.py',
|
||||
'PYMODULE'),
|
||||
('email.header',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\header.py',
|
||||
'PYMODULE'),
|
||||
('email.headerregistry',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\headerregistry.py',
|
||||
'PYMODULE'),
|
||||
('email.iterators',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\iterators.py',
|
||||
'PYMODULE'),
|
||||
('email.message',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\message.py',
|
||||
'PYMODULE'),
|
||||
('email.parser',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\parser.py',
|
||||
'PYMODULE'),
|
||||
('email.policy',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\policy.py',
|
||||
'PYMODULE'),
|
||||
('email.quoprimime',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\quoprimime.py',
|
||||
'PYMODULE'),
|
||||
('email.utils',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\email\\utils.py',
|
||||
'PYMODULE'),
|
||||
('fnmatch',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\fnmatch.py',
|
||||
'PYMODULE'),
|
||||
('fractions',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\fractions.py',
|
||||
'PYMODULE'),
|
||||
('getopt',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\getopt.py',
|
||||
'PYMODULE'),
|
||||
('gettext',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\gettext.py',
|
||||
'PYMODULE'),
|
||||
('gzip',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\gzip.py',
|
||||
'PYMODULE'),
|
||||
('hashlib',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\hashlib.py',
|
||||
'PYMODULE'),
|
||||
('importlib',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('importlib._abc',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\_abc.py',
|
||||
'PYMODULE'),
|
||||
('importlib._bootstrap',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\_bootstrap.py',
|
||||
'PYMODULE'),
|
||||
('importlib._bootstrap_external',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\_bootstrap_external.py',
|
||||
'PYMODULE'),
|
||||
('importlib.abc',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\abc.py',
|
||||
'PYMODULE'),
|
||||
('importlib.machinery',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\machinery.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._adapters',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\_adapters.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._collections',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\_collections.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._functools',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\_functools.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._itertools',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\_itertools.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._meta',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\_meta.py',
|
||||
'PYMODULE'),
|
||||
('importlib.metadata._text',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\metadata\\_text.py',
|
||||
'PYMODULE'),
|
||||
('importlib.readers',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\readers.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources._adapters',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\_adapters.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources._common',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\_common.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources._itertools',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\_itertools.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources._legacy',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\_legacy.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources.abc',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\abc.py',
|
||||
'PYMODULE'),
|
||||
('importlib.resources.readers',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\resources\\readers.py',
|
||||
'PYMODULE'),
|
||||
('importlib.util',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\importlib\\util.py',
|
||||
'PYMODULE'),
|
||||
('inspect',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\inspect.py',
|
||||
'PYMODULE'),
|
||||
('ipaddress',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\ipaddress.py',
|
||||
'PYMODULE'),
|
||||
('logging',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\logging\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('lzma',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\lzma.py',
|
||||
'PYMODULE'),
|
||||
('numbers',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\numbers.py',
|
||||
'PYMODULE'),
|
||||
('opcode',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\opcode.py',
|
||||
'PYMODULE'),
|
||||
('pathlib',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\pathlib.py',
|
||||
'PYMODULE'),
|
||||
('pickle',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\pickle.py',
|
||||
'PYMODULE'),
|
||||
('pprint',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\pprint.py',
|
||||
'PYMODULE'),
|
||||
('psutil',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\.venv\\Lib\\site-packages\\psutil\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('psutil._common',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\.venv\\Lib\\site-packages\\psutil\\_common.py',
|
||||
'PYMODULE'),
|
||||
('psutil._pswindows',
|
||||
'C:\\Users\\vlado\\PycharmProjects\\medicus\\.venv\\Lib\\site-packages\\psutil\\_pswindows.py',
|
||||
'PYMODULE'),
|
||||
('py_compile',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\py_compile.py',
|
||||
'PYMODULE'),
|
||||
('quopri',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\quopri.py',
|
||||
'PYMODULE'),
|
||||
('random',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\random.py',
|
||||
'PYMODULE'),
|
||||
('selectors',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\selectors.py',
|
||||
'PYMODULE'),
|
||||
('shutil',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\shutil.py',
|
||||
'PYMODULE'),
|
||||
('signal',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\signal.py',
|
||||
'PYMODULE'),
|
||||
('socket',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\socket.py',
|
||||
'PYMODULE'),
|
||||
('statistics',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\statistics.py',
|
||||
'PYMODULE'),
|
||||
('string',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\string.py',
|
||||
'PYMODULE'),
|
||||
('stringprep',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\stringprep.py',
|
||||
'PYMODULE'),
|
||||
('subprocess',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\subprocess.py',
|
||||
'PYMODULE'),
|
||||
('tarfile',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tarfile.py',
|
||||
'PYMODULE'),
|
||||
('tempfile',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tempfile.py',
|
||||
'PYMODULE'),
|
||||
('textwrap',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\textwrap.py',
|
||||
'PYMODULE'),
|
||||
('threading',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\threading.py',
|
||||
'PYMODULE'),
|
||||
('token',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\token.py',
|
||||
'PYMODULE'),
|
||||
('tokenize',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tokenize.py',
|
||||
'PYMODULE'),
|
||||
('tracemalloc',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\tracemalloc.py',
|
||||
'PYMODULE'),
|
||||
('typing',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\typing.py',
|
||||
'PYMODULE'),
|
||||
('urllib',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\urllib\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('urllib.parse',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\urllib\\parse.py',
|
||||
'PYMODULE'),
|
||||
('zipfile',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\zipfile\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('zipfile.__main__',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\zipfile\\__main__.py',
|
||||
'PYMODULE'),
|
||||
('zipfile._path',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\zipfile\\_path\\__init__.py',
|
||||
'PYMODULE'),
|
||||
('zipfile._path.glob',
|
||||
'C:\\Users\\vlado\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\zipfile\\_path\\glob.py',
|
||||
'PYMODULE')])
|
||||
Binary file not shown.
+25
@@ -0,0 +1,25 @@
|
||||
|
||||
This file lists modules PyInstaller was not able to find. This does not
|
||||
necessarily mean this module is required for running your program. Python and
|
||||
Python 3rd-party packages include a lot of conditional or optional modules. For
|
||||
example the module 'ntpath' only exists on Windows, whereas the module
|
||||
'posixpath' only exists on Posix systems.
|
||||
|
||||
Types if import:
|
||||
* top-level: imported at the top-level - look at these first
|
||||
* conditional: imported within an if-statement
|
||||
* delayed: imported within a function
|
||||
* optional: imported within a try-except-statement
|
||||
|
||||
IMPORTANT: Do NOT post this list to the issue-tracker. Use it as a basis for
|
||||
tracking down the missing module yourself. Thanks!
|
||||
|
||||
missing module named _sha512 - imported by random (optional)
|
||||
missing module named posix - imported by os (conditional, optional), shutil (conditional), importlib._bootstrap_external (conditional), posixpath (optional)
|
||||
missing module named resource - imported by posix (top-level)
|
||||
missing module named _frozen_importlib_external - imported by importlib._bootstrap (delayed), importlib (optional), importlib.abc (optional)
|
||||
excluded module named _frozen_importlib - imported by importlib (optional), importlib.abc (optional)
|
||||
missing module named pwd - imported by posixpath (delayed, conditional, optional), shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional), psutil (optional)
|
||||
missing module named grp - imported by shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional)
|
||||
missing module named _posixsubprocess - imported by subprocess (conditional)
|
||||
missing module named fcntl - imported by subprocess (optional)
|
||||
+7423
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,168 @@
|
||||
"""
|
||||
001_pruzkum_PN_tabulek.py
|
||||
=========================
|
||||
Průzkum Medicus DB – hledáme tabulky a strukturu relacionou s PN
|
||||
(pracovní neschopnost).
|
||||
|
||||
Spustit na Windows:
|
||||
python "001_pruzkum_PN_tabulek.py"
|
||||
|
||||
Výstup: přehled tabulek s PN v názvu + jejich sloupce + ukázka dat.
|
||||
"""
|
||||
import fdb
|
||||
import datetime
|
||||
|
||||
# ── Připojení ──────────────────────────────────────────────────────────────────
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA',
|
||||
password='masterkey',
|
||||
charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
# ── 1. Všechny tabulky s "PN" v názvu ─────────────────────────────────────────
|
||||
print("=" * 60)
|
||||
print("TABULKY obsahující 'PN' v názvu")
|
||||
print("=" * 60)
|
||||
|
||||
cur.execute("""
|
||||
SELECT TRIM(rdb$relation_name)
|
||||
FROM rdb$relations
|
||||
WHERE rdb$view_blr IS NULL
|
||||
AND (rdb$system_flag IS NULL OR rdb$system_flag = 0)
|
||||
AND UPPER(rdb$relation_name) CONTAINING 'PN'
|
||||
ORDER BY rdb$relation_name
|
||||
""")
|
||||
pn_tables = [row[0] for row in cur.fetchall()]
|
||||
|
||||
if pn_tables:
|
||||
for t in pn_tables:
|
||||
print(f" {t}")
|
||||
else:
|
||||
print(" (žádná)")
|
||||
|
||||
# ── 2. Sloupce každé PN tabulky + ukázka dat ──────────────────────────────────
|
||||
for table in pn_tables:
|
||||
print(f"\n{'─' * 60}")
|
||||
print(f"TABULKA: {table}")
|
||||
print(f"{'─' * 60}")
|
||||
|
||||
# Sloupce
|
||||
cur.execute("""
|
||||
SELECT TRIM(rf.rdb$field_name),
|
||||
f.rdb$field_type,
|
||||
f.rdb$field_length,
|
||||
rf.rdb$null_flag
|
||||
FROM rdb$relation_fields rf
|
||||
JOIN rdb$fields f ON rf.rdb$field_source = f.rdb$field_name
|
||||
WHERE TRIM(rf.rdb$relation_name) = ?
|
||||
ORDER BY rf.rdb$field_position
|
||||
""", (table,))
|
||||
cols = cur.fetchall()
|
||||
|
||||
# field_type kódy Firebirdu (nejběžnější)
|
||||
type_map = {
|
||||
7: 'SMALLINT', 8: 'INTEGER', 10: 'FLOAT', 12: 'DATE',
|
||||
13: 'TIME', 14: 'CHAR', 16: 'BIGINT', 27: 'DOUBLE',
|
||||
35: 'TIMESTAMP', 37: 'VARCHAR', 261: 'BLOB',
|
||||
}
|
||||
|
||||
col_names = []
|
||||
for c in cols:
|
||||
name, ftype, flen, nullable = c
|
||||
type_str = type_map.get(ftype, f'type#{ftype}')
|
||||
if flen and ftype in (14, 37):
|
||||
type_str += f'({flen})'
|
||||
null_str = '' if nullable else ' NOT NULL'
|
||||
print(f" {name:<25} {type_str}{null_str}")
|
||||
col_names.append(name)
|
||||
|
||||
# Počet řádků
|
||||
try:
|
||||
cur.execute(f'SELECT COUNT(*) FROM {table}')
|
||||
pocet = cur.fetchone()[0]
|
||||
print(f"\n --> Celkem řádků: {pocet}")
|
||||
except Exception as e:
|
||||
print(f"\n --> COUNT selhalo: {e}")
|
||||
continue
|
||||
|
||||
# Ukázka prvních 5 řádků
|
||||
if pocet > 0:
|
||||
try:
|
||||
cur.execute(f'SELECT FIRST 5 * FROM {table}')
|
||||
rows = cur.fetchall()
|
||||
print(f"\n Ukázka (max 5 řádků):")
|
||||
header = " | ".join(f"{n[:12]:>12}" for n in col_names)
|
||||
print(f" {header}")
|
||||
print(f" {'-' * len(header)}")
|
||||
for row in rows:
|
||||
vals = []
|
||||
for v in row:
|
||||
if isinstance(v, (datetime.date, datetime.datetime)):
|
||||
vals.append(v.isoformat()[:12])
|
||||
elif isinstance(v, bytes):
|
||||
vals.append(f'<blob {len(v)}B>')
|
||||
elif v is None:
|
||||
vals.append('NULL')
|
||||
else:
|
||||
vals.append(str(v)[:12])
|
||||
print(f" {' | '.join(f'{v:>12}' for v in vals)}")
|
||||
except Exception as e:
|
||||
print(f" Ukázka selhala: {e}")
|
||||
|
||||
# ── 3. Hledáme i tabulky s "NESCHOP" nebo "NESCHOPENK" ────────────────────────
|
||||
print(f"\n{'=' * 60}")
|
||||
print("TABULKY s 'NESCHOP' nebo 'PRACOV' nebo 'SICK' v názvu")
|
||||
print("=" * 60)
|
||||
|
||||
cur.execute("""
|
||||
SELECT TRIM(rdb$relation_name)
|
||||
FROM rdb$relations
|
||||
WHERE rdb$view_blr IS NULL
|
||||
AND (rdb$system_flag IS NULL OR rdb$system_flag = 0)
|
||||
AND (
|
||||
UPPER(rdb$relation_name) CONTAINING 'NESCHOP'
|
||||
OR UPPER(rdb$relation_name) CONTAINING 'PRACOV'
|
||||
OR UPPER(rdb$relation_name) CONTAINING 'SICK'
|
||||
)
|
||||
ORDER BY rdb$relation_name
|
||||
""")
|
||||
extra = [row[0] for row in cur.fetchall()]
|
||||
if extra:
|
||||
for t in extra:
|
||||
print(f" {t}")
|
||||
else:
|
||||
print(" (žádná)")
|
||||
|
||||
# ── 4. Rychlý přehled – tabulky s datem "OD/DO" nebo "ZACATEK/KONEC" ──────────
|
||||
print(f"\n{'=' * 60}")
|
||||
print("SLOUPCE: hledáme 'DATPN', 'DATDO', 'DATUM_DO' v celé DB")
|
||||
print("=" * 60)
|
||||
|
||||
cur.execute("""
|
||||
SELECT TRIM(rf.rdb$relation_name), TRIM(rf.rdb$field_name)
|
||||
FROM rdb$relation_fields rf
|
||||
JOIN rdb$relations r ON rf.rdb$relation_name = r.rdb$relation_name
|
||||
WHERE (r.rdb$system_flag IS NULL OR r.rdb$system_flag = 0)
|
||||
AND r.rdb$view_blr IS NULL
|
||||
AND (
|
||||
UPPER(rf.rdb$field_name) CONTAINING 'DATPN'
|
||||
OR UPPER(rf.rdb$field_name) = 'DATDO'
|
||||
OR UPPER(rf.rdb$field_name) CONTAINING 'DATUM_DO'
|
||||
OR UPPER(rf.rdb$field_name) CONTAINING 'DAT_DO'
|
||||
OR UPPER(rf.rdb$field_name) CONTAINING 'PN'
|
||||
)
|
||||
ORDER BY rf.rdb$relation_name, rf.rdb$field_name
|
||||
""")
|
||||
hits = cur.fetchall()
|
||||
if hits:
|
||||
for tab, col in hits:
|
||||
print(f" {tab:<30} {col}")
|
||||
else:
|
||||
print(" (nic nenalezeno)")
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
print(f"\n{'=' * 60}")
|
||||
print("Průzkum dokončen.")
|
||||
@@ -0,0 +1,184 @@
|
||||
"""
|
||||
002_detail_NES_NP_tabulky.py
|
||||
============================
|
||||
Průzkum klíčových tabulek neschopenky:
|
||||
- NES → hlavní tabulka pracovní neschopnosti
|
||||
- NP → pravděpodobně neschopenka podání
|
||||
- SOC_NEPRITOMNOST → sociální nepřítomnost
|
||||
|
||||
Spustit na Windows:
|
||||
python "002_detail_NES_NP_tabulky.py"
|
||||
"""
|
||||
import fdb
|
||||
import datetime
|
||||
|
||||
conn = fdb.connect(
|
||||
dsn=r'localhost:c:\medicus 3\data\medicus.fdb',
|
||||
user='SYSDBA',
|
||||
password='masterkey',
|
||||
charset='win1250'
|
||||
)
|
||||
cur = conn.cursor()
|
||||
|
||||
|
||||
def serialize(v):
|
||||
if isinstance(v, (datetime.date, datetime.datetime)):
|
||||
return v.isoformat()
|
||||
if isinstance(v, datetime.time):
|
||||
return v.isoformat()
|
||||
if isinstance(v, bytes):
|
||||
# Pro BLOB zkusíme dekódovat jako text
|
||||
try:
|
||||
return v.decode('cp1250')[:80]
|
||||
except Exception:
|
||||
return f'<blob {len(v)}B>'
|
||||
if v is None:
|
||||
return 'NULL'
|
||||
return str(v)
|
||||
|
||||
|
||||
def inspect_table(table):
|
||||
print(f"\n{'=' * 70}")
|
||||
print(f" TABULKA: {table}")
|
||||
print(f"{'=' * 70}")
|
||||
|
||||
# Sloupce
|
||||
cur.execute("""
|
||||
SELECT TRIM(rf.rdb$field_name),
|
||||
f.rdb$field_type,
|
||||
f.rdb$field_length
|
||||
FROM rdb$relation_fields rf
|
||||
JOIN rdb$fields f ON rf.rdb$field_source = f.rdb$field_name
|
||||
WHERE TRIM(rf.rdb$relation_name) = ?
|
||||
ORDER BY rf.rdb$field_position
|
||||
""", (table,))
|
||||
cols = cur.fetchall()
|
||||
type_map = {
|
||||
7: 'SMALLINT', 8: 'INTEGER', 10: 'FLOAT', 12: 'DATE',
|
||||
13: 'TIME', 14: 'CHAR', 16: 'BIGINT', 27: 'DOUBLE',
|
||||
35: 'TIMESTAMP', 37: 'VARCHAR', 261: 'BLOB',
|
||||
}
|
||||
col_names = [c[0] for c in cols]
|
||||
print("\nSloupce:")
|
||||
for c in cols:
|
||||
name, ftype, flen = c
|
||||
type_str = type_map.get(ftype, f'type#{ftype}')
|
||||
if flen and ftype in (14, 37):
|
||||
type_str += f'({flen})'
|
||||
print(f" {name:<30} {type_str}")
|
||||
|
||||
# Počet řádků
|
||||
cur.execute(f'SELECT COUNT(*) FROM {table}')
|
||||
pocet = cur.fetchone()[0]
|
||||
print(f"\nCelkem řádků: {pocet}")
|
||||
|
||||
if pocet == 0:
|
||||
return col_names
|
||||
|
||||
# Ukázka 5 nejnovějších (pokud má datum sloupec)
|
||||
date_cols = [c[0] for c in cols if c[1] in (12, 35)] # DATE nebo TIMESTAMP
|
||||
order = f'ORDER BY {date_cols[0]} DESC' if date_cols else ''
|
||||
|
||||
try:
|
||||
cur.execute(f'SELECT FIRST 5 * FROM {table} {order}')
|
||||
rows = cur.fetchall()
|
||||
print(f"\nUkázka (5 {'nejnovějších' if order else 'prvních'}):")
|
||||
for row in rows:
|
||||
print()
|
||||
for name, val in zip(col_names, row):
|
||||
s = serialize(val)
|
||||
if s != 'NULL':
|
||||
print(f" {name:<30} {s}")
|
||||
except Exception as e:
|
||||
print(f" Ukázka selhala: {e}")
|
||||
|
||||
return col_names
|
||||
|
||||
|
||||
# ── Prozkoumáme klíčové tabulky ───────────────────────────────────────────────
|
||||
for tbl in ['NES', 'NP', 'SOC_NEPRITOMNOST']:
|
||||
inspect_table(tbl)
|
||||
|
||||
# ── Speciálně: aktivní PN ─────────────────────────────────────────────────────
|
||||
print(f"\n\n{'=' * 70}")
|
||||
print(" POKUS: aktivní záznamy v NES (konec NULL nebo v budoucnosti)")
|
||||
print(f"{'=' * 70}")
|
||||
|
||||
dnes = datetime.date.today()
|
||||
|
||||
# Nejdřív zjistíme sloupce NES
|
||||
cur.execute("""
|
||||
SELECT TRIM(rf.rdb$field_name), f.rdb$field_type
|
||||
FROM rdb$relation_fields rf
|
||||
JOIN rdb$fields f ON rf.rdb$field_source = f.rdb$field_name
|
||||
WHERE TRIM(rf.rdb$relation_name) = 'NES'
|
||||
ORDER BY rf.rdb$field_position
|
||||
""")
|
||||
nes_cols = [(r[0], r[1]) for r in cur.fetchall()]
|
||||
nes_col_names = [c[0] for c in nes_cols]
|
||||
|
||||
print(f"\nVšechny sloupce NES: {', '.join(nes_col_names)}")
|
||||
|
||||
# Hledáme sloupce s datumem konce PN
|
||||
date_cols_nes = [c[0] for c in nes_cols if c[1] in (12, 35)]
|
||||
print(f"Datumové sloupce: {date_cols_nes}")
|
||||
|
||||
# Zkusíme různé kandidátní sloupce pro "datum do"
|
||||
candidates_do = [c for c in nes_col_names if 'DO' in c or 'KONEC' in c or 'END' in c.upper()]
|
||||
candidates_od = [c for c in nes_col_names if 'OD' in c or 'ZACATEK' in c or 'VZNIK' in c or 'START' in c.upper()]
|
||||
candidates_pac = [c for c in nes_col_names if 'PAC' in c or 'IDPAC' in c]
|
||||
print(f"Kandidáti DATUM_DO: {candidates_do}")
|
||||
print(f"Kandidáti DATUM_OD: {candidates_od}")
|
||||
print(f"Kandidáti IDPAC: {candidates_pac}")
|
||||
|
||||
# Ukázka posledních 10 záznamů NES seřazená dle prvního datumového sloupce
|
||||
if date_cols_nes:
|
||||
try:
|
||||
cur.execute(f'SELECT FIRST 10 * FROM NES ORDER BY {date_cols_nes[0]} DESC')
|
||||
rows = cur.fetchall()
|
||||
print(f"\nPosledních 10 záznamů NES (dle {date_cols_nes[0]} DESC):")
|
||||
for row in rows:
|
||||
print()
|
||||
for name, val in zip(nes_col_names, row):
|
||||
s = serialize(val)
|
||||
if s != 'NULL':
|
||||
print(f" {name:<30} {s}")
|
||||
except Exception as e:
|
||||
print(f"Selhalo: {e}")
|
||||
|
||||
# ── NP tabulka – stejný rozbor ────────────────────────────────────────────────
|
||||
print(f"\n\n{'=' * 70}")
|
||||
print(" POKUS: aktivní záznamy v NP")
|
||||
print(f"{'=' * 70}")
|
||||
|
||||
cur.execute("""
|
||||
SELECT TRIM(rf.rdb$field_name), f.rdb$field_type
|
||||
FROM rdb$relation_fields rf
|
||||
JOIN rdb$fields f ON rf.rdb$field_source = f.rdb$field_name
|
||||
WHERE TRIM(rf.rdb$relation_name) = 'NP'
|
||||
ORDER BY rf.rdb$field_position
|
||||
""")
|
||||
np_cols = [(r[0], r[1]) for r in cur.fetchall()]
|
||||
np_col_names = [c[0] for c in np_cols]
|
||||
print(f"\nVšechny sloupce NP: {', '.join(np_col_names)}")
|
||||
date_cols_np = [c[0] for c in np_cols if c[1] in (12, 35)]
|
||||
print(f"Datumové sloupce: {date_cols_np}")
|
||||
|
||||
if date_cols_np:
|
||||
try:
|
||||
cur.execute(f'SELECT FIRST 10 * FROM NP ORDER BY {date_cols_np[0]} DESC')
|
||||
rows = cur.fetchall()
|
||||
print(f"\nPosledních 10 záznamů NP (dle {date_cols_np[0]} DESC):")
|
||||
for row in rows:
|
||||
print()
|
||||
for name, val in zip(np_col_names, row):
|
||||
s = serialize(val)
|
||||
if s != 'NULL':
|
||||
print(f" {name:<30} {s}")
|
||||
except Exception as e:
|
||||
print(f"Selhalo: {e}")
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
print(f"\n{'=' * 70}")
|
||||
print("Hotovo.")
|
||||
@@ -0,0 +1,208 @@
|
||||
"""
|
||||
003_aktivni_PN_seznam.py
|
||||
========================
|
||||
Vypíše seznam pacientů s aktivní pracovní neschopností (PN) k dnešnímu datu.
|
||||
|
||||
SQL logika převzata přímo z Medicusu (report "Přehled práce neschopných pacientů"):
|
||||
- ZACNES <= dnes
|
||||
- (KONNES >= dnes) OR (KONNES IS NULL)
|
||||
- PRACNE = 'A'
|
||||
- STORNO <> 'T'
|
||||
|
||||
Spouštění: tlačítkem z Medicusu nebo přímo pythonu.
|
||||
"""
|
||||
import sys, os, traceback
|
||||
|
||||
_LOG = r"C:\Users\vlado\PycharmProjects\Medicus\PNWithClaude\003_error.log"
|
||||
|
||||
def _log_exception(exc_type, exc_value, exc_tb):
|
||||
with open(_LOG, "w", encoding="utf-8") as f:
|
||||
traceback.print_exception(exc_type, exc_value, exc_tb, file=f)
|
||||
sys.excepthook = _log_exception
|
||||
|
||||
import fdb
|
||||
import datetime
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
# ── Připojení ─────────────────────────────────────────────────────────────────
|
||||
DSN = r'localhost:c:\medicus 3\data\medicus.fdb'
|
||||
USER = 'SYSDBA'
|
||||
PASSWORD = 'masterkey'
|
||||
CHARSET = 'win1250'
|
||||
|
||||
# ── SQL ───────────────────────────────────────────────────────────────────────
|
||||
SQL = """
|
||||
SELECT
|
||||
kar.PRIJMENI,
|
||||
kar.JMENO,
|
||||
kar.RODCIS,
|
||||
nes.ZACNES,
|
||||
nes.KONNES,
|
||||
nes.DIAGNO,
|
||||
COALESCE(nes.ECN, nes.CISNES) AS CISNES
|
||||
FROM NES nes
|
||||
JOIN KAR kar ON kar.IDPAC = nes.IDPAC
|
||||
WHERE
|
||||
nes.ZACNES <= ?
|
||||
AND (nes.KONNES >= ? OR nes.KONNES IS NULL)
|
||||
AND nes.PRACNE = 'A'
|
||||
AND nes.STORNO <> 'T'
|
||||
ORDER BY kar.PRIJMENI, kar.JMENO
|
||||
"""
|
||||
|
||||
|
||||
def nacti_data():
|
||||
dnes = datetime.date.today()
|
||||
conn = fdb.connect(dsn=DSN, user=USER, password=PASSWORD, charset=CHARSET)
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute(SQL, (dnes, dnes))
|
||||
rows = cur.fetchall()
|
||||
return dnes, rows
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def delka_PN(zacnes, konnes, dnes):
|
||||
"""Počet dní PN (od začátku do dnes nebo do konce)."""
|
||||
do = konnes if konnes else dnes
|
||||
return (do - zacnes).days + 1
|
||||
|
||||
|
||||
def zobraz_okno(dnes, rows):
|
||||
root = tk.Tk()
|
||||
root.title(f"Aktivní pracovní neschopnost – {dnes.strftime('%d.%m.%Y')}")
|
||||
w, h = 900, 550
|
||||
root.update_idletasks()
|
||||
sw = root.winfo_screenwidth()
|
||||
sh = root.winfo_screenheight()
|
||||
x = (sw - w) // 2
|
||||
y = (sh - h) // 2
|
||||
root.geometry(f"{w}x{h}+{x}+{y}")
|
||||
|
||||
# ── Záhlaví ───────────────────────────────────────────────────────────────
|
||||
hlavicka = tk.Frame(root, bg="#2c5f8a", pady=8)
|
||||
hlavicka.pack(fill=tk.X)
|
||||
tk.Label(
|
||||
hlavicka,
|
||||
text=f"Pracovní neschopnost – aktivní k {dnes.strftime('%d.%m.%Y')} ({len(rows)} pacientů)",
|
||||
font=("Segoe UI", 13, "bold"),
|
||||
fg="white", bg="#2c5f8a"
|
||||
).pack()
|
||||
|
||||
# ── Tabulka ───────────────────────────────────────────────────────────────
|
||||
cols = ("Příjmení", "Jméno", "Rodné číslo", "Začátek", "Konec", "Dní", "Diagnóza", "Č. neschopenky")
|
||||
frame = tk.Frame(root)
|
||||
frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
scroll_y = tk.Scrollbar(frame, orient=tk.VERTICAL)
|
||||
scroll_y.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
scroll_x = tk.Scrollbar(frame, orient=tk.HORIZONTAL)
|
||||
scroll_x.pack(side=tk.BOTTOM, fill=tk.X)
|
||||
|
||||
tree = ttk.Treeview(
|
||||
frame,
|
||||
columns=cols,
|
||||
show="headings",
|
||||
yscrollcommand=scroll_y.set,
|
||||
xscrollcommand=scroll_x.set
|
||||
)
|
||||
scroll_y.config(command=tree.yview)
|
||||
scroll_x.config(command=tree.xview)
|
||||
|
||||
# Šířky sloupců
|
||||
widths = [120, 100, 110, 85, 85, 50, 80, 150]
|
||||
for col, w in zip(cols, widths):
|
||||
tree.column(col, width=w, anchor=tk.W)
|
||||
|
||||
# ── Řazení kliknutím na záhlaví ───────────────────────────────────────────
|
||||
_sort_state = {} # col -> ascending True/False
|
||||
|
||||
def sort_by(col):
|
||||
ascending = not _sort_state.get(col, False)
|
||||
_sort_state[col] = ascending
|
||||
# Index sloupce pro čtení hodnoty
|
||||
col_idx = cols.index(col)
|
||||
data = [(tree.set(k, col), k) for k in tree.get_children("")]
|
||||
# Číselné řazení pro "Dní"
|
||||
if col == "Dní":
|
||||
data.sort(key=lambda t: int(t[0]) if t[0].isdigit() else 0, reverse=not ascending)
|
||||
else:
|
||||
data.sort(key=lambda t: t[0].lower(), reverse=not ascending)
|
||||
for idx, (_, k) in enumerate(data):
|
||||
tree.move(k, "", idx)
|
||||
# Šipka v záhlaví
|
||||
for c in cols:
|
||||
arrow = (" ▲" if ascending else " ▼") if c == col else ""
|
||||
tree.heading(c, text=c + arrow, command=lambda c=c: sort_by(c))
|
||||
# Obarvi znovu střídavě
|
||||
for idx, (_, k) in enumerate(data):
|
||||
current_tags = list(tree.item(k, "tags"))
|
||||
dlouha = "dlouha" in current_tags
|
||||
if dlouha:
|
||||
tree.item(k, tags=("dlouha",))
|
||||
else:
|
||||
tree.item(k, tags=("even" if idx % 2 == 0 else "odd",))
|
||||
|
||||
for col in cols:
|
||||
tree.heading(col, text=col, command=lambda c=col: sort_by(c))
|
||||
|
||||
# Střídavé barvy řádků
|
||||
tree.tag_configure("even", background="#f0f4f8")
|
||||
tree.tag_configure("odd", background="white")
|
||||
tree.tag_configure("dlouha", foreground="#c0392b") # červeně > 90 dní
|
||||
|
||||
for i, row in enumerate(rows):
|
||||
prijmeni, jmeno, rodcis, zacnes, konnes, diagno, cisnes = row
|
||||
dni = delka_PN(zacnes, konnes, dnes)
|
||||
konec_str = konnes.strftime("%d.%m.%Y") if konnes else "trvá"
|
||||
diagno_str = (diagno or "").strip()
|
||||
cisnes_str = (cisnes or "").strip()
|
||||
|
||||
tag = "dlouha" if dni > 90 else ("even" if i % 2 == 0 else "odd")
|
||||
tree.insert("", tk.END, values=(
|
||||
prijmeni,
|
||||
jmeno,
|
||||
rodcis,
|
||||
zacnes.strftime("%d.%m.%Y"),
|
||||
konec_str,
|
||||
dni,
|
||||
diagno_str,
|
||||
cisnes_str,
|
||||
), tags=(tag,))
|
||||
|
||||
tree.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# ── Výchozí řazení: Dní vzestupně ─────────────────────────────────────────
|
||||
sort_by("Dní")
|
||||
|
||||
# ── Legenda + zavřít ──────────────────────────────────────────────────────
|
||||
spodek = tk.Frame(root, pady=6)
|
||||
spodek.pack(fill=tk.X)
|
||||
tk.Label(
|
||||
spodek,
|
||||
text="Červeně = PN delší než 90 dní",
|
||||
font=("Segoe UI", 9),
|
||||
fg="#c0392b"
|
||||
).pack(side=tk.LEFT, padx=15)
|
||||
tk.Button(
|
||||
spodek,
|
||||
text="Zavřít",
|
||||
command=root.destroy,
|
||||
width=12,
|
||||
font=("Segoe UI", 10)
|
||||
).pack(side=tk.RIGHT, padx=15)
|
||||
|
||||
root.mainloop()
|
||||
|
||||
|
||||
# ── Hlavní tok ────────────────────────────────────────────────────────────────
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
dnes, rows = nacti_data()
|
||||
zobraz_okno(dnes, rows)
|
||||
except Exception:
|
||||
with open(_LOG, "w", encoding="utf-8") as f:
|
||||
traceback.print_exc(file=f)
|
||||
raise
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user