瀏覽代碼

省际度假游轮获客情况日报表

caotao 9 小時之前
父節點
當前提交
12488bef35

+ 2 - 1
ship-module-ota/ship-module-ota-biz/src/main/java/com/yc/ship/module/ota/service/distributorcategory/DistributorCategoryServiceImpl.java

@@ -91,7 +91,8 @@ public class DistributorCategoryServiceImpl implements DistributorCategoryServic
         return  distributorCategoryMapper.selectList(new LambdaQueryWrapperX<DistributorCategoryDO>()
                 .eq(DistributorCategoryDO::getGroupType, groupType)
                 .eq(DistributorCategoryDO::getUseStatus, 1)
-                .eq(DistributorCategoryDO::getDeleted, 0));
+                .eq(DistributorCategoryDO::getDeleted, 0)
+                .orderByAsc(DistributorCategoryDO::getSortNum));
     }
 
 }

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

@@ -0,0 +1,54 @@
+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.AcquisitionDailyReqVO;
+import com.yc.ship.module.trade.controller.admin.report.vo.CabinMixDailyReqVO;
+import com.yc.ship.module.trade.service.report.AcquisitionDailyService;
+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.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+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/cruise_acquisition_daily")
+@Validated
+@Slf4j
+public class AcquisitionDailyController {
+    @Resource
+    private AcquisitionDailyService acquisitionDailyService;
+
+    /**
+     * 查询省际度假游轮获客情况日报表
+     */
+    @GetMapping("/list")
+    @Operation(summary = "查询省际度假游轮获客情况日报表")
+    @ResponseBody
+    public CommonResult<List<Map<String, Object>>> list(@Validated AcquisitionDailyReqVO reqVO) {
+        List<Map<String, Object>> list = acquisitionDailyService.getList(reqVO);
+        return success(list);
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出省际度假游轮获客情况日报表 Excel")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportCabinMixDaily(@Valid AcquisitionDailyReqVO reqVO,
+                                    HttpServletResponse response) throws IOException {
+        acquisitionDailyService.exportCabinMixDaily(reqVO, response);
+    }
+}

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

@@ -0,0 +1,15 @@
+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 AcquisitionDailyReqVO {
+    @Schema(description = "开始日期", example = "2026-01-01")
+    private String startDate;
+    @Schema(description = "结束日期", example = "2026-03-31")
+    private String endDate;
+}

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

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

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

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

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

@@ -0,0 +1,507 @@
+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.ota.dal.dataobject.distributorcategory.DistributorCategoryDO;
+import com.yc.ship.module.ota.service.distributorcategory.DistributorCategoryService;
+import com.yc.ship.module.resource.helper.DateHelper;
+import com.yc.ship.module.trade.controller.admin.report.vo.AcquisitionDailyReqVO;
+import com.yc.ship.module.trade.controller.admin.report.vo.CabinMixDailyReqVO;
+import com.yc.ship.module.trade.dal.mysql.report.AcquisitionDailyMapper;
+import com.yc.ship.module.trade.service.report.AcquisitionDailyService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+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 AcquisitionDailyServiceImpl implements AcquisitionDailyService {
+    @Resource
+    private DistributorCategoryService distributorCategoryService;
+    @Resource
+    private AcquisitionDailyMapper acquisitionDailyMapper;
+
+
+    @Override
+    public List<Map<String, Object>> getList(AcquisitionDailyReqVO reqVO) {
+        // 查询供应商财务分组
+        List<DistributorCategoryDO> categoryList= distributorCategoryService.getCategoryList(2);
+        if (categoryList == null || categoryList.isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<Map<String,Object>> currentDataList = acquisitionDailyMapper.getList(reqVO, buildDistributorCategoryColumns(categoryList));
+        if (currentDataList == null || currentDataList.isEmpty()) {
+            return Collections.emptyList();
+        }
+        List<Map<String, Object>> lastYearDataList = queryLastYearData(reqVO, categoryList);
+
+        return processAcquisitionDailyData(currentDataList, lastYearDataList, categoryList);
+    }
+
+    private String buildDistributorCategoryColumns(List<DistributorCategoryDO> categoryList) {
+        StringBuilder columns = new StringBuilder();
+        for (int i = 0; i < categoryList.size(); i++) {
+            DistributorCategoryDO categoryDO = categoryList.get(i);
+            Long categoryId = categoryDO.getId();
+            String cateName = categoryDO.getCateName();
+
+            String columnSql = String.format(
+                    "COALESCE(SUM(CASE WHEN odc.id = %s THEN 1 ELSE 0 END), 0) AS `%s`",
+                    categoryId, cateName
+            );
+            columns.append(columnSql);
+            if (i < categoryList.size() - 1) {
+                columns.append(", ");
+            }
+        }
+        return columns.toString();
+    }
+
+    private List<Map<String, Object>> queryLastYearData(AcquisitionDailyReqVO reqVO, List<DistributorCategoryDO> categoryList) {
+        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;
+
+            AcquisitionDailyReqVO lastYearReq = new AcquisitionDailyReqVO();
+            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 acquisitionDailyMapper.getList(lastYearReq, buildDistributorCategoryColumns(categoryList));
+        } catch (Exception e) {
+            log.warn("[queryLastYearData] 查询去年同期数据失败: {}", e.getMessage());
+            return new ArrayList<>();
+        }
+    }
+
+    private List<Map<String, Object>> processAcquisitionDailyData(
+            List<Map<String, Object>> currentDataList,
+            List<Map<String, Object>> lastYearDataList,
+            List<DistributorCategoryDO> categoryList) {
+
+        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> cumulativeCategoryCountMap = new LinkedHashMap<>();
+        Map<String, Double> lastYearCumulativeMap = new LinkedHashMap<>();
+        double cumulativeTotalCount = 0.0;
+        double lastYearCumulativeTotalCount = 0.0;
+        boolean isFirstMonth = true;
+
+        // 获取分销商类别名称列表
+        List<String> categoryNames = categoryList.stream()
+                .map(DistributorCategoryDO::getCateName)
+                .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++, categoryNames);
+                row.put("month", formatMonth(month));
+                row.put("isSubtotal", false);
+                row.put("isCumulative", false);
+                result.add(row);
+
+                accumulateSingleCategoryCount(cumulativeCategoryCountMap, data, categoryNames);
+            }
+
+            // 计算月度小计
+            Map<String, Double> monthCategoryCountMap = calculateMonthCategoryCount(dailyItems, categoryNames);
+            double totalCount = calculateTotalCountSum(dailyItems);
+            cumulativeTotalCount += totalCount;
+            Map<String, Object> subtotalDataVO = createSummaryRow(
+                    month + "月小计", "", "", "数据", monthCategoryCountMap, totalCount, 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> lastYearMonthCategoryCountMap = calculateMonthCategoryCount(lastYearMonthData, categoryNames);
+            double lastYearMonthTotal = calculateTotalCountSum(lastYearMonthData);  // 新增:去年同期当月 totalCount
+            lastYearCumulativeTotalCount += lastYearMonthTotal;  // 新增:累加到去年同期累计
+
+            Map<String, Object> subtotalYoyVO = createYoyRow(
+                    monthCategoryCountMap, lastYearMonthCategoryCountMap,
+                    totalCount, lastYearMonthTotal,
+                    categoryNames, true, false);
+            result.add(subtotalYoyVO);
+
+            accumulateListCategoryCount(lastYearCumulativeMap, lastYearMonthData, categoryNames);
+            // 累计(从第二个月开始)
+            if (!isFirstMonth) {
+
+                Map<String, Object> cumulativeDataVO = createSummaryRow(
+                        "累计", "", "", "数据", cumulativeCategoryCountMap, cumulativeTotalCount, false, true);
+                cumulativeDataVO.put("index", "");
+                result.add(cumulativeDataVO);
+
+                Map<String, Object> cumulativeYoyVO = createYoyRow(
+                        cumulativeCategoryCountMap, lastYearCumulativeMap,
+                        cumulativeTotalCount, lastYearCumulativeTotalCount,
+                        categoryNames, 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> categoryNames) {
+        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 categoryName : categoryNames) {
+            Double count = data.get(categoryName) != null ?
+                    ((Number) data.get(categoryName)).doubleValue() : 0.0;
+            row.put(categoryName, count);
+        }
+
+        Object totalCount = data.get("totalCount");
+        if (totalCount != null) {
+            row.put("totalCount", ((Number) totalCount).doubleValue());
+        }
+
+        return row;
+    }
+
+    /**
+     * 计算月度分销商类别统计
+     */
+    private Map<String, Double> calculateMonthCategoryCount(List<Map<String, Object>> dataList, List<String> categoryNames) {
+        Map<String, Double> categoryCountMap = new LinkedHashMap<>();
+
+        for (String categoryName : categoryNames) {
+            double sum = dataList.stream()
+                    .mapToDouble(data -> {
+                        Object value = data.get(categoryName);
+                        return value != null ? ((Number) value).doubleValue() : 0.0;
+                    })
+                    .sum();
+            categoryCountMap.put(categoryName, sum);
+        }
+
+        return categoryCountMap;
+    }
+
+    /**
+     * 累计单个数据的分销商类别数量
+     */
+    private void accumulateSingleCategoryCount(Map<String, Double> cumulativeMap, Map<String, Object> data, List<String> categoryNames) {
+        for (String categoryName : categoryNames) {
+            Double count = data.get(categoryName) != null ?
+                    ((Number) data.get(categoryName)).doubleValue() : 0.0;
+            cumulativeMap.merge(categoryName, count, Double::sum);
+        }
+    }
+
+    /**
+     * 累计列表数据的分销商类别数量
+     */
+    private void accumulateListCategoryCount(Map<String, Double> cumulativeMap, List<Map<String, Object>> dataList, List<String> categoryNames) {
+        for (String categoryName : categoryNames) {
+            double sum = dataList.stream()
+                    .mapToDouble(data -> {
+                        Object value = data.get(categoryName);
+                        return value != null ? ((Number) value).doubleValue() : 0.0;
+                    })
+                    .sum();
+            cumulativeMap.merge(categoryName, sum, Double::sum);
+        }
+    }
+
+    /**
+     * 创建汇总行
+     */
+    private Map<String, Object> createSummaryRow(
+            String month, String date, String ship, String voyageNo,
+            Map<String, Double> categoryCountMap, double totalCount,
+            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 : categoryCountMap.entrySet()) {
+            row.put(entry.getKey(), entry.getValue());
+        }
+
+        row.put("totalCount", totalCount);
+        row.put("isSubtotal", isSubtotal);
+        row.put("isCumulative", isCumulative);
+
+        return row;
+    }
+
+    /**
+     * 创建同比行
+     */
+    private Map<String, Object> createYoyRow(
+            Map<String, Double> currentMap,
+            Map<String, Double> lastYearMap,
+            double currentTotal,
+            double lastYearTotal,
+            List<String> categoryNames, 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 categoryName : categoryNames) {
+            Double currentVal = currentMap.getOrDefault(categoryName, 0.0);
+            Double lastYearVal = lastYearMap.getOrDefault(categoryName, 0.0);
+            String yoyPercent = calcYoyPercent(currentVal, lastYearVal);
+            yoyRow.put(categoryName, yoyPercent);
+        }
+
+        yoyRow.put("totalCount", calcYoyPercent(currentTotal, lastYearTotal));  // 修改:使用传入的 totalCount
+
+        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);
+        }
+    }
+
+    /**
+     * 计算列表的 totalCount 之和
+     */
+    private double calculateTotalCountSum(List<Map<String, Object>> dataList) {
+        return dataList.stream()
+                .mapToDouble(data -> {
+                    Object totalCount = data.get("totalCount");
+                    return totalCount != null ? ((Number) totalCount).doubleValue() : 0.0;
+                })
+                .sum();
+    }
+
+
+    @Override
+    public void exportCabinMixDaily(AcquisitionDailyReqVO reqVO, HttpServletResponse response) throws IOException  {
+        // 1. 查询供应商财务分组
+        List<DistributorCategoryDO> categoryList = distributorCategoryService.getCategoryList(2);
+        if (categoryList == null || categoryList.isEmpty()) {
+            throw new IllegalArgumentException("暂无供应商财务分组配置");
+        }
+
+        // 2. 查询数据
+        List<Map<String, Object>> dataList = getList(reqVO);
+        if (dataList == null || dataList.isEmpty()) {
+            throw new IllegalArgumentException("暂无数据可导出");
+        }
+        // 3. 构建表头
+        List<List<String>> headers = buildExportHeaders(categoryList);
+        // 4. 转换数据
+        List<List<Object>> exportData = transformExportData(dataList, categoryList);
+        // 5. 导出Excel
+        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<DistributorCategoryDO> categoryList) {
+        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("航次号")));
+
+        List<DistributorCategoryDO> domesticList = categoryList.stream()
+                .filter(c -> c.getMarketArea() != null && c.getMarketArea() == 1)
+                .collect(Collectors.toList());
+        List<DistributorCategoryDO> overseasList = categoryList.stream()
+                .filter(c -> c.getMarketArea() != null && c.getMarketArea() == 2)
+                .collect(Collectors.toList());
+
+        // 国内市场 二级表头
+        for (DistributorCategoryDO category : domesticList) {
+            headers.add(new ArrayList<>(Arrays.asList("国内市场", category.getCateName())));
+        }
+
+        // 国外市场 二级表头
+        for (DistributorCategoryDO category : overseasList) {
+            headers.add(new ArrayList<>(Arrays.asList("国外市场", category.getCateName())));
+        }
+
+        // 合计列
+        headers.add(new ArrayList<>(Arrays.asList("合计")));
+
+        return headers;
+    }
+
+    /**
+     * 转换导出数据
+     */
+    private List<List<Object>> transformExportData(List<Map<String, Object>> dataList,
+                                                   List<DistributorCategoryDO> categoryList) {
+        List<List<Object>> result = new ArrayList<>(dataList.size());
+
+        // 获取分销商类别名称列表
+        List<String> categoryNames = categoryList.stream()
+                .map(DistributorCategoryDO::getCateName)
+                .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) {
+                // 同比行:前面固定列留空,voyageNo 显示"同比"
+                rowData.add("");
+                rowData.add("");
+                rowData.add("");
+                rowData.add("");
+                rowData.add("");
+                rowData.add("同比");
+
+                // 各分销商类别的同比数据
+                for (String categoryName : categoryNames) {
+                    rowData.add(row.get(categoryName) != null ? row.get(categoryName) : "-");
+                }
+
+                // 合计列同比
+                rowData.add(row.get("totalCount") != null ? row.get("totalCount") : "-");
+            } 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 categoryName : categoryNames) {
+                    Object value = row.get(categoryName);
+                    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 totalCount = row.get("totalCount");
+                if (totalCount != null) {
+                    double num = ((Number) totalCount).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;
+    }
+}

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

@@ -0,0 +1,41 @@
+<?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.AcquisitionDailyMapper">
+    <select id="getList" resultType="java.util.Map">
+        SELECT
+        DATE_FORMAT(v.boarding_time, '%Y-%m') AS `month`,
+        DATE_FORMAT(v.boarding_time, '%Y-%m-%d') AS `date`,
+        s.name AS ship,
+        r.name AS route,
+        v.name AS voyageNo,
+        ${categoryColumns},
+        count(1) AS totalCount,
+        td.voyage_id
+        FROM
+        trade_order td
+        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 ota_distributor od ON td.source_id = od.id
+        LEFT JOIN ota_distributor_category odc ON od.finance_category_id = odc.id
+        LEFT JOIN trade_visitor tv ON td.id = tv.order_id AND tv.deleted=0
+        WHERE
+        td.deleted = 0
+        AND td.order_status > 0
+        <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>