package com.prolog.rdc.tms.service.intelligent.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.prolog.common.util.ToolKits;
import com.prolog.framework.core.exception.PrologException;
import com.prolog.framework.core.pojo.Page;
import com.prolog.framework.core.restriction.Criteria;
import com.prolog.framework.core.restriction.Restriction;
import com.prolog.framework.core.restriction.Restrictions;
import com.prolog.framework.dao.util.PageUtils;
import com.prolog.product.core.framework.entity.UserEntity;
import com.prolog.product.core.framework.util.PlgFxUsrUtils;
import com.prolog.rdc.tms.constant.BussinessConstant;
import com.prolog.rdc.tms.constant.DataDictionaryConstant;
import com.prolog.rdc.tms.constant.SeriaNnumberConstant;
import com.prolog.rdc.tms.constant.enums.intelligent.IntelligentDispatchCallBackTypeEnum;
import com.prolog.rdc.tms.constant.enums.intelligent.IntelligentDispatchLoadingStatusEnum;
import com.prolog.rdc.tms.constant.enums.intelligent.IntelligentDispatchRateEnum;
import com.prolog.rdc.tms.constant.enums.intelligent.IntelligentDispatchStageEnum;
import com.prolog.rdc.tms.constant.enums.intelligent.IntelligentDispatchStatusEnum;
import com.prolog.rdc.tms.constant.enums.intelligent.IntelligentDispatchTypeEnum;
import com.prolog.rdc.tms.constant.enums.intelligent.IntelligentLockEnum;
import com.prolog.rdc.tms.constant.enums.intelligent.IntelligentQueryModeEnum;
import com.prolog.rdc.tms.constant.enums.intelligent.IntelligentRestrictionEnum;
import com.prolog.rdc.tms.constant.enums.intelligent.IntelligentRuleEnum;
import com.prolog.rdc.tms.dao.mapper.TmsIntelligentDispatchLoadingMapper;
import com.prolog.rdc.tms.dao.mapper.TmsIntelligentDispatchMapper;
import com.prolog.rdc.tms.dao.mapper.TmsIntelligentDispatchShipMapper;
import com.prolog.rdc.tms.dao.mapper.TmsShipbillDetailMapper;
import com.prolog.rdc.tms.dao.mapper.TmsShipbillHeaderMapper;
import com.prolog.rdc.tms.model.dto.intelligent.BranchDTO;
import com.prolog.rdc.tms.model.dto.intelligent.ConsigneeDTO;
import com.prolog.rdc.tms.model.dto.intelligent.CustomerDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentBranchDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentConsigneeDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentCustomerDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentCustomersDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentDispatchCallBackDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentDispatchDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentDispatchGroupCountDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentDispatchGroupDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentDispatchGroupPreDataDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentDispatchLoadingDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentDispatchResultConsigneeRouteDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentDispatchResultDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentDispatchResultRoutesInfoDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentDispatchShipCountDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentDispatchShipDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentLimitOrRuleDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentModeDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentResultDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentShipBillDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentShipBillDetailDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentStopDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentStopResultDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentVehicleDTO;
import com.prolog.rdc.tms.model.dto.intelligent.IntelligentVehicleTypeDTO;
import com.prolog.rdc.tms.model.dto.intelligent.VehicleDTO;
import com.prolog.rdc.tms.model.dto.intelligent.VehicleTypeDTO;
import com.prolog.rdc.tms.model.entity.SysRefdatadModel;
import com.prolog.rdc.tms.model.entity.TmsIntelligentDispatch;
import com.prolog.rdc.tms.model.entity.TmsIntelligentDispatchLoading;
import com.prolog.rdc.tms.model.entity.TmsIntelligentDispatchShip;
import com.prolog.rdc.tms.model.entity.TmsShipbillDetailModel;
import com.prolog.rdc.tms.model.entity.TmsShipbillHeaderModel;
import com.prolog.rdc.tms.model.vo.LoadingBillDetailVO;
import com.prolog.rdc.tms.model.vo.LoadingBillHeaderVO;
import com.prolog.rdc.tms.model.vo.PendingDispatchVO;
import com.prolog.rdc.tms.model.vo.intelligent.IntelligentDispatchDistanceVO;
import com.prolog.rdc.tms.model.vo.intelligent.IntelligentDispatchGroupQueryVO;
import com.prolog.rdc.tms.model.vo.intelligent.IntelligentDispatchLoadingAdjustVO;
import com.prolog.rdc.tms.model.vo.intelligent.IntelligentDispatchLoadingQueryVO;
import com.prolog.rdc.tms.model.vo.intelligent.IntelligentDispatchLoadingVO;
import com.prolog.rdc.tms.model.vo.intelligent.IntelligentDispatchQueryVO;
import com.prolog.rdc.tms.model.vo.intelligent.IntelligentDispatchShipAdjustVO;
import com.prolog.rdc.tms.model.vo.intelligent.IntelligentDispatchShipBillQueryVO;
import com.prolog.rdc.tms.model.vo.intelligent.IntelligentDispatchVO;
import com.prolog.rdc.tms.model.vo.intelligent.IntelligentDispatchVehicleTypeVO;
import com.prolog.rdc.tms.proxy.MainDataProxy;
import com.prolog.rdc.tms.service.PendingDispatchService;
import com.prolog.rdc.tms.service.intelligent.IntelligentDispatchService;
import com.prolog.rdc.tms.util.ConvertorUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

/**
 * @author hongbo.pan
 * @date 2023/8/24
 */
@Slf4j
@Service
public class IntelligentDispatchServiceImpl implements IntelligentDispatchService {

    /**
     * NULL
     */
    private static final String NULL = "NULL";

    /**
     * Y
     */
    private static final String Y = "Y";

    /**
     * N
     */
    private static final String N = "N";

    /**
     * 0：速度优先（只返回一条路线），此路线不一定距离最短
     * 1：费用优先（只返回一条路线），不走收费路段，且耗时最少的路线
     * 2：距离优先（只返回一条路线），仅走距离最短的路线，但是可能存在穿越小路/小区的情况
     * 3：速度优先（只返回一条路线），不走快速路，例如京通快速路
     * 32：默认，高德推荐，同高德地图APP默认
     * 33：躲避拥堵
     * 34：高速优先
     * 35：不走高速
     * 36：少收费
     * 37：大路优先
     * 38：速度最快
     * 39：躲避拥堵＋高速优先
     * 40：躲避拥堵＋不走高速
     * 41：躲避拥堵＋少收费
     * 42：少收费＋不走高速
     * 43：躲避拥堵＋少收费＋不走高速
     * 44：躲避拥堵＋大路优先
     * 45：躲避拥堵＋速度最快
     */
    private static final Integer STRATEGY = 2;

    /**
     * 超时
     */
    private static final Integer TIME_OUT = 30 * 1000;

    @Autowired
    private TmsIntelligentDispatchMapper mapper;

    @Autowired
    private TmsIntelligentDispatchLoadingMapper loadingMapper;

    @Autowired
    private TmsIntelligentDispatchShipMapper shipMapper;

    @Autowired
    private TmsShipbillHeaderMapper shipbillHeaderMapper;

    @Autowired
    private TmsShipbillDetailMapper shipbillDetailMapper;

    @Autowired
    private ConvertorUtil convertorUtil;

    @Autowired
    private PendingDispatchService pendingDispatchService;

    @Autowired
    private MainDataProxy proxy;

    @Value("${prolog.amap.key:2c39c21d367ba0c19a984b148c6cfa32}")
    private String aMapKey;

    @Value("${prolog.intelligent.dispatch.url:http://192.168.110.11:7000}")
    private String intelligentDispatchUrl;

    @Value("${prolog.intelligent.dispatch.vehicleDispatchPath:/VehicleDispatch}")
    private String vehicleDispatchPath;

    @Value("${prolog.intelligent.dispatch.stopVehicleDispatchPath:/VehicleDispatch/stop}")
    private String stopVehicleDispatchPath;

    @Override
    public IntelligentDispatchGroupDTO queryGroup(IntelligentDispatchGroupQueryVO queryVO) {
        setDataPermission(queryVO);
        List<IntelligentDispatchGroupPreDataDTO> dtoList = mapper.queryGroup(queryVO);
        IntelligentDispatchGroupDTO groupDTO = new IntelligentDispatchGroupDTO();
        groupDTO.setTotalShipBillNum(0L);
        if (CollectionUtil.isNotEmpty(dtoList)) {
            List<SysRefdatadModel> dicData = convertorUtil.getDictionary(new String[] {
                    DataDictionaryConstant.DELIVERY_METHOD_TYPE,
                    DataDictionaryConstant.TRANSPORT_TYPE,
                    DataDictionaryConstant.TEMPERATURE_REQUIREMENT,
                    DataDictionaryConstant.ORDER_BUSINESS_TYPE
            });
            groupDTO.setTotalShipBillNum(count(dtoList));
            groupDTO.setCargoOwnerList(sort(groupByCargoOwner(dtoList)));
            groupDTO.setOrderBusinessTypeList(sort(groupByOrderBusinessType(dtoList, dicData)));
            groupDTO.setTransportTypeList(sort(groupByTransportType(dtoList, dicData)));
            groupDTO.setDeliveryMethodTypeList(sort(groupByDeliveryMethodType(dtoList, dicData)));
            groupDTO.setTemperatureRequirementList(sort(groupByTemperatureRequirement(dtoList, dicData)));
            groupDTO.setLineList(sort(groupByLine(dtoList)));
            groupDTO.setItemCategoryList(sort(groupByItemCategory(dtoList)));
            groupDTO.setShipperStationList(sort(groupByShipperStation(dtoList)));
            groupDTO.setShipperStationAreasList(sort(groupByShipperStationAreas(dtoList)));
            groupDTO.setConsigneeStationList(sort(groupByConsigneeStation(dtoList)));
            groupDTO.setConsigneeStationAreasList(sort(groupByConsigneeStationAreas(dtoList)));
        }
        return groupDTO;
    }

    @Override
    public Page<IntelligentDispatchShipDTO> queryShipBill(IntelligentDispatchShipBillQueryVO queryVO) {
        if (StrUtil.isBlank(queryVO.getQueryModel())) {
            throw new PrologException("查询模式不能为空");
        }
        if (CollectionUtil.isEmpty(queryVO.getCargoOwnerList())
                && CollectionUtil.isEmpty(queryVO.getOrderBusinessTypeList())
                && CollectionUtil.isEmpty(queryVO.getTransportTypeList())
                && CollectionUtil.isEmpty(queryVO.getDeliveryMethodTypeList())
                && CollectionUtil.isEmpty(queryVO.getTemperatureRequirementList())
                && CollectionUtil.isEmpty(queryVO.getLineList())
                && CollectionUtil.isEmpty(queryVO.getItemCategoryList())
                && CollectionUtil.isEmpty(queryVO.getShipperStationList())
                && CollectionUtil.isEmpty(queryVO.getShipperStationAreasList())
                && CollectionUtil.isEmpty(queryVO.getConsigneeStationList())
                && CollectionUtil.isEmpty(queryVO.getConsigneeStationAreasList())) {
            return PageUtils.getPage(Lists.newArrayList());
        }
        if (IntelligentQueryModeEnum.MAP.getCode().equals(queryVO.getQueryModel())) {
            queryVO.setPageNum(1);
            queryVO.setPageSize(Integer.MAX_VALUE);
        } else {
            if (ObjectUtil.isNull(queryVO.getPageSize()) || ObjectUtil.isNull(queryVO.getPageNum())) {
                throw new PrologException("列表模式，分页参数不能为空");
            }
        }
        setDataPermission(queryVO);
        setInitData(queryVO);
        PageUtils.startPage(queryVO.getPageNum(), queryVO.getPageSize());
        List<IntelligentDispatchShipDTO> dtoList = mapper.queryShipBill(queryVO);
        if (CollectionUtil.isNotEmpty(dtoList)) {
            List<SysRefdatadModel> dicData = convertorUtil.getDictionary(new String[] {
                    DataDictionaryConstant.SHIPBILL_STATUS,
                    DataDictionaryConstant.DELIVERY_METHOD_TYPE,
                    DataDictionaryConstant.TRANSPORT_TYPE,
                    DataDictionaryConstant.TEMPERATURE_REQUIREMENT,
                    DataDictionaryConstant.ORDER_TYPE,
                    DataDictionaryConstant.ORDER_BUSINESS_TYPE
            });
            dtoList.forEach(dto -> {
                dto.setStatusName(ConvertorUtil.getName(dicData, DataDictionaryConstant.SHIPBILL_STATUS, String.valueOf(dto.getStatus())));
                dto.setOrderTypeName(ConvertorUtil.getName(dicData, DataDictionaryConstant.ORDER_TYPE, String.valueOf(dto.getOrderType())));
                dto.setOrderBusinessTypeName(ConvertorUtil.getName(dicData, DataDictionaryConstant.ORDER_BUSINESS_TYPE, String.valueOf(dto.getOrderBusinessType())));
                dto.setDeliveryMethodTypeName(ConvertorUtil.getName(dicData, DataDictionaryConstant.DELIVERY_METHOD_TYPE, String.valueOf(dto.getDeliveryMethodType())));
                dto.setTransportTypeName(ConvertorUtil.getName(dicData, DataDictionaryConstant.TRANSPORT_TYPE, String.valueOf(dto.getTransportType())));
                dto.setTemperatureRequirementName(ConvertorUtil.getName(dicData, DataDictionaryConstant.TEMPERATURE_REQUIREMENT, String.valueOf(dto.getTemperatureRequirement())));
            });
        }
        return PageUtils.getPage(dtoList);
    }

    @Override
    public IntelligentDispatchShipCountDTO countShipBill(IntelligentDispatchShipBillQueryVO queryVO) {
        if (CollectionUtil.isEmpty(queryVO.getCargoOwnerList())
                && CollectionUtil.isEmpty(queryVO.getOrderBusinessTypeList())
                && CollectionUtil.isEmpty(queryVO.getTransportTypeList())
                && CollectionUtil.isEmpty(queryVO.getDeliveryMethodTypeList())
                && CollectionUtil.isEmpty(queryVO.getTemperatureRequirementList())
                && CollectionUtil.isEmpty(queryVO.getLineList())
                && CollectionUtil.isEmpty(queryVO.getItemCategoryList())
                && CollectionUtil.isEmpty(queryVO.getShipperStationList())
                && CollectionUtil.isEmpty(queryVO.getShipperStationAreasList())
                && CollectionUtil.isEmpty(queryVO.getConsigneeStationList())
                && CollectionUtil.isEmpty(queryVO.getConsigneeStationAreasList())) {
            return IntelligentDispatchShipCountDTO.builder().selectShipBillNum(0).
                    totalVolume(BigDecimal.ZERO).totalWeight(BigDecimal.ZERO).build();
        }
        setDataPermission(queryVO);
        setInitData(queryVO);
        IntelligentDispatchShipCountDTO countDTO = mapper.countShipBill(queryVO);
        countDTO.setTotalWeight(NumberUtil.div(countDTO.getTotalWeight(), 1000, 2, RoundingMode.HALF_UP));
        return countDTO;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public IntelligentDispatchDTO dispatch(IntelligentDispatchVO vo) {
        UserEntity user = PlgFxUsrUtils.getUserEntity();
        setDataPermission(vo);
        setInitData(vo);
        List<String> shipBillIdList = mapper.queryShipBillId(vo);
        int lockShipBillNum = mapper.lockShipBill(vo);
        if (lockShipBillNum != vo.getSelectShipBillNum()) {
            throw new PrologException("所选运单数据发送变化，请重新选择");
        }
        List<SysRefdatadModel> dicData = convertorUtil.getDictionary(new String[] {
                IntelligentRestrictionEnum.NAME,
                IntelligentRuleEnum.NAME
        });
        String intelligentDispatchId = proxy.getCodeNumberByCode(SeriaNnumberConstant.TMS_INTELLIGENT_DISPATCH_ID);
        IntelligentDTO intelligentDTO = buildIntelligentDTO(shipBillIdList, vo, dicData);
        intelligentDTO.setTaskId(intelligentDispatchId);
        String requestJson = JSON.toJSONString(intelligentDTO, SerializerFeature.WriteNullStringAsEmpty);
        TmsIntelligentDispatch intelligentDispatch = new TmsIntelligentDispatch();
        intelligentDispatch.setIntelligentDispatchId(intelligentDispatchId);
        intelligentDispatch.setIntelligentDispatchNo(intelligentDispatchId);
        intelligentDispatch.setIntelligentDispatchTime(DateUtil.date());
        intelligentDispatch.setCondition(vo.getIntelligentRestrictionList().stream().map(restriction ->
                ConvertorUtil.getName(dicData, IntelligentRestrictionEnum.NAME, restriction)).collect(Collectors.joining(",")));
        intelligentDispatch.setRule(ConvertorUtil.getName(dicData, IntelligentRuleEnum.NAME, vo.getIntelligentRule()));
        intelligentDispatch.setVehicleNum(0);
        intelligentDispatch.setLineNum(0);
        intelligentDispatch.setShipBillNum(0);
        intelligentDispatch.setDispatchVehicleNum(0);
        intelligentDispatch.setDispatchLineNum(0);
        intelligentDispatch.setDispatchShipBillNum(0);
        intelligentDispatch.setTotalShipBillNum(lockShipBillNum);
        intelligentDispatch.setEstimateCost(BigDecimal.ZERO);
        intelligentDispatch.setStatus(IntelligentDispatchStatusEnum.NEW.getCode());
        intelligentDispatch.setShipBillIds(String.join(",", shipBillIdList));
        intelligentDispatch.setRequestJson(requestJson);
        intelligentDispatch.setEnterpriseId(user.getEnterpriseId());
        intelligentDispatch.setEnterpriseName(user.getEnterpriseName());
        intelligentDispatch.setCreateperson(user.getNickname());
        intelligentDispatch.setCreatetime(DateUtil.date());
        intelligentDispatch.setUpdateperson(user.getNickname());
        intelligentDispatch.setUpdatetime(DateUtil.date());
        String responseJson = remoteDispatch(intelligentDispatchUrl + vehicleDispatchPath, requestJson);
        IntelligentResultDTO resultDTO = JSON.parseObject(responseJson, IntelligentResultDTO.class);
        if (resultDTO.getSuccess() == 0) {
            throw new PrologException("调用智能调度接口失败，信息=" + resultDTO.getMessage());
        }
        mapper.save(intelligentDispatch);
        return BeanUtil.copyProperties(intelligentDispatch, IntelligentDispatchDTO.class);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void cancelDispatch(String intelligentDispatchId) {
        UserEntity user = PlgFxUsrUtils.getUserEntity();
        TmsIntelligentDispatch intelligentDispatch = findById(intelligentDispatchId);
        if (ObjectUtil.isNull(intelligentDispatch)) {
            throw new PrologException("调度记录不存在，intelligentDispatchId=" + intelligentDispatchId);
        }
        if (!IntelligentDispatchStatusEnum.NEW.getCode().equals(intelligentDispatch.getStatus())) {
            throw new PrologException("调度记录不为新建，不能取消");
        }
        String shipBillIds = intelligentDispatch.getShipBillIds();
        Map<String, Object> updateMap = Maps.newHashMap();
        updateMap.put("updateperson", user.getNickname());
        updateMap.put("updatetime", DateUtil.date());
        updateMap.put("intelligentLock", IntelligentLockEnum.UN_LOCK.getCode());
        Criteria criteria = Criteria.forClass(TmsShipbillHeaderModel.class);
        Restriction r1 = Restrictions.in(TmsShipbillHeaderModel::getShipbillId, shipBillIds.split(","));
        Restriction r2 = Restrictions.eq(TmsShipbillHeaderModel::getEnterpriseId, user.getEnterpriseId());
        criteria.setRestriction(Restrictions.and(r1, r2));
        shipbillHeaderMapper.updateMapByCriteria(updateMap, criteria);
        mapper.deleteById(intelligentDispatchId, TmsIntelligentDispatch.class);
        criteria = Criteria.forClass(TmsIntelligentDispatchLoading.class);
        r1 = Restrictions.eq(TmsIntelligentDispatchLoading::getIntelligentDispatchId, intelligentDispatchId);
        r2 = Restrictions.eq(TmsIntelligentDispatchLoading::getEnterpriseId, user.getEnterpriseId());
        criteria.setRestriction(Restrictions.and(r1, r2));
        loadingMapper.deleteByCriteria(criteria);
        criteria = Criteria.forClass(TmsIntelligentDispatchShip.class);
        r1 = Restrictions.eq(TmsIntelligentDispatchShip::getIntelligentDispatchId, intelligentDispatchId);
        r2 = Restrictions.eq(TmsIntelligentDispatchShip::getEnterpriseId, user.getEnterpriseId());
        criteria.setRestriction(Restrictions.and(r1, r2));
        shipMapper.deleteByCriteria(criteria);
        IntelligentStopDTO stopDTO = new IntelligentStopDTO();
        stopDTO.setTaskId(intelligentDispatchId);
        stopDTO.setPid(intelligentDispatch.getProcessId());
        String responseJson = remoteDispatch(intelligentDispatchUrl + stopVehicleDispatchPath, JSON.toJSONString(stopDTO));
        IntelligentStopResultDTO stopResultDTO = JSON.parseObject(responseJson, IntelligentStopResultDTO.class);
        if (stopResultDTO.getSuccess() == 0) {
            throw new PrologException("调用智能调度终止接口失败，信息=" + stopResultDTO.getMessage());
        }
    }

    @Override
    public IntelligentDispatchDTO getDispatchInfo(String intelligentDispatchId) {
        TmsIntelligentDispatch intelligentDispatch = findById(intelligentDispatchId);
        if (ObjectUtil.isNull(intelligentDispatch)) {
            throw new PrologException("智能调度记录不存在，intelligentDispatchId=" + intelligentDispatchId);
        }
        IntelligentDispatchDTO intelligentDispatchDTO = BeanUtil.copyProperties(intelligentDispatch, IntelligentDispatchDTO.class);
        if (IntelligentDispatchStatusEnum.NEW.getCode().equals(intelligentDispatchDTO.getStatus())) {
            String processResult = intelligentDispatchDTO.getProcessResult();
            if (StrUtil.isNotBlank(processResult)) {
                IntelligentDispatchCallBackDTO callBackDTO = JSON.parseObject(processResult, IntelligentDispatchCallBackDTO.class);
                if (IntelligentDispatchCallBackTypeEnum.PROCESS_ID.getCode().equals(callBackDTO.getCallBackType())) {
                    intelligentDispatchDTO.setStage(IntelligentDispatchStageEnum.NOT_START.getCode());
                    intelligentDispatchDTO.setStageName(IntelligentDispatchStageEnum.NOT_START.getDesc());
                } else {
                    intelligentDispatchDTO.setStage(callBackDTO.getStage());
                    intelligentDispatchDTO.setStageName(IntelligentDispatchStageEnum.getByCode(callBackDTO.getStage()).getDesc());
                    intelligentDispatchDTO.setProgress(callBackDTO.getProgress());
                    intelligentDispatchDTO.setSurplusTime(callBackDTO.getSurplusTime());
                }
            } else {
                intelligentDispatchDTO.setStage(IntelligentDispatchStageEnum.NOT_START.getCode());
                intelligentDispatchDTO.setStageName(IntelligentDispatchStageEnum.NOT_START.getDesc());
            }
        } else {
            intelligentDispatchDTO.setStage(IntelligentDispatchStageEnum.FINISH.getCode());
            intelligentDispatchDTO.setStageName(IntelligentDispatchStageEnum.FINISH.getDesc());
        }
        return intelligentDispatchDTO;
    }

    @Override
    public Page<IntelligentDispatchDTO> findByCondition(IntelligentDispatchQueryVO vo) {
        UserEntity user = PlgFxUsrUtils.getUserEntity();
        vo.setEnterpriseId(user.getEnterpriseId());
        PageUtils.startPage(vo.getPageNum(), vo.getPageSize());
        List<IntelligentDispatchDTO> dtoList = mapper.findByCondition(vo);
        if (CollectionUtil.isNotEmpty(dtoList)) {
            List<SysRefdatadModel> dicData = convertorUtil.getDictionary(new String[] {
                    IntelligentDispatchStatusEnum.NAME
            });
            dtoList.forEach(dto ->
                    dto.setStatusName(ConvertorUtil.getName(dicData, IntelligentDispatchStatusEnum.NAME, String.valueOf(dto.getStatus()))));
        }
        return PageUtils.getPage(dtoList);
    }

    @Override
    public IntelligentDispatchDTO getDispatchResult(String intelligentDispatchId) {
        TmsIntelligentDispatch intelligentDispatch = findById(intelligentDispatchId);
        if (ObjectUtil.isNull(intelligentDispatch)) {
            throw new PrologException("智能调度记录不存在，intelligentDispatchId=" + intelligentDispatchId);
        }
        List<SysRefdatadModel> dicData = convertorUtil.getDictionary(new String[] {
                IntelligentDispatchStatusEnum.NAME,
                IntelligentDispatchLoadingStatusEnum.NAME,
                DataDictionaryConstant.TRANSPORT_STATUS,
                DataDictionaryConstant.ABNORMAL_STATUS,
                DataDictionaryConstant.DELIVERY_METHOD_TYPE,
                DataDictionaryConstant.TRANSPORT_TYPE,
                DataDictionaryConstant.TEMPERATURE_REQUIREMENT,
                DataDictionaryConstant.SHIPPER_TYPE,
                DataDictionaryConstant.CONSIGNEE_TYPE,
                DataDictionaryConstant.ORDER_TYPE,
                DataDictionaryConstant.ORDER_BUSINESS_TYPE,
                DataDictionaryConstant.VEHICLE_TYPE_CATEGORY,
                DataDictionaryConstant.PACK_TYPE
        });
        IntelligentDispatchDTO dispatchDTO = BeanUtil.copyProperties(intelligentDispatch, IntelligentDispatchDTO.class);
        dispatchDTO.setStatusName(ConvertorUtil.getName(dicData, IntelligentDispatchStatusEnum.NAME, String.valueOf(dispatchDTO.getStatus())));
        dispatchDTO.setResidueShipBillNum(NumberUtil.sub(dispatchDTO.getTotalShipBillNum(), dispatchDTO.getShipBillNum()).intValue());
        List<TmsIntelligentDispatchLoading> loadingList = findLoadingByIntelligentDispatchId(intelligentDispatchId);
        if (CollectionUtil.isNotEmpty(loadingList)) {
            List<TmsIntelligentDispatchShip> shipList = findShipByIntelligentDispatchId(intelligentDispatchId);
            List<IntelligentDispatchShipDTO> shipDTOList = convertIntelligentDispatchShipDTO(shipList, dicData);
            List<IntelligentDispatchLoadingDTO> loadingDTOList = convertIntelligentDispatchLoadingDTO(loadingList, shipDTOList, dicData);
            dispatchDTO.setLoadingDTOList(loadingDTOList);
        }
        return dispatchDTO;
    }

    @Override
    public List<IntelligentDispatchLoadingDTO> filterDispatchLoading(IntelligentDispatchLoadingQueryVO vo) {
        List<TmsIntelligentDispatchLoading> loadingList = findLoadingByIntelligentDispatchId(vo.getIntelligentDispatchId());
        loadingList = loadingList.stream().filter(loading -> {
            boolean flag = Boolean.TRUE;
            if (ObjectUtil.isNotEmpty(vo.getVehicleId())) {
                flag = vo.getVehicleId().equals(loading.getVehicleId());
            }
            if (ObjectUtil.isNotEmpty(vo.getVehicleTypeId())) {
                flag = flag && vo.getVehicleTypeId().equals(loading.getVehicleTypeId());
            }
            if (ObjectUtil.isNotEmpty(vo.getStatus())) {
                flag = flag && vo.getStatus().equals(loading.getStatus());
            }
            if (ObjectUtil.isNotEmpty(vo.getDepartmentId())) {
                flag = flag && loading.getDepartmentId().contains(vo.getDepartmentId());
            }
            if (ObjectUtil.isNotEmpty(vo.getRate())) {
                flag = flag && NumberUtil.isLess(loading.getLoadingRate(), IntelligentDispatchRateEnum.getByCode(vo.getRate()).getRate());
            }
            return flag;
        }).collect(Collectors.toList());
        if (CollectionUtil.isNotEmpty(loadingList)) {
            List<TmsIntelligentDispatchShip> shipList = findShipByIntelligentDispatchLoadingList(loadingList);
            List<SysRefdatadModel> dicData = convertorUtil.getDictionary(new String[] {
                    IntelligentDispatchLoadingStatusEnum.NAME,
                    DataDictionaryConstant.TRANSPORT_STATUS,
                    DataDictionaryConstant.ABNORMAL_STATUS,
                    DataDictionaryConstant.DELIVERY_METHOD_TYPE,
                    DataDictionaryConstant.TRANSPORT_TYPE,
                    DataDictionaryConstant.TEMPERATURE_REQUIREMENT,
                    DataDictionaryConstant.SHIPPER_TYPE,
                    DataDictionaryConstant.CONSIGNEE_TYPE,
                    DataDictionaryConstant.ORDER_TYPE,
                    DataDictionaryConstant.ORDER_BUSINESS_TYPE,
                    DataDictionaryConstant.VEHICLE_TYPE_CATEGORY,
                    DataDictionaryConstant.PACK_TYPE
            });
            List<IntelligentDispatchShipDTO> shipDTOList = convertIntelligentDispatchShipDTO(shipList, dicData);
            return convertIntelligentDispatchLoadingDTO(loadingList, shipDTOList, dicData);
        }
        return Lists.newArrayList();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void dispatchLoading(List<String> loadingIdList) {
        dispatchOrReviewLoading(loadingIdList, "dispatch");
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void dispatchReviewLoading(List<String> loadingIdList) {
        dispatchOrReviewLoading(loadingIdList, "dispatchReview");
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void cancelLoading(List<String> loadingIdList) {
        UserEntity user = PlgFxUsrUtils.getUserEntity();
        List<TmsIntelligentDispatchLoading> loadingList = preCheck(loadingIdList);
        List<TmsIntelligentDispatchShip> shipList = findShipByIntelligentDispatchLoadingList(loadingList);
        List<TmsShipbillHeaderModel> shipBillHeaderList = shipbillHeaderMapper.findByIds(TmsShipbillHeaderModel.class, shipList.stream().map(TmsIntelligentDispatchShip::getShipbillId).toArray(), null);
        shipBillHeaderList.forEach(shipBillHeader -> {
            shipBillHeader.setIntelligentLock(IntelligentLockEnum.UN_LOCK.getCode());
            shipBillHeader.setUpdateperson(user.getNickname());
            shipBillHeader.setUpdatetime(DateUtil.date());
            shipbillHeaderMapper.update(shipBillHeader);
        });
        if (CollectionUtil.isNotEmpty(shipList)) {
            shipMapper.deleteByIds(shipList.stream().map(TmsIntelligentDispatchShip::getId).toArray(), TmsIntelligentDispatchShip.class);
        }
        loadingMapper.deleteByIds(loadingList.stream().map(TmsIntelligentDispatchLoading::getLoadingId).toArray(), TmsIntelligentDispatchLoading.class);
        recalculate(loadingList.get(0).getIntelligentDispatchId());
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void assignVehicleDriver(IntelligentDispatchLoadingVO vo) {
        TmsIntelligentDispatchLoading loading = loadingMapper.findById(vo.getLoadingId(), TmsIntelligentDispatchLoading.class);
        checkLoading(loading);
        if (ObjectUtil.isNotNull(vo.getDriverMaxMileage())) {
            if (NumberUtil.isGreater(loading.getTotalMileage(), vo.getDriverMaxMileage())) {
                throw new PrologException("线路最长行驶里程超过了司机的最长行驶里程，请选择其他司机！");
            }
        }
        if (ObjectUtil.isNotNull(vo.getDriverMaxTravelTime())) {
            if (NumberUtil.isGreater(loading.getDriveDuration(), vo.getDriverMaxTravelTime())) {
                throw new PrologException("线路最长行驶时间超过了司机的最长行驶时间，请选择其他司机！");
            }
        }
        if (ObjectUtil.isNotNull(vo.getDriverMaxDeliveryCustomer())) {
            List<TmsIntelligentDispatchShip> shipList = findShipByIntelligentDispatchLoadingList(Lists.newArrayList(loading));
            List<TmsShipbillHeaderModel> shipBillHeaderList = shipbillHeaderMapper.findByIds(TmsShipbillHeaderModel.class, shipList.stream().map(TmsIntelligentDispatchShip::getShipbillId).toArray(), null);
            Long consigneeCount = shipBillHeaderList.stream().map(TmsShipbillHeaderModel::getConsigneeId).distinct().count();
            if (NumberUtil.isGreater(NumberUtil.toBigDecimal(consigneeCount), vo.getDriverMaxDeliveryCustomer())) {
                throw new PrologException("线路配送客户超过了司机的最多配送客户，请选择其他司机！");
            }
        }
        BeanUtil.copyProperties(vo, loading, CopyOptions.create().ignoreNullValue());
        UserEntity user = PlgFxUsrUtils.getUserEntity();
        loading.setUpdateperson(user.getNickname());
        loading.setUpdatetime(DateUtil.date());
        loadingMapper.update(loading);
        recalculate(loading.getIntelligentDispatchId());
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void adjust(List<IntelligentDispatchLoadingAdjustVO> voList) {
        UserEntity user = PlgFxUsrUtils.getUserEntity();
        voList.forEach(vo -> {
            TmsIntelligentDispatchLoading loading = loadingMapper.findById(vo.getLoadingId(), TmsIntelligentDispatchLoading.class);
            checkLoading(loading);
            List<TmsIntelligentDispatchShip> shipList = findShipByIntelligentDispatchLoadingList(Lists.newArrayList(loading));
            if (CollectionUtil.isNotEmpty(shipList)) {
                shipMapper.deleteByIds(shipList.stream().map(TmsIntelligentDispatchShip::getId).toArray(), TmsIntelligentDispatchShip.class);
            }
            List<IntelligentDispatchShipAdjustVO> shipAdjustVOList = vo.getShipAdjustVOList();
            if (CollectionUtil.isNotEmpty(shipAdjustVOList)) {
                shipMapper.saveBatch(shipAdjustVOList.stream().
                    filter(shipAdjustVO -> 0 != shipAdjustVO.getDeliveryOrder()).
                    map(shipAdjustVO -> {
                        TmsIntelligentDispatchShip ship = BeanUtil.copyProperties(shipAdjustVO, TmsIntelligentDispatchShip.class);
                        ship.setId(ToolKits.getUUID());
                        ship.setDeliveryOrder(0);
                        ship.setIntelligentDispatchId(loading.getIntelligentDispatchId());
                        ship.setLoadingId(loading.getLoadingId());
                        ship.setEnterpriseId(user.getEnterpriseId());
                        ship.setEnterpriseName(user.getEnterpriseName());
                        ship.setCreateperson(user.getNickname());
                        ship.setCreatetime(DateUtil.date());
                        ship.setUpdateperson(user.getNickname());
                        ship.setUpdatetime(DateUtil.date());
                        return ship;
                }).collect(Collectors.toList()));
                shipMapper.saveBatch(shipAdjustVOList.stream().
                    filter(shipAdjustVO -> 0 != shipAdjustVO.getDeliveryOrder()).
                    map(shipAdjustVO -> {
                        TmsIntelligentDispatchShip ship = BeanUtil.copyProperties(shipAdjustVO, TmsIntelligentDispatchShip.class);
                        ship.setId(ToolKits.getUUID());
                        ship.setIntelligentDispatchId(loading.getIntelligentDispatchId());
                        ship.setLoadingId(loading.getLoadingId());
                        ship.setEnterpriseId(user.getEnterpriseId());
                        ship.setEnterpriseName(user.getEnterpriseName());
                        ship.setCreateperson(user.getNickname());
                        ship.setCreatetime(DateUtil.date());
                        ship.setUpdateperson(user.getNickname());
                        ship.setUpdatetime(DateUtil.date());
                        return ship;
                }).collect(Collectors.toList()));
                loading.setShipBillNum((int) shipAdjustVOList.stream().map(IntelligentDispatchShipAdjustVO::getShipbillId).distinct().count());
            } else {
                loading.setShipBillNum(0);
            }
            BeanUtil.copyProperties(vo, loading, CopyOptions.create().ignoreNullValue());
            loading.setUpdateperson(user.getNickname());
            loading.setUpdatetime(DateUtil.date());
            loadingMapper.update(loading);
        });
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void callBack(IntelligentDispatchCallBackDTO callBackDTO) {
        log.info("callBackDTO={}", JSON.toJSONString(callBackDTO));
        TmsIntelligentDispatch intelligentDispatch = findById(callBackDTO.getTaskId());
        if (ObjectUtil.isNotEmpty(intelligentDispatch)) {
            intelligentDispatch.setProcessId(callBackDTO.getProcessId());
            intelligentDispatch.setProcessResult(JSON.toJSONString(callBackDTO));
            intelligentDispatch.setUpdatetime(DateUtil.date());
            if (IntelligentDispatchCallBackTypeEnum.RESULT.getCode().equals(callBackDTO.getCallBackType())) {
                intelligentDispatch.setStatus(IntelligentDispatchStatusEnum.PENDING.getCode());
                String resultJson = callBackDTO.getResultJson();
                IntelligentDispatchResultDTO resultDTO = JSON.parseObject(resultJson, IntelligentDispatchResultDTO.class);
                List<IntelligentDispatchResultRoutesInfoDTO> routesInfo = resultDTO.getRoutesInfo();
                if (CollectionUtil.isNotEmpty(routesInfo)) {
                    intelligentDispatch.setLineNum(routesInfo.size());
                    Map<String, VehicleDTO> vehicleMap = Maps.newHashMap();
                    Map<Long, VehicleTypeDTO> vehicleTypeMap;
                    List<Long> vehicleTypeIdList;
                    if (IntelligentDispatchTypeEnum.VEHICLE.getCode().equals(resultDTO.getDispatchMode())) {
                        intelligentDispatch.setVehicleNum(routesInfo.size());
                        List<String> vehiclePlateNumberList = routesInfo.stream().map(IntelligentDispatchResultRoutesInfoDTO::getVehiclePlate).collect(Collectors.toList());
                        List<VehicleDTO> vehicleList = proxy.findVehicleByPlateNumberList(vehiclePlateNumberList);
                        vehicleTypeIdList = vehicleList.stream().map(VehicleDTO::getVehicleTypeId).collect(Collectors.toList());
                        vehicleMap = vehicleList.stream().collect(Collectors.toMap(VehicleDTO::getPlateNumber, Function.identity(), (t1, t2) -> t2));
                    } else {
                        vehicleTypeIdList = routesInfo.stream().map(IntelligentDispatchResultRoutesInfoDTO::getVehicleType).collect(Collectors.toList());
                    }
                    List<VehicleTypeDTO> vehicleTypeList = proxy.findVehicleTypeByIdList(vehicleTypeIdList);
                    vehicleTypeMap = vehicleTypeList.stream().collect(Collectors.toMap(VehicleTypeDTO::getId, Function.identity(), (t1, t2) -> t2));
                    List<String> shipBillIdList = routesInfo.stream().flatMap(routeInfo -> {
                        List<IntelligentDispatchResultConsigneeRouteDTO> consigneeRouteList = routeInfo.getConsigneeRoute();
                        if (CollectionUtil.isNotEmpty(consigneeRouteList)) {
                            return consigneeRouteList.stream().filter(consigneeRoute ->
                                    CollectionUtil.isNotEmpty(consigneeRoute.getShipBillIds())).flatMap(consigneeRoute ->
                                    consigneeRoute.getShipBillIds().stream());
                        }
                        return Stream.empty();
                    }).distinct().collect(Collectors.toList());
                    intelligentDispatch.setShipBillNum(shipBillIdList.size());
                    List<String> unLockShipBill = Arrays.stream(intelligentDispatch.getShipBillIds().split(",")).filter(shipBillId -> !shipBillIdList.contains(shipBillId)).collect(Collectors.toList());
                    if (CollectionUtil.isNotEmpty(unLockShipBill)) {
                        Map<String, Object> updateMap = Maps.newHashMap();
                        updateMap.put("updatetime", DateUtil.date());
                        updateMap.put("intelligentLock", IntelligentLockEnum.UN_LOCK.getCode());
                        Criteria criteria = Criteria.forClass(TmsShipbillHeaderModel.class);
                        Restriction r1 = Restrictions.in(TmsShipbillHeaderModel::getShipbillId, unLockShipBill);
                        criteria.setRestriction(Restrictions.and(r1));
                        shipbillHeaderMapper.updateMapByCriteria(updateMap, criteria);
                    }
                    Map<Long, VehicleTypeDTO> finalVehicleTypeMap = vehicleTypeMap;
                    Map<String, VehicleDTO> finalVehicleMap = vehicleMap;
                    routesInfo.forEach(routeInfo ->
                            createLoadingAndShip(finalVehicleTypeMap, finalVehicleMap, routeInfo, intelligentDispatch));
                }
            }
            mapper.update(intelligentDispatch);
        }
    }

    /**
     * createLoadingAndShip
     */
    private void createLoadingAndShip(Map<Long, VehicleTypeDTO> vehicleTypeMap,
                                      Map<String, VehicleDTO> vehicleMap,
                                      IntelligentDispatchResultRoutesInfoDTO routeInfo,
                                      TmsIntelligentDispatch intelligentDispatch) {
        VehicleTypeDTO vehicleTypeDTO = vehicleTypeMap.get(routeInfo.getVehicleType());
        VehicleDTO vehicleDTO = vehicleMap.get(routeInfo.getVehiclePlate());
        List<IntelligentDispatchResultConsigneeRouteDTO> consigneeRouteList = routeInfo.getConsigneeRoute();
        if (CollectionUtil.isNotEmpty(consigneeRouteList)) {
            List<String> shipBillIdList = consigneeRouteList.stream().filter(consigneeRoute ->
                    CollectionUtil.isNotEmpty(consigneeRoute.getShipBillIds())).flatMap(consigneeRoute ->
                    consigneeRoute.getShipBillIds().stream()).distinct().collect(Collectors.toList());
            consigneeRouteList.forEach(consigneeRoute -> {
                if (0 == consigneeRoute.getDeliveryOrder() && CollectionUtil.isEmpty(consigneeRoute.getShipBillIds())) {
                    consigneeRoute.setShipBillIds(shipBillIdList);
                }
            });
            TmsIntelligentDispatchLoading loading = createLoading(vehicleTypeDTO, vehicleDTO, consigneeRouteList, intelligentDispatch, shipBillIdList);
            createShip(consigneeRouteList, loading);
        }
    }

    /**
     * createShip
     */
    private void createShip(List<IntelligentDispatchResultConsigneeRouteDTO> consigneeRouteList, TmsIntelligentDispatchLoading loading) {
        List<TmsIntelligentDispatchShip> shipList = Lists.newArrayList();
        consigneeRouteList.forEach(consigneeRoute -> consigneeRoute.getShipBillIds().forEach(shipBillId -> {
            TmsIntelligentDispatchShip ship = new TmsIntelligentDispatchShip();
            ship.setId(ToolKits.getUUID());
            ship.setShipbillId(shipBillId);
            ship.setLoadingId(loading.getLoadingId());
            ship.setIntelligentDispatchId(loading.getIntelligentDispatchId());
            ship.setDeliveryOrder(consigneeRoute.getDeliveryOrder());
            ship.setEstimateCost(BigDecimal.ZERO);
            ship.setConsigneeId(consigneeRoute.getConsigneeId());
            ship.setLongitude(consigneeRoute.getLongitudeLatitude().get(0));
            ship.setLatitude(consigneeRoute.getLongitudeLatitude().get(1));
            ship.setEnterpriseId(loading.getEnterpriseId());
            ship.setEnterpriseName(loading.getEnterpriseName());
            ship.setCreatetime(DateUtil.date());
            ship.setUpdatetime(DateUtil.date());
            shipList.add(ship);
        }));
        shipMapper.saveBatch(shipList);
    }

    /**
     * createLoading
     */
    private TmsIntelligentDispatchLoading createLoading(VehicleTypeDTO vehicleTypeDTO,
                                                        VehicleDTO vehicleDTO,
                                                        List<IntelligentDispatchResultConsigneeRouteDTO> consigneeRouteList,
                                                        TmsIntelligentDispatch intelligentDispatch,
                                                        List<String> shipBillIdList) {
        TmsIntelligentDispatchLoading loading = new TmsIntelligentDispatchLoading();
        loading.setLoadingId(ToolKits.getUUID());
        loading.setIntelligentDispatchId(intelligentDispatch.getIntelligentDispatchId());
        consigneeRouteList = consigneeRouteList.stream().sorted(Comparator.comparing(IntelligentDispatchResultConsigneeRouteDTO::getDeliveryOrder)).collect(Collectors.toList());
        List<IntelligentDispatchResultConsigneeRouteDTO> finalConsigneeRouteList = consigneeRouteList;
        List<IntelligentDispatchDistanceVO> distanceVOList = Lists.newArrayList();
        IntStream.range(0, finalConsigneeRouteList.size()-1).forEach(i -> {
            IntelligentDispatchResultConsigneeRouteDTO first = finalConsigneeRouteList.get(i);
            IntelligentDispatchResultConsigneeRouteDTO second = finalConsigneeRouteList.get(i+1);
            IntelligentDispatchDistanceVO distanceVO = new IntelligentDispatchDistanceVO();
            distanceVO.setOriginLongitude(first.getLongitudeLatitude().get(0));
            distanceVO.setOriginLatitude(first.getLongitudeLatitude().get(1));
            distanceVO.setDestinationLongitude(second.getLongitudeLatitude().get(0));
            distanceVO.setDestinationLatitude(second.getLongitudeLatitude().get(1));
            distanceVO.setStrategy(STRATEGY);
            distanceVOList.add(distanceVO);
        });
        Pair<BigDecimal, BigDecimal> result = getDistanceAndDuration(distanceVOList);
        loading.setTotalMileage(result.getKey());
        loading.setDriveDuration(result.getValue());
        loading.setEstimateCost(BigDecimal.ZERO);
        loading.setStatus(IntelligentDispatchLoadingStatusEnum.PENDING.getCode());
        loading.setEnterpriseId(intelligentDispatch.getEnterpriseId());
        loading.setEnterpriseName(intelligentDispatch.getEnterpriseName());
        loading.setCreatetime(DateUtil.date());
        loading.setUpdatetime(DateUtil.date());
        if (ObjectUtil.isNotNull(vehicleDTO)) {
            loading.setVehicleId(vehicleDTO.getId());
            loading.setVehicleCode(vehicleDTO.getCode());
            loading.setVehicleName(vehicleDTO.getPlateNumber());
            loading.setVehiclePlateNumber(vehicleDTO.getPlateNumber());
            loading.setVehicleAscription(vehicleDTO.getType());
            loading.setVehicleAffiliationId(ObjectUtil.isNotNull(vehicleDTO.getAffiliationId()) ? vehicleDTO.getAffiliationId().toString() : "");
            loading.setVehicleAffiliationCode(vehicleDTO.getAffiliationCode());
            loading.setVehicleAffiliationName(vehicleDTO.getAffiliationName());
        }
        loading.setVehicleTypeId(vehicleTypeDTO.getId());
        loading.setVehicleTypeCode(vehicleTypeDTO.getCode());
        loading.setVehicleTypeName(vehicleTypeDTO.getName());
        loading.setVehicleTypeCategory(ObjectUtil.isNotNull(vehicleTypeDTO.getType()) ? vehicleTypeDTO.getType().toString() : "");
        loading.setVehicleTypeVolume(vehicleTypeDTO.getVolume());
        loading.setVehicleTypeWeight(vehicleTypeDTO.getWeight());
        loading.setCarrierType(DataDictionaryConstant.LINE_CARRIER_TYPE_SJ);
        loading.setShipBillNum(shipBillIdList.size());
        List<TmsShipbillHeaderModel> shipbillHeaderList = shipbillHeaderMapper.findByIds(TmsShipbillHeaderModel.class, shipBillIdList.toArray(), null);
        loading.setTotalPcs(shipbillHeaderList.stream().map(TmsShipbillHeaderModel::getTotalPcs).reduce(BigDecimal.ZERO, NumberUtil::add));
        loading.setTotalVolume(shipbillHeaderList.stream().map(TmsShipbillHeaderModel::getTotalVolume).reduce(BigDecimal.ZERO, NumberUtil::add));
        loading.setTotalWeight(shipbillHeaderList.stream().map(TmsShipbillHeaderModel::getTotalWeight).reduce(BigDecimal.ZERO, NumberUtil::add));
        loading.setVolumeLoadingRate(NumberUtil.div(NumberUtil.mul(loading.getTotalVolume(), 100), vehicleTypeDTO.getVolume(), 0, RoundingMode.HALF_UP));
        loading.setWeightLoadingRate(NumberUtil.div(NumberUtil.mul(loading.getTotalWeight(), 100), vehicleTypeDTO.getWeight(), 0, RoundingMode.HALF_UP));
        loading.setLoadingRate(NumberUtil.max(loading.getVolumeLoadingRate(), loading.getWeightLoadingRate()));
        loading.setDepartmentId(shipbillHeaderList.stream().map(TmsShipbillHeaderModel::getDepartmentId).distinct().collect(Collectors.joining(",")));
        loading.setDepartmentCode(shipbillHeaderList.stream().map(TmsShipbillHeaderModel::getDepartmentCode).distinct().collect(Collectors.joining(",")));
        loading.setDepartmentName(shipbillHeaderList.stream().map(TmsShipbillHeaderModel::getDepartmentName).distinct().collect(Collectors.joining(",")));
        loadingMapper.save(loading);
        return loading;
    }

    /**
     * checkLoading
     */
    private void checkLoading(TmsIntelligentDispatchLoading loading) {
        if (ObjectUtil.isNull(loading)) {
            throw new PrologException("线路不存在");
        }
        if (IntelligentDispatchLoadingStatusEnum.DISPATCH.getCode().equals(loading.getStatus())) {
            throw new PrologException("该车辆【" + loading.getVehiclePlateNumber() + "】的线路已调度");
        }
    }

    /**
     * dispatchOrReviewLoading
     */
    private void dispatchOrReviewLoading(List<String> loadingIdList, String type) {
        UserEntity user = PlgFxUsrUtils.getUserEntity();
        List<TmsIntelligentDispatchLoading> loadingList = preCheck(loadingIdList);
        secondCheck(loadingList);
        loadingList.forEach(loading -> {
            loading.setStatus(IntelligentDispatchLoadingStatusEnum.DISPATCH.getCode());
            loading.setUpdateperson(user.getNickname());
            loading.setUpdatetime(DateUtil.date());
            loadingMapper.update(loading);
        });
        List<PendingDispatchVO> dispatchVOList = buildPendingDispatchVO(loadingList);
        if ("dispatch".equals(type)) {
            dispatchVOList.forEach(pendingDispatchService::dispatch);
        } else {
            dispatchVOList.forEach(pendingDispatchService::dispatchReview);
        }
        recalculate(loadingList.get(0).getIntelligentDispatchId());
    }

    /**
     * buildPendingDispatchVO
     */
    private List<PendingDispatchVO> buildPendingDispatchVO(List<TmsIntelligentDispatchLoading> loadingList) {
        List<TmsIntelligentDispatchShip> shipList = findShipByIntelligentDispatchLoadingList(loadingList);
        Map<String, List<TmsIntelligentDispatchShip>> shipMap = shipList.stream().collect(Collectors.groupingBy(TmsIntelligentDispatchShip::getLoadingId));
        return loadingList.stream().map(loading -> {
            PendingDispatchVO dispatchVO = new PendingDispatchVO();
            LoadingBillHeaderVO headerVO = BeanUtil.copyProperties(loading, LoadingBillHeaderVO.class);
            headerVO.setCarrierType(DataDictionaryConstant.LINE_CARRIER_TYPE_SJ);
            List<TmsIntelligentDispatchShip> ships = shipMap.get(loading.getLoadingId());
            if (CollectionUtil.isEmpty(ships)) {
                throw new PrologException("运单信息为空，不能调度，loadingId=" + loading.getLoadingId());
            }
            dispatchVO.setLoadingBillDetailVO(ships.stream().filter(ship -> 0 != ship.getDeliveryOrder()).map(ship -> {
                LoadingBillDetailVO detailVO = new LoadingBillDetailVO();
                detailVO.setShippingOrder(ship.getDeliveryOrder());
                detailVO.setPickUpOrder(1);
                detailVO.setShipbillId(ship.getShipbillId());
                return detailVO;
            }).collect(Collectors.toList()));
            dispatchVO.setLoadingBillHeaderVO(headerVO);
            return dispatchVO;
        }).collect(Collectors.toList());
    }

    /**
     * recalculate
     */
    private void recalculate(String intelligentDispatchId) {
        UserEntity user = PlgFxUsrUtils.getUserEntity();
        TmsIntelligentDispatch intelligentDispatch = findById(intelligentDispatchId);
        List<TmsIntelligentDispatchLoading> loadingList = findLoadingByIntelligentDispatchId(intelligentDispatchId);
        intelligentDispatch.setVehicleNum((int)loadingList.stream().filter(loading -> ObjectUtil.isNotEmpty(loading.getVehicleId())).count());
        intelligentDispatch.setLineNum(loadingList.size());
        intelligentDispatch.setShipBillNum(loadingList.stream().mapToInt(TmsIntelligentDispatchLoading::getShipBillNum).sum());
        intelligentDispatch.setDispatchVehicleNum((int)loadingList.stream().filter(loading -> IntelligentDispatchLoadingStatusEnum.DISPATCH.getCode().equals(loading.getStatus())).map(TmsIntelligentDispatchLoading::getVehicleId).distinct().count());
        intelligentDispatch.setDispatchLineNum((int)loadingList.stream().filter(loading -> IntelligentDispatchLoadingStatusEnum.DISPATCH.getCode().equals(loading.getStatus())).count());
        intelligentDispatch.setDispatchShipBillNum(loadingList.stream().filter(loading -> IntelligentDispatchLoadingStatusEnum.DISPATCH.getCode().equals(loading.getStatus())).mapToInt(TmsIntelligentDispatchLoading::getShipBillNum).sum());
        intelligentDispatch.setEstimateCost(loadingList.stream().map(TmsIntelligentDispatchLoading::getEstimateCost).reduce(BigDecimal.ZERO, NumberUtil::add));
        intelligentDispatch.setUpdateperson(user.getNickname());
        intelligentDispatch.setUpdatetime(DateUtil.date());
        if (loadingList.stream().allMatch(loading -> IntelligentDispatchLoadingStatusEnum.DISPATCH.getCode().equals(loading.getStatus()))) {
            intelligentDispatch.setStatus(IntelligentDispatchStatusEnum.FINISH.getCode());
        } else if (loadingList.stream().allMatch(loading -> IntelligentDispatchLoadingStatusEnum.PENDING.getCode().equals(loading.getStatus()))) {
            intelligentDispatch.setStatus(IntelligentDispatchStatusEnum.PENDING.getCode());
        } else {
            intelligentDispatch.setStatus(IntelligentDispatchStatusEnum.DISPATCH.getCode());
        }
        mapper.update(intelligentDispatch);
    }

    /**
     * preCheck
     */
    private List<TmsIntelligentDispatchLoading> preCheck(List<String> loadingIdList) {
        if (CollectionUtil.isEmpty(loadingIdList)) {
            throw new PrologException("线路id为空");
        }
        List<TmsIntelligentDispatchLoading> loadingList = loadingMapper.findByIds(TmsIntelligentDispatchLoading.class, loadingIdList.toArray(), null);
        if (CollectionUtil.isEmpty(loadingList)) {
            throw new PrologException("线路不存在");
        }
        List<TmsIntelligentDispatchLoading> filterLoading = loadingList.stream().filter(loading -> IntelligentDispatchLoadingStatusEnum.DISPATCH.getCode().equals(loading.getStatus())).collect(Collectors.toList());
        if (CollectionUtil.isNotEmpty(filterLoading)) {
            throw new PrologException("该车辆【" + filterLoading.get(0).getVehiclePlateNumber() + "】的线路已调度");
        }
        if (loadingList.stream().anyMatch(loading -> ObjectUtil.isNull(loading.getVehicleId()))) {
            throw new PrologException("调度车辆不能为空");
        }
        if (loadingList.stream().anyMatch(loading -> ObjectUtil.isNull(loading.getDriverId()))) {
            throw new PrologException("调度司机不能为空");
        }
        return loadingList;
    }

    /**
     * secondCheck
     */
    private void secondCheck(List<TmsIntelligentDispatchLoading> loadingList) {
        if (loadingList.stream().anyMatch(loading -> ObjectUtil.isEmpty(loading.getDriverId()))) {
            throw new PrologException("司机信息为空，不能调度");
        }
        if (loadingList.stream().anyMatch(loading -> ObjectUtil.isEmpty(loading.getVehicleId()))) {
            throw new PrologException("车辆信息为空，不能调度");
        }
    }

    /**
     * convertIntelligentDispatchLoadingDTO
     */
    private List<IntelligentDispatchLoadingDTO> convertIntelligentDispatchLoadingDTO(List<TmsIntelligentDispatchLoading> loadingList, List<IntelligentDispatchShipDTO> shipDTOList, List<SysRefdatadModel> dicData) {
        Map<String, List<IntelligentDispatchShipDTO>> shipDTOMap = shipDTOList.stream().collect(Collectors.groupingBy(IntelligentDispatchShipDTO::getLoadingId));
        return loadingList.stream().map(loading -> {
            IntelligentDispatchLoadingDTO loadingDTO = BeanUtil.copyProperties(loading, IntelligentDispatchLoadingDTO.class);
            loadingDTO.setStatusName(ConvertorUtil.getName(dicData, IntelligentDispatchLoadingStatusEnum.NAME, String.valueOf(loadingDTO.getStatus())));
            loadingDTO.setVehicleTypeCategoryName(ConvertorUtil.getName(dicData, DataDictionaryConstant.VEHICLE_TYPE_CATEGORY, loadingDTO.getVehicleTypeCategory()));
            loadingDTO.setLoadingRateName(NumberUtil.null2Zero(loadingDTO.getLoadingRate()).stripTrailingZeros().toPlainString() + "%");
            loadingDTO.setShipDTOList(shipDTOMap.getOrDefault(loadingDTO.getLoadingId(), Lists.newArrayList()));
            if (CollectionUtil.isNotEmpty(loadingDTO.getShipDTOList())) {
                loadingDTO.setShipmentAddress(loadingDTO.getShipDTOList().get(0).getShipperAddress());
                loadingDTO.setReceiptAddress(loadingDTO.getShipDTOList().get(loadingDTO.getShipDTOList().size() - 1).getConsigneeAddress());
            }
            return loadingDTO;
        }).sorted(Comparator.comparing(IntelligentDispatchLoadingDTO::getStatus)
                .thenComparing(Comparator.comparing(IntelligentDispatchLoadingDTO::getVehicleTypeVolume).reversed())
                .thenComparing(IntelligentDispatchLoadingDTO::getLoadingRate)).collect(Collectors.toList());
    }

    /**
     * convertIntelligentDispatchShipDTO
     */
    private List<IntelligentDispatchShipDTO> convertIntelligentDispatchShipDTO(List<TmsIntelligentDispatchShip> shipList, List<SysRefdatadModel> dicData) {
        if (CollectionUtil.isEmpty(shipList)) {
            return Lists.newArrayList();
        }
        List<TmsShipbillHeaderModel> shipBillHeaderList = shipbillHeaderMapper.findByIds(TmsShipbillHeaderModel.class, shipList.stream().map(TmsIntelligentDispatchShip::getShipbillId).distinct().toArray(), null);
        UserEntity user = PlgFxUsrUtils.getUserEntity();
        Criteria criteria = Criteria.forClass(TmsShipbillDetailModel.class);
        Restriction r1 = Restrictions.eq(TmsShipbillDetailModel::getEnterpriseId, user.getEnterpriseId());
        Restriction r2 = Restrictions.in(TmsShipbillDetailModel::getShipbillId, shipList.stream().map(TmsIntelligentDispatchShip::getShipbillId).distinct().collect(Collectors.toList()));
        criteria.setRestriction(Restrictions.and(r1, r2));
        List<TmsShipbillDetailModel> shipBillDetailList = shipbillDetailMapper.findByCriteria(criteria);
        Map<String, List<TmsShipbillDetailModel>> shipBillDetailMap = shipBillDetailList.stream().collect(Collectors.groupingBy(TmsShipbillDetailModel::getShipbillId));
        Map<String, List<TmsIntelligentDispatchShip>> shipMap = shipList.stream().collect(Collectors.groupingBy(TmsIntelligentDispatchShip::getShipbillId));
        return shipBillHeaderList.stream().flatMap(shipBillHeader -> {
            List<TmsIntelligentDispatchShip> dispatchShipList = shipMap.getOrDefault(shipBillHeader.getShipbillId(), Lists.newArrayList());
            return dispatchShipList.stream().map(dispatchShip -> {
                IntelligentDispatchShipDTO shipDTO = BeanUtil.copyProperties(shipBillHeader, IntelligentDispatchShipDTO.class);
                shipDTO.setLoadingId(dispatchShip.getLoadingId());
                shipDTO.setIntelligentDispatchId(dispatchShip.getIntelligentDispatchId());
                shipDTO.setDeliveryOrder(dispatchShip.getDeliveryOrder());
                shipDTO.setEstimateCost(dispatchShip.getEstimateCost());
                shipDTO.setLongitude(dispatchShip.getLongitude());
                shipDTO.setLatitude(dispatchShip.getLatitude());
                List<TmsShipbillDetailModel> detailList = shipBillDetailMap.get(shipDTO.getShipbillId());
                if (CollectionUtil.isNotEmpty(detailList)) {
                    shipDTO.setItemInfo(detailList.stream().map(detail -> detail.getItemName() + NumberUtil.null2Zero(detail.getPcs()).stripTrailingZeros().toPlainString() +
                            (ObjectUtil.isNotNull(detail.getPackType()) ? ConvertorUtil.getName(dicData, DataDictionaryConstant.PACK_TYPE, String.valueOf(detail.getPackType())) : "")
                    ).collect(Collectors.joining(",")));
                }
                shipDTO.setStatusName(ConvertorUtil.getName(dicData, DataDictionaryConstant.SHIPBILL_STATUS, String.valueOf(shipDTO.getStatus())));
                shipDTO.setTransportStatusName(ConvertorUtil.getName(dicData, DataDictionaryConstant.TRANSPORT_STATUS, String.valueOf(shipDTO.getTransportStatus())));
                shipDTO.setAbnormalStatusName(ConvertorUtil.getName(dicData, DataDictionaryConstant.ABNORMAL_STATUS, String.valueOf(shipDTO.getAbnormalStatus())));
                shipDTO.setOrderTypeName(ConvertorUtil.getName(dicData, DataDictionaryConstant.ORDER_TYPE, String.valueOf(shipDTO.getOrderType())));
                shipDTO.setOrderBusinessTypeName(ConvertorUtil.getName(dicData, DataDictionaryConstant.ORDER_BUSINESS_TYPE, String.valueOf(shipDTO.getOrderBusinessType())));
                shipDTO.setDeliveryMethodTypeName(ConvertorUtil.getName(dicData, DataDictionaryConstant.DELIVERY_METHOD_TYPE, String.valueOf(shipDTO.getDeliveryMethodType())));
                shipDTO.setTransportTypeName(ConvertorUtil.getName(dicData, DataDictionaryConstant.TRANSPORT_TYPE, String.valueOf(shipDTO.getTransportType())));
                shipDTO.setTemperatureRequirementName(ConvertorUtil.getName(dicData, DataDictionaryConstant.TEMPERATURE_REQUIREMENT, String.valueOf(shipDTO.getTemperatureRequirement())));
                shipDTO.setShipperTypeName(ConvertorUtil.getName(dicData, DataDictionaryConstant.SHIPPER_TYPE, String.valueOf(shipDTO.getShipperType())));
                shipDTO.setConsigneeTypeName(ConvertorUtil.getName(dicData, DataDictionaryConstant.CONSIGNEE_TYPE, String.valueOf(shipDTO.getConsigneeType())));
                return shipDTO;
            });
        }).sorted(Comparator.comparing(IntelligentDispatchShipDTO::getDeliveryOrder)
                .thenComparing(Comparator.comparing(IntelligentDispatchShipDTO::getShipbillId).reversed())).collect(Collectors.toList());
    }

    /**
     * findById
     */
    private TmsIntelligentDispatch findById(String intelligentDispatchId) {
        return mapper.findById(intelligentDispatchId, TmsIntelligentDispatch.class);
    }

    /**
     * findLoadingByIntelligentDispatchId
     */
    private List<TmsIntelligentDispatchLoading> findLoadingByIntelligentDispatchId(String intelligentDispatchId) {
        UserEntity user = PlgFxUsrUtils.getUserEntity();
        Criteria criteria = Criteria.forClass(TmsIntelligentDispatchLoading.class);
        Restriction r1 = Restrictions.eq(TmsIntelligentDispatchLoading::getEnterpriseId, user.getEnterpriseId());
        Restriction r2 = Restrictions.eq(TmsIntelligentDispatchLoading::getIntelligentDispatchId, intelligentDispatchId);
        criteria.setRestriction(Restrictions.and(r1, r2));
        return loadingMapper.findByCriteria(criteria);
    }

    /**
     * findShipByIntelligentDispatchId
     */
    private List<TmsIntelligentDispatchShip> findShipByIntelligentDispatchId(String intelligentDispatchId) {
        UserEntity user = PlgFxUsrUtils.getUserEntity();
        Criteria criteria = Criteria.forClass(TmsIntelligentDispatchShip.class);
        Restriction r1 = Restrictions.eq(TmsIntelligentDispatchShip::getEnterpriseId, user.getEnterpriseId());
        Restriction r2 = Restrictions.eq(TmsIntelligentDispatchShip::getIntelligentDispatchId, intelligentDispatchId);
        criteria.setRestriction(Restrictions.and(r1, r2));
        return shipMapper.findByCriteria(criteria);
    }

    /**
     * findShipByIntelligentDispatchLoadingList
     */
    private List<TmsIntelligentDispatchShip> findShipByIntelligentDispatchLoadingList(List<TmsIntelligentDispatchLoading> loadingList) {
        UserEntity user = PlgFxUsrUtils.getUserEntity();
        Criteria criteria = Criteria.forClass(TmsIntelligentDispatchShip.class);
        Restriction r1 = Restrictions.eq(TmsIntelligentDispatchShip::getEnterpriseId, user.getEnterpriseId());
        Restriction r2 = Restrictions.in(TmsIntelligentDispatchShip::getLoadingId, loadingList.stream().map(TmsIntelligentDispatchLoading::getLoadingId).collect(Collectors.toList()));
        criteria.setRestriction(Restrictions.and(r1, r2));
        return shipMapper.findByCriteria(criteria);
    }

    /**
     * 设置数据权限
     */
    private void setDataPermission(IntelligentDispatchGroupQueryVO queryVO) {
        UserEntity user = PlgFxUsrUtils.getUserEntity();
        queryVO.setEnterpriseId(user.getEnterpriseId());
        queryVO.setUserType(user.getUserType());
        // 承运商
        if (BussinessConstant.USER_TYPE_CYS.equals(user.getUserType())){
            queryVO.setCarrierId(Long.valueOf(user.getLoginUserCompanyInfo().getId()));
        } else {
            queryVO.setDeptList(user.getDepts());
            // 部门权限
            if (BussinessConstant.USER_DEPARTMENT_AUTH_Y.equals(user.getOwnedAllDataAuthFlag())) {
                queryVO.setIsDept(Y);
            } else {
                queryVO.setIsDept(N);
                queryVO.setConsignorList(user.getConsignors());
                List<Long> unitIdList = Lists.newArrayList();
                if (CollectionUtil.isNotEmpty(user.getCustomers())) {
                    unitIdList.addAll(user.getCustomers());
                }
                if (CollectionUtil.isNotEmpty(user.getSuppliers())) {
                    unitIdList.addAll(user.getSuppliers());
                }
                if (CollectionUtil.isNotEmpty(user.getWarehouses())) {
                    unitIdList.addAll(user.getWarehouses());
                }
                queryVO.setUnitIdList(unitIdList.stream().filter(ObjectUtil::isNotNull).distinct().collect(Collectors.toList()));
            }
        }
    }

    /**
     * 初始化数据
     */
    private void setInitData(IntelligentDispatchShipBillQueryVO queryVO) {
        queryVO.setEmptyLine(N);
        queryVO.setEmptyShipperStation(N);
        queryVO.setEmptyShipperStationAreas(N);
        queryVO.setEmptyConsigneeStation(N);
        queryVO.setEmptyConsigneeStationAreas(N);
        if (CollectionUtil.isNotEmpty(queryVO.getLineList()) && queryVO.getLineList().contains(NULL)) {
            queryVO.setEmptyLine(Y);
            queryVO.setLineList(queryVO.getLineList().stream().filter(s -> !NULL.equals(s)).collect(Collectors.toList()));
        }
        if (CollectionUtil.isNotEmpty(queryVO.getShipperStationList()) && queryVO.getShipperStationList().contains(NULL)) {
            queryVO.setEmptyShipperStation(Y);
            queryVO.setShipperStationList(queryVO.getShipperStationList().stream().filter(s -> !NULL.equals(s)).collect(Collectors.toList()));
        }
        if (CollectionUtil.isNotEmpty(queryVO.getShipperStationAreasList()) && queryVO.getShipperStationAreasList().contains(NULL)) {
            queryVO.setEmptyShipperStationAreas(Y);
            queryVO.setShipperStationAreasList(queryVO.getShipperStationAreasList().stream().filter(s -> !NULL.equals(s)).collect(Collectors.toList()));
        }
        if (CollectionUtil.isNotEmpty(queryVO.getConsigneeStationList()) && queryVO.getConsigneeStationList().contains(NULL)) {
            queryVO.setEmptyConsigneeStation(Y);
            queryVO.setConsigneeStationList(queryVO.getConsigneeStationList().stream().filter(s -> !NULL.equals(s)).collect(Collectors.toList()));
        }
        if (CollectionUtil.isNotEmpty(queryVO.getConsigneeStationAreasList()) && queryVO.getConsigneeStationAreasList().contains(NULL)) {
            queryVO.setEmptyConsigneeStationAreas(Y);
            queryVO.setConsigneeStationAreasList(queryVO.getConsigneeStationAreasList().stream().filter(s -> !NULL.equals(s)).collect(Collectors.toList()));
        }
    }

    /**
     * groupByCargoOwner
     */
    private List<IntelligentDispatchGroupCountDTO> groupByCargoOwner(List<IntelligentDispatchGroupPreDataDTO> dtoList) {
        Map<Long, List<IntelligentDispatchGroupPreDataDTO>> dtoMap = dtoList.stream().collect(Collectors.groupingBy(IntelligentDispatchGroupPreDataDTO::getCargoOwnerId));
        List<IntelligentDispatchGroupCountDTO> list = Lists.newArrayList();
        dtoMap.forEach((key, value) -> {
            IntelligentDispatchGroupCountDTO countDTO = new IntelligentDispatchGroupCountDTO();
            if (ObjectUtil.isEmpty(key)) {
                countDTO.setId(NULL);
                countDTO.setName("无");
            } else {
                countDTO.setId(key.toString());
                countDTO.setName(value.get(0).getCargoOwnerName());
            }
            countDTO.setCount(count(value));
            list.add(countDTO);
        });
        return list;
    }

    /**
     * groupByOrderBusinessType
     */
    private List<IntelligentDispatchGroupCountDTO> groupByOrderBusinessType(List<IntelligentDispatchGroupPreDataDTO> dtoList, List<SysRefdatadModel> dicData) {
        Map<Integer, List<IntelligentDispatchGroupPreDataDTO>> dtoMap = dtoList.stream().collect(Collectors.groupingBy(IntelligentDispatchGroupPreDataDTO::getOrderBusinessType));
        List<IntelligentDispatchGroupCountDTO> list = Lists.newArrayList();
        dtoMap.forEach((key, value) -> {
            IntelligentDispatchGroupCountDTO countDTO = new IntelligentDispatchGroupCountDTO();
            if (ObjectUtil.isEmpty(key)) {
                countDTO.setId(NULL);
                countDTO.setName("无");
            } else {
                countDTO.setId(key.toString());
                countDTO.setName(ConvertorUtil.getName(dicData, DataDictionaryConstant.ORDER_BUSINESS_TYPE, String.valueOf(key)));
            }
            countDTO.setCount(count(value));
            list.add(countDTO);
        });
        return list;
    }

    /**
     * groupByTransportType
     */
    private List<IntelligentDispatchGroupCountDTO> groupByTransportType(List<IntelligentDispatchGroupPreDataDTO> dtoList, List<SysRefdatadModel> dicData) {
        Map<Integer, List<IntelligentDispatchGroupPreDataDTO>> dtoMap = dtoList.stream().collect(Collectors.groupingBy(IntelligentDispatchGroupPreDataDTO::getTransportType));
        List<IntelligentDispatchGroupCountDTO> list = Lists.newArrayList();
        dtoMap.forEach((key, value) -> {
            IntelligentDispatchGroupCountDTO countDTO = new IntelligentDispatchGroupCountDTO();
            if (ObjectUtil.isEmpty(key)) {
                countDTO.setId(NULL);
                countDTO.setName("无");
            } else {
                countDTO.setId(key.toString());
                countDTO.setName(ConvertorUtil.getName(dicData, DataDictionaryConstant.TRANSPORT_TYPE, String.valueOf(key)));
            }
            countDTO.setCount(count(value));
            list.add(countDTO);
        });
        return list;
    }

    /**
     * groupByDeliveryMethodType
     */
    private List<IntelligentDispatchGroupCountDTO> groupByDeliveryMethodType(List<IntelligentDispatchGroupPreDataDTO> dtoList, List<SysRefdatadModel> dicData) {
        Map<Integer, List<IntelligentDispatchGroupPreDataDTO>> dtoMap = dtoList.stream().collect(Collectors.groupingBy(IntelligentDispatchGroupPreDataDTO::getDeliveryMethodType));
        List<IntelligentDispatchGroupCountDTO> list = Lists.newArrayList();
        dtoMap.forEach((key, value) -> {
            IntelligentDispatchGroupCountDTO countDTO = new IntelligentDispatchGroupCountDTO();
            if (ObjectUtil.isEmpty(key)) {
                countDTO.setId(NULL);
                countDTO.setName("无");
            } else {
                countDTO.setId(key.toString());
                countDTO.setName(ConvertorUtil.getName(dicData, DataDictionaryConstant.DELIVERY_METHOD_TYPE, String.valueOf(key)));
            }
            countDTO.setCount(count(value));
            list.add(countDTO);
        });
        return list;
    }

    /**
     * groupByTemperatureRequirement
     */
    private List<IntelligentDispatchGroupCountDTO> groupByTemperatureRequirement(List<IntelligentDispatchGroupPreDataDTO> dtoList, List<SysRefdatadModel> dicData) {
        Map<Integer, List<IntelligentDispatchGroupPreDataDTO>> dtoMap = dtoList.stream().collect(Collectors.groupingBy(IntelligentDispatchGroupPreDataDTO::getTemperatureRequirement));
        List<IntelligentDispatchGroupCountDTO> list = Lists.newArrayList();
        dtoMap.forEach((key, value) -> {
            IntelligentDispatchGroupCountDTO countDTO = new IntelligentDispatchGroupCountDTO();
            if (ObjectUtil.isEmpty(key)) {
                countDTO.setId(NULL);
                countDTO.setName("无");
            } else {
                countDTO.setId(key.toString());
                countDTO.setName(ConvertorUtil.getName(dicData, DataDictionaryConstant.TEMPERATURE_REQUIREMENT, String.valueOf(key)));
            }
            countDTO.setCount(count(value));
            list.add(countDTO);
        });
        return list;
    }

    /**
     * groupByLine
     */
    private List<IntelligentDispatchGroupCountDTO> groupByLine(List<IntelligentDispatchGroupPreDataDTO> dtoList) {
        Map<String, List<IntelligentDispatchGroupPreDataDTO>> dtoMap = dtoList.stream().collect(Collectors.groupingBy(IntelligentDispatchGroupPreDataDTO::getLineId));
        List<IntelligentDispatchGroupCountDTO> list = Lists.newArrayList();
        dtoMap.forEach((key, value) -> {
            IntelligentDispatchGroupCountDTO countDTO = new IntelligentDispatchGroupCountDTO();
            countDTO.setId(key);
            if (NULL.equals(key)) {
                countDTO.setName("无");
            } else {
                countDTO.setName(value.get(0).getLineName());
            }
            countDTO.setCount(count(value));
            list.add(countDTO);
        });
        return list;
    }

    /**
     * groupByItemCategory
     */
    private List<IntelligentDispatchGroupCountDTO> groupByItemCategory(List<IntelligentDispatchGroupPreDataDTO> dtoList) {
        Map<String, List<IntelligentDispatchGroupPreDataDTO>> dtoMap = dtoList.stream().collect(Collectors.groupingBy(IntelligentDispatchGroupPreDataDTO::getItemCategoryId));
        List<IntelligentDispatchGroupCountDTO> list = Lists.newArrayList();
        dtoMap.forEach((key, value) -> {
            IntelligentDispatchGroupCountDTO countDTO = new IntelligentDispatchGroupCountDTO();
            countDTO.setId(key);
            if (NULL.equals(key)) {
                countDTO.setName("无");
            } else {
                countDTO.setName(value.get(0).getItemCategoryName());
            }
            countDTO.setCount(count(value));
            list.add(countDTO);
        });
        return list;
    }

    /**
     * groupByShipperStation
     */
    private List<IntelligentDispatchGroupCountDTO> groupByShipperStation(List<IntelligentDispatchGroupPreDataDTO> dtoList) {
        Map<String, List<IntelligentDispatchGroupPreDataDTO>> dtoMap = dtoList.stream().collect(Collectors.groupingBy(IntelligentDispatchGroupPreDataDTO::getShipperStationId));
        List<IntelligentDispatchGroupCountDTO> list = Lists.newArrayList();
        dtoMap.forEach((key, value) -> {
            IntelligentDispatchGroupCountDTO countDTO = new IntelligentDispatchGroupCountDTO();
            countDTO.setId(key);
            if (NULL.equals(key)) {
                countDTO.setName("无");
            } else {
                countDTO.setName(value.get(0).getShipperStationName());
            }
            countDTO.setCount(count(value));
            list.add(countDTO);
        });
        return list;
    }

    /**
     * groupByShipperStationAreas
     */
    private List<IntelligentDispatchGroupCountDTO> groupByShipperStationAreas(List<IntelligentDispatchGroupPreDataDTO> dtoList) {
        Map<String, List<IntelligentDispatchGroupPreDataDTO>> dtoMap = dtoList.stream().collect(Collectors.groupingBy(IntelligentDispatchGroupPreDataDTO::getShipperStationAreas));
        return mapToList(dtoMap);
    }

    /**
     * groupByConsigneeStation
     */
    private List<IntelligentDispatchGroupCountDTO> groupByConsigneeStation(List<IntelligentDispatchGroupPreDataDTO> dtoList) {
        Map<String, List<IntelligentDispatchGroupPreDataDTO>> dtoMap = dtoList.stream().collect(Collectors.groupingBy(IntelligentDispatchGroupPreDataDTO::getConsigneeStationId));
        List<IntelligentDispatchGroupCountDTO> list = Lists.newArrayList();
        dtoMap.forEach((key, value) -> {
            IntelligentDispatchGroupCountDTO countDTO= new IntelligentDispatchGroupCountDTO();
            countDTO.setId(key);
            if (NULL.equals(key)) {
                countDTO.setName("无");
            } else {
                countDTO.setName(value.get(0).getConsigneeStationName());
            }
            countDTO.setCount(count(value));
            list.add(countDTO);
        });
        return list;
    }

    /**
     * groupByConsigneeStationAreas
     */
    private List<IntelligentDispatchGroupCountDTO> groupByConsigneeStationAreas(List<IntelligentDispatchGroupPreDataDTO> dtoList) {
        Map<String, List<IntelligentDispatchGroupPreDataDTO>> dtoMap = dtoList.stream().collect(Collectors.groupingBy(IntelligentDispatchGroupPreDataDTO::getConsigneeStationAreas));
        return mapToList(dtoMap);
    }

    /**
     * mapToList
     */
    private List<IntelligentDispatchGroupCountDTO> mapToList(Map<String, List<IntelligentDispatchGroupPreDataDTO>> dtoMap) {
        List<IntelligentDispatchGroupCountDTO> list = Lists.newArrayList();
        dtoMap.forEach((key, value) -> {
            IntelligentDispatchGroupCountDTO countDTO= new IntelligentDispatchGroupCountDTO();
            countDTO.setId(key);
            if (NULL.equals(key)) {
                countDTO.setName("无");
            } else {
                countDTO.setName(key);
            }
            countDTO.setCount(count(value));
            list.add(countDTO);
        });
        return list;
    }

    /**
     * sort
     */
    private List<IntelligentDispatchGroupCountDTO> sort(List<IntelligentDispatchGroupCountDTO> list) {
        if (CollectionUtil.isNotEmpty(list)) {
            return list.stream().sorted(Comparator.comparing(IntelligentDispatchGroupCountDTO::getId)).collect(Collectors.toList());
        }
        return list;
    }

    /**
     * count
     */
    private Long count(List<IntelligentDispatchGroupPreDataDTO> dtoList) {
        return dtoList.stream().map(IntelligentDispatchGroupPreDataDTO::getShipbillId).distinct().count();
    }

    /**
     * getDistanceAndDuration
     */
    private Pair<BigDecimal, BigDecimal> getDistanceAndDuration(List<IntelligentDispatchDistanceVO> distanceVOList) {
        BigDecimal distance = BigDecimal.ZERO;
        BigDecimal duration = BigDecimal.ZERO;
        for (IntelligentDispatchDistanceVO distanceVO : distanceVOList){
            Pair<BigDecimal, BigDecimal> distanceAndDuration;
            try {
                distanceAndDuration = getDistanceAndDurationByAmap(distanceVO);
            } catch (Exception e) {
                log.error("getDistanceAndDuration distanceVOList={}", JSON.toJSONString(distanceVOList), e);
                return new Pair<>(BigDecimal.ZERO, BigDecimal.ZERO);
            }
            distance = NumberUtil.add(distance, distanceAndDuration.getKey());
            duration = NumberUtil.add(duration, distanceAndDuration.getValue());
        }
        distance = NumberUtil.div(distance, 1000, 1, RoundingMode.HALF_UP);
        duration = NumberUtil.div(duration, 3600, 2, RoundingMode.HALF_UP);
        return new Pair<>(distance, duration);
    }

    /**
     * getDistanceAndDurationByAmap
     */
    private Pair<BigDecimal, BigDecimal> getDistanceAndDurationByAmap(IntelligentDispatchDistanceVO distanceVO) {
        String origin = NumberUtil.null2Zero(distanceVO.getOriginLongitude()).stripTrailingZeros().toPlainString() + "," + NumberUtil.null2Zero(distanceVO.getOriginLatitude()).stripTrailingZeros().toPlainString();
        String destination = NumberUtil.null2Zero(distanceVO.getDestinationLongitude()).stripTrailingZeros().toPlainString() + "," + NumberUtil.null2Zero(distanceVO.getDestinationLatitude()).stripTrailingZeros().toPlainString();
        String showFields = "cost";
        String requestUrl = StrUtil.format("https://restapi.amap.com/v5/direction/driving?key={}&origin={}&destination={}&show_fields={}&strategy={}", aMapKey, origin, destination, showFields, distanceVO.getStrategy());
        log.info("getDistanceAndDurationByAmap requestUrl={}", requestUrl);
        String resultJson = HttpUtil.get(requestUrl, TIME_OUT);
        log.info("getDistanceAndDurationByAmap resultJson={}", resultJson);
        JSONObject result = JSON.parseObject(resultJson);
        String status = result.getString("status");
        if (!"1".equals(status)) {
            throw new PrologException("调用地图失败，原因：" + result.getString("info"));
        }
        JSONObject path = (JSONObject)result.getJSONObject("route").getJSONArray("paths").get(0);
        BigDecimal distance = path.getBigDecimal("distance");
        BigDecimal duration = path.getJSONObject("cost").getBigDecimal("duration");
        return new Pair<>(distance, duration);
    }

    /**
     * buildIntelligentDTO
     */
    private IntelligentDTO buildIntelligentDTO(List<String> shipBillIdList, IntelligentDispatchVO vo, List<SysRefdatadModel> dicData) {
        if (IntelligentDispatchTypeEnum.VEHICLE_TYPE.getCode().equals(vo.getIntelligentDispatchType()) && CollectionUtil.isEmpty(vo.getVehicleTypeVOList())) {
            throw new PrologException("按车型调度，车型不能为空");
        }
        if (IntelligentDispatchTypeEnum.VEHICLE.getCode().equals(vo.getIntelligentDispatchType()) && CollectionUtil.isEmpty(vo.getVehicleIdList())) {
            throw new PrologException("按车辆调度，车辆不能为空");
        }
        UserEntity user = PlgFxUsrUtils.getUserEntity();
        Criteria criteria = Criteria.forClass(TmsShipbillDetailModel.class);
        Restriction r1 = Restrictions.eq(TmsShipbillDetailModel::getEnterpriseId, user.getEnterpriseId());
        Restriction r2 = Restrictions.in(TmsShipbillDetailModel::getShipbillId, shipBillIdList);
        criteria.setRestriction(Restrictions.and(r1, r2));
        List<TmsShipbillDetailModel> shipBillDetailList = shipbillDetailMapper.findByCriteria(criteria);
        Map<String, List<TmsShipbillDetailModel>> shipBillDetailMap = shipBillDetailList.stream().collect(Collectors.groupingBy(TmsShipbillDetailModel::getShipbillId));
        List<TmsShipbillHeaderModel> shipBillHeaderList = shipbillHeaderMapper.findByIds(TmsShipbillHeaderModel.class, shipBillIdList.toArray(), null);
        List<Long> branchIdList = Lists.newArrayList();
        List<Long> customerIdList = Lists.newArrayList();
        List<Long> consigneeIdList = Lists.newArrayList();
        Map<String, Pair<BigDecimal, BigDecimal>> pathMap = Maps.newHashMap();
        List<IntelligentShipBillDTO> shipbills = Lists.newArrayList();
        shipBillHeaderList.forEach(shipBillHeader -> {
            if (DataDictionaryConstant.SHIPPER_TYPE_KH.equals(shipBillHeader.getShipperType())) {
                customerIdList.add(shipBillHeader.getShipperUnitId());
                consigneeIdList.add(shipBillHeader.getShipperId());
            }
            if (DataDictionaryConstant.SHIPPER_TYPE_CK.equals(shipBillHeader.getShipperType())) {
                branchIdList.add(shipBillHeader.getShipperId());
            }
            if (DataDictionaryConstant.CONSIGNEE_TYPE_KH.equals(shipBillHeader.getConsigneeType())) {
                customerIdList.add(shipBillHeader.getConsigneeUnitId());
                consigneeIdList.add(shipBillHeader.getConsigneeId());
            }
            if (DataDictionaryConstant.CONSIGNEE_TYPE_CK.equals(shipBillHeader.getConsigneeType())) {
                branchIdList.add(shipBillHeader.getConsigneeId());
            }
            pathMap.put(shipBillHeader.getShipperType() + "-" + shipBillHeader.getShipperId(), new Pair<>(shipBillHeader.getShipperLongitude(), shipBillHeader.getShipperLatitude()));
            pathMap.put(shipBillHeader.getConsigneeType() + "-" + shipBillHeader.getConsigneeId(), new Pair<>(shipBillHeader.getConsigneeLongitude(), shipBillHeader.getConsigneeLatitude()));
            shipbills.add(buildIntelligentShipBillDTO(shipBillHeader, shipBillDetailMap.get(shipBillHeader.getShipbillId())));
        });
        List<Map<String, String>> pathDistances = buildPathDistance(pathMap);
        List<IntelligentVehicleDTO> vehicle = buildIntelligentVehicleDTOList(vo.getVehicleIdList());
        if (CollectionUtil.isNotEmpty(vehicle)) {
            if (CollectionUtil.isEmpty(vo.getVehicleTypeVOList())) {
                vo.setVehicleTypeVOList(Lists.newArrayList());
            }
            vehicle.stream().collect(Collectors.groupingBy(IntelligentVehicleDTO::getVehicleTypeId)).forEach((k, v) -> {
                IntelligentDispatchVehicleTypeVO vehicleTypeVO = new IntelligentDispatchVehicleTypeVO();
                vehicleTypeVO.setId(Long.valueOf(k));
                vehicleTypeVO.setVehicleNum(v.size());
                vo.getVehicleTypeVOList().add(vehicleTypeVO);
            });
        }
        List<IntelligentVehicleTypeDTO> vehicleTypes = buildIntelligentVehicleTypeDTOList(vo.getVehicleTypeVOList());
        List<IntelligentBranchDTO> branchs = buildIntelligentBranchDTOList(branchIdList);
        List<IntelligentCustomerDTO> customerList = buildIntelligentCustomerDTOList(customerIdList);
        List<IntelligentConsigneeDTO> consignees = buildIntelligentConsigneeDTOList(consigneeIdList);
        Map<String, List<IntelligentConsigneeDTO>> consigneeMap = consignees.stream().collect(Collectors.groupingBy(IntelligentConsigneeDTO::getCustomerId));
        customerList.forEach(customer -> customer.setConsignees(consigneeMap.getOrDefault(customer.getId(), Lists.newArrayList())));
        IntelligentCustomersDTO customersDTO = new IntelligentCustomersDTO();
        customersDTO.setCustomerList(customerList);
        IntelligentDTO dto = new IntelligentDTO();
        dto.setDispatchLimit(buildDispatchLimit(vo, dicData));
        dto.setDispatchRules(buildDispatchRule(vo, dicData));
        dto.setDispatchModes(buildIntelligentModeDTO(vo));
        dto.setBranchs(branchs);
        dto.setVehicle(vehicle);
        dto.setVehicleTypes(vehicleTypes);
        dto.setCustomers(customersDTO);
        dto.setPathDistances(pathDistances);
        dto.setShipbills(shipbills);
        return dto;
    }

    /**
     * buildDispatchLimit
     */
    private List<IntelligentLimitOrRuleDTO> buildDispatchLimit(IntelligentDispatchVO vo, List<SysRefdatadModel> dicData) {
        return vo.getIntelligentRestrictionList().stream().map(restriction -> {
            IntelligentLimitOrRuleDTO limit = new IntelligentLimitOrRuleDTO();
            limit.setId(restriction);
            limit.setCode(restriction);
            limit.setName(ConvertorUtil.getName(dicData, IntelligentRestrictionEnum.NAME, restriction));
            return limit;
        }).collect(Collectors.toList());
    }

    /**
     * buildDispatchRule
     */
    private List<IntelligentLimitOrRuleDTO> buildDispatchRule(IntelligentDispatchVO vo, List<SysRefdatadModel> dicData) {
        IntelligentLimitOrRuleDTO rule = new IntelligentLimitOrRuleDTO();
        rule.setId(vo.getIntelligentRule());
        rule.setCode(vo.getIntelligentRule());
        rule.setName(ConvertorUtil.getName(dicData, IntelligentRuleEnum.NAME, vo.getIntelligentRule()));
        return Lists.newArrayList(rule);
    }

    /**
     * buildIntelligentModeDTO
     */
    private IntelligentModeDTO buildIntelligentModeDTO(IntelligentDispatchVO vo) {
        IntelligentModeDTO intelligentModeDTO = new IntelligentModeDTO();
        intelligentModeDTO.setDispatchMode(vo.getIntelligentDispatchType());
        return intelligentModeDTO;
    }

    /**
     * buildIntelligentShipBillDTO
     */
    private IntelligentShipBillDTO buildIntelligentShipBillDTO(TmsShipbillHeaderModel shipBillHeader, List<TmsShipbillDetailModel> shipBillDetailList) {
        IntelligentShipBillDTO intelligentShipBillDTO = new IntelligentShipBillDTO();
        intelligentShipBillDTO.setShipbillId(shipBillHeader.getShipbillId());
        intelligentShipBillDTO.setShipbillNo(shipBillHeader.getShipbillNo());
        intelligentShipBillDTO.setBranchId(objToString(shipBillHeader.getBranchId()));
        intelligentShipBillDTO.setBranchCode(shipBillHeader.getBranchCode());
        intelligentShipBillDTO.setBranchName(shipBillHeader.getBranchName());
        intelligentShipBillDTO.setCargoOwnerId(objToString(shipBillHeader.getCargoOwnerId()));
        intelligentShipBillDTO.setCargoOwnerCode(shipBillHeader.getCargoOwnerCode());
        intelligentShipBillDTO.setCargoOwnerName(shipBillHeader.getCargoOwnerName());
        intelligentShipBillDTO.setOrderDate(objToString(shipBillHeader.getOrderDate()));
        intelligentShipBillDTO.setOrderType(objToString(shipBillHeader.getOrderType()));
        intelligentShipBillDTO.setOrderBusinessType(objToString(shipBillHeader.getOrderBusinessType()));
        intelligentShipBillDTO.setDeliveryMethodType(objToString(shipBillHeader.getDeliveryMethodType()));
        intelligentShipBillDTO.setTemperatureRequirement(objToString(shipBillHeader.getTemperatureRequirement()));
        intelligentShipBillDTO.setEstimatedPickupStartTime(objToString(shipBillHeader.getEstimatedPickupStartTime()));
        intelligentShipBillDTO.setEstimatedPickupEndTime(objToString(shipBillHeader.getEstimatedPickupEndTime()));
        intelligentShipBillDTO.setEstimatedDeliveryStartTime(objToString(shipBillHeader.getEstimatedDeliveryStartTime()));
        intelligentShipBillDTO.setEstimatedDeliveryEndTime(objToString(shipBillHeader.getEstimatedDeliveryEndTime()));
        intelligentShipBillDTO.setTransportAging(objToString(shipBillHeader.getTransportAging()));
        intelligentShipBillDTO.setShipperType(objToString(shipBillHeader.getShipperType()));
        intelligentShipBillDTO.setShipperUnitId(objToString(shipBillHeader.getShipperUnitId()));
        intelligentShipBillDTO.setShipperUnitCode(shipBillHeader.getShipperUnitCode());
        intelligentShipBillDTO.setShipperUnitName(shipBillHeader.getShipperUnitName());
        intelligentShipBillDTO.setShipperId(objToString(shipBillHeader.getShipperId()));
        intelligentShipBillDTO.setShipperCode(shipBillHeader.getShipperCode());
        intelligentShipBillDTO.setShipperName(shipBillHeader.getShipperName());
        intelligentShipBillDTO.setShipperProvince(shipBillHeader.getShipperProvince());
        intelligentShipBillDTO.setShipperProvinceName(shipBillHeader.getShipperProvinceName());
        intelligentShipBillDTO.setShipperCity(shipBillHeader.getShipperCity());
        intelligentShipBillDTO.setShipperCityName(shipBillHeader.getShipperCityName());
        intelligentShipBillDTO.setShipperDistrict(shipBillHeader.getShipperDistrict());
        intelligentShipBillDTO.setShipperDistrictName(shipBillHeader.getShipperDistrictName());
        intelligentShipBillDTO.setShipperAddress(shipBillHeader.getShipperAddress());
        intelligentShipBillDTO.setShipperLongitude(objToString(shipBillHeader.getShipperLongitude()));
        intelligentShipBillDTO.setShipperLatitude(objToString(shipBillHeader.getShipperLatitude()));
        intelligentShipBillDTO.setConsigneeType(objToString(shipBillHeader.getConsigneeType()));
        intelligentShipBillDTO.setConsigneeUnitId(objToString(shipBillHeader.getConsigneeUnitId()));
        intelligentShipBillDTO.setConsigneeUnitCode(shipBillHeader.getConsigneeUnitCode());
        intelligentShipBillDTO.setConsigneeUnitName(shipBillHeader.getConsigneeUnitName());
        intelligentShipBillDTO.setConsigneeId(objToString(shipBillHeader.getConsigneeId()));
        intelligentShipBillDTO.setConsigneeCode(shipBillHeader.getConsigneeCode());
        intelligentShipBillDTO.setConsigneeName(shipBillHeader.getConsigneeName());
        intelligentShipBillDTO.setConsigneeProvince(shipBillHeader.getConsigneeProvince());
        intelligentShipBillDTO.setConsigneeProvinceName(shipBillHeader.getConsigneeProvinceName());
        intelligentShipBillDTO.setConsigneeCity(shipBillHeader.getConsigneeCity());
        intelligentShipBillDTO.setConsigneeCityName(shipBillHeader.getConsigneeCityName());
        intelligentShipBillDTO.setConsigneeDistrict(shipBillHeader.getConsigneeDistrict());
        intelligentShipBillDTO.setConsigneeDistrictName(shipBillHeader.getConsigneeDistrictName());
        intelligentShipBillDTO.setConsigneeAddress(shipBillHeader.getConsigneeAddress());
        intelligentShipBillDTO.setConsigneeLongitude(objToString(shipBillHeader.getConsigneeLongitude()));
        intelligentShipBillDTO.setConsigneeLatitude(objToString(shipBillHeader.getConsigneeLatitude()));
        intelligentShipBillDTO.setLineId(objToString(shipBillHeader.getLineId()));
        intelligentShipBillDTO.setLineCode(shipBillHeader.getLineCode());
        intelligentShipBillDTO.setLineName(shipBillHeader.getLineName());
        intelligentShipBillDTO.setTotalPcs(objToString(shipBillHeader.getTotalPcs()));
        intelligentShipBillDTO.setTotalVolume(objToString(shipBillHeader.getTotalVolume()));
        intelligentShipBillDTO.setTotalWeight(objToString(shipBillHeader.getTotalWeight()));
        intelligentShipBillDTO.setPackageWholePiece(objToString(shipBillHeader.getPackageWholePiece()));
        intelligentShipBillDTO.setPackageLclPiece(objToString(shipBillHeader.getPackageLclPiece()));
        intelligentShipBillDTO.setPackageTotalVolume(objToString(shipBillHeader.getPackageTotalVolume()));
        intelligentShipBillDTO.setPackageTotalWeight(objToString(shipBillHeader.getPackageTotalWeight()));
        intelligentShipBillDTO.setCarrierType(objToString(shipBillHeader.getCarrierType()));
        intelligentShipBillDTO.setCarrierId(objToString(shipBillHeader.getCarrierId()));
        intelligentShipBillDTO.setCarrierCode(shipBillHeader.getCarrierCode());
        intelligentShipBillDTO.setCarrierName(shipBillHeader.getCarrierName());
        if (CollectionUtil.isNotEmpty(shipBillDetailList)) {
            intelligentShipBillDTO.setDetails(shipBillDetailList.stream().map(this::buildIntelligentShipBillDetailDTO).collect(Collectors.toList()));
        }
        return intelligentShipBillDTO;
    }

    /**
     * buildIntelligentShipBillDetailDTO
     */
    private IntelligentShipBillDetailDTO buildIntelligentShipBillDetailDTO(TmsShipbillDetailModel shipBillDetail) {
        IntelligentShipBillDetailDTO intelligentShipBillDetailDTO = new IntelligentShipBillDetailDTO();
        intelligentShipBillDetailDTO.setId(shipBillDetail.getId());
        intelligentShipBillDetailDTO.setShipbillId(shipBillDetail.getShipbillId());
        intelligentShipBillDetailDTO.setShipbillNo(shipBillDetail.getShipbillNo());
        intelligentShipBillDetailDTO.setSeqno(objToString(shipBillDetail.getSeqno()));
        intelligentShipBillDetailDTO.setItemId(objToString(shipBillDetail.getItemId()));
        intelligentShipBillDetailDTO.setItemCode(objToString(shipBillDetail.getItemCode()));
        intelligentShipBillDetailDTO.setItemName(objToString(shipBillDetail.getItemName()));
        intelligentShipBillDetailDTO.setItemCategoryId(objToString(shipBillDetail.getItemCategoryId()));
        intelligentShipBillDetailDTO.setItemCategoryCode(shipBillDetail.getItemCategoryCode());
        intelligentShipBillDetailDTO.setItemCategoryName(shipBillDetail.getItemCategoryName());
        intelligentShipBillDetailDTO.setPackType(objToString(shipBillDetail.getPackType()));
        intelligentShipBillDetailDTO.setCargoThermosphere(objToString(shipBillDetail.getCargoThermosphere()));
        intelligentShipBillDetailDTO.setPcs(objToString(shipBillDetail.getPcs()));
        intelligentShipBillDetailDTO.setVolume(objToString(shipBillDetail.getVolume()));
        intelligentShipBillDetailDTO.setWeight(objToString(shipBillDetail.getWeight()));
        intelligentShipBillDetailDTO.setUnitVolume(objToString(shipBillDetail.getUnitVolume()));
        intelligentShipBillDetailDTO.setUnitWeight(objToString(shipBillDetail.getUnitWeight()));
        return intelligentShipBillDetailDTO;
    }

    /**
     * buildPathDistance
     */
    private List<Map<String, String>> buildPathDistance(Map<String, Pair<BigDecimal, BigDecimal>> pathMap) {
        if (ObjectUtil.isEmpty(pathMap)) {
            return Lists.newArrayList();
        }
        List<Map<String, String>> pathDistances = Lists.newArrayList();
        Set<String> keys = pathMap.keySet();
        for (String key1 : keys) {
            for (String key2 : keys) {
                if (!key1.equals(key2)) {
                    IntelligentDispatchDistanceVO vo = new IntelligentDispatchDistanceVO();
                    vo.setOriginLongitude(pathMap.get(key1).getKey());
                    vo.setOriginLatitude(pathMap.get(key1).getValue());
                    vo.setDestinationLongitude(pathMap.get(key2).getKey());
                    vo.setDestinationLatitude(pathMap.get(key2).getValue());
                    vo.setStrategy(STRATEGY);
                    Pair<BigDecimal, BigDecimal> result = getDistanceAndDurationByAmap(vo);
                    String key = key1 + "-" + key2;
                    Map<String, String> pathDistance = new HashMap<String, String>(){{
                        put(key, objToString(NumberUtil.div(result.getKey(), 1000, 1, RoundingMode.HALF_UP)));
                    }};
                    pathDistances.add(pathDistance);
                }
            }
        }
        return pathDistances;
    }

    /**
     * buildIntelligentBranchDTOList
     */
    private List<IntelligentBranchDTO> buildIntelligentBranchDTOList(List<Long> idList) {
        if (ObjectUtil.isEmpty(idList)) {
            return Lists.newArrayList();
        }
        List<BranchDTO> dtoList = proxy.findBranchByIdList(idList);
        return dtoList.stream().map(this::buildIntelligentBranchDTO).collect(Collectors.toList());
    }

    /**
     * buildIntelligentBranchDTO
     */
    private IntelligentBranchDTO buildIntelligentBranchDTO(BranchDTO dto) {
        IntelligentBranchDTO intelligentBranchDTO = new IntelligentBranchDTO();
        intelligentBranchDTO.setId(objToString(dto.getId()));
        intelligentBranchDTO.setCode(dto.getCode());
        intelligentBranchDTO.setName(dto.getName());
        intelligentBranchDTO.setProvince(dto.getProvince());
        intelligentBranchDTO.setProvinceName(dto.getProvinceName());
        intelligentBranchDTO.setCity(dto.getCity());
        intelligentBranchDTO.setCityName(dto.getCityName());
        intelligentBranchDTO.setDistrict(dto.getDistrict());
        intelligentBranchDTO.setDistrictName(dto.getDistrictName());
        intelligentBranchDTO.setAddress(dto.getAddress());
        intelligentBranchDTO.setLongitude(objToString((dto.getLongitude())));
        intelligentBranchDTO.setLatitude(objToString((dto.getLatitude())));
        intelligentBranchDTO.setStartTime(dto.getStartTime());
        intelligentBranchDTO.setEndTime(dto.getEndTime());
        intelligentBranchDTO.setLoadingTime(objToString(dto.getLoadingTime()));
        return intelligentBranchDTO;
    }

    /**
     * buildIntelligentCustomerDTOList
     */
    private List<IntelligentCustomerDTO> buildIntelligentCustomerDTOList(List<Long> idList) {
        if (ObjectUtil.isEmpty(idList)) {
            return Lists.newArrayList();
        }
        List<CustomerDTO> dtoList = proxy.findCustomerByIdList(idList);
        return dtoList.stream().map(this::buildIntelligentCustomerDTO).collect(Collectors.toList());
    }

    /**
     * buildIntelligentCustomerDTO
     */
    private IntelligentCustomerDTO buildIntelligentCustomerDTO(CustomerDTO dto) {
        IntelligentCustomerDTO intelligentCustomerDTO = new IntelligentCustomerDTO();
        intelligentCustomerDTO.setId(objToString(dto.getId()));
        intelligentCustomerDTO.setCode(dto.getCode());
        intelligentCustomerDTO.setName(dto.getName());
        intelligentCustomerDTO.setProvince(dto.getProvince());
        intelligentCustomerDTO.setProvinceName(dto.getProvinceName());
        intelligentCustomerDTO.setCity(dto.getCity());
        intelligentCustomerDTO.setCityName(dto.getCityName());
        intelligentCustomerDTO.setDistrict(dto.getDistrict());
        intelligentCustomerDTO.setDistrictName(dto.getDistrictName());
        intelligentCustomerDTO.setAddress(dto.getAddress());
        intelligentCustomerDTO.setContact(dto.getContact());
        intelligentCustomerDTO.setPhoneNumber(dto.getPhoneNumber());
        intelligentCustomerDTO.setLandlineNumber(dto.getLandlineNumber());
        intelligentCustomerDTO.setEmail(dto.getEmail());
        intelligentCustomerDTO.setPostCode(dto.getPostCode());
        return intelligentCustomerDTO;
    }

    /**
     * buildIntelligentConsigneeDTOList
     */
    private List<IntelligentConsigneeDTO> buildIntelligentConsigneeDTOList(List<Long> idList) {
        if (ObjectUtil.isEmpty(idList)) {
            return Lists.newArrayList();
        }
        List<ConsigneeDTO> dtoList = proxy.findConsigneeByIdList(idList);
        return dtoList.stream().map(this::buildIntelligentConsigneeDTO).collect(Collectors.toList());
    }

    /**
     * buildIntelligentConsigneeDTO
     */
    private IntelligentConsigneeDTO buildIntelligentConsigneeDTO(ConsigneeDTO dto) {
        IntelligentConsigneeDTO intelligentConsigneeDTO = new IntelligentConsigneeDTO();
        intelligentConsigneeDTO.setId(objToString(dto.getId()));
        intelligentConsigneeDTO.setCode(dto.getCode());
        intelligentConsigneeDTO.setName(dto.getName());
        intelligentConsigneeDTO.setCustomerId(objToString(dto.getCustomerId()));
        intelligentConsigneeDTO.setCustomerCode(dto.getCustomerCode());
        intelligentConsigneeDTO.setCustomerName(dto.getCustomerName());
        intelligentConsigneeDTO.setContact(dto.getContact());
        intelligentConsigneeDTO.setPhoneNumber(dto.getPhoneNumber());
        intelligentConsigneeDTO.setLandlineNumber(dto.getLandlineNumber());
        intelligentConsigneeDTO.setEmail(dto.getEmail());
        intelligentConsigneeDTO.setRelationPhoneNumber(dto.getRelationPhoneNumber());
        intelligentConsigneeDTO.setProvince(dto.getProvince());
        intelligentConsigneeDTO.setProvinceName(dto.getProvinceName());
        intelligentConsigneeDTO.setCity(dto.getCity());
        intelligentConsigneeDTO.setCityName(dto.getCityName());
        intelligentConsigneeDTO.setDistrict(dto.getDistrict());
        intelligentConsigneeDTO.setDistrictName(dto.getDistrictName());
        intelligentConsigneeDTO.setAddress(dto.getAddress());
        intelligentConsigneeDTO.setLongitude(objToString(dto.getLongitude()));
        intelligentConsigneeDTO.setLatitude(objToString(dto.getLatitude()));
        intelligentConsigneeDTO.setStartTime(dto.getStartTime());
        intelligentConsigneeDTO.setEndTime(dto.getEndTime());
        intelligentConsigneeDTO.setLoadingTime(objToString(NumberUtil.toBigDecimal(dto.getLoadingTime())));
        return intelligentConsigneeDTO;
    }

    /**
     * buildIntelligentVehicleDTOList
     */
    private List<IntelligentVehicleDTO> buildIntelligentVehicleDTOList(List<Long> idList) {
        if (ObjectUtil.isEmpty(idList)) {
            return Lists.newArrayList();
        }
        List<VehicleDTO> dtoList = proxy.findVehicleByIdList(idList);
        return dtoList.stream().map(this::buildIntelligentVehicleDTO).collect(Collectors.toList());
    }

    /**
     * buildIntelligentVehicleDTO
     */
    private IntelligentVehicleDTO buildIntelligentVehicleDTO(VehicleDTO dto) {
        IntelligentVehicleDTO intelligentVehicleDTO = new IntelligentVehicleDTO();
        intelligentVehicleDTO.setId(objToString(dto.getId()));
        intelligentVehicleDTO.setCode(dto.getCode());
        intelligentVehicleDTO.setPlateNumber(dto.getPlateNumber());
        intelligentVehicleDTO.setType(objToString(dto.getType()));
        intelligentVehicleDTO.setAffiliationId(objToString(dto.getAffiliationId()));
        intelligentVehicleDTO.setAffiliationCode(dto.getAffiliationCode());
        intelligentVehicleDTO.setAffiliationName(dto.getAffiliationName());
        intelligentVehicleDTO.setVehicleTypeId(objToString(dto.getVehicleTypeId()));
        intelligentVehicleDTO.setVehicleTypeCode(dto.getVehicleTypeCode());
        intelligentVehicleDTO.setVehicleTypeName(dto.getVehicleTypeName());
        intelligentVehicleDTO.setMemo(dto.getMemo());
        return intelligentVehicleDTO;
    }

    /**
     * buildIntelligentVehicleDTOList
     */
    private List<IntelligentVehicleTypeDTO> buildIntelligentVehicleTypeDTOList(List<IntelligentDispatchVehicleTypeVO> vehicleTypeVOList) {
        if (ObjectUtil.isEmpty(vehicleTypeVOList)) {
            return Lists.newArrayList();
        }
        List<VehicleTypeDTO> dtoList = proxy.findVehicleTypeByIdList(vehicleTypeVOList.stream().map(IntelligentDispatchVehicleTypeVO::getId).collect(Collectors.toList()));
        Map<Long, Integer> vehicleTypeNumberMap = vehicleTypeVOList.stream().collect(Collectors.toMap(IntelligentDispatchVehicleTypeVO::getId, IntelligentDispatchVehicleTypeVO::getVehicleNum, (t1, t2) -> t2));
        return dtoList.stream().map(dto -> buildIntelligentVehicleTypeDTO(dto, vehicleTypeNumberMap)).collect(Collectors.toList());
    }

    /**
     * buildIntelligentVehicleTypeDTO
     */
    private IntelligentVehicleTypeDTO buildIntelligentVehicleTypeDTO(VehicleTypeDTO dto, Map<Long, Integer> vehicleTypeNumberMap) {
        IntelligentVehicleTypeDTO intelligentVehicleTypeDTO = new IntelligentVehicleTypeDTO();
        intelligentVehicleTypeDTO.setId(objToString(dto.getId()));
        intelligentVehicleTypeDTO.setCode(dto.getCode());
        intelligentVehicleTypeDTO.setName(dto.getName());
        intelligentVehicleTypeDTO.setType(objToString(dto.getType()));
        intelligentVehicleTypeDTO.setLength(objToString(dto.getLength()));
        intelligentVehicleTypeDTO.setWidth(objToString(dto.getWidth()));
        intelligentVehicleTypeDTO.setHeight(objToString(dto.getHeight()));
        intelligentVehicleTypeDTO.setMaxMileage(objToString(dto.getMaxMileage()));
        intelligentVehicleTypeDTO.setMaxOrders(objToString(dto.getMaxOrders()));
        intelligentVehicleTypeDTO.setWeight(objToString(dto.getWeight()));
        intelligentVehicleTypeDTO.setVolume(objToString(dto.getVolume()));
        intelligentVehicleTypeDTO.setMaxLoadingVolume(objToString(dto.getMaxLoadingVolume()));
        intelligentVehicleTypeDTO.setMaxLoadingWeight(objToString(dto.getMaxLoadingWeight()));
        intelligentVehicleTypeDTO.setMaxLoadingPcs(objToString(dto.getMaxLoadingPcs()));
        intelligentVehicleTypeDTO.setMaxLoadingRatio(objToString(NumberUtil.div(dto.getMaxLoadingRatio(), 100, 2, RoundingMode.HALF_UP)));
        intelligentVehicleTypeDTO.setMinLoadingRatio(objToString(NumberUtil.div(dto.getMinLoadingRatio(), 100, 2, RoundingMode.HALF_UP)));
        intelligentVehicleTypeDTO.setSites(objToString(dto.getSites()));
        intelligentVehicleTypeDTO.setUsableCount(objToString(NumberUtil.toBigDecimal(vehicleTypeNumberMap.get(dto.getId()))));
        return intelligentVehicleTypeDTO;
    }

    /**
     * remoteDispatch
     */
    private String remoteDispatch(String requestUrl, String requestJson) {
        log.info("remoteDispatch requestUrl={}", requestUrl);
        log.info("remoteDispatch requestJson={}", requestJson);
        String responseJson = HttpUtil.post(requestUrl, requestJson, TIME_OUT);
        log.info("remoteDispatch responseJson={}", responseJson);
        return responseJson;
    }

    /**
     * objToString
     */
    private String objToString(Object obj) {
        if (ObjectUtil.isEmpty(obj)) {
            return null;
        }
        if (obj instanceof BigDecimal) {
            return ((BigDecimal)obj).stripTrailingZeros().toPlainString();
        }
        if (obj instanceof Date) {
            return DateUtil.formatDateTime((Date)obj);
        }
        return obj.toString();
    }

}
