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} + 履历 + 培训目录。 * *

培训目录 {@code catalogList} 只读 {@code tms_person_train}, * 数据来自培训记录归档({@link TmsRecordServiceImpl#upsertPersonTrain}),非实时学习写入。

*/ @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 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 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 normalizeIds(Object raw) { if (raw == null) { return Collections.emptyList(); } if (raw instanceof List) { List list = (List) raw; List 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 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 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 positionIds = normalizeIds(user.getPositionIds()); if (!positionIds.isEmpty()) { try { List 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 loadWorkList(String archiveId) { LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); qw.eq(TmsPersonWorkEntity::getForeignId, archiveId); qw.orderByAsc(TmsPersonWorkEntity::getHireDate); List rows = personWorkMapper.selectList(qw); if (rows == null || rows.isEmpty()) { return Collections.emptyList(); } List 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 loadCatalogList(String archiveId, String userId) { LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); qw.and(w -> w.eq(TmsPersonTrainEntity::getArchiveId, archiveId) .or() .eq(TmsPersonTrainEntity::getUserId, userId)); qw.orderByDesc(TmsPersonTrainEntity::getStartTime); List rows = personTrainMapper.selectList(qw); if (rows == null || rows.isEmpty()) { return Collections.emptyList(); } // 去重:同一 id 只保留一条 List 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 teacherIds = new HashSet<>(); for (TmsPersonTrainEntity row : distinct) { teacherIds.addAll(splitIds(row.getTeacherId())); } Map teacherNames = loadUserNames(teacherIds); List list = new ArrayList<>(distinct.size()); for (TmsPersonTrainEntity row : distinct) { list.add(toCatalog(row, teacherNames)); } return list; } /** * 转为档案目录项 VO */ private TmsTrainArchiveCatalogVO toCatalog(TmsPersonTrainEntity row, Map 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 loadUserNames(Set userIds) { Map map = new HashMap<>(); if (userIds == null || userIds.isEmpty()) { return map; } try { List 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 nameMap) { if (StringUtil.isEmpty(teacherId)) { return ""; } List ids = splitIds(teacherId); if (ids.isEmpty()) { return teacherId; } List names = new ArrayList<>(); for (String id : ids) { names.add(nameMap.getOrDefault(id, id)); } return String.join("、", names); } /** * 拆分 ID 字符串 */ private List splitIds(String raw) { if (StringUtil.isEmpty(raw)) { return Collections.emptyList(); } String[] parts = raw.split("[,,;;\\s]+"); List 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(); } } }