# User-Admin: Zusammenhänge und relevante Daten Stand: 2026-06-11 (aktualisiert nach Phase 8 + KI-Pipeline) Diese Notiz beschreibt den User als fachlichen Mittelpunkt für die weitere Konzeption des Admin-User-Bereichs. Grundlage sind die aktuellen Models und Migrationen im Laravel-Projekt. > **Was seit Phase 8 + KI-Pipeline dazugekommen ist (29.05.–11.06.2026)**: > > - `press_releases`: `placeholder_variant` (SVG-Titelbild-Platzhalter, 8G), > `classification`/`classified_at` (KI-Klassifikation Rot/Gelb/Grün), > `content_score`/`content_tier`/`scored_at` (Content-Score). > - `press_release_images`: Lizenz-/Rechtefelder `author`, `license_type`, > `license_detail`, `license_url`, `source_url`, `people_rights_status`, > `property_rights_status`, `rights_notes`, `persons_consent`, > `rights_confirmed_at` (8H + Erweiterung 10.06.). > - `users`: Quota-Stub `press_release_quota` + > `press_release_quota_used_this_month` (8J; wird vom Tarif-Modul abgelöst). > - Neue Tabelle `ki_audits` (Modell `KiAudit`, append-only Audit-Log jeder > KI-Entscheidung) mit Relation `PressRelease::kiAudits()`. > - Neue Enums: `PressReleasePlaceholder`, `ImageLicenseType`, > `PressReleaseClassification`, `PressReleaseContentTier`. > - Neue Services/Jobs: `PressReleaseCoverImage` (Cover-Resolver), > Treiber-Architektur unter `Services/PressRelease/Classification/` und > `…/ContentScore/`, Jobs `ClassifyPressRelease`/`ScorePressRelease` > (Queue `classification`), Konfiguration in `config/scoring.php`. > - Neue Commands: `press-releases:reset-monthly-quota` (Scheduler, 1. des > Monats), `classification:work` (Queue-Drain zum Testen). > - Zeitzonen-Konstante `PressRelease::DISPLAY_TIMEZONE` (Europe/Berlin) mit > `scheduledAtLocal()`/`embargoAtLocal()` für alle Termin-Anzeigen. > **Was sich seit dem ursprünglichen Stand (2026-05-05) geändert hat**: > > - Pressemitteilungen haben zusätzliche Felder: `subtitle`, `scheduled_at`, > `embargo_at`, `boilerplate_override`, `no_export` (Phase 7). > - Neue Tabelle `press_release_attachments` (Modell `PressReleaseAttachment`). > UI ist temporär deaktiviert (Security-Review), Tabelle und Service > bleiben aber erhalten. > - Neuer Service-Layer für PMs: `PressReleaseService` (Status-Übergänge, > Scheduling-Auflösung), `PressReleaseHtmlSanitizer` (HTMLPurifier-Wrapper), > `PressReleaseAttachmentStorage`. > - Neuer Console-Command `php artisan press-releases:publish-scheduled` > (Scheduler-Intervall 5 Min) veröffentlicht geplante PMs automatisch. > - Pivot `press_release_contact` bleibt n:m, der Customer-Flow speichert > aber nur einen Kontakt pro PM und das Feld ist nullable. ## Zentraler Ausgangspunkt Ein `User` ist nicht nur ein Login-Konto, sondern bündelt im System mehrere fachliche Bereiche: - Zugang und Status: Name, E-Mail, Verifikation, Passwort, 2FA, Aktiv/Inaktiv, Super-Admin-Flag, Rollen/Berechtigungen. - Portal- und Legacy-Zuordnung: `portal`, `registration_type`, `language`, `legacy_portal`, `legacy_id`. - CRM-Zuordnung: Firmen, Firmenrollen, Kontakte. - Content-Zuordnung: Pressemitteilungen, Bilder, Statusverlauf. - Abrechnung und Verträge: Rechnungsadresse, Zahlungsoptionen, Zahlungen, Rechnungen, Legacy-Rechnungen. - API und Sicherheit: Sanctum-Tokens, API-Nutzung, Magic-Links. - Kommunikation und Arbeitskomfort: Newsletter-Abos, Filter-Presets. ## Datenmodell im Überblick ```mermaid erDiagram users ||--o| profiles : "hat" users ||--o| billing_addresses : "hat" users ||--o{ companies : "owner_user_id" users }o--o{ companies : "company_user.role" users }o--o{ contacts : "contact_user" companies ||--o{ contacts : "hat" users ||--o{ press_releases : "autor" companies ||--o{ press_releases : "zugeordnet" contacts }o--o{ press_releases : "press_release_contact" press_releases ||--o{ press_release_images : "hat" press_releases ||--o{ press_release_status_logs : "hat" users ||--o{ press_release_status_logs : "changed_by_user_id" users ||--o{ user_payment_options : "hat" user_payment_options }o--o{ companies : "user_payment_option_company" user_payment_options ||--o{ user_payments : "hat" user_payments ||--o{ invoices : "erzeugt" users ||--o{ invoices : "hat" users ||--o{ legacy_invoices : "hat" users ||--o{ newsletter_subscriptions : "hat" users ||--o{ magic_links : "hat" users ||--o{ user_filter_presets : "hat" users ||--o{ api_usage_logs : "hat" ``` ## Direkte Relationen am User `profile` - Kardinalität: 1:0..1. - Tabelle: `profiles`, Foreign Key `user_id` ist eindeutig. - Inhalt: persönliche Profildaten, Anrede, Titel, Vor-/Nachname, Telefon, Adresse, Land, Geburtsdatum, Backlink, Statistik-/Footer-Code-Flags, Validierungs-/Vertragsdaten, Steuerdaten. - Relevanz im Admin: Datenqualität, Legacy-Profil, persönliche Stammdaten, Vertrag/Validierung. `billingAddress` - Kardinalität: 1:0..1. - Tabelle: `billing_addresses`, Foreign Key `user_id` ist eindeutig. - Inhalt: Rechnungsname, Adresse, PLZ, Ort, Land. - Relevanz im Admin: Rechnungsfähigkeit, fehlende Abrechnungsdaten, Kundendatenpflege. `ownedCompanies` - Kardinalität: 1:n. - Tabelle: `companies`, Foreign Key `owner_user_id`. - Bedeutung: Der User ist fachlicher Eigentümer einer Firma, auch ohne Pivot-Eintrag in `company_user`. - Relevanz im Admin: wichtig für Rechte im Customer-Profil und für klare Verantwortlichkeit. `companies` - Kardinalität: n:m. - Pivot: `company_user` mit `company_id`, `user_id`, `role`. - Rollen: `member`, `responsible`, `owner`. - Bedeutung: Ein User kann mehrere Firmen haben; eine Firma kann mehreren Usern zugeordnet sein. - Relevanz im Admin: Hauptstruktur für Kundenkonto, Berechtigungen in der Firmenpflege und spätere User-Detailansicht. `contacts` - Kardinalität: n:m. - Pivot: `contact_user` mit `contact_id`, `user_id`. - Zusätzlich gehört jeder Kontakt zwingend zu genau einer Firma über `contacts.company_id`. - Bedeutung: Kontakte können direkt Usern zugeordnet sein, fachlich sind sie aber an Firmen aufgehängt. - Relevanz im Admin: Ansprechpartner und Zuordnungsqualität. `pressReleases` - Kardinalität: 1:n. - Tabelle: `press_releases`, Foreign Key `user_id`. - Zusätzlich: optionale Firmenzuordnung über `company_id` und Pflichtkategorie über `category_id`. - Wichtig: Datenbankseitig ist `company_id` nullable, fachlich sollte eine Customer-PM einer Firma zugeordnet sein. Der Customer-Flow erzwingt aktuell eine eigene Firma und leitet daraus das Portal ab. - Relevanz im Admin: Content-Historie, Status, Veröffentlichungen, Qualität, Freigabeprozess. `newsletterSubscriptions` - Kardinalität: 1:n. - Tabelle: `newsletter_subscriptions`, Foreign Key `user_id`. - Inhalt: Portal, Name, E-Mail, IP, Bestätigung, Subscribe/Unsubscribe, Legacy-Zuordnung. - Relevanz im Admin: Kommunikationsstatus und Opt-in/Opt-out-Historie. `billing / payments` - `userPaymentOptions`: 1:n über `user_payment_options.user_id`. - `invoices`: 1:n über `invoices.user_id`. - `legacyInvoices`: 1:n über `legacy_invoices.user_id`. - Indirekt: `user_payment_options` hat n:m zu Firmen über `user_payment_option_company`; `user_payments` hängt an `user_payment_options`; `invoices` können zusätzlich an `user_payments` und `invoice_billing_addresses` hängen. - Relevanz im Admin: Vertrags-/Abo-Status, aktive API-Freischaltung, Rechnungen, Legacy-Archiv. `tokens` - Kommt über Laravel Sanctum `HasApiTokens`. - Tabelle: `personal_access_tokens` mit polymorphem `tokenable_type` / `tokenable_id`. - Im Customer-Bereich werden Tokens für API-Zugriffe verwaltet. - Relevanz im Admin: API-Zugang, genutzte Berechtigungen, letzter Zugriff. `magicLinks` - Kardinalität: 1:n. - Tabelle: `magic_links`, Foreign Key `user_id`. - Inhalt: Token-Hash, Zweck, Payload, Ablauf, Verbrauch, IPs. - Relevanz im Admin: Auth-/Support-Kontext, Magic-Login-Historie. `filterPresets` - Kardinalität: 1:n. - Tabelle: `user_filter_presets`, Foreign Key `user_id`. - Inhalt: Seite, Name, Default-Flag, letzte Nutzung, Filter-JSON. - Relevanz im Admin: eher Arbeitskomfort/Personalisierung, nicht Kern-Kundendaten. `apiUsageLogs` - Kardinalität: faktisch 1:n über `api_usage_logs.user_id`, aber aktuell keine Relation im `User`-Model. - Inhalt: Token, Methode, Pfad, Route, Status, IP, User-Agent, Dauer, Zeitpunkt. - Relevanz im Admin: API-Aktivität, Support und Missbrauchsanalyse. `roles` und `permissions` - Kommen über Spatie `HasRoles`. - Tabellen u. a. `roles`, `permissions`, `model_has_roles`, `model_has_permissions`, `role_has_permissions`. - Relevanz im Admin: Trennung zwischen Admin, Editor, Customer, Super-Admin und feineren Berechtigungen wie `press-releases:publish` oder `users:manage`. ## Firmen, Kontakte und Pressemitteilungen Die fachliche CRM-/Content-Struktur läuft im Kern so: - User hat mehrere Firmen über `company_user`. - User kann zusätzlich direkter Eigentümer einer Firma über `companies.owner_user_id` sein. - Firma hat mehrere Kontakte über `contacts.company_id`. - Firma hat mehrere Pressemitteilungen über `press_releases.company_id`. - Pressemitteilung hat genau einen Autor/User über `press_releases.user_id`. - Pressemitteilung kann mehrere Kontakte über `press_release_contact` referenzieren. - Kontakt kann mehreren Usern direkt zugeordnet sein über `contact_user`, bleibt aber immer an genau eine Firma gebunden. Daraus ergibt sich für den User-Admin eine wichtige Sicht: - User-Zentrum: Wer ist der Account? - Firmen-Zentrum: Welche Firmen gehören dazu, in welcher Rolle? - Kontakt-Zentrum: Welche Ansprechpartner hängen an diesen Firmen und/oder am User? - Content-Zentrum: Welche Pressemitteilungen wurden vom User erstellt und welcher Firma sind sie zugeordnet? - Abrechnungs-Zentrum: Welche Zahloptionen, Rechnungen und Legacy-Rechnungen hängen am User und ggf. an Firmen? ## Pressemitteilungen im Detail Eine Pressemitteilung (`press_releases`) enthält: - Identifikation: `id`, `uuid`, `slug`, `legacy_portal`, `legacy_id`. - Zuordnung: `portal`, `language`, `user_id`, `company_id`, `category_id`. - Inhalt: `title`, `subtitle`, `text`, `backlink_url`, `keywords`, `boilerplate_override`. - Status/Publikation: `status`, `published_at`, `scheduled_at`, `embargo_at`, `no_export`, `hits`, `teaser_begin`, `teaser_end`. - Medien: Bilder über `press_release_images`, Anhänge über `press_release_attachments` (UI deaktiviert). - Kontakte: Pivot `press_release_contact`. - Verlauf: Statuslogs über `press_release_status_logs`, inklusive `changed_by_user_id`, Statuswechsel, Grund und Quelle. Service-Layer rund um PMs: - `App\Services\PressRelease\PressReleaseService` — kapselt die Übergänge `submitForReview`, `publish`, `reject`, `archive`, `deleteFromAdmin` sowie das Auflösen von `scheduled_at`/`embargo_at` für die echte Veröffentlichung. - `App\Services\PressRelease\PressReleaseHtmlSanitizer` — Wrapper um `mews/purifier` mit definierter Tag-/Attribut-Whitelist. - `App\Services\PressRelease\PressReleaseAttachmentStorage` — kapselt Datei-Operationen auf dem `public`-Disk (UI aktuell deaktiviert). - `App\Console\Commands\PublishScheduledPressReleases` — wird per Scheduler (alle 5 Min) ausgeführt und veröffentlicht freigegebene PMs zum geplanten Zeitpunkt, respektiert Embargo. Fachliche Konsequenz: - Für Kunden sollte `company_id` praktisch Pflicht sein, obwohl die DB es nullable erlaubt. - Für Admins kann `company_id = null` als Migrations-/Altfall auftauchen und sollte im User-Admin sichtbar markiert werden. - `portal` sollte mit der Firma konsistent sein. Im Customer-Flow wird das Portal aus der Firma abgeleitet; im Admin-Bereich sollte eine bewusste Korrektur möglich sein. ## User-relevante Tabellen Kernkonto: - `users` - `profiles` - `billing_addresses` - `sessions` - `password_reset_tokens` - `personal_access_tokens` - `magic_links` - Spatie Permission-Tabellen CRM: - `companies` - `company_user` - `contacts` - `contact_user` Content: - `press_releases` - `press_release_images` - `press_release_attachments` (UI temporaer deaktiviert) - `press_release_contact` - `press_release_status_logs` - `categories` - `category_translations` Billing: - `billing_addresses` - `user_payment_options` - `user_payment_option_company` - `user_payments` - `payment_options` - `payment_option_translations` - `invoices` - `invoice_billing_addresses` - `legacy_invoices` Kommunikation/API/Arbeitskomfort: - `newsletter_subscriptions` - `api_usage_logs` - `user_filter_presets` ## Relevante Inhalte für den Admin-User-Bereich Für eine optimierte Admin-User-Ansicht sollten diese Blöcke geplant werden: - Account: Status, Portal, Sprache, Registrierungstyp, Rollen, Berechtigungen, Super-Admin, letzte Aktivität, letzte IP, E-Mail-Verifikation, 2FA-Status. - Datenqualität: Profil vorhanden, Rechnungsadresse vorhanden, Firma vorhanden, Kontakte vorhanden, veröffentlichte PMs vorhanden, Legacy-Zuordnung vorhanden. - Firmen: Firmenliste mit Rolle, Eigentümerstatus, Portal, Aktivstatus, Logo, Footer-Code-Flag, Kontakt-/PM-Anzahl. - Kontakte: Kontakte je Firma plus direkte User-Kontakte aus `contact_user`. - Pressemitteilungen: Gesamtzahl, Statusverteilung, letzte PMs, veröffentlichte PMs, PMs ohne Firma, PMs mit Portalabweichung. - Abrechnung: Rechnungsadresse, aktive Zahlungsoptionen, verknüpfte Firmen zu Zahlungsoptionen, Zahlungen, Rechnungen, Legacy-Rechnungen. - API: Token-Anzahl, Berechtigungen, letzte Nutzung, API-Usage-Logs. - Support/Admin-Aktionen: User bearbeiten, Rollen/Berechtigungen ändern, Impersonation, Magic-Link-Kontext, ggf. Account deaktivieren. ## Offene Punkte für die weitere Konzeption - `User` hat keine direkte `apiUsageLogs()`-Relation, obwohl `api_usage_logs.user_id` existiert. Für Admin-Detailansichten wäre eine Relation sinnvoll. - `press_releases.company_id` ist nullable. Fachlich sollte geklärt werden, ob das nur Legacy-/Admin-Fälle erlaubt oder langfristig verboten werden soll. - `contacts` gehören immer zu einer Firma, können aber zusätzlich direkt Usern zugeordnet werden. Für die Oberfläche muss klar sein, ob "User-Kontakte" nur direkte Pivot-Kontakte meint oder alle Kontakte seiner Firmen. - Eine Firma kann über `owner_user_id` und zusätzlich über `company_user.role = owner` eine Eigentümerinformation haben. Das sollte im Admin eindeutig dargestellt und ggf. harmonisiert werden. - Billing hängt primär am User, Zahlungsoptionen können aber über Pivot auch Firmen betreffen. Für den Admin sollte klar werden, ob Rechnungen immer User-Rechnungen sind oder später firmenscharf betrachtet werden sollen. - Portal-Scope ist auf Firmen, Kontakte, Pressemitteilungen und Newsletter aktiv. Admin-Auswertungen nutzen teils `withoutGlobalScopes()`. In der User-Admin-Konzeption muss entschieden werden, wann portalübergreifend gesucht und angezeigt wird. ## Quellen im Code Modelle: - `app/Models/User.php` - `app/Models/Company.php` - `app/Models/Contact.php` - `app/Models/PressRelease.php` - `app/Models/PressReleaseImage.php` - `app/Models/PressReleaseAttachment.php` - `app/Models/PressReleaseStatusLog.php` - `app/Models/Profile.php` - `app/Models/BillingAddress.php` - `app/Models/Invoice.php` - `app/Models/LegacyInvoice.php` - `app/Models/UserPaymentOption.php` - `app/Models/UserPayment.php` - `app/Models/NewsletterSubscription.php` - `app/Models/MagicLink.php` - `app/Models/UserFilterPreset.php` - `app/Models/ApiUsageLog.php` Services / Commands: - `app/Services/PressRelease/PressReleaseService.php` - `app/Services/PressRelease/PressReleaseHtmlSanitizer.php` - `app/Services/PressRelease/PressReleaseAttachmentStorage.php` - `app/Services/Image/ImageService.php` - `app/Console/Commands/PublishScheduledPressReleases.php` - `routes/console.php` (Scheduler-Eintraege) Migrationen: - `database/migrations/*`