package jnpf.tmsService.impl;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
import jnpf.exception.DataException;
|
import jnpf.tmsEntity.TmsMaterialEntity;
|
import jnpf.tmsEntity.TmsPersonTaskEntity;
|
import jnpf.tmsEntity.TmsTaskEntity;
|
import jnpf.tmsEntity.TmsTaskFileEntity;
|
import jnpf.tmsEntity.TmsTrainModeEntity;
|
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.tmsMapper.TmsMaterialMapper;
|
import jnpf.tmsMapper.TmsPersonTaskMapper;
|
import jnpf.tmsMapper.TmsTaskFileMapper;
|
import jnpf.tmsMapper.TmsTaskMapper;
|
import jnpf.tmsMapper.TmsTrainModeMapper;
|
import jnpf.tmsService.TmsPersonTaskService;
|
import jnpf.util.DateUtil;
|
import jnpf.util.StringUtil;
|
import jnpf.util.UserProvider;
|
import lombok.RequiredArgsConstructor;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.math.BigDecimal;
|
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;
|
|
@Service
|
@RequiredArgsConstructor
|
public class TmsPersonTaskServiceImpl implements TmsPersonTaskService {
|
|
private static final String FMT = "yyyy-MM-dd HH:mm:ss";
|
private static final String STATUS_CANCELLED = "cancelled";
|
private static final String STATUS_DONE = "done";
|
private static final String STATUS_SIGNED = "signed";
|
private static final String STATUS_TODO = "todo";
|
private static final String SIGN_MODE_ONLINE = "online";
|
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;
|
|
@Override
|
public List<TmsPersonTaskMineForm> getMine(TmsPersonTaskQuery query) {
|
String userId = UserProvider.getLoginUserId();
|
LambdaQueryWrapper<TmsPersonTaskEntity> qw = new LambdaQueryWrapper<>();
|
qw.eq(TmsPersonTaskEntity::getUserId, userId);
|
qw.ne(TmsPersonTaskEntity::getBizStatus, STATUS_CANCELLED);
|
if (StringUtil.isNotEmpty(query.getBizStatus())) {
|
qw.eq(TmsPersonTaskEntity::getBizStatus, query.getBizStatus().trim());
|
}
|
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;
|
List<TmsPersonTaskMineForm> all = new ArrayList<>();
|
for (TmsPersonTaskEntity person : persons) {
|
TmsTaskEntity task = taskMap.get(person.getTaskId());
|
if (task == null || !"published".equals(task.getPublishStatus())) {
|
continue;
|
}
|
if (keyword != null && !matchKeyword(person, task, keyword)) {
|
continue;
|
}
|
all.add(toForm(person, task, false));
|
}
|
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("培训任务不存在");
|
}
|
|
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 = "exam".equals(task.getEvalMode()) && StringUtil.isNotEmpty(task.getPaperId());
|
if (justFull) {
|
if (!needExam && !STATUS_DONE.equals(person.getBizStatus())
|
&& !STATUS_CANCELLED.equals(person.getBizStatus())) {
|
person.setBizStatus(STATUS_DONE);
|
person.setLastModifyUserId(UserProvider.getLoginUserId());
|
person.setLastModifyTime(new Date());
|
personTaskMapper.updateById(person);
|
result.setAutoCompleted(true);
|
result.setBizStatus(STATUS_DONE);
|
result.setMessage("已学满规定课时,系统已自动确认完成");
|
} else if (needExam) {
|
if (person.getSignTime() == null) {
|
result.setMessage("已学满规定课时,请先签到后再进入考试");
|
} else {
|
result.setMessage("已学满规定课时,可以进入考试");
|
}
|
}
|
}
|
|
TmsPersonTaskMineForm tmp = new TmsPersonTaskMineForm();
|
fillExamFlag(tmp, task, person);
|
result.setCanExam(Boolean.TRUE.equals(tmp.getCanExam()));
|
result.setExamTip(tmp.getExamTip());
|
return result;
|
}
|
|
@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 (!"published".equals(task.getPublishStatus())) {
|
throw new DataException("任务未发布,不可签到");
|
}
|
if (STATUS_CANCELLED.equals(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(SIGN_MODE_ONLINE);
|
if (STATUS_TODO.equals(person.getBizStatus()) || StringUtil.isEmpty(person.getBizStatus())) {
|
person.setBizStatus(STATUS_SIGNED);
|
}
|
person.setLastModifyUserId(UserProvider.getLoginUserId());
|
person.setLastModifyTime(now);
|
personTaskMapper.updateById(person);
|
|
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);
|
result.setCanExam(Boolean.TRUE.equals(tmp.getCanExam()));
|
result.setExamTip(tmp.getExamTip());
|
return result;
|
}
|
|
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);
|
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 (!"exam".equals(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;
|
}
|
if ("online".equals(task.getTrainMode())) {
|
int required = toRequiredSeconds(person.getHours());
|
int learned = person.getLearnSeconds() == null ? 0 : person.getLearnSeconds();
|
if (required <= 0) {
|
form.setCanExam(false);
|
form.setExamTip("未配置课时,暂不能考试");
|
return;
|
}
|
if (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 (STATUS_CANCELLED.equals(person.getBizStatus())) {
|
return "任务已取消,不可签到";
|
}
|
if (task == null || !"published".equals(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) {
|
if (StringUtil.isEmpty(trainMode)) {
|
return false;
|
}
|
// 配置表优先:sign_rule=both 才允许在线;无配置时按字典约定 online 允许
|
TmsTrainModeEntity mode = trainModeMapper.selectOne(
|
new LambdaQueryWrapper<TmsTrainModeEntity>()
|
.eq(TmsTrainModeEntity::getModeCode, trainMode)
|
.last("LIMIT 1"));
|
if (mode != null && StringUtil.isNotEmpty(mode.getSignRule())) {
|
return "both".equalsIgnoreCase(mode.getSignRule());
|
}
|
return "online".equalsIgnoreCase(trainMode);
|
}
|
|
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) {
|
String url = extractUrl(file.getFileJson());
|
String ext = extractExt(file.getFileJson(), file.getFileName());
|
if ((StringUtil.isEmpty(url) || StringUtil.isEmpty(ext)) && StringUtil.isNotEmpty(file.getMaterialId())) {
|
TmsMaterialEntity material = materialMap.get(file.getMaterialId());
|
if (material != null) {
|
if (StringUtil.isEmpty(url)) {
|
url = extractUrl(material.getFileJson());
|
}
|
if (StringUtil.isEmpty(ext)) {
|
ext = extractExt(material.getFileJson(), material.getMaterialName());
|
}
|
}
|
}
|
TmsPersonTaskFileForm item = new TmsPersonTaskFileForm();
|
item.setId(file.getId());
|
item.setName(file.getFileName());
|
item.setUrl(url);
|
item.setFileExt(ext);
|
item.setPreviewType(resolvePreviewType(ext));
|
list.add(item);
|
}
|
return list;
|
}
|
|
private String extractExt(String fileJson, String fileName) {
|
try {
|
if (StringUtil.isNotEmpty(fileJson)) {
|
String text = fileJson.trim();
|
if (text.startsWith("[")) {
|
List<Map<String, Object>> arr = OBJECT_MAPPER.readValue(text, new TypeReference<List<Map<String, Object>>>() {
|
});
|
if (arr != null && !arr.isEmpty() && arr.get(0).get("fileExtension") != null) {
|
return String.valueOf(arr.get(0).get("fileExtension")).toLowerCase();
|
}
|
if (arr != null && !arr.isEmpty() && arr.get(0).get("name") != null) {
|
fileName = String.valueOf(arr.get(0).get("name"));
|
}
|
} else {
|
Map<String, Object> map = OBJECT_MAPPER.readValue(text, new TypeReference<Map<String, Object>>() {
|
});
|
if (map.get("fileExtension") != null) {
|
return String.valueOf(map.get("fileExtension")).toLowerCase();
|
}
|
if (map.get("name") != null) {
|
fileName = String.valueOf(map.get("name"));
|
}
|
}
|
}
|
} catch (Exception ignored) {
|
// ignore
|
}
|
if (StringUtil.isEmpty(fileName) || !fileName.contains(".")) {
|
return "";
|
}
|
return fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
|
}
|
|
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", "xls", "xlsx", "ppt", "pptx").contains(e)) {
|
return "office";
|
}
|
return "other";
|
}
|
|
private String extractUrl(String fileJson) {
|
if (StringUtil.isEmpty(fileJson)) {
|
return null;
|
}
|
try {
|
String text = fileJson.trim();
|
if (text.startsWith("[")) {
|
List<Map<String, Object>> arr = OBJECT_MAPPER.readValue(text, new TypeReference<List<Map<String, Object>>>() {
|
});
|
if (arr == null || arr.isEmpty() || arr.get(0).get("url") == null) {
|
return null;
|
}
|
return String.valueOf(arr.get(0).get("url"));
|
}
|
Map<String, Object> map = OBJECT_MAPPER.readValue(text, new TypeReference<Map<String, Object>>() {
|
});
|
return map.get("url") == null ? null : String.valueOf(map.get("url"));
|
} catch (Exception ex) {
|
return null;
|
}
|
}
|
|
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 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);
|
}
|
}
|