P2503 · PRD · Projektpartnerportal · RSM-Fallback

RSM-Fallback: Projektangebote-Import + Bewerbungsflow

ready-for-agent Erstellt: 2026-05-30 · ADR-0048
Kontext: Salesforce (SF) geht nicht rechtzeitig zum SOLCOM-Go-Live (08.07.2026) live. Für ca. 2–3 Monate übernimmt das RSM-Altsystem als Datenquelle für Projektangebote. Dieses PRD definiert die vollständige Übergangslösung.

Problem Statement

Die neue SOLCOM-Website soll Projektangebote anzeigen und Freelancern ermöglichen, sich direkt zu bewerben. Die geplante Datenquelle (Salesforce-API) ist zum Go-Live-Termin noch nicht produktionsbereit. Das Altsystem (RSM) enthält alle aktiven Projektangebote und exportiert sie täglich als XML-Datei, kann aber nicht als API-Endpunkt genutzt werden. Bewerbungen aus dem alten Projektpartnerportal (PPP) liefen über eine Microsoft Power App, die E-Mails in einem exakten Format aus einem Postfach liest.

Ohne Übergangslösung startet die neue Website mit einer leeren Projektliste und bietet keinen funktionierenden Bewerbungsweg.

Solution

Ein neues Sub-Modul solcom_rsm_import importiert die täglich per SFTP gelieferte RSM-XML in Drupal-Projektangebot-Nodes. Ein State Flag steuert, ob RSM oder SF als Datenquelle aktiv ist — dieser Schalter steuert gleichzeitig den Bewerbungsweg. Während des RSM-Fallbacks bietet jedes Projektangebot zwei Bewerbungsoptionen: einen Minggo-Link (wo vorhanden) und ein Drupal-Webform, das eine E-Mail im exakten Power-App-Format generiert. Zusätzlich erhält die User-Entity ein neues field_ppp_id-Feld für die Projektpartnerportal-ID.

User Stories

Dataimport

  1. Als Projektpartner möchte ich ab Go-Live aktuelle Projektangebote auf der SOLCOM-Website sehen, damit ich nicht auf den Salesforce-Launch warten muss.
  2. Als Redakteur möchte ich, dass Projektangebote täglich automatisch aktualisiert werden, ohne manuelle Eingriffe.
  3. Als Redakteur möchte ich, dass deaktivierte RSM-Datensätze (Aktiv=0) automatisch unpublished werden, damit keine abgelaufenen Angebote sichtbar sind.
  4. Als Entwickler möchte ich den RSM-Import per Drush-State-Flag ein- und ausschalten können, damit ich beim SF-Go-Live nahtlos umschalten kann.
  5. Als Entwickler möchte ich, dass der Import idempotent ist (Folge-Run ohne Duplikate), damit tägliche Cron-Ausführungen zuverlässig bleiben.
  6. Als Entwickler möchte ich, dass die neueste XML-Datei automatisch ausgewählt wird, damit der Import auch bei tagesstempel-basierten Dateinamen funktioniert.
  7. Als Entwickler möchte ich, dass alte XML-Dateien nach 30 Tagen automatisch gelöscht werden, damit das private://rsm/-Verzeichnis nicht überläuft.
  8. Als Entwickler möchte ich den Salesforce-Record-ID aus dem FreelancerLink-Feld extrahieren und auf dem Node speichern, damit spätere SF-Migrationen ohne Duplikate möglich sind.

Bewerbungsformular

  1. Als Projektpartner (eingeloggt oder anonym) möchte ich mich auf ein konkretes Projektangebot bewerben können, damit meine Bewerbung beim zuständigen Bearbeiter ankommt.
  2. Als eingeloggter Projektpartner möchte ich, dass mein Name und meine E-Mail im Formular vorausgefüllt sind, damit ich Zeit spare.
  3. Als anonymer Besucher möchte ich das Bewerbungsformular ausfüllen können, ohne einen Account anlegen zu müssen, damit der Einstieg niedrigschwellig bleibt.
  4. Als Projektpartner möchte ich mein CV als Datei hochladen können, damit meine Unterlagen direkt mit der Bewerbung ankommen.
  5. Als Projektpartner möchte ich eine Bestätigung nach dem Absenden sehen, damit ich weiß, dass meine Bewerbung eingegangen ist.
  6. Als Bearbeiter bei SOLCOM möchte ich eine E-Mail mit allen Bewerberdaten im exakten Format erhalten, damit die Power App sie automatisch verarbeiten kann.
  7. Als Bearbeiter bei SOLCOM möchte ich im CC der Bewerbungs-E-Mail stehen, damit ich sofort informiert bin.
  8. Als Projektpartner möchte ich auf Angeboten mit Minggo-Link zusätzlich direkt über Minggo bewerben können, damit ich den für mich passenden Weg wählen kann.
  9. Als Projektpartner möchte ich das Webform nicht sehen, sobald das System auf Salesforce umgestellt ist, damit es keine veralteten Bewerbungswege gibt.

PPP-ID

  1. Als Administrator möchte ich einem Drupal-User eine PPP-ID (numerisch, eindeutig) zuweisen können, damit die Identität aus dem Altsystem erhalten bleibt.
  2. Als eingeloggter Bewerber möchte ich, dass meine PPP-ID automatisch in der Bewerbungs-E-Mail erscheint, damit die Power App mich zuordnen kann.
  3. Als anonymer Bewerber möchte ich, dass eine temporäre ID für mich generiert wird, damit das E-Mail-Format der Power App vollständig bleibt.
  4. Als Entwickler möchte ich sicherstellen, dass keine zwei Nutzer dieselbe PPP-ID haben können, damit die Eindeutigkeit aus dem Altsystem erhalten bleibt.

Implementation Decisions

Modul-Struktur

Das neue Sub-Modul solcom_rsm_import liegt unter web/modules/custom/solcom_project_offer/modules/solcom_rsm_import/. Es ist ein eigenständiges, deaktivierbares Modul — beim SF-Go-Live wird es einfach per Drush deaktiviert.

State Flag (Kernschalter)

Ein einziger Drupal State Flag solcom_project_offer.rsm_fallback_enabled (Boolean) steuert beide Aspekte der Übergangslösung:

  • 1 → RSM-Import-Cron läuft, SF-API-Pull schläft; Webform sichtbar
  • 0 → SF-API-Pull läuft, RSM-Import schläft; Webform versteckt (Minggo-Links bleiben)

Flip-Kommando: drush state:set solcom_project_offer.rsm_fallback_enabled 1. Kein Admin-UI nötig für die temporäre Phase. Das Pattern folgt ADR-0047 (solcom_personio.apply_redirect_enabled).

MigrateSourcePlugin: RsmXmlSource

Ein Drupal Migrate SourcePlugin RsmXmlSource implementiert das Interface für XML-Datei-basierte Quellen. Es liest die Datei mit dem neuesten filemtime aus private://rsm/, dekodiert UTF-16 (das RSM-Format), und liefert je <Datensatz> einen Record. Migrations-Key ist lfdNr (Integer, eindeutig im RSM-System).

Feldmapping RSM → project_offer

RSM XML-FeldDrupal-FeldAnmerkung
lfdNrfield_rsm_lfd_nrMigrations-Key; neues Feld
TiteltitleNode-Titel
BeschreibungbodyHTML-Fragment aus RSM
Ortfield_locationBestehendes Feld prüfen/anlegen
Startfield_start_dateFormat: DD.MM.YYYY → Date
Dauerfield_durationFreitext-String
Stellentypfield_job_typeFreitext-String
Bearbeiterfield_contact_emailE-Mail des Zuständigen; für Webform-CC
Aktivstatus0 → unpublished
FreelancerLink (URL-Suffix)field_sf_idLetztes URL-Segment extrahieren
FreelancerLink (gesamte URL)field_minggo_urlFür Minggo-Link-Button
Regionfield_regionNeu anlegen wenn nicht vorhanden
Branchefield_industryNeu anlegen wenn nicht vorhanden
skillsfield_skillsFreitext oder Tags

Neue Felder auf project_offer: field_rsm_lfd_nr (Integer, eindeutig), field_sf_id (String, nullable), field_minggo_url (String, nullable), field_contact_email (Email, nullable).

Bewerbungs-Webform: Email-Format (nicht verhandelbar)

Ein Drupal Webform-Handler generiert beim Submit eine E-Mail im folgenden exakten Textformat. Die Microsoft Power App parst den Body Zeile für Zeile — jede Abweichung von Reihenfolge oder Bezeichnung bricht die automatische Verarbeitung.

Betreff:  Bewerbung über Projektportal / Projekt-Nummer: {lfdNr} / {Titel}
An:       bewerbung@solcom.de
Von:      SOLCOM Projektportal <no-reply@solcom.de>
CC:       {field_contact_email des Projektangebots}

Projektnummer: {lfdNr}
Projekt: {Titel}
Titel: {Anrede}
Anrede: {Herr/Frau/Divers}
Vorname: {Vorname}
Nachname: {Nachname}
Telefon: {Telefon}
E-Mail-Adresse: {Email}
Straße: {Straße}
Postleitzahl: {PLZ}
Ort: {Ort}
Land: {Land}
Bewerbertyp: {Account User | Anonymous User}  ← Werte von SOLCOM zu bestätigen
Datenschutzerklärung akzeptiert true
Gesendet {Y-m-d H:i:s}
Session ID {UUID — pro Submit neu generiert}
PPP-ID {field_ppp_id des Users | UUID (wenn anonym)}
Datei 1 von 1 {Dateiname des Uploads}

PPP-ID-Logik im Webform

  • Eingeloggt: $user->get('field_ppp_id')->value in die Email-Zeile
  • Anonym: \Drupal::service('uuid')->generate() generiert eine UUID per Submit-Vorgang

Dual Apply auf der Detailseite

Beide Optionen erscheinen gleichzeitig wenn der Flag aktiv ist:

  • Minggo-Button (bedingt): nur wenn field_minggo_url nicht leer — Link zu solcom.minggo.io/UUID/apply/{SF_ID}
  • Webform-Block (immer bei aktivem Flag): Drupal Webform, eingebettet auf der Node-Detailseite via Canvas oder Block

field_ppp_id auf User-Entity

  • Base Field: Integer, Unique-Constraint, Nullable
  • Admin-only (nicht im Frontend-Profil sichtbar)
  • Implementiert via hook_entity_base_field_info()
  • Angelegt per hook_update_N für bestehende Instanzen
  • Modul-Zuordnung: eigenes solcom_user-Modul (kein direktes Abhängigkeitsverhältnis zu Projektangeboten)

lfdNr → SF-ID Übergang (späterer Task)

Beim SF-Go-Live liefert SOLCOM eine Mapping-Tabelle lfdNr → SF-Record-ID. Die SF-Migration matcht dann über field_rsm_lfd_nr und vermeidet Duplikate. Dieser Schritt ist explizit Out of Scope dieses PRD.

Testing Decisions

Was einen guten Test ausmacht

Tests prüfen ausschließlich externes Verhalten — was Drupal-APIs zurückgeben, welche Nodes angelegt werden, welche E-Mails gesendet werden. Keine Tests für interne Parsing-Hilfsfunktionen oder private Methoden. Tests sind wiederholbar (idempotente Testdaten, kein Produktionsstatus).

Modul: solcom_rsm_import

  • Kernel-Test für RsmXmlSource: eine Test-XML im UTF-16-Format wird aus private://rsm/ gelesen, der Plugin gibt korrekte Record-Iterationen zurück. Prüft: Dekodierung, Feld-Extraktion, SF-ID-Parsing aus FreelancerLink.
  • Kernel-Test für den Migrate-Prozess: vollständiger Durchlauf mit Fixture-XML erzeugt die erwarteten project_offer-Nodes (Titel, Status, field_rsm_lfd_nr, field_sf_id). Prüft Idempotenz (zweiter Run: keine Duplikate).
  • Kernel-Test für den State Flag: bei Flag=0 läuft kein RSM-Cron-Import.
  • Prior Art: solcom_project_offer/tests/ (SF-Pull-Tests) als strukturelle Referenz.

Modul: Webform-Handler (Email-Generierung)

  • Kernel-Test: Webform-Submit mit Test-Submission-Daten → E-Mail-Body enthält exakte Zeilen in korrekter Reihenfolge.
  • Kernel-Test: Eingeloggt → field_ppp_id erscheint in PPP-ID-Zeile; Anonym → UUID-Format (kein leerer Wert).
  • Kernel-Test: Betreff-Format mit lfdNr und Titel korrekt.
  • Prior Art: web/modules/custom/solcom_vacancy_application/tests/ (Personio-Inbox-Bridge-Tests, ähnliche Email-Handler-Logik per ADR-0047).

Modul: solcom_user (field_ppp_id)

  • Kernel-Test: Feld existiert auf User-Entity, ist speicherbar, Unique-Constraint verhindert Duplikate.

Kein Browser-Test in diesem Scope

Die UI-Integration (Webform auf Detailseite, Minggo-Button-Kondition) wird via manuellem Browser-Smoketest nach Deployment verifiziert (QA-Plan in den Story-Hubs).

Out of Scope

  • Bestandsnutzer-Migration (PPP-IDs aus dem alten Projektpartnerportal zu Drupal-Usern) — separater Task nach diesem Sprint
  • lfdNr → SF-ID Mapping und SF-Migrate-Übergang — Task beim SF-Go-Live; SOLCOM liefert Mapping-Tabelle
  • PPP-ID Auto-Assignment bei Neuregistrierung (Strategie folgt nach Migrations-Task)
  • SF-nativer Bewerbungsflow (nach SF-Go-Live, eigener Task)
  • Initiativbewerbung via Minggo (F-330, separates iFrame-Embed auf anderem Canvas-Block)
  • SFTP-Konfiguration auf dem Server (operativer Schritt, nicht Modul-Code)
  • Admin-UI zum Flip des State Flags — Drush-Kommando ist ausreichend für die kurze Übergangsphase

Further Notes

Offene Punkte (SOLCOM muss liefern)

Bewerbertyp-Werte: Genaue Strings für eingeloggte vs. anonyme Bewerber noch ausstehend. Vorschlag: Account User / Anonymous User. Muss vor Webform-Implementierung bestätigt werden, da die Power App exakt matcht.
Datenschutzerklärung-URL: URL für Checkbox-Label im Webform noch zu klären.
lfdNr → SF-ID Mapping: SOLCOM muss beim SF-Go-Live eine Mapping-Tabelle liefern. Ohne sie entstehen beim SF-Migrate Duplikate.

Architektur-Referenzen

XML-Dateiformat (Referenz)

Dateiname-Pattern: OpenPos_YYYYMMDD_HHmmss.xml. Encoding: UTF-16LE. Root-Element: <OpenPos>, Records als <Datensatz>. Beispieldatei: docs/rsm/OpenPos_20260522_165400.xml.