# 2026-09-10

## Fit wie ein Fisch: Mailvorlagen, Buchungsflow, Sammelanmeldung, Warteliste, Listen

**Auftrag:** E-Mail-Vorlagen wie in den anderen Systemen (Versand vorläufig über die bestehende
Adresse, mit Hinweis), Sammelanmeldung für Schulen mit kurz codierten Elternlinks, Formularbuilder
für Einzel-/Schul-/Elternformular, Buchungsflow mit Validierungslink, Standort-Zusatzinfos in den
Mails, Warteliste mit Detailansicht und Automatik, Kontaktdaten und CSV in den Listen.

**Leitentscheidung:** Nichts mit BZ/Schwimmen/Event Center vermischen (Wartbarkeit). Das Fisch-System
bekommt deshalb eigene Vorlagen-Tabelle, eigenen Fallback-Katalog, eigenen Mail- und
Wartelisten-Service — auch wenn die Muster aus den anderen Systemen übernommen sind.

**Ausgangslage:** Es gab im Fisch-System bisher **keinen** Mailversand. `/admin/fisch/email-templates`
und `/admin/fisch/sammelanmeldung` waren Platzhalterseiten, Standorte kannten nur Name/Slug/Sortierung,
die Warteliste konnte nur händisch übernommen werden.

### Datenmodell

Migration `Version20260910150000` (angewandt):

- neu `fisch_email_template` (Typ, Betreff, Inhalt, Absender, optional Standort-Bindung),
  neu `fisch_form_definition` (Formulartyp + Felder als JSON).
- `fisch_pool` +6 Spalten: `address`, `directions`, `parking_info`, `maps_url`, `maps_embed_url`,
  `info_text`.
- `fisch_registration` + `pending_until`, `institution_name`, `extra_fields`.
- `fisch_waitlist_entry` + `offer_token` (unique), `offered_at`, `offer_until`, `offered_course_id`,
  `notes`, `extra_fields`.
- `fisch_bulk_registration` + Status, `token` (unique), Kontaktname, `confirmed_at`,
  `invitation_sent_at`, Notizen, Extrafelder; `fisch_bulk_registration_link` + Elternmail,
  Kindbezeichnung, `sent_at`.

### Services

- `FischMailService` — 32 Platzhalter inklusive `{bad_adresse}`, `{bad_anfahrt}`, `{bad_parkplatz}`,
  `{bad_maps_link}`; rendert Vorlage oder Fallback (`FischFallbackEmailCatalog`) und liefert die
  Quelle mit. Absender: Vorlage, sonst `ADMIN_EMAIL`. Versandfehler werden geloggt, brechen den
  Buchungsvorgang nie ab.
- `FischRegistrationService` — Anmeldung erzeugt `pending` + Token + 72-Stunden-Frist und schickt die
  Validierungsmail; `verify()` bestätigt und schickt die Bestätigungsmail. Storno benachrichtigt und
  bietet den Platz der Warteliste an.
- `FischWaitlistService` — Angebot mit 24-Stunden-Frist, FIFO-Weitergabe je Altersgruppe (unter
  Berücksichtigung bevorzugter Bäder/Zeiten), Annahme, Ablauf, Rücknahme, händisches Übernehmen.
- `FischCapacityService` — `countBooked()` zählt jetzt bestätigte **und** offene Anmeldungen, offene
  Sammelanmeldungs-Reservierungen und laufende Wartelisten-Angebote. Ohne das könnte der
  Validierungslink zu Überbuchungen führen.
- `FischBulkRegistrationService` — Schulcode 8, Elterncode 10 Zeichen aus einem Alphabet ohne
  verwechselbare Zeichen (`I`, `O`, `0`, `1`); Einladung → Bestätigung durch die Schule → Elternlinks.
- `FischFormDefinitionService` — Defaults je Formulartyp, Merge mit gespeicherter Konfiguration,
  Validierung. Kernfelder (Kindname, Geburtsdatum, Elternmail) sind gesperrt, weil Altersgruppe,
  Kapazitätsprüfung und Mailversand daran hängen.

### Admin

- `/admin/fisch/email-templates` — Liste je Typ mit Fallback-Kennzeichnung, Editor mit klickbaren
  Platzhaltern, Vorschau; Warnbanner zur noch fehlenden Fisch-Absenderadresse.
- `/admin/fisch/formulare` — Formularbuilder (Einzel / Schule / Eltern) mit Feldtypen, Pflichtflag,
  Sichtbarkeit und Zurücksetzen auf die Standardfelder.
- `/admin/fisch/sammelanmeldung` — Einladung anlegen, Schul-Link und Elternlinks anzeigen/erneut
  senden, stornieren.
- `/admin/fisch/warteliste` — Kontaktspalte, Status mit Angebotsfrist, Detail-Modal (JSON-Endpunkt),
  „Platz anbieten", Angebot zurücknehmen, „Direkt übernehmen", CSV um Status/Frist erweitert.
- `/admin/fisch/standorte` — Adresse, Anfahrt, Parkplatz, Maps-Link, Maps-Ausschnitt, Hinweistext.
- Kurs-Teilnehmerliste und `/admin/fisch/teilnehmer` — Kontakt-, Adress- und Statusspalten; neuer
  CSV-Export je Kurs (`/admin/fisch/kurse/{id}/teilnehmer/export.csv`, 14 Spalten).

### Öffentlich

Neu: `/booking/fisch/bestaetigen?token=` (Validierung), `/booking/fisch/warteliste/annehmen`
(Platzangebot), `/booking/fisch/schule/{token}` (Schule bucht den Slot),
`/booking/fisch/e/{token}` (Eltern melden das Kind endgültig an). Die Formularfelder kommen aus dem
Builder (`_dynamic_fields.html.twig`).

### Commands

`app:fisch:waitlist-expire` und `app:fisch:expire-pending`, beide mit `--dry-run`, dokumentiert in
`docs/CRONJOBS.md`.

**Test:** `lint:container` und `lint:twig` (34 Dateien) fehlerfrei, `doctrine:schema:validate
--skip-sync` ohne Fisch-Befund (die gemeldeten Mapping-Fehler betreffen ältere Entities anderer
Systeme), alle 9 Mailtypen gerendert (aktuell durchweg Fallback, da noch keine Vorlage in der DB),
Formularvalidierung gegen alle drei Formulartypen, Kapazitätszählung gegen echte Kurse, beide neue
Commands mit `--dry-run`. Zusätzlich `MAILER_DSN=null://null php bin/console app:fisch:e2e-test`:
Anmeldung→Verify, Warteliste Angebot/Claim, Sammelanmeldung Schule→Eltern — grün. Nebenbei Fix:
bei Elternanmeldung über Sammellink wird `usedSlots` vor der Kapazitätsprüfung geflusht, sonst
scheitert die Anmeldung bei voller Reservierung.

**Offen:** Absenderadresse, Vorlagen anlegen, Cronjobs einrichten — siehe `docs/TODO.md`.
Anmeldephase startet planmäßig 2026-09-17.

## Öffentliche URL fit-fisch.de

Parameter `FISCH_PUBLIC_URL` (Symfony: `app.fisch_public_url`): nur für öffentliche
Fisch-Buchungsseiten und Mail-Links (Validierung, Warteliste, Schule, Eltern). Admin und übrige
Systeme bleiben bei `APP_URL`. Fehlt die Env-Variable, greift der Fallback auf `APP_URL`.
Verdrahtet in `FischMailService`, Sammelanmeldung-Admin, Twig-Global `fisch_public_url`
(Portal-Link in der Sidebar).

## Niklas-Feedback umgesetzt (Abend)

Landing-Texte angepasst; Mehrsprachigkeit per Flaggen (DE/EN/TR/UK/AR) auf der öffentlichen
Startseite; Anmeldemaske auch bei geschlossener Phase einsehbar; Teilnehmer-UI mit Handynummer
und Aktionsmenü inkl. E-Mail erneut; Warteliste/Umbuchen mit Altersausnahme für beliebige Kurse;
Altersregler-Hilfe in den Grundeinstellungen; Standard-E-Mail-Vorlagen inkl. Bestätigung je
Standort per `app:fisch:seed-email-templates` angelegt.

## Sprachumschalter: echte Flaggen-SVGs

Emoji-Flaggen (🇩🇪 …) erscheinen unter Windows oft nur als „DE/GB/…“. Umgestellt auf SVG-Dateien
unter `public/images/fisch/flags/` und `<img>` im Layout (`_layout.html.twig`, CSS
`fisch-lang-switcher__flag`).

## Testanmeldung von der Startseite entfernt

CTA „Maske anzeigen“ entfällt bei geschlossener Phase. Testlink über Token in
`/admin/fisch/grundeinstellungen` (Feld + „neuen Token erzeugen“, kopierbarer Link).
Optionaler Env-Fallback `FISCH_TEST_BOOKING_TOKEN`. Migration `Version20260910220000`.

## Teilnehmer-Detailmodal (wie BZ)

Auf „Alle Teilnehmer“ und „Kursteilnehmer“: Auge-Button / Name öffnet Modal mit Kind, Kontakt,
Kurs, Gesundheit/Meta und optionalen Extra-Feldern; leere Felder ausblendbar.
## Schwimmkurse: Checkbox / AGB / Geschwister

Bei `/appointments/admin/courses/` und öffentlicher Buchung:
- Zusatzfelder vom Typ Checkbox werden als Checkbox gerendert (nicht mehr Textfeld)
- AGB-Zustimmung immer Pflicht (+ Seite `/booking/agb`)
- Option „Geschwisterkinder erlauben“ am Kurs; Frontend kann mehrere Kinder mit gleicher Eltern-Mail buchen
- Kind-Name liegt auf `appointment.participant_*`, Duplikat-Check pro Kind+E-Mail+Kurs
- Migration `Version20260914160000`

