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
- Als Projektpartner möchte ich ab Go-Live aktuelle Projektangebote auf der SOLCOM-Website sehen, damit ich nicht auf den Salesforce-Launch warten muss.
- Als Redakteur möchte ich, dass Projektangebote täglich automatisch aktualisiert werden, ohne manuelle Eingriffe.
- Als Redakteur möchte ich, dass deaktivierte RSM-Datensätze (
Aktiv=0) automatisch unpublished werden, damit keine abgelaufenen Angebote sichtbar sind. - 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.
- Als Entwickler möchte ich, dass der Import idempotent ist (Folge-Run ohne Duplikate), damit tägliche Cron-Ausführungen zuverlässig bleiben.
- Als Entwickler möchte ich, dass die neueste XML-Datei automatisch ausgewählt wird, damit der Import auch bei tagesstempel-basierten Dateinamen funktioniert.
- Als Entwickler möchte ich, dass alte XML-Dateien nach 30 Tagen automatisch gelöscht werden, damit das
private://rsm/-Verzeichnis nicht überläuft. - 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
- Als Projektpartner (eingeloggt oder anonym) möchte ich mich auf ein konkretes Projektangebot bewerben können, damit meine Bewerbung beim zuständigen Bearbeiter ankommt.
- Als eingeloggter Projektpartner möchte ich, dass mein Name und meine E-Mail im Formular vorausgefüllt sind, damit ich Zeit spare.
- Als anonymer Besucher möchte ich das Bewerbungsformular ausfüllen können, ohne einen Account anlegen zu müssen, damit der Einstieg niedrigschwellig bleibt.
- Als Projektpartner möchte ich mein CV als Datei hochladen können, damit meine Unterlagen direkt mit der Bewerbung ankommen.
- Als Projektpartner möchte ich eine Bestätigung nach dem Absenden sehen, damit ich weiß, dass meine Bewerbung eingegangen ist.
- 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.
- Als Bearbeiter bei SOLCOM möchte ich im CC der Bewerbungs-E-Mail stehen, damit ich sofort informiert bin.
- 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.
- Als Projektpartner möchte ich das Webform nicht sehen, sobald das System auf Salesforce umgestellt ist, damit es keine veralteten Bewerbungswege gibt.
PPP-ID
- 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.
- Als eingeloggter Bewerber möchte ich, dass meine PPP-ID automatisch in der Bewerbungs-E-Mail erscheint, damit die Power App mich zuordnen kann.
- 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.
- 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 sichtbar0→ 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-Feld | Drupal-Feld | Anmerkung |
|---|---|---|
lfdNr | field_rsm_lfd_nr | Migrations-Key; neues Feld |
Titel | title | Node-Titel |
Beschreibung | body | HTML-Fragment aus RSM |
Ort | field_location | Bestehendes Feld prüfen/anlegen |
Start | field_start_date | Format: DD.MM.YYYY → Date |
Dauer | field_duration | Freitext-String |
Stellentyp | field_job_type | Freitext-String |
Bearbeiter | field_contact_email | E-Mail des Zuständigen; für Webform-CC |
Aktiv | status | 0 → unpublished |
FreelancerLink (URL-Suffix) | field_sf_id | Letztes URL-Segment extrahieren |
FreelancerLink (gesamte URL) | field_minggo_url | Für Minggo-Link-Button |
Region | field_region | Neu anlegen wenn nicht vorhanden |
Branche | field_industry | Neu anlegen wenn nicht vorhanden |
skills | field_skills | Freitext 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')->valuein 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_urlnicht leer — Link zusolcom.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_Nfü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 ausprivate://rsm/gelesen, der Plugin gibt korrekte Record-Iterationen zurück. Prüft: Dekodierung, Feld-Extraktion, SF-ID-Parsing ausFreelancerLink. - 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_iderscheint in PPP-ID-Zeile; Anonym → UUID-Format (kein leerer Wert). - Kernel-Test: Betreff-Format mit
lfdNrund 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)
Account User / Anonymous User. Muss vor Webform-Implementierung bestätigt werden, da die Power App exakt matcht.
Architektur-Referenzen
- ADR-0048: alle Architektur-Entscheidungen mit Rejected Alternatives
- ADR-0047: State-Flag-Pattern (Personio-Inbox-Bridge als Vorlage)
- Story: RSM XML Import
- Story: Bewerbungs-Webform
- Story: PPP-ID Feld auf User
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.