fix: 修复测试发现的 7 个问题 + 全 workspace clippy 清零
功能修复: 1. 患者创建空名称验证:后端添加 name.trim().is_empty() 检查 2. 仪表盘统计容错:单个查询失败返回零值而非 500 3. FHIR 路由修复:从 /fhir 移到 /api/v1/fhir 保持一致 4. 冻结模块后端中间件:新增 frozen_module_middleware 拦截冻结路径 5. 积分端点权限码:health.health-data.list → health.points.list 6. 角色权限迁移:护士补充 devices.list,运营补充 points.list/manage 7. 测试结果文档:R01-R05 角色测试 + T00/T10 结果归档 Clippy 全 workspace 清零(14→0 errors): - erp-core: 修复 empty doc line、collapsible if、redundant closure 等 9 处 - erp-health: 修复 too_many_arguments、unused var、unnecessary parens 等 58 处 - erp-ai: 修复 dead_code、unused import 等 11 处 - erp-plugin: 修复 too_many_arguments、wildcard pattern 等 11 处 - erp-server-migration: 修复 enum_variant_names 5 处 - erp-auth/config/workflow/message: 各 1-3 处 工程改进: - lint-staged 配置迁移到 .lintstagedrc.js(函数式避免文件列表传给 clippy) - cargo fmt 统一格式化
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
pub use sea_orm_migration::prelude::*;
|
||||
|
||||
mod m20260410_000001_create_tenant;
|
||||
@@ -49,18 +51,23 @@ mod m20260424_000046_health_constraints_fix;
|
||||
mod m20260424_000047_health_index_fix;
|
||||
mod m20260425_000048_add_patient_id_number_hash;
|
||||
mod m20260425_000049_widen_patient_id_number;
|
||||
mod m20260425_00050_add_doctor_name_column;
|
||||
mod m20260425_000051_dialysis_and_lab_enhance;
|
||||
mod m20260425_000052_create_ai_tables;
|
||||
mod m20260425_000053_create_points_tables;
|
||||
mod m20260425_000054_create_daily_monitoring;
|
||||
mod m20260425_000055_points_checkin_standard_fields;
|
||||
mod m20260425_00050_add_doctor_name_column;
|
||||
mod m20260426_000056_create_diagnosis;
|
||||
mod m20260426_000057_rename_points_transaction_type_column;
|
||||
mod m20260426_000058_merge_daily_monitoring_into_vital_signs;
|
||||
mod m20260426_000059_seed_menus;
|
||||
mod m20260426_000060_create_critical_value_thresholds;
|
||||
mod m20260426_000061_create_consent;
|
||||
mod m20260426_000073_create_device_readings;
|
||||
mod m20260426_000074_create_vital_signs_hourly;
|
||||
mod m20260426_000075_create_patient_devices;
|
||||
mod m20260426_000076_create_alert_rules;
|
||||
mod m20260426_000077_create_alerts;
|
||||
mod m20260427_000062_create_tenant_crypto_keys;
|
||||
mod m20260427_000063_content_management;
|
||||
mod m20260427_000064_add_patient_pii_fields;
|
||||
@@ -72,11 +79,6 @@ mod m20260427_000069_add_dialysis_record_key_version;
|
||||
mod m20260427_000070_add_lab_report_key_version;
|
||||
mod m20260427_000071_add_diagnosis_key_version;
|
||||
mod m20260427_000072_widen_encrypted_phone_columns;
|
||||
mod m20260426_000073_create_device_readings;
|
||||
mod m20260426_000074_create_vital_signs_hourly;
|
||||
mod m20260426_000075_create_patient_devices;
|
||||
mod m20260426_000076_create_alert_rules;
|
||||
mod m20260426_000077_create_alerts;
|
||||
mod m20260427_000078_normalize_follow_up_types;
|
||||
mod m20260427_000079_add_vital_signs_fields;
|
||||
mod m20260427_000080_create_medication_record;
|
||||
@@ -128,6 +130,7 @@ mod m20260506_000125_restructure_menus_and_roles;
|
||||
mod m20260506_000126_fix_role_permissions_cleanup;
|
||||
mod m20260507_000127_fix_doctor_extra_permissions;
|
||||
mod m20260507_000128_fix_alert_status_and_menu_perms;
|
||||
mod m20260507_000129_fix_nurse_operator_points_permissions;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
@@ -241,7 +244,9 @@ impl MigratorTrait for Migrator {
|
||||
Box::new(m20260504_000104_create_vital_signs_daily::Migration),
|
||||
Box::new(m20260504_000105_alter_patient_devices_add_status::Migration),
|
||||
Box::new(m20260504_000106_create_api_clients::Migration),
|
||||
Box::new(m20260504_000107_alter_article_article_tag_add_tenant_and_soft_delete::Migration),
|
||||
Box::new(
|
||||
m20260504_000107_alter_article_article_tag_add_tenant_and_soft_delete::Migration,
|
||||
),
|
||||
Box::new(m20260504_000108_alter_vital_signs_hourly_add_soft_delete::Migration),
|
||||
Box::new(m20260504_000109_add_missing_fk_constraints::Migration),
|
||||
Box::new(m20260504_000110_alter_critical_alerts_version_i32::Migration),
|
||||
@@ -263,6 +268,7 @@ impl MigratorTrait for Migrator {
|
||||
Box::new(m20260506_000126_fix_role_permissions_cleanup::Migration),
|
||||
Box::new(m20260507_000127_fix_doctor_extra_permissions::Migration),
|
||||
Box::new(m20260507_000128_fix_alert_status_and_menu_perms::Migration),
|
||||
Box::new(m20260507_000129_fix_nurse_operator_points_permissions::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,16 @@ impl MigrationTrait for Migration {
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("name")).string_len(200).not_null())
|
||||
.col(ColumnDef::new(Alias::new("plugin_version")).string_len(50).not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("name"))
|
||||
.string_len(200)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("plugin_version"))
|
||||
.string_len(50)
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("description")).text().null())
|
||||
.col(ColumnDef::new(Alias::new("author")).string_len(200).null())
|
||||
.col(
|
||||
@@ -29,9 +37,21 @@ impl MigrationTrait for Migration {
|
||||
.not_null()
|
||||
.default("uploaded"),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("manifest_json")).json().not_null())
|
||||
.col(ColumnDef::new(Alias::new("wasm_binary")).binary().not_null())
|
||||
.col(ColumnDef::new(Alias::new("wasm_hash")).string_len(64).not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("manifest_json"))
|
||||
.json()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("wasm_binary"))
|
||||
.binary()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("wasm_hash"))
|
||||
.string_len(64)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("config_json"))
|
||||
.json()
|
||||
@@ -39,8 +59,16 @@ impl MigrationTrait for Migration {
|
||||
.default(Expr::val("{}")),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("error_message")).text().null())
|
||||
.col(ColumnDef::new(Alias::new("installed_at")).timestamp_with_time_zone().null())
|
||||
.col(ColumnDef::new(Alias::new("enabled_at")).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("installed_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("enabled_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
// 标准字段
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
@@ -56,7 +84,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid().null())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid().null())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("deleted_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
@@ -102,8 +134,16 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("plugin_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("entity_name")).string_len(100).not_null())
|
||||
.col(ColumnDef::new(Alias::new("table_name")).string_len(200).not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("entity_name"))
|
||||
.string_len(100)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("table_name"))
|
||||
.string_len(200)
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("schema_json")).json().not_null())
|
||||
// 标准字段
|
||||
.col(
|
||||
@@ -120,7 +160,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid().null())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid().null())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("deleted_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
@@ -154,7 +198,11 @@ impl MigrationTrait for Migration {
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("plugin_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("event_pattern")).string_len(200).not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("event_pattern"))
|
||||
.string_len(200)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
@@ -180,10 +228,18 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("plugin_event_subscriptions")).to_owned())
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("plugin_event_subscriptions"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("plugin_entities")).to_owned())
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("plugin_entities"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("plugins")).to_owned())
|
||||
|
||||
@@ -65,14 +65,19 @@ impl MigrationTrait for Migration {
|
||||
WHERE permission_id IN (
|
||||
SELECT id FROM permissions WHERE code IN ('plugin.admin', 'plugin.list')
|
||||
)
|
||||
"#.to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
"#
|
||||
.to_string(),
|
||||
))
|
||||
.await
|
||||
.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
// 删除 plugin 权限
|
||||
db.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"DELETE FROM permissions WHERE code IN ('plugin.admin', 'plugin.list')".to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
))
|
||||
.await
|
||||
.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -26,9 +26,17 @@ impl MigrationTrait for Migration {
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("plugin_entity_id")).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("plugin_entity_id"))
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("field_name")).string().not_null())
|
||||
.col(ColumnDef::new(Alias::new("column_name")).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("column_name"))
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("sql_type")).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("is_generated"))
|
||||
|
||||
@@ -11,21 +11,13 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(Alias::new("user_departments"))
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("user_id"))
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("user_id")).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("department_id"))
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("tenant_id"))
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("is_primary"))
|
||||
.boolean()
|
||||
@@ -92,7 +84,11 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("user_departments")).to_owned())
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("user_departments"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,11 @@ impl MigrationTrait for Migration {
|
||||
action = 'customer_tag.manage',
|
||||
updated_at = NOW()
|
||||
WHERE code = 'erp-crm.tag.manage' AND deleted_at IS NULL
|
||||
"#.to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
"#
|
||||
.to_string(),
|
||||
))
|
||||
.await
|
||||
.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
// 2. 重命名权限码:erp-crm.relationship.list → erp-crm.customer_relationship.list
|
||||
db.execute(sea_orm::Statement::from_string(
|
||||
@@ -40,8 +43,11 @@ impl MigrationTrait for Migration {
|
||||
action = 'customer_relationship.list',
|
||||
updated_at = NOW()
|
||||
WHERE code = 'erp-crm.relationship.list' AND deleted_at IS NULL
|
||||
"#.to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
"#
|
||||
.to_string(),
|
||||
))
|
||||
.await
|
||||
.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
// 3. 重命名权限码:erp-crm.relationship.manage → erp-crm.customer_relationship.manage
|
||||
db.execute(sea_orm::Statement::from_string(
|
||||
@@ -54,8 +60,11 @@ impl MigrationTrait for Migration {
|
||||
action = 'customer_relationship.manage',
|
||||
updated_at = NOW()
|
||||
WHERE code = 'erp-crm.relationship.manage' AND deleted_at IS NULL
|
||||
"#.to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
"#
|
||||
.to_string(),
|
||||
))
|
||||
.await
|
||||
.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
// 4. 补充缺失的 customer_tag.list 权限(原 manifest 只有 manage 没有 list)
|
||||
db.execute(sea_orm::Statement::from_string(
|
||||
@@ -101,14 +110,19 @@ impl MigrationTrait for Migration {
|
||||
WHERE permission_id IN (
|
||||
SELECT id FROM permissions WHERE code = 'erp-crm.customer_tag.list'
|
||||
)
|
||||
"#.to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
"#
|
||||
.to_string(),
|
||||
))
|
||||
.await
|
||||
.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
// 删除新增的 customer_tag.list 权限
|
||||
db.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"DELETE FROM permissions WHERE code = 'erp-crm.customer_tag.list'".to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
))
|
||||
.await
|
||||
.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
// 回滚权限码:erp-crm.customer_tag.manage → erp-crm.tag.manage
|
||||
db.execute(sea_orm::Statement::from_string(
|
||||
@@ -120,8 +134,11 @@ impl MigrationTrait for Migration {
|
||||
action = 'tag.manage',
|
||||
updated_at = NOW()
|
||||
WHERE code = 'erp-crm.customer_tag.manage' AND deleted_at IS NULL
|
||||
"#.to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
"#
|
||||
.to_string(),
|
||||
))
|
||||
.await
|
||||
.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
// 回滚:erp-crm.customer_relationship.list → erp-crm.relationship.list
|
||||
db.execute(sea_orm::Statement::from_string(
|
||||
@@ -133,8 +150,11 @@ impl MigrationTrait for Migration {
|
||||
action = 'relationship.list',
|
||||
updated_at = NOW()
|
||||
WHERE code = 'erp-crm.customer_relationship.list' AND deleted_at IS NULL
|
||||
"#.to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
"#
|
||||
.to_string(),
|
||||
))
|
||||
.await
|
||||
.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
// 回滚:erp-crm.customer_relationship.manage → erp-crm.relationship.manage
|
||||
db.execute(sea_orm::Statement::from_string(
|
||||
@@ -146,8 +166,11 @@ impl MigrationTrait for Migration {
|
||||
action = 'relationship.manage',
|
||||
updated_at = NOW()
|
||||
WHERE code = 'erp-crm.customer_relationship.manage' AND deleted_at IS NULL
|
||||
"#.to_string(),
|
||||
)).await.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
"#
|
||||
.to_string(),
|
||||
))
|
||||
.await
|
||||
.map_err(|e| DbErr::Custom(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -27,26 +27,55 @@ impl MigrationTrait for Migration {
|
||||
.col(ColumnDef::new(Alias::new("tags")).json()) // 标签列表
|
||||
.col(ColumnDef::new(Alias::new("icon_url")).string())
|
||||
.col(ColumnDef::new(Alias::new("screenshots")).json()) // 截图 URL 列表
|
||||
.col(ColumnDef::new(Alias::new("wasm_binary")).binary().not_null())
|
||||
.col(ColumnDef::new(Alias::new("manifest_toml")).text().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("wasm_binary"))
|
||||
.binary()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("manifest_toml"))
|
||||
.text()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("wasm_hash")).string().not_null())
|
||||
.col(ColumnDef::new(Alias::new("min_platform_version")).string())
|
||||
.col(ColumnDef::new(Alias::new("status"))
|
||||
.string()
|
||||
.not_null()
|
||||
.default("published")) // published | suspended
|
||||
.col(ColumnDef::new(Alias::new("download_count")).integer().not_null().default(0))
|
||||
.col(ColumnDef::new(Alias::new("rating_avg")).decimal().not_null().default(0.0))
|
||||
.col(ColumnDef::new(Alias::new("rating_count")).integer().not_null().default(0))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("status"))
|
||||
.string()
|
||||
.not_null()
|
||||
.default("published"),
|
||||
) // published | suspended
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("download_count"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("rating_avg"))
|
||||
.decimal()
|
||||
.not_null()
|
||||
.default(0.0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("rating_count"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("changelog")).text()) // 版本更新日志
|
||||
.col(ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()))
|
||||
.col(ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
@@ -65,13 +94,19 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("user_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("market_entry_id")).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("market_entry_id"))
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("rating")).integer().not_null()) // 1-5
|
||||
.col(ColumnDef::new(Alias::new("review_text")).text())
|
||||
.col(ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
@@ -94,10 +129,18 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("plugin_market_reviews")).to_owned())
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("plugin_market_reviews"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("plugin_market_entries")).to_owned())
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("plugin_market_entries"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,18 +21,31 @@ impl MigrationTrait for Migration {
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("user_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("plugin_id")).string().not_null())
|
||||
.col(ColumnDef::new(Alias::new("entity_name")).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("entity_name"))
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("view_name")).string().not_null())
|
||||
.col(ColumnDef::new(Alias::new("view_config")).json().not_null())
|
||||
.col(ColumnDef::new(Alias::new("is_default")).boolean().not_null().default(false))
|
||||
.col(ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()))
|
||||
.col(ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("is_default"))
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(false),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
@@ -40,7 +53,11 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("plugin_user_views")).to_owned())
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("plugin_user_views"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,16 @@ impl MigrationTrait for Migration {
|
||||
.col(ColumnDef::new(Patient::IdNumber).string_len(20).null())
|
||||
.col(ColumnDef::new(Patient::AllergyHistory).text().null())
|
||||
.col(ColumnDef::new(Patient::MedicalHistorySummary).text().null())
|
||||
.col(ColumnDef::new(Patient::EmergencyContactName).string_len(100).null())
|
||||
.col(ColumnDef::new(Patient::EmergencyContactPhone).string_len(20).null())
|
||||
.col(
|
||||
ColumnDef::new(Patient::EmergencyContactName)
|
||||
.string_len(100)
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Patient::EmergencyContactPhone)
|
||||
.string_len(20)
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Patient::Status)
|
||||
.string_len(20)
|
||||
@@ -52,7 +60,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(Patient::CreatedBy).uuid().null())
|
||||
.col(ColumnDef::new(Patient::UpdatedBy).uuid().null())
|
||||
.col(ColumnDef::new(Patient::DeletedAt).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(Patient::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Patient::Version)
|
||||
.integer()
|
||||
@@ -110,11 +122,31 @@ impl MigrationTrait for Migration {
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(PatientFamilyMember::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(PatientFamilyMember::PatientId).uuid().not_null())
|
||||
.col(ColumnDef::new(PatientFamilyMember::Name).string_len(100).not_null())
|
||||
.col(ColumnDef::new(PatientFamilyMember::Relationship).string_len(50).not_null())
|
||||
.col(ColumnDef::new(PatientFamilyMember::Phone).string_len(20).null())
|
||||
.col(
|
||||
ColumnDef::new(PatientFamilyMember::TenantId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(PatientFamilyMember::PatientId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(PatientFamilyMember::Name)
|
||||
.string_len(100)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(PatientFamilyMember::Relationship)
|
||||
.string_len(50)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(PatientFamilyMember::Phone)
|
||||
.string_len(20)
|
||||
.null(),
|
||||
)
|
||||
.col(ColumnDef::new(PatientFamilyMember::BirthDate).date().null())
|
||||
.col(ColumnDef::new(PatientFamilyMember::Notes).text().null())
|
||||
.col(
|
||||
@@ -158,7 +190,12 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(PatientTag::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(PatientTag::Id).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(PatientTag::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(PatientTag::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(PatientTag::Name).string_len(50).not_null())
|
||||
.col(ColumnDef::new(PatientTag::Color).string_len(20).null())
|
||||
@@ -183,7 +220,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(PatientTag::CreatedBy).uuid().null())
|
||||
.col(ColumnDef::new(PatientTag::UpdatedBy).uuid().null())
|
||||
.col(ColumnDef::new(PatientTag::DeletedAt).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(PatientTag::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(PatientTag::Version)
|
||||
.integer()
|
||||
@@ -206,8 +247,16 @@ impl MigrationTrait for Migration {
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(PatientTagRelation::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(PatientTagRelation::PatientId).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(PatientTagRelation::TenantId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(PatientTagRelation::PatientId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(PatientTagRelation::TagId).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(PatientTagRelation::CreatedAt)
|
||||
@@ -287,14 +336,35 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(DoctorProfile::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(DoctorProfile::Id).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(DoctorProfile::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(DoctorProfile::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(DoctorProfile::UserId).uuid().null())
|
||||
.col(ColumnDef::new(DoctorProfile::Name).string_len(100).not_null())
|
||||
.col(ColumnDef::new(DoctorProfile::Department).string_len(100).null())
|
||||
.col(
|
||||
ColumnDef::new(DoctorProfile::Name)
|
||||
.string_len(100)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(DoctorProfile::Department)
|
||||
.string_len(100)
|
||||
.null(),
|
||||
)
|
||||
.col(ColumnDef::new(DoctorProfile::Title).string_len(50).null())
|
||||
.col(ColumnDef::new(DoctorProfile::Specialty).string_len(200).null())
|
||||
.col(ColumnDef::new(DoctorProfile::LicenseNumber).string_len(50).null())
|
||||
.col(
|
||||
ColumnDef::new(DoctorProfile::Specialty)
|
||||
.string_len(200)
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(DoctorProfile::LicenseNumber)
|
||||
.string_len(50)
|
||||
.null(),
|
||||
)
|
||||
.col(ColumnDef::new(DoctorProfile::Bio).text().null())
|
||||
.col(
|
||||
ColumnDef::new(DoctorProfile::OnlineStatus)
|
||||
@@ -316,7 +386,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(DoctorProfile::CreatedBy).uuid().null())
|
||||
.col(ColumnDef::new(DoctorProfile::UpdatedBy).uuid().null())
|
||||
.col(ColumnDef::new(DoctorProfile::DeletedAt).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(DoctorProfile::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(DoctorProfile::Version)
|
||||
.integer()
|
||||
@@ -352,9 +426,21 @@ impl MigrationTrait for Migration {
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(PatientDoctorRelation::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(PatientDoctorRelation::PatientId).uuid().not_null())
|
||||
.col(ColumnDef::new(PatientDoctorRelation::DoctorId).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(PatientDoctorRelation::TenantId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(PatientDoctorRelation::PatientId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(PatientDoctorRelation::DoctorId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(PatientDoctorRelation::RelationshipType)
|
||||
.string_len(20)
|
||||
@@ -373,8 +459,16 @@ impl MigrationTrait for Migration {
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(PatientDoctorRelation::CreatedBy).uuid().null())
|
||||
.col(ColumnDef::new(PatientDoctorRelation::UpdatedBy).uuid().null())
|
||||
.col(
|
||||
ColumnDef::new(PatientDoctorRelation::CreatedBy)
|
||||
.uuid()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(PatientDoctorRelation::UpdatedBy)
|
||||
.uuid()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(PatientDoctorRelation::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
@@ -382,13 +476,19 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.from(PatientDoctorRelation::Table, PatientDoctorRelation::PatientId)
|
||||
.from(
|
||||
PatientDoctorRelation::Table,
|
||||
PatientDoctorRelation::PatientId,
|
||||
)
|
||||
.to(Patient::Table, Patient::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade),
|
||||
)
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.from(PatientDoctorRelation::Table, PatientDoctorRelation::DoctorId)
|
||||
.from(
|
||||
PatientDoctorRelation::Table,
|
||||
PatientDoctorRelation::DoctorId,
|
||||
)
|
||||
.to(DoctorProfile::Table, DoctorProfile::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade),
|
||||
)
|
||||
@@ -425,7 +525,12 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(HealthRecord::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(HealthRecord::Id).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(HealthRecord::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(HealthRecord::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(HealthRecord::PatientId).uuid().not_null())
|
||||
.col(
|
||||
@@ -436,8 +541,16 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(HealthRecord::RecordDate).date().not_null())
|
||||
.col(ColumnDef::new(HealthRecord::Source).string_len(200).null())
|
||||
.col(ColumnDef::new(HealthRecord::OverallAssessment).text().null())
|
||||
.col(ColumnDef::new(HealthRecord::ReportFileUrl).string_len(500).null())
|
||||
.col(
|
||||
ColumnDef::new(HealthRecord::OverallAssessment)
|
||||
.text()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(HealthRecord::ReportFileUrl)
|
||||
.string_len(500)
|
||||
.null(),
|
||||
)
|
||||
.col(ColumnDef::new(HealthRecord::Notes).text().null())
|
||||
.col(
|
||||
ColumnDef::new(HealthRecord::CreatedAt)
|
||||
@@ -453,7 +566,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(HealthRecord::CreatedBy).uuid().null())
|
||||
.col(ColumnDef::new(HealthRecord::UpdatedBy).uuid().null())
|
||||
.col(ColumnDef::new(HealthRecord::DeletedAt).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(HealthRecord::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(HealthRecord::Version)
|
||||
.integer()
|
||||
@@ -489,17 +606,42 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(VitalSigns::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(VitalSigns::Id).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(VitalSigns::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(VitalSigns::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(VitalSigns::PatientId).uuid().not_null())
|
||||
.col(ColumnDef::new(VitalSigns::RecordDate).date().not_null())
|
||||
.col(ColumnDef::new(VitalSigns::SystolicBpMorning).integer().null())
|
||||
.col(ColumnDef::new(VitalSigns::DiastolicBpMorning).integer().null())
|
||||
.col(ColumnDef::new(VitalSigns::SystolicBpEvening).integer().null())
|
||||
.col(ColumnDef::new(VitalSigns::DiastolicBpEvening).integer().null())
|
||||
.col(
|
||||
ColumnDef::new(VitalSigns::SystolicBpMorning)
|
||||
.integer()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(VitalSigns::DiastolicBpMorning)
|
||||
.integer()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(VitalSigns::SystolicBpEvening)
|
||||
.integer()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(VitalSigns::DiastolicBpEvening)
|
||||
.integer()
|
||||
.null(),
|
||||
)
|
||||
.col(ColumnDef::new(VitalSigns::HeartRate).integer().null())
|
||||
.col(ColumnDef::new(VitalSigns::Weight).decimal_len(5, 1).null())
|
||||
.col(ColumnDef::new(VitalSigns::BloodSugar).decimal_len(5, 1).null())
|
||||
.col(
|
||||
ColumnDef::new(VitalSigns::BloodSugar)
|
||||
.decimal_len(5, 1)
|
||||
.null(),
|
||||
)
|
||||
.col(ColumnDef::new(VitalSigns::WaterIntakeMl).integer().null())
|
||||
.col(ColumnDef::new(VitalSigns::UrineOutputMl).integer().null())
|
||||
.col(ColumnDef::new(VitalSigns::Notes).text().null())
|
||||
@@ -517,7 +659,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(VitalSigns::CreatedBy).uuid().null())
|
||||
.col(ColumnDef::new(VitalSigns::UpdatedBy).uuid().null())
|
||||
.col(ColumnDef::new(VitalSigns::DeletedAt).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(VitalSigns::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(VitalSigns::Version)
|
||||
.integer()
|
||||
@@ -553,14 +699,27 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(LabReport::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(LabReport::Id).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(LabReport::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(LabReport::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(LabReport::PatientId).uuid().not_null())
|
||||
.col(ColumnDef::new(LabReport::ReportDate).date().not_null())
|
||||
.col(ColumnDef::new(LabReport::ReportType).string_len(50).not_null())
|
||||
.col(
|
||||
ColumnDef::new(LabReport::ReportType)
|
||||
.string_len(50)
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(LabReport::Indicators).json_binary().null())
|
||||
.col(ColumnDef::new(LabReport::ImageUrls).json_binary().null())
|
||||
.col(ColumnDef::new(LabReport::DoctorInterpretation).text().null())
|
||||
.col(
|
||||
ColumnDef::new(LabReport::DoctorInterpretation)
|
||||
.text()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(LabReport::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
@@ -575,7 +734,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(LabReport::CreatedBy).uuid().null())
|
||||
.col(ColumnDef::new(LabReport::UpdatedBy).uuid().null())
|
||||
.col(ColumnDef::new(LabReport::DeletedAt).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(LabReport::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(LabReport::Version)
|
||||
.integer()
|
||||
@@ -611,20 +774,37 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(HealthTrend::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(HealthTrend::Id).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(HealthTrend::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(HealthTrend::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(HealthTrend::PatientId).uuid().not_null())
|
||||
.col(ColumnDef::new(HealthTrend::PeriodStart).date().not_null())
|
||||
.col(ColumnDef::new(HealthTrend::PeriodEnd).date().not_null())
|
||||
.col(ColumnDef::new(HealthTrend::IndicatorSummary).json_binary().null())
|
||||
.col(ColumnDef::new(HealthTrend::AbnormalItems).json_binary().null())
|
||||
.col(
|
||||
ColumnDef::new(HealthTrend::IndicatorSummary)
|
||||
.json_binary()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(HealthTrend::AbnormalItems)
|
||||
.json_binary()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(HealthTrend::GenerationType)
|
||||
.string_len(20)
|
||||
.not_null()
|
||||
.default("auto"),
|
||||
)
|
||||
.col(ColumnDef::new(HealthTrend::ReportFileUrl).string_len(500).null())
|
||||
.col(
|
||||
ColumnDef::new(HealthTrend::ReportFileUrl)
|
||||
.string_len(500)
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(HealthTrend::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
@@ -639,7 +819,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(HealthTrend::CreatedBy).uuid().null())
|
||||
.col(ColumnDef::new(HealthTrend::UpdatedBy).uuid().null())
|
||||
.col(ColumnDef::new(HealthTrend::DeletedAt).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(HealthTrend::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(HealthTrend::Version)
|
||||
.integer()
|
||||
@@ -662,7 +846,12 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(Appointment::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Appointment::Id).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(Appointment::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Appointment::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(Appointment::PatientId).uuid().not_null())
|
||||
.col(ColumnDef::new(Appointment::DoctorId).uuid().null())
|
||||
@@ -672,7 +861,11 @@ impl MigrationTrait for Migration {
|
||||
.not_null()
|
||||
.default("outpatient"),
|
||||
)
|
||||
.col(ColumnDef::new(Appointment::AppointmentDate).date().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Appointment::AppointmentDate)
|
||||
.date()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Appointment::StartTime).time().not_null())
|
||||
.col(ColumnDef::new(Appointment::EndTime).time().not_null())
|
||||
.col(
|
||||
@@ -697,7 +890,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(Appointment::CreatedBy).uuid().null())
|
||||
.col(ColumnDef::new(Appointment::UpdatedBy).uuid().null())
|
||||
.col(ColumnDef::new(Appointment::DeletedAt).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(Appointment::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Appointment::Version)
|
||||
.integer()
|
||||
@@ -751,10 +948,19 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(DoctorSchedule::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(DoctorSchedule::Id).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(DoctorSchedule::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(DoctorSchedule::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(DoctorSchedule::DoctorId).uuid().not_null())
|
||||
.col(ColumnDef::new(DoctorSchedule::ScheduleDate).date().not_null())
|
||||
.col(
|
||||
ColumnDef::new(DoctorSchedule::ScheduleDate)
|
||||
.date()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(DoctorSchedule::PeriodType)
|
||||
.string_len(20)
|
||||
@@ -763,7 +969,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(DoctorSchedule::StartTime).time().not_null())
|
||||
.col(ColumnDef::new(DoctorSchedule::EndTime).time().not_null())
|
||||
.col(ColumnDef::new(DoctorSchedule::MaxAppointments).integer().not_null())
|
||||
.col(
|
||||
ColumnDef::new(DoctorSchedule::MaxAppointments)
|
||||
.integer()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(DoctorSchedule::CurrentAppointments)
|
||||
.integer()
|
||||
@@ -846,7 +1056,12 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(FollowUpTask::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(FollowUpTask::Id).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(FollowUpTask::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(FollowUpTask::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(FollowUpTask::PatientId).uuid().not_null())
|
||||
.col(ColumnDef::new(FollowUpTask::AssignedTo).uuid().null())
|
||||
@@ -864,7 +1079,11 @@ impl MigrationTrait for Migration {
|
||||
.default("pending"),
|
||||
)
|
||||
.col(ColumnDef::new(FollowUpTask::ContentTemplate).text().null())
|
||||
.col(ColumnDef::new(FollowUpTask::RelatedAppointmentId).uuid().null())
|
||||
.col(
|
||||
ColumnDef::new(FollowUpTask::RelatedAppointmentId)
|
||||
.uuid()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(FollowUpTask::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
@@ -879,7 +1098,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(FollowUpTask::CreatedBy).uuid().null())
|
||||
.col(ColumnDef::new(FollowUpTask::UpdatedBy).uuid().null())
|
||||
.col(ColumnDef::new(FollowUpTask::DeletedAt).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(FollowUpTask::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(FollowUpTask::Version)
|
||||
.integer()
|
||||
@@ -927,20 +1150,37 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(FollowUpRecord::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(FollowUpRecord::Id).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(FollowUpRecord::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(FollowUpRecord::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(FollowUpRecord::TaskId).uuid().not_null())
|
||||
.col(ColumnDef::new(FollowUpRecord::ExecutedBy).uuid().null())
|
||||
.col(ColumnDef::new(FollowUpRecord::ExecutedDate).date().not_null())
|
||||
.col(
|
||||
ColumnDef::new(FollowUpRecord::ExecutedDate)
|
||||
.date()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(FollowUpRecord::Result)
|
||||
.string_len(20)
|
||||
.not_null()
|
||||
.default("followed_up"),
|
||||
)
|
||||
.col(ColumnDef::new(FollowUpRecord::PatientCondition).text().null())
|
||||
.col(
|
||||
ColumnDef::new(FollowUpRecord::PatientCondition)
|
||||
.text()
|
||||
.null(),
|
||||
)
|
||||
.col(ColumnDef::new(FollowUpRecord::MedicalAdvice).text().null())
|
||||
.col(ColumnDef::new(FollowUpRecord::NextFollowUpDate).date().null())
|
||||
.col(
|
||||
ColumnDef::new(FollowUpRecord::NextFollowUpDate)
|
||||
.date()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(FollowUpRecord::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
@@ -1000,8 +1240,16 @@ impl MigrationTrait for Migration {
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(ConsultationSession::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(ConsultationSession::PatientId).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(ConsultationSession::TenantId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ConsultationSession::PatientId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(ConsultationSession::DoctorId).uuid().null())
|
||||
.col(
|
||||
ColumnDef::new(ConsultationSession::ConsultationType)
|
||||
@@ -1015,7 +1263,11 @@ impl MigrationTrait for Migration {
|
||||
.not_null()
|
||||
.default("waiting"),
|
||||
)
|
||||
.col(ColumnDef::new(ConsultationSession::LastMessageAt).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(ConsultationSession::LastMessageAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ConsultationSession::UnreadCountPatient)
|
||||
.integer()
|
||||
@@ -1106,9 +1358,21 @@ impl MigrationTrait for Migration {
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(ConsultationMessage::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(ConsultationMessage::SessionId).uuid().not_null())
|
||||
.col(ColumnDef::new(ConsultationMessage::SenderId).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(ConsultationMessage::TenantId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ConsultationMessage::SessionId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ConsultationMessage::SenderId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ConsultationMessage::SenderRole)
|
||||
.string_len(20)
|
||||
@@ -1120,7 +1384,11 @@ impl MigrationTrait for Migration {
|
||||
.not_null()
|
||||
.default("text"),
|
||||
)
|
||||
.col(ColumnDef::new(ConsultationMessage::Content).text().not_null())
|
||||
.col(
|
||||
ColumnDef::new(ConsultationMessage::Content)
|
||||
.text()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ConsultationMessage::IsRead)
|
||||
.boolean()
|
||||
@@ -1192,22 +1460,54 @@ impl MigrationTrait for Migration {
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager.drop_table(Table::drop().table(ConsultationMessage::Table).to_owned()).await?;
|
||||
manager.drop_table(Table::drop().table(ConsultationSession::Table).to_owned()).await?;
|
||||
manager.drop_table(Table::drop().table(FollowUpRecord::Table).to_owned()).await?;
|
||||
manager.drop_table(Table::drop().table(FollowUpTask::Table).to_owned()).await?;
|
||||
manager.drop_table(Table::drop().table(Appointment::Table).to_owned()).await?;
|
||||
manager.drop_table(Table::drop().table(DoctorSchedule::Table).to_owned()).await?;
|
||||
manager.drop_table(Table::drop().table(HealthTrend::Table).to_owned()).await?;
|
||||
manager.drop_table(Table::drop().table(LabReport::Table).to_owned()).await?;
|
||||
manager.drop_table(Table::drop().table(VitalSigns::Table).to_owned()).await?;
|
||||
manager.drop_table(Table::drop().table(HealthRecord::Table).to_owned()).await?;
|
||||
manager.drop_table(Table::drop().table(PatientDoctorRelation::Table).to_owned()).await?;
|
||||
manager.drop_table(Table::drop().table(PatientTagRelation::Table).to_owned()).await?;
|
||||
manager.drop_table(Table::drop().table(PatientTag::Table).to_owned()).await?;
|
||||
manager.drop_table(Table::drop().table(PatientFamilyMember::Table).to_owned()).await?;
|
||||
manager.drop_table(Table::drop().table(DoctorProfile::Table).to_owned()).await?;
|
||||
manager.drop_table(Table::drop().table(Patient::Table).to_owned()).await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(ConsultationMessage::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(ConsultationSession::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(FollowUpRecord::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(FollowUpTask::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(Appointment::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(DoctorSchedule::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(HealthTrend::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(LabReport::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(VitalSigns::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(HealthRecord::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(PatientDoctorRelation::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(PatientTagRelation::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(PatientTag::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(PatientFamilyMember::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(DoctorProfile::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(Patient::Table).to_owned())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1410,6 +1710,7 @@ enum HealthTrend {
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
enum Appointment {
|
||||
Table,
|
||||
Id,
|
||||
|
||||
@@ -11,7 +11,12 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(WechatUsers::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(WechatUsers::Id).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(WechatUsers::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(WechatUsers::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(WechatUsers::Openid).string().not_null())
|
||||
.col(ColumnDef::new(WechatUsers::UnionId).string())
|
||||
@@ -31,11 +36,13 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(WechatUsers::CreatedBy).uuid())
|
||||
.col(ColumnDef::new(WechatUsers::UpdatedBy).uuid())
|
||||
.col(ColumnDef::new(WechatUsers::DeletedAt).timestamp_with_time_zone())
|
||||
.col(
|
||||
ColumnDef::new(WechatUsers::DeletedAt)
|
||||
.timestamp_with_time_zone(),
|
||||
ColumnDef::new(WechatUsers::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.col(ColumnDef::new(WechatUsers::Version).integer().not_null().default(1))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -19,7 +19,11 @@ impl MigrationTrait for Migration {
|
||||
.col(ColumnDef::new(Article::CoverImage).string_len(500).null())
|
||||
.col(ColumnDef::new(Article::Category).string_len(50).null())
|
||||
.col(ColumnDef::new(Article::Author).string_len(100).null())
|
||||
.col(ColumnDef::new(Article::PublishedAt).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(Article::PublishedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Article::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
@@ -34,7 +38,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(Article::CreatedBy).uuid().null())
|
||||
.col(ColumnDef::new(Article::UpdatedBy).uuid().null())
|
||||
.col(ColumnDef::new(Article::DeletedAt).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(Article::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Article::Version)
|
||||
.integer()
|
||||
@@ -72,7 +80,11 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_index(Index::drop().name("idx_article_tenant_published").to_owned())
|
||||
.drop_index(
|
||||
Index::drop()
|
||||
.name("idx_article_tenant_published")
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.drop_index(Index::drop().name("idx_article_tenant_category").to_owned())
|
||||
|
||||
@@ -59,10 +59,18 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.drop_index(Index::drop().name("idx_health_trend_patient_period").to_owned())
|
||||
.drop_index(
|
||||
Index::drop()
|
||||
.name("idx_health_trend_patient_period")
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.drop_index(Index::drop().name("idx_follow_up_record_task_date").to_owned())
|
||||
.drop_index(
|
||||
Index::drop()
|
||||
.name("idx_follow_up_record_task_date")
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -10,19 +10,22 @@ impl MigrationTrait for Migration {
|
||||
let conn = manager.get_connection();
|
||||
|
||||
// C-4: patient.id_number 唯一索引 — 重建为 partial index WHERE deleted_at IS NULL
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_patient_tenant_id_number").await?;
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_patient_tenant_id_number")
|
||||
.await?;
|
||||
conn.execute_unprepared(
|
||||
"CREATE UNIQUE INDEX idx_patient_tenant_id_number ON patient (tenant_id, id_number) WHERE deleted_at IS NULL AND id_number IS NOT NULL"
|
||||
).await?;
|
||||
|
||||
// C-5: patient_tag.name 唯一索引 — 重建为 partial index WHERE deleted_at IS NULL
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_patient_tag_tenant_name_unique").await?;
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_patient_tag_tenant_name_unique")
|
||||
.await?;
|
||||
conn.execute_unprepared(
|
||||
"CREATE UNIQUE INDEX idx_patient_tag_tenant_name_unique ON patient_tag (tenant_id, name) WHERE deleted_at IS NULL"
|
||||
).await?;
|
||||
|
||||
// C-6: doctor_schedule 唯一索引 — 重建为 partial index,修正列选择为 (tenant_id, doctor_id, schedule_date, period_type)
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_doctor_schedule_unique_slot").await?;
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_doctor_schedule_unique_slot")
|
||||
.await?;
|
||||
conn.execute_unprepared(
|
||||
"CREATE UNIQUE INDEX idx_doctor_schedule_unique_slot ON doctor_schedule (tenant_id, doctor_id, schedule_date, period_type) WHERE deleted_at IS NULL"
|
||||
).await?;
|
||||
@@ -39,12 +42,14 @@ impl MigrationTrait for Migration {
|
||||
|
||||
// H-8: follow_up_task.related_appointment_id 添加 FK 约束
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE follow_up_task DROP CONSTRAINT IF EXISTS fk_follow_up_task_appointment"
|
||||
).await?;
|
||||
"ALTER TABLE follow_up_task DROP CONSTRAINT IF EXISTS fk_follow_up_task_appointment",
|
||||
)
|
||||
.await?;
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE follow_up_task ADD CONSTRAINT fk_follow_up_task_appointment \
|
||||
FOREIGN KEY (related_appointment_id) REFERENCES appointment(id) ON DELETE SET NULL"
|
||||
).await?;
|
||||
FOREIGN KEY (related_appointment_id) REFERENCES appointment(id) ON DELETE SET NULL",
|
||||
)
|
||||
.await?;
|
||||
|
||||
// M-6: lab_report 添加 (tenant_id, report_type) 索引
|
||||
conn.execute_unprepared(
|
||||
@@ -58,36 +63,40 @@ impl MigrationTrait for Migration {
|
||||
let conn = manager.get_connection();
|
||||
|
||||
// 恢复原始索引(非 partial)
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_patient_tenant_id_number").await?;
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_patient_tenant_id_number")
|
||||
.await?;
|
||||
conn.execute_unprepared(
|
||||
"CREATE UNIQUE INDEX idx_patient_tenant_id_number ON patient (tenant_id, id_number)"
|
||||
).await?;
|
||||
"CREATE UNIQUE INDEX idx_patient_tenant_id_number ON patient (tenant_id, id_number)",
|
||||
)
|
||||
.await?;
|
||||
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_patient_tag_tenant_name_unique").await?;
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_patient_tag_tenant_name_unique")
|
||||
.await?;
|
||||
conn.execute_unprepared(
|
||||
"CREATE UNIQUE INDEX idx_patient_tag_tenant_name_unique ON patient_tag (tenant_id, name)"
|
||||
).await?;
|
||||
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_doctor_schedule_unique_slot").await?;
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_doctor_schedule_unique_slot")
|
||||
.await?;
|
||||
conn.execute_unprepared(
|
||||
"CREATE UNIQUE INDEX idx_doctor_schedule_unique_slot ON doctor_schedule (tenant_id, doctor_id, schedule_date, start_time)"
|
||||
).await?;
|
||||
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE patient_tag_relation DROP COLUMN IF EXISTS version"
|
||||
).await?;
|
||||
conn.execute_unprepared("ALTER TABLE patient_tag_relation DROP COLUMN IF EXISTS version")
|
||||
.await?;
|
||||
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE patient_doctor_relation DROP COLUMN IF EXISTS version"
|
||||
).await?;
|
||||
"ALTER TABLE patient_doctor_relation DROP COLUMN IF EXISTS version",
|
||||
)
|
||||
.await?;
|
||||
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE follow_up_task DROP CONSTRAINT IF EXISTS fk_follow_up_task_appointment"
|
||||
).await?;
|
||||
"ALTER TABLE follow_up_task DROP CONSTRAINT IF EXISTS fk_follow_up_task_appointment",
|
||||
)
|
||||
.await?;
|
||||
|
||||
conn.execute_unprepared(
|
||||
"DROP INDEX IF EXISTS idx_lab_report_tenant_type"
|
||||
).await?;
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_lab_report_tenant_type")
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -9,21 +9,22 @@ impl MigrationTrait for Migration {
|
||||
let db = manager.get_connection();
|
||||
|
||||
// 删除旧索引(缺少 tenant_id 前导列)
|
||||
db.execute_unprepared(
|
||||
"DROP INDEX IF EXISTS idx_health_trend_patient_period"
|
||||
).await?;
|
||||
db.execute_unprepared("DROP INDEX IF EXISTS idx_health_trend_patient_period")
|
||||
.await?;
|
||||
|
||||
// 重建为包含 tenant_id 的正确索引
|
||||
db.execute_unprepared(
|
||||
"CREATE INDEX IF NOT EXISTS idx_health_trend_tenant_patient_period \
|
||||
ON health_trend (tenant_id, patient_id, period_start DESC)"
|
||||
).await?;
|
||||
ON health_trend (tenant_id, patient_id, period_start DESC)",
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 添加 follow_up_record 缺失的 (tenant_id, executed_date) 索引
|
||||
db.execute_unprepared(
|
||||
"CREATE INDEX IF NOT EXISTS idx_follow_up_record_tenant_executed_date \
|
||||
ON follow_up_record (tenant_id, executed_date DESC)"
|
||||
).await?;
|
||||
ON follow_up_record (tenant_id, executed_date DESC)",
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -31,18 +32,17 @@ impl MigrationTrait for Migration {
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
|
||||
db.execute_unprepared(
|
||||
"DROP INDEX IF EXISTS idx_health_trend_tenant_patient_period"
|
||||
).await?;
|
||||
db.execute_unprepared("DROP INDEX IF EXISTS idx_health_trend_tenant_patient_period")
|
||||
.await?;
|
||||
|
||||
db.execute_unprepared(
|
||||
"DROP INDEX IF EXISTS idx_follow_up_record_tenant_executed_date"
|
||||
).await?;
|
||||
db.execute_unprepared("DROP INDEX IF EXISTS idx_follow_up_record_tenant_executed_date")
|
||||
.await?;
|
||||
|
||||
db.execute_unprepared(
|
||||
"CREATE INDEX IF NOT EXISTS idx_health_trend_patient_period \
|
||||
ON health_trend (patient_id, period_start)"
|
||||
).await?;
|
||||
ON health_trend (patient_id, period_start)",
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -15,11 +15,7 @@ impl MigrationTrait for Migration {
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Alias::new("patient"))
|
||||
.add_column(
|
||||
ColumnDef::new(Alias::new("id_number_hash"))
|
||||
.string()
|
||||
.null(),
|
||||
)
|
||||
.add_column(ColumnDef::new(Alias::new("id_number_hash")).string().null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -18,10 +18,8 @@ impl MigrationTrait for Migration {
|
||||
.await?;
|
||||
|
||||
// 加宽 id_number 列:varchar(20) → varchar(255),容纳 AES-256-GCM 加密值(~88 字符)
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE patient ALTER COLUMN id_number TYPE varchar(255)",
|
||||
)
|
||||
.await?;
|
||||
conn.execute_unprepared("ALTER TABLE patient ALTER COLUMN id_number TYPE varchar(255)")
|
||||
.await?;
|
||||
|
||||
// 重建唯一索引(partial,排除软删除和空值)
|
||||
conn.execute_unprepared(
|
||||
@@ -37,10 +35,8 @@ impl MigrationTrait for Migration {
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_patient_tenant_id_number")
|
||||
.await?;
|
||||
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE patient ALTER COLUMN id_number TYPE varchar(20)",
|
||||
)
|
||||
.await?;
|
||||
conn.execute_unprepared("ALTER TABLE patient ALTER COLUMN id_number TYPE varchar(20)")
|
||||
.await?;
|
||||
|
||||
conn.execute_unprepared(
|
||||
"CREATE UNIQUE INDEX idx_patient_tenant_id_number ON patient (tenant_id, id_number) WHERE deleted_at IS NULL AND id_number IS NOT NULL",
|
||||
|
||||
@@ -13,16 +13,37 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(Alias::new("dialysis_record"))
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("patient_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("dialysis_date")).date().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("dialysis_date"))
|
||||
.date()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("start_time")).time())
|
||||
.col(ColumnDef::new(Alias::new("end_time")).time())
|
||||
// 体重 (Decimal 5,1)
|
||||
.col(ColumnDef::new(Alias::new("dry_weight")).decimal().extra("CHECK(dry_weight IS NULL OR dry_weight >= 0)"))
|
||||
.col(ColumnDef::new(Alias::new("pre_weight")).decimal().extra("CHECK(pre_weight IS NULL OR pre_weight >= 0)"))
|
||||
.col(ColumnDef::new(Alias::new("post_weight")).decimal().extra("CHECK(post_weight IS NULL OR post_weight >= 0)"))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("dry_weight"))
|
||||
.decimal()
|
||||
.extra("CHECK(dry_weight IS NULL OR dry_weight >= 0)"),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("pre_weight"))
|
||||
.decimal()
|
||||
.extra("CHECK(pre_weight IS NULL OR pre_weight >= 0)"),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("post_weight"))
|
||||
.decimal()
|
||||
.extra("CHECK(post_weight IS NULL OR post_weight >= 0)"),
|
||||
)
|
||||
// 血压
|
||||
.col(ColumnDef::new(Alias::new("pre_bp_systolic")).integer())
|
||||
.col(ColumnDef::new(Alias::new("pre_bp_diastolic")).integer())
|
||||
@@ -36,20 +57,45 @@ impl MigrationTrait for Migration {
|
||||
.col(ColumnDef::new(Alias::new("dialysis_duration")).integer())
|
||||
.col(ColumnDef::new(Alias::new("blood_flow_rate")).integer())
|
||||
// HD / HDF / HF
|
||||
.col(ColumnDef::new(Alias::new("dialysis_type")).string().not_null().default("HD"))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("dialysis_type"))
|
||||
.string()
|
||||
.not_null()
|
||||
.default("HD"),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("symptoms")).json())
|
||||
.col(ColumnDef::new(Alias::new("complication_notes")).text())
|
||||
// draft / completed / reviewed
|
||||
.col(ColumnDef::new(Alias::new("status")).string().not_null().default("draft"))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("status"))
|
||||
.string()
|
||||
.not_null()
|
||||
.default("draft"),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("reviewed_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("reviewed_at")).timestamp_with_time_zone())
|
||||
// 标准字段
|
||||
.col(ColumnDef::new(Alias::new("created_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(ColumnDef::new(Alias::new("updated_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("version")).integer().not_null().default(1))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
@@ -84,7 +130,11 @@ impl MigrationTrait for Migration {
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Alias::new("lab_report"))
|
||||
.add_column(ColumnDef::new(Alias::new("source")).string().default("manual_input"))
|
||||
.add_column(
|
||||
ColumnDef::new(Alias::new("source"))
|
||||
.string()
|
||||
.default("manual_input"),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
@@ -94,7 +144,12 @@ impl MigrationTrait for Migration {
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Alias::new("lab_report"))
|
||||
.add_column(ColumnDef::new(Alias::new("status")).string().not_null().default("pending"))
|
||||
.add_column(
|
||||
ColumnDef::new(Alias::new("status"))
|
||||
.string()
|
||||
.not_null()
|
||||
.default("pending"),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
@@ -112,37 +167,53 @@ impl MigrationTrait for Migration {
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Alias::new("lab_report"))
|
||||
.add_column(ColumnDef::new(Alias::new("reviewed_at")).timestamp_with_time_zone())
|
||||
.add_column(
|
||||
ColumnDef::new(Alias::new("reviewed_at")).timestamp_with_time_zone(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 重命名 indicators → items (V2 JSON 结构含 name/value/unit/reference_low/reference_high/is_abnormal)
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"ALTER TABLE lab_report RENAME COLUMN indicators TO items".to_string(),
|
||||
)).await?;
|
||||
manager
|
||||
.get_connection()
|
||||
.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"ALTER TABLE lab_report RENAME COLUMN indicators TO items".to_string(),
|
||||
))
|
||||
.await?;
|
||||
|
||||
// 重命名 doctor_interpretation → doctor_notes
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"ALTER TABLE lab_report RENAME COLUMN doctor_interpretation TO doctor_notes".to_string(),
|
||||
)).await?;
|
||||
manager
|
||||
.get_connection()
|
||||
.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"ALTER TABLE lab_report RENAME COLUMN doctor_interpretation TO doctor_notes"
|
||||
.to_string(),
|
||||
))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// 恢复 lab_report 列名
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"ALTER TABLE lab_report RENAME COLUMN doctor_notes TO doctor_interpretation".to_string(),
|
||||
)).await?;
|
||||
manager
|
||||
.get_connection()
|
||||
.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"ALTER TABLE lab_report RENAME COLUMN doctor_notes TO doctor_interpretation"
|
||||
.to_string(),
|
||||
))
|
||||
.await?;
|
||||
|
||||
manager.get_connection().execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"ALTER TABLE lab_report RENAME COLUMN items TO indicators".to_string(),
|
||||
)).await?;
|
||||
manager
|
||||
.get_connection()
|
||||
.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"ALTER TABLE lab_report RENAME COLUMN items TO indicators".to_string(),
|
||||
))
|
||||
.await?;
|
||||
|
||||
// 删除新增列
|
||||
manager
|
||||
@@ -159,7 +230,12 @@ impl MigrationTrait for Migration {
|
||||
|
||||
// 删除 dialysis_record 表
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("dialysis_record")).if_exists().to_owned())
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("dialysis_record"))
|
||||
.if_exists()
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -62,7 +62,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(AiPrompt::CreatedBy).uuid().null())
|
||||
.col(ColumnDef::new(AiPrompt::UpdatedBy).uuid().null())
|
||||
.col(ColumnDef::new(AiPrompt::DeletedAt).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(AiPrompt::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiPrompt::VersionLock)
|
||||
.integer()
|
||||
@@ -92,7 +96,12 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(AiAnalysis::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(AiAnalysis::Id).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(AiAnalysis::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(AiAnalysis::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(AiAnalysis::PatientId).uuid().not_null())
|
||||
.col(
|
||||
@@ -106,7 +115,11 @@ impl MigrationTrait for Migration {
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(AiAnalysis::PromptId).uuid().not_null())
|
||||
.col(ColumnDef::new(AiAnalysis::PromptVersion).integer().not_null())
|
||||
.col(
|
||||
ColumnDef::new(AiAnalysis::PromptVersion)
|
||||
.integer()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiAnalysis::ModelUsed)
|
||||
.string_len(100)
|
||||
|
||||
@@ -13,16 +13,56 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(Alias::new("points_account"))
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("patient_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("balance")).integer().not_null().default(0))
|
||||
.col(ColumnDef::new(Alias::new("total_earned")).integer().not_null().default(0))
|
||||
.col(ColumnDef::new(Alias::new("total_spent")).integer().not_null().default(0))
|
||||
.col(ColumnDef::new(Alias::new("total_expired")).integer().not_null().default(0))
|
||||
.col(ColumnDef::new(Alias::new("version")).integer().not_null().default(1))
|
||||
.col(ColumnDef::new(Alias::new("created_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(ColumnDef::new(Alias::new("updated_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("balance"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("total_earned"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("total_spent"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("total_expired"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone())
|
||||
@@ -30,7 +70,15 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.create_index(Index::create().if_not_exists().name("idx_points_account_patient").table(Alias::new("points_account")).col(Alias::new("patient_id")).unique().to_owned())
|
||||
.create_index(
|
||||
Index::create()
|
||||
.if_not_exists()
|
||||
.name("idx_points_account_patient")
|
||||
.table(Alias::new("points_account"))
|
||||
.col(Alias::new("patient_id"))
|
||||
.unique()
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 2. points_rule — 积分获取规则
|
||||
@@ -39,28 +87,93 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(Alias::new("points_rule"))
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("event_type")).string_len(64).not_null())
|
||||
.col(ColumnDef::new(Alias::new("name")).string_len(128).not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("event_type"))
|
||||
.string_len(64)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("name"))
|
||||
.string_len(128)
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("description")).text())
|
||||
.col(ColumnDef::new(Alias::new("points_value")).integer().not_null().default(0))
|
||||
.col(ColumnDef::new(Alias::new("daily_cap")).integer().not_null().default(0))
|
||||
.col(ColumnDef::new(Alias::new("streak_7d_bonus")).integer().not_null().default(0))
|
||||
.col(ColumnDef::new(Alias::new("streak_14d_bonus")).integer().not_null().default(0))
|
||||
.col(ColumnDef::new(Alias::new("streak_30d_bonus")).integer().not_null().default(0))
|
||||
.col(ColumnDef::new(Alias::new("is_active")).boolean().not_null().default(true))
|
||||
.col(ColumnDef::new(Alias::new("created_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(ColumnDef::new(Alias::new("updated_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("points_value"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("daily_cap"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("streak_7d_bonus"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("streak_14d_bonus"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("streak_30d_bonus"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("is_active"))
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(true),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("version")).integer().not_null().default(1))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.create_index(Index::create().if_not_exists().name("idx_points_rule_event_type").table(Alias::new("points_rule")).col(Alias::new("event_type")).to_owned())
|
||||
.create_index(
|
||||
Index::create()
|
||||
.if_not_exists()
|
||||
.name("idx_points_rule_event_type")
|
||||
.table(Alias::new("points_rule"))
|
||||
.col(Alias::new("event_type"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 3. points_transaction — 积分流水(FIFO 桶模型)
|
||||
@@ -69,31 +182,79 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(Alias::new("points_transaction"))
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("account_id")).uuid().not_null())
|
||||
// earn / spend / expired / refund
|
||||
.col(ColumnDef::new(Alias::new("r#type")).string_len(16).not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("r#type"))
|
||||
.string_len(16)
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("amount")).integer().not_null())
|
||||
.col(ColumnDef::new(Alias::new("remaining_amount")).integer().not_null().default(0))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("remaining_amount"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
// active / expired / consumed
|
||||
.col(ColumnDef::new(Alias::new("status")).string_len(16).not_null().default("active"))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("status"))
|
||||
.string_len(16)
|
||||
.not_null()
|
||||
.default("active"),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("expires_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("balance_after")).integer().not_null().default(0))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("balance_after"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("rule_id")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("order_id")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("description")).string_len(256))
|
||||
.col(ColumnDef::new(Alias::new("created_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(ColumnDef::new(Alias::new("updated_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("version")).integer().not_null().default(1))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.create_index(Index::create().if_not_exists().name("idx_points_txn_account").table(Alias::new("points_transaction")).col(Alias::new("account_id")).col(Alias::new("status")).col(Alias::new("expires_at")).to_owned())
|
||||
.create_index(
|
||||
Index::create()
|
||||
.if_not_exists()
|
||||
.name("idx_points_txn_account")
|
||||
.table(Alias::new("points_transaction"))
|
||||
.col(Alias::new("account_id"))
|
||||
.col(Alias::new("status"))
|
||||
.col(Alias::new("expires_at"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 4. points_product — 兑换商品
|
||||
@@ -102,24 +263,72 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(Alias::new("points_product"))
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("name")).string_len(128).not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("name"))
|
||||
.string_len(128)
|
||||
.not_null(),
|
||||
)
|
||||
// physical / service / privilege
|
||||
.col(ColumnDef::new(Alias::new("product_type")).string_len(16).not_null().default("physical"))
|
||||
.col(ColumnDef::new(Alias::new("points_cost")).integer().not_null())
|
||||
.col(ColumnDef::new(Alias::new("stock")).integer().not_null().default(-1))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("product_type"))
|
||||
.string_len(16)
|
||||
.not_null()
|
||||
.default("physical"),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("points_cost"))
|
||||
.integer()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("stock"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(-1),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("image_url")).string_len(512))
|
||||
.col(ColumnDef::new(Alias::new("description")).text())
|
||||
.col(ColumnDef::new(Alias::new("service_config")).json())
|
||||
.col(ColumnDef::new(Alias::new("is_active")).boolean().not_null().default(true))
|
||||
.col(ColumnDef::new(Alias::new("sort_order")).integer().not_null().default(0))
|
||||
.col(ColumnDef::new(Alias::new("created_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(ColumnDef::new(Alias::new("updated_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("is_active"))
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(true),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("sort_order"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("version")).integer().not_null().default(1))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
@@ -130,32 +339,76 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(Alias::new("points_order"))
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("patient_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("product_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("points_cost")).integer().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("points_cost"))
|
||||
.integer()
|
||||
.not_null(),
|
||||
)
|
||||
// pending / verified / cancelled / expired
|
||||
.col(ColumnDef::new(Alias::new("status")).string_len(16).not_null().default("pending"))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("status"))
|
||||
.string_len(16)
|
||||
.not_null()
|
||||
.default("pending"),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("qr_code")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("verified_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("verified_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("expires_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("notes")).string_len(256))
|
||||
.col(ColumnDef::new(Alias::new("created_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(ColumnDef::new(Alias::new("updated_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("version")).integer().not_null().default(1))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.create_index(Index::create().if_not_exists().name("idx_points_order_patient").table(Alias::new("points_order")).col(Alias::new("patient_id")).col(Alias::new("status")).to_owned())
|
||||
.create_index(
|
||||
Index::create()
|
||||
.if_not_exists()
|
||||
.name("idx_points_order_patient")
|
||||
.table(Alias::new("points_order"))
|
||||
.col(Alias::new("patient_id"))
|
||||
.col(Alias::new("status"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.create_index(Index::create().if_not_exists().name("idx_points_order_qr").table(Alias::new("points_order")).col(Alias::new("qr_code")).to_owned())
|
||||
.create_index(
|
||||
Index::create()
|
||||
.if_not_exists()
|
||||
.name("idx_points_order_qr")
|
||||
.table(Alias::new("points_order"))
|
||||
.col(Alias::new("qr_code"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 6. points_checkin — 每日打卡
|
||||
@@ -164,17 +417,41 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(Alias::new("points_checkin"))
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("patient_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("checkin_date")).date().not_null())
|
||||
.col(ColumnDef::new(Alias::new("consecutive_days")).integer().not_null().default(1))
|
||||
.col(ColumnDef::new(Alias::new("created_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("consecutive_days"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.create_index(Index::create().if_not_exists().name("idx_points_checkin_unique").table(Alias::new("points_checkin")).col(Alias::new("patient_id")).col(Alias::new("checkin_date")).unique().to_owned())
|
||||
.create_index(
|
||||
Index::create()
|
||||
.if_not_exists()
|
||||
.name("idx_points_checkin_unique")
|
||||
.table(Alias::new("points_checkin"))
|
||||
.col(Alias::new("patient_id"))
|
||||
.col(Alias::new("checkin_date"))
|
||||
.unique()
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 7. offline_event — 线下活动
|
||||
@@ -183,26 +460,70 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(Alias::new("offline_event"))
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("title")).string_len(256).not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("title"))
|
||||
.string_len(256)
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("description")).text())
|
||||
.col(ColumnDef::new(Alias::new("event_date")).date().not_null())
|
||||
.col(ColumnDef::new(Alias::new("start_time")).time())
|
||||
.col(ColumnDef::new(Alias::new("end_time")).time())
|
||||
.col(ColumnDef::new(Alias::new("location")).string_len(256))
|
||||
.col(ColumnDef::new(Alias::new("points_reward")).integer().not_null().default(0))
|
||||
.col(ColumnDef::new(Alias::new("max_participants")).integer().not_null().default(0))
|
||||
.col(ColumnDef::new(Alias::new("current_participants")).integer().not_null().default(0))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("points_reward"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("max_participants"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("current_participants"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
// draft / published / ongoing / completed / cancelled
|
||||
.col(ColumnDef::new(Alias::new("status")).string_len(16).not_null().default("draft"))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("status"))
|
||||
.string_len(16)
|
||||
.not_null()
|
||||
.default("draft"),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("image_url")).string_len(512))
|
||||
.col(ColumnDef::new(Alias::new("created_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(ColumnDef::new(Alias::new("updated_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("version")).integer().not_null().default(1))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
@@ -213,26 +534,65 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(Alias::new("offline_event_registration"))
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("event_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("patient_id")).uuid().not_null())
|
||||
// registered / checked_in / cancelled
|
||||
.col(ColumnDef::new(Alias::new("status")).string_len(16).not_null().default("registered"))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("status"))
|
||||
.string_len(16)
|
||||
.not_null()
|
||||
.default("registered"),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("checked_in_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("checked_in_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("points_granted")).boolean().not_null().default(false))
|
||||
.col(ColumnDef::new(Alias::new("created_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(ColumnDef::new(Alias::new("updated_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("points_granted"))
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(false),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("version")).integer().not_null().default(1))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.create_index(Index::create().if_not_exists().name("idx_event_reg_unique").table(Alias::new("offline_event_registration")).col(Alias::new("event_id")).col(Alias::new("patient_id")).unique().to_owned())
|
||||
.create_index(
|
||||
Index::create()
|
||||
.if_not_exists()
|
||||
.name("idx_event_reg_unique")
|
||||
.table(Alias::new("offline_event_registration"))
|
||||
.col(Alias::new("event_id"))
|
||||
.col(Alias::new("patient_id"))
|
||||
.unique()
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -12,7 +12,12 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(Alias::new("daily_monitoring"))
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("patient_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("record_date")).date().not_null())
|
||||
@@ -23,21 +28,44 @@ impl MigrationTrait for Migration {
|
||||
.col(ColumnDef::new(Alias::new("evening_bp_systolic")).integer())
|
||||
.col(ColumnDef::new(Alias::new("evening_bp_diastolic")).integer())
|
||||
// 体重 (Decimal 5,1)
|
||||
.col(ColumnDef::new(Alias::new("weight")).decimal().extra("CHECK(weight IS NULL OR weight >= 0)"))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("weight"))
|
||||
.decimal()
|
||||
.extra("CHECK(weight IS NULL OR weight >= 0)"),
|
||||
)
|
||||
// 血糖 (Decimal 4,1)
|
||||
.col(ColumnDef::new(Alias::new("blood_sugar")).decimal().extra("CHECK(blood_sugar IS NULL OR blood_sugar >= 0)"))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("blood_sugar"))
|
||||
.decimal()
|
||||
.extra("CHECK(blood_sugar IS NULL OR blood_sugar >= 0)"),
|
||||
)
|
||||
// 出入量
|
||||
.col(ColumnDef::new(Alias::new("fluid_intake")).integer())
|
||||
.col(ColumnDef::new(Alias::new("urine_output")).integer())
|
||||
// 备注
|
||||
.col(ColumnDef::new(Alias::new("notes")).text())
|
||||
// 标准字段
|
||||
.col(ColumnDef::new(Alias::new("created_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(ColumnDef::new(Alias::new("updated_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("version")).integer().not_null().default(1))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
@@ -74,7 +102,12 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("daily_monitoring")).if_exists().to_owned())
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("daily_monitoring"))
|
||||
.if_exists()
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -22,8 +22,16 @@ impl MigrationTrait for Migration {
|
||||
.col(string("status").not_null().default("active"))
|
||||
.col(uuid_null("diagnosed_by"))
|
||||
.col(string_null("notes"))
|
||||
.col(timestamp_with_time_zone("created_at").not_null().default(Expr::current_timestamp()))
|
||||
.col(timestamp_with_time_zone("updated_at").not_null().default(Expr::current_timestamp()))
|
||||
.col(
|
||||
timestamp_with_time_zone("created_at")
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
timestamp_with_time_zone("updated_at")
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(uuid_null("created_by"))
|
||||
.col(uuid_null("updated_by"))
|
||||
.col(timestamp_with_time_zone_null("deleted_at"))
|
||||
|
||||
@@ -16,11 +16,12 @@ impl MigrationTrait for Migration {
|
||||
.await?;
|
||||
|
||||
// 获取默认租户 ID
|
||||
let result = db.query_one(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"SELECT id::text FROM tenant LIMIT 1".to_string(),
|
||||
))
|
||||
.await?;
|
||||
let result = db
|
||||
.query_one(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"SELECT id::text FROM tenant LIMIT 1".to_string(),
|
||||
))
|
||||
.await?;
|
||||
|
||||
let tid = match result {
|
||||
Some(row) => row.try_get_by_index::<String>(0).unwrap_or_default(),
|
||||
@@ -31,45 +32,330 @@ impl MigrationTrait for Migration {
|
||||
let nil = "NULL";
|
||||
|
||||
// === Directory 节点 ===
|
||||
insert_dir(db, &tid, "a0000000-0000-0000-0000-000000000001", "基础模块", 1, sys).await?;
|
||||
insert_dir(db, &tid, "a0000000-0000-0000-0000-000000000002", "业务模块", 2, sys).await?;
|
||||
insert_dir(db, &tid, "a0000000-0000-0000-0000-000000000003", "健康管理", 3, sys).await?;
|
||||
insert_dir(db, &tid, "a0000000-0000-0000-0000-000000000004", "系统", 4, sys).await?;
|
||||
insert_dir(
|
||||
db,
|
||||
&tid,
|
||||
"a0000000-0000-0000-0000-000000000001",
|
||||
"基础模块",
|
||||
1,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_dir(
|
||||
db,
|
||||
&tid,
|
||||
"a0000000-0000-0000-0000-000000000002",
|
||||
"业务模块",
|
||||
2,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_dir(
|
||||
db,
|
||||
&tid,
|
||||
"a0000000-0000-0000-0000-000000000003",
|
||||
"健康管理",
|
||||
3,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_dir(
|
||||
db,
|
||||
&tid,
|
||||
"a0000000-0000-0000-0000-000000000004",
|
||||
"系统",
|
||||
4,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// === 基础模块菜单 ===
|
||||
let d1 = "a0000000-0000-0000-0000-000000000001";
|
||||
insert_menu(db, &tid, d1, "b0000001-0000-0000-0000-000000000001", "工作台", "/", "HomeOutlined", 0, sys).await?;
|
||||
insert_menu(db, &tid, d1, "b0000001-0000-0000-0000-000000000002", "用户管理", "/users", "UserOutlined", 1, sys).await?;
|
||||
insert_menu(db, &tid, d1, "b0000001-0000-0000-0000-000000000003", "权限管理", "/roles", "SafetyOutlined", 2, sys).await?;
|
||||
insert_menu(db, &tid, d1, "b0000001-0000-0000-0000-000000000004", "组织架构", "/organizations", "ApartmentOutlined", 3, sys).await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d1,
|
||||
"b0000001-0000-0000-0000-000000000001",
|
||||
"工作台",
|
||||
"/",
|
||||
"HomeOutlined",
|
||||
0,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d1,
|
||||
"b0000001-0000-0000-0000-000000000002",
|
||||
"用户管理",
|
||||
"/users",
|
||||
"UserOutlined",
|
||||
1,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d1,
|
||||
"b0000001-0000-0000-0000-000000000003",
|
||||
"权限管理",
|
||||
"/roles",
|
||||
"SafetyOutlined",
|
||||
2,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d1,
|
||||
"b0000001-0000-0000-0000-000000000004",
|
||||
"组织架构",
|
||||
"/organizations",
|
||||
"ApartmentOutlined",
|
||||
3,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// === 业务模块菜单 ===
|
||||
let d2 = "a0000000-0000-0000-0000-000000000002";
|
||||
insert_menu(db, &tid, d2, "b0000002-0000-0000-0000-000000000001", "工作流", "/workflow", "PartitionOutlined", 0, sys).await?;
|
||||
insert_menu(db, &tid, d2, "b0000002-0000-0000-0000-000000000002", "消息中心", "/messages", "MessageOutlined", 1, sys).await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d2,
|
||||
"b0000002-0000-0000-0000-000000000001",
|
||||
"工作流",
|
||||
"/workflow",
|
||||
"PartitionOutlined",
|
||||
0,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d2,
|
||||
"b0000002-0000-0000-0000-000000000002",
|
||||
"消息中心",
|
||||
"/messages",
|
||||
"MessageOutlined",
|
||||
1,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// === 健康管理菜单 ===
|
||||
let d3 = "a0000000-0000-0000-0000-000000000003";
|
||||
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000001", "统计报表", "/health/statistics", "DashboardOutlined", 0, sys).await?;
|
||||
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000002", "患者管理", "/health/patients", "TeamOutlined", 1, sys).await?;
|
||||
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000003", "医护管理", "/health/doctors", "MedicineBoxOutlined", 2, sys).await?;
|
||||
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000004", "预约排班", "/health/appointments", "CalendarOutlined", 3, sys).await?;
|
||||
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000005", "排班管理", "/health/schedules", "HeartOutlined", 4, sys).await?;
|
||||
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000006", "随访管理", "/health/follow-up-tasks", "PhoneOutlined", 5, sys).await?;
|
||||
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000007", "咨询管理", "/health/consultations", "CommentOutlined", 6, sys).await?;
|
||||
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000008", "标签管理", "/health/tags", "TagsOutlined", 7, sys).await?;
|
||||
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000009", "积分规则", "/health/points-rules", "TrophyOutlined", 8, sys).await?;
|
||||
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000010", "商品管理", "/health/points-products", "ShopOutlined", 9, sys).await?;
|
||||
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000011", "订单管理", "/health/points-orders", "FileTextOutlined", 10, sys).await?;
|
||||
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000012", "线下活动", "/health/offline-events", "CalendarOutlined", 11, sys).await?;
|
||||
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000013", "AI Prompt 管理", "/health/ai-prompts", "RobotOutlined", 12, sys).await?;
|
||||
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000014", "AI 分析历史", "/health/ai-analysis", "HistoryOutlined", 13, sys).await?;
|
||||
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000015", "AI 用量统计", "/health/ai-usage", "BarChartOutlined", 14, sys).await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000001",
|
||||
"统计报表",
|
||||
"/health/statistics",
|
||||
"DashboardOutlined",
|
||||
0,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000002",
|
||||
"患者管理",
|
||||
"/health/patients",
|
||||
"TeamOutlined",
|
||||
1,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000003",
|
||||
"医护管理",
|
||||
"/health/doctors",
|
||||
"MedicineBoxOutlined",
|
||||
2,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000004",
|
||||
"预约排班",
|
||||
"/health/appointments",
|
||||
"CalendarOutlined",
|
||||
3,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000005",
|
||||
"排班管理",
|
||||
"/health/schedules",
|
||||
"HeartOutlined",
|
||||
4,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000006",
|
||||
"随访管理",
|
||||
"/health/follow-up-tasks",
|
||||
"PhoneOutlined",
|
||||
5,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000007",
|
||||
"咨询管理",
|
||||
"/health/consultations",
|
||||
"CommentOutlined",
|
||||
6,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000008",
|
||||
"标签管理",
|
||||
"/health/tags",
|
||||
"TagsOutlined",
|
||||
7,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000009",
|
||||
"积分规则",
|
||||
"/health/points-rules",
|
||||
"TrophyOutlined",
|
||||
8,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000010",
|
||||
"商品管理",
|
||||
"/health/points-products",
|
||||
"ShopOutlined",
|
||||
9,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000011",
|
||||
"订单管理",
|
||||
"/health/points-orders",
|
||||
"FileTextOutlined",
|
||||
10,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000012",
|
||||
"线下活动",
|
||||
"/health/offline-events",
|
||||
"CalendarOutlined",
|
||||
11,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000013",
|
||||
"AI Prompt 管理",
|
||||
"/health/ai-prompts",
|
||||
"RobotOutlined",
|
||||
12,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000014",
|
||||
"AI 分析历史",
|
||||
"/health/ai-analysis",
|
||||
"HistoryOutlined",
|
||||
13,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000015",
|
||||
"AI 用量统计",
|
||||
"/health/ai-usage",
|
||||
"BarChartOutlined",
|
||||
14,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// === 系统菜单 ===
|
||||
let d4 = "a0000000-0000-0000-0000-000000000004";
|
||||
insert_menu(db, &tid, d4, "b0000004-0000-0000-0000-000000000001", "系统设置", "/settings", "SettingOutlined", 0, sys).await?;
|
||||
insert_menu(db, &tid, d4, "b0000004-0000-0000-0000-000000000002", "插件管理", "/plugins/admin", "AppstoreOutlined", 1, sys).await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d4,
|
||||
"b0000004-0000-0000-0000-000000000001",
|
||||
"系统设置",
|
||||
"/settings",
|
||||
"SettingOutlined",
|
||||
0,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d4,
|
||||
"b0000004-0000-0000-0000-000000000002",
|
||||
"插件管理",
|
||||
"/plugins/admin",
|
||||
"AppstoreOutlined",
|
||||
1,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let _ = nil;
|
||||
Ok(())
|
||||
|
||||
@@ -42,10 +42,7 @@ impl MigrationTrait for Migration {
|
||||
.not_null()
|
||||
.default("critical"),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(CriticalValueThreshold::Department)
|
||||
.string_len(100),
|
||||
)
|
||||
.col(ColumnDef::new(CriticalValueThreshold::Department).string_len(100))
|
||||
.col(ColumnDef::new(CriticalValueThreshold::AgeMin).integer())
|
||||
.col(ColumnDef::new(CriticalValueThreshold::AgeMax).integer())
|
||||
.col(
|
||||
@@ -68,7 +65,10 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(CriticalValueThreshold::CreatedBy).uuid())
|
||||
.col(ColumnDef::new(CriticalValueThreshold::UpdatedBy).uuid())
|
||||
.col(ColumnDef::new(CriticalValueThreshold::DeletedAt).timestamp_with_time_zone())
|
||||
.col(
|
||||
ColumnDef::new(CriticalValueThreshold::DeletedAt)
|
||||
.timestamp_with_time_zone(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(CriticalValueThreshold::Version)
|
||||
.integer()
|
||||
|
||||
@@ -10,16 +10,19 @@ impl MigrationTrait for Migration {
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Consent::Table)
|
||||
.col(
|
||||
ColumnDef::new(Consent::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Consent::Id).uuid().not_null().primary_key())
|
||||
.col(ColumnDef::new(Consent::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(Consent::PatientId).uuid().not_null())
|
||||
.col(ColumnDef::new(Consent::ConsentType).string_len(50).not_null())
|
||||
.col(ColumnDef::new(Consent::ConsentScope).string_len(100).not_null())
|
||||
.col(
|
||||
ColumnDef::new(Consent::ConsentType)
|
||||
.string_len(50)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Consent::ConsentScope)
|
||||
.string_len(100)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Consent::Status)
|
||||
.string_len(20)
|
||||
@@ -79,6 +82,7 @@ impl MigrationTrait for Migration {
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
enum Consent {
|
||||
Table,
|
||||
Id,
|
||||
|
||||
@@ -24,9 +24,10 @@ impl MigrationTrait for Migration {
|
||||
manager.get_connection().execute_unprepared(sql).await?;
|
||||
|
||||
// 分区表主键必须包含分区键
|
||||
manager.get_connection().execute_unprepared(
|
||||
"ALTER TABLE device_readings ADD PRIMARY KEY (id, measured_at);"
|
||||
).await?;
|
||||
manager
|
||||
.get_connection()
|
||||
.execute_unprepared("ALTER TABLE device_readings ADD PRIMARY KEY (id, measured_at);")
|
||||
.await?;
|
||||
|
||||
// 核心查询索引
|
||||
manager.get_connection().execute_unprepared(
|
||||
@@ -47,7 +48,10 @@ impl MigrationTrait for Migration {
|
||||
let partition_sql = format!(
|
||||
"CREATE TABLE IF NOT EXISTS device_readings_{suffix} PARTITION OF device_readings FOR VALUES FROM ('{start}') TO ('{end}');"
|
||||
);
|
||||
manager.get_connection().execute_unprepared(&partition_sql).await?;
|
||||
manager
|
||||
.get_connection()
|
||||
.execute_unprepared(&partition_sql)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -55,13 +59,16 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
for suffix in ["2026_05", "2026_06", "2026_07", "2026_08"] {
|
||||
manager.get_connection().execute_unprepared(
|
||||
&format!("DROP TABLE IF EXISTS device_readings_{suffix};")
|
||||
).await.ok();
|
||||
manager
|
||||
.get_connection()
|
||||
.execute_unprepared(&format!("DROP TABLE IF EXISTS device_readings_{suffix};"))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
manager.get_connection().execute_unprepared(
|
||||
"DROP TABLE IF EXISTS device_readings;"
|
||||
).await?;
|
||||
manager
|
||||
.get_connection()
|
||||
.execute_unprepared("DROP TABLE IF EXISTS device_readings;")
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,53 +6,97 @@ pub struct Migration;
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("vital_signs_hourly"))
|
||||
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key().default(Expr::cust("gen_random_uuid()")))
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("patient_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("device_type")).string().not_null())
|
||||
.col(ColumnDef::new(Alias::new("hour_start")).timestamp_with_time_zone().not_null())
|
||||
.col(ColumnDef::new(Alias::new("min_val")).double())
|
||||
.col(ColumnDef::new(Alias::new("max_val")).double())
|
||||
.col(ColumnDef::new(Alias::new("avg_val")).double().not_null())
|
||||
.col(ColumnDef::new(Alias::new("sample_count")).integer().not_null().default(1))
|
||||
.col(ColumnDef::new(Alias::new("created_at")).timestamp_with_time_zone().default(Expr::cust("NOW()")))
|
||||
.col(ColumnDef::new(Alias::new("updated_at")).timestamp_with_time_zone().default(Expr::cust("NOW()")))
|
||||
.col(ColumnDef::new(Alias::new("version")).integer().not_null().default(1))
|
||||
.to_owned(),
|
||||
).await?;
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("vital_signs_hourly"))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key()
|
||||
.default(Expr::cust("gen_random_uuid()")),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("patient_id")).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("device_type"))
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("hour_start"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("min_val")).double())
|
||||
.col(ColumnDef::new(Alias::new("max_val")).double())
|
||||
.col(ColumnDef::new(Alias::new("avg_val")).double().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("sample_count"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.default(Expr::cust("NOW()")),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.default(Expr::cust("NOW()")),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// UNIQUE 约束 — 每个患者每个指标每小时一条
|
||||
manager.create_index(
|
||||
Index::create()
|
||||
.name("idx_vsh_unique")
|
||||
.table(Alias::new("vital_signs_hourly"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("patient_id"))
|
||||
.col(Alias::new("device_type"))
|
||||
.col(Alias::new("hour_start"))
|
||||
.unique()
|
||||
.to_owned(),
|
||||
).await?;
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_vsh_unique")
|
||||
.table(Alias::new("vital_signs_hourly"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("patient_id"))
|
||||
.col(Alias::new("device_type"))
|
||||
.col(Alias::new("hour_start"))
|
||||
.unique()
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 查询索引
|
||||
manager.create_index(
|
||||
Index::create()
|
||||
.name("idx_vsh_tenant_patient")
|
||||
.table(Alias::new("vital_signs_hourly"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("patient_id"))
|
||||
.col(Alias::new("device_type"))
|
||||
.col(Alias::new("hour_start"))
|
||||
.to_owned(),
|
||||
).await?;
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_vsh_tenant_patient")
|
||||
.table(Alias::new("vital_signs_hourly"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("patient_id"))
|
||||
.col(Alias::new("device_type"))
|
||||
.col(Alias::new("hour_start"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager.drop_table(Table::drop().table(Alias::new("vital_signs_hourly")).to_owned()).await
|
||||
manager
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("vital_signs_hourly"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,52 +6,87 @@ pub struct Migration;
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("patient_devices"))
|
||||
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key().default(Expr::cust("gen_random_uuid()")))
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("patient_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("device_id")).string().not_null())
|
||||
.col(ColumnDef::new(Alias::new("device_model")).string())
|
||||
.col(ColumnDef::new(Alias::new("device_type")).string())
|
||||
.col(ColumnDef::new(Alias::new("bound_at")).timestamp_with_time_zone().default(Expr::cust("NOW()")))
|
||||
.col(ColumnDef::new(Alias::new("last_sync_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("created_at")).timestamp_with_time_zone().default(Expr::cust("NOW()")))
|
||||
.col(ColumnDef::new(Alias::new("updated_at")).timestamp_with_time_zone().default(Expr::cust("NOW()")))
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("version")).integer().not_null().default(1))
|
||||
.to_owned(),
|
||||
).await?;
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("patient_devices"))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key()
|
||||
.default(Expr::cust("gen_random_uuid()")),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("patient_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("device_id")).string().not_null())
|
||||
.col(ColumnDef::new(Alias::new("device_model")).string())
|
||||
.col(ColumnDef::new(Alias::new("device_type")).string())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("bound_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.default(Expr::cust("NOW()")),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("last_sync_at")).timestamp_with_time_zone())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.default(Expr::cust("NOW()")),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.default(Expr::cust("NOW()")),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 每个患者每个设备只能绑定一次
|
||||
manager.create_index(
|
||||
Index::create()
|
||||
.name("idx_pd_unique")
|
||||
.table(Alias::new("patient_devices"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("patient_id"))
|
||||
.col(Alias::new("device_id"))
|
||||
.unique()
|
||||
.to_owned(),
|
||||
).await?;
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_pd_unique")
|
||||
.table(Alias::new("patient_devices"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("patient_id"))
|
||||
.col(Alias::new("device_id"))
|
||||
.unique()
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 查询索引
|
||||
manager.create_index(
|
||||
Index::create()
|
||||
.name("idx_pd_tenant_patient")
|
||||
.table(Alias::new("patient_devices"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("patient_id"))
|
||||
.to_owned(),
|
||||
).await?;
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_pd_tenant_patient")
|
||||
.table(Alias::new("patient_devices"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("patient_id"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager.drop_table(Table::drop().table(Alias::new("patient_devices")).to_owned()).await
|
||||
manager
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("patient_devices"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,45 +6,102 @@ pub struct Migration;
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("alert_rules"))
|
||||
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key().default(Expr::cust("gen_random_uuid()")))
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("name")).string().not_null())
|
||||
.col(ColumnDef::new(Alias::new("description")).text())
|
||||
.col(ColumnDef::new(Alias::new("device_type")).string().not_null())
|
||||
.col(ColumnDef::new(Alias::new("condition_type")).string().not_null())
|
||||
.col(ColumnDef::new(Alias::new("condition_params")).json_binary().not_null().default(Expr::cust("'{}'::jsonb")))
|
||||
.col(ColumnDef::new(Alias::new("severity")).string().not_null().default("'warning'"))
|
||||
.col(ColumnDef::new(Alias::new("is_active")).boolean().not_null().default(Expr::cust("true")))
|
||||
.col(ColumnDef::new(Alias::new("apply_tags")).json_binary())
|
||||
.col(ColumnDef::new(Alias::new("notify_roles")).json_binary().default(Expr::cust("'[]'::jsonb")))
|
||||
.col(ColumnDef::new(Alias::new("cooldown_minutes")).integer().not_null().default(60))
|
||||
.col(ColumnDef::new(Alias::new("created_at")).timestamp_with_time_zone().default(Expr::cust("NOW()")))
|
||||
.col(ColumnDef::new(Alias::new("updated_at")).timestamp_with_time_zone().default(Expr::cust("NOW()")))
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("version")).integer().not_null().default(1))
|
||||
.to_owned(),
|
||||
).await?;
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("alert_rules"))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key()
|
||||
.default(Expr::cust("gen_random_uuid()")),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("name")).string().not_null())
|
||||
.col(ColumnDef::new(Alias::new("description")).text())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("device_type"))
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("condition_type"))
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("condition_params"))
|
||||
.json_binary()
|
||||
.not_null()
|
||||
.default(Expr::cust("'{}'::jsonb")),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("severity"))
|
||||
.string()
|
||||
.not_null()
|
||||
.default("'warning'"),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("is_active"))
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(Expr::cust("true")),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("apply_tags")).json_binary())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("notify_roles"))
|
||||
.json_binary()
|
||||
.default(Expr::cust("'[]'::jsonb")),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("cooldown_minutes"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(60),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.default(Expr::cust("NOW()")),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.default(Expr::cust("NOW()")),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 查询索引
|
||||
manager.create_index(
|
||||
Index::create()
|
||||
.name("idx_ar_tenant_active")
|
||||
.table(Alias::new("alert_rules"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("is_active"))
|
||||
.col(Alias::new("device_type"))
|
||||
.to_owned(),
|
||||
).await?;
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_ar_tenant_active")
|
||||
.table(Alias::new("alert_rules"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("is_active"))
|
||||
.col(Alias::new("device_type"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager.drop_table(Table::drop().table(Alias::new("alert_rules")).to_owned()).await
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("alert_rules")).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,71 +6,109 @@ pub struct Migration;
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("alerts"))
|
||||
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key().default(Expr::cust("gen_random_uuid()")))
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("patient_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("rule_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("severity")).string().not_null())
|
||||
.col(ColumnDef::new(Alias::new("title")).string().not_null())
|
||||
.col(ColumnDef::new(Alias::new("detail")).json_binary().default(Expr::cust("'{}'::jsonb")))
|
||||
.col(ColumnDef::new(Alias::new("status")).string().not_null().default("'pending'"))
|
||||
.col(ColumnDef::new(Alias::new("acknowledged_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("acknowledged_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("resolved_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("created_at")).timestamp_with_time_zone().default(Expr::cust("NOW()")))
|
||||
.col(ColumnDef::new(Alias::new("updated_at")).timestamp_with_time_zone().default(Expr::cust("NOW()")))
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("version")).integer().not_null().default(1))
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.from(Alias::new("alerts"), Alias::new("rule_id"))
|
||||
.to(Alias::new("alert_rules"), Alias::new("id"))
|
||||
.on_delete(ForeignKeyAction::Restrict)
|
||||
)
|
||||
.to_owned(),
|
||||
).await?;
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Alias::new("alerts"))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key()
|
||||
.default(Expr::cust("gen_random_uuid()")),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("patient_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("rule_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("severity")).string().not_null())
|
||||
.col(ColumnDef::new(Alias::new("title")).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("detail"))
|
||||
.json_binary()
|
||||
.default(Expr::cust("'{}'::jsonb")),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("status"))
|
||||
.string()
|
||||
.not_null()
|
||||
.default("'pending'"),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("acknowledged_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("acknowledged_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("resolved_at")).timestamp_with_time_zone())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.default(Expr::cust("NOW()")),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.default(Expr::cust("NOW()")),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.from(Alias::new("alerts"), Alias::new("rule_id"))
|
||||
.to(Alias::new("alert_rules"), Alias::new("id"))
|
||||
.on_delete(ForeignKeyAction::Restrict),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 按患者查询告警
|
||||
manager.create_index(
|
||||
Index::create()
|
||||
.name("idx_alerts_tenant_patient")
|
||||
.table(Alias::new("alerts"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("patient_id"))
|
||||
.col(Alias::new("created_at"))
|
||||
.to_owned(),
|
||||
).await?;
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_alerts_tenant_patient")
|
||||
.table(Alias::new("alerts"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("patient_id"))
|
||||
.col(Alias::new("created_at"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 按状态筛选
|
||||
manager.create_index(
|
||||
Index::create()
|
||||
.name("idx_alerts_status")
|
||||
.table(Alias::new("alerts"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("status"))
|
||||
.col(Alias::new("created_at"))
|
||||
.to_owned(),
|
||||
).await?;
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_alerts_status")
|
||||
.table(Alias::new("alerts"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("status"))
|
||||
.col(Alias::new("created_at"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 冷却期查询 — 同规则同患者
|
||||
manager.create_index(
|
||||
Index::create()
|
||||
.name("idx_alerts_cooldown")
|
||||
.table(Alias::new("alerts"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("patient_id"))
|
||||
.col(Alias::new("rule_id"))
|
||||
.col(Alias::new("created_at"))
|
||||
.to_owned(),
|
||||
).await?;
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_alerts_cooldown")
|
||||
.table(Alias::new("alerts"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("patient_id"))
|
||||
.col(Alias::new("rule_id"))
|
||||
.col(Alias::new("created_at"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager.drop_table(Table::drop().table(Alias::new("alerts")).to_owned()).await
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("alerts")).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,11 @@ impl MigrationTrait for Migration {
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(TenantCryptoKey::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(TenantCryptoKey::EncryptedDek).string_len(128).not_null())
|
||||
.col(
|
||||
ColumnDef::new(TenantCryptoKey::EncryptedDek)
|
||||
.string_len(128)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(TenantCryptoKey::KeyVersion)
|
||||
.integer()
|
||||
|
||||
@@ -11,14 +11,38 @@ impl MigrationTrait for Migration {
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Article::Table)
|
||||
.add_column(ColumnDef::new(Article::Status).string_len(20).not_null().default("draft"))
|
||||
.add_column(
|
||||
ColumnDef::new(Article::Status)
|
||||
.string_len(20)
|
||||
.not_null()
|
||||
.default("draft"),
|
||||
)
|
||||
.add_column(ColumnDef::new(Article::Slug).string_len(200).null())
|
||||
.add_column(ColumnDef::new(Article::ContentType).string_len(20).not_null().default("rich_text"))
|
||||
.add_column(
|
||||
ColumnDef::new(Article::ContentType)
|
||||
.string_len(20)
|
||||
.not_null()
|
||||
.default("rich_text"),
|
||||
)
|
||||
.add_column(ColumnDef::new(Article::ReviewedBy).uuid().null())
|
||||
.add_column(ColumnDef::new(Article::ReviewedAt).timestamp_with_time_zone().null())
|
||||
.add_column(
|
||||
ColumnDef::new(Article::ReviewedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.add_column(ColumnDef::new(Article::ReviewNote).text().null())
|
||||
.add_column(ColumnDef::new(Article::ViewCount).integer().not_null().default(0))
|
||||
.add_column(ColumnDef::new(Article::SortOrder).integer().not_null().default(0))
|
||||
.add_column(
|
||||
ColumnDef::new(Article::ViewCount)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.add_column(
|
||||
ColumnDef::new(Article::SortOrder)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.add_column(ColumnDef::new(Article::CategoryId).uuid().null())
|
||||
.to_owned(),
|
||||
)
|
||||
@@ -37,19 +61,52 @@ impl MigrationTrait for Migration {
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(ArticleCategory::Table)
|
||||
.col(ColumnDef::new(ArticleCategory::Id).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(ArticleCategory::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(ArticleCategory::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(ArticleCategory::Name).string_len(100).not_null())
|
||||
.col(
|
||||
ColumnDef::new(ArticleCategory::Name)
|
||||
.string_len(100)
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(ArticleCategory::Slug).string_len(100).null())
|
||||
.col(ColumnDef::new(ArticleCategory::ParentId).uuid().null())
|
||||
.col(ColumnDef::new(ArticleCategory::Description).text().null())
|
||||
.col(ColumnDef::new(ArticleCategory::SortOrder).integer().not_null().default(0))
|
||||
.col(ColumnDef::new(ArticleCategory::CreatedAt).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(ColumnDef::new(ArticleCategory::UpdatedAt).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(
|
||||
ColumnDef::new(ArticleCategory::SortOrder)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ArticleCategory::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ArticleCategory::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(ArticleCategory::CreatedBy).uuid().null())
|
||||
.col(ColumnDef::new(ArticleCategory::UpdatedBy).uuid().null())
|
||||
.col(ColumnDef::new(ArticleCategory::DeletedAt).timestamp_with_time_zone().null())
|
||||
.col(ColumnDef::new(ArticleCategory::Version).integer().not_null().default(1))
|
||||
.col(
|
||||
ColumnDef::new(ArticleCategory::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ArticleCategory::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
@@ -69,15 +126,39 @@ impl MigrationTrait for Migration {
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(ArticleTag::Table)
|
||||
.col(ColumnDef::new(ArticleTag::Id).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(ArticleTag::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(ArticleTag::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(ArticleTag::Name).string_len(50).not_null())
|
||||
.col(ColumnDef::new(ArticleTag::CreatedAt).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(ColumnDef::new(ArticleTag::UpdatedAt).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(
|
||||
ColumnDef::new(ArticleTag::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ArticleTag::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(ArticleTag::CreatedBy).uuid().null())
|
||||
.col(ColumnDef::new(ArticleTag::UpdatedBy).uuid().null())
|
||||
.col(ColumnDef::new(ArticleTag::DeletedAt).timestamp_with_time_zone().null())
|
||||
.col(ColumnDef::new(ArticleTag::Version).integer().not_null().default(1))
|
||||
.col(
|
||||
ColumnDef::new(ArticleTag::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ArticleTag::Version)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
@@ -97,7 +178,11 @@ impl MigrationTrait for Migration {
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(ArticleArticleTag::Table)
|
||||
.col(ColumnDef::new(ArticleArticleTag::ArticleId).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(ArticleArticleTag::ArticleId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(ArticleArticleTag::TagId).uuid().not_null())
|
||||
.primary_key(
|
||||
Index::create()
|
||||
@@ -113,15 +198,33 @@ impl MigrationTrait for Migration {
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(ArticleRevision::Table)
|
||||
.col(ColumnDef::new(ArticleRevision::Id).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(ArticleRevision::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(ArticleRevision::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(ArticleRevision::ArticleId).uuid().not_null())
|
||||
.col(ColumnDef::new(ArticleRevision::RevisionNumber).integer().not_null())
|
||||
.col(ColumnDef::new(ArticleRevision::Title).string_len(255).not_null())
|
||||
.col(
|
||||
ColumnDef::new(ArticleRevision::RevisionNumber)
|
||||
.integer()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ArticleRevision::Title)
|
||||
.string_len(255)
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(ArticleRevision::Content).text().not_null())
|
||||
.col(ColumnDef::new(ArticleRevision::Summary).text().null())
|
||||
.col(ColumnDef::new(ArticleRevision::CreatedBy).uuid().null())
|
||||
.col(ColumnDef::new(ArticleRevision::CreatedAt).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(
|
||||
ColumnDef::new(ArticleRevision::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
@@ -140,10 +243,18 @@ impl MigrationTrait for Migration {
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager.drop_table(Table::drop().table(ArticleRevision::Table).to_owned()).await?;
|
||||
manager.drop_table(Table::drop().table(ArticleArticleTag::Table).to_owned()).await?;
|
||||
manager.drop_table(Table::drop().table(ArticleTag::Table).to_owned()).await?;
|
||||
manager.drop_table(Table::drop().table(ArticleCategory::Table).to_owned()).await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(ArticleRevision::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(ArticleArticleTag::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(ArticleTag::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(ArticleCategory::Table).to_owned())
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.alter_table(
|
||||
|
||||
@@ -10,7 +10,11 @@ impl MigrationTrait for Migration {
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Patient::Table)
|
||||
.add_column(ColumnDef::new(Patient::EmergencyContactPhoneHash).string_len(64).null())
|
||||
.add_column(
|
||||
ColumnDef::new(Patient::EmergencyContactPhoneHash)
|
||||
.string_len(64)
|
||||
.null(),
|
||||
)
|
||||
.add_column(ColumnDef::new(Patient::KeyVersion).integer().null())
|
||||
.to_owned(),
|
||||
)
|
||||
@@ -31,7 +35,11 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_index(Index::drop().name("idx_patient_emergency_phone_hash").to_owned())
|
||||
.drop_index(
|
||||
Index::drop()
|
||||
.name("idx_patient_emergency_phone_hash")
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
|
||||
@@ -10,7 +10,11 @@ impl MigrationTrait for Migration {
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(ConsultationMessage::Table)
|
||||
.add_column(ColumnDef::new(ConsultationMessage::KeyVersion).integer().null())
|
||||
.add_column(
|
||||
ColumnDef::new(ConsultationMessage::KeyVersion)
|
||||
.integer()
|
||||
.null(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -10,8 +10,16 @@ impl MigrationTrait for Migration {
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(PatientFamilyMember::Table)
|
||||
.add_column(ColumnDef::new(PatientFamilyMember::PhoneHash).string_len(64).null())
|
||||
.add_column(ColumnDef::new(PatientFamilyMember::KeyVersion).integer().null())
|
||||
.add_column(
|
||||
ColumnDef::new(PatientFamilyMember::PhoneHash)
|
||||
.string_len(64)
|
||||
.null(),
|
||||
)
|
||||
.add_column(
|
||||
ColumnDef::new(PatientFamilyMember::KeyVersion)
|
||||
.integer()
|
||||
.null(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
@@ -31,7 +39,11 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_index(Index::drop().name("idx_family_member_phone_hash").to_owned())
|
||||
.drop_index(
|
||||
Index::drop()
|
||||
.name("idx_family_member_phone_hash")
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
|
||||
@@ -10,7 +10,11 @@ impl MigrationTrait for Migration {
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(DoctorProfile::Table)
|
||||
.add_column(ColumnDef::new(DoctorProfile::LicenseNumberHash).string_len(64).null())
|
||||
.add_column(
|
||||
ColumnDef::new(DoctorProfile::LicenseNumberHash)
|
||||
.string_len(64)
|
||||
.null(),
|
||||
)
|
||||
.add_column(ColumnDef::new(DoctorProfile::KeyVersion).integer().null())
|
||||
.to_owned(),
|
||||
)
|
||||
@@ -31,7 +35,11 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_index(Index::drop().name("idx_doctor_profile_license_hash").to_owned())
|
||||
.drop_index(
|
||||
Index::drop()
|
||||
.name("idx_doctor_profile_license_hash")
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
|
||||
@@ -18,10 +18,8 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.await?;
|
||||
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE vital_signs ADD COLUMN IF NOT EXISTS spo2 INTEGER",
|
||||
)
|
||||
.await?;
|
||||
conn.execute_unprepared("ALTER TABLE vital_signs ADD COLUMN IF NOT EXISTS spo2 INTEGER")
|
||||
.await?;
|
||||
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE vital_signs ADD COLUMN IF NOT EXISTS blood_sugar_type VARCHAR(20) DEFAULT 'fasting'",
|
||||
@@ -34,20 +32,14 @@ impl MigrationTrait for Migration {
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let conn = manager.get_connection();
|
||||
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE vital_signs DROP COLUMN IF EXISTS blood_sugar_type",
|
||||
)
|
||||
.await?;
|
||||
conn.execute_unprepared("ALTER TABLE vital_signs DROP COLUMN IF EXISTS blood_sugar_type")
|
||||
.await?;
|
||||
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE vital_signs DROP COLUMN IF EXISTS spo2",
|
||||
)
|
||||
.await?;
|
||||
conn.execute_unprepared("ALTER TABLE vital_signs DROP COLUMN IF EXISTS spo2")
|
||||
.await?;
|
||||
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE vital_signs DROP COLUMN IF EXISTS body_temperature",
|
||||
)
|
||||
.await?;
|
||||
conn.execute_unprepared("ALTER TABLE vital_signs DROP COLUMN IF EXISTS body_temperature")
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -11,18 +11,40 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(Alias::new("medication_record"))
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("patient_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("medication_name")).string_len(200).not_null())
|
||||
.col(ColumnDef::new(Alias::new("generic_name")).string_len(200).null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("medication_name"))
|
||||
.string_len(200)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("generic_name"))
|
||||
.string_len(200)
|
||||
.null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("dosage")).string_len(50).null())
|
||||
.col(ColumnDef::new(Alias::new("unit")).string_len(20).null())
|
||||
.col(ColumnDef::new(Alias::new("frequency")).string_len(20).null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("frequency"))
|
||||
.string_len(20)
|
||||
.null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("route")).string_len(20).null())
|
||||
.col(ColumnDef::new(Alias::new("start_date")).date().null())
|
||||
.col(ColumnDef::new(Alias::new("end_date")).date().null())
|
||||
.col(ColumnDef::new(Alias::new("is_current")).boolean().not_null().default(true))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("is_current"))
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(true),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("prescribed_by")).uuid().null())
|
||||
.col(ColumnDef::new(Alias::new("notes")).text().null())
|
||||
.col(
|
||||
@@ -39,7 +61,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid().null())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid().null())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("deleted_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
@@ -80,7 +106,11 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("medication_record")).to_owned())
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("medication_record"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,39 +11,104 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(Alias::new("dialysis_prescription"))
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("patient_id")).uuid().not_null())
|
||||
// 透析器型号
|
||||
.col(ColumnDef::new(Alias::new("dialyzer_model")).string_len(100).null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("dialyzer_model"))
|
||||
.string_len(100)
|
||||
.null(),
|
||||
)
|
||||
// 膜面积 (m²)
|
||||
.col(ColumnDef::new(Alias::new("membrane_area")).decimal_len(5, 2).null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("membrane_area"))
|
||||
.decimal_len(5, 2)
|
||||
.null(),
|
||||
)
|
||||
// 透析液钾浓度 (mmol/L)
|
||||
.col(ColumnDef::new(Alias::new("dialysate_potassium")).decimal_len(5, 2).null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("dialysate_potassium"))
|
||||
.decimal_len(5, 2)
|
||||
.null(),
|
||||
)
|
||||
// 透析液钙浓度 (mmol/L)
|
||||
.col(ColumnDef::new(Alias::new("dialysate_calcium")).decimal_len(5, 2).null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("dialysate_calcium"))
|
||||
.decimal_len(5, 2)
|
||||
.null(),
|
||||
)
|
||||
// 透析液碳酸氢盐浓度 (mmol/L)
|
||||
.col(ColumnDef::new(Alias::new("dialysate_bicarbonate")).decimal_len(5, 2).null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("dialysate_bicarbonate"))
|
||||
.decimal_len(5, 2)
|
||||
.null(),
|
||||
)
|
||||
// 抗凝方式: heparin/lmwh/heparin_free
|
||||
.col(ColumnDef::new(Alias::new("anticoagulation_type")).string_len(20).null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("anticoagulation_type"))
|
||||
.string_len(20)
|
||||
.null(),
|
||||
)
|
||||
// 抗凝剂剂量
|
||||
.col(ColumnDef::new(Alias::new("anticoagulation_dose")).string_len(50).null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("anticoagulation_dose"))
|
||||
.string_len(50)
|
||||
.null(),
|
||||
)
|
||||
// 目标超滤量 (ml)
|
||||
.col(ColumnDef::new(Alias::new("target_ultrafiltration_ml")).integer().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("target_ultrafiltration_ml"))
|
||||
.integer()
|
||||
.null(),
|
||||
)
|
||||
// 目标干体重 (kg)
|
||||
.col(ColumnDef::new(Alias::new("target_dry_weight")).decimal_len(5, 2).null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("target_dry_weight"))
|
||||
.decimal_len(5, 2)
|
||||
.null(),
|
||||
)
|
||||
// 血流量 (ml/min)
|
||||
.col(ColumnDef::new(Alias::new("blood_flow_rate")).integer().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("blood_flow_rate"))
|
||||
.integer()
|
||||
.null(),
|
||||
)
|
||||
// 透析液流量 (ml/min)
|
||||
.col(ColumnDef::new(Alias::new("dialysate_flow_rate")).integer().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("dialysate_flow_rate"))
|
||||
.integer()
|
||||
.null(),
|
||||
)
|
||||
// 每周透析频次
|
||||
.col(ColumnDef::new(Alias::new("frequency_per_week")).integer().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("frequency_per_week"))
|
||||
.integer()
|
||||
.null(),
|
||||
)
|
||||
// 每次透析时长 (分钟)
|
||||
.col(ColumnDef::new(Alias::new("duration_minutes")).integer().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("duration_minutes"))
|
||||
.integer()
|
||||
.null(),
|
||||
)
|
||||
// 血管通路类型: avf/avg/cvc
|
||||
.col(ColumnDef::new(Alias::new("vascular_access_type")).string_len(20).null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("vascular_access_type"))
|
||||
.string_len(20)
|
||||
.null(),
|
||||
)
|
||||
// 血管通路位置
|
||||
.col(ColumnDef::new(Alias::new("vascular_access_location")).string_len(100).null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("vascular_access_location"))
|
||||
.string_len(100)
|
||||
.null(),
|
||||
)
|
||||
// 生效日期
|
||||
.col(ColumnDef::new(Alias::new("effective_from")).date().null())
|
||||
// 失效日期
|
||||
@@ -74,7 +139,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid().null())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid().null())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("deleted_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
@@ -127,7 +196,11 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("dialysis_prescription")).to_owned())
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("dialysis_prescription"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,12 @@ impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
|
||||
let result = db.query_one(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"SELECT id::text FROM tenant LIMIT 1".to_string(),
|
||||
))
|
||||
.await?;
|
||||
let result = db
|
||||
.query_one(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"SELECT id::text FROM tenant LIMIT 1".to_string(),
|
||||
))
|
||||
.await?;
|
||||
|
||||
let tid = match result {
|
||||
Some(row) => row.try_get_by_index::<String>(0).unwrap_or_default(),
|
||||
|
||||
@@ -12,14 +12,27 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(Alias::new("follow_up_template"))
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
// 模板名称
|
||||
.col(ColumnDef::new(Alias::new("name")).string_len(200).not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("name"))
|
||||
.string_len(200)
|
||||
.not_null(),
|
||||
)
|
||||
// 模板描述
|
||||
.col(ColumnDef::new(Alias::new("description")).text().null())
|
||||
// 随访类型: phone/outpatient/home_visit/online/wechat
|
||||
.col(ColumnDef::new(Alias::new("follow_up_type")).string_len(20).not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("follow_up_type"))
|
||||
.string_len(20)
|
||||
.not_null(),
|
||||
)
|
||||
// 适用疾病/科室(JSON 数组)
|
||||
.col(ColumnDef::new(Alias::new("applicable_scope")).text().null())
|
||||
// 状态: active/disabled
|
||||
@@ -44,7 +57,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid().null())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid().null())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("deleted_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
@@ -72,15 +89,32 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(Alias::new("follow_up_template_field"))
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("template_id")).uuid().not_null())
|
||||
// 字段标签
|
||||
.col(ColumnDef::new(Alias::new("label")).string_len(200).not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("label"))
|
||||
.string_len(200)
|
||||
.not_null(),
|
||||
)
|
||||
// 字段键名(用于程序引用)
|
||||
.col(ColumnDef::new(Alias::new("field_key")).string_len(100).not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("field_key"))
|
||||
.string_len(100)
|
||||
.not_null(),
|
||||
)
|
||||
// 字段类型: text/number/date/select/checkbox/textarea/scale
|
||||
.col(ColumnDef::new(Alias::new("field_type")).string_len(20).not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("field_type"))
|
||||
.string_len(20)
|
||||
.not_null(),
|
||||
)
|
||||
// 是否必填
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("required"))
|
||||
@@ -91,7 +125,11 @@ impl MigrationTrait for Migration {
|
||||
// 选项(JSON 数组,select/checkbox 时使用)
|
||||
.col(ColumnDef::new(Alias::new("options")).text().null())
|
||||
// 占位提示
|
||||
.col(ColumnDef::new(Alias::new("placeholder")).string_len(200).null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("placeholder"))
|
||||
.string_len(200)
|
||||
.null(),
|
||||
)
|
||||
// 校验规则(JSON)
|
||||
.col(ColumnDef::new(Alias::new("validation")).text().null())
|
||||
// 排序序号
|
||||
@@ -116,7 +154,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid().null())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid().null())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("deleted_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
@@ -154,10 +196,18 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("follow_up_template_field")).to_owned())
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("follow_up_template_field"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("follow_up_template")).to_owned())
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("follow_up_template"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,17 +12,48 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(Alias::new("domain_events_archive"))
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("event_type")).string_len(200).not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("event_type"))
|
||||
.string_len(200)
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("payload")).json().null())
|
||||
.col(ColumnDef::new(Alias::new("correlation_id")).uuid().null())
|
||||
.col(ColumnDef::new(Alias::new("status")).string_len(20).not_null())
|
||||
.col(ColumnDef::new(Alias::new("attempts")).integer().not_null().default(0))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("status"))
|
||||
.string_len(20)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("attempts"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("last_error")).text().null())
|
||||
.col(ColumnDef::new(Alias::new("created_at")).timestamp_with_time_zone().not_null())
|
||||
.col(ColumnDef::new(Alias::new("published_at")).timestamp_with_time_zone().null())
|
||||
.col(ColumnDef::new(Alias::new("archived_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("published_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("archived_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
@@ -85,7 +116,11 @@ impl MigrationTrait for Migration {
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("domain_events_archive")).to_owned())
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("domain_events_archive"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -12,9 +12,22 @@ impl MigrationTrait for Migration {
|
||||
.table(Alias::new("processed_events"))
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Alias::new("event_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("consumer_id")).string_len(200).not_null())
|
||||
.col(ColumnDef::new(Alias::new("processed_at")).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.primary_key(Index::create().col(Alias::new("event_id")).col(Alias::new("consumer_id")))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("consumer_id"))
|
||||
.string_len(200)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("processed_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.primary_key(
|
||||
Index::create()
|
||||
.col(Alias::new("event_id"))
|
||||
.col(Alias::new("consumer_id")),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
@@ -56,7 +69,11 @@ impl MigrationTrait for Migration {
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("processed_events")).to_owned())
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("processed_events"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -37,7 +37,8 @@ impl MigrationTrait for Migration {
|
||||
END;
|
||||
$$;
|
||||
"#,
|
||||
).await?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -66,7 +67,8 @@ impl MigrationTrait for Migration {
|
||||
END;
|
||||
$$;
|
||||
"#,
|
||||
).await?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -8,15 +8,11 @@ impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let conn = manager.get_connection();
|
||||
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS prev_hash TEXT",
|
||||
)
|
||||
.await?;
|
||||
conn.execute_unprepared("ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS prev_hash TEXT")
|
||||
.await?;
|
||||
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS record_hash TEXT",
|
||||
)
|
||||
.await?;
|
||||
conn.execute_unprepared("ALTER TABLE audit_logs ADD COLUMN IF NOT EXISTS record_hash TEXT")
|
||||
.await?;
|
||||
|
||||
// 为 record_hash 创建索引(用于快速查找最新哈希)
|
||||
conn.execute_unprepared(
|
||||
@@ -38,25 +34,17 @@ impl MigrationTrait for Migration {
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let conn = manager.get_connection();
|
||||
|
||||
conn.execute_unprepared(
|
||||
"DROP INDEX IF EXISTS idx_audit_logs_tenant_created",
|
||||
)
|
||||
.await?;
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_audit_logs_tenant_created")
|
||||
.await?;
|
||||
|
||||
conn.execute_unprepared(
|
||||
"DROP INDEX IF EXISTS idx_audit_logs_record_hash",
|
||||
)
|
||||
.await?;
|
||||
conn.execute_unprepared("DROP INDEX IF EXISTS idx_audit_logs_record_hash")
|
||||
.await?;
|
||||
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE audit_logs DROP COLUMN IF EXISTS record_hash",
|
||||
)
|
||||
.await?;
|
||||
conn.execute_unprepared("ALTER TABLE audit_logs DROP COLUMN IF EXISTS record_hash")
|
||||
.await?;
|
||||
|
||||
conn.execute_unprepared(
|
||||
"ALTER TABLE audit_logs DROP COLUMN IF EXISTS prev_hash",
|
||||
)
|
||||
.await?;
|
||||
conn.execute_unprepared("ALTER TABLE audit_logs DROP COLUMN IF EXISTS prev_hash")
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -37,7 +37,8 @@ impl MigrationTrait for Migration {
|
||||
END;
|
||||
$$;
|
||||
"#,
|
||||
).await?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -73,7 +74,8 @@ impl MigrationTrait for Migration {
|
||||
END;
|
||||
$$;
|
||||
"#,
|
||||
).await?;
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -88,10 +88,7 @@ impl MigrationTrait for Migration {
|
||||
.default("pending"),
|
||||
)
|
||||
.col(ColumnDef::new(CriticalAlert::AcknowledgedBy).uuid())
|
||||
.col(
|
||||
ColumnDef::new(CriticalAlert::AcknowledgedAt)
|
||||
.timestamp_with_time_zone(),
|
||||
)
|
||||
.col(ColumnDef::new(CriticalAlert::AcknowledgedAt).timestamp_with_time_zone())
|
||||
.col(
|
||||
ColumnDef::new(CriticalAlert::EscalationLevel)
|
||||
.small_integer()
|
||||
@@ -182,8 +179,7 @@ impl MigrationTrait for Migration {
|
||||
.col(ColumnDef::new(CriticalAlertResponse::CreatedBy).uuid())
|
||||
.col(ColumnDef::new(CriticalAlertResponse::UpdatedBy).uuid())
|
||||
.col(
|
||||
ColumnDef::new(CriticalAlertResponse::DeletedAt)
|
||||
.timestamp_with_time_zone(),
|
||||
ColumnDef::new(CriticalAlertResponse::DeletedAt).timestamp_with_time_zone(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(CriticalAlertResponse::Version)
|
||||
@@ -193,10 +189,7 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.from(
|
||||
CriticalAlertResponse::Table,
|
||||
CriticalAlertResponse::AlertId,
|
||||
)
|
||||
.from(CriticalAlertResponse::Table, CriticalAlertResponse::AlertId)
|
||||
.to(CriticalAlert::Table, CriticalAlert::Id),
|
||||
)
|
||||
.to_owned(),
|
||||
@@ -206,11 +199,7 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(CriticalAlertResponse::Table)
|
||||
.to_owned(),
|
||||
)
|
||||
.drop_table(Table::drop().table(CriticalAlertResponse::Table).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(CriticalAlert::Table).to_owned())
|
||||
|
||||
@@ -16,8 +16,9 @@ impl MigrationTrait for Migration {
|
||||
// 同一患者、同一设备、同一指标、同一测量时间只允许一条记录
|
||||
db.execute_unprepared(
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS uq_device_readings_dedup
|
||||
ON device_readings (tenant_id, patient_id, device_id, metric, measured_at);"
|
||||
).await?;
|
||||
ON device_readings (tenant_id, patient_id, device_id, metric, measured_at);",
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -25,9 +26,8 @@ impl MigrationTrait for Migration {
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
|
||||
db.execute_unprepared(
|
||||
"DROP INDEX IF EXISTS uq_device_readings_dedup;"
|
||||
).await?;
|
||||
db.execute_unprepared("DROP INDEX IF EXISTS uq_device_readings_dedup;")
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -11,11 +11,12 @@ impl MigrationTrait for Migration {
|
||||
let db = manager.get_connection();
|
||||
|
||||
// 获取默认租户 ID
|
||||
let result = db.query_one(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"SELECT id::text FROM tenant LIMIT 1".to_string(),
|
||||
))
|
||||
.await?;
|
||||
let result = db
|
||||
.query_one(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"SELECT id::text FROM tenant LIMIT 1".to_string(),
|
||||
))
|
||||
.await?;
|
||||
|
||||
let tid = match result {
|
||||
Some(row) => row.try_get_by_index::<String>(0).unwrap_or_default(),
|
||||
@@ -26,12 +27,56 @@ impl MigrationTrait for Migration {
|
||||
let d3 = "a0000000-0000-0000-0000-000000000003"; // 健康管理目录
|
||||
|
||||
// 告警相关菜单(排在 AI 用量统计之后)
|
||||
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000016", "告警仪表盘", "/health/alert-dashboard", "AlertOutlined", 15, sys).await?;
|
||||
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000017", "告警列表", "/health/alerts", "BellOutlined", 16, sys).await?;
|
||||
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000018", "告警规则", "/health/alert-rules", "ControlOutlined", 17, sys).await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000016",
|
||||
"告警仪表盘",
|
||||
"/health/alert-dashboard",
|
||||
"AlertOutlined",
|
||||
15,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000017",
|
||||
"告警列表",
|
||||
"/health/alerts",
|
||||
"BellOutlined",
|
||||
16,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000018",
|
||||
"告警规则",
|
||||
"/health/alert-rules",
|
||||
"ControlOutlined",
|
||||
17,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 设备管理菜单
|
||||
insert_menu(db, &tid, d3, "b0000003-0000-0000-0000-000000000019", "设备管理", "/health/devices", "ApiOutlined", 18, sys).await?;
|
||||
insert_menu(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000019",
|
||||
"设备管理",
|
||||
"/health/devices",
|
||||
"ApiOutlined",
|
||||
18,
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -11,23 +11,54 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(Alias::new("medication_reminder"))
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(Alias::new("id")).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("id"))
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("patient_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("medication_name")).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("medication_name"))
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("dosage")).string())
|
||||
.col(ColumnDef::new(Alias::new("frequency")).string())
|
||||
.col(ColumnDef::new(Alias::new("reminder_times")).json().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("reminder_times"))
|
||||
.json()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("start_date")).date())
|
||||
.col(ColumnDef::new(Alias::new("end_date")).date())
|
||||
.col(ColumnDef::new(Alias::new("is_active")).boolean().not_null().default(true))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("is_active"))
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(true),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("notes")).string())
|
||||
.col(ColumnDef::new(Alias::new("created_at")).timestamp_with_time_zone().not_null())
|
||||
.col(ColumnDef::new(Alias::new("updated_at")).timestamp_with_time_zone().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("updated_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("version")).integer().not_null().default(1))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
@@ -47,7 +78,11 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("medication_reminder")).to_owned())
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("medication_reminder"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,35 +13,116 @@ impl MigrationTrait for Migration {
|
||||
// === 更新已有菜单的 permission 字段 ===
|
||||
|
||||
// 健康管理菜单
|
||||
update_perm(db, "b0000003-0000-0000-0000-000000000002", "health.patient.list").await?;
|
||||
update_perm(db, "b0000003-0000-0000-0000-000000000003", "health.doctor.list").await?;
|
||||
update_perm(db, "b0000003-0000-0000-0000-000000000004", "health.appointment.list").await?;
|
||||
update_perm(db, "b0000003-0000-0000-0000-000000000005", "health.appointment.list").await?;
|
||||
update_perm(db, "b0000003-0000-0000-0000-000000000006", "health.follow-up.list").await?;
|
||||
update_perm(db, "b0000003-0000-0000-0000-000000000007", "health.consultation.list").await?;
|
||||
update_perm(db, "b0000003-0000-0000-0000-000000000008", "health.patient.list").await?;
|
||||
update_perm(db, "b0000003-0000-0000-0000-000000000009", "health.points.list").await?;
|
||||
update_perm(db, "b0000003-0000-0000-0000-000000000010", "health.points.list").await?;
|
||||
update_perm(db, "b0000003-0000-0000-0000-000000000011", "health.points.list").await?;
|
||||
update_perm(db, "b0000003-0000-0000-0000-000000000012", "health.points.list").await?;
|
||||
update_perm(
|
||||
db,
|
||||
"b0000003-0000-0000-0000-000000000002",
|
||||
"health.patient.list",
|
||||
)
|
||||
.await?;
|
||||
update_perm(
|
||||
db,
|
||||
"b0000003-0000-0000-0000-000000000003",
|
||||
"health.doctor.list",
|
||||
)
|
||||
.await?;
|
||||
update_perm(
|
||||
db,
|
||||
"b0000003-0000-0000-0000-000000000004",
|
||||
"health.appointment.list",
|
||||
)
|
||||
.await?;
|
||||
update_perm(
|
||||
db,
|
||||
"b0000003-0000-0000-0000-000000000005",
|
||||
"health.appointment.list",
|
||||
)
|
||||
.await?;
|
||||
update_perm(
|
||||
db,
|
||||
"b0000003-0000-0000-0000-000000000006",
|
||||
"health.follow-up.list",
|
||||
)
|
||||
.await?;
|
||||
update_perm(
|
||||
db,
|
||||
"b0000003-0000-0000-0000-000000000007",
|
||||
"health.consultation.list",
|
||||
)
|
||||
.await?;
|
||||
update_perm(
|
||||
db,
|
||||
"b0000003-0000-0000-0000-000000000008",
|
||||
"health.patient.list",
|
||||
)
|
||||
.await?;
|
||||
update_perm(
|
||||
db,
|
||||
"b0000003-0000-0000-0000-000000000009",
|
||||
"health.points.list",
|
||||
)
|
||||
.await?;
|
||||
update_perm(
|
||||
db,
|
||||
"b0000003-0000-0000-0000-000000000010",
|
||||
"health.points.list",
|
||||
)
|
||||
.await?;
|
||||
update_perm(
|
||||
db,
|
||||
"b0000003-0000-0000-0000-000000000011",
|
||||
"health.points.list",
|
||||
)
|
||||
.await?;
|
||||
update_perm(
|
||||
db,
|
||||
"b0000003-0000-0000-0000-000000000012",
|
||||
"health.points.list",
|
||||
)
|
||||
.await?;
|
||||
// AI 模块菜单
|
||||
update_perm(db, "b0000003-0000-0000-0000-000000000013", "ai.prompt.list").await?;
|
||||
update_perm(db, "b0000003-0000-0000-0000-000000000014", "ai.analysis.list").await?;
|
||||
update_perm(
|
||||
db,
|
||||
"b0000003-0000-0000-0000-000000000014",
|
||||
"ai.analysis.list",
|
||||
)
|
||||
.await?;
|
||||
update_perm(db, "b0000003-0000-0000-0000-000000000015", "ai.usage.list").await?;
|
||||
// 告警菜单
|
||||
update_perm(db, "b0000003-0000-0000-0000-000000000016", "health.alerts.list").await?;
|
||||
update_perm(db, "b0000003-0000-0000-0000-000000000017", "health.alerts.list").await?;
|
||||
update_perm(db, "b0000003-0000-0000-0000-000000000018", "health.alert-rules.list").await?;
|
||||
update_perm(
|
||||
db,
|
||||
"b0000003-0000-0000-0000-000000000016",
|
||||
"health.alerts.list",
|
||||
)
|
||||
.await?;
|
||||
update_perm(
|
||||
db,
|
||||
"b0000003-0000-0000-0000-000000000017",
|
||||
"health.alerts.list",
|
||||
)
|
||||
.await?;
|
||||
update_perm(
|
||||
db,
|
||||
"b0000003-0000-0000-0000-000000000018",
|
||||
"health.alert-rules.list",
|
||||
)
|
||||
.await?;
|
||||
// 设备菜单
|
||||
update_perm(db, "b0000003-0000-0000-0000-000000000019", "health.devices.list").await?;
|
||||
update_perm(
|
||||
db,
|
||||
"b0000003-0000-0000-0000-000000000019",
|
||||
"health.devices.list",
|
||||
)
|
||||
.await?;
|
||||
|
||||
// === 补全缺失菜单 ===
|
||||
|
||||
let result = db.query_one(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"SELECT id::text FROM tenant LIMIT 1".to_string(),
|
||||
))
|
||||
.await?;
|
||||
let result = db
|
||||
.query_one(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"SELECT id::text FROM tenant LIMIT 1".to_string(),
|
||||
))
|
||||
.await?;
|
||||
|
||||
let tid = match result {
|
||||
Some(row) => row.try_get_by_index::<String>(0).unwrap_or_default(),
|
||||
@@ -52,9 +133,33 @@ impl MigrationTrait for Migration {
|
||||
let d3 = "a0000000-0000-0000-0000-000000000003"; // 健康管理目录
|
||||
|
||||
// 透析管理(sort 19)
|
||||
insert_menu_with_perm(db, &tid, d3, "b0000003-0000-0000-0000-000000000020", "透析管理", "/health/dialysis", "ExperimentOutlined", 19, "health.dialysis.list", sys).await?;
|
||||
insert_menu_with_perm(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000020",
|
||||
"透析管理",
|
||||
"/health/dialysis",
|
||||
"ExperimentOutlined",
|
||||
19,
|
||||
"health.dialysis.list",
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
// 资讯管理(sort 20)
|
||||
insert_menu_with_perm(db, &tid, d3, "b0000003-0000-0000-0000-000000000021", "资讯管理", "/health/articles", "ReadOutlined", 20, "health.articles.list", sys).await?;
|
||||
insert_menu_with_perm(
|
||||
db,
|
||||
&tid,
|
||||
d3,
|
||||
"b0000003-0000-0000-0000-000000000021",
|
||||
"资讯管理",
|
||||
"/health/articles",
|
||||
"ReadOutlined",
|
||||
20,
|
||||
"health.articles.list",
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -29,7 +29,11 @@ impl MigrationTrait for Migration {
|
||||
.string_len(10)
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("params")).json_binary().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("params"))
|
||||
.json_binary()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("status"))
|
||||
.string_len(20)
|
||||
@@ -52,9 +56,7 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version_lock"))
|
||||
.integer()
|
||||
|
||||
@@ -38,9 +38,7 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version_lock"))
|
||||
.integer()
|
||||
@@ -68,7 +66,11 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("ai_risk_threshold")).to_owned())
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("ai_risk_threshold"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,10 +54,8 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
db.execute_unprepared(
|
||||
"DELETE FROM menus WHERE path = '/health/action-inbox'",
|
||||
)
|
||||
.await?;
|
||||
db.execute_unprepared("DELETE FROM menus WHERE path = '/health/action-inbox'")
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,16 @@ impl MigrationTrait for Migration {
|
||||
|
||||
// ── 1. 科室 health_department ──
|
||||
let dict_dept = "d1000001-0000-0000-0000-000000000001";
|
||||
insert_dict(db, &tid, dict_dept, "科室", "health_department", "医护科室分类", sys).await?;
|
||||
insert_dict(
|
||||
db,
|
||||
&tid,
|
||||
dict_dept,
|
||||
"科室",
|
||||
"health_department",
|
||||
"医护科室分类",
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
let dept_items = [
|
||||
("全科", "全科", 1),
|
||||
("内科", "内科", 2),
|
||||
@@ -41,12 +50,32 @@ impl MigrationTrait for Migration {
|
||||
("体检中心", "体检中心", 11),
|
||||
];
|
||||
for (i, (label, value, sort)) in dept_items.iter().enumerate() {
|
||||
insert_item(db, &tid, dict_dept, label, value, *sort, None, &(i + 1), sys).await?;
|
||||
insert_item(
|
||||
db,
|
||||
&tid,
|
||||
dict_dept,
|
||||
label,
|
||||
value,
|
||||
*sort,
|
||||
None,
|
||||
&(i + 1),
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// ── 2. 职称 health_title ──
|
||||
let dict_title = "d1000001-0000-0000-0000-000000000002";
|
||||
insert_dict(db, &tid, dict_title, "职称", "health_title", "医护职称分类", sys).await?;
|
||||
insert_dict(
|
||||
db,
|
||||
&tid,
|
||||
dict_title,
|
||||
"职称",
|
||||
"health_title",
|
||||
"医护职称分类",
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
let title_items = [
|
||||
("住院医师", "住院医师", 1),
|
||||
("主治医师", "主治医师", 2),
|
||||
@@ -59,12 +88,32 @@ impl MigrationTrait for Migration {
|
||||
("主任护师", "主任护师", 9),
|
||||
];
|
||||
for (i, (label, value, sort)) in title_items.iter().enumerate() {
|
||||
insert_item(db, &tid, dict_title, label, value, *sort, None, &(i + 1), sys).await?;
|
||||
insert_item(
|
||||
db,
|
||||
&tid,
|
||||
dict_title,
|
||||
label,
|
||||
value,
|
||||
*sort,
|
||||
None,
|
||||
&(i + 1),
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// ── 3. 设备类型 health_device_type ──
|
||||
let dict_dev = "d1000001-0000-0000-0000-000000000003";
|
||||
insert_dict(db, &tid, dict_dev, "设备类型", "health_device_type", "健康监测设备类型", sys).await?;
|
||||
insert_dict(
|
||||
db,
|
||||
&tid,
|
||||
dict_dev,
|
||||
"设备类型",
|
||||
"health_device_type",
|
||||
"健康监测设备类型",
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
let dev_items = [
|
||||
("血压计", "blood_pressure", 1),
|
||||
("血糖仪", "blood_glucose", 2),
|
||||
@@ -81,7 +130,16 @@ impl MigrationTrait for Migration {
|
||||
|
||||
// ── 4. 随访类型 health_follow_up_type ──
|
||||
let dict_fu = "d1000001-0000-0000-0000-000000000004";
|
||||
insert_dict(db, &tid, dict_fu, "随访类型", "health_follow_up_type", "随访方式分类", sys).await?;
|
||||
insert_dict(
|
||||
db,
|
||||
&tid,
|
||||
dict_fu,
|
||||
"随访类型",
|
||||
"health_follow_up_type",
|
||||
"随访方式分类",
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
let fu_items = [
|
||||
("电话", "phone", 1),
|
||||
("门诊", "outpatient", 2),
|
||||
@@ -96,7 +154,13 @@ impl MigrationTrait for Migration {
|
||||
// ── 5. 咨询类型 health_consultation_type ──
|
||||
let dict_consult = "d1000001-0000-0000-0000-000000000005";
|
||||
insert_dict(
|
||||
db, &tid, dict_consult, "咨询类型", "health_consultation_type", "咨询会话类型", sys,
|
||||
db,
|
||||
&tid,
|
||||
dict_consult,
|
||||
"咨询类型",
|
||||
"health_consultation_type",
|
||||
"咨询会话类型",
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
let consult_items = [
|
||||
@@ -105,12 +169,32 @@ impl MigrationTrait for Migration {
|
||||
("健康咨询", "health_consultation", 3),
|
||||
];
|
||||
for (i, (label, value, sort)) in consult_items.iter().enumerate() {
|
||||
insert_item(db, &tid, dict_consult, label, value, *sort, None, &(i + 1), sys).await?;
|
||||
insert_item(
|
||||
db,
|
||||
&tid,
|
||||
dict_consult,
|
||||
label,
|
||||
value,
|
||||
*sort,
|
||||
None,
|
||||
&(i + 1),
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// ── 6. 关系 health_relationship ──
|
||||
let dict_rel = "d1000001-0000-0000-0000-000000000006";
|
||||
insert_dict(db, &tid, dict_rel, "关系", "health_relationship", "家属与患者关系", sys).await?;
|
||||
insert_dict(
|
||||
db,
|
||||
&tid,
|
||||
dict_rel,
|
||||
"关系",
|
||||
"health_relationship",
|
||||
"家属与患者关系",
|
||||
sys,
|
||||
)
|
||||
.await?;
|
||||
let rel_items = [
|
||||
("父母", "parent", 1),
|
||||
("配偶", "spouse", 2),
|
||||
|
||||
@@ -56,10 +56,8 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
db.execute_unprepared(
|
||||
"DELETE FROM menus WHERE path = '/health/follow-up-templates'",
|
||||
)
|
||||
.await?;
|
||||
db.execute_unprepared("DELETE FROM menus WHERE path = '/health/follow-up-templates'")
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,20 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("tenant_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("patient_id")).uuid().not_null())
|
||||
.col(ColumnDef::new(Alias::new("device_type")).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("device_type"))
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("date_bucket")).date().not_null())
|
||||
.col(ColumnDef::new(Alias::new("min_val")).double())
|
||||
.col(ColumnDef::new(Alias::new("max_val")).double())
|
||||
.col(ColumnDef::new(Alias::new("avg_val")).double().not_null())
|
||||
.col(ColumnDef::new(Alias::new("sample_count")).integer().not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("sample_count"))
|
||||
.integer()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("percentile_95")).double())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
@@ -67,7 +75,11 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("vital_signs_daily")).to_owned())
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("vital_signs_daily"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,11 @@ impl MigrationTrait for Migration {
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("scopes")).json().not_null())
|
||||
.col(ColumnDef::new(Alias::new("allowed_patient_ids")).json().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("allowed_patient_ids"))
|
||||
.json()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("rate_limit_per_minute"))
|
||||
.integer()
|
||||
@@ -66,7 +70,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("created_by")).uuid().null())
|
||||
.col(ColumnDef::new(Alias::new("updated_by")).uuid().null())
|
||||
.col(ColumnDef::new(Alias::new("deleted_at")).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("deleted_at"))
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("version"))
|
||||
.integer()
|
||||
|
||||
@@ -10,14 +10,70 @@ impl MigrationTrait for Migration {
|
||||
|
||||
// 安全创建外键:先检查是否已存在,不存在才创建
|
||||
let fks: &[(&str, &str, &str, &str, &str, &str)] = &[
|
||||
("fk_follow_up_task_appointment", "follow_up_task", "related_appointment_id", "appointment", "id", "SET NULL"),
|
||||
("fk_points_transaction_account", "points_transaction", "account_id", "points_account", "id", "CASCADE"),
|
||||
("fk_points_transaction_rule", "points_transaction", "rule_id", "points_rule", "id", "SET NULL"),
|
||||
("fk_points_transaction_order", "points_transaction", "order_id", "points_order", "id", "SET NULL"),
|
||||
("fk_points_order_product", "points_order", "product_id", "points_product", "id", "RESTRICT"),
|
||||
("fk_points_order_patient", "points_order", "patient_id", "patient", "id", "CASCADE"),
|
||||
("fk_offline_event_registration_event", "offline_event_registration", "event_id", "offline_event", "id", "CASCADE"),
|
||||
("fk_offline_event_registration_patient", "offline_event_registration", "patient_id", "patient", "id", "CASCADE"),
|
||||
(
|
||||
"fk_follow_up_task_appointment",
|
||||
"follow_up_task",
|
||||
"related_appointment_id",
|
||||
"appointment",
|
||||
"id",
|
||||
"SET NULL",
|
||||
),
|
||||
(
|
||||
"fk_points_transaction_account",
|
||||
"points_transaction",
|
||||
"account_id",
|
||||
"points_account",
|
||||
"id",
|
||||
"CASCADE",
|
||||
),
|
||||
(
|
||||
"fk_points_transaction_rule",
|
||||
"points_transaction",
|
||||
"rule_id",
|
||||
"points_rule",
|
||||
"id",
|
||||
"SET NULL",
|
||||
),
|
||||
(
|
||||
"fk_points_transaction_order",
|
||||
"points_transaction",
|
||||
"order_id",
|
||||
"points_order",
|
||||
"id",
|
||||
"SET NULL",
|
||||
),
|
||||
(
|
||||
"fk_points_order_product",
|
||||
"points_order",
|
||||
"product_id",
|
||||
"points_product",
|
||||
"id",
|
||||
"RESTRICT",
|
||||
),
|
||||
(
|
||||
"fk_points_order_patient",
|
||||
"points_order",
|
||||
"patient_id",
|
||||
"patient",
|
||||
"id",
|
||||
"CASCADE",
|
||||
),
|
||||
(
|
||||
"fk_offline_event_registration_event",
|
||||
"offline_event_registration",
|
||||
"event_id",
|
||||
"offline_event",
|
||||
"id",
|
||||
"CASCADE",
|
||||
),
|
||||
(
|
||||
"fk_offline_event_registration_patient",
|
||||
"offline_event_registration",
|
||||
"patient_id",
|
||||
"patient",
|
||||
"id",
|
||||
"CASCADE",
|
||||
),
|
||||
];
|
||||
|
||||
for &(name, from_table, from_col, to_table, to_col, on_delete) in fks {
|
||||
@@ -54,11 +110,9 @@ impl MigrationTrait for Migration {
|
||||
"fk_follow_up_task_appointment",
|
||||
];
|
||||
for fk in &fks {
|
||||
db.execute_unprepared(&format!(
|
||||
"ALTER TABLE dummy DROP CONSTRAINT IF EXISTS {fk}"
|
||||
))
|
||||
.await
|
||||
.ok();
|
||||
db.execute_unprepared(&format!("ALTER TABLE dummy DROP CONSTRAINT IF EXISTS {fk}"))
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -31,7 +31,11 @@ impl MigrationTrait for Migration {
|
||||
.not_null()
|
||||
.default("draft"),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("title")).string_len(200).not_null())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("title"))
|
||||
.string_len(200)
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("goals"))
|
||||
.json_binary()
|
||||
@@ -122,7 +126,11 @@ impl MigrationTrait for Migration {
|
||||
.default("pending"),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("schedule")).string_len(100))
|
||||
.col(ColumnDef::new(Alias::new("sort_order")).integer().default(0))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("sort_order"))
|
||||
.integer()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
.timestamp_with_time_zone()
|
||||
@@ -198,10 +206,7 @@ impl MigrationTrait for Migration {
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("current_value")).string_len(50))
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("measured_at"))
|
||||
.timestamp_with_time_zone(),
|
||||
)
|
||||
.col(ColumnDef::new(Alias::new("measured_at")).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(Alias::new("notes")).text())
|
||||
.col(
|
||||
ColumnDef::new(Alias::new("created_at"))
|
||||
@@ -252,10 +257,18 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("care_plan_outcomes")).to_owned())
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("care_plan_outcomes"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("care_plan_items")).to_owned())
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(Alias::new("care_plan_items"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("care_plans")).to_owned())
|
||||
|
||||
@@ -74,17 +74,30 @@ impl MigrationTrait for Migration {
|
||||
|
||||
// 外键(幂等)
|
||||
let fks = [
|
||||
("fk_patient_assignments_shift", "ALTER TABLE patient_assignment ADD CONSTRAINT fk_patient_assignments_shift FOREIGN KEY (shift_id) REFERENCES shift(id) ON DELETE CASCADE"),
|
||||
("fk_handoff_log_from_shift", "ALTER TABLE handoff_log ADD CONSTRAINT fk_handoff_log_from_shift FOREIGN KEY (from_shift_id) REFERENCES shift(id) ON DELETE CASCADE"),
|
||||
("fk_handoff_log_to_shift", "ALTER TABLE handoff_log ADD CONSTRAINT fk_handoff_log_to_shift FOREIGN KEY (to_shift_id) REFERENCES shift(id) ON DELETE CASCADE"),
|
||||
(
|
||||
"fk_patient_assignments_shift",
|
||||
"ALTER TABLE patient_assignment ADD CONSTRAINT fk_patient_assignments_shift FOREIGN KEY (shift_id) REFERENCES shift(id) ON DELETE CASCADE",
|
||||
),
|
||||
(
|
||||
"fk_handoff_log_from_shift",
|
||||
"ALTER TABLE handoff_log ADD CONSTRAINT fk_handoff_log_from_shift FOREIGN KEY (from_shift_id) REFERENCES shift(id) ON DELETE CASCADE",
|
||||
),
|
||||
(
|
||||
"fk_handoff_log_to_shift",
|
||||
"ALTER TABLE handoff_log ADD CONSTRAINT fk_handoff_log_to_shift FOREIGN KEY (to_shift_id) REFERENCES shift(id) ON DELETE CASCADE",
|
||||
),
|
||||
];
|
||||
for (name, sql) in &fks {
|
||||
let check = format!(
|
||||
"SELECT COUNT(*) FROM information_schema.table_constraints WHERE constraint_name = '{name}'"
|
||||
);
|
||||
if let Some(row) = db.query_one(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres, check,
|
||||
)).await? {
|
||||
if let Some(row) = db
|
||||
.query_one(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
check,
|
||||
))
|
||||
.await?
|
||||
{
|
||||
let count: i64 = row.try_get_by_index::<i64>(0).unwrap_or(0);
|
||||
if count == 0 {
|
||||
db.execute_unprepared(sql).await?;
|
||||
@@ -97,8 +110,10 @@ impl MigrationTrait for Migration {
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
db.execute_unprepared("DROP TABLE IF EXISTS handoff_log").await?;
|
||||
db.execute_unprepared("DROP TABLE IF EXISTS patient_assignment").await?;
|
||||
db.execute_unprepared("DROP TABLE IF EXISTS handoff_log")
|
||||
.await?;
|
||||
db.execute_unprepared("DROP TABLE IF EXISTS patient_assignment")
|
||||
.await?;
|
||||
db.execute_unprepared("DROP TABLE IF EXISTS shift").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -51,10 +51,22 @@ impl MigrationTrait for Migration {
|
||||
|
||||
// 索引(幂等)
|
||||
let indexes = [
|
||||
("idx_ble_gateways_tenant_id", "CREATE INDEX IF NOT EXISTS idx_ble_gateways_tenant_id ON ble_gateways (tenant_id)"),
|
||||
("idx_ble_gateways_api_key_prefix", "CREATE INDEX IF NOT EXISTS idx_ble_gateways_api_key_prefix ON ble_gateways (api_key_prefix)"),
|
||||
("idx_gateway_patient_bindings_gateway", "CREATE INDEX IF NOT EXISTS idx_gateway_patient_bindings_gateway ON gateway_patient_bindings (gateway_id_fk)"),
|
||||
("idx_gateway_patient_bindings_patient", "CREATE INDEX IF NOT EXISTS idx_gateway_patient_bindings_patient ON gateway_patient_bindings (patient_id)"),
|
||||
(
|
||||
"idx_ble_gateways_tenant_id",
|
||||
"CREATE INDEX IF NOT EXISTS idx_ble_gateways_tenant_id ON ble_gateways (tenant_id)",
|
||||
),
|
||||
(
|
||||
"idx_ble_gateways_api_key_prefix",
|
||||
"CREATE INDEX IF NOT EXISTS idx_ble_gateways_api_key_prefix ON ble_gateways (api_key_prefix)",
|
||||
),
|
||||
(
|
||||
"idx_gateway_patient_bindings_gateway",
|
||||
"CREATE INDEX IF NOT EXISTS idx_gateway_patient_bindings_gateway ON gateway_patient_bindings (gateway_id_fk)",
|
||||
),
|
||||
(
|
||||
"idx_gateway_patient_bindings_patient",
|
||||
"CREATE INDEX IF NOT EXISTS idx_gateway_patient_bindings_patient ON gateway_patient_bindings (patient_id)",
|
||||
),
|
||||
];
|
||||
for (_, sql) in &indexes {
|
||||
db.execute_unprepared(sql).await.ok();
|
||||
@@ -62,17 +74,25 @@ impl MigrationTrait for Migration {
|
||||
|
||||
// 外键约束(幂等)
|
||||
let fks = [
|
||||
("fk_gpb_gateway", "ALTER TABLE gateway_patient_bindings ADD CONSTRAINT fk_gpb_gateway FOREIGN KEY (gateway_id_fk) REFERENCES ble_gateways(id) ON DELETE CASCADE"),
|
||||
("fk_gpb_patient", "ALTER TABLE gateway_patient_bindings ADD CONSTRAINT fk_gpb_patient FOREIGN KEY (patient_id) REFERENCES patient(id) ON DELETE CASCADE"),
|
||||
(
|
||||
"fk_gpb_gateway",
|
||||
"ALTER TABLE gateway_patient_bindings ADD CONSTRAINT fk_gpb_gateway FOREIGN KEY (gateway_id_fk) REFERENCES ble_gateways(id) ON DELETE CASCADE",
|
||||
),
|
||||
(
|
||||
"fk_gpb_patient",
|
||||
"ALTER TABLE gateway_patient_bindings ADD CONSTRAINT fk_gpb_patient FOREIGN KEY (patient_id) REFERENCES patient(id) ON DELETE CASCADE",
|
||||
),
|
||||
];
|
||||
for (name, sql) in &fks {
|
||||
let check = format!(
|
||||
"SELECT COUNT(*) FROM information_schema.table_constraints WHERE constraint_name = '{name}'"
|
||||
);
|
||||
let result = db.query_one(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
check,
|
||||
)).await?;
|
||||
let result = db
|
||||
.query_one(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
check,
|
||||
))
|
||||
.await?;
|
||||
let count: i64 = result.unwrap().try_get_by_index::<i64>(0).unwrap_or(0);
|
||||
if count == 0 {
|
||||
db.execute_unprepared(sql).await?;
|
||||
|
||||
@@ -10,17 +10,83 @@ impl MigrationTrait for Migration {
|
||||
|
||||
// 批量插入缺失的健康管理菜单(多租户安全)
|
||||
let menus: &[(&str, &str, &str, &str, i32)] = &[
|
||||
("b0000003-0000-7000-8000-000000000022", "护理计划", "/health/care-plans", "SolutionOutlined", 19),
|
||||
("b0000003-0000-7000-8000-000000000023", "班次管理", "/health/shifts", "ClockCircleOutlined", 20),
|
||||
("b0000003-0000-7000-8000-000000000024", "用药记录", "/health/medications", "MedicineBoxOutlined", 21),
|
||||
("b0000003-0000-7000-8000-000000000025", "BLE 网关", "/health/ble-gateways", "WifiOutlined", 22),
|
||||
("b0000003-0000-7000-8000-000000000026", "危急值阈值", "/health/critical-value-thresholds","SafetyCertificateOutlined", 23),
|
||||
("b0000003-0000-7000-8000-000000000027", "诊断记录", "/health/diagnoses", "FileSearchOutlined", 24),
|
||||
("b0000003-0000-7000-8000-000000000028", "家庭健康代理", "/health/family-proxy", "TeamOutlined", 25),
|
||||
("b0000003-0000-7000-8000-000000000029", "知情同意", "/health/consents", "AuditOutlined", 26),
|
||||
("b0000003-0000-7000-8000-000000000030", "实时监控", "/health/realtime-monitor", "MonitorOutlined", 27),
|
||||
("b0000003-0000-7000-8000-000000000031", "OAuth 合作方", "/health/oauth-clients", "ApiOutlined", 28),
|
||||
("b0000003-0000-7000-8000-000000000032", "随访模板管理", "/health/follow-up-templates", "FormOutlined", 29),
|
||||
(
|
||||
"b0000003-0000-7000-8000-000000000022",
|
||||
"护理计划",
|
||||
"/health/care-plans",
|
||||
"SolutionOutlined",
|
||||
19,
|
||||
),
|
||||
(
|
||||
"b0000003-0000-7000-8000-000000000023",
|
||||
"班次管理",
|
||||
"/health/shifts",
|
||||
"ClockCircleOutlined",
|
||||
20,
|
||||
),
|
||||
(
|
||||
"b0000003-0000-7000-8000-000000000024",
|
||||
"用药记录",
|
||||
"/health/medications",
|
||||
"MedicineBoxOutlined",
|
||||
21,
|
||||
),
|
||||
(
|
||||
"b0000003-0000-7000-8000-000000000025",
|
||||
"BLE 网关",
|
||||
"/health/ble-gateways",
|
||||
"WifiOutlined",
|
||||
22,
|
||||
),
|
||||
(
|
||||
"b0000003-0000-7000-8000-000000000026",
|
||||
"危急值阈值",
|
||||
"/health/critical-value-thresholds",
|
||||
"SafetyCertificateOutlined",
|
||||
23,
|
||||
),
|
||||
(
|
||||
"b0000003-0000-7000-8000-000000000027",
|
||||
"诊断记录",
|
||||
"/health/diagnoses",
|
||||
"FileSearchOutlined",
|
||||
24,
|
||||
),
|
||||
(
|
||||
"b0000003-0000-7000-8000-000000000028",
|
||||
"家庭健康代理",
|
||||
"/health/family-proxy",
|
||||
"TeamOutlined",
|
||||
25,
|
||||
),
|
||||
(
|
||||
"b0000003-0000-7000-8000-000000000029",
|
||||
"知情同意",
|
||||
"/health/consents",
|
||||
"AuditOutlined",
|
||||
26,
|
||||
),
|
||||
(
|
||||
"b0000003-0000-7000-8000-000000000030",
|
||||
"实时监控",
|
||||
"/health/realtime-monitor",
|
||||
"MonitorOutlined",
|
||||
27,
|
||||
),
|
||||
(
|
||||
"b0000003-0000-7000-8000-000000000031",
|
||||
"OAuth 合作方",
|
||||
"/health/oauth-clients",
|
||||
"ApiOutlined",
|
||||
28,
|
||||
),
|
||||
(
|
||||
"b0000003-0000-7000-8000-000000000032",
|
||||
"随访模板管理",
|
||||
"/health/follow-up-templates",
|
||||
"FormOutlined",
|
||||
29,
|
||||
),
|
||||
];
|
||||
|
||||
for &(id, title, path, icon, sort) in menus {
|
||||
|
||||
@@ -11,7 +11,12 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(AiTenantConfig::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(AiTenantConfig::Id).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(AiTenantConfig::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(AiTenantConfig::TenantId).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(AiTenantConfig::DefaultProvider)
|
||||
@@ -19,8 +24,16 @@ impl MigrationTrait for Migration {
|
||||
.not_null()
|
||||
.default("claude"),
|
||||
)
|
||||
.col(ColumnDef::new(AiTenantConfig::FallbackProvider).string_len(50).null())
|
||||
.col(ColumnDef::new(AiTenantConfig::AnalysisTypeOverrides).json().null())
|
||||
.col(
|
||||
ColumnDef::new(AiTenantConfig::FallbackProvider)
|
||||
.string_len(50)
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiTenantConfig::AnalysisTypeOverrides)
|
||||
.json()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiTenantConfig::MonthlyTokenBudget)
|
||||
.big_integer()
|
||||
@@ -53,7 +66,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(AiTenantConfig::CreatedBy).uuid().null())
|
||||
.col(ColumnDef::new(AiTenantConfig::UpdatedBy).uuid().null())
|
||||
.col(ColumnDef::new(AiTenantConfig::DeletedAt).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(AiTenantConfig::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiTenantConfig::VersionLock)
|
||||
.integer()
|
||||
|
||||
@@ -11,7 +11,12 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(AiAnalysisQueue::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(AiAnalysisQueue::Id).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(AiAnalysisQueue::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(AiAnalysisQueue::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(AiAnalysisQueue::PatientId).uuid().not_null())
|
||||
.col(
|
||||
@@ -31,7 +36,11 @@ impl MigrationTrait for Migration {
|
||||
.not_null()
|
||||
.default("pending"),
|
||||
)
|
||||
.col(ColumnDef::new(AiAnalysisQueue::SourceEvent).string_len(100).null())
|
||||
.col(
|
||||
ColumnDef::new(AiAnalysisQueue::SourceEvent)
|
||||
.string_len(100)
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiAnalysisQueue::SourceRef)
|
||||
.string_len(200)
|
||||
@@ -44,9 +53,21 @@ impl MigrationTrait for Migration {
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(AiAnalysisQueue::StartedAt).timestamp_with_time_zone().null())
|
||||
.col(ColumnDef::new(AiAnalysisQueue::CompletedAt).timestamp_with_time_zone().null())
|
||||
.col(ColumnDef::new(AiAnalysisQueue::ResultAnalysisId).uuid().null())
|
||||
.col(
|
||||
ColumnDef::new(AiAnalysisQueue::StartedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiAnalysisQueue::CompletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiAnalysisQueue::ResultAnalysisId)
|
||||
.uuid()
|
||||
.null(),
|
||||
)
|
||||
.col(ColumnDef::new(AiAnalysisQueue::ErrorMessage).text().null())
|
||||
.col(
|
||||
ColumnDef::new(AiAnalysisQueue::RetryCount)
|
||||
@@ -74,7 +95,11 @@ impl MigrationTrait for Migration {
|
||||
)
|
||||
.col(ColumnDef::new(AiAnalysisQueue::CreatedBy).uuid().null())
|
||||
.col(ColumnDef::new(AiAnalysisQueue::UpdatedBy).uuid().null())
|
||||
.col(ColumnDef::new(AiAnalysisQueue::DeletedAt).timestamp_with_time_zone().null())
|
||||
.col(
|
||||
ColumnDef::new(AiAnalysisQueue::DeletedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiAnalysisQueue::VersionLock)
|
||||
.integer()
|
||||
|
||||
@@ -30,20 +30,66 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(AiKnowledgeRules::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(AiKnowledgeRules::Id).uuid().not_null().primary_key())
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeRules::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(AiKnowledgeRules::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(AiKnowledgeRules::RuleName).string().not_null())
|
||||
.col(ColumnDef::new(AiKnowledgeRules::AnalysisType).string().not_null())
|
||||
.col(ColumnDef::new(AiKnowledgeRules::ConditionExpr).string().not_null())
|
||||
.col(ColumnDef::new(AiKnowledgeRules::ActionText).string().not_null())
|
||||
.col(ColumnDef::new(AiKnowledgeRules::Priority).integer().not_null().default(0))
|
||||
.col(ColumnDef::new(AiKnowledgeRules::IsEnabled).boolean().not_null().default(true))
|
||||
.col(ColumnDef::new(AiKnowledgeRules::CreatedAt).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(ColumnDef::new(AiKnowledgeRules::UpdatedAt).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeRules::RuleName)
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeRules::AnalysisType)
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeRules::ConditionExpr)
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeRules::ActionText)
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeRules::Priority)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeRules::IsEnabled)
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(true),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeRules::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeRules::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(AiKnowledgeRules::CreatedBy).uuid())
|
||||
.col(ColumnDef::new(AiKnowledgeRules::UpdatedBy).uuid())
|
||||
.col(ColumnDef::new(AiKnowledgeRules::DeletedAt).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(AiKnowledgeRules::VersionLock).integer().not_null().default(1))
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeRules::VersionLock)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -31,22 +31,69 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(AiKnowledgeReferences::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(AiKnowledgeReferences::Id).uuid().not_null().primary_key())
|
||||
.col(ColumnDef::new(AiKnowledgeReferences::TenantId).uuid().not_null())
|
||||
.col(ColumnDef::new(AiKnowledgeReferences::Title).string().not_null())
|
||||
.col(ColumnDef::new(AiKnowledgeReferences::AnalysisType).string().not_null())
|
||||
.col(ColumnDef::new(AiKnowledgeReferences::SourceName).string().not_null())
|
||||
.col(ColumnDef::new(AiKnowledgeReferences::ContentSummary).text().not_null())
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeReferences::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeReferences::TenantId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeReferences::Title)
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeReferences::AnalysisType)
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeReferences::SourceName)
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeReferences::ContentSummary)
|
||||
.text()
|
||||
.not_null(),
|
||||
)
|
||||
// vector(1536) — 兼容 OpenAI text-embedding-ada-002
|
||||
.col(ColumnDef::new(AiKnowledgeReferences::Embedding).custom("vector"))
|
||||
.col(ColumnDef::new(AiKnowledgeReferences::Tags).json())
|
||||
.col(ColumnDef::new(AiKnowledgeReferences::IsEnabled).boolean().not_null().default(true))
|
||||
.col(ColumnDef::new(AiKnowledgeReferences::CreatedAt).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(ColumnDef::new(AiKnowledgeReferences::UpdatedAt).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeReferences::IsEnabled)
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(true),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeReferences::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeReferences::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(AiKnowledgeReferences::CreatedBy).uuid())
|
||||
.col(ColumnDef::new(AiKnowledgeReferences::UpdatedBy).uuid())
|
||||
.col(ColumnDef::new(AiKnowledgeReferences::DeletedAt).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(AiKnowledgeReferences::VersionLock).integer().not_null().default(1))
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeReferences::DeletedAt).timestamp_with_time_zone(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeReferences::VersionLock)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -30,20 +30,53 @@ impl MigrationTrait for Migration {
|
||||
Table::create()
|
||||
.table(AiKnowledgeGuides::Table)
|
||||
.if_not_exists()
|
||||
.col(ColumnDef::new(AiKnowledgeGuides::Id).uuid().not_null().primary_key())
|
||||
.col(ColumnDef::new(AiKnowledgeGuides::TenantId).uuid().not_null())
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeGuides::Id)
|
||||
.uuid()
|
||||
.not_null()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeGuides::TenantId)
|
||||
.uuid()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(AiKnowledgeGuides::Title).string().not_null())
|
||||
.col(ColumnDef::new(AiKnowledgeGuides::AnalysisType).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeGuides::AnalysisType)
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(AiKnowledgeGuides::Content).text().not_null())
|
||||
.col(ColumnDef::new(AiKnowledgeGuides::Category).string())
|
||||
.col(ColumnDef::new(AiKnowledgeGuides::Embedding).custom("vector"))
|
||||
.col(ColumnDef::new(AiKnowledgeGuides::IsEnabled).boolean().not_null().default(true))
|
||||
.col(ColumnDef::new(AiKnowledgeGuides::CreatedAt).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(ColumnDef::new(AiKnowledgeGuides::UpdatedAt).timestamp_with_time_zone().not_null().default(Expr::current_timestamp()))
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeGuides::IsEnabled)
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(true),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeGuides::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeGuides::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null()
|
||||
.default(Expr::current_timestamp()),
|
||||
)
|
||||
.col(ColumnDef::new(AiKnowledgeGuides::CreatedBy).uuid())
|
||||
.col(ColumnDef::new(AiKnowledgeGuides::UpdatedBy).uuid())
|
||||
.col(ColumnDef::new(AiKnowledgeGuides::DeletedAt).timestamp_with_time_zone())
|
||||
.col(ColumnDef::new(AiKnowledgeGuides::VersionLock).integer().not_null().default(1))
|
||||
.col(
|
||||
ColumnDef::new(AiKnowledgeGuides::VersionLock)
|
||||
.integer()
|
||||
.not_null()
|
||||
.default(1),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -11,7 +11,8 @@ impl MigrationTrait for Migration {
|
||||
let db = manager.get_connection();
|
||||
|
||||
// 化验单解读 — 强化系统指令
|
||||
let sys_lab = esc(r#"你是一名专业的医学检验解读助手。这是由健康管理系统自动触发的分析任务,不是对话。
|
||||
let sys_lab = esc(
|
||||
r#"你是一名专业的医学检验解读助手。这是由健康管理系统自动触发的分析任务,不是对话。
|
||||
|
||||
请直接输出结构化的分析结果,格式如下:
|
||||
|
||||
@@ -30,10 +31,12 @@ impl MigrationTrait for Migration {
|
||||
要求:
|
||||
1. 直接输出结果,不要寒暄或询问
|
||||
2. 使用通俗易懂的语言
|
||||
3. 异常指标要重点标注"#);
|
||||
3. 异常指标要重点标注"#,
|
||||
);
|
||||
|
||||
// 趋势分析 — 保持已有的 v2 格式,只加非对话指令前缀
|
||||
let sys_trend = esc(r#"你是一名健康数据分析专家。你将收到经过预处理的结构化统计摘要数据,包括线性回归趋势、异常检测结果等。
|
||||
let sys_trend = esc(
|
||||
r#"你是一名健康数据分析专家。你将收到经过预处理的结构化统计摘要数据,包括线性回归趋势、异常检测结果等。
|
||||
这是由健康管理系统自动触发的分析任务,不是对话。请直接输出结构化的分析结果。
|
||||
|
||||
要求:
|
||||
@@ -42,10 +45,12 @@ impl MigrationTrait for Migration {
|
||||
3. **综合分析** — 考虑各指标间的关联性(如血压和体重、血糖和心率)
|
||||
4. **临床建议** — 给出切实可行的健康管理建议,不替代医生诊断
|
||||
5. **风险评级** — 对整体健康风险给出低/中/高评估并说明理由
|
||||
6. **关注重点** — 用简洁的语言总结最需要关注的 2-3 个问题"#);
|
||||
6. **关注重点** — 用简洁的语言总结最需要关注的 2-3 个问题"#,
|
||||
);
|
||||
|
||||
// 体检方案 — 加非对话指令
|
||||
let sys_checkup = esc(r#"你是一名健康管理顾问。这是由健康管理系统自动触发的分析任务,不是对话。
|
||||
let sys_checkup = esc(
|
||||
r#"你是一名健康管理顾问。这是由健康管理系统自动触发的分析任务,不是对话。
|
||||
请直接输出个性化的体检方案,格式如下:
|
||||
|
||||
## 推荐检查项目
|
||||
@@ -60,10 +65,12 @@ impl MigrationTrait for Migration {
|
||||
要求:
|
||||
1. 直接输出结果,不要寒暄或询问
|
||||
2. 基于患者年龄、性别、既往病史推荐
|
||||
3. 按优先级排序"#);
|
||||
3. 按优先级排序"#,
|
||||
);
|
||||
|
||||
// 报告摘要 — 加非对话指令
|
||||
let sys_summary = esc(r#"你是一名医疗报告摘要撰写专家。这是由健康管理系统自动触发的分析任务,不是对话。
|
||||
let sys_summary = esc(
|
||||
r#"你是一名医疗报告摘要撰写专家。这是由健康管理系统自动触发的分析任务,不是对话。
|
||||
请直接输出结构化的报告摘要,格式如下:
|
||||
|
||||
## 关键发现
|
||||
@@ -81,7 +88,8 @@ impl MigrationTrait for Migration {
|
||||
要求:
|
||||
1. 直接输出结果,不要寒暄或询问
|
||||
2. 控制在 500 字以内
|
||||
3. 语言简洁专业"#);
|
||||
3. 语言简洁专业"#,
|
||||
);
|
||||
|
||||
for (name, sys) in [
|
||||
("lab_report_interpretation", sys_lab),
|
||||
|
||||
@@ -23,10 +23,7 @@ impl MigrationTrait for Migration {
|
||||
for path in &frozen_paths {
|
||||
db.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
format!(
|
||||
"UPDATE menus SET visible = false WHERE path = '{}'",
|
||||
path
|
||||
),
|
||||
format!("UPDATE menus SET visible = false WHERE path = '{}'", path),
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
@@ -50,10 +47,7 @@ impl MigrationTrait for Migration {
|
||||
for path in &frozen_paths {
|
||||
db.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
format!(
|
||||
"UPDATE menus SET visible = true WHERE path = '{}'",
|
||||
path
|
||||
),
|
||||
format!("UPDATE menus SET visible = true WHERE path = '{}'", path),
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,8 @@ impl MigrationTrait for Migration {
|
||||
for &(path, sort) in sys_sort {
|
||||
db.execute_unprepared(&format!(
|
||||
"UPDATE menus SET sort_order = {sort} WHERE path = '{path}' AND deleted_at IS NULL"
|
||||
)).await?;
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
|
||||
// 调整"健康业务"目录下的排序 — 按功能域分组
|
||||
@@ -106,7 +107,8 @@ impl MigrationTrait for Migration {
|
||||
for &(path, sort) in health_sort {
|
||||
db.execute_unprepared(&format!(
|
||||
"UPDATE menus SET sort_order = {sort} WHERE path = '{path}' AND deleted_at IS NULL"
|
||||
)).await?;
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
@@ -117,7 +119,11 @@ impl MigrationTrait for Migration {
|
||||
let roles: &[(&str, &str, &str)] = &[
|
||||
("doctor", "医生", "负责患者诊疗、随访管理、AI辅助诊断"),
|
||||
("nurse", "护士", "负责患者护理、体征监测、用药管理"),
|
||||
("health_manager", "健康管理师", "负责健康管理计划、随访协调、运营统计"),
|
||||
(
|
||||
"health_manager",
|
||||
"健康管理师",
|
||||
"负责健康管理计划、随访协调、运营统计",
|
||||
),
|
||||
("operator", "运营人员", "负责内容运营、积分商城、活动管理"),
|
||||
];
|
||||
|
||||
@@ -140,81 +146,138 @@ impl MigrationTrait for Migration {
|
||||
// ================================================================
|
||||
|
||||
// doctor 权限: 患者 + 医护 + 随访 + 咨询 + AI + 告警 + 日常监测 + 诊断 + 知情同意 + 行动收件箱 + 消息(只读)
|
||||
assign_perms_by_codes(db, "doctor", &[
|
||||
"health.patient.list", "health.patient.manage",
|
||||
"health.doctor.list", "health.doctor.manage",
|
||||
"health.follow-up.list", "health.follow-up.manage",
|
||||
"health.consultation.list", "health.consultation.manage",
|
||||
"health.action-inbox.list", "health.action-inbox.manage",
|
||||
"health.daily-monitoring.list", "health.daily-monitoring.manage",
|
||||
"health.alerts.list", "health.alerts.manage",
|
||||
"health.alert-rules.list",
|
||||
"health.critical-alerts.list",
|
||||
"health.diagnosis.list", "health.diagnosis.manage",
|
||||
"health.consent.list", "health.consent.manage",
|
||||
"health.health-data.list",
|
||||
"ai.analysis.list", "ai.suggestion.list",
|
||||
"message.list",
|
||||
"workflow.list", "workflow.read",
|
||||
]).await?;
|
||||
assign_perms_by_codes(
|
||||
db,
|
||||
"doctor",
|
||||
&[
|
||||
"health.patient.list",
|
||||
"health.patient.manage",
|
||||
"health.doctor.list",
|
||||
"health.doctor.manage",
|
||||
"health.follow-up.list",
|
||||
"health.follow-up.manage",
|
||||
"health.consultation.list",
|
||||
"health.consultation.manage",
|
||||
"health.action-inbox.list",
|
||||
"health.action-inbox.manage",
|
||||
"health.daily-monitoring.list",
|
||||
"health.daily-monitoring.manage",
|
||||
"health.alerts.list",
|
||||
"health.alerts.manage",
|
||||
"health.alert-rules.list",
|
||||
"health.critical-alerts.list",
|
||||
"health.diagnosis.list",
|
||||
"health.diagnosis.manage",
|
||||
"health.consent.list",
|
||||
"health.consent.manage",
|
||||
"health.health-data.list",
|
||||
"ai.analysis.list",
|
||||
"ai.suggestion.list",
|
||||
"message.list",
|
||||
"workflow.list",
|
||||
"workflow.read",
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
|
||||
// nurse 权限: 患者 + 随访 + 咨询 + 告警 + 日常监测 + 诊断 + 知情同意 + 行动收件箱 + 消息(只读)
|
||||
assign_perms_by_codes(db, "nurse", &[
|
||||
"health.patient.list", "health.patient.manage",
|
||||
"health.follow-up.list", "health.follow-up.manage",
|
||||
"health.consultation.list",
|
||||
"health.action-inbox.list", "health.action-inbox.manage",
|
||||
"health.daily-monitoring.list", "health.daily-monitoring.manage",
|
||||
"health.alerts.list",
|
||||
"health.critical-alerts.list",
|
||||
"health.diagnosis.list",
|
||||
"health.consent.list", "health.consent.manage",
|
||||
"health.health-data.list",
|
||||
"health.device-readings.list",
|
||||
"message.list",
|
||||
]).await?;
|
||||
assign_perms_by_codes(
|
||||
db,
|
||||
"nurse",
|
||||
&[
|
||||
"health.patient.list",
|
||||
"health.patient.manage",
|
||||
"health.follow-up.list",
|
||||
"health.follow-up.manage",
|
||||
"health.consultation.list",
|
||||
"health.action-inbox.list",
|
||||
"health.action-inbox.manage",
|
||||
"health.daily-monitoring.list",
|
||||
"health.daily-monitoring.manage",
|
||||
"health.alerts.list",
|
||||
"health.critical-alerts.list",
|
||||
"health.diagnosis.list",
|
||||
"health.consent.list",
|
||||
"health.consent.manage",
|
||||
"health.health-data.list",
|
||||
"health.device-readings.list",
|
||||
"message.list",
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
|
||||
// health_manager 权限: 统计 + 患者 + 医护 + 随访 + 咨询 + AI + 告警 + 设备 + 日常监测 + 标签 + 诊断 + 知情同意 + 行动收件箱 + 随访模板
|
||||
assign_perms_by_codes(db, "health_manager", &[
|
||||
"health.patient.list", "health.patient.manage",
|
||||
"health.doctor.list",
|
||||
"health.follow-up.list", "health.follow-up.manage",
|
||||
"health.consultation.list", "health.consultation.manage",
|
||||
"health.action-inbox.list", "health.action-inbox.manage", "health.action-inbox.team",
|
||||
"health.daily-monitoring.list", "health.daily-monitoring.manage",
|
||||
"health.alerts.list", "health.alerts.manage",
|
||||
"health.alert-rules.list", "health.alert-rules.manage",
|
||||
"health.critical-alerts.list",
|
||||
"health.critical-value-thresholds.list",
|
||||
"health.devices.list",
|
||||
"health.tags.list", "health.tags.manage",
|
||||
"health.diagnosis.list", "health.diagnosis.manage",
|
||||
"health.consent.list", "health.consent.manage",
|
||||
"health.health-data.list", "health.health-data.manage",
|
||||
"health.follow-up-templates.list", "health.follow-up-templates.manage",
|
||||
"health.dashboard.manage",
|
||||
"ai.analysis.list", "ai.analysis.manage",
|
||||
"ai.prompt.list",
|
||||
"ai.suggestion.list", "ai.suggestion.manage",
|
||||
"ai.usage.list",
|
||||
"message.list",
|
||||
"workflow.list", "workflow.read", "workflow.start",
|
||||
]).await?;
|
||||
assign_perms_by_codes(
|
||||
db,
|
||||
"health_manager",
|
||||
&[
|
||||
"health.patient.list",
|
||||
"health.patient.manage",
|
||||
"health.doctor.list",
|
||||
"health.follow-up.list",
|
||||
"health.follow-up.manage",
|
||||
"health.consultation.list",
|
||||
"health.consultation.manage",
|
||||
"health.action-inbox.list",
|
||||
"health.action-inbox.manage",
|
||||
"health.action-inbox.team",
|
||||
"health.daily-monitoring.list",
|
||||
"health.daily-monitoring.manage",
|
||||
"health.alerts.list",
|
||||
"health.alerts.manage",
|
||||
"health.alert-rules.list",
|
||||
"health.alert-rules.manage",
|
||||
"health.critical-alerts.list",
|
||||
"health.critical-value-thresholds.list",
|
||||
"health.devices.list",
|
||||
"health.tags.list",
|
||||
"health.tags.manage",
|
||||
"health.diagnosis.list",
|
||||
"health.diagnosis.manage",
|
||||
"health.consent.list",
|
||||
"health.consent.manage",
|
||||
"health.health-data.list",
|
||||
"health.health-data.manage",
|
||||
"health.follow-up-templates.list",
|
||||
"health.follow-up-templates.manage",
|
||||
"health.dashboard.manage",
|
||||
"ai.analysis.list",
|
||||
"ai.analysis.manage",
|
||||
"ai.prompt.list",
|
||||
"ai.suggestion.list",
|
||||
"ai.suggestion.manage",
|
||||
"ai.usage.list",
|
||||
"message.list",
|
||||
"workflow.list",
|
||||
"workflow.read",
|
||||
"workflow.start",
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
|
||||
// operator 权限: 统计 + 标签 + 内容 + 积分 + 活动 + 设备 + 告警(只读)
|
||||
assign_perms_by_codes(db, "operator", &[
|
||||
"health.patient.list",
|
||||
"health.tags.list", "health.tags.manage",
|
||||
"health.articles.list", "health.articles.manage",
|
||||
"health.articles.review",
|
||||
"health.points.list", "health.points.manage",
|
||||
"health.offline-events.list", "health.offline-events.manage",
|
||||
"health.devices.list",
|
||||
"health.alerts.list",
|
||||
"health.dashboard.manage",
|
||||
"ai.usage.list",
|
||||
"message.list",
|
||||
]).await?;
|
||||
assign_perms_by_codes(
|
||||
db,
|
||||
"operator",
|
||||
&[
|
||||
"health.patient.list",
|
||||
"health.tags.list",
|
||||
"health.tags.manage",
|
||||
"health.articles.list",
|
||||
"health.articles.manage",
|
||||
"health.articles.review",
|
||||
"health.points.list",
|
||||
"health.points.manage",
|
||||
"health.offline-events.list",
|
||||
"health.offline-events.manage",
|
||||
"health.devices.list",
|
||||
"health.alerts.list",
|
||||
"health.dashboard.manage",
|
||||
"ai.usage.list",
|
||||
"message.list",
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
|
||||
// ================================================================
|
||||
// Part 5: 菜单-角色关联(menu_roles)
|
||||
@@ -224,39 +287,64 @@ impl MigrationTrait for Migration {
|
||||
|
||||
// doctor 可见菜单路径
|
||||
let doctor_paths: &[&str] = &[
|
||||
"/", "/health/statistics",
|
||||
"/health/patients", "/health/doctors",
|
||||
"/health/follow-up-tasks", "/health/consultations",
|
||||
"/health/action-inbox", "/health/follow-up-templates",
|
||||
"/health/daily-monitoring", "/health/consents", "/health/diagnoses",
|
||||
"/health/alert-dashboard", "/health/alerts",
|
||||
"/health/ai-analysis", "/health/ai-usage",
|
||||
"/",
|
||||
"/health/statistics",
|
||||
"/health/patients",
|
||||
"/health/doctors",
|
||||
"/health/follow-up-tasks",
|
||||
"/health/consultations",
|
||||
"/health/action-inbox",
|
||||
"/health/follow-up-templates",
|
||||
"/health/daily-monitoring",
|
||||
"/health/consents",
|
||||
"/health/diagnoses",
|
||||
"/health/alert-dashboard",
|
||||
"/health/alerts",
|
||||
"/health/ai-analysis",
|
||||
"/health/ai-usage",
|
||||
"/messages",
|
||||
];
|
||||
assign_menus_for_role(db, "doctor", doctor_paths).await?;
|
||||
|
||||
// nurse 可见菜单路径
|
||||
let nurse_paths: &[&str] = &[
|
||||
"/", "/health/statistics",
|
||||
"/",
|
||||
"/health/statistics",
|
||||
"/health/patients",
|
||||
"/health/follow-up-tasks", "/health/consultations",
|
||||
"/health/follow-up-tasks",
|
||||
"/health/consultations",
|
||||
"/health/action-inbox",
|
||||
"/health/daily-monitoring", "/health/consents", "/health/diagnoses",
|
||||
"/health/alert-dashboard", "/health/alerts",
|
||||
"/health/daily-monitoring",
|
||||
"/health/consents",
|
||||
"/health/diagnoses",
|
||||
"/health/alert-dashboard",
|
||||
"/health/alerts",
|
||||
"/messages",
|
||||
];
|
||||
assign_menus_for_role(db, "nurse", nurse_paths).await?;
|
||||
|
||||
// health_manager 可见菜单路径
|
||||
let hm_paths: &[&str] = &[
|
||||
"/", "/health/statistics",
|
||||
"/health/patients", "/health/doctors", "/health/tags",
|
||||
"/health/follow-up-tasks", "/health/consultations",
|
||||
"/health/action-inbox", "/health/follow-up-templates",
|
||||
"/health/daily-monitoring", "/health/consents", "/health/diagnoses",
|
||||
"/health/alert-dashboard", "/health/alerts", "/health/alert-rules",
|
||||
"/health/devices", "/health/critical-value-thresholds",
|
||||
"/health/ai-prompts", "/health/ai-analysis", "/health/ai-usage",
|
||||
"/",
|
||||
"/health/statistics",
|
||||
"/health/patients",
|
||||
"/health/doctors",
|
||||
"/health/tags",
|
||||
"/health/follow-up-tasks",
|
||||
"/health/consultations",
|
||||
"/health/action-inbox",
|
||||
"/health/follow-up-templates",
|
||||
"/health/daily-monitoring",
|
||||
"/health/consents",
|
||||
"/health/diagnoses",
|
||||
"/health/alert-dashboard",
|
||||
"/health/alerts",
|
||||
"/health/alert-rules",
|
||||
"/health/devices",
|
||||
"/health/critical-value-thresholds",
|
||||
"/health/ai-prompts",
|
||||
"/health/ai-analysis",
|
||||
"/health/ai-usage",
|
||||
"/health/realtime-monitor",
|
||||
"/messages",
|
||||
];
|
||||
@@ -264,13 +352,18 @@ impl MigrationTrait for Migration {
|
||||
|
||||
// operator 可见菜单路径
|
||||
let op_paths: &[&str] = &[
|
||||
"/", "/health/statistics",
|
||||
"/health/patients", "/health/tags",
|
||||
"/",
|
||||
"/health/statistics",
|
||||
"/health/patients",
|
||||
"/health/tags",
|
||||
"/health/articles",
|
||||
"/health/points-rules", "/health/points-products", "/health/points-orders",
|
||||
"/health/points-rules",
|
||||
"/health/points-products",
|
||||
"/health/points-orders",
|
||||
"/health/offline-events",
|
||||
"/health/devices",
|
||||
"/health/alert-dashboard", "/health/alerts",
|
||||
"/health/alert-dashboard",
|
||||
"/health/alerts",
|
||||
"/health/ai-usage",
|
||||
"/messages",
|
||||
];
|
||||
@@ -302,7 +395,8 @@ impl MigrationTrait for Migration {
|
||||
// 软删除角色
|
||||
db.execute_unprepared(&format!(
|
||||
"UPDATE roles SET deleted_at = NOW() WHERE code = '{code}' AND is_system = false"
|
||||
)).await?;
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
|
||||
// 恢复目录名
|
||||
@@ -314,17 +408,20 @@ impl MigrationTrait for Migration {
|
||||
for &(old, new) in dir_renames {
|
||||
db.execute_unprepared(&format!(
|
||||
"UPDATE menus SET title = '{new}' WHERE title = '{old}' AND menu_type = 'directory'"
|
||||
)).await?;
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
|
||||
// 恢复"系统"目录
|
||||
db.execute_unprepared(
|
||||
"UPDATE menus SET deleted_at = NULL WHERE title = '配置' AND menu_type = 'directory'"
|
||||
).await?;
|
||||
"UPDATE menus SET deleted_at = NULL WHERE title = '配置' AND menu_type = 'directory'",
|
||||
)
|
||||
.await?;
|
||||
// 恢复"系统"名称
|
||||
db.execute_unprepared(
|
||||
"UPDATE menus SET title = '系统' WHERE title = '配置' AND menu_type = 'directory'"
|
||||
).await?;
|
||||
"UPDATE menus SET title = '系统' WHERE title = '配置' AND menu_type = 'directory'",
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -50,10 +50,7 @@ impl MigrationTrait for Migration {
|
||||
}
|
||||
|
||||
// === Nurse 角色权限清理 ===
|
||||
let nurse_remove = vec![
|
||||
"health.doctor.list",
|
||||
"health.alerts.manage",
|
||||
];
|
||||
let nurse_remove = vec!["health.doctor.list", "health.alerts.manage"];
|
||||
for code in &nurse_remove {
|
||||
db.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
@@ -93,7 +90,8 @@ impl MigrationTrait for Migration {
|
||||
WHERE menu_roles.role_id = r.id AND menu_roles.menu_id = m.id \
|
||||
AND r.code = 'operator' AND m.title = '{title}'",
|
||||
),
|
||||
)).await?;
|
||||
))
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -11,10 +11,7 @@ impl MigrationTrait for Migration {
|
||||
// Doctor 移除 health.health-data.manage 和 ai.analysis.manage
|
||||
// 000125 正确分配了 health.health-data.list 和 ai.analysis.list,
|
||||
// 但早期迁移分配了 .manage 权限且未被 000126 清理
|
||||
let doctor_remove = vec![
|
||||
"health.health-data.manage",
|
||||
"ai.analysis.manage",
|
||||
];
|
||||
let doctor_remove = vec!["health.health-data.manage", "ai.analysis.manage"];
|
||||
for code in &doctor_remove {
|
||||
db.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
|
||||
@@ -33,13 +33,19 @@ impl MigrationTrait for Migration {
|
||||
("/health/shifts", "health.shifts.list"),
|
||||
("/health/medications", "health.medication-records.manage"),
|
||||
("/health/ble-gateways", "health.ble-gateways.list"),
|
||||
("/health/critical-value-thresholds", "health.critical-value-thresholds.list"),
|
||||
(
|
||||
"/health/critical-value-thresholds",
|
||||
"health.critical-value-thresholds.list",
|
||||
),
|
||||
("/health/diagnoses", "health.health-data.list"),
|
||||
("/health/family-proxy", "health.patient.list"),
|
||||
("/health/consents", "health.consent.list"),
|
||||
("/health/realtime-monitor", "health.device-readings.list"),
|
||||
("/health/oauth-clients", "health.oauth.list"),
|
||||
("/health/follow-up-templates", "health.follow-up-templates.list"),
|
||||
(
|
||||
"/health/follow-up-templates",
|
||||
"health.follow-up-templates.list",
|
||||
),
|
||||
];
|
||||
|
||||
for &(path, perm) in menu_perms {
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
//! 修复护士设备权限 + 运营积分/线下活动权限
|
||||
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
|
||||
// === 1. 护士角色:添加 health.devices.list 权限 ===
|
||||
// 护士有 health.device-readings.list(设备读数)但缺少 health.devices.list(设备绑定列表)
|
||||
db.execute_unprepared(
|
||||
"INSERT INTO role_permissions (role_id, permission_id)
|
||||
SELECT r.id, p.id FROM roles r, permissions p
|
||||
WHERE r.name = 'nurse' AND p.code = 'health.devices.list'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM role_permissions rp
|
||||
WHERE rp.role_id = r.id AND rp.permission_id = p.id
|
||||
)",
|
||||
)
|
||||
.await?;
|
||||
|
||||
// === 2. 运营角色:添加 health.points.list 和 health.points.manage ===
|
||||
// 运营需要管理积分商城,之前患者端端点错误使用了 health.health-data.list,
|
||||
// 已修正为 health.points.list,需给运营角色补充对应权限
|
||||
db.execute_unprepared(
|
||||
"INSERT INTO role_permissions (role_id, permission_id)
|
||||
SELECT r.id, p.id FROM roles r, permissions p
|
||||
WHERE r.name = 'operator' AND p.code IN ('health.points.list', 'health.points.manage')
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM role_permissions rp
|
||||
WHERE rp.role_id = r.id AND rp.permission_id = p.id
|
||||
)",
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
|
||||
db.execute_unprepared(
|
||||
"DELETE FROM role_permissions
|
||||
WHERE role_id = (SELECT id FROM roles WHERE name = 'nurse')
|
||||
AND permission_id = (SELECT id FROM permissions WHERE code = 'health.devices.list')",
|
||||
)
|
||||
.await?;
|
||||
|
||||
db.execute_unprepared(
|
||||
"DELETE FROM role_permissions
|
||||
WHERE role_id = (SELECT id FROM roles WHERE name = 'operator')
|
||||
AND permission_id IN (SELECT id FROM permissions WHERE code IN ('health.points.list', 'health.points.manage'))",
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user