Commit 6d256b00 by Ren Ping

feat:自动派工任务算法优化

3.2,算法需要带入每个工程师的工作日历,支持工作队的关闭工作日配置
3.5,自动排班需要考虑已经分配人的订单,调整人工单的时间计划
3.6,排除指定工程师逻辑处理
3.7,技术员可以从家庭地址出发
1 parent c8621058
...@@ -16,10 +16,13 @@ public interface DispatchEngineerRepository extends CrudRepository<DispatchEngin ...@@ -16,10 +16,13 @@ public interface DispatchEngineerRepository extends CrudRepository<DispatchEngin
List<DispatchEngineer> findByTeamIdAndBatchNo(String teamId, String batchNo); List<DispatchEngineer> findByTeamIdAndBatchNo(String teamId, String batchNo);
@Query(value = "SELECT ?1 group_id, o.team_id,?2 batch_no,o.engineer_code, a.name engineer_name, b.x, b.y , max_num, max_minute, max_distance, b.vehicle vehicle_type FROM `org_team_engineer` o\n" @Query(value = "SELECT ?1 group_id, o.team_id,?2 batch_no,o.engineer_code, a.name engineer_name,"
+ " b.x, b.y , max_num, max_minute, max_distance, b.vehicle vehicle_type, b.departure, a.work_address,a.work_x,a.work_y"
+ " FROM `org_team_engineer` o\n"
+ " join engineer_info a on o.engineer_code=a.engineer_code\n" + " join engineer_info a on o.engineer_code=a.engineer_code\n"
+ " left join engineer_business b on a.engineer_code = b.engineer_code\n" + " join engineer_business b on a.engineer_code = b.engineer_code\n"
+ " WHERE o.team_id=?3 AND o.`status`=1\n", + " WHERE o.team_id=?3 AND o.`status`=1\n"
+ " and (b.departure!=3 or (a.work_x is not null and a.work_y is not null))",
nativeQuery = true) nativeQuery = true)
List<Map<String,Object>> getNewDispatchEngineer(String groupId, String batchNo, String teamId); List<Map<String,Object>> getNewDispatchEngineer(String groupId, String batchNo, String teamId);
} }
\ No newline at end of file
...@@ -43,7 +43,7 @@ public interface DispatchOrderRepository extends CrudRepository<DispatchOrder, L ...@@ -43,7 +43,7 @@ public interface DispatchOrderRepository extends CrudRepository<DispatchOrder, L
+ " a.expect_time_begin, a.expect_time_end, a.tags, a.priority , \n" + " a.expect_time_begin, a.expect_time_end, a.tags, a.priority , \n"
+ " CONCAT(a.brand, '-', a.type, '-', a.skill) skills , a.take_time , a.appointment_status status\n" + " CONCAT(a.brand, '-', a.type, '-', a.skill) skills , a.take_time , a.appointment_status status\n"
+ " FROM order_info a \n" + " WHERE a.org_team_id=?3 AND a.dt = ?4 AND bean_status='OPEN'\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%' AND a.appointment_status IN ('INIT', 'PRE')\n" + " AND (appointment_method like 'AUTO%' or appointment_method='MANUAL') AND a.appointment_status IN ('INIT', 'PRE')\n"
+ " AND order_status ='NORMAL' AND service_status='INIT'\n" + " AND order_status ='NORMAL' AND service_status='INIT'\n"
+ " ORDER BY a.expect_time_begin ASC \n", + " ORDER BY a.expect_time_begin ASC \n",
nativeQuery = true) nativeQuery = true)
...@@ -57,7 +57,7 @@ public interface DispatchOrderRepository extends CrudRepository<DispatchOrder, L ...@@ -57,7 +57,7 @@ public interface DispatchOrderRepository extends CrudRepository<DispatchOrder, L
" a.engineer_code, date_format(a.plan_start_time,'%Y-%m-%d %H:%i:%s') time_begin, date_format(a.plan_end_time,'%Y-%m-%d %H:%i:%s') time_end \n" + " a.engineer_code, date_format(a.plan_start_time,'%Y-%m-%d %H:%i:%s') time_begin, date_format(a.plan_end_time,'%Y-%m-%d %H:%i:%s') time_end \n" +
" from order_info a \n" + " from order_info a \n" +
" 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%' 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 ='NORMAL' and service_status='INIT'\n" + " and order_status ='NORMAL' and service_status='INIT'\n" +
" order by a.expect_time_begin asc ", " order by a.expect_time_begin asc ",
nativeQuery = true) nativeQuery = true)
......
package com.dituhui.pea.dispatch.dao;
import com.dituhui.pea.dispatch.entity.OrgWarehouseInfoEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface OrgWarehouseInfoDao extends JpaRepository<OrgWarehouseInfoEntity, Long> {
OrgWarehouseInfoEntity findByWarehouseId(String wareHouseId);
}
...@@ -65,4 +65,22 @@ public class DispatchEngineer implements Serializable { ...@@ -65,4 +65,22 @@ public class DispatchEngineer implements Serializable {
@Column(name = "update_time") @Column(name = "update_time")
private LocalDateTime updateTime; private LocalDateTime updateTime;
/**
* 常规出发地,1配件仓,3工作地址 默认工作地址
*/
@Column(name = "departure")
private Integer departure;
/**
* 工作地址经度
*/
@Column(name = "work_x")
private String workX;
/**
* 工作地址纬度
*/
@Column(name = "work_y")
private String workY;
} }
\ No newline at end of file
...@@ -64,4 +64,22 @@ public class EngineerInfo implements Serializable { ...@@ -64,4 +64,22 @@ public class EngineerInfo implements Serializable {
@Column(name = "update_time") @Column(name = "update_time")
private LocalDateTime updateTime; private LocalDateTime updateTime;
/**
* 工作地址经度
*/
@Column(name = "work_address")
private String workAddress;
/**
* 工作地址经度
*/
@Column(name = "work_x")
private String workX;
/**
* 工作地址纬度
*/
@Column(name = "work_y")
private String workY;
} }
package com.dituhui.pea.dispatch.entity;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.time.LocalDateTime;
@Data
@Entity
@Table(name = "org_warehouse_info")
public class OrgWarehouseInfoEntity {
private static final long serialVersionUID = 1L;
@Id
private Integer id;
/**
* 配件仓id
*/
private String warehouseId;
/**
* 配件仓名称
*/
private String warehouseName;
/**
* 归属分部
*/
private String branchId;
/**
* 归属分站
*/
private String groupId;
/**
* 所在城市id
*/
private String cityCode;
/**
* 配件仓
*/
private String address;
/**
* 办公地址经度
*/
private String x;
/**
* 办公地址纬度
*/
private String y;
/**
* 配件到达时间
*/
private String arriveTime;
/**
* 配件到达时间
*/
private String cutoffTime;
/**
* 类型(1分部仓-可分发,2小组仓-快递柜)
*/
private int kind;
/**
* 管理员姓名
*/
private String managerName;
/**
* 管理员手机号码
*/
private String managerPhone;
/**
* 备注
*/
private String memo;
/**
* 创建时间
*/
private LocalDateTime createTime = LocalDateTime.now();
/**
* 更新时间
*/
private LocalDateTime updateTime = LocalDateTime.now();
}
package com.dituhui.pea.dispatch.enums;
import cn.hutool.core.util.ObjectUtil;
import java.util.Objects;
/**
* 常规出发地,1配件仓,3工作地址
*
* @author RenPing
* @date 2023/10/24
*/
public enum DepartureEnum {
WARE_HOUSE(1, "配件仓"),
WORK_ADDR(3, "工作地址");
private Integer code;
private String name;
private DepartureEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
public Integer getCode() {
return code;
}
public String getName() {
return name;
}
public static String getNameByValue(Integer value) {
if (Objects.isNull(value)) {
return null;
}
for (DepartureEnum enums : DepartureEnum.values()) {
if (ObjectUtil.equal(enums.getCode(), value)) {
return enums.getName();
}
}
return null;
}
}
...@@ -100,6 +100,20 @@ public class Technician { ...@@ -100,6 +100,20 @@ public class Technician {
this.preferredlocationDistanceMap = preferredlocationDistanceMap; this.preferredlocationDistanceMap = preferredlocationDistanceMap;
} }
public Technician(long id, String code, int maxCount, int maxMinute, int maxDistanceMeter, Integer vehicleType, Depot depot,
int[][] timeWindows, Set<String> skills, Map<String, Long> preferredlocationDistanceMap) {
this.id = id;
this.code = code;
this.maxCount = maxCount;
this.maxMinute = maxMinute;
this.maxDistanceMeter = maxDistanceMeter;
this.vehicleType = vehicleType;
this.depot = depot;
this.timeWindows = timeWindows;
this.skills = skills;
this.preferredlocationDistanceMap = preferredlocationDistanceMap;
}
// ************************************************************************ // ************************************************************************
// Complex methods // Complex methods
// ************************************************************************ // ************************************************************************
......
...@@ -73,7 +73,7 @@ public class BatchScheduler { ...@@ -73,7 +73,7 @@ public class BatchScheduler {
UUID problemId = solveService.generateProblemId(teamId, batchNo); UUID problemId = solveService.generateProblemId(teamId, batchNo);
log.info("dispatchRun teamId:{}, day:{}, batch:{}, problemId:{}", teamId, currDay, batchNo, problemId); log.info("dispatchRun teamId:{}, day:{}, batch:{}, problemId:{}", teamId, currDay, batchNo, problemId);
DispatchSolution problem = solveService.prepareSolution2(teamId, batchNo); DispatchSolution problem = solveService.prepareSolution2(teamId, batchNo, currDay);
if (problem.getCustomerList().size() <= 0) { if (problem.getCustomerList().size() <= 0) {
log.info("dispatchRun 当前批次没有待指派工单 , teamId:{}, day:{}, batch:{}, problemId:{}, order-size:{}", teamId, currDay, batchNo, problemId, problem.getCustomerList().size()); log.info("dispatchRun 当前批次没有待指派工单 , teamId:{}, day:{}, batch:{}, problemId:{}, order-size:{}", teamId, currDay, batchNo, problemId, problem.getCustomerList().size());
......
...@@ -25,7 +25,7 @@ public interface SolveService { ...@@ -25,7 +25,7 @@ public interface SolveService {
DispatchSolution prepareSolution(String groupId, String batchNo) ; DispatchSolution prepareSolution(String groupId, String batchNo) ;
DispatchSolution prepareSolution2(String teamId, String batchNo) ; DispatchSolution prepareSolution2(String teamId, String batchNo, String currDay) ;
/* /*
......
...@@ -129,7 +129,7 @@ public class SchedulerServiceImpl implements SchedulerService { ...@@ -129,7 +129,7 @@ public class SchedulerServiceImpl implements SchedulerService {
UUID problemId = solveService.generateProblemId(teamId, batchNo); UUID problemId = solveService.generateProblemId(teamId, batchNo);
log.info("dispatchRun teamId:{}, day:{}, batch:{}, problemId:{}", teamId, currDay, batchNo, problemId); log.info("dispatchRun teamId:{}, day:{}, batch:{}, problemId:{}", teamId, currDay, batchNo, problemId);
DispatchSolution problem = solveService.prepareSolution2(teamId, batchNo); DispatchSolution problem = solveService.prepareSolution2(teamId, batchNo, currDay);
if (problem.getCustomerList().size() <= 0) { if (problem.getCustomerList().size() <= 0) {
log.info("dispatchRun 当前批次没有待指派工单 , teamId:{}, day:{}, batch:{}, problemId:{}, order-size:{}", teamId, currDay, batchNo, problemId, problem.getCustomerList().size()); log.info("dispatchRun 当前批次没有待指派工单 , teamId:{}, day:{}, batch:{}, problemId:{}, order-size:{}", teamId, currDay, batchNo, problemId, problem.getCustomerList().size());
......
package com.dituhui.pea.dispatch.service.impl; package com.dituhui.pea.dispatch.service.impl;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.SecureUtil;
import com.dituhui.pea.dispatch.common.GeoDistanceCalculator; import com.dituhui.pea.dispatch.common.GeoDistanceCalculator;
import com.dituhui.pea.dispatch.constraint.DispatchConstraintProvider; import com.dituhui.pea.dispatch.constraint.DispatchConstraintProvider;
import com.dituhui.pea.dispatch.dao.*; import com.dituhui.pea.dispatch.dao.*;
import com.dituhui.pea.dispatch.entity.DispatchOrder; import com.dituhui.pea.dispatch.entity.*;
import com.dituhui.pea.dispatch.entity.OrderInfo; import com.dituhui.pea.dispatch.enums.DepartureEnum;
import com.dituhui.pea.dispatch.entity.OrgGroup;
import com.dituhui.pea.dispatch.entity.OrgTeamEntity;
import com.dituhui.pea.dispatch.pojo.*; import com.dituhui.pea.dispatch.pojo.*;
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;
import com.dituhui.pea.dispatch.utils.DispatchSolutionUtils; import com.dituhui.pea.dispatch.utils.DispatchSolutionUtils;
...@@ -77,6 +77,12 @@ public class SolveServiceImpl implements SolveService { ...@@ -77,6 +77,12 @@ public class SolveServiceImpl implements SolveService {
@Autowired @Autowired
OrgTeamDao orgTeamDao; OrgTeamDao orgTeamDao;
@Autowired
private OrgWarehouseInfoDao orgWarehouseInfoDao;
@Autowired
private EngineerCalendarService engineerCalendarService;
// 查询技术员所有技能集 // 查询技术员所有技能集
private List<String> queryEngineerSkills(String engineerCode) { private List<String> queryEngineerSkills(String engineerCode) {
String sql = "select concat( b.brand, '-', b.type, '-', b.skill) as skill from engineer_skill_group a left join skill_info b \n" + " on a.skill_group_code= b.skill_group_code where a.engineer_code=? and a.status=1 \n" + " and b.brand is not null "; String sql = "select concat( b.brand, '-', b.type, '-', b.skill) as skill from engineer_skill_group a left join skill_info b \n" + " on a.skill_group_code= b.skill_group_code where a.engineer_code=? and a.status=1 \n" + " and b.brand is not null ";
...@@ -195,7 +201,7 @@ public class SolveServiceImpl implements SolveService { ...@@ -195,7 +201,7 @@ public class SolveServiceImpl implements SolveService {
// 按小队,批号组装问题对象 // 按小队,批号组装问题对象
@Override @Override
public DispatchSolution prepareSolution2(String teamId, String batchNo) { public DispatchSolution prepareSolution2(String teamId, String batchNo, String currDay) {
log.info("组织问题对象, teamId:{}, batchNo:{}", teamId, batchNo); log.info("组织问题对象, teamId:{}, batchNo:{}", teamId, batchNo);
entityManager.clear(); entityManager.clear();
...@@ -204,15 +210,14 @@ public class SolveServiceImpl implements SolveService { ...@@ -204,15 +210,14 @@ public class SolveServiceImpl implements SolveService {
// 统一出发地 // 统一出发地
Depot oneDepot; Depot oneDepot;
Optional<OrgGroup> optional = groupRepository.findByGroupId(team.getGroupId()); OrgWarehouseInfoEntity orgWarehouseInfoEntity = orgWarehouseInfoDao.findByWarehouseId(team.getWarehouseId());
if (optional.isEmpty()) { if (Objects.isNull(orgWarehouseInfoEntity)) {
log.error("组织问题对象, 未查询到组织信息 ,groupId:{}, batchNo:{}", team.getGroupId(), batchNo); log.error("配件仓问题对象, 未查询到配件仓信息 ,teamId:{}, warehouseId:{}, batchNo:{}", team.getTeamId(), team.getWarehouseId(), batchNo);
throw new RuntimeException(String.format("组织问题对象, 未查询到组织信息 ,groupId:%s, batchNo:%s", team.getGroupId(), batchNo)); throw new RuntimeException(String.format("组织问题对象, 未查询到组织信息 ,teamId:, %s, warehouseId:%s, batchNo:%s", team.getTeamId(), team.getWarehouseId(), batchNo));
} }
OrgGroup oneGroup = optional.get(); Location deptLocation = new Location(orgWarehouseInfoEntity.getId(), orgWarehouseInfoEntity.getWarehouseId(), "起点", Double.parseDouble(orgWarehouseInfoEntity.getX()), Double.parseDouble(orgWarehouseInfoEntity.getY()));
Location deptLocation = new Location(oneGroup.getId(), oneGroup.getGroupId(), "起点", Double.parseDouble(oneGroup.getX()), Double.parseDouble(oneGroup.getY())); oneDepot = new Depot(orgWarehouseInfoEntity.getId(), orgWarehouseInfoEntity.getWarehouseId(), deptLocation, 60 * 8, 60 * 18);
oneDepot = new Depot(oneGroup.getId(), oneGroup.getGroupId(), deptLocation, 60 * 8, 60 * 18);
// customerlist // customerlist
...@@ -295,7 +300,20 @@ public class SolveServiceImpl implements SolveService { ...@@ -295,7 +300,20 @@ public class SolveServiceImpl implements SolveService {
if (engineer.getVehicleType() != null && engineer.getVehicleType() == 2) { if (engineer.getVehicleType() != null && engineer.getVehicleType() == 2) {
maxDistance = 40 * 1000; maxDistance = 40 * 1000;
} }
Technician vehicle = new Technician(engineer.getId(), engineer.getEngineerCode(), engineer.getMaxNum(), engineer.getMaxMinute(), maxDistance, engineer.getVehicleType(), oneDepot, 60 * 8, 60 * 18, Set.copyOf(skillList), preferedLoctionDistanceMap);
Double x = null;
Double y = null;
if (ObjectUtil.equal(engineer.getDeparture(), DepartureEnum.WORK_ADDR.getCode())) {
x = Double.parseDouble(engineer.getWorkX());
y = Double.parseDouble(engineer.getWorkY());
} else {
x = Double.parseDouble(orgWarehouseInfoEntity.getX());
y = Double.parseDouble(orgWarehouseInfoEntity.getY());
}
Location engineerLocation = new Location(engineer.getId(), engineer.getEngineerCode(), "起点", x, y);
Depot engineerDepot = new Depot(engineer.getId(), engineer.getEngineerCode(), engineerLocation, 60 * 8, 60 * 18);
int[][] timeWindows = engineerCalendarService.timeWindows(engineer.getEngineerCode(), teamId, LocalDateTimeUtil.parseDate(currDay, DateTimeFormatter.ofPattern("yyyy-MM-dd")));
Technician vehicle = new Technician(engineer.getId(), engineer.getEngineerCode(), engineer.getMaxNum(), engineer.getMaxMinute(), maxDistance, engineer.getVehicleType(), engineerDepot, timeWindows, Set.copyOf(skillList), preferedLoctionDistanceMap);
technicianList.add(vehicle); technicianList.add(vehicle);
}); });
...@@ -303,7 +321,7 @@ public class SolveServiceImpl implements SolveService { ...@@ -303,7 +321,7 @@ public class SolveServiceImpl implements SolveService {
log.info("组织问题对象, technician-list, teamId:{}, batchNo:{}, technician-list:{}", teamId, batchNo, technicianList.size()); log.info("组织问题对象, technician-list, teamId:{}, batchNo:{}, technician-list:{}", teamId, batchNo, technicianList.size());
// locationlist 起点+订单地点 // locationlist 起点+订单地点
List<Location> locationList = Stream.concat(Lists.newArrayList(oneDepot).stream().map(Depot::getLocation), customerList.stream().map(Customer::getLocation)).collect(Collectors.toList()); List<Location> locationList = Stream.concat(technicianList.stream().map(technician -> technician.getDepot().getLocation()), customerList.stream().map(Customer::getLocation)).collect(Collectors.toList());
DispatchSolution solution = new DispatchSolution(team.getGroupId(), teamId, batchNo, locationList, oneDepot, technicianList, customerList); DispatchSolution solution = new DispatchSolution(team.getGroupId(), teamId, batchNo, locationList, oneDepot, technicianList, customerList);
......
...@@ -3,15 +3,15 @@ server: ...@@ -3,15 +3,15 @@ server:
dispatch: dispatch:
cron: cron:
expr: 0 55 8-22 * * ? expr: 0 15 8-23 * * ?
next-day-limit: 2 next-day-limit: 2
# expr: 0 */10 8-18 * * ? # expr: 0 */10 8-18 * * ?
scheduler: scheduler:
init-engineer-capacity: init-engineer-capacity:
# 每天22点1次 # 每天22点1次
#cron-expr: 0 0 22 * * ? cron-expr: 0 0 22 * * ?
cron-expr: 0 51 * * * ? #cron-expr: 0 51 * * * ?
day-offset-begin: 0 day-offset-begin: 0
day-offset-end: 20 day-offset-end: 20
rewrite-force: true rewrite-force: true
......
...@@ -3,7 +3,7 @@ server: ...@@ -3,7 +3,7 @@ server:
dispatch: dispatch:
cron: cron:
expr: 0 */30 8-18 * * ? expr: 0 */30 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!