|
@@ -501,6 +501,8 @@ public class FinanceInvoiceService extends ServiceImpl<FinanceInvoiceMapper, Fin
|
|
|
* @throws Exception
|
|
* @throws Exception
|
|
|
*/
|
|
*/
|
|
|
public String saveFormOnly(FinanceInvoiceDTO financeInvoiceDTO) throws Exception{
|
|
public String saveFormOnly(FinanceInvoiceDTO financeInvoiceDTO) throws Exception{
|
|
|
|
|
+ // 行级开票金额一致性校验,与 saveForm 保持一致
|
|
|
|
|
+ validateBaseAccount(financeInvoiceDTO);
|
|
|
//添加钉钉通知处理
|
|
//添加钉钉通知处理
|
|
|
boolean isDDNotice = false;
|
|
boolean isDDNotice = false;
|
|
|
String isOms = "0";
|
|
String isOms = "0";
|
|
@@ -687,6 +689,8 @@ public class FinanceInvoiceService extends ServiceImpl<FinanceInvoiceMapper, Fin
|
|
|
|
|
|
|
|
|
|
|
|
|
public String saveForm(FinanceInvoiceDTO financeInvoiceDTO) throws Exception{
|
|
public String saveForm(FinanceInvoiceDTO financeInvoiceDTO) throws Exception{
|
|
|
|
|
+ // 行级开票金额一致性校验:必须在 handleDoInvoice(触发真实开票)与任何写库之前拦截
|
|
|
|
|
+ validateBaseAccount(financeInvoiceDTO);
|
|
|
//添加钉钉通知处理
|
|
//添加钉钉通知处理
|
|
|
boolean isDDNotice = false;
|
|
boolean isDDNotice = false;
|
|
|
if(financeInvoiceDTO.getStatus().equals("2")){
|
|
if(financeInvoiceDTO.getStatus().equals("2")){
|
|
@@ -911,6 +915,69 @@ public class FinanceInvoiceService extends ServiceImpl<FinanceInvoiceMapper, Fin
|
|
|
return financeInvoice.getId();
|
|
return financeInvoice.getId();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 基本信息行级开票金额校验。
|
|
|
|
|
+ * <p>
|
|
|
|
|
+ * 规则:
|
|
|
|
|
+ * 1. 仅在送审(status=2)与开票审核通过(status=5)时校验,暂存允许金额不完整;
|
|
|
|
|
+ * 2. 基本信息为空(如作废/红冲、收款登记等场景不回传 base)时直接放行;
|
|
|
|
|
+ * 3. 所有行的 account 均为空时,视为历史"一票多项目未拆分"单据,放行且不改写主表 account,
|
|
|
|
|
+ * 避免卡死审批节点(审批表单的基本信息表格是禁用的,审批人无法补录金额);
|
|
|
|
|
+ * 4. 只要有一行填了金额,就要求每行都填,且各行合计必须等于主表发票金额。
|
|
|
|
|
+ */
|
|
|
|
|
+ private void validateBaseAccount(FinanceInvoiceDTO dto) {
|
|
|
|
|
+ if (dto == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ String status = dto.getStatus();
|
|
|
|
|
+ if (!"2".equals(status) && !"5".equals(status)) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ List<FinanceInvoiceBaseDTO> baseList = dto.getFinanceInvoiceBaseDTOList();
|
|
|
|
|
+ if (CollectionUtil.isEmpty(baseList)) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ boolean anyFilled = false;
|
|
|
|
|
+ for (FinanceInvoiceBaseDTO base : baseList) {
|
|
|
|
|
+ if (StringUtils.isNotBlank(base.getAccount())) {
|
|
|
|
|
+ anyFilled = true;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 历史未拆分单据放行
|
|
|
|
|
+ if (!anyFilled) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ BigDecimal sum = BigDecimal.ZERO;
|
|
|
|
|
+ for (int i = 0; i < baseList.size(); i++) {
|
|
|
|
|
+ String rowAccount = baseList.get(i).getAccount();
|
|
|
|
|
+ if (StringUtils.isBlank(rowAccount)) {
|
|
|
|
|
+ throw new RuntimeException("基本信息第 " + (i + 1) + " 行的开票金额未填写,请补全后再提交");
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ sum = sum.add(new BigDecimal(rowAccount.trim()));
|
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
|
+ throw new RuntimeException("基本信息第 " + (i + 1) + " 行的开票金额格式不正确:" + rowAccount);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String total = dto.getAccount();
|
|
|
|
|
+ if (StringUtils.isBlank(total)) {
|
|
|
|
|
+ throw new RuntimeException("发票金额不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ BigDecimal totalValue;
|
|
|
|
|
+ try {
|
|
|
|
|
+ totalValue = new BigDecimal(total.trim());
|
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
|
+ throw new RuntimeException("发票金额格式不正确:" + total);
|
|
|
|
|
+ }
|
|
|
|
|
+ if (sum.compareTo(totalValue) != 0) {
|
|
|
|
|
+ throw new RuntimeException("基本信息各行开票金额合计 " + sum.stripTrailingZeros().toPlainString()
|
|
|
|
|
+ + " 与发票金额 " + totalValue.stripTrailingZeros().toPlainString() + " 不一致,请检查");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
public String addDingtalkNotice(FinanceInvoiceDTO dto){
|
|
public String addDingtalkNotice(FinanceInvoiceDTO dto){
|
|
|
dingTalkUtil.addDingtalkNotice(dto);
|
|
dingTalkUtil.addDingtalkNotice(dto);
|
|
|
return "ok";
|
|
return "ok";
|
|
@@ -2179,6 +2246,14 @@ public class FinanceInvoiceService extends ServiceImpl<FinanceInvoiceMapper, Fin
|
|
|
FinanceInvoiceBase::getInvoiceId,
|
|
FinanceInvoiceBase::getInvoiceId,
|
|
|
Collectors.mapping(FinanceInvoiceBase::getReportNo, Collectors.toList())
|
|
Collectors.mapping(FinanceInvoiceBase::getReportNo, Collectors.toList())
|
|
|
));
|
|
));
|
|
|
|
|
+ // 与 invoiceIdToReportNos 完全相同的过滤条件和遍历顺序,保证 baseAccounts 与 reportNos 下标对齐。
|
|
|
|
|
+ // account 为 null(历史未拆分)时映射为空串,导出时显示为空白。
|
|
|
|
|
+ Map<String, List<String>> invoiceIdToBaseAccounts = bases.stream()
|
|
|
|
|
+ .filter(b -> StringUtils.isNotBlank(b.getReportNo()))
|
|
|
|
|
+ .collect(Collectors.groupingBy(
|
|
|
|
|
+ FinanceInvoiceBase::getInvoiceId,
|
|
|
|
|
+ Collectors.mapping(b -> b.getAccount() == null ? "" : b.getAccount(), Collectors.toList())
|
|
|
|
|
+ ));
|
|
|
|
|
|
|
|
// 7. 批量查询归档信息
|
|
// 7. 批量查询归档信息
|
|
|
Set<String> programIds = bases.stream()
|
|
Set<String> programIds = bases.stream()
|
|
@@ -2368,6 +2443,7 @@ public class FinanceInvoiceService extends ServiceImpl<FinanceInvoiceMapper, Fin
|
|
|
if (invoice != null) {
|
|
if (invoice != null) {
|
|
|
result.setInvoiceNo(invoice.getNo());
|
|
result.setInvoiceNo(invoice.getNo());
|
|
|
result.setRedInvoiceFlag(invoice.getRedInvoiceFlag());
|
|
result.setRedInvoiceFlag(invoice.getRedInvoiceFlag());
|
|
|
|
|
+ result.setMainAccount(invoice.getAccount());
|
|
|
// 将redInvoiceRelevancyNumber(number)转换为对应的开票编号(no)
|
|
// 将redInvoiceRelevancyNumber(number)转换为对应的开票编号(no)
|
|
|
String relevancyNumber = invoice.getRedInvoiceRelevancyNumber();
|
|
String relevancyNumber = invoice.getRedInvoiceRelevancyNumber();
|
|
|
if (StringUtils.isNotBlank(relevancyNumber)) {
|
|
if (StringUtils.isNotBlank(relevancyNumber)) {
|
|
@@ -2377,6 +2453,7 @@ public class FinanceInvoiceService extends ServiceImpl<FinanceInvoiceMapper, Fin
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
result.setReportNos(invoiceIdToReportNos.getOrDefault(invoiceId, new ArrayList<>()));
|
|
result.setReportNos(invoiceIdToReportNos.getOrDefault(invoiceId, new ArrayList<>()));
|
|
|
|
|
+ result.setBaseAccounts(invoiceIdToBaseAccounts.getOrDefault(invoiceId, new ArrayList<>()));
|
|
|
|
|
|
|
|
List<FinanceInvoiceBase> invoiceBases = bases.stream()
|
|
List<FinanceInvoiceBase> invoiceBases = bases.stream()
|
|
|
.filter(b -> invoiceId.equals(b.getInvoiceId()))
|
|
.filter(b -> invoiceId.equals(b.getInvoiceId()))
|
|
@@ -2520,6 +2597,11 @@ public class FinanceInvoiceService extends ServiceImpl<FinanceInvoiceMapper, Fin
|
|
|
}
|
|
}
|
|
|
String archiveStatus = convertArchiveStatus(match.getArchiveStatus());
|
|
String archiveStatus = convertArchiveStatus(match.getArchiveStatus());
|
|
|
|
|
|
|
|
|
|
+ // 整票总额(Q列),拆行时仅首行填充(与报告备案只填首行口径一致)
|
|
|
|
|
+ String mainAccount = StringUtils.defaultString(match.getMainAccount(), "");
|
|
|
|
|
+ // 各行开票金额(R列),与 reportNos 下标对齐
|
|
|
|
|
+ List<String> baseAccounts = match.getBaseAccounts();
|
|
|
|
|
+
|
|
|
// 报告号拆行
|
|
// 报告号拆行
|
|
|
List<String> reportNos = match.getReportNos();
|
|
List<String> reportNos = match.getReportNos();
|
|
|
if (reportNos.isEmpty()) {
|
|
if (reportNos.isEmpty()) {
|
|
@@ -2529,6 +2611,8 @@ public class FinanceInvoiceService extends ServiceImpl<FinanceInvoiceMapper, Fin
|
|
|
out.setArchiveStatus(archiveStatus);
|
|
out.setArchiveStatus(archiveStatus);
|
|
|
out.setIsRedInvoice(isRedInvoice);
|
|
out.setIsRedInvoice(isRedInvoice);
|
|
|
out.setRedInvoiceTarget(redInvoiceTarget);
|
|
out.setRedInvoiceTarget(redInvoiceTarget);
|
|
|
|
|
+ out.setProjectAccount("");
|
|
|
|
|
+ out.setInvoiceTotalAccount(mainAccount);
|
|
|
out.setHighlightType(0);
|
|
out.setHighlightType(0);
|
|
|
exportRows.add(out);
|
|
exportRows.add(out);
|
|
|
} else if (reportNos.size() == 1) {
|
|
} else if (reportNos.size() == 1) {
|
|
@@ -2538,17 +2622,25 @@ public class FinanceInvoiceService extends ServiceImpl<FinanceInvoiceMapper, Fin
|
|
|
out.setArchiveStatus(archiveStatus);
|
|
out.setArchiveStatus(archiveStatus);
|
|
|
out.setIsRedInvoice(isRedInvoice);
|
|
out.setIsRedInvoice(isRedInvoice);
|
|
|
out.setRedInvoiceTarget(redInvoiceTarget);
|
|
out.setRedInvoiceTarget(redInvoiceTarget);
|
|
|
|
|
+ out.setProjectAccount(baseAccounts != null && !baseAccounts.isEmpty()
|
|
|
|
|
+ ? StringUtils.defaultString(baseAccounts.get(0), "") : "");
|
|
|
|
|
+ out.setInvoiceTotalAccount(mainAccount);
|
|
|
out.setHighlightType(0);
|
|
out.setHighlightType(0);
|
|
|
exportRows.add(out);
|
|
exportRows.add(out);
|
|
|
} else {
|
|
} else {
|
|
|
// 多个报告号,拆成多行,淡黄色
|
|
// 多个报告号,拆成多行,淡黄色
|
|
|
- for (String reportNo : reportNos) {
|
|
|
|
|
|
|
+ for (int i = 0; i < reportNos.size(); i++) {
|
|
|
InvoiceLedgerImportDTO splitRow = copyRow(row);
|
|
InvoiceLedgerImportDTO splitRow = copyRow(row);
|
|
|
splitRow.setInvoiceNo(invoiceNo);
|
|
splitRow.setInvoiceNo(invoiceNo);
|
|
|
- splitRow.setReportNo(reportNo);
|
|
|
|
|
|
|
+ splitRow.setReportNo(reportNos.get(i));
|
|
|
splitRow.setArchiveStatus(archiveStatus);
|
|
splitRow.setArchiveStatus(archiveStatus);
|
|
|
splitRow.setIsRedInvoice(isRedInvoice);
|
|
splitRow.setIsRedInvoice(isRedInvoice);
|
|
|
splitRow.setRedInvoiceTarget(redInvoiceTarget);
|
|
splitRow.setRedInvoiceTarget(redInvoiceTarget);
|
|
|
|
|
+ // R列:当前报告号对应行的开票金额(下标越界或历史未拆分则留空)
|
|
|
|
|
+ splitRow.setProjectAccount(baseAccounts != null && i < baseAccounts.size()
|
|
|
|
|
+ ? StringUtils.defaultString(baseAccounts.get(i), "") : "");
|
|
|
|
|
+ // Q列:整票总额只在首行填,其余留空,保证整列求和等于发票总额(与报告备案只填首行口径一致)
|
|
|
|
|
+ splitRow.setInvoiceTotalAccount(i == 0 ? mainAccount : "");
|
|
|
splitRow.setHighlightType(1);
|
|
splitRow.setHighlightType(1);
|
|
|
exportRows.add(splitRow);
|
|
exportRows.add(splitRow);
|
|
|
}
|
|
}
|
|
@@ -2610,10 +2702,11 @@ public class FinanceInvoiceService extends ServiceImpl<FinanceInvoiceMapper, Fin
|
|
|
blueStyle.setFillForegroundColor(IndexedColors.LIGHT_CORNFLOWER_BLUE.getIndex());
|
|
blueStyle.setFillForegroundColor(IndexedColors.LIGHT_CORNFLOWER_BLUE.getIndex());
|
|
|
blueStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
|
blueStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
|
|
|
|
|
|
|
- // 表头(A-K原有 + L开票编号 + M报告文号 + N归档情况 + O是否红冲 + P红冲对象)
|
|
|
|
|
|
|
+ // 表头(A-K原有 + L开票编号 + M报告文号 + N归档情况 + O是否红冲 + P红冲对象 + Q开票总金额 + R项目开票金额)
|
|
|
String[] headers = {"开票日期", "发票号码", "购方企业名称", "商品名称", "单价", "金额",
|
|
String[] headers = {"开票日期", "发票号码", "购方企业名称", "商品名称", "单价", "金额",
|
|
|
"税额", "开票人", "收款日期", "收款凭证", "客户编码",
|
|
"税额", "开票人", "收款日期", "收款凭证", "客户编码",
|
|
|
- "开票编号", "报告文号", "归档情况", "是否红冲", "红冲对象"};
|
|
|
|
|
|
|
+ "开票编号", "报告文号", "归档情况", "是否红冲", "红冲对象",
|
|
|
|
|
+ "开票总金额", "项目开票金额"};
|
|
|
|
|
|
|
|
Row headerRow = sheet.createRow(0);
|
|
Row headerRow = sheet.createRow(0);
|
|
|
headerRow.setHeightInPoints(25);
|
|
headerRow.setHeightInPoints(25);
|
|
@@ -2624,7 +2717,7 @@ public class FinanceInvoiceService extends ServiceImpl<FinanceInvoiceMapper, Fin
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 设置列宽
|
|
// 设置列宽
|
|
|
- int[] colWidths = {15, 18, 35, 25, 12, 15, 12, 10, 15, 15, 12, 18, 30, 12, 10, 20};
|
|
|
|
|
|
|
+ int[] colWidths = {15, 18, 35, 25, 12, 15, 12, 10, 15, 15, 12, 18, 30, 12, 10, 20, 16, 16};
|
|
|
for (int i = 0; i < colWidths.length; i++) {
|
|
for (int i = 0; i < colWidths.length; i++) {
|
|
|
sheet.setColumnWidth(i, colWidths[i] * 256);
|
|
sheet.setColumnWidth(i, colWidths[i] * 256);
|
|
|
}
|
|
}
|
|
@@ -2660,6 +2753,8 @@ public class FinanceInvoiceService extends ServiceImpl<FinanceInvoiceMapper, Fin
|
|
|
ExcelCellUtil.setCellValue(dataRow.createCell(13), row.getArchiveStatus(), rowStyle);
|
|
ExcelCellUtil.setCellValue(dataRow.createCell(13), row.getArchiveStatus(), rowStyle);
|
|
|
ExcelCellUtil.setCellValue(dataRow.createCell(14), row.getIsRedInvoice(), rowStyle);
|
|
ExcelCellUtil.setCellValue(dataRow.createCell(14), row.getIsRedInvoice(), rowStyle);
|
|
|
ExcelCellUtil.setCellValue(dataRow.createCell(15), row.getRedInvoiceTarget(), rowStyle);
|
|
ExcelCellUtil.setCellValue(dataRow.createCell(15), row.getRedInvoiceTarget(), rowStyle);
|
|
|
|
|
+ ExcelCellUtil.setCellValue(dataRow.createCell(16), row.getInvoiceTotalAccount(), rowStyle);
|
|
|
|
|
+ ExcelCellUtil.setCellValue(dataRow.createCell(17), row.getProjectAccount(), rowStyle);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 输出到response
|
|
// 输出到response
|