refactor(health): 状态转换验证统一到 validation 模块
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

- article/dialysis/lab_report 审核流程改用 validation 函数校验状态转换
- 移除 article_service/approve/reject/unpublish 中硬编码的 if 检查
- dialysis review 和 lab report review 新增前置状态校验
- 修正 article 状态转换图:pending_review → published(直接发布,无中间 approved)
This commit is contained in:
iven
2026-04-26 14:44:01 +08:00
parent 5bb6105127
commit a19b097409
4 changed files with 15 additions and 21 deletions

View File

@@ -15,6 +15,7 @@ use crate::entity::article;
use crate::entity::article_article_tag;
use crate::entity::article_tag;
use crate::error::{HealthError, HealthResult};
use crate::service::validation;
use crate::state::HealthState;
/// 文章列表(管理端,支持状态/分类/标签/关键词筛选)
@@ -130,9 +131,7 @@ pub async fn submit_article(
let next_ver = check_version(expected_version, model.version)
.map_err(|_| HealthError::VersionMismatch)?;
if model.status != "draft" && model.status != "rejected" {
return Err(HealthError::InvalidStatusTransition(format!("无法从 {} 提交审核", model.status)));
}
validation::validate_article_status_transition(&model.status, "pending_review")?;
let mut active: article::ActiveModel = model.into();
active.status = Set("pending_review".into());
@@ -164,9 +163,7 @@ pub async fn approve_article(
let next_ver = check_version(expected_version, model.version)
.map_err(|_| HealthError::VersionMismatch)?;
if model.status != "pending_review" {
return Err(HealthError::InvalidStatusTransition(format!("无法从 {} 审核通过", model.status)));
}
validation::validate_article_status_transition(&model.status, "published")?;
let now = Utc::now();
let mut active: article::ActiveModel = model.into();
@@ -203,9 +200,7 @@ pub async fn reject_article(
let next_ver = check_version(expected_version, model.version)
.map_err(|_| HealthError::VersionMismatch)?;
if model.status != "pending_review" {
return Err(HealthError::InvalidStatusTransition(format!("无法从 {} 审核拒绝", model.status)));
}
validation::validate_article_status_transition(&model.status, "rejected")?;
let now = Utc::now();
let mut active: article::ActiveModel = model.into();
@@ -240,9 +235,7 @@ pub async fn unpublish_article(
let next_ver = check_version(expected_version, model.version)
.map_err(|_| HealthError::VersionMismatch)?;
if model.status != "published" {
return Err(HealthError::InvalidStatusTransition(format!("无法从 {} 撤回发布", model.status)));
}
validation::validate_article_status_transition(&model.status, "draft")?;
let mut active: article::ActiveModel = model.into();
active.status = Set("draft".into());

View File

@@ -15,6 +15,7 @@ use erp_core::types::PaginatedResponse;
use crate::dto::dialysis_dto::*;
use crate::entity::{dialysis_record, patient};
use crate::error::{HealthError, HealthResult};
use crate::service::validation;
use crate::state::HealthState;
pub async fn list_dialysis_records(
@@ -221,6 +222,8 @@ pub async fn review_dialysis_record(
let next_ver = check_version(expected_version, model.version)
.map_err(|_| HealthError::VersionMismatch)?;
validation::validate_dialysis_status_transition(&model.status, "reviewed")?;
let mut active: dialysis_record::ActiveModel = model.into();
active.status = Set("reviewed".to_string());
active.reviewed_by = Set(Some(reviewer_id));

View File

@@ -16,7 +16,7 @@ use erp_core::types::PaginatedResponse;
use crate::dto::health_data_dto::*;
use crate::entity::{doctor_profile, health_record, lab_report, patient, patient_doctor_relation, vital_signs};
use crate::error::{HealthError, HealthResult};
use crate::service::validation::validate_record_type;
use crate::service::validation::{validate_record_type, validate_lab_report_status_transition};
use crate::state::HealthState;
// ---------------------------------------------------------------------------
@@ -512,6 +512,8 @@ pub async fn review_lab_report(
let next_ver = check_version(expected_version, model.version)
.map_err(|_| HealthError::VersionMismatch)?;
validate_lab_report_status_transition(&model.status, "reviewed")?;
let mut active: lab_report::ActiveModel = model.into();
active.status = Set("reviewed".to_string());
active.reviewed_by = Set(Some(reviewer_id));

View File

@@ -141,8 +141,7 @@ pub fn validate_article_status(value: &str) -> HealthResult<()> {
/// article.status 状态转换
/// draft → pending_review, rejected → pending_review
/// pending_review → approved / rejected
/// approved → published
/// pending_review → published / rejected(审核通过直接发布,或拒绝)
/// published → draft (下架)
pub fn validate_article_status_transition(current: &str, new: &str) -> HealthResult<()> {
if current == new {
@@ -150,8 +149,7 @@ pub fn validate_article_status_transition(current: &str, new: &str) -> HealthRes
}
let allowed = match current {
"draft" => matches!(new, "pending_review"),
"pending_review" => matches!(new, "approved" | "rejected"),
"approved" => matches!(new, "published"),
"pending_review" => matches!(new, "published" | "rejected"),
"rejected" => matches!(new, "pending_review"),
"published" => matches!(new, "draft"),
_ => false,
@@ -369,19 +367,17 @@ mod tests {
#[test]
fn art_draft_to_published_fails() { assert!(validate_article_status_transition("draft", "published").is_err()); }
#[test]
fn art_pending_review_to_approved() { assert!(validate_article_status_transition("pending_review", "approved").is_ok()); }
fn art_pending_review_to_published() { assert!(validate_article_status_transition("pending_review", "published").is_ok()); }
#[test]
fn art_pending_review_to_rejected() { assert!(validate_article_status_transition("pending_review", "rejected").is_ok()); }
#[test]
fn art_pending_review_to_draft_fails() { assert!(validate_article_status_transition("pending_review", "draft").is_err()); }
#[test]
fn art_approved_to_published() { assert!(validate_article_status_transition("approved", "published").is_ok()); }
#[test]
fn art_rejected_to_pending_review() { assert!(validate_article_status_transition("rejected", "pending_review").is_ok()); }
#[test]
fn art_published_to_draft() { assert!(validate_article_status_transition("published", "draft").is_ok()); }
#[test]
fn art_published_to_approved_fails() { assert!(validate_article_status_transition("published", "approved").is_err()); }
fn art_published_to_pending_fails() { assert!(validate_article_status_transition("published", "pending_review").is_err()); }
#[test]
fn art_same_status_ok() { assert!(validate_article_status_transition("draft", "draft").is_ok()); }