z230
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user