Files
Jürgen MummertandClaude Fable 5.1 3aad6aa559 Store the display fields of events as virtual fields
- Move 29 display-only event fields (subtitles, audience, tags, prices,
  price types, organizer) from columns into the jsonData storage; fields
  used for filtering, joins and calculations stay real columns
- Add EventFieldsToVirtualMigration, which copies the values and drops
  the columns on contao:migrate
- Read and write the fields in the import, the preview and the booking
  through EventRowCodec; other bundles' keys in the storage are kept
- Validate prices in a save callback instead of the percent check that
  rejected every price above 100; accept a decimal comma
- Do not report events as changed after a backend save: ignore the
  spelling of HTML entities in teaser and topics and the order of the
  speakers

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-27 20:42:47 +02:00

198 lines
21 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# W&K-Sync Bundle
Verbindet **Contao 6** mit der Seminarverwaltung **W&K** (Windows-Software ohne Schnittstelle) über CSV-Dateien:
1. **Import**: Die Redaktion exportiert in W&K die Seminarliste als CSV und lädt sie im Contao-Backend hoch. Vor dem Schreiben zeigt eine **Vorschau**, was sich ändert (neu, geändert, storniert, unverändert, übersprungen, Warnungen). Erst nach Bestätigung werden die Veranstaltungen im Kalender angelegt bzw. aktualisiert.
2. **Online-Anmeldung**: Besucher melden sich über ein Formular des **Formulargenerators** zu einer Veranstaltung an. Das Bundle blendet je nach Veranstaltung Felder ein und aus, prüft Plätze und Anmeldeschluss, speichert die Anmeldung, verschickt die passende Benachrichtigung (Notification Center 2) und leitet auf die Bestätigungsseite weiter.
3. **Posteingang**: Anmeldungen und Kontaktanfragen werden in Ordnern bearbeitet (Neu, Download-Liste, Bereits bearbeitet, Ignoriert/Spam, Exportiert).
4. **Export**: Die Download-Liste wird als CSV für den W&K-Import erzeugt (ISO-8859-1, Semikolon, feste Spalten), die Datensätze werden als exportiert markiert und die freien Plätze der Veranstaltungen reduziert.
- Contao **6**, PHP **8.5**, Doctrine DBAL **4**, MySQL 8 oder MariaDB
- Keine Abhängigkeiten außer dem Contao-Core und dem Kalender-Bundle
- **Notification Center 2** wird für Benachrichtigungen empfohlen (Typ „W&K-Anmeldung“), ist aber nicht erforderlich; ohne Notification Center werden Anmeldungen gespeichert und protokolliert, aber keine Mails verschickt (Stand September 2026 unterstützt das Notification Center Contao 6 noch nicht)
- Newsletter-Anmeldung (Double-Opt-in) mit dem Newsletter-Bundle, optional
---
## Installation
```bash
composer require mummert/wk-sync-bundle
```
Das Paket liegt nicht auf Packagist; das Repository in der `composer.json` des Projekts eintragen:
```json
"repositories": [
{ "type": "vcs", "url": "ssh://git@git.mummert.media:3245/mummert/wk-sync-bundle.git" }
]
```
Danach die Datenbank aktualisieren:
```bash
vendor/bin/contao-console contao:migrate
```
---
## Einrichtung
1. **Kategorien anlegen** – Backend „W&K-Tools → Kategorien“ oder die Standardkategorien des TPI Moritzburg per Befehl:
```bash
vendor/bin/contao-console wk-sync:categories:seed
```
Jede Kategorie hat eine **Art** (Schule, Evangelische Schulen, Elementarbereich, Gemeinde, Gottesdienst mit Kindern, Tagungen, Ausbildung, Mentoring, Sonstiges) und die **W&K-Bezeichnungen** (Werte des Felds `kbsgebiet`, eine pro Zeile). Beim Import zählen genaue Treffer zuerst (alle Kategorien mit dieser Bezeichnung, z. B. „Alle Schularten“ für Grundschule *und* weiterführende Schule), sonst der erste Teilstring-Treffer in Sortierreihenfolge. Die erste zugeordnete Kategorie wird Hauptkategorie und steuert die Formularregeln und die Benachrichtigung.
2. **Anmeldeformular anlegen** – im Formulargenerator, am einfachsten per Befehl (legt alle Felder mit passenden Namen, Optionen und CSS-Klassen an und trägt es im Kalender ein):
```bash
vendor/bin/contao-console wk-sync:form:create --calendar=1
```
Die Feldnamen entsprechen den Spalten von `tl_wk_booking` (`gender`, `firstname`, `lastname`, `street`, `postal`, `city`, `email`, `phone`, `position`, `birthday`, `teacherPosition`, `schoolName`, `schoolCity`, `vegetarian`, `overnightStay`, `addDifferentInvoiceAddress`, `invoice…`, `addNotes`, `notes`, `newsletter`, `privacyAccepted`, `agbAccepted`). Spamschutz über das ALTCHA-Feld des Contao-Kerns. Das Formular braucht **keine** Weiterleitungsseite und **kein** „Daten in Tabelle speichern“ – beides übernimmt das Bundle.
Der Befehl legt bei jedem Aufruf ein neues Formular an (Alias `wk-anmeldung`, bei Bedarf nummeriert); bei der Installation passiert nichts automatisch. Ein von Hand gebautes Formular funktioniert genauso, solange die Feldnamen stimmen – welches Formular gilt, entscheidet allein die Auswahl „Anmeldeformular“ in den Einstellungen.
3. **Einstellungen anlegen** – Backend „W&K-Tools → Einstellungen → Neu“, ein Eintrag je Kalender:
- Kalender, in den importiert wird und dessen Veranstaltungen buchbar sind
- Anmeldeformular, Seite mit dem Anmeldeformular, Bestätigungsseite, Seite mit dem Anfrageformular
- Anmeldeschluss (Tage vor Beginn, Standard 1) und angezeigter Anmeldeschluss (Standard 31)
- Eigene Veranstaltungsorte (Standard „TPI Moritzburg“; Orte, die so beginnen, gelten als „im eigenen Haus“)
- Autor importierter Veranstaltungen, Mitgliedergruppen für Kursleitung und Referenten (leer = keine Mitglieder anlegen, die Namen aus W&K bleiben dann unberücksichtigt)
- Newsletter-Verteiler und Aktivierungsseite (optional)
- Benachrichtigungen: Standard sowie je Bedingung 1–12 (siehe unten); die Auswahl zeigt den Titel mit ID. Das Formular erklärt die Bedingungen mit einer Matrix (Art × ein-/mehrtägig); solange keine Benachrichtigung vom Typ „W&K-Anmeldung“ existiert, steht dort ein Hinweis mit Link zum Notification Center.
`wk-sync:form:create --calendar=1` legt den Eintrag bei Bedarf mit an. Am Kalender selbst wird nichts konfiguriert.
Beim Bearbeiten zeigt die Legende **Prüfung der Einrichtung** oben im Formular, ob die Verkettung stimmt: Feldnamen des Anmeldeformulars (fehlen Vorname, Nachname oder E-Mail, ist das ein Fehler), Modul „W&K-Anmeldeformular“ auf der Anmeldeseite, Bestätigungsseite, Anfrageformular mit Zieltabelle `tl_wk_inquiry`, Modul „W&K-Anmeldestatus“ auf der Weiterleitungsseite des Kalenders und mindestens eine zugeordnete Benachrichtigung. Grün, wenn alles passt, rot mit konkreten Hinweisen sonst.
4. **Seiten und Module**
- Seite **Kurs** (`kurs`): Modul *Event-Reader* des Kerns plus Modul **W&K-Anmeldestatus** (freie Plätze, Anmeldeschluss, Link „Jetzt anmelden“, Hinweis bei externer Anmeldung, geschlossenem Teilnehmerkreis usw.)
- Seite **Anmeldung** (`kurs/anmeldung`): Modul **W&K-Anmeldeformular** – wird wie der Event-Reader mit dem Alias der Veranstaltung aufgerufen (`/kurs/anmeldung/<alias>`)
- Seite **Anmeldung eingegangen**: Text; der Link enthält `?booking=<Token>`, damit Folgeformulare (z. B. Workshop-Auswahl) die Anmeldung kennen (siehe unten)
- Seite **Anfrage**: Formular des Formulargenerators mit „Daten in Tabelle speichern“ → `tl_wk_inquiry` (Feldnamen `gender`, `academicTitle`, `firstname`, `lastname`, `street`, `postal`, `city`, `email`, `phone`, `subject`, `notes`, `newsletter`, `privacy`). Der Link vom Anmeldestatus hängt `?event=<ID>` an; das Bundle trägt die Veranstaltung automatisch ein.
- Kalender-Module des Kerns (Eventliste, Kalender, Eventmenü) erhalten das Feld **W&K-Kategorien**, um nur Veranstaltungen bestimmter Kategorien zu zeigen.
5. **Benachrichtigungen** (Notification Center 2): Benachrichtigungen vom Typ **W&K-Anmeldung** anlegen (System → Benachrichtigungen) und unter W&K-Tools → Einstellungen den Bedingungen zuordnen. Ohne zugeordnete Benachrichtigung wird die Anmeldung gespeichert, aber keine Mail verschickt (Warnung im Systemprotokoll). Tokens: `##recipient_email##`, `##subscription_*##` (alle Felder der Anmeldung, Auswahlfelder zusätzlich als `##subscription_gender_label##` usw., Ja/Nein-Felder als `##subscription_vegetarian_label##`), `##event_*##` (alle Felder der Veranstaltung, Datumsfelder formatiert), `##event_datetime##`, `##event_link##`, `##event_categories##`, `##event_speakers##`, `##subscription_event_wk_id##`, `##subscription_confirmation_page_url##`, `##calendar_*##`, `##admin_email##`. Die bisherigen Token-Namen `event_organizerCity` und `event_internalEventNumber` bleiben erhalten.
6. **Benutzergruppen**: Zugriff auf die Module des Bereichs „W&K-Tools“ (Import, Posteingang, Anmeldungen, Kontaktanfragen, Exporte, Kategorien, Einstellungen, Protokoll) freigeben. Anmeldungen werden ausschließlich dort bearbeitet, nicht am Event im Kalender.
---
## Import aus W&K
**Backend → W&K-Tools → Import → Datei hochladen.** Erwartet wird der Seminarexport aus W&K (Semikolon, Anführungszeichen, Windows-1252 oder UTF-8, mehrzeilige Felder). Wichtig: in W&K das größtmögliche Zeitintervall exportieren – Seminare, die nicht in der Datei stehen, werden **storniert und ausgeblendet** (nur Veranstaltungen des Kalenders, die eine W&K-Nummer tragen; manuell angelegte Veranstaltungen ohne W&K-Nummer bleiben unberührt).
Regeln beim Import:
| W&K | Contao | Bemerkung |
|---|---|---|
| `kbnr` | `wkId` | Muss mit einer führenden Null beginnen, sonst wird der Datensatz übersprungen |
| `kbvsnr` | `wkInternalEventNumber` | **Leer = nicht veröffentlichen** (übersprungen); „keine“ wird zu leer |
| `kbtitel`, `kbutitel`, `kbrtitel` | `title`, `wkSubTitle`, `wkSubTitleAdditional` | Titel ist Pflicht |
| `kbanreise`, `kbabreise`, `kbuanreise`, `kbuabreise` | `startDate`, `endDate`, `startTime`, `endTime`, `addTime` | Datum `TT.MM.JJJJ`, Uhrzeit `HH.MM`; wie im Contao-Kern (ganztägig bis 23:59:59) |
| `kbstatus1` | `wkState` | 1–5; **5 (Storno) blendet aus**. Manuelles Ausblenden bleibt erhalten, bis sich der W&K-Status ändert |
| `kbsgebiet` | `wkCategoryText`, `wkCategories`, `wkCategoryPrimary` | Keine Zuordnung möglich → Storno + Hinweis |
| `kbpmemo`, `kbzusatz` | `teaser`, `wkTopics` | Text wird HTML-sicher in Absätze bzw. Listen (`- `) umgewandelt |
| `kbwmemo` | `wkTags` | Kommagetrennt, dedupliziert |
| `kbkln1`, `kbmref` | `wkInstructor`, `wkSpeakers` | Mitglieder nach Nachname; werden nur angelegt, wenn in den Einstellungen Mitgliedergruppen gesetzt sind (die Vorschau kündigt das an). „N.N.“ wird ignoriert |
| `kbanzmax`, `kbanztat` | `wkMaxPlaces`, `wkPlacesLeft` | Freie Plätze = max − belegt |
| `kbkp1`–`kbkp6`, `kbktxp1`–`kbktxp6` | `wkPrice1`–`6`, `wkPriceType1`–`6` | Dezimalzahlen, gespeichert mit zwei Nachkommastellen |
| `kbkd*` | `wkOrganizer*` | Straße und Landeskennzahl richtig zugeordnet (war vertauscht) |
| `kbkdemail` | `wkOrganizerEmail` | E-Mail **oder** URL (Veranstaltungsart 3: Anmeldung beim Veranstalter) |
| `kbvort`, `kbgruppe`, `kbvsart`, `kbztermin` | `location`, `wkAudience`, `wkEventType`, `wkAdditionalDates` | „geschlossener Teilnehmerkreis“ verhindert die Online-Anmeldung |
Das Euro-Zeichen, das W&K als „Ç“ exportiert, wird im Umfeld von Zahlen korrigiert. Aliase werden nur beim Anlegen erzeugt und bleiben danach stabil. Ein erneuter Import derselben Datei meldet alle Seminare als unverändert, auch nachdem eine Veranstaltung im Backend gespeichert wurde: Die Schreibweise von HTML-Entities in Teaser und Themen (`&#34;` statt `&quot;`) und die Reihenfolge der Referenten gelten nicht als Änderung. Jeder Import wird protokolliert (W&K-Tools → Protokoll); die Datei liegt unter `var/wk-sync/imports/`. Alte Import- und Exportdateien räumt `vendor/bin/contao-console wk-sync:purge --days=90` auf (z. B. per Cronjob); die Protokolleinträge bleiben erhalten.
Auf der Konsole:
```bash
vendor/bin/contao-console wk-sync:import files/Import/stva.csv 1 # Vorschau
vendor/bin/contao-console wk-sync:import files/Import/stva.csv 1 --apply # anwenden
```
---
## Formularregeln
Welche Felder das Anmeldeformular zeigt, hängt von der Hauptkategorie (Art), dem Veranstaltungsort und der Veranstaltungsnummer ab:
- **Lehrertätigkeit** (`teacherPosition`, `schoolName`, `schoolCity`): nur bei den Arten Schule und Evangelische Schulen.
- **Abweichende Rechnungsanschrift**: bei Schulkursen nur, wenn „Ich unterrichte an einer Schule in sonstiger Trägerschaft“ gewählt ist; sonst immer.
- **Übernachtung**: nur bei mehrtägigen Veranstaltungen. Bei EXTP-Kursen und Elementarkursen im eigenen Haus erscheint stattdessen der Hinweis, dass Übernachtung und Verpflegung direkt im Gästehaus gebucht werden (Erklärungsfeld mit CSS-Klasse `wk-slot-no-overnight-stay`). Ausnahme: Lehrerkurse außer Haus zeigen das Feld.
- **Vegetarisch**: nicht bei Online-Kursen und nicht bei mehrtägigen EXTP-Kursen oder Elementarkursen (außer Tagungen); Lehrerkurse außer Haus zeigen das Feld.
- Abhängige Felder (Schule, Rechnungsanschrift, Nachricht) werden serverseitig nur validiert und gespeichert, wenn ihr steuerndes Feld sichtbar ist und den passenden Wert hat; ein kleines Skript (`bundles/contaowksync/wk-sync.js`) blendet sie im Browser ein und aus.
Zusätzlich prüft das Bundle vor dem Anzeigen und noch einmal beim Absenden: veröffentlicht, nicht storniert, kein geschlossener Teilnehmerkreis, keine externe Anmeldung (Veranstaltungsart 3 mit E-Mail/URL), Anmeldeschluss, freie Plätze (W&K-Stand minus offene Anmeldungen, zusätzlich maximale Plätze).
## Benachrichtigungs-Bedingungen
Vorrang: Benachrichtigung an der Veranstaltung (Legende „Anmeldung“) → erste erfüllte Bedingung in den Einstellungen → Standard-Benachrichtigung der Einstellungen. Eine zutreffende Bedingung ohne Zuordnung wird übersprungen.
| Nr. | Bedingung |
|---|---|
| 1 | Online-Kurs (Veranstaltungsort „Online“) |
| 2 / 5 | Tages- / Mehrtagesveranstaltung, Art Schule, Nummer beginnt mit EXTP |
| 3 / 7 | Tages- / Mehrtagesveranstaltung, Art Gemeinde, Gottesdienst mit Kindern oder Tagungen |
| 4 / 6 | Tages- / Mehrtagesveranstaltung, Art Elementarbereich |
| 8 / 9 | Tages- / Mehrtagesveranstaltung, Art Evangelische Schulen |
| 10 / 11 | Tages- / Mehrtagesveranstaltung, Art Mentoring |
| 12 | Mehrtägige Ausbildung |
| – | Alles andere (Schule ohne EXTP-Nummer, eintägige Ausbildung, Sonstiges wie AusZEIT) → Standard-Benachrichtigung |
Die Bedingungen sind bewusst fest im Code hinterlegt (`NotificationRouter`), weil sie kundenspezifisch sind; die Einstellungen zeigen sie als Matrix mit Erklärungen an jedem Auswahlfeld. Ohne Notification Center werden Anmeldungen gespeichert, aber keine Mails verschickt (Warnung im Systemprotokoll).
## Folgeformulare (z. B. Workshop-Auswahl)
Die Bestätigungsseite wird mit `?booking=<Token>` aufgerufen. Formulare des Formulargenerators auf dieser Seite erhalten beim Absenden automatisch die Felder `subscription` (ID der Anmeldung), `subscription_email`, `subscription_firstname`, `subscription_lastname`, `event`, `event_title` und `event_wk_id` – in Benachrichtigungen als `##form_subscription_email##` usw. verwendbar. Eine eigene Bestätigungsseite lässt sich je Veranstaltung hinterlegen.
## Posteingang und Export
**W&K-Tools → Posteingang.** Neue Anmeldungen und Kontaktanfragen werden gesichtet und einzeln oder gesammelt verschoben. Doppelte Anmeldungen (gleiche E-Mail-Adresse, gleiche Veranstaltung) werden markiert. „Bearbeiten“ öffnet den Datensatz unter W&K-Tools → Anmeldungen bzw. W&K-Tools → Kontaktanfragen; dort lassen sich alle Angaben korrigieren (Liste mit Filter nach Veranstaltung und Ordner, Suche nach Name/E-Mail; der Geburtstag wird wie im Formular als `TT.MM.JJJJ` gespeichert, so wie W&K es erwartet). Beim Löschen einer Veranstaltung werden ihre Anmeldungen mitgelöscht.
**Export**: In der Download-Liste erzeugt „Download-Liste für W&K exportieren“ die Datei `JJJJ-MM-TT_website_kontaktanfragen_wk.csv` mit den Spalten `KBNR;KNKDAN;KNKDTITEL;KNKDN2;KNKDN1;KNKDSTR;KNKDPLZ;KNKDORT;KNGEBDAT;KDEMAIL;KDTEL1;KDFREI01;KDFREI02;KDFREI03;KDFREI04;KNUKAT;KNVEG;schoolName;schoolCity`. Kontaktanfragen stehen ohne Kursnummer in der Datei. Die Datensätze werden als „Exportiert“ markiert, die freien Plätze der Veranstaltungen um die Zahl der Anmeldungen reduziert (bis zum nächsten W&K-Import), und die Datei bleibt unter W&K-Tools → Exporte zum erneuten Download (mehrere Exporte am selben Tag tragen den gleichen Dateinamen, liegen auf dem Server aber mit Zeitstempel getrennt unter `var/wk-sync/exports/`).
## Berechtigungen
Alle Unterpunkte von „W&K-Tools“ sind normale Contao-Backend-Module und werden je Benutzergruppe unter **Benutzergruppen → Erlaubte Module** freigeschaltet oder ausgeblendet (Gruppe „W&K-Tools“: Import, Posteingang, Anmeldungen, Kontaktanfragen, Exporte, Kategorien, Einstellungen, Protokoll). Was nicht freigeschaltet ist, erscheint nicht in der linken Spalte und ist auch per URL nicht aufrufbar. Wie bei den Tabellen des Contao-Kerns gilt darüber hinaus: Unter **Tabellenrechte** (Spalten „Anlegen“, „Bearbeiten“, „Löschen“) legt die Gruppe fest, ob sie Datensätze der Tabellen `tl_wk_booking`, `tl_wk_inquiry`, `tl_wk_category` und `tl_wk_config` anlegen, bearbeiten und löschen darf, und unter **Erlaubte Felder** sind alle Felder dieser Tabellen sowie die W&K-Felder der Veranstaltungen einzeln freigebbar. Zum Bearbeiten von Anmeldungen braucht eine Gruppe also das Modul „Anmeldungen“, unter Tabellenrechte „Bearbeiten“ für `tl_wk_booking` und die gewünschten Felder; ein Zugriff auf den Kalender ist nicht nötig (die Anmeldungen hängen bewusst nicht als Untertabelle an den Veranstaltungen). Eine typische Redaktionsgruppe erhält Posteingang, Anmeldungen, Kontaktanfragen und Exporte plus die Felder von `tl_wk_booking` und `tl_wk_inquiry`; Import, Kategorien und Einstellungen bleiben der Leitung bzw. Administratoren vorbehalten.
## Eigene Templates
- Frontend-Module: `frontend_module/wk_booking_form.html.twig` und `frontend_module/wk_booking_status.html.twig` (Blöcke `booking_header`, `booking_status`, `booking_form` bzw. `status`, `inquiry`).
- Twig-Funktionen für eigene Event-Templates: `wk_booking_info(event.id)` (Verfügbarkeit, freie Plätze, Anmeldeschluss, Links), `wk_event_categories(event)` (Kategorien), `wk_event_people(event)` (Kursleitung, Referenten).
## Sicherheit und Robustheit
- Alle schreibenden Backend-Aktionen laufen über POST mit Request-Token; Ausgaben sind escaped.
- Upload nur `.csv`/`.txt`, Dateien liegen außerhalb des Web-Roots (`var/wk-sync/`).
- Import in einer Transaktion; fehlerhafte Zeilen brechen den Import nicht ab, sondern werden gemeldet.
- Verfügbarkeit wird beim Absenden erneut geprüft (Wettlauf um den letzten Platz).
- Anmeldungen und Anfragen werden nur mit bekannten Spalten gespeichert.
## Contao 6
Das Bundle setzt Contao 6 voraus und nutzt dessen Konventionen:
- **Output-Encoding:** Contao speichert Eingaben unkodiert. Import, Anmeldungen und Export arbeiten durchgehend mit Klartext, die W&K-Datei enthält also nie HTML-Entities. Die Backend-Oberfläche ist Twig (automatisches Escaping); die Kategorienliste liefert ein kodiertes `RecordLabel`.
- **Anzeigefelder:** Die Prüfung der Einrichtung, die Erklärung der Benachrichtigungslogik und der Hinweis bei fehlenden Benachrichtigungen sind `input_field_callback`-Felder ohne Spalte. Sie legen kein virtuelles Feld (`jsonData`) an.
- **Backend-Stylesheet:** `public/wk-sync-backend.css` wird über `contao.backend.custom_css` eingebunden (Icon der Navigationsgruppe, Posteingang, Einstellungen).
- **Veranstaltungsfelder:** Von den 43 `wk…`-Feldern in `tl_calendar_events` sind 29 reine Anzeigefelder **virtuelle Felder** (Untertitel, Bereich, Zielgruppe, Schlagworte, Zusatztermine, Preise und Preisarten, Veranstalter). Contao speichert sie gemeinsam im JSON-Feld `jsonData` statt in je einer Spalte; das spart rund 13 KB der auf 65 KB begrenzten Zeilengröße. Echte Spalten bleiben alle Felder, mit denen gerechnet, gefiltert oder verknüpft wird (`wkId`, `wkState`, `wkInternalEventNumber`, `wkEventType`, `wkMaxPlaces`, `wkPlacesLeft`, `wkImported`, `wkCategories`, `wkCategoryPrimary`, `wkTopics`, `wkInstructor`, `wkSpeakers`, `wkNotification`, `wkConfirmationPage`). Die Liste steht in `Sync\EventFields`.
- Backend, Modelle und Templates (`event.wkSubTitle`, `event.wkPrice1` …) arbeiten unverändert, auch die Backend-Suche findet die Felder.
- Eigene SQL-Abfragen lesen ein virtuelles Feld mit `JSON_UNQUOTE(JSON_EXTRACT(jsonData, '$.wkSubTitle'))`. In PHP liefert `Sync\EventRowCodec::expand($row)` die Zeile mit allen Feldern, `combine($set, $bisherigesJson)` bereitet Werte zum Speichern auf.
- Bestehende Installationen stellt die Migration `EventFieldsToVirtualMigration` bei `contao:migrate` um: Werte in das JSON-Feld kopieren, Spalten entfernen.
- Preise werden als Text mit zwei Nachkommastellen gespeichert (`240.00`). Im Backend sind `240`, `240,50` und `1.240,50` als Eingabe erlaubt.
- **Ohne Ersatz im Core:** Das Backend-Modul (Import, Posteingang, Exporte) ist ein `BE_MOD`-Callback-Modul, das Anmeldeformular wird über `Controller::getForm()` gerendert und die Veranstaltung wird über `Input::get('auto_item')` ermittelt. Alle drei sind in Contao 6 nicht als veraltet markiert.
## Entwicklung
```bash
composer install
vendor/bin/phpunit
```
Die Tests decken CSV-Parsing, Zeilen-Zuordnung, Kategorie-Auflösung, Formularregeln, Benachrichtigungs-Routing, Textaufbereitung und den W&K-Export ab.
Backend-Bausteine, die sich auch anderswo nutzen lassen:
- Anzeigefelder in DCA-Formularen (Prüfung der Einrichtung, Erklärtexte) sind `input_field_callback`-Felder: Feld ohne `inputType` und `sql` mit `eval.doNotShow`, gerendert von einem Listener mit `#[AsCallback(target: 'fields.<feld>.input_field')]`.
- Das Backend-Stylesheet samt Icon der Navigationsgruppe wird in `ContaoWkSyncBundle::prependExtension()` über `contao.backend.custom_css` registriert.