feat(health): appointment list API 内联 patient_name/doctor_name

列表查询后批量获取 patient 和 doctor 名称,消除前端 N+1 请求。
DTO 新增 patient_name/doctor_name Option 字段,向后兼容。
This commit is contained in:
iven
2026-04-27 09:34:04 +08:00
parent c09f6ecdc8
commit 432f6e3554
2 changed files with 38 additions and 0 deletions

View File

@@ -38,6 +38,8 @@ pub struct AppointmentResp {
pub id: Uuid,
pub patient_id: Uuid,
pub doctor_id: Option<Uuid>,
pub patient_name: Option<String>,
pub doctor_name: Option<String>,
pub appointment_type: String,
pub appointment_date: NaiveDate,
pub start_time: NaiveTime,

View File

@@ -14,6 +14,7 @@ use erp_core::types::PaginatedResponse;
use crate::dto::appointment_dto::*;
use crate::entity::{appointment, doctor_profile, doctor_schedule, patient};
use crate::error::{HealthError, HealthResult};
use std::collections::{HashMap, HashSet};
use crate::service::validation::{
validate_appointment_status_transition, validate_appointment_type,
validate_period_type, validate_schedule_status,
@@ -54,9 +55,41 @@ pub async fn list_appointments(
.all(&state.db)
.await?;
// 批量查询 patient_name 和 doctor_name
let patient_ids: HashSet<Uuid> = models.iter().map(|m| m.patient_id).collect();
let doctor_ids: HashSet<Uuid> = models.iter().filter_map(|m| m.doctor_id).collect();
let patient_names: HashMap<Uuid, String> = if !patient_ids.is_empty() {
patient::Entity::find()
.filter(patient::Column::Id.is_in(patient_ids))
.filter(patient::Column::TenantId.eq(tenant_id))
.all(&state.db)
.await?
.into_iter()
.map(|p| (p.id, p.name))
.collect()
} else {
HashMap::new()
};
let doctor_names: HashMap<Uuid, String> = if !doctor_ids.is_empty() {
doctor_profile::Entity::find()
.filter(doctor_profile::Column::Id.is_in(doctor_ids))
.filter(doctor_profile::Column::TenantId.eq(tenant_id))
.all(&state.db)
.await?
.into_iter()
.map(|d| (d.id, d.name))
.collect()
} else {
HashMap::new()
};
let total_pages = total.div_ceil(limit.max(1));
let data = models.into_iter().map(|m| AppointmentResp {
id: m.id, patient_id: m.patient_id, doctor_id: m.doctor_id,
patient_name: patient_names.get(&m.patient_id).cloned(),
doctor_name: m.doctor_id.and_then(|did| doctor_names.get(&did).cloned()),
appointment_type: m.appointment_type, appointment_date: m.appointment_date,
start_time: m.start_time, end_time: m.end_time,
status: m.status, cancel_reason: m.cancel_reason, notes: m.notes,
@@ -81,6 +114,7 @@ pub async fn get_appointment(
Ok(AppointmentResp {
id: m.id, patient_id: m.patient_id, doctor_id: m.doctor_id,
patient_name: None, doctor_name: None,
appointment_type: m.appointment_type, appointment_date: m.appointment_date,
start_time: m.start_time, end_time: m.end_time,
status: m.status, cancel_reason: m.cancel_reason, notes: m.notes,
@@ -189,6 +223,7 @@ pub async fn create_appointment(
Ok(AppointmentResp {
id: m.id, patient_id: m.patient_id, doctor_id: m.doctor_id,
patient_name: None, doctor_name: None,
appointment_type: m.appointment_type, appointment_date: m.appointment_date,
start_time: m.start_time, end_time: m.end_time,
status: m.status, cancel_reason: m.cancel_reason, notes: m.notes,
@@ -280,6 +315,7 @@ pub async fn update_appointment_status(
Ok(AppointmentResp {
id: m.id, patient_id: m.patient_id, doctor_id: m.doctor_id,
patient_name: None, doctor_name: None,
appointment_type: m.appointment_type, appointment_date: m.appointment_date,
start_time: m.start_time, end_time: m.end_time,
status: m.status, cancel_reason: m.cancel_reason, notes: m.notes,