Commit b2c159d3 by 刘鑫

Merge branch 'develop-16542' into 'develop'

Develop 16542

See merge request !361
2 parents 36263ee8 185fa736
...@@ -4,8 +4,10 @@ import com.dituhui.pea.order.common.jackson.DateUtil; ...@@ -4,8 +4,10 @@ import com.dituhui.pea.order.common.jackson.DateUtil;
import com.dituhui.pea.order.dao.MapBlockInfoDao; import com.dituhui.pea.order.dao.MapBlockInfoDao;
import com.dituhui.pea.order.dao.SkillInfoDao; import com.dituhui.pea.order.dao.SkillInfoDao;
import com.dituhui.pea.order.dto.param.CapacityQueryDTO; import com.dituhui.pea.order.dto.param.CapacityQueryDTO;
import com.dituhui.pea.order.entity.CapacityEngineerCalendarEntity;
import com.dituhui.pea.order.entity.CapacityEngineerSliceUsedEntity; import com.dituhui.pea.order.entity.CapacityEngineerSliceUsedEntity;
import com.dituhui.pea.order.entity.MapBlockInfoEntity; import com.dituhui.pea.order.entity.MapBlockInfoEntity;
import com.dituhui.pea.order.entity.OrderInfoEntity;
import com.dituhui.pea.order.entity.SkillInfoEntity; import com.dituhui.pea.order.entity.SkillInfoEntity;
import com.dituhui.pea.order.entity.TimeSliceEntity; import com.dituhui.pea.order.entity.TimeSliceEntity;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
...@@ -13,17 +15,23 @@ import org.apache.commons.collections.CollectionUtils; ...@@ -13,17 +15,23 @@ import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.time.Duration;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.LocalTime; import java.time.LocalTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.OptionalLong; import java.util.OptionalLong;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask; import java.util.concurrent.ForkJoinTask;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
@Component @Component
@Slf4j @Slf4j
...@@ -70,6 +78,48 @@ public class CapacityUtils { ...@@ -70,6 +78,48 @@ public class CapacityUtils {
return layers; return layers;
} }
//根据capacity_engineer_calendar和order_info,来确定当天剩下的最大连续时间区块 和已使用容量;
public static List<OccupyInfo> caculate(final LocalDateTime startTime, final LocalDateTime endTime, List<OrderInfoEntity> orders,
List<CapacityEngineerCalendarEntity> 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))
.collect(Collectors.toList());
// 工单已使用的时间片
List<OccupyInfo> order = Optional.ofNullable(orders).orElse(Collections.emptyList())
.stream()
.filter(t -> DateUtil.checkTimesHasOverlap(t.getPlanStartTime(), t.getPlanEndTime(), startTime, endTime))
.map(e -> DateUtil.timesOverlap(e.getPlanStartTime(), e.getPlanEndTime(), startTime, endTime))
.collect(Collectors.toList());
//存放所有的已用时间段信息
return Stream.of(calendar, order).flatMap(Collection::stream)
.sorted(Comparator.comparing(OccupyInfo::getBeginTime)).collect(Collectors.toList());
}
public static Long getMaxRemainBlock(LocalDateTime startTime, LocalDateTime endTime, List<OccupyInfo> occupyInfos) {
if (org.springframework.util.CollectionUtils.isEmpty(occupyInfos)) {
return Duration.between(startTime, endTime).toMinutes();
}
//空闲时间
List<Long> idlePeriods = new ArrayList<>();
LocalDateTime preLast = startTime;
for (OccupyInfo o : occupyInfos) {
if (o.getBeginTime().isAfter(preLast)) {
idlePeriods.add(Duration.between(startTime, o.getBeginTime()).toMinutes());
}
preLast = o.getEndTime();
}
if (preLast.isBefore(endTime)) {
idlePeriods.add(Duration.between(preLast, endTime).toMinutes());
}
return org.springframework.util.CollectionUtils.isEmpty(idlePeriods) ? Duration.between(startTime, endTime).toMinutes() : Collections.max(idlePeriods);
}
public static List<CapacityQueryDTO.Segment> getEngineerTypeDay(List<TimeSliceEntity> typeTimeSlice, public static List<CapacityQueryDTO.Segment> getEngineerTypeDay(List<TimeSliceEntity> typeTimeSlice,
List<CapacityEngineerSliceUsedEntity> engineerTimeSlice, List<CapacityEngineerSliceUsedEntity> engineerTimeSlice,
......
package com.dituhui.pea.order.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class OccupyInfo {
private LocalDateTime beginTime;
private LocalDateTime endTime;
}
package com.dituhui.pea.order.common.jackson; package com.dituhui.pea.order.common.jackson;
import com.dituhui.pea.order.common.DateSplit; import com.dituhui.pea.order.common.DateSplit;
import com.dituhui.pea.order.common.OccupyInfo;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import org.springframework.util.Assert; import org.springframework.util.Assert;
...@@ -9,6 +10,7 @@ import java.time.Duration; ...@@ -9,6 +10,7 @@ import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Period; import java.time.Period;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
...@@ -470,6 +472,7 @@ public class DateUtil { ...@@ -470,6 +472,7 @@ public class DateUtil {
return Duration.between(startDate.toInstant(), endDate.toInstant()); return Duration.between(startDate.toInstant(), endDate.toInstant());
} }
/** /**
* 判断两个时间范围是否有交集 * 判断两个时间范围是否有交集
* *
...@@ -477,16 +480,25 @@ public class DateUtil { ...@@ -477,16 +480,25 @@ public class DateUtil {
* @param dynaEndTime 比较时间段结束时间 * @param dynaEndTime 比较时间段结束时间
* @param fixedStartTime 参考时间段开始时间 * @param fixedStartTime 参考时间段开始时间
* @param fixedEndTime 参考时间段结束时间 * @param fixedEndTime 参考时间段结束时间
* @return * @return 求交集时间片时 返回交集时间片, 没有对应情况则返回null
*/ */
public static boolean checkTimesHasOverlap(Date dynaStartTime, Date dynaEndTime, Date fixedStartTime, Date fixedEndTime) { public static OccupyInfo timesOverlap(LocalDateTime dynaStartTime, LocalDateTime dynaEndTime,
if (dynaStartTime.getTime() <= fixedStartTime.getTime() && dynaEndTime.getTime() > fixedStartTime.getTime()) { LocalDateTime fixedStartTime, LocalDateTime fixedEndTime) {
return true;
} else if (dynaStartTime.getTime() >= fixedStartTime.getTime() && dynaStartTime.getTime() < fixedEndTime.getTime()) { return intersection(dynaStartTime, dynaEndTime, fixedStartTime, fixedEndTime);
return true; }
} else {
return false;
// 计算两个时间片的交集
public OccupyInfo intersection(LocalDateTime dynaStartTime, LocalDateTime dynaEndTime,
LocalDateTime fixedStartTime, LocalDateTime fixedEndTime) {
// 如果两个时间片没有交集,返回null
if (!checkTimesHasOverlap(dynaStartTime, dynaEndTime, fixedStartTime, fixedEndTime)) {
return null;
} }
// 否则,返回一个新的时间片,其开始时间是两个时间片中较晚的开始时间,其结束时间是两个时间片中较早的结束时间
return new OccupyInfo(dynaStartTime.isAfter(fixedStartTime) ? dynaStartTime : fixedStartTime,
dynaEndTime.isBefore(fixedEndTime) ? dynaEndTime : fixedEndTime);
} }
/** /**
...@@ -496,18 +508,43 @@ public class DateUtil { ...@@ -496,18 +508,43 @@ public class DateUtil {
* @param dynaEndTime 比较时间段结束时间 * @param dynaEndTime 比较时间段结束时间
* @param fixedStartTime 参考时间段开始时间 * @param fixedStartTime 参考时间段开始时间
* @param fixedEndTime 参考时间段结束时间 * @param fixedEndTime 参考时间段结束时间
* @return * @return 是否有交集
* @apiNote 要计算两个时间段的交集,我们可以使用以下公式:
* max(fixedStartTime, dynaStartTime) < min(fixedEndTime, dynaEndTime)
* 如果这个条件成立,说明两个时间段有交集,否则没有交集。
*/ */
public static boolean checkTimesHasOverlap(LocalDateTime dynaStartTime, LocalDateTime dynaEndTime, public static boolean checkTimesHasOverlap(LocalDateTime dynaStartTime, LocalDateTime dynaEndTime,
LocalDateTime fixedStartTime, LocalDateTime fixedEndTime) { LocalDateTime fixedStartTime, LocalDateTime fixedEndTime) {
if (dynaStartTime.compareTo(fixedStartTime) <= 0 && dynaEndTime.compareTo(fixedStartTime) > 0) { // 取两个开始时间中较大的一个
return true; LocalDateTime maxStartTime = dynaStartTime.compareTo(fixedStartTime) > 0 ? dynaStartTime : fixedStartTime;
} else if (dynaStartTime.compareTo(fixedStartTime) >= 0 && dynaStartTime.compareTo(fixedEndTime) < 0) { // 取两个结束时间中较小的一个
return true; LocalDateTime minEndTime = dynaEndTime.compareTo(fixedEndTime) < 0 ? dynaEndTime : fixedEndTime;
} else {
return false; return maxStartTime.isBefore(minEndTime);
} }
/**
* 判断两个时间范围是否有交集
*
* @param dynaStartTime 比较时间段开始时间
* @param dynaEndTime 比较时间段结束时间
* @param fixedStartTime 参考时间段开始时间
* @param fixedEndTime 参考时间段结束时间
* @return 是否有交集
* @apiNote 要计算两个时间段的交集,我们可以使用以下公式:
* max(fixedStartTime, dynaStartTime) < min(fixedEndTime, dynaEndTime)
* 如果这个条件成立,说明两个时间段有交集,否则没有交集。
*/
public static boolean checkTimesHasOverlap(LocalTime dynaStartTime, LocalTime dynaEndTime,
LocalTime fixedStartTime, LocalTime fixedEndTime) {
// 取两个开始时间中较大的一个
LocalTime maxStartTime = dynaStartTime.compareTo(fixedStartTime) > 0 ? dynaStartTime : fixedStartTime;
// 取两个结束时间中较小的一个
LocalTime minEndTime = dynaEndTime.compareTo(fixedEndTime) < 0 ? dynaEndTime : fixedEndTime;
return maxStartTime.isBefore(minEndTime);
} }
/** /**
......
package com.dituhui.pea.order.scheduler; package com.dituhui.pea.order.scheduler;
import com.dituhui.pea.order.common.CapacityUtils;
import com.dituhui.pea.order.common.DateUtils; import com.dituhui.pea.order.common.DateUtils;
import com.dituhui.pea.order.common.OccupyInfo;
import com.dituhui.pea.order.common.jackson.DateTimeUtil; import com.dituhui.pea.order.common.jackson.DateTimeUtil;
import com.dituhui.pea.order.common.jackson.DateUtil; import com.dituhui.pea.order.common.jackson.DateUtil;
import com.dituhui.pea.order.dao.CapacityEngineerCalendarDao; import com.dituhui.pea.order.dao.CapacityEngineerCalendarDao;
...@@ -17,8 +19,6 @@ import com.dituhui.pea.order.entity.EngineerBusinessEntity; ...@@ -17,8 +19,6 @@ import com.dituhui.pea.order.entity.EngineerBusinessEntity;
import com.dituhui.pea.order.entity.EngineerInfoEntity; import com.dituhui.pea.order.entity.EngineerInfoEntity;
import com.dituhui.pea.order.entity.OrderInfoEntity; import com.dituhui.pea.order.entity.OrderInfoEntity;
import com.dituhui.pea.order.entity.TimeSliceEntity; import com.dituhui.pea.order.entity.TimeSliceEntity;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
...@@ -34,9 +34,8 @@ import java.util.ArrayList; ...@@ -34,9 +34,8 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
...@@ -95,7 +94,14 @@ public class CalcEngineerCapacityScheduler { ...@@ -95,7 +94,14 @@ public class CalcEngineerCapacityScheduler {
// 根据capacity_engineer_calendar和order_info,来确定当天剩下的最大连续时间区块; // 根据capacity_engineer_calendar和order_info,来确定当天剩下的最大连续时间区块;
EngineerBusinessEntity businessEntity = engineerBusinessDao.getByEngineerCode(engineerCode); EngineerBusinessEntity businessEntity = engineerBusinessDao.getByEngineerCode(engineerCode);
// 就算单小时时间段工程师技能已用容量存储至时间切片工程师时间表内 // 就算单小时时间段工程师技能已用容量存储至时间切片工程师时间表内
extracted(date, engineerCode, orders, ss); //添加工程师日历
List<CapacityEngineerCalendarEntity> configs = capacityEngineerCalendarDao.findCalendarByWorkdayAndEngineerCode(date, engineerCode);
//剔除重排和取消单
orders = Optional.ofNullable(orders).orElse(Collections.emptyList())
.stream()
.filter(e -> !ss.contains(e.getOrderStatus()))
.collect(Collectors.toList());
initOneEngineerTimeSlot(date, engineerCode, configs, orders);
int used = orders.stream().map(e -> { int used = orders.stream().map(e -> {
...@@ -105,9 +111,7 @@ public class CalcEngineerCapacityScheduler { ...@@ -105,9 +111,7 @@ public class CalcEngineerCapacityScheduler {
return e.getTakeTime(); return e.getTakeTime();
} }
}).mapToInt(Integer::intValue).sum(); }).mapToInt(Integer::intValue).sum();
long cnt = orders.stream() long cnt = orders.size();
.filter(e -> !ss.contains(e.getOrderStatus()))
.count();
long max = getMaxRemainBlock(date, engineerCode, orders, businessEntity); long max = getMaxRemainBlock(date, engineerCode, orders, businessEntity);
log.info("正在处理: 日期[{}]技术员[{}]容量相关信息 ==> used:{}, orderCnt:{}, maxDuration:{}", date, engineerCode, used, cnt, max); log.info("正在处理: 日期[{}]技术员[{}]容量相关信息 ==> used:{}, orderCnt:{}, maxDuration:{}", date, engineerCode, used, cnt, max);
statEntity.setOrderCount((int) cnt); statEntity.setOrderCount((int) cnt);
...@@ -118,56 +122,36 @@ public class CalcEngineerCapacityScheduler { ...@@ -118,56 +122,36 @@ public class CalcEngineerCapacityScheduler {
capacityEngineerStatDao.save(statEntity); capacityEngineerStatDao.save(statEntity);
} }
private void extracted(String date, String engineerCode, List<OrderInfoEntity> orders, Set<String> ss) { //添加工程师日历参数
private void initOneEngineerTimeSlot(String date, String engineerCode, List<CapacityEngineerCalendarEntity> configs,
List<OrderInfoEntity> orders) {
final LocalDate localDate = LocalDate.parse(date, DateTimeUtil.DATE_FORMAT);
//查询时间片容量 //查询时间片容量
List<CapacityEngineerSliceUsedEntity> engineerTimeSlice = engineerSliceUsedCapacityDao.findByWorkdayAndEngineerCode(date, engineerCode); List<CapacityEngineerSliceUsedEntity> engineerTimeSlice = engineerSliceUsedCapacityDao.findByWorkdayAndEngineerCode(date, engineerCode);
for (CapacityEngineerSliceUsedEntity sliceCap : engineerTimeSlice) { for (CapacityEngineerSliceUsedEntity sliceCap : engineerTimeSlice) {
final TimeSliceEntity timeSlice = sliceCap.getTimmeSlice(); final TimeSliceEntity timeSlice = sliceCap.getTimmeSlice();
LocalTime sliceStartLocalTime = LocalTime.parse(timeSlice.getStart(), DateUtil.TIME_FORMATTER); LocalTime sliceStartLocalTime = LocalTime.parse(timeSlice.getStart(), DateUtil.TIME_FORMATTER);
LocalTime sliceEndLocalTime = LocalTime.parse(timeSlice.getEnd(), DateUtil.TIME_FORMATTER); LocalTime sliceEndLocalTime = LocalTime.parse(timeSlice.getEnd(), DateUtil.TIME_FORMATTER);
if (Objects.isNull(sliceCap.getCapTotal()) || 0 == sliceCap.getCapTotal()) { LocalDateTime startTime = LocalDateTime.of(localDate, sliceStartLocalTime);
log.info("-----------------》工程师{}的时间片总容量为0, 跳过容量计算", engineerCode); LocalDateTime endTime = LocalDateTime.of(localDate, sliceEndLocalTime);
continue;
} List<OccupyInfo> occupyInfo = CapacityUtils.caculate(startTime, endTime, orders, configs);
long totalUseTime = 0; //已用容量
long maxDuration = 0; long totalUseTime = occupyInfo.stream().mapToLong(t -> Duration.between(t.getEndTime(), t.getBeginTime()).toMinutes()).sum();
for (OrderInfoEntity order : orders) { //最大连续时长
LocalTime planStartTime = order.getPlanStartTime().toLocalTime(); Long maxRemainBlock = CapacityUtils.getMaxRemainBlock(startTime, endTime, occupyInfo);
LocalTime planEndTime = order.getPlanEndTime().toLocalTime();
boolean startIn = DateTimeUtil.isIn(planStartTime, sliceStartLocalTime, sliceEndLocalTime);
boolean endAfter = planEndTime.isAfter(sliceEndLocalTime);
//8:00- 8:30 8:00 - 10:30
boolean contains = ss.contains(order.getOrderStatus());
//请假时间仅落在当前时间段内, 当前时间段请假时长为 请假结束时间- 请假开始时间
if (startIn && DateTimeUtil.isIn(planEndTime, sliceStartLocalTime, sliceEndLocalTime)) {
long useTime = DateTimeUtil.betweenTwoTime(planStartTime, planEndTime, TimeUnit.MINUTES);
totalUseTime = contains
? totalUseTime + useTime : totalUseTime - useTime;
} else if (startIn && endAfter) {
//落在当前时间段和下一个时间段
long useTime = DateTimeUtil.betweenTwoTime(planStartTime, sliceEndLocalTime, TimeUnit.MINUTES);
totalUseTime = contains
? totalUseTime + useTime : totalUseTime - useTime;
} else if (planStartTime.isBefore(sliceStartLocalTime) && endAfter) {
long usedTime = DateTimeUtil.betweenTwoTime(sliceStartLocalTime, sliceEndLocalTime, TimeUnit.MINUTES);
totalUseTime = contains
? totalUseTime + usedTime : totalUseTime - usedTime;
}
}
if (totalUseTime < 0) {
totalUseTime = 0;
}
sliceCap.setCapLeft(sliceCap.getCapTotal() - totalUseTime); sliceCap.setCapLeft(sliceCap.getCapTotal() - totalUseTime);
sliceCap.setMaxDuration(sliceCap.getCapTotal() - totalUseTime);
sliceCap.setCapUsed(totalUseTime); sliceCap.setCapUsed(totalUseTime);
sliceCap.setOrderCount((long) orders.size());
sliceCap.setMaxDuration(maxRemainBlock);
sliceCap.setUpdateTime(LocalDateTime.now(ZoneId.of("+8"))); sliceCap.setUpdateTime(LocalDateTime.now(ZoneId.of("+8")));
} }
engineerSliceUsedCapacityDao.saveAll(engineerTimeSlice); engineerSliceUsedCapacityDao.saveAll(engineerTimeSlice);
} }
private long getMaxRemainBlock(String date, String engineerCode, List<OrderInfoEntity> orders, EngineerBusinessEntity businessEntity) { private long getMaxRemainBlock(String date, String engineerCode, List<OrderInfoEntity> orders, EngineerBusinessEntity businessEntity) {
// 根据capacity_engineer_calendar和order_info,来确定当天剩下的最大连续时间区块; // 根据capacity_engineer_calendar和order_info,来确定当天剩下的最大连续时间区块;
LocalDateTime startTime = DateUtils.localDateTimeFromStr(String.format("%s %s:00", date, businessEntity.getWorkOn())); LocalDateTime startTime = DateUtils.localDateTimeFromStr(String.format("%s %s:00", date, businessEntity.getWorkOn()));
...@@ -208,10 +192,4 @@ public class CalcEngineerCapacityScheduler { ...@@ -208,10 +192,4 @@ public class CalcEngineerCapacityScheduler {
} }
} }
@Data
@Accessors(chain = true)
private static class OccupyInfo {
private LocalDateTime beginTime;
private LocalDateTime endTime;
}
} }
...@@ -154,7 +154,7 @@ public class CapacityQueryServiceImpl implements CapacityQueryService { ...@@ -154,7 +154,7 @@ public class CapacityQueryServiceImpl implements CapacityQueryService {
final int corePoolSize = Runtime.getRuntime().availableProcessors(); final int corePoolSize = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(corePoolSize, corePoolSize, 1, TimeUnit.SECONDS, ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(corePoolSize, corePoolSize, 1, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(16)); new ArrayBlockingQueue<>(30));
Semaphore semaphore = new Semaphore(corePoolSize); Semaphore semaphore = new Semaphore(corePoolSize);
List<Future<CapacityQueryDTO.Data>> futureDatas = new ArrayList<>(); List<Future<CapacityQueryDTO.Data>> futureDatas = new ArrayList<>();
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!