|
@@ -3,13 +3,19 @@ package com.qmth.exam.reserve.service.impl;
|
|
|
import java.io.InputStream;
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.Arrays;
|
|
|
+import java.util.Comparator;
|
|
|
import java.util.Date;
|
|
|
import java.util.HashMap;
|
|
|
import java.util.Iterator;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
+import java.util.UUID;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
@@ -18,7 +24,6 @@ import org.springframework.transaction.interceptor.TransactionAspectSupport;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
|
|
-import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
import com.qmth.boot.core.collection.PageResult;
|
|
@@ -31,17 +36,22 @@ import com.qmth.exam.reserve.bean.stdapply.AgentAndTimeVO;
|
|
|
import com.qmth.exam.reserve.bean.stdapply.StudentApplyReq;
|
|
|
import com.qmth.exam.reserve.bean.stdapply.StudentApplyVO;
|
|
|
import com.qmth.exam.reserve.bean.stdapply.StudentImportVO;
|
|
|
+import com.qmth.exam.reserve.cache.CacheConstants;
|
|
|
+import com.qmth.exam.reserve.cache.RedisClient;
|
|
|
import com.qmth.exam.reserve.dao.StudentApplyDao;
|
|
|
import com.qmth.exam.reserve.entity.ApplyTaskEntity;
|
|
|
import com.qmth.exam.reserve.entity.CategoryEntity;
|
|
|
+import com.qmth.exam.reserve.entity.ExamRoomEntity;
|
|
|
import com.qmth.exam.reserve.entity.ExamSiteEntity;
|
|
|
import com.qmth.exam.reserve.entity.StudentApplyEntity;
|
|
|
import com.qmth.exam.reserve.entity.StudentEntity;
|
|
|
import com.qmth.exam.reserve.entity.TimePeriodEntity;
|
|
|
import com.qmth.exam.reserve.enums.CategoryLevel;
|
|
|
+import com.qmth.exam.reserve.enums.DateField;
|
|
|
import com.qmth.exam.reserve.enums.EventType;
|
|
|
import com.qmth.exam.reserve.service.ApplyTaskService;
|
|
|
import com.qmth.exam.reserve.service.CategoryService;
|
|
|
+import com.qmth.exam.reserve.service.ExamRoomService;
|
|
|
import com.qmth.exam.reserve.service.ExamSiteService;
|
|
|
import com.qmth.exam.reserve.service.OperateLogService;
|
|
|
import com.qmth.exam.reserve.service.StudentApplyService;
|
|
@@ -55,9 +65,13 @@ import com.qmth.exam.reserve.util.PageUtil;
|
|
|
public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, StudentApplyEntity>
|
|
|
implements StudentApplyService {
|
|
|
|
|
|
+ private final static Logger log = LoggerFactory.getLogger(StudentApplyServiceImpl.class);
|
|
|
+
|
|
|
private static final String[] EXCEL_HEADER = new String[] { "学号", "姓名", "证件号", "所属教学点", "预约考点1", "预约时段1", "预约考点2",
|
|
|
"预约时段2", "预约考点3", "预约时段3", "预约考点4", "预约时段4" };
|
|
|
|
|
|
+ private final long TIMEOUT = 60;
|
|
|
+
|
|
|
@Autowired
|
|
|
private TimePeriodService timePeriodService;
|
|
|
|
|
@@ -76,6 +90,12 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
|
|
|
@Autowired
|
|
|
private OperateLogService operateLogService;
|
|
|
|
|
|
+ @Autowired
|
|
|
+ private RedisClient redisClient;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private ExamRoomService examRoomService;
|
|
|
+
|
|
|
@Override
|
|
|
public PageResult<StudentApplyVO> page(StudentApplyReq req) {
|
|
|
IPage<StudentApplyVO> iPage = this.baseMapper
|
|
@@ -93,11 +113,9 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
|
|
|
TimePeriodEntity timePeroid = timePeriodService.getById(studentApply.getTimePeriodId());
|
|
|
if (timePeroid == null)
|
|
|
throw new StatusException("考试时段不存在,请检查考试时段数据!");
|
|
|
- LambdaQueryWrapper<ApplyTaskEntity> wrapper = new LambdaQueryWrapper<ApplyTaskEntity>()
|
|
|
- .eq(ApplyTaskEntity::getEnable, Boolean.TRUE);
|
|
|
- ApplyTaskEntity task = applyTaskService.getOne(wrapper);
|
|
|
+ ApplyTaskEntity task = getApplyTask();
|
|
|
Date applyDate = DateUtil.parse(DateUtil.getShortDateByLongTime(timePeroid.getStartTime()), "yyyy-MM-dd");
|
|
|
- Date canCancelDay = DateUtil.addValues(applyDate, 5, -task.getAllowApplyCancelDays());
|
|
|
+ Date canCancelDay = DateUtil.addValues(applyDate, DateField.DAY.getValue(), -task.getAllowApplyCancelDays());
|
|
|
if (new Date().after(canCancelDay))
|
|
|
throw new StatusException("可取消时间已过,无法取消!");
|
|
|
studentApply.setCancel(Boolean.TRUE);
|
|
@@ -308,9 +326,7 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
|
|
|
}
|
|
|
|
|
|
private void checkInOpenTime() {
|
|
|
- LambdaQueryWrapper<ApplyTaskEntity> wrapper = new LambdaQueryWrapper<ApplyTaskEntity>()
|
|
|
- .eq(ApplyTaskEntity::getEnable, Boolean.TRUE);
|
|
|
- ApplyTaskEntity task = applyTaskService.getOne(wrapper);
|
|
|
+ ApplyTaskEntity task = getApplyTask();
|
|
|
Date start = DateUtil.parse(DateUtil.getLongDateByLongTime(task.getOpenApplyStartTime()), null);
|
|
|
Date end = DateUtil.parse(DateUtil.getLongDateByLongTime(task.getOpenApplyEndTime()), null);
|
|
|
if (!DateUtil.isBetwwen(start, end)) {
|
|
@@ -318,6 +334,12 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private ApplyTaskEntity getApplyTask() {
|
|
|
+ LambdaQueryWrapper<ApplyTaskEntity> wrapper = new LambdaQueryWrapper<ApplyTaskEntity>()
|
|
|
+ .eq(ApplyTaskEntity::getEnable, Boolean.TRUE);
|
|
|
+ return applyTaskService.getOne(wrapper);
|
|
|
+ }
|
|
|
+
|
|
|
private void saveStdApply(Long userId, StudentImportVO vo) {
|
|
|
List<AgentAndTimeVO> agentTimeList = vo.getAgentTimeList();
|
|
|
LambdaQueryWrapper<StudentApplyEntity> lm = new LambdaQueryWrapper<>();
|
|
@@ -415,38 +437,45 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
|
|
|
@Transactional
|
|
|
@Override
|
|
|
public void autoAssign(Long taskId) {
|
|
|
- // checkAfterOpenTime();
|
|
|
- // 1、未完成预约的考生
|
|
|
- LambdaQueryWrapper<StudentEntity> lm = new LambdaQueryWrapper<>();
|
|
|
- lm.eq(StudentEntity::getApplyFinished, Boolean.FALSE);
|
|
|
- List<StudentEntity> studentList = studentService.list(lm);
|
|
|
- Map<Long, List<StudentEntity>> map = studentList.stream()
|
|
|
- .collect(Collectors.groupingBy(StudentEntity::getCategoryId));
|
|
|
- // 2、考位是否充足
|
|
|
- List<TimePeriodEntity> timeList = listTimePeroid(taskId);
|
|
|
- checkTeachingCapacity(map, timeList, taskId);
|
|
|
- // 3、按照教学点安排考位。规则:不能和已预约的时间上有冲突
|
|
|
- for (Long key : map.keySet()) {
|
|
|
- List<ExamSiteEntity> siteList = listExamSite(key);
|
|
|
-
|
|
|
- List<StudentEntity> teachingStudentList = map.get(key);
|
|
|
- for (TimePeriodEntity time : timeList) {
|
|
|
- for (ExamSiteEntity site : siteList) {
|
|
|
- // 该时段已预约的考生
|
|
|
- Integer haveApplyNum = getHaveApplyNum(site.getId(), time.getId());
|
|
|
- // 剩余的考位
|
|
|
- Integer remainNum = site.getCapacity() - haveApplyNum;
|
|
|
- assignStdApply(site.getId(), time.getId(), teachingStudentList, remainNum);
|
|
|
+ checkAfterOpenTime();
|
|
|
+ try {
|
|
|
+ // 1、未完成预约的考生
|
|
|
+ LambdaQueryWrapper<StudentEntity> lm = new LambdaQueryWrapper<>();
|
|
|
+ lm.eq(StudentEntity::getApplyFinished, Boolean.FALSE);
|
|
|
+ List<StudentEntity> studentList = studentService.list(lm);
|
|
|
+ Map<Long, List<StudentEntity>> map = studentList.stream()
|
|
|
+ .collect(Collectors.groupingBy(StudentEntity::getCategoryId));
|
|
|
+ // 2、考位是否充足
|
|
|
+ List<TimePeriodEntity> timeList = listTimePeroid(taskId);
|
|
|
+ checkTeachingCapacity(map, timeList, taskId);
|
|
|
+ // 3、按照教学点安排考位。规则:不能和已预约的时间上有冲突
|
|
|
+ for (Long key : map.keySet()) {
|
|
|
+ List<ExamSiteEntity> siteList = listExamSite(key);
|
|
|
+ List<StudentEntity> teachingStudentList = map.get(key);
|
|
|
+ for (TimePeriodEntity time : timeList) {
|
|
|
+ for (ExamSiteEntity site : siteList) {
|
|
|
+ // 该时段已预约的考生
|
|
|
+ Integer haveApplyNum = getHaveApplyNum(site.getId(), time.getId());
|
|
|
+ // 剩余的考位
|
|
|
+ Integer remainNum = site.getCapacity() - haveApplyNum;
|
|
|
+ assignStudentApply(site.getId(), time.getId(), teachingStudentList, remainNum);
|
|
|
+ }
|
|
|
}
|
|
|
+ // 4、判断是否还有剩余考生未完成预约,提醒考位不够
|
|
|
+ if (teachingStudentList.size() > 0)
|
|
|
+ throw new StatusException("【" + categoryService.getById(key).getName() + "】教学点考位不足");
|
|
|
}
|
|
|
- // 4、判断是否还有剩余考生未完成预约,提醒考位不够
|
|
|
- if (teachingStudentList.size() > 0)
|
|
|
- throw new StatusException("【" + categoryService.getById(key).getName() + "】教学点考位不足");
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error(e.getMessage());
|
|
|
+ throw new StatusException(e.getMessage());
|
|
|
+ } finally {
|
|
|
+
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
- private void assignStdApply(Long siteId, Long timeId, List<StudentEntity> teachingStudentList, Integer remainNum) {
|
|
|
+ private void assignStudentApply(Long siteId, Long timeId, List<StudentEntity> teachingStudentList,
|
|
|
+ Integer remainNum) {
|
|
|
int num = 0;
|
|
|
for (Iterator<StudentEntity> iterator = teachingStudentList.iterator(); iterator.hasNext();) {
|
|
|
StudentEntity student = iterator.next();
|
|
@@ -493,9 +522,7 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
|
|
|
}
|
|
|
|
|
|
private void checkAfterOpenTime() {
|
|
|
- LambdaQueryWrapper<ApplyTaskEntity> wrapper = new LambdaQueryWrapper<ApplyTaskEntity>()
|
|
|
- .eq(ApplyTaskEntity::getEnable, Boolean.TRUE);
|
|
|
- ApplyTaskEntity task = applyTaskService.getOne(wrapper);
|
|
|
+ ApplyTaskEntity task = getApplyTask();
|
|
|
Date openEndTime = DateUtil.parse(DateUtil.getLongDateByLongTime(task.getOpenApplyEndTime()), null);
|
|
|
Date selfStartTime = DateUtil.parse(DateUtil.getLongDateByLongTime(task.getSelfApplyStartTime()), null);
|
|
|
if (!DateUtil.isBetwwen(openEndTime, selfStartTime)) {
|
|
@@ -511,8 +538,8 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
|
|
|
Integer total = siteList.stream().collect(Collectors.summingInt(ExamSiteEntity::getCapacity))
|
|
|
* timeList.size();
|
|
|
// 已经预约的数量
|
|
|
- Integer haveApplyNum = this.getBaseMapper()
|
|
|
- .getHaveApplyCount(siteList.stream().map(site -> site.getId()).collect(Collectors.toList()));
|
|
|
+ Integer haveApplyNum = this.getBaseMapper().getHaveApplyCount(
|
|
|
+ siteList.stream().map(site -> site.getId()).collect(Collectors.toList()), Boolean.FALSE);
|
|
|
// 未预约的数量
|
|
|
Integer noApplyNum = getNoApplyNum(map.get(key));
|
|
|
if (noApplyNum > total - haveApplyNum) {
|
|
@@ -560,4 +587,128 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
|
|
|
return examSiteService.list(wrapper);
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public void autoLayout(Long teachingId) {
|
|
|
+ ApplyTaskEntity applyTask = getApplyTask();
|
|
|
+ if (applyTask == null) {
|
|
|
+ log.info("没有开启的预约任务");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ boolean isSuccess = redisClient.tryLock(
|
|
|
+ CacheConstants.LOCK_ARRANGE_EXAM + DateUtil.formatShortDateString(new Date()), UUID.randomUUID(),
|
|
|
+ TIMEOUT, TimeUnit.MINUTES);
|
|
|
+ try {
|
|
|
+ if (isSuccess) {
|
|
|
+ // 1.根据当前日期,查询不能取消的时段
|
|
|
+ List<TimePeriodEntity> timePeriodList = listTimePeroid(applyTask.getId());
|
|
|
+ List<TimePeriodEntity> noCancelTimePeroidList = listNoCancelApplyTimePeroid(timePeriodList,
|
|
|
+ applyTask.getAllowApplyCancelDays());
|
|
|
+ if (noCancelTimePeroidList.isEmpty()) {
|
|
|
+ log.info("当前时间,没有取消的时段。");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 2.查询考试日期的待排考的考生
|
|
|
+ List<StudentApplyEntity> toBeLayoutStudentList = this.baseMapper.listTimePeriod(
|
|
|
+ noCancelTimePeroidList.stream().map(item -> item.getId()).collect(Collectors.toList()),
|
|
|
+ Boolean.FALSE);
|
|
|
+ // 3.开始排考
|
|
|
+ Map<Long, List<StudentApplyEntity>> toBeLayoutStudentMap = toBeLayoutStudentList.stream()
|
|
|
+ .collect(Collectors.groupingBy(StudentApplyEntity::getExamSiteId));
|
|
|
+ for (Long examSiteId : toBeLayoutStudentMap.keySet()) {
|
|
|
+ Map<Long, List<StudentApplyEntity>> timeLayoutStudentMap = toBeLayoutStudentMap.get(examSiteId)
|
|
|
+ .stream().collect(Collectors.groupingBy(StudentApplyEntity::getTimePeriodId));
|
|
|
+ List<ExamRoomEntity> roomList = listExamRoom(examSiteId);
|
|
|
+ if (roomList.isEmpty()) {
|
|
|
+ throw new StatusException(examSiteId + ":未设置考场");
|
|
|
+ }
|
|
|
+ ExamSiteEntity examSite = examSiteService.getById(examSiteId);
|
|
|
+ layoutStudentByTimePeriod(applyTask.getId(), examSite, roomList, timeLayoutStudentMap);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (StatusException e) {
|
|
|
+ log.error(e.getMessage());
|
|
|
+ e.printStackTrace();
|
|
|
+ } finally {
|
|
|
+ redisClient.delete(CacheConstants.LOCK_ARRANGE_EXAM + DateUtil.formatShortDateString(new Date()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void layoutStudentByTimePeriod(Long taskId, ExamSiteEntity examSite, List<ExamRoomEntity> roomList,
|
|
|
+ Map<Long, List<StudentApplyEntity>> timeLayoutStudentMap) {
|
|
|
+ for (Long timePeriodId : timeLayoutStudentMap.keySet()) {
|
|
|
+ List<StudentApplyEntity> studentApplyList = timeLayoutStudentMap.get(timePeriodId);
|
|
|
+ layoutStudentToRoom(taskId, examSite, roomList, studentApplyList, timePeriodService.getById(timePeriodId));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void layoutStudentToRoom(Long taskId, ExamSiteEntity examSite, List<ExamRoomEntity> roomList,
|
|
|
+ List<StudentApplyEntity> studentApplyList, TimePeriodEntity timePeriod) {
|
|
|
+ Integer timePeriodOrder = getTimePeriodOrder(taskId, timePeriod);
|
|
|
+ for (ExamRoomEntity room : roomList) {
|
|
|
+ Integer num = 0;
|
|
|
+ for (Iterator<StudentApplyEntity> iterator = studentApplyList.iterator(); iterator.hasNext();) {
|
|
|
+ StudentApplyEntity student = iterator.next();
|
|
|
+ if (num >= room.getCapacity())
|
|
|
+ break;
|
|
|
+ String seatNumber = StringUtils.leftPad(String.valueOf(++num), 3, '0');
|
|
|
+ student.setExamRoomId(room.getId());
|
|
|
+ student.setSeatNumber(seatNumber);
|
|
|
+ student.setTicketNumber(
|
|
|
+ generateTicketNumber(timePeriodOrder, examSite.getCode(), room.getCode(), seatNumber));
|
|
|
+ this.baseMapper.updateById(student);
|
|
|
+ iterator.remove();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Integer getTimePeriodOrder(Long taskId, TimePeriodEntity timePeriod) {
|
|
|
+ List<TimePeriodEntity> timeList = listTimePeroid(taskId);
|
|
|
+ List<TimePeriodEntity> sameDayTimeList = listSameDayTimePeroid(timeList, timePeriod.getStartTime());
|
|
|
+ for (int i = 0; i < sameDayTimeList.size(); i++) {
|
|
|
+ TimePeriodEntity time = sameDayTimeList.get(i);
|
|
|
+ if (time.getStartTime().equals(timePeriod.getStartTime()))
|
|
|
+ return i + 1;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<TimePeriodEntity> listSameDayTimePeroid(List<TimePeriodEntity> timeList, Long startTime) {
|
|
|
+ String day = DateUtil.getShortDateWithoutSplitByLongTime(startTime);
|
|
|
+ List<TimePeriodEntity> resultList = new ArrayList<>();
|
|
|
+ for (TimePeriodEntity time : timeList) {
|
|
|
+ if (DateUtil.getShortDateWithoutSplitByLongTime(startTime).equals(day))
|
|
|
+ resultList.add(time);
|
|
|
+ }
|
|
|
+ return resultList.stream().sorted(Comparator.comparing(TimePeriodEntity::getStartTime))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ }
|
|
|
+
|
|
|
+ private String generateTicketNumber(Integer timePeriodOrder, String examSiteCode, String roomCode,
|
|
|
+ String seatNumber) {
|
|
|
+ return DateUtil.formatShortDateString(new Date()) + timePeriodOrder + examSiteCode + roomCode + seatNumber;
|
|
|
+ }
|
|
|
+
|
|
|
+ public List<ExamRoomEntity> listExamRoom(Long examSiteId) {
|
|
|
+ LambdaQueryWrapper<ExamRoomEntity> wrapper = new LambdaQueryWrapper<>();
|
|
|
+ wrapper.eq(ExamRoomEntity::getExamSiteId, examSiteId);
|
|
|
+ wrapper.orderByAsc(ExamRoomEntity::getCode);
|
|
|
+ return examRoomService.list(wrapper);
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<TimePeriodEntity> listNoCancelApplyTimePeroid(List<TimePeriodEntity> list,
|
|
|
+ Integer allowApplyCancelDays) {
|
|
|
+ String noCancelDate = getNoCancelApplyDate(allowApplyCancelDays);
|
|
|
+ List<TimePeriodEntity> noCancelTimePeroidList = new ArrayList<>();
|
|
|
+ for (TimePeriodEntity time : list) {
|
|
|
+ if (DateUtil.getShortDateWithoutSplitByLongTime(time.getStartTime()).equals(noCancelDate))
|
|
|
+ noCancelTimePeroidList.add(time);
|
|
|
+ }
|
|
|
+ return noCancelTimePeroidList;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getNoCancelApplyDate(Integer allowApplyCancelDays) {
|
|
|
+ return DateUtil.formatShortDateString(DateUtil.addValues(DateField.DAY.getValue(), allowApplyCancelDays));
|
|
|
+ }
|
|
|
+
|
|
|
}
|