From 4c80b934aba2deb3c5eac9952fcd0139530c6d75 Mon Sep 17 00:00:00 2001 From: Kevin Adametz Date: Fri, 19 Jun 2026 11:21:22 +0000 Subject: [PATCH] Doku: Phase 3 im Entwicklungsplan abgeschlossen markiert (3.1-3.3 nachgezogen) Co-Authored-By: Claude Opus 4.8 (1M context) --- dev/optimize/entwicklungsplan.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dev/optimize/entwicklungsplan.md b/dev/optimize/entwicklungsplan.md index 2d436d50..01e2bc97 100644 --- a/dev/optimize/entwicklungsplan.md +++ b/dev/optimize/entwicklungsplan.md @@ -57,9 +57,9 @@ Keine Fachlogik-Änderung. Reduziert erreichbare Demo-Komponenten und Altlasten. ## Phase 3 — Backend-Querschnittslogik zentralisieren (Paket D + Domain) -- [ ] **3.1 `Company::scopeAccessibleBy()` / `CompanyPolicy`** — identische `owner_user_id == me OR whereHas('users', me)`-Closure in 4 Controllern + Überlappung mit `User::accessibleCompanyIds()` und `CustomerCompanyContext::accessibleCompanyQuery()`. Policy wird in `ContactAccessService:229` bereits referenziert. → konsequent über Gate/Policy. -- [ ] **3.2 API-PressRelease-Zugriff + Eager-Loads zentralisieren** — `findOwnedPressRelease` doppelt (PressRelease/Image-Controller); Relation-Liste (`company:…`, `category.translations`, `images`) mehrfach. → expliziter Scope (`ownedByApiUser`) + `withApiResourceRelations()`. Unique-Slug-Reimplementierung (`PressReleaseController:201-220`) durch bestehendes `HasUniqueSlug` ersetzen. -- [ ] **3.3 `DomainContext`-Service** — Host→Config-Auflösung 4× (`ThemeServiceProvider::boot`, `ThemeMiddleware`, `SetDomainUrl`, `SetCurrentPortal`). **Vorher prüfen, ob `SetDomainUrl` toter Code ist** (scheint nicht in `bootstrap/app.php` registriert). `CurrentPortalContext` mutable static → request-scoped Binding. +- [x] **3.1 `Company::scopeAccessibleBy()`** — `owner_user_id == me OR whereHas('users', me)` als Scope (Single Source of Truth); `CompanyController` (2×) und `CustomerCompanyContext::accessibleCompanyQuery()` nutzen ihn. Test: `CompanySearchByTermTest` (accessibleBy). +- [x] **3.2 API-PressRelease-Zugriff + Eager-Loads zentralisieren** — `PressRelease::scopeOwnedByApiUser()` (bewusst enger als `accessibleBy`: nur Autorschaft) für `findOwnedPressRelease` in PressRelease- und Image-Controller; `apiResourceRelations()` bündelt die 5× wiederholte Relation-Liste. Test: `PressReleaseAccessScopeTest`. **Bewusst nicht ersetzt:** Unique-Slug-Reimplementierung — der API-Fallback (`Str::random(8)`) divergiert vom Trait-`slugFallback()`. +- [x] **3.3 `DomainContext` / toter Code** — **Fund:** `SetDomainUrl` war nirgends registriert (toter Code) → gelöscht; die Host→Config-Auflösung läuft real nur noch zentral über `DomainAssetContext::resolve()` (`ThemeServiceProvider`/`SetCurrentPortal`), keine 4-fache Dopplung mehr. **Bewusst nicht angefasst:** `CurrentPortalContext` mutable static → request-scoped Binding (test-gekoppelt, separater Schritt). - [x] **3.4 Performance** — `LogApiUsage` ist jetzt terminable (INSERT nach gesendeter Antwort, kein Queue-Worker nötig; Startzeit über Request-Attribut). Offene Single-Purchase über `User::openSinglePurchaseCount()` per-Request memoisiert (vorher 3× Count + 1× exists pro Status-Widget-Render → jetzt 1× Count), genutzt von Quota-Total/-Remaining, `hasActiveBooking` und `CustomerStatusSummary`. `CustomerCompanyContext::selectedCompany()` nutzt eine einzige `first()`-Query statt exists()+first() (Verwerfen bei Nichterreichbarkeit bleibt). `LegacySubscriptionService::cancelBundleFor` ist ein einziges Bulk-Update (UserPaymentOption ohne Observer → sicher). - [x] **3.5 Kleinere Zentralisierungen** — `VatResolver::normalizeCountryCode()`/`isEuCountry()` als SSOT, genutzt von `resolve()`, `VatIdValidationService` und `StripeCheckoutService` (3×). `User::findByEmail()` bündelt die `LOWER(email)`-Lookups (ContactAccessService 2×, SocialAuthService 1×); bleibt bewusst case-insensitiv, weil E-Mails nicht normalisiert gespeichert werden — der indexfreundliche Direktvergleich braucht erst eine Schreib-Normalisierung (offen). Währung über `config('billing.default_currency')` ('EUR'; für Stripe-Calls `strtolower(...)`), ersetzt 8 verstreute `'EUR'`/`'eur'`-Literale und vereinheitlicht nebenbei die uneinheitliche `SinglePurchase.currency`. Neuer `CustomerAccountProvisioner::provisionVerifiedCustomer()` ist die gemeinsame Provisioning-Methode für ContactAccessService und SocialAuthService (verifiziert + aktiv + customer-Rolle). Tests: `UserFindByEmailTest`, erweiterte `VatResolverTest`. - [x] **3.6 Auth-Gates** — `Gate::define('access-admin'|'access-customer')` als SSOT (AppServiceProvider), nutzbar via `@can`/Policies. `EnsureUserIsAdmin`/`EnsureUserIsCustomer` prüfen jetzt über diese Gates (`Gate::denies(...)`). Der Impersonation-Redirect ist in die eigene Middleware `BlockAdminDuringImpersonation` ausgelagert (Sitzungs-Bedingung statt Rollenfrage), sie läuft vor der Zugriffsprüfung und ist zusätzlich als persistente Livewire-Middleware registriert. Tests: `Auth/AccessGatesTest`, bestehende `UserImpersonationTest` (Redirect bleibt grün).