补齐套餐详情页与订阅无回执治理入口测试
This commit is contained in:
@@ -162,6 +162,64 @@ class PlanController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function show(Request $request, Plan $plan): View
|
||||||
|
{
|
||||||
|
$this->ensurePlatformAdmin($request);
|
||||||
|
|
||||||
|
$plan->loadCount(['subscriptions', 'platformOrders']);
|
||||||
|
|
||||||
|
$summaryStats = [
|
||||||
|
'subscriptions_count' => (int) SiteSubscription::query()->where('plan_id', $plan->id)->count(),
|
||||||
|
'activated_subscriptions_count' => (int) SiteSubscription::query()->where('plan_id', $plan->id)->where('status', 'activated')->count(),
|
||||||
|
'expiring_7d_subscriptions_count' => (int) SiteSubscription::query()
|
||||||
|
->where('plan_id', $plan->id)
|
||||||
|
->whereNotNull('ends_at')
|
||||||
|
->whereBetween('ends_at', [now(), now()->copy()->addDays(7)])
|
||||||
|
->count(),
|
||||||
|
'platform_orders_count' => (int) PlatformOrder::query()->where('plan_id', $plan->id)->count(),
|
||||||
|
'paid_orders_count' => (int) PlatformOrder::query()->where('plan_id', $plan->id)->where('payment_status', 'paid')->count(),
|
||||||
|
'paid_amount_total' => (float) (PlatformOrder::query()->where('plan_id', $plan->id)->where('payment_status', 'paid')->sum('paid_amount') ?? 0),
|
||||||
|
'paid_no_receipt_orders_count' => (int) PlatformOrder::query()
|
||||||
|
->where('plan_id', $plan->id)
|
||||||
|
->where('payment_status', 'paid')
|
||||||
|
->whereRaw("JSON_EXTRACT(meta, '$.payment_summary.total_amount') IS NULL")
|
||||||
|
->whereRaw("JSON_EXTRACT(meta, '$.payment_receipts[0].amount') IS NULL")
|
||||||
|
->count(),
|
||||||
|
'sync_failed_orders_count' => (int) PlatformOrder::query()
|
||||||
|
->where('plan_id', $plan->id)
|
||||||
|
->whereRaw("JSON_EXTRACT(meta, '$.subscription_activation_error.message') IS NOT NULL")
|
||||||
|
->count(),
|
||||||
|
'renewal_missing_subscription_orders_count' => (int) PlatformOrder::query()
|
||||||
|
->where('plan_id', $plan->id)
|
||||||
|
->where('order_type', 'renewal')
|
||||||
|
->whereNull('site_subscription_id')
|
||||||
|
->count(),
|
||||||
|
];
|
||||||
|
|
||||||
|
$recentOrders = PlatformOrder::query()
|
||||||
|
->with(['merchant', 'siteSubscription'])
|
||||||
|
->where('plan_id', $plan->id)
|
||||||
|
->orderByDesc('id')
|
||||||
|
->limit(10)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$recentSubscriptions = SiteSubscription::query()
|
||||||
|
->with('merchant')
|
||||||
|
->where('plan_id', $plan->id)
|
||||||
|
->orderByDesc('id')
|
||||||
|
->limit(10)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return view('admin.plans.show', [
|
||||||
|
'plan' => $plan,
|
||||||
|
'summaryStats' => $summaryStats,
|
||||||
|
'recentOrders' => $recentOrders,
|
||||||
|
'recentSubscriptions' => $recentSubscriptions,
|
||||||
|
'statusLabels' => $this->statusLabels(),
|
||||||
|
'billingCycleLabels' => $this->billingCycleLabels(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function store(Request $request): RedirectResponse
|
public function store(Request $request): RedirectResponse
|
||||||
{
|
{
|
||||||
$this->ensurePlatformAdmin($request);
|
$this->ensurePlatformAdmin($request);
|
||||||
|
|||||||
@@ -257,6 +257,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@php
|
@php
|
||||||
|
$showPlanUrl = \App\Support\BackUrl::withBack('/admin/plans/' . $plan->id, $selfWithoutBack);
|
||||||
$editPlanUrl = \App\Support\BackUrl::withBack('/admin/plans/' . $plan->id . '/edit', $selfWithoutBack);
|
$editPlanUrl = \App\Support\BackUrl::withBack('/admin/plans/' . $plan->id . '/edit', $selfWithoutBack);
|
||||||
$createOrderUrl = \App\Support\BackUrl::withBack('/admin/platform-orders/create?' . \Illuminate\Support\Arr::query([
|
$createOrderUrl = \App\Support\BackUrl::withBack('/admin/platform-orders/create?' . \Illuminate\Support\Arr::query([
|
||||||
'plan_id' => $plan->id,
|
'plan_id' => $plan->id,
|
||||||
@@ -275,6 +276,7 @@
|
|||||||
]);
|
]);
|
||||||
@endphp
|
@endphp
|
||||||
<div class="actions gap-10">
|
<div class="actions gap-10">
|
||||||
|
<a href="{!! $showPlanUrl !!}" class="btn btn-secondary btn-sm">查看详情</a>
|
||||||
<a href="{!! $editPlanUrl !!}" class="btn btn-secondary btn-sm">编辑</a>
|
<a href="{!! $editPlanUrl !!}" class="btn btn-secondary btn-sm">编辑</a>
|
||||||
@if((string) ($plan->status ?? '') === 'active')
|
@if((string) ($plan->status ?? '') === 'active')
|
||||||
<a href="{!! $createOrderUrl !!}" class="btn btn-sm">创建订单</a>
|
<a href="{!! $createOrderUrl !!}" class="btn btn-sm">创建订单</a>
|
||||||
|
|||||||
215
resources/views/admin/plans/show.blade.php
Normal file
215
resources/views/admin/plans/show.blade.php
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
@extends('admin.layouts.app')
|
||||||
|
|
||||||
|
@section('title', '套餐详情')
|
||||||
|
@section('page_title', '套餐详情')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
@php
|
||||||
|
$planShowSelf = \App\Support\BackUrl::selfWithoutBack();
|
||||||
|
|
||||||
|
$incomingBack = (string) request()->query('back', '');
|
||||||
|
$safeBackForLinks = \App\Support\BackUrl::sanitizeForLinks($incomingBack);
|
||||||
|
|
||||||
|
$makeSubscriptionUrl = function (array $query) use ($planShowSelf) {
|
||||||
|
return \App\Support\BackUrl::withBack('/admin/site-subscriptions?' . \Illuminate\Support\Arr::query($query), $planShowSelf);
|
||||||
|
};
|
||||||
|
|
||||||
|
$makePlatformOrderUrl = function (array $query) use ($planShowSelf) {
|
||||||
|
return \App\Support\BackUrl::withBack('/admin/platform-orders?' . \Illuminate\Support\Arr::query($query), $planShowSelf);
|
||||||
|
};
|
||||||
|
|
||||||
|
$editPlanUrl = \App\Support\BackUrl::withBack('/admin/plans/' . $plan->id . '/edit', $planShowSelf);
|
||||||
|
$createOrderUrl = \App\Support\BackUrl::withBack('/admin/platform-orders/create?' . \Illuminate\Support\Arr::query([
|
||||||
|
'plan_id' => $plan->id,
|
||||||
|
'order_type' => 'new_purchase',
|
||||||
|
]), $planShowSelf);
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="page-header mb-20" data-page="admin.plans.show">
|
||||||
|
<div class="page-header-main">
|
||||||
|
<div>
|
||||||
|
<div class="page-header-title">套餐详情</div>
|
||||||
|
<div class="page-header-subtitle">这里用于总台运营查看套餐主数据、关联订阅、关联平台订单,以及当前需要优先治理的收费链路问题。</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-header-actions">
|
||||||
|
@if($safeBackForLinks !== '')
|
||||||
|
<a href="{!! $safeBackForLinks !!}" class="btn btn-secondary btn-sm">返回上一页(保留上下文)</a>
|
||||||
|
@else
|
||||||
|
<a href="/admin/plans" class="btn btn-secondary btn-sm">返回套餐列表</a>
|
||||||
|
@endif
|
||||||
|
<a href="{!! $editPlanUrl !!}" class="btn btn-secondary btn-sm">编辑套餐</a>
|
||||||
|
@if((string) ($plan->status ?? '') === 'active')
|
||||||
|
<a href="{!! $createOrderUrl !!}" class="btn btn-sm">创建订单</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-header-meta">
|
||||||
|
<div>套餐名称:<strong>{{ $plan->name }}</strong></div>
|
||||||
|
<div>编码:{{ $plan->code }}</div>
|
||||||
|
<div>状态:{{ $statusLabels[$plan->status] ?? $plan->status }}({{ $plan->status }})</div>
|
||||||
|
<div>计费周期:{{ $billingCycleLabels[$plan->billing_cycle] ?? $plan->billing_cycle }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid-4 mb-20">
|
||||||
|
<div class="card">
|
||||||
|
<h3>关联订阅总量</h3>
|
||||||
|
<div class="num-md">
|
||||||
|
<a class="link" href="{!! $makeSubscriptionUrl(['plan_id' => $plan->id]) !!}">{{ $summaryStats['subscriptions_count'] ?? 0 }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>已生效订阅</h3>
|
||||||
|
<div class="num-md">
|
||||||
|
<a class="link" href="{!! $makeSubscriptionUrl(['plan_id' => $plan->id, 'status' => 'activated']) !!}">{{ $summaryStats['activated_subscriptions_count'] ?? 0 }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>7天内到期订阅</h3>
|
||||||
|
<div class="num-md">
|
||||||
|
<a class="link" href="{!! $makeSubscriptionUrl(['plan_id' => $plan->id, 'expiry' => 'expiring_7d']) !!}">{{ $summaryStats['expiring_7d_subscriptions_count'] ?? 0 }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>关联平台订单</h3>
|
||||||
|
<div class="num-md">
|
||||||
|
<a class="link" href="{!! $makePlatformOrderUrl(['plan_id' => $plan->id]) !!}">{{ $summaryStats['platform_orders_count'] ?? 0 }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>已支付订单</h3>
|
||||||
|
<div class="num-md">
|
||||||
|
<a class="link" href="{!! $makePlatformOrderUrl(['plan_id' => $plan->id, 'payment_status' => 'paid']) !!}">{{ $summaryStats['paid_orders_count'] ?? 0 }}</a>
|
||||||
|
</div>
|
||||||
|
<div class="muted muted-xs mt-6">已付总额:¥{{ number_format((float) ($summaryStats['paid_amount_total'] ?? 0), 2) }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>已付无回执</h3>
|
||||||
|
<div class="num-md">
|
||||||
|
<a class="link" href="{!! $makePlatformOrderUrl(['plan_id' => $plan->id, 'payment_status' => 'paid', 'receipt_status' => 'none']) !!}">{{ $summaryStats['paid_no_receipt_orders_count'] ?? 0 }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>同步失败订单</h3>
|
||||||
|
<div class="num-md">
|
||||||
|
<a class="link" href="{!! $makePlatformOrderUrl(['plan_id' => $plan->id, 'sync_status' => 'failed']) !!}">{{ $summaryStats['sync_failed_orders_count'] ?? 0 }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>续费缺订阅</h3>
|
||||||
|
<div class="num-md">
|
||||||
|
<a class="link" href="{!! $makePlatformOrderUrl(['plan_id' => $plan->id, 'renewal_missing_subscription' => '1']) !!}">{{ $summaryStats['renewal_missing_subscription_orders_count'] ?? 0 }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-20">
|
||||||
|
<h3>套餐信息</h3>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr><th class="w-160">ID</th><td>{{ $plan->id }}</td></tr>
|
||||||
|
<tr><th>套餐名称</th><td>{{ $plan->name }}</td></tr>
|
||||||
|
<tr><th>编码</th><td>{{ $plan->code }}</td></tr>
|
||||||
|
<tr><th>状态</th><td>{{ $statusLabels[$plan->status] ?? $plan->status }} <span class="muted">({{ $plan->status }})</span></td></tr>
|
||||||
|
<tr><th>计费周期</th><td>{{ $billingCycleLabels[$plan->billing_cycle] ?? $plan->billing_cycle }}</td></tr>
|
||||||
|
<tr><th>售价 / 划线价</th><td>¥{{ number_format((float) $plan->price, 2) }} / ¥{{ number_format((float) $plan->list_price, 2) }}</td></tr>
|
||||||
|
<tr><th>发布时间</th><td>{{ optional($plan->published_at)->format('Y-m-d H:i:s') ?: '-' }}</td></tr>
|
||||||
|
<tr><th>描述</th><td>{{ $plan->description ?: '暂无说明' }}</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-20">
|
||||||
|
<h3>治理入口</h3>
|
||||||
|
<div class="actions gap-10">
|
||||||
|
<a class="btn btn-secondary btn-sm" href="{!! $makeSubscriptionUrl(['plan_id' => $plan->id]) !!}">查看关联订阅</a>
|
||||||
|
<a class="btn btn-secondary btn-sm" href="{!! $makeSubscriptionUrl(['plan_id' => $plan->id, 'expiry' => 'expiring_7d']) !!}">查看 7 天内到期订阅</a>
|
||||||
|
<a class="btn btn-secondary btn-sm" href="{!! $makePlatformOrderUrl(['plan_id' => $plan->id, 'payment_status' => 'paid', 'receipt_status' => 'none']) !!}">查看已付无回执订单</a>
|
||||||
|
<a class="btn btn-secondary btn-sm" href="{!! $makePlatformOrderUrl(['plan_id' => $plan->id, 'renewal_missing_subscription' => '1']) !!}">查看续费缺订阅订单</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card mb-20 list-card">
|
||||||
|
<div class="list-card-header">
|
||||||
|
<div>
|
||||||
|
<h3 class="list-card-title">最近平台订单</h3>
|
||||||
|
<p class="muted muted-xs list-card-subtitle">展示最近 10 条,便于从套餐维度快速下钻订单治理。</p>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-secondary btn-sm" href="{!! $makePlatformOrderUrl(['plan_id' => $plan->id]) !!}">查看全部订单</a>
|
||||||
|
</div>
|
||||||
|
<div class="list-card-body">
|
||||||
|
<table class="list-card-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>订单号</th>
|
||||||
|
<th>站点</th>
|
||||||
|
<th>订单类型</th>
|
||||||
|
<th>订单状态</th>
|
||||||
|
<th>支付状态</th>
|
||||||
|
<th>应付 / 已付</th>
|
||||||
|
<th>关联订阅</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse($recentOrders as $order)
|
||||||
|
<tr>
|
||||||
|
<td>{{ $order->order_no }}</td>
|
||||||
|
<td>{{ $order->merchant?->name ?? '未关联站点' }}</td>
|
||||||
|
<td>{{ $order->orderTypeLabel() }}</td>
|
||||||
|
<td>{{ $order->status }}</td>
|
||||||
|
<td>{{ $order->payment_status }}</td>
|
||||||
|
<td>¥{{ number_format((float) $order->payable_amount, 2) }} / ¥{{ number_format((float) $order->paid_amount, 2) }}</td>
|
||||||
|
<td>{{ $order->siteSubscription?->subscription_no ?? '-' }}</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="muted table-empty">暂无平台订单</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card list-card">
|
||||||
|
<div class="list-card-header">
|
||||||
|
<div>
|
||||||
|
<h3 class="list-card-title">最近订阅</h3>
|
||||||
|
<p class="muted muted-xs list-card-subtitle">展示最近 10 条,便于从套餐维度快速下钻订阅治理。</p>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-secondary btn-sm" href="{!! $makeSubscriptionUrl(['plan_id' => $plan->id]) !!}">查看全部订阅</a>
|
||||||
|
</div>
|
||||||
|
<div class="list-card-body">
|
||||||
|
<table class="list-card-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>订阅号</th>
|
||||||
|
<th>站点</th>
|
||||||
|
<th>状态</th>
|
||||||
|
<th>金额</th>
|
||||||
|
<th>开始时间</th>
|
||||||
|
<th>到期时间</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse($recentSubscriptions as $subscription)
|
||||||
|
<tr>
|
||||||
|
<td>{{ $subscription->subscription_no }}</td>
|
||||||
|
<td>{{ $subscription->merchant?->name ?? '未关联站点' }}</td>
|
||||||
|
<td>{{ $subscription->status }}</td>
|
||||||
|
<td>¥{{ number_format((float) $subscription->amount, 2) }}</td>
|
||||||
|
<td>{{ optional($subscription->starts_at)->format('Y-m-d H:i:s') ?: '-' }}</td>
|
||||||
|
<td>{{ optional($subscription->ends_at)->format('Y-m-d H:i:s') ?: '-' }}</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" class="muted table-empty">暂无订阅记录</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@@ -260,6 +260,10 @@
|
|||||||
/ ¥{{ number_format((float) ($summaryStats['total_receipt_amount'] ?? 0), 2) }}
|
/ ¥{{ number_format((float) ($summaryStats['total_receipt_amount'] ?? 0), 2) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="muted muted-xs">点击订单数可跳转:该订阅下「有回执」订单</div>
|
<div class="muted muted-xs">点击订单数可跳转:该订阅下「有回执」订单</div>
|
||||||
|
<div class="muted muted-xs">
|
||||||
|
无回执订单(广义):
|
||||||
|
<a class="link" href="{!! $makePlatformOrderUrl(['site_subscription_id' => $subscription->id, 'receipt_status' => 'none']) !!}">{{ $summaryStats['no_receipt_orders'] ?? 0 }}</a>
|
||||||
|
</div>
|
||||||
<div class="muted muted-xs">
|
<div class="muted muted-xs">
|
||||||
已付无回执订单:
|
已付无回执订单:
|
||||||
<a class="link" href="{!! $makePlatformOrderUrl(['site_subscription_id' => $subscription->id, 'payment_status' => 'paid', 'receipt_status' => 'none']) !!}">{{ $summaryStats['no_receipt_orders'] ?? 0 }}</a>
|
<a class="link" href="{!! $makePlatformOrderUrl(['site_subscription_id' => $subscription->id, 'payment_status' => 'paid', 'receipt_status' => 'none']) !!}">{{ $summaryStats['no_receipt_orders'] ?? 0 }}</a>
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ Route::prefix('admin')->group(function () {
|
|||||||
Route::post('/plans', [PlanController::class, 'store']);
|
Route::post('/plans', [PlanController::class, 'store']);
|
||||||
// 注意:必须放在 /plans/{plan} 之前,避免被参数路由吞掉导致 404
|
// 注意:必须放在 /plans/{plan} 之前,避免被参数路由吞掉导致 404
|
||||||
Route::post('/plans/seed-defaults', [PlanController::class, 'seedDefaults']);
|
Route::post('/plans/seed-defaults', [PlanController::class, 'seedDefaults']);
|
||||||
|
Route::get('/plans/{plan}', [PlanController::class, 'show']);
|
||||||
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']);
|
||||||
|
|||||||
182
tests/Feature/AdminPlanShowTest.php
Normal file
182
tests/Feature/AdminPlanShowTest.php
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Models\Merchant;
|
||||||
|
use App\Models\Plan;
|
||||||
|
use App\Models\PlatformOrder;
|
||||||
|
use App\Models\SiteSubscription;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class AdminPlanShowTest 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_open_plan_show_page(): void
|
||||||
|
{
|
||||||
|
$this->loginAsPlatformAdmin();
|
||||||
|
|
||||||
|
$merchant = Merchant::query()->firstOrFail();
|
||||||
|
|
||||||
|
$plan = Plan::query()->create([
|
||||||
|
'code' => 'plan_show_test',
|
||||||
|
'name' => '套餐详情测试套餐',
|
||||||
|
'billing_cycle' => 'monthly',
|
||||||
|
'price' => 99,
|
||||||
|
'list_price' => 199,
|
||||||
|
'status' => 'active',
|
||||||
|
'sort' => 10,
|
||||||
|
'description' => '用于验证套餐详情页',
|
||||||
|
'published_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$subscription = SiteSubscription::query()->create([
|
||||||
|
'merchant_id' => $merchant->id,
|
||||||
|
'plan_id' => $plan->id,
|
||||||
|
'status' => 'activated',
|
||||||
|
'source' => 'manual',
|
||||||
|
'subscription_no' => 'SUB_PLAN_SHOW_0001',
|
||||||
|
'plan_name' => $plan->name,
|
||||||
|
'billing_cycle' => $plan->billing_cycle,
|
||||||
|
'period_months' => 1,
|
||||||
|
'amount' => 99,
|
||||||
|
'starts_at' => now()->subDay(),
|
||||||
|
'ends_at' => now()->addDays(5),
|
||||||
|
'activated_at' => now()->subDay(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
PlatformOrder::query()->create([
|
||||||
|
'merchant_id' => $merchant->id,
|
||||||
|
'plan_id' => $plan->id,
|
||||||
|
'site_subscription_id' => $subscription->id,
|
||||||
|
'order_no' => 'PO_PLAN_SHOW_0001',
|
||||||
|
'order_type' => 'renewal',
|
||||||
|
'status' => 'activated',
|
||||||
|
'payment_status' => 'paid',
|
||||||
|
'plan_name' => $plan->name,
|
||||||
|
'billing_cycle' => $plan->billing_cycle,
|
||||||
|
'period_months' => 1,
|
||||||
|
'quantity' => 1,
|
||||||
|
'list_amount' => 99,
|
||||||
|
'discount_amount' => 0,
|
||||||
|
'payable_amount' => 99,
|
||||||
|
'paid_amount' => 99,
|
||||||
|
'placed_at' => now()->subHour(),
|
||||||
|
'paid_at' => now()->subMinutes(50),
|
||||||
|
'activated_at' => now()->subMinutes(40),
|
||||||
|
'meta' => [],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$res = $this->get('/admin/plans/' . $plan->id);
|
||||||
|
$res->assertOk()
|
||||||
|
->assertSee('套餐详情')
|
||||||
|
->assertSee('套餐详情测试套餐')
|
||||||
|
->assertSee('查看已付无回执订单')
|
||||||
|
->assertSee('查看续费缺订阅订单');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_guest_cannot_open_plan_show_page(): void
|
||||||
|
{
|
||||||
|
$plan = Plan::query()->create([
|
||||||
|
'code' => 'plan_show_guest_test',
|
||||||
|
'name' => '游客不可见套餐',
|
||||||
|
'billing_cycle' => 'monthly',
|
||||||
|
'price' => 10,
|
||||||
|
'list_price' => 10,
|
||||||
|
'status' => 'active',
|
||||||
|
'sort' => 10,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->get('/admin/plans/' . $plan->id)->assertRedirect('/admin/login');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_plan_show_links_to_subscriptions_and_orders_should_contain_back_to_plan_show(): void
|
||||||
|
{
|
||||||
|
$this->loginAsPlatformAdmin();
|
||||||
|
|
||||||
|
$plan = Plan::query()->create([
|
||||||
|
'code' => 'plan_show_back_test',
|
||||||
|
'name' => '套餐详情 back 测试套餐',
|
||||||
|
'billing_cycle' => 'monthly',
|
||||||
|
'price' => 88,
|
||||||
|
'list_price' => 108,
|
||||||
|
'status' => 'active',
|
||||||
|
'sort' => 10,
|
||||||
|
'published_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$res = $this->get('/admin/plans/' . $plan->id);
|
||||||
|
$res->assertOk();
|
||||||
|
|
||||||
|
$back = '/admin/plans/' . $plan->id;
|
||||||
|
|
||||||
|
$expectedSubscriptionUrl = '/admin/site-subscriptions?' . Arr::query([
|
||||||
|
'plan_id' => $plan->id,
|
||||||
|
'back' => $back,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$expectedOrderUrl = '/admin/platform-orders?' . Arr::query([
|
||||||
|
'plan_id' => $plan->id,
|
||||||
|
'back' => $back,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$expectedPaidNoReceiptUrl = '/admin/platform-orders?' . Arr::query([
|
||||||
|
'plan_id' => $plan->id,
|
||||||
|
'payment_status' => 'paid',
|
||||||
|
'receipt_status' => 'none',
|
||||||
|
'back' => $back,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$expectedRenewalMissingUrl = '/admin/platform-orders?' . Arr::query([
|
||||||
|
'plan_id' => $plan->id,
|
||||||
|
'renewal_missing_subscription' => '1',
|
||||||
|
'back' => $back,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$res->assertSee($expectedSubscriptionUrl, false);
|
||||||
|
$res->assertSee($expectedOrderUrl, false);
|
||||||
|
$res->assertSee($expectedPaidNoReceiptUrl, false);
|
||||||
|
$res->assertSee($expectedRenewalMissingUrl, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_plan_index_show_link_should_carry_back_to_index_self_without_back(): void
|
||||||
|
{
|
||||||
|
$this->loginAsPlatformAdmin();
|
||||||
|
|
||||||
|
$plan = Plan::query()->create([
|
||||||
|
'code' => 'plan_index_show_link_test',
|
||||||
|
'name' => '套餐列表详情入口测试套餐',
|
||||||
|
'billing_cycle' => 'monthly',
|
||||||
|
'price' => 18,
|
||||||
|
'list_price' => 18,
|
||||||
|
'status' => 'active',
|
||||||
|
'sort' => 10,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$res = $this->get('/admin/plans?status=active&back=' . urlencode('/admin/platform-orders'));
|
||||||
|
$res->assertOk();
|
||||||
|
|
||||||
|
$expectedBack = '/admin/plans?' . Arr::query([
|
||||||
|
'status' => 'active',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$expectedShowUrl = '/admin/plans/' . $plan->id . '?' . Arr::query([
|
||||||
|
'back' => $expectedBack,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$res->assertSee($expectedShowUrl, false);
|
||||||
|
$res->assertSee('查看详情');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Models\Merchant;
|
||||||
|
use App\Models\Plan;
|
||||||
|
use App\Models\SiteSubscription;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class AdminSiteSubscriptionShowBroadNoReceiptOrdersLinkShouldUseSubscriptionShowSelfBackTest 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_broad_no_receipt_orders_link_should_use_subscription_show_self_as_back(): void
|
||||||
|
{
|
||||||
|
$this->loginAsPlatformAdmin();
|
||||||
|
|
||||||
|
$merchant = Merchant::query()->firstOrFail();
|
||||||
|
$plan = Plan::query()->create([
|
||||||
|
'code' => 'sub_show_broad_no_receipt_self_back_plan',
|
||||||
|
'name' => '订阅详情广义无回执回跳自环测试套餐',
|
||||||
|
'billing_cycle' => 'monthly',
|
||||||
|
'price' => 66,
|
||||||
|
'list_price' => 66,
|
||||||
|
'status' => 'active',
|
||||||
|
'sort' => 10,
|
||||||
|
'published_at' => now(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$subscription = SiteSubscription::query()->create([
|
||||||
|
'merchant_id' => $merchant->id,
|
||||||
|
'plan_id' => $plan->id,
|
||||||
|
'status' => 'active',
|
||||||
|
'source' => 'manual',
|
||||||
|
'subscription_no' => 'SS_SHOW_BNR_SELF_BACK_0001',
|
||||||
|
'plan_name' => $plan->name,
|
||||||
|
'billing_cycle' => $plan->billing_cycle,
|
||||||
|
'period_months' => 1,
|
||||||
|
'amount' => 66,
|
||||||
|
'starts_at' => now()->subDay(),
|
||||||
|
'ends_at' => now()->addMonth(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$res = $this->get('/admin/site-subscriptions/' . $subscription->id . '?back=' . urlencode('/admin/platform-orders?payment_status=paid'));
|
||||||
|
$res->assertOk();
|
||||||
|
|
||||||
|
$html = (string) $res->getContent();
|
||||||
|
$matched = preg_match('/无回执订单(广义):\s*<a[^>]+href="([^"]+)"/u', $html, $m);
|
||||||
|
$this->assertSame(1, $matched, '未找到订阅详情页“无回执订单(广义)”链接');
|
||||||
|
|
||||||
|
$href = html_entity_decode($m[1] ?? '');
|
||||||
|
$expectedUrl = '/admin/platform-orders?' . Arr::query([
|
||||||
|
'site_subscription_id' => $subscription->id,
|
||||||
|
'receipt_status' => 'none',
|
||||||
|
'back' => '/admin/site-subscriptions/' . $subscription->id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertSame($expectedUrl, $href);
|
||||||
|
$this->assertStringNotContainsString('payment_status=paid', $href);
|
||||||
|
$this->assertStringNotContainsString(urlencode('/admin/platform-orders?payment_status=paid'), $href);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user