Allow superadmin to bypass onboarding billing
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-25 00:05:34 +01:00
parent c4ac38e41a
commit 78bd3c9267
6 changed files with 76 additions and 1 deletions

View File

@@ -8,6 +8,7 @@ use App\Filament\Resources\TenantResource\RelationManagers\PackagePurchasesRelat
use App\Filament\Resources\TenantResource\RelationManagers\TenantPackagesRelationManager;
use App\Filament\Resources\TenantResource\Schemas\TenantInfolist;
use App\Jobs\AnonymizeAccount;
use App\Models\Package;
use App\Models\Tenant;
use App\Notifications\InactiveTenantDeletionWarning;
use App\Services\Audit\SuperAdminAuditLogger;
@@ -205,11 +206,13 @@ class TenantResource extends Resource
Forms\Components\Textarea::make('reason')->label('Grund')->rows(3),
])
->action(function (Tenant $record, array $data) {
$package = Package::query()->find($data['package_id']);
\App\Models\TenantPackage::create([
'tenant_id' => $record->id,
'package_id' => $data['package_id'],
'expires_at' => $data['expires_at'],
'active' => true,
'price' => $package?->price ?? 0,
'reason' => $data['reason'] ?? null,
]);
\App\Models\PackagePurchase::create([

View File

@@ -103,6 +103,11 @@ class TenantPackage extends Model
$tenantPackage->purchased_at = now();
}
if ($tenantPackage->price === null) {
$package = $tenantPackage->package ?? Package::query()->find($tenantPackage->package_id);
$tenantPackage->price = $package?->price ?? 0;
}
$package = $tenantPackage->package;
if ($package && $package->isReseller()) {

View File

@@ -14,6 +14,7 @@ describe('resolveOnboardingRedirect', () => {
isBillingPath: false,
isOnboardingDismissed: false,
isOnboardingCompleted: false,
isSuperAdmin: false,
});
expect(result).toBeNull();
});
@@ -27,6 +28,7 @@ describe('resolveOnboardingRedirect', () => {
isBillingPath: true,
isOnboardingDismissed: false,
isOnboardingCompleted: false,
isSuperAdmin: false,
});
expect(result).toBeNull();
});
@@ -40,6 +42,7 @@ describe('resolveOnboardingRedirect', () => {
isBillingPath: false,
isOnboardingDismissed: false,
isOnboardingCompleted: false,
isSuperAdmin: false,
});
expect(result).toBeNull();
});
@@ -53,6 +56,7 @@ describe('resolveOnboardingRedirect', () => {
isBillingPath: false,
isOnboardingDismissed: false,
isOnboardingCompleted: false,
isSuperAdmin: false,
});
expect(result).toBe(ADMIN_BILLING_PATH);
});
@@ -66,6 +70,7 @@ describe('resolveOnboardingRedirect', () => {
isBillingPath: false,
isOnboardingDismissed: false,
isOnboardingCompleted: false,
isSuperAdmin: false,
});
expect(result).toBeNull();
});
@@ -79,6 +84,7 @@ describe('resolveOnboardingRedirect', () => {
isBillingPath: false,
isOnboardingDismissed: false,
isOnboardingCompleted: false,
isSuperAdmin: false,
});
expect(result).toBeNull();
});
@@ -92,6 +98,7 @@ describe('resolveOnboardingRedirect', () => {
isBillingPath: false,
isOnboardingDismissed: false,
isOnboardingCompleted: false,
isSuperAdmin: false,
});
expect(result).toBeNull();
});
@@ -105,6 +112,7 @@ describe('resolveOnboardingRedirect', () => {
isBillingPath: false,
isOnboardingDismissed: true,
isOnboardingCompleted: false,
isSuperAdmin: false,
});
expect(result).toBeNull();
});
@@ -117,6 +125,21 @@ describe('resolveOnboardingRedirect', () => {
pathname: '/event-admin/mobile/dashboard',
isBillingPath: false,
isOnboardingCompleted: true,
isSuperAdmin: false,
});
expect(result).toBeNull();
});
it('returns null for super admins without packages', () => {
const result = resolveOnboardingRedirect({
hasEvents: false,
hasActivePackage: false,
remainingEvents: null,
pathname: '/event-admin/mobile/dashboard',
isBillingPath: false,
isOnboardingDismissed: false,
isOnboardingCompleted: false,
isSuperAdmin: true,
});
expect(result).toBeNull();
});

View File

@@ -11,6 +11,7 @@ type OnboardingRedirectInput = {
isBillingPath: boolean;
isOnboardingDismissed?: boolean;
isOnboardingCompleted?: boolean;
isSuperAdmin?: boolean;
};
export function resolveOnboardingRedirect({
@@ -21,7 +22,12 @@ export function resolveOnboardingRedirect({
isBillingPath,
isOnboardingDismissed,
isOnboardingCompleted,
isSuperAdmin,
}: OnboardingRedirectInput): string | null {
if (isSuperAdmin) {
return null;
}
if (isOnboardingDismissed || isOnboardingCompleted) {
return null;
}

View File

@@ -61,8 +61,15 @@ function RequireAuth() {
const isWelcomePath = location.pathname.startsWith(ADMIN_WELCOME_BASE_PATH);
const isBillingPath = location.pathname.startsWith(ADMIN_BILLING_PATH);
const isTenantAdmin = Boolean(user && user.role !== 'member');
const isSuperAdmin = user?.role === 'super_admin' || user?.role === 'superadmin';
const shouldCheckPackages =
status === 'authenticated' && isTenantAdmin && !eventsLoading && !hasEvents && !isWelcomePath && !isBillingPath;
status === 'authenticated'
&& isTenantAdmin
&& !isSuperAdmin
&& !eventsLoading
&& !hasEvents
&& !isWelcomePath
&& !isBillingPath;
const { data: packagesData, isLoading: packagesLoading } = useQuery({
queryKey: ['mobile', 'onboarding', 'packages-overview'],
@@ -93,6 +100,7 @@ function RequireAuth() {
isBillingPath,
isOnboardingDismissed,
isOnboardingCompleted,
isSuperAdmin,
});
if (status === 'loading') {

View File

@@ -0,0 +1,30 @@
<?php
namespace Tests\Feature;
use App\Models\Package;
use App\Models\Tenant;
use App\Models\TenantPackage;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class TenantPackageTest extends TestCase
{
use RefreshDatabase;
public function test_missing_price_defaults_to_package_price(): void
{
$tenant = Tenant::factory()->create();
$package = Package::factory()->create([
'price' => 123.45,
]);
$tenantPackage = TenantPackage::create([
'tenant_id' => $tenant->id,
'package_id' => $package->id,
'active' => true,
])->refresh();
$this->assertSame('123.45', $tenantPackage->price);
}
}