package jnpf.tmsService.impl;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
import jnpf.audit.sdk.annotation.AuditLog;
|
import jnpf.exception.DataException;
|
import jnpf.permission.OrganizeApi;
|
import jnpf.permission.UserApi;
|
import jnpf.permission.entity.OrganizeEntity;
|
import jnpf.permission.entity.UserEntity;
|
import jnpf.tmsEntity.TmsAnnualPlanItemEntity;
|
import jnpf.tmsEntity.TmsMaterialEntity;
|
import jnpf.tmsEntity.TmsPersonFileEntity;
|
import jnpf.tmsEntity.TmsPersonTaskEntity;
|
import jnpf.tmsEntity.TmsPersonTrainEntity;
|
import jnpf.tmsEntity.TmsPostPlanItemEntity;
|
import jnpf.tmsEntity.TmsQuizScoreEntity;
|
import jnpf.tmsEntity.TmsRecordEntity;
|
import jnpf.tmsEntity.TmsRecordSignEntity;
|
import jnpf.tmsEntity.TmsSignModeEntity;
|
import jnpf.tmsEntity.TmsTaskEntity;
|
import jnpf.tmsEntity.TmsTaskFileEntity;
|
import jnpf.tmsEntity.TmsTaskQuizEntity;
|
import jnpf.tmsEntity.TmsTrainModeEntity;
|
import jnpf.tmsEntity.person.TmsPersonTaskSignResult;
|
import jnpf.tmsEntity.record.TmsQuizAnswerPaperVO;
|
import jnpf.tmsEntity.record.TmsRecordAttachmentForm;
|
import jnpf.tmsEntity.record.TmsRecordForm;
|
import jnpf.tmsEntity.record.TmsRecordIncompleteVO;
|
import jnpf.tmsEntity.record.TmsRecordParticipantForm;
|
import jnpf.tmsEntity.record.TmsRecordProgressVO;
|
import jnpf.tmsEntity.record.TmsRecordQuery;
|
import jnpf.tmsEntity.record.TmsRecordScanSignForm;
|
import jnpf.tmsEntity.record.TmsRecordSignForm;
|
import jnpf.tmsEntity.record.TmsRecordSignQrModeVO;
|
import jnpf.tmsEntity.record.TmsRecordSignQrVO;
|
import jnpf.tmsMapper.TmsAnnualPlanItemMapper;
|
import jnpf.tmsMapper.TmsMaterialMapper;
|
import jnpf.tmsMapper.TmsPersonFileMapper;
|
import jnpf.tmsMapper.TmsPersonTaskMapper;
|
import jnpf.tmsMapper.TmsPersonTrainMapper;
|
import jnpf.tmsMapper.TmsPostPlanItemMapper;
|
import jnpf.tmsMapper.TmsQuizScoreMapper;
|
import jnpf.tmsMapper.TmsRecordMapper;
|
import jnpf.tmsMapper.TmsRecordSignMapper;
|
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.TmsTaskKind;
|
import jnpf.tmsEntity.enums.TmsTaskCategory;
|
import jnpf.tmsEntity.enums.TmsTaskPublishStatus;
|
import jnpf.tmsEntity.enums.TmsArchiveStatus;
|
import jnpf.tmsEntity.enums.TmsTaskSourceType;
|
import jnpf.tmsEntity.enums.TmsPlanLineStatus;
|
import jnpf.tmsEntity.enums.TmsTrainMode;
|
import jnpf.tmsEntity.enums.TmsSignRule;
|
import jnpf.tmsEntity.enums.TmsSignMode;
|
import jnpf.tmsEntity.enums.TmsYesNo;
|
import jnpf.tmsEntity.enums.TmsEvalMode;
|
import jnpf.tmsEntity.enums.TmsValidRevoked;
|
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.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.math.BigDecimal;
|
import java.time.LocalDate;
|
import java.time.format.DateTimeFormatter;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.Calendar;
|
import java.util.Collections;
|
import java.util.Comparator;
|
import java.util.Date;
|
import java.util.HashMap;
|
import java.util.HashSet;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Set;
|
import java.util.stream.Collectors;
|
|
/**
|
* 培训记录:签到二维码、归档状态机、归档写个人培训目录。
|
*
|
* <p>归档状态({@link jnpf.tmsEntity.enums.TmsArchiveStatus}):
|
* open → pending(关闭时间到)→ ready(可选)→ archived;或 invalid。</p>
|
* <p>归档成功后才写入 {@code tms_person_train}(个人档案「培训目录」);
|
* 学员学完/考过不会直接写目录。</p>
|
*
|
* @see #archive(java.util.List)
|
* @see #archiveOne(jnpf.tmsEntity.TmsRecordEntity, java.util.Date, String)
|
*/
|
@Service
|
@RequiredArgsConstructor
|
public class TmsRecordServiceImpl implements TmsRecordService {
|
|
private static final String FMT = "yyyy-MM-dd HH:mm:ss";
|
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
private static final long SIGN_QR_TTL_MS = 5L * 60L * 1000L;
|
private static final String PREFIX_DRAFT = "TD";
|
/** 归档/作废产生的再培训草稿:category 固定 retrain(字典 tmsTaskCategory) */
|
|
private final TmsRecordMapper recordMapper;
|
private final TmsRecordSignMapper recordSignMapper;
|
private final TmsTaskMapper taskMapper;
|
private final TmsTaskFileMapper taskFileMapper;
|
private final TmsMaterialMapper materialMapper;
|
private final TmsPersonTaskMapper personTaskMapper;
|
private final TmsPersonTrainMapper personTrainMapper;
|
private final TmsPersonFileMapper personFileMapper;
|
private final TmsTrainModeMapper trainModeMapper;
|
private final TmsSignModeMapper signModeMapper;
|
private final TmsAnnualPlanItemMapper annualPlanItemMapper;
|
private final TmsPostPlanItemMapper postPlanItemMapper;
|
private final TmsPersonTaskService personTaskService;
|
private final TmsQuizScoreMapper quizScoreMapper;
|
private final TmsTaskQuizMapper taskQuizMapper;
|
private final UserApi userApi;
|
private final OrganizeApi organizeApi;
|
|
/**
|
* 培训记录:条件分页查询列表
|
*/
|
@Override
|
public List<TmsRecordForm> getList(TmsRecordQuery query) {
|
LambdaQueryWrapper<TmsRecordEntity> qw = new LambdaQueryWrapper<>();
|
applyListMode(qw, query);
|
|
if (StringUtil.isNotEmpty(query.getRecordNo())) {
|
qw.like(TmsRecordEntity::getRecordNo, query.getRecordNo().trim());
|
}
|
if (StringUtil.isNotEmpty(query.getSubject())) {
|
qw.like(TmsRecordEntity::getSubject, query.getSubject().trim());
|
}
|
if (StringUtil.isNotEmpty(query.getCategory())) {
|
qw.eq(TmsRecordEntity::getCategory, query.getCategory().trim());
|
}
|
if (StringUtil.isNotEmpty(query.getTrainMode())) {
|
qw.eq(TmsRecordEntity::getTrainMode, query.getTrainMode().trim());
|
}
|
if (StringUtil.isNotEmpty(query.getEvalMode())) {
|
qw.eq(TmsRecordEntity::getEvalMode, query.getEvalMode().trim());
|
}
|
if (StringUtil.isNotEmpty(query.getKeyword()) && !"null".equalsIgnoreCase(query.getKeyword())) {
|
String kw = query.getKeyword().trim();
|
qw.and(w -> w.like(TmsRecordEntity::getRecordNo, kw)
|
.or().like(TmsRecordEntity::getSubject, kw));
|
}
|
Date from = parseDayStart(query.getStartTimeFrom());
|
Date to = parseDayEnd(query.getStartTimeTo());
|
if (from != null) {
|
qw.ge(TmsRecordEntity::getStartTime, from);
|
}
|
if (to != null) {
|
qw.le(TmsRecordEntity::getStartTime, to);
|
}
|
Date endFrom = parseDayStart(query.getEndTimeFrom());
|
Date endTo = parseDayEnd(query.getEndTimeTo());
|
if (endFrom != null) {
|
qw.ge(TmsRecordEntity::getEndTime, endFrom);
|
}
|
if (endTo != null) {
|
qw.le(TmsRecordEntity::getEndTime, endTo);
|
}
|
qw.orderByDesc(TmsRecordEntity::getStartTime).orderByDesc(TmsRecordEntity::getCreatorTime);
|
|
long current = query.getCurrentPage() > 0 ? query.getCurrentPage() : 1;
|
long size = query.getPageSize() > 0 ? query.getPageSize() : 20;
|
Page<TmsRecordEntity> page = recordMapper.selectPage(new Page<>(current, size), qw);
|
|
List<TmsRecordEntity> rows = page.getRecords();
|
Map<String, String> names = loadUserNames(rows.stream()
|
.map(TmsRecordEntity::getTeacherId)
|
.filter(StringUtil::isNotEmpty)
|
.distinct()
|
.collect(Collectors.toList()));
|
|
List<TmsRecordForm> list = new ArrayList<>(rows.size());
|
String trainerFilter = StringUtil.isNotEmpty(query.getTrainerName()) ? query.getTrainerName().trim() : null;
|
for (TmsRecordEntity row : rows) {
|
TmsRecordForm form = toListForm(row, names);
|
if (trainerFilter != null) {
|
String name = form.getTrainerName() == null ? "" : form.getTrainerName();
|
if (!name.contains(trainerFilter)) {
|
continue;
|
}
|
}
|
list.add(form);
|
}
|
query.setData(list, page.getTotal());
|
return list;
|
}
|
/**
|
* 培训记录:按主键查询详情
|
*/
|
@Override
|
public TmsRecordForm getInfo(String id) {
|
TmsRecordEntity entity = requireOne(id);
|
Map<String, String> teacherNames = loadUserNames(
|
StringUtil.isEmpty(entity.getTeacherId())
|
? Collections.emptyList()
|
: Collections.singletonList(entity.getTeacherId()));
|
TmsRecordForm form = toListForm(entity, teacherNames);
|
|
TmsTaskEntity task = StringUtil.isEmpty(entity.getTaskId()) ? null : taskMapper.selectById(entity.getTaskId());
|
if (task != null) {
|
form.setKeyPoints(task.getKeyPoints());
|
form.setPlaceName(task.getPlaceName());
|
if (StringUtil.isEmpty(form.getTrainType()) && StringUtil.isNotEmpty(task.getTrainType())) {
|
form.setTrainType(task.getTrainType());
|
}
|
if (StringUtil.isEmpty(form.getTrainMode()) && StringUtil.isNotEmpty(task.getTrainMode())) {
|
form.setTrainMode(task.getTrainMode());
|
}
|
}
|
String trainMode = StringUtil.isNotEmpty(form.getTrainMode())
|
? form.getTrainMode()
|
: (task != null ? task.getTrainMode() : null);
|
String signRule = resolveSignRule(trainMode);
|
form.setSignRule(signRule);
|
form.setCanScanSign(canScanByRule(signRule));
|
|
List<TmsPersonTaskEntity> persons = StringUtil.isEmpty(entity.getTaskId())
|
? Collections.emptyList()
|
: personTaskMapper.selectList(new LambdaQueryWrapper<TmsPersonTaskEntity>()
|
.eq(TmsPersonTaskEntity::getTaskId, entity.getTaskId())
|
.orderByAsc(TmsPersonTaskEntity::getCreatorTime));
|
|
Map<String, String> userNames = loadUserNames(persons.stream()
|
.map(TmsPersonTaskEntity::getUserId)
|
.filter(StringUtil::isNotEmpty)
|
.distinct()
|
.collect(Collectors.toList()));
|
Set<String> deptIds = new HashSet<>();
|
for (TmsPersonTaskEntity p : persons) {
|
if (p != null && StringUtil.isNotEmpty(p.getDeptId())) {
|
deptIds.add(p.getDeptId());
|
}
|
}
|
Map<String, String> deptNames = loadDeptNames(deptIds);
|
|
List<TmsRecordParticipantForm> participants = new ArrayList<>(persons.size());
|
List<TmsRecordSignForm> signs = new ArrayList<>();
|
List<String> traineeNames = new ArrayList<>();
|
|
// 优先读签到明细表;无数据时回退 person_task 投影
|
List<TmsRecordSignEntity> signRows = recordSignMapper.selectList(
|
new LambdaQueryWrapper<TmsRecordSignEntity>()
|
.eq(TmsRecordSignEntity::getForeignId, entity.getId())
|
.orderByAsc(TmsRecordSignEntity::getSignTime));
|
Map<String, TmsRecordSignEntity> signByUser = new HashMap<>();
|
if (signRows != null) {
|
for (TmsRecordSignEntity s : signRows) {
|
if (s != null && StringUtil.isNotEmpty(s.getUserId())) {
|
signByUser.putIfAbsent(s.getUserId(), s);
|
}
|
}
|
}
|
Set<String> quizAnswerUsers = loadQuizAnswerUserIds(entity.getTaskId(), entity.getId());
|
|
for (TmsPersonTaskEntity p : persons) {
|
String userName = userNames.getOrDefault(p.getUserId(), p.getUserId());
|
traineeNames.add(userName);
|
|
TmsRecordParticipantForm pf = new TmsRecordParticipantForm();
|
pf.setId(p.getId());
|
pf.setUserId(p.getUserId());
|
pf.setUserName(userName);
|
pf.setDeptName(deptNames.getOrDefault(p.getDeptId(), ""));
|
pf.setSignDate(formatDate(p.getSignTime()));
|
pf.setExamScore(p.getExamScore());
|
pf.setPassFlag(p.getPassFlag());
|
TmsRecordSignEntity sign = signByUser.get(p.getUserId());
|
if (sign != null && sign.getQuizScore() != null) {
|
pf.setQuizScore(sign.getQuizScore());
|
}
|
pf.setHasQuizAnswer(quizAnswerUsers.contains(p.getUserId()));
|
participants.add(pf);
|
}
|
|
if (signRows != null && !signRows.isEmpty()) {
|
Map<String, String> signNames = loadUserNames(signRows.stream()
|
.map(TmsRecordSignEntity::getUserId)
|
.filter(StringUtil::isNotEmpty)
|
.distinct()
|
.collect(Collectors.toList()));
|
for (TmsRecordSignEntity s : signRows) {
|
TmsRecordSignForm sf = new TmsRecordSignForm();
|
sf.setId(s.getId());
|
sf.setUserName(StringUtil.isNotEmpty(s.getUserName())
|
? s.getUserName()
|
: signNames.getOrDefault(s.getUserId(), s.getUserId()));
|
sf.setSignTime(formatDate(s.getSignTime()));
|
sf.setSignMode(s.getSignMode());
|
sf.setGuestFlag(s.getGuestFlag());
|
signs.add(sf);
|
}
|
} else {
|
for (TmsPersonTaskEntity p : persons) {
|
if (p.getSignTime() == null) {
|
continue;
|
}
|
String userName = userNames.getOrDefault(p.getUserId(), p.getUserId());
|
TmsRecordSignForm sf = new TmsRecordSignForm();
|
sf.setId(p.getId());
|
sf.setUserName(userName);
|
sf.setSignTime(formatDate(p.getSignTime()));
|
sf.setSignMode(p.getSignMode());
|
sf.setGuestFlag(p.getGuestFlag());
|
signs.add(sf);
|
}
|
}
|
form.setParticipants(participants);
|
form.setSignList(signs);
|
form.setTrainees(String.join(",", traineeNames));
|
form.setAttachments(loadAttachments(entity));
|
form.setRemark(entity.getRemark());
|
return form;
|
}
|
/**
|
* 获取签到二维码信息
|
*/
|
@Override
|
public TmsRecordSignQrVO getSignQr(String id) {
|
TmsRecordEntity entity = requireOne(id);
|
if (TmsArchiveStatus.ARCHIVED.equalsCode(entity.getArchiveStatus())) {
|
throw new DataException("已存档记录不可生成签到码");
|
}
|
TmsTaskEntity task = StringUtil.isEmpty(entity.getTaskId()) ? null : taskMapper.selectById(entity.getTaskId());
|
String trainMode = StringUtil.isNotEmpty(entity.getTrainMode())
|
? entity.getTrainMode()
|
: (task != null ? task.getTrainMode() : null);
|
String signRule = resolveSignRule(trainMode);
|
|
TmsRecordSignQrVO vo = new TmsRecordSignQrVO();
|
vo.setRecordId(entity.getId());
|
vo.setTaskId(entity.getTaskId());
|
vo.setSubject(entity.getSubject());
|
vo.setSignRule(signRule);
|
vo.setModes(Collections.emptyList());
|
|
if (!canScanByRule(signRule)) {
|
vo.setCanScanSign(false);
|
vo.setTip("当前培训方式不支持扫码签到,请学员在「个人培训任务」中在线签到");
|
return vo;
|
}
|
|
List<TmsSignModeEntity> scanModes = signModeMapper.selectList(
|
new LambdaQueryWrapper<TmsSignModeEntity>()
|
.eq(TmsSignModeEntity::getEnabled, TmsYesNo.YES.getCode())
|
.in(TmsSignModeEntity::getModeCode, Arrays.asList(
|
TmsSignMode.APP_SCAN.getCode(), TmsSignMode.WX_SCAN.getCode()))
|
.orderByAsc(TmsSignModeEntity::getModeCode));
|
List<TmsRecordSignQrModeVO> modes = new ArrayList<>();
|
for (TmsSignModeEntity sm : scanModes) {
|
TmsRecordSignQrModeVO m = new TmsRecordSignQrModeVO();
|
m.setModeCode(sm.getModeCode());
|
m.setModeName(sm.getModeName());
|
modes.add(m);
|
}
|
vo.setModes(modes);
|
if (modes.isEmpty()) {
|
vo.setCanScanSign(false);
|
vo.setTip("暂无启用的扫码签到方式(APP扫码/微信扫码),请先在签到方式中启用");
|
return vo;
|
}
|
|
long expMs = System.currentTimeMillis() + SIGN_QR_TTL_MS;
|
Date expireAt = new Date(expMs);
|
Map<String, Object> payload = new LinkedHashMap<>();
|
payload.put("t", "tms_sign");
|
payload.put("recordId", entity.getId());
|
payload.put("taskId", entity.getTaskId());
|
payload.put("exp", expMs);
|
try {
|
vo.setQrContent(OBJECT_MAPPER.writeValueAsString(payload));
|
} catch (Exception e) {
|
throw new DataException("生成签到码失败");
|
}
|
vo.setExpireAt(DateUtil.dateToString(expireAt, FMT));
|
vo.setCanScanSign(true);
|
vo.setTip("");
|
return vo;
|
}
|
/**
|
* 扫码签到
|
*/
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public TmsPersonTaskSignResult scanSign(TmsRecordScanSignForm form) {
|
if (form == null) {
|
throw new DataException("签到参数不能为空");
|
}
|
return personTaskService.scanSign(form.getQrContent(), form.getSignMode());
|
}
|
|
/**
|
* 解析签到规则
|
*/
|
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();
|
}
|
|
/**
|
* 按规则判断是否允许扫码签到
|
*/
|
private boolean canScanByRule(String signRule) {
|
return TmsSignRule.SCAN.equalsCodeIgnoreCase(signRule) || TmsSignRule.BOTH.equalsCodeIgnoreCase(signRule);
|
}
|
/**
|
* 培训记录:更新
|
*/
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void update(String id, TmsRecordForm form) {
|
TmsRecordEntity entity = requireOne(id);
|
if (form == null) {
|
throw new DataException("表单不能为空");
|
}
|
if (StringUtil.isNotEmpty(form.getSubject())) {
|
entity.setSubject(form.getSubject().trim());
|
}
|
if (form.getCategory() != null) {
|
entity.setCategory(form.getCategory());
|
}
|
if (form.getTrainType() != null) {
|
entity.setTrainType(form.getTrainType());
|
}
|
if (form.getTrainMode() != null) {
|
entity.setTrainMode(form.getTrainMode());
|
}
|
if (form.getEvalMode() != null) {
|
entity.setEvalMode(form.getEvalMode());
|
}
|
entity.setRemark(form.getRemark());
|
entity.setLastModifyUserId(UserProvider.getLoginUserId());
|
entity.setLastModifyTime(new Date());
|
recordMapper.updateById(entity);
|
}
|
/**
|
* 按结束时间将记录置为可归档
|
*/
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public int markReadyByCloseTime() {
|
Date now = new Date();
|
// FDS:关闭时间到 → pending;进行中为 open
|
List<TmsRecordEntity> rows = recordMapper.selectList(
|
new LambdaQueryWrapper<TmsRecordEntity>()
|
.eq(TmsRecordEntity::getArchiveStatus, TmsArchiveStatus.OPEN.getCode())
|
.isNotNull(TmsRecordEntity::getCloseTime)
|
.le(TmsRecordEntity::getCloseTime, now));
|
if (rows == null || rows.isEmpty()) {
|
return 0;
|
}
|
String userId = UserProvider.getLoginUserId();
|
int count = 0;
|
for (TmsRecordEntity entity : rows) {
|
if (entity == null || StringUtil.isEmpty(entity.getId())) {
|
continue;
|
}
|
entity.setArchiveStatus(TmsArchiveStatus.PENDING.getCode());
|
entity.setLastModifyUserId(userId);
|
entity.setLastModifyTime(now);
|
recordMapper.updateById(entity);
|
count++;
|
}
|
return count;
|
}
|
/**
|
* 推进为可归档
|
*/
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public int promoteToReady(List<String> ids) {
|
if (ids == null || ids.isEmpty()) {
|
throw new DataException("请选择要转入可归档的记录");
|
}
|
int count = 0;
|
Date now = new Date();
|
String userId = UserProvider.getLoginUserId();
|
for (String id : ids) {
|
if (StringUtil.isEmpty(id)) {
|
continue;
|
}
|
TmsRecordEntity entity = recordMapper.selectById(id);
|
if (entity == null) {
|
continue;
|
}
|
if (!TmsArchiveStatus.PENDING.equalsCode(entity.getArchiveStatus())) {
|
throw new DataException("仅待归档状态可转入可归档:" + entity.getRecordNo());
|
}
|
entity.setArchiveStatus(TmsArchiveStatus.READY.getCode());
|
entity.setLastModifyUserId(userId);
|
entity.setLastModifyTime(now);
|
recordMapper.updateById(entity);
|
count++;
|
}
|
return count;
|
}
|
/**
|
* 恢复为待处理
|
*/
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public int restoreToPending(List<String> ids) {
|
if (ids == null || ids.isEmpty()) {
|
throw new DataException("请选择要恢复的记录");
|
}
|
int count = 0;
|
Date now = new Date();
|
String userId = UserProvider.getLoginUserId();
|
for (String id : ids) {
|
if (StringUtil.isEmpty(id)) {
|
continue;
|
}
|
TmsRecordEntity entity = recordMapper.selectById(id);
|
if (entity == null) {
|
continue;
|
}
|
if (!TmsArchiveStatus.READY.equalsCode(entity.getArchiveStatus())) {
|
throw new DataException("仅可归档状态可恢复为待归档:" + entity.getRecordNo());
|
}
|
entity.setArchiveStatus(TmsArchiveStatus.PENDING.getCode());
|
entity.setLastModifyUserId(userId);
|
entity.setLastModifyTime(now);
|
recordMapper.updateById(entity);
|
count++;
|
}
|
return count;
|
}
|
|
/**
|
* 批量归档:仅 pending / ready 可归档。
|
* 每条走 {@link #archiveOne}(写 person_train、任务 done、未完成人再培训草稿)。
|
*/
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
@AuditLog(action = "RECORD_ARCHIVE", label = "归档培训记录", bizType = "TMS_RECORD",
|
bizCodeExpr = "#ids != null && !#ids.isEmpty() ? #ids[0] : 'BATCH'",
|
targetTable = "tms_record", targetIdExpr = "#ids != null && !#ids.isEmpty() ? #ids[0] : ''",
|
targetIdsExpr = "#ids", bizCodeRequired = false)
|
/**
|
* 归档
|
*/
|
public int archive(List<String> ids) {
|
if (ids == null || ids.isEmpty()) {
|
throw new DataException("请选择要归档的记录");
|
}
|
int count = 0;
|
Date now = new Date();
|
String userId = UserProvider.getLoginUserId();
|
for (String id : ids) {
|
if (StringUtil.isEmpty(id)) {
|
continue;
|
}
|
TmsRecordEntity entity = recordMapper.selectById(id);
|
if (entity == null) {
|
continue;
|
}
|
String st = entity.getArchiveStatus();
|
if (!TmsArchiveStatus.PENDING.equalsCode(st) && !TmsArchiveStatus.READY.equalsCode(st)) {
|
throw new DataException("仅待归档/可归档状态下的记录可归档:" + entity.getRecordNo());
|
}
|
archiveOne(entity, now, userId);
|
count++;
|
}
|
return count;
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
@AuditLog(action = "RECORD_INVALID", label = "作废培训记录", bizType = "TMS_RECORD",
|
bizCodeExpr = "#ids != null && !#ids.isEmpty() ? #ids[0] : 'BATCH'",
|
targetTable = "tms_record", targetIdExpr = "#ids != null && !#ids.isEmpty() ? #ids[0] : ''",
|
targetIdsExpr = "#ids", bizCodeRequired = false)
|
/**
|
* 标记作废
|
*/
|
public int markInvalid(List<String> ids) {
|
if (ids == null || ids.isEmpty()) {
|
throw new DataException("请选择要作废的记录");
|
}
|
int count = 0;
|
Date now = new Date();
|
String operatorId = UserProvider.getLoginUserId();
|
for (String id : ids) {
|
if (StringUtil.isEmpty(id)) {
|
continue;
|
}
|
TmsRecordEntity entity = recordMapper.selectById(id);
|
if (entity == null) {
|
continue;
|
}
|
String st = entity.getArchiveStatus();
|
if (TmsArchiveStatus.ARCHIVED.equalsCode(st) || TmsArchiveStatus.INVALID.equalsCode(st)) {
|
throw new DataException("已归档/已作废记录不可再作废:" + entity.getRecordNo());
|
}
|
List<TmsPersonTaskEntity> persons = StringUtil.isEmpty(entity.getTaskId())
|
? Collections.emptyList()
|
: personTaskMapper.selectList(new LambdaQueryWrapper<TmsPersonTaskEntity>()
|
.eq(TmsPersonTaskEntity::getTaskId, entity.getTaskId()));
|
boolean anyoneDone = false;
|
List<TmsPersonTaskEntity> incomplete = new ArrayList<>();
|
for (TmsPersonTaskEntity p : persons) {
|
if (p == null || "1".equals(p.getGuestFlag())) {
|
continue;
|
}
|
if (isCompletedForInvalid(entity.getEvalMode(), p)) {
|
anyoneDone = true;
|
break;
|
}
|
incomplete.add(p);
|
}
|
if (anyoneDone) {
|
throw new DataException("已有人员完成,不可作废:" + entity.getRecordNo());
|
}
|
entity.setArchiveStatus(TmsArchiveStatus.INVALID.getCode());
|
entity.setLastModifyUserId(operatorId);
|
entity.setLastModifyTime(now);
|
recordMapper.updateById(entity);
|
|
if (StringUtil.isNotEmpty(entity.getTaskId())) {
|
TmsTaskEntity task = taskMapper.selectById(entity.getTaskId());
|
if (task != null && !TmsTaskPublishStatus.CANCELLED.equalsCode(task.getPublishStatus())
|
&& !TmsTaskPublishStatus.DONE.equalsCode(task.getPublishStatus())) {
|
task.setPublishStatus(TmsTaskPublishStatus.CANCELLED.getCode());
|
task.setLastModifyUserId(operatorId);
|
task.setLastModifyTime(now);
|
taskMapper.updateById(task);
|
}
|
}
|
if (!incomplete.isEmpty()) {
|
createContinueDraftTask(entity, incomplete, now, operatorId);
|
} else if (StringUtil.isNotEmpty(entity.getTaskId())) {
|
// 无人完成也无人名册时,仍按原任务对象生成空壳再维护草稿(复制原 trainees)
|
TmsTaskEntity src = taskMapper.selectById(entity.getTaskId());
|
if (src != null && StringUtil.isNotEmpty(src.getTrainees())) {
|
List<TmsPersonTaskEntity> roster = new ArrayList<>();
|
for (String uid : src.getTrainees().split(",")) {
|
if (StringUtil.isEmpty(uid)) {
|
continue;
|
}
|
TmsPersonTaskEntity stub = new TmsPersonTaskEntity();
|
stub.setUserId(uid.trim());
|
roster.add(stub);
|
}
|
if (!roster.isEmpty()) {
|
createContinueDraftTask(entity, roster, now, operatorId);
|
}
|
}
|
}
|
count++;
|
}
|
return count;
|
}
|
|
/** 作废判定:在线考试须合格;其它方式已签到视为完成 */
|
private boolean isCompletedForInvalid(String evalMode, TmsPersonTaskEntity person) {
|
if (person.getSignTime() == null) {
|
return false;
|
}
|
if (TmsEvalMode.EXAM.equalsCodeIgnoreCase(evalMode)) {
|
return TmsPassFlag.isPass(person.getPassFlag());
|
}
|
return true;
|
}
|
/**
|
* 统计测验/考试次数
|
*/
|
@Override
|
public int calcQuizCount(String recordId) {
|
if (StringUtil.isEmpty(recordId)) {
|
throw new DataException("记录ID不能为空");
|
}
|
TmsRecordEntity record = recordMapper.selectById(recordId);
|
if (record == null) {
|
throw new DataException("培训记录不存在");
|
}
|
if (StringUtil.isEmpty(record.getTaskId())) {
|
return 0;
|
}
|
TmsTaskEntity task = taskMapper.selectById(record.getTaskId());
|
if (task == null) {
|
return 0;
|
}
|
Long signed = personTaskMapper.selectCount(
|
new LambdaQueryWrapper<TmsPersonTaskEntity>()
|
.eq(TmsPersonTaskEntity::getTaskId, record.getTaskId())
|
.isNotNull(TmsPersonTaskEntity::getSignTime)
|
.and(w -> w.isNull(TmsPersonTaskEntity::getGuestFlag)
|
.or().ne(TmsPersonTaskEntity::getGuestFlag, "1")));
|
int signedCount = signed == null ? 0 : signed.intValue();
|
return TmsQuizHelper.quizCount(signedCount, task.getQuizRatio());
|
}
|
|
@Override
|
public TmsQuizAnswerPaperVO getQuizAnswers(String recordId, String userId) {
|
TmsRecordEntity record = requireOne(recordId);
|
if (StringUtil.isEmpty(userId)) {
|
throw new DataException("用户ID不能为空");
|
}
|
if (StringUtil.isEmpty(record.getTaskId())) {
|
throw new DataException("培训记录未关联任务");
|
}
|
List<TmsQuizScoreEntity> scores = quizScoreMapper.selectList(new LambdaQueryWrapper<TmsQuizScoreEntity>()
|
.eq(TmsQuizScoreEntity::getTaskId, record.getTaskId())
|
.eq(TmsQuizScoreEntity::getUserId, userId.trim())
|
.and(w -> w.eq(TmsQuizScoreEntity::getRecordId, record.getId())
|
.or().isNull(TmsQuizScoreEntity::getRecordId))
|
.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, record.getTaskId())
|
.orderByAsc(TmsTaskQuizEntity::getSortNo)
|
.orderByAsc(TmsTaskQuizEntity::getCreatorTime));
|
Map<String, String> names = loadUserNames(Collections.singletonList(userId.trim()));
|
String userName = names.getOrDefault(userId.trim(), userId.trim());
|
String passFlag = null;
|
BigDecimal quizScore = null;
|
TmsPersonTaskEntity person = personTaskMapper.selectOne(new LambdaQueryWrapper<TmsPersonTaskEntity>()
|
.eq(TmsPersonTaskEntity::getTaskId, record.getTaskId())
|
.eq(TmsPersonTaskEntity::getUserId, userId.trim())
|
.last("LIMIT 1"));
|
if (person != null) {
|
passFlag = person.getPassFlag();
|
}
|
TmsRecordSignEntity sign = recordSignMapper.selectOne(new LambdaQueryWrapper<TmsRecordSignEntity>()
|
.eq(TmsRecordSignEntity::getForeignId, record.getId())
|
.eq(TmsRecordSignEntity::getUserId, userId.trim())
|
.last("LIMIT 1"));
|
if (sign != null) {
|
quizScore = sign.getQuizScore();
|
if (StringUtil.isEmpty(passFlag)) {
|
passFlag = sign.getResultFlag();
|
}
|
}
|
String quizTime = formatDate(scores.get(0).getQuizTime() != null
|
? scores.get(0).getQuizTime()
|
: scores.get(0).getCreatorTime());
|
TmsQuizAnswerPaperVO paper = TmsQuizHelper.buildAnswerPaper(
|
record.getId(),
|
record.getTaskId(),
|
userId.trim(),
|
userName,
|
passFlag,
|
quizTime,
|
scores,
|
presets);
|
if (quizScore != null) {
|
paper.setTotalScore(quizScore);
|
}
|
return paper;
|
}
|
|
private Set<String> loadQuizAnswerUserIds(String taskId, String recordId) {
|
if (StringUtil.isEmpty(taskId)) {
|
return Collections.emptySet();
|
}
|
List<TmsQuizScoreEntity> rows = quizScoreMapper.selectList(new LambdaQueryWrapper<TmsQuizScoreEntity>()
|
.select(TmsQuizScoreEntity::getUserId)
|
.eq(TmsQuizScoreEntity::getTaskId, taskId)
|
.and(w -> w.eq(TmsQuizScoreEntity::getRecordId, recordId)
|
.or().isNull(TmsQuizScoreEntity::getRecordId)));
|
if (rows == null || rows.isEmpty()) {
|
return Collections.emptySet();
|
}
|
return rows.stream()
|
.map(TmsQuizScoreEntity::getUserId)
|
.filter(StringUtil::isNotEmpty)
|
.collect(Collectors.toSet());
|
}
|
|
/**
|
* 归档单条核心:
|
* <ol>
|
* <li>统计签到率 / 合格率(访客不计)</li>
|
* <li>记录 → archived;任务 → done;计划明细 → done</li>
|
* <li>每位非访客学员 {@link #upsertPersonTrain} → 个人档案培训目录</li>
|
* <li>未完成人 {@link #createContinueDraftTask}(不自动发布)</li>
|
* </ol>
|
*/
|
private void archiveOne(TmsRecordEntity entity, Date now, String operatorId) {
|
String taskId = entity.getTaskId();
|
List<TmsPersonTaskEntity> persons = Collections.emptyList();
|
if (StringUtil.isNotEmpty(taskId)) {
|
persons = personTaskMapper.selectList(
|
new LambdaQueryWrapper<TmsPersonTaskEntity>()
|
.eq(TmsPersonTaskEntity::getTaskId, taskId)
|
.orderByAsc(TmsPersonTaskEntity::getCreatorTime));
|
}
|
|
int total = 0;
|
int signed = 0;
|
int passed = 0;
|
List<TmsPersonTaskEntity> incomplete = new ArrayList<>();
|
for (TmsPersonTaskEntity p : persons) {
|
if (p == null || "1".equals(p.getGuestFlag())) {
|
continue;
|
}
|
total++;
|
boolean hasSign = p.getSignTime() != null;
|
if (hasSign) {
|
signed++;
|
}
|
boolean pass = TmsPassFlag.isPass(p.getPassFlag());
|
if (pass) {
|
passed++;
|
}
|
if (isIncomplete(entity.getEvalMode(), p)) {
|
incomplete.add(p);
|
}
|
}
|
entity.setSignRate(rateFloor(signed, total));
|
entity.setPassRate(rateFloor(passed, total));
|
entity.setArchiveStatus(TmsArchiveStatus.ARCHIVED.getCode());
|
entity.setArchiveTime(now);
|
entity.setArchiverId(operatorId);
|
entity.setLastModifyUserId(operatorId);
|
entity.setLastModifyTime(now);
|
recordMapper.updateById(entity);
|
|
if (StringUtil.isNotEmpty(taskId)) {
|
TmsTaskEntity task = taskMapper.selectById(taskId);
|
if (task != null) {
|
task.setSignRate(entity.getSignRate());
|
task.setPassRate(entity.getPassRate());
|
task.setPublishStatus(TmsTaskPublishStatus.DONE.getCode());
|
task.setLastModifyUserId(operatorId);
|
task.setLastModifyTime(now);
|
taskMapper.updateById(task);
|
markPlanItemDone(task);
|
}
|
}
|
|
for (TmsPersonTaskEntity p : persons) {
|
if (p == null || "1".equals(p.getGuestFlag()) || StringUtil.isEmpty(p.getUserId())) {
|
continue;
|
}
|
upsertPersonTrain(entity, p, now, operatorId);
|
}
|
|
if (!incomplete.isEmpty()) {
|
createContinueDraftTask(entity, incomplete, now, operatorId);
|
}
|
}
|
|
/**
|
* 实时参与进度
|
*/
|
@Override
|
public TmsRecordProgressVO getProgress(String recordId) {
|
TmsRecordEntity record = requireOne(recordId);
|
List<TmsPersonTaskEntity> persons = listNonGuestPersons(record.getTaskId());
|
return buildProgress(record, persons);
|
}
|
|
/**
|
* 未完成人员清单(按部门名称、姓名排序)
|
*/
|
@Override
|
public List<TmsRecordIncompleteVO> listIncomplete(String recordId) {
|
TmsRecordEntity record = requireOne(recordId);
|
String evalMode = record.getEvalMode();
|
List<TmsPersonTaskEntity> persons = listNonGuestPersons(record.getTaskId());
|
List<TmsPersonTaskEntity> incomplete = new ArrayList<>();
|
for (TmsPersonTaskEntity p : persons) {
|
if (isIncomplete(evalMode, p)) {
|
incomplete.add(p);
|
}
|
}
|
Map<String, String> userNames = loadUserNames(incomplete.stream()
|
.map(TmsPersonTaskEntity::getUserId)
|
.filter(StringUtil::isNotEmpty)
|
.distinct()
|
.collect(Collectors.toList()));
|
Set<String> deptIds = new HashSet<>();
|
for (TmsPersonTaskEntity p : incomplete) {
|
if (StringUtil.isNotEmpty(p.getDeptId())) {
|
deptIds.add(p.getDeptId());
|
}
|
}
|
Map<String, String> deptNames = loadDeptNames(deptIds);
|
|
List<TmsRecordIncompleteVO> list = new ArrayList<>(incomplete.size());
|
for (TmsPersonTaskEntity p : incomplete) {
|
TmsRecordIncompleteVO vo = new TmsRecordIncompleteVO();
|
vo.setPersonTaskId(p.getId());
|
vo.setUserId(p.getUserId());
|
vo.setUserName(userNames.getOrDefault(p.getUserId(), p.getUserId()));
|
vo.setDeptId(p.getDeptId());
|
vo.setDeptName(deptNames.getOrDefault(p.getDeptId(), ""));
|
vo.setBizStatus(p.getBizStatus());
|
vo.setSignTime(formatDate(p.getSignTime()));
|
vo.setExamScore(p.getExamScore());
|
vo.setPassFlag(p.getPassFlag());
|
vo.setReason(incompleteReason(evalMode, p));
|
list.add(vo);
|
}
|
list.sort(Comparator
|
.comparing((TmsRecordIncompleteVO v) -> StringUtil.isEmpty(v.getDeptName()) ? "" : v.getDeptName())
|
.thenComparing(v -> StringUtil.isEmpty(v.getUserName()) ? "" : v.getUserName()));
|
return list;
|
}
|
|
/**
|
* 按任务回写记录签到率/合格率
|
*/
|
@Override
|
public void refreshRatesByTaskId(String taskId) {
|
if (StringUtil.isEmpty(taskId)) {
|
return;
|
}
|
TmsRecordEntity record = recordMapper.selectOne(new LambdaQueryWrapper<TmsRecordEntity>()
|
.eq(TmsRecordEntity::getTaskId, taskId)
|
.last("LIMIT 1"));
|
if (record == null) {
|
return;
|
}
|
List<TmsPersonTaskEntity> persons = listNonGuestPersons(taskId);
|
TmsRecordProgressVO progress = buildProgress(record, persons);
|
record.setSignRate(progress.getSignRate());
|
record.setPassRate(progress.getPassRate());
|
record.setLastModifyTime(new Date());
|
recordMapper.updateById(record);
|
|
TmsTaskEntity task = taskMapper.selectById(taskId);
|
if (task != null) {
|
task.setSignRate(progress.getSignRate());
|
task.setPassRate(progress.getPassRate());
|
task.setLastModifyTime(new Date());
|
taskMapper.updateById(task);
|
}
|
}
|
|
private TmsRecordProgressVO buildProgress(TmsRecordEntity record, List<TmsPersonTaskEntity> persons) {
|
int total = 0;
|
int signed = 0;
|
int passed = 0;
|
int incomplete = 0;
|
String evalMode = record.getEvalMode();
|
for (TmsPersonTaskEntity p : persons) {
|
if (p == null) {
|
continue;
|
}
|
total++;
|
if (p.getSignTime() != null) {
|
signed++;
|
}
|
if (TmsPassFlag.isPass(p.getPassFlag())) {
|
passed++;
|
}
|
if (isIncomplete(evalMode, p)) {
|
incomplete++;
|
}
|
}
|
TmsRecordProgressVO vo = new TmsRecordProgressVO();
|
vo.setRecordId(record.getId());
|
vo.setTaskId(record.getTaskId());
|
vo.setTotalCount(total);
|
vo.setSignedCount(signed);
|
vo.setPassedCount(passed);
|
vo.setIncompleteCount(incomplete);
|
vo.setSignRate(rateFloor(signed, total));
|
vo.setPassRate(rateFloor(passed, total));
|
vo.setIncompleteRate(rateFloor(incomplete, total));
|
vo.setEvalMode(evalMode);
|
return vo;
|
}
|
|
private List<TmsPersonTaskEntity> listNonGuestPersons(String taskId) {
|
if (StringUtil.isEmpty(taskId)) {
|
return Collections.emptyList();
|
}
|
List<TmsPersonTaskEntity> rows = personTaskMapper.selectList(new LambdaQueryWrapper<TmsPersonTaskEntity>()
|
.eq(TmsPersonTaskEntity::getTaskId, taskId)
|
.orderByAsc(TmsPersonTaskEntity::getCreatorTime));
|
if (rows == null || rows.isEmpty()) {
|
return Collections.emptyList();
|
}
|
List<TmsPersonTaskEntity> list = new ArrayList<>();
|
for (TmsPersonTaskEntity p : rows) {
|
if (p != null && !"1".equals(p.getGuestFlag())) {
|
list.add(p);
|
}
|
}
|
return list;
|
}
|
|
private Map<String, String> loadDeptNames(Set<String> deptIds) {
|
Map<String, String> map = new HashMap<>();
|
if (deptIds == null || deptIds.isEmpty()) {
|
return map;
|
}
|
for (String id : deptIds) {
|
try {
|
OrganizeEntity org = organizeApi.getInfoById(id);
|
if (org != null && StringUtil.isNotEmpty(org.getFullName())) {
|
map.put(id, org.getFullName());
|
}
|
} catch (Exception ignored) {
|
// ignore
|
}
|
}
|
return map;
|
}
|
|
private String incompleteReason(String evalMode, TmsPersonTaskEntity person) {
|
if (person.getSignTime() == null) {
|
return "未签到";
|
}
|
if (TmsEvalMode.EXAM.equalsCodeIgnoreCase(evalMode) && !TmsPassFlag.isPass(person.getPassFlag())) {
|
return "考试未合格";
|
}
|
if (TmsEvalMode.QUIZ.equalsCodeIgnoreCase(evalMode) && !TmsPassFlag.isPass(person.getPassFlag())) {
|
return "提问未合格";
|
}
|
if (TmsEvalMode.PRACTICE.equalsCodeIgnoreCase(evalMode) && !TmsPassFlag.isPass(person.getPassFlag())) {
|
return "实操未合格";
|
}
|
return "未完成";
|
}
|
|
/** 未完成:未签到;考试/提问/实操另要求未合格 */
|
private boolean isIncomplete(String evalMode, TmsPersonTaskEntity person) {
|
if (person.getSignTime() == null) {
|
return true;
|
}
|
if (TmsEvalMode.EXAM.equalsCodeIgnoreCase(evalMode)
|
|| TmsEvalMode.QUIZ.equalsCodeIgnoreCase(evalMode)
|
|| TmsEvalMode.PRACTICE.equalsCodeIgnoreCase(evalMode)) {
|
return !TmsPassFlag.isPass(person.getPassFlag());
|
}
|
return false;
|
}
|
|
/**
|
* 回写计划明细已完成
|
*/
|
private void markPlanItemDone(TmsTaskEntity task) {
|
if (task == null || StringUtil.isEmpty(task.getSourceItemId())) {
|
return;
|
}
|
if (TmsTaskSourceType.ANNUAL.equalsCode(task.getSourceType())) {
|
TmsAnnualPlanItemEntity item = annualPlanItemMapper.selectById(task.getSourceItemId());
|
if (item != null) {
|
item.setLineStatus(TmsPlanLineStatus.DONE.getCode());
|
annualPlanItemMapper.updateById(item);
|
}
|
return;
|
}
|
if (TmsTaskSourceType.POST.equalsCode(task.getSourceType())) {
|
TmsPostPlanItemEntity item = postPlanItemMapper.selectById(task.getSourceItemId());
|
if (item != null) {
|
item.setLineStatus(TmsPlanLineStatus.DONE.getCode());
|
postPlanItemMapper.updateById(item);
|
}
|
}
|
}
|
|
/**
|
* 完成率向下取整
|
*/
|
private static int rateFloor(int part, int total) {
|
if (total <= 0) {
|
return 0;
|
}
|
return (int) Math.floor(part * 100.0 / total);
|
}
|
|
/**
|
* 写入/更新个人培训目录 {@code tms_person_train}(档案页 catalogList 的数据源)。
|
* 无个人档案主档时先 {@link #ensurePersonFile}。
|
* 同一 recordId+userId 幂等更新。
|
*/
|
private void upsertPersonTrain(TmsRecordEntity record, TmsPersonTaskEntity person, Date now, String operatorId) {
|
TmsPersonTrainEntity exists = personTrainMapper.selectOne(
|
new LambdaQueryWrapper<TmsPersonTrainEntity>()
|
.eq(TmsPersonTrainEntity::getRecordId, record.getId())
|
.eq(TmsPersonTrainEntity::getUserId, person.getUserId())
|
.last("LIMIT 1"));
|
if (exists != null) {
|
exists.setPassFlag(TmsPassFlag.normalize(person.getPassFlag()));
|
exists.setHours(person.getHours());
|
exists.setSubject(record.getSubject());
|
exists.setCategory(record.getCategory());
|
exists.setTrainMode(record.getTrainMode());
|
exists.setEvalMode(record.getEvalMode());
|
exists.setTeacherId(record.getTeacherId());
|
exists.setStartTime(record.getStartTime());
|
exists.setEndTime(record.getEndTime());
|
exists.setLastModifyUserId(operatorId);
|
exists.setLastModifyTime(now);
|
personTrainMapper.updateById(exists);
|
return;
|
}
|
|
TmsPersonFileEntity file = ensurePersonFile(person.getUserId(), now, operatorId);
|
TmsPersonTrainEntity row = new TmsPersonTrainEntity();
|
row.setId(RandomUtil.uuId());
|
row.setArchiveId(file.getId());
|
row.setUserId(person.getUserId());
|
row.setRecordId(record.getId());
|
row.setRecordNo(record.getRecordNo());
|
row.setCategory(record.getCategory());
|
row.setSubject(record.getSubject());
|
row.setTrainMode(record.getTrainMode());
|
row.setTeacherId(record.getTeacherId());
|
row.setEvalMode(record.getEvalMode());
|
row.setPassFlag(TmsPassFlag.normalize(person.getPassFlag()));
|
row.setStartTime(record.getStartTime());
|
row.setEndTime(record.getEndTime());
|
row.setHours(person.getHours());
|
row.setCreatorUserId(operatorId);
|
row.setCreatorTime(now);
|
personTrainMapper.insert(row);
|
}
|
|
/** 确保存在个人档案主档 tms_person_file(归档写目录前调用)。 */
|
private TmsPersonFileEntity ensurePersonFile(String userId, Date now, String operatorId) {
|
TmsPersonFileEntity file = personFileMapper.selectOne(
|
new LambdaQueryWrapper<TmsPersonFileEntity>()
|
.eq(TmsPersonFileEntity::getUserId, userId)
|
.last("LIMIT 1"));
|
if (file != null) {
|
return file;
|
}
|
UserEntity user = null;
|
try {
|
user = userApi.getInfoById(userId);
|
} catch (Exception ignored) {
|
// ignore remote failures; still create a stub archive
|
}
|
file = new TmsPersonFileEntity();
|
file.setId(RandomUtil.uuId());
|
file.setArchiveNo(nextArchiveNo());
|
file.setArchiveDate(now);
|
file.setUserId(userId);
|
if (user != null) {
|
file.setUserName(user.getRealName());
|
file.setDeptId(user.getOrganizeId());
|
}
|
file.setBizStatus(TmsValidRevoked.VALID.getCode());
|
file.setCreatorUserId(operatorId);
|
file.setCreatorTime(now);
|
personFileMapper.insert(file);
|
return file;
|
}
|
|
/**
|
* 生成档案编号
|
*/
|
private String nextArchiveNo() {
|
int year = Calendar.getInstance().get(Calendar.YEAR);
|
String prefix = "AR" + year;
|
TmsPersonFileEntity last = personFileMapper.selectOne(
|
new LambdaQueryWrapper<TmsPersonFileEntity>()
|
.likeRight(TmsPersonFileEntity::getArchiveNo, prefix)
|
.orderByDesc(TmsPersonFileEntity::getArchiveNo)
|
.last("LIMIT 1"));
|
int seq = 1;
|
if (last != null && StringUtil.isNotEmpty(last.getArchiveNo())
|
&& last.getArchiveNo().length() >= prefix.length() + 4) {
|
try {
|
seq = Integer.parseInt(last.getArchiveNo().substring(prefix.length())) + 1;
|
} catch (NumberFormatException ignored) {
|
seq = 1;
|
}
|
}
|
return prefix + String.format("%04d", Math.min(seq, 9999));
|
}
|
|
/** 未完成人员 → 草稿再维护任务(不自动发布) */
|
/**
|
* 为未完成学员生成「再培训」草稿任务(TD…,category=retrain,不自动发布),
|
* 便于维护后再次送审/发布。
|
*/
|
private void createContinueDraftTask(TmsRecordEntity record, List<TmsPersonTaskEntity> incomplete,
|
Date now, String operatorId) {
|
if (StringUtil.isEmpty(record.getTaskId())) {
|
return;
|
}
|
TmsTaskEntity src = taskMapper.selectById(record.getTaskId());
|
if (src == null) {
|
return;
|
}
|
// 同一原任务若已有未发布 continue,则合并培训对象,避免重复草稿
|
TmsTaskEntity existing = taskMapper.selectOne(
|
new LambdaQueryWrapper<TmsTaskEntity>()
|
.eq(TmsTaskEntity::getPrevTaskNo, src.getTaskNo())
|
.eq(TmsTaskEntity::getTaskKind, TmsTaskKind.CONTINUE.getCode())
|
.eq(TmsTaskEntity::getPublishStatus, TmsTaskPublishStatus.DRAFT.getCode())
|
.last("LIMIT 1"));
|
List<String> userIds = incomplete.stream()
|
.map(TmsPersonTaskEntity::getUserId)
|
.filter(StringUtil::isNotEmpty)
|
.distinct()
|
.collect(Collectors.toList());
|
if (userIds.isEmpty()) {
|
return;
|
}
|
if (existing != null) {
|
List<String> merged = new ArrayList<>();
|
if (StringUtil.isNotEmpty(existing.getTrainees())) {
|
for (String id : existing.getTrainees().split(",")) {
|
if (StringUtil.isNotEmpty(id) && !merged.contains(id.trim())) {
|
merged.add(id.trim());
|
}
|
}
|
}
|
for (String id : userIds) {
|
if (!merged.contains(id)) {
|
merged.add(id);
|
}
|
}
|
existing.setTrainees(String.join(",", merged));
|
existing.setCategory(TmsTaskCategory.RETRAIN.getCode());
|
existing.setTaskKind(TmsTaskKind.CONTINUE.getCode());
|
existing.setLastModifyUserId(operatorId);
|
existing.setLastModifyTime(now);
|
taskMapper.updateById(existing);
|
return;
|
}
|
|
TmsTaskEntity draft = new TmsTaskEntity();
|
draft.setId(RandomUtil.uuId());
|
draft.setTaskNo(nextDraftTaskNo());
|
draft.setCategory(TmsTaskCategory.RETRAIN.getCode());
|
draft.setTrainType(src.getTrainType());
|
draft.setSourceType(src.getSourceType());
|
draft.setSourcePlanNo(src.getSourcePlanNo());
|
draft.setSourceId(src.getSourceId());
|
draft.setSourceItemId(src.getSourceItemId());
|
draft.setSubject(src.getSubject());
|
draft.setKeyPoints(src.getKeyPoints());
|
draft.setMaterialId(src.getMaterialId());
|
draft.setMaterialNo(src.getMaterialNo());
|
draft.setPlaceId(src.getPlaceId());
|
draft.setPlaceNo(src.getPlaceNo());
|
draft.setPlaceName(src.getPlaceName());
|
draft.setPaperId(src.getPaperId());
|
draft.setPaperName(src.getPaperName());
|
draft.setTeacherId(src.getTeacherId());
|
draft.setTrainMode(src.getTrainMode());
|
draft.setEvalMode(src.getEvalMode());
|
draft.setTrainees(String.join(",", userIds));
|
draft.setTaskKind(TmsTaskKind.CONTINUE.getCode());
|
draft.setPrevTaskNo(src.getTaskNo());
|
draft.setPublishStatus(TmsTaskPublishStatus.DRAFT.getCode());
|
draft.setRetakeLimit(src.getRetakeLimit());
|
draft.setPassScore(src.getPassScore());
|
draft.setQuizRatio(src.getQuizRatio());
|
draft.setOverdueRemind(src.getOverdueRemind());
|
draft.setInviteFlag(src.getInviteFlag());
|
draft.setRemark("由培训记录 " + record.getRecordNo() + " 归档自动生成(未完成人员再维护)");
|
draft.setCreatorUserId(operatorId);
|
draft.setCreatorTime(now);
|
taskMapper.insert(draft);
|
|
// 复制任务附件引用
|
List<TmsTaskFileEntity> files = taskFileMapper.selectList(
|
new LambdaQueryWrapper<TmsTaskFileEntity>()
|
.eq(TmsTaskFileEntity::getForeignId, src.getId()));
|
for (TmsTaskFileEntity f : files) {
|
TmsTaskFileEntity copy = new TmsTaskFileEntity();
|
copy.setId(RandomUtil.uuId());
|
copy.setForeignId(draft.getId());
|
copy.setFileName(f.getFileName());
|
copy.setFileNo(f.getFileNo());
|
copy.setMaterialId(f.getMaterialId());
|
copy.setFileJson(f.getFileJson());
|
copy.setCanDownload(f.getCanDownload());
|
copy.setCreatorUserId(operatorId);
|
copy.setCreatorTime(now);
|
taskFileMapper.insert(copy);
|
}
|
}
|
|
/**
|
* 生成草稿任务编号
|
*/
|
private synchronized String nextDraftTaskNo() {
|
String ym = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
|
String prefix = PREFIX_DRAFT + ym;
|
String max = taskMapper.selectMaxTaskNo(prefix);
|
int seq = 1;
|
if (StringUtil.isNotEmpty(max) && max.length() == 12) {
|
try {
|
seq = Integer.parseInt(max.substring(8)) + 1;
|
} catch (NumberFormatException ignored) {
|
seq = 1;
|
}
|
}
|
if (seq > 9999) {
|
throw new DataException("当月草稿任务编号已用尽");
|
}
|
return prefix + String.format("%04d", seq);
|
}
|
/**
|
* 培训记录:删除
|
*/
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void delete(List<String> ids) {
|
if (ids == null || ids.isEmpty()) {
|
throw new DataException("请选择要删除的记录");
|
}
|
for (String id : ids) {
|
if (StringUtil.isEmpty(id)) {
|
continue;
|
}
|
recordMapper.deleteById(id);
|
}
|
}
|
|
/**
|
* 应用列表查询模式过滤
|
*/
|
private void applyListMode(LambdaQueryWrapper<TmsRecordEntity> qw, TmsRecordQuery query) {
|
String archiveStatus = query.getArchiveStatus();
|
if (StringUtil.isNotEmpty(archiveStatus)) {
|
qw.eq(TmsRecordEntity::getArchiveStatus, archiveStatus);
|
return;
|
}
|
String mode = query.getListMode();
|
// 口径:open进行中 / pending待归档 / ready可归档 / archived已归档
|
if (TmsArchiveStatus.READY.equalsCode(mode)) {
|
qw.in(TmsRecordEntity::getArchiveStatus,
|
TmsArchiveStatus.PENDING.getCode(), TmsArchiveStatus.READY.getCode());
|
} else if (TmsArchiveStatus.ARCHIVED.equalsCode(mode)) {
|
qw.eq(TmsRecordEntity::getArchiveStatus, TmsArchiveStatus.ARCHIVED.getCode());
|
} else {
|
qw.eq(TmsRecordEntity::getArchiveStatus, TmsArchiveStatus.OPEN.getCode());
|
}
|
}
|
|
/**
|
* 转为列表表单
|
*/
|
private TmsRecordForm toListForm(TmsRecordEntity entity, Map<String, String> names) {
|
TmsRecordForm form = new TmsRecordForm();
|
form.setId(entity.getId());
|
form.setRecordNo(entity.getRecordNo());
|
form.setTaskId(entity.getTaskId());
|
form.setTaskNo(entity.getTaskNo());
|
form.setCategory(entity.getCategory());
|
form.setSubject(entity.getSubject());
|
form.setTeacherId(entity.getTeacherId());
|
form.setTrainerName(names.getOrDefault(entity.getTeacherId(),
|
StringUtil.isEmpty(entity.getTeacherId()) ? "-" : entity.getTeacherId()));
|
form.setTrainType(StringUtil.isNotEmpty(entity.getTrainType())
|
? entity.getTrainType()
|
: entity.getTrainMode());
|
form.setTrainMode(entity.getTrainMode());
|
form.setEvalMode(entity.getEvalMode());
|
form.setStartTime(formatDate(entity.getStartTime()));
|
form.setEndTime(formatDate(entity.getEndTime()));
|
form.setCloseTime(formatDate(entity.getCloseTime()));
|
form.setArchiveStatus(entity.getArchiveStatus());
|
form.setSignRate(entity.getSignRate());
|
form.setPassRate(entity.getPassRate());
|
form.setRemark(entity.getRemark());
|
return form;
|
}
|
|
/**
|
* 加载培训记录附件
|
*/
|
private List<TmsRecordAttachmentForm> loadAttachments(TmsRecordEntity entity) {
|
if (entity == null) {
|
return Collections.emptyList();
|
}
|
// 优先用发布时同步的 lecture_file 快照
|
List<TmsRecordAttachmentForm> fromSnap = parseLectureFile(entity.getLectureFile());
|
if (!fromSnap.isEmpty()) {
|
return fromSnap;
|
}
|
return loadAttachmentsFromTask(entity.getTaskId());
|
}
|
|
/**
|
* 解析课件 JSON
|
*/
|
private List<TmsRecordAttachmentForm> parseLectureFile(String lectureFile) {
|
if (StringUtil.isEmpty(lectureFile)) {
|
return Collections.emptyList();
|
}
|
try {
|
List<Map<String, Object>> rows = OBJECT_MAPPER.readValue(lectureFile.trim(),
|
new TypeReference<List<Map<String, Object>>>() {
|
});
|
if (rows == null || rows.isEmpty()) {
|
return Collections.emptyList();
|
}
|
// 快照里教材附件常只有 materialId、无 fileJson,需回落教材表补 url
|
List<String> materialIds = new ArrayList<>();
|
for (Map<String, Object> row : rows) {
|
if (row == null) {
|
continue;
|
}
|
Object mid = row.get("materialId");
|
if (mid != null && StringUtil.isNotEmpty(String.valueOf(mid))) {
|
materialIds.add(String.valueOf(mid));
|
}
|
}
|
Map<String, TmsMaterialEntity> materialMap = loadMaterialMap(materialIds);
|
|
List<TmsRecordAttachmentForm> list = new ArrayList<>();
|
for (Map<String, Object> row : rows) {
|
if (row == null) {
|
continue;
|
}
|
Object id = row.get("id");
|
Object name = row.get("name");
|
String fileName = name == null ? null : String.valueOf(name);
|
Map<String, String> meta = extractFileMeta(row.get("fileJson"), fileName);
|
// 快照顶层也可能直接带 url / fileId
|
mergeMetaFromRow(meta, row);
|
Object mid = row.get("materialId");
|
if ((StringUtil.isEmpty(meta.get("url")) || StringUtil.isEmpty(meta.get("fileId"))
|
|| StringUtil.isEmpty(meta.get("ext")))
|
&& mid != null && StringUtil.isNotEmpty(String.valueOf(mid))) {
|
TmsMaterialEntity material = materialMap.get(String.valueOf(mid));
|
if (material != null) {
|
mergeMeta(meta, extractFileMeta(material.getFileJson(), material.getMaterialName()));
|
}
|
}
|
list.add(buildAttachmentForm(
|
id == null ? null : String.valueOf(id),
|
StringUtil.isNotEmpty(fileName) ? fileName : meta.get("name"),
|
meta));
|
}
|
return list;
|
} catch (Exception ex) {
|
return Collections.emptyList();
|
}
|
}
|
|
/**
|
* 加载教材映射
|
*/
|
private Map<String, TmsMaterialEntity> loadMaterialMap(List<String> materialIds) {
|
Map<String, TmsMaterialEntity> materialMap = new HashMap<>();
|
if (materialIds == null || materialIds.isEmpty()) {
|
return materialMap;
|
}
|
List<String> ids = materialIds.stream()
|
.filter(StringUtil::isNotEmpty)
|
.distinct()
|
.collect(Collectors.toList());
|
if (ids.isEmpty()) {
|
return materialMap;
|
}
|
for (TmsMaterialEntity material : materialMapper.selectBatchIds(ids)) {
|
materialMap.put(material.getId(), material);
|
}
|
return materialMap;
|
}
|
|
/**
|
* 从行数据合并附件元数据
|
*/
|
private void mergeMetaFromRow(Map<String, String> meta, Map<String, Object> row) {
|
if (meta == null || row == null) {
|
return;
|
}
|
if (StringUtil.isEmpty(meta.get("url")) && row.get("url") != null) {
|
meta.put("url", String.valueOf(row.get("url")));
|
}
|
if (StringUtil.isEmpty(meta.get("fileId"))) {
|
Object fid = row.get("fileId");
|
if (fid == null) {
|
fid = row.get("file_id");
|
}
|
if (fid != null) {
|
meta.put("fileId", String.valueOf(fid));
|
}
|
}
|
if (StringUtil.isEmpty(meta.get("fileVersionId"))) {
|
Object fvid = row.get("fileVersionId");
|
if (fvid == null) {
|
fvid = row.get("fileVersion");
|
}
|
if (fvid != null) {
|
meta.put("fileVersionId", String.valueOf(fvid));
|
}
|
}
|
if (StringUtil.isEmpty(meta.get("ext")) && row.get("fileExt") != null) {
|
meta.put("ext", String.valueOf(row.get("fileExt")).toLowerCase());
|
}
|
}
|
|
/**
|
* 合并附件元数据
|
*/
|
private void mergeMeta(Map<String, String> target, Map<String, String> source) {
|
if (target == null || source == null) {
|
return;
|
}
|
if (StringUtil.isEmpty(target.get("url")) && StringUtil.isNotEmpty(source.get("url"))) {
|
target.put("url", source.get("url"));
|
}
|
if (StringUtil.isEmpty(target.get("ext")) && StringUtil.isNotEmpty(source.get("ext"))) {
|
target.put("ext", source.get("ext"));
|
}
|
if (StringUtil.isEmpty(target.get("fileId")) && StringUtil.isNotEmpty(source.get("fileId"))) {
|
target.put("fileId", source.get("fileId"));
|
}
|
if (StringUtil.isEmpty(target.get("fileVersionId")) && StringUtil.isNotEmpty(source.get("fileVersionId"))) {
|
target.put("fileVersionId", source.get("fileVersionId"));
|
}
|
if (StringUtil.isEmpty(target.get("name")) && StringUtil.isNotEmpty(source.get("name"))) {
|
target.put("name", source.get("name"));
|
}
|
}
|
|
/**
|
* 从任务同步加载附件
|
*/
|
private List<TmsRecordAttachmentForm> loadAttachmentsFromTask(String taskId) {
|
if (StringUtil.isEmpty(taskId)) {
|
return Collections.emptyList();
|
}
|
List<TmsTaskFileEntity> files = taskFileMapper.selectList(
|
new LambdaQueryWrapper<TmsTaskFileEntity>()
|
.eq(TmsTaskFileEntity::getForeignId, taskId)
|
.orderByAsc(TmsTaskFileEntity::getCreatorTime));
|
if (files.isEmpty()) {
|
return Collections.emptyList();
|
}
|
|
Map<String, TmsMaterialEntity> materialMap = loadMaterialMap(files.stream()
|
.map(TmsTaskFileEntity::getMaterialId)
|
.filter(StringUtil::isNotEmpty)
|
.distinct()
|
.collect(Collectors.toList()));
|
|
List<TmsRecordAttachmentForm> 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.isEmpty(meta.get("fileId")))
|
&& StringUtil.isNotEmpty(file.getMaterialId())) {
|
TmsMaterialEntity material = materialMap.get(file.getMaterialId());
|
if (material != null) {
|
mergeMeta(meta, extractFileMeta(material.getFileJson(), material.getMaterialName()));
|
}
|
}
|
list.add(buildAttachmentForm(
|
file.getId(),
|
StringUtil.isNotEmpty(file.getFileName()) ? file.getFileName() : meta.get("name"),
|
meta));
|
}
|
return list;
|
}
|
|
/**
|
* 构建附件表单
|
*/
|
private TmsRecordAttachmentForm buildAttachmentForm(String id, String name, Map<String, String> meta) {
|
TmsRecordAttachmentForm item = new TmsRecordAttachmentForm();
|
item.setId(id);
|
item.setName(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")));
|
return item;
|
}
|
|
/** 从 JNPF 附件 JSON 解析 url / fileId / 扩展名等(支持 String / Map / List)。 */
|
private Map<String, String> extractFileMeta(Object 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 {
|
Map<String, Object> map = coerceFileJsonMap(fileJson);
|
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;
|
}
|
/**
|
* 规范化文件 JSON 为 Map
|
*/
|
@SuppressWarnings("unchecked")
|
private Map<String, Object> coerceFileJsonMap(Object fileJson) throws Exception {
|
if (fileJson == null) {
|
return null;
|
}
|
if (fileJson instanceof Map) {
|
return (Map<String, Object>) fileJson;
|
}
|
if (fileJson instanceof List) {
|
List<?> arr = (List<?>) fileJson;
|
if (arr.isEmpty() || !(arr.get(0) instanceof Map)) {
|
return null;
|
}
|
return (Map<String, Object>) arr.get(0);
|
}
|
String text = String.valueOf(fileJson).trim();
|
if (StringUtil.isEmpty(text) || "null".equalsIgnoreCase(text)) {
|
return null;
|
}
|
// 双重 JSON 字符串:'"{\"url\":\"...\"}"'
|
if (text.startsWith("\"") && text.endsWith("\"")) {
|
text = OBJECT_MAPPER.readValue(text, String.class);
|
if (StringUtil.isEmpty(text)) {
|
return null;
|
}
|
text = text.trim();
|
}
|
if (text.startsWith("[")) {
|
List<Map<String, Object>> arr = OBJECT_MAPPER.readValue(text,
|
new TypeReference<List<Map<String, Object>>>() {
|
});
|
if (arr == null || arr.isEmpty()) {
|
return null;
|
}
|
return arr.get(0);
|
}
|
return OBJECT_MAPPER.readValue(text, new TypeReference<Map<String, Object>>() {
|
});
|
}
|
|
/**
|
* 解析附件预览类型
|
*/
|
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, String> loadUserNames(List<String> userIds) {
|
if (userIds == null || userIds.isEmpty()) {
|
return Collections.emptyMap();
|
}
|
List<String> ids = userIds.stream().filter(StringUtil::isNotEmpty).distinct().collect(Collectors.toList());
|
if (ids.isEmpty()) {
|
return Collections.emptyMap();
|
}
|
try {
|
List<UserEntity> users = userApi.getUserName(ids);
|
if (users == null || users.isEmpty()) {
|
return Collections.emptyMap();
|
}
|
Map<String, String> map = new LinkedHashMap<>();
|
for (UserEntity user : users) {
|
if (user == null || StringUtil.isEmpty(user.getId())) {
|
continue;
|
}
|
String name = StringUtil.isNotEmpty(user.getRealName()) ? user.getRealName() : user.getAccount();
|
map.put(user.getId(), name);
|
}
|
return map;
|
} catch (Exception ex) {
|
return Collections.emptyMap();
|
}
|
}
|
|
/**
|
* 按 ID 查询,不存在则抛错
|
*/
|
private TmsRecordEntity requireOne(String id) {
|
if (StringUtil.isEmpty(id)) {
|
throw new DataException("记录ID不能为空");
|
}
|
TmsRecordEntity entity = recordMapper.selectById(id);
|
if (entity == null) {
|
throw new DataException("培训记录不存在");
|
}
|
return entity;
|
}
|
|
/**
|
* 格式化日期时间
|
*/
|
private String formatDate(Date date) {
|
return date == null ? null : DateUtil.dateToString(date, FMT);
|
}
|
|
/**
|
* 解析日期时间
|
*/
|
private static Date parseDate(String text) {
|
if (StringUtil.isEmpty(text)) {
|
return null;
|
}
|
String raw = text.trim();
|
try {
|
if (raw.matches("^\\d{10,13}$")) {
|
long ts = Long.parseLong(raw);
|
if (raw.length() == 10) {
|
ts = ts * 1000L;
|
}
|
return new Date(ts);
|
}
|
return DateUtil.stringToDate(raw);
|
} catch (Exception ex) {
|
throw new DataException("时间格式无效:" + text);
|
}
|
}
|
|
/**
|
* 解析日开始时间
|
*/
|
private static Date parseDayStart(String day) {
|
if (StringUtil.isEmpty(day) || "null".equalsIgnoreCase(day)) {
|
return null;
|
}
|
String raw = day.trim();
|
if (raw.length() == 10) {
|
raw = raw + " 00:00:00";
|
}
|
return parseDate(raw);
|
}
|
|
/**
|
* 解析日结束时间
|
*/
|
private static Date parseDayEnd(String day) {
|
if (StringUtil.isEmpty(day) || "null".equalsIgnoreCase(day)) {
|
return null;
|
}
|
String raw = day.trim();
|
if (raw.length() == 10) {
|
raw = raw + " 23:59:59";
|
}
|
return parseDate(raw);
|
}
|
}
|