✨ 新增结算规则定时生成,并支持付款申请多结算单与导入停用费用项提示
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+3
-2
@@ -309,12 +309,13 @@ public interface ISysClient {
|
||||
R<Region> getRegion(@RequestParam("code") String code);
|
||||
|
||||
/**
|
||||
* 获取启用的费用项
|
||||
* 获取费用项。默认仅返回启用档案;includeDisabled 为 true 时同时返回停用档案。
|
||||
*
|
||||
* @param includeDisabled 是否包含停用费用项
|
||||
* @return 费用项集合
|
||||
*/
|
||||
@GetMapping(FEE_ITEMS)
|
||||
R<List<FeeItem>> getFeeItems();
|
||||
R<List<FeeItem>> getFeeItems(@RequestParam(value = "includeDisabled", required = false) Boolean includeDisabled);
|
||||
|
||||
@GetMapping(CARGO_TYPES)
|
||||
R<List<CargoType>> getCargoTypes();
|
||||
|
||||
+1
-1
@@ -165,7 +165,7 @@ public class ISysClientFallback implements ISysClient {
|
||||
}
|
||||
|
||||
@Override
|
||||
public R<List<FeeItem>> getFeeItems() {
|
||||
public R<List<FeeItem>> getFeeItems(Boolean includeDisabled) {
|
||||
return R.fail("获取数据失败");
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -35,6 +35,7 @@ public class PaymentApplicationSaveRequest implements Serializable {
|
||||
private Long settlementId;
|
||||
private List<Long> settlementIds;
|
||||
private Long preSettlementId;
|
||||
private List<Long> preSettlementIds;
|
||||
private Long projectId;
|
||||
private String projectName;
|
||||
private Long deptId;
|
||||
|
||||
+6
@@ -1,5 +1,6 @@
|
||||
package org.springblade.transport.pojo.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
@@ -17,7 +18,12 @@ public class PaymentApplicationSettlement extends TenantEntity {
|
||||
private Long paymentApplicationId;
|
||||
private Long formalSettlementId;
|
||||
private String formalSettlementNo;
|
||||
private Long preSettlementId;
|
||||
private String preSettlementNo;
|
||||
private BigDecimal settlementAmount;
|
||||
private BigDecimal appliedAmount;
|
||||
private BigDecimal paidAmount;
|
||||
/** 查看时回填的可付款金额,不落库 */
|
||||
@TableField(exist = false)
|
||||
private BigDecimal payableAmount;
|
||||
}
|
||||
|
||||
+3
-2
@@ -32,6 +32,7 @@ import org.springblade.core.tool.api.R;
|
||||
import org.springblade.system.pojo.entity.*;
|
||||
import org.springblade.system.service.*;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
|
||||
@@ -218,9 +219,9 @@ public class SysClient implements ISysClient {
|
||||
|
||||
@Override
|
||||
@GetMapping(FEE_ITEMS)
|
||||
public R<List<FeeItem>> getFeeItems() {
|
||||
public R<List<FeeItem>> getFeeItems(@RequestParam(value = "includeDisabled", required = false) Boolean includeDisabled) {
|
||||
return R.data(feeItemService.list(Wrappers.<FeeItem>lambdaQuery()
|
||||
.eq(FeeItem::getStatus, 1)
|
||||
.eq(!Boolean.TRUE.equals(includeDisabled), FeeItem::getStatus, 1)
|
||||
.eq(FeeItem::getIsDeleted, 0)
|
||||
.orderByAsc(FeeItem::getFeeCategory, FeeItem::getName)));
|
||||
}
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.service;
|
||||
|
||||
/**
|
||||
* 按合同结算规则自动生成预结算单、正式结算单
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
public interface ISettlementRuleAutoGenerateService {
|
||||
|
||||
/**
|
||||
* 扫描已开启自动生成的合同,为截至昨日已关闭的账期生成结算草稿。
|
||||
*
|
||||
* @return 本次生成结果摘要
|
||||
*/
|
||||
String generateDueSettlements();
|
||||
|
||||
}
|
||||
+193
-23
@@ -116,10 +116,12 @@ import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -257,10 +259,20 @@ 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()
|
||||
List<PaymentApplicationSettlement> settlements = settlementRelationMapper.selectList(Wrappers.<PaymentApplicationSettlement>lambdaQuery()
|
||||
.eq(PaymentApplicationSettlement::getPaymentApplicationId, id)
|
||||
.eq(PaymentApplicationSettlement::getIsDeleted, 0)
|
||||
.orderByAsc(PaymentApplicationSettlement::getCreateTime)));
|
||||
.orderByAsc(PaymentApplicationSettlement::getCreateTime));
|
||||
for (PaymentApplicationSettlement relation : settlements) {
|
||||
if (relation.getPreSettlementId() != null) {
|
||||
relation.setPayableAmount(payableOfPreSettlement(relation.getPreSettlementId(), relation.getSettlementAmount(), id)
|
||||
.setScale(2, RoundingMode.HALF_UP));
|
||||
} else if (relation.getFormalSettlementId() != null) {
|
||||
relation.setPayableAmount(payableOfFormalSettlement(relation.getFormalSettlementId(), relation.getSettlementAmount(), id)
|
||||
.setScale(2, RoundingMode.HALF_UP));
|
||||
}
|
||||
}
|
||||
vo.setSettlements(settlements);
|
||||
return vo;
|
||||
}
|
||||
|
||||
@@ -359,9 +371,15 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
&& request.getSettlementIds() != null && !request.getSettlementIds().isEmpty()) {
|
||||
request.setSettlementId(request.getSettlementIds().get(0));
|
||||
}
|
||||
List<Long> preSettlementIds = resolvePreSettlementIds(request);
|
||||
if (request.getPreSettlementId() == null && !preSettlementIds.isEmpty()) {
|
||||
request.setPreSettlementId(preSettlementIds.get(0));
|
||||
}
|
||||
fillReference(entity, request);
|
||||
List<FormalSettlement> formalSettlements = resolveFormalSettlements(request, entity);
|
||||
if (formalSettlements.size() > 1) fillFormalAggregateReference(entity, formalSettlements);
|
||||
List<PreSettlement> preSettlements = resolvePreSettlements(preSettlementIds);
|
||||
if (preSettlements.size() > 1) fillPreAggregateReference(entity, preSettlements);
|
||||
fillBillLedger(entity, request);
|
||||
validateQuota(entity);
|
||||
saveOrUpdate(entity);
|
||||
@@ -396,6 +414,7 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
entity.setPaidAmount(paidAmount);
|
||||
updateById(entity);
|
||||
saveSettlementRelations(entity, formalSettlements);
|
||||
savePreSettlementRelations(entity, preSettlements);
|
||||
return entity.getId();
|
||||
}
|
||||
|
||||
@@ -1556,18 +1575,38 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
}
|
||||
|
||||
private void syncPreSettlementAdvance(PaymentApplication entity) {
|
||||
if (entity.getPreSettlementId() == null) return;
|
||||
if (!List.of("progress_advance", "settlement_payment").contains(entity.getPaymentType())) return;
|
||||
List<PaymentApplicationSettlement> relations = settlementRelationMapper.selectList(
|
||||
Wrappers.<PaymentApplicationSettlement>lambdaQuery()
|
||||
.eq(PaymentApplicationSettlement::getPaymentApplicationId, entity.getId())
|
||||
.eq(PaymentApplicationSettlement::getIsDeleted, 0)
|
||||
.isNotNull(PaymentApplicationSettlement::getPreSettlementId));
|
||||
if (relations.isEmpty()) {
|
||||
if (entity.getPreSettlementId() == null) return;
|
||||
upsertPreSettlementAdvance(entity.getPreSettlementId(), entity.getPaymentNo(), money(entity.getAppliedAmount()), entity);
|
||||
refreshPreSettlementPaidLinks(entity.getPreSettlementId());
|
||||
return;
|
||||
}
|
||||
for (PaymentApplicationSettlement relation : relations) {
|
||||
String advanceNo = relations.size() == 1 ? entity.getPaymentNo()
|
||||
: trimText(entity.getPaymentNo() + "-" + relation.getPreSettlementNo(), 100);
|
||||
upsertPreSettlementAdvance(relation.getPreSettlementId(), advanceNo, money(relation.getAppliedAmount()), entity);
|
||||
refreshPreSettlementPaidLinks(relation.getPreSettlementId());
|
||||
}
|
||||
}
|
||||
|
||||
private void upsertPreSettlementAdvance(Long preSettlementId, String advanceNo, BigDecimal appliedAmount,
|
||||
PaymentApplication entity) {
|
||||
PreSettlementAdvance advance = preSettlementAdvanceMapper.selectList(
|
||||
Wrappers.<PreSettlementAdvance>lambdaQuery()
|
||||
.eq(PreSettlementAdvance::getPreSettlementId, entity.getPreSettlementId())
|
||||
.eq(PreSettlementAdvance::getPreSettlementId, preSettlementId)
|
||||
.eq(PreSettlementAdvance::getIsDeleted, 0)
|
||||
.ne(PreSettlementAdvance::getBillStatus, TransportBusiConstant.VOIDED)
|
||||
.eq(PreSettlementAdvance::getAdvanceNo, entity.getPaymentNo())
|
||||
.eq(PreSettlementAdvance::getAdvanceNo, advanceNo)
|
||||
.last("limit 1")).stream().findFirst().orElse(null);
|
||||
if (advance == null) {
|
||||
if (advance == null && Objects.equals(advanceNo, entity.getPaymentNo())) {
|
||||
advance = preSettlementAdvanceMapper.selectList(Wrappers.<PreSettlementAdvance>lambdaQuery()
|
||||
.eq(PreSettlementAdvance::getPreSettlementId, entity.getPreSettlementId())
|
||||
.eq(PreSettlementAdvance::getPreSettlementId, preSettlementId)
|
||||
.eq(PreSettlementAdvance::getIsDeleted, 0)
|
||||
.ne(PreSettlementAdvance::getBillStatus, TransportBusiConstant.VOIDED)
|
||||
.eq(PreSettlementAdvance::getPaidAmount, BigDecimal.ZERO)
|
||||
@@ -1576,24 +1615,34 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
}
|
||||
if (advance == null) {
|
||||
advance = new PreSettlementAdvance();
|
||||
advance.setPreSettlementId(entity.getPreSettlementId());
|
||||
advance.setAdvanceNo(entity.getPaymentNo());
|
||||
advance.setAppliedAmount(money(entity.getAppliedAmount()));
|
||||
advance.setPreSettlementId(preSettlementId);
|
||||
advance.setAdvanceNo(advanceNo);
|
||||
advance.setAppliedAmount(money(appliedAmount));
|
||||
}
|
||||
advance.setPaidAmount(money(entity.getAppliedAmount()));
|
||||
advance.setPaidAmount(money(appliedAmount));
|
||||
advance.setBillStatus("paid");
|
||||
advance.setKingdeeAdvanceNo(entity.getKingdeeBillNo());
|
||||
if (advance.getId() == null) preSettlementAdvanceMapper.insert(advance);
|
||||
else preSettlementAdvanceMapper.updateById(advance);
|
||||
refreshPreSettlementAdvanceSummary(entity.getPreSettlementId());
|
||||
}
|
||||
|
||||
private void refreshPreSettlementPaidLinks(Long preSettlementId) {
|
||||
refreshPreSettlementAdvanceSummary(preSettlementId);
|
||||
formalSettlementSourceMapper.selectList(Wrappers.<FormalSettlementSource>lambdaQuery()
|
||||
.eq(FormalSettlementSource::getPreSettlementId, entity.getPreSettlementId())
|
||||
.eq(FormalSettlementSource::getPreSettlementId, preSettlementId)
|
||||
.eq(FormalSettlementSource::getIsDeleted, 0)).forEach(source -> {
|
||||
source.setAdvanceAppliedAmount(findPreSettlementApplied(entity.getPreSettlementId()));
|
||||
source.setAdvancePaidAmount(findPreSettlementPaid(entity.getPreSettlementId()));
|
||||
source.setAdvanceAppliedAmount(findPreSettlementApplied(preSettlementId));
|
||||
source.setAdvancePaidAmount(findPreSettlementPaid(preSettlementId));
|
||||
formalSettlementSourceMapper.updateById(source);
|
||||
});
|
||||
formalSettlementService.refreshPaymentSummariesForPreSettlement(entity.getPreSettlementId());
|
||||
formalSettlementService.refreshPaymentSummariesForPreSettlement(preSettlementId);
|
||||
}
|
||||
|
||||
private String trimText(String value, int length) {
|
||||
if (value == null || value.length() <= length) {
|
||||
return value;
|
||||
}
|
||||
return value.substring(0, length);
|
||||
}
|
||||
|
||||
private BigDecimal findPreSettlementPaid(Long preSettlementId) {
|
||||
@@ -1683,11 +1732,14 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
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 && Func.isEmpty(request.getSettlementIds())) throw new ServiceException("非项目预付必须关联结算单");
|
||||
&& request.getPreSettlementId() == null && Func.isEmpty(request.getSettlementIds())
|
||||
&& Func.isEmpty(request.getPreSettlementIds())) 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 ("progress_advance".equals(request.getPaymentType()) && request.getPreSettlementId() == null
|
||||
&& Func.isEmpty(request.getPreSettlementIds())) throw new ServiceException("进度预付必须关联预结算单");
|
||||
if (TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT.equals(request.getPaymentType()) && request.getSettlementId() == null
|
||||
&& Func.isEmpty(request.getSettlementIds()) && request.getPreSettlementId() == null) {
|
||||
&& Func.isEmpty(request.getSettlementIds()) && request.getPreSettlementId() == null
|
||||
&& Func.isEmpty(request.getPreSettlementIds())) {
|
||||
throw new ServiceException("尾款付款必须关联正式结算单或预结算单");
|
||||
}
|
||||
}
|
||||
@@ -1764,7 +1816,7 @@ 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 (!hasMultipleFormalReferences(request)
|
||||
if (!hasMultipleFormalReferences(request) && !hasMultiplePreReferences(request)
|
||||
&& money(entity.getAppliedAmount()).compareTo(money(entity.getPayableAmount())) > 0
|
||||
&& money(entity.getPayableAmount()).compareTo(BigDecimal.ZERO) > 0) throw new ServiceException("申请付款金额不能超过可付款金额");
|
||||
}
|
||||
@@ -1830,6 +1882,98 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
}
|
||||
}
|
||||
|
||||
private List<Long> resolvePreSettlementIds(PaymentApplicationSaveRequest request) {
|
||||
if (!List.of("progress_advance", TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT).contains(request.getPaymentType())) {
|
||||
return List.of();
|
||||
}
|
||||
boolean formalSelected = TransportBusiConstant.PAYMENT_TYPE_SETTLEMENT.equals(request.getPaymentType())
|
||||
&& ((request.getSettlementIds() != null && request.getSettlementIds().stream().anyMatch(Objects::nonNull))
|
||||
|| request.getSettlementId() != null)
|
||||
&& Func.isEmpty(request.getPreSettlementIds());
|
||||
if (formalSelected) {
|
||||
return List.of();
|
||||
}
|
||||
List<Long> ids = request.getPreSettlementIds() == null ? List.of() : request.getPreSettlementIds();
|
||||
ids = ids.stream().filter(Objects::nonNull).distinct().toList();
|
||||
if (ids.isEmpty() && request.getPreSettlementId() != null) {
|
||||
return List.of(request.getPreSettlementId());
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
private boolean hasMultiplePreReferences(PaymentApplicationSaveRequest request) {
|
||||
return request.getPreSettlementIds() != null
|
||||
&& request.getPreSettlementIds().stream().filter(Objects::nonNull).distinct().count() > 1;
|
||||
}
|
||||
|
||||
private List<PreSettlement> resolvePreSettlements(List<Long> ids) {
|
||||
if (ids.size() <= 1) {
|
||||
return List.of();
|
||||
}
|
||||
List<PreSettlement> settlements = ids.stream().map(preSettlementMapper::selectById).toList();
|
||||
if (settlements.stream().anyMatch(item -> item == null || Objects.equals(item.getIsDeleted(), 1)
|
||||
|| !TransportBusiConstant.APPROVED.equals(item.getApprovalStatus()))) {
|
||||
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 void fillPreAggregateReference(PaymentApplication entity, List<PreSettlement> settlements) {
|
||||
BigDecimal settlementAmount = settlements.stream().map(PreSettlement::getSettlementAmount).map(this::money)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_UP);
|
||||
BigDecimal payableAmount = settlements.stream()
|
||||
.map(item -> payableOfPreSettlement(item.getId(), item.getSettlementAmount(), entity.getId()))
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add).setScale(2, RoundingMode.HALF_UP);
|
||||
if (money(entity.getAppliedAmount()).compareTo(payableAmount) > 0) {
|
||||
throw new ServiceException("申请付款金额不能超过所选预结算单可付款金额合计");
|
||||
}
|
||||
PreSettlement first = settlements.get(0);
|
||||
String preSettlementNo = settlements.stream().map(PreSettlement::getPreSettlementNo).filter(Objects::nonNull)
|
||||
.collect(Collectors.joining("、"));
|
||||
entity.setPreSettlementId(first.getId());
|
||||
entity.setPreSettlementNo(preSettlementNo.length() > 500 ? preSettlementNo.substring(0, 500) : preSettlementNo);
|
||||
entity.setSettlementId(null);
|
||||
entity.setSettlementNo(null);
|
||||
entity.setSettlementAmount(settlementAmount);
|
||||
entity.setPayableAmount(payableAmount);
|
||||
entity.setBillType("预结算单");
|
||||
}
|
||||
|
||||
private void savePreSettlementRelations(PaymentApplication entity, List<PreSettlement> settlements) {
|
||||
if (settlements.size() > 1) {
|
||||
settlementRelationMapper.delete(Wrappers.<PaymentApplicationSettlement>lambdaQuery()
|
||||
.eq(PaymentApplicationSettlement::getPaymentApplicationId, entity.getId()));
|
||||
} else {
|
||||
settlementRelationMapper.delete(Wrappers.<PaymentApplicationSettlement>lambdaQuery()
|
||||
.eq(PaymentApplicationSettlement::getPaymentApplicationId, entity.getId())
|
||||
.isNotNull(PaymentApplicationSettlement::getPreSettlementId));
|
||||
return;
|
||||
}
|
||||
BigDecimal totalBase = settlements.stream()
|
||||
.map(item -> payableOfPreSettlement(item.getId(), item.getSettlementAmount(), entity.getId()))
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
BigDecimal remainingApplied = money(entity.getAppliedAmount());
|
||||
for (int index = 0; index < settlements.size(); index++) {
|
||||
PreSettlement settlement = settlements.get(index);
|
||||
BigDecimal base = payableOfPreSettlement(settlement.getId(), settlement.getSettlementAmount(), entity.getId());
|
||||
BigDecimal applied = index == settlements.size() - 1 || totalBase.compareTo(BigDecimal.ZERO) == 0
|
||||
? remainingApplied : money(entity.getAppliedAmount()).multiply(base).divide(totalBase, 2, RoundingMode.DOWN);
|
||||
PaymentApplicationSettlement relation = new PaymentApplicationSettlement();
|
||||
relation.setPaymentApplicationId(entity.getId());
|
||||
relation.setPreSettlementId(settlement.getId());
|
||||
relation.setPreSettlementNo(settlement.getPreSettlementNo());
|
||||
relation.setSettlementAmount(money(settlement.getSettlementAmount()).setScale(2, RoundingMode.HALF_UP));
|
||||
relation.setAppliedAmount(applied.setScale(2, RoundingMode.HALF_UP));
|
||||
relation.setPaidAmount(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP));
|
||||
settlementRelationMapper.insert(relation);
|
||||
remainingApplied = remainingApplied.subtract(applied);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateQuota(PaymentApplication entity) {
|
||||
if (!"project_advance".equals(entity.getPaymentType())) return;
|
||||
BigDecimal appliedAmount = money(entity.getAppliedAmount());
|
||||
@@ -1888,10 +2032,36 @@ public class PaymentApplicationServiceImpl extends BaseServiceImpl<PaymentApplic
|
||||
}
|
||||
|
||||
private BigDecimal cumulativePreSettlementApplied(Long preSettlementId, Long excludeId) {
|
||||
// 进度预付、尾款付款关联同一预结算单时累计占用可付款额度
|
||||
return sumApplied(Wrappers.<PaymentApplication>lambdaQuery()
|
||||
// 进度预付、尾款付款关联同一预结算单时累计占用可付款额度;多选时按关联行的申请金额累计,避免把整单金额算到第一张预结算上
|
||||
List<PaymentApplicationSettlement> relations = settlementRelationMapper.selectList(
|
||||
Wrappers.<PaymentApplicationSettlement>lambdaQuery()
|
||||
.eq(PaymentApplicationSettlement::getPreSettlementId, preSettlementId)
|
||||
.eq(PaymentApplicationSettlement::getIsDeleted, 0));
|
||||
Set<Long> coveredPaymentIds = new HashSet<>();
|
||||
BigDecimal relationAmount = BigDecimal.ZERO;
|
||||
if (!relations.isEmpty()) {
|
||||
Map<Long, PaymentApplication> payments = listByIds(relations.stream()
|
||||
.map(PaymentApplicationSettlement::getPaymentApplicationId).filter(Objects::nonNull).distinct().toList())
|
||||
.stream().collect(Collectors.toMap(PaymentApplication::getId, item -> item, (left, right) -> left));
|
||||
for (PaymentApplicationSettlement relation : relations) {
|
||||
PaymentApplication payment = payments.get(relation.getPaymentApplicationId());
|
||||
if (payment == null || Objects.equals(payment.getIsDeleted(), 1)) {
|
||||
continue;
|
||||
}
|
||||
coveredPaymentIds.add(payment.getId());
|
||||
if (Objects.equals(payment.getId(), excludeId) || TransportBusiConstant.VOIDED.equals(payment.getApprovalStatus())) {
|
||||
continue;
|
||||
}
|
||||
relationAmount = relationAmount.add(money(relation.getAppliedAmount()));
|
||||
}
|
||||
}
|
||||
BigDecimal legacyAmount = list(Wrappers.<PaymentApplication>lambdaQuery()
|
||||
.eq(PaymentApplication::getPreSettlementId, preSettlementId)
|
||||
.ne(excludeId != null, PaymentApplication::getId, excludeId));
|
||||
.ne(excludeId != null, PaymentApplication::getId, excludeId)
|
||||
.notIn(!coveredPaymentIds.isEmpty(), PaymentApplication::getId, coveredPaymentIds)
|
||||
.ne(PaymentApplication::getApprovalStatus, TransportBusiConstant.VOIDED)).stream()
|
||||
.map(PaymentApplication::getAppliedAmount).map(this::money).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
return relationAmount.add(legacyAmount);
|
||||
}
|
||||
|
||||
/** 正式结算可付款 = 结算金额 − 已申请付款 − 项目预付核销 */
|
||||
|
||||
+1
-1
@@ -166,7 +166,7 @@ public class PreSettlementServiceImpl extends BaseServiceImpl<PreSettlementMappe
|
||||
Map<String, String> categoryNames = DictBizCache.getList("fee_category").stream()
|
||||
.collect(Collectors.toMap(DictBiz::getDictKey, DictBiz::getDictValue, (first, second) -> first,
|
||||
LinkedHashMap::new));
|
||||
org.springblade.core.tool.api.R<List<FeeItem>> response = sysClient.getFeeItems();
|
||||
org.springblade.core.tool.api.R<List<FeeItem>> response = sysClient.getFeeItems(false);
|
||||
if (response == null || !response.isSuccess() || response.getData() == null) {
|
||||
throw new ServiceException("费用项基础档案读取失败,请稍后重试");
|
||||
}
|
||||
|
||||
+34
-14
@@ -703,7 +703,9 @@ public class SettlementImportBatchServiceImpl
|
||||
|
||||
private Map<Integer, String> validateSettlementExtras(SettlementImportBatchRequest request) {
|
||||
Map<Integer, String> errorMap = new LinkedHashMap<>();
|
||||
Set<String> feeItemCatalog = loadFeeItemNames();
|
||||
Set<String> enabledFeeItemNames = new HashSet<>();
|
||||
Set<String> disabledFeeItemNames = new HashSet<>();
|
||||
loadFeeItemNames(enabledFeeItemNames, disabledFeeItemNames);
|
||||
List<String> feeItemNames = resolveFeeItemNames(request);
|
||||
List<Map<String, Object>> rows = request.getRows();
|
||||
for (int index = 0; index < rows.size(); index++) {
|
||||
@@ -716,15 +718,21 @@ public class SettlementImportBatchServiceImpl
|
||||
errors.add("*运输费必须大于等于0");
|
||||
}
|
||||
Map<String, BigDecimal> feeItems = extractFeeItems(row, feeItemNames);
|
||||
for (String name : feeItems.keySet()) {
|
||||
if (!feeItemCatalog.contains(name)) {
|
||||
errors.add("费用项「" + name + "」未在费用项主数据中匹配");
|
||||
for (Map.Entry<String, BigDecimal> entry : feeItems.entrySet()) {
|
||||
String name = normalizeFeeName(entry.getKey());
|
||||
BigDecimal amount = entry.getValue() == null ? BigDecimal.ZERO : entry.getValue();
|
||||
// 模板自带的装卸费、过路费等列未填写时金额为 0,不要求费用项主数据存在同名档案
|
||||
if (Func.isEmpty(name) || amount.compareTo(BigDecimal.ZERO) == 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (String name : feeItemNames) {
|
||||
if (Func.isNotEmpty(name) && !feeItemCatalog.contains(name)) {
|
||||
errors.add("费用项「" + name + "」未在费用项主数据中匹配");
|
||||
if (enabledFeeItemNames.contains(name)) {
|
||||
continue;
|
||||
}
|
||||
if (disabledFeeItemNames.contains(name)) {
|
||||
errors.add("费用项「" + name + "」已停用");
|
||||
continue;
|
||||
}
|
||||
errors.add("费用项「" + name + "」未在费用项主数据中匹配");
|
||||
}
|
||||
if (!errors.isEmpty()) {
|
||||
errorMap.put(index, String.join("; ", errors));
|
||||
@@ -733,15 +741,27 @@ public class SettlementImportBatchServiceImpl
|
||||
return errorMap;
|
||||
}
|
||||
|
||||
private Set<String> loadFeeItemNames() {
|
||||
R<List<FeeItem>> response = sysClient.getFeeItems();
|
||||
private void loadFeeItemNames(Set<String> enabledFeeItemNames, Set<String> disabledFeeItemNames) {
|
||||
R<List<FeeItem>> response = sysClient.getFeeItems(true);
|
||||
if (response == null || !response.isSuccess() || response.getData() == null) {
|
||||
throw new ServiceException("费用项基础档案读取失败,请稍后重试");
|
||||
}
|
||||
return response.getData().stream()
|
||||
.map(FeeItem::getName)
|
||||
.filter(Func::isNotEmpty)
|
||||
.collect(Collectors.toCollection(HashSet::new));
|
||||
for (FeeItem feeItem : response.getData()) {
|
||||
String name = normalizeFeeName(feeItem.getName());
|
||||
if (Func.isEmpty(name)) {
|
||||
continue;
|
||||
}
|
||||
if (feeItem.getStatus() == null || Objects.equals(feeItem.getStatus(), 1)) {
|
||||
enabledFeeItemNames.add(name);
|
||||
} else {
|
||||
disabledFeeItemNames.add(name);
|
||||
}
|
||||
}
|
||||
disabledFeeItemNames.removeAll(enabledFeeItemNames);
|
||||
}
|
||||
|
||||
private String normalizeFeeName(String name) {
|
||||
return name == null ? "" : name.trim();
|
||||
}
|
||||
|
||||
private List<String> resolveFeeItemNames(SettlementImportBatchRequest request) {
|
||||
|
||||
+622
@@ -0,0 +1,622 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.core.tenant.TenantUtil;
|
||||
import org.springblade.core.tool.jackson.JsonUtil;
|
||||
import org.springblade.core.tool.utils.Func;
|
||||
import org.springblade.transport.constant.TransportBusiConstant;
|
||||
import org.springblade.transport.mapper.PreSettlementDetailMapper;
|
||||
import org.springblade.transport.mapper.ReceivablePayableDetailMapper;
|
||||
import org.springblade.transport.pojo.dto.FormalSettlementSaveRequest;
|
||||
import org.springblade.transport.pojo.dto.PreSettlementSaveRequest;
|
||||
import org.springblade.transport.pojo.entity.ContractManage;
|
||||
import org.springblade.transport.pojo.entity.PreSettlement;
|
||||
import org.springblade.transport.pojo.entity.PreSettlementDetail;
|
||||
import org.springblade.transport.pojo.entity.ReceivablePayableDetail;
|
||||
import org.springblade.transport.service.IContractManageService;
|
||||
import org.springblade.transport.service.IFormalSettlementService;
|
||||
import org.springblade.transport.service.IPreSettlementService;
|
||||
import org.springblade.transport.service.ISettlementRuleAutoGenerateService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.ZoneId;
|
||||
import java.time.YearMonth;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 按合同结算规则自动生成结算草稿。
|
||||
* <p>
|
||||
* 账期在次日零点视为关闭:日结/现结取前一日,周结和固定天数从账单开始日期顺延,
|
||||
* 月结按自然月、固定截单日或自定义多周期截取。没有可归集明细时不生成空单。
|
||||
* 同一明细只进入一张结算单,预结算先于正式结算执行。正式结算同时归集费用日期完整落在账期内、且尚未转正式结算的已审批预结算单。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class SettlementRuleAutoGenerateServiceImpl implements ISettlementRuleAutoGenerateService {
|
||||
|
||||
private static final int QUERY_BATCH_SIZE = 500;
|
||||
|
||||
private static final String LOCAL_CURRENCY = "RMB";
|
||||
|
||||
private final IContractManageService contractManageService;
|
||||
|
||||
private final IPreSettlementService preSettlementService;
|
||||
|
||||
private final IFormalSettlementService formalSettlementService;
|
||||
|
||||
private final ReceivablePayableDetailMapper receivablePayableDetailMapper;
|
||||
|
||||
private final PreSettlementDetailMapper preSettlementDetailMapper;
|
||||
|
||||
private final PlatformTransactionManager transactionManager;
|
||||
|
||||
@Override
|
||||
public synchronized String generateDueSettlements() {
|
||||
LocalDate today = LocalDate.now();
|
||||
List<ContractManage> contracts = TenantUtil.ignore(this::listAutoContracts);
|
||||
int preCount = 0;
|
||||
int formalCount = 0;
|
||||
int failedCount = 0;
|
||||
for (ContractManage contract : contracts) {
|
||||
if (Func.isBlank(contract.getTenantId())) {
|
||||
log.warn("合同[{}]缺少租户,跳过自动生成结算单", contract.getContractNo());
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
int[] created = TenantUtil.use(contract.getTenantId(), () -> generateContract(contract, today));
|
||||
preCount += created[0];
|
||||
formalCount += created[1];
|
||||
} catch (Exception exception) {
|
||||
failedCount++;
|
||||
log.error("合同[{}]自动生成结算单失败", contract.getContractNo(), exception);
|
||||
}
|
||||
}
|
||||
return "扫描合同" + contracts.size() + "份,生成预结算单" + preCount + "张,正式结算单" + formalCount
|
||||
+ "张,失败" + failedCount + "份";
|
||||
}
|
||||
|
||||
private List<ContractManage> listAutoContracts() {
|
||||
return contractManageService.list(Wrappers.<ContractManage>lambdaQuery()
|
||||
.in(ContractManage::getApprovalStatus, "approved", "change_approved")
|
||||
.in(ContractManage::getContractCategory, "客户合同", "承运商合同")
|
||||
.and(query -> query.isNull(ContractManage::getContractStage)
|
||||
.or().ne(ContractManage::getContractStage, "terminated"))
|
||||
.and(query -> query.like(ContractManage::getPreSettlementConfigJson, "autoGenerate")
|
||||
.or().like(ContractManage::getFormalSettlementConfigJson, "autoGenerate")
|
||||
.or().like(ContractManage::getSettlementRuleJson, "autoGenerate")));
|
||||
}
|
||||
|
||||
private int[] generateContract(ContractManage contract, LocalDate today) {
|
||||
String settlementType = contractSettlementType(contract);
|
||||
if (settlementType == null || contract.getProjectId() == null || contract.getOrganizationId() == null) {
|
||||
log.warn("合同[{}]缺少结算类型、项目或所属组织,跳过自动生成", contract.getContractNo());
|
||||
return new int[]{0, 0};
|
||||
}
|
||||
int preCount = generateBills(contract, settlementType, resolveRule(contract, true), today, true);
|
||||
int formalCount = generateBills(contract, settlementType, resolveRule(contract, false), today, false);
|
||||
return new int[]{preCount, formalCount};
|
||||
}
|
||||
|
||||
private int generateBills(ContractManage contract, String settlementType, Map<String, Object> rule,
|
||||
LocalDate today, boolean preBill) {
|
||||
if (!enabled(rule.get("autoGenerate"))) {
|
||||
return 0;
|
||||
}
|
||||
String cycleName = Func.toStr(rule.get("settlementType")).trim();
|
||||
if (!knownCycle(cycleName)) {
|
||||
log.warn("合同[{}]{}结算周期[{}]无法识别,跳过自动生成", contract.getContractNo(),
|
||||
preBill ? "预" : "正式", cycleName);
|
||||
return 0;
|
||||
}
|
||||
List<DateRange> ranges = closedPeriods(rule, today);
|
||||
if (ranges.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
if (preBill) {
|
||||
return createPreBills(contract, ranges);
|
||||
}
|
||||
return createFormalBills(contract, settlementType, ranges);
|
||||
}
|
||||
|
||||
private int createPreBills(ContractManage contract, List<DateRange> ranges) {
|
||||
List<ReceivablePayableDetail> details = loadPendingDetails(contract, ranges);
|
||||
Map<DateRange, List<ReceivablePayableDetail>> grouped = groupDetails(ranges, details);
|
||||
int created = 0;
|
||||
for (Map.Entry<DateRange, List<ReceivablePayableDetail>> entry : grouped.entrySet()) {
|
||||
Map<String, List<Long>> byCurrency = new LinkedHashMap<>();
|
||||
for (ReceivablePayableDetail detail : entry.getValue()) {
|
||||
String currency = Func.isEmpty(detail.getCurrency()) ? LOCAL_CURRENCY : detail.getCurrency();
|
||||
byCurrency.computeIfAbsent(currency, key -> new ArrayList<>()).add(detail.getId());
|
||||
}
|
||||
for (List<Long> detailIds : byCurrency.values()) {
|
||||
try {
|
||||
inTransaction(() -> {
|
||||
PreSettlementSaveRequest request = new PreSettlementSaveRequest();
|
||||
request.setContractId(contract.getId());
|
||||
request.setSourceDetailIds(detailIds);
|
||||
request.setRemark(periodRemark(entry.getKey()));
|
||||
preSettlementService.saveDraft(request);
|
||||
});
|
||||
created++;
|
||||
log.info("合同[{}]自动生成预结算单,账期{}至{},明细{}条", contract.getContractNo(),
|
||||
entry.getKey().start(), entry.getKey().end(), detailIds.size());
|
||||
} catch (Exception exception) {
|
||||
log.error("合同[{}]自动生成预结算单失败,账期{}至{}", contract.getContractNo(),
|
||||
entry.getKey().start(), entry.getKey().end(), exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
return created;
|
||||
}
|
||||
|
||||
private int createFormalBills(ContractManage contract, String settlementType, List<DateRange> ranges) {
|
||||
Map<DateRange, List<ReceivablePayableDetail>> detailGroups = groupDetails(ranges, loadPendingDetails(contract, ranges));
|
||||
Map<DateRange, List<PreSettlement>> preGroups = groupApprovedPres(contract, settlementType, ranges);
|
||||
int created = 0;
|
||||
for (DateRange range : ranges) {
|
||||
Map<String, List<Long>> detailIds = new LinkedHashMap<>();
|
||||
for (ReceivablePayableDetail detail : detailGroups.getOrDefault(range, List.of())) {
|
||||
String currency = detail.getCurrency() == null ? "" : detail.getCurrency();
|
||||
detailIds.computeIfAbsent(currency, key -> new ArrayList<>()).add(detail.getId());
|
||||
}
|
||||
Map<String, List<Long>> preIds = new LinkedHashMap<>();
|
||||
for (PreSettlement preSettlement : preGroups.getOrDefault(range, List.of())) {
|
||||
String currency = preSettlement.getCurrency() == null ? "" : preSettlement.getCurrency();
|
||||
preIds.computeIfAbsent(currency, key -> new ArrayList<>()).add(preSettlement.getId());
|
||||
}
|
||||
Set<String> currencies = new LinkedHashSet<>();
|
||||
currencies.addAll(detailIds.keySet());
|
||||
currencies.addAll(preIds.keySet());
|
||||
for (String currency : currencies) {
|
||||
List<Long> sourcePreSettlementIds = preIds.getOrDefault(currency, List.of());
|
||||
List<Long> sourceDetailIds = detailIds.getOrDefault(currency, List.of());
|
||||
if (sourcePreSettlementIds.isEmpty() && sourceDetailIds.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
inTransaction(() -> {
|
||||
FormalSettlementSaveRequest request = new FormalSettlementSaveRequest();
|
||||
request.setContractId(contract.getId());
|
||||
request.setSettlementType(settlementType);
|
||||
request.setSourcePreSettlementIds(sourcePreSettlementIds.isEmpty() ? null : sourcePreSettlementIds);
|
||||
request.setSourceDetailIds(sourceDetailIds.isEmpty() ? null : sourceDetailIds);
|
||||
request.setRemark(periodRemark(range));
|
||||
formalSettlementService.saveDraft(request);
|
||||
});
|
||||
created++;
|
||||
log.info("合同[{}]自动生成正式结算单,账期{}至{},预结算{}张,明细{}条", contract.getContractNo(),
|
||||
range.start(), range.end(), sourcePreSettlementIds.size(), sourceDetailIds.size());
|
||||
} catch (Exception exception) {
|
||||
log.error("合同[{}]自动生成正式结算单失败,账期{}至{}", contract.getContractNo(),
|
||||
range.start(), range.end(), exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
return created;
|
||||
}
|
||||
|
||||
private List<ReceivablePayableDetail> loadPendingDetails(ContractManage contract, List<DateRange> ranges) {
|
||||
LocalDate minDate = ranges.get(0).start();
|
||||
LocalDate maxDate = ranges.get(0).end();
|
||||
for (DateRange range : ranges) {
|
||||
if (range.start().isBefore(minDate)) {
|
||||
minDate = range.start();
|
||||
}
|
||||
if (range.end().isAfter(maxDate)) {
|
||||
maxDate = range.end();
|
||||
}
|
||||
}
|
||||
LocalDate periodStart = minDate;
|
||||
LocalDate periodEnd = maxDate;
|
||||
return receivablePayableDetailMapper.selectList(Wrappers.<ReceivablePayableDetail>lambdaQuery()
|
||||
.eq(ReceivablePayableDetail::getIsDeleted, 0)
|
||||
.eq(ReceivablePayableDetail::getContractId, contract.getId())
|
||||
.eq(ReceivablePayableDetail::getProjectId, contract.getProjectId())
|
||||
.eq(ReceivablePayableDetail::getDeptId, contract.getOrganizationId())
|
||||
.eq(ReceivablePayableDetail::getSettlementType, contractSettlementType(contract))
|
||||
.eq(ReceivablePayableDetail::getSettlementStatus, "pending")
|
||||
.and(query -> query.isNull(ReceivablePayableDetail::getPreSettlementNo)
|
||||
.or().eq(ReceivablePayableDetail::getPreSettlementNo, ""))
|
||||
.and(query -> query.isNull(ReceivablePayableDetail::getFormalSettlementNo)
|
||||
.or().eq(ReceivablePayableDetail::getFormalSettlementNo, ""))
|
||||
.and(query -> query.eq(ReceivablePayableDetail::getCustomerName, contract.getPartyA())
|
||||
.or().eq(ReceivablePayableDetail::getCustomerName, contract.getPartyB()))
|
||||
.and(query -> query.between(ReceivablePayableDetail::getFeeDate, periodStart, periodEnd)
|
||||
.or(nested -> nested.isNull(ReceivablePayableDetail::getFeeDate)
|
||||
.ge(ReceivablePayableDetail::getCreateTime, periodStart.atStartOfDay())
|
||||
.lt(ReceivablePayableDetail::getCreateTime, periodEnd.plusDays(1).atStartOfDay()))));
|
||||
}
|
||||
|
||||
private Map<DateRange, List<PreSettlement>> groupApprovedPres(ContractManage contract, String settlementType,
|
||||
List<DateRange> ranges) {
|
||||
Map<DateRange, List<PreSettlement>> grouped = new LinkedHashMap<>();
|
||||
ranges.forEach(range -> grouped.put(range, new ArrayList<>()));
|
||||
List<PreSettlement> preSettlements = preSettlementService.list(Wrappers.<PreSettlement>lambdaQuery()
|
||||
.eq(PreSettlement::getContractId, contract.getId())
|
||||
.eq(PreSettlement::getSettlementType, settlementType)
|
||||
.eq(PreSettlement::getApprovalStatus, TransportBusiConstant.APPROVED)
|
||||
.and(query -> query.isNull(PreSettlement::getFormalSettlementNo)
|
||||
.or().eq(PreSettlement::getFormalSettlementNo, "")));
|
||||
if (preSettlements.isEmpty()) {
|
||||
return grouped;
|
||||
}
|
||||
List<Long> preSettlementIds = preSettlements.stream().map(PreSettlement::getId).toList();
|
||||
Map<Long, List<Long>> sourceIdsByPre = new LinkedHashMap<>();
|
||||
for (List<Long> batch : partition(preSettlementIds)) {
|
||||
List<PreSettlementDetail> lines = preSettlementDetailMapper.selectList(Wrappers.<PreSettlementDetail>lambdaQuery()
|
||||
.in(PreSettlementDetail::getPreSettlementId, batch)
|
||||
.eq(PreSettlementDetail::getIsDeleted, 0));
|
||||
for (PreSettlementDetail line : lines) {
|
||||
if (line.getSourceDetailId() == null) {
|
||||
continue;
|
||||
}
|
||||
sourceIdsByPre.computeIfAbsent(line.getPreSettlementId(), key -> new ArrayList<>()).add(line.getSourceDetailId());
|
||||
}
|
||||
}
|
||||
Set<Long> sourceIds = new LinkedHashSet<>();
|
||||
sourceIdsByPre.values().forEach(sourceIds::addAll);
|
||||
Map<Long, ReceivablePayableDetail> sourceMap = new LinkedHashMap<>();
|
||||
for (List<Long> batch : partition(sourceIds)) {
|
||||
receivablePayableDetailMapper.selectBatchIds(batch)
|
||||
.forEach(detail -> sourceMap.put(detail.getId(), detail));
|
||||
}
|
||||
for (PreSettlement preSettlement : preSettlements) {
|
||||
List<Long> sourceDetailIds = sourceIdsByPre.getOrDefault(preSettlement.getId(), List.of());
|
||||
LocalDate minDate = null;
|
||||
LocalDate maxDate = null;
|
||||
boolean invalid = sourceDetailIds.isEmpty();
|
||||
for (Long sourceDetailId : sourceDetailIds) {
|
||||
ReceivablePayableDetail source = sourceMap.get(sourceDetailId);
|
||||
LocalDate businessDate = source == null ? null : businessDate(source);
|
||||
if (businessDate == null) {
|
||||
invalid = true;
|
||||
break;
|
||||
}
|
||||
minDate = minDate == null || businessDate.isBefore(minDate) ? businessDate : minDate;
|
||||
maxDate = maxDate == null || businessDate.isAfter(maxDate) ? businessDate : maxDate;
|
||||
}
|
||||
if (invalid || minDate == null || maxDate == null) {
|
||||
log.warn("预结算单[{}]缺少可归集的费用日期,跳过自动转正式结算", preSettlement.getPreSettlementNo());
|
||||
continue;
|
||||
}
|
||||
boolean matched = false;
|
||||
for (DateRange range : ranges) {
|
||||
if (!minDate.isBefore(range.start()) && !maxDate.isAfter(range.end())) {
|
||||
grouped.get(range).add(preSettlement);
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!matched && !maxDate.isAfter(LocalDate.now().minusDays(1))) {
|
||||
log.info("预结算单[{}]费用日期{}至{}未完整落入已关闭账期,暂不自动归集正式结算",
|
||||
preSettlement.getPreSettlementNo(), minDate, maxDate);
|
||||
}
|
||||
}
|
||||
return grouped;
|
||||
}
|
||||
|
||||
private Map<DateRange, List<ReceivablePayableDetail>> groupDetails(List<DateRange> ranges,
|
||||
List<ReceivablePayableDetail> details) {
|
||||
Map<DateRange, List<ReceivablePayableDetail>> grouped = new LinkedHashMap<>();
|
||||
ranges.forEach(range -> grouped.put(range, new ArrayList<>()));
|
||||
for (ReceivablePayableDetail detail : details) {
|
||||
LocalDate businessDate = businessDate(detail);
|
||||
if (businessDate == null) {
|
||||
continue;
|
||||
}
|
||||
for (DateRange range : ranges) {
|
||||
if (!businessDate.isBefore(range.start()) && !businessDate.isAfter(range.end())) {
|
||||
grouped.get(range).add(detail);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return grouped;
|
||||
}
|
||||
|
||||
private List<DateRange> closedPeriods(Map<String, Object> rule, LocalDate today) {
|
||||
LocalDate billStart = dateValue(rule.get("billStartDate"));
|
||||
LocalDate yesterday = today.minusDays(1);
|
||||
if (billStart == null || yesterday.isBefore(billStart)) {
|
||||
return List.of();
|
||||
}
|
||||
String settlementType = Func.toStr(rule.get("settlementType")).trim();
|
||||
return switch (settlementType) {
|
||||
case "现结", "日结" -> cyclePeriods(billStart, yesterday, 1);
|
||||
case "周结" -> cyclePeriods(billStart, yesterday, 7);
|
||||
case "按固定天数周期", "固定天数周期结算" -> {
|
||||
Integer cycleDays = intValue(rule.get("cycleDays"));
|
||||
if (cycleDays == null || cycleDays < 1 || cycleDays > 365) {
|
||||
yield List.of();
|
||||
}
|
||||
yield cyclePeriods(billStart, yesterday, cycleDays);
|
||||
}
|
||||
case "月结" -> monthlyPeriods(rule, billStart, yesterday);
|
||||
default -> List.of();
|
||||
};
|
||||
}
|
||||
|
||||
private List<DateRange> monthlyPeriods(Map<String, Object> rule, LocalDate billStart, LocalDate yesterday) {
|
||||
String cycleType = Func.toStr(rule.get("billCycleType")).trim();
|
||||
if (Func.isBlank(cycleType) || "自然月".equals(cycleType)) {
|
||||
return naturalMonthPeriods(billStart, yesterday);
|
||||
}
|
||||
if ("固定截单日".equals(cycleType)) {
|
||||
Integer cutoffDay = intValue(rule.get("billCutoffDay"));
|
||||
if (cutoffDay == null || cutoffDay < 1 || cutoffDay > 31) {
|
||||
return List.of();
|
||||
}
|
||||
return fixedCutoffPeriods(billStart, yesterday, cutoffDay);
|
||||
}
|
||||
if ("自定义多周期".equals(cycleType)) {
|
||||
return customMonthPeriods(rule.get("customPeriods"), billStart, yesterday);
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
private List<DateRange> cyclePeriods(LocalDate billStart, LocalDate yesterday, int days) {
|
||||
List<DateRange> ranges = new ArrayList<>();
|
||||
LocalDate cursor = billStart;
|
||||
while (true) {
|
||||
LocalDate end = cursor.plusDays(days - 1L);
|
||||
if (end.isAfter(yesterday)) {
|
||||
break;
|
||||
}
|
||||
ranges.add(new DateRange(cursor, end));
|
||||
cursor = end.plusDays(1);
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
private List<DateRange> naturalMonthPeriods(LocalDate billStart, LocalDate yesterday) {
|
||||
List<DateRange> ranges = new ArrayList<>();
|
||||
YearMonth cursor = YearMonth.from(billStart);
|
||||
YearMonth lastMonth = YearMonth.from(yesterday);
|
||||
while (!cursor.isAfter(lastMonth)) {
|
||||
LocalDate end = cursor.atEndOfMonth();
|
||||
if (!end.isAfter(yesterday) && !end.isBefore(billStart)) {
|
||||
LocalDate start = cursor.atDay(1).isBefore(billStart) ? billStart : cursor.atDay(1);
|
||||
ranges.add(new DateRange(start, end));
|
||||
}
|
||||
cursor = cursor.plusMonths(1);
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
private List<DateRange> fixedCutoffPeriods(LocalDate billStart, LocalDate yesterday, int cutoffDay) {
|
||||
List<DateRange> ranges = new ArrayList<>();
|
||||
YearMonth cursor = YearMonth.from(billStart).minusMonths(1);
|
||||
YearMonth limit = YearMonth.from(yesterday).plusMonths(1);
|
||||
LocalDate previousEnd = null;
|
||||
while (!cursor.isAfter(limit)) {
|
||||
LocalDate end = cursor.atDay(Math.min(cutoffDay, cursor.lengthOfMonth()));
|
||||
if (!end.isBefore(billStart) && !end.isAfter(yesterday)) {
|
||||
LocalDate start = previousEnd == null ? billStart : previousEnd.plusDays(1);
|
||||
if (start.isBefore(billStart)) {
|
||||
start = billStart;
|
||||
}
|
||||
if (!start.isAfter(end)) {
|
||||
ranges.add(new DateRange(start, end));
|
||||
}
|
||||
}
|
||||
if (!end.isBefore(billStart.minusMonths(1))) {
|
||||
previousEnd = end;
|
||||
}
|
||||
cursor = cursor.plusMonths(1);
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
private List<DateRange> customMonthPeriods(Object periodsValue, LocalDate billStart, LocalDate yesterday) {
|
||||
List<int[]> periods = customPeriods(periodsValue);
|
||||
if (periods.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
List<DateRange> ranges = new ArrayList<>();
|
||||
YearMonth cursor = YearMonth.from(billStart);
|
||||
YearMonth lastMonth = YearMonth.from(yesterday);
|
||||
while (!cursor.isAfter(lastMonth)) {
|
||||
for (int[] period : periods) {
|
||||
int startDay = Math.min(period[0], cursor.lengthOfMonth());
|
||||
int endDay = Math.min(period[1], cursor.lengthOfMonth());
|
||||
if (startDay > endDay) {
|
||||
continue;
|
||||
}
|
||||
LocalDate end = cursor.atDay(endDay);
|
||||
if (end.isAfter(yesterday) || end.isBefore(billStart)) {
|
||||
continue;
|
||||
}
|
||||
LocalDate start = cursor.atDay(startDay);
|
||||
if (start.isBefore(billStart)) {
|
||||
start = billStart;
|
||||
}
|
||||
if (!start.isAfter(end)) {
|
||||
ranges.add(new DateRange(start, end));
|
||||
}
|
||||
}
|
||||
cursor = cursor.plusMonths(1);
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
private List<int[]> customPeriods(Object value) {
|
||||
if (!(value instanceof List<?> periodList)) {
|
||||
return List.of();
|
||||
}
|
||||
List<int[]> periods = new ArrayList<>();
|
||||
for (Object item : periodList) {
|
||||
if (!(item instanceof Map<?, ?> period)) {
|
||||
continue;
|
||||
}
|
||||
Integer startDay = intValue(period.get("startDay"));
|
||||
Integer endDay = intValue(period.get("endDay"));
|
||||
if (startDay == null || endDay == null || startDay < 1 || endDay > 31 || endDay < startDay) {
|
||||
continue;
|
||||
}
|
||||
periods.add(new int[]{startDay, endDay});
|
||||
}
|
||||
return periods;
|
||||
}
|
||||
|
||||
private Map<String, Object> resolveRule(ContractManage contract, boolean preRule) {
|
||||
Map<String, Object> direct = parseObject(preRule ? contract.getPreSettlementConfigJson() : contract.getFormalSettlementConfigJson());
|
||||
if (!direct.isEmpty()) {
|
||||
return direct;
|
||||
}
|
||||
Map<String, Object> payload = parseObject(contract.getSettlementRuleJson());
|
||||
Object nested = payload.get(preRule ? "preSettlementConfig" : "formalSettlementConfig");
|
||||
if (nested instanceof Map<?, ?> nestedMap) {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
nestedMap.forEach((key, value) -> result.put(String.valueOf(key), value));
|
||||
return result;
|
||||
}
|
||||
if (!payload.containsKey("preSettlementConfig") && !payload.containsKey("formalSettlementConfig")) {
|
||||
return payload;
|
||||
}
|
||||
return Map.of();
|
||||
}
|
||||
|
||||
private Map<String, Object> parseObject(String json) {
|
||||
if (Func.isBlank(json)) {
|
||||
return Map.of();
|
||||
}
|
||||
try {
|
||||
Object parsed = JsonUtil.parse(json, Map.class);
|
||||
if (!(parsed instanceof Map<?, ?> raw) || raw.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
raw.forEach((key, value) -> result.put(String.valueOf(key), value));
|
||||
return result;
|
||||
} catch (Exception exception) {
|
||||
log.warn("结算规则JSON解析失败:{}", exception.getMessage());
|
||||
return Map.of();
|
||||
}
|
||||
}
|
||||
|
||||
private void inTransaction(Runnable runnable) {
|
||||
new TransactionTemplate(transactionManager).executeWithoutResult(status -> runnable.run());
|
||||
}
|
||||
|
||||
private List<List<Long>> partition(Collection<Long> ids) {
|
||||
List<Long> source = ids instanceof List<Long> list ? list : new ArrayList<>(ids);
|
||||
List<List<Long>> batches = new ArrayList<>();
|
||||
for (int index = 0; index < source.size(); index += QUERY_BATCH_SIZE) {
|
||||
batches.add(source.subList(index, Math.min(index + QUERY_BATCH_SIZE, source.size())));
|
||||
}
|
||||
return batches;
|
||||
}
|
||||
|
||||
private String contractSettlementType(ContractManage contract) {
|
||||
if ("客户合同".equals(contract.getContractCategory())) {
|
||||
return "receivable";
|
||||
}
|
||||
if ("承运商合同".equals(contract.getContractCategory())) {
|
||||
return "payable";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private LocalDate businessDate(ReceivablePayableDetail detail) {
|
||||
if (detail.getFeeDate() != null) {
|
||||
return detail.getFeeDate();
|
||||
}
|
||||
if (detail.getCreateTime() == null) {
|
||||
return null;
|
||||
}
|
||||
return detail.getCreateTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
||||
}
|
||||
|
||||
private String periodRemark(DateRange range) {
|
||||
return "系统按结算规则自动生成(" + range.start() + "至" + range.end() + ")";
|
||||
}
|
||||
|
||||
private boolean enabled(Object value) {
|
||||
if (value instanceof Boolean enabled) {
|
||||
return enabled;
|
||||
}
|
||||
if (value instanceof Number number) {
|
||||
return number.intValue() == 1;
|
||||
}
|
||||
String text = value == null ? "" : String.valueOf(value).trim();
|
||||
return "1".equals(text) || "true".equalsIgnoreCase(text);
|
||||
}
|
||||
|
||||
private boolean knownCycle(String settlementType) {
|
||||
return List.of("现结", "日结", "周结", "月结", "按固定天数周期", "固定天数周期结算").contains(settlementType);
|
||||
}
|
||||
|
||||
private Integer intValue(Object value) {
|
||||
if (value instanceof Number number) {
|
||||
return number.intValue();
|
||||
}
|
||||
if (value == null || Func.isBlank(String.valueOf(value))) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(String.valueOf(value).trim());
|
||||
} catch (NumberFormatException exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private LocalDate dateValue(Object value) {
|
||||
if (value == null || Func.isBlank(String.valueOf(value))) {
|
||||
return null;
|
||||
}
|
||||
String text = String.valueOf(value).trim();
|
||||
if (text.length() >= 10) {
|
||||
text = text.substring(0, 10);
|
||||
}
|
||||
try {
|
||||
return LocalDate.parse(text);
|
||||
} catch (DateTimeParseException exception) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private record DateRange(LocalDate start, LocalDate end) {
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -828,7 +828,7 @@ public class KingdeeApBillSyncSupport {
|
||||
}
|
||||
Map<String, String> categoryNames = DictBizCache.getList(FEE_CATEGORY_DICT).stream()
|
||||
.collect(Collectors.toMap(DictBiz::getDictKey, DictBiz::getDictValue, (first, second) -> first));
|
||||
R<List<FeeItem>> response = sysClient.getFeeItems();
|
||||
R<List<FeeItem>> response = sysClient.getFeeItems(false);
|
||||
if (response == null || !response.isSuccess() || Func.isEmpty(response.getData())) {
|
||||
throw new ServiceException("推送金蝶失败:费用项基础档案读取失败,请稍后重试");
|
||||
}
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* BladeX Commercial License Agreement
|
||||
* Copyright (c) 2018-2099, https://bladex.cn. All rights reserved.
|
||||
* <p>
|
||||
* Use of this software is governed by the Commercial License Agreement
|
||||
* obtained after purchasing a license from BladeX.
|
||||
* <p>
|
||||
* 1. This software is for development use only under a valid license
|
||||
* from BladeX.
|
||||
* <p>
|
||||
* 2. Redistribution of this software's source code to any third party
|
||||
* without a commercial license is strictly prohibited.
|
||||
* <p>
|
||||
* 3. Licensees may copyright their own code but cannot use segments
|
||||
* from this software for such purposes. Copyright of this software
|
||||
* remains with BladeX.
|
||||
* <p>
|
||||
* Using this software signifies agreement to this License, and the software
|
||||
* must not be used for illegal purposes.
|
||||
* <p>
|
||||
* THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY. The author is
|
||||
* not liable for any claims arising from secondary or illegal development.
|
||||
* <p>
|
||||
* Author: Chill Zhuang (bladejava@qq.com)
|
||||
*/
|
||||
package org.springblade.transport.task;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springblade.transport.service.ISettlementRuleAutoGenerateService;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 合同结算规则定时生成结算单。
|
||||
* <p>
|
||||
* 每天零点执行:预结算或正式结算配置开启自动生成时,按账单开始日期和结算周期归集已关闭账期的运单明细。
|
||||
*
|
||||
* @author Chill
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class SettlementRuleAutoGenerateTask {
|
||||
|
||||
private final ISettlementRuleAutoGenerateService settlementRuleAutoGenerateService;
|
||||
|
||||
/** 每天 00:00 生成到期的预结算单、正式结算单 */
|
||||
@Scheduled(cron = "${blade.settlement.auto-generate-cron:0 0 0 * * ?}")
|
||||
public void generateDueSettlements() {
|
||||
try {
|
||||
String summary = settlementRuleAutoGenerateService.generateDueSettlements();
|
||||
log.info("定时自动生成结算单:{}", summary);
|
||||
} catch (Exception exception) {
|
||||
log.error("定时自动生成结算单异常", exception);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
-- 付款申请可关联多张预结算单:关联表补充预结算字段,正式结算字段改为可空
|
||||
|
||||
SET @ddl_sql = (
|
||||
SELECT IF(COUNT(*) > 0,
|
||||
'ALTER TABLE `blade_payment_application_settlement` MODIFY COLUMN `formal_settlement_id` bigint(20) NULL COMMENT ''正式结算单ID''',
|
||||
'SELECT 1')
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'blade_payment_application_settlement'
|
||||
AND COLUMN_NAME = 'formal_settlement_id'
|
||||
AND IS_NULLABLE = 'NO'
|
||||
);
|
||||
PREPARE ddl_stmt FROM @ddl_sql;
|
||||
EXECUTE ddl_stmt;
|
||||
DEALLOCATE PREPARE ddl_stmt;
|
||||
|
||||
SET @ddl_sql = (
|
||||
SELECT IF(COUNT(*) > 0,
|
||||
'ALTER TABLE `blade_payment_application_settlement` MODIFY COLUMN `formal_settlement_no` varchar(100) COLLATE utf8mb4_general_ci NULL COMMENT ''正式结算单号''',
|
||||
'SELECT 1')
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'blade_payment_application_settlement'
|
||||
AND COLUMN_NAME = 'formal_settlement_no'
|
||||
AND IS_NULLABLE = 'NO'
|
||||
);
|
||||
PREPARE ddl_stmt FROM @ddl_sql;
|
||||
EXECUTE ddl_stmt;
|
||||
DEALLOCATE PREPARE ddl_stmt;
|
||||
|
||||
SET @ddl_sql = (
|
||||
SELECT IF(COUNT(*) = 0,
|
||||
'ALTER TABLE `blade_payment_application_settlement` ADD COLUMN `pre_settlement_id` bigint(20) DEFAULT NULL COMMENT ''预结算单ID'' AFTER `formal_settlement_no`',
|
||||
'SELECT 1')
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'blade_payment_application_settlement'
|
||||
AND COLUMN_NAME = 'pre_settlement_id'
|
||||
);
|
||||
PREPARE ddl_stmt FROM @ddl_sql;
|
||||
EXECUTE ddl_stmt;
|
||||
DEALLOCATE PREPARE ddl_stmt;
|
||||
|
||||
SET @ddl_sql = (
|
||||
SELECT IF(COUNT(*) = 0,
|
||||
'ALTER TABLE `blade_payment_application_settlement` ADD COLUMN `pre_settlement_no` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ''预结算单号'' AFTER `pre_settlement_id`',
|
||||
'SELECT 1')
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'blade_payment_application_settlement'
|
||||
AND COLUMN_NAME = 'pre_settlement_no'
|
||||
);
|
||||
PREPARE ddl_stmt FROM @ddl_sql;
|
||||
EXECUTE ddl_stmt;
|
||||
DEALLOCATE PREPARE ddl_stmt;
|
||||
|
||||
SET @ddl_sql = (
|
||||
SELECT IF(COUNT(*) = 0,
|
||||
'ALTER TABLE `blade_payment_application_settlement` ADD KEY `idx_payment_settlement_pre` (`pre_settlement_id`)',
|
||||
'SELECT 1')
|
||||
FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'blade_payment_application_settlement'
|
||||
AND INDEX_NAME = 'idx_payment_settlement_pre'
|
||||
);
|
||||
PREPARE ddl_stmt FROM @ddl_sql;
|
||||
EXECUTE ddl_stmt;
|
||||
DEALLOCATE PREPARE ddl_stmt;
|
||||
|
||||
SET @ddl_sql = (
|
||||
SELECT IF(COUNT(*) > 0,
|
||||
'ALTER TABLE `blade_payment_application` MODIFY COLUMN `pre_settlement_no` varchar(500) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ''预结算单号''',
|
||||
'SELECT 1')
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'blade_payment_application'
|
||||
AND COLUMN_NAME = 'pre_settlement_no'
|
||||
AND CHARACTER_MAXIMUM_LENGTH < 500
|
||||
);
|
||||
PREPARE ddl_stmt FROM @ddl_sql;
|
||||
EXECUTE ddl_stmt;
|
||||
DEALLOCATE PREPARE ddl_stmt;
|
||||
Reference in New Issue
Block a user