- timeutils: next_appointment_slot() parst OSM opening_hours, findet Slot - GET /health/terminvorschlaege: fällige/überfällige Einträge (30-Tage-Horizont) Impfung/Tierarzt nutzen Praxis-Öffnungszeiten, Rest nächster Werktag 09:00 - Frontend: Terminvorschlags-Karten, bestätigbares Modal, legt Event an - ki.py: Admins, Moderatoren, Media Manager bypassen CLOUD_WEEKLY_LIMIT
550 lines
21 KiB
Python
550 lines
21 KiB
Python
"""BAN YARO — Gesundheit & Impfpass Routes"""
|
|
|
|
import os, uuid
|
|
from datetime import date, datetime
|
|
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
|
|
from pydantic import BaseModel
|
|
from typing import Optional
|
|
from database import db
|
|
from auth import get_current_user
|
|
from media_utils import safe_media_path
|
|
|
|
router = APIRouter()
|
|
MEDIA_DIR = os.getenv("MEDIA_DIR", "/data/media")
|
|
|
|
ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".webp", ".pdf"}
|
|
|
|
# Erlaubte Typen
|
|
TYPEN = {"impfung", "entwurmung", "tierarzt", "medikament", "gewicht", "allergie", "dokument", "laeufigkeit"}
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# Schemas
|
|
# ------------------------------------------------------------------
|
|
class HealthCreate(BaseModel):
|
|
typ: str
|
|
bezeichnung: Optional[str] = None
|
|
datum: str
|
|
naechstes: Optional[str] = None
|
|
notiz: Optional[str] = None
|
|
# Gewicht
|
|
wert: Optional[float] = None
|
|
einheit: Optional[str] = "kg"
|
|
# Impfung
|
|
charge_nr: Optional[str] = None
|
|
tierarzt_name: Optional[str] = None
|
|
# Tierarztbesuch
|
|
kosten: Optional[float] = None
|
|
diagnose: Optional[str] = None
|
|
# Medikament
|
|
dosierung: Optional[str] = None
|
|
haeufigkeit: Optional[str] = None
|
|
aktiv: Optional[int] = 1
|
|
bis_datum: Optional[str] = None
|
|
# Allergie
|
|
schweregrad: Optional[str] = None # leicht | mittel | schwer
|
|
reaktion: Optional[str] = None
|
|
erinnerung: Optional[int] = 1
|
|
intervall_tage: Optional[int] = None # Wiederkehrend alle X Tage
|
|
# Tierarzt-Verknüpfung
|
|
tierarzt_id: Optional[int] = None
|
|
|
|
|
|
class HealthUpdate(BaseModel):
|
|
bezeichnung: Optional[str] = None
|
|
datum: Optional[str] = None
|
|
naechstes: Optional[str] = None
|
|
notiz: Optional[str] = None
|
|
wert: Optional[float] = None
|
|
einheit: Optional[str] = None
|
|
charge_nr: Optional[str] = None
|
|
tierarzt_name: Optional[str] = None
|
|
kosten: Optional[float] = None
|
|
diagnose: Optional[str] = None
|
|
dosierung: Optional[str] = None
|
|
haeufigkeit: Optional[str] = None
|
|
aktiv: Optional[int] = None
|
|
bis_datum: Optional[str] = None
|
|
schweregrad: Optional[str] = None
|
|
reaktion: Optional[str] = None
|
|
erinnerung: Optional[int] = None
|
|
intervall_tage: Optional[int] = None
|
|
tierarzt_id: Optional[int] = None
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# Hilfsfunktionen
|
|
# ------------------------------------------------------------------
|
|
def _sync_gewicht(conn, dog_id: int):
|
|
"""Aktualisiert dogs.gewicht_kg auf den neuesten Gewichtseintrag (nach datum)."""
|
|
conn.execute(
|
|
"""UPDATE dogs SET gewicht_kg = (
|
|
SELECT wert FROM health
|
|
WHERE dog_id=? AND typ='gewicht' AND wert IS NOT NULL
|
|
ORDER BY datum DESC, id DESC LIMIT 1
|
|
) WHERE id=?""",
|
|
(dog_id, dog_id)
|
|
)
|
|
|
|
|
|
def _check_dog_owner(conn, dog_id: int, user_id: int):
|
|
dog = conn.execute(
|
|
"SELECT id FROM dogs WHERE id=? AND user_id=?", (dog_id, user_id)
|
|
).fetchone()
|
|
if not dog:
|
|
raise HTTPException(404, "Hund nicht gefunden.")
|
|
return dog
|
|
|
|
|
|
def _fetch_media_items(conn, entry_ids: list) -> dict:
|
|
"""Gibt {health_id: [{id, url, media_type}, ...]} zurück."""
|
|
if not entry_ids:
|
|
return {}
|
|
ph = ",".join("?" * len(entry_ids))
|
|
rows = conn.execute(
|
|
f"SELECT id, health_id, url, media_type FROM health_media "
|
|
f"WHERE health_id IN ({ph}) ORDER BY health_id, sort_order",
|
|
entry_ids
|
|
).fetchall()
|
|
result = {}
|
|
for r in rows:
|
|
result.setdefault(r["health_id"], []).append({
|
|
"id": r["id"], "url": r["url"], "media_type": r["media_type"]
|
|
})
|
|
return result
|
|
|
|
|
|
def _entry_with_media(row, media_map: dict) -> dict:
|
|
e = dict(row)
|
|
e["media_items"] = media_map.get(e["id"], [])
|
|
return e
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# GET /api/dogs/{dog_id}/health
|
|
# ------------------------------------------------------------------
|
|
@router.get("/{dog_id}/health")
|
|
async def list_health(dog_id: int, typ: Optional[str] = None,
|
|
user=Depends(get_current_user)):
|
|
with db() as conn:
|
|
_check_dog_owner(conn, dog_id, user["id"])
|
|
if typ:
|
|
rows = conn.execute(
|
|
"SELECT * FROM health WHERE dog_id=? AND typ=? ORDER BY datum DESC",
|
|
(dog_id, typ)
|
|
).fetchall()
|
|
else:
|
|
rows = conn.execute(
|
|
"SELECT * FROM health WHERE dog_id=? ORDER BY datum DESC",
|
|
(dog_id,)
|
|
).fetchall()
|
|
ids = [r["id"] for r in rows]
|
|
media_map = _fetch_media_items(conn, ids)
|
|
return [_entry_with_media(r, media_map) for r in rows]
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# POST /api/dogs/{dog_id}/health
|
|
# ------------------------------------------------------------------
|
|
@router.post("/{dog_id}/health", status_code=201)
|
|
async def create_health(dog_id: int, data: HealthCreate,
|
|
user=Depends(get_current_user)):
|
|
if data.typ not in TYPEN:
|
|
raise HTTPException(400, f"Unbekannter Typ. Erlaubt: {', '.join(TYPEN)}")
|
|
|
|
with db() as conn:
|
|
_check_dog_owner(conn, dog_id, user["id"])
|
|
conn.execute(
|
|
"""INSERT INTO health
|
|
(dog_id, typ, bezeichnung, datum, naechstes, notiz,
|
|
wert, einheit, charge_nr, tierarzt_name, kosten, diagnose,
|
|
dosierung, haeufigkeit, aktiv, bis_datum,
|
|
schweregrad, reaktion, erinnerung, tierarzt_id)
|
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
|
|
(dog_id, data.typ, data.bezeichnung, data.datum, data.naechstes,
|
|
data.notiz, data.wert, data.einheit, data.charge_nr,
|
|
data.tierarzt_name, data.kosten, data.diagnose, data.dosierung,
|
|
data.haeufigkeit, data.aktiv, data.bis_datum,
|
|
data.schweregrad, data.reaktion, data.erinnerung, data.tierarzt_id)
|
|
)
|
|
row = conn.execute(
|
|
"SELECT * FROM health WHERE dog_id=? ORDER BY id DESC LIMIT 1",
|
|
(dog_id,)
|
|
).fetchone()
|
|
media_map = _fetch_media_items(conn, [row["id"]])
|
|
if data.typ == 'gewicht':
|
|
_sync_gewicht(conn, dog_id)
|
|
return _entry_with_media(row, media_map)
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# PATCH /api/dogs/{dog_id}/health/{id}
|
|
# ------------------------------------------------------------------
|
|
@router.patch("/{dog_id}/health/{entry_id}")
|
|
async def update_health(dog_id: int, entry_id: int, data: HealthUpdate,
|
|
user=Depends(get_current_user)):
|
|
with db() as conn:
|
|
_check_dog_owner(conn, dog_id, user["id"])
|
|
entry = conn.execute(
|
|
"SELECT * FROM health WHERE id=? AND dog_id=?", (entry_id, dog_id)
|
|
).fetchone()
|
|
if not entry:
|
|
raise HTTPException(404, "Eintrag nicht gefunden.")
|
|
|
|
updates = {k: v for k, v in data.model_dump().items() if v is not None}
|
|
if not updates:
|
|
return dict(entry)
|
|
|
|
set_clause = ", ".join(f"{k}=?" for k in updates)
|
|
values = list(updates.values()) + [entry_id]
|
|
conn.execute(f"UPDATE health SET {set_clause} WHERE id=?", values)
|
|
row = conn.execute("SELECT * FROM health WHERE id=?", (entry_id,)).fetchone()
|
|
media_map = _fetch_media_items(conn, [entry_id])
|
|
if row["typ"] == 'gewicht':
|
|
_sync_gewicht(conn, dog_id)
|
|
return _entry_with_media(row, media_map)
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# DELETE /api/dogs/{dog_id}/health/{id}
|
|
# ------------------------------------------------------------------
|
|
@router.delete("/{dog_id}/health/{entry_id}", status_code=204)
|
|
async def delete_health(dog_id: int, entry_id: int, user=Depends(get_current_user)):
|
|
with db() as conn:
|
|
_check_dog_owner(conn, dog_id, user["id"])
|
|
entry = conn.execute(
|
|
"SELECT id, typ FROM health WHERE id=? AND dog_id=?", (entry_id, dog_id)
|
|
).fetchone()
|
|
if not entry:
|
|
raise HTTPException(404, "Eintrag nicht gefunden.")
|
|
was_gewicht = entry["typ"] == 'gewicht'
|
|
conn.execute("DELETE FROM health WHERE id=?", (entry_id,))
|
|
if was_gewicht:
|
|
_sync_gewicht(conn, dog_id)
|
|
return None
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# DELETE /api/dogs/{dog_id}/health/{id}/dokument — Datei löschen
|
|
# ------------------------------------------------------------------
|
|
@router.delete("/{dog_id}/health/{entry_id}/dokument")
|
|
async def delete_dokument(dog_id: int, entry_id: int, user=Depends(get_current_user)):
|
|
with db() as conn:
|
|
_check_dog_owner(conn, dog_id, user["id"])
|
|
entry = conn.execute(
|
|
"SELECT datei_url FROM health WHERE id=? AND dog_id=?", (entry_id, dog_id)
|
|
).fetchone()
|
|
if not entry:
|
|
raise HTTPException(404, "Eintrag nicht gefunden.")
|
|
|
|
datei_url = entry["datei_url"]
|
|
if datei_url:
|
|
path = safe_media_path(MEDIA_DIR, datei_url)
|
|
if path and os.path.isfile(path):
|
|
os.remove(path)
|
|
|
|
conn.execute(
|
|
"UPDATE health SET datei_url=NULL, datei_typ=NULL WHERE id=?", (entry_id,)
|
|
)
|
|
|
|
return {"ok": True}
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# POST /api/dogs/{dog_id}/health/{id}/dokument — Datei-Upload
|
|
# ------------------------------------------------------------------
|
|
@router.post("/{dog_id}/health/{entry_id}/dokument")
|
|
async def upload_dokument(
|
|
dog_id: int,
|
|
entry_id: int,
|
|
file: UploadFile = File(...),
|
|
user=Depends(get_current_user),
|
|
):
|
|
with db() as conn:
|
|
_check_dog_owner(conn, dog_id, user["id"])
|
|
entry = conn.execute(
|
|
"SELECT id FROM health WHERE id=? AND dog_id=?", (entry_id, dog_id)
|
|
).fetchone()
|
|
if not entry:
|
|
raise HTTPException(404, "Eintrag nicht gefunden.")
|
|
|
|
ext = os.path.splitext(file.filename or "")[1].lower() or ".jpg"
|
|
if ext not in {".jpg", ".jpeg", ".png", ".pdf", ".webp"}:
|
|
raise HTTPException(400, "Nur JPG, PNG, WebP und PDF erlaubt.")
|
|
|
|
filename = f"health_{entry_id}_{uuid.uuid4().hex[:8]}{ext}"
|
|
path = os.path.join(MEDIA_DIR, "health", filename)
|
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
|
|
with open(path, "wb") as f:
|
|
f.write(await file.read())
|
|
|
|
datei_url = f"/media/health/{filename}"
|
|
datei_typ = "pdf" if ext == ".pdf" else "image"
|
|
|
|
with db() as conn:
|
|
conn.execute(
|
|
"UPDATE health SET datei_url=?, datei_typ=? WHERE id=?",
|
|
(datei_url, datei_typ, entry_id)
|
|
)
|
|
|
|
return {"datei_url": datei_url, "datei_typ": datei_typ}
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# POST /api/dogs/{dog_id}/health/{entry_id}/media — Datei-Upload (Multi)
|
|
# ------------------------------------------------------------------
|
|
@router.post("/{dog_id}/health/{entry_id}/media")
|
|
async def upload_media(
|
|
dog_id: int,
|
|
entry_id: int,
|
|
file: UploadFile = File(...),
|
|
user=Depends(get_current_user),
|
|
):
|
|
with db() as conn:
|
|
_check_dog_owner(conn, dog_id, user["id"])
|
|
entry = conn.execute(
|
|
"SELECT id FROM health WHERE id=? AND dog_id=?", (entry_id, dog_id)
|
|
).fetchone()
|
|
if not entry:
|
|
raise HTTPException(404, "Eintrag nicht gefunden.")
|
|
|
|
ext = os.path.splitext(file.filename or "")[1].lower() or ".jpg"
|
|
if ext not in ALLOWED_EXTENSIONS:
|
|
raise HTTPException(400, "Nur JPG, PNG, WebP und PDF erlaubt.")
|
|
|
|
filename = f"health_{entry_id}_{uuid.uuid4().hex[:8]}{ext}"
|
|
path = os.path.join(MEDIA_DIR, "health", filename)
|
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
|
|
with open(path, "wb") as f:
|
|
f.write(await file.read())
|
|
|
|
media_url = f"/media/health/{filename}"
|
|
media_type = "pdf" if ext == ".pdf" else "image"
|
|
|
|
with db() as conn:
|
|
max_order = conn.execute(
|
|
"SELECT COALESCE(MAX(sort_order), -1) FROM health_media WHERE health_id=?",
|
|
(entry_id,)
|
|
).fetchone()[0]
|
|
conn.execute(
|
|
"INSERT INTO health_media (health_id, url, media_type, sort_order) VALUES (?,?,?,?)",
|
|
(entry_id, media_url, media_type, max_order + 1)
|
|
)
|
|
new_id = conn.execute(
|
|
"SELECT id FROM health_media WHERE health_id=? ORDER BY id DESC LIMIT 1",
|
|
(entry_id,)
|
|
).fetchone()["id"]
|
|
|
|
return {"id": new_id, "url": media_url, "media_type": media_type, "sort_order": max_order + 1}
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# DELETE /api/dogs/{dog_id}/health/{entry_id}/media/{media_id}
|
|
# ------------------------------------------------------------------
|
|
@router.delete("/{dog_id}/health/{entry_id}/media/{media_id}", status_code=204)
|
|
async def delete_media_item(dog_id: int, entry_id: int, media_id: int,
|
|
user=Depends(get_current_user)):
|
|
with db() as conn:
|
|
_check_dog_owner(conn, dog_id, user["id"])
|
|
row = conn.execute(
|
|
"SELECT hm.id, hm.url FROM health_media hm "
|
|
"JOIN health h ON h.id = hm.health_id "
|
|
"WHERE hm.id=? AND hm.health_id=? AND h.dog_id=?",
|
|
(media_id, entry_id, dog_id)
|
|
).fetchone()
|
|
if not row:
|
|
raise HTTPException(404, "Medium nicht gefunden.")
|
|
file_path = safe_media_path(MEDIA_DIR, row["url"])
|
|
if file_path:
|
|
try: os.remove(file_path)
|
|
except OSError: pass
|
|
conn.execute("DELETE FROM health_media WHERE id=?", (media_id,))
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# GET /api/dogs/{dog_id}/health/gewicht — Gewichtsverlauf
|
|
# ------------------------------------------------------------------
|
|
@router.get("/{dog_id}/health/gewicht")
|
|
async def list_gewicht(dog_id: int, user=Depends(get_current_user)):
|
|
with db() as conn:
|
|
_check_dog_owner(conn, dog_id, user["id"])
|
|
rows = conn.execute(
|
|
"""SELECT datum, wert AS gewicht FROM health
|
|
WHERE dog_id=? AND typ='gewicht' AND wert IS NOT NULL
|
|
ORDER BY datum ASC""",
|
|
(dog_id,)
|
|
).fetchall()
|
|
return [dict(r) for r in rows]
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# POST /api/dogs/{dog_id}/health/symptom-check — KI-Symptomprüfung
|
|
# ------------------------------------------------------------------
|
|
class SymptomCheckRequest(BaseModel):
|
|
symptoms: str
|
|
|
|
|
|
@router.post("/{dog_id}/health/symptom-check")
|
|
async def symptom_check(dog_id: int, data: SymptomCheckRequest,
|
|
user=Depends(get_current_user)):
|
|
from ki import symptom_check as ki_symptom_check, KIUnavailableError
|
|
|
|
with db() as conn:
|
|
dog = conn.execute(
|
|
"SELECT * FROM dogs WHERE id=? AND user_id=?", (dog_id, user["id"])
|
|
).fetchone()
|
|
if not dog:
|
|
raise HTTPException(404, "Hund nicht gefunden.")
|
|
|
|
dog_info = dict(dog)
|
|
if dog_info.get("geburtstag"):
|
|
try:
|
|
from datetime import date
|
|
geb = date.fromisoformat(dog_info["geburtstag"])
|
|
dog_info["alter_jahre"] = round((date.today() - geb).days / 365.25, 1)
|
|
except Exception:
|
|
dog_info["alter_jahre"] = "unbekannt"
|
|
|
|
try:
|
|
result = await ki_symptom_check(
|
|
symptoms=data.symptoms,
|
|
dog_info=dog_info,
|
|
user_is_premium=bool(user.get("is_premium")),
|
|
user_id=user["id"],
|
|
)
|
|
return result
|
|
except KIUnavailableError as e:
|
|
raise HTTPException(503, str(e))
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# POST /api/dogs/{dog_id}/health/ki-zusammenfassung
|
|
# ------------------------------------------------------------------
|
|
@router.post("/{dog_id}/health/ki-zusammenfassung")
|
|
async def ki_zusammenfassung(dog_id: int, user=Depends(get_current_user)):
|
|
from ki import health_summary, KIUnavailableError, KIPremiumRequired
|
|
|
|
with db() as conn:
|
|
dog = conn.execute(
|
|
"SELECT * FROM dogs WHERE id=? AND user_id=?", (dog_id, user["id"])
|
|
).fetchone()
|
|
if not dog:
|
|
raise HTTPException(404, "Hund nicht gefunden.")
|
|
|
|
rows = conn.execute(
|
|
"SELECT * FROM health WHERE dog_id=? ORDER BY datum DESC",
|
|
(dog_id,)
|
|
).fetchall()
|
|
|
|
health_data = [dict(r) for r in rows]
|
|
|
|
try:
|
|
result = await health_summary(
|
|
health_data=health_data,
|
|
dog_info=dict(dog),
|
|
user_is_premium=bool(user.get("is_premium")),
|
|
user_id=user["id"],
|
|
)
|
|
return {"zusammenfassung": result}
|
|
except KIPremiumRequired as e:
|
|
raise HTTPException(402, str(e))
|
|
except KIUnavailableError as e:
|
|
raise HTTPException(503, str(e))
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# GET /api/dogs/{dog_id}/health/ki-berichte
|
|
# ------------------------------------------------------------------
|
|
@router.get("/{dog_id}/health/ki-berichte")
|
|
async def list_ki_berichte(dog_id: int, user=Depends(get_current_user)):
|
|
with db() as conn:
|
|
_check_dog_owner(conn, dog_id, user["id"])
|
|
rows = conn.execute(
|
|
"""SELECT id, bericht, erstellt_at FROM ki_health_reports
|
|
WHERE dog_id=? ORDER BY erstellt_at DESC LIMIT 5""",
|
|
(dog_id,)
|
|
).fetchall()
|
|
return [dict(r) for r in rows]
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# GET /api/dogs/{dog_id}/health/terminvorschlaege
|
|
# Gibt strukturierte Termin-Vorschläge auf Basis fälliger health-Einträge.
|
|
# ------------------------------------------------------------------
|
|
_TERMIN_TYPEN = {
|
|
'impfung': {'label': 'Impfung', 'beim_tierarzt': True, 'icon': 'syringe'},
|
|
'entwurmung': {'label': 'Entwurmung', 'beim_tierarzt': False, 'icon': 'pill'},
|
|
'tierarzt': {'label': 'Tierarztbesuch','beim_tierarzt': True, 'icon': 'first-aid'},
|
|
'medikament': {'label': 'Medikament', 'beim_tierarzt': False, 'icon': 'pill'},
|
|
'laeufigkeit': {'label': 'Läufigkeit', 'beim_tierarzt': False, 'icon': 'calendar'},
|
|
}
|
|
|
|
@router.get("/{dog_id}/health/terminvorschlaege")
|
|
async def terminvorschlaege(dog_id: int, user=Depends(get_current_user)):
|
|
from timeutils import next_appointment_slot
|
|
from datetime import date, timedelta
|
|
|
|
today = date.today()
|
|
horizon = today + timedelta(days=30)
|
|
|
|
with db() as conn:
|
|
_check_dog_owner(conn, dog_id, user["id"])
|
|
|
|
# Einträge mit fälligem naechstes (überfällig oder in 30 Tagen)
|
|
rows = conn.execute(
|
|
"""SELECT id, typ, bezeichnung, naechstes, tierarzt_id
|
|
FROM health
|
|
WHERE dog_id=? AND naechstes IS NOT NULL
|
|
AND naechstes <= ? AND aktiv=1
|
|
ORDER BY naechstes ASC""",
|
|
(dog_id, horizon.isoformat())
|
|
).fetchall()
|
|
|
|
# Primäre Praxis des Users (erste aktive)
|
|
praxis = conn.execute(
|
|
"SELECT name, opening_hours, lat, lon FROM tieraerzte "
|
|
"WHERE user_id=? AND aktiv=1 ORDER BY id LIMIT 1",
|
|
(user["id"],)
|
|
).fetchone()
|
|
|
|
oh = praxis["opening_hours"] if praxis else None
|
|
praxis_name = praxis["name"] if praxis else None
|
|
praxis_lat = praxis["lat"] if praxis else None
|
|
praxis_lon = praxis["lon"] if praxis else None
|
|
|
|
vorschlaege = []
|
|
for r in rows:
|
|
cfg = _TERMIN_TYPEN.get(r["typ"])
|
|
if not cfg:
|
|
continue
|
|
|
|
naechstes = date.fromisoformat(r["naechstes"])
|
|
ueberfaellig = naechstes < today
|
|
delta_tage = (naechstes - today).days
|
|
|
|
# Terminfindung: bei Tierarzt-Typen Öffnungszeiten nutzen
|
|
slot_oh = oh if cfg["beim_tierarzt"] else None
|
|
# Frühestens ab morgen, aber nicht vor dem Fälligkeitsdatum wenn noch in der Zukunft
|
|
start = today if ueberfaellig else naechstes - timedelta(days=1)
|
|
datum_v, uhrzeit_v = next_appointment_slot(slot_oh, start_from=start)
|
|
|
|
vorschlaege.append({
|
|
"health_id": r["id"],
|
|
"typ": r["typ"],
|
|
"label": cfg["label"],
|
|
"icon": cfg["icon"],
|
|
"bezeichnung": r["bezeichnung"],
|
|
"naechstes": r["naechstes"],
|
|
"ueberfaellig": ueberfaellig,
|
|
"delta_tage": delta_tage,
|
|
"beim_tierarzt": cfg["beim_tierarzt"],
|
|
"datum_vorschlag": datum_v,
|
|
"uhrzeit_vorschlag": uhrzeit_v,
|
|
"praxis_name": praxis_name if cfg["beim_tierarzt"] else None,
|
|
"praxis_lat": praxis_lat if cfg["beim_tierarzt"] else None,
|
|
"praxis_lon": praxis_lon if cfg["beim_tierarzt"] else None,
|
|
})
|
|
|
|
return vorschlaege
|