feat: 开票结果回写落库发票文件地址并支持正式结算单详情查看下载
- A303-5 VO 补数电 PDF/XML 直链字段,回写落库四个发票文件地址(板式文件/预览图/数电PDF/数电XML) - 已落库但缺地址的历史记录由回写任务字段级补空,不覆盖已有值 - 正式结算单详情新增 issuedInvoices:经结算单-开票申请关联查出回写发票,只读不参与匹配金额 - 新增建表补丁 blade_invoice_application_invoice_urls_20260930.sql - 同步更新金蝶附件上传下载链路梳理文档(新增 3.4 销项直链节)
This commit is contained in:
+35
@@ -28,6 +28,9 @@ import org.springblade.transport.mapper.FormalSettlementSummaryFeeMapper;
|
||||
import org.springblade.transport.mapper.FormalSettlementPaymentMapper;
|
||||
import org.springblade.transport.mapper.InvoiceReceiptMapper;
|
||||
import org.springblade.transport.mapper.InvoiceReceiptSettlementMapper;
|
||||
import org.springblade.transport.mapper.InvoiceApplicationInvoiceMapper;
|
||||
import org.springblade.transport.mapper.InvoiceApplicationMapper;
|
||||
import org.springblade.transport.mapper.InvoiceApplicationSettlementMapper;
|
||||
import org.springblade.transport.mapper.PreSettlementDetailMapper;
|
||||
import org.springblade.transport.mapper.PreSettlementDetailFeeMapper;
|
||||
import org.springblade.transport.mapper.PreSettlementMapper;
|
||||
@@ -58,6 +61,9 @@ import org.springblade.transport.pojo.entity.FormalSettlementSummaryFee;
|
||||
import org.springblade.transport.pojo.entity.FormalSettlementPayment;
|
||||
import org.springblade.transport.pojo.entity.InvoiceReceipt;
|
||||
import org.springblade.transport.pojo.entity.InvoiceReceiptSettlement;
|
||||
import org.springblade.transport.pojo.entity.InvoiceApplication;
|
||||
import org.springblade.transport.pojo.entity.InvoiceApplicationInvoice;
|
||||
import org.springblade.transport.pojo.entity.InvoiceApplicationSettlement;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.PreSettlement;
|
||||
import org.springblade.transport.pojo.entity.PreSettlementDetail;
|
||||
@@ -129,6 +135,9 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
private final FormalSettlementInvoiceMapper invoiceMapper;
|
||||
private final InvoiceReceiptMapper invoiceReceiptMapper;
|
||||
private final InvoiceReceiptSettlementMapper invoiceReceiptSettlementMapper;
|
||||
private final InvoiceApplicationMapper invoiceApplicationMapper;
|
||||
private final InvoiceApplicationInvoiceMapper invoiceApplicationInvoiceMapper;
|
||||
private final InvoiceApplicationSettlementMapper invoiceApplicationSettlementMapper;
|
||||
private final FormalSettlementChangeRecordMapper changeRecordMapper;
|
||||
private final FormalSettlementDetailMapper detailMapper;
|
||||
private final FormalSettlementDetailFeeMapper detailFeeMapper;
|
||||
@@ -269,6 +278,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
vo.setPayments(paymentMapper.selectList(Wrappers.<FormalSettlementPayment>lambdaQuery()
|
||||
.eq(FormalSettlementPayment::getFormalSettlementId, id).orderByDesc(FormalSettlementPayment::getCreateTime)));
|
||||
vo.setInvoices(listInvoices(settlement));
|
||||
vo.setIssuedInvoices(listIssuedInvoices(id));
|
||||
Set<Long> relationApplicationIds = paymentApplicationSettlementMapper.selectList(
|
||||
Wrappers.<PaymentApplicationSettlement>lambdaQuery()
|
||||
.select(PaymentApplicationSettlement::getPaymentApplicationId)
|
||||
@@ -386,6 +396,31 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
return normalizedInvoiceNo.isEmpty() ? fallback : normalizedInvoiceNo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 开票申请经金蝶回写的发票结果(A303-5 落库,含板式文件/数电PDF等文件地址):
|
||||
* 通过结算单与开票申请的关联关系查出,仅保留有效(非作废未删除)开票申请的发票,
|
||||
* 只读展示于详情页,不落 blade_formal_settlement_invoice、不参与匹配金额计算
|
||||
*/
|
||||
private List<InvoiceApplicationInvoice> listIssuedInvoices(Long settlementId) {
|
||||
List<Long> applicationIds = invoiceApplicationSettlementMapper.selectList(
|
||||
Wrappers.<InvoiceApplicationSettlement>lambdaQuery()
|
||||
.select(InvoiceApplicationSettlement::getInvoiceApplicationId)
|
||||
.eq(InvoiceApplicationSettlement::getFormalSettlementId, settlementId)
|
||||
).stream().map(InvoiceApplicationSettlement::getInvoiceApplicationId)
|
||||
.filter(Objects::nonNull).distinct().toList();
|
||||
if (applicationIds.isEmpty()) return List.of();
|
||||
Set<Long> activeApplicationIds = invoiceApplicationMapper.selectBatchIds(applicationIds).stream()
|
||||
.filter(application -> !TransportBusiConstant.VOIDED.equals(application.getApprovalStatus())
|
||||
&& !Objects.equals(application.getIsDeleted(), 1))
|
||||
.map(InvoiceApplication::getId).collect(Collectors.toSet());
|
||||
if (activeApplicationIds.isEmpty()) return List.of();
|
||||
return invoiceApplicationInvoiceMapper.selectList(
|
||||
Wrappers.<InvoiceApplicationInvoice>lambdaQuery()
|
||||
.in(InvoiceApplicationInvoice::getInvoiceApplicationId, activeApplicationIds)
|
||||
.orderByAsc(InvoiceApplicationInvoice::getInvoiceApplicationId)
|
||||
.orderByAsc(InvoiceApplicationInvoice::getCreateTime));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long saveDraft(FormalSettlementSaveRequest request) {
|
||||
|
||||
+69
-29
@@ -941,11 +941,12 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl<InvoiceApplic
|
||||
continue;
|
||||
}
|
||||
log.info("金蝶开票结果回写批次查询成功:serialNo=[{}],返回单据数={}", batchSerialNos, result.getData().size());
|
||||
// 批量查询当前批次已存在的发票号码(避免 N+1)
|
||||
Set<String> existingInvoiceNos = batchQueryExistingInvoiceNos(batch);
|
||||
log.debug("金蝶开票结果回写:批次已存在发票号码数={}", existingInvoiceNos.size());
|
||||
// 收集待插入的发票记录和待更新的开票申请
|
||||
// 批量查询当前批次已落库的发票记录(按发票号索引,用于幂等去重与缺文件地址补齐)
|
||||
Map<String, InvoiceApplicationInvoice> existingInvoiceMap = batchQueryExistingInvoices(batch);
|
||||
log.debug("金蝶开票结果回写:批次已存在发票记录数={}", existingInvoiceMap.size());
|
||||
// 收集待插入的发票记录、待补文件地址的已有发票记录和待更新的开票申请
|
||||
List<InvoiceApplicationInvoice> toInsertInvoices = new ArrayList<>();
|
||||
List<InvoiceApplicationInvoice> toPatchInvoices = new ArrayList<>();
|
||||
List<InvoiceApplication> toUpdateApplications = new ArrayList<>();
|
||||
for (BfiInvoiceApplyOpenApiBillVO bill : result.getData()) {
|
||||
String billNo = bill.getBillNo();
|
||||
@@ -959,24 +960,32 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl<InvoiceApplic
|
||||
if (Func.isBlank(invoiceNo)) {
|
||||
continue;
|
||||
}
|
||||
// 使用批量查询结果判断是否已存在
|
||||
if (existingInvoiceNos.contains(invoiceNo)) {
|
||||
log.debug("金蝶开票结果回写:发票号码[{}]已存在,跳过", invoiceNo);
|
||||
continue;
|
||||
InvoiceApplicationInvoice existing = existingInvoiceMap.get(invoiceNo);
|
||||
if (existing == null) {
|
||||
InvoiceApplicationInvoice record = new InvoiceApplicationInvoice();
|
||||
record.setInvoiceApplicationId(application.getId());
|
||||
record.setSerialNo(billNo);
|
||||
record.setInvoiceNo(invoiceNo);
|
||||
record.setInvoiceDate(parseInvoiceDateTimestamp(invoice.getInvoiceDate()));
|
||||
record.setInvoiceAmount(invoice.getIncludeTaxAmount());
|
||||
// 发票文件地址独立落库:板式文件/预览图/数电PDF/数电XML,供正式结算单详情查看下载
|
||||
record.setInvoiceFileUrl(invoice.getInvoiceFileUrl());
|
||||
record.setInvoiceImageUrl(invoice.getInvoiceImageUrl());
|
||||
record.setInvoicePdfFileUrl(invoice.getInvoicePdfFileUrl());
|
||||
record.setInvoiceXmlFileUrl(invoice.getInvoiceXmlFileUrl());
|
||||
record.setRawJson(JsonUtil.toJson(invoice));
|
||||
toInsertInvoices.add(record);
|
||||
inserted++;
|
||||
// 标记为已存在,避免同批次重复插入
|
||||
existingInvoiceMap.put(invoiceNo, record);
|
||||
log.info("金蝶开票结果回写:写入发票,申请单号=[{}],单据编号=[{}],发票号码=[{}],含税金额=[{}]",
|
||||
application.getApplicationNo(), billNo, invoiceNo, invoice.getIncludeTaxAmount());
|
||||
} else if (fillMissingInvoiceFileUrls(existing, invoice)) {
|
||||
// 历史已落库但缺文件地址的记录按本次返回补齐(字段级补空,不覆盖已有值)
|
||||
toPatchInvoices.add(existing);
|
||||
log.info("金蝶开票结果回写:补齐发票文件地址,申请单号=[{}],单据编号=[{}],发票号码=[{}]",
|
||||
application.getApplicationNo(), billNo, invoiceNo);
|
||||
}
|
||||
InvoiceApplicationInvoice record = new InvoiceApplicationInvoice();
|
||||
record.setInvoiceApplicationId(application.getId());
|
||||
record.setSerialNo(billNo);
|
||||
record.setInvoiceNo(invoiceNo);
|
||||
record.setInvoiceDate(parseInvoiceDateTimestamp(invoice.getInvoiceDate()));
|
||||
record.setInvoiceAmount(invoice.getIncludeTaxAmount());
|
||||
record.setRawJson(JsonUtil.toJson(invoice));
|
||||
toInsertInvoices.add(record);
|
||||
inserted++;
|
||||
// 标记为已存在,避免同批次重复插入
|
||||
existingInvoiceNos.add(invoiceNo);
|
||||
log.info("金蝶开票结果回写:写入发票,申请单号=[{}],单据编号=[{}],发票号码=[{}],含税金额=[{}]",
|
||||
application.getApplicationNo(), billNo, invoiceNo, invoice.getIncludeTaxAmount());
|
||||
}
|
||||
if (inserted > 0) {
|
||||
invoiceWritten += inserted;
|
||||
@@ -992,6 +1001,11 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl<InvoiceApplic
|
||||
Db.saveBatch(toInsertInvoices);
|
||||
log.info("金蝶开票结果回写:批量插入发票记录{}条", toInsertInvoices.size());
|
||||
}
|
||||
// 批量补齐历史记录缺失的发票文件地址
|
||||
if (!toPatchInvoices.isEmpty()) {
|
||||
Db.updateBatchById(toPatchInvoices);
|
||||
log.info("金蝶开票结果回写:批量补齐发票文件地址{}条", toPatchInvoices.size());
|
||||
}
|
||||
// 批量更新开票申请状态
|
||||
if (!toUpdateApplications.isEmpty()) {
|
||||
updateBatchById(toUpdateApplications);
|
||||
@@ -1010,20 +1024,46 @@ public class InvoiceApplicationServiceImpl extends BaseServiceImpl<InvoiceApplic
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量查询当前批次已存在的发票号码
|
||||
* 批量查询当前批次已落库的发票记录,按发票号索引
|
||||
*
|
||||
* @param serialNos 单据编号列表
|
||||
* @return 已存在的发票号码集合
|
||||
* @return 发票号 → 已落库发票记录
|
||||
*/
|
||||
private Set<String> batchQueryExistingInvoiceNos(List<String> serialNos) {
|
||||
private Map<String, InvoiceApplicationInvoice> batchQueryExistingInvoices(List<String> serialNos) {
|
||||
List<InvoiceApplicationInvoice> existingList = invoiceApplicationInvoiceMapper.selectList(
|
||||
Wrappers.<InvoiceApplicationInvoice>lambdaQuery()
|
||||
.in(InvoiceApplicationInvoice::getSerialNo, serialNos)
|
||||
.select(InvoiceApplicationInvoice::getInvoiceNo));
|
||||
.in(InvoiceApplicationInvoice::getSerialNo, serialNos));
|
||||
return existingList.stream()
|
||||
.map(InvoiceApplicationInvoice::getInvoiceNo)
|
||||
.filter(Func::isNotBlank)
|
||||
.collect(Collectors.toSet());
|
||||
.filter(record -> Func.isNotBlank(record.getInvoiceNo()))
|
||||
.collect(Collectors.toMap(InvoiceApplicationInvoice::getInvoiceNo, Function.identity(), (left, right) -> left));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 A303-5 返回的发票文件地址补齐到已落库记录(仅补空字段,不覆盖已有值)
|
||||
*
|
||||
* @param record 已落库发票记录
|
||||
* @param invoice A303-5 返回发票明细
|
||||
* @return 发生补齐时返回 true
|
||||
*/
|
||||
private boolean fillMissingInvoiceFileUrls(InvoiceApplicationInvoice record, BfiInvoiceApplyOpenApiInvoiceVO invoice) {
|
||||
boolean patched = false;
|
||||
if (Func.isBlank(record.getInvoiceFileUrl()) && Func.isNotBlank(invoice.getInvoiceFileUrl())) {
|
||||
record.setInvoiceFileUrl(invoice.getInvoiceFileUrl());
|
||||
patched = true;
|
||||
}
|
||||
if (Func.isBlank(record.getInvoiceImageUrl()) && Func.isNotBlank(invoice.getInvoiceImageUrl())) {
|
||||
record.setInvoiceImageUrl(invoice.getInvoiceImageUrl());
|
||||
patched = true;
|
||||
}
|
||||
if (Func.isBlank(record.getInvoicePdfFileUrl()) && Func.isNotBlank(invoice.getInvoicePdfFileUrl())) {
|
||||
record.setInvoicePdfFileUrl(invoice.getInvoicePdfFileUrl());
|
||||
patched = true;
|
||||
}
|
||||
if (Func.isBlank(record.getInvoiceXmlFileUrl()) && Func.isNotBlank(invoice.getInvoiceXmlFileUrl())) {
|
||||
record.setInvoiceXmlFileUrl(invoice.getInvoiceXmlFileUrl());
|
||||
patched = true;
|
||||
}
|
||||
return patched;
|
||||
}
|
||||
|
||||
/** 从票易通原文 Map 中按候选键顺序取首个非空文本值 */
|
||||
|
||||
Reference in New Issue
Block a user