Commit 97b0ec4627ef5e0f208f18527777cd293fe8d9df
1 parent
71d2b279
day10-智能调度之取派件调度
Showing
3 changed files
with
564 additions
and
1 deletions
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 | +} |