User Story
Als Vertriebsmitarbeiter möchte ich meine offenen Projektanfragen automatisch über eine einfache HTML-Linkliste an Gulp übergeben, damit die Reichweite meiner Projektangebote ohne manuelle Pflege auf der Vendor-Plattform steigt.
Technische Beschreibung
Drupal publiziert unter /feeds/gulp.html eine HTML-Linkliste. Jeder Eintrag ist ein <a>-Tag mit der kanonischen Drupal-Detail-URL (/fuer-freiberufler/projektliste/<slug>, siehe ADR-0032) und dem Jobtitel als Linktext. Filterung über das bereits existierende Boolean-Feld field_sf_channel_gulp. Bewerben passiert auf der Detail-Seite über den bestehenden Minggo-Flow (F-330). Code lebt im bereits in F-340 angelegten Sub-Modul solcom_project_offer/modules/solcom_project_offer_feeds/.
Korrektur-Notiz 2026-05-21
Die Story trug ursprünglich den Arbeitstitel „Randstad XML / API". Refinement hat zwei Drifts korrigiert, die im Story-Hub-File als History dokumentiert sind:
- Vendor: Randstad → Gulp. Randstad hat keine öffentlich dokumentierte Publisher-API; der heute aktive Vendor-Feed adressiert Gulp.
- Pattern: Push-Multiposting-Distributor (verworfener Plugin-Architektur-Ansatz) → Pull-HTML-Feed (Pattern-Spiegel zu F-340).
- Output-Format: XML wie F-340 → HTML, weil Gulp eine simple Linkliste erwartet (User-Screenshot 2026-05-21).
Worklog
P2503-178 — URL-Kanon-Cutover 2026-05-25 — mp/P2503-178
Folge-Sub-Task zu P2503-113. Live-Pathauto-Pattern fuer project_offer war noch auf /projektpartnerportal/[node:title] (per ADR-0032 R1), die Test-Assertions in P2503-113 wurden aber bereits gegen den kanonischen Sitemap-Pfad /fuer-freiberufler/projektliste/<slug> (Sitemap N16 unter N14, Context-0002) gehaertet. Diese Story korrigiert das.
Files: config/sync/pathauto.pattern.project_offer_content.yml (Pattern-Edit), web/modules/custom/solcom_project_offer/solcom_project_offer.install (hook_update_9007), web/modules/custom/solcom_project_offer/tests/src/Kernel/PathautoUrlCanonRedirectTest.php (3 Kernel-Tests), docs/adr/0033-project-offer-url-canon-correction.html (neu), docs/adr/0032-project-offer-sync-hygiene.html (Supersede-Note an R1).
Live-Resultat: 211 Alias(e) regeneriert, 211 301-Redirect(s) angelegt, 0 Duplikate, 0 Non-Offer uebersprungen. Live-Smoke gruen: /projektpartnerportal/<slug> → HTTP 301 + Location /fuer-freiberufler/projektliste/<slug>; neue URL → HTTP 200; Gulp-Feed enthaelt keine alten Pfade mehr.
Test-Gate triple-green: PHPUnit 3/3 (43 Assertions), PHPCS clean, PHPStan 0 Errors (45 files).
solcom-review 2026-05-25 — feature/P2503-113
3-Achsen-In-Session-Review gegen develop. Findings: standards=5 (2 hard, 3 judgement), spec=3, canvas=skipped. Worst: Hard 1 — TrustedRedirectResponse 301 ohne addCacheableDependency() (3-Zeilen-Patch). Spec-Findings sind dokumentierte Test-Assertion-Tradeoffs (Pathauto nicht im KernelTestBase). Volltext: docs/reports/2026-05-25-feature-P2503-113-review.html.
Clean-Branch-Cutover 2026-05-25 — feature/P2503-113 ab origin/develop
Die Story-Implementation lief urspruenglich auf mp/P2503-113 mit 15 Ralph-Loop-Commits. Fuer den finalen PR gegen develop wurde der Branch feature/P2503-113 ab origin/develop neu aufgesetzt und der State der 9 Story-Files per git checkout mp/P2503-113 -- <paths> uebernommen. mp/P2503-113 bleibt als Backup-Branch unberuehrt. Vier Low-Findings aus der Vorrunde wurden inline gefixt (siehe Bloecke unten).
P2503-169 — Gulp HTML-Feed unter /feeds/gulp.html live
AK-Refs: AK-1, AK-2, AK-3, AK-4, AK-5, AK-6, AK-7
Files (Subset des feat(P2503-113)-Commits, da VendorFeedController.php + routing.yml mit P2503-170 geteilt werden):
solcom_project_offer_feeds.module,
solcom_project_offer_feeds.routing.yml (route /feeds/gulp.html),
src/Controller/VendorFeedController.php (Methoden gulp() + respond()),
src/Service/VendorFeedBuilder.php (getEligibleOffers + buildGulp),
templates/solcom-feed-gulp.html.twig,
tests/src/Kernel/GulpFeedBuilderTest.php,
tests/src/Kernel/GulpFeedRouteTest.php
Low-Findings aus Vorrunde adressiert auf feature/P2503-113:
- P3 — AK-5 Content-Type-Assertion exact:
GulpFeedRouteTest.php:154 jetzt assertSame('text/html; charset=utf-8', ...) statt Substring-Check. Implementation am Controller (respond($rendered, 'text/html; charset=utf-8')) bereits korrekt.
- P4 — AK-6 Cache-Tag-Invalidation echter Save+Re-Fetch:
GulpFeedBuilderTest.php alte Methode testCacheTagInvalidatedAfterNodeSave umbenannt in testCacheTagsAttachedToResponse (drueckt aus, was sie wirklich testet); neue Methode testFeedRefreshesAfterNodeSave verifiziert echten Save-dann-Re-Fetch-Zyklus mit Cache-Tag-Invalidation. installSchema('node', ['node_access']) in setUp() ergaenzt (Kernel-Test-Quirk fuer Resave).
- P5 — respond() Raw-Cache-Control entfernt: Doppelter Cache-Pfad eliminiert.
CacheableMetadata::setCacheMaxAge(3600) ist source-of-truth; Raw-Header 'Cache-Control' => 'public, max-age=3600' aus dem CacheableResponse-Constructor entfernt. F-340 XML-Feeds nicht regressiv (alle 25 Tests gruen).
QA: 11/12 strict pass live in DDEV, 1 deviation (AK-2 URL-Pfad config-Drift auf Pathauto-Pattern, out-of-scope), 1 covered-via-PHPUnit (AK-5 Cutover-Path post-deploy verify). Details in docs/reports/2026-05-25-feature-P2503-113-qa.html.
P2503-170 — Gulp-Legacy-Backlink-Redirect (/asp/robots/detail.aspx → kanonische Detail-URL)
AK-Refs: P2503-170 AK-1 (301 mit Location), AK-2 (404 fuer unbekannte SF-ID), AK-3 (Redirect dauerhaft aktiv, kein Verfallsdatum im Controller); Story-Level AK-5 (Cutover-Backlink fuer alte Gulp-Anzeigen).
Files (Subset des feat(P2503-113)-Commits):
solcom_project_offer_feeds.routing.yml (route /asp/robots/detail.aspx),
src/Controller/VendorFeedController.php (Methode detailRedirect(), accessCheck(FALSE) mit Begruendungs-Kommentar),
tests/src/Kernel/GulpLegacyRedirectTest.php
Low-Findings aus Vorrunde adressiert:
- P6 — 404 als CacheableResponse: Beide
new Response('', 404)-Stellen (Z. 41 leerer SF-ID-Pfad, Z. 56 No-Match-Pfad) ersetzt durch (new CacheableResponse('', 404))->addCacheableDependency($notFoundTags) mit Cache-Tag node_list:project_offer. Damit invalidieren spaetere SF-Sync-Pflege-Aktionen (neuer Offer mit der bisher unbekannten SF-ID) die 404-Response automatisch.
Live-QA AK5a (gueltige SF-ID): HTTP 301 + Location auf kanonische Detail-URL. AK5b (unbekannte SF-ID): HTTP 404. Cutover-Path mit Host: www.solcom.de in DDEV nicht testbar (Router-Vhost), covered via PHPUnit GulpLegacyRedirectTest + post-deploy verify in Staging.