Commit 97b0ec4627ef5e0f208f18527777cd293fe8d9df

Authored by shuhongfan
1 parent 71d2b279

day10-智能调度之取派件调度

sl-express-ms-dispatch-service/src/main/java/com/sl/ms/dispatch/mq/OrderMQListener.java
1 package com.sl.ms.dispatch.mq; 1 package com.sl.ms.dispatch.mq;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.convert.Convert;
  5 +import cn.hutool.core.date.DateUtil;
  6 +import cn.hutool.core.date.LocalDateTimeUtil;
  7 +import cn.hutool.json.JSONUtil;
  8 +import com.sl.ms.api.CourierFeign;
  9 +import com.sl.ms.base.api.common.MQFeign;
  10 +import com.sl.ms.work.api.PickupDispatchTaskFeign;
  11 +import com.sl.ms.work.domain.dto.CourierTaskCountDTO;
  12 +import com.sl.ms.work.domain.enums.pickupDispatchtask.PickupDispatchTaskType;
3 import com.sl.transport.common.constant.Constants; 13 import com.sl.transport.common.constant.Constants;
  14 +import com.sl.transport.common.util.ObjectUtil;
  15 +import com.sl.transport.common.vo.CourierTaskMsg;
  16 +import com.sl.transport.common.vo.OrderMsg;
4 import lombok.extern.slf4j.Slf4j; 17 import lombok.extern.slf4j.Slf4j;
5 import org.springframework.amqp.core.ExchangeTypes; 18 import org.springframework.amqp.core.ExchangeTypes;
6 import org.springframework.amqp.rabbit.annotation.Exchange; 19 import org.springframework.amqp.rabbit.annotation.Exchange;
7 import org.springframework.amqp.rabbit.annotation.Queue; 20 import org.springframework.amqp.rabbit.annotation.Queue;
8 import org.springframework.amqp.rabbit.annotation.QueueBinding; 21 import org.springframework.amqp.rabbit.annotation.QueueBinding;
9 import org.springframework.amqp.rabbit.annotation.RabbitListener; 22 import org.springframework.amqp.rabbit.annotation.RabbitListener;
  23 +import org.springframework.beans.factory.annotation.Autowired;
10 import org.springframework.stereotype.Component; 24 import org.springframework.stereotype.Component;
11 25
  26 +import java.time.LocalDateTime;
  27 +import java.time.temporal.ChronoUnit;
  28 +import java.util.List;
  29 +import java.util.stream.Collectors;
  30 +import java.util.stream.Stream;
  31 +
12 /** 32 /**
13 * 订单业务消息,接收到新订单后,根据快递员的负载情况,分配快递员 33 * 订单业务消息,接收到新订单后,根据快递员的负载情况,分配快递员
14 */ 34 */
@@ -16,6 +36,16 @@ import org.springframework.stereotype.Component; @@ -16,6 +36,16 @@ import org.springframework.stereotype.Component;
16 @Component 36 @Component
17 public class OrderMQListener { 37 public class OrderMQListener {
18 38
  39 + @Autowired
  40 + private MQFeign mqFeign;
  41 +
  42 + @Autowired
  43 + private CourierFeign courierFeign;
  44 +
  45 + @Autowired
  46 + private PickupDispatchTaskFeign pickupDispatchTaskFeign;
  47 +
  48 +
19 /** 49 /**
20 * 如果有多个快递员,需要查询快递员今日的取派件数,根据此数量进行计算 50 * 如果有多个快递员,需要查询快递员今日的取派件数,根据此数量进行计算
21 * 计算的逻辑:优先分配取件任务少的,取件数相同的取第一个分配 51 * 计算的逻辑:优先分配取件任务少的,取件数相同的取第一个分配
@@ -36,6 +66,93 @@ public class OrderMQListener { @@ -36,6 +66,93 @@ public class OrderMQListener {
36 //{"orderId":123, "agencyId": 8001, "taskType":1, "mark":"带包装", "longitude":116.111, "latitude":39.00, "created":1654224658728, "estimatedStartTime": 1654224658728} 66 //{"orderId":123, "agencyId": 8001, "taskType":1, "mark":"带包装", "longitude":116.111, "latitude":39.00, "created":1654224658728, "estimatedStartTime": 1654224658728}
37 log.info("接收到订单的消息 >>> msg = {}", msg); 67 log.info("接收到订单的消息 >>> msg = {}", msg);
38 68
  69 + OrderMsg orderMsg = JSONUtil.toBean(msg, OrderMsg.class);
  70 + Long agencyId = orderMsg.getAgencyId();
  71 +
  72 +// 通过快递员微服务查询 可以为发件人服务的快递员(正常上班、服务范围内)
  73 + Double longitude = orderMsg.getLongitude();
  74 + Double latitude = orderMsg.getLatitude();
  75 +
  76 + Long selectedCourierId = null;
  77 + List<Long> courierIds = courierFeign.queryCourierIdListByCondition(
  78 + agencyId,
  79 + longitude,
  80 + latitude,
  81 + LocalDateTimeUtil.toEpochMilli(orderMsg.getEstimatedEndTime()));
  82 + log.info("快递员微服务查出的ids:{}", courierIds);
  83 + if (CollUtil.isNotEmpty(courierIds)) {
  84 +// 选中快递员
  85 + selectedCourierId = selectCourier(courierIds, orderMsg.getTaskType());
  86 + log.info("根据当前任务选出的快递员ID:{}", selectedCourierId);
  87 + }
  88 +
  89 +// 发送消息
  90 + CourierTaskMsg courierTaskMsg = CourierTaskMsg.builder()
  91 + .courierId(selectedCourierId)
  92 + .agencyId(agencyId)
  93 + .taskType(orderMsg.getTaskType())
  94 + .orderId(orderMsg.getOrderId())
  95 + .mark(orderMsg.getMark())
  96 + .estimatedEndTime(orderMsg.getEstimatedEndTime())
  97 + .created(System.currentTimeMillis())
  98 + .build();
  99 +
  100 +// 计算时间差
  101 + long between = LocalDateTimeUtil.between(
  102 + LocalDateTime.now(),
  103 + orderMsg.getEstimatedEndTime(),
  104 + ChronoUnit.MINUTES
  105 + );
  106 +
  107 + int delay = Constants.MQ.DEFAULT_DELAY;
  108 + if (between > 120 && ObjectUtil.equalsAny(orderMsg.getTaskType(), 1)) {
  109 +// 计算延时时间,单位毫秒
  110 + LocalDateTime sendDatatTime = LocalDateTimeUtil.offset(orderMsg.getEstimatedEndTime(), -2, ChronoUnit.HOURS);
  111 + delay = Convert.toInt(LocalDateTimeUtil.between(LocalDateTime.now(), sendDatatTime, ChronoUnit.MILLIS));
  112 + }
  113 +
39 //TODO 待实现 114 //TODO 待实现
  115 + this.mqFeign.sendMsg(Constants.MQ.Exchanges.PICKUP_DISPATCH_TASK_DELAYED,
  116 + Constants.MQ.RoutingKeys.PICKUP_DISPATCH_TASK_CREATE,
  117 + courierTaskMsg.toJson(),
  118 + delay);
40 } 119 }
  120 + /**
  121 + * 根据当日的任务数选取快递员
  122 + *
  123 + * @param courierIds 快递员列个表
  124 + * @param taskType 任务类型
  125 + * @return 选中的快递员id
  126 + */
  127 + private Long selectCourier(List<Long> courierIds, Integer taskType) {
  128 + if (courierIds.size() == 1) {
  129 + return courierIds.get(0);
  130 + }
  131 +
  132 + String date = DateUtil.date().toDateStr();
  133 + List<CourierTaskCountDTO> courierTaskCountDTOS = pickupDispatchTaskFeign.findCountByCourierIds(
  134 + courierIds,
  135 + PickupDispatchTaskType.codeOf(taskType),
  136 + date);
  137 +
  138 + if (CollUtil.isEmpty(courierTaskCountDTOS)) {
  139 +// 没有查到任务数量,默认给第一个快递员分配任务
  140 + return courierIds.get(0);
  141 + }
  142 +
  143 +// 查看任务数是否与快递员数相同,如果不相同需要补齐,设置任务数0,这样就可以确保每个快递员都能分配搭配到任务
  144 + if (ObjectUtil.notEqual(courierIds.size(), courierTaskCountDTOS.size())) {
  145 + List<CourierTaskCountDTO> dtoList = courierIds.stream().filter(courierId -> {
  146 + int index = CollUtil.indexOf(courierTaskCountDTOS, dto -> ObjectUtil.equal(courierId, dto.getCourierId()));
  147 + return index == -1;
  148 + })
  149 + .map(courierId -> CourierTaskCountDTO.builder().courierId(courierId).count(0L).build())
  150 + .collect(Collectors.toList());
  151 + courierTaskCountDTOS.addAll(dtoList);
  152 + }
  153 +// 选中任务数最小的快递员进行分配
  154 + CollUtil.sortByProperty(courierTaskCountDTOS, "count");
  155 + return courierTaskCountDTOS.get(0).getCourierId();
  156 + }
  157 +
41 } 158 }
sl-express-ms-work-service/src/main/java/com/sl/ms/work/mq/CourierMQListener.java
1 package com.sl.ms.work.mq; 1 package com.sl.ms.work.mq;
2 2
  3 +import cn.hutool.core.date.LocalDateTimeUtil;
  4 +import cn.hutool.core.util.StrUtil;
3 import cn.hutool.json.JSONUtil; 5 import cn.hutool.json.JSONUtil;
  6 +import com.sl.ms.oms.api.OrderFeign;
  7 +import com.sl.ms.oms.dto.OrderDTO;
  8 +import com.sl.ms.oms.enums.OrderStatus;
  9 +import com.sl.ms.work.domain.enums.pickupDispatchtask.PickupDispatchTaskAssignedStatus;
  10 +import com.sl.ms.work.domain.enums.pickupDispatchtask.PickupDispatchTaskSignStatus;
  11 +import com.sl.ms.work.domain.enums.pickupDispatchtask.PickupDispatchTaskStatus;
  12 +import com.sl.ms.work.domain.enums.pickupDispatchtask.PickupDispatchTaskType;
  13 +import com.sl.ms.work.entity.PickupDispatchTaskEntity;
4 import com.sl.ms.work.service.PickupDispatchTaskService; 14 import com.sl.ms.work.service.PickupDispatchTaskService;
5 import com.sl.ms.work.service.TransportOrderService; 15 import com.sl.ms.work.service.TransportOrderService;
6 import com.sl.transport.common.constant.Constants; 16 import com.sl.transport.common.constant.Constants;
  17 +import com.sl.transport.common.exception.SLException;
  18 +import com.sl.transport.common.util.BeanUtil;
  19 +import com.sl.transport.common.util.ObjectUtil;
7 import com.sl.transport.common.vo.CourierMsg; 20 import com.sl.transport.common.vo.CourierMsg;
8 import com.sl.transport.common.vo.CourierTaskMsg; 21 import com.sl.transport.common.vo.CourierTaskMsg;
9 import lombok.extern.slf4j.Slf4j; 22 import lombok.extern.slf4j.Slf4j;
@@ -15,6 +28,10 @@ import org.springframework.amqp.rabbit.annotation.RabbitListener; @@ -15,6 +28,10 @@ import org.springframework.amqp.rabbit.annotation.RabbitListener;
15 import org.springframework.beans.factory.annotation.Autowired; 28 import org.springframework.beans.factory.annotation.Autowired;
16 import org.springframework.stereotype.Component; 29 import org.springframework.stereotype.Component;
17 30
  31 +import java.time.LocalDateTime;
  32 +import java.time.temporal.ChronoUnit;
  33 +import java.util.List;
  34 +
18 /** 35 /**
19 * 快递员的消息处理,该处理器处理两个消息: 36 * 快递员的消息处理,该处理器处理两个消息:
20 * 1. 生成快递员取派件任务 37 * 1. 生成快递员取派件任务
@@ -29,6 +46,8 @@ public class CourierMQListener { @@ -29,6 +46,8 @@ public class CourierMQListener {
29 46
30 @Autowired 47 @Autowired
31 private TransportOrderService transportOrderService; 48 private TransportOrderService transportOrderService;
  49 + @Autowired
  50 + private OrderFeign orderFeign;
32 51
33 52
34 /** 53 /**
@@ -47,8 +66,55 @@ public class CourierMQListener { @@ -47,8 +66,55 @@ public class CourierMQListener {
47 //解析消息 66 //解析消息
48 CourierTaskMsg courierTaskMsg = JSONUtil.toBean(msg, CourierTaskMsg.class); 67 CourierTaskMsg courierTaskMsg = JSONUtil.toBean(msg, CourierTaskMsg.class);
49 68
  69 +// 幂等性处理,判断订单对应的取派件任务是否存在,判断条件:订单号+任务状态
  70 + List<PickupDispatchTaskEntity> list = pickupDispatchTaskService.findByOrderId(
  71 + courierTaskMsg.getOrderId(),
  72 + PickupDispatchTaskType.codeOf(courierTaskMsg.getTaskType()));
  73 + for (PickupDispatchTaskEntity pickupDispatchTaskEntity : list) {
  74 + if (pickupDispatchTaskEntity.getStatus() == PickupDispatchTaskStatus.NEW) {
  75 +// 消息重复消息
  76 + }
  77 + }
  78 +
  79 +// 订单不存在,不进行调度
  80 + OrderDTO orderDTO = orderFeign.findById(courierTaskMsg.getOrderId());
  81 + if (ObjectUtil.isEmpty(orderDTO)) {
  82 + return;
  83 + }
  84 +
  85 +// 如果已经取消或者删除 则不进行调度
  86 + if (orderDTO.getStatus().equals(OrderStatus.CANCELLED.getCode()) ||
  87 + orderDTO.getStatus().equals(OrderStatus.DEL.getCode())) {
  88 + return;
  89 + }
  90 +
  91 + PickupDispatchTaskEntity pickupDispatchTask = BeanUtil.toBean(courierTaskMsg, PickupDispatchTaskEntity.class);
  92 +
  93 +// 任务类型
  94 + pickupDispatchTask.setTaskType(PickupDispatchTaskType.codeOf(courierTaskMsg.getTaskType()));
  95 +
  96 +// 预计开始时间,结束时间向前推一个小时
  97 + LocalDateTime estimatedStartTime = LocalDateTimeUtil.offset(pickupDispatchTask.getEstimatedEndTime(), -1, ChronoUnit.HOURS);
  98 + pickupDispatchTask.setEstimatedStartTime(estimatedStartTime);
  99 +
  100 +// 默认未签收状态
  101 + pickupDispatchTask.setSignStatus(PickupDispatchTaskSignStatus.NOT_SIGNED);
  102 +
  103 +// 分配状态
  104 + if (ObjectUtil.isNotEmpty(pickupDispatchTask.getCourierId())) {
  105 + pickupDispatchTask.setAssignedStatus(PickupDispatchTaskAssignedStatus.DISTRIBUTED);
  106 + } else {
  107 + pickupDispatchTask.setAssignedStatus(PickupDispatchTaskAssignedStatus.MANUAL_DISTRIBUTED);
  108 + }
  109 +
  110 + PickupDispatchTaskEntity result = pickupDispatchTaskService.saveTaskPickupDispatch(pickupDispatchTask);
  111 + if (result == null) {
  112 + //保存任务失败
  113 + throw new SLException(StrUtil.format("快递员任务保存失败 >>> msg = {}", msg));
  114 + }
  115 +
50 // 订单转运单 116 // 订单转运单
51 - transportOrderService.orderToTransportOrder(courierTaskMsg.getOrderId()); 117 +// transportOrderService.orderToTransportOrder(courierTaskMsg.getOrderId());
52 } 118 }
53 119
54 /** 120 /**
sl-express-ms-work-service/src/main/java/com/sl/ms/work/service/impl/PickupDispatchTaskServiceImpl.java 0 → 100644
  1 +package com.sl.ms.work.service.impl;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.collection.ListUtil;
  5 +import cn.hutool.core.convert.Convert;
  6 +import cn.hutool.core.date.DateTime;
  7 +import cn.hutool.core.date.DateUtil;
  8 +import cn.hutool.core.date.LocalDateTimeUtil;
  9 +import cn.hutool.core.util.ObjectUtil;
  10 +import com.baomidou.mybatisplus.core.conditions.Wrapper;
  11 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  12 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  13 +import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  14 +import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  15 +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  16 +import com.sl.ms.oms.api.OrderFeign;
  17 +import com.sl.ms.oms.enums.OrderStatus;
  18 +import com.sl.ms.work.domain.dto.CourierTaskCountDTO;
  19 +import com.sl.ms.work.domain.dto.PickupDispatchTaskDTO;
  20 +import com.sl.ms.work.domain.dto.request.PickupDispatchTaskPageQueryDTO;
  21 +import com.sl.ms.work.domain.dto.response.PickupDispatchTaskStatisticsDTO;
  22 +import com.sl.ms.work.domain.enums.WorkExceptionEnum;
  23 +import com.sl.ms.work.domain.enums.pickupDispatchtask.*;
  24 +import com.sl.ms.work.entity.PickupDispatchTaskEntity;
  25 +import com.sl.ms.work.mapper.TaskPickupDispatchMapper;
  26 +import com.sl.ms.work.service.PickupDispatchTaskService;
  27 +import com.sl.ms.work.service.TransportOrderService;
  28 +import com.sl.transport.common.exception.SLException;
  29 +import com.sl.transport.common.util.BeanUtil;
  30 +import com.sl.transport.common.util.PageResponse;
  31 +import com.sl.transport.common.vo.OrderMsg;
  32 +import org.springframework.beans.factory.annotation.Autowired;
  33 +import org.springframework.stereotype.Service;
  34 +import org.springframework.transaction.annotation.Transactional;
  35 +
  36 +import java.time.LocalDateTime;
  37 +import java.util.Date;
  38 +import java.util.List;
  39 +import java.util.stream.Collectors;
  40 +
  41 +@Service
  42 +public class PickupDispatchTaskServiceImpl extends ServiceImpl<TaskPickupDispatchMapper, PickupDispatchTaskEntity> implements PickupDispatchTaskService {
  43 +
  44 + @Autowired
  45 + private TaskPickupDispatchMapper taskPickupDispatchMapper;
  46 +
  47 + @Autowired
  48 + private TransportOrderService transportOrderService;
  49 +
  50 + @Autowired
  51 + private OrderFeign orderFeign;
  52 +
  53 + /**
  54 + * 更新取派件状态,不允许 NEW 状态
  55 + *
  56 + * @param pickupDispatchTaskDTO 修改的数据
  57 + * @return 是否成功
  58 + */
  59 + @Override
  60 + @Transactional
  61 + public Boolean updateStatus(PickupDispatchTaskDTO pickupDispatchTaskDTO) {
  62 + WorkExceptionEnum paramError = WorkExceptionEnum.PICKUP_DISPATCH_TASK_PARAM_ERROR;
  63 + if (ObjectUtil.hasEmpty(pickupDispatchTaskDTO.getId(), pickupDispatchTaskDTO.getStatus())) {
  64 + throw new SLException("更新取派件任务状态,id或status不能为空", paramError.getCode());
  65 + }
  66 +
  67 + PickupDispatchTaskEntity pickupDispatchTask = getById(pickupDispatchTaskDTO.getId());
  68 +
  69 + switch (pickupDispatchTask.getStatus()) {
  70 + case NEW:
  71 + throw new SLException(WorkExceptionEnum.PICKUP_DISPATCH_TASK_STATUS_NOT_NEW);
  72 + case COMPLETED:
  73 +// 任务完成
  74 + pickupDispatchTask.setStatus(PickupDispatchTaskStatus.COMPLETED);
  75 +// 设置完成时间
  76 + pickupDispatchTask.setActualEndTime(LocalDateTime.now());
  77 +
  78 + if (PickupDispatchTaskType.DISPATCH == pickupDispatchTask.getTaskType()) {
  79 +// 如果是派件任务的完成,已签收需要设置签收状态和签收人,拒收只需要设置签收状态
  80 + if (ObjectUtil.isEmpty(pickupDispatchTaskDTO.getSignStatus())) {
  81 + throw new SLException("完成派件任务,签收状态不能为空", paramError.getCode());
  82 + }
  83 + pickupDispatchTask.setSignStatus(pickupDispatchTaskDTO.getSignStatus());
  84 +
  85 + if (PickupDispatchTaskSignStatus.RECEIVED == pickupDispatchTaskDTO.getSignStatus()) {
  86 + if (ObjectUtil.isEmpty(pickupDispatchTaskDTO.getSignRecipient())) {
  87 + throw new SLException("完成派件任务,签收人不能为空", paramError.getCode());
  88 + }
  89 + pickupDispatchTask.setSignRecipient(pickupDispatchTaskDTO.getSignRecipient());
  90 + }
  91 + }
  92 + break;
  93 + case CANCELLED:
  94 + if (ObjectUtil.isEmpty(pickupDispatchTaskDTO.getCancelReason())) {
  95 + throw new SLException("取消任务,原因不能为空", paramError.getCode());
  96 + }
  97 + pickupDispatchTask.setStatus(PickupDispatchTaskStatus.CANCELLED);
  98 + pickupDispatchTask.setCancelReason(pickupDispatchTaskDTO.getCancelReason());
  99 + pickupDispatchTask.setCancelReasonDescription(pickupDispatchTaskDTO.getCancelReasonDescription());
  100 + pickupDispatchTask.setCancelTime(LocalDateTime.now());
  101 +
  102 + if (pickupDispatchTaskDTO.getCancelReason() == PickupDispatchTaskCancelReason.RETURN_TO_AGENCY) {
  103 + //发送分配快递员派件任务的消息
  104 + OrderMsg orderMsg = OrderMsg.builder()
  105 + .agencyId(pickupDispatchTask.getAgencyId())
  106 + .orderId(pickupDispatchTask.getOrderId())
  107 + .created(DateUtil.current())
  108 + .taskType(PickupDispatchTaskType.PICKUP.getCode()) //取件任务
  109 + .mark(pickupDispatchTask.getMark())
  110 + .estimatedEndTime(pickupDispatchTask.getEstimatedEndTime()).build();
  111 + //发送消息(取消任务发生在取件之前,没有运单,参数直接填入null)
  112 + transportOrderService.sendPickupDispatchTaskMsgToDispatch(null, orderMsg);
  113 + } else if (pickupDispatchTaskDTO.getCancelReason() == PickupDispatchTaskCancelReason.CANCEL_BY_USER) {
  114 + //原因是用户取消,则订单状态改为取消
  115 + orderFeign.updateStatus(ListUtil.of(pickupDispatchTask.getOrderId()), OrderStatus.CANCELLED.getCode());
  116 + } else {
  117 + //其他原因则关闭订单
  118 + orderFeign.updateStatus(ListUtil.of(pickupDispatchTask.getOrderId()), OrderStatus.CLOSE.getCode());
  119 + }
  120 + break;
  121 + default:
  122 + throw new SLException("其他未知状态,不能完成更新操作", paramError.getCode());
  123 + }
  124 +
  125 + return updateById(pickupDispatchTask);
  126 + }
  127 +
  128 + /**
  129 + * 改派快递员
  130 + *
  131 + * @param ids 任务id列表
  132 + * @param originalCourierId 原快递员id
  133 + * @param targetCourierId 目标快递员id
  134 + * @return 是否成功
  135 + */
  136 + @Override
  137 + public Boolean updateCourierId(List<Long> ids, Long originalCourierId, Long targetCourierId) {
  138 + if (ObjectUtil.hasEmpty(ids, targetCourierId, originalCourierId)) {
  139 + throw new SLException(WorkExceptionEnum.UPDATE_COURIER_PARAM_ERROR);
  140 + }
  141 +
  142 + if (ObjectUtil.equal(originalCourierId, targetCourierId)) {
  143 + throw new SLException(WorkExceptionEnum.UPDATE_COURIER_EQUAL_PARAM_ERROR);
  144 + }
  145 +
  146 + List<PickupDispatchTaskEntity> pickupDispatchTaskEntities = listByIds(ids);
  147 + if (CollUtil.isEmpty(pickupDispatchTaskEntities)) {
  148 + throw new SLException(WorkExceptionEnum.PICKUP_DISPATCH_TASK_NOT_FOUND);
  149 + }
  150 +
  151 + pickupDispatchTaskEntities.forEach(pickupDispatchTaskEntity -> {
  152 +// 校验原快递id是否正确(本来无快递员ID的情况除外)
  153 + if (ObjectUtil.isNotEmpty(pickupDispatchTaskEntity.getCourierId())
  154 + && ObjectUtil.notEqual(pickupDispatchTaskEntity.getCourierId(), originalCourierId)) {
  155 + throw new SLException(WorkExceptionEnum.UPDATE_COURIER_ID_PARAM_ERROR);
  156 + }
  157 +
  158 +// 更新快递员ID
  159 + pickupDispatchTaskEntity.setCourierId(targetCourierId);
  160 +
  161 +// 标识已分配状态
  162 + pickupDispatchTaskEntity.setAssignedStatus(PickupDispatchTaskAssignedStatus.DISTRIBUTED);
  163 + });
  164 +
  165 +// 批量更新
  166 + List<Long> taskIds = pickupDispatchTaskEntities.stream().map(PickupDispatchTaskEntity::getId).collect(Collectors.toList());
  167 +
  168 + LambdaUpdateWrapper<PickupDispatchTaskEntity> updateWrapper = Wrappers.<PickupDispatchTaskEntity>lambdaUpdate()
  169 + .in(PickupDispatchTaskEntity::getId, taskIds)
  170 + .set(PickupDispatchTaskEntity::getCourierId, targetCourierId)
  171 + .set(PickupDispatchTaskEntity::getAssignedStatus, PickupDispatchTaskAssignedStatus.DISTRIBUTED);
  172 +
  173 + boolean result = update(updateWrapper);
  174 + if (result) {
  175 +
  176 + }
  177 +
  178 + return result;
  179 + }
  180 +
  181 + /**
  182 + * 新增取派件任务
  183 + *
  184 + * @param taskPickupDispatch 取派件任务信息
  185 + * @return 取派件任务信息
  186 + */
  187 + @Override
  188 + public PickupDispatchTaskEntity saveTaskPickupDispatch(PickupDispatchTaskEntity taskPickupDispatch) {
  189 +// 设置任务状态为新任务
  190 + taskPickupDispatch.setStatus(PickupDispatchTaskStatus.NEW);
  191 + boolean result = super.save(taskPickupDispatch);
  192 +
  193 + if (result) {
  194 +
  195 + return taskPickupDispatch;
  196 + }
  197 + throw new SLException(WorkExceptionEnum.PICKUP_DISPATCH_TASK_SAVE_ERROR);
  198 + }
  199 +
  200 + /**
  201 + * 分页查询取派件任务
  202 + *
  203 + * @param dto 查询条件
  204 + * @return 分页结果
  205 + */
  206 + @Override
  207 + public PageResponse<PickupDispatchTaskDTO> findByPage(PickupDispatchTaskPageQueryDTO dto) {
  208 +// 1.构造条件
  209 + Page<PickupDispatchTaskEntity> iPage = new Page<>(dto.getPage(), dto.getPageSize());
  210 + LambdaQueryWrapper<PickupDispatchTaskEntity> queryWrapper = Wrappers.<PickupDispatchTaskEntity>lambdaQuery()
  211 + .like(ObjectUtil.isNotEmpty(dto.getId()), PickupDispatchTaskEntity::getId, dto.getId())
  212 + .like(ObjectUtil.isNotEmpty(dto.getOrderId()), PickupDispatchTaskEntity::getOrderId, dto.getOrderId())
  213 + .eq(ObjectUtil.isNotEmpty(dto.getAgencyId()), PickupDispatchTaskEntity::getAgencyId, dto.getAgencyId())
  214 + .eq(ObjectUtil.isNotEmpty(dto.getCourierId()), PickupDispatchTaskEntity::getCourierId, dto.getCourierId())
  215 + .eq(ObjectUtil.isNotEmpty(dto.getTaskType()), PickupDispatchTaskEntity::getTaskType, dto.getTaskType())
  216 + .eq(ObjectUtil.isNotEmpty(dto.getStatus()), PickupDispatchTaskEntity::getStatus, dto.getStatus())
  217 + .eq(ObjectUtil.isNotEmpty(dto.getAssignedStatus()), PickupDispatchTaskEntity::getAssignedStatus, dto.getAssignedStatus())
  218 + .eq(ObjectUtil.isNotEmpty(dto.getSignStatus()), PickupDispatchTaskEntity::getSignStatus, dto.getSignStatus())
  219 + .eq(ObjectUtil.isNotEmpty(dto.getIsDeleted()), PickupDispatchTaskEntity::getIsDeleted, dto.getIsDeleted())
  220 + .between(ObjectUtil.isNotEmpty(dto.getMinEstimatedEndTime()), PickupDispatchTaskEntity::getEstimatedEndTime, dto.getMinEstimatedEndTime(), dto.getMaxEstimatedEndTime())
  221 + .between(ObjectUtil.isNotEmpty(dto.getMinActualEndTime()), PickupDispatchTaskEntity::getActualEndTime, dto.getMinActualEndTime(), dto.getMaxActualEndTime())
  222 + .orderByDesc(PickupDispatchTaskEntity::getUpdated);
  223 +
  224 +// 2. 分页查询
  225 + Page<PickupDispatchTaskEntity> result = super.page(iPage, queryWrapper);
  226 +
  227 +// 3.实体类转dto
  228 + return PageResponse.of(result, PickupDispatchTaskDTO.class);
  229 + }
  230 +
  231 + /**
  232 + * 按照当日快递员id列表查询每个快递员的取派件任务数
  233 + *
  234 + * @param courierIds 快递员id列表
  235 + * @param pickupDispatchTaskType 任务类型
  236 + * @param date 日期,格式:yyyy-MM-dd 或 yyyyMMdd
  237 + * @return 任务数
  238 + */
  239 + @Override
  240 + public List<CourierTaskCountDTO> findCountByCourierIds(List<Long> courierIds, PickupDispatchTaskType pickupDispatchTaskType, String date) {
  241 + DateTime dateTime = DateUtil.parse(date);
  242 + LocalDateTime startDateTime = DateUtil.beginOfDay(dateTime).toLocalDateTime();
  243 + LocalDateTime endDateTime = DateUtil.endOfDay(dateTime).toLocalDateTime();
  244 + return taskPickupDispatchMapper.findCountByCourierIds(courierIds,
  245 + pickupDispatchTaskType.getCode(),
  246 + startDateTime,
  247 + endDateTime);
  248 + }
  249 +
  250 + /**
  251 + * 根据订单id查询取派件任务
  252 + *
  253 + * @param orderId 订单id
  254 + * @param taskType 任务类型
  255 + * @return 任务
  256 + */
  257 + @Override
  258 + public List<PickupDispatchTaskEntity> findByOrderId(Long orderId, PickupDispatchTaskType taskType) {
  259 + LambdaQueryWrapper<PickupDispatchTaskEntity> wrapper = Wrappers.<PickupDispatchTaskEntity>lambdaQuery()
  260 + .eq(PickupDispatchTaskEntity::getOrderId, orderId)
  261 + .eq(PickupDispatchTaskEntity::getTaskType, taskType)
  262 + .orderByAsc(PickupDispatchTaskEntity::getCreated);
  263 + return list(wrapper);
  264 + }
  265 +
  266 + @Override
  267 + public boolean deleteByIds(List<Long> ids) {
  268 + if (CollUtil.isEmpty(ids)) {
  269 + return false;
  270 + }
  271 +
  272 +// 通过Id列表构造对象列表
  273 + List<PickupDispatchTaskEntity> list = ids.stream().map(id -> {
  274 + PickupDispatchTaskEntity pickupDispatchTaskEntity = new PickupDispatchTaskEntity();
  275 + pickupDispatchTaskEntity.setId(id);
  276 + pickupDispatchTaskEntity.setIsDeleted(PickupDispatchTaskIsDeleted.IS_DELETED);
  277 +
  278 + return pickupDispatchTaskEntity;
  279 + }).collect(Collectors.toList());
  280 +
  281 + return updateBatchById(list);
  282 + }
  283 +
  284 + /**
  285 + * 今日任务分类计数
  286 + *
  287 + * @param courierId 快递员id
  288 + * @param taskType 任务类型,1为取件任务,2为派件任务
  289 + * @param status 任务状态,1新任务,2已完成,3已取消
  290 + * @param isDeleted 是否逻辑删除
  291 + * @return 任务数量
  292 + */
  293 + @Override
  294 + public Integer todayTasksCount(Long courierId, PickupDispatchTaskType taskType, PickupDispatchTaskStatus status, PickupDispatchTaskIsDeleted isDeleted) {
  295 +// 构建查询条件
  296 + LambdaQueryWrapper<PickupDispatchTaskEntity> queryWrapper = Wrappers.<PickupDispatchTaskEntity>lambdaQuery()
  297 + .eq(ObjectUtil.isNotEmpty(courierId), PickupDispatchTaskEntity::getCourierId, courierId)
  298 + .eq(ObjectUtil.isNotEmpty(taskType), PickupDispatchTaskEntity::getTaskType, taskType)
  299 + .eq(ObjectUtil.isNotEmpty(status), PickupDispatchTaskEntity::getStatus, status)
  300 + .eq(ObjectUtil.isNotEmpty(isDeleted), PickupDispatchTaskEntity::getIsDeleted, isDeleted);
  301 +
  302 +// 根据任务状态限定查询的日期条件
  303 + LocalDateTime startTime = LocalDateTimeUtil.of(DateUtil.beginOfDay(new Date()));
  304 + LocalDateTime endTime = LocalDateTimeUtil.of(DateUtil.endOfDay(new Date()));
  305 + if (status == null) {
  306 +// 没有任务状态,查询任务创建时间
  307 + queryWrapper.between(PickupDispatchTaskEntity::getCreated, startTime, endTime);
  308 + } else if (status == PickupDispatchTaskStatus.NEW) {
  309 +// 新任务状态,查询预计结束时间
  310 + queryWrapper.between(PickupDispatchTaskEntity::getEstimatedEndTime, startTime, endTime);
  311 + } else if (status == PickupDispatchTaskStatus.COMPLETED) {
  312 +// 完成状态,查询实际完成时间
  313 + queryWrapper.between(PickupDispatchTaskEntity::getActualEndTime, startTime, endTime);
  314 + } else if (status == PickupDispatchTaskStatus.CANCELLED) {
  315 +// 取消状态,查询取消时间
  316 + queryWrapper.between(PickupDispatchTaskEntity::getCancelTime, startTime, endTime);
  317 + }
  318 +
  319 +
  320 + return Convert.toInt(count(queryWrapper));
  321 + }
  322 +
  323 + /**
  324 + * 条件查询所有
  325 + *
  326 + * @param courierId 快递员id
  327 + * @param taskType 任务类型,1为取件任务,2为派件任务
  328 + * @param taskStatus 任务状态,1新任务,2已完成,3已取消
  329 + * @param isDeleted 是否逻辑删除
  330 + * @return 取派件任务列表
  331 + */
  332 + @Override
  333 + public List<PickupDispatchTaskDTO> findAll(Long courierId, PickupDispatchTaskType taskType, PickupDispatchTaskStatus taskStatus, PickupDispatchTaskIsDeleted isDeleted) {
  334 +// 构建查询条件
  335 + LambdaQueryWrapper<PickupDispatchTaskEntity> queryWrapper = Wrappers.<PickupDispatchTaskEntity>lambdaQuery()
  336 + .eq(ObjectUtil.isNotEmpty(courierId), PickupDispatchTaskEntity::getCourierId, courierId)
  337 + .eq(ObjectUtil.isNotEmpty(taskType), PickupDispatchTaskEntity::getTaskType, taskType)
  338 + .eq(ObjectUtil.isNotEmpty(taskStatus), PickupDispatchTaskEntity::getStatus, taskStatus)
  339 + .eq(ObjectUtil.isNotEmpty(isDeleted), PickupDispatchTaskEntity::getIsDeleted, isDeleted);
  340 +
  341 + List<PickupDispatchTaskEntity> taskEntities = list(queryWrapper);
  342 + return BeanUtil.copyToList(taskEntities, PickupDispatchTaskDTO.class);
  343 + }
  344 +
  345 + /**
  346 + * 今日任务分类统计
  347 + *
  348 + * @param courierId 快递员id
  349 + * @return 统计结果
  350 + */
  351 + @Override
  352 + public PickupDispatchTaskStatisticsDTO todayTaskStatistics(Long courierId) {
  353 + PickupDispatchTaskStatisticsDTO taskStatisticsDTO = new PickupDispatchTaskStatisticsDTO();
  354 +
  355 +// 今日取件任务数量
  356 + taskStatisticsDTO.setPickupNum(todayTasksCount(courierId, PickupDispatchTaskType.PICKUP, null, PickupDispatchTaskIsDeleted.NOT_DELETED));
  357 +
  358 + //今日待取件任务数量
  359 + taskStatisticsDTO.setNewPickUpNum(todayTasksCount(courierId, PickupDispatchTaskType.PICKUP, PickupDispatchTaskStatus.NEW, PickupDispatchTaskIsDeleted.NOT_DELETED));
  360 +
  361 + //今日已取件任务数量
  362 + taskStatisticsDTO.setCompletePickUpNum(todayTasksCount(courierId, PickupDispatchTaskType.PICKUP, PickupDispatchTaskStatus.COMPLETED, PickupDispatchTaskIsDeleted.NOT_DELETED));
  363 +
  364 + //今日已取消取件任务数量
  365 + taskStatisticsDTO.setCancelPickUpNum(todayTasksCount(courierId, PickupDispatchTaskType.PICKUP, PickupDispatchTaskStatus.CANCELLED, PickupDispatchTaskIsDeleted.NOT_DELETED));
  366 +
  367 + //今日派件任务数量
  368 + taskStatisticsDTO.setDispatchNum(todayTasksCount(courierId, PickupDispatchTaskType.DISPATCH, null, PickupDispatchTaskIsDeleted.NOT_DELETED));
  369 +
  370 + //今日待派件任务数量
  371 + taskStatisticsDTO.setNewDispatchNum(todayTasksCount(courierId, PickupDispatchTaskType.DISPATCH, PickupDispatchTaskStatus.NEW, PickupDispatchTaskIsDeleted.NOT_DELETED));
  372 +
  373 + //今日已签收任务数量
  374 + taskStatisticsDTO.setSignedNum(todayTasksCount(courierId, PickupDispatchTaskType.DISPATCH, PickupDispatchTaskStatus.COMPLETED, PickupDispatchTaskIsDeleted.NOT_DELETED));
  375 +
  376 + //今日已取消派件任务数量
  377 + taskStatisticsDTO.setCancelDispatchNum(todayTasksCount(courierId, PickupDispatchTaskType.DISPATCH, PickupDispatchTaskStatus.CANCELLED, PickupDispatchTaskIsDeleted.NOT_DELETED));
  378 + return taskStatisticsDTO;
  379 + }
  380 +}