M8 CRM Liječnički pregledi: lista + isteci + ZZJZ PGŽ scheduling
- /api/crm/lijecnicki[CRUD] s filterima (klub/clan/status/placeno) + summary
- /api/crm/lijecnicki/uskoro-isticu — istekli + ≤30 dana (parametri days, include_expired)
- /api/crm/lijecnicki/{id} — detalji s status_calc + dana_do_isteka
- /api/crm/lijecnicki/{id}/zakazi — mock booking (upisuje termin u napomenu)
- /api/crm/zzjz/info — kontakt podaci ZZJZ PGŽ
- /api/crm/zzjz/termini — mock dostupnih termina za sportsku medicinu
(deterministička dostupnost; realni scraper TODO)
This commit is contained in:
@@ -0,0 +1,445 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# ═══════════════════════════════════════════════════════════════════
|
||||||
|
# Fajl: routers/lijecnicki_router.py | v1.0.0 | 04.05.2026
|
||||||
|
# Autor: Damir Radulić <dradulic@outlook.com> / damir@rinet.one
|
||||||
|
# Lokacija: /opt/pgz-sport/routers/lijecnicki_router.py
|
||||||
|
# Svrha: M8 — CRM Liječnički pregledi + ZZJZ PGŽ scheduling integracija
|
||||||
|
# ═══════════════════════════════════════════════════════════════════
|
||||||
|
"""M8 Liječnički router.
|
||||||
|
|
||||||
|
Endpointi (montirani na /api/crm):
|
||||||
|
GET /lijecnicki → lista (filteri)
|
||||||
|
POST /lijecnicki → novi pregled
|
||||||
|
GET /lijecnicki/{id} → detalji
|
||||||
|
PUT /lijecnicki/{id} → update
|
||||||
|
DELETE /lijecnicki/{id} → brisanje
|
||||||
|
GET /lijecnicki/uskoro-isticu → istekao + idućih 30 dana
|
||||||
|
POST /lijecnicki/{id}/zakazi → zakaži termin (ZZJZ PGŽ mock)
|
||||||
|
GET /zzjz/termini → dostupni termini ZZJZ PGŽ (mock + scrape stub)
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from datetime import date, datetime, timedelta
|
||||||
|
from decimal import Decimal
|
||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
|
import psycopg2
|
||||||
|
from psycopg2.extras import RealDictCursor
|
||||||
|
from fastapi import APIRouter, HTTPException, Query
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/api/crm", tags=["crm-lijecnicki"])
|
||||||
|
|
||||||
|
DSN = "host=10.10.0.2 port=6432 dbname=rinet_v3 user=rinet password=R1net2026!SecureDB#v7"
|
||||||
|
|
||||||
|
ZZJZ_BASE = "https://zzjzpgz.hr"
|
||||||
|
ZZJZ_INFO = {
|
||||||
|
"naziv": "Nastavni zavod za javno zdravstvo PGŽ",
|
||||||
|
"adresa": "Krešimirova 52a, 51000 Rijeka",
|
||||||
|
"telefon": "+385 51 358 770",
|
||||||
|
"email": "info@zzjzpgz.hr",
|
||||||
|
"web": ZZJZ_BASE,
|
||||||
|
"url_sportska_medicina": f"{ZZJZ_BASE}/djelatnosti/sportska-medicina/",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _conn():
|
||||||
|
return psycopg2.connect(DSN, cursor_factory=RealDictCursor)
|
||||||
|
|
||||||
|
|
||||||
|
def _conv(v):
|
||||||
|
if isinstance(v, (date, datetime)):
|
||||||
|
return v.isoformat()
|
||||||
|
if isinstance(v, Decimal):
|
||||||
|
return float(v)
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
def _row(d):
|
||||||
|
return {k: _conv(v) for k, v in dict(d).items()}
|
||||||
|
|
||||||
|
|
||||||
|
# ───────────── modeli ─────────────
|
||||||
|
|
||||||
|
class LijecnickiIn(BaseModel):
|
||||||
|
clan_id: int
|
||||||
|
klub_id: Optional[int] = None
|
||||||
|
datum_pregleda: date
|
||||||
|
vrijedi_do: Optional[date] = None
|
||||||
|
vrsta_pregleda: Optional[str] = "temeljni"
|
||||||
|
ustanova: Optional[str] = "ZZJZ PGŽ"
|
||||||
|
lijecnik: Optional[str] = None
|
||||||
|
spreman_za_natjecanje: Optional[bool] = True
|
||||||
|
ekg: Optional[bool] = False
|
||||||
|
krv: Optional[bool] = False
|
||||||
|
spirometrija: Optional[bool] = False
|
||||||
|
nalaz: Optional[str] = None
|
||||||
|
komentar_lijecnika: Optional[str] = None
|
||||||
|
preporuke: Optional[str] = None
|
||||||
|
iznos: Optional[float] = 0
|
||||||
|
iznos_zzjz: Optional[float] = 0
|
||||||
|
iznos_klub: Optional[float] = 0
|
||||||
|
iznos_clan: Optional[float] = 0
|
||||||
|
datum_placanja: Optional[date] = None
|
||||||
|
placeno: Optional[bool] = False
|
||||||
|
racun_broj: Optional[str] = None
|
||||||
|
nacin_placanja: Optional[str] = None
|
||||||
|
napomena: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class LijecnickiPatch(BaseModel):
|
||||||
|
klub_id: Optional[int] = None
|
||||||
|
datum_pregleda: Optional[date] = None
|
||||||
|
vrijedi_do: Optional[date] = None
|
||||||
|
vrsta_pregleda: Optional[str] = None
|
||||||
|
ustanova: Optional[str] = None
|
||||||
|
lijecnik: Optional[str] = None
|
||||||
|
spreman_za_natjecanje: Optional[bool] = None
|
||||||
|
ekg: Optional[bool] = None
|
||||||
|
krv: Optional[bool] = None
|
||||||
|
spirometrija: Optional[bool] = None
|
||||||
|
nalaz: Optional[str] = None
|
||||||
|
komentar_lijecnika: Optional[str] = None
|
||||||
|
preporuke: Optional[str] = None
|
||||||
|
iznos: Optional[float] = None
|
||||||
|
iznos_zzjz: Optional[float] = None
|
||||||
|
iznos_klub: Optional[float] = None
|
||||||
|
iznos_clan: Optional[float] = None
|
||||||
|
datum_placanja: Optional[date] = None
|
||||||
|
placeno: Optional[bool] = None
|
||||||
|
racun_broj: Optional[str] = None
|
||||||
|
nacin_placanja: Optional[str] = None
|
||||||
|
napomena: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ZakaziIn(BaseModel):
|
||||||
|
datum: date
|
||||||
|
vrijeme: Optional[str] = "09:00"
|
||||||
|
ustanova: Optional[str] = "ZZJZ PGŽ"
|
||||||
|
napomena: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
# ───────────── lista ─────────────
|
||||||
|
|
||||||
|
@router.get("/lijecnicki")
|
||||||
|
def list_lijecnicki(
|
||||||
|
klub_id: Optional[int] = Query(None),
|
||||||
|
clan_id: Optional[int] = Query(None),
|
||||||
|
status: Optional[str] = Query(None,
|
||||||
|
description="vazeci|uskoro|istekao"),
|
||||||
|
placeno: Optional[bool] = Query(None),
|
||||||
|
sort: str = Query("vrijedi_do"),
|
||||||
|
order: str = Query("asc"),
|
||||||
|
limit: int = Query(500, le=2000),
|
||||||
|
):
|
||||||
|
where, params = [], []
|
||||||
|
if klub_id:
|
||||||
|
where.append("l.klub_id = %s"); params.append(klub_id)
|
||||||
|
if clan_id:
|
||||||
|
where.append("l.clan_id = %s"); params.append(clan_id)
|
||||||
|
if placeno is not None:
|
||||||
|
where.append("l.placeno = %s"); params.append(placeno)
|
||||||
|
# status_calc: vazeci = >30d, uskoro = 0..30d, istekao = <0
|
||||||
|
if status == "vazeci":
|
||||||
|
where.append("l.vrijedi_do > (CURRENT_DATE + INTERVAL '30 days')")
|
||||||
|
elif status == "uskoro":
|
||||||
|
where.append("l.vrijedi_do BETWEEN CURRENT_DATE AND (CURRENT_DATE + INTERVAL '30 days')")
|
||||||
|
elif status == "istekao":
|
||||||
|
where.append("l.vrijedi_do < CURRENT_DATE")
|
||||||
|
|
||||||
|
sort_map = {
|
||||||
|
"vrijedi_do": "l.vrijedi_do",
|
||||||
|
"datum_pregleda": "l.datum_pregleda",
|
||||||
|
"klub": "k.naziv",
|
||||||
|
"clan": "cl.prezime",
|
||||||
|
"iznos": "l.iznos",
|
||||||
|
}
|
||||||
|
sort_col = sort_map.get(sort, "l.vrijedi_do")
|
||||||
|
order_sql = "DESC" if order.lower() == "desc" else "ASC"
|
||||||
|
where_sql = ("WHERE " + " AND ".join(where)) if where else ""
|
||||||
|
params.append(limit)
|
||||||
|
|
||||||
|
sql = f"""
|
||||||
|
SELECT l.*,
|
||||||
|
cl.ime || ' ' || cl.prezime AS clan,
|
||||||
|
cl.oib AS clan_oib, cl.email AS clan_email,
|
||||||
|
k.naziv AS klub, k.oib AS klub_oib,
|
||||||
|
CASE
|
||||||
|
WHEN l.vrijedi_do IS NULL THEN 'nepoznato'
|
||||||
|
WHEN l.vrijedi_do < CURRENT_DATE THEN 'istekao'
|
||||||
|
WHEN l.vrijedi_do <= (CURRENT_DATE + INTERVAL '30 days') THEN 'uskoro'
|
||||||
|
ELSE 'vazeci'
|
||||||
|
END AS status_calc,
|
||||||
|
(l.vrijedi_do - CURRENT_DATE)::int AS dana_do_isteka
|
||||||
|
FROM pgz_sport.lijecnicki_pregledi l
|
||||||
|
LEFT JOIN pgz_sport.clanovi cl ON cl.id = l.clan_id
|
||||||
|
LEFT JOIN pgz_sport.klubovi k ON k.id = l.klub_id
|
||||||
|
{where_sql}
|
||||||
|
ORDER BY {sort_col} {order_sql} NULLS LAST
|
||||||
|
LIMIT %s
|
||||||
|
"""
|
||||||
|
|
||||||
|
sum_sql = f"""
|
||||||
|
SELECT COUNT(*) AS total,
|
||||||
|
COUNT(*) FILTER (WHERE l.vrijedi_do < CURRENT_DATE) AS istekli,
|
||||||
|
COUNT(*) FILTER (WHERE l.vrijedi_do BETWEEN CURRENT_DATE AND CURRENT_DATE + INTERVAL '30 days') AS uskoro,
|
||||||
|
COUNT(*) FILTER (WHERE l.vrijedi_do > CURRENT_DATE + INTERVAL '30 days') AS vazeci,
|
||||||
|
COUNT(*) FILTER (WHERE l.placeno IS TRUE) AS placeni,
|
||||||
|
COALESCE(SUM(l.iznos), 0)::numeric(10,2) AS total_iznos
|
||||||
|
FROM pgz_sport.lijecnicki_pregledi l
|
||||||
|
LEFT JOIN pgz_sport.klubovi k ON k.id = l.klub_id
|
||||||
|
{where_sql}
|
||||||
|
"""
|
||||||
|
|
||||||
|
with _conn() as conn, conn.cursor() as cur:
|
||||||
|
cur.execute(sql, params)
|
||||||
|
rows = [_row(r) for r in cur.fetchall()]
|
||||||
|
cur.execute(sum_sql, params[:-1])
|
||||||
|
summary = _row(cur.fetchone() or {})
|
||||||
|
|
||||||
|
return {"count": len(rows), "rows": rows, "summary": summary}
|
||||||
|
|
||||||
|
|
||||||
|
# ───────────── uskoro isticu (30 dana + istekli) ─────────────
|
||||||
|
|
||||||
|
@router.get("/lijecnicki/uskoro-isticu")
|
||||||
|
def list_uskoro_isticu(
|
||||||
|
klub_id: Optional[int] = Query(None),
|
||||||
|
days: int = Query(30, ge=1, le=180),
|
||||||
|
include_expired: bool = Query(True),
|
||||||
|
):
|
||||||
|
where = ["l.vrijedi_do IS NOT NULL"]
|
||||||
|
params: list = []
|
||||||
|
if include_expired:
|
||||||
|
where.append("l.vrijedi_do <= (CURRENT_DATE + (%s || ' days')::interval)")
|
||||||
|
else:
|
||||||
|
where.append("l.vrijedi_do BETWEEN CURRENT_DATE AND (CURRENT_DATE + (%s || ' days')::interval)")
|
||||||
|
params.append(str(days))
|
||||||
|
if klub_id:
|
||||||
|
where.append("l.klub_id = %s"); params.append(klub_id)
|
||||||
|
|
||||||
|
sql = f"""
|
||||||
|
SELECT l.id, l.clan_id, l.klub_id, l.datum_pregleda, l.vrijedi_do,
|
||||||
|
l.vrsta_pregleda, l.ustanova, l.lijecnik, l.placeno,
|
||||||
|
cl.ime || ' ' || cl.prezime AS clan,
|
||||||
|
cl.email AS clan_email, cl.telefon AS clan_telefon,
|
||||||
|
k.naziv AS klub, k.oib AS klub_oib,
|
||||||
|
(l.vrijedi_do - CURRENT_DATE)::int AS dana_do_isteka,
|
||||||
|
CASE
|
||||||
|
WHEN l.vrijedi_do < CURRENT_DATE THEN 'istekao'
|
||||||
|
ELSE 'uskoro'
|
||||||
|
END AS status_calc
|
||||||
|
FROM pgz_sport.lijecnicki_pregledi l
|
||||||
|
LEFT JOIN pgz_sport.clanovi cl ON cl.id = l.clan_id
|
||||||
|
LEFT JOIN pgz_sport.klubovi k ON k.id = l.klub_id
|
||||||
|
WHERE {' AND '.join(where)}
|
||||||
|
ORDER BY l.vrijedi_do ASC
|
||||||
|
"""
|
||||||
|
|
||||||
|
with _conn() as conn, conn.cursor() as cur:
|
||||||
|
cur.execute(sql, params)
|
||||||
|
rows = [_row(r) for r in cur.fetchall()]
|
||||||
|
n_istekli = sum(1 for r in rows if (r.get("dana_do_isteka") or 0) < 0)
|
||||||
|
n_uskoro = len(rows) - n_istekli
|
||||||
|
return {"count": len(rows), "istekli": n_istekli, "uskoro": n_uskoro,
|
||||||
|
"days_window": days, "rows": rows}
|
||||||
|
|
||||||
|
|
||||||
|
# ───────────── detalji ─────────────
|
||||||
|
|
||||||
|
@router.get("/lijecnicki/{lid}")
|
||||||
|
def get_lijecnicki(lid: int):
|
||||||
|
with _conn() as conn, conn.cursor() as cur:
|
||||||
|
cur.execute("""
|
||||||
|
SELECT l.*,
|
||||||
|
cl.ime || ' ' || cl.prezime AS clan,
|
||||||
|
cl.oib AS clan_oib, cl.email AS clan_email,
|
||||||
|
cl.telefon AS clan_telefon,
|
||||||
|
k.naziv AS klub, k.oib AS klub_oib,
|
||||||
|
CASE
|
||||||
|
WHEN l.vrijedi_do IS NULL THEN 'nepoznato'
|
||||||
|
WHEN l.vrijedi_do < CURRENT_DATE THEN 'istekao'
|
||||||
|
WHEN l.vrijedi_do <= (CURRENT_DATE + INTERVAL '30 days') THEN 'uskoro'
|
||||||
|
ELSE 'vazeci'
|
||||||
|
END AS status_calc,
|
||||||
|
(l.vrijedi_do - CURRENT_DATE)::int AS dana_do_isteka
|
||||||
|
FROM pgz_sport.lijecnicki_pregledi l
|
||||||
|
LEFT JOIN pgz_sport.clanovi cl ON cl.id = l.clan_id
|
||||||
|
LEFT JOIN pgz_sport.klubovi k ON k.id = l.klub_id
|
||||||
|
WHERE l.id = %s
|
||||||
|
""", (lid,))
|
||||||
|
r = cur.fetchone()
|
||||||
|
if not r:
|
||||||
|
raise HTTPException(404, "Liječnički pregled ne postoji")
|
||||||
|
return _row(r)
|
||||||
|
|
||||||
|
|
||||||
|
# ───────────── kreiraj ─────────────
|
||||||
|
|
||||||
|
@router.post("/lijecnicki")
|
||||||
|
def create_lijecnicki(body: LijecnickiIn):
|
||||||
|
klub_id = body.klub_id
|
||||||
|
with _conn() as conn, conn.cursor() as cur:
|
||||||
|
if not klub_id:
|
||||||
|
cur.execute("SELECT klub_id FROM pgz_sport.clanovi WHERE id=%s", (body.clan_id,))
|
||||||
|
r = cur.fetchone()
|
||||||
|
klub_id = r["klub_id"] if r else None
|
||||||
|
# default vrijedi_do = +1 godina ako nije postavljeno
|
||||||
|
vrijedi_do = body.vrijedi_do
|
||||||
|
if vrijedi_do is None:
|
||||||
|
vrijedi_do = body.datum_pregleda + timedelta(days=365)
|
||||||
|
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO pgz_sport.lijecnicki_pregledi
|
||||||
|
(clan_id, klub_id, datum_pregleda, vrijedi_do, vrsta_pregleda,
|
||||||
|
ustanova, lijecnik, spreman_za_natjecanje, ekg, krv, spirometrija,
|
||||||
|
nalaz, komentar_lijecnika, preporuke, iznos, iznos_zzjz,
|
||||||
|
iznos_klub, iznos_clan, datum_placanja, placeno, racun_broj,
|
||||||
|
nacin_placanja, napomena)
|
||||||
|
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
|
||||||
|
RETURNING *
|
||||||
|
""", (body.clan_id, klub_id, body.datum_pregleda, vrijedi_do,
|
||||||
|
body.vrsta_pregleda, body.ustanova, body.lijecnik,
|
||||||
|
body.spreman_za_natjecanje, body.ekg, body.krv, body.spirometrija,
|
||||||
|
body.nalaz, body.komentar_lijecnika, body.preporuke,
|
||||||
|
body.iznos, body.iznos_zzjz, body.iznos_klub, body.iznos_clan,
|
||||||
|
body.datum_placanja, body.placeno, body.racun_broj,
|
||||||
|
body.nacin_placanja, body.napomena))
|
||||||
|
r = cur.fetchone()
|
||||||
|
conn.commit()
|
||||||
|
return _row(r)
|
||||||
|
|
||||||
|
|
||||||
|
# ───────────── update / delete ─────────────
|
||||||
|
|
||||||
|
@router.put("/lijecnicki/{lid}")
|
||||||
|
def update_lijecnicki(lid: int, patch: LijecnickiPatch):
|
||||||
|
fields, params = [], []
|
||||||
|
for f in ("klub_id", "datum_pregleda", "vrijedi_do", "vrsta_pregleda",
|
||||||
|
"ustanova", "lijecnik", "spreman_za_natjecanje",
|
||||||
|
"ekg", "krv", "spirometrija", "nalaz", "komentar_lijecnika",
|
||||||
|
"preporuke", "iznos", "iznos_zzjz", "iznos_klub", "iznos_clan",
|
||||||
|
"datum_placanja", "placeno", "racun_broj", "nacin_placanja",
|
||||||
|
"napomena"):
|
||||||
|
v = getattr(patch, f)
|
||||||
|
if v is not None:
|
||||||
|
fields.append(f"{f} = %s"); params.append(v)
|
||||||
|
if not fields:
|
||||||
|
raise HTTPException(400, "Nema polja za izmjenu")
|
||||||
|
fields.append("updated_at = now()")
|
||||||
|
params.append(lid)
|
||||||
|
with _conn() as conn, conn.cursor() as cur:
|
||||||
|
cur.execute(f"UPDATE pgz_sport.lijecnicki_pregledi SET {', '.join(fields)} WHERE id=%s RETURNING *",
|
||||||
|
params)
|
||||||
|
r = cur.fetchone()
|
||||||
|
if not r:
|
||||||
|
raise HTTPException(404, "Liječnički pregled ne postoji")
|
||||||
|
conn.commit()
|
||||||
|
return _row(r)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/lijecnicki/{lid}")
|
||||||
|
def delete_lijecnicki(lid: int):
|
||||||
|
with _conn() as conn, conn.cursor() as cur:
|
||||||
|
cur.execute("DELETE FROM pgz_sport.lijecnicki_pregledi WHERE id=%s RETURNING id", (lid,))
|
||||||
|
r = cur.fetchone()
|
||||||
|
conn.commit()
|
||||||
|
if not r:
|
||||||
|
raise HTTPException(404, "Liječnički pregled ne postoji")
|
||||||
|
return {"ok": True, "id": lid, "deleted": True}
|
||||||
|
|
||||||
|
|
||||||
|
# ───────────── ZZJZ PGŽ scheduling ─────────────
|
||||||
|
|
||||||
|
def _mock_zzjz_termini(week_start: date) -> list[dict]:
|
||||||
|
"""
|
||||||
|
Mock dostupnih termina za sportsku medicinu.
|
||||||
|
TODO: zamijeniti realnim scrapeom iz https://zzjzpgz.hr/djelatnosti/sportska-medicina/
|
||||||
|
Format termina: po danu (pon-pet), 09:00-15:00 svakih 30 min.
|
||||||
|
"""
|
||||||
|
out = []
|
||||||
|
times = ["08:00", "08:30", "09:00", "09:30", "10:00", "10:30",
|
||||||
|
"11:00", "11:30", "12:30", "13:00", "13:30", "14:00", "14:30"]
|
||||||
|
for d in range(5):
|
||||||
|
day = week_start + timedelta(days=d)
|
||||||
|
if day.weekday() >= 5:
|
||||||
|
continue
|
||||||
|
for t in times:
|
||||||
|
# pseudo-availability deterministic by day*hour
|
||||||
|
h = int(t.split(":")[0])
|
||||||
|
available = ((day.day + h) % 3) != 0
|
||||||
|
out.append({
|
||||||
|
"datum": day.isoformat(),
|
||||||
|
"vrijeme": t,
|
||||||
|
"doktor": "Dr. Sportska medicina",
|
||||||
|
"ustanova": "ZZJZ PGŽ",
|
||||||
|
"available": available,
|
||||||
|
"iznos_eur": 25.00,
|
||||||
|
})
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/zzjz/info")
|
||||||
|
def zzjz_info():
|
||||||
|
return ZZJZ_INFO
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/zzjz/termini")
|
||||||
|
def zzjz_termini(
|
||||||
|
od: Optional[date] = Query(None,
|
||||||
|
description="Početak tjedna; default = ovaj tjedan"),
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Vraća dostupne termine za sportsku medicinu pri ZZJZ PGŽ.
|
||||||
|
Trenutno: mock (deterministička dostupnost). Stvarna integracija
|
||||||
|
čeka API ili scraping form-e na zzjzpgz.hr.
|
||||||
|
"""
|
||||||
|
if od is None:
|
||||||
|
today = date.today()
|
||||||
|
od = today - timedelta(days=today.weekday())
|
||||||
|
termini = _mock_zzjz_termini(od)
|
||||||
|
return {
|
||||||
|
"ustanova": ZZJZ_INFO,
|
||||||
|
"week_start": od.isoformat(),
|
||||||
|
"count": len(termini),
|
||||||
|
"available": sum(1 for t in termini if t["available"]),
|
||||||
|
"termini": termini,
|
||||||
|
"note": "Mock podaci. Realni termini čekaju ZZJZ PGŽ API ili authorizirani scraper.",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/lijecnicki/{lid}/zakazi")
|
||||||
|
def zakazi_termin(lid: int, body: ZakaziIn):
|
||||||
|
"""
|
||||||
|
Stvara zakazani termin (mock) za pregled koji još nije obavljen.
|
||||||
|
Realna integracija: POST na ZZJZ PGŽ booking endpoint kad bude dostupan.
|
||||||
|
"""
|
||||||
|
with _conn() as conn, conn.cursor() as cur:
|
||||||
|
cur.execute("SELECT id, clan_id, ustanova FROM pgz_sport.lijecnicki_pregledi WHERE id=%s", (lid,))
|
||||||
|
r = cur.fetchone()
|
||||||
|
if not r:
|
||||||
|
raise HTTPException(404, "Liječnički pregled ne postoji")
|
||||||
|
new_napomena = (
|
||||||
|
f"Termin zakazan: {body.datum.isoformat()} {body.vrijeme} @ "
|
||||||
|
f"{body.ustanova}. {body.napomena or ''}"
|
||||||
|
).strip()
|
||||||
|
cur.execute("""
|
||||||
|
UPDATE pgz_sport.lijecnicki_pregledi
|
||||||
|
SET ustanova = COALESCE(%s, ustanova),
|
||||||
|
napomena = %s,
|
||||||
|
updated_at = now()
|
||||||
|
WHERE id = %s
|
||||||
|
RETURNING *
|
||||||
|
""", (body.ustanova, new_napomena, lid))
|
||||||
|
upd = cur.fetchone()
|
||||||
|
conn.commit()
|
||||||
|
return {
|
||||||
|
"ok": True,
|
||||||
|
"id": lid,
|
||||||
|
"zakazano_za": f"{body.datum.isoformat()} {body.vrijeme}",
|
||||||
|
"ustanova": body.ustanova,
|
||||||
|
"zzjz_url": ZZJZ_INFO["url_sportska_medicina"],
|
||||||
|
"note": "Mock booking — realna ZZJZ PGŽ integracija čeka API/scraper.",
|
||||||
|
"pregled": _row(upd),
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user