Feature: Aktive Erinnerungen, Versicherung, Verhaltensprotokoll, Hundefreundliche Orte (SW by-v874)

This commit is contained in:
rene 2026-05-11 22:24:42 +02:00
parent 83034c0db0
commit b818f85f36
11 changed files with 589 additions and 14 deletions

View file

@ -569,3 +569,190 @@ async def terminvorschlaege(dog_id: int, user=Depends(get_current_user)):
})
return vorschlaege
# ==================================================================
# VERSICHERUNGS-VERWALTUNG
# ==================================================================
class InsuranceCreate(BaseModel):
anbieter: str
police_nr: Optional[str] = None
jahresbeitrag: Optional[float] = None
kontakt: Optional[str] = None
ablaufdatum: Optional[str] = None
notizen: Optional[str] = None
class InsuranceUpdate(BaseModel):
anbieter: Optional[str] = None
police_nr: Optional[str] = None
jahresbeitrag: Optional[float] = None
kontakt: Optional[str] = None
ablaufdatum: Optional[str] = None
notizen: Optional[str] = None
@router.get("/{dog_id}/insurance")
async def get_insurance(dog_id: int, user=Depends(get_current_user)):
with db() as conn:
conn.execute("SELECT id FROM dogs WHERE id=? AND user_id=?", (dog_id, user["id"])).fetchone() or (_ for _ in ()).throw(HTTPException(404))
rows = conn.execute(
"SELECT * FROM dog_insurance WHERE dog_id=? ORDER BY created_at DESC", (dog_id,)
).fetchall()
return [dict(r) for r in rows]
@router.post("/{dog_id}/insurance", status_code=201)
async def create_insurance(dog_id: int, data: InsuranceCreate, user=Depends(get_current_user)):
with db() as conn:
dog = conn.execute("SELECT id FROM dogs WHERE id=? AND user_id=?", (dog_id, user["id"])).fetchone()
if not dog:
raise HTTPException(404)
cur = conn.execute(
"""INSERT INTO dog_insurance (dog_id, anbieter, police_nr, jahresbeitrag, kontakt, ablaufdatum, notizen)
VALUES (?,?,?,?,?,?,?)""",
(dog_id, data.anbieter, data.police_nr, data.jahresbeitrag, data.kontakt, data.ablaufdatum, data.notizen)
)
row = conn.execute("SELECT * FROM dog_insurance WHERE id=?", (cur.lastrowid,)).fetchone()
return dict(row)
@router.patch("/{dog_id}/insurance/{ins_id}")
async def update_insurance(dog_id: int, ins_id: int, data: InsuranceUpdate, user=Depends(get_current_user)):
fields = {k: v for k, v in data.model_dump().items() if v is not None}
if not fields:
raise HTTPException(400, "Keine Änderungen.")
with db() as conn:
dog = conn.execute("SELECT id FROM dogs WHERE id=? AND user_id=?", (dog_id, user["id"])).fetchone()
if not dog:
raise HTTPException(404)
set_clause = ", ".join(f"{k}=?" for k in fields)
conn.execute(f"UPDATE dog_insurance SET {set_clause} WHERE id=? AND dog_id=?",
list(fields.values()) + [ins_id, dog_id])
row = conn.execute("SELECT * FROM dog_insurance WHERE id=?", (ins_id,)).fetchone()
return dict(row) if row else {}
@router.delete("/{dog_id}/insurance/{ins_id}", status_code=204)
async def delete_insurance(dog_id: int, ins_id: int, user=Depends(get_current_user)):
with db() as conn:
dog = conn.execute("SELECT id FROM dogs WHERE id=? AND user_id=?", (dog_id, user["id"])).fetchone()
if not dog:
raise HTTPException(404)
conn.execute("DELETE FROM dog_insurance WHERE id=? AND dog_id=?", (ins_id, dog_id))
# ==================================================================
# VERHALTENS-PROTOKOLL
# ==================================================================
BEHAVIOR_KATEGORIEN = {
"angst": {"label": "Angst / Panik", "icon": "smiley-nervous"},
"aggression": {"label": "Aggression", "icon": "warning"},
"ueberreaktion":{"label": "Überreaktion", "icon": "lightning"},
"ressource": {"label": "Ressourcenverteidigung", "icon": "lock"},
"separation": {"label": "Trennungsangst", "icon": "house"},
"leine": {"label": "Leinenprobleme", "icon": "path"},
"sozial": {"label": "Sozialkompetenz", "icon": "users"},
"sonstiges": {"label": "Sonstiges", "icon": "note"},
}
BEHAVIOR_TRIGGER = [
"fremde_hunde", "fremde_menschen", "kinder", "laerm_feuerwerk",
"laerm_gewitter", "auto_fahrrad", "tierarzt", "allein_zuhause",
"andere_tiere", "besucher_zuhause", "sonstiges"
]
TRIGGER_LABELS = {
"fremde_hunde": "Fremde Hunde", "fremde_menschen": "Fremde Menschen",
"kinder": "Kinder", "laerm_feuerwerk": "Feuerwerk/Knaller",
"laerm_gewitter": "Gewitter", "auto_fahrrad": "Autos/Fahrräder",
"tierarzt": "Tierarztbesuch", "allein_zuhause": "Allein zuhause",
"andere_tiere": "Andere Tiere", "besucher_zuhause": "Besucher",
"sonstiges": "Sonstiges"
}
class BehaviorCreate(BaseModel):
datum: str
uhrzeit: Optional[str] = None
kategorie: str
intensitaet: int = 3
trigger: Optional[str] = None
notiz: Optional[str] = None
@router.get("/{dog_id}/behavior")
async def get_behavior(dog_id: int, user=Depends(get_current_user)):
with db() as conn:
dog = conn.execute("SELECT id FROM dogs WHERE id=? AND user_id=?", (dog_id, user["id"])).fetchone()
if not dog:
raise HTTPException(404)
rows = conn.execute(
"SELECT * FROM behavior_log WHERE dog_id=? ORDER BY datum DESC, uhrzeit DESC LIMIT 100",
(dog_id,)
).fetchall()
return {
"entries": [dict(r) for r in rows],
"kategorien": BEHAVIOR_KATEGORIEN,
"trigger_labels": TRIGGER_LABELS,
}
@router.post("/{dog_id}/behavior", status_code=201)
async def create_behavior(dog_id: int, data: BehaviorCreate, user=Depends(get_current_user)):
with db() as conn:
dog = conn.execute("SELECT id FROM dogs WHERE id=? AND user_id=?", (dog_id, user["id"])).fetchone()
if not dog:
raise HTTPException(404)
if data.kategorie not in BEHAVIOR_KATEGORIEN:
raise HTTPException(400, "Unbekannte Kategorie.")
cur = conn.execute(
"""INSERT INTO behavior_log (dog_id, datum, uhrzeit, kategorie, intensitaet, trigger, notiz)
VALUES (?,?,?,?,?,?,?)""",
(dog_id, data.datum, data.uhrzeit, data.kategorie,
max(1, min(5, data.intensitaet)), data.trigger, data.notiz)
)
row = conn.execute("SELECT * FROM behavior_log WHERE id=?", (cur.lastrowid,)).fetchone()
return dict(row)
@router.delete("/{dog_id}/behavior/{entry_id}", status_code=204)
async def delete_behavior(dog_id: int, entry_id: int, user=Depends(get_current_user)):
with db() as conn:
dog = conn.execute("SELECT id FROM dogs WHERE id=? AND user_id=?", (dog_id, user["id"])).fetchone()
if not dog:
raise HTTPException(404)
conn.execute("DELETE FROM behavior_log WHERE id=? AND dog_id=?", (entry_id, dog_id))
@router.get("/{dog_id}/reminders")
async def get_upcoming_reminders(dog_id: int, user=Depends(get_current_user)):
"""Bevorstehende Erinnerungen der nächsten 30 Tage + überfällige."""
from datetime import timedelta
today = date.today()
in30 = today + timedelta(days=30)
with db() as conn:
dog = conn.execute("SELECT id FROM dogs WHERE id=? AND user_id=?", (dog_id, user["id"])).fetchone()
if not dog:
raise HTTPException(404)
rows = conn.execute(
"""SELECT id, typ, bezeichnung, naechstes
FROM health
WHERE dog_id=? AND naechstes IS NOT NULL
AND (erinnerung IS NULL OR erinnerung = 1)
AND typ IN ('impfung','entwurmung','medikament')
ORDER BY naechstes ASC""",
(dog_id,)
).fetchall()
result = []
for r in rows:
try:
d = date.fromisoformat(r["naechstes"])
except Exception:
continue
delta = (d - today).days
if delta < -30 or delta > 30:
continue
result.append({**dict(r), "delta_tage": delta, "ueberfaellig": delta < 0})
return result