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
16
22
4
50
51 umgesetzt (36%) 16 teilweise 22 abweichend 4 unklar 50 fehlt (35%)

Fazit

Registrierung & Aktivierung Login Passwort vergessen & zuruecksetzen Account-Daten ändern Account löschen Profil & Dokumente pflegen Projekt finden & merken Auf ein Projekt bewerben Suchagent / Projekt-Alert & Benachrichtigung Verfuegbarkeit melden

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-RoutenCore _csrf_request_header_token: 'TRUE' + Header im JS. Verifiziert offen.
Secure-Cookie-HärtungSession-Config in settings.php/services.yml.
Bewerbungs-Eingangsbestätigung an BewerberAuslieferung & Handler existieren bereits — nur die Bestätigungsmail ergänzen.
Nur konfigurieren / aktivieren — kein Code4
Login per E-Mail aktivierenlogin_emailusername installiert, aber 0 in core.extension — einschalten.
Spam-Schutz aktivierencaptcha/honeypot installiert, derzeit aus — auf öffentliche Formulare.
Fehlende Bewerbungs-FormularfelderWebform-Elemente ergänzen (Mobil, Titel, Geburtsdatum, Firma, Webseite, Einsatzorte, Stundensatz, Bewerbungstext).
Upload-Regeln & PflichtfelderWebform managed_file-/Required-Settings — Konfiguration.
Bauen, aber vereinfacht — Legacy nicht 1:1 nachbilden3
E-Mail-Wechsel mit Bestätigung der neuen AdresseMVP-Pflicht (Entscheid). Contrib „confirm-to-new-address“ statt custom Pending-ohne-Session-Flow prüfen.
RMS-/PPP-Nummer: Feld + SF-RücksyncEntscheid: SF vergibt, Drupal speichert/zeigt. Kein Counter-Build — ein Profilfeld + Inbound-Befüllung. Abhängig davon, dass SF die Nummer bereitstellt.
Token-Gültigkeit vereinheitlichenEinen 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 AuslieferungRsmProjectOfferMailHandler + rsm_project_offer_application-Webform („exact Power-App format“).
Profiländerungs-Benachrichtigung an SFProfileChangeSfSync-QueueWorker + ppp_sf_sync-Mailtyp + Kernel-Test.
Registrierung / Verify / Profil / Account-Löschung / MerklisteKern-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-DimensionNicht im MVP gewählt. Double-Opt-In, Hash-Abmeldung, Login-Übernahme — hohe Komplexität.
Magazin-/Newsletter-Opt-In via MailingworkNicht im MVP gewählt. Marketing-Integration, nicht Kern-Projektpartner.
1h-Inaktivitäts-LogoutNicht 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üftCore /user/passwordsubmitForm 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üftCore Flood-Control (user.flood, user.password_request_ip/_user) — Default aktiv.
Login per E-Mail-AdressegeprüftContrib 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üftCore 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üftWebform managed_file-Settings + Core File-Validation-Constraints (FileSizeLimit, FileExtension, FileNameLength) via FileValidator — Konfiguration. (D11-Mechanik, nicht mehr file_validate_*.)
Dateiname-Bereinigung beim UploadgeprüftCore Transliteration + FileNameLengthConstraint — automatisch.
Land als AuswahllisteCore 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üftContrib captcha + honeypot (installiert, derzeit deaktiviert) — aktivieren.
Temp-Datei-CleanupCore File-GC (temporäre Dateien via System-Cron). Spezifische TTLs = kleiner Delta.
Volltext-/Einsatzort-Filter der ProjektlisteViews Exposed Filter — Konfiguration (teilweise vorhanden).
CSRF-Schutz auf den Merkliste-Raw-Routen (add/remove/merge)geprüftCore 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üftBeide 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 entfernenKleiner Zusatz am bestehenden WatchlistController.
Filter-Persistenz der Projektliste über BesucheViews Exposed Filter + Session/Cookie — kleine Erweiterung.
Inaktivitäts-Logout nach 1 hgeprüftKein 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 LoginCore 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 NeuregistrierungKein Core-Sequenz-Mechanismus. Join-Key für Salesforce/Verfügbarkeit. Höchstes Go-Live-Risiko.
Bestätigungsmails bei Passwort- und E-Mail-ÄnderunggeprüftCore 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-LandeseiteCore ändert die E-Mail sofort und bestätigt die neue Adresse NICHT. Contrib „Verify Email/Email Confirmer“ nicht installiert.
Magazin-/Newsletter-Opt-In via MailingworkExterne Double-Opt-In-Integration.
Verfügbarkeitsmeldung — ganze JourneyHash-Link ohne Login, Token-Validierung, Datenmodell, Cron-Erinnerungen, 4 Mailtypen.
Suchagent-Gast-DimensionDouble-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-BenachrichtigunglastAccountEditDate-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)StatusEvidenz (Drupal)Anmerkung
Abgelaufener Link: neuen Aktivierungslink zusenden lassen (Re-Aktivierung / Resend)fehltkeine 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 Mailingworkfehltkeine 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 vergebenfehltkeine 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)fehltkeine 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 gueltigabweichendProjektpartnerMailManager.php:183-189 (nutzt user.settings password_reset_timeout); config/sync/user.settings.yml:17 password_reset_timeout: 86400Ablauf 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 Profilabweichendwebform.webform.projektpartner_registration.yml:113-115 (#file_extensions 'pdf doc docx odt txt rtf jpg jpeg png', #max_filesize '10MB'); moveDocuments ProjektpartnerRegistrationSubmitter.php:189-230Upload 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)teilweisewebform.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 :124Mobil 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)teilweisewebform.yml:126-145 (availability/deployment_locations/hourly_rate); Submitter :154-156Felder 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 Transaktionumgesetztweb/modules/custom/solcom_projektpartner/src/Service/ProjektpartnerRegistrationSubmitter.php:63-77; createBlockedUser status=0 :110-117Transaktionaler 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 GastumgesetztProjektpartnerRegistrationSubmitter.php:69 (sendVerification); ProjektpartnerMailManager.php:34-38; easy_email.easy_email_type.ppp_verify.ymlEasy-Email-Template ppp_verify mit [solcom:verification-url] geht an Nutzer-Adresse. Entspricht Legacy registerAction +Mail.
Aktivierungs-Token-Mechanik + Klick aktiviert Konto, Login danach moeglichumgesetztProjektpartnerVerificationController.php:36-44; ProjektpartnerMailManager.php:43-61 (verifyAccount setzt status=1); verificationUrl :142-150Hash 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)umgesetztProjektpartnerRegistrationHandler.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 gesperrtumgesetztProjektpartnerRegistrationAccess.php:35-46 (anonymous allowed, authenticated forbidden); routing.yml:1-8Custom-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)umgesetztwebform.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 Policyumgesetztwebform.yml:76-79 (solcom_projektpartner_password_confirm); Handler validateForm :86-101 (pass1!=pass2 Fehler); Submitter assertPasswordPolicy :90-102Custom Confirm-Element + Match-Validierung + password_policy.projektpartner. Doppeltes Tor (Handler-UX + Submitter-Backstop).
Consents Datenschutz + Nutzungsbedingungen (Pflicht) mit Timestamp/Versionumgesetztwebform.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)umgesetztwebform.yml:146-188; Submitter skillReferences :303-327, listValues languages, focus_areas/cover_letter, gulp_id/freelance_de/xing/freelancermap/websiteAlle 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)StatusEvidenz (Drupal)Anmerkung
Übernahme von Gast-Projekt-Alerts (Suchagenten, per E-Mail angelegt) auf den User beim Login (Alert-Migration, Gast-Datensatz entfernt)fehltkeine 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-FeldKernlü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)fehltkeine 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.jsonKein 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 Cookieabweichendweb/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')teilweiseweb/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)teilweiseweb/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')unklarkeine gefunden — keine Custom-Login-Template/Message-Logik in solcom_projektpartner; Core liefert eigene Standard-MessagesDa 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-Seiteunklarkeine 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)umgesetztweb/modules/custom/solcom_projektpartner/tests/src/Kernel/ProjektpartnerLoginTest.php:48 (testProjektpartnerCanAuthenticateWithEmailAndPassword); contrib login_emailusername ^3.0.1 (composer.json), enabled in test:29E-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)umgesetztweb/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 Loginumgesetztweb/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)StatusEvidenz (Drupal)Anmerkung
Fallback-Hinweis bei noch nicht aktiviertem Konto (Legacy 'registerFlowExisting'-Meldung)fehltkeine 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 Resetfehltkeine 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)abweichendconfig/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)abweichendconfig/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=abweichendweb/modules/custom/solcom_projektpartner/solcom_projektpartner.routing.yml (nur registrieren/verifizieren/profil); Core-Pfad /user/passwordIA-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 umgeleitetunklarkeine 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 anfordernumgesetztweb/core/modules/user/src/Form/UserPasswordForm.php:154 (validateForm) + :195 (submitForm); keine Custom-Route in web/modules/custom/solcom_projektpartner/solcom_projektpartner.routing.ymlVollstaendig 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 istumgesetztweb/core/modules/user/src/Form/UserPasswordForm.php:172-189 + :195-205Drupal-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)umgesetztconfig/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 einloggenumgesetztCore 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)StatusEvidenz (Drupal)Anmerkung
Kombiniertes Formular 'Zugangsdaten ändern' (#edit-account): zwei optionale Blöcke E-Mail + Passwort, 'leer lassen = unverändert'-Semantik, eingeloggt-onlyfehltkeine 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)fehltkeine 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)fehltkeine 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 Änderungsformularfehltkeine 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)fehltkeine 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)fehltkeine 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/abgelaufenfehltkeine 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)teilweisekeine 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)StatusEvidenz (Drupal)Anmerkung
Interne Lösch-Benachrichtigung an SOLCOM (bewerbung@)fehltkeine 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 Kontosfehltkeine 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öschungabweichendweb/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 Nutzerabweichendconfig/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)teilweiseconfig/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 Journeyteilweisetests/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)umgesetztconfig/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,158Bestä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 Datenumgesetztweb/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-136User + 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)umgesetztsrc/Service/ProjektpartnerAccountPurger.php:40-41,89-127,132-147; tests/src/Kernel/ProjektpartnerAccountDeletionTest.php:128-132field_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)StatusEvidenz (Drupal)Anmerkung
Pflichtangaben-Validierung (Legacy-Pflicht: Anrede*, Vorname*, Nachname*, Mobil*, E-Mail*, Straße*, PLZ*, Ort*, Land*)fehltfield.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)abweichendcore.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-Pendantabweichendfield_skills type entity_reference (taxonomy_term, vocabulary skill); form_display entity_reference_autocomplete_tags w24Drupal 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)abweichendfield.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 w31Upload 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 ausgeblendetabweichendcore.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 w30privacy/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, Landteilweisecore.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'teilweisecore.entity_form_display.profile.projektpartner.default.yml: field_hourly_rate w15 type number; field_availability w16 type string_textfieldStundensatz 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)teilweisefield.storage.profile.field_languages.yml cardinality -1; form_display options_buttons w21; kein Length/Count-Constraint in solcom_projektpartner.module:104Sprachfeld 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-Editunklarkeine 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)umgesetztsolcom_projektpartner.routing.yml:33; src/Controller/ProjektpartnerProfileController.php:65Clean-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 Blickumgesetztsrc/Controller/ProjektpartnerProfileController.php:75getForm($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, Firmaumgesetztcore.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)umgesetztfield.storage.profile.field_deployment_locations.yml allowed_values (all/AT/CH/DE/other), cardinality -1; form_display options_buttons w14Optionsset und Mehrfachauswahl entsprechen dem Legacy-Mockup.
Feldcluster 'Netzwerke': GULP-ID, Freelance.de, XING, Freelancermapumgesetztcore.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)umgesetztfield_focus_areas type string_long, form_display string_textarea w23; Length-Constraint max 500 in solcom_projektpartner.module:127-136Legacy-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)umgesetztfield_cover_letter type string_long, form_display string_textarea w22; Length-Constraint max 2000 in solcom_projektpartner.module:116-1242000-Zeichen-Limit per Constraint erzwungen.
Dokumente löschenumgesetztcore.entity_form_display.profile.projektpartner.default.yml: field_documents type file_generic; field.storage.profile.field_documents.yml cardinality -1Lö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-Nummerumgesetztmigrate_plus.migration.solcom_projektpartner_neos_profile.yml:23 (ppp_id: rmsnumber); kein field_rms_number in config/install; keine RMS-Zeile im form_displayRMS-Nummer wird nur als ppp_id (User-Lookup) migriert, existiert nicht als Profilfeld und ist im Edit-Form korrekt abwesend.
Systemfelder NICHT pflegbar: Standort-Codeumgesetztkeine 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)StatusEvidenz (Drupal)Anmerkung
Gewählte Filter bleiben bei erneutem Besuch erhalten (Legacy: Cookies searchParameter/location/business/zip)fehltconfig/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)fehltgrep removeall/removeAll/clear/deleteAll über web/modules/custom/solcom_project_offer/: keine gefunden; routing hat nur add/remove/merge/listKein 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)abweichendconfig/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)abweichendweb/themes/custom/byte_theme/js/watchlist.js:101-103 (data-watchlist-counter = list.length); kein dedizierter count-Endpoint im routingZä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)abweichendWatchlistController.php:230-257 (stale-Zählung + Info-Banner), aber kein Aufruf von watchlistStorage->remove für stale-IDs; Soll: docs/context/0009-...html:2179/2186Veraltete 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)abweichendjs/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)teilweiseconfig/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 filternumgesetztconfig/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)umgesetztweb/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/entfernenumgesetztweb/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)umgesetztweb/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)umgesetztWatchlistController.php:87-137 (add/remove); WatchlistStorage.php:75-105 (user.data-basiert); routing _role:projektpartner + CSRFServerseitige Persistenz über user.data für Rolle projektpartner; API add/remove/list mit CSRF und _role-Gate.
Add/Remove-Aktion (API-Endpoints)umgesetztWatchlistController.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 LoginumgesetztWatchlistController.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)umgesetztWatchlistController.php:174-375 (page, buildAuthenticatedPage, buildAnonymousPage); routing solcom_project_offer.watchlistServer-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)StatusEvidenz (Drupal)Anmerkung
Eingangsbestaetigungs-Mail an den Bewerber (Legacy: ApplicationConfirmation, BCC bewerbung@)fehltkeine gefundenKein 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 anlegenfehltkeine gefundenWebform 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)fehltkeine gefundenWebform hat nur ein 'phone'-Feld; Legacy-Mockup #job-application hat Telefon UND Mobil (Mobil Pflicht).
Formularfelder Titel, Geburtsdatum, Firmafehltkeine gefundenIm Cluster 'Zu Ihrer Person' des Legacy-Mockups vorhanden (Titel/Geburtsdatum/Firma), in der Drupal-Webform nicht abgebildet.
Formularfeld Eigene Webseite (mit URL-Validierung)fehltkeine gefundenLegacy-Kontaktdaten-Cluster enthaelt 'Eigene Webseite'; fehlt in der Webform.
Magazin-Checkbox (SOLCOM Freiberufler Magazin / Newsletter-Opt-in)fehltkeine gefundenLegacy-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, Verfuegbarkeitfehltkeine gefundenLegacy-Mockup #job-application enthaelt diese Cluster (Einsatzorte-Checkboxen, Stundensatz EUR, Verfuegbarkeitsdatum). In der Webform nicht vorhanden.
Bewerbungstext/Anschreiben fuer diese Bewerbung (max. 2000 Zeichen)fehltkeine gefundenLegacy hat projektspezifisches Anschreiben-Textfeld; fehlt in der Webform.
Bewerbungsformular auf der Projekt-Detailseite (Legacy-Route /projektliste/<projekt>/bewerben)abweichendsolcom_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)abweichendwebform...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)abweichendRsmProjectOfferMailHandler.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)teilweisesolcom_project_offer.module:184-265; WebformPrefillFromProfileTest.php:206-238Prefill 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-Dateienteilweisesolcom_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 eingeloggtumgesetztwebform.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 Zielprojektumgesetztwebform...yml:106-136 (hidden: node_id/title/lfd_nr/contact_email/session_uuid/ppp_id); solcom_project_offer.module:165-182Projektkontext (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)umgesetztwebform...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 + AnhangumgesetztRsmProjectOfferMailHandler.php:129-160; solcom_project_offer.module:128-144; RsmProjectOfferMailHandlerTest.php:91-99Mail 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 Absendenumgesetztwebform...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)umgesetztwebform...yml:18-85 (section_person, section_address)Pflicht-Kernfelder vorhanden; Anrede bietet zusaetzlich 'Divers' (Legacy nur Herr/Frau).
Datenschutz-Zustimmung (Pflicht-Checkbox)umgesetztwebform...yml:98-105 (privacy_accepted checkbox #required true)Pflicht-Checkbox mit Link auf /datenschutz; entspricht Legacy-Datenschutzerklaerung.
Ungueltige Datei -> Fehlermeldung, kein Versandumgesetztwebform...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 MailtextumgesetztRsmProjectOfferMailHandler.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)StatusEvidenz (Drupal)Anmerkung
UND/ODER-Logik: bis 4 Begriffe je Feld (UND), bis 4 Felder (ODER), plus bis 2 Ausschluss-Felderfehltsrc/Service/PppAlertMatcher.php:93-94 (field_skills.target_id IN), keine noTags-Felder gefundenKomplexeste 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)fehltkeine 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)fehltkeine 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)fehltsrc/Form/PppAlertForm.php:91-165 (nur Skills/Regionen/Status); Route _user_is_logged_in:TRUE in routing.yml:8Formular ist login-pflichtig, kennt nur Kriterien-Felder. Kein Gast-Pfad, keine Kontaktdatenfelder.
Newsletter / Freiberufler-Magazin-Checkbox (nur Gaeste; eingeloggt field_magazine_option=TRUE)fehltkeine 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)fehltkeine 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/Aktualisierenfehltsrc/Form/PppAlertForm.php:213-219 (save + redirect, kein send); Legacy: Flow-Inventar 2784 signInAction ruft send() sofortDrupal 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)fehltkeine 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 uebernehmenfehltkeine 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 ueberfuehrenfehltkeine 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)abweichendweb/modules/custom/solcom_projektpartner_alert/src/Entity/PppAlert.php:82-97; src/Form/PppAlertForm.php:110-121Legacy = 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)abweichendsrc/Entity/PppAlert.php:100-116 (field_deployment_locations); src/Service/PppAlertMatcher.php:130-162INVERTIERT: 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)abweichendsrc/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)teilweiseconfig/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 Neosteilweisesrc/Plugin/migrate/source/NeosAlertSource.php:55-62; config/install/migrate_plus.migration.solcom_projektpartner_alert_neos.ymlSource 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 matchenumgesetztsolcom_projektpartner_alert.module:16-19 (hook_cron); src/Service/PppAlertCronWorker.php:47-105; src/Service/PppAlertMatcher.php:83-120hook_cron laeuft, laedt aktive Alerts, matcht neue project_offer-Nodes via Matcher. Funktioniert (im Rahmen der divergenten Match-Logik).
Benachrichtigungs-Mail je Treffer mit Projektlisteumgesetztsrc/Service/PppAlertMailManager.php:50-122; config/install/easy_email.easy_email_type.ppp_alert.yml:16-21Easy-Email-Typ 'ppp_alert' mit Projektliste-Platzhalter und Name-Platzhalter; Mail wird je Alert mit Treffern versendet.
Alert aus Profil loeschen (eigener Alert, zugriffsgesichert)umgesetztsrc/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)umgesetztsrc/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 Erstlaufumgesetztsolcom_projektpartner_alert.install:26-33 (hook_install setzt STATE_KEY=now); src/Service/PppAlertCronWorker.php:51-58hook_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)StatusEvidenz (Drupal)Anmerkung
Personalisierte Hash-Link-Route /projektportal/verfuegbarkeit?id=… (oeffentlich, ohne Login, einmalig gueltig)fehltsolcom_projektpartner.routing.yml (nur registration, verify, profile, profile_edit) — keine gefundenKeine 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)fehltkeine gefundenEs 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)fehltkeine gefunden — nur webform.webform.projektpartner_registration.yml existiertKein 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)fehltkeine gefundenKein Upload-Element in einem Availability-Formular vorhanden (das Formular selbst fehlt komplett).
Freitext-Feld 'Ihre Nachricht an uns'fehltgrep 'Nachricht an uns' — keine gefundenNicht vorhanden; kein Availability-Formular existiert.
Pflicht-Consent-Checkboxen (Datenschutz + Nutzungsbedingungen)fehltkeine gefundenKein 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. …'fehltProjektpartnerMailManager.php:18-142 (nur sendVerification, sendWelcome, sendProfileChangeNotification) — keine Availability-MethodeMailManager kennt keine Availability-Mail. Kein Submit-Controller (Neos: AvailabilityController::sendAction) im Modul vorhanden.
Bestaetigungs-E-Mail an den Projektpartner nach Submitfehltkeine gefundenNicht implementiert (Neos: Mail/Availability/Confirmation.html, optional bei vorhandener E-Mail-Adresse).
Ergebnis-/Status-Seite nach Submit (status=true/false)fehltkeine gefundenKeine formResultAction-Entsprechung; Journey-Endpunkt fehlt vollstaendig.
Cron/Queue: faellige Datensaetze anlegen + Erinnerungs-Mails mit Hash-Link versenden (Neos: AvailabilityCommandController::importCommand, 4 Mailtypen)fehltsolcom_projektpartner.module:246-280 (nur Queue 'profile_change_sf_sync'); kein hook_cron fuer Availability — keine gefundenEinziger 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)fehltfield.field.profile.projektpartner.field_availability.yml (string-Feld am Profil) — keine Availability-Entitaet gefundenEs 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)fehltkeine gefundenKein hook_cron/QueueWorker fuer Upload-Cleanup. Da die Journey/Uploads selbst fehlen, ist auch das Cleanup nicht vorhanden.
Verfuegbarkeits-Datumsfeld (Wiederverwendung als Profilfeld)teilweisefield.field.profile.projektpartner.field_availability.yml:1-23; core.entity_form_display.profile.projektpartner.default.yml; ProjektpartnerRegistrationSubmitter.phpfield_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.

Querschnittsthemen (7)

Berühren mehrere Journeys gleichzeitig — als eigene Arbeitspakete einplanen, nicht je Journey duplizieren.

Go-Live-Risiken (7)

Priorisierte Gaps mit dem höchsten Schaden, wenn sie bis zum 08.07.2026 offen bleiben.

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.