diff --git a/app/Http/Controllers/Admin/SiteSubscriptionController.php b/app/Http/Controllers/Admin/SiteSubscriptionController.php index 79a7aa9..84e0f2e 100644 --- a/app/Http/Controllers/Admin/SiteSubscriptionController.php +++ b/app/Http/Controllers/Admin/SiteSubscriptionController.php @@ -332,6 +332,30 @@ class SiteSubscriptionController extends Controller $baseQuery = $this->applyFilters(SiteSubscription::query(), $filters); + $expiryMerchantRows = []; + if ((string) ($filters['expiry'] ?? '') === 'expiring_7d') { + // 到期提醒清单:站点维度 Top10(用于运营快速触达/续费跟进) + $expiryMerchantRows = $this->applyFilters(SiteSubscription::query(), $filters) + ->leftJoin('merchants', 'site_subscriptions.merchant_id', '=', 'merchants.id') + ->whereNotNull('site_subscriptions.ends_at') + ->selectRaw('site_subscriptions.merchant_id as merchant_id, merchants.name as merchant_name, count(*) as cnt, min(site_subscriptions.ends_at) as min_ends_at') + ->groupBy('site_subscriptions.merchant_id', 'merchants.name') + ->orderByDesc('cnt') + ->orderBy('min_ends_at') + ->limit(10) + ->get() + ->map(function ($row) { + return [ + 'merchant_id' => (int) ($row->merchant_id ?? 0), + 'merchant_name' => (string) ($row->merchant_name ?? ''), + 'count' => (int) ($row->cnt ?? 0), + 'min_ends_at' => (string) ($row->min_ends_at ?? ''), + ]; + }) + ->values() + ->all(); + } + return view('admin.site_subscriptions.index', [ 'subscriptions' => $subscriptions, 'filters' => $filters, @@ -357,6 +381,7 @@ class SiteSubscriptionController extends Controller ->where('ends_at', '<', now()->addDays(7)) ->count(), ], + 'expiryMerchantRows' => $expiryMerchantRows, ]); } diff --git a/resources/views/admin/site_subscriptions/index.blade.php b/resources/views/admin/site_subscriptions/index.blade.php index 6979312..7052295 100644 --- a/resources/views/admin/site_subscriptions/index.blade.php +++ b/resources/views/admin/site_subscriptions/index.blade.php @@ -100,6 +100,43 @@

到期治理

按到期时间(ends_at)快速定位需要续费/处理的订阅集合(不改变订阅 status 字段)。
+ @if(($filters['expiry'] ?? '') === 'expiring_7d') +
+
7天内到期|站点维度提醒清单(Top10)
+
用于运营优先触达:按“到期订阅数”排序,同时展示最早到期时间(min_ends_at)。
+ + + + + + + + + + + @forelse(($expiryMerchantRows ?? []) as $row) + @php + $mid = (int) ($row['merchant_id'] ?? 0); + $mname = (string) ($row['merchant_name'] ?? ''); + $cnt = (int) ($row['count'] ?? 0); + $minEndsAt = (string) ($row['min_ends_at'] ?? ''); + $merchantUrl = $buildSelfUrl(['merchant_id' => $mid, 'page' => null]); + @endphp + + + + + + @empty + + + + @endforelse + +
站点到期订阅数最早到期
{{ $mname !== '' ? $mname : ('站点#' . $mid) }}{{ $cnt }}{{ $minEndsAt !== '' ? $minEndsAt : '-' }}
暂无数据
+
+ @endif + @php $expiredUrl = $buildQuickFilterUrl(['status' => null, 'expiry' => 'expired']); $expiring7dUrl = $buildQuickFilterUrl(['status' => null, 'expiry' => 'expiring_7d']); diff --git a/tests/Feature/AdminSiteSubscriptionIndexExpiring7dShouldRenderMerchantTop10ListTest.php b/tests/Feature/AdminSiteSubscriptionIndexExpiring7dShouldRenderMerchantTop10ListTest.php new file mode 100644 index 0000000..34897b9 --- /dev/null +++ b/tests/Feature/AdminSiteSubscriptionIndexExpiring7dShouldRenderMerchantTop10ListTest.php @@ -0,0 +1,123 @@ +seed(); + + $this->post('/admin/login', [ + 'email' => 'platform.admin@demo.local', + 'password' => 'Platform@123456', + ])->assertRedirect('/admin'); + } + + public function test_expiring_7d_view_should_render_merchant_top10_table(): void + { + $this->loginAsPlatformAdmin(); + + $m1 = Merchant::query()->firstOrFail(); + $m2 = Merchant::query()->create([ + 'name' => '到期治理站点B', + 'slug' => 'expiry-merchant-b', + 'status' => 'active', + ]); + + $plan = Plan::query()->create([ + 'code' => 'sub_expiry_merchant_top10_plan', + 'name' => '到期治理 Top10 测试套餐', + 'billing_cycle' => 'monthly', + 'price' => 1, + 'list_price' => 1, + 'status' => 'active', + 'sort' => 10, + 'published_at' => now(), + ]); + + // m1: 2 条 7 天内到期 + SiteSubscription::query()->create([ + 'merchant_id' => $m1->id, + 'plan_id' => $plan->id, + 'status' => 'activated', + 'source' => 'manual', + 'subscription_no' => 'SUB_EXP_TOP10_M1_0001', + 'plan_name' => $plan->name, + 'billing_cycle' => $plan->billing_cycle, + 'period_months' => 1, + 'amount' => 1, + 'starts_at' => now()->subDays(10), + 'ends_at' => now()->addDays(3), + 'activated_at' => now()->subDays(10), + ]); + SiteSubscription::query()->create([ + 'merchant_id' => $m1->id, + 'plan_id' => $plan->id, + 'status' => 'activated', + 'source' => 'manual', + 'subscription_no' => 'SUB_EXP_TOP10_M1_0002', + 'plan_name' => $plan->name, + 'billing_cycle' => $plan->billing_cycle, + 'period_months' => 1, + 'amount' => 1, + 'starts_at' => now()->subDays(10), + 'ends_at' => now()->addDays(6), + 'activated_at' => now()->subDays(10), + ]); + + // m2: 1 条 7 天内到期 + SiteSubscription::query()->create([ + 'merchant_id' => $m2->id, + 'plan_id' => $plan->id, + 'status' => 'activated', + 'source' => 'manual', + 'subscription_no' => 'SUB_EXP_TOP10_M2_0001', + 'plan_name' => $plan->name, + 'billing_cycle' => $plan->billing_cycle, + 'period_months' => 1, + 'amount' => 1, + 'starts_at' => now()->subDays(10), + 'ends_at' => now()->addDays(2), + 'activated_at' => now()->subDays(10), + ]); + + // 一条已过期(不应进入 expiring_7d) + SiteSubscription::query()->create([ + 'merchant_id' => $m2->id, + 'plan_id' => $plan->id, + 'status' => 'activated', + 'source' => 'manual', + 'subscription_no' => 'SUB_EXP_TOP10_EXPIRED_0001', + 'plan_name' => $plan->name, + 'billing_cycle' => $plan->billing_cycle, + 'period_months' => 1, + 'amount' => 1, + 'starts_at' => now()->subDays(40), + 'ends_at' => now()->subDays(1), + 'activated_at' => now()->subDays(40), + ]); + + $res = $this->get('/admin/site-subscriptions?expiry=expiring_7d'); + $res->assertOk(); + + $res->assertSee('站点维度提醒清单', false); + $res->assertSee('data-role="expiring-7d-merchant-top10"', false); + + // m1 的到期订阅数应该显示 2 + $res->assertSee((string) $m1->name, false); + $res->assertSee('>2<', false); + + // m2 的到期订阅数应该显示 1 + $res->assertSee((string) $m2->name, false); + $res->assertSee('>1<', false); + } +}