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();
}
}
}