From 07fb547e0e1f2afdd4d65230c78bdef468ff064b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=90=9D=E5=8D=9C?= Date: Mon, 16 Mar 2026 22:12:45 +0800 Subject: [PATCH] chore(seed): enrich plan share demo data for dashboard top5 --- database/seeders/InitialDemoSeeder.php | 163 ++++++++++++++++++ ...ouldLinkToPlatformOrdersListByPlanTest.php | 32 ++-- 2 files changed, 181 insertions(+), 14 deletions(-) diff --git a/database/seeders/InitialDemoSeeder.php b/database/seeders/InitialDemoSeeder.php index 86569bf..6fc4897 100644 --- a/database/seeders/InitialDemoSeeder.php +++ b/database/seeders/InitialDemoSeeder.php @@ -138,6 +138,40 @@ class InitialDemoSeeder extends Seeder 'description' => '面向成长型站点,后续搭配授权项配置。', 'published_at' => now()->subDays(3), ], + // 用于仪表盘“套餐订单占比(Top5)”演示:额外补一些不同档位的套餐 + [ + 'code' => 'growth_monthly', + 'name' => '成长版(月付)', + 'billing_cycle' => 'monthly', + 'price' => 199, + 'list_price' => 239, + 'status' => 'active', + 'sort' => 30, + 'description' => '用于验证套餐占比/收入分布。', + 'published_at' => now()->subDays(2), + ], + [ + 'code' => 'team_monthly', + 'name' => '团队版(月付)', + 'billing_cycle' => 'monthly', + 'price' => 399, + 'list_price' => 499, + 'status' => 'active', + 'sort' => 40, + 'description' => '用于验证 Top5 占比卡多行展示。', + 'published_at' => now()->subDays(2), + ], + [ + 'code' => 'enterprise_yearly', + 'name' => '企业版(年付)', + 'billing_cycle' => 'yearly', + 'price' => 9999, + 'list_price' => 12999, + 'status' => 'active', + 'sort' => 50, + 'description' => '用于验证高客单价档位展示。', + 'published_at' => now()->subDays(1), + ], ]; foreach ($plans as $planData) { @@ -147,6 +181,135 @@ class InitialDemoSeeder extends Seeder ); } + // 用于仪表盘:补齐「套餐订单占比(Top5)」与「站点收入排行 Top5」所需的多维演示数据 + // 原则: + // - 站点名称不含“演示”,避免命中某些测试用例的 keyword=演示 + // - plan 字段不设为 pro,避免命中某些测试用例的 plan=pro + // - 平台订单补齐回执证据 & 写入 subscription_activation.subscription_id,避免仪表盘最近订单出现“无回执/可同步”等治理提示干扰 + $planIds = [ + 'starter_monthly' => (int) Plan::query()->where('code', 'starter_monthly')->value('id'), + 'pro_yearly' => (int) Plan::query()->where('code', 'pro_yearly')->value('id'), + 'growth_monthly' => (int) Plan::query()->where('code', 'growth_monthly')->value('id'), + 'team_monthly' => (int) Plan::query()->where('code', 'team_monthly')->value('id'), + 'enterprise_yearly' => (int) Plan::query()->where('code', 'enterprise_yearly')->value('id'), + ]; + + $extraMerchants = [ + ['slug' => 'demo-site-b', 'name' => '样例站点B', 'plan' => 'starter'], + ['slug' => 'demo-site-c', 'name' => '样例站点C', 'plan' => 'starter'], + ['slug' => 'demo-site-d', 'name' => '样例站点D', 'plan' => 'starter'], + ['slug' => 'demo-site-e', 'name' => '样例站点E', 'plan' => 'starter'], + ]; + + foreach ($extraMerchants as $m) { + Merchant::query()->firstOrCreate( + ['slug' => $m['slug']], + [ + 'name' => $m['name'], + 'domain' => null, + 'contact_name' => '样例联系人', + 'contact_phone' => '13800000000', + 'contact_email' => 'demo@example.com', + 'plan' => (string) ($m['plan'] ?? 'starter'), + 'status' => 'active', + 'activated_at' => now(), + 'settings' => ['currency' => 'CNY'], + ] + ); + } + + // 套餐占比(Top5)演示:近 7 天各套餐造不同数量的订单,形成 Top5 梯度 + // 说明:占比卡按“订单数”统计,不以 paid_sum 作为分母。 + $shareSeed = [ + // starter:8 单 + ['plan_code' => 'starter_monthly', 'cnt' => 8, 'amount' => 99], + // pro:6 单 + ['plan_code' => 'pro_yearly', 'cnt' => 6, 'amount' => 1999], + // growth:4 单 + ['plan_code' => 'growth_monthly', 'cnt' => 4, 'amount' => 199], + // team:3 单 + ['plan_code' => 'team_monthly', 'cnt' => 3, 'amount' => 399], + // enterprise:2 单 + ['plan_code' => 'enterprise_yearly', 'cnt' => 2, 'amount' => 9999], + ]; + + $merchantSlugsForShare = ['demo-shop', 'demo-site-b', 'demo-site-c', 'demo-site-d', 'demo-site-e']; + $merchantsBySlug = Merchant::query()->whereIn('slug', $merchantSlugsForShare)->get()->keyBy('slug'); + + $seq = 1; + foreach ($shareSeed as $seed) { + $planCode = (string) ($seed['plan_code'] ?? ''); + $planId = (int) ($planIds[$planCode] ?? 0); + if ($planId <= 0) { + continue; + } + + $cnt = (int) ($seed['cnt'] ?? 0); + $cnt = max(0, min(50, $cnt)); + $amount = (float) ($seed['amount'] ?? 0); + + for ($i = 0; $i < $cnt; $i++) { + $slug = $merchantSlugsForShare[($seq - 1) % count($merchantSlugsForShare)]; + $mch = $merchantsBySlug->get($slug); + if (! $mch) { + $seq++; + continue; + } + + // 分散到近 7 天,避免趋势图/排行过于集中到某一天 + $daysAgo = ($seq - 1) % 7; + $createdAt = now()->subDays($daysAgo)->setTime(12, 0, 0); + + $orderNo = 'PO_DEMO_SHARE_' . strtoupper(substr($planCode, 0, 3)) . '_' . str_pad((string) $seq, 4, '0', STR_PAD_LEFT); + + PlatformOrder::query()->updateOrCreate( + ['order_no' => $orderNo], + [ + 'merchant_id' => $mch->id, + 'plan_id' => $planId, + 'site_subscription_id' => null, + 'created_by_admin_id' => $platformAdmin->id, + 'order_type' => 'new_purchase', + 'status' => 'activated', + 'payment_status' => 'paid', + 'payment_channel' => 'manual', + 'plan_name' => (string) Plan::query()->where('id', $planId)->value('name'), + 'billing_cycle' => (string) Plan::query()->where('id', $planId)->value('billing_cycle'), + 'period_months' => 1, + 'quantity' => 1, + 'list_amount' => $amount, + 'discount_amount' => 0, + 'payable_amount' => $amount, + 'paid_amount' => $amount, + 'placed_at' => $createdAt, + 'paid_at' => $createdAt, + 'activated_at' => $createdAt, + 'meta' => [ + 'payment_summary' => [ + 'total_amount' => $amount, + ], + 'payment_receipts' => [ + [ + 'amount' => $amount, + 'channel' => 'manual', + 'paid_at' => $createdAt->toDateTimeString(), + ], + ], + 'subscription_activation' => [ + 'subscription_id' => 20000 + $seq, + ], + 'note' => '初始化占比演示订单', + ], + 'remark' => '初始化占比演示订单', + 'created_at' => $createdAt, + 'updated_at' => $createdAt, + ] + ); + + $seq++; + } + } + $subscription = SiteSubscription::query()->firstOrCreate( ['subscription_no' => 'SUB202603100001'], [ diff --git a/tests/Feature/AdminDashboardPlanOrderShareTop5ShouldLinkToPlatformOrdersListByPlanTest.php b/tests/Feature/AdminDashboardPlanOrderShareTop5ShouldLinkToPlatformOrdersListByPlanTest.php index 28be7f3..9bf8ee7 100644 --- a/tests/Feature/AdminDashboardPlanOrderShareTop5ShouldLinkToPlatformOrdersListByPlanTest.php +++ b/tests/Feature/AdminDashboardPlanOrderShareTop5ShouldLinkToPlatformOrdersListByPlanTest.php @@ -41,20 +41,24 @@ class AdminDashboardPlanOrderShareTop5ShouldLinkToPlatformOrdersListByPlanTest e 'published_at' => now(), ]); - PlatformOrder::query()->create([ - 'merchant_id' => $merchant->id, - 'plan_id' => $plan->id, - 'site_subscription_id' => null, - 'created_by_admin_id' => $platformAdminId ?: null, - 'order_no' => 'PO_DASH_SHARE_LINK_0001', - 'order_type' => 'new_purchase', - 'status' => 'pending', - 'payment_status' => 'paid', - 'payable_amount' => 10, - 'paid_amount' => 10, - 'placed_at' => now(), - 'meta' => [], - ]); + // 说明:InitialDemoSeeder 会补齐较多“套餐占比”演示数据;为了确保本用例构造的套餐进入 Top5, + // 这里需要造足够多的同套餐订单(按“订单数”口径统计)。 + for ($i = 1; $i <= 30; $i++) { + PlatformOrder::query()->create([ + 'merchant_id' => $merchant->id, + 'plan_id' => $plan->id, + 'site_subscription_id' => null, + 'created_by_admin_id' => $platformAdminId ?: null, + 'order_no' => 'PO_DASH_SHARE_LINK_' . str_pad((string) $i, 4, '0', STR_PAD_LEFT), + 'order_type' => 'new_purchase', + 'status' => 'pending', + 'payment_status' => 'paid', + 'payable_amount' => 10, + 'paid_amount' => 10, + 'placed_at' => now(), + 'meta' => [], + ]); + } $res = $this->get('/admin'); $res->assertOk();