fix: 修复测试发现的 7 个问题 + 全 workspace clippy 清零
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

功能修复:
1. 患者创建空名称验证:后端添加 name.trim().is_empty() 检查
2. 仪表盘统计容错:单个查询失败返回零值而非 500
3. FHIR 路由修复:从 /fhir 移到 /api/v1/fhir 保持一致
4. 冻结模块后端中间件:新增 frozen_module_middleware 拦截冻结路径
5. 积分端点权限码:health.health-data.list → health.points.list
6. 角色权限迁移:护士补充 devices.list,运营补充 points.list/manage
7. 测试结果文档:R01-R05 角色测试 + T00/T10 结果归档

Clippy 全 workspace 清零(14→0 errors):
- erp-core: 修复 empty doc line、collapsible if、redundant closure 等 9 处
- erp-health: 修复 too_many_arguments、unused var、unnecessary parens 等 58 处
- erp-ai: 修复 dead_code、unused import 等 11 处
- erp-plugin: 修复 too_many_arguments、wildcard pattern 等 11 处
- erp-server-migration: 修复 enum_variant_names 5 处
- erp-auth/config/workflow/message: 各 1-3 处

工程改进:
- lint-staged 配置迁移到 .lintstagedrc.js(函数式避免文件列表传给 clippy)
- cargo fmt 统一格式化
This commit is contained in:
iven
2026-05-07 23:43:14 +08:00
parent 786f57c151
commit 6d5a711d2c
323 changed files with 15662 additions and 6603 deletions

View File

@@ -432,7 +432,10 @@ impl FlowExecutor {
active.status = Set("completed".to_string());
active.version = Set(ver + 1);
active.updated_at = Set(chrono::Utc::now());
active.update(txn).await.map_err(|e| WorkflowError::Validation(e.to_string()))?;
active
.update(txn)
.await
.map_err(|e| WorkflowError::Validation(e.to_string()))?;
}
}

View File

@@ -389,17 +389,16 @@ mod tests {
// find_logical_op 先找 || → split at ||
// left = "amount > 1000 && score > 80", right = "amount > 3000"
// left = true, right = false → true || false = true
assert!(ExpressionEvaluator::eval(
"amount > 1000 && score > 80 || amount > 3000", &vars
).unwrap());
assert!(
ExpressionEvaluator::eval("amount > 1000 && score > 80 || amount > 3000", &vars)
.unwrap()
);
}
#[test]
fn test_compound_all_false() {
let vars = make_vars();
assert!(!ExpressionEvaluator::eval(
"amount > 2000 && score > 90", &vars
).unwrap());
assert!(!ExpressionEvaluator::eval("amount > 2000 && score > 90", &vars).unwrap());
}
#[test]

View File

@@ -306,9 +306,7 @@ mod tests {
#[test]
fn test_edge_references_nonexistent_source() {
let nodes = vec![make_start(), make_end()];
let edges = vec![
make_edge("e1", "ghost", "end"),
];
let edges = vec![make_edge("e1", "ghost", "end")];
let result = parse_and_validate(&nodes, &edges);
assert!(result.is_err());
let msg = result.unwrap_err().to_string();
@@ -318,9 +316,7 @@ mod tests {
#[test]
fn test_edge_references_nonexistent_target() {
let nodes = vec![make_start(), make_end()];
let edges = vec![
make_edge("e1", "start", "ghost"),
];
let edges = vec![make_edge("e1", "start", "ghost")];
let result = parse_and_validate(&nodes, &edges);
assert!(result.is_err());
let msg = result.unwrap_err().to_string();
@@ -367,10 +363,7 @@ mod tests {
},
make_end(),
];
let edges = vec![
make_edge("e1", "start", "gw"),
make_edge("e2", "gw", "end"),
];
let edges = vec![make_edge("e1", "start", "gw"), make_edge("e2", "gw", "end")];
assert!(parse_and_validate(&nodes, &edges).is_ok());
}

View File

@@ -121,7 +121,7 @@ mod tests {
let err = WorkflowError::VersionMismatch;
let app: AppError = err.into();
match app {
AppError::VersionMismatch => {},
AppError::VersionMismatch => {}
other => panic!("期望 AppError::VersionMismatch得到 {:?}", other),
}
}

View File

@@ -192,14 +192,9 @@ where
{
require_permission(&ctx, "workflow.publish")?;
let resp = DefinitionService::deprecate(
id,
ctx.tenant_id,
ctx.user_id,
&state.db,
&state.event_bus,
)
.await?;
let resp =
DefinitionService::deprecate(id, ctx.tenant_id, ctx.user_id, &state.db, &state.event_bus)
.await?;
Ok(Json(ApiResponse::ok(resp)))
}

View File

@@ -83,10 +83,7 @@ impl WorkflowModule {
"/workflow/tasks/{id}/delegate",
post(task_handler::delegate_task),
)
.route(
"/workflow/tasks/{id}/claim",
put(task_handler::claim_task),
)
.route("/workflow/tasks/{id}/claim", put(task_handler::claim_task))
}
/// 启动超时检查后台任务。
@@ -103,7 +100,11 @@ impl WorkflowModule {
loop {
interval.tick().await;
match crate::engine::timeout::TimeoutChecker::find_all_overdue_tasks_with_details(&db).await {
match crate::engine::timeout::TimeoutChecker::find_all_overdue_tasks_with_details(
&db,
)
.await
{
Ok(overdue) => {
if !overdue.is_empty() {
tracing::warn!(
@@ -177,7 +178,9 @@ async fn handle_ai_action_start(
};
// 构造启动变量
let risk_level = event.payload.get("risk_level")
let risk_level = event
.payload
.get("risk_level")
.and_then(|v| v.as_str())
.unwrap_or("medium")
.to_string();
@@ -191,14 +194,18 @@ async fn handle_ai_action_start(
crate::dto::SetVariableReq {
name: "patient_id".into(),
var_type: Some("string".into()),
value: event.payload.get("patient_id")
value: event
.payload
.get("patient_id")
.cloned()
.unwrap_or(serde_json::Value::Null),
},
crate::dto::SetVariableReq {
name: "action_type".into(),
var_type: Some("string".into()),
value: event.payload.get("action_type")
value: event
.payload
.get("action_type")
.and_then(|v| v.as_str())
.map(|s| serde_json::Value::String(s.to_string()))
.unwrap_or(serde_json::Value::Null),
@@ -206,7 +213,9 @@ async fn handle_ai_action_start(
crate::dto::SetVariableReq {
name: "params".into(),
var_type: Some("string".into()),
value: event.payload.get("params")
value: event
.payload
.get("params")
.cloned()
.unwrap_or(serde_json::Value::Null),
},
@@ -214,18 +223,17 @@ async fn handle_ai_action_start(
let req = crate::dto::StartInstanceReq {
definition_id: def.id,
business_key: Some(format!("ai_action_{}", chrono::Utc::now().timestamp_millis())),
business_key: Some(format!(
"ai_action_{}",
chrono::Utc::now().timestamp_millis()
)),
variables: Some(variables),
};
let system_id = Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap();
match crate::service::instance_service::InstanceService::start(
tenant_id,
system_id,
&req,
db,
event_bus,
tenant_id, system_id, &req, db, event_bus,
)
.await
{
@@ -310,8 +318,10 @@ impl ErpModule for WorkflowModule {
);
// 查找该用户有活跃任务的流程实例
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
use chrono::Utc;
use sea_orm::{
ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set,
};
// 查找该用户作为 assignee 的 pending 任务
let active_tasks = crate::entity::task::Entity::find()
@@ -336,34 +346,36 @@ impl ErpModule for WorkflowModule {
for instance_id in &instance_ids {
// 将实例状态设置为 terminated
let instance = crate::entity::process_instance::Entity::find_by_id(*instance_id)
let instance =
crate::entity::process_instance::Entity::find_by_id(
*instance_id,
)
.one(&db)
.await;
if let Ok(Some(inst)) = instance {
if inst.tenant_id == event.tenant_id
&& inst.deleted_at.is_none()
&& inst.status == "running"
{
let ver = inst.version;
let mut active: crate::entity::process_instance::ActiveModel = inst.into();
active.status = Set("terminated".to_string());
active.updated_at = Set(Utc::now());
active.version = Set(ver + 1);
match active.update(&db).await {
Ok(_) => {
tracing::info!(
instance_id = %instance_id,
"流程实例已终止(用户被删除)"
);
}
Err(e) => {
tracing::warn!(
instance_id = %instance_id,
error = %e,
"终止流程实例失败"
);
}
if let Ok(Some(inst)) = instance
&& inst.tenant_id == event.tenant_id
&& inst.deleted_at.is_none()
&& inst.status == "running"
{
let ver = inst.version;
let mut active: crate::entity::process_instance::ActiveModel = inst.into();
active.status = Set("terminated".to_string());
active.updated_at = Set(Utc::now());
active.version = Set(ver + 1);
match active.update(&db).await {
Ok(_) => {
tracing::info!(
instance_id = %instance_id,
"流程实例已终止(用户被删除)"
);
}
Err(e) => {
tracing::warn!(
instance_id = %instance_id,
error = %e,
"终止流程实例失败"
);
}
}
}
@@ -399,7 +411,10 @@ impl ErpModule for WorkflowModule {
}
});
tracing::info!(module = "workflow", "Workflow 事件处理器已注册(监听 user.deleted");
tracing::info!(
module = "workflow",
"Workflow 事件处理器已注册(监听 user.deleted"
);
// 订阅 AI 行动工作流启动请求
let (mut ai_rx, _ai_handle) = bus.subscribe_filtered("workflow.ai_action.".to_string());
@@ -477,14 +492,54 @@ impl ErpModule for WorkflowModule {
fn permissions(&self) -> Vec<PermissionDescriptor> {
vec![
PermissionDescriptor { code: "workflow.create".into(), name: "创建流程".into(), description: "创建流程定义".into(), module: "workflow".into() },
PermissionDescriptor { code: "workflow.list".into(), name: "查看流程".into(), description: "查看流程列表".into(), module: "workflow".into() },
PermissionDescriptor { code: "workflow.read".into(), name: "查看流程详情".into(), description: "查看流程定义详情".into(), module: "workflow".into() },
PermissionDescriptor { code: "workflow.update".into(), name: "编辑流程".into(), description: "编辑流程定义".into(), module: "workflow".into() },
PermissionDescriptor { code: "workflow.publish".into(), name: "发布流程".into(), description: "发布流程定义".into(), module: "workflow".into() },
PermissionDescriptor { code: "workflow.start".into(), name: "发起流程".into(), description: "发起流程实例".into(), module: "workflow".into() },
PermissionDescriptor { code: "workflow.approve".into(), name: "审批任务".into(), description: "审批流程任务".into(), module: "workflow".into() },
PermissionDescriptor { code: "workflow.delegate".into(), name: "委派任务".into(), description: "委派流程任务".into(), module: "workflow".into() },
PermissionDescriptor {
code: "workflow.create".into(),
name: "创建流程".into(),
description: "创建流程定义".into(),
module: "workflow".into(),
},
PermissionDescriptor {
code: "workflow.list".into(),
name: "查看流程".into(),
description: "查看流程列表".into(),
module: "workflow".into(),
},
PermissionDescriptor {
code: "workflow.read".into(),
name: "查看流程详情".into(),
description: "查看流程定义详情".into(),
module: "workflow".into(),
},
PermissionDescriptor {
code: "workflow.update".into(),
name: "编辑流程".into(),
description: "编辑流程定义".into(),
module: "workflow".into(),
},
PermissionDescriptor {
code: "workflow.publish".into(),
name: "发布流程".into(),
description: "发布流程定义".into(),
module: "workflow".into(),
},
PermissionDescriptor {
code: "workflow.start".into(),
name: "发起流程".into(),
description: "发起流程实例".into(),
module: "workflow".into(),
},
PermissionDescriptor {
code: "workflow.approve".into(),
name: "审批任务".into(),
description: "审批流程任务".into(),
module: "workflow".into(),
},
PermissionDescriptor {
code: "workflow.delegate".into(),
name: "委派任务".into(),
description: "委派流程任务".into(),
module: "workflow".into(),
},
]
}

View File

@@ -31,7 +31,8 @@ fn followup_nodes() -> Vec<serde_json::Value> {
{"id": "gw_outcome", "type": "ExclusiveGateway", "name": "审批结果"},
{"id": "end_approved", "type": "EndEvent", "name": "已批准"},
{"id": "end_rejected", "type": "EndEvent", "name": "已拒绝"}
])).unwrap()
]))
.unwrap()
}
fn followup_edges() -> Vec<serde_json::Value> {
@@ -46,7 +47,8 @@ fn followup_edges() -> Vec<serde_json::Value> {
"condition": "outcome == \"approved\"", "label": "批准"},
{"id": "e6", "source": "gw_outcome", "target": "end_rejected",
"condition": "outcome == \"rejected\"", "label": "拒绝"}
])).unwrap()
]))
.unwrap()
}
/// AI 预约审批流程
@@ -60,7 +62,8 @@ fn appointment_nodes() -> Vec<serde_json::Value> {
{"id": "gw_outcome", "type": "ExclusiveGateway", "name": "确认结果"},
{"id": "end_approved", "type": "EndEvent", "name": "已确认"},
{"id": "end_rejected", "type": "EndEvent", "name": "已拒绝"}
])).unwrap()
]))
.unwrap()
}
fn appointment_edges() -> Vec<serde_json::Value> {
@@ -75,7 +78,8 @@ fn appointment_edges() -> Vec<serde_json::Value> {
"condition": "outcome == \"approved\"", "label": "确认"},
{"id": "e6", "source": "gw_outcome", "target": "end_rejected",
"condition": "outcome == \"rejected\"", "label": "拒绝"}
])).unwrap()
]))
.unwrap()
}
/// AI 预警确认流程
@@ -89,7 +93,8 @@ fn alert_nodes() -> Vec<serde_json::Value> {
{"id": "gw_outcome", "type": "ExclusiveGateway", "name": "确认结果"},
{"id": "end_acknowledged", "type": "EndEvent", "name": "已确认"},
{"id": "end_escalated", "type": "EndEvent", "name": "已升级"}
])).unwrap()
]))
.unwrap()
}
fn alert_edges() -> Vec<serde_json::Value> {
@@ -104,7 +109,8 @@ fn alert_edges() -> Vec<serde_json::Value> {
"condition": "outcome == \"approved\"", "label": "确认"},
{"id": "e6", "source": "gw_outcome", "target": "end_escalated",
"condition": "outcome == \"rejected\"", "label": "升级"}
])).unwrap()
]))
.unwrap()
}
struct WorkflowTemplate {

View File

@@ -173,12 +173,23 @@ impl DefinitionService {
}
// 当 nodes 或 edges 任一存在时,取最终值验证流程图完整性
let _final_nodes = req.nodes.as_ref().or_else(|| {
serde_json::from_value::<Vec<crate::dto::NodeDef>>(active.nodes.as_ref().clone()).ok().as_ref().map(|_| unreachable!())
serde_json::from_value::<Vec<crate::dto::NodeDef>>(active.nodes.as_ref().clone())
.ok()
.as_ref()
.map(|_| unreachable!())
});
// 简化:如果提供了 nodes 或 edges将两者合并后验证
if req.nodes.is_some() || req.edges.is_some() {
let nodes_val = req.nodes.as_ref().map(|n| serde_json::to_value(n).unwrap_or_default()).unwrap_or(active.nodes.as_ref().clone());
let edges_val = req.edges.as_ref().map(|e| serde_json::to_value(e).unwrap_or_default()).unwrap_or(active.edges.as_ref().clone());
let nodes_val = req
.nodes
.as_ref()
.map(|n| serde_json::to_value(n).unwrap_or_default())
.unwrap_or(active.nodes.as_ref().clone());
let edges_val = req
.edges
.as_ref()
.map(|e| serde_json::to_value(e).unwrap_or_default())
.unwrap_or(active.edges.as_ref().clone());
let nodes: Vec<crate::dto::NodeDef> = serde_json::from_value(nodes_val)
.map_err(|e| WorkflowError::Validation(format!("节点数据无效: {e}")))?;
let edges: Vec<crate::dto::EdgeDef> = serde_json::from_value(edges_val)

View File

@@ -322,9 +322,9 @@ impl InstanceService {
id: Set(event_id),
tenant_id: Set(tenant_id),
event_type: Set(event_type),
payload: Set(Some(
erp_core::events::build_event_payload(serde_json::json!({ "instance_id": id, "changed_by": operator_id })),
)),
payload: Set(Some(erp_core::events::build_event_payload(
serde_json::json!({ "instance_id": id, "changed_by": operator_id }),
))),
correlation_id: Set(Some(Uuid::now_v7())),
status: Set("pending".to_string()),
attempts: Set(0),