import "server-only" import { cache } from "react" import { count, eq, sql } from "drizzle-orm" import { db } from "@/shared/db" import { courseSelections, electiveCourses } from "@/shared/db/schema" /** * 选课模块管理员概览统计(P1-13 新增)。 * 用于 admin/teacher 列表页顶部展示关键指标。 */ export interface ElectiveOverviewStats { /** 课程总数 */ totalCourses: number /** 总选课人数(已录取 + 候补 + 已选) */ totalEnrolled: number /** 平均容量使用率(百分比,0-100) */ avgUtilization: number /** 待抽签的课程数(selectionMode=lottery 且 status=open 且存在 selected 记录) */ pendingLottery: number } /** * 获取选修课全局概览统计(admin 视角)。 * * 实现要点: * - 4 个独立查询合并为 3 个 SQL(pendingLottery 需 join),避免 N+1 * - 使用 SQL 聚合而非拉全表后 reduce,避免大数据量内存峰值 * - admin 不做 scope 过滤(统计全部课程) */ export const getElectiveOverviewStats = cache( async (): Promise => { // 并行执行聚合查询 const [totalRow, enrolledRow, utilizationRow, pendingRow] = await Promise.all([ // 1. 课程总数 db .select({ total: count() }) .from(electiveCourses), // 2. 总选课人数(活跃选课记录数) db .select({ total: count() }) .from(courseSelections) .where( sql`${courseSelections.status} IN ('selected', 'enrolled', 'waitlist')` ), // 3. 平均容量使用率(capacity > 0 时计算 enrolledCount/capacity 平均值) db .select({ avg: sql`COALESCE( AVG( CASE WHEN ${electiveCourses.capacity} > 0 THEN ${electiveCourses.enrolledCount}::float / ${electiveCourses.capacity} ELSE 0 END ) * 100, 0 )`, }) .from(electiveCourses), // 4. 待抽签课程数:lottery 模式且 status=open 且有 selected 状态的选课记录 db .select({ total: sql`count(distinct ${electiveCourses.id})` }) .from(electiveCourses) .innerJoin( courseSelections, eq(courseSelections.courseId, electiveCourses.id) ) .where( sql`${electiveCourses.selectionMode} = 'lottery' AND ${electiveCourses.status} = 'open' AND ${courseSelections.status} = 'selected'` ), ]) return { totalCourses: totalRow[0]?.total ?? 0, totalEnrolled: enrolledRow[0]?.total ?? 0, avgUtilization: Math.round(Number(utilizationRow[0]?.avg ?? 0)), pendingLottery: pendingRow[0]?.total ?? 0, } } )