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
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"
+ " left join engineer_business b on a.engineer_code = b.engineer_code\n"
+ " WHERE o.team_id=?3 AND o.`status`=1\n",
+ " join engineer_business b on a.engineer_code = b.engineer_code\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)
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
+ " 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"
+ " 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"
+ " ORDER BY a.expect_time_begin ASC \n",
nativeQuery = true)
......@@ -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" +
" 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 ('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" +
" order by a.expect_time_begin asc ",
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 {
@Column(name = "update_time")
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 {
@Column(name = "update_time")
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 {
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
// ************************************************************************
......
......@@ -73,7 +73,7 @@ public class BatchScheduler {
UUID problemId = solveService.generateProblemId(teamId, batchNo);
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) {
log.info("dispatchRun 当前批次没有待指派工单 , teamId:{}, day:{}, batch:{}, problemId:{}, order-size:{}", teamId, currDay, batchNo, problemId, problem.getCustomerList().size());
......
......@@ -25,7 +25,7 @@ public interface SolveService {
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 {
UUID problemId = solveService.generateProblemId(teamId, batchNo);
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) {
log.info("dispatchRun 当前批次没有待指派工单 , teamId:{}, day:{}, batch:{}, problemId:{}, order-size:{}", teamId, currDay, batchNo, problemId, problem.getCustomerList().size());
......
package com.dituhui.pea.dispatch.service.impl;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.dituhui.pea.dispatch.common.GeoDistanceCalculator;
import com.dituhui.pea.dispatch.constraint.DispatchConstraintProvider;
import com.dituhui.pea.dispatch.dao.*;
import com.dituhui.pea.dispatch.entity.DispatchOrder;
import com.dituhui.pea.dispatch.entity.OrderInfo;
import com.dituhui.pea.dispatch.entity.OrgGroup;
import com.dituhui.pea.dispatch.entity.OrgTeamEntity;
import com.dituhui.pea.dispatch.entity.*;
import com.dituhui.pea.dispatch.enums.DepartureEnum;
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.SolveService;
import com.dituhui.pea.dispatch.utils.DispatchSolutionUtils;
......@@ -77,6 +77,12 @@ public class SolveServiceImpl implements SolveService {
@Autowired
OrgTeamDao orgTeamDao;
@Autowired
private OrgWarehouseInfoDao orgWarehouseInfoDao;
@Autowired
private EngineerCalendarService engineerCalendarService;
// 查询技术员所有技能集
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 ";
......@@ -195,7 +201,7 @@ public class SolveServiceImpl implements SolveService {
// 按小队,批号组装问题对象
@Override
public DispatchSolution prepareSolution2(String teamId, String batchNo) {
public DispatchSolution prepareSolution2(String teamId, String batchNo, String currDay) {
log.info("组织问题对象, teamId:{}, batchNo:{}", teamId, batchNo);
entityManager.clear();
......@@ -204,15 +210,14 @@ public class SolveServiceImpl implements SolveService {
// 统一出发地
Depot oneDepot;
Optional<OrgGroup> optional = groupRepository.findByGroupId(team.getGroupId());
if (optional.isEmpty()) {
log.error("组织问题对象, 未查询到组织信息 ,groupId:{}, batchNo:{}", team.getGroupId(), batchNo);
throw new RuntimeException(String.format("组织问题对象, 未查询到组织信息 ,groupId:%s, batchNo:%s", team.getGroupId(), batchNo));
OrgWarehouseInfoEntity orgWarehouseInfoEntity = orgWarehouseInfoDao.findByWarehouseId(team.getWarehouseId());
if (Objects.isNull(orgWarehouseInfoEntity)) {
log.error("配件仓问题对象, 未查询到配件仓信息 ,teamId:{}, warehouseId:{}, batchNo:{}", team.getTeamId(), team.getWarehouseId(), 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(oneGroup.getId(), oneGroup.getGroupId(), "起点", Double.parseDouble(oneGroup.getX()), Double.parseDouble(oneGroup.getY()));
oneDepot = new Depot(oneGroup.getId(), oneGroup.getGroupId(), deptLocation, 60 * 8, 60 * 18);
Location deptLocation = new Location(orgWarehouseInfoEntity.getId(), orgWarehouseInfoEntity.getWarehouseId(), "起点", Double.parseDouble(orgWarehouseInfoEntity.getX()), Double.parseDouble(orgWarehouseInfoEntity.getY()));
oneDepot = new Depot(orgWarehouseInfoEntity.getId(), orgWarehouseInfoEntity.getWarehouseId(), deptLocation, 60 * 8, 60 * 18);
// customerlist
......@@ -295,7 +300,20 @@ public class SolveServiceImpl implements SolveService {
if (engineer.getVehicleType() != null && engineer.getVehicleType() == 2) {
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);
});
......@@ -303,7 +321,7 @@ public class SolveServiceImpl implements SolveService {
log.info("组织问题对象, technician-list, teamId:{}, batchNo:{}, technician-list:{}", teamId, batchNo, technicianList.size());
// 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);
......
......@@ -3,15 +3,15 @@ server:
dispatch:
cron:
expr: 0 55 8-22 * * ?
expr: 0 15 8-23 * * ?
next-day-limit: 2
# expr: 0 */10 8-18 * * ?
scheduler:
init-engineer-capacity:
# 每天22点1次
#cron-expr: 0 0 22 * * ?
cron-expr: 0 51 * * * ?
cron-expr: 0 0 22 * * ?
#cron-expr: 0 51 * * * ?
day-offset-begin: 0
day-offset-end: 20
rewrite-force: true
......
......@@ -3,7 +3,7 @@ server:
dispatch:
cron:
expr: 0 */30 8-18 * * ?
expr: 0 */30 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!