feat: build collaboration workspaces
This commit is contained in:
218
modules/devices/server/operations.ts
Normal file
218
modules/devices/server/operations.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
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])));
|
||||
}
|
||||
Reference in New Issue
Block a user