currentSeasonCount(); return "{$current} subscribers this season."; } /** Count real current-season subscribers using the model directly (safe) */ private function currentSeasonCount(): int { return ScheduledGivingDonation::query() ->whereNotNull('customer_id') ->where('total_amount', '>', 0) ->whereNull('deleted_at') ->whereHas('payments', fn ($q) => $q->whereNull('deleted_at')) ->whereHas('payments', fn ($q) => $q->whereNull('deleted_at')->where('expected_at', '>', now())) ->count(); } /** * Apply "real subscriber" filter using whereIn subqueries * instead of whereHas — avoids null model crash during tab init. */ private function applyReal(Builder $q): Builder { return $q ->whereNotNull('customer_id') ->where('total_amount', '>', 0) ->whereNull('scheduled_giving_donations.deleted_at') ->whereIn('scheduled_giving_donations.id', fn ($sub) => $sub ->select('scheduled_giving_donation_id') ->from('scheduled_giving_payments') ->whereNull('deleted_at')); } /** Real + has future payment = current season */ private function applyCurrentSeason(Builder $q): Builder { return $this->applyReal($q) ->whereIn('scheduled_giving_donations.id', fn ($sub) => $sub ->select('scheduled_giving_donation_id') ->from('scheduled_giving_payments') ->whereNull('deleted_at') ->where('expected_at', '>', now())); } /** Real + NO future payments = expired */ private function applyExpired(Builder $q): Builder { return $this->applyReal($q) ->whereNotIn('scheduled_giving_donations.id', fn ($sub) => $sub ->select('scheduled_giving_donation_id') ->from('scheduled_giving_payments') ->whereNull('deleted_at') ->where('expected_at', '>', now())); } public function getTabs(): array { $campaigns = ScheduledGivingCampaign::all(); $currentCount = $this->currentSeasonCount(); $tabs = []; $tabs['current'] = Tab::make('This Season') ->icon('heroicon-o-sun') ->badge($currentCount) ->badgeColor('success') ->modifyQueryUsing(fn (Builder $q) => $this->applyCurrentSeason($q)); foreach ($campaigns as $c) { $slug = str($c->title)->slug()->toString(); $count = ScheduledGivingDonation::query() ->whereNotNull('customer_id') ->where('total_amount', '>', 0) ->whereNull('deleted_at') ->where('scheduled_giving_campaign_id', $c->id) ->whereHas('payments', fn ($q) => $q->whereNull('deleted_at')) ->whereHas('payments', fn ($q) => $q->whereNull('deleted_at')->where('expected_at', '>', now())) ->count(); if ($count === 0) continue; $tabs[$slug] = Tab::make($c->title) ->icon('heroicon-o-calendar') ->badge($count) ->badgeColor('primary') ->modifyQueryUsing(fn (Builder $q) => $this->applyCurrentSeason($q) ->where('scheduled_giving_campaign_id', $c->id)); } // Failed (current season) $failedCount = ScheduledGivingDonation::query() ->whereNotNull('customer_id') ->where('total_amount', '>', 0) ->whereNull('deleted_at') ->whereHas('payments', fn ($q) => $q->whereNull('deleted_at')) ->whereHas('payments', fn ($q) => $q->whereNull('deleted_at')->where('expected_at', '>', now())) ->whereHas('payments', fn ($q) => $q->where('is_paid', false)->where('attempts', '>', 0)->whereNull('deleted_at')) ->count(); if ($failedCount > 0) { $tabs['failed'] = Tab::make('Failed') ->icon('heroicon-o-exclamation-triangle') ->badge($failedCount) ->badgeColor('danger') ->modifyQueryUsing(fn (Builder $q) => $this->applyCurrentSeason($q) ->whereIn('scheduled_giving_donations.id', fn ($sub) => $sub ->select('scheduled_giving_donation_id') ->from('scheduled_giving_payments') ->where('is_paid', false) ->where('attempts', '>', 0) ->whereNull('deleted_at'))); } // Past seasons $expiredCount = ScheduledGivingDonation::query() ->whereNotNull('customer_id') ->where('total_amount', '>', 0) ->whereNull('deleted_at') ->whereHas('payments', fn ($q) => $q->whereNull('deleted_at')) ->whereDoesntHave('payments', fn ($q) => $q->whereNull('deleted_at')->where('expected_at', '>', now())) ->count(); $tabs['past'] = Tab::make('Past Seasons') ->icon('heroicon-o-archive-box') ->badge($expiredCount > 0 ? $expiredCount : null) ->badgeColor('gray') ->modifyQueryUsing(fn (Builder $q) => $this->applyExpired($q)); $tabs['all'] = Tab::make('All') ->icon('heroicon-o-squares-2x2') ->modifyQueryUsing(fn (Builder $q) => $this->applyReal($q)); return $tabs; } protected function getHeaderActions(): array { return []; } public function getDefaultActiveTab(): string|int|null { return 'current'; } }