Commit graph

55 commits

Author SHA1 Message Date
aae456954a billing: Nebenläufigkeit & Idempotenz härten (H4, Stripe, Boost-Slots)
- CreditTopup-Gutschrift, MAN-Rechnung, Proof-PDF und Stripe-Webhook-
  Spiegelung sperren die betroffene Zeile innerhalb der Transaktion und
  prüfen die Idempotenz erst danach (verhindert Doppelgutschrift bei
  at-least-once-Webhooks, H4).
- Gemeinsamer LocksModelForUpdate-Trait für die häufige Lock-by-Key-Form
  (CreditTopup, ManualInvoice).
- Unique-Index auf invoices.stripe_invoice_id; Boost-Inventar über
  boost_slot_locks serialisiert, verwaiste Kategorie-Locks werden beim
  Löschen der Rubrik entfernt.

Deployment: vor migrate prüfen, ob invoices.stripe_invoice_id bereits
Duplikate enthält (die Unique-Migration bricht sonst bewusst ab).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 13:47:21 +00:00
0cda19458d KI-Kennzeichnung: 2 Optionen im Submit-Modal + Labels auf Detailseite
Submit-Modal auf zwei Auswahloptionen reduziert (kein Label vs.
"Ganz oder ueberwiegend mit generativer KI erstellt"); alle vier
Enum-Werte bleiben fuer API/Bestand gueltig und mappen per formOption()
auf die zwei Gruppen. Oeffentliche Detailseite rendert jetzt Text-Label
("KI-generiert: Nach Angaben ...") und Bild-Badge (KI-generiert/
KI-bearbeitet, abgedunkelter Hintergrund) — fehlte dort bisher komplett.
Bildnachweis-Vorschlag ohne "Foto:"-Praefix (stand sonst doppelt).
Einreich-Toast + FAQ: automatische Pruefung in Minuten statt 24 h.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-10 16:51:23 +00:00
b26dc9057c Admin-PM-Liste: KI-Pruefungs-Modal + geschaerfte KI-Prompts
- Spalten umsortiert (Aktionen, Datum, KI-Pruefung zuerst); Pruefen-Button
  nur noch Icon, oeffnet Detail-Modal mit Klassifikation, Begruendung,
  Content-Score inkl. Faktor-Breakdown und Re-Check (wire:poll bis Queue durch)
- Content-Score: Bild-Punkte (10) systemseitig statt KI (nur ein Upload-Bild
  moeglich), Quellen 10->5, Pressestil 20->25; Breakdown wandert ins Audit-Log
- Klassifikations-Prompt prueft gegen aktive Ablehnungskategorien (K1-K6,
  rejection_categories): klarer Verstoss => direkt rot mit Kategorie-Code

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-10 16:37:47 +00:00
6a45ebf607 Legacy-API-Paritaet S1+S2: connektar-Flow verhaltensgleich + KI-Schalter
Eigene Compat-Controller (Api/Legacy) fuer die 5 real genutzten
/service-Endpoints (Prod-Log-Beleg): Company-/Kontakt-Create (liefen
410!), PM-Create, Bild-Upload, published-only-GET — verschachtelte
Alt-Params, 201+Location+{}, Alt-Fehlerformat, Hit-Zaehlung,
Legacy-Land-Mapping.

- Anlegen = Einreichen (Alt-Verhalten); Sofort-Publish via neuer
  Permission api:immediate-publication (WS-7: connektar zuweisen)
- Admin-Schalter 'KI-Pruefung Legacy-API' (app_settings-Tabelle,
  Toggle auf /admin/press-releases): an = review + KI-Routing
- Admin-PM-Liste: KI-Spalte (Ampel-Badge) + manueller Pruef-Button
  ohne Routing fuer ungepruefte PMs
- Mails bei API-Einlieferern an Pressekontakt, sonst Firmen-Mail,
  Fallback Einlieferer (User-Entscheidung 10.07.)
- Tests: LegacyServiceParityTest (connektar-Flow end-to-end);
  Suite 1182 gruen

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 14:09:40 +00:00
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
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
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
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
2a9be7a9af T10: DSA-Meldeformulare mit URL-Feld, Vorgangsnummer und Eingangsbestaetigung
Zwei eigenstaendige Formulare unter /melden/rechtswidrig (DSA Art. 16:
URL, Name, E-Mail, Begruendung, Wahrheitsversicherung) und
/melden/kontaktdaten (DSGVO-Antrag) auf allen drei Domains. Gemeldete
URLs werden automatisch der passenden veroeffentlichten PM zugeordnet;
ohne Treffer bleibt die URL im payload. Jede Meldung erhaelt eine
Vorgangsnummer (M-{id}, analog K-{id} der Kuendigung), die meldende
Person unverzueglich eine Eingangsbestaetigung (DSA Art. 16 Abs. 4) und
das Melde-Postfach einen internen Hinweis — auch im bestehenden
Per-PM-Flow. Admin-Queue zeigt Vorgangsnummer, Name und gemeldete URL;
/melden verlinkt die neuen Formulare als ersten Weg.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 09:35:30 +00:00
89550dac99 Security (R6.2): Haertung nach Audit
- Security-Header-Middleware (nosniff, X-Frame-Options, Referrer-/
  Permissions-Policy) global, proxy-unabhaengig; CSP bewusst separat.
- Firmenlogo-Upload im Create-Formular durch den ImageService-Pfad
  ersetzt (Mime-Whitelist ohne SVG, Re-Encode, UUID-Name) - vorher
  rohe store()-Ablage ohne Server-Validierung (SVG/XSS moeglich).
- Rate-Limits ergaenzt: Passwort-Reset (3/E-Mail + 15/IP pro Std.),
  Registrierung (10/IP pro Std.), throttle auf magic-login- und
  pm-vorschau-Consume-Routen (Defense-in-Depth).
- EnsureUserIsCustomer als persistente Livewire-Middleware
  (symmetrisch zum Admin-Gate, greift auch bei Folge-Requests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 13:43:59 +00:00
1c3a52ae3c Recht: Kuendigungsbutton nach Par. 312k BGB - Backend + Admin-Queue
Oeffentliche Bestaetigungsseite /de/kuendigung ohne Login: Art der
Kuendigung (Grund nur bei ausserordentlich), Zuordnungsdaten,
Beendigungszeitpunkt; Honeypot + Rate-Limit wie beim Widerruf.
Sofortige automatische Zugangsbestaetigung per E-Mail (Par. 312k
Abs. 4 BGB) + Betreiber-Mail. Kuendigungen laufen in der
Recht-&-Compliance-Queue unter den Widerrufen auf (K-Vorgaenge,
eigener Filter, Erledigt/Wieder-oeffnen, vierte KPI-Karte).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 11:32:43 +00:00
d6461f5c5e Admin: Widerrufe laufen in der Recht-&-Compliance-Queue auf
Eigene Widerrufe-Sektion unter der Anfragen-Tabelle in
/admin/legal-requests: Vorgangsnummer, Zuordnungsdaten, Statusfilter
(offen/alle/erledigt, eigener Paginator), Erledigt/Wieder-oeffnen-Aktion
mit resolved_at; KPI-Karte "Offene Widerrufe". Dazu
WithdrawalRequest-Factory (+resolved-State) fuer die Tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 10:29:32 +00:00
f9e3c64238 R0.3: Newsletter-Sync-Binding config-aware + ehrliche Test-Sync-Meldung
Bisher wurde der NullNewsletterSyncClient unbedingt gebunden -
NEWSLETTER_SYNC_ENABLED=true mit echtem Provider haette stumm nichts
getan. Jetzt: Platzhalter-Provider -> No-Op-Client, aktivierter aber
nicht implementierter Provider -> RuntimeException (fail fast). Das
Admin-UI weist beim Test-Sync auf den Platzhalter hin.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 07:55:35 +00:00
349991a4e8 5.2: User-Formular-Feldraster in Blade-Components (role/billing) extrahiert
role- und billing-Feldgrids aus create+edit in <x-portal.user-role-fields> und <x-portal.user-billing-fields> gezogen (nur das innere Grid; Panel-/Card-Wrapper bleiben pro Seite). Nebenbei-Bugfix: create zeigt jetzt den billing.name-Validierungsfehler (Feld wurde via userBillingRules() schon validiert, Fehler aber nie gerendert). company-link-picker + contact-fields bleiben bewusst inline (divergiert bzw. nur in edit).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 10:43:05 +00:00
55d0804917 5.3: PressReleaseStatus::badgeClass() zentralisiert + toten statusColor-Code entfernt
Status-Badge-Farblogik (ok/warn/err/muted) aus 7 divergenten match-Bloecken in eine Enum-Methode gezogen. Archiviert/Entwurf jetzt konsistent 'muted' (grau) auch auf Detail-/Edit-/Log-Seiten (vorher dort 'hub'-Akzent). Toter statusColor-Block (Flux-Farben, nie referenziert) in customer/show entfernt. flux:badge :color in admin/users bleibt bewusst separat (eigenes Farbvokabular, Einzel-Call-Site).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 10:36:15 +00:00
1729e35943 5.2: gemeinsamen Firmen-Sync-Kern als User::syncCompaniesWithRoles() gezogen
Analyse der beiden ~40/160-Zeilen-save()-Methoden (users/create vs edit): sie
teilen nur den User-Attribut-Payload und den byte-identischen Firmen-Sync-Loop
(Bearbeiten hat zusaetzlich Profil/Kontakte/volle Billing-CRUD). Ein voller
UserUpsertService waere Over-Engineering (ueberwiegend Branches) — daher nur
der wirklich gemeinsame Persistenz-Kern:

User::syncCompaniesWithRoles(array $companyIds, array $roles) baut das
Pivot-Payload (fehlende Rolle => 'member') und synct. Beide Formulare nutzen
es; im Bearbeiten kommen die linkedCompanyIds nun direkt aus $validated.

Test: UserSyncCompaniesTest (Rollen-Zuordnung, member-Default, Detach).
29 User-Tests gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 08:47:23 +00:00
587d1fd312 5.5: <x-portal.alert> + 6 Flash-Bloecke umgestellt
Erste 5.5-Component: buendelt den kopierten session()-Flash-Alert (Icon +
Farbschema) mit variant=success|error; Nachricht als Slot. Erzeugt
identisches Markup.

Umgestellt: categories/create (success), categories/edit (success+error),
footer-codes/index (success+error), presets/index (success).

Bewusst NICHT umgestellt: companies/show (abweichendes, icon-loses Design
mit zusaetzlicher info-Variante und anderen Farbtokens) — bleibt bespoke.

Render-Smoke geprueft, alle Views kompilieren, 23 Tests gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 08:39:44 +00:00
505852f29e 5.4: <x-portal.table-pagination> + 13 Wrapper ersetzt
Buendelt den 13-fach byte-identischen Paginierungs-Fuss (border-t + p-4 um
->links('components.portal.pagination')) in eine Component mit :paginator-
Prop. Erzeugt identisches HTML -> kein visuelles Risiko.

Ersetzt in: users, invoices/index, payments/index (4x: Bestandsschutz,
Subscriptions, Purchases, Invoices), contacts/index, legal-requests/index,
presets/index, companies/index, footer-codes/index, customer/invoices,
customer/press-releases/index.

Bewusst BARE gelassen (kein Wrapper im Original): press-releases/index,
categories/index, press-kits/index. Alle Views kompilieren, 251 Tests gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 08:35:37 +00:00
077d313baa 5.4: <x-portal.empty-state> + admin/press-releases/index umgestellt
Zweite Struktur-Component: buendelt den empty-stage-Block (runder Icon-Rahmen
+ Titel + Subtext + optionale Aktionen). Props: title, subtitle, variant
(default|warm|err); Slots :icon und :actions.

Erst-Umstellung: die zwei Leerzustaende der Admin-PM-Liste (gefiltert = warm
mit 'Filter zuruecksetzen', leer = Standard mit 'Neue PM anlegen'). Weitere
Vorkommen (customer/press-releases, customer/press-kits) folgen nach Sicht-
pruefung. 36 Tests gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 11:05:19 +00:00
feda8be4d3 5.4 (Teil 6): page-header in payments/invoices/legal/coupons/reports/newsletter
Component minimal-additiv erweitert (Defaults unveraendert):
- size=sm (28px) fuer legal-requests/show
- description-width=720px fuer breite Fliesstexte

Umgestellt (letzter grosser Batch):
- payments/index, payments/plans (720px, Aktionen)
- invoices/index, coupons/index (720px, Badge via :meta, keine Aktionen)
- legal-requests/index (Standard), legal-requests/show (sm, Status-Badge)
- reports/slow-requests (Logdatei-Zaehler-Badge als Aktion)
- newsletter/sync (Sync-Status via :meta, Dry-Run/Test-Sync-Buttons)

CSS-Hash unveraendert -> keine neuen Klassen. 74 Tests gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 10:36:33 +00:00
27a77bfc50 5.4 (Teil 5): page-header in roles/* + footer-codes/* + presets/*
Neun weitere Standard-Header (md) auf <x-portal.page-header>:
- roles/index (bedingter Anlegen-Button), roles/create (Guard-Badge via
  :meta), roles/edit (ID + Guard + bedingte Systemrolle via :meta)
- footer-codes/index, footer-codes/create, footer-codes/edit (ID + Global +
  Aktiv/Inaktiv via :meta, Titel als Beschreibung)
- presets/index, presets/create, presets/edit (ID via :meta)

Roles-/FooterCode-/Preset-Tests (23) gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 10:01:47 +00:00
1eb2243959 5.4 (Teil 4): page-header size=lg-Variante + press-releases/* + companies/index
Component um size-Prop erweitert: md (30px, Standard) | lg (34px, Landing-
Header). lg vergroessert zusaetzlich Beschreibung (13.5px) und Button-Abstand
(gap-3) — exakt die bisherige Sonderoptik der Listen-Landing-Header.

Umgestellt:
- press-releases/create (md), press-releases/edit (md, Status-Badge + ID via
  :meta, 3 Aktionen inkl. Modal-Trigger)
- press-releases/index (lg) und companies/index (lg) — Letzterer war zuvor
  bewusst ausgelassen, ist mit der lg-Variante jetzt originalgetreu abbildbar.

Bewusst NICHT umgestellt: press-releases/show (zusaetzlicher Untertitel +
mehrteilige Firma/Kategorie/Autor-Beschreibung, bleibt bespoke).

md-Ausgabe unveraendert (nur Klassenreihenfolge). 321 Tests gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 09:16:40 +00:00
2a3b5612cb 5.4 (Teil 3): page-header in users/* + contacts/* umgestellt
Sieben weitere Header auf <x-portal.page-header>:
- users (index), users/create, contacts/index, contacts/create (Standard;
  contacts/create mit bedingtem 'Firma vorausgewaehlt'-Badge via :meta)
- users/show, users/edit (ID + Portal + Aktiv/Inaktiv-Badges via :meta,
  dynamischer Titel/Beschreibung aus User-Daten)
- contacts/edit (ID + Portal-Label via :meta, ohne Beschreibung)

Component: break-words am H1 ergaenzt (fuer lange Namen/E-Mails in show/edit;
bei kurzen Titeln ein No-op). User-/Contact-Tests (129) gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 09:08:06 +00:00
af15e84984 5.4 (Teil 2): page-header in categories/* + companies/create,edit umgestellt
Vier weitere Standard-Header auf <x-portal.page-header> umgestellt:
categories/index, categories/edit (ID/PM/Unterkategorien-Badges via :meta),
companies/create, companies/edit (ID-Badge via :meta).

Bewusst NICHT umgestellt (Sonderfaelle, bleiben unveraendert):
- companies/index: abweichende Titelgroesse (text-[34px] statt 30px)
- companies/show: Sonder-Header mit Logo-Bild + Website-Link

Categories-/Companies-Tests (82) gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 08:31:45 +00:00
83bb2a7caa 5.4 (Teil 1): <x-portal.page-header> + categories/create umgestellt
Erste Seiten-Struktur-Component fuer das Admin-Backend: buendelt den 35-fach
kopierten page-header-Block (Hub-Badge + Eyebrow + H1 + Beschreibung, dazu
Slots :meta und :actions fuer die variablen Teile). Muster wie <x-portal.
stat-card>.

Bewusst nur EINE Seite (categories/create) umgestellt, damit die Optik
einzeln im Browser gegengeprueft werden kann, bevor die restlichen 34 Header
folgen. AdminCategoryManagement-Tests gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 14:00:09 +00:00
055fd5c47f Optimierung 5.3: toten portalBadgeColor()-Helfer entfernt
In companies/index, companies/show und contacts/index war die private
Methode portalBadgeColor() definiert, aber nirgends aufgerufen (kein
Aufruf im Blade-Markup oder PHP). Entfernt; in companies/show wird damit
auch der Portal-Import ungenutzt und faellt weg.

Klaert den im Wiedereinstieg-Plan offenen Punkt: keine Portal::badgeColor()-
Zentralisierung noetig, da es keine echten Call-Sites gab.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 10:56:50 +00:00
40679119c3 Optimierung 5.3 (Teil 2): Category::localizedName()
Name aus der locale-gefiltert geladenen Uebersetzung mit #id-Fallback als
Model-Methode. Ersetzt die identische Inline-Logik an 4 Stellen (Footer-Codes
und Kategorien jeweils create/edit).

Test: CategoryLocalizedNameTest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 13:19:06 +00:00
69395efa53 Optimierung 5.3 (Teil 1): Contact::displayName()
Anzeigename eines Kontakts (Vor-/Nachname mit Fallback) als Model-Methode.
Ersetzt die identische Inline-Logik an 6 eindeutigen Relation-$contact-Stellen
(Admin: Benutzer-/PM-/Firmen-Detail; Kunde: Press-Kit-/PM-Detail).

Bewusst noch offen: Select-/toBase-Varianten ($contactOption/$contactResult/
$lookupContact, stdClass-Risiko) sowie web/release-detail (User-Frontend).

Test: ContactDisplayNameTest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 13:15:38 +00:00
89842ac37f Optimierung 5.2 (Teil 1): ProvidesUserFormRules-Concern
Die zwischen Benutzer-Anlegen und -Bearbeiten identischen Validierungsbloecke
liegen jetzt in ProvidesUserFormRules: userIdentityRules(?int $ignore) (E-Mail-
Unique ignoriert beim Bearbeiten den eigenen Datensatz), userRolesAndCompaniesRules()
und userBillingRules(). Beide Formulare nutzen die Array-Spreads; die nur im
Bearbeiten genutzten Profil-/Kontakt-Regeln bleiben dort inline. Ungenutzten
Rule-Import aus dem Anlegen-Formular entfernt.

Offen (mit User abzustimmen, UI-/Divergenz-lastig): gemeinsamer Save-Service und
die Blade-Feld-Components.

31 User-Tests gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 12:41:57 +00:00
a8958c176e Optimierung 5.1: Tabellen-Concerns (Sortierung, Volltext-Heuristik, Stats)
- HasSortableTable::sort() ersetzt 5 byte-identische sort()-Kopien in den
  Admin-Listen (Benutzer, Firmen, Kategorien, Kontakte, Pressemitteilungen).
  $sortBy/$sortDir-Defaults bleiben je Komponente, da sie divergieren.
- HasFullTextSearch::supportsFullTextSearch() ersetzt 9 byte-identische Kopien
  (4 Index-Listen + 5 Firmen-Comboboxen); ungenutzte DB-Imports entfernt.
- AdminPerformanceCache::activeInactiveStats(Builder) buendelt den identischen
  SUM(CASE WHEN is_active)-Block der Benutzer- und Firmenliste.

Zurueckgestellt (real divergent): HasResettableFilters (updatedXxxFilter
normalisieren teils zusaetzlich) und eine gemeinsame hydrateRelationCounts().

Tests: erweiterte AdminPerformanceCacheTest; 99 Admin-Tests bleiben gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 12:36:48 +00:00
153bac7a9c Optimierung 2.8: <x-portal.pr-presubmit-check> Blade-Component
Den in allen vier PM-Editoren identischen, rein praesentationalen
Pre-Submit-Checklisten-Block (~34 Zeilen je Datei) in eine
Blade-Component ausgelagert, die $this->presubmitChecks als Prop nimmt.
Render-Test ergaenzt.

Bewusst konservativ: Nur dieser wire:model-freie, presentational Block
wurde extrahiert. Die interaktiven Markup-Panels (Tag-Input, Publish-
Schedule, Kontakt-/Firmen-Select) bleiben in den Editoren, da ihre
wire:model-/Alpine-Bindungen ohne visuelle QA nicht risikofrei in
Components zu ueberfuehren sind und die Tests gerendertes Markup nur
begrenzt abdecken.

Tests: volle Suite gruen (800 passed, 1 skipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:16:21 +00:00
3a18a2b494 Optimierung 2.9: HandlesAdminPressReleaseActions::findAdminPR()
Den ~15x duplizierten Admin-Lookup PressRelease::withoutGlobalScopes()
->findOrFail($id) in einen findAdminPR()-Helfer (Concern) zentralisiert;
index/edit/show nutzen ihn. Die eager-load-Query in show::with() bleibt
unberuehrt (anderer Zweck).

Bewusst NICHT vereinheitlicht: das Status-Badge-Mapping divergiert real
(index: archived/draft -> muted; edit/show: -> hub via default) -> waere
eine Design-Entscheidung ueber kanonische Badge-Farben, keine mechanische
Dedup.

Tests: volle Suite gruen (799 passed, 1 skipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:11:48 +00:00
86ba6c1a51 Optimierung 2.7: Company::scopeSearchByTerm fuer die Admin-Combobox
Die in admin/create und admin/edit identische Inline-Firmensuche
(Volltext-oder-LIKE) inkl. des 2x duplizierten supportsFullTextSearch()-
Treiберchecks in einen Company::scopeSearchByTerm() zentralisiert; beide
Editoren nutzen ihn jetzt (Verhalten unveraendert). Ungenutzte DB-Importe
entfernt. Customer nutzt bereits CustomerCompanyContext (eigene
join-/user-gescopte Query, daher nicht zusammengelegt).

Tests: neuer Scope-Test; volle Suite gruen (799 passed, 1 skipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:06:54 +00:00
83d2ee659a Optimierung 2.6: reprocessAfterContentChange im PressReleaseService
Den 3x identischen contentChanged->fresh->reclassifyIfClassified->
rescoreIfScored-Block (admin/edit 1x, customer/edit 2x) in
PressReleaseService::reprocessAfterContentChange() zentralisiert.

Hinweis: Eine vollstaendige createDraft()/updateContent()-Verlagerung
wurde bewusst NICHT erzwungen — die Attributsaetze und der umgebende
Validierungs-/Fehlerfluss von Admin (no_export, direkter review-Status)
und Customer (placeholder_variant, Titel-Fallback, immer Draft) divergieren
real; eine gemeinsame Persistenzmethode waere fragil bei grosser Testflaeche.

Tests: 2 neue Faelle; volle Suite gruen (796 passed, 1 skipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 10:01:29 +00:00
8c53146548 Optimierung 2.5: Kontakt-Helfer + tagSuggestionsFor zentralisiert
Erkenntnis: Die Admin-Inline-Query (Contact::withoutGlobalScopes()->where
->orderBy) ist exakt der Contact::forCompany()-Scope -> alle vier
Kontakt-Helfer waren funktional identisch. companyContacts/companyContact/
defaultContactIdFor in neuen Concern InteractsWithPressReleaseContacts
(ueber forCompany) ausgelagert. tagSuggestionsFor() (4x identisch) in
InteractsWithPressReleaseTags verschoben. Ungenutzte Collection-Importe
in den Customer-Editoren entfernt; selectedCompany() bleibt je Kontext
(Admin: withoutGlobalScopes find; Customer: CustomerCompanyContext).

Tests: volle Suite gruen (794 passed, 1 skipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 08:59:38 +00:00
b41d34e39f Optimierung 2.4: Kanonische Pre-Submit-Checkliste
presubmitChecks() der vier Editoren in ProvidesPressReleasePresubmitChecks
zentralisiert (war 4x dupliziert mit Drift). Kanonische Version uebernimmt
die bessere Customer-Variante: Firmenname statt leerer Hinweis in der
company-Zeile. Dafuer erhalten die Admin-Editoren ein selectedCompany()
(Company::withoutGlobalScopes find), das auch in with() genutzt wird;
Customer behaelt seine user-gescopte Aufloesung via CustomerCompanyContext.

Tests: volle Suite gruen (794 passed, 1 skipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 08:53:06 +00:00
fa995e30a2 Optimierung 2.3: formRules() via ProvidesPressReleaseFormRules
Die ~95% identischen Validierungsregeln (Inhaltsfelder, publishMode,
terminabhängiger Scheduling-Block mit zeitzonenbewusster Closure, Embargo)
in baseFormRules() zentralisiert. Die vier Editor-formRules() ergänzen nur
noch die abweichenden Schlüssel (portal nur Admin, companyId mit/ohne
exists). Ungenutzten Rule-Import in customer/edit entfernt.

Tests: volle Suite gruen (794 passed, 1 skipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 08:46:27 +00:00
64349ce6b4 Optimierung 2.2: InteractsWithPressReleaseScheduling-Trait
Die in allen vier PM-Editoren wortgleiche Scheduling-Maschinerie
(updatedPublishMode/ScheduledDate/ScheduledTime, updated, syncScheduledAt,
resolveScheduledAt, validateScheduledAtWhenReady, notifyValidationError)
in einen gemeinsamen Concern ausgelagert (spiegelt InteractsWith-
PressReleaseTags). ~85 Zeilen je Datei entfernt.

Tests: volle Suite gruen (794 passed, 1 skipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 08:41:21 +00:00
0344ac26af Optimierung 2.1: Admin-PM-Editoren nutzen Tags-Trait
Admin create/edit nutzen jetzt InteractsWithPressReleaseTags (wie die
Customer-Editoren) statt die byte-gleiche addTag/removeTag/tagsArray-Logik
lokal zu duplizieren. ~48 Zeilen je Datei entfernt.

Tests: 259 PressRelease-Tests gruen.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 08:32:41 +00:00
554a731a24 Optimierung Phase 1: Aufraeumen / toter Code
1.1 Prototyp admin/users/table entfernt: Route (war durch admin/users/{id}
    ohnehin unerreichbar) + Volt-Komponente table.blade.php geloescht,
    ADMIN_ROUTES.md aktualisiert; Regressions-Test in PanelConsolidationTest.
1.2 16 tote Legacy-Wrapper-Views unter resources/views/admin/* geloescht
    (nutzten alte Route-Parameter, nirgends via view/@include/@livewire
    gerendert); admin.dashboard + .md-Doku behalten.
1.3 Tote Helfer entfernt: EnsureUserIsAdmin redundantes loadMissing('roles'),
    ungenutzter statusColor-Key aus with() in admin PR edit/show,
    LegacyInvoicePdfController Laufzeit-Schema::hasColumn (Spalte existiert
    seit Migration) inkl. Schema-Import.

Volle Suite gruen (794 passed, 1 skipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 08:27:12 +00:00
e96a11824b Paket-Vorteile: dynamische, im Admin pflegbare Vorteilsliste je Tarif
Neue JSON-Spalte plans.benefits (Migration + Cast + Plan::benefitsList()).
Im Admin (Tarife & Pakete) pro Tarif zeilenweise pflegbar; die In-App-
Buchungsseite rendert die Liste in den Tarif-Karten statt einer festen
Zeile. Seeder/Factory mit Beispiel-Vorteilen. Single Source of Truth fuer
Buchungsseite und (folgt separat) die oeffentliche Preise-Seite.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 16:36:09 +00:00
624fca68a8 Zeitzone Europe/Berlin: unnoetige UTC-Konvertierung entfernt
Beim Speichern/Veroeffentlichen waren Termine 2 Stunden versetzt: die
App-Zeitzone stand auf UTC, Termine wurden nach UTC umgerechnet, aber
published_at roh angezeigt.

- config/app.php: timezone UTC -> Europe/Berlin
- PressRelease: DISPLAY_TIMEZONE + setTimezone-Umrechnung entfernt
- Admin-/Customer-Formulare: scheduledAtUtc() -> resolveScheduledAt(),
  parst direkt in App-Zeitzone; Edit-Hydration ohne setTimezone
- ProofPdfRenderer + Boost-Anzeige: setTimezone entfernt
- Tests an verschiebungsfreies Verhalten angepasst

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 12:40:19 +00:00
f87c470a0b WS-4: Bestandsschutz-Abrechnung – gekoppelte Kündigung + Admin-Sichtbarkeit
M1 (portalübergreifend) und M5 (04:30-Worker) verifiziert; M2 + M3 umgesetzt.

- M2 (Entscheidung: getrennte Rechnungen, gekoppelte Kündigung): neuer
  LegacySubscriptionService::cancelBundleFor() kündigt das gesamte
  Bestandsschutz-Bündel eines Kunden gemeinsam – keine Einzelkündigung. Kein
  Rechnungs-Schema-Umbau (jede Kondition behält ihre MAN-Rechnung).
- M3: Admin-Payments-Ansicht zeigt eine "Bestandsschutz-Posten"-Tabelle
  (Badge, Portal, Netto, nächste Fälligkeit, "Bündel (N)") + Bündel-Kündigung
  mit Bestätigungsmodal.
- Tests: tests/Feature/Billing/LegacyBundleTest.php (M1 cross-portal, M2 Bündel-
  Kündigung + Gate, M3 Admin-Liste + Kündigung). Detailplan WS-4 dokumentiert.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 15:20:18 +00:00
9bce9afe0b WS-3: Recht & Compliance – Rechts-Kern (DSGVO/Persönlichkeitsrecht/Melden + Queue)
Launch-pflichtiger Compliance-Slice: öffentliche Anfrage zu einer PM speist eine
manuelle Admin-Queue (keine KI).

- Migration legal_requests + Model + Enums (Type: dsgvo/personal_rights/report,
  Status: open/in_progress/resolved/rejected) + Factory.
- Öffentliches Formular /release/{slug}/rechtliches (LegalRequestController +
  web/legal-request.blade.php): typ-abhängiger Hinweistext (Alpine), E-Mail bei
  DSGVO/Persönlichkeitsrecht erforderlich, zwei versteckte Honeypot-Felder,
  Rate-Limit + Bremse "1 offene Anfrage pro PM/Typ". Regeltexte als Entwurf mit
  TODO für rechtliche Finalisierung markiert.
- Routen bewusst in eigener routes/legal.php (entkoppelt vom laufenden Web-Umbau),
  host-agnostisch via domains.php eingebunden.
- Admin-Bereich "Recht & Compliance": Sidebar-Nav mit Offen-Zähler, Volt-Queue
  index/show (in Bearbeitung/erledigt/abgelehnt/wieder öffnen + interne Notiz).
- Tests: je Typ, Honeypots (Dataset), Bremse, Admin-Queue + Status-Übergänge.
- Doku: Detailplan WS-3-Status + Deployment-Migrationsreihenfolge ergänzt.

Hinweis: Der "Melden"-/E&F-Button auf der PM-Detailseite (release-detail.blade.php)
wird mit dem separaten Web-Frontend-Commit verdrahtet; Ziel ist legal-request.create.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 14:20:05 +00:00
ced39dd371 KI-generierte Bilder: eigener Lizenztyp, Anbieter-Bestätigung, Kennzeichnung
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 16:04:12 +00:00
ad75886929 PM-Vorschau: Workflow oben + farbig, Metadaten, saubere Inhalts-Typografie
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 15:16:52 +00:00
b126254d53 Responsive-Härtung: Seiten-Header, Kontextleiste, Stat-Cards
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 14:08:08 +00:00
c5907dacda Admin-Zahlungsmodul: Zahlungs-Übersicht + Tarif-Verwaltung mit Stripe-Sync
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 13:54:53 +00:00
48993f5506 User Panel: Phase-8-Abschluss, Titelbild/Lizenzen/Zeitzonen und KI-Pruef-Pipeline
Phase 8 (Rest) + Umbauten vom 10./11.06.:
- Ein Titelbild pro PM (Cover 1280x580), SVG-Platzhalter-Set + Picker,
  PressReleaseCoverImage-Resolver
- Lizenz-/Rechteformular nach "Lizenztyp Bildupload" (7 Lizenztypen,
  Personen-/Sachrechte-Status, bedingte Pflichtfelder, Risikohinweise)
- Veroeffentlichungs-Box vereinfacht (Embargo aus der Form-UI entfernt),
  geplante Termine in Europe/Berlin (Speicherung UTC, DISPLAY_TIMEZONE)
- Quota-Stub (users.press_release_quota) + monatlicher Reset-Command
- Einreichungs-Modal einheitlich in Show/Create/Edit; Ghost-Buttons auf
  filled; PM-Editor-Layout responsive entkoppelt (.pr-editor-layout)

KI-Pruef-Pipeline (Phasen 1-5 des Entwicklungsplans):
- API-Haertung: status nicht mehr per API setzbar, eigene Submit-Route
  durch denselben Funnel (Blacklist, Quota, Status-Log)
- Klassifikation Rot/Gelb/Gruen asynchron (Queue classification,
  OpenAI-Treiber + deterministischer Fallback), ki_audits-Audit-Log
- Routing: Rot -> rejected + Mail, Gelb -> Review-Queue, Gruen ->
  Auto-Publish; Scheduler publiziert nur gruene faellige PMs
- Content-Score 0-100 -> Stufe (Standard/Geprueft/Hochwertig) inkl.
  Editor-Panel und Badges; Re-Klassifikation/-Score bei Aenderung
- Admin: KI-Badge + Filter, On-Demand-Pruefung mit Anbieter-Override

Suite: 442 passed, 4 skipped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 08:30:13 +00:00
fd1f6bd401 22-05-2026 Optimierung der User und Admin Panels 2026-05-22 11:18:59 +02:00