248 lines
10 KiB
PHP
248 lines
10 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers\Admin;
|
||
|
||
use App\Http\Controllers\Concerns\ResolvesPlatformAdminContext;
|
||
use App\Http\Controllers\Controller;
|
||
use App\Models\SiteSubscription;
|
||
use Illuminate\Database\Eloquent\Builder;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\View\View;
|
||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||
use App\Models\PlatformOrder;
|
||
|
||
class SiteSubscriptionController extends Controller
|
||
{
|
||
use ResolvesPlatformAdminContext;
|
||
|
||
public function show(Request $request, SiteSubscription $subscription): View
|
||
{
|
||
$this->ensurePlatformAdmin($request);
|
||
|
||
$subscription->loadMissing(['merchant', 'plan']);
|
||
|
||
$platformOrders = PlatformOrder::query()
|
||
->where('site_subscription_id', $subscription->id)
|
||
->latest('id')
|
||
->paginate(10)
|
||
->withQueryString();
|
||
|
||
$endsAt = $subscription->ends_at;
|
||
$expiryLabel = '无到期';
|
||
if ($endsAt) {
|
||
if ($endsAt->lt(now())) {
|
||
$expiryLabel = '已过期';
|
||
} elseif ($endsAt->lt(now()->addDays(7))) {
|
||
$expiryLabel = '7天内到期';
|
||
} else {
|
||
$expiryLabel = '未到期';
|
||
}
|
||
}
|
||
|
||
return view('admin.site_subscriptions.show', [
|
||
'subscription' => $subscription,
|
||
'platformOrders' => $platformOrders,
|
||
'statusLabels' => $this->statusLabels(),
|
||
'expiryLabel' => $expiryLabel,
|
||
]);
|
||
}
|
||
|
||
public function export(Request $request): StreamedResponse
|
||
{
|
||
$this->ensurePlatformAdmin($request);
|
||
|
||
$filters = [
|
||
'status' => trim((string) $request->query('status', '')),
|
||
'keyword' => trim((string) $request->query('keyword', '')),
|
||
'merchant_id' => trim((string) $request->query('merchant_id', '')),
|
||
'plan_id' => trim((string) $request->query('plan_id', '')),
|
||
'expiry' => trim((string) $request->query('expiry', '')),
|
||
];
|
||
|
||
$query = $this->applyFilters(
|
||
SiteSubscription::query()->with(['merchant', 'plan'])->withCount('platformOrders'),
|
||
$filters
|
||
)->orderBy('id');
|
||
|
||
$filename = 'site_subscriptions_' . now()->format('Ymd_His') . '.csv';
|
||
|
||
return response()->streamDownload(function () use ($query) {
|
||
$out = fopen('php://output', 'w');
|
||
|
||
// UTF-8 BOM,避免 Excel 打开中文乱码
|
||
fwrite($out, "\xEF\xBB\xBF");
|
||
|
||
fputcsv($out, [
|
||
'ID',
|
||
'订阅号',
|
||
'站点',
|
||
'套餐',
|
||
'关联订单数',
|
||
'状态',
|
||
'计费周期',
|
||
'周期(月)',
|
||
'金额',
|
||
'开始时间',
|
||
'到期时间',
|
||
'到期状态',
|
||
'试用到期',
|
||
'生效时间',
|
||
'取消时间',
|
||
]);
|
||
|
||
$statusLabels = $this->statusLabels();
|
||
|
||
$query->chunkById(500, function ($subs) use ($out, $statusLabels) {
|
||
foreach ($subs as $sub) {
|
||
$endsAt = $sub->ends_at;
|
||
$expiryLabel = '无到期';
|
||
if ($endsAt) {
|
||
if ($endsAt->lt(now())) {
|
||
$expiryLabel = '已过期';
|
||
} elseif ($endsAt->lt(now()->addDays(7))) {
|
||
$expiryLabel = '7天内到期';
|
||
} else {
|
||
$expiryLabel = '未到期';
|
||
}
|
||
}
|
||
|
||
$status = (string) ($sub->status ?? '');
|
||
$statusText = ($statusLabels[$status] ?? $status);
|
||
$statusText = $statusText . ' (' . $status . ')';
|
||
|
||
fputcsv($out, [
|
||
$sub->id,
|
||
$sub->subscription_no,
|
||
$sub->merchant?->name ?? '',
|
||
$sub->plan_name ?: ($sub->plan?->name ?? ''),
|
||
(int) ($sub->platform_orders_count ?? 0),
|
||
$statusText,
|
||
$sub->billing_cycle ?: '',
|
||
(int) $sub->period_months,
|
||
(float) $sub->amount,
|
||
optional($sub->starts_at)->format('Y-m-d H:i:s') ?: '',
|
||
optional($sub->ends_at)->format('Y-m-d H:i:s') ?: '',
|
||
$expiryLabel,
|
||
optional($sub->trial_ends_at)->format('Y-m-d H:i:s') ?: '',
|
||
optional($sub->activated_at)->format('Y-m-d H:i:s') ?: '',
|
||
optional($sub->cancelled_at)->format('Y-m-d H:i:s') ?: '',
|
||
]);
|
||
}
|
||
});
|
||
|
||
fclose($out);
|
||
}, $filename, [
|
||
'Content-Type' => 'text/csv; charset=UTF-8',
|
||
]);
|
||
}
|
||
|
||
public function index(Request $request): View
|
||
{
|
||
$this->ensurePlatformAdmin($request);
|
||
|
||
$filters = [
|
||
'status' => trim((string) $request->query('status', '')),
|
||
'keyword' => trim((string) $request->query('keyword', '')),
|
||
'merchant_id' => trim((string) $request->query('merchant_id', '')),
|
||
'plan_id' => trim((string) $request->query('plan_id', '')),
|
||
// 到期辅助筛选(不改变 status 字段,仅按 ends_at 计算)
|
||
// - expired:已过期(ends_at < now)
|
||
// - expiring_7d:7 天内到期(now <= ends_at < now+7d)
|
||
'expiry' => trim((string) $request->query('expiry', '')),
|
||
];
|
||
|
||
$query = $this->applyFilters(
|
||
SiteSubscription::query()->with(['merchant', 'plan'])->withCount('platformOrders'),
|
||
$filters
|
||
);
|
||
|
||
$subscriptions = (clone $query)
|
||
->latest('id')
|
||
->paginate(10)
|
||
->withQueryString();
|
||
|
||
$baseQuery = $this->applyFilters(SiteSubscription::query(), $filters);
|
||
|
||
return view('admin.site_subscriptions.index', [
|
||
'subscriptions' => $subscriptions,
|
||
'filters' => $filters,
|
||
'statusLabels' => $this->statusLabels(),
|
||
'filterOptions' => [
|
||
'statuses' => $this->statusLabels(),
|
||
],
|
||
'merchants' => SiteSubscription::query()->with('merchant')->select('merchant_id')->distinct()->get()->pluck('merchant')->filter()->unique('id')->values(),
|
||
'plans' => SiteSubscription::query()->with('plan')->select('plan_id')->whereNotNull('plan_id')->distinct()->get()->pluck('plan')->filter()->unique('id')->values(),
|
||
'summaryStats' => [
|
||
'total_subscriptions' => (clone $baseQuery)->count(),
|
||
'activated_subscriptions' => (clone $baseQuery)->where('status', 'activated')->count(),
|
||
'pending_subscriptions' => (clone $baseQuery)->where('status', 'pending')->count(),
|
||
'cancelled_subscriptions' => (clone $baseQuery)->where('status', 'cancelled')->count(),
|
||
// 可治理辅助指标:按 ends_at 计算
|
||
'expired_subscriptions' => (clone $baseQuery)
|
||
->whereNotNull('ends_at')
|
||
->where('ends_at', '<', now())
|
||
->count(),
|
||
'expiring_7d_subscriptions' => (clone $baseQuery)
|
||
->whereNotNull('ends_at')
|
||
->where('ends_at', '>=', now())
|
||
->where('ends_at', '<', now()->addDays(7))
|
||
->count(),
|
||
],
|
||
]);
|
||
}
|
||
|
||
protected function statusLabels(): array
|
||
{
|
||
return [
|
||
'pending' => '待生效',
|
||
'activated' => '已生效',
|
||
'cancelled' => '已取消',
|
||
'expired' => '已过期',
|
||
];
|
||
}
|
||
|
||
protected function applyFilters(Builder $query, array $filters): Builder
|
||
{
|
||
return $query
|
||
->when($filters['status'] !== '', fn (Builder $builder) => $builder->where('status', $filters['status']))
|
||
->when(($filters['merchant_id'] ?? '') !== '', fn (Builder $builder) => $builder->where('merchant_id', (int) $filters['merchant_id']))
|
||
->when(($filters['plan_id'] ?? '') !== '', fn (Builder $builder) => $builder->where('plan_id', (int) $filters['plan_id']))
|
||
->when(($filters['expiry'] ?? '') !== '', function (Builder $builder) use ($filters) {
|
||
$expiry = (string) ($filters['expiry'] ?? '');
|
||
if ($expiry === 'expired') {
|
||
$builder->whereNotNull('ends_at')->where('ends_at', '<', now());
|
||
} elseif ($expiry === 'expiring_7d') {
|
||
$builder->whereNotNull('ends_at')
|
||
->where('ends_at', '>=', now())
|
||
->where('ends_at', '<', now()->addDays(7));
|
||
}
|
||
})
|
||
->when($filters['keyword'] !== '', function (Builder $builder) use ($filters) {
|
||
// 关键词搜索:订阅号 / 站点 / 套餐 / 计费周期
|
||
$keyword = trim((string) ($filters['keyword'] ?? ''));
|
||
if ($keyword === '') {
|
||
return;
|
||
}
|
||
|
||
$builder->where(function (Builder $q) use ($keyword) {
|
||
$q->where('subscription_no', 'like', '%' . $keyword . '%')
|
||
->orWhere('plan_name', 'like', '%' . $keyword . '%')
|
||
->orWhere('billing_cycle', 'like', '%' . $keyword . '%')
|
||
->orWhereHas('merchant', function (Builder $mq) use ($keyword) {
|
||
$mq->where('name', 'like', '%' . $keyword . '%')
|
||
->orWhere('slug', 'like', '%' . $keyword . '%');
|
||
})
|
||
->orWhereHas('plan', function (Builder $pq) use ($keyword) {
|
||
$pq->where('name', 'like', '%' . $keyword . '%')
|
||
->orWhere('code', 'like', '%' . $keyword . '%');
|
||
});
|
||
|
||
if (ctype_digit($keyword)) {
|
||
$q->orWhere('id', (int) $keyword);
|
||
}
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|