Commit d4857185 by 丁伟峰

Merge branch 'dev-dingwf' into develop

2 parents 17fbcf27 38454d25
...@@ -19,12 +19,13 @@ package com.dituhui.pea.order; ...@@ -19,12 +19,13 @@ package com.dituhui.pea.order;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan; import org.springframework.scheduling.annotation.EnableScheduling;
/** /**
* @author TrevorLink * @author TrevorLink
*/ */
@SpringBootApplication @SpringBootApplication
@EnableScheduling
@EnableFeignClients(basePackages = {"com.dituhui.pea.user", "com.dituhui.pea.order"}) @EnableFeignClients(basePackages = {"com.dituhui.pea.user", "com.dituhui.pea.order"})
public class OrderServiceApplication { public class OrderServiceApplication {
......
...@@ -22,4 +22,6 @@ public interface CapacityEngineerCalendarDao extends JpaRepository<CapacityEngin ...@@ -22,4 +22,6 @@ public interface CapacityEngineerCalendarDao extends JpaRepository<CapacityEngin
@Modifying @Modifying
@Query("delete from CapacityEngineerCalendarEntity a where a.engineerCode in :engineerCodes and a.type = :type and a.workday between :startDate and :endDate") @Query("delete from CapacityEngineerCalendarEntity a where a.engineerCode in :engineerCodes and a.type = :type and a.workday between :startDate and :endDate")
void deleteByTypeAndEngineerCodesAndBetweenDates(List<String> engineerCodes, String type, String startDate, String endDate); void deleteByTypeAndEngineerCodesAndBetweenDates(List<String> engineerCodes, String type, String startDate, String endDate);
List<CapacityEngineerCalendarEntity> findCalendarByWorkdayAndEngineerCode(String date, String engineerCode);
} }
...@@ -17,4 +17,6 @@ public interface CapacityOrgStatDao extends JpaRepository<CapacityOrgStatEntity, ...@@ -17,4 +17,6 @@ public interface CapacityOrgStatDao extends JpaRepository<CapacityOrgStatEntity,
@Query("select c from CapacityOrgStatEntity c where c.type = 'branch' and c.orgId in :branchIds and c.workday between :beginDate and :endDate") @Query("select c from CapacityOrgStatEntity c where c.type = 'branch' and c.orgId in :branchIds and c.workday between :beginDate and :endDate")
Page<?> findByBranchIdsAndWorkdayBetween(List<String> branchIds, String beginDate, String endDate, Pageable page); Page<?> findByBranchIdsAndWorkdayBetween(List<String> branchIds, String beginDate, String endDate, Pageable page);
CapacityOrgStatEntity getByTypeAndWorkdayAndOrgIdAndLayer(String group, String date, String groupId, String layer);
} }
package com.dituhui.pea.order.dao;
import com.dituhui.pea.order.entity.CapacityTeamStatEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface CapacityStatisticDao extends JpaRepository<CapacityTeamStatEntity, Long> {
@Query(value = "SELECT a.workday, " +
" v.layer_id, " +
" v.layer, " +
" count(distinct a.engineer_code) engineer_count, " +
" ifnull(sum(a.cap_total),0) cap_total, " +
" ifnull(sum(cap_used),0)+ifnull(sum(cap_used_travel),0) as cap_used, " +
" ifnull(sum(a.cap_left),0) cap_left" +
" FROM capacity_engineer_stat a\n" +
" INNER JOIN org_team_engineer b ON a.engineer_code=b.engineer_code AND b.status=1" +
" INNER JOIN v_engineer_skill v ON a.engineer_code = v.engineer_code" +
" WHERE b.team_id= :teamId " +
" AND a.workday= :date" +
" GROUP BY a.workday, v.layer_id, v.layer", nativeQuery = true)
List<Object[]> sumTeamCapacity(String date, String teamId);
@Query(value = "SELECT a.workday, " +
" v.layer_id, " +
" v.layer, " +
" count(distinct a.engineer_code) engineer_count, " +
" sum(a.cap_total) cap_total, " +
" ifnull(sum(cap_used),0)+ifnull(sum(cap_used_travel),0) as cap_used, " +
" sum(a.cap_left) cap_left\n" +
" FROM capacity_engineer_stat a\n" +
" JOIN v_engineer_skill v ON a.engineer_code = v.engineer_code\n" +
" JOIN engineer_info z ON a.engineer_code = z.engineer_code\n" +
" WHERE z.group_id= :groupId AND a.workday= :date \n" +
" GROUP BY a.workday, v.layer_id, v.layer;", nativeQuery = true)
List<Object[]> sumGroupCapacity(String date, String groupId);
@Query(value = "SELECT a.workday, " +
" v.layer_id, " +
" v.layer, " +
" count(distinct a.engineer_code) engineer_count, " +
" sum(a.cap_total) cap_total, " +
" ifnull(sum(cap_used),0)+ifnull(sum(cap_used_travel),0) as cap_used, " +
" sum(a.cap_left) cap_left\n" +
" FROM capacity_engineer_stat a\n" +
" JOIN v_engineer_skill v ON a.engineer_code = v.engineer_code\n" +
" JOIN engineer_info z ON a.engineer_code = z.engineer_code\n" +
" JOIN org_group g ON z.group_id = g.group_id\n" +
" WHERE g.branch_id= :branchId AND a.workday= :date \n" +
" GROUP BY a.workday, v.layer_id, v.layer;", nativeQuery = true)
List<Object[]> sumBranchCapacity(String date, String branchId);
}
...@@ -12,7 +12,7 @@ import java.util.List; ...@@ -12,7 +12,7 @@ import java.util.List;
@Repository @Repository
public interface CapacityTeamStatDao extends JpaRepository<CapacityTeamStatEntity, Long> { public interface CapacityTeamStatDao extends JpaRepository<CapacityTeamStatEntity, Long> {
List<CapacityTeamStatEntity> findAllByTeamIdAndLayerAndWorkdayBetween(String teamId, String layer, String beginDate, String endDate); CapacityTeamStatEntity getByWorkdayAndTeamIdAndLayer(String date, String teamId, String layer);
@Query("select c from CapacityTeamStatEntity c where c.teamId IN :teamIds and c.workday between :beginDate and :endDate") @Query("select c from CapacityTeamStatEntity c where c.teamId IN :teamIds and c.workday between :beginDate and :endDate")
Page<?> findByTeamIdsAndWorkdayBetween(List<String> teamIds, String beginDate, String endDate, Pageable pageable); Page<?> findByTeamIdsAndWorkdayBetween(List<String> teamIds, String beginDate, String endDate, Pageable pageable);
...@@ -24,6 +24,4 @@ public interface CapacityTeamStatDao extends JpaRepository<CapacityTeamStatEntit ...@@ -24,6 +24,4 @@ public interface CapacityTeamStatDao extends JpaRepository<CapacityTeamStatEntit
@Query(value = "SELECT team_id FROM capacity_team_stat ORDER BY RAND() LIMIT 1", nativeQuery = true) @Query(value = "SELECT team_id FROM capacity_team_stat ORDER BY RAND() LIMIT 1", nativeQuery = true)
String getRandomTeamId(); String getRandomTeamId();
// @Query("select TeamCapacityStageDTO(sum(a.capTotal), sum(a.capUsed), sum(a.capLeft), count(1)) from CapacityEngineerStatEntity a join OrgTeamEngineerEntity t on a.engineerCode = t.engineerCode where t.teamId = :teamId")
// TeamCapacityStageDTO getTeamCapacityStage(String teamId);
} }
package com.dituhui.pea.order.dto;
import lombok.Data;
@Data
public class CapacityOrgStatDTO {
private String date;
private String orgId;
private String layerId;
private String layer;
private Integer engineerCount;
private Integer capTotal;
private Integer capUsed;
private Integer capLeft;
}
...@@ -22,15 +22,13 @@ public class CapacityTeamStatEntity implements Serializable { ...@@ -22,15 +22,13 @@ public class CapacityTeamStatEntity implements Serializable {
private String workday; private String workday;
private String timeSpan; // private String timeSpan;
//
// private String timeSpanDetail;
private String timeSpanDetail;
private String teamId; private String teamId;
private String layer; private String layer;
......
package com.dituhui.pea.order.scheduler;
import com.dituhui.pea.order.common.DateUtils;
import com.dituhui.pea.order.dao.*;
import com.dituhui.pea.order.entity.*;
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.util.*;
import java.util.stream.Collectors;
@Slf4j
@Component
public class CalcEngineerCapacityScheduler {
@Value("${scheduler.calc-engineer-capacity.day-offset-begin}")
private int dayOffsetBegin;
@Value("${scheduler.calc-engineer-capacity.day-offset-end}")
private int dayOffsetEnd;
@Autowired
private CapacityEngineerStatDao capacityEngineerStatDao;
@Autowired
private OrderInfoDao orderInfoDao;
@Autowired
private CapacityEngineerCalendarDao capacityEngineerCalendarDao;
@Autowired
private EngineerBusinessDao engineerBusinessDao;
@Autowired
private EngineerInfoDao engineerInfoDao;
@Scheduled(cron = "${scheduler.calc-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));
calcAllEngineerByDays(bdate, edate);
}
private void calcAllEngineerByDays(String bdate, String 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) {
calcOneEngineer(DateUtils.formatDate(currentDate), engineerCode);
}
currentDate = currentDate.plusDays(1);
}
}
private void calcOneEngineer(String date, String engineerCode) {
Set<String> ss = Set.of("CANCELED", "RESCHEDULED");
List<OrderInfoEntity> orders = orderInfoDao.findByDtAndEngineerCode(DateUtils.localDateFromStr(date), engineerCode);
int used = orders.stream().map(e -> {
if (ss.contains(e.getOrderStatus())) {
return 0;
} else {
return e.getTakeTime();
}
}).mapToInt(Integer::intValue).sum();
long cnt = orders.stream()
.filter(e -> !ss.contains(e.getOrderStatus()))
.count();
long max = getMaxRemainBlock(date, engineerCode, orders);
log.info("正在处理: 日期[{}]技术员[{}]容量相关信息 ==> used:{}, orderCnt:{}, maxDuration:{}", date, engineerCode, used, cnt, max);
CapacityEngineerStatEntity statEntity = capacityEngineerStatDao.getByWorkdayAndEngineerCode(date, engineerCode);
if (statEntity == null) {
log.error("错误:日期[{}]技术员[{}]容量尚未初始化,忽略退出!", date, engineerCode);
return;
}
statEntity.setOrderCount((int) cnt);
statEntity.setCapUsed(used);
statEntity.setCapLeft(statEntity.getCapTotal() - used);
statEntity.setMaxDuration((int) max);
statEntity.setUpdateTime(LocalDateTime.now());
capacityEngineerStatDao.save(statEntity);
}
private long getMaxRemainBlock(String date, String engineerCode, List<OrderInfoEntity> orders) {
// 根据capacity_engineer_calendar和order_info,来确定当天剩下的最大连续时间区块;
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()));
List<OccupyInfo> occupyInfos = new ArrayList<>();
List<CapacityEngineerCalendarEntity> configs = capacityEngineerCalendarDao.findCalendarByWorkdayAndEngineerCode(date, engineerCode);
if (!configs.isEmpty()) {
occupyInfos.addAll(
configs.stream().map(e -> new OccupyInfo().setBeginTime(e.getStartTime()).setEndTime(e.getEndTime())).collect(Collectors.toList())
);
}
if (!orders.isEmpty()) {
occupyInfos.addAll(
orders.stream().map(e -> new OccupyInfo().setBeginTime(e.getPlanStartTime()).setEndTime(e.getPlanEndTime())).collect(Collectors.toList())
);
}
if (occupyInfos.isEmpty()) {
return Duration.between(startTime, endTime).toMinutes();
} else {
occupyInfos.sort(Comparator.comparing(OccupyInfo::getBeginTime));
// 从 occupyInfos的配置间隙中,获取最大的闲时段,理论上,上面的配置段之间,是不会交叉的,如果交叉,那是存在问题的!
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 Collections.max(idlePeriods);
}
}
@Data
@Accessors(chain = true)
private static class OccupyInfo {
private LocalDateTime beginTime;
private LocalDateTime endTime;
}
}
package com.dituhui.pea.order.scheduler;
import com.dituhui.pea.order.common.DateUtils;
import com.dituhui.pea.order.dao.*;
import com.dituhui.pea.order.dto.CapacityOrgStatDTO;
import com.dituhui.pea.order.entity.*;
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.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
@Slf4j
@Component
public class CalcOrgCapacityScheduler {
@Autowired
private CapacityStatisticDao capacityStatisticDao;
@Autowired
private OrgBranchDao orgBranchDao;
@Autowired
private OrgGroupDao orgGroupDao;
@Autowired
private OrgTeamDao orgTeamDao;
@Autowired
private CapacityOrgStatDao capacityOrgStatDao;
@Autowired
private CapacityTeamStatDao capacityTeamStatDao;
@Value("${scheduler.calc-org-capacity.day-offset-begin}")
private int dayOffsetBegin;
@Value("${scheduler.calc-org-capacity.day-offset-end}")
private int dayOffsetEnd;
private final ExecutorService executorService = Executors.newFixedThreadPool(3);
@Scheduled(cron = "${scheduler.calc-org-capacity.cron-expr}")
public void run() {
log.info("工作队/各级机构容量定时计算,根据相关的工程师的容量进行汇总");
String bdate = DateUtils.formatDate(LocalDate.now().plusDays(dayOffsetBegin));
String edate = DateUtils.formatDate(LocalDate.now().plusDays(dayOffsetEnd));
executorService.submit(() -> calcAllTeamByDays(bdate, edate));
executorService.submit(() -> calcAllGroupByDays(bdate, edate));
executorService.submit(() -> calcAllBranchByDays(bdate, edate));
}
private void calcOneTeam(String date, String teamId) {
// 重新计算保存1个工作队1天的容量
List<Object[]> nativeQueryResult = capacityStatisticDao.sumTeamCapacity(date, teamId);
List<CapacityOrgStatDTO> teamStats = new ArrayList<>();
for (Object[] result : nativeQueryResult) {
CapacityOrgStatDTO dto = new CapacityOrgStatDTO();
dto.setDate(date);
dto.setOrgId(teamId);
dto.setLayerId((String) result[1]);
dto.setLayer((String) result[2]);
dto.setEngineerCount((Integer) result[3]);
dto.setCapTotal((Integer) result[4]);
dto.setCapUsed((Integer) result[5]);
dto.setCapLeft((Integer) result[6]);
teamStats.add(dto);
}
// 逐条保存
for (CapacityOrgStatDTO dto : teamStats) {
CapacityTeamStatEntity entity = capacityTeamStatDao.getByWorkdayAndTeamIdAndLayer(date, teamId, dto.getLayer());
if (entity == null) {
entity = new CapacityTeamStatEntity();
entity.setWorkday(date);
entity.setTeamId(teamId);
entity.setLayer(dto.getLayer());
entity.setCapUsedTravel(0);
entity.setOrderCount(0);
entity.setCreateTime(LocalDateTime.now());
}
entity.setEngineerCount(dto.getEngineerCount());
entity.setCapTotal(dto.getCapTotal());
entity.setCapUsed(dto.getCapUsed());
entity.setCapLeft(dto.getCapLeft());
entity.setUpdateTime(LocalDateTime.now());
// todo entity.setOrderCount();
capacityTeamStatDao.save(entity);
}
}
private void calcOneGroup(String date, String groupId) {
// 重新计算1个分组/分站 1天的容量
List<Object[]> nativeQueryResult = capacityStatisticDao.sumGroupCapacity(date, groupId);
List<CapacityOrgStatDTO> stats = new ArrayList<>();
for (Object[] result : nativeQueryResult) {
CapacityOrgStatDTO dto = new CapacityOrgStatDTO();
dto.setDate(date);
dto.setOrgId(groupId);
dto.setLayerId((String) result[1]);
dto.setLayer((String) result[2]);
dto.setEngineerCount((Integer) result[3]);
dto.setCapTotal((Integer) result[4]);
dto.setCapUsed((Integer) result[5]);
dto.setCapLeft((Integer) result[6]);
stats.add(dto);
}
// 逐条保存
for (CapacityOrgStatDTO dto : stats) {
CapacityOrgStatEntity entity = capacityOrgStatDao.getByTypeAndWorkdayAndOrgIdAndLayer("group", date, groupId, dto.getLayer());
if (entity == null) {
entity = new CapacityOrgStatEntity();
entity.setType("group");
entity.setWorkday(date);
entity.setOrgId(groupId);
entity.setLayer(dto.getLayer());
entity.setOrderCount(0);
entity.setCreateTime(LocalDateTime.now());
}
entity.setEngineerCount(dto.getEngineerCount());
entity.setCapTotal(dto.getCapTotal());
entity.setCapUsedTotal(dto.getCapUsed());
entity.setCapLeft(dto.getCapLeft());
entity.setUpdateTime(LocalDateTime.now());
// todo entity.setOrderCount();
capacityOrgStatDao.save(entity);
}
}
private void calcOneBranch(String date, String branchId) {
//
List<Object[]> nativeQueryResult = capacityStatisticDao.sumBranchCapacity(date, branchId);
List<CapacityOrgStatDTO> stats = new ArrayList<>();
for (Object[] result : nativeQueryResult) {
CapacityOrgStatDTO dto = new CapacityOrgStatDTO();
dto.setDate(date);
dto.setOrgId(branchId);
dto.setLayerId((String) result[1]);
dto.setLayer((String) result[2]);
dto.setEngineerCount((Integer) result[3]);
dto.setCapTotal((Integer) result[4]);
dto.setCapUsed((Integer) result[5]);
dto.setCapLeft((Integer) result[6]);
stats.add(dto);
}
// 逐条保存
for (CapacityOrgStatDTO dto : stats) {
CapacityOrgStatEntity entity = capacityOrgStatDao.getByTypeAndWorkdayAndOrgIdAndLayer("branch", date, branchId, dto.getLayer());
if (entity == null) {
entity = new CapacityOrgStatEntity();
entity.setType("branch");
entity.setWorkday(date);
entity.setOrgId(branchId);
entity.setLayer(dto.getLayer());
entity.setOrderCount(0);
entity.setCreateTime(LocalDateTime.now());
}
entity.setEngineerCount(dto.getEngineerCount());
entity.setCapTotal(dto.getCapTotal());
entity.setCapUsedTotal(dto.getCapUsed());
entity.setCapLeft(dto.getCapLeft());
entity.setUpdateTime(LocalDateTime.now());
// todo entity.setOrderCount();
capacityOrgStatDao.save(entity);
}
}
private void calcAllTeamByDays(String bdate, String edate) {
LocalDate currentDate = DateUtils.localDateFromStr(bdate);
LocalDate endDate = DateUtils.localDateFromStr(edate);
List<String> allTeamIds = orgTeamDao.findAll().stream().map(OrgTeamEntity::getTeamId).collect(Collectors.toList());
while (!currentDate.isAfter(endDate)) {
for (String teamId : allTeamIds) {
calcOneTeam(DateUtils.formatDate(currentDate), teamId);
}
currentDate = currentDate.plusDays(1);
}
}
private void calcAllGroupByDays(String bdate, String edate) {
LocalDate currentDate = DateUtils.localDateFromStr(bdate);
LocalDate endDate = DateUtils.localDateFromStr(edate);
List<String> allGroupIds = orgGroupDao.findAll().stream().map(OrgGroupEntity::getGroupId).collect(Collectors.toList());
while (!currentDate.isAfter(endDate)) {
for (String groupId : allGroupIds) {
calcOneGroup(DateUtils.formatDate(currentDate), groupId);
}
currentDate = currentDate.plusDays(1);
}
}
private void calcAllBranchByDays(String bdate, String edate) {
LocalDate currentDate = DateUtils.localDateFromStr(bdate);
LocalDate endDate = DateUtils.localDateFromStr(edate);
List<String> allBranchIds = orgBranchDao.findAll().stream().map(OrgBranchEntity::getBranchId).collect(Collectors.toList());
while (!currentDate.isAfter(endDate)) {
for (String branchId : allBranchIds) {
calcOneBranch(DateUtils.formatDate(currentDate), branchId);
}
currentDate = currentDate.plusDays(1);
}
}
}
package com.dituhui.pea.order.scheduler;
import com.dituhui.pea.order.common.DateUtils;
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.entity.CapacityEngineerCalendarEntity;
import com.dituhui.pea.order.entity.CapacityEngineerStatEntity;
import com.dituhui.pea.order.entity.EngineerBusinessEntity;
import com.dituhui.pea.order.entity.EngineerInfoEntity;
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.util.Comparator;
import java.util.List;
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;
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<CapacityEngineerCalendarEntity> configs) {
return configs.stream().mapToLong(e -> Duration.between(e.getStartTime(), e.getEndTime()).toMinutes()).sum();
}
private CapacityStats calculateWorkTime(String date, String engineerCode, List<CapacityEngineerCalendarEntity> 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 initOneEngineer(String date, String engineerCode) {
log.info("正在处理日期[{}] 技术员[{}]", date, engineerCode);
// 初始化一个工程师、一天的容量
CapacityEngineerStatEntity statEntity = capacityEngineerStatDao.getByWorkdayAndEngineerCode(date, engineerCode);
if (statEntity != null && !rewriteForce) {
log.error("技术员容量信息记录已存在, 直接返回");
return;
}
List<CapacityEngineerCalendarEntity> configs = capacityEngineerCalendarDao.findCalendarByWorkdayAndEngineerCode(date, engineerCode)
.stream().sorted(Comparator.comparing(CapacityEngineerCalendarEntity::getStartTime)).collect(Collectors.toList());
if (!configs.isEmpty() && !verifyCalendar(configs)) {
log.error("配置检查失败,忽略退出");
return;
}
String memo = configs.stream().map(CapacityEngineerCalendarEntity::getType).collect(Collectors.joining("/"));
log.info("日期[{}] 技术员[{}] 有日历记录需要特别处理 === {}", date, engineerCode, memo);
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.setMemo(memo);
statEntity.setUpdateTime(LocalDateTime.now());
capacityEngineerStatDao.save(statEntity);
log.info("====== 处理完毕 ======");
}
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;
}
}
...@@ -175,7 +175,7 @@ public class OrderCreateServiceImpl implements OrderCreateService { ...@@ -175,7 +175,7 @@ public class OrderCreateServiceImpl implements OrderCreateService {
if (teamIds == null || teamIds.isEmpty()) { if (teamIds == null || teamIds.isEmpty()) {
return Result.failed("没有找到匹配的工作队"); return Result.failed("没有找到匹配的工作队");
} }
// 选择一个工作队(理论上,只有1个合适的工作队),然后遍历下面的各个技术员,从符合技能的技术员中,汇总相关的容量 // 选择一个工作队(理论上,只有1个合适的工作队),然后遍历下面的符合技能的技术员
// 遍历工作队,每个工作队 // 遍历工作队,每个工作队
String teamId = teamIds.get(0); String teamId = teamIds.get(0);
OrgTeamEntity teamEntity = orgTeamDao.getByTeamId(teamId); OrgTeamEntity teamEntity = orgTeamDao.getByTeamId(teamId);
...@@ -199,19 +199,18 @@ public class OrderCreateServiceImpl implements OrderCreateService { ...@@ -199,19 +199,18 @@ public class OrderCreateServiceImpl implements OrderCreateService {
return Result.success(null); return Result.success(null);
} }
private void tryVirtualAppointment(OrderInfoEntity entity, String source, String teamId) { private void tryVirtualAppointment(OrderInfoEntity thisOrderEntity, String source, String teamId) {
// todo 等王力那边修改后,这边再进行整合,需要返回当前工单、后面的一个工单的数据情况,这边再进行修改
try { try {
List<String> engineerCodes = engineerUtils.getEngineersByLevel("team", teamId).stream() List<String> engineerCodes = engineerUtils.getEngineersByLevel("team", teamId).stream()
.map(EngineerInfoEntity::getEngineerCode).collect(Collectors.toList()); .map(EngineerInfoEntity::getEngineerCode).collect(Collectors.toList());
List<String> matchEngineerCodes = engineerSkillDao.findAllByBrandAndTypeAndSkillAndEngineerCodeIn(entity.getBrand(), entity.getType(), entity.getSkill(), engineerCodes).stream() List<String> matchEngineerCodes = engineerSkillDao.findAllByBrandAndTypeAndSkillAndEngineerCodeIn(thisOrderEntity.getBrand(), thisOrderEntity.getType(), thisOrderEntity.getSkill(), engineerCodes).stream()
.map(EngineerSkillEntity::getEngineerCode).collect(Collectors.toList()); .map(EngineerSkillEntity::getEngineerCode).collect(Collectors.toList());
String assignEngineerCode = null; String assignEngineerCode = null;
OrderAssignCheck.Result checkResult = null; OrderAssignCheck.Result checkResult = null;
log.info("=== 准备调用指派,候选的技术员列表: {}", matchEngineerCodes); log.info("=== 准备调用指派,候选的技术员列表: {}", matchEngineerCodes);
for (String engineerCode : matchEngineerCodes) { for (String engineerCode : matchEngineerCodes) {
checkResult = orderAssignCheck.orderAssignCheck(entity.getOrderId(), entity.getDt(), engineerCode); checkResult = orderAssignCheck.orderAssignCheck(thisOrderEntity.getOrderId(), thisOrderEntity.getDt(), engineerCode);
log.info("orderAssignCheck ===> orderId[{}]engineerCode[{}] ==> result[{}]", entity.getOrderId(), engineerCode, checkResult); log.info("orderAssignCheck ===> orderId[{}]engineerCode[{}] ==> result[{}]", thisOrderEntity.getOrderId(), engineerCode, checkResult);
if (checkResult.getIndex() < 0) { if (checkResult.getIndex() < 0) {
assignEngineerCode = engineerCode; assignEngineerCode = engineerCode;
break; break;
...@@ -223,22 +222,33 @@ public class OrderCreateServiceImpl implements OrderCreateService { ...@@ -223,22 +222,33 @@ public class OrderCreateServiceImpl implements OrderCreateService {
// 修改当前工单 // 修改当前工单
OrderAssignCheck.OrderNode insertNode = checkResult.getCurOrderNode(); OrderAssignCheck.OrderNode insertNode = checkResult.getCurOrderNode();
entity.setEngineerCode(assignEngineerCode); thisOrderEntity.setEngineerCode(assignEngineerCode);
EngineerInfoEntity engineerInfo = engineerInfoDao.getByEngineerCode(assignEngineerCode); EngineerInfoEntity engineerInfo = engineerInfoDao.getByEngineerCode(assignEngineerCode);
entity.setEngineerName(engineerInfo.getName()); thisOrderEntity.setEngineerName(engineerInfo.getName());
entity.setEngineerPhone(engineerInfo.getPhone()); thisOrderEntity.setEngineerPhone(engineerInfo.getPhone());
entity.setAppointmentStatus("PRE"); thisOrderEntity.setAppointmentStatus("PRE");
entity.setDispatcher("AUTO_NOW"); thisOrderEntity.setDispatcher("AUTO_NOW");
entity.setPlanStartTime(insertNode.getPlanStartTime()); thisOrderEntity.setPlanStartTime(insertNode.getPlanStartTime());
entity.setPlanEndTime(insertNode.getPlanEndTime()); thisOrderEntity.setPlanEndTime(insertNode.getPlanEndTime());
entity.setArriveDistance(checkResult.getAdditionDistance()); thisOrderEntity.setArriveDistance(checkResult.getAdditionDistance());
entity.setArriveElapsed(checkResult.getAdditionElapsed()); thisOrderEntity.setArriveElapsed(checkResult.getAdditionElapsed());
orderInfoDao.save(entity); orderInfoDao.save(thisOrderEntity);
// 如果影响到原有工单,修改原有工单
if (checkResult.getPostOrderNode() != null) {
// 如果后面还有一个工单,可能会影响下一个工单的在途时间和里程,修改原有工单
OrderAssignCheck.OrderNode postNode = checkResult.getPostOrderNode();
OrderInfoEntity postOrderEntity = orderInfoDao.getByOrderId(checkResult.getPostOrderId());
if (postOrderEntity == null) {
log.error("受影响的原有工单信息不存在!orderId[{}]", checkResult.getPostOrderId());
// todo 可能需要抛出异常
} else {
postOrderEntity.setArriveDistance(postNode.getArriveDistance());
postOrderEntity.setArriveElapsed(postNode.getArriveElapsed());
orderInfoDao.save(postOrderEntity);
}
}
// 登记 // 登记
commonService.addOrderEvent(entity.getOrderId(), entity.getSubId(), source, "API", "虚拟指派", "虚拟指派", ""); commonService.addOrderEvent(thisOrderEntity.getOrderId(), thisOrderEntity.getSubId(), source, "API", "虚拟指派", "虚拟指派", "");
} }
log.info("==== 已经完成虚拟指派 ===="); log.info("==== 已经完成虚拟指派 ====");
} catch (Exception e) { } catch (Exception e) {
...@@ -250,8 +260,8 @@ public class OrderCreateServiceImpl implements OrderCreateService { ...@@ -250,8 +260,8 @@ public class OrderCreateServiceImpl implements OrderCreateService {
return String.format("%s_%s", orderId, DateUtils.formatDateTime(LocalDateTime.now(), "MMdd")); return String.format("%s_%s", orderId, DateUtils.formatDateTime(LocalDateTime.now(), "MMdd"));
} }
private String fixBrand(String brand){ private String fixBrand(String brand) {
if (!brand.equals("嘉格纳")){ if (!brand.equals("嘉格纳")) {
return "博世/西门子以及其他品牌"; return "博世/西门子以及其他品牌";
} else { } else {
return brand; return brand;
......
...@@ -60,3 +60,22 @@ SaaS: ...@@ -60,3 +60,22 @@ SaaS:
url: https://pea-test.bshg.com.cn url: https://pea-test.bshg.com.cn
ak: 64e1cde3f9144bfb850b7d37c51af559 ak: 64e1cde3f9144bfb850b7d37c51af559
scheduler:
init-engineer-capacity:
# 每天22点1次
cron-expr: 0 0 22 * * ?
day-offset-begin: 0
day-offset-end: 14
rewrite-force: true
calc-engineer-capacity:
# 8-20点,每30分钟1次
cron-expr: 0 */30 1-23 * * ?
day-offset-begin: 0
day-offset-end: 14
calc-org-capacity:
# 8-20点,每小时1次
cron-expr: 0 0 1-23 * * ?
day-offset-begin: 0
day-offset-end: 14
\ No newline at end of file
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!