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<TmsTrainRecordListVO> getList(TmsTrainRecordQuery query) {
|
LambdaQueryWrapper<TmsPersonFileEntity> 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<TmsPersonFileEntity> page = personFileMapper.selectPage(new Page<>(current, size), qw);
|
List<TmsPersonFileEntity> rows = page.getRecords();
|
if (rows == null || rows.isEmpty()) {
|
query.setData(Collections.emptyList(), page.getTotal());
|
return Collections.emptyList();
|
}
|
|
Map<String, String> deptNames = loadDeptNames(rows.stream()
|
.map(TmsPersonFileEntity::getDeptId)
|
.filter(StringUtil::isNotEmpty)
|
.collect(Collectors.toSet()));
|
Map<String, String> postNames = loadPostNames(rows);
|
|
List<TmsTrainRecordListVO> 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<TmsTrainRecordCatalogItemVO> items = new ArrayList<>();
|
List<TmsPersonTrainEntity> personTrains = loadPersonTrains(file);
|
Set<String> trainTeacherIds = personTrains.stream()
|
.map(TmsPersonTrainEntity::getTeacherId)
|
.filter(StringUtil::isNotEmpty)
|
.flatMap(s -> splitIds(s).stream())
|
.collect(Collectors.toSet());
|
Map<String, String> 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<TmsTrainRecordCatalogItemVO> list = catalog.getList() == null
|
? Collections.emptyList()
|
: catalog.getList();
|
if (list.isEmpty()) {
|
throw new DataException("暂无已归档培训目录可导出");
|
}
|
List<String> headers = Arrays.asList(
|
"培训记录编号", "培训类型", "培训内容", "培训方式", "培训师",
|
"考核方式", "是否合格", "培训开始时间", "培训结束时间");
|
List<Integer> widths = Arrays.asList(140, 90, 200, 90, 90, 90, 70, 140, 140);
|
List<List<String>> 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<TmsPersonTrainEntity> loadPersonTrains(TmsPersonFileEntity file) {
|
LambdaQueryWrapper<TmsPersonTrainEntity> qw = new LambdaQueryWrapper<>();
|
qw.and(w -> w.eq(TmsPersonTrainEntity::getArchiveId, file.getId())
|
.or()
|
.eq(TmsPersonTrainEntity::getUserId, file.getUserId()));
|
qw.orderByDesc(TmsPersonTrainEntity::getStartTime);
|
List<TmsPersonTrainEntity> 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<String, String> 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<String, String> deptNames,
|
Map<String, String> 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<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 e) {
|
log.debug("resolve dept failed: {}", e.getMessage());
|
}
|
}
|
return map;
|
}
|
|
/**
|
* 批量加载岗位名称
|
*/
|
private Map<String, String> loadPostNames(List<TmsPersonFileEntity> rows) {
|
Set<String> 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<String, String> map = new HashMap<>();
|
if (ids.isEmpty()) {
|
return map;
|
}
|
try {
|
List<PositionEntity> 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<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;
|
}
|
|
/**
|
* 是否在日期范围内
|
*/
|
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;
|
}
|
}
|