调整结算

This commit is contained in:
2026-09-05 23:26:00 +08:00
parent 14a3c23c9d
commit 397e9f3b62
11 changed files with 213 additions and 19 deletions
@@ -22,6 +22,7 @@ import org.springblade.transport.pojo.dto.FormalSettlementSaveRequest;
import org.springblade.transport.pojo.dto.FormalSettlementPaymentRequest;
import org.springblade.transport.pojo.dto.FormalSettlementBatchPaymentRequest;
import org.springblade.transport.pojo.dto.FormalSettlementStatusRequest;
import org.springblade.transport.pojo.dto.FormalSettlementInvoiceClaimRequest;
import org.springblade.transport.pojo.dto.PreSettlementDetailAdjustRequest;
import org.springblade.transport.pojo.entity.FormalSettlementDetailFee;
import org.springblade.transport.pojo.vo.FormalSettlementVO;
@@ -169,6 +170,14 @@ public class FormalSettlementController extends BladeController {
return R.data(formalSettlementService.applyPayments(request));
}
@PostMapping("/claim-invoices")
@ApiOperationSupport(order = 18)
@Operation(summary = "认领发票并同步付款申请")
public R claimInvoices(@RequestBody FormalSettlementInvoiceClaimRequest request) {
formalSettlementService.claimInvoices(request);
return R.success("发票认领成功");
}
@GetMapping("/receipt-claims")
@ApiOperationSupport(order = 19)
@Operation(summary = "应收正式结算单收款认领信息")
@@ -21,6 +21,7 @@ import org.springblade.transport.pojo.entity.FormalSettlementDetailFee;
import java.util.List;
import org.springblade.transport.pojo.dto.FormalSettlementPaymentRequest;
import org.springblade.transport.pojo.dto.FormalSettlementBatchPaymentRequest;
import org.springblade.transport.pojo.dto.FormalSettlementInvoiceClaimRequest;
/**
* 正式结算单服务
@@ -45,4 +46,5 @@ public interface IFormalSettlementService extends BaseService<FormalSettlement>
void refreshPaymentSummariesForPreSettlement(Long preSettlementId);
String applyPayment(FormalSettlementPaymentRequest request);
List<String> applyPayments(FormalSettlementBatchPaymentRequest request);
void claimInvoices(FormalSettlementInvoiceClaimRequest request);
}
@@ -32,6 +32,7 @@ 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.PaymentApplicationInvoiceMapper;
import org.springblade.transport.mapper.PaymentApplicationSettlementMapper;
import org.springblade.transport.mapper.ReceiptClaimSettlementMapper;
import org.springblade.transport.mapper.ReceivablePayableDetailMapper;
@@ -40,6 +41,7 @@ import org.springblade.transport.mapper.SettlementAdjustmentMapper;
import org.springblade.transport.pojo.dto.FormalSettlementSaveRequest;
import org.springblade.transport.pojo.dto.FormalSettlementPaymentRequest;
import org.springblade.transport.pojo.dto.FormalSettlementBatchPaymentRequest;
import org.springblade.transport.pojo.dto.FormalSettlementInvoiceClaimRequest;
import org.springblade.transport.pojo.dto.FormalSettlementStatusRequest;
import org.springblade.transport.pojo.dto.PreSettlementDetailAdjustRequest;
import org.springblade.transport.pojo.entity.FormalSettlement;
@@ -58,6 +60,7 @@ 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.PaymentApplicationInvoice;
import org.springblade.transport.pojo.entity.PaymentApplicationSettlement;
import org.springblade.transport.pojo.entity.ReceiptClaimSettlement;
import org.springblade.transport.pojo.entity.ReceivablePayableDetail;
@@ -125,6 +128,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
private final ReceivablePayableDetailMapper receivablePayableMapper;
private final ReceivablePayableCargoFeeMapper receivablePayableCargoFeeMapper;
private final PaymentApplicationMapper paymentApplicationMapper;
private final PaymentApplicationInvoiceMapper paymentApplicationInvoiceMapper;
private final PaymentApplicationSettlementMapper paymentApplicationSettlementMapper;
private final ReceiptClaimSettlementMapper receiptClaimSettlementMapper;
private final SettlementAdjustmentMapper settlementAdjustmentMapper;
@@ -458,6 +462,81 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
return paymentNos;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void claimInvoices(FormalSettlementInvoiceClaimRequest request) {
if (request == null || request.getFormalSettlementId() == null || Func.isEmpty(request.getInvoices())) {
throw new ServiceException("请选择需要认领的发票");
}
FormalSettlement settlement = existing(request.getFormalSettlementId());
List<FormalSettlementSaveRequest.Invoice> invoices = request.getInvoices();
Set<String> invoiceNumbers = new LinkedHashSet<>();
BigDecimal matchedTotal = BigDecimal.ZERO;
for (FormalSettlementSaveRequest.Invoice item : invoices) {
String invoiceNo = item == null ? "" : requiredText(item.getInvoiceNo(), "发票号");
if (!invoiceNumbers.add(invoiceNo)) throw new ServiceException("发票号" + invoiceNo + "重复");
matchedTotal = matchedTotal.add(money(item.getMatchedAmount()));
}
if (matchedTotal.compareTo(money(settlement.getSettlementAmount())) > 0) {
throw new ServiceException("发票匹配结算单金额合计不能超过结算金额");
}
List<FormalSettlementInvoice> existingInvoices = invoiceMapper.selectList(
Wrappers.<FormalSettlementInvoice>lambdaQuery().eq(FormalSettlementInvoice::getFormalSettlementId, settlement.getId()));
Set<String> existingNumbers = existingInvoices.stream().map(FormalSettlementInvoice::getInvoiceNo)
.filter(Objects::nonNull).collect(Collectors.toSet());
int lineNo = existingInvoices.stream().map(FormalSettlementInvoice::getLineNo).filter(Objects::nonNull)
.max(Integer::compareTo).orElse(0) + 1;
BigDecimal insertedMatchedTotal = BigDecimal.ZERO;
for (FormalSettlementSaveRequest.Invoice item : invoices) {
if (existingNumbers.contains(item.getInvoiceNo())) continue;
FormalSettlementInvoice invoice = new FormalSettlementInvoice();
invoice.setFormalSettlementId(settlement.getId()); invoice.setLineNo(lineNo++);
invoice.setInvoiceNo(item.getInvoiceNo()); invoice.setInvoiceDate(item.getInvoiceDate());
invoice.setInvoiceType(item.getInvoiceType()); invoice.setTaxRate(item.getTaxRate());
invoice.setInvoiceAmount(money(item.getInvoiceAmount()));
invoice.setAvailableInvoiceAmount(money(item.getAvailableInvoiceAmount()));
invoice.setMatchedAmount(money(item.getMatchedAmount())); invoice.setAttachmentJson(item.getAttachmentJson());
invoiceMapper.insert(invoice);
insertedMatchedTotal = insertedMatchedTotal.add(money(item.getMatchedAmount()));
}
List<PaymentApplicationSettlement> relations = paymentApplicationSettlementMapper.selectList(
Wrappers.<PaymentApplicationSettlement>lambdaQuery().eq(PaymentApplicationSettlement::getFormalSettlementId, settlement.getId())
.eq(PaymentApplicationSettlement::getIsDeleted, 0));
Set<Long> paymentApplicationIds = new LinkedHashSet<>(relations.stream()
.map(PaymentApplicationSettlement::getPaymentApplicationId).filter(Objects::nonNull).toList());
paymentApplicationIds.addAll(paymentApplicationMapper.selectList(Wrappers.<PaymentApplication>lambdaQuery()
.eq(PaymentApplication::getSettlementId, settlement.getId()).eq(PaymentApplication::getIsDeleted, 0))
.stream().map(PaymentApplication::getId).toList());
for (Long paymentApplicationId : paymentApplicationIds) {
List<PaymentApplicationInvoice> old = paymentApplicationInvoiceMapper.selectList(
Wrappers.<PaymentApplicationInvoice>lambdaQuery().eq(PaymentApplicationInvoice::getPaymentApplicationId, paymentApplicationId));
Set<String> nums = old.stream().map(PaymentApplicationInvoice::getInvoiceNo).filter(Objects::nonNull).collect(Collectors.toSet());
BigDecimal paymentMatchedTotal = old.stream().map(PaymentApplicationInvoice::getMatchedAmount).map(this::money)
.reduce(BigDecimal.ZERO, BigDecimal::add);
int paymentLine = old.stream().map(PaymentApplicationInvoice::getLineNo).filter(Objects::nonNull).max(Integer::compareTo).orElse(0) + 1;
for (FormalSettlementSaveRequest.Invoice item : invoices) {
if (nums.contains(item.getInvoiceNo())) continue;
PaymentApplicationInvoice invoice = new PaymentApplicationInvoice();
invoice.setPaymentApplicationId(paymentApplicationId); invoice.setLineNo(paymentLine++);
invoice.setSettlementNo(settlement.getFormalSettlementNo()); invoice.setInvoiceNo(item.getInvoiceNo());
invoice.setInvoiceDate(item.getInvoiceDate()); invoice.setInvoiceType(item.getInvoiceType());
invoice.setTaxRate(item.getTaxRate()); invoice.setInvoiceAmount(money(item.getInvoiceAmount()));
invoice.setMatchedAmount(money(item.getMatchedAmount())); invoice.setAttachmentJson(item.getAttachmentJson());
paymentApplicationInvoiceMapper.insert(invoice);
paymentMatchedTotal = paymentMatchedTotal.add(money(item.getMatchedAmount()));
}
PaymentApplication payment = paymentApplicationMapper.selectById(paymentApplicationId);
if (payment != null) {
payment.setMatchedInvoiceAmount(paymentMatchedTotal);
payment.setInvoiceStatus(paymentMatchedTotal.compareTo(BigDecimal.ZERO) > 0 ? "matched" : "unmatched");
paymentApplicationMapper.updateById(payment);
}
}
settlement.setInvoiceAmount(money(settlement.getInvoiceAmount()).add(insertedMatchedTotal));
settlement.setInvoiceStatus(invoiceStatus(settlement.getInvoiceAmount(), settlement.getSettlementAmount()));
updateById(settlement);
}
private String createPayment(FormalSettlement settlement, BigDecimal appliedAmount, String remark) {
if (!APPROVED.equals(settlement.getApprovalStatus())) throw new ServiceException("仅审批通过的正式结算单允许发起付款申请");
if (!"payable".equals(settlement.getSettlementType())) throw new ServiceException("仅应付正式结算单允许发起付款申请");
@@ -808,8 +887,8 @@ 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.getAppliedPaymentAmount()))
.subtract(money(settlement.getPaidAmount())).max(BigDecimal.ZERO));
settlement.setRemainingPayableAmount(amount.subtract(money(settlement.getPaidAmount()))
.max(BigDecimal.ZERO));
updateById(settlement);
refreshPaymentSummary(settlement.getId());
}
@@ -822,7 +901,7 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
settlement.setAppliedPaymentAmount(summary.appliedAmount());
settlement.setPaidAmount(summary.paidAmount());
settlement.setRemainingPayableAmount(money(settlement.getSettlementAmount())
.subtract(summary.appliedAmount()).subtract(summary.paidAmount()).max(BigDecimal.ZERO));
.subtract(summary.paidAmount()).max(BigDecimal.ZERO));
settlement.setPaymentStatus(paymentStatus(summary.paidAmount(), settlement.getSettlementAmount()));
updateById(settlement);
}
@@ -1024,8 +1103,8 @@ public class FormalSettlementServiceImpl extends BaseServiceImpl<FormalSettlemen
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.setRemainingPayableAmount(money(entity.getSettlementAmount()).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;
}
@@ -605,6 +605,8 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
throw new ServiceException("费用调整行数据不完整");
}
List<String> changes = new ArrayList<>();
Map<String, Object> beforeData = new LinkedHashMap<>();
Map<String, Object> afterData = new LinkedHashMap<>();
for (PreSettlementDetailAdjustRequest.FeeRow requestRow : request.getRows()) {
PreSettlementDetailFee fee = existingMap.get(requestRow.getId());
if (fee == null) {
@@ -632,19 +634,19 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
fee.setRemark(limitRemark(requestRow.getRemark(), 200));
detailFeeMapper.updateById(fee);
String prefix = "" + firstNotEmpty(fee.getCargoName(), fee.getLineNo()) + "";
appendChange(changes, prefix + "运输总量", beforeQuantity, fee.getTransportQuantity());
appendChange(changes, prefix + "里程", beforeMileage, fee.getMileage());
appendChange(changes, prefix + "运输单价", beforeUnitPrice, fee.getUnitPrice());
appendChange(changes, prefix + "运费", beforeFreight, fee.getFreightAmount());
appendChange(changes, beforeData, afterData, prefix + "运输总量", beforeQuantity, fee.getTransportQuantity());
appendChange(changes, beforeData, afterData, prefix + "里程", beforeMileage, fee.getMileage());
appendChange(changes, beforeData, afterData, prefix + "运输单价", beforeUnitPrice, fee.getUnitPrice());
appendChange(changes, beforeData, afterData, prefix + "运费", beforeFreight, fee.getFreightAmount());
Map<String, BigDecimal> afterFeeItems = parseFeeItems(fee.getFeeItemsJson());
Set<String> feeItemNames = new LinkedHashSet<>(beforeFeeItems.keySet());
feeItemNames.addAll(afterFeeItems.keySet());
feeItemNames.forEach(name -> appendChange(changes, prefix + name,
feeItemNames.forEach(name -> appendChange(changes, beforeData, afterData, prefix + name,
beforeFeeItems.get(name), afterFeeItems.get(name)));
appendChange(changes, prefix + "结算金额(含税)", beforeAmount, afterAmount);
appendChange(changes, prefix + "结算金额(不含税)", beforeNoTaxAmount,
appendChange(changes, beforeData, afterData, prefix + "结算金额(含税)", beforeAmount, afterAmount);
appendChange(changes, beforeData, afterData, prefix + "结算金额(不含税)", beforeNoTaxAmount,
fee.getSettlementAmountNoTax());
appendTextChange(changes, prefix + "备注", beforeRemark, fee.getRemark());
appendTextChange(changes, beforeData, afterData, prefix + "备注", beforeRemark, fee.getRemark());
}
if (changes.isEmpty()) {
throw new ServiceException("未修改任何结算明细费用");
@@ -653,7 +655,7 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
rebuildSummaryFees(settlement.getId(), true);
refreshSettlementAmount(settlement);
saveChange(settlement.getId(), "结算明细项", detail.getLineNo(), "调整",
String.join("", changes), changeReason);
String.join("", changes), changeReason, beforeData, afterData);
}
@Override
@@ -1313,12 +1315,19 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
private void saveChange(Long settlementId, String changeType, Integer lineNo, String operationType,
String content, String reason) {
saveChange(settlementId, changeType, lineNo, operationType, content, reason, null, null);
}
private void saveChange(Long settlementId, String changeType, Integer lineNo, String operationType,
String content, String reason, Map<String, Object> beforeData, Map<String, Object> afterData) {
PreSettlementChangeRecord record = new PreSettlementChangeRecord();
record.setPreSettlementId(settlementId);
record.setChangeType(changeType);
record.setLineNo(lineNo);
record.setOperationType(operationType);
record.setChangeContent(content);
record.setBeforeData(beforeData == null ? null : JsonUtil.toJson(beforeData));
record.setAfterData(afterData == null ? null : JsonUtil.toJson(afterData));
record.setOperatorName(Func.isEmpty(AuthUtil.getUserName()) ? "system" : AuthUtil.getUserName());
record.setChangeReason(reason);
record.setChangeTime(LocalDateTime.now());
@@ -1476,6 +1485,17 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
}
}
private void appendChange(List<String> changes, Map<String, Object> beforeData, Map<String, Object> afterData,
String fieldName, BigDecimal before, BigDecimal after) {
BigDecimal oldValue = money(before);
BigDecimal newValue = money(after);
if (oldValue.compareTo(newValue) != 0) {
changes.add("" + fieldName + "】从【" + oldValue + "】调整为【" + newValue + "");
beforeData.put(fieldName, before);
afterData.put(fieldName, after);
}
}
private void appendTextChange(List<String> changes, String fieldName, String before, String after) {
String oldValue = before == null ? "" : before;
String newValue = after == null ? "" : after;
@@ -1484,6 +1504,17 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
}
}
private void appendTextChange(List<String> changes, Map<String, Object> beforeData, Map<String, Object> afterData,
String fieldName, String before, String after) {
String oldValue = before == null ? "" : before;
String newValue = after == null ? "" : after;
if (!Objects.equals(oldValue, newValue)) {
changes.add("" + fieldName + "】从【" + oldValue + "】调整为【" + newValue + "");
beforeData.put(fieldName, before);
afterData.put(fieldName, after);
}
}
private String firstNotEmpty(String first, String second) {
return Func.isNotEmpty(first) ? first : second;
}
@@ -1447,7 +1447,25 @@ public class ReceivablePayableDetailServiceImpl
fee.setBillingType(stringValue(rule, "billingType", ""));
fee.setTransportQuantity(measure(rule, feeWaybill));
fee.setPriceUnit(stringValue(rule, "billingUnit", feeWaybill.getPriceUnit()));
fee.setUnitPrice(decimal(rule.get("unitPrice")));
fee.setUnitPrice(resolveCalculatedUnitPrice(rule, feeWaybill));
}
/**
* 解析费用行展示用的实际命中单价。
* 区间计费的规则默认单价仅用于兜底,费用行应展示当前计费量命中的区间单价。
*/
private BigDecimal resolveCalculatedUnitPrice(Map<String, Object> rule, Waybill waybill) {
BigDecimal defaultUnitPrice = decimal(rule.get("unitPrice"));
String billingType = stringValue(rule, "billingType", "");
if (!"区间单价".equals(billingType) && !"区间阶梯一口价".equals(billingType)) {
return defaultUnitPrice;
}
Optional<Map<String, Object>> matchedRange = range(ranges(rule), measure(rule, waybill));
if (matchedRange.isEmpty()) {
return defaultUnitPrice;
}
BigDecimal rangeUnitPrice = decimal(matchedRange.get().get("unitPrice"));
return rangeUnitPrice.signum() == 0 ? defaultUnitPrice : rangeUnitPrice;
}
private List<String> cargoFeeKey(Waybill waybill, Map<String, String> feeGoods) {
@@ -255,8 +255,8 @@ public class SettlementAdjustmentServiceImpl extends BaseServiceImpl<SettlementA
BigDecimal amount = money(formal.getSettlementAmount()).add(money(adjustment.getAdjustmentAmount()));
formal.setSettlementAmount(amount);
formal.setLocalSettlementAmount(amount.multiply(formal.getExchangeRate() == null ? BigDecimal.ONE : formal.getExchangeRate()));
formal.setRemainingPayableAmount(amount.subtract(money(formal.getAppliedPaymentAmount()))
.subtract(money(formal.getPaidAmount())).max(BigDecimal.ZERO));
formal.setRemainingPayableAmount(amount.subtract(money(formal.getPaidAmount()))
.max(BigDecimal.ZERO));
formalMapper.updateById(formal);
saveFormalChange(formal.getId(), "合计费用项", null, "调整",
"调整单" + adjustment.getAdjustmentNo() + "调整金额【" + money(adjustment.getAdjustmentAmount()) + "",
@@ -32,9 +32,8 @@ 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(appliedPaymentAmount).subtract(paidAmount).max(BigDecimal.ZERO));
vo.setRemainingPayableAmount(settlementAmount.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()) ? "应收" : "应付");