M9 CRM Obrasci + ZZJZ booking detect + e-mail fallback
Obrasci (M9):
- /api/crm/forms — katalog form_templates (15 templata već seedan)
- /api/crm/forms/templates — alias (kompatibilnost)
- /api/crm/forms/{code|id} — detalji + schema_json
- /api/crm/forms/{code|id}/prefill — autopopulacija polja iz baze
(klub_id/clan_id/user_id → polja na obrascu mapirana po imenima)
- /api/crm/forms/submissions [GET/POST] — lista + create draft
- /api/crm/forms/submissions/{id} — detalji s schema + klub/clan
- /api/crm/forms/submissions/{id}/submit — submit + sha256 potpis sadržaja
- /api/crm/forms/submissions/{id}/sign — re-sign / potpis bez statusa change
- /api/crm/forms/submissions/{id}/approve|reject — workflow
- /api/crm/forms/submissions/{id}/pdf — generirani PDF s metapodacima i potpisom
- /api/crm/forms/{code|id}/submit — shortcut: kreiraj+submit u jednom POST
ZZJZ PGŽ (M8 dopuna):
- /api/crm/zzjz/info — dodan online_booking probe (HTTP scrape best-effort)
- /api/crm/lijecnicki/{id}/zakazi — vraća booking URL ako postoji, inače mailto:
- /api/crm/lijecnicki/zakazi-email — generira mailto: deeplink s pred-popunjenim
podacima sportaša/kluba (fallback kad nema online termina)
- URL sportske medicine ispravljen na školska/adolescentna medicina (jedini stvarni
odjel ZZJZ PGŽ koji obavlja sportske preglede).
This commit is contained in:
@@ -40,7 +40,8 @@ ZZJZ_INFO = {
|
||||
"telefon": "+385 51 358 770",
|
||||
"email": "info@zzjzpgz.hr",
|
||||
"web": ZZJZ_BASE,
|
||||
"url_sportska_medicina": f"{ZZJZ_BASE}/djelatnosti/sportska-medicina/",
|
||||
# Najbliži postojeći odjel — sportski liječnički ide preko adolescentne medicine
|
||||
"url_sportska_medicina": f"{ZZJZ_BASE}/zavod/odjeli/odjel-za-skolsku-i-adolescentnu-medicinu/",
|
||||
}
|
||||
|
||||
|
||||
@@ -382,7 +383,46 @@ def _mock_zzjz_termini(week_start: date) -> list[dict]:
|
||||
|
||||
@router.get("/zzjz/info")
|
||||
def zzjz_info():
|
||||
return ZZJZ_INFO
|
||||
"""Vraća kontakt + provjerava ima li online termin sustav (best-effort scrape)."""
|
||||
online_booking = _detect_zzjz_booking()
|
||||
return {**ZZJZ_INFO, "online_booking": online_booking}
|
||||
|
||||
|
||||
def _detect_zzjz_booking() -> dict:
|
||||
"""
|
||||
Best-effort detekcija da li ZZJZ PGŽ ima online termin formu na stranici.
|
||||
Vraća: {available: bool, url: str|None, kind: 'iframe'|'link'|'email'}
|
||||
Ne baca iznimku — uvijek vrati strukturu (fallback je email).
|
||||
"""
|
||||
try:
|
||||
import urllib.request
|
||||
import re as _re
|
||||
req = urllib.request.Request(ZZJZ_INFO["url_sportska_medicina"],
|
||||
headers={"User-Agent": "PGZSport/1.0"})
|
||||
with urllib.request.urlopen(req, timeout=4) as resp:
|
||||
html = resp.read(200_000).decode("utf-8", errors="ignore")
|
||||
# tražimo standardne oznake online booking sustava
|
||||
patterns = [
|
||||
r'(https?://[^"\']*(?:doktor|booking|narucivanje|naruci|termin)[^"\']*)',
|
||||
r'<iframe[^>]+src="([^"]+)"',
|
||||
]
|
||||
for p in patterns:
|
||||
m = _re.search(p, html, _re.IGNORECASE)
|
||||
if m:
|
||||
url = m.group(1)
|
||||
if "iframe" in p:
|
||||
return {"available": True, "url": url, "kind": "iframe"}
|
||||
return {"available": True, "url": url, "kind": "link"}
|
||||
return {"available": False, "url": ZZJZ_INFO["url_sportska_medicina"],
|
||||
"kind": "email",
|
||||
"fallback_email": ZZJZ_INFO["email"],
|
||||
"note": "Nije pronađen online sustav — koristi e-mail kontakt."}
|
||||
except Exception as e:
|
||||
return {"available": False, "url": ZZJZ_INFO["url_sportska_medicina"],
|
||||
"kind": "email",
|
||||
"fallback_email": ZZJZ_INFO["email"],
|
||||
"error": str(e)[:120],
|
||||
"note": "Detekcija nije uspjela — fallback na e-mail."}
|
||||
|
||||
|
||||
@router.get("/zzjz/termini")
|
||||
@@ -412,11 +452,22 @@ def zzjz_termini(
|
||||
@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.
|
||||
Zakazuje termin za pregled.
|
||||
- Ako ZZJZ PGŽ ima online booking → vraća iframe/deeplink URL.
|
||||
- Ako nema → vraća mailto: deeplink za zahtjev e-mailom.
|
||||
Status pregleda u DB se ažurira (ustanova + napomena).
|
||||
"""
|
||||
with _conn() as conn, conn.cursor() as cur:
|
||||
cur.execute("SELECT id, clan_id, ustanova FROM pgz_sport.lijecnicki_pregledi WHERE id=%s", (lid,))
|
||||
cur.execute("""
|
||||
SELECT l.id, l.clan_id, l.ustanova,
|
||||
cl.ime || ' ' || cl.prezime AS clan,
|
||||
cl.email AS clan_email,
|
||||
k.naziv AS klub
|
||||
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")
|
||||
@@ -434,12 +485,85 @@ def zakazi_termin(lid: int, body: ZakaziIn):
|
||||
""", (body.ustanova, new_napomena, lid))
|
||||
upd = cur.fetchone()
|
||||
conn.commit()
|
||||
|
||||
booking = _detect_zzjz_booking()
|
||||
from urllib.parse import quote as _q
|
||||
subj = _q(f"Zahtjev za termin sportske medicine — {r.get('clan') or '(sportaš)'}")
|
||||
body_email = _q(
|
||||
f"Poštovani,\n\nMolim Vas termin za sportski liječnički pregled.\n\n"
|
||||
f"Sportaš: {r.get('clan') or ''}\n"
|
||||
f"Klub: {r.get('klub') or ''}\n"
|
||||
f"Željeni datum: {body.datum.isoformat()} oko {body.vrijeme}\n"
|
||||
f"Kontakt: {r.get('clan_email') or '(nepoznato)'}\n\n"
|
||||
f"Lijep pozdrav,\nPGŽ Sport platforma"
|
||||
)
|
||||
mailto = f"mailto:{ZZJZ_INFO['email']}?subject={subj}&body={body_email}"
|
||||
|
||||
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.",
|
||||
"zzjz": ZZJZ_INFO,
|
||||
"booking": booking,
|
||||
"mailto": mailto,
|
||||
"note": (
|
||||
"Online booking detektiran — koristi 'booking.url' za iframe/redirect."
|
||||
if booking.get("available") else
|
||||
"Online booking nije pronađen — fallback: koristi 'mailto' za zahtjev e-mailom."
|
||||
),
|
||||
"pregled": _row(upd),
|
||||
}
|
||||
|
||||
|
||||
class ZakaziEmailIn(BaseModel):
|
||||
klub_id: Optional[int] = None
|
||||
clan_id: int
|
||||
zeljeni_datum: Optional[date] = None
|
||||
zeljeno_vrijeme: Optional[str] = "09:00"
|
||||
napomena: Optional[str] = None
|
||||
|
||||
|
||||
@router.post("/lijecnicki/zakazi-email")
|
||||
def zakazi_email(body: ZakaziEmailIn):
|
||||
"""
|
||||
Bez postojećeg pregleda — generira mailto: link s pred-popunjenim
|
||||
podacima sportaša/kluba za slanje zahtjeva ZZJZ PGŽ.
|
||||
"""
|
||||
with _conn() as conn, conn.cursor() as cur:
|
||||
cur.execute("""
|
||||
SELECT cl.id, cl.ime || ' ' || cl.prezime AS clan,
|
||||
cl.email AS clan_email, cl.telefon AS clan_telefon,
|
||||
cl.datum_rodenja, cl.oib AS clan_oib,
|
||||
k.naziv AS klub, k.oib AS klub_oib
|
||||
FROM pgz_sport.clanovi cl
|
||||
LEFT JOIN pgz_sport.klubovi k ON k.id = cl.klub_id
|
||||
WHERE cl.id=%s
|
||||
""", (body.clan_id,))
|
||||
r = cur.fetchone()
|
||||
if not r:
|
||||
raise HTTPException(404, "Član ne postoji")
|
||||
|
||||
from urllib.parse import quote as _q
|
||||
when = (body.zeljeni_datum.isoformat() if body.zeljeni_datum else "po dogovoru")
|
||||
subj = _q(f"Zahtjev za termin sportske medicine — {r['clan']}")
|
||||
body_email = _q(
|
||||
f"Poštovani,\n\nMolim Vas termin za sportski liječnički pregled.\n\n"
|
||||
f"Sportaš: {r['clan']}\n"
|
||||
f"OIB: {r['clan_oib'] or '—'}\n"
|
||||
f"Datum rođenja: {r['datum_rodenja'] or '—'}\n"
|
||||
f"Klub: {r['klub'] or '—'}\n"
|
||||
f"Željeni termin: {when} oko {body.zeljeno_vrijeme}\n"
|
||||
f"Kontakt: {r['clan_email'] or '—'} / {r['clan_telefon'] or '—'}\n\n"
|
||||
f"Napomena: {body.napomena or '—'}\n\n"
|
||||
f"Lijep pozdrav,\nPGŽ Sport platforma"
|
||||
)
|
||||
mailto = f"mailto:{ZZJZ_INFO['email']}?subject={subj}&body={body_email}"
|
||||
booking = _detect_zzjz_booking()
|
||||
return {
|
||||
"ok": True,
|
||||
"clan": r["clan"],
|
||||
"zzjz": ZZJZ_INFO,
|
||||
"booking": booking,
|
||||
"mailto": mailto,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user