Commit 102701f5 by Ren Ping

feat:自动排版优化

1 parent 2c85e672
......@@ -2,46 +2,53 @@ package com.dituhui.pea.dispatch.constraint;
/**
* 约束枚举
*
* @author zhangguoping
*
* @author zhangguoping
*/
public enum ConstraintNameEnum {
// 硬约束
/**
* 技术员技能跟订单相匹配
*/
skillMatch,
/**
* 订单到达时间跟时间窗吻合
*/
customerTimeWindowsMatch,
/**
* 技术员不加班
*/
technicianTimeWindowsMatch,
/**
* 技术员订单数量不超过最大值
*/
technicianCapacityMatch,
/**
* 已分配和已排除技术员匹配
*/
dispatchedMatch,
// 软约束
/**
* 订单数量均衡
*/
technicianBalanceSoft,
/**
* 总路程最小
*/
totalDistance,
/**
* 技术员中心点偏好
*/
preferredTotalDistance
// 硬约束
/**
* 技术员技能跟订单相匹配
*/
skillMatch,
/**
* 订单到达时间跟时间窗吻合
*/
customerTimeWindowsMatch,
/**
* 工单副工程师和主工程师必须同时存在
*/
customerSubAndMainEngineerMustCoexist,
/**
* 技术员不加班
*/
technicianTimeWindowsMatch,
/**
* 技术员订单数量不超过最大值
*/
technicianCapacityMatch,
/**
* 已分配和已排除技术员匹配
*/
dispatchedMatch,
// 软约束
/**
* 订单数量均衡
*/
technicianBalanceSoft,
/**
* 总路程最小
*/
totalDistance,
/**
* 技术员中心点偏好
*/
preferredTotalDistance
}
......@@ -3,6 +3,7 @@ package com.dituhui.pea.dispatch.constraint;
import java.util.List;
import cn.hutool.core.util.StrUtil;
import com.dituhui.pea.dispatch.quartz.dispatch.AutoDispatchJob;
import org.optaplanner.core.api.score.buildin.hardsoftlong.HardSoftLongScore;
import org.optaplanner.core.api.score.stream.Constraint;
import org.optaplanner.core.api.score.stream.ConstraintFactory;
......@@ -26,6 +27,8 @@ public class DispatchConstraintProvider implements ConstraintProvider {
technicianCapacityMatch(factory),
skillMatch(factory),
dispatchedMatch(factory),
//工单副工程师和主工程师必须同时存在
customerSubAndMainEngineerMustCoexist(factory),
// 软约束
technicianBalanceSoft(factory),
......@@ -55,6 +58,15 @@ public class DispatchConstraintProvider implements ConstraintProvider {
.asConstraint(ConstraintNameEnum.customerTimeWindowsMatch.name());
}
//工单副工程师和主工程师必须同时存在
public Constraint customerSubAndMainEngineerMustCoexist(ConstraintFactory factory) {
return factory.forEach(Customer.class).filter(customer -> customer.getCode().endsWith(AutoDispatchJob.SUB_ORDER_ID_SUFFIX)
&& null != customer.getTechnician()
&& null != customer.getMainCustomer()
&& null == customer.getMainCustomer().getTechnician())
.penalizeLong(HardSoftLongScore.ONE_HARD, Customer -> 1).asConstraint(ConstraintNameEnum.customerSubAndMainEngineerMustCoexist.name());
}
protected Constraint technicianTimeWindowsMatch(ConstraintFactory factory) {
return factory.forEach(Technician.class).filter(
technician -> {
......@@ -129,7 +141,9 @@ public class DispatchConstraintProvider implements ConstraintProvider {
protected Constraint skillMatch(ConstraintFactory factory) {
return factory.forEach(Customer.class)
.filter(customer -> customer.getTechnician() != null
&& !customer.getCode().endsWith("_sub")
&& !customer.getTechnician().getSkills().contains(customer.getRequiredSkill()))
.penalizeLong(HardSoftLongScore.ONE_HARD,
// 技能匹配跟时间窗匹配存在很明显的跷跷板效应,权重小于3就会存在技能匹配问题
// 3-技能匹配问题1个,时间窗问题8个
......
......@@ -2,6 +2,7 @@ package com.dituhui.pea.dispatch.dao;
import com.dituhui.pea.dispatch.entity.DispatchEngineer;
import com.dituhui.pea.dispatch.entity.DispatchOrder;
import com.dituhui.pea.dispatch.quartz.dispatch.AutoDispatchJob;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
......@@ -58,8 +59,21 @@ public interface DispatchOrderRepository extends CrudRepository<DispatchOrder, L
" where a.org_team_id=?3 and a.dt = ?4 and bean_status='OPEN'\n" +
" and (appointment_method like 'AUTO%' or appointment_method='MANUAL') and a.appointment_status in ('CONFIRM')\n" +
" and order_status in ('NORMAL','RESCHEDULED') and service_status='INIT'\n" +
" and (a.is_multiple<>1 or a.engineer_code_sub is null)" + //指定了辅助工程师的不参与自动派单
" order by a.expect_time_begin asc ",
nativeQuery = true)
List<Map<String,Object>> getNewDispatchConfirmOrder(String groupId, String batchNo, String teamId, String batchDay);
@Query(value = " select ?1 group_id, ?2 batch_no, a.org_team_id team_id, concat(a.order_id,'"+ AutoDispatchJob.SUB_ORDER_ID_SUFFIX +"')order_id, date_format(a.dt,'%Y-%m-%d') dt, a.x, a.y , \n" +
" a.expect_time_begin, DATE_ADD(a.expect_time_end, INTERVAL 15 MINUTE) expect_time_end, a.tags, a.priority , \n" +
" '' skills , 15 take_time, a.appointment_status status, \n" +
" a.engineer_code_sub engineer_code, date_format(a.sub_plan_start_time,'%Y-%m-%d %H:%i:%s') time_begin, date_format(a.sub_plan_end_time,'%Y-%m-%d %H:%i:%s') time_end \n" +
" from order_info a \n" +
" where a.org_team_id=?3 and a.dt = ?4 and bean_status='OPEN'\n" +
" and (appointment_method like 'AUTO%' or appointment_method='MANUAL') and a.appointment_status in ('INIT', 'PRE', 'CONFIRM')\n" +
" and order_status in ('NORMAL','RESCHEDULED') and service_status='INIT'\n" +
" and (a.is_multiple=1 and a.engineer_code is not null and a.engineer_code !='' and a.engineer_code_sub is not null and a.engineer_code_sub !='')\n" +
" order by a.expect_time_begin asc ",
nativeQuery = true)
List<Map<String,Object>> getNewDispatchSubOrder(String groupId, String batchNo, String teamId, String batchDay);
}
\ No newline at end of file
......@@ -3,9 +3,12 @@ package com.dituhui.pea.dispatch.dao;
import java.util.List;
import com.dituhui.pea.dispatch.entity.OrderEvent;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
import com.fasterxml.jackson.annotation.JsonFormat;
public interface OrderEventRepository extends CrudRepository<OrderEvent, Long> {
public interface OrderEventRepository extends CrudRepository<OrderEvent, Long>, JpaRepository<OrderEvent, Long> {
long countByOrderIdAndEventLike(String orderId, String event);
}
package com.dituhui.pea.dispatch.dao;
import com.dituhui.pea.dispatch.entity.OrderInfo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
......@@ -9,16 +10,16 @@ import java.util.List;
import java.util.Optional;
public interface OrderInfoRepository extends CrudRepository<OrderInfo, Long> {
public interface OrderInfoRepository extends CrudRepository<OrderInfo, Long>, JpaRepository<OrderInfo, Long> {
// @Query(value = "SELECT * FROM order_info WHERE order_id=:orderId ORDER BY dt DESC LIMIT 1",nativeQuery = true)
Optional<OrderInfo> findOrderInfoByOrderIdAndDt(String orderId, LocalDate dt);
List<OrderInfo> findByOrderId(String orderId);
List<OrderInfo> findByOrgTeamIdAndDt(String teamId, LocalDate dt);
List<OrderInfo> findByOrgGroupIdAndDt(String groupId, LocalDate dt);
}
......@@ -26,7 +26,7 @@ public class DispatchOrder implements Serializable {
@Column(name = "group_id")
private String groupId;
@Column(name = "batch_no")
private String batchNo;
......
......@@ -55,6 +55,11 @@ public class Customer {
private boolean isInTechnicianTimeWindows = true;
/**
* 主工程师工单
*/
private Customer mainCustomer;
public Customer() {
}
......@@ -164,7 +169,8 @@ public class Customer {
@Override
public int hashCode() {
return Long.valueOf(this.id).hashCode();
//return Long.valueOf(this.id).hashCode();
return this.code.hashCode();
}
@Override
......@@ -175,7 +181,8 @@ public class Customer {
return false;
if (obj == this)
return true;
return this.id == ((Customer) obj).getId();
//return this.id == ((Customer) obj).getId();
return this.code.equals(((Customer) obj).getCode());
}
@Override
......
......@@ -26,6 +26,8 @@ public class AutoDispatchJob extends QuartzJobBean {
public static final String TEAM_JOB_PREFIX = "BOXI_TEAM_";
public static final String SUB_ORDER_ID_SUFFIX = "_sub";
@Resource
private SchedulerService schedulerService;
......
......@@ -279,10 +279,14 @@ public class BatchServiceImpl implements BatchService {
dispatchOrderRepository.saveAll(JSONObject.parseArray(JSONObject.toJSONString(orderConfirmList), DispatchOrder.class));
int orderConfirmCount = orderConfirmList.size();
log.info("准备批次数据 engCount:{}, orderCount:{}, orderConfirmCount:{}", engCount, orderCount, orderConfirmCount);
List<Map<String, Object>> orderSubList = dispatchOrderRepository.getNewDispatchSubOrder(groupId, batchNo, teamId, batchDay);
dispatchOrderRepository.saveAll(JSONObject.parseArray(JSONObject.toJSONString(orderSubList), DispatchOrder.class));
int orderSubCount = orderSubList.size();
log.info("准备批次数据 engCount:{}, orderCount:{}, orderConfirmCount:{}, orderSubCount:{}", engCount, orderCount, orderConfirmCount,orderSubCount);
if (orderCount + orderConfirmCount > 0) {
if (orderCount + orderConfirmCount + orderSubCount > 0) {
jdbcTemplate.update("update dispatch_batch set engineer_num=? , order_num=?, start_time=?, end_time=null, status='RUNNING', cutoffed_time=? where team_id=? and batch_no=?",
engCount, orderCount + orderConfirmCount, LocalDateTime.now(), cutOff ? LocalDateTime.now() : null, teamId, batchNo);
} else {
......
......@@ -10,6 +10,7 @@ import com.dituhui.pea.dispatch.dao.*;
import com.dituhui.pea.dispatch.entity.*;
import com.dituhui.pea.dispatch.enums.DepartureEnum;
import com.dituhui.pea.dispatch.pojo.*;
import com.dituhui.pea.dispatch.quartz.dispatch.AutoDispatchJob;
import com.dituhui.pea.dispatch.service.EngineerCalendarService;
import com.dituhui.pea.dispatch.service.ExtractService;
import com.dituhui.pea.dispatch.service.SolveService;
......@@ -35,6 +36,7 @@ import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
......@@ -260,26 +262,36 @@ public class SolveServiceImpl implements SolveService {
}
Customer customer = new Customer(order.getId(), order.getOrderId(), order.getDt(), location, start, end, order.getSkills(), order.getTakeTime(), order.getStatus());
OrderInfo orderInfo = orderInfoRepository.findByOrderId(order.getOrderId()).get(0);
if ((StrUtil.equals("MANUAL", orderInfo.getAppointmentMethod())
&& StrUtil.equals("CONFIRM", orderInfo.getAppointmentStatus()))
|| ObjectUtil.equal(1, orderInfo.getIsAppointEngineer())) {
customer.setDispatchedTechnicianCode(orderInfo.getEngineerCode());
}
if (!order.getOrderId().endsWith("_sub")) {
OrderInfo orderInfo = orderInfoRepository.findByOrderId(order.getOrderId()).get(0);
if ((StrUtil.equals("MANUAL", orderInfo.getAppointmentMethod())
&& StrUtil.equals("CONFIRM", orderInfo.getAppointmentStatus()))
|| ObjectUtil.equal(1, orderInfo.getIsAppointEngineer())) {
customer.setDispatchedTechnicianCode(orderInfo.getEngineerCode());
}
if (ObjectUtil.equal(2, orderInfo.getIsAppointEngineer())) {
customer.setExclusiveTechnicianCode(Objects.nonNull(orderInfo.getAppointEngineerCodes()) ?
orderInfo.getAppointEngineerCodes().trim() : null);
}
if (ObjectUtil.equal(2, orderInfo.getIsAppointEngineer())) {
customer.setExclusiveTechnicianCode(Objects.nonNull(orderInfo.getAppointEngineerCodes()) ?
orderInfo.getAppointEngineerCodes().trim() : null);
}
log.info(
"订单指定排除工程师, teamId:{}, batchNo:{}, orderId:{}, dispatchedTechnicianCode:{}, exclusiveTechnicianCode:{}",
teamId, batchNo, order.getOrderId(), customer.getDispatchedTechnicianCode(),
customer.getExclusiveTechnicianCode());
log.info(
"订单指定排除工程师, teamId:{}, batchNo:{}, orderId:{}, dispatchedTechnicianCode:{}, exclusiveTechnicianCode:{}",
teamId, batchNo, order.getOrderId(), customer.getDispatchedTechnicianCode(),
customer.getExclusiveTechnicianCode());
}
customerList.add(customer);
});
Map<String,Customer> customerCodeMap= customerList.stream().collect(Collectors.toMap(Customer::getCode, Function.identity()));
customerList.forEach(customer -> {
if(customer.getCode().endsWith(AutoDispatchJob.SUB_ORDER_ID_SUFFIX)){
String code=customer.getCode();
customer.setMainCustomer(customerCodeMap.get(code.substring(0,code.length()-AutoDispatchJob.SUB_ORDER_ID_SUFFIX.length())));
}
});
log.info("组织问题对象, customer-list, teamId:{}, batchNo:{}, customer-list:{}", teamId, batchNo, customerList.size());
// technicianList
......
......@@ -443,6 +443,30 @@ public class DispatchSolutionUtils {
}
}
break;
case customerSubAndMainEngineerMustCoexist:
for (Object indictedObject : constraintMatch.getIndictedObjectList()) {
// 违反硬约束对象,根据具体约束返回不同类型对象
if (indictedObject instanceof Customer) {
Customer customer = (Customer) indictedObject;
log.warn(">>> 副工单({})违背 customerSubAndMainEngineerMustCoexist 硬约束: 主工单:{}, 主工程师: {}, 副工程师: {}, arriveTime: {},departureTime: {}",
customer.getCode(),
customer.getMainCustomer().getCode(),
null != customer.getMainCustomer().getTechnician() ? customer.getMainCustomer().getTechnician().getCode() : null,
null != customer.getTechnician() ? customer.getTechnician().getCode() : null,
customer.getArrivalTime(),
customer.getDepartureTime());
List<Customer> removeCustomers = CollectionUtil.toList(customer,customer.getMainCustomer());
removeCustomers.forEach(customer1 -> {
// 更新shadow变量
updateShadowVariable(customer1);
// 移除技术员
customer1.getTechnician().getCustomerList().remove(customer1);
solution.getUnDispatchedCustomers().add(customer1);
});
}
}
break;
default:
break;
}
......
......@@ -3,7 +3,7 @@ server:
dispatch:
cron:
expr: 0 57 8-23 * * ?
expr: 0 22 8-23 * * ?
next-day-limit: 2
scheduler:
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!