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:
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user