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') // 口径对齐“待生效”语义:排除明确的同步失败(失败单应该去同步失败治理) ->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(), ] ); // 趋势卡(最小可用):近 7 天平台订单按天统计(订单数 + 已付金额) $trendDays = 7; $trendStart = now()->startOfDay()->subDays($trendDays - 1); $trendEnd = now()->endOfDay(); $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() ->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(按已付金额) $merchantRevenueRank7d = PlatformOrder::query() ->selectRaw('merchant_id, 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('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(); $merchantIdToName = Merchant::query()->pluck('name', 'id')->all(); return view('admin.dashboard', [ 'adminName' => $admin->name, 'stats' => $stats, 'platformOrderTrend7d' => $platformOrderTrend7d, 'recentPlatformOrders' => $recentPlatformOrders, 'planOrderShare' => $planOrderShare, 'planOrderShareTotal' => (int) $planOrderShareTotal, 'planIdToName' => $planIdToName, 'merchantRevenueRank7d' => $merchantRevenueRank7d, 'merchantIdToName' => $merchantIdToName, 'platformAdmin' => $admin, 'cacheMeta' => [ 'store' => config('cache.default'), 'ttl' => '10m', ], 'platformOverview' => [ 'system_role' => '总台管理', 'current_scope' => '总台运营方视角', 'merchant_mode' => '统一管理多个站点', 'channel_count' => 5, 'active_merchants' => $stats['active_merchants'], 'pending_orders' => $stats['pending_orders'], ], ]); } }