columns([ TextColumn::make('name') ->label(__('admin.common.tenant')) ->searchable() ->sortable(), TextColumn::make('slug') ->label(__('admin.common.slug')) ->searchable() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('contact_email') ->label(__('admin.tenants.fields.contact_email')) ->searchable() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('paddle_customer_id') ->label('Paddle customer') ->toggleable(isToggledHiddenByDefault: true) ->copyable() ->formatStateUsing(fn (?string $state) => $state ?: '—'), TextColumn::make('subscription_status') ->label('Subscription') ->badge() ->color(fn (?string $state) => match ($state) { 'active' => 'success', 'suspended' => 'warning', 'expired' => 'danger', 'free' => 'gray', default => 'gray', }), TextColumn::make('active_reseller_package') ->label('Active package') ->getStateUsing(fn (Tenant $record) => $record->activeResellerPackage?->package?->name ?? '—') ->badge() ->color(fn (string $state) => $state === '—' ? 'gray' : 'success') ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('paddle_subscription_id') ->label('Paddle subscription') ->toggleable(isToggledHiddenByDefault: true) ->copyable() ->getStateUsing(fn (Tenant $record) => $record->activeResellerPackage?->paddle_subscription_id) ->formatStateUsing(fn (?string $state) => $state ?: '—'), IconColumn::make('missing_paddle_subscription') ->label('Missing Paddle subscription') ->boolean() ->getStateUsing(fn (Tenant $record) => self::missingPaddleSubscription($record)), IconColumn::make('status_mismatch') ->label('Status mismatch') ->boolean() ->getStateUsing(fn (Tenant $record) => self::hasStatusMismatch($record)), TextColumn::make('paddle_customer_duplicates') ->label('Paddle duplicates') ->sortable() ->toggleable(isToggledHiddenByDefault: true) ->formatStateUsing(fn (?int $state) => $state && $state > 1 ? (string) $state : '—'), TextColumn::make('paddle_sync_status') ->label('Paddle sync') ->badge() ->color(fn (?string $state) => match ($state) { 'synced' => 'success', 'syncing' => 'warning', 'pulled' => 'info', 'dry-run' => 'gray', 'failed', 'pull-failed' => 'danger', default => 'gray', }) ->formatStateUsing(fn (?string $state) => $state ? Str::headline($state) : '—') ->getStateUsing(fn (Tenant $record) => $record->activeResellerPackage?->package?->paddle_sync_status) ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('paddle_synced_at') ->label('Paddle synced') ->badge() ->color(fn ($state) => self::syncAgeColor($state)) ->formatStateUsing(fn ($state) => $state?->diffForHumans() ?? '—') ->getStateUsing(fn (Tenant $record) => $record->activeResellerPackage?->package?->paddle_synced_at), TextColumn::make('last_paddle_transaction_at') ->label('Last Paddle tx') ->badge() ->color(fn (?Carbon $state) => self::transactionAgeColor($state)) ->getStateUsing(fn (Tenant $record) => $record->last_paddle_transaction_at ? Carbon::parse($record->last_paddle_transaction_at) : null) ->formatStateUsing(fn (?Carbon $state) => $state?->diffForHumans() ?? '—') ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('paddle_transaction_count_window') ->label('Paddle tx (30d)') ->default('0') ->sortable() ->toggleable(), TextColumn::make('paddle_transaction_total_window') ->label('Paddle total (30d)') ->default(0) ->money('EUR') ->sortable() ->toggleable(), TextColumn::make('paddle_refund_count_window') ->label('Refunds (30d)') ->badge() ->color(fn (?int $state) => $state && $state > 0 ? 'danger' : 'gray') ->default('0') ->sortable() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('paddle_refund_total_window') ->label('Refund total (30d)') ->default(0) ->money('EUR') ->sortable() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('paddle_checkout_requires_action_count') ->label('Checkout action required') ->badge() ->color(fn (?int $state) => $state && $state > 0 ? 'warning' : 'gray') ->default('0') ->sortable() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('paddle_checkout_processing_count') ->label('Checkout processing') ->badge() ->color(fn (?int $state) => $state && $state > 0 ? 'warning' : 'gray') ->default('0') ->sortable() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('paddle_checkout_expired_count') ->label('Checkout expired') ->badge() ->color(fn (?int $state) => $state && $state > 0 ? 'danger' : 'gray') ->default('0') ->sortable() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('paddle_transaction_count') ->label('Paddle tx (all)') ->default('0') ->sortable() ->toggleable(isToggledHiddenByDefault: true), TextColumn::make('paddle_transaction_total') ->label('Paddle total (all)') ->default(0) ->money('EUR') ->sortable() ->toggleable(isToggledHiddenByDefault: true), ]) ->filters([ Filter::make('missing_paddle_customer') ->label('Missing Paddle customer') ->indicator('Missing Paddle customer') ->query(fn (Builder $query) => $query->whereNull('paddle_customer_id')), Filter::make('missing_paddle_subscription') ->label('Missing Paddle subscription') ->indicator('Missing Paddle subscription') ->query(fn (Builder $query) => $query->whereHas('activeResellerPackage', fn (Builder $query) => $query ->where('active', true) ->whereNull('paddle_subscription_id'))), Filter::make('duplicate_paddle_customer') ->label('Duplicate Paddle customer') ->indicator('Duplicate Paddle customer') ->query(fn (Builder $query) => $query ->whereNotNull('paddle_customer_id') ->whereIn('paddle_customer_id', function ($subquery) { $subquery->select('paddle_customer_id') ->from('tenants') ->whereNotNull('paddle_customer_id') ->groupBy('paddle_customer_id') ->havingRaw('count(*) > 1'); })), Filter::make('status_mismatch') ->label('Status mismatch') ->indicator('Status mismatch') ->query(fn (Builder $query) => self::applyStatusMismatchFilter($query)), Filter::make('active_package') ->label('Active package') ->indicator('Active package') ->query(fn (Builder $query) => $query->whereHas('activeResellerPackage', function (Builder $query) { $query->where('active', true) ->where(function (Builder $query) { $query->whereNull('expires_at') ->orWhere('expires_at', '>=', now()); }); })), Filter::make('not_suspended_or_deleted') ->label('Not suspended/deleted') ->indicator('Not suspended/deleted') ->query(fn (Builder $query) => $query ->where('is_suspended', false) ->whereNull('pending_deletion_at') ->whereNull('anonymized_at')), Filter::make('paddle_sync_failed') ->label('Paddle sync failed') ->indicator('Paddle sync failed') ->query(fn (Builder $query) => $query->whereHas('activeResellerPackage.package', fn (Builder $query) => $query ->whereIn('paddle_sync_status', self::FAILED_SYNC_STATUSES))), Filter::make('paddle_sync_stale') ->label('Paddle sync stale') ->indicator('Paddle sync stale') ->query(fn (Builder $query) => $query->whereHas('activeResellerPackage.package', fn (Builder $query) => $query ->whereNotNull('paddle_synced_at') ->where('paddle_synced_at', '<', now()->subDays(TenantPaddleHealthResource::STALE_SYNC_DAYS)))), Filter::make('paddle_sync_missing') ->label('Missing Paddle sync timestamp') ->indicator('Missing Paddle sync timestamp') ->query(fn (Builder $query) => $query->whereHas('activeResellerPackage.package', fn (Builder $query) => $query ->whereNull('paddle_synced_at'))), Filter::make('paddle_transaction_stale') ->label('Stale Paddle transactions') ->indicator('Stale Paddle transactions') ->query(function (Builder $query): Builder { $cutoff = now()->subDays(TenantPaddleHealthResource::TRANSACTION_WINDOW_DAYS); return $query ->whereHas('purchases', fn (Builder $query) => $query->where('provider', 'paddle')) ->whereDoesntHave('purchases', fn (Builder $query) => $query ->where('provider', 'paddle') ->where('purchased_at', '>=', $cutoff)); }), Filter::make('checkout_attention') ->label('Checkout attention') ->indicator('Checkout attention') ->query(fn (Builder $query) => $query->whereHas('checkoutSessions', function (Builder $query) { $query->where('provider', 'paddle') ->where(function (Builder $query) { $query->whereIn('status', [ CheckoutSession::STATUS_REQUIRES_CUSTOMER_ACTION, CheckoutSession::STATUS_PROCESSING, ]) ->orWhere(function (Builder $query) { $query->whereNotIn('status', [ CheckoutSession::STATUS_COMPLETED, CheckoutSession::STATUS_CANCELLED, ]) ->whereNotNull('expires_at') ->where('expires_at', '<', now()); }); }); })), Filter::make('refund_spike') ->label('Refund spike (30d)') ->form([ TextInput::make('min_refunds') ->label('Minimum refunds') ->numeric() ->default(1) ->minValue(1), ]) ->indicateUsing(function (array $data): ?string { $min = (int) ($data['min_refunds'] ?? 0); return $min > 0 ? "Refunds >= {$min} (30d)" : null; }) ->query(function (Builder $query, array $data): Builder { $min = (int) ($data['min_refunds'] ?? 0); if ($min < 1) { return $query; } $cutoff = now()->subDays(TenantPaddleHealthResource::TRANSACTION_WINDOW_DAYS); return $query->whereHas('purchases', fn (Builder $query) => $query ->where('provider', 'paddle') ->where('refunded', true) ->where('purchased_at', '>=', $cutoff), '>=', $min); }), SelectFilter::make('subscription_status') ->label('Subscription') ->options([ 'active' => 'Active', 'suspended' => 'Suspended', 'expired' => 'Expired', 'free' => 'Free', ]), ]) ->actions([]); } private static function hasStatusMismatch(Tenant $record): bool { $hasActivePackage = (bool) ($record->has_active_reseller_package ?? $record->activeResellerPackage); $status = (string) ($record->subscription_status ?? ''); $expiresAt = $record->subscription_expires_at; if ($status === 'active' && ! $hasActivePackage) { return true; } if ($status !== 'active' && $hasActivePackage) { return true; } if ($status === 'active' && $expiresAt && $expiresAt->isPast()) { return true; } return false; } private static function missingPaddleSubscription(Tenant $record): bool { $package = $record->activeResellerPackage; return $package && $package->active && ! $package->paddle_subscription_id; } private static function applyStatusMismatchFilter(Builder $query): Builder { return $query->where(function (Builder $query) { $query->where(function (Builder $query) { $query->where('subscription_status', 'active') ->whereDoesntHave('activeResellerPackage'); })->orWhere(function (Builder $query) { $query->where('subscription_status', '!=', 'active') ->whereHas('activeResellerPackage'); })->orWhere(function (Builder $query) { $query->where('subscription_status', 'active') ->whereNotNull('subscription_expires_at') ->where('subscription_expires_at', '<', now()); }); }); } private static function syncAgeColor($state): string { if (! $state) { return 'gray'; } if ($state->lt(now()->subDays(TenantPaddleHealthResource::STALE_SYNC_DAYS))) { return 'danger'; } return 'success'; } private static function transactionAgeColor(?Carbon $state): string { if (! $state) { return 'gray'; } if ($state->lt(now()->subDays(TenantPaddleHealthResource::TRANSACTION_WINDOW_DAYS))) { return 'danger'; } return 'success'; } }