import "server-only" import { cache } from "react" import { eq, and } from "drizzle-orm" import { db } from "@/shared/db" import { systemSettings } from "@/shared/db/schema" /** * 选课模块配置化设置(P2-4 新增)。 * * 设计原则: * - 复用全局 `system_settings` 表(category="elective"),避免新增独立表 * - 支持按年级覆盖(key=`creditLimit:grade:`),fallback 到全局(key=`creditLimit:default`) * - 用 React `cache()` 包装,单次请求内去重 * - 配置缺失时使用默认值(向后兼容) * * 配置项: * - `creditLimit:default` / `creditLimit:grade:`:学期学分上限(默认 10) * - `capacityNotifyThreshold`:容量阈值通知比例 0-1(默认 0.9) */ const SETTINGS_CATEGORY = "elective" /** 默认学期学分上限(K12 选修课) */ const DEFAULT_MAX_CREDIT_PER_TERM = 10 /** 默认容量阈值通知比例(90%) */ const DEFAULT_CAPACITY_NOTIFY_THRESHOLD = 0.9 /** * 读取 system_settings 中指定 key 的值。 * 失败或未配置时返回 null(不抛错,保证向后兼容)。 */ async function readSettingValue(key: string): Promise { const [row] = await db .select({ value: systemSettings.value, valueType: systemSettings.valueType }) .from(systemSettings) .where( and( eq(systemSettings.category, SETTINGS_CATEGORY), eq(systemSettings.key, key) ) ) .limit(1) return row?.value ?? null } /** * 获取学期学分上限(P2-4 新增)。 * * 查询顺序: * 1. 若传入 gradeId,先查 `creditLimit:grade:` * 2. 若未配置或未传入 gradeId,fallback 到 `creditLimit:default` * 3. 都未配置则返回默认值 10 * * @param gradeId 学生所在年级 ID(可选) */ export const getElectiveCreditLimit = cache( async (gradeId?: string | null): Promise => { if (gradeId) { const gradeValue = await readSettingValue(`creditLimit:grade:${gradeId}`) if (gradeValue !== null) { const parsed = Number(gradeValue) if (!Number.isNaN(parsed) && parsed > 0) return parsed } } const defaultValue = await readSettingValue("creditLimit:default") if (defaultValue !== null) { const parsed = Number(defaultValue) if (!Number.isNaN(parsed) && parsed > 0) return parsed } return DEFAULT_MAX_CREDIT_PER_TERM } ) /** * 获取容量阈值通知比例(P2-4 新增)。 * * 当课程 `enrolledCount >= capacity * threshold` 时触发管理员通知。 * 默认 0.9(90%)。 */ export const getCapacityNotifyThreshold = cache( async (): Promise => { const value = await readSettingValue("capacityNotifyThreshold") if (value !== null) { const parsed = Number(value) if (!Number.isNaN(parsed) && parsed > 0 && parsed <= 1) return parsed } return DEFAULT_CAPACITY_NOTIFY_THRESHOLD } ) /** 导出默认值常量(供测试与文档引用) */ export const ELECTIVE_DEFAULTS = { MAX_CREDIT_PER_TERM: DEFAULT_MAX_CREDIT_PER_TERM, CAPACITY_NOTIFY_THRESHOLD: DEFAULT_CAPACITY_NOTIFY_THRESHOLD, } as const