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

@@ -58,20 +58,20 @@ pub struct PluginEntity {
#[serde(default)]
pub relations: Vec<PluginRelation>,
#[serde(default)]
pub data_scope: Option<bool>, // 是否启用行级数据权限
pub data_scope: Option<bool>, // 是否启用行级数据权限
#[serde(default)]
pub is_public: Option<bool>, // 是否可被其他插件引用
pub is_public: Option<bool>, // 是否可被其他插件引用
#[serde(default)]
pub importable: Option<bool>, // 是否支持数据导入
pub importable: Option<bool>, // 是否支持数据导入
#[serde(default)]
pub exportable: Option<bool>, // 是否支持数据导出
pub exportable: Option<bool>, // 是否支持数据导出
}
/// 字段校验规则
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldValidation {
pub pattern: Option<String>, // 正则表达式
pub message: Option<String>, // 校验失败提示
pub pattern: Option<String>, // 正则表达式
pub message: Option<String>, // 校验失败提示
}
/// 插件字段定义
@@ -95,18 +95,18 @@ pub struct PluginField {
pub sortable: Option<bool>,
#[serde(default)]
pub visible_when: Option<String>,
pub ref_entity: Option<String>, // 外键引用的实体名
pub ref_label_field: Option<String>, // entity_select 下拉显示的字段名
pub ref_entity: Option<String>, // 外键引用的实体名
pub ref_label_field: Option<String>, // entity_select 下拉显示的字段名
pub ref_search_fields: Option<Vec<String>>, // entity_select 搜索匹配的字段列表
pub cascade_from: Option<String>, // 级联过滤的来源字段(当前实体)
pub cascade_filter: Option<String>, // 级联过滤的目标字段(引用实体的字段)
pub validation: Option<FieldValidation>, // 字段校验规则
pub cascade_from: Option<String>, // 级联过滤的来源字段(当前实体)
pub cascade_filter: Option<String>, // 级联过滤的目标字段(引用实体的字段)
pub validation: Option<FieldValidation>, // 字段校验规则
#[serde(default)]
pub no_cycle: Option<bool>, // 禁止循环引用
pub no_cycle: Option<bool>, // 禁止循环引用
#[serde(default)]
pub scope_role: Option<String>, // 标记为数据权限的"所有者"字段
pub ref_plugin: Option<String>, // 跨插件引用的目标插件 manifest ID如 "erp-crm"
pub ref_fallback_label: Option<String>, // 目标插件未安装时的降级显示文本
pub scope_role: Option<String>, // 标记为数据权限的"所有者"字段
pub ref_plugin: Option<String>, // 跨插件引用的目标插件 manifest ID如 "erp-crm"
pub ref_fallback_label: Option<String>, // 目标插件未安装时的降级显示文本
}
/// 字段类型
@@ -198,9 +198,9 @@ pub struct PluginIndex {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum OnDeleteStrategy {
Nullify, // 置空外键字段
Cascade, // 级联软删除
Restrict, // 存在关联时拒绝删除
Nullify, // 置空外键字段
Cascade, // 级联软删除
Restrict, // 存在关联时拒绝删除
}
/// 实体关联关系声明
@@ -210,11 +210,11 @@ pub struct PluginRelation {
pub foreign_key: String,
pub on_delete: OnDeleteStrategy,
#[serde(default)]
pub name: Option<String>, // 关联名称UI 显示用)
pub name: Option<String>, // 关联名称UI 显示用)
#[serde(default, alias = "type")]
pub relation_type: Option<String>, // "one_to_many" | "many_to_one" | "many_to_many"
pub relation_type: Option<String>, // "one_to_many" | "many_to_one" | "many_to_many"
#[serde(default)]
pub display_field: Option<String>, // 关联记录的显示字段
pub display_field: Option<String>, // 关联记录的显示字段
}
/// 事件订阅配置
@@ -311,10 +311,7 @@ pub enum PluginPageType {
#[serde(tag = "type", rename_all = "snake_case")]
pub enum PluginWidget {
#[serde(rename = "stat_cards")]
StatCards {
label: String,
cards: Vec<StatCard>,
},
StatCards { label: String, cards: Vec<StatCard> },
#[serde(rename = "action_list")]
ActionList {
label: String,
@@ -385,10 +382,7 @@ pub struct ActionQuery {
#[serde(tag = "type")]
pub enum PluginSection {
#[serde(rename = "fields")]
Fields {
label: String,
fields: Vec<String>,
},
Fields { label: String, fields: Vec<String> },
#[serde(rename = "crud")]
Crud {
label: String,
@@ -408,7 +402,7 @@ pub struct PluginPermission {
#[serde(default)]
pub description: String,
#[serde(default)]
pub data_scope_levels: Option<Vec<String>>, // 支持的数据范围等级
pub data_scope_levels: Option<Vec<String>>, // 支持的数据范围等级
}
// ============================================================
@@ -545,7 +539,9 @@ pub fn parse_manifest(toml_str: &str) -> PluginResult<PluginManifest> {
// 验证必填字段
if manifest.metadata.id.is_empty() {
return Err(PluginError::InvalidManifest("metadata.id 不能为空".to_string()));
return Err(PluginError::InvalidManifest(
"metadata.id 不能为空".to_string(),
));
}
if manifest.metadata.name.is_empty() {
return Err(PluginError::InvalidManifest(
@@ -642,7 +638,9 @@ fn validate_pages(pages: &[PluginPageType]) -> PluginResult<()> {
));
}
}
PluginPageType::Detail { entity, sections, .. } => {
PluginPageType::Detail {
entity, sections, ..
} => {
if entity.is_empty() {
return Err(PluginError::InvalidManifest(
"detail page 的 entity 不能为空".into(),
@@ -937,7 +935,10 @@ label = "空标签页"
fn field_type_to_sql_mapping() {
assert_eq!(PluginFieldType::String.generated_sql_type(), "TEXT");
assert_eq!(PluginFieldType::Integer.generated_sql_type(), "INTEGER");
assert_eq!(PluginFieldType::Float.generated_sql_type(), "DOUBLE PRECISION");
assert_eq!(
PluginFieldType::Float.generated_sql_type(),
"DOUBLE PRECISION"
);
assert_eq!(PluginFieldType::Decimal.generated_sql_type(), "NUMERIC");
assert_eq!(PluginFieldType::Boolean.generated_sql_type(), "BOOLEAN");
assert_eq!(PluginFieldType::Date.generated_sql_type(), "DATE");
@@ -948,9 +949,18 @@ label = "空标签页"
#[test]
fn field_type_generated_expression() {
assert_eq!(PluginFieldType::String.generated_expr("name"), "data->>'name'");
assert_eq!(PluginFieldType::Integer.generated_expr("age"), "(data->>'age')::INTEGER");
assert_eq!(PluginFieldType::Uuid.generated_expr("ref_id"), "(data->>'ref_id')::UUID");
assert_eq!(
PluginFieldType::String.generated_expr("name"),
"data->>'name'"
);
assert_eq!(
PluginFieldType::Integer.generated_expr("age"),
"(data->>'age')::INTEGER"
);
assert_eq!(
PluginFieldType::Uuid.generated_expr("ref_id"),
"(data->>'ref_id')::UUID"
);
}
#[test]
@@ -1064,7 +1074,10 @@ on_delete = "cascade"
assert_eq!(entity.relations.len(), 2);
assert_eq!(entity.relations[0].entity, "contact");
assert_eq!(entity.relations[0].foreign_key, "customer_id");
assert!(matches!(entity.relations[0].on_delete, OnDeleteStrategy::Cascade));
assert!(matches!(
entity.relations[0].on_delete,
OnDeleteStrategy::Cascade
));
}
#[test]
@@ -1139,9 +1152,7 @@ ref_search_fields = ["name", "code"]
assert_eq!(field.ref_label_field.as_deref(), Some("name"));
assert_eq!(
field.ref_search_fields.as_deref(),
Some(
&["name".to_string(), "code".to_string()][..]
)
Some(&["name".to_string(), "code".to_string()][..])
);
}
@@ -1406,7 +1417,10 @@ description = "发票创建后是否自动发送通知"
assert_eq!(settings.fields[0].group.as_deref(), Some("财务"));
assert_eq!(settings.fields[1].name, "invoice_prefix");
assert_eq!(settings.fields[2].name, "auto_notify");
assert!(matches!(settings.fields[2].field_type, PluginSettingType::Boolean));
assert!(matches!(
settings.fields[2].field_type,
PluginSettingType::Boolean
));
}
#[test]
@@ -1436,7 +1450,10 @@ seq_length = 4
assert_eq!(numbering[0].entity, "invoice");
assert_eq!(numbering[0].field, "invoice_no");
assert_eq!(numbering[0].prefix, "INV");
assert!(matches!(numbering[0].reset_rule, PluginNumberingReset::Yearly));
assert!(matches!(
numbering[0].reset_rule,
PluginNumberingReset::Yearly
));
}
#[test]
@@ -1716,7 +1733,9 @@ tags = ["status"]
assert_eq!(ui.pages.len(), 1);
match &ui.pages[0] {
PluginPageType::Dashboard {
label, icon, widgets,
label,
icon,
widgets,
} => {
assert_eq!(label, "工作台");
assert_eq!(icon.as_deref(), Some("DashboardOutlined"));
@@ -1738,7 +1757,9 @@ tags = ["status"]
// action_list
match &widgets[1] {
PluginWidget::ActionList {
label, max_items, queries,
label,
max_items,
queries,
} => {
assert_eq!(label, "紧急待办");
assert_eq!(*max_items, Some(5));
@@ -1752,7 +1773,11 @@ tags = ["status"]
// funnel
match &widgets[2] {
PluginWidget::Funnel {
label, entity, lane_field, value_field, lane_order,
label,
entity,
lane_field,
value_field,
lane_order,
} => {
assert_eq!(label, "商机漏斗");
assert_eq!(entity, "invoice");
@@ -1766,7 +1791,10 @@ tags = ["status"]
// card_list
match &widgets[3] {
PluginWidget::CardList {
label, entity, title_field, ..
label,
entity,
title_field,
..
} => {
assert_eq!(label, "活跃项目");
assert_eq!(entity, "invoice");