package jnpf.tmsService.impl;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import jnpf.permission.OrganizeApi;
|
import jnpf.permission.entity.OrganizeEntity;
|
import jnpf.tmsEntity.TmsPersonTaskEntity;
|
import jnpf.tmsEntity.TmsRecordEntity;
|
import jnpf.tmsEntity.enums.TmsEvalMode;
|
import jnpf.tmsEntity.enums.TmsPassFlag;
|
import jnpf.tmsEntity.enums.TmsTaskCategory;
|
import jnpf.tmsEntity.stats.TmsTrainStatsDetailVO;
|
import jnpf.tmsEntity.stats.TmsTrainStatsQuery;
|
import jnpf.tmsEntity.stats.TmsTrainStatsRowVO;
|
import jnpf.tmsMapper.TmsPersonTaskMapper;
|
import jnpf.tmsMapper.TmsRecordMapper;
|
import jnpf.tmsService.TmsTrainStatsService;
|
import jnpf.tmsUtil.TmsExcelExportHelper;
|
import jnpf.util.DateUtil;
|
import jnpf.util.StringUtil;
|
import lombok.RequiredArgsConstructor;
|
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.Date;
|
import java.util.HashMap;
|
import java.util.HashSet;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Set;
|
|
@Service
|
@RequiredArgsConstructor
|
public class TmsTrainStatsServiceImpl implements TmsTrainStatsService {
|
|
private final TmsRecordMapper recordMapper;
|
private final TmsPersonTaskMapper personTaskMapper;
|
private final OrganizeApi organizeApi;
|
|
@Override
|
public List<TmsTrainStatsRowVO> list(TmsTrainStatsQuery query) {
|
return buildRows(query == null ? new TmsTrainStatsQuery() : query);
|
}
|
|
@Override
|
public List<TmsTrainStatsDetailVO> detail(TmsTrainStatsQuery query) {
|
TmsTrainStatsQuery q = query == null ? new TmsTrainStatsQuery() : query;
|
String groupBy = normalizeGroupBy(q.getGroupBy());
|
String dimCode = StringUtil.isEmpty(q.getDimCode()) ? "-" : q.getDimCode().trim();
|
List<TmsRecordEntity> records = loadRecords(q);
|
if (records.isEmpty()) {
|
return Collections.emptyList();
|
}
|
Set<String> deptFilter = StringUtil.isEmpty(q.getDeptId())
|
? null
|
: Collections.singleton(q.getDeptId().trim());
|
|
List<TmsTrainStatsDetailVO> result = new ArrayList<>();
|
for (TmsRecordEntity record : records) {
|
if (record == null || StringUtil.isEmpty(record.getTaskId())) {
|
continue;
|
}
|
List<TmsPersonTaskEntity> persons = loadPersons(record.getTaskId());
|
List<TmsPersonTaskEntity> matched = matchPersons(groupBy, dimCode, record, persons, deptFilter);
|
if (matched == null) {
|
continue;
|
}
|
int personCount = 0;
|
int signedCount = 0;
|
int passedCount = 0;
|
for (TmsPersonTaskEntity p : matched) {
|
personCount++;
|
if (p.getSignTime() != null) {
|
signedCount++;
|
}
|
if (TmsPassFlag.isPass(p.getPassFlag())) {
|
passedCount++;
|
}
|
}
|
TmsTrainStatsDetailVO vo = new TmsTrainStatsDetailVO();
|
vo.setRecordId(record.getId());
|
vo.setRecordNo(record.getRecordNo());
|
vo.setSubject(record.getSubject());
|
vo.setCategory(record.getCategory());
|
vo.setEvalMode(record.getEvalMode());
|
vo.setStartTime(record.getStartTime());
|
vo.setEndTime(record.getEndTime());
|
vo.setPersonCount(personCount);
|
vo.setSignedCount(signedCount);
|
vo.setPassedCount(passedCount);
|
vo.setSignRate(rateFloor(signedCount, personCount));
|
vo.setPassRate(rateFloor(passedCount, personCount));
|
result.add(vo);
|
}
|
return result;
|
}
|
|
@Override
|
public void exportExcel(TmsTrainStatsQuery query, HttpServletResponse response) {
|
TmsTrainStatsQuery q = query == null ? new TmsTrainStatsQuery() : query;
|
String groupBy = normalizeGroupBy(q.getGroupBy());
|
List<TmsTrainStatsRowVO> rows = list(q);
|
List<String> headers = Arrays.asList(
|
dimHeaderOf(groupBy), "培训场次", "应到人次", "签到人次", "合格人次", "签到率(%)", "合格率(%)");
|
List<Integer> widths = Arrays.asList(140, 80, 80, 80, 80, 80, 80);
|
List<List<String>> data = new ArrayList<>();
|
for (TmsTrainStatsRowVO row : rows) {
|
data.add(Arrays.asList(
|
StringUtil.isNotEmpty(row.getDimName()) ? row.getDimName() : row.getDimCode(),
|
str(row.getSessionCount()),
|
str(row.getPersonCount()),
|
str(row.getSignedCount()),
|
str(row.getPassedCount()),
|
str(row.getSignRate()),
|
str(row.getPassRate())
|
));
|
}
|
try {
|
TmsExcelExportHelper.writeSpreadsheet(response, "培训效果统计", "统计", headers, widths, data);
|
} catch (IOException e) {
|
throw new RuntimeException("导出失败", e);
|
}
|
}
|
|
/** 导出首列表头:统计维度(部门/培训分类/考核方式) */
|
private static String dimHeaderOf(String groupBy) {
|
if ("dept".equalsIgnoreCase(groupBy)) {
|
return "统计维度(部门)";
|
}
|
if ("evalMode".equalsIgnoreCase(groupBy)) {
|
return "统计维度(考核方式)";
|
}
|
return "统计维度(培训分类)";
|
}
|
|
private List<TmsTrainStatsRowVO> buildRows(TmsTrainStatsQuery query) {
|
String groupBy = normalizeGroupBy(query.getGroupBy());
|
List<TmsRecordEntity> records = loadRecords(query);
|
if (records.isEmpty()) {
|
return Collections.emptyList();
|
}
|
|
Map<String, Agg> aggMap = new LinkedHashMap<>();
|
Set<String> deptFilter = StringUtil.isEmpty(query.getDeptId())
|
? null
|
: Collections.singleton(query.getDeptId().trim());
|
|
for (TmsRecordEntity record : records) {
|
if (record == null || StringUtil.isEmpty(record.getTaskId())) {
|
continue;
|
}
|
List<TmsPersonTaskEntity> persons = loadPersons(record.getTaskId());
|
if ("dept".equalsIgnoreCase(groupBy)) {
|
Map<String, List<TmsPersonTaskEntity>> byDept = new HashMap<>();
|
for (TmsPersonTaskEntity p : persons) {
|
if (p == null || "1".equals(p.getGuestFlag())) {
|
continue;
|
}
|
String deptId = StringUtil.isEmpty(p.getDeptId()) ? "-" : p.getDeptId();
|
if (deptFilter != null && !deptFilter.contains(deptId)) {
|
continue;
|
}
|
byDept.computeIfAbsent(deptId, k -> new ArrayList<>()).add(p);
|
}
|
if (byDept.isEmpty()) {
|
continue;
|
}
|
for (Map.Entry<String, List<TmsPersonTaskEntity>> e : byDept.entrySet()) {
|
Agg agg = aggMap.computeIfAbsent(e.getKey(), k -> new Agg(k));
|
agg.sessionCount++;
|
accumulate(agg, e.getValue());
|
}
|
} else {
|
List<TmsPersonTaskEntity> filtered = filterByDept(persons, deptFilter);
|
if (deptFilter != null && filtered.isEmpty()) {
|
continue;
|
}
|
String key = dimKeyOfRecord(groupBy, record);
|
Agg agg = aggMap.computeIfAbsent(key, k -> new Agg(k));
|
agg.sessionCount++;
|
accumulate(agg, filtered);
|
}
|
}
|
|
Map<String, String> deptNames = resolveDeptNames(groupBy, aggMap.keySet());
|
List<TmsTrainStatsRowVO> result = new ArrayList<>();
|
for (Agg agg : aggMap.values()) {
|
TmsTrainStatsRowVO vo = new TmsTrainStatsRowVO();
|
vo.setDimCode(agg.key);
|
vo.setDimName(resolveDimName(groupBy, agg.key, deptNames));
|
vo.setSessionCount(agg.sessionCount);
|
vo.setPersonCount(agg.personCount);
|
vo.setSignedCount(agg.signedCount);
|
vo.setPassedCount(agg.passedCount);
|
vo.setSignRate(rateFloor(agg.signedCount, agg.personCount));
|
vo.setPassRate(rateFloor(agg.passedCount, agg.personCount));
|
result.add(vo);
|
}
|
return result;
|
}
|
|
/**
|
* 返回命中当前维度的学员列表;未命中返回 null(该场次不计入明细)。
|
*/
|
private List<TmsPersonTaskEntity> matchPersons(
|
String groupBy,
|
String dimCode,
|
TmsRecordEntity record,
|
List<TmsPersonTaskEntity> persons,
|
Set<String> deptFilter) {
|
if ("dept".equalsIgnoreCase(groupBy)) {
|
List<TmsPersonTaskEntity> matched = new ArrayList<>();
|
for (TmsPersonTaskEntity p : persons) {
|
if (p == null || "1".equals(p.getGuestFlag())) {
|
continue;
|
}
|
String deptId = StringUtil.isEmpty(p.getDeptId()) ? "-" : p.getDeptId();
|
if (deptFilter != null && !deptFilter.contains(deptId)) {
|
continue;
|
}
|
if (!dimCode.equals(deptId)) {
|
continue;
|
}
|
matched.add(p);
|
}
|
return matched.isEmpty() ? null : matched;
|
}
|
if (!dimCode.equals(dimKeyOfRecord(groupBy, record))) {
|
return null;
|
}
|
List<TmsPersonTaskEntity> filtered = filterByDept(persons, deptFilter);
|
if (deptFilter != null && filtered.isEmpty()) {
|
return null;
|
}
|
return filtered;
|
}
|
|
private List<TmsPersonTaskEntity> filterByDept(List<TmsPersonTaskEntity> persons, Set<String> deptFilter) {
|
List<TmsPersonTaskEntity> filtered = new ArrayList<>();
|
for (TmsPersonTaskEntity p : persons) {
|
if (p == null || "1".equals(p.getGuestFlag())) {
|
continue;
|
}
|
if (deptFilter != null && !deptFilter.contains(StringUtil.isEmpty(p.getDeptId()) ? "-" : p.getDeptId())) {
|
continue;
|
}
|
filtered.add(p);
|
}
|
return filtered;
|
}
|
|
private String dimKeyOfRecord(String groupBy, TmsRecordEntity record) {
|
if ("evalMode".equalsIgnoreCase(groupBy)) {
|
return StringUtil.isEmpty(record.getEvalMode()) ? "-" : record.getEvalMode();
|
}
|
return StringUtil.isEmpty(record.getCategory()) ? "-" : record.getCategory();
|
}
|
|
private List<TmsRecordEntity> loadRecords(TmsTrainStatsQuery query) {
|
LambdaQueryWrapper<TmsRecordEntity> qw = new LambdaQueryWrapper<>();
|
Date from = parseDate(query.getStartTimeFrom());
|
Date to = parseDate(query.getStartTimeTo());
|
if (from != null) {
|
qw.ge(TmsRecordEntity::getStartTime, from);
|
}
|
if (to != null) {
|
qw.le(TmsRecordEntity::getStartTime, to);
|
}
|
if (StringUtil.isNotEmpty(query.getCategory())) {
|
qw.eq(TmsRecordEntity::getCategory, query.getCategory().trim());
|
}
|
if (StringUtil.isNotEmpty(query.getEvalMode())) {
|
qw.eq(TmsRecordEntity::getEvalMode, query.getEvalMode().trim());
|
}
|
qw.orderByDesc(TmsRecordEntity::getStartTime);
|
List<TmsRecordEntity> records = recordMapper.selectList(qw);
|
return records == null ? Collections.emptyList() : records;
|
}
|
|
private List<TmsPersonTaskEntity> loadPersons(String taskId) {
|
List<TmsPersonTaskEntity> persons = personTaskMapper.selectList(
|
new LambdaQueryWrapper<TmsPersonTaskEntity>()
|
.eq(TmsPersonTaskEntity::getTaskId, taskId));
|
return persons == null ? Collections.emptyList() : persons;
|
}
|
|
private Map<String, String> resolveDeptNames(String groupBy, Set<String> keys) {
|
Map<String, String> deptNames = new HashMap<>();
|
if (!"dept".equalsIgnoreCase(groupBy) || keys == null || keys.isEmpty()) {
|
return deptNames;
|
}
|
Set<String> ids = new HashSet<>(keys);
|
ids.remove("-");
|
for (String id : ids) {
|
try {
|
OrganizeEntity org = organizeApi.getInfoById(id);
|
if (org != null && StringUtil.isNotEmpty(org.getFullName())) {
|
deptNames.put(id, org.getFullName());
|
}
|
} catch (Exception ignored) {
|
// ignore
|
}
|
}
|
return deptNames;
|
}
|
|
private String normalizeGroupBy(String groupBy) {
|
return StringUtil.isEmpty(groupBy) ? "category" : groupBy.trim();
|
}
|
|
/** 维度展示名:部门取组织名,分类/考核方式取枚举中文,避免前端回显字典编码 */
|
private String resolveDimName(String groupBy, String key, Map<String, String> deptNames) {
|
if (StringUtil.isEmpty(key) || "-".equals(key)) {
|
if ("dept".equalsIgnoreCase(groupBy)) {
|
return "未分配部门";
|
}
|
if ("evalMode".equalsIgnoreCase(groupBy)) {
|
return "未设置考核方式";
|
}
|
return "未分类";
|
}
|
if ("dept".equalsIgnoreCase(groupBy)) {
|
return deptNames.getOrDefault(key, key);
|
}
|
if ("evalMode".equalsIgnoreCase(groupBy)) {
|
TmsEvalMode mode = TmsEvalMode.of(key);
|
return mode != null ? mode.getLabel() : key;
|
}
|
TmsTaskCategory category = TmsTaskCategory.of(key);
|
return category != null ? category.getLabel() : key;
|
}
|
|
private void accumulate(Agg agg, List<TmsPersonTaskEntity> persons) {
|
for (TmsPersonTaskEntity p : persons) {
|
agg.personCount++;
|
if (p.getSignTime() != null) {
|
agg.signedCount++;
|
}
|
if (TmsPassFlag.isPass(p.getPassFlag())) {
|
agg.passedCount++;
|
}
|
}
|
}
|
|
private static int rateFloor(int num, int den) {
|
if (den <= 0) {
|
return 0;
|
}
|
return (int) Math.floor(num * 100.0 / den);
|
}
|
|
private Date parseDate(String text) {
|
if (StringUtil.isEmpty(text)) {
|
return null;
|
}
|
try {
|
return DateUtil.stringToDate(text);
|
} catch (Exception ex) {
|
return null;
|
}
|
}
|
|
private static String str(Integer v) {
|
return v == null ? "0" : String.valueOf(v);
|
}
|
|
private static class Agg {
|
private final String key;
|
private int sessionCount;
|
private int personCount;
|
private int signedCount;
|
private int passedCount;
|
|
private Agg(String key) {
|
this.key = key;
|
}
|
}
|
}
|