219 lines
8.4 KiB
TypeScript
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])));
|
|
}
|