feat(message+health): 补全 14 个事件消费者 + 修复 6 个事件 payload 缺失字段
Some checks failed
CI / rust-check (push) Has been cancelled
CI / rust-test (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled

事件消费者补全(erp-message/module.rs):
- consultation.opened: 医生收到新咨询会话通知
- consultation.closed: 患者收到会话结束通知
- follow_up.created: 被分配人收到新随访任务通知
- follow_up.completed: 患者收到随访完成通知
- points.earned: 患者收到积分到账通知
- points.exchanged: 患者收到兑换成功通知
- points.expired: 患者收到积分过期提醒
- article.published/rejected: 作者收到审核结果通知
- ai.analysis.failed: 医生收到 AI 分析失败通知
- lab_report.uploaded/patient.updated/daily_monitoring/doctor: 审计日志记录

事件 payload 补充(erp-health services):
- consultation.opened: 添加 doctor_id 字段
- follow_up.created: 添加 assigned_to + planned_date 字段
- points.earned: 添加 patient_id + reason 字段
- points.exchanged: 添加 product_name 字段
- article.rejected: 添加 author_id 字段
This commit is contained in:
iven
2026-04-30 08:31:12 +08:00
parent cec487bd2c
commit 1925568c13
5 changed files with 363 additions and 4 deletions

View File

@@ -235,7 +235,8 @@ pub async fn reject_article(
state.event_bus.publish(
DomainEvent::new(crate::event::ARTICLE_REJECTED, tenant_id, erp_core::events::build_event_payload(serde_json::json!({
"article_id": m.id, "title": m.title,
"article_id": m.id.to_string(), "title": m.title,
"author_id": m.created_by.map(|id| id.to_string()).unwrap_or_default(),
}))),
&state.db,
).await;

View File

@@ -77,7 +77,11 @@ pub async fn create_session(
let event = DomainEvent::new(
crate::event::CONSULTATION_OPENED,
tenant_id,
erp_core::events::build_event_payload(serde_json::json!({ "session_id": m.id, "patient_id": m.patient_id })),
erp_core::events::build_event_payload(serde_json::json!({
"session_id": m.id.to_string(),
"patient_id": m.patient_id.to_string(),
"doctor_id": m.doctor_id.map(|id| id.to_string()).unwrap_or_default(),
})),
);
state.event_bus.publish(event, &state.db).await;
@@ -175,7 +179,10 @@ pub async fn close_session(
let event = DomainEvent::new(
crate::event::CONSULTATION_CLOSED,
tenant_id,
erp_core::events::build_event_payload(serde_json::json!({ "session_id": m.id, "patient_id": m.patient_id })),
erp_core::events::build_event_payload(serde_json::json!({
"session_id": m.id.to_string(),
"patient_id": m.patient_id.to_string(),
})),
);
state.event_bus.publish(event, &state.db).await;

View File

@@ -170,7 +170,12 @@ pub async fn create_task(
let event = DomainEvent::new(
crate::event::FOLLOW_UP_CREATED,
tenant_id,
erp_core::events::build_event_payload(serde_json::json!({ "task_id": m.id, "patient_id": m.patient_id })),
erp_core::events::build_event_payload(serde_json::json!({
"task_id": m.id.to_string(),
"patient_id": m.patient_id.to_string(),
"assigned_to": m.assigned_to.map(|id| id.to_string()).unwrap_or_default(),
"planned_date": m.planned_date.to_string(),
})),
);
state.event_bus.publish(event, &state.db).await;

View File

@@ -195,6 +195,7 @@ pub async fn earn_points(
DomainEvent::new(crate::event::POINTS_EARNED, tenant_id, erp_core::events::build_event_payload(serde_json::json!({
"transaction_id": inserted.id, "account_id": inserted.account_id,
"amount": inserted.amount, "balance_after": inserted.balance_after,
"patient_id": patient_id.to_string(), "reason": event_type,
}))),
&state.db,
).await;
@@ -980,6 +981,7 @@ pub async fn exchange_product(
DomainEvent::new(crate::event::POINTS_EXCHANGED, tenant_id, erp_core::events::build_event_payload(serde_json::json!({
"order_id": inserted_order.id, "patient_id": inserted_order.patient_id,
"product_id": inserted_order.product_id, "points_cost": inserted_order.points_cost,
"product_name": product.name,
}))),
&state.db,
).await;

View File

@@ -602,6 +602,350 @@ async fn handle_workflow_event(
.map_err(|e| e.to_string())?;
}
}
// 咨询会话开启通知医生
"consultation.opened" => {
let doctor_id = event
.payload
.get("doctor_id")
.and_then(|v| v.as_str())
.and_then(|s| uuid::Uuid::parse_str(s).ok());
let patient_name = event
.payload
.get("patient_name")
.and_then(|v| v.as_str())
.unwrap_or("患者");
let session_id = event
.payload
.get("session_id")
.and_then(|v| v.as_str())
.unwrap_or("");
if let Some(did) = doctor_id {
if should_skip_for_dnd(event.tenant_id, did, "normal", db).await {
return Ok(());
}
let _ = crate::service::message_service::MessageService::send_system(
event.tenant_id,
did,
format!("新咨询会话 — {}", patient_name),
format!("患者 {} 发起了咨询会话,请及时响应。", patient_name),
"normal",
Some("consultation".to_string()),
uuid::Uuid::parse_str(session_id).ok(),
db,
event_bus,
)
.await
.map_err(|e| e.to_string())?;
}
}
// 咨询会话关闭通知患者
"consultation.closed" => {
let patient_id = event
.payload
.get("patient_id")
.and_then(|v| v.as_str())
.and_then(|s| uuid::Uuid::parse_str(s).ok());
let session_id = event
.payload
.get("session_id")
.and_then(|v| v.as_str())
.unwrap_or("");
if let Some(pid) = patient_id {
if should_skip_for_dnd(event.tenant_id, pid, "normal", db).await {
return Ok(());
}
let _ = crate::service::message_service::MessageService::send_system(
event.tenant_id,
pid,
"咨询会话已结束".to_string(),
"您的咨询会话已由医生关闭,感谢使用。".to_string(),
"normal",
Some("consultation".to_string()),
uuid::Uuid::parse_str(session_id).ok(),
db,
event_bus,
)
.await
.map_err(|e| e.to_string())?;
}
}
// 随访任务创建通知被分配人
"follow_up.created" => {
let assigned_to = event
.payload
.get("assigned_to")
.and_then(|v| v.as_str())
.and_then(|s| uuid::Uuid::parse_str(s).ok());
let patient_name = event
.payload
.get("patient_name")
.and_then(|v| v.as_str())
.unwrap_or("");
let planned_date = event
.payload
.get("planned_date")
.and_then(|v| v.as_str())
.unwrap_or("未知日期");
let task_id = event
.payload
.get("task_id")
.and_then(|v| v.as_str())
.unwrap_or("");
if let Some(assignee) = assigned_to {
if should_skip_for_dnd(event.tenant_id, assignee, "normal", db).await {
return Ok(());
}
let patient_info = if patient_name.is_empty() {
String::new()
} else {
format!("(患者:{}", patient_name)
};
let _ = crate::service::message_service::MessageService::send_system(
event.tenant_id,
assignee,
format!("新随访任务{}", patient_info),
format!("您被分配了一个随访任务{},计划日期:{}", patient_info, planned_date),
"normal",
Some("follow_up".to_string()),
uuid::Uuid::parse_str(task_id).ok(),
db,
event_bus,
)
.await
.map_err(|e| e.to_string())?;
}
}
// 随访完成通知患者
"follow_up.completed" => {
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 {
if should_skip_for_dnd(event.tenant_id, pid, "normal", db).await {
return Ok(());
}
let _ = crate::service::message_service::MessageService::send_system(
event.tenant_id,
pid,
"随访已完成".to_string(),
"您的随访任务已完成,感谢配合。".to_string(),
"normal",
Some("follow_up".to_string()),
None,
db,
event_bus,
)
.await
.map_err(|e| e.to_string())?;
}
}
// 积分获得通知患者
"points.earned" => {
let patient_id = event
.payload
.get("patient_id")
.and_then(|v| v.as_str())
.and_then(|s| uuid::Uuid::parse_str(s).ok());
let points = event
.payload
.get("points")
.and_then(|v| v.as_i64())
.unwrap_or(0);
let reason = event
.payload
.get("reason")
.and_then(|v| v.as_str())
.unwrap_or("系统奖励");
if let Some(pid) = patient_id {
if should_skip_for_dnd(event.tenant_id, pid, "low", db).await {
return Ok(());
}
let _ = crate::service::message_service::MessageService::send_system(
event.tenant_id,
pid,
"积分到账".to_string(),
format!("您获得了 {} 积分({})。", points, reason),
"low",
Some("points".to_string()),
None,
db,
event_bus,
)
.await
.map_err(|e| e.to_string())?;
}
}
// 积分兑换通知患者
"points.exchanged" => {
let patient_id = event
.payload
.get("patient_id")
.and_then(|v| v.as_str())
.and_then(|s| uuid::Uuid::parse_str(s).ok());
let product_name = event
.payload
.get("product_name")
.and_then(|v| v.as_str())
.unwrap_or("商品");
if let Some(pid) = patient_id {
if should_skip_for_dnd(event.tenant_id, pid, "normal", db).await {
return Ok(());
}
let _ = crate::service::message_service::MessageService::send_system(
event.tenant_id,
pid,
"兑换成功".to_string(),
format!("您已成功兑换「{}」,请留意后续通知。", product_name),
"normal",
Some("points".to_string()),
None,
db,
event_bus,
)
.await
.map_err(|e| e.to_string())?;
}
}
// 积分过期通知患者
"points.expired" => {
let patient_id = event
.payload
.get("patient_id")
.and_then(|v| v.as_str())
.and_then(|s| uuid::Uuid::parse_str(s).ok());
let points = event
.payload
.get("points")
.and_then(|v| v.as_i64())
.unwrap_or(0);
if let Some(pid) = patient_id {
if should_skip_for_dnd(event.tenant_id, pid, "low", db).await {
return Ok(());
}
let _ = crate::service::message_service::MessageService::send_system(
event.tenant_id,
pid,
"积分过期提醒".to_string(),
format!("您有 {} 积分已过期。", points),
"low",
Some("points".to_string()),
None,
db,
event_bus,
)
.await
.map_err(|e| e.to_string())?;
}
}
// 文章发布通知(暂记录日志,无直接用户推送)
"article.published" => {
tracing::info!(
article_id = %event.payload.get("article_id").and_then(|v| v.as_str()).unwrap_or(""),
"文章已发布"
);
}
// 文章驳回通知作者
"article.rejected" => {
let author_id = event
.payload
.get("author_id")
.and_then(|v| v.as_str())
.and_then(|s| uuid::Uuid::parse_str(s).ok());
let title = event
.payload
.get("title")
.and_then(|v| v.as_str())
.unwrap_or("文章");
if let Some(aid) = author_id {
if should_skip_for_dnd(event.tenant_id, aid, "normal", db).await {
return Ok(());
}
let _ = crate::service::message_service::MessageService::send_system(
event.tenant_id,
aid,
format!("文章未通过审核 — {}", title),
format!("您的文章「{}」未通过审核,请修改后重新提交。", title),
"normal",
Some("article".to_string()),
None,
db,
event_bus,
)
.await
.map_err(|e| e.to_string())?;
}
}
// 患者信息更新通知(审计日志用途)
"patient.updated" => {
tracing::info!(
patient_id = %event.payload.get("patient_id").and_then(|v| v.as_str()).unwrap_or(""),
"患者信息已更新"
);
}
// AI 分析失败通知医生
"ai.analysis.failed" => {
let doctor_id = event
.payload
.get("doctor_id")
.and_then(|v| v.as_str())
.and_then(|s| uuid::Uuid::parse_str(s).ok());
let error = event
.payload
.get("error")
.and_then(|v| v.as_str())
.unwrap_or("未知错误");
if let Some(did) = doctor_id {
if should_skip_for_dnd(event.tenant_id, did, "normal", db).await {
return Ok(());
}
let _ = crate::service::message_service::MessageService::send_system(
event.tenant_id,
did,
"AI 分析失败".to_string(),
format!("AI 分析任务执行失败:{},请稍后重试。", error),
"normal",
Some("ai".to_string()),
None,
db,
event_bus,
)
.await
.map_err(|e| e.to_string())?;
}
}
// 化验单上传记录日志
"lab_report.uploaded" => {
tracing::info!(
patient_id = %event.payload.get("patient_id").and_then(|v| v.as_str()).unwrap_or(""),
"化验单已上传"
);
}
// 日常监测记录日志
"daily_monitoring.created" => {
tracing::info!(
patient_id = %event.payload.get("patient_id").and_then(|v| v.as_str()).unwrap_or(""),
"日常监测数据已记录"
);
}
// 医生在线状态变更记录日志
"doctor.online_status_changed" => {
tracing::info!(
doctor_id = %event.payload.get("doctor_id").and_then(|v| v.as_str()).unwrap_or(""),
status = %event.payload.get("status").and_then(|v| v.as_str()).unwrap_or(""),
"医生在线状态变更"
);
}
_ => {}
}
Ok(())