feat: add care execution workspace

This commit is contained in:
2026-07-03 00:35:04 -07:00
parent df7c2efcc5
commit b6b15acc69
20 changed files with 4270 additions and 18 deletions

View File

@@ -6,6 +6,7 @@ import {
beds,
buildings,
campuses,
careTasks,
chronicConditions,
elders,
floors,
@@ -92,6 +93,18 @@ type SeedHealthReview = {
vitalIndex: number;
};
type SeedCareTask = {
assigneeLabel: string;
careType: "daily_care" | "meal" | "medication" | "rehab" | "inspection" | "cleaning" | "other";
completedHoursAgo?: number;
elderName: string;
executionNotes: string;
priority: "low" | "normal" | "high" | "urgent";
scheduledHoursOffset: number;
status: "pending" | "in_progress" | "completed" | "cancelled";
title: string;
};
const DEFAULT_ROOMS: SeedRoom[] = [
{ campusName: "主院区", buildingName: "护理楼", floorName: "一层", floorLevel: 1, code: "A101", name: "阳光房", capacity: 2 },
{ campusName: "主院区", buildingName: "护理楼", floorName: "一层", floorLevel: 1, code: "A102", name: "康养房", capacity: 2 },
@@ -374,6 +387,60 @@ const DEFAULT_HEALTH_REVIEWS: SeedHealthReview[] = [
},
];
const DEFAULT_CARE_TASKS: SeedCareTask[] = [
{
elderName: "王桂兰",
title: "晨间协助洗漱与翻身",
careType: "daily_care",
priority: "normal",
status: "pending",
scheduledHoursOffset: 1,
assigneeLabel: "一号护理组",
executionNotes: "",
},
{
elderName: "孙秀梅",
title: "膝关节康复训练陪同",
careType: "rehab",
priority: "high",
status: "in_progress",
scheduledHoursOffset: -1,
assigneeLabel: "康复护理组",
executionNotes: "已完成热身,等待康复师复核动作。",
},
{
elderName: "周玉珍",
title: "餐前血糖记录与助餐",
careType: "meal",
priority: "normal",
status: "completed",
scheduledHoursOffset: -4,
completedHoursAgo: 3,
assigneeLabel: "二号护理组",
executionNotes: "餐前血糖已记录,午餐摄入约八成。",
},
{
elderName: "赵国华",
title: "重点照护夜间巡检补记",
careType: "inspection",
priority: "urgent",
status: "pending",
scheduledHoursOffset: -2,
assigneeLabel: "夜班护理组",
executionNotes: "",
},
{
elderName: "刘长青",
title: "用药提醒与胸痛询问",
careType: "medication",
priority: "high",
status: "pending",
scheduledHoursOffset: 3,
assigneeLabel: "三号护理组",
executionNotes: "",
},
];
function hasRows(rows: unknown[]): boolean {
return rows.length > 0;
}
@@ -558,6 +625,31 @@ export async function seedDefaultWorkspaceData(organizationId: string): Promise<
}),
);
const now = new Date();
await transaction.insert(careTasks).values(
DEFAULT_CARE_TASKS.map((task) => {
const elderId = elderIdByName.get(task.elderName);
if (!elderId) {
throw new Error("默认护理任务初始化失败");
}
const completedAt = task.completedHoursAgo === undefined ? undefined : new Date(now.getTime() - task.completedHoursAgo * 60 * 60 * 1000);
return {
organizationId,
elderId,
title: task.title,
careType: task.careType,
priority: task.priority,
status: task.status,
scheduledAt: new Date(now.getTime() + task.scheduledHoursOffset * 60 * 60 * 1000),
assigneeLabel: task.assigneeLabel,
executionNotes: task.executionNotes,
completedAt,
};
}),
);
await transaction.insert(healthProfiles).values(
DEFAULT_HEALTH_PROFILES.map((profile) => {
const elderId = elderIdByName.get(profile.elderName);
@@ -577,7 +669,6 @@ export async function seedDefaultWorkspaceData(organizationId: string): Promise<
}),
);
const now = new Date();
const vitalRows = await transaction
.insert(vitalRecords)
.values(

View File

@@ -25,6 +25,8 @@ export const PERMISSION_DEFINITIONS: PermissionDefinition[] = [
{ id: "audit:read", label: "查看审计", category: "审计", description: "查看关键操作审计日志。" },
{ id: "incident:read", label: "查看故障", category: "运维", description: "查看系统健康和故障中心。" },
{ id: "incident:manage", label: "处理故障", category: "运维", description: "确认、处理和关闭故障事件。" },
{ id: "care:read", label: "查看护理任务", category: "护理", description: "查看护理服务任务、执行状态和记录。" },
{ id: "care:manage", label: "处理护理任务", category: "护理", description: "启动、完成和维护护理服务执行记录。" },
{ id: "health:read", label: "查看健康数据", category: "健康", description: "查看老人健康档案、生命体征和异常复核。" },
{ id: "health:manage", label: "管理健康数据", category: "健康", description: "维护健康档案、生命体征、慢病和异常复核。" },
{ id: "facility:read", label: "查看房间床位", category: "床位", description: "查看房间、床位和占用状态。" },
@@ -91,6 +93,8 @@ export const SYSTEM_ROLE_DEFINITIONS: SeedRole[] = [
"permission:read",
"audit:read",
"incident:read",
"care:read",
"care:manage",
"health:read",
"health:manage",
"facility:read",
@@ -111,6 +115,8 @@ export const SYSTEM_ROLE_DEFINITIONS: SeedRole[] = [
"account:read",
"role:read",
"audit:read",
"care:read",
"care:manage",
"health:read",
"health:manage",
"facility:read",
@@ -127,13 +133,13 @@ export const SYSTEM_ROLE_DEFINITIONS: SeedRole[] = [
key: "caregiver",
scope: "organization",
description: "照护人员,查看床位和维护老人照护信息。",
permissions: ["health:read", "health:manage", "facility:read", "admission:read", "elder:read", "elder:update"],
permissions: ["care:read", "care:manage", "health:read", "health:manage", "facility:read", "admission:read", "elder:read", "elder:update"],
},
{
key: "viewer",
scope: "organization",
description: "普通用户,只读查看老人、床位和入住信息。",
permissions: ["health:read", "facility:read", "admission:read", "elder:read"],
permissions: ["care:read", "health:read", "facility:read", "admission:read", "elder:read"],
},
{
key: "family",

View File

@@ -27,6 +27,9 @@ export const joinRequestStatusEnum = pgEnum("join_request_status", ["pending", "
export const vitalRecordSourceEnum = pgEnum("vital_record_source", ["manual", "device", "import"]);
export const chronicConditionStatusEnum = pgEnum("chronic_condition_status", ["active", "controlled", "resolved"]);
export const healthReviewStatusEnum = pgEnum("health_review_status", ["pending", "reviewed", "resolved"]);
export const careTaskTypeEnum = pgEnum("care_task_type", ["daily_care", "meal", "medication", "rehab", "inspection", "cleaning", "other"]);
export const careTaskPriorityEnum = pgEnum("care_task_priority", ["low", "normal", "high", "urgent"]);
export const careTaskStatusEnum = pgEnum("care_task_status", ["pending", "in_progress", "completed", "cancelled"]);
export const systemSettings = pgTable("system_settings", {
id: text("id").primaryKey().default("global"),
@@ -299,6 +302,27 @@ export const healthAnomalyReviews = pgTable("health_anomaly_reviews", {
reviewQueueLookup: index("health_anomaly_reviews_queue_lookup").on(table.organizationId, table.status, table.severity),
}));
export const careTasks = pgTable("care_tasks", {
id: uuid("id").primaryKey().defaultRandom(),
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
elderId: uuid("elder_id").references(() => elders.id, { onDelete: "set null" }),
title: text("title").notNull(),
careType: careTaskTypeEnum("care_type").notNull(),
priority: careTaskPriorityEnum("priority").notNull().default("normal"),
status: careTaskStatusEnum("status").notNull().default("pending"),
scheduledAt: timestamp("scheduled_at", { withTimezone: true }).notNull(),
assigneeLabel: text("assignee_label").notNull().default(""),
executionNotes: text("execution_notes").notNull().default(""),
completedAt: timestamp("completed_at", { withTimezone: true }),
createdByAccountId: uuid("created_by_account_id").references(() => accounts.id),
completedByAccountId: uuid("completed_by_account_id").references(() => accounts.id),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
}, (table) => ({
queueLookup: index("care_tasks_queue_lookup").on(table.organizationId, table.status, table.priority),
scheduleLookup: index("care_tasks_schedule_lookup").on(table.organizationId, table.scheduledAt),
}));
export const admissions = pgTable("admissions", {
id: uuid("id").primaryKey().defaultRandom(),
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),