🐛 修复进度预付驳回重提,并校正结算费用与运输计划组织
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+2
@@ -36,6 +36,8 @@ public class PaymentApplicationSaveRequest implements Serializable {
|
||||
private List<Long> settlementIds;
|
||||
private Long preSettlementId;
|
||||
private List<Long> preSettlementIds;
|
||||
/** 预结算单号。驳回重提时用于在主键失真后找回预结算单 */
|
||||
private String preSettlementNo;
|
||||
private Long projectId;
|
||||
private String projectName;
|
||||
private Long deptId;
|
||||
|
||||
+7
-4
@@ -61,8 +61,9 @@ public class BillLedgerController extends BladeController {
|
||||
@ApiOperationSupport(order = 4)
|
||||
@Operation(summary = "付款申请可用汇票")
|
||||
public R<List<BillLedgerVO>> availableOptions(@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) Long deptId, @RequestParam(required = false) Long selectedId) {
|
||||
return R.data(billLedgerService.availableOptions(keyword, deptId, selectedId));
|
||||
@RequestParam(required = false) Long deptId, @RequestParam(required = false) Long selectedId,
|
||||
@RequestParam(required = false) String payerName, @RequestParam(required = false) String payeeName) {
|
||||
return R.data(billLedgerService.availableOptions(keyword, deptId, selectedId, payerName, payeeName));
|
||||
}
|
||||
|
||||
@GetMapping("/available-page")
|
||||
@@ -70,8 +71,10 @@ public class BillLedgerController extends BladeController {
|
||||
@Operation(summary = "付款申请可用汇票分页")
|
||||
public R<IPage<BillLedgerVO>> availablePage(Query pageQuery,
|
||||
@RequestParam(required = false) String keyword, @RequestParam(required = false) Long deptId,
|
||||
@RequestParam(required = false) Long selectedId) {
|
||||
return R.data(billLedgerService.availablePage(Condition.getPage(pageQuery), keyword, deptId, selectedId));
|
||||
@RequestParam(required = false) Long selectedId, @RequestParam(required = false) String payerName,
|
||||
@RequestParam(required = false) String payeeName) {
|
||||
return R.data(billLedgerService.availablePage(Condition.getPage(pageQuery), keyword, deptId, selectedId,
|
||||
payerName, payeeName));
|
||||
}
|
||||
|
||||
@PostMapping("/submit")
|
||||
|
||||
+3
-2
@@ -18,8 +18,9 @@ public interface IBillLedgerService extends BaseService<BillLedger> {
|
||||
IPage<BillLedgerVO> selectPage(IPage<BillLedger> page, BillLedgerVO query);
|
||||
BillLedgerVO detail(Long id);
|
||||
Map<String, Long> expiryCounts();
|
||||
List<BillLedgerVO> availableOptions(String keyword, Long deptId, Long selectedId);
|
||||
IPage<BillLedgerVO> availablePage(IPage<BillLedger> page, String keyword, Long deptId, Long selectedId);
|
||||
List<BillLedgerVO> availableOptions(String keyword, Long deptId, Long selectedId, String payerName, String payeeName);
|
||||
IPage<BillLedgerVO> availablePage(IPage<BillLedger> page, String keyword, Long deptId, Long selectedId,
|
||||
String payerName, String payeeName);
|
||||
Long submit(BillLedgerSaveRequest request);
|
||||
void removeLedger(Long id);
|
||||
}
|
||||
|
||||
+38
-9
@@ -86,14 +86,15 @@ public class BillLedgerServiceImpl extends BaseServiceImpl<BillLedgerMapper, Bil
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BillLedgerVO> availableOptions(String keyword, Long deptId, Long selectedId) {
|
||||
return availableList(keyword, deptId, selectedId);
|
||||
public List<BillLedgerVO> availableOptions(String keyword, Long deptId, Long selectedId, String payerName,
|
||||
String payeeName) {
|
||||
return availableList(keyword, deptId, selectedId, payerName, payeeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IPage<BillLedgerVO> availablePage(IPage<BillLedger> page, String keyword, Long deptId,
|
||||
Long selectedId) {
|
||||
List<BillLedgerVO> available = availableList(keyword, deptId, selectedId);
|
||||
Long selectedId, String payerName, String payeeName) {
|
||||
List<BillLedgerVO> available = availableList(keyword, deptId, selectedId, payerName, payeeName);
|
||||
long current = Math.max(page.getCurrent(), 1);
|
||||
long size = Math.max(page.getSize(), 1);
|
||||
long from = Math.min((current - 1) * size, available.size());
|
||||
@@ -103,27 +104,50 @@ public class BillLedgerServiceImpl extends BaseServiceImpl<BillLedgerMapper, Bil
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<BillLedgerVO> availableList(String keyword, Long deptId, Long selectedId) {
|
||||
private List<BillLedgerVO> availableList(String keyword, Long deptId, Long selectedId, String payerName,
|
||||
String payeeName) {
|
||||
LocalDate today = LocalDate.now();
|
||||
// 付款申请仅可选:收票类型、未到期、有可用余额;已选中的汇票始终带回以便回显
|
||||
// 未到期且有余额;收票且部门可用,或出票/收票单位与结算收付款方一致(开票、收票都可)
|
||||
return list(Wrappers.<BillLedger>lambdaQuery()
|
||||
.and(Func.isNotEmpty(keyword), wrapper -> wrapper.like(BillLedger::getBillNo, keyword)
|
||||
.or().like(BillLedger::getIssuerName, keyword)
|
||||
.or().like(BillLedger::getReceiverName, keyword))
|
||||
.and(wrapper -> wrapper
|
||||
.and(available -> available
|
||||
.eq(BillLedger::getBillType, "received")
|
||||
.ge(BillLedger::getMaturityDate, today)
|
||||
.gt(BillLedger::getAvailableBalance, BigDecimal.ZERO))
|
||||
.or(selectedId != null, child -> child.eq(BillLedger::getId, selectedId)))
|
||||
.orderByAsc(BillLedger::getMaturityDate)
|
||||
.orderByDesc(BillLedger::getCreateTime)).stream()
|
||||
.filter(item -> selectedId != null && Objects.equals(item.getId(), selectedId)
|
||||
|| departmentAvailable(item, deptId))
|
||||
|| selectableBill(item, deptId, payerName, payeeName))
|
||||
.map(item -> BillLedgerWrapper.build().entityVO(item))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 收票且当前部门在可用范围内,或出票单位、收票单位与结算付款方、收款方一致(顺序不限)。
|
||||
*/
|
||||
private boolean selectableBill(BillLedger ledger, Long deptId, String payerName, String payeeName) {
|
||||
boolean receivedAndDept = "received".equals(ledger.getBillType()) && departmentAvailable(ledger, deptId);
|
||||
return receivedAndDept || partiesMatch(ledger.getIssuerName(), ledger.getReceiverName(), payerName, payeeName);
|
||||
}
|
||||
|
||||
private boolean partiesMatch(String issuerName, String receiverName, String payerName, String payeeName) {
|
||||
String issuer = trimName(issuerName);
|
||||
String receiver = trimName(receiverName);
|
||||
String payer = trimName(payerName);
|
||||
String payee = trimName(payeeName);
|
||||
if (issuer.isEmpty() || receiver.isEmpty() || payer.isEmpty() || payee.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return (issuer.equals(payer) && receiver.equals(payee)) || (issuer.equals(payee) && receiver.equals(payer));
|
||||
}
|
||||
|
||||
private String trimName(String value) {
|
||||
return value == null ? "" : value.trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long submit(BillLedgerSaveRequest request) {
|
||||
@@ -141,6 +165,11 @@ public class BillLedgerServiceImpl extends BaseServiceImpl<BillLedgerMapper, Bil
|
||||
}
|
||||
CustomerArchive issuer = customer(request.getIssuerId(), "出票单位");
|
||||
CustomerArchive feeBearer = customer(request.getFeeBearerId(), "费用承担方");
|
||||
String receiverName = required(request.getReceiverName(), "收票单位", 100);
|
||||
if (!Objects.equals(feeBearer.getId(), issuer.getId())
|
||||
&& !customerName(feeBearer).equals(receiverName)) {
|
||||
throw new ServiceException("费用承担方只能选择收票单位或出票单位");
|
||||
}
|
||||
BigDecimal faceAmount = positive(request.getFaceAmount(), "票面金额").setScale(2, RoundingMode.HALF_UP);
|
||||
BigDecimal usedAmount = entity.getId() == null ? BigDecimal.ZERO : activeUsedAmount(entity.getId());
|
||||
if (faceAmount.compareTo(usedAmount) < 0) {
|
||||
@@ -150,7 +179,7 @@ public class BillLedgerServiceImpl extends BaseServiceImpl<BillLedgerMapper, Bil
|
||||
entity.setBillNo(billNo);
|
||||
entity.setIssuerId(issuer.getId());
|
||||
entity.setIssuerName(customerName(issuer));
|
||||
entity.setReceiverName(required(request.getReceiverName(), "收票单位", 100));
|
||||
entity.setReceiverName(receiverName);
|
||||
entity.setBillType(request.getBillType());
|
||||
entity.setFaceAmount(faceAmount);
|
||||
entity.setAvailableBalance(faceAmount.subtract(usedAmount).setScale(2, RoundingMode.HALF_UP));
|
||||
|
||||
+112
-19
@@ -374,8 +374,8 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
matchedInvoiceAmount = matchedInvoiceAmount.add(money(item.getMatchedAmount()));
|
||||
}
|
||||
entity.setMatchedInvoiceAmount(matchedInvoiceAmount);
|
||||
// 进度预付只关联预结算单。编辑回传的 settlementId 可能是预结算单 ID,不能拿去查正式结算单。
|
||||
normalizeSettlementReference(request);
|
||||
// 进度预付只关联预结算单。编辑回传的 settlementId 可能是预结算单 ID 或已失效的正式结算单 ID。
|
||||
normalizeSettlementReference(request, entity);
|
||||
if (TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT.equals(request.getPaymentType()) && request.getSettlementId() == null
|
||||
&& request.getSettlementIds() != null && !request.getSettlementIds().isEmpty()) {
|
||||
request.setSettlementId(request.getSettlementIds().get(0));
|
||||
@@ -1897,16 +1897,19 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
/**
|
||||
* 进度预付只认预结算单;尾款付款若 settlementId 实际是预结算单 ID 或正式结算单已不存在,改为按预结算单保存。
|
||||
*/
|
||||
private void normalizeSettlementReference(PaymentApplicationSaveRequest request) {
|
||||
if ("progress_advance".equals(request.getPaymentType())) {
|
||||
if (request.getPreSettlementId() == null && request.getPreSettlementIds() != null) {
|
||||
request.setPreSettlementId(request.getPreSettlementIds().stream().filter(Objects::nonNull).findFirst().orElse(null));
|
||||
private void normalizeSettlementReference(PaymentApplicationSaveRequest request, PaymentApplication entity) {
|
||||
if ("progress_advance".equals(request.getPaymentType())
|
||||
|| (entity != null && "progress_advance".equals(entity.getPaymentType())
|
||||
&& Func.isEmpty(request.getPaymentType()))) {
|
||||
if (Func.isEmpty(request.getPaymentType())) {
|
||||
request.setPaymentType("progress_advance");
|
||||
}
|
||||
if (request.getPreSettlementId() == null) {
|
||||
request.setPreSettlementId(request.getSettlementId());
|
||||
}
|
||||
if (request.getPreSettlementId() == null && request.getSettlementIds() != null) {
|
||||
request.setPreSettlementId(request.getSettlementIds().stream().filter(Objects::nonNull).findFirst().orElse(null));
|
||||
Long preSettlementId = resolveProgressPreSettlementId(request, entity);
|
||||
request.setPreSettlementId(preSettlementId);
|
||||
if (preSettlementId == null) {
|
||||
request.setPreSettlementIds(null);
|
||||
} else if (request.getPreSettlementIds() == null || request.getPreSettlementIds().stream().noneMatch(Objects::nonNull)) {
|
||||
request.setPreSettlementIds(List.of(preSettlementId));
|
||||
}
|
||||
request.setSettlementId(null);
|
||||
request.setSettlementIds(null);
|
||||
@@ -1963,8 +1966,61 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
entity.setSettlementNo(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 进度预付驳回重提时,请求里的结算主键可能是失效正式结算单或精度失真后的 ID。
|
||||
* 依次用预结算主键、正式结算位上的预结算主键、单据已保存的预结算主键和预结算单号找回。
|
||||
*/
|
||||
private Long resolveProgressPreSettlementId(PaymentApplicationSaveRequest request, PaymentApplication entity) {
|
||||
List<Long> candidates = new java.util.ArrayList<>();
|
||||
if (request.getPreSettlementId() != null) {
|
||||
candidates.add(request.getPreSettlementId());
|
||||
}
|
||||
if (request.getPreSettlementIds() != null) {
|
||||
request.getPreSettlementIds().stream().filter(Objects::nonNull).forEach(candidates::add);
|
||||
}
|
||||
if (request.getSettlementId() != null) {
|
||||
candidates.add(request.getSettlementId());
|
||||
}
|
||||
if (request.getSettlementIds() != null) {
|
||||
request.getSettlementIds().stream().filter(Objects::nonNull).forEach(candidates::add);
|
||||
}
|
||||
if (entity != null && entity.getPreSettlementId() != null) {
|
||||
candidates.add(entity.getPreSettlementId());
|
||||
}
|
||||
for (Long candidate : candidates) {
|
||||
PreSettlement preSettlement = preSettlementMapper.selectById(candidate);
|
||||
if (preSettlement != null && !Objects.equals(preSettlement.getIsDeleted(), 1)) {
|
||||
return preSettlement.getId();
|
||||
}
|
||||
}
|
||||
String preSettlementNo = request.getPreSettlementNo();
|
||||
if (Func.isEmpty(preSettlementNo) && entity != null) {
|
||||
preSettlementNo = entity.getPreSettlementNo();
|
||||
}
|
||||
if (Func.isNotEmpty(preSettlementNo)) {
|
||||
String settlementNo = preSettlementNo.split("、")[0].trim();
|
||||
PreSettlement preSettlement = preSettlementMapper.selectOne(Wrappers.<PreSettlement>lambdaQuery()
|
||||
.eq(PreSettlement::getPreSettlementNo, settlementNo)
|
||||
.eq(PreSettlement::getIsDeleted, 0)
|
||||
.last("limit 1"));
|
||||
if (preSettlement != null) {
|
||||
return preSettlement.getId();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void fillReference(PaymentApplication entity, PaymentApplicationSaveRequest request) {
|
||||
if ("project_advance".equals(request.getPaymentType())) {
|
||||
if ("progress_advance".equals(request.getPaymentType())) {
|
||||
Long preSettlementId = resolveProgressPreSettlementId(request, entity);
|
||||
request.setPreSettlementId(preSettlementId);
|
||||
if (preSettlementId == null) {
|
||||
throw new ServiceException("进度预付必须关联预结算单");
|
||||
}
|
||||
request.setSettlementId(null);
|
||||
request.setSettlementIds(null);
|
||||
fillPreSettlementReference(entity, requireApprovedPreSettlement(request.getPreSettlementId()));
|
||||
} else if ("project_advance".equals(request.getPaymentType())) {
|
||||
ProjectApply project = projectApplyMapper.selectById(request.getProjectId());
|
||||
if (project == null || Objects.equals(project.getIsDeleted(), 1)) throw new ServiceException("所属项目不存在");
|
||||
entity.setSettlementId(null); entity.setSettlementNo(null); entity.setPreSettlementId(null); entity.setPreSettlementNo(null);
|
||||
@@ -1981,6 +2037,10 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
} else if (request.getPreSettlementId() != null) {
|
||||
request.setSettlementId(null);
|
||||
fillPreSettlementReference(entity, requireApprovedPreSettlement(request.getPreSettlementId()));
|
||||
} else if (entity.getPreSettlementId() != null) {
|
||||
request.setSettlementId(null);
|
||||
request.setPreSettlementId(entity.getPreSettlementId());
|
||||
fillPreSettlementReference(entity, requireApprovedPreSettlement(entity.getPreSettlementId()));
|
||||
} else {
|
||||
throw new ServiceException("正式结算单不存在");
|
||||
}
|
||||
@@ -2487,7 +2547,8 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
return;
|
||||
}
|
||||
BillLedger ledger = billLedgerMapper.selectById(request.getBillLedgerId());
|
||||
validateBill(ledger, entity.getDeptId(), entity.getAppliedAmount(), true, entity.getId());
|
||||
validateBill(ledger, entity.getDeptId(), entity.getAppliedAmount(), true, entity.getId(),
|
||||
entity.getSettlementId(), entity.getPreSettlementId());
|
||||
entity.setBillLedgerId(ledger.getId());
|
||||
entity.setBillNo(ledger.getBillNo());
|
||||
}
|
||||
@@ -2495,25 +2556,57 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
private void validateSelectedBill(PaymentApplication entity, boolean locked) {
|
||||
if (!isBillPayment(entity.getPaymentMethod())) return;
|
||||
BillLedger ledger = locked ? lockedBill(entity.getBillLedgerId()) : billLedgerMapper.selectById(entity.getBillLedgerId());
|
||||
validateBill(ledger, entity.getDeptId(), entity.getAppliedAmount(), true, entity.getId());
|
||||
validateBill(ledger, entity.getDeptId(), entity.getAppliedAmount(), true, entity.getId(),
|
||||
entity.getSettlementId(), entity.getPreSettlementId());
|
||||
}
|
||||
|
||||
private void validateBill(BillLedger ledger, Long deptId, BigDecimal amount, boolean checkMaturity, Long paymentApplicationId) {
|
||||
private void validateBill(BillLedger ledger, Long deptId, BigDecimal amount, boolean checkMaturity,
|
||||
Long paymentApplicationId, Long settlementId, Long preSettlementId) {
|
||||
if (ledger == null || Objects.equals(ledger.getIsDeleted(), 1)) throw new ServiceException("所选汇票台账不存在");
|
||||
if (!"received".equals(ledger.getBillType())) {
|
||||
throw new ServiceException("汇票付款仅可选择收票类型的汇票");
|
||||
boolean partyMatched = settlementPartiesMatch(ledger, settlementId, preSettlementId);
|
||||
if (!"received".equals(ledger.getBillType()) && !partyMatched) {
|
||||
throw new ServiceException("汇票付款仅可选择收票类型的汇票,或出票单位与收票单位均为当前结算收付款方的汇票");
|
||||
}
|
||||
if (checkMaturity && (ledger.getMaturityDate() == null || ledger.getMaturityDate().isBefore(LocalDate.now()))) {
|
||||
throw new ServiceException("所选汇票已到期");
|
||||
}
|
||||
BigDecimal available = money(ledger.getAvailableBalance()).add(occupiedAmount(paymentApplicationId, ledger.getId()));
|
||||
if (money(amount).compareTo(available) > 0) throw new ServiceException("申请付款金额超过汇票可用余额");
|
||||
if (!departmentAvailable(ledger, deptId)) throw new ServiceException("当前使用部门不在汇票可用部门范围内");
|
||||
if (!partyMatched && !departmentAvailable(ledger, deptId)) throw new ServiceException("当前使用部门不在汇票可用部门范围内");
|
||||
}
|
||||
|
||||
/** 出票单位、收票单位与结算付款方、收款方一致(顺序不限) */
|
||||
private boolean settlementPartiesMatch(BillLedger ledger, Long settlementId, Long preSettlementId) {
|
||||
String payerName = null;
|
||||
String payeeName = null;
|
||||
if (settlementId != null) {
|
||||
FormalSettlement settlement = formalSettlementMapper.selectById(settlementId);
|
||||
if (settlement != null && !Objects.equals(settlement.getIsDeleted(), 1)) {
|
||||
payerName = settlement.getPayerName();
|
||||
payeeName = settlement.getPayeeName();
|
||||
}
|
||||
}
|
||||
if ((payerName == null || payeeName == null) && preSettlementId != null) {
|
||||
PreSettlement settlement = preSettlementMapper.selectById(preSettlementId);
|
||||
if (settlement != null && !Objects.equals(settlement.getIsDeleted(), 1)) {
|
||||
payerName = settlement.getPayerName();
|
||||
payeeName = settlement.getPayeeName();
|
||||
}
|
||||
}
|
||||
String issuer = ledger.getIssuerName() == null ? "" : ledger.getIssuerName().trim();
|
||||
String receiver = ledger.getReceiverName() == null ? "" : ledger.getReceiverName().trim();
|
||||
String payer = payerName == null ? "" : payerName.trim();
|
||||
String payee = payeeName == null ? "" : payeeName.trim();
|
||||
if (issuer.isEmpty() || receiver.isEmpty() || payer.isEmpty() || payee.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return (issuer.equals(payer) && receiver.equals(payee)) || (issuer.equals(payee) && receiver.equals(payer));
|
||||
}
|
||||
|
||||
private void useBillBalance(PaymentApplication entity) {
|
||||
BillLedger ledger = lockedBill(entity.getBillLedgerId());
|
||||
validateBill(ledger, entity.getDeptId(), entity.getAppliedAmount(), true, null);
|
||||
validateBill(ledger, entity.getDeptId(), entity.getAppliedAmount(), true, null,
|
||||
entity.getSettlementId(), entity.getPreSettlementId());
|
||||
BillLedgerUsage exists = lockedBillUsage(entity.getId());
|
||||
if (exists != null && TransportBusiConstant.APPROVED.equals(exists.getUsageStatus())) {
|
||||
throw new ServiceException("该付款申请已生成汇票使用记录");
|
||||
|
||||
+82
-65
@@ -206,6 +206,9 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
vo.setChangeRecords(changeRecordMapper.selectList(Wrappers.<PreSettlementChangeRecord>lambdaQuery()
|
||||
.eq(PreSettlementChangeRecord::getPreSettlementId, id)
|
||||
.eq(PreSettlementChangeRecord::getIsDeleted, 0)
|
||||
.and(wrapper -> wrapper.isNull(PreSettlementChangeRecord::getChangeContent)
|
||||
.or()
|
||||
.ne(PreSettlementChangeRecord::getChangeContent, "预结算审批通过"))
|
||||
.orderByDesc(PreSettlementChangeRecord::getChangeTime)));
|
||||
vo.setPrintTemplates(resolvePrintTemplates(settlement));
|
||||
return vo;
|
||||
@@ -355,8 +358,10 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
synchronizeDetails(settlement, request.getSourceDetailIds(), Boolean.TRUE.equals(request.getAllowSourceMismatch()));
|
||||
}
|
||||
settlement.setExchangeRate(normalizeRate(request.getExchangeRate(), settlement.getCurrency()));
|
||||
List<PreSettlementSummaryFee> summaryBefore = created ? new ArrayList<>() : listSummaryFees(settlement.getId());
|
||||
rebuildSummaryFees(settlement.getId(), true);
|
||||
applySummaryRequest(settlement.getId(), contractChanged ? List.of() : request.getSummaryFees());
|
||||
recordSummaryFeeChanges(settlement.getId(), summaryBefore);
|
||||
refreshSettlementAmount(settlement);
|
||||
projectAdvanceWriteOffService.replaceWriteOffs(
|
||||
TransportBusiConstant.SETTLEMENT_BILL_PRE,
|
||||
@@ -425,7 +430,6 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
settlement.setCurrentProcessor(AuthUtil.getUserName());
|
||||
settlement.setApprovedTime(LocalDateTime.now());
|
||||
updateById(settlement);
|
||||
saveChange(settlement.getId(), "结算单基本信息", null, "调整", "预结算审批通过", request.getReason());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1129,11 +1133,6 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
throw new ServiceException("存在无效的手工费用行");
|
||||
}
|
||||
boolean creating = row.getId() == null;
|
||||
String beforeFeeType = creating ? null : row.getFeeType();
|
||||
String beforeFeeItem = creating ? null : row.getFeeItem();
|
||||
BigDecimal beforeAdjustAmount = creating ? null : money(row.getAdjustAmount());
|
||||
BigDecimal beforeSettlementAmount = creating ? null : money(row.getSettlementAmount());
|
||||
String beforeRemark = creating ? null : row.getRemark();
|
||||
row.setPreSettlementId(settlementId);
|
||||
row.setLineNo(lineNo++);
|
||||
row.setFeeType(feeType);
|
||||
@@ -1149,8 +1148,6 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
summaryFeeMapper.updateById(row);
|
||||
}
|
||||
retainedManualIds.add(row.getId());
|
||||
saveManualSummaryFeeChange(settlementId, creating, beforeFeeType, beforeFeeItem,
|
||||
beforeAdjustAmount, beforeSettlementAmount, beforeRemark, row);
|
||||
continue;
|
||||
}
|
||||
String feeItem = requiredText(requestRow.getFeeItem(), "费用项");
|
||||
@@ -1175,45 +1172,16 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
row.setFeeItem(feeItem);
|
||||
// 生成费用行的三个金额必须来源于结算明细快照,不能接受前端回传值覆盖原金额。
|
||||
// rebuildSummaryFees 已按明细重建 original/adjust/settlement,此处仅保留备注。
|
||||
BigDecimal before = money(row.getAdjustAmount());
|
||||
row.setOriginalAmount(money(row.getOriginalAmount()));
|
||||
row.setSettlementAmount(money(row.getSettlementAmount()));
|
||||
row.setAdjustAmount(money(row.getSettlementAmount().subtract(row.getOriginalAmount())));
|
||||
row.setRemark(limitRemark(requestRow.getRemark(), 50));
|
||||
if (row.getId() == null) summaryFeeMapper.insert(row); else summaryFeeMapper.updateById(row);
|
||||
retainedGeneratedIds.add(row.getId());
|
||||
if (before.compareTo(row.getAdjustAmount()) != 0) {
|
||||
Map<String, Object> beforeData = new LinkedHashMap<>();
|
||||
Map<String, Object> afterData = new LinkedHashMap<>();
|
||||
beforeData.put("费用项", row.getFeeItem());
|
||||
beforeData.put("调整金额", before);
|
||||
beforeData.put("结算金额", money(row.getOriginalAmount().add(before)));
|
||||
afterData.put("费用项", row.getFeeItem());
|
||||
afterData.put("调整金额", row.getAdjustAmount());
|
||||
afterData.put("结算金额", money(row.getSettlementAmount()));
|
||||
saveChange(settlementId, "合计费用项", row.getLineNo(), "调整",
|
||||
"【" + row.getFeeItem() + "-调整金额】从【" + before + "】调整为【" + row.getAdjustAmount() + "】",
|
||||
"", beforeData, afterData);
|
||||
}
|
||||
}
|
||||
for (PreSettlementSummaryFee manualRow : manualRows) {
|
||||
if (!retainedManualIds.contains(manualRow.getId())) {
|
||||
summaryFeeMapper.deleteById(manualRow.getId());
|
||||
Map<String, Object> beforeData = new LinkedHashMap<>();
|
||||
Map<String, Object> afterData = new LinkedHashMap<>();
|
||||
beforeData.put("费用类型", manualRow.getFeeType());
|
||||
beforeData.put("费用项", manualRow.getFeeItem());
|
||||
beforeData.put("调整金额", money(manualRow.getAdjustAmount()));
|
||||
beforeData.put("结算金额", money(manualRow.getSettlementAmount()));
|
||||
beforeData.put("备注", manualRow.getRemark());
|
||||
afterData.put("费用类型", "");
|
||||
afterData.put("费用项", "");
|
||||
afterData.put("调整金额", BigDecimal.ZERO.setScale(2));
|
||||
afterData.put("结算金额", BigDecimal.ZERO.setScale(2));
|
||||
afterData.put("备注", "");
|
||||
saveChange(settlementId, "合计费用项", manualRow.getLineNo(), "删除",
|
||||
"删除合计费用项【" + manualRow.getFeeItem() + "】金额【" + money(manualRow.getSettlementAmount()) + "】",
|
||||
"", beforeData, afterData);
|
||||
}
|
||||
}
|
||||
for (PreSettlementSummaryFee generatedRow : existingGeneratedRows) {
|
||||
@@ -1563,38 +1531,87 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
}
|
||||
}
|
||||
|
||||
private void saveManualSummaryFeeChange(Long settlementId, boolean creating, String beforeFeeType,
|
||||
String beforeFeeItem, BigDecimal beforeAdjustAmount, BigDecimal beforeSettlementAmount, String beforeRemark,
|
||||
PreSettlementSummaryFee row) {
|
||||
List<String> changes = new ArrayList<>();
|
||||
private void recordSummaryFeeChanges(Long settlementId, List<PreSettlementSummaryFee> beforeRows) {
|
||||
List<Map<String, Object>> beforeLines = summaryFeeLines(beforeRows);
|
||||
List<Map<String, Object>> afterLines = summaryFeeLines(listSummaryFees(settlementId));
|
||||
if (beforeLines.equals(afterLines)) {
|
||||
return;
|
||||
}
|
||||
String content = summaryFeeChangeContent(beforeLines, afterLines);
|
||||
if (Func.isEmpty(content)) {
|
||||
return;
|
||||
}
|
||||
Map<String, Object> beforeData = new LinkedHashMap<>();
|
||||
Map<String, Object> afterData = new LinkedHashMap<>();
|
||||
if (creating) {
|
||||
changes.add("新增合计费用项【" + row.getFeeItem() + "】金额【" + money(row.getSettlementAmount()) + "】");
|
||||
beforeData.put("费用类型", "");
|
||||
beforeData.put("费用项", "");
|
||||
beforeData.put("调整金额", BigDecimal.ZERO.setScale(2));
|
||||
beforeData.put("结算金额", BigDecimal.ZERO.setScale(2));
|
||||
beforeData.put("备注", "");
|
||||
afterData.put("费用类型", row.getFeeType());
|
||||
afterData.put("费用项", row.getFeeItem());
|
||||
afterData.put("调整金额", money(row.getAdjustAmount()));
|
||||
afterData.put("结算金额", money(row.getSettlementAmount()));
|
||||
afterData.put("备注", row.getRemark());
|
||||
saveChange(settlementId, "合计费用项", row.getLineNo(), "新增", String.join(";", changes), "",
|
||||
beforeData, afterData);
|
||||
return;
|
||||
beforeData.put("结算合计", beforeLines);
|
||||
afterData.put("结算合计", afterLines);
|
||||
String operationType = beforeLines.isEmpty() ? "新增" : afterLines.isEmpty() ? "删除" : "调整";
|
||||
saveChange(settlementId, "结算合计", null, operationType, content, "", beforeData, afterData);
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> summaryFeeLines(List<PreSettlementSummaryFee> rows) {
|
||||
Map<String, String> categoryNames = DictBizCache.getList("fee_category").stream()
|
||||
.collect(Collectors.toMap(DictBiz::getDictKey, DictBiz::getDictValue, (first, second) -> first));
|
||||
List<Map<String, Object>> lines = new ArrayList<>();
|
||||
for (PreSettlementSummaryFee row : rows) {
|
||||
String feeType = row.getFeeType() == null ? "" : row.getFeeType();
|
||||
Map<String, Object> line = new LinkedHashMap<>();
|
||||
line.put("来源", Integer.valueOf(1).equals(row.getManualFlag()) ? "手工新增" : "系统归集");
|
||||
line.put("费用类型", categoryNames.getOrDefault(feeType, feeType));
|
||||
line.put("费用项", row.getFeeItem() == null ? "" : row.getFeeItem());
|
||||
line.put("原金额", money(row.getOriginalAmount()));
|
||||
line.put("调整金额", money(row.getAdjustAmount()));
|
||||
line.put("结算金额", money(row.getSettlementAmount()));
|
||||
line.put("备注", row.getRemark() == null ? "" : row.getRemark());
|
||||
lines.add(line);
|
||||
}
|
||||
appendTextChange(changes, beforeData, afterData, "费用类型", beforeFeeType, row.getFeeType());
|
||||
appendTextChange(changes, beforeData, afterData, "费用项", beforeFeeItem, row.getFeeItem());
|
||||
appendChange(changes, beforeData, afterData, "调整金额", beforeAdjustAmount, row.getAdjustAmount());
|
||||
appendChange(changes, beforeData, afterData, "结算金额", beforeSettlementAmount, row.getSettlementAmount());
|
||||
appendTextChange(changes, beforeData, afterData, "备注", beforeRemark, row.getRemark());
|
||||
if (changes.isEmpty()) {
|
||||
return;
|
||||
return lines;
|
||||
}
|
||||
|
||||
private String summaryFeeChangeContent(List<Map<String, Object>> beforeLines, List<Map<String, Object>> afterLines) {
|
||||
Map<String, Map<String, Object>> beforeMap = indexSummaryLines(beforeLines);
|
||||
Map<String, Map<String, Object>> afterMap = indexSummaryLines(afterLines);
|
||||
List<String> changes = new ArrayList<>();
|
||||
afterMap.forEach((key, afterLine) -> {
|
||||
Map<String, Object> beforeLine = beforeMap.get(key);
|
||||
String label = summaryLineLabel(afterLine);
|
||||
if (beforeLine == null) {
|
||||
changes.add("新增结算合计【" + label + "】结算金额【" + afterLine.get("结算金额") + "】");
|
||||
return;
|
||||
}
|
||||
if (!Objects.equals(beforeLine.get("原金额"), afterLine.get("原金额"))
|
||||
|| !Objects.equals(beforeLine.get("调整金额"), afterLine.get("调整金额"))
|
||||
|| !Objects.equals(beforeLine.get("结算金额"), afterLine.get("结算金额"))
|
||||
|| !Objects.equals(String.valueOf(beforeLine.get("备注")), String.valueOf(afterLine.get("备注")))) {
|
||||
changes.add("调整结算合计【" + label + "】结算金额从【" + beforeLine.get("结算金额")
|
||||
+ "】调整为【" + afterLine.get("结算金额") + "】");
|
||||
}
|
||||
});
|
||||
beforeMap.forEach((key, beforeLine) -> {
|
||||
if (!afterMap.containsKey(key)) {
|
||||
changes.add("删除结算合计【" + summaryLineLabel(beforeLine) + "】结算金额【" + beforeLine.get("结算金额") + "】");
|
||||
}
|
||||
});
|
||||
return String.join(";", changes);
|
||||
}
|
||||
|
||||
private Map<String, Map<String, Object>> indexSummaryLines(List<Map<String, Object>> lines) {
|
||||
Map<String, Map<String, Object>> indexed = new LinkedHashMap<>();
|
||||
Map<String, Integer> counts = new HashMap<>();
|
||||
for (Map<String, Object> line : lines) {
|
||||
String base = String.valueOf(line.get("来源")) + "|" + line.get("费用类型") + "|" + line.get("费用项");
|
||||
int index = counts.merge(base, 1, Integer::sum);
|
||||
indexed.put(base + "#" + index, line);
|
||||
}
|
||||
saveChange(settlementId, "合计费用项", row.getLineNo(), "调整", String.join(";", changes), "",
|
||||
beforeData, afterData);
|
||||
return indexed;
|
||||
}
|
||||
|
||||
private String summaryLineLabel(Map<String, Object> line) {
|
||||
String feeType = String.valueOf(line.getOrDefault("费用类型", ""));
|
||||
String feeItem = String.valueOf(line.getOrDefault("费用项", ""));
|
||||
String source = String.valueOf(line.getOrDefault("来源", ""));
|
||||
String name = feeType.isBlank() ? feeItem : feeType + "-" + feeItem;
|
||||
return source + " " + name;
|
||||
}
|
||||
|
||||
private void saveChange(Long settlementId, String changeType, Integer lineNo, String operationType,
|
||||
|
||||
+27
-11
@@ -424,9 +424,7 @@ public class ReceivablePayableDetailServiceImpl
|
||||
}
|
||||
});
|
||||
ReceivablePayableFeeDetailVO result = buildFeeDetail(rows);
|
||||
LinkedHashSet<String> feeItemNames = new LinkedHashSet<>(contractFeeItemNames(detail.getContractId()));
|
||||
feeItemNames.addAll(result.getFeeItemNames());
|
||||
result.setFeeItemNames(new ArrayList<>(feeItemNames));
|
||||
// 只展示本明细已生成的费用项。合同上其他计费方案的费用项不能叠到当前明细。
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1968,21 +1966,35 @@ public class ReceivablePayableDetailServiceImpl
|
||||
}
|
||||
|
||||
private List<ReceivablePayableCargoFee> calculatedFees(Waybill waybill, ContractManage contract, String planId) {
|
||||
return calculatedFees(waybill, contract, planId, false);
|
||||
return calculatedFees(waybill, contract, planId, false, false);
|
||||
}
|
||||
|
||||
private List<ReceivablePayableCargoFee> calculatedFees(Waybill waybill, ContractManage contract, String planId, boolean matchOnly) {
|
||||
private List<ReceivablePayableCargoFee> calculatedFees(Waybill waybill, ContractManage contract, String planId,
|
||||
boolean matchOnly) {
|
||||
return calculatedFees(waybill, contract, planId, matchOnly, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param strictPlan 更新费用时为 true:选定方案有规则但未命中时,不再回退运单历史费用,避免旧费用项叠加
|
||||
*/
|
||||
private List<ReceivablePayableCargoFee> calculatedFees(Waybill waybill, ContractManage contract, String planId,
|
||||
boolean matchOnly, boolean strictPlan) {
|
||||
List<Map<String, Object>> plans = parseList(contract == null ? null : contract.getBillingPlanJson());
|
||||
Map<String, Object> plan = "__matched__".equals(planId)
|
||||
? resolveDefaultBillingPlan(plans, waybill.getTransportType())
|
||||
: resolveBillingPlan(plans, planId);
|
||||
return calculatedFees(waybill, plan, matchOnly);
|
||||
return calculatedFees(waybill, plan, matchOnly, strictPlan);
|
||||
}
|
||||
|
||||
private List<ReceivablePayableCargoFee> calculatedFees(Waybill waybill, Map<String, Object> plan,
|
||||
boolean matchOnly) {
|
||||
return calculatedFees(waybill, plan, matchOnly, false);
|
||||
}
|
||||
|
||||
private List<ReceivablePayableCargoFee> calculatedFees(Waybill waybill, Map<String, Object> plan,
|
||||
boolean matchOnly, boolean strictPlan) {
|
||||
if (plan == null || !(plan.get("rules") instanceof List<?>)) {
|
||||
return matchOnly ? List.of() : buildCargoFees(waybill);
|
||||
return matchOnly || strictPlan ? List.of() : buildCargoFees(waybill);
|
||||
}
|
||||
Map<List<String>, ReceivablePayableCargoFee> feesByCargo = new LinkedHashMap<>();
|
||||
Map<List<String>, Map<String, BigDecimal>> feeItemsByCargo = new LinkedHashMap<>();
|
||||
@@ -2024,7 +2036,11 @@ public class ReceivablePayableDetailServiceImpl
|
||||
ReceivablePayableCargoFee fee = result.get(index);
|
||||
fee.setLineNo(String.format("%04d", index + 1));
|
||||
}
|
||||
return result.isEmpty() && !matchOnly ? buildCargoFees(waybill) : result;
|
||||
// 已选定计费方案时,只保留该方案算出的费用项,不用运单上的历史费用回填。
|
||||
if (strictPlan || matchOnly) {
|
||||
return result;
|
||||
}
|
||||
return result.isEmpty() ? buildCargoFees(waybill) : result;
|
||||
}
|
||||
|
||||
private ReceivablePayableCargoFee buildCalculatedCargoFee(Waybill waybill, Waybill feeWaybill,
|
||||
@@ -2934,7 +2950,7 @@ public class ReceivablePayableDetailServiceImpl
|
||||
throw new ServiceException("关联运单不存在");
|
||||
}
|
||||
ContractManage contract = contractManageService.getById(detail.getContractId());
|
||||
List<ReceivablePayableCargoFee> fees = calculatedFees(waybill, contract, billingPlanId);
|
||||
List<ReceivablePayableCargoFee> fees = calculatedFees(waybill, contract, billingPlanId, false, true);
|
||||
cargoFeeMapper.delete(Wrappers.<ReceivablePayableCargoFee>lambdaQuery().eq(ReceivablePayableCargoFee::getDetailId, detail.getId()));
|
||||
fees.forEach(fee -> { fee.setDetailId(detail.getId()); cargoFeeMapper.insert(fee); });
|
||||
BigDecimal freight = fees.stream().map(fee -> money(fee.getFreightAmount()))
|
||||
@@ -2959,7 +2975,7 @@ public class ReceivablePayableDetailServiceImpl
|
||||
ContractManage contract = contractManageService.getById(detail.getContractId());
|
||||
List<Waybill> masterGoods = masterOrderGoods(masterOrder);
|
||||
List<ReceivablePayableCargoFee> fees = masterGoods.stream()
|
||||
.flatMap(goods -> calculatedFees(goods, contract, billingPlanId).stream()).toList();
|
||||
.flatMap(goods -> calculatedFees(goods, contract, billingPlanId, false, true).stream()).toList();
|
||||
normalizeMasterFeeLines(fees);
|
||||
cargoFeeMapper.delete(Wrappers.<ReceivablePayableCargoFee>lambdaQuery()
|
||||
.eq(ReceivablePayableCargoFee::getDetailId, detail.getId()));
|
||||
@@ -2995,7 +3011,7 @@ public class ReceivablePayableDetailServiceImpl
|
||||
}
|
||||
ContractManage contract = contractManageService.getById(detail.getContractId());
|
||||
List<ReceivablePayableCargoFee> fees = waybills.stream()
|
||||
.flatMap(waybill -> calculatedFees(waybill, contract, billingPlanId).stream()).toList();
|
||||
.flatMap(waybill -> calculatedFees(waybill, contract, billingPlanId, false, true).stream()).toList();
|
||||
normalizeWaybillFeeLines(fees);
|
||||
cargoFeeMapper.delete(Wrappers.<ReceivablePayableCargoFee>lambdaQuery()
|
||||
.eq(ReceivablePayableCargoFee::getDetailId, detail.getId()));
|
||||
|
||||
+31
-1
@@ -81,6 +81,8 @@ public class TransportPlanServiceImpl extends BaseServiceImpl<TransportPlanMappe
|
||||
|
||||
@Override
|
||||
public IPage<TransportPlanVO> selectTransportPlanPage(IPage<TransportPlan> page, TransportPlanVO transportPlan) {
|
||||
// 历史手工创建未写入 dept_id,allDept=0 按组织过滤时会被排除
|
||||
backfillMissingDept(transportPlan);
|
||||
IPage<TransportPlan> entityPage = page(page, buildQuery(transportPlan));
|
||||
IPage<TransportPlanVO> resultPage = TransportPlanWrapper.build().pageVO(entityPage);
|
||||
fillCustomerNamesFromContracts(resultPage.getRecords());
|
||||
@@ -151,6 +153,12 @@ public class TransportPlanServiceImpl extends BaseServiceImpl<TransportPlanMappe
|
||||
if (created) {
|
||||
transportPlan.setPlanNo(nextCode());
|
||||
}
|
||||
// 新建或历史空组织一律落到当前组织,避免列表按 dept_id 过滤后看不到
|
||||
if (created || Func.isEmpty(transportPlan.getDeptId()) || transportPlan.getDeptId() <= 0) {
|
||||
Dept dept = TransportBusinessSupport.currentDept("运输计划");
|
||||
transportPlan.setDeptId(dept.getId());
|
||||
transportPlan.setDeptName(dept.getDeptName());
|
||||
}
|
||||
validate(transportPlan);
|
||||
return saveOrUpdate(transportPlan);
|
||||
}
|
||||
@@ -662,11 +670,33 @@ public class TransportPlanServiceImpl extends BaseServiceImpl<TransportPlanMappe
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 把所属组织为空、且创建组织等于当前组织的运输计划回填 dept_id,使 allDept=0 列表能查到。
|
||||
*/
|
||||
private void backfillMissingDept(TransportPlanVO transportPlan) {
|
||||
if (Objects.equals(transportPlan.getAllDept(), 1)) {
|
||||
return;
|
||||
}
|
||||
Dept dept = TransportBusinessSupport.currentDept("运输计划");
|
||||
if (dept == null || dept.getId() == null) {
|
||||
return;
|
||||
}
|
||||
update(Wrappers.<TransportPlan>lambdaUpdate()
|
||||
.set(TransportPlan::getDeptId, dept.getId())
|
||||
.set(TransportPlan::getDeptName, dept.getDeptName())
|
||||
.isNull(TransportPlan::getDeptId)
|
||||
.eq(TransportPlan::getCreateDept, dept.getId())
|
||||
.eq(TransportPlan::getIsDeleted, 0));
|
||||
}
|
||||
|
||||
private LambdaQueryWrapper<TransportPlan> buildQuery(TransportPlanVO transportPlan) {
|
||||
TransportBusinessSupport.validateAllDept(transportPlan.getAllDept(), "运输计划");
|
||||
LambdaQueryWrapper<TransportPlan> queryWrapper = Wrappers.<TransportPlan>lambdaQuery().eq(TransportPlan::getIsDeleted, 0);
|
||||
if (!Objects.equals(transportPlan.getAllDept(), 1)) {
|
||||
queryWrapper.eq(TransportPlan::getDeptId, TransportBusinessSupport.currentDeptId("运输计划"));
|
||||
Long currentDeptId = TransportBusinessSupport.currentDeptId("运输计划");
|
||||
// dept_id 为空的历史数据按创建组织归入当前组织
|
||||
queryWrapper.and(wrapper -> wrapper.eq(TransportPlan::getDeptId, currentDeptId)
|
||||
.or(item -> item.isNull(TransportPlan::getDeptId).eq(TransportPlan::getCreateDept, currentDeptId)));
|
||||
} else if (Func.isNotEmpty(transportPlan.getDeptId())) {
|
||||
queryWrapper.eq(TransportPlan::getDeptId, transportPlan.getDeptId());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user