banyaro/promotion/banyaro_zuechterolle_ausarbeitung.md
rene 4ac1c27b75 Dateien nach „promotion“ hochladen
Vorbereitung für Züchterfeatures
2026-04-28 08:16:26 +02:00

13 KiB
Raw Blame History

Züchter-Rolle — Banyaro.app

Vollständige Ausarbeitung zur Umsetzung


1. Übersicht

Rollen-Modell

Rolle Beschreibung
user Normaler Hundebesitzer (bestehend)
breeder Verifizierter Züchter (neu)
admin René — Plattform-Administration (bestehend)

Die Rolle breeder erweitert user — ein Züchter hat alle normalen Nutzerrechte plus Züchter-spezifische Features.


2. Datenbankstruktur

Tabelle: users (Erweiterung)

ALTER TABLE users ADD COLUMN role VARCHAR(20) DEFAULT 'user';
-- Werte: 'user', 'breeder', 'admin'

ALTER TABLE users ADD COLUMN breeder_status VARCHAR(20) DEFAULT NULL;
-- Werte: NULL (kein Antrag), 'pending', 'approved', 'rejected'

Tabelle: breeder_profiles

CREATE TABLE breeder_profiles (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id         INTEGER NOT NULL UNIQUE,
    kennel_name     VARCHAR(100) NOT NULL,       -- Zwingername
    breed_id        INTEGER NOT NULL,            -- Nur eine Rasse (FK auf breeds)
    vdh_member      BOOLEAN DEFAULT FALSE,
    association     VARCHAR(100),                -- Zuchtverein (z.B. KFZ, VDH)
    description     TEXT,                        -- Freitext Vorstellung
    website         VARCHAR(255),
    location_lat    FLOAT,
    location_lng    FLOAT,
    location_city   VARCHAR(100),
    show_on_map     BOOLEAN DEFAULT TRUE,        -- Auf Karte sichtbar
    verified_at     DATETIME DEFAULT NULL,
    created_at      DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id),
    FOREIGN KEY (breed_id) REFERENCES breeds(id)
);

Tabelle: breeder_documents

CREATE TABLE breeder_documents (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id         INTEGER NOT NULL,
    document_type   VARCHAR(50) NOT NULL,
    -- Werte: 'vdh_membership', 'breeding_permit'
    file_path       VARCHAR(255) NOT NULL,
    uploaded_at     DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

Tabelle: litters (Würfe)

CREATE TABLE litters (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    breeder_id      INTEGER NOT NULL,            -- FK auf breeder_profiles
    breed_id        INTEGER NOT NULL,
    father_name     VARCHAR(100),
    mother_name     VARCHAR(100),
    birth_date      DATE,
    expected_date   DATE,                        -- Bei geplantem Wurf
    total_puppies   INTEGER,
    available_count INTEGER,
    description     TEXT,
    health_tests    TEXT,                        -- HD, ED, Augen etc. als JSON
    price_range     VARCHAR(50),                 -- Optional, z.B. "1200-1500 €"
    status          VARCHAR(20) DEFAULT 'planned',
    -- Werte: 'planned', 'born', 'available', 'closed'
    visible         BOOLEAN DEFAULT TRUE,        -- Züchter steuert Sichtbarkeit
    visible_until   DATE DEFAULT NULL,           -- Optional: Ablaufdatum
    created_at      DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (breeder_id) REFERENCES breeder_profiles(id),
    FOREIGN KEY (breed_id) REFERENCES breeds(id)
);

Tabelle: puppies (Einzelne Welpen — optional pro Wurf)

CREATE TABLE puppies (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    litter_id       INTEGER NOT NULL,
    name            VARCHAR(100),                -- Optional
    gender          VARCHAR(10),                 -- 'male', 'female'
    color           VARCHAR(50),
    chip_number     VARCHAR(50),
    birth_weight    FLOAT,                       -- Gramm
    status          VARCHAR(20) DEFAULT 'available',
    -- Werte: 'available', 'reserved', 'adopted'
    show_status     BOOLEAN DEFAULT TRUE,        -- Züchter entscheidet ob Status sichtbar
    notes           TEXT,                        -- Interne Notiz des Züchters
    created_at      DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (litter_id) REFERENCES litters(id)
);

Tabelle: puppy_weights (Gewichtsverlauf Welpen)

CREATE TABLE puppy_weights (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    puppy_id    INTEGER NOT NULL,
    weight_g    FLOAT NOT NULL,
    measured_at DATE NOT NULL,
    FOREIGN KEY (puppy_id) REFERENCES puppies(id)
);

Tabelle: breeder_inquiries (Interessenten)

CREATE TABLE breeder_inquiries (
    id              INTEGER PRIMARY KEY AUTOINCREMENT,
    litter_id       INTEGER NOT NULL,
    sender_id       INTEGER NOT NULL,            -- User der anfrägt
    message         TEXT NOT NULL,
    status          VARCHAR(20) DEFAULT 'new',
    -- Werte: 'new', 'replied', 'reserved', 'closed'
    created_at      DATETIME DEFAULT CURRENT_TIMESTAMP,
    FOREIGN KEY (litter_id) REFERENCES litters(id),
    FOREIGN KEY (sender_id) REFERENCES users(id)
);

3. Registrierungsprozess

Schritt 1 — Antrag stellen (Frontend)

Der User navigiert zu "Mein Profil → Züchter werden".

Formular:

Zwingername *
Zuchtverein / Organisation *
Rasse * (Dropdown aus breeds-Tabelle)
VDH-Mitglied? (Ja / Nein)
Stadt / Region *
Website (optional)
Kurze Vorstellung (Freitext)

Dokumente hochladen:
[ ] VDH-Mitgliedsausweis (PDF oder Bild)
[ ] Zuchtzulassung des Vereins (PDF oder Bild)
→ Mindestens eines der beiden Dokumente erforderlich

[ ] Ich bestätige, dass meine Angaben korrekt sind
[ ] Ich habe die Nutzungsbedingungen für Züchter gelesen

[Antrag absenden]

Schritt 2 — Backend-Verarbeitung

# POST /api/breeder/apply

@router.post("/breeder/apply")
async def apply_for_breeder(
    current_user: User = Depends(get_current_user),
    form_data: BreederApplicationForm,
    documents: List[UploadFile]
):
    # Validierung
    if current_user.breeder_status == 'pending':
        raise HTTPException(400, "Antrag bereits gestellt")
    if current_user.role == 'breeder':
        raise HTTPException(400, "Bereits verifizierter Züchter")
    if len(documents) == 0:
        raise HTTPException(400, "Mindestens ein Dokument erforderlich")

    # Dokumente speichern (nicht öffentlich erreichbar)
    for doc in documents:
        save_path = f"/private/breeder_docs/{current_user.id}/{doc.filename}"
        save_document(doc, save_path)
        db.add(BreederDocument(
            user_id=current_user.id,
            document_type=detect_doc_type(doc.filename),
            file_path=save_path
        ))

    # Profil-Entwurf anlegen
    db.add(BreederProfile(
        user_id=current_user.id,
        kennel_name=form_data.kennel_name,
        breed_id=form_data.breed_id,
        ...
    ))

    # Status setzen
    current_user.breeder_status = 'pending'
    db.commit()

    # Admin benachrichtigen
    await notify_admin_new_breeder_application(current_user, form_data)

    return {"message": "Antrag eingereicht. Du wirst per E-Mail benachrichtigt."}

Schritt 3 — Admin-Benachrichtigung

E-Mail an René:

Betreff: [Banyaro] Neuer Züchter-Antrag — {Zwingername}

Neuer Antrag von: {Username} ({E-Mail})
Zwingername: {Zwingername}
Rasse: {Rasse}
Verein: {Verein}
VDH: Ja/Nein
Dokumente: {Anzahl} hochgeladen

→ Im Admin-Bereich prüfen: banyaro.app/admin/breeders

Schritt 4 — Admin-Bereich (Prüfung)

Route: /admin/breeders

Ansicht je Antrag:

  • Antragsdaten vollständig anzeigen
  • Dokumente inline anzeigen (PDF-Viewer oder Bild)
  • Buttons: [Freischalten] | [Ablehnen]
  • Freitextfeld für Ablehnungsgrund (wird per E-Mail gesendet)
# POST /api/admin/breeder/{user_id}/approve

@router.post("/admin/breeder/{user_id}/approve")
async def approve_breeder(
    user_id: int,
    admin: User = Depends(require_admin)
):
    user = db.get(User, user_id)
    user.role = 'breeder'
    user.breeder_status = 'approved'

    profile = db.query(BreederProfile).filter_by(user_id=user_id).first()
    profile.verified_at = datetime.utcnow()
    db.commit()

    await send_breeder_approval_email(user)
    return {"message": "Züchter freigeschaltet"}


# POST /api/admin/breeder/{user_id}/reject

@router.post("/admin/breeder/{user_id}/reject")
async def reject_breeder(
    user_id: int,
    reason: str,
    admin: User = Depends(require_admin)
):
    user = db.get(User, user_id)
    user.breeder_status = 'rejected'
    db.commit()

    await send_breeder_rejection_email(user, reason)
    return {"message": "Antrag abgelehnt"}

Schritt 5 — User-Benachrichtigung

Bei Freischaltung:

Betreff: Willkommen als Züchter bei Banyaro! 🐾

Hallo {Username},

dein Züchter-Profil wurde erfolgreich verifiziert.
Ab sofort hast du Zugang zu allen Züchter-Features:

✅ Züchter-Profil auf der Karte
✅ Wurf-Verwaltung
✅ Wurfankündigungen
✅ Interessenten-Nachrichten

→ Jetzt Profil vervollständigen: banyaro.app/breeder/profile

Viel Erfolg mit deiner Zucht!
Das Banyaro-Team

Bei Ablehnung:

Betreff: Dein Züchter-Antrag bei Banyaro

Hallo {Username},

leider konnten wir deinen Antrag aktuell nicht bestätigen.

Grund: {Ablehnungsgrund}

Du kannst einen neuen Antrag stellen sobald du die
fehlenden Dokumente vorliegen hast.

Bei Fragen: mail@motocamp.de

4. Züchter-Features im Detail

4.1 Öffentliches Züchter-Profil

Route: /breeder/{kennel_name}

Anzeige:

  • Zwingername + verifiziertes Badge ✓
  • Rasse (verlinkt auf Wiki-Eintrag)
  • Zuchtverein
  • Stadt / Region
  • Beschreibung
  • Website-Link
  • Aktive Wurfankündigungen
  • Kontakt-Button → öffnet internes Chat

Auf der Karte:

  • Eigener Marker-Typ "Züchter" (unterschiedliches Icon)
  • Popup mit Zwingername, Rasse, Link zum Profil

4.2 Wurf-Verwaltung

Route: /breeder/litters

Funktionen:

  • Neuen Wurf anlegen (geplant oder bereits geboren)
  • Einzelne Welpen anlegen (optional)
  • Gewichtsverlauf pro Welpe erfassen
  • Status pro Welpe: verfügbar / reserviert / abgegeben
  • Züchter steuert selbst was öffentlich sichtbar ist
  • Wurf archivieren (bleibt als Referenz erhalten)

4.3 Wurfankündigung (öffentlich)

Route: /litters (öffentliche Übersicht für alle User)

Filter:

  • Nach Rasse
  • Nach PLZ / Umkreis
  • Nach Status (geplant / verfügbar)

Detailseite pro Wurf:

  • Elterntiere (Name, Foto optional)
  • Geburtsdatum / erwarteter Termin
  • Anzahl Welpen gesamt / verfügbar
  • Gesundheitsuntersuchungen der Eltern
  • Preisrahmen (optional)
  • Fotos
  • [Nachricht senden]-Button

4.4 Interessenten-Verwaltung

Eingehende Anfragen erscheinen im internen Chat-System. Züchter kann Status der Anfrage setzen: neu / beantwortet / reserviert / abgeschlossen.


4.5 Läufigkeits-Tracker (Züchter-Erweiterung)

Der bestehende Läufigkeits-Tracker wird für Züchter erweitert:

  • Deckdatum erfassen
  • Automatische Berechnung des erwarteten Wurftermins (63 Tage Trächtigkeitsdauer)
  • Erinnerung: "Wurftermin in 7 Tagen"
  • Direkte Verknüpfung: Läufigkeit → neuen Wurf anlegen

5. API-Endpunkte Übersicht

# Registrierung
POST   /api/breeder/apply                    Antrag stellen
GET    /api/breeder/status                   Antragsstatus abfragen

# Admin
GET    /api/admin/breeders/pending           Offene Anträge
POST   /api/admin/breeder/{id}/approve       Freischalten
POST   /api/admin/breeder/{id}/reject        Ablehnen

# Profil
GET    /api/breeder/{kennel_name}            Öffentliches Profil
PUT    /api/breeder/profile                  Profil bearbeiten

# Würfe
GET    /api/litters                          Öffentliche Übersicht
GET    /api/litters/{id}                     Wurf-Detail
POST   /api/breeder/litters                  Neuen Wurf anlegen
PUT    /api/breeder/litters/{id}             Wurf bearbeiten
DELETE /api/breeder/litters/{id}             Wurf archivieren

# Welpen
POST   /api/breeder/litters/{id}/puppies     Welpe anlegen
PUT    /api/breeder/puppies/{id}             Welpe bearbeiten
POST   /api/breeder/puppies/{id}/weight      Gewicht erfassen

# Karte
GET    /api/map/breeders                     Züchter für Karte

6. Berechtigungsprüfung (Middleware)

def require_breeder(current_user: User = Depends(get_current_user)):
    if current_user.role not in ('breeder', 'admin'):
        raise HTTPException(
            status_code=403,
            detail="Diese Funktion ist nur für verifizierte Züchter verfügbar"
        )
    return current_user

Verwendung:

@router.post("/breeder/litters")
async def create_litter(
    breeder: User = Depends(require_breeder),
    ...
):

7. Sicherheit & Datenschutz

  • Hochgeladene Dokumente werden außerhalb des öffentlichen Webroot gespeichert
  • Nur Admin kann Dokumente abrufen (authentifizierter Endpunkt)
  • Dokumente werden nach Prüfung nicht dauerhaft benötigt — optional nach 90 Tagen löschen
  • Standort des Züchters: nur Stadt/Region öffentlich, keine genaue Adresse
  • Datenschutzerklärung um Züchter-Daten ergänzen

8. Umsetzungsreihenfolge (empfohlen)

Schritt Was Aufwand
1 DB-Migration (users, breeder_profiles, breeder_documents) Klein
2 Antrag-Formular Frontend + Dokument-Upload Mittel
3 Admin-Bereich: Anträge prüfen + freischalten Mittel
4 E-Mail-Benachrichtigungen (Antrag, Freischaltung, Ablehnung) Klein
5 Öffentliches Züchter-Profil + Karten-Marker Mittel
6 Wurf-Verwaltung (CRUD) Mittel
7 Öffentliche Wurfankündigung + Filtersuche Mittel
8 Welpen-Verwaltung + Gewichtsverlauf Klein
9 Läufigkeits-Tracker Erweiterung Klein
10 Interessenten-Chat-Integration Klein

Gesamtaufwand geschätzt: 35 Tage mit Claude Code