feat(health): Phase 4 跨模块集成与架构优化 — 通知/标签/待办/数据录入
后端: - erp-message: 添加 appointment.created/confirmed/cancelled 事件监听,自动发送站内通知 - erp-health: 新增 GET /health/patient-tags 标签列表端点 + list_tags service - wechat-templates: 添加 isTemplateConfigured 运行时校验 前端: - 新增 Zustand useHealthStore 共享患者/医生名称缓存 - PatientTagManage: UUID 输入替换为 Checkbox 标签选择器 - VitalSignsTab: 添加体征数据录入 Modal (血压/心率/体重/血糖) - LabReportsTab: 添加化验报告创建 Modal - HealthRecordsTab: 添加健康记录创建 Modal - patients API: 添加 TagItem 类型 + listTags 方法 小程序: - 首页待办事项接入预约和随访 API,替换硬编码 EmptyState
This commit is contained in:
@@ -130,3 +130,11 @@ pub struct PatientListQuery {
|
||||
pub tag_id: Option<Uuid>,
|
||||
pub status: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, ToSchema)]
|
||||
pub struct TagResp {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub color: Option<String>,
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
@@ -306,3 +306,16 @@ pub struct FamilyMemberUpdateWithVersion {
|
||||
pub notes: Option<String>,
|
||||
pub version: i32,
|
||||
}
|
||||
|
||||
pub async fn list_tags<S>(
|
||||
State(state): State<HealthState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
) -> Result<Json<ApiResponse<Vec<crate::dto::patient_dto::TagResp>>>, AppError>
|
||||
where
|
||||
HealthState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
require_permission(&ctx, "health.patient.list")?;
|
||||
let tags = patient_service::list_tags(&state, ctx.tenant_id).await?;
|
||||
Ok(Json(ApiResponse::ok(tags)))
|
||||
}
|
||||
|
||||
@@ -69,6 +69,10 @@ impl HealthModule {
|
||||
"/health/patients/{id}/tags",
|
||||
axum::routing::post(patient_handler::manage_patient_tags),
|
||||
)
|
||||
.route(
|
||||
"/health/patient-tags",
|
||||
axum::routing::get(patient_handler::list_tags),
|
||||
)
|
||||
.route(
|
||||
"/health/patients/{id}/health-summary",
|
||||
axum::routing::get(patient_handler::get_health_summary),
|
||||
|
||||
@@ -760,3 +760,27 @@ fn model_to_resp_decrypted(crypto: &crate::crypto::HealthCrypto, m: patient::Mod
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub async fn list_tags(
|
||||
state: &crate::state::HealthState,
|
||||
tenant_id: Uuid,
|
||||
) -> HealthResult<Vec<crate::dto::patient_dto::TagResp>> {
|
||||
use crate::entity::patient_tag;
|
||||
let tags = patient_tag::Entity::find()
|
||||
.filter(patient_tag::Column::TenantId.eq(tenant_id))
|
||||
.filter(patient_tag::Column::DeletedAt.is_null())
|
||||
.order_by_asc(patient_tag::Column::Name)
|
||||
.all(&state.db)
|
||||
.await
|
||||
.map_err(|e| crate::error::HealthError::DbError(e.to_string()))?;
|
||||
|
||||
Ok(tags
|
||||
.into_iter()
|
||||
.map(|t| crate::dto::patient_dto::TagResp {
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
color: t.color,
|
||||
description: t.description,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
@@ -195,6 +195,91 @@ async fn handle_workflow_event(
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
}
|
||||
// 预约事件通知
|
||||
"appointment.created" => {
|
||||
let appointment_id = event
|
||||
.payload
|
||||
.get("appointment_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("unknown");
|
||||
let patient_id = event
|
||||
.payload
|
||||
.get("patient_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| uuid::Uuid::parse_str(s).ok());
|
||||
|
||||
if let Some(pid) = patient_id {
|
||||
let _ = crate::service::message_service::MessageService::send_system(
|
||||
event.tenant_id,
|
||||
pid,
|
||||
"预约已创建".to_string(),
|
||||
format!("您的新预约 {} 已创建,请等待确认。", &appointment_id[..8.min(appointment_id.len())]),
|
||||
"normal",
|
||||
Some("appointment".to_string()),
|
||||
uuid::Uuid::parse_str(appointment_id).ok(),
|
||||
db,
|
||||
event_bus,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
}
|
||||
"appointment.confirmed" => {
|
||||
let appointment_id = event
|
||||
.payload
|
||||
.get("appointment_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("unknown");
|
||||
let patient_id = event
|
||||
.payload
|
||||
.get("patient_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| uuid::Uuid::parse_str(s).ok());
|
||||
|
||||
if let Some(pid) = patient_id {
|
||||
let _ = crate::service::message_service::MessageService::send_system(
|
||||
event.tenant_id,
|
||||
pid,
|
||||
"预约已确认".to_string(),
|
||||
format!("您的预约 {} 已确认,请按时就诊。", &appointment_id[..8.min(appointment_id.len())]),
|
||||
"important",
|
||||
Some("appointment".to_string()),
|
||||
uuid::Uuid::parse_str(appointment_id).ok(),
|
||||
db,
|
||||
event_bus,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
}
|
||||
"appointment.cancelled" => {
|
||||
let appointment_id = event
|
||||
.payload
|
||||
.get("appointment_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("unknown");
|
||||
let patient_id = event
|
||||
.payload
|
||||
.get("patient_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| uuid::Uuid::parse_str(s).ok());
|
||||
|
||||
if let Some(pid) = patient_id {
|
||||
let _ = crate::service::message_service::MessageService::send_system(
|
||||
event.tenant_id,
|
||||
pid,
|
||||
"预约已取消".to_string(),
|
||||
format!("您的预约 {} 已被取消。", &appointment_id[..8.min(appointment_id.len())]),
|
||||
"normal",
|
||||
Some("appointment".to_string()),
|
||||
uuid::Uuid::parse_str(appointment_id).ok(),
|
||||
db,
|
||||
event_bus,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user