Feature: Trauer-Feature, Futter-Verträglichkeit, Multi-Hund-Fixes, Wetter-Ort (Sprint 47)

- dog-profile.js: Verstorben-Button, Gedenkseite, KI-Abschiedstext
- database.py: futter_eintraege/reaktionen, route_dogs, exercise_progress.dog_id
- routes/ernaehrung.py: Futter-Verträglichkeit mit 20 Reaktionstypen + Analyse
- routes/routen.py: route_dogs Many-to-Many, Routen editierbar
- routes/training.py: exercise_progress per dog_id
- routes/ki.py: /ki/abschied Trauer-KI
- weather.py: Nominatim Ortsname parallel geladen
- ui.js: dogChip/bindDogChip, visualViewport-Modal
- api.js: gedenken, gedenkseite, futter-Methoden, route_dogs
- worlds.js: Ortsname im Wetter-Chip
- uebungen.js: _progressLoaded-Flag, dog-spezifischer Fortschritt
- trainingsplaene.js: dog_id Unterstützung
- diary.js/health.js: P-Badge Cleanup
- map.js: Wetter-Ort-Anzeige entfernt
- wetter.js: Ort in Wetter-Detail
This commit is contained in:
rene 2026-05-11 19:28:38 +02:00
parent 1ce802c8dc
commit bda61a0e40
16 changed files with 713 additions and 181 deletions

View file

@ -189,6 +189,13 @@ def init_db():
);
CREATE INDEX IF NOT EXISTS idx_route_walks_user ON route_walks(user_id);
CREATE TABLE IF NOT EXISTS route_dogs (
route_id INTEGER NOT NULL REFERENCES routes(id) ON DELETE CASCADE,
dog_id INTEGER NOT NULL REFERENCES dogs(id) ON DELETE CASCADE,
PRIMARY KEY (route_id, dog_id)
);
CREATE INDEX IF NOT EXISTS idx_route_dogs_dog ON route_dogs(dog_id);
CREATE TABLE IF NOT EXISTS exercise_progress (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
@ -1974,6 +1981,38 @@ def _migrate(conn_factory):
""")
logger.info("Migration: futter_profil bereit.")
# Futter-Einträge & Reaktionen (Verträglichkeits-Tracking)
try:
conn.executescript("""
CREATE TABLE IF NOT EXISTS futter_eintraege (
id INTEGER PRIMARY KEY AUTOINCREMENT,
dog_id INTEGER NOT NULL REFERENCES dogs(id) ON DELETE CASCADE,
datum TEXT NOT NULL,
uhrzeit TEXT NOT NULL,
futter_name TEXT NOT NULL,
futter_typ TEXT NOT NULL DEFAULT 'trockenfutter',
menge_g INTEGER,
notiz TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_futter_eintraege_dog ON futter_eintraege(dog_id, datum DESC);
CREATE TABLE IF NOT EXISTS futter_reaktionen (
id INTEGER PRIMARY KEY AUTOINCREMENT,
dog_id INTEGER NOT NULL REFERENCES dogs(id) ON DELETE CASCADE,
datum TEXT NOT NULL,
uhrzeit TEXT NOT NULL,
reaktion_typ TEXT NOT NULL,
intensitaet INTEGER NOT NULL DEFAULT 3,
notiz TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_futter_reaktionen_dog ON futter_reaktionen(dog_id, datum DESC);
""")
logger.info("Migration: futter_eintraege + futter_reaktionen bereit.")
except Exception as e:
logger.warning(f"Migration futter_eintraege/reaktionen: {e}")
# Wiederkehrende Ausgaben (Daueraufträge)
conn.executescript("""
CREATE TABLE IF NOT EXISTS recurring_expenses (
@ -2104,6 +2143,85 @@ def _migrate(conn_factory):
except Exception:
pass # Spalte existiert bereits
# exercise_progress + training_plan_progress: dog_id ergänzen
existing_ep = [r[1] for r in conn.execute("PRAGMA table_info(exercise_progress)").fetchall()]
if 'dog_id' not in existing_ep:
try:
# Neue Tabelle mit dog_id erstellen
conn.execute("""
CREATE TABLE exercise_progress_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
dog_id INTEGER REFERENCES dogs(id) ON DELETE CASCADE,
exercise_id TEXT NOT NULL,
status TEXT,
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
UNIQUE(dog_id, exercise_id)
)
""")
# Bestehende Daten migrieren: dog_id = erster Hund des Users
conn.execute("""
INSERT INTO exercise_progress_new (user_id, dog_id, exercise_id, status, updated_at)
SELECT ep.user_id,
(SELECT id FROM dogs WHERE user_id=ep.user_id ORDER BY id LIMIT 1),
ep.exercise_id, ep.status, ep.updated_at
FROM exercise_progress ep
""")
conn.execute("DROP TABLE exercise_progress")
conn.execute("ALTER TABLE exercise_progress_new RENAME TO exercise_progress")
conn.execute("CREATE INDEX IF NOT EXISTS idx_exercise_progress_user ON exercise_progress(user_id)")
conn.execute("CREATE INDEX IF NOT EXISTS idx_exercise_progress_dog ON exercise_progress(dog_id)")
logger.info("Migration: exercise_progress.dog_id hinzugefügt.")
except Exception as e:
logger.warning(f"Migration exercise_progress.dog_id fehlgeschlagen: {e}")
existing_tp = [r[1] for r in conn.execute("PRAGMA table_info(training_plan_progress)").fetchall()]
if 'dog_id' not in existing_tp:
try:
conn.execute("""
CREATE TABLE training_plan_progress_new (
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
dog_id INTEGER REFERENCES dogs(id) ON DELETE CASCADE,
item_key TEXT NOT NULL,
checked INTEGER NOT NULL DEFAULT 1,
checked_at TEXT NOT NULL DEFAULT (datetime('now')),
PRIMARY KEY (dog_id, item_key)
)
""")
conn.execute("""
INSERT INTO training_plan_progress_new (user_id, dog_id, item_key, checked, checked_at)
SELECT tp.user_id,
(SELECT id FROM dogs WHERE user_id=tp.user_id ORDER BY id LIMIT 1),
tp.item_key, tp.checked, tp.checked_at
FROM training_plan_progress tp
""")
conn.execute("DROP TABLE training_plan_progress")
conn.execute("ALTER TABLE training_plan_progress_new RENAME TO training_plan_progress")
logger.info("Migration: training_plan_progress.dog_id hinzugefügt.")
except Exception as e:
logger.warning(f"Migration training_plan_progress.dog_id fehlgeschlagen: {e}")
# verstorben_am: Hund als verstorben markierbar
try:
conn.execute("ALTER TABLE dogs ADD COLUMN verstorben_am TEXT")
logger.info("Migration: dogs.verstorben_am hinzugefügt.")
except Exception:
pass
# route_dogs: bestehende Routen allen Hunden des Users zuweisen
try:
existing = conn.execute("SELECT COUNT(*) FROM route_dogs").fetchone()[0]
if existing == 0:
conn.execute("""
INSERT OR IGNORE INTO route_dogs (route_id, dog_id)
SELECT r.id, d.id
FROM routes r
JOIN dogs d ON d.user_id = r.user_id
""")
logger.info("Migration: route_dogs mit bestehenden Routen befüllt.")
except Exception as e:
logger.warning(f"Migration route_dogs fehlgeschlagen: {e}")
def _seed_help_articles(conn):
"""Befüllt help_articles mit Starter-FAQs — nur wenn die Tabelle noch leer ist."""