Files

219 lines
8.4 KiB
TypeScript

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<string, string>): 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<DeviceOperationsData> {
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<DeviceAsset | DeviceMutationFailure> {
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<DeviceAsset | DeviceMutationFailure> {
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<DeviceAsset | DeviceMutationFailure> {
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<Map<string, string> | 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<MaintenanceTicket | DeviceMutationFailure> {
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<MaintenanceTicket | DeviceMutationFailure> {
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<MaintenanceTicket | DeviceMutationFailure> {
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])));
}