|
|
@@ -20,6 +20,7 @@ import com.yc.ship.module.trade.dal.mysql.report.VoyageStockBoardMapper;
|
|
|
import com.yc.ship.module.trade.service.report.VoyageStockBoardService;
|
|
|
import com.yc.ship.module.trade.utils.IdCardProvinceUtil;
|
|
|
import com.yc.ship.module.trade.utils.excel.AllVoyageStockBoardExportStyleHandler;
|
|
|
+import com.yc.ship.module.trade.utils.excel.VoyageDataSummaryExportStyleHandler;
|
|
|
import com.yc.ship.module.trade.utils.excel.VoyageStockBoardExportStyleHandler;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
@@ -1405,4 +1406,557 @@ public class VoyageStockBoardServiceImpl implements VoyageStockBoardService {
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
+
|
|
|
+ // ==================== 航次数据情况汇总(多航次合并为一个结果) ====================
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public VoyageDataSummaryRespVO getVoyageDataSummary(VoyageDataSummaryReqVO reqVO) {
|
|
|
+ // 1. 查询基础数据(不分航次,一次性查所有)
|
|
|
+ List<Map<String, Object>> visitorList = voyageStockBoardMapper.selectVoyageDataSummaryVisitorList(
|
|
|
+ reqVO.getShipId(), reqVO.getVoyageIds());
|
|
|
+ List<Map<String, Object>> roomList = voyageStockBoardMapper.selectVoyageDataSummaryRoomList(
|
|
|
+ reqVO.getShipId(), reqVO.getVoyageIds());
|
|
|
+ List<Map<String, Object>> voyageBaseList = voyageStockBoardMapper.selectVoyageBaseInfoList(reqVO.getShipId(), reqVO.getVoyageIds());
|
|
|
+
|
|
|
+ if (CollUtil.isEmpty(voyageBaseList)) {
|
|
|
+ return buildEmptyVoyageDataSummaryVO(reqVO.getShipId());
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 构建合并后的汇总VO
|
|
|
+ return buildMergedVoyageDataSummaryVO(voyageBaseList, visitorList, roomList);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void exportVoyageDataSummaryExcel(VoyageDataSummaryReqVO reqVO, HttpServletResponse response) throws IOException {
|
|
|
+ VoyageDataSummaryRespVO vo = getVoyageDataSummary(reqVO);
|
|
|
+
|
|
|
+ if (vo == null || vo.getTotalPeople() == null || vo.getTotalPeople() == 0) {
|
|
|
+ ExcelUtils.exportEmpty(response, "航次数据情况汇总");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 生成文件名和标题
|
|
|
+ String fileName = buildExportFileName(vo);
|
|
|
+ String title = buildExportTitle(vo);
|
|
|
+
|
|
|
+ // 构建动态表头和数据
|
|
|
+ List<List<String>> head = buildVoyageDataSummaryHeaders(vo);
|
|
|
+ List<List<Object>> exportData = buildVoyageDataSummaryExportData(vo);
|
|
|
+ int dataRowCount = exportData.size();
|
|
|
+
|
|
|
+ response.addHeader("Content-Disposition",
|
|
|
+ "attachment;filename=" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()));
|
|
|
+ response.setContentType("application/vnd.ms-excel;charset=UTF-8");
|
|
|
+
|
|
|
+ EasyExcel.write(response.getOutputStream())
|
|
|
+ .head(head)
|
|
|
+ .relativeHeadRowIndex(1)
|
|
|
+ .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
|
|
+ .registerWriteHandler(new VoyageDataSummaryExportStyleHandler(title, dataRowCount))
|
|
|
+ .registerConverter(new LongStringConverter())
|
|
|
+ .sheet("各航次情况子表")
|
|
|
+ .doWrite(exportData);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建空结果VO
|
|
|
+ */
|
|
|
+ private VoyageDataSummaryRespVO buildEmptyVoyageDataSummaryVO(Long shipId) {
|
|
|
+ VoyageDataSummaryRespVO vo = new VoyageDataSummaryRespVO();
|
|
|
+ vo.setShipName("");
|
|
|
+ vo.setVoyageName("");
|
|
|
+ vo.setDirection("");
|
|
|
+ vo.setTotalPeople(0);
|
|
|
+ vo.setTotalRooms(BigDecimal.ZERO);
|
|
|
+ vo.setMaleCount(0);
|
|
|
+ vo.setFemaleCount(0);
|
|
|
+ vo.setMaleFemaleRatio("0%:0%");
|
|
|
+ vo.setDomesticCount(0);
|
|
|
+ vo.setOverseasCount(0);
|
|
|
+ vo.setDomesticOverseasRatio("0%:0%");
|
|
|
+ vo.setTeamCount(0);
|
|
|
+ vo.setIndividualCount(0);
|
|
|
+ vo.setTeamIndividualRatio("0%:0%");
|
|
|
+ vo.setRoomTypeList(Collections.emptyList());
|
|
|
+ vo.setAgeGroupList(Collections.emptyList());
|
|
|
+ vo.setDomesticProvinceList(Collections.emptyList());
|
|
|
+ vo.setInternationalRegionList(Collections.emptyList());
|
|
|
+ vo.setVoyageInfoList(Collections.emptyList());
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建合并后的航次数据汇总 VO(多航次合并为一个结果)
|
|
|
+ */
|
|
|
+ private VoyageDataSummaryRespVO buildMergedVoyageDataSummaryVO(List<Map<String, Object>> voyageBaseList,
|
|
|
+ List<Map<String, Object>> visitorList,
|
|
|
+ List<Map<String, Object>> roomList) {
|
|
|
+ VoyageDataSummaryRespVO vo = new VoyageDataSummaryRespVO();
|
|
|
+
|
|
|
+ // 游轮名称(所有航次相同)
|
|
|
+ String shipName = voyageBaseList.get(0).get("shipName") != null
|
|
|
+ ? String.valueOf(voyageBaseList.get(0).get("shipName")) : "";
|
|
|
+ vo.setShipName(shipName);
|
|
|
+
|
|
|
+ // 构建航次信息列表
|
|
|
+ List<VoyageDataSummaryRespVO.VoyageInfoVO> voyageInfoList = new ArrayList<>();
|
|
|
+ StringBuilder voyageNameSb = new StringBuilder();
|
|
|
+ StringBuilder directionSb = new StringBuilder();
|
|
|
+ Set<String> directionSet = new LinkedHashSet<>();
|
|
|
+
|
|
|
+ for (int i = 0; i < voyageBaseList.size(); i++) {
|
|
|
+ Map<String, Object> base = voyageBaseList.get(i);
|
|
|
+ Long voyageId = Long.valueOf(String.valueOf(base.get("voyageId")));
|
|
|
+ String vName = formatVoyageName(base);
|
|
|
+ String dir = base.get("directionLabel") != null ? String.valueOf(base.get("directionLabel")) : "";
|
|
|
+
|
|
|
+ VoyageDataSummaryRespVO.VoyageInfoVO info = new VoyageDataSummaryRespVO.VoyageInfoVO();
|
|
|
+ info.setVoyageId(voyageId);
|
|
|
+ info.setVoyageName(vName);
|
|
|
+ info.setDirection(dir);
|
|
|
+ voyageInfoList.add(info);
|
|
|
+
|
|
|
+ if (i > 0) {
|
|
|
+ voyageNameSb.append("、");
|
|
|
+ }
|
|
|
+ voyageNameSb.append(vName);
|
|
|
+ if (StrUtil.isNotBlank(dir)) {
|
|
|
+ directionSet.add(dir);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ vo.setVoyageInfoList(voyageInfoList);
|
|
|
+ vo.setVoyageName(voyageNameSb.toString());
|
|
|
+
|
|
|
+ // 方向:单航次直接显示,多航次用顿号连接
|
|
|
+ if (directionSet.size() == 1) {
|
|
|
+ vo.setDirection(directionSet.iterator().next());
|
|
|
+ } else {
|
|
|
+ vo.setDirection(String.join("、", directionSet));
|
|
|
+ }
|
|
|
+
|
|
|
+ // ========== 合并统计计算 ==========
|
|
|
+
|
|
|
+ // 总房间数(按房型名称合并)
|
|
|
+ Map<String, BigDecimal> roomTypeCountMap = new HashMap<>();
|
|
|
+ for (Map<String, Object> room : roomList) {
|
|
|
+ String roomModelName = room.get("roomModelName") != null ? String.valueOf(room.get("roomModelName")) : "";
|
|
|
+ BigDecimal roomNum = new BigDecimal(String.valueOf(room.get("roomNum")));
|
|
|
+ if (StrUtil.isNotBlank(roomModelName) && roomNum != null && roomNum.compareTo(BigDecimal.ZERO) > 0) {
|
|
|
+ roomTypeCountMap.merge(roomModelName, roomNum, BigDecimal::add);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ BigDecimal totalRooms = BigDecimal.valueOf(roomTypeCountMap.values().stream().mapToDouble(BigDecimal::doubleValue).sum());
|
|
|
+ vo.setTotalRooms(totalRooms);
|
|
|
+
|
|
|
+ // 总人数
|
|
|
+ int totalPeople = visitorList.size();
|
|
|
+ vo.setTotalPeople(totalPeople);
|
|
|
+
|
|
|
+ // 性别统计
|
|
|
+ int maleCount = 0;
|
|
|
+ int femaleCount = 0;
|
|
|
+ for (Map<String, Object> v : visitorList) {
|
|
|
+ Integer gender = parseIntValue(v.get("gender"));
|
|
|
+ if (gender != null) {
|
|
|
+ if (gender == 1) maleCount++;
|
|
|
+ else if (gender == 0) femaleCount++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ vo.setMaleCount(maleCount);
|
|
|
+ vo.setFemaleCount(femaleCount);
|
|
|
+ int genderTotal = maleCount + femaleCount;
|
|
|
+ vo.setMaleFemaleRatio(genderTotal > 0
|
|
|
+ ? calcRatio(maleCount, genderTotal) + ":" + calcRatio(femaleCount, genderTotal)
|
|
|
+ : "0%:0%");
|
|
|
+
|
|
|
+ // 境内外统计(合并所有航次)
|
|
|
+ int domesticCount = 0;
|
|
|
+ int overseasCount = 0;
|
|
|
+ Map<String, Integer> provinceCountMap = new HashMap<>();
|
|
|
+ Map<String, Integer> countryCountMap = new HashMap<>();
|
|
|
+
|
|
|
+ for (Map<String, Object> v : visitorList) {
|
|
|
+ String nationalityName = v.get("nationalityName") != null ? String.valueOf(v.get("nationalityName")) : "";
|
|
|
+ String credentialNo = v.get("credentialNo") != null ? String.valueOf(v.get("credentialNo")) : "";
|
|
|
+ Integer credentialType = parseIntValue(v.get("credentialType"));
|
|
|
+
|
|
|
+ String province = IdCardProvinceUtil.getProvinceName(credentialType, credentialNo, nationalityName);
|
|
|
+
|
|
|
+ if (StrUtil.isNotBlank(province)) {
|
|
|
+ domesticCount++;
|
|
|
+ provinceCountMap.merge(province, 1, Integer::sum);
|
|
|
+ } else {
|
|
|
+ String country = StrUtil.isNotBlank(nationalityName) ? nationalityName : "其他";
|
|
|
+ overseasCount++;
|
|
|
+ countryCountMap.merge(country, 1, Integer::sum);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ vo.setDomesticCount(domesticCount);
|
|
|
+ vo.setOverseasCount(overseasCount);
|
|
|
+ int totalDomesticOverseas = domesticCount + overseasCount;
|
|
|
+ vo.setDomesticOverseasRatio(totalDomesticOverseas > 0
|
|
|
+ ? calcRatio(domesticCount, totalDomesticOverseas) + ":" + calcRatio(overseasCount, totalDomesticOverseas)
|
|
|
+ : "0%:0%");
|
|
|
+
|
|
|
+ // 房型统计(合并后重新计算占比)
|
|
|
+ List<VoyageDataSummaryRespVO.DimensionItemVO> roomTypeList = roomTypeCountMap.entrySet().stream()
|
|
|
+ .sorted(Map.Entry.<String, BigDecimal>comparingByValue().reversed())
|
|
|
+ .map(entry -> {
|
|
|
+ VoyageDataSummaryRespVO.DimensionItemVO item = new VoyageDataSummaryRespVO.DimensionItemVO();
|
|
|
+ item.setName(entry.getKey());
|
|
|
+ item.setCount(entry.getValue().intValue());
|
|
|
+ item.setRatio(calcRatio(entry.getValue().intValue(), totalRooms.intValue()));
|
|
|
+ return item;
|
|
|
+ })
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ vo.setRoomTypeList(roomTypeList);
|
|
|
+
|
|
|
+ // 年龄区间统计(合并所有航次)
|
|
|
+ List<VoyageDataSummaryRespVO.DimensionItemVO> ageGroupList = analyzeAgeGroupsMerged(visitorList);
|
|
|
+ vo.setAgeGroupList(ageGroupList);
|
|
|
+
|
|
|
+ // 境内省份(合并后重新计算占比)
|
|
|
+ int finalDomesticCount = domesticCount;
|
|
|
+ List<VoyageDataSummaryRespVO.DimensionItemVO> domesticProvinceList = provinceCountMap.entrySet().stream()
|
|
|
+ .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
|
|
|
+ .map(entry -> {
|
|
|
+ VoyageDataSummaryRespVO.DimensionItemVO item = new VoyageDataSummaryRespVO.DimensionItemVO();
|
|
|
+ item.setName(entry.getKey());
|
|
|
+ item.setCount(entry.getValue());
|
|
|
+ item.setRatio(calcRatio(entry.getValue(), finalDomesticCount));
|
|
|
+ return item;
|
|
|
+ })
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ vo.setDomesticProvinceList(domesticProvinceList);
|
|
|
+
|
|
|
+ // 境外国家(合并后重新计算占比)
|
|
|
+ int finalOverseasCount = overseasCount;
|
|
|
+ List<VoyageDataSummaryRespVO.DimensionItemVO> internationalRegionList = countryCountMap.entrySet().stream()
|
|
|
+ .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
|
|
|
+ .map(entry -> {
|
|
|
+ VoyageDataSummaryRespVO.DimensionItemVO item = new VoyageDataSummaryRespVO.DimensionItemVO();
|
|
|
+ item.setName(entry.getKey());
|
|
|
+ item.setCount(entry.getValue());
|
|
|
+ item.setRatio(calcRatio(entry.getValue(), finalOverseasCount));
|
|
|
+ return item;
|
|
|
+ })
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ vo.setInternationalRegionList(internationalRegionList);
|
|
|
+
|
|
|
+ // 团散统计:按订单游客数判断,>=10人为团,<10人为散
|
|
|
+ Map<String, Long> orderVisitorCount = visitorList.stream()
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
+ v -> String.valueOf(v.get("orderId")),
|
|
|
+ Collectors.counting()
|
|
|
+ ));
|
|
|
+
|
|
|
+ int teamCount = 0;
|
|
|
+ int individualCount = 0;
|
|
|
+ for (Long visitorCount : orderVisitorCount.values()) {
|
|
|
+ if (visitorCount >= 10) {
|
|
|
+ teamCount += visitorCount;
|
|
|
+ } else {
|
|
|
+ individualCount += visitorCount;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ vo.setTeamCount(teamCount);
|
|
|
+ vo.setIndividualCount(individualCount);
|
|
|
+ int teamIndividualTotal = teamCount + individualCount;
|
|
|
+ vo.setTeamIndividualRatio(teamIndividualTotal > 0
|
|
|
+ ? calcRatio(teamCount, teamIndividualTotal) + ":" + calcRatio(individualCount, teamIndividualTotal)
|
|
|
+ : "0%:0%");
|
|
|
+
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析整数值(兼容Number和String)
|
|
|
+ */
|
|
|
+ private Integer parseIntValue(Object obj) {
|
|
|
+ if (obj == null) return null;
|
|
|
+ if (obj instanceof Number) return ((Number) obj).intValue();
|
|
|
+ try {
|
|
|
+ return Integer.parseInt(String.valueOf(obj));
|
|
|
+ } catch (Exception e) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 格式化航次名称(如 0418航次)
|
|
|
+ */
|
|
|
+ private String formatVoyageName(Map<String, Object> base) {
|
|
|
+ Object startTimeObj = base.get("startTime");
|
|
|
+ if (startTimeObj == null) return "";
|
|
|
+ String startTimeStr = String.valueOf(startTimeObj);
|
|
|
+ try {
|
|
|
+ if (startTimeStr.length() >= 10) {
|
|
|
+ String monthDay = startTimeStr.substring(5, 7) + startTimeStr.substring(8, 10);
|
|
|
+ return monthDay + "航次";
|
|
|
+ }
|
|
|
+ } catch (Exception ignored) {}
|
|
|
+ return startTimeStr;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 年龄段分组统计(合并版)
|
|
|
+ */
|
|
|
+ private List<VoyageDataSummaryRespVO.DimensionItemVO> analyzeAgeGroupsMerged(List<Map<String, Object>> visitorList) {
|
|
|
+ String[] ageGroups = {"12岁以下", "12岁-18岁", "18岁-30岁", "30岁-45岁", "45岁-60岁", "60岁以上"};
|
|
|
+ Map<String, Integer> ageGroupCountMap = new LinkedHashMap<>();
|
|
|
+ for (String group : ageGroups) {
|
|
|
+ ageGroupCountMap.put(group, 0);
|
|
|
+ }
|
|
|
+
|
|
|
+ int totalWithAge = 0;
|
|
|
+ for (Map<String, Object> v : visitorList) {
|
|
|
+ Object ageObj = v.get("age");
|
|
|
+ if (ageObj == null) continue;
|
|
|
+ int age;
|
|
|
+ try {
|
|
|
+ age = Integer.parseInt(String.valueOf(ageObj));
|
|
|
+ } catch (Exception e) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ totalWithAge++;
|
|
|
+ String group = getAgeGroupLabel(age);
|
|
|
+ ageGroupCountMap.merge(group, 1, Integer::sum);
|
|
|
+ }
|
|
|
+
|
|
|
+ List<VoyageDataSummaryRespVO.DimensionItemVO> result = new ArrayList<>();
|
|
|
+ for (String group : ageGroups) {
|
|
|
+ Integer count = ageGroupCountMap.getOrDefault(group, 0);
|
|
|
+ VoyageDataSummaryRespVO.DimensionItemVO item = new VoyageDataSummaryRespVO.DimensionItemVO();
|
|
|
+ item.setName(group);
|
|
|
+ item.setCount(count);
|
|
|
+ item.setRatio(calcRatio(count, totalWithAge));
|
|
|
+ result.add(item);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取年龄段标签
|
|
|
+ */
|
|
|
+ private String getAgeGroupLabel(int age) {
|
|
|
+ if (age < 12) return "12岁以下";
|
|
|
+ else if (age < 18) return "12岁-18岁";
|
|
|
+ else if (age < 30) return "18岁-30岁";
|
|
|
+ else if (age < 45) return "30岁-45岁";
|
|
|
+ else if (age < 60) return "45岁-60岁";
|
|
|
+ else return "60岁以上";
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成导出文件名
|
|
|
+ * 单航次:长江行·揽月 0418航次(下水)数据情况.xls
|
|
|
+ * 多航次:长江行·揽月 各航次数据情况.xls
|
|
|
+ */
|
|
|
+ private String buildExportFileName(VoyageDataSummaryRespVO vo) {
|
|
|
+ String shipName = vo.getShipName() != null ? vo.getShipName() : "";
|
|
|
+ List<VoyageDataSummaryRespVO.VoyageInfoVO> infoList = vo.getVoyageInfoList();
|
|
|
+ if (CollUtil.isEmpty(infoList) || infoList.size() == 1) {
|
|
|
+ String voyageName = vo.getVoyageName() != null ? vo.getVoyageName() : "";
|
|
|
+ String direction = vo.getDirection() != null ? vo.getDirection() : "";
|
|
|
+ return shipName + " " + voyageName + "(" + direction + ")数据情况.xls";
|
|
|
+ }
|
|
|
+ return shipName + " 各航次数据情况.xls";
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建导出标题
|
|
|
+ */
|
|
|
+ private String buildExportTitle(VoyageDataSummaryRespVO vo) {
|
|
|
+ String shipName = vo.getShipName() != null ? vo.getShipName() : "";
|
|
|
+ List<VoyageDataSummaryRespVO.VoyageInfoVO> infoList = vo.getVoyageInfoList();
|
|
|
+ if (CollUtil.isEmpty(infoList) || infoList.size() == 1) {
|
|
|
+ String voyageName = vo.getVoyageName() != null ? vo.getVoyageName() : "";
|
|
|
+ String direction = vo.getDirection() != null ? vo.getDirection() : "";
|
|
|
+ return shipName + " " + voyageName + "(" + direction + ")数据情况";
|
|
|
+ }
|
|
|
+ return shipName + " 各航次数据情况";
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建航次数据汇总导出表头(2层多级表头,共18列)
|
|
|
+ */
|
|
|
+ private List<List<String>> buildVoyageDataSummaryHeaders(VoyageDataSummaryRespVO vo) {
|
|
|
+ List<List<String>> head = new ArrayList<>();
|
|
|
+
|
|
|
+ // 航次(1列)
|
|
|
+ head.add(Arrays.asList("航次", "航次"));
|
|
|
+
|
|
|
+ // 数据汇总(2列)
|
|
|
+ head.add(Arrays.asList("数据汇总", ""));
|
|
|
+ head.add(Arrays.asList("数据汇总", ""));
|
|
|
+
|
|
|
+ // 预定间数及占比(3列)
|
|
|
+ head.add(Arrays.asList("预定间数及占比", "房型"));
|
|
|
+ head.add(Arrays.asList("预定间数及占比", "间数"));
|
|
|
+ head.add(Arrays.asList("预定间数及占比", "占比"));
|
|
|
+
|
|
|
+ // 年龄区间(3列)
|
|
|
+ head.add(Arrays.asList("年龄区间", "年龄"));
|
|
|
+ head.add(Arrays.asList("年龄区间", "人数"));
|
|
|
+ head.add(Arrays.asList("年龄区间", "占比"));
|
|
|
+
|
|
|
+ // 境内(3列),显示人数/占比
|
|
|
+ String domesticHeader = "境内";
|
|
|
+ if (vo.getDomesticCount() != null && vo.getOverseasCount() != null) {
|
|
|
+ int total = vo.getDomesticCount() + vo.getOverseasCount();
|
|
|
+ if (total > 0) {
|
|
|
+ domesticHeader = "境内(" + vo.getDomesticCount() + "/" + calcRatio(vo.getDomesticCount(), total) + ")";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ head.add(Arrays.asList(domesticHeader, "省份"));
|
|
|
+ head.add(Arrays.asList(domesticHeader, "人数"));
|
|
|
+ head.add(Arrays.asList(domesticHeader, "占比"));
|
|
|
+
|
|
|
+ // 境外(3列),显示人数/占比
|
|
|
+ String overseasHeader = "境外";
|
|
|
+ if (vo.getDomesticCount() != null && vo.getOverseasCount() != null) {
|
|
|
+ int total = vo.getDomesticCount() + vo.getOverseasCount();
|
|
|
+ if (total > 0) {
|
|
|
+ overseasHeader = "境外(" + vo.getOverseasCount() + "/" + calcRatio(vo.getOverseasCount(), total) + ")";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ head.add(Arrays.asList(overseasHeader, "国家/区域"));
|
|
|
+ head.add(Arrays.asList(overseasHeader, "人数"));
|
|
|
+ head.add(Arrays.asList(overseasHeader, "占比"));
|
|
|
+
|
|
|
+ // 团散占比(3列)
|
|
|
+ head.add(Arrays.asList("团散占比", "团队人数"));
|
|
|
+ head.add(Arrays.asList("团散占比", "散客人数"));
|
|
|
+ head.add(Arrays.asList("团散占比", "占比"));
|
|
|
+
|
|
|
+ return head;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建航次数据汇总导出数据(单个VO)
|
|
|
+ */
|
|
|
+ private List<List<Object>> buildVoyageDataSummaryExportData(VoyageDataSummaryRespVO vo) {
|
|
|
+ List<List<Object>> result = new ArrayList<>();
|
|
|
+
|
|
|
+ List<VoyageDataSummaryRespVO.DimensionItemVO> roomList = vo.getRoomTypeList() != null ? vo.getRoomTypeList() : Collections.emptyList();
|
|
|
+ List<VoyageDataSummaryRespVO.DimensionItemVO> ageList = vo.getAgeGroupList() != null ? vo.getAgeGroupList() : Collections.emptyList();
|
|
|
+ List<VoyageDataSummaryRespVO.DimensionItemVO> domesticList = vo.getDomesticProvinceList() != null ? vo.getDomesticProvinceList() : Collections.emptyList();
|
|
|
+ List<VoyageDataSummaryRespVO.DimensionItemVO> internationalList = vo.getInternationalRegionList() != null ? vo.getInternationalRegionList() : Collections.emptyList();
|
|
|
+
|
|
|
+ // 数据汇总固定4行
|
|
|
+ String[] summaryLabels = {"总人数", "总房间数", "男女比例", "境内外比例"};
|
|
|
+ Object[] summaryValues = {vo.getTotalPeople(), vo.getTotalRooms(), vo.getMaleFemaleRatio(), vo.getDomesticOverseasRatio()};
|
|
|
+
|
|
|
+ // 航次列显示内容:多航次时展示所有航次
|
|
|
+ String voyageCellContent = buildVoyageCellContent(vo);
|
|
|
+
|
|
|
+ int maxRows = Math.max(4, Math.max(roomList.size(),
|
|
|
+ Math.max(ageList.size(),
|
|
|
+ Math.max(domesticList.size(), internationalList.size()))));
|
|
|
+
|
|
|
+ for (int i = 0; i < maxRows; i++) {
|
|
|
+ List<Object> row = new ArrayList<>();
|
|
|
+
|
|
|
+ // 航次(第1行显示,后续为空,由WriteHandler合并)
|
|
|
+ if (i == 0) {
|
|
|
+ row.add(voyageCellContent);
|
|
|
+ } else {
|
|
|
+ row.add("");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 数据汇总(标签和数值)
|
|
|
+ if (i < 4) {
|
|
|
+ row.add(summaryLabels[i]);
|
|
|
+ row.add(summaryValues[i]);
|
|
|
+ } else {
|
|
|
+ row.add("");
|
|
|
+ row.add("");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 房型
|
|
|
+ if (i < roomList.size()) {
|
|
|
+ row.add(roomList.get(i).getName());
|
|
|
+ row.add(roomList.get(i).getCount());
|
|
|
+ row.add(roomList.get(i).getRatio());
|
|
|
+ } else {
|
|
|
+ row.add("");
|
|
|
+ row.add("");
|
|
|
+ row.add("");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 年龄
|
|
|
+ if (i < ageList.size()) {
|
|
|
+ row.add(ageList.get(i).getName());
|
|
|
+ row.add(ageList.get(i).getCount());
|
|
|
+ row.add(ageList.get(i).getRatio());
|
|
|
+ } else {
|
|
|
+ row.add("");
|
|
|
+ row.add("");
|
|
|
+ row.add("");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 境内
|
|
|
+ if (i < domesticList.size()) {
|
|
|
+ row.add(domesticList.get(i).getName());
|
|
|
+ row.add(domesticList.get(i).getCount());
|
|
|
+ row.add(domesticList.get(i).getRatio());
|
|
|
+ } else {
|
|
|
+ row.add("");
|
|
|
+ row.add("");
|
|
|
+ row.add("");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 境外
|
|
|
+ if (i < internationalList.size()) {
|
|
|
+ row.add(internationalList.get(i).getName());
|
|
|
+ row.add(internationalList.get(i).getCount());
|
|
|
+ row.add(internationalList.get(i).getRatio());
|
|
|
+ } else {
|
|
|
+ row.add("");
|
|
|
+ row.add("");
|
|
|
+ row.add("");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 团散占比(只有第1行)
|
|
|
+ if (i == 0) {
|
|
|
+ row.add(vo.getTeamCount());
|
|
|
+ row.add(vo.getIndividualCount());
|
|
|
+ row.add(vo.getTeamIndividualRatio());
|
|
|
+ } else {
|
|
|
+ row.add("");
|
|
|
+ row.add("");
|
|
|
+ row.add("");
|
|
|
+ }
|
|
|
+
|
|
|
+ result.add(row);
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建航次列的显示内容
|
|
|
+ * 单航次:0418航次\n(下水)
|
|
|
+ * 多航次:0418(下水)\n0422(上水)\n...
|
|
|
+ */
|
|
|
+ private String buildVoyageCellContent(VoyageDataSummaryRespVO vo) {
|
|
|
+ List<VoyageDataSummaryRespVO.VoyageInfoVO> infoList = vo.getVoyageInfoList();
|
|
|
+ if (CollUtil.isEmpty(infoList)) {
|
|
|
+ return vo.getVoyageName() + "\n(" + vo.getDirection() + ")";
|
|
|
+ }
|
|
|
+ if (infoList.size() == 1) {
|
|
|
+ return infoList.get(0).getVoyageName() + "\n(" + infoList.get(0).getDirection() + ")";
|
|
|
+ }
|
|
|
+ // 多航次:每行一个航次
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ for (int i = 0; i < infoList.size(); i++) {
|
|
|
+ VoyageDataSummaryRespVO.VoyageInfoVO info = infoList.get(i);
|
|
|
+ if (i > 0) sb.append("\n");
|
|
|
+ sb.append(info.getVoyageName());
|
|
|
+ if (StrUtil.isNotBlank(info.getDirection())) {
|
|
|
+ sb.append("(").append(info.getDirection()).append(")");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
}
|