|
|
@@ -0,0 +1,885 @@
|
|
|
+package com.sckw.transport.service;
|
|
|
+
|
|
|
+import com.alibaba.fastjson2.JSON;
|
|
|
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
|
+import com.sckw.core.web.constant.HttpStatus;
|
|
|
+import com.sckw.core.web.response.HttpResult;
|
|
|
+import com.sckw.fleet.api.feign.FleetTruckFeignService;
|
|
|
+import com.sckw.order.api.feign.TradeOrderApi;
|
|
|
+import com.sckw.order.api.model.OrderPara;
|
|
|
+import com.sckw.order.api.model.TradeOrderVo;
|
|
|
+import com.sckw.transport.model.KwtLogisticsOrder;
|
|
|
+import com.sckw.transport.model.KwtLogisticsOrderAddress;
|
|
|
+import com.sckw.transport.model.KwtLogisticsOrderGoods;
|
|
|
+import com.sckw.transport.model.KwtWaybillOrder;
|
|
|
+import com.sckw.transport.model.KwtWaybillOrderSubtask;
|
|
|
+import com.sckw.transport.model.KwtWaybillOrderTicket;
|
|
|
+import com.sckw.transport.model.param.TradeOrderTransportQueryReq;
|
|
|
+import com.sckw.transport.model.param.WaybillTransportQueryReq;
|
|
|
+import com.sckw.transport.model.vo.TradeOrderTransportInfoResp;
|
|
|
+import com.sckw.transport.repository.KwtLogisticsOrderAddressRepository;
|
|
|
+import com.sckw.transport.repository.KwtLogisticsOrderGoodsRepository;
|
|
|
+import com.sckw.transport.repository.KwtLogisticsOrderRepository;
|
|
|
+import com.sckw.transport.repository.KwtWaybillOrderRepository;
|
|
|
+import com.sckw.transport.repository.KwtWaybillOrderSubtaskRepository;
|
|
|
+import com.sckw.transport.repository.KwtWaybillOrderTicketRepository;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.commons.collections4.CollectionUtils;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.Comparator;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Objects;
|
|
|
+import java.util.Optional;
|
|
|
+import java.util.function.Function;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 贸易订单运输信息查询服务。
|
|
|
+ *
|
|
|
+ * @author system
|
|
|
+ * @date 2026-05-08
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class TradeOrderTransportInfoService {
|
|
|
+
|
|
|
+ private static final int LOAD_TICKET_TYPE = 1;
|
|
|
+ private static final int UNLOAD_ADDRESS_TYPE = 2;
|
|
|
+ private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
|
|
|
+
|
|
|
+ private final TradeOrderApi tradeOrderApi;
|
|
|
+ private final FleetTruckFeignService fleetTruckFeignService;
|
|
|
+ private final KwtLogisticsOrderRepository logisticsOrderRepository;
|
|
|
+ private final KwtWaybillOrderRepository waybillOrderRepository;
|
|
|
+ private final KwtWaybillOrderSubtaskRepository waybillOrderSubtaskRepository;
|
|
|
+ private final KwtWaybillOrderTicketRepository waybillOrderTicketRepository;
|
|
|
+ private final KwtLogisticsOrderGoodsRepository logisticsOrderGoodsRepository;
|
|
|
+ private final KwtLogisticsOrderAddressRepository logisticsOrderAddressRepository;
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据贸易订单号查询运输相关信息。
|
|
|
+ * <p>
|
|
|
+ * 该方法通过贸易订单号串联查询物流订单、运单及关联的运输任务详情,组装成完整的运输信息响应对象。
|
|
|
+ * 主要流程:
|
|
|
+ * 1. 校验入参及贸易订单号有效性。
|
|
|
+ * 2. 查询贸易订单基础信息(用于补充买卖双方企业名称等)。
|
|
|
+ * 3. 根据贸易订单号查询关联的物流订单列表,若不存在则抛出异常。
|
|
|
+ * 4. 提取物流订单ID,批量查询关联的运单(运输任务)列表,若不存在则抛出异常。
|
|
|
+ * 5. 组装并返回包含任务、货物、车辆、司机等信息的响应对象。
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @param req 查询参数,包含贸易订单号
|
|
|
+ * @return 贸易订单运输信息响应对象
|
|
|
+ * @throws IllegalArgumentException 当参数为空、贸易订单号为空、未查询到物流订单或未查询到运单时抛出
|
|
|
+ */
|
|
|
+ public TradeOrderTransportInfoResp queryByTradeOrderNo(TradeOrderTransportQueryReq req) {
|
|
|
+ // 1. 参数校验
|
|
|
+ if (Objects.isNull(req)) {
|
|
|
+ log.warn("查询贸易订单运输信息失败:查询参数为空");
|
|
|
+ throw new IllegalArgumentException("查询参数不能为空");
|
|
|
+ }
|
|
|
+ String tradeOrderNo = StringUtils.trimToEmpty(req.getTradeOrderNo());
|
|
|
+ if (StringUtils.isBlank(tradeOrderNo)) {
|
|
|
+ log.warn("查询贸易订单运输信息失败:贸易订单号为空");
|
|
|
+ throw new IllegalArgumentException("贸易订单号不能为空");
|
|
|
+ }
|
|
|
+ log.info("查询贸易订单运输信息开始,tradeOrderNo:{}", tradeOrderNo);
|
|
|
+
|
|
|
+ // 2. 查询贸易订单基础信息
|
|
|
+ TradeOrderVo tradeOrder = queryTradeOrder(tradeOrderNo);
|
|
|
+
|
|
|
+ // 3. 查询关联的物流订单列表
|
|
|
+ List<KwtLogisticsOrder> logisticsOrders = queryLogisticsOrders(tradeOrderNo, tradeOrder);
|
|
|
+ if (CollectionUtils.isEmpty(logisticsOrders)) {
|
|
|
+ log.warn("查询贸易订单运输信息失败:未查询到关联的物流订单,tradeOrderNo:{}", tradeOrderNo);
|
|
|
+ throw new IllegalArgumentException("未查询到贸易订单关联的物流订单");
|
|
|
+ }
|
|
|
+ log.debug("查询到关联物流订单数量:{}", logisticsOrders.size());
|
|
|
+
|
|
|
+ // 4. 提取物流订单ID并查询关联的运单(运输任务)列表
|
|
|
+ List<Long> logisticsOrderIds = logisticsOrders.stream()
|
|
|
+ .map(KwtLogisticsOrder::getId)
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .distinct()
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ List<KwtWaybillOrder> waybillOrders = queryWaybillOrders(logisticsOrderIds);
|
|
|
+ if (CollectionUtils.isEmpty(waybillOrders)) {
|
|
|
+ log.warn("查询贸易订单运输信息失败:未查询到关联的运输任务,tradeOrderNo:{}, logisticsOrderIds:{}",
|
|
|
+ tradeOrderNo, logisticsOrderIds);
|
|
|
+ throw new IllegalArgumentException("未查询到贸易订单关联的运输任务");
|
|
|
+ }
|
|
|
+ log.debug("查询到关联运单数量:{}", waybillOrders.size());
|
|
|
+
|
|
|
+ // 5. 组装响应数据
|
|
|
+ TradeOrderTransportInfoResp resp = buildTransportInfo(logisticsOrders, waybillOrders, tradeOrder);
|
|
|
+ resp.setTradeOrderNo(tradeOrderNo);
|
|
|
+
|
|
|
+ log.info("查询贸易订单运输信息完成,tradeOrderNo:{}, taskSize:{}", tradeOrderNo,
|
|
|
+ Objects.nonNull(resp.getTasks()) ? resp.getTasks().size() : 0);
|
|
|
+ return resp;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询贸易订单基础信息。
|
|
|
+ *
|
|
|
+ * @param tradeOrderNo 贸易订单号
|
|
|
+ * @return 贸易订单基础信息
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * 根据运单号查询运输相关信息。
|
|
|
+ *
|
|
|
+ * @param req 查询参数
|
|
|
+ * @return 运单运输信息
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * 根据运单号查询运输相关信息。
|
|
|
+ * <p>
|
|
|
+ * 该方法通过运单号反向追溯关联的物流订单及贸易订单,组装完整的运输链路信息。
|
|
|
+ * 主要流程:
|
|
|
+ * 1. 校验入参及运单号有效性。
|
|
|
+ * 2. 查询运单基础信息,校验是否存在及是否关联物流订单。
|
|
|
+ * 3. 根据运单关联的物流订单ID,查询物流订单详情。
|
|
|
+ * 4. 若物流订单中存在贸易订单号,则进一步查询贸易订单详细信息(如买卖双方)。
|
|
|
+ * 5. 组装并返回包含任务、货物、车辆、司机等信息的响应对象。
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @param req 查询参数,包含运单号
|
|
|
+ * @return 贸易订单运输信息响应对象(复用同一响应结构)
|
|
|
+ * @throws IllegalArgumentException 当参数为空、运单不存在、未关联物流订单或物流订单不存在时抛出
|
|
|
+ */
|
|
|
+ public TradeOrderTransportInfoResp queryByWaybillNo(WaybillTransportQueryReq req) {
|
|
|
+ log.info("根据运单号查询运输信息开始,请求参数:{}", JSON.toJSONString( req));
|
|
|
+ // 1. 参数校验
|
|
|
+ if (Objects.isNull(req)) {
|
|
|
+ log.warn("根据运单号查询运输信息失败:查询参数为空");
|
|
|
+ throw new IllegalArgumentException("查询参数不能为空");
|
|
|
+ }
|
|
|
+ String waybillNo = StringUtils.trimToEmpty(req.getWaybillNo());
|
|
|
+ if (StringUtils.isBlank(waybillNo)) {
|
|
|
+ log.warn("根据运单号查询运输信息失败:运单号为空");
|
|
|
+ throw new IllegalArgumentException("运单号不能为空");
|
|
|
+ }
|
|
|
+ log.info("根据运单号查询运输信息开始,waybillNo:{}", waybillNo);
|
|
|
+
|
|
|
+ // 2. 查询运单信息
|
|
|
+ KwtWaybillOrder waybillOrder = waybillOrderRepository.queryByWayOrderNo(waybillNo);
|
|
|
+ if (Objects.isNull(waybillOrder)) {
|
|
|
+ log.warn("根据运单号查询运输信息失败:未查询到运单信息,waybillNo:{}", waybillNo);
|
|
|
+ throw new IllegalArgumentException("未查询到运单信息");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 校验运单是否关联物流订单
|
|
|
+ if (Objects.isNull(waybillOrder.getLOrderId())) {
|
|
|
+ log.warn("根据运单号查询运输信息失败:运单未关联物流订单,waybillNo:{}, waybillId:{}", waybillNo, waybillOrder.getId());
|
|
|
+ throw new IllegalArgumentException("运单未关联物流订单");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 查询关联的物流订单
|
|
|
+ KwtLogisticsOrder logisticsOrder = logisticsOrderRepository.queryByLogisticsOrderId(waybillOrder.getLOrderId());
|
|
|
+ if (Objects.isNull(logisticsOrder)) {
|
|
|
+ log.error("根据运单号查询运输信息失败:未查询到运单关联的物流订单,waybillNo:{}, logisticsOrderId:{}",
|
|
|
+ waybillNo, waybillOrder.getLOrderId());
|
|
|
+ throw new IllegalArgumentException("未查询到运单关联的物流订单");
|
|
|
+ }
|
|
|
+ log.debug("查询到关联物流订单,logisticsOrderId:{}, tradeOrderNo:{}", logisticsOrder.getId(), logisticsOrder.getTOrderNo());
|
|
|
+
|
|
|
+ // 5. 查询贸易订单信息(可选,用于补充买卖双方企业名称等)
|
|
|
+ TradeOrderVo tradeOrder = null;
|
|
|
+ if (StringUtils.isNotBlank(logisticsOrder.getTOrderNo())) {
|
|
|
+ try {
|
|
|
+ tradeOrder = queryTradeOrder(logisticsOrder.getTOrderNo());
|
|
|
+ if (Objects.nonNull(tradeOrder)) {
|
|
|
+ log.debug("查询到关联贸易订单,tradeOrderNo:{}", tradeOrder.getTOrderNo());
|
|
|
+ } else {
|
|
|
+ log.warn("物流订单关联了贸易订单号但未查询到详细信息,tradeOrderNo:{}", logisticsOrder.getTOrderNo());
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("查询贸易订单异常,但不影响主流程,tradeOrderNo:{}", logisticsOrder.getTOrderNo(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 组装响应数据
|
|
|
+ TradeOrderTransportInfoResp resp = buildTransportInfo(
|
|
|
+ Collections.singletonList(logisticsOrder),
|
|
|
+ Collections.singletonList(waybillOrder),
|
|
|
+ tradeOrder
|
|
|
+ );
|
|
|
+
|
|
|
+ log.info("根据运单号查询运输信息完成,waybillNo:{}, taskSize:{}", waybillNo,
|
|
|
+ Objects.nonNull(resp.getTasks()) ? resp.getTasks().size() : 0);
|
|
|
+ return resp;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询贸易订单详细信息。
|
|
|
+ * <p>
|
|
|
+ * 通过 Feign 调用订单服务接口,根据贸易订单号获取对应的贸易订单数据。
|
|
|
+ * 若返回多个订单,优先匹配订单号完全一致的记录;若无完全匹配,则返回列表中的第一个订单作为兜底。
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @param tradeOrderNo 贸易订单号
|
|
|
+ * @return 贸易订单视图对象,若未查询到或发生异常则返回 null
|
|
|
+ */
|
|
|
+ private TradeOrderVo queryTradeOrder(String tradeOrderNo) {
|
|
|
+ log.debug("开始查询贸易订单详情,tradeOrderNo:{}", tradeOrderNo);
|
|
|
+ try {
|
|
|
+ // 构建查询参数
|
|
|
+ OrderPara para = new OrderPara();
|
|
|
+ para.setOrderNo(tradeOrderNo);
|
|
|
+
|
|
|
+ // 调用远程订单服务接口
|
|
|
+ List<TradeOrderVo> tradeOrders = tradeOrderApi.trade(para);
|
|
|
+
|
|
|
+ if (CollectionUtils.isEmpty(tradeOrders)) {
|
|
|
+ log.warn("远程服务返回的贸易订单列表为空,tradeOrderNo:{}", tradeOrderNo);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 优先精确匹配订单号
|
|
|
+ Optional<TradeOrderVo> exactMatch = tradeOrders.stream()
|
|
|
+ .filter(item -> tradeOrderNo.equals(item.getTOrderNo()))
|
|
|
+ .findFirst();
|
|
|
+
|
|
|
+ if (exactMatch.isPresent()) {
|
|
|
+ log.debug("精确匹配到贸易订单,tradeOrderNo:{}", tradeOrderNo);
|
|
|
+ return exactMatch.get();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 若无精确匹配,取列表第一个作为兜底(兼容历史数据或特殊场景)
|
|
|
+ TradeOrderVo fallbackOrder = tradeOrders.get(0);
|
|
|
+ log.warn("未精确匹配到贸易订单号,使用返回列表中的第一个订单作为兜底,requestNo:{}, actualOrderNo:{}",
|
|
|
+ tradeOrderNo, fallbackOrder.getTOrderNo());
|
|
|
+ return fallbackOrder;
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("Feign 调用订单服务查询贸易订单异常,tradeOrderNo:{}, errorMessage:{}",
|
|
|
+ tradeOrderNo, e.getMessage(), e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询物流订单,优先使用本地贸易订单号精确匹配。
|
|
|
+ *
|
|
|
+ * @param tradeOrderNo 贸易订单号
|
|
|
+ * @param tradeOrder 远程贸易订单
|
|
|
+ * @return 物流订单列表
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * 查询物流订单列表。
|
|
|
+ * <p>
|
|
|
+ * 查询策略采用“优先单号匹配,降级ID匹配”的方式:
|
|
|
+ * 1. 首先尝试通过贸易订单号(tradeOrderNo)在本地数据库查询物流订单。
|
|
|
+ * 2. 如果查询结果为空,且提供了有效的贸易订单对象及其ID(tOrderId),则降级使用贸易订单ID进行查询。
|
|
|
+ * 3. 这种设计兼容了不同数据来源或历史数据中订单号不一致但ID关联的场景。
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @param tradeOrderNo 贸易订单号
|
|
|
+ * @param tradeOrder 贸易订单详细信息,用于获取贸易订单ID作为兜底查询条件
|
|
|
+ * @return 物流订单列表,若未查询到则返回空列表
|
|
|
+ */
|
|
|
+ private List<KwtLogisticsOrder> queryLogisticsOrders(String tradeOrderNo, TradeOrderVo tradeOrder) {
|
|
|
+ log.debug("开始查询物流订单,tradeOrderNo:{}", tradeOrderNo);
|
|
|
+
|
|
|
+ // 第一步:优先通过贸易订单号查询
|
|
|
+ List<KwtLogisticsOrder> logisticsOrders = logisticsOrderRepository.queryByTradeOrderNo(tradeOrderNo);
|
|
|
+
|
|
|
+ // 如果查询到了结果,或者无法进行降级查询(贸易订单为空或缺少ID),则直接返回当前结果
|
|
|
+ if (CollectionUtils.isNotEmpty(logisticsOrders)) {
|
|
|
+ log.debug("通过贸易订单号查询到物流订单,count:{}", logisticsOrders.size());
|
|
|
+ return logisticsOrders;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Objects.isNull(tradeOrder) || Objects.isNull(tradeOrder.getTOrderId())) {
|
|
|
+ log.warn("通过贸易订单号未查询到物流订单,且无法通过贸易订单ID降级查询(tradeOrder为空或tOrderId为空)");
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 第二步:降级通过贸易订单ID查询
|
|
|
+ log.info("通过贸易订单号未查询到物流订单,尝试通过贸易订单ID降级查询,tOrderId:{}", tradeOrder.getTOrderId());
|
|
|
+ List<KwtLogisticsOrder> fallbackOrders = logisticsOrderRepository.queryByTradeOrderId(tradeOrder.getTOrderId());
|
|
|
+
|
|
|
+ if (CollectionUtils.isNotEmpty(fallbackOrders)) {
|
|
|
+ log.info("通过贸易订单ID成功查询到物流订单,count:{}", fallbackOrders.size());
|
|
|
+ } else {
|
|
|
+ log.warn("通过贸易订单ID也未查询到物流订单,tOrderId:{}", tradeOrder.getTOrderId());
|
|
|
+ }
|
|
|
+
|
|
|
+ return fallbackOrders;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询运单列表。
|
|
|
+ *
|
|
|
+ * @param logisticsOrderIds 物流订单ID列表
|
|
|
+ * @return 运单列表
|
|
|
+ */
|
|
|
+ private List<KwtWaybillOrder> queryWaybillOrders(List<Long> logisticsOrderIds) {
|
|
|
+ if (CollectionUtils.isEmpty(logisticsOrderIds)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ return waybillOrderRepository.list(Wrappers.<KwtWaybillOrder>lambdaQuery()
|
|
|
+ .eq(KwtWaybillOrder::getDelFlag, 0)
|
|
|
+ .in(KwtWaybillOrder::getLOrderId, logisticsOrderIds)
|
|
|
+ .orderByDesc(KwtWaybillOrder::getTaskEndTime)
|
|
|
+ .orderByDesc(KwtWaybillOrder::getUpdateTime)
|
|
|
+ .orderByDesc(KwtWaybillOrder::getId));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 填充贸易订单企业信息。
|
|
|
+ *
|
|
|
+ * @param resp 响应对象
|
|
|
+ * @param tradeOrder 远程贸易订单
|
|
|
+ * @param logisticsOrder 本地物流订单
|
|
|
+ */
|
|
|
+ private void fillTradeOrderInfo(TradeOrderTransportInfoResp resp, TradeOrderVo tradeOrder, KwtLogisticsOrder logisticsOrder) {
|
|
|
+ resp.setSupplierName(Objects.nonNull(tradeOrder) ? tradeOrder.getSellEntName() : null);
|
|
|
+ resp.setCustomerName(Objects.nonNull(tradeOrder) ? tradeOrder.getBuyEntName() : null);
|
|
|
+ if (StringUtils.isBlank(resp.getTradeOrderNo()) && Objects.nonNull(logisticsOrder)) {
|
|
|
+ resp.setTradeOrderNo(logisticsOrder.getTOrderNo());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组装任务信息列表。
|
|
|
+ *
|
|
|
+ * @param logisticsOrders 物流订单列表
|
|
|
+ * @param waybillOrders 运单列表
|
|
|
+ * @return 任务信息列表
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * 组装运输信息响应。
|
|
|
+ *
|
|
|
+ * @param logisticsOrders 物流订单列表
|
|
|
+ * @param waybillOrders 运单列表
|
|
|
+ * @param tradeOrder 贸易订单信息
|
|
|
+ * @return 运输信息响应
|
|
|
+ */
|
|
|
+ private TradeOrderTransportInfoResp buildTransportInfo(List<KwtLogisticsOrder> logisticsOrders,
|
|
|
+ List<KwtWaybillOrder> waybillOrders,
|
|
|
+ TradeOrderVo tradeOrder) {
|
|
|
+ TradeOrderTransportInfoResp resp = new TradeOrderTransportInfoResp();
|
|
|
+ KwtLogisticsOrder firstLogisticsOrder = CollectionUtils.isEmpty(logisticsOrders) ? null : logisticsOrders.get(0);
|
|
|
+ fillTradeOrderInfo(resp, tradeOrder, firstLogisticsOrder);
|
|
|
+ resp.setTasks(buildTaskInfoList(logisticsOrders, waybillOrders));
|
|
|
+ return resp;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组装运输任务信息列表。
|
|
|
+ * <p>
|
|
|
+ * 该方法负责将物流订单和运单数据转换为前端展示的任务信息列表。
|
|
|
+ * 主要步骤包括:
|
|
|
+ * 1. 构建物流订单映射,以便快速通过ID获取订单详情。
|
|
|
+ * 2. 提取有效的物流订单ID和运单ID列表。
|
|
|
+ * 3. 批量查询并构建子任务、装货单、货物信息、卸货地址的映射关系,优化查询性能。
|
|
|
+ * 4. 遍历运单列表,结合上述映射数据组装每个任务的详细信息。
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @param logisticsOrders 物流订单列表
|
|
|
+ * @param waybillOrders 运单列表
|
|
|
+ * @return 运输任务信息列表
|
|
|
+ */
|
|
|
+ private List<TradeOrderTransportInfoResp.TaskInfo> buildTaskInfoList(List<KwtLogisticsOrder> logisticsOrders,
|
|
|
+ List<KwtWaybillOrder> waybillOrders) {
|
|
|
+ log.debug("开始组装运输任务信息列表,logisticsOrderCount:{}, waybillOrderCount:{}",
|
|
|
+ CollectionUtils.isEmpty(logisticsOrders) ? 0 : logisticsOrders.size(),
|
|
|
+ CollectionUtils.isEmpty(waybillOrders) ? 0 : waybillOrders.size());
|
|
|
+
|
|
|
+ // 1. 构建物流订单ID到对象的映射,处理可能的重复ID(保留第一个)
|
|
|
+ Map<Long, KwtLogisticsOrder> logisticsOrderMap = logisticsOrders.stream()
|
|
|
+ .filter(item -> Objects.nonNull(item.getId()))
|
|
|
+ .collect(Collectors.toMap(KwtLogisticsOrder::getId, Function.identity(), (a, b) -> a));
|
|
|
+
|
|
|
+ // 2. 提取有效的ID列表用于后续批量查询
|
|
|
+ List<Long> logisticsOrderIds = logisticsOrders.stream()
|
|
|
+ .map(KwtLogisticsOrder::getId)
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .toList();
|
|
|
+ List<Long> waybillOrderIds = waybillOrders.stream()
|
|
|
+ .map(KwtWaybillOrder::getId)
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .toList();
|
|
|
+
|
|
|
+ if (CollectionUtils.isEmpty(waybillOrderIds)) {
|
|
|
+ log.warn("运单ID列表为空,无法组装任务信息");
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 批量查询关联数据并构建映射,以优化数据库交互次数
|
|
|
+
|
|
|
+ // 查询运单子任务,若有多个子任务则选择最新更新的记录
|
|
|
+ Map<Long, KwtWaybillOrderSubtask> subtaskMap = waybillOrderSubtaskRepository.queryByWOrderIds(waybillOrderIds).stream()
|
|
|
+ .collect(Collectors.toMap(KwtWaybillOrderSubtask::getWOrderId, Function.identity(), this::chooseLatestSubtask));
|
|
|
+ log.debug("查询到运单子任务数量:{}", subtaskMap.size());
|
|
|
+
|
|
|
+ // 查询装货单(类型为LOAD_TICKET_TYPE),若有多个则选择最新更新的记录
|
|
|
+ Map<Long, KwtWaybillOrderTicket> loadTicketMap = waybillOrderTicketRepository.queryByWOrderIdsAndType(waybillOrderIds, LOAD_TICKET_TYPE).stream()
|
|
|
+ .collect(Collectors.toMap(KwtWaybillOrderTicket::getWOrderId, Function.identity(), this::chooseLatestTicket));
|
|
|
+ log.debug("查询到装货单数量:{}", loadTicketMap.size());
|
|
|
+
|
|
|
+ // 查询物流订单货物信息
|
|
|
+ Map<Long, KwtLogisticsOrderGoods> goodsMap = logisticsOrderGoodsRepository.queryByLogOrderIds(logisticsOrderIds).stream()
|
|
|
+ .collect(Collectors.toMap(KwtLogisticsOrderGoods::getLOrderId, Function.identity(), (a, b) -> a));
|
|
|
+ log.debug("查询到货物信息数量:{}", goodsMap.size());
|
|
|
+
|
|
|
+ // 查询卸货地址(类型为UNLOAD_ADDRESS_TYPE)
|
|
|
+ Map<Long, KwtLogisticsOrderAddress> unloadAddressMap = logisticsOrderAddressRepository.queryByLogOrderIds(logisticsOrderIds).stream()
|
|
|
+ .filter(item -> Objects.equals(UNLOAD_ADDRESS_TYPE, item.getAddressType()))
|
|
|
+ .collect(Collectors.toMap(KwtLogisticsOrderAddress::getLOrderId, Function.identity(), (a, b) -> a));
|
|
|
+ log.debug("查询到卸货地址数量:{}", unloadAddressMap.size());
|
|
|
+
|
|
|
+ // 用于缓存车辆轴数信息,避免重复Feign调用
|
|
|
+ Map<String, String> truckAxleCache = new LinkedHashMap<>();
|
|
|
+
|
|
|
+ // 4. 遍历运单,组装每个任务的详细信息
|
|
|
+ List<TradeOrderTransportInfoResp.TaskInfo> taskInfoList = waybillOrders.stream()
|
|
|
+ .map(waybillOrder -> buildTaskInfo(waybillOrder,
|
|
|
+ logisticsOrderMap.get(waybillOrder.getLOrderId()),
|
|
|
+ subtaskMap.get(waybillOrder.getId()),
|
|
|
+ loadTicketMap.get(waybillOrder.getId()),
|
|
|
+ goodsMap.get(waybillOrder.getLOrderId()),
|
|
|
+ unloadAddressMap.get(waybillOrder.getLOrderId()),
|
|
|
+ truckAxleCache))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ log.info("运输任务信息列表组装完成,最终任务数量:{}", taskInfoList.size());
|
|
|
+ return taskInfoList;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组装单车任务信息。
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * 组装单车任务详细信息。
|
|
|
+ * <p>
|
|
|
+ * 该方法将运单、物流订单、子任务、装货单、货物信息及卸货地址等分散的数据源,
|
|
|
+ * 整合为一个完整的任务信息对象(TaskInfo),用于前端展示单个运输任务的详细状态。
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @param waybillOrder 运单主表信息,包含任务号、司机、车辆及基础时间信息
|
|
|
+ * @param logisticsOrder 关联的物流订单信息,用于补充货物基础单位及重量兜底
|
|
|
+ * @param subtask 运单子任务信息,包含实际装卸时间、委托量等执行细节
|
|
|
+ * @param loadTicket 装货单信息,包含皮重、毛重、净重及打印/上传时间
|
|
|
+ * @param goods 物流订单货物明细,包含货物名称、规格等静态属性
|
|
|
+ * @param unloadAddress 卸货地址信息,用于获取目的地城市名称
|
|
|
+ * @param truckAxleCache 车辆轴数缓存Map,Key为车牌号,Value为轴数,避免重复远程调用
|
|
|
+ * @return 组装好的单车任务信息对象
|
|
|
+ */
|
|
|
+ private TradeOrderTransportInfoResp.TaskInfo buildTaskInfo(KwtWaybillOrder waybillOrder,
|
|
|
+ KwtLogisticsOrder logisticsOrder,
|
|
|
+ KwtWaybillOrderSubtask subtask,
|
|
|
+ KwtWaybillOrderTicket loadTicket,
|
|
|
+ KwtLogisticsOrderGoods goods,
|
|
|
+ KwtLogisticsOrderAddress unloadAddress,
|
|
|
+ Map<String, String> truckAxleCache) {
|
|
|
+ log.debug("开始组装单车任务信息,waybillNo:{}", Objects.nonNull(waybillOrder) ? waybillOrder.getWOrderNo() : null);
|
|
|
+
|
|
|
+ TradeOrderTransportInfoResp.TaskInfo taskInfo = new TradeOrderTransportInfoResp.TaskInfo();
|
|
|
+
|
|
|
+ // 1. 设置任务编号
|
|
|
+ if (Objects.nonNull(waybillOrder)) {
|
|
|
+ taskInfo.setTaskNo(waybillOrder.getWOrderNo());
|
|
|
+ log.debug("设置任务编号: {}", waybillOrder.getWOrderNo());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 设置接单时间:优先使用任务开始时间,若为空则降级使用创建时间
|
|
|
+ Date acceptTime = firstNotNull(
|
|
|
+ Objects.nonNull(waybillOrder) ? waybillOrder.getTaskStartTime() : null,
|
|
|
+ Objects.nonNull(waybillOrder) ? waybillOrder.getCreateTime() : null
|
|
|
+ );
|
|
|
+ taskInfo.setAcceptTime(formatDate(acceptTime));
|
|
|
+ log.debug("设置接单时间: {}", formatDate(acceptTime));
|
|
|
+
|
|
|
+ // 3. 设置完成时间:优先使用任务结束时间,若为空则尝试从子任务中获取卸货时间
|
|
|
+ Date finishTime = firstNotNull(
|
|
|
+ Objects.nonNull(waybillOrder) ? waybillOrder.getTaskEndTime() : null,
|
|
|
+ Objects.nonNull(subtask) ? subtask.getUnloadTime() : null
|
|
|
+ );
|
|
|
+ taskInfo.setFinishTime(formatDate(finishTime));
|
|
|
+ log.debug("设置完成时间: {}", formatDate(finishTime));
|
|
|
+
|
|
|
+ // 4. 组装司机信息(姓名、电话、脱敏身份证号)
|
|
|
+ taskInfo.setDriverInfo(buildDriverInfo(waybillOrder));
|
|
|
+
|
|
|
+ // 5. 组装货物及重量信息(名称、规格、单位、各类重量数据)
|
|
|
+ taskInfo.setGoodsInfo(buildGoodsInfo(logisticsOrder, subtask, loadTicket, goods));
|
|
|
+
|
|
|
+ // 6. 组装车辆信息(车牌号、轴数-含缓存逻辑)
|
|
|
+ taskInfo.setTruckInfo(buildTruckInfo(waybillOrder, truckAxleCache));
|
|
|
+
|
|
|
+ // 7. 设置目的地城市名称
|
|
|
+ String destination = Objects.nonNull(unloadAddress) ? unloadAddress.getCityName() : null;
|
|
|
+ taskInfo.setDestination(destination);
|
|
|
+ log.debug("设置目的地: {}", destination);
|
|
|
+
|
|
|
+ // 8. 组装打印信息(主要基于装货单上传时间)
|
|
|
+ taskInfo.setPrintInfo(buildPrintInfo(loadTicket));
|
|
|
+
|
|
|
+ log.debug("单车任务信息组装完成,waybillNo:{}", Objects.nonNull(waybillOrder) ? waybillOrder.getWOrderNo() : null);
|
|
|
+ return taskInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组装司机信息。
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * 组装司机信息。
|
|
|
+ * <p>
|
|
|
+ * 从运单对象中提取司机姓名、电话及身份证号,并对身份证号进行脱敏处理以保护隐私。
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @param waybillOrder 运单信息,包含司机相关字段
|
|
|
+ * @return 组装后的司机信息对象
|
|
|
+ */
|
|
|
+ private TradeOrderTransportInfoResp.DriverInfo buildDriverInfo(KwtWaybillOrder waybillOrder) {
|
|
|
+ log.debug("开始组装司机信息,waybillNo:{}", Objects.nonNull(waybillOrder) ? waybillOrder.getWOrderNo() : null);
|
|
|
+
|
|
|
+ TradeOrderTransportInfoResp.DriverInfo driverInfo = new TradeOrderTransportInfoResp.DriverInfo();
|
|
|
+
|
|
|
+ if (Objects.nonNull(waybillOrder)) {
|
|
|
+ // 设置司机姓名
|
|
|
+ driverInfo.setName(waybillOrder.getDriverName());
|
|
|
+ // 设置司机电话
|
|
|
+ driverInfo.setPhone(waybillOrder.getDriverPhone());
|
|
|
+ // 设置脱敏后的身份证号
|
|
|
+ String maskedIdCard = maskIdCard(waybillOrder.getDriverIdcard());
|
|
|
+ driverInfo.setIdCard(maskedIdCard);
|
|
|
+
|
|
|
+ log.debug("司机信息组装完成,name:{}, phone:{}, idCardMasked:{}",
|
|
|
+ waybillOrder.getDriverName(),
|
|
|
+ waybillOrder.getDriverPhone(),
|
|
|
+ maskedIdCard);
|
|
|
+ } else {
|
|
|
+ log.warn("运单对象为空,无法组装司机信息");
|
|
|
+ }
|
|
|
+
|
|
|
+ return driverInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组装货物及重量信息。
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * 组装货物及重量信息。
|
|
|
+ * <p>
|
|
|
+ * 该方法负责从物流订单、运单子任务、装货单及货物明细中提取并整合货物相关信息。
|
|
|
+ * 主要包含货物名称、规格、单位、委托量以及皮重、毛重、净重等关键指标。
|
|
|
+ * </p>
|
|
|
+ * <p>
|
|
|
+ * 数据取值优先级策略:
|
|
|
+ * 1. 基础属性(名称、规格):优先取自货物明细表 (KwtLogisticsOrderGoods)。
|
|
|
+ * 2. 单位与委托量:优先取自运单子任务 (KwtWaybillOrderSubtask),若缺失则降级使用物流订单 (KwtLogisticsOrder) 中的数据。
|
|
|
+ * 3. 重量信息:
|
|
|
+ * - 皮重/毛重:直接取自装货单 (KwtWaybillOrderTicket)。
|
|
|
+ * - 净重:采用多级兜底策略,优先级依次为:
|
|
|
+ * a. 装货单记录的净重 (loadTicket.getAmount())
|
|
|
+ * b. 计算得出的净重 (毛重 - 皮重)
|
|
|
+ * c. 子任务记录的装载量 (subtask.getLoadAmount())
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @param logisticsOrder 物流订单信息,用于获取基础单位和重量兜底
|
|
|
+ * @param subtask 运单子任务信息,包含实际执行层面的单位、委托量及装载量
|
|
|
+ * @param loadTicket 装货单信息,包含称重相关的皮重、毛重及净重数据
|
|
|
+ * @param goods 货物明细信息,包含货物名称、规格等静态属性
|
|
|
+ * @return 组装后的货物信息对象
|
|
|
+ */
|
|
|
+ private TradeOrderTransportInfoResp.GoodsInfo buildGoodsInfo(KwtLogisticsOrder logisticsOrder,
|
|
|
+ KwtWaybillOrderSubtask subtask,
|
|
|
+ KwtWaybillOrderTicket loadTicket,
|
|
|
+ KwtLogisticsOrderGoods goods) {
|
|
|
+ log.debug("开始组装货物及重量信息,logisticsOrderId:{}, waybillSubtaskId:{}, loadTicketId:{}, goodsId:{}",
|
|
|
+ Objects.nonNull(logisticsOrder) ? logisticsOrder.getId() : null,
|
|
|
+ Objects.nonNull(subtask) ? subtask.getId() : null,
|
|
|
+ Objects.nonNull(loadTicket) ? loadTicket.getId() : null,
|
|
|
+ Objects.nonNull(goods) ? goods.getId() : null);
|
|
|
+
|
|
|
+ TradeOrderTransportInfoResp.GoodsInfo goodsInfo = new TradeOrderTransportInfoResp.GoodsInfo();
|
|
|
+
|
|
|
+ // 1. 设置货物基础属性:名称和规格
|
|
|
+ if (Objects.nonNull(goods)) {
|
|
|
+ goodsInfo.setMaterialName(goods.getGoodsName());
|
|
|
+ goodsInfo.setSpecification(goods.getRemark());
|
|
|
+ log.debug("设置货物名称: {}, 规格: {}", goods.getGoodsName(), goods.getRemark());
|
|
|
+ } else {
|
|
|
+ log.warn("货物明细为空,无法设置货物名称和规格");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 设置计量单位:优先使用子任务中的单位,若无则使用物流订单中的单位
|
|
|
+ String unit = firstNotBlank(
|
|
|
+ Objects.nonNull(subtask) ? subtask.getUnit() : null,
|
|
|
+ Objects.nonNull(logisticsOrder) ? logisticsOrder.getUnit() : null
|
|
|
+ );
|
|
|
+ goodsInfo.setUnit(unit);
|
|
|
+ log.debug("设置货物单位: {}", unit);
|
|
|
+
|
|
|
+ // 3. 设置委托任务量:优先使用子任务中的委托量,若无则使用物流订单中的总量
|
|
|
+ BigDecimal taskAmount = firstNotNull(
|
|
|
+ Objects.nonNull(subtask) ? subtask.getEntrustAmount() : null,
|
|
|
+ Objects.nonNull(logisticsOrder) ? logisticsOrder.getAmount() : null
|
|
|
+ );
|
|
|
+ goodsInfo.setTaskAmount(taskAmount);
|
|
|
+ log.debug("设置委托任务量: {}", taskAmount);
|
|
|
+
|
|
|
+ // 4. 设置皮重和毛重:直接从装货单获取
|
|
|
+ if (Objects.nonNull(loadTicket)) {
|
|
|
+ goodsInfo.setTareWeight(loadTicket.getTareAmount());
|
|
|
+ goodsInfo.setGrossWeight(loadTicket.getGrossAmount());
|
|
|
+ log.debug("设置皮重: {}, 毛重: {}", loadTicket.getTareAmount(), loadTicket.getGrossAmount());
|
|
|
+ } else {
|
|
|
+ log.warn("装货单信息为空,无法设置皮重和毛重");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 计算并设置净重:采用多级兜底策略
|
|
|
+ // 优先级:装货单净重 > (毛重 - 皮重) > 子任务装载量
|
|
|
+ BigDecimal netWeight = firstNotNull(
|
|
|
+ Objects.nonNull(loadTicket) ? loadTicket.getAmount() : null,
|
|
|
+ subtract(goodsInfo.getGrossWeight(), goodsInfo.getTareWeight()),
|
|
|
+ Objects.nonNull(subtask) ? subtask.getLoadAmount() : null
|
|
|
+ );
|
|
|
+ goodsInfo.setNetWeight(netWeight);
|
|
|
+ log.debug("设置净重: {} (来源优先级: 装货单记录 > 计算值 > 子任务记录)", netWeight);
|
|
|
+
|
|
|
+ log.debug("货物及重量信息组装完成,materialName:{}, netWeight:{}",
|
|
|
+ goodsInfo.getMaterialName(), goodsInfo.getNetWeight());
|
|
|
+ return goodsInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组装车辆信息。
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * 组装车辆信息。
|
|
|
+ * <p>
|
|
|
+ * 该方法负责从运单对象中提取车辆相关信息,并查询车辆轴数。
|
|
|
+ * 为了减少远程 Feign 调用的次数,使用了传入的缓存 Map (truckAxleCache) 来存储已查询过的车牌号对应的轴数。
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @param waybillOrder 运单信息,包含车牌号等基础数据
|
|
|
+ * @param truckAxleCache 车辆轴数缓存 Map,Key 为车牌号,Value 为轴数
|
|
|
+ * @return 组装后的车辆信息对象
|
|
|
+ */
|
|
|
+ private TradeOrderTransportInfoResp.TruckInfo buildTruckInfo(KwtWaybillOrder waybillOrder, Map<String, String> truckAxleCache) {
|
|
|
+ log.debug("开始组装车辆信息,waybillNo:{}", Objects.nonNull(waybillOrder) ? waybillOrder.getWOrderNo() : null);
|
|
|
+
|
|
|
+ TradeOrderTransportInfoResp.TruckInfo truckInfo = new TradeOrderTransportInfoResp.TruckInfo();
|
|
|
+
|
|
|
+ if (Objects.nonNull(waybillOrder)) {
|
|
|
+ // 设置车牌号
|
|
|
+ String truckNo = waybillOrder.getTruckNo();
|
|
|
+ truckInfo.setTruckNo(truckNo);
|
|
|
+ log.debug("设置车牌号: {}", truckNo);
|
|
|
+
|
|
|
+ // 获取车辆轴数:优先从缓存中获取,若不存在则调用 queryTruckAxle 查询并放入缓存
|
|
|
+ // 使用 StringUtils.defaultString 防止 key 为 null 导致 Map 操作异常
|
|
|
+ String cacheKey = StringUtils.defaultString(truckNo);
|
|
|
+ String truckAxle = truckAxleCache.computeIfAbsent(cacheKey, this::queryTruckAxle);
|
|
|
+ truckInfo.setTruckAxle(truckAxle);
|
|
|
+ log.debug("设置车辆轴数: {} (来源: {})", truckAxle, truckAxleCache.containsKey(cacheKey) && truckAxle != null ? "缓存/新查询" : "查询失败/null");
|
|
|
+ } else {
|
|
|
+ log.warn("运单对象为空,无法组装车辆信息");
|
|
|
+ }
|
|
|
+
|
|
|
+ log.debug("车辆信息组装完成,truckNo:{}, truckAxle:{}",
|
|
|
+ Objects.nonNull(truckInfo.getTruckNo()) ? truckInfo.getTruckNo() : "N/A",
|
|
|
+ truckInfo.getTruckAxle());
|
|
|
+ return truckInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 通过车辆 Feign 查询车辆轴数。
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * 通过车辆 Feign 查询车辆轴数。
|
|
|
+ * <p>
|
|
|
+ * 该方法调用远程车队服务接口,根据车牌号获取车辆的轴数信息。
|
|
|
+ * 主要用于补充运输任务中的车辆详细属性,以支持后续的运费计算或合规性校验。
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @param truckNo 车牌号
|
|
|
+ * @return 车辆轴数(字符串格式),若查询失败、无数据或发生异常则返回 null
|
|
|
+ */
|
|
|
+ private String queryTruckAxle(String truckNo) {
|
|
|
+ // 1. 参数校验:车牌号为空直接返回
|
|
|
+ if (StringUtils.isBlank(truckNo)) {
|
|
|
+ log.debug("查询车辆轴数跳过:车牌号为空");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ log.debug("开始查询车辆轴数,truckNo:{}", truckNo);
|
|
|
+ try {
|
|
|
+ // 2. 调用远程 Feign 接口
|
|
|
+ HttpResult result = fleetTruckFeignService.findByTruckNo(truckNo);
|
|
|
+
|
|
|
+ // 3. 校验响应结果有效性
|
|
|
+ // - 响应对象非空
|
|
|
+ // - 状态码为成功 (HttpStatus.SUCCESS_CODE)
|
|
|
+ // - 返回数据结构为 Map 类型
|
|
|
+ if (Objects.isNull(result) || result.getCode() != HttpStatus.SUCCESS_CODE || !(result.getData() instanceof Map<?, ?> dataMap)) {
|
|
|
+ log.warn("查询车辆轴数失败:远程服务返回异常或数据格式不符,truckNo:{}, resultCode:{}, dataType:{}",
|
|
|
+ truckNo,
|
|
|
+ Objects.nonNull(result) ? result.getCode() : "N/A",
|
|
|
+ Objects.nonNull(result) && Objects.nonNull(result.getData()) ? result.getData().getClass().getSimpleName() : "N/A");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 提取轴数字段 "carAxis"
|
|
|
+ Object carAxis = dataMap.get("carAxis");
|
|
|
+
|
|
|
+ // 5. 转换并返回结果
|
|
|
+ if (Objects.nonNull(carAxis)) {
|
|
|
+ String axleStr = String.valueOf(carAxis);
|
|
|
+ log.debug("成功查询到车辆轴数,truckNo:{}, axle:{}", truckNo, axleStr);
|
|
|
+ return axleStr;
|
|
|
+ } else {
|
|
|
+ log.debug("远程服务返回数据中未包含轴数信息,truckNo:{}", truckNo);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ // 6. 异常处理:记录错误日志并返回 null,避免影响主流程
|
|
|
+ log.error("Feign 调用查询车辆轴数发生异常,truckNo:{}, errorMessage:{}", truckNo, e.getMessage(), e);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组装打印信息,当前运输库无独立打印记录时使用装货单上传时间兜底。
|
|
|
+ */
|
|
|
+ /**
|
|
|
+ * 组装打印信息。
|
|
|
+ * <p>
|
|
|
+ * 该方法负责构建任务相关的打印信息对象。
|
|
|
+ * 由于当前运输库中可能没有独立的打印记录表,因此采用“装货单上传时间”作为打印时间的兜底数据。
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @param loadTicket 装货单信息,包含上传时间等关键字段
|
|
|
+ * @return 组装后的打印信息对象,若装货单为空则返回仅包含空字段的对象
|
|
|
+ */
|
|
|
+ private TradeOrderTransportInfoResp.PrintInfo buildPrintInfo(KwtWaybillOrderTicket loadTicket) {
|
|
|
+ log.debug("开始组装打印信息,loadTicketId:{}", Objects.nonNull(loadTicket) ? loadTicket.getId() : null);
|
|
|
+
|
|
|
+ TradeOrderTransportInfoResp.PrintInfo printInfo = new TradeOrderTransportInfoResp.PrintInfo();
|
|
|
+
|
|
|
+ if (Objects.nonNull(loadTicket)) {
|
|
|
+ // 使用装货单的上传时间作为打印时间
|
|
|
+ Date uploadingTime = loadTicket.getUploadingTime();
|
|
|
+ String printTimeStr = formatDate(uploadingTime);
|
|
|
+ printInfo.setPrintTime(printTimeStr);
|
|
|
+
|
|
|
+ log.debug("设置打印时间(源自装货单上传时间): {}", printTimeStr);
|
|
|
+ } else {
|
|
|
+ log.warn("装货单信息为空,无法设置打印时间");
|
|
|
+ }
|
|
|
+
|
|
|
+ log.debug("打印信息组装完成");
|
|
|
+ return printInfo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 选择最新的运单子任务。
|
|
|
+ * <p>
|
|
|
+ * 当存在多个子任务时,根据更新时间(UpdateTime)进行比较,保留最新更新的记录。
|
|
|
+ * 若更新时间为空,则视为较小值(排在后面),确保非空时间优先被选中。
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @param source 源子任务
|
|
|
+ * @param target 目标子任务
|
|
|
+ * @return 更新时间较晚的子任务;若两者均为 null 或更新时间相同,返回 source
|
|
|
+ */
|
|
|
+ private KwtWaybillOrderSubtask chooseLatestSubtask(KwtWaybillOrderSubtask source, KwtWaybillOrderSubtask target) {
|
|
|
+ log.debug("比较运单子任务更新时间,sourceId:{}, sourceUpdateTime:{}, targetId:{}, targetUpdateTime:{}",
|
|
|
+ Objects.nonNull(source) ? source.getId() : null,
|
|
|
+ Objects.nonNull(source) ? source.getUpdateTime() : null,
|
|
|
+ Objects.nonNull(target) ? target.getId() : null,
|
|
|
+ Objects.nonNull(target) ? target.getUpdateTime() : null);
|
|
|
+
|
|
|
+ KwtWaybillOrderSubtask latest = Comparator.nullsLast(Comparator.comparing(KwtWaybillOrderSubtask::getUpdateTime, Comparator.nullsLast(Date::compareTo)))
|
|
|
+ .compare(source, target) >= 0 ? source : target;
|
|
|
+
|
|
|
+ log.debug("选择最新的运单子任务,selectedId:{}, selectedUpdateTime:{}",
|
|
|
+ Objects.nonNull(latest) ? latest.getId() : null,
|
|
|
+ Objects.nonNull(latest) ? latest.getUpdateTime() : null);
|
|
|
+
|
|
|
+ return latest;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 选择最新的装货单。
|
|
|
+ * <p>
|
|
|
+ * 当存在多个装货单时,根据更新时间(UpdateTime)进行比较,保留最新更新的记录。
|
|
|
+ * 若更新时间为空,则视为较小值(排在后面),确保非空时间优先被选中。
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @param source 源装货单
|
|
|
+ * @param target 目标装货单
|
|
|
+ * @return 更新时间较晚的装货单;若两者均为 null 或更新时间相同,返回 source
|
|
|
+ */
|
|
|
+ private KwtWaybillOrderTicket chooseLatestTicket(KwtWaybillOrderTicket source, KwtWaybillOrderTicket target) {
|
|
|
+ log.debug("比较装货单更新时间,sourceId:{}, sourceUpdateTime:{}, targetId:{}, targetUpdateTime:{}",
|
|
|
+ Objects.nonNull(source) ? source.getId() : null,
|
|
|
+ Objects.nonNull(source) ? source.getUpdateTime() : null,
|
|
|
+ Objects.nonNull(target) ? target.getId() : null,
|
|
|
+ Objects.nonNull(target) ? target.getUpdateTime() : null);
|
|
|
+
|
|
|
+ KwtWaybillOrderTicket latest = Comparator.nullsLast(Comparator.comparing(KwtWaybillOrderTicket::getUpdateTime, Comparator.nullsLast(Date::compareTo)))
|
|
|
+ .compare(source, target) >= 0 ? source : target;
|
|
|
+
|
|
|
+ log.debug("选择最新的装货单,selectedId:{}, selectedUpdateTime:{}",
|
|
|
+ Objects.nonNull(latest) ? latest.getId() : null,
|
|
|
+ Objects.nonNull(latest) ? latest.getUpdateTime() : null);
|
|
|
+
|
|
|
+ return latest;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static String maskIdCard(String idCard) {
|
|
|
+ if (StringUtils.isBlank(idCard) || idCard.length() < 8) {
|
|
|
+ return idCard;
|
|
|
+ }
|
|
|
+ return idCard.substring(0, 5) + "****" + idCard.substring(idCard.length() - 4);
|
|
|
+ }
|
|
|
+
|
|
|
+ @SafeVarargs
|
|
|
+ public static <T> T firstNotNull(T... values) {
|
|
|
+ if (Objects.isNull(values)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ for (T value : values) {
|
|
|
+ if (Objects.nonNull(value)) {
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static BigDecimal subtract(BigDecimal source, BigDecimal target) {
|
|
|
+ if (Objects.isNull(source) || Objects.isNull(target)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return source.subtract(target);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String firstNotBlank(String source, String target) {
|
|
|
+ return StringUtils.isNotBlank(source) ? source : target;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String formatDate(Date date) {
|
|
|
+ if (Objects.isNull(date)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return new SimpleDateFormat(DATE_TIME_PATTERN).format(date);
|
|
|
+ }
|
|
|
+}
|