Vertikale Slices
Aufteilung gefolgt der PRD-Anbindungs-Reihenfolge (Schritt 5 explizit als „separate Slice, kann parallel laufen" markiert). Beide Slices sind AFK — der ralph-Loop kann sie ohne HITL durchziehen.
| Slice | Stakeholder-Titel | Jira-Key | Status | Blocked by |
|---|---|---|---|---|
| 1 | Gulp-Linkliste unter /feeds/gulp.html live | P2503-169 | IDEE | — |
| 2 | Alte Gulp-Anzeigen-Backlinks landen nach Cutover auf der neuen Detailseite | P2503-170 | IDEE | — |
Slice 1 — Gulp-Linkliste live
Stakeholder-Outcome (siehe Jira-AKs): Vertrieb sieht jedes für Gulp freigegebene Projektangebot automatisch in /feeds/gulp.html ohne Vendor-Pflege; Vendor-Klick führt auf /fuer-freiberufler/projektliste/<slug>.
Tech-Deltas im Sub-Modul solcom_project_offer/modules/solcom_project_offer_feeds/
- Routing: Eintrag
solcom_project_offer_feeds.gulpmitpath: /feeds/gulp.htmlinsolcom_project_offer_feeds.routing.yml. ControllerVendorFeedController::gulp. - Controller-Methode:
VendorFeedController::gulp(): CacheableResponse— eine-zu-eins-Spiegel zufreelancermap()/freelance(), mitContent-Type: text/html; charset=utf-8+X-Robots-Tag: noindex, nofollow+ Cache-Tagnode_list:project_offer+ einzelnenode:<nid>-Tags +max-age=3600. - Builder-Methode:
VendorFeedBuilder::buildGulp(array $offers, VendorContext $ctx): array. RuftgetEligibleOffers('field_sf_channel_gulp')auf (die bereits kanal-parametrisierte F-340-Filter-Methode). Render-Pfad: minimalerVendorContextmit Default-Werten (jobNumberSuffix='',utmParams=[],emitUtmUrl=FALSE). - Twig-Template:
templates/solcom-feed-gulp.html.twigmit<!doctype html>-Hülle und<ul>von<a>-Tags. Linktext ={{ job_title }} (m/w/d).href={{ node.toUrl('canonical', {'absolute': true}) }}. - Theme-Hook:
solcom_feed_gulpinsolcom_project_offer_feeds.moduleregistrieren, Variablepositionsanalog zu den XML-Templates.
Job-Title-Suffix-Logik
(m/w/d) an, außer der Roh-Titel enthält bereits eine Marker-Variante. Regex (Implementations-Detail): /\((m\s*[\/|]\s*w\s*[\/|]\s*[dxa])\)/i — Match → Suffix weglassen, kein Match → " (m/w/d)" anhängen. Suffix-Pflege in Drupal, nicht in Salesforce (Drift-Vermeidung).
Caching-Strategie
Drupal-Page-Cache aktiviert. CacheableResponse mit Cache-Tags node_list:project_offer + einzelne node:<nid>-Tags pro gerenderter Offer. max-age = 3600 als Sicherheitsnetz. Cache-Invalidation passiert automatisch über Drupal-Cache-Tag-Mechanik bei Migrate-Save eines Offers.
Pflicht-Tests
- Kernel —
VendorFeedBuilderTest::testBuildGulp(): 5–7 Fixture-Nodes mit verschiedenen Kombinationen ausstatus,field_sf_pub_status,field_sf_channel_gulp,field_sf_publish_start,field_sf_publish_end. Assert: gerendertes HTML enthält genau ein<a>pro publizierbarem Offer, korrekterhref(absolute Drupal-URL), korrekter Linktext mit Suffix. - Functional —
VendorFeedRouteTest::testGulpRoute(): HTTP-GET gegen/feeds/gulp.html. Asserts: HTTP-200,Content-Type: text/html; charset=utf-8,X-Robots-Tag: noindex, nofollow, Cache-Tag-Header enthältnode_list:project_offer, Response-Body enthält erwartete Detail-URLs.
AK-Mapping (Stakeholder-AKs aus Jira)
Slice 1 deckt AKs 1, 2, 3, 4, 5, 6, 7 der Sub-Task-Description (Stakeholder-Formulierung). Die Tech-AKs aus dem Story-Hub (AK 9 „Code lebt im Sub-Modul …" und AK 10 „Pflicht-Tests existieren") werden hier durch die Tech-Deltas + Pflicht-Tests-Sektion abgedeckt.
Slice 2 — Legacy-Backlinks 301
Stakeholder-Outcome (siehe Jira-AKs): Aufruf einer alten Gulp-Backlink-URL https://www.solcom.de/asp/robots/detail.aspx?mode=GULP&id=<sf-id> antwortet mit 301 auf die kanonische Drupal-Detail-URL des passenden Projektangebots. Kein Klick-Verlust bei aktiven Vendor-Anzeigen.
Tech-Deltas
- Routing: Eintrag
solcom_project_offer_feeds.legacy_detailmitpath: /asp/robots/detail.aspxinsolcom_project_offer_feeds.routing.yml. ControllerLegacyDetailRedirectController::redirect. Wichtig: der bestehendeLegacyRedirectSubscriber(statisches Path-Mapping aus F-340) wird nicht erweitert — dort gehört nur das Path-zu-Path-Mapping rein, nicht parametrisierter Query-Lookup. - Controller:
LegacyDetailRedirectController::redirect(Request $request)insrc/Controller/LegacyDetailRedirectController.php. Liestid-Query-Param, sucht Node viaentityTypeManager->getStorage('node')->loadByProperties(['type' => 'project_offer', 'field_sf_id' => $sfId]). Treffer: HTTP-301 auf$node->toUrl('canonical', ['absolute' => TRUE]). Kein Treffer:throw new NotFoundHttpException()→ HTTP-404. - Mode-Param: der
mode=GULP-Query-Param wird nicht ausgewertet — falls in Zukunft weitere Vendor-spezifische Lookups kommen, kann der Controller den Mode lesen, aber für F-360 ist nur derid-Param relevant. - UTM-Params durchreichen? Nein — kanonische Drupal-URL als Redirect-Ziel ohne Query-String. Tracking-Pflege passiert vendor-seitig.
Pflicht-Tests
- Functional —
LegacyDetailRedirectTest: Zwei Cases. (a) HTTP-GET ohne Follow gegen/asp/robots/detail.aspx?mode=GULP&id=<fixture-sf-id>→ Assert HTTP-301,Locationzeigt auf kanonische Drupal-Detail-URL des Fixture-Nodes. (b) HTTP-GET gegen/asp/robots/detail.aspx?mode=GULP&id=unbekannte-sf-id→ Assert HTTP-404.
AK-Mapping (Stakeholder-AKs aus Jira)
Slice 2 deckt AKs 1, 2, 3 der Sub-Task-Description (Redirect-Treffer, Nicht-Treffer-404, dauerhaft-aktiv).
Anbindungs-Reihenfolge
Innerhalb Slice 1 sequenziell, Slice 2 läuft parallel (eigener Controller, kein Konflikt mit Slice-1-Code):
- Routing + Controller-Methode
gulp()→ Smoketest gegen leere Liste. - Builder-Methode
buildGulp()+ Theme-Hook + Twig-Template. - Kernel-Test der Builder-Methode.
- Functional-Test der Route inkl. Cache-Tag- und Header-Asserts.
- Slice 2 (parallel ab Schritt 1): Routing +
LegacyDetailRedirectController+ Functional-Test.
Offene Fragen (aus PRD)
- Listentyp
<ul>vs.<table>: Default-Vorschlag<ul>, finalisierung beim Gulp-Vendor-Smoketest vor Cutover. Implementation startet mit<ul>. - Suffix-Logik: oben fixiert — Marker-Variante respektieren, sonst
" (m/w/d)"anhängen. - UTM-Akzeptanz Stakeholder: Drupal-Feed liefert kanonische Links ohne UTM. Wenn Gulp UTM-Decoration braucht, dekoriert Gulp vor dem Outbound-Klick. Position offen, aber Implementation entscheidet sich für „keine UTM im Feed" (Single Source of Truth: Drupal-canonical-URL).
Abhängigkeiten
- F-340 / P2503-112 produktiv — Sub-Modul + VendorFeedBuilder + Filter-Logik etabliert.
- F-290 / P2503-108 produktiv — Migrate-Sync liefert Project-Offer-Nodes.
- Pathauto-Pattern auf
/fuer-freiberufler/projektliste/[node:title](Vorbedingung wie bei F-340, gemäß ADR-0032). - Salesforce-Feld
SC_Publishing_Channel_Gulp__cexistiert und ist im Vertriebs-Workflow verankert.