浏览代码

自动分配

haogh 1 年之前
父节点
当前提交
d3c3fd0fdf

+ 14 - 7
src/main/java/com/qmth/exam/reserve/controller/admin/StudentApplyController.java

@@ -26,6 +26,7 @@ import com.qmth.exam.reserve.bean.stdapply.CategoryVO;
 import com.qmth.exam.reserve.bean.stdapply.StudentApplyReq;
 import com.qmth.exam.reserve.bean.stdapply.StudentApplyVO;
 import com.qmth.exam.reserve.controller.BaseController;
+import com.qmth.exam.reserve.enums.Role;
 import com.qmth.exam.reserve.service.ApplyTaskService;
 import com.qmth.exam.reserve.service.CategoryService;
 import com.qmth.exam.reserve.service.ExamSiteService;
@@ -96,11 +97,10 @@ public class StudentApplyController extends BaseController {
     @PostMapping(value = "/import")
     public Map<String, Object> importPreExamStd(@ApiParam("教学点ID") @RequestParam Long teachingId,
             @RequestParam MultipartFile file) {
-        /*
-         * LoginUser user = this.curLoginUser(); if
-         * (!Role.TEACHING.equals(user.getRole())) { throw new StatusException("没有权限");
-         * }
-         */
+        LoginUser user = this.curLoginUser();
+        if (!Role.TEACHING.equals(user.getRole())) {
+            throw new StatusException("没有权限");
+        }
         List<Map<String, Object>> failRecords = new ArrayList<Map<String, Object>>();
         try {
             failRecords = studentApplyService.importPreExam(null, teachingId, file.getInputStream());
@@ -115,8 +115,15 @@ public class StudentApplyController extends BaseController {
 
     @ApiOperation(value = "一键自动分配")
     @PostMapping(value = "/std/auto/assign")
-    public void autoAssign() {
-        // TODO 只有学校管理员和第一阶段结束之后第二阶段开始之前,才可以点击
+    public void autoAssign(@ApiParam("任务ID") @RequestParam Long taskId) {
+        if (taskId == null) {
+            throw new StatusException("请选择预约任务");
+        }
+        // LoginUser user = this.curLoginUser();
+        // if (!Role.ADMIN.equals(user.getRole())) {
+        // throw new StatusException("没有权限");
+        // }
+        studentApplyService.autoAssign(taskId);
     }
 
     @ApiOperation(value = "打印签到表")

+ 4 - 0
src/main/java/com/qmth/exam/reserve/dao/StudentApplyDao.java

@@ -1,5 +1,7 @@
 package com.qmth.exam.reserve.dao;
 
+import java.util.List;
+
 import org.apache.ibatis.annotations.Param;
 
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
@@ -12,4 +14,6 @@ import com.qmth.exam.reserve.entity.StudentApplyEntity;
 public interface StudentApplyDao extends BaseMapper<StudentApplyEntity> {
 
     IPage<StudentApplyVO> page(Page<StudentApplyVO> page, @Param(value = "req") StudentApplyReq req);
+
+    Integer getHaveApplyCount(@Param(value = "examSiteIds") List<Long> examSiteIds);
 }

+ 2 - 4
src/main/java/com/qmth/exam/reserve/enums/CategoryLevel.java

@@ -7,11 +7,9 @@ import lombok.Getter;
 @AllArgsConstructor
 public enum CategoryLevel {
 
-    SCHOOL(1, "第一层级"),
+    CITY(1, "第一层级"),
 
-    CITY(2, "第二层级"),
-
-    TEACHING(3, "第三层级");
+    TEACHING(2, "第二层级");
 
     private Integer value;
     private String name;

+ 3 - 1
src/main/java/com/qmth/exam/reserve/enums/EventType.java

@@ -7,7 +7,9 @@ import lombok.Getter;
 @AllArgsConstructor
 public enum EventType {
 
-    CANCEL("取消预约");
+    CANCEL_APPLY("取消预约"),
+
+    DELETE_APPLY("删除预约");
 
     private String name;
 

+ 2 - 0
src/main/java/com/qmth/exam/reserve/service/OperateLogService.java

@@ -2,7 +2,9 @@ package com.qmth.exam.reserve.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.qmth.exam.reserve.entity.OperateLogEntity;
+import com.qmth.exam.reserve.enums.EventType;
 
 public interface OperateLogService extends IService<OperateLogEntity> {
 
+    void insertOperateLog(Long operateId, EventType eventType, String content);
 }

+ 2 - 0
src/main/java/com/qmth/exam/reserve/service/StudentApplyService.java

@@ -19,4 +19,6 @@ public interface StudentApplyService extends IService<StudentApplyEntity> {
 
     List<Map<String, Object>> importPreExam(LoginUser user, Long teachingId, InputStream inputStream);
 
+    void autoAssign(Long taskId);
+
 }

+ 15 - 3
src/main/java/com/qmth/exam/reserve/service/impl/OperateLogServiceImpl.java

@@ -1,17 +1,29 @@
 package com.qmth.exam.reserve.service.impl;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.qmth.exam.reserve.dao.OperateLogDao;
 import com.qmth.exam.reserve.entity.OperateLogEntity;
+import com.qmth.exam.reserve.enums.EventType;
 import com.qmth.exam.reserve.service.OperateLogService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Service;
 
 @Service
 public class OperateLogServiceImpl extends ServiceImpl<OperateLogDao, OperateLogEntity> implements OperateLogService {
 
     private static final Logger log = LoggerFactory.getLogger(OperateLogServiceImpl.class);
 
+    @Override
+    public void insertOperateLog(Long operateId, EventType eventType, String content) {
+        OperateLogEntity operateLog = new OperateLogEntity();
+        operateLog.setCreateTime(System.currentTimeMillis());
+        operateLog.setUpdateTime(System.currentTimeMillis());
+        operateLog.setOperateId(operateId);
+        operateLog.setEventType(eventType.toString());
+        operateLog.setContent(content);
+        this.baseMapper.insert(operateLog);
+    }
 
 }

+ 197 - 27
src/main/java/com/qmth/exam/reserve/service/impl/StudentApplyServiceImpl.java

@@ -5,6 +5,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.stream.Collectors;
@@ -30,23 +31,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.dao.ApplyTaskDao;
-import com.qmth.exam.reserve.dao.OperateLogDao;
 import com.qmth.exam.reserve.dao.StudentApplyDao;
-import com.qmth.exam.reserve.dao.TimePeriodDao;
 import com.qmth.exam.reserve.entity.ApplyTaskEntity;
 import com.qmth.exam.reserve.entity.CategoryEntity;
 import com.qmth.exam.reserve.entity.ExamSiteEntity;
-import com.qmth.exam.reserve.entity.OperateLogEntity;
 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.EventType;
+import com.qmth.exam.reserve.service.ApplyTaskService;
 import com.qmth.exam.reserve.service.CategoryService;
 import com.qmth.exam.reserve.service.ExamSiteService;
+import com.qmth.exam.reserve.service.OperateLogService;
 import com.qmth.exam.reserve.service.StudentApplyService;
 import com.qmth.exam.reserve.service.StudentService;
+import com.qmth.exam.reserve.service.TimePeriodService;
 import com.qmth.exam.reserve.util.DateUtil;
 import com.qmth.exam.reserve.util.JsonHelper;
 import com.qmth.exam.reserve.util.PageUtil;
@@ -59,13 +59,10 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
             "预约时段2", "预约考点3", "预约时段3", "预约考点4", "预约时段4" };
 
     @Autowired
-    private TimePeriodDao timePeriodDao;
+    private TimePeriodService timePeriodService;
 
     @Autowired
-    private ApplyTaskDao applyTaskDao;
-
-    @Autowired
-    private OperateLogDao operateLogDao;
+    private ApplyTaskService applyTaskService;
 
     @Autowired
     private CategoryService categoryService;
@@ -76,6 +73,9 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
     @Autowired
     private ExamSiteService examSiteService;
 
+    @Autowired
+    private OperateLogService operateLogService;
+
     @Override
     public PageResult<StudentApplyVO> page(StudentApplyReq req) {
         IPage<StudentApplyVO> iPage = this.baseMapper
@@ -90,12 +90,12 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
         StudentApplyEntity studentApply = this.baseMapper.selectById(id);
         if (studentApply == null || studentApply.getTimePeriodId() == null)
             throw new StatusException("考生没有预约,无法取消!");
-        TimePeriodEntity timePeroid = this.timePeriodDao.selectById(studentApply.getTimePeriodId());
+        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 = applyTaskDao.selectOne(wrapper);
+        ApplyTaskEntity task = applyTaskService.getOne(wrapper);
         Date applyDate = DateUtil.parse(DateUtil.getShortDateByLongTime(timePeroid.getStartTime()), "yyyy-MM-dd");
         Date canCancelDay = DateUtil.addValues(applyDate, 5, -task.getAllowApplyCancelDays());
         if (new Date().after(canCancelDay))
@@ -103,21 +103,13 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
         studentApply.setCancel(Boolean.TRUE);
         this.baseMapper.updateById(studentApply);
         // TODO redis更新:该时段redis已预约的数量减1
-
-        // 写入日志
-        OperateLogEntity log = new OperateLogEntity();
-        log.setCreateTime(System.currentTimeMillis());
-        log.setUpdateTime(System.currentTimeMillis());
-        log.setOperateId(user.getId());
-        log.setEventType(EventType.CANCEL.toString());
-        log.setContent(JsonHelper.toJson(studentApply));
-        operateLogDao.insert(log);
+        operateLogService.insertOperateLog(user.getId(), EventType.CANCEL_APPLY, JsonHelper.toJson(studentApply));
     }
 
     @Transactional
     @Override
     public List<Map<String, Object>> importPreExam(LoginUser user, Long teachingId, InputStream inputStream) {
-        checkInTime();
+        checkInOpenTime();
         List<DataMap> lineList = null;
         ExcelReader reader = ExcelReader.create(ExcelType.XLSX, inputStream, 0);
         try {
@@ -282,7 +274,7 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
         for (int i = 0; i < applyList.size(); i++) {
             StudentImportVO vo = applyList.get(i);
             try {
-                saveStdApply(vo);
+                saveStdApply(user.getId(), vo);
             } catch (StatusException e) {
                 failRecords.add(newError(i + 1, e.getMessage()));
             } catch (Exception e) {
@@ -293,23 +285,44 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
         if (CollectionUtils.isNotEmpty(failRecords)) {
             TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
         }
+        if (CollectionUtils.isEmpty(failRecords)) {
+            new Thread(() -> {
+                checkStudentApplyFinish(applyList);
+            }).start();
+        }
         // TODO 更新redis
         return failRecords;
 
     }
 
-    private void checkInTime() {
+    private void checkStudentApplyFinish(List<StudentImportVO> applyList) {
+        for (StudentImportVO importVO : applyList) {
+            StudentEntity student = studentService.getById(importVO.getStudentId());
+            List<StudentApplyEntity> haveApplyList = listStudentApply(importVO.getStudentId(), Boolean.TRUE);
+            if (student.getApplyNumber().intValue() == haveApplyList.size()) {
+                // 更新考生的完成状态
+                student.setApplyFinished(Boolean.TRUE);
+                studentService.updateById(student);
+            }
+        }
+    }
+
+    private void checkInOpenTime() {
         LambdaQueryWrapper<ApplyTaskEntity> wrapper = new LambdaQueryWrapper<ApplyTaskEntity>()
                 .eq(ApplyTaskEntity::getEnable, Boolean.TRUE);
-        ApplyTaskEntity task = applyTaskDao.selectOne(wrapper);
+        ApplyTaskEntity task = applyTaskService.getOne(wrapper);
         Date start = DateUtil.parse(DateUtil.getLongDateByLongTime(task.getOpenApplyStartTime()), null);
-        // DateUtil.isBetwwen(start, end)
+        Date end = DateUtil.parse(DateUtil.getLongDateByLongTime(task.getOpenApplyEndTime()), null);
+        if (!DateUtil.isBetwwen(start, end)) {
+            throw new StatusException("导入预考,必须要在第一阶段导入!");
+        }
     }
 
-    private void saveStdApply(StudentImportVO vo) {
+    private void saveStdApply(Long userId, StudentImportVO vo) {
         List<AgentAndTimeVO> agentTimeList = vo.getAgentTimeList();
         LambdaQueryWrapper<StudentApplyEntity> lm = new LambdaQueryWrapper<>();
         lm.eq(StudentApplyEntity::getStudentId, vo.getStudentId());
+        LogStdApply(userId, this.baseMapper.selectList(lm));
         this.baseMapper.delete(lm);
         for (AgentAndTimeVO agentTime : agentTimeList) {
             StudentApplyEntity entity = new StudentApplyEntity();
@@ -323,6 +336,15 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
         }
     }
 
+    private void LogStdApply(Long userId, List<StudentApplyEntity> existList) {
+        if (!existList.isEmpty()) {
+            for (StudentApplyEntity studentApply : existList) {
+                this.operateLogService.insertOperateLog(userId, EventType.DELETE_APPLY,
+                        JsonHelper.toJson(studentApply));
+            }
+        }
+    }
+
     private Long checkTimePeriod(String timePeriod, Map<String, Long> timeCache) {
         if (timePeriod.split(" ").length != 2) {
             throw new StatusException(" 预约时段格式不正确");
@@ -341,7 +363,7 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
     private Map<String, Long> getTimePeriodCache() {
         Map<String, Long> map = new HashMap<>();
         LambdaQueryWrapper<TimePeriodEntity> lm = new LambdaQueryWrapper<>();
-        List<TimePeriodEntity> timeList = timePeriodDao.selectList(lm);
+        List<TimePeriodEntity> timeList = timePeriodService.list(lm);
         for (TimePeriodEntity time : timeList) {
             map.put(time.getStartTime() + "-" + time.getEndTime(), time.getId());
         }
@@ -390,4 +412,152 @@ public class StudentApplyServiceImpl extends ServiceImpl<StudentApplyDao, Studen
         return s.trim();
     }
 
+    @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);
+                }
+            }
+            // 4、判断是否还有剩余考生未完成预约,提醒考位不够
+            if (teachingStudentList.size() > 0)
+                throw new StatusException("【" + categoryService.getById(key).getName() + "】教学点考位不足");
+        }
+
+    }
+
+    private void assignStdApply(Long siteId, Long timeId, List<StudentEntity> teachingStudentList, Integer remainNum) {
+        int num = 0;
+        for (Iterator<StudentEntity> iterator = teachingStudentList.iterator(); iterator.hasNext();) {
+            StudentEntity student = iterator.next();
+            if (num >= remainNum)
+                break;
+            List<StudentApplyEntity> studentApplyList = listStudentApply(student.getId(), Boolean.FALSE);
+            int toApplyNum = student.getApplyNumber() - studentApplyList.size();
+            if (toApplyNum > 0 && !haveApplySameTimePeriod(siteId, timeId, student.getId())) {
+                StudentApplyEntity studentApply = new StudentApplyEntity();
+                studentApply.setCreateTime(System.currentTimeMillis());
+                studentApply.setUpdateTime(System.currentTimeMillis());
+                studentApply.setStudentId(student.getId());
+                studentApply.setExamSiteId(siteId);
+                studentApply.setCancel(Boolean.FALSE);
+                studentApply.setTimePeriodId(timeId);
+                baseMapper.insert(studentApply);
+                num++;
+                if (toApplyNum - (studentApplyList.size() + 1) == 0) {
+                    iterator.remove();
+                    student.setApplyFinished(true);
+                    this.studentService.updateById(student);
+                }
+            }
+        }
+    }
+
+    private boolean haveApplySameTimePeriod(Long siteId, Long timeId, Long studentId) {
+        LambdaQueryWrapper<StudentApplyEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StudentApplyEntity::getExamSiteId, siteId);
+        wrapper.eq(StudentApplyEntity::getTimePeriodId, timeId);
+        wrapper.eq(StudentApplyEntity::getStudentId, studentId);
+        wrapper.eq(StudentApplyEntity::getCancel, Boolean.FALSE);
+        StudentApplyEntity studentApply = baseMapper.selectOne(wrapper);
+        return studentApply == null ? false : true;
+    }
+
+    private Integer getHaveApplyNum(Long siteId, Long timeId) {
+        LambdaQueryWrapper<StudentApplyEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StudentApplyEntity::getExamSiteId, siteId);
+        wrapper.eq(StudentApplyEntity::getTimePeriodId, timeId);
+        wrapper.eq(StudentApplyEntity::getCancel, Boolean.FALSE);
+        List<StudentApplyEntity> haveAplyList = baseMapper.selectList(wrapper);
+        return haveAplyList == null ? 0 : haveAplyList.size();
+    }
+
+    private void checkAfterOpenTime() {
+        LambdaQueryWrapper<ApplyTaskEntity> wrapper = new LambdaQueryWrapper<ApplyTaskEntity>()
+                .eq(ApplyTaskEntity::getEnable, Boolean.TRUE);
+        ApplyTaskEntity task = applyTaskService.getOne(wrapper);
+        Date openEndTime = DateUtil.parse(DateUtil.getLongDateByLongTime(task.getOpenApplyEndTime()), null);
+        Date selfStartTime = DateUtil.parse(DateUtil.getLongDateByLongTime(task.getSelfApplyStartTime()), null);
+        if (!DateUtil.isBetwwen(openEndTime, selfStartTime)) {
+            throw new StatusException("自动分配,时间必须要在第一阶段结束之后,第三阶段开始之前");
+        }
+    }
+
+    private void checkTeachingCapacity(Map<Long, List<StudentEntity>> map, List<TimePeriodEntity> timeList,
+            Long taskId) {
+        for (Long key : map.keySet()) {
+            List<ExamSiteEntity> siteList = listExamSite(key);
+            // 总考位数量
+            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 noApplyNum = getNoApplyNum(map.get(key));
+            if (noApplyNum > total - haveApplyNum) {
+                CategoryEntity category = categoryService.getById(key);
+                throw new StatusException("【" + category.getName() + "】教学点考位不足!剩余的考位数量:【" + (total - haveApplyNum)
+                        + "】,实际需要的考位数量:【" + noApplyNum + "】");
+            }
+        }
+    }
+
+    private Integer getNoApplyNum(List<StudentEntity> list) {
+        Integer noApplyNum = 0;
+        for (StudentEntity student : list) {
+            if (student.getApplyNumber().intValue() == 1) {
+                noApplyNum++;
+            } else if (student.getApplyNumber().intValue() > 1) {
+                noApplyNum = noApplyNum
+                        + (student.getApplyNumber() - listStudentApply(student.getId(), Boolean.TRUE).size());
+            }
+        }
+        return noApplyNum;
+    }
+
+    private List<StudentApplyEntity> listStudentApply(Long stdId, Boolean cancel) {
+        LambdaQueryWrapper<StudentApplyEntity> lm = new LambdaQueryWrapper<>();
+        lm.eq(StudentApplyEntity::getStudentId, stdId);
+        lm.eq(StudentApplyEntity::getCancel, cancel);
+        return this.baseMapper.selectList(lm);
+    }
+
+    private List<TimePeriodEntity> listTimePeroid(Long taskId) {
+        LambdaQueryWrapper<TimePeriodEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(TimePeriodEntity::getApplyTaskId, taskId);
+        wrapper.orderByAsc(TimePeriodEntity::getStartTime);
+        List<TimePeriodEntity> timeList = timePeriodService.list(wrapper);
+        if (timeList.isEmpty()) {
+            throw new StatusException("考试时段未设置");
+        }
+        return timeList;
+    }
+
+    private List<ExamSiteEntity> listExamSite(Long categoryId) {
+        LambdaQueryWrapper<ExamSiteEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(ExamSiteEntity::getCategoryId, categoryId);
+        return examSiteService.list(wrapper);
+    }
+
 }

+ 2 - 0
src/main/java/com/qmth/exam/reserve/util/DateUtil.java

@@ -106,6 +106,8 @@ public class DateUtil {
     }
 
     public static Date parse(String source, String pattern) {
+        if (StringUtils.isEmpty(pattern))
+            pattern = LongDateString;
         if (StringUtils.isEmpty(source))
             return null;
         SimpleDateFormat sdf = getFormatter(pattern);

+ 7 - 0
src/main/resources/mapper/StudentApplyMapper.xml

@@ -37,4 +37,11 @@
 		</if>
 		order by a.id
 	</select>
+	
+	<select id="getHaveApplyCount" resultType="int">
+		select count(sa.id) from t_student_apply sa, t_exam_site s where sa.exam_site_id=s.id and sa.cancel=1 and sa.exam_site_id in
+		<foreach collection="examSiteIds" item="item" index="index" separator="," open="(" close=")">
+			#{item}
+		</foreach>
+	</select>
 </mapper>