包含 classes/exams/homework/grades/attendance/scheduling 域、outbox、iam-consumer、redis 配置等完整实现
83 lines
1.8 KiB
TypeScript
83 lines
1.8 KiB
TypeScript
/**
|
||
* 排课冲突检测(纯函数)
|
||
*
|
||
* 冲突维度(P3):
|
||
* - teacher: 同一教师在同一时间段有排课冲突
|
||
* - class: 同一班级在同一时间段有排课冲突
|
||
*
|
||
* room_id 仅预留字段(ISSUE-006 决策 #2),P3 不校验教室冲突。
|
||
*/
|
||
|
||
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 };
|
||
}
|