Files
akmon/doc_eldercare/eldercare_system_database_postgresql.sql
2026-01-20 08:04:15 +08:00

1239 lines
54 KiB
PL/PgSQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- ================================================
-- 养老管理系统数据库结构 (PostgreSQL版本)
-- ElderCare Management System Database Schema
-- PostgreSQL 13+ 兼容版本
-- ================================================
-- 确保启用必要的PostgreSQL扩展
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- ------------------------------------------------
-- 1. 权限角色系统 (扩展现有)
-- ------------------------------------------------
-- 基于现有角色系统,增加养老系统特有角色
INSERT INTO public.ak_roles (name, description, level, is_system) VALUES
('elder', '入住老人', 5, true),
('family', '家属用户', 15, true),
('caregiver', '护理员', 25, true),
('nurse', '护士', 35, true),
('doctor', '医生', 45, true),
('facility_admin', '机构管理员', 55, true),
('system_admin', '系统管理员', 100, true)
ON CONFLICT (name) DO NOTHING;
-- 添加养老系统权限
INSERT INTO public.ak_permissions (code, name, resource_type, action, description) VALUES
-- 老人权限
('elder.profile.read', '查看个人档案', 'profile', 'read', '查看自己的档案信息'),
('elder.health.read', '查看健康记录', 'health', 'read', '查看自己的健康数据'),
-- 家属权限
('family.elder.read', '查看老人信息', 'elder', 'read', '查看关联老人的信息'),
('family.visit.create', '预约探访', 'visit', 'create', '预约探访老人'),
-- 护理员权限
('caregiver.care.manage', '护理管理', 'care', 'manage', '执行日常护理任务'),
('caregiver.elder.read', '查看老人信息', 'elder', 'read', '查看所有老人信息'),
-- 护士权限
('nurse.health.manage', '健康管理', 'health', 'manage', '管理老人健康记录'),
('nurse.medication.manage', '用药管理', 'medication', 'manage', '管理用药计划'),
-- 医生权限
('doctor.diagnosis.manage', '诊疗管理', 'diagnosis', 'manage', '管理医疗诊断'),
('doctor.prescription.manage', '处方管理', 'prescription', 'manage', '开具处方')
ON CONFLICT (code) DO NOTHING;
-- 创建获取当前用户ID的函数替代auth.uid()
CREATE OR REPLACE FUNCTION get_current_user_id()
RETURNS uuid AS $$
BEGIN
-- 这里可以根据实际的认证系统进行调整
-- 示例从session变量获取用户ID
RETURN COALESCE(
(current_setting('app.current_user_id', true))::uuid,
'00000000-0000-0000-0000-000000000000'::uuid
);
EXCEPTION
WHEN OTHERS THEN
RETURN '00000000-0000-0000-0000-000000000000'::uuid;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- ------------------------------------------------
-- 2. 多租户架构 (改造复用)
-- ------------------------------------------------
-- 地区管理 → 机构区域管理 (直接使用 ak_regions 表)
-- 学校管理 → 养老机构管理
-- 基于 ak_schools 表结构
CREATE TABLE IF NOT EXISTS public.ec_facilities (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(128) NOT NULL, -- 机构名称
region_id uuid REFERENCES public.ak_regions(id), -- 所属地区
type VARCHAR(32) CHECK (type IN ('nursing_home', 'assisted_living', 'day_care')), -- 机构类型
license_number VARCHAR(64), -- 许可证号
contact_phone VARCHAR(32), -- 联系电话
address TEXT, -- 详细地址
capacity INT CHECK (capacity > 0), -- 床位总数
current_occupancy INT DEFAULT 0 CHECK (current_occupancy >= 0), -- 当前入住人数
admin_id uuid REFERENCES public.ak_users(id), -- 管理员
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- 班级管理 → 护理单元管理
-- 基于 ak_classes 表结构
CREATE TABLE IF NOT EXISTS public.ec_care_units (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
facility_id uuid NOT NULL REFERENCES public.ec_facilities(id) ON DELETE CASCADE,
name VARCHAR(32) NOT NULL, -- 护理单元名称(如A栋1楼、康复区)
unit_type VARCHAR(32) CHECK (unit_type IN ('general', 'dementia', 'medical', 'rehab')), -- 单元类型
bed_capacity INT CHECK (bed_capacity > 0), -- 床位数
current_occupancy INT DEFAULT 0 CHECK (current_occupancy >= 0), -- 当前入住数
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- ------------------------------------------------
-- 3. 设备管理系统 (改造复用)
-- ------------------------------------------------
-- 基于 ak_devices 表,扩展为健康监测设备
CREATE TABLE IF NOT EXISTS public.ec_devices (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
facility_id uuid REFERENCES public.ec_facilities(id),
elder_id uuid REFERENCES public.ak_users(id), -- 关联老人
device_type VARCHAR(32) NOT NULL CHECK (device_type IN ('blood_pressure', 'glucose', 'heart_rate', 'fall_detector')), -- 设备类型
device_name VARCHAR(64), -- 设备名称
device_mac VARCHAR(64), -- 设备MAC
location VARCHAR(128), -- 设备位置
bind_time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
status VARCHAR(16) DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'maintenance')), -- 状态
last_sync TIMESTAMP WITH TIME ZONE, -- 最后同步时间
extra JSONB, -- 设备特有配置
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- ------------------------------------------------
-- 4. 老人档案管理
-- ------------------------------------------------
-- 老人基本档案
CREATE TABLE IF NOT EXISTS public.ec_elders (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid UNIQUE REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 关联用户表
facility_id uuid REFERENCES public.ec_facilities(id), -- 所属机构
care_unit_id uuid REFERENCES public.ec_care_units(id), -- 护理单元
elder_code VARCHAR(32) UNIQUE, -- 老人编号
name VARCHAR(64) NOT NULL, -- 姓名
id_card VARCHAR(32), -- 身份证号
gender VARCHAR(16) CHECK (gender IN ('male', 'female', 'other')), -- 性别
birthday DATE CHECK (birthday <= CURRENT_DATE), -- 出生日期
nationality VARCHAR(32), -- 民族
religion VARCHAR(32), -- 宗教信仰
marital_status VARCHAR(16) CHECK (marital_status IN ('single', 'married', 'divorced', 'widowed')), -- 婚姻状况
education VARCHAR(32), -- 文化程度
occupation VARCHAR(64), -- 职业
admission_date DATE DEFAULT CURRENT_DATE, -- 入住日期
care_level VARCHAR(16) CHECK (care_level IN ('self_care', 'assisted', 'full_care', 'dementia')), -- 护理等级
room_number VARCHAR(16), -- 房间号
bed_number VARCHAR(16), -- 床位号
payment_method VARCHAR(16) CHECK (payment_method IN ('self_pay', 'insurance', 'government')), -- 付费方式
monthly_fee DECIMAL(10,2) CHECK (monthly_fee >= 0), -- 月费用
deposit DECIMAL(10,2) CHECK (deposit >= 0), -- 押金
status VARCHAR(16) DEFAULT 'active' CHECK (status IN ('active', 'discharged', 'deceased')), -- 状态
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- 家属联系人
CREATE TABLE IF NOT EXISTS public.ec_family_contacts (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
elder_id uuid NOT NULL REFERENCES public.ec_elders(id) ON DELETE CASCADE,
user_id uuid REFERENCES public.ak_users(id), -- 关联家属用户账号
name VARCHAR(64) NOT NULL, -- 联系人姓名
relationship VARCHAR(32) CHECK (relationship IN ('son', 'daughter', 'spouse', 'sibling', 'other')), -- 关系
phone VARCHAR(32), -- 电话
email VARCHAR(128), -- 邮箱
address TEXT, -- 地址
is_emergency_contact BOOLEAN DEFAULT false, -- 是否紧急联系人
is_primary BOOLEAN DEFAULT false, -- 是否主要联系人
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- ------------------------------------------------
-- 5. 健康管理
-- ------------------------------------------------
-- 健康档案
CREATE TABLE IF NOT EXISTS public.ec_health_records (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
elder_id uuid NOT NULL REFERENCES public.ec_elders(id) ON DELETE CASCADE,
record_type VARCHAR(32) CHECK (record_type IN ('admission', 'routine', 'emergency', 'discharge')), -- 记录类型
record_date DATE DEFAULT CURRENT_DATE,
height_cm INT CHECK (height_cm > 0 AND height_cm < 300), -- 身高
weight_kg DECIMAL(5,2) CHECK (weight_kg > 0 AND weight_kg < 500), -- 体重
blood_pressure_systolic INT CHECK (blood_pressure_systolic > 0 AND blood_pressure_systolic < 300), -- 收缩压
blood_pressure_diastolic INT CHECK (blood_pressure_diastolic > 0 AND blood_pressure_diastolic < 200), -- 舒张压
heart_rate INT CHECK (heart_rate > 0 AND heart_rate < 300), -- 心率
temperature DECIMAL(4,1) CHECK (temperature > 30 AND temperature < 45), -- 体温
blood_sugar DECIMAL(5,2) CHECK (blood_sugar > 0 AND blood_sugar < 50), -- 血糖
medical_history TEXT, -- 病史
current_medications TEXT, -- 当前用药
allergies TEXT, -- 过敏史
mental_status TEXT, -- 心理状态
mobility_level VARCHAR(16) CHECK (mobility_level IN ('independent', 'assisted', 'wheelchair', 'bedridden')), -- 行动能力
cognitive_level VARCHAR(16) CHECK (cognitive_level IN ('normal', 'mild', 'moderate', 'severe')), -- 认知能力
recorded_by uuid REFERENCES public.ak_users(id), -- 记录人
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- 基于现有 ak_biometric_data 表结构扩展
-- 生命体征监测数据
CREATE TABLE IF NOT EXISTS public.ec_vital_signs (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
elder_id uuid NOT NULL REFERENCES public.ec_elders(id) ON DELETE CASCADE,
device_id uuid REFERENCES public.ec_devices(id), -- 监测设备
vital_type VARCHAR(32) CHECK (vital_type IN ('blood_pressure', 'heart_rate', 'temperature', 'oxygen', 'glucose')), -- 体征类型
systolic_pressure INT CHECK (systolic_pressure > 0 AND systolic_pressure < 300), -- 收缩压
diastolic_pressure INT CHECK (diastolic_pressure > 0 AND diastolic_pressure < 200), -- 舒张压
heart_rate INT CHECK (heart_rate > 0 AND heart_rate < 300), -- 心率
temperature DECIMAL(4,1) CHECK (temperature > 30 AND temperature < 45), -- 体温
oxygen_saturation INT CHECK (oxygen_saturation > 0 AND oxygen_saturation <= 100), -- 血氧饱和度
glucose_level DECIMAL(5,2) CHECK (glucose_level > 0 AND glucose_level < 50), -- 血糖
measured_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, -- 测量时间
measured_by uuid REFERENCES public.ak_users(id), -- 测量人
notes TEXT, -- 备注
is_abnormal BOOLEAN DEFAULT false, -- 是否异常
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- ------------------------------------------------
-- 6. 护理管理
-- ------------------------------------------------
-- 护理计划 (基于 ak_training_plans 改造)
CREATE TABLE IF NOT EXISTS public.ec_care_plans (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
elder_id uuid NOT NULL REFERENCES public.ec_elders(id) ON DELETE CASCADE,
plan_name VARCHAR(128) NOT NULL, -- 计划名称
plan_type VARCHAR(32) CHECK (plan_type IN ('daily', 'medical', 'rehabilitation', 'nutrition')), -- 计划类型
description TEXT, -- 描述
start_date DATE DEFAULT CURRENT_DATE, -- 开始日期
end_date DATE, -- 结束日期
frequency VARCHAR(32) CHECK (frequency IN ('daily', 'weekly', 'monthly')), -- 频率
assigned_caregiver uuid REFERENCES public.ak_users(id), -- 负责护理员
created_by uuid REFERENCES public.ak_users(id), -- 创建人
status VARCHAR(16) DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'completed')), -- 状态
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT check_end_date CHECK (end_date IS NULL OR end_date >= start_date)
);
-- 护理任务 (基于 ak_assignments 改造)
CREATE TABLE IF NOT EXISTS public.ec_care_tasks (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
elder_id uuid NOT NULL REFERENCES public.ec_elders(id) ON DELETE CASCADE,
care_plan_id uuid REFERENCES public.ec_care_plans(id),
task_name VARCHAR(128) NOT NULL, -- 任务名称
task_type VARCHAR(32) CHECK (task_type IN ('medication', 'hygiene', 'mobility', 'nutrition', 'social')), -- 任务类型
description TEXT, -- 任务描述
scheduled_time TIMESTAMP WITH TIME ZONE, -- 计划执行时间
assigned_to uuid REFERENCES public.ak_users(id), -- 分配给
priority VARCHAR(16) DEFAULT 'normal' CHECK (priority IN ('low', 'normal', 'high', 'urgent')), -- 优先级
estimated_duration INT CHECK (estimated_duration > 0), -- 预计耗时(分钟)
status VARCHAR(16) DEFAULT 'pending' CHECK (status IN ('pending', 'in_progress', 'completed', 'cancelled')), -- 状态
due_date TIMESTAMP WITH TIME ZONE, -- 截止时间
created_by uuid REFERENCES public.ak_users(id), -- 创建人
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- 护理记录 (基于 ak_assignment_submissions 改造)
CREATE TABLE IF NOT EXISTS public.ec_care_records (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
task_id uuid REFERENCES public.ec_care_tasks(id) ON DELETE CASCADE,
elder_id uuid NOT NULL REFERENCES public.ec_elders(id) ON DELETE CASCADE,
caregiver_id uuid NOT NULL REFERENCES public.ak_users(id) ON DELETE CASCADE, -- 护理员
start_time TIMESTAMP WITH TIME ZONE, -- 开始时间
end_time TIMESTAMP WITH TIME ZONE, -- 结束时间
actual_duration INT CHECK (actual_duration > 0), -- 实际耗时(分钟)
care_content TEXT, -- 护理内容
elder_condition TEXT, -- 老人状况描述
issues_notes TEXT, -- 问题或注意事项
photo_urls TEXT[], -- 相关照片
status VARCHAR(16) DEFAULT 'completed' CHECK (status IN ('completed', 'incomplete', 'cancelled')), -- 状态
rating INT CHECK (rating >= 1 AND rating <= 5), -- 护理质量评分(1-5)
supervisor_notes TEXT, -- 主管备注
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT check_time_order CHECK (end_time IS NULL OR end_time >= start_time)
);
-- ------------------------------------------------
-- 7. 医疗管理
-- ------------------------------------------------
-- 医疗记录
CREATE TABLE IF NOT EXISTS public.ec_medical_records (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
elder_id uuid NOT NULL REFERENCES public.ec_elders(id) ON DELETE CASCADE,
doctor_id uuid REFERENCES public.ak_users(id), -- 医生
visit_type VARCHAR(32) CHECK (visit_type IN ('routine', 'emergency', 'consultation', 'follow_up')), -- 就诊类型
visit_date TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, -- 就诊时间
chief_complaint TEXT, -- 主诉
symptoms TEXT, -- 症状
diagnosis TEXT, -- 诊断
treatment_plan TEXT, -- 治疗方案
prescription TEXT, -- 处方
follow_up_date DATE, -- 复诊日期
notes TEXT, -- 医生备注
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- 用药管理
CREATE TABLE IF NOT EXISTS public.ec_medications (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
elder_id uuid NOT NULL REFERENCES public.ec_elders(id) ON DELETE CASCADE,
medical_record_id uuid REFERENCES public.ec_medical_records(id), -- 关联医疗记录
medication_name VARCHAR(128) NOT NULL, -- 药品名称
dosage VARCHAR(64), -- 剂量
frequency VARCHAR(64), -- 用药频率
route VARCHAR(32) CHECK (route IN ('oral', 'injection', 'topical')), -- 给药途径
start_date DATE DEFAULT CURRENT_DATE, -- 开始日期
end_date DATE, -- 结束日期
prescribed_by uuid REFERENCES public.ak_users(id), -- 开药医生
instructions TEXT, -- 用药说明
side_effects TEXT, -- 副作用注意
status VARCHAR(16) DEFAULT 'active' CHECK (status IN ('active', 'completed', 'discontinued')), -- 状态
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT check_medication_dates CHECK (end_date IS NULL OR end_date >= start_date)
);
-- 用药记录
CREATE TABLE IF NOT EXISTS public.ec_medication_logs (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
medication_id uuid NOT NULL REFERENCES public.ec_medications(id) ON DELETE CASCADE,
elder_id uuid NOT NULL REFERENCES public.ec_elders(id) ON DELETE CASCADE,
administered_by uuid REFERENCES public.ak_users(id), -- 给药人
scheduled_time TIMESTAMP WITH TIME ZONE, -- 计划给药时间
actual_time TIMESTAMP WITH TIME ZONE, -- 实际给药时间
dosage_given VARCHAR(64), -- 实际给药剂量
status VARCHAR(16) CHECK (status IN ('given', 'refused', 'missed', 'delayed')), -- 状态
notes TEXT, -- 备注
side_effects_observed TEXT, -- 观察到的副作用
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- ------------------------------------------------
-- 8. 服务管理
-- ------------------------------------------------
-- 餐饮服务
CREATE TABLE IF NOT EXISTS public.ec_meal_services (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
facility_id uuid NOT NULL REFERENCES public.ec_facilities(id) ON DELETE CASCADE,
meal_date DATE NOT NULL DEFAULT CURRENT_DATE, -- 用餐日期
meal_type VARCHAR(16) CHECK (meal_type IN ('breakfast', 'lunch', 'dinner', 'snack')), -- 餐次类型
menu_items JSONB, -- 菜单项目
nutritional_info JSONB, -- 营养信息
special_diet_options JSONB, -- 特殊饮食选项
created_by uuid REFERENCES public.ak_users(id), -- 创建人
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- 用餐记录
CREATE TABLE IF NOT EXISTS public.ec_meal_records (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
elder_id uuid NOT NULL REFERENCES public.ec_elders(id) ON DELETE CASCADE,
meal_service_id uuid REFERENCES public.ec_meal_services(id),
meal_type VARCHAR(16) CHECK (meal_type IN ('breakfast', 'lunch', 'dinner', 'snack')), -- 餐次
meal_date DATE DEFAULT CURRENT_DATE, -- 用餐日期
food_items JSONB, -- 实际用餐项目
appetite_level VARCHAR(16) CHECK (appetite_level IN ('good', 'fair', 'poor')), -- 食欲
amount_consumed VARCHAR(16) CHECK (amount_consumed IN ('full', 'half', 'quarter', 'none')), -- 进食量
assistance_needed BOOLEAN DEFAULT false, -- 是否需要协助
assisted_by uuid REFERENCES public.ak_users(id), -- 协助人员
notes TEXT, -- 备注
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- 活动安排
CREATE TABLE IF NOT EXISTS public.ec_activities (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
facility_id uuid NOT NULL REFERENCES public.ec_facilities(id) ON DELETE CASCADE,
activity_name VARCHAR(128) NOT NULL, -- 活动名称
activity_type VARCHAR(32) CHECK (activity_type IN ('recreation', 'therapy', 'education', 'social', 'exercise')), -- 活动类型
description TEXT, -- 活动描述
location VARCHAR(128), -- 活动地点
start_time TIMESTAMP WITH TIME ZONE, -- 开始时间
end_time TIMESTAMP WITH TIME ZONE, -- 结束时间
max_participants INT CHECK (max_participants > 0), -- 最大参与人数
instructor uuid REFERENCES public.ak_users(id), -- 活动指导员
requirements TEXT, -- 参与要求
materials_needed TEXT, -- 所需物品
status VARCHAR(16) DEFAULT 'scheduled' CHECK (status IN ('scheduled', 'in_progress', 'completed', 'cancelled')), -- 状态
created_by uuid REFERENCES public.ak_users(id), -- 创建人
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT check_activity_time CHECK (end_time IS NULL OR end_time >= start_time)
);
-- 活动参与记录
CREATE TABLE IF NOT EXISTS public.ec_activity_participations (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
activity_id uuid NOT NULL REFERENCES public.ec_activities(id) ON DELETE CASCADE,
elder_id uuid NOT NULL REFERENCES public.ec_elders(id) ON DELETE CASCADE,
participation_status VARCHAR(16) CHECK (participation_status IN ('registered', 'attended', 'absent', 'cancelled')), -- 参与状态
enjoyment_level VARCHAR(16) CHECK (enjoyment_level IN ('excellent', 'good', 'fair', 'poor')), -- 参与满意度
behavior_notes TEXT, -- 行为观察记录
staff_notes TEXT, -- 工作人员备注
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- 访客管理
CREATE TABLE IF NOT EXISTS public.ec_visits (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
elder_id uuid NOT NULL REFERENCES public.ec_elders(id) ON DELETE CASCADE,
visitor_name VARCHAR(64) NOT NULL, -- 访客姓名
visitor_relationship VARCHAR(32), -- 与老人关系
visitor_id_card VARCHAR(32), -- 访客身份证
visitor_phone VARCHAR(32), -- 访客电话
visit_date DATE DEFAULT CURRENT_DATE, -- 访问日期
start_time TIME, -- 开始时间
end_time TIME, -- 结束时间
visit_purpose TEXT, -- 访问目的
items_brought TEXT, -- 携带物品
approved_by uuid REFERENCES public.ak_users(id), -- 审批人
check_in_time TIMESTAMP WITH TIME ZONE, -- 实际签到时间
check_out_time TIMESTAMP WITH TIME ZONE, -- 实际签出时间
status VARCHAR(16) DEFAULT 'scheduled' CHECK (status IN ('scheduled', 'in_progress', 'completed', 'cancelled')), -- 状态
notes TEXT, -- 备注
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT check_visit_time CHECK (end_time IS NULL OR end_time >= start_time)
);
-- ------------------------------------------------
-- 9. AI分析与报告系统 (基于现有系统改造)
-- ------------------------------------------------
-- 健康预警系统 (基于 ak_ai_reports 改造)
CREATE TABLE IF NOT EXISTS public.ec_health_alerts (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
elder_id uuid NOT NULL REFERENCES public.ec_elders(id) ON DELETE CASCADE,
alert_type VARCHAR(32) CHECK (alert_type IN ('vital_signs', 'medication', 'fall_risk', 'behavior')), -- 预警类型
severity VARCHAR(16) CHECK (severity IN ('low', 'medium', 'high', 'critical')), -- 严重程度
title VARCHAR(128), -- 预警标题
description TEXT, -- 预警描述
data_source JSONB, -- 触发预警的数据
ai_analysis JSONB, -- AI分析结果
recommendations TEXT, -- 建议措施
status VARCHAR(16) DEFAULT 'active' CHECK (status IN ('active', 'acknowledged', 'resolved')), -- 状态
acknowledged_by uuid REFERENCES public.ak_users(id), -- 确认人
acknowledged_at TIMESTAMP WITH TIME ZONE, -- 确认时间
resolved_by uuid REFERENCES public.ak_users(id), -- 处理人
resolved_at TIMESTAMP WITH TIME ZONE, -- 处理时间
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- 行为分析 (基于 ak_video_analysis 改造)
CREATE TABLE IF NOT EXISTS public.ec_behavior_analysis (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
elder_id uuid NOT NULL REFERENCES public.ec_elders(id) ON DELETE CASCADE,
device_id uuid REFERENCES public.ec_devices(id), -- 监控设备
analysis_type VARCHAR(32) CHECK (analysis_type IN ('fall_detection', 'wandering', 'sleep_pattern', 'activity_level')), -- 分析类型
video_url TEXT, -- 视频链接
timestamp_start TIMESTAMP WITH TIME ZONE, -- 分析开始时间
timestamp_end TIMESTAMP WITH TIME ZONE, -- 分析结束时间
ai_results JSONB, -- AI分析结果
confidence_score DECIMAL(3,2) CHECK (confidence_score >= 0 AND confidence_score <= 1), -- 置信度
is_anomaly BOOLEAN DEFAULT false, -- 是否异常
alert_generated BOOLEAN DEFAULT false, -- 是否生成预警
reviewed_by uuid REFERENCES public.ak_users(id), -- 审核人
review_notes TEXT, -- 审核备注
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT check_analysis_time CHECK (timestamp_end IS NULL OR timestamp_end >= timestamp_start)
);
-- ------------------------------------------------
-- 10. 系统配置与管理
-- ------------------------------------------------
-- 通知模板系统 (基于现有通知系统扩展)
CREATE TABLE IF NOT EXISTS public.ec_notification_templates (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
template_code VARCHAR(64) UNIQUE NOT NULL, -- 模板代码
template_name VARCHAR(128) NOT NULL, -- 模板名称
category VARCHAR(32) CHECK (category IN ('health', 'care', 'visit', 'emergency', 'system')), -- 分类
subject_template TEXT, -- 标题模板
content_template TEXT, -- 内容模板
variables JSONB, -- 模板变量定义
delivery_methods VARCHAR(16)[] CHECK (delivery_methods <@ ARRAY['app', 'sms', 'email']), -- 发送方式
is_active BOOLEAN DEFAULT true, -- 是否启用
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- 通知发送记录
CREATE TABLE IF NOT EXISTS public.ec_notification_logs (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
template_id uuid REFERENCES public.ec_notification_templates(id),
recipient_id uuid REFERENCES public.ak_users(id), -- 接收人
elder_id uuid REFERENCES public.ec_elders(id), -- 关联老人
subject VARCHAR(256), -- 实际标题
content TEXT, -- 实际内容
delivery_method VARCHAR(16) CHECK (delivery_method IN ('app', 'sms', 'email')), -- 发送方式
delivery_status VARCHAR(16) DEFAULT 'pending' CHECK (delivery_status IN ('pending', 'sent', 'failed', 'delivered')), -- 发送状态
sent_at TIMESTAMP WITH TIME ZONE, -- 发送时间
delivered_at TIMESTAMP WITH TIME ZONE, -- 送达时间
error_message TEXT, -- 错误信息
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- ------------------------------------------------
-- 11. 索引优化
-- ------------------------------------------------
-- 老人档案相关索引
CREATE INDEX IF NOT EXISTS idx_ec_elders_facility_id ON public.ec_elders(facility_id);
CREATE INDEX IF NOT EXISTS idx_ec_elders_care_unit_id ON public.ec_elders(care_unit_id);
CREATE INDEX IF NOT EXISTS idx_ec_elders_status ON public.ec_elders(status);
CREATE INDEX IF NOT EXISTS idx_ec_elders_elder_code ON public.ec_elders(elder_code);
CREATE INDEX IF NOT EXISTS idx_ec_elders_user_id ON public.ec_elders(user_id);
-- 护理任务相关索引
CREATE INDEX IF NOT EXISTS idx_ec_care_tasks_elder_id ON public.ec_care_tasks(elder_id);
CREATE INDEX IF NOT EXISTS idx_ec_care_tasks_assigned_to ON public.ec_care_tasks(assigned_to);
CREATE INDEX IF NOT EXISTS idx_ec_care_tasks_status ON public.ec_care_tasks(status);
CREATE INDEX IF NOT EXISTS idx_ec_care_tasks_scheduled_time ON public.ec_care_tasks(scheduled_time);
-- 生命体征相关索引
CREATE INDEX IF NOT EXISTS idx_ec_vital_signs_elder_id ON public.ec_vital_signs(elder_id);
CREATE INDEX IF NOT EXISTS idx_ec_vital_signs_measured_at ON public.ec_vital_signs(measured_at);
CREATE INDEX IF NOT EXISTS idx_ec_vital_signs_vital_type ON public.ec_vital_signs(vital_type);
CREATE INDEX IF NOT EXISTS idx_ec_vital_signs_is_abnormal ON public.ec_vital_signs(is_abnormal);
-- 护理记录相关索引
CREATE INDEX IF NOT EXISTS idx_ec_care_records_elder_id ON public.ec_care_records(elder_id);
CREATE INDEX IF NOT EXISTS idx_ec_care_records_caregiver_id ON public.ec_care_records(caregiver_id);
CREATE INDEX IF NOT EXISTS idx_ec_care_records_created_at ON public.ec_care_records(created_at);
-- 用药相关索引
CREATE INDEX IF NOT EXISTS idx_ec_medications_elder_id ON public.ec_medications(elder_id);
CREATE INDEX IF NOT EXISTS idx_ec_medication_logs_elder_id ON public.ec_medication_logs(elder_id);
CREATE INDEX IF NOT EXISTS idx_ec_medication_logs_scheduled_time ON public.ec_medication_logs(scheduled_time);
-- 健康预警相关索引
CREATE INDEX IF NOT EXISTS idx_ec_health_alerts_elder_id ON public.ec_health_alerts(elder_id);
CREATE INDEX IF NOT EXISTS idx_ec_health_alerts_status ON public.ec_health_alerts(status);
CREATE INDEX IF NOT EXISTS idx_ec_health_alerts_severity ON public.ec_health_alerts(severity);
CREATE INDEX IF NOT EXISTS idx_ec_health_alerts_created_at ON public.ec_health_alerts(created_at);
-- 家属联系人索引
CREATE INDEX IF NOT EXISTS idx_ec_family_contacts_elder_id ON public.ec_family_contacts(elder_id);
CREATE INDEX IF NOT EXISTS idx_ec_family_contacts_user_id ON public.ec_family_contacts(user_id);
-- 活动相关索引
CREATE INDEX IF NOT EXISTS idx_ec_activities_facility_id ON public.ec_activities(facility_id);
CREATE INDEX IF NOT EXISTS idx_ec_activities_start_time ON public.ec_activities(start_time);
CREATE INDEX IF NOT EXISTS idx_ec_activity_participations_elder_id ON public.ec_activity_participations(elder_id);
-- ------------------------------------------------
-- 12. 触发器和函数
-- ------------------------------------------------
-- 自动更新时间戳函数
CREATE OR REPLACE FUNCTION update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 为需要自动更新时间戳的表创建触发器
DROP TRIGGER IF EXISTS trigger_ec_facilities_updated_at ON public.ec_facilities;
CREATE TRIGGER trigger_ec_facilities_updated_at
BEFORE UPDATE ON public.ec_facilities
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
DROP TRIGGER IF EXISTS trigger_ec_care_units_updated_at ON public.ec_care_units;
CREATE TRIGGER trigger_ec_care_units_updated_at
BEFORE UPDATE ON public.ec_care_units
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
DROP TRIGGER IF EXISTS trigger_ec_devices_updated_at ON public.ec_devices;
CREATE TRIGGER trigger_ec_devices_updated_at
BEFORE UPDATE ON public.ec_devices
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
DROP TRIGGER IF EXISTS trigger_ec_elders_updated_at ON public.ec_elders;
CREATE TRIGGER trigger_ec_elders_updated_at
BEFORE UPDATE ON public.ec_elders
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
DROP TRIGGER IF EXISTS trigger_ec_family_contacts_updated_at ON public.ec_family_contacts;
CREATE TRIGGER trigger_ec_family_contacts_updated_at
BEFORE UPDATE ON public.ec_family_contacts
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
DROP TRIGGER IF EXISTS trigger_ec_health_records_updated_at ON public.ec_health_records;
CREATE TRIGGER trigger_ec_health_records_updated_at
BEFORE UPDATE ON public.ec_health_records
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
DROP TRIGGER IF EXISTS trigger_ec_care_plans_updated_at ON public.ec_care_plans;
CREATE TRIGGER trigger_ec_care_plans_updated_at
BEFORE UPDATE ON public.ec_care_plans
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
DROP TRIGGER IF EXISTS trigger_ec_care_tasks_updated_at ON public.ec_care_tasks;
CREATE TRIGGER trigger_ec_care_tasks_updated_at
BEFORE UPDATE ON public.ec_care_tasks
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
DROP TRIGGER IF EXISTS trigger_ec_care_records_updated_at ON public.ec_care_records;
CREATE TRIGGER trigger_ec_care_records_updated_at
BEFORE UPDATE ON public.ec_care_records
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- 自动生成健康预警
CREATE OR REPLACE FUNCTION generate_health_alert()
RETURNS TRIGGER AS $$
BEGIN
-- 检查生命体征异常并自动生成预警
IF NEW.is_abnormal = TRUE THEN
INSERT INTO public.ec_health_alerts (
elder_id,
alert_type,
severity,
title,
description,
data_source
) VALUES (
NEW.elder_id,
'vital_signs',
'medium',
'生命体征异常',
'检测到异常的生命体征数据',
jsonb_build_object(
'vital_signs_id', NEW.id,
'vital_type', NEW.vital_type,
'measured_at', NEW.measured_at
)
);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 先安全删除再创建触发器,避免重复报错
DROP TRIGGER IF EXISTS trigger_generate_health_alert ON public.ec_vital_signs;
CREATE TRIGGER trigger_generate_health_alert
AFTER INSERT ON public.ec_vital_signs
FOR EACH ROW
EXECUTE FUNCTION generate_health_alert();
-- 更新机构入住率
CREATE OR REPLACE FUNCTION update_facility_occupancy()
RETURNS TRIGGER AS $$
DECLARE
facility_id_val uuid;
BEGIN
-- 获取机构ID
IF TG_OP = 'INSERT' THEN
facility_id_val := NEW.facility_id;
ELSIF TG_OP = 'DELETE' THEN
facility_id_val := OLD.facility_id;
ELSE
facility_id_val := COALESCE(NEW.facility_id, OLD.facility_id);
END IF;
-- 更新机构当前入住人数
UPDATE public.ec_facilities
SET current_occupancy = (
SELECT COUNT(*)
FROM public.ec_elders
WHERE facility_id = facility_id_val
AND status = 'active'
),
updated_at = CURRENT_TIMESTAMP
WHERE id = facility_id_val;
RETURN CASE WHEN TG_OP = 'DELETE' THEN OLD ELSE NEW END;
END;
$$ LANGUAGE plpgsql;
-- 更新护理单元入住率
CREATE OR REPLACE FUNCTION update_care_unit_occupancy()
RETURNS TRIGGER AS $$
DECLARE
care_unit_id_val uuid;
BEGIN
-- 获取护理单元ID
IF TG_OP = 'INSERT' THEN
care_unit_id_val := NEW.care_unit_id;
ELSIF TG_OP = 'DELETE' THEN
care_unit_id_val := OLD.care_unit_id;
ELSE
care_unit_id_val := COALESCE(NEW.care_unit_id, OLD.care_unit_id);
END IF;
-- 如果有护理单元,更新入住人数
IF care_unit_id_val IS NOT NULL THEN
UPDATE public.ec_care_units
SET current_occupancy = (
SELECT COUNT(*)
FROM public.ec_elders
WHERE care_unit_id = care_unit_id_val
AND status = 'active'
),
updated_at = CURRENT_TIMESTAMP
WHERE id = care_unit_id_val;
END IF;
RETURN CASE WHEN TG_OP = 'DELETE' THEN OLD ELSE NEW END;
END;
$$ LANGUAGE plpgsql;
-- ------------------------------------------------
-- 7. 医疗预约管理
-- ------------------------------------------------
-- 医疗预约表(老人、家属、医生均可用)
CREATE TABLE IF NOT EXISTS public.ec_appointments (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
elder_id uuid NOT NULL REFERENCES public.ec_elders(id) ON DELETE CASCADE, -- 关联老人
doctor_id uuid REFERENCES public.ak_users(id), -- 关联医生
scheduled_time TIMESTAMP WITH TIME ZONE NOT NULL, -- 预约时间
appointment_type VARCHAR(32) NOT NULL CHECK (appointment_type IN ('consultation', 'followup', 'treatment', 'checkup', 'other')), -- 预约类型
status VARCHAR(16) NOT NULL DEFAULT 'scheduled' CHECK (status IN ('scheduled', 'completed', 'cancelled', 'no_show')), -- 状态
notes TEXT, -- 备注
created_by uuid REFERENCES public.ak_users(id), -- 创建人(预约发起人)
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- 启用 RLS
ALTER TABLE public.ec_appointments ENABLE ROW LEVEL SECURITY;
-- 预约插入策略authenticated 只能为自己/关联老人预约
DROP POLICY IF EXISTS appointments_insert_authenticated ON public.ec_appointments;
CREATE POLICY appointments_insert_authenticated ON public.ec_appointments
FOR INSERT TO authenticated
WITH CHECK (
elder_id IN (
SELECT id FROM public.ec_elders
WHERE user_id = get_current_user_id()
OR id IN (SELECT elder_id FROM public.ec_family_contacts WHERE user_id = get_current_user_id())
OR id IN (SELECT DISTINCT elder_id FROM public.ec_care_tasks WHERE assigned_to = get_current_user_id())
)
OR doctor_id = get_current_user_id()
OR created_by = get_current_user_id()
);
-- 预约查询策略authenticated 只能查自己/关联老人/自己为医生的预约
DROP POLICY IF EXISTS appointments_select_authenticated ON public.ec_appointments;
CREATE POLICY appointments_select_authenticated ON public.ec_appointments
FOR SELECT TO authenticated
USING (
elder_id IN (
SELECT id FROM public.ec_elders
WHERE user_id = get_current_user_id()
OR id IN (SELECT elder_id FROM public.ec_family_contacts WHERE user_id = get_current_user_id())
OR id IN (SELECT DISTINCT elder_id FROM public.ec_care_tasks WHERE assigned_to = get_current_user_id())
)
OR doctor_id = get_current_user_id()
OR created_by = get_current_user_id()
);
-- 预约更新策略authenticated 只能改自己/关联老人/自己为医生的预约
DROP POLICY IF EXISTS appointments_update_authenticated ON public.ec_appointments;
CREATE POLICY appointments_update_authenticated ON public.ec_appointments
FOR UPDATE TO authenticated
USING (
elder_id IN (
SELECT id FROM public.ec_elders
WHERE user_id = get_current_user_id()
OR id IN (SELECT elder_id FROM public.ec_family_contacts WHERE user_id = get_current_user_id())
OR id IN (SELECT DISTINCT elder_id FROM public.ec_care_tasks WHERE assigned_to = get_current_user_id())
)
OR doctor_id = get_current_user_id()
OR created_by = get_current_user_id()
);
-- 自动更新时间戳触发器(如有需要可复用 update_updated_at_column
DROP TRIGGER IF EXISTS trigger_ec_appointments_updated_at ON public.ec_appointments;
CREATE TRIGGER trigger_ec_appointments_updated_at
BEFORE UPDATE ON public.ec_appointments
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
-- ------------------------------------------------
-- 13. RLS (行级安全策略) - PostgreSQL兼容版本
-- ------------------------------------------------
-- 启用 RLS
ALTER TABLE public.ec_elders ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ec_care_records ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ec_vital_signs ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ec_health_alerts ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ec_family_contacts ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ec_care_tasks ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ec_service_requests ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.ec_appointments ENABLE ROW LEVEL SECURITY;
-- 老人只能查看自己的数据
DROP POLICY IF EXISTS elder_own_data ON public.ec_elders;
CREATE POLICY elder_own_data ON public.ec_elders
FOR ALL USING (user_id = get_current_user_id());
-- 家属只能查看关联老人的数据
DROP POLICY IF EXISTS family_elder_data ON public.ec_elders;
CREATE POLICY family_elder_data ON public.ec_elders
FOR SELECT USING (
id IN (
SELECT elder_id
FROM public.ec_family_contacts
WHERE user_id = get_current_user_id()
)
);
-- 护理员可以查看所有老人的数据
DROP POLICY IF EXISTS caregiver_assigned_elders ON public.ec_elders;
CREATE POLICY caregiver_assigned_elders ON public.ec_elders
FOR SELECT USING (
EXISTS (
SELECT 1 FROM public.ak_user_roles ur
JOIN public.ak_roles r ON ur.role_id = r.id
WHERE ur.user_id = get_current_user_id()
AND r.name = 'caregiver'
)
OR
id IN (
SELECT DISTINCT elder_id
FROM public.ec_care_tasks
WHERE assigned_to = get_current_user_id()
)
);
-- 机构管理员可以查看本机构所有数据
DROP POLICY IF EXISTS facility_admin_all_data ON public.ec_elders;
CREATE POLICY facility_admin_all_data ON public.ec_elders
FOR ALL USING (
facility_id IN (
SELECT id FROM public.ec_facilities WHERE admin_id = get_current_user_id()
)
);
-- 护理记录访问策略
DROP POLICY IF EXISTS care_records_access ON public.ec_care_records;
CREATE POLICY care_records_access ON public.ec_care_records
FOR ALL USING (
elder_id IN (
SELECT id FROM public.ec_elders
WHERE user_id = get_current_user_id()
OR id IN (
SELECT elder_id
FROM public.ec_family_contacts
WHERE user_id = get_current_user_id()
)
OR id IN (
SELECT DISTINCT elder_id
FROM public.ec_care_tasks
WHERE assigned_to = get_current_user_id()
)
)
OR caregiver_id = get_current_user_id()
);
-- 生命体征访问策略
DROP POLICY IF EXISTS vital_signs_access ON public.ec_vital_signs;
CREATE POLICY vital_signs_access ON public.ec_vital_signs
FOR ALL USING (
elder_id IN (
SELECT id FROM public.ec_elders
WHERE user_id = get_current_user_id()
OR id IN (
SELECT elder_id
FROM public.ec_family_contacts
WHERE user_id = get_current_user_id()
)
OR id IN (
SELECT DISTINCT elder_id
FROM public.ec_care_tasks
WHERE assigned_to = get_current_user_id()
)
)
OR measured_by = get_current_user_id()
);
-- 健康预警访问策略
DROP POLICY IF EXISTS health_alerts_access ON public.ec_health_alerts;
CREATE POLICY health_alerts_access ON public.ec_health_alerts
FOR ALL USING (
elder_id IN (
SELECT id FROM public.ec_elders
WHERE user_id = get_current_user_id()
OR id IN (
SELECT elder_id
FROM public.ec_family_contacts
WHERE user_id = get_current_user_id()
)
OR id IN (
SELECT DISTINCT elder_id
FROM public.ec_care_tasks
WHERE assigned_to = get_current_user_id()
)
)
);
-- ec_service_requests RLS 策略:允许 authenticated 用户插入、查询、更新自己的请求
DROP POLICY IF EXISTS service_requests_insert_authenticated ON public.ec_service_requests;
CREATE POLICY service_requests_insert_authenticated ON public.ec_service_requests
FOR INSERT TO authenticated
WITH CHECK (
elder_id IN (
SELECT id FROM public.ec_elders
WHERE user_id = get_current_user_id()
OR id IN (SELECT elder_id FROM public.ec_family_contacts WHERE user_id = get_current_user_id())
)
);
DROP POLICY IF EXISTS service_requests_select_authenticated ON public.ec_service_requests;
CREATE POLICY service_requests_select_authenticated ON public.ec_service_requests
FOR SELECT TO authenticated
USING (
elder_id IN (
SELECT id FROM public.ec_elders
WHERE user_id = get_current_user_id()
OR id IN (SELECT elder_id FROM public.ec_family_contacts WHERE user_id = get_current_user_id())
)
OR created_by = get_current_user_id()
OR handled_by = get_current_user_id()
);
DROP POLICY IF EXISTS service_requests_update_authenticated ON public.ec_service_requests;
CREATE POLICY service_requests_update_authenticated ON public.ec_service_requests
FOR UPDATE TO authenticated
USING (
elder_id IN (
SELECT id FROM public.ec_elders
WHERE user_id = get_current_user_id()
OR id IN (SELECT elder_id FROM public.ec_family_contacts WHERE user_id = get_current_user_id())
)
OR created_by = get_current_user_id()
OR handled_by = get_current_user_id()
);
-- ------------------------------------------------
-- 14. 视图和查询优化
-- ------------------------------------------------
-- 老人详细信息视图
CREATE OR REPLACE VIEW public.vw_elder_details AS
SELECT
e.id,
e.elder_code,
e.name,
e.gender,
e.birthday,
EXTRACT(YEAR FROM AGE(e.birthday)) as age,
e.care_level,
e.room_number,
e.bed_number,
e.status,
f.name as facility_name,
cu.name as care_unit_name,
fc.name as primary_contact_name,
fc.phone as primary_contact_phone,
e.created_at,
e.updated_at
FROM public.ec_elders e
LEFT JOIN public.ec_facilities f ON e.facility_id = f.id
LEFT JOIN public.ec_care_units cu ON e.care_unit_id = cu.id
LEFT JOIN public.ec_family_contacts fc ON e.id = fc.elder_id AND fc.is_primary = true;
-- 护理任务统计视图
CREATE OR REPLACE VIEW public.vw_care_task_stats AS
SELECT
e.id as elder_id,
e.name as elder_name,
COUNT(ct.id) as total_tasks,
COUNT(CASE WHEN ct.status = 'completed' THEN 1 END) as completed_tasks,
COUNT(CASE WHEN ct.status = 'pending' THEN 1 END) as pending_tasks,
COUNT(CASE WHEN ct.status = 'overdue' AND ct.due_date < CURRENT_TIMESTAMP THEN 1 END) as overdue_tasks
FROM public.ec_elders e
LEFT JOIN public.ec_care_tasks ct ON e.id = ct.elder_id
GROUP BY e.id, e.name;
-- 健康监测摘要视图
CREATE OR REPLACE VIEW public.vw_health_summary AS
SELECT
e.id as elder_id,
e.name as elder_name,
vs.vital_type,
vs.measured_at as last_measurement,
vs.is_abnormal,
COUNT(ha.id) as active_alerts
FROM public.ec_elders e
LEFT JOIN public.ec_vital_signs vs ON e.id = vs.elder_id
LEFT JOIN public.ec_health_alerts ha ON e.id = ha.elder_id AND ha.status = 'active'
WHERE vs.measured_at = (
SELECT MAX(measured_at)
FROM public.ec_vital_signs vs2
WHERE vs2.elder_id = e.id AND vs2.vital_type = vs.vital_type
)
GROUP BY e.id, e.name, vs.vital_type, vs.measured_at, vs.is_abnormal;
-- ================================================
-- 数据库初始化完成
-- ================================================
-- 创建必要的序列和默认值
CREATE SEQUENCE IF NOT EXISTS public.elder_code_seq START 1000;
-- 创建生成老人编号的函数
CREATE OR REPLACE FUNCTION generate_elder_code()
RETURNS VARCHAR(32) AS $$
BEGIN
RETURN 'E' || TO_CHAR(CURRENT_DATE, 'YYYYMMDD') || LPAD(nextval('public.elder_code_seq')::text, 4, '0');
END;
$$ LANGUAGE plpgsql;
-- 为老人表添加自动生成编号的触发器
CREATE OR REPLACE FUNCTION set_elder_code()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.elder_code IS NULL THEN
NEW.elder_code := generate_elder_code();
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS trigger_set_elder_code ON public.ec_elders;
CREATE TRIGGER trigger_set_elder_code
BEFORE INSERT ON public.ec_elders
FOR EACH ROW
EXECUTE FUNCTION set_elder_code();
-- 创建数据完整性检查函数
CREATE OR REPLACE FUNCTION check_elder_data_integrity()
RETURNS BOOLEAN AS $$
DECLARE
integrity_issues INTEGER := 0;
BEGIN
-- 检查孤立的护理记录
SELECT COUNT(*) INTO integrity_issues
FROM public.ec_care_records cr
LEFT JOIN public.ec_elders e ON cr.elder_id = e.id
WHERE e.id IS NULL;
IF integrity_issues > 0 THEN
RAISE WARNING '发现 % 条孤立的护理记录', integrity_issues;
RETURN FALSE;
END IF;
-- 检查异常的生命体征数据
SELECT COUNT(*) INTO integrity_issues
FROM public.ec_vital_signs
WHERE (heart_rate < 30 OR heart_rate > 200)
OR (temperature < 35 OR temperature > 42)
OR (systolic_pressure < 60 OR systolic_pressure > 250);
IF integrity_issues > 0 THEN
RAISE WARNING '发现 % 条异常的生命体征数据', integrity_issues;
RETURN FALSE;
END IF;
RETURN TRUE;
END;
$$ LANGUAGE plpgsql;
-- 创建示例数据插入函数(用于测试)
CREATE OR REPLACE FUNCTION insert_sample_eldercare_data()
RETURNS VOID AS $$
BEGIN
-- 插入示例机构数据
INSERT INTO public.ec_facilities (name, type, contact_phone, address, capacity) VALUES
('阳光养老院', 'nursing_home', '010-12345678', '北京市朝阳区阳光街123号', 100),
('康乐护理中心', 'assisted_living', '021-87654321', '上海市浦东新区康乐路456号', 80)
ON CONFLICT DO NOTHING;
RAISE NOTICE '示例数据插入完成';
END;
$$ LANGUAGE plpgsql;
-- 创建数据库状态检查函数
CREATE OR REPLACE FUNCTION eldercare_system_status()
RETURNS TABLE(
table_name TEXT,
record_count BIGINT,
last_updated TIMESTAMP WITH TIME ZONE
) AS $$
BEGIN
RETURN QUERY
SELECT
'ec_facilities'::TEXT,
COUNT(*),
MAX(created_at)
FROM public.ec_facilities
UNION ALL
SELECT
'ec_elders'::TEXT,
COUNT(*),
MAX(created_at)
FROM public.ec_elders
UNION ALL
SELECT
'ec_care_tasks'::TEXT,
COUNT(*),
MAX(created_at)
FROM public.ec_care_tasks
UNION ALL
SELECT
'ec_vital_signs'::TEXT,
COUNT(*),
MAX(created_at)
FROM public.ec_vital_signs
UNION ALL
SELECT
'ec_health_alerts'::TEXT,
COUNT(*),
MAX(created_at)
FROM public.ec_health_alerts;
END;
$$ LANGUAGE plpgsql;
-- 创建清理过期数据的函数
CREATE OR REPLACE FUNCTION cleanup_expired_data()
RETURNS INTEGER AS $$
DECLARE
deleted_count INTEGER := 0;
temp_count INTEGER := 0;
BEGIN
-- 删除已解决的超过30天的健康预警
DELETE FROM public.ec_health_alerts
WHERE status = 'resolved'
AND resolved_at < CURRENT_DATE - INTERVAL '30 days';
GET DIAGNOSTICS deleted_count = ROW_COUNT;
-- 删除超过1年的通知日志
DELETE FROM public.ec_notification_logs
WHERE created_at < CURRENT_DATE - INTERVAL '1 year';
GET DIAGNOSTICS temp_count = ROW_COUNT;
deleted_count := deleted_count + temp_count;
RETURN deleted_count;
END;
$$ LANGUAGE plpgsql;
-- ================================================
-- 15. 急诊管理
-- ================================================
-- 急诊事件表
CREATE TABLE IF NOT EXISTS public.ec_emergencies (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
appointment_id uuid REFERENCES public.ec_appointments(id), -- 关联预约
elder_id uuid NOT NULL REFERENCES public.ec_elders(id) ON DELETE CASCADE, -- 关联老人
doctor_id uuid REFERENCES public.ak_users(id), -- 处理医生
emergency_type VARCHAR(32) CHECK (emergency_type IN ('fall', 'stroke', 'cardiac', 'other')), -- 急诊类型
severity VARCHAR(16) CHECK (severity IN ('low', 'medium', 'high', 'critical')), -- 紧急程度
status VARCHAR(16) DEFAULT 'active' CHECK (status IN ('active', 'processing', 'resolved', 'cancelled')), -- 状态
description TEXT, -- 急诊描述
occurred_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, -- 发生时间
handled_at TIMESTAMP WITH TIME ZONE, -- 处理时间
handler_notes TEXT, -- 处理说明
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- 启用 RLS
ALTER TABLE public.ec_emergencies ENABLE ROW LEVEL SECURITY;
-- 插入策略authenticated 只能登记自己/关联老人的急诊
DROP POLICY IF EXISTS emergencies_insert_authenticated ON public.ec_emergencies;
CREATE POLICY emergencies_insert_authenticated ON public.ec_emergencies
FOR INSERT TO authenticated
WITH CHECK (elder_id = get_current_user_id() OR doctor_id = get_current_user_id());
-- 查询策略authenticated 只能查自己/关联老人/自己为医生的急诊
DROP POLICY IF EXISTS emergencies_select_authenticated ON public.ec_emergencies;
CREATE POLICY emergencies_select_authenticated ON public.ec_emergencies
FOR SELECT TO authenticated
USING (elder_id = get_current_user_id() OR doctor_id = get_current_user_id());
-- 更新策略authenticated 只能改自己/关联老人/自己为医生的急诊
DROP POLICY IF EXISTS emergencies_update_authenticated ON public.ec_emergencies;
CREATE POLICY emergencies_update_authenticated ON public.ec_emergencies
FOR UPDATE TO authenticated
USING (elder_id = get_current_user_id() OR doctor_id = get_current_user_id());
-- 自动更新时间戳触发器
DROP TRIGGER IF EXISTS trigger_ec_emergencies_updated_at ON public.ec_emergencies;
CREATE TRIGGER trigger_ec_emergencies_updated_at
BEFORE UPDATE ON public.ec_emergencies
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- 急诊相关索引
CREATE INDEX IF NOT EXISTS idx_ec_emergencies_elder_id ON public.ec_emergencies(elder_id);
CREATE INDEX IF NOT EXISTS idx_ec_emergencies_doctor_id ON public.ec_emergencies(doctor_id);
CREATE INDEX IF NOT EXISTS idx_ec_emergencies_status ON public.ec_emergencies(status);
CREATE INDEX IF NOT EXISTS idx_ec_emergencies_severity ON public.ec_emergencies(severity);
CREATE INDEX IF NOT EXISTS idx_ec_emergencies_occurred_at ON public.ec_emergencies(occurred_at);
-- 急诊处理记录表
CREATE TABLE IF NOT EXISTS public.ec_emergency_handlings (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
emergency_id uuid NOT NULL REFERENCES public.ec_emergencies(id) ON DELETE CASCADE,
handler_id uuid NOT NULL REFERENCES public.ak_users(id),
handle_time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
handling_notes TEXT,
status VARCHAR(16) CHECK (status IN ('pending', 'in_progress', 'resolved', 'closed')),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- 启用 RLS
ALTER TABLE public.ec_emergency_handlings ENABLE ROW LEVEL SECURITY;
-- 插入策略
DROP POLICY IF EXISTS emergency_handlings_insert_authenticated ON public.ec_emergency_handlings;
CREATE POLICY emergency_handlings_insert_authenticated ON public.ec_emergency_handlings
FOR INSERT TO authenticated
WITH CHECK (true);
-- 查询策略
DROP POLICY IF EXISTS emergency_handlings_select_authenticated ON public.ec_emergency_handlings;
CREATE POLICY emergency_handlings_select_authenticated ON public.ec_emergency_handlings
FOR SELECT TO authenticated
USING (handler_id = get_current_user_id());
-- 更新策略
DROP POLICY IF EXISTS emergency_handlings_update_authenticated ON public.ec_emergency_handlings;
CREATE POLICY emergency_handlings_update_authenticated ON public.ec_emergency_handlings
FOR UPDATE TO authenticated
USING (handler_id = get_current_user_id());