**根目录清理:** - 删除 CLAUDE-1.md(ZCLAW 旧项目配置,HMS 已完全脱离) - 移动 DESIGN.md → docs/archive/(ERP 旧设计系统) - 删除 plans/ 98 个临时会话计划文件 **归档重组:** - V1 审计(12 文件)→ docs/archive/audits-v1/ - 早期 CRM/插件迭代设计(13 文件)→ docs/archive/superpowers-early/ - 已完成/已取代设计(28 文件)→ docs/archive/superpowers-completed/ - 早期讨论/测试报告 → docs/archive/discussions-early/ + test-reports-early/ - QA 重复文件清理(3 个旧版 result 文件) **wiki 数据校正:** - 迁移数 137→145,源文件 599→649,提交数 720→800+ - 小程序文件 124→163,Web 前端 297→332 - 后端测试 999→943(实际统计),权限码 75+→128 - 文档索引新增归档目录说明 **CLAUDE.md 规则优化:** - §2.5 闭环工作法:提交+文档+推送三合一 + wiki 更新触发条件 - §2.6 Feature DoD:新增文档一致性检查项 - §6 反模式:新增 wiki 更新滞后/推送不及时警告
715 lines
20 KiB
Markdown
715 lines
20 KiB
Markdown
# 架构反思实施计划
|
||
|
||
> **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 插件)完全独立,随时可执行
|