187 lines
6.0 KiB
Python
187 lines
6.0 KiB
Python
# 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()
|