Add tenant lifecycle view and limit controls
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-01-01 19:36:51 +01:00
parent 117250879b
commit da06db2d3b
22 changed files with 1312 additions and 148 deletions

View File

@@ -5,9 +5,12 @@ namespace App\Services\Packages;
use App\Models\Event;
use App\Models\EventPackage;
use App\Models\Tenant;
use App\Services\Tenant\TenantUsageService;
class PackageLimitEvaluator
{
public function __construct(private readonly TenantUsageService $tenantUsageService) {}
public function assessEventCreation(Tenant $tenant): ?array
{
$hasEndcustomerPackage = $tenant->tenantPackages()
@@ -60,8 +63,12 @@ class PackageLimitEvaluator
];
}
public function assessPhotoUpload(Tenant $tenant, int $eventId, ?Event $preloadedEvent = null): ?array
{
public function assessPhotoUpload(
Tenant $tenant,
int $eventId,
?Event $preloadedEvent = null,
?int $incomingBytes = null
): ?array {
[$event, $eventPackage] = $this->resolveEventAndPackage($tenant, $eventId, $preloadedEvent);
if (! $event) {
@@ -92,11 +99,7 @@ class PackageLimitEvaluator
$maxPhotos = $eventPackage->effectivePhotoLimit();
if ($maxPhotos === null) {
return null;
}
if ($eventPackage->used_photos >= $maxPhotos) {
if ($maxPhotos !== null && $eventPackage->used_photos >= $maxPhotos) {
return [
'code' => 'photo_limit_exceeded',
'title' => 'Photo upload limit reached',
@@ -113,6 +116,51 @@ class PackageLimitEvaluator
];
}
$tenantPhotoLimit = $this->normalizeTenantLimit($tenant->max_photos_per_event);
if ($tenantPhotoLimit !== null && ($maxPhotos === null || $tenantPhotoLimit < $maxPhotos)) {
if ($eventPackage->used_photos >= $tenantPhotoLimit) {
return [
'code' => 'tenant_photo_limit_exceeded',
'title' => 'Tenant photo limit reached',
'message' => 'This tenant has reached its photo allowance for the event.',
'status' => 402,
'meta' => [
'scope' => 'photos',
'used' => (int) $eventPackage->used_photos,
'limit' => (int) $tenantPhotoLimit,
'remaining' => max(0, (int) $tenantPhotoLimit - (int) $eventPackage->used_photos),
'event_id' => $event->id,
'limit_source' => 'tenant',
],
];
}
}
$storageLimitBytes = $this->tenantUsageService->storageLimitBytes($tenant);
if ($storageLimitBytes !== null) {
$usedBytes = $this->tenantUsageService->storageUsedBytes($tenant);
$projectedBytes = $usedBytes + max(0, (int) ($incomingBytes ?? 0));
if ($projectedBytes >= $storageLimitBytes) {
return [
'code' => 'tenant_storage_limit_exceeded',
'title' => 'Tenant storage limit reached',
'message' => 'This tenant has reached its storage allowance.',
'status' => 402,
'meta' => [
'scope' => 'storage',
'used_bytes' => $usedBytes,
'limit_bytes' => $storageLimitBytes,
'remaining_bytes' => max(0, $storageLimitBytes - $usedBytes),
'event_id' => $event->id,
'limit_source' => 'tenant',
],
];
}
}
return null;
}
@@ -307,4 +355,19 @@ class PackageLimitEvaluator
'expired_notified_at' => $eventPackage->gallery_expired_notified_at?->toIso8601String(),
];
}
private function normalizeTenantLimit(?int $value): ?int
{
if ($value === null) {
return null;
}
$value = (int) $value;
if ($value <= 0) {
return null;
}
return $value;
}
}