import { and, desc, eq, inArray } from "drizzle-orm"; import { getDatabase } from "@/modules/core/server/db"; import { accounts, alertRules, alertTriggers, elders } from "@/modules/core/server/schema"; import type { AlertCenterData, AlertRule, AlertRuleInput, AlertTrigger, AlertTriggerInput } from "@/modules/alerts/types"; export type AlertMutationFailure = { success: false; reason: string; status: number; }; export function isAlertMutationFailure(value: unknown): value is AlertMutationFailure { return ( typeof value === "object" && value !== null && "success" in value && (value as { success?: unknown }).success === false ); } function iso(value: Date): string { return value.toISOString(); } function optionalIso(value: Date | null): string | undefined { return value ? iso(value) : undefined; } function toAlertRule(row: typeof alertRules.$inferSelect): AlertRule { return { id: row.id, organizationId: row.organizationId, name: row.name, ruleType: row.ruleType, severity: row.severity, status: row.status, conditionSummary: row.conditionSummary, suggestion: row.suggestion, createdAt: iso(row.createdAt), updatedAt: iso(row.updatedAt), }; } function toAlertTrigger( row: typeof alertTriggers.$inferSelect, ruleNameById: Map, elderNameById: Map, accountNameById: Map, ): AlertTrigger { const ruleId = row.ruleId ?? undefined; const elderId = row.elderId ?? undefined; const handledByAccountId = row.handledByAccountId ?? undefined; return { id: row.id, organizationId: row.organizationId, ruleId, ruleName: ruleId ? ruleNameById.get(ruleId) ?? "" : "", elderId, elderName: elderId ? elderNameById.get(elderId) ?? "" : "", title: row.title, description: row.description, status: row.status, source: row.source, handledByAccountId, handledByName: handledByAccountId ? accountNameById.get(handledByAccountId) : undefined, handledAt: optionalIso(row.handledAt), handlingNotes: row.handlingNotes, createdAt: iso(row.createdAt), updatedAt: iso(row.updatedAt), }; } async function validateRuleAndElder(input: { elderId?: string; organizationId: string; ruleId?: string; }): Promise<{ elderNameById: Map; ruleNameById: Map; } | AlertMutationFailure> { const database = getDatabase(); const [ruleRows, elderRows] = await Promise.all([ input.ruleId ? database.select({ id: alertRules.id, name: alertRules.name }).from(alertRules).where(and(eq(alertRules.id, input.ruleId), eq(alertRules.organizationId, input.organizationId))) : [], input.elderId ? database.select({ id: elders.id, name: elders.name }).from(elders).where(and(eq(elders.id, input.elderId), eq(elders.organizationId, input.organizationId))) : [], ]); if (input.ruleId && !ruleRows[0]) { return { success: false, reason: "预警规则不存在", status: 404 }; } if (input.elderId && !elderRows[0]) { return { success: false, reason: "老人档案不存在", status: 404 }; } return { ruleNameById: new Map(ruleRows.map((rule) => [rule.id, rule.name])), elderNameById: new Map(elderRows.map((elder) => [elder.id, elder.name])), }; } export async function listAlertCenterData(organizationId: string): Promise { const database = getDatabase(); const [ruleRows, triggerRows] = await Promise.all([ database.select().from(alertRules).where(eq(alertRules.organizationId, organizationId)).orderBy(desc(alertRules.updatedAt)), database.select().from(alertTriggers).where(eq(alertTriggers.organizationId, organizationId)).orderBy(desc(alertTriggers.updatedAt)), ]); const ruleIds = Array.from(new Set(triggerRows.map((trigger) => trigger.ruleId).filter((id): id is string => typeof id === "string"))); const elderIds = Array.from(new Set(triggerRows.map((trigger) => trigger.elderId).filter((id): id is string => typeof id === "string"))); const accountIds = Array.from(new Set(triggerRows.map((trigger) => trigger.handledByAccountId).filter((id): id is string => typeof id === "string"))); const [triggerRuleRows, elderRows, accountRows] = await Promise.all([ ruleIds.length > 0 ? database.select({ id: alertRules.id, name: alertRules.name }).from(alertRules).where(inArray(alertRules.id, ruleIds)) : [], elderIds.length > 0 ? database.select({ id: elders.id, name: elders.name }).from(elders).where(and(eq(elders.organizationId, organizationId), inArray(elders.id, elderIds))) : [], accountIds.length > 0 ? database.select({ id: accounts.id, name: accounts.name }).from(accounts).where(inArray(accounts.id, accountIds)) : [], ]); const ruleNameById = new Map([...ruleRows.map((rule) => [rule.id, rule.name] as const), ...triggerRuleRows.map((rule) => [rule.id, rule.name] as const)]); return { metrics: { enabledRules: ruleRows.filter((rule) => rule.status === "enabled").length, openTriggers: triggerRows.filter((trigger) => trigger.status === "open" || trigger.status === "acknowledged").length, criticalTriggers: triggerRows.filter((trigger) => { const ruleId = trigger.ruleId; const rule = ruleId ? ruleRows.find((item) => item.id === ruleId) : undefined; return rule?.severity === "critical"; }).length, resolvedTriggers: triggerRows.filter((trigger) => trigger.status === "resolved" || trigger.status === "closed").length, }, rules: ruleRows.map(toAlertRule), triggers: triggerRows.map((trigger) => toAlertTrigger( trigger, ruleNameById, new Map(elderRows.map((elder) => [elder.id, elder.name])), new Map(accountRows.map((account) => [account.id, account.name])), ), ), }; } export async function createAlertRule(input: AlertRuleInput & { organizationId: string }): Promise { const database = getDatabase(); const rows = await database.insert(alertRules).values({ ...input }).returning(); const rule = rows[0]; return rule ? toAlertRule(rule) : { success: false, reason: "预警规则创建失败", status: 500 }; } export async function updateAlertRule(input: AlertRuleInput & { id: string; organizationId: string }): Promise { const database = getDatabase(); const rows = await database .update(alertRules) .set({ name: input.name, ruleType: input.ruleType, severity: input.severity, status: input.status, conditionSummary: input.conditionSummary, suggestion: input.suggestion, updatedAt: new Date(), }) .where(and(eq(alertRules.id, input.id), eq(alertRules.organizationId, input.organizationId))) .returning(); const rule = rows[0]; return rule ? toAlertRule(rule) : { success: false, reason: "预警规则不存在", status: 404 }; } export async function deleteAlertRule(input: { id: string; organizationId: string }): Promise { const database = getDatabase(); const rows = await database.delete(alertRules).where(and(eq(alertRules.id, input.id), eq(alertRules.organizationId, input.organizationId))).returning(); const rule = rows[0]; return rule ? toAlertRule(rule) : { success: false, reason: "预警规则不存在", status: 404 }; } export async function createAlertTrigger(input: AlertTriggerInput & { accountId: string; organizationId: string }): Promise { const maps = await validateRuleAndElder(input); if (isAlertMutationFailure(maps)) { return maps; } const database = getDatabase(); const now = new Date(); const handledAt = input.status === "resolved" || input.status === "closed" || input.status === "acknowledged" ? now : undefined; const rows = await database .insert(alertTriggers) .values({ organizationId: input.organizationId, ruleId: input.ruleId, elderId: input.elderId, title: input.title, description: input.description, status: input.status, source: input.source, handledByAccountId: handledAt ? input.accountId : undefined, handledAt, handlingNotes: input.handlingNotes, }) .returning(); const trigger = rows[0]; return trigger ? toAlertTrigger(trigger, maps.ruleNameById, maps.elderNameById, new Map()) : { success: false, reason: "预警触发记录创建失败", status: 500 }; } export async function updateAlertTrigger(input: AlertTriggerInput & { accountId: string; id: string; organizationId: string }): Promise { const maps = await validateRuleAndElder(input); if (isAlertMutationFailure(maps)) { return maps; } const database = getDatabase(); const now = new Date(); const handledAt = input.status === "resolved" || input.status === "closed" || input.status === "acknowledged" ? now : null; const rows = await database .update(alertTriggers) .set({ ruleId: input.ruleId ?? null, elderId: input.elderId ?? null, title: input.title, description: input.description, status: input.status, source: input.source, handledByAccountId: handledAt ? input.accountId : null, handledAt, handlingNotes: input.handlingNotes, updatedAt: now, }) .where(and(eq(alertTriggers.id, input.id), eq(alertTriggers.organizationId, input.organizationId))) .returning(); const trigger = rows[0]; return trigger ? toAlertTrigger(trigger, maps.ruleNameById, maps.elderNameById, new Map([[input.accountId, ""]])) : { success: false, reason: "预警触发记录不存在", status: 404 }; } export async function deleteAlertTrigger(input: { id: string; organizationId: string }): Promise { const database = getDatabase(); const rows = await database.delete(alertTriggers).where(and(eq(alertTriggers.id, input.id), eq(alertTriggers.organizationId, input.organizationId))).returning(); const trigger = rows[0]; return trigger ? toAlertTrigger(trigger, new Map(), new Map(), new Map()) : { success: false, reason: "预警触发记录不存在", status: 404 }; }