feat(health): Phase 1 业务改进 — 诊断编码/统计API/体征表合并/积分修复
1.1 Dashboard 统计: 新增 3 个统计端点 (patient/consultation/follow-up) 1.2 事件发布: follow_up.overdue + health_data.critical_alert 事件 1.3 体征表合并: vital_signs 添加 source 列, daily_monitoring 委托写入 1.4 实时预警: 创建体征时检测血压/心率/血糖异常并发布事件 1.5 诊断编码: 新建 diagnosis entity/service/handler + ICD-10 支持 1.6 积分过期: expire_points 定时任务 + 修复 r#type 列名问题 修复: points_transaction.r#type → transaction_type 列重命名 修复: consultation_message.sender_type → sender_role SQL 列名 前端: 3 个统计 API 从伪实现改为真实调用
This commit is contained in:
@@ -55,6 +55,9 @@ 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 m20260426_000056_create_diagnosis;
|
||||
mod m20260426_000057_rename_points_transaction_type_column;
|
||||
mod m20260426_000058_merge_daily_monitoring_into_vital_signs;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
@@ -117,6 +120,9 @@ impl MigratorTrait for Migrator {
|
||||
Box::new(m20260425_000053_create_points_tables::Migration),
|
||||
Box::new(m20260425_000054_create_daily_monitoring::Migration),
|
||||
Box::new(m20260425_000055_points_checkin_standard_fields::Migration),
|
||||
Box::new(m20260426_000056_create_diagnosis::Migration),
|
||||
Box::new(m20260426_000057_rename_points_transaction_type_column::Migration),
|
||||
Box::new(m20260426_000058_merge_daily_monitoring_into_vital_signs::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
use sea_orm_migration::{prelude::*, schema::*};
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
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("diagnosis"))
|
||||
.if_not_exists()
|
||||
.col(uuid("id").primary_key())
|
||||
.col(uuid("tenant_id").not_null())
|
||||
.col(uuid("patient_id").not_null())
|
||||
.col(uuid_null("health_record_id"))
|
||||
.col(string_uniq("icd_code").not_null())
|
||||
.col(string("diagnosis_name").not_null())
|
||||
.col(string("diagnosis_type").not_null().default("primary"))
|
||||
.col(date("diagnosed_date").not_null())
|
||||
.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(uuid_null("created_by"))
|
||||
.col(uuid_null("updated_by"))
|
||||
.col(timestamp_with_time_zone_null("deleted_at"))
|
||||
.col(integer("version").not_null().default(1))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.if_not_exists()
|
||||
.name("idx_diagnosis_tenant_patient")
|
||||
.table(Alias::new("diagnosis"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("patient_id"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.if_not_exists()
|
||||
.name("idx_diagnosis_icd_code")
|
||||
.table(Alias::new("diagnosis"))
|
||||
.col(Alias::new("tenant_id"))
|
||||
.col(Alias::new("icd_code"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.if_not_exists()
|
||||
.name("idx_diagnosis_deleted_at")
|
||||
.table(Alias::new("diagnosis"))
|
||||
.col(Alias::new("deleted_at"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Alias::new("diagnosis")).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
/// 修复 points_transaction 表列名:r#type → transaction_type
|
||||
/// 原迁移使用 Alias::new("r#type") 导致实际 PG 列名为 "r#type",
|
||||
/// 但 SeaORM DeriveEntityModel 将 Rust 的 r#type 映射为 SQL 列名 "type",造成查询失败。
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.get_connection()
|
||||
.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
r#"ALTER TABLE points_transaction RENAME COLUMN "r#type" TO transaction_type"#,
|
||||
))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.get_connection()
|
||||
.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
r#"ALTER TABLE points_transaction RENAME COLUMN transaction_type TO "r#type""#,
|
||||
))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
/// 合并 daily_monitoring 到 vital_signs:
|
||||
/// 1. 给 vital_signs 添加 source 列(标记数据来源)
|
||||
/// 2. 迁移 daily_monitoring 已有数据到 vital_signs(设置 source = 'daily_monitoring')
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// 1. 给 vital_signs 添加 source 列
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Alias::new("vital_signs"))
|
||||
.add_column(
|
||||
ColumnDef::new(Alias::new("source"))
|
||||
.string_len(20)
|
||||
.not_null()
|
||||
.default("manual"),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 2. 迁移 daily_monitoring 数据到 vital_signs
|
||||
let migrate_sql = r#"
|
||||
INSERT INTO vital_signs (
|
||||
id, tenant_id, patient_id, record_date,
|
||||
systolic_bp_morning, diastolic_bp_morning,
|
||||
systolic_bp_evening, diastolic_bp_evening,
|
||||
heart_rate, weight, blood_sugar,
|
||||
water_intake_ml, urine_output_ml, notes,
|
||||
created_at, updated_at, created_by, updated_by,
|
||||
deleted_at, version, source
|
||||
)
|
||||
SELECT
|
||||
id, tenant_id, patient_id, record_date,
|
||||
morning_bp_systolic, morning_bp_diastolic,
|
||||
evening_bp_systolic, evening_bp_diastolic,
|
||||
NULL, weight, blood_sugar,
|
||||
fluid_intake, urine_output, notes,
|
||||
created_at, updated_at, created_by, updated_by,
|
||||
deleted_at, version, 'daily_monitoring'
|
||||
FROM daily_monitoring
|
||||
ON CONFLICT (id) DO NOTHING
|
||||
"#;
|
||||
manager
|
||||
.get_connection()
|
||||
.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
migrate_sql,
|
||||
))
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// 删除从 daily_monitoring 迁移过来的数据
|
||||
manager
|
||||
.get_connection()
|
||||
.execute(sea_orm::Statement::from_string(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
"DELETE FROM vital_signs WHERE source = 'daily_monitoring'",
|
||||
))
|
||||
.await?;
|
||||
|
||||
// 移除 source 列
|
||||
manager
|
||||
.alter_table(
|
||||
Table::alter()
|
||||
.table(Alias::new("vital_signs"))
|
||||
.drop_column(Alias::new("source"))
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -417,9 +417,8 @@ async fn main() -> anyhow::Result<()> {
|
||||
erp_workflow::WorkflowModule::start_timeout_checker(db.clone());
|
||||
tracing::info!("Timeout checker started");
|
||||
|
||||
// Start follow-up overdue checker (every 6 hours)
|
||||
erp_health::HealthModule::start_overdue_checker(db.clone());
|
||||
tracing::info!("Follow-up overdue checker started");
|
||||
// Start follow-up overdue checker (handled by HealthModule::on_startup)
|
||||
tracing::info!("Follow-up overdue checker delegated to module on_startup");
|
||||
|
||||
let host = config.server.host.clone();
|
||||
let port = config.server.port;
|
||||
|
||||
Reference in New Issue
Block a user