Commit b57d5895 by 刘鑫

fix(容量时间片计算添加多工作队支持): 容量时间片计算添加单人多工作队支持

1 parent 99978b2e
......@@ -81,13 +81,13 @@ public class CapacityUtils {
//根据capacity_engineer_calendar和order_info,来确定当天剩下的最大连续时间区块 和已使用容量;
public static List<OccupyInfo> caculate(final LocalDateTime startTime, final LocalDateTime endTime, List<OrderInfoEntity> orders,
List<CapacityEngineerCalendarEntity> configs) {
List<OccupyInfoDetail> configs) {
//工作日历使用时间片
List<OccupyInfo> calendar = Optional.ofNullable(configs).orElse(Collections.emptyList())
.stream()
.filter(t -> DateUtil.checkTimesHasOverlap(t.getStartTime(), t.getEndTime(), startTime, endTime))
.map(e -> DateUtil.timesOverlap(e.getStartTime(), e.getEndTime(), startTime, endTime))
.filter(t -> DateUtil.checkTimesHasOverlap(t.getBeginTime(), t.getEndTime(), startTime, endTime))
.map(e -> DateUtil.timesOverlap(e.getBeginTime(), e.getEndTime(), startTime, endTime))
.collect(Collectors.toList());
// 工单已使用的时间片
List<OccupyInfo> order = Optional.ofNullable(orders).orElse(Collections.emptyList())
......
......@@ -11,6 +11,7 @@ import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Set;
@Repository
@Where(clause = "status = 1")
......@@ -41,6 +42,15 @@ public interface OrgTeamDao extends JpaRepository<OrgTeamEntity, Integer>, JpaSp
@Modifying
@Query("UPDATE OrgTeamEntity tt SET tt.status = :status WHERE tt.teamId = :teamId")
void updateStatusByTeamId(String teamId, int status);
public List<OrgTeamEntity> findByTeamIdIn(List<String> ids);
/**
* 获取工程师所在的工作队
*
* @param engineerCode 工程师代码
* @return 工程师所在工作队信息
*/
@Query(value = "SELECT ot.* FROM org_team ot LEFT JOIN org_team_engineer ote ON ote.team_id = ot.team_id WHERE ote.engineer_code = :engineerCode", nativeQuery = true)
Set<OrgTeamEntity> selectTeamByEngineerCode(String engineerCode);
}
......@@ -20,6 +20,7 @@ import com.dituhui.pea.order.entity.EngineerBusinessEntity;
import com.dituhui.pea.order.entity.EngineerInfoEntity;
import com.dituhui.pea.order.entity.OrderInfoEntity;
import com.dituhui.pea.order.entity.TimeSliceEntity;
import com.dituhui.pea.order.service.EngineerCalendarService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
......@@ -62,6 +63,8 @@ public class CalcEngineerCapacityScheduler {
private TimeSliceDao timeSliceDao;
@Autowired
private EngineerSliceUsedCapacityDao engineerSliceUsedCapacityDao;
@Autowired
private EngineerCalendarService engineerCalendarService;
@Scheduled(cron = "${scheduler.calc-engineer-capacity.cron-expr}")
public void run() {
......@@ -96,7 +99,7 @@ public class CalcEngineerCapacityScheduler {
EngineerBusinessEntity businessEntity = engineerBusinessDao.getByEngineerCode(engineerCode);
// 就算单小时时间段工程师技能已用容量存储至时间切片工程师时间表内
//添加工程师日历
List<CapacityEngineerCalendarEntity> configs = capacityEngineerCalendarDao.findCalendarByWorkdayAndEngineerCode(date, engineerCode);
List<OccupyInfoDetail> configs = engineerCalendarService.getEngineerWorkDayCalendar(engineerCode, LocalDate.parse(date, DateTimeUtil.DATE_FORMAT));
//剔除重排和取消单
orders = Optional.ofNullable(orders).orElse(Collections.emptyList())
.stream()
......@@ -124,7 +127,7 @@ public class CalcEngineerCapacityScheduler {
}
//添加工程师日历参数
private void initOneEngineerTimeSlot(String date, String engineerCode, List<CapacityEngineerCalendarEntity> configs,
private void initOneEngineerTimeSlot(String date, String engineerCode, List<OccupyInfoDetail> configs,
List<OrderInfoEntity> orders) {
final LocalDate localDate = LocalDate.parse(date, DateTimeUtil.DATE_FORMAT);
......
......@@ -2,6 +2,7 @@ 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;
......@@ -15,6 +16,7 @@ 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;
......@@ -62,6 +64,8 @@ public class InitEngineerCapacityScheduler {
private TimeSliceDao timeSliceDao;
@Autowired
private EngineerSliceUsedCapacityService EngineerSliceUsedCapacityService;
@Autowired
private EngineerCalendarService engineerCalendarService;
private boolean verifyCalendar(List<CapacityEngineerCalendarEntity> configs) {
// 检查多条请假配置是否有交叉行为; configs已经根据startTime排序
......@@ -80,11 +84,11 @@ public class InitEngineerCapacityScheduler {
return true;
}
private long sumLeaveTime(List<CapacityEngineerCalendarEntity> configs) {
return configs.stream().mapToLong(e -> Duration.between(e.getStartTime(), e.getEndTime()).toMinutes()).sum();
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<CapacityEngineerCalendarEntity> configs) {
private CapacityStats calculateWorkTime(String date, String engineerCode, List<OccupyInfoDetail> configs) {
// 计算一个工程师,一天的工作容量信息
// 省略实现细节
CapacityStats r = new CapacityStats();
......@@ -101,7 +105,7 @@ public class InitEngineerCapacityScheduler {
return new CapacityStats().setTotal(totalWorkTime).setUsed(totalLeaveTime).setRemain(totalWorkTime - totalLeaveTime);
}
private void initOneEngineerSlice(String date, String engineerCode, List<CapacityEngineerCalendarEntity> configs,
private void initOneEngineerSlice(String date, String engineerCode, List<OccupyInfoDetail> configs,
List<TimeSliceEntity> commonTimeSliceList) {
//查询时间片容量
List<CapacityEngineerSliceUsedEntity> engineerTimeSlice = EngineerSliceUsedCapacityService.findByWorkdayAndEngineerCode(date, engineerCode);
......@@ -110,6 +114,8 @@ public class InitEngineerCapacityScheduler {
return;
}
//TODO 日历需要添加
// 查询工程师正常的工作时间 并按小时切片:
EngineerBusinessEntity businessEntity = engineerBusinessDao.getByEngineerCode(engineerCode);
LocalDateTime workStartTime = DateUtils.localDateTimeFromStr(String.format("%s %s:00", date, businessEntity.getWorkOn()));
......@@ -134,8 +140,8 @@ public class InitEngineerCapacityScheduler {
//已用容量
long lengthOfLeave = 0L;
for (int i = 0; i < configs.size(); i++) {
CapacityEngineerCalendarEntity leave = configs.get(i);
LocalTime leaveStartTime = leave.getStartTime().toLocalTime();
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);
......@@ -149,7 +155,7 @@ public class InitEngineerCapacityScheduler {
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) {
} else if (leaveStartTime.isBefore(sliceStartLocalTime) && leaveEndTime.compareTo(sliceEndLocalTime) <= 0) {
lengthOfLeave += DateTimeUtil.betweenTwoTime(sliceStartLocalTime, leaveEndTime, TimeUnit.MINUTES);
}
}
......@@ -172,8 +178,13 @@ public class InitEngineerCapacityScheduler {
//切片开始时间
LocalTime sliceStartHour = LocalTime.of(workStartTime.getHour(), 0);
//切片结束时间
LocalTime sliceEndHour = LocalTime.of(workEndTime.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 -> {
......@@ -187,13 +198,9 @@ public class InitEngineerCapacityScheduler {
private void initOneEngineer(String date, String engineerCode) {
log.info("正在处理日期[{}] 技术员[{}]", date, engineerCode);
List<CapacityEngineerCalendarEntity> configs = capacityEngineerCalendarDao.findCalendarByWorkdayAndEngineerCode(date, engineerCode)
.stream().sorted(Comparator.comparing(CapacityEngineerCalendarEntity::getStartTime)).collect(Collectors.toList());
//事程日历添加工作队休息时间
List<OccupyInfoDetail> configs = engineerCalendarService.getEngineerWorkDayCalendar(engineerCode, LocalDate.parse(date, DateTimeUtil.DATE_FORMAT));
if (!configs.isEmpty() && !verifyCalendar(configs)) {
log.error("日期[{}]技术员[{}]请假配置检查失败,忽略退出", date, engineerCode);
return;
}
List<TimeSliceEntity> commonTimeSliceList = timeSliceDao.findByType("HOURS");
//初始化一个工程师时间切片容量
initOneEngineerSlice(date, engineerCode, configs, commonTimeSliceList);
......@@ -203,8 +210,7 @@ public class InitEngineerCapacityScheduler {
log.error("技术员容量信息记录已存在, 直接返回");
return;
}
String memo = configs.stream().map(CapacityEngineerCalendarEntity::getType).collect(Collectors.joining("/"));
log.info("日期[{}] 技术员[{}] 有日历记录需要特别处理 === {}", date, engineerCode, memo);
log.info("日期[{}] 技术员[{}] 有日历记录需要特别处理 ===", date, engineerCode);
CapacityStats stats = calculateWorkTime(date, engineerCode, configs);
log.info("日期[{}]技术员[{}],总容量[{}] 占用容量[{}] 剩余容量[{}]", date, engineerCode, stats.getTotal(), stats.getUsed(), stats.getRemain());
if (statEntity == null) {
......@@ -219,7 +225,6 @@ public class InitEngineerCapacityScheduler {
statEntity.setCapUsed((int) stats.getUsed());
statEntity.setCapLeft((int) stats.getRemain());
statEntity.setMaxDuration((int) stats.getRemain());
statEntity.setMemo(memo);
statEntity.setUpdateTime(LocalDateTime.now());
capacityEngineerStatDao.save(statEntity);
log.info("====== 处理完毕 ======");
......
package com.dituhui.pea.order.service;
import com.dituhui.pea.common.Result;
import com.dituhui.pea.order.dto.*;
import com.dituhui.pea.order.common.OccupyInfoDetail;
import com.dituhui.pea.order.dto.CalendarBatAddDTO;
import com.dituhui.pea.order.dto.CalendarBatDelDTO;
import com.dituhui.pea.order.dto.CalendarDeleteDTO;
import com.dituhui.pea.order.dto.CalendarDetailDTO;
import com.dituhui.pea.order.dto.CalendarQueryNumDTO;
import com.dituhui.pea.order.dto.CalendarUpdateDTO;
import com.dituhui.pea.order.dto.EngineerCalendarDTO;
import com.dituhui.pea.order.dto.param.EngineerCalendarResultDTO;
import java.time.LocalDate;
import java.util.List;
public interface EngineerCalendarService {
......@@ -26,10 +34,20 @@ public interface EngineerCalendarService {
/**
* 查询工程师日期范围内的日历事件安排, 返回的内容包括:(多条)开始时间,结束时间,事件代码,事件名称,事件说明
*
* @param engineerCode 工程师编码
* @param startDate 开始日期
* @param endDate 结束日期
* @return 日期范围内的日历, 包含开始时间,结束时间,事件代码,事件名称,事件说明
* @param startDate 开始日期
* @param endDate 结束日期
* @return 日期范围内的日历, 包含开始时间,结束时间,事件代码,事件名称,事件说明
*/
EngineerCalendarResultDTO queryEngineerCalendar(String engineerCode, LocalDate startDate, LocalDate endDate);
/**
* 获取工程师在指定日期的非工作时间安排 包含工作队休息时间和日程表事件记录时间, 其中日程表内事件未与工作队时间判定交集, 实际计算非工作时间时需求并集
*
* @param engineerCode 工程师编号
* @param targetDate 目标日期
* @return 没有交集的事件日程时间段即已占用的时间段
*/
List<OccupyInfoDetail> getEngineerWorkDayCalendar(String engineerCode, LocalDate targetDate);
}
......@@ -5,8 +5,10 @@ import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.IdUtil;
import com.dituhui.pea.common.BusinessException;
import com.dituhui.pea.common.Result;
import com.dituhui.pea.order.common.CapacityUtils;
import com.dituhui.pea.order.common.DateUtils;
import com.dituhui.pea.order.common.EngineerUtils;
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;
......@@ -55,11 +57,14 @@ import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
......@@ -543,6 +548,54 @@ public class EngineerCalendarServiceImpl implements EngineerCalendarService {
}
}
/**
* 获取工程师在指定日期的非工作时间安排 包含工作队休息时间和日程表事件记录时间, 其中日程表内事件未与工作队时间判定交集, 实际计算非工作时间时需求并集
*
* @param engineerCode 工程师编号
* @param targetDate 目标日期
* @return 没有交集的事件日程时间段即已占用的时间段
*/
@Override
public List<OccupyInfoDetail> getEngineerWorkDayCalendar(String engineerCode, LocalDate targetDate) {
//日程表事件记录时间
List<CapacityEngineerCalendarEntity> configs = capacityEngineerCalendarDao.findCalendarByWorkdayAndEngineerCode(DateTimeUtil.formatDate(targetDate), engineerCode);
// 这里没有重复时间段
List<OccupyInfoDetail> calendarInfoList = Optional.ofNullable(configs).orElse(Collections.emptyList())
.stream()
.map(leave -> {
LocalTime leaveStartTime = leave.getStartTime().toLocalTime();
LocalTime leaveEndTime = leave.getEndTime().toLocalTime();
return new OccupyInfoDetail(LocalDateTime.of(targetDate, leaveStartTime), LocalDateTime.of(targetDate, leaveEndTime));
}).collect(Collectors.toList());
//获取工作队休息时间, 判定目标时间是否在工作队休息日中
Set<OrgTeamEntity> teams = orgTeamDao.selectTeamByEngineerCode(engineerCode);
if (!CollectionUtils.isEmpty(teams)) {
List<String> teamCommonWorkdaysOfWeek = teams.stream()
.map(team -> Arrays.asList(team.getWorkdays().split(",")))
.flatMap(Collection::stream)
.distinct()
.collect(Collectors.toList());
List<String> allWorkDayOfWeek = List.of("1", "2", "3", "4", "5", "6", "7");
// 求多个工作队的公共空闲时间
final int dayOfWeek = targetDate.getDayOfWeek().getValue();
List<String> commonLeisureDayOfWeek = allWorkDayOfWeek.stream().filter(day -> teamCommonWorkdaysOfWeek.stream().noneMatch(tDay -> Objects.equals(day, tDay)))
.collect(Collectors.toList());
if (commonLeisureDayOfWeek.contains(String.valueOf(dayOfWeek))) {
LocalDateTime startTime = LocalDateTime.of(targetDate, LocalTime.of(8, 0));
LocalDateTime endTime = LocalDateTime.of(targetDate, LocalTime.of(23, 59, 59));
calendarInfoList.add(new OccupyInfoDetail(startTime, endTime));
}
//求所有事程时间与工作队休息时间的并集
calendarInfoList = CapacityUtils.calculateUnion(calendarInfoList);
}
return calendarInfoList;
}
private EngineerCalendarDTO.Calendar getEmptyCalendar(String teamId, String date) {
// 初始化一天的日历
OrgTeamEntity e = orgTeamDao.getByTeamId(teamId);
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!