hasEventAllowance()) { return null; } $package = $tenant->getActiveResellerPackage(); if ($package) { $limit = $package->package->max_events_per_year ?? 0; return [ 'code' => 'event_limit_exceeded', 'title' => 'Event quota reached', 'message' => 'Your current package has no remaining event slots. Please upgrade or renew your subscription.', 'status' => 402, 'meta' => [ 'scope' => 'events', 'used' => (int) $package->used_events, 'limit' => $limit, 'remaining' => max(0, $limit - $package->used_events), 'tenant_package_id' => $package->id, 'package_id' => $package->package_id, ], ]; } return [ 'code' => 'event_credits_exhausted', 'title' => 'No event credits remaining', 'message' => 'You have no event credits remaining. Purchase additional credits or a package to create new events.', 'status' => 402, 'meta' => [ 'scope' => 'credits', 'balance' => (int) ($tenant->event_credits_balance ?? 0), ], ]; } public function assessPhotoUpload(Tenant $tenant, int $eventId, ?Event $preloadedEvent = null): ?array { [$event, $eventPackage] = $this->resolveEventAndPackage($tenant, $eventId, $preloadedEvent); if (! $event) { return [ 'code' => 'event_not_found', 'title' => 'Event not accessible', 'message' => 'The selected event could not be found or belongs to another tenant.', 'status' => 404, 'meta' => [ 'scope' => 'photos', 'event_id' => $eventId, ], ]; } if (! $eventPackage || ! $eventPackage->package) { return [ 'code' => 'event_package_missing', 'title' => 'Event package missing', 'message' => 'No package is attached to this event. Assign a package to enable uploads.', 'status' => 409, 'meta' => [ 'scope' => 'photos', 'event_id' => $event->id, ], ]; } $maxPhotos = $eventPackage->package->max_photos; if ($maxPhotos === null) { return null; } if ($eventPackage->used_photos >= $maxPhotos) { return [ 'code' => 'photo_limit_exceeded', 'title' => 'Photo upload limit reached', 'message' => 'This event has reached its photo allowance. Upgrade the event package to accept more uploads.', 'status' => 402, 'meta' => [ 'scope' => 'photos', 'used' => (int) $eventPackage->used_photos, 'limit' => (int) $maxPhotos, 'remaining' => 0, 'event_id' => $event->id, 'package_id' => $eventPackage->package_id, ], ]; } return null; } public function resolveEventPackageForPhotoUpload( Tenant $tenant, int $eventId, ?Event $preloadedEvent = null ): ?EventPackage { [, $eventPackage] = $this->resolveEventAndPackage($tenant, $eventId, $preloadedEvent); return $eventPackage; } /** * @return array{0: ?Event, 1: ?\App\Models\EventPackage} */ private function resolveEventAndPackage( Tenant $tenant, int $eventId, ?Event $preloadedEvent = null ): array { $event = $preloadedEvent; if (! $event) { $event = Event::with(['eventPackage.package', 'eventPackages.package']) ->find($eventId); } if (! $event || $event->tenant_id !== $tenant->id) { return [null, null]; } $eventPackage = $event->eventPackage; if (! $eventPackage && method_exists($event, 'eventPackages')) { $eventPackage = $event->eventPackages() ->with('package') ->orderByDesc('purchased_at') ->orderByDesc('created_at') ->first(); } if ($eventPackage && ! $eventPackage->relationLoaded('package')) { $eventPackage->load('package'); } return [$event, $eventPackage]; } }