Feature: Vollständige Züchter-Rolle — Antrag, Würfe, Stammbaum, Genetik

Basis-Features (Schritte 1–11):
- Züchter-Antrag mit Dokument-Upload, Admin-Prüfung, E-Mail-Benachrichtigungen
- Öffentliches Züchter-Profil + Karten-Marker (lila, certificate-Icon)
- Wurfverwaltung: Würfe, Welpen, Gewichtsverlauf, Foto-System
- Wurfbörse (öffentlich) mit Filtersuche nach Rasse/Status
- Läufigkeits-Tracker: Deckdatum + Wurftermin (+63 Tage, nur für Züchter)
- Interessenten-Chat: Kontakt-Button in Wurfbörse und Züchter-Profil
- Sidebar-Einträge: Zuchtkartei + Wurfverwaltung für Züchter/Admin

Stammbaum & Genetik (Schritte 1–8):
- Zuchtkartei: Hunde-Stammdaten mit Vater/Mutter-Verknüpfung
- Stammbaum-Visualisierung: 4 Generationen, horizontales CSS-Grid
- Gesundheitstests (HD, ED, OCD, Augen…) mit farbigen Ergebnis-Badges
- Genetische Tests (MDR1, PRA, DM…): clear/carrier/affected
- Titel & Auszeichnungen (CAC, CACIB, IPO…)
- Probeverpaarung: IK-Berechnung nach Wright + Ampel-Bewertung
- Teilen-Link für öffentliche Hunde-Profile
- Kaufvertrag: druckbares HTML-Dokument pro Welpe

Technisch: 4 neue Route-Dateien, 5 neue Page-Module, 11 neue DB-Tabellen,
icons shield-check + certificate + tree-structure im Sprite — SW by-v465, APP_VER 444
This commit is contained in:
rene 2026-04-28 18:25:21 +02:00
parent 58cb2b4ad3
commit 91340be5a3
24 changed files with 6660 additions and 27 deletions

View file

@ -549,6 +549,8 @@ def _migrate(conn_factory):
("notes", "location_name", "TEXT"),
("notes", "parent_label", "TEXT"),
("users", "notes_ki_enabled", "INTEGER NOT NULL DEFAULT 1"),
# Züchter-Rolle
("users", "breeder_status", "TEXT"),
]
with conn_factory() as conn:
for table, column, col_type in migrations:
@ -1223,3 +1225,178 @@ def _migrate(conn_factory):
conn.execute("CREATE INDEX IF NOT EXISTS idx_diary_created ON diary(dog_id, created_at DESC)")
conn.execute("CREATE INDEX IF NOT EXISTS idx_notes_created ON notes(user_id, created_at DESC)")
logger.info("Migration: Performance-Indizes bereit.")
# Züchter-Tabellen
conn.executescript("""
CREATE TABLE IF NOT EXISTS breeder_profiles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL UNIQUE REFERENCES users(id) ON DELETE CASCADE,
zwingername TEXT NOT NULL,
rasse_text TEXT NOT NULL,
verein TEXT NOT NULL,
vdh_mitglied INTEGER NOT NULL DEFAULT 0,
stadt TEXT NOT NULL,
website TEXT,
beschreibung TEXT,
location_lat REAL,
location_lng REAL,
verified_at TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE TABLE IF NOT EXISTS breeder_documents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
dokument_typ TEXT NOT NULL,
file_path TEXT NOT NULL,
uploaded_at TEXT NOT NULL DEFAULT (datetime('now'))
);
""")
logger.info("Migration: breeder_profiles + breeder_documents bereit.")
# Würfe + Welpen
conn.executescript("""
CREATE TABLE IF NOT EXISTS litters (
id INTEGER PRIMARY KEY AUTOINCREMENT,
breeder_id INTEGER NOT NULL REFERENCES breeder_profiles(id) ON DELETE CASCADE,
vater_name TEXT,
mutter_name TEXT,
geburt_datum TEXT,
erwartetes_datum TEXT,
welpen_gesamt INTEGER,
welpen_verfuegbar INTEGER,
beschreibung TEXT,
gesundheitstests TEXT,
preis_spanne TEXT,
status TEXT NOT NULL DEFAULT 'geplant',
sichtbar INTEGER NOT NULL DEFAULT 1,
sichtbar_bis TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_litters_breeder ON litters(breeder_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_litters_status ON litters(status, sichtbar);
CREATE TABLE IF NOT EXISTS puppies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
wurf_id INTEGER NOT NULL REFERENCES litters(id) ON DELETE CASCADE,
name TEXT,
geschlecht TEXT,
farbe TEXT,
chip_nr TEXT,
geburtsgewicht REAL,
status TEXT NOT NULL DEFAULT 'verfuegbar',
status_sichtbar INTEGER NOT NULL DEFAULT 1,
notiz TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_puppies_wurf ON puppies(wurf_id);
CREATE TABLE IF NOT EXISTS puppy_weights (
id INTEGER PRIMARY KEY AUTOINCREMENT,
welpe_id INTEGER NOT NULL REFERENCES puppies(id) ON DELETE CASCADE,
gewicht_g REAL NOT NULL,
gemessen_am TEXT NOT NULL
);
""")
logger.info("Migration: litters + puppies + puppy_weights bereit.")
# Züchter-Fotos
conn.executescript("""
CREATE TABLE IF NOT EXISTS breeder_photos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
breeder_id INTEGER NOT NULL REFERENCES breeder_profiles(id) ON DELETE CASCADE,
entity_type TEXT NOT NULL,
entity_id INTEGER NOT NULL,
file_path TEXT NOT NULL,
thumbnail_path TEXT,
caption TEXT,
is_primary INTEGER NOT NULL DEFAULT 0,
visibility TEXT NOT NULL DEFAULT 'public',
sort_order INTEGER NOT NULL DEFAULT 0,
uploaded_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_breeder_photos_entity
ON breeder_photos(entity_type, entity_id);
""")
logger.info("Migration: breeder_photos bereit.")
# Züchter-Hunde-Stammdaten (Stammbaum, Gesundheit, Genetik, Titel)
conn.executescript("""
CREATE TABLE IF NOT EXISTS zucht_hunde (
id INTEGER PRIMARY KEY AUTOINCREMENT,
breeder_id INTEGER REFERENCES breeder_profiles(id) ON DELETE SET NULL,
name TEXT NOT NULL,
rufname TEXT,
geschlecht TEXT,
geburtsdatum TEXT,
sterbedatum TEXT,
chip_nr TEXT,
taetowiernummer TEXT,
zuchtbuchnummer TEXT,
farbe TEXT,
vater_id INTEGER REFERENCES zucht_hunde(id),
mutter_id INTEGER REFERENCES zucht_hunde(id),
zuechter_name TEXT,
eigentuemer_name TEXT,
is_public INTEGER NOT NULL DEFAULT 1,
notiz TEXT,
foto_url TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_zucht_hunde_breeder ON zucht_hunde(breeder_id);
CREATE INDEX IF NOT EXISTS idx_zucht_hunde_eltern ON zucht_hunde(vater_id, mutter_id);
CREATE TABLE IF NOT EXISTS dog_health_tests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hund_id INTEGER NOT NULL REFERENCES zucht_hunde(id) ON DELETE CASCADE,
test_typ TEXT NOT NULL,
test_name TEXT,
ergebnis TEXT NOT NULL,
untersuch_am TEXT NOT NULL,
gueltig_bis TEXT,
untersucher TEXT,
labor TEXT,
zertifikat_nr TEXT,
is_public INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_dog_health_tests_hund ON dog_health_tests(hund_id);
CREATE TABLE IF NOT EXISTS dog_genetic_tests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hund_id INTEGER NOT NULL REFERENCES zucht_hunde(id) ON DELETE CASCADE,
marker_name TEXT NOT NULL,
marker_kategorie TEXT,
genotyp TEXT,
ergebnis_klasse TEXT,
getestet_am TEXT NOT NULL,
labor TEXT,
zertifikat_nr TEXT,
is_public INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_dog_genetic_tests_hund ON dog_genetic_tests(hund_id);
CREATE TABLE IF NOT EXISTS dog_titles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hund_id INTEGER NOT NULL REFERENCES zucht_hunde(id) ON DELETE CASCADE,
titel_typ TEXT NOT NULL,
titel_name TEXT NOT NULL,
verliehen_am TEXT NOT NULL,
ort TEXT,
richter TEXT,
ausstellung TEXT,
formwert TEXT,
is_public INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_dog_titles_hund ON dog_titles(hund_id);
""")
logger.info("Migration: zucht_hunde + dog_health_tests + dog_genetic_tests + dog_titles bereit.")
# Läufigkeit: Deckdatum + Wurftermin
existing_h = [row[1] for row in conn.execute("PRAGMA table_info(health)").fetchall()]
for col, typedef in [("deckdatum", "TEXT"), ("wurftermin", "TEXT")]:
if col not in existing_h:
conn.execute(f"ALTER TABLE health ADD COLUMN {col} {typedef}")
logger.info(f"Migration: health.{col} hinzugefügt.")