From fa114ac0dc4ba6751716ae32e2474e4014d708ff Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Fri, 6 Feb 2026 12:01:43 +0100 Subject: [PATCH] Fix reseller package selection when older batches are exhausted --- .../Api/Tenant/EventController.php | 4 ++- .../Api/Tenant/TenantAdminTokenController.php | 4 +-- .../Api/TenantPackageController.php | 2 +- app/Models/Tenant.php | 12 ++++++-- tests/Unit/TenantModelTest.php | 30 +++++++++++++++++++ 5 files changed, 45 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/Api/Tenant/EventController.php b/app/Http/Controllers/Api/Tenant/EventController.php index 1591c22d..70c55c6a 100644 --- a/app/Http/Controllers/Api/Tenant/EventController.php +++ b/app/Http/Controllers/Api/Tenant/EventController.php @@ -247,11 +247,13 @@ class EventController extends Controller $tenant->refresh(); $event->load(['eventType', 'tenant', 'eventPackages.package', 'eventPackages.addons']); + $activeResellerPackage = $tenant->getActiveResellerPackage(); + return response()->json([ 'message' => 'Event created successfully', 'data' => new EventResource($event), 'package' => $event->eventPackage ? $event->eventPackage->package->name : 'None', - 'remaining_events' => $tenant->activeResellerPackage ? $tenant->activeResellerPackage->remaining_events : 0, + 'remaining_events' => $activeResellerPackage?->remaining_events ?? 0, ], 201); } diff --git a/app/Http/Controllers/Api/Tenant/TenantAdminTokenController.php b/app/Http/Controllers/Api/Tenant/TenantAdminTokenController.php index 8997d729..b333c206 100644 --- a/app/Http/Controllers/Api/Tenant/TenantAdminTokenController.php +++ b/app/Http/Controllers/Api/Tenant/TenantAdminTokenController.php @@ -119,8 +119,6 @@ class TenantAdminTokenController extends Controller ], 404); } - $tenant->loadMissing('activeResellerPackage'); - $user = $request->user(); $abilities = $user?->currentAccessToken()?->abilities ?? []; @@ -131,7 +129,7 @@ class TenantAdminTokenController extends Controller $fullName = trim($first.' '.$last) ?: null; } - $activePackage = $tenant->activeResellerPackage; + $activePackage = $tenant->getActiveResellerPackage(); return response()->json([ 'id' => $tenant->id, diff --git a/app/Http/Controllers/Api/TenantPackageController.php b/app/Http/Controllers/Api/TenantPackageController.php index 505f691c..e9c998f1 100644 --- a/app/Http/Controllers/Api/TenantPackageController.php +++ b/app/Http/Controllers/Api/TenantPackageController.php @@ -37,7 +37,7 @@ class TenantPackageController extends Controller $this->hydratePackageSnapshot($package, $eventPackage); }); - $activePackage = $tenant->activeResellerPackage?->load('package'); + $activePackage = $tenant->getActiveResellerPackage(); if (! ($activePackage instanceof TenantPackage)) { $activePackage = $packages->firstWhere('active', true); diff --git a/app/Models/Tenant.php b/app/Models/Tenant.php index 2cb2e8d2..cdefadd0 100644 --- a/app/Models/Tenant.php +++ b/app/Models/Tenant.php @@ -233,10 +233,18 @@ class Tenant extends Model public function getActiveResellerPackage(): ?TenantPackage { - return $this->activeResellerPackage()->with('package')->first(); + return $this->getActiveResellerPackageFor(null); } public function getActiveResellerPackageFor(?string $includedPackageSlug): ?TenantPackage + { + $candidates = $this->activeResellerPackagesQuery($includedPackageSlug)->get(); + + return $candidates->first(fn (TenantPackage $package) => $package->canCreateEvent()) + ?? $candidates->first(); + } + + private function activeResellerPackagesQuery(?string $includedPackageSlug): HasMany { $query = $this->tenantPackages() ->with('package') @@ -258,7 +266,7 @@ class Tenant extends Model }); } - return $query->first(); + return $query; } public function activeSubscription(): Attribute diff --git a/tests/Unit/TenantModelTest.php b/tests/Unit/TenantModelTest.php index c5b664ad..505131bd 100644 --- a/tests/Unit/TenantModelTest.php +++ b/tests/Unit/TenantModelTest.php @@ -181,4 +181,34 @@ class TenantModelTest extends TestCase $this->assertFalse($tenant->consumeEventAllowance()); } + + public function test_get_active_reseller_package_skips_exhausted_legacy_batches(): void + { + $tenant = Tenant::factory()->create(); + + $package = Package::factory()->reseller()->create([ + 'max_events_per_year' => 1, + ]); + + $exhaustedBatch = TenantPackage::factory()->for($tenant)->for($package)->create([ + 'used_events' => 1, + 'active' => true, + 'expires_at' => null, + 'purchased_at' => now()->subDay(), + ]); + + $availableBatch = TenantPackage::factory()->for($tenant)->for($package)->create([ + 'used_events' => 0, + 'active' => true, + 'expires_at' => null, + 'purchased_at' => now(), + ]); + + $resolved = $tenant->getActiveResellerPackage(); + + $this->assertNotNull($resolved); + $this->assertSame($availableBatch->id, $resolved?->id); + $this->assertNotSame($exhaustedBatch->id, $resolved?->id); + $this->assertTrue($tenant->hasEventAllowance()); + } }