chore: fix mrr calculation and grid import
This commit is contained in:
@@ -8,11 +8,11 @@ use App\Models\Subscription;
|
|||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\Concerns\InteractsWithActions;
|
use Filament\Actions\Concerns\InteractsWithActions;
|
||||||
use Filament\Forms\Components\DatePicker;
|
use Filament\Forms\Components\DatePicker;
|
||||||
use Filament\Forms\Components\Grid;
|
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
use Filament\Forms\Concerns\InteractsWithForms;
|
use Filament\Forms\Concerns\InteractsWithForms;
|
||||||
use Filament\Forms\Contracts\HasForms;
|
use Filament\Forms\Contracts\HasForms;
|
||||||
use Filament\Pages\Page;
|
use Filament\Pages\Page;
|
||||||
|
use Filament\Schemas\Components\Grid;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Concerns\InteractsWithTable;
|
use Filament\Tables\Concerns\InteractsWithTable;
|
||||||
use Filament\Tables\Contracts\HasTable;
|
use Filament\Tables\Contracts\HasTable;
|
||||||
@@ -129,19 +129,19 @@ class CustomerAnalytics extends Page implements HasForms, HasTable
|
|||||||
$this->getCustomerAnalyticsQuery()
|
$this->getCustomerAnalyticsQuery()
|
||||||
)
|
)
|
||||||
->columns([
|
->columns([
|
||||||
TextColumn::make('user_name')
|
TextColumn::make('user.name')
|
||||||
->label('Customer')
|
->label('Customer')
|
||||||
->searchable()
|
->searchable()
|
||||||
->sortable()
|
->sortable()
|
||||||
->weight('medium'),
|
->weight('medium'),
|
||||||
|
|
||||||
TextColumn::make('user_email')
|
TextColumn::make('user.email')
|
||||||
->label('Email')
|
->label('Email')
|
||||||
->searchable()
|
->searchable()
|
||||||
->copyable()
|
->copyable()
|
||||||
->toggleable(),
|
->toggleable(),
|
||||||
|
|
||||||
TextColumn::make('plan_name')
|
TextColumn::make('plan.name')
|
||||||
->label('Plan')
|
->label('Plan')
|
||||||
->searchable()
|
->searchable()
|
||||||
->sortable()
|
->sortable()
|
||||||
@@ -173,7 +173,7 @@ class CustomerAnalytics extends Page implements HasForms, HasTable
|
|||||||
|
|
||||||
TextColumn::make('subscription_age')
|
TextColumn::make('subscription_age')
|
||||||
->label('Age')
|
->label('Age')
|
||||||
->formatStateUsing(function ($record) {
|
->state(function ($record) {
|
||||||
$started = $record->starts_at ?? $record->created_at;
|
$started = $record->starts_at ?? $record->created_at;
|
||||||
|
|
||||||
return $started ? $started->diffForHumans() : 'Unknown';
|
return $started ? $started->diffForHumans() : 'Unknown';
|
||||||
@@ -188,19 +188,20 @@ class CustomerAnalytics extends Page implements HasForms, HasTable
|
|||||||
|
|
||||||
TextColumn::make('trial_extensions_count')
|
TextColumn::make('trial_extensions_count')
|
||||||
->label('Trial Extensions')
|
->label('Trial Extensions')
|
||||||
->formatStateUsing(fn ($record) => $record->trial_extensions_count ?? 0)
|
->state(fn ($record) => $record->trial_extensions_count ?? 0)
|
||||||
->alignCenter()
|
->alignCenter()
|
||||||
->toggleable(),
|
->toggleable(),
|
||||||
|
|
||||||
TextColumn::make('subscription_changes_count')
|
TextColumn::make('subscription_changes_count')
|
||||||
->label('Changes')
|
->label('Changes')
|
||||||
->formatStateUsing(fn ($record) => $record->subscription_changes_count ?? 0)
|
->state(fn ($record) => $record->subscription_changes_count ?? 0)
|
||||||
->alignCenter()
|
->alignCenter()
|
||||||
->toggleable(),
|
->toggleable(),
|
||||||
|
|
||||||
TextColumn::make('mrr')
|
TextColumn::make('mrr')
|
||||||
->label('MRR')
|
->label('MRR')
|
||||||
->money('USD')
|
->money('USD')
|
||||||
|
->state(fn ($record) => $record->calculateMRR())
|
||||||
->sortable()
|
->sortable()
|
||||||
->toggleable(),
|
->toggleable(),
|
||||||
])
|
])
|
||||||
@@ -226,7 +227,7 @@ class CustomerAnalytics extends Page implements HasForms, HasTable
|
|||||||
]),
|
]),
|
||||||
|
|
||||||
Filter::make('date_range')
|
Filter::make('date_range')
|
||||||
->form([
|
->schema([
|
||||||
DatePicker::make('start_date')
|
DatePicker::make('start_date')
|
||||||
->label('Start Date')
|
->label('Start Date')
|
||||||
->required(),
|
->required(),
|
||||||
@@ -316,7 +317,7 @@ class CustomerAnalytics extends Page implements HasForms, HasTable
|
|||||||
$subscription->couponUsages()->sum('discount_amount'),
|
$subscription->couponUsages()->sum('discount_amount'),
|
||||||
$subscription->trialExtensions()->count(),
|
$subscription->trialExtensions()->count(),
|
||||||
$subscription->subscriptionChanges()->count(),
|
$subscription->subscriptionChanges()->count(),
|
||||||
$subscription->plan?->monthly_price ?? 0,
|
$subscription->calculateMRR(),
|
||||||
$subscription->created_at->toDateTimeString(),
|
$subscription->created_at->toDateTimeString(),
|
||||||
$subscription->trial_ends_at?->toDateTimeString() ?? 'N/A',
|
$subscription->trial_ends_at?->toDateTimeString() ?? 'N/A',
|
||||||
$subscription->ends_at?->toDateTimeString() ?? 'N/A',
|
$subscription->ends_at?->toDateTimeString() ?? 'N/A',
|
||||||
|
|||||||
@@ -264,6 +264,34 @@ class Plan extends Model
|
|||||||
return $this->monthly_billing ? 'Monthly' : 'Yearly';
|
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
|
* Get plan metadata value
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -473,4 +473,79 @@ class Subscription extends Model
|
|||||||
|
|
||||||
return $processedCount;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,5 @@
|
|||||||
<x-filament-panels::page>
|
<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">
|
<div class="mt-6">
|
||||||
{{ $this->table }}
|
{{ $this->table }}
|
||||||
</div>
|
</div>
|
||||||
</x-filament-panels::page>
|
</x-filament-panels::page>
|
||||||
|
|||||||
Reference in New Issue
Block a user