chore: fix mrr calculation and grid import

This commit is contained in:
idevakk
2025-11-21 12:41:54 -08:00
parent 0baacdc386
commit 38ae2770ea
4 changed files with 114 additions and 33 deletions

View File

@@ -8,11 +8,11 @@ use App\Models\Subscription;
use Filament\Actions\Action;
use Filament\Actions\Concerns\InteractsWithActions;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Grid;
use Filament\Forms\Components\Select;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Pages\Page;
use Filament\Schemas\Components\Grid;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
@@ -129,19 +129,19 @@ class CustomerAnalytics extends Page implements HasForms, HasTable
$this->getCustomerAnalyticsQuery()
)
->columns([
TextColumn::make('user_name')
TextColumn::make('user.name')
->label('Customer')
->searchable()
->sortable()
->weight('medium'),
TextColumn::make('user_email')
TextColumn::make('user.email')
->label('Email')
->searchable()
->copyable()
->toggleable(),
TextColumn::make('plan_name')
TextColumn::make('plan.name')
->label('Plan')
->searchable()
->sortable()
@@ -173,7 +173,7 @@ class CustomerAnalytics extends Page implements HasForms, HasTable
TextColumn::make('subscription_age')
->label('Age')
->formatStateUsing(function ($record) {
->state(function ($record) {
$started = $record->starts_at ?? $record->created_at;
return $started ? $started->diffForHumans() : 'Unknown';
@@ -188,19 +188,20 @@ class CustomerAnalytics extends Page implements HasForms, HasTable
TextColumn::make('trial_extensions_count')
->label('Trial Extensions')
->formatStateUsing(fn ($record) => $record->trial_extensions_count ?? 0)
->state(fn ($record) => $record->trial_extensions_count ?? 0)
->alignCenter()
->toggleable(),
TextColumn::make('subscription_changes_count')
->label('Changes')
->formatStateUsing(fn ($record) => $record->subscription_changes_count ?? 0)
->state(fn ($record) => $record->subscription_changes_count ?? 0)
->alignCenter()
->toggleable(),
TextColumn::make('mrr')
->label('MRR')
->money('USD')
->state(fn ($record) => $record->calculateMRR())
->sortable()
->toggleable(),
])
@@ -226,7 +227,7 @@ class CustomerAnalytics extends Page implements HasForms, HasTable
]),
Filter::make('date_range')
->form([
->schema([
DatePicker::make('start_date')
->label('Start Date')
->required(),
@@ -316,7 +317,7 @@ class CustomerAnalytics extends Page implements HasForms, HasTable
$subscription->couponUsages()->sum('discount_amount'),
$subscription->trialExtensions()->count(),
$subscription->subscriptionChanges()->count(),
$subscription->plan?->monthly_price ?? 0,
$subscription->calculateMRR(),
$subscription->created_at->toDateTimeString(),
$subscription->trial_ends_at?->toDateTimeString() ?? 'N/A',
$subscription->ends_at?->toDateTimeString() ?? 'N/A',

View File

@@ -264,6 +264,34 @@ class Plan extends Model
return $this->monthly_billing ? 'Monthly' : 'Yearly';
}
/**
* Calculate Monthly Recurring Revenue (MRR) for this plan
*/
public function calculateMRR(): float
{
if (! $this->price) {
return 0;
}
// Use the new billing cycle system if available
if ($this->billing_cycle_days) {
return ($this->price / $this->billing_cycle_days) * 30;
}
// Fallback to legacy system
$cycleDays = $this->monthly_billing ? 30 : 365;
return ($this->price / $cycleDays) * 30;
}
/**
* Get monthly price attribute for compatibility
*/
public function getMonthlyPriceAttribute(): float
{
return $this->calculateMRR();
}
/**
* Get plan metadata value
*/

View File

@@ -473,4 +473,79 @@ class Subscription extends Model
return $processedCount;
}
/**
* Calculate Monthly Recurring Revenue (MRR) for this subscription
*/
public function calculateMRR(): float
{
// Only active and trialing subscriptions contribute to MRR
if (! in_array($this->status, ['active', 'trialing'])) {
return 0;
}
// Check if subscription has ended
if ($this->ends_at && $this->ends_at->isPast()) {
return 0;
}
// Get the plan's MRR calculation
if ($this->plan) {
return $this->plan->calculateMRR();
}
// Fallback: try to calculate from legacy data or provider data
$price = $this->getLegacyPrice();
if ($price > 0) {
$cycleDays = $this->getLegacyBillingCycleDays();
return ($price / $cycleDays) * 30;
}
return 0;
}
/**
* Get price from legacy data or provider data
*/
private function getLegacyPrice(): float
{
// Try to get price from plan first
if ($this->plan && $this->plan->price) {
return (float) $this->plan->price;
}
// Try provider data
if ($this->provider_data && isset($this->provider_data['plan_details']['price'])) {
return (float) $this->provider_data['plan_details']['price'];
}
// Try legacy stripe_price field
if ($this->stripe_price) {
// This would need additional logic to get price from Stripe
// For now, return 0 as we can't easily get the amount
return 0;
}
return 0;
}
/**
* Get billing cycle days from legacy data
*/
private function getLegacyBillingCycleDays(): int
{
// Try to get from plan first
if ($this->plan && $this->plan->billing_cycle_days) {
return (int) $this->plan->billing_cycle_days;
}
// Try provider data
if ($this->provider_data && isset($this->provider_data['plan_details']['billing_cycle_days'])) {
return (int) $this->provider_data['plan_details']['billing_cycle_days'];
}
// Fallback to legacy monthly_billing
return $this->plan && $this->plan->monthly_billing ? 30 : 365;
}
}

View File

@@ -1,28 +1,5 @@
<x-filament-panels::page>
<form wire:submit.prevent="refresh">
<div class="space-y-6">
{{ $this->form }}
<div class="flex gap-2">
<x-filament::button
type="submit"
wire:loading.attr="disabled"
>
<x-filament::loading-indicator class="h-5 w-5" wire:loading wire:target="refresh" />
Apply Filters
</x-filament::button>
<x-filament::button
wire:click="$reset"
variant="outline"
>
Reset
</x-filament::button>
</div>
</div>
</form>
<div class="mt-6">
{{ $this->table }}
</div>
</x-filament-panels::page>
</x-filament-panels::page>