feat(auth): 用户管理页面过滤纯患者用户 + fix(health): clippy 修复
后端 list_users API 新增 exclude_only_roles 参数,排除仅有指定角色的用户。 前端用户管理页面默认传 'patient' 过滤,wx_* 微信患者不再混入员工列表。 同时修复 erp-health dashboard.rs 的 clippy 警告(unused import + collapsible if)。
This commit is contained in:
@@ -21,6 +21,9 @@ pub struct UserListParams {
|
||||
pub page_size: Option<u64>,
|
||||
/// Optional search term — filters by username (case-insensitive contains).
|
||||
pub search: Option<String>,
|
||||
/// Exclude users whose *only* role is one of these comma-separated role codes.
|
||||
/// Example: `exclude_only_roles=patient` hides users that have no role other than "patient".
|
||||
pub exclude_only_roles: Option<String>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -54,10 +57,17 @@ where
|
||||
page: params.page,
|
||||
page_size: params.page_size,
|
||||
};
|
||||
let exclude_only_roles: Option<Vec<String>> = params
|
||||
.exclude_only_roles
|
||||
.as_deref()
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.split(',').map(|c| c.trim().to_string()).collect());
|
||||
|
||||
let (users, total) = UserService::list(
|
||||
ctx.tenant_id,
|
||||
&pagination,
|
||||
params.search.as_deref(),
|
||||
exclude_only_roles.as_deref(),
|
||||
&state.db,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -144,10 +144,15 @@ impl UserService {
|
||||
///
|
||||
/// Returns `(users, total_count)`. When `search` is provided, filters
|
||||
/// by username using case-insensitive substring match.
|
||||
///
|
||||
/// When `exclude_only_roles` is provided, users whose *only* role is one
|
||||
/// of the listed role codes are excluded (e.g. `["patient"]` hides
|
||||
/// patient-only users from the staff management page).
|
||||
pub async fn list(
|
||||
tenant_id: Uuid,
|
||||
pagination: &Pagination,
|
||||
search: Option<&str>,
|
||||
exclude_only_roles: Option<&[String]>,
|
||||
db: &sea_orm::DatabaseConnection,
|
||||
) -> AuthResult<(Vec<UserResp>, u64)> {
|
||||
let mut query = user::Entity::find()
|
||||
@@ -161,6 +166,56 @@ impl UserService {
|
||||
query = query.filter(Expr::col(user::Column::Username).like(format!("%{}%", term)));
|
||||
}
|
||||
|
||||
// Exclude users whose only role is one of the excluded role codes.
|
||||
// Two-step approach: first find user_ids that have ONLY excluded roles
|
||||
// via raw SQL, then exclude them from the main query.
|
||||
if let Some(roles) = exclude_only_roles
|
||||
&& !roles.is_empty()
|
||||
{
|
||||
use sea_orm::{ConnectionTrait, Statement};
|
||||
|
||||
let codes: Vec<String> = roles
|
||||
.iter()
|
||||
.map(|r| format!("'{}'", r.replace('\'', "''")))
|
||||
.collect();
|
||||
let codes_csv = codes.join(",");
|
||||
|
||||
// Find user_ids whose ONLY roles are in the excluded list.
|
||||
// A user qualifies if:
|
||||
// - they have at least one role in the excluded list
|
||||
// - they have ZERO roles outside the excluded list
|
||||
let excluded: Vec<Uuid> = db
|
||||
.query_all(Statement::from_sql_and_values(
|
||||
sea_orm::DatabaseBackend::Postgres,
|
||||
format!(
|
||||
r#"SELECT u.id FROM users u
|
||||
WHERE u.tenant_id = $1 AND u.deleted_at IS NULL
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM user_roles ur
|
||||
JOIN roles r ON r.id = ur.role_id AND r.deleted_at IS NULL
|
||||
WHERE ur.user_id = u.id AND ur.tenant_id = $1
|
||||
AND r.code IN ({codes_csv})
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM user_roles ur
|
||||
JOIN roles r ON r.id = ur.role_id AND r.deleted_at IS NULL
|
||||
WHERE ur.user_id = u.id AND ur.tenant_id = $1
|
||||
AND r.code NOT IN ({codes_csv})
|
||||
)"#
|
||||
),
|
||||
[tenant_id.into()],
|
||||
))
|
||||
.await
|
||||
.map_err(|e| AuthError::DbError(e.to_string()))?
|
||||
.iter()
|
||||
.filter_map(|row| row.try_get("", "id").ok())
|
||||
.collect();
|
||||
|
||||
if !excluded.is_empty() {
|
||||
query = query.filter(user::Column::Id.is_not_in(excluded));
|
||||
}
|
||||
}
|
||||
|
||||
let paginator = query.paginate(db, pagination.limit());
|
||||
|
||||
let total = paginator
|
||||
|
||||
Reference in New Issue
Block a user