Forráskód Böngészése

Merge remote-tracking branch 'origin/main'

lishiqiang 2 hónapja
szülő
commit
d7e8ea1ea5
22 módosított fájl, 511 hozzáadás és 182 törlés
  1. 23 3
      ship-module-ota/ship-module-ota-biz/src/main/resources/mapper/distributor/DistributorMapper.xml
  2. 1 1
      ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/dal/mysql/voyagestockdetail/VoyageStockDetailMapper.java
  3. 8 0
      ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/listener/RoomStockInitEventListener.java
  4. 9 0
      ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/service/voyage/VoyageServiceImpl.java
  5. 13 1
      ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/service/voyagestockdetail/VoyageStockDetailServiceImpl.java
  6. 8 0
      ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/service/voyagestockdistribute/VoyageStockDistributeNewServiceImpl.java
  7. 3 0
      ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/utils/ProductRedisUtils.java
  8. 2 1
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/order/vo/order/TradeOrderRespNewVO.java
  9. 3 0
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/order/vo/order/TradeOrderRespVO.java
  10. 10 0
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/dal/dataobject/order/TradeOrderDO.java
  11. 2 0
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/business/DailyBusinessStatisticsServiceImpl.java
  12. 16 0
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/order/impl/TradeOrderServiceImpl.java
  13. 50 18
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/otc/impl/OtcTradeOrderServiceImpl.java
  14. 6 1
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/pay/impl/TradeOrderPayServiceImpl.java
  15. 8 2
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/refund/impl/TradeRefundServiceImpl.java
  16. 5 1
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/report/impl/VoyageStockBoardServiceImpl.java
  17. 239 87
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/utils/IdCardProvinceUtil.java
  18. 40 24
      ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/order/TradeOrderMapper.xml
  19. 5 5
      ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/orderjzdetail/OrderGiftVisitorMapper.xml
  20. 18 12
      ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/orderjzdetail/OrderJzDetailMapper.xml
  21. 21 21
      ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/report/DistributorBalanceMapper.xml
  22. 21 5
      ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/report/YangtzePassengerSummaryMapper.xml

+ 23 - 3
ship-module-ota/ship-module-ota-biz/src/main/resources/mapper/distributor/DistributorMapper.xml

@@ -137,9 +137,29 @@
     </select>
 
     <select id="queryDistributorMoneyTotal" resultType="com.yc.ship.module.ota.controller.admin.distributor.vo.DistributorRespVO">
-        SELECT sum(IF(recharge_type = 1, money, 0.00)) balanceTotal, sum(IF(recharge_type = 2, money, 0.00)) payCreditTotal
-        FROM ota_distributor_recharge
-        WHERE `status` = 1 and deleted = 0 and distributor_id = #{distributorId}
+
+        SELECT
+            t1.payCreditTotal,
+            t1.balanceTotal + IFNULL(t2.amount, 0) AS balanceTotal
+        FROM (
+                 SELECT
+                     SUM(IF(recharge_type = 1, money, 0.00)) balanceTotal,
+                     SUM(IF(recharge_type = 2, money, 0.00)) payCreditTotal
+                 FROM ota_distributor_recharge
+                 WHERE
+                     `status` = 1
+                   AND deleted = 0
+                   AND distributor_id = #{distributorId}
+             ) t1
+                 LEFT JOIN (
+            SELECT
+                SUM(sr.money) AS amount
+            FROM ota_distributor_self_recharge sr
+            WHERE
+                sr.deleted = 0
+              AND sr.recharge_state = 1
+              AND sr.distributor_id = #{distributorId}
+        ) t2 ON 1 = 1;
     </select>
 
     <select id="getPlatTenantId" resultType="com.yc.ship.module.ota.dal.dataobject.distributor.DistributorDO">

+ 1 - 1
ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/dal/mysql/voyagestockdetail/VoyageStockDetailMapper.java

@@ -77,7 +77,7 @@ public interface VoyageStockDetailMapper extends BaseMapperX<VoyageStockDetailDO
             "    torm.floor AS floor," +
             "    COALESCE(SUM(CASE WHEN td.order_status = 14 THEN torm.use_room_num ELSE 0 END), 0) AS leaveNum," +
             "    COALESCE(SUM(CASE WHEN td.order_status IN (1, 6) THEN torm.use_room_num ELSE 0 END), 0) AS lockNum," +
-            "    COALESCE(SUM(CASE WHEN td.order_status > 0 AND td.order_status NOT IN (1, 6, 14) THEN torm.use_room_num ELSE 0 END), 0) AS otherNum " +
+            "    COALESCE(SUM(CASE WHEN td.order_status > 0 AND td.order_status NOT IN (1, 6, 11, 14) THEN torm.use_room_num ELSE 0 END), 0) AS otherNum " +
             "FROM trade_order td " +
             "         LEFT JOIN trade_order_room_model torm ON torm.deleted = 0 AND td.id = torm.order_id " +
             "         LEFT JOIN resource_room_model rrm ON torm.room_model_id = rrm.id AND rrm.deleted = '0' " +

+ 8 - 0
ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/listener/RoomStockInitEventListener.java

@@ -8,6 +8,7 @@ import com.yc.ship.module.product.dal.mysql.voyage.VoyageMapper;
 import com.yc.ship.module.product.dal.mysql.voyagestock.VoyageStockMapper;
 import com.yc.ship.module.product.dal.mysql.voyagestockdetail.VoyageStockDetailMapper;
 import com.yc.ship.module.product.framework.lock.ProductRedisKeyConstants;
+import com.yc.ship.module.product.utils.ProductRedisUtils;
 import com.yc.ship.module.resource.dal.dataobject.roommodel.ResourceRoomModelDO;
 import com.yc.ship.module.resource.dal.mysql.roommodel.ResourceRoomModelMapper;
 import com.yc.ship.module.resource.event.room.RoomStockInitEvent;
@@ -48,6 +49,9 @@ public class RoomStockInitEventListener {
     @Resource
     private ResourceRoomModelMapper resourceRoomModelMapper;
 
+    @Resource
+    private ProductRedisUtils productRedisUtils;
+
     @EventListener
     public void handleRoomStockInit(RoomStockInitEvent event) {
         try {
@@ -106,6 +110,8 @@ public class RoomStockInitEventListener {
             voyageStock.setSurplusNum(voyageStock.getSurplusNum().add(quantityDecimal));
 
             voyageStockMapper.updateById(voyageStock);
+            // 设置缓存
+            productRedisUtils.set(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId, voyageStock);
 
 
             ResourceRoomModelDO roomModel = resourceRoomModelMapper.selectById(event.getRoomModelId());
@@ -131,6 +137,8 @@ public class RoomStockInitEventListener {
             List<VoyageStockDetailDO> detailList =  new java.util.ArrayList<>();
             detailList.add(detail);
             voyageStockDetailMapper.insertBatch(detailList);
+            // 设置缓存
+            productRedisUtils.set(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId, detail);
 
             log.info("航次ID: {} 新增房型库存记录,数量: {}", voyageId, event.getQuantity());
 

+ 9 - 0
ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/service/voyage/VoyageServiceImpl.java

@@ -44,6 +44,7 @@ import com.yc.ship.module.product.framework.config.ShipPlatformProperties;
 import com.yc.ship.module.product.framework.lock.ProductRedisKeyConstants;
 import com.yc.ship.module.product.service.pricetemplate.PriceTemplateService;
 import com.yc.ship.module.product.service.pricevoyage.PriceVoyageService;
+import com.yc.ship.module.product.utils.ProductRedisUtils;
 import com.yc.ship.module.product.utils.SM2Utils;
 import com.yc.ship.module.product.utils.TicketTypeUtil;
 import com.yc.ship.module.product.utils.VoyageUUCodeUtils;
@@ -163,6 +164,9 @@ public class VoyageServiceImpl implements VoyageService {
     @Resource
     private AgentOrderApi agentOrderApi;
 
+    @Resource
+    private ProductRedisUtils productRedisUtils;
+
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -273,6 +277,8 @@ public class VoyageServiceImpl implements VoyageService {
             lock.lock(60, TimeUnit.SECONDS);
             if(stockDO != null) {
                 voyageStockMapper.updateById(voyageStockDO);
+                // 设置缓存
+                productRedisUtils.set(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId, stockDO);
             }else {
                 //新增时添加统计数据
                 List<RoomModelFloorNumDTO> list = shipApi.queryRoomModelFloorToNum(reqVO.getShipId());
@@ -291,6 +297,7 @@ public class VoyageServiceImpl implements VoyageService {
                 voyageStockDO.setShareNum(BigDecimal.ZERO);
                 voyageStockDO.setVoyageId(voyageId);
                 voyageStockMapper.insert(voyageStockDO);
+                productRedisUtils.set(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId, voyageStockDO);
                 //添加库存详情
                 List<VoyageStockDetailDO> stockDetailList = new ArrayList<>();
                 list.forEach(item -> {
@@ -314,6 +321,7 @@ public class VoyageServiceImpl implements VoyageService {
                     stockDetailList.add(stockDetailDO);
                 });
                 voyageStockDetailMapper.insertBatch(stockDetailList);
+                productRedisUtils.set(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId, stockDetailList);
             }
         }finally {
             if(lock.isHeldByCurrentThread() && lock.isLocked()){
@@ -350,6 +358,7 @@ public class VoyageServiceImpl implements VoyageService {
             voyageStockDO.setShareNum(zero);
             voyageStockDO.setVoyageId(item.getId());
             voyageStockDOList.add(voyageStockDO);
+            // 设置缓存
             list.forEach(roomModelFloorNumDTO -> {
                 BigDecimal num = BigDecimal.valueOf(roomModelFloorNumDTO.getNum());
                 VoyageStockDetailDO stockDetailDO = new VoyageStockDetailDO();

+ 13 - 1
ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/service/voyagestockdetail/VoyageStockDetailServiceImpl.java

@@ -15,6 +15,7 @@ import com.yc.ship.module.product.dal.mysql.voyagestock.VoyageStockMapper;
 import com.yc.ship.module.product.dal.mysql.voyagestockdistribute.VoyageStockDistributeNewMapper;
 import com.yc.ship.module.product.enums.ProductStockStaticsName;
 import com.yc.ship.module.product.framework.lock.ProductRedisKeyConstants;
+import com.yc.ship.module.product.utils.ProductRedisUtils;
 import com.yc.ship.module.resource.api.ship.ShipApi;
 import com.yc.ship.module.resource.api.ship.dto.RoomModelFloorNumDTO;
 import com.yc.ship.module.resource.dal.dataobject.room.ResourceRoomDO;
@@ -63,6 +64,9 @@ public class VoyageStockDetailServiceImpl implements VoyageStockDetailService {
     @Resource
     private ShipApi shipApi;
 
+    @Resource
+    private ProductRedisUtils productRedisUtils;
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Boolean createVoyageStock(List<VoyageStockDetailSaveReqVO> createReqVO) {
@@ -88,7 +92,12 @@ public class VoyageStockDetailServiceImpl implements VoyageStockDetailService {
                     insertList.add(item);
                 }
             });
-            return voyageStockDetailMapper.insertBatch(insertList);
+            Boolean aBoolean = voyageStockDetailMapper.insertBatch(insertList);
+            // 设置缓存
+            if(aBoolean) {
+                productRedisUtils.set(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId, insertList);
+            }
+            return aBoolean;
         }finally {
             if(lock.isHeldByCurrentThread() && lock.isLocked()){
                 lock.unlock();
@@ -110,6 +119,8 @@ public class VoyageStockDetailServiceImpl implements VoyageStockDetailService {
         }
         stockDO.setShareNum(stockDO.getCanSellNum().subtract(distributeNum));
         voyageStockMapper.updateById(stockDO);
+        // 设置缓存
+        productRedisUtils.set(ProductRedisKeyConstants.STOCK_TOTAL_REDIS_KEY_PREFIX + voyageId, stockDO);
         if(stockDistributeNewList != null && !stockDistributeNewList.isEmpty()) {
             Map<String, BigDecimal> numMap = stockDistributeNewList.stream().collect(Collectors.groupingBy(item -> item.getRoomModelId() + "_" + item.getFloor(), Collectors.mapping(VoyageStockDistributeNewDO::getNum, Collectors.reducing(BigDecimal.ZERO, BigDecimal::add))));
             List<VoyageStockDetailDO> list = voyageStockDetailMapper.selectListByVoyageId(voyageId);
@@ -121,6 +132,7 @@ public class VoyageStockDetailServiceImpl implements VoyageStockDetailService {
                 }
             });
             voyageStockDetailMapper.updateBatch(list);
+            productRedisUtils.set(ProductRedisKeyConstants.STOCK_DETAIL_REDIS_KEY_PREFIX + voyageId, list);
         }
     }
 

+ 8 - 0
ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/service/voyagestockdistribute/VoyageStockDistributeNewServiceImpl.java

@@ -20,6 +20,7 @@ import com.yc.ship.module.product.enums.YesOrNoEnum;
 import com.yc.ship.module.product.framework.lock.ProductRedisKeyConstants;
 import com.yc.ship.module.product.service.voyagestock.VoyageStockService;
 import com.yc.ship.module.product.service.voyagestockdetail.VoyageStockDetailService;
+import com.yc.ship.module.product.utils.ProductRedisUtils;
 import com.yc.ship.module.resource.api.room.RoomApi;
 import com.yc.ship.module.resource.api.room.dto.RoomRespDTO;
 import com.yc.ship.module.resource.dal.dataobject.roommodel.ResourceRoomModelDO;
@@ -79,6 +80,9 @@ public class VoyageStockDistributeNewServiceImpl implements VoyageStockDistribut
     @Resource
     private ResourceRouteService routeService;
 
+    @Resource
+    private ProductRedisUtils productRedisUtils;
+
 
 
     @Override
@@ -136,6 +140,10 @@ public class VoyageStockDistributeNewServiceImpl implements VoyageStockDistribut
             }
             BigDecimal distributeNum = getDistributeNum(voyageId);
             List<VoyageStockDistributeNewDO> newList = voyageStockDistributeNewMapper.selectListByVoyageId(voyageId, type);
+            Map<Long, List<VoyageStockDistributeNewDO>> listMap = CollectionUtils.convertMultiMap(newList, (item) -> item.getDistributorId());
+            for(Long distributorId : listMap.keySet()) {
+                productRedisUtils.set(ProductRedisKeyConstants.STOCK_DISTRIBUTOR_REDIS_KEY_PREFIX + voyageId + "_" + distributorId, listMap.get(distributorId));
+            }
             voyageStockDetailService.handleShareNum(voyageId,  distributeNum, newList);
         }finally {
             if(lock.isHeldByCurrentThread() && lock.isLocked()){

+ 3 - 0
ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/utils/ProductRedisUtils.java

@@ -62,6 +62,9 @@ public class ProductRedisUtils {
         }
         return value == null ? null : fromJsonList(value, clazz);
     }
+    public <T> List<T> getList(String key, Class<T> clazz) {
+        return getList(key, clazz, NOT_EXPIRE);
+    }
 
     public <T> T get(String key, Class<T> clazz) {
         return get(key, clazz, NOT_EXPIRE);

+ 2 - 1
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/order/vo/order/TradeOrderRespNewVO.java

@@ -268,7 +268,8 @@ public class TradeOrderRespNewVO {
     @Schema(description = "投保错误描述")
     private String resMsg;
 
-
+    @Schema(description = "留位到期时间")
+    private LocalDateTime delayExpireTime;
 
 
 }

+ 3 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/order/vo/order/TradeOrderRespVO.java

@@ -565,5 +565,8 @@ public class TradeOrderRespVO {
     @Schema(description = "投保错误描述")
     private String resMsg;
 
+    @Schema(description = "留位到期时间")
+    private LocalDateTime delayExpireTime;
+
 
 }

+ 10 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/dal/dataobject/order/TradeOrderDO.java

@@ -374,4 +374,14 @@ public class TradeOrderDO extends TenantSyncDO {
      * 排序
      */
     private Integer orderSort;
+
+    /**
+     * 计调游船部备注
+     */
+    private String jdRemark;
+
+    /**
+     * 延迟队列到期时间
+     */
+    private LocalDateTime delayExpireTime;
 }

+ 2 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/business/DailyBusinessStatisticsServiceImpl.java

@@ -3,6 +3,7 @@ package com.yc.ship.module.trade.service.business;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.yc.ship.framework.common.pojo.PageResult;
+import com.yc.ship.framework.tenant.core.aop.TenantIgnore;
 import com.yc.ship.module.trade.controller.admin.business.vo.DailyBusinessStatisticsReqVO;
 import com.yc.ship.module.trade.controller.admin.business.vo.DailyBusinessStatisticsRespVO;
 import com.yc.ship.module.trade.dal.mysql.order.TradeOrderMapper;
@@ -20,6 +21,7 @@ public class DailyBusinessStatisticsServiceImpl implements DailyBusinessStatisti
     private TradeOrderMapper tradeOrderMapper;
 
     @Override
+    @TenantIgnore
     public PageResult<DailyBusinessStatisticsRespVO> getDailyBusinessStatisticsPage(DailyBusinessStatisticsReqVO pageReqVO) {
         IPage<DailyBusinessStatisticsRespVO> page = new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());
         IPage<DailyBusinessStatisticsRespVO> resultPage = tradeOrderMapper.getDailyBusinessStatisticsPage(page, pageReqVO);

+ 16 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/order/impl/TradeOrderServiceImpl.java

@@ -29,6 +29,7 @@ import com.yc.ship.module.product.api.ProductApi;
 import com.yc.ship.module.product.api.dto.*;
 import com.yc.ship.module.product.enums.ProductTypeEnum;
 import com.yc.ship.module.product.enums.SpecTypeEnum;
+import com.yc.ship.module.product.framework.lock.ProductRedisKeyConstants;
 import com.yc.ship.module.product.service.voyage.VoyageService;
 import com.yc.ship.module.resource.api.ship.ShipApi;
 import com.yc.ship.module.resource.dal.dataobject.route.ResourceRouteDO;
@@ -70,8 +71,14 @@ import com.yc.ship.module.trade.utils.BeanUtils;
 import com.yc.ship.module.trade.utils.TradeUUCodeUtils;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.interceptor.TransactionAspectSupport;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
 
 import javax.annotation.Resource;
 import java.math.BigDecimal;
@@ -116,6 +123,8 @@ public class TradeOrderServiceImpl implements TradeOrderService {
     @Resource
     private ProductApi productApi;
     @Resource
+    private RedissonClient redissonClient;
+    @Resource
     private OtaDistributorApi otaDistributorApi;
     @Resource
     private ThirdGateSystemService thirdGateSystemService;
@@ -239,9 +248,13 @@ public class TradeOrderServiceImpl implements TradeOrderService {
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
     @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ORDER_CREATE)
     public CommonResult<TradeOrderRespDTO> createOrder(TradeOrderCreateReqDTO orderCreateReqDTO) {
         TradeOrderBO tradeOrderBO = null;
+        RLock stockLock = null;
+        String stockLockKey = null;
+        boolean unlockRegistered = false;
         try {
             //1.构建订单
             tradeOrderBO = buildCreateOrder(orderCreateReqDTO);
@@ -285,6 +298,7 @@ public class TradeOrderServiceImpl implements TradeOrderService {
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
     public CommonResult<TradeOrderRespDTO> createShipOrder(ShipTradeOrderCreateReqVO shipTradeOrderCreateReqVO) {
         TradeOrderBO tradeOrderBO = null;
         try {
@@ -315,11 +329,13 @@ public class TradeOrderServiceImpl implements TradeOrderService {
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
     public CommonResult<TradeOrderRespDTO> modifyShipOrder(ShipTradeOrderCreateReqVO shipTradeOrderCreateReqVO) {
         TradeOrderBO tradeOrderBO = null;
         try {
             //1.构建订单
             tradeOrderBO = buildShipCreateOrder(shipTradeOrderCreateReqVO);
+
             tradeOrderBO.setOldTradeOrderDO(shipTradeOrderCreateReqVO.getOldtradeOrderDO());
             //2.下单前处理
             beforeOrderCreate(tradeOrderBO);

+ 50 - 18
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/otc/impl/OtcTradeOrderServiceImpl.java

@@ -8,10 +8,7 @@ import com.alibaba.excel.EasyExcel;
 import com.alibaba.excel.EasyExcelFactory;
 import com.alibaba.excel.ExcelWriter;
 import com.alibaba.excel.metadata.Head;
-import com.alibaba.excel.metadata.data.ImageData;
-import com.alibaba.excel.metadata.data.WriteCellData;
 import com.alibaba.excel.util.DateUtils;
-import com.alibaba.excel.util.FileUtils;
 import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
 import com.alibaba.excel.write.merge.AbstractMergeStrategy;
 import com.alibaba.excel.write.metadata.WriteSheet;
@@ -25,7 +22,6 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.core.toolkit.IdWorker;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.fhs.core.trans.anno.IgnoreTrans;
 import com.yc.ship.framework.common.enums.UserTypeEnum;
 import com.yc.ship.framework.common.lang.Asserts;
 import com.yc.ship.framework.common.pojo.CommonResult;
@@ -34,7 +30,6 @@ import com.yc.ship.framework.common.util.collection.CollectionUtils;
 import com.yc.ship.framework.common.util.object.BeanUtils;
 import com.yc.ship.framework.common.util.object.ObjectUtils;
 import com.yc.ship.framework.dict.core.DictFrameworkUtils;
-import com.yc.ship.framework.excel.core.merge.MergeStrategy;
 import com.yc.ship.framework.ip.core.utils.AreaUtils;
 import com.yc.ship.framework.mybatis.core.query.LambdaQueryWrapperX;
 import com.yc.ship.framework.security.core.LoginUser;
@@ -76,7 +71,6 @@ import com.yc.ship.module.product.api.ProductApi;
 import com.yc.ship.module.product.api.VoyageApi;
 import com.yc.ship.module.product.api.dto.*;
 import com.yc.ship.module.product.controller.admin.voyage.vo.VoyageRespVO;
-import com.yc.ship.module.trade.controller.admin.order.vo.order.PublicOrderVoyageRespVO;
 import com.yc.ship.module.product.dal.dataobject.priceroommodeltype.PriceRoomModelTypeDO;
 import com.yc.ship.module.product.dal.dataobject.voyage.VoyageDO;
 import com.yc.ship.module.product.enums.DistributorOrStoreEnum;
@@ -128,8 +122,6 @@ import com.yc.ship.module.trade.framework.annotation.TradeOrderLog;
 import com.yc.ship.module.trade.framework.common.ConfigUtils;
 import com.yc.ship.module.trade.framework.common.ThreadLocalUtil;
 import com.yc.ship.module.trade.framework.common.TradeOrderLogUtils;
-import com.yc.ship.module.trade.service.refund.TradeRefundService;
-import com.yc.ship.module.trade.utils.IdCardProvinceUtil;
 import com.yc.ship.module.trade.framework.mq.TradePublishUtils;
 import com.yc.ship.module.trade.service.invoice.InvoiceService;
 import com.yc.ship.module.trade.service.order.TradeOrderRepositoryService;
@@ -137,7 +129,9 @@ import com.yc.ship.module.trade.service.order.TradeOrderService;
 import com.yc.ship.module.trade.service.otc.OtcTradeOrderService;
 import com.yc.ship.module.trade.service.pay.TradeOrderPayService;
 import com.yc.ship.module.trade.service.refund.TradeRefundRepositoryService;
+import com.yc.ship.module.trade.service.refund.TradeRefundService;
 import com.yc.ship.module.trade.utils.AgencyAuthUtils;
+import com.yc.ship.module.trade.utils.IdCardProvinceUtil;
 import com.yc.ship.module.trade.utils.excel.ExcelStyleHandler;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
@@ -146,18 +140,22 @@ import org.apache.poi.ss.usermodel.*;
 import org.apache.poi.ss.util.CellRangeAddress;
 import org.apache.poi.ss.util.RegionUtil;
 import org.apache.poi.util.IOUtils;
-import org.apache.poi.xssf.usermodel.XSSFSheet;
 import org.apache.poi.xssf.usermodel.XSSFWorkbook;
 import org.jetbrains.annotations.NotNull;
 import org.redisson.api.RLock;
 import org.redisson.api.RedissonClient;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
-import java.io.*;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
+import java.time.Duration;
 import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
@@ -445,7 +443,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
 
     @Override
     @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ORDER_AUDIT)
-    @Transactional(rollbackFor = Exception.class)
+    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
     @TenantIgnore
     public CommonResult<?> orderAudit(Long orderId, Integer auditStatus,String auditAdvice) {
         TradeOrderDO tradeOrderDO = tradeOrderMapper.selectById(orderId);
@@ -626,6 +624,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
                 List<OrderRoomUseDTO> orderRoomUseDTOList = tradeVisitorMapper.selectRoomShipVisitor2(lastTradeOrder.getId());
                 reqDTO.setOrderRoomList(orderRoomUseDTOList);
                 reqDTO.setOrderRoomList(orderRoomUseDTOList);
+
                 Integer orderStatus = reqDTO.getOrderStatus();
                 if (Objects.equals(orderStatus, TradeOrderStatusEnum.YD.getStatus())) {
                     voyageApi.preReduceStock(reqDTO);
@@ -1431,7 +1430,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
 
     @Override
     @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.CANCEL_AUDIT)
-    @Transactional(rollbackFor = Exception.class)
+    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
     @TenantIgnore
     public CommonResult<?> cancelAuto(Long orderId) {
         TradeOrderDO tradeOrderDO = tradeOrderMapper.selectById(orderId);
@@ -1491,6 +1490,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
 
             List<OrderRoomUseDTO> orderRoomUseDTOList = tradeVisitorMapper.selectRoomShipVisitor2(lastTradeOrder.getId());
             reqDTO.setOrderRoomList(orderRoomUseDTOList);
+
             Integer orderStatus = reqDTO.getOrderStatus();
             if (Objects.equals(orderStatus, TradeOrderStatusEnum.YD.getStatus())) {
                 voyageApi.preReduceStock(reqDTO);
@@ -1499,7 +1499,6 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
                 voyageApi.reduceStock(reqDTO);
             }
 
-
             Map<String, Object> extMap = new HashMap<>();
             extMap.put("result", "审核撤回");
             TradeOrderLogUtils.setOrderInfo(orderId, tradeOrderDO.getOrderStatus(), tradeOrderDO.getOrderStatus(), extMap);
@@ -2326,11 +2325,12 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
+    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
     @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ORDER_CREATE)
     public CommonResult<?> createOtcOrder(LoginUser loginUser, TradeOrderOtcCreateVO createVO) {
         Long distributorId = createVO.getDistributorId() != null ? createVO.getDistributorId() : loginUser.getDistributorId();
         Long storeId = createVO.getStoreId() != null ? createVO.getStoreId() : loginUser.getStoreId();
+        Long voyageId = createVO.getVoyageId();
         //获取当前分销商信息
         if (distributorId == null) {
             return CommonResult.error(ORDER_OTC_FAIL);
@@ -2345,7 +2345,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
         if (storeId != null) {
             storeInfo = storeApi.getStoreInfo(storeId);
         }
-        VoyageRespDTO voyage = productApi.getVoyage(createVO.getVoyageId());
+        VoyageRespDTO voyage = productApi.getVoyage(voyageId);
         RouteRespDTO routeRespDTO = routeApi.queryRouteById(voyage.getRouteId());
         ShipRespDTO ship = shipApi.queryShip(createVO.getShipId());
 
@@ -2362,7 +2362,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
             ccode = ship.getCode() + "-" + date + "-" + code + "-";
         }
 
-        int maxOrderNo = tradeOrderService.getMaxOrderNo(createVO.getVoyageId(),ccode);
+        int maxOrderNo = tradeOrderService.getMaxOrderNo(voyageId,ccode);
         String orderNo = ccode+(maxOrderNo + 1);
         //现在给整个订单号的加锁
         String lockKey = String.format(ProductRedisKeyConstants.ORDER_REDIS_KEY_PREFIX, orderNo);
@@ -2624,8 +2624,11 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
                 }
                 double hours = Double.parseDouble(autoClearTime);
                 long delayMs = (long) (hours * 60 * 60 * 1000);
+                // 根据当前时间获取延迟队列到期时间
+                LocalDateTime expireDateTime = LocalDateTime.now().plus(Duration.ofMillis(delayMs));
                 tradePublishUtils.publishHoldOrderCancelDelayMsg(orderId.toString(), delayMs);
                 log.info("留位订单创建成功,已发送{}小时自动取消延迟消息:orderId={}", autoClearTime,orderId);
+                tradeOrderMapper.update(new UpdateWrapper<TradeOrderDO>().set("delay_expire_time", expireDateTime).eq("id", orderId));
             }
             OtcTradeOrderCreateRespVO respVO = TradeOrderConvert.INSTANCE.convert(result.getData());
             TradeOrderLogUtils.setOrderInfo(orderId, null, orderStatus, append);
@@ -2661,13 +2664,14 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
+    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
     @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ORDER_MODIFY)
     public CommonResult<?> motifyOtcOrder(LoginUser loginUser, TradeOrderOtcCreateVO createVO) {
         // 获取分销商id
         Long distributorId = createVO.getDistributorId() != null ? createVO.getDistributorId() : loginUser.getDistributorId();
         // 获取门店id
         Long storeId = createVO.getStoreId() != null ? createVO.getStoreId() : loginUser.getStoreId();
+        Long voyageId = createVO.getVoyageId();
         //获取当前分销商信息
         if (distributorId == null) {
             return CommonResult.error(ORDER_OTC_FAIL);
@@ -2699,7 +2703,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
         int auditType = 0;
         String orderNo = tradeOrderDO.getOrderNo();
         Long oldvoyageId = tradeOrderDO.getVoyageId();
-        Long newvoyageId = createVO.getVoyageId();
+        Long newvoyageId = voyageId;
         List<TradeOrderPolicyReqVO> policyList = createVO.getPolicyList();
         // 航次ID
         if (newvoyageId == null) {
@@ -2715,6 +2719,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
         //现在给整个订单号的加锁
         String lockKey = String.format(ProductRedisKeyConstants.ORDER_REDIS_KEY_PREFIX, orderNo);
         RLock lock = redissonClient.getLock(lockKey);
+        boolean stockUnlockRegistered = false;
         try {
             lock.lock(60, TimeUnit.SECONDS);
             Date updateTime = new Date();
@@ -3763,11 +3768,38 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
         if (tradeOrderDO == null) {
             return CommonResult.error(ORDER_NOT_EXIST);
         }
+        // 延迟过期时间
+        LocalDateTime delayExpireTime = tradeOrderDO.getDelayExpireTime();
+        // 判断延迟过期时间是否过期
+        LocalDateTime now = LocalDateTime.now();
+        Boolean delayExpireFlag = true;
+        if (ObjectUtil.isNotNull(delayExpireTime)) {
+            // 过期时间晚于当前时间 = 未过期
+            if (delayExpireTime.isAfter(now)) {
+                delayExpireFlag = false;
+            }
+        }
         tradeOrderMapper.update(Wrappers.<TradeOrderDO>lambdaUpdate()
                 .set(TradeOrderDO::getOrderStatus, status)
                 .set(TradeOrderDO::getUpdateTime, LocalDateTime.now())
                 .eq(TradeOrderDO::getId, orderId)
         );
+        // 如果改为留位,并且时间已过期
+        if (status == TradeOrderStatusEnum.YD.getStatus() && delayExpireFlag) {
+            log.info("订单修改为留位,orderId={}", orderId);
+            // 查询配置信息,获取取消时间
+            String autoClearTime = configApi.getPlatformConfigValueByKey("autoClearTimeoutParam.four_hours");
+            if (StrUtil.isBlank(autoClearTime)) {
+                autoClearTime = "4";
+            }
+            double hours = Double.parseDouble(autoClearTime);
+            long delayMs = (long) (hours * 60 * 60 * 1000);
+            // 根据当前时间获取延迟队列到期时间
+            LocalDateTime expireDateTime = LocalDateTime.now().plus(Duration.ofMillis(delayMs));
+            tradePublishUtils.publishHoldOrderCancelDelayMsg(orderId.toString(), delayMs);
+            log.info("订单修改为留位状态成功,已发送{}小时自动取消延迟消息:orderId={}", autoClearTime,orderId);
+            tradeOrderMapper.update(new UpdateWrapper<TradeOrderDO>().set("delay_expire_time", expireDateTime).eq("id", orderId));
+        }
         return CommonResult.success("修改成功");
     }
 

+ 6 - 1
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/pay/impl/TradeOrderPayServiceImpl.java

@@ -29,6 +29,7 @@ import com.yc.ship.module.product.api.VoyageApi;
 import com.yc.ship.module.product.api.dto.OrderRoomUseDTO;
 import com.yc.ship.module.product.api.dto.ReduceStockReqDTO;
 import com.yc.ship.module.product.enums.DistributorOrStoreEnum;
+import com.yc.ship.module.product.framework.lock.ProductRedisKeyConstants;
 import com.yc.ship.module.trade.controller.admin.order.vo.order.PayOrderReqVO;
 import com.yc.ship.module.trade.controller.admin.order.vo.order.PayOrderRespVO;
 import com.yc.ship.module.trade.dal.dataobject.order.*;
@@ -57,7 +58,10 @@ import org.redisson.api.RLock;
 import org.redisson.api.RedissonClient;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
 
 import javax.annotation.Resource;
 import java.math.BigDecimal;
@@ -497,7 +501,7 @@ public class TradeOrderPayServiceImpl implements TradeOrderPayService {
     }
 
     @Override
-    @Transactional(rollbackFor = Exception.class)
+    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
     @TradeOrderLog(operateType = TradeOrderOperateTypeEnum.MANUL_CANCEL)
     public CommonResult<?> orderPayCancelHandler(Long orderId) {
         String lockKey = String.format(UPDATE_ORDER_LOCK, orderId);
@@ -538,6 +542,7 @@ public class TradeOrderPayServiceImpl implements TradeOrderPayService {
                     }
                 });
                 reqDTO.setOrderRoomList(orderRoomUseDTOList);
+
                 if (Objects.equals(orderStatus, TradeOrderStatusEnum.YD.getStatus())) {
                     voyageApi.preCancelReduceStock(reqDTO);
                 } else {

+ 8 - 2
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/refund/impl/TradeRefundServiceImpl.java

@@ -28,6 +28,7 @@ import com.yc.ship.module.product.api.dto.ProductSpuRespDTO;
 import com.yc.ship.module.product.api.dto.ReduceStockReqDTO;
 import com.yc.ship.module.product.enums.DistributorOrStoreEnum;
 import com.yc.ship.module.product.enums.RefundRuleEnum;
+import com.yc.ship.module.product.framework.lock.ProductRedisKeyConstants;
 import com.yc.ship.module.trade.api.dto.*;
 import com.yc.ship.module.trade.controller.admin.order.vo.refund.TradeRefundAmountReqVO;
 import com.yc.ship.module.trade.convert.order.TradeRefundConvert;
@@ -67,6 +68,10 @@ import org.redisson.api.RLock;
 import org.redisson.api.RedissonClient;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Isolation;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
 
 import javax.annotation.Resource;
 import java.math.BigDecimal;
@@ -137,7 +142,6 @@ public class TradeRefundServiceImpl implements TradeRefundService {
     @Resource
     private RefundMapper refundMapper;
 
-
     @Override
     public CommonResult<TradeRefundRespDTO> refund(TradeRefundCreateReqDTO refundCreateReqDTO) {
         String lockKey = String.format(UPDATE_ORDER_LOCK, refundCreateReqDTO.getOrderId());
@@ -282,6 +286,7 @@ public class TradeRefundServiceImpl implements TradeRefundService {
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
     public CommonResult<TradeRefundAuditRespDTO> refundAudit(TradeRefundAuditReqDTO refundAuditReqDTO) {
         //创建审核记录
         RefundDO refundDO = tradeRefundRepositoryService.getById(refundAuditReqDTO.getRefundId());
@@ -319,8 +324,8 @@ public class TradeRefundServiceImpl implements TradeRefundService {
                 reqDTO.setOrderRoomList(orderRoomUseDTOList);
                 voyageApi.cancelReduceStockNew(reqDTO);
             } catch (Exception e) {
-                e.printStackTrace();
                 log.error("取消库存cancelReduceStock失败", e);
+                throw new RuntimeException("取消库存失败:" + e.getMessage(), e);
             }
             //发送审核通知消息
             tradePublishUtils.publishRefundAuditMsg(refundAuditDO.getId().toString());
@@ -488,6 +493,7 @@ public class TradeRefundServiceImpl implements TradeRefundService {
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED)
     public CommonResult<?> refundByPaymentType(Long refundId) {
         String lockKey = String.format(UPDATE_REFUND_LOCK, refundId);
         RLock lock = redissonClient.getLock(lockKey);

+ 5 - 1
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/report/impl/VoyageStockBoardServiceImpl.java

@@ -818,7 +818,11 @@ public class VoyageStockBoardServiceImpl implements VoyageStockBoardService {
         // 全国城市来源排行
         List<String> nationalCityRankList = cityCountMap.entrySet().stream()
                 .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
-                .map(Map.Entry::getKey)
+                .map(entry -> {
+                    String key = entry.getKey() == null ? "" : entry.getKey().toString();
+                    String value = entry.getValue() == null ? "" : entry.getValue().toString();
+                    return key + "(" + value + ")";
+                })
                 .collect(Collectors.toList());
 
         // 省份-城市排行映射

+ 239 - 87
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/utils/IdCardProvinceUtil.java

@@ -114,112 +114,264 @@ public class IdCardProvinceUtil {
         // 91开头的为国外身份证件,无对应省份
         PROVINCE_MAP = Collections.unmodifiableMap(map);
 
-        Map<String, String> cityMap = new HashMap<>(80);
-        // 直辖市
-        cityMap.put("1100", "北京"); cityMap.put("1101", "北京");
-        cityMap.put("1200", "天津"); cityMap.put("1201", "天津");
-        cityMap.put("3100", "上海"); cityMap.put("3101", "上海");
-        cityMap.put("5000", "重庆"); cityMap.put("5001", "重庆");
-        // 河北
+        Map<String, String> cityMap = new HashMap<>(350);
+        // ==================== 直辖市 ====================
+        cityMap.put("1100", "北京"); cityMap.put("1101", "北京"); cityMap.put("1102", "北京"); // 1102: 北京旧县区码
+        cityMap.put("1200", "天津"); cityMap.put("1201", "天津"); cityMap.put("1202", "天津"); // 1202: 天津旧县区码
+        cityMap.put("3100", "上海"); cityMap.put("3101", "上海"); cityMap.put("3102", "上海"); // 3102: 上海旧县区码
+        cityMap.put("5000", "重庆"); cityMap.put("5001", "重庆"); cityMap.put("5002", "重庆"); // 5002: 重庆旧县区码
+
+        // ==================== 河北(13) ====================
         cityMap.put("1301", "石家庄"); cityMap.put("1302", "唐山"); cityMap.put("1303", "秦皇岛"); cityMap.put("1304", "邯郸");
-        // 山西
-        cityMap.put("1401", "太原"); cityMap.put("1402", "大同");
-        // 内蒙古
-        cityMap.put("1501", "呼和浩特"); cityMap.put("1502", "包头");
-        // 辽宁
+        cityMap.put("1305", "邢台");   cityMap.put("1306", "保定");   cityMap.put("1307", "张家口");
+        cityMap.put("1308", "承德");   cityMap.put("1309", "沧州");   cityMap.put("1310", "廊坊"); cityMap.put("1311", "衡水");
+        // 河北旧地区码(已撤并入地级市)
+        cityMap.put("1321", "邯郸"); cityMap.put("1322", "邢台"); cityMap.put("1323", "石家庄");
+        cityMap.put("1324", "保定"); cityMap.put("1325", "张家口"); cityMap.put("1326", "承德");
+        cityMap.put("1327", "唐山"); cityMap.put("1328", "衡水"); cityMap.put("1329", "沧州"); cityMap.put("1330", "廊坊");
+
+        // ==================== 山西(14) ====================
+        cityMap.put("1401", "太原"); cityMap.put("1402", "大同");   cityMap.put("1403", "阳泉");
+        cityMap.put("1404", "长治"); cityMap.put("1405", "晋城");   cityMap.put("1406", "朔州");
+        cityMap.put("1407", "晋中"); cityMap.put("1408", "运城");   cityMap.put("1409", "忻州");
+        cityMap.put("1410", "临汾"); cityMap.put("1411", "吕梁");
+        // 山西旧地区码
+        cityMap.put("1422", "忻州"); cityMap.put("1423", "吕梁"); cityMap.put("1424", "晋中");
+        cityMap.put("1426", "临汾"); cityMap.put("1427", "运城"); cityMap.put("1421", "雁北");
+
+        // ==================== 内蒙古(15) ====================
+        cityMap.put("1501", "呼和浩特"); cityMap.put("1502", "包头");   cityMap.put("1503", "乌海");
+        cityMap.put("1504", "赤峰");     cityMap.put("1505", "通辽");   cityMap.put("1506", "鄂尔多斯");
+        cityMap.put("1507", "呼伦贝尔"); cityMap.put("1508", "巴彦淖尔"); cityMap.put("1509", "乌兰察布");
+        cityMap.put("1522", "兴安盟");   cityMap.put("1525", "锡林郭勒盟"); cityMap.put("1529", "阿拉善盟");
+        // 内蒙古旧盟码
+        cityMap.put("1521", "呼伦贝尔"); cityMap.put("1523", "通辽");   cityMap.put("1524", "兴安盟");
+        cityMap.put("1526", "乌兰察布"); cityMap.put("1527", "鄂尔多斯"); cityMap.put("1528", "巴彦淖尔");
+
+        // ==================== 辽宁(21) ====================
         cityMap.put("2101", "沈阳"); cityMap.put("2102", "大连"); cityMap.put("2103", "鞍山");
-        // 吉林
-        cityMap.put("2201", "长春"); cityMap.put("2202", "吉林");
-        // 黑龙江
-        cityMap.put("2301", "哈尔滨"); cityMap.put("2302", "齐齐哈尔");
-        // 江苏
-        cityMap.put("3201", "南京"); cityMap.put("3202", "无锡"); cityMap.put("3203", "徐州");
-        cityMap.put("3204", "常州"); cityMap.put("3205", "苏州"); cityMap.put("3206", "南通");
+        cityMap.put("2104", "抚顺"); cityMap.put("2105", "本溪"); cityMap.put("2106", "丹东");
+        cityMap.put("2107", "锦州"); cityMap.put("2108", "营口"); cityMap.put("2109", "阜新");
+        cityMap.put("2110", "辽阳"); cityMap.put("2111", "盘锦"); cityMap.put("2112", "铁岭");
+        cityMap.put("2113", "朝阳"); cityMap.put("2114", "葫芦岛");
+        // 辽宁旧地区码(铁岭地区/朝阳地区)
+        cityMap.put("2121", "铁岭"); cityMap.put("2122", "朝阳");
+
+        // ==================== 吉林(22) ====================
+        cityMap.put("2201", "长春"); cityMap.put("2202", "吉林"); cityMap.put("2203", "四平");
+        cityMap.put("2204", "辽源"); cityMap.put("2205", "通化"); cityMap.put("2206", "白山");
+        cityMap.put("2207", "松原"); cityMap.put("2208", "白城"); cityMap.put("2224", "延边");
+        // 吉林旧地区码
+        cityMap.put("2223", "延边"); cityMap.put("2224", "延边");
+
+        // ==================== 黑龙江(23) ====================
+        cityMap.put("2301", "哈尔滨"); cityMap.put("2302", "齐齐哈尔"); cityMap.put("2303", "鸡西");
+        cityMap.put("2304", "鹤岗");   cityMap.put("2305", "双鸭山");   cityMap.put("2306", "大庆");
+        cityMap.put("2307", "伊春");   cityMap.put("2308", "佳木斯");   cityMap.put("2309", "七台河");
+        cityMap.put("2310", "牡丹江"); cityMap.put("2311", "黑河");     cityMap.put("2312", "绥化");
+        cityMap.put("2327", "大兴安岭");
+        // 黑龙江旧地区码
+        cityMap.put("2321", "松花江"); cityMap.put("2323", "绥化"); cityMap.put("2326", "黑河");
+
+        // ==================== 江苏(32) ====================
+        cityMap.put("3200", "南京"); // 直辖/省级直辖旧码
+        cityMap.put("3201", "南京"); cityMap.put("3202", "无锡");   cityMap.put("3203", "徐州");
+        cityMap.put("3204", "常州"); cityMap.put("3205", "苏州");   cityMap.put("3206", "南通");
         cityMap.put("3207", "连云港"); cityMap.put("3208", "淮安"); cityMap.put("3209", "盐城");
-        cityMap.put("3210", "扬州"); cityMap.put("3211", "镇江"); cityMap.put("3212", "泰州"); cityMap.put("3213", "宿迁");
-        // 浙江
+        cityMap.put("3210", "扬州"); cityMap.put("3211", "镇江");   cityMap.put("3212", "泰州"); cityMap.put("3213", "宿迁");
+
+        // ==================== 浙江(33) ====================
+        cityMap.put("3300", "杭州"); // 直辖旧码
         cityMap.put("3301", "杭州"); cityMap.put("3302", "宁波"); cityMap.put("3303", "温州");
         cityMap.put("3304", "嘉兴"); cityMap.put("3305", "湖州"); cityMap.put("3306", "绍兴");
         cityMap.put("3307", "金华"); cityMap.put("3308", "衢州"); cityMap.put("3309", "舟山");
         cityMap.put("3310", "台州"); cityMap.put("3311", "丽水");
-        // 安徽
-        cityMap.put("3401", "合肥"); cityMap.put("3402", "芜湖"); cityMap.put("3403", "蚌埠"); cityMap.put("3404", "淮南");
-        cityMap.put("3405", "马鞍山"); cityMap.put("3406", "淮北"); cityMap.put("3407", "铜陵"); cityMap.put("3408", "安庆");
-        // 福建
+
+        // ==================== 安徽(34) ====================
+        cityMap.put("3401", "合肥");   cityMap.put("3402", "芜湖");   cityMap.put("3403", "蚌埠"); cityMap.put("3404", "淮南");
+        cityMap.put("3405", "马鞍山"); cityMap.put("3406", "淮北");   cityMap.put("3407", "铜陵"); cityMap.put("3408", "安庆");
+        cityMap.put("3410", "黄山");   cityMap.put("3411", "滁州");   cityMap.put("3412", "阜阳"); cityMap.put("3413", "宿州");
+        cityMap.put("3415", "六安");   cityMap.put("3416", "亳州");   cityMap.put("3417", "池州"); cityMap.put("3418", "宣城");
+        // 安徽旧地区码(巢湖已撤销并入合肥/芜湖/马鞍山)
+        cityMap.put("3414", "巢湖"); cityMap.put("3421", "阜阳"); cityMap.put("3422", "宿州");
+        cityMap.put("3423", "滁州"); cityMap.put("3424", "六安"); cityMap.put("3425", "宣城");
+        cityMap.put("3426", "巢湖"); cityMap.put("3427", "徽州"); cityMap.put("3428", "安庆");
+        cityMap.put("3429", "池州");
+
+        // ==================== 福建(35) ====================
+        cityMap.put("3500", "福州"); // 直辖旧码
         cityMap.put("3501", "福州"); cityMap.put("3502", "厦门"); cityMap.put("3503", "莆田"); cityMap.put("3504", "三明");
         cityMap.put("3505", "泉州"); cityMap.put("3506", "漳州"); cityMap.put("3507", "南平"); cityMap.put("3508", "龙岩"); cityMap.put("3509", "宁德");
-        // 江西
+        // 福建旧地区码(建阳→南平)
+        cityMap.put("3521", "南平"); cityMap.put("3522", "宁德");
+
+        // ==================== 江西(36) ====================
+        cityMap.put("3600", "南昌"); // 直辖旧码
         cityMap.put("3601", "南昌"); cityMap.put("3602", "景德镇"); cityMap.put("3603", "萍乡"); cityMap.put("3604", "九江");
-        cityMap.put("3605", "新余"); cityMap.put("3606", "鹰潭"); cityMap.put("3607", "赣州"); cityMap.put("3608", "吉安");
-        cityMap.put("3609", "宜春"); cityMap.put("3610", "抚州"); cityMap.put("3611", "上饶");
-        // 山东
-        cityMap.put("3701", "济南"); cityMap.put("3702", "青岛"); cityMap.put("3703", "淄博"); cityMap.put("3704", "枣庄");
-        cityMap.put("3705", "东营"); cityMap.put("3706", "烟台"); cityMap.put("3707", "潍坊"); cityMap.put("3708", "济宁");
-        cityMap.put("3709", "泰安"); cityMap.put("3710", "威海"); cityMap.put("3711", "日照"); cityMap.put("3713", "临沂");
-        // 河南
-        cityMap.put("4101", "郑州"); cityMap.put("4102", "开封"); cityMap.put("4103", "洛阳"); cityMap.put("4104", "平顶山");
-        cityMap.put("4105", "安阳"); cityMap.put("4106", "鹤壁"); cityMap.put("4107", "新乡"); cityMap.put("4108", "焦作");
-        cityMap.put("4109", "濮阳"); cityMap.put("4110", "许昌"); cityMap.put("4111", "漯河"); cityMap.put("4112", "三门峡");
-        cityMap.put("4113", "南阳"); cityMap.put("4114", "商丘"); cityMap.put("4115", "信阳"); cityMap.put("4116", "周口"); cityMap.put("4117", "驻马店");
-        // 湖北
-        cityMap.put("4201", "武汉"); cityMap.put("4202", "黄石"); cityMap.put("4203", "十堰"); cityMap.put("4205", "宜昌");
-        cityMap.put("4206", "襄阳"); cityMap.put("4207", "鄂州"); cityMap.put("4208", "荆门"); cityMap.put("4209", "孝感");
-        cityMap.put("4210", "荆州"); cityMap.put("4211", "黄冈"); cityMap.put("4212", "咸宁"); cityMap.put("4213", "随州");
+        cityMap.put("3605", "新余"); cityMap.put("3606", "鹰潭");   cityMap.put("3607", "赣州"); cityMap.put("3608", "吉安");
+        cityMap.put("3609", "宜春"); cityMap.put("3610", "抚州");   cityMap.put("3611", "上饶");
+        // 江西旧地区码
+        cityMap.put("3621", "赣州"); cityMap.put("3622", "宜春"); cityMap.put("3623", "上饶");
+        cityMap.put("3624", "吉安"); cityMap.put("3625", "抚州"); cityMap.put("3626", "九江");
+
+        // ==================== 山东(37) ====================
+        cityMap.put("3700", "济南"); // 直辖旧码
+        cityMap.put("3701", "济南"); cityMap.put("3702", "青岛");   cityMap.put("3703", "淄博"); cityMap.put("3704", "枣庄");
+        cityMap.put("3705", "东营"); cityMap.put("3706", "烟台");   cityMap.put("3707", "潍坊"); cityMap.put("3708", "济宁");
+        cityMap.put("3709", "泰安"); cityMap.put("3710", "威海");   cityMap.put("3711", "日照"); cityMap.put("3712", "莱芜");
+        cityMap.put("3713", "临沂"); cityMap.put("3714", "德州");   cityMap.put("3715", "聊城"); cityMap.put("3716", "滨州"); cityMap.put("3717", "菏泽");
+        // 山东旧地区码
+        cityMap.put("3723", "滨州"); cityMap.put("3724", "德州");   cityMap.put("3725", "聊城");
+        cityMap.put("3728", "临沂"); cityMap.put("3729", "菏泽");
+
+        // ==================== 河南(41) ====================
+        cityMap.put("4101", "郑州"); cityMap.put("4102", "开封");   cityMap.put("4103", "洛阳"); cityMap.put("4104", "平顶山");
+        cityMap.put("4105", "安阳"); cityMap.put("4106", "鹤壁");   cityMap.put("4107", "新乡"); cityMap.put("4108", "焦作");
+        cityMap.put("4109", "濮阳"); cityMap.put("4110", "许昌");   cityMap.put("4111", "漯河"); cityMap.put("4112", "三门峡");
+        cityMap.put("4113", "南阳"); cityMap.put("4114", "商丘");   cityMap.put("4115", "信阳"); cityMap.put("4116", "周口"); cityMap.put("4117", "驻马店");
+        // 河南旧地区码
+        cityMap.put("4123", "新乡"); cityMap.put("4125", "商丘");   cityMap.put("4127", "周口");
+        cityMap.put("4128", "驻马店"); cityMap.put("4129", "南阳"); cityMap.put("4130", "信阳");
+
+        // ==================== 湖北(42) ====================
+        cityMap.put("4200", "武汉"); // 直辖旧码
+        cityMap.put("4201", "武汉"); cityMap.put("4202", "黄石");   cityMap.put("4203", "十堰"); cityMap.put("4205", "宜昌");
+        cityMap.put("4206", "襄阳"); cityMap.put("4207", "鄂州");   cityMap.put("4208", "荆门"); cityMap.put("4209", "孝感");
+        cityMap.put("4210", "荆州"); cityMap.put("4211", "黄冈");   cityMap.put("4212", "咸宁"); cityMap.put("4213", "随州");
         cityMap.put("4228", "恩施");
-        // 湖南
-        cityMap.put("4301", "长沙"); cityMap.put("4302", "株洲"); cityMap.put("4303", "湘潭"); cityMap.put("4304", "衡阳");
-        cityMap.put("4305", "邵阳"); cityMap.put("4306", "岳阳"); cityMap.put("4307", "常德"); cityMap.put("4308", "张家界");
-        cityMap.put("4309", "益阳"); cityMap.put("4310", "郴州"); cityMap.put("4311", "永州"); cityMap.put("4312", "怀化");
+        // 湖北旧地区码
+        cityMap.put("4221", "黄冈"); cityMap.put("4222", "孝感");   cityMap.put("4223", "咸宁");
+        cityMap.put("4224", "荆州"); cityMap.put("4225", "襄阳");   cityMap.put("4226", "郧阳");
+        cityMap.put("4227", "宜昌"); cityMap.put("4229", "鄂西");
+        // 湖北省直辖县级
+        cityMap.put("4290", "湖北直辖"); cityMap.put("4294", "仙桃"); cityMap.put("4295", "潜江");
+        cityMap.put("4296", "天门");   cityMap.put("4292", "神农架");
+
+        // ==================== 湖南(43) ====================
+        cityMap.put("4300", "长沙"); // 直辖旧码
+        cityMap.put("4301", "长沙"); cityMap.put("4302", "株洲");   cityMap.put("4303", "湘潭"); cityMap.put("4304", "衡阳");
+        cityMap.put("4305", "邵阳"); cityMap.put("4306", "岳阳");   cityMap.put("4307", "常德"); cityMap.put("4308", "张家界");
+        cityMap.put("4309", "益阳"); cityMap.put("4310", "郴州");   cityMap.put("4311", "永州"); cityMap.put("4312", "怀化");
         cityMap.put("4313", "娄底"); cityMap.put("4331", "湘西");
-        // 广东
-        cityMap.put("4401", "广州"); cityMap.put("4402", "韶关"); cityMap.put("4403", "深圳"); cityMap.put("4404", "珠海");
-        cityMap.put("4405", "汕头"); cityMap.put("4406", "佛山"); cityMap.put("4407", "江门"); cityMap.put("4408", "湛江");
-        cityMap.put("4409", "茂名"); cityMap.put("4412", "肇庆"); cityMap.put("4413", "惠州"); cityMap.put("4414", "梅州");
-        cityMap.put("4415", "汕尾"); cityMap.put("4416", "河源"); cityMap.put("4417", "阳江"); cityMap.put("4418", "清远");
-        cityMap.put("4419", "东莞"); cityMap.put("4420", "中山"); cityMap.put("4451", "潮州"); cityMap.put("4452", "揭阳"); cityMap.put("4453", "云浮");
-        // 广西
-        cityMap.put("4501", "南宁"); cityMap.put("4502", "柳州"); cityMap.put("4503", "桂林"); cityMap.put("4504", "梧州");
+        // 湖南旧地区码
+        cityMap.put("4321", "湘潭"); cityMap.put("4322", "衡阳");   cityMap.put("4323", "邵阳");
+        cityMap.put("4324", "岳阳"); cityMap.put("4325", "常德");   cityMap.put("4326", "益阳");
+        cityMap.put("4327", "郴州"); cityMap.put("4328", "永州");   cityMap.put("4329", "娄底");
+        cityMap.put("4330", "怀化");
+
+        // ==================== 广东(44) ====================
+        cityMap.put("4400", "广州"); // 直辖旧码
+        cityMap.put("4401", "广州"); cityMap.put("4402", "韶关");   cityMap.put("4403", "深圳"); cityMap.put("4404", "珠海");
+        cityMap.put("4405", "汕头"); cityMap.put("4406", "佛山");   cityMap.put("4407", "江门"); cityMap.put("4408", "湛江");
+        cityMap.put("4409", "茂名"); cityMap.put("4412", "肇庆");   cityMap.put("4413", "惠州"); cityMap.put("4414", "梅州");
+        cityMap.put("4415", "汕尾"); cityMap.put("4416", "河源");   cityMap.put("4417", "阳江"); cityMap.put("4418", "清远");
+        cityMap.put("4419", "东莞"); cityMap.put("4420", "中山");   cityMap.put("4451", "潮州"); cityMap.put("4452", "揭阳"); cityMap.put("4453", "云浮");
+        // 广东旧地区码(海南行政区已独立)
+        cityMap.put("4421", "海南行政区"); cityMap.put("4425", "梅州");   cityMap.put("4428", "肇庆");
+        cityMap.put("4429", "湛江");       cityMap.put("4490", "广东直辖");
+
+        // ==================== 广西(45) ====================
+        cityMap.put("4500", "南宁"); // 直辖旧码
+        cityMap.put("4501", "南宁"); cityMap.put("4502", "柳州");   cityMap.put("4503", "桂林"); cityMap.put("4504", "梧州");
         cityMap.put("4505", "北海"); cityMap.put("4506", "防城港"); cityMap.put("4507", "钦州"); cityMap.put("4508", "贵港");
-        cityMap.put("4509", "玉林"); cityMap.put("4510", "百色"); cityMap.put("4511", "贺州"); cityMap.put("4512", "河池");
+        cityMap.put("4509", "玉林"); cityMap.put("4510", "百色");   cityMap.put("4511", "贺州"); cityMap.put("4512", "河池");
         cityMap.put("4513", "来宾"); cityMap.put("4514", "崇左");
-        // 海南
+        // 广西旧地区码
+        cityMap.put("4521", "南宁"); cityMap.put("4522", "柳州");   cityMap.put("4523", "桂林");
+        cityMap.put("4524", "梧州"); cityMap.put("4525", "玉林");   cityMap.put("4526", "百色");
+        cityMap.put("4527", "河池"); cityMap.put("4528", "钦州");
+
+        // ==================== 海南(46) ====================
+        cityMap.put("4600", "海口"); // 直辖旧码
         cityMap.put("4601", "海口"); cityMap.put("4602", "三亚"); cityMap.put("4604", "儋州");
-        // 四川
-        cityMap.put("5101", "成都"); cityMap.put("5103", "自贡"); cityMap.put("5104", "攀枝花"); cityMap.put("5105", "泸州");
-        cityMap.put("5106", "德阳"); cityMap.put("5107", "绵阳"); cityMap.put("5108", "广元"); cityMap.put("5109", "遂宁");
-        cityMap.put("5110", "内江"); cityMap.put("5111", "乐山"); cityMap.put("5113", "南充"); cityMap.put("5114", "眉山");
-        cityMap.put("5115", "宜宾"); cityMap.put("5116", "广安"); cityMap.put("5117", "达州"); cityMap.put("5118", "雅安");
-        cityMap.put("5119", "巴中"); cityMap.put("5120", "资阳"); cityMap.put("5132", "阿坝"); cityMap.put("5133", "甘孜"); cityMap.put("5134", "凉山");
-        // 贵州
+        // 海南旧码(原属广东)
+        cityMap.put("4603", "三沙");
+
+        // ==================== 四川(51) ====================
+        cityMap.put("5100", "成都"); // 直辖旧码
+        cityMap.put("5101", "成都"); cityMap.put("5103", "自贡");   cityMap.put("5104", "攀枝花"); cityMap.put("5105", "泸州");
+        cityMap.put("5106", "德阳"); cityMap.put("5107", "绵阳");   cityMap.put("5108", "广元"); cityMap.put("5109", "遂宁");
+        cityMap.put("5110", "内江"); cityMap.put("5111", "乐山");   cityMap.put("5113", "南充"); cityMap.put("5114", "眉山");
+        cityMap.put("5115", "宜宾"); cityMap.put("5116", "广安");   cityMap.put("5117", "达州"); cityMap.put("5118", "雅安");
+        cityMap.put("5119", "巴中"); cityMap.put("5120", "资阳");   cityMap.put("5132", "阿坝"); cityMap.put("5133", "甘孜"); cityMap.put("5134", "凉山");
+        // 四川旧地区码
+        cityMap.put("5121", "内江"); cityMap.put("5122", "涪陵");   cityMap.put("5123", "万县");
+        cityMap.put("5125", "宜宾"); cityMap.put("5128", "南充");   cityMap.put("5129", "达州");
+        cityMap.put("5130", "雅安"); cityMap.put("5131", "黔江");
+
+        // ==================== 贵州(52) ====================
+        cityMap.put("5200", "贵阳"); // 直辖旧码
         cityMap.put("5201", "贵阳"); cityMap.put("5202", "六盘水"); cityMap.put("5203", "遵义"); cityMap.put("5204", "安顺");
-        cityMap.put("5205", "毕节"); cityMap.put("5206", "铜仁"); cityMap.put("5223", "黔西南"); cityMap.put("5226", "黔东南"); cityMap.put("5227", "黔南");
-        // 云南
-        cityMap.put("5301", "昆明"); cityMap.put("5303", "曲靖"); cityMap.put("5304", "玉溪"); cityMap.put("5305", "保山");
-        cityMap.put("5306", "昭通"); cityMap.put("5307", "丽江"); cityMap.put("5308", "普洱"); cityMap.put("5309", "临沧");
-        cityMap.put("5323", "楚雄"); cityMap.put("5325", "红河"); cityMap.put("5326", "文山"); cityMap.put("5328", "西双版纳");
-        cityMap.put("5329", "大理"); cityMap.put("5331", "德宏"); cityMap.put("5333", "怒江"); cityMap.put("5334", "迪庆");
-        // 西藏
-        cityMap.put("5401", "拉萨"); cityMap.put("5402", "日喀则"); cityMap.put("5403", "昌都"); cityMap.put("5404", "林芝");
-        cityMap.put("5405", "山南"); cityMap.put("5406", "那曲"); cityMap.put("5425", "阿里");
-        // 陕西
-        cityMap.put("6101", "西安"); cityMap.put("6102", "铜川"); cityMap.put("6103", "宝鸡"); cityMap.put("6104", "咸阳");
-        cityMap.put("6105", "渭南"); cityMap.put("6106", "延安"); cityMap.put("6107", "汉中"); cityMap.put("6108", "榆林");
+        cityMap.put("5205", "毕节"); cityMap.put("5206", "铜仁");   cityMap.put("5223", "黔西南");
+        cityMap.put("5226", "黔东南"); cityMap.put("5227", "黔南");
+        // 贵州旧地区码
+        cityMap.put("5221", "遵义"); cityMap.put("5222", "铜仁");   cityMap.put("5224", "毕节");
+        cityMap.put("5225", "安顺");
+
+        // ==================== 云南(53) ====================
+        cityMap.put("5300", "昆明"); // 直辖旧码
+        cityMap.put("5301", "昆明"); cityMap.put("5303", "曲靖");   cityMap.put("5304", "玉溪"); cityMap.put("5305", "保山");
+        cityMap.put("5306", "昭通"); cityMap.put("5307", "丽江");   cityMap.put("5308", "普洱"); cityMap.put("5309", "临沧");
+        cityMap.put("5323", "楚雄"); cityMap.put("5325", "红河");   cityMap.put("5326", "文山");
+        cityMap.put("5328", "西双版纳"); cityMap.put("5329", "大理"); cityMap.put("5331", "德宏");
+        cityMap.put("5333", "怒江"); cityMap.put("5334", "迪庆");
+        // 云南旧地区码
+        cityMap.put("5321", "昭通"); cityMap.put("5322", "曲靖");   cityMap.put("5324", "玉溪");
+        cityMap.put("5327", "普洱"); cityMap.put("5330", "保山");   cityMap.put("5332", "临沧");
+
+        // ==================== 西藏(54) ====================
+        cityMap.put("5400", "拉萨"); // 直辖旧码
+        cityMap.put("5401", "拉萨"); cityMap.put("5402", "日喀则"); cityMap.put("5403", "昌都");
+        cityMap.put("5404", "林芝"); cityMap.put("5405", "山南");   cityMap.put("5406", "那曲"); cityMap.put("5425", "阿里");
+        // 西藏旧地区码
+        cityMap.put("5421", "昌都"); cityMap.put("5422", "山南");   cityMap.put("5423", "日喀则");
+        cityMap.put("5424", "那曲"); cityMap.put("5426", "林芝");
+
+        // ==================== 陕西(61) ====================
+        cityMap.put("6100", "西安"); // 直辖旧码
+        cityMap.put("6101", "西安"); cityMap.put("6102", "铜川");   cityMap.put("6103", "宝鸡"); cityMap.put("6104", "咸阳");
+        cityMap.put("6105", "渭南"); cityMap.put("6106", "延安");   cityMap.put("6107", "汉中"); cityMap.put("6108", "榆林");
         cityMap.put("6109", "安康"); cityMap.put("6110", "商洛");
-        // 甘肃
+        // 陕西旧地区码
+        cityMap.put("6121", "渭南"); cityMap.put("6123", "汉中");   cityMap.put("6124", "安康");
+        cityMap.put("6125", "商洛"); cityMap.put("6126", "延安");   cityMap.put("6127", "榆林");
+
+        // ==================== 甘肃(62) ====================
+        cityMap.put("6200", "兰州"); // 直辖旧码
         cityMap.put("6201", "兰州"); cityMap.put("6202", "嘉峪关"); cityMap.put("6203", "金昌"); cityMap.put("6204", "白银");
-        cityMap.put("6205", "天水"); cityMap.put("6206", "武威"); cityMap.put("6207", "张掖"); cityMap.put("6208", "平凉");
-        cityMap.put("6209", "酒泉"); cityMap.put("6210", "庆阳"); cityMap.put("6211", "定西"); cityMap.put("6212", "陇南");
-        // 青海
-        cityMap.put("6301", "西宁"); cityMap.put("6302", "海东"); cityMap.put("6322", "海北"); cityMap.put("6323", "黄南");
-        cityMap.put("6325", "海南"); cityMap.put("6326", "果洛"); cityMap.put("6327", "玉树"); cityMap.put("6328", "海西");
-        // 宁夏
-        cityMap.put("6401", "银川"); cityMap.put("6402", "石嘴山"); cityMap.put("6403", "吴忠"); cityMap.put("6404", "固原"); cityMap.put("6405", "中卫");
-        // 新疆
+        cityMap.put("6205", "天水"); cityMap.put("6206", "武威");   cityMap.put("6207", "张掖"); cityMap.put("6208", "平凉");
+        cityMap.put("6209", "酒泉"); cityMap.put("6210", "庆阳");   cityMap.put("6211", "定西"); cityMap.put("6212", "陇南");
+        // 甘肃旧地区码
+        cityMap.put("6221", "酒泉"); cityMap.put("6222", "张掖");   cityMap.put("6223", "武威");
+        cityMap.put("6224", "定西"); cityMap.put("6225", "天水");   cityMap.put("6226", "陇南");
+        cityMap.put("6227", "平凉"); cityMap.put("6228", "庆阳");   cityMap.put("6229", "临夏");
+
+        // ==================== 青海(63) ====================
+        cityMap.put("6300", "西宁"); // 直辖旧码
+        cityMap.put("6301", "西宁"); cityMap.put("6302", "海东");   cityMap.put("6322", "海北");
+        cityMap.put("6323", "黄南"); cityMap.put("6325", "海南");   cityMap.put("6326", "果洛");
+        cityMap.put("6327", "玉树"); cityMap.put("6328", "海西");
+        // 青海旧地区码
+        cityMap.put("6321", "海东");
+
+        // ==================== 宁夏(64) ====================
+        cityMap.put("6400", "银川"); // 直辖旧码
+        cityMap.put("6401", "银川"); cityMap.put("6402", "石嘴山"); cityMap.put("6403", "吴忠");
+        cityMap.put("6404", "固原"); cityMap.put("6405", "中卫");
+        // 宁夏旧地区码
+        cityMap.put("6421", "吴忠"); cityMap.put("6422", "固原");
+
+        // ==================== 新疆(65) ====================
+        cityMap.put("6500", "乌鲁木齐"); // 直辖旧码
         cityMap.put("6501", "乌鲁木齐"); cityMap.put("6502", "克拉玛依"); cityMap.put("6504", "吐鲁番"); cityMap.put("6505", "哈密");
-        cityMap.put("6523", "昌吉"); cityMap.put("6527", "博尔塔拉"); cityMap.put("6528", "巴音郭楞"); cityMap.put("6529", "阿克苏");
-        cityMap.put("6530", "克孜勒苏"); cityMap.put("6531", "喀什"); cityMap.put("6532", "和田"); cityMap.put("6540", "伊犁");
-        cityMap.put("6542", "塔城"); cityMap.put("6543", "阿勒泰"); cityMap.put("6590", "自治区直辖县级");
-        // 港澳台
+        cityMap.put("6523", "昌吉");     cityMap.put("6527", "博尔塔拉"); cityMap.put("6528", "巴音郭楞");
+        cityMap.put("6529", "阿克苏");   cityMap.put("6530", "克孜勒苏"); cityMap.put("6531", "喀什");
+        cityMap.put("6532", "和田");     cityMap.put("6540", "伊犁");     cityMap.put("6542", "塔城");
+        cityMap.put("6543", "阿勒泰");   cityMap.put("6590", "自治区直辖县级");
+        // 新疆旧地区码
+        cityMap.put("6521", "吐鲁番"); cityMap.put("6522", "哈密");
+
+        // 港澳台(暂不启用)
         // cityMap.put("7100", "台湾"); cityMap.put("8100", "香港"); cityMap.put("8200", "澳门");
         CITY_MAP = Collections.unmodifiableMap(cityMap);
     }

+ 40 - 24
ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/order/TradeOrderMapper.xml

@@ -462,38 +462,54 @@
 
     <select id="getDailyBusinessStatisticsPage"
             resultType="com.yc.ship.module.trade.controller.admin.business.vo.DailyBusinessStatisticsRespVO">
-        SELECT
-            td.source_id AS sourceId,
-            td.source_name as sourceName,
-            IFNULL(tot.free_num, 0) AS freeNum,
-            0 AS freeCount,
-            IFNULL(topay.actual_amount, 0) AS actualAmount,
-            COUNT(1) AS totalCount,
-            SUM(CASE WHEN tv.type IN ('adultPlus', 'adultTake') THEN 1 ELSE 0 END) AS adultCount,
-            SUM(CASE WHEN tv.type IN ('childTake','childPlus','childNonTake') THEN 1 ELSE 0 END) AS childCount,
-            SUM(CASE WHEN tv.type = 'with' THEN 1 ELSE 0 END) AS withCount,
-            SUM(CASE WHEN tv.type = 'leader' THEN 1 ELSE 0 END) AS leaderCount,
-            SUM(CASE WHEN tv.type IN ('babyTake','babyPlus','babyNonTake') THEN 1 ELSE 0 END) AS infantCount,
-            COUNT(1) - IFNULL(tot.free_num, 0) AS validCount
+        select  t.sourceId,
+        t.sourceName,
+        sum(t.freeNum) as freeNum,
+        sum(t.freeCount) as freeCount,
+        sum(t.actualAmount) as actualAmount,
+        sum(t.totalCount) as totalCount,
+        SUM(t.adultCount) AS adultCount,
+        SUM(t.childCount) AS childCount,
+        SUM(t.withCount) AS withCount,
+        SUM(t.leaderCount) AS leaderCount,
+        SUM(t.infantCount) AS infantCount,
+        sum(t.validCount) as validCount from (SELECT
+        DISTINCT td.id,
+        td.source_id AS sourceId,
+        td.source_name as sourceName,
+        IFNULL(tot.free_num, 0) AS freeNum,
+        CASE WHEN td.source_id IN ('2039225917977522177', '2041321150626263042','2042951563081805826') THEN COUNT(1) ELSE 0 END AS freeCount,
+        IFNULL(topay.actual_amount, 0) AS actualAmount,
+        COUNT(1) AS totalCount,
+        SUM(CASE WHEN tv.type IN ('adultPlus', 'adultTake') THEN 1 ELSE 0 END) AS adultCount,
+        SUM(CASE WHEN tv.type IN ('childTake','childPlus','childNonTake') THEN 1 ELSE 0 END) AS childCount,
+        SUM(CASE WHEN tv.type = 'with' THEN 1 ELSE 0 END) AS withCount,
+        SUM(CASE WHEN tv.type = 'leader' THEN 1 ELSE 0 END) AS leaderCount,
+        SUM(CASE WHEN tv.type IN ('babyTake','babyPlus','babyNonTake') THEN 1 ELSE 0 END) AS infantCount,
+        COUNT(1) - IFNULL(tot.free_num, 0) - CASE
+        WHEN td.source_id IN ('2039225917977522177', '2041321150626263042', '2042951563081805826')
+        THEN COUNT(1)
+        ELSE 0
+        END  AS validCount
         FROM trade_order td
-                 INNER JOIN trade_order_user tou ON td.id = tou.order_id AND tou.deleted = 0
-                 LEFT JOIN trade_detail tdl ON td.id = tdl.order_id AND tdl.deleted = 0
-                 LEFT JOIN trade_visitor tv ON tdl.id = tv.detail_id AND tv.deleted = 0
-                 left join trade_order_total tot on td.id = tot.old_order_id and tot.deleted = 0
-                 LEFT JOIN (SELECT order_id, SUM(pay_amount) AS actual_amount
-                            FROM trade_order_pay
-                            WHERE pay_status = 1 GROUP BY order_id) topay ON td.id = topay.order_id
+        INNER JOIN trade_order_user tou ON td.id = tou.order_id AND tou.deleted = 0
+        LEFT JOIN trade_detail tdl ON td.id = tdl.order_id AND tdl.deleted = 0
+        LEFT JOIN trade_visitor tv ON tdl.id = tv.detail_id AND tv.deleted = 0
+        left join trade_order_total tot on td.id = tot.old_order_id and tot.deleted = 0
+        LEFT JOIN (SELECT order_id, SUM(pay_amount) AS actual_amount
+        FROM trade_order_pay
+        WHERE pay_status = 1 and deleted=0 GROUP BY order_id) topay ON td.id = topay.order_id
         WHERE
-            td.deleted = 0
-            AND tv.type IS NOT NULL
-            AND td.order_status > 0
+        td.deleted = 0
+        AND tv.type IS NOT NULL
+        AND td.order_status in (1, 6, 9, 10, 13, 14, 15)
         <if test="vo.voyageId != null and vo.voyageId != ''">
             AND td.voyage_id = #{vo.voyageId}
         </if>
         <if test="vo.routeId != null and vo.routeId != ''">
             AND exists (select 1 from product_voyage top where top.id = td.voyage_id and top.route_id = #{vo.routeId})
         </if>
-        GROUP BY td.source_id;
+        GROUP BY td.id) t GROUP BY t.sourceId;
 
     </select>
 

+ 5 - 5
ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/orderjzdetail/OrderGiftVisitorMapper.xml

@@ -29,13 +29,13 @@
               dj.remark as dispatchRemark,
              gfv.sign_remark as remark
         FROM trade_visitor v
-        LEFT JOIN  trade_order_jz_gift_visitor gfv ON gfv.visitor_id = v.id
+        LEFT JOIN  trade_order_jz_gift_visitor gfv ON gfv.visitor_id = v.id and gfv.deleted = 0
         INNER JOIN trade_order o ON v.order_id = o.id
         INNER JOIN product_voyage pv ON o.voyage_id = pv.id
         inner join trade_detail td on td.order_id = o.id and td.visitor_id = v.id  and td.product_id = 2034458675435925505 and td.deleted = 0
         INNER JOIN resource_route r ON pv.route_id = r.id
-        LEFT JOIN resource_room_model rm ON v.room_model_id = rm.id
-        LEFT JOIN trade_order_jz_dispatch dj ON gfv.dispatch_id = dj.id AND dj.source_type = 2
+        LEFT JOIN resource_room_model rm ON v.room_model_id = rm.id and rm.deleted = 0
+        LEFT JOIN trade_order_jz_dispatch dj ON gfv.dispatch_id = dj.id AND dj.source_type = 2 and dj.deleted = 0
         WHERE v.deleted = 0 AND o.deleted = 0 AND pv.deleted = 0 AND o.order_status IN (15, 14, 13, 10, 12, 9, 8, 7, 6, 5, 4, 3, 1, 0)
         AND o.voyage_id = #{vo.voyageId}
         <if test="vo.idCard != null and vo.idCard != ''">
@@ -92,13 +92,13 @@
             dj.remark as dispatchRemark,
             gfv.sign_remark as remark
         FROM trade_visitor v
-        LEFT JOIN  trade_order_jz_gift_visitor gfv ON gfv.visitor_id = v.id AND v.deleted = 0
+        LEFT JOIN  trade_order_jz_gift_visitor gfv ON gfv.visitor_id = v.id AND v.deleted = 0 and gfv.deleted = 0
         INNER JOIN trade_order o ON v.order_id = o.id AND o.deleted = 0
         inner join trade_detail td on td.order_id = o.id and td.visitor_id = v.id and td.product_id = 2034458675435925505 and td.deleted = 0
         INNER JOIN product_voyage pv ON o.voyage_id = pv.id AND pv.deleted = 0
         INNER JOIN resource_route r ON pv.route_id = r.id
         LEFT JOIN resource_room_model rm ON v.room_model_id = rm.id
-        LEFT JOIN trade_order_jz_dispatch dj ON gfv.dispatch_id = dj.id AND dj.source_type = 2
+        LEFT JOIN trade_order_jz_dispatch dj ON gfv.dispatch_id = dj.id AND dj.source_type = 2 and dj.deleted = 0
         WHERE  o.voyage_id = #{vo.voyageId} AND o.order_status IN (15, 14, 13, 10, 12, 9, 8, 7, 6, 5, 4, 3, 1, 0)
         <if test="vo.idCard != null and vo.idCard != ''">
             AND v.credential_no LIKE CONCAT('%', #{vo.idCard}, '%')

+ 18 - 12
ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/orderjzdetail/OrderJzDetailMapper.xml

@@ -16,13 +16,13 @@
         dj.bus_number as busNumber, dj.driver_name as driverName, dj.driver_phone as driverPhone,
         dj.receiver_name as receiverName, dj.receiver_phone as receiverPhone, d.sign_remark as dispatchRemark
         from trade_order_jz_detail d
-        inner join trade_order_jz oz on d.jz_id = oz.id
+        inner join trade_order_jz oz on d.jz_id = oz.id and oz.deleted = 0
         inner join trade_order o on o.deleted = 0 and o.id = oz.order_id
-        inner join product_voyage v on o.voyage_id = v.id
-        inner join resource_route r on v.route_id = r.id
-        inner join trade_visitor u on u.credential_no = d.id_card and u.order_id = oz.order_id
-        left join resource_room_model rm on u.room_model_id = rm.id
-        left join trade_order_jz_dispatch dj on d.dispatch_id = dj.id
+        inner join product_voyage v on o.voyage_id = v.id and v.deleted = 0
+        inner join resource_route r on v.route_id = r.id and r.deleted = 0
+        inner join trade_visitor u on u.credential_no = d.id_card and u.order_id = oz.order_id and u.deleted = 0
+        left join resource_room_model rm on u.room_model_id = rm.id and rm.deleted = 0
+        left join trade_order_jz_dispatch dj on d.dispatch_id = dj.id and dj.deleted = 0
         inner join trade_detail td on td.order_id = o.id and td.product_id != 2034458675435925505
         where d.deleted = 0 and o.voyage_id = #{vo.voyageId} and o.order_status IN (15, 14, 13, 10, 12, 9, 8, 7, 6, 5, 4, 3, 1, 0)
         <if test="vo.idCard != null">
@@ -40,6 +40,9 @@
         <if test="vo.dispatchNo != null and vo.dispatchNo != ''">
             and dj.dispatch_no like concat('%', #{vo.dispatchNo}, '%')
         </if>
+        <if test="vo.orderNo != null and vo.orderNo != ''">
+            and o.order_no = #{vo.orderNo}
+        </if>
         <if test="vo.signStatus != null and vo.signStatus == 1">
             and d.sign_time is not null
         </if>
@@ -55,13 +58,13 @@
         select distinct d.* , r.direction , rm.name as room_model_name,
         o.order_no as orderNo,o.source_name as sourceName, dj.dispatch_no as dispatchNo, dj.group_no as groupNo, dj.bus_number as busNumber,dj.driver_name as driverName,dj.driver_phone as driverPhone,dj.receiver_name as receiverName,dj.receiver_phone as receiverPhone,d.sign_remark as dispatchRemark
         from trade_order_jz_detail d
-        inner join trade_order_jz oz on d.jz_id = oz.id
+        inner join trade_order_jz oz on d.jz_id = oz.id and oz.deleted = 0
         inner join trade_order o on o.deleted = 0 and o.id = oz.order_id
-        inner join product_voyage v on o.voyage_id = v.id
-        inner join resource_route r on v.route_id = r.id
-        inner join trade_visitor u on u.credential_no = d.id_card and u.order_id = oz.order_id
-        left join resource_room_model rm on u.room_model_id = rm.id
-        left join trade_order_jz_dispatch dj on d.dispatch_id = dj.id
+        inner join product_voyage v on o.voyage_id = v.id and v.deleted = 0
+        inner join resource_route r on v.route_id = r.id and r.deleted = 0
+        inner join trade_visitor u on u.credential_no = d.id_card and u.order_id = oz.order_id and u.deleted = 0
+        left join resource_room_model rm on u.room_model_id = rm.id and rm.deleted = 0
+        left join trade_order_jz_dispatch dj on d.dispatch_id = dj.id and dj.deleted = 0
         inner join trade_detail td on td.order_id = o.id and td.product_id != 2034458675435925505
         where d.deleted = 0 and o.voyage_id = #{vo.voyageId} and o.order_status IN (15, 14, 13, 10, 12, 9, 8, 7, 6, 5, 4, 3, 1, 0)
         <if test="vo.idCard != null and vo.idCard != ''">
@@ -76,6 +79,9 @@
         <if test="vo.arriveTime != null and vo.arriveTime != ''">
             and d.arrive_time = #{vo.arriveTime}
         </if>
+        <if test="vo.orderNo != null and vo.orderNo != ''">
+            and o.order_no = #{vo.orderNo}
+        </if>
         <if test="vo.dispatchNo != null and vo.dispatchNo != ''">
             and dj.dispatch_no like concat('%', #{vo.dispatchNo}, '%')
         </if>

+ 21 - 21
ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/report/DistributorBalanceMapper.xml

@@ -17,7 +17,7 @@
                 d.id,
                 d.name AS travelAgency,
                 IFNULL(recharge.total_recharge, 0) AS totalRechargeAmount,
-                IFNULL(consume.total_consume, 0) AS totalConsumeAmount,
+                IFNULL(topay.actual_amount, 0) AS totalConsumeAmount,
                 IFNULL(d.balance, 0) AS balance
             FROM ota_distributor d
             LEFT JOIN (
@@ -46,36 +46,36 @@
                         sr.distributor_id,
                         SUM(sr.money) AS recharge_amount
                     FROM ota_distributor_self_recharge sr
-                    INNER JOIN ota_trade_log tl ON tl.id = sr.recharge_log_id AND tl.deleted = 0
                     WHERE sr.deleted = 0
                       AND sr.recharge_state = 1
-                      AND tl.trade_type = 1
                     <if test="vo.startDate != null">
-                        AND DATE(tl.trade_time) &gt;= #{vo.startDate}
+                        AND DATE(sr.create_time) &gt;= #{vo.startDate}
                     </if>
                     <if test="vo.endDate != null">
-                        AND DATE(tl.trade_time) &lt;= #{vo.endDate}
+                        AND DATE(sr.create_time) &lt;= #{vo.endDate}
                     </if>
                     GROUP BY sr.distributor_id
                 ) t
                 GROUP BY distributor_id
             ) recharge ON d.id = recharge.distributor_id
-            LEFT JOIN (
-                SELECT 
-                    tl.distributor_id,
-                    SUM(tl.trade_amount) AS total_consume
-                FROM ota_trade_log tl
-                WHERE tl.deleted = 0
-                  AND tl.trade_mode = 2
-                  AND tl.trade_type = 1
-                <if test="vo.startDate != null">
-                    AND DATE(tl.trade_time) &gt;= #{vo.startDate}
-                </if>
-                <if test="vo.endDate != null">
-                    AND DATE(tl.trade_time) &lt;= #{vo.endDate}
-                </if>
-                GROUP BY tl.distributor_id
-            ) consume ON d.id = consume.distributor_id
+
+            LEFT JOIN (SELECT
+                        td.source_id,
+                        SUM( tp.pay_amount ) AS actual_amount
+                    FROM
+                    trade_order_pay tp
+                    LEFT JOIN trade_order td ON td.id = tp.order_id
+                    AND td.deleted = 0
+                    where
+                    tp.pay_status = 1
+                    AND tp.deleted = 0
+                    <if test="vo.startDate != null">
+                        AND DATE(tp.payment_date) &gt;= #{vo.startDate}
+                    </if>
+                    <if test="vo.endDate != null">
+                        AND DATE(tp.payment_date) &lt;= #{vo.endDate}
+                    </if>
+                GROUP BY td.source_id) topay ON d.id = topay.source_id
             WHERE d.deleted = 0
             <if test="vo.travelAgency != null and vo.travelAgency != ''">
                 AND d.name LIKE CONCAT('%', #{vo.travelAgency}, '%')

+ 21 - 5
ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/report/YangtzePassengerSummaryMapper.xml

@@ -25,7 +25,7 @@
             COALESCE(visitor_stats.totalPassengers, 0) AS totalPassengers,
             COALESCE(free_stats.ticketedPassengers, 0) AS ticketedPassengers,
             COALESCE(free_stats.freePassengers, 0) AS freePassengers,
-            COALESCE(room_stats.reservedRooms * 2 + visitor_stats.totalPassengers, 0) AS estimatedPassengers,
+            COALESCE(room_stats.reservedRooms, 0) * 2 + COALESCE(visitor_stats.totalPassengers, 0) AS estimatedPassengers,
             COALESCE(finance_stats.receivableAmount, 0) AS receivableAmount,
             COALESCE(finance_stats.receivedAmount, 0) AS receivedAmount,
             COALESCE(finance_stats.receivableAmount, 0) - COALESCE(finance_stats.receivedAmount, 0) AS unreceivedAmount,
@@ -39,7 +39,9 @@
             COALESCE(team_stats.groupCount, 0) AS groupCount,
             COALESCE(team_stats.individualCount, 0) AS individualCount,
             COALESCE(nationality_stats.domesticCount, 0) AS domesticCount,
-            COALESCE(nationality_stats.overseasCount, 0) AS overseasCount
+            COALESCE(nationality_stats.overseasCount, 0) AS overseasCount,
+            COALESCE(product_stats.upgradeFloorCount, 0) AS upgradeFloorCount,
+            COALESCE(product_stats.suiteDiscountCount, 0) AS suiteDiscountCount
         FROM product_voyage v
         INNER JOIN resource_ship s ON v.ship_id = s.id AND s.deleted = 0
         INNER JOIN resource_route r ON v.route_id = r.id AND r.deleted = 0
@@ -47,9 +49,9 @@
         LEFT JOIN (
         SELECT
         o.voyage_id,
-        COUNT(DISTINCT tot.room_index_id) AS totalRooms,
-        COUNT(DISTINCT CASE WHEN o.order_status != 14 THEN tot.room_index_id END) AS paidRooms,
-        COUNT(DISTINCT CASE WHEN o.order_status = 14 THEN tot.room_index_id END) AS reservedRooms
+        SUM(COALESCE(tot.use_room_num, 0)) AS totalRooms,
+        SUM( CASE WHEN o.order_status != 14 THEN COALESCE(tot.use_room_num, 0)  END) AS paidRooms,
+        SUM( CASE WHEN o.order_status = 14 THEN COALESCE(tot.use_room_num, 0)  END) AS reservedRooms
         FROM trade_order_room_model tot
         INNER JOIN trade_order o ON o.id = tot.order_id
         WHERE o.deleted = 0
@@ -141,6 +143,20 @@
             WHERE o.deleted = 0 AND o.order_status IN (15, 14, 13, 10, 12, 9, 8, 7, 6, 5, 4, 3, 1, 0)
             GROUP BY o.voyage_id
         ) nationality_stats ON v.id = nationality_stats.voyage_id
+
+        LEFT JOIN (
+            SELECT
+                o.voyage_id,
+                COUNT(DISTINCT CASE WHEN td.product_id IN (2007974975358070785, 2012077983546568706, 2012078139826335745) THEN td.id END) AS upgradeFloorCount,
+                COUNT(DISTINCT CASE WHEN td.product_id = 2013782764375310338 THEN td.id END) AS suiteDiscountCount
+            FROM trade_detail td
+            INNER JOIN trade_order o ON o.id = td.order_id AND o.deleted = 0
+            WHERE td.deleted = 0
+              AND o.order_status IN (14, 13, 6, 1)
+              AND td.product_id IN (2007974975358070785, 2012077983546568706, 2012078139826335745, 2013782764375310338)
+            GROUP BY o.voyage_id
+        ) product_stats ON v.id = product_stats.voyage_id
+
         WHERE v.deleted = 0
         <if test="vo.voyageIds != null and vo.voyageIds.size() > 0">
             AND v.id IN