Commit bb3c8ace by 刘鑫

Merge branch 'develop' of https://gitlab.dituhui.com/bsh/project/project into develop

2 parents 096b2f0b 75f99c75
......@@ -76,6 +76,9 @@ public class DispatchConstraintProvider implements ConstraintProvider {
if (!in) {
// 到达时间在日历窗口外,惩罚得分
ret = true;
customer.setInTechnicianTimeWindows(false);
} else {
customer.setInTechnicianTimeWindows(true);
}
}
}
......@@ -93,18 +96,18 @@ public class DispatchConstraintProvider implements ConstraintProvider {
.asConstraint(ConstraintNameEnum.technicianCapacityMatch.name());
}
protected Constraint dispatchedMatch(ConstraintFactory factory) {
return factory.forEach(Customer.class).filter(customer ->
// 已分配但是分给了别人
(customer.getDispatchedTechnicianCode() != null && customer.getTechnician() != null
&& !StringUtils.equals(customer.getDispatchedTechnicianCode(), customer.getTechnician().getCode())) ||
// 已排除但是分给了这个人
(customer.getExclusiveTechnicianCode() != null && customer.getTechnician() != null
&& StringUtils.equals(customer.getExclusiveTechnicianCode(),
customer.getTechnician().getCode())))
.penalizeLong(HardSoftLongScore.ONE_HARD, customer -> 50)
.asConstraint(ConstraintNameEnum.dispatchedMatch.name());
}
protected Constraint dispatchedMatch(ConstraintFactory factory) {
return factory.forEach(Customer.class).filter(customer ->
// 已分配但是分给了别人
(customer.getDispatchedTechnicianCode() != null && customer.getTechnician() != null
&& !StringUtils.equals(customer.getDispatchedTechnicianCode(), customer.getTechnician().getCode())) ||
// 已排除但是分给了这个人
(customer.getExclusiveTechnicianCode() != null && customer.getTechnician() != null
&& StringUtils.equals(customer.getExclusiveTechnicianCode(),
customer.getTechnician().getCode())))
.penalizeLong(HardSoftLongScore.ONE_HARD, customer -> 50)
.asConstraint(ConstraintNameEnum.dispatchedMatch.name());
}
// protected Constraint customerTimeWindowsMatch1(ConstraintFactory factory) {
// return factory.forEach(Customer.class).filter(
......
......@@ -21,12 +21,12 @@ public class Customer {
private long id;
private String code;
// 已分配
private String dispatchedTechnicianCode;
// 已排除
private String exclusiveTechnicianCode;
// orderid(code)+dt 确定唯一一条工单
private String dt;
@JsonIgnore
......@@ -50,6 +50,8 @@ public class Customer {
// 离开时间
// private Integer departureTime;
private boolean isInTechnicianTimeWindows = true;
public Customer() {
}
......@@ -135,9 +137,9 @@ public class Customer {
// throw new IllegalStateException("This method must not be called when the shadow variables are not initialized yet.");
}
if (previousCustomer == null) {
return technician.getDepot().getLocation().getDistanceTo(technician.getVehicleType(),location);
return technician.getDepot().getLocation().getDistanceTo(technician.getVehicleType(), location);
}
return previousCustomer.getLocation().getDistanceTo(technician.getVehicleType(),location);
return previousCustomer.getLocation().getDistanceTo(technician.getVehicleType(), location);
}
/**
......@@ -151,9 +153,9 @@ public class Customer {
// throw new IllegalStateException("This method must not be called when the shadow variables are not initialized yet.");
}
if (previousCustomer == null) {
return technician.getDepot().getLocation().getPathTimeTo(technician.getVehicleType(),location);
return technician.getDepot().getLocation().getPathTimeTo(technician.getVehicleType(), location);
}
return previousCustomer.getLocation().getPathTimeTo(technician.getVehicleType(),location);
return previousCustomer.getLocation().getPathTimeTo(technician.getVehicleType(), location);
}
@Override
......
package com.dituhui.pea.dispatch.quartz.dispatch;
import com.dituhui.pea.dispatch.common.RedissonUtil;
import com.dituhui.pea.dispatch.service.SchedulerService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
......@@ -23,7 +24,7 @@ import javax.annotation.Resource;
@DisallowConcurrentExecution
public class AutoDispatchJob extends QuartzJobBean {
public static final String TEAM_JOB_PREFIX="BOXI_TEAM_";
public static final String TEAM_JOB_PREFIX = "BOXI_TEAM_";
@Resource
private SchedulerService schedulerService;
......@@ -36,6 +37,9 @@ public class AutoDispatchJob extends QuartzJobBean {
String teamId = name.substring(TEAM_JOB_PREFIX.length());
long start = System.currentTimeMillis();
log.info(">>> 自动派工(teamId:{}) 自动任务开始", teamId);
/*RedissonUtil.lockOperation(AutoDispatchJob.TEAM_JOB_PREFIX + teamId, 60, () -> {
schedulerService.dispatchRun2(teamId);
});*/
schedulerService.dispatchRun2(teamId);
long end = System.currentTimeMillis();
log.info(">>> 自动派工(teamId:{}) 自动任务结束,耗时:{}", teamId, end - start);
......
......@@ -113,52 +113,45 @@ public class SchedulerServiceImpl implements SchedulerService {
for (int i = 1; i <= nextDaysLimit; i++) {
String currDay = LocalDate.now().plusDays(i).format(DateTimeFormatter.ISO_LOCAL_DATE);
boolean finalCutOff = cutOff;
RedissonUtil.lockOperation(AutoDispatchJob.TEAM_JOB_PREFIX + teamId, 60, () -> {
dispatchRun2OneDay(teamId, currDay, today, finalCutOff);
});
}
}
private void dispatchRun2OneDay(String teamId, String currDay, String today, boolean cutOff) {
Optional<DispatchBatch> optional = dispatchBatchRepository.findByTeamIdAndBatchDate(teamId, currDay);
if (optional.isPresent()
&& Objects.nonNull(optional.get().getCutoffedTime())
&& DateUtil.format(optional.get().getCutoffedTime(), "yyyy-MM-dd").equals(today)) {
//自动任务截止
log.error(">>> teamId:{}, day:{} 自动任务已截止", teamId, currDay);
return;
}
try {
log.info("dispatchRun begin----- teamId:{}, day:{}", teamId, currDay);
String batchNo = batchService.buildBatchData2(teamId, currDay, cutOff);
UUID problemId = solveService.generateProblemId(teamId, batchNo);
log.info("dispatchRun teamId:{}, day:{}, batch:{}, problemId:{}", teamId, currDay, batchNo, problemId);
DispatchSolution problem = solveService.prepareSolution2(teamId, batchNo, currDay);
if (problem.getCustomerList().size() <= 0) {
log.info("dispatchRun 当前批次没有待指派工单 , teamId:{}, day:{}, batch:{}, problemId:{}, order-size:{}", teamId, currDay, batchNo, problemId, problem.getCustomerList().size());
Optional<DispatchBatch> optional = dispatchBatchRepository.findByTeamIdAndBatchDate(teamId, currDay);
if (optional.isPresent()
&& Objects.nonNull(optional.get().getCutoffedTime())
&& DateUtil.format(optional.get().getCutoffedTime(), "yyyy-MM-dd").equals(today)) {
//自动任务截止
log.error(">>> teamId:{}, day:{} 自动任务已截止", teamId, currDay);
return;
}
log.info("dispatchRun prepare done, teamId:{}, day:{}, batch:{}, problemId:{}", teamId, currDay, batchNo, problemId);
Solver<DispatchSolution> solver = solverFactory.buildSolver();
DispatchSolution solution = solver.solve(problem);
DispatchSolutionUtils.removeHardConstraintCustomer(solution, solverFactory);
log.info("dispatchRun solve done, teamId:{}, day:{}, batch:{}, problemId:{}, score:{}", teamId, currDay, batchNo, problemId, solution.getScore().toShortString());
this.solveService.saveSolutionWrp2(solution);
this.extractService.extractDispatchToOrder2(teamId, batchNo, cutOff);
log.info("dispatchRun done ------ teamId:{}, day:{}", teamId, currDay);
JacksonSolutionFileIO<DispatchSolution> exporter = new JacksonSolutionFileIO<DispatchSolution>(DispatchSolution.class);
exporter.write(solution, new File(String.format("dispatchSolution_%s_%s.json", teamId, currDay)));
//log.info("dispatchRun group:{}, team:{} done", groupId, teamId);
} catch (Exception e) {
log.error(">>> (teamId:{}, day:{})自动排班失败:{}", teamId, currDay, e.getMessage(), e);
//throw e;
try {
log.info("dispatchRun begin----- teamId:{}, day:{}", teamId, currDay);
String batchNo = batchService.buildBatchData2(teamId, currDay, cutOff);
UUID problemId = solveService.generateProblemId(teamId, batchNo);
log.info("dispatchRun teamId:{}, day:{}, batch:{}, problemId:{}", teamId, currDay, batchNo, problemId);
DispatchSolution problem = solveService.prepareSolution2(teamId, batchNo, currDay);
if (problem.getCustomerList().size() <= 0) {
log.info("dispatchRun 当前批次没有待指派工单 , teamId:{}, day:{}, batch:{}, problemId:{}, order-size:{}", teamId, currDay, batchNo, problemId, problem.getCustomerList().size());
return;
}
log.info("dispatchRun prepare done, teamId:{}, day:{}, batch:{}, problemId:{}", teamId, currDay, batchNo, problemId);
Solver<DispatchSolution> solver = solverFactory.buildSolver();
DispatchSolution solution = solver.solve(problem);
DispatchSolutionUtils.removeHardConstraintCustomer(solution, solverFactory);
log.info("dispatchRun solve done, teamId:{}, day:{}, batch:{}, problemId:{}, score:{}", teamId, currDay, batchNo, problemId, solution.getScore().toShortString());
this.solveService.saveSolutionWrp2(solution);
this.extractService.extractDispatchToOrder2(teamId, batchNo, cutOff);
log.info("dispatchRun done ------ teamId:{}, day:{}", teamId, currDay);
JacksonSolutionFileIO<DispatchSolution> exporter = new JacksonSolutionFileIO<DispatchSolution>(DispatchSolution.class);
exporter.write(solution, new File(String.format("dispatchSolution_%s_%s.json", teamId, currDay)));
//log.info("dispatchRun group:{}, team:{} done", groupId, teamId);
} catch (Exception e) {
log.error(">>> (teamId:{}, day:{})自动排班失败:{}", teamId, currDay, e.getMessage(), e);
//throw e;
}
}
}
}
\ No newline at end of file
......@@ -5,13 +5,12 @@ import static java.util.Comparator.comparing;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.optaplanner.constraint.streams.drools.DroolsConstraintStreamScoreDirector;
......@@ -38,423 +37,442 @@ import com.dituhui.pea.dispatch.pojo.Technician;
public class DispatchSolutionUtils {
/**
* 打印方案
*
* @param solution
*/
public static void printSolution(DispatchSolution solution) {
System.out.println("Score: " + solution.getScore().toShortString());
System.out.println("技能约束:");
solution.getTechnicianList().forEach(technician -> {
System.out.printf("技术员%s(%s) %s%n", technician.getId(), technician.getCode(), technician.getSkills());
for (Customer customer : technician.getCustomerList()) {
if (!technician.getSkills().contains(customer.getRequiredSkill())) {
// no match
System.err.printf(" 预约单%s(%s) %s%n", customer.getId(), customer.getCode(),
customer.getRequiredSkill());
} else {
System.out.printf(" 预约单%s(%s) %s%n", customer.getId(), customer.getCode(),
customer.getRequiredSkill());
}
}
});
AtomicInteger totalNum = new AtomicInteger(0);
solution.getTechnicianList().forEach(technician -> {
System.out.printf("技术员%s(%s) [%s,%s]%n", technician.getId(), technician.getCode(),
printTime(technician.getTimeWindows()[0][0]), printTime(technician.getTimeWindows()[0][1]));
totalNum.addAndGet(technician.getCustomerList().size());
for (Customer customer : technician.getCustomerList()) {
Customer previousCustomer = customer.getPreviousCustomer();
int startPath, endPath;// 路上时间
if (null == previousCustomer) {
startPath = technician.getDepot().getStartTime();
// endPath = startPath +
// customer.getLocation().getPathTimeTo(technician.getDepot().getLocation());
endPath = startPath + technician.getDepot().getLocation().getPathTimeTo(technician.getVehicleType(),customer.getLocation());
} else {
startPath = previousCustomer.getDepartureTime();
// endPath = startPath +
// customer.getLocation().getPathTimeTo(previousCustomer.getLocation());
endPath = startPath + previousCustomer.getLocation().getPathTimeTo(technician.getVehicleType(),customer.getLocation());
}
if (customer.getArrivalTime() > customer.getEndTime()) {
// 迟到
System.err.printf(
" 预约单%s(%s) 预约时间窗[%s=>%s] 路上时间[%s=>%s] 早到等待时间[%s=>%s] 派工时间[%s=>%s] 迟到时间[%s=>%s]%n",
customer.getId(), customer.getCode(),
// 预约时间窗
printTime(customer.getStartTime()), printTime(customer.getEndTime()),
// 路上时间
printTime(startPath), printTime(endPath),
// 早到等待时间
customer.getArrivalTime() < customer.getStartTime() ? printTime(endPath) : "",
customer.getArrivalTime() < customer.getStartTime() ? printTime(customer.getStartTime())
: "",
// 派工时间
printTime(customer.getArrivalTime()), printTime(customer.getDepartureTime()),
// 迟到时间
printTime(customer.getEndTime()), printTime(customer.getArrivalTime()));
} else {
System.out.printf(
" 预约单%s(%s) 预约时间窗[%s=>%s] 路上时间[%s=>%s] 早到等待时间[%s=>%s] 派工时间[%s=>%s] 迟到时间[%s=>%s]%n",
customer.getId(), customer.getCode(),
// 预约时间窗
printTime(customer.getStartTime()), printTime(customer.getEndTime()),
// 路上时间
printTime(startPath), printTime(endPath),
// 早到等待时间
customer.getArrivalTime() < customer.getStartTime() ? printTime(endPath) : "",
customer.getArrivalTime() < customer.getStartTime() ? printTime(customer.getStartTime())
: "",
// 派工时间
printTime(customer.getArrivalTime()), printTime(customer.getDepartureTime()),
// 迟到时间
"", "");
}
}
});
}
private static String printTime(int startTime) {
int hour = startTime / 60;
int minite = startTime % 60;
return StringUtils.leftPad("" + hour, 2, '0') + ":" + StringUtils.leftPad("" + minite, 2, '0');
}
/**
* 生成方案地图页
*
* @param solution
* @param filename
*/
public static void exportMapHtml(DispatchSolution solution, String filename) {
try {
// 仓库起点
String depot = "\"" + solution.getDepot().getLocation().getX() + ","
+ solution.getDepot().getLocation().getY() + "\"";
// 技术员路线
String lines_ = "[";
for (Technician technician : solution.getTechnicianList()) {
if (technician.getCustomerList().size() > 0) {
lines_ += "\"" + technician.getCustomerList().stream()
.map(c -> c.getLocation().getX() + "," + c.getLocation().getY())
.reduce((a, b) -> a + ";" + b).get() + "\",";
}
}
lines_ += "]";
final String lines = lines_;
// 技术员偏好中心点
String preferredlocation = "\"" + solution.getTechnicianList().stream()
.map(c -> (c.getPreferredlocation() == null ? "" : c.getPreferredlocation().getX()) + ","
+ (c.getPreferredlocation() == null ? "" : c.getPreferredlocation().getY()))
.reduce((a, b) -> a + ";" + b).get() + "\"";
// 技术员名称
String names = "\"" + solution.getTechnicianList().stream()
.map(c -> (c.getPreferredlocation() == null ? "" : c.getPreferredlocation().getX()) + ","
+ (c.getPreferredlocation() == null ? "" : c.getPreferredlocation().getY()) + ","
+ c.getCode())
.reduce((a, b) -> a + ";" + b).get() + "\"";
List<String> dispatchMapLines = IOUtils.readLines(new FileInputStream("data/dispatchMap.html"), "GBK");
dispatchMapLines = dispatchMapLines.stream().map(line -> {
if (StringUtils.startsWith(line, " var depot = ")) {
return " var depot = " + depot;
} else if (StringUtils.startsWith(line, " var preferredlocation = ")) {
return " var preferredlocation = " + preferredlocation;
} else if (StringUtils.startsWith(line, " var lines = ")) {
return " var lines = " + lines;
} else if (StringUtils.startsWith(line, " var names = ")) {
return " var names = " + names;
} else{
return line;
}
}).collect(Collectors.toList());
IOUtils.writeLines(dispatchMapLines, "\r\n", new FileOutputStream(filename + ".html"), "GBK");
System.out.println("output map : " + filename);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 计算方案分数
*
* @param solution
* @return
*/
public static HardSoftLongScore getScore(DispatchSolution solution) {
DefaultSolverFactory<DispatchSolution> solverFactory = getSolverFactory();
ScoreDirectorFactory<DispatchSolution> scoreDirectorFactory = solverFactory.getScoreDirectorFactory();
DroolsConstraintStreamScoreDirector<DispatchSolution, HardSoftLongScore> scoreDirector = (DroolsConstraintStreamScoreDirector<DispatchSolution, HardSoftLongScore>) scoreDirectorFactory
.buildScoreDirector();
// Set the working solution of the ScoreDirector object.
scoreDirector.setWorkingSolution(solution);
// Iterate over the constraints in your problem and call the
// `addConstraintMatch()` method
// for each constraint that is violated by the solution.
// for (Constraint constraint : myConstraints) {
// if (constraint.isViolated(mySolution)) {
// scoreDirector.addConstraintMatch(constraint, constraint.getScore());
// }
// }
// Calculate the score of the solution.
HardSoftLongScore score = (HardSoftLongScore) scoreDirector.calculateScore();
return score;
}
/**
* 移动某个技术员的订单,返回新方案<br>
* FIXME 没有成功
*
* @param solution
* @param source 技术员
* @param sourceIndex 0开始
* @param destinationIndex 0开始
* @return
*/
public static DispatchSolution moveCustomer(DefaultSolverFactory<DispatchSolution> solverFactory,
DispatchSolution solution, Technician source, int sourceIndex, int destinationIndex) {
if (null == solverFactory) {
solverFactory = getSolverFactory();
}
ScoreDirectorFactory<DispatchSolution> scoreDirectorFactory = solverFactory.getScoreDirectorFactory();
DroolsConstraintStreamScoreDirector<DispatchSolution, HardSoftLongScore> scoreDirector = (DroolsConstraintStreamScoreDirector<DispatchSolution, HardSoftLongScore>) scoreDirectorFactory
.buildScoreDirector();
// Make a change to the solution.
// doMove(), doSwap(), or doChangeVariable()
// Create a variable descriptor for the "customerList" list property
ListVariableDescriptor<DispatchSolution> customerDescriptor = (ListVariableDescriptor<DispatchSolution>) scoreDirector
.getSolutionDescriptor().getEntityDescriptorStrict(Technician.class)
.getVariableDescriptor("customerList");
// Create the ListChangeMove
ListChangeMove<DispatchSolution> move = new ListChangeMove<DispatchSolution>(customerDescriptor, source,
sourceIndex, source, destinationIndex);
// apply the move and re-evaluate the score
scoreDirector.doAndProcessMove(move, true);
return scoreDirector.getWorkingSolution();
}
public static DefaultSolverFactory<DispatchSolution> getSolverFactory() {
return getSolverFactory(0, 0);
}
public static DefaultSolverFactory<DispatchSolution> getSolverFactory(long unimprovedSecondsSpentLimit,
long secondsSpentLimit) {
// 创建求解器配置
// 创建 SolverConfig 对象,并设置求解器配置
SolverConfig solverConfig = new SolverConfig();
solverConfig.setSolutionClass(DispatchSolution.class);
solverConfig.withEntityClassList(Arrays.asList(Technician.class, Customer.class));// 这里不能漏掉,否则约束不生效
TerminationConfig terminationConfig = new TerminationConfig();
terminationConfig
.setUnimprovedSecondsSpentLimit(unimprovedSecondsSpentLimit <= 0 ? 5 : unimprovedSecondsSpentLimit);// XX秒没有找到更好方案
terminationConfig.setSecondsSpentLimit(secondsSpentLimit <= 0 ? 60 : secondsSpentLimit);// 总时间不能超过XXs
solverConfig.withTerminationConfig(terminationConfig);
// 约束条件
solverConfig.withConstraintProviderClass(DispatchConstraintProvider.class);
solverConfig.setScoreDirectorFactoryConfig(
new ScoreDirectorFactoryConfig().withConstraintProviderClass(DispatchConstraintProvider.class));
DefaultSolverFactory<DispatchSolution> solverFactory = (DefaultSolverFactory<DispatchSolution>) SolverFactory
.<DispatchSolution>create(solverConfig);
return solverFactory;
}
/**
* 打印详细约束得分
*
* @param solution
* @param solverFactory
*/
public static void explainSolutionConstraintDetail(DispatchSolution solution, DefaultSolverFactory<DispatchSolution> solverFactory) {
// Obtain a ScoreExplanation object for the best solution
// Using score calculation outside the Solver
// https://www.optaplanner.org/docs/optaplanner/latest/score-calculation/score-calculation.html
SolutionManager<DispatchSolution, HardSoftLongScore> scoreManager = SolutionManager.create(solverFactory);
ScoreExplanation<DispatchSolution, HardSoftLongScore> scoreExplanation = scoreManager.explain(solution);
// System.out.println(scoreExplanation.getSummary());
Map<String, ConstraintMatchTotal<HardSoftLongScore>> constraintMatchTotalMap = scoreExplanation
.getConstraintMatchTotalMap();
constraintMatchTotalMap.forEach((key, value) -> {
if (!value.getScore().isFeasible()) {
// 违反硬约束
System.out.printf("%s 匹配%s次 hard得分:%s%n", value.getConstraintName(), value.getConstraintMatchCount(),
value.getScore().hardScore());
} else {
// 软约束
System.out.printf("%s 匹配%s次 soft得分:%s%n", value.getConstraintName(), value.getConstraintMatchCount(),
value.getScore().softScore());
}
value.getConstraintMatchSet().stream().sorted(comparing(ConstraintMatch::getScore))
.forEach(constraintMatch -> {
String text = "";
switch (ConstraintNameEnum.valueOf(value.getConstraintName())) {
case skillMatch:
case customerTimeWindowsMatch:
case dispatchedMatch:
for (Object indictedObject : constraintMatch.getIndictedObjectList()) {
// 违反硬约束对象,根据具体约束返回不同类型对象
if (indictedObject instanceof Customer) {
Customer customer = (Customer) indictedObject;
text += customer.getCode() + ",";
}
}
System.out.printf(" 预约单(%s)违反约束,扣分%s%n", text, constraintMatch.getScore().toShortString());
break;
case totalDistance:
case preferredTotalDistance:
for (Object indictedObject : constraintMatch.getIndictedObjectList()) {
// 违反软约束对象,根据具体约束返回不同类型对象
if (indictedObject instanceof Technician) {
Technician technician = (Technician) indictedObject;
text += technician.getCode() + ",";
}
}
System.out.printf(" 技术员(%s)总路程得分%s%n", text, constraintMatch.getScore().toShortString());
break;
case technicianBalanceSoft:
if (constraintMatch.getScore().softScore() < 0) {
for (Object indictedObject : constraintMatch.getIndictedObjectList()) {
// 违反软约束对象,根据具体约束返回不同类型对象
if (indictedObject instanceof Technician) {
Technician technician = (Technician) indictedObject;
text += technician.getCode() + ",";
}
}
System.out.printf(" 技术员(%s)订单量差距得分%s%n", text,
constraintMatch.getScore().toShortString());
}
break;
default:
break;
}
});
});
}
/**
* 导出json结构
*
* @param solution
* @param fileName
*/
public static void exportSolutionJson(DispatchSolution solution, String fileName) {
// Create a JacksonSolutionFileIO instance.
JacksonSolutionFileIO<DispatchSolution> exporter = new JacksonSolutionFileIO<DispatchSolution>(
DispatchSolution.class);
// Set the output file.
exporter.write(solution, new File(fileName));
}
/**
* 移除hard约束元素订单
*
* @param solution
* @param solverFactory
*/
public static void removeHardConstraintCustomer(DispatchSolution solution,
SolverFactory<DispatchSolution> solverFactory) {
SolutionManager<DispatchSolution, HardSoftLongScore> scoreManager = SolutionManager.create(solverFactory);
ScoreExplanation<DispatchSolution, HardSoftLongScore> scoreExplanation = scoreManager.explain(solution);
Map<String, ConstraintMatchTotal<HardSoftLongScore>> constraintMatchTotalMap = scoreExplanation
.getConstraintMatchTotalMap();
constraintMatchTotalMap.forEach((key, value) -> {
if (!value.getScore().isFeasible()) {
// 违反硬约束
value.getConstraintMatchSet().stream().sorted(comparing(ConstraintMatch::getScore))
.forEach(constraintMatch -> {
switch (ConstraintNameEnum.valueOf(value.getConstraintName())) {
case skillMatch:
case customerTimeWindowsMatch:
for (Object indictedObject : constraintMatch.getIndictedObjectList()) {
// 违反硬约束对象,根据具体约束返回不同类型对象
if (indictedObject instanceof Customer) {
Customer customer = (Customer) indictedObject;
// 更新shadow变量
updateShadowVariable(customer);
// 移除技术员
customer.getTechnician().getCustomerList().remove(customer);
solution.getUnDispatchedCustomers().add(customer);
}
}
break;
default:
break;
}
});
}
});
}
private static void updateShadowVariable(Customer sourceCustomer) {
if (sourceCustomer.getTechnician() == null) {
if (sourceCustomer.getArrivalTime() != null) {
sourceCustomer.setArrivalTime(null);
}
return;
}
// 移除当前订单引用
Customer previousCustomer = sourceCustomer.getPreviousCustomer();
Customer nextCustomer = sourceCustomer.getNextCustomer();
if (previousCustomer == null) {
// 当前订单是第一个订单
if(null != nextCustomer) {
nextCustomer.setPreviousCustomer(null);
}
}
if (nextCustomer == null) {
// 当前订单是最后一个订单
if(null != previousCustomer) {
previousCustomer.setNextCustomer(null);
}
}
if (previousCustomer != null && null != nextCustomer) {
previousCustomer.setNextCustomer(nextCustomer);
nextCustomer.setPreviousCustomer(previousCustomer);
}
// 前一个的离开时间
Integer departureTime;
if (previousCustomer == null) {
departureTime = (sourceCustomer.getTechnician().getDepot()).getStartTime();
} else {
departureTime = previousCustomer.getDepartureTime();
}
if (nextCustomer != null) {
// 更新后续订单
Customer shadowCustomer = nextCustomer;
Integer arrivalTime = calculateArrivalTime(shadowCustomer, departureTime);
while (shadowCustomer != null && !Objects.equals(shadowCustomer.getArrivalTime(), arrivalTime)) {
shadowCustomer.setArrivalTime(arrivalTime);
departureTime = shadowCustomer.getDepartureTime();
shadowCustomer = shadowCustomer.getNextCustomer();
arrivalTime = calculateArrivalTime(shadowCustomer, departureTime);
}
}
}
private static Integer calculateArrivalTime(Customer customer, Integer previousDepartureTime) {
if (customer == null || previousDepartureTime == null) {
return null;
}
return previousDepartureTime + customer.getPathTimeFromPreviousStandstill();
}
/**
* 打印方案
*
* @param solution
*/
public static void printSolution(DispatchSolution solution) {
System.out.println("Score: " + solution.getScore().toShortString());
System.out.println("技能约束:");
solution.getTechnicianList().forEach(technician -> {
System.out.printf("技术员%s(%s) %s%n", technician.getId(), technician.getCode(), technician.getSkills());
for (Customer customer : technician.getCustomerList()) {
if (!technician.getSkills().contains(customer.getRequiredSkill())) {
// no match
System.err.printf(" 预约单%s(%s) %s%n", customer.getId(), customer.getCode(),
customer.getRequiredSkill());
} else {
System.out.printf(" 预约单%s(%s) %s%n", customer.getId(), customer.getCode(),
customer.getRequiredSkill());
}
}
});
AtomicInteger totalNum = new AtomicInteger(0);
solution.getTechnicianList().forEach(technician -> {
System.out.printf("技术员%s(%s) [%s,%s]%n", technician.getId(), technician.getCode(),
printTime(technician.getTimeWindows()[0][0]), printTime(technician.getTimeWindows()[0][1]));
totalNum.addAndGet(technician.getCustomerList().size());
for (Customer customer : technician.getCustomerList()) {
Customer previousCustomer = customer.getPreviousCustomer();
int startPath, endPath;// 路上时间
if (null == previousCustomer) {
startPath = technician.getDepot().getStartTime();
// endPath = startPath +
// customer.getLocation().getPathTimeTo(technician.getDepot().getLocation());
endPath = startPath + technician.getDepot().getLocation().getPathTimeTo(technician.getVehicleType(), customer.getLocation());
} else {
startPath = previousCustomer.getDepartureTime();
// endPath = startPath +
// customer.getLocation().getPathTimeTo(previousCustomer.getLocation());
endPath = startPath + previousCustomer.getLocation().getPathTimeTo(technician.getVehicleType(), customer.getLocation());
}
if (customer.getArrivalTime() > customer.getEndTime()) {
// 迟到
System.err.printf(
" 预约单%s(%s) 预约时间窗[%s=>%s] 路上时间[%s=>%s] 早到等待时间[%s=>%s] 派工时间[%s=>%s] 迟到时间[%s=>%s]%n",
customer.getId(), customer.getCode(),
// 预约时间窗
printTime(customer.getStartTime()), printTime(customer.getEndTime()),
// 路上时间
printTime(startPath), printTime(endPath),
// 早到等待时间
customer.getArrivalTime() < customer.getStartTime() ? printTime(endPath) : "",
customer.getArrivalTime() < customer.getStartTime() ? printTime(customer.getStartTime())
: "",
// 派工时间
printTime(customer.getArrivalTime()), printTime(customer.getDepartureTime()),
// 迟到时间
printTime(customer.getEndTime()), printTime(customer.getArrivalTime()));
} else {
System.out.printf(
" 预约单%s(%s) 预约时间窗[%s=>%s] 路上时间[%s=>%s] 早到等待时间[%s=>%s] 派工时间[%s=>%s] 迟到时间[%s=>%s]%n",
customer.getId(), customer.getCode(),
// 预约时间窗
printTime(customer.getStartTime()), printTime(customer.getEndTime()),
// 路上时间
printTime(startPath), printTime(endPath),
// 早到等待时间
customer.getArrivalTime() < customer.getStartTime() ? printTime(endPath) : "",
customer.getArrivalTime() < customer.getStartTime() ? printTime(customer.getStartTime())
: "",
// 派工时间
printTime(customer.getArrivalTime()), printTime(customer.getDepartureTime()),
// 迟到时间
"", "");
}
}
});
}
private static String printTime(int startTime) {
int hour = startTime / 60;
int minite = startTime % 60;
return StringUtils.leftPad("" + hour, 2, '0') + ":" + StringUtils.leftPad("" + minite, 2, '0');
}
/**
* 生成方案地图页
*
* @param solution
* @param filename
*/
public static void exportMapHtml(DispatchSolution solution, String filename) {
try {
// 仓库起点
String depot = "\"" + solution.getDepot().getLocation().getX() + ","
+ solution.getDepot().getLocation().getY() + "\"";
// 技术员路线
String lines_ = "[";
for (Technician technician : solution.getTechnicianList()) {
if (technician.getCustomerList().size() > 0) {
lines_ += "\"" + technician.getCustomerList().stream()
.map(c -> c.getLocation().getX() + "," + c.getLocation().getY())
.reduce((a, b) -> a + ";" + b).get() + "\",";
}
}
lines_ += "]";
final String lines = lines_;
// 技术员偏好中心点
String preferredlocation = "\"" + solution.getTechnicianList().stream()
.map(c -> (c.getPreferredlocation() == null ? "" : c.getPreferredlocation().getX()) + ","
+ (c.getPreferredlocation() == null ? "" : c.getPreferredlocation().getY()))
.reduce((a, b) -> a + ";" + b).get() + "\"";
// 技术员名称
String names = "\"" + solution.getTechnicianList().stream()
.map(c -> (c.getPreferredlocation() == null ? "" : c.getPreferredlocation().getX()) + ","
+ (c.getPreferredlocation() == null ? "" : c.getPreferredlocation().getY()) + ","
+ c.getCode())
.reduce((a, b) -> a + ";" + b).get() + "\"";
List<String> dispatchMapLines = IOUtils.readLines(new FileInputStream("data/dispatchMap.html"), "GBK");
dispatchMapLines = dispatchMapLines.stream().map(line -> {
if (StringUtils.startsWith(line, " var depot = ")) {
return " var depot = " + depot;
} else if (StringUtils.startsWith(line, " var preferredlocation = ")) {
return " var preferredlocation = " + preferredlocation;
} else if (StringUtils.startsWith(line, " var lines = ")) {
return " var lines = " + lines;
} else if (StringUtils.startsWith(line, " var names = ")) {
return " var names = " + names;
} else {
return line;
}
}).collect(Collectors.toList());
IOUtils.writeLines(dispatchMapLines, "\r\n", new FileOutputStream(filename + ".html"), "GBK");
System.out.println("output map : " + filename);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 计算方案分数
*
* @param solution
* @return
*/
public static HardSoftLongScore getScore(DispatchSolution solution) {
DefaultSolverFactory<DispatchSolution> solverFactory = getSolverFactory();
ScoreDirectorFactory<DispatchSolution> scoreDirectorFactory = solverFactory.getScoreDirectorFactory();
DroolsConstraintStreamScoreDirector<DispatchSolution, HardSoftLongScore> scoreDirector = (DroolsConstraintStreamScoreDirector<DispatchSolution, HardSoftLongScore>) scoreDirectorFactory
.buildScoreDirector();
// Set the working solution of the ScoreDirector object.
scoreDirector.setWorkingSolution(solution);
// Iterate over the constraints in your problem and call the
// `addConstraintMatch()` method
// for each constraint that is violated by the solution.
// for (Constraint constraint : myConstraints) {
// if (constraint.isViolated(mySolution)) {
// scoreDirector.addConstraintMatch(constraint, constraint.getScore());
// }
// }
// Calculate the score of the solution.
HardSoftLongScore score = (HardSoftLongScore) scoreDirector.calculateScore();
return score;
}
/**
* 移动某个技术员的订单,返回新方案<br>
* FIXME 没有成功
*
* @param solution
* @param source 技术员
* @param sourceIndex 0开始
* @param destinationIndex 0开始
* @return
*/
public static DispatchSolution moveCustomer(DefaultSolverFactory<DispatchSolution> solverFactory,
DispatchSolution solution, Technician source, int sourceIndex, int destinationIndex) {
if (null == solverFactory) {
solverFactory = getSolverFactory();
}
ScoreDirectorFactory<DispatchSolution> scoreDirectorFactory = solverFactory.getScoreDirectorFactory();
DroolsConstraintStreamScoreDirector<DispatchSolution, HardSoftLongScore> scoreDirector = (DroolsConstraintStreamScoreDirector<DispatchSolution, HardSoftLongScore>) scoreDirectorFactory
.buildScoreDirector();
// Make a change to the solution.
// doMove(), doSwap(), or doChangeVariable()
// Create a variable descriptor for the "customerList" list property
ListVariableDescriptor<DispatchSolution> customerDescriptor = (ListVariableDescriptor<DispatchSolution>) scoreDirector
.getSolutionDescriptor().getEntityDescriptorStrict(Technician.class)
.getVariableDescriptor("customerList");
// Create the ListChangeMove
ListChangeMove<DispatchSolution> move = new ListChangeMove<DispatchSolution>(customerDescriptor, source,
sourceIndex, source, destinationIndex);
// apply the move and re-evaluate the score
scoreDirector.doAndProcessMove(move, true);
return scoreDirector.getWorkingSolution();
}
public static DefaultSolverFactory<DispatchSolution> getSolverFactory() {
return getSolverFactory(0, 0);
}
public static DefaultSolverFactory<DispatchSolution> getSolverFactory(long unimprovedSecondsSpentLimit,
long secondsSpentLimit) {
// 创建求解器配置
// 创建 SolverConfig 对象,并设置求解器配置
SolverConfig solverConfig = new SolverConfig();
solverConfig.setSolutionClass(DispatchSolution.class);
solverConfig.withEntityClassList(Arrays.asList(Technician.class, Customer.class));// 这里不能漏掉,否则约束不生效
TerminationConfig terminationConfig = new TerminationConfig();
terminationConfig
.setUnimprovedSecondsSpentLimit(unimprovedSecondsSpentLimit <= 0 ? 5 : unimprovedSecondsSpentLimit);// XX秒没有找到更好方案
terminationConfig.setSecondsSpentLimit(secondsSpentLimit <= 0 ? 60 : secondsSpentLimit);// 总时间不能超过XXs
solverConfig.withTerminationConfig(terminationConfig);
// 约束条件
solverConfig.withConstraintProviderClass(DispatchConstraintProvider.class);
solverConfig.setScoreDirectorFactoryConfig(
new ScoreDirectorFactoryConfig().withConstraintProviderClass(DispatchConstraintProvider.class));
DefaultSolverFactory<DispatchSolution> solverFactory = (DefaultSolverFactory<DispatchSolution>) SolverFactory
.<DispatchSolution>create(solverConfig);
return solverFactory;
}
/**
* 打印详细约束得分
*
* @param solution
* @param solverFactory
*/
public static void explainSolutionConstraintDetail(DispatchSolution solution, DefaultSolverFactory<DispatchSolution> solverFactory) {
// Obtain a ScoreExplanation object for the best solution
// Using score calculation outside the Solver
// https://www.optaplanner.org/docs/optaplanner/latest/score-calculation/score-calculation.html
SolutionManager<DispatchSolution, HardSoftLongScore> scoreManager = SolutionManager.create(solverFactory);
ScoreExplanation<DispatchSolution, HardSoftLongScore> scoreExplanation = scoreManager.explain(solution);
// System.out.println(scoreExplanation.getSummary());
Map<String, ConstraintMatchTotal<HardSoftLongScore>> constraintMatchTotalMap = scoreExplanation
.getConstraintMatchTotalMap();
constraintMatchTotalMap.forEach((key, value) -> {
if (!value.getScore().isFeasible()) {
// 违反硬约束
System.out.printf("%s 匹配%s次 hard得分:%s%n", value.getConstraintName(), value.getConstraintMatchCount(),
value.getScore().hardScore());
} else {
// 软约束
System.out.printf("%s 匹配%s次 soft得分:%s%n", value.getConstraintName(), value.getConstraintMatchCount(),
value.getScore().softScore());
}
value.getConstraintMatchSet().stream().sorted(comparing(ConstraintMatch::getScore))
.forEach(constraintMatch -> {
String text = "";
switch (ConstraintNameEnum.valueOf(value.getConstraintName())) {
case skillMatch:
case customerTimeWindowsMatch:
case dispatchedMatch:
for (Object indictedObject : constraintMatch.getIndictedObjectList()) {
// 违反硬约束对象,根据具体约束返回不同类型对象
if (indictedObject instanceof Customer) {
Customer customer = (Customer) indictedObject;
text += customer.getCode() + ",";
}
}
System.out.printf(" 预约单(%s)违反约束,扣分%s%n", text, constraintMatch.getScore().toShortString());
break;
case totalDistance:
case preferredTotalDistance:
for (Object indictedObject : constraintMatch.getIndictedObjectList()) {
// 违反软约束对象,根据具体约束返回不同类型对象
if (indictedObject instanceof Technician) {
Technician technician = (Technician) indictedObject;
text += technician.getCode() + ",";
}
}
System.out.printf(" 技术员(%s)总路程得分%s%n", text, constraintMatch.getScore().toShortString());
break;
case technicianBalanceSoft:
if (constraintMatch.getScore().softScore() < 0) {
for (Object indictedObject : constraintMatch.getIndictedObjectList()) {
// 违反软约束对象,根据具体约束返回不同类型对象
if (indictedObject instanceof Technician) {
Technician technician = (Technician) indictedObject;
text += technician.getCode() + ",";
}
}
System.out.printf(" 技术员(%s)订单量差距得分%s%n", text,
constraintMatch.getScore().toShortString());
}
break;
default:
break;
}
});
});
}
/**
* 导出json结构
*
* @param solution
* @param fileName
*/
public static void exportSolutionJson(DispatchSolution solution, String fileName) {
// Create a JacksonSolutionFileIO instance.
JacksonSolutionFileIO<DispatchSolution> exporter = new JacksonSolutionFileIO<DispatchSolution>(
DispatchSolution.class);
// Set the output file.
exporter.write(solution, new File(fileName));
}
/**
* 移除hard约束元素订单
*
* @param solution
* @param solverFactory
*/
public static void removeHardConstraintCustomer(DispatchSolution solution,
SolverFactory<DispatchSolution> solverFactory) {
if ("1719308075152764928".equals(solution.getTeamId())) {
System.out.println(solution.getTeamId());
}
SolutionManager<DispatchSolution, HardSoftLongScore> scoreManager = SolutionManager.create(solverFactory);
ScoreExplanation<DispatchSolution, HardSoftLongScore> scoreExplanation = scoreManager.explain(solution);
Map<String, ConstraintMatchTotal<HardSoftLongScore>> constraintMatchTotalMap = scoreExplanation
.getConstraintMatchTotalMap();
constraintMatchTotalMap.forEach((key, value) -> {
if (!value.getScore().isFeasible()) {
// 违反硬约束
value.getConstraintMatchSet().stream().sorted(comparing(ConstraintMatch::getScore))
.forEach(constraintMatch -> {
switch (ConstraintNameEnum.valueOf(value.getConstraintName())) {
case skillMatch:
case customerTimeWindowsMatch:
for (Object indictedObject : constraintMatch.getIndictedObjectList()) {
// 违反硬约束对象,根据具体约束返回不同类型对象
if (indictedObject instanceof Customer) {
Customer customer = (Customer) indictedObject;
// 更新shadow变量
updateShadowVariable(customer);
// 移除技术员
customer.getTechnician().getCustomerList().remove(customer);
solution.getUnDispatchedCustomers().add(customer);
}
}
break;
case technicianTimeWindowsMatch:
for (Object indictedObject : constraintMatch.getIndictedObjectList()) {
// 违反硬约束对象,根据具体约束返回不同类型对象
if (indictedObject instanceof Technician
&& ObjectUtil.isNotEmpty(((Technician) indictedObject).getCustomerList())) {
List<Customer> customerList = new ArrayList<>();
customerList.addAll(((Technician) indictedObject).getCustomerList());
for (Customer customer : customerList) {
if (!customer.isInTechnicianTimeWindows()) {
// 更新shadow变量
updateShadowVariable(customer);
// 移除技术员
customer.getTechnician().getCustomerList().remove(customer);
solution.getUnDispatchedCustomers().add(customer);
}
}
}
}
break;
default:
break;
}
});
}
});
}
private static void updateShadowVariable(Customer sourceCustomer) {
if (sourceCustomer.getTechnician() == null) {
if (sourceCustomer.getArrivalTime() != null) {
sourceCustomer.setArrivalTime(null);
}
return;
}
// 移除当前订单引用
Customer previousCustomer = sourceCustomer.getPreviousCustomer();
Customer nextCustomer = sourceCustomer.getNextCustomer();
if (previousCustomer == null) {
// 当前订单是第一个订单
if (null != nextCustomer) {
nextCustomer.setPreviousCustomer(null);
}
}
if (nextCustomer == null) {
// 当前订单是最后一个订单
if (null != previousCustomer) {
previousCustomer.setNextCustomer(null);
}
}
if (previousCustomer != null && null != nextCustomer) {
previousCustomer.setNextCustomer(nextCustomer);
nextCustomer.setPreviousCustomer(previousCustomer);
}
// 前一个的离开时间
Integer departureTime;
if (previousCustomer == null) {
departureTime = (sourceCustomer.getTechnician().getDepot()).getStartTime();
} else {
departureTime = previousCustomer.getDepartureTime();
}
if (nextCustomer != null) {
// 更新后续订单
Customer shadowCustomer = nextCustomer;
Integer arrivalTime = calculateArrivalTime(shadowCustomer, departureTime);
while (shadowCustomer != null && !Objects.equals(shadowCustomer.getArrivalTime(), arrivalTime)) {
shadowCustomer.setArrivalTime(arrivalTime);
departureTime = shadowCustomer.getDepartureTime();
shadowCustomer = shadowCustomer.getNextCustomer();
arrivalTime = calculateArrivalTime(shadowCustomer, departureTime);
}
}
}
private static Integer calculateArrivalTime(Customer customer, Integer previousDepartureTime) {
if (customer == null || previousDepartureTime == null) {
return null;
}
return previousDepartureTime + customer.getPathTimeFromPreviousStandstill();
}
}
......@@ -3,8 +3,8 @@ server:
dispatch:
cron:
expr: 0 */3 8-23 * * ?
next-day-limit: 20
expr: 0 10 8-23 * * ?
next-day-limit: 2
scheduler:
init-engineer-capacity:
......
......@@ -147,6 +147,8 @@ public enum StatusCodeEnum {
ORDER_FINISHED("030", "订单已结束,请勿操作", false),
FENDAN_IS_TRANSCEND_AND_SPECIAL("031", "分单超派和特殊时间", false),
ENGINEER_IS_LEAVE_TIME("032", "工单(%s)指派失败!%s的工作日历在该时间段已有日程安排", false),
;
/**
......
......@@ -357,7 +357,7 @@ public class DispatchServiceImpl implements DispatchService {
Result<OrderInfoEntity> entityResult = orderInfoService.insterEngineerOrders(engineerOrders, entity, skill, byTeamId, engineer);
if (!entityResult.getCode().equals(ResultEnum.SUCCESS.getCode())) {
// return Result.failed("当前工程师无法预约合适时间");
errorList.add(entity.getOrderId());
errorList.add("单号:" + entity.getOrderId());
continue;
}
entity = entityResult.getResult();
......@@ -378,11 +378,9 @@ public class DispatchServiceImpl implements DispatchService {
successList.add(entity.getOrderId());
}
List<String> resultList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(successList)) {
resultList.add(String.format("%s订单指派(改派)成功", String.join(",", successList)));
}
if (CollectionUtils.isNotEmpty(errorList)) {
resultList.add(String.format("%s订单指派(改派)失败,当前工程师无法预约合适时间", String.join(",", errorList)));
String msg = errorList.size() > 3 ? String.join(",", errorList.subList(0, 3)) + "..." : String.join(",", errorList);
resultList.add(String.format(StatusCodeEnum.ENGINEER_IS_LEAVE_TIME.getDesc(), msg, engineer.getName()));
}
return Result.success(CollectionUtils.isNotEmpty(resultList) ? String.join(",", resultList) : null);
}
......
......@@ -290,7 +290,7 @@ public class FendanServiceImpl implements FendanService {
}
//最后处理全技能数据
if (i == mapBlockInfoList.size() - 1 && null != allLayer) {
OrgTeamInfo orgTeamInfoAll = BeanUtil.copyProperties(orgTeamEntity, OrgTeamInfo.class);
OrgTeamInfo orgTeamInfoAll = BeanUtil.copyProperties(teamMap.get(allLayer.getTeamId()), OrgTeamInfo.class);
orgGroup.setTeamInfos(Arrays.asList(orgTeamInfoAll));
allLayer = null;
}
......
......@@ -35,6 +35,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
......@@ -450,7 +451,7 @@ public class OrderInfoServiceImpl implements OrderInfoService {
/**
* 根据工程师当日订单插入订单
* 根据工程师当日订单插入订单(根据工单预计开始时间插入,其他顺延)
*
* @param skillInfo 待指派订单技能
* @param engineerOrders 待指派工程师当日工单
......@@ -462,20 +463,16 @@ public class OrderInfoServiceImpl implements OrderInfoService {
@Transactional
@Override
public Result<OrderInfoEntity> insterEngineerOrders(List<OrderInfoEntity> engineerOrders, OrderInfoEntity insertOrder, SkillInfoEntity skillInfo, OrgTeamEntity orgTeam, EngineerInfoEntity engineer) {
// 获取客户期望时间段
LocalDateTime workStartTime = insertOrder.getExpectTimeBegin();
LocalDateTime workEndTime = insertOrder.getExpectTimeEnd();
//查询工程师当天出勤时间
// List<OccupyInfoDetail> engineerWorkTimeSlice = engineerCalendarService.timeWindowsSlice(engineer.getEngineerCode(), orgTeam.getTeamId(), insertOrder.getDt());
//查询工程师当天请假时间
List<OccupyInfoDetail> engineerLeaveTimeSlice = engineerCalendarService.getEngineerWorkDayCalendar(engineer.getEngineerCode(), insertOrder.getDt());
log.info("【engineerWorkTimeSlice】结果------------->{}", JsonUtil.toJson(engineerLeaveTimeSlice));
// 没有单直接返回
if (CollectionUtils.isEmpty(engineerOrders)) {
LocalDateTime planStartTime = handleLeaveTime(engineerLeaveTimeSlice, insertOrder.getExpectTimeBegin(), insertOrder.getExpectTimeEnd(), skillInfo.getTakeTime());
if (ObjectUtil.isNull(planStartTime)) {
Result handleLeaveTimeResult = handleLeaveTime(engineerLeaveTimeSlice, insertOrder.getExpectTimeBegin(), insertOrder.getExpectTimeEnd(), skillInfo.getTakeTime(), true, insertOrder);
if (!handleLeaveTimeResult.getCode().equals(StatusCodeEnum.SUCCESS)) {
return Result.failed(insertOrder);
}
LocalDateTime planStartTime = (LocalDateTime) handleLeaveTimeResult.getResult();
insertOrder.setTakeTime(skillInfo.getTakeTime());
insertOrder.setPlanStartTime(planStartTime);
insertOrder.setPlanEndTime(planStartTime.plusMinutes(skillInfo.getTakeTime()));
......@@ -501,96 +498,114 @@ public class OrderInfoServiceImpl implements OrderInfoService {
}
// 没有单直接返回
if (CollectionUtils.isEmpty(orderSegments)) {
LocalDateTime planStartTime = handleLeaveTime(engineerLeaveTimeSlice, insertOrder.getExpectTimeBegin(), insertOrder.getExpectTimeEnd(), skillInfo.getTakeTime());
if (ObjectUtil.isNull(planStartTime)) {
Result handleLeaveTimeResult = handleLeaveTime(engineerLeaveTimeSlice, insertOrder.getExpectTimeBegin(), insertOrder.getExpectTimeEnd(), skillInfo.getTakeTime(), true, insertOrder);
if (!handleLeaveTimeResult.getCode().equals(StatusCodeEnum.SUCCESS)) {
return Result.failed(insertOrder);
}
LocalDateTime planStartTime = (LocalDateTime) handleLeaveTimeResult.getResult();
insertOrder.setTakeTime(skillInfo.getTakeTime());
insertOrder.setPlanStartTime(planStartTime);
insertOrder.setPlanEndTime(planStartTime.plusMinutes(skillInfo.getTakeTime()));
return Result.success(insertOrder);
}
OrderSegment seg = new OrderSegment();
seg.setX(Double.parseDouble(insertOrder.getX()));
seg.setY(Double.parseDouble(insertOrder.getY()));
seg.setOrderId(insertOrder.getOrderId());
seg.setElapsed(insertOrder.getArriveElapsed());
seg.setDistance(insertOrder.getArriveDistance());
seg.setStart(insertOrder.getExpectTimeBegin());
seg.setEnd(insertOrder.getExpectTimeEnd());
orderSegments.add(seg);
orderSegments = orderSegments.stream().sorted(Comparator.comparing(OrderSegment::getStart)).collect(Collectors.toList());
// 工单排序
boolean isMove = false;
int moveTime = 0;
for (int i = 0; i < orderSegments.size(); i++) {
OrderSegment orderSegment = orderSegments.get(i);
Result<DistanceDTO> roadResult = pathService.getRoadDistance(insertOrder.getOrderId(), orderSegment.getOrderId(), engineer.getVehicle());
log.info("【getRoadDistance】参数------------->{},{},{}", insertOrder.getOrderId(), orderSegment.getOrderId(), engineer.getVehicle());
log.info("【getRoadDistance】结果------------->{}", JsonUtil.toJson(roadResult));
if (!roadResult.getCode().equals(ResultEnum.SUCCESS.getCode())) {
if (isMove && !orderSegment.getOrderId().equals(insertOrder.getOrderId()) && moveTime > 0) {
OrderInfoEntity nestOrder = orderInfoDao.getByOrderId(orderSegment.getOrderId());
nestOrder.setPlanStartTime(nestOrder.getPlanStartTime().plusMinutes(moveTime));
nestOrder.setPlanEndTime(nestOrder.getPlanEndTime().plusMinutes(moveTime));
orderInfoDao.save(nestOrder);
continue;
}
OrderSegment nestOrderSegment = orderSegments.get(i + 1);
//最近两单都不是需要插入的单直接跳过当前单
if (!orderSegment.getOrderId().equals(insertOrder.getOrderId()) && !nestOrderSegment.getOrderId().equals(insertOrder.getOrderId())) {
continue;
}
Result<DistanceDTO> roadResult = pathService.getRoadDistance(orderSegment.getOrderId(), nestOrderSegment.getOrderId(), engineer.getVehicle());
log.info("【getRoadDistance】参数------------->{},{},{}", orderSegment.getOrderId(), nestOrderSegment.getOrderId(), engineer.getVehicle());
log.info("【getRoadDistance】结果------------->{}", JsonUtil.toJson(roadResult));
DistanceDTO distanceDTO = roadResult.getResult();
// int roadTime = distanceDTO.getTime() / 60;
int roadTime = BigDecimal.valueOf(distanceDTO.getTime()).divide(BigDecimal.valueOf(60), 0, RoundingMode.HALF_UP).intValue();
// 先判断第一单开始时间
if (i == 0 && orderSegment.getStart().compareTo(workStartTime.plusMinutes(takeTime + roadTime)) >= 0) {
LocalDateTime planStartTime = handleLeaveTime(engineerLeaveTimeSlice, insertOrder.getExpectTimeBegin(), insertOrder.getExpectTimeEnd(), skillInfo.getTakeTime());
if (ObjectUtil.isNull(planStartTime)) {
if (orderSegment.getOrderId().equals(insertOrder.getOrderId())) {
Result handleLeaveTimeResult = handleLeaveTime(engineerLeaveTimeSlice, insertOrder.getExpectTimeBegin(), insertOrder.getExpectTimeEnd(), skillInfo.getTakeTime(), true, insertOrder);
if (!handleLeaveTimeResult.getCode().equals(ResultEnum.SUCCESS.getCode())) {
return Result.failed(insertOrder);
}
LocalDateTime planStartTime = (LocalDateTime) handleLeaveTimeResult.getResult();
insertOrder.setTakeTime(skillInfo.getTakeTime());
insertOrder.setPlanStartTime(planStartTime);
insertOrder.setPlanEndTime(planStartTime.plusMinutes(skillInfo.getTakeTime()));
OrderInfoEntity nestOrder = orderInfoDao.getByOrderId(orderSegment.getOrderId());
nestOrder.setArriveDistance(roadTime);
insertOrder.setArriveDistance(BigDecimal.valueOf(distanceDTO.getDis() * 1000).intValue());
OrderInfoEntity nestOrder = orderInfoDao.getByOrderId(nestOrderSegment.getOrderId());
nestOrder.setArriveElapsed(roadTime);
nestOrder.setArriveDistance(BigDecimal.valueOf(distanceDTO.getDis() * 1000).intValue());
if (orderSegment.getStart().plusMinutes(takeTime + roadTime).compareTo(nestOrder.getPlanStartTime()) > 0) {
long minutes = Duration.between(nestOrder.getPlanStartTime(), planStartTime.plusMinutes(skillInfo.getTakeTime() + roadTime)).toMinutes();
nestOrder.setPlanStartTime(nestOrder.getPlanStartTime().plusMinutes(minutes));
nestOrder.setPlanEndTime(nestOrder.getPlanEndTime().plusMinutes(minutes));
orderInfoDao.save(nestOrder);
moveTime = (int) minutes;
isMove = true;
continue;
}
orderInfoDao.save(nestOrder);
return Result.success(insertOrder);
}
// 判断当前是不是最后一单
if (i == orderSegments.size() - 1) {
// 最后一单如果没有符合条件的则代表没有合适时间
if (workEndTime.compareTo(orderSegment.getEnd().plusMinutes(takeTime + roadTime)) < 0) {
return Result.failed(insertOrder);
if (nestOrderSegment.getOrderId().equals(insertOrder.getOrderId())) {
Result handleLeaveTimeResult = handleLeaveTime(engineerLeaveTimeSlice, insertOrder.getExpectTimeBegin(), insertOrder.getExpectTimeEnd(), skillInfo.getTakeTime(), true, insertOrder);
if (!handleLeaveTimeResult.getCode().equals(ResultEnum.SUCCESS.getCode())) {
return handleLeaveTimeResult;
}
// 判断工单开始时间
LocalDateTime planStartTime = insertOrder.getExpectTimeBegin().compareTo(orderSegment.getEnd().plusMinutes(roadTime)) >= 0 ?
insertOrder.getExpectTimeBegin() : orderSegment.getEnd().plusMinutes(roadTime);
planStartTime = handleLeaveTime(engineerLeaveTimeSlice, planStartTime, insertOrder.getExpectTimeEnd(), skillInfo.getTakeTime());
if (ObjectUtil.isNull(planStartTime)) {
return Result.failed(insertOrder);
LocalDateTime planStartTime = (LocalDateTime) handleLeaveTimeResult.getResult();
if (orderSegment.getEnd().plusMinutes(roadTime).compareTo(planStartTime) <= 0) {
OrderInfoEntity nestOrder = orderInfoDao.getByOrderId(nestOrderSegment.getOrderId());
nestOrder.setArriveElapsed(roadTime);
nestOrder.setArriveDistance(BigDecimal.valueOf(distanceDTO.getDis() * 1000).intValue());
orderInfoDao.save(nestOrder);
continue;
}
insertOrder.setArriveElapsed(roadTime);
insertOrder.setArriveDistance(BigDecimal.valueOf(distanceDTO.getDis() * 1000).intValue());
insertOrder.setTakeTime(skillInfo.getTakeTime());
insertOrder.setPlanStartTime(planStartTime);
insertOrder.setPlanEndTime(planStartTime.plusMinutes(skillInfo.getTakeTime()));
return Result.success(insertOrder);
}
// 当前单在中间
OrderSegment nestOrderSegment = orderSegments.get(i + 1);
Result<DistanceDTO> roadResult2 = pathService.getRoadDistance(nestOrderSegment.getOrderId(), insertOrder.getOrderId(), engineer.getVehicle());
log.info("【getRoadDistance2】参数------------->{},{},{}", nestOrderSegment.getOrderId(), insertOrder.getOrderId(), engineer.getVehicle());
log.info("【getRoadDistance2】结果------------->{}", JsonUtil.toJson(roadResult2));
if (!roadResult.getCode().equals(ResultEnum.SUCCESS.getCode())) {
continue;
}
int roadTime2 = BigDecimal.valueOf(roadResult2.getResult().getTime()).divide(BigDecimal.valueOf(60), 0, RoundingMode.HALF_UP).intValue();
// 判断工单开始时间 todo 计算不准确 if (nestOrderSegment.getStart().minusMinutes(roadTime2).compareTo(planStartTime.plusMinutes(takeTime + roadTime)) >= 0) {
LocalDateTime planStartTime = insertOrder.getExpectTimeBegin().compareTo(orderSegment.getEnd().plusMinutes(roadTime)) >= 0 ?
insertOrder.getExpectTimeBegin() : orderSegment.getEnd().plusMinutes(roadTime);
if (nestOrderSegment.getStart().minusMinutes(roadTime2).compareTo(planStartTime.plusMinutes(takeTime + roadTime)) >= 0) {
planStartTime = handleLeaveTime(engineerLeaveTimeSlice, planStartTime, insertOrder.getExpectTimeEnd(), skillInfo.getTakeTime());
if (ObjectUtil.isNull(planStartTime)) {
return Result.failed(insertOrder);
if (i > 0) {
OrderSegment orderSegmentPrev = orderSegments.get(i - 1);
Result<DistanceDTO> roadResultPrev = pathService.getRoadDistance(orderSegmentPrev.getOrderId(), insertOrder.getOrderId(), engineer.getVehicle());
log.info("【getRoadDistancePrev】参数------------->{},{},{}", orderSegmentPrev.getOrderId(), insertOrder.getOrderId(), engineer.getVehicle());
log.info("【getRoadDistancePrev】结果------------->{}", JsonUtil.toJson(roadResultPrev));
int roadTimePrev = BigDecimal.valueOf(roadResultPrev.getResult().getTime()).divide(BigDecimal.valueOf(60), 0, RoundingMode.HALF_UP).intValue();
insertOrder.setArriveElapsed(roadTimePrev);
insertOrder.setArriveDistance(BigDecimal.valueOf(roadResultPrev.getResult().getDis() * 1000).intValue());
}
insertOrder.setArriveElapsed(roadTime);
insertOrder.setArriveDistance(BigDecimal.valueOf(distanceDTO.getDis() * 1000).intValue());
insertOrder.setTakeTime(skillInfo.getTakeTime());
insertOrder.setPlanStartTime(planStartTime);
insertOrder.setPlanEndTime(planStartTime.plusMinutes(skillInfo.getTakeTime()));
OrderInfoEntity nestOrder = orderInfoDao.getByOrderId(nestOrderSegment.getOrderId());
nestOrder.setArriveElapsed(roadTime2);
nestOrder.setArriveDistance(BigDecimal.valueOf(roadResult2.getResult().getDis() * 1000).intValue());
OrderInfoEntity nestOrder = orderInfoDao.getByOrderId(orderSegment.getOrderId());
nestOrder.setArriveElapsed(roadTime);
nestOrder.setArriveDistance(BigDecimal.valueOf(distanceDTO.getDis() * 1000).intValue());
long minutes = Duration.between(nestOrder.getPlanStartTime(), planStartTime.plusMinutes(skillInfo.getTakeTime() + roadTime)).toMinutes();
nestOrder.setPlanStartTime(nestOrder.getPlanStartTime().plusMinutes(minutes));
nestOrder.setPlanEndTime(nestOrder.getPlanEndTime().plusMinutes(minutes));
orderInfoDao.save(nestOrder);
return Result.success(insertOrder);
moveTime = (int) minutes;
isMove = true;
}
}
return Result.failed(insertOrder);
return Result.success(insertOrder);
}
/**
* 处理工程师请假时间和工单计划开始时间
*
......@@ -599,22 +614,64 @@ public class OrderInfoServiceImpl implements OrderInfoService {
* @param expectTimeEnd
* @return 工单计划开始时间
*/
private LocalDateTime handleLeaveTime(List<OccupyInfoDetail> engineerLeaveTimeSlice, LocalDateTime expectTimeBegin, LocalDateTime expectTimeEnd, Integer takeTime) {
private LocalDateTime handleLeaveTime(List<OccupyInfoDetail> engineerLeaveTimeSlice, LocalDateTime expectTimeBegin, LocalDateTime expectTimeEnd, Integer takeTime, Integer rodeTime, boolean isLeave) {
if (CollectionUtils.isEmpty(engineerLeaveTimeSlice)) {
return expectTimeBegin;
}
for (int i = 0; i < engineerLeaveTimeSlice.size(); i++) {
OccupyInfoDetail occupyInfoDetail = engineerLeaveTimeSlice.get(i);
// 处理请假开始时间
if (expectTimeBegin.plusMinutes(takeTime).compareTo(occupyInfoDetail.getBeginTime()) <= 0) {
// 请假之中
if (isLeave && (expectTimeBegin.compareTo(occupyInfoDetail.getBeginTime()) >= 0 || expectTimeBegin.plusMinutes(takeTime + rodeTime).compareTo(occupyInfoDetail.getBeginTime()) >= 0) &&
(expectTimeEnd.compareTo(occupyInfoDetail.getEndTime()) <= 0 || expectTimeEnd.plusMinutes(takeTime + rodeTime).compareTo(occupyInfoDetail.getEndTime()) <= 0)) {
return null;
}
// 请假开始之前能插入
if (expectTimeBegin.plusMinutes(takeTime + rodeTime).compareTo(occupyInfoDetail.getBeginTime()) <= 0) {
return expectTimeBegin;
}
if (expectTimeEnd.compareTo(occupyInfoDetail.getEndTime().plusMinutes(takeTime)) >= 0) {
// 请假时间内则顺延
if (expectTimeBegin.compareTo(occupyInfoDetail.getBeginTime()) >= 0 || expectTimeBegin.compareTo(occupyInfoDetail.getEndTime()) <= 0) {
return occupyInfoDetail.getEndTime();
}
if (expectTimeEnd.compareTo(occupyInfoDetail.getEndTime().plusMinutes(takeTime + rodeTime)) >= 0) {
return expectTimeBegin.compareTo(occupyInfoDetail.getEndTime()) >= 0 ? expectTimeBegin : occupyInfoDetail.getEndTime();
}
return null;
}
return null;
return expectTimeBegin;
}
/**
* 处理工程师请假时间和工单计划开始时间
*
* @param engineerLeaveTimeSlice
* @param expectTimeBegin
* @param expectTimeEnd
* @return 工单计划开始时间
*/
private Result<?> handleLeaveTime(List<OccupyInfoDetail> engineerLeaveTimeSlice, LocalDateTime expectTimeBegin, LocalDateTime expectTimeEnd, Integer takeTime, boolean isLeave, OrderInfoEntity insertOrder) {
if (CollectionUtils.isEmpty(engineerLeaveTimeSlice)) {
return Result.success(expectTimeBegin);
}
for (int i = 0; i < engineerLeaveTimeSlice.size(); i++) {
OccupyInfoDetail occupyInfoDetail = engineerLeaveTimeSlice.get(i);
// 请假之中
if (isLeave && (expectTimeBegin.compareTo(occupyInfoDetail.getBeginTime()) >= 0 || expectTimeBegin.plusMinutes(takeTime).compareTo(occupyInfoDetail.getBeginTime()) >= 0) &&
(expectTimeEnd.compareTo(occupyInfoDetail.getEndTime()) <= 0 || expectTimeEnd.plusMinutes(takeTime).compareTo(occupyInfoDetail.getEndTime()) <= 0)) {
return Result.failed(StatusCodeEnum.ENGINEER_IS_LEAVE_TIME);
}
// 请假开始之前能插入
if (expectTimeBegin.plusMinutes(takeTime).compareTo(occupyInfoDetail.getBeginTime()) <= 0) {
return Result.success(expectTimeBegin);
}
// 请假时间内则顺延
if (expectTimeBegin.compareTo(occupyInfoDetail.getBeginTime()) >= 0 || expectTimeBegin.compareTo(occupyInfoDetail.getEndTime()) <= 0) {
return Result.success(occupyInfoDetail.getEndTime());
}
if (expectTimeEnd.compareTo(occupyInfoDetail.getEndTime().plusMinutes(takeTime)) >= 0) {
return Result.success(expectTimeBegin.compareTo(occupyInfoDetail.getEndTime()) >= 0 ? expectTimeBegin : occupyInfoDetail.getEndTime());
}
}
return Result.success(expectTimeBegin);
}
/**
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!