Files
saasshop/app/Http/Controllers/Admin/SiteSubscriptionController.php

299 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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']);
$baseOrdersQuery = PlatformOrder::query()
->where('site_subscription_id', $subscription->id);
// 可治理摘要:订阅下的订单同步情况(基于全量关联订单,不受页面筛选影响)
$summaryStats = [
'total_orders' => (clone $baseOrdersQuery)->count(),
'synced_orders' => (clone $baseOrdersQuery)
->whereRaw("JSON_EXTRACT(meta, '$.subscription_activation.subscription_id') IS NOT NULL")
->whereRaw("JSON_EXTRACT(meta, '$.subscription_activation_error.message') IS NULL")
->count(),
'failed_orders' => (clone $baseOrdersQuery)
->whereRaw("JSON_EXTRACT(meta, '$.subscription_activation_error.message') IS NOT NULL")
->count(),
'unsynced_orders' => (clone $baseOrdersQuery)
->whereRaw("JSON_EXTRACT(meta, '$.subscription_activation.subscription_id') IS NULL")
->whereRaw("JSON_EXTRACT(meta, '$.subscription_activation_error.message') IS NULL")
->count(),
'syncable_orders' => (clone $baseOrdersQuery)
->where('payment_status', 'paid')
->where('status', 'activated')
->whereRaw("JSON_EXTRACT(meta, '$.subscription_activation.subscription_id') IS NULL")
->count(),
];
// 页面列表筛选:仅影响“关联平台订单”列表展示,不影响摘要统计
$orderSyncStatus = trim((string) $request->query('order_sync_status', ''));
$displayOrdersQuery = (clone $baseOrdersQuery);
if ($orderSyncStatus === 'synced') {
$displayOrdersQuery
->whereRaw("JSON_EXTRACT(meta, '$.subscription_activation.subscription_id') IS NOT NULL")
->whereRaw("JSON_EXTRACT(meta, '$.subscription_activation_error.message') IS NULL");
} elseif ($orderSyncStatus === 'failed') {
$displayOrdersQuery
->whereRaw("JSON_EXTRACT(meta, '$.subscription_activation_error.message') IS NOT NULL");
} elseif ($orderSyncStatus === 'unsynced') {
$displayOrdersQuery
->whereRaw("JSON_EXTRACT(meta, '$.subscription_activation.subscription_id') IS NULL")
->whereRaw("JSON_EXTRACT(meta, '$.subscription_activation_error.message') IS NULL");
} elseif ($orderSyncStatus === 'syncable') {
$displayOrdersQuery
->where('payment_status', 'paid')
->where('status', 'activated')
->whereRaw("JSON_EXTRACT(meta, '$.subscription_activation.subscription_id') IS NULL");
}
$platformOrders = $displayOrdersQuery
->latest('id')
->paginate(10)
->withQueryString();
// 可治理摘要:订阅下的订单同步情况
$summaryStats = $summaryStats + [
'current_order_sync_status' => $orderSyncStatus,
];
$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,
'summaryStats' => $summaryStats,
'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_7d7 天内到期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);
}
});
});
}
}