import { and, desc, eq, inArray } from "drizzle-orm"; import { getDatabase } from "@/modules/core/server/db"; import { deviceAssets, maintenanceTickets } from "@/modules/core/server/schema"; import type { DeviceAsset, DeviceAssetInput, DeviceOperationsData, MaintenanceTicket, MaintenanceTicketInput } from "@/modules/devices/types"; export type DeviceMutationFailure = { success: false; reason: string; status: number; }; export function isDeviceMutationFailure(value: unknown): value is DeviceMutationFailure { 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 toDeviceAsset(row: typeof deviceAssets.$inferSelect): DeviceAsset { return { id: row.id, organizationId: row.organizationId, name: row.name, code: row.code, category: row.category, location: row.location, status: row.status, lastInspectedAt: optionalIso(row.lastInspectedAt), notes: row.notes, createdAt: iso(row.createdAt), updatedAt: iso(row.updatedAt), }; } function toMaintenanceTicket(row: typeof maintenanceTickets.$inferSelect, deviceNameById: Map): MaintenanceTicket { const deviceId = row.deviceId ?? undefined; return { id: row.id, organizationId: row.organizationId, deviceId, deviceName: deviceId ? deviceNameById.get(deviceId) ?? "" : "", title: row.title, description: row.description, priority: row.priority, status: row.status, assigneeLabel: row.assigneeLabel, resolutionNotes: row.resolutionNotes, resolvedAt: optionalIso(row.resolvedAt), createdAt: iso(row.createdAt), updatedAt: iso(row.updatedAt), }; } export async function listDeviceOperationsData(organizationId: string): Promise { const database = getDatabase(); const [deviceRows, ticketRows] = await Promise.all([ database.select().from(deviceAssets).where(eq(deviceAssets.organizationId, organizationId)).orderBy(desc(deviceAssets.updatedAt)), database.select().from(maintenanceTickets).where(eq(maintenanceTickets.organizationId, organizationId)).orderBy(desc(maintenanceTickets.updatedAt)), ]); const deviceNameById = new Map(deviceRows.map((device) => [device.id, device.name])); return { metrics: { activeDevices: deviceRows.filter((device) => device.status === "active").length, maintenanceDevices: deviceRows.filter((device) => device.status === "maintenance").length, openTickets: ticketRows.filter((ticket) => ticket.status === "open" || ticket.status === "assigned").length, urgentTickets: ticketRows.filter((ticket) => ticket.priority === "urgent").length, }, devices: deviceRows.map(toDeviceAsset), tickets: ticketRows.map((ticket) => toMaintenanceTicket(ticket, deviceNameById)), }; } export async function createDeviceAsset(input: DeviceAssetInput & { organizationId: string }): Promise { const database = getDatabase(); const rows = await database .insert(deviceAssets) .values({ organizationId: input.organizationId, name: input.name, code: input.code, category: input.category, location: input.location, status: input.status, lastInspectedAt: input.lastInspectedAt ? new Date(input.lastInspectedAt) : undefined, notes: input.notes, }) .returning(); const device = rows[0]; return device ? toDeviceAsset(device) : { success: false, reason: "设备创建失败", status: 500 }; } export async function updateDeviceAsset(input: DeviceAssetInput & { id: string; organizationId: string }): Promise { const database = getDatabase(); const rows = await database .update(deviceAssets) .set({ name: input.name, code: input.code, category: input.category, location: input.location, status: input.status, lastInspectedAt: input.lastInspectedAt ? new Date(input.lastInspectedAt) : null, notes: input.notes, updatedAt: new Date(), }) .where(and(eq(deviceAssets.id, input.id), eq(deviceAssets.organizationId, input.organizationId))) .returning(); const device = rows[0]; return device ? toDeviceAsset(device) : { success: false, reason: "设备不存在", status: 404 }; } export async function deleteDeviceAsset(input: { id: string; organizationId: string }): Promise { const database = getDatabase(); const rows = await database .delete(deviceAssets) .where(and(eq(deviceAssets.id, input.id), eq(deviceAssets.organizationId, input.organizationId))) .returning(); const device = rows[0]; return device ? toDeviceAsset(device) : { success: false, reason: "设备不存在", status: 404 }; } async function getDeviceNameById(organizationId: string, deviceId: string | undefined): Promise | DeviceMutationFailure> { if (!deviceId) { return new Map(); } const database = getDatabase(); const rows = await database .select({ id: deviceAssets.id, name: deviceAssets.name }) .from(deviceAssets) .where(and(eq(deviceAssets.id, deviceId), eq(deviceAssets.organizationId, organizationId))); const device = rows[0]; return device ? new Map([[device.id, device.name]]) : { success: false, reason: "设备不存在", status: 404 }; } export async function createMaintenanceTicket(input: MaintenanceTicketInput & { organizationId: string }): Promise { const deviceNameById = await getDeviceNameById(input.organizationId, input.deviceId); if (isDeviceMutationFailure(deviceNameById)) { return deviceNameById; } const database = getDatabase(); const resolvedAt = input.status === "resolved" || input.status === "closed" ? new Date() : undefined; const rows = await database .insert(maintenanceTickets) .values({ organizationId: input.organizationId, deviceId: input.deviceId, title: input.title, description: input.description, priority: input.priority, status: input.status, assigneeLabel: input.assigneeLabel, resolutionNotes: input.resolutionNotes, resolvedAt, }) .returning(); const ticket = rows[0]; return ticket ? toMaintenanceTicket(ticket, deviceNameById) : { success: false, reason: "维修工单创建失败", status: 500 }; } export async function updateMaintenanceTicket(input: MaintenanceTicketInput & { id: string; organizationId: string }): Promise { const deviceNameById = await getDeviceNameById(input.organizationId, input.deviceId); if (isDeviceMutationFailure(deviceNameById)) { return deviceNameById; } const database = getDatabase(); const resolvedAt = input.status === "resolved" || input.status === "closed" ? new Date() : null; const rows = await database .update(maintenanceTickets) .set({ deviceId: input.deviceId ?? null, title: input.title, description: input.description, priority: input.priority, status: input.status, assigneeLabel: input.assigneeLabel, resolutionNotes: input.resolutionNotes, resolvedAt, updatedAt: new Date(), }) .where(and(eq(maintenanceTickets.id, input.id), eq(maintenanceTickets.organizationId, input.organizationId))) .returning(); const ticket = rows[0]; return ticket ? toMaintenanceTicket(ticket, deviceNameById) : { success: false, reason: "维修工单不存在", status: 404 }; } export async function deleteMaintenanceTicket(input: { id: string; organizationId: string }): Promise { const database = getDatabase(); const rows = await database .delete(maintenanceTickets) .where(and(eq(maintenanceTickets.id, input.id), eq(maintenanceTickets.organizationId, input.organizationId))) .returning(); const ticket = rows[0]; if (!ticket) { return { success: false, reason: "维修工单不存在", status: 404 }; } const deviceIds = ticket.deviceId ? [ticket.deviceId] : []; const deviceRows = deviceIds.length > 0 ? await database.select({ id: deviceAssets.id, name: deviceAssets.name }).from(deviceAssets).where(inArray(deviceAssets.id, deviceIds)) : []; return toMaintenanceTicket(ticket, new Map(deviceRows.map((device) => [device.id, device.name]))); }