fix: 系统性预防角色测试高频问题(5 方案落地)
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

P0 — 默认拒绝 + 强制守卫:
- 创建 routeConfig.ts 作为前端路由权限的单一真相源
- TypeScript 强制每个路由声明非空权限数组,不可能遗漏
- 自动生成 ROUTE_PERMISSIONS 和 FROZEN_ROUTES
- 修正 3 个前端权限码不匹配后端

P0 — CI 权限扫描:
- 新增 tools/check_permissions.py 校验脚本
- 发现并修复 tenant.manage 未注册问题

P1 — 聚合接口容错:
- erp-core 新增 safe_aggregate 工具函数
- 仪表盘统计 handler 重构

P1 — 状态机一致性自检:
- validation.rs 新增 3 个自检测试

fix: lint-staged eslint Windows 兼容性
This commit is contained in:
iven
2026-05-08 08:52:16 +08:00
parent 645ec39e8b
commit c82f7bda1d
11 changed files with 594 additions and 90 deletions

View File

@@ -1,5 +1,6 @@
use axum::Extension;
use axum::extract::{FromRef, Json, State};
use erp_core::aggregate::safe_aggregate;
use erp_core::error::AppError;
use erp_core::rbac::require_permission;
use erp_core::types::{ApiResponse, TenantContext};
@@ -57,42 +58,23 @@ where
{
require_permission(&ctx, "health.patient.list")?;
let patients = stats_service::get_patient_statistics(&state, ctx.tenant_id)
.await
.unwrap_or_else(|e| {
tracing::warn!("仪表盘患者统计查询失败: {e}");
PatientStatisticsResp {
total_patients: 0,
new_this_month: 0,
new_this_week: 0,
active_this_month: 0,
}
});
let patients = safe_aggregate(
stats_service::get_patient_statistics(&state, ctx.tenant_id),
"患者统计",
)
.await;
let consultations = stats_service::get_consultation_statistics(&state, ctx.tenant_id)
.await
.unwrap_or_else(|e| {
tracing::warn!("仪表盘咨询统计查询失败: {e}");
ConsultationStatisticsResp {
total_sessions: 0,
pending_reply: 0,
avg_response_time_minutes: None,
this_month: 0,
}
});
let consultations = safe_aggregate(
stats_service::get_consultation_statistics(&state, ctx.tenant_id),
"咨询统计",
)
.await;
let follow_ups = stats_service::get_follow_up_statistics(&state, ctx.tenant_id)
.await
.unwrap_or_else(|e| {
tracing::warn!("仪表盘随访统计查询失败: {e}");
FollowUpStatisticsResp {
total_tasks: 0,
completed: 0,
pending: 0,
overdue: 0,
completion_rate: 0.0,
}
});
let follow_ups = safe_aggregate(
stats_service::get_follow_up_statistics(&state, ctx.tenant_id),
"随访统计",
)
.await;
Ok(Json(ApiResponse::ok(DashboardStatsResp {
patients,