202 lines
7.7 KiB
Python
202 lines
7.7 KiB
Python
"""
|
||
Export zdravotních výkonů do XLSX — jeden list na odbornost.
|
||
|
||
Každý výkon se rozvine do tolika řádků, kolik má nositelů.
|
||
Pokud nositelé chybí, výkon dostane jeden prázdný řádek.
|
||
|
||
Požadavky:
|
||
pip install pymongo openpyxl
|
||
"""
|
||
|
||
from pathlib import Path
|
||
from datetime import datetime, timezone
|
||
|
||
from pymongo import MongoClient
|
||
import openpyxl
|
||
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
|
||
from openpyxl.utils import get_column_letter
|
||
|
||
# ── Nastavení ─────────────────────────────────────────────────────────────────
|
||
ODBORNOSTI = ["001", "002"] # [] = všechny odbornosti
|
||
VYSTUP = Path(__file__).parent / "vykony_report.xlsx"
|
||
# ──────────────────────────────────────────────────────────────────────────────
|
||
|
||
MONGO_URI = "mongodb://192.168.1.76:27017/"
|
||
MONGO_DB = "zdravotni_vykony"
|
||
|
||
SLOUPCE = [
|
||
("cislo_vykonu", "Číslo výkonu", 10),
|
||
("nazev", "Název", 45),
|
||
("kategorie", "Kategorie", 22),
|
||
("typ_formulare", "Typ formuláře", 14),
|
||
("doba_trvani", "Doba trvání", 10),
|
||
("omezeni_mistem", "Omezení místem", 22),
|
||
("omezeni_frekvenci", "Omezení frekvencí", 16),
|
||
("nepocitat_rezii", "Nepočítat režii", 12),
|
||
("body_prime", "Body přímé", 10),
|
||
("body_osobni", "Body osobní", 10),
|
||
("body_rezijni", "Body režijní", 10),
|
||
("body_celkem", "Body celkem", 10),
|
||
("postup", "Postup výkonu", 50),
|
||
("nositel_kategorie", "Nositel – kategorie", 14),
|
||
("nositel_funkce", "Nositel – funkce", 30),
|
||
("nositel_cas", "Nositel – čas", 10),
|
||
("nositel_body", "Nositel – body", 12),
|
||
]
|
||
|
||
BARVA_HLAVICKA = "1F4E79"
|
||
BARVA_VYKON_A = "DEEAF1" # sudý výkon
|
||
BARVA_VYKON_B = "FFFFFF" # lichý výkon
|
||
|
||
|
||
def _bool_text(v) -> str:
|
||
if v is True: return "ano"
|
||
if v is False: return "ne"
|
||
return ""
|
||
|
||
|
||
def _postup(d: dict) -> str:
|
||
casti = []
|
||
if d.get("cim_zacina"): casti.append(d["cim_zacina"].strip())
|
||
if d.get("obsah_rozsah"): casti.append(d["obsah_rozsah"].strip())
|
||
if d.get("cim_konci"): casti.append(d["cim_konci"].strip())
|
||
return "\n\n".join(casti)
|
||
|
||
|
||
def _nositele(d: dict) -> list[dict]:
|
||
"""Vrátí nositelé bez řádků Celkem a bez prázdných řádků."""
|
||
result = []
|
||
for n in d.get("nositele", []):
|
||
# přeskoč řádky kde kdekoliv je "Celkem:"
|
||
hodnoty = [str(v) for v in n.values()]
|
||
if any("celkem" in v.lower() for v in hodnoty):
|
||
continue
|
||
# přeskoč zcela prázdné řádky
|
||
if not any(v.strip() for v in hodnoty if v):
|
||
continue
|
||
result.append(n)
|
||
return result
|
||
|
||
|
||
def _vykon_radky(v: dict, d: dict) -> list[dict]:
|
||
"""Rozlož výkon do řádků podle nositelů. Min. 1 řádek."""
|
||
base = {
|
||
"cislo_vykonu": v.get("cislo_vykonu", ""),
|
||
"nazev": d.get("nazev") or v.get("nazev_vykonu", ""),
|
||
"kategorie": d.get("kategorie") or v.get("kategorie", ""),
|
||
"typ_formulare": d.get("typ_formulare", ""),
|
||
"doba_trvani": d.get("doba_trvani") if d.get("doba_trvani") is not None else v.get("doba_trvani", ""),
|
||
"omezeni_mistem": d.get("omezeni_mistem") or v.get("omezeni_mistem", ""),
|
||
"omezeni_frekvenci": d.get("omezeni_frekvenci") or v.get("omezeni_frekvenci", ""),
|
||
"nepocitat_rezii": _bool_text(d.get("nepocitat_rezii")),
|
||
"body_prime": d.get("body_prime") if d.get("body_prime") is not None else v.get("prime_naklady", ""),
|
||
"body_osobni": d.get("body_osobni") if d.get("body_osobni") is not None else v.get("osobni", ""),
|
||
"body_rezijni": d.get("body_rezijni") if d.get("body_rezijni") is not None else v.get("body_rezijni", ""),
|
||
"body_celkem": d.get("body_celkem") if d.get("body_celkem") is not None else v.get("body_celkem", ""),
|
||
"postup": _postup(d),
|
||
}
|
||
|
||
nositele = _nositele(d)
|
||
if not nositele:
|
||
return [{**base, "nositel_kategorie": "", "nositel_funkce": "", "nositel_cas": "", "nositel_body": ""}]
|
||
|
||
radky = []
|
||
for n in nositele:
|
||
radky.append({
|
||
**base,
|
||
"nositel_kategorie": n.get("Kategorie", ""),
|
||
"nositel_funkce": n.get("Funkce", ""),
|
||
"nositel_cas": n.get("Cas", ""),
|
||
"nositel_body": n.get("Bodyaktualni", ""),
|
||
})
|
||
return radky
|
||
|
||
|
||
def nastav_list(ws):
|
||
"""Záhlaví + šířky sloupců."""
|
||
hl_font = Font(bold=True, color="FFFFFF", size=10)
|
||
hl_fill = PatternFill("solid", fgColor=BARVA_HLAVICKA)
|
||
hl_align = Alignment(horizontal="center", vertical="center", wrap_text=True)
|
||
thin = Side(style="thin", color="AAAAAA")
|
||
border = Border(left=thin, right=thin, bottom=thin)
|
||
|
||
for col_idx, (_, label, sirka) in enumerate(SLOUPCE, 1):
|
||
cell = ws.cell(row=1, column=col_idx, value=label)
|
||
cell.font = hl_font
|
||
cell.fill = hl_fill
|
||
cell.alignment = hl_align
|
||
cell.border = border
|
||
ws.column_dimensions[get_column_letter(col_idx)].width = sirka
|
||
|
||
ws.row_dimensions[1].height = 30
|
||
ws.freeze_panes = "A2"
|
||
|
||
|
||
def zapis_radek(ws, row_idx: int, radek: dict, barva: str):
|
||
fill = PatternFill("solid", fgColor=barva)
|
||
thin = Side(style="thin", color="DDDDDD")
|
||
border = Border(left=thin, right=thin, bottom=thin)
|
||
align_def = Alignment(vertical="top", wrap_text=False)
|
||
align_wrap= Alignment(vertical="top", wrap_text=True)
|
||
|
||
for col_idx, (klic, _, _) in enumerate(SLOUPCE, 1):
|
||
hodnota = radek.get(klic, "")
|
||
cell = ws.cell(row=row_idx, column=col_idx, value=hodnota)
|
||
cell.fill = fill
|
||
cell.border = border
|
||
cell.alignment = align_wrap if klic in ("nazev", "postup", "nositel_funkce") else align_def
|
||
|
||
|
||
def main():
|
||
print("Připojuji k MongoDB...")
|
||
client = MongoClient(MONGO_URI)
|
||
col_vykony = client[MONGO_DB]["vykony"]
|
||
col_detaily = client[MONGO_DB]["detaily"]
|
||
|
||
# Zjisti odbornosti
|
||
if ODBORNOSTI:
|
||
odbornosti = ODBORNOSTI
|
||
else:
|
||
odbornosti = sorted(col_vykony.distinct("odbornost", {"_aktivni": True}))
|
||
print(f"Odbornosti: {odbornosti}")
|
||
|
||
wb = openpyxl.Workbook()
|
||
wb.remove(wb.active) # odstraň defaultní prázdný list
|
||
|
||
for odbornost in odbornosti:
|
||
print(f" Zpracovávám odbornost {odbornost}...")
|
||
|
||
vykony = list(col_vykony.find(
|
||
{"odbornost": odbornost, "_aktivni": True},
|
||
{"_id": 0},
|
||
).sort("cislo_vykonu", 1))
|
||
|
||
cisla = [v["cislo_vykonu"] for v in vykony]
|
||
detaily_map = {
|
||
d["cislo_vykonu"]: d
|
||
for d in col_detaily.find({"cislo_vykonu": {"$in": cisla}}, {"_id": 0})
|
||
}
|
||
|
||
ws = wb.create_sheet(title=f"Odbornost {odbornost}")
|
||
nastav_list(ws)
|
||
|
||
row_idx = 2
|
||
for vykon_idx, v in enumerate(vykony):
|
||
d = detaily_map.get(v["cislo_vykonu"], {})
|
||
radky = _vykon_radky(v, d)
|
||
barva = BARVA_VYKON_A if vykon_idx % 2 == 0 else BARVA_VYKON_B
|
||
for radek in radky:
|
||
zapis_radek(ws, row_idx, radek, barva)
|
||
row_idx += 1
|
||
|
||
print(f" → {row_idx - 2} řádků, {len(vykony)} výkonů")
|
||
|
||
client.close()
|
||
|
||
wb.save(VYSTUP)
|
||
print(f"\nUloženo: {VYSTUP}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|