Prechádzať zdrojové kódy

新增自动派单评分逻辑

donglang 1 mesiac pred
rodič
commit
01a93f427d
20 zmenil súbory, kde vykonal 910 pridanie a 79 odobranie
  1. 1 0
      sckw-common/sckw-common-core/src/main/java/com/sckw/core/common/enums/enums/DictTypeEnum.java
  2. 2 0
      sckw-modules-api/sckw-fleet-api/src/main/java/com/sckw/fleet/api/RemoteFleetService.java
  3. 17 0
      sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/dubbo/RemoteFleetServiceImpl.java
  4. 5 0
      sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/model/KwfDriver.java
  5. 8 0
      sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/repository/KwfDriverRepository.java
  6. 4 3
      sckw-modules/sckw-product/src/main/java/com/sckw/product/controller/KwpGoodsController.java
  7. 6 0
      sckw-modules/sckw-product/src/main/java/com/sckw/product/model/vo/res/GoodsDetail.java
  8. 6 0
      sckw-modules/sckw-product/src/main/java/com/sckw/product/model/vo/res/GoodsList.java
  9. 17 6
      sckw-modules/sckw-product/src/main/java/com/sckw/product/service/KwpGoodsService.java
  10. 38 1
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/handler/AbstractWaybillOrderHandler.java
  11. 11 2
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/handler/CancelHandler.java
  12. 107 7
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/handler/ComeIntoHandler.java
  13. 240 16
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/handler/UnloadingHandler.java
  14. 0 6
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/KwtWaybillOrder.java
  15. 5 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/KwtWaybillOrderNode.java
  16. 1 1
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtWaybillOrderAddressRepository.java
  17. 27 2
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtWaybillOrderNodeRepository.java
  18. 8 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtWaybillOrderRepository.java
  19. 376 4
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/KwtWaybillOrderV1Service.java
  20. 31 31
      sql/2025/12/01/2025_12_04_donglang.sql

+ 1 - 0
sckw-common/sckw-common-core/src/main/java/com/sckw/core/common/enums/enums/DictTypeEnum.java

@@ -54,6 +54,7 @@ public enum DictTypeEnum {
     SETTLEMENT_WAY(" settlement_way", "结算方式"),
     LOAD_UNLOAD_WAY("load_unload_way", "装卸方式"),
     DISPATCHING_TYPE("dispatching_type", "派车方式"),
+    GOODS_SPEC("goods_spec", "商品规格"),
     ;
 
     private final String type;

+ 2 - 0
sckw-modules-api/sckw-fleet-api/src/main/java/com/sckw/fleet/api/RemoteFleetService.java

@@ -120,4 +120,6 @@ public interface RemoteFleetService {
 
     DriverConductRulesVO findDriverConductRulesByEntId(Long entId);
 
+    void updateDriverScore(Long entId, Long driverId, Integer score);
+
 }

+ 17 - 0
sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/dubbo/RemoteFleetServiceImpl.java

@@ -1,7 +1,9 @@
 package com.sckw.fleet.dubbo;
 
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.sckw.core.common.enums.enums.ErrorCodeEnum;
 import com.sckw.core.exception.BusinessException;
+import com.sckw.core.exception.BusinessPlatfromException;
 import com.sckw.core.model.constant.Global;
 import com.sckw.core.utils.BeanUtils;
 import com.sckw.core.utils.CollectionUtils;
@@ -551,4 +553,19 @@ public class RemoteFleetServiceImpl implements RemoteFleetService {
 
         return rulesVO;
     }
+
+    @Override
+    public void updateDriverScore(Long entId, Long driverId, Integer score) {
+        if (score == null) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "加分分数不能为空!");
+        }
+        KwfDriver driver = driverRepository.findByEntAndDriverIds(entId, driverId);
+        if (driver == null) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.DATA_NOT_EXIST, "司机数据不存在!");
+        }
+        int currentScore = driver.getScore() == null || driver.getScore() < 0  ? 0 : driver.getScore();
+        driver.setScore(currentScore + score);
+
+        driverRepository.updateById(driver);
+    }
 }

+ 5 - 0
sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/model/KwfDriver.java

@@ -71,4 +71,9 @@ public class KwfDriver extends BaseModel {
      */
     private Integer authStatus;
 
+    /**
+     * 自动派车评分
+     */
+    private Integer score;
+
 }

+ 8 - 0
sckw-modules/sckw-fleet/src/main/java/com/sckw/fleet/repository/KwfDriverRepository.java

@@ -35,4 +35,12 @@ public class KwfDriverRepository extends ServiceImpl<KwfDriverMapper, KwfDriver>
                 .eq(BaseModel::getDelFlag,0)
                 .in(KwfDriver::getId,driveIdList));
     }
+
+    public KwfDriver findByEntAndDriverIds(Long entId, Long driveId) {
+        return getOne(Wrappers.<KwfDriver>lambdaQuery()
+                .eq(BaseModel::getDelFlag,0)
+                .eq(KwfDriver::getEntId,entId)
+                .eq(KwfDriver::getId,driveId)
+                .last("limit 1"));
+    }
 }

+ 4 - 3
sckw-modules/sckw-product/src/main/java/com/sckw/product/controller/KwpGoodsController.java

@@ -146,15 +146,16 @@ public class KwpGoodsController {
 
 
     /**
-     * @desc: 单个上架
+     * @desc: 批量上架
      * @author: yzc
      * @date: 2023-07-06 8:56
      * @Param id:
      * @return: com.sckw.core.web.response.HttpResult
      */
     @GetMapping("/batchPutOnShelves")
-    public HttpResult batchPutOnShelves(@RequestParam List<Long> id) {
-        kwpGoodsService.batchPutOnShelves(id);
+    public HttpResult batchPutOnShelves(@RequestParam String ids) {
+        List<Long> goodIds = StringUtils.splitStrToList(ids, ",", Long.class);
+        kwpGoodsService.batchPutOnShelves(goodIds);
         return HttpResult.ok("上架成功");
     }
 

+ 6 - 0
sckw-modules/sckw-product/src/main/java/com/sckw/product/model/vo/res/GoodsDetail.java

@@ -160,6 +160,12 @@ public class GoodsDetail {
     @Schema(description = "尺寸大小")
     private String spec;
 
+    /**
+     * 尺寸大小lab
+     */
+    @Schema(description = "尺寸大小")
+    private String specLabel;
+
     /**
      * 预付款最低限额
      */

+ 6 - 0
sckw-modules/sckw-product/src/main/java/com/sckw/product/model/vo/res/GoodsList.java

@@ -87,6 +87,12 @@ public class GoodsList {
     @Schema(description = "尺寸大小")
     private String spec;
 
+    /**
+     * 尺寸大小lab
+     */
+    @Schema(description = "尺寸大小")
+    private String specLabel;
+
     /**
      * 库存数量
      */

+ 17 - 6
sckw-modules/sckw-product/src/main/java/com/sckw/product/service/KwpGoodsService.java

@@ -159,7 +159,7 @@ public class KwpGoodsService {
         goods.setCode(param.getCode())
                 .setAreaCode(address.getCityCode())
                 .setAddressName(address.getName())
-                .setThumb(FileUtils.replaceAll(goods.getThumb()))
+                .setThumb(goods.getThumb())
                 .setEntId(LoginUserHolder.getEntId())
                 .setAddedTime(new Date())
                 .setPriceUnit("元")
@@ -388,16 +388,20 @@ public class KwpGoodsService {
                 DictTypeEnum.UNIT_TYPE.getType(),
                 DictTypeEnum.TAX_RATE.getType(),
                 DictTypeEnum.GOODS_STATUS.getType(),
-                DictTypeEnum.ADDRESS_TYPE.getType()));
+                DictTypeEnum.ADDRESS_TYPE.getType(),
+                DictTypeEnum.GOODS_SPEC.getType()
+                ));
         Map<String, String> productNameMap = new HashMap<>(Global.NUMERICAL_SIXTEEN);
         Map<String, String> unitMap = new HashMap<>(Global.NUMERICAL_SIXTEEN);
         Map<String, String> goodsStatusMap = new HashMap<>(Global.NUMERICAL_SIXTEEN);
         Map<String, String> addressMap = new HashMap<>(Global.NUMERICAL_SIXTEEN);
+        Map<String, String> goodsSpecMap = new HashMap<>(Global.NUMERICAL_SIXTEEN);
         if (CollectionUtils.isNotEmpty(dict)) {
             productNameMap = dict.get(DictTypeEnum.PRODUCT_NAME_TYPE.getType());
             unitMap = dict.get(DictTypeEnum.UNIT_TYPE.getType());
             goodsStatusMap = dict.get(DictTypeEnum.GOODS_STATUS.getType());
             addressMap = dict.get(DictTypeEnum.ADDRESS_TYPE.getType());
+            goodsSpecMap = dict.get(DictTypeEnum.GOODS_SPEC.getType());
         }
         List<SysDictResDto> types = remoteSystemService.queryDictFrontAll(DictTypeEnum.PRODUCT_NAME_TYPE.getType(), detail.getGoodsType());
         if (CollectionUtils.isNotEmpty(types)) {
@@ -406,7 +410,8 @@ public class KwpGoodsService {
         }
         detail.setGoodsTypeLabel(CollectionUtils.isNotEmpty(productNameMap) ? productNameMap.get(detail.getGoodsType()) : null)
                 .setUnitLabel(CollectionUtils.isNotEmpty(unitMap) ? unitMap.get(detail.getUnit()) : null)
-                .setStatusLabel(CollectionUtils.isNotEmpty(goodsStatusMap) ? goodsStatusMap.get(String.valueOf(detail.getStatus())) : null);
+                .setStatusLabel(CollectionUtils.isNotEmpty(goodsStatusMap) ? goodsStatusMap.get(String.valueOf(detail.getStatus())) : null)
+                .setSpecLabel(CollectionUtils.isNotEmpty(goodsSpecMap) ? goodsSpecMap.get(String.valueOf(detail.getSpec())) : null);
         //商品图片信息
         if (Objects.nonNull(goods.getThumb())) {
             detail.setThumb(goods.getThumb());
@@ -492,7 +497,7 @@ public class KwpGoodsService {
                 .set(KwpGoods::getContent, param.getContent())
                 .set(KwpGoods::getSupplyEntId, param.getSupplyEntId())
                 .set(KwpGoods::getRemark, param.getRemark())
-                .set(KwpGoods::getThumb, FileUtils.replaceAll(param.getThumb()))
+                .set(KwpGoods::getThumb, param.getThumb())
                 .set(KwpGoods::getAreaCode, areaCode)
                 .set(KwpGoods::getAddressName, addressName)
                 .eq(KwpGoods::getId, param.getId());
@@ -798,16 +803,21 @@ public class KwpGoodsService {
             entList = remoteSystemService.queryEntCacheByIds(supplyEntIds);
         }
         Map<String, Map<String, String>> dict = remoteSystemService.queryDictByType(List.of(DictTypeEnum.PRODUCT_NAME_TYPE.getType(),
-                DictTypeEnum.UNIT_TYPE.getType(), DictTypeEnum.TAX_RATE.getType(), DictTypeEnum.GOODS_STATUS.getType()));
-        Map<String, String> productNameMap, unitMap, goodsStatusMap;
+                DictTypeEnum.UNIT_TYPE.getType(),
+                DictTypeEnum.TAX_RATE.getType(),
+                DictTypeEnum.GOODS_STATUS.getType(),
+                DictTypeEnum.GOODS_SPEC.getType()));
+        Map<String, String> productNameMap, unitMap, goodsStatusMap, goodsSpecMap;
         if (CollectionUtils.isNotEmpty(dict)) {
             productNameMap = dict.get(DictTypeEnum.PRODUCT_NAME_TYPE.getType());
             unitMap = dict.get(DictTypeEnum.UNIT_TYPE.getType());
             goodsStatusMap = dict.get(DictTypeEnum.GOODS_STATUS.getType());
+            goodsSpecMap = dict.get(DictTypeEnum.GOODS_SPEC.getType());
         } else {
             productNameMap = new HashMap<>(Global.NUMERICAL_SIXTEEN);
             unitMap = new HashMap<>(Global.NUMERICAL_SIXTEEN);
             goodsStatusMap = new HashMap<>(Global.NUMERICAL_SIXTEEN);
+            goodsSpecMap = new HashMap<>(Global.NUMERICAL_SIXTEEN);
         }
         Map<Long, String> entMap = entList.stream().collect(Collectors.toMap(EntCacheResDto::getId, EntCacheResDto::getFirmName, (k1, k2) -> k1));
         list.forEach(e -> {
@@ -817,6 +827,7 @@ public class KwpGoodsService {
             goodsList.setStatusLabel(CollectionUtils.isNotEmpty(goodsStatusMap) ? goodsStatusMap.get(String.valueOf(goodsList.getStatus())) : null)
                     .setGoodsTypeLabel(CollectionUtils.isNotEmpty(productNameMap) ? productNameMap.get(goodsList.getGoodsType()) : null)
                     .setUnitLabel(CollectionUtils.isNotEmpty(unitMap) ? unitMap.get(goodsList.getUnit()) : null)
+                    .setSpecLabel(CollectionUtils.isNotEmpty(goodsSpecMap) ? goodsSpecMap.get(goodsList.getSpec()) : null)
                     .setSupplyEnt(entMap.get(e.getSupplyEntId()))
                     .setThumb(FileUtils.splice(e.getThumb()))
                     .setCreateByName(Objects.nonNull(createUser) ? createUser.getName() : null);

+ 38 - 1
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/handler/AbstractWaybillOrderHandler.java

@@ -2,9 +2,11 @@ package com.sckw.transport.handler;
 
 
 import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.sckw.core.common.enums.enums.ErrorCodeEnum;
 import com.sckw.core.exception.BusinessPlatfromException;
 import com.sckw.core.model.enums.CarWaybillV1Enum;
+import com.sckw.core.utils.CollectionUtils;
 import com.sckw.fleet.api.RemoteFleetService;
 import com.sckw.fleet.api.model.vo.DriverConductRulesVO;
 import com.sckw.fleet.api.model.vo.TruckDispatchCoefficientVO;
@@ -20,7 +22,9 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.util.Date;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
 /**
  * Author: donglang
@@ -242,10 +246,43 @@ public abstract class AbstractWaybillOrderHandler<T extends WaybillOrderProcessP
         return driverRulesVO;
     }
 
+    /**
+     * 查询该司机连续卸货节点数据
+     * @param waybillOrder
+     * @param continuousOnTimes
+     * @return
+     */
+    protected List<KwtWaybillOrderNode> getWaybillOrderNodesByStatus(KwtWaybillOrder waybillOrder, Integer continuousOnTimes, CarWaybillV1Enum waybillV1Enum) {
+        //司机运单数据
+        List<KwtWaybillOrder> waybillOrders = waybillOrderRepository.queryByEntId(waybillOrder.getEntId(), waybillOrder.getDriverId());
+        if (CollectionUtils.isEmpty(waybillOrders)) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.WAYBILL_ORDER_NOT_FOUND, "物流运单不存在!");
+        }
+        List<Long> wOrderIds = waybillOrders.stream().map(KwtWaybillOrder::getId).collect(Collectors.toList());
+        //查询司机的节点数据
+        List<KwtWaybillOrderNode> waybillOrderNodeList = waybillOrderNodeRepository
+                .queryContinuousNodesByDriverId(wOrderIds, waybillOrder.getDriverId(), waybillV1Enum.getCode());
+        if (CollectionUtils.isEmpty(waybillOrderNodeList) || waybillOrderNodeList.size() < continuousOnTimes) {
+            log.info("司机{}按时接节点不足{}次,当前记录数:{}", waybillOrder.getDriverId(), continuousOnTimes, waybillOrderNodeList.size());
+            return null;
+        }
+        return waybillOrderNodeList;
+    }
 
+    /**
+     * 给连续的10次的节点数据打标
+     */
+    protected void updateNode(KwtWaybillOrder waybillOrder, List<Long> nodeIds, Integer continuousOnTimes, Integer continuousOnTimeScore) {
+        KwtWaybillOrderNode updateEntity = new KwtWaybillOrderNode();
+        updateEntity.setContinuous(1);
+        LambdaQueryWrapper<KwtWaybillOrderNode> updateWrapper = new LambdaQueryWrapper<KwtWaybillOrderNode>()
+                .in(KwtWaybillOrderNode::getId, nodeIds);
+        waybillOrderNodeRepository.update(updateEntity, updateWrapper);
+        log.info("司机{}最新{}条节点全部未超时,加分:{},节点ID:{}", waybillOrder.getDriverId(), continuousOnTimes, continuousOnTimeScore, nodeIds);
+    }
 
     /**
-     * 计算两个时间的分钟差值(正数:comeIntoTime 在 takingOrderTime 之后;负数:时间顺序异常;null:时间为空)
+     * 计算两个时间的分钟差值
      * @param startTime 开始时间
      * @param endTime 结束时间
      * @return

+ 11 - 2
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/handler/CancelHandler.java

@@ -123,8 +123,13 @@ public class CancelHandler extends AbstractWaybillOrderHandler<WaybillOrderCance
 
     @Override
     protected void calculateAutoDispatchScore(WaybillOrderCancelParam param, KwtWaybillOrder waybillOrder) {
+        if (true) {
+            //先不执行自动派单逻辑
+            return;
+        }
         // 获取司机行为规则配置(司机违规取消运单分钟数)
         DriverConductRulesVO driverRulesVO = getDriverConductRulesByEntId(waybillOrder.getEntId());
+        Integer illegalCancelOrder = driverRulesVO.getIllegalCancelOrder();
         Integer illegalCancelOrderMinutes = driverRulesVO.getIllegalCancelOrderMinutes();
         if (illegalCancelOrderMinutes == null) {
             log.warn("获取司机违规取消运单分钟数失败:企业{}的司机违规取消运单分钟数配置为空,运单ID:{}", waybillOrder.getEntId(), waybillOrder.getId());
@@ -139,10 +144,14 @@ public class CancelHandler extends AbstractWaybillOrderHandler<WaybillOrderCance
 
         // 计算两个时间的分钟差
         Long timeDiffMinutes = calculateTimeDiffMinutes(takingOrderTime, cancelNodeTime);
-
         if (timeDiffMinutes > illegalCancelOrderMinutes.longValue()) {
             log.info("司机取消运单超时!司机违规取消运单分钟数限制:" + illegalCancelOrderMinutes + "分钟,实际:" + timeDiffMinutes + "分钟");
-            //执行扣分逻辑 TODO DONGLANG
+            //1、更新司机分数(减分)
+            illegalCancelOrder = -Math.abs(illegalCancelOrder);
+            remoteFleetService.updateDriverScore(waybillOrder.getEntId(), waybillOrder.getDriverId(), illegalCancelOrder);
+
+            //2、更新企业分数(减分)
+
         }
 
     }

+ 107 - 7
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/handler/ComeIntoHandler.java

@@ -7,6 +7,7 @@ import com.sckw.core.common.enums.enums.ErrorCodeEnum;
 import com.sckw.core.exception.BusinessPlatfromException;
 import com.sckw.core.model.enums.AddressTypeEnum;
 import com.sckw.core.model.enums.CarWaybillV1Enum;
+import com.sckw.fleet.api.model.vo.DriverConductRulesVO;
 import com.sckw.fleet.api.model.vo.RTruckVo;
 import com.sckw.fleet.api.model.vo.TruckDispatchCoefficientVO;
 import com.sckw.transport.model.KwtWaybillOrder;
@@ -17,9 +18,12 @@ import com.sckw.transport.repository.KwtWaybillOrderTicketRepository;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -107,19 +111,62 @@ public class ComeIntoHandler extends AbstractWaybillOrderHandler<WaybillOrderCme
      * @param waybillOrder
      */
     @Override
+    @Transactional(rollbackFor = Exception.class)
     protected void calculateAutoDispatchScore(WaybillOrderCmeIntoWeighParam param, KwtWaybillOrder waybillOrder) {
+        if (true) {
+            //先不执行自动派单逻辑
+            return;
+        }
+        // 司机到达装货点是否超时(未按时到场)
+        checkArrivedLoadingPointTimeout(waybillOrder);
+
+        //校验连续按时到场次数
+        checkContinuousArriveTimes(waybillOrder);
+
+    }
+
+
+    /**
+     * 校验司机到达装货点是否超时
+     * @param waybillOrder
+     */
+    private void checkArrivedLoadingPointTimeout(KwtWaybillOrder waybillOrder) {
+        //计算司机到达装货点是否超时
+        Boolean isTimeOut = isTimeOut(waybillOrder.getEntId(), waybillOrder.getId());
+        if (isTimeOut) {
+            //1、更新司机分数(减分)
+            DriverConductRulesVO rulesVO = getDriverConductRulesByEntId(waybillOrder.getEntId());
+            Integer notOnTimeArrive = rulesVO.getNotOnTimeArrive();
+            if (notOnTimeArrive == null) {
+                log.warn("该司机扣分失败,企业{}的司机超时到达装货点分数配置为空,运单ID:{}", waybillOrder.getEntId(), waybillOrder.getId());
+                throw new BusinessPlatfromException(ErrorCodeEnum.DATA_NOT_EXIST, "司机超时到达装货点分数配置为空!");
+            }
+            // 扣分:将配置值转为负数实现扣分
+            notOnTimeArrive = -Math.abs(notOnTimeArrive);
+            remoteFleetService.updateDriverScore(waybillOrder.getEntId(), waybillOrder.getDriverId(), notOnTimeArrive);
+
+            //2、更新企业分数(减分)
+
+        }
+    }
+
+    /**
+     * 司机到达装货点是否超时
+     * @param entId
+     */
+    private Boolean isTimeOut(Long entId, Long wOrderId) {
         // 获取自动派单系数配置(司机超时限制)
-        TruckDispatchCoefficientVO truckDispatchVO = getAutoTruckDispatchByEntId(waybillOrder.getEntId());
+        TruckDispatchCoefficientVO truckDispatchVO = getAutoTruckDispatchByEntId(entId);
         Integer driverTimeout = truckDispatchVO.getDriverTimeoutLimit();
-        if (driverTimeout == null) {
-            log.warn("企业{}的司机超时限制配置为空,运单ID:{}", waybillOrder.getEntId(), waybillOrder.getId());
-            throw new BusinessPlatfromException(ErrorCodeEnum.DATA_NOT_EXIST, "司机超时限制配置为空!");
+        if (driverTimeout == null  || driverTimeout < 0) {
+            log.warn("企业{}的司机超时限制配置为空,运单ID:{}", entId,wOrderId);
+            throw new BusinessPlatfromException(ErrorCodeEnum.DATA_NOT_EXIST, "司机超时限制配置不能小于0!");
         }
         //司机接单时间
-        KwtWaybillOrderNode takingOrderNodes = getNodesByOrderId(waybillOrder.getId(), CarWaybillV1Enum.PENDING_VEHICLE.getCode());
+        KwtWaybillOrderNode takingOrderNodes = getNodesByOrderId(wOrderId, CarWaybillV1Enum.PENDING_VEHICLE.getCode());
         Date takingOrderTime = takingOrderNodes.getCreateTime() != null ? takingOrderNodes.getCreateTime() : null;
         //司机到底装货点时间
-        KwtWaybillOrderNode comeIntoNode = getNodesByOrderId(waybillOrder.getId(), CarWaybillV1Enum.REFUSE_TRAFFIC.getCode());
+        KwtWaybillOrderNode comeIntoNode = getNodesByOrderId(wOrderId, CarWaybillV1Enum.REFUSE_TRAFFIC.getCode());
         Date comeIntoTime = comeIntoNode.getCreateTime() != null ? comeIntoNode.getCreateTime() : null;
 
         // 计算两个时间的分钟差
@@ -127,15 +174,68 @@ public class ComeIntoHandler extends AbstractWaybillOrderHandler<WaybillOrderCme
         //超时
         if (timeDiffMinutes > driverTimeout.longValue()) {
             log.info("司机到底装货点耗时超时!司机超时限制:" + driverTimeout + "分钟,实际:" + timeDiffMinutes + "分钟");
-            //执行扣分逻辑 TODO DONGLANG
+            return true;
+        }
+        return false;
+    }
+
 
+    /**
+     * 校验连续按时到场次数
+     * @param waybillOrder
+     */
+    private void checkContinuousArriveTimes(KwtWaybillOrder waybillOrder) {
+        // 获取司机行为规则配置(连续按时到场次数)
+        DriverConductRulesVO driverRulesVO = getDriverConductRulesByEntId(waybillOrder.getEntId());
+        Integer continuousOnTimeArriveTimes = driverRulesVO.getContinuousOnTimeArriveTimes();
+        Integer continuousOnTimeArrive = driverRulesVO.getContinuousOnTimeArrive();
+        if (continuousOnTimeArriveTimes <= 0 || continuousOnTimeArrive <= 0) {
+            log.warn("该司机无需加分,企业{}的连续按时到场配置异常,运单ID:{}", waybillOrder.getEntId(), waybillOrder.getId());
+            throw new BusinessPlatfromException(ErrorCodeEnum.DATA_NOT_EXIST, "连续按时到场次数和分数不能小于等于0!");
         }
 
+        //查询该司机连续到达装货点的节点数据
+        List<KwtWaybillOrderNode> waybillOrderNodeList = getWaybillOrderNodesByStatus(waybillOrder, continuousOnTimeArriveTimes, CarWaybillV1Enum.REFUSE_TRAFFIC);
+        if (waybillOrderNodeList == null)  {
+            return;
+        }
+        //记录连续到场次数
+        int continuousCount = 0;
+        List<Long> qualifiedNodeIds = new ArrayList<>();
+        List<Long> nodeIds = new ArrayList<>();
+        for (KwtWaybillOrderNode orderNode : waybillOrderNodeList) {
+            //计算每次运单到达场地耗时时间
+            Boolean isTimeOut = isTimeOut(waybillOrder.getEntId(), orderNode.getWOrderId());
+            if (isTimeOut) {
+                log.info("司机{}存在超时情况,运单id:{}", orderNode.getDriverId(), orderNode.getWOrderId());
+                return;
+            }
+            continuousCount++;
+            qualifiedNodeIds.add(orderNode.getId());
+
+            // 达到10次,返回最新的10条ID
+            if (continuousCount >= continuousOnTimeArriveTimes) {
+                // 截断前10条
+                nodeIds = qualifiedNodeIds.subList(0, continuousOnTimeArriveTimes);
+                break;
+            }
+        }
+        // 校验是否达到次数
+        if (continuousCount < continuousOnTimeArriveTimes) {
+            log.info("司机{}连续按时到场次数{},未达到配置阈值{}", waybillOrder.getDriverId(), continuousCount, continuousOnTimeArriveTimes);
+            return;
+        }
 
+        //1. 给连续的10次节点数据打标
+        updateNode(waybillOrder, nodeIds, continuousOnTimeArriveTimes, continuousOnTimeArrive);
 
+        //2 .更新司机分数(加分)
+        remoteFleetService.updateDriverScore(waybillOrder.getEntId(), waybillOrder.getDriverId(), continuousOnTimeArrive);
 
+        //3. 更新企业分数(加分)
 
     }
 
 
+
 }

+ 240 - 16
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/handler/UnloadingHandler.java

@@ -6,7 +6,9 @@ import com.sckw.core.exception.BusinessPlatfromException;
 import com.sckw.core.model.constant.Global;
 import com.sckw.core.model.enums.AddressTypeEnum;
 import com.sckw.core.model.enums.CarWaybillV1Enum;
+import com.sckw.core.utils.CollectionUtils;
 import com.sckw.fleet.api.model.vo.DriverConductRulesVO;
+import com.sckw.fleet.api.model.vo.TruckDispatchCoefficientVO;
 import com.sckw.order.api.model.OrderDetailVo;
 import com.sckw.transport.model.*;
 import com.sckw.transport.model.param.WaybillOrderUnloadParam;
@@ -14,14 +16,15 @@ import com.sckw.transport.repository.KwtWaybillOrderSubtaskRepository;
 import com.sckw.transport.utils.DistanceUtils;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
+import org.jetbrains.annotations.NotNull;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
-import java.util.Date;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
+import java.math.RoundingMode;
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 /**
  * Author: donglang
@@ -182,6 +185,7 @@ public class UnloadingHandler extends AbstractWaybillOrderHandler<WaybillOrderUn
         if (this.targetStatus == null) {
             throw new BusinessPlatfromException(ErrorCodeEnum.SYSTEM_ERROR, "[卸货]计算方式为空!");
         }
+        //订单按照装货方式计算
         if (this.targetStatus == Global.NUMERICAL_ONE) {
             // 第一条:卸货已完成
             KwtWaybillOrderNode node1 = getWaybillOrderNode(param, waybillOrder);
@@ -196,6 +200,7 @@ public class UnloadingHandler extends AbstractWaybillOrderHandler<WaybillOrderUn
             waybillOrderNodeRepository.save(node2);
             log.info("记录【完成】节点轨迹成功,节点ID:{}", node2.getId());
         } else {
+            //订单按照卸货货方式计算
             KwtWaybillOrderNode node = getWaybillOrderNode(param, waybillOrder);
             node.setRemark("司机[" + waybillOrder.getDriverName() + "]已上传卸货凭证");
             waybillOrderNodeRepository.save(node);
@@ -205,31 +210,250 @@ public class UnloadingHandler extends AbstractWaybillOrderHandler<WaybillOrderUn
 
     @Override
     protected void calculateAutoDispatchScore(WaybillOrderUnloadParam param, KwtWaybillOrder waybillOrder) {
+        if (true) {
+            //先不执行自动派单逻辑
+            return;
+        }
+        //查询运单装卸货地址
+        List<KwtWaybillOrderAddress> addressList = waybillOrderAddressRepository.queryByWOrderId(waybillOrder.getId());
+        if (CollectionUtils.isEmpty(addressList)) {
+            log.info("物流运单无装卸货地址信息,运单id:{}", waybillOrder.getId());
+            throw new BusinessPlatfromException(ErrorCodeEnum.LOGISTICS_ORDER_NOT_ADDRESS, "物流订单无装卸货地址信息");
+        }
+        Map<Integer, KwtWaybillOrderAddress> addressMap = addressList.stream()
+                .filter(addr -> addr.getAddressType() != null)
+                .collect(Collectors.toMap(KwtWaybillOrderAddress::getAddressType, Function.identity(),
+                        (x,y) ->x));
+
+        //1. 校验司机是否虚假卸货
+        checkFakeUnloadDistance(waybillOrder, addressMap);
+
+        //订单若按照装货方式计算:此时整个运单已经结束,计算运单总耗时。如按照卸货方式计算,运单还未结算,不进行司机加减分
+        if (this.targetStatus == null || this.targetStatus != Global.NUMERICAL_ONE) {
+            log.info("当前运单是按照“卸货方式计算“,运单未完成,跳过超时校验!运单id: {}", waybillOrder.getId());
+            return;
+        }
+
+        //2. 校验司机卸货严重超时(运单完成)
+        checkWaybillCompletionTimeout(waybillOrder, addressMap);
+
+        //3. 校验司机连续准时卸货
+        checkContinuousUnload(waybillOrder, addressMap);
+    }
+
+
+    /**
+     * 校验司机是否虚假卸货
+     * @param waybillOrder
+     * @return
+     */
+    @NotNull
+    private void checkFakeUnloadDistance(KwtWaybillOrder waybillOrder, Map<Integer, KwtWaybillOrderAddress> addressMap) {
+        log.info("校验司机是否虚假卸货:waybillOrderId:{}", waybillOrder.getId());
         // 获取司机行为规则配置(虚假卸货偏差距离)
         DriverConductRulesVO driverRulesVO = getDriverConductRulesByEntId(waybillOrder.getEntId());
+        Integer fakeUnload = driverRulesVO.getFakeUnload();
         Integer distance = driverRulesVO.getFakeUnloadDistance();
-        if (distance == null) {
-            log.warn("获取虚假卸货偏差距离失败:企业{}的虚假卸货偏差距离配置为空,运单ID:{}", waybillOrder.getEntId(), waybillOrder.getId());
-            throw new BusinessPlatfromException(ErrorCodeEnum.DATA_NOT_EXIST, "虚假卸货偏差距离配置为空!");
-        }
-        //查询运单卸货地址
-        KwtWaybillOrderAddress address = waybillOrderAddressRepository.queryByBillOrderIdAndType(waybillOrder.getId(), AddressTypeEnum.SHIPMENT.getCode());
-        if (address == null) {
-            throw new BusinessPlatfromException(ErrorCodeEnum.WAYBILL_ORDER_NOT_TICKET, "物流运单无关联卸货地址信息!");
+        if (distance < 0) {
+            log.warn("获取虚假卸货偏差距离失败:企业{}的虚假卸货偏差距离配置异常,运单ID:{}", waybillOrder.getEntId(), waybillOrder.getId());
+            throw new BusinessPlatfromException(ErrorCodeEnum.DATA_NOT_EXIST, "虚假卸货偏差距离配置异常!");
         }
+        //运单应卸货地址
+        KwtWaybillOrderAddress takeAddress = addressMap.getOrDefault(AddressTypeEnum.TAKE.getCode(), new KwtWaybillOrderAddress());
         //实际卸货地址
         KwtWaybillOrderNode unloadingNodes = getNodesByOrderId(waybillOrder.getId(), CarWaybillV1Enum.COMPLETION_LOADING.getCode());
 
         //应卸货地与实际卸货地之间距离
         double distanceKm = DistanceUtils.calculateDistance(
-                Optional.ofNullable(address.getLng()).map(Double::valueOf).orElse(null),
-                Optional.ofNullable(address.getLat()).map(Double::valueOf).orElse(null),
+                Optional.ofNullable(takeAddress.getLng()).map(Double::valueOf).orElse(null),
+                Optional.ofNullable(takeAddress.getLat()).map(Double::valueOf).orElse(null),
                 Optional.ofNullable(unloadingNodes.getLng()).map(Double::valueOf).orElse(null),
                 Optional.ofNullable(unloadingNodes.getLat()).map(Double::valueOf).orElse(null));
         if (distanceKm > distance) {
-            log.info("司机未在真实卸货地点进行卸货!卸货地址偏差:" + distanceKm + "KM");
-            //执行扣分逻辑 TODO DONGLANG
+            log.info("【司机虚假卸货】运单ID={},卸货地址偏差={}KM",waybillOrder.getId(), distanceKm);
+            //1、更新司机分数(减分)
+            fakeUnload = -Math.abs(fakeUnload);
+            remoteFleetService.updateDriverScore(waybillOrder.getEntId(), waybillOrder.getDriverId(), fakeUnload);
+
+            //2、更新企业分数(减分)
+
+        }
+
+
+    }
+
+    /**
+     * 校验司机运单完成是否超时
+     * @param waybillOrder 运单
+     * @param addressMap  运单地址
+     */
+    protected void checkWaybillCompletionTimeout(KwtWaybillOrder waybillOrder, Map<Integer, KwtWaybillOrderAddress> addressMap) {
+        //查询司机行为规则配置(严重超时倍数)
+        DriverConductRulesVO rulesVO = getDriverConductRulesByEntId(waybillOrder.getEntId());
+        //严重超时倍数
+        BigDecimal timeoutMultiple = rulesVO.getUnloadSeriousTimeoutMultiple();
+        //严重超时分数
+        Integer timeoutScore = rulesVO.getUnloadSeriousTimeout();
+        if (timeoutMultiple == null || timeoutMultiple.compareTo(BigDecimal.ZERO) <= 0 || timeoutScore <= 0) {
+            log.warn("该司机扣分失败,企业{}的司机严重超时数据配置为空,运单ID:{}", waybillOrder.getEntId(), waybillOrder.getId());
+            throw new BusinessPlatfromException(ErrorCodeEnum.DATA_NOT_EXIST, "司机严重超时数据配置异常!");
+        }
+        //校验是否超时
+        Boolean timeOut = isTimeOut(waybillOrder, addressMap, timeoutMultiple);
+        if (timeOut) {
+            //1、更新司机分数(减分)
+            timeoutScore = -Math.abs(timeoutScore);
+            remoteFleetService.updateDriverScore(waybillOrder.getEntId(), waybillOrder.getDriverId(), timeoutScore);
+
+            //2、更新企业分数(减分)
+        }
+
+    }
+
+    /**
+     * 校验司机运单完成是否超时
+     * @param waybillOrder 运单
+     * @param addressMap  运单地址
+     */
+    protected Boolean isTimeOut(KwtWaybillOrder waybillOrder, Map<Integer, KwtWaybillOrderAddress> addressMap, BigDecimal timeoutMultiple) {
+        log.info("校验司机运单完成是否超时:运单ID:{}", waybillOrder.getId());
+        //运单总耗时
+        Long orderTotalTimes = calOrderTotalTimes(waybillOrder);
+        //平台配置的运单单趟总耗时
+        Integer singleTripTotalTimes = calSingleTripTotalTimes(waybillOrder, addressMap);
+        //计算超时阈值
+        BigDecimal threshold = timeoutMultiple.compareTo(BigDecimal.ZERO) == 0 ?
+                new BigDecimal(singleTripTotalTimes) : timeoutMultiple.multiply(new BigDecimal(singleTripTotalTimes));
+        BigDecimal actualTimes = new BigDecimal(orderTotalTimes);
+        if (actualTimes.compareTo(threshold) > 0) {
+            log.warn("【司机运单超时】运单ID:{},实际总耗时:{}分钟,平台单趟耗时:{}分钟", waybillOrder.getId(), orderTotalTimes, singleTripTotalTimes);
+            return true;
         }
+        return false;
+    }
+
+    /**
+     * 计算司机接单到结束的总耗时
+     * @param waybillOrder
+     * @return
+     */
+    private Long calOrderTotalTimes(KwtWaybillOrder waybillOrder) {
+        //司机接单时间
+        KwtWaybillOrderNode takingOrderNodes = getNodesByOrderId(waybillOrder.getId(), CarWaybillV1Enum.PENDING_VEHICLE.getCode());
+        Date takingOrderTime = takingOrderNodes.getCreateTime() != null ? takingOrderNodes.getCreateTime() : null;
+        //司机完成运单时间
+        KwtWaybillOrderNode overOrderNodes = getNodesByOrderId(waybillOrder.getId(), CarWaybillV1Enum.WAIT_UNLOADING.getCode());
+        Date overOrderTime = overOrderNodes.getCreateTime() != null ? overOrderNodes.getCreateTime() : null;
+        //运单总耗时(分钟)
+        Long orderTotalTimes = calculateTimeDiffMinutes(takingOrderTime, overOrderTime);
+        log.info("司机完成运单总耗时:{}", orderTotalTimes);
+        return orderTotalTimes;
+    }
+
+    /**
+     * 计算平台配置的运单单趟总耗时
+     * @param waybillOrder
+     * @param addressMap
+     * @return
+     */
+    private Integer calSingleTripTotalTimes(KwtWaybillOrder waybillOrder, Map<Integer, KwtWaybillOrderAddress> addressMap) {
+        // 获取自动派单系数配置(单趟耗时 = 装货时间 + 卸货时间 + 运输时间 + 返回时间)
+        TruckDispatchCoefficientVO truckDispatchVO = getAutoTruckDispatchByEntId(waybillOrder.getEntId());
+        //装货时长
+        Integer vehicleLoadingHours = truckDispatchVO.getVehicleLoadingHours();
+        //卸货货时长
+        Integer vehicleUnloadingHours = truckDispatchVO.getVehicleUnloadingHours();
+        //平均行驶速度
+        Integer vehicleAvgSpeed = truckDispatchVO.getVehicleAvgSpeed();
+        if (vehicleLoadingHours <= 0 || vehicleUnloadingHours <= 0 || vehicleAvgSpeed <= 0) {
+            log.warn("企业{}的司机装货时长/卸货时长/平均行驶速度配置数据异常(装货:{},卸货:{},速度:{}),运单ID:{}",
+                    waybillOrder.getEntId(), vehicleLoadingHours, vehicleUnloadingHours, vehicleAvgSpeed, waybillOrder.getId());
+            throw new BusinessPlatfromException(ErrorCodeEnum.DATA_NOT_EXIST, "司机配置的装货时长/卸货时长/平均行驶速度需要≥0!");
+        }
+        //查询运单装卸货地址
+        KwtWaybillOrderAddress shipmentAddress = addressMap.getOrDefault(AddressTypeEnum.SHIPMENT.getCode(), new KwtWaybillOrderAddress());
+        KwtWaybillOrderAddress takeAddress = addressMap.getOrDefault(AddressTypeEnum.TAKE.getCode(), new KwtWaybillOrderAddress());
+
+        //应卸货地与实际卸货地之间距离
+        double twoPlaceDistanceKm = DistanceUtils.calculateDistance(
+                Optional.ofNullable(takeAddress.getLng()).map(Double::valueOf).orElse(null),
+                Optional.ofNullable(takeAddress.getLat()).map(Double::valueOf).orElse(null),
+                Optional.ofNullable(shipmentAddress.getLng()).map(Double::valueOf).orElse(null),
+                Optional.ofNullable(shipmentAddress.getLat()).map(Double::valueOf).orElse(null));
+        //运输时间/返回时间(小时) = 两地距离除以平均行驶速度
+        if (twoPlaceDistanceKm == 0.0 || Double.isNaN(twoPlaceDistanceKm)) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "物流运单装货地址有误!");
+        }
+        BigDecimal bdDividend = BigDecimal.valueOf(twoPlaceDistanceKm);
+        BigDecimal bdDivisor = BigDecimal.valueOf(vehicleAvgSpeed);
+        //运输时间小时
+        BigDecimal transportTimeHours = bdDividend.divide(bdDivisor, 4, RoundingMode.HALF_UP);
+        //运输时间分钟取整
+        Integer transportTimeMinutes = transportTimeHours.multiply(BigDecimal.valueOf(60)).setScale(0, RoundingMode.HALF_UP).intValue();
+        //往返时间=运输时间
+        Integer returnTimeMinutes = transportTimeMinutes;
+        //单趟总耗时
+        Integer singleTripTotalTimes = vehicleLoadingHours + vehicleUnloadingHours + transportTimeMinutes + returnTimeMinutes;
+        log.info("平台配置的运单单趟总耗时:{}", singleTripTotalTimes);
+        return singleTripTotalTimes;
 
     }
+
+    /**
+     * 校验连续准时卸货次数
+     * @param waybillOrder
+     */
+    private void checkContinuousUnload(KwtWaybillOrder waybillOrder, Map<Integer, KwtWaybillOrderAddress> addressMap) {
+        // 获取司机行为规则配置(连续准时卸货次数)
+        DriverConductRulesVO driverRulesVO = getDriverConductRulesByEntId(waybillOrder.getEntId());
+        //连续卸货次数
+        Integer continuousOnTimes = driverRulesVO.getContinuousOnTimeUnloadTimes();
+        //连续卸货加分分数
+        Integer continuousOnTimeUnload = driverRulesVO.getContinuousOnTimeUnload();
+        if (continuousOnTimes <= 0 || continuousOnTimeUnload <= 0) {
+            log.warn("该司机无需加分,企业{}的连续按时到场配置异常,运单ID:{}", waybillOrder.getEntId(), waybillOrder.getId());
+            throw new BusinessPlatfromException(ErrorCodeEnum.DATA_NOT_EXIST, "连续准时卸货次数和分数不能小于等于0!");
+        }
+
+        //查询该司机连续准时卸货的节点数据
+        List<KwtWaybillOrderNode> waybillOrderNodeList = getWaybillOrderNodesByStatus(waybillOrder, continuousOnTimes, CarWaybillV1Enum.WAIT_UNLOADING);
+        if (waybillOrderNodeList == null)  {
+            return;
+        }
+        //记录连续卸货次数
+        int continuousCount = 0;
+        List<Long> qualifiedNodeIds = new ArrayList<>();
+        List<Long> nodeIds = new ArrayList<>();
+        for (KwtWaybillOrderNode orderNode : waybillOrderNodeList) {
+            KwtWaybillOrder wOrder = getWaybillOrder(orderNode.getWOrderId());
+            Boolean timeOut = isTimeOut(wOrder, addressMap, BigDecimal.ZERO);
+            if (timeOut) {
+                log.info("司机{}存在超时情况,运单id:{}", orderNode.getDriverId(), orderNode.getWOrderId());
+                return;
+            }
+            continuousCount++;
+            qualifiedNodeIds.add(orderNode.getId());
+            // 达到10次,返回最新的10条ID
+            if (continuousCount >= continuousOnTimes) {
+                // 截断前10条
+                nodeIds = qualifiedNodeIds.subList(0, continuousOnTimes);
+                break;
+            }
+        }
+        // 校验是否达到次数
+        if (continuousCount < continuousOnTimes) {
+            log.info("司机{}连续准时卸货次数{},未达到配置阈值{}", waybillOrder.getDriverId(), continuousCount, continuousOnTimes);
+            return;
+        }
+        //1. 给连续的10次节点数据打标
+        updateNode(waybillOrder, nodeIds, continuousCount, continuousOnTimeUnload);
+
+        //2 .更新司机分数(加分)
+        remoteFleetService.updateDriverScore(waybillOrder.getEntId(), waybillOrder.getDriverId(), continuousOnTimeUnload);
+
+        //3. 更新企业分数(加分)
+
+    }
+
+
 }

+ 0 - 6
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/KwtWaybillOrder.java

@@ -32,12 +32,6 @@ public class KwtWaybillOrder implements Serializable {
      */
     private Long lOrderId;
 
-    /**
-     * 运单ID
-     */
-    @TableField(exist = false)
-    private String wOrderId;
-
     /**
      * 编号
      */

+ 5 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/KwtWaybillOrderNode.java

@@ -98,6 +98,11 @@ public class KwtWaybillOrderNode implements Serializable {
      */
     private String remark;
 
+    /**
+     * 是否已用于连续加分
+     */
+    private Integer continuous;
+
     /**
      * 创建时间
      */

+ 1 - 1
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtWaybillOrderAddressRepository.java

@@ -37,7 +37,7 @@ public class KwtWaybillOrderAddressRepository extends ServiceImpl<KwtWaybillOrde
     public KwtWaybillOrderAddress queryByBillOrderIdAndType(Long wOrderId, Integer type) {
         return getOne(Wrappers.<KwtWaybillOrderAddress>lambdaQuery()
                 .eq(KwtWaybillOrderAddress::getWOrderId, wOrderId)
-                .eq(KwtWaybillOrderAddress::getType, type)
+                .eq(KwtWaybillOrderAddress::getAddressType, type)
                 .eq(KwtWaybillOrderAddress::getDelFlag,0));
     }
 }

+ 27 - 2
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtWaybillOrderNodeRepository.java

@@ -42,10 +42,35 @@ public class KwtWaybillOrderNodeRepository extends ServiceImpl<KwtWaybillOrderNo
         return getOne(Wrappers.<KwtWaybillOrderNode>lambdaQuery()
                 .eq(KwtWaybillOrderNode::getWOrderId, wOrderId)
                 .eq(KwtWaybillOrderNode::getOrderStatus, status)
-                .orderByAsc(KwtWaybillOrderNode::getCreateTime)
-                .orderByAsc(KwtWaybillOrderNode::getId));
+                .orderByDesc(KwtWaybillOrderNode::getCreateTime)
+                .orderByDesc(KwtWaybillOrderNode::getId));
     }
 
+    /**
+     * 查询司机的运单节点数据
+     * @param wOrderIds
+     * @return
+     */
+    public List<KwtWaybillOrderNode> queryContinuousNodesByDriverId(List<Long> wOrderIds, Long driverId, Integer status) {
+        return list(Wrappers.<KwtWaybillOrderNode>lambdaQuery()
+                .in(KwtWaybillOrderNode::getWOrderId, wOrderIds)
+                .eq(KwtWaybillOrderNode::getDriverId, driverId)
+                .eq(KwtWaybillOrderNode::getOrderStatus, status)
+                .eq(KwtWaybillOrderNode::getContinuous, 0)
+                .orderByAsc(KwtWaybillOrderNode::getCreateTime));
+    }
+
+    /**
+     * 通过运单id查询运单节点数据
+     * @param wOrderIds
+     * @return
+     */
+    public List<KwtWaybillOrderNode> queryNodesByOrderIds(List<Long> wOrderIds, Integer status) {
+        return list(Wrappers.<KwtWaybillOrderNode>lambdaQuery()
+                .in(KwtWaybillOrderNode::getWOrderId, wOrderIds)
+                .eq(KwtWaybillOrderNode::getOrderStatus, status)
+                .orderByDesc(KwtWaybillOrderNode::getCreateTime));
+    }
 
 
 }

+ 8 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/repository/KwtWaybillOrderRepository.java

@@ -282,4 +282,12 @@ public class KwtWaybillOrderRepository extends ServiceImpl<KwtWaybillOrderMapper
                 .eq(KwtWaybillOrder::getStatus, 1)
                 .last("limit 1"));
     }
+
+    public List<KwtWaybillOrder> queryByEntId(Long entId, Long driverId) {
+        return list(Wrappers.<KwtWaybillOrder>lambdaQuery()
+                        .eq(KwtWaybillOrder::getEntId, entId)
+                        .eq(KwtWaybillOrder::getDriverId, driverId)
+                        .eq(KwtWaybillOrder::getDelFlag, 0)
+                        .orderByAsc(KwtWaybillOrder::getUpdateTime));
+    }
 }

+ 376 - 4
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/KwtWaybillOrderV1Service.java

@@ -4,8 +4,8 @@ import cn.hutool.core.bean.BeanUtil;
 import com.alibaba.fastjson2.JSON;
 import com.alibaba.fastjson2.JSONObject;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
 import com.google.common.collect.Lists;
@@ -18,7 +18,6 @@ import com.sckw.core.exception.BusinessException;
 import com.sckw.core.exception.BusinessPlatfromException;
 import com.sckw.core.model.constant.Global;
 import com.sckw.core.model.constant.NumberConstant;
-import com.sckw.core.model.constant.UrlConstants;
 import com.sckw.core.model.enums.*;
 import com.sckw.core.model.page.PageHelperUtil;
 import com.sckw.core.model.page.PageResult;
@@ -27,8 +26,8 @@ import com.sckw.core.utils.*;
 import com.sckw.core.web.constant.CommonConstants;
 import com.sckw.core.web.constant.HttpStatus;
 import com.sckw.core.web.context.LoginUserHolder;
-import com.sckw.core.web.response.HttpResult;
 import com.sckw.core.web.response.BaseResult;
+import com.sckw.core.web.response.HttpResult;
 import com.sckw.core.web.response.result.PageDataResult;
 import com.sckw.excel.utils.DateUtil;
 import com.sckw.fleet.api.RemoteFleetService;
@@ -45,7 +44,6 @@ import com.sckw.system.api.RemoteSystemService;
 import com.sckw.system.api.RemoteUserService;
 import com.sckw.system.api.model.dto.res.*;
 import com.sckw.transport.api.feign.VehicleTraceClient;
-import com.sckw.transport.api.model.vo.VehicleTraceResponse;
 import com.sckw.transport.common.config.UrlConfigProperties;
 import com.sckw.transport.dao.*;
 import com.sckw.transport.model.*;
@@ -80,6 +78,7 @@ import java.math.RoundingMode;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -3655,11 +3654,384 @@ public class KwtWaybillOrderV1Service {
             if (!b ){
                throw new BusinessException("完善单证失败");
             }
+
             return Boolean.TRUE;
         }
+        //计算司机分值
+        calculateAutoDispatchScore(billOrder, status);
+
         return Boolean.FALSE;
     }
 
+    /**
+     * 自动派单计算司机分值
+     * @param billOrder
+     */
+    protected void calculateAutoDispatchScore(KwtWaybillOrder billOrder, Integer status) {
+        if (true) {
+            //先不执行自动派单逻辑
+            return;
+        }
+        //查询运单装卸货地址
+        List<KwtWaybillOrderAddress> addressList = waybillOrderAddressRepository.queryByWOrderId(billOrder.getId());
+        if (CollectionUtils.isEmpty(addressList)) {
+            log.info("物流运单无装卸货地址信息,运单id:{}", billOrder.getId());
+            throw new BusinessPlatfromException(ErrorCodeEnum.LOGISTICS_ORDER_NOT_ADDRESS, "物流订单无装卸货地址信息");
+        }
+        Map<Integer, KwtWaybillOrderAddress> addressMap = addressList.stream()
+                .filter(addr -> addr.getAddressType() != null)
+                .collect(Collectors.toMap(KwtWaybillOrderAddress::getAddressType, Function.identity(),
+                        (x,y) ->x));
+
+        // 获取司机行为规则配置(司机违规取消运单分钟数)
+        DriverConductRulesVO driverRulesVO = remoteFleetService.findDriverConductRulesByEntId(billOrder.getEntId());
+        if (driverRulesVO == null) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.DATA_NOT_EXIST, "未找到司机行为规则数据!");
+        }
+
+
+        //1. 运单单据错误被驳回
+        checkRefuseScore(billOrder, status, driverRulesVO);
+
+        //运单没通过,不执行以下逻辑
+        if (!Objects.equals(status, CarWaybillV1Enum.WAIT_UNLOADING.getCode())) {
+            return;
+        }
+
+        //2. 校验司机运单完成是否超时
+        checkCompletionTimeout(billOrder, addressMap, driverRulesVO);
+
+        //3. 校验司机连续准时卸货(审核通过)
+        checkContinuousUnload(billOrder, addressMap);
+
+        //4. 校验司机连续准确填写卸货信息(审核通过)
+        checkContinuousPass(billOrder, addressMap);
+
+    }
+
+    /**
+     * 运单被驳回,扣减司机分数
+     * @param waybillOrder
+     */
+    private void checkRefuseScore(KwtWaybillOrder waybillOrder, Integer status, DriverConductRulesVO driverRulesVO) {
+        //运单驳回
+        if (!Objects.equals(status, CarWaybillV1Enum.COMPLETION_UNLOADING.getCode())) {
+            return;
+        }
+        //单据错误驳回分数
+        Integer documentErrorMissing = driverRulesVO.getDocumentErrorMissing();
+        if (documentErrorMissing <= 0) {
+            log.warn("司机扣分失败:企业{}的司机单据错误/缺失分数配置需大于0,运单ID:{}", waybillOrder.getEntId(), waybillOrder.getId());
+            throw new BusinessPlatfromException(ErrorCodeEnum.DATA_NOT_EXIST, "司机扣分失败,单据错误/缺失分数配置需大于0!");
+        }
+
+        //1、更新司机分数(减分)
+        documentErrorMissing = -Math.abs(documentErrorMissing);
+        remoteFleetService.updateDriverScore(waybillOrder.getEntId(), waybillOrder.getDriverId(), documentErrorMissing);
+
+        //2、更新企业分数(减分)
+
+
+
+
+    }
+
+    /**
+     * 校验司机运单完成是否超时
+     * @param waybillOrder
+     */
+    private void checkCompletionTimeout(KwtWaybillOrder waybillOrder, Map<Integer, KwtWaybillOrderAddress> addressMap,
+                                        DriverConductRulesVO driverRulesVO) {
+        //单趟严重超时分数
+        BigDecimal timeoutMultiple = driverRulesVO.getUnloadSeriousTimeoutMultiple();
+
+        //单趟严重超时分数
+        Integer timeoutScore = driverRulesVO.getUnloadSeriousTimeout();
+        if (timeoutScore <= 0) {
+            log.warn("司机扣分失败:企业{}的司机单趟严重超时分数配置需大于0,运单ID:{}", waybillOrder.getEntId(), waybillOrder.getId());
+            throw new BusinessPlatfromException(ErrorCodeEnum.DATA_NOT_EXIST, "司机扣分失败,司机单趟严重超时分数配置需大于0!");
+        }
+
+        //校验司机运单完成是否超时
+        Boolean isTimeOut = isTimeOut(waybillOrder, addressMap, timeoutMultiple);
+        if (isTimeOut) {
+            //1、更新司机分数(减分)
+            timeoutScore = -Math.abs(timeoutScore);
+            remoteFleetService.updateDriverScore(waybillOrder.getEntId(), waybillOrder.getDriverId(), timeoutScore);
+
+            //2、更新企业分数(减分)
+        }
+    }
+
+    /**
+     * 校验司机运单完成是否超时
+     * @param waybillOrder 运单
+     * @param addressMap  运单地址
+     */
+    protected Boolean isTimeOut(KwtWaybillOrder waybillOrder, Map<Integer, KwtWaybillOrderAddress> addressMap, BigDecimal timeoutMultiple) {
+        log.info("校验司机运单完成是否超时:运单ID:{}", waybillOrder.getId());
+        //运单总耗时
+        Long orderTotalTimes = calOrderTotalTimes(waybillOrder);
+        //平台配置的运单单趟总耗时
+        Integer singleTripTotalTimes = calSingleTripTotalTimes(waybillOrder, addressMap);
+        //计算超时阈值
+        BigDecimal threshold = timeoutMultiple.compareTo(BigDecimal.ZERO) == 0 ?
+                new BigDecimal(singleTripTotalTimes) : timeoutMultiple.multiply(new BigDecimal(singleTripTotalTimes));
+        BigDecimal actualTimes = new BigDecimal(orderTotalTimes);
+        if (actualTimes.compareTo(threshold) > 0) {
+            log.warn("【司机运单超时】运单ID:{},实际总耗时:{}分钟,平台单趟耗时:{}分钟", waybillOrder.getId(), orderTotalTimes, singleTripTotalTimes);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 计算司机接单到结束的总耗时
+     * @param waybillOrder
+     * @return
+     */
+    private Long calOrderTotalTimes(KwtWaybillOrder waybillOrder) {
+        //司机接单时间
+        KwtWaybillOrderNode takingOrderNodes = kwtWaybillOrderNodeRepository.queryNodesByOrderId(waybillOrder.getId(), CarWaybillV1Enum.PENDING_VEHICLE.getCode());
+        if (takingOrderNodes == null) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.WAYBILL_NODE_NOT_EXIST, "未找到关联的运单节点数据!");
+        }
+        Date takingOrderTime = takingOrderNodes.getCreateTime() != null ? takingOrderNodes.getCreateTime() : null;
+        //司机完成运单时间
+        KwtWaybillOrderNode overOrderNodes = kwtWaybillOrderNodeRepository.queryNodesByOrderId(waybillOrder.getId(), CarWaybillV1Enum.WAIT_UNLOADING.getCode());
+        if (overOrderNodes == null) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.WAYBILL_NODE_NOT_EXIST, "未找到关联的运单节点数据!");
+        }
+        Date overOrderTime = overOrderNodes.getCreateTime() != null ? overOrderNodes.getCreateTime() : null;
+        //运单总耗时(分钟)
+        Long orderTotalTimes = calculateTimeDiffMinutes(takingOrderTime, overOrderTime);
+        log.info("司机完成运单总耗时:{}", orderTotalTimes);
+        return orderTotalTimes;
+    }
+
+    /**
+     * 计算平台配置的运单单趟总耗时
+     * @param waybillOrder
+     * @param addressMap
+     * @return
+     */
+    private Integer calSingleTripTotalTimes(KwtWaybillOrder waybillOrder, Map<Integer, KwtWaybillOrderAddress> addressMap) {
+        // 获取自动派单系数配置(单趟耗时 = 装货时间 + 卸货时间 + 运输时间 + 返回时间)
+        TruckDispatchCoefficientVO truckDispatchVO = remoteFleetService.findAutoTruckDispatchByEntId(waybillOrder.getEntId());
+        if (truckDispatchVO == null) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.DATA_NOT_EXIST, "未找到自动派车系数!");
+        }
+        //装货时长
+        Integer vehicleLoadingHours = truckDispatchVO.getVehicleLoadingHours();
+        //卸货货时长
+        Integer vehicleUnloadingHours = truckDispatchVO.getVehicleUnloadingHours();
+        //平均行驶速度
+        Integer vehicleAvgSpeed = truckDispatchVO.getVehicleAvgSpeed();
+        if (vehicleLoadingHours <= 0 || vehicleUnloadingHours <= 0 || vehicleAvgSpeed <= 0) {
+            log.warn("企业{}的司机装货时长/卸货时长/平均行驶速度配置数据异常(装货:{},卸货:{},速度:{}),运单ID:{}",
+                    waybillOrder.getEntId(), vehicleLoadingHours, vehicleUnloadingHours, vehicleAvgSpeed, waybillOrder.getId());
+            throw new BusinessPlatfromException(ErrorCodeEnum.DATA_NOT_EXIST, "司机配置的装货时长/卸货时长/平均行驶速度需要≥0!");
+        }
+        //查询运单装卸货地址
+        KwtWaybillOrderAddress shipmentAddress = addressMap.getOrDefault(AddressTypeEnum.SHIPMENT.getCode(), new KwtWaybillOrderAddress());
+        KwtWaybillOrderAddress takeAddress = addressMap.getOrDefault(AddressTypeEnum.TAKE.getCode(), new KwtWaybillOrderAddress());
+
+        //应卸货地与实际卸货地之间距离
+        double twoPlaceDistanceKm = DistanceUtils.calculateDistance(
+                Optional.ofNullable(takeAddress.getLng()).map(Double::valueOf).orElse(null),
+                Optional.ofNullable(takeAddress.getLat()).map(Double::valueOf).orElse(null),
+                Optional.ofNullable(shipmentAddress.getLng()).map(Double::valueOf).orElse(null),
+                Optional.ofNullable(shipmentAddress.getLat()).map(Double::valueOf).orElse(null));
+        //运输时间/返回时间(小时) = 两地距离除以平均行驶速度
+        if (twoPlaceDistanceKm == 0.0 || Double.isNaN(twoPlaceDistanceKm)) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "物流运单装货地址有误!");
+        }
+        BigDecimal bdDividend = BigDecimal.valueOf(twoPlaceDistanceKm);
+        BigDecimal bdDivisor = BigDecimal.valueOf(vehicleAvgSpeed);
+        //运输时间小时
+        BigDecimal transportTimeHours = bdDividend.divide(bdDivisor, 4, RoundingMode.HALF_UP);
+        //运输时间分钟取整
+        Integer transportTimeMinutes = transportTimeHours.multiply(BigDecimal.valueOf(60)).setScale(0, RoundingMode.HALF_UP).intValue();
+        //往返时间=运输时间
+        Integer returnTimeMinutes = transportTimeMinutes;
+        //单趟总耗时
+        Integer singleTripTotalTimes = vehicleLoadingHours + vehicleUnloadingHours + transportTimeMinutes + returnTimeMinutes;
+        log.info("平台配置的运单单趟总耗时:{}", singleTripTotalTimes);
+        return singleTripTotalTimes;
+
+    }
+
+
+    /**
+     * 计算两个时间的分钟差值
+     * @param startTime 开始时间
+     * @param endTime 结束时间
+     * @return
+     */
+    private Long calculateTimeDiffMinutes(Date startTime, Date endTime) {
+        // 校验
+        if (startTime == null || endTime == null) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR,
+                    "计算时间差失败:,startTime=[" + startTime + "], endTime=[" + endTime + "]");
+        }
+        // 计算时间戳差值(毫秒)
+        long diffMillis = endTime.getTime() - startTime.getTime();
+        long diffMinutes = TimeUnit.MILLISECONDS.toMinutes(diffMillis);
+        // 时间顺序异常提醒(结束时间早于开始时间)
+        if (diffMinutes < 0) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR,
+                    "时间顺序异常,结束时间早于开始时间,startTime=[" + startTime + "], endTime=[" + endTime + "], 差值:[" + diffMinutes + "]分钟");
+        }
+        return diffMinutes;
+    }
+
+
+    /**
+     * 校验司机连续准时卸货
+     * @param waybillOrder
+     */
+    private void checkContinuousUnload(KwtWaybillOrder waybillOrder, Map<Integer, KwtWaybillOrderAddress> addressMap) {
+        // 获取司机行为规则配置(连续准时卸货次数)
+        DriverConductRulesVO driverRulesVO = remoteFleetService.findDriverConductRulesByEntId(waybillOrder.getEntId());
+        if (driverRulesVO == null) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.DATA_NOT_EXIST, "未找到司机行为规则数据!");
+        }
+        //连续卸货次数
+        Integer continuousOnTimes = driverRulesVO.getContinuousOnTimeUnloadTimes();
+        //连续卸货加分分数
+        Integer continuousOnTimeUnload = driverRulesVO.getContinuousOnTimeUnload();
+        if (continuousOnTimes <= 0 || continuousOnTimeUnload <= 0) {
+            log.warn("该司机无需加分,获取连续准时卸货数据失败:企业{}的连续按时到场配置需大于0,运单ID:{}", waybillOrder.getEntId(), waybillOrder.getId());
+            throw new BusinessPlatfromException(ErrorCodeEnum.DATA_NOT_EXIST, "连续准时卸货次数和分数需大于0!");
+        }
+
+        //查询该司机连续审核通过节点数据
+        List<KwtWaybillOrderNode> waybillOrderNodeList = getWaybillOrderNodesByStatus(waybillOrder, continuousOnTimes, CarWaybillV1Enum.WAIT_UNLOADING);
+        if (waybillOrderNodeList == null)  {
+            return;
+        }
+        //记录连续审核通过次数
+        int continuousCount = 0;
+        List<Long> qualifiedNodeIds = new ArrayList<>();
+        List<Long> nodeIds = new ArrayList<>();
+        for (KwtWaybillOrderNode orderNode : waybillOrderNodeList) {
+            KwtWaybillOrder wOrder = kwtWaybillOrderRepository.queryByBillOrderId(orderNode.getWOrderId());
+            Boolean timeOut = isTimeOut(wOrder, addressMap, BigDecimal.ZERO);
+            if (timeOut) {
+                log.info("司机{}存在超时情况,运单id:{}", orderNode.getDriverId(), orderNode.getWOrderId());
+                return;
+            }
+            continuousCount++;
+            qualifiedNodeIds.add(orderNode.getId());
+            // 达到10次,返回最新的10条ID
+            if (continuousCount >= continuousOnTimes) {
+                // 截断前10条
+                nodeIds = qualifiedNodeIds.subList(0, continuousOnTimes);
+                break;
+            }
+        }
+        // 校验是否达到次数
+        if (continuousCount < continuousOnTimes) {
+            log.info("司机{}连续卸货完成次数{},未达到配置阈值{}", waybillOrder.getDriverId(), continuousCount, continuousOnTimes);
+            return;
+        }
+        //1. 给连续的10次节点数据打标
+        updateNode(waybillOrder, nodeIds, continuousOnTimeUnload);
+
+        //2 .更新司机分数(加分)
+        remoteFleetService.updateDriverScore(waybillOrder.getEntId(), waybillOrder.getDriverId(), continuousOnTimeUnload);
+
+        //3. 更新企业分数(加分)
+    }
+
+    /**
+     * 校验司机连续准确填写卸货信息
+     * @param waybillOrder
+     */
+    private void checkContinuousPass(KwtWaybillOrder waybillOrder, Map<Integer, KwtWaybillOrderAddress> addressMap) {
+        // 获取司机行为规则配置(连续准确填写卸货信息次数)
+        DriverConductRulesVO driverRulesVO = remoteFleetService.findDriverConductRulesByEntId(waybillOrder.getEntId());
+        if (driverRulesVO == null) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.DATA_NOT_EXIST, "未找到司机行为规则数据!");
+        }
+        //连续卸货次数
+        Integer continuousAccurateUnloadTimes = driverRulesVO.getContinuousAccurateUnloadTimes();
+        //连续卸货加分分数
+        Integer continuousAccurateUnload = driverRulesVO.getContinuousAccurateUnload();
+        if (continuousAccurateUnloadTimes <= 0 || continuousAccurateUnload <= 0) {
+            log.warn("该司机无需加分,获取连续准确填写卸货信息数据失败:企业{}的连续按时到场配置异常,运单ID:{}", waybillOrder.getEntId(), waybillOrder.getId());
+            throw new BusinessPlatfromException(ErrorCodeEnum.DATA_NOT_EXIST, "连续准确填写卸货信息次数和分数不能小于等于0!");
+        }
+
+        //查询该司机连续审核通过节点数据
+        List<KwtWaybillOrderNode> waybillOrderNodeList = getWaybillOrderNodesByStatus(waybillOrder, continuousAccurateUnloadTimes, CarWaybillV1Enum.WAIT_UNLOADING);
+        if (waybillOrderNodeList == null)  {
+            return;
+        }
+        //记录连续审核通过次数
+        int continuousCount = 0;
+        List<Long> qualifiedNodeIds = new ArrayList<>();
+        List<Long> nodeIds = new ArrayList<>();
+        for (KwtWaybillOrderNode orderNode : waybillOrderNodeList) {
+            continuousCount++;
+            qualifiedNodeIds.add(orderNode.getId());
+            // 达到10次,返回最新的10条ID
+            if (continuousCount >= continuousAccurateUnloadTimes) {
+                // 截断前10条
+                nodeIds = qualifiedNodeIds.subList(0, continuousAccurateUnloadTimes);
+                break;
+            }
+        }
+        // 校验是否达到次数
+        if (continuousCount < continuousAccurateUnloadTimes) {
+            log.info("司机{}连续准确填写卸货信息次数{},未达到配置阈值{}", waybillOrder.getDriverId(), continuousCount, continuousAccurateUnloadTimes);
+            return;
+        }
+        //1. 给连续的10次节点数据打标
+        updateNode(waybillOrder, nodeIds, continuousAccurateUnloadTimes);
+
+        //2 .更新司机分数(加分)
+        remoteFleetService.updateDriverScore(waybillOrder.getEntId(), waybillOrder.getDriverId(), continuousAccurateUnload);
+
+        //3. 更新企业分数(加分)
+    }
+
+    /**
+     * 查询该司机连续卸货节点数据
+     * @param waybillOrder
+     * @param continuousOnTimes
+     * @return
+     */
+    protected List<KwtWaybillOrderNode> getWaybillOrderNodesByStatus(KwtWaybillOrder waybillOrder, Integer continuousOnTimes, CarWaybillV1Enum waybillV1Enum) {
+        //司机运单数据
+        List<KwtWaybillOrder> waybillOrders = kwtWaybillOrderRepository.queryByEntId(waybillOrder.getEntId(), waybillOrder.getDriverId());
+        if (CollectionUtils.isEmpty(waybillOrders)) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.WAYBILL_ORDER_NOT_FOUND, "物流运单不存在!");
+        }
+        List<Long> wOrderIds = waybillOrders.stream().map(KwtWaybillOrder::getId).collect(Collectors.toList());
+        //查询司机的节点数据
+        List<KwtWaybillOrderNode> waybillOrderNodeList = kwtWaybillOrderNodeRepository
+                .queryContinuousNodesByDriverId(wOrderIds, waybillOrder.getDriverId(), waybillV1Enum.getCode());
+        if (CollectionUtils.isEmpty(waybillOrderNodeList) || waybillOrderNodeList.size() < continuousOnTimes) {
+            log.info("司机{}按时接节点不足{}次,当前记录数:{}", waybillOrder.getDriverId(), continuousOnTimes, waybillOrderNodeList.size());
+            return null;
+        }
+        return waybillOrderNodeList;
+    }
+
+    /**
+     * 给连续的10次的节点数据打标
+     */
+    protected void updateNode(KwtWaybillOrder waybillOrder, List<Long> nodeIds, Integer continuousOnTimeScore) {
+        KwtWaybillOrderNode updateEntity = new KwtWaybillOrderNode();
+        updateEntity.setContinuous(1);
+        LambdaQueryWrapper<KwtWaybillOrderNode> updateWrapper = new LambdaQueryWrapper<KwtWaybillOrderNode>()
+                .in(KwtWaybillOrderNode::getId, nodeIds);
+        kwtWaybillOrderNodeRepository.update(updateEntity, updateWrapper);
+        log.info("司机{}连续执行运单节点,加分:{},节点ID:{}", waybillOrder.getDriverId(), continuousOnTimeScore, JSON.toJSONString(nodeIds));
+    }
+
+
     private void noticeTraderOrder(KwtLogisticsOrder kwtLogisticsOrder, KwtLogisticsOrder kwtLogistics) {
 
         if (!Objects.equals(kwtLogisticsOrder.getStatus(),LogisticsOrderV1Enum.HAVE_RECONCILED.getCode())){

+ 31 - 31
sql/2025/12/01/2025_12_04_donglang.sql

@@ -1,22 +1,22 @@
 
 create table kwf_driver_conduct_rules
 (
-    id                               bigint          NOT NULL AUTO_INCREMENT COMMENT '主键',
-    ent_id                           bigint          NOT NULL comment '企业id',
-    unload_serious_timeout           int             NOT NULL comment '单趟严重超时(违规次数/扣分)',
-    unload_serious_timeout_multiple  decimal(8,2)    NOT NULL DEFAULT '00.00' comment '单趟严重超时倍数',
-    document_error_missing           int             NOT NULL comment '单据错误/缺失(违规次数/扣分)',
-    not_on_time_arrive               int             NOT NULL comment '未按时到场(违规次数/扣分)',
-    fake_unload                      int             NOT NULL comment '虚假卸货(违规次数/扣分)',
-    fake_unload_distance             int             NOT NULL comment '虚假卸货偏差距离',
-    illegal_cancel_order             int             NOT NULL comment '违规取消运单(违规次数/扣分)',
-    illegal_cancel_order_minutes     int              NOT NULL comment '违规取消运单分钟数',
-    continuous_on_time_arrive        int             NOT NULL comment '连续按时到场(达标次数/加分)',
-    continuous_on_time_arrive_times  int             NOT NULL comment '连续按时到场次数',
-    continuous_on_time_unload        int             NOT NULL comment '连续准时卸货(达标次数/加分)',
-    continuous_on_time_unload_times  int             NOT NULL comment '连续准时卸货次数',
-    continuous_accurate_unload       int             NOT NULL comment '连续准确填写卸货信息(达标次数/加分)',
-    continuous_accurate_unload_times int             NOT NULL comment '连续准确填写卸货信息次数',
+    id                               bigint         NOT NULL AUTO_INCREMENT COMMENT '主键',
+    ent_id                           bigint         NOT NULL DEFAULT '-1'comment '企业id',
+    unload_serious_timeout           int            NOT NULL DEFAULT '-1'comment '单趟严重超时(违规次数/扣分)',
+    unload_serious_timeout_multiple  decimal(8,2)   NOT NULL DEFAULT '00.00' comment '单趟严重超时倍数',
+    document_error_missing           int            NOT NULL DEFAULT '-1'comment '单据错误/缺失(违规次数/扣分)',
+    not_on_time_arrive               int            NOT NULL DEFAULT '-1'comment '未按时到场(违规次数/扣分)',
+    fake_unload                      int            NOT NULL DEFAULT '-1'comment '虚假卸货(违规次数/扣分)',
+    fake_unload_distance             int            NOT NULL DEFAULT '-1'comment '虚假卸货偏差距离',
+    illegal_cancel_order             int            NOT NULL DEFAULT '-1'comment '违规取消运单(违规次数/扣分)',
+    illegal_cancel_order_minutes     int            NOT NULL DEFAULT '-1'comment '违规取消运单分钟数',
+    continuous_on_time_arrive        int            NOT NULL DEFAULT '-1'comment '连续按时到场(达标次数/加分)',
+    continuous_on_time_arrive_times  int            NOT NULL DEFAULT '-1'comment '连续按时到场次数',
+    continuous_on_time_unload        int            NOT NULL DEFAULT '-1'comment '连续准时卸货(达标次数/加分)',
+    continuous_on_time_unload_times  int            NOT NULL DEFAULT '-1'comment '连续准时卸货次数',
+    continuous_accurate_unload       int            NOT NULL DEFAULT '-1'comment '连续准确填写卸货信息(达标次数/加分)',
+    continuous_accurate_unload_times int            NOT NULL DEFAULT '-1'comment '连续准确填写卸货信息次数',
     create_time                     datetime        NOT NULL default CURRENT_TIMESTAMP comment '创建时间',
     update_time                     datetime        NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP comment '更新时间',
     create_user                     bigint          NOT NULL DEFAULT '-1' comment '创建人',
@@ -29,8 +29,8 @@ create table kwf_driver_conduct_rules
 create table kwf_driver_conduct_rules_log
 (
     id                              bigint          NOT NULL AUTO_INCREMENT COMMENT '主键',
-    dcr_id                          bigint          NOT NULL comment '司机行为规则id',
-    description                  varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci   DEFAULT NULL COMMENT '日志内容',
+    dcr_id                          bigint          NOT NULL DEFAULT '-1' comment '司机行为规则id',
+    description                     varchar(255)    CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci   DEFAULT NULL COMMENT '日志内容',
     del_flag                        int             NOT NULL DEFAULT '0' COMMENT '是否删除(0未删除,1删除)',
     create_time                     datetime        NOT NULL default CURRENT_TIMESTAMP comment '创建时间',
     update_time                     datetime        NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP comment '更新时间',
@@ -43,20 +43,20 @@ create table kwf_driver_conduct_rules_log
 create table kwt_truck_dispatch_coefficient
 (
     id                              bigint          NOT NULL AUTO_INCREMENT COMMENT '主键',
-    ent_id                          bigint          NOT NULL comment '企业id',
-    vehicle_work_hours              int             NOT NULL comment  '车辆工作时长',
-    vehicle_loading_hours           int             NOT NULL comment  '车辆装货时长',
-    vehicle_unloading_hours         int             NOT NULL comment '车辆卸货时长',
-    driver_timeout_limit            int             NOT NULL comment '司机超时限制',
-    vehicle_avg_load                int             NOT NULL comment '车辆平均载重',
-    vehicle_avg_speed               int             NOT NULL comment '车辆平均速度',
-    vehicle_max_tasks               int             NOT NULL comment '车辆最大任务数',
-    driver_order_limit              int             NOT NULL comment '司机接单限制',
-    yard_vehicle_capacity           int             NOT NULL comment '场内车辆容量',
-    max_ratio                       int             NOT NULL comment '最大占比',
+    ent_id                          bigint          NOT NULL DEFAULT '-1' comment '企业id',
+    vehicle_work_hours              int             NOT NULL DEFAULT '-1' comment  '车辆工作时长',
+    vehicle_loading_hours           int             NOT NULL DEFAULT '-1' comment  '车辆装货时长',
+    vehicle_unloading_hours         int             NOT NULL DEFAULT '-1' comment '车辆卸货时长',
+    driver_timeout_limit            int             NOT NULL DEFAULT '-1' comment '司机超时限制',
+    vehicle_avg_load                int             NOT NULL DEFAULT '-1' comment '车辆平均载重',
+    vehicle_avg_speed               int             NOT NULL DEFAULT '-1' comment '车辆平均速度',
+    vehicle_max_tasks               int             NOT NULL DEFAULT '-1' comment '车辆最大任务数',
+    driver_order_limit              int             NOT NULL DEFAULT '-1' comment '司机接单限制',
+    yard_vehicle_capacity           int             NOT NULL DEFAULT '-1' comment '场内车辆容量',
+    max_ratio                       int             NOT NULL DEFAULT '-1' comment '最大占比',
     buffer_coefficient              decimal(8,2)    NOT NULL DEFAULT '00.00' comment '缓冲系数',
-    create_time                     datetime        NOT NULL default CURRENT_TIMESTAMP comment '创建时间',
-    update_time                     datetime        NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP comment '更新时间',
+    create_time                     datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP comment '创建时间',
+    update_time                     datetime        NOT NULL DEFAULT CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP comment '更新时间',
     create_user                     bigint          NOT NULL DEFAULT '-1' comment '创建人',
     update_user                     bigint          NOT NULL DEFAULT '-1' comment '更新人',
     PRIMARY KEY (`id`) USING BTREE