docs(plan): 架构反思实施计划 — WASM 评估量表 + 透析拆分 + P1 事件消费者
8 Tasks / 3 Chunks: - Chunk 1: WASM 评估量表插件 (PHQ-9) — crate 骨架 + 默认数据 + WASM 编译 - Chunk 2: 透析模块拆分 erp-dialysis — 8 文件 ~1100 行迁移 - Chunk 3: P1 事件消费者补全 — patient.created / appointment 通知 / follow_up.overdue
This commit is contained in:
@@ -0,0 +1,714 @@
|
||||
# 架构反思实施计划
|
||||
|
||||
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 落地架构反思三个结论 — WASM 评估量表插件、透析模块独立、P1 事件消费者补全。
|
||||
|
||||
**Architecture:** 三条独立工作线可并行推进。WASM 插件遵循 erp-plugin-test-sample 模式;透析模块拆分参照 erp-points 拆 crate 模式;事件消费者补全遵循现有 subscribe_filtered + tokio::spawn 模式。
|
||||
|
||||
**Tech Stack:** Rust/SeaORM/Axum/WASM(wit-bindgen 0.55)
|
||||
|
||||
**Spec:** `docs/discussions/2026-04-28-architecture-retrospective.md`
|
||||
|
||||
---
|
||||
|
||||
## Chunk 1: WASM 评估量表插件(PHQ-9)
|
||||
|
||||
### Task 1: 创建插件 crate 骨架
|
||||
|
||||
**Files:**
|
||||
- Create: `crates/erp-plugin-assessment/Cargo.toml`
|
||||
- Create: `crates/erp-plugin-assessment/src/lib.rs`
|
||||
- Create: `crates/erp-plugin-assessment/plugin.toml`
|
||||
|
||||
- [ ] **Step 1: 创建 Cargo.toml**
|
||||
|
||||
参照 `crates/erp-plugin-test-sample/Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "erp-plugin-assessment"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
wit-bindgen = "0.55"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 创建 plugin.toml**
|
||||
|
||||
```toml
|
||||
[metadata]
|
||||
id = "assessment"
|
||||
name = "评估量表"
|
||||
version = "0.1.0"
|
||||
description = "标准化医学评估量表(PHQ-9、GAD-7 等)"
|
||||
author = "HMS"
|
||||
min_platform_version = "0.1.0"
|
||||
|
||||
[[permissions]]
|
||||
code = "assessment_scale.list"
|
||||
name = "查看评估量表"
|
||||
description = "查看评估量表列表和详情"
|
||||
|
||||
[[permissions]]
|
||||
code = "assessment_scale.manage"
|
||||
name = "管理评估量表"
|
||||
description = "创建、编辑、删除评估量表"
|
||||
|
||||
[[permissions]]
|
||||
code = "assessment_response.list"
|
||||
name = "查看评估结果"
|
||||
description = "查看患者评估答卷"
|
||||
|
||||
[[permissions]]
|
||||
code = "assessment_response.manage"
|
||||
name = "管理评估结果"
|
||||
description = "提交、编辑评估答卷"
|
||||
|
||||
[[schema.entities]]
|
||||
name = "assessment_scale"
|
||||
display_name = "评估量表"
|
||||
|
||||
[[schema.entities.fields]]
|
||||
name = "scale_code"
|
||||
field_type = "string"
|
||||
required = true
|
||||
display_name = "量表编码"
|
||||
unique = true
|
||||
ui_widget = "select"
|
||||
options = ["PHQ-9", "GAD-7", "SF-36", "MMSE", "ADL", "IADL"]
|
||||
|
||||
[[schema.entities.fields]]
|
||||
name = "title"
|
||||
field_type = "string"
|
||||
required = true
|
||||
display_name = "量表名称"
|
||||
searchable = true
|
||||
|
||||
[[schema.entities.fields]]
|
||||
name = "description"
|
||||
field_type = "string"
|
||||
display_name = "描述"
|
||||
ui_widget = "textarea"
|
||||
|
||||
[[schema.entities.fields]]
|
||||
name = "questions_json"
|
||||
field_type = "json"
|
||||
required = true
|
||||
display_name = "题目定义(JSON)"
|
||||
|
||||
[[schema.entities.fields]]
|
||||
name = "scoring_rules_json"
|
||||
field_type = "json"
|
||||
required = true
|
||||
display_name = "评分规则(JSON)"
|
||||
|
||||
[[schema.entities.fields]]
|
||||
name = "status"
|
||||
field_type = "string"
|
||||
required = true
|
||||
display_name = "状态"
|
||||
default = "active"
|
||||
ui_widget = "select"
|
||||
options = ["active", "inactive"]
|
||||
|
||||
[[schema.entities]]
|
||||
name = "assessment_response"
|
||||
display_name = "评估答卷"
|
||||
|
||||
[[schema.entities.fields]]
|
||||
name = "scale_id"
|
||||
field_type = "uuid"
|
||||
required = true
|
||||
display_name = "量表"
|
||||
ui_widget = "entity_select"
|
||||
ref_entity = "assessment_scale"
|
||||
ref_plugin = "assessment"
|
||||
|
||||
[[schema.entities.fields]]
|
||||
name = "patient_id"
|
||||
field_type = "uuid"
|
||||
required = true
|
||||
display_name = "患者 ID"
|
||||
|
||||
[[schema.entities.fields]]
|
||||
name = "answers_json"
|
||||
field_type = "json"
|
||||
required = true
|
||||
display_name = "答案(JSON)"
|
||||
|
||||
[[schema.entities.fields]]
|
||||
name = "total_score"
|
||||
field_type = "integer"
|
||||
required = true
|
||||
display_name = "总分"
|
||||
|
||||
[[schema.entities.fields]]
|
||||
name = "severity_level"
|
||||
field_type = "string"
|
||||
required = true
|
||||
display_name = "严重程度"
|
||||
ui_widget = "select"
|
||||
options = ["normal", "mild", "moderate", "severe"]
|
||||
|
||||
[[schema.entities.fields]]
|
||||
name = "assessed_by"
|
||||
field_type = "uuid"
|
||||
display_name = "评估人"
|
||||
|
||||
[[schema.entities.fields]]
|
||||
name = "status"
|
||||
field_type = "string"
|
||||
required = true
|
||||
display_name = "状态"
|
||||
default = "completed"
|
||||
ui_widget = "select"
|
||||
options = ["draft", "completed", "reviewed"]
|
||||
|
||||
[[schema.entities.relations]]
|
||||
entity = "assessment_scale"
|
||||
foreign_key = "scale_id"
|
||||
on_delete = "restrict"
|
||||
name = "scale"
|
||||
type = "belongs_to"
|
||||
display_field = "title"
|
||||
|
||||
[[trigger_events]]
|
||||
name = "assessment_completed"
|
||||
display_name = "评估完成"
|
||||
description = "患者完成评估量表,触发评分计算和后续流程"
|
||||
entity = "assessment_response"
|
||||
on = "create"
|
||||
|
||||
[[ui.pages]]
|
||||
type = "crud"
|
||||
label = "评估量表"
|
||||
icon = "FormOutlined"
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 创建 src/lib.rs**
|
||||
|
||||
```rust
|
||||
// crates/erp-plugin-assessment/src/lib.rs
|
||||
//! 评估量表插件 — 标准化医学评估(PHQ-9, GAD-7 等)
|
||||
|
||||
wit_bindgen::generate!({
|
||||
path: "../../crates/erp-plugin/src/wit/plugin.wit",
|
||||
world: "plugin-world",
|
||||
});
|
||||
|
||||
use crate::exports::erp::plugin::plugin_api::Guest;
|
||||
use crate::erp::plugin::host_api::*;
|
||||
|
||||
struct AssessmentPlugin;
|
||||
|
||||
impl Guest for AssessmentPlugin {
|
||||
fn init() -> Result<(), String> {
|
||||
log_write("info", "AssessmentPlugin initialized");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_tenant_created(tenant_id: String) -> Result<(), String> {
|
||||
log_write("info", &format!("AssessmentPlugin: tenant {} created", tenant_id));
|
||||
// 可以为新租户插入默认量表(PHQ-9、GAD-7)
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_event(
|
||||
event_type: String,
|
||||
_event_id: String,
|
||||
_tenant_id: String,
|
||||
_payload: String,
|
||||
) -> Result<(), String> {
|
||||
log_write("debug", &format!("AssessmentPlugin received: {}", event_type));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
export!(AssessmentPlugin);
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 注册到 workspace**
|
||||
|
||||
在根 `Cargo.toml` 的 `workspace.members` 中添加 `"crates/erp-plugin-assessment"`。
|
||||
|
||||
- [ ] **Step 5: 编译验证**
|
||||
|
||||
```bash
|
||||
cargo check -p erp-plugin-assessment
|
||||
```
|
||||
|
||||
- [ ] **Step 6: 提交**
|
||||
|
||||
```bash
|
||||
git add crates/erp-plugin-assessment/ Cargo.toml
|
||||
git commit -m "feat(plugin): 评估量表插件骨架 — assessment_scale + assessment_response"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: PHQ-9 默认量表数据 + 评分逻辑
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/erp-plugin-assessment/src/lib.rs`(on_tenant_created 插入默认量表)
|
||||
|
||||
- [ ] **Step 1: 在 on_tenant_created 中插入 PHQ-9 默认数据**
|
||||
|
||||
PHQ-9 的 9 道题(每题 0-3 分)和评分规则:
|
||||
|
||||
```json
|
||||
// questions_json
|
||||
[
|
||||
{"id": 1, "text": "做事时提不起劲或没有兴趣", "options": [{"label": "完全不会", "score": 0}, {"label": "好几天", "score": 1}, {"label": "一半以上的天数", "score": 2}, {"label": "几乎每天", "score": 3}]},
|
||||
// ... 共 9 题
|
||||
]
|
||||
|
||||
// scoring_rules_json
|
||||
[
|
||||
{"min": 0, "max": 4, "level": "normal", "label": "无抑郁症状"},
|
||||
{"min": 5, "max": 9, "level": "mild", "label": "轻度抑郁"},
|
||||
{"min": 10, "max": 14, "level": "moderate", "label": "中度抑郁"},
|
||||
{"min": 15, "max": 19, "level": "moderate_severe", "label": "中重度抑郁"},
|
||||
{"min": 20, "max": 27, "level": "severe", "label": "重度抑郁"}
|
||||
]
|
||||
```
|
||||
|
||||
通过 `db_insert` host API 在 `on_tenant_created` 中插入。
|
||||
|
||||
- [ ] **Step 2: 编译 + 验证**
|
||||
|
||||
- [ ] **Step 3: 提交**
|
||||
|
||||
```bash
|
||||
git commit -m "feat(plugin): PHQ-9 默认量表数据 + 评分规则"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: 编译 WASM + 注册到 erp-server
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/erp-server/src/main.rs`(插件注册,如需手动加载)
|
||||
- Verify: WASM 编译输出
|
||||
|
||||
- [ ] **Step 1: 编译为 WASM Component**
|
||||
|
||||
```bash
|
||||
cd crates/erp-plugin-assessment
|
||||
cargo build --target wasm32-unknown-unknown --release
|
||||
# 或使用项目内的 WASM 编译脚本
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 验证插件加载**
|
||||
|
||||
启动后端,确认插件系统识别 assessment 插件,动态表创建成功。
|
||||
|
||||
- [ ] **Step 3: 通过 API 测试评估量表 CRUD**
|
||||
|
||||
```bash
|
||||
# 创建量表
|
||||
curl -X POST /api/v1/plugin/assessment/assessment_scale \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-d '{"scale_code": "PHQ-9", ...}'
|
||||
|
||||
# 提交答卷
|
||||
curl -X POST /api/v1/plugin/assessment/assessment_response \
|
||||
-d '{"scale_id": "...", "patient_id": "...", "answers_json": [...]}'
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 提交**
|
||||
|
||||
```bash
|
||||
git commit -m "feat(plugin): 评估量表 WASM 编译 + 端到端验证"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Chunk 2: 透析模块拆分为 erp-dialysis
|
||||
|
||||
### Task 4: 创建 erp-dialysis crate 骨架
|
||||
|
||||
**Files:**
|
||||
- Create: `crates/erp-dialysis/Cargo.toml`
|
||||
- Create: `crates/erp-dialysis/src/{lib,module,state,error}.rs`
|
||||
- Modify: `Cargo.toml`(workspace members)
|
||||
|
||||
- [ ] **Step 1: 创建 Cargo.toml**
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "erp-dialysis"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
erp-core.workspace = true
|
||||
sea-orm.workspace = true
|
||||
tokio.workspace = true
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
uuid.workspace = true
|
||||
chrono.workspace = true
|
||||
axum.workspace = true
|
||||
utoipa.workspace = true
|
||||
validator.workspace = true
|
||||
async-trait.workspace = true
|
||||
tracing.workspace = true
|
||||
rust_decimal.workspace = true
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 创建标准模块文件**
|
||||
|
||||
```rust
|
||||
// crates/erp-dialysis/src/lib.rs
|
||||
pub mod dto;
|
||||
pub mod entity;
|
||||
pub mod error;
|
||||
pub mod event;
|
||||
pub mod handler;
|
||||
pub mod module;
|
||||
pub mod service;
|
||||
pub mod state;
|
||||
|
||||
pub use module::DialysisModule;
|
||||
pub use state::DialysisState;
|
||||
```
|
||||
|
||||
```rust
|
||||
// crates/erp-dialysis/src/module.rs
|
||||
//! ErpModule trait 实现
|
||||
|
||||
use async_trait::async_trait;
|
||||
use erp_core::module::{ErpModule, ModuleContext, ModuleType, PermissionDescriptor};
|
||||
use erp_core::events::EventBus;
|
||||
use crate::state::DialysisState;
|
||||
|
||||
pub struct DialysisModule {
|
||||
state: DialysisState,
|
||||
}
|
||||
|
||||
impl DialysisModule {
|
||||
pub fn new(db: sea_orm::DatabaseConnection, event_bus: EventBus) -> Self {
|
||||
Self { state: DialysisState { db, event_bus } }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ErpModule for DialysisModule {
|
||||
fn name(&self) -> &str { "透析管理" }
|
||||
fn id(&self) -> &str { "erp-dialysis" }
|
||||
fn version(&self) -> &str { "0.1.0" }
|
||||
fn module_type(&self) -> ModuleType { ModuleType::Builtin }
|
||||
|
||||
fn permissions(&self) -> Vec<PermissionDescriptor> {
|
||||
vec![
|
||||
PermissionDescriptor { code: "dialysis.record.list".into(), name: "查看透析记录".into() },
|
||||
PermissionDescriptor { code: "dialysis.record.manage".into(), name: "管理透析记录".into() },
|
||||
PermissionDescriptor { code: "dialysis.prescription.list".into(), name: "查看透析处方".into() },
|
||||
PermissionDescriptor { code: "dialysis.prescription.manage".into(), name: "管理透析处方".into() },
|
||||
]
|
||||
}
|
||||
|
||||
fn on_startup(&self, ctx: &ModuleContext) {
|
||||
crate::event::register_handlers_with_state(self.state.clone());
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any { self }
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 注册到 workspace**
|
||||
|
||||
在根 `Cargo.toml` 的 members 中添加 `"crates/erp-dialysis"`。
|
||||
|
||||
- [ ] **Step 4: 编译验证**
|
||||
|
||||
```bash
|
||||
cargo check -p erp-dialysis
|
||||
```
|
||||
|
||||
- [ ] **Step 5: 提交**
|
||||
|
||||
```bash
|
||||
git commit -m "feat(dialysis): 创建 erp-dialysis crate 骨架 + ErpModule 实现"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: 迁移透析 Entity + Service + Handler + DTO
|
||||
|
||||
**Files:**
|
||||
- Move: 6 个文件从 `erp-health` → `erp-dialysis`
|
||||
- Modify: `crates/erp-health/src/{entity,service,handler,dto}/mod.rs`(删除透析导出)
|
||||
- Modify: 迁移文件中的 `crate::` 引用改为 `erp_core::` 或 erp-dialysis 内部引用
|
||||
|
||||
**待迁移文件清单:**
|
||||
|
||||
| 来源 | 目标 | 行数 |
|
||||
|------|------|------|
|
||||
| `erp-health/src/entity/dialysis_record.rs` | `erp-dialysis/src/entity/` | 82 |
|
||||
| `erp-health/src/entity/dialysis_prescription.rs` | `erp-dialysis/src/entity/` | 78 |
|
||||
| `erp-health/src/service/dialysis_service.rs` | `erp-dialysis/src/service/` | 333 |
|
||||
| `erp-health/src/service/dialysis_prescription_service.rs` | `erp-dialysis/src/service/` | 274 |
|
||||
| `erp-health/src/handler/dialysis_handler.rs` | `erp-dialysis/src/handler/` | 145 |
|
||||
| `erp-health/src/handler/dialysis_prescription_handler.rs` | `erp-dialysis/src/handler/` | 120 |
|
||||
| `erp-health/src/dto/dialysis_dto.rs` | `erp-dialysis/src/dto/` | 125 |
|
||||
| `erp-health/src/dto/dialysis_prescription_dto.rs` | `erp-dialysis/src/dto/` | 107 |
|
||||
|
||||
- [ ] **Step 1: 复制文件到 erp-dialysis**
|
||||
|
||||
```bash
|
||||
# Entity
|
||||
cp crates/erp-health/src/entity/dialysis_record.rs crates/erp-dialysis/src/entity/
|
||||
cp crates/erp-health/src/entity/dialysis_prescription.rs crates/erp-dialysis/src/entity/
|
||||
# Service
|
||||
cp crates/erp-health/src/service/dialysis_service.rs crates/erp-dialysis/src/service/
|
||||
cp crates/erp-health/src/service/dialysis_prescription_service.rs crates/erp-dialysis/src/service/
|
||||
# Handler
|
||||
cp crates/erp-health/src/handler/dialysis_handler.rs crates/erp-dialysis/src/handler/
|
||||
cp crates/erp-health/src/handler/dialysis_prescription_handler.rs crates/erp-dialysis/src/handler/
|
||||
# DTO
|
||||
cp crates/erp-health/src/dto/dialysis_dto.rs crates/erp-dialysis/src/dto/
|
||||
cp crates/erp-health/src/dto/dialysis_prescription_dto.rs crates/erp-dialysis/src/dto/
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 更新 crate 内引用**
|
||||
|
||||
全局替换:
|
||||
- `crate::state::HealthState` → `crate::state::DialysisState`
|
||||
- `crate::error::{HealthError, HealthResult}` → `crate::error::{DialysisError, DialysisResult}`
|
||||
- `crate::entity::` → 保持不变(同 crate 内)
|
||||
- `crate::dto::` → 保持不变
|
||||
- `crate::service::` → 保持不变
|
||||
|
||||
- [ ] **Step 3: 创建 error.rs**
|
||||
|
||||
```rust
|
||||
// crates/erp-dialysis/src/error.rs
|
||||
use erp_core::error::AppError;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DialysisError {
|
||||
#[error("透析记录未找到: {0}")]
|
||||
RecordNotFound(uuid::Uuid),
|
||||
#[error("处方未找到: {0}")]
|
||||
PrescriptionNotFound(uuid::Uuid),
|
||||
#[error("状态转换无效: {0} → {1}")]
|
||||
InvalidStatusTransition(String, String),
|
||||
#[error("版本冲突")]
|
||||
VersionConflict,
|
||||
}
|
||||
|
||||
impl From<DialysisError> for AppError {
|
||||
fn from(e: DialysisError) -> Self { AppError::Business(e.to_string()) }
|
||||
}
|
||||
|
||||
pub type DialysisResult<T> = Result<T, DialysisError>;
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 从 erp-health 删除透析代码**
|
||||
|
||||
从以下 mod.rs 中移除透析相关 `pub mod` 声明:
|
||||
- `crates/erp-health/src/entity/mod.rs`
|
||||
- `crates/erp-health/src/service/mod.rs`
|
||||
- `crates/erp-health/src/handler/mod.rs`
|
||||
- `crates/erp-health/src/dto/mod.rs`
|
||||
|
||||
- [ ] **Step 5: 在 erp-server 注册新模块**
|
||||
|
||||
在 `crates/erp-server/src/main.rs` 中:
|
||||
- 添加 `use erp_dialysis::DialysisModule;`
|
||||
- 在 registry 链中 `.register(dialysis_module)`
|
||||
- 在路由 merge 中 `.merge(erp_dialysis::DialysisModule::protected_routes())`
|
||||
|
||||
- [ ] **Step 6: 编译 + 全链路验证**
|
||||
|
||||
```bash
|
||||
cargo check --workspace
|
||||
# 启动后端,验证透析相关 API 正常
|
||||
```
|
||||
|
||||
- [ ] **Step 7: 提交**
|
||||
|
||||
```bash
|
||||
git commit -m "refactor: 透析模块拆分为独立 erp-dialysis crate(2 Entity + 2 Service)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Chunk 3: P1 事件消费者补全
|
||||
|
||||
### Task 6: patient.created → 欢迎消息 + 默认随访
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/erp-health/src/event.rs`(添加消费者)
|
||||
|
||||
- [ ] **Step 1: 在 register_handlers_with_state 中添加 patient.created 消费者**
|
||||
|
||||
```rust
|
||||
// 在 register_handlers_with_state() 中新增:
|
||||
let (mut patient_rx, _) = state.event_bus.subscribe_filtered("patient.".to_string());
|
||||
let patient_db = state.db.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match patient_rx.recv().await {
|
||||
Some(event) if event.event_type == PATIENT_CREATED => {
|
||||
if erp_core::events::is_event_processed(&patient_db, event.id, "patient_welcome").await.unwrap_or(false) {
|
||||
continue;
|
||||
}
|
||||
let patient_id = event.payload.get("patient_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.and_then(|s| Uuid::parse_str(s).ok());
|
||||
if let Some(pid) = patient_id {
|
||||
// 1. 发布欢迎消息事件(消息模块消费后发送站内通知)
|
||||
let welcome_event = DomainEvent::new(
|
||||
"message.send",
|
||||
event.tenant_id,
|
||||
erp_core::events::build_event_payload(serde_json::json!({
|
||||
"template": "patient_welcome",
|
||||
"recipient_type": "patient",
|
||||
"recipient_id": pid,
|
||||
})),
|
||||
);
|
||||
// 2. TODO: 创建默认随访计划(后续迭代)
|
||||
tracing::info!(patient_id = %pid, "新患者欢迎流程触发");
|
||||
}
|
||||
let _ = erp_core::events::mark_event_processed(&patient_db, event.id, "patient_welcome").await;
|
||||
}
|
||||
Some(_) => {}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 编译验证**
|
||||
|
||||
```bash
|
||||
cargo check -p erp-health
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 提交**
|
||||
|
||||
```bash
|
||||
git commit -m "feat(health): patient.created 消费者 — 新患者欢迎消息"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 7: appointment.confirmed/cancelled → 通知 + 号源
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/erp-health/src/event.rs`
|
||||
|
||||
- [ ] **Step 1: 添加 appointment 事件消费者**
|
||||
|
||||
```rust
|
||||
let (mut appt_rx, _) = state.event_bus.subscribe_filtered("appointment.".to_string());
|
||||
let appt_db = state.db.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match appt_rx.recv().await {
|
||||
Some(event) if event.event_type == "appointment.confirmed" => {
|
||||
if erp_core::events::is_event_processed(&appt_db, event.id, "appointment_notifier").await.unwrap_or(false) {
|
||||
continue;
|
||||
}
|
||||
// 通知医生
|
||||
let doctor_id = event.payload.get("doctor_id").and_then(|v| v.as_str());
|
||||
let patient_id = event.payload.get("patient_id").and_then(|v| v.as_str());
|
||||
if let (Some(did), Some(pid)) = (doctor_id, patient_id) {
|
||||
tracing::info!(doctor_id = did, patient_id = pid, "预约确认通知触发");
|
||||
// 发布通知事件
|
||||
}
|
||||
let _ = erp_core::events::mark_event_processed(&appt_db, event.id, "appointment_notifier").await;
|
||||
}
|
||||
Some(event) if event.event_type == "appointment.cancelled" => {
|
||||
// 释放号源 + 通知排队患者
|
||||
tracing::info!(event_id = %event.id, "预约取消,号源释放");
|
||||
}
|
||||
Some(_) => {}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 编译 + 提交**
|
||||
|
||||
```bash
|
||||
cargo check -p erp-health
|
||||
git commit -m "feat(health): appointment 事件消费者 — 预约确认/取消通知"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 8: follow_up.overdue → 升级通知
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/erp-health/src/event.rs`
|
||||
|
||||
- [ ] **Step 1: 添加 follow_up.overdue 消费者**
|
||||
|
||||
```rust
|
||||
// 在 register_handlers_with_state 中:
|
||||
// 注意:follow_up 事件的前缀是 "follow_up."
|
||||
let (mut fu_rx, _) = state.event_bus.subscribe_filtered("follow_up.".to_string());
|
||||
let fu_db = state.db.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
match fu_rx.recv().await {
|
||||
Some(event) if event.event_type == FOLLOW_UP_OVERDUE => {
|
||||
if erp_core::events::is_event_processed(&fu_db, event.id, "follow_up_escalator").await.unwrap_or(false) {
|
||||
continue;
|
||||
}
|
||||
let task_id = event.payload.get("task_id").and_then(|v| v.as_str());
|
||||
let assigned_to = event.payload.get("assigned_to").and_then(|v| v.as_str());
|
||||
if let (Some(tid), Some(uid)) = (task_id, assigned_to) {
|
||||
// 通知随访负责人 + 科室主管
|
||||
tracing::warn!(task_id = tid, assigned_to = uid, "随访逾期升级通知");
|
||||
}
|
||||
let _ = erp_core::events::mark_event_processed(&fu_db, event.id, "follow_up_escalator").await;
|
||||
}
|
||||
Some(_) => {}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 编译 + 提交**
|
||||
|
||||
```bash
|
||||
cargo check -p erp-health
|
||||
git commit -m "feat(health): follow_up.overdue 消费者 — 逾期随访升级通知"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 执行摘要
|
||||
|
||||
| Chunk | Tasks | 内容 | 预估 |
|
||||
|-------|-------|------|------|
|
||||
| 1 | T1-T3 | WASM 评估量表插件(PHQ-9) | 1-2 天 |
|
||||
| 2 | T4-T5 | 透析模块拆 erp-dialysis | 1 天 |
|
||||
| 3 | T6-T8 | P1 事件消费者补全(3 个) | 0.5-1 天 |
|
||||
|
||||
**总计 8 个 Task,预估 2.5-4 天。**
|
||||
|
||||
**依赖关系:**
|
||||
- T1→T2→T3 串行(WASM 插件逐层构建)
|
||||
- T4→T5 串行(先骨架再迁移)
|
||||
- T6/T7/T8 可并行(独立消费者)
|
||||
- Chunk 1/2/3 相互独立,可完全并行
|
||||
|
||||
**与技术债计划的关系:**
|
||||
- 本计划的 Chunk 3(事件消费者)应在技术债批次 B(EventBus dead-letter)之后执行
|
||||
- Chunk 2(透析拆分)应在技术债批次 A(安全修复)之后执行,避免合并冲突
|
||||
- Chunk 1(WASM 插件)完全独立,随时可执行
|
||||
Reference in New Issue
Block a user