Skip to content

Commit c3c94e9

Browse files
authored
Merge pull request #61 from Boyuan-IT-Club/Red_Moon
修正面试时间分配逻辑
2 parents b753fd9 + aa78346 commit c3c94e9

File tree

1 file changed

+137
-46
lines changed

1 file changed

+137
-46
lines changed

src/main/java/club/boyuan/official/service/impl/InterviewAssignmentServiceImpl.java

Lines changed: 137 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ private InterviewAssignmentResultDTO assignInterviewsWithOptimization(
9595

9696
// 创建候选人列表,包含他们的偏好信息
9797
List<CandidateInfo> candidates = new ArrayList<>();
98+
List<InterviewAssignmentResultDTO.UnassignedUserDTO> unassignedUsers = new ArrayList<>();
99+
98100
for (Resume resume : resumes) {
99101
User user = userService.getUserById(resume.getUserId());
100102
if (user == null) {
@@ -105,13 +107,29 @@ private InterviewAssignmentResultDTO assignInterviewsWithOptimization(
105107
List<String> preferredTimes = userPreferredTimes.getOrDefault(resume.getUserId(), new ArrayList<>());
106108
List<String> preferredDepartments = userPreferredDepartments.getOrDefault(resume.getUserId(), new ArrayList<>());
107109

108-
// 如果用户没有填写期望面试时间或部门,跳过分配
109-
if (preferredTimes.isEmpty() || preferredDepartments.isEmpty()) {
110-
logger.info("用户 {} 没有填写期望面试时间或部门,跳过面试时间分配", user.getUsername());
110+
// 添加调试日志
111+
logger.info("用户 {} 的期望面试时间: {}", user.getUsername(), preferredTimes);
112+
logger.info("用户 {} 的期望部门: {}", user.getUsername(), preferredDepartments);
113+
114+
// 如果用户没有填写期望面试时间,则加入未分配列表
115+
if (preferredTimes.isEmpty()) {
116+
logger.info("用户 {} 没有填写期望面试时间,加入未分配列表", user.getUsername());
117+
unassignedUsers.add(new InterviewAssignmentResultDTO.UnassignedUserDTO(
118+
user.getUserId(), user.getUsername(), user.getName(), "未填写期望面试时间",
119+
preferredDepartments.isEmpty() ? "未填写期望部门" : String.join(", ", preferredDepartments)));
120+
continue;
121+
}
122+
123+
// 如果用户没有填写期望部门,则加入未分配列表
124+
if (preferredDepartments.isEmpty()) {
125+
logger.info("用户 {} 没有填写期望部门,加入未分配列表", user.getUsername());
126+
unassignedUsers.add(new InterviewAssignmentResultDTO.UnassignedUserDTO(
127+
user.getUserId(), user.getUsername(), user.getName(),
128+
String.join(", ", preferredTimes), "未填写期望部门"));
111129
continue;
112130
}
113131

114-
String firstDepartment = preferredDepartments.isEmpty() ? "未指定部门" : preferredDepartments.get(0);
132+
String firstDepartment = preferredDepartments.get(0);
115133
candidates.add(new CandidateInfo(user, preferredTimes, preferredDepartments, firstDepartment));
116134
}
117135

@@ -128,7 +146,6 @@ private InterviewAssignmentResultDTO assignInterviewsWithOptimization(
128146

129147
// 分配面试时间
130148
List<InterviewAssignmentResultDTO.AssignedInterviewDTO> assignedInterviews = new ArrayList<>();
131-
List<InterviewAssignmentResultDTO.UnassignedUserDTO> unassignedUsers = new ArrayList<>();
132149

133150
for (CandidateInfo candidate : candidates) {
134151
User user = candidate.user;
@@ -143,8 +160,11 @@ private InterviewAssignmentResultDTO assignInterviewsWithOptimization(
143160
if (!assigned) {
144161
String preferredTimesStr = String.join(", ", preferredTimes);
145162
String preferredDepartmentsStr = String.join(", ", candidate.preferredDepartments);
163+
logger.info("用户 {} 未被分配,期望时间: {},期望部门: {}", user.getUsername(), preferredTimesStr, preferredDepartmentsStr);
146164
unassignedUsers.add(new InterviewAssignmentResultDTO.UnassignedUserDTO(
147165
user.getUserId(), user.getUsername(), user.getName(), preferredTimesStr, preferredDepartmentsStr));
166+
} else {
167+
logger.info("用户 {} 已成功分配面试时间", user.getUsername());
148168
}
149169
}
150170

@@ -199,21 +219,30 @@ private Map<Integer, List<String>> getUserPreferredTimes(List<Resume> resumes, I
199219

200220
for (Resume resume : resumes) {
201221
List<ResumeFieldValue> fieldValues = resumeService.getFieldValuesByResumeId(resume.getResumeId());
202-
Optional<ResumeFieldValue> interviewTimeValue = fieldValues.stream()
222+
// 获取所有匹配的字段值
223+
List<ResumeFieldValue> interviewTimeValues = fieldValues.stream()
203224
.filter(value -> fieldId.equals(value.getFieldId()))
204-
.findFirst();
225+
.collect(Collectors.toList());
205226

206-
if (interviewTimeValue.isPresent()) {
207-
try {
208-
// 解析JSON数组
209-
List<String> preferredTimes = objectMapper.readValue(
210-
interviewTimeValue.get().getFieldValue(),
211-
new TypeReference<List<String>>() {});
212-
userPreferredTimes.put(resume.getUserId(), preferredTimes);
213-
} catch (Exception e) {
214-
logger.warn("解析用户 {} 的期望面试时间失败: {}", resume.getUserId(),
215-
interviewTimeValue.get().getFieldValue(), e);
227+
logger.info("用户 {} 的期望面试时间字段值数量: {}", resume.getUserId(), interviewTimeValues.size());
228+
229+
if (!interviewTimeValues.isEmpty()) {
230+
List<String> allPreferredTimes = new ArrayList<>();
231+
for (ResumeFieldValue interviewTimeValue : interviewTimeValues) {
232+
logger.info("用户 {} 的期望面试时间字段值: {}", resume.getUserId(), interviewTimeValue.getFieldValue());
233+
try {
234+
// 解析JSON数组
235+
List<String> preferredTimes = objectMapper.readValue(
236+
interviewTimeValue.getFieldValue(),
237+
new TypeReference<List<String>>() {});
238+
allPreferredTimes.addAll(preferredTimes);
239+
} catch (Exception e) {
240+
logger.warn("解析用户 {} 的期望面试时间失败: {}", resume.getUserId(),
241+
interviewTimeValue.getFieldValue(), e);
242+
}
216243
}
244+
logger.info("用户 {} 解析后的所有期望面试时间: {}", resume.getUserId(), allPreferredTimes);
245+
userPreferredTimes.put(resume.getUserId(), allPreferredTimes);
217246
}
218247
}
219248

@@ -228,21 +257,30 @@ private Map<Integer, List<String>> getUserPreferredDepartments(List<Resume> resu
228257

229258
for (Resume resume : resumes) {
230259
List<ResumeFieldValue> fieldValues = resumeService.getFieldValuesByResumeId(resume.getResumeId());
231-
Optional<ResumeFieldValue> expectedDepartmentsValue = fieldValues.stream()
260+
// 获取所有匹配的字段值
261+
List<ResumeFieldValue> expectedDepartmentsValues = fieldValues.stream()
232262
.filter(value -> fieldId.equals(value.getFieldId()))
233-
.findFirst();
263+
.collect(Collectors.toList());
264+
265+
logger.info("用户 {} 的期望部门字段值数量: {}", resume.getUserId(), expectedDepartmentsValues.size());
234266

235-
if (expectedDepartmentsValue.isPresent()) {
236-
try {
237-
// 解析JSON数组
238-
List<String> preferredDepartments = objectMapper.readValue(
239-
expectedDepartmentsValue.get().getFieldValue(),
240-
new TypeReference<List<String>>() {});
241-
userPreferredDepartments.put(resume.getUserId(), preferredDepartments);
242-
} catch (Exception e) {
243-
logger.warn("解析用户 {} 的期望部门失败: {}", resume.getUserId(),
244-
expectedDepartmentsValue.get().getFieldValue(), e);
267+
if (!expectedDepartmentsValues.isEmpty()) {
268+
List<String> allPreferredDepartments = new ArrayList<>();
269+
for (ResumeFieldValue expectedDepartmentsValue : expectedDepartmentsValues) {
270+
logger.info("用户 {} 的期望部门字段值: {}", resume.getUserId(), expectedDepartmentsValue.getFieldValue());
271+
try {
272+
// 解析JSON数组
273+
List<String> preferredDepartments = objectMapper.readValue(
274+
expectedDepartmentsValue.getFieldValue(),
275+
new TypeReference<List<String>>() {});
276+
allPreferredDepartments.addAll(preferredDepartments);
277+
} catch (Exception e) {
278+
logger.warn("解析用户 {} 的期望部门失败: {}", resume.getUserId(),
279+
expectedDepartmentsValue.getFieldValue(), e);
280+
}
245281
}
282+
logger.info("用户 {} 解析后的所有期望部门: {}", resume.getUserId(), allPreferredDepartments);
283+
userPreferredDepartments.put(resume.getUserId(), allPreferredDepartments);
246284
}
247285
}
248286

@@ -259,12 +297,12 @@ private List<LocalDateTime> generateTimeSlotsForSpecificDays(LocalDate day1, Loc
259297
LocalDate[] dates = {day1, day2};
260298
for (LocalDate date : dates) {
261299
// 生成上午时间段 (9:00-11:00)
262-
for (LocalTime time = MORNING_START; time.isBefore(MORNING_END); time = time.plusMinutes(INTERVIEW_DURATION)) {
300+
for (LocalTime time = MORNING_START; !time.equals(MORNING_END); time = time.plusMinutes(INTERVIEW_DURATION)) {
263301
timeSlots.add(LocalDateTime.of(date, time));
264302
}
265303

266304
// 生成下午时间段 (13:00-17:00)
267-
for (LocalTime time = AFTERNOON_START; time.isBefore(AFTERNOON_END); time = time.plusMinutes(INTERVIEW_DURATION)) {
305+
for (LocalTime time = AFTERNOON_START; !time.equals(AFTERNOON_END); time = time.plusMinutes(INTERVIEW_DURATION)) {
268306
timeSlots.add(LocalDateTime.of(date, time));
269307
}
270308
}
@@ -283,12 +321,12 @@ private List<LocalDateTime> generateTimeSlots(LocalDate startDate, LocalDate end
283321
LocalDate date = startDate.plusDays(dayOffset);
284322

285323
// 生成上午时间段 (9:00-11:00)
286-
for (LocalTime time = MORNING_START; time.isBefore(MORNING_END); time = time.plusMinutes(INTERVIEW_DURATION)) {
324+
for (LocalTime time = MORNING_START; !time.equals(MORNING_END); time = time.plusMinutes(INTERVIEW_DURATION)) {
287325
timeSlots.add(LocalDateTime.of(date, time));
288326
}
289327

290328
// 生成下午时间段 (13:00-17:00)
291-
for (LocalTime time = AFTERNOON_START; time.isBefore(AFTERNOON_END); time = time.plusMinutes(INTERVIEW_DURATION)) {
329+
for (LocalTime time = AFTERNOON_START; !time.equals(AFTERNOON_END); time = time.plusMinutes(INTERVIEW_DURATION)) {
292330
timeSlots.add(LocalDateTime.of(date, time));
293331
}
294332
}
@@ -344,22 +382,62 @@ private boolean tryAssignInterviewTime(User user, List<String> preferredTimes, S
344382
return false;
345383
}
346384

385+
logger.info("开始为用户 {} 分配面试时间,期望时间: {}", user.getUsername(), preferredTimes);
386+
347387
// 按照用户期望的时间顺序尝试分配
348388
for (String preferredTime : preferredTimes) {
349-
LocalDateTime assignedSlot = findAndReserveSlot(preferredTime, department, departmentSlotAvailability);
350-
if (assignedSlot != null) {
351-
// 成功分配时间
352-
String period = assignedSlot.getHour() < 12 ? "上午" : "下午";
353-
assignedInterviews.add(new InterviewAssignmentResultDTO.AssignedInterviewDTO(
354-
user.getUserId(), user.getUsername(), user.getName(), assignedSlot, period, department));
355-
return true;
389+
logger.info("尝试为用户 {} 分配时间: {}", user.getUsername(), preferredTime);
390+
// 检查该时间段是否有空位
391+
if (hasAvailableSlot(preferredTime, department, departmentSlotAvailability)) {
392+
logger.info("用户 {} 的时间段 {} 有空位,正在分配", user.getUsername(), preferredTime);
393+
// 如果有空位,则分配时间
394+
LocalDateTime assignedSlot = findAndReserveSlot(preferredTime, department, departmentSlotAvailability);
395+
if (assignedSlot != null) {
396+
// 成功分配时间
397+
String period = assignedSlot.getHour() < 12 ? "上午" : "下午";
398+
logger.info("成功为用户 {} 分配面试时间: {}", user.getUsername(), assignedSlot);
399+
assignedInterviews.add(new InterviewAssignmentResultDTO.AssignedInterviewDTO(
400+
user.getUserId(), user.getUsername(), user.getName(), assignedSlot, period, department));
401+
return true;
402+
}
403+
} else {
404+
logger.info("用户 {} 的时间段 {} 没有空位,尝试下一个时间段", user.getUsername(), preferredTime);
356405
}
357406
}
358407

359-
// 如果按照用户期望的时间无法分配,则将用户加入未分配列表
360-
// 不再尝试分配任何可用的时间,确保只在用户期望的时间段内分配
408+
// 如果用户选择的所有时间段都没有空位,则不分配时间
409+
logger.info("用户 {} 的所有期望时间段都没有空位,无法分配", user.getUsername());
410+
return false;
411+
}
412+
413+
/**
414+
* 检查指定时间段是否还有空位
415+
*/
416+
private boolean hasAvailableSlot(String preferredTime, String department,
417+
Map<String, Map<LocalDateTime, Boolean>> departmentSlotAvailability) {
418+
Map<LocalDateTime, Boolean> slotAvailability = departmentSlotAvailability.get(department);
419+
if (slotAvailability == null) {
420+
return false;
421+
}
422+
423+
// 获取所有可用的时间槽
424+
List<LocalDateTime> availableSlots = slotAvailability.entrySet().stream()
425+
.filter(Map.Entry::getValue) // 只考虑可用的时间槽
426+
.map(Map.Entry::getKey)
427+
.sorted()
428+
.collect(Collectors.toList());
429+
430+
logger.debug("部门 {} 的可用时间槽: {}", department, availableSlots);
361431

362-
return false; // 无法分配时间
432+
// 检查是否有符合期望时间段的时间槽
433+
for (LocalDateTime slotTime : availableSlots) {
434+
if (isSlotMatchPreference(slotTime, preferredTime)) {
435+
logger.debug("找到匹配的时间槽: {} 对应偏好时间: {}", slotTime, preferredTime);
436+
return true;
437+
}
438+
}
439+
440+
return false;
363441
}
364442

365443
/**
@@ -384,6 +462,7 @@ private LocalDateTime findAndReserveSlot(String preferredTime, String department
384462
if (isSlotMatchPreference(slotTime, preferredTime)) {
385463
// 预留该时间槽
386464
slotAvailability.put(slotTime, false);
465+
logger.info("为部门 {} 预留时间槽: {}", department, slotTime);
387466
return slotTime;
388467
}
389468
}
@@ -419,6 +498,7 @@ private LocalDateTime findAnyAvailableSlot(String department,
419498

420499
/**
421500
* 检查时间槽是否符合用户的期望时间段
501+
* 严格按照用户选择的具体日期和时间段进行匹配
422502
*/
423503
private boolean isSlotMatchPreference(LocalDateTime slotTime, String preferredTime) {
424504
String[] parts = preferredTime.split(" ");
@@ -430,9 +510,20 @@ private boolean isSlotMatchPreference(LocalDateTime slotTime, String preferredTi
430510
int dayNumber = Integer.parseInt(parts[1]);
431511
String period = parts[2]; // "上午" 或 "下午"
432512

433-
// 判断是第几天 (假设面试从招募周期开始日期算起)
434-
// 注意:这里简化处理,实际应该根据开始日期计算
435-
if (dayNumber == 1 || dayNumber == 2) {
513+
// 根据面试时间槽的日期确定是第几天
514+
LocalDate day1 = LocalDate.of(2025, 9, 27);
515+
LocalDate day2 = LocalDate.of(2025, 9, 28);
516+
517+
// 严格匹配具体日期和时间段
518+
if (dayNumber == 1 && slotTime.toLocalDate().equals(day1)) {
519+
// 检查时间段是否匹配
520+
LocalTime time = slotTime.toLocalTime();
521+
if ("上午".equals(period)) {
522+
return !time.isBefore(MORNING_START) && time.isBefore(MORNING_END);
523+
} else if ("下午".equals(period)) {
524+
return !time.isBefore(AFTERNOON_START) && time.isBefore(AFTERNOON_END);
525+
}
526+
} else if (dayNumber == 2 && slotTime.toLocalDate().equals(day2)) {
436527
// 检查时间段是否匹配
437528
LocalTime time = slotTime.toLocalTime();
438529
if ("上午".equals(period)) {

0 commit comments

Comments
 (0)