feat: add care execution workspace
This commit is contained in:
141
modules/care/server/operations.ts
Normal file
141
modules/care/server/operations.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { and, desc, eq, inArray } from "drizzle-orm";
|
||||
|
||||
import { getDatabase } from "@/modules/core/server/db";
|
||||
import { accounts, careTasks, elders } from "@/modules/core/server/schema";
|
||||
import type { CareExecutionData, CareTask, CareTaskStatusUpdateInput } from "@/modules/care/types";
|
||||
|
||||
export type CareMutationFailure = {
|
||||
success: false;
|
||||
reason: string;
|
||||
status: number;
|
||||
};
|
||||
|
||||
type AccountNameRow = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type ElderNameRow = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export function isCareMutationFailure(value: unknown): value is CareMutationFailure {
|
||||
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 buildNameMap(rows: AccountNameRow[] | ElderNameRow[]): Map<string, string> {
|
||||
return new Map(rows.map((row) => [row.id, row.name]));
|
||||
}
|
||||
|
||||
function toCareTask(
|
||||
row: typeof careTasks.$inferSelect,
|
||||
elderNameById: Map<string, string>,
|
||||
accountNameById: Map<string, string>,
|
||||
): CareTask {
|
||||
const elderId = row.elderId ?? undefined;
|
||||
const completedByAccountId = row.completedByAccountId ?? undefined;
|
||||
|
||||
return {
|
||||
id: row.id,
|
||||
organizationId: row.organizationId,
|
||||
elderId,
|
||||
elderName: elderId ? elderNameById.get(elderId) ?? "" : "",
|
||||
title: row.title,
|
||||
careType: row.careType,
|
||||
priority: row.priority,
|
||||
status: row.status,
|
||||
scheduledAt: iso(row.scheduledAt),
|
||||
assigneeLabel: row.assigneeLabel,
|
||||
executionNotes: row.executionNotes,
|
||||
completedAt: optionalIso(row.completedAt),
|
||||
completedByAccountId,
|
||||
completedByName: completedByAccountId ? accountNameById.get(completedByAccountId) : undefined,
|
||||
createdAt: iso(row.createdAt),
|
||||
updatedAt: iso(row.updatedAt),
|
||||
};
|
||||
}
|
||||
|
||||
export async function listCareExecutionData(organizationId: string): Promise<CareExecutionData> {
|
||||
const database = getDatabase();
|
||||
const todayStart = new Date();
|
||||
todayStart.setHours(0, 0, 0, 0);
|
||||
|
||||
const taskRows = await database
|
||||
.select()
|
||||
.from(careTasks)
|
||||
.where(eq(careTasks.organizationId, organizationId))
|
||||
.orderBy(desc(careTasks.scheduledAt));
|
||||
|
||||
const elderIds = Array.from(new Set(taskRows.map((task) => task.elderId).filter((elderId): elderId is string => typeof elderId === "string")));
|
||||
const accountIds = Array.from(
|
||||
new Set(taskRows.map((task) => task.completedByAccountId).filter((accountId): accountId is string => typeof accountId === "string")),
|
||||
);
|
||||
|
||||
const [elderRows, accountRows] = await Promise.all([
|
||||
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 tasks = taskRows.map((task) => toCareTask(task, buildNameMap(elderRows), buildNameMap(accountRows)));
|
||||
|
||||
return {
|
||||
metrics: {
|
||||
pending: taskRows.filter((task) => task.status === "pending").length,
|
||||
inProgress: taskRows.filter((task) => task.status === "in_progress").length,
|
||||
completedToday: taskRows.filter((task) => task.status === "completed" && task.completedAt !== null && task.completedAt >= todayStart).length,
|
||||
highPriority: taskRows.filter((task) => task.priority === "high" || task.priority === "urgent").length,
|
||||
},
|
||||
tasks,
|
||||
};
|
||||
}
|
||||
|
||||
export async function updateCareTaskStatus(
|
||||
input: CareTaskStatusUpdateInput & { accountId: string; id: string; organizationId: string },
|
||||
): Promise<CareTask | CareMutationFailure> {
|
||||
const database = getDatabase();
|
||||
const completedAt = input.status === "completed" ? new Date() : null;
|
||||
const completedByAccountId = input.status === "completed" ? input.accountId : null;
|
||||
|
||||
const rows = await database
|
||||
.update(careTasks)
|
||||
.set({
|
||||
status: input.status,
|
||||
executionNotes: input.executionNotes,
|
||||
completedAt,
|
||||
completedByAccountId,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(and(eq(careTasks.id, input.id), eq(careTasks.organizationId, input.organizationId)))
|
||||
.returning();
|
||||
const task = rows[0];
|
||||
if (!task) {
|
||||
return { success: false, reason: "护理任务不存在", status: 404 };
|
||||
}
|
||||
|
||||
const [elderRows, accountRows] = await Promise.all([
|
||||
task.elderId
|
||||
? database.select({ id: elders.id, name: elders.name }).from(elders).where(and(eq(elders.id, task.elderId), eq(elders.organizationId, input.organizationId)))
|
||||
: [],
|
||||
task.completedByAccountId
|
||||
? database.select({ id: accounts.id, name: accounts.name }).from(accounts).where(eq(accounts.id, task.completedByAccountId))
|
||||
: [],
|
||||
]);
|
||||
|
||||
return toCareTask(task, buildNameMap(elderRows), buildNameMap(accountRows));
|
||||
}
|
||||
Reference in New Issue
Block a user