import { query } from '../../db/client.js' import { TASK_STATUS } from '../../domain/task-status.js' import { nowIso } from '../../utils/time.js' import type { JsonObject } from '../../types/json.js' export async function getAdminDashboardSummary() { const { todayStartIso, tomorrowStartIso } = resolveChinaTodayRange(nowIso()) const paidPendingClaimStatuses = [ TASK_STATUS.PAID, TASK_STATUS.LINK_GENERATED, TASK_STATUS.PENDING_BINDING_PREPARE, TASK_STATUS.WAITING_BINDING, ] const claimingStatuses = [TASK_STATUS.CLAIMED, TASK_STATUS.ROLE_CONFIRMED, TASK_STATUS.REDEEMING] const abnormalStatuses = [TASK_STATUS.RETRY_PENDING, TASK_STATUS.MANUAL_REVIEW] const result = await query( ` SELECT ( SELECT COUNT(*)::int FROM orders WHERE created_at >= $1 AND created_at < $2 ) AS today_orders, ( SELECT COUNT(*)::int FROM fulfillment_tasks WHERE task_status = ANY($3::text[]) ) AS paid_pending_claim, ( SELECT COUNT(*)::int FROM fulfillment_tasks WHERE task_status = ANY($4::text[]) ) AS claiming_tasks, ( SELECT COUNT(*)::int FROM fulfillment_tasks WHERE task_status = $5 AND updated_at >= $1 AND updated_at < $2 ) AS redeemed_today, ( SELECT COUNT(*)::int FROM fulfillment_tasks WHERE task_status = ANY($6::text[]) ) AS abnormal_tasks `, [ todayStartIso, tomorrowStartIso, paidPendingClaimStatuses, claimingStatuses, TASK_STATUS.REDEEMED, abnormalStatuses, ], ) const summary: JsonObject = result.rows[0] || {} return { todayOrders: Number(summary?.today_orders || 0), paidPendingClaim: Number(summary?.paid_pending_claim || 0), claimingTasks: Number(summary?.claiming_tasks || 0), redeemedToday: Number(summary?.redeemed_today || 0), abnormalTasks: Number(summary?.abnormal_tasks || 0), } } /** 返回中国时区当天对应的 UTC 查询范围,便于使用时间索引。 */ function resolveChinaTodayRange(now: string) { const parts = new Intl.DateTimeFormat('en-US', { timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit', }) .formatToParts(new Date(now)) .reduce>((result, part) => { result[part.type] = part.value return result }, {}) const year = Number(parts.year || 0) const month = Number(parts.month || 1) const day = Number(parts.day || 1) const todayStart = Date.UTC(year || 0, (month || 1) - 1, day || 1, -8) const tomorrowStart = todayStart + 24 * 60 * 60 * 1000 return { todayStartIso: new Date(todayStart).toISOString(), tomorrowStartIso: new Date(tomorrowStart).toISOString(), } }