Commit graph

251 commits

Author SHA1 Message Date
fd8e050cb7 Backend-WIP: Kontakt-Queue, Page-Cache, Validierungs-Feedback, i18n
Gebuendelt, da ueber gemeinsame lang-/Service-Dateien verschraenkt:
- Kontakt-Nachrichten: contact_messages-Tabelle + Model/Enums/Factory,
  Admin-Queue (livewire/admin/contact-messages), ContactController
  persistiert das Anliegen zusaetzlich zur Brand-Mail
- Page-Data-Cache (Services/Web/*PageData) + web-archive-Indizes auf
  press_releases
- Validierungs-Feedback + Company-Portal-Restriction (Feature-Tests)
- i18n-Auslagerung lang/de|en (auth, passwords, validation, static)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:38:11 +00:00
c6b5ec8479 Release-Doku: Stand 09.07. nachgezogen; Runbook FULLTEXT-Fenster + Monitoring
Stand-Block 07.-09.07. (Billing/USt, Tarifwechsel, Agentur-Attribution,
KI-Kennzeichnung, Tages-Fixes), R5.3 abgehakt, Commit-Zahlen aktualisiert.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:34:40 +00:00
6984846233 Monitoring (R5.3): Betriebs-Alerts fuer Scheduler-/Queue-Ausfaelle
OperationalAlert loggt critical + Best-Effort-Mail an MONITORING_ALERT_EMAIL
(Fallback LEGAL_CONTACT_EMAIL, feature-flagged). Alle 6 Scheduler-Tasks mit
onFailure, Queue::failing-Listener; .env.example: LOG_STACK=daily als Prod-Soll.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:34:40 +00:00
ac870a4076 Buchungsseite: Buchungs- und Verlaengerungsdatum des Abos aus Stripe
Cashier spiegelt die Periode nicht lokal; neuer SubscriptionPeriodResolver
holt start_date + current_period_end (Item-Ebene) gecacht, Fallback null.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:34:28 +00:00
a254bbd6a3 Depublikation: Credit-Preis auf 35 (O6), Preis-Tests nachgezogen
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:34:28 +00:00
07e0bc598e API-Doku: OpenAPI-Spec aus public/ statt geloeschtem docs/ ausliefern
Der docs/-Mount kam nicht mit; die Route /docs/api/v1 lief dadurch auf 500.
Liest jetzt public/api/v1.yml mit 404-Guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 15:34:28 +00:00
f11e8aab7c PM-Editor UX: Kategorie in die Schreibfläche, Bild-Upload als Modal-Flow
Kategorie-Pflichtfeld direkt unter die Firma statt in die Sidebar.
Titelbild-Upload öffnet ein Modal: erst Bild wählen, dann erscheinen
die gestrafften Schritte (Herkunft & Bildnachweis mit Vorschlag aus
dem Urheber, Personen & Rechte, Bestätigung), Optionales eingeklappt.
Bildrechte-Checkbox im Einreichen-Modal nur noch mit eigenem Titelbild.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-09 13:55:21 +00:00
807fbc1b80 KI-Kennzeichnung (Art. 50 KI-VO): Pflicht-Deklaration für Text & Bilder
Selbstdeklaration des KI-Einsatzes je PM (Text) und je Bild inkl.
Submit-Gate im PressReleaseService, versioniertem Audit-Trail
(press_release_ai_declarations) und öffentlichem KI-Label in
Einreicher-Stimme. Bestätigung im Submit-Modal ist Pflicht.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-09 13:54:43 +00:00
74e82eb643 Form-Hooks: Fehl-Scroll aufs erste Feld bei Live-Validation behoben
Zwei Bugs im Scroll-zum-Fehler-Helper: '[wire\:submit]' matchte das
<form> selbst, sodass jeder Klick in ein Feld den Scroll armierte
(sichtbar beim USt-ID-Live-Check im Profil); und der leere, versteckte
Flux-Error-Container unter jedem Feld galt wegen Whitespace als
':not(:empty)' — fokussiert wurde dadurch grundlos das erste Feld.
Enter-Submit wird jetzt über das submit-Event erkannt, Fehlerfelder
nur noch bei echtem Fehlertext.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 13:29:12 +00:00
3297179226 USt-ID vor jeder Buchung per eVatR gegenprüfen, Stripe-Tax-IDs abgleichen
USt-IDs laufen ab — Kunden bestellten anderswo über veraltete IDs
steuerfrei. Vor jedem Checkout wird die Profil-USt-ID jetzt einmal per
eVatR (BZSt) geprüft (Ergebnis 6 h gecacht): Eine definitiv abgelehnte
ID geht nicht an Stripe, nicht mehr passende eu_vat-Einträge am
Stripe-Customer werden entfernt — Stripe Tax rechnet dann wieder mit
Steuer statt Reverse Charge. Fail-open bei API-Ausfall und deutschen
IDs (eVatR prüft nur ausländische EU-IDs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 13:28:56 +00:00
fe8bd2b931 Buchungsseite: Verlauf mit Kauftyp, Erfolgsmeldungen grün
„Eingelöste Käufe" nennt jetzt Kauftyp und PM-Titel (z. B.
„Depublikation: <Titel>") über das neue kompakte shortLabel() am
SinglePurchaseType. Die Checkout-Erfolgsbanner (Buchung, Tarifwechsel,
vorgemerkter Wechsel) und die Extra-PM-Bestätigung nutzen das
etablierte grüne Success-Muster (ok-soft) statt Hub-Blau.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 11:25:24 +00:00
f6882b621a Depublikation: Kauf als eingelöst markieren, Rechnung & Bestätigungs-Mail
Drei Lücken im bezahlten Depublikations-Flow (T12) geschlossen:

- Webhook setzt den Kauf jetzt auf status=consumed (vorher nur
  consumed_at) — erst damit erscheint er unter „Eingelöste Käufe".
- Einmalkäufe (Depublikation, Einzel-PM, Credit-Topup) aktivieren
  invoice_creation im Stripe-Checkout: Ohne das Flag erzeugt Stripe im
  Payment-Mode keine Rechnung und die STR-Spiegelung lief ins Leere.
  Abo-Checkouts bleiben unverändert (Parameter dort unzulässig).
- Neue Bestätigungs-Mail PressReleaseDepublished an den Käufer
  (nicht zwingend der PM-Autor) nach vollzogener Depublikation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 11:25:12 +00:00
d57f6b5c66 Agentur-Attribution: Plan abgehakt, Abnahme-Status 08.07. dokumentiert
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:51:56 +00:00
3ff95bc93a UI: Agentur-Angaben im Profil, Firmen-Typ-Auswahl entfernt
A5 des Umbaus Agentur-Attribution → User-Profil: Kunden-Profil erhält
den Abschnitt „Agentur-Angaben" (Feld Agentur-/Verteilername, öffentlich
als Übermittler sichtbar). Firma-Anlegen (Kunde + Admin) und
Admin-Firma-Bearbeiten kommen ohne Typ-Feld aus — Neuanlagen sind immer
type=company. Firmen-Übersicht zeigt kein Typ-Label mehr. Admin-User-
Formulare pflegen agency_name; die Detailseite zeigt den Übermittler
als Badge. CompanyFactory erzeugt keine zufälligen Agentur-Typen mehr.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:44:17 +00:00
59ac67a4c8 companies:retire-agency-type: Firmen-Typ „Agentur" abschaffen
A4 des Umbaus Agentur-Attribution → User-Profil: Agentur-Firmen mit
PMs oder Pressekontakten werden zu type=company umtypisiert (sie sind
faktisch Newsrooms), leere Agentur-Firmen soft-gelöscht. Geschützt vor
Löschung (nur Umtypisierung): Mitgliedschaften Dritter in company_user
oder Verknüpfungen in user_payment_option_company. --dry-run +
JSON-Report; im Deploy-Runbook nach legacy:migrate-agency-names
eingereiht.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:33:39 +00:00
85fddbfdf8 Attribution: Übermittler aus users.agency_name statt Agentur-Firma
A3 des Umbaus Agentur-Attribution → User-Profil: Eager Loading der
Detailseite/Vorschau lädt user:id,agency_name; die Legacy-Relation
PressRelease::submittingAgency() (Pseudo-Firma type=agency via
owner_user_id) entfällt. Tests decken Verteiler-Fall, Namensgleichheit
und PM ohne Übermittler ab.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:30:41 +00:00
a62a5eea6e PM-Detailseite: Attributions-Zeile Urheber/Übermittler
Rendert unter dem Inhalt „Original-Inhalt von {Firma} und übermittelt
von {Übermittler}" (Legacy-Konvention), Übermittler aus dem Profilfeld
users.agency_name; bei Namensgleichheit kollabiert die Zeile zu
„Inhalt übermittelt von …", ohne Übermittler zu „Original-Inhalt von …".

Nur die Attributions-Hunks aus dem laufenden Web-Umbau; die übrigen
Working-Tree-Änderungen an View/Sprachdateien bleiben unangetastet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:30:20 +00:00
402fa4a38b legacy:migrate-agency-names: Agentur-Namen → users.agency_name
Übernimmt je Owner den Namen seiner Legacy-Agentur-Firma(en) ins neue
Profilfeld (A2 des Umbaus Agentur-Attribution → User-Profil). Regeln:
zuletzt aktualisierter Datensatz gewinnt bei Mehrfach-Namen (alle
Varianten im Report), bereits gesetzte Werte werden nie überschrieben,
Owner-lose Agentur-Firmen werden nur reportet. --dry-run + JSON-Report
nach storage/app/private/migration/; im Deploy-Runbook (Erst-Einrichtung)
nach dem Firmen-/User-Import eingereiht.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:25:04 +00:00
e457e7a81c Profilfeld users.agency_name für Übermittler-Attribution
Erster Schritt des Umbaus Agentur-Attribution → User-Profil
(dev/agentur-attribution/entwicklungsplan.md, A1): Die Übermittler-
Angabe wird Profil-Eigenschaft des einliefernden Accounts statt
Pseudo-Firma vom Typ „agency".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-08 09:20:32 +00:00
881a583bec Depublikation: abgebrochener Checkout blockiert nicht mehr
Der Klick auf "Kostenpflichtig depublizieren" legte sofort einen
Pending-Kauf an; wer bei Stripe abbrach, sah danach dauerhaft "liegt
bereits ein Depublikations-Vorgang vor". Jetzt sperrt nur ein bezahlter
Vorgang — ein offener Pending-Kauf wird beim nächsten Klick
wiederverwendet (kein Duplikat), und die Detailseite zeigt das Angebot
weiterhin an.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 15:07:32 +00:00
28de92e18b PM-Detailseite: Direktlinks zur öffentlichen Seite je Portal
Veröffentlichte PMs zeigten im Backend nur den Status-Badge, ohne Weg
zur öffentlichen Seite. Im Seitenkopf stehen jetzt Direktlinks — bei
Portal::Both einer je Portal (QA-Befund 07.07.).

Die URL-Logik ist als PressRelease::publicPortalLinks() zentralisiert
(vorher doppelt in der Published-Mail und fehlerhaft im Nachweis-PDF:
dort fehlte das /de/press-release/-Segment, die dokumentierte URL lief
ins Leere; bei Both nennt der Nachweis jetzt beide URLs). Edition folgt
der PM-Sprache statt hart „de".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 15:00:40 +00:00
930760a056 single_purchases: type-ENUM um depublication erweitern
Die create-Migration baut das ENUM dynamisch aus
SinglePurchaseType::cases(); Datenbanken von vor dem T12-Case kannten
depublication daher nicht und der Depublikations-Checkout brach mit
"Data truncated for column 'type'" ab. Die Migration gleicht das ENUM
per MODIFY COLUMN auf den aktuellen Case-Stand ab (nur MySQL; frische
DBs bekommen die volle Liste ohnehin über die create-Migration).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 14:51:56 +00:00
d7f17bbc76 PM-Detailseite: Depublikations-Sektion ans Seitenende verschoben
Der seltene Ausnahme-Service stand zwischen Add-ons und Inhalt; jetzt
steht er als letzte Sektion unter dem Inhalt (QA-Befund 07.07.). Dazu
zwei UI-Tests: Sektion + Unternehmer-Bestätigungs-Protokoll auf der
Detailseite, Ausblenden ohne Preis bzw. bei laufendem Vorgang.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 14:35:42 +00:00
9600a4f887 Add-ons: Modal statt Toast bei fehlendem Credit-Guthaben
Scheiterte ein Boost- oder Nachweis-Kauf am Wallet-Guthaben, gab es nur
einen flüchtigen Toast ohne Weg zur Lösung. Jetzt öffnet sich ein Modal
mit Kosten, Guthaben und fehlender Differenz sowie einem Direktlink
"Credits aufladen" zur Wallet-Sektion der Buchungsseite (neuer
#credits-Anker).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 14:30:57 +00:00
0f8f0b2d31 Einreichen-Modal: Panel-Struktur und fixierter Kontingent-Badge
Das Modal war ein unstrukturierter Stapel aus zwei Boxen und losen
Switches; der Kontingent-Badge wurde vom Flex-Layout zusammengedrückt
und brach um ("3 /" / "3"). Jetzt: drei beschriftete Panels (Rechtliche
Hinweise, PM-Kontingent, Ihre Bestätigungen) im portalweiten
panel/panel-head-Stil, die Switches als getrennte Zeilen mit
Fortschrittszähler (x / 4) im Panel-Kopf, Badge mit shrink-0 und
whitespace-nowrap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 14:16:56 +00:00
acd241b3f2 PM-Editor: Ablehnungsbegründung direkt auf der Bearbeiten-Seite anzeigen
Bei abgelehnten Pressemitteilungen stand die Begründung bisher nur auf
der Detailseite. Der Editor zeigt jetzt dasselbe Hinweis-Panel (Begründung
+ Ablehnungsdatum) über der Schreibfläche, damit der Kunde beim
Überarbeiten sieht, was zu ändern ist.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 14:09:42 +00:00
c4ccc751e9 Rechnungs-Seite: Archiv-Filter entfernt, Altsystem-Archiv nur für Bestandskunden
QA-Befund 07.07.: Die Filter-/Suchzeile unter den aktuellen Rechnungen
entfällt; das Rechnungsarchiv (Altsystem) erscheint nur noch, wenn der
User dort tatsächlich importierte Rechnungen hat — Neukunden sehen es
nicht mehr. Seitenbeschreibung passt sich entsprechend an.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 13:53:05 +00:00
80e04e26e0 Downgrades ohne Gutschrift: Tarifwechsel nach unten erst zum Periodenende
QA-Entscheidung 07.07.: Eine Buchung ist nicht rückabwickelbar — es darf
nie eine negative Proration-Rechnung oder ein Stripe-Guthaben entstehen.

- StripeCheckoutService::isDowngrade: sofortiger Wechsel nur, wenn der
  Periodenpreis des Ziel-Tarifs den aktuellen erreicht (Wechsel-Rechnung
  damit garantiert >= 0); sonst Downgrade
- scheduleDowngrade: Stripe Subscription Schedule stellt das Abo zum Ende
  der bezahlten Periode um (proration_behavior none), hebt eine
  vorgemerkte Kündigung auf und spiegelt die Vormerkung lokal
  (neue Spalten scheduled_stripe_price/scheduled_change_at/
  stripe_schedule_id an subscriptions)
- Sofort-Upgrade löst eine bestehende Vormerkung vorher auf (release)
- Webhook customer.subscription.updated räumt die Vormerkung nach
  Vollzug ab
- Buchungs-Modal unterscheidet Upgrade (sofort, Differenz-Rechnung) und
  Downgrade (Vormerkung, keine Zahlung heute); Banner + Hinweis auf der
  Paket-Karte zeigen den vorgemerkten Wechsel mit Datum
- Kontingent-Sperre aus 4b7f6d3 entfernt — ohne Gutschrift gegenstandslos

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 13:38:57 +00:00
388ca0e620 Downgrade-Vorkehrungen: Kontingent-Sperre beim Tarifwechsel, Gutschriften nicht spiegeln
QA-Befund 07.07.: Der Wechsel zurück in einen kleineren Tarif erzeugte
eine Proration-Gutschrift (negative Stripe-Rechnung) — die STR-Spiegelung
crashte an den unsigned-Spalten (Webhook-500), und der Wechsel war trotz
verbrauchtem Monatskontingent möglich.

- ProcessStripeWebhook: Belege ohne positiven Rechnungsbetrag
  (Gutschrift/Null-Rechnung) werden geloggt übersprungen statt gespiegelt
- CheckoutController::swap: Wechsel gesperrt, wenn der Monatsverbrauch
  das Kontingent des Ziel-Tarifs übersteigt (möglich ab Monatserstem)
- Buchungs-Modal: erklärt die Sperre inkl. Datum statt Bestätigungs-
  Button; Proration-Hinweis deckt jetzt auch die Guthaben-Richtung ab

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 13:22:30 +00:00
14994e4748 PM-Vorschau: echtes Portal-Design, Direkt-Button und Portal-Switch
Der Vorschau-Link (/pm-vorschau/{token}) renderte eine schlichte
Standalone-Seite, die dem spaeteren Erscheinungsbild nicht entsprach.
Jetzt rendert er die echte Detailseite (web.release-detail) im Theme
des Zielportals:

- Controller wendet Domain-/Theme-Konfig des Zielportals an (Sprache
  der PM, Links zeigen auf die Portal-Domain), kein Hit-Zaehler
- Gelbes Vorschau-Banner mit Status und Link-Gueltigkeit; noindex per
  Meta-Tag + X-Robots-Tag-Header, Canonical entfaellt bei noindex
  (neue robots-Section im Web-Layout)
- Teilen-Buttons teilen den Vorschau-Link selbst (oeffentliche URL
  existiert vor Veroeffentlichung noch nicht)
- PMs fuer beide Portale: ?portal=... waehlt die Ansicht, Umschalter
  im Banner; im Kundenpanel je Portal ein Vorschau-Button, sonst ein
  einzelner "Vorschau oeffnen"-Button (Link-Feld jetzt kopierbar)
- Alte press-release-preview-View entfernt

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 13:11:57 +00:00
db65c2ed7d Suche: Volltext-Index + Fix fuer doppelte Alpine-Instanz
Archiv-Suche lief auf grossen Datenbestaenden langsam und war im Browser
komplett tot: app.js startete ein eigenes Alpine, Livewire injizierte
zusaetzlich seins - der DOM-Morph brach mit "Alpine.transaction is not a
function" ab (keine Rubrik klickbar, Suchbegriff nicht aenderbar).

- app.js buendelt jetzt Livewire + Alpine aus livewire.esm,
  @livewireScriptConfig im Web-Layout stoppt die Auto-Injektion
- MySQL-Volltext-Index (Boolean-Mode, +wort*) statt LIKE ueber alles;
  LIKE-Fallback fuer SQLite-Tests und kurze Tokens
- Firmen-/Rubrik-Treffer als ID-Listen statt orWhereHas (OR + EXISTS
  hebelte den Volltext-Index aus)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 13:03:14 +00:00
b3780e0fea Tarifwechsel im Buchungs-Modal: Abo-Swap mit anteiliger Verrechnung
Tarif-Karten bieten bei bestehendem Abo jetzt 'Zu diesem Tarif
wechseln' (pro Intervall, aktueller Tarif deaktiviert). Der Wechsel
laeuft ueber das bekannte Bestaetigungs-Modal (T1neu-Checkbox) als
POST auf die neue checkout.swap-Route: Cashier swapAndInvoice stellt
sofort um, verrechnet anteilig und hebt eine vorgemerkte Kuendigung
auf. SCA-Faelle leiten auf die Cashier-Bezahlseite. QA-Befund 07.07.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:58:32 +00:00
af156608b1 Checkout: Wallet-Daten duerfen Profil-Rechnungsadresse nicht ueberschreiben
customer_update: auto liess Apple Pay den zuvor synchronisierten
Stripe-Customer ueberschreiben (Kartenname + nur Land) — die Rechnung
trug den falschen Empfaenger. Mit Profil-Rechnungsadresse bleibt der
Customer jetzt unangetastet; nur ohne Adresse erfasst Stripe sie
selbst im Checkout (inkl. USt-ID-Abfrage).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:33:48 +00:00
304c5817b5 Checkout: Profil-Rechnungsadresse als Stripe-Rechnungsempfaenger
Die lokal gepflegte Rechnungsadresse stand nur bei der Erstanlage am
Stripe-Customer; der Checkout erzwang die Neueingabe und ueberschrieb
sie — auf der Rechnung stand die getippte Adresse statt der Profildaten
(QA-Befund 07.07.). Jetzt: Customer-Sync (Name/Firma, Adresse, USt-ID)
vor jedem Checkout, stripeName()-Hook liefert Firma bzw. Rechnungsname
statt Login-Anzeigename, billing_address_collection auf 'auto' (keine
Zwangs-Neueingabe bei hinterlegter Adresse).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:09:05 +00:00
5166fc7b40 Rechnungsseite: neue STR/MAN-Rechnungen gelistet, STR bei Stripe verlinkt
Die Kundenseite /admin/me/invoices zeigte bisher nur das Legacy-Archiv.
Neue Sektion "Aktuelle Rechnungen" mit Netto/USt/Gesamt und Status;
STR-Rechnungen oeffnen die gehostete Stripe-Rechnungsansicht ueber
einen Redirect-Endpunkt (URL wird je Aufruf frisch geholt, Zugriff nur
fuer den Eigentuemer). KPIs zaehlen neu + Archiv zusammen.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 12:01:12 +00:00
2a8fd0179e STR-Spiegelung: USt aus total_taxes lesen (Stripe-API ohne tax-Feld)
Aktuelle Stripe-API-Versionen liefern kein Top-Level-`tax` mehr auf der
Invoice — die Spiegelung buchte 0 USt und markierte die Rechnung als
netto (QA-Befund 07.07., STR-00001). Fallback-Kette: tax (alt) →
total_taxes (aktuell) → total_tax_amounts (Übergangsname).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:42:31 +00:00
54213cac64 Stripe Checkout: automatic_tax aktiviert (USt fehlte in der Session)
Cashier::calculateTaxes() greift nur bei direkter Abo-Erstellung, nicht
bei Checkout-Sessions — Stripe zeigte darum nur den Nettopreis
(QA-Befund 07.07.). Session-Optionen setzen jetzt automatic_tax +
tax_id_collection; Ad-hoc-Buchungen (Credits, Depublikation) laufen
statt ueber checkoutCharge ueber price_data mit tax_behavior=exclusive,
da automatic_tax das verlangt. Stripe Tax im Testmode eingerichtet
(Origin Bielefeld, DE-Registrierung); Live-Schritt in WS-7 ergaenzt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:25:28 +00:00
d1c976372e Buchungs-Modal: Dark-Mode-lesbare Preistabelle + aufgeraeumtes Layout
Preistabelle und Trenner nutzen Alpha-Farben (black/white) statt
Theme-Tokens, die im Dark Mode gegen den Modal-Hintergrund untergingen;
--color-bg-subtle war zudem nirgends definiert (Gesamt-Zeile ohne Bg).
Unternehmer-Bestaetigung als abgesetzter Consent-Block, Fusszeile mit
Trennlinie, Subtitle in den Kopf integriert. QA: A5 abgehakt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:13:55 +00:00
4fe0882dea QA-Checkliste: A1-A4 manuell abgenommen (07.07., inkl. 2FA-Fixes)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:01:46 +00:00
b42e0e3f62 2FA-Challenge: Fehlversuch crasht nicht mehr (Route two-factor.login fehlt)
Fortifys FailedTwoFactorLoginResponse leitet auf die Route
two-factor.login um, die bei views => false nie registriert wird
(RouteNotFoundException). Eigener Response leitet zurück auf die
Volt-Challenge-Seite und meldet den Fehler auf Deutsch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:58:17 +00:00
15387d0a3f QA-Befunde A4: 2FA-Bestätigungsschritt + sicherer E-Mail-Wechsel
- 2FA: Einrichtung verlangt jetzt die Bestätigung mit einem App-Code
  (Fortify confirm-Semantik); vorher blieb two_factor_confirmed_at
  leer und der Login fragte nie nach der Challenge.
- Recovery-Codes nur noch einmalig sichtbar (nach Bestätigung bzw.
  Neu-Erzeugung), nicht mehr bei jedem Seitenaufruf.
- E-Mail-Änderung als Pending-Flow: Bestätigungslink geht an die NEUE
  Adresse, die alte bleibt bis zum Klick aktiv — kein Aussperren durch
  Tippfehler und keine Registrierungs-Notice mehr nach dem Ändern.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 10:53:24 +00:00
b7bbf6df4e Panel-Navigation: Buchungen zu Finanzen, neue Gruppe Portale
- "Buchungen & Add-ons" aus "Mein Bereich" in die Finanzen-Gruppe verschoben
  (vor Rechnungen)
- Neue Gruppe "Portale" nach Konto: externe Links auf die Startseiten von
  presseecho und businessportal24 (URLs aus config/domains.php, neuer Tab)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:02:47 +00:00
1904ea588b Platzhalter-Eintraege ("spaeter") aus der Panel-Seitennavigation entfernt
Statistiken, Credits & Tarif, Zahlungsarten und Benachrichtigungen waren
als ausgegraute Platzhalter sichtbar — fuers Release raus (User 06.07.);
die Features sind ohnehin nachgelagert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:00:55 +00:00
339ae5de70 Plan: Adress-Import, Key-Import-Verifikation und Git-Hygiene 06.07. dokumentiert
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:37:42 +00:00
2c7b5564d1 Produktiv-SQL-Dumps enttrackt (Security-Review-Befund R6.2)
dev/migration 2026/sql/ enthaelt echte Kundendaten aus den Altportalen —
per git rm --cached enttrackt und ignoriert; die Dateien bleiben lokal
liegen (Import-Rehearsal R6.3 braucht sie). Entscheidung 06.07.2026:
ohne History-Bereinigung, solange das Repo privat bleibt. docs/legal/
(Rechtstext-Quellen, Arbeitsstand des Users) ebenfalls ignoriert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:36:47 +00:00
bfad13af7d Spoke-Frontend-Audit: Fake-Inhalte raus, Newsletter-Rueckbau, echte Boersendaten, EN-Lokalisierung
Frontend-Umbau (Kevin, 06.07.2026), Ergebnis des businessportal24-Audits:

- Fake-Anzeigen und Mock-/Fallback-Daten entfernt (focus-ad, feed-ad,
  highlight-card, press-release-card, featured-releases, press-releases-grid);
  Sektionen rendern nur noch mit echten Daten oder gar nicht
- Newsletter-Strip und Newsletter-Box zurueckgebaut (Feature nachgelagert,
  vor Reaktivierung DOI-Flow noetig)
- Echte Boersendaten: MarketDataService (Yahoo-Finance-Quotes, config/market.php,
  Cache) + market:refresh alle 15 Min. im Scheduler; live-ticker blendet sich
  ohne Daten aus
- EN-Lokalisierung der Kern-Seiten via lang/de/web.php + lang/en/web.php;
  Carbon-Locale folgt app()->getLocale()
- Web-Tests auf den neuen Frontend-Stand nachgezogen (Suite: 993 gruen)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:36:23 +00:00
3320aaaa85 Rechnungsadressen der Bestandskunden aus dem Legacy-Archiv uebernehmen
Befund 06.07.: Der MAN-Rechnungslauf ueberspringt Vereinbarungen ohne
Rechnungsadresse — der Bestandsschutz laeuft dann am Periodenende faktisch
aus. Der Grandfather-Import nahm die Adressen bisher nicht mit, weil die
Altportale sie nur als Freitext-Blob fuehren.

- legacy:import-billing-addresses: uebernimmt Adressen der grandfathered
  Kunden aus der Quell-Rechnung im Archiv (pdf_payload.billing_address),
  nie ueberschreibend, mit Dry-Run und Report der manuell nachzupflegenden
  Faelle (leere/unparsebare Blobs, unbekannte Laender)
- LegacyAddressParser: Best-Effort-Strukturierung der Blobs (mehrzeilig,
  kommasepariert, CH-PLZ-Praefix, Firmen-/Zusatzzeilen); raet nie
- Laenderaufloesung ueber die country-Tabellen der Legacy-Verbindungen
- config/countries.php: CY ergaenzt (reale Bestandskunden aus Zypern)
- WS-7- und QA-Checkliste: Import-Schritt nach dem Grandfathering

Lokal: 11 von 21 Bestandskunden importiert, 9 im Report (7 leere Blobs);
MAN-Lauf rechnet zuvor blockierte Vereinbarungen jetzt ab.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 12:19:50 +00:00
387d41a782 Plan + QA-Checkliste: Legacy-Fixes vom 06.07. dokumentiert
Buchungs-Gate-Bug (grandfatheredNow) und Legacy-API-Uebernahme im
Stand-Block festgehalten; QA-Punkt zur API von "api_key wird abgewiesen"
auf die neue Kompatibilitaets-Semantik umgestellt (inkl. Import-Pruefung
beim Staging-Rehearsal).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 11:59:04 +00:00
91992ac4b0 Uebernahme bestehender Legacy-API-Integrationen (Keys, Auth, Pfade)
Bestehende Kunden-Integrationen der Altportale laufen nach dem Relaunch
weiter, ohne dass Kunden etwas umstellen muessen:

1. legacy:import-api-keys uebernimmt die api_keys aus den Legacy-DBs
   (sf_guard_user_profile) als Sanctum-Tokens mit identischem Secret
   (Sanctum loest Tokens ohne id|-Praefix per sha256-Hash auf) — nur
   fuer berechtigte Nutzer (aktiver Zahlungsstatus/Bestandsschutz,
   ApiAccessEligibilityService); idempotent, mit Dry-Run. Probe gegen
   die lokalen Snapshots: 406 Keys wuerden uebernommen.
2. Die Auth-Middleware uebersetzt die alten Uebergabeformen (X-ApiKey-
   Header, api_key-Parameter) fuer gueltige Keys in Bearer; unbekannte
   Keys erhalten weiterhin 410 mit Migrationshinweis.
3. Die dokumentierten /service/...-Pfade der Alt-API sind als Aliasse
   auf die v1-Controller gelegt (pressreleases inkl. Bilder, companies
   lesend); nicht uebernommene Alt-Endpoints (Pressemappen/Kontakte
   anlegen) antworten 410 mit Hinweis auf Kundenkonto und neue Doku.

API-Seite + OpenAPI entsprechend aktualisiert; Go-Live-Checkliste um den
Import-Schritt (nach dem Grandfathering) ergaenzt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 11:55:22 +00:00
3cd7395319 Fix: Bestandsschutz ohne explizites Vertragsende zaehlte nie als aktiv
Der grandfatheredNow-Scope verlangte ein gesetztes grandfathered_until —
das ist aber das explizite Legacy-Vertragsende (valid_until_date) und im
Import-Regelfall NULL (offene Dauervereinbarung). Ergebnis: praktisch
kein Legacy-Kunde hatte eine aktive Zahlungsoption; eingeloggte
Bestandskunden sahen "Buchung erforderlich" und konnten nicht einreichen.
Jetzt gilt: grandfathered_until deckelt, wenn gesetzt; sonst zaehlt die
laufende, vom MAN-Rechnungslauf fortgerollte Periode (current_period_end
= next_due_date). Tests decken beide NULL-Faelle und den Deckel ab.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 11:40:39 +00:00