Files
Edu/services/core-edu/src/scheduling/schedule-conflict.ts
SpecialX 58c0ba1bd9 feat(core-edu): 完整实现 core-edu 教学核心服务
包含 classes/exams/homework/grades/attendance/scheduling 域、outbox、iam-consumer、redis 配置等完整实现
2026-07-10 19:08:56 +08:00

83 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 排课冲突检测(纯函数)
*
* 冲突维度P3
* - teacher: 同一教师在同一时间段有排课冲突
* - class: 同一班级在同一时间段有排课冲突
*
* room_id 仅预留字段ISSUE-006 决策 #2P3 不校验教室冲突。
*/
export interface ScheduleSlot {
id: string;
teacherId: string;
classId: string;
startTime: Date;
endTime: Date;
roomId?: string | null;
}
export interface ConflictResult {
hasConflict: boolean;
conflictingSlot?: ScheduleSlot;
conflictType: "teacher" | "class" | null;
}
/**
* 检查时间区间重叠。
* [startA, endA) 与 [startB, endB) 重叠的条件startA < endB && startB < endA
*/
export function isTimeOverlap(
startA: Date,
endA: Date,
startB: Date,
endB: Date,
): boolean {
return startA < endB && startB < endA;
}
/**
* 检查新排课与现有排课列表的冲突。
* 排除自身(如果是更新场景,传入 excludeId
*/
export function detectConflict(
newSlot: Omit<ScheduleSlot, "id"> & { id?: string },
existingSlots: ScheduleSlot[],
excludeId?: string,
): ConflictResult {
for (const slot of existingSlots) {
if (excludeId && slot.id === excludeId) continue;
if (
!isTimeOverlap(
newSlot.startTime,
newSlot.endTime,
slot.startTime,
slot.endTime,
)
) {
continue;
}
// 教师冲突
if (newSlot.teacherId === slot.teacherId) {
return {
hasConflict: true,
conflictingSlot: slot,
conflictType: "teacher",
};
}
// 班级冲突
if (newSlot.classId === slot.classId) {
return {
hasConflict: true,
conflictingSlot: slot,
conflictType: "class",
};
}
}
return { hasConflict: false, conflictType: null };
}