Commit e5ccf54d by 刘鑫

feat(单时间片容量): 单时间片容量查询

1 parent 5a1074da
......@@ -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.SkillInfoDao;
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.MapBlockInfoEntity;
import com.dituhui.pea.order.entity.OrderInfoEntity;
import com.dituhui.pea.order.entity.SkillInfoEntity;
import com.dituhui.pea.order.entity.TimeSliceEntity;
import lombok.extern.slf4j.Slf4j;
......@@ -13,17 +15,23 @@ import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
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.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalLong;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Component
@Slf4j
......@@ -70,6 +78,48 @@ public class CapacityUtils {
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,
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;
import com.dituhui.pea.order.common.DateSplit;
import com.dituhui.pea.order.common.OccupyInfo;
import lombok.experimental.UtilityClass;
import org.springframework.util.Assert;
......@@ -9,6 +10,7 @@ import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
......@@ -470,6 +472,7 @@ public class DateUtil {
return Duration.between(startDate.toInstant(), endDate.toInstant());
}
/**
* 判断两个时间范围是否有交集
*
......@@ -477,16 +480,25 @@ public class DateUtil {
* @param dynaEndTime 比较时间段结束时间
* @param fixedStartTime 参考时间段开始时间
* @param fixedEndTime 参考时间段结束时间
* @return
* @return 求交集时间片时 返回交集时间片, 没有对应情况则返回null
*/
public static boolean checkTimesHasOverlap(Date dynaStartTime, Date dynaEndTime, Date fixedStartTime, Date fixedEndTime) {
if (dynaStartTime.getTime() <= fixedStartTime.getTime() && dynaEndTime.getTime() > fixedStartTime.getTime()) {
return true;
} else if (dynaStartTime.getTime() >= fixedStartTime.getTime() && dynaStartTime.getTime() < fixedEndTime.getTime()) {
return true;
} else {
return false;
public static OccupyInfo timesOverlap(LocalDateTime dynaStartTime, LocalDateTime dynaEndTime,
LocalDateTime fixedStartTime, LocalDateTime fixedEndTime) {
return intersection(dynaStartTime, dynaEndTime, fixedStartTime, fixedEndTime);
}
// 计算两个时间片的交集
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,17 +508,42 @@ public class DateUtil {
* @param dynaEndTime 比较时间段结束时间
* @param fixedStartTime 参考时间段开始时间
* @param fixedEndTime 参考时间段结束时间
* @return
* @return 是否有交集
* @apiNote 要计算两个时间段的交集,我们可以使用以下公式:
* max(fixedStartTime, dynaStartTime) < min(fixedEndTime, dynaEndTime)
* 如果这个条件成立,说明两个时间段有交集,否则没有交集。
*/
public static boolean checkTimesHasOverlap(LocalDateTime dynaStartTime, LocalDateTime dynaEndTime,
LocalDateTime fixedStartTime, LocalDateTime fixedEndTime) {
if (dynaStartTime.compareTo(fixedStartTime) <= 0 && dynaEndTime.compareTo(fixedStartTime) > 0) {
return true;
} else if (dynaStartTime.compareTo(fixedStartTime) >= 0 && dynaStartTime.compareTo(fixedEndTime) < 0) {
return true;
} else {
return false;
}
// 取两个开始时间中较大的一个
LocalDateTime maxStartTime = dynaStartTime.compareTo(fixedStartTime) > 0 ? dynaStartTime : fixedStartTime;
// 取两个结束时间中较小的一个
LocalDateTime minEndTime = dynaEndTime.compareTo(fixedEndTime) < 0 ? dynaEndTime : fixedEndTime;
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;
import com.dituhui.pea.order.common.CapacityUtils;
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.DateUtil;
import com.dituhui.pea.order.dao.CapacityEngineerCalendarDao;
......@@ -17,8 +19,6 @@ 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 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;
......@@ -34,9 +34,8 @@ 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.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Slf4j
......@@ -95,7 +94,14 @@ public class CalcEngineerCapacityScheduler {
// 根据capacity_engineer_calendar和order_info,来确定当天剩下的最大连续时间区块;
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 -> {
......@@ -105,9 +111,7 @@ public class CalcEngineerCapacityScheduler {
return e.getTakeTime();
}
}).mapToInt(Integer::intValue).sum();
long cnt = orders.stream()
.filter(e -> !ss.contains(e.getOrderStatus()))
.count();
long cnt = orders.size();
long max = getMaxRemainBlock(date, engineerCode, orders, businessEntity);
log.info("正在处理: 日期[{}]技术员[{}]容量相关信息 ==> used:{}, orderCnt:{}, maxDuration:{}", date, engineerCode, used, cnt, max);
statEntity.setOrderCount((int) cnt);
......@@ -118,56 +122,36 @@ public class CalcEngineerCapacityScheduler {
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);
for (CapacityEngineerSliceUsedEntity sliceCap : engineerTimeSlice) {
final TimeSliceEntity timeSlice = sliceCap.getTimmeSlice();
LocalTime sliceStartLocalTime = LocalTime.parse(timeSlice.getStart(), DateUtil.TIME_FORMATTER);
LocalTime sliceEndLocalTime = LocalTime.parse(timeSlice.getEnd(), DateUtil.TIME_FORMATTER);
if (Objects.isNull(sliceCap.getCapTotal()) || 0 == sliceCap.getCapTotal()) {
log.info("-----------------》工程师{}的时间片总容量为0, 跳过容量计算", engineerCode);
continue;
}
long totalUseTime = 0;
long maxDuration = 0;
for (OrderInfoEntity order : orders) {
LocalTime planStartTime = order.getPlanStartTime().toLocalTime();
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;
}
LocalDateTime startTime = LocalDateTime.of(localDate, sliceStartLocalTime);
LocalDateTime endTime = LocalDateTime.of(localDate, sliceEndLocalTime);
List<OccupyInfo> occupyInfo = CapacityUtils.caculate(startTime, endTime, orders, configs);
//已用容量
long totalUseTime = occupyInfo.stream().mapToLong(t -> Duration.between(t.getEndTime(), t.getBeginTime()).toMinutes()).sum();
//最大连续时长
Long maxRemainBlock = CapacityUtils.getMaxRemainBlock(startTime, endTime, occupyInfo);
}
if (totalUseTime < 0) {
totalUseTime = 0;
}
sliceCap.setCapLeft(sliceCap.getCapTotal() - totalUseTime);
sliceCap.setMaxDuration(sliceCap.getCapTotal() - totalUseTime);
sliceCap.setCapUsed(totalUseTime);
sliceCap.setOrderCount((long) orders.size());
sliceCap.setMaxDuration(maxRemainBlock);
sliceCap.setUpdateTime(LocalDateTime.now(ZoneId.of("+8")));
}
engineerSliceUsedCapacityDao.saveAll(engineerTimeSlice);
}
private long getMaxRemainBlock(String date, String engineerCode, List<OrderInfoEntity> orders, EngineerBusinessEntity businessEntity) {
// 根据capacity_engineer_calendar和order_info,来确定当天剩下的最大连续时间区块;
LocalDateTime startTime = DateUtils.localDateTimeFromStr(String.format("%s %s:00", date, businessEntity.getWorkOn()));
......@@ -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 {
final int corePoolSize = Runtime.getRuntime().availableProcessors();
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(corePoolSize, corePoolSize, 1, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(16));
new ArrayBlockingQueue<>(30));
Semaphore semaphore = new Semaphore(corePoolSize);
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!