P2503 · PRD · Projektpartnerportal · Self-Service

Projektpartnerportal Self-Service

ready-for-agent Erstellt: 2026-06-15 · ADR-0052 · Jira P2503-226
Kontext: Das Projektpartnerportal erhält eine authentifizierte Self-Service-Schicht. Freiberufler registrieren sich, pflegen ihr Profil und laden private Unterlagen hoch — heute im Neos-CMS-Altsystem, künftig in Drupal. ~37.700 Bestandspartner werden migriert. Dieses PRD definiert die vollständige Lösung; das Datenmodell ist in ADR-0052 entschieden.

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

  1. Als Freiberufler möchte ich mich über ein dreiseitiges Formular registrieren (Kontakt & Unterlagen / Verfügbarkeit / Qualifikation), damit ich geführt mein Profil anlege.
  2. Als Freiberufler möchte ich schon auf Seite 1 Unterlagen hochladen und Datenschutz + Nutzungserklärung bestätigen, damit meine Registrierung vollständig ist.
  3. 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.
  4. Als Freiberufler möchte ich mich künftig mit E-Mail + Passwort einloggen, damit ich mir keinen Benutzernamen merken muss.
  5. Als Freiberufler möchte ich mein Passwort selbst zurücksetzen können, damit ich nicht auf Support angewiesen bin.

Profil-Pflege

  1. Als eingeloggter Projektpartner möchte ich alle meine Profilfelder (Person, Kontakt, Verfügbarkeit, Qualifikation) nachträglich bearbeiten, damit meine Daten aktuell bleiben.
  2. Als Projektpartner möchte ich private Unterlagen hochladen, die ausschließlich ich (und Admins) sehen, damit meine Dokumente DSGVO-konform geschützt sind.
  3. Als Projektpartner möchte ich ausschließlich mein eigenes Profil sehen und bearbeiten, niemals fremde.
  4. Als Projektpartner möchte ich mein Konto vollständig löschen können (Recht auf Vergessenwerden), inklusive aller privaten Dateien.

Suchagent & gemerkte Projekte

  1. Als Projektpartner möchte ich Suchkriterien (Skills + Einsatz-Regionen) hinterlegen und bei passenden neuen Projektangeboten per E-Mail benachrichtigt werden.
  2. Als migrierter Bestandspartner möchte ich, dass mein bestehender Suchagent weiterläuft, ohne ihn neu anlegen zu müssen.
  3. Als Projektpartner möchte ich Projektangebote merken, um sie später wiederzufinden.

Redaktion & Betrieb

  1. Als Redakteur möchte ich weiterhin das Standard-Drupal-Benutzerformular nutzen — das Partner-Profil darf meine Account-Verwaltung nicht verändern.
  2. Als Betreiber möchte ich die bestehenden Projektpartner aus Neos nach Drupal migrieren, damit Altdaten und Dokumente im neuen Portal verfügbar sind.
  3. 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.
  4. 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.
  5. 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.
  6. 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:

GruppeFelder (aus Neos-Bestand abgeleitet)
PersonAnrede, Titel, Vorname, Nachname, Geburtsdatum, Firma
KontaktTelefon, Mobil, Website, Straße/Nr., PLZ, Ort, Land (E-Mail = Account-Mail)
VerfügbarkeitEinsatz-Regionen (Mehrfach: alle/AT/CH/DE/andere), Stundensatz (EUR), Verfügbarkeits-Status, Netzwerk-IDs GULP / freelance.de / Xing / freelancermap
QualifikationSprachen (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, in config/sync versioniert.
  • Rollen-Trennung: ergibt sich strukturell aus der separaten Profile-Edit-Route — Redakteure/Admins behalten /user/{uid}/edit, kein hook_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.

Vollständige testbare Click-Pfade pro Akzeptanzkriterium siehe Story-Hub p2503-226.html, Abschnitt „QA-Plan".

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_skills als 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.