|
@@ -16,7 +16,9 @@ import com.jeeplus.assess.invoice.service.dto.*;
|
|
|
import com.jeeplus.assess.invoice.thread.ApprovalThread;
|
|
import com.jeeplus.assess.invoice.thread.ApprovalThread;
|
|
|
import com.jeeplus.assess.invoice.thread.RedApprovalThread;
|
|
import com.jeeplus.assess.invoice.thread.RedApprovalThread;
|
|
|
import com.jeeplus.assess.invoice.utils.DingTalkUtil;
|
|
import com.jeeplus.assess.invoice.utils.DingTalkUtil;
|
|
|
|
|
+import com.jeeplus.assess.invoice.utils.ExcelCellUtil;
|
|
|
import com.jeeplus.assess.invoice.utils.OMS.FileHandlingUtil;
|
|
import com.jeeplus.assess.invoice.utils.OMS.FileHandlingUtil;
|
|
|
|
|
+import com.jeeplus.assess.program.configuration.projectList.domain.ProgramArchive;
|
|
|
import com.jeeplus.assess.program.configuration.projectList.mapper.ProgramArchiveMapper;
|
|
import com.jeeplus.assess.program.configuration.projectList.mapper.ProgramArchiveMapper;
|
|
|
import com.jeeplus.common.TokenProvider;
|
|
import com.jeeplus.common.TokenProvider;
|
|
|
import com.jeeplus.core.query.QueryWrapperGenerator;
|
|
import com.jeeplus.core.query.QueryWrapperGenerator;
|
|
@@ -41,8 +43,18 @@ import org.springframework.http.ResponseEntity;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
|
|
|
|
|
|
+import org.apache.poi.ss.usermodel.*;
|
|
|
|
|
+import org.apache.poi.ss.util.CellRangeAddress;
|
|
|
|
|
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
|
|
|
|
+import org.springframework.web.multipart.MultipartFile;
|
|
|
|
|
+
|
|
|
import javax.annotation.Resource;
|
|
import javax.annotation.Resource;
|
|
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
|
|
+import java.io.*;
|
|
|
|
|
+import java.lang.reflect.Field;
|
|
|
|
|
+import java.lang.reflect.Type;
|
|
|
import java.math.BigDecimal;
|
|
import java.math.BigDecimal;
|
|
|
|
|
+import java.net.URLEncoder;
|
|
|
import java.text.SimpleDateFormat;
|
|
import java.text.SimpleDateFormat;
|
|
|
import java.time.Instant;
|
|
import java.time.Instant;
|
|
|
import java.time.ZoneId;
|
|
import java.time.ZoneId;
|
|
@@ -1907,4 +1919,752 @@ public class FinanceInvoiceService extends ServiceImpl<FinanceInvoiceMapper, Fin
|
|
|
e.printStackTrace();
|
|
e.printStackTrace();
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // ==================== 台账导入导出 ====================
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 台账导入导出核心方法
|
|
|
|
|
+ * 解析上传的Excel → 按发票号匹配数据库 → 补充报告号/归档/红冲信息 → 一对多拆行 → 生成Excel返回
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param file 上传的XLS/XLSX文件
|
|
|
|
|
+ * @param response HTTP响应对象,用于输出文件流
|
|
|
|
|
+ */
|
|
|
|
|
+ public void importAndExportLedger(MultipartFile file, HttpServletResponse response) throws Exception {
|
|
|
|
|
+ // 第1步:解析Excel,识别数据sheet,读取有效行
|
|
|
|
|
+ List<InvoiceLedgerImportDTO> ledgerRows = parseLedgerExcel(file);
|
|
|
|
|
+
|
|
|
|
|
+ // 第2步:收集所有发票号,批量查询数据库
|
|
|
|
|
+ Map<String, LedgerMatchResultDTO> matchMap = batchMatchInvoices(ledgerRows);
|
|
|
|
|
+
|
|
|
|
|
+ // 第3步:数据组装 + 一对多拆行
|
|
|
|
|
+ List<InvoiceLedgerImportDTO> exportRows = assembleExportRows(ledgerRows, matchMap);
|
|
|
|
|
+
|
|
|
|
|
+ // 第4步:生成Excel并返回
|
|
|
|
|
+ generateLedgerExcel(exportRows, response);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 第1步:解析Excel文件,识别数据sheet,读取有效行
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<InvoiceLedgerImportDTO> parseLedgerExcel(MultipartFile file) throws Exception {
|
|
|
|
|
+ List<InvoiceLedgerImportDTO> rows = new ArrayList<>();
|
|
|
|
|
+
|
|
|
|
|
+ Workbook workbook = WorkbookFactory.create(file.getInputStream());
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 遍历所有sheet,找表头包含"发票号码"的sheet
|
|
|
|
|
+ Sheet dataSheet = null;
|
|
|
|
|
+ int headerRowIndex = -1;
|
|
|
|
|
+
|
|
|
|
|
+ for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
|
|
|
|
|
+ Sheet sheet = workbook.getSheetAt(i);
|
|
|
|
|
+ for (int r = 0; r <= Math.min(sheet.getLastRowNum(), 5); r++) {
|
|
|
|
|
+ Row row = sheet.getRow(r);
|
|
|
|
|
+ if (row == null) continue;
|
|
|
|
|
+ for (int c = 0; c < row.getLastCellNum(); c++) {
|
|
|
|
|
+ Cell cell = row.getCell(c);
|
|
|
|
|
+ if (cell != null && ExcelCellUtil.getCellStringValue(cell) != null
|
|
|
|
|
+ && ExcelCellUtil.getCellStringValue(cell).contains("发票号码")) {
|
|
|
|
|
+ dataSheet = sheet;
|
|
|
|
|
+ headerRowIndex = r;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dataSheet != null) break;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dataSheet != null) break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (dataSheet == null) {
|
|
|
|
|
+ throw new RuntimeException("未找到包含'发票号码'表头的sheet,请确认文件格式正确");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 从表头行的下一行开始读取数据
|
|
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
|
|
|
|
+
|
|
|
|
|
+ for (int r = headerRowIndex + 1; r <= dataSheet.getLastRowNum(); r++) {
|
|
|
|
|
+ Row row = dataSheet.getRow(r);
|
|
|
|
|
+ if (row == null) continue;
|
|
|
|
|
+
|
|
|
|
|
+ InvoiceLedgerImportDTO dto = new InvoiceLedgerImportDTO();
|
|
|
|
|
+
|
|
|
|
|
+ // A列 - 开票日期
|
|
|
|
|
+ Cell cellA = row.getCell(0);
|
|
|
|
|
+ if (cellA != null) {
|
|
|
|
|
+ if (cellA.getCellType() == CellType.NUMERIC && DateUtil.isCellDateFormatted(cellA)) {
|
|
|
|
|
+ dto.setBillingDate(sdf.format(cellA.getDateCellValue()));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ dto.setBillingDate(ExcelCellUtil.getCellStringValue(cellA));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // C列 - 购方企业名称
|
|
|
|
|
+ Cell cellC = row.getCell(2);
|
|
|
|
|
+ dto.setBuyerName(ExcelCellUtil.getCellStringValue(cellC));
|
|
|
|
|
+
|
|
|
|
|
+ // D列 - 商品名称
|
|
|
|
|
+ Cell cellD = row.getCell(3);
|
|
|
|
|
+ dto.setProductName(ExcelCellUtil.getCellStringValue(cellD));
|
|
|
|
|
+
|
|
|
|
|
+ // E列 - 单价
|
|
|
|
|
+ Cell cellE = row.getCell(4);
|
|
|
|
|
+ dto.setUnitPrice(ExcelCellUtil.getCellNumericString(cellE));
|
|
|
|
|
+
|
|
|
|
|
+ // F列 - 金额
|
|
|
|
|
+ Cell cellF = row.getCell(5);
|
|
|
|
|
+ dto.setAmount(ExcelCellUtil.getCellNumericString(cellF));
|
|
|
|
|
+
|
|
|
|
|
+ // G列 - 税额
|
|
|
|
|
+ Cell cellG = row.getCell(6);
|
|
|
|
|
+ dto.setTaxAmount(ExcelCellUtil.getCellNumericString(cellG));
|
|
|
|
|
+
|
|
|
|
|
+ // H列 - 开票人
|
|
|
|
|
+ Cell cellH = row.getCell(7);
|
|
|
|
|
+ dto.setIssuer(ExcelCellUtil.getCellStringValue(cellH));
|
|
|
|
|
+
|
|
|
|
|
+ // I列 - 收款日期
|
|
|
|
|
+ Cell cellI = row.getCell(8);
|
|
|
|
|
+ if (cellI != null) {
|
|
|
|
|
+ if (cellI.getCellType() == CellType.NUMERIC && DateUtil.isCellDateFormatted(cellI)) {
|
|
|
|
|
+ dto.setReceivablesDate(sdf.format(cellI.getDateCellValue()));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ dto.setReceivablesDate(ExcelCellUtil.getCellStringValue(cellI));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // J列 - 收款凭证
|
|
|
|
|
+ Cell cellJ = row.getCell(9);
|
|
|
|
|
+ dto.setReceiptVoucher(ExcelCellUtil.getCellStringValue(cellJ));
|
|
|
|
|
+
|
|
|
|
|
+ // K列 - 客户编码(可能因VLOOKUP引用缺失而显示#REF!或乱码)
|
|
|
|
|
+ Cell cellK = row.getCell(10);
|
|
|
|
|
+ String customerCode = ExcelCellUtil.getCellStringValue(cellK);
|
|
|
|
|
+ if (customerCode != null && (customerCode.contains("#REF") || customerCode.contains("#VALUE"))) {
|
|
|
|
|
+ customerCode = ""; // 引用失效时置空
|
|
|
|
|
+ }
|
|
|
|
|
+ dto.setCustomerCode(customerCode);
|
|
|
|
|
+
|
|
|
|
|
+ // B列 - 发票号码(关键匹配字段)
|
|
|
|
|
+ Cell cellB = row.getCell(1);
|
|
|
|
|
+ String invoiceNumber = ExcelCellUtil.getCellStringValue(cellB);
|
|
|
|
|
+
|
|
|
|
|
+ if (StringUtils.isBlank(invoiceNumber)) {
|
|
|
|
|
+ // 发票号码为空:检查是否为月度分隔行(如"一月"、"2月"等)
|
|
|
|
|
+ boolean hasContent = StringUtils.isNotBlank(dto.getBillingDate())
|
|
|
|
|
+ || StringUtils.isNotBlank(dto.getBuyerName())
|
|
|
|
|
+ || StringUtils.isNotBlank(dto.getProductName())
|
|
|
|
|
+ || StringUtils.isNotBlank(dto.getAmount());
|
|
|
|
|
+ if (hasContent) {
|
|
|
|
|
+ dto.setSectionHeader(true);
|
|
|
|
|
+ dto.setHighlightType(3);
|
|
|
|
|
+ rows.add(dto);
|
|
|
|
|
+ }
|
|
|
|
|
+ // 完全空行则跳过
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ invoiceNumber = invoiceNumber.trim();
|
|
|
|
|
+ dto.setInvoiceNumber(invoiceNumber);
|
|
|
|
|
+
|
|
|
|
|
+ // 处理多发票号码(用"、"或","分隔的情况)
|
|
|
|
|
+ List<String> splitNumbers = Arrays.stream(invoiceNumber.split("[、,,/]"))
|
|
|
|
|
+ .map(String::trim)
|
|
|
|
|
+ .filter(StringUtils::isNotBlank)
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+ dto.setSplitInvoiceNumbers(splitNumbers);
|
|
|
|
|
+
|
|
|
|
|
+ rows.add(dto);
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ workbook.close();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return rows;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 第2步:批量查询数据库,按发票号匹配(支持8位后缀匹配20位数电票、多发票号拆分、被红冲反向查找)
|
|
|
|
|
+ */
|
|
|
|
|
+ private Map<String, LedgerMatchResultDTO> batchMatchInvoices(List<InvoiceLedgerImportDTO> rows) {
|
|
|
|
|
+ Map<String, LedgerMatchResultDTO> matchMap = new HashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 收集所有需要查询的发票号(拆分多发票号情况)
|
|
|
|
|
+ Set<String> allLedgerNumbers = new LinkedHashSet<>();
|
|
|
|
|
+ for (InvoiceLedgerImportDTO row : rows) {
|
|
|
|
|
+ if (row.isSectionHeader()) continue;
|
|
|
|
|
+ if (row.getSplitInvoiceNumbers() != null) {
|
|
|
|
|
+ allLedgerNumbers.addAll(row.getSplitInvoiceNumbers());
|
|
|
|
|
+ } else if (StringUtils.isNotBlank(row.getInvoiceNumber())) {
|
|
|
|
|
+ allLedgerNumbers.add(row.getInvoiceNumber().trim());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (allLedgerNumbers.isEmpty()) return matchMap;
|
|
|
|
|
+
|
|
|
|
|
+ List<String> numberList = new ArrayList<>(allLedgerNumbers);
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 精确匹配
|
|
|
|
|
+ List<FinanceInvoiceDetail> exactDetails = financeInvoiceDetailMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<FinanceInvoiceDetail>()
|
|
|
|
|
+ .in(FinanceInvoiceDetail::getNumber, numberList)
|
|
|
|
|
+ .eq(FinanceInvoiceDetail::getDelFlag, "0")
|
|
|
|
|
+ );
|
|
|
|
|
+ Map<String, String> exactNumberToInvoiceId = new HashMap<>();
|
|
|
|
|
+ for (FinanceInvoiceDetail d : exactDetails) {
|
|
|
|
|
+ if (!exactNumberToInvoiceId.containsKey(d.getNumber())) {
|
|
|
|
|
+ exactNumberToInvoiceId.put(d.getNumber(), d.getInvoiceId());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 对未精确匹配的号码,做后缀匹配(台账号可能是7位、8位等,数电票是20+位)
|
|
|
|
|
+ Map<String, String> suffixNumberToInvoiceId = new HashMap<>();
|
|
|
|
|
+ List<String> unmatchedNumbers = numberList.stream()
|
|
|
|
|
+ .filter(n -> !exactNumberToInvoiceId.containsKey(n))
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+
|
|
|
|
|
+ if (!unmatchedNumbers.isEmpty()) {
|
|
|
|
|
+ // 收集台账号码的长度集合,只构建需要的后缀长度
|
|
|
|
|
+ Set<Integer> neededLengths = unmatchedNumbers.stream()
|
|
|
|
|
+ .map(String::length).collect(Collectors.toSet());
|
|
|
|
|
+
|
|
|
|
|
+ List<FinanceInvoiceDetail> allDetails = financeInvoiceDetailMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<FinanceInvoiceDetail>()
|
|
|
|
|
+ .eq(FinanceInvoiceDetail::getDelFlag, "0")
|
|
|
|
|
+ );
|
|
|
|
|
+ // 按长度分组构建后缀索引:suffixLength -> (suffix -> invoiceId)
|
|
|
|
|
+ Map<Integer, Map<String, String>> lengthSuffixIndex = new HashMap<>();
|
|
|
|
|
+ for (int len : neededLengths) {
|
|
|
|
|
+ Map<String, String> index = new HashMap<>();
|
|
|
|
|
+ for (FinanceInvoiceDetail d : allDetails) {
|
|
|
|
|
+ String num = d.getNumber();
|
|
|
|
|
+ if (num != null && num.length() > len) {
|
|
|
|
|
+ index.put(num.substring(num.length() - len), d.getInvoiceId());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ lengthSuffixIndex.put(len, index);
|
|
|
|
|
+ }
|
|
|
|
|
+ for (String num : unmatchedNumbers) {
|
|
|
|
|
+ Map<String, String> index = lengthSuffixIndex.get(num.length());
|
|
|
|
|
+ if (index != null && index.containsKey(num)) {
|
|
|
|
|
+ suffixNumberToInvoiceId.put(num, index.get(num));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 合并所有匹配
|
|
|
|
|
+ Map<String, String> allMatched = new HashMap<>();
|
|
|
|
|
+ allMatched.putAll(exactNumberToInvoiceId);
|
|
|
|
|
+ allMatched.putAll(suffixNumberToInvoiceId);
|
|
|
|
|
+ if (allMatched.isEmpty()) return matchMap;
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 批量查询发票主表(红冲信息)
|
|
|
|
|
+ Set<String> allInvoiceIds = new LinkedHashSet<>(allMatched.values());
|
|
|
|
|
+ List<FinanceInvoice> invoices = financeInvoiceMapper.selectBatchIds(new ArrayList<>(allInvoiceIds));
|
|
|
|
|
+ Map<String, FinanceInvoice> invoiceMap = invoices.stream()
|
|
|
|
|
+ .collect(Collectors.toMap(FinanceInvoice::getId, inv -> inv, (a, b) -> a));
|
|
|
|
|
+
|
|
|
|
|
+ // 6. 批量查询发票-项目关联(报告号)
|
|
|
|
|
+ List<FinanceInvoiceBase> bases = financeInvoiceBaseMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<FinanceInvoiceBase>()
|
|
|
|
|
+ .in(FinanceInvoiceBase::getInvoiceId, allInvoiceIds)
|
|
|
|
|
+ .eq(FinanceInvoiceBase::getDelFlag, "0")
|
|
|
|
|
+ );
|
|
|
|
|
+ Map<String, List<String>> invoiceIdToReportNos = bases.stream()
|
|
|
|
|
+ .filter(b -> StringUtils.isNotBlank(b.getReportNo()))
|
|
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
|
|
+ FinanceInvoiceBase::getInvoiceId,
|
|
|
|
|
+ Collectors.mapping(FinanceInvoiceBase::getReportNo, Collectors.toList())
|
|
|
|
|
+ ));
|
|
|
|
|
+
|
|
|
|
|
+ // 7. 批量查询归档信息
|
|
|
|
|
+ Set<String> programIds = bases.stream()
|
|
|
|
|
+ .map(FinanceInvoiceBase::getProgramId)
|
|
|
|
|
+ .filter(StringUtils::isNotBlank)
|
|
|
|
|
+ .collect(Collectors.toSet());
|
|
|
|
|
+ Map<String, String> programIdToArchiveStatus = new HashMap<>();
|
|
|
|
|
+ for (String pid : programIds) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ ProgramArchive archive = programArchiveMapper.selectOne(
|
|
|
|
|
+ new LambdaQueryWrapper<ProgramArchive>()
|
|
|
|
|
+ .eq(ProgramArchive::getProgramId, pid)
|
|
|
|
|
+ .eq(ProgramArchive::getDelFlag, "0")
|
|
|
|
|
+ .last("LIMIT 1")
|
|
|
|
|
+ );
|
|
|
|
|
+ if (archive != null) {
|
|
|
|
|
+ programIdToArchiveStatus.put(pid, archive.getStatus());
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ // 单个项目归档查询失败不影响整体
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 8. 构建"被红冲"反向映射
|
|
|
|
|
+ // 收集所有已匹配发票的no和number,建立映射关系
|
|
|
|
|
+ Set<String> matchedNos = new HashSet<>();
|
|
|
|
|
+ Map<String, String> noToNumberMap = new HashMap<>(); // no → number
|
|
|
|
|
+ Map<String, String> numberToNoMap = new HashMap<>(); // number → no
|
|
|
|
|
+ for (FinanceInvoice inv : invoices) {
|
|
|
|
|
+ if (StringUtils.isNotBlank(inv.getNo())) {
|
|
|
|
|
+ matchedNos.add(inv.getNo());
|
|
|
|
|
+ // 获取该发票的detail.number
|
|
|
|
|
+ List<FinanceInvoiceDetail> details = financeInvoiceDetailMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<FinanceInvoiceDetail>()
|
|
|
|
|
+ .eq(FinanceInvoiceDetail::getInvoiceId, inv.getId())
|
|
|
|
|
+ .eq(FinanceInvoiceDetail::getDelFlag, "0")
|
|
|
|
|
+ .last("LIMIT 1")
|
|
|
|
|
+ );
|
|
|
|
|
+ if (!details.isEmpty() && StringUtils.isNotBlank(details.get(0).getNumber())) {
|
|
|
|
|
+ String number = details.get(0).getNumber();
|
|
|
|
|
+ noToNumberMap.put(inv.getNo(), number);
|
|
|
|
|
+ numberToNoMap.put(number, inv.getNo());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 查所有红冲发票,找出引用了已匹配发票number的(redInvoiceRelevancyNumber存储的是number而非no)
|
|
|
|
|
+ Map<String, String> originalNoToRedNoMap = new HashMap<>();
|
|
|
|
|
+ Set<String> matchedNumbers = new HashSet<>(numberToNoMap.keySet());
|
|
|
|
|
+ if (!matchedNumbers.isEmpty()) {
|
|
|
|
|
+ List<FinanceInvoice> allRedInvoices = financeInvoiceMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<FinanceInvoice>()
|
|
|
|
|
+ .eq(FinanceInvoice::getRedInvoiceFlag, "1")
|
|
|
|
|
+ .in(FinanceInvoice::getRedInvoiceRelevancyNumber, matchedNumbers)
|
|
|
|
|
+ .eq(FinanceInvoice::getDelFlag, "0")
|
|
|
|
|
+ );
|
|
|
|
|
+ for (FinanceInvoice redInv : allRedInvoices) {
|
|
|
|
|
+ // redInvoiceRelevancyNumber存储的是原发票的number,需要转换回no
|
|
|
|
|
+ String originalNo = numberToNoMap.get(redInv.getRedInvoiceRelevancyNumber());
|
|
|
|
|
+ if (originalNo != null) {
|
|
|
|
|
+ originalNoToRedNoMap.put(originalNo, redInv.getNo());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 同时从已匹配集合中补充(红冲方也在台账中的情况)
|
|
|
|
|
+ for (FinanceInvoice inv : invoices) {
|
|
|
|
|
+ if ("1".equals(inv.getRedInvoiceFlag()) && StringUtils.isNotBlank(inv.getRedInvoiceRelevancyNumber())) {
|
|
|
|
|
+ // 尝试通过number找到原发票的no
|
|
|
|
|
+ String originalNo = numberToNoMap.get(inv.getRedInvoiceRelevancyNumber());
|
|
|
|
|
+ if (originalNo != null) {
|
|
|
|
|
+ originalNoToRedNoMap.putIfAbsent(originalNo, inv.getNo());
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果numberToNoMap中没有,尝试直接用redInvoiceRelevancyNumber作为no
|
|
|
|
|
+ originalNoToRedNoMap.putIfAbsent(inv.getRedInvoiceRelevancyNumber(), inv.getNo());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取被红冲发票的detail.number(用于判断台账号码是否匹配到被红冲的发票)
|
|
|
|
|
+ Set<String> redInvoicedDetailNumbers = new HashSet<>();
|
|
|
|
|
+ if (!originalNoToRedNoMap.isEmpty()) {
|
|
|
|
|
+ Set<String> targetNos = originalNoToRedNoMap.keySet();
|
|
|
|
|
+ List<FinanceInvoice> redTargetInvoices = financeInvoiceMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<FinanceInvoice>()
|
|
|
|
|
+ .in(FinanceInvoice::getNo, targetNos)
|
|
|
|
|
+ .eq(FinanceInvoice::getDelFlag, "0")
|
|
|
|
|
+ );
|
|
|
|
|
+ if (!redTargetInvoices.isEmpty()) {
|
|
|
|
|
+ List<String> redTargetIds = redTargetInvoices.stream()
|
|
|
|
|
+ .map(FinanceInvoice::getId).collect(Collectors.toList());
|
|
|
|
|
+ List<FinanceInvoiceDetail> redTargetDetails = financeInvoiceDetailMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<FinanceInvoiceDetail>()
|
|
|
|
|
+ .in(FinanceInvoiceDetail::getInvoiceId, redTargetIds)
|
|
|
|
|
+ .eq(FinanceInvoiceDetail::getDelFlag, "0")
|
|
|
|
|
+ );
|
|
|
|
|
+ redTargetDetails.forEach(d -> redInvoicedDetailNumbers.add(d.getNumber()));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 后缀也加入被红冲集合(只生成台账中实际出现的后缀长度)
|
|
|
|
|
+ Set<Integer> ledgerNumberLengths = allLedgerNumbers.stream()
|
|
|
|
|
+ .map(String::length).collect(Collectors.toSet());
|
|
|
|
|
+ Set<String> redInvoicedSuffixes = new HashSet<>();
|
|
|
|
|
+ for (String num : redInvoicedDetailNumbers) {
|
|
|
|
|
+ if (num != null) {
|
|
|
|
|
+ for (int len : ledgerNumberLengths) {
|
|
|
|
|
+ if (num.length() > len) {
|
|
|
|
|
+ redInvoicedSuffixes.add(num.substring(num.length() - len));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ redInvoicedSuffixes.add(num);
|
|
|
|
|
+ }
|
|
|
|
|
+ // 构建 台账号码后缀 → 红冲方no 的映射(使用number而非no,因为台账中是发票号)
|
|
|
|
|
+ Map<String, String> suffixToRedNo = new HashMap<>();
|
|
|
|
|
+ for (Map.Entry<String, String> entry : originalNoToRedNoMap.entrySet()) {
|
|
|
|
|
+ String originalNo = entry.getKey();
|
|
|
|
|
+ String redNo = entry.getValue();
|
|
|
|
|
+ // 通过no找到对应的number
|
|
|
|
|
+ String originalNumber = noToNumberMap.get(originalNo);
|
|
|
|
|
+ if (originalNumber != null) {
|
|
|
|
|
+ // 用number建立映射
|
|
|
|
|
+ suffixToRedNo.put(originalNumber, redNo);
|
|
|
|
|
+ for (int len : ledgerNumberLengths) {
|
|
|
|
|
+ if (originalNumber.length() > len) {
|
|
|
|
|
+ suffixToRedNo.put(originalNumber.substring(originalNumber.length() - len), redNo);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果没有number,用no作为备选
|
|
|
|
|
+ suffixToRedNo.put(originalNo, redNo);
|
|
|
|
|
+ for (int len : ledgerNumberLengths) {
|
|
|
|
|
+ if (originalNo.length() > len) {
|
|
|
|
|
+ suffixToRedNo.put(originalNo.substring(originalNo.length() - len), redNo);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 8.5 构建 number → no 的映射(用于将红冲发票的redInvoiceRelevancyNumber转换为开票编号)
|
|
|
|
|
+ // 收集所有红冲发票的redInvoiceRelevancyNumber(存储的是原发票的number)
|
|
|
|
|
+ Set<String> redRelevancyNumbers = new HashSet<>();
|
|
|
|
|
+ for (FinanceInvoice inv : invoices) {
|
|
|
|
|
+ if ("1".equals(inv.getRedInvoiceFlag()) && StringUtils.isNotBlank(inv.getRedInvoiceRelevancyNumber())) {
|
|
|
|
|
+ redRelevancyNumbers.add(inv.getRedInvoiceRelevancyNumber());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 查询这些number对应的发票,获取其no
|
|
|
|
|
+ Map<String, String> relevancyNumberToNoMap = new HashMap<>();
|
|
|
|
|
+ if (!redRelevancyNumbers.isEmpty()) {
|
|
|
|
|
+ // 通过detail.number查找对应的发票
|
|
|
|
|
+ List<FinanceInvoiceDetail> relevancyDetails = financeInvoiceDetailMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<FinanceInvoiceDetail>()
|
|
|
|
|
+ .in(FinanceInvoiceDetail::getNumber, redRelevancyNumbers)
|
|
|
|
|
+ .eq(FinanceInvoiceDetail::getDelFlag, "0")
|
|
|
|
|
+ );
|
|
|
|
|
+ if (!relevancyDetails.isEmpty()) {
|
|
|
|
|
+ Set<String> relevancyInvoiceIds = relevancyDetails.stream()
|
|
|
|
|
+ .map(FinanceInvoiceDetail::getInvoiceId).collect(Collectors.toSet());
|
|
|
|
|
+ List<FinanceInvoice> relevancyInvoices = financeInvoiceMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<FinanceInvoice>()
|
|
|
|
|
+ .in(FinanceInvoice::getId, relevancyInvoiceIds)
|
|
|
|
|
+ .eq(FinanceInvoice::getDelFlag, "0")
|
|
|
|
|
+ );
|
|
|
|
|
+ // 建立 number → no 映射
|
|
|
|
|
+ Map<String, String> invoiceIdToNo = new HashMap<>();
|
|
|
|
|
+ for (FinanceInvoice inv : relevancyInvoices) {
|
|
|
|
|
+ invoiceIdToNo.put(inv.getId(), inv.getNo());
|
|
|
|
|
+ }
|
|
|
|
|
+ for (FinanceInvoiceDetail detail : relevancyDetails) {
|
|
|
|
|
+ String no = invoiceIdToNo.get(detail.getInvoiceId());
|
|
|
|
|
+ if (no != null) {
|
|
|
|
|
+ relevancyNumberToNoMap.put(detail.getNumber(), no);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 9. 组装每个发票号的匹配结果
|
|
|
|
|
+ for (Map.Entry<String, String> entry : allMatched.entrySet()) {
|
|
|
|
|
+ String ledgerNumber = entry.getKey();
|
|
|
|
|
+ String invoiceId = entry.getValue();
|
|
|
|
|
+
|
|
|
|
|
+ LedgerMatchResultDTO result = new LedgerMatchResultDTO();
|
|
|
|
|
+ result.setInvoiceId(invoiceId);
|
|
|
|
|
+ result.setMatchedNumber(ledgerNumber);
|
|
|
|
|
+
|
|
|
|
|
+ FinanceInvoice invoice = invoiceMap.get(invoiceId);
|
|
|
|
|
+ if (invoice != null) {
|
|
|
|
|
+ result.setInvoiceNo(invoice.getNo());
|
|
|
|
|
+ result.setRedInvoiceFlag(invoice.getRedInvoiceFlag());
|
|
|
|
|
+ // 将redInvoiceRelevancyNumber(number)转换为对应的开票编号(no)
|
|
|
|
|
+ String relevancyNumber = invoice.getRedInvoiceRelevancyNumber();
|
|
|
|
|
+ if (StringUtils.isNotBlank(relevancyNumber)) {
|
|
|
|
|
+ String relevancyNo = relevancyNumberToNoMap.get(relevancyNumber);
|
|
|
|
|
+ result.setRedInvoiceRelevancyNumber(relevancyNo != null ? relevancyNo : relevancyNumber);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ result.setReportNos(invoiceIdToReportNos.getOrDefault(invoiceId, new ArrayList<>()));
|
|
|
|
|
+
|
|
|
|
|
+ List<FinanceInvoiceBase> invoiceBases = bases.stream()
|
|
|
|
|
+ .filter(b -> invoiceId.equals(b.getInvoiceId()))
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+ if (!invoiceBases.isEmpty()) {
|
|
|
|
|
+ String firstProgramId = invoiceBases.get(0).getProgramId();
|
|
|
|
|
+ if (StringUtils.isNotBlank(firstProgramId)) {
|
|
|
|
|
+ result.setArchiveStatus(programIdToArchiveStatus.get(firstProgramId));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 被红冲判断
|
|
|
|
|
+ boolean isRedInvoiced = redInvoicedDetailNumbers.contains(ledgerNumber)
|
|
|
|
|
+ || redInvoicedSuffixes.contains(ledgerNumber);
|
|
|
|
|
+ result.setRedInvoiced(isRedInvoiced);
|
|
|
|
|
+ if (isRedInvoiced) {
|
|
|
|
|
+ result.setRedInvoiceNo(suffixToRedNo.get(ledgerNumber));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ matchMap.put(ledgerNumber, result);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return matchMap;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 归档状态字典值转中文
|
|
|
|
|
+ */
|
|
|
|
|
+ private String convertArchiveStatus(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 "未归档";
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 第3步:数据组装 + 一对多拆行 + 多发票号拆行
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<InvoiceLedgerImportDTO> assembleExportRows(
|
|
|
|
|
+ List<InvoiceLedgerImportDTO> ledgerRows,
|
|
|
|
|
+ Map<String, LedgerMatchResultDTO> matchMap) {
|
|
|
|
|
+
|
|
|
|
|
+ List<InvoiceLedgerImportDTO> exportRows = new ArrayList<>();
|
|
|
|
|
+
|
|
|
|
|
+ for (InvoiceLedgerImportDTO row : ledgerRows) {
|
|
|
|
|
+ // 月度分隔行:原样保留,不做匹配
|
|
|
|
|
+ if (row.isSectionHeader()) {
|
|
|
|
|
+ row.setInvoiceNo("");
|
|
|
|
|
+ row.setReportNo("");
|
|
|
|
|
+ row.setArchiveStatus("");
|
|
|
|
|
+ row.setIsRedInvoice("");
|
|
|
|
|
+ row.setRedInvoiceTarget("");
|
|
|
|
|
+ exportRows.add(row);
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 收集本行所有匹配结果(支持多发票号拆分)
|
|
|
|
|
+ // 同一invoiceId的多个号码应合并报告号,不拆成多行
|
|
|
|
|
+ Map<String, LedgerMatchResultDTO> matchByIdMap = new LinkedHashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ List<String> splitNumbers = row.getSplitInvoiceNumbers();
|
|
|
|
|
+ if (splitNumbers != null) {
|
|
|
|
|
+ for (String num : splitNumbers) {
|
|
|
|
|
+ LedgerMatchResultDTO match = matchMap.get(num);
|
|
|
|
|
+ if (match != null) {
|
|
|
|
|
+ String iid = match.getInvoiceId();
|
|
|
|
|
+ if (matchByIdMap.containsKey(iid)) {
|
|
|
|
|
+ // 同一invoiceId:合并报告号
|
|
|
|
|
+ LedgerMatchResultDTO existing = matchByIdMap.get(iid);
|
|
|
|
|
+ for (String rno : match.getReportNos()) {
|
|
|
|
|
+ if (!existing.getReportNos().contains(rno)) {
|
|
|
|
|
+ existing.getReportNos().add(rno);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ matchByIdMap.put(iid, match);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (StringUtils.isNotBlank(row.getInvoiceNumber())) {
|
|
|
|
|
+ LedgerMatchResultDTO match = matchMap.get(row.getInvoiceNumber().trim());
|
|
|
|
|
+ if (match != null) {
|
|
|
|
|
+ matchByIdMap.put(match.getInvoiceId(), match);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ List<LedgerMatchResultDTO> matches = new ArrayList<>(matchByIdMap.values());
|
|
|
|
|
+
|
|
|
|
|
+ if (matches.isEmpty()) {
|
|
|
|
|
+ // 全部匹配不上:淡蓝色
|
|
|
|
|
+ row.setHighlightType(2);
|
|
|
|
|
+ row.setInvoiceNo("");
|
|
|
|
|
+ row.setReportNo("");
|
|
|
|
|
+ row.setArchiveStatus("");
|
|
|
|
|
+ row.setIsRedInvoice("");
|
|
|
|
|
+ row.setRedInvoiceTarget("");
|
|
|
|
|
+ exportRows.add(row);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 有匹配结果:为每个不同的invoiceId生成导出行
|
|
|
|
|
+ for (LedgerMatchResultDTO match : matches) {
|
|
|
|
|
+ populateMatchedRow(row, match, exportRows);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return exportRows;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 填充匹配行的数据(处理报告号一对多拆行 + 被红冲状态)
|
|
|
|
|
+ */
|
|
|
|
|
+ private void populateMatchedRow(InvoiceLedgerImportDTO row, LedgerMatchResultDTO match,
|
|
|
|
|
+ List<InvoiceLedgerImportDTO> exportRows) {
|
|
|
|
|
+ // 开票编号
|
|
|
|
|
+ String invoiceNo = StringUtils.defaultString(match.getInvoiceNo(), "");
|
|
|
|
|
+
|
|
|
|
|
+ // 红冲信息
|
|
|
|
|
+ String isRedInvoice;
|
|
|
|
|
+ String redInvoiceTarget;
|
|
|
|
|
+ if ("1".equals(match.getRedInvoiceFlag())) {
|
|
|
|
|
+ isRedInvoice = "是";
|
|
|
|
|
+ // 本发票是红冲发票,红冲对象 = 被红冲发票的开票编号
|
|
|
|
|
+ redInvoiceTarget = StringUtils.defaultString(match.getRedInvoiceRelevancyNumber(), "");
|
|
|
|
|
+ } else if (match.isRedInvoiced()) {
|
|
|
|
|
+ isRedInvoice = "被红冲";
|
|
|
|
|
+ // 本发票被别人红冲了,红冲对象 = 红冲方的开票编号
|
|
|
|
|
+ redInvoiceTarget = StringUtils.defaultString(match.getRedInvoiceNo(), "");
|
|
|
|
|
+ } else {
|
|
|
|
|
+ isRedInvoice = "否";
|
|
|
|
|
+ redInvoiceTarget = "";
|
|
|
|
|
+ }
|
|
|
|
|
+ String archiveStatus = convertArchiveStatus(match.getArchiveStatus());
|
|
|
|
|
+
|
|
|
|
|
+ // 报告号拆行
|
|
|
|
|
+ List<String> reportNos = match.getReportNos();
|
|
|
|
|
+ if (reportNos.isEmpty()) {
|
|
|
|
|
+ InvoiceLedgerImportDTO out = copyRow(row);
|
|
|
|
|
+ out.setInvoiceNo(invoiceNo);
|
|
|
|
|
+ out.setReportNo("");
|
|
|
|
|
+ out.setArchiveStatus(archiveStatus);
|
|
|
|
|
+ out.setIsRedInvoice(isRedInvoice);
|
|
|
|
|
+ out.setRedInvoiceTarget(redInvoiceTarget);
|
|
|
|
|
+ out.setHighlightType(0);
|
|
|
|
|
+ exportRows.add(out);
|
|
|
|
|
+ } else if (reportNos.size() == 1) {
|
|
|
|
|
+ InvoiceLedgerImportDTO out = copyRow(row);
|
|
|
|
|
+ out.setInvoiceNo(invoiceNo);
|
|
|
|
|
+ out.setReportNo(reportNos.get(0));
|
|
|
|
|
+ out.setArchiveStatus(archiveStatus);
|
|
|
|
|
+ out.setIsRedInvoice(isRedInvoice);
|
|
|
|
|
+ out.setRedInvoiceTarget(redInvoiceTarget);
|
|
|
|
|
+ out.setHighlightType(0);
|
|
|
|
|
+ exportRows.add(out);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 多个报告号,拆成多行,淡黄色
|
|
|
|
|
+ for (String reportNo : reportNos) {
|
|
|
|
|
+ InvoiceLedgerImportDTO splitRow = copyRow(row);
|
|
|
|
|
+ splitRow.setInvoiceNo(invoiceNo);
|
|
|
|
|
+ splitRow.setReportNo(reportNo);
|
|
|
|
|
+ splitRow.setArchiveStatus(archiveStatus);
|
|
|
|
|
+ splitRow.setIsRedInvoice(isRedInvoice);
|
|
|
|
|
+ splitRow.setRedInvoiceTarget(redInvoiceTarget);
|
|
|
|
|
+ splitRow.setHighlightType(1);
|
|
|
|
|
+ exportRows.add(splitRow);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 复制行数据(A-K列)
|
|
|
|
|
+ */
|
|
|
|
|
+ private InvoiceLedgerImportDTO copyRow(InvoiceLedgerImportDTO source) {
|
|
|
|
|
+ InvoiceLedgerImportDTO copy = new InvoiceLedgerImportDTO();
|
|
|
|
|
+ copy.setBillingDate(source.getBillingDate());
|
|
|
|
|
+ copy.setInvoiceNumber(source.getInvoiceNumber());
|
|
|
|
|
+ copy.setBuyerName(source.getBuyerName());
|
|
|
|
|
+ copy.setProductName(source.getProductName());
|
|
|
|
|
+ copy.setUnitPrice(source.getUnitPrice());
|
|
|
|
|
+ copy.setAmount(source.getAmount());
|
|
|
|
|
+ copy.setTaxAmount(source.getTaxAmount());
|
|
|
|
|
+ copy.setIssuer(source.getIssuer());
|
|
|
|
|
+ copy.setReceivablesDate(source.getReceivablesDate());
|
|
|
|
|
+ copy.setReceiptVoucher(source.getReceiptVoucher());
|
|
|
|
|
+ copy.setCustomerCode(source.getCustomerCode());
|
|
|
|
|
+ return copy;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 第4步:生成Excel并输出到response
|
|
|
|
|
+ */
|
|
|
|
|
+ private void generateLedgerExcel(List<InvoiceLedgerImportDTO> rows, HttpServletResponse response) throws Exception {
|
|
|
|
|
+ Workbook workbook = new XSSFWorkbook();
|
|
|
|
|
+ Sheet sheet = workbook.createSheet("评估公司台账");
|
|
|
|
|
+
|
|
|
|
|
+ // 样式定义
|
|
|
|
|
+ CellStyle headerStyle = workbook.createCellStyle();
|
|
|
|
|
+ Font headerFont = workbook.createFont();
|
|
|
|
|
+ headerFont.setBold(true);
|
|
|
|
|
+ headerFont.setFontHeightInPoints((short) 11);
|
|
|
|
|
+ headerStyle.setFont(headerFont);
|
|
|
|
|
+ headerStyle.setAlignment(HorizontalAlignment.CENTER);
|
|
|
|
|
+ headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
|
|
+ headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
|
|
|
|
|
+ headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
|
|
|
|
+ ExcelCellUtil.setFullBorder(headerStyle);
|
|
|
|
|
+
|
|
|
|
|
+ CellStyle dataStyle = workbook.createCellStyle();
|
|
|
|
|
+ dataStyle.setAlignment(HorizontalAlignment.CENTER);
|
|
|
|
|
+ dataStyle.setVerticalAlignment(VerticalAlignment.CENTER);
|
|
|
|
|
+ ExcelCellUtil.setFullBorder(dataStyle);
|
|
|
|
|
+
|
|
|
|
|
+ // 淡黄色样式(一对多拆行)
|
|
|
|
|
+ CellStyle yellowStyle = workbook.createCellStyle();
|
|
|
|
|
+ yellowStyle.cloneStyleFrom(dataStyle);
|
|
|
|
|
+ yellowStyle.setFillForegroundColor(IndexedColors.LEMON_CHIFFON.getIndex());
|
|
|
|
|
+ yellowStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
|
|
|
|
+
|
|
|
|
|
+ // 淡蓝色样式(未匹配)
|
|
|
|
|
+ CellStyle blueStyle = workbook.createCellStyle();
|
|
|
|
|
+ blueStyle.cloneStyleFrom(dataStyle);
|
|
|
|
|
+ blueStyle.setFillForegroundColor(IndexedColors.LIGHT_CORNFLOWER_BLUE.getIndex());
|
|
|
|
|
+ blueStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
|
|
|
|
+
|
|
|
|
|
+ // 表头(A-K原有 + L开票编号 + M报告文号 + N归档情况 + O是否红冲 + P红冲对象)
|
|
|
|
|
+ String[] headers = {"开票日期", "发票号码", "购方企业名称", "商品名称", "单价", "金额",
|
|
|
|
|
+ "税额", "开票人", "收款日期", "收款凭证", "客户编码",
|
|
|
|
|
+ "开票编号", "报告文号", "归档情况", "是否红冲", "红冲对象"};
|
|
|
|
|
+
|
|
|
|
|
+ Row headerRow = sheet.createRow(0);
|
|
|
|
|
+ headerRow.setHeightInPoints(25);
|
|
|
|
|
+ for (int i = 0; i < headers.length; i++) {
|
|
|
|
|
+ Cell cell = headerRow.createCell(i);
|
|
|
|
|
+ cell.setCellValue(headers[i]);
|
|
|
|
|
+ cell.setCellStyle(headerStyle);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 设置列宽
|
|
|
|
|
+ int[] colWidths = {15, 18, 35, 25, 12, 15, 12, 10, 15, 15, 12, 18, 30, 12, 10, 20};
|
|
|
|
|
+ for (int i = 0; i < colWidths.length; i++) {
|
|
|
|
|
+ sheet.setColumnWidth(i, colWidths[i] * 256);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 数据行
|
|
|
|
|
+ for (int i = 0; i < rows.size(); i++) {
|
|
|
|
|
+ InvoiceLedgerImportDTO row = rows.get(i);
|
|
|
|
|
+ Row dataRow = sheet.createRow(i + 1);
|
|
|
|
|
+ dataRow.setHeightInPoints(20);
|
|
|
|
|
+
|
|
|
|
|
+ CellStyle rowStyle;
|
|
|
|
|
+ if (row.getHighlightType() == 1) {
|
|
|
|
|
+ rowStyle = yellowStyle;
|
|
|
|
|
+ } else if (row.getHighlightType() == 2) {
|
|
|
|
|
+ rowStyle = blueStyle;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ rowStyle = dataStyle;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ExcelCellUtil.setCellValue(dataRow.createCell(0), row.getBillingDate(), rowStyle);
|
|
|
|
|
+ ExcelCellUtil.setCellValue(dataRow.createCell(1), row.getInvoiceNumber(), rowStyle);
|
|
|
|
|
+ ExcelCellUtil.setCellValue(dataRow.createCell(2), row.getBuyerName(), rowStyle);
|
|
|
|
|
+ ExcelCellUtil.setCellValue(dataRow.createCell(3), row.getProductName(), rowStyle);
|
|
|
|
|
+ ExcelCellUtil.setCellValue(dataRow.createCell(4), row.getUnitPrice(), rowStyle);
|
|
|
|
|
+ ExcelCellUtil.setCellValue(dataRow.createCell(5), row.getAmount(), rowStyle);
|
|
|
|
|
+ ExcelCellUtil.setCellValue(dataRow.createCell(6), row.getTaxAmount(), rowStyle);
|
|
|
|
|
+ ExcelCellUtil.setCellValue(dataRow.createCell(7), row.getIssuer(), rowStyle);
|
|
|
|
|
+ ExcelCellUtil.setCellValue(dataRow.createCell(8), row.getReceivablesDate(), rowStyle);
|
|
|
|
|
+ ExcelCellUtil.setCellValue(dataRow.createCell(9), row.getReceiptVoucher(), rowStyle);
|
|
|
|
|
+ ExcelCellUtil.setCellValue(dataRow.createCell(10), row.getCustomerCode(), rowStyle);
|
|
|
|
|
+ ExcelCellUtil.setCellValue(dataRow.createCell(11), row.getInvoiceNo(), rowStyle);
|
|
|
|
|
+ ExcelCellUtil.setCellValue(dataRow.createCell(12), row.getReportNo(), rowStyle);
|
|
|
|
|
+ ExcelCellUtil.setCellValue(dataRow.createCell(13), row.getArchiveStatus(), rowStyle);
|
|
|
|
|
+ ExcelCellUtil.setCellValue(dataRow.createCell(14), row.getIsRedInvoice(), rowStyle);
|
|
|
|
|
+ ExcelCellUtil.setCellValue(dataRow.createCell(15), row.getRedInvoiceTarget(), rowStyle);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 输出到response
|
|
|
|
|
+ String fileName = "评估公司台账_" + new SimpleDateFormat("yyyy-MM-dd").format(new Date()) + ".xlsx";
|
|
|
|
|
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
|
|
|
|
+ response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
|
|
|
|
|
+
|
|
|
|
|
+ OutputStream os = response.getOutputStream();
|
|
|
|
|
+ workbook.write(os);
|
|
|
|
|
+ os.flush();
|
|
|
|
|
+ os.close();
|
|
|
|
|
+ workbook.close();
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|