Skip to content

Commit 70b52b4

Browse files
simonhampclaude
andauthored
Group bundle purchases into one row per sale in admin panel (#285)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5999bdb commit 70b52b4

2 files changed

Lines changed: 164 additions & 6 deletions

File tree

app/Filament/Resources/PluginBundleResource/RelationManagers/LicensesRelationManager.php

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
namespace App\Filament\Resources\PluginBundleResource\RelationManagers;
44

5+
use App\Models\PluginLicense;
56
use Filament\Resources\RelationManagers\RelationManager;
67
use Filament\Tables;
78
use Filament\Tables\Table;
9+
use Illuminate\Database\Eloquent\Builder;
810

911
class LicensesRelationManager extends RelationManager
1012
{
@@ -15,18 +17,48 @@ class LicensesRelationManager extends RelationManager
1517
public function table(Table $table): Table
1618
{
1719
return $table
20+
->modifyQueryUsing(function (Builder $query): void {
21+
$bundleId = $this->ownerRecord->getKey();
22+
23+
$query
24+
->select('plugin_licenses.*')
25+
->addSelect([
26+
'sale_total' => PluginLicense::query()
27+
->selectRaw('SUM(price_paid)')
28+
->whereColumn('user_id', 'plugin_licenses.user_id')
29+
->whereColumn('purchased_at', 'plugin_licenses.purchased_at')
30+
->where('plugin_bundle_id', $bundleId),
31+
'sale_plugins_count' => PluginLicense::query()
32+
->selectRaw('COUNT(*)')
33+
->whereColumn('user_id', 'plugin_licenses.user_id')
34+
->whereColumn('purchased_at', 'plugin_licenses.purchased_at')
35+
->where('plugin_bundle_id', $bundleId),
36+
])
37+
->whereIn('plugin_licenses.id', function ($sub) use ($bundleId): void {
38+
$sub->selectRaw('MIN(id)')
39+
->from('plugin_licenses as pl')
40+
->where('pl.plugin_bundle_id', $bundleId)
41+
->groupBy('pl.user_id', 'pl.purchased_at');
42+
});
43+
})
1844
->columns([
1945
Tables\Columns\TextColumn::make('user.email')
2046
->label('User')
2147
->searchable(),
2248

23-
Tables\Columns\TextColumn::make('plugin.name')
24-
->label('Plugin')
25-
->fontFamily('mono'),
49+
Tables\Columns\TextColumn::make('sale_plugins_count')
50+
->label('Plugins')
51+
->badge(),
52+
53+
Tables\Columns\TextColumn::make('sale_total')
54+
->label('Total')
55+
->formatStateUsing(fn (?int $state, PluginLicense $record): string => $record->is_grandfathered
56+
? '$0.00'
57+
: '$'.number_format(($state ?? 0) / 100, 2)),
2658

27-
Tables\Columns\TextColumn::make('price_paid')
28-
->label('Allocated Amount')
29-
->formatStateUsing(fn (int $state): string => '$'.number_format($state / 100, 2)),
59+
Tables\Columns\IconColumn::make('is_grandfathered')
60+
->label('Granted')
61+
->boolean(),
3062

3163
Tables\Columns\TextColumn::make('purchased_at')
3264
->dateTime()
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?php
2+
3+
namespace Tests\Feature\Filament;
4+
5+
use App\Filament\Resources\PluginBundleResource\RelationManagers\LicensesRelationManager;
6+
use App\Models\Plugin;
7+
use App\Models\PluginBundle;
8+
use App\Models\PluginLicense;
9+
use App\Models\User;
10+
use Illuminate\Foundation\Testing\RefreshDatabase;
11+
use Livewire\Livewire;
12+
use Tests\TestCase;
13+
14+
class BundlePurchasesRelationManagerTest extends TestCase
15+
{
16+
use RefreshDatabase;
17+
18+
private User $admin;
19+
20+
private PluginBundle $bundle;
21+
22+
protected function setUp(): void
23+
{
24+
parent::setUp();
25+
26+
$this->admin = User::factory()->create(['email' => 'admin@test.com']);
27+
config(['filament.users' => ['admin@test.com']]);
28+
29+
$this->bundle = PluginBundle::factory()->active()->create();
30+
}
31+
32+
public function test_it_groups_bundle_licenses_into_a_single_sale_row(): void
33+
{
34+
$buyer = User::factory()->create();
35+
$purchasedAt = now();
36+
37+
$plugins = Plugin::factory()->count(3)->approved()->create();
38+
$this->bundle->plugins()->attach($plugins->pluck('id'));
39+
40+
foreach ($plugins as $plugin) {
41+
PluginLicense::factory()->create([
42+
'user_id' => $buyer->id,
43+
'plugin_id' => $plugin->id,
44+
'plugin_bundle_id' => $this->bundle->id,
45+
'price_paid' => 1000,
46+
'purchased_at' => $purchasedAt,
47+
]);
48+
}
49+
50+
Livewire::actingAs($this->admin)
51+
->test(LicensesRelationManager::class, [
52+
'ownerRecord' => $this->bundle,
53+
'pageClass' => \App\Filament\Resources\PluginBundleResource\Pages\ViewPluginBundle::class,
54+
])
55+
->assertCanSeeTableRecords(
56+
PluginLicense::where('plugin_bundle_id', $this->bundle->id)
57+
->whereIn('id', function ($sub) {
58+
$sub->selectRaw('MIN(id)')
59+
->from('plugin_licenses as pl')
60+
->where('pl.plugin_bundle_id', $this->bundle->id)
61+
->groupBy('pl.user_id', 'pl.purchased_at');
62+
})
63+
->get()
64+
)
65+
->assertCountTableRecords(1);
66+
}
67+
68+
public function test_it_shows_separate_rows_for_different_sales(): void
69+
{
70+
$buyer1 = User::factory()->create();
71+
$buyer2 = User::factory()->create();
72+
$purchasedAt = now();
73+
74+
$plugins = Plugin::factory()->count(2)->approved()->create();
75+
$this->bundle->plugins()->attach($plugins->pluck('id'));
76+
77+
foreach ($plugins as $plugin) {
78+
PluginLicense::factory()->create([
79+
'user_id' => $buyer1->id,
80+
'plugin_id' => $plugin->id,
81+
'plugin_bundle_id' => $this->bundle->id,
82+
'price_paid' => 1500,
83+
'purchased_at' => $purchasedAt,
84+
]);
85+
}
86+
87+
foreach ($plugins as $plugin) {
88+
PluginLicense::factory()->create([
89+
'user_id' => $buyer2->id,
90+
'plugin_id' => $plugin->id,
91+
'plugin_bundle_id' => $this->bundle->id,
92+
'price_paid' => 1500,
93+
'purchased_at' => $purchasedAt,
94+
]);
95+
}
96+
97+
Livewire::actingAs($this->admin)
98+
->test(LicensesRelationManager::class, [
99+
'ownerRecord' => $this->bundle,
100+
'pageClass' => \App\Filament\Resources\PluginBundleResource\Pages\ViewPluginBundle::class,
101+
])
102+
->assertCountTableRecords(2);
103+
}
104+
105+
public function test_it_shows_grandfathered_status(): void
106+
{
107+
$buyer = User::factory()->create();
108+
$plugin = Plugin::factory()->approved()->create();
109+
$this->bundle->plugins()->attach($plugin);
110+
111+
PluginLicense::factory()->grandfathered()->create([
112+
'user_id' => $buyer->id,
113+
'plugin_id' => $plugin->id,
114+
'plugin_bundle_id' => $this->bundle->id,
115+
'price_paid' => 0,
116+
'purchased_at' => now(),
117+
]);
118+
119+
Livewire::actingAs($this->admin)
120+
->test(LicensesRelationManager::class, [
121+
'ownerRecord' => $this->bundle,
122+
'pageClass' => \App\Filament\Resources\PluginBundleResource\Pages\ViewPluginBundle::class,
123+
])
124+
->assertCountTableRecords(1);
125+
}
126+
}

0 commit comments

Comments
 (0)