- 创建 erp-dialysis crate(DialysisState + DialysisError + DialysisModule) - 迁移 2 Entity + 2 Service + 2 Handler + 2 DTO 共 8 个文件 - Entity 移除跨 crate patient Relation(FK 列保留) - Service 内联 validation 逻辑,移除 patient 存在性检查(FK 约束保证) - erp-health 的 stats/consultation 中 dialysis 查询改为 raw SQL - ReviewLabReportReq 从 dialysis_dto 移至 health_data_dto(正确归属) - workspace 全量编译通过
278 lines
8.7 KiB
Rust
278 lines
8.7 KiB
Rust
use chrono::NaiveDate;
|
||
use erp_core::sanitize::sanitize_option;
|
||
use serde::{Deserialize, Serialize};
|
||
use utoipa::ToSchema;
|
||
use uuid::Uuid;
|
||
|
||
/// 用 f64 表示 Decimal 值以满足 utoipa ToSchema 要求。
|
||
/// 对于健康数值(血压 60-200mmHg、血糖 3.9-11.1mmol/L、体重 30-300kg),
|
||
/// f64 的 15 位有效数字精度完全足够,不存在实际精度丢失风险。
|
||
/// 数据库层仍使用 SeaORM Decimal 类型,转换仅在 DTO 边界进行。
|
||
type Decimal = f64;
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||
pub struct CreateVitalSignsReq {
|
||
pub record_date: NaiveDate,
|
||
pub systolic_bp_morning: Option<i32>,
|
||
pub diastolic_bp_morning: Option<i32>,
|
||
pub systolic_bp_evening: Option<i32>,
|
||
pub diastolic_bp_evening: Option<i32>,
|
||
pub heart_rate: Option<i32>,
|
||
pub weight: Option<Decimal>,
|
||
pub blood_sugar: Option<Decimal>,
|
||
pub body_temperature: Option<Decimal>,
|
||
pub spo2: Option<i32>,
|
||
/// fasting / postprandial / random / ogtt
|
||
pub blood_sugar_type: Option<String>,
|
||
pub water_intake_ml: Option<i32>,
|
||
pub urine_output_ml: Option<i32>,
|
||
pub notes: Option<String>,
|
||
pub source: Option<String>,
|
||
}
|
||
|
||
impl CreateVitalSignsReq {
|
||
pub fn sanitize(&mut self) {
|
||
self.notes = sanitize_option(self.notes.take());
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||
pub struct UpdateVitalSignsReq {
|
||
pub record_date: Option<NaiveDate>,
|
||
pub systolic_bp_morning: Option<i32>,
|
||
pub diastolic_bp_morning: Option<i32>,
|
||
pub systolic_bp_evening: Option<i32>,
|
||
pub diastolic_bp_evening: Option<i32>,
|
||
pub heart_rate: Option<i32>,
|
||
pub weight: Option<Decimal>,
|
||
pub blood_sugar: Option<Decimal>,
|
||
pub body_temperature: Option<Decimal>,
|
||
pub spo2: Option<i32>,
|
||
pub blood_sugar_type: Option<String>,
|
||
pub water_intake_ml: Option<i32>,
|
||
pub urine_output_ml: Option<i32>,
|
||
pub notes: Option<String>,
|
||
}
|
||
|
||
impl UpdateVitalSignsReq {
|
||
pub fn sanitize(&mut self) {
|
||
self.notes = sanitize_option(self.notes.take());
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||
pub struct VitalSignsResp {
|
||
pub id: Uuid,
|
||
pub patient_id: Uuid,
|
||
pub record_date: NaiveDate,
|
||
pub source: String,
|
||
pub systolic_bp_morning: Option<i32>,
|
||
pub diastolic_bp_morning: Option<i32>,
|
||
pub systolic_bp_evening: Option<i32>,
|
||
pub diastolic_bp_evening: Option<i32>,
|
||
pub heart_rate: Option<i32>,
|
||
pub weight: Option<Decimal>,
|
||
pub blood_sugar: Option<Decimal>,
|
||
pub body_temperature: Option<Decimal>,
|
||
pub spo2: Option<i32>,
|
||
pub blood_sugar_type: Option<String>,
|
||
pub water_intake_ml: Option<i32>,
|
||
pub urine_output_ml: Option<i32>,
|
||
pub notes: Option<String>,
|
||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||
pub version: i32,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||
pub struct CreateLabReportReq {
|
||
pub report_date: NaiveDate,
|
||
pub report_type: String,
|
||
/// manual_input / photo_upload
|
||
pub source: Option<String>,
|
||
/// V2 JSON 结构: [{name, value, unit, reference_low, reference_high, is_abnormal}]
|
||
pub items: Option<serde_json::Value>,
|
||
pub image_urls: Option<serde_json::Value>,
|
||
pub doctor_notes: Option<String>,
|
||
}
|
||
|
||
impl CreateLabReportReq {
|
||
pub fn sanitize(&mut self) {
|
||
self.doctor_notes = sanitize_option(self.doctor_notes.take());
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||
pub struct UpdateLabReportReq {
|
||
pub report_date: Option<NaiveDate>,
|
||
pub report_type: Option<String>,
|
||
pub source: Option<String>,
|
||
pub items: Option<serde_json::Value>,
|
||
pub image_urls: Option<serde_json::Value>,
|
||
pub doctor_notes: Option<String>,
|
||
}
|
||
|
||
impl UpdateLabReportReq {
|
||
pub fn sanitize(&mut self) {
|
||
self.doctor_notes = sanitize_option(self.doctor_notes.take());
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||
pub struct LabReportResp {
|
||
pub id: Uuid,
|
||
pub patient_id: Uuid,
|
||
pub report_date: NaiveDate,
|
||
pub report_type: String,
|
||
pub source: Option<String>,
|
||
pub items: Option<serde_json::Value>,
|
||
pub image_urls: Option<serde_json::Value>,
|
||
pub doctor_notes: Option<String>,
|
||
pub status: String,
|
||
pub reviewed_by: Option<Uuid>,
|
||
pub reviewed_at: Option<chrono::DateTime<chrono::Utc>>,
|
||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||
pub version: i32,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||
pub struct CreateHealthRecordReq {
|
||
pub record_type: Option<String>,
|
||
pub record_date: NaiveDate,
|
||
pub source: Option<String>,
|
||
pub overall_assessment: Option<String>,
|
||
pub report_file_url: Option<String>,
|
||
pub notes: Option<String>,
|
||
}
|
||
|
||
impl CreateHealthRecordReq {
|
||
pub fn sanitize(&mut self) {
|
||
self.source = sanitize_option(self.source.take());
|
||
self.overall_assessment = sanitize_option(self.overall_assessment.take());
|
||
self.notes = sanitize_option(self.notes.take());
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||
pub struct UpdateHealthRecordReq {
|
||
pub record_type: Option<String>,
|
||
pub record_date: Option<NaiveDate>,
|
||
pub source: Option<String>,
|
||
pub overall_assessment: Option<String>,
|
||
pub report_file_url: Option<String>,
|
||
pub notes: Option<String>,
|
||
}
|
||
|
||
impl UpdateHealthRecordReq {
|
||
pub fn sanitize(&mut self) {
|
||
self.source = sanitize_option(self.source.take());
|
||
self.overall_assessment = sanitize_option(self.overall_assessment.take());
|
||
self.notes = sanitize_option(self.notes.take());
|
||
}
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||
pub struct HealthRecordResp {
|
||
pub id: Uuid,
|
||
pub patient_id: Uuid,
|
||
pub record_type: String,
|
||
pub record_date: NaiveDate,
|
||
pub source: Option<String>,
|
||
pub overall_assessment: Option<String>,
|
||
pub report_file_url: Option<String>,
|
||
pub notes: Option<String>,
|
||
pub created_at: chrono::DateTime<chrono::Utc>,
|
||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||
pub version: i32,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||
pub struct TrendResp {
|
||
pub id: Uuid,
|
||
pub patient_id: Uuid,
|
||
pub period_start: NaiveDate,
|
||
pub period_end: NaiveDate,
|
||
pub indicator_summary: Option<serde_json::Value>,
|
||
pub abnormal_items: Option<serde_json::Value>,
|
||
pub generation_type: String,
|
||
pub report_file_url: Option<String>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||
pub struct IndicatorTimeseriesResp {
|
||
pub indicator: String,
|
||
pub data: Vec<DataPoint>,
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 小程序趋势查询(通过当前用户关联 patient)
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/// 小程序趋势查询参数
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||
pub struct MiniTrendQueryParams {
|
||
/// 指标名称,如 "blood_pressure_systolic", "heart_rate" 等
|
||
pub indicator: String,
|
||
/// 时间范围:"7d"(默认), "30d", "90d"
|
||
pub range: Option<String>,
|
||
}
|
||
|
||
/// 小程序趋势数据点
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||
pub struct DataPoint {
|
||
/// 日期,格式 YYYY-MM-DD
|
||
pub date: String,
|
||
/// 指标数值
|
||
pub value: f64,
|
||
}
|
||
|
||
/// 小程序趋势响应
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||
pub struct MiniTrendResp {
|
||
/// 指标名称
|
||
pub indicator: String,
|
||
/// 数据点列表(按日期升序)
|
||
pub data_points: Vec<DataPoint>,
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 小程序今日体征摘要
|
||
// ---------------------------------------------------------------------------
|
||
|
||
/// 单个指标摘要
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||
pub struct IndicatorSummary {
|
||
/// 指标数值
|
||
pub value: f64,
|
||
/// 状态: "normal" | "high" | "low"
|
||
pub status: String,
|
||
/// 参考范围,如 "60-100"
|
||
pub reference_range: Option<String>,
|
||
/// 收缩压(仅血压指标)
|
||
pub systolic: Option<f64>,
|
||
/// 舒张压(仅血压指标)
|
||
pub diastolic: Option<f64>,
|
||
}
|
||
|
||
/// 小程序今日体征摘要响应
|
||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||
pub struct MiniTodayResp {
|
||
pub blood_pressure: Option<IndicatorSummary>,
|
||
pub heart_rate: Option<IndicatorSummary>,
|
||
pub blood_sugar: Option<IndicatorSummary>,
|
||
pub weight: Option<IndicatorSummary>,
|
||
}
|
||
|
||
#[derive(Debug, Clone, serde::Deserialize, ToSchema)]
|
||
pub struct ReviewLabReportReq {
|
||
pub doctor_notes: Option<String>,
|
||
pub items: Option<serde_json::Value>,
|
||
}
|
||
|
||
impl ReviewLabReportReq {
|
||
pub fn sanitize(&mut self) {
|
||
self.doctor_notes = sanitize_option(self.doctor_notes.take());
|
||
}
|
||
}
|