Commit 16618b85 by Ren Ping

feat:初始化工程师容量定时任务 dispatch

1 parent 3fd98370
package com.dituhui.pea.order.scheduler;
import cn.hutool.core.collection.CollectionUtil;
import com.dituhui.pea.order.common.DateUtils;
import com.dituhui.pea.order.common.OccupyInfoDetail;
import com.dituhui.pea.order.common.jackson.DateTimeUtil;
import com.dituhui.pea.order.common.jackson.DateUtil;
import com.dituhui.pea.order.dao.CapacityEngineerCalendarDao;
import com.dituhui.pea.order.dao.CapacityEngineerStatDao;
import com.dituhui.pea.order.dao.EngineerBusinessDao;
import com.dituhui.pea.order.dao.EngineerInfoDao;
import com.dituhui.pea.order.dao.TimeSliceDao;
import com.dituhui.pea.order.entity.CapacityEngineerCalendarEntity;
import com.dituhui.pea.order.entity.CapacityEngineerSliceUsedEntity;
import com.dituhui.pea.order.entity.CapacityEngineerStatEntity;
import com.dituhui.pea.order.entity.EngineerBusinessEntity;
import com.dituhui.pea.order.entity.EngineerInfoEntity;
import com.dituhui.pea.order.entity.TimeSliceEntity;
import com.dituhui.pea.order.service.EngineerCalendarService;
import com.dituhui.pea.order.service.EngineerSliceUsedCapacityService;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Slf4j
@Component
public class InitEngineerCapacityScheduler {
@Value("${scheduler.init-engineer-capacity.day-offset-begin}")
private int dayOffsetBegin;
@Value("${scheduler.init-engineer-capacity.day-offset-end}")
private int dayOffsetEnd;
@Value("${scheduler.init-engineer-capacity.rewrite-force}")
private boolean rewriteForce;
@Autowired
private EngineerBusinessDao engineerBusinessDao;
@Autowired
private CapacityEngineerCalendarDao capacityEngineerCalendarDao;
@Autowired
private CapacityEngineerStatDao capacityEngineerStatDao;
@Autowired
private EngineerInfoDao engineerInfoDao;
@Autowired
private TimeSliceDao timeSliceDao;
@Autowired
private EngineerSliceUsedCapacityService EngineerSliceUsedCapacityService;
@Autowired
private EngineerCalendarService engineerCalendarService;
private boolean verifyCalendar(List<CapacityEngineerCalendarEntity> configs) {
// 检查多条请假配置是否有交叉行为; configs已经根据startTime排序
for (int i = 0; i < configs.size(); i++) {
for (int j = i + 1; j < configs.size(); j++) {
CapacityEngineerCalendarEntity config1 = configs.get(i);
CapacityEngineerCalendarEntity config2 = configs.get(j);
// 必须是 config1的start<end; config2的start<end, 且config1.end <= config2.start
if (config1.getStartTime().isAfter(config1.getEndTime())
|| config2.getStartTime().isAfter(config2.getStartTime())
|| config1.getEndTime().isAfter(config2.getStartTime())) {
return false;
}
}
}
return true;
}
private long sumLeaveTime(List<OccupyInfoDetail> configs) {
return configs.stream().mapToLong(e -> Duration.between(e.getBeginTime(), e.getEndTime()).toMinutes()).sum();
}
private CapacityStats calculateWorkTime(String date, String engineerCode, List<OccupyInfoDetail> configs) {
// 计算一个工程师,一天的工作容量信息
// 省略实现细节
CapacityStats r = new CapacityStats();
EngineerBusinessEntity businessEntity = engineerBusinessDao.getByEngineerCode(engineerCode);
LocalDateTime startTime = DateUtils.localDateTimeFromStr(String.format("%s %s:00", date, businessEntity.getWorkOn()));
LocalDateTime endTime = DateUtils.localDateTimeFromStr(String.format("%s %s:00", date, businessEntity.getWorkOff()));
long totalWorkTime = Duration.between(startTime, endTime).toMinutes();
long totalLeaveTime = 0;
if (configs.isEmpty()) {
log.warn("日期[{}]技术员[{}]无记录,当全勤处理", date, engineerCode);
} else {
totalLeaveTime = sumLeaveTime(configs);
}
return new CapacityStats().setTotal(totalWorkTime).setUsed(totalLeaveTime).setRemain(totalWorkTime - totalLeaveTime);
}
private void initOneEngineerSlice(String date, String engineerCode, List<OccupyInfoDetail> configs,
List<TimeSliceEntity> commonTimeSliceList) {
//查询时间片容量
List<CapacityEngineerSliceUsedEntity> engineerTimeSlice = EngineerSliceUsedCapacityService.findByWorkdayAndEngineerCode(date, engineerCode);
if (!CollectionUtil.isEmpty(engineerTimeSlice) && !rewriteForce) {
log.warn("工程师:{}存在日期:{}时间切片记录, 无需初始化", engineerCode, date);
return;
}
// 查询工程师正常的工作时间 并按小时切片:
EngineerBusinessEntity businessEntity = engineerBusinessDao.getByEngineerCode(engineerCode);
LocalDateTime workStartTime = DateUtils.localDateTimeFromStr(String.format("%s %s:00", date, businessEntity.getWorkOn()));
LocalDateTime workEndTime = DateUtils.localDateTimeFromStr(String.format("%s %s:00", date, businessEntity.getWorkOff()));
List<TimeSliceEntity> timeCorridor = getTimeSliceEntities(workStartTime, workEndTime, commonTimeSliceList);
ArrayList<CapacityEngineerSliceUsedEntity> resultList = new ArrayList<>(timeCorridor.size());
for (TimeSliceEntity timeSlice : timeCorridor) {
final Long id = timeSlice.getId();
Optional<CapacityEngineerSliceUsedEntity> sliceUsedEntity = Optional.ofNullable(engineerTimeSlice).orElse(Collections.emptyList())
.stream()
.filter(t -> Objects.equals(t.getTimmeSlice().getId(), id)).findFirst();
CapacityEngineerSliceUsedEntity r = sliceUsedEntity.orElseGet(CapacityEngineerSliceUsedEntity::new);
r.setTimmeSlice(timeSlice);
r.setEngineerCode(engineerCode);
r.setWorkday(date);
LocalTime sliceStartLocalTime = LocalTime.parse(timeSlice.getStart(), DateUtil.TIME_FORMATTER);
LocalTime sliceEndLocalTime = LocalTime.parse(timeSlice.getEnd(), DateUtil.TIME_FORMATTER);
r.setCapTotal(DateTimeUtil.betweenTwoTime(sliceStartLocalTime, sliceEndLocalTime, TimeUnit.MINUTES));
//校验是否有在当前时间段的请假时间
//已用容量
long lengthOfLeave = 0L;
for (int i = 0; i < configs.size(); i++) {
OccupyInfoDetail leave = configs.get(i);
LocalTime leaveStartTime = leave.getBeginTime().toLocalTime();
LocalTime leaveEndTime = leave.getEndTime().toLocalTime();
boolean startIn = DateTimeUtil.isIn(leaveStartTime, sliceStartLocalTime, sliceEndLocalTime);
boolean endAfter = leaveEndTime.isAfter(sliceEndLocalTime);
//8:00- 8:30 8:00 - 10:30
//请假时间仅落在当前时间段内, 当前时间段请假时长为 请假结束时间- 请假开始时间
if (startIn && DateTimeUtil.isIn(leaveEndTime, sliceStartLocalTime, sliceEndLocalTime)) {
lengthOfLeave += DateTimeUtil.betweenTwoTime(leaveStartTime, leaveEndTime, TimeUnit.MINUTES);
} else if (startIn && endAfter) {
//落在当前时间段和下一个时间段
lengthOfLeave += DateTimeUtil.betweenTwoTime(leaveStartTime, sliceEndLocalTime, TimeUnit.MINUTES);
} else if (leaveStartTime.isBefore(sliceStartLocalTime) && leaveEndTime.isAfter(sliceEndLocalTime)) {
lengthOfLeave += DateTimeUtil.betweenTwoTime(sliceStartLocalTime, sliceEndLocalTime, TimeUnit.MINUTES);
} else if (leaveStartTime.isBefore(sliceStartLocalTime) && leaveEndTime.compareTo(sliceEndLocalTime) <= 0) {
lengthOfLeave += DateTimeUtil.betweenTwoTime(sliceStartLocalTime, leaveEndTime, TimeUnit.MINUTES);
}
}
// 剩余可约容量
long leftUseTime = r.getCapTotal() - lengthOfLeave;
r.setCapLeft(leftUseTime);
r.setMaxDuration(leftUseTime);
r.setCapUsed(lengthOfLeave);
r.setCreateTime(LocalDateTime.now());
r.setUpdateTime(LocalDateTime.now());
resultList.add(r);
}
EngineerSliceUsedCapacityService.saveAll(resultList);
}
private List<TimeSliceEntity> getTimeSliceEntities(LocalDateTime workStartTime, LocalDateTime workEndTime,
List<TimeSliceEntity> commonTimeSliceList) {
//切片开始时间
LocalTime sliceStartHour = LocalTime.of(workStartTime.getHour(), 0);
//切片结束时间
LocalTime sliceEndHour;
int minuteOfHour = workEndTime.getMinute();
if (minuteOfHour > 0) {
sliceEndHour = LocalTime.of(workEndTime.getHour(), 0).plusHours(1);
} else {
sliceEndHour = LocalTime.of(workEndTime.getHour(), 0);
}
List<TimeSliceEntity> timeCorridor = commonTimeSliceList.stream()
.filter(slice -> {
LocalTime startLocalTime = LocalTime.parse(slice.getStart(), DateUtil.TIME_FORMATTER);
LocalTime endLocalTime = LocalTime.parse(slice.getEnd(), DateUtil.TIME_FORMATTER);
return (startLocalTime.isAfter(sliceStartHour) && endLocalTime.isBefore(sliceEndHour)) ||
(startLocalTime.equals(sliceStartHour) || endLocalTime.equals(sliceEndHour));
}).sorted(Comparator.comparing(TimeSliceEntity::getId)).collect(Collectors.toList());
return timeCorridor;
}
private void initOneEngineer(String date, String engineerCode) {
log.info("正在处理日期[{}] 技术员[{}]", date, engineerCode);
//事程日历添加工作队休息时间
List<OccupyInfoDetail> configs = engineerCalendarService.getEngineerWorkDayCalendar(engineerCode, LocalDate.parse(date, DateTimeUtil.DATE_FORMAT));
List<TimeSliceEntity> commonTimeSliceList = timeSliceDao.findByType("HOURS");
//初始化一个工程师时间切片容量
initOneEngineerSlice(date, engineerCode, configs, commonTimeSliceList);
// 初始化一个工程师、一天的容量
CapacityEngineerStatEntity statEntity = capacityEngineerStatDao.getByWorkdayAndEngineerCode(date, engineerCode);
if (statEntity != null && !rewriteForce) {
log.error("技术员容量信息记录已存在, 直接返回");
return;
}
log.info("日期[{}] 技术员[{}] 有日历记录需要特别处理 ===", date, engineerCode);
CapacityStats stats = calculateWorkTime(date, engineerCode, configs);
log.info("日期[{}]技术员[{}],总容量[{}] 占用容量[{}] 剩余容量[{}]", date, engineerCode, stats.getTotal(), stats.getUsed(), stats.getRemain());
if (statEntity == null) {
statEntity = new CapacityEngineerStatEntity();
statEntity.setEngineerCode(engineerCode);
statEntity.setWorkday(date);
statEntity.setCapUsedTravel(0);
statEntity.setOrderCount(0);
statEntity.setCreateTime(LocalDateTime.now());
}
statEntity.setCapTotal((int) stats.getTotal());
statEntity.setCapUsed((int) stats.getUsed());
statEntity.setCapLeft((int) stats.getRemain());
statEntity.setMaxDuration((int) stats.getRemain());
statEntity.setUpdateTime(LocalDateTime.now());
capacityEngineerStatDao.save(statEntity);
log.info("====== 处理完毕 ======");
}
public void initOneEngineerByDays(String bdate, String edate, String engineerCode) {
log.info("==== initAllEngineerByDays, bdate[{}] edate[{}]", bdate, edate);
LocalDate currentDate = DateUtils.localDateFromStr(bdate);
LocalDate endDate = DateUtils.localDateFromStr(edate);
List<String> allEngineerCodes = engineerInfoDao.findAll().stream().map(EngineerInfoEntity::getEngineerCode).collect(Collectors.toList());
while (!currentDate.isAfter(endDate)) {
initOneEngineer(DateUtils.formatDate(currentDate), engineerCode);
currentDate = currentDate.plusDays(1);
}
}
private void initAllEngineerByDays(String bdate, String edate) {
log.info("==== initAllEngineerByDays, bdate[{}] edate[{}]", bdate, edate);
LocalDate currentDate = DateUtils.localDateFromStr(bdate);
LocalDate endDate = DateUtils.localDateFromStr(edate);
List<String> allEngineerCodes = engineerInfoDao.findAll().stream().map(EngineerInfoEntity::getEngineerCode).collect(Collectors.toList());
while (!currentDate.isAfter(endDate)) {
for (String engineerCode : allEngineerCodes) {
initOneEngineer(DateUtils.formatDate(currentDate), engineerCode);
}
currentDate = currentDate.plusDays(1);
}
}
//@Scheduled(cron = "${scheduler.init-engineer-capacity.cron-expr}")
public void run() {
log.info("开始初始化,所有工程师的容量将根据日历表的记录进行计算设置");
String bdate = DateUtils.formatDate(LocalDate.now().plusDays(dayOffsetBegin));
String edate = DateUtils.formatDate(LocalDate.now().plusDays(dayOffsetEnd));
initAllEngineerByDays(bdate, edate);
}
@Data
@Accessors(chain = true)
private static class CapacityStats {
private long total;
private long used;
private long remain;
}
}
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!