|
|
@@ -0,0 +1,381 @@
|
|
|
+package com.jeeplus.assess.program.configuration.projectList.utils;
|
|
|
+
|
|
|
+import com.jeeplus.assess.invoice.domain.FinanceInvoice;
|
|
|
+import com.jeeplus.assess.invoice.domain.FinanceInvoiceBase;
|
|
|
+import com.jeeplus.assess.program.configuration.projectList.domain.ProgramArchive;
|
|
|
+import com.jeeplus.assess.program.configuration.projectList.domain.ProgramProjectListInfo;
|
|
|
+import com.jeeplus.assess.program.configuration.projectList.utils.dto.ProvincialFilingRowData;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+import org.apache.poi.ss.usermodel.*;
|
|
|
+import org.apache.poi.ss.util.CellRangeAddress;
|
|
|
+
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
+import java.io.InputStream;
|
|
|
+import java.io.OutputStream;
|
|
|
+import java.net.URLEncoder;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 省级项目备案Excel导入工具类
|
|
|
+ * 负责Excel文件的读取、列追加、样式设置和输出下载
|
|
|
+ */
|
|
|
+public class ProvincialFilingExcelUtils {
|
|
|
+
|
|
|
+ /** 追加的8列详细表头 */
|
|
|
+ private static final String[] NEW_HEADERS = {"是否归档", "收费标准", "责任人", "开票编号", "开票金额", "是否开票", "开票状态", "开票时间"};
|
|
|
+ /** 大类表头名称 */
|
|
|
+ private static final String CATEGORY_HEADER = "项目与开票信息";
|
|
|
+ /** 需要匹配的列名 */
|
|
|
+ private static final String REPORT_NO_HEADER = "报告文号";
|
|
|
+ /** 是否作废列名 */
|
|
|
+ private static final String CANCEL_HEADER = "是否作废";
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 读取Excel并提取报告文号列索引、是否作废列索引和所有数据行信息
|
|
|
+ * @param inputStream Excel文件流
|
|
|
+ * @return [0]=Workbook, [1]=报告文号列索引(Integer), [2]=最后列索引(Integer),
|
|
|
+ * [3]=行号→报告文号映射(Map<Integer,String>), [4]=是否作废列索引(Integer),
|
|
|
+ * [5]=行号→是否作废值映射(Map<Integer,String>)
|
|
|
+ */
|
|
|
+ public static Object[] readAndExtractReportNos(InputStream inputStream) throws Exception {
|
|
|
+ Workbook workbook = WorkbookFactory.create(inputStream);
|
|
|
+ Sheet sheet = workbook.getSheetAt(0);
|
|
|
+
|
|
|
+ Row headerRow = sheet.getRow(1);
|
|
|
+ if (headerRow == null) {
|
|
|
+ workbook.close();
|
|
|
+ throw new RuntimeException("Excel格式不正确,未找到表头行");
|
|
|
+ }
|
|
|
+
|
|
|
+ int reportNoColIndex = -1;
|
|
|
+ int cancelColIndex = -1;
|
|
|
+ int lastColIndex = 0;
|
|
|
+ for (int i = 0; i <= headerRow.getLastCellNum(); i++) {
|
|
|
+ Cell cell = headerRow.getCell(i);
|
|
|
+ if (cell == null) continue;
|
|
|
+ String headerName = getCellStringValue(cell);
|
|
|
+ if (REPORT_NO_HEADER.equals(headerName)) {
|
|
|
+ reportNoColIndex = i;
|
|
|
+ }
|
|
|
+ if (CANCEL_HEADER.equals(headerName)) {
|
|
|
+ cancelColIndex = i;
|
|
|
+ }
|
|
|
+ if (i > lastColIndex) {
|
|
|
+ lastColIndex = i;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (reportNoColIndex == -1) {
|
|
|
+ workbook.close();
|
|
|
+ throw new RuntimeException("未在Excel表头中找到\"" + REPORT_NO_HEADER + "\"列,请确认文件格式");
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<Integer, String> rowReportNoMap = new LinkedHashMap<>();
|
|
|
+ Map<Integer, String> rowCancelMap = new LinkedHashMap<>();
|
|
|
+ for (int i = 2; i <= sheet.getLastRowNum(); i++) {
|
|
|
+ Row row = sheet.getRow(i);
|
|
|
+ if (row == null) continue;
|
|
|
+ Cell cell = row.getCell(reportNoColIndex);
|
|
|
+ String reportNo = getCellStringValue(cell);
|
|
|
+ if (StringUtils.isNotBlank(reportNo)) {
|
|
|
+ rowReportNoMap.put(i, reportNo);
|
|
|
+ // 提取是否作废列的值
|
|
|
+ if (cancelColIndex != -1) {
|
|
|
+ Cell cancelCell = row.getCell(cancelColIndex);
|
|
|
+ String cancelValue = getCellStringValue(cancelCell);
|
|
|
+ rowCancelMap.put(i, StringUtils.defaultString(cancelValue));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return new Object[]{workbook, reportNoColIndex, lastColIndex, rowReportNoMap, cancelColIndex, rowCancelMap};
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将查询到的数据追加到Excel末尾7列,并输出下载
|
|
|
+ */
|
|
|
+ public static void appendColumnsAndDownload(
|
|
|
+ Workbook workbook,
|
|
|
+ int lastColIndex,
|
|
|
+ Map<Integer, String> rowReportNoMap,
|
|
|
+ Map<String, ProvincialFilingRowData> dataMap,
|
|
|
+ String originalFileName,
|
|
|
+ HttpServletResponse response
|
|
|
+ ) throws Exception {
|
|
|
+ Sheet sheet = workbook.getSheetAt(0);
|
|
|
+ Row headerRow = sheet.getRow(1);
|
|
|
+ int newColCount = NEW_HEADERS.length;
|
|
|
+
|
|
|
+ // 检测第0行已有合并区域的最大结束列,避免重叠冲突
|
|
|
+ int maxMergedEndCol = -1;
|
|
|
+ for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
|
|
|
+ CellRangeAddress merged = sheet.getMergedRegion(i);
|
|
|
+ if (merged.getFirstRow() == 0 && merged.getLastRow() == 0) {
|
|
|
+ if (merged.getLastColumn() > maxMergedEndCol) {
|
|
|
+ maxMergedEndCol = merged.getLastColumn();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 新列起始位置 = max(表头最后列, 已有合并区域结束列+1)
|
|
|
+ int newStartCol = Math.max(lastColIndex, maxMergedEndCol + 1);
|
|
|
+
|
|
|
+ // 创建样式
|
|
|
+ CellStyle lightGreenStyle = createStyle(workbook, IndexedColors.LIGHT_GREEN);
|
|
|
+ CellStyle lightYellowStyle = createStyle(workbook, IndexedColors.LEMON_CHIFFON);
|
|
|
+ CellStyle normalBorderStyle = createBorderStyle(workbook);
|
|
|
+
|
|
|
+ // 写第1行大类表头(合并单元格)
|
|
|
+ Row categoryRow = sheet.getRow(0);
|
|
|
+ if (categoryRow == null) {
|
|
|
+ categoryRow = sheet.createRow(0);
|
|
|
+ }
|
|
|
+ Cell categoryCell = categoryRow.createCell(newStartCol);
|
|
|
+ categoryCell.setCellValue(CATEGORY_HEADER);
|
|
|
+ categoryCell.setCellStyle(lightGreenStyle);
|
|
|
+ if (newColCount > 1) {
|
|
|
+ sheet.addMergedRegion(new CellRangeAddress(0, 0, newStartCol, newStartCol + newColCount - 1));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 写第2行详细表头
|
|
|
+ for (int i = 0; i < newColCount; i++) {
|
|
|
+ Cell cell = headerRow.createCell(newStartCol + i);
|
|
|
+ cell.setCellValue(NEW_HEADERS[i]);
|
|
|
+ cell.setCellStyle(lightGreenStyle);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 遍历数据行,填入新列数据
|
|
|
+ for (Map.Entry<Integer, String> entry : rowReportNoMap.entrySet()) {
|
|
|
+ int rowIndex = entry.getKey();
|
|
|
+ String reportNo = entry.getValue();
|
|
|
+ Row dataRow = sheet.getRow(rowIndex);
|
|
|
+ if (dataRow == null) {
|
|
|
+ dataRow = sheet.createRow(rowIndex);
|
|
|
+ }
|
|
|
+
|
|
|
+ ProvincialFilingRowData rowData = dataMap.get(reportNo);
|
|
|
+ boolean needHighlight = (rowData != null && rowData.isNeedHighlight());
|
|
|
+
|
|
|
+ // 新列整列绿色背景
|
|
|
+ CellStyle colStyle = lightGreenStyle;
|
|
|
+
|
|
|
+ String isArchived = (rowData != null) ? rowData.getIsArchived() : "否";
|
|
|
+ String reportCharges = (rowData != null) ? rowData.getReportCharges() : "";
|
|
|
+ String projectManagerName = (rowData != null) ? rowData.getProjectManagerName() : "";
|
|
|
+ String invoiceNos = (rowData != null && rowData.getInvoiceNos() != null) ? String.join(",", rowData.getInvoiceNos()) : "";
|
|
|
+ Double invoiceAmount = (rowData != null) ? rowData.getInvoiceAmount() : null;
|
|
|
+ String isInvoiced = (rowData != null) ? rowData.getIsInvoiced() : "否";
|
|
|
+ String invoiceStatuses = (rowData != null && rowData.getInvoiceStatuses() != null) ? String.join(",", rowData.getInvoiceStatuses()) : "";
|
|
|
+ String invoiceDates = (rowData != null && rowData.getInvoiceDates() != null) ? String.join(",", rowData.getInvoiceDates()) : "";
|
|
|
+
|
|
|
+ // 是否归档
|
|
|
+ setCell(dataRow, newStartCol, isArchived, colStyle);
|
|
|
+ // 收费标准
|
|
|
+ setCell(dataRow, newStartCol + 1, reportCharges, colStyle);
|
|
|
+ // 责任人
|
|
|
+ setCell(dataRow, newStartCol + 2, projectManagerName, colStyle);
|
|
|
+ // 开票编号
|
|
|
+ setCell(dataRow, newStartCol + 3, invoiceNos, colStyle);
|
|
|
+ // 开票金额
|
|
|
+ Cell amountCell = dataRow.createCell(newStartCol + 4);
|
|
|
+ if (invoiceAmount != null) {
|
|
|
+ amountCell.setCellValue(invoiceAmount);
|
|
|
+ }
|
|
|
+ amountCell.setCellStyle(colStyle);
|
|
|
+ // 是否开票
|
|
|
+ setCell(dataRow, newStartCol + 5, isInvoiced, colStyle);
|
|
|
+ // 开票状态
|
|
|
+ setCell(dataRow, newStartCol + 6, invoiceStatuses, colStyle);
|
|
|
+ // 开票时间
|
|
|
+ setCell(dataRow, newStartCol + 7, invoiceDates, colStyle);
|
|
|
+
|
|
|
+ // 如果需要标黄,将整行所有单元格设为黄色(覆盖绿色)
|
|
|
+ if (needHighlight) {
|
|
|
+ int lastCellNum = dataRow.getLastCellNum();
|
|
|
+ for (int col = 0; col < lastCellNum; col++) {
|
|
|
+ Cell cell = dataRow.getCell(col);
|
|
|
+ if (cell != null) {
|
|
|
+ cell.setCellStyle(lightYellowStyle);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 输出下载
|
|
|
+ String downloadName = "备案数据_" + originalFileName;
|
|
|
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
|
|
+ response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(downloadName, "UTF-8"));
|
|
|
+ OutputStream out = response.getOutputStream();
|
|
|
+ workbook.write(out);
|
|
|
+ out.flush();
|
|
|
+ out.close();
|
|
|
+ workbook.close();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据查询结果构建每行的数据
|
|
|
+ */
|
|
|
+ public static Map<String, ProvincialFilingRowData> buildDataMap(
|
|
|
+ List<String> reportNoList,
|
|
|
+ Map<String, List<FinanceInvoiceBase>> reportNoToBasesMap,
|
|
|
+ Map<String, FinanceInvoice> invoiceMap,
|
|
|
+ Map<String, Set<String>> invoiceToReportNosMap,
|
|
|
+ Map<String, ProgramProjectListInfo> programMap,
|
|
|
+ Map<String, ProgramArchive> archiveMap
|
|
|
+ ) {
|
|
|
+ Map<String, ProvincialFilingRowData> dataMap = new HashMap<>();
|
|
|
+
|
|
|
+ for (String reportNo : reportNoList) {
|
|
|
+ ProvincialFilingRowData rowData = new ProvincialFilingRowData();
|
|
|
+ List<FinanceInvoiceBase> bases = reportNoToBasesMap.get(reportNo);
|
|
|
+
|
|
|
+ if (bases != null && !bases.isEmpty()) {
|
|
|
+ // 取第一个base的programId获取项目和归档信息
|
|
|
+ String programId = bases.get(0).getProgramId();
|
|
|
+ if (StringUtils.isNotBlank(programId)) {
|
|
|
+ ProgramProjectListInfo prog = programMap.get(programId);
|
|
|
+ if (prog != null) {
|
|
|
+ rowData.setProjectManagerName(StringUtils.defaultString(prog.getProjectManagerName()));
|
|
|
+ }
|
|
|
+ ProgramArchive archive = archiveMap.get(programId);
|
|
|
+ if (archive != null) {
|
|
|
+ // 状态5=已归档,7=超期已归档
|
|
|
+ String archiveStatus = archive.getStatus();
|
|
|
+ if ("5".equals(archiveStatus) || "7".equals(archiveStatus)) {
|
|
|
+ rowData.setIsArchived("是");
|
|
|
+ }
|
|
|
+ rowData.setReportCharges(StringUtils.defaultString(archive.getReportCharges()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 遍历关联的发票
|
|
|
+ Set<String> processedInvoiceIds = new HashSet<>();
|
|
|
+ for (FinanceInvoiceBase base : bases) {
|
|
|
+ String invoiceId = base.getInvoiceId();
|
|
|
+ if (StringUtils.isBlank(invoiceId) || processedInvoiceIds.contains(invoiceId)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ processedInvoiceIds.add(invoiceId);
|
|
|
+
|
|
|
+ // 检查该发票是否关联了多个不同的报告文号(多对多标黄)
|
|
|
+ Set<String> linkedReportNos = invoiceToReportNosMap.get(invoiceId);
|
|
|
+ if (linkedReportNos != null && linkedReportNos.size() > 1) {
|
|
|
+ rowData.setNeedHighlight(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ FinanceInvoice inv = invoiceMap.get(invoiceId);
|
|
|
+ if (inv != null) {
|
|
|
+ if (StringUtils.isNotBlank(inv.getNo())) {
|
|
|
+ rowData.getInvoiceNos().add(inv.getNo());
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(inv.getAccount())) {
|
|
|
+ try {
|
|
|
+ double currentAmount = rowData.getInvoiceAmount() != null ? rowData.getInvoiceAmount() : 0;
|
|
|
+ rowData.setInvoiceAmount(currentAmount + Double.parseDouble(inv.getAccount()));
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
+ // 忽略无法解析的金额
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotBlank(inv.getStatus())) {
|
|
|
+ rowData.getInvoiceStatuses().add(convertInvoiceStatus(inv.getStatus()));
|
|
|
+ }
|
|
|
+ if (inv.getBillingDate() != null) {
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
+ rowData.getInvoiceDates().add(sdf.format(inv.getBillingDate()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断是否开票(开票成功为已开票)
|
|
|
+ boolean isInvoiced = rowData.getInvoiceStatuses().stream().anyMatch(s -> "开票成功".equals(s));
|
|
|
+ rowData.setIsInvoiced(isInvoiced ? "是" : "否");
|
|
|
+ }
|
|
|
+
|
|
|
+ dataMap.put(reportNo, rowData);
|
|
|
+ }
|
|
|
+
|
|
|
+ return dataMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 私有工具方法 ====================
|
|
|
+
|
|
|
+ private static CellStyle createStyle(Workbook workbook, IndexedColors color) {
|
|
|
+ CellStyle style = workbook.createCellStyle();
|
|
|
+ style.setFillForegroundColor(color.getIndex());
|
|
|
+ style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
|
|
+ style.setAlignment(HorizontalAlignment.CENTER);
|
|
|
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
+ style.setBorderBottom(BorderStyle.THIN);
|
|
|
+ style.setBorderTop(BorderStyle.THIN);
|
|
|
+ style.setBorderLeft(BorderStyle.THIN);
|
|
|
+ style.setBorderRight(BorderStyle.THIN);
|
|
|
+ return style;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static CellStyle createBorderStyle(Workbook workbook) {
|
|
|
+ CellStyle style = workbook.createCellStyle();
|
|
|
+ style.setAlignment(HorizontalAlignment.CENTER);
|
|
|
+ style.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
+ style.setBorderBottom(BorderStyle.THIN);
|
|
|
+ style.setBorderTop(BorderStyle.THIN);
|
|
|
+ style.setBorderLeft(BorderStyle.THIN);
|
|
|
+ style.setBorderRight(BorderStyle.THIN);
|
|
|
+ return style;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void setCell(Row row, int colIndex, String value, CellStyle style) {
|
|
|
+ Cell cell = row.createCell(colIndex);
|
|
|
+ cell.setCellValue(value);
|
|
|
+ cell.setCellStyle(style);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发票状态码转中文
|
|
|
+ */
|
|
|
+ private static String convertInvoiceStatus(String status) {
|
|
|
+ if (StringUtils.isBlank(status)) {
|
|
|
+ return "未知";
|
|
|
+ }
|
|
|
+ switch (status) {
|
|
|
+ case "0": return "暂存";
|
|
|
+ case "1": return "待审核";
|
|
|
+ case "2": return "审核中";
|
|
|
+ case "3": return "已撤回";
|
|
|
+ case "4": return "已驳回";
|
|
|
+ case "5": return "开票成功";
|
|
|
+ case "6": return "审核中";
|
|
|
+ case "7": return "已作废";
|
|
|
+ default: return status;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 安全获取单元格的字符串值
|
|
|
+ */
|
|
|
+ public static String getCellStringValue(Cell cell) {
|
|
|
+ if (cell == null) return "";
|
|
|
+ switch (cell.getCellType()) {
|
|
|
+ case STRING:
|
|
|
+ return cell.getStringCellValue().trim();
|
|
|
+ case NUMERIC:
|
|
|
+ if (DateUtil.isCellDateFormatted(cell)) {
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
+ return sdf.format(cell.getDateCellValue());
|
|
|
+ }
|
|
|
+ double numVal = cell.getNumericCellValue();
|
|
|
+ if (numVal == Math.floor(numVal) && !Double.isInfinite(numVal)) {
|
|
|
+ return String.valueOf((long) numVal);
|
|
|
+ }
|
|
|
+ return String.valueOf(numVal);
|
|
|
+ case BOOLEAN:
|
|
|
+ return String.valueOf(cell.getBooleanCellValue());
|
|
|
+ case FORMULA:
|
|
|
+ try {
|
|
|
+ return cell.getStringCellValue().trim();
|
|
|
+ } catch (Exception e) {
|
|
|
+ return String.valueOf(cell.getNumericCellValue());
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|