fix: 修复测试发现的 7 个问题 + 全 workspace clippy 清零
功能修复: 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:
@@ -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()))?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user