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;
/**
* 学员端个人培训任务:我的列表、学完、签到/扫码签到、考试结果回写。
*
*
不负责写入个人档案培训目录;目录仅在培训记录归档时由 {@link TmsRecordServiceImpl} 写入。
* 考试交卷后通过 {@link #writeBackExamResult} 更新本表成绩与合格标记。
*/
@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 getMine(TmsPersonTaskQuery query) {
String userId = UserProvider.getLoginUserId();
LambdaQueryWrapper 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 persons = personTaskMapper.selectList(qw);
Map 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 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 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()
.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()
.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()
.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 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()
.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 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 presets = taskQuizMapper.selectList(new LambdaQueryWrapper()
.eq(TmsTaskQuizEntity::getForeignId, task.getId())
.orderByAsc(TmsTaskQuizEntity::getSortNo)
.orderByAsc(TmsTaskQuizEntity::getCreatorTime));
if (presets == null || presets.isEmpty()) {
throw new DataException("任务未配置提问预设");
}
Map 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()
.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()
.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 scores = quizScoreMapper.selectList(new LambdaQueryWrapper()
.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 presets = taskQuizMapper.selectList(new LambdaQueryWrapper()
.eq(TmsTaskQuizEntity::getForeignId, task.getId())
.orderByAsc(TmsTaskQuizEntity::getSortNo)
.orderByAsc(TmsTaskQuizEntity::getCreatorTime));
TmsRecordEntity record = recordMapper.selectOne(new LambdaQueryWrapper()
.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()
.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 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()
.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()
.eq(TmsQuizScoreEntity::getTaskId, taskId)
.eq(TmsQuizScoreEntity::getUserId, userId));
return cnt != null && cnt > 0;
}
private List buildQuizQuestions(String taskId) {
List rows = taskQuizMapper.selectList(new LambdaQueryWrapper()
.eq(TmsTaskQuizEntity::getForeignId, taskId)
.orderByAsc(TmsTaskQuizEntity::getSortNo)
.orderByAsc(TmsTaskQuizEntity::getCreatorTime));
List 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()
.eq(TmsRecordEntity::getTaskId, person.getTaskId())
.last("LIMIT 1"));
if (record == null) {
return;
}
TmsRecordSignEntity sign = recordSignMapper.selectOne(
new LambdaQueryWrapper()
.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()
.eq(TmsRecordEntity::getTaskId, person.getTaskId())
.last("LIMIT 1"));
}
if (byId == null) {
return;
}
recordId = byId.getId();
TmsRecordSignEntity exists = recordSignMapper.selectOne(
new LambdaQueryWrapper()
.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()
.eq(TmsTrainModeEntity::getModeCode, trainModeCode)
.last("LIMIT 1"));
}
/**
* 解析签到二维码内容
*/
private Map parseSignQr(String qrContent) {
if (StringUtil.isEmpty(qrContent)) {
throw new DataException("签到二维码内容为空");
}
try {
Map map = OBJECT_MAPPER.readValue(qrContent.trim(),
new TypeReference