WS-8: N+1 entschaerfen und PM-Zugriffsmuster konsolidieren
- User::currentPlan() und hasUnlimitedPressReleaseQuota() pro Request memoisiert. Beide werden je Render mehrfach abgefragt (Status-Widget auf jeder Seite, Buchungsseite, Quota-Methoden) und loesten bisher jeweils eigene subscription-/Plan-/payment-option-Queries aus. fresh() setzt den Cache zurueck. - show.blade.php: getMyPR() war NICHT memoisiert und wurde pro Request bis zu 7x mit voller Eager-Load-Query aufgerufen -> jetzt memoisiert. Firmen- Kennzahlen direkt per withCount() eager geladen statt nachtraeglichem loadCount(). - Dupliziertes Zugriffsmuster (withoutGlobalScopes + user_id/company_id) aus edit/show/index in PressRelease::scopeAccessibleBy(User) zentralisiert (Single Source of Truth fuer Kunden-PM-Zugriff), mit Tests abgesichert. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
c43ef6a5bd
commit
3da335388e
6 changed files with 139 additions and 38 deletions
|
|
@ -184,6 +184,28 @@ class PressRelease extends Model
|
|||
return $query->whereHas('boosts', fn (Builder $q) => $q->active());
|
||||
}
|
||||
|
||||
/**
|
||||
* Beschränkt auf PMs, die dem User gehören (als Autor) oder zu einer ihm
|
||||
* zugeordneten Firma. Bewusst ohne Portal-GlobalScope: ein Kunde darf
|
||||
* seine eigenen PMs portalübergreifend sehen und bearbeiten.
|
||||
*
|
||||
* Single Source of Truth für den Kunden-Zugriff auf PMs — genutzt von der
|
||||
* Übersicht, Detail-, Bearbeiten- und Nachweis-Ansicht.
|
||||
*/
|
||||
public function scopeAccessibleBy(Builder $query, User $user): Builder
|
||||
{
|
||||
return $query->withoutGlobalScopes()
|
||||
->where(function (Builder $q) use ($user): void {
|
||||
$q->where('user_id', $user->id);
|
||||
|
||||
$companyIds = $user->accessibleCompanyIds();
|
||||
|
||||
if ($companyIds !== []) {
|
||||
$q->orWhereIn('company_id', $companyIds);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Display-ready text. Returns sanitized HTML for Phase-7+ PMs and
|
||||
* <p>/<br>-wrapped legacy plain text for older imports.
|
||||
|
|
|
|||
|
|
@ -117,25 +117,45 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||
return (bool) $this->billingAddress?->isComplete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-Request-Memoisierung: Tarif und Unlimited-Flag werden pro Render
|
||||
* mehrfach abgefragt (u. a. vom immer sichtbaren Status-Widget und der
|
||||
* Buchungsseite). Beide ändern sich innerhalb eines Requests nicht über
|
||||
* In-App-Aktionen (Abo läuft über Stripe-Redirect, Bestandstarif über
|
||||
* CLI), daher ist die Memoisierung sicher; `fresh()` liefert eine neue
|
||||
* Instanz und setzt den Cache zurück.
|
||||
*/
|
||||
private bool $currentPlanResolved = false;
|
||||
|
||||
private ?Plan $cachedCurrentPlan = null;
|
||||
|
||||
private ?bool $cachedHasUnlimitedQuota = null;
|
||||
|
||||
/**
|
||||
* Der Tarif des aktiven Stripe-Abos, aufgelöst über die in `plans`
|
||||
* gepflegten Stripe-Preis-IDs. Null ohne (gültiges) Abo.
|
||||
*/
|
||||
public function currentPlan(): ?Plan
|
||||
{
|
||||
if ($this->currentPlanResolved) {
|
||||
return $this->cachedCurrentPlan;
|
||||
}
|
||||
|
||||
$this->currentPlanResolved = true;
|
||||
|
||||
$subscription = $this->subscription();
|
||||
|
||||
if (! $subscription?->valid()) {
|
||||
return null;
|
||||
return $this->cachedCurrentPlan = null;
|
||||
}
|
||||
|
||||
$priceId = $subscription->stripe_price;
|
||||
|
||||
if (! $priceId) {
|
||||
return null;
|
||||
return $this->cachedCurrentPlan = null;
|
||||
}
|
||||
|
||||
return Plan::query()
|
||||
return $this->cachedCurrentPlan = Plan::query()
|
||||
->where('stripe_price_id_monthly', $priceId)
|
||||
->orWhere('stripe_price_id_yearly', $priceId)
|
||||
->first();
|
||||
|
|
@ -160,11 +180,15 @@ class User extends Authenticatable implements MustVerifyEmail
|
|||
*/
|
||||
public function hasUnlimitedPressReleaseQuota(): bool
|
||||
{
|
||||
if (! config('billing.enforce_booking')) {
|
||||
return true;
|
||||
if ($this->cachedHasUnlimitedQuota !== null) {
|
||||
return $this->cachedHasUnlimitedQuota;
|
||||
}
|
||||
|
||||
return $this->userPaymentOptions()
|
||||
if (! config('billing.enforce_booking')) {
|
||||
return $this->cachedHasUnlimitedQuota = true;
|
||||
}
|
||||
|
||||
return $this->cachedHasUnlimitedQuota = $this->userPaymentOptions()
|
||||
->whereIn('status', [
|
||||
UserPaymentOptionStatus::Active->value,
|
||||
UserPaymentOptionStatus::Grandfathered->value,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue