调整基础数据、车船务、项目、合同
This commit is contained in:
+6
@@ -34,6 +34,7 @@ import lombok.EqualsAndHashCode;
|
||||
import org.springblade.core.mp.base.BaseEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 费用项实体类
|
||||
@@ -64,6 +65,11 @@ public class FeeItem extends BaseEntity {
|
||||
*/
|
||||
@Schema(description = "费用项代码")
|
||||
private String englishName;
|
||||
/**
|
||||
* 税率(百分比)
|
||||
*/
|
||||
@Schema(description = "税率(百分比)")
|
||||
private BigDecimal taxRate;
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
|
||||
+7
-1
@@ -150,7 +150,7 @@ public class ContractManage extends TenantEntity {
|
||||
@Schema(description = "其它附件JSON")
|
||||
private String attachmentsJson;
|
||||
|
||||
@Schema(description = "计费方案JSON")
|
||||
@Schema(description = "计费方案JSON(含规则税率)")
|
||||
private String billingPlanJson;
|
||||
|
||||
@Schema(description = "结算生成规则JSON")
|
||||
@@ -172,11 +172,17 @@ public class ContractManage extends TenantEntity {
|
||||
private String changeRecordJson;
|
||||
|
||||
@Schema(description = "变更内容")
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
private String changeContent;
|
||||
|
||||
@Schema(description = "变更原因")
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
private String changeReason;
|
||||
|
||||
@Schema(description = "待审批变更附件JSON")
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
private String changeAttachmentsJson;
|
||||
|
||||
@Schema(description = "终止原因")
|
||||
private String terminateReason;
|
||||
|
||||
|
||||
+4
@@ -34,6 +34,7 @@ import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 费用项 Excel
|
||||
@@ -60,6 +61,9 @@ public class FeeItemExcel implements Serializable {
|
||||
@ExcelProperty("*费用项")
|
||||
private String name;
|
||||
|
||||
@ExcelProperty("*税率")
|
||||
private BigDecimal taxRate;
|
||||
|
||||
@ExcelIgnore
|
||||
private String errorMessage;
|
||||
|
||||
|
||||
+4
@@ -15,6 +15,7 @@ import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
@@ -40,6 +41,9 @@ public class FeeItemExportExcel implements Serializable {
|
||||
@ExcelProperty("费用项")
|
||||
private String name;
|
||||
|
||||
@ExcelProperty("税率")
|
||||
private BigDecimal taxRate;
|
||||
|
||||
@ExcelProperty("备注")
|
||||
private String remark;
|
||||
|
||||
|
||||
+4
@@ -33,6 +33,7 @@ import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 费用项导入失败 Excel
|
||||
@@ -56,6 +57,9 @@ public class FeeItemImportFailureExcel implements Serializable {
|
||||
@ExcelProperty("*费用项")
|
||||
private String name;
|
||||
|
||||
@ExcelProperty("*税率")
|
||||
private BigDecimal taxRate;
|
||||
|
||||
@ExcelProperty("导入失败原因")
|
||||
private String failureReason;
|
||||
|
||||
|
||||
+1
@@ -16,6 +16,7 @@
|
||||
<result column="fee_category" property="feeCategory"/>
|
||||
<result column="name" property="name"/>
|
||||
<result column="english_name" property="englishName"/>
|
||||
<result column="tax_rate" property="taxRate"/>
|
||||
<result column="remark" property="remark"/>
|
||||
</resultMap>
|
||||
|
||||
|
||||
+16
-1
@@ -47,6 +47,7 @@ import org.springblade.system.service.IFeeItemService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@@ -66,6 +67,8 @@ public class FeeItemServiceImpl extends BaseServiceImpl<FeeItemMapper, FeeItem>
|
||||
private static final int NAME_MAX_LENGTH = 50;
|
||||
private static final int ENGLISH_NAME_MAX_LENGTH = 100;
|
||||
private static final int REMARK_MAX_LENGTH = 200;
|
||||
private static final BigDecimal TAX_RATE_MIN = BigDecimal.ZERO;
|
||||
private static final BigDecimal TAX_RATE_MAX = new BigDecimal("100");
|
||||
|
||||
@Override
|
||||
public IPage<FeeItemVO> selectFeeItemPage(IPage<FeeItemVO> page, FeeItemVO feeItem) {
|
||||
@@ -115,7 +118,7 @@ public class FeeItemServiceImpl extends BaseServiceImpl<FeeItemMapper, FeeItem>
|
||||
save(feeItem);
|
||||
} catch (Exception exception) {
|
||||
String message = exception instanceof ServiceException ? exception.getMessage() : "导入失败";
|
||||
failureList.add(toImportFailureExcel(excel, message));
|
||||
failureList.add(toImportFailureExcel(excel, "第" + (index + 2) + "行:" + message));
|
||||
}
|
||||
}
|
||||
return failureList;
|
||||
@@ -156,6 +159,16 @@ public class FeeItemServiceImpl extends BaseServiceImpl<FeeItemMapper, FeeItem>
|
||||
if (Func.isNotEmpty(feeItem.getEnglishName()) && feeItem.getEnglishName().length() > ENGLISH_NAME_MAX_LENGTH) {
|
||||
throw new ServiceException("费用项代码不能超过100字");
|
||||
}
|
||||
BigDecimal taxRate = feeItem.getTaxRate();
|
||||
if (taxRate == null) {
|
||||
throw new ServiceException("税率不能为空");
|
||||
}
|
||||
if (taxRate.compareTo(TAX_RATE_MIN) < 0 || taxRate.compareTo(TAX_RATE_MAX) > 0) {
|
||||
throw new ServiceException("税率必须在0到100之间");
|
||||
}
|
||||
if (taxRate.stripTrailingZeros().scale() > 2) {
|
||||
throw new ServiceException("税率最多保留2位小数");
|
||||
}
|
||||
if (Func.isNotEmpty(feeItem.getRemark()) && feeItem.getRemark().length() > REMARK_MAX_LENGTH) {
|
||||
throw new ServiceException("备注不能超过200个字");
|
||||
}
|
||||
@@ -192,6 +205,7 @@ public class FeeItemServiceImpl extends BaseServiceImpl<FeeItemMapper, FeeItem>
|
||||
feeItem.setFeeCategory(resolveFeeCategory(excel.getFeeCategory()));
|
||||
feeItem.setEnglishName(trimToNull(excel.getEnglishName()));
|
||||
feeItem.setName(trimToEmpty(excel.getName()));
|
||||
feeItem.setTaxRate(excel.getTaxRate());
|
||||
feeItem.setStatus(STATUS_ENABLED);
|
||||
return feeItem;
|
||||
}
|
||||
@@ -212,6 +226,7 @@ public class FeeItemServiceImpl extends BaseServiceImpl<FeeItemMapper, FeeItem>
|
||||
failureExcel.setFeeCategory(excel.getFeeCategory());
|
||||
failureExcel.setEnglishName(excel.getEnglishName());
|
||||
failureExcel.setName(excel.getName());
|
||||
failureExcel.setTaxRate(excel.getTaxRate());
|
||||
failureExcel.setFailureReason(failureReason);
|
||||
return failureExcel;
|
||||
}
|
||||
|
||||
+1
-1
@@ -131,7 +131,7 @@ public class ContractManageController extends BladeController {
|
||||
@ApiOperationSupport(order = 10)
|
||||
@Operation(summary = "发起变更", description = "传入id、changeContent和changeReason")
|
||||
public R startChange(@Parameter(description = "主键", required = true) @RequestParam Long id,
|
||||
@RequestParam String changeContent,
|
||||
@RequestParam(required = false) String changeContent,
|
||||
@RequestParam String changeReason) {
|
||||
return R.status(contractManageService.startChange(id, changeContent, changeReason));
|
||||
}
|
||||
|
||||
+192
-9
@@ -42,16 +42,18 @@ import org.springblade.transport.wrapper.ContractManageWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 合同管理 服务实现类
|
||||
@@ -72,6 +74,9 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
private static final String STATUS_CHANGE_REVIEWING = "change_reviewing";
|
||||
private static final String STATUS_CHANGE_APPROVED = "change_approved";
|
||||
private static final String STATUS_CHANGE_REJECTED = "change_rejected";
|
||||
private static final long TEMPORARY_VALIDITY_DAYS = 30L;
|
||||
private static final BigDecimal TAX_RATE_MIN = BigDecimal.ZERO;
|
||||
private static final BigDecimal TAX_RATE_MAX = BigDecimal.valueOf(100);
|
||||
|
||||
@Override
|
||||
public IPage<ContractManageVO> selectContractManagePage(IPage<ContractManage> page, ContractManageVO contractManage) {
|
||||
@@ -93,7 +98,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
public boolean saveDraft(ContractManage contractManage) {
|
||||
prepare(contractManage);
|
||||
validateDraft(contractManage);
|
||||
if (!Objects.equals(contractManage.getContractStage(), STAGE_DRAFT)) {
|
||||
if (Func.isNotEmpty(contractManage.getId()) && !Objects.equals(contractManage.getContractStage(), STAGE_DRAFT)) {
|
||||
validateContractPartyCombinationUnique(contractManage);
|
||||
}
|
||||
if (Func.isNotEmpty(contractManage.getId())) {
|
||||
@@ -115,13 +120,16 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
prepare(contractManage);
|
||||
validateDraft(contractManage);
|
||||
validateTemporary(contractManage);
|
||||
if (Func.isNotEmpty(contractManage.getId())) {
|
||||
validateContractPartyCombinationUnique(contractManage);
|
||||
}
|
||||
if (Func.isNotEmpty(contractManage.getId())) {
|
||||
loadEditable(contractManage.getId());
|
||||
}
|
||||
prepareCreateOrUpdate(contractManage);
|
||||
contractManage.setContractStage(STAGE_TEMPORARY);
|
||||
contractManage.setApprovalStatus(STATUS_REVIEWING);
|
||||
applyTemporaryValidity(contractManage);
|
||||
appendCreateChangeRecordIfAbsent(contractManage);
|
||||
return saveOrUpdate(contractManage);
|
||||
}
|
||||
@@ -133,9 +141,11 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
if (!Objects.equals(contractManage.getContractStage(), STAGE_DRAFT) || !Objects.equals(contractManage.getApprovalStatus(), STATUS_DRAFT)) {
|
||||
throw new ServiceException("仅草稿合同允许转临时合同");
|
||||
}
|
||||
validateContractPartyCombinationUnique(contractManage);
|
||||
// 临时放开新增合同从草稿转临时合同时的组合重复校验。
|
||||
// validateContractPartyCombinationUnique(contractManage);
|
||||
contractManage.setContractStage(STAGE_TEMPORARY);
|
||||
contractManage.setApprovalStatus(STATUS_REVIEWING);
|
||||
applyTemporaryValidity(contractManage);
|
||||
contractManage.setCurrentNode("临时合同审批");
|
||||
contractManage.setCurrentProcessor("待处理");
|
||||
return updateById(contractManage);
|
||||
@@ -149,7 +159,8 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
throw new ServiceException("仅临时合同允许转正式合同");
|
||||
}
|
||||
validateFormal(contractManage);
|
||||
validateContractPartyCombinationUnique(contractManage);
|
||||
// 临时放开新增合同从临时转正式合同时的组合重复校验。
|
||||
// validateContractPartyCombinationUnique(contractManage);
|
||||
contractManage.setContractStage(STAGE_FORMAL);
|
||||
contractManage.setApprovalStatus(STATUS_REVIEWING);
|
||||
contractManage.setCurrentNode("正式合同审批");
|
||||
@@ -166,8 +177,14 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
contractManage.setCurrentNode("审批通过");
|
||||
contractManage.setCurrentProcessor(AuthUtil.getUserName());
|
||||
contractManage.setApprovedTime(LocalDateTime.now());
|
||||
if (Objects.equals(contractManage.getContractStage(), STAGE_TEMPORARY) && contractManage.getTemporaryStartDate() == null) {
|
||||
contractManage.setTemporaryStartDate(LocalDate.now());
|
||||
if (Objects.equals(contractManage.getContractStage(), STAGE_TEMPORARY)) {
|
||||
ensureTemporaryValidity(contractManage);
|
||||
}
|
||||
if (changeReviewing) {
|
||||
contractManage.setAttachmentsJson(mergeAttachmentJson(contractManage.getAttachmentsJson(), contractManage.getChangeAttachmentsJson()));
|
||||
contractManage.setChangeAttachmentsJson(null);
|
||||
contractManage.setChangeContent(null);
|
||||
contractManage.setChangeReason(null);
|
||||
}
|
||||
updateLatestReviewingChangeRecord(contractManage, contractManage.getApprovalStatus(), "审核通过");
|
||||
return updateById(contractManage);
|
||||
@@ -181,6 +198,11 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
contractManage.setApprovalStatus(changeReviewing ? STATUS_CHANGE_REJECTED : STATUS_REJECTED);
|
||||
contractManage.setCurrentNode("已驳回");
|
||||
contractManage.setCurrentProcessor(AuthUtil.getUserName());
|
||||
if (changeReviewing) {
|
||||
contractManage.setChangeAttachmentsJson(null);
|
||||
contractManage.setChangeContent(null);
|
||||
contractManage.setChangeReason(null);
|
||||
}
|
||||
updateLatestReviewingChangeRecord(contractManage, contractManage.getApprovalStatus(), "审核不通过");
|
||||
return updateById(contractManage);
|
||||
}
|
||||
@@ -193,6 +215,11 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
contractManage.setApprovalStatus(changeReviewing ? STATUS_APPROVED : STATUS_DRAFT);
|
||||
contractManage.setCurrentNode("已撤回");
|
||||
contractManage.setCurrentProcessor(AuthUtil.getUserName());
|
||||
if (changeReviewing) {
|
||||
contractManage.setChangeAttachmentsJson(null);
|
||||
contractManage.setChangeContent(null);
|
||||
contractManage.setChangeReason(null);
|
||||
}
|
||||
updateLatestReviewingChangeRecord(contractManage, "withdrawn", "已撤回");
|
||||
return updateById(contractManage);
|
||||
}
|
||||
@@ -205,9 +232,12 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
|| !List.of(STATUS_APPROVED, STATUS_CHANGE_APPROVED, STATUS_CHANGE_REJECTED).contains(contractManage.getApprovalStatus())) {
|
||||
throw new ServiceException("仅正式合同允许发起变更");
|
||||
}
|
||||
String normalizedReason = TransportBusinessSupport.trimToNull(changeReason);
|
||||
TransportBusinessSupport.validateRequired(normalizedReason, "请输入变更原因");
|
||||
TransportBusinessSupport.validateLength(normalizedReason, 500, "变更原因不能超过500字");
|
||||
contractManage.setApprovalStatus(STATUS_CHANGE_REVIEWING);
|
||||
contractManage.setChangeContent(TransportBusinessSupport.trimToNull(changeContent));
|
||||
contractManage.setChangeReason(TransportBusinessSupport.trimToNull(changeReason));
|
||||
contractManage.setChangeReason(normalizedReason);
|
||||
contractManage.setCurrentNode("合同变更审批");
|
||||
contractManage.setCurrentProcessor("待处理");
|
||||
appendChangeRecord(contractManage, "合同信息变更", contractManage.getChangeReason(), STATUS_CHANGE_REVIEWING, "审核中");
|
||||
@@ -223,12 +253,16 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
throw new ServiceException("当前合同状态不允许发起变更");
|
||||
}
|
||||
TransportBusinessSupport.validateRequired(request.getSettlementCurrency(), "请选择结算币种");
|
||||
String changeReason = TransportBusinessSupport.trimToNull(request.getChangeReason());
|
||||
TransportBusinessSupport.validateRequired(changeReason, "请输入变更原因");
|
||||
TransportBusinessSupport.validateLength(changeReason, 500, "变更原因不能超过500字");
|
||||
ContractManage candidate = new ContractManage();
|
||||
BeanUtil.copyProperties(source, candidate);
|
||||
candidate.setPartyB(TransportBusinessSupport.trimToNull(request.getPartyB()));
|
||||
validateContractPartyCombinationUnique(candidate);
|
||||
validateBillingPlans(request.getBillingPlanJson());
|
||||
validatePaymentRatios(request.getPaymentRatioJson());
|
||||
Map<String, Object> beforeData = buildContractChangeSnapshot(source);
|
||||
source.setContractName(request.getContractName()); source.setPartyB(request.getPartyB());
|
||||
source.setStartDate(request.getStartDate()); source.setEndDate(request.getEndDate()); source.setContractFormat(request.getContractFormat());
|
||||
source.setSettlementMode(request.getSettlementMode()); source.setLegalSealFlag(request.getLegalSealFlag()); source.setCopyCount(request.getCopyCount());
|
||||
@@ -237,8 +271,16 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
normalizeOptionalIntegerFields(source);
|
||||
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());
|
||||
updateById(source);
|
||||
return startChange(source.getId(), request.getChangeContent(), request.getChangeReason());
|
||||
source.setChangeAttachmentsJson(TransportBusinessSupport.trimToNull(request.getChangeAttachmentsJson()));
|
||||
Map<String, Object> afterData = buildContractChangeSnapshot(source);
|
||||
retainChangedSnapshotFields(beforeData, afterData);
|
||||
source.setApprovalStatus(STATUS_CHANGE_REVIEWING);
|
||||
source.setChangeContent(TransportBusinessSupport.trimToNull(request.getChangeContent()));
|
||||
source.setChangeReason(changeReason);
|
||||
source.setCurrentNode("合同变更审批");
|
||||
source.setCurrentProcessor("待处理");
|
||||
appendChangeRecord(source, "合同信息变更", changeReason, STATUS_CHANGE_REVIEWING, "审核中", beforeData, afterData);
|
||||
return updateById(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -299,6 +341,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
target.setApprovedTime(null);
|
||||
target.setChangeContent(null);
|
||||
target.setChangeReason(null);
|
||||
target.setChangeAttachmentsJson(null);
|
||||
target.setTerminateReason(null);
|
||||
saveDraft(target);
|
||||
return detail(target.getId());
|
||||
@@ -501,6 +544,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
}
|
||||
}
|
||||
validateBillingMatchConditions(plan);
|
||||
validateBillingRuleTaxRates(plan);
|
||||
}
|
||||
} catch (ServiceException exception) {
|
||||
throw exception;
|
||||
@@ -525,6 +569,34 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
}
|
||||
}
|
||||
|
||||
private void validateBillingRuleTaxRates(Map<?, ?> plan) {
|
||||
if (!(plan.get("rules") instanceof List<?> rules)) return;
|
||||
for (int index = 0; index < rules.size(); index++) {
|
||||
Object ruleValue = rules.get(index);
|
||||
if (!(ruleValue instanceof Map<?, ?> rule)) continue;
|
||||
Object value = rule.get("taxRate");
|
||||
if (value == null || String.valueOf(value).isBlank()) {
|
||||
throw new ServiceException("计费方案第" + (index + 1) + "行税率不能为空");
|
||||
}
|
||||
String taxRateText = String.valueOf(value).trim();
|
||||
if (!taxRateText.matches("\\d+(\\.\\d{1,2})?")) {
|
||||
throw new ServiceException("计费方案第" + (index + 1) + "行税率格式不正确");
|
||||
}
|
||||
BigDecimal taxRate;
|
||||
try {
|
||||
taxRate = new BigDecimal(taxRateText);
|
||||
} catch (NumberFormatException exception) {
|
||||
throw new ServiceException("计费方案第" + (index + 1) + "行税率格式不正确");
|
||||
}
|
||||
if (taxRate.compareTo(TAX_RATE_MIN) < 0 || taxRate.compareTo(TAX_RATE_MAX) > 0) {
|
||||
throw new ServiceException("计费方案第" + (index + 1) + "行税率必须在0到100之间");
|
||||
}
|
||||
if (taxRate.stripTrailingZeros().scale() > 2) {
|
||||
throw new ServiceException("计费方案第" + (index + 1) + "行税率最多保留2位小数");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasConfiguredValue(Object value) {
|
||||
if (value instanceof Collection<?> values) {
|
||||
return values.stream().anyMatch(this::hasConfiguredValue);
|
||||
@@ -597,6 +669,29 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
TransportBusinessSupport.validateRequired(contractManage.getSignType(), "请选择签约类型");
|
||||
}
|
||||
|
||||
/**
|
||||
* 临时合同提交时从当前日期开始计算临时效力期,避免使用客户端传入的日期。
|
||||
*/
|
||||
private void applyTemporaryValidity(ContractManage contractManage) {
|
||||
LocalDate temporaryStartDate = LocalDate.now();
|
||||
contractManage.setTemporaryStartDate(temporaryStartDate);
|
||||
contractManage.setTemporaryEndDate(temporaryStartDate.plusDays(TEMPORARY_VALIDITY_DAYS));
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批历史临时合同时仅补齐缺失的日期,不覆盖已按提交时间保存的效力期。
|
||||
*/
|
||||
private void ensureTemporaryValidity(ContractManage contractManage) {
|
||||
LocalDate temporaryStartDate = contractManage.getTemporaryStartDate();
|
||||
if (temporaryStartDate == null) {
|
||||
temporaryStartDate = LocalDate.now();
|
||||
contractManage.setTemporaryStartDate(temporaryStartDate);
|
||||
}
|
||||
if (contractManage.getTemporaryEndDate() == null) {
|
||||
contractManage.setTemporaryEndDate(temporaryStartDate.plusDays(TEMPORARY_VALIDITY_DAYS));
|
||||
}
|
||||
}
|
||||
|
||||
private void validateFormal(ContractManage contractManage) {
|
||||
TransportBusinessSupport.validateRequired(contractManage.getSettlementCurrency(), "请选择结算币种");
|
||||
TransportBusinessSupport.validateRequired(contractManage.getPartyA(), "请输入甲方");
|
||||
@@ -643,19 +738,66 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
}
|
||||
|
||||
private void appendChangeRecord(ContractManage contractManage, String changeType, String changeReason, String status, String statusName) {
|
||||
appendChangeRecord(contractManage, changeType, changeReason, status, statusName, null, null);
|
||||
}
|
||||
|
||||
private void appendChangeRecord(ContractManage contractManage, String changeType, String changeReason, String status,
|
||||
String statusName, Map<String, Object> beforeData, Map<String, Object> afterData) {
|
||||
List<Map<String, Object>> records = parseChangeRecords(contractManage.getChangeRecordJson());
|
||||
Map<String, Object> record = new LinkedHashMap<>();
|
||||
record.put("changeDate", LocalDate.now().toString());
|
||||
record.put("handlerUserId", AuthUtil.getUserId());
|
||||
record.put("handlerUserName", AuthUtil.getUserName());
|
||||
record.put("changeType", changeType);
|
||||
record.put("changeContent", TransportBusinessSupport.trimToNull(contractManage.getChangeContent()));
|
||||
record.put("changeReason", TransportBusinessSupport.trimToNull(changeReason));
|
||||
record.put("status", status);
|
||||
record.put("statusName", statusName);
|
||||
if (beforeData != null && afterData != null) {
|
||||
record.put("beforeData", beforeData);
|
||||
record.put("afterData", afterData);
|
||||
}
|
||||
records.add(record);
|
||||
contractManage.setChangeRecordJson(JsonUtil.toJson(records));
|
||||
}
|
||||
|
||||
private Map<String, Object> buildContractChangeSnapshot(ContractManage contractManage) {
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("contractName", contractManage.getContractName());
|
||||
data.put("partyB", contractManage.getPartyB());
|
||||
data.put("startDate", contractManage.getStartDate());
|
||||
data.put("endDate", contractManage.getEndDate());
|
||||
data.put("contractFormat", contractManage.getContractFormat());
|
||||
data.put("settlementMode", contractManage.getSettlementMode());
|
||||
data.put("legalSealFlag", contractManage.getLegalSealFlag());
|
||||
data.put("copyCount", contractManage.getCopyCount());
|
||||
data.put("settlementCurrency", contractManage.getSettlementCurrency());
|
||||
data.put("invoiceCycle", contractManage.getInvoiceCycle());
|
||||
data.put("paymentDays", contractManage.getPaymentDays());
|
||||
data.put("remark", contractManage.getRemark());
|
||||
data.put("feeGenerationMode", contractManage.getFeeGenerationMode());
|
||||
data.put("billingPlanJson", contractManage.getBillingPlanJson());
|
||||
data.put("settlementRuleJson", contractManage.getSettlementRuleJson());
|
||||
data.put("preSettlementConfigJson", contractManage.getPreSettlementConfigJson());
|
||||
data.put("formalSettlementConfigJson", contractManage.getFormalSettlementConfigJson());
|
||||
data.put("paymentRatioJson", contractManage.getPaymentRatioJson());
|
||||
data.put("contractFileJson", contractManage.getContractFileJson());
|
||||
data.put("attachmentsJson", contractManage.getAttachmentsJson());
|
||||
data.put("changeAttachmentsJson", contractManage.getChangeAttachmentsJson());
|
||||
return data;
|
||||
}
|
||||
|
||||
private void retainChangedSnapshotFields(Map<String, Object> beforeData, Map<String, Object> afterData) {
|
||||
Set<String> changedFields = new HashSet<>();
|
||||
beforeData.forEach((field, value) -> {
|
||||
if (!Objects.equals(value, afterData.get(field))) {
|
||||
changedFields.add(field);
|
||||
}
|
||||
});
|
||||
beforeData.keySet().retainAll(changedFields);
|
||||
afterData.keySet().retainAll(changedFields);
|
||||
}
|
||||
|
||||
private void updateLatestReviewingChangeRecord(ContractManage contractManage, String status, String statusName) {
|
||||
List<Map<String, Object>> records = parseChangeRecords(contractManage.getChangeRecordJson());
|
||||
for (int index = records.size() - 1; index >= 0; index--) {
|
||||
@@ -669,6 +811,47 @@ public class ContractManageServiceImpl extends BaseServiceImpl<ContractManageMap
|
||||
}
|
||||
}
|
||||
|
||||
private String mergeAttachmentJson(String existingJson, String pendingJson) {
|
||||
List<Object> merged = parseJsonArray(existingJson);
|
||||
List<Object> pending = parseJsonArray(pendingJson);
|
||||
if (pending.isEmpty()) {
|
||||
return TransportBusinessSupport.trimToNull(existingJson);
|
||||
}
|
||||
Set<String> keys = new HashSet<>();
|
||||
merged.forEach(item -> keys.add(attachmentKey(item)));
|
||||
pending.forEach(item -> {
|
||||
if (item != null && keys.add(attachmentKey(item))) {
|
||||
merged.add(item);
|
||||
}
|
||||
});
|
||||
return merged.isEmpty() ? null : JsonUtil.toJson(merged);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Object> parseJsonArray(String value) {
|
||||
if (Func.isEmpty(value)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
try {
|
||||
Object parsed = JsonUtil.parse(value, List.class);
|
||||
return parsed instanceof List<?> list ? new ArrayList<>((List<Object>) list) : new ArrayList<>();
|
||||
} catch (Exception ignored) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
private String attachmentKey(Object attachment) {
|
||||
if (attachment instanceof Map<?, ?> map) {
|
||||
for (String field : List.of("url", "link", "fileUrl", "downloadUrl", "uid", "originalName", "name")) {
|
||||
Object value = map.get(field);
|
||||
if (value != null && !String.valueOf(value).isBlank()) {
|
||||
return field + ":" + value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return String.valueOf(attachment);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Map<String, Object>> parseChangeRecords(String value) {
|
||||
if (Func.isEmpty(value)) {
|
||||
|
||||
+3
@@ -297,6 +297,9 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl<ProjectApplyMapper,
|
||||
|
||||
private void prepareCreateOrUpdate(ProjectApply projectApply) {
|
||||
if (Func.isEmpty(projectApply.getId())) {
|
||||
// 新增项目不产生变更记录,避免客户端误传变更字段导致记录回显。
|
||||
projectApply.setChangeContent(null);
|
||||
projectApply.setChangeReason(null);
|
||||
if (Func.isEmpty(projectApply.getApplyNo())) {
|
||||
projectApply.setApplyNo(nextApplyNo());
|
||||
}
|
||||
|
||||
+17
-17
@@ -941,22 +941,29 @@ public class ReceivablePayableDetailServiceImpl
|
||||
Map<String, Object> goods, String fallbackName) {
|
||||
Object actualPath = goods.get("cargoTypePath");
|
||||
if (matchesCargoTypePath(expectedPath, actualPath)) return true;
|
||||
String actualCode = firstNotBlank(
|
||||
goods.get("cargoTypeCode"),
|
||||
goods.get("secondCargoTypeCode"),
|
||||
goods.get("firstCargoTypeCode"));
|
||||
if (!isBlank(expectedCode) && Func.isNotEmpty(actualCode)) {
|
||||
String expectedCodeText = String.valueOf(expectedCode).trim();
|
||||
if (expectedCodeText.equals(actualCode)) return true;
|
||||
if (isFirstLevelCargoType(expectedPath, expectedCodeText)
|
||||
&& actualCode.startsWith(expectedCodeText)) return true;
|
||||
}
|
||||
if (matchesCargoTypeCode(expectedPath, expectedCode, goods)) return true;
|
||||
if (isBlank(expectedName)) return false;
|
||||
return matchesCondition(expectedName, stringValue(goods, "cargoType", fallbackName))
|
||||
|| matchesCondition(expectedName, stringValue(goods, "secondCargoTypeName", ""))
|
||||
|| matchesCondition(expectedName, stringValue(goods, "firstCargoTypeName", ""));
|
||||
}
|
||||
|
||||
/**
|
||||
* 一级货物类型使用父级编码匹配二级货物编码。历史货物明细可能同时存在多个编码字段,
|
||||
* 不能只取第一个非空值,否则二级编码会遮蔽可用于前缀匹配的一级编码。
|
||||
*/
|
||||
private boolean matchesCargoTypeCode(Object expectedPath, Object expectedCode,
|
||||
Map<String, Object> goods) {
|
||||
if (isBlank(expectedCode)) return false;
|
||||
String expected = String.valueOf(expectedCode).trim();
|
||||
boolean firstLevel = isFirstLevelCargoType(expectedPath, expected);
|
||||
return java.util.stream.Stream.of(goods.get("cargoTypeCode"), goods.get("secondCargoTypeCode"),
|
||||
goods.get("firstCargoTypeCode"))
|
||||
.filter(value -> !isBlank(value))
|
||||
.map(value -> String.valueOf(value).trim())
|
||||
.anyMatch(actual -> expected.equals(actual) || (firstLevel && actual.startsWith(expected)));
|
||||
}
|
||||
|
||||
private boolean matchesCargoTypePath(Object expectedPath, Object actualPath) {
|
||||
if (!(expectedPath instanceof Collection<?> expectedValues)
|
||||
|| !(actualPath instanceof Collection<?> actualValues)
|
||||
@@ -978,13 +985,6 @@ public class ReceivablePayableDetailServiceImpl
|
||||
return expectedCode.matches("\\d{2}");
|
||||
}
|
||||
|
||||
private String firstNotBlank(Object... values) {
|
||||
for (Object value : values) {
|
||||
if (!isBlank(value)) return String.valueOf(value).trim();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private String resolveRegionCode(Long addressId) {
|
||||
if (addressId == null) return "";
|
||||
try {
|
||||
|
||||
@@ -6521,6 +6521,7 @@ CREATE TABLE "BLADEX"."BLADE_FEE_ITEM" (
|
||||
"ID" NUMBER(20,0) NOT NULL,
|
||||
"NAME" NVARCHAR2(50) NOT NULL,
|
||||
"ENGLISH_NAME" NVARCHAR2(100),
|
||||
"TAX_RATE" NUMBER(6,2) DEFAULT 0 NOT NULL,
|
||||
"CREATE_USER" NUMBER(20,0),
|
||||
"CREATE_DEPT" NUMBER(20,0),
|
||||
"CREATE_TIME" DATE,
|
||||
|
||||
@@ -2565,6 +2565,7 @@ CREATE TABLE "blade_fee_item" (
|
||||
"fee_category" varchar(50) NOT NULL,
|
||||
"name" varchar(50) NOT NULL,
|
||||
"english_name" varchar(100),
|
||||
"tax_rate" numeric(6,2) NOT NULL DEFAULT 0.00,
|
||||
"create_user" int8,
|
||||
"create_dept" int8,
|
||||
"create_time" timestamp(6),
|
||||
|
||||
@@ -1721,6 +1721,7 @@ CREATE TABLE `blade_fee_item` (
|
||||
`fee_category` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '费用类型',
|
||||
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '费用项',
|
||||
`english_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '费用项代码',
|
||||
`tax_rate` decimal(6,2) NOT NULL DEFAULT '0.00' COMMENT '税率(百分比)',
|
||||
`remark` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
|
||||
`create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
|
||||
`create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门',
|
||||
|
||||
@@ -6755,6 +6755,7 @@ CREATE TABLE "BLADE_FEE_ITEM" (
|
||||
"ID" NUMBER(20,0) NOT NULL,
|
||||
"NAME" NVARCHAR2(50) NOT NULL,
|
||||
"ENGLISH_NAME" NVARCHAR2(100),
|
||||
"TAX_RATE" NUMBER(6,2) DEFAULT 0 NOT NULL,
|
||||
"CREATE_USER" NUMBER(20,0),
|
||||
"CREATE_DEPT" NUMBER(20,0),
|
||||
"CREATE_TIME" DATE,
|
||||
|
||||
@@ -2565,6 +2565,7 @@ CREATE TABLE "blade_fee_item" (
|
||||
"fee_category" varchar(50) NOT NULL,
|
||||
"name" varchar(50) NOT NULL,
|
||||
"english_name" varchar(100),
|
||||
"tax_rate" numeric(6,2) NOT NULL DEFAULT 0.00,
|
||||
"create_user" int8,
|
||||
"create_dept" int8,
|
||||
"create_time" timestamp(6),
|
||||
|
||||
@@ -7730,6 +7730,7 @@ CREATE TABLE [dbo].[blade_fee_item] (
|
||||
[fee_category] nvarchar(50) NOT NULL,
|
||||
[name] nvarchar(50) NOT NULL,
|
||||
[english_name] nvarchar(100) NULL,
|
||||
[tax_rate] decimal(6,2) NOT NULL CONSTRAINT [df_blade_fee_item_tax_rate] DEFAULT (0.00),
|
||||
[create_user] bigint NULL,
|
||||
[create_dept] bigint NULL,
|
||||
[create_time] datetime2(0) NULL,
|
||||
|
||||
@@ -28645,6 +28645,7 @@ CREATE TABLE "BLADE_FEE_ITEM" (
|
||||
"ID" NUMBER(20,0) NOT NULL,
|
||||
"NAME" NVARCHAR2(50) NOT NULL,
|
||||
"ENGLISH_NAME" NVARCHAR2(100),
|
||||
"TAX_RATE" NUMBER(6,2) DEFAULT 0 NOT NULL,
|
||||
"CREATE_USER" NUMBER(20,0),
|
||||
"CREATE_DEPT" NUMBER(20,0),
|
||||
"CREATE_TIME" DATE,
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
-- 合同计费方案规则新增税率配置。
|
||||
-- 税率按规则存储于 billing_plan_json 的 taxRate 属性,无需新增独立表字段。
|
||||
|
||||
ALTER TABLE `blade_contract_manage`
|
||||
MODIFY COLUMN `billing_plan_json` text DEFAULT NULL COMMENT '计费方案JSON(含运输方式、默认方案及规则税率配置)';
|
||||
@@ -0,0 +1,3 @@
|
||||
-- 合同变更附件暂存字段,审批通过后归集到其它附件
|
||||
ALTER TABLE `blade_contract_manage`
|
||||
ADD COLUMN `change_attachments_json` text DEFAULT NULL COMMENT '待审批变更附件JSON' AFTER `change_reason`;
|
||||
@@ -7,6 +7,7 @@ CREATE TABLE `blade_fee_item` (
|
||||
`fee_category` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '费用类型',
|
||||
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '费用项',
|
||||
`english_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '费用项代码',
|
||||
`tax_rate` decimal(6,2) NOT NULL DEFAULT '0.00' COMMENT '税率(百分比)',
|
||||
`remark` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
|
||||
`create_user` bigint NULL DEFAULT NULL COMMENT '创建人',
|
||||
`create_dept` bigint NULL DEFAULT NULL COMMENT '创建部门',
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
-- 费用项新增税率字段(百分比)
|
||||
-- 现有数据以 0.00 初始化,后续新增或修改费用项必须填写 0-100 范围内的税率。
|
||||
|
||||
ALTER TABLE `blade_fee_item`
|
||||
ADD COLUMN `tax_rate` decimal(6,2) NOT NULL DEFAULT '0.00' COMMENT '税率(百分比)' AFTER `english_name`;
|
||||
@@ -144,12 +144,13 @@ CREATE TABLE `blade_contract_manage` (
|
||||
`billing_enabled` int(11) DEFAULT '0' COMMENT '计费信息开关',
|
||||
`contract_file_json` text DEFAULT NULL COMMENT '合同主文件JSON',
|
||||
`attachments_json` text DEFAULT NULL COMMENT '其它附件JSON',
|
||||
`billing_plan_json` text DEFAULT NULL COMMENT '计费方案JSON(含运输方式及默认方案配置)',
|
||||
`billing_plan_json` text DEFAULT NULL COMMENT '计费方案JSON(含运输方式、默认方案及规则税率配置)',
|
||||
`settlement_rule_json` text DEFAULT NULL COMMENT '结算生成规则JSON',
|
||||
`reconciliation_json` text DEFAULT NULL COMMENT '对账配置JSON',
|
||||
`change_record_json` text DEFAULT NULL COMMENT '变更记录JSON',
|
||||
`change_content` text DEFAULT NULL COMMENT '变更内容',
|
||||
`change_reason` varchar(500) DEFAULT NULL COMMENT '变更原因',
|
||||
`change_attachments_json` text DEFAULT NULL COMMENT '待审批变更附件JSON',
|
||||
`terminate_reason` varchar(500) DEFAULT NULL COMMENT '终止原因',
|
||||
`remark` varchar(2000) DEFAULT NULL COMMENT '备注',
|
||||
PRIMARY KEY (`id`) USING BTREE,
|
||||
|
||||
Reference in New Issue
Block a user