新增护理计划(Care Plan)完整 CRUD:3 张表(care_plans / care_plan_items / care_plan_outcomes)、3 个 SeaORM Entity、15 个 API 端点、4 个事件常量、 2 个权限码。支持透析/慢性/预防/康复计划类型,条目分干预/监测/目标/教育四类, 预后测量含基线/目标/当前值追踪。
648 lines
19 KiB
Rust
648 lines
19 KiB
Rust
use chrono::Utc;
|
|
use sea_orm::entity::prelude::*;
|
|
use sea_orm::{ActiveValue::Set, QueryOrder, QuerySelect};
|
|
use uuid::Uuid;
|
|
|
|
use erp_core::audit::AuditLog;
|
|
use erp_core::audit_service;
|
|
use erp_core::error::check_version;
|
|
use erp_core::events::DomainEvent;
|
|
use erp_core::types::PaginatedResponse;
|
|
|
|
use crate::dto::care_plan_dto::*;
|
|
use crate::entity::{care_plan, care_plan_item, care_plan_outcome, patient};
|
|
use crate::error::{HealthError, HealthResult};
|
|
use crate::state::HealthState;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CarePlan CRUD
|
|
// ---------------------------------------------------------------------------
|
|
|
|
pub async fn list_care_plans(
|
|
state: &HealthState,
|
|
tenant_id: Uuid,
|
|
params: &ListCarePlansParams,
|
|
) -> HealthResult<PaginatedResponse<CarePlanResp>> {
|
|
let page = params.page.unwrap_or(1);
|
|
let page_size = params.page_size.unwrap_or(20);
|
|
let limit = page_size.min(100);
|
|
let offset = page.saturating_sub(1) * limit;
|
|
|
|
let mut query = care_plan::Entity::find()
|
|
.filter(care_plan::Column::TenantId.eq(tenant_id))
|
|
.filter(care_plan::Column::DeletedAt.is_null());
|
|
|
|
if let Some(pid) = params.patient_id {
|
|
query = query.filter(care_plan::Column::PatientId.eq(pid));
|
|
}
|
|
if let Some(ref pt) = params.plan_type {
|
|
query = query.filter(care_plan::Column::PlanType.eq(pt.as_str()));
|
|
}
|
|
if let Some(ref st) = params.status {
|
|
query = query.filter(care_plan::Column::Status.eq(st.as_str()));
|
|
}
|
|
|
|
let total: u64 = query.clone().count(&state.db).await?;
|
|
let rows: Vec<care_plan::Model> = query
|
|
.order_by_desc(care_plan::Column::CreatedAt)
|
|
.limit(limit)
|
|
.offset(offset)
|
|
.all(&state.db)
|
|
.await?;
|
|
|
|
let total_pages = total.div_ceil(limit.max(1));
|
|
let data = rows.into_iter().map(plan_to_resp).collect();
|
|
|
|
Ok(PaginatedResponse {
|
|
data,
|
|
total,
|
|
page,
|
|
page_size,
|
|
total_pages,
|
|
})
|
|
}
|
|
|
|
pub async fn get_care_plan(
|
|
state: &HealthState,
|
|
tenant_id: Uuid,
|
|
plan_id: Uuid,
|
|
) -> HealthResult<CarePlanResp> {
|
|
let m = find_plan(state, tenant_id, plan_id).await?;
|
|
Ok(plan_to_resp(m))
|
|
}
|
|
|
|
pub async fn create_care_plan(
|
|
state: &HealthState,
|
|
tenant_id: Uuid,
|
|
operator_id: Option<Uuid>,
|
|
req: CreateCarePlanReq,
|
|
) -> HealthResult<CarePlanResp> {
|
|
patient::Entity::find()
|
|
.filter(patient::Column::Id.eq(req.patient_id))
|
|
.filter(patient::Column::TenantId.eq(tenant_id))
|
|
.filter(patient::Column::DeletedAt.is_null())
|
|
.one(&state.db)
|
|
.await?
|
|
.ok_or(HealthError::PatientNotFound)?;
|
|
|
|
validate_plan_type(&req.plan_type)?;
|
|
|
|
let now = Utc::now();
|
|
let active = care_plan::ActiveModel {
|
|
id: Set(Uuid::now_v7()),
|
|
tenant_id: Set(tenant_id),
|
|
patient_id: Set(req.patient_id),
|
|
plan_type: Set(req.plan_type),
|
|
status: Set("draft".to_string()),
|
|
title: Set(req.title),
|
|
goals: Set(req.goals.unwrap_or(serde_json::json!([]))),
|
|
start_date: Set(req.start_date),
|
|
end_date: Set(req.end_date),
|
|
notes: Set(req.notes),
|
|
created_at: Set(now),
|
|
updated_at: Set(now),
|
|
created_by: Set(operator_id),
|
|
updated_by: Set(operator_id),
|
|
deleted_at: Set(None),
|
|
version: Set(1),
|
|
};
|
|
let m = active.insert(&state.db).await?;
|
|
|
|
audit_service::record(
|
|
AuditLog::new(tenant_id, operator_id, "care_plan.created", "care_plan")
|
|
.with_resource_id(m.id),
|
|
&state.db,
|
|
)
|
|
.await;
|
|
|
|
state
|
|
.event_bus
|
|
.publish(
|
|
DomainEvent::new(
|
|
crate::event::CARE_PLAN_CREATED,
|
|
tenant_id,
|
|
erp_core::events::build_event_payload(serde_json::json!({
|
|
"plan_id": m.id, "patient_id": m.patient_id,
|
|
"plan_type": m.plan_type, "title": m.title,
|
|
})),
|
|
),
|
|
&state.db,
|
|
)
|
|
.await;
|
|
|
|
Ok(plan_to_resp(m))
|
|
}
|
|
|
|
pub async fn update_care_plan(
|
|
state: &HealthState,
|
|
tenant_id: Uuid,
|
|
plan_id: Uuid,
|
|
operator_id: Option<Uuid>,
|
|
req: UpdateCarePlanWithVersion,
|
|
) -> HealthResult<CarePlanResp> {
|
|
let existing = find_plan(state, tenant_id, plan_id).await?;
|
|
let _old_status = existing.status.clone(); // 用于后续事件类型判断
|
|
let next_ver = check_version(req.version, existing.version)
|
|
.map_err(|_| HealthError::VersionMismatch)?;
|
|
|
|
let old_status = existing.status.clone();
|
|
let mut active: care_plan::ActiveModel = existing.into();
|
|
let now = Utc::now();
|
|
|
|
if let Some(v) = req.data.plan_type {
|
|
validate_plan_type(&v)?;
|
|
active.plan_type = Set(v);
|
|
}
|
|
if let Some(v) = req.data.title {
|
|
active.title = Set(v);
|
|
}
|
|
if let Some(v) = req.data.status {
|
|
validate_plan_status(&v)?;
|
|
active.status = Set(v);
|
|
}
|
|
if let Some(v) = req.data.goals {
|
|
active.goals = Set(v);
|
|
}
|
|
if req.data.start_date.is_some() {
|
|
active.start_date = Set(req.data.start_date);
|
|
}
|
|
if req.data.end_date.is_some() {
|
|
active.end_date = Set(req.data.end_date);
|
|
}
|
|
if req.data.notes.is_some() {
|
|
active.notes = Set(req.data.notes);
|
|
}
|
|
active.updated_at = Set(now);
|
|
active.updated_by = Set(operator_id);
|
|
active.version = Set(next_ver);
|
|
|
|
let m = active.update(&state.db).await?;
|
|
|
|
audit_service::record(
|
|
AuditLog::new(tenant_id, operator_id, "care_plan.updated", "care_plan")
|
|
.with_resource_id(m.id),
|
|
&state.db,
|
|
)
|
|
.await;
|
|
|
|
let event_type = match m.status.as_str() {
|
|
"active" => crate::event::CARE_PLAN_ACTIVATED,
|
|
"completed" => crate::event::CARE_PLAN_COMPLETED,
|
|
_ => crate::event::CARE_PLAN_UPDATED,
|
|
};
|
|
state
|
|
.event_bus
|
|
.publish(
|
|
DomainEvent::new(
|
|
event_type,
|
|
tenant_id,
|
|
erp_core::events::build_event_payload(serde_json::json!({
|
|
"plan_id": m.id, "patient_id": m.patient_id,
|
|
"status": m.status,
|
|
})),
|
|
),
|
|
&state.db,
|
|
)
|
|
.await;
|
|
|
|
Ok(plan_to_resp(m))
|
|
}
|
|
|
|
pub async fn delete_care_plan(
|
|
state: &HealthState,
|
|
tenant_id: Uuid,
|
|
plan_id: Uuid,
|
|
operator_id: Option<Uuid>,
|
|
version: i32,
|
|
) -> HealthResult<()> {
|
|
let existing = find_plan(state, tenant_id, plan_id).await?;
|
|
let next_ver = check_version(version, existing.version)
|
|
.map_err(|_| HealthError::VersionMismatch)?;
|
|
|
|
let now = Utc::now();
|
|
let mut active: care_plan::ActiveModel = existing.into();
|
|
active.deleted_at = Set(Some(now));
|
|
active.updated_at = Set(now);
|
|
active.updated_by = Set(operator_id);
|
|
active.version = Set(next_ver);
|
|
active.update(&state.db).await?;
|
|
|
|
audit_service::record(
|
|
AuditLog::new(tenant_id, operator_id, "care_plan.deleted", "care_plan")
|
|
.with_resource_id(plan_id),
|
|
&state.db,
|
|
)
|
|
.await;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CarePlanItem CRUD
|
|
// ---------------------------------------------------------------------------
|
|
|
|
pub async fn list_care_plan_items(
|
|
state: &HealthState,
|
|
tenant_id: Uuid,
|
|
plan_id: Uuid,
|
|
page: u64,
|
|
page_size: u64,
|
|
) -> HealthResult<PaginatedResponse<CarePlanItemResp>> {
|
|
let _plan = find_plan(state, tenant_id, plan_id).await?;
|
|
let limit = page_size.min(100);
|
|
let offset = page.saturating_sub(1) * limit;
|
|
|
|
let query = care_plan_item::Entity::find()
|
|
.filter(care_plan_item::Column::TenantId.eq(tenant_id))
|
|
.filter(care_plan_item::Column::PlanId.eq(plan_id))
|
|
.filter(care_plan_item::Column::DeletedAt.is_null());
|
|
|
|
let total: u64 = query.clone().count(&state.db).await?;
|
|
let rows: Vec<care_plan_item::Model> = query
|
|
.order_by_asc(care_plan_item::Column::SortOrder)
|
|
.order_by_desc(care_plan_item::Column::CreatedAt)
|
|
.limit(limit)
|
|
.offset(offset)
|
|
.all(&state.db)
|
|
.await?;
|
|
|
|
let total_pages = total.div_ceil(limit.max(1));
|
|
let data = rows.into_iter().map(item_to_resp).collect();
|
|
|
|
Ok(PaginatedResponse {
|
|
data,
|
|
total,
|
|
page,
|
|
page_size,
|
|
total_pages,
|
|
})
|
|
}
|
|
|
|
pub async fn create_care_plan_item(
|
|
state: &HealthState,
|
|
tenant_id: Uuid,
|
|
plan_id: Uuid,
|
|
operator_id: Option<Uuid>,
|
|
req: CreateCarePlanItemReq,
|
|
) -> HealthResult<CarePlanItemResp> {
|
|
let _plan = find_plan(state, tenant_id, plan_id).await?;
|
|
validate_item_type(&req.item_type)?;
|
|
|
|
let now = Utc::now();
|
|
let active = care_plan_item::ActiveModel {
|
|
id: Set(Uuid::now_v7()),
|
|
tenant_id: Set(tenant_id),
|
|
plan_id: Set(plan_id),
|
|
item_type: Set(req.item_type),
|
|
title: Set(req.title),
|
|
description: Set(req.description),
|
|
status: Set("pending".to_string()),
|
|
schedule: Set(req.schedule),
|
|
sort_order: Set(req.sort_order),
|
|
created_at: Set(now),
|
|
updated_at: Set(now),
|
|
created_by: Set(operator_id),
|
|
updated_by: Set(operator_id),
|
|
deleted_at: Set(None),
|
|
version: Set(1),
|
|
};
|
|
let m = active.insert(&state.db).await?;
|
|
|
|
Ok(item_to_resp(m))
|
|
}
|
|
|
|
pub async fn update_care_plan_item(
|
|
state: &HealthState,
|
|
tenant_id: Uuid,
|
|
plan_id: Uuid,
|
|
item_id: Uuid,
|
|
operator_id: Option<Uuid>,
|
|
req: UpdateCarePlanItemWithVersion,
|
|
) -> HealthResult<CarePlanItemResp> {
|
|
let _plan = find_plan(state, tenant_id, plan_id).await?;
|
|
let existing = care_plan_item::Entity::find_by_id(item_id)
|
|
.one(&state.db)
|
|
.await?
|
|
.ok_or(HealthError::CarePlanItemNotFound)?;
|
|
|
|
if existing.tenant_id != tenant_id || existing.plan_id != plan_id {
|
|
return Err(HealthError::CarePlanItemNotFound);
|
|
}
|
|
|
|
let next_ver = check_version(req.version, existing.version)
|
|
.map_err(|_| HealthError::VersionMismatch)?;
|
|
|
|
let mut active: care_plan_item::ActiveModel = existing.into();
|
|
let now = Utc::now();
|
|
|
|
if let Some(v) = req.data.item_type {
|
|
validate_item_type(&v)?;
|
|
active.item_type = Set(v);
|
|
}
|
|
if let Some(v) = req.data.title {
|
|
active.title = Set(v);
|
|
}
|
|
if let Some(v) = req.data.status {
|
|
active.status = Set(v);
|
|
}
|
|
if req.data.description.is_some() {
|
|
active.description = Set(req.data.description);
|
|
}
|
|
if req.data.schedule.is_some() {
|
|
active.schedule = Set(req.data.schedule);
|
|
}
|
|
if req.data.sort_order.is_some() {
|
|
active.sort_order = Set(req.data.sort_order);
|
|
}
|
|
active.updated_at = Set(now);
|
|
active.updated_by = Set(operator_id);
|
|
active.version = Set(next_ver);
|
|
|
|
let m = active.update(&state.db).await?;
|
|
Ok(item_to_resp(m))
|
|
}
|
|
|
|
pub async fn delete_care_plan_item(
|
|
state: &HealthState,
|
|
tenant_id: Uuid,
|
|
plan_id: Uuid,
|
|
item_id: Uuid,
|
|
operator_id: Option<Uuid>,
|
|
version: i32,
|
|
) -> HealthResult<()> {
|
|
let _plan = find_plan(state, tenant_id, plan_id).await?;
|
|
let existing = care_plan_item::Entity::find_by_id(item_id)
|
|
.one(&state.db)
|
|
.await?
|
|
.ok_or(HealthError::CarePlanItemNotFound)?;
|
|
|
|
if existing.tenant_id != tenant_id || existing.plan_id != plan_id {
|
|
return Err(HealthError::CarePlanItemNotFound);
|
|
}
|
|
|
|
let next_ver = check_version(version, existing.version)
|
|
.map_err(|_| HealthError::VersionMismatch)?;
|
|
|
|
let now = Utc::now();
|
|
let mut active: care_plan_item::ActiveModel = existing.into();
|
|
active.deleted_at = Set(Some(now));
|
|
active.updated_at = Set(now);
|
|
active.updated_by = Set(operator_id);
|
|
active.version = Set(next_ver);
|
|
active.update(&state.db).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CarePlanOutcome CRUD
|
|
// ---------------------------------------------------------------------------
|
|
|
|
pub async fn list_care_plan_outcomes(
|
|
state: &HealthState,
|
|
tenant_id: Uuid,
|
|
plan_id: Uuid,
|
|
page: u64,
|
|
page_size: u64,
|
|
) -> HealthResult<PaginatedResponse<CarePlanOutcomeResp>> {
|
|
let _plan = find_plan(state, tenant_id, plan_id).await?;
|
|
let limit = page_size.min(100);
|
|
let offset = page.saturating_sub(1) * limit;
|
|
|
|
let query = care_plan_outcome::Entity::find()
|
|
.filter(care_plan_outcome::Column::TenantId.eq(tenant_id))
|
|
.filter(care_plan_outcome::Column::PlanId.eq(plan_id))
|
|
.filter(care_plan_outcome::Column::DeletedAt.is_null());
|
|
|
|
let total: u64 = query.clone().count(&state.db).await?;
|
|
let rows: Vec<care_plan_outcome::Model> = query
|
|
.order_by_desc(care_plan_outcome::Column::CreatedAt)
|
|
.limit(limit)
|
|
.offset(offset)
|
|
.all(&state.db)
|
|
.await?;
|
|
|
|
let total_pages = total.div_ceil(limit.max(1));
|
|
let data = rows.into_iter().map(outcome_to_resp).collect();
|
|
|
|
Ok(PaginatedResponse {
|
|
data,
|
|
total,
|
|
page,
|
|
page_size,
|
|
total_pages,
|
|
})
|
|
}
|
|
|
|
pub async fn create_care_plan_outcome(
|
|
state: &HealthState,
|
|
tenant_id: Uuid,
|
|
plan_id: Uuid,
|
|
operator_id: Option<Uuid>,
|
|
req: CreateCarePlanOutcomeReq,
|
|
) -> HealthResult<CarePlanOutcomeResp> {
|
|
let _plan = find_plan(state, tenant_id, plan_id).await?;
|
|
|
|
let now = Utc::now();
|
|
let active = care_plan_outcome::ActiveModel {
|
|
id: Set(Uuid::now_v7()),
|
|
tenant_id: Set(tenant_id),
|
|
plan_id: Set(plan_id),
|
|
item_id: Set(req.item_id),
|
|
metric: Set(req.metric),
|
|
baseline_value: Set(req.baseline_value),
|
|
target_value: Set(req.target_value),
|
|
current_value: Set(req.current_value),
|
|
measured_at: Set(req.measured_at),
|
|
notes: Set(req.notes),
|
|
created_at: Set(now),
|
|
updated_at: Set(now),
|
|
created_by: Set(operator_id),
|
|
updated_by: Set(operator_id),
|
|
deleted_at: Set(None),
|
|
version: Set(1),
|
|
};
|
|
let m = active.insert(&state.db).await?;
|
|
|
|
Ok(outcome_to_resp(m))
|
|
}
|
|
|
|
pub async fn update_care_plan_outcome(
|
|
state: &HealthState,
|
|
tenant_id: Uuid,
|
|
plan_id: Uuid,
|
|
outcome_id: Uuid,
|
|
operator_id: Option<Uuid>,
|
|
req: UpdateCarePlanOutcomeWithVersion,
|
|
) -> HealthResult<CarePlanOutcomeResp> {
|
|
let _plan = find_plan(state, tenant_id, plan_id).await?;
|
|
let existing = care_plan_outcome::Entity::find_by_id(outcome_id)
|
|
.one(&state.db)
|
|
.await?
|
|
.ok_or(HealthError::CarePlanOutcomeNotFound)?;
|
|
|
|
if existing.tenant_id != tenant_id || existing.plan_id != plan_id {
|
|
return Err(HealthError::CarePlanOutcomeNotFound);
|
|
}
|
|
|
|
let next_ver = check_version(req.version, existing.version)
|
|
.map_err(|_| HealthError::VersionMismatch)?;
|
|
|
|
let mut active: care_plan_outcome::ActiveModel = existing.into();
|
|
let now = Utc::now();
|
|
|
|
if req.data.current_value.is_some() {
|
|
active.current_value = Set(req.data.current_value);
|
|
active.measured_at = Set(Some(now));
|
|
}
|
|
if let Some(v) = req.data.target_value {
|
|
active.target_value = Set(v);
|
|
}
|
|
if req.data.measured_at.is_some() {
|
|
active.measured_at = Set(req.data.measured_at);
|
|
}
|
|
if req.data.notes.is_some() {
|
|
active.notes = Set(req.data.notes);
|
|
}
|
|
active.updated_at = Set(now);
|
|
active.updated_by = Set(operator_id);
|
|
active.version = Set(next_ver);
|
|
|
|
let m = active.update(&state.db).await?;
|
|
Ok(outcome_to_resp(m))
|
|
}
|
|
|
|
pub async fn delete_care_plan_outcome(
|
|
state: &HealthState,
|
|
tenant_id: Uuid,
|
|
plan_id: Uuid,
|
|
outcome_id: Uuid,
|
|
operator_id: Option<Uuid>,
|
|
version: i32,
|
|
) -> HealthResult<()> {
|
|
let _plan = find_plan(state, tenant_id, plan_id).await?;
|
|
let existing = care_plan_outcome::Entity::find_by_id(outcome_id)
|
|
.one(&state.db)
|
|
.await?
|
|
.ok_or(HealthError::CarePlanOutcomeNotFound)?;
|
|
|
|
if existing.tenant_id != tenant_id || existing.plan_id != plan_id {
|
|
return Err(HealthError::CarePlanOutcomeNotFound);
|
|
}
|
|
|
|
let next_ver = check_version(version, existing.version)
|
|
.map_err(|_| HealthError::VersionMismatch)?;
|
|
|
|
let now = Utc::now();
|
|
let mut active: care_plan_outcome::ActiveModel = existing.into();
|
|
active.deleted_at = Set(Some(now));
|
|
active.updated_at = Set(now);
|
|
active.updated_by = Set(operator_id);
|
|
active.version = Set(next_ver);
|
|
active.update(&state.db).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
async fn find_plan(
|
|
state: &HealthState,
|
|
tenant_id: Uuid,
|
|
plan_id: Uuid,
|
|
) -> HealthResult<care_plan::Model> {
|
|
care_plan::Entity::find_by_id(plan_id)
|
|
.one(&state.db)
|
|
.await?
|
|
.filter(|m| m.tenant_id == tenant_id && m.deleted_at.is_none())
|
|
.ok_or(HealthError::CarePlanNotFound)
|
|
}
|
|
|
|
fn plan_to_resp(m: care_plan::Model) -> CarePlanResp {
|
|
CarePlanResp {
|
|
id: m.id,
|
|
patient_id: m.patient_id,
|
|
plan_type: m.plan_type,
|
|
status: m.status,
|
|
title: m.title,
|
|
goals: m.goals,
|
|
start_date: m.start_date,
|
|
end_date: m.end_date,
|
|
notes: m.notes,
|
|
created_at: m.created_at,
|
|
updated_at: m.updated_at,
|
|
version: m.version,
|
|
}
|
|
}
|
|
|
|
fn item_to_resp(m: care_plan_item::Model) -> CarePlanItemResp {
|
|
CarePlanItemResp {
|
|
id: m.id,
|
|
plan_id: m.plan_id,
|
|
item_type: m.item_type,
|
|
title: m.title,
|
|
description: m.description,
|
|
status: m.status,
|
|
schedule: m.schedule,
|
|
sort_order: m.sort_order,
|
|
created_at: m.created_at,
|
|
updated_at: m.updated_at,
|
|
version: m.version,
|
|
}
|
|
}
|
|
|
|
fn outcome_to_resp(m: care_plan_outcome::Model) -> CarePlanOutcomeResp {
|
|
CarePlanOutcomeResp {
|
|
id: m.id,
|
|
plan_id: m.plan_id,
|
|
item_id: m.item_id,
|
|
metric: m.metric,
|
|
baseline_value: m.baseline_value,
|
|
target_value: m.target_value,
|
|
current_value: m.current_value,
|
|
measured_at: m.measured_at,
|
|
notes: m.notes,
|
|
created_at: m.created_at,
|
|
updated_at: m.updated_at,
|
|
version: m.version,
|
|
}
|
|
}
|
|
|
|
fn validate_plan_type(plan_type: &str) -> HealthResult<()> {
|
|
let valid = ["dialysis", "chronic", "preventive", "rehabilitation"];
|
|
if valid.contains(&plan_type) {
|
|
Ok(())
|
|
} else {
|
|
Err(HealthError::Validation(format!(
|
|
"plan_type 必须为以下之一: {}",
|
|
valid.join(", ")
|
|
)))
|
|
}
|
|
}
|
|
|
|
fn validate_plan_status(status: &str) -> HealthResult<()> {
|
|
let valid = ["draft", "active", "paused", "completed", "cancelled"];
|
|
if valid.contains(&status) {
|
|
Ok(())
|
|
} else {
|
|
Err(HealthError::Validation(format!(
|
|
"status 必须为以下之一: {}",
|
|
valid.join(", ")
|
|
)))
|
|
}
|
|
}
|
|
|
|
fn validate_item_type(item_type: &str) -> HealthResult<()> {
|
|
let valid = ["intervention", "monitoring", "goal", "education"];
|
|
if valid.contains(&item_type) {
|
|
Ok(())
|
|
} else {
|
|
Err(HealthError::Validation(format!(
|
|
"item_type 必须为以下之一: {}",
|
|
valid.join(", ")
|
|
)))
|
|
}
|
|
}
|