package com.java110.fee.cmd.fee; import com.alibaba.fastjson.JSONObject; import com.java110.core.annotation.Java110Cmd; import com.java110.core.context.ICmdDataFlowContext; import com.java110.core.event.cmd.Cmd; import com.java110.core.event.cmd.CmdEvent; import com.java110.dto.community.CommunityDto; import com.java110.dto.fee.FeeConfigDto; import com.java110.dto.room.RoomDto; import com.java110.intf.community.ICommunityInnerServiceSMO; import com.java110.intf.community.IRoomInnerServiceSMO; import com.java110.intf.fee.IFeeConfigInnerServiceSMO; import com.java110.intf.fee.IReportFeeInnerServiceSMO; import com.java110.utils.exception.CmdException; import com.java110.utils.util.Assert; import com.java110.utils.util.BeanConvertUtil; import com.java110.utils.util.DateUtil; import com.java110.vo.ResultVo; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.streaming.SXSSFWorkbook; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.math.BigDecimal; import java.text.ParseException; import java.util.*; import java.util.stream.Collectors; @Java110Cmd(serviceCode = "fee.exportPropertyFeeExcelCmd") public class ExportPropertyFeeExcelCmd extends Cmd { @Autowired private IReportFeeInnerServiceSMO reportFeeInnerServiceSMOImpl; @Autowired private ICommunityInnerServiceSMO communityInnerServiceSMOImpl; @Autowired private IRoomInnerServiceSMO roomInnerServiceSMOImpl; @Autowired private IFeeConfigInnerServiceSMO feeConfigInnerServiceSMOImpl; @Override public void validate(CmdEvent event, ICmdDataFlowContext context, JSONObject reqJson) throws CmdException, ParseException { Assert.hasKey(reqJson, "communityId", "请求报文中未包含小区编号"); } @Override public void doCmd(CmdEvent event, ICmdDataFlowContext context, JSONObject reqJson) throws CmdException, ParseException { // 设置年份范围:2020年至当前年份 int startYear = 2020; int currentYear = DateUtil.getYear(); int endYear = currentYear; String communityId = reqJson.getString("communityId"); // 获取小区信息 CommunityDto communityDto = new CommunityDto(); communityDto.setCommunityId(communityId); List communityDtos = communityInnerServiceSMOImpl.queryCommunitys(communityDto); Assert.notEmpty(communityDtos, "未查询到小区信息"); // 查询房屋数据(不分页,获取全部) RoomDto roomDto = new RoomDto(); roomDto.setCommunityId(communityId); roomDto.setFloorNum(reqJson.containsKey("floorId") && !reqJson.getString("floorId").equals("") ? reqJson.getString("floorId") : null); roomDto.setOwnerName(reqJson.containsKey("ownerName") && !reqJson.getString("ownerName").equals("") ? reqJson.getString("ownerName") : null); // 取消分页限制 roomDto.setRow(Integer.MAX_VALUE); roomDto.setPage(1); List rooms = roomInnerServiceSMOImpl.queryRoomsAsReport(roomDto); if (rooms.isEmpty()) { ResultVo resultVo = new ResultVo("未查询到数据"); ResponseEntity responseEntity = new ResponseEntity<>(resultVo.toString(), HttpStatus.OK); context.setResponseEntity(responseEntity); return; } // 获取费用配置 FeeConfigDto feeConfigDto = new FeeConfigDto(); feeConfigDto.setCommunityId(communityId); if (reqJson.containsKey("feeTypeCd") && !reqJson.getString("feeTypeCd").equals("")) { feeConfigDto.setFeeTypeCd(reqJson.getString("feeTypeCd")); } else { feeConfigDto.setFeeTypeCds(new String[]{"630000001"}); } if (reqJson.containsKey("importFee") && !reqJson.getString("importFee").equals("")) { feeConfigDto.setFeeNameEq(reqJson.getString("importFee")); } else { feeConfigDto.setFeeTypeCds(new String[]{"630000001"}); } List originalDtos = feeConfigInnerServiceSMOImpl.queryFeeConfigs(feeConfigDto); // 处理费用配置:去重、排序 List feeConfigDtos = originalDtos.stream() .sorted(Comparator.comparing(FeeConfigDto::getSquarePrice, Comparator.nullsLast(Comparator.naturalOrder())) .thenComparing(FeeConfigDto::getStartTime, Comparator.nullsLast(Comparator.reverseOrder()))) .collect(Collectors.groupingBy(dto -> (dto.getSquarePrice() == null) ? new Object() : dto.getSquarePrice(), Collectors.toList())) .values().stream() .map(group -> group.get(0)) .collect(Collectors.toList()); // 创建Excel try (SXSSFWorkbook workbook = new SXSSFWorkbook(100); // 内存中保留100行,其余写入临时文件 ByteArrayOutputStream out = new ByteArrayOutputStream()) { // 创建工作表 Sheet sheet = workbook.createSheet("物业费报表_" + communityDtos.get(0).getName()); // 创建表头样式 CellStyle headerStyle = createHeaderStyle(workbook); // 创建内容样式 CellStyle contentStyle = createContentStyle(workbook); // 生成表头 int headerRowIndex = createExcelHeader(sheet, headerStyle, feeConfigDtos, startYear, endYear); // 填充数据 fillExcelData(sheet, contentStyle, rooms, feeConfigDtos, startYear, endYear, headerRowIndex); // 调整列宽 autoSizeColumns(sheet, 0, 15 + 2 * feeConfigDtos.size() + 3 * (endYear - startYear + 1)); // 写入输出流 workbook.write(out); workbook.dispose(); // 清理临时文件 // 构建响应 byte[] bytes = out.toByteArray(); HttpHeaders headers = createExcelHeaders("物业费报表_" + DateUtil.getNow("yyyy-MM-dd") + ".xlsx"); ResponseEntity responseEntity = new ResponseEntity<>(bytes, headers, HttpStatus.OK); context.setResponseEntity(responseEntity); } catch (IOException e) { throw new CmdException("导出Excel失败:" + e.getMessage()); } } /** * 创建表头样式 */ private CellStyle createHeaderStyle(Workbook workbook) { CellStyle style = workbook.createCellStyle(); // 设置边框 style.setBorderTop(BorderStyle.THIN); style.setBorderBottom(BorderStyle.THIN); style.setBorderLeft(BorderStyle.THIN); style.setBorderRight(BorderStyle.THIN); // 设置居中 style.setAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER); // 设置背景色 style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); style.setFillPattern(FillPatternType.SOLID_FOREGROUND); // 设置字体 Font font = workbook.createFont(); font.setBold(true); style.setFont(font); return style; } /** * 创建内容样式 */ private CellStyle createContentStyle(Workbook workbook) { CellStyle style = workbook.createCellStyle(); // 设置边框 style.setBorderTop(BorderStyle.THIN); style.setBorderBottom(BorderStyle.THIN); style.setBorderLeft(BorderStyle.THIN); style.setBorderRight(BorderStyle.THIN); // 设置居中 style.setAlignment(HorizontalAlignment.CENTER); style.setVerticalAlignment(VerticalAlignment.CENTER); return style; } /** * 创建Excel表头 */ private int createExcelHeader(Sheet sheet, CellStyle headerStyle, List feeConfigDtos, int startYear, int endYear) { int rowIndex = 0; int feeCount = feeConfigDtos.size(); int yearCount = endYear - startYear + 1; // 第一行表头 Row row1 = sheet.createRow(rowIndex++); int colIndex = 0; // 基础信息列 String[] baseHeaders = {"序号", "物业类型", "楼栋号/弄", "门号", "室号", "门室号", "产证地址", "收费面积(m²)", "购房人姓名"}; for (String header : baseHeaders) { createCell(row1, colIndex++, header, headerStyle); } // 费用配置列 for (int i = 0; i < feeCount; i++) { createCell(row1, colIndex++, feeConfigDtos.get(i).getFeeName(), headerStyle); } // 其他固定列 createCell(row1, colIndex++, "设备运作费", headerStyle); for (int i = 0; i < feeCount; i++) { createCell(row1, colIndex++, feeConfigDtos.get(i).getFeeName(), headerStyle); } createCell(row1, colIndex++, "年应收款", headerStyle); createCell(row1, colIndex++, "合计(" + startYear + "年1月-至今)", headerStyle); createCell(row1, colIndex++, "历年实收", headerStyle); createCell(row1, colIndex++, "操作", headerStyle); // 第二行表头 Row row2 = sheet.createRow(rowIndex++); colIndex = 0; // 跳过基础信息列 colIndex += baseHeaders.length + feeCount + 1 + feeCount + 1; // 统计信息列 String[] statsHeaders = {"应收", "实收", "代收", startYear + "年", (startYear + 1) + "年", "待收月数", "待收金额", "代收区间"}; for (String header : statsHeaders) { createCell(row2, colIndex++, header, headerStyle); } // 年份列 for (int i = startYear; i <= endYear; i++) { createCell(row2, colIndex++, String.valueOf(i), headerStyle); } createCell(row2, colIndex++, "打折金额汇总", headerStyle); // 第三行表头 Row row3 = sheet.createRow(rowIndex++); colIndex = 0; // 跳过前面的列 colIndex += baseHeaders.length + feeCount + 1 + feeCount + 1 + statsHeaders.length; // 年份详情列 for (int i = startYear; i <= endYear; i++) { createCell(row3, colIndex++, "已收月数", headerStyle); createCell(row3, colIndex++, "已收区间", headerStyle); createCell(row3, colIndex++, "未收区间", headerStyle); } // 合并单元格 // 这里可以根据需要添加单元格合并逻辑 return rowIndex; } /** * 填充Excel数据 */ private void fillExcelData(Sheet sheet, CellStyle contentStyle, List rooms, List feeConfigDtos, int startYear, int endYear, int startRowIndex) { int rowIndex = startRowIndex; int feeCount = feeConfigDtos.size(); int yearCount = endYear - startYear + 1; for (int i = 0; i < rooms.size(); i++) { Map room = rooms.get(i); Row row = sheet.createRow(rowIndex++); int colIndex = 0; // 基础信息 createCell(row, colIndex++, String.valueOf(i + 1), contentStyle); // 序号 createCell(row, colIndex++, room.get("property_type") != null ? room.get("property_type").toString() : "", contentStyle); createCell(row, colIndex++, room.get("floor_num") != null ? room.get("floor_num").toString() : "", contentStyle); createCell(row, colIndex++, room.get("unit_num") != null ? room.get("unit_num").toString() : "", contentStyle); createCell(row, colIndex++, room.get("room_num") != null ? room.get("room_num").toString() : "", contentStyle); createCell(row, colIndex++, (room.get("unit_num") != null ? room.get("unit_num").toString() : "") + "-" + (room.get("room_num") != null ? room.get("room_num").toString() : ""), contentStyle); createCell(row, colIndex++, room.get("property_address") != null ? room.get("property_address").toString() : "", contentStyle); createCell(row, colIndex++, room.get("room_area") != null ? room.get("room_area").toString() : "", contentStyle); createCell(row, colIndex++, room.get("name") != null ? room.get("name").toString() : "", contentStyle); // 费用配置信息 for (FeeConfigDto config : feeConfigDtos) { createCell(row, colIndex++, config.getSquarePrice() != null ? config.getSquarePrice() : "", contentStyle); } // 设备运作费 createCell(row, colIndex++, "", contentStyle); // 费用计算 BigDecimal roomArea = room.get("room_area") != null ? (BigDecimal) room.get("room_area") : BigDecimal.ZERO; for (FeeConfigDto config : feeConfigDtos) { double price = config.getSquarePrice() != null ? Double.parseDouble(config.getSquarePrice()) : 0; double total = price * roomArea.doubleValue(); createCell(row, colIndex++, String.valueOf(doublequ2(total)), contentStyle); } // 年应收款 double year应收 = feeConfigDtos.stream() .mapToDouble(config -> { double price = config.getSquarePrice() != null ? Double.parseDouble(config.getSquarePrice()) : 0; return price * roomArea.doubleValue() * 12; }) .sum(); createCell(row, colIndex++, String.valueOf(doublequ2(year应收)), contentStyle); // 获取费用数据 HashMap params = new HashMap<>(); params.put("payObjId", room.get("room_id")); List feeData = reportFeeInnerServiceSMOImpl.onceRoomFee(params); // 计算合计信息 double allMoney = 0; double allPayMoney = 0; int monthCount = 0; int noPayMoney = 0; double discountMoney = 0; for (Map fee : feeData) { if (fee.containsKey("统计维度") && !fee.get("统计维度").toString().contains("总计")) { allMoney += ((BigDecimal) fee.get("应收金额")).doubleValue(); allPayMoney += ((BigDecimal) fee.get("实收金额")).doubleValue(); monthCount += ((BigDecimal) fee.get("未收月数")).intValue(); noPayMoney += ((BigDecimal) fee.get("未收金额")).intValue(); discountMoney += ((BigDecimal) fee.get("折扣金额")).doubleValue(); } } // 填充合计信息 createCell(row, colIndex++, String.valueOf(doublequ2(allMoney)), contentStyle); // 合计 createCell(row, colIndex++, String.valueOf(doublequ2(allPayMoney)), contentStyle); // 历年实收 createCell(row, colIndex++, "", contentStyle); // 操作 // 填充统计信息 createCell(row, colIndex++, String.valueOf(doublequ2(allMoney)), contentStyle); // 应收 createCell(row, colIndex++, String.valueOf(doublequ2(allPayMoney)), contentStyle); // 实收 createCell(row, colIndex++, "", contentStyle); // 代收 createCell(row, colIndex++, "", contentStyle); // 2020年 createCell(row, colIndex++, "", contentStyle); // 2021年 createCell(row, colIndex++, String.valueOf(monthCount), contentStyle); // 待收月数 createCell(row, colIndex++, String.valueOf(noPayMoney), contentStyle); // 待收金额 createCell(row, colIndex++, "", contentStyle); // 代收区间 // 填充年份数据 for (int year = startYear; year <= endYear; year++) { // 年份总数据(占位) createCell(row, colIndex++, "", contentStyle); // 已收月数、已收区间、未收区间 String receivedMonths = "0"; String receivedRange = ""; String unpaidRange = ""; for (Map fee : feeData) { if (fee.get("统计维度").equals(year + "年")) { receivedMonths = ((BigDecimal) fee.get("已收月数")).intValue() + ""; receivedRange = fee.get("已收区间") != null ? fee.get("已收区间").toString() : ""; unpaidRange = fee.get("未收区间") != null ? fee.get("未收区间").toString() : ""; break; } } createCell(row, colIndex++, receivedMonths, contentStyle); createCell(row, colIndex++, receivedRange, contentStyle); createCell(row, colIndex++, unpaidRange, contentStyle); } // 打折金额汇总 createCell(row, colIndex++, String.valueOf(doublequ2(discountMoney)), contentStyle); } } /** * 创建单元格并设置值和样式 */ private void createCell(Row row, int column, String value, CellStyle style) { Cell cell = row.createCell(column); cell.setCellValue(value); cell.setCellStyle(style); } /** * 自动调整列宽 */ private void autoSizeColumns(Sheet sheet, int startCol, int endCol) { for (int i = startCol; i <= endCol; i++) { sheet.autoSizeColumn(i); // 适当调整自动列宽,避免过窄 int width = sheet.getColumnWidth(i) + 256; sheet.setColumnWidth(i, width > 65535 ? 65535 : width); } } /** * 创建Excel响应头 */ private HttpHeaders createExcelHeaders(String fileName) { HttpHeaders headers = new HttpHeaders(); try { // 设置文件名编码 String encodedFileName = new String(fileName.getBytes("UTF-8"), "ISO-8859-1"); headers.setContentDispositionFormData("attachment", encodedFileName); } catch (Exception e) { headers.setContentDispositionFormData("attachment", fileName); } headers.setContentType(org.springframework.http.MediaType.APPLICATION_OCTET_STREAM); return headers; } /** * 保留两位小数 */ public double doublequ2(double num) { return (int) (num * 100) / 100.0; } }