feat(health+message): 关怀已送达通知管道 — care.action.performed 事件 + 温暖消息推送
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

- 新增 CARE_ACTION_PERFORMED 事件常量(care.action.performed)
- care_plan_service 在护理项完成、测量数据更新、干预项创建时发布关怀行动事件
- erp-message 新增 care_plan.activated/completed + care.action.performed 消息处理
- 温暖消息文案:护理计划启动/完成通知、关怀已送达、健康数据已更新
- 事件测试覆盖新常量、payload 契约、通知分支逻辑
This commit is contained in:
iven
2026-05-04 18:56:52 +08:00
parent 0a5290aee4
commit 3ff17382ff
3 changed files with 321 additions and 5 deletions

View File

@@ -63,6 +63,9 @@ pub const CARE_PLAN_UPDATED: &str = "care_plan.updated";
pub const CARE_PLAN_ACTIVATED: &str = "care_plan.activated";
pub const CARE_PLAN_COMPLETED: &str = "care_plan.completed";
// 关怀行动
pub const CARE_ACTION_PERFORMED: &str = "care.action.performed";
/// 兼容旧签名 — 不做任何实际订阅(逻辑已迁移到 on_startup
pub fn register_handlers(_bus: &EventBus) {
// 事件处理器已迁移到 on_startup → register_handlers_with_state
@@ -995,6 +998,11 @@ mod tests {
POINTS_EXPIRED,
POINTS_EARNED,
POINTS_EXCHANGED,
CARE_PLAN_CREATED,
CARE_PLAN_UPDATED,
CARE_PLAN_ACTIVATED,
CARE_PLAN_COMPLETED,
CARE_ACTION_PERFORMED,
];
for t in &all_types {
assert_valid_event_type(t);
@@ -1030,6 +1038,11 @@ mod tests {
POINTS_EXPIRED,
POINTS_EARNED,
POINTS_EXCHANGED,
CARE_PLAN_CREATED,
CARE_PLAN_UPDATED,
CARE_PLAN_ACTIVATED,
CARE_PLAN_COMPLETED,
CARE_ACTION_PERFORMED,
];
let set: HashSet<&&str> = all_types.iter().collect();
assert_eq!(
@@ -1068,6 +1081,11 @@ mod tests {
assert_eq!(POINTS_EXPIRED, "points.expired");
assert_eq!(POINTS_EARNED, "points.earned");
assert_eq!(POINTS_EXCHANGED, "points.exchanged");
assert_eq!(CARE_PLAN_CREATED, "care_plan.created");
assert_eq!(CARE_PLAN_UPDATED, "care_plan.updated");
assert_eq!(CARE_PLAN_ACTIVATED, "care_plan.activated");
assert_eq!(CARE_PLAN_COMPLETED, "care_plan.completed");
assert_eq!(CARE_ACTION_PERFORMED, "care.action.performed");
}
/// 消费者中硬编码的事件类型(非通过常量引用)也必须可被常量覆盖
@@ -1686,6 +1704,15 @@ mod tests {
assert!(LAB_REPORT_REVIEWED.starts_with(prefix));
}
#[test]
fn subscribe_prefix_covers_all_care_plan_events() {
let prefix = "care_plan.";
assert!(CARE_PLAN_CREATED.starts_with(prefix));
assert!(CARE_PLAN_UPDATED.starts_with(prefix));
assert!(CARE_PLAN_ACTIVATED.starts_with(prefix));
assert!(CARE_PLAN_COMPLETED.starts_with(prefix));
}
// ── device.readings.synced 消费者设备类型列表测试 ──────────────────
#[test]
@@ -2145,4 +2172,117 @@ mod tests {
];
assert_eq!(consumer_ids.len(), 23, "消费者总数应为 23");
}
// ── care.action.performed 事件 payload 契约测试 ──────────────────
/// care.action.performed (item_completed) payload 契约
#[test]
fn care_action_item_completed_payload_contract() {
let plan_id = Uuid::now_v7();
let patient_id = Uuid::now_v7();
let payload = build_event_payload(json!({
"plan_id": plan_id.to_string(),
"patient_id": patient_id.to_string(),
"action": "item_completed",
"item_title": "血压监测",
"item_type": "monitoring",
"operator_id": Uuid::now_v7().to_string(),
}));
let pid = payload.get("patient_id").and_then(|v| v.as_str()).and_then(|s| Uuid::parse_str(s).ok());
assert!(pid.is_some(), "消费者需要 patient_id");
let action = payload.get("action").and_then(|v| v.as_str()).unwrap_or("");
assert_eq!(action, "item_completed");
let item_title = payload.get("item_title").and_then(|v| v.as_str()).unwrap_or("护理项目");
assert_eq!(item_title, "血压监测");
}
/// care.action.performed (outcome_measured) payload 契约
#[test]
fn care_action_outcome_measured_payload_contract() {
let plan_id = Uuid::now_v7();
let patient_id = Uuid::now_v7();
let payload = build_event_payload(json!({
"plan_id": plan_id.to_string(),
"patient_id": patient_id.to_string(),
"action": "outcome_measured",
"metric": "血压",
"operator_id": Uuid::now_v7().to_string(),
}));
let pid = payload.get("patient_id").and_then(|v| v.as_str()).and_then(|s| Uuid::parse_str(s).ok());
assert!(pid.is_some(), "消费者需要 patient_id");
let action = payload.get("action").and_then(|v| v.as_str()).unwrap_or("");
assert_eq!(action, "outcome_measured");
let metric = payload.get("metric").and_then(|v| v.as_str()).unwrap_or("健康指标");
assert_eq!(metric, "血压");
}
/// care_plan.activated / care_plan.completed payload 契约
#[test]
fn care_plan_lifecycle_payload_contract() {
let plan_id = Uuid::now_v7();
let patient_id = Uuid::now_v7();
for (event_type, status) in [
(CARE_PLAN_ACTIVATED, "active"),
(CARE_PLAN_COMPLETED, "completed"),
] {
let payload = build_event_payload(json!({
"plan_id": plan_id.to_string(),
"patient_id": patient_id.to_string(),
"status": status,
}));
let pid = payload.get("patient_id").and_then(|v| v.as_str()).and_then(|s| Uuid::parse_str(s).ok());
assert!(pid.is_some(), "{} 消费者需要 patient_id", event_type);
let plid = payload.get("plan_id").and_then(|v| v.as_str()).and_then(|s| Uuid::parse_str(s).ok());
assert!(plid.is_some(), "{} 消费者需要 plan_id", event_type);
}
}
/// 关怀行动通知消息分支逻辑
#[test]
fn care_action_notification_branch_logic() {
// item_completed → 温暖通知
let item_payload = build_event_payload(json!({
"action": "item_completed",
"item_title": "血压监测",
}));
let action = item_payload.get("action").and_then(|v| v.as_str()).unwrap_or("");
let (title, _body) = match action {
"item_completed" => ("关怀已送达".to_string(), "您的护理团队已完成「血压监测」".to_string()),
"outcome_measured" => ("健康数据已更新".to_string(), "数据已记录".to_string()),
_ => ("关怀已送达".to_string(), "正在关注".to_string()),
};
assert_eq!(title, "关怀已送达");
// outcome_measured → 数据更新通知
let outcome_payload = build_event_payload(json!({
"action": "outcome_measured",
"metric": "血压",
}));
let action2 = outcome_payload.get("action").and_then(|v| v.as_str()).unwrap_or("");
let (title2, _body2) = match action2 {
"item_completed" => ("关怀已送达".to_string(), "已完成".to_string()),
"outcome_measured" => ("健康数据已更新".to_string(), "血压数据已记录".to_string()),
_ => ("关怀已送达".to_string(), "正在关注".to_string()),
};
assert_eq!(title2, "健康数据已更新");
// 未知 action → 默认通知
let unknown_payload = build_event_payload(json!({ "action": "unknown" }));
let action3 = unknown_payload.get("action").and_then(|v| v.as_str()).unwrap_or("");
let (title3, _body3) = match action3 {
"item_completed" => ("关怀已送达".to_string(), "已完成".to_string()),
"outcome_measured" => ("健康数据已更新".to_string(), "已记录".to_string()),
_ => ("关怀已送达".to_string(), "正在关注".to_string()),
};
assert_eq!(title3, "关怀已送达");
}
}