581 lines
34 KiB
TypeScript
581 lines
34 KiB
TypeScript
import { sql } from "drizzle-orm";
|
|
import {
|
|
boolean,
|
|
index,
|
|
integer,
|
|
jsonb,
|
|
pgEnum,
|
|
pgTable,
|
|
primaryKey,
|
|
text,
|
|
timestamp,
|
|
uniqueIndex,
|
|
uuid,
|
|
} from "drizzle-orm/pg-core";
|
|
|
|
export const accountStatusEnum = pgEnum("account_status", ["active", "disabled", "pending"]);
|
|
export const organizationStatusEnum = pgEnum("organization_status", ["active", "disabled"]);
|
|
export const roleScopeEnum = pgEnum("role_scope", ["platform", "organization"]);
|
|
export const membershipStatusEnum = pgEnum("membership_status", ["active", "disabled", "pending"]);
|
|
export const bedStatusEnum = pgEnum("bed_status", ["available", "occupied", "maintenance", "disabled"]);
|
|
export const elderStatusEnum = pgEnum("elder_status", ["active", "pending", "discharged"]);
|
|
export const careLevelEnum = pgEnum("care_level", ["self-care", "semi-assisted", "assisted", "intensive"]);
|
|
export const genderEnum = pgEnum("gender", ["male", "female", "other"]);
|
|
export const admissionStatusEnum = pgEnum("admission_status", ["active", "transferred", "discharged"]);
|
|
export const auditResultEnum = pgEnum("audit_result", ["success", "denied", "failure"]);
|
|
export const incidentStatusEnum = pgEnum("incident_status", ["open", "acknowledged", "resolved", "closed"]);
|
|
export const incidentSeverityEnum = pgEnum("incident_severity", ["info", "warning", "critical"]);
|
|
export const joinRequestStatusEnum = pgEnum("join_request_status", ["pending", "approved", "rejected"]);
|
|
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 deviceAssetStatusEnum = pgEnum("device_asset_status", ["active", "maintenance", "disabled", "retired"]);
|
|
export const maintenanceTicketStatusEnum = pgEnum("maintenance_ticket_status", ["open", "assigned", "resolved", "closed", "cancelled"]);
|
|
export const maintenanceTicketPriorityEnum = pgEnum("maintenance_ticket_priority", ["low", "normal", "high", "urgent"]);
|
|
export const noticeStatusEnum = pgEnum("notice_status", ["draft", "published", "retracted"]);
|
|
export const alertRuleTypeEnum = pgEnum("alert_rule_type", ["health", "care", "device", "incident", "admission", "other"]);
|
|
export const alertRuleStatusEnum = pgEnum("alert_rule_status", ["enabled", "disabled"]);
|
|
export const alertTriggerStatusEnum = pgEnum("alert_trigger_status", ["open", "acknowledged", "resolved", "closed"]);
|
|
export const familyContactStatusEnum = pgEnum("family_contact_status", ["active", "suspended", "archived"]);
|
|
export const familyVisitStatusEnum = pgEnum("family_visit_status", ["requested", "approved", "completed", "cancelled"]);
|
|
export const familyFeedbackTypeEnum = pgEnum("family_feedback_type", ["service", "care", "meal", "environment", "other"]);
|
|
export const familyFeedbackStatusEnum = pgEnum("family_feedback_status", ["open", "in_progress", "resolved", "closed"]);
|
|
export const aiKnowledgeScopeEnum = pgEnum("ai_knowledge_scope", ["platform", "organization"]);
|
|
export const aiKnowledgeStatusEnum = pgEnum("ai_knowledge_status", ["enabled", "disabled"]);
|
|
export const elderAiAnalysisStatusEnum = pgEnum("elder_ai_analysis_status", ["completed", "failed"]);
|
|
|
|
export const systemSettings = pgTable("system_settings", {
|
|
id: text("id").primaryKey().default("global"),
|
|
registrationEnabled: boolean("registration_enabled").notNull().default(true),
|
|
oidcEnabled: boolean("oidc_enabled").notNull().default(false),
|
|
oidcIssuerUrl: text("oidc_issuer_url").notNull().default(""),
|
|
oidcClientId: text("oidc_client_id").notNull().default(""),
|
|
oidcClientSecret: text("oidc_client_secret").notNull().default(""),
|
|
oidcScopes: text("oidc_scopes").notNull().default("openid profile email"),
|
|
oidcRedirectUri: text("oidc_redirect_uri").notNull().default(""),
|
|
oidcAvatarClaim: text("oidc_avatar_claim").notNull().default("picture"),
|
|
oidcAutoProvision: boolean("oidc_auto_provision").notNull().default(false),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
});
|
|
|
|
export const organizations = pgTable("organizations", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
name: text("name").notNull(),
|
|
slug: text("slug").notNull(),
|
|
status: organizationStatusEnum("status").notNull().default("active"),
|
|
registrationEnabled: boolean("registration_enabled").notNull().default(true),
|
|
oidcEnabled: boolean("oidc_enabled").notNull().default(false),
|
|
oidcIssuerUrl: text("oidc_issuer_url").notNull().default(""),
|
|
oidcClientId: text("oidc_client_id").notNull().default(""),
|
|
oidcClientSecret: text("oidc_client_secret").notNull().default(""),
|
|
oidcScopes: text("oidc_scopes").notNull().default("openid profile email"),
|
|
oidcRedirectUri: text("oidc_redirect_uri").notNull().default(""),
|
|
oidcAvatarClaim: text("oidc_avatar_claim").notNull().default("picture"),
|
|
oidcAutoProvision: boolean("oidc_auto_provision").notNull().default(false),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
slugUnique: uniqueIndex("organizations_slug_unique").on(table.slug),
|
|
}));
|
|
|
|
export const permissions = pgTable("permissions", {
|
|
id: text("id").primaryKey(),
|
|
label: text("label").notNull(),
|
|
category: text("category").notNull(),
|
|
description: text("description").notNull(),
|
|
isEnabled: boolean("is_enabled").notNull().default(true),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
});
|
|
|
|
export const roles = pgTable("roles", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
key: text("key").notNull(),
|
|
organizationId: uuid("organization_id").references(() => organizations.id, { onDelete: "cascade" }),
|
|
scope: roleScopeEnum("scope").notNull(),
|
|
label: text("label").notNull(),
|
|
description: text("description").notNull(),
|
|
isSystem: boolean("is_system").notNull().default(false),
|
|
isEnabled: boolean("is_enabled").notNull().default(true),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
roleKeyUnique: uniqueIndex("roles_key_organization_unique").on(table.key, table.organizationId),
|
|
}));
|
|
|
|
export const rolePermissions = pgTable("role_permissions", {
|
|
roleId: uuid("role_id").notNull().references(() => roles.id, { onDelete: "cascade" }),
|
|
permissionId: text("permission_id").notNull().references(() => permissions.id, { onDelete: "cascade" }),
|
|
}, (table) => ({
|
|
pk: primaryKey({ columns: [table.roleId, table.permissionId] }),
|
|
}));
|
|
|
|
export const accounts = pgTable("accounts", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
platformRoleId: uuid("platform_role_id").references(() => roles.id),
|
|
name: text("name").notNull(),
|
|
email: text("email").notNull(),
|
|
avatarUrl: text("avatar_url").notNull().default(""),
|
|
passwordHash: text("password_hash").notNull(),
|
|
passwordSalt: text("password_salt").notNull(),
|
|
status: accountStatusEnum("status").notNull().default("active"),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
emailUnique: uniqueIndex("accounts_email_unique").on(table.email),
|
|
}));
|
|
|
|
export const memberships = pgTable("memberships", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
accountId: uuid("account_id").notNull().references(() => accounts.id, { onDelete: "cascade" }),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
roleId: uuid("role_id").notNull().references(() => roles.id),
|
|
status: membershipStatusEnum("status").notNull().default("active"),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
accountOrganizationUnique: uniqueIndex("memberships_account_organization_unique").on(
|
|
table.accountId,
|
|
table.organizationId,
|
|
),
|
|
}));
|
|
|
|
export const sessions = pgTable("sessions", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
accountId: uuid("account_id").notNull().references(() => accounts.id, { onDelete: "cascade" }),
|
|
activeOrganizationId: uuid("active_organization_id").references(() => organizations.id),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
});
|
|
|
|
export const joinRequests = pgTable("join_requests", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
accountId: uuid("account_id").notNull().references(() => accounts.id, { onDelete: "cascade" }),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
status: joinRequestStatusEnum("status").notNull().default("pending"),
|
|
reason: text("reason").notNull().default(""),
|
|
reviewedByAccountId: uuid("reviewed_by_account_id").references(() => accounts.id),
|
|
reviewedAt: timestamp("reviewed_at", { withTimezone: true }),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
});
|
|
|
|
export const organizationInvitations = pgTable("organization_invitations", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
roleId: uuid("role_id").notNull().references(() => roles.id),
|
|
email: text("email").notNull().default(""),
|
|
token: text("token").notNull(),
|
|
status: text("status").notNull().default("active"),
|
|
createdByAccountId: uuid("created_by_account_id").references(() => accounts.id),
|
|
acceptedByAccountId: uuid("accepted_by_account_id").references(() => accounts.id),
|
|
acceptedAt: timestamp("accepted_at", { withTimezone: true }),
|
|
expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(),
|
|
maxUses: integer("max_uses").notNull().default(1),
|
|
usedCount: integer("used_count").notNull().default(0),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
tokenUnique: uniqueIndex("organization_invitations_token_unique").on(table.token),
|
|
}));
|
|
|
|
export const campuses = pgTable("campuses", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
name: text("name").notNull(),
|
|
address: text("address").notNull().default(""),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
});
|
|
|
|
export const buildings = pgTable("buildings", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
campusId: uuid("campus_id").notNull().references(() => campuses.id, { onDelete: "cascade" }),
|
|
name: text("name").notNull(),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
});
|
|
|
|
export const floors = pgTable("floors", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
buildingId: uuid("building_id").notNull().references(() => buildings.id, { onDelete: "cascade" }),
|
|
name: text("name").notNull(),
|
|
level: integer("level").notNull(),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
});
|
|
|
|
export const rooms = pgTable("rooms", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
floorId: uuid("floor_id").notNull().references(() => floors.id, { onDelete: "cascade" }),
|
|
name: text("name").notNull(),
|
|
code: text("code").notNull(),
|
|
capacity: integer("capacity").notNull().default(1),
|
|
status: organizationStatusEnum("status").notNull().default("active"),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
roomCodeUnique: uniqueIndex("rooms_code_organization_unique").on(table.organizationId, table.code),
|
|
}));
|
|
|
|
export const beds = pgTable("beds", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
roomId: uuid("room_id").notNull().references(() => rooms.id, { onDelete: "cascade" }),
|
|
code: text("code").notNull(),
|
|
status: bedStatusEnum("status").notNull().default("available"),
|
|
notes: text("notes").notNull().default(""),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
bedCodeUnique: uniqueIndex("beds_code_room_unique").on(table.roomId, table.code),
|
|
}));
|
|
|
|
export const elders = pgTable("elders", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
name: text("name").notNull(),
|
|
gender: genderEnum("gender").notNull(),
|
|
age: integer("age").notNull(),
|
|
careLevel: careLevelEnum("care_level").notNull(),
|
|
status: elderStatusEnum("status").notNull().default("pending"),
|
|
primaryContact: text("primary_contact").notNull(),
|
|
phone: text("phone").notNull(),
|
|
medicalNotes: text("medical_notes").notNull().default(""),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
});
|
|
|
|
export const healthProfiles = pgTable("health_profiles", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
elderId: uuid("elder_id").notNull().references(() => elders.id, { onDelete: "cascade" }),
|
|
allergyNotes: text("allergy_notes").notNull().default(""),
|
|
medicalHistory: text("medical_history").notNull().default(""),
|
|
medicationNotes: text("medication_notes").notNull().default(""),
|
|
careRestrictions: text("care_restrictions").notNull().default(""),
|
|
emergencyNotes: text("emergency_notes").notNull().default(""),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
elderUnique: uniqueIndex("health_profiles_organization_elder_unique").on(table.organizationId, table.elderId),
|
|
}));
|
|
|
|
export const vitalRecords = pgTable("vital_records", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
elderId: uuid("elder_id").notNull().references(() => elders.id, { onDelete: "cascade" }),
|
|
recordedAt: timestamp("recorded_at", { withTimezone: true }).notNull(),
|
|
source: vitalRecordSourceEnum("source").notNull().default("manual"),
|
|
systolicBp: integer("systolic_bp"),
|
|
diastolicBp: integer("diastolic_bp"),
|
|
heartRate: integer("heart_rate"),
|
|
temperatureTenths: integer("temperature_tenths"),
|
|
spo2: integer("spo2"),
|
|
bloodGlucoseTenths: integer("blood_glucose_tenths"),
|
|
weightTenths: integer("weight_tenths"),
|
|
notes: text("notes").notNull().default(""),
|
|
createdByAccountId: uuid("created_by_account_id").references(() => accounts.id),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
recentLookup: index("vital_records_recent_lookup").on(table.organizationId, table.elderId, table.recordedAt),
|
|
}));
|
|
|
|
export const chronicConditions = pgTable("chronic_conditions", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
elderId: uuid("elder_id").notNull().references(() => elders.id, { onDelete: "cascade" }),
|
|
name: text("name").notNull(),
|
|
status: chronicConditionStatusEnum("status").notNull().default("active"),
|
|
diagnosedAt: timestamp("diagnosed_at", { withTimezone: true }),
|
|
treatmentNotes: text("treatment_notes").notNull().default(""),
|
|
followUpNotes: text("follow_up_notes").notNull().default(""),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
elderStatusLookup: index("chronic_conditions_elder_status_lookup").on(table.organizationId, table.elderId, table.status),
|
|
}));
|
|
|
|
export const healthAnomalyReviews = pgTable("health_anomaly_reviews", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
elderId: uuid("elder_id").notNull().references(() => elders.id, { onDelete: "cascade" }),
|
|
vitalRecordId: uuid("vital_record_id").references(() => vitalRecords.id, { onDelete: "set null" }),
|
|
severity: incidentSeverityEnum("severity").notNull(),
|
|
status: healthReviewStatusEnum("status").notNull().default("pending"),
|
|
title: text("title").notNull(),
|
|
description: text("description").notNull(),
|
|
reviewedByAccountId: uuid("reviewed_by_account_id").references(() => accounts.id),
|
|
reviewedAt: timestamp("reviewed_at", { withTimezone: true }),
|
|
resolutionNotes: text("resolution_notes").notNull().default(""),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
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 deviceAssets = pgTable("device_assets", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
name: text("name").notNull(),
|
|
code: text("code").notNull(),
|
|
category: text("category").notNull().default(""),
|
|
location: text("location").notNull().default(""),
|
|
status: deviceAssetStatusEnum("status").notNull().default("active"),
|
|
lastInspectedAt: timestamp("last_inspected_at", { withTimezone: true }),
|
|
notes: text("notes").notNull().default(""),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
codeUnique: uniqueIndex("device_assets_code_organization_unique").on(table.organizationId, table.code),
|
|
statusLookup: index("device_assets_status_lookup").on(table.organizationId, table.status),
|
|
}));
|
|
|
|
export const maintenanceTickets = pgTable("maintenance_tickets", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
deviceId: uuid("device_id").references(() => deviceAssets.id, { onDelete: "set null" }),
|
|
title: text("title").notNull(),
|
|
description: text("description").notNull().default(""),
|
|
priority: maintenanceTicketPriorityEnum("priority").notNull().default("normal"),
|
|
status: maintenanceTicketStatusEnum("status").notNull().default("open"),
|
|
assigneeLabel: text("assignee_label").notNull().default(""),
|
|
resolutionNotes: text("resolution_notes").notNull().default(""),
|
|
resolvedAt: timestamp("resolved_at", { withTimezone: true }),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
queueLookup: index("maintenance_tickets_queue_lookup").on(table.organizationId, table.status, table.priority),
|
|
deviceLookup: index("maintenance_tickets_device_lookup").on(table.organizationId, table.deviceId),
|
|
}));
|
|
|
|
export const notices = pgTable("notices", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
title: text("title").notNull(),
|
|
content: text("content").notNull().default(""),
|
|
audience: text("audience").notNull().default("全体员工"),
|
|
status: noticeStatusEnum("status").notNull().default("draft"),
|
|
publishedAt: timestamp("published_at", { withTimezone: true }),
|
|
createdByAccountId: uuid("created_by_account_id").references(() => accounts.id),
|
|
updatedByAccountId: uuid("updated_by_account_id").references(() => accounts.id),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
statusLookup: index("notices_status_lookup").on(table.organizationId, table.status),
|
|
}));
|
|
|
|
export const noticeReadReceipts = pgTable("notice_read_receipts", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
noticeId: uuid("notice_id").notNull().references(() => notices.id, { onDelete: "cascade" }),
|
|
accountId: uuid("account_id").notNull().references(() => accounts.id, { onDelete: "cascade" }),
|
|
readAt: timestamp("read_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
accountNoticeUnique: uniqueIndex("notice_read_receipts_account_notice_unique").on(table.noticeId, table.accountId),
|
|
noticeLookup: index("notice_read_receipts_notice_lookup").on(table.organizationId, table.noticeId),
|
|
}));
|
|
|
|
export const alertRules = pgTable("alert_rules", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
name: text("name").notNull(),
|
|
ruleType: alertRuleTypeEnum("rule_type").notNull().default("other"),
|
|
severity: incidentSeverityEnum("severity").notNull().default("warning"),
|
|
status: alertRuleStatusEnum("status").notNull().default("enabled"),
|
|
conditionSummary: text("condition_summary").notNull().default(""),
|
|
suggestion: text("suggestion").notNull().default(""),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
statusLookup: index("alert_rules_status_lookup").on(table.organizationId, table.status, table.ruleType),
|
|
}));
|
|
|
|
export const alertTriggers = pgTable("alert_triggers", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
ruleId: uuid("rule_id").references(() => alertRules.id, { onDelete: "set null" }),
|
|
elderId: uuid("elder_id").references(() => elders.id, { onDelete: "set null" }),
|
|
title: text("title").notNull(),
|
|
description: text("description").notNull().default(""),
|
|
status: alertTriggerStatusEnum("status").notNull().default("open"),
|
|
source: text("source").notNull().default(""),
|
|
handledByAccountId: uuid("handled_by_account_id").references(() => accounts.id),
|
|
handledAt: timestamp("handled_at", { withTimezone: true }),
|
|
handlingNotes: text("handling_notes").notNull().default(""),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
queueLookup: index("alert_triggers_queue_lookup").on(table.organizationId, table.status),
|
|
ruleLookup: index("alert_triggers_rule_lookup").on(table.organizationId, table.ruleId),
|
|
}));
|
|
|
|
export const familyContacts = pgTable("family_contacts", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
elderId: uuid("elder_id").notNull().references(() => elders.id, { onDelete: "cascade" }),
|
|
name: text("name").notNull(),
|
|
relationship: text("relationship").notNull(),
|
|
phone: text("phone").notNull(),
|
|
status: familyContactStatusEnum("status").notNull().default("active"),
|
|
notes: text("notes").notNull().default(""),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
elderLookup: index("family_contacts_elder_lookup").on(table.organizationId, table.elderId, table.status),
|
|
}));
|
|
|
|
export const familyVisitAppointments = pgTable("family_visit_appointments", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
elderId: uuid("elder_id").notNull().references(() => elders.id, { onDelete: "cascade" }),
|
|
contactId: uuid("contact_id").references(() => familyContacts.id, { onDelete: "set null" }),
|
|
scheduledAt: timestamp("scheduled_at", { withTimezone: true }).notNull(),
|
|
status: familyVisitStatusEnum("status").notNull().default("requested"),
|
|
notes: text("notes").notNull().default(""),
|
|
handledByAccountId: uuid("handled_by_account_id").references(() => accounts.id),
|
|
handledAt: timestamp("handled_at", { withTimezone: true }),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
scheduleLookup: index("family_visit_appointments_schedule_lookup").on(table.organizationId, table.status, table.scheduledAt),
|
|
}));
|
|
|
|
export const familyFeedback = pgTable("family_feedback", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
elderId: uuid("elder_id").notNull().references(() => elders.id, { onDelete: "cascade" }),
|
|
contactId: uuid("contact_id").references(() => familyContacts.id, { onDelete: "set null" }),
|
|
feedbackType: familyFeedbackTypeEnum("feedback_type").notNull().default("other"),
|
|
content: text("content").notNull(),
|
|
status: familyFeedbackStatusEnum("status").notNull().default("open"),
|
|
responseNotes: text("response_notes").notNull().default(""),
|
|
handledByAccountId: uuid("handled_by_account_id").references(() => accounts.id),
|
|
handledAt: timestamp("handled_at", { withTimezone: true }),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
queueLookup: index("family_feedback_queue_lookup").on(table.organizationId, table.status, table.feedbackType),
|
|
}));
|
|
|
|
export const admissions = pgTable("admissions", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
elderId: uuid("elder_id").notNull().references(() => elders.id, { onDelete: "cascade" }),
|
|
bedId: uuid("bed_id").notNull().references(() => beds.id),
|
|
status: admissionStatusEnum("status").notNull().default("active"),
|
|
admittedAt: timestamp("admitted_at", { withTimezone: true }).notNull().defaultNow(),
|
|
dischargedAt: timestamp("discharged_at", { withTimezone: true }),
|
|
notes: text("notes").notNull().default(""),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
});
|
|
|
|
export const auditLogs = pgTable("audit_logs", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").references(() => organizations.id, { onDelete: "cascade" }),
|
|
timestamp: timestamp("timestamp", { withTimezone: true }).notNull().defaultNow(),
|
|
actorAccountId: uuid("actor_account_id").references(() => accounts.id),
|
|
actorEmail: text("actor_email"),
|
|
action: text("action").notNull(),
|
|
targetType: text("target_type").notNull(),
|
|
targetId: text("target_id"),
|
|
result: auditResultEnum("result").notNull(),
|
|
reason: text("reason").notNull(),
|
|
});
|
|
|
|
export const systemIncidents = pgTable("system_incidents", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").references(() => organizations.id, { onDelete: "cascade" }),
|
|
severity: incidentSeverityEnum("severity").notNull(),
|
|
status: incidentStatusEnum("status").notNull().default("open"),
|
|
title: text("title").notNull(),
|
|
description: text("description").notNull(),
|
|
source: text("source").notNull(),
|
|
acknowledgedByAccountId: uuid("acknowledged_by_account_id").references(() => accounts.id),
|
|
acknowledgedAt: timestamp("acknowledged_at", { withTimezone: true }),
|
|
resolvedByAccountId: uuid("resolved_by_account_id").references(() => accounts.id),
|
|
resolvedAt: timestamp("resolved_at", { withTimezone: true }),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
});
|
|
|
|
export const aiKnowledgeEntries = pgTable("ai_knowledge_entries", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
scope: aiKnowledgeScopeEnum("scope").notNull(),
|
|
organizationId: uuid("organization_id").references(() => organizations.id, { onDelete: "cascade" }),
|
|
title: text("title").notNull(),
|
|
category: text("category").notNull().default(""),
|
|
tags: text("tags").notNull().default(""),
|
|
body: text("body").notNull(),
|
|
status: aiKnowledgeStatusEnum("status").notNull().default("enabled"),
|
|
createdByAccountId: uuid("created_by_account_id").references(() => accounts.id, { onDelete: "set null" }),
|
|
updatedByAccountId: uuid("updated_by_account_id").references(() => accounts.id, { onDelete: "set null" }),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
scopeLookup: index("ai_knowledge_entries_scope_lookup").on(table.scope, table.status),
|
|
organizationLookup: index("ai_knowledge_entries_organization_lookup").on(table.organizationId, table.status),
|
|
}));
|
|
|
|
export const aiKnowledgeChunks = pgTable("ai_knowledge_chunks", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
entryId: uuid("entry_id").notNull().references(() => aiKnowledgeEntries.id, { onDelete: "cascade" }),
|
|
organizationId: uuid("organization_id").references(() => organizations.id, { onDelete: "cascade" }),
|
|
scope: aiKnowledgeScopeEnum("scope").notNull(),
|
|
chunkIndex: integer("chunk_index").notNull(),
|
|
content: text("content").notNull(),
|
|
embedding: jsonb("embedding").$type<number[]>().notNull().default(sql`'[]'::jsonb`),
|
|
sourceTitle: text("source_title").notNull(),
|
|
sourceCategory: text("source_category").notNull().default(""),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
entryLookup: index("ai_knowledge_chunks_entry_lookup").on(table.entryId),
|
|
scopeLookup: index("ai_knowledge_chunks_scope_lookup").on(table.scope, table.organizationId),
|
|
}));
|
|
|
|
export const elderAiAnalyses = pgTable("elder_ai_analyses", {
|
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
|
|
elderId: uuid("elder_id").notNull().references(() => elders.id, { onDelete: "cascade" }),
|
|
actorAccountId: uuid("actor_account_id").references(() => accounts.id, { onDelete: "set null" }),
|
|
status: elderAiAnalysisStatusEnum("status").notNull(),
|
|
dataScopes: jsonb("data_scopes").$type<string[]>().notNull().default(sql`'[]'::jsonb`),
|
|
resultJson: jsonb("result_json"),
|
|
citationsJson: jsonb("citations_json").notNull().default(sql`'[]'::jsonb`),
|
|
modelSummaryJson: jsonb("model_summary_json").notNull().default(sql`'{}'::jsonb`),
|
|
errorCategory: text("error_category").notNull().default(""),
|
|
errorReason: text("error_reason").notNull().default(""),
|
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
}, (table) => ({
|
|
elderLookup: index("elder_ai_analyses_elder_lookup").on(table.organizationId, table.elderId, table.createdAt),
|
|
statusLookup: index("elder_ai_analyses_status_lookup").on(table.organizationId, table.status),
|
|
}));
|