# medicus_db.py — aktuální k 2026-05-26 # Připojení k Firebird databázi Medicus (medicus.fdb). Volí DSN podle názvu počítače. # Obsahuje třídu MedicusDB s metodami pro dotazy na pacienty, registrace a faktury. import os import socket import fdb def get_medicus_connection(): """ Připojí se k Firebird medicus.fdb. DSN se vybere takto: 1) env MEDICUS_FDB_DSN (má přednost — nutné v dockeru, kde hostname = ID kontejneru), 2) podle názvu počítače (dsn_map), 3) default. Vrátí fdb.Connection nebo vyhodí RuntimeError. """ computer_name = socket.gethostname().upper() dsn_map = { "LEKAR": r"localhost:M:\medicus\data\medicus.fdb", "SESTRA": r"192.168.1.10:m:\medicus\data\medicus.fdb", "LENOVO": r"192.168.1.10:m:\medicus\data\medicus.fdb", "NTBVBHP470G10": r"192.168.1.76:/firebird/data/medicus.fdb", # přepnuto z reporteru na tower 2026-06-14 "Z230": r"192.168.1.76:/firebird/data/medicus.fdb", # přepnuto z reporteru na tower 2026-06-14 "TOWER": r"192.168.1.76:/firebird/data/medicus.fdb", # Firebird 2.5 docker kontejner na toweru } dsn = (os.environ.get("MEDICUS_FDB_DSN") or dsn_map.get(computer_name, r"localhost:c:\medicus 3\data\medicus.fdb")) import sys print(f"[medicus_db] Pripojuji se jako {computer_name} -> {dsn}", file=sys.stderr, flush=True) return fdb.connect(dsn=dsn, user="SYSDBA", password="masterkey", charset="win1250") class ReconnectingConnection: """Obal nad fdb.Connection, který se sám znovu připojí, když spojení umře. Dlouho běžící procesy (např. MCP server) drží jedno spojení od startu. To se rozpadne, když: * se notebook uspí/hibernuje (TCP socket odumře), nebo * na serveru proběhne denní gbak restore (Firebird zabije stará spojení). Výsledkem je `SQLCODE -902 Error writing data to the connection`. Tato třída ověří před každým použitím, že spojení žije, a když ne, tiše ho znovu naváže. Volající kód používá .cursor()/.commit() beze změny. """ def __init__(self, connect_fn=get_medicus_connection): self._connect = connect_fn self._conn = None def _alive(self): if self._conn is None: return False try: cur = self._conn.cursor() cur.execute("SELECT 1 FROM RDB$DATABASE") cur.fetchone() return True except Exception: return False def _ensure(self): if not self._alive(): if self._conn is not None: try: self._conn.close() except Exception: pass self._conn = None self._conn = self._connect() return self._conn def cursor(self): return self._ensure().cursor() def commit(self): return self._ensure().commit() def rollback(self): if self._conn is not None: return self._conn.rollback() def close(self): if self._conn is not None: try: self._conn.close() finally: self._conn = None def __getattr__(self, name): # ostatní atributy/metody proxy na živé spojení return getattr(self._ensure(), name) def get_medicus_connection_reconnecting(): """Vrátí spojení s automatickým reconnectem (vhodné pro dlouho běžící procesy).""" return ReconnectingConnection(get_medicus_connection) def get_medicus_db(): """Vrátí MedicusDB instanci s připojením podle názvu počítače.""" conn = get_medicus_connection() instance = object.__new__(MedicusDB) instance.conn = conn instance.cur = conn.cursor() return instance class MedicusDB: def __init__(self, host, db_path, user="SYSDBA", password="masterkey", charset="WIN1250"): self.conn = fdb.connect( host=host, database=db_path, user=user, password=password, charset=charset ) self.cur = self.conn.cursor() def get_fak_kapitace(self, as_dict=False): sql = """ SELECT fak.id, fak.cisfak, fak.poj, fak.kapdetail FROM fak WHERE fak.kapdetail IS NOT NULL AND fak.kapdetail <> '' """ if as_dict: return self.query_dict(sql) return self.query(sql) def query(self, sql, params=None): self.cur.execute(sql, params or ()) return self.cur.fetchall() def query_dict(self, sql, params=None): self.cur.execute(sql, params or ()) cols = [d[0].strip().lower() for d in self.cur.description] return [dict(zip(cols, row)) for row in self.cur.fetchall()] def get_active_registered_patients(self, as_dict=False): sql = """ SELECT kar.rodcis, kar.prijmeni, kar.jmeno, kar.poj FROM kar WHERE kar.vyrazen = 'N' AND kar.rodcis IS NOT NULL AND kar.rodcis <> '' AND EXISTS ( SELECT r.id FROM registr r JOIN icp i ON r.idicp = i.idicp WHERE r.idpac = kar.idpac AND r.datum <= CURRENT_DATE AND (r.datum_zruseni IS NULL OR r.datum_zruseni >= CURRENT_DATE) AND r.priznak IN ('V','D','A') AND i.icp = '09305001' AND i.odb = '001' ) ORDER BY kar.prijmeni, kar.rodcis """ if as_dict: return self.query_dict(sql) return self.query(sql) def get_all_patients(self, as_dict=False): sql = """ SELECT kar.rodcis, kar.prijmeni, kar.jmeno, kar.poj FROM kar """ if as_dict: return self.query_dict(sql) return self.query(sql) def close(self): self.conn.close()