Ver código fonte

Merge branch 'main' of http://47.98.207.247:3000/lsq/ship-ota-server

lishiqiang 2 dias atrás
pai
commit
ae29f1909b
15 arquivos alterados com 311 adições e 128 exclusões
  1. 3 0
      ship-module-ota/ship-module-ota-biz/src/main/java/com/yc/ship/module/ota/dal/mysql/distributor/DistributorMapper.java
  2. 7 0
      ship-module-ota/ship-module-ota-biz/src/main/resources/mapper/distributor/DistributorMapper.xml
  3. 2 1
      ship-module-product/ship-module-product-api/src/main/java/com/yc/ship/module/product/enums/ErrorCodeConstants.java
  4. 1 0
      ship-module-product/ship-module-product-api/src/main/java/com/yc/ship/module/product/enums/ProductStockStaticsName.java
  5. 1 0
      ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/controller/admin/voyagestockdetail/VoyageStockDetailController.java
  6. 4 0
      ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/controller/admin/voyagestockdetail/vo/VoyageStockDetailNewRespVO.java
  7. 47 5
      ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/service/voyagestockdetail/VoyageStockDetailServiceImpl.java
  8. 11 2
      ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/service/voyagestockdistribute/VoyageStockDistributeNewServiceImpl.java
  9. 2 2
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/report/vo/YangtzePassengerSummaryPageReqVO.java
  10. 5 0
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/order/impl/AdminTradeOrderServiceImpl.java
  11. 28 12
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/otc/impl/OtcTradeOrderServiceImpl.java
  12. 92 1
      ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/report/impl/OpsDailyServiceImpl.java
  13. 1 1
      ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/order/TradeOrderMapper.xml
  14. 1 1
      ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/report/AcquisitionDailyMapper.xml
  15. 106 103
      ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/report/YangtzePassengerSummaryMapper.xml

+ 3 - 0
ship-module-ota/ship-module-ota-biz/src/main/java/com/yc/ship/module/ota/dal/mysql/distributor/DistributorMapper.java

@@ -132,4 +132,7 @@ public interface DistributorMapper extends BaseMapperX<DistributorDO> {
     }
     @TenantIgnore
     List<DistributorDO> selectChildNo();
+
+
+    List<DistributorDO> selectListByGroupUserId(@Param("userId") Long userId);
 }

+ 7 - 0
ship-module-ota/ship-module-ota-biz/src/main/resources/mapper/distributor/DistributorMapper.xml

@@ -245,4 +245,11 @@
     <select id="selectChildNo" resultType="com.yc.ship.module.ota.dal.dataobject.distributor.DistributorDO">
         select * from ota_distributor where deleted = 0 and is_use = 1 and cmb_short_no is not null
     </select>
+
+
+    <select id="selectListByGroupUserId" resultType="com.yc.ship.module.ota.dal.dataobject.distributor.DistributorDO">
+        select d.* from ota_distributor d
+                            inner join ota_distributor_category dc on dc.id = d.ota_category_id
+        where d.deleted = 0 and d.is_use = 1   and d.cmb_short_no is not null and dc.deleted  = 0 and dc.user_id = #{userId}
+    </select>
 </mapper>

+ 2 - 1
ship-module-product/ship-module-product-api/src/main/java/com/yc/ship/module/product/enums/ErrorCodeConstants.java

@@ -43,6 +43,7 @@ public interface ErrorCodeConstants {
 
     ErrorCode VOYAGE_NO_STOCK = new ErrorCode(10_028, "该航次未设置库存");
 
+
     ErrorCode VOYAGE_SHARE_STOCK_NOT_ENOUGH = new ErrorCode(10_029, "该航次共享库存不足,库存为:%s,需要库存:%s");
 
     ErrorCode VOYAGE_SHARE_ROOM_NOT_ENOUGH = new ErrorCode(10_030, "共享库存-%s-%sF可售房间不足, 库存为:%s, 需要库存:%s");
@@ -50,7 +51,7 @@ public interface ErrorCodeConstants {
     ErrorCode VOYAGE_DISTRIBUTOR_ROOM_NOT_ENOUGH = new ErrorCode(10_031, "分销商库存-%s-%sF可售房间不足, 库存为:%s, 需要库存: %s");
 
     ErrorCode VOYAGE_ROOM_NOT_EXIST = new ErrorCode(10_032, "订单所选房间%s不存在");
-
+    ErrorCode VOYAGE_STOCK_CHANGED = new ErrorCode(10_033, "该航次库存数有变动,请刷新页面后重新设置");
     ErrorCode VOYAGE_DATA_NOT_EXISTS = new ErrorCode(10_200, "航行数据不存在");
     ErrorCode REDD_DATA_NOT_EXISTS = new ErrorCode(10_201, "碳排放指标管理不存在");
 

+ 1 - 0
ship-module-product/ship-module-product-api/src/main/java/com/yc/ship/module/product/enums/ProductStockStaticsName.java

@@ -9,5 +9,6 @@ public interface ProductStockStaticsName {
     public static final String CAN_SELL_NUM = "可售房间数";
     public static final String OVERSOLD_NUM = "超卖房间数";
     public static final String SURPLUS_NUM = "剩余房间数";
+    public static final String IN_ROOM = "内舱房";
     public static final String TOTAL = "合计";
 }

+ 1 - 0
ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/controller/admin/voyagestockdetail/VoyageStockDetailController.java

@@ -195,6 +195,7 @@ public class VoyageStockDetailController {
         sumDetailNewResp.setCanSellNum(filterList.stream().map(VoyageStockDetailNewRespVO::getCanSellNum).filter(java.util.Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
         sumDetailNewResp.setOversoldNum(filterList.stream().map(VoyageStockDetailNewRespVO::getOversoldNum).filter(java.util.Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
         sumDetailNewResp.setSurplusNum(filterList.stream().map(VoyageStockDetailNewRespVO::getSurplusNum).filter(java.util.Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
+        sumDetailNewResp.setVirtualNumber(filterList.stream().map(VoyageStockDetailNewRespVO::getVirtualNumber).filter(java.util.Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
         sumDetailNewResp.setVirtualNum(filterList.stream().map(VoyageStockDetailNewRespVO::getVirtualNum).filter(java.util.Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
         sumDetailNewResp.setRealTotalNum(filterList.stream().map(VoyageStockDetailNewRespVO::getRealTotalNum).filter(java.util.Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
         sumDetailNewResp.setShareNum(filterList.stream().map(VoyageStockDetailNewRespVO::getShareNum).filter(java.util.Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));

+ 4 - 0
ship-module-product/ship-module-product-biz/src/main/java/com/yc/ship/module/product/controller/admin/voyagestockdetail/vo/VoyageStockDetailNewRespVO.java

@@ -70,6 +70,10 @@ public class VoyageStockDetailNewRespVO {
     /**
      * 虚拟房间数
      */
+    private BigDecimal virtualNumber;
+    /**
+     * 虚拟房间库存数
+     */
     private BigDecimal virtualNum;
 
     /**

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

@@ -38,6 +38,9 @@ import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
+import static com.yc.ship.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static com.yc.ship.module.product.enums.ErrorCodeConstants.VOYAGE_STOCK_CHANGED;
+
 /**
  * 航次库存 Service 实现类
  *
@@ -79,8 +82,32 @@ public class VoyageStockDetailServiceImpl implements VoyageStockDetailService {
         RLock lock = redissonClient.getLock(lockKey);
         try {
             lock.lock(60, TimeUnit.SECONDS);
-            voyageStockDetailMapper.deleteByVoyageId(voyageId);
+            // 获取数据库中现在的库存详情数
+            List<VoyageStockDetailDO> detailDos = voyageStockDetailMapper.selectListByVoyageId(voyageId);
             List<VoyageStockDetailDO> list = BeanUtils.toBean(createReqVO, VoyageStockDetailDO.class);
+            // 现有库存数中的预约数、实际房间数和修改的预约数、实际房间数比较,如果不一致,则返回库存数有变动,请刷新页面后重新设置
+            Map<String, VoyageStockDetailDO> existMap = detailDos.stream()
+                    .collect(Collectors.toMap(
+                            item -> item.getRoomModelId() + "_" + item.getFloor(),
+                            item -> item,
+                            (a, b) -> a));
+            for (VoyageStockDetailDO reqItem : list) {
+                // 排除合计和内舱房
+                if(ProductStockStaticsName.TOTAL.equals(reqItem.getRoomModelName()) || ProductStockStaticsName.IN_ROOM.equals(reqItem.getRoomModelName())) {
+                    continue;
+                }
+                String key = reqItem.getRoomModelId() + "_" + reqItem.getFloor();
+                VoyageStockDetailDO exist = existMap.get(key);
+                if (exist == null) {
+                    // 现有库存里没有该房型楼层,视为已被变动(如被删除/新增)
+                    throw exception(VOYAGE_STOCK_CHANGED);
+                }
+                if (!equalsBigDecimal(exist.getBookNum(), reqItem.getBookNum())
+                        || !equalsBigDecimal(exist.getRealTotalNum(), reqItem.getRealTotalNum())) {
+                    throw exception(VOYAGE_STOCK_CHANGED);
+                }
+            }
+            voyageStockDetailMapper.deleteByVoyageId(voyageId);
             List<VoyageStockDetailDO> insertList = new ArrayList<>();
             list.stream().forEach(item -> {
                 if(ProductStockStaticsName.TOTAL.equals(item.getRoomModelName())) {
@@ -93,10 +120,6 @@ public class VoyageStockDetailServiceImpl implements VoyageStockDetailService {
                 }
             });
             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()){
@@ -105,6 +128,22 @@ public class VoyageStockDetailServiceImpl implements VoyageStockDetailService {
         }
     }
 
+    /**
+     * 比较两个 BigDecimal 是否相等
+     * @param a
+     * @param b
+     * @return
+     */
+    private boolean equalsBigDecimal(BigDecimal a, BigDecimal b) {
+        if (a == null && b == null) {
+            return true;
+        }
+        if (a == null || b == null) {
+            return false;
+        }
+        return a.compareTo(b) == 0;   // 用 compareTo 避免 1.0 与 1.00 被判不等
+    }
+
     /**
      * 处理共享库存
      * @param voyageId
@@ -269,6 +308,9 @@ public class VoyageStockDetailServiceImpl implements VoyageStockDetailService {
                 item.setLockNum(BigDecimal.ZERO);
                 item.setOtherNum(BigDecimal.ZERO);
             }
+            BigDecimal roomNum = item.getRoomNum() == null ? BigDecimal.ZERO : item.getRoomNum();
+            BigDecimal shelfedNum = item.getShelfedNum() == null ? BigDecimal.ZERO : item.getShelfedNum();
+            item.setVirtualNumber(shelfedNum.subtract(roomNum).max(BigDecimal.ZERO));
         });
         return bean;
     }

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

@@ -348,8 +348,17 @@ public class VoyageStockDistributeNewServiceImpl implements VoyageStockDistribut
 //        List<VoyageStockDetailDO> stockDetailDOList1 = stockDetailDOList.stream().filter(item -> item.getCanSellNum().compareTo(BigDecimal.ZERO) > 0).collect(Collectors.toList());
         //减去已分配的库存,剩下的就是共享库存
         List<VoyageStockDistributeNewDO> distributeNewList = voyageStockDistributeNewMapper.selectListByVoyageId(reqVO.getVoyageId(), reqVO.getType());
-        Map<String, BigDecimal> distributeNewMap = CollectionUtils.convertMap(distributeNewList, item -> item.getRoomModelId() + "_" + item.getFloor(), item -> item.getNum());
-
+//        Map<String, BigDecimal> distributeNewMap = CollectionUtils.convertMap(distributeNewList, item -> item.getRoomModelId() + "_" + item.getFloor(), item -> item.getNum());
+        Map<String, List<VoyageStockDistributeNewDO>> groupMap = CollectionUtils.convertMultiMap(
+                distributeNewList,
+                item -> item.getRoomModelId() + "_" + item.getFloor());
+        Map<String, BigDecimal> distributeNewMap = new HashMap<>();
+        groupMap.forEach((key, l) -> {
+            BigDecimal sum = l.stream()
+                    .map(item -> item.getNum() == null ? BigDecimal.ZERO : item.getNum())
+                    .reduce(BigDecimal.ZERO, BigDecimal::add);
+            distributeNewMap.put(key, sum);
+        });
         stockDetailDOList.forEach(item -> {
             BigDecimal bigDecimal = distributeNewMap.get(item.getRoomModelId() + "_" + item.getFloor());
             if (bigDecimal == null) {

+ 2 - 2
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/report/vo/YangtzePassengerSummaryPageReqVO.java

@@ -22,8 +22,8 @@ public class YangtzePassengerSummaryPageReqVO extends PageParam {
     @Schema(description = "航次ID列表(支持多选)", example = "[1,2,3]")
     private List<Long> voyageIds;
 
-    @Schema(description = "游轮ID", example = "1")
-    private Long shipId;
+    @Schema(description = "游轮ID列表(支持多选)", example = "[1,2,3]")
+    private List<Long> shipIds;
 
     @Schema(description = "开始日期", example = "2026-01-01")
     private String startDate;

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

@@ -358,6 +358,11 @@ public class AdminTradeOrderServiceImpl implements AdminTradeOrderService {
     public CommonResult<?> cancelOrder(Long orderId) {
         TradeOrderDO tradeOrderDO = tradeOrderRepositoryService.getById(orderId);
         Asserts.isTrue(tradeOrderDO != null, "订单不存在");
+        Integer orderStatus = tradeOrderDO.getOrderStatus();
+        if(orderStatus!=TradeOrderStatusEnum.UNPAID.getStatus() && orderStatus!=TradeOrderStatusEnum.PAYING.getStatus()
+                && orderStatus!=TradeOrderStatusEnum.YD.getStatus() && orderStatus!=TradeOrderStatusEnum.INIT.getStatus()){
+            return CommonResult.error(100001,"订单状态不支持取消");
+        }
         TradeOrderBindDO tradeOrderBindDO = new TradeOrderBindDO();
         tradeOrderBindDO.setId(IdWorker.getId());
         tradeOrderBindDO.setType(1);

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

@@ -311,6 +311,9 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
     @Resource
     private TradeRefundService tradeRefundService;
 
+    @Resource
+    private TradeOrderBindMapper tradeOrderBindMapper;
+
 
     public final static String AGENCY_LOGIN_INFO = "agencyLoginInfo";
 
@@ -712,7 +715,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
     public void rollbackOrderUserDataVersion(Long orderId){
 
         //首先把旧数据删除
-        tradeOrderUserMapper.delete(TradeOrderUserDO::getOrderId,orderId);
+        tradeOrderUserMapper.updateDeleted(orderId,1);
 
         // ====================== 【关键1】加锁查询:锁住当前版本行 ======================
         // FOR UPDATE 行锁 → 并发请求会排队,绝对安全
@@ -770,8 +773,8 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
     @Transactional(rollbackFor = Exception.class)
     public void rollbackOrderDetailDataVersion(Long orderId){
         //首先把旧数据删除
-        tradeDetailMapper.delete(TradeDetailDO::getOrderId,orderId);
-        
+        tradeDetailMapper.updateDeleted(orderId,1);
+
         // ====================== 【关键1】加锁查询:锁住当前版本行 ======================
         // FOR UPDATE 行锁 → 并发请求会排队,绝对安全
 
@@ -828,8 +831,8 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
     @Transactional(rollbackFor = Exception.class)
     public void rollbackOrderDetailBaseDataVersion(Long orderId){
         //首先把旧数据删除
-        tradeDetailBaseMapper.delete(TradeDetailBaseDO::getOrderId,orderId);
-        
+        tradeDetailBaseMapper.updateDeleted(orderId,1);
+
         // ====================== 【关键1】加锁查询:锁住当前版本行 ======================
         // FOR UPDATE 行锁 → 并发请求会排队,绝对安全
 
@@ -886,7 +889,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
     @Transactional(rollbackFor = Exception.class)
     public void rollbackOrderTotalDataVersion(Long orderId){
         //首先把旧数据删除
-        tradeOrderTotalMapper.delete(TradeOrderTotalDO::getOldOrderId,orderId);
+        tradeOrderTotalMapper.updateDeleted(orderId,1);
 
         // ====================== 【关键1】加锁查询:锁住当前版本行 ======================
         // FOR UPDATE 行锁 → 并发请求会排队,绝对安全
@@ -940,7 +943,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
     @Transactional(rollbackFor = Exception.class)
     public void rollbackOrderCountryDataVersion(Long orderId){
         //首先把旧数据删除
-        tradeOrderCountryMapper.delete(TradeOrderCountryDO::getOrderId,orderId);
+        tradeOrderCountryMapper.updateDeleted(orderId,1);
 
         // ====================== 【关键1】加锁查询:锁住当前版本行 ======================
         // FOR UPDATE 行锁 → 并发请求会排队,绝对安全
@@ -998,7 +1001,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
     @Transactional(rollbackFor = Exception.class)
     public void rollbackOrderRoomModelDataVersion(Long orderId){
         //首先把旧数据删除,26-6-8修改:作逻辑删除,然后回滚时手动设置deleted=0去insertOrUpdate
-        tradeOrderRoomModelMapper.delete(TradeOrderRoomModelDO::getOrderId,orderId);
+        tradeOrderRoomModelMapper.updateDeleted(orderId,1);
 
         // ====================== 【关键1】加锁查询:锁住当前版本行 ======================
         // FOR UPDATE 行锁 → 并发请求会排队,绝对安全
@@ -1056,7 +1059,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
     @Transactional(rollbackFor = Exception.class)
     public void rollbackOrderVisitorDataVersion(Long orderId){
         //首先把旧数据删除
-        tradeVisitorMapper.delete(TradeVisitorDO::getOrderId,orderId);
+        tradeVisitorMapper.updateDeleted(orderId,1);
 
         // ====================== 【关键1】加锁查询:锁住当前版本行 ======================
         // FOR UPDATE 行锁 → 并发请求会排队,绝对安全
@@ -1114,7 +1117,7 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
     @Transactional(rollbackFor = Exception.class)
     public void rollbackOrderFloorDataVersion(Long orderId){
         //首先把旧数据删除
-        tradeOrderFloorMapper.delete(TradeOrderFloorDO::getOrderId,orderId);
+        tradeOrderFloorMapper.updateDeleted(orderId,1);
 
         // ====================== 【关键1】加锁查询:锁住当前版本行 ======================
         // FOR UPDATE 行锁 → 并发请求会排队,绝对安全
@@ -3071,7 +3074,15 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
                 tradeOrderBindDO.setBindId(orderId);
                 tradeOrderBindDO.setCustom("");
                 tradeOrderBindDO.setRemark(desc);
-                shipTradeOrderCreateReqVO.setTradeOrderBindDO(tradeOrderBindDO);
+                tradeOrderBindDO.setOrderId(tradeOrderDO.getId());
+                tradeOrderBindDO.setCreateTime(LocalDateTime.now());
+                tradeOrderBindDO.setCreator(tradeOrderDO.getCreator());
+                tradeOrderBindMapper.update(new LambdaUpdateWrapper<TradeOrderBindDO>()
+                        .set(TradeOrderBindDO::getDeleted, 1)
+                        .set(TradeOrderBindDO::getUpdateTime, tradeOrderBindDO.getUpdateTime()==null?new Date():tradeOrderBindDO.getUpdateTime())
+                        .eq(TradeOrderBindDO::getOrderId, shipTradeOrderCreateReqVO.getOldtradeOrderDO().getId()));
+                tradeOrderBindMapper.insert(tradeOrderBindDO);
+//                shipTradeOrderCreateReqVO.setTradeOrderBindDO(tradeOrderBindDO);
             } else if (oldDeposi.compareTo(newDeposi)!=0) {
                 AuditUserDO auditUserDO = auditUserMapper.selectOne(new LambdaQueryWrapperX<AuditUserDO>().eq(AuditUserDO::getType, 3).eq(AuditUserDO::getAuditStatus, 1).eq(AuditUserDO::getDeleted, 0).orderByDesc(AuditUserDO::getCreateTime).last("limit 1"));
                 tradeOrderMapper.update(new UpdateWrapper<TradeOrderDO>().set("audit_type", 3).set("audit_user", auditUserDO == null ? "" : auditUserDO.getAuditUser()).set("order_status", TradeOrderStatusEnum.TRANSFER.getStatus()).set("audit_status", 1).set("confirm_type",1).eq("id", neworderId));
@@ -3083,7 +3094,12 @@ public class OtcTradeOrderServiceImpl implements OtcTradeOrderService {
                 tradeOrderBindDO.setBindId(orderId);
                 tradeOrderBindDO.setCustom("");
                 tradeOrderBindDO.setRemark(desc);
-                shipTradeOrderCreateReqVO.setTradeOrderBindDO(tradeOrderBindDO);
+                tradeOrderBindMapper.update(new LambdaUpdateWrapper<TradeOrderBindDO>()
+                        .set(TradeOrderBindDO::getDeleted, 1)
+                        .set(TradeOrderBindDO::getUpdateTime, tradeOrderBindDO.getUpdateTime()==null?new Date():tradeOrderBindDO.getUpdateTime())
+                        .eq(TradeOrderBindDO::getOrderId, shipTradeOrderCreateReqVO.getOldtradeOrderDO().getId()));
+                tradeOrderBindMapper.insert(tradeOrderBindDO);
+//                shipTradeOrderCreateReqVO.setTradeOrderBindDO(tradeOrderBindDO);
                 log.error("{}>>>>>>>>>>>>>>>>>>>>>> motifyOtcOrder 进入审核流程2 <<<<<<<<<<<<<<<<<<<<<<" + 3 + ";{}", orderNo, auditUserDO == null ? "" : auditUserDO.getAuditUser());
             }
 //            if(Objects.equals(TradeOrderStatusEnum.TRANSFER.getStatus(), neworderStatus)) {

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

@@ -633,6 +633,10 @@ public class OpsDailyServiceImpl implements OpsDailyService {
         // 转换数据
         List<List<Object>> exportData = transformExportData2(dataList);
 
+        // 追加合计行
+        List<Object> summaryRow = buildSummaryRow2(dataList);
+        exportData.add(summaryRow);
+
         // 先设置响应头
         response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
         response.addHeader("Content-Disposition",
@@ -823,8 +827,95 @@ public class OpsDailyServiceImpl implements OpsDailyService {
     }
 
     /**
-     * 格式化金额为 BigDecimal(保留2位小数),直接输出数字类型避免 Excel 文本格式
+     * 构建合计行(用于导出Excel末尾)
      */
+    private List<Object> buildSummaryRow2(List<YangtzePassengerSummaryRespVO> dataList) {
+        List<Object> row = new ArrayList<>(30);
+        // 序号、船舶、航次、倒计时 — 合计行留空或标注
+        row.add("");
+        row.add("");
+        row.add("合计");
+        row.add("");
+        // 预订间数 - 汇总
+        row.add(sumBigDecimal(dataList, YangtzePassengerSummaryRespVO::getTotalRooms));
+        row.add(sumBigDecimal(dataList, YangtzePassengerSummaryRespVO::getPaidRooms));
+        row.add(sumBigDecimal(dataList, YangtzePassengerSummaryRespVO::getReservedRooms));
+        // 实收人数 - 汇总
+        row.add(sumInt(dataList, YangtzePassengerSummaryRespVO::getTotalPassengers));
+        row.add(sumInt(dataList, YangtzePassengerSummaryRespVO::getTicketedPassengers));
+        row.add(sumInt(dataList, YangtzePassengerSummaryRespVO::getFreePassengers));
+        // 预计收客人数 - 汇总
+        row.add(sumInt(dataList, YangtzePassengerSummaryRespVO::getEstimatedPassengers));
+        // 备注 - 留空
+        row.add("");
+        // 财务情况 - 汇总
+        BigDecimal totalReceivable = sumBigDecimal(dataList, YangtzePassengerSummaryRespVO::getReceivableAmount);
+        BigDecimal totalReceived = sumBigDecimal(dataList, YangtzePassengerSummaryRespVO::getReceivedAmount);
+        BigDecimal totalUnreceived = sumBigDecimal(dataList, YangtzePassengerSummaryRespVO::getUnreceivedAmount);
+        row.add(formatMoney(totalReceivable));
+        row.add(formatMoney(totalReceived));
+        row.add(formatMoney(totalUnreceived));
+        // 均价 = 总应收 / 总购票人数
+        int totalTicketed = sumInt(dataList, YangtzePassengerSummaryRespVO::getTicketedPassengers);
+        if (totalTicketed > 0) {
+            row.add(formatMoney(totalReceivable.divide(BigDecimal.valueOf(totalTicketed), 2, RoundingMode.HALF_UP)));
+        } else {
+            row.add(formatMoney(BigDecimal.ZERO));
+        }
+        // 床位载客率 - 客容量汇总,载客率 = 总实收人数 / 总客容量
+        int totalCapacity = sumInt(dataList, YangtzePassengerSummaryRespVO::getCapacity);
+        int totalPassengers = sumInt(dataList, YangtzePassengerSummaryRespVO::getTotalPassengers);
+        row.add(totalCapacity);
+        if (totalCapacity > 0) {
+            double rate = (double) totalPassengers / totalCapacity * 100;
+            row.add(String.format("%.1f%%", rate));
+        } else {
+            row.add("-");
+        }
+        // 实收详情 - 汇总
+        row.add(sumInt(dataList, YangtzePassengerSummaryRespVO::getAdultCount));
+        row.add(sumInt(dataList, YangtzePassengerSummaryRespVO::getChildCount));
+        row.add(sumInt(dataList, YangtzePassengerSummaryRespVO::getInfantCount));
+        row.add(sumInt(dataList, YangtzePassengerSummaryRespVO::getCompanionLeaderCount));
+        // 团队情况 - 汇总
+        int totalGroup = sumInt(dataList, YangtzePassengerSummaryRespVO::getGroupCount);
+        int totalIndividual = sumInt(dataList, YangtzePassengerSummaryRespVO::getIndividualCount);
+        row.add(totalGroup);
+        row.add(totalIndividual);
+        row.add(calcRatio(totalGroup, totalIndividual));
+        int totalDomestic = sumInt(dataList, YangtzePassengerSummaryRespVO::getDomesticCount);
+        int totalOverseas = sumInt(dataList, YangtzePassengerSummaryRespVO::getOverseasCount);
+        row.add(totalDomestic);
+        row.add(totalOverseas);
+        row.add(calcRatio(totalDomestic, totalOverseas));
+        // 升楼层礼遇、套房95折 - 汇总
+        row.add(sumInt(dataList, YangtzePassengerSummaryRespVO::getUpgradeFloorCount));
+        row.add(sumInt(dataList, YangtzePassengerSummaryRespVO::getSuiteDiscountCount));
+        return row;
+    }
+
+    /**
+     * 求和 - Integer 字段
+     */
+    private int sumInt(List<YangtzePassengerSummaryRespVO> dataList,
+                       java.util.function.Function<YangtzePassengerSummaryRespVO, Integer> getter) {
+        return dataList.stream()
+                .map(getter)
+                .filter(java.util.Objects::nonNull)
+                .mapToInt(Integer::intValue)
+                .sum();
+    }
+
+    /**
+     * 求和 - BigDecimal 字段
+     */
+    private BigDecimal sumBigDecimal(List<YangtzePassengerSummaryRespVO> dataList,
+                                     java.util.function.Function<YangtzePassengerSummaryRespVO, BigDecimal> getter) {
+        return dataList.stream()
+                .map(getter)
+                .filter(java.util.Objects::nonNull)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+    }
     private BigDecimal formatMoney(BigDecimal amount) {
         if (amount == null) return BigDecimal.ZERO;
         return amount.setScale(2, RoundingMode.HALF_UP);

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

@@ -2866,7 +2866,7 @@
                 left join trade_visitor t5 on t1.id = t5.order_id
         WHERE
             t1.deleted = 0
-          and t1.order_status in (3,6,7,8,9)
+          and t1.order_status in (15, 14, 13, 10, 12, 9, 8, 7, 6, 5, 4, 3, 1, 0)
           <choose>
               <when test="keyword != null and keyword != ''">
                   and (t5.mobile = #{keyword} or t5.credential_no = #{keyword})

+ 1 - 1
ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/report/AcquisitionDailyMapper.xml

@@ -21,7 +21,7 @@
         LEFT JOIN trade_visitor tv ON td.id = tv.order_id AND tv.deleted=0
         WHERE
         td.deleted = 0
-        AND td.order_status > 0
+        AND td.order_status in (1, 6, 9, 10, 13, 14, 15)
         <if test="vo.startDate != null and vo.startDate != ''">
             AND v.boarding_time &gt;= #{vo.startDate}
         </if>

+ 106 - 103
ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/report/YangtzePassengerSummaryMapper.xml

@@ -13,35 +13,35 @@
     -->
     <sql id="selectYangtzePassengerSummary">
         SELECT
-            v.id AS voyageId,
-            s.name AS shipName,
-            CONCAT(DATE_FORMAT(v.boarding_time, '%Y-%m-%d'), ' ', CASE WHEN r.direction = 1 THEN '上水' ELSE '下水' END) AS voyageInfo,
-            GREATEST(DATEDIFF(DATE(v.boarding_time), CURDATE()), 0) AS countdown,
-            v.remark AS remark,
-            528 AS capacity,
-            COALESCE(room_stats.totalRooms, 0) AS totalRooms,
-            COALESCE(room_stats.paidRooms, 0) AS paidRooms,
-            COALESCE(room_stats.reservedRooms, 0) AS reservedRooms,
-            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, 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,
-            CASE WHEN COALESCE(free_stats.ticketedPassengers, 0) > 0
-                 THEN ROUND(COALESCE(finance_stats.receivableAmount, 0) / free_stats.ticketedPassengers, 2)
-                 ELSE 0 END AS avgPrice,
-            COALESCE(visitor_stats.adultCount, 0) AS adultCount,
-            COALESCE(visitor_stats.childCount, 0) AS childCount,
-            COALESCE(visitor_stats.infantCount, 0) AS infantCount,
-            COALESCE(visitor_stats.companionLeaderCount, 0) AS companionLeaderCount,
-            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(product_stats.upgradeFloorCount, 0) AS upgradeFloorCount,
-            COALESCE(product_stats.suiteDiscountCount, 0) AS suiteDiscountCount
+        v.id AS voyageId,
+        s.name AS shipName,
+        CONCAT(DATE_FORMAT(v.boarding_time, '%Y-%m-%d'), ' ', CASE WHEN r.direction = 1 THEN '上水' ELSE '下水' END) AS voyageInfo,
+        GREATEST(DATEDIFF(DATE(v.boarding_time), CURDATE()), 0) AS countdown,
+        v.remark AS remark,
+        528 AS capacity,
+        COALESCE(room_stats.totalRooms, 0) AS totalRooms,
+        COALESCE(room_stats.paidRooms, 0) AS paidRooms,
+        COALESCE(room_stats.reservedRooms, 0) AS reservedRooms,
+        COALESCE(visitor_stats.totalPassengers, 0) AS totalPassengers,
+        COALESCE(free_stats.ticketedPassengers, 0) AS ticketedPassengers,
+        COALESCE(free_stats.freePassengers, 0) AS freePassengers,
+        COALESCE(estimated_stats.estimatedPassengers1, 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,
+        CASE WHEN COALESCE(free_stats.ticketedPassengers, 0) > 0
+        THEN ROUND(COALESCE(finance_stats.receivableAmount, 0) / free_stats.ticketedPassengers, 2)
+        ELSE 0 END AS avgPrice,
+        COALESCE(visitor_stats.adultCount, 0) AS adultCount,
+        COALESCE(visitor_stats.childCount, 0) AS childCount,
+        COALESCE(visitor_stats.infantCount, 0) AS infantCount,
+        COALESCE(visitor_stats.companionLeaderCount, 0) AS companionLeaderCount,
+        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(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
@@ -61,100 +61,100 @@
         ) room_stats ON v.id = room_stats.voyage_id
 
         LEFT JOIN (
-            SELECT
-                o.voyage_id,
-                COUNT(tv.id) AS totalPassengers,
-                SUM(CASE WHEN o.order_status IN (6) THEN 1 ELSE 0 END) AS ticketedPassengers1,
-                SUM(CASE WHEN tv.type IN ('adultTake', 'adultPlus') 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 IN ('babyTake', 'babyPlus', 'babyNonTake') THEN 1 ELSE 0 END) AS infantCount,
-                SUM(CASE WHEN tv.type IN ('with', 'leader') THEN 1 ELSE 0 END) AS companionLeaderCount
-            FROM trade_order o
-            INNER JOIN trade_visitor tv ON o.id = tv.order_id AND tv.deleted = 0
+        SELECT
+        o.voyage_id,
+        COUNT(tv.id) AS totalPassengers,
+        SUM(CASE WHEN o.order_status IN (6) THEN 1 ELSE 0 END) AS ticketedPassengers1,
+        SUM(CASE WHEN tv.type IN ('adultTake', 'adultPlus') 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 IN ('babyTake', 'babyPlus', 'babyNonTake') THEN 1 ELSE 0 END) AS infantCount,
+        SUM(CASE WHEN tv.type IN ('with', 'leader') THEN 1 ELSE 0 END) AS companionLeaderCount
+        FROM trade_order o
+        INNER JOIN trade_visitor tv ON o.id = tv.order_id AND tv.deleted = 0
         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
+        GROUP BY o.voyage_id
         ) visitor_stats ON v.id = visitor_stats.voyage_id
 
         LEFT JOIN (
-            SELECT
-                o.voyage_id,
-                CASE WHEN o.order_status = 1 or o.order_status = 14 THEN SUM(COALESCE(tot.adult_total_num, 0) + COALESCE(tot.child_total_num, 0)
-                    + COALESCE(tot.baby_total_num, 0) + COALESCE(tot.with_total_num, 0)
-                    + COALESCE(tot.leader_total_num, 0)) ELSE 0 END AS estimatedPassengers1
-            FROM trade_order o
-            LEFT JOIN trade_order_total tot ON o.id = tot.old_order_id AND tot.deleted = 0
+        SELECT
+        o.voyage_id,
+        SUM(COALESCE(tot.adult_total_num, 0) + COALESCE(tot.child_total_num, 0)
+        + COALESCE(tot.baby_total_num, 0) + COALESCE(tot.with_total_num, 0)
+        + COALESCE(tot.leader_total_num, 0))  AS estimatedPassengers1
+        FROM trade_order o
+        LEFT JOIN trade_order_total tot ON o.id = tot.old_order_id AND tot.deleted = 0
         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
+        GROUP BY o.voyage_id
         ) estimated_stats ON v.id = estimated_stats.voyage_id
 
         LEFT JOIN (
-            SELECT
-                o.voyage_id,
-                SUM(CASE WHEN od.name LIKE '免票%' THEN 1 ELSE 0 END) AS freePassengers,
-                 SUM(CASE WHEN od.name LIKE '免票%' THEN 0 ELSE 1 END) AS ticketedPassengers,
-                SUM(CASE WHEN (o.order_status = 1 or o.order_status = 14) and tv.type IN ('babyTake','babyPlus','babyNonTake','leader','with','childTake','childPlus','childNonTake','adultPlus', 'adultTake') THEN 1 ELSE 0 END) AS estimatedPassengers
+        SELECT
+        o.voyage_id,
+        SUM(CASE WHEN od.name LIKE '免票%' THEN 1 ELSE 0 END) AS freePassengers,
+        SUM(CASE WHEN od.name LIKE '免票%' THEN 0 ELSE 1 END) AS ticketedPassengers,
+        SUM(CASE WHEN (o.order_status = 1 or o.order_status = 14) and tv.type IN ('babyTake','babyPlus','babyNonTake','leader','with','childTake','childPlus','childNonTake','adultPlus', 'adultTake') THEN 1 ELSE 0 END) AS estimatedPassengers
         FROM trade_order o
-            INNER JOIN trade_visitor tv ON o.id = tv.order_id AND tv.deleted = 0
-            LEFT JOIN ota_distributor od ON o.source_id = od.id AND od.deleted = 0
+        INNER JOIN trade_visitor tv ON o.id = tv.order_id AND tv.deleted = 0
+        LEFT JOIN ota_distributor od ON o.source_id = od.id AND od.deleted = 0
         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
+        GROUP BY o.voyage_id
         ) free_stats ON v.id = free_stats.voyage_id
 
         LEFT JOIN (
-            SELECT
-                o.voyage_id,
-                SUM(o.pay_amount) AS receivableAmount,
-                SUM(CASE WHEN o.pay_status = 1 THEN COALESCE(p.pay_amount, o.real_pay_amount)
-                         ELSE COALESCE(o.real_pay_amount, 0) END) AS receivedAmount
-            FROM trade_order o
-            LEFT JOIN (
-                SELECT order_id, SUM(pay_amount) AS pay_amount
-                FROM trade_order_pay
-                WHERE deleted = 0 AND pay_status = 1
-                GROUP BY order_id
-            ) p ON o.pay_status = 1 AND p.order_id = o.id
-            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
+        SELECT
+        o.voyage_id,
+        SUM(o.pay_amount) AS receivableAmount,
+        SUM(CASE WHEN o.pay_status = 1 THEN COALESCE(p.pay_amount, o.real_pay_amount)
+        ELSE COALESCE(o.real_pay_amount, 0) END) AS receivedAmount
+        FROM trade_order o
+        LEFT JOIN (
+        SELECT order_id, SUM(pay_amount) AS pay_amount
+        FROM trade_order_pay
+        WHERE deleted = 0 AND pay_status = 1
+        GROUP BY order_id
+        ) p ON o.pay_status = 1 AND p.order_id = o.id
+        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
         ) finance_stats ON v.id = finance_stats.voyage_id
 
         LEFT JOIN (
-            SELECT
-                o.voyage_id,
-                SUM(CASE WHEN visitor_cnt.cnt >= 10 THEN visitor_cnt.cnt ELSE 0 END) AS groupCount,
-                SUM(CASE WHEN visitor_cnt.cnt &lt; 10 THEN visitor_cnt.cnt ELSE 0 END) AS individualCount
-            FROM trade_order o
-            INNER JOIN (
-                SELECT order_id, COUNT(id) AS cnt
-                FROM trade_visitor
-                WHERE deleted = 0
-                GROUP BY order_id
-            ) visitor_cnt ON o.id = visitor_cnt.order_id
-            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
+        SELECT
+        o.voyage_id,
+        SUM(CASE WHEN visitor_cnt.cnt >= 10 THEN visitor_cnt.cnt ELSE 0 END) AS groupCount,
+        SUM(CASE WHEN visitor_cnt.cnt &lt; 10 THEN visitor_cnt.cnt ELSE 0 END) AS individualCount
+        FROM trade_order o
+        INNER JOIN (
+        SELECT order_id, COUNT(id) AS cnt
+        FROM trade_visitor
+        WHERE deleted = 0
+        GROUP BY order_id
+        ) visitor_cnt ON o.id = visitor_cnt.order_id
+        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
         ) team_stats ON v.id = team_stats.voyage_id
 
         LEFT JOIN (
-            SELECT
-                o.voyage_id,
-                SUM(CASE WHEN tv.nationality = '1' OR tv.nationality IS NULL OR tv.nationality = '' THEN 1 ELSE 0 END) AS domesticCount,
-                SUM(CASE WHEN tv.nationality != '1' AND tv.nationality IS NOT NULL AND tv.nationality != '' THEN 1 ELSE 0 END) AS overseasCount
-            FROM trade_order o
-            INNER JOIN trade_visitor tv ON o.id = tv.order_id AND tv.deleted = 0
-            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
+        SELECT
+        o.voyage_id,
+        SUM(CASE WHEN tv.nationality = '1' OR tv.nationality IS NULL OR tv.nationality = '' THEN 1 ELSE 0 END) AS domesticCount,
+        SUM(CASE WHEN tv.nationality != '1' AND tv.nationality IS NOT NULL AND tv.nationality != '' THEN 1 ELSE 0 END) AS overseasCount
+        FROM trade_order o
+        INNER JOIN trade_visitor tv ON o.id = tv.order_id AND tv.deleted = 0
+        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
+        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
@@ -164,8 +164,11 @@
                 #{id}
             </foreach>
         </if>
-        <if test="vo.shipId != null">
-            AND v.ship_id = #{vo.shipId}
+        <if test="vo.shipIds != null and vo.shipIds.size() > 0">
+            AND v.ship_id IN
+            <foreach collection="vo.shipIds" item="shipId" open="(" separator="," close=")">
+                #{shipId}
+            </foreach>
         </if>
         <if test="vo.startDate != null and vo.startDate != ''">
             AND v.start_time &gt;= #{vo.startDate}