package jnpf.tmsService.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 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.trainrecord.TmsTrainRecordCatalogItemVO; import jnpf.tmsEntity.trainrecord.TmsTrainRecordCatalogVO; import jnpf.tmsEntity.trainrecord.TmsTrainRecordListVO; import jnpf.tmsEntity.trainrecord.TmsTrainRecordQuery; import jnpf.tmsMapper.TmsPersonFileMapper; import jnpf.tmsMapper.TmsPersonTrainMapper; import jnpf.tmsService.TmsTrainRecordService; import jnpf.tmsUtil.TmsExcelExportHelper; 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.util.DateUtil; import jnpf.util.StringUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; 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.Objects; import java.util.Set; import java.util.stream.Collectors; /** * 管理端「个人培训记录」:按人员档案列出,培训目录只读 {@code tms_person_train}(归档写入)。 * 与学员端 {@link TmsTrainArchiveServiceImpl} 同源表,视角不同。 */ @Slf4j @Service @RequiredArgsConstructor public class TmsTrainRecordServiceImpl implements TmsTrainRecordService { private static final String DATE_FMT = "yyyy-MM-dd"; private static final String DATETIME_FMT = "yyyy-MM-dd HH:mm"; private final TmsPersonFileMapper personFileMapper; private final TmsPersonTrainMapper personTrainMapper; private final OrganizeApi organizeApi; private final PositionApi positionApi; private final UserApi userApi; /** * 培训师授课档案:条件分页查询列表 */ @Override public List getList(TmsTrainRecordQuery query) { LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); if (StringUtil.isNotEmpty(query.getArchiveNo()) && !"null".equalsIgnoreCase(query.getArchiveNo())) { qw.like(TmsPersonFileEntity::getArchiveNo, query.getArchiveNo().trim()); } if (StringUtil.isNotEmpty(query.getKeyword()) && !"null".equalsIgnoreCase(query.getKeyword())) { String kw = query.getKeyword().trim(); qw.and(w -> w.like(TmsPersonFileEntity::getUserName, kw) .or().like(TmsPersonFileEntity::getArchiveNo, kw) .or().like(TmsPersonFileEntity::getDeptId, kw)); } qw.orderByDesc(TmsPersonFileEntity::getArchiveDate) .orderByDesc(TmsPersonFileEntity::getCreatorTime); long current = query.getCurrentPage() > 0 ? query.getCurrentPage() : 1; long size = query.getPageSize() > 0 ? query.getPageSize() : 20; Page page = personFileMapper.selectPage(new Page<>(current, size), qw); List rows = page.getRecords(); if (rows == null || rows.isEmpty()) { query.setData(Collections.emptyList(), page.getTotal()); return Collections.emptyList(); } Map deptNames = loadDeptNames(rows.stream() .map(TmsPersonFileEntity::getDeptId) .filter(StringUtil::isNotEmpty) .collect(Collectors.toSet())); Map postNames = loadPostNames(rows); List list = new ArrayList<>(rows.size()); for (TmsPersonFileEntity row : rows) { list.add(toListVo(row, deptNames, postNames)); } query.setData(list, page.getTotal()); return list; } /** * 查询培训师授课目录 */ @Override public TmsTrainRecordCatalogVO getCatalog(String archiveId, String startDate, String endDate) { if (StringUtil.isEmpty(archiveId)) { throw new DataException("档案ID不能为空"); } TmsPersonFileEntity file = personFileMapper.selectById(archiveId); if (file == null) { throw new DataException("个人培训记录不存在"); } // 与「个人培训档案」同源:仅读已归档写入的 tms_person_train(FDS:目录随结束任务/归档形成) List items = new ArrayList<>(); List personTrains = loadPersonTrains(file); Set trainTeacherIds = personTrains.stream() .map(TmsPersonTrainEntity::getTeacherId) .filter(StringUtil::isNotEmpty) .flatMap(s -> splitIds(s).stream()) .collect(Collectors.toSet()); Map trainTeacherNames = loadUserNames(trainTeacherIds); for (TmsPersonTrainEntity row : personTrains) { TmsTrainRecordCatalogItemVO item = fromPersonTrain(row, trainTeacherNames); if (item == null || !inDateRange(item.getTrainDate(), startDate, endDate)) { continue; } items.add(item); } items.sort(Comparator.comparing(TmsTrainRecordCatalogItemVO::getTrainDate, Comparator.nullsLast(Comparator.reverseOrder()))); TmsTrainRecordCatalogVO vo = new TmsTrainRecordCatalogVO(); vo.setId(file.getId()); vo.setUserName(file.getUserName()); vo.setArchiveNo(file.getArchiveNo()); vo.setList(items); return vo; } /** * 导出授课目录 */ @Override public void exportCatalog(String archiveId, String startDate, String endDate, HttpServletResponse response) { TmsTrainRecordCatalogVO catalog = getCatalog(archiveId, startDate, endDate); List list = catalog.getList() == null ? Collections.emptyList() : catalog.getList(); if (list.isEmpty()) { throw new DataException("暂无已归档培训目录可导出"); } List headers = Arrays.asList( "培训记录编号", "培训类型", "培训内容", "培训方式", "培训师", "考核方式", "是否合格", "培训开始时间", "培训结束时间"); List widths = Arrays.asList(140, 90, 200, 90, 90, 90, 70, 140, 140); List> rows = new ArrayList<>(list.size()); for (TmsTrainRecordCatalogItemVO item : list) { rows.add(Arrays.asList( nvl(item.getRecordNo()), nvl(item.getTrainType()), nvl(item.getTrainContent()), nvl(item.getTrainMode()), nvl(item.getTrainerName()), nvl(item.getEvalMode()), passText(item.getPassFlag()), nvl(item.getTrainStart()), nvl(item.getTrainEnd()))); } String userPart = StringUtil.isNotEmpty(catalog.getUserName()) ? catalog.getUserName() : (StringUtil.isNotEmpty(catalog.getArchiveNo()) ? catalog.getArchiveNo() : "export"); try { TmsExcelExportHelper.writeSpreadsheet(response, "培训目录_" + userPart, "培训目录", headers, widths, rows); } catch (IOException e) { throw new DataException("导出培训目录失败"); } } /** * 空值兜底 */ private static String nvl(String v) { return v == null ? "" : v; } /** * 合格标识展示文本 */ private static String passText(String passFlag) { return TmsPassFlag.label(passFlag); } /** * 加载个人培训目录明细 */ private List loadPersonTrains(TmsPersonFileEntity file) { LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); qw.and(w -> w.eq(TmsPersonTrainEntity::getArchiveId, file.getId()) .or() .eq(TmsPersonTrainEntity::getUserId, file.getUserId())); qw.orderByDesc(TmsPersonTrainEntity::getStartTime); List rows = personTrainMapper.selectList(qw); if (rows == null || rows.isEmpty()) { return Collections.emptyList(); } return new ArrayList<>(rows.stream() .filter(Objects::nonNull) .collect(Collectors.toMap(TmsPersonTrainEntity::getId, r -> r, (a, b) -> a, LinkedHashMap::new)) .values()); } /** * 个人培训目录转 VO */ private TmsTrainRecordCatalogItemVO fromPersonTrain(TmsPersonTrainEntity row, Map teacherNames) { TmsTrainRecordCatalogItemVO item = new TmsTrainRecordCatalogItemVO(); item.setId(row.getId()); item.setRecordNo(row.getRecordNo()); item.setTrainType(labelCategory(row.getCategory())); item.setTrainContent(row.getSubject()); item.setTrainMode(labelTrainMode(row.getTrainMode())); item.setEvalMode(labelEvalMode(row.getEvalMode())); item.setTrainerName(resolveTeacherNames(row.getTeacherId(), teacherNames)); item.setPassFlag(normalizePassFlag(row.getPassFlag())); item.setTrainStart(fmtDateTime(row.getStartTime())); item.setTrainEnd(fmtDateTime(row.getEndTime())); item.setTrainDate(fmtDate(row.getStartTime())); return item; } /** * 转为列表 VO */ private TmsTrainRecordListVO toListVo(TmsPersonFileEntity row, Map deptNames, Map postNames) { TmsTrainRecordListVO vo = new TmsTrainRecordListVO(); vo.setId(row.getId()); vo.setArchiveNo(row.getArchiveNo()); Date post = row.getHireDate() != null ? row.getHireDate() : row.getArchiveDate(); vo.setPostDate(fmtDate(post)); vo.setUserName(row.getUserName()); vo.setDeptName(deptNames.getOrDefault(row.getDeptId(), "")); vo.setPostName(postNames.getOrDefault(row.getMainPostId(), "")); vo.setPartPostName(postNames.getOrDefault(row.getPartPostId(), "")); vo.setMajor(""); vo.setEducation(""); vo.setTechTitle(""); vo.setHireDate(fmtDate(row.getHireDate())); vo.setLeaveDate(fmtDate(row.getLeaveDate())); return vo; } /** * 批量加载部门名称 */ private Map loadDeptNames(Set deptIds) { Map 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 e) { log.debug("resolve dept failed: {}", e.getMessage()); } } return map; } /** * 批量加载岗位名称 */ private Map loadPostNames(List rows) { Set ids = new HashSet<>(); for (TmsPersonFileEntity row : rows) { if (StringUtil.isNotEmpty(row.getMainPostId())) { ids.add(row.getMainPostId()); } if (StringUtil.isNotEmpty(row.getPartPostId())) { ids.add(row.getPartPostId()); } } Map map = new HashMap<>(); if (ids.isEmpty()) { return map; } try { List list = positionApi.getListByIds(new ArrayList<>(ids)); if (list != null) { for (PositionEntity p : list) { if (p != null && StringUtil.isNotEmpty(p.getId())) { map.put(p.getId(), p.getFullName()); } } } } catch (Exception e) { log.debug("resolve posts failed: {}", e.getMessage()); for (String id : ids) { try { PositionEntity p = positionApi.queryInfoById(id); if (p != null) { map.put(id, p.getFullName()); } } catch (Exception ignored) { // ignore } } } return map; } /** * 批量加载用户姓名 */ 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; } /** * 是否在日期范围内 */ private boolean inDateRange(String trainDate, String startDate, String endDate) { if (StringUtil.isEmpty(trainDate)) { return StringUtil.isEmpty(startDate) && StringUtil.isEmpty(endDate); } if (StringUtil.isNotEmpty(startDate) && trainDate.compareTo(startDate) < 0) { return false; } if (StringUtil.isNotEmpty(endDate) && trainDate.compareTo(endDate) > 0) { return false; } return true; } /** * 格式化日期 */ private String fmtDate(Date date) { return date == null ? null : DateUtil.dateToString(date, DATE_FMT); } /** * 格式化日期时间 */ private String fmtDateTime(Date date) { return date == null ? null : DateUtil.dateToString(date, DATETIME_FMT); } /** * 培训类别标签 */ 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(); } } /** * 规范化合格标识 */ private String normalizePassFlag(String raw) { String n = TmsPassFlag.normalize(raw); return n == null ? "" : n; } }