Slice-Übersicht
Dependency-getriebene Reihenfolge. Fundament-Slices (0a–0c) laufen parallel; der kritische Pfad ist 0a → 2 → 3 → 4 (Registrierungs- und Verifizierungs-Flow).
| Phase | Title | Type | Blocked by | Jira-Ticket |
|---|---|---|---|---|
| 0a | Datenmodell: drupal/profile + Rolle + Felder + field_ppp_id | AFK | — | P2503-227 |
| 0b | Test-Harness: FunctionalJavascript + Chromedriver in DDEV | AFK | — | P2503-240 |
| 0c | Form-Primitive-SDCs (Inputfield/Textarea/Dropdown/Upload) | AFK | — | P2503-123…126 |
| 1 | Login mit E-Mail | AFK | 0a | P2503-230 |
| 2 | Private Uploads (owner-only) | AFK | 0a | P2503-229 |
| 3 | Registrierungs-Wizard (Webform) | AFK | 0a, 0c, 2 | P2503-228 |
| 4 | E-Mail-Flows (Verifizierung/Willkommen/Reset) | AFK | 3 | P2503-231 |
| 4b | Bestandsnutzer-Aktivierung: Passwort-Policy + Willkommens-Mail | AFK | 4, 8 | P2503-241 |
| 5 | Profil-Edit (native Profile-Form) | AFK | 0a, 0c | P2503-232 |
| 6 | Rollen-Trennung (Verifikation) | AFK | 0a, 5 | P2503-234 |
| 7 | Account-Löschung (DSGVO) | AFK | 0a, 2 | P2503-233 |
| 8 | Migration (SqlBase, Neos→Drupal) | AFK | 0a, 2 | P2503-235 |
| 9 | Salesforce-Sync per Mail (Profil-Diff) | AFK | 0a, 5 | P2503-239 |
| 10 | Suchagent / Projekt-Alert (Prio hoch) | AFK | 0a, Projektliste | P2503-237 |
| 11 | Merkliste-Upgrade (auf bestehender Lösung) | AFK | 0a, Merkliste, Projektliste | P2503-238 |
Slice-Details
0a — Datenmodell (drupal/profile)
Composer-Require drupal/profile (1.14.0) + drupal/entity (1.6.0). Profiltyp projektpartner mit allen vier Feldgruppen (Person/Kontakt/Verfügbarkeit/Qualifikation) in config/sync. Rolle projektpartner mit Permissions (eigenes Profil ansehen/bearbeiten, eigenen Account löschen). field_ppp_id (Integer, unique, nullable) auf der User-Entity. Consent-Feld mit Timestamp+Version. field_skills ist ein Taxonomie-Referenz-Feld (neues Vokabular „skills"). Datenmodell-Entscheidung: ADR-0052. Kernel-Test für das Schema.
- Profiltyp
projektpartner+ Rolle existieren in config/sync,drush config:statussauber - Alle vier Feldgruppen als Profilfelder;
field_ppp_idauf User - Consent-Feld speichert Annahme mit Timestamp + Version
- Kernel-Test grün; Modul-Dependency in info.yml deklariert
0b — Test-Harness (FunctionalJavascript)
.ddev/docker-compose.testing.yaml mit Chromedriver-Service (Image drupalci/webdriver-chromedriver), MINK_DRIVER_ARGS_WEBDRIVER + SIMPLETEST_* Env. Ein Referenz-WebDriverTestBase-Test, der den Stack beweist. Querschnitt — früh aufsetzen, da alle Formular-Slices darauf testen. Playwright bleibt per Repo-Policy ausgeschlossen.
- Chromedriver-Service startet mit DDEV; Env-Vars gesetzt
- Ein FunctionalJavascript-Referenz-Test grün lokal in DDEV
- Test läuft in der Azure-DevOps-Pipeline grün
0c — Form-Primitive-SDCs
Inputfield, Textarea, Dropdown, Upload-field als SDCs im byte_theme (eigene Tickets P2503-123…126, ready-for-dev). Dependency für alle Portal-Formulare; läuft theme-seitig parallel zum Fundament. Checkbox/Radio/Password sind eine bekannte Design-Lücke und ggf. zu ergänzen.
- Vier Form-Primitive als SDC mit component.yml + Kernel-Render-Test
- Checkbox/Radio/Password-States geklärt (Figma oder Core-Forms-Styling)
1 — Login mit E-Mail
login_emailusername (bereits aktiv) konfigurieren. Standard-Drupal-Login der Redakteure bleibt funktionsfähig. Verständliche Fehlermeldung bei Fehllogin.
- Login per E-Mail + Passwort funktioniert
- Redakteur-Standard-Login unverändert
- FunctionalJavascript-Test für Login-Form
2 — Private Uploads (owner-only)
private://-Upload-Feld (private://ppp/<uid>/), erlaubte Typen + Größenlimit. hook_file_download / Profile-Access-Handler erzwingt Ownership-Check. Pattern wie PrivateFileUriItemOverride in solcom_vacancy_application.
- Datei nur für Eigentümer + Admin abrufbar; Fremde → 403 (Test belegt)
- Kein öffentlicher Pfad
- Einzeln entfernbar, auch im Wizard (Draft-File-Handling)
3 — Registrierungs-Wizard (Webform)
3-seitige Webform (Kontakt&Unterlagen Pflicht / Verfügbarkeit / Qualifikation optional), Vor/Zurück, Honeypot, Per-Page-Validierung. Draft-Submission als Zwischenstand (Files vor User-Existenz). Final-Submit-Handler legt User + projektpartner-Profil + Rolle atomar an (gesperrt bis Verifizierung), hängt Dateien um. Verwaiste Drafts per Cron-Purge.
- 3 Seiten mit Navigation; Passwort-Match + Consent-Checkboxen Pflicht
- Final-Submit erzeugt User+Profil+Rolle atomar, Account gesperrt
- Doppelte E-Mail + Honeypot abgefangen
- FunctionalJavascript-Test über alle drei Schritte inkl. Upload
4 — E-Mail-Flows
easy_email-Templates (Verifizierung, Willkommen, Passwort-Reset) im SOLCOM-Layout, in config/sync versioniert. Double-Opt-in aktiviert den Account. Migrierte Partner erhalten beim ersten Login einen erzwungenen Reset.
- Verifizierungs-Mail nach Registrierung; Klick aktiviert Account
- Passwort-Reset-Flow via easy_email-Template
- Templates in config/sync; Willkommens-Mail nach Aktivierung
4b — Bestandsnutzer-Aktivierung (Passwort-Policy + Willkommens-Mail)
Neue Dependency drupal/password_policy + Submodul password_policy_expiration (D11-Auflösung ohne Konflikt geprüft, Version beim Bau pinnen). Migrierte Accounts (aus Slice 8) bekommen ein abgelaufenes Passwort-Datum → beim ersten Login erzwingt Drupal „Passwort abgelaufen, bitte neu setzen". Zusätzlich ein Drush-Batch-Command, der zum Go-Live an alle migrierten Partner eine Willkommens-Mail mit Einmal-Login-Link (user_pass_reset, easy_email) versendet. Allgemeine Passwort-Richtlinie (Mindestlänge/Komplexität) gilt damit für alle.
- password_policy + Expiration installiert + konfiguriert
- Migrierter Account: erster Login erzwingt Passwort-Wechsel (Functional-Test)
- Batch-Command versendet Willkommens-Mail mit Einmal-Login-Link an migrierte Partner
- Nach Reset normaler Zugang; allgemeine Policy greift für alle Nutzer
5 — Profil-Edit (native Profile-Form)
Native drupal/profile-Edit-Route /user/{uid}/projektpartner. Feldliste einmal als Profilfelder definiert; Webform spiegelt nur die Erstanlage. Zugriffsschutz: nur eigenes Profil.
- Alle Profilfelder + Unterlagen editierbar; Validierungen greifen
- Partner sieht/bearbeitet nur eigenes Profil (Fremd → 403)
- FunctionalJavascript-Test für Edit-Form
6 — Rollen-Trennung (Verifikation)
Folgt strukturell aus 0a + 5 (separate Profile-Route). Verifizieren: Redakteure/Admins behalten /user/{uid}/edit, Wizard nur für Selbstregistrierung erreichbar.
- Nutzer ohne projektpartner-Rolle sehen Standard-Account-Formular
- Wizard für redaktionelle Nutzeranlage nicht erreichbar
7 — Account-Löschung (DSGVO)
Self-Service-Löschung mit Bestätigungs-Schritt. user_cancel_delete + hook_user_cancel (Pattern wie ApplicationPurger): entfernt Account + Profil + alle privaten Dateien + Verzeichnis.
- Löschung mit Bestätigungs-Schritt
- Profil + Files + Verzeichnis unwiderruflich entfernt; PII-frei (Test belegt)
8 — Migration (SqlBase, Neos→Drupal)
SqlBase-Source direkt gegen den Neos-Dump (temporäre DB-Connection). Zwei verkettete Migrations: User (inkl. field_ppp_id) vor Profil via migration_dependencies/migration_lookup. DC2Type:array-Felder per callback-unserialize. Datei-Migration über Joins in private://. Passwörter nicht migriert → force-reset. Umfang: alle Bestandspartner (ungefiltert; DSGVO-Retention-Nachbewertung nach Go-Live). Freitext-Skills werden auf die Skill-Taxonomie normalisiert (Fuzzy-Mapping; unbekannte Terme landen in einem Review-Report).
- User + Profil koordiniert angelegt; field_ppp_id für Idempotenz
- Dateien dem richtigen Partner zugeordnet
- Freitext-Skills auf Taxonomie-Terme normalisiert; unbekannte Terme im Review-Report
- Idempotent (rollback/update-fähig); Trockenlauf-/Validierungsbericht liegt vor
- Alle Datensätze migriert (kein Consent-Filter im Bau)
9 — Salesforce-Sync per Mail (Profil-Diff)
hook_ENTITY_TYPE_update auf projektpartner-Profil vergleicht $entity->original, ermittelt geänderte Felder, legt bei Änderung ein Queue-Item an; QueueWorker mit injiziertem MailManager versendet via easy_email eine Diff-Mail (alt → neu) an einen konfigurierbaren Empfänger. Pattern wie SalesforceUrlWriteback; beim SF-Go-Live wird der Worker gegen einen SF-PATCH getauscht. Default-Empfänger: Config-Platzhalter, vom Fachbereich nachzutragen — blockiert den Bau nicht.
- Profil-Änderung → genau eine Diff-Mail mit geänderten Feldern
- Speichern ohne Änderung → keine Mail, kein Queue-Item
- Empfänger als Config-Wert; Kernel-Test für Diff + Mail (Mock-MailManager)
10 — Suchagent / Projekt-Alert (Prio hoch)
Pro Partner ein kriterienbasierter Alert (Skills + Regionen). Cron matcht neue project_offer gegen Kriterien → easy_email-Benachrichtigung (Pattern wie solcom_project_offer_alert-Digest). Migration der ~20.000 Bestands-Kriterien; die Alert-Skill-Begriffe werden auf dieselbe Skill-Taxonomie gemappt wie die Profile, sodass das Matching gegen Taxonomie-Terme läuft. Gesetzt, nicht optional.
- Alert anlegen/bearbeiten/(de)aktivieren/löschen; ein Alert pro Partner
- Cron-Mail bei Treffer, keine Mail ohne Treffer
- Bestands-Kriterien migriert, Vokabular gemappt
- FunctionalJavascript-Test für die Alert-Form
11 — Merkliste-Upgrade
Bestehende Frontend-Merkliste (WatchlistController, watchlist.js localStorage, SDCs watchlist-toggle/watchlist-bar) für eingeloggte Partner backend-persistent erweitern (geräteübergreifend, ohne 5er-Limit). Anonyme behalten localStorage; beim Login werden lokale Einträge übernommen. Refactor, kein Neubau — bestehende SDCs/Controller erweitern.
- Eingeloggt: Merkliste backend-persistent, geräteübergreifend, ohne Limit
- Anonym: localStorage-Verhalten bleibt; Login übernimmt lokale Einträge
- Bestehende SDCs/Controller erweitert, nicht ersetzt
- FunctionalJavascript-Test für Toggle + Login-Merge
QA-Plan
Click-Pfade pro Akzeptanzkriterium — Quelle der Wahrheit für die Pre-PR-Acceptance-Stage des Ralph-Loops und die Mensch-QA nach PR-Open (ADR-0037). Basis-URL https://solcom.ddev.site. Routen mit * in der Implementierung final zu bestätigen. Identisch gepflegt im Story-Hub.
| AK-# | URL / Pfad | Action | Expected |
|---|---|---|---|
| AK-1 | /projektpartnerportal/registrieren * | Wizard Seite 1→2→3 + „Zurück" | Drei Seiten, Progress, Vor/Zurück erhält Werte |
| AK-2 | /projektpartnerportal/registrieren * | (a) Passwort ≠ Wdh (b) Consent leer (c) korrekt | (a) Match-Fehler (b) Submit blockiert (c) Seite 1 ok |
| AK-3 | nach Final-Submit + drush user:information | Registrierung abschließen | User+Profil+Rolle angelegt, Account gesperrt, Consent mit Timestamp |
| AK-4 | Test-Mailbox + Verifizierungs-Link | Verifizierungs-Mail, Aktivierungs-Link klicken | Mail im SOLCOM-Layout; Klick aktiviert Account |
| AK-5 | /user/login | (a) korrekt (b) falsches Passwort | (a) eingeloggt (b) verständlicher Fehler; Redakteur-Login bleibt |
| AK-6 | /user/password | Reset auslösen, Link klicken | Reset-Mail via easy_email; neues Passwort funktioniert |
| AK-7 | /projektpartnerportal/registrieren * (DevTools) | (a) doppelte E-Mail (b) Honeypot füllen | (a) Fehler, kein 2. Account (b) schein-ok, kein User |
| AK-8 | /user/{uid}/projektpartner | Alle Feldgruppen bearbeiten + speichern | Editierbar; Zeichenlimits greifen; persistiert |
| AK-9 | private File-URL | (a) Eigentümer (b) anderer Partner (c) anonym | (a) Datei (b) 403 (c) 403; kein public path |
| AK-10 | /user/{uid_fremd}/projektpartner | Fremdes Profil aufrufen | HTTP 403 |
| AK-11 | Account-Bereich → Löschen * + ls private://ppp/<uid>/ | Löschung mit Bestätigung | Account+Profil+Dateien+Verzeichnis weg; keine PII auffindbar |
| AK-12 | /user/{uid}/edit als Redakteur | Account-Formular öffnen | Standard-Drupal-Formular, keine Profilfelder |
| AK-13 | drush migrate:import solcom_projektpartner_user,…_profile | Migration ausführen | User+Profil je Partner; field_ppp_id; DC2Type:array deserialisiert |
| AK-14 | ls private://ppp/<uid>/ nach Migration | Datei-Migration prüfen | Bestands-Dokumente korrekt zugeordnet |
| AK-15 | /user/login als migrierter Partner | Erstes Login | Erzwungener Passwort-Reset |
| AK-16 | migrate:import --update + migrate:rollback | 2× laufen + rollback | Idempotent; sauberer Rollback; Validierungsbericht |
| AK-17 | Account → Suchagent * + drush cron + Mailbox | Kriterien anlegen, passendes Projekt publizieren, Cron | Mail bei Treffer, keine bei Nicht-Treffer |
| AK-18 | drush migrate:import solcom_projektpartner_alert | Alert-Migration | Migrierte Alerts aktiv; Vokabular gemappt; ein Alert/Partner |
| AK-19 | /projektportal/merkliste (anonym, dann eingeloggt) | (a) anonym merken (b) einloggen (c) 2. Gerät | (a) localStorage bleibt (b) lokale Einträge übernommen (c) geräteübergreifend, kein 5er-Limit |
| AK-20 | /user/{uid}/projektpartner + SF-Sync-Mailbox | (a) Feld ändern+speichern (b) ohne Änderung speichern | (a) genau eine Diff-Mail (alt→neu), Queue-Item (b) keine Mail |
| AK-21 | ddev exec ./vendor/bin/phpunit --testsuite functional-javascript | Frontend-Test-Suite gegen Chromedriver | Wizard-Test grün; Smoke-Tests contact_form/newsletter_signup/vacancy_application grün |
Geklärte Entscheidungen (2026-06-15)
- Migrations-Umfang: Alle Bestandspartner werden migriert (ungefiltert), maximaler Datenerhalt. DSGVO-Hinweis: nie-aktivierte / consent-lose Accounts sind nach Go-Live über eine Retention-Policy rechtlich nachzubewerten.
- SF-Sync-Empfänger: Config-Platzhalter; echte Adresse wird vor Go-Live nachgetragen (blockiert den Bau nicht).
- Skill-Vokabular:
field_skillsals strukturierte Taxonomie/Tags (nicht Freitext). Konsequenz: Datenmodell (0a) legt ein Skill-Vokabular an, Migration (8) normalisiert die ~37.700 Neos-Freitext-Skills auf Taxonomie-Terme (Fuzzy-Mapping + Review unbekannter Terme), Suchagent (10) matcht gegen die Taxonomie. - UI-Label nach Login: „Mein Projektpartnerprofil".
- Echter SF-PATCH-Writeback — Phase 2 (nach SF-Go-Live); bis dahin Mail.