presseportale/app/Services/Billing/LegacySubscriptionService.php
Kevin Adametz 31d407d3ad Optimierung 3.4: Performance (Logging terminable, Single-Purchase memoisiert)
- LogApiUsage als terminable Middleware: der ApiUsageLog-INSERT laeuft nach
  gesendeter Antwort und verlaengert nicht mehr die Request-Dauer. Startzeit
  wird ueber ein Request-Attribut uebergeben (terminate() laeuft auf frischer
  Instanz).
- User::openSinglePurchaseCount() memoisiert die offenen Einmalkaeufe pro
  Request. Vorher wurde derselbe grantingSubmission-Count im Status-Widget
  mehrfach (Quota-Total, Quota-Remaining, openSinglePurchases) plus ein
  exists() in hasActiveBooking abgesetzt -> jetzt eine Query.
- CustomerCompanyContext::selectedCompany() nutzt eine einzige first()-Query
  statt exists()+first(); das Verwerfen nicht (mehr) erreichbarer Auswahlen
  bleibt erhalten.
- LegacySubscriptionService::cancelBundleFor() ist ein einziges Bulk-Update
  statt Update-pro-Iteration (UserPaymentOption hat keine Observer/Events).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 11:05:07 +00:00

54 lines
1.8 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Services\Billing;
use App\Enums\UserPaymentOptionStatus;
use App\Models\User;
use App\Models\UserPaymentOption;
use Illuminate\Support\Collection;
/**
* Bestandsschutz-Vereinbarungen (WS-4, M2).
*
* Legacy-Posten eines Kunden gelten als Bündel: Sie werden je Portal als eigene
* Kondition geführt (kein Tarif-Merge), sind aber NUR GEMEINSAM kündbar – es gibt
* bewusst keine Einzelkündigung. Die Abrechnung selbst bleibt pro Posten (eigene
* MAN-Rechnung, siehe ManualInvoiceService).
*/
class LegacySubscriptionService
{
/**
* Noch laufende Bestandsschutz-Posten eines Kunden (das „Bündel").
*
* @return Collection<int, UserPaymentOption>
*/
public function grandfatheredFor(User $user): Collection
{
return UserPaymentOption::query()
->where('user_id', $user->getKey())
->where('status', UserPaymentOptionStatus::Grandfathered->value)
->whereNull('cancelled_at')
->get();
}
/**
* Kündigt das GESAMTE Bestandsschutz-Bündel eines Kunden gemeinsam. Es gibt
* keine Einzelkündigung – alle laufenden Grandfathered-Posten werden in einem
* Schritt auf `cancelled` gesetzt, womit der 04:30-Worker keine weiteren
* MAN-Rechnungen mehr erzeugt (duePaymentOptions berücksichtigt nur
* Active/Grandfathered).
*
* @return int Anzahl der gekündigten Posten
*/
public function cancelBundleFor(User $user): int
{
return UserPaymentOption::query()
->where('user_id', $user->getKey())
->where('status', UserPaymentOptionStatus::Grandfathered->value)
->whereNull('cancelled_at')
->update([
'status' => UserPaymentOptionStatus::Cancelled->value,
'cancelled_at' => now(),
]);
}
}