🐛 修复收款流水继续认领与付款可付口径,并改用OA登录名提交流程

收款流水部分认领预填关联结算单并修正认领完成判定;正式结算尾款扣减预付占用;预付驳回重提纠正结算引用;临时额度取消强制项目额度校验;用户增加OA登录名供MK流程使用。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-19 17:12:22 +08:00
parent 68a5d581f3
commit fce582e80b
15 changed files with 337 additions and 84 deletions
@@ -1729,7 +1729,8 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
Map<Long, BigDecimal> receiptClaimPaidBySettlement = hasReceivable
? receiptClaimSettlementMapper.selectList(Wrappers.<ReceiptClaimSettlement>lambdaQuery()
.in(ReceiptClaimSettlement::getFormalSettlementId, settledIds)
.eq(ReceiptClaimSettlement::getStatus, 1)).stream()
.eq(ReceiptClaimSettlement::getStatus, 1)
.eq(ReceiptClaimSettlement::getIsDeleted, 0)).stream()
.collect(Collectors.groupingBy(ReceiptClaimSettlement::getFormalSettlementId,
Collectors.reducing(BigDecimal.ZERO,
item -> money(item.getAllocatedReceiptAmount()), BigDecimal::add)))
@@ -2161,7 +2162,8 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
: receiptClaimSettlementMapper.selectList(
Wrappers.<ReceiptClaimSettlement>lambdaQuery()
.eq(ReceiptClaimSettlement::getFormalSettlementId, settlement.getId())
.eq(ReceiptClaimSettlement::getStatus, 1))
.eq(ReceiptClaimSettlement::getStatus, 1)
.eq(ReceiptClaimSettlement::getIsDeleted, 0))
.stream().map(ReceiptClaimSettlement::getAllocatedReceiptAmount).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
@@ -321,11 +321,15 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
BigDecimal writeOffAmount = preSettlementReference
? projectAdvanceWriteOffService.sumBySettlement(TransportBusiConstant.SETTLEMENT_BILL_PRE, referenceId)
: projectAdvanceWriteOffService.sumBySettlement(TransportBusiConstant.SETTLEMENT_BILL_FORMAL, referenceId);
// 正式结算尾款还需扣减来源预结算进度预付(与正式结算剩余应付口径一致)
BigDecimal advanceOccupied = preSettlementReference
? BigDecimal.ZERO
: formalSettlementAdvanceOccupied(referenceId);
BigDecimal occupiedTotal = cumulativeAppliedAmount.add(writeOffAmount).add(advanceOccupied);
PaymentApplicationReferenceAmountVO amount = new PaymentApplicationReferenceAmountVO();
amount.setSettlementAmount(settlementAmount);
amount.setCumulativeAppliedAmount(cumulativeAppliedAmount.add(writeOffAmount));
amount.setPayableAmount(settlementAmount.subtract(cumulativeAppliedAmount).subtract(writeOffAmount)
.max(BigDecimal.ZERO));
amount.setCumulativeAppliedAmount(occupiedTotal);
amount.setPayableAmount(settlementAmount.subtract(occupiedTotal).max(BigDecimal.ZERO));
amount.setPaymentSequence(resolvePaymentSequence(preSettlementReference, referenceId, excludeId));
return amount;
}
@@ -1891,7 +1895,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
}
/**
* 进度预付只认预结算单。尾款付款若同时带回一个并不存在的正式结算单 ID,改为按预结算单保存。
* 进度预付只认预结算单;尾款付款若 settlementId 实际是预结算单 ID 或正式结算单已不存在,改为按预结算单保存。
*/
private void normalizeSettlementReference(PaymentApplicationSaveRequest request) {
if ("progress_advance".equals(request.getPaymentType())) {
@@ -1901,33 +1905,62 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
if (request.getPreSettlementId() == null) {
request.setPreSettlementId(request.getSettlementId());
}
if (request.getPreSettlementId() == null && request.getSettlementIds() != null) {
request.setPreSettlementId(request.getSettlementIds().stream().filter(Objects::nonNull).findFirst().orElse(null));
}
request.setSettlementId(null);
request.setSettlementIds(null);
return;
}
if (!TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT.equals(request.getPaymentType()) || request.getSettlementId() == null) {
if (!TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT.equals(request.getPaymentType())) {
return;
}
boolean hasPreSettlement = request.getPreSettlementId() != null
|| (request.getPreSettlementIds() != null && request.getPreSettlementIds().stream().anyMatch(Objects::nonNull));
if (!hasPreSettlement) {
// settlementIds 中若含无效正式结算单 ID,先尝试纠正为预结算
if (request.getSettlementId() == null && request.getSettlementIds() != null
&& !request.getSettlementIds().isEmpty()) {
request.setSettlementId(request.getSettlementIds().stream().filter(Objects::nonNull).findFirst().orElse(null));
}
if (request.getSettlementId() == null) {
return;
}
FormalSettlement formalSettlement = formalSettlementMapper.selectById(request.getSettlementId());
if (formalSettlement == null || Objects.equals(formalSettlement.getIsDeleted(), 1)) {
if (formalSettlement != null && !Objects.equals(formalSettlement.getIsDeleted(), 1)) {
return;
}
// 正式结算单不存在:若该 ID 实为预结算单,或请求已带预结算引用,则清空正式结算引用
PreSettlement preSettlement = preSettlementMapper.selectById(request.getSettlementId());
boolean isPreSettlement = preSettlement != null && !Objects.equals(preSettlement.getIsDeleted(), 1);
boolean hasPreSettlement = request.getPreSettlementId() != null
|| (request.getPreSettlementIds() != null && request.getPreSettlementIds().stream().anyMatch(Objects::nonNull));
if (isPreSettlement && request.getPreSettlementId() == null) {
request.setPreSettlementId(request.getSettlementId());
if (request.getPreSettlementIds() == null || request.getPreSettlementIds().isEmpty()) {
request.setPreSettlementIds(List.of(request.getSettlementId()));
}
}
if (isPreSettlement || hasPreSettlement) {
request.setSettlementId(null);
request.setSettlementIds(null);
}
}
/** 预结算付款不挂正式结算单时,清掉历史误写入的正式结算单 ID,避免再次提交按正式结算单刷新。 */
/**
* 预结算付款不挂正式结算单时,清掉历史误写入的正式结算单 ID,避免再次提交按正式结算单刷新。
*/
private void clearDetachedFormalReference(PaymentApplication entity) {
if (entity.getId() == null || entity.getPreSettlementId() == null || entity.getSettlementId() != null) {
if (entity.getId() == null) {
return;
}
boolean progressAdvance = "progress_advance".equals(entity.getPaymentType());
boolean preOnly = entity.getPreSettlementId() != null && entity.getSettlementId() == null;
if (!progressAdvance && !preOnly) {
return;
}
baseMapper.update(null, Wrappers.<PaymentApplication>lambdaUpdate()
.eq(PaymentApplication::getId, entity.getId())
.setSql("settlement_id = null, settlement_no = null"));
entity.setSettlementId(null);
entity.setSettlementNo(null);
}
private void fillReference(PaymentApplication entity, PaymentApplicationSaveRequest request) {
@@ -1938,15 +1971,26 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
entity.setProjectId(request.getProjectId()); entity.setProjectName(request.getProjectName()); entity.setDeptId(request.getDeptId()); entity.setDeptName(request.getDeptName()); entity.setContractId(request.getContractId()); entity.setContractNo(request.getContractNo()); entity.setContractName(request.getContractName()); entity.setPayerName(request.getPayerName()); entity.setPayeeName(request.getPayeeName()); entity.setSettlementAmount(request.getSettlementAmount()); entity.setPayableAmount(money(project.getFundDemand()).multiply(BigDecimal.valueOf(10000))); entity.setBillType(Func.isEmpty(project.getSettlementMode()) ? null : "项目预付");
} else if (request.getSettlementId() != null) {
FormalSettlement source = formalSettlementMapper.selectById(request.getSettlementId());
if (source == null) throw new ServiceException("正式结算单不存在");
if (!TransportBusiConstant.APPROVED.equals(source.getApprovalStatus())) throw new ServiceException("只能选择审批通过、未作废的正式结算单");
entity.setSettlementId(source.getId()); entity.setSettlementNo(source.getFormalSettlementNo()); entity.setPreSettlementId(null); entity.setPreSettlementNo(null);
entity.setProjectId(source.getProjectId()); entity.setProjectName(source.getProjectName()); entity.setDeptId(source.getDeptId()); entity.setDeptName(source.getDeptName()); entity.setContractId(source.getContractId()); entity.setContractNo(source.getContractNo()); entity.setContractName(source.getContractName()); entity.setPayerName(source.getPayerName()); entity.setPayeeName(source.getPayeeName()); entity.setSettlementAmount(source.getSettlementAmount()); entity.setPayableAmount(payableOfFormalSettlement(source.getId(), source.getSettlementAmount(), entity.getId())); entity.setBillType("正式结算单");
if (source == null || Objects.equals(source.getIsDeleted(), 1)) {
// 驳回重提等场景:settlementId 可能误存为预结算单 ID,降级走预结算分支
PreSettlement preSettlement = preSettlementMapper.selectById(request.getSettlementId());
if (preSettlement != null && !Objects.equals(preSettlement.getIsDeleted(), 1)) {
request.setPreSettlementId(preSettlement.getId());
request.setSettlementId(null);
fillPreSettlementReference(entity, preSettlement);
} else if (request.getPreSettlementId() != null) {
request.setSettlementId(null);
fillPreSettlementReference(entity, requireApprovedPreSettlement(request.getPreSettlementId()));
} else {
throw new ServiceException("正式结算单不存在");
}
} else {
if (!TransportBusiConstant.APPROVED.equals(source.getApprovalStatus())) throw new ServiceException("只能选择审批通过、未作废的正式结算单");
entity.setSettlementId(source.getId()); entity.setSettlementNo(source.getFormalSettlementNo()); entity.setPreSettlementId(null); entity.setPreSettlementNo(null);
entity.setProjectId(source.getProjectId()); entity.setProjectName(source.getProjectName()); entity.setDeptId(source.getDeptId()); entity.setDeptName(source.getDeptName()); entity.setContractId(source.getContractId()); entity.setContractNo(source.getContractNo()); entity.setContractName(source.getContractName()); entity.setPayerName(source.getPayerName()); entity.setPayeeName(source.getPayeeName()); entity.setSettlementAmount(source.getSettlementAmount()); entity.setPayableAmount(payableOfFormalSettlement(source.getId(), source.getSettlementAmount(), entity.getId())); entity.setBillType("正式结算单");
}
} else if (request.getPreSettlementId() != null) {
PreSettlement source = preSettlementMapper.selectById(request.getPreSettlementId());
if (source == null) throw new ServiceException("预结算单不存在");
if (!TransportBusiConstant.APPROVED.equals(source.getApprovalStatus())) throw new ServiceException("只能选择审批通过、未作废的预结算单");
entity.setPreSettlementId(source.getId()); entity.setPreSettlementNo(source.getPreSettlementNo()); entity.setSettlementId(null); entity.setSettlementNo(null); entity.setProjectId(source.getProjectId()); entity.setProjectName(source.getProjectName()); entity.setDeptId(source.getDeptId()); entity.setDeptName(source.getDeptName()); entity.setContractId(source.getContractId()); entity.setContractNo(source.getContractNo()); entity.setContractName(source.getContractName()); entity.setPayerName(source.getPayerName()); entity.setPayeeName(source.getPayeeName()); entity.setSettlementAmount(source.getSettlementAmount()); entity.setPayableAmount(payableOfPreSettlement(source.getId(), source.getSettlementAmount(), entity.getId())); entity.setBillType("预结算单");
fillPreSettlementReference(entity, requireApprovedPreSettlement(request.getPreSettlementId()));
} else {
entity.setProjectId(request.getProjectId()); entity.setProjectName(request.getProjectName()); entity.setDeptId(request.getDeptId()); entity.setDeptName(request.getDeptName()); entity.setContractId(request.getContractId()); entity.setContractNo(request.getContractNo()); entity.setContractName(request.getContractName()); entity.setPayerName(request.getPayerName()); entity.setPayeeName(request.getPayeeName()); entity.setSettlementAmount(request.getSettlementAmount()); entity.setPayableAmount(request.getPayableAmount()); entity.setBillType(request.getBillType());
}
@@ -1955,6 +1999,36 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
&& money(entity.getPayableAmount()).compareTo(BigDecimal.ZERO) > 0) throw new ServiceException("申请付款金额不能超过可付款金额");
}
private PreSettlement requireApprovedPreSettlement(Long preSettlementId) {
PreSettlement source = preSettlementMapper.selectById(preSettlementId);
if (source == null || Objects.equals(source.getIsDeleted(), 1)) {
throw new ServiceException("预结算单不存在");
}
if (!TransportBusiConstant.APPROVED.equals(source.getApprovalStatus())) {
throw new ServiceException("只能选择审批通过、未作废的预结算单");
}
return source;
}
private void fillPreSettlementReference(PaymentApplication entity, PreSettlement source) {
entity.setPreSettlementId(source.getId());
entity.setPreSettlementNo(source.getPreSettlementNo());
entity.setSettlementId(null);
entity.setSettlementNo(null);
entity.setProjectId(source.getProjectId());
entity.setProjectName(source.getProjectName());
entity.setDeptId(source.getDeptId());
entity.setDeptName(source.getDeptName());
entity.setContractId(source.getContractId());
entity.setContractNo(source.getContractNo());
entity.setContractName(source.getContractName());
entity.setPayerName(source.getPayerName());
entity.setPayeeName(source.getPayeeName());
entity.setSettlementAmount(source.getSettlementAmount());
entity.setPayableAmount(payableOfPreSettlement(source.getId(), source.getSettlementAmount(), entity.getId()));
entity.setBillType("预结算单");
}
private List<FormalSettlement> resolveFormalSettlements(PaymentApplicationSaveRequest request, PaymentApplication entity) {
if (!TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT.equals(request.getPaymentType())) return List.of();
List<Long> ids = request.getSettlementIds() == null ? List.of() : request.getSettlementIds();
@@ -2358,15 +2432,45 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
return relationAmount.add(legacyAmount);
}
/** 正式结算可付款 = 结算金额 − 已申请付款 − 项目预付核销 */
/**
* 正式结算可付款 = 结算金额 − 已申请付款 − 项目预付核销 − 来源预结算进度预付占用。
* 进度预付占用与正式结算台账 remainingPayable 口径对齐,避免转正式后仍可按全额申请尾款。
*/
private BigDecimal payableOfFormalSettlement(Long settlementId, BigDecimal settlementAmount, Long excludeId) {
return money(settlementAmount)
.subtract(cumulativeFormalSettlementApplied(settlementId, excludeId))
.subtract(projectAdvanceWriteOffService.sumBySettlement(
TransportBusiConstant.SETTLEMENT_BILL_FORMAL, settlementId))
.subtract(formalSettlementAdvanceOccupied(settlementId))
.max(BigDecimal.ZERO);
}
/**
* 正式结算来源预结算的进度预付占用合计:优先取来源行快照,快照为空时回退预结算实时已申请/已付。
*/
private BigDecimal formalSettlementAdvanceOccupied(Long settlementId) {
if (settlementId == null) {
return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
}
List<FormalSettlementSource> sources = formalSettlementSourceMapper.selectList(
Wrappers.<FormalSettlementSource>lambdaQuery()
.eq(FormalSettlementSource::getFormalSettlementId, settlementId)
.eq(FormalSettlementSource::getIsDeleted, 0));
if (sources.isEmpty()) {
return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
}
BigDecimal total = BigDecimal.ZERO;
for (FormalSettlementSource source : sources) {
BigDecimal occupied = money(source.getAdvanceAppliedAmount()).max(money(source.getAdvancePaidAmount()));
if (occupied.compareTo(BigDecimal.ZERO) <= 0 && source.getPreSettlementId() != null) {
occupied = findPreSettlementApplied(source.getPreSettlementId())
.max(findPreSettlementPaid(source.getPreSettlementId()));
}
total = total.add(occupied);
}
return total;
}
/** 预结算可付款 = 结算金额 − 已申请付款 − 项目预付核销 */
private BigDecimal payableOfPreSettlement(Long preSettlementId, BigDecimal settlementAmount, Long excludeId) {
return money(settlementAmount)
@@ -252,8 +252,8 @@ public class ReceiptClaimRecordServiceImpl extends BaseServiceImpl<ReceiptClaimM
BigDecimal flowClaimedAfter = money(flow.getClaimedAmount())
.subtract(money(claim.getClaimAmount())).max(BigDecimal.ZERO);
flow.setClaimedAmount(flowClaimedAfter);
// 认领状态对照公告未认领金额重算(作废分摊不计入累计)
flow.setClaimStatus(amountClaimStatus(flowClaimedAfter, flow.getUnclaimedAmount()));
// 认领状态对照可认领上限重算(作废分摊不计入累计)
flow.setClaimStatus(amountClaimStatus(flowClaimedAfter, claimCeiling(flow)));
receiptFlowMapper.updateById(flow);
// 作废不生成金蝶认领冲单:金蝶单号/状态保留原值(未推送为空,已推送为真实收款单信息),
@@ -411,13 +411,28 @@ public class ReceiptClaimRecordServiceImpl extends BaseServiceImpl<ReceiptClaimM
}
/**
* 累计认领金额 → 流水认领状态:对照公告未认领金额判定(与收款流水侧口径一致)
* 累计认领金额 → 流水认领状态:对照可认领上限判定(与收款流水侧口径一致)
*/
private String amountClaimStatus(BigDecimal claimedAmount, BigDecimal unclaimedAmount) {
private String amountClaimStatus(BigDecimal claimedAmount, BigDecimal claimCeiling) {
if (claimedAmount.compareTo(BigDecimal.ZERO) <= 0) {
return "unclaimed";
}
return claimedAmount.compareTo(money(unclaimedAmount)) >= 0 ? "claimed" : "partial";
return claimedAmount.compareTo(money(claimCeiling)) >= 0 ? "claimed" : "partial";
}
/**
* 可认领上限:公告未认领金额与收款金额取较小正值(与收款流水侧口径一致)
*/
private BigDecimal claimCeiling(KingdeeReceiptFlow flow) {
BigDecimal unclaimed = money(flow.getUnclaimedAmount());
BigDecimal receipt = money(flow.getReceiptAmount());
if (receipt.compareTo(BigDecimal.ZERO) <= 0) {
return unclaimed;
}
if (unclaimed.compareTo(BigDecimal.ZERO) <= 0) {
return unclaimed;
}
return unclaimed.min(receipt);
}
private String amountStatus(BigDecimal paidAmount, BigDecimal settlementAmount) {
@@ -194,12 +194,16 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
.eq(ReceiptFlowRecord::getReceiptFlowId, id)
.in(ReceiptFlowRecord::getActionType, List.of("claim", "push"))
.orderByDesc(ReceiptFlowRecord::getCreateTime)));
// 继续认领:带回本流水历史关联且仍有剩余可认领金额的结算单
vo.setRelatedSettlements(relatedSettlementCandidates(id));
return vo;
}
@Override
public List<Map<String, Object>> settlementCandidates(String keyword, Long flowId) {
existing(flowId);
// 本流水历史关联结算单优先保留(避免仅按创建时间 limit 截断后选不到)
Set<Long> relatedIds = relatedSettlementIds(flowId);
List<FormalSettlement> settlements = formalSettlementMapper.selectList(
Wrappers.<FormalSettlement>lambdaQuery()
.eq(FormalSettlement::getSettlementType, RECEIVABLE)
@@ -210,7 +214,24 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
.or().like(FormalSettlement::getProjectName, keyword)
.or().like(FormalSettlement::getContractName, keyword))
.orderByDesc(FormalSettlement::getCreateTime)
.last("limit 200"));
.last("limit 500"));
if (!relatedIds.isEmpty()) {
Set<Long> loadedIds = settlements.stream().map(FormalSettlement::getId)
.filter(Objects::nonNull).collect(Collectors.toSet());
List<Long> missingIds = relatedIds.stream().filter(id -> !loadedIds.contains(id)).toList();
if (!missingIds.isEmpty()) {
List<FormalSettlement> relatedSettlements = formalSettlementMapper.selectList(
Wrappers.<FormalSettlement>lambdaQuery()
.in(FormalSettlement::getId, missingIds)
.eq(FormalSettlement::getSettlementType, RECEIVABLE)
.eq(FormalSettlement::getApprovalStatus, APPROVED)
.eq(FormalSettlement::getStatus, 1));
List<FormalSettlement> merged = new ArrayList<>(relatedSettlements.size() + settlements.size());
merged.addAll(relatedSettlements);
merged.addAll(settlements);
settlements = merged;
}
}
// 候选结算单的累计认领金额一次聚合查询(避免逐单 N+1)
Map<Long, BigDecimal> claimedMap = receiptClaimedAmounts(settlements.stream()
.map(FormalSettlement::getId).toList());
@@ -218,6 +239,14 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
.map(settlement -> candidateRow(settlement,
claimedMap.getOrDefault(settlement.getId(), BigDecimal.ZERO)))
.filter(row -> ((BigDecimal) row.get("remainingReceiptAmount")).compareTo(BigDecimal.ZERO) > 0)
.sorted((left, right) -> {
boolean leftRelated = relatedIds.contains(toLong(left.get("id")));
boolean rightRelated = relatedIds.contains(toLong(right.get("id")));
if (leftRelated == rightRelated) {
return 0;
}
return leftRelated ? -1 : 1;
})
.toList();
}
@@ -234,6 +263,7 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
Wrappers.<ReceiptClaimSettlement>lambdaQuery()
.eq(ReceiptClaimSettlement::getFormalSettlementId, formalSettlementId)
.eq(ReceiptClaimSettlement::getStatus, 1)
.eq(ReceiptClaimSettlement::getIsDeleted, 0)
.orderByDesc(ReceiptClaimSettlement::getCreateTime));
if (relations.isEmpty()) {
return List.of();
@@ -315,19 +345,19 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
allocatedTotal = allocatedTotal.add(allocated);
}
// 公告未认领金额(认领上限与认领完成判定基准,A301-2 口径)
// 认领上限:公告未认领金额与收款金额取较小值(避免未认领金额异常偏大导致无法认领完成)
if (flow.getUnclaimedAmount() == null) {
throw new ServiceException("公告未认领金额缺失,请先手动同步金蝶收款流水");
}
BigDecimal unclaimedAmount = money(flow.getUnclaimedAmount());
if (unclaimedAmount.compareTo(BigDecimal.ZERO) <= 0) {
throw new ServiceException("该流水公告未认领金额为0,无需认领");
BigDecimal claimCeiling = claimCeiling(flow);
if (claimCeiling.compareTo(BigDecimal.ZERO) <= 0) {
throw new ServiceException("该流水可认领金额为0,无需认领");
}
// 流水认领前已认领金额
BigDecimal previousFlowClaimed = flowClaimedAmount(flow.getId());
if (previousFlowClaimed.add(allocatedTotal).compareTo(unclaimedAmount) > 0) {
throw new ServiceException("累计认领金额不能超过公告未认领金额,剩余可认领 "
+ unclaimedAmount.subtract(previousFlowClaimed).max(BigDecimal.ZERO));
if (previousFlowClaimed.add(allocatedTotal).compareTo(claimCeiling) > 0) {
throw new ServiceException("累计认领金额不能超过可认领上限,剩余可认领 "
+ claimCeiling.subtract(previousFlowClaimed).max(BigDecimal.ZERO));
}
// 当前用户部门
@@ -380,6 +410,9 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
relation.setClaimedReceiptAmount(previousClaimed);
// 本次分摊金额
relation.setAllocatedReceiptAmount(allocated);
// 有效分摊(与汇总/候选查询 status=1 口径一致)
relation.setStatus(1);
relation.setIsDeleted(0);
claimSettlementMapper.insert(relation);
}
@@ -390,8 +423,8 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
String fromStatus = normalizeClaimStatus(flow.getClaimStatus());
// 认领后累计金额
BigDecimal claimedAfter = previousFlowClaimed.add(allocatedTotal);
// 认领后状态(对照公告未认领金额)
String toStatus = amountClaimStatus(claimedAfter, unclaimedAmount);
// 认领后状态(对照可认领上限)
String toStatus = amountClaimStatus(claimedAfter, claimCeiling);
// 更新流水已认领金额
flow.setClaimedAmount(claimedAfter);
// 更新流水认领状态
@@ -499,9 +532,9 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
// 复制公告数据到实体(不覆盖本地认领字段)
copyAnnounceRow(entity, announce, detailSerialNo, receiptAmount);
// 根据本地累计认领金额与最新公告未认领金额重新计算认领状态
// 根据本地累计认领金额与可认领上限重新计算认领状态
entity.setClaimStatus(amountClaimStatus(money(entity.getClaimedAmount()),
entity.getUnclaimedAmount()));
claimCeiling(entity)));
if (created) {
toInsert.add(entity);
@@ -621,6 +654,46 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
}
}
/**
* 本流水历史关联过的结算单 id(有效分摊)
*/
private Set<Long> relatedSettlementIds(Long flowId) {
if (flowId == null) {
return Set.of();
}
return claimSettlementMapper.selectList(Wrappers.<ReceiptClaimSettlement>lambdaQuery()
.select(ReceiptClaimSettlement::getFormalSettlementId)
.eq(ReceiptClaimSettlement::getReceiptFlowId, flowId)
.eq(ReceiptClaimSettlement::getStatus, 1)
.eq(ReceiptClaimSettlement::getIsDeleted, 0)).stream()
.map(ReceiptClaimSettlement::getFormalSettlementId)
.filter(Objects::nonNull)
.collect(Collectors.toCollection(HashSet::new));
}
/**
* 继续认领预填:本流水历史关联且仍有剩余可认领金额的结算单
*/
private List<Map<String, Object>> relatedSettlementCandidates(Long flowId) {
Set<Long> relatedIds = relatedSettlementIds(flowId);
if (relatedIds.isEmpty()) {
return List.of();
}
List<FormalSettlement> settlements = formalSettlementMapper.selectList(
Wrappers.<FormalSettlement>lambdaQuery()
.in(FormalSettlement::getId, relatedIds)
.eq(FormalSettlement::getSettlementType, RECEIVABLE)
.eq(FormalSettlement::getApprovalStatus, APPROVED)
.eq(FormalSettlement::getStatus, 1));
Map<Long, BigDecimal> claimedMap = receiptClaimedAmounts(settlements.stream()
.map(FormalSettlement::getId).toList());
return settlements.stream()
.map(settlement -> candidateRow(settlement,
claimedMap.getOrDefault(settlement.getId(), BigDecimal.ZERO)))
.filter(row -> ((BigDecimal) row.get("remainingReceiptAmount")).compareTo(BigDecimal.ZERO) > 0)
.toList();
}
/**
* 批量聚合结算单的累计有效认领金额:一次 IN 查询按结算单分组求和,避免逐单 N+1
*/
@@ -631,7 +704,8 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
}
return claimSettlementMapper.selectList(Wrappers.<ReceiptClaimSettlement>lambdaQuery()
.in(ReceiptClaimSettlement::getFormalSettlementId, ids)
.eq(ReceiptClaimSettlement::getStatus, 1)).stream()
.eq(ReceiptClaimSettlement::getStatus, 1)
.eq(ReceiptClaimSettlement::getIsDeleted, 0)).stream()
.collect(Collectors.groupingBy(ReceiptClaimSettlement::getFormalSettlementId,
Collectors.reducing(BigDecimal.ZERO,
item -> money(item.getAllocatedReceiptAmount()), BigDecimal::add)));
@@ -640,12 +714,42 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
private BigDecimal flowClaimedAmount(Long flowId) {
return claimSettlementMapper.selectList(Wrappers.<ReceiptClaimSettlement>lambdaQuery()
.eq(ReceiptClaimSettlement::getReceiptFlowId, flowId)
.eq(ReceiptClaimSettlement::getStatus, 1)).stream()
.eq(ReceiptClaimSettlement::getStatus, 1)
.eq(ReceiptClaimSettlement::getIsDeleted, 0)).stream()
.map(ReceiptClaimSettlement::getAllocatedReceiptAmount)
.map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
/**
* 可认领上限:公告未认领金额与收款金额取较小正值。
* 主口径仍为 A301-2 公告未认领金额;与收款金额取 min,避免未认领金额异常偏大导致无法进入「认领完成」。
*/
private BigDecimal claimCeiling(KingdeeReceiptFlow flow) {
BigDecimal unclaimed = money(flow.getUnclaimedAmount());
BigDecimal receipt = money(flow.getReceiptAmount());
if (receipt.compareTo(BigDecimal.ZERO) <= 0) {
return unclaimed;
}
if (unclaimed.compareTo(BigDecimal.ZERO) <= 0) {
return unclaimed;
}
return unclaimed.min(receipt);
}
private Long toLong(Object value) {
if (value == null) {
return null;
}
if (value instanceof Long longValue) {
return longValue;
}
if (value instanceof Number number) {
return number.longValue();
}
return Func.toLong(String.valueOf(value));
}
private KingdeeReceiptFlow lockedFlow(Long flowId) {
KingdeeReceiptFlow flow = baseMapper.selectOne(Wrappers.<KingdeeReceiptFlow>lambdaQuery()
.eq(KingdeeReceiptFlow::getId, flowId)
@@ -824,18 +928,20 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
flow.setClaimMe(announce.getClaimme());
BigDecimal claimedTotal = flowClaimedAmount(flow.getId());
flow.setClaimedAmount(claimedTotal);
String claimStatus = amountClaimStatus(claimedTotal, flow.getUnclaimedAmount());
BigDecimal claimCeiling = claimCeiling(flow);
String claimStatus = amountClaimStatus(claimedTotal, claimCeiling);
flow.setClaimStatus(claimStatus);
updateById(flow);
if (!CLAIMED.equals(claimStatus)) {
return ReceiptClaimPushResult.fail("累计认领金额(" + claimedTotal + ")与公告未认领金额("
+ money(flow.getUnclaimedAmount()) + ")不一致,不满足推送条件");
return ReceiptClaimPushResult.fail("累计认领金额(" + claimedTotal + ")与可认领上限("
+ claimCeiling + ")不一致,不满足推送条件");
}
// 分录取该流水全部有效分摊行(结算单id升序,跨认领主单归集)
List<ReceiptClaimSettlement> allocations = claimSettlementMapper.selectList(
Wrappers.<ReceiptClaimSettlement>lambdaQuery()
.eq(ReceiptClaimSettlement::getReceiptFlowId, flow.getId())
.eq(ReceiptClaimSettlement::getStatus, 1)
.eq(ReceiptClaimSettlement::getIsDeleted, 0)
.orderByAsc(ReceiptClaimSettlement::getFormalSettlementId));
if (allocations.isEmpty()) {
return ReceiptClaimPushResult.fail("该流水无有效认领分摊,无法推送");
@@ -1075,13 +1181,13 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
}
/**
* 累计认领金额 → 流水认领状态:对照公告未认领金额判定(claimed 即「认领完成」,唯一可推送状态)
* 累计认领金额 → 流水认领状态:对照可认领上限判定(claimed 即「认领完成」,唯一可推送状态)
*/
private String amountClaimStatus(BigDecimal claimedAmount, BigDecimal unclaimedAmount) {
private String amountClaimStatus(BigDecimal claimedAmount, BigDecimal claimCeiling) {
if (claimedAmount.compareTo(BigDecimal.ZERO) <= 0) {
return UNCLAIMED;
}
return claimedAmount.compareTo(money(unclaimedAmount)) >= 0 ? CLAIMED : PARTIAL;
return claimedAmount.compareTo(money(claimCeiling)) >= 0 ? CLAIMED : PARTIAL;
}
private String amountStatus(BigDecimal claimedAmount, BigDecimal settlementAmount) {
@@ -246,10 +246,8 @@ public class TemporaryCreditLimitServiceImpl extends BaseServiceImpl<TemporaryCr
TransportBusinessSupport.validateRequired(temporaryCreditLimit.getProjectCode(), "项目编号不能为空");
TransportBusinessSupport.validateRequired(temporaryCreditLimit.getUndertakeDeptName(), "承办部门不能为空");
TransportBusinessSupport.validateRequired(temporaryCreditLimit.getValidUntil() == null ? null : temporaryCreditLimit.getValidUntil().toString(), "请选择申请有效期至");
if (temporaryCreditLimit.getProjectFundLimit() == null || temporaryCreditLimit.getUsedFundLimit() == null || temporaryCreditLimit.getRemainingFundLimit() == null) {
throw new ServiceException("项目额度数据异常,请重新选择项目");
}
if (temporaryCreditLimit.getRemainingFundLimit().compareTo(BigDecimal.ZERO) < 0) {
if (temporaryCreditLimit.getRemainingFundLimit() != null
&& temporaryCreditLimit.getRemainingFundLimit().compareTo(BigDecimal.ZERO) < 0) {
throw new ServiceException("项目剩余额度不能小于0");
}
}
@@ -70,10 +70,15 @@ public class ReceiptFlowWrapper extends BaseEntityWrapper<KingdeeReceiptFlow, Re
case "failed" -> "处理失败";
default -> "未推送";
});
// 剩余可认领金额 = 公告未认领金额 − 累计认领金额(认领上限为公告口径)
// 剩余可认领金额 = 可认领上限 − 累计认领金额(上限取公告未认领与收款金额较小值)
BigDecimal unclaimedAmount = money(entity.getUnclaimedAmount());
BigDecimal receiptAmount = money(entity.getReceiptAmount());
BigDecimal claimCeiling = unclaimedAmount;
if (receiptAmount.compareTo(BigDecimal.ZERO) > 0 && unclaimedAmount.compareTo(BigDecimal.ZERO) > 0) {
claimCeiling = unclaimedAmount.min(receiptAmount);
}
BigDecimal claimedAmount = money(entity.getClaimedAmount());
vo.setRemainingAmount(unclaimedAmount.subtract(claimedAmount).max(BigDecimal.ZERO));
vo.setRemainingAmount(claimCeiling.subtract(claimedAmount).max(BigDecimal.ZERO));
vo.setCreateUserName(UserCache.getUserRealName(entity.getCreateUser()));
vo.setUpdateUserName(UserCache.getUserRealName(entity.getUpdateUser()));
return vo;