260 预约管理-》医生排班管理:系统未正确限制同一天同一时段(上午/下午)的重复排班,导致同一医生在同一时间段可被多次排班,产生数据冲突和资源调度混乱
This commit is contained in:
@@ -163,6 +163,30 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
|
|||||||
return R.fail("限号数量必须大于0");
|
return R.fail("限号数量必须大于0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查结束时间必须大于开始时间
|
||||||
|
if (doctorSchedule.getStartTime() != null && doctorSchedule.getEndTime() != null) {
|
||||||
|
if (!doctorSchedule.getStartTime().isBefore(doctorSchedule.getEndTime())) {
|
||||||
|
return R.fail("结束时间必须大于开始时间");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查时间重叠(同一医生、同一天时间段有重叠)
|
||||||
|
if (doctorSchedule.getDoctorId() != null && scheduledDate != null
|
||||||
|
&& doctorSchedule.getStartTime() != null && doctorSchedule.getEndTime() != null) {
|
||||||
|
LocalDate scheduleDate = LocalDate.parse(scheduledDate);
|
||||||
|
boolean hasOverlap = checkTimeOverlap(
|
||||||
|
doctorSchedule.getDoctorId(),
|
||||||
|
scheduleDate,
|
||||||
|
doctorSchedule.getStartTime(),
|
||||||
|
doctorSchedule.getEndTime()
|
||||||
|
);
|
||||||
|
if (hasOverlap) {
|
||||||
|
return R.fail("该医生在 " + scheduledDate + " 的 "
|
||||||
|
+ doctorSchedule.getStartTime() + "-" + doctorSchedule.getEndTime()
|
||||||
|
+ " 时间段与已有排班重叠,不能重复添加");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 创建新对象,排除id字段(数据库id列是GENERATED ALWAYS,由数据库自动生成)
|
// 创建新对象,排除id字段(数据库id列是GENERATED ALWAYS,由数据库自动生成)
|
||||||
DoctorSchedule newSchedule = new DoctorSchedule();
|
DoctorSchedule newSchedule = new DoctorSchedule();
|
||||||
newSchedule.setWeekday(doctorSchedule.getWeekday());
|
newSchedule.setWeekday(doctorSchedule.getWeekday());
|
||||||
@@ -381,6 +405,42 @@ public class DoctorScheduleAppServiceImpl implements IDoctorScheduleAppService {
|
|||||||
return slots;
|
return slots;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查同一医生、同一天、同时段是否已存在排班
|
||||||
|
*
|
||||||
|
* @param doctorId 医生ID
|
||||||
|
* @param scheduleDate 出诊日期
|
||||||
|
* @param timePeriod 时段(上午/下午)
|
||||||
|
* @return true表示存在重复排班,false表示不存在
|
||||||
|
*/
|
||||||
|
// private boolean checkDuplicateSchedule(Long doctorId, LocalDate scheduleDate, String timePeriod) {
|
||||||
|
// return schedulePoolService.lambdaQuery()
|
||||||
|
// .eq(SchedulePool::getDoctorId, doctorId)
|
||||||
|
// .eq(SchedulePool::getScheduleDate, scheduleDate)
|
||||||
|
// .eq(SchedulePool::getShift, timePeriod)
|
||||||
|
// .exists();
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查同一医生、同一天是否有时间重叠的排班
|
||||||
|
* 重叠判断:startA < endB AND startB < endA(即时间段有交集)
|
||||||
|
*
|
||||||
|
* @param doctorId 医生ID
|
||||||
|
* @param scheduleDate 出诊日期
|
||||||
|
* @param startTime 开始时间
|
||||||
|
* @param endTime 结束时间
|
||||||
|
* @return true表示存在时间重叠,false表示不重叠
|
||||||
|
*/
|
||||||
|
private boolean checkTimeOverlap(Long doctorId, LocalDate scheduleDate,
|
||||||
|
LocalTime startTime, LocalTime endTime) {
|
||||||
|
return schedulePoolService.lambdaQuery()
|
||||||
|
.eq(SchedulePool::getDoctorId, doctorId)
|
||||||
|
.eq(SchedulePool::getScheduleDate, scheduleDate)
|
||||||
|
.lt(SchedulePool::getStartTime, endTime) // 已有开始时间 < 新结束时间
|
||||||
|
.gt(SchedulePool::getEndTime, startTime) // 已有结束时间 > 新开始时间
|
||||||
|
.exists();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据星期几计算具体日期(下周的对应星期)
|
* 根据星期几计算具体日期(下周的对应星期)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -166,7 +166,19 @@
|
|||||||
<span class="weekday-text">{{ dateGroup.weekday }}</span>
|
<span class="weekday-text">{{ dateGroup.weekday }}</span>
|
||||||
</div>
|
</div>
|
||||||
<el-table :data="dateGroup.items" border style="width: 100%" class="schedule-table">
|
<el-table :data="dateGroup.items" border style="width: 100%" class="schedule-table">
|
||||||
<el-table-column prop="timeSlot" label="时段" width="100"></el-table-column>
|
<el-table-column label="时段" width="100">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-select
|
||||||
|
v-model="scope.row.timeSlot"
|
||||||
|
placeholder="请选择"
|
||||||
|
:disabled="!isEditMode"
|
||||||
|
@change="(val) => handleTimeSlotChange(val, scope.row)"
|
||||||
|
>
|
||||||
|
<el-option label="上午" value="上午"></el-option>
|
||||||
|
<el-option label="下午" value="下午"></el-option>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column prop="doctorName" :label="filterParams.appointmentType === '专家' ? '专家' : '医生'" width="150">
|
<el-table-column prop="doctorName" :label="filterParams.appointmentType === '专家' ? '专家' : '医生'" width="150">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-select
|
<el-select
|
||||||
@@ -215,6 +227,7 @@
|
|||||||
placeholder="选择开始时间"
|
placeholder="选择开始时间"
|
||||||
:disabled="!isEditMode"
|
:disabled="!isEditMode"
|
||||||
class="time-picker"
|
class="time-picker"
|
||||||
|
@change="(val) => handleStartTimeChange(val, scope.row)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -929,17 +942,24 @@ const handleAppointmentSettingCancel = () => {
|
|||||||
// 路由和导航
|
// 路由和导航
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
// 根据开始时间自动判断时段(上午/下午)
|
||||||
|
const getTimeSlot = (startTime) => {
|
||||||
|
if (!startTime) return '上午';
|
||||||
|
const hour = parseInt(startTime.split(':')[0]);
|
||||||
|
return hour < 12 ? '上午' : '下午';
|
||||||
|
};
|
||||||
|
|
||||||
// 生成一周排班数据
|
// 生成一周排班数据
|
||||||
const generateWeekSchedule = (startDate, workTimeConfig) => {
|
const generateWeekSchedule = (startDate, workTimeConfig) => {
|
||||||
const days = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
|
const days = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
|
||||||
const timeSlots = [
|
const timeSlots = [
|
||||||
{
|
{
|
||||||
label: '上午',
|
label: getTimeSlot(workTimeConfig?.morningStart || '08:00'),
|
||||||
startTime: workTimeConfig?.morningStart || '08:00',
|
startTime: workTimeConfig?.morningStart || '08:00',
|
||||||
endTime: workTimeConfig?.morningEnd || '12:00'
|
endTime: workTimeConfig?.morningEnd || '12:00'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '下午',
|
label: getTimeSlot(workTimeConfig?.afternoonStart || '14:30'),
|
||||||
startTime: workTimeConfig?.afternoonStart || '14:30',
|
startTime: workTimeConfig?.afternoonStart || '14:30',
|
||||||
endTime: workTimeConfig?.afternoonEnd || '18:00'
|
endTime: workTimeConfig?.afternoonEnd || '18:00'
|
||||||
}
|
}
|
||||||
@@ -1380,16 +1400,17 @@ const isLastDraftRowInSlot = (row) => {
|
|||||||
|
|
||||||
const handleAddSchedule = (row) => {
|
const handleAddSchedule = (row) => {
|
||||||
// 创建新的排班记录,基于当前行的日期和时段
|
// 创建新的排班记录,基于当前行的日期和时段
|
||||||
|
const startTime = row.startTime || '08:00';
|
||||||
const newSchedule = {
|
const newSchedule = {
|
||||||
id: `new-${Date.now()}-${Math.random()}`,
|
id: `new-${Date.now()}-${Math.random()}`,
|
||||||
date: row.date,
|
date: row.date,
|
||||||
weekday: row.weekday,
|
weekday: row.weekday,
|
||||||
timeSlot: row.timeSlot,
|
timeSlot: getTimeSlot(startTime),
|
||||||
startTime: row.startTime || '08:00',
|
startTime: startTime,
|
||||||
endTime: row.endTime || '12:00',
|
endTime: row.endTime || '12:00',
|
||||||
doctorName: '',
|
doctorName: '',
|
||||||
room: '',
|
room: '',
|
||||||
maxNumber: '',
|
maxNumber: row.maxNumber ?? '',
|
||||||
appointmentItem: '',
|
appointmentItem: '',
|
||||||
registrationFee: 0,
|
registrationFee: 0,
|
||||||
clinicItem: '',
|
clinicItem: '',
|
||||||
@@ -1412,6 +1433,29 @@ const handleAddSchedule = (row) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 时段变化时,更新默认的开始结束时间
|
||||||
|
const handleTimeSlotChange = (val, row) => {
|
||||||
|
const config = workTimeConfig.value;
|
||||||
|
if (val === '上午') {
|
||||||
|
if (!row.startTime || row.startTime >= '12:00') {
|
||||||
|
row.startTime = config?.morningStart || '08:00';
|
||||||
|
row.endTime = config?.morningEnd || '12:00';
|
||||||
|
}
|
||||||
|
} else if (val === '下午') {
|
||||||
|
if (!row.startTime || row.startTime < '12:00') {
|
||||||
|
row.startTime = config?.afternoonStart || '14:30';
|
||||||
|
row.endTime = config?.afternoonEnd || '18:00';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 开始时间变化时,自动更新时段
|
||||||
|
const handleStartTimeChange = (val, row) => {
|
||||||
|
if (val) {
|
||||||
|
row.timeSlot = getTimeSlot(val);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 删除排班
|
// 删除排班
|
||||||
const handleDeleteSchedule = (row) => {
|
const handleDeleteSchedule = (row) => {
|
||||||
if (isLastDraftRowInSlot(row)) {
|
if (isLastDraftRowInSlot(row)) {
|
||||||
@@ -1585,6 +1629,19 @@ const handleSave = async () => {
|
|||||||
return !!resolveDoctorName(item) || !!item.doctorId;
|
return !!resolveDoctorName(item) || !!item.doctorId;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 验证结束时间必须大于开始时间
|
||||||
|
const invalidTimeSchedules = filledSchedules.filter(item => {
|
||||||
|
if (item.startTime && item.endTime) {
|
||||||
|
return item.startTime >= item.endTime;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (invalidTimeSchedules.length > 0) {
|
||||||
|
ElMessage.warning('结束时间必须大于开始时间,请检查排班记录');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const incompleteSchedules = filledSchedules.filter(item => {
|
const incompleteSchedules = filledSchedules.filter(item => {
|
||||||
const isDoctorValid = !!resolveDoctorName(item) || !!item.doctorId;
|
const isDoctorValid = !!resolveDoctorName(item) || !!item.doctorId;
|
||||||
const isRoomValid = item.room && item.room.trim() !== '';
|
const isRoomValid = item.room && item.room.trim() !== '';
|
||||||
|
|||||||
Reference in New Issue
Block a user