diff --git a/backend/database.py b/backend/database.py
index 8a00a29..2ff12c3 100644
--- a/backend/database.py
+++ b/backend/database.py
@@ -1469,3 +1469,16 @@ def _migrate(conn_factory):
ex
)
logger.info(f"Migration: Übung '{ex[1]}' eingefügt.")
+
+ # Gespeicherte KI-Jahresberichte für Züchter
+ conn.executescript("""
+ CREATE TABLE IF NOT EXISTS breeder_jahresberichte (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
+ breeder_id INTEGER NOT NULL,
+ jahr INTEGER NOT NULL,
+ text TEXT NOT NULL,
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
+ );
+ CREATE INDEX IF NOT EXISTS idx_bj_user ON breeder_jahresberichte(user_id, jahr DESC);
+ """)
diff --git a/backend/routes/zucht_ki.py b/backend/routes/zucht_ki.py
index 0d366c2..e25c49c 100644
--- a/backend/routes/zucht_ki.py
+++ b/backend/routes/zucht_ki.py
@@ -462,7 +462,45 @@ Zeitraum: letzte 2 Jahre (bis {date.today().isoformat()})
requires_premium=False,
user_id=user["id"],
)
- return {"text": text}
except Exception as e:
logger.warning(f"KI nicht verfügbar: {e}")
- return {"text": _FALLBACK}
+ text = _FALLBACK
+
+ # Bericht speichern
+ jahr = date.today().year
+ with db() as conn:
+ bp2 = conn.execute("SELECT id FROM breeder_profiles WHERE user_id=?", (user["id"],)).fetchone()
+ if bp2:
+ conn.execute(
+ "INSERT INTO breeder_jahresberichte (user_id, breeder_id, jahr, text) VALUES (?,?,?,?)",
+ (user["id"], bp2["id"], jahr, text)
+ )
+ saved_id = conn.execute("SELECT last_insert_rowid()").fetchone()[0]
+ else:
+ saved_id = None
+
+ return {"text": text, "saved_id": saved_id, "jahr": jahr}
+
+
+# GET gespeicherte Berichte
+# ------------------------------------------------------------------
+@router.get("/zucht-ki/jahresbericht")
+async def jahresbericht_list(user=Depends(_require_breeder)):
+ with db() as conn:
+ rows = conn.execute(
+ "SELECT id, jahr, created_at FROM breeder_jahresberichte WHERE user_id=? ORDER BY created_at DESC LIMIT 20",
+ (user["id"],)
+ ).fetchall()
+ return [{"id": r["id"], "jahr": r["jahr"], "created_at": r["created_at"]} for r in rows]
+
+
+@router.get("/zucht-ki/jahresbericht/{bericht_id}")
+async def jahresbericht_get(bericht_id: int, user=Depends(_require_breeder)):
+ with db() as conn:
+ row = conn.execute(
+ "SELECT id, jahr, text, created_at FROM breeder_jahresberichte WHERE id=? AND user_id=?",
+ (bericht_id, user["id"])
+ ).fetchone()
+ if not row:
+ raise HTTPException(404, "Bericht nicht gefunden.")
+ return dict(row)
diff --git a/backend/static/js/api.js b/backend/static/js/api.js
index ea6ad6d..e4623f0 100644
--- a/backend/static/js/api.js
+++ b/backend/static/js/api.js
@@ -692,6 +692,8 @@ const API = (() => {
},
hundBeschreibung(hundId) { return post('/zucht-ki/hund-beschreibung', { hund_id: hundId }); },
jahresbericht() { return post('/zucht-ki/jahresbericht', {}); },
+ jahresberichtList() { return get('/zucht-ki/jahresbericht'); },
+ jahresberichtGet(id) { return get(`/zucht-ki/jahresbericht/${id}`); },
};
const osm = {
diff --git a/backend/static/js/app.js b/backend/static/js/app.js
index 1d17ac9..6c32af2 100644
--- a/backend/static/js/app.js
+++ b/backend/static/js/app.js
@@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
-const APP_VER = '481'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
+const APP_VER = '482'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const App = (() => {
diff --git a/backend/static/js/pages/zuchthunde.js b/backend/static/js/pages/zuchthunde.js
index 0b515cc..78fbb3e 100644
--- a/backend/static/js/pages/zuchthunde.js
+++ b/backend/static/js/pages/zuchthunde.js
@@ -117,6 +117,9 @@ window.Page_zuchthunde = (() => {
${_appState?.user?.ki_zucht_jahresbericht !== 0 ? `
${UI.icon('chart-bar')} Jahresbericht
+
+
+ ${UI.icon('archive')}
` : ''}
@@ -132,6 +135,7 @@ window.Page_zuchthunde = (() => {
document.getElementById('zh-new-btn')?.addEventListener('click', () => _showHundForm(null));
document.getElementById('zh-trial-btn')?.addEventListener('click', () => _showTrialMatingModal());
document.getElementById('zh-jahresbericht-btn')?.addEventListener('click', () => _showJahresbericht());
+ document.getElementById('zh-jahresbericht-archiv-btn')?.addEventListener('click', () => _showJahresberichtArchiv());
document.getElementById('zh-search')?.addEventListener('input', e => {
_query = e.target.value.toLowerCase().trim();
@@ -1285,7 +1289,7 @@ window.Page_zuchthunde = (() => {
}
// ----------------------------------------------------------
- // KI: Jahresbericht
+ // KI: Jahresbericht generieren
// ----------------------------------------------------------
async function _showJahresbericht() {
UI.modal.open({
@@ -1294,10 +1298,12 @@ window.Page_zuchthunde = (() => {
footer: '',
});
- let text = '';
+ let text = '', savedId = null, jahr = new Date().getFullYear();
try {
const result = await API.zuchtKi.jahresbericht();
- text = result.text || result.content || result.bericht || JSON.stringify(result);
+ text = result.text || result.content || result.bericht || JSON.stringify(result);
+ savedId = result.saved_id;
+ jahr = result.jahr || jahr;
} catch (err) {
UI.modal.open({
title: `${UI.icon('chart-bar')} KI-Jahresbericht`,
@@ -1307,23 +1313,93 @@ window.Page_zuchthunde = (() => {
return;
}
+ _renderBerichtModal(text, jahr, savedId);
+ }
+
+ function _renderBerichtModal(text, jahr, savedId) {
UI.modal.open({
- title: `${UI.icon('chart-bar')} KI-Jahresbericht`,
- body: `
${_esc(text)}
`,
+ title: `${UI.icon('chart-bar')} KI-Jahresbericht ${jahr}`,
+ body: `
+ ${savedId ? `
+ ${UI.icon('check-circle')} Automatisch gespeichert
` : ''}
+
${_esc(text)}
`,
footer: `
-