package jnpf.tmsService.impl;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
import jnpf.exception.DataException;
|
import jnpf.tmsEntity.TmsEvalModeEntity;
|
import jnpf.tmsEntity.TmsExamEntity;
|
import jnpf.tmsEntity.TmsMaterialEntity;
|
import jnpf.tmsEntity.TmsPaperEntity;
|
import jnpf.tmsEntity.TmsPersonTaskEntity;
|
import jnpf.tmsEntity.TmsQuizScoreEntity;
|
import jnpf.tmsEntity.TmsRecordEntity;
|
import jnpf.tmsEntity.TmsRecordSignEntity;
|
import jnpf.tmsEntity.TmsTaskEntity;
|
import jnpf.tmsEntity.TmsTaskFileEntity;
|
import jnpf.tmsEntity.TmsTaskQuizEntity;
|
import jnpf.tmsEntity.TmsSignModeEntity;
|
import jnpf.tmsEntity.TmsTrainModeEntity;
|
import jnpf.tmsMapper.TmsEvalModeMapper;
|
import jnpf.tmsMapper.TmsExamMapper;
|
import jnpf.tmsMapper.TmsPaperMapper;
|
import jnpf.tmsMapper.TmsQuizScoreMapper;
|
import jnpf.tmsMapper.TmsRecordMapper;
|
import jnpf.tmsMapper.TmsRecordSignMapper;
|
import jnpf.tmsEntity.person.TmsPersonTaskFileForm;
|
import jnpf.tmsEntity.person.TmsPersonTaskLearnResult;
|
import jnpf.tmsEntity.person.TmsPersonTaskMineForm;
|
import jnpf.tmsEntity.person.TmsPersonTaskQuery;
|
import jnpf.tmsEntity.person.TmsPersonTaskSignResult;
|
import jnpf.tmsEntity.person.TmsQuizAnswerForm;
|
import jnpf.tmsEntity.person.TmsQuizQuestionVO;
|
import jnpf.tmsEntity.person.TmsQuizSubmitResult;
|
import jnpf.tmsEntity.record.TmsQuizAnswerPaperVO;
|
import jnpf.tmsMapper.TmsMaterialMapper;
|
import jnpf.tmsMapper.TmsPersonTaskMapper;
|
import jnpf.tmsMapper.TmsSignModeMapper;
|
import jnpf.tmsMapper.TmsTaskFileMapper;
|
import jnpf.tmsMapper.TmsTaskMapper;
|
import jnpf.tmsMapper.TmsTaskQuizMapper;
|
import jnpf.tmsMapper.TmsTrainModeMapper;
|
import jnpf.tmsService.TmsPersonTaskService;
|
import jnpf.tmsService.TmsRecordService;
|
import jnpf.tmsEntity.enums.TmsPassFlag;
|
import jnpf.tmsEntity.enums.TmsPersonTaskStatus;
|
import jnpf.tmsEntity.enums.TmsSignMode;
|
import jnpf.tmsEntity.enums.TmsQrType;
|
import jnpf.tmsEntity.enums.TmsExamStatus;
|
import jnpf.tmsEntity.enums.TmsTaskPublishStatus;
|
import jnpf.tmsEntity.enums.TmsArchiveStatus;
|
import jnpf.tmsEntity.enums.TmsEvalMode;
|
import jnpf.tmsEntity.enums.TmsSignRule;
|
import jnpf.tmsEntity.enums.TmsTrainMode;
|
import jnpf.tmsUtil.TmsQuizHelper;
|
import jnpf.util.DateUtil;
|
import jnpf.util.RandomUtil;
|
import jnpf.util.StringUtil;
|
import jnpf.util.UserProvider;
|
import lombok.RequiredArgsConstructor;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.context.annotation.Lazy;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.Comparator;
|
import java.util.Date;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Set;
|
import java.util.stream.Collectors;
|
|
/**
|
* 学员端个人培训任务:我的列表、学完、签到/扫码签到、考试结果回写。
|
*
|
* <p>不负责写入个人档案培训目录;目录仅在培训记录归档时由 {@link TmsRecordServiceImpl} 写入。</p>
|
* <p>考试交卷后通过 {@link #writeBackExamResult} 更新本表成绩与合格标记。</p>
|
*/
|
@Service
|
@RequiredArgsConstructor
|
public class TmsPersonTaskServiceImpl implements TmsPersonTaskService {
|
|
private static final String FMT = "yyyy-MM-dd HH:mm:ss";
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
|
private final TmsPersonTaskMapper personTaskMapper;
|
private final TmsTaskMapper taskMapper;
|
private final TmsTaskFileMapper taskFileMapper;
|
private final TmsMaterialMapper materialMapper;
|
private final TmsTrainModeMapper trainModeMapper;
|
private final TmsSignModeMapper signModeMapper;
|
private final TmsRecordMapper recordMapper;
|
private final TmsRecordSignMapper recordSignMapper;
|
private final TmsExamMapper examMapper;
|
private final TmsPaperMapper paperMapper;
|
private final TmsTaskQuizMapper taskQuizMapper;
|
private final TmsQuizScoreMapper quizScoreMapper;
|
private final TmsEvalModeMapper evalModeMapper;
|
|
@Lazy
|
@Autowired
|
private TmsRecordService recordService;
|
|
/**
|
* 查询我的培训任务列表
|
*/
|
@Override
|
public List<TmsPersonTaskMineForm> getMine(TmsPersonTaskQuery query) {
|
String userId = UserProvider.getLoginUserId();
|
LambdaQueryWrapper<TmsPersonTaskEntity> qw = new LambdaQueryWrapper<>();
|
qw.eq(TmsPersonTaskEntity::getUserId, userId);
|
if (StringUtil.isNotEmpty(query.getBizStatus())) {
|
qw.eq(TmsPersonTaskEntity::getBizStatus, query.getBizStatus().trim());
|
} else {
|
qw.ne(TmsPersonTaskEntity::getBizStatus, TmsPersonTaskStatus.CANCELLED.getCode());
|
}
|
if (StringUtil.isNotEmpty(query.getTaskNo())) {
|
qw.like(TmsPersonTaskEntity::getTaskNo, query.getTaskNo().trim());
|
}
|
if ("1".equals(query.getSigned())) {
|
qw.isNotNull(TmsPersonTaskEntity::getSignTime);
|
} else if ("0".equals(query.getSigned())) {
|
qw.isNull(TmsPersonTaskEntity::getSignTime);
|
}
|
qw.orderByDesc(TmsPersonTaskEntity::getCreatorTime);
|
List<TmsPersonTaskEntity> persons = personTaskMapper.selectList(qw);
|
|
Map<String, TmsTaskEntity> taskMap = loadTasks(persons);
|
String keyword = StringUtil.isNotEmpty(query.getKeyword()) ? query.getKeyword().trim() : null;
|
String trainMode = StringUtil.isNotEmpty(query.getTrainMode()) ? query.getTrainMode().trim() : null;
|
String evalMode = StringUtil.isNotEmpty(query.getEvalMode()) ? query.getEvalMode().trim() : null;
|
String category = StringUtil.isNotEmpty(query.getCategory()) ? query.getCategory().trim() : null;
|
Date startFrom = parseDayStart(query.getStartTimeFrom());
|
Date startTo = parseDayEnd(query.getStartTimeTo());
|
List<TmsPersonTaskMineForm> all = new ArrayList<>();
|
for (TmsPersonTaskEntity person : persons) {
|
TmsTaskEntity task = taskMap.get(person.getTaskId());
|
if (task == null || !TmsTaskPublishStatus.PUBLISHED.equalsCode(task.getPublishStatus())) {
|
continue;
|
}
|
if (keyword != null && !matchKeyword(person, task, keyword)) {
|
continue;
|
}
|
if (trainMode != null && !trainMode.equals(task.getTrainMode())) {
|
continue;
|
}
|
if (evalMode != null && !evalMode.equals(task.getEvalMode())) {
|
continue;
|
}
|
if (category != null && !category.equals(task.getCategory())) {
|
continue;
|
}
|
if (startFrom != null && (task.getStartTime() == null || task.getStartTime().before(startFrom))) {
|
continue;
|
}
|
if (startTo != null && (task.getStartTime() == null || task.getStartTime().after(startTo))) {
|
continue;
|
}
|
if (!matchPassFlag(query.getPassFlag(), person.getPassFlag())) {
|
continue;
|
}
|
TmsPersonTaskMineForm form = toForm(person, task, false);
|
if (!matchYesNo(query.getCanExam(), Boolean.TRUE.equals(form.getCanExam()))) {
|
continue;
|
}
|
all.add(form);
|
}
|
all.sort(Comparator.comparing(TmsPersonTaskMineForm::getStartTime,
|
Comparator.nullsLast(Comparator.reverseOrder())));
|
|
long current = query.getCurrentPage() > 0 ? query.getCurrentPage() : 1;
|
long size = query.getPageSize() > 0 ? query.getPageSize() : 20;
|
int from = (int) Math.min((current - 1) * size, all.size());
|
int to = (int) Math.min(from + size, all.size());
|
List<TmsPersonTaskMineForm> page = all.subList(from, to);
|
query.setData(page, all.size());
|
return page;
|
}
|
/**
|
* 个人培训任务:按主键查询详情
|
*/
|
@Override
|
public TmsPersonTaskMineForm getInfo(String id) {
|
TmsPersonTaskEntity person = requireMine(id);
|
TmsTaskEntity task = taskMapper.selectById(person.getTaskId());
|
if (task == null) {
|
throw new DataException("培训任务不存在");
|
}
|
TmsPersonTaskMineForm form = toForm(person, task, true);
|
form.setFiles(loadFiles(task.getId()));
|
return form;
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
/**
|
* 标记学习进度/完成
|
*/
|
public TmsPersonTaskLearnResult markLearned(String id, Integer seconds) {
|
TmsPersonTaskEntity person = requireMine(id);
|
TmsTaskEntity task = taskMapper.selectById(person.getTaskId());
|
if (task == null) {
|
throw new DataException("培训任务不存在");
|
}
|
if (person.getSignTime() == null) {
|
throw new DataException("请先签到后再学习");
|
}
|
|
int before = person.getLearnSeconds() == null ? 0 : person.getLearnSeconds();
|
int required = toRequiredSeconds(person.getHours());
|
boolean alreadyFull = required > 0 && before >= required;
|
|
int add = seconds == null ? 0 : Math.max(0, seconds);
|
if (add > 0) {
|
person.setLearnSeconds(before + add);
|
person.setLastModifyUserId(UserProvider.getLoginUserId());
|
person.setLastModifyTime(new Date());
|
personTaskMapper.updateById(person);
|
}
|
|
int learned = person.getLearnSeconds() == null ? 0 : person.getLearnSeconds();
|
boolean justFull = required > 0 && !alreadyFull && learned >= required;
|
|
TmsPersonTaskLearnResult result = new TmsPersonTaskLearnResult();
|
result.setLearnedSeconds(learned);
|
result.setRequiredSeconds(required);
|
result.setLearnStatus(resolveLearnStatus(learned, required));
|
result.setBizStatus(person.getBizStatus());
|
result.setAutoCompleted(false);
|
result.setMessage("");
|
|
boolean needExam = TmsEvalMode.EXAM.equalsCode(task.getEvalMode()) && StringUtil.isNotEmpty(task.getPaperId());
|
if (justFull) {
|
if (!needExam && !TmsPersonTaskStatus.DONE.equalsCode(person.getBizStatus())
|
&& !TmsPersonTaskStatus.CANCELLED.equalsCode(person.getBizStatus())) {
|
person.setBizStatus(TmsPersonTaskStatus.DONE.getCode());
|
person.setLastModifyUserId(UserProvider.getLoginUserId());
|
person.setLastModifyTime(new Date());
|
personTaskMapper.updateById(person);
|
result.setAutoCompleted(true);
|
result.setBizStatus(TmsPersonTaskStatus.DONE.getCode());
|
result.setMessage("已学满规定课时,系统已自动确认完成");
|
} else if (needExam) {
|
ensurePendingExam(person, task);
|
result.setMessage("已学满规定课时,待考试卷已生成,可以进入考试");
|
}
|
}
|
|
TmsPersonTaskMineForm tmp = new TmsPersonTaskMineForm();
|
fillExamFlag(tmp, task, person);
|
if (Boolean.TRUE.equals(tmp.getCanExam()) && needExam) {
|
ensurePendingExam(person, task);
|
}
|
result.setCanExam(Boolean.TRUE.equals(tmp.getCanExam()));
|
result.setExamTip(tmp.getExamTip());
|
return result;
|
}
|
|
/**
|
* 确保存在待考答卷壳
|
*/
|
private void ensurePendingExam(TmsPersonTaskEntity person, TmsTaskEntity task) {
|
if (person == null || task == null || StringUtil.isEmpty(task.getPaperId())) {
|
return;
|
}
|
if (TmsPersonTaskStatus.CANCELLED.equalsCode(person.getBizStatus())) {
|
return;
|
}
|
Long exists = examMapper.selectCount(new LambdaQueryWrapper<TmsExamEntity>()
|
.eq(TmsExamEntity::getPersonTaskId, person.getId())
|
.eq(TmsExamEntity::getPaperId, task.getPaperId())
|
.and(w -> w.isNull(TmsExamEntity::getExamStatus)
|
.or().eq(TmsExamEntity::getExamStatus, "")
|
.or().eq(TmsExamEntity::getExamStatus, TmsExamStatus.NOT_STARTED.getCode())
|
.or().eq(TmsExamEntity::getExamStatus, TmsExamStatus.DOING.getCode())
|
.or().eq(TmsExamEntity::getExamStatus, TmsExamStatus.SUBMITTED.getCode())));
|
if (exists != null && exists > 0) {
|
// 已有答卷:把仍为空的状态补成 notStarted,便于考试记录显示「待考」
|
examMapper.update(null, new LambdaUpdateWrapper<TmsExamEntity>()
|
.eq(TmsExamEntity::getPersonTaskId, person.getId())
|
.eq(TmsExamEntity::getPaperId, task.getPaperId())
|
.and(w -> w.isNull(TmsExamEntity::getExamStatus)
|
.or().eq(TmsExamEntity::getExamStatus, ""))
|
.set(TmsExamEntity::getExamStatus, TmsExamStatus.NOT_STARTED.getCode()));
|
return;
|
}
|
|
String recordId = null;
|
TmsRecordEntity record = recordMapper.selectOne(new LambdaQueryWrapper<TmsRecordEntity>()
|
.eq(TmsRecordEntity::getTaskId, task.getId())
|
.last("limit 1"));
|
if (record != null) {
|
recordId = record.getId();
|
}
|
TmsPaperEntity paper = paperMapper.selectById(task.getPaperId());
|
BigDecimal passScore = task.getPassScore();
|
if (passScore == null && paper != null) {
|
passScore = paper.getPassScore();
|
}
|
Date now = new Date();
|
TmsExamEntity exam = new TmsExamEntity();
|
exam.setId(RandomUtil.uuId());
|
exam.setTaskId(task.getId());
|
exam.setPersonTaskId(person.getId());
|
exam.setRecordId(recordId);
|
exam.setPaperId(task.getPaperId());
|
exam.setPaperName(StringUtil.isNotEmpty(task.getPaperName())
|
? task.getPaperName()
|
: (paper == null ? null : paper.getPaperName()));
|
exam.setUserId(person.getUserId());
|
exam.setAttemptNo(1);
|
exam.setExamStatus(TmsExamStatus.NOT_STARTED.getCode());
|
exam.setPassScore(passScore);
|
exam.setDurationMin(paper == null ? null : paper.getDurationMin());
|
exam.setCreatorUserId(UserProvider.getLoginUserId());
|
exam.setCreatorTime(now);
|
examMapper.insert(exam);
|
}
|
/**
|
* 学员签到
|
*/
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public TmsPersonTaskSignResult sign(String id) {
|
TmsPersonTaskEntity person = requireMine(id);
|
TmsTaskEntity task = taskMapper.selectById(person.getTaskId());
|
if (task == null) {
|
throw new DataException("培训任务不存在");
|
}
|
if (!TmsTaskPublishStatus.PUBLISHED.equalsCode(task.getPublishStatus())) {
|
throw new DataException("任务未发布,不可签到");
|
}
|
if (TmsPersonTaskStatus.CANCELLED.equalsCode(person.getBizStatus())) {
|
throw new DataException("任务已取消,不可签到");
|
}
|
if (person.getSignTime() != null) {
|
throw new DataException("您已签到,无需重复签到");
|
}
|
String deny = resolveSignDenyReason(task, person);
|
if (StringUtil.isNotEmpty(deny)) {
|
throw new DataException(deny);
|
}
|
|
Date now = new Date();
|
person.setSignTime(now);
|
person.setSignMode(TmsSignMode.ONLINE.getCode());
|
if (TmsPersonTaskStatus.TODO.equalsCode(person.getBizStatus()) || StringUtil.isEmpty(person.getBizStatus())) {
|
person.setBizStatus(TmsPersonTaskStatus.SIGNED.getCode());
|
}
|
person.setLastModifyUserId(UserProvider.getLoginUserId());
|
person.setLastModifyTime(now);
|
personTaskMapper.updateById(person);
|
upsertRecordSign(task.getId(), person, TmsSignMode.ONLINE.getCode(), now);
|
refreshRecordRates(task.getId());
|
|
TmsPersonTaskSignResult result = new TmsPersonTaskSignResult();
|
result.setSignTime(formatDate(now));
|
result.setBizStatus(person.getBizStatus());
|
result.setSigned(true);
|
result.setCanSign(false);
|
result.setSignTip("已签到");
|
result.setMessage("签到成功");
|
TmsPersonTaskMineForm tmp = new TmsPersonTaskMineForm();
|
fillExamFlag(tmp, task, person);
|
if (Boolean.TRUE.equals(tmp.getCanExam())
|
&& TmsEvalMode.EXAM.equalsCode(task.getEvalMode())
|
&& StringUtil.isNotEmpty(task.getPaperId())) {
|
ensurePendingExam(person, task);
|
}
|
result.setCanExam(Boolean.TRUE.equals(tmp.getCanExam()));
|
result.setExamTip(tmp.getExamTip());
|
result.setPersonTaskId(person.getId());
|
fillQuizOnSignResult(result, task, person);
|
return result;
|
}
|
/**
|
* 扫码签到
|
*/
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public TmsPersonTaskSignResult scanSign(String qrContent, String signMode) {
|
Map<String, Object> payload = parseSignQr(qrContent);
|
String type = stringVal(payload.get("t"));
|
if (!TmsQrType.SIGN.equalsCode(type)) {
|
throw new DataException("无效的签到二维码");
|
}
|
long expMs = toLong(payload.get("exp"));
|
if (expMs <= 0 || System.currentTimeMillis() > expMs) {
|
throw new DataException("签到二维码已过期,请刷新后重扫");
|
}
|
String recordId = stringVal(payload.get("recordId"));
|
String taskId = stringVal(payload.get("taskId"));
|
if (StringUtil.isEmpty(recordId)) {
|
throw new DataException("签到二维码缺少培训记录");
|
}
|
|
TmsRecordEntity record = recordMapper.selectById(recordId);
|
if (record == null) {
|
throw new DataException("培训记录不存在");
|
}
|
if (TmsArchiveStatus.ARCHIVED.equalsCode(record.getArchiveStatus())) {
|
throw new DataException("已存档记录不可签到");
|
}
|
if (StringUtil.isEmpty(taskId)) {
|
taskId = record.getTaskId();
|
}
|
if (StringUtil.isEmpty(taskId) || !taskId.equals(record.getTaskId())) {
|
throw new DataException("签到二维码与培训记录不匹配");
|
}
|
|
TmsTaskEntity task = taskMapper.selectById(taskId);
|
if (task == null) {
|
throw new DataException("培训任务不存在");
|
}
|
if (!TmsTaskPublishStatus.PUBLISHED.equalsCode(task.getPublishStatus())) {
|
throw new DataException("任务未发布,不可签到");
|
}
|
String signRule = resolveSignRule(task.getTrainMode());
|
if (!TmsSignRule.SCAN.equalsCodeIgnoreCase(signRule) && !TmsSignRule.BOTH.equalsCodeIgnoreCase(signRule)) {
|
throw new DataException("该培训方式不支持扫码签到");
|
}
|
|
String mode = normalizeScanSignMode(signMode);
|
TmsSignModeEntity signModeCfg = requireEnabledSignMode(mode);
|
|
String userId = UserProvider.getLoginUserId();
|
if (StringUtil.isEmpty(userId)) {
|
throw new DataException("请先登录后再扫码签到");
|
}
|
TmsPersonTaskEntity person = personTaskMapper.selectOne(
|
new LambdaQueryWrapper<TmsPersonTaskEntity>()
|
.eq(TmsPersonTaskEntity::getTaskId, taskId)
|
.eq(TmsPersonTaskEntity::getUserId, userId)
|
.last("LIMIT 1"));
|
boolean guest = false;
|
if (person == null) {
|
if (!allowGuestScan(task.getTrainMode(), signModeCfg)) {
|
throw new DataException("您不在本场培训名单中,无法签到");
|
}
|
person = createGuestPersonTask(task, userId);
|
guest = true;
|
}
|
if (TmsPersonTaskStatus.CANCELLED.equalsCode(person.getBizStatus())) {
|
throw new DataException("任务已取消,不可签到");
|
}
|
if (person.getSignTime() != null) {
|
TmsPersonTaskSignResult dup = new TmsPersonTaskSignResult();
|
dup.setSignTime(formatDate(person.getSignTime()));
|
dup.setBizStatus(person.getBizStatus());
|
dup.setSigned(true);
|
dup.setCanSign(false);
|
dup.setSignTip("已签到");
|
dup.setMessage("您已签到,无需重复签到");
|
TmsPersonTaskMineForm tmp = new TmsPersonTaskMineForm();
|
fillExamFlag(tmp, task, person);
|
dup.setCanExam(Boolean.TRUE.equals(tmp.getCanExam()));
|
dup.setExamTip(tmp.getExamTip());
|
dup.setPersonTaskId(person.getId());
|
fillQuizOnSignResult(dup, task, person);
|
return dup;
|
}
|
|
String timeDeny = resolveScanTimeDenyReason(task);
|
if (StringUtil.isNotEmpty(timeDeny)) {
|
throw new DataException(timeDeny);
|
}
|
|
Date now = new Date();
|
person.setSignTime(now);
|
person.setSignMode(mode);
|
if (guest) {
|
person.setGuestFlag("1");
|
}
|
if (TmsPersonTaskStatus.TODO.equalsCode(person.getBizStatus()) || StringUtil.isEmpty(person.getBizStatus())) {
|
person.setBizStatus(TmsPersonTaskStatus.SIGNED.getCode());
|
}
|
person.setLastModifyUserId(userId);
|
person.setLastModifyTime(now);
|
personTaskMapper.updateById(person);
|
upsertRecordSign(recordId, person, mode, now);
|
if (!guest) {
|
refreshRecordRates(taskId);
|
}
|
|
TmsPersonTaskSignResult result = new TmsPersonTaskSignResult();
|
result.setSignTime(formatDate(now));
|
result.setBizStatus(person.getBizStatus());
|
result.setSigned(true);
|
result.setCanSign(false);
|
result.setSignTip("已签到");
|
result.setMessage(guest ? "扫码签到成功(名单外)" : "扫码签到成功");
|
TmsPersonTaskMineForm tmp = new TmsPersonTaskMineForm();
|
fillExamFlag(tmp, task, person);
|
result.setCanExam(Boolean.TRUE.equals(tmp.getCanExam()));
|
result.setExamTip(tmp.getExamTip());
|
result.setPersonTaskId(person.getId());
|
if (!guest) {
|
fillQuizOnSignResult(result, task, person);
|
} else {
|
result.setNeedQuiz(false);
|
}
|
return result;
|
}
|
|
@Override
|
public List<TmsQuizQuestionVO> listQuizQuestions(String personTaskId) {
|
TmsPersonTaskEntity person = requireMine(personTaskId);
|
TmsTaskEntity task = taskMapper.selectById(person.getTaskId());
|
if (task == null || !needsQuiz(task)) {
|
return Collections.emptyList();
|
}
|
if (hasQuizScore(task.getId(), person.getUserId())) {
|
return Collections.emptyList();
|
}
|
return buildQuizQuestions(task.getId());
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public TmsQuizSubmitResult submitQuiz(String personTaskId, TmsQuizAnswerForm form) {
|
TmsPersonTaskEntity person = requireMine(personTaskId);
|
TmsTaskEntity task = taskMapper.selectById(person.getTaskId());
|
if (task == null) {
|
throw new DataException("培训任务不存在");
|
}
|
if (!needsQuiz(task)) {
|
throw new DataException("当前任务无需提问");
|
}
|
if (person.getSignTime() == null) {
|
throw new DataException("请先完成签到");
|
}
|
if (hasQuizScore(task.getId(), person.getUserId())) {
|
throw new DataException("您已提交过提问答案");
|
}
|
List<TmsTaskQuizEntity> presets = taskQuizMapper.selectList(new LambdaQueryWrapper<TmsTaskQuizEntity>()
|
.eq(TmsTaskQuizEntity::getForeignId, task.getId())
|
.orderByAsc(TmsTaskQuizEntity::getSortNo)
|
.orderByAsc(TmsTaskQuizEntity::getCreatorTime));
|
if (presets == null || presets.isEmpty()) {
|
throw new DataException("任务未配置提问预设");
|
}
|
Map<String, String> answerMap = new HashMap<>();
|
if (form != null && form.getAnswers() != null) {
|
for (TmsQuizAnswerForm.Item item : form.getAnswers()) {
|
if (item != null && StringUtil.isNotEmpty(item.getQuizId())) {
|
answerMap.put(item.getQuizId(), item.getAnswer() == null ? "" : item.getAnswer().trim());
|
}
|
}
|
}
|
for (TmsTaskQuizEntity q : presets) {
|
if (StringUtil.isEmpty(answerMap.get(q.getId()))) {
|
throw new DataException("请完成全部提问后再提交");
|
}
|
}
|
|
TmsRecordEntity record = recordMapper.selectOne(new LambdaQueryWrapper<TmsRecordEntity>()
|
.eq(TmsRecordEntity::getTaskId, task.getId())
|
.last("LIMIT 1"));
|
String recordId = record == null ? null : record.getId();
|
Date now = new Date();
|
int correct = 0;
|
BigDecimal unit = BigDecimal.valueOf(100)
|
.divide(BigDecimal.valueOf(presets.size()), 2, RoundingMode.HALF_UP);
|
for (TmsTaskQuizEntity q : presets) {
|
String userAns = answerMap.getOrDefault(q.getId(), "");
|
boolean ok = TmsQuizHelper.answersMatch(q.getAnswer(), userAns);
|
if (ok) {
|
correct++;
|
}
|
TmsQuizScoreEntity scoreRow = new TmsQuizScoreEntity();
|
scoreRow.setId(RandomUtil.uuId());
|
scoreRow.setTaskId(task.getId());
|
scoreRow.setRecordId(recordId);
|
scoreRow.setUserId(person.getUserId());
|
scoreRow.setQuestion(q.getQuestion());
|
scoreRow.setAnswer(userAns);
|
scoreRow.setScore(ok ? unit : BigDecimal.ZERO);
|
scoreRow.setQuizTime(now);
|
scoreRow.setCreatorUserId(person.getUserId());
|
scoreRow.setCreatorTime(now);
|
quizScoreMapper.insert(scoreRow);
|
}
|
|
BigDecimal totalScore = BigDecimal.valueOf(correct)
|
.multiply(BigDecimal.valueOf(100))
|
.divide(BigDecimal.valueOf(presets.size()), 1, RoundingMode.HALF_UP);
|
BigDecimal passScore = task.getPassScore() == null ? BigDecimal.valueOf(60) : task.getPassScore();
|
String passFlag = totalScore.compareTo(passScore) >= 0
|
? TmsPassFlag.PASS.getCode()
|
: TmsPassFlag.FAIL.getCode();
|
|
person.setPassFlag(passFlag);
|
if (TmsPassFlag.isPass(passFlag)) {
|
person.setBizStatus(TmsPersonTaskStatus.DONE.getCode());
|
}
|
person.setLastModifyUserId(person.getUserId());
|
person.setLastModifyTime(now);
|
personTaskMapper.updateById(person);
|
|
if (StringUtil.isNotEmpty(recordId)) {
|
TmsRecordSignEntity sign = recordSignMapper.selectOne(new LambdaQueryWrapper<TmsRecordSignEntity>()
|
.eq(TmsRecordSignEntity::getForeignId, recordId)
|
.eq(TmsRecordSignEntity::getUserId, person.getUserId())
|
.last("LIMIT 1"));
|
if (sign != null) {
|
sign.setQuizScore(totalScore);
|
sign.setResultFlag(passFlag);
|
sign.setLastModifyUserId(person.getUserId());
|
sign.setLastModifyTime(now);
|
recordSignMapper.updateById(sign);
|
}
|
}
|
refreshRecordRates(task.getId());
|
|
TmsQuizSubmitResult result = new TmsQuizSubmitResult();
|
result.setScore(totalScore);
|
result.setPassFlag(passFlag);
|
result.setMessage(TmsPassFlag.isPass(passFlag)
|
? "提问已提交,判定合格"
|
: "提问已提交,未达合格分");
|
return result;
|
}
|
|
@Override
|
public TmsQuizAnswerPaperVO getMyQuizAnswers(String personTaskId) {
|
TmsPersonTaskEntity person = requireMine(personTaskId);
|
TmsTaskEntity task = taskMapper.selectById(person.getTaskId());
|
if (task == null) {
|
throw new DataException("培训任务不存在");
|
}
|
if (!needsQuiz(task)) {
|
throw new DataException("当前任务无需提问");
|
}
|
List<TmsQuizScoreEntity> scores = quizScoreMapper.selectList(new LambdaQueryWrapper<TmsQuizScoreEntity>()
|
.eq(TmsQuizScoreEntity::getTaskId, task.getId())
|
.eq(TmsQuizScoreEntity::getUserId, person.getUserId())
|
.orderByAsc(TmsQuizScoreEntity::getCreatorTime)
|
.orderByAsc(TmsQuizScoreEntity::getId));
|
if (scores == null || scores.isEmpty()) {
|
throw new DataException("尚未提交提问答卷");
|
}
|
List<TmsTaskQuizEntity> presets = taskQuizMapper.selectList(new LambdaQueryWrapper<TmsTaskQuizEntity>()
|
.eq(TmsTaskQuizEntity::getForeignId, task.getId())
|
.orderByAsc(TmsTaskQuizEntity::getSortNo)
|
.orderByAsc(TmsTaskQuizEntity::getCreatorTime));
|
TmsRecordEntity record = recordMapper.selectOne(new LambdaQueryWrapper<TmsRecordEntity>()
|
.eq(TmsRecordEntity::getTaskId, task.getId())
|
.last("LIMIT 1"));
|
String recordId = record == null ? null : record.getId();
|
BigDecimal quizScore = null;
|
if (StringUtil.isNotEmpty(recordId)) {
|
TmsRecordSignEntity sign = recordSignMapper.selectOne(new LambdaQueryWrapper<TmsRecordSignEntity>()
|
.eq(TmsRecordSignEntity::getForeignId, recordId)
|
.eq(TmsRecordSignEntity::getUserId, person.getUserId())
|
.last("LIMIT 1"));
|
if (sign != null) {
|
quizScore = sign.getQuizScore();
|
}
|
}
|
String quizTime = formatDate(scores.get(0).getQuizTime() != null
|
? scores.get(0).getQuizTime()
|
: scores.get(0).getCreatorTime());
|
TmsQuizAnswerPaperVO paper = TmsQuizHelper.buildAnswerPaper(
|
recordId,
|
task.getId(),
|
person.getUserId(),
|
null,
|
person.getPassFlag(),
|
quizTime,
|
scores,
|
presets);
|
if (quizScore != null) {
|
paper.setTotalScore(quizScore);
|
}
|
return paper;
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
/**
|
* 在线考试交卷后回写:成绩、合格标记、个人任务业务状态。
|
* 不写 tms_person_train(档案目录仍等记录归档)。
|
*/
|
public void writeBackExamResult(String personTaskId, BigDecimal examScore, String passFlag) {
|
if (StringUtil.isEmpty(personTaskId)) {
|
return;
|
}
|
TmsPersonTaskEntity person = personTaskMapper.selectById(personTaskId);
|
if (person == null || TmsPersonTaskStatus.CANCELLED.equalsCode(person.getBizStatus())) {
|
return;
|
}
|
Date now = new Date();
|
person.setExamScore(examScore);
|
person.setPassFlag(TmsPassFlag.normalize(passFlag));
|
person.setBizStatus(TmsPersonTaskStatus.DONE.getCode());
|
person.setLastModifyTime(now);
|
String loginId = UserProvider.getLoginUserId();
|
if (StringUtil.isNotEmpty(loginId)) {
|
person.setLastModifyUserId(loginId);
|
}
|
personTaskMapper.updateById(person);
|
syncRecordSignExamResult(person, examScore, person.getPassFlag(), now);
|
refreshRecordRates(person.getTaskId());
|
}
|
|
private void refreshRecordRates(String taskId) {
|
try {
|
if (recordService != null) {
|
recordService.refreshRatesByTaskId(taskId);
|
}
|
} catch (Exception ignored) {
|
// 进度回写失败不影响主流程
|
}
|
}
|
|
private void fillQuizOnSignResult(TmsPersonTaskSignResult result, TmsTaskEntity task, TmsPersonTaskEntity person) {
|
if (result == null || task == null || person == null) {
|
return;
|
}
|
if (!needsQuiz(task) || hasQuizScore(task.getId(), person.getUserId())) {
|
result.setNeedQuiz(false);
|
result.setQuizQuestions(Collections.emptyList());
|
return;
|
}
|
List<TmsQuizQuestionVO> questions = buildQuizQuestions(task.getId());
|
result.setNeedQuiz(!questions.isEmpty());
|
result.setQuizQuestions(questions);
|
}
|
|
private boolean needsQuiz(TmsTaskEntity task) {
|
if (task == null) {
|
return false;
|
}
|
if (TmsEvalMode.QUIZ.equalsCode(task.getEvalMode())) {
|
return true;
|
}
|
if (StringUtil.isEmpty(task.getEvalMode())) {
|
return false;
|
}
|
TmsEvalModeEntity mode = evalModeMapper.selectOne(new LambdaQueryWrapper<TmsEvalModeEntity>()
|
.eq(TmsEvalModeEntity::getModeCode, task.getEvalMode())
|
.last("LIMIT 1"));
|
return mode != null && "1".equals(mode.getNeedQuiz());
|
}
|
|
private boolean hasQuizScore(String taskId, String userId) {
|
if (StringUtil.isEmpty(taskId) || StringUtil.isEmpty(userId)) {
|
return false;
|
}
|
Long cnt = quizScoreMapper.selectCount(new LambdaQueryWrapper<TmsQuizScoreEntity>()
|
.eq(TmsQuizScoreEntity::getTaskId, taskId)
|
.eq(TmsQuizScoreEntity::getUserId, userId));
|
return cnt != null && cnt > 0;
|
}
|
|
private List<TmsQuizQuestionVO> buildQuizQuestions(String taskId) {
|
List<TmsTaskQuizEntity> rows = taskQuizMapper.selectList(new LambdaQueryWrapper<TmsTaskQuizEntity>()
|
.eq(TmsTaskQuizEntity::getForeignId, taskId)
|
.orderByAsc(TmsTaskQuizEntity::getSortNo)
|
.orderByAsc(TmsTaskQuizEntity::getCreatorTime));
|
List<TmsQuizQuestionVO> list = new ArrayList<>();
|
if (rows == null) {
|
return list;
|
}
|
for (TmsTaskQuizEntity row : rows) {
|
if (row == null || StringUtil.isEmpty(row.getQuestion())) {
|
continue;
|
}
|
TmsQuizQuestionVO vo = new TmsQuizQuestionVO();
|
vo.setId(row.getId());
|
vo.setSortNo(row.getSortNo());
|
vo.setQuestion(row.getQuestion());
|
list.add(vo);
|
}
|
return list;
|
}
|
|
/**
|
* 同步签到记录中的考试结果
|
*/
|
private void syncRecordSignExamResult(TmsPersonTaskEntity person, BigDecimal examScore, String passFlag, Date now) {
|
if (person == null || StringUtil.isEmpty(person.getUserId()) || StringUtil.isEmpty(person.getTaskId())) {
|
return;
|
}
|
TmsRecordEntity record = recordMapper.selectOne(
|
new LambdaQueryWrapper<TmsRecordEntity>()
|
.eq(TmsRecordEntity::getTaskId, person.getTaskId())
|
.last("LIMIT 1"));
|
if (record == null) {
|
return;
|
}
|
TmsRecordSignEntity sign = recordSignMapper.selectOne(
|
new LambdaQueryWrapper<TmsRecordSignEntity>()
|
.eq(TmsRecordSignEntity::getForeignId, record.getId())
|
.eq(TmsRecordSignEntity::getUserId, person.getUserId())
|
.last("LIMIT 1"));
|
if (sign == null) {
|
return;
|
}
|
sign.setExamScore(examScore);
|
sign.setResultFlag(TmsPassFlag.normalize(passFlag));
|
sign.setLastModifyUserId(UserProvider.getLoginUserId());
|
sign.setLastModifyTime(now);
|
recordSignMapper.updateById(sign);
|
}
|
|
/** 写入/更新培训记录签到明细 */
|
private void upsertRecordSign(String recordIdOrTaskId, TmsPersonTaskEntity person, String signMode, Date now) {
|
if (person == null || StringUtil.isEmpty(person.getUserId())) {
|
return;
|
}
|
String recordId = recordIdOrTaskId;
|
TmsRecordEntity byId = StringUtil.isNotEmpty(recordId) ? recordMapper.selectById(recordId) : null;
|
if (byId == null && StringUtil.isNotEmpty(person.getTaskId())) {
|
byId = recordMapper.selectOne(
|
new LambdaQueryWrapper<TmsRecordEntity>()
|
.eq(TmsRecordEntity::getTaskId, person.getTaskId())
|
.last("LIMIT 1"));
|
}
|
if (byId == null) {
|
return;
|
}
|
recordId = byId.getId();
|
TmsRecordSignEntity exists = recordSignMapper.selectOne(
|
new LambdaQueryWrapper<TmsRecordSignEntity>()
|
.eq(TmsRecordSignEntity::getForeignId, recordId)
|
.eq(TmsRecordSignEntity::getUserId, person.getUserId())
|
.last("LIMIT 1"));
|
if (exists != null) {
|
exists.setSignTime(now);
|
exists.setSignMode(signMode);
|
exists.setGuestFlag(StringUtil.isNotEmpty(person.getGuestFlag()) ? person.getGuestFlag() : "0");
|
exists.setDeptId(person.getDeptId());
|
exists.setLastModifyUserId(UserProvider.getLoginUserId());
|
exists.setLastModifyTime(now);
|
recordSignMapper.updateById(exists);
|
return;
|
}
|
TmsRecordSignEntity row = new TmsRecordSignEntity();
|
row.setId(RandomUtil.uuId());
|
row.setForeignId(recordId);
|
row.setUserId(person.getUserId());
|
row.setDeptId(person.getDeptId());
|
row.setSignTime(now);
|
row.setSignMode(signMode);
|
row.setGuestFlag(StringUtil.isNotEmpty(person.getGuestFlag()) ? person.getGuestFlag() : "0");
|
row.setCreatorUserId(UserProvider.getLoginUserId());
|
row.setCreatorTime(now);
|
recordSignMapper.insert(row);
|
}
|
|
/**
|
* 创建访客个人任务
|
*/
|
private TmsPersonTaskEntity createGuestPersonTask(TmsTaskEntity task, String userId) {
|
Date now = new Date();
|
TmsPersonTaskEntity pt = new TmsPersonTaskEntity();
|
pt.setId(RandomUtil.uuId());
|
pt.setTaskId(task.getId());
|
pt.setTaskNo(task.getTaskNo());
|
pt.setUserId(userId);
|
pt.setHours(null);
|
pt.setBizStatus(TmsPersonTaskStatus.TODO.getCode());
|
pt.setGuestFlag("1");
|
pt.setCreatorUserId(userId);
|
pt.setCreatorTime(now);
|
personTaskMapper.insert(pt);
|
return pt;
|
}
|
|
/**
|
* 是否允许访客扫码
|
*/
|
private boolean allowGuestScan(String trainModeCode, TmsSignModeEntity signMode) {
|
if (signMode != null && "1".equals(signMode.getMatchRoster())) {
|
return false;
|
}
|
TmsTrainModeEntity tm = loadTrainMode(trainModeCode);
|
return tm != null && "1".equals(tm.getAllowGuestSign());
|
}
|
|
/**
|
* 加载培训方式配置
|
*/
|
private TmsTrainModeEntity loadTrainMode(String trainModeCode) {
|
if (StringUtil.isEmpty(trainModeCode)) {
|
return null;
|
}
|
return trainModeMapper.selectOne(
|
new LambdaQueryWrapper<TmsTrainModeEntity>()
|
.eq(TmsTrainModeEntity::getModeCode, trainModeCode)
|
.last("LIMIT 1"));
|
}
|
|
/**
|
* 解析签到二维码内容
|
*/
|
private Map<String, Object> parseSignQr(String qrContent) {
|
if (StringUtil.isEmpty(qrContent)) {
|
throw new DataException("签到二维码内容为空");
|
}
|
try {
|
Map<String, Object> map = OBJECT_MAPPER.readValue(qrContent.trim(),
|
new TypeReference<Map<String, Object>>() {
|
});
|
if (map == null || map.isEmpty()) {
|
throw new DataException("无效的签到二维码");
|
}
|
return map;
|
} catch (DataException ex) {
|
throw ex;
|
} catch (Exception ex) {
|
throw new DataException("无效的签到二维码");
|
}
|
}
|
|
/**
|
* 规范化扫码签到方式
|
*/
|
private String normalizeScanSignMode(String signMode) {
|
if (TmsSignMode.WX_SCAN.getCode().equalsIgnoreCase(signMode)) {
|
return TmsSignMode.WX_SCAN.getCode();
|
}
|
return TmsSignMode.APP_SCAN.getCode();
|
}
|
|
/**
|
* 确保扫码签到方式已启用
|
*/
|
private void ensureScanModeEnabled(String modeCode) {
|
requireEnabledSignMode(modeCode);
|
}
|
|
/**
|
* 校验签到方式已启用
|
*/
|
private TmsSignModeEntity requireEnabledSignMode(String modeCode) {
|
TmsSignModeEntity sm = signModeMapper.selectOne(
|
new LambdaQueryWrapper<TmsSignModeEntity>()
|
.eq(TmsSignModeEntity::getModeCode, modeCode)
|
.eq(TmsSignModeEntity::getEnabled, "1")
|
.last("LIMIT 1"));
|
if (sm == null) {
|
throw new DataException("签到方式未启用:" + modeCode);
|
}
|
return sm;
|
}
|
|
/** 扫码时间窗(不含在线签到规则校验) */
|
private String resolveScanTimeDenyReason(TmsTaskEntity task) {
|
Date now = new Date();
|
if (task.getStartTime() != null && now.before(task.getStartTime())) {
|
return "未到培训开始时间,暂不可签到";
|
}
|
if (task.getCloseTime() != null && now.after(task.getCloseTime())) {
|
return "培训已关闭,不可签到";
|
}
|
if (task.getEndTime() != null && now.after(task.getEndTime()) && task.getCloseTime() == null) {
|
return "已过培训结束时间,不可签到";
|
}
|
return "";
|
}
|
|
/**
|
* 安全转字符串
|
*/
|
private static String stringVal(Object raw) {
|
return raw == null ? "" : String.valueOf(raw).trim();
|
}
|
|
/**
|
* 转 Long
|
*/
|
private static long toLong(Object raw) {
|
if (raw == null) {
|
return 0L;
|
}
|
if (raw instanceof Number) {
|
return ((Number) raw).longValue();
|
}
|
try {
|
return Long.parseLong(String.valueOf(raw).trim());
|
} catch (Exception ex) {
|
return 0L;
|
}
|
}
|
|
/**
|
* 实体转为表单/VO
|
*/
|
private TmsPersonTaskMineForm toForm(TmsPersonTaskEntity person, TmsTaskEntity task, boolean detail) {
|
TmsPersonTaskMineForm form = new TmsPersonTaskMineForm();
|
form.setId(person.getId());
|
form.setTaskId(task.getId());
|
form.setTaskNo(StringUtil.isNotEmpty(person.getTaskNo()) ? person.getTaskNo() : task.getTaskNo());
|
form.setSubject(task.getSubject());
|
form.setCategory(task.getCategory());
|
form.setTrainMode(task.getTrainMode());
|
form.setEvalMode(task.getEvalMode());
|
form.setStartTime(formatDate(task.getStartTime()));
|
form.setEndTime(formatDate(task.getEndTime()));
|
form.setCloseTime(formatDate(task.getCloseTime()));
|
form.setPlaceName(task.getPlaceName());
|
form.setBizStatus(person.getBizStatus());
|
form.setSignTime(formatDate(person.getSignTime()));
|
form.setSigned(person.getSignTime() != null);
|
form.setSignRule(resolveSignRule(task.getTrainMode()));
|
form.setTipSeconds(resolveOnlineTipSeconds());
|
fillSignFlag(form, task, person);
|
form.setLearnSeconds(person.getLearnSeconds());
|
int learned = person.getLearnSeconds() == null ? 0 : person.getLearnSeconds();
|
form.setLearnedSeconds(learned);
|
BigDecimal hours = person.getHours();
|
form.setRequiredHours(hours);
|
int requiredSeconds = toRequiredSeconds(hours);
|
form.setRequiredSeconds(requiredSeconds);
|
form.setLearnStatus(resolveLearnStatus(learned, requiredSeconds));
|
form.setExamScore(person.getExamScore());
|
form.setPassFlag(person.getPassFlag());
|
form.setPaperId(task.getPaperId());
|
form.setPaperName(task.getPaperName());
|
fillExamFlag(form, task, person);
|
if (detail) {
|
form.setKeyPoints(task.getKeyPoints());
|
}
|
return form;
|
}
|
|
/**
|
* 回填考试标识
|
*/
|
private void fillExamFlag(TmsPersonTaskMineForm form, TmsTaskEntity task, TmsPersonTaskEntity person) {
|
if (!TmsEvalMode.EXAM.equalsCode(task.getEvalMode())) {
|
form.setCanExam(false);
|
form.setExamTip("本次培训无需在线考试");
|
return;
|
}
|
if (StringUtil.isEmpty(task.getPaperId())) {
|
form.setCanExam(false);
|
form.setExamTip("未配置试卷");
|
return;
|
}
|
if (person.getSignTime() == null) {
|
form.setCanExam(false);
|
form.setExamTip("请先签到后再考试");
|
return;
|
}
|
// 配置了课时则须学满后再考(在线/现场一致),便于「学完生成待考试卷」
|
int required = toRequiredSeconds(person.getHours());
|
int learned = person.getLearnSeconds() == null ? 0 : person.getLearnSeconds();
|
if (required > 0 && learned < required) {
|
form.setCanExam(false);
|
form.setExamTip("请先学满规定课时后再考试(已学 "
|
+ formatDuration(learned) + " / 需学 " + formatDuration(required) + ")");
|
return;
|
}
|
Date now = new Date();
|
if (task.getExamStart() != null && now.before(task.getExamStart())) {
|
form.setCanExam(false);
|
form.setExamTip("未到考试开始时间");
|
return;
|
}
|
if (task.getExamEnd() != null && now.after(task.getExamEnd())) {
|
form.setCanExam(false);
|
form.setExamTip("已过考试结束时间");
|
return;
|
}
|
form.setCanExam(true);
|
form.setExamTip("");
|
}
|
|
/**
|
* 回填签到标识
|
*/
|
private void fillSignFlag(TmsPersonTaskMineForm form, TmsTaskEntity task, TmsPersonTaskEntity person) {
|
if (person.getSignTime() != null) {
|
form.setCanSign(false);
|
form.setSignTip("已签到");
|
return;
|
}
|
String deny = resolveSignDenyReason(task, person);
|
if (StringUtil.isNotEmpty(deny)) {
|
form.setCanSign(false);
|
form.setSignTip(deny);
|
return;
|
}
|
form.setCanSign(true);
|
form.setSignTip("");
|
}
|
|
/** 返回不可签到原因;空字符串表示可签到 */
|
private String resolveSignDenyReason(TmsTaskEntity task, TmsPersonTaskEntity person) {
|
if (TmsPersonTaskStatus.CANCELLED.equalsCode(person.getBizStatus())) {
|
return "任务已取消,不可签到";
|
}
|
if (task == null || !TmsTaskPublishStatus.PUBLISHED.equalsCode(task.getPublishStatus())) {
|
return "任务未发布,不可签到";
|
}
|
if (!allowOnlineSign(task.getTrainMode())) {
|
return "该培训方式不支持在线签到,请到培训现场扫码签到";
|
}
|
Date now = new Date();
|
if (task.getStartTime() != null && now.before(task.getStartTime())) {
|
return "未到培训开始时间,暂不可签到";
|
}
|
if (task.getCloseTime() != null && now.after(task.getCloseTime())) {
|
return "培训已关闭,不可签到";
|
}
|
if (task.getEndTime() != null && now.after(task.getEndTime()) && task.getCloseTime() == null) {
|
return "已过培训结束时间,不可签到";
|
}
|
return "";
|
}
|
|
/**
|
* 是否允许在线签到
|
*/
|
private boolean allowOnlineSign(String trainMode) {
|
return TmsSignRule.BOTH.equalsCodeIgnoreCase(resolveSignRule(trainMode));
|
}
|
|
/** scan=仅扫码 / both=扫码+在线;无配置时 online→both,其它→scan */
|
private String resolveSignRule(String trainMode) {
|
if (StringUtil.isEmpty(trainMode)) {
|
return "";
|
}
|
TmsTrainModeEntity mode = trainModeMapper.selectOne(
|
new LambdaQueryWrapper<TmsTrainModeEntity>()
|
.eq(TmsTrainModeEntity::getModeCode, trainMode)
|
.last("LIMIT 1"));
|
if (mode != null && StringUtil.isNotEmpty(mode.getSignRule())) {
|
return mode.getSignRule().trim();
|
}
|
return TmsTrainMode.ONLINE.equalsCodeIgnoreCase(trainMode) ? TmsSignRule.BOTH.getCode() : TmsSignRule.SCAN.getCode();
|
}
|
|
/** 启用的在线签到方式要点秒数;未配置或未启用则 0 */
|
private int resolveOnlineTipSeconds() {
|
TmsSignModeEntity sm = signModeMapper.selectOne(
|
new LambdaQueryWrapper<TmsSignModeEntity>()
|
.eq(TmsSignModeEntity::getModeCode, TmsSignMode.ONLINE.getCode())
|
.eq(TmsSignModeEntity::getEnabled, "1")
|
.last("LIMIT 1"));
|
if (sm == null || sm.getTipSeconds() == null || sm.getTipSeconds() < 0) {
|
return 0;
|
}
|
return sm.getTipSeconds();
|
}
|
|
/**
|
* 换算要求学习秒数
|
*/
|
private int toRequiredSeconds(BigDecimal hours) {
|
if (hours == null || hours.compareTo(BigDecimal.ZERO) <= 0) {
|
return 0;
|
}
|
return hours.multiply(BigDecimal.valueOf(3600)).intValue();
|
}
|
|
/**
|
* 解析学习状态
|
*/
|
private String resolveLearnStatus(int learned, int required) {
|
if (required > 0 && learned >= required) {
|
return "已学满";
|
}
|
if (learned > 0) {
|
return "学习中";
|
}
|
return "未学习";
|
}
|
|
/**
|
* 格式化时长
|
*/
|
private String formatDuration(int seconds) {
|
int h = seconds / 3600;
|
int m = (seconds % 3600) / 60;
|
int s = seconds % 60;
|
if (h > 0) {
|
return h + "小时" + m + "分";
|
}
|
if (m > 0) {
|
return m + "分" + s + "秒";
|
}
|
return s + "秒";
|
}
|
|
/**
|
* 加载学习课件/附件
|
*/
|
private List<TmsPersonTaskFileForm> loadFiles(String taskId) {
|
List<TmsTaskFileEntity> files = taskFileMapper.selectList(
|
new LambdaQueryWrapper<TmsTaskFileEntity>()
|
.eq(TmsTaskFileEntity::getForeignId, taskId)
|
.orderByAsc(TmsTaskFileEntity::getCreatorTime));
|
if (files.isEmpty()) {
|
return Collections.emptyList();
|
}
|
List<String> materialIds = files.stream()
|
.map(TmsTaskFileEntity::getMaterialId)
|
.filter(StringUtil::isNotEmpty)
|
.distinct()
|
.collect(Collectors.toList());
|
Map<String, TmsMaterialEntity> materialMap = new HashMap<>();
|
if (!materialIds.isEmpty()) {
|
for (TmsMaterialEntity material : materialMapper.selectBatchIds(materialIds)) {
|
materialMap.put(material.getId(), material);
|
}
|
}
|
List<TmsPersonTaskFileForm> list = new ArrayList<>();
|
for (TmsTaskFileEntity file : files) {
|
Map<String, String> meta = extractFileMeta(file.getFileJson(), file.getFileName());
|
if ((StringUtil.isEmpty(meta.get("url")) || StringUtil.isEmpty(meta.get("ext")))
|
&& StringUtil.isNotEmpty(file.getMaterialId())) {
|
TmsMaterialEntity material = materialMap.get(file.getMaterialId());
|
if (material != null) {
|
Map<String, String> matMeta = extractFileMeta(material.getFileJson(), material.getMaterialName());
|
if (StringUtil.isEmpty(meta.get("url"))) {
|
meta.put("url", matMeta.get("url"));
|
}
|
if (StringUtil.isEmpty(meta.get("ext"))) {
|
meta.put("ext", matMeta.get("ext"));
|
}
|
if (StringUtil.isEmpty(meta.get("fileId"))) {
|
meta.put("fileId", matMeta.get("fileId"));
|
}
|
if (StringUtil.isEmpty(meta.get("fileVersionId"))) {
|
meta.put("fileVersionId", matMeta.get("fileVersionId"));
|
}
|
if (StringUtil.isEmpty(meta.get("name")) && StringUtil.isNotEmpty(matMeta.get("name"))) {
|
meta.put("name", matMeta.get("name"));
|
}
|
}
|
}
|
TmsPersonTaskFileForm item = new TmsPersonTaskFileForm();
|
item.setId(file.getId());
|
item.setName(StringUtil.isNotEmpty(file.getFileName()) ? file.getFileName() : meta.get("name"));
|
item.setUrl(meta.get("url"));
|
item.setFileId(meta.get("fileId"));
|
item.setFileVersionId(meta.get("fileVersionId"));
|
item.setFileExt(meta.get("ext"));
|
item.setPreviewType(resolvePreviewType(meta.get("ext")));
|
list.add(item);
|
}
|
return list;
|
}
|
|
/** 从 JNPF 附件 JSON 解析 url / fileId / 扩展名等。 */
|
private Map<String, String> extractFileMeta(String fileJson, String fileName) {
|
Map<String, String> meta = new HashMap<>();
|
meta.put("url", null);
|
meta.put("fileId", null);
|
meta.put("fileVersionId", null);
|
meta.put("ext", null);
|
meta.put("name", fileName);
|
try {
|
if (StringUtil.isNotEmpty(fileJson)) {
|
String text = fileJson.trim();
|
Map<String, Object> map = null;
|
if (text.startsWith("[")) {
|
List<Map<String, Object>> arr = OBJECT_MAPPER.readValue(text, new TypeReference<List<Map<String, Object>>>() {
|
});
|
if (arr != null && !arr.isEmpty()) {
|
map = arr.get(0);
|
}
|
} else {
|
map = OBJECT_MAPPER.readValue(text, new TypeReference<Map<String, Object>>() {
|
});
|
}
|
if (map != null) {
|
if (map.get("url") != null) {
|
meta.put("url", String.valueOf(map.get("url")));
|
}
|
Object fid = map.get("fileId");
|
if (fid == null) {
|
fid = map.get("file_id");
|
}
|
if (fid != null) {
|
meta.put("fileId", String.valueOf(fid));
|
}
|
Object fvid = map.get("fileVersionId");
|
if (fvid == null) {
|
fvid = map.get("fileVersion");
|
}
|
if (fvid != null) {
|
meta.put("fileVersionId", String.valueOf(fvid));
|
}
|
if (map.get("fileExtension") != null) {
|
meta.put("ext", String.valueOf(map.get("fileExtension")).toLowerCase());
|
}
|
if (map.get("name") != null) {
|
meta.put("name", String.valueOf(map.get("name")));
|
if (StringUtil.isEmpty(meta.get("ext"))) {
|
fileName = String.valueOf(map.get("name"));
|
}
|
}
|
}
|
}
|
} catch (Exception ignored) {
|
// ignore
|
}
|
if (StringUtil.isEmpty(meta.get("ext")) && StringUtil.isNotEmpty(fileName) && fileName.contains(".")) {
|
meta.put("ext", fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase());
|
}
|
return meta;
|
}
|
|
/**
|
* 解析附件预览类型
|
*/
|
private String resolvePreviewType(String ext) {
|
if (StringUtil.isEmpty(ext)) {
|
return "other";
|
}
|
String e = ext.toLowerCase();
|
if (Set.of("jpg", "jpeg", "png", "gif", "webp", "bmp", "svg").contains(e)) {
|
return "image";
|
}
|
if (Set.of("mp4", "webm", "ogg", "mov", "m4v").contains(e)) {
|
return "video";
|
}
|
if (Set.of("mp3", "wav", "aac", "m4a", "flac").contains(e)) {
|
return "audio";
|
}
|
if ("pdf".equals(e)) {
|
return "pdf";
|
}
|
if (Set.of("doc", "docx", "docm", "dot", "dotx", "xls", "xlsx", "xlsm", "xlt", "xltx",
|
"ppt", "pptx", "pptm", "pps", "ppsx", "pot", "potx", "csv", "rtf",
|
"odt", "ods", "odp", "ott", "ots", "otp").contains(e)) {
|
return "office";
|
}
|
return "other";
|
}
|
|
/**
|
* 批量加载培训任务
|
*/
|
private Map<String, TmsTaskEntity> loadTasks(List<TmsPersonTaskEntity> persons) {
|
List<String> ids = persons.stream()
|
.map(TmsPersonTaskEntity::getTaskId)
|
.filter(StringUtil::isNotEmpty)
|
.distinct()
|
.collect(Collectors.toList());
|
if (ids.isEmpty()) {
|
return Collections.emptyMap();
|
}
|
Map<String, TmsTaskEntity> map = new HashMap<>();
|
for (TmsTaskEntity task : taskMapper.selectBatchIds(ids)) {
|
map.put(task.getId(), task);
|
}
|
return map;
|
}
|
|
/**
|
* 关键字匹配
|
*/
|
private boolean matchKeyword(TmsPersonTaskEntity person, TmsTaskEntity task, String keyword) {
|
String no = person.getTaskNo() == null ? "" : person.getTaskNo();
|
String taskNo = task.getTaskNo() == null ? "" : task.getTaskNo();
|
String subject = task.getSubject() == null ? "" : task.getSubject();
|
return no.contains(keyword) || taskNo.contains(keyword) || subject.contains(keyword);
|
}
|
|
/**
|
* 筛选合格标识
|
*/
|
private boolean matchPassFlag(String queryFlag, String passFlag) {
|
if (StringUtil.isEmpty(queryFlag)) {
|
return true;
|
}
|
String q = queryFlag.trim();
|
if ("none".equalsIgnoreCase(q)) {
|
return StringUtil.isEmpty(passFlag);
|
}
|
String normalizedQuery = TmsPassFlag.normalize(q);
|
String normalizedPass = TmsPassFlag.normalize(passFlag);
|
if (normalizedQuery == null) {
|
return false;
|
}
|
return normalizedQuery.equals(normalizedPass);
|
}
|
|
/**
|
* 是否类条件匹配
|
*/
|
private boolean matchYesNo(String query, boolean actual) {
|
if (StringUtil.isEmpty(query)) {
|
return true;
|
}
|
if ("1".equals(query) || "true".equalsIgnoreCase(query) || "yes".equalsIgnoreCase(query)) {
|
return actual;
|
}
|
if ("0".equals(query) || "false".equalsIgnoreCase(query) || "no".equalsIgnoreCase(query)) {
|
return !actual;
|
}
|
return true;
|
}
|
|
/**
|
* 解析日开始时间
|
*/
|
private static Date parseDayStart(String day) {
|
if (StringUtil.isEmpty(day) || "null".equalsIgnoreCase(day)) {
|
return null;
|
}
|
String raw = day.trim();
|
if (raw.matches("^\\d{13}$")) {
|
return new Date(Long.parseLong(raw));
|
}
|
if (raw.matches("^\\d{10}$")) {
|
return new Date(Long.parseLong(raw) * 1000L);
|
}
|
if (raw.length() == 10) {
|
raw = raw + " 00:00:00";
|
}
|
return DateUtil.stringToDate(raw);
|
}
|
|
/**
|
* 解析日结束时间
|
*/
|
private static Date parseDayEnd(String day) {
|
if (StringUtil.isEmpty(day) || "null".equalsIgnoreCase(day)) {
|
return null;
|
}
|
String raw = day.trim();
|
if (raw.matches("^\\d{13}$") || raw.matches("^\\d{10}$")) {
|
long ms = raw.length() == 13 ? Long.parseLong(raw) : Long.parseLong(raw) * 1000L;
|
java.util.Calendar cal = java.util.Calendar.getInstance();
|
cal.setTimeInMillis(ms);
|
// DateRange 常传当天 00:00,按日末过滤
|
if (cal.get(java.util.Calendar.HOUR_OF_DAY) == 0
|
&& cal.get(java.util.Calendar.MINUTE) == 0
|
&& cal.get(java.util.Calendar.SECOND) == 0) {
|
cal.set(java.util.Calendar.HOUR_OF_DAY, 23);
|
cal.set(java.util.Calendar.MINUTE, 59);
|
cal.set(java.util.Calendar.SECOND, 59);
|
cal.set(java.util.Calendar.MILLISECOND, 999);
|
}
|
return cal.getTime();
|
}
|
if (raw.length() == 10) {
|
raw = raw + " 23:59:59";
|
}
|
return DateUtil.stringToDate(raw);
|
}
|
|
/**
|
* 校验并获取当前用户个人任务
|
*/
|
private TmsPersonTaskEntity requireMine(String id) {
|
if (StringUtil.isEmpty(id)) {
|
throw new DataException("个人任务ID不能为空");
|
}
|
TmsPersonTaskEntity person = personTaskMapper.selectById(id);
|
if (person == null) {
|
throw new DataException("个人培训任务不存在");
|
}
|
if (!UserProvider.getLoginUserId().equals(person.getUserId())) {
|
throw new DataException("只能查看自己的培训任务");
|
}
|
return person;
|
}
|
|
/**
|
* 格式化日期时间
|
*/
|
private String formatDate(Date date) {
|
return date == null ? null : DateUtil.dateToString(date, FMT);
|
}
|
}
|