调整 合同

This commit is contained in:
2026-09-14 15:00:40 +08:00
parent 940f0b9b9f
commit 70e44da789
10 changed files with 254 additions and 20 deletions
@@ -31,6 +31,7 @@ import lombok.EqualsAndHashCode;
import org.springblade.core.tenant.mp.TenantEntity; import org.springblade.core.tenant.mp.TenantEntity;
import java.io.Serial; import java.io.Serial;
import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -123,12 +124,31 @@ public class ContractManage extends TenantEntity {
@TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS) @TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS)
private Integer paymentDays; private Integer paymentDays;
@Schema(description = "合同金额")
@TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS)
private BigDecimal contractAmount;
@Schema(description = "是否范本")
@TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS)
private Integer templateFlag;
@Schema(description = "原件合同编号")
@TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS)
private String originalContractNo;
@Schema(description = "是否电子章")
@TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS)
private Integer electronicSealFlag;
@Schema(description = "合同阶段") @Schema(description = "合同阶段")
private String contractStage; private String contractStage;
@Schema(description = "审核状态") @Schema(description = "审核状态")
private String approvalStatus; private String approvalStatus;
@Schema(description = "归档状态:未归档/已归档")
private String archiveStatus;
@Schema(description = "当前节点") @Schema(description = "当前节点")
private String currentNode; private String currentNode;
@@ -141,7 +161,7 @@ public class ContractManage extends TenantEntity {
@Schema(description = "计费信息开关") @Schema(description = "计费信息开关")
private Integer billingEnabled; private Integer billingEnabled;
@Schema(description = "费用生成模式:system系统生成,manual手动生成") @Schema(description = "费用生成模式:system系统生成,manual账单导入生成")
private String feeGenerationMode; private String feeGenerationMode;
@Schema(description = "合同主文件JSON") @Schema(description = "合同主文件JSON")
@@ -80,6 +80,10 @@ public class ProjectApplyVO extends ProjectApply {
@Schema(description = "是否仅查询可用于临时额度申请的项目") @Schema(description = "是否仅查询可用于临时额度申请的项目")
private Boolean temporaryCreditLimitSelectable; private Boolean temporaryCreditLimitSelectable;
@TableField(exist = false)
@Schema(description = "是否仅查询可用于合同选择的项目(含临时项目与正式审批通过项目)")
private Boolean contractSelectable;
@TableField(exist = false) @TableField(exist = false)
@Schema(description = "资金使用风险等级:high、medium、none") @Schema(description = "资金使用风险等级:high、medium、none")
private String fundUseRisk; private String fundUseRisk;
@@ -23,6 +23,7 @@
package org.springblade.transport.excel; package org.springblade.transport.excel;
import cn.idev.excel.annotation.ExcelProperty; import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.format.NumberFormat;
import cn.idev.excel.annotation.write.style.ColumnWidth; import cn.idev.excel.annotation.write.style.ColumnWidth;
import cn.idev.excel.annotation.write.style.ContentRowHeight; import cn.idev.excel.annotation.write.style.ContentRowHeight;
import cn.idev.excel.annotation.write.style.HeadRowHeight; import cn.idev.excel.annotation.write.style.HeadRowHeight;
@@ -30,6 +31,7 @@ import lombok.Data;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -77,6 +79,8 @@ public class ContractManageExcel implements Serializable {
private String contractStage; private String contractStage;
@ExcelProperty("审核状态") @ExcelProperty("审核状态")
private String approvalStatus; private String approvalStatus;
@ExcelProperty("归档状态")
private String archiveStatus;
@ExcelProperty("结算币种") @ExcelProperty("结算币种")
private String settlementCurrency; private String settlementCurrency;
@ExcelProperty("结算方式") @ExcelProperty("结算方式")
@@ -87,6 +91,15 @@ public class ContractManageExcel implements Serializable {
private Integer copyCount; private Integer copyCount;
@ExcelProperty("回款账期(天)") @ExcelProperty("回款账期(天)")
private Integer paymentDays; private Integer paymentDays;
@ExcelProperty("合同金额")
@NumberFormat("0.00")
private BigDecimal contractAmount;
@ExcelProperty("是否范本")
private Integer templateFlag;
@ExcelProperty("原件合同编号")
private String originalContractNo;
@ExcelProperty("是否电子章")
private Integer electronicSealFlag;
@ExcelProperty("当前节点") @ExcelProperty("当前节点")
private String currentNode; private String currentNode;
@ExcelProperty("当前处理人") @ExcelProperty("当前处理人")
@@ -71,9 +71,13 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
private static final String STATUS_REVIEWING = "reviewing"; private static final String STATUS_REVIEWING = "reviewing";
private static final String STATUS_REJECTED = "rejected"; private static final String STATUS_REJECTED = "rejected";
private static final String STATUS_APPROVED = "approved"; private static final String STATUS_APPROVED = "approved";
private static final String STATUS_WITHDRAWN = "withdrawn";
private static final String STATUS_CHANGE_REVIEWING = "change_reviewing"; private static final String STATUS_CHANGE_REVIEWING = "change_reviewing";
private static final String STATUS_CHANGE_APPROVED = "change_approved"; private static final String STATUS_CHANGE_APPROVED = "change_approved";
private static final String STATUS_CHANGE_REJECTED = "change_rejected"; private static final String STATUS_CHANGE_REJECTED = "change_rejected";
private static final String ARCHIVE_STATUS_PENDING = "未归档";
private static final String ARCHIVE_STATUS_ARCHIVED = "已归档";
private static final String FILE_TYPE_SEAL_ARCHIVE = "双章归档文件";
private static final long TEMPORARY_VALIDITY_DAYS = 30L; private static final long TEMPORARY_VALIDITY_DAYS = 30L;
private static final BigDecimal TAX_RATE_MIN = BigDecimal.ZERO; private static final BigDecimal TAX_RATE_MIN = BigDecimal.ZERO;
private static final BigDecimal TAX_RATE_MAX = BigDecimal.valueOf(100); private static final BigDecimal TAX_RATE_MAX = BigDecimal.valueOf(100);
@@ -138,9 +142,10 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public boolean toTemporary(Long id) { public boolean toTemporary(Long id) {
ContractManage contractManage = loadEditable(id); ContractManage contractManage = loadEditable(id);
if (!Objects.equals(contractManage.getContractStage(), STAGE_DRAFT) || !Objects.equals(contractManage.getApprovalStatus(), STATUS_DRAFT)) { if (!canResubmitTemporary(contractManage)) {
throw new ServiceException("仅草稿合同允许临时合同"); throw new ServiceException("仅草稿或已撤回/已驳回的临时合同允许提交临时合同审批");
} }
validateTemporary(contractManage);
// 临时放开新增合同从草稿转临时合同时的组合重复校验。 // 临时放开新增合同从草稿转临时合同时的组合重复校验。
// validateContractPartyCombinationUnique(contractManage); // validateContractPartyCombinationUnique(contractManage);
contractManage.setContractStage(STAGE_TEMPORARY); contractManage.setContractStage(STAGE_TEMPORARY);
@@ -155,8 +160,8 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public boolean submitFormal(Long id) { public boolean submitFormal(Long id) {
ContractManage contractManage = loadExists(id); ContractManage contractManage = loadExists(id);
if (!Objects.equals(contractManage.getContractStage(), STAGE_TEMPORARY) || !List.of(STATUS_REJECTED, STATUS_APPROVED, STATUS_DRAFT).contains(contractManage.getApprovalStatus())) { if (!canResubmitFormal(contractManage)) {
throw new ServiceException("仅临时合同允许转正式合同"); throw new ServiceException("仅临时合同或已撤回/已驳回的正式合同允许提交正式合同审批");
} }
validateFormal(contractManage); validateFormal(contractManage);
// 临时放开新增合同从临时转正式合同时的组合重复校验。 // 临时放开新增合同从临时转正式合同时的组合重复校验。
@@ -186,6 +191,8 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
contractManage.setChangeContent(null); contractManage.setChangeContent(null);
contractManage.setChangeReason(null); contractManage.setChangeReason(null);
} }
contractManage.setContractFileJson(markContractFilesApproved(contractManage.getContractFileJson()));
refreshArchiveStatusOnApprove(contractManage);
updateLatestReviewingChangeRecord(contractManage, contractManage.getApprovalStatus(), "审核通过"); updateLatestReviewingChangeRecord(contractManage, contractManage.getApprovalStatus(), "审核通过");
return updateById(contractManage); return updateById(contractManage);
} }
@@ -212,7 +219,8 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
public boolean withdraw(Long id) { public boolean withdraw(Long id) {
ContractManage contractManage = loadReviewing(id); ContractManage contractManage = loadReviewing(id);
boolean changeReviewing = Objects.equals(contractManage.getApprovalStatus(), STATUS_CHANGE_REVIEWING); boolean changeReviewing = Objects.equals(contractManage.getApprovalStatus(), STATUS_CHANGE_REVIEWING);
contractManage.setApprovalStatus(changeReviewing ? STATUS_APPROVED : STATUS_DRAFT); // 变更审批撤回回已通过;普通审批撤回为已撤回,便于再次编辑并重新提交。
contractManage.setApprovalStatus(changeReviewing ? STATUS_APPROVED : STATUS_WITHDRAWN);
contractManage.setCurrentNode("已撤回"); contractManage.setCurrentNode("已撤回");
contractManage.setCurrentProcessor(AuthUtil.getUserName()); contractManage.setCurrentProcessor(AuthUtil.getUserName());
if (changeReviewing) { if (changeReviewing) {
@@ -268,7 +276,11 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
source.setSettlementMode(request.getSettlementMode()); source.setLegalSealFlag(request.getLegalSealFlag()); source.setCopyCount(request.getCopyCount()); source.setSettlementMode(request.getSettlementMode()); source.setLegalSealFlag(request.getLegalSealFlag()); source.setCopyCount(request.getCopyCount());
source.setSettlementCurrency(TransportBusinessSupport.trimToNull(request.getSettlementCurrency())); source.setInvoiceCycle(request.getInvoiceCycle()); source.setSettlementCurrency(TransportBusinessSupport.trimToNull(request.getSettlementCurrency())); source.setInvoiceCycle(request.getInvoiceCycle());
source.setPaymentDays(request.getPaymentDays()); source.setRemark(request.getRemark()); source.setBillingEnabled(request.getBillingEnabled()); source.setFeeGenerationMode(request.getFeeGenerationMode()); source.setPaymentDays(request.getPaymentDays()); source.setRemark(request.getRemark()); source.setBillingEnabled(request.getBillingEnabled()); source.setFeeGenerationMode(request.getFeeGenerationMode());
source.setContractAmount(request.getContractAmount()); source.setTemplateFlag(request.getTemplateFlag());
source.setOriginalContractNo(TransportBusinessSupport.trimToNull(request.getOriginalContractNo()));
source.setElectronicSealFlag(request.getElectronicSealFlag());
normalizeOptionalIntegerFields(source); normalizeOptionalIntegerFields(source);
validateAdditionalFields(source);
source.setBillingPlanJson(request.getBillingPlanJson()); source.setSettlementRuleJson(request.getSettlementRuleJson()); source.setPreSettlementConfigJson(request.getPreSettlementConfigJson()); source.setFormalSettlementConfigJson(request.getFormalSettlementConfigJson()); source.setPaymentRatioJson(request.getPaymentRatioJson()); source.setBillingPlanJson(request.getBillingPlanJson()); source.setSettlementRuleJson(request.getSettlementRuleJson()); source.setPreSettlementConfigJson(request.getPreSettlementConfigJson()); source.setFormalSettlementConfigJson(request.getFormalSettlementConfigJson()); source.setPaymentRatioJson(request.getPaymentRatioJson());
source.setContractFileJson(request.getContractFileJson()); source.setAttachmentsJson(request.getAttachmentsJson()); source.setContractFileJson(request.getContractFileJson()); source.setAttachmentsJson(request.getAttachmentsJson());
source.setChangeAttachmentsJson(TransportBusinessSupport.trimToNull(request.getChangeAttachmentsJson())); source.setChangeAttachmentsJson(TransportBusinessSupport.trimToNull(request.getChangeAttachmentsJson()));
@@ -291,8 +303,10 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|| !List.of(STATUS_APPROVED, STATUS_CHANGE_APPROVED).contains(source.getApprovalStatus())) { || !List.of(STATUS_APPROVED, STATUS_CHANGE_APPROVED).contains(source.getApprovalStatus())) {
throw new ServiceException("仅审批通过的正式合同允许上传附件"); throw new ServiceException("仅审批通过的正式合同允许上传附件");
} }
source.setContractFileJson(TransportBusinessSupport.trimToNull(contractManage.getContractFileJson())); String nextContractFileJson = mergeApprovedContractFiles(source.getContractFileJson(), contractManage.getContractFileJson());
source.setContractFileJson(nextContractFileJson);
source.setAttachmentsJson(TransportBusinessSupport.trimToNull(contractManage.getAttachmentsJson())); source.setAttachmentsJson(TransportBusinessSupport.trimToNull(contractManage.getAttachmentsJson()));
refreshArchiveStatusOnApprove(source);
return updateById(source); return updateById(source);
} }
@@ -336,6 +350,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
target.setContractName(source.getContractName() + " - 副本"); target.setContractName(source.getContractName() + " - 副本");
target.setContractStage(STAGE_DRAFT); target.setContractStage(STAGE_DRAFT);
target.setApprovalStatus(STATUS_DRAFT); target.setApprovalStatus(STATUS_DRAFT);
target.setArchiveStatus(ARCHIVE_STATUS_PENDING);
target.setCurrentNode(null); target.setCurrentNode(null);
target.setCurrentProcessor(null); target.setCurrentProcessor(null);
target.setApprovedTime(null); target.setApprovedTime(null);
@@ -396,6 +411,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
.eq(Func.isNotEmpty(contractManage.getContractCategory()), ContractManage::getContractCategory, contractManage.getContractCategory()) .eq(Func.isNotEmpty(contractManage.getContractCategory()), ContractManage::getContractCategory, contractManage.getContractCategory())
.eq(Func.isNotEmpty(contractManage.getSignType()), ContractManage::getSignType, contractManage.getSignType()) .eq(Func.isNotEmpty(contractManage.getSignType()), ContractManage::getSignType, contractManage.getSignType())
.eq(Func.isNotEmpty(contractManage.getApprovalStatus()), ContractManage::getApprovalStatus, contractManage.getApprovalStatus()) .eq(Func.isNotEmpty(contractManage.getApprovalStatus()), ContractManage::getApprovalStatus, contractManage.getApprovalStatus())
.eq(Func.isNotEmpty(contractManage.getArchiveStatus()), ContractManage::getArchiveStatus, contractManage.getArchiveStatus())
.eq(Func.isNotEmpty(contractManage.getContractStage()), ContractManage::getContractStage, contractManage.getContractStage()) .eq(Func.isNotEmpty(contractManage.getContractStage()), ContractManage::getContractStage, contractManage.getContractStage())
.orderByDesc(ContractManage::getCreateTime); .orderByDesc(ContractManage::getCreateTime);
applyEffectiveType(queryWrapper, contractManage.getEffectiveType()); applyEffectiveType(queryWrapper, contractManage.getEffectiveType());
@@ -463,6 +479,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
contractManage.setHandlerUserId(AuthUtil.getUserId()); contractManage.setHandlerUserId(AuthUtil.getUserId());
contractManage.setHandlerUserName(AuthUtil.getUserName()); contractManage.setHandlerUserName(AuthUtil.getUserName());
} }
contractManage.setArchiveStatus(ARCHIVE_STATUS_PENDING);
} else { } else {
ContractManage oldRecord = loadExists(contractManage.getId()); ContractManage oldRecord = loadExists(contractManage.getId());
contractManage.setContractNo(oldRecord.getContractNo()); contractManage.setContractNo(oldRecord.getContractNo());
@@ -472,10 +489,99 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
} }
contractManage.setHandlerUserId(oldRecord.getHandlerUserId()); contractManage.setHandlerUserId(oldRecord.getHandlerUserId());
contractManage.setHandlerUserName(oldRecord.getHandlerUserName()); contractManage.setHandlerUserName(oldRecord.getHandlerUserName());
// 归档状态仅由审核通过逻辑自动维护,保存时保留库中已有值。
contractManage.setArchiveStatus(Func.isEmpty(oldRecord.getArchiveStatus())
? ARCHIVE_STATUS_PENDING : oldRecord.getArchiveStatus());
} }
if (Func.isEmpty(contractManage.getContractStage())) { if (Func.isEmpty(contractManage.getContractStage())) {
contractManage.setContractStage(STAGE_DRAFT); contractManage.setContractStage(STAGE_DRAFT);
} }
if (Func.isEmpty(contractManage.getArchiveStatus())) {
contractManage.setArchiveStatus(ARCHIVE_STATUS_PENDING);
}
}
private void refreshArchiveStatusOnApprove(ContractManage contractManage) {
if (hasSealArchiveFile(contractManage.getContractFileJson())) {
contractManage.setArchiveStatus(ARCHIVE_STATUS_ARCHIVED);
} else if (Func.isEmpty(contractManage.getArchiveStatus())) {
contractManage.setArchiveStatus(ARCHIVE_STATUS_PENDING);
}
}
private boolean hasSealArchiveFile(String contractFileJson) {
return parseJsonArray(contractFileJson).stream().anyMatch(item -> {
if (!(item instanceof Map<?, ?> map)) {
return false;
}
Object fileType = map.get("fileType");
return fileType != null && FILE_TYPE_SEAL_ARCHIVE.equals(String.valueOf(fileType).trim());
});
}
@SuppressWarnings("unchecked")
private String markContractFilesApproved(String contractFileJson) {
List<Object> files = parseJsonArray(contractFileJson);
if (files.isEmpty()) {
return TransportBusinessSupport.trimToNull(contractFileJson);
}
List<Map<String, Object>> marked = new ArrayList<>();
for (Object item : files) {
if (item instanceof Map<?, ?> map) {
Map<String, Object> next = new LinkedHashMap<>((Map<String, Object>) map);
next.put("approved", true);
marked.add(next);
}
}
return marked.isEmpty() ? null : JsonUtil.toJson(marked);
}
@SuppressWarnings("unchecked")
private String mergeApprovedContractFiles(String originalJson, String requestJson) {
List<Object> originalFiles = parseJsonArray(originalJson);
List<Object> requestFiles = parseJsonArray(requestJson);
Map<String, Map<String, Object>> originalApproved = new LinkedHashMap<>();
for (Object item : originalFiles) {
if (!(item instanceof Map<?, ?> map)) {
continue;
}
Map<String, Object> file = new LinkedHashMap<>((Map<String, Object>) map);
if (isAttachmentApproved(file)) {
originalApproved.put(attachmentKey(file), file);
}
}
Set<String> requestKeys = new HashSet<>();
List<Map<String, Object>> merged = new ArrayList<>();
for (Object item : requestFiles) {
if (!(item instanceof Map<?, ?> map)) {
continue;
}
Map<String, Object> file = new LinkedHashMap<>((Map<String, Object>) map);
String key = attachmentKey(file);
requestKeys.add(key);
Map<String, Object> approvedOriginal = originalApproved.get(key);
if (approvedOriginal != null) {
// 已审核通过附件不允许改删,保留原记录。
merged.add(approvedOriginal);
continue;
}
file.put("approved", true);
merged.add(file);
}
for (Map.Entry<String, Map<String, Object>> entry : originalApproved.entrySet()) {
if (!requestKeys.contains(entry.getKey())) {
throw new ServiceException("已审核通过的合同文件不允许删除");
}
}
return merged.isEmpty() ? null : JsonUtil.toJson(merged);
}
private boolean isAttachmentApproved(Map<String, Object> file) {
Object approved = file.get("approved");
if (Boolean.TRUE.equals(approved) || Objects.equals(approved, 1) || Objects.equals(String.valueOf(approved), "1")) {
return true;
}
return Objects.equals(String.valueOf(file.get("approvalStatus")), STATUS_APPROVED);
} }
private void prepare(ContractManage contractManage) { private void prepare(ContractManage contractManage) {
@@ -490,6 +596,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
contractManage.setContractFormat(TransportBusinessSupport.trimToNull(contractManage.getContractFormat())); contractManage.setContractFormat(TransportBusinessSupport.trimToNull(contractManage.getContractFormat()));
contractManage.setRemark(TransportBusinessSupport.trimToNull(contractManage.getRemark())); contractManage.setRemark(TransportBusinessSupport.trimToNull(contractManage.getRemark()));
contractManage.setSettlementCurrency(TransportBusinessSupport.trimToNull(contractManage.getSettlementCurrency())); contractManage.setSettlementCurrency(TransportBusinessSupport.trimToNull(contractManage.getSettlementCurrency()));
contractManage.setOriginalContractNo(TransportBusinessSupport.trimToNull(contractManage.getOriginalContractNo()));
contractManage.setContractFileJson(TransportBusinessSupport.trimToNull(contractManage.getContractFileJson())); contractManage.setContractFileJson(TransportBusinessSupport.trimToNull(contractManage.getContractFileJson()));
contractManage.setAttachmentsJson(TransportBusinessSupport.trimToNull(contractManage.getAttachmentsJson())); contractManage.setAttachmentsJson(TransportBusinessSupport.trimToNull(contractManage.getAttachmentsJson()));
contractManage.setBillingPlanJson(TransportBusinessSupport.trimToNull(contractManage.getBillingPlanJson())); contractManage.setBillingPlanJson(TransportBusinessSupport.trimToNull(contractManage.getBillingPlanJson()));
@@ -498,6 +605,25 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
contractManage.setReconciliationJson(TransportBusinessSupport.trimToNull(contractManage.getReconciliationJson())); contractManage.setReconciliationJson(TransportBusinessSupport.trimToNull(contractManage.getReconciliationJson()));
contractManage.setChangeRecordJson(TransportBusinessSupport.trimToNull(contractManage.getChangeRecordJson())); contractManage.setChangeRecordJson(TransportBusinessSupport.trimToNull(contractManage.getChangeRecordJson()));
normalizeOptionalIntegerFields(contractManage); normalizeOptionalIntegerFields(contractManage);
validateAdditionalFields(contractManage);
}
private void validateAdditionalFields(ContractManage contractManage) {
BigDecimal contractAmount = contractManage.getContractAmount();
if (contractAmount != null && contractAmount.compareTo(BigDecimal.ZERO) < 0) {
throw new ServiceException("合同金额不能小于0");
}
if (contractAmount != null && contractAmount.scale() > 2) {
throw new ServiceException("合同金额最多保留2位小数");
}
validateFlag(contractManage.getTemplateFlag(), "是否范本");
validateFlag(contractManage.getElectronicSealFlag(), "是否电子章");
}
private void validateFlag(Integer value, String fieldName) {
if (value != null && value != 0 && value != 1) {
throw new ServiceException(fieldName + "只能选择是或否");
}
} }
private void normalizeOptionalIntegerFields(ContractManage contractManage) { private void normalizeOptionalIntegerFields(ContractManage contractManage) {
@@ -515,11 +641,8 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
private void validateDraft(ContractManage contractManage) { private void validateDraft(ContractManage contractManage) {
TransportBusinessSupport.validateRequired(contractManage.getContractName(), "请输入合同名称"); TransportBusinessSupport.validateRequired(contractManage.getContractName(), "请输入合同名称");
TransportBusinessSupport.validateRequired(contractManage.getContractCategory(), "请选择合同类别"); TransportBusinessSupport.validateRequired(contractManage.getContractCategory(), "请选择合同类别");
TransportBusinessSupport.validateRequired(contractManage.getSettlementCurrency(), "请选择结算币种");
validateContractNameUnique(contractManage); validateContractNameUnique(contractManage);
TransportBusinessSupport.validateLength(contractManage.getRemark(), 2000, "备注不能超过2000字"); TransportBusinessSupport.validateLength(contractManage.getRemark(), 2000, "备注不能超过2000字");
validateBillingPlans(contractManage.getBillingPlanJson());
validatePaymentRatios(contractManage.getPaymentRatioJson());
} }
private void validateBillingPlans(String billingPlanJson) { private void validateBillingPlans(String billingPlanJson) {
@@ -667,6 +790,12 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
private void validateTemporary(ContractManage contractManage) { private void validateTemporary(ContractManage contractManage) {
TransportBusinessSupport.validateRequired(contractManage.getSignType(), "请选择签约类型"); TransportBusinessSupport.validateRequired(contractManage.getSignType(), "请选择签约类型");
TransportBusinessSupport.validateRequired(contractManage.getContractName(), "请输入合同名称");
TransportBusinessSupport.validateRequired(contractManage.getContractCategory(), "请选择合同类型");
TransportBusinessSupport.validateRequired(contractManage.getProjectName(), "请选择所属项目");
TransportBusinessSupport.validateRequired(contractManage.getOrganizationName(), "请选择所属组织");
TransportBusinessSupport.validateRequired(contractManage.getPartyA(), "请选择甲方");
TransportBusinessSupport.validateRequired(contractManage.getPartyB(), "请选择乙方");
} }
/** /**
@@ -693,6 +822,11 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
} }
private void validateFormal(ContractManage contractManage) { private void validateFormal(ContractManage contractManage) {
TransportBusinessSupport.validateRequired(contractManage.getSignType(), "请选择签约类型");
TransportBusinessSupport.validateRequired(contractManage.getContractName(), "请输入合同名称");
TransportBusinessSupport.validateRequired(contractManage.getContractCategory(), "请选择合同类型");
TransportBusinessSupport.validateRequired(contractManage.getProjectName(), "请选择所属项目");
TransportBusinessSupport.validateRequired(contractManage.getOrganizationName(), "请选择所属组织");
TransportBusinessSupport.validateRequired(contractManage.getSettlementCurrency(), "请选择结算币种"); TransportBusinessSupport.validateRequired(contractManage.getSettlementCurrency(), "请选择结算币种");
TransportBusinessSupport.validateRequired(contractManage.getPartyA(), "请输入甲方"); TransportBusinessSupport.validateRequired(contractManage.getPartyA(), "请输入甲方");
TransportBusinessSupport.validateRequired(contractManage.getPartyB(), "请输入乙方"); TransportBusinessSupport.validateRequired(contractManage.getPartyB(), "请输入乙方");
@@ -700,6 +834,8 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
TransportBusinessSupport.validateRequired(contractManage.getSignDate() == null ? null : contractManage.getSignDate().toString(), "请选择签订日期"); TransportBusinessSupport.validateRequired(contractManage.getSignDate() == null ? null : contractManage.getSignDate().toString(), "请选择签订日期");
TransportBusinessSupport.validateDateRange(contractManage.getStartDate(), contractManage.getEndDate(), "合同期限开始日期不能晚于结束日期"); TransportBusinessSupport.validateDateRange(contractManage.getStartDate(), contractManage.getEndDate(), "合同期限开始日期不能晚于结束日期");
TransportBusinessSupport.validateLength(contractManage.getRemark(), 2000, "备注不能超过2000字"); TransportBusinessSupport.validateLength(contractManage.getRemark(), 2000, "备注不能超过2000字");
validateBillingPlans(contractManage.getBillingPlanJson());
validatePaymentRatios(contractManage.getPaymentRatioJson());
} }
private ContractManage loadExists(Long id) { private ContractManage loadExists(Long id) {
@@ -712,7 +848,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
private ContractManage loadEditable(Long id) { private ContractManage loadEditable(Long id) {
ContractManage contractManage = loadExists(id); ContractManage contractManage = loadExists(id);
if (!Objects.equals(contractManage.getApprovalStatus(), STATUS_DRAFT)) { if (!List.of(STATUS_DRAFT, STATUS_WITHDRAWN, STATUS_REJECTED).contains(contractManage.getApprovalStatus())) {
throw new ServiceException("当前状态不允许编辑"); throw new ServiceException("当前状态不允许编辑");
} }
if (!canOperateInCurrentScope(contractManage)) { if (!canOperateInCurrentScope(contractManage)) {
@@ -721,6 +857,26 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
return contractManage; return contractManage;
} }
private boolean canResubmitTemporary(ContractManage contractManage) {
String stage = contractManage.getContractStage();
String status = contractManage.getApprovalStatus();
if (!List.of(STATUS_DRAFT, STATUS_WITHDRAWN, STATUS_REJECTED).contains(status)) {
return false;
}
return Objects.equals(stage, STAGE_DRAFT) || Objects.equals(stage, STAGE_TEMPORARY);
}
private boolean canResubmitFormal(ContractManage contractManage) {
String stage = contractManage.getContractStage();
String status = contractManage.getApprovalStatus();
if (Objects.equals(stage, STAGE_TEMPORARY)
&& List.of(STATUS_REJECTED, STATUS_APPROVED, STATUS_DRAFT, STATUS_WITHDRAWN).contains(status)) {
return true;
}
return Objects.equals(stage, STAGE_FORMAL)
&& List.of(STATUS_REJECTED, STATUS_DRAFT, STATUS_WITHDRAWN).contains(status);
}
private ContractManage loadReviewing(Long id) { private ContractManage loadReviewing(Long id) {
ContractManage contractManage = loadExists(id); ContractManage contractManage = loadExists(id);
if (!List.of(STATUS_REVIEWING, STATUS_CHANGE_REVIEWING).contains(contractManage.getApprovalStatus())) { if (!List.of(STATUS_REVIEWING, STATUS_CHANGE_REVIEWING).contains(contractManage.getApprovalStatus())) {
@@ -774,6 +930,10 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
data.put("settlementCurrency", contractManage.getSettlementCurrency()); data.put("settlementCurrency", contractManage.getSettlementCurrency());
data.put("invoiceCycle", contractManage.getInvoiceCycle()); data.put("invoiceCycle", contractManage.getInvoiceCycle());
data.put("paymentDays", contractManage.getPaymentDays()); data.put("paymentDays", contractManage.getPaymentDays());
data.put("contractAmount", contractManage.getContractAmount());
data.put("templateFlag", contractManage.getTemplateFlag());
data.put("originalContractNo", contractManage.getOriginalContractNo());
data.put("electronicSealFlag", contractManage.getElectronicSealFlag());
data.put("remark", contractManage.getRemark()); data.put("remark", contractManage.getRemark());
data.put("feeGenerationMode", contractManage.getFeeGenerationMode()); data.put("feeGenerationMode", contractManage.getFeeGenerationMode());
data.put("billingPlanJson", contractManage.getBillingPlanJson()); data.put("billingPlanJson", contractManage.getBillingPlanJson());
@@ -361,6 +361,14 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl<ProjectApplyMapper,
queryWrapper.eq(ProjectApply::getEffectiveType, EFFECTIVE_FORMAL) queryWrapper.eq(ProjectApply::getEffectiveType, EFFECTIVE_FORMAL)
.in(ProjectApply::getApprovalStatus, STATUS_APPROVED, "change_approved"); .in(ProjectApply::getApprovalStatus, STATUS_APPROVED, "change_approved");
} }
if (Boolean.TRUE.equals(projectApply.getContractSelectable())) {
// 正式审批通过项目 + 未作废的临时项目均可被合同选择
queryWrapper.and(wrapper -> wrapper
.nested(formal -> formal.eq(ProjectApply::getEffectiveType, EFFECTIVE_FORMAL)
.in(ProjectApply::getApprovalStatus, STATUS_APPROVED, "change_approved"))
.or(temporary -> temporary.eq(ProjectApply::getEffectiveType, EFFECTIVE_TEMPORARY)
.ne(ProjectApply::getApprovalStatus, STATUS_VOIDED)));
}
if (Func.isNotEmpty(projectApply.getFundUseRisk())) { if (Func.isNotEmpty(projectApply.getFundUseRisk())) {
List<ProjectApplyVO> riskRecords = baseMapper.selectList(queryWrapper).stream() List<ProjectApplyVO> riskRecords = baseMapper.selectList(queryWrapper).stream()
.map(record -> ProjectApplyWrapper.build().entityVO(record)) .map(record -> ProjectApplyWrapper.build().entityVO(record))
@@ -102,7 +102,7 @@ public class ReceivablePayableDetailServiceImpl
private static final String SOURCE_MASTER_ORDER = "总单系统生成"; private static final String SOURCE_MASTER_ORDER = "总单系统生成";
private static final String SOURCE_LOADING_ORDER = "配载单系统生成"; private static final String SOURCE_LOADING_ORDER = "配载单系统生成";
private static final String SOURCE_MANUAL_GENERATION = "手动生成"; private static final String SOURCE_MANUAL_GENERATION = "账单导入生成";
private static final String TRANSPORT_TYPE_SELF = "自运"; private static final String TRANSPORT_TYPE_SELF = "自运";
private static final String FEE_SOURCE_AUTO = "自动生成"; private static final String FEE_SOURCE_AUTO = "自动生成";
private static final String FEE_SOURCE_MANUAL = "手动录入"; private static final String FEE_SOURCE_MANUAL = "手动录入";
@@ -900,7 +900,6 @@ public class ReceivablePayableDetailServiceImpl
if (!(conditionValue instanceof Map<?, ?> condition) || !hasConfiguredMatchCondition(condition)) return true; if (!(conditionValue instanceof Map<?, ?> condition) || !hasConfiguredMatchCondition(condition)) return true;
return matchesLocation(condition, "origin", waybill.getDepartureAddressId(), waybill.getDepartureName(), waybill.getDepartureAddress()) return matchesLocation(condition, "origin", waybill.getDepartureAddressId(), waybill.getDepartureName(), waybill.getDepartureAddress())
&& matchesLocation(condition, "destination", waybill.getArrivalAddressId(), waybill.getArrivalName(), waybill.getArrivalAddress()) && matchesLocation(condition, "destination", waybill.getArrivalAddressId(), waybill.getArrivalName(), waybill.getArrivalAddress())
&& matchesCondition(condition.get("transportMode"), waybill.getTransportType())
&& matchesCargo(condition, waybill); && matchesCargo(condition, waybill);
} }
@@ -909,7 +908,6 @@ public class ReceivablePayableDetailServiceImpl
|| !isBlank(condition.get("originCode")) || !isBlank(condition.get("originCode"))
|| !isBlank(condition.get("destination")) || !isBlank(condition.get("destination"))
|| !isBlank(condition.get("destinationCode")) || !isBlank(condition.get("destinationCode"))
|| !isBlank(condition.get("transportMode"))
|| !isBlank(condition.get("cargoType")) || !isBlank(condition.get("cargoType"))
|| !isBlank(condition.get("cargoTypeCode")) || !isBlank(condition.get("cargoTypeCode"))
|| !isBlank(condition.get("cargoTypePath")) || !isBlank(condition.get("cargoTypePath"))
@@ -1576,12 +1574,24 @@ public class ReceivablePayableDetailServiceImpl
if (ranges.isEmpty() || "固定单价".equals(type)) return base.multiply(unit).setScale(2, RoundingMode.HALF_UP); if (ranges.isEmpty() || "固定单价".equals(type)) return base.multiply(unit).setScale(2, RoundingMode.HALF_UP);
if (intervalFlatPrice) return range(ranges, base).map(r -> decimal(r.get("unitPrice")).signum() == 0 ? unit : decimal(r.get("unitPrice"))).orElse(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP); if (intervalFlatPrice) return range(ranges, base).map(r -> decimal(r.get("unitPrice")).signum() == 0 ? unit : decimal(r.get("unitPrice"))).orElse(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP);
if (intervalUnitPrice) { if (intervalUnitPrice) {
BigDecimal rangeBase = base; List<Map<String, Object>> sortedRanges = ranges.stream()
return range(ranges, rangeBase).map(r -> { .sorted(Comparator.comparing(item -> decimal(item.get("lowerLimit"))))
BigDecimal minimum = decimal(r.get("minimumBillingWeight")); .toList();
if (minimum.signum() <= 0) minimum = decimal(rule.get("minimumBillingWeight")); return range(sortedRanges, base).map(matchedRange -> {
BigDecimal effectiveBase = applyMinimum(rangeBase, element, minimum, waybill); boolean firstTier = !sortedRanges.isEmpty() && matchedRange == sortedRanges.get(0);
return decimal(r.get("unitPrice")).multiply(effectiveBase); BigDecimal effectiveBase = base;
if (firstTier) {
BigDecimal minimum = decimal(matchedRange.get("minimumBillingWeight"));
if (minimum.signum() <= 0) {
minimum = decimal(rule.get("minimumBillingWeight"));
}
effectiveBase = applyMinimum(base, element, minimum, waybill);
}
BigDecimal matchedUnitPrice = decimal(matchedRange.get("unitPrice"));
if (matchedUnitPrice.signum() == 0) {
matchedUnitPrice = unit;
}
return matchedUnitPrice.multiply(effectiveBase);
}).orElse(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP); }).orElse(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP);
} }
if ("阶梯单价".equals(type)) { if ("阶梯单价".equals(type)) {
@@ -71,6 +71,7 @@ public class ContractManageWrapper extends BaseEntityWrapper<ContractManage, Con
case "draft" -> "草稿"; case "draft" -> "草稿";
case "reviewing" -> "审批中"; case "reviewing" -> "审批中";
case "rejected" -> "已驳回"; case "rejected" -> "已驳回";
case "withdrawn" -> "已撤回";
case "approved" -> "审批通过"; case "approved" -> "审批通过";
case "change_reviewing" -> "变更审批中"; case "change_reviewing" -> "变更审批中";
case "change_rejected" -> "变更驳回"; case "change_rejected" -> "变更驳回";
@@ -0,0 +1,7 @@
-- 合同管理新增归档状态:默认未归档;审核通过且合同文件含双章归档文件时自动置为已归档
ALTER TABLE `blade_contract_manage`
ADD COLUMN `archive_status` varchar(50) DEFAULT '未归档' COMMENT '归档状态:未归档/已归档' AFTER `approval_status`;
UPDATE `blade_contract_manage`
SET `archive_status` = '未归档'
WHERE `archive_status` IS NULL OR `archive_status` = '';
@@ -0,0 +1,6 @@
-- 合同管理新增合同金额、范本、原件编号及电子章字段
ALTER TABLE `blade_contract_manage`
ADD COLUMN `contract_amount` decimal(18,2) DEFAULT NULL COMMENT '合同金额' AFTER `payment_days`,
ADD COLUMN `template_flag` int(11) DEFAULT '0' COMMENT '是否范本' AFTER `contract_amount`,
ADD COLUMN `original_contract_no` varchar(100) DEFAULT NULL COMMENT '原件合同编号' AFTER `template_flag`,
ADD COLUMN `electronic_seal_flag` int(11) DEFAULT '0' COMMENT '是否电子章' AFTER `original_contract_no`;
@@ -139,8 +139,13 @@ CREATE TABLE `blade_contract_manage` (
`settlement_currency` varchar(50) DEFAULT NULL COMMENT '结算币种', `settlement_currency` varchar(50) DEFAULT NULL COMMENT '结算币种',
`invoice_cycle` int(11) DEFAULT NULL COMMENT '开票周期(天)', `invoice_cycle` int(11) DEFAULT NULL COMMENT '开票周期(天)',
`payment_days` int(11) DEFAULT NULL COMMENT '回款账期(天)', `payment_days` int(11) DEFAULT NULL COMMENT '回款账期(天)',
`contract_amount` decimal(18,2) DEFAULT NULL COMMENT '合同金额',
`template_flag` int(11) DEFAULT '0' COMMENT '是否范本',
`original_contract_no` varchar(100) DEFAULT NULL COMMENT '原件合同编号',
`electronic_seal_flag` int(11) DEFAULT '0' COMMENT '是否电子章',
`contract_stage` varchar(50) DEFAULT NULL COMMENT '合同阶段', `contract_stage` varchar(50) DEFAULT NULL COMMENT '合同阶段',
`approval_status` varchar(50) DEFAULT NULL COMMENT '审核状态', `approval_status` varchar(50) DEFAULT NULL COMMENT '审核状态',
`archive_status` varchar(50) DEFAULT '未归档' COMMENT '归档状态:未归档/已归档',
`current_node` varchar(100) DEFAULT NULL COMMENT '当前节点', `current_node` varchar(100) DEFAULT NULL COMMENT '当前节点',
`current_processor` varchar(100) DEFAULT NULL COMMENT '当前处理人', `current_processor` varchar(100) DEFAULT NULL COMMENT '当前处理人',
`approved_time` datetime DEFAULT NULL COMMENT '审核通过时间', `approved_time` datetime DEFAULT NULL COMMENT '审核通过时间',