Commit 102701f5 by Ren Ping

feat:自动排版优化

1 parent 2c85e672
...@@ -4,7 +4,6 @@ package com.dituhui.pea.dispatch.constraint; ...@@ -4,7 +4,6 @@ package com.dituhui.pea.dispatch.constraint;
* 约束枚举 * 约束枚举
* *
* @author zhangguoping * @author zhangguoping
*
*/ */
public enum ConstraintNameEnum { public enum ConstraintNameEnum {
...@@ -17,14 +16,22 @@ public enum ConstraintNameEnum { ...@@ -17,14 +16,22 @@ public enum ConstraintNameEnum {
* 订单到达时间跟时间窗吻合 * 订单到达时间跟时间窗吻合
*/ */
customerTimeWindowsMatch, customerTimeWindowsMatch,
/**
* 工单副工程师和主工程师必须同时存在
*/
customerSubAndMainEngineerMustCoexist,
/** /**
* 技术员不加班 * 技术员不加班
*/ */
technicianTimeWindowsMatch, technicianTimeWindowsMatch,
/** /**
* 技术员订单数量不超过最大值 * 技术员订单数量不超过最大值
*/ */
technicianCapacityMatch, technicianCapacityMatch,
/** /**
* 已分配和已排除技术员匹配 * 已分配和已排除技术员匹配
*/ */
......
...@@ -3,6 +3,7 @@ package com.dituhui.pea.dispatch.constraint; ...@@ -3,6 +3,7 @@ package com.dituhui.pea.dispatch.constraint;
import java.util.List; import java.util.List;
import cn.hutool.core.util.StrUtil; 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.buildin.hardsoftlong.HardSoftLongScore;
import org.optaplanner.core.api.score.stream.Constraint; import org.optaplanner.core.api.score.stream.Constraint;
import org.optaplanner.core.api.score.stream.ConstraintFactory; import org.optaplanner.core.api.score.stream.ConstraintFactory;
...@@ -26,6 +27,8 @@ public class DispatchConstraintProvider implements ConstraintProvider { ...@@ -26,6 +27,8 @@ public class DispatchConstraintProvider implements ConstraintProvider {
technicianCapacityMatch(factory), technicianCapacityMatch(factory),
skillMatch(factory), skillMatch(factory),
dispatchedMatch(factory), dispatchedMatch(factory),
//工单副工程师和主工程师必须同时存在
customerSubAndMainEngineerMustCoexist(factory),
// 软约束 // 软约束
technicianBalanceSoft(factory), technicianBalanceSoft(factory),
...@@ -55,6 +58,15 @@ public class DispatchConstraintProvider implements ConstraintProvider { ...@@ -55,6 +58,15 @@ public class DispatchConstraintProvider implements ConstraintProvider {
.asConstraint(ConstraintNameEnum.customerTimeWindowsMatch.name()); .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) { protected Constraint technicianTimeWindowsMatch(ConstraintFactory factory) {
return factory.forEach(Technician.class).filter( return factory.forEach(Technician.class).filter(
technician -> { technician -> {
...@@ -129,7 +141,9 @@ public class DispatchConstraintProvider implements ConstraintProvider { ...@@ -129,7 +141,9 @@ public class DispatchConstraintProvider implements ConstraintProvider {
protected Constraint skillMatch(ConstraintFactory factory) { protected Constraint skillMatch(ConstraintFactory factory) {
return factory.forEach(Customer.class) return factory.forEach(Customer.class)
.filter(customer -> customer.getTechnician() != null .filter(customer -> customer.getTechnician() != null
&& !customer.getCode().endsWith("_sub")
&& !customer.getTechnician().getSkills().contains(customer.getRequiredSkill())) && !customer.getTechnician().getSkills().contains(customer.getRequiredSkill()))
.penalizeLong(HardSoftLongScore.ONE_HARD, .penalizeLong(HardSoftLongScore.ONE_HARD,
// 技能匹配跟时间窗匹配存在很明显的跷跷板效应,权重小于3就会存在技能匹配问题 // 技能匹配跟时间窗匹配存在很明显的跷跷板效应,权重小于3就会存在技能匹配问题
// 3-技能匹配问题1个,时间窗问题8个 // 3-技能匹配问题1个,时间窗问题8个
......
...@@ -2,6 +2,7 @@ package com.dituhui.pea.dispatch.dao; ...@@ -2,6 +2,7 @@ package com.dituhui.pea.dispatch.dao;
import com.dituhui.pea.dispatch.entity.DispatchEngineer; import com.dituhui.pea.dispatch.entity.DispatchEngineer;
import com.dituhui.pea.dispatch.entity.DispatchOrder; 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.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
...@@ -58,8 +59,21 @@ public interface DispatchOrderRepository extends CrudRepository<DispatchOrder, L ...@@ -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" + " 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 (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 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 ", " order by a.expect_time_begin asc ",
nativeQuery = true) nativeQuery = true)
List<Map<String,Object>> getNewDispatchConfirmOrder(String groupId, String batchNo, String teamId, String batchDay); 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; ...@@ -3,9 +3,12 @@ package com.dituhui.pea.dispatch.dao;
import java.util.List; import java.util.List;
import com.dituhui.pea.dispatch.entity.OrderEvent; import com.dituhui.pea.dispatch.entity.OrderEvent;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import com.fasterxml.jackson.annotation.JsonFormat; 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; package com.dituhui.pea.dispatch.dao;
import com.dituhui.pea.dispatch.entity.OrderInfo; 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.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
...@@ -9,7 +10,7 @@ import java.util.List; ...@@ -9,7 +10,7 @@ import java.util.List;
import java.util.Optional; 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) // @Query(value = "SELECT * FROM order_info WHERE order_id=:orderId ORDER BY dt DESC LIMIT 1",nativeQuery = true)
......
...@@ -55,6 +55,11 @@ public class Customer { ...@@ -55,6 +55,11 @@ public class Customer {
private boolean isInTechnicianTimeWindows = true; private boolean isInTechnicianTimeWindows = true;
/**
* 主工程师工单
*/
private Customer mainCustomer;
public Customer() { public Customer() {
} }
...@@ -164,7 +169,8 @@ public class Customer { ...@@ -164,7 +169,8 @@ public class Customer {
@Override @Override
public int hashCode() { public int hashCode() {
return Long.valueOf(this.id).hashCode(); //return Long.valueOf(this.id).hashCode();
return this.code.hashCode();
} }
@Override @Override
...@@ -175,7 +181,8 @@ public class Customer { ...@@ -175,7 +181,8 @@ public class Customer {
return false; return false;
if (obj == this) if (obj == this)
return true; return true;
return this.id == ((Customer) obj).getId(); //return this.id == ((Customer) obj).getId();
return this.code.equals(((Customer) obj).getCode());
} }
@Override @Override
......
...@@ -26,6 +26,8 @@ public class AutoDispatchJob extends QuartzJobBean { ...@@ -26,6 +26,8 @@ public class AutoDispatchJob extends QuartzJobBean {
public static final String TEAM_JOB_PREFIX = "BOXI_TEAM_"; public static final String TEAM_JOB_PREFIX = "BOXI_TEAM_";
public static final String SUB_ORDER_ID_SUFFIX = "_sub";
@Resource @Resource
private SchedulerService schedulerService; private SchedulerService schedulerService;
......
...@@ -279,10 +279,14 @@ public class BatchServiceImpl implements BatchService { ...@@ -279,10 +279,14 @@ public class BatchServiceImpl implements BatchService {
dispatchOrderRepository.saveAll(JSONObject.parseArray(JSONObject.toJSONString(orderConfirmList), DispatchOrder.class)); dispatchOrderRepository.saveAll(JSONObject.parseArray(JSONObject.toJSONString(orderConfirmList), DispatchOrder.class));
int orderConfirmCount = orderConfirmList.size(); 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=?", 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); engCount, orderCount + orderConfirmCount, LocalDateTime.now(), cutOff ? LocalDateTime.now() : null, teamId, batchNo);
} else { } else {
......
...@@ -10,6 +10,7 @@ import com.dituhui.pea.dispatch.dao.*; ...@@ -10,6 +10,7 @@ import com.dituhui.pea.dispatch.dao.*;
import com.dituhui.pea.dispatch.entity.*; import com.dituhui.pea.dispatch.entity.*;
import com.dituhui.pea.dispatch.enums.DepartureEnum; import com.dituhui.pea.dispatch.enums.DepartureEnum;
import com.dituhui.pea.dispatch.pojo.*; 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.EngineerCalendarService;
import com.dituhui.pea.dispatch.service.ExtractService; import com.dituhui.pea.dispatch.service.ExtractService;
import com.dituhui.pea.dispatch.service.SolveService; import com.dituhui.pea.dispatch.service.SolveService;
...@@ -35,6 +36,7 @@ import java.time.LocalDateTime; ...@@ -35,6 +36,7 @@ import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
...@@ -260,6 +262,7 @@ public class SolveServiceImpl implements SolveService { ...@@ -260,6 +262,7 @@ public class SolveServiceImpl implements SolveService {
} }
Customer customer = new Customer(order.getId(), order.getOrderId(), order.getDt(), location, start, end, order.getSkills(), order.getTakeTime(), order.getStatus()); Customer customer = new Customer(order.getId(), order.getOrderId(), order.getDt(), location, start, end, order.getSkills(), order.getTakeTime(), order.getStatus());
if (!order.getOrderId().endsWith("_sub")) {
OrderInfo orderInfo = orderInfoRepository.findByOrderId(order.getOrderId()).get(0); OrderInfo orderInfo = orderInfoRepository.findByOrderId(order.getOrderId()).get(0);
if ((StrUtil.equals("MANUAL", orderInfo.getAppointmentMethod()) if ((StrUtil.equals("MANUAL", orderInfo.getAppointmentMethod())
&& StrUtil.equals("CONFIRM", orderInfo.getAppointmentStatus())) && StrUtil.equals("CONFIRM", orderInfo.getAppointmentStatus()))
...@@ -276,10 +279,19 @@ public class SolveServiceImpl implements SolveService { ...@@ -276,10 +279,19 @@ public class SolveServiceImpl implements SolveService {
"订单指定排除工程师, teamId:{}, batchNo:{}, orderId:{}, dispatchedTechnicianCode:{}, exclusiveTechnicianCode:{}", "订单指定排除工程师, teamId:{}, batchNo:{}, orderId:{}, dispatchedTechnicianCode:{}, exclusiveTechnicianCode:{}",
teamId, batchNo, order.getOrderId(), customer.getDispatchedTechnicianCode(), teamId, batchNo, order.getOrderId(), customer.getDispatchedTechnicianCode(),
customer.getExclusiveTechnicianCode()); customer.getExclusiveTechnicianCode());
}
customerList.add(customer); 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()); log.info("组织问题对象, customer-list, teamId:{}, batchNo:{}, customer-list:{}", teamId, batchNo, customerList.size());
// technicianList // technicianList
......
...@@ -443,6 +443,30 @@ public class DispatchSolutionUtils { ...@@ -443,6 +443,30 @@ public class DispatchSolutionUtils {
} }
} }
break; 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: default:
break; break;
} }
......
...@@ -3,7 +3,7 @@ server: ...@@ -3,7 +3,7 @@ server:
dispatch: dispatch:
cron: cron:
expr: 0 57 8-23 * * ? expr: 0 22 8-23 * * ?
next-day-limit: 2 next-day-limit: 2
scheduler: scheduler:
......
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!