fix(health): 预约 CAS 从精确匹配改为排班时段范围匹配
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

预约创建时 CAS 原子操作要求排班 start_time 精确等于预约 start_time,
导致排班 08:00-12:00 只能在 08:00 开始预约,无法选择 09:00 等子时段。

修改为范围匹配:排班 start_time <= 预约 start_time 且
排班 end_time >= 预约 end_time,预约可落在排班时段内任意子时段。

增加 rows_affected > 1 保护:若排班数据存在重叠时段则拒绝并告警。
This commit is contained in:
iven
2026-05-01 00:37:11 +08:00
parent 63d8b7a65d
commit 9b8c2ff7e1

View File

@@ -154,10 +154,9 @@ pub async fn create_appointment(
let txn = state.db.begin().await?;
// 原子 CAS: 排班名额 +1
// 注意: CAS 按 (tenant_id, doctor_id, schedule_date, start_time) 匹配
// 唯一索引在 (tenant_id, doctor_id, schedule_date, period_type) 上。
// 这要求同一医生同一天同一 start_time 只有一个排班记录,否则 CAS 可能递增错误的行
// 当前业务逻辑保证: 每个医生每天每个时间段最多一条排班。
// CAS 按 (tenant_id, doctor_id, schedule_date) + 时间范围匹配
// 排班的 start_time <= 预约 start_time 且排班的 end_time >= 预约 end_time
// 即预约时段落在排班时段内即可。rows_affected 必须恰好为 1同一医生同一天不应有时段重叠
let cas_result = doctor_schedule::Entity::update_many()
.col_expr(
doctor_schedule::Column::CurrentAppointments,
@@ -167,7 +166,8 @@ pub async fn create_appointment(
.filter(doctor_schedule::Column::TenantId.eq(tenant_id))
.filter(doctor_schedule::Column::DoctorId.eq(doctor_id_val))
.filter(doctor_schedule::Column::ScheduleDate.eq(req.appointment_date))
.filter(doctor_schedule::Column::StartTime.eq(req.start_time))
.filter(doctor_schedule::Column::StartTime.lte(req.start_time))
.filter(doctor_schedule::Column::EndTime.gte(req.end_time))
.filter(
Condition::all()
.add(doctor_schedule::Column::DeletedAt.is_null())
@@ -183,6 +183,16 @@ pub async fn create_appointment(
txn.rollback().await?;
return Err(HealthError::ScheduleFull);
}
if cas_result.rows_affected > 1 {
txn.rollback().await?;
tracing::error!(
doctor_id = %doctor_id_val,
date = %req.appointment_date,
matched = cas_result.rows_affected,
"CAS matched multiple schedules — overlapping schedule data"
);
return Err(HealthError::Validation("排班数据异常:存在重叠时段,请联系管理员".to_string()));
}
let now = Utc::now();
let active = appointment::ActiveModel {
@@ -554,7 +564,7 @@ pub async fn send_reminders(
db: &sea_orm::DatabaseConnection,
event_bus: &erp_core::events::EventBus,
) -> crate::error::HealthResult<usize> {
use chrono::{Local, NaiveDate};
use chrono::Local;
use serde_json::json;
let tomorrow = Local::now().date_naive() + chrono::Duration::days(1);