Status-Report · Parity-Audit · read-only
Projektpartnerportal — Feature-Parität Legacy-Neos → Drupal
Systematischer Soll-Ist-Abgleich des Drupal-11-Nachbaus (Branch
feature/p2503-226-projektpartnerportal) gegen die Legacy-Neos-Spezifikation
(Context-Snapshots 0009 +
0010). Erzeugt durch ein
read-only Multi-Agent-Audit: ein Auditor je User-Journey plus ein Completeness-Critic. Kein Code geändert.
Datum 2026-06-17
Journeys 10
Features geprüft 143
Methode 10 Journey-Auditoren · Completeness-Critic · 1 Critic-Pass
Go-Live 08.07.2026
51 umgesetzt (36%)
16 teilweise
22 abweichend
4 unklar
50 fehlt (35%)
Fazit
- Kein Journey ist „grüne Wiese“, aber keiner ist fertig. Von 10 Journeys sind
2 weitgehend umgesetzt, 6 teilweise,
2 großteils offen. Über alle 143 geprüften Features:
36% umgesetzt, 35% fehlen ganz.
- Stärken: Registrierungs-Kern (Webform-Wizard, Verifizierung, gesperrter User + Profil),
Merkliste (anonym + eingeloggt + Login-Merge), Profilfeld-Abdeckung, Account-Löschung (Core-Cancel + Purger),
Suchagent-Kern (Entity, Cron-Matcher, Treffer-Mail).
- Größtes Risiko: Neuregistrierungen erhalten keine RMS-/PPP-Nummer — der Join-Key für
Salesforce-Sync, Verfügbarkeits-Mails und interne Benachrichtigung. Stiller Datenbruch ab Go-Live.
- Komplett offen: Account-E-Mail-Wechsel (Double-Opt-In), Verfügbarkeitsmeldung (ganzer Hash-Link-Prozess),
interne Recruiting-Mail an
bewerbung@ mit Dokumenten, Gast-Dimension des Suchagenten, Magazin-Opt-In.
- Querschnitt: Mailabdeckung solide (~14 easy_email-Typen gebaut), aber Session-Härtung (secure-Cookie, 1h-Timeout)
und CSRF auf Raw-Controller-Routen offen.
- Audit-Grenze (nicht geprüft, aber vorhanden): geprüft wurden nur die 10 Account-Journeys.
Die angrenzenden Integrationen existieren als eigene Module und sind nicht Teil dieses Parity-Scopes:
Freelancer-Plattform-Feeds (
solcom_project_offer_feeds: XML/HTML-Feeds für Freelancermap/freelance.de/Gulp),
Personio-Karriere-Bewerbung (solcom_vacancy_application, ADR-0050: API-POST + DLQ),
Salesforce-Projektangebots-Sync (solcom_project_offer).
- Aufwand richtig lesen: Die „fehlt“/„abweichend“-Befunde sind nicht alle Neubauten —
ein großer Teil ist Konfiguration oder Core-Verdrahtung. Die Core-Deckung
unten sortiert nach echtem Bau-Aufwand (Konfiguration / Core+Delta / Custom-Build).
Umsetzungs-Empfehlung — MVP-Scope zum 08.07.2026
Maßstab: harter Go-Live am 08.07.2026, nicht „hatte Neos das“. Nach Core-Deckungs-Re-Bewertung,
Code-Verifikation im Branch und Produkt-Scope-Entscheid schrumpft die Liste deutlich:
die echten Neubauten sind wenige, vieles ist Konfiguration oder existiert bereits.
RMS-Nummer ist kein Drupal-Counter mehr (Salesforce vergibt, Drupal speichert),
Verfügbarkeitsmeldung ist Phase 2.
Jetzt bauen — Blocker oder Sicherheit, geringer Aufwand3
| CSRF-Schutz auf den Merkliste-Routen | Core _csrf_request_header_token: 'TRUE' + Header im JS. Verifiziert offen. |
| Secure-Cookie-Härtung | Session-Config in settings.php/services.yml. |
| Bewerbungs-Eingangsbestätigung an Bewerber | Auslieferung & Handler existieren bereits — nur die Bestätigungsmail ergänzen. |
Nur konfigurieren / aktivieren — kein Code4
| Login per E-Mail aktivieren | login_emailusername installiert, aber 0 in core.extension — einschalten. |
| Spam-Schutz aktivieren | captcha/honeypot installiert, derzeit aus — auf öffentliche Formulare. |
| Fehlende Bewerbungs-Formularfelder | Webform-Elemente ergänzen (Mobil, Titel, Geburtsdatum, Firma, Webseite, Einsatzorte, Stundensatz, Bewerbungstext). |
| Upload-Regeln & Pflichtfelder | Webform managed_file-/Required-Settings — Konfiguration. |
Bauen, aber vereinfacht — Legacy nicht 1:1 nachbilden3
| E-Mail-Wechsel mit Bestätigung der neuen Adresse | MVP-Pflicht (Entscheid). Contrib „confirm-to-new-address“ statt custom Pending-ohne-Session-Flow prüfen. |
| RMS-/PPP-Nummer: Feld + SF-Rücksync | Entscheid: SF vergibt, Drupal speichert/zeigt. Kein Counter-Build — ein Profilfeld + Inbound-Befüllung. Abhängig davon, dass SF die Nummer bereitstellt. |
| Token-Gültigkeit vereinheitlichen | Einen sinnvollen Core-password_reset_timeout wählen — keine Dual-Token-Logik für 10 Tage vs. 12 Stunden. |
Schon (teilweise) da — verifizieren, nicht neu bauen3
| Projekt-Bewerbung Auslieferung | RsmProjectOfferMailHandler + rsm_project_offer_application-Webform („exact Power-App format“). |
| Profiländerungs-Benachrichtigung an SF | ProfileChangeSfSync-QueueWorker + ppp_sf_sync-Mailtyp + Kernel-Test. |
| Registrierung / Verify / Profil / Account-Löschung / Merkliste | Kern-Journeys laut Audit umgesetzt — Restdeltas sind Config (siehe oben). |
Phase 2 / nach Go-Live — bewusst nicht im MVP4
| Verfügbarkeitsmeldung (ganze Journey) | Entscheid: Phase 2. Größter Einzel-Custom-Block (Hash-Link, Cron, Datenmodell, 4 Mailtypen). |
| Suchagent-Gast-Dimension | Nicht im MVP gewählt. Double-Opt-In, Hash-Abmeldung, Login-Übernahme — hohe Komplexität. |
| Magazin-/Newsletter-Opt-In via Mailingwork | Nicht im MVP gewählt. Marketing-Integration, nicht Kern-Projektpartner. |
| 1h-Inaktivitäts-Logout | Nicht im MVP gewählt. Contrib autologout nachrüstbar, wenn später gewünscht. |
Core-Deckung — was Drupal schon mitbringt
Warum diese Sektion: Die 50 „fehlt“- und 22 „abweichend“-Befunde oben sind
nicht 72 Neubauten. Ein erheblicher Teil ist Konfiguration oder Core-Verdrahtung.
Die folgende Re-Bewertung sortiert die gap/divergent/partial-Themen nach tatsächlichem Bau-Aufwand:
14 sind Konfiguration (Tier A), 7 Core-Basis + Delta (Tier B),
11 echter Custom-Build (Tier C). „geprüft“ markiert Aussagen, die in dieser Session an Core-Quellcode
oder synchronisierter Config verifiziert wurden; die übrigen folgen bekanntem Core-/Contrib-Verhalten.
Tier A · Konfiguration
14 Themen
Konfigurieren, aktivieren oder verdrahten — kein Neubau. Der „gap“/„abweichend“-Status des Audits spiegelt hier fehlende Konfiguration, nicht fehlende Substanz.
| Passwort-Reset: Grundflow, keine Konto-Enumeration, One-Time-Logingeprüft | Core /user/password — submitForm zeigt immer die generische Meldung „If … is a valid account …“; validateForm wirft nur bei formal ungültiger Eingabe. Default-Verhalten. |
| Brute-Force-Schutz am Login & Resetgeprüft | Core Flood-Control (user.flood, user.password_request_ip/_user) — Default aktiv. |
| Login per E-Mail-Adressegeprüft | Contrib login_emailusername (installiert) — aktivieren & konfigurieren. |
| Passwortänderung eingeloggt (Mechanik, sofort wirksam) | Core User-Edit-Form. (Die Bestätigungs-Mail dazu ist separat — siehe Tier C.) |
| Account-Löschung: Logout + Bestätigungsseite + Bestätigungsmail an Nutzergeprüft | Core user_cancel + user.settings: cancel_method + notify-Flag — Konfiguration. |
| Pflichtangaben-Validierung (Registrierung, Profil, Bewerbung) | Webform #required bzw. Feld-Required + Form-Display — Konfiguration. |
| Datei-Upload-Regeln: Typ, Größe, Anzahl (4 Upload-Formulare)geprüft | Webform managed_file-Settings + Core File-Validation-Constraints (FileSizeLimit, FileExtension, FileNameLength) via FileValidator — Konfiguration. (D11-Mechanik, nicht mehr file_validate_*.) |
| Dateiname-Bereinigung beim Uploadgeprüft | Core Transliteration + FileNameLengthConstraint — automatisch. |
| Land als Auswahlliste | Core Listen-/Country-Feld — Konfiguration. |
| Fehlende Bewerbungs-Formularfelder (Mobil, Titel, Geburtsdatum, Firma, Webseite, Einsatzorte, Stundensatz, Bewerbungstext, Magazin-Checkbox) | Webform-Elemente ergänzen — Konfiguration, kein Code. |
| Spam-Schutz auf Formularengeprüft | Contrib captcha + honeypot (installiert, derzeit deaktiviert) — aktivieren. |
| Temp-Datei-Cleanup | Core File-GC (temporäre Dateien via System-Cron). Spezifische TTLs = kleiner Delta. |
| Volltext-/Einsatzort-Filter der Projektliste | Views Exposed Filter — Konfiguration (teilweise vorhanden). |
| CSRF-Schutz auf den Merkliste-Raw-Routen (add/remove/merge)geprüft | Core Route-Requirement _csrf_request_header_token: 'TRUE' + Header im JS. Verifiziert: aktuell nicht gesetzt, nur _user_is_logged_in. |
Tier B · Core + Delta
7 Themen
Der Mechanismus existiert in Core, erfüllt das Legacy-Soll aber nicht vollständig — ein begrenzter Zusatz reicht.
| Getrennte Token-Gültigkeiten: Aktivierung 10 Tage vs. Reset 12 Stundengeprüft | Beide hängen heute am einen Core-password_reset_timeout (aktuell 86400 = 24 h). Beide Soll-Werte gleichzeitig gehen nur mit getrennter Token-Logik. |
| Kombiniertes „Zugangsdaten ändern“-Formular (E-Mail + Passwort, „leer lassen = unverändert“, Wiederholungsfeld) | Core User-Edit deckt die Änderung; Layout, Repeat-Feld & Leer-Semantik = Delta. |
| Merkliste leeren / alle entfernen | Kleiner Zusatz am bestehenden WatchlistController. |
| Filter-Persistenz der Projektliste über Besuche | Views Exposed Filter + Session/Cookie — kleine Erweiterung. |
| Inaktivitäts-Logout nach 1 hgeprüft | Kein Core-Default. Contrib autologout nachrüsten (nicht installiert) + konfigurieren. |
| Session-Cookie-Härtung (secure) | Core/Symfony Session-Config (settings.php / services.yml) — Härtung, kein Build. |
| „Konto noch nicht aktiviert“-Hinweis beim Login | Core blockt gesperrte User bereits; nur die spezifische Meldung ist Mini-Delta. |
Tier C · Custom-Build
11 Themen
Geschäftsprozesse ohne Core-Entsprechung. Die Mail-Infrastruktur (easy_email, symfony_mailer_lite) ist installiert — die Flows sind zu bauen.
| RMS-/PPP-Nummer-Counter + Vergabe bei Neuregistrierung | Kein Core-Sequenz-Mechanismus. Join-Key für Salesforce/Verfügbarkeit. Höchstes Go-Live-Risiko. |
| Bestätigungsmails bei Passwort- und E-Mail-Änderunggeprüft | Core kennt nur 9 Mailtypen (password_reset, status_*, register_*, cancel_confirm) — kein password_changed / email_changed. Wert ändert Core, Bestätigung ist custom. Infra installiert. |
Interne Recruiting-Mail an bewerbung@ mit Dokument-Anhängen (Aktivierung + Bewerbung) | Eigener Mail-Flow inkl. Attachment-Sammlung. |
| Bewerbungs-Eingangsbestätigung an Bewerber (BCC) | Eigener Mail-Flow. |
| Account-Login-E-Mail-Wechsel als Double-Opt-In zur NEUEN Adresse + zwei Erfolgsmails + Pending ohne Session-Verlust + Token-Landeseite | Core ändert die E-Mail sofort und bestätigt die neue Adresse NICHT. Contrib „Verify Email/Email Confirmer“ nicht installiert. |
| Magazin-/Newsletter-Opt-In via Mailingwork | Externe Double-Opt-In-Integration. |
| Verfügbarkeitsmeldung — ganze Journey | Hash-Link ohne Login, Token-Validierung, Datenmodell, Cron-Erinnerungen, 4 Mailtypen. |
| Suchagent-Gast-Dimension | Double-Opt-In, Gast-Kontaktblock, Hash-Abmeldung, UND/ODER-Schlagwortlogik, Sofort-Match beim Anlegen, Login-Übernahme von Gast-Alerts, Konvertierung bei Account-Löschung. |
| Gast-Flow „Bewerben + gleichzeitig Konto anlegen“ | Kombinierter Bewerbungs-/Registrierungs-Pfad. |
| Profiländerungs-Tracking + tägliche Sammel-Benachrichtigung | lastAccountEditDate-Feld + Cron-Job + interne Mail. |
| Cron-Prune-Jobs (abgelaufene Alerts/Notifications/Verfügbarkeiten) | Idiomatisch via Drupal-Cron/Queue, aber custom zu schreiben. |
Gap-Matrix je Journey
Registrierung & Aktivierung (Double-Opt-In)
Teilweise
4 fehlt 2 abweichend 2 teilweise 9 umgesetzt
Der Kern-Flow steht: ein dreistufiger Webform-Wizard legt per Handler/Submitter einen gesperrten projektpartner-User plus Profil an, verschiebt Uploads ins Private-Verzeichnis, versendet eine Verifizierungs-Mail und aktiviert den Account ueber einen Token-Link, der eine Willkommens-Mail mit One-Time-Login ausloest. Mehrere Legacy-Sollanforderungen fehlen jedoch: Magazin-Double-Opt-In (Mailingwork), interne Aktivierungs-Mail an bewerbung@ mit Dokument-Anhaengen, RMS-Nummer-Vergabe bei Registrierung sowie ein Resend-/Re-Aktivierungs-Pfad. Token-Ablauf (24h statt 10 Tage), Upload-Constraints (10MB + breite Typenliste statt 5MB pdf/doc/docx) und die Pflichtfeld-Pruefung (Mobil/Adresse/Anrede nicht required) weichen vom Soll ab.
| Feature / Verhalten (Legacy-Soll) | Status | Evidenz (Drupal) | Anmerkung |
| Abgelaufener Link: neuen Aktivierungslink zusenden lassen (Re-Aktivierung / Resend) | fehlt | keine gefunden (kein reActivation-Pendant in routing.yml, MailManager, Controller) | Legacy AccountController::reActivationAction fehlt komplett. Kein Resend-Endpoint, keine UI fuer abgelaufenen Token. AK 'kann mir neuen zusenden lassen' nicht erfuellt. |
| Magazin-Opt-In (SOLCOM Freiberufler Magazin) mit Double-Opt-In via Mailingwork | fehlt | keine gefunden (kein Magazin-Element im Webform, kein field_magazine, kein Mailingwork/optinrecipient-Code) | Legacy: Checkbox 'Freiberufler Magazin' + separater Double-Opt-In ueber Mailingwork (optinrecipient, 48h). Im Drupal-Stand komplett fehlend - weder Formularfeld noch Integration. |
| RMS-Nummer (pppid/counter) bei erfolgreicher Neuregistrierung vergeben | fehlt | keine gefunden im Registrierungspfad; ppp_id existiert nur in migrate-Quellen (migrate_plus.migration.solcom_projektpartner_neos_user.yml:16,51) | Legacy FlowUserCreationService vergibt bei Registrierung eine neue RMS-Nummer aus einem autoinkrementierenden Counter. Der Submitter setzt keine RMS-/ppp_id fuer Neuregistrierungen; field_ppp_id wird nur fuer Bestandsdaten-Migration befuellt. Counter-Mechanik fehlt. |
| Interne Aktivierungs-Mail an bewerbung@/SOLCOM (sendMailToSolcom) inkl. Dokument-Anhaenge bei Account-Aktivierung (zwei Mails: intern + Nutzer) | fehlt | keine gefunden; verifyAccount sendet nur sendWelcome an Nutzer (ProjektpartnerMailManager.php:58,66-70) | Legacy activationAction loest zwei Mails aus: Nutzer-Willkommen UND interne Mail an SOLCOM mit allen UserFiles als Anhang (Template RegistrationUserNotification). Im Drupal-Stand fehlt die interne Benachrichtigung samt Anhaengen komplett; die eval-Liste fordert explizit zwei Mails. |
| Aktivierungslink 10 Tage gueltig | abweichend | ProjektpartnerMailManager.php:183-189 (nutzt user.settings password_reset_timeout); config/sync/user.settings.yml:17 password_reset_timeout: 86400 | Ablauf an Core password_reset_timeout gekoppelt = 24h. Legacy-Soll: 10 Tage (864000 s). Abweichend; AK fordert 10-Tage-Gueltigkeit. |
| Datei-Upload PDF/DOC/DOCX, max. 5 MB, bis zu 5 Dateien; Anhang ans Profil | abweichend | webform.webform.projektpartner_registration.yml:113-115 (#file_extensions 'pdf doc docx odt txt rtf jpg jpeg png', #max_filesize '10MB'); moveDocuments ProjektpartnerRegistrationSubmitter.php:189-230 | Upload funktioniert (Multiple, Private, Re-Home ins Profil-Verzeichnis, field_documents). Aber: Typenliste breiter als pdf/doc/docx, max_filesize 10MB statt 5MB; kein Limit von 5 Dateien konfiguriert. Constraints weichen vom Soll ab. |
| Pflichtangaben werden vor Absenden geprueft (E-Mail, Passwort, Name, Mobilnummer, Datenschutz) | teilweise | webform.webform.projektpartner_registration.yml:72-75 (mobile OHNE #required); required gesetzt nur first_name :44, last_name :49, email :66, password :79, privacy_consent :119, terms_consent :124 | Mobil ist NICHT required (Legacy AK 361 + Test 365 fordern Mobilnummer-Pflicht explizit). Ebenso Anrede/Strasse/PLZ/Ort/Land im Mockup als Pflicht markiert, hier optional. Submitter behandelt mobile als optionalString (:148). Pflichtfeld-Validierung daher abweichend/lueckenhaft. |
| Optionaler Schritt 2 - Verfuegbarkeit (Einsatzorte, Stundensatz, Verfuegbar ab) | teilweise | webform.yml:126-145 (availability/deployment_locations/hourly_rate); Submitter :154-156 | Felder vorhanden und gemappt. Aber 'Verfuegbar ab' ist plain textfield ohne Zukunfts-Datum-Validierung (Legacy 'nur Zukunft', Format dd.mm.yyyy). Funktional vorhanden, Validierung abweichend. |
| Registrierungsformular absenden legt genau einen Pending-Datensatz an (gesperrter User + Profil) in einer Transaktion | umgesetzt | web/modules/custom/solcom_projektpartner/src/Service/ProjektpartnerRegistrationSubmitter.php:63-77; createBlockedUser status=0 :110-117 | Transaktionaler Submit erzeugt gesperrten User (status 0, Rolle projektpartner) + Profile. created_user_id-Guard im Handler verhindert Doppel-Anlage (Handler :183). |
| Bestaetigungs-/Aktivierungs-Mail nach Absenden an den Gast | umgesetzt | ProjektpartnerRegistrationSubmitter.php:69 (sendVerification); ProjektpartnerMailManager.php:34-38; easy_email.easy_email_type.ppp_verify.yml | Easy-Email-Template ppp_verify mit [solcom:verification-url] geht an Nutzer-Adresse. Entspricht Legacy registerAction +Mail. |
| Aktivierungs-Token-Mechanik + Klick aktiviert Konto, Login danach moeglich | umgesetzt | ProjektpartnerVerificationController.php:36-44; ProjektpartnerMailManager.php:43-61 (verifyAccount setzt status=1); verificationUrl :142-150 | Hash via user_pass_rehash, Route /projektportal/verifizieren/{user}/{timestamp}/{hash}. Aktivierung setzt status=1 und versendet Willkommens-Mail mit One-Time-Login (sendWelcome :66-70). |
| Bereits registrierte E-Mail wird mit klarem Hinweis abgelehnt (keine Doppelanlage) | umgesetzt | ProjektpartnerRegistrationHandler.php:110-120 (User-Query auf mail, setErrorByName 'bereits registriert') | Email-Uniqueness im validateForm geprueft, Inline-Fehler 'Diese E-Mail-Adresse ist bereits registriert.' |
| Zugriff oeffentlich, fuer eingeloggte Nutzer gesperrt | umgesetzt | ProjektpartnerRegistrationAccess.php:35-46 (anonymous allowed, authenticated forbidden); routing.yml:1-8 | Custom-Access-Callback entspricht Legacy 'oeffentlich, gesperrt fuer eingeloggte Nutzer'. |
| Person/Kontakt-Felder (Anrede, Titel, Vorname, Nachname, Geburtsdatum, Firma, Telefon, Mobil, E-Mail, Website, Strasse, Hausnr, PLZ, Ort, Land) | umgesetzt | webform.webform.projektpartner_registration.yml:26-107; field.field.profile.projektpartner.* (salutation/title/first_name/last_name/birthdate/company/phone/mobile/street/house_number/postal_code/city/country/website) | Alle Person-/Kontakt-Cluster-Felder als Webform-Elemente + Profilfelder vorhanden und im Submitter gemappt (buildProfileValues :141-164). |
| Passwort + Passwort-Wiederholung mit Match-Pruefung und Policy | umgesetzt | webform.yml:76-79 (solcom_projektpartner_password_confirm); Handler validateForm :86-101 (pass1!=pass2 Fehler); Submitter assertPasswordPolicy :90-102 | Custom Confirm-Element + Match-Validierung + password_policy.projektpartner. Doppeltes Tor (Handler-UX + Submitter-Backstop). |
| Consents Datenschutz + Nutzungsbedingungen (Pflicht) mit Timestamp/Version | umgesetzt | webform.yml:116-125 (privacy_consent/terms_consent required); Submitter :167-172 (consent + _at + _version); field_privacy_consent*/field_terms_consent* | Beide Consents Pflicht; Submitter setzt Boolean + Timestamp + Versions-String (PRIVACY_VERSION/TERMS_VERSION :27-28). |
| Optionaler Schritt 3 - Qualifikationen (Skills, Sprachen, Fachliche Schwerpunkte, Anschreiben, Online-Profile GULP/freelance.de/XING/freelancermap) | umgesetzt | webform.yml:146-188; Submitter skillReferences :303-327, listValues languages, focus_areas/cover_letter, gulp_id/freelance_de/xing/freelancermap/website | Alle Qualifikations- und Netzwerk-Felder vorhanden. Skills werden zu Taxonomie-Termen (Vokabular skill) mit Spam-Limits (MAX_SKILLS 50, MAX_SKILL_NAME_LENGTH 100). |
Login (mit Übernahme von Gast-Daten)
Teilweise
2 fehlt 1 abweichend 2 teilweise 2 unklar 3 umgesetzt
Der Kern-Login (E-Mail/Passwort über geteiltes Core-/user/login, P2503-230 via contrib login_emailusername) und die Übernahme der Gast-Merkliste sind funktional vorhanden. Die zweite Hälfte des Legacy-★-Verhaltens fehlt jedoch komplett: Es gibt KEINEN hook_user_login in custom Code, daher wird der per E-Mail angelegte Gast-Suchagent beim ersten Login nicht auf das Konto übernommen — die beiden Alert-Systeme (email-keyed Gast vs. uid-basiert eingeloggt) sind technisch nicht verbunden. Zusätzlich weicht die Merkliste-Merge-Semantik vom Legacy ab (Union ohne 7er-Cap) und der dokumentierte Inaktivitäts-Timeout (3600s) fehlt.
| Feature / Verhalten (Legacy-Soll) | Status | Evidenz (Drupal) | Anmerkung |
| Übernahme von Gast-Projekt-Alerts (Suchagenten, per E-Mail angelegt) auf den User beim Login (Alert-Migration, Gast-Datensatz entfernt) | fehlt | keine gefunden — grep 'function *_user_login' über web/modules/custom/ liefert 0 Treffer; ProjectOfferAlert-Entity (solcom_project_offer/modules/solcom_project_offer_alert/src/Entity/ProjectOfferAlert.php:54) hat nur 'email'-Feld, KEIN uid-Feld | Kernlücke der Journey. Es gibt zwei getrennte, unverbundene Alert-Systeme: (1) solcom_project_offer_alert = email-keyed Gast-Alert (Double-Opt-In, öffentliche Subscribe-Form, kein uid), (2) solcom_projektpartner_alert = uid-basiert, login-only (PppAlert.php:72 uid required, 1 pro User). KEIN hook_user_login adoptiert den email-Gast-Alert auf das Konto; die Entities sind nicht verknüpfbar (kein uid am Gast-Alert). Der Legacy-AK 'Gast-Suchagent unter Konto-E-Mail wird dem Konto zugeordnet und Gast-Datensatz entfernt' (Flow-Inventar:382) ist nicht umgesetzt. |
| Automatische Abmeldung nach 1 Stunde (3600s) Inaktivität (Session-Timeout) | fehlt | keine gefunden — grep 'autologout|session.*timeout|inactiv|gc_maxlifetime' über web/modules/custom/ + config/sync/ liefert nur unrelated Treffer (user.flood ip_window 3600, Cron-Intervalle); kein autologout-Modul in composer.json | Kein inaktivitätsbasierter Sliding-Logout. Drupal-Core hat das nicht nativ, kein autologout-Contrib installiert, keine Session-GC-Config gepflegt. Legacy-AK 'nach 1 Stunde Inaktivität automatisch abgemeldet' (Flow-Inventar:380,382) nicht abgebildet. |
| Gast-Bookmark-Quelle: localStorage statt Cookie | abweichend | web/themes/custom/byte_theme/js/watchlist.js:11-16 (localStorage); Flow-Inventar:651 (Neos: @Flow\Scope('session') Cookie) | Legacy nutzt server-seitige Flow-Session/Cookie für Gast-Bookmarks; Drupal nutzt localStorage. Funktional äquivalent für den Merge-Zweck, aber technisch abweichende Persistenzschicht (im Drupal-Nachbau bewusst gewählt). |
| Nicht-aktivierte Konten: Login erst nach Token-Klick möglich ('Konto noch nicht aktiviert') | teilweise | web/modules/custom/solcom_projektpartner/src/Service/ProjektpartnerRegistrationSubmitter.php:65,114 (createBlockedUser, status => 0) | Registrierung legt blocked User (status=0) an → Core verhindert Login nativ, Aktivierung via ProjektpartnerActivationCommands. Funktional korrekt, ABER Core liefert die generische Meldung ('not been activated or is blocked'), nicht den Legacy-Wortlaut 'Konto noch nicht aktiviert' — Wording divergiert; nicht explizit getestet. |
| Übernahme der anonymen Gast-Merkliste beim ersten Login (Bookmarks → DB mergen) | teilweise | web/modules/custom/solcom_project_offer/src/Controller/WatchlistController.php:145 (merge); web/modules/custom/solcom_project_offer/src/Service/WatchlistStorage.php:118 (merge); web/themes/custom/byte_theme/js/watchlist.js:207-212 (serverMerge on first authenticated attach) | Merge existiert und triggert JS-getrieben beim ersten authentifizierten Page-Load (localStorage → POST /projektportal/merkliste/merge). Übernahme bei leerem Konto erfüllt (Union = Gast-Stand). ABER: kein server-seitiger hook_user_login-Trigger (rein clientseitig, JS-abhängig), und Merge ist Union OHNE 7er-Cap (WatchlistStorage:108 'union, no cap') — Legacy-Business-Regel max. 7 Bookmarks (Flow-Inventar:663) nicht durchgesetzt. |
| Status-Meldungen oberhalb des Login-Formulars ('Logout erfolgreich', 'Login fehlgeschlagen', 'Konto noch nicht aktiviert') | unklar | keine gefunden — keine Custom-Login-Template/Message-Logik in solcom_projektpartner; Core liefert eigene Standard-Messages | Da geteiltes Core /user/login genutzt wird, kommen Core-Standardmeldungen — der spezifische Legacy-Wortlaut ('Logout erfolgreich' etc.) ist nicht custom abgebildet. Ob das als äquivalent akzeptiert wird, ist eine Stakeholder-Entscheidung; technisch nicht eindeutig done/gap ohne UX-Vorgabe. |
| Footer-Links 'Passwort vergessen?' und 'Noch kein Nutzerkonto? Jetzt registrieren!' auf der Login-Seite | unklar | keine gefunden — geteilte Core /user/login Seite; kein Custom-Form-Alter für Footer-Links in solcom_projektpartner.module (nur #submit-Handler ergänzt, Form:35) | Core-Login-Seite hat eigene Tabs (Passwort-Reset). Ob die Projektportal-spezifischen Footer-Links/Verweise zur Projektpartner-Registrierung vorhanden sind, ist aus dem geprüften Code nicht ersichtlich (kein Form-Alter dafür gefunden) — Browser-Verify nötig für eindeutige Klassifizierung. |
| Login mit E-Mail-Adresse + Passwort (Provider-Äquivalent Contenance.Solcom:Login) | umgesetzt | web/modules/custom/solcom_projektpartner/tests/src/Kernel/ProjektpartnerLoginTest.php:48 (testProjektpartnerCanAuthenticateWithEmailAndPassword); contrib login_emailusername ^3.0.1 (composer.json), enabled in test:29 | E-Mail-Login über geteiltes Core /user/login + contrib login_emailusername (P2503-230). Username-Fallback für Editoren bleibt erhalten (Test:68). Kein Custom-Login-Route laut ADR-0053 — Drupal-way, kein Gap. |
| Falsche Zugangsdaten → Fehlermeldung, bleibt ausgeloggt (onAuthenticationFailure) | umgesetzt | web/modules/custom/solcom_projektpartner/tests/src/Kernel/ProjektpartnerLoginTest.php:88 (testWrongPasswordIsRejectedForEmailLogin) | Core-Auth lehnt falsches Passwort ab; Test verifiziert. Standard-Drupal-Fehlermeldung. |
| Redirect zu Profil/Bewerbung nach erfolgreichem Login | umgesetzt | web/modules/custom/solcom_projektpartner/solcom_projektpartner.module:47 (solcom_projektpartner_user_login_submit → Url internal:/projektportal) | Post-Login-Submit-Handler leitet Projektpartner auf Canvas-Hub /projektportal (von dort Profil/Suchagent/Merkliste erreichbar). Legacy leitet auf Profil/Bewerbung — Ziel-Pfad leicht abweichend (kuratierte Hub-Seite statt Profil direkt), funktional gleichwertig. |
Passwort vergessen & zuruecksetzen (Projektpartner)
Weitgehend
2 fehlt 3 abweichend 1 unklar 4 umgesetzt
Die Journey ist im Drupal-Nachbau NICHT custom implementiert, sondern wird vollstaendig vom Drupal-Core-Flow /user/password getragen — es gibt im solcom_projektpartner-Modul keine eigene "passwort-vergessen"-Route, kein eigenes Formular, keine sendPasswordResetMail-Methode und keinen einzigen Test fuer diese Journey. Der Core-Flow (Drupal 11, web/core/modules/user/src/Form/UserPasswordForm.php) deckt die sicherheitskritischen Soll-Anforderungen ab: keine Konto-Enumeration (unbekannte/gesperrte/nicht-aktivierte Konten erzeugen identische UI-Antwort ohne Fehler), Einmal-Token und Token-Login. Zwei harte Divergenzen bleiben: die Token-Gueltigkeit ist auf 24h (86400 s) statt der Legacy-12h gesetzt, und die Legacy-spezifische Hinweis-Logik fuer noch nicht aktivierte Konten ("registerFlowExisting") sowie das lastAccountEditDate-Tracking fehlen. WICHTIG fuers Backlog: die "Drupal-Nachbau"-Notiz im Legacy-Doc, die ProjektpartnerMailManager::sendPasswordResetMail als vorhanden bezeichnet, ist falsch — diese Methode existiert nicht.
| Feature / Verhalten (Legacy-Soll) | Status | Evidenz (Drupal) | Anmerkung |
| Fallback-Hinweis bei noch nicht aktiviertem Konto (Legacy 'registerFlowExisting'-Meldung) | fehlt | keine gefunden (Registrierung legt status=0 an: web/modules/custom/solcom_projektpartner/src/Service/ProjektpartnerRegistrationSubmitter.php:114; kein hook_form_user_pass_alter im Modul) | Legacy zeigt eine distinkte Meldung, wenn ein RegistrationFlow (unaktiviertes Konto) existiert. Drupal behandelt solche status=0-Konten still wie nicht existent (kein Fehler, keine Mail) — sicher, aber der eigene Hinweis fehlt. Die Legacy-Doc nennt dies explizit als nachzubauende Besonderheit (Zeile 2428). |
| lastAccountEditDate-Tracking bei erfolgreichem Reset | fehlt | keine gefunden (kein Custom-Reset-Code in web/modules/custom/solcom_projektpartner/) | Legacy-Doc (Zeile 2448) sagt explizit: lastAccountEditDate-Tracking 'muss im Custom-Code nachgezogen werden'. Da der gesamte Reset ueber Core laeuft und kein Custom-Submit-Handler existiert, wird kein solches Datum gesetzt. |
| Reset-Token-Gueltigkeit 12 Stunden (Legacy: 43200 s) | abweichend | config/sync/user.settings.yml:17 (password_reset_timeout: 86400) | Token gilt 24h statt der geforderten 12h. Korrektur waere ein One-Liner in user.settings.yml (43200). Die Legacy-AK 'Reset-Link 12 Stunden gueltig' ist damit nicht erfuellt. |
| Dedizierte Reset-Mail (Legacy-Betreff 'Zuruecksetzen Ihres Passwortes / SOLCOM Projektportal', SOLCOM-Branding) | abweichend | config/sync/user.mail.yml:7-9 (password_reset; subject: 'Du hast dein Passwort fuer [site:name] vergessen?') | Core-Standard-Mail (deutsch, geduzt) ist vorhanden, aber Betreff/Branding weichen vom Legacy-Soll ab. Eine projekt-eigene sendPasswordResetMail-Methode existiert NICHT (Legacy-Doc-Notiz dazu ist falsch — ProjektpartnerMailManager.php hat nur sendVerification/sendWelcome/sendProfileChangeNotification). |
| Route/URL-Schema /projektportal/passwort-vergessen + /passwort-zuruecksetzen?token= | abweichend | web/modules/custom/solcom_projektpartner/solcom_projektpartner.routing.yml (nur registrieren/verifizieren/profil); Core-Pfad /user/password | IA-Inkonsistenz: Registrierung bekam eine Custom-Route unter /projektportal/registrieren, der Passwort-Reset nicht — er laeuft ueber den generischen Core-Pfad /user/password statt unter dem /projektportal-Namespace. |
| Logged-in-User-Lockout: eingeloggte Nutzer werden vom Passwort-vergessen-Formular auf Profil umgeleitet | unklar | keine gefunden (kein Redirect-Handler/Access-Check fuer user.pass im Modul) | Legacy redirectet eingeloggte Nutzer weg. Core /user/password ist auch fuer authentifizierte User erreichbar ohne speziellen Redirect; ob das als Gap zaehlt, haengt vom Soll ab — kein Custom-Code dazu vorhanden, daher nicht eindeutig als done/gap feststellbar ohne Laufzeit-Check. |
| Passwort-vergessen-Formular: E-Mail eingeben und Reset-Link anfordern | umgesetzt | web/core/modules/user/src/Form/UserPasswordForm.php:154 (validateForm) + :195 (submitForm); keine Custom-Route in web/modules/custom/solcom_projektpartner/solcom_projektpartner.routing.yml | Vollstaendig durch Core-Flow /user/password abgedeckt. Kein Custom-Code, kein projekt-eigener Test fuer diese Journey vorhanden (Core-provided, ungetestet auf Projektebene). |
| Keine Konto-Enumeration: identische UI-Antwort egal ob E-Mail registriert ist | umgesetzt | web/core/modules/user/src/Form/UserPasswordForm.php:172-189 + :195-205 | Drupal-11-Core ist enumeration-safe: unbekannte E-Mail, gesperrtes UND nicht-aktiviertes Konto (Gate auf isActive() in :179) erzeugen keinen Fehler und keine Mail; submitForm zeigt immer dieselbe generische Bestaetigung. AK erfuellt. Kein projekt-eigener Regressionstest. |
| Einmal-Verwendung des Reset-Tokens (Token nach Nutzung entwertet) | umgesetzt | config/sync/user.mail.yml:9 (one-time-login-url, 'Dieser Link kann nur einmal verwendet werden'); web/core/modules/user/src/Form/UserPasswordForm.php:198 (_user_mail_notify('password_reset')) | Core-One-Time-Login-Mechanik: Hash basiert auf login-Timestamp, wird nach Login/Passwortwechsel ungueltig. Core-provided, kein projekt-eigener Test. |
| Neues Passwort setzen ueber Token-Link und anschliessend einloggen | umgesetzt | Core user/reset-Flow (UserController::resetPassEdit); keine Custom-Route/Form in web/modules/custom/solcom_projektpartner/ | Core-Flow setzt nach Token-Login das neue Passwort via Standard-Profilformular. Funktioniert, aber ohne projekt-eigene Testabdeckung. |
Account-Daten ändern (Passwort / Login-E-Mail)
Großteils offen
7 fehlt 1 teilweise
Der Kern dieser Journey fehlt im Drupal-Stand: Es gibt kein dediziertes "Zugangsdaten ändern"-Formular (#edit-account), keinen Double-Opt-In für den Login-E-Mail-Wechsel und keine begleitenden Bestätigungs-/Info-Mails. Eine Passwortänderung ist nur über das generische Drupal-Core-User-Edit-Formular technisch möglich (sofort wirksam), aber ohne die vom Legacy-Soll geforderte Bestätigungs-E-Mail und mit abweichender Formular-Struktur. Die docs-Note (Zeile 427) ist insofern zu optimistisch: P2503-230 war "Login per E-Mail absichern", keine Passwort-Änderungs-Funktion mit Mail-Bestätigung.
| Feature / Verhalten (Legacy-Soll) | Status | Evidenz (Drupal) | Anmerkung |
| Kombiniertes Formular 'Zugangsdaten ändern' (#edit-account): zwei optionale Blöcke E-Mail + Passwort, 'leer lassen = unverändert'-Semantik, eingeloggt-only | fehlt | keine gefunden (solcom_projektpartner.routing.yml hat nur registration/verify/profile/profile_edit; profile_edit = ProjektpartnerProfileController::edit bearbeitet Profilfelder, keine Zugangsdaten) | Es existiert keine Route/Form für die kombinierte Zugangsdaten-Änderung. Ersatzweise nur Drupal-Core /user/{uid}/edit, das ein einziges E-Mail-Feld (ohne Wiederholung) und ein Passwort-Feld mit Pflicht-'aktuelles Passwort' hat — strukturell abweichend von der Legacy-Maske. |
| Passwortänderung wird per E-Mail bestätigt (genau eine Bestätigungs-Mail) | fehlt | keine gefunden (grep hook_user_update/_user_mail_notify in web/modules/custom/ = 0 Treffer; kein easy_email-Type für Passwort-Änderung in config/sync/easy_email.easy_email_type.*.yml — nur password_recovery) | Drupal-Core verschickt bei Passwortänderung keine Bestätigungsmail, und es gibt keinen Custom-Hook/easy_email-Type dafür. AK 'sofort wirksam UND per E-Mail bestätigt' nur zur Hälfte erfüllbar. |
| Login-E-Mail-Änderung erst nach Klick auf Bestätigungslink an die NEUE Adresse wirksam (Double-Opt-In / AccountChangeFlow) | fehlt | keine gefunden (verify_mail: true in config/sync/user.settings.yml betrifft nur Registrierung, nicht E-Mail-Wechsel bestehender Nutzer; kein contrib-Modul wie verify_email installiert) | Drupal-Core ändert die E-Mail bei /user/edit sofort, ohne Bestätigungslink an die neue Adresse. Der Legacy-AccountChangeFlow mit Pending-Token (10 Tage) ist nicht nachgebaut. Entspricht der docs-Note (Z.427) und P2503-226-Scope. |
| E-Mail-Adresse-Wiederholungsfeld + Übereinstimmungs-Validierung im Änderungsformular | fehlt | keine gefunden (kein #edit-account-Formular vorhanden; Core-User-Edit hat nur ein einzelnes E-Mail-Feld) | Legacy-Mockup verlangt 'E-Mail wiederholen / muss übereinstimmen'. Kein Äquivalent im Drupal-Stand. |
| Bei E-Mail-Änderung Benachrichtigung an ALTE und NEUE Adresse (zwei Erfolgs-Mails nach Bestätigung) | fehlt | keine gefunden (kein Mail-Versand-Code an alte+neue Adresse in web/modules/custom/; kein passender easy_email-Type) | Duale Benachrichtigung fehlt vollständig — direkte Folge des fehlenden AccountChangeFlow. |
| Bis zur Bestätigung bleibt Nutzer voll mit alter Adresse eingeloggt (Pending-Zustand ohne Session-Verlust) | fehlt | keine gefunden (kein Pending-State/Flow-Entity für E-Mail-Wechsel im Code) | Ohne AccountChangeFlow gibt es keinen Pending-Zwischenzustand; Core wechselt sofort, sodass das 'alte Adresse bleibt gültig bis Bestätigung'-Verhalten nicht abgebildet ist. |
| Token-Status-Landeseite 'E-Mail-Änderung bestätigen' (/de/projektportal/account/email-bestaetigen?token=…) mit Zuständen gültig/ungültig/abgelaufen | fehlt | keine gefunden (keine entsprechende Route in solcom_projektpartner.routing.yml; vorhandene verify-Route gilt nur der Registrierungs-Aktivierung) | Die Bestätigungs-Landeseite für den E-Mail-Wechsel existiert nicht. Vorhandene ProjektpartnerVerificationController::verify deckt nur Registrierungs-Aktivierung ab. |
| Passwortänderung wird sofort wirksam (Mechanismus) | teilweise | keine gefunden (kein Custom-Code); Mechanismus nur via Drupal-Core user/{uid}/edit. Rolle projektpartner ohne eigene Permissions: config/sync/user.role.projektpartner.yml (permissions: leer) | Technisch über Core-User-Edit möglich und sofort wirksam, aber kein portal-eigener Pfad/Link verifiziert; PPP-Rolle vergibt keine expliziten Account-Edit-Rechte (verlässt sich auf Core-Eigenkonto-Zugriff). Nicht als dediziertes Feature umgesetzt. |
Account löschen (Self-Service)
Teilweise
2 fehlt 2 abweichend 2 teilweise 3 umgesetzt
Die Daten-/Datei-Löschung ist sauber über Drupal-Core gelöst: ein erzwungener user_cancel_delete via hook_user_cancel_methods_alter + ein ProjektpartnerAccountPurger auf hook_user_predelete entfernt Profil, managed files und das private Dokumentenverzeichnis (per Kernel-Test belegt). Die umliegende Journey weicht aber an mehreren Stellen vom Legacy-Soll ab: durch notify.cancel_confirm=true ist der Flow ein E-Mail-gated Zweischritt (Bestätigungslink statt sofortigem Logout+Bestätigungsseite), die Nutzer-Mail ist eine Core-Confirm-Link-Mail statt der Legacy-Lösch-Quittung, die interne SOLCOM-Mail (bewerbung@) fehlt komplett, und bestehende Suchagenten (PppAlert) werden bei der Löschung nicht behandelt.
| Feature / Verhalten (Legacy-Soll) | Status | Evidenz (Drupal) | Anmerkung |
| Interne Lösch-Benachrichtigung an SOLCOM (bewerbung@) | fehlt | keine gefunden (kein hook_user_cancel / kein Mailversand in solcom_projektpartner.module:290-296 oder ProjektpartnerAccountPurger.php; ProjektpartnerMailManager.php hat keine delete/intern-Methode) | Legacy AccountController::deleteAction sendet parallel eine interne Mail an bewerbung@. Im Drupal-Stand existiert nichts, was SOLCOM intern über die Selbstlöschung informiert. |
| Behandlung bestehender Suchagenten des Kontos | fehlt | keine gefunden (solcom_projektpartner_alert hat kein hook_user_predelete/hook_user_cancel; src/Entity/PppAlert.php:51-72 nutzt EntityOwnerTrait mit uid-entity_reference, keine Cleanup-Logik) | PppAlert (Suchagent) hängt per uid am User, wird bei der Löschung weder mitgelöscht noch in einen Gast-Suchagenten überführt → verwaister/dangling Datensatz. Legacy-AK fordert explizit konsistente Behandlung (Zielverhalten noch festzulegen) — derzeit kein definiertes Verhalten. |
| Automatischer Logout + Bestätigungsseite nach Löschung | abweichend | web/core/modules/user/src/Form/UserCancelForm.php:151,158,164-167 (cancel_confirm: kein Sofort-Delete, Redirect auf entity.user.canonical, Status 'Bestätigungs-Mail gesendet') | Legacy: sofort nach Anfrage Logout + Bestätigungsseite. IST (durch cancel_confirm=true): erst Hinweis 'Bestätigungslink per E-Mail gesendet' + Redirect auf eigene User-Seite; Logout/Delete erst nach Klick auf den E-Mail-Link (UserController::confirmCancel → user_cancel). Anderes Timing, kein dedizierter Bestätigungs-Screen wie im Legacy-Flow. |
| Lösch-Bestätigungsmail an den Nutzer | abweichend | config/sync/user.settings.yml:7 + web/core/modules/user/src/Form/UserCancelForm.php:151 (_user_mail_notify('cancel_confirm')); keine DeleteNotice-Mail im Modul (kein hook_mail/Mail-Key in ProjektpartnerMailManager.php oder .module) | Core sendet eine Mail an den Nutzer — aber inhaltlich/zeitlich anders: ein VOR-Löschung Confirm-Link (cancel_confirm), NICHT die Legacy-NACH-Löschung-Quittung (Template DeleteNoticeForPP.html, Betreff 'Bestätigung: Löschung SOLCOM Nutzerkonto', from no-reply@solcom.de). status_canceled=false → keine Post-Delete-Mail. Soll-Mail fehlt; nur ein abweichendes Surrogat vorhanden. |
| Erreichbarkeit der Self-Service-Löschung (Route/Link) | teilweise | config/install/user.role.projektpartner.yml:15 (Permission 'cancel account'); web/modules/custom/solcom_projektpartner/solcom_projektpartner.module:146-157 (forces user_cancel_delete) | Löschung läuft über Core-Route entity.user.cancel_form (/user/{uid}/cancel) — kein Custom-Route/-Form nötig. ABER: kein eigener Menüpunkt im Account-Menü; links.menu.yml listet nur Profil/Suchagent/Merkliste. Erreichbar nur über die User-Edit-Seite → schlechtere Discoverability als Legacy. |
| Automatisierte Testabdeckung der Journey | teilweise | tests/src/Kernel/ProjektpartnerAccountDeletionTest.php:105-142 (nur Profil/Files/Verzeichnis) | Test deckt nur Daten-/Datei-Purge ab (via $user->delete()). Keine Tests für die zwei Mails (intern+Nutzer), Session-/Logout-Ende oder Suchagenten-Behandlung — also genau die Dev-Eval-Punkte aus dem Legacy-Soll, die offen/abweichend sind, sind ungetestet. |
| Bestätigung vor Löschung (Confirm-Step) | umgesetzt | config/sync/user.settings.yml:7 (cancel_confirm: true); web/modules/custom/solcom_projektpartner/solcom_projektpartner.module:164-186 (Warntext im user_cancel_form); web/core/modules/user/src/Form/UserCancelForm.php:151,158 | Bestätigung existiert sogar zweistufig: Confirm-Formular plus E-Mail-Bestätigungslink. Warntext zu unwiderruflicher Löschung von Account/Profil/Unterlagen wird eingeblendet. |
| Löschung von Account + personenbezogenen Daten | umgesetzt | web/modules/custom/solcom_projektpartner/solcom_projektpartner.module:290-296 (hook_user_predelete → purger); src/Service/ProjektpartnerAccountPurger.php:28-52,60-78; tests/src/Kernel/ProjektpartnerAccountDeletionTest.php:128-136 | User + projektpartner-Profil werden gelöscht; Test prüft, dass User/Profil/Felder (Vor-/Nachname) weg sind. Vollständig. |
| Löschung hochgeladener Dokumente aus dem Speicher (DSGVO) | umgesetzt | src/Service/ProjektpartnerAccountPurger.php:40-41,89-127,132-147; tests/src/Kernel/ProjektpartnerAccountDeletionTest.php:128-132 | field_documents-Files inkl. file_usage-Rows und das gesamte private://ppp/<uid>-Verzeichnis werden entfernt. Per Test belegt (File geNULLt, Verzeichnis weg, file_usage leer). |
Profil & Dokumente pflegen
Teilweise
1 fehlt 4 abweichend 3 teilweise 1 unklar 10 umgesetzt
Die Felddeckung der Profil-Journey ist nahezu vollständig: Alle pflegbaren Legacy-Profilfelder (Person, Kontakt, Stundensatz, Verfügbarkeit, Einsatzorte, Netzwerke, Skills, Sprachen, Anschreiben) existieren als Profilfelder und sind im Edit-Form-Display sichtbar, RMS-Nummer/Standort-Code sind korrekt nicht pflegbar, Upload/Löschung läuft über file_generic. Zwei systematische Lücken ziehen das Gesamtbild auf "partial": (1) ALLE Profilfelder sind required:false — die im Legacy als Pflicht markierten Felder (Anrede, Vorname, Nachname, Mobil, E-Mail, Straße, PLZ, Ort, Land) werden nicht erzwungen; (2) der Controller rendert den default-Form-Display, wodurch system-/registrierungs-gebundene Consent-Metadaten (privacy/terms consent + version + at) im Profil-Edit fälschlich editierbar erscheinen. Zusätzlich mehrere Widget-/Typ-Divergenzen (Land als Freitext statt Select, Verfügbarkeit als String statt Datum, E-Mail außerhalb des Profilformulars, Sprachen ohne max-5-Constraint).
| Feature / Verhalten (Legacy-Soll) | Status | Evidenz (Drupal) | Anmerkung |
| Pflichtangaben-Validierung (Legacy-Pflicht: Anrede*, Vorname*, Nachname*, Mobil*, E-Mail*, Straße*, PLZ*, Ort*, Land*) | fehlt | field.field.profile.projektpartner.field_{salutation,first_name,last_name,mobile,street,postal_code,city,country}.yml: alle 'required: false' | KEIN einziges Profilfeld ist required. Die gesamte Pflichtangaben-Dimension des Legacy-Profil-Edit ist nicht umgesetzt — Speichern mit leeren Pflichtfeldern wäre möglich. |
| Feld 'Land' als Auswahlliste (Länderliste) | abweichend | core.entity_form_display.profile.projektpartner.default.yml: field_country type string_textfield (size 60) | Legacy: Select mit Länderliste (Deutschland/Österreich/Schweiz/…). Drupal: Freitext-String, keine Optionsliste, keine Validierung. |
| Zusätzliches Skills-Feld (Taxonomy-Tags) ohne Legacy-Profil-Pendant | abweichend | field_skills type entity_reference (taxonomy_term, vocabulary skill); form_display entity_reference_autocomplete_tags w24 | Drupal hat ZWEI Skill-Felder: field_focus_areas (Legacy-Textarea) und field_skills (Tags). Letzteres existiert im Legacy-Profil-Edit-Mockup nicht — Erweiterung, kein Legacy-Soll. |
| Dokumente hochladen (.pdf/.docx/.doc, max. 5 MB) | abweichend | field.field.profile.projektpartner.field_documents.yml: file_extensions 'pdf doc docx odt txt rtf jpg jpeg png', max_filesize '10 MB', file_directory 'ppp/[current-user:uid]'; form_display file_generic w31 | Upload vorhanden, aber Constraints weiter als Legacy: 9 Extensions statt 3, 10 MB statt 5 MB. Privater Speicherpfad pro User. Funktional erfüllt, Limits abweichend. |
| Consent-/System-Metadaten korrekt aus dem Profil-Edit ausgeblendet | abweichend | core.entity_form_display.profile.projektpartner.default.yml: field_privacy_consent w25, field_privacy_consent_at w26, field_privacy_consent_version w27, field_terms_consent w28, field_terms_consent_at w29, field_terms_consent_version w30 | privacy/terms consent + _at + _version sind registrierungs-/systemverwaltete Metadaten, erscheinen aber im default-Form-Display und damit im Profil-Edit als editierbar. Im Legacy-Profil-Edit-Mockup nicht vorhanden — sollten ausgeblendet sein. |
| Feldcluster 'Ihre Kontaktdaten': Telefon, Mobil, E-Mail, Webseite, Straße/Nr., PLZ, Ort, Land | teilweise | core.entity_form_display.profile.projektpartner.default.yml (field_phone w6, field_mobile w7, field_website w8 link_default, field_street w9, field_house_number w10, field_postal_code w11, field_city w12, field_country w13) | E-Mail fehlt im Profilformular (Drupal-User-Property, separat über Core /user/{uid}/edit; keine modul-eigene Zugangsdaten-Route). 'Straße, Nr.' in Legacy ein Feld, hier auf field_street + field_house_number aufgeteilt. |
| Feldcluster 'Stundensatz und Verfügbarkeit' | teilweise | core.entity_form_display.profile.projektpartner.default.yml: field_hourly_rate w15 type number; field_availability w16 type string_textfield | Stundensatz als number-Widget (Legacy: 'nur Zahlen') = done. Verfügbarkeit divergent: string_textfield statt Datum; keine Datums-/Zukunfts-Validierung wie Legacy (dd.mm.yyyy, nur Zukunft). |
| Sprachen-Auswahl (Mehrfachauswahl, max. 5) | teilweise | field.storage.profile.field_languages.yml cardinality -1; form_display options_buttons w21; kein Length/Count-Constraint in solcom_projektpartner.module:104 | Sprachfeld mit Optionsset vorhanden (de/en/fr/es/it/other), aber cardinality -1 (unbegrenzt). Legacy-Limit max. 5 wird NICHT erzwungen. |
| SOLCOM Freiberufler Magazin / Newsletter-Opt-in im Profil-Edit | unklar | keine gefunden (kein field_magazine/newsletter in config/install/*.profile.projektpartner*) | Legacy-Profil-Edit-Mockup (#edit-profile) enthält KEIN Magazin-Feld (nur Registrierung/Bewerbung). Abwesenheit im Profilfeld-Display ist daher korrekt; falls Newsletter-Status andernorts (User/Webform) gepflegt wird, nicht in dieser Journey verifizierbar. |
| Route /projektportal/profil/bearbeiten (Edit-Einstieg, eigenes Profil, Auto-Anlage bei Nicht-Existenz) | umgesetzt | solcom_projektpartner.routing.yml:33; src/Controller/ProjektpartnerProfileController.php:65 | Clean-URL ohne ID, scoped auf currentUser, legt Profil bei Bedarf an. Permission 'update own projektpartner profile'. |
| Einseitiges Formular (kein Multi-Step), alle pflegbaren Felder auf einen Blick | umgesetzt | src/Controller/ProjektpartnerProfileController.php:75 | getForm($profile,'edit') rendert ein Single-Page-Entity-Form. 'edit'-Mode fällt auf default-Display zurück (kein eigener edit-Display). |
| Feldcluster 'Zu Ihrer Person': Titel, Anrede, Vorname, Nachname, Geburtsdatum, Firma | umgesetzt | core.entity_form_display.profile.projektpartner.default.yml (field_title w0, field_salutation w1, field_first_name w2, field_last_name w3, field_birthdate w4 datetime_default, field_company w5) | Alle sechs Felder vorhanden. Anrede mit 4 Optionen (Herr/Frau/Divers/Keine Angabe) vs. Legacy 2 — erweitert, kein Gap. Geburtsdatum als datetime_default. |
| Feldcluster 'Einsatzorte' (Mehrfachauswahl: Alle/AT/CH/DE/Andere) | umgesetzt | field.storage.profile.field_deployment_locations.yml allowed_values (all/AT/CH/DE/other), cardinality -1; form_display options_buttons w14 | Optionsset und Mehrfachauswahl entsprechen dem Legacy-Mockup. |
| Feldcluster 'Netzwerke': GULP-ID, Freelance.de, XING, Freelancermap | umgesetzt | core.entity_form_display.profile.projektpartner.default.yml (field_gulp_id w17, field_freelance_de w18, field_xing w19, field_freelancermap w20) | Alle vier Netzwerkfelder als string_textfield vorhanden. Legacy-Hinweis 'GULP-ID oder URL' nicht separat validiert (unkritisch). |
| Fachliche Schwerpunkte / Ihre Skills (Freitext, max. 500 Zeichen) | umgesetzt | field_focus_areas type string_long, form_display string_textarea w23; Length-Constraint max 500 in solcom_projektpartner.module:127-136 | Legacy-Skill-Textarea = field_focus_areas. 500-Zeichen-Limit per addPropertyConstraints erzwungen (nicht nur Description). |
| Vorlage für künftige Bewerbungs-Anschreiben (Freitext, max. 2000 Zeichen) | umgesetzt | field_cover_letter type string_long, form_display string_textarea w22; Length-Constraint max 2000 in solcom_projektpartner.module:116-124 | 2000-Zeichen-Limit per Constraint erzwungen. |
| Dokumente löschen | umgesetzt | core.entity_form_display.profile.projektpartner.default.yml: field_documents type file_generic; field.storage.profile.field_documents.yml cardinality -1 | Löschung über das Remove-Standardverhalten des file_generic-Widgets (Default-Widget-Verhalten, kein custom Delete-Pfad). Mehrfach-Upload via cardinality -1; Legacy 'bis 5 Dateien' nicht hart begrenzt. |
| Systemfelder NICHT pflegbar: RMS-Nummer | umgesetzt | migrate_plus.migration.solcom_projektpartner_neos_profile.yml:23 (ppp_id: rmsnumber); kein field_rms_number in config/install; keine RMS-Zeile im form_display | RMS-Nummer wird nur als ppp_id (User-Lookup) migriert, existiert nicht als Profilfeld und ist im Edit-Form korrekt abwesend. |
| Systemfelder NICHT pflegbar: Standort-Code | umgesetzt | keine gefunden (grep -ri standort/location_code/niederlassung über config/ + src/ ohne Profilfeld-Treffer) | Kein Standort-Code-Feld existiert in Drupal überhaupt; durch Abwesenheit erfüllt — entsprechend aber auch nicht als systemverwalteter Wert vorhanden. |
Projekt finden & merken (Merkliste)
Weitgehend
2 fehlt 4 abweichend 1 teilweise 8 umgesetzt
Die Merkliste-Mechanik (anonym clientseitig via localStorage, eingeloggt serverseitig via user.data, Login-Merge, Add/Remove, Zähler, Toggle auf Liste/Detail/Merkliste-Seite) ist vollständig und idiomatisch nachgebaut. Zwei Soll-Punkte fehlen bzw. weichen ab: die Filter-Persistenz über den Besuch hinaus ist gar nicht umgesetzt (alle exposed Filter haben remember:false, kein Cookie/Session/URL-Persist), und der Einsatzort-Filter ist ein freier String-Filter auf field_sf_city statt des Legacy-RMS-Code-Mappings (DE/CH/A/Remote/International). Zusätzlich weicht das Merklisten-Limit ab und das automatische Bereinigen veralteter Einträge ist nur anzeigend statt löschend.
| Feature / Verhalten (Legacy-Soll) | Status | Evidenz (Drupal) | Anmerkung |
| Gewählte Filter bleiben bei erneutem Besuch erhalten (Legacy: Cookies searchParameter/location/business/zip) | fehlt | config/sync/views.view.project_offers_listing.yml: alle expose-Blöcke tragen 'remember: false' (z.B. Zeilen 311,366,402,438,485); keine Cookie/Session/URL-Persistenz im Code gefunden (grep über web/modules/custom + js: keine gefunden) | Filter-Persistenz fehlt komplett. Alle exposed Filter haben remember:false und es gibt keinen Cookie-/Session-/sessionStorage-Mechanismus. Nach Reload sind die Filter zurückgesetzt — AC 'Filter bleiben über den Besuch erhalten' nicht erfüllt. |
| Alle Bookmarks löschen / Merkliste leeren (Legacy: api/bookmark/removeall, auto-clear nach Bewerbung) | fehlt | grep removeall/removeAll/clear/deleteAll über web/modules/custom/solcom_project_offer/: keine gefunden; routing hat nur add/remove/merge/list | Kein removeAll-Endpoint und keine Bulk-Clear-Funktion. Anmerkung: das Auto-Clear-nach-Bewerbung gehört zur Bewerbungs-Journey und liegt außerhalb der fünf Kern-AKs dieser Journey; der reine removeAll fehlt aber dennoch. |
| Projektliste per Einsatzort filtern (DE/CH/A/Remote/International, RMS-Codes D1-D9) | abweichend | config/sync/views.view.project_offers_listing.yml:377-410 (field_sf_city_value, plugin_id string, identifier 'einsatzort'); separater numeric-Filter field_remote_share_value:444-490 (identifier 'remote') | Einsatzort ist ein freier String-Filter auf die Stadt, kein RMS-Regions-Code-Mapping (DE/CH/A/International, D1-D9 PLZ-Regionen). Remote ist als separater numerischer Remote-Anteil-Filter abgebildet, nicht als Einsatzort-Option. Das Legacy-Sollwert-Set der Regionscodes ist nicht reproduziert. |
| Zähler zeigt jederzeit Anzahl gemerkter Projekte (Legacy: api/bookmark/count) | abweichend | web/themes/custom/byte_theme/js/watchlist.js:101-103 (data-watchlist-counter = list.length); kein dedizierter count-Endpoint im routing | Zähler funktioniert, ist aber clientseitig aus der Listenlänge abgeleitet statt über einen dedizierten count-Endpoint wie Legacy api/bookmark/count. Funktional erfüllt, Mechanik abweichend. |
| Automatisches Bereinigen nicht mehr existenter/abgelaufener Projekte aus der Merkliste (Legacy indexAction entfernt veraltete Bookmarks aus Store) | abweichend | WatchlistController.php:230-257 (stale-Zählung + Info-Banner), aber kein Aufruf von watchlistStorage->remove für stale-IDs; Soll: docs/context/0009-...html:2179/2186 | Veraltete Einträge werden nur als Banner angezeigt, NICHT aus user.data gelöscht. Legacy entfernte sie aktiv aus dem Store. Folge: Banner erscheint bei jedem Besuch erneut, der Store wächst mit toten SF-IDs. |
| Merklisten-Limit (Legacy: max 7 Einträge, status 3 = Maximum erreicht) | abweichend | js/watchlist.js:27 (MAX_ANON_ENTRIES = 5) + :298-310 (Cap nur anonym); WatchlistStorage.php Kommentar :13-15 + add():75-85 (kein Cap für eingeloggte User) | Limit weicht dreifach ab: anonym 5 statt 7, eingeloggt gar kein Cap (Legacy hatte einheitlich 7 mit explizitem status-3-Rückgabewert). Cap nur clientseitig durchgesetzt, serverseitig nicht. |
| Projektliste per Volltext filtern (Legacy: searchParameter über Titel+Beschreibung) | teilweise | config/sync/views.view.project_offers_listing.yml:338-368 (field_sf_long_description_value, identifier 'aufgaben', exposed:true) | Volltext-Filter existiert, aber nur auf field_sf_long_description (ein Feld), nicht kombiniert über Titel+Beschreibung; kein Search-API-Volltext. Funktional vorhanden, Reichweite enger als Legacy-searchParameter. |
| Projektliste per Branche filtern | umgesetzt | config/sync/views.view.project_offers_listing.yml:292-330 (field_sf_industry_target_id, identifier 'branche', exposed:true, vid:industry) | Branchen-Filter über Taxonomie industry vorhanden und exposed. |
| Projekt aus Liste auf Merkliste setzen/entfernen (Herz-Symbol auf Teaser) | umgesetzt | web/themes/custom/byte_theme/templates/node/node--project-offer--teaser.html.twig (referenziert watchlist-toggle); views.view.project_offers_listing.yml:509 (view_mode: teaser) | Listing rendert teaser, das teaser-Template bindet das watchlist-toggle ein. Klick-Toggle in js/watchlist.js:259-318. |
| Projekt aus Detailseite auf Merkliste setzen/entfernen | umgesetzt | web/themes/custom/byte_theme/templates/node/node--project-offer--full.html.twig (referenziert watchlist-toggle) | full-Template (Detailseite) bindet das watchlist-toggle ein. |
| Merkliste anonym clientseitig halten (Gast: localStorage) | umgesetzt | web/themes/custom/byte_theme/js/watchlist.js:40-65 (readList/writeList localStorage), :292-317 (anonymer Pfad) | Anonyme Merkliste vollständig in localStorage; Toggle, Zähler, Cross-Tab-Sync (storage event :324-329) umgesetzt. |
| Merkliste eingeloggt serverseitig persistieren (User: DB) | umgesetzt | WatchlistController.php:87-137 (add/remove); WatchlistStorage.php:75-105 (user.data-basiert); routing _role:projektpartner + CSRF | Serverseitige Persistenz über user.data für Rolle projektpartner; API add/remove/list mit CSRF und _role-Gate. |
| Add/Remove-Aktion (API-Endpoints) | umgesetzt | WatchlistController.php:87-137 (add, remove); routing watchlist_api_add/remove (POST, CSRF, _role) | Symmetrisches Add/Remove serverseitig + clientseitig. Vorhanden. |
| Merge der Gast-Merkliste in das Konto beim Login | umgesetzt | WatchlistController.php:145-166 (merge); WatchlistStorage.php:118-132 (Union ohne Cap); js/watchlist.js:199-213 (serverMerge bei erstem auth-Init, clearLocalStorage) | localStorage-Einträge werden beim ersten eingeloggten Init in den Server-Store gemergt (Union) und localStorage geleert. Entspricht Legacy LoginController-Merge. |
| Merkliste-Übersichtsseite anzeigen (/projektportal/merkliste) | umgesetzt | WatchlistController.php:174-375 (page, buildAuthenticatedPage, buildAnonymousPage); routing solcom_project_offer.watchlist | Server-gerenderte Seite für Projektpartner aus user.data; localStorage-Bootstrap-Form für Anonyme. Card-View-Mode-Rendering, Empty-State, Window-Access-Check vorhanden. |
Auf ein Projekt bewerben
Teilweise
8 fehlt 3 abweichend 2 teilweise 9 umgesetzt
Die Journey ist im Drupal-Stand grundsaetzlich vorhanden: Modul solcom_project_offer bettet eine Webform (rsm_project_offer_application) in die Projekt-Detailseite ein, prefillt Profildaten via hook_webform_submission_form_alter und sendet beim Submit eine E-Mail an bewerbung@solcom.de mit CC an den Projekt-Kontakt und CV-Anhang (RsmProjectOfferMailHandler). Entscheidend ist aber: das native Formular ist nur ein hinter dem State-Flag solcom_project_offer.rsm_fallback_enabled (Default FALSE) versteckter RSM-Fallback — im Normalbetrieb sieht der Nutzer stattdessen den externen 'Via Minggo bewerben'-Link. Zudem fehlen viele Legacy-Formularfelder, der Prefill ist unvollstaendig (keine Adresse), und es gibt keine Eingangsbestaetigungs-Mail an den Bewerber sowie keinen kombinierten Gast-Bewerben-plus-Konto-anlegen-Flow.
| Feature / Verhalten (Legacy-Soll) | Status | Evidenz (Drupal) | Anmerkung |
| Eingangsbestaetigungs-Mail an den Bewerber (Legacy: ApplicationConfirmation, BCC bewerbung@) | fehlt | keine gefunden | Kein Mailversand an die Bewerber-E-Mail; nur inline confirmation. Flow-Inventar:1062-1068 markiert dies bereits als Luecke (Betreff 'Eingangsbestaetigung / Projekt-Nummer', From no-reply@solcom.de). |
| Kombinierter Gast-Flow: Bewerben + gleichzeitig Konto anlegen | fehlt | keine gefunden | Webform hat keine Passwort-/Registrierungsfelder; Gast bewirbt sich anonym ohne Kontoanlage. Flow-Inventar:1137-1140 markiert den kombinierten Flow explizit als 'fehlt noch'. |
| Formularfeld Mobil (separat von Telefon) | fehlt | keine gefunden | Webform hat nur ein 'phone'-Feld; Legacy-Mockup #job-application hat Telefon UND Mobil (Mobil Pflicht). |
| Formularfelder Titel, Geburtsdatum, Firma | fehlt | keine gefunden | Im Cluster 'Zu Ihrer Person' des Legacy-Mockups vorhanden (Titel/Geburtsdatum/Firma), in der Drupal-Webform nicht abgebildet. |
| Formularfeld Eigene Webseite (mit URL-Validierung) | fehlt | keine gefunden | Legacy-Kontaktdaten-Cluster enthaelt 'Eigene Webseite'; fehlt in der Webform. |
| Magazin-Checkbox (SOLCOM Freiberufler Magazin / Newsletter-Opt-in) | fehlt | keine gefunden | Legacy-Mockup #job-application hat Magazin-Checkbox; Flow-Inventar:984-987 nennt magazine='true'-Mitfuehrung + Geopard-Newsletter-Edge. Kein magazine-Element in der Webform. |
| Formularfelder Einsatzorte, Stundensatz, Verfuegbarkeit | fehlt | keine gefunden | Legacy-Mockup #job-application enthaelt diese Cluster (Einsatzorte-Checkboxen, Stundensatz EUR, Verfuegbarkeitsdatum). In der Webform nicht vorhanden. |
| Bewerbungstext/Anschreiben fuer diese Bewerbung (max. 2000 Zeichen) | fehlt | keine gefunden | Legacy hat projektspezifisches Anschreiben-Textfeld; fehlt in der Webform. |
| Bewerbungsformular auf der Projekt-Detailseite (Legacy-Route /projektliste/<projekt>/bewerben) | abweichend | solcom_project_offer.module:66-98 (_solcom_project_offer_add_rsm_apply_region) | Formular ist nur RSM-Fallback hinter State-Flag rsm_fallback_enabled (Default FALSE, module:67); im Normalbetrieb erscheint stattdessen externer 'Via Minggo bewerben'-Link (module:86-97). Keine eigene /bewerben-Route, Formular wird per hook_node_view inline in die Detailseite gehaengt. |
| Datei-Upload-Regeln wie beim Profil-Upload (Typ/Groesse) | abweichend | webform...yml:94-97 (#file_extensions pdf doc docx, #max_filesize 10MB, #multiple false) | Typen pdf/doc/docx korrekt, aber max_filesize ist 10 MB statt Legacy 5 MB; single file statt 'bis zu 5 Dateien'; und 'bestehende Unterlagen waehlbar' (Auswahl aus Profil-Dokumenten) fehlt komplett — nur Neu-Upload moeglich. |
| Mail-Inhalt mit vollem Bewerber-/Projektdatensatz (Legacy: skills, applicationText, languages, networks, hourlyrate, availability) | abweichend | RsmProjectOfferMailHandler.php:194-253 (buildEmailBody) | Drupal sendet ein schlankes Power-App-Zeilenformat mit nur Basis-Kontakt + Adresse + Datei + Session/PPP-ID. Legacy-Mail (Flow-Inventar:1208) trug zusaetzlich skills, applicationText, languages, Netzwerke, hourlyrate, availability, utm-Daten. |
| Prefill aus Profil bei eingeloggten Nutzern (Name, Anrede, Telefon, E-Mail) | teilweise | solcom_project_offer.module:184-265; WebformPrefillFromProfileTest.php:206-238 | Prefill fuer first_name, last_name, phone (field_phone/field_mobile-Fallback), gender (aus field_salutation), email (aus User) und ppp_id vorhanden. ABER: Adressfelder street/postal_code/city/country werden NICHT vorbefuellt (form_alter endet :274), obwohl sie required sind — Profilnutzer muss Adresse jedes Mal neu eingeben. Legacy-Soll: ganzer Cluster vorbefuellt. |
| Temp-File-Lifecycle / automatischer Cleanup der Upload-Dateien | teilweise | solcom_project_offer.module:406-414 (hook_cron setzt nur Timestamp); webform...yml:264 (purge: none) | Verwaiste temporaere managed_files raeumt Drupal-Core-GC (system_cron) automatisch ab — Legacy-AC weitgehend gedeckt. Rest-Gap: abgesendete Bewerbungs-Dateien in private://rsm-applications/ bleiben wegen purge: none dauerhaft liegen; kein dedizierter Cleanup im Modul (hook_cron speichert nur last_cron). |
| Zugriff oeffentlich (Gast) ODER eingeloggt | umgesetzt | webform.webform.rsm_project_offer_application.yml:273-279 (access.create roles anonymous + authenticated) | Beide Rollen duerfen das Formular absenden, wie im Legacy-Soll. |
| Versteckte Kontext-Felder referenzieren das Zielprojekt | umgesetzt | webform...yml:106-136 (hidden: node_id/title/lfd_nr/contact_email/session_uuid/ppp_id); solcom_project_offer.module:165-182 | Projektkontext (Node-ID, Titel, RSM-LfdNr, Kontakt-E-Mail) wird in Hidden-Feldern gesetzt; entspricht Legacy 'versteckte Felder referenzieren das Zielprojekt'. |
| Datei-Anhang ist Pflicht (mind. 1 Dokument) | umgesetzt | webform...yml:90-97 (cv managed_file, #required: true) | CV-Upload required: true; entspricht Legacy 'mind. 1 Dokument Pflicht'. |
| Mailversand an SOLCOM (bewerbung@solcom.de) mit CC Projekt-Kontakt + Anhang | umgesetzt | RsmProjectOfferMailHandler.php:129-160; solcom_project_offer.module:128-144; RsmProjectOfferMailHandlerTest.php:91-99 | Mail geht an bewerbung@solcom.de, CC = field_contact_email, CV als Anhang; Test verifiziert to/Cc/Anhang. Fehlende Kontakt-E-Mail loggt Warnung statt Abbruch (sinnvoll). |
| Erfolgsseite nach Absenden | umgesetzt | webform...yml:242-245 (confirmation_type inline, confirmation_message) | Inline-Bestaetigung 'Vielen Dank fuer Ihre Bewerbung...' nach Submit; deckt Legacy-AC 'Bestaetigungsseite'. |
| Formularfelder: Anrede, Vorname, Nachname, Telefon, E-Mail, Adresse (Strasse/PLZ/Ort/Land) | umgesetzt | webform...yml:18-85 (section_person, section_address) | Pflicht-Kernfelder vorhanden; Anrede bietet zusaetzlich 'Divers' (Legacy nur Herr/Frau). |
| Datenschutz-Zustimmung (Pflicht-Checkbox) | umgesetzt | webform...yml:98-105 (privacy_accepted checkbox #required true) | Pflicht-Checkbox mit Link auf /datenschutz; entspricht Legacy-Datenschutzerklaerung. |
| Ungueltige Datei -> Fehlermeldung, kein Versand | umgesetzt | webform...yml:94-97 (#file_extensions, #max_filesize), RsmProjectOfferMailHandler.php:95-99 (postSave nur bei neuem Submit) | Webform-Standardvalidierung blockt falsche Typen/Groesse vor Submit; Mailversand erfolgt erst in postSave bei erfolgreichem Submit. Kein expliziter Negativ-Test gefunden (nur Happy-Path + missing-file im MailHandlerTest). |
| Bewerbertyp-Erkennung (Gast vs. eingeloggter User) im Mailtext | umgesetzt | RsmProjectOfferMailHandler.php:212-214 (Bewerbertyp Account User/Anonymous User) | Anonymous vs. Account User wird ermittelt und in der Mail ausgewiesen; entspricht Legacy guestOrUser-Unterscheidung. |
Suchagent / Projekt-Alert & Benachrichtigung
Teilweise
10 fehlt 3 abweichend 2 teilweise 5 umgesetzt
Der Drupal-Nachbau deckt nur den eingeloggten Projektpartner-Pfad ab: eine ppp_alert-Entity mit Skill-Kriterien und Regionen, ein CRUD-Formular, ein Cron-Matcher mit Treffer-Mail und eine Migration. Die gesamte Gast-Dimension (Double-Opt-In, Kontaktdaten, Hash-Abmeldung, Newsletter) fehlt komplett, obwohl die Journey laut Legacy ausdruecklich "Gast oder Projektpartner" ist. Zentrale Mechaniken weichen ab: Schlagwort-UND/ODER-Logik wurde durch Skill-Term-Overlap ersetzt, der Gebiets-AUSSCHLUSS wurde zu einer Regions-INKLUSION invertiert, und die Per-Treffer-Dedup-Entity wurde durch einen globalen Timestamp ersetzt.
| Feature / Verhalten (Legacy-Soll) | Status | Evidenz (Drupal) | Anmerkung |
| UND/ODER-Logik: bis 4 Begriffe je Feld (UND), bis 4 Felder (ODER), plus bis 2 Ausschluss-Felder | fehlt | src/Service/PppAlertMatcher.php:93-94 (field_skills.target_id IN), keine noTags-Felder gefunden | Komplexeste Legacy-Eigenschaft (4 OR-Gruppen je bis 4 AND-Tags) fehlt vollstaendig. Matcher nutzt simplen IN-Overlap = reines ODER ueber alle Skills, kein UND-innerhalb-Feld, keine OR-Gruppen-Struktur. |
| Ausgeschlossene Schlagworte (noTags / tagsNot, bis 2 Felder) | fehlt | keine gefunden (kein noTags-Feld in PppAlert.php, kein Ausschluss-Filter in PppAlertMatcher.php) | Kein Ausschluss-Tag-Feld in Entity, Form, Matcher oder Migration. Komplett fehlend. |
| Gast: Double-Opt-In per E-Mail-Link (Aktivierung, active=FALSE bis bestaetigt) | fehlt | keine gefunden (kein Hash/Token-Feld, keine signIn-Route in solcom_projektpartner_alert.routing.yml:1-8) | Kein Double-Opt-In. Alert ist sofort aktiv (status default TRUE, PppAlert.php:119-122). Hinweis: die 10-Tage-Frist war im Legacy ohnehin nur Info-Text, technisch nicht erzwungen (Flow-Inventar 2777) - Backlog-Item sollte 'DOI bauen' lauten, nicht 'DOI mit erzwungener 10-Tage-Expiry'. |
| Gast-Kontaktdatenblock im Formular (Anrede, Titel, Vorname, Nachname, E-Mail) | fehlt | src/Form/PppAlertForm.php:91-165 (nur Skills/Regionen/Status); Route _user_is_logged_in:TRUE in routing.yml:8 | Formular ist login-pflichtig, kennt nur Kriterien-Felder. Kein Gast-Pfad, keine Kontaktdatenfelder. |
| Newsletter / Freiberufler-Magazin-Checkbox (nur Gaeste; eingeloggt field_magazine_option=TRUE) | fehlt | keine gefunden (keine newsletter/magazin-Logik in PppAlertForm.php) | Kein Newsletter-/Magazin-Opt-in im Alert-Formular. Legacy wertet newsletter==1 aus (Mailingwork bzw. field_magazine_option). |
| Datenschutz-Zustimmung (Pflicht-Checkbox) | fehlt | keine gefunden (kein policy/Zustimmungsfeld in PppAlertForm.php) | Pflicht-Datenschutz-Checkbox aus dem Mockup fehlt (nur fuer den Gast-Flow relevant). |
| Sofortiger Match+Versand beim Anlegen/Aktivieren/Aktualisieren | fehlt | src/Form/PppAlertForm.php:213-219 (save + redirect, kein send); Legacy: Flow-Inventar 2784 signInAction ruft send() sofort | Drupal speichert nur und leitet weiter; kein synchroner send(). Folge: ein neu angelegter Alert wird nie ueber bereits existierende passende Projekte benachrichtigt (Matcher sieht nur created > last_run). Eigenstaendige Verhaltens-Luecke neben Cron. |
| Abmelde-Fallback per E-Mail-Eingabe (Gast, hash=noemail) | fehlt | keine gefunden (keine signOutGuest-Route/Form in routing.yml) | Kein E-Mail-Eingabe-Fallback-Formular fuer Gaeste ohne Hash-Link. |
| Login-Uebernahme: Gast-Suchagenten auf Konto uebernehmen | fehlt | keine gefunden (keine Alert-/Guest-Merge-Logik in web/modules/custom/solcom_projektpartner/src) | Login-AC (Flow-Inventar 380) verlangt Uebernahme von Gast-Suchagenten beim Login. Da keine Gast-Alerts existieren, ist auch der Merge nicht gebaut. |
| Account-Loeschung: User-Alert in Gast-Alert ueberfuehren | fehlt | keine gefunden; Legacy: Flow-Inventar 2515 (UserProjectAlert -> GuestProjectAlert bei deleteAction) | Beim Account-Loeschen soll der Alert in einen Gast-Alert ueberfuehrt werden. Ohne Gast-Modell nicht abbildbar; aktuelle Behandlung bei Account-Loeschung im Alert-Modul nicht nachgewiesen. |
| Suchkriterien speichern: Schlagworte/Tags (Pflicht, Feld 1) | abweichend | web/modules/custom/solcom_projektpartner_alert/src/Entity/PppAlert.php:82-97; src/Form/PppAlertForm.php:110-121 | Legacy = freie Schlagworte gegen Projektdaten. Drupal = entity_reference auf kontrolliertes 'skill'-Vokabular (Select-Multiselect). Anderes Matching-Modell; ausserdem #required=FALSE statt Pflicht (Legacy Feld 1 ist Pflichtangabe). |
| Gebiets-/Regions-Ausschluss (Gebiete ausschliessen, inkl. PLZ-Bereiche DE 0-9, Remote) | abweichend | src/Entity/PppAlert.php:100-116 (field_deployment_locations); src/Service/PppAlertMatcher.php:130-162 | INVERTIERT: Legacy ist 'Gebiete AUSSCHLIESSEN' (CH waehlen = CH verbergen), Drupal ist 'Gesuchte Regionen' INKLUSION (CH waehlen = nur CH zeigen). Migrierte Region-Intents kippen damit semantisch. Werte-Set weicht ab: kein 'Remote', keine PLZ-Bereiche 0-9; AT/CH per Freitext-stripos auf field_region (heuristisch, TODO im Code). |
| Dedup: kein Doppelversand pro (Alert, Projekt) | abweichend | src/Service/PppAlertCronWorker.php:47-70 (STATE_KEY last_run); src/Service/PppAlertMatcher.php:90 (created > since) | Legacy nutzt persistente ProjectAlertNotification-Entity je (Alert,Projekt). Drupal hat KEINE solche Entity - Dedup laeuft rein ueber globalen last_run-Timestamp (created > since). Steady-State-Doppelversand wird verhindert, aber Mechanik anders: ein erneut/spaeter angelegter Alert sieht aeltere Projekte nie; keine Per-Paar-Historie. |
| Abmelden per Hash-Link (SignOut, Gast und User) | teilweise | config/install/easy_email.easy_email_type.ppp_alert.yml:19 (Link auf projektportal/suchagent); src/Form/PppAlertForm.php:154-162,225-239 (Loeschen/Deaktivieren) | Kein dedizierter Hash-basierter One-Click-SignOut und kein Gast-Abmeldeformular (hash=noemail Fallback). ABER: die Treffer-Mail verlinkt 'Mein Suchagent', wo der eingeloggte User den Alert deaktivieren oder loeschen kann. Fuer eingeloggte User also Abmeldung erreichbar; Gast-Abmeldung und Hash-Link fehlen. |
| Migration der Bestands-Suchagenten aus Neos | teilweise | src/Plugin/migrate/source/NeosAlertSource.php:55-62; config/install/migrate_plus.migration.solcom_projektpartner_alert_neos.yml | Source importiert nur skills + deployment_locations je ppp_id (neuester Eintrag). Importiert NICHT: Ausschluss-Schlagworte, Aktivierungsstatus und saemtliche Gast-Alerts. Zusammen mit der invertierten Regions-Semantik (siehe oben) Parity-Risiko fuer migrierte Nutzer. |
| Cron: neue passende Projekte matchen | umgesetzt | solcom_projektpartner_alert.module:16-19 (hook_cron); src/Service/PppAlertCronWorker.php:47-105; src/Service/PppAlertMatcher.php:83-120 | hook_cron laeuft, laedt aktive Alerts, matcht neue project_offer-Nodes via Matcher. Funktioniert (im Rahmen der divergenten Match-Logik). |
| Benachrichtigungs-Mail je Treffer mit Projektliste | umgesetzt | src/Service/PppAlertMailManager.php:50-122; config/install/easy_email.easy_email_type.ppp_alert.yml:16-21 | Easy-Email-Typ 'ppp_alert' mit Projektliste-Platzhalter und Name-Platzhalter; Mail wird je Alert mit Treffern versendet. |
| Alert aus Profil loeschen (eigener Alert, zugriffsgesichert) | umgesetzt | src/Form/PppAlertForm.php:225-239 (deleteAlert); src/Plugin/Validation/Constraint/PppAlertUniqueConstraintValidator.php:34-61; routing.yml:7 (manage own ppp alert) | Eingeloggter User loescht eigenen Alert ueber das Formular; per Permission/uid-Bindung abgesichert. Sogar besser als Legacy (dort konnte jeder eingeloggte User per ID jeden Alert loeschen - Security-Bug). |
| Genau ein Suchagent je Projektpartner (Unique-Constraint) | umgesetzt | src/Entity/PppAlert.php:44-46; src/Plugin/Validation/Constraint/PppAlertUniqueConstraintValidator.php:34-61; src/Form/PppAlertForm.php:50-62 (load-or-create) | Unique-Constraint je uid plus Redirect-to-Edit im Formular. Entspricht Legacy findByUser-Verhalten fuer eingeloggte User. |
| Baseline-Schutz gegen Massen-Mail beim Erstlauf | umgesetzt | solcom_projektpartner_alert.install:26-33 (hook_install setzt STATE_KEY=now); src/Service/PppAlertCronWorker.php:51-58 | hook_install seedet last_run=jetzt; fehlende Baseline setzt im ersten Cron-Lauf nur den Timestamp ohne Versand. Entspricht der Legacy-Anforderung 'historische Projekte nicht als neu werten'. |
Verfuegbarkeit melden
Großteils offen
12 fehlt 1 teilweise
Die Journey "Verfuegbarkeit melden" ist im Drupal-Nachbau praktisch nicht vorhanden. Es existiert keine login-freie Hash-Link-Route, kein Verfuegbarkeits-Formular (Datum + Upload + Nachricht + Consent), kein Submit-Handler/Mail-Versand an projektpartner@solcom.de und kein Erinnerungs-Cron/Queue, der faellige Datensaetze anlegt. Wiederverwendbar ist lediglich das Profilfeld field_availability (string), das aber nur im Registrierungs-/Profil-Edit-Kontext lebt, nicht in dieser eigenstaendigen Self-Service-Journey.
| Feature / Verhalten (Legacy-Soll) | Status | Evidenz (Drupal) | Anmerkung |
| Personalisierte Hash-Link-Route /projektportal/verfuegbarkeit?id=… (oeffentlich, ohne Login, einmalig gueltig) | fehlt | solcom_projektpartner.routing.yml (nur registration, verify, profile, profile_edit) — keine gefunden | Keine Availability-Route. routing.yml definiert ausschliesslich Registrierung, Verifikation und Profil-Self-Service. Kein login-freier Hash-Einstieg vorhanden. |
| Token/Hash-Validierung des Links (ungueltig/abgelaufen zeigt Fehlermeldung, gueltig zeigt Formular) | fehlt | keine gefunden | Es gibt eine Hash-Verifikation fuer Account-Aktivierung (ProjektpartnerVerificationController), aber keinen Availability-Hash-Mechanismus (Neos: UUIDv4 in Availability-Entitaet). |
| Verfuegbarkeits-Formular: Pflichtfeld 'Naechstmoegliche Verfuegbarkeit' (Datum, nur Zukunft, dd.mm.yyyy) | fehlt | keine gefunden — nur webform.webform.projektpartner_registration.yml existiert | Kein eigenstaendiges Availability-Formular. Das einzige Webform im Modul ist die Registrierung. Das Pflicht-Datum-mit-Zukunfts-Validierung des Mockups #availability ist nicht abgebildet. |
| Optionaler Datei-Upload (.pdf/.docx/.doc, max. 5 MB, bis zu mehreren Dateien) | fehlt | keine gefunden | Kein Upload-Element in einem Availability-Formular vorhanden (das Formular selbst fehlt komplett). |
| Freitext-Feld 'Ihre Nachricht an uns' | fehlt | grep 'Nachricht an uns' — keine gefunden | Nicht vorhanden; kein Availability-Formular existiert. |
| Pflicht-Consent-Checkboxen (Datenschutz + Nutzungsbedingungen) | fehlt | keine gefunden | Kein Availability-Formular und damit keine Consent-Validierung (Neos: policy-Checkbox Pflicht, sonst error='noDate'). |
| Submit-Verarbeitung: Validierung (Consent + gueltiges Datum), Mail an projektpartner@solcom.de mit Anhaengen, Betreff 'Aktualisierung RMS Nr. …' | fehlt | ProjektpartnerMailManager.php:18-142 (nur sendVerification, sendWelcome, sendProfileChangeNotification) — keine Availability-Methode | MailManager kennt keine Availability-Mail. Kein Submit-Controller (Neos: AvailabilityController::sendAction) im Modul vorhanden. |
| Bestaetigungs-E-Mail an den Projektpartner nach Submit | fehlt | keine gefunden | Nicht implementiert (Neos: Mail/Availability/Confirmation.html, optional bei vorhandener E-Mail-Adresse). |
| Ergebnis-/Status-Seite nach Submit (status=true/false) | fehlt | keine gefunden | Keine formResultAction-Entsprechung; Journey-Endpunkt fehlt vollstaendig. |
| Cron/Queue: faellige Datensaetze anlegen + Erinnerungs-Mails mit Hash-Link versenden (Neos: AvailabilityCommandController::importCommand, 4 Mailtypen) | fehlt | solcom_projektpartner.module:246-280 (nur Queue 'profile_change_sf_sync'); kein hook_cron fuer Availability — keine gefunden | Einziger QueueWorker ist ProfileChangeSfSync (Salesforce-Profilaenderung). Kein Drush-Command/Cron fuer Availability-Reminder, keine XML/Quell-Import-Logik, keine Hash-Generierung, keine 4 Mailtyp-Templates. |
| Datenmodell: eigenstaendiger Availability-Datensatz mit Hash, staffId, status (1=offen/gemeldet) | fehlt | field.field.profile.projektpartner.field_availability.yml (string-Feld am Profil) — keine Availability-Entitaet gefunden | Es existiert kein Availability-Entitaet/Tabelle. Status-Tracking 'gemeldet' nach Submit (AK) nicht abbildbar. |
| Cron: Temp-Datei-/Ordner-Cleanup fuer Availability-Uploads (40-Min-TTL Files, 30-Tage-TTL Ordner) | fehlt | keine gefunden | Kein hook_cron/QueueWorker fuer Upload-Cleanup. Da die Journey/Uploads selbst fehlen, ist auch das Cleanup nicht vorhanden. |
| Verfuegbarkeits-Datumsfeld (Wiederverwendung als Profilfeld) | teilweise | field.field.profile.projektpartner.field_availability.yml:1-23; core.entity_form_display.profile.projektpartner.default.yml; ProjektpartnerRegistrationSubmitter.php | field_availability existiert als string-Feld am projektpartner-Profil und wird im Registrierungs-/Profil-Edit-Formular gepflegt. Deckt aber NICHT die login-freie Self-Service-Journey ab — nur das Datum als Profil-Attribut. Kein 'nur Zukunft'/Datepicker-Constraint sichtbar (field_type: string ohne settings). |
Completeness-Critic — was die Einzel-Journeys nicht sahen
Übersehene Legacy-Features (12)
Funktionen aus 0009, die in keiner der 10 Journey-Listen auftauchten — meist Cron-Jobs, interne Mail-Flows, Datenmodell-Felder.
- AVV / Lead2faForm Double-Opt-In (Opt-In + Opt-Out, AvvController::optInAction/optOutAction, 32-stelliger Token, Entity mit token/accepted/acceptedDate, Bestaetigungsmail an crm@solcom.de): eigenstaendige DSGVO-Auftragsverarbeitungs-Einwilligung — taucht in KEINER der 10 Journeys auf, ist aber ein vollstaendiger Token-Flow mit eigener Entity und CRM-Mail.
- Cron-Prune-Commands fehlen komplett (ProjectAlertCommandController::pruneCommand + pruneNotificationsCommand, AvailabilityCommandController::pruneCommand, ProjectCommandController::pruneCommand): keine Journey erfasst das Aufraeumen abgelaufener Alerts, Notifications oder Verfuegbarkeits-Datensaetze; im Drupal-Stack als Drush/Queue-Cleanup nicht vorgesehen.
- RMS-Counter-Mechanik fehlt vollstaendig (UtilityCommandController::createCounterCommand Startnummer 121212, setCounter, setCounterToUsers, removeAllCommand): das System, das jeder Neu-Registrierung eine fortlaufende PPP-/RMS-Nummer vergibt, hat im Drupal-Modul kein Pendant — bestaetigt durch Code: ProjektpartnerRegistrationSubmitter vergibt KEINE Nummer, es existiert kein field_ppp_id/field_rms_number am Profil, nur die Migration mappt legacy rmsnumber.
- Standort-Code-Feld (location D1-D9 / DE / CH / A / Remote, fuer RMS-Sync reserviert, kein UI-Feld) hat im Drupal-Profil kein Pendant — kein field_location_code; relevant fuer den Salesforce-Sync und die Availability-Mail-Betreffzeile, in keiner Journey erwaehnt.
- Magazin-/Newsletter-Opt-In-Feld (User.magazineOption, Mailingwork/Mailingworks-Double-Opt-In via addMagazineAction/removeMagazineAction) existiert als Profilfeld NICHT (kein field_magazine_option in config/install); mehrere Auditoren flaggen die Magazin-Checkbox pro Formular, aber das persistente Profil-Feld + die Mailingwork-Anbindung als Querschnitt fehlen ganz.
- lastAccountEditDate-Tracking (Zeitstempel jeder Kontoaenderung, FlowUserCreationService) hat kein Drupal-Feld — relevant fuer die taegliche Profil-Aenderungs-Cron-Notification und als Audit-Spur; in keiner Journey als eigenes Datum erfasst.
- Taegliche Profil-Aenderungs-Sammelmail an bewerbung@solcom.de (UserNotificationCommandController::sendCommand + notifyCommand, Template UserNotification/Notification.html, Betreff 'SOLCOM PPP-ID [RMS-Nr] hat seine Daten aktualisiert', mit gestern hochgeladenen Dokumenten als Anhang, shouldNotSendAsNotification-Flag auf UserFile) — kompletter Cron-Mail-Flow ohne jegliche Journey-Abdeckung.
- Error-/Alert-Mailer (MailerService::sendAlert an externe Dienstleister-Adressen, Betreff '***CRITICAL***/***INFO*** SOLCOM Webseiten - Error', Template Mail/Error/GeneralError.html) — operatives Monitoring per Mail, in keiner Journey; im Drupal-Nachbau via Watchdog/Alert-Modul zu ersetzen, hardcodierte Empfaenger zu entfernen.
- Statistik-/Bewerbungszaehler (UtilityCommandController::addStatisticsProjectAppilcationCounterCommand, Statistics-Entity mit Counter) — Bewerbungs-Zaehlmechanik, in keiner Journey.
- IndexedSearch-AJAX-Typeahead (IndexedSearchController::searchResultAjaxAction, mehrsprachig, paralleler AJAX-Such-Flow) — Live-Suche/Autocomplete der Projektliste, kein Journey-Auditor hat den Typeahead-Endpoint erfasst.
- shouldNotSendAsNotification-Flag am UserFile/Dokument-Modell — steuert, ob ein hochgeladenes Dokument als Mail-Anhang in der internen Benachrichtigung mitgeht; kein Pendant am Drupal-field_documents, in keiner Journey.
- MIME-/Groessen-Untergrenze und Filename-Sanitizing (min. 1 KB / 1024 Bytes, preg_replace auf [a-zA-Z0-9_.-], Titel-Konstruktion 'Lebenslauf_<datum>.pdf') — Auditoren erfassen 5-MB-Obergrenze und Typen, aber die Mindestgroesse + Dateinamen-Bereinigung als Upload-Sicherheitsregel fehlt durchgaengig.
Querschnittsthemen (7)
Berühren mehrere Journeys gleichzeitig — als eigene Arbeitspakete einplanen, nicht je Journey duplizieren.
- Auth-/Session-Modell: Legacy-Inaktivitaets-Timeout 3600 s (1 h) und Session-Cookie-Haertung (httponly true, secure ZWINGEND setzen — Legacy hatte secure=false) betreffen Login, Account-Aendern, Profil, Merkliste, Verfuegbarkeit gleichzeitig; Journey-Auditoren flaggen den Timeout nur unter Login als gap, die Session-Cookie-Haertung taucht nirgends auf.
- CSRF-Schutz als Querschnitt: Legacy hatte 22x @Flow\SkipCsrfProtection auf state-mutierenden Aktionen (Account-Loeschung, Datei-Upload, Projekt-Alert, Admin-Edit). Die Drupal-Raw-Controller-Routen (WatchlistController add/remove, Account-Loeschung, Datei-Upload) brauchen explizit _csrf_token; FormBase-Forms bekommen ihn gratis — kein Auditor prueft CSRF auf den Nicht-Form-Routen.
- RMS-/PPP-ID + Salesforce-Sync: Die PPP-/RMS-Nummer ist der Join-Key fuer (a) den Profil-Aenderungs-Sync (QueueWorker ProfileChangeSfSync existiert, aber Sync-Richtung/Abdeckung unklar), (b) die Availability-Mail-Betreffzeile 'Aktualisierung RMS Nr. [ID]', (c) die interne Registrierungs-Benachrichtigung. Net-neue Registrierungen erhalten aktuell KEINE Nummer (Submitter vergibt keine), nur migrierte Bestands-User bringen sie mit — bricht alle drei abhaengigen Flows fuer Neuregistrierungen.
- Mailversand-Infrastruktur: Legacy kennt ~25 distinkte Mail-Typen mit festen From-Adressen (no-reply@/info@/projektpartner@/projektalert@/crm@/karriere@) und Signaturen. Implementiert sind nur 3 easy_email-Typen (ppp_verify, ppp_welcome, ppp_sf_sync) + ppp_alert. Es fehlen u.a.: interne Aktivierungs-Mail an bewerbung@ mit Dokument-Anhaengen, Bewerbungs-Eingangsbestaetigung (ApplicationConfirmation, BCC bewerbung@), beide Account-E-Mail-Aenderungs-Mails (alt+neu), Verfuegbarkeits-Bestaetigung, Re-Aktivierungs-Resend, Passwort-Aenderungs-Bestaetigung-Variante.
- DSGVO-Consent-Versionierung: Drupal hat field_privacy_consent + field_terms_consent jeweils mit _at und _version (TERMS_VERSION 'terms-2026-06-15') — das ist SAUBERER als Legacy (dort nur boolean policyOption ohne Timestamp/Version). Querschnitt offen: dieselbe Consent-Erfassung muss auch im Bewerbungs-Flow, im Gast-Projekt-Alert und im Verfuegbarkeits-Formular greifen, nicht nur bei der Registrierung — dort fehlt die versionierte Erfassung.
- Datei-Storage als Querschnitt: Legacy nutzt eine UserFile-Entity mit OneToMany am User, waehrend der Registrierung temporaer am RegistrationFlow haengend; Uploads aus Registrierung, Bewerbung und Verfuegbarkeit teilen dieselben Regeln (pdf/doc/docx, 1 KB-5 MB, Sanitizing). Drupal legt nach private://ppp/<uid>/ ab; offene Querschnitts-Frage: Temp-File-Lifecycle/Cleanup (Legacy Availability: 40-Min-TTL Files, 30-Tage-TTL Ordner) ist fuer keinen der drei Upload-Pfade abgebildet.
- Gast->User-Datenuebernahme beim Login als doppelter Querschnitt: nicht nur Merkliste (localStorage->DB), sondern AUCH Gast-Suchagenten (per E-Mail angelegt) muessen beim ersten Login auf das Konto umgehaengt und der Gast-Datensatz entfernt werden; spiegelbildlich bei Account-Loeschung User-Alert wieder in Gast-Alert ueberfuehren. Beruehrt Login, Merkliste und Suchagent zugleich.
Go-Live-Risiken (7)
Priorisierte Gaps mit dem höchsten Schaden, wenn sie bis zum 08.07.2026 offen bleiben.
- RMS-/PPP-ID-Vergabe bei Neuregistrierung fehlt (verifiziert: ProjektpartnerRegistrationSubmitter vergibt keine Nummer, kein field_ppp_id am Profil, kein Counter-Mechanismus). Folge: jeder ab Go-Live neu registrierte Projektpartner hat keinen Join-Key fuer Salesforce-Sync, Availability-Mail-Betreff und interne Benachrichtigung — stiller Datenbruch zwischen Portal und RMS/Salesforce. Hoechstes Go-Live-Risiko.
- Interne Aktivierungs-/Bewerbungs-Mail an bewerbung@solcom.de mit Dokument-Anhaengen fehlt (Journey 1 + Mail-Katalog): SOLCOM erfaehrt beim aktuellen Stand NICHT, dass sich ein neuer Projektpartner aktiviert/beworben hat, und bekommt die hochgeladenen Lebenslaeufe nicht — der gesamte fachliche Zweck (Bewerber-Eingang im Recruiting) bricht weg, obwohl der Nutzer einen Erfolg sieht.
- Bewerbungs-Eingangsbestaetigung an den Bewerber (ApplicationConfirmation, BCC bewerbung@) fehlt: Bewerber bekommt keine Quittung, SOLCOM keine BCC-Spur — bei einem Recruiting-Portal ein Vertrauens- und Nachweis-Defekt direkt im Kern-Flow.
- Account-E-Mail-Aenderung per Double-Opt-In komplett ungebaut (ganze Journey 4 ist gap: AccountChangeFlow, Token-Landeseite email-bestaetigen, zwei Erfolgsmails alt+neu, Pending-ohne-Session-Verlust). Nutzer koennen ihre Login-E-Mail nicht sicher aendern — funktionale Luecke mit DSGVO-/Account-Recovery-Relevanz.
- Verfuegbarkeit-melden ist als ganze Journey 10 gap (Hash-Link-Route, Token-Validierung, Mail an projektpartner@ mit Betreff 'Aktualisierung RMS Nr.', Cron/Queue fuer faellige Erinnerungen, 4 Mailtypen, Availability-Datenmodell). Das ist ein eigenstaendiger Geschaeftsprozess (RMS-Export -> Erinnerungsmail -> PP meldet Verfuegbarkeit zurueck), der bis Go-Live komplett fehlt.
- Session-Cookie nicht gehaertet + 1h-Inaktivitaets-Logout nicht abgebildet: secure-Flag muss gesetzt sein (Legacy war secure=false), sonst Session-Hijack-Risiko ueber unverschluesselte Uebertragung; betrifft alle eingeloggten Flows.
- CSRF auf state-mutierenden Raw-Controller-Routen (Merkliste add/remove via WatchlistController, Account-Loeschung, Datei-Upload): wenn _csrf_token dort nicht gesetzt ist, sind genau die Aktionen, die Legacy via SkipCsrfProtection offen hatte, im Neubau weiterhin angreifbar — gezielt verifizieren vor Go-Live.
Methode & Restunsicherheit (Critic): Methode: Die 10 Journey-Feature-Listen wurden gegen Context-Snapshot 0009 (Flow-Inventory: Auth-/Session-Modell, ~25 Mail-Typen-Katalog, 40-Feld-Datenmodell, Cron-Command-Enumeration inkl. eigener id=gaps-Sektion eines vorherigen Opus-Passes) und 0010 (Formular-Mockups) sowie gegen den realen Drupal-Code unter web/modules/custom gekreuzt. Verifiziert im Code: (1) ProfileChangeSfSync existiert als QueueWorker in solcom_projektpartner (SF-Sync also scaffolded, NICHT missing — offen ist Sync-Richtung/Coverage). (2) Keine RMS-/PPP-ID-Vergabe bei Neuregistrierung, kein entsprechendes Profilfeld. (3) Alert-Matching-Cron + Notification-Mail SIND in einem dedizierten Modul solcom_projektpartner_alert real implementiert (PppAlertCronWorker + PppAlertMatcher + PppAlertMailManager) — Journey-9-'done' auf den Kern-Cron ist somit korrekt, die dort geflaggten Gaps (Gast-Double-Opt-In, Login-Uebernahme, Account-Loesch-Konvertierung) bleiben gueltig. Bewusst NICHT als missing gelistet: 2FA als Login-Faktor (Legacy hatte keins; Lead2faForm ist DSGVO-Opt-In, separat unter AVV gefuehrt), generische Neos-Form-Finisher/CRM-Anfrageformular/Karriere-Formulare (out-of-portal-scope), Twitter-Integration (im Legacy bereits auskommentiert), Donation/Spenden-Cron (nicht Portal-Scope). Restunsicherheit: die genaue Feld-Abdeckung des Salesforce-Sync (welche Profilfelder in welche Richtung) und der Temp-File-Cleanup-Lifecycle wurden nicht zeilengenau im SF-Mapping verifiziert; ProfileChangeSfSync und field_documents-Storage-Settings sollten vor Go-Live gegen die RMS-Feldliste gegengelesen werden.