feat: 正式结算单整合项目预付核销并关联展示预付付款记录
核销金额计入结算单已申请/已付汇总(仅已付款预付可核销);保存转移来源预结算核销行、调整重推迁移核销行;付款申请作废/结算单作废/金蝶删单回调/预结算作废联动清理核销行;结算调整前校验核销合计;详情返回统一预付付款记录(advancePayments);列表页批量汇总避免N+1
This commit is contained in:
+1
@@ -72,6 +72,7 @@ public class FormalSettlementVO extends FormalSettlement implements KingdeeUnloc
|
||||
@TableField(exist = false) @Schema(description = "开票申请经金蝶回写的发票结果(含文件地址,只读展示)")
|
||||
private List<InvoiceApplicationInvoice> issuedInvoices;
|
||||
@TableField(exist = false) private List<PaymentApplicationVO> paymentApplications;
|
||||
@TableField(exist = false) private List<SettlementAdvancePaymentVO> advancePayments;
|
||||
@TableField(exist = false) private List<SettlementAdjustmentVO> adjustments;
|
||||
@TableField(exist = false) private List<FormalSettlementChangeRecord> changeRecords;
|
||||
}
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.pojo.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 结算单统一预付付款记录(进度预付 / 项目预付核销,供付款记录合并展示)
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "结算单统一预付付款记录")
|
||||
public class SettlementAdvancePaymentVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "记录类型:progress_advance 进度预付 / project_advance 项目预付核销")
|
||||
private String recordType;
|
||||
|
||||
@Schema(description = "付款单号")
|
||||
private String paymentNo;
|
||||
|
||||
@Schema(description = "申请金额(进度预付为申请预付金额,项目预付为本次核销金额)")
|
||||
private BigDecimal appliedAmount;
|
||||
|
||||
@Schema(description = "已付款金额(底层付款申请实付)")
|
||||
private BigDecimal paidAmount;
|
||||
|
||||
@Schema(description = "单据状态")
|
||||
private String billStatus;
|
||||
|
||||
@Schema(description = "金蝶单据号")
|
||||
private String kingdeeBillNo;
|
||||
|
||||
@Schema(description = "创建人姓名")
|
||||
private String createUserName;
|
||||
|
||||
@Schema(description = "创建时间(统一取底层付款申请创建时间)")
|
||||
private Date createTime;
|
||||
|
||||
}
|
||||
+11
@@ -15,6 +15,9 @@ import org.apache.ibatis.annotations.Param;
|
||||
import org.springblade.transport.pojo.entity.SettlementProjectAdvanceWriteOff;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 结算单项目预付核销 Mapper
|
||||
@@ -37,4 +40,12 @@ public interface SettlementProjectAdvanceWriteOffMapper extends BaseMapper<Settl
|
||||
BigDecimal sumWriteOffBySettlement(@Param("settlementType") String settlementType,
|
||||
@Param("settlementId") Long settlementId);
|
||||
|
||||
/**
|
||||
* 按结算单集合分组统计核销合计(一次 group-by,供批量刷新与列表页使用,避免逐单 N+1)。
|
||||
*
|
||||
* @return settlementId / totalAmount 两列
|
||||
*/
|
||||
List<Map<String, Object>> sumWriteOffBySettlementIds(@Param("settlementType") String settlementType,
|
||||
@Param("settlementIds") Collection<Long> settlementIds);
|
||||
|
||||
}
|
||||
|
||||
+12
@@ -32,4 +32,16 @@
|
||||
AND settlement_id = #{settlementId}
|
||||
</select>
|
||||
|
||||
<select id="sumWriteOffBySettlementIds" resultType="java.util.HashMap">
|
||||
SELECT settlement_id AS settlementId, COALESCE(SUM(write_off_amount), 0) AS totalAmount
|
||||
FROM blade_settlement_project_advance_write_off
|
||||
WHERE is_deleted = 0
|
||||
AND settlement_type = #{settlementType}
|
||||
AND settlement_id IN
|
||||
<foreach collection="settlementIds" item="settlementId" open="(" separator="," close=")">
|
||||
#{settlementId}
|
||||
</foreach>
|
||||
GROUP BY settlement_id
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
+18
-1
@@ -14,7 +14,9 @@ import org.springblade.transport.pojo.entity.SettlementProjectAdvanceWriteOff;
|
||||
import org.springblade.transport.pojo.vo.ProjectAdvanceWriteOffOptionVO;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 结算单项目预付核销服务
|
||||
@@ -24,7 +26,7 @@ import java.util.List;
|
||||
public interface ISettlementProjectAdvanceWriteOffService {
|
||||
|
||||
/**
|
||||
* 按合同查询可选项目预付(审批通过且仍有可核销余额)。
|
||||
* 按合同查询可选项目预付(审批通过、金蝶已付款且仍有可核销余额)。
|
||||
*/
|
||||
List<ProjectAdvanceWriteOffOptionVO> listOptions(Long contractId, String excludeSettlementType,
|
||||
Long excludeSettlementId);
|
||||
@@ -39,6 +41,11 @@ public interface ISettlementProjectAdvanceWriteOffService {
|
||||
*/
|
||||
BigDecimal sumBySettlement(String settlementType, Long settlementId);
|
||||
|
||||
/**
|
||||
* 按结算单集合分组统计核销合计(一次 group-by,供批量刷新与列表页使用)。
|
||||
*/
|
||||
Map<Long, BigDecimal> sumBySettlementIds(String settlementType, Collection<Long> settlementIds);
|
||||
|
||||
/**
|
||||
* 覆盖保存核销明细,并校验不超过可核销金额与结算单金额。
|
||||
*/
|
||||
@@ -50,4 +57,14 @@ public interface ISettlementProjectAdvanceWriteOffService {
|
||||
*/
|
||||
void deleteBySettlement(String settlementType, Long settlementId);
|
||||
|
||||
/**
|
||||
* 删除指定结算单集合上的核销明细(核销行转移/结算单作废联动),返回删除行数。
|
||||
*/
|
||||
int deleteBySettlementIds(String settlementType, Collection<Long> settlementIds);
|
||||
|
||||
/**
|
||||
* 按付款申请删除其核销明细(付款申请作废联动),返回受影响的正式结算单id。
|
||||
*/
|
||||
List<Long> deleteByPaymentApplicationIds(Collection<Long> paymentApplicationIds);
|
||||
|
||||
}
|
||||
|
||||
+144
-7
@@ -33,12 +33,14 @@ 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.PreSettlementAdvanceMapper;
|
||||
import org.springblade.transport.mapper.PreSettlementMapper;
|
||||
import org.springblade.transport.mapper.PreSettlementSummaryFeeMapper;
|
||||
import org.springblade.transport.mapper.PaymentApplicationMapper;
|
||||
import org.springblade.transport.mapper.PaymentApplicationInvoiceMapper;
|
||||
import org.springblade.transport.mapper.PaymentApplicationSettlementMapper;
|
||||
import org.springblade.transport.mapper.ReceiptClaimSettlementMapper;
|
||||
import org.springblade.transport.mapper.SettlementProjectAdvanceWriteOffMapper;
|
||||
import org.springblade.transport.constant.KingdeeAttachmentConstant;
|
||||
import org.springblade.transport.config.BfiProperties;
|
||||
import org.springblade.transport.constant.KingdeeBfiConstant;
|
||||
@@ -69,6 +71,7 @@ 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.PreSettlementAdvance;
|
||||
import org.springblade.transport.pojo.entity.PreSettlementDetail;
|
||||
import org.springblade.transport.pojo.entity.PreSettlementDetailFee;
|
||||
import org.springblade.transport.pojo.entity.PreSettlementSummaryFee;
|
||||
@@ -79,9 +82,11 @@ import org.springblade.transport.pojo.entity.ReceiptClaimSettlement;
|
||||
import org.springblade.transport.pojo.entity.ReceivablePayableDetail;
|
||||
import org.springblade.transport.pojo.entity.ReceivablePayableCargoFee;
|
||||
import org.springblade.transport.pojo.entity.SettlementAdjustment;
|
||||
import org.springblade.transport.pojo.entity.SettlementProjectAdvanceWriteOff;
|
||||
import org.springblade.transport.pojo.entity.Waybill;
|
||||
import org.springblade.transport.pojo.vo.FormalSettlementVO;
|
||||
import org.springblade.transport.pojo.vo.PreSettlementVO;
|
||||
import org.springblade.transport.pojo.vo.SettlementAdvancePaymentVO;
|
||||
import org.springblade.transport.pojo.vo.SettlementAdjustmentVO;
|
||||
import org.springblade.transport.service.IFormalSettlementService;
|
||||
import org.springblade.transport.service.IInvoiceReceiptService;
|
||||
@@ -113,6 +118,8 @@ import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
@@ -162,6 +169,8 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
private final PreSettlementSummaryFeeMapper preSummaryFeeMapper;
|
||||
private final PreSettlementDetailMapper preDetailMapper;
|
||||
private final PreSettlementDetailFeeMapper preDetailFeeMapper;
|
||||
private final PreSettlementAdvanceMapper preSettlementAdvanceMapper;
|
||||
private final SettlementProjectAdvanceWriteOffMapper projectAdvanceWriteOffMapper;
|
||||
private final ReceivablePayableDetailMapper receivablePayableMapper;
|
||||
private final ReceivablePayableCargoFeeMapper receivablePayableCargoFeeMapper;
|
||||
private final PaymentApplicationMapper paymentApplicationMapper;
|
||||
@@ -207,7 +216,13 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
if (ids.isEmpty()) wrapper.eq(FormalSettlement::getId, -1L); else wrapper.in(FormalSettlement::getId, ids);
|
||||
}
|
||||
IPage<FormalSettlement> result = page(page, wrapper.orderByDesc(FormalSettlement::getCreateTime));
|
||||
IPage<FormalSettlementVO> voPage = result.convert(this::toVO);
|
||||
// 本页应付结算单的项目预付核销合计一次查询,避免逐行 toVO 重算汇总时的 N+1
|
||||
Map<Long, BigDecimal> writeOffAmounts = this.projectAdvanceWriteOffService.sumBySettlementIds(
|
||||
TransportBusiConstant.SETTLEMENT_BILL_FORMAL,
|
||||
result.getRecords().stream()
|
||||
.filter(item -> "payable".equals(item.getSettlementType()))
|
||||
.map(FormalSettlement::getId).toList());
|
||||
IPage<FormalSettlementVO> voPage = result.convert(item -> this.toVO(item, writeOffAmounts));
|
||||
fillAttachmentSyncStatus(voPage.getRecords());
|
||||
fillKingdeeLockStatus(voPage.getRecords());
|
||||
return voPage;
|
||||
@@ -322,6 +337,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
vo.setIssuedInvoices(listIssuedInvoices(id));
|
||||
vo.setProjectAdvanceWriteOffs(projectAdvanceWriteOffService.listBySettlement(
|
||||
TransportBusiConstant.SETTLEMENT_BILL_FORMAL, id));
|
||||
vo.setAdvancePayments(this.listAdvancePayments(sources, vo.getProjectAdvanceWriteOffs()));
|
||||
Set<Long> relationApplicationIds = paymentApplicationSettlementMapper.selectList(
|
||||
Wrappers.<PaymentApplicationSettlement>lambdaQuery()
|
||||
.select(PaymentApplicationSettlement::getPaymentApplicationId)
|
||||
@@ -545,12 +561,17 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
applySummaryRequest(settlement.getId(), request.getSummaryFees());
|
||||
refreshSettlementAmount(settlement);
|
||||
rebuildInvoices(settlement, request.getInvoices());
|
||||
// 预结算转正式/调整来源:先转移来源预结算的项目预付核销行(释放可核销占用),replaceWriteOffs 的余额校验才能通过
|
||||
this.projectAdvanceWriteOffService.deleteBySettlementIds(
|
||||
TransportBusiConstant.SETTLEMENT_BILL_PRE, request.getSourcePreSettlementIds());
|
||||
projectAdvanceWriteOffService.replaceWriteOffs(
|
||||
TransportBusiConstant.SETTLEMENT_BILL_FORMAL,
|
||||
settlement.getId(),
|
||||
settlement.getContractId(),
|
||||
settlement.getSettlementAmount(),
|
||||
request.getProjectAdvanceWriteOffs());
|
||||
// 核销行计入收付汇总(refreshSettlementAmount 内的刷新发生在核销落库之前,覆盖不到核销行)
|
||||
this.refreshPaymentSummary(settlement.getId());
|
||||
if (!creating && !restoring) {
|
||||
saveChange(settlement.getId(), "结算单基本信息", null, "调整",
|
||||
"保存正式结算单" + settlement.getFormalSettlementNo(), request.getRemark());
|
||||
@@ -605,6 +626,9 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
settlement.setCurrentNode("已作废");
|
||||
settlement.setVoidReason(required(limit(request.getReason(), 200), "作废原因"));
|
||||
updateById(settlement);
|
||||
// 联动删除项目预付核销行,释放可核销占用并避免残留记录
|
||||
this.projectAdvanceWriteOffService.deleteBySettlementIds(
|
||||
TransportBusiConstant.SETTLEMENT_BILL_FORMAL, List.of(settlement.getId()));
|
||||
saveChange(settlement.getId(), "结算单基本信息", null, "调整", "作废正式结算单", request.getReason());
|
||||
}
|
||||
|
||||
@@ -624,6 +648,9 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
.set(FormalSettlement::getKingdeeBillNo, null)
|
||||
.set(FormalSettlement::getKingdeeBillId, null)
|
||||
.set(FormalSettlement::getSyncedTime, null));
|
||||
// 联动删除项目预付核销行,释放可核销占用并避免残留记录
|
||||
this.projectAdvanceWriteOffService.deleteBySettlementIds(
|
||||
TransportBusiConstant.SETTLEMENT_BILL_FORMAL, List.of(id));
|
||||
// 变更流水:回调无登录态,操作人取金蝶报文(兜底 system,与 saveChange 同款)
|
||||
FormalSettlementChangeRecord record = new FormalSettlementChangeRecord();
|
||||
record.setFormalSettlementId(id);
|
||||
@@ -702,8 +729,24 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
cloneRelation.setFormalSettlementId(clone.getId());
|
||||
sourceMapper.insert(cloneRelation);
|
||||
}
|
||||
log.info("结算调整重推克隆正式结算单:原单[{}] -> 新单[{}](明细{}条)",
|
||||
source.getFormalSettlementNo(), clone.getFormalSettlementNo(), details.size());
|
||||
// 核销行迁移:克隆到新单并删除原单行,避免预付可核销额度双占(原单为调整存档,收付口径以新单为准)
|
||||
List<SettlementProjectAdvanceWriteOff> writeOffRows = this.projectAdvanceWriteOffMapper.selectList(
|
||||
Wrappers.<SettlementProjectAdvanceWriteOff>lambdaQuery()
|
||||
.eq(SettlementProjectAdvanceWriteOff::getSettlementType, TransportBusiConstant.SETTLEMENT_BILL_FORMAL)
|
||||
.eq(SettlementProjectAdvanceWriteOff::getSettlementId, sourceSettlementId)
|
||||
.eq(SettlementProjectAdvanceWriteOff::getIsDeleted, 0));
|
||||
for (SettlementProjectAdvanceWriteOff writeOffRow : writeOffRows) {
|
||||
SettlementProjectAdvanceWriteOff cloneWriteOff = Objects.requireNonNull(
|
||||
BeanUtil.copyProperties(writeOffRow, SettlementProjectAdvanceWriteOff.class));
|
||||
resetAuditFields(cloneWriteOff);
|
||||
cloneWriteOff.setSettlementId(clone.getId());
|
||||
this.projectAdvanceWriteOffMapper.insert(cloneWriteOff);
|
||||
}
|
||||
this.projectAdvanceWriteOffService.deleteBySettlementIds(
|
||||
TransportBusiConstant.SETTLEMENT_BILL_FORMAL, List.of(sourceSettlementId));
|
||||
this.refreshPaymentSummaries(List.of(sourceSettlementId, clone.getId()));
|
||||
log.info("结算调整重推克隆正式结算单:原单[{}] -> 新单[{}](明细{}条,核销迁移{}条)",
|
||||
source.getFormalSettlementNo(), clone.getFormalSettlementNo(), details.size(), writeOffRows.size());
|
||||
return new CloneResult(clone.getId(), detailIdMap, feeIdMap);
|
||||
}
|
||||
|
||||
@@ -1601,6 +1644,11 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
Collectors.reducing(BigDecimal.ZERO,
|
||||
item -> money(item.getAllocatedReceiptAmount()), BigDecimal::add)))
|
||||
: Map.of();
|
||||
// 项目预付核销合计(仅应付结算单需要,一次 group-by 避免逐单查询)
|
||||
Map<Long, BigDecimal> writeOffAmountsBySettlement = hasPayable
|
||||
? this.projectAdvanceWriteOffService.sumBySettlementIds(
|
||||
TransportBusiConstant.SETTLEMENT_BILL_FORMAL, settledIds)
|
||||
: Map.of();
|
||||
|
||||
for (FormalSettlement settlement : settlements) {
|
||||
List<PaymentApplicationSettlement> relations = relationsBySettlement
|
||||
@@ -1616,7 +1664,8 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
PaymentSummary summary = calculatePaymentSummary(settlement, new PaymentSummaryData(
|
||||
relations, directApplications, relationStatuses,
|
||||
sourcesBySettlement.getOrDefault(settlement.getId(), List.of()),
|
||||
receiptClaimPaidBySettlement.getOrDefault(settlement.getId(), BigDecimal.ZERO)));
|
||||
receiptClaimPaidBySettlement.getOrDefault(settlement.getId(), BigDecimal.ZERO),
|
||||
writeOffAmountsBySettlement));
|
||||
settlement.setAppliedPaymentAmount(summary.appliedAmount());
|
||||
settlement.setPaidAmount(summary.paidAmount());
|
||||
settlement.setRemainingPayableAmount(money(settlement.getSettlementAmount())
|
||||
@@ -1825,9 +1874,16 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
}
|
||||
|
||||
private FormalSettlementVO toVO(FormalSettlement entity) {
|
||||
return this.toVO(entity, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param writeOffAmounts 结算单id -> 项目预付核销合计(列表页批量传入避免 N+1;null 表示按需单笔查询)
|
||||
*/
|
||||
private FormalSettlementVO toVO(FormalSettlement entity, Map<Long, BigDecimal> writeOffAmounts) {
|
||||
FormalSettlementVO vo = FormalSettlementWrapper.build().entityVO(entity);
|
||||
vo.setInvoiceStatus(invoiceStatus(entity.getInvoiceAmount(), entity.getSettlementAmount()));
|
||||
PaymentSummary summary = calculatePaymentSummary(entity);
|
||||
PaymentSummary summary = this.calculatePaymentSummary(entity, writeOffAmounts);
|
||||
vo.setAppliedPaymentAmount(summary.appliedAmount());
|
||||
vo.setPaidAmount(summary.paidAmount());
|
||||
vo.setRemainingPayableAmount(money(entity.getSettlementAmount()).subtract(summary.paidAmount())
|
||||
@@ -1837,6 +1893,10 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
}
|
||||
|
||||
private PaymentSummary calculatePaymentSummary(FormalSettlement settlement) {
|
||||
return this.calculatePaymentSummary(settlement, (Map<Long, BigDecimal>) null);
|
||||
}
|
||||
|
||||
private PaymentSummary calculatePaymentSummary(FormalSettlement settlement, Map<Long, BigDecimal> writeOffAmounts) {
|
||||
List<PaymentApplicationSettlement> relations = paymentApplicationSettlementMapper.selectList(
|
||||
Wrappers.<PaymentApplicationSettlement>lambdaQuery()
|
||||
.eq(PaymentApplicationSettlement::getFormalSettlementId, settlement.getId())
|
||||
@@ -1854,7 +1914,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
.in(PaymentApplication::getId, relationApplicationIds).eq(PaymentApplication::getIsDeleted, 0))
|
||||
.stream().collect(Collectors.toMap(PaymentApplication::getId, PaymentApplication::getApprovalStatus));
|
||||
return calculatePaymentSummary(settlement, new PaymentSummaryData(relations, applications,
|
||||
relationStatuses, null, null));
|
||||
relationStatuses, null, null, writeOffAmounts));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1887,6 +1947,13 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
.map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add));
|
||||
paidAmount = paidAmount.add(sources.stream().map(FormalSettlementSource::getAdvancePaidAmount)
|
||||
.map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add));
|
||||
// 项目预付核销计入已付/已申请(核销行底层预付必已付款,核销额即实付抵扣)
|
||||
BigDecimal writeOffAmount = data.writeOffAmounts() != null
|
||||
? data.writeOffAmounts().getOrDefault(settlement.getId(), BigDecimal.ZERO)
|
||||
: this.projectAdvanceWriteOffService.sumBySettlement(
|
||||
TransportBusiConstant.SETTLEMENT_BILL_FORMAL, settlement.getId());
|
||||
appliedAmount = appliedAmount.add(this.money(writeOffAmount));
|
||||
paidAmount = paidAmount.add(this.money(writeOffAmount));
|
||||
}
|
||||
if ("receivable".equals(settlement.getSettlementType())) {
|
||||
paidAmount = data.receiptClaimPaid() != null ? data.receiptClaimPaid()
|
||||
@@ -1922,10 +1989,12 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
* @param relationStatuses 付款申请id → 审批状态
|
||||
* @param sources 预收来源行(仅应付结算单;null 表示由纯计算按需查询)
|
||||
* @param receiptClaimPaid 收款认领分摊聚合(仅应收结算单;null 表示由纯计算按需查询)
|
||||
* @param writeOffAmounts 结算单id → 项目预付核销合计(仅应付结算单;null 表示由纯计算按需查询)
|
||||
*/
|
||||
private record PaymentSummaryData(List<PaymentApplicationSettlement> relations,
|
||||
List<PaymentApplication> directApplications, Map<Long, String> relationStatuses,
|
||||
List<FormalSettlementSource> sources, BigDecimal receiptClaimPaid) {
|
||||
List<FormalSettlementSource> sources, BigDecimal receiptClaimPaid,
|
||||
Map<Long, BigDecimal> writeOffAmounts) {
|
||||
}
|
||||
|
||||
private SettlementAdjustmentVO toAdjustmentVO(SettlementAdjustment entity) {
|
||||
@@ -1937,6 +2006,74 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一预付付款记录:进度预付(来源预结算台账)+ 项目预付核销(回填底层付款申请实付)。
|
||||
* createTime 统一取底层付款申请创建时间(项目预付核销行已回填 paymentCreateTime,进度预付按单号反查)。
|
||||
*/
|
||||
private List<SettlementAdvancePaymentVO> listAdvancePayments(List<FormalSettlementSource> sources,
|
||||
List<SettlementProjectAdvanceWriteOff> writeOffs) {
|
||||
List<SettlementAdvancePaymentVO> records = new ArrayList<>();
|
||||
List<Long> preSettlementIds = sources.stream().map(FormalSettlementSource::getPreSettlementId)
|
||||
.filter(Objects::nonNull).distinct().toList();
|
||||
if (!preSettlementIds.isEmpty()) {
|
||||
List<PreSettlementAdvance> advances = this.preSettlementAdvanceMapper.selectList(
|
||||
Wrappers.<PreSettlementAdvance>lambdaQuery()
|
||||
.eq(PreSettlementAdvance::getIsDeleted, 0)
|
||||
.ne(PreSettlementAdvance::getBillStatus, TransportBusiConstant.VOIDED)
|
||||
.in(PreSettlementAdvance::getPreSettlementId, preSettlementIds));
|
||||
// 进度预付行的展示时间取底层付款申请创建时间(advanceNo 即付款申请单号)
|
||||
List<String> advanceNos = advances.stream().map(PreSettlementAdvance::getAdvanceNo)
|
||||
.filter(Func::isNotEmpty).distinct().toList();
|
||||
Map<String, Date> applicationTimes = advanceNos.isEmpty() ? Map.of()
|
||||
: this.paymentApplicationMapper.selectList(Wrappers.<PaymentApplication>lambdaQuery()
|
||||
.select(PaymentApplication::getPaymentNo, PaymentApplication::getCreateTime)
|
||||
.eq(PaymentApplication::getIsDeleted, 0)
|
||||
.in(PaymentApplication::getPaymentNo, advanceNos))
|
||||
.stream().filter(item -> item.getCreateTime() != null)
|
||||
.collect(Collectors.toMap(PaymentApplication::getPaymentNo,
|
||||
PaymentApplication::getCreateTime, (first, second) -> first));
|
||||
for (PreSettlementAdvance advance : advances) {
|
||||
SettlementAdvancePaymentVO record = new SettlementAdvancePaymentVO();
|
||||
record.setRecordType(TransportBusiConstant.PAYMENT_TYPE_PROGRESS_ADVANCE);
|
||||
record.setPaymentNo(advance.getAdvanceNo());
|
||||
record.setAppliedAmount(advance.getAppliedAmount());
|
||||
record.setPaidAmount(advance.getPaidAmount());
|
||||
record.setBillStatus(advance.getBillStatus());
|
||||
record.setKingdeeBillNo(advance.getKingdeeAdvanceNo());
|
||||
record.setCreateUserName(UserCache.getUserRealName(advance.getCreateUser()));
|
||||
record.setCreateTime(applicationTimes.getOrDefault(advance.getAdvanceNo(), advance.getCreateTime()));
|
||||
records.add(record);
|
||||
}
|
||||
}
|
||||
if (Func.isNotEmpty(writeOffs)) {
|
||||
List<Long> applicationIds = writeOffs.stream()
|
||||
.map(SettlementProjectAdvanceWriteOff::getPaymentApplicationId)
|
||||
.filter(Objects::nonNull).distinct().toList();
|
||||
Map<Long, PaymentApplication> applications = applicationIds.isEmpty() ? Map.of()
|
||||
: this.paymentApplicationMapper.selectBatchIds(applicationIds).stream()
|
||||
.collect(Collectors.toMap(PaymentApplication::getId, Function.identity(),
|
||||
(first, second) -> first));
|
||||
for (SettlementProjectAdvanceWriteOff writeOff : writeOffs) {
|
||||
PaymentApplication application = applications.get(writeOff.getPaymentApplicationId());
|
||||
SettlementAdvancePaymentVO record = new SettlementAdvancePaymentVO();
|
||||
record.setRecordType(TransportBusiConstant.PAYMENT_TYPE_PROJECT_ADVANCE);
|
||||
record.setPaymentNo(writeOff.getPaymentNo());
|
||||
record.setAppliedAmount(writeOff.getWriteOffAmount());
|
||||
record.setPaidAmount(application == null ? null : application.getPaidAmount());
|
||||
record.setBillStatus(application == null ? null : application.getApprovalStatus());
|
||||
record.setKingdeeBillNo(writeOff.getKingdeeBillNo());
|
||||
record.setCreateUserName(UserCache.getUserRealName(application == null
|
||||
? writeOff.getCreateUser() : application.getCreateUser()));
|
||||
record.setCreateTime(writeOff.getPaymentCreateTime() != null
|
||||
? writeOff.getPaymentCreateTime() : writeOff.getCreateTime());
|
||||
records.add(record);
|
||||
}
|
||||
}
|
||||
records.sort(Comparator.comparing(SettlementAdvancePaymentVO::getCreateTime,
|
||||
Comparator.nullsLast(Comparator.reverseOrder())));
|
||||
return records;
|
||||
}
|
||||
|
||||
private String approvalStatusName(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
|
||||
+5
@@ -456,6 +456,11 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
updateById(entity);
|
||||
// 联动作废提前建档的预结算预付记录,避免残留记录阻断预结算作废
|
||||
voidPreSettlementAdvance(entity);
|
||||
// 联动删除项目预付核销行,避免结算单已付汇总虚高
|
||||
List<Long> writeOffSettlementIds = this.projectAdvanceWriteOffService.deleteByPaymentApplicationIds(List.of(entity.getId()));
|
||||
if (!writeOffSettlementIds.isEmpty()) {
|
||||
formalSettlementService.refreshPaymentSummaries(writeOffSettlementIds);
|
||||
}
|
||||
refreshFormalSettlement(entity);
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -461,6 +461,9 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
settlement.setCurrentProcessor(AuthUtil.getUserName());
|
||||
settlement.setVoidReason(reason);
|
||||
updateById(settlement);
|
||||
// 联动删除项目预付核销行,避免残留记录与可核销占用
|
||||
this.projectAdvanceWriteOffService.deleteBySettlementIds(
|
||||
TransportBusiConstant.SETTLEMENT_BILL_PRE, List.of(settlement.getId()));
|
||||
releaseSourceDetails(settlement, listDetails(settlement.getId()));
|
||||
saveChange(settlement.getId(), "结算单基本信息", null, "调整", "作废预结算单", reason);
|
||||
}
|
||||
|
||||
+10
@@ -41,6 +41,7 @@ import org.springblade.transport.pojo.entity.SettlementAdjustmentDetail;
|
||||
import org.springblade.transport.pojo.vo.SettlementAdjustmentVO;
|
||||
import org.springblade.transport.service.IFormalSettlementService;
|
||||
import org.springblade.transport.service.ISettlementAdjustmentService;
|
||||
import org.springblade.transport.service.ISettlementProjectAdvanceWriteOffService;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -73,6 +74,7 @@ public class SettlementAdjustmentServiceImpl extends BaseServiceImpl<SettlementA
|
||||
private final PaymentApplicationMapper paymentApplicationMapper;
|
||||
private final PaymentApplicationSettlementMapper paymentApplicationSettlementMapper;
|
||||
private final IFormalSettlementService formalSettlementService;
|
||||
private final ISettlementProjectAdvanceWriteOffService projectAdvanceWriteOffService;
|
||||
private final KingdeeApBillSyncSupport kingdeeApBillSyncSupport;
|
||||
private final KingdeeArBillSyncSupport kingdeeArBillSyncSupport;
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
@@ -382,10 +384,18 @@ public class SettlementAdjustmentServiceImpl extends BaseServiceImpl<SettlementA
|
||||
detail.setAdjustAmount(detail.getSettlementAmountTax().subtract(money(detail.getOriginalAmount()))); formalDetailMapper.updateById(detail);
|
||||
}
|
||||
BigDecimal amount = money(formal.getSettlementAmount()).add(money(adjustment.getAdjustmentAmount()));
|
||||
// 调整后结算金额不得低于项目预付核销合计,否则已付将超过结算金额且无法拦截
|
||||
BigDecimal writeOffTotal = this.projectAdvanceWriteOffService.sumBySettlement(
|
||||
TransportBusiConstant.SETTLEMENT_BILL_FORMAL, formal.getId());
|
||||
if (money(writeOffTotal).compareTo(money(amount)) > 0) {
|
||||
throw new ServiceException("调整后结算金额不能低于项目预付核销合计" + money(writeOffTotal) + ",请先调减项目预付核销");
|
||||
}
|
||||
formal.setSettlementAmount(amount);
|
||||
formal.setLocalSettlementAmount(amount.multiply(formal.getExchangeRate() == null ? BigDecimal.ONE : formal.getExchangeRate()));
|
||||
formal.setRemainingPayableAmount(amount.subtract(money(formal.getPaidAmount())).max(BigDecimal.ZERO));
|
||||
formalMapper.updateById(formal);
|
||||
// 重算收付汇总(含核销计入),保持付款状态与剩余可付准确
|
||||
formalSettlementService.refreshPaymentSummary(formal.getId());
|
||||
}
|
||||
|
||||
/** 涉事事务的自代理:跨方法调用确保 {@link #cloneAndApplyForRepush} 的事务注解生效 */
|
||||
|
||||
+56
@@ -14,6 +14,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import org.springblade.core.log.exception.ServiceException;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.system.cache.UserCache;
|
||||
import org.springblade.transport.constant.KingdeeBfiConstant;
|
||||
import org.springblade.transport.constant.TransportBusiConstant;
|
||||
import org.springblade.transport.mapper.PaymentApplicationMapper;
|
||||
import org.springblade.transport.mapper.SettlementProjectAdvanceWriteOffMapper;
|
||||
@@ -28,8 +29,11 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -56,6 +60,7 @@ public class SettlementProjectAdvanceWriteOffServiceImpl implements ISettlementP
|
||||
.eq(PaymentApplication::getContractId, contractId)
|
||||
.eq(PaymentApplication::getPaymentType, TransportBusiConstant.PAYMENT_TYPE_PROJECT_ADVANCE)
|
||||
.eq(PaymentApplication::getApprovalStatus, TransportBusiConstant.APPROVED)
|
||||
.eq(PaymentApplication::getKingdeeStatus, KingdeeBfiConstant.KINGDEE_STATUS_PAID)
|
||||
.eq(PaymentApplication::getIsDeleted, 0)
|
||||
.orderByDesc(PaymentApplication::getCreateTime));
|
||||
List<ProjectAdvanceWriteOffOptionVO> options = new ArrayList<>();
|
||||
@@ -121,6 +126,20 @@ public class SettlementProjectAdvanceWriteOffServiceImpl implements ISettlementP
|
||||
return money(writeOffMapper.sumWriteOffBySettlement(settlementType, settlementId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Long, BigDecimal> sumBySettlementIds(String settlementType, Collection<Long> settlementIds) {
|
||||
if (Func.isEmpty(settlementType) || Func.isEmpty(settlementIds)) {
|
||||
return Map.of();
|
||||
}
|
||||
Map<Long, BigDecimal> result = new HashMap<>();
|
||||
for (Map<String, Object> row : writeOffMapper.sumWriteOffBySettlementIds(settlementType, settlementIds)) {
|
||||
if (row.get("settlementId") instanceof Number settlementId && row.get("totalAmount") instanceof BigDecimal totalAmount) {
|
||||
result.put(settlementId.longValue(), money(totalAmount));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void replaceWriteOffs(String settlementType, Long settlementId, Long contractId,
|
||||
@@ -157,6 +176,9 @@ public class SettlementProjectAdvanceWriteOffServiceImpl implements ISettlementP
|
||||
if (!TransportBusiConstant.APPROVED.equals(application.getApprovalStatus())) {
|
||||
throw new ServiceException("仅可选择审核通过的项目预付单");
|
||||
}
|
||||
if (!KingdeeBfiConstant.KINGDEE_STATUS_PAID.equals(application.getKingdeeStatus())) {
|
||||
throw new ServiceException("预付单" + Func.toStr(application.getPaymentNo(), "") + "尚未付款,项目预付须金蝶付款完成后方可核销");
|
||||
}
|
||||
if (!Objects.equals(contractId, application.getContractId())) {
|
||||
throw new ServiceException("项目预付单与结算单合同不一致");
|
||||
}
|
||||
@@ -203,6 +225,40 @@ public class SettlementProjectAdvanceWriteOffServiceImpl implements ISettlementP
|
||||
.eq(SettlementProjectAdvanceWriteOff::getSettlementId, settlementId));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int deleteBySettlementIds(String settlementType, Collection<Long> settlementIds) {
|
||||
if (Func.isEmpty(settlementType) || Func.isEmpty(settlementIds)) {
|
||||
return 0;
|
||||
}
|
||||
return writeOffMapper.delete(Wrappers.<SettlementProjectAdvanceWriteOff>lambdaQuery()
|
||||
.eq(SettlementProjectAdvanceWriteOff::getSettlementType, settlementType)
|
||||
.in(SettlementProjectAdvanceWriteOff::getSettlementId, settlementIds));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public List<Long> deleteByPaymentApplicationIds(Collection<Long> paymentApplicationIds) {
|
||||
if (Func.isEmpty(paymentApplicationIds)) {
|
||||
return List.of();
|
||||
}
|
||||
List<SettlementProjectAdvanceWriteOff> rows = writeOffMapper.selectList(
|
||||
Wrappers.<SettlementProjectAdvanceWriteOff>lambdaQuery()
|
||||
.eq(SettlementProjectAdvanceWriteOff::getIsDeleted, 0)
|
||||
.in(SettlementProjectAdvanceWriteOff::getPaymentApplicationId, paymentApplicationIds));
|
||||
if (rows.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
writeOffMapper.delete(Wrappers.<SettlementProjectAdvanceWriteOff>lambdaQuery()
|
||||
.in(SettlementProjectAdvanceWriteOff::getPaymentApplicationId, paymentApplicationIds));
|
||||
return rows.stream()
|
||||
.filter(row -> TransportBusiConstant.SETTLEMENT_BILL_FORMAL.equals(row.getSettlementType()))
|
||||
.map(SettlementProjectAdvanceWriteOff::getSettlementId)
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.toList();
|
||||
}
|
||||
|
||||
private BigDecimal money(BigDecimal value) {
|
||||
return value == null ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP)
|
||||
: value.setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
Reference in New Issue
Block a user