package jnpf.tmsService.impl;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import jnpf.exception.DataException;
|
import jnpf.tmsEntity.TmsAnnualPlanEntity;
|
import jnpf.tmsEntity.TmsAnnualPlanItemEntity;
|
import jnpf.tmsEntity.TmsMaterialEntity;
|
import jnpf.tmsEntity.TmsPaperEntity;
|
import jnpf.tmsEntity.TmsPersonTaskEntity;
|
import jnpf.tmsEntity.TmsPlaceEntity;
|
import jnpf.tmsEntity.TmsPostPlanEntity;
|
import jnpf.tmsEntity.TmsPostPlanItemEntity;
|
import jnpf.tmsEntity.TmsRecordEntity;
|
import jnpf.tmsEntity.TmsTaskEntity;
|
import jnpf.tmsEntity.TmsTaskFileEntity;
|
import jnpf.tmsEntity.TmsTrainerEntity;
|
import jnpf.tmsEntity.task.TmsFromPlanResult;
|
import jnpf.tmsEntity.task.TmsPersonTaskForm;
|
import jnpf.tmsEntity.task.TmsTaskFileForm;
|
import jnpf.tmsEntity.task.TmsTaskForm;
|
import jnpf.tmsEntity.task.TmsTaskQuery;
|
import jnpf.tmsMapper.TmsAnnualPlanItemMapper;
|
import jnpf.tmsMapper.TmsAnnualPlanMapper;
|
import jnpf.tmsMapper.TmsMaterialMapper;
|
import jnpf.tmsMapper.TmsPaperMapper;
|
import jnpf.tmsMapper.TmsPersonTaskMapper;
|
import jnpf.tmsMapper.TmsPlaceMapper;
|
import jnpf.tmsMapper.TmsPostPlanItemMapper;
|
import jnpf.tmsMapper.TmsPostPlanMapper;
|
import jnpf.tmsMapper.TmsRecordMapper;
|
import jnpf.tmsMapper.TmsTaskFileMapper;
|
import jnpf.tmsMapper.TmsTaskMapper;
|
import jnpf.tmsMapper.TmsTrainerMapper;
|
import jnpf.tmsService.TmsTaskService;
|
import jnpf.util.DateUtil;
|
import jnpf.util.RandomUtil;
|
import jnpf.util.StringUtil;
|
import jnpf.util.UserProvider;
|
import lombok.RequiredArgsConstructor;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.time.LocalDate;
|
import java.time.YearMonth;
|
import java.time.ZoneId;
|
import java.time.format.DateTimeFormatter;
|
import java.math.BigDecimal;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.Calendar;
|
import java.util.Date;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Objects;
|
import java.util.Set;
|
import java.util.stream.Collectors;
|
|
@Service
|
@RequiredArgsConstructor
|
public class TmsTaskServiceImpl implements TmsTaskService {
|
|
private static final String STATUS_DRAFT = "draft";
|
private static final String STATUS_PUBLISHED = "published";
|
private static final String STATUS_CANCELLED = "cancelled";
|
|
private static final String PERSON_TODO = "todo";
|
private static final String PERSON_SIGNED = "signed";
|
private static final String PERSON_DONE = "done";
|
private static final String PERSON_CANCELLED = "cancelled";
|
|
private static final String PREFIX_DRAFT = "TD";
|
private static final String PREFIX_PUBLISH = "TT";
|
private static final String FMT = "yyyy-MM-dd HH:mm:ss";
|
|
private final TmsTaskMapper taskMapper;
|
private final TmsTrainerMapper trainerMapper;
|
private final TmsPersonTaskMapper personTaskMapper;
|
private final TmsRecordMapper recordMapper;
|
private final TmsPaperMapper paperMapper;
|
private final TmsMaterialMapper materialMapper;
|
private final TmsTaskFileMapper taskFileMapper;
|
private final TmsPlaceMapper placeMapper;
|
private final TmsAnnualPlanMapper annualPlanMapper;
|
private final TmsAnnualPlanItemMapper annualPlanItemMapper;
|
private final TmsPostPlanMapper postPlanMapper;
|
private final TmsPostPlanItemMapper postPlanItemMapper;
|
|
@Override
|
public List<TmsTaskForm> getList(TmsTaskQuery query) {
|
LambdaQueryWrapper<TmsTaskEntity> qw = new LambdaQueryWrapper<>();
|
if (StringUtil.isNotEmpty(query.getPublishStatus())) {
|
qw.eq(TmsTaskEntity::getPublishStatus, query.getPublishStatus());
|
}
|
if (StringUtil.isNotEmpty(query.getTrainMode())) {
|
qw.eq(TmsTaskEntity::getTrainMode, query.getTrainMode());
|
}
|
if (StringUtil.isNotEmpty(query.getSourceType())) {
|
qw.eq(TmsTaskEntity::getSourceType, query.getSourceType());
|
}
|
if (StringUtil.isNotEmpty(query.getKeyword()) && !"null".equalsIgnoreCase(query.getKeyword())) {
|
String kw = query.getKeyword().trim();
|
qw.and(w -> w.like(TmsTaskEntity::getTaskNo, kw).or().like(TmsTaskEntity::getSubject, kw));
|
}
|
Date from = parseDayStart(query.getStartTimeFrom());
|
Date to = parseDayEnd(query.getStartTimeTo());
|
if (from != null) {
|
qw.ge(TmsTaskEntity::getStartTime, from);
|
}
|
if (to != null) {
|
qw.le(TmsTaskEntity::getStartTime, to);
|
}
|
qw.orderByDesc(TmsTaskEntity::getStartTime).orderByDesc(TmsTaskEntity::getCreatorTime);
|
|
long current = query.getCurrentPage() > 0 ? query.getCurrentPage() : 1;
|
long size = query.getPageSize() > 0 ? query.getPageSize() : 20;
|
Page<TmsTaskEntity> page = taskMapper.selectPage(new Page<>(current, size), qw);
|
|
List<TmsTaskForm> list = new ArrayList<>(page.getRecords().size());
|
for (TmsTaskEntity row : page.getRecords()) {
|
list.add(toForm(row, false));
|
}
|
query.setData(list, page.getTotal());
|
return list;
|
}
|
|
@Override
|
public TmsTaskForm getInfo(String id) {
|
TmsTaskEntity entity = requireOne(id);
|
TmsTaskForm form = toForm(entity, true);
|
List<TmsPersonTaskEntity> persons = personTaskMapper.selectList(
|
new LambdaQueryWrapper<TmsPersonTaskEntity>()
|
.eq(TmsPersonTaskEntity::getTaskId, id)
|
.orderByAsc(TmsPersonTaskEntity::getCreatorTime));
|
List<TmsPersonTaskForm> personForms = new ArrayList<>(persons.size());
|
for (TmsPersonTaskEntity p : persons) {
|
TmsPersonTaskForm pf = new TmsPersonTaskForm();
|
pf.setId(p.getId());
|
pf.setUserId(p.getUserId());
|
pf.setDeptId(p.getDeptId());
|
pf.setBizStatus(p.getBizStatus());
|
pf.setSignTime(formatDate(p.getSignTime()));
|
pf.setExamScore(p.getExamScore());
|
pf.setPassFlag(p.getPassFlag());
|
pf.setGuestFlag(p.getGuestFlag());
|
personForms.add(pf);
|
}
|
form.setPersonTasks(personForms);
|
form.setPersonCount(personForms.size());
|
form.setFiles(listTaskFiles(id));
|
return form;
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public String create(TmsTaskForm form) {
|
// 空白新增仅允许临时培训;年/岗计划任务应由计划侧生成并带 source_id/item_id
|
boolean hasSourceLink = StringUtil.isNotEmpty(form.getSourceId())
|
|| StringUtil.isNotEmpty(form.getSourceItemId());
|
if (!hasSourceLink) {
|
form.setSourceType("temp");
|
form.setCategory("temp");
|
}
|
validateForm(form, true);
|
TmsTaskEntity entity = new TmsTaskEntity();
|
entity.setId(RandomUtil.uuId());
|
entity.setTaskNo(nextTaskNo(PREFIX_DRAFT));
|
entity.setPublishStatus(STATUS_DRAFT);
|
entity.setTaskKind(StringUtil.isNotEmpty(form.getTaskKind()) ? form.getTaskKind() : "new");
|
fillEntity(entity, form, false);
|
applyDefaults(entity);
|
fillRefs(entity);
|
taskMapper.insert(entity);
|
saveTaskFiles(entity.getId(), form.getFiles());
|
return entity.getId();
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void update(String id, TmsTaskForm form) {
|
TmsTaskEntity entity = requireOne(id);
|
if (!STATUS_DRAFT.equals(entity.getPublishStatus())) {
|
throw new DataException("仅未发布任务可编辑");
|
}
|
boolean fromPlan = isPlanSourced(entity);
|
if (fromPlan) {
|
// 计划带出:来源链路只读,业务内容允许维护补全
|
form.setCategory(entity.getCategory());
|
form.setSourceType(entity.getSourceType());
|
form.setSourcePlanNo(entity.getSourcePlanNo());
|
form.setSourceId(entity.getSourceId());
|
form.setSourceItemId(entity.getSourceItemId());
|
}
|
validateForm(form, false);
|
fillEntity(entity, form, false);
|
applyDefaults(entity);
|
fillRefs(entity);
|
entity.setLastModifyUserId(UserProvider.getLoginUserId());
|
entity.setLastModifyTime(new Date());
|
taskMapper.updateById(entity);
|
if (form.getFiles() != null) {
|
saveTaskFiles(entity.getId(), form.getFiles());
|
}
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void delete(String id) {
|
TmsTaskEntity entity = requireOne(id);
|
if (!STATUS_DRAFT.equals(entity.getPublishStatus())) {
|
throw new DataException("仅未发布任务可删除");
|
}
|
Long cnt = personTaskMapper.selectCount(
|
new LambdaQueryWrapper<TmsPersonTaskEntity>().eq(TmsPersonTaskEntity::getTaskId, id));
|
if (cnt != null && cnt > 0) {
|
throw new DataException("已生成个人任务,不可删除");
|
}
|
taskMapper.deleteById(id);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void publish(String id) {
|
TmsTaskEntity entity = requireOne(id);
|
if (!STATUS_DRAFT.equals(entity.getPublishStatus())) {
|
throw new DataException("仅未发布任务可发布");
|
}
|
validateForPublish(entity);
|
|
String taskNo = entity.getTaskNo();
|
if (StringUtil.isEmpty(taskNo) || taskNo.startsWith(PREFIX_DRAFT)) {
|
taskNo = nextTaskNo(PREFIX_PUBLISH);
|
entity.setTaskNo(taskNo);
|
}
|
|
List<String> userIds = splitTrainees(entity.getTrainees());
|
if (userIds.isEmpty()) {
|
throw new DataException("培训对象不能为空");
|
}
|
|
Date now = new Date();
|
BigDecimal hours = resolveTaskHours(entity);
|
for (String userId : userIds) {
|
TmsPersonTaskEntity pt = new TmsPersonTaskEntity();
|
pt.setId(RandomUtil.uuId());
|
pt.setTaskId(entity.getId());
|
pt.setTaskNo(taskNo);
|
pt.setUserId(userId);
|
pt.setHours(hours);
|
pt.setBizStatus(PERSON_TODO);
|
pt.setGuestFlag("0");
|
pt.setCreatorUserId(UserProvider.getLoginUserId());
|
pt.setCreatorTime(now);
|
personTaskMapper.insert(pt);
|
}
|
|
Long recordCnt = recordMapper.selectCount(
|
new LambdaQueryWrapper<TmsRecordEntity>().eq(TmsRecordEntity::getTaskId, entity.getId()));
|
if (recordCnt == null || recordCnt == 0) {
|
TmsRecordEntity record = new TmsRecordEntity();
|
record.setId(RandomUtil.uuId());
|
record.setRecordNo(taskNo);
|
record.setTaskId(entity.getId());
|
record.setTaskNo(taskNo);
|
record.setCategory(entity.getCategory());
|
record.setSubject(entity.getSubject());
|
record.setTeacherId(entity.getTeacherId());
|
record.setTrainType(entity.getTrainType());
|
record.setTrainMode(entity.getTrainMode());
|
record.setEvalMode(entity.getEvalMode());
|
record.setStartTime(entity.getStartTime());
|
record.setEndTime(entity.getEndTime());
|
record.setCloseTime(entity.getCloseTime());
|
record.setArchiveStatus("pending");
|
record.setCreatorUserId(UserProvider.getLoginUserId());
|
record.setCreatorTime(now);
|
recordMapper.insert(record);
|
}
|
|
entity.setPublishStatus(STATUS_PUBLISHED);
|
entity.setPublishTime(now);
|
entity.setLastModifyUserId(UserProvider.getLoginUserId());
|
entity.setLastModifyTime(now);
|
taskMapper.updateById(entity);
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void cancel(String id) {
|
TmsTaskEntity entity = requireOne(id);
|
if (!STATUS_PUBLISHED.equals(entity.getPublishStatus())) {
|
throw new DataException("仅已发布任务可取消");
|
}
|
Long signed = personTaskMapper.selectCount(
|
new LambdaQueryWrapper<TmsPersonTaskEntity>()
|
.eq(TmsPersonTaskEntity::getTaskId, id)
|
.and(w -> w.isNotNull(TmsPersonTaskEntity::getSignTime)
|
.or().in(TmsPersonTaskEntity::getBizStatus, PERSON_SIGNED, PERSON_DONE)));
|
if (signed != null && signed > 0) {
|
throw new DataException("已有人员签到,不可取消");
|
}
|
|
Date now = new Date();
|
entity.setPublishStatus(STATUS_CANCELLED);
|
entity.setLastModifyUserId(UserProvider.getLoginUserId());
|
entity.setLastModifyTime(now);
|
taskMapper.updateById(entity);
|
|
personTaskMapper.update(null, new LambdaUpdateWrapper<TmsPersonTaskEntity>()
|
.eq(TmsPersonTaskEntity::getTaskId, id)
|
.set(TmsPersonTaskEntity::getBizStatus, PERSON_CANCELLED)
|
.set(TmsPersonTaskEntity::getLastModifyUserId, UserProvider.getLoginUserId())
|
.set(TmsPersonTaskEntity::getLastModifyTime, now));
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void cancelBatch(List<String> ids) {
|
if (ids == null || ids.isEmpty()) {
|
throw new DataException("请选择要取消的任务");
|
}
|
for (String id : ids) {
|
if (StringUtil.isNotEmpty(id)) {
|
cancel(id);
|
}
|
}
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public void restore(String id) {
|
TmsTaskEntity entity = requireOne(id);
|
if (!STATUS_CANCELLED.equals(entity.getPublishStatus())) {
|
throw new DataException("仅已取消任务可恢复");
|
}
|
Date now = new Date();
|
String userId = UserProvider.getLoginUserId();
|
|
// 逻辑删除已生成的个人任务与记录,便于再次编辑后发布
|
List<TmsPersonTaskEntity> persons = personTaskMapper.selectList(
|
new LambdaQueryWrapper<TmsPersonTaskEntity>().eq(TmsPersonTaskEntity::getTaskId, id));
|
for (TmsPersonTaskEntity p : persons) {
|
personTaskMapper.deleteById(p.getId());
|
}
|
List<TmsRecordEntity> records = recordMapper.selectList(
|
new LambdaQueryWrapper<TmsRecordEntity>().eq(TmsRecordEntity::getTaskId, id));
|
for (TmsRecordEntity r : records) {
|
recordMapper.deleteById(r.getId());
|
}
|
|
entity.setPublishStatus(STATUS_DRAFT);
|
entity.setPublishTime(null);
|
entity.setLastModifyUserId(userId);
|
entity.setLastModifyTime(now);
|
taskMapper.updateById(entity);
|
// 清空发布时间字段(updateById 可能跳过 null)
|
taskMapper.update(null, new LambdaUpdateWrapper<TmsTaskEntity>()
|
.eq(TmsTaskEntity::getId, id)
|
.set(TmsTaskEntity::getPublishTime, null));
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public TmsFromPlanResult createFromAnnual(String planId) {
|
if (StringUtil.isEmpty(planId)) {
|
throw new DataException("年计划ID不能为空");
|
}
|
String id = planId.trim();
|
TmsAnnualPlanEntity plan = annualPlanMapper.selectById(id);
|
if (plan == null) {
|
throw new DataException("年度培训计划不存在: " + id);
|
}
|
List<TmsAnnualPlanItemEntity> items = annualPlanItemMapper.selectList(
|
new LambdaQueryWrapper<TmsAnnualPlanItemEntity>()
|
.eq(TmsAnnualPlanItemEntity::getForeignId, plan.getId())
|
.orderByAsc(TmsAnnualPlanItemEntity::getTrainMonth));
|
if (items.isEmpty()) {
|
throw new DataException("年度计划没有明细,无法生成任务");
|
}
|
|
TmsFromPlanResult result = new TmsFromPlanResult();
|
result.setPlanId(plan.getId());
|
Date now = new Date();
|
String userId = UserProvider.getLoginUserId();
|
|
for (TmsAnnualPlanItemEntity item : items) {
|
TmsTaskEntity existed = findTaskBySourceItem(item.getId());
|
if (existed != null) {
|
result.setSkipped(result.getSkipped() + 1);
|
if (StringUtil.isEmpty(item.getTaskId())) {
|
item.setTaskId(existed.getId());
|
item.setLineStatus("tasked");
|
annualPlanItemMapper.updateById(item);
|
}
|
continue;
|
}
|
|
Date[] window = monthWindow(parseYear(plan.getPlanYear()), item.getTrainMonth());
|
TmsTaskEntity task = new TmsTaskEntity();
|
task.setId(RandomUtil.uuId());
|
task.setTaskNo(nextTaskNo(PREFIX_DRAFT));
|
task.setPublishStatus(STATUS_DRAFT);
|
task.setTaskKind("new");
|
task.setCategory("annual_plan");
|
task.setSourceType("annual");
|
task.setSourcePlanNo(plan.getPlanNo());
|
task.setSourceId(plan.getId());
|
task.setSourceItemId(item.getId());
|
String subject = StringUtil.isNotEmpty(item.getTrainSubject()) ? item.getTrainSubject().trim() : "年度培训";
|
task.setSubject(subject);
|
task.setTrainMode(item.getTrainMode());
|
task.setEvalMode(item.getEvalMode());
|
task.setTeacherId(resolveTeacherUserId(item.getTeacherId()));
|
task.setTrainees(normalizeTrainees(item.getTrainees()));
|
if (window != null) {
|
task.setStartTime(window[0]);
|
task.setEndTime(window[1]);
|
task.setCloseTime(window[1]);
|
}
|
task.setRetakeLimit(1);
|
task.setOverdueRemind("0");
|
task.setInviteFlag("0");
|
task.setCreatorUserId(userId);
|
task.setCreatorTime(now);
|
fillRefs(task);
|
taskMapper.insert(task);
|
|
item.setTaskId(task.getId());
|
item.setLineStatus("tasked");
|
annualPlanItemMapper.updateById(item);
|
|
result.setCreated(result.getCreated() + 1);
|
result.getTaskIds().add(task.getId());
|
}
|
return result;
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public TmsFromPlanResult createFromPost(String planId) {
|
if (StringUtil.isEmpty(planId)) {
|
throw new DataException("岗位培训计划ID不能为空");
|
}
|
String id = planId.trim();
|
TmsPostPlanEntity plan = postPlanMapper.selectById(id);
|
if (plan == null) {
|
throw new DataException("岗位培训计划不存在: " + id);
|
}
|
List<TmsPostPlanItemEntity> items = postPlanItemMapper.selectList(
|
new LambdaQueryWrapper<TmsPostPlanItemEntity>()
|
.eq(TmsPostPlanItemEntity::getForeignId, plan.getId())
|
.orderByAsc(TmsPostPlanItemEntity::getPlanTime));
|
if (items.isEmpty()) {
|
throw new DataException("岗位培训计划没有明细,无法生成任务");
|
}
|
|
TmsFromPlanResult result = new TmsFromPlanResult();
|
result.setPlanId(plan.getId());
|
Date now = new Date();
|
String userId = UserProvider.getLoginUserId();
|
String trainees = normalizeTrainees(plan.getTrainees());
|
|
for (TmsPostPlanItemEntity item : items) {
|
TmsTaskEntity existed = findTaskBySourceItem(item.getId());
|
if (existed != null) {
|
result.setSkipped(result.getSkipped() + 1);
|
if (StringUtil.isEmpty(item.getTaskId())) {
|
item.setTaskId(existed.getId());
|
item.setLineStatus("tasked");
|
postPlanItemMapper.updateById(item);
|
}
|
continue;
|
}
|
|
Date[] window = dayWindow(item.getPlanTime() != null ? item.getPlanTime() : plan.getPlanEndTime());
|
TmsTaskEntity task = new TmsTaskEntity();
|
task.setId(RandomUtil.uuId());
|
task.setTaskNo(nextTaskNo(PREFIX_DRAFT));
|
task.setPublishStatus(STATUS_DRAFT);
|
task.setTaskKind("new");
|
task.setCategory("post_plan");
|
task.setSourceType("post");
|
task.setSourcePlanNo(plan.getPlanNo());
|
task.setSourceId(plan.getId());
|
task.setSourceItemId(item.getId());
|
String subject = StringUtil.isNotEmpty(item.getSubject()) ? item.getSubject().trim() : null;
|
if (StringUtil.isEmpty(subject)) {
|
subject = StringUtil.isNotEmpty(plan.getPostName()) ? plan.getPostName() + "岗位培训" : "岗位培训";
|
}
|
task.setSubject(subject);
|
task.setTrainMode(item.getTrainMode());
|
task.setEvalMode(item.getEvalMode());
|
task.setTeacherId(resolveTeacherUserId(item.getTeacherId()));
|
task.setTrainees(trainees);
|
task.setMaterialId(item.getMaterialId());
|
if (window != null) {
|
task.setStartTime(window[0]);
|
task.setEndTime(window[1]);
|
task.setCloseTime(window[1]);
|
}
|
task.setRetakeLimit(1);
|
task.setOverdueRemind("0");
|
task.setInviteFlag("0");
|
task.setCreatorUserId(userId);
|
task.setCreatorTime(now);
|
fillRefs(task);
|
taskMapper.insert(task);
|
|
item.setTaskId(task.getId());
|
item.setLineStatus("tasked");
|
postPlanItemMapper.updateById(item);
|
|
result.setCreated(result.getCreated() + 1);
|
result.getTaskIds().add(task.getId());
|
}
|
return result;
|
}
|
|
private TmsTaskEntity findTaskBySourceItem(String itemId) {
|
if (StringUtil.isEmpty(itemId)) {
|
return null;
|
}
|
return taskMapper.selectOne(new LambdaQueryWrapper<TmsTaskEntity>()
|
.eq(TmsTaskEntity::getSourceItemId, itemId)
|
.last("limit 1"));
|
}
|
|
/**
|
* 年份:2026、2026-01-01…、或 13 位毫秒时间戳。
|
* 在线表单日期控件会把年份存成日期字符串。
|
*/
|
private static Integer parseYear(String raw) {
|
if (StringUtil.isEmpty(raw)) {
|
return null;
|
}
|
String text = raw.trim();
|
try {
|
if (text.matches("\\d{4}")) {
|
return Integer.parseInt(text);
|
}
|
if (text.matches("\\d{13}")) {
|
return java.time.Instant.ofEpochMilli(Long.parseLong(text))
|
.atZone(ZoneId.systemDefault()).getYear();
|
}
|
if (text.length() >= 4 && text.substring(0, 4).matches("\\d{4}")) {
|
return Integer.parseInt(text.substring(0, 4));
|
}
|
} catch (Exception ignored) {
|
return null;
|
}
|
return null;
|
}
|
|
/** 月份占位:精确时间在任务「执行安排」再改。支持 yyyyMM、1-12、yyyy-MM-dd、13 位毫秒时间戳。 */
|
private static Date[] monthWindow(Integer planYear, String trainMonth) {
|
if (StringUtil.isEmpty(trainMonth)) {
|
return null;
|
}
|
String raw = trainMonth.trim();
|
int year;
|
int month;
|
try {
|
if (raw.matches("\\d{13}")) {
|
java.time.LocalDate day = java.time.Instant.ofEpochMilli(Long.parseLong(raw))
|
.atZone(ZoneId.systemDefault()).toLocalDate();
|
year = day.getYear();
|
month = day.getMonthValue();
|
} else if (raw.matches("\\d{4}-\\d{2}-\\d{2}.*")) {
|
year = Integer.parseInt(raw.substring(0, 4));
|
month = Integer.parseInt(raw.substring(5, 7));
|
} else if (raw.matches("\\d{6}")) {
|
year = Integer.parseInt(raw.substring(0, 4));
|
month = Integer.parseInt(raw.substring(4, 6));
|
} else if (raw.matches("\\d{1,2}")) {
|
if (planYear == null) {
|
return null;
|
}
|
year = planYear;
|
month = Integer.parseInt(raw);
|
} else {
|
return null;
|
}
|
if (month < 1 || month > 12) {
|
return null;
|
}
|
YearMonth ym = YearMonth.of(year, month);
|
ZoneId zone = ZoneId.systemDefault();
|
Date start = Date.from(ym.atDay(1).atStartOfDay(zone).toInstant());
|
Date end = Date.from(ym.atEndOfMonth().atTime(23, 59, 59).atZone(zone).toInstant());
|
return new Date[]{start, end};
|
} catch (Exception ex) {
|
return null;
|
}
|
}
|
|
/** 岗位计划明细是具体日期;没有明细日期时用计划结束日。 */
|
private static Date[] dayWindow(Date day) {
|
if (day == null) {
|
return null;
|
}
|
try {
|
java.time.LocalDate date = day.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
ZoneId zone = ZoneId.systemDefault();
|
Date start = Date.from(date.atStartOfDay(zone).toInstant());
|
Date end = Date.from(date.atTime(23, 59, 59).atZone(zone).toInstant());
|
return new Date[]{start, end};
|
} catch (Exception ex) {
|
return null;
|
}
|
}
|
|
private static String normalizeTrainees(String raw) {
|
if (StringUtil.isEmpty(raw)) {
|
return null;
|
}
|
String text = raw.trim();
|
if (text.startsWith("[") && text.endsWith("]")) {
|
text = text.substring(1, text.length() - 1).replace("\"", "");
|
}
|
List<String> ids = splitTrainees(text);
|
return ids.isEmpty() ? text : joinTrainees(ids);
|
}
|
|
private void validateForm(TmsTaskForm form, boolean creating) {
|
if (form == null) {
|
throw new DataException("表单不能为空");
|
}
|
if (StringUtil.isEmpty(form.getSubject())) {
|
throw new DataException("培训主题不能为空");
|
}
|
if (StringUtil.isEmpty(form.getCategory())) {
|
throw new DataException("培训分类不能为空");
|
}
|
if (StringUtil.isEmpty(form.getTrainMode())) {
|
throw new DataException("培训方式不能为空");
|
}
|
if (StringUtil.isEmpty(form.getSourceType())) {
|
throw new DataException("来源类型不能为空");
|
}
|
if (StringUtil.isEmpty(form.getEvalMode())) {
|
throw new DataException("考核方式不能为空");
|
}
|
Date start = parseDate(form.getStartTime());
|
Date end = parseDate(form.getEndTime());
|
Date close = parseDate(form.getCloseTime());
|
if (start == null || end == null || close == null) {
|
throw new DataException("开始/结束/关闭时间不能为空");
|
}
|
if (start.after(end)) {
|
throw new DataException("开始时间不能晚于结束时间");
|
}
|
if (end.after(close)) {
|
throw new DataException("结束时间不能晚于关闭时间");
|
}
|
if (creating && start.before(new Date())) {
|
// 允许当天稍早几分钟;严格:开始不得早于当前日 00:00
|
Calendar cal = Calendar.getInstance();
|
cal.set(Calendar.HOUR_OF_DAY, 0);
|
cal.set(Calendar.MINUTE, 0);
|
cal.set(Calendar.SECOND, 0);
|
cal.set(Calendar.MILLISECOND, 0);
|
if (start.before(cal.getTime())) {
|
throw new DataException("开始时间不得早于当天");
|
}
|
}
|
|
String mode = form.getTrainMode();
|
if ("onsite".equals(mode) || "practice".equals(mode)) {
|
if (!sameDay(start, end)) {
|
throw new DataException("集中/操作授课的开始与结束须为同一天");
|
}
|
if (StringUtil.isEmpty(form.getPlaceId()) && StringUtil.isEmpty(form.getPlaceName())) {
|
throw new DataException("现场类培训须填写地点");
|
}
|
}
|
if ("online".equals(mode) && splitTrainees(form.getMaterialId()).isEmpty()) {
|
throw new DataException("在线学习须选择教材");
|
}
|
if ("exam".equals(form.getEvalMode())) {
|
if (StringUtil.isEmpty(form.getPaperId())) {
|
throw new DataException("在线考试须选择试卷");
|
}
|
Date examStart = parseDate(form.getExamStart());
|
Date examEnd = parseDate(form.getExamEnd());
|
if (examStart != null && examStart.before(start)) {
|
throw new DataException("考试开始不得早于培训开始");
|
}
|
if (examStart != null && examEnd != null && examStart.after(examEnd)) {
|
throw new DataException("考试开始不能晚于考试结束");
|
}
|
}
|
|
List<String> trainees = resolveTraineeIds(form);
|
if (trainees.isEmpty()) {
|
throw new DataException("培训对象不能为空");
|
}
|
|
Integer retake = form.getRetakeLimit();
|
if (retake != null && retake < 0) {
|
throw new DataException("补考次数不能为负数");
|
}
|
}
|
|
private void validateForPublish(TmsTaskEntity entity) {
|
if (StringUtil.isEmpty(entity.getSubject())
|
|| StringUtil.isEmpty(entity.getTrainMode())
|
|| StringUtil.isEmpty(entity.getEvalMode())
|
|| entity.getStartTime() == null
|
|| entity.getEndTime() == null
|
|| entity.getCloseTime() == null) {
|
throw new DataException("任务信息不完整,请先完善后发布");
|
}
|
if (splitTrainees(entity.getTrainees()).isEmpty()) {
|
throw new DataException("培训对象不能为空");
|
}
|
if ("exam".equals(entity.getEvalMode())) {
|
if (StringUtil.isEmpty(entity.getPaperId())) {
|
throw new DataException("在线考试须选择试卷");
|
}
|
TmsPaperEntity paper = paperMapper.selectById(entity.getPaperId());
|
if (paper == null) {
|
throw new DataException("所选试卷不存在");
|
}
|
if ("invalid".equals(paper.getBizStatus())) {
|
throw new DataException("失效试卷不可用于发布");
|
}
|
}
|
}
|
|
private void fillEntity(TmsTaskEntity entity, TmsTaskForm form, boolean keepPlanContent) {
|
if (!keepPlanContent) {
|
entity.setCategory(form.getCategory());
|
entity.setTrainType(form.getTrainType());
|
entity.setSourceType(form.getSourceType());
|
entity.setSourcePlanNo(form.getSourcePlanNo());
|
entity.setSourceId(form.getSourceId());
|
entity.setSourceItemId(form.getSourceItemId());
|
entity.setSubject(form.getSubject() == null ? null : form.getSubject().trim());
|
entity.setKeyPoints(form.getKeyPoints());
|
entity.setMaterialId(firstId(form.getMaterialId()));
|
entity.setMaterialNo(firstId(form.getMaterialNo()));
|
entity.setTeacherId(form.getTeacherId());
|
entity.setTrainMode(form.getTrainMode());
|
entity.setEvalMode(form.getEvalMode());
|
entity.setTrainees(joinTrainees(resolveTraineeIds(form)));
|
if (StringUtil.isNotEmpty(form.getTaskKind())) {
|
entity.setTaskKind(form.getTaskKind());
|
}
|
entity.setPrevTaskNo(form.getPrevTaskNo());
|
}
|
// 执行信息:计划/临时均可维护
|
entity.setPlaceId(form.getPlaceId());
|
entity.setPlaceNo(form.getPlaceNo());
|
entity.setPlaceName(form.getPlaceName());
|
entity.setStartTime(parseDate(form.getStartTime()));
|
entity.setEndTime(parseDate(form.getEndTime()));
|
entity.setCloseTime(parseDate(form.getCloseTime()));
|
entity.setPaperId(form.getPaperId());
|
entity.setPaperName(form.getPaperName());
|
entity.setExamStart(parseDate(form.getExamStart()));
|
entity.setExamEnd(parseDate(form.getExamEnd()));
|
entity.setRetakeLimit(form.getRetakeLimit() == null ? 1 : form.getRetakeLimit());
|
entity.setPassScore(form.getPassScore());
|
entity.setQuizRatio(form.getQuizRatio());
|
entity.setOverdueRemind(StringUtil.isEmpty(form.getOverdueRemind()) ? "0" : form.getOverdueRemind());
|
entity.setInviteFlag(StringUtil.isEmpty(form.getInviteFlag()) ? "0" : form.getInviteFlag());
|
entity.setRemark(form.getRemark());
|
}
|
|
private static boolean isPlanSourced(TmsTaskEntity entity) {
|
if (StringUtil.isNotEmpty(entity.getSourceId())
|
|| StringUtil.isNotEmpty(entity.getSourceItemId())
|
|| StringUtil.isNotEmpty(entity.getSourcePlanNo())) {
|
return true;
|
}
|
String st = entity.getSourceType();
|
return StringUtil.isNotEmpty(st) && !"temp".equals(st);
|
}
|
|
private void applyDefaults(TmsTaskEntity entity) {
|
if (entity.getRetakeLimit() == null) {
|
entity.setRetakeLimit(1);
|
}
|
if (StringUtil.isEmpty(entity.getOverdueRemind())) {
|
entity.setOverdueRemind("0");
|
}
|
if (StringUtil.isEmpty(entity.getInviteFlag())) {
|
entity.setInviteFlag("0");
|
}
|
if (StringUtil.isEmpty(entity.getTaskKind())) {
|
entity.setTaskKind("new");
|
}
|
}
|
|
private void fillRefs(TmsTaskEntity entity) {
|
if (StringUtil.isNotEmpty(entity.getPaperId())) {
|
TmsPaperEntity paper = paperMapper.selectById(entity.getPaperId());
|
if (paper != null) {
|
entity.setPaperName(paper.getPaperName());
|
if (entity.getPassScore() == null) {
|
entity.setPassScore(paper.getPassScore());
|
}
|
}
|
} else {
|
entity.setPaperName(null);
|
}
|
if (StringUtil.isNotEmpty(entity.getMaterialId())) {
|
TmsMaterialEntity material = materialMapper.selectById(entity.getMaterialId());
|
if (material != null && StringUtil.isNotEmpty(material.getMaterialNo())) {
|
entity.setMaterialNo(material.getMaterialNo());
|
}
|
} else {
|
entity.setMaterialNo(null);
|
}
|
if (StringUtil.isNotEmpty(entity.getPlaceId())) {
|
TmsPlaceEntity place = placeMapper.selectById(entity.getPlaceId());
|
if (place != null) {
|
entity.setPlaceNo(place.getPlaceNo());
|
entity.setPlaceName(place.getPlaceName());
|
}
|
}
|
}
|
|
private void saveTaskFiles(String taskId, List<TmsTaskFileForm> files) {
|
taskFileMapper.delete(new LambdaQueryWrapper<TmsTaskFileEntity>()
|
.eq(TmsTaskFileEntity::getForeignId, taskId));
|
if (files == null || files.isEmpty()) {
|
return;
|
}
|
Date now = new Date();
|
String userId = UserProvider.getLoginUserId();
|
for (TmsTaskFileForm row : files) {
|
if (row == null || StringUtil.isEmpty(row.getFileName())) {
|
continue;
|
}
|
TmsTaskFileEntity entity = new TmsTaskFileEntity();
|
entity.setId(RandomUtil.uuId());
|
entity.setForeignId(taskId);
|
entity.setFileNo(row.getFileNo());
|
entity.setFileName(row.getFileName().trim());
|
entity.setMaterialId(row.getMaterialId());
|
entity.setFileJson(row.getFileJson());
|
entity.setCanDownload(StringUtil.isEmpty(row.getCanDownload()) ? "0" : row.getCanDownload());
|
entity.setCreatorUserId(userId);
|
entity.setCreatorTime(now);
|
taskFileMapper.insert(entity);
|
}
|
}
|
|
private List<TmsTaskFileForm> listTaskFiles(String taskId) {
|
List<TmsTaskFileEntity> rows = taskFileMapper.selectList(
|
new LambdaQueryWrapper<TmsTaskFileEntity>()
|
.eq(TmsTaskFileEntity::getForeignId, taskId)
|
.orderByAsc(TmsTaskFileEntity::getCreatorTime));
|
List<TmsTaskFileForm> list = new ArrayList<>(rows.size());
|
for (TmsTaskFileEntity row : rows) {
|
TmsTaskFileForm form = new TmsTaskFileForm();
|
form.setId(row.getId());
|
form.setFileNo(row.getFileNo());
|
form.setFileName(row.getFileName());
|
form.setMaterialId(row.getMaterialId());
|
form.setFileJson(row.getFileJson());
|
form.setCanDownload(row.getCanDownload());
|
list.add(form);
|
}
|
return list;
|
}
|
|
private static String firstId(String raw) {
|
List<String> ids = splitTrainees(raw);
|
return ids.isEmpty() ? null : ids.get(0);
|
}
|
|
private synchronized String nextTaskNo(String typePrefix) {
|
String ym = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
|
String prefix = typePrefix + ym;
|
String max = taskMapper.selectMaxTaskNo(prefix);
|
int seq = 1;
|
if (StringUtil.isNotEmpty(max) && max.length() == 12) {
|
try {
|
seq = Integer.parseInt(max.substring(8)) + 1;
|
} catch (NumberFormatException ignored) {
|
seq = 1;
|
}
|
}
|
if (seq > 9999) {
|
throw new DataException("当月任务编号已用尽");
|
}
|
return prefix + String.format("%04d", seq);
|
}
|
|
/**
|
* 年计划子表 teacher_id 多为培训师档案 f_id;任务表单用 UserSelect,需落到系统用户。
|
*/
|
private String resolveTeacherUserId(String teacherRef) {
|
if (StringUtil.isEmpty(teacherRef)) {
|
return null;
|
}
|
String raw = teacherRef.trim();
|
TmsTrainerEntity trainer = trainerMapper.selectById(raw);
|
if (trainer != null && StringUtil.isNotEmpty(trainer.getUserId())) {
|
return trainer.getUserId();
|
}
|
// 已是系统用户,或档案尚未关联用户
|
return raw;
|
}
|
|
private TmsTaskEntity requireOne(String id) {
|
if (StringUtil.isEmpty(id)) {
|
throw new DataException("任务ID不能为空");
|
}
|
TmsTaskEntity entity = taskMapper.selectById(id);
|
if (entity == null) {
|
throw new DataException("培训任务不存在");
|
}
|
return entity;
|
}
|
|
private TmsTaskForm toForm(TmsTaskEntity entity, boolean detail) {
|
TmsTaskForm form = new TmsTaskForm();
|
form.setId(entity.getId());
|
form.setTaskNo(entity.getTaskNo());
|
form.setCategory(entity.getCategory());
|
form.setTrainType(entity.getTrainType());
|
form.setSourceType(entity.getSourceType());
|
form.setSourcePlanNo(entity.getSourcePlanNo());
|
form.setSourceId(entity.getSourceId());
|
form.setSourceItemId(entity.getSourceItemId());
|
form.setSubject(entity.getSubject());
|
form.setKeyPoints(entity.getKeyPoints());
|
form.setMaterialId(entity.getMaterialId());
|
form.setMaterialNo(entity.getMaterialNo());
|
form.setPlaceId(entity.getPlaceId());
|
form.setPlaceNo(entity.getPlaceNo());
|
form.setPlaceName(entity.getPlaceName());
|
form.setStartTime(formatDate(entity.getStartTime()));
|
form.setEndTime(formatDate(entity.getEndTime()));
|
form.setCloseTime(formatDate(entity.getCloseTime()));
|
form.setPaperId(entity.getPaperId());
|
form.setPaperName(entity.getPaperName());
|
form.setExamStart(formatDate(entity.getExamStart()));
|
form.setExamEnd(formatDate(entity.getExamEnd()));
|
form.setTeacherId(resolveTeacherUserId(entity.getTeacherId()));
|
form.setTrainMode(entity.getTrainMode());
|
form.setEvalMode(entity.getEvalMode());
|
form.setTrainees(entity.getTrainees());
|
form.setTraineeIds(splitTrainees(entity.getTrainees()));
|
form.setTaskKind(entity.getTaskKind());
|
form.setPrevTaskNo(entity.getPrevTaskNo());
|
form.setPublishStatus(entity.getPublishStatus());
|
form.setPublishTime(formatDate(entity.getPublishTime()));
|
form.setRetakeLimit(entity.getRetakeLimit());
|
form.setPassScore(entity.getPassScore());
|
form.setQuizRatio(entity.getQuizRatio());
|
form.setOverdueRemind(entity.getOverdueRemind());
|
form.setInviteFlag(entity.getInviteFlag());
|
form.setRemark(entity.getRemark());
|
form.setCreatorTime(formatDate(entity.getCreatorTime()));
|
form.setCreatorUserId(entity.getCreatorUserId());
|
if (detail) {
|
// person tasks filled by caller
|
}
|
return form;
|
}
|
|
private static List<String> resolveTraineeIds(TmsTaskForm form) {
|
Set<String> set = new LinkedHashSet<>();
|
if (form.getTraineeIds() != null) {
|
for (String id : form.getTraineeIds()) {
|
if (StringUtil.isNotEmpty(id)) {
|
set.add(id.trim());
|
}
|
}
|
}
|
if (set.isEmpty() && StringUtil.isNotEmpty(form.getTrainees())) {
|
set.addAll(splitTrainees(form.getTrainees()));
|
}
|
return new ArrayList<>(set);
|
}
|
|
private static List<String> splitTrainees(String raw) {
|
if (StringUtil.isEmpty(raw)) {
|
return new ArrayList<>();
|
}
|
return Arrays.stream(raw.split("[,;,;\\s]+"))
|
.map(String::trim)
|
.filter(s -> !s.isEmpty())
|
.distinct()
|
.collect(Collectors.toList());
|
}
|
|
private static String joinTrainees(List<String> ids) {
|
if (ids == null || ids.isEmpty()) {
|
return null;
|
}
|
return ids.stream().filter(Objects::nonNull).map(String::trim).filter(s -> !s.isEmpty())
|
.distinct().collect(Collectors.joining(","));
|
}
|
|
private static boolean sameDay(Date a, Date b) {
|
LocalDate da = a.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
LocalDate db = b.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
return da.equals(db);
|
}
|
|
private static Date parseDate(String text) {
|
if (StringUtil.isEmpty(text)) {
|
return null;
|
}
|
String raw = text.trim();
|
try {
|
if (raw.matches("^\\d{10,13}$")) {
|
long ts = Long.parseLong(raw);
|
if (raw.length() == 10) {
|
ts = ts * 1000L;
|
}
|
return new Date(ts);
|
}
|
return DateUtil.stringToDate(raw);
|
} catch (Exception ex) {
|
throw new DataException("时间格式无效:" + text);
|
}
|
}
|
|
private static Date parseDayStart(String day) {
|
if (StringUtil.isEmpty(day) || "null".equalsIgnoreCase(day)) {
|
return null;
|
}
|
String raw = day.trim();
|
if (raw.length() == 10) {
|
raw = raw + " 00:00:00";
|
}
|
return parseDate(raw);
|
}
|
|
private static Date parseDayEnd(String day) {
|
if (StringUtil.isEmpty(day) || "null".equalsIgnoreCase(day)) {
|
return null;
|
}
|
String raw = day.trim();
|
if (raw.length() == 10) {
|
raw = raw + " 23:59:59";
|
}
|
return parseDate(raw);
|
}
|
|
private BigDecimal resolveTaskHours(TmsTaskEntity task) {
|
if (task == null || StringUtil.isEmpty(task.getSourceItemId())) {
|
return null;
|
}
|
if ("annual".equals(task.getSourceType())) {
|
TmsAnnualPlanItemEntity item = annualPlanItemMapper.selectById(task.getSourceItemId());
|
return item == null ? null : item.getHours();
|
}
|
if ("post".equals(task.getSourceType())) {
|
TmsPostPlanItemEntity item = postPlanItemMapper.selectById(task.getSourceItemId());
|
return item == null ? null : item.getHours();
|
}
|
return null;
|
}
|
|
private static String formatDate(Date date) {
|
if (date == null) {
|
return null;
|
}
|
return DateUtil.dateToString(date, FMT);
|
}
|
}
|