Problem Statement
Die Freiberufler-Selbstverwaltung läuft heute über ein Neos-CMS/Flow-Altsystem: ~37.700 Projektpartner pflegen dort Profil-Stammdaten, Skills, Verfügbarkeit, Netzwerk-IDs und Bewerbungstexte, laden private Dokumente hoch, registrieren sich selbst und betreiben Suchagenten für passende Projekte. Die neue SOLCOM-Website muss diese Funktionen übernehmen — „im Prinzip alles, was das Bestandssystem auch hat" — und die Bestandsdaten verlustarm überführen.
Ohne diese Schicht ist das Projektpartnerportal ein reines anonymes Browse-Portal: Projektangebote ansehen, aber kein Konto, kein pflegbares Profil, keine personalisierten Suchagenten. Bestandspartner verlören ihren Zugang und ihre gepflegten Daten.
Solution
Ein neues Custom-Modul (Arbeitsname solcom_projektpartner) bildet die Self-Service-Schicht ab. Das Fachprofil ist eine drupal/profile-Entity vom Typ projektpartner (ADR-0052); die User-Entity bleibt Login-Anker und trägt field_ppp_id. Selbst-Registrierung läuft über einen Webform-Wizard (3 Seiten), der bei Abschluss User + Profil + Rolle atomar anlegt; die laufende Pflege erfolgt über die native Profile-Edit-Form. Private Uploads liegen im private://-Stream mit owner-only-Zugriff. Login per E-Mail (login_emailusername), transaktionale Mails über easy_email. Die Bestandsdaten werden per Migrate-API (SqlBase direkt gegen den Neos-Dump) überführt. Suchagenten kommen als Feature samt Kriterien-Migration mit; gemerkte Projekte werden neu aufgebaut (greenfield).
User Stories
Registrierung & Auth
- Als Freiberufler möchte ich mich über ein dreiseitiges Formular registrieren (Kontakt & Unterlagen / Verfügbarkeit / Qualifikation), damit ich geführt mein Profil anlege.
- Als Freiberufler möchte ich schon auf Seite 1 Unterlagen hochladen und Datenschutz + Nutzungserklärung bestätigen, damit meine Registrierung vollständig ist.
- Als Freiberufler möchte ich nach der Registrierung eine Verifizierungs-Mail erhalten und meinen Account per Klick aktivieren (Double-Opt-in), damit mein Zugang sicher ist.
- Als Freiberufler möchte ich mich künftig mit E-Mail + Passwort einloggen, damit ich mir keinen Benutzernamen merken muss.
- Als Freiberufler möchte ich mein Passwort selbst zurücksetzen können, damit ich nicht auf Support angewiesen bin.
Profil-Pflege
- Als eingeloggter Projektpartner möchte ich alle meine Profilfelder (Person, Kontakt, Verfügbarkeit, Qualifikation) nachträglich bearbeiten, damit meine Daten aktuell bleiben.
- Als Projektpartner möchte ich private Unterlagen hochladen, die ausschließlich ich (und Admins) sehen, damit meine Dokumente DSGVO-konform geschützt sind.
- Als Projektpartner möchte ich ausschließlich mein eigenes Profil sehen und bearbeiten, niemals fremde.
- Als Projektpartner möchte ich mein Konto vollständig löschen können (Recht auf Vergessenwerden), inklusive aller privaten Dateien.
Suchagent & gemerkte Projekte
- Als Projektpartner möchte ich Suchkriterien (Skills + Einsatz-Regionen) hinterlegen und bei passenden neuen Projektangeboten per E-Mail benachrichtigt werden.
- Als migrierter Bestandspartner möchte ich, dass mein bestehender Suchagent weiterläuft, ohne ihn neu anlegen zu müssen.
- Als Projektpartner möchte ich Projektangebote merken, um sie später wiederzufinden.
Redaktion & Betrieb
- Als Redakteur möchte ich weiterhin das Standard-Drupal-Benutzerformular nutzen — das Partner-Profil darf meine Account-Verwaltung nicht verändern.
- Als Betreiber möchte ich die bestehenden Projektpartner aus Neos nach Drupal migrieren, damit Altdaten und Dokumente im neuen Portal verfügbar sind.
- Als Betreiber möchte ich, dass migrierte Partner ein abgelaufenes Passwort haben und beim ersten Login zwingend ein neues setzen müssen, damit keine Alt-Passwörter übernommen werden.
- Als Betreiber möchte ich allen migrierten Partnern zum Go-Live eine Willkommens-Mail mit Einmal-Login-Link schicken, damit sie ihr neues Passwort proaktiv setzen können, ohne auf den ersten Login-Versuch zu warten.
- Als Vertrieb möchte ich über jede Profil-Änderung eines Partners informiert werden (geänderte Felder), damit die Daten in Salesforce aktuell gehalten werden — übergangsweise per Mail.
- Als Qualitätsverantwortlicher möchte ich, dass alle Formulare automatisiert im Browser durchgetestet werden, damit Regressionen vor dem Release auffallen.
Implementation Decisions
Datenmodell (ADR-0052)
Separate drupal/profile-Entity (Bundle projektpartner, Versionen Profile 1.14.0 + Entity 1.6.0, D11-bestätigt). User-Entity = Login-Anker mit field_ppp_id (Integer, unique, nullable). Rolle projektpartner mit Permissions „eigenes Profil ansehen/bearbeiten, eigenen Account löschen". Profilfelder in vier Gruppen:
| Gruppe | Felder (aus Neos-Bestand abgeleitet) |
|---|---|
| Person | Anrede, Titel, Vorname, Nachname, Geburtsdatum, Firma |
| Kontakt | Telefon, Mobil, Website, Straße/Nr., PLZ, Ort, Land (E-Mail = Account-Mail) |
| Verfügbarkeit | Einsatz-Regionen (Mehrfach: alle/AT/CH/DE/andere), Stundensatz (EUR), Verfügbarkeits-Status, Netzwerk-IDs GULP / freelance.de / Xing / freelancermap |
| Qualifikation | Sprachen (Mehrfach), Anschreiben (≤2000 Zeichen), fachliche Schwerpunkte (≤500 Zeichen), Skills |
Consent-Feld(er) speichern Datenschutz- und Nutzungserklärungs-Annahme mit Timestamp + Version (DSGVO-Nachweis). Seite-2- und Seite-3-Felder sind optional.
Registrierungs-Wizard
Webform mit drei Wizard-Pages (Repo-konsistent mit solcom_vacancy_application). Seite 1 „Kontakt & Unterlagen" (Pflicht inkl. Passwort + Wiederholung mit Match-Validierung, Consent-Checkboxen, Upload), Seite 2 „Verfügbarkeit" (optional), Seite 3 „Qualifikationen" (optional). Vor/Zurück-Navigation, Honeypot, Per-Page-Validierung. Der Zwischenstand inkl. hochgeladener Dateien lebt in einer Draft-Submission — das löst das Orphan-File-Problem, da die Dateien an der Submission hängen, nicht an einem noch nicht existierenden User. Ein Final-Submit-Handler legt User + projektpartner-Profil + Rolle atomar an, setzt den Account gesperrt (Aktivierung erst nach E-Mail-Verifizierung) und hängt die Dateien an das frische Profil / den private://-Stream um. Verwaiste Drafts räumt die Webform-Purge-Mechanik per Cron weg. Doppelte E-Mail wird abgefangen.
Private Uploads (owner-only)
Upload-Feld nutzt private:// (z. B. private://ppp/<uid>/); erlaubte Typen + Größenlimit konfiguriert. Bereits hochgeladene Unterlagen werden angezeigt und sind einzeln entfernbar — auch während des Wizards (Draft-File-Handling). Owner-only-Zugriff über einen Profile-Access-Handler bzw. hook_file_download mit Ownership-Check: Datei nur abrufbar, wenn Eigentümer == aktueller Nutzer oder Administrator; fremde Nutzer erhalten 403 (per Test belegt). Kein öffentlicher Pfad.
Profil-Edit, Login, E-Mail-Flows, Rollen-Trennung
- Edit: native
drupal/profile-Edit-Route/user/{uid}/projektpartner— Feldliste einmal definiert, Webform spiegelt nur die Erstanlage. - Login:
login_emailusername(bereits aktiv); Standard-Drupal-Login der Redakteure bleibt funktionsfähig. - E-Mail-Flows:
easy_email(bereits aktiv) für Verifizierung, Willkommen, Passwort-Reset; Templates im SOLCOM-Layout, inconfig/syncversioniert. - Rollen-Trennung: ergibt sich strukturell aus der separaten Profile-Edit-Route — Redakteure/Admins behalten
/user/{uid}/edit, keinhook_form_alter. Der Registrierungs-Wizard ist nur für die Partner-Selbstregistrierung erreichbar.
Account-Löschung (DSGVO)
Self-Service-Löschung mit Bestätigungs-Schritt. user_cancel Methode user_cancel_delete + hook_user_cancel (Pattern wie ApplicationPurger in solcom_vacancy_application): entfernt User-Account, projektpartner-Profil und alle privaten Dateien (File-Entities + physische Dateien + Verzeichnis) unwiderruflich. Profile-Cascade-Default wird nicht blind vertraut, sondern verifiziert und per Test belegt.
Migration (Neos → Drupal)
Migrate-API mit SqlBase direkt gegen den Neos-Dump (in temporäre DB geladen, als zweite DB-Connection registriert) — kein XML-Zwischenschritt, da die Quelle SQL ist. Zwei verkettete Migrations: User (inkl. field_ppp_id) vor Profil, via migration_dependencies + migration_lookup. Mehrwert-Felder aus Neos sind PHP-serialize()-Arrays (DC2Type:array — z. B. languages, Alert-tags) und werden per callback-Process-Plugin deserialisiert. Dateien werden über Joins (user ⋈ userfile ⋈ persistentresource) in den private://-Stream importiert und dem Partner zugeordnet. Passwörter werden nicht migriert; migrierte Accounts erzwingen Passwort-Reset beim ersten Login. Migration ist idempotent (rollback/update-fähig); ein Trockenlauf-/Validierungsbericht liegt vor.
Suchagent (Projekt-Alert)
Pro Partner ein kriterienbasierter Alert (Skills + Regionen), Benachrichtigung bei passenden neuen Projektangeboten per E-Mail (Cron + Matching gegen project_offer). Die ~20.000 Bestands-Kriterien werden migriert — sie sind projekt-unabhängig; das Skill-Vokabular wird auf Drupals field_skills gemappt. Priorität: hoch — der Suchagent ist gesetzt und nicht optional. Das Pattern für den Mail-Versand (Queue + easy_email) deckt sich mit dem bestehenden solcom_project_offer_alert-Digest.
Merkliste / Bookmarks (Upgrade einer bestehenden Lösung)
Es existiert bereits eine Frontend-Merkliste: WatchlistController (Route /projektportal/merkliste), watchlist.js (localStorage solcom.watchlist, max. 5 Einträge, anonym, Cross-Tab-Sync) und die SDCs watchlist-toggle + watchlist-bar. Kein Neubau: für eingeloggte Projektpartner wird die Merkliste backend-persistent gemacht (geräteübergreifend, ohne 5er-Limit) — anonyme Besucher behalten die localStorage-Variante; beim Login werden lokale Einträge in den persistenten Speicher übernommen. Keine Migration von Neos-Bookmarks (kein gemeinsamer Projekt-Schlüssel, Ziele meist abgelaufen). Die bestehenden SDCs und der Controller werden erweitert, nicht ersetzt.
Salesforce-Sync bei Profil-Änderung (Übergang: per Mail)
Profil-Änderungen müssen an Salesforce zurückgespielt werden. Solange Salesforce noch nicht produktiv ist (vgl. RSM-Fallback, ADR-0048), erfolgt die Meldung per Mail an ein SOLCOM-Postfach. Pattern (idiomatisch, deckt sich mit dem bestehenden SalesforceUrlWriteback-QueueWorker): hook_ENTITY_TYPE_update auf das projektpartner-Profil vergleicht $entity->original, ermittelt die geänderten Felder, legt bei relevanter Änderung ein Queue-Item an; ein QueueWorker mit injiziertem MailManager versendet via easy_email eine Diff-Mail (nur geänderte Felder, alt → neu). Entspricht dem Neos-accountchangeflow/userupdatenotification. Der Empfänger ist ein Config-Wert (Adresse vom Fachbereich). Beim späteren Salesforce-Go-Live wird der QueueWorker gegen einen echten SF-PATCH getauscht — der Trigger- und Diff-Mechanismus bleibt.
Bestandsnutzer & Passwort-Erneuerung
Die Neos-Passwörter werden nicht migriert (anderes Hash-Verfahren, DSGVO). Jeder migrierte Account erhält daher ein abgelaufenes Passwort-Datum über drupal/password_policy + Submodul password_policy_expiration (neue Composer-Dependency, analog zur drupal/profile-Entscheidung; D11-Auflösung ohne Konflikt geprüft, Version beim Bau zu pinnen). Beim ersten Login zeigt Drupal dann automatisch „Passwort abgelaufen, bitte neu setzen" und erzwingt den Wechsel — genau das gewünschte Verhalten. Zusätzlich erhalten alle migrierten Partner zum Go-Live eine Willkommens-Mail mit Einmal-Login-Link (Drupal-Core user_pass_reset, easy_email-Template), damit sie ihr Passwort proaktiv setzen können; die Expiration ist das Sicherheitsnetz für verpasste Mails. Als Nebeneffekt gilt die allgemeine Passwort-Richtlinie (Mindestlänge, Komplexität) damit für alle Nutzer.
Frontend-/Formular-Test-Harness (FunctionalJavascript)
Das Repo hat bisher nur PHP-Tests (Kernel + Functional via BrowserTestBase), aber kein JS-/Browser-Test-Setup. Für die mehrstufigen Formulare (Wizard-Navigation, Datei-Upload, Passwort-Match, AJAX) wird ein FunctionalJavascript-Harness aufgesetzt: Chromedriver in DDEV über .ddev/docker-compose.testing.yaml (Image drupalci/webdriver-chromedriver, MINK_DRIVER_ARGS_WEBDRIVER), Tests als WebDriverTestBase bzw. via Drupal Test Traits (ExistingSiteSelenium2DriverTestBase). Damit werden alle Formulare abgedeckt — die neuen Portal-Formulare ebenso wie die bestehenden (contact_form, newsletter_signup, vacancy_application). Playwright ist im Repo per Policy ausgeschlossen; E2E-Sicht-Verifikation läuft zusätzlich über agent-browser.
Testing Decisions
- Access-Control (Pflicht): Owner-only-Datei-Zugriff — fremder Nutzer erhält 403, Eigentümer + Admin erhalten die Datei (Kernel/Functional).
- Profil-Isolation: Partner A kann Profil von Partner B weder sehen noch bearbeiten.
- DSGVO-Löschung: nach Account-Löschung sind keine personenbezogenen Daten oder Dateien des Partners mehr auffindbar (Profil + Files + Verzeichnis).
- Registrierung: atomare Anlage von User+Profil+Rolle; gesperrter Account bis Verifizierung; doppelte E-Mail abgefangen; Passwort-Match-Validierung.
- Migration: Trockenlauf-Validierungsbericht; Idempotenz (Folge-Run ohne Duplikate);
DC2Type:array-Deserialisierung korrekt; Datei-Zuordnung stimmt. - Rollen-Trennung: Nutzer ohne
projektpartner-Rolle sehen unverändert das Standard-Account-Formular. - Salesforce-Sync: Profil-Feld-Änderung erzeugt genau eine Diff-Mail mit den geänderten Feldern; keine Mail bei Speichern ohne Änderung.
Frontend-Test-Harness
Browser-/JS-Tests laufen als FunctionalJavascript (WebDriverTestBase / Drupal Test Traits) gegen Chromedriver in DDEV. Abgedeckt werden alle Formulare: Registrierungs-Wizard (Mehrschritt-Navigation, Passwort-Match, Upload, Honeypot), Profil-Edit, Suchagent-Form, Merkliste-Toggle sowie die Bestands-Formulare contact_form, newsletter_signup, vacancy_application. Playwright ist per Repo-Policy ausgeschlossen; ergänzende visuelle E2E-Verifikation über agent-browser an drei Breakpoints.
Out of Scope
- Verfügbarkeits-Abfrage-Flow (Neos
availability, ~196.000 Zeilen) — internes SOLCOM-Outbound-/Vertriebs-Tool, kein Self-Service. Höchstens das Verfügbarkeits-Flag am Profil bleibt. - Bookmark-Migration aus Neos — entfällt (kein gemeinsamer Projekt-Schlüssel). Die Merkliste selbst wird aus der bestehenden Frontend-Lösung weiterentwickelt, nicht neu gebaut.
- Echter Salesforce-PATCH-Writeback — Phase 2 (sobald Salesforce produktiv ist); bis dahin Mail-Notification.
- Projektliste selbst — kommt aus der laufenden Salesforce/RSM-Pipeline (
solcom_project_offer, ADR-0029/0032/0048), nicht aus diesem Neos-Dump. - RMS-/Salesforce-Übernahme neuer Selbstregistrierer (PPP-ID-Vergabe) — eigener späterer Flow.
Entscheidungen & offene technische Punkte
- Migrations-Umfang — entschieden: Alle ~37.700 Bestandspartner werden migriert (ungefiltert). DSGVO-Hinweis: nie-aktivierte / consent-lose Accounts sind nach Go-Live über eine Retention-Policy rechtlich nachzubewerten.
- Skill-Vokabular — entschieden:
field_skillsals strukturierte Taxonomie/Tags. Migration normalisiert die Neos-Freitext-Skills auf Taxonomie-Terme (Fuzzy-Mapping; unbekannte Terme zur Review); Suchagent matcht gegen die Taxonomie. - UI-Label nach Login — entschieden: „Mein Projektpartnerprofil".
- SF-Sync-Empfänger — entschieden: Config-Platzhalter; echte Adresse wird vor Go-Live nachgetragen.
- Echter SF-PATCH-Writeback — Phase 2 (nach Salesforce-Go-Live); bis dahin Mail.
- Offen (technisch): Webform-Profile-Handler D11-Stabilität + Admin-/Front-End-Theme-Integration der Profile-Edit-Form — vor dem jeweiligen Slice verifizieren.