ensurePlatformAdmin($request); $stats = Cache::remember( CacheKeys::platformDashboardStats(), now()->addMinutes(10), fn () => [ 'merchants' => Merchant::count(), 'admins' => Admin::count(), 'users' => User::count(), 'products' => Product::count(), 'orders' => Order::count(), // 收费中心(平台侧) 'plans' => Plan::count(), 'site_subscriptions' => SiteSubscription::count(), 'site_subscriptions_expired' => SiteSubscription::query() ->whereNotNull('ends_at') ->where('ends_at', '<', now()) ->count(), 'site_subscriptions_expiring_7d' => SiteSubscription::query() ->whereNotNull('ends_at') ->where('ends_at', '>=', now()) ->where('ends_at', '<', now()->addDays(7)) ->count(), 'platform_orders' => PlatformOrder::count(), 'platform_orders_unpaid_pending' => PlatformOrder::query() ->where('payment_status', 'unpaid') ->where('status', 'pending') ->count(), 'platform_orders_paid_pending' => PlatformOrder::query() ->where('payment_status', 'paid') ->where('status', 'pending') // 口径对齐“待生效”语义(sync_status=unsynced):未同步 + 非失败 ->whereRaw("JSON_EXTRACT(meta, '$.subscription_activation.subscription_id') IS NULL") ->whereRaw("JSON_EXTRACT(meta, '$.subscription_activation_error.message') IS NULL") ->count(), // 可同步:沿用平台订单列表口径(paid+activated+未同步+无失败),且排除续费缺订阅脏数据 'platform_orders_syncable' => PlatformOrder::query() ->where('payment_status', 'paid') ->where('status', 'activated') ->whereRaw("JSON_EXTRACT(meta, '$.subscription_activation.subscription_id') IS NULL") ->whereRaw("JSON_EXTRACT(meta, '$.subscription_activation_error.message') IS NULL") ->where(function ($q) { $q->where('order_type', '!=', 'renewal') ->orWhereNotNull('site_subscription_id'); }) ->count(), // 同步失败:沿用平台订单列表口径(meta.subscription_activation_error.message 存在即失败) 'platform_orders_sync_failed' => PlatformOrder::query() ->whereRaw("JSON_EXTRACT(meta, '$.subscription_activation_error.message') IS NOT NULL") ->count(), 'platform_orders_renewal_missing_subscription' => PlatformOrder::query() ->where('order_type', 'renewal') ->whereNull('site_subscription_id') ->count(), // BMPA 失败:用于运营快速定位“批量标记支付并生效”失败的订单集合 'platform_orders_bmpa_failed' => PlatformOrder::query() ->whereRaw("JSON_EXTRACT(meta, '$.batch_mark_paid_and_activate_error.message') IS NOT NULL") ->count(), // 无回执(已支付但缺少回执证据):用于治理“已付但无回执”的风险订单 'platform_orders_paid_no_receipt' => PlatformOrder::query() ->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(), // 对账不一致(基于 paid_amount vs 回执总额,容差见 config('saasshop.amounts.tolerance')) 'platform_orders_reconcile_mismatch' => (function () { $tol = (float) config('saasshop.amounts.tolerance', 0.01); $tolCents = (int) round($tol * 100); $tolCents = max(1, $tolCents); $q = PlatformOrder::query(); $driver = $q->getQuery()->getConnection()->getDriverName(); // 重要:与 PlatformOrder::isReconcileMismatch() 口径一致:若无回执证据,不判定 mismatch(而是走“无回执”治理集合) $q->where(function ($b) { $b->whereRaw("JSON_EXTRACT(meta, '$.payment_summary.total_amount') IS NOT NULL") ->orWhereRaw("JSON_EXTRACT(meta, '$.payment_receipts[0].amount') IS NOT NULL"); }); if ($driver === 'sqlite') { $q->whereRaw("ABS(ROUND((CASE WHEN JSON_EXTRACT(meta, '$.payment_summary.total_amount') IS NOT NULL THEN CAST(JSON_EXTRACT(meta, '$.payment_summary.total_amount') AS REAL) ELSE (SELECT IFNULL(SUM(CAST(JSON_EXTRACT(value, '$.amount') AS REAL)), 0) FROM json_each(COALESCE(JSON_EXTRACT(meta, '$.payment_receipts'), '[]'))) END) * 100) - ROUND(paid_amount * 100)) >= {$tolCents}"); } else { $q->whereRaw("ABS(ROUND((CASE WHEN JSON_EXTRACT(meta, '$.payment_summary.total_amount') IS NOT NULL THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(meta, '$.payment_summary.total_amount')) AS DECIMAL(12,2)) ELSE (SELECT IFNULL(SUM(j.amount), 0) FROM JSON_TABLE(meta, '$.payment_receipts[*]' COLUMNS(amount DECIMAL(12,2) PATH '$.amount')) j) END) * 100) - ROUND(paid_amount * 100)) >= {$tolCents}"); } return $q->count(); })(), // 退款数据不一致(口径与平台订单列表 refund_inconsistent=1 一致) 'platform_orders_refund_inconsistent' => (function () { $tol = (float) config('saasshop.amounts.tolerance', 0.01); $tolCents = (int) round($tol * 100); $tolCents = max(1, $tolCents); $q = PlatformOrder::query(); $driver = $q->getQuery()->getConnection()->getDriverName(); if ($driver === 'sqlite') { $refundTotalExpr = "(CASE WHEN JSON_EXTRACT(meta, '$.refund_summary.total_amount') IS NOT NULL THEN CAST(JSON_EXTRACT(meta, '$.refund_summary.total_amount') AS REAL) ELSE (SELECT IFNULL(SUM(CAST(JSON_EXTRACT(value, '$.amount') AS REAL)), 0) FROM json_each(COALESCE(JSON_EXTRACT(meta, '$.refund_receipts'), '[]'))) END)"; $q->where(function ($b) use ($refundTotalExpr, $tolCents) { $b->where(function ($q2) use ($refundTotalExpr, $tolCents) { $q2->where('payment_status', 'refunded') ->whereRaw('paid_amount > 0') ->whereRaw("(ROUND($refundTotalExpr * 100) + {$tolCents}) < ROUND(paid_amount * 100)"); })->orWhere(function ($q2) use ($refundTotalExpr, $tolCents) { $q2->where('payment_status', '!=', 'refunded') ->whereRaw('paid_amount > 0') ->whereRaw("ROUND($refundTotalExpr * 100) >= (ROUND(paid_amount * 100) + {$tolCents})"); }); }); } else { $refundTotalExpr = "(CASE WHEN JSON_EXTRACT(meta, '$.refund_summary.total_amount') IS NOT NULL THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(meta, '$.refund_summary.total_amount')) AS DECIMAL(12,2)) ELSE (SELECT IFNULL(SUM(j.amount), 0) FROM JSON_TABLE(meta, '$.refund_receipts[*]' COLUMNS(amount DECIMAL(12,2) PATH '$.amount')) j) END)"; $q->where(function ($b) use ($refundTotalExpr, $tolCents) { $b->where(function ($q2) use ($refundTotalExpr, $tolCents) { $q2->where('payment_status', 'refunded') ->whereRaw('paid_amount > 0') ->whereRaw("(ROUND($refundTotalExpr * 100) + {$tolCents}) < ROUND(paid_amount * 100)"); })->orWhere(function ($q2) use ($refundTotalExpr, $tolCents) { $q2->where('payment_status', '!=', 'refunded') ->whereRaw('paid_amount > 0') ->whereRaw("ROUND($refundTotalExpr * 100) >= (ROUND(paid_amount * 100) + {$tolCents})"); }); }); } return $q->count(); })(), // 站点治理 'active_merchants' => Merchant::query()->where('status', 'active')->count(), 'pending_orders' => Order::query()->where('status', 'pending')->count(), ] ); // 统一基准时间:避免本方法内多次 now() 调用在跨天瞬间造成口径漂移 $baseNow = now(); // 趋势卡(最小可用):近 7 天平台订单按天统计(订单数 + 已付金额) $trendDays = 7; $trendStart = $baseNow->copy()->startOfDay()->subDays($trendDays - 1); $trendEnd = $baseNow->copy()->endOfDay(); // 统一提供给视图做“趋势/排行/占比”跳转的日期范围口径,避免 Blade 内重复 now() 计算导致漂移。 $dashboardRangeFrom7d = (string) $trendStart->format('Y-m-d'); $dashboardRangeTo7d = (string) $trendEnd->format('Y-m-d'); $trendRawRows = PlatformOrder::query() ->selectRaw("DATE(created_at) as d") ->selectRaw('COUNT(*) as cnt') ->selectRaw("SUM(CASE WHEN payment_status = 'paid' THEN paid_amount ELSE 0 END) as paid_sum") ->whereBetween('created_at', [$trendStart, $trendEnd]) ->groupBy('d') ->orderBy('d') ->get(); $trendByDate = []; foreach ($trendRawRows as $r) { $trendByDate[(string) $r->d] = [ 'date' => (string) $r->d, 'count' => (int) ($r->cnt ?? 0), 'paid_sum' => (float) ($r->paid_sum ?? 0), ]; } $platformOrderTrend7d = []; for ($i = 0; $i < $trendDays; $i++) { $day = $trendStart->copy()->addDays($i)->format('Y-m-d'); $platformOrderTrend7d[] = $trendByDate[$day] ?? [ 'date' => $day, 'count' => 0, 'paid_sum' => 0.0, ]; } $recentPlatformOrders = PlatformOrder::query() ->with(['merchant', 'plan']) ->orderByDesc('id') ->limit(5) ->get(); // 占比卡(最小可用):近 7 天按套餐统计平台订单数量 TopN // 说明: // - Top5 数据用于“对比/聚焦”(减少噪音) // - total 用于计算“占比/覆盖率”,避免将 Top5 误当全量 $planOrderShareTotal = PlatformOrder::query() ->whereBetween('created_at', [$trendStart, $trendEnd]) ->whereNotNull('plan_id') ->count(); $planOrderShare = PlatformOrder::query() ->selectRaw('plan_id, COUNT(*) as cnt') ->whereBetween('created_at', [$trendStart, $trendEnd]) ->whereNotNull('plan_id') ->groupBy('plan_id') ->orderByDesc('cnt') ->limit(5) ->get() ->map(function ($row) { return [ 'plan_id' => (int) $row->plan_id, 'count' => (int) $row->cnt, ]; }) ->values() ->all(); $planIdToName = Plan::query()->pluck('name', 'id')->all(); // 排行卡(最小可用):近 7 天站点收入排行 Top5(按已付金额) // 说明:只统计 payment_status=paid,避免“未支付订单=0金额”混入排行造成噪音。 $merchantRevenueRank7d = PlatformOrder::query() ->selectRaw('merchant_id, COUNT(*) as cnt') ->selectRaw('SUM(paid_amount) as paid_sum') ->whereBetween('created_at', [$trendStart, $trendEnd]) ->where('payment_status', 'paid') ->groupBy('merchant_id') ->orderByDesc('paid_sum') ->limit(5) ->get() ->map(function ($row) { return [ 'merchant_id' => (int) ($row->merchant_id ?? 0), 'count' => (int) ($row->cnt ?? 0), 'paid_sum' => (float) ($row->paid_sum ?? 0), ]; }) ->values() ->all(); // 用于计算“Top5覆盖率/其它”的全量分母(近 7 天全站点已付总额/总订单数) $merchantRevenueTotalPaid7d = (float) PlatformOrder::query() ->whereBetween('created_at', [$trendStart, $trendEnd]) ->where('payment_status', 'paid') ->sum('paid_amount'); $merchantRevenueTotalOrders7d = (int) PlatformOrder::query() ->whereBetween('created_at', [$trendStart, $trendEnd]) ->where('payment_status', 'paid') ->count(); $merchantIdToName = Merchant::query()->pluck('name', 'id')->all(); // 平台定位(运营版):北极星指标 + 明确治理入口(不暴露组合维度) $range30Start = $baseNow->copy()->subDays(29)->startOfDay(); $range30End = $baseNow->copy()->endOfDay(); $range30From = (string) $range30Start->format('Y-m-d'); $range30To = (string) $range30End->format('Y-m-d'); $paidRevenue30d = (float) PlatformOrder::query() ->where('payment_status', 'paid') ->whereBetween('created_at', [$range30Start, $range30End]) ->sum('paid_amount'); $activePaidMerchants = (int) SiteSubscription::query() ->whereNotNull('merchant_id') ->where('status', 'activated') ->whereNotNull('ends_at') ->where('ends_at', '>=', $baseNow) ->distinct() ->count('merchant_id'); $renewalCreated30d = (int) PlatformOrder::query() ->where('order_type', 'renewal') ->whereBetween('created_at', [$range30Start, $range30End]) ->count(); $renewalSuccess30d = (int) PlatformOrder::query() ->where('order_type', 'renewal') ->where('payment_status', 'paid') ->where('status', 'activated') ->whereBetween('created_at', [$range30Start, $range30End]) ->count(); $renewalSuccessRate30d = $renewalCreated30d > 0 ? min(100, max(0, round(($renewalSuccess30d / $renewalCreated30d) * 100, 1))) : 0; $ordersTotal7d = (int) PlatformOrder::query() ->whereBetween('created_at', [$trendStart, $trendEnd]) ->count(); $funnelUnpaidPending7d = (int) PlatformOrder::query() ->whereBetween('created_at', [$trendStart, $trendEnd]) ->where('payment_status', 'unpaid') ->where('status', 'pending') ->count(); $funnelPaid7d = (int) PlatformOrder::query() ->whereBetween('created_at', [$trendStart, $trendEnd]) ->where('payment_status', 'paid') ->count(); $funnelPaidActivated7d = (int) PlatformOrder::query() ->whereBetween('created_at', [$trendStart, $trendEnd]) ->where('payment_status', 'paid') ->where('status', 'activated') ->count(); // 将平台定位的关键指标与“可执行动作入口”绑定(回到仪表盘自身) $opsLinks = [ 'revenue_30d_paid_orders' => \App\Support\BackUrl::withBack('/admin/platform-orders?' . \Illuminate\Support\Arr::query([ 'payment_status' => 'paid', 'created_from' => $range30From, 'created_to' => $range30To, ]), \App\Support\BackUrl::selfWithoutBack()), 'active_paid_merchants_subscriptions' => \App\Support\BackUrl::withBack('/admin/site-subscriptions?' . \Illuminate\Support\Arr::query([ 'status' => 'activated', // 口径对齐 active_paid_merchants:已生效且未到期(ends_at >= today) 'ends_from' => (string) $baseNow->format('Y-m-d'), 'page' => null, ]), \App\Support\BackUrl::selfWithoutBack()), 'renewal_orders_30d' => \App\Support\BackUrl::withBack('/admin/platform-orders?' . \Illuminate\Support\Arr::query([ 'order_type' => 'renewal', 'created_from' => $range30From, 'created_to' => $range30To, ]), \App\Support\BackUrl::selfWithoutBack()), 'funnel_unpaid_pending_7d' => \App\Support\BackUrl::withBack('/admin/platform-orders?' . \Illuminate\Support\Arr::query([ 'payment_status' => 'unpaid', 'status' => 'pending', 'created_from' => $dashboardRangeFrom7d, 'created_to' => $dashboardRangeTo7d, ]), \App\Support\BackUrl::selfWithoutBack()), 'funnel_paid_7d' => \App\Support\BackUrl::withBack('/admin/platform-orders?' . \Illuminate\Support\Arr::query([ 'payment_status' => 'paid', 'created_from' => $dashboardRangeFrom7d, 'created_to' => $dashboardRangeTo7d, ]), \App\Support\BackUrl::selfWithoutBack()), 'funnel_paid_activated_7d' => \App\Support\BackUrl::withBack('/admin/platform-orders?' . \Illuminate\Support\Arr::query([ 'payment_status' => 'paid', 'status' => 'activated', 'created_from' => $dashboardRangeFrom7d, 'created_to' => $dashboardRangeTo7d, ]), \App\Support\BackUrl::selfWithoutBack()), ]; return view('admin.dashboard', [ 'adminName' => $admin->name, 'stats' => $stats, 'platformOrderTrend7d' => $platformOrderTrend7d, 'recentPlatformOrders' => $recentPlatformOrders, 'dashboardRangeFrom7d' => $dashboardRangeFrom7d, 'dashboardRangeTo7d' => $dashboardRangeTo7d, // 注意:旧版的 platformPositioning 已弃用;当前仪表盘使用 platformOpsOverview 作为“平台定位(运营版)”的数据源。 // 这里先移除 platformPositioning,避免遗留变量命名不一致导致 /admin 500,破坏 Dashboard 回归基线。 'planOrderShare' => $planOrderShare, 'planOrderShareTotal' => (int) $planOrderShareTotal, 'planIdToName' => $planIdToName, 'merchantRevenueRank7d' => $merchantRevenueRank7d, 'merchantRevenueTotalPaid7d' => (float) $merchantRevenueTotalPaid7d, 'merchantRevenueTotalOrders7d' => (int) $merchantRevenueTotalOrders7d, 'merchantIdToName' => $merchantIdToName, 'platformAdmin' => $admin, 'cacheMeta' => [ 'store' => config('cache.default'), 'ttl' => '10m', ], 'platformOpsOverview' => [ // 北极星指标 'paid_revenue_30d' => $paidRevenue30d, 'active_paid_merchants' => $activePaidMerchants, 'renewal_success_rate_30d' => $renewalSuccessRate30d, 'renewal_success_30d' => $renewalSuccess30d, 'renewal_created_30d' => $renewalCreated30d, // 漏斗(近7天) 'orders_total_7d' => $ordersTotal7d, 'funnel_unpaid_pending_7d' => $funnelUnpaidPending7d, 'funnel_paid_7d' => $funnelPaid7d, 'funnel_paid_activated_7d' => $funnelPaidActivated7d, // 待处理治理(积压口径,全量) 'govern_bmpa_processable' => (int) ($stats['platform_orders_unpaid_pending'] ?? 0), 'govern_syncable' => (int) ($stats['platform_orders_syncable'] ?? 0), 'govern_sync_failed' => (int) ($stats['platform_orders_sync_failed'] ?? 0), 'links' => $opsLinks, ], 'platformOverview' => [ 'system_role' => '总台管理', 'current_scope' => '总台运营方视角', 'merchant_mode' => '统一管理多个站点', 'channel_count' => 5, 'active_merchants' => $stats['active_merchants'], 'pending_orders' => $stats['pending_orders'], ], ]); } }