|
|
@@ -4,14 +4,21 @@ import cn.hutool.core.collection.CollUtil;
|
|
|
import com.alibaba.excel.EasyExcel;
|
|
|
import com.alibaba.excel.converters.longconverter.LongStringConverter;
|
|
|
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
|
|
+import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
+import com.yc.ship.framework.common.pojo.PageResult;
|
|
|
import com.yc.ship.framework.excel.core.util.ExcelUtils;
|
|
|
import com.yc.ship.module.resource.helper.DateHelper;
|
|
|
import com.yc.ship.module.resource.helper.MathHelper;
|
|
|
import com.yc.ship.module.trade.controller.admin.report.vo.CruiseOpsDailyReqVO;
|
|
|
import com.yc.ship.module.trade.controller.admin.report.vo.CruiseOpsDailyRespVO;
|
|
|
+import com.yc.ship.module.trade.controller.admin.report.vo.YangtzePassengerSummaryPageReqVO;
|
|
|
+import com.yc.ship.module.trade.controller.admin.report.vo.YangtzePassengerSummaryRespVO;
|
|
|
import com.yc.ship.module.trade.dal.mysql.report.OpsDailyMapper;
|
|
|
+import com.yc.ship.module.trade.dal.mysql.report.YangtzePassengerSummaryMapper;
|
|
|
import com.yc.ship.module.trade.service.report.OpsDailyService;
|
|
|
import com.yc.ship.module.trade.utils.excel.CruiseOpsDailyExportStyleHandler;
|
|
|
+import com.yc.ship.module.trade.utils.excel.YangtzePassengerSummaryExportStyleHandler;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
@@ -508,4 +515,223 @@ public class OpsDailyServiceImpl implements OpsDailyService {
|
|
|
return val != null ? val : BigDecimal.ZERO;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private YangtzePassengerSummaryMapper yangtzePassengerSummaryMapper;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public PageResult<YangtzePassengerSummaryRespVO> getYangtzePassengerSummaryPage(YangtzePassengerSummaryPageReqVO pageReqVO) {
|
|
|
+ IPage<YangtzePassengerSummaryRespVO> page = yangtzePassengerSummaryMapper.selectYangtzePassengerSummaryPage(
|
|
|
+ new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()), pageReqVO);
|
|
|
+ List<YangtzePassengerSummaryRespVO> records = page.getRecords();
|
|
|
+ // 设置序号
|
|
|
+ int startIdx = (pageReqVO.getPageNo() - 1) * pageReqVO.getPageSize() + 1;
|
|
|
+ for (int i = 0; i < records.size(); i++) {
|
|
|
+ records.get(i).setIndex(startIdx + i);
|
|
|
+ }
|
|
|
+ // 计算派生字段
|
|
|
+ calculateDerivedFields2(records);
|
|
|
+ return new PageResult<>(records, page.getTotal());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void exportYangtzePassengerSummaryExcel(YangtzePassengerSummaryPageReqVO pageReqVO,
|
|
|
+ HttpServletResponse response) throws IOException {
|
|
|
+ // 查询全部数据(不分页)
|
|
|
+ List<YangtzePassengerSummaryRespVO> dataList = yangtzePassengerSummaryMapper.selectYangtzePassengerSummaryExportList(pageReqVO);
|
|
|
+ if (CollUtil.isEmpty(dataList)) {
|
|
|
+ ExcelUtils.exportEmpty(response, "长江行游轮游客数据及营收统计一览表");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ calculateDerivedFields2(dataList);
|
|
|
+
|
|
|
+ // 设置序号
|
|
|
+ int idx = 1;
|
|
|
+ for (YangtzePassengerSummaryRespVO row : dataList) {
|
|
|
+ row.setIndex(idx++);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建多级表头
|
|
|
+ List<List<String>> head = buildExportHeader();
|
|
|
+
|
|
|
+ // 转换数据
|
|
|
+ List<List<Object>> exportData = transformExportData2(dataList);
|
|
|
+
|
|
|
+ // 先设置响应头
|
|
|
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
|
|
|
+ response.addHeader("Content-Disposition",
|
|
|
+ "attachment;filename=" + URLEncoder.encode("长江行游轮游客数据及营收统计一览表.xlsx", StandardCharsets.UTF_8.name()));
|
|
|
+
|
|
|
+ // 使用 EasyExcel 写出
|
|
|
+ YangtzePassengerSummaryExportStyleHandler styleHandler = new YangtzePassengerSummaryExportStyleHandler();
|
|
|
+ EasyExcel.write(response.getOutputStream())
|
|
|
+ .head(head)
|
|
|
+ .autoCloseStream(false)
|
|
|
+ .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
|
|
+ .registerWriteHandler(styleHandler)
|
|
|
+ .registerConverter(new LongStringConverter())
|
|
|
+ .sheet("长江行游轮游客数据及营收统计")
|
|
|
+ .doWrite(exportData);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 私有方法 ====================
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算派生字段(床位载客率、团散比、境内外比、均价)
|
|
|
+ */
|
|
|
+ private void calculateDerivedFields2(List<YangtzePassengerSummaryRespVO> dataList) {
|
|
|
+ for (YangtzePassengerSummaryRespVO row : dataList) {
|
|
|
+ // 床位载客率 = 实收人数总数 / 客容量 * 100
|
|
|
+ if (row.getCapacity() != null && row.getCapacity() > 0 && row.getTotalPassengers() != null) {
|
|
|
+ double rate = (double) row.getTotalPassengers() / row.getCapacity() * 100;
|
|
|
+ row.setBedOccupancyRate(String.format("%.1f%%", rate));
|
|
|
+ } else {
|
|
|
+ row.setBedOccupancyRate("-");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 团散比
|
|
|
+ int group = row.getGroupCount() != null ? row.getGroupCount() : 0;
|
|
|
+ int individual = row.getIndividualCount() != null ? row.getIndividualCount() : 0;
|
|
|
+ row.setGroupIndividualRatio(calcRatio(group, individual));
|
|
|
+
|
|
|
+ // 境内外比
|
|
|
+ int domestic = row.getDomesticCount() != null ? row.getDomesticCount() : 0;
|
|
|
+ int overseas = row.getOverseasCount() != null ? row.getOverseasCount() : 0;
|
|
|
+ row.setDomesticOverseasRatio(calcRatio(domestic, overseas));
|
|
|
+
|
|
|
+ // 均价 = 实收 / 购票人数
|
|
|
+ if (row.getReceivedAmount() != null && row.getTicketedPassengers() != null && row.getTicketedPassengers() > 0) {
|
|
|
+ row.setAvgPrice(row.getReceivedAmount().divide(
|
|
|
+ BigDecimal.valueOf(row.getTicketedPassengers()), 2, RoundingMode.HALF_UP));
|
|
|
+ } else {
|
|
|
+ row.setAvgPrice(BigDecimal.ZERO);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 未收 = 应收 - 实收
|
|
|
+ BigDecimal receivable = row.getReceivableAmount() != null ? row.getReceivableAmount() : BigDecimal.ZERO;
|
|
|
+ BigDecimal received = row.getReceivedAmount() != null ? row.getReceivedAmount() : BigDecimal.ZERO;
|
|
|
+ row.setUnreceivedAmount(receivable.subtract(received));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算比率字符串(如 2:1)
|
|
|
+ */
|
|
|
+ private String calcRatio(int a, int b) {
|
|
|
+ if (b == 0 && a == 0) return "-";
|
|
|
+ if (b == 0) return a + ":0";
|
|
|
+ if (a == 0) return "0:" + b;
|
|
|
+ int gcd = gcd(a, b);
|
|
|
+ return (a / gcd) + ":" + (b / gcd);
|
|
|
+ }
|
|
|
+
|
|
|
+ private int gcd(int a, int b) {
|
|
|
+ a = Math.abs(a);
|
|
|
+ b = Math.abs(b);
|
|
|
+ while (b != 0) {
|
|
|
+ int temp = b;
|
|
|
+ b = a % b;
|
|
|
+ a = temp;
|
|
|
+ }
|
|
|
+ return a;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建导出多级表头
|
|
|
+ * 与图片的列结构保持一致
|
|
|
+ */
|
|
|
+ private List<List<String>> buildExportHeader() {
|
|
|
+ List<List<String>> head = new ArrayList<>();
|
|
|
+
|
|
|
+ // 1. 序号
|
|
|
+ head.add(new ArrayList<>(Collections.singletonList("序号")));
|
|
|
+ // 2. 船舶
|
|
|
+ head.add(new ArrayList<>(Collections.singletonList("船舶")));
|
|
|
+ // 3. 航次
|
|
|
+ head.add(new ArrayList<>(Collections.singletonList("航次")));
|
|
|
+ // 4. 倒计时(天)
|
|
|
+ head.add(new ArrayList<>(Collections.singletonList("倒计时(天)")));
|
|
|
+ // 5-7. 预订间数
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("预订间数", "总数")));
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("预订间数", "实收房间数")));
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("预订间数", "占位房间数")));
|
|
|
+ // 8-10. 实收人数
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("实收人数", "总数")));
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("实收人数", "购票人数")));
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("实收人数", "免票人数")));
|
|
|
+ // 11. 预计收客人数
|
|
|
+ head.add(new ArrayList<>(Collections.singletonList("预计收客人数")));
|
|
|
+ // 12-15. 财务情况
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("财务情况", "应收")));
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("财务情况", "实收")));
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("财务情况", "未收")));
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("财务情况", "均价")));
|
|
|
+ // 16-17. 床位载客率
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("床位载客率(人/航次)", "客容量")));
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("床位载客率(人/航次)", "载客率")));
|
|
|
+ // 18-21. 实收详情
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("实收详情", "成人")));
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("实收详情", "儿童")));
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("实收详情", "婴儿")));
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("实收详情", "陪同+领队")));
|
|
|
+ // 22-27. 团队情况
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("团队情况", "团队人次(>10人)")));
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("团队情况", "散客人次(<10人)")));
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("团队情况", "团散比")));
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("团队情况", "境内")));
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("团队情况", "境外")));
|
|
|
+ head.add(new ArrayList<>(Arrays.asList("团队情况", "境内外比")));
|
|
|
+
|
|
|
+ return head;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将报表数据转为 EasyExcel 导出格式
|
|
|
+ */
|
|
|
+ private List<List<Object>> transformExportData2(List<YangtzePassengerSummaryRespVO> dataList) {
|
|
|
+ List<List<Object>> result = new ArrayList<>(dataList.size());
|
|
|
+ for (YangtzePassengerSummaryRespVO row : dataList) {
|
|
|
+ List<Object> rowData = new ArrayList<>(27);
|
|
|
+ rowData.add(row.getIndex() != null ? String.valueOf(row.getIndex()) : "");
|
|
|
+ rowData.add(row.getShipName() != null ? row.getShipName() : "");
|
|
|
+ rowData.add(row.getVoyageInfo() != null ? row.getVoyageInfo() : "");
|
|
|
+ rowData.add(row.getCountdown() != null ? String.valueOf(row.getCountdown()) : "");
|
|
|
+ rowData.add(formatNumber2(row.getTotalRooms()));
|
|
|
+ rowData.add(formatNumber2(row.getPaidRooms()));
|
|
|
+ rowData.add(formatNumber2(row.getReservedRooms()));
|
|
|
+ rowData.add(formatNumber2(row.getTotalPassengers()));
|
|
|
+ rowData.add(formatNumber2(row.getTicketedPassengers()));
|
|
|
+ rowData.add(formatNumber2(row.getFreePassengers()));
|
|
|
+ rowData.add(formatNumber2(row.getEstimatedPassengers()));
|
|
|
+ rowData.add(formatMoney(row.getReceivableAmount()));
|
|
|
+ rowData.add(formatMoney(row.getReceivedAmount()));
|
|
|
+ rowData.add(formatMoney(row.getUnreceivedAmount()));
|
|
|
+ rowData.add(formatMoney(row.getAvgPrice()));
|
|
|
+ rowData.add(formatNumber2(row.getCapacity()));
|
|
|
+ rowData.add(row.getBedOccupancyRate() != null ? row.getBedOccupancyRate() : "");
|
|
|
+ rowData.add(formatNumber2(row.getAdultCount()));
|
|
|
+ rowData.add(formatNumber2(row.getChildCount()));
|
|
|
+ rowData.add(formatNumber2(row.getInfantCount()));
|
|
|
+ rowData.add(formatNumber2(row.getCompanionLeaderCount()));
|
|
|
+ rowData.add(formatNumber2(row.getGroupCount()));
|
|
|
+ rowData.add(formatNumber2(row.getIndividualCount()));
|
|
|
+ rowData.add(row.getGroupIndividualRatio() != null ? row.getGroupIndividualRatio() : "");
|
|
|
+ rowData.add(formatNumber2(row.getDomesticCount()));
|
|
|
+ rowData.add(formatNumber2(row.getOverseasCount()));
|
|
|
+ rowData.add(row.getDomesticOverseasRatio() != null ? row.getDomesticOverseasRatio() : "");
|
|
|
+ result.add(rowData);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private String formatNumber2(Integer val) {
|
|
|
+ return val != null ? String.valueOf(val) : "0";
|
|
|
+ }
|
|
|
+
|
|
|
+ private String formatMoney(BigDecimal amount) {
|
|
|
+ if (amount == null) return "0.00";
|
|
|
+ return amount.setScale(2, RoundingMode.HALF_UP).toPlainString();
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
}
|