瀏覽代碼

省际度假游轮房型分布日报表

caotao 1 月之前
父節點
當前提交
641ac93073

+ 26 - 0
ship-module-resource/ship-module-resource-biz/src/main/java/com/yc/ship/module/resource/dal/mysql/room/ResourceRoomMapper.java

@@ -12,6 +12,7 @@ import org.apache.ibatis.annotations.Param;
 import org.apache.ibatis.annotations.Select;
 
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 /**
@@ -71,4 +72,29 @@ public interface ResourceRoomMapper extends BaseMapperX<ResourceRoomDO> {
 
     List<ResourceRoomRespVO> selectAllByShipIdAndFloors(@Param("shipId") String shipId, @Param("voyageId") String voyageId, @Param("floors") String floors);
 
+    /**
+     * 获取省际度假游轮房型分布日报表表头
+     * @param shipId
+     * @return
+     */
+    @Select("SELECT" +
+            " t1.room_model_id AS roomModelId," +
+            " count( t1.id ) AS num," +
+            " t2.sort," +
+            " t2.NAME AS room_model_name," +
+            " t2.short_name AS shortName " +
+            " FROM" +
+            " resource_room t1" +
+            " LEFT JOIN resource_room_model t2 ON t1.room_model_id = t2.id " +
+            " WHERE" +
+            " t1.ship_id = #{shipId} " +
+            " AND t1.deleted = 0 " +
+            " AND t2.deleted = 0 " +
+            " GROUP BY" +
+            " t1.room_model_id" +
+            " ORDER BY" +
+            " CASE WHEN t2.sort = 999 THEN 0 ELSE 1 END ASC," +
+            " t2.sort ASC," +
+            " t2.id ASC")
+    List<Map<String, Object>> getCabinMixDailyTableHeadList(@Param("shipId")Long shipId);
 }

+ 63 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/report/CabinMixDailyController.java

@@ -0,0 +1,63 @@
+package com.yc.ship.module.trade.controller.admin.report;
+
+import com.yc.ship.framework.apilog.core.annotation.ApiAccessLog;
+import com.yc.ship.framework.common.pojo.CommonResult;
+import com.yc.ship.module.trade.controller.admin.report.vo.CabinMixDailyReqVO;
+import com.yc.ship.module.trade.controller.admin.report.vo.ConsumptionIncomeDailyReqVO;
+import com.yc.ship.module.trade.controller.admin.report.vo.CruiseOpsDailyRespVO;
+import com.yc.ship.module.trade.service.report.CabinMixDailyService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+import static com.yc.ship.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static com.yc.ship.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "管理后台 - 省际度假游轮房型分布日报表")
+@RestController
+@RequestMapping("/report/interprovincial/cabin-mix-daily")
+@Validated
+@Slf4j
+public class CabinMixDailyController {
+    @Resource
+    private CabinMixDailyService cabinMixDailyService;
+    // ==================== 省际度假游轮房型分布日报表 ====================
+    /**
+     * 查询省际度假游轮房型分布表头
+     */
+    @GetMapping("/getTableHead")
+    @Operation(summary = "查询省际度假游轮房型分布表头")
+    public CommonResult<List<Map<String, Object>>> getTableHeadList(@RequestParam("shipId") Long shipId) {
+        List<Map<String, Object>> headList = cabinMixDailyService.getTableHeadList(shipId);
+        return success(headList);
+    }
+
+    /**
+     * 查询省际度假游轮房型分布日报表
+     */
+    @GetMapping("/list")
+    @Operation(summary = "查询省际度假游轮房型分布日报表")
+    @ResponseBody
+    public CommonResult<List<Map<String, Object>>> list(@Validated CabinMixDailyReqVO reqVO) {
+        List<Map<String, Object>> list = cabinMixDailyService.getList(reqVO);
+        return success(list);
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出省际度假游轮房型分布日报表 Excel")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportCabinMixDaily(@Valid CabinMixDailyReqVO reqVO,
+                                                  HttpServletResponse response) throws IOException {
+        cabinMixDailyService.exportCabinMixDaily(reqVO, response);
+    }
+
+}

+ 17 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/controller/admin/report/vo/CabinMixDailyReqVO.java

@@ -0,0 +1,17 @@
+package com.yc.ship.module.trade.controller.admin.report.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.ToString;
+
+@Schema(description = "管理后台 - 省际度假游轮房型分布日报表 Request VO")
+@Data
+@ToString(callSuper = true)
+public class CabinMixDailyReqVO {
+    @Schema(description = "开始日期", example = "2026-01-01")
+    private String startDate;
+    @Schema(description = "结束日期", example = "2026-03-31")
+    private String endDate;
+    @Schema(description = "游轮ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long shipId;
+}

+ 13 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/dal/mysql/report/CabinMixDailyMapper.java

@@ -0,0 +1,13 @@
+package com.yc.ship.module.trade.dal.mysql.report;
+
+import com.yc.ship.module.trade.controller.admin.report.vo.CabinMixDailyReqVO;
+import com.yc.ship.module.trade.controller.admin.report.vo.CruiseOpsDailyRespVO;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+import java.util.Map;
+
+@Mapper
+public interface CabinMixDailyMapper {
+    List<Map<String,Object>> getList(@Param("vo") CabinMixDailyReqVO reqVO, @Param("roomColumns") String roomColumns);}

+ 17 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/report/CabinMixDailyService.java

@@ -0,0 +1,17 @@
+package com.yc.ship.module.trade.service.report;
+
+import com.yc.ship.module.trade.controller.admin.report.vo.CabinMixDailyReqVO;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+public interface CabinMixDailyService {
+    List<Map<String, Object>> getTableHeadList(Long shipId);
+
+    List<Map<String, Object>> getList(CabinMixDailyReqVO reqVO);
+
+    void exportCabinMixDaily(@Valid CabinMixDailyReqVO reqVO, HttpServletResponse response)throws IOException;
+}

+ 457 - 0
ship-module-trade/ship-module-trade-biz/src/main/java/com/yc/ship/module/trade/service/report/impl/CabinMixDailyServiceImpl.java

@@ -0,0 +1,457 @@
+package com.yc.ship.module.trade.service.report.impl;
+
+import com.alibaba.excel.EasyExcel;
+import com.alibaba.excel.converters.longconverter.LongStringConverter;
+import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
+import com.yc.ship.module.resource.dal.mysql.room.ResourceRoomMapper;
+import com.yc.ship.module.resource.helper.DateHelper;
+import com.yc.ship.module.trade.controller.admin.report.vo.CabinMixDailyReqVO;
+import com.yc.ship.module.trade.controller.admin.report.vo.ConsumptionIncomeDailyReqVO;
+import com.yc.ship.module.trade.dal.mysql.report.CabinMixDailyMapper;
+import com.yc.ship.module.trade.service.report.CabinMixDailyService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.io.IOException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+@Slf4j
+public class CabinMixDailyServiceImpl implements CabinMixDailyService {
+    @Resource
+    private ResourceRoomMapper resourceRoomMapper;
+    @Resource
+    private CabinMixDailyMapper cabinMixDailyMapper;
+
+    @Override
+    public List<Map<String, Object>> getTableHeadList(Long shipId) {
+        List<Map<String, Object>> list = resourceRoomMapper.getCabinMixDailyTableHeadList(shipId);
+        return list;
+    }
+
+    @Override
+    public List<Map<String, Object>> getList(CabinMixDailyReqVO reqVO) {
+        if (reqVO.getShipId() == null) {
+            return Collections.emptyList();
+        }
+
+        List<Map<String, Object>> roomTypeList = resourceRoomMapper.getCabinMixDailyTableHeadList(reqVO.getShipId());
+        if (roomTypeList == null || roomTypeList.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        List<Map<String,Object>> currentDataList = cabinMixDailyMapper.getList(reqVO, buildRoomColumns(roomTypeList));
+
+        if (currentDataList == null || currentDataList.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        List<Map<String, Object>> lastYearDataList = queryLastYearData(reqVO, roomTypeList);
+
+        return processCabinMixData(currentDataList, lastYearDataList, roomTypeList);
+    }
+
+
+    private List<Map<String, Object>> queryLastYearData(CabinMixDailyReqVO reqVO, List<Map<String, Object>> roomTypeList) {
+        try {
+            String startDate = reqVO.getStartDate();
+            String endDate = reqVO.getEndDate();
+
+            if (startDate == null || startDate.isEmpty()) {
+                return new ArrayList<>();
+            }
+
+            Date startD = DateHelper.parseDate(startDate);
+            Date endD = endDate != null && !endDate.isEmpty() ? DateHelper.parseDate(endDate) : null;
+
+            CabinMixDailyReqVO lastYearReq = new CabinMixDailyReqVO();
+            lastYearReq.setShipId(reqVO.getShipId());
+            lastYearReq.setStartDate(DateHelper.getAppointYearStartDate(startD));
+
+            if (endD != null) {
+                Calendar cal = Calendar.getInstance();
+                cal.setTime(startD);
+                int daysDiff = (int) ((endD.getTime() - startD.getTime()) / (1000 * 60 * 60 * 24));
+                cal.add(Calendar.YEAR, -1);
+                cal.add(Calendar.DATE, daysDiff);
+                lastYearReq.setEndDate(DateHelper.format(cal.getTime(), "yyyy-MM-dd"));
+            } else {
+                lastYearReq.setEndDate(DateHelper.format(new Date(), "yyyy-MM-dd"));
+            }
+
+            return cabinMixDailyMapper.getList(lastYearReq, buildRoomColumns(roomTypeList));
+        } catch (Exception e) {
+            log.warn("[queryLastYearData] 查询去年同期数据失败: {}", e.getMessage());
+            return new ArrayList<>();
+        }
+    }
+
+    private String buildRoomColumns(List<Map<String, Object>> roomTypeList) {
+        StringBuilder columns = new StringBuilder();
+        for (int i = 0; i < roomTypeList.size(); i++) {
+            Map<String, Object> roomType = roomTypeList.get(i);
+            String roomModelName = roomType.get("room_model_name").toString();
+            Long roomModelId = Long.valueOf(roomType.get("roomModelId").toString());
+
+            String columnSql;
+            if (roomModelName.contains("内舱")) {
+                columnSql = String.format(
+                        "COALESCE(voyage_visitor.total_visitor / 2, 0) AS `%s`",
+                        roomModelName
+                );
+            } else {
+                columnSql = String.format(
+                        "COALESCE(SUM(CASE WHEN torm.room_model_id = %s THEN torm.use_room_num ELSE 0 END), 0) AS `%s`",
+                        roomModelId, roomModelName
+                );
+            }
+
+            columns.append(columnSql);
+
+            if (i < roomTypeList.size() - 1) {
+                columns.append(", ");
+            }
+        }
+        return columns.toString();
+    }
+
+    private List<Map<String, Object>> processCabinMixData(
+            List<Map<String, Object>> currentDataList,
+            List<Map<String, Object>> lastYearDataList,
+            List<Map<String, Object>> roomTypeList) {
+
+        List<Map<String, Object>> result = new ArrayList<>();
+
+        Map<String, List<Map<String, Object>>> currentMonthMap = currentDataList.stream()
+                .collect(Collectors.groupingBy(
+                        item -> String.valueOf(item.get("month")),
+                        TreeMap::new, Collectors.toList()));
+
+        Map<String, List<Map<String, Object>>> lastYearMonthMap = lastYearDataList.stream()
+                .collect(Collectors.groupingBy(
+                        item -> String.valueOf(item.get("month")),
+                        TreeMap::new, Collectors.toList()));
+
+        int index = 1;
+        Map<String, Double> cumulativeRoomCountMap = new LinkedHashMap<>();
+        Map<String, Double> lastYearCumulativeMap = new LinkedHashMap<>();
+        boolean isFirstMonth = true;
+
+        List<String> roomModelNames = roomTypeList.stream()
+                .map(room -> room.get("room_model_name").toString())
+                .collect(Collectors.toList());
+
+        for (Map.Entry<String, List<Map<String, Object>>> entry : currentMonthMap.entrySet()) {
+            String month = entry.getKey();
+            List<Map<String, Object>> dailyItems = entry.getValue();
+
+            for (Map<String, Object> data : dailyItems) {
+                Map<String, Object> row = createBasicRow(data, index++, roomModelNames);
+                row.put("month", formatMonth(month));
+                row.put("isSubtotal", false);
+                row.put("isCumulative", false);
+                result.add(row);
+
+                accumulateSingleRoomCount(cumulativeRoomCountMap, data, roomModelNames);
+            }
+
+            Map<String, Double> monthRoomCountMap = calculateMonthRoomCount(dailyItems, roomModelNames);
+            double totalRooms = monthRoomCountMap.values().stream().mapToDouble(Double::doubleValue).sum();
+
+            Map<String, Object> subtotalDataVO = createSummaryRow(
+                    month + "月小计", "", "", "数据", monthRoomCountMap, totalRooms, true, false);
+            subtotalDataVO.put("index", "");
+            result.add(subtotalDataVO);
+
+            String lastYearMonth = getMonthMinusOneYear(month);
+            List<Map<String, Object>> lastYearMonthData = lastYearMonthMap.getOrDefault(lastYearMonth, new ArrayList<>());
+            Map<String, Double> lastYearMonthRoomCountMap = calculateMonthRoomCount(lastYearMonthData, roomModelNames);
+            Map<String, Object> subtotalYoyVO = createYoyRow(monthRoomCountMap, lastYearMonthRoomCountMap, roomModelNames,true, false);
+            result.add(subtotalYoyVO);
+
+            accumulateListRoomCount(lastYearCumulativeMap, lastYearMonthData, roomModelNames);
+
+            if (!isFirstMonth) {
+                double cumulativeTotal = cumulativeRoomCountMap.values().stream().mapToDouble(Double::doubleValue).sum();
+
+                Map<String, Object> cumulativeDataVO = createSummaryRow(
+                        "累计", "", "", "数据", cumulativeRoomCountMap, cumulativeTotal, false, true);
+                cumulativeDataVO.put("index", "");
+                result.add(cumulativeDataVO);
+
+                Map<String, Object> cumulativeYoyVO = createYoyRow(cumulativeRoomCountMap, lastYearCumulativeMap, roomModelNames,false, true);
+                result.add(cumulativeYoyVO);
+            }
+
+            isFirstMonth = false;
+        }
+
+        return result;
+    }
+
+    private String getMonthMinusOneYear(String month) {
+        if (month == null || month.length() < 7) {
+            return "";
+        }
+        try {
+            int year = Integer.parseInt(month.substring(0, 4));
+            String rest = month.substring(4);
+            return (year - 1) + rest;
+        } catch (NumberFormatException e) {
+            return "";
+        }
+    }
+
+    private String formatMonth(String month) {
+        if (month == null || month.length() < 5) {
+            return month;
+        }
+        return month.substring(5);
+    }
+
+    private Map<String, Object> createBasicRow(Map<String, Object> data, int index, List<String> roomModelNames) {
+        Map<String, Object> row = new LinkedHashMap<>();
+        row.put("index", index);
+        row.put("date", String.valueOf(data.get("date")));
+        row.put("ship", String.valueOf(data.get("ship")));
+        row.put("route", String.valueOf(data.get("route")));
+        row.put("voyageNo", String.valueOf(data.get("voyageNo")));
+
+        for (String roomModelName : roomModelNames) {
+            Double roomNum = data.get(roomModelName) != null ?
+                    ((Number) data.get(roomModelName)).doubleValue() : 0.0;
+            row.put(roomModelName, roomNum);
+        }
+
+        Object totalRooms = data.get("totalRooms");
+        if (totalRooms != null) {
+            row.put("totalRooms", ((Number) totalRooms).doubleValue());
+        }
+
+        return row;
+    }
+
+    private Map<String, Double> calculateMonthRoomCount(List<Map<String, Object>> dataList, List<String> roomModelNames) {
+        Map<String, Double> roomCountMap = new LinkedHashMap<>();
+
+        for (String roomModelName : roomModelNames) {
+            double sum = dataList.stream()
+                    .mapToDouble(data -> {
+                        Object value = data.get(roomModelName);
+                        return value != null ? ((Number) value).doubleValue() : 0.0;
+                    })
+                    .sum();
+            roomCountMap.put(roomModelName, sum);
+        }
+
+        return roomCountMap;
+    }
+
+    private void accumulateSingleRoomCount(Map<String, Double> cumulativeMap, Map<String, Object> data, List<String> roomModelNames) {
+        for (String roomModelName : roomModelNames) {
+            Double roomNum = data.get(roomModelName) != null ?
+                    ((Number) data.get(roomModelName)).doubleValue() : 0.0;
+            cumulativeMap.merge(roomModelName, roomNum, Double::sum);
+        }
+    }
+
+    private void accumulateListRoomCount(Map<String, Double> cumulativeMap, List<Map<String, Object>> dataList, List<String> roomModelNames) {
+        for (String roomModelName : roomModelNames) {
+            double sum = dataList.stream()
+                    .mapToDouble(data -> {
+                        Object value = data.get(roomModelName);
+                        return value != null ? ((Number) value).doubleValue() : 0.0;
+                    })
+                    .sum();
+            cumulativeMap.merge(roomModelName, sum, Double::sum);
+        }
+    }
+
+    private Map<String, Object> createSummaryRow(
+            String month, String date, String ship, String voyageNo,
+            Map<String, Double> roomCountMap, double totalRooms,
+            boolean isSubtotal, boolean isCumulative) {
+
+        Map<String, Object> row = new LinkedHashMap<>();
+        row.put("month", month);
+        row.put("date", date);
+        row.put("ship", ship);
+        row.put("route", "");
+        row.put("voyageNo", voyageNo);
+
+        for (Map.Entry<String, Double> entry : roomCountMap.entrySet()) {
+            row.put(entry.getKey(), entry.getValue());
+        }
+
+        row.put("totalRooms", totalRooms);
+        row.put("isSubtotal", isSubtotal);
+        row.put("isCumulative", isCumulative);
+
+        return row;
+    }
+
+    private Map<String, Object> createYoyRow(
+            Map<String, Double> currentMap,
+            Map<String, Double> lastYearMap,
+            List<String> roomModelNames,boolean isSubtotal, boolean isCumulative) {
+
+        Map<String, Object> yoyRow = new LinkedHashMap<>();
+        yoyRow.put("month", "");
+        yoyRow.put("date", "");
+        yoyRow.put("ship", "");
+        yoyRow.put("route", "");
+        yoyRow.put("voyageNo", "同比");
+        yoyRow.put("isSubtotal", isSubtotal);
+        yoyRow.put("isCumulative", isCumulative);
+
+        for (String roomModelName : roomModelNames) {
+            Double currentVal = currentMap.getOrDefault(roomModelName, 0.0);
+            Double lastYearVal = lastYearMap.getOrDefault(roomModelName, 0.0);
+            String yoyPercent = calcYoyPercent(currentVal, lastYearVal);
+            yoyRow.put(roomModelName, yoyPercent);
+        }
+
+        double currentTotal = currentMap.values().stream().mapToDouble(Double::doubleValue).sum();
+        double lastYearTotal = lastYearMap.values().stream().mapToDouble(Double::doubleValue).sum();
+        yoyRow.put("totalRooms", calcYoyPercent(currentTotal, lastYearTotal));
+
+        return yoyRow;
+    }
+
+    private String calcYoyPercent(Number current, Number last) {
+        if (current == null || last == null || last.doubleValue() == 0) {
+            return "-";
+        }
+        double diff = current.doubleValue() - last.doubleValue();
+        double percent = (diff / last.doubleValue()) * 100;
+        if (percent >= 0) {
+            return String.format("+%.1f%%", percent);
+        } else {
+            return String.format("%.1f%%", percent);
+        }
+    }
+
+    @Override
+    public void exportCabinMixDaily(@Valid CabinMixDailyReqVO reqVO, HttpServletResponse response) throws IOException {
+        if (reqVO.getShipId() == null) {
+            throw new IllegalArgumentException("请选择游轮");
+        }
+
+        List<Map<String, Object>> roomTypeList = resourceRoomMapper.getCabinMixDailyTableHeadList(reqVO.getShipId());
+        if (roomTypeList == null || roomTypeList.isEmpty()) {
+            throw new IllegalArgumentException("该游轮暂无房型配置");
+        }
+
+        List<Map<String, Object>> dataList = getList(reqVO);
+        if (dataList == null || dataList.isEmpty()) {
+            throw new IllegalArgumentException("暂无数据可导出");
+        }
+
+        List<List<String>> headers = buildExportHeaders(roomTypeList);
+        List<List<Object>> exportData = transformExportData(dataList, roomTypeList);
+
+        EasyExcel.write(response.getOutputStream())
+                .head(headers)
+                .autoCloseStream(false)
+                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
+                .registerConverter(new LongStringConverter())
+                .sheet("省际度假游轮房型分布日报")
+                .doWrite(exportData);
+
+        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
+        response.addHeader("Content-Disposition",
+                "attachment;filename=" + URLEncoder.encode("省际度假游轮房型分布日报表.xlsx", StandardCharsets.UTF_8.name()));
+    }
+
+    private List<List<String>> buildExportHeaders(List<Map<String, Object>> roomTypeList) {
+        List<List<String>> headers = new ArrayList<>();
+
+        headers.add(new ArrayList<>(Arrays.asList("序号")));
+        headers.add(new ArrayList<>(Arrays.asList("月份")));
+        headers.add(new ArrayList<>(Arrays.asList("日期")));
+        headers.add(new ArrayList<>(Arrays.asList("船舶")));
+        headers.add(new ArrayList<>(Arrays.asList("航线")));
+        headers.add(new ArrayList<>(Arrays.asList("航次号")));
+
+        for (Map<String, Object> roomType : roomTypeList) {
+            String roomModelName = roomType.get("room_model_name").toString();
+            headers.add(new ArrayList<>(Arrays.asList(roomModelName)));
+        }
+
+        headers.add(new ArrayList<>(Arrays.asList("合计")));
+
+        return headers;
+    }
+
+    private List<List<Object>> transformExportData(List<Map<String, Object>> dataList, List<Map<String, Object>> roomTypeList) {
+        List<List<Object>> result = new ArrayList<>(dataList.size());
+        List<String> roomModelNames = roomTypeList.stream()
+                .map(room -> room.get("room_model_name").toString())
+                .collect(Collectors.toList());
+
+        for (Map<String, Object> row : dataList) {
+            List<Object> rowData = new ArrayList<>();
+
+            boolean isYoy = "同比".equals(row.get("voyageNo"));
+            boolean isSubtotalOrCumulative = Boolean.TRUE.equals(row.get("isSubtotal"))
+                    || Boolean.TRUE.equals(row.get("isCumulative"));
+
+            if (isYoy) {
+                rowData.add("");
+                rowData.add("");
+                rowData.add("");
+                rowData.add("");
+                rowData.add("");
+                rowData.add("同比");
+
+                for (String roomModelName : roomModelNames) {
+                    rowData.add(row.get(roomModelName) != null ? row.get(roomModelName) : "-");
+                }
+                rowData.add(row.get("totalRooms") != null ? row.get("totalRooms") : "-");
+            } else {
+                rowData.add(row.get("index") != null ? String.valueOf(row.get("index")) : "");
+                rowData.add(row.get("month") != null ? row.get("month") : "");
+                rowData.add(row.get("date") != null ? row.get("date") : "");
+                rowData.add(row.get("ship") != null ? row.get("ship") : "");
+                rowData.add(row.get("route") != null ? row.get("route") : "");
+                rowData.add(row.get("voyageNo") != null ? row.get("voyageNo") : "");
+
+                for (String roomModelName : roomModelNames) {
+                    Object value = row.get(roomModelName);
+                    if (value != null) {
+                        double num = ((Number) value).doubleValue();
+                        if (num == (int) num) {
+                            rowData.add(String.valueOf((int) num));
+                        } else {
+                            rowData.add(String.format("%.1f", num));
+                        }
+                    } else {
+                        rowData.add("-");
+                    }
+                }
+
+                Object totalRooms = row.get("totalRooms");
+                if (totalRooms != null) {
+                    double num = ((Number) totalRooms).doubleValue();
+                    if (num == (int) num) {
+                        rowData.add(String.valueOf((int) num));
+                    } else {
+                        rowData.add(String.format("%.1f", num));
+                    }
+                } else {
+                    rowData.add("-");
+                }
+            }
+
+            result.add(rowData);
+        }
+
+        return result;
+    }
+}

+ 52 - 0
ship-module-trade/ship-module-trade-biz/src/main/resources/mapper/report/CabinMixDailyMapper.xml

@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.yc.ship.module.trade.dal.mysql.report.CabinMixDailyMapper">
+    <select id="getList" resultType="java.util.Map">
+        SELECT
+        DATE_FORMAT(v.boarding_time, '%Y-%m') AS `month`,
+        DATE_FORMAT(v.boarding_time, '%d') AS `date`,
+        s.name AS ship,
+        r.name AS route,
+        v.name AS voyageNo,
+        td.voyage_id,
+        ${roomColumns},
+        COALESCE(SUM(torm.use_room_num), 0) + COALESCE ( voyage_visitor.total_visitor / 2, 0 ) AS totalRooms
+        FROM
+        trade_order td
+        LEFT JOIN trade_order_room_model torm ON torm.deleted = 0 AND td.id = torm.order_id
+        LEFT JOIN product_voyage v ON td.voyage_id = v.id AND v.deleted = 0
+        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
+        LEFT JOIN (
+        SELECT
+        tod.voyage_id,
+        COUNT(tv.id) AS total_visitor
+        FROM trade_visitor tv
+        LEFT JOIN trade_order tod
+        ON tv.order_id = tod.id AND tod.deleted = 0
+        WHERE tv.type IN ('with','leader') AND tv.deleted = 0
+        GROUP BY tod.voyage_id
+        ) voyage_visitor ON voyage_visitor.voyage_id = v.id
+        WHERE
+        td.deleted = 0
+        AND td.order_status > 0
+        <if test="vo.shipId != null">
+            AND v.ship_id = #{vo.shipId}
+        </if>
+        <if test="vo.startDate != null and vo.startDate != ''">
+            AND v.boarding_time &gt;= #{vo.startDate}
+        </if>
+        <if test="vo.endDate != null and vo.endDate != ''">
+            AND v.boarding_time &lt; DATE_ADD(#{vo.endDate}, INTERVAL 1 DAY)
+        </if>
+        GROUP BY
+        td.voyage_id,
+        v.boarding_time,
+        s.name,
+        r.name,
+        v.name
+        ORDER BY
+        v.boarding_time
+    </select>
+
+</mapper>