This commit is contained in:
2026-08-28 15:30:12 +08:00
parent 15dc0791f1
commit 81f49babd0
12 changed files with 353 additions and 30 deletions
@@ -0,0 +1,10 @@
package org.springblade.transport.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.springblade.transport.pojo.entity.PaymentApplicationSettlement;
/** 付款申请关联正式结算单 Mapper。 */
@Mapper
public interface PaymentApplicationSettlementMapper extends BaseMapper<PaymentApplicationSettlement> {
}
@@ -41,6 +41,8 @@ public interface IFormalSettlementService extends BaseService<FormalSettlement>
String syncKingdee(Long id);
List<FormalSettlementDetailFee> detailFees(Long detailId);
void adjustDetail(PreSettlementDetailAdjustRequest request);
void refreshPaymentSummary(Long settlementId);
void refreshPaymentSummariesForPreSettlement(Long preSettlementId);
String applyPayment(FormalSettlementPaymentRequest request);
List<String> applyPayments(FormalSettlementBatchPaymentRequest request);
}
@@ -30,6 +30,8 @@ import org.springblade.transport.mapper.PreSettlementDetailFeeMapper;
import org.springblade.transport.mapper.PreSettlementMapper;
import org.springblade.transport.mapper.PreSettlementSummaryFeeMapper;
import org.springblade.transport.mapper.PaymentApplicationMapper;
import org.springblade.transport.mapper.PaymentApplicationSettlementMapper;
import org.springblade.transport.mapper.ReceiptClaimSettlementMapper;
import org.springblade.transport.mapper.ReceivablePayableDetailMapper;
import org.springblade.transport.mapper.ReceivablePayableCargoFeeMapper;
import org.springblade.transport.mapper.SettlementAdjustmentMapper;
@@ -52,6 +54,8 @@ import org.springblade.transport.pojo.entity.PreSettlementDetail;
import org.springblade.transport.pojo.entity.PreSettlementDetailFee;
import org.springblade.transport.pojo.entity.PreSettlementSummaryFee;
import org.springblade.transport.pojo.entity.PaymentApplication;
import org.springblade.transport.pojo.entity.PaymentApplicationSettlement;
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;
@@ -114,6 +118,8 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
private final ReceivablePayableDetailMapper receivablePayableMapper;
private final ReceivablePayableCargoFeeMapper receivablePayableCargoFeeMapper;
private final PaymentApplicationMapper paymentApplicationMapper;
private final PaymentApplicationSettlementMapper paymentApplicationSettlementMapper;
private final ReceiptClaimSettlementMapper receiptClaimSettlementMapper;
private final SettlementAdjustmentMapper settlementAdjustmentMapper;
private final IContractManageService contractManageService;
private final IPreSettlementService preSettlementService;
@@ -394,8 +400,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
payment.setBillStatus(REVIEWING);
payment.setRemark(limit(remark, 200));
paymentMapper.insert(payment);
settlement.setAppliedPaymentAmount(money(settlement.getAppliedPaymentAmount()).add(amount));
updateById(settlement);
refreshPaymentSummary(settlement.getId());
return payment.getPaymentNo();
}
@@ -652,8 +657,34 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
settlement.setSettlementAmount(amount);
settlement.setLocalSettlementAmount(amount.multiply(
settlement.getExchangeRate() == null ? BigDecimal.ONE : settlement.getExchangeRate()));
settlement.setRemainingPayableAmount(amount.subtract(money(settlement.getPaidAmount())).max(BigDecimal.ZERO));
settlement.setRemainingPayableAmount(amount.subtract(money(settlement.getAppliedPaymentAmount()))
.subtract(money(settlement.getPaidAmount())).max(BigDecimal.ZERO));
updateById(settlement);
refreshPaymentSummary(settlement.getId());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void refreshPaymentSummary(Long settlementId) {
FormalSettlement settlement = existing(settlementId);
PaymentSummary summary = calculatePaymentSummary(settlement);
settlement.setAppliedPaymentAmount(summary.appliedAmount());
settlement.setPaidAmount(summary.paidAmount());
settlement.setRemainingPayableAmount(money(settlement.getSettlementAmount())
.subtract(summary.appliedAmount()).subtract(summary.paidAmount()).max(BigDecimal.ZERO));
settlement.setPaymentStatus(paymentStatus(summary.paidAmount(), settlement.getSettlementAmount()));
updateById(settlement);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void refreshPaymentSummariesForPreSettlement(Long preSettlementId) {
if (preSettlementId == null) return;
sourceMapper.selectList(Wrappers.<FormalSettlementSource>lambdaQuery()
.eq(FormalSettlementSource::getPreSettlementId, preSettlementId)
.eq(FormalSettlementSource::getIsDeleted, 0)).stream()
.map(FormalSettlementSource::getFormalSettlementId).distinct()
.forEach(this::refreshPaymentSummary);
}
private List<FormalSettlementSummaryFee> listSummaryFees(Long settlementId) {
@@ -838,10 +869,91 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
private FormalSettlementVO toVO(FormalSettlement entity) {
FormalSettlementVO vo = FormalSettlementWrapper.build().entityVO(entity);
PaymentSummary summary = calculatePaymentSummary(entity);
vo.setAppliedPaymentAmount(summary.appliedAmount());
vo.setPaidAmount(summary.paidAmount());
vo.setRemainingPayableAmount(money(entity.getSettlementAmount()).subtract(summary.appliedAmount())
.subtract(summary.paidAmount()).max(BigDecimal.ZERO));
vo.setPreSettlementNos(sourceMapper.selectList(Wrappers.<FormalSettlementSource>lambdaQuery().eq(FormalSettlementSource::getFormalSettlementId, entity.getId())).stream().map(FormalSettlementSource::getPreSettlementNo).collect(Collectors.joining(",")));
return vo;
}
private PaymentSummary calculatePaymentSummary(FormalSettlement settlement) {
List<Long> preSettlementIds = sourceMapper.selectList(Wrappers.<FormalSettlementSource>lambdaQuery()
.eq(FormalSettlementSource::getFormalSettlementId, settlement.getId())
.eq(FormalSettlementSource::getIsDeleted, 0)).stream()
.map(FormalSettlementSource::getPreSettlementId).filter(Objects::nonNull).toList();
LambdaQueryWrapper<PaymentApplication> query = Wrappers.<PaymentApplication>lambdaQuery()
.eq(PaymentApplication::getIsDeleted, 0);
if (preSettlementIds.isEmpty()) {
query.eq(PaymentApplication::getSettlementId, settlement.getId());
} else {
query.and(wrapper -> wrapper.eq(PaymentApplication::getSettlementId, settlement.getId())
.or().in(PaymentApplication::getPreSettlementId, preSettlementIds));
}
List<PaymentApplicationSettlement> relations = paymentApplicationSettlementMapper.selectList(
Wrappers.<PaymentApplicationSettlement>lambdaQuery()
.eq(PaymentApplicationSettlement::getFormalSettlementId, settlement.getId())
.eq(PaymentApplicationSettlement::getIsDeleted, 0));
Set<Long> relationApplicationIds = relations.stream().map(PaymentApplicationSettlement::getPaymentApplicationId)
.filter(Objects::nonNull).collect(Collectors.toSet());
if (!relationApplicationIds.isEmpty()) query.notIn(PaymentApplication::getId, relationApplicationIds);
List<PaymentApplication> applications = paymentApplicationMapper.selectList(query);
BigDecimal appliedAmount = applications.stream()
.filter(item -> REVIEWING.equals(item.getApprovalStatus()))
.map(PaymentApplication::getAppliedAmount).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal paidAmount = applications.stream()
.filter(item -> APPROVED.equals(item.getApprovalStatus()))
.map(PaymentApplication::getPaidAmount).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add);
Map<Long, String> relationStatuses = relationApplicationIds.isEmpty() ? Map.of() : paymentApplicationMapper.selectList(
Wrappers.<PaymentApplication>lambdaQuery().in(PaymentApplication::getId, relationApplicationIds))
.stream().collect(Collectors.toMap(PaymentApplication::getId, PaymentApplication::getApprovalStatus));
appliedAmount = appliedAmount.add(relations.stream()
.filter(item -> REVIEWING.equals(relationStatuses.get(item.getPaymentApplicationId())))
.map(PaymentApplicationSettlement::getAppliedAmount).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add));
paidAmount = paidAmount.add(relations.stream()
.filter(item -> APPROVED.equals(relationStatuses.get(item.getPaymentApplicationId())))
.map(PaymentApplicationSettlement::getPaidAmount).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add));
List<FormalSettlementPayment> legacyPayments = paymentMapper.selectList(
Wrappers.<FormalSettlementPayment>lambdaQuery()
.eq(FormalSettlementPayment::getFormalSettlementId, settlement.getId())
.eq(FormalSettlementPayment::getIsDeleted, 0));
appliedAmount = appliedAmount.add(legacyPayments.stream()
.filter(item -> REVIEWING.equals(item.getBillStatus()))
.map(FormalSettlementPayment::getAppliedAmount).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add));
paidAmount = paidAmount.add(legacyPayments.stream()
.filter(item -> APPROVED.equals(item.getBillStatus()))
.map(FormalSettlementPayment::getPaidAmount).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add));
paidAmount = paidAmount.add(sourceMapper.selectList(Wrappers.<FormalSettlementSource>lambdaQuery()
.eq(FormalSettlementSource::getFormalSettlementId, settlement.getId())
.eq(FormalSettlementSource::getIsDeleted, 0)).stream()
.map(FormalSettlementSource::getAdvancePaidAmount).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add));
if ("receivable".equals(settlement.getSettlementType())) {
paidAmount = paidAmount.add(receiptClaimSettlementMapper.selectList(
Wrappers.<ReceiptClaimSettlement>lambdaQuery()
.eq(ReceiptClaimSettlement::getFormalSettlementId, settlement.getId())
.eq(ReceiptClaimSettlement::getStatus, 1))
.stream().map(ReceiptClaimSettlement::getAllocatedReceiptAmount).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add));
}
return new PaymentSummary(money(appliedAmount), money(paidAmount));
}
private String paymentStatus(BigDecimal paidAmount, BigDecimal settlementAmount) {
if (paidAmount.compareTo(BigDecimal.ZERO) <= 0) return "unpaid";
return paidAmount.compareTo(money(settlementAmount)) >= 0 ? "paid" : "partial";
}
private record PaymentSummary(BigDecimal appliedAmount, BigDecimal paidAmount) {
}
private SettlementAdjustmentVO toAdjustmentVO(SettlementAdjustment entity, String kingdeeBillNo) {
SettlementAdjustmentVO vo = Objects.requireNonNull(
BeanUtil.copyProperties(entity, SettlementAdjustmentVO.class));
@@ -39,6 +39,7 @@ import org.springblade.transport.mapper.ContractManageMapper;
import org.springblade.transport.mapper.PaymentApplicationInvoiceMapper;
import org.springblade.transport.mapper.PaymentApplicationMapper;
import org.springblade.transport.mapper.PaymentApplicationRecordMapper;
import org.springblade.transport.mapper.PaymentApplicationSettlementMapper;
import org.springblade.transport.mapper.TemporaryCreditLimitMapper;
import org.springblade.transport.pojo.dto.PaymentApplicationInvoiceRequest;
import org.springblade.transport.pojo.dto.PaymentApplicationRecordRequest;
@@ -54,10 +55,12 @@ import org.springblade.transport.pojo.entity.ContractManage;
import org.springblade.transport.pojo.entity.PaymentApplication;
import org.springblade.transport.pojo.entity.PaymentApplicationInvoice;
import org.springblade.transport.pojo.entity.PaymentApplicationRecord;
import org.springblade.transport.pojo.entity.PaymentApplicationSettlement;
import org.springblade.transport.pojo.entity.TemporaryCreditLimit;
import org.springblade.transport.pojo.vo.PaymentApplicationVO;
import org.springblade.transport.pojo.vo.PaymentApplicationReferenceAmountVO;
import org.springblade.transport.service.IPaymentApplicationService;
import org.springblade.transport.service.IFormalSettlementService;
import org.springblade.transport.wrapper.PaymentApplicationWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -84,6 +87,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
private static final String VOIDED = "voided";
private final PaymentApplicationInvoiceMapper invoiceMapper;
private final PaymentApplicationRecordMapper recordMapper;
private final PaymentApplicationSettlementMapper settlementRelationMapper;
private final PreSettlementMapper preSettlementMapper;
private final FormalSettlementMapper formalSettlementMapper;
private final ProjectApplyMapper projectApplyMapper;
@@ -92,6 +96,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
private final BillLedgerMapper billLedgerMapper;
private final BillLedgerUsageMapper billLedgerUsageMapper;
private final TemporaryCreditLimitMapper temporaryCreditLimitMapper;
private final IFormalSettlementService formalSettlementService;
@Override
public IPage<PaymentApplicationVO> selectPage(IPage<PaymentApplication> page, PaymentApplicationVO query) {
@@ -122,6 +127,10 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
vo.setPaymentRecords(recordMapper.selectList(Wrappers.<org.springblade.transport.pojo.entity.PaymentApplicationRecord>lambdaQuery()
.eq(org.springblade.transport.pojo.entity.PaymentApplicationRecord::getPaymentApplicationId, id)
.orderByDesc(org.springblade.transport.pojo.entity.PaymentApplicationRecord::getCreateTime)));
vo.setSettlements(settlementRelationMapper.selectList(Wrappers.<PaymentApplicationSettlement>lambdaQuery()
.eq(PaymentApplicationSettlement::getPaymentApplicationId, id)
.eq(PaymentApplicationSettlement::getIsDeleted, 0)
.orderByAsc(PaymentApplicationSettlement::getCreateTime)));
return vo;
}
@@ -191,7 +200,13 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
matchedInvoiceAmount = matchedInvoiceAmount.add(money(item.getMatchedAmount()));
}
entity.setMatchedInvoiceAmount(matchedInvoiceAmount);
if ("settlement_payment".equals(request.getPaymentType()) && request.getSettlementId() == null
&& request.getSettlementIds() != null && !request.getSettlementIds().isEmpty()) {
request.setSettlementId(request.getSettlementIds().get(0));
}
fillReference(entity, request);
List<FormalSettlement> formalSettlements = resolveFormalSettlements(request, entity);
if (formalSettlements.size() > 1) fillFormalAggregateReference(entity, formalSettlements);
fillBillLedger(entity, request);
validateQuota(entity);
saveOrUpdate(entity);
@@ -225,11 +240,12 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
entity.setInvoiceStatus(matchedInvoiceAmount.compareTo(BigDecimal.ZERO) > 0 ? "matched" : "unmatched");
entity.setPaidAmount(paidAmount);
updateById(entity);
saveSettlementRelations(entity, formalSettlements);
return entity.getId();
}
@Override @Transactional(rollbackFor = Exception.class)
public void removeDraft(Long id) { PaymentApplication entity = editable(id); invoiceMapper.delete(Wrappers.<PaymentApplicationInvoice>lambdaQuery().eq(PaymentApplicationInvoice::getPaymentApplicationId, id)); recordMapper.delete(Wrappers.<PaymentApplicationRecord>lambdaQuery().eq(PaymentApplicationRecord::getPaymentApplicationId, id)); removeById(entity); }
public void removeDraft(Long id) { PaymentApplication entity = editable(id); invoiceMapper.delete(Wrappers.<PaymentApplicationInvoice>lambdaQuery().eq(PaymentApplicationInvoice::getPaymentApplicationId, id)); recordMapper.delete(Wrappers.<PaymentApplicationRecord>lambdaQuery().eq(PaymentApplicationRecord::getPaymentApplicationId, id)); settlementRelationMapper.delete(Wrappers.<PaymentApplicationSettlement>lambdaQuery().eq(PaymentApplicationSettlement::getPaymentApplicationId, id)); removeById(entity); }
@Override
@Transactional(rollbackFor = Exception.class)
public void submit(PaymentApplicationStatusRequest request) {
@@ -244,6 +260,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
entity.setCurrentNode("财务审核");
entity.setCurrentProcessor(AuthUtil.getUserName());
updateById(entity);
refreshFormalSettlement(entity);
}
@Override public void returnBill(PaymentApplicationStatusRequest request) { change(request.getId(), REVIEWING, RETURNED, "已驳回", request.getReason()); }
@Override
@@ -257,6 +274,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
entity.setCurrentProcessor(AuthUtil.getUserName());
entity.setRemark(request.getReason() == null ? entity.getRemark() : limit(request.getReason(), 200));
updateById(entity);
refreshFormalSettlement(entity);
}
@Override
@@ -267,6 +285,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
if (isBillPayment(entity.getPaymentMethod())) useBillBalance(entity);
entity.setApprovalStatus(APPROVED); entity.setCurrentNode("审批通过"); entity.setCurrentProcessor(AuthUtil.getUserName());
updateById(entity);
refreshFormalSettlement(entity);
}
@Override
@@ -286,10 +305,12 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
if (isBillPayment(request.getPaymentMethod()) && request.getBillLedgerId() == null) throw new ServiceException("汇票付款必须选择汇票台账");
if (request.getPaymentRatio() != null && (request.getPaymentRatio().compareTo(BigDecimal.ZERO) < 0 || request.getPaymentRatio().compareTo(BigDecimal.valueOf(100)) > 0)) throw new ServiceException("付款比例必须在0-100之间");
if (request.getAppliedAmount() == null || request.getAppliedAmount().compareTo(BigDecimal.ZERO) < 0) throw new ServiceException("申请付款金额不能小于0");
if (!"project_advance".equals(request.getPaymentType()) && request.getSettlementId() == null && request.getPreSettlementId() == null) throw new ServiceException("非项目预付必须关联结算单");
if (!"project_advance".equals(request.getPaymentType()) && request.getSettlementId() == null
&& request.getPreSettlementId() == null && Func.isEmpty(request.getSettlementIds())) throw new ServiceException("非项目预付必须关联结算单");
if ("project_advance".equals(request.getPaymentType()) && request.getProjectId() == null) throw new ServiceException("项目预付必须选择所属项目");
if ("progress_advance".equals(request.getPaymentType()) && request.getPreSettlementId() == null) throw new ServiceException("进度预付必须关联预结算单");
if ("settlement_payment".equals(request.getPaymentType()) && request.getSettlementId() == null) throw new ServiceException("结算付款必须关联正式结算单");
if ("settlement_payment".equals(request.getPaymentType()) && request.getSettlementId() == null
&& Func.isEmpty(request.getSettlementIds())) throw new ServiceException("结算付款必须关联正式结算单");
}
private void validateInvoice(PaymentApplicationInvoiceRequest invoice) {
@@ -383,7 +404,70 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
} 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());
}
if (money(entity.getAppliedAmount()).compareTo(money(entity.getPayableAmount())) > 0 && money(entity.getPayableAmount()).compareTo(BigDecimal.ZERO) > 0) throw new ServiceException("申请付款金额不能超过可付款金额");
if (!hasMultipleFormalReferences(request)
&& money(entity.getAppliedAmount()).compareTo(money(entity.getPayableAmount())) > 0
&& money(entity.getPayableAmount()).compareTo(BigDecimal.ZERO) > 0) throw new ServiceException("申请付款金额不能超过可付款金额");
}
private List<FormalSettlement> resolveFormalSettlements(PaymentApplicationSaveRequest request, PaymentApplication entity) {
if (!"settlement_payment".equals(request.getPaymentType())) return List.of();
List<Long> ids = request.getSettlementIds() == null ? List.of() : request.getSettlementIds();
if (ids.isEmpty() && request.getSettlementId() != null) ids = List.of(request.getSettlementId());
ids = ids.stream().filter(Objects::nonNull).distinct().toList();
List<FormalSettlement> settlements = ids.stream().map(id -> formalSettlementMapper.selectById(id)).toList();
if (settlements.stream().anyMatch(item -> item == null || Objects.equals(item.getIsDeleted(), 1)
|| !APPROVED.equals(item.getApprovalStatus()) || !"payable".equals(item.getSettlementType()))) {
throw new ServiceException("只能选择审批通过的应付正式结算单");
}
Long contractId = settlements.get(0).getContractId();
if (contractId == null || settlements.stream().anyMatch(item -> !Objects.equals(contractId, item.getContractId()))) {
throw new ServiceException("多选正式结算单必须属于同一合同");
}
return settlements;
}
private boolean hasMultipleFormalReferences(PaymentApplicationSaveRequest request) {
return "settlement_payment".equals(request.getPaymentType()) && request.getSettlementIds() != null
&& request.getSettlementIds().stream().filter(Objects::nonNull).distinct().count() > 1;
}
private void fillFormalAggregateReference(PaymentApplication entity, List<FormalSettlement> settlements) {
BigDecimal settlementAmount = settlements.stream().map(FormalSettlement::getSettlementAmount)
.map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal payableAmount = settlements.stream()
.map(item -> money(item.getSettlementAmount()).subtract(cumulativeAppliedAmount("settlement_payment", item.getId(), entity.getId())).max(BigDecimal.ZERO))
.reduce(BigDecimal.ZERO, BigDecimal::add);
if (money(entity.getAppliedAmount()).compareTo(payableAmount) > 0) throw new ServiceException("申请付款金额不能超过所选结算单可付款金额合计");
FormalSettlement first = settlements.get(0);
entity.setSettlementId(first.getId());
entity.setSettlementNo(settlements.stream().map(FormalSettlement::getFormalSettlementNo).filter(Objects::nonNull).collect(java.util.stream.Collectors.joining("")));
entity.setSettlementAmount(settlementAmount);
entity.setPayableAmount(payableAmount);
}
private void saveSettlementRelations(PaymentApplication entity, List<FormalSettlement> settlements) {
if (settlements.isEmpty()) return;
settlementRelationMapper.delete(Wrappers.<PaymentApplicationSettlement>lambdaQuery()
.eq(PaymentApplicationSettlement::getPaymentApplicationId, entity.getId()));
BigDecimal totalBase = settlements.stream().map(item -> money(item.getSettlementAmount()).subtract(
cumulativeAppliedAmount("settlement_payment", item.getId(), entity.getId())).max(BigDecimal.ZERO))
.reduce(BigDecimal.ZERO, BigDecimal::add);
BigDecimal remainingApplied = money(entity.getAppliedAmount());
BigDecimal remainingPaid = money(entity.getPaidAmount());
for (int index = 0; index < settlements.size(); index++) {
FormalSettlement settlement = settlements.get(index);
BigDecimal base = money(settlement.getSettlementAmount()).subtract(
cumulativeAppliedAmount("settlement_payment", settlement.getId(), entity.getId())).max(BigDecimal.ZERO);
BigDecimal applied = index == settlements.size() - 1 ? remainingApplied
: money(entity.getAppliedAmount()).multiply(base).divide(totalBase, 2, RoundingMode.DOWN);
BigDecimal paid = index == settlements.size() - 1 ? remainingPaid
: money(entity.getPaidAmount()).multiply(applied).divide(money(entity.getAppliedAmount()).max(BigDecimal.ONE), 2, RoundingMode.DOWN);
PaymentApplicationSettlement relation = new PaymentApplicationSettlement();
relation.setPaymentApplicationId(entity.getId()); relation.setFormalSettlementId(settlement.getId());
relation.setFormalSettlementNo(settlement.getFormalSettlementNo()); relation.setSettlementAmount(money(settlement.getSettlementAmount()));
relation.setAppliedAmount(applied); relation.setPaidAmount(paid); settlementRelationMapper.insert(relation);
remainingApplied = remainingApplied.subtract(applied); remainingPaid = remainingPaid.subtract(paid);
}
}
private void validateQuota(PaymentApplication entity) {
@@ -546,7 +630,13 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
return List.of("bank_draft", "commercial_draft").contains(paymentMethod);
}
private void change(Long id, String from, String to, String node, String reason) { PaymentApplication entity = existing(id); if (!from.equals(entity.getApprovalStatus())) throw new ServiceException("当前状态不允许此操作"); entity.setApprovalStatus(to); entity.setCurrentNode(node); entity.setCurrentProcessor(AuthUtil.getUserName()); entity.setRemark(reason == null ? entity.getRemark() : limit(reason, 200)); updateById(entity); }
private void change(Long id, String from, String to, String node, String reason) { PaymentApplication entity = existing(id); if (!from.equals(entity.getApprovalStatus())) throw new ServiceException("当前状态不允许此操作"); entity.setApprovalStatus(to); entity.setCurrentNode(node); entity.setCurrentProcessor(AuthUtil.getUserName()); entity.setRemark(reason == null ? entity.getRemark() : limit(reason, 200)); updateById(entity); refreshFormalSettlement(entity); }
private void refreshFormalSettlement(PaymentApplication entity) {
if (entity.getSettlementId() != null) formalSettlementService.refreshPaymentSummary(entity.getSettlementId());
else if (entity.getPreSettlementId() != null) {
formalSettlementService.refreshPaymentSummariesForPreSettlement(entity.getPreSettlementId());
}
}
private PaymentApplication existing(Long id) { PaymentApplication entity = getById(id); if (entity == null || Objects.equals(entity.getIsDeleted(), 1)) throw new ServiceException("付款申请不存在"); return entity; }
private PaymentApplication editable(Long id) { PaymentApplication entity = existing(id); if (!List.of(DRAFT, RETURNED).contains(entity.getApprovalStatus())) throw new ServiceException("当前状态不可编辑"); return entity; }
private String nextNo() { String prefix = "FKD" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); return prefix + String.format("%05d", count(Wrappers.<PaymentApplication>lambdaQuery().likeRight(PaymentApplication::getPaymentNo, prefix)) + 1); }
@@ -45,6 +45,7 @@ import org.springblade.transport.pojo.entity.ReceiptClaimSettlement;
import org.springblade.transport.pojo.entity.ReceiptFlowRecord;
import org.springblade.transport.pojo.vo.ReceiptClaimRecordVO;
import org.springblade.transport.service.IReceiptClaimRecordService;
import org.springblade.transport.service.IFormalSettlementService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -71,6 +72,7 @@ public class ReceiptClaimRecordServiceImpl extends BaseServiceImpl<ReceiptClaimM
private static final String APPROVED = "approved";
private final ReceiptClaimSettlementMapper claimSettlementMapper;
private final IFormalSettlementService formalSettlementService;
private final KingdeeReceiptFlowMapper receiptFlowMapper;
private final FormalSettlementMapper formalSettlementMapper;
private final ReceiptFlowRecordMapper recordMapper;
@@ -177,15 +179,9 @@ public class ReceiptClaimRecordServiceImpl extends BaseServiceImpl<ReceiptClaimM
if (settlement == null || Objects.equals(settlement.getIsDeleted(), 1)) {
throw new ServiceException("关联结算单不存在");
}
BigDecimal paidAfter = money(settlement.getPaidAmount())
.subtract(money(relation.getAllocatedReceiptAmount())).max(BigDecimal.ZERO);
settlement.setPaidAmount(paidAfter);
settlement.setRemainingPayableAmount(money(settlement.getSettlementAmount())
.subtract(paidAfter).max(BigDecimal.ZERO));
settlement.setPaymentStatus(amountStatus(paidAfter, settlement.getSettlementAmount()));
formalSettlementMapper.updateById(settlement);
relation.setStatus(0);
claimSettlementMapper.updateById(relation);
formalSettlementService.refreshPaymentSummary(settlement.getId());
}
BigDecimal flowClaimedAfter = money(flow.getClaimedAmount())
@@ -50,6 +50,7 @@ import org.springblade.transport.pojo.entity.ReceiptClaimSettlement;
import org.springblade.transport.pojo.entity.ReceiptFlowRecord;
import org.springblade.transport.pojo.vo.ReceiptFlowVO;
import org.springblade.transport.service.IReceiptFlowService;
import org.springblade.transport.service.IFormalSettlementService;
import org.springblade.transport.support.TransportBusinessSupport;
import org.springblade.transport.wrapper.ReceiptFlowWrapper;
import org.springframework.stereotype.Service;
@@ -89,6 +90,7 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
private final ReceiptClaimSettlementMapper claimSettlementMapper;
private final ReceiptFlowRecordMapper recordMapper;
private final FormalSettlementMapper formalSettlementMapper;
private final IFormalSettlementService formalSettlementService;
@Override
public IPage<ReceiptFlowVO> selectPage(IPage<KingdeeReceiptFlow> page, ReceiptFlowVO query) {
@@ -212,11 +214,7 @@ public class ReceiptFlowServiceImpl extends BaseServiceImpl<KingdeeReceiptFlowMa
relation.setAllocatedReceiptAmount(allocated);
claimSettlementMapper.insert(relation);
settlement.setPaidAmount(claimedAfter);
settlement.setRemainingPayableAmount(money(settlement.getSettlementAmount())
.subtract(claimedAfter).max(BigDecimal.ZERO));
settlement.setPaymentStatus(amountStatus(claimedAfter, settlement.getSettlementAmount()));
formalSettlementMapper.updateById(settlement);
formalSettlementService.refreshPaymentSummary(settlement.getId());
}
String fromStatus = normalizeClaimStatus(flow.getClaimStatus());
@@ -24,24 +24,30 @@ import org.springblade.transport.mapper.FormalSettlementDetailFeeMapper;
import org.springblade.transport.mapper.FormalSettlementDetailMapper;
import org.springblade.transport.mapper.FormalSettlementMapper;
import org.springblade.transport.mapper.FormalSettlementSourceMapper;
import org.springblade.transport.mapper.LoadingManageMapper;
import org.springblade.transport.mapper.MasterOrderMapper;
import org.springblade.transport.mapper.ReceivablePayableCargoFeeMapper;
import org.springblade.transport.mapper.ReceivablePayableDetailMapper;
import org.springblade.transport.mapper.TransportReconciliationChangeRecordMapper;
import org.springblade.transport.mapper.TransportReconciliationExternalMapper;
import org.springblade.transport.mapper.TransportReconciliationInternalMapper;
import org.springblade.transport.mapper.TransportReconciliationMapper;
import org.springblade.transport.mapper.WaybillMapper;
import org.springblade.transport.pojo.dto.TransportReconciliationManualMatchRequest;
import org.springblade.transport.pojo.dto.TransportReconciliationSaveRequest;
import org.springblade.transport.pojo.entity.FormalSettlement;
import org.springblade.transport.pojo.entity.FormalSettlementDetail;
import org.springblade.transport.pojo.entity.FormalSettlementDetailFee;
import org.springblade.transport.pojo.entity.FormalSettlementSource;
import org.springblade.transport.pojo.entity.LoadingManage;
import org.springblade.transport.pojo.entity.MasterOrder;
import org.springblade.transport.pojo.entity.ReceivablePayableCargoFee;
import org.springblade.transport.pojo.entity.ReceivablePayableDetail;
import org.springblade.transport.pojo.entity.TransportReconciliation;
import org.springblade.transport.pojo.entity.TransportReconciliationChangeRecord;
import org.springblade.transport.pojo.entity.TransportReconciliationExternal;
import org.springblade.transport.pojo.entity.TransportReconciliationInternal;
import org.springblade.transport.pojo.entity.Waybill;
import org.springblade.transport.pojo.vo.TransportReconciliationVO;
import org.springblade.transport.service.ITransportReconciliationService;
import org.springblade.transport.wrapper.TransportReconciliationWrapper;
@@ -80,12 +86,17 @@ public class TransportReconciliationServiceImpl
private static final String MATCHED = "matched";
private static final String UNMATCHED = "unmatched";
private static final String DUPLICATE = "suspected_duplicate";
private static final String MASTER_ORDER_SOURCE = "总单系统生成";
private static final String LOADING_ORDER_SOURCE = "配载单系统生成";
private final FormalSettlementMapper formalSettlementMapper;
private final FormalSettlementSourceMapper formalSourceMapper;
private final FormalSettlementDetailMapper formalDetailMapper;
private final FormalSettlementDetailFeeMapper formalDetailFeeMapper;
private final ReceivablePayableDetailMapper receivablePayableMapper;
private final ReceivablePayableCargoFeeMapper cargoFeeMapper;
private final WaybillMapper waybillMapper;
private final MasterOrderMapper masterOrderMapper;
private final LoadingManageMapper loadingManageMapper;
private final TransportReconciliationInternalMapper internalMapper;
private final TransportReconciliationExternalMapper externalMapper;
private final TransportReconciliationChangeRecordMapper changeRecordMapper;
@@ -299,7 +310,7 @@ public class TransportReconciliationServiceImpl
editable(internal.getReconciliationId());
internal.setTransportQuantity(nonNegative(row.getTransportQuantity(), "运输量"));
internal.setUnitPrice(nonNegative(row.getUnitPrice(), "运输单价"));
internal.setMileage(nonNegative(row.getMileage(), "里程"));
if (row.getMileage() != null) internal.setMileage(row.getMileage());
internal.setFreightAmount(nonNegative(row.getFreightAmount(), "运输费"));
internal.setFeeItemsJson(row.getFeeItemsJson());
internal.setSettlementAmount(nonNegative(row.getSettlementAmount(), "结算金额"));
@@ -338,13 +349,18 @@ public class TransportReconciliationServiceImpl
@Transactional(rollbackFor = Exception.class)
public void complete(Long id) {
TransportReconciliation bill = editable(id);
assertAllMatched(bill);
refreshStats(id);
bill = existing(id);
if (bill.getDifferenceCount() != 0 || money(bill.getDifferenceQuantity()).compareTo(BigDecimal.ZERO) != 0
|| money(bill.getDifferenceAmount()).compareTo(BigDecimal.ZERO) != 0) {
List<TransportReconciliationInternal> internals = internalRows(id);
List<TransportReconciliationExternal> externals = externalRows(id);
int internalUnmatched = (int) internals.stream().filter(item -> !MATCHED.equals(item.getMatchResult())).count();
int externalUnmatched = (int) externals.stream().filter(item -> !MATCHED.equals(item.getMatchStatus())).count();
int differenceCount = Math.abs(internals.size() - externals.size()) + Math.min(internalUnmatched, externalUnmatched);
BigDecimal differenceQuantity = sumInternalQuantity(internals).subtract(sumExternalQuantity(externals)).abs();
BigDecimal differenceAmount = sumInternalAmount(internals).subtract(sumExternalAmount(externals)).abs();
if (differenceCount != 0 || differenceQuantity.compareTo(BigDecimal.ZERO) != 0
|| differenceAmount.compareTo(BigDecimal.ZERO) != 0) {
throw new ServiceException("差异单数、差异货量和差异金额必须全部为0才可完成对账");
}
assertAllMatched(bill);
bill.setReconciliationStatus(COMPLETED);
bill.setCompletedTime(LocalDateTime.now());
updateById(bill);
@@ -370,6 +386,7 @@ public class TransportReconciliationServiceImpl
BeanUtil.copyProperties(detail, row);
row.setId(null); row.setReconciliationId(billId); row.setFormalSettlementDetailId(detail.getId());
row.setSourceDetailId(detail.getSourceDetailId()); row.setLineNo(lineNo); row.setMatchResult(UNMATCHED); row.setUpdateResult("not_updated");
fillInternalAddresses(row, detail);
if (fee != null) {
row.setFormalSettlementDetailFeeId(fee.getId()); row.setSourceCargoFeeId(fee.getSourceFeeId());
row.setCargoName(fee.getCargoName()); row.setCargoType(fee.getCargoType()); row.setTransportQuantity(fee.getTransportQuantity());
@@ -383,6 +400,47 @@ public class TransportReconciliationServiceImpl
internalMapper.insert(row);
}
private void fillInternalAddresses(TransportReconciliationInternal row, FormalSettlementDetail detail) {
ReceivablePayableDetail source = detail.getSourceDetailId() == null ? null
: receivablePayableMapper.selectById(detail.getSourceDetailId());
if (source != null && MASTER_ORDER_SOURCE.equals(source.getSourceType())) {
MasterOrder masterOrder = masterOrderMapper.selectOne(Wrappers.<MasterOrder>lambdaQuery()
.eq(MasterOrder::getMasterNo, source.getWaybillNo()));
if (masterOrder != null) {
copyAddresses(row, masterOrder.getDepartureAddress(), masterOrder.getDepartureName(),
masterOrder.getArrivalAddress(), masterOrder.getArrivalName());
return;
}
}
if (source != null && LOADING_ORDER_SOURCE.equals(source.getSourceType())) {
LoadingManage loading = loadingManageMapper.selectOne(Wrappers.<LoadingManage>lambdaQuery()
.eq(LoadingManage::getLoadingNo, source.getWaybillNo()));
if (loading != null) {
copyAddresses(row, loading.getDepartureAddress(), null, loading.getArrivalAddress(), null);
return;
}
}
Long waybillId = detail.getWaybillId() != null ? detail.getWaybillId()
: source == null ? null : source.getWaybillId();
Waybill waybill = waybillId == null ? null : waybillMapper.selectById(waybillId);
if (waybill == null) {
String waybillNo = firstNotEmpty(detail.getWaybillNo(), source == null ? null : source.getWaybillNo());
if (Func.isNotEmpty(waybillNo)) {
waybill = waybillMapper.selectOne(Wrappers.<Waybill>lambdaQuery().eq(Waybill::getWaybillNo, waybillNo));
}
}
if (waybill != null) {
copyAddresses(row, waybill.getDepartureAddress(), waybill.getDepartureName(),
waybill.getArrivalAddress(), waybill.getArrivalName());
}
}
private void copyAddresses(TransportReconciliationInternal row, String departureAddress, String departureName,
String arrivalAddress, String arrivalName) {
row.setDepartureAddress(firstNotEmpty(departureAddress, departureName, row.getDepartureAddress()));
row.setArrivalAddress(firstNotEmpty(arrivalAddress, arrivalName, row.getArrivalAddress()));
}
private void applyAmount(TransportReconciliation bill, TransportReconciliationInternal internal, TransportReconciliationExternal external) {
BigDecimal before = money(internal.getSettlementAmount());
BigDecimal after = money(external.getSettlementAmount());
@@ -395,7 +453,18 @@ public class TransportReconciliationServiceImpl
}
} else {
FormalSettlementDetail detail = formalDetailMapper.selectById(internal.getFormalSettlementDetailId());
detail.setSettlementAmountTax(after); detail.setAdjustAmount(after.subtract(money(detail.getOriginalAmount()))); formalDetailMapper.updateById(detail);
List<FormalSettlementDetailFee> fees = formalDetailFeeMapper.selectList(Wrappers.<FormalSettlementDetailFee>lambdaQuery()
.eq(FormalSettlementDetailFee::getFormalSettlementDetailId, internal.getFormalSettlementDetailId()));
if (fees.size() == 1) {
FormalSettlementDetailFee fee = fees.get(0);
fee.setSettlementAmountTax(after); fee.setAdjustAmount(after.subtract(money(fee.getOriginalAmount()))); formalDetailFeeMapper.updateById(fee);
if (fee.getSourceFeeId() != null) {
ReceivablePayableCargoFee sourceFee = cargoFeeMapper.selectById(fee.getSourceFeeId());
if (sourceFee != null) { sourceFee.setAfterAmount(after); sourceFee.setAdjustAmount(after.subtract(money(sourceFee.getOriginalAmount()))); cargoFeeMapper.updateById(sourceFee); }
}
} else {
detail.setSettlementAmountTax(after); detail.setAdjustAmount(after.subtract(money(detail.getOriginalAmount()))); formalDetailMapper.updateById(detail);
}
ReceivablePayableDetail source = receivablePayableMapper.selectById(internal.getSourceDetailId());
if (source != null) { source.setTotalAmount(after); receivablePayableMapper.updateById(source); }
}
@@ -537,10 +606,14 @@ public class TransportReconciliationServiceImpl
private LocalDateTime parseTimeNullable(String value, String field) {
if (Func.isEmpty(value)) return null;
for (String pattern : List.of("yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm")) {
for (String pattern : List.of("yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-M-d HH:mm:ss", "yyyy-M-d HH:mm",
"yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/M/d HH:mm:ss", "yyyy/M/d HH:mm")) {
try { return LocalDateTime.parse(value.trim(), DateTimeFormatter.ofPattern(pattern)); } catch (DateTimeParseException ignored) { }
}
throw new ServiceException(field + "格式应为yyyy-MM-dd HH:mm:ss");
for (String pattern : List.of("yyyy-MM-dd", "yyyy-M-d", "yyyy/MM/dd", "yyyy/M/d")) {
try { return LocalDate.parse(value.trim(), DateTimeFormatter.ofPattern(pattern)).atStartOfDay(); } catch (DateTimeParseException ignored) { }
}
throw new ServiceException(field + "格式应为yyyy-MM-dd HH:mm:ss或yyyy-MM-dd");
}
private String vehicleKey(TransportReconciliationInternal row) { return key(row.getVehicleNo(), row.getCargoName(), row.getActualDepartureTime(), row.getBatchNo(), row.getTransportQuantity()); }
@@ -549,6 +622,10 @@ public class TransportReconciliationServiceImpl
private String cargoKey(TransportReconciliationExternal row) { return key(row.getVehicleNo(), row.getDepartureAddress(), row.getArrivalAddress(), row.getCargoName(), row.getActualDepartureTime(), row.getTransportQuantity()); }
private String key(Object... values) { StringBuilder builder = new StringBuilder(); for (Object value : values) builder.append(normal(value)).append('|'); return builder.toString(); }
private String normal(Object value) { if (value == null) return ""; if (value instanceof BigDecimal decimal) return decimal.stripTrailingZeros().toPlainString(); return value.toString().trim().replaceAll("\\s+", "").toLowerCase(); }
private String firstNotEmpty(String... values) {
for (String value : values) if (Func.isNotEmpty(value)) return value;
return null;
}
private boolean equalMoney(BigDecimal left, BigDecimal right) { return money(left).compareTo(money(right)) == 0; }
private BigDecimal money(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; }
private BigDecimal nonNegative(BigDecimal value, String field) { if (value == null || value.compareTo(BigDecimal.ZERO) < 0) throw new ServiceException(field + "不能小于0"); return value; }
@@ -32,8 +32,9 @@ public class FormalSettlementWrapper extends BaseEntityWrapper<FormalSettlement,
public FormalSettlementVO entityVO(FormalSettlement entity) {
FormalSettlementVO vo = Objects.requireNonNull(BeanUtil.copyProperties(entity, FormalSettlementVO.class));
BigDecimal settlementAmount = entity.getSettlementAmount() == null ? BigDecimal.ZERO : entity.getSettlementAmount();
BigDecimal appliedPaymentAmount = entity.getAppliedPaymentAmount() == null ? BigDecimal.ZERO : entity.getAppliedPaymentAmount();
BigDecimal paidAmount = entity.getPaidAmount() == null ? BigDecimal.ZERO : entity.getPaidAmount();
vo.setRemainingPayableAmount(settlementAmount.subtract(paidAmount).max(BigDecimal.ZERO));
vo.setRemainingPayableAmount(settlementAmount.subtract(appliedPaymentAmount).subtract(paidAmount).max(BigDecimal.ZERO));
vo.setInvoiceAmount(entity.getInvoiceAmount() == null ? BigDecimal.ZERO : entity.getInvoiceAmount());
vo.setCreateUserName(UserCache.getUserRealName(entity.getCreateUser()));
vo.setSettlementTypeName("receivable".equals(entity.getSettlementType()) ? "应收" : "应付");