package jnpf.tmsService.impl;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import jnpf.base.UserInfo;
|
import jnpf.exception.DataException;
|
import jnpf.permission.OrganizeApi;
|
import jnpf.permission.PositionApi;
|
import jnpf.permission.UserApi;
|
import jnpf.permission.entity.OrganizeEntity;
|
import jnpf.permission.entity.PositionEntity;
|
import jnpf.permission.entity.UserEntity;
|
import jnpf.tmsEntity.TmsPersonFileEntity;
|
import jnpf.tmsEntity.TmsPersonTrainEntity;
|
import jnpf.tmsEntity.TmsPersonWorkEntity;
|
import jnpf.tmsEntity.archive.TmsTrainArchiveCatalogVO;
|
import jnpf.tmsEntity.archive.TmsTrainArchiveDetailVO;
|
import jnpf.tmsEntity.archive.TmsTrainArchiveProfileVO;
|
import jnpf.tmsEntity.archive.TmsTrainArchiveWorkVO;
|
import jnpf.tmsMapper.TmsPersonFileMapper;
|
import jnpf.tmsMapper.TmsPersonTrainMapper;
|
import jnpf.tmsMapper.TmsPersonWorkMapper;
|
import jnpf.tmsService.TmsTrainArchiveService;
|
import jnpf.tmsEntity.enums.TmsPassFlag;
|
import jnpf.tmsEntity.enums.TmsTaskSourceType;
|
import jnpf.tmsEntity.enums.TmsTrainMode;
|
import jnpf.tmsEntity.enums.TmsTaskCategory;
|
import jnpf.tmsEntity.enums.TmsEvalMode;
|
import jnpf.tmsEntity.enums.TmsValidRevoked;
|
import jnpf.util.DateUtil;
|
import jnpf.util.RandomUtil;
|
import jnpf.util.StringUtil;
|
import jnpf.util.UserProvider;
|
import lombok.RequiredArgsConstructor;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.util.ArrayList;
|
import java.util.Calendar;
|
import java.util.Collections;
|
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.Objects;
|
import java.util.Set;
|
import java.util.stream.Collectors;
|
|
/**
|
* 学员「我的个人培训档案」:主档 {@code tms_person_file} + 履历 + 培训目录。
|
*
|
* <p>培训目录 {@code catalogList} 只读 {@code tms_person_train},
|
* 数据来自培训记录归档({@link TmsRecordServiceImpl#upsertPersonTrain}),非实时学习写入。</p>
|
*/
|
@Slf4j
|
@Service
|
@RequiredArgsConstructor
|
public class TmsTrainArchiveServiceImpl implements TmsTrainArchiveService {
|
|
private final TmsPersonFileMapper personFileMapper;
|
private final TmsPersonWorkMapper personWorkMapper;
|
private final TmsPersonTrainMapper personTrainMapper;
|
private final OrganizeApi organizeApi;
|
private final PositionApi positionApi;
|
private final UserApi userApi;
|
|
/**
|
* 当前登录人档案;无主档则自动建档。
|
* catalogList ← {@link #loadCatalogList} ← tms_person_train。
|
*/
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public TmsTrainArchiveDetailVO getMine() {
|
UserInfo user = UserProvider.getUser();
|
if (user == null || StringUtil.isEmpty(user.getUserId())) {
|
throw new DataException("未获取到登录用户");
|
}
|
String userId = user.getUserId();
|
TmsPersonFileEntity file = findByUserId(userId);
|
if (file == null) {
|
file = createArchive(user);
|
}
|
|
TmsTrainArchiveDetailVO detail = new TmsTrainArchiveDetailVO();
|
detail.setProfile(toProfile(file, user));
|
detail.setWorkList(loadWorkList(file.getId()));
|
detail.setCatalogList(loadCatalogList(file.getId(), userId));
|
return detail;
|
}
|
|
/**
|
* 按用户 ID 查找档案主档
|
*/
|
private TmsPersonFileEntity findByUserId(String userId) {
|
LambdaQueryWrapper<TmsPersonFileEntity> qw = new LambdaQueryWrapper<>();
|
qw.eq(TmsPersonFileEntity::getUserId, userId);
|
qw.last("LIMIT 1");
|
return personFileMapper.selectOne(qw);
|
}
|
|
/**
|
* 创建个人培训档案主档
|
*/
|
private TmsPersonFileEntity createArchive(UserInfo user) {
|
Date now = new Date();
|
TmsPersonFileEntity entity = new TmsPersonFileEntity();
|
entity.setId(RandomUtil.uuId());
|
entity.setArchiveNo(nextArchiveNo());
|
entity.setArchiveDate(now);
|
entity.setUserId(user.getUserId());
|
entity.setUserName(user.getUserName());
|
entity.setDeptId(user.getOrganizeId());
|
List<String> positionIds = normalizeIds(user.getPositionIds());
|
if (!positionIds.isEmpty()) {
|
entity.setMainPostId(positionIds.get(0));
|
if (positionIds.size() > 1) {
|
entity.setPartPostId(positionIds.get(1));
|
}
|
}
|
entity.setBizStatus(TmsValidRevoked.VALID.getCode());
|
entity.setCreatorUserId(user.getUserId());
|
entity.setCreatorTime(now);
|
personFileMapper.insert(entity);
|
return entity;
|
}
|
|
/**
|
* 规范化 ID 列表
|
*/
|
private List<String> normalizeIds(Object raw) {
|
if (raw == null) {
|
return Collections.emptyList();
|
}
|
if (raw instanceof List) {
|
List<?> list = (List<?>) raw;
|
List<String> out = new ArrayList<>();
|
for (Object o : list) {
|
if (o != null && StringUtil.isNotEmpty(String.valueOf(o))) {
|
out.add(String.valueOf(o));
|
}
|
}
|
return out;
|
}
|
if (raw instanceof String[]) {
|
List<String> out = new ArrayList<>();
|
for (String s : (String[]) raw) {
|
if (StringUtil.isNotEmpty(s)) {
|
out.add(s);
|
}
|
}
|
return out;
|
}
|
if (raw instanceof String && StringUtil.isNotEmpty((String) raw)) {
|
return Collections.singletonList((String) raw);
|
}
|
return Collections.emptyList();
|
}
|
|
/** 档案编号:AR + 年份 + 4 位流水 */
|
private String nextArchiveNo() {
|
int year = Calendar.getInstance().get(Calendar.YEAR);
|
String prefix = "AR" + year;
|
LambdaQueryWrapper<TmsPersonFileEntity> qw = new LambdaQueryWrapper<>();
|
qw.likeRight(TmsPersonFileEntity::getArchiveNo, prefix);
|
qw.orderByDesc(TmsPersonFileEntity::getArchiveNo);
|
qw.last("LIMIT 1");
|
TmsPersonFileEntity last = personFileMapper.selectOne(qw);
|
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));
|
}
|
|
/**
|
* 转为档案基本信息 VO
|
*/
|
private TmsTrainArchiveProfileVO toProfile(TmsPersonFileEntity file, UserInfo user) {
|
TmsTrainArchiveProfileVO vo = new TmsTrainArchiveProfileVO();
|
vo.setId(file.getId());
|
vo.setArchiveNo(file.getArchiveNo());
|
vo.setArchiveDate(file.getArchiveDate() == null ? null : DateUtil.dateToString(file.getArchiveDate(), "yyyy-MM-dd"));
|
vo.setUserId(file.getUserId());
|
vo.setUserName(StringUtil.isNotEmpty(file.getUserName()) ? file.getUserName() : user.getUserName());
|
vo.setDeptName(resolveDeptName(file.getDeptId(), user));
|
vo.setMainPostName(resolvePostName(file.getMainPostId(), user, true));
|
vo.setPartPostName(resolvePostName(file.getPartPostId(), user, false));
|
return vo;
|
}
|
|
/**
|
* 解析部门名称
|
*/
|
private String resolveDeptName(String deptId, UserInfo user) {
|
if (StringUtil.isNotEmpty(deptId)) {
|
try {
|
OrganizeEntity org = organizeApi.getInfoById(deptId);
|
if (org != null && StringUtil.isNotEmpty(org.getFullName())) {
|
return org.getFullName();
|
}
|
} catch (Exception e) {
|
log.debug("resolve dept name failed: {}", e.getMessage());
|
}
|
}
|
if (user != null && StringUtil.isNotEmpty(user.getOrganize())) {
|
return user.getOrganize();
|
}
|
return "无";
|
}
|
|
/**
|
* 解析岗位名称
|
*/
|
private String resolvePostName(String postId, UserInfo user, boolean main) {
|
if (StringUtil.isNotEmpty(postId)) {
|
try {
|
PositionEntity pos = positionApi.queryInfoById(postId);
|
if (pos != null && StringUtil.isNotEmpty(pos.getFullName())) {
|
return pos.getFullName();
|
}
|
} catch (Exception e) {
|
log.debug("resolve post name failed: {}", e.getMessage());
|
}
|
}
|
if (main && user != null) {
|
List<String> positionIds = normalizeIds(user.getPositionIds());
|
if (!positionIds.isEmpty()) {
|
try {
|
List<PositionEntity> list = positionApi.getListByIds(positionIds);
|
if (list != null && !list.isEmpty() && StringUtil.isNotEmpty(list.get(0).getFullName())) {
|
return list.get(0).getFullName();
|
}
|
} catch (Exception e) {
|
log.debug("resolve main post from user failed: {}", e.getMessage());
|
}
|
}
|
}
|
return "无";
|
}
|
|
/**
|
* 加载工作履历
|
*/
|
private List<TmsTrainArchiveWorkVO> loadWorkList(String archiveId) {
|
LambdaQueryWrapper<TmsPersonWorkEntity> qw = new LambdaQueryWrapper<>();
|
qw.eq(TmsPersonWorkEntity::getForeignId, archiveId);
|
qw.orderByAsc(TmsPersonWorkEntity::getHireDate);
|
List<TmsPersonWorkEntity> rows = personWorkMapper.selectList(qw);
|
if (rows == null || rows.isEmpty()) {
|
return Collections.emptyList();
|
}
|
List<TmsTrainArchiveWorkVO> list = new ArrayList<>(rows.size());
|
for (TmsPersonWorkEntity row : rows) {
|
TmsTrainArchiveWorkVO vo = new TmsTrainArchiveWorkVO();
|
vo.setId(row.getId());
|
vo.setCompanyName(row.getCompanyName());
|
vo.setPostName(row.getPostName());
|
vo.setHireDate(row.getHireDate() == null ? null : DateUtil.dateToString(row.getHireDate(), "yyyy-MM-dd"));
|
vo.setLeaveDate(row.getLeaveDate() == null ? null : DateUtil.dateToString(row.getLeaveDate(), "yyyy-MM-dd"));
|
list.add(vo);
|
}
|
return list;
|
}
|
|
/** 按档案/用户加载培训目录(已归档写入的 person_train)。 */
|
private List<TmsTrainArchiveCatalogVO> loadCatalogList(String archiveId, String userId) {
|
LambdaQueryWrapper<TmsPersonTrainEntity> qw = new LambdaQueryWrapper<>();
|
qw.and(w -> w.eq(TmsPersonTrainEntity::getArchiveId, archiveId)
|
.or()
|
.eq(TmsPersonTrainEntity::getUserId, userId));
|
qw.orderByDesc(TmsPersonTrainEntity::getStartTime);
|
List<TmsPersonTrainEntity> rows = personTrainMapper.selectList(qw);
|
if (rows == null || rows.isEmpty()) {
|
return Collections.emptyList();
|
}
|
// 去重:同一 id 只保留一条
|
List<TmsPersonTrainEntity> distinct = new ArrayList<>(rows.stream()
|
.filter(Objects::nonNull)
|
.collect(Collectors.toMap(TmsPersonTrainEntity::getId, r -> r, (a, b) -> a, LinkedHashMap::new))
|
.values());
|
distinct.sort((a, b) -> {
|
Date ta = a.getStartTime();
|
Date tb = b.getStartTime();
|
if (ta == null && tb == null) return 0;
|
if (ta == null) return 1;
|
if (tb == null) return -1;
|
return tb.compareTo(ta);
|
});
|
|
Set<String> teacherIds = new HashSet<>();
|
for (TmsPersonTrainEntity row : distinct) {
|
teacherIds.addAll(splitIds(row.getTeacherId()));
|
}
|
Map<String, String> teacherNames = loadUserNames(teacherIds);
|
|
List<TmsTrainArchiveCatalogVO> list = new ArrayList<>(distinct.size());
|
for (TmsPersonTrainEntity row : distinct) {
|
list.add(toCatalog(row, teacherNames));
|
}
|
return list;
|
}
|
|
/**
|
* 转为档案目录项 VO
|
*/
|
private TmsTrainArchiveCatalogVO toCatalog(TmsPersonTrainEntity row, Map<String, String> teacherNames) {
|
TmsTrainArchiveCatalogVO vo = new TmsTrainArchiveCatalogVO();
|
vo.setId(row.getId());
|
vo.setRecordNo(row.getRecordNo());
|
vo.setTrainType(labelCategory(row.getCategory()));
|
vo.setTrainContent(row.getSubject());
|
vo.setTrainMode(labelTrainMode(row.getTrainMode()));
|
vo.setTrainerName(resolveTeacherNames(row.getTeacherId(), teacherNames));
|
vo.setEvalMode(labelEvalMode(row.getEvalMode()));
|
vo.setPassFlag(normalizePassFlag(row.getPassFlag()));
|
vo.setTrainStart(row.getStartTime() == null ? null : DateUtil.dateToString(row.getStartTime(), "yyyy-MM-dd HH:mm"));
|
vo.setTrainEnd(row.getEndTime() == null ? null : DateUtil.dateToString(row.getEndTime(), "yyyy-MM-dd HH:mm"));
|
return vo;
|
}
|
|
/**
|
* 批量加载用户姓名
|
*/
|
private Map<String, String> loadUserNames(Set<String> userIds) {
|
Map<String, String> map = new HashMap<>();
|
if (userIds == null || userIds.isEmpty()) {
|
return map;
|
}
|
try {
|
List<UserEntity> users = userApi.getUserName(new ArrayList<>(userIds));
|
if (users != null) {
|
for (UserEntity u : users) {
|
if (u != null && StringUtil.isNotEmpty(u.getId())) {
|
map.put(u.getId(), StringUtil.isNotEmpty(u.getRealName()) ? u.getRealName() : u.getId());
|
}
|
}
|
}
|
} catch (Exception e) {
|
log.debug("resolve users failed: {}", e.getMessage());
|
}
|
return map;
|
}
|
|
/**
|
* 解析讲师姓名(可多人)
|
*/
|
private String resolveTeacherNames(String teacherId, Map<String, String> nameMap) {
|
if (StringUtil.isEmpty(teacherId)) {
|
return "";
|
}
|
List<String> ids = splitIds(teacherId);
|
if (ids.isEmpty()) {
|
return teacherId;
|
}
|
List<String> names = new ArrayList<>();
|
for (String id : ids) {
|
names.add(nameMap.getOrDefault(id, id));
|
}
|
return String.join("、", names);
|
}
|
|
/**
|
* 拆分 ID 字符串
|
*/
|
private List<String> splitIds(String raw) {
|
if (StringUtil.isEmpty(raw)) {
|
return Collections.emptyList();
|
}
|
String[] parts = raw.split("[,,;;\\s]+");
|
List<String> out = new ArrayList<>();
|
for (String p : parts) {
|
if (StringUtil.isNotEmpty(p)) {
|
out.add(p.trim());
|
}
|
}
|
return out;
|
}
|
|
/** 归一为 tmsPassFlag:pass/fail/absent/na;兼容历史 1/0 */
|
private String normalizePassFlag(String raw) {
|
String n = TmsPassFlag.normalize(raw);
|
return n == null ? "" : n;
|
}
|
|
/**
|
* 培训类别标签
|
*/
|
private String labelCategory(String code) {
|
if (StringUtil.isEmpty(code)) {
|
return "";
|
}
|
TmsTaskCategory category = TmsTaskCategory.of(code);
|
if (category != null) {
|
return category == TmsTaskCategory.OUT_TRAIN ? "外派培训" : category.getLabel();
|
}
|
TmsTaskSourceType source = TmsTaskSourceType.of(code);
|
if (source != null) {
|
return source.getLabel();
|
}
|
if ("temporary".equals(code)) {
|
return TmsTaskCategory.TEMP.getLabel();
|
}
|
return code;
|
}
|
|
/**
|
* 培训方式标签
|
*/
|
private String labelTrainMode(String code) {
|
if (StringUtil.isEmpty(code)) {
|
return "";
|
}
|
TmsTrainMode mode = TmsTrainMode.of(code.toLowerCase());
|
if (mode == null) {
|
return code;
|
}
|
switch (mode) {
|
case ONLINE:
|
return "在线学习";
|
case ONSITE:
|
return "集中授课";
|
case PRACTICE:
|
return "操作授课";
|
default:
|
return mode.getLabel();
|
}
|
}
|
|
/**
|
* 评估方式标签
|
*/
|
private String labelEvalMode(String code) {
|
if (StringUtil.isEmpty(code)) {
|
return "";
|
}
|
TmsEvalMode mode = TmsEvalMode.of(code.toLowerCase());
|
if (mode == null) {
|
return code;
|
}
|
switch (mode) {
|
case EXAM:
|
return "在线考试";
|
case NONE:
|
return "无需考核";
|
case QUIZ:
|
return "现场提问";
|
case PRACTICE:
|
return "实操考核";
|
default:
|
return mode.getLabel();
|
}
|
}
|
}
|