feat(plans): 空库一键初始化默认套餐(带护栏与测试)
This commit is contained in:
@@ -228,6 +228,49 @@ class PlanController extends Controller
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function seedDefaults(Request $request): RedirectResponse
|
||||||
|
{
|
||||||
|
$this->ensurePlatformAdmin($request);
|
||||||
|
|
||||||
|
// 安全护栏:仅当当前库没有任何套餐时才允许一键初始化
|
||||||
|
$existingCount = Plan::query()->count();
|
||||||
|
if ($existingCount > 0) {
|
||||||
|
return redirect()->back()->with('error', '当前已有套餐(' . $existingCount . ' 条),为避免污染运营数据,已阻止一键初始化。');
|
||||||
|
}
|
||||||
|
|
||||||
|
$now = now();
|
||||||
|
$defaults = [
|
||||||
|
[
|
||||||
|
'code' => 'starter_monthly',
|
||||||
|
'name' => '基础版(月付)',
|
||||||
|
'billing_cycle' => 'monthly',
|
||||||
|
'price' => 99,
|
||||||
|
'list_price' => 129,
|
||||||
|
'status' => 'active',
|
||||||
|
'sort' => 10,
|
||||||
|
'description' => '适合刚开站的试运营阶段,可用于 Demo 场景。',
|
||||||
|
'published_at' => $now,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'code' => 'pro_yearly',
|
||||||
|
'name' => '专业版(年付)',
|
||||||
|
'billing_cycle' => 'yearly',
|
||||||
|
'price' => 1999,
|
||||||
|
'list_price' => 2599,
|
||||||
|
'status' => 'active',
|
||||||
|
'sort' => 20,
|
||||||
|
'description' => '面向成长型站点,后续搭配授权项配置。',
|
||||||
|
'published_at' => $now,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($defaults as $row) {
|
||||||
|
Plan::query()->create($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect('/admin/plans')->with('success', '已初始化默认套餐:' . count($defaults) . ' 条。');
|
||||||
|
}
|
||||||
|
|
||||||
protected function statusLabels(): array
|
protected function statusLabels(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
<div class="card mb-20">
|
<div class="card mb-20">
|
||||||
<h3>工具</h3>
|
<h3>工具</h3>
|
||||||
|
<div class="grid-2">
|
||||||
<form method="get" action="/admin/plans/export">
|
<form method="get" action="/admin/plans/export">
|
||||||
<input type="hidden" name="status" value="{{ $filters['status'] ?? '' }}">
|
<input type="hidden" name="status" value="{{ $filters['status'] ?? '' }}">
|
||||||
<input type="hidden" name="published" value="{{ $filters['published'] ?? '' }}">
|
<input type="hidden" name="published" value="{{ $filters['published'] ?? '' }}">
|
||||||
@@ -18,6 +19,13 @@
|
|||||||
<input type="hidden" name="keyword" value="{{ $filters['keyword'] ?? '' }}">
|
<input type="hidden" name="keyword" value="{{ $filters['keyword'] ?? '' }}">
|
||||||
<button type="submit">导出当前筛选结果(CSV)</button>
|
<button type="submit">导出当前筛选结果(CSV)</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<form method="post" action="/admin/plans/seed-defaults" onsubmit="return confirm('仅在当前没有任何套餐时才会初始化。确认执行吗?');">
|
||||||
|
@csrf
|
||||||
|
<button type="submit" class="btn">一键初始化默认套餐(空库)</button>
|
||||||
|
<div class="muted muted-xs" style="margin-top:6px;">安全护栏:当库中已存在套餐时会自动阻止,避免污染运营数据。</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card mb-20">
|
<div class="card mb-20">
|
||||||
|
|||||||
@@ -121,6 +121,8 @@ Route::prefix('admin')->group(function () {
|
|||||||
Route::get('/plans/export', [PlanController::class, 'export']);
|
Route::get('/plans/export', [PlanController::class, 'export']);
|
||||||
Route::get('/plans/create', [PlanController::class, 'create']);
|
Route::get('/plans/create', [PlanController::class, 'create']);
|
||||||
Route::post('/plans', [PlanController::class, 'store']);
|
Route::post('/plans', [PlanController::class, 'store']);
|
||||||
|
// 注意:必须放在 /plans/{plan} 之前,避免被参数路由吞掉导致 404
|
||||||
|
Route::post('/plans/seed-defaults', [PlanController::class, 'seedDefaults']);
|
||||||
Route::get('/plans/{plan}/edit', [PlanController::class, 'edit']);
|
Route::get('/plans/{plan}/edit', [PlanController::class, 'edit']);
|
||||||
Route::post('/plans/{plan}', [PlanController::class, 'update']);
|
Route::post('/plans/{plan}', [PlanController::class, 'update']);
|
||||||
Route::post('/plans/{plan}/set-status', [PlanController::class, 'setStatus']);
|
Route::post('/plans/{plan}/set-status', [PlanController::class, 'setStatus']);
|
||||||
|
|||||||
58
tests/Feature/AdminPlanSeedDefaultsTest.php
Normal file
58
tests/Feature/AdminPlanSeedDefaultsTest.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Models\Plan;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class AdminPlanSeedDefaultsTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
protected function loginAsPlatformAdmin(): void
|
||||||
|
{
|
||||||
|
$this->seed();
|
||||||
|
|
||||||
|
$this->post('/admin/login', [
|
||||||
|
'email' => 'platform.admin@demo.local',
|
||||||
|
'password' => 'Platform@123456',
|
||||||
|
])->assertRedirect('/admin');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_platform_admin_can_seed_default_plans_when_empty(): void
|
||||||
|
{
|
||||||
|
$this->loginAsPlatformAdmin();
|
||||||
|
|
||||||
|
// 清空 seed 带来的默认套餐(测试口径:模拟空库)
|
||||||
|
Plan::query()->delete();
|
||||||
|
$this->assertSame(0, Plan::query()->count());
|
||||||
|
|
||||||
|
$this->post('/admin/plans/seed-defaults')
|
||||||
|
->assertRedirect('/admin/plans');
|
||||||
|
|
||||||
|
$this->assertSame(2, Plan::query()->count());
|
||||||
|
$this->assertNotNull(Plan::query()->where('code', 'starter_monthly')->first());
|
||||||
|
$this->assertNotNull(Plan::query()->where('code', 'pro_yearly')->first());
|
||||||
|
|
||||||
|
$this->get('/admin/plans')
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('基础版(月付)')
|
||||||
|
->assertSee('专业版(年付)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_seed_default_plans_is_blocked_when_plans_exist(): void
|
||||||
|
{
|
||||||
|
$this->loginAsPlatformAdmin();
|
||||||
|
|
||||||
|
$this->assertTrue(Plan::query()->count() > 0);
|
||||||
|
|
||||||
|
$this->post('/admin/plans/seed-defaults')
|
||||||
|
->assertRedirect();
|
||||||
|
|
||||||
|
// 不应新增到 2 条以上;且仍能打开列表
|
||||||
|
$this->get('/admin/plans')
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('套餐管理');
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user