diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ContractManage.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ContractManage.java index f703836..d56fec7 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ContractManage.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ContractManage.java @@ -31,6 +31,7 @@ import lombok.EqualsAndHashCode; import org.springblade.core.tenant.mp.TenantEntity; import java.io.Serial; +import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; @@ -123,12 +124,31 @@ public class ContractManage extends TenantEntity { @TableField(insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS) 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 = "合同阶段") private String contractStage; @Schema(description = "审核状态") private String approvalStatus; + @Schema(description = "归档状态:未归档/已归档") + private String archiveStatus; + @Schema(description = "当前节点") private String currentNode; @@ -141,7 +161,7 @@ public class ContractManage extends TenantEntity { @Schema(description = "计费信息开关") private Integer billingEnabled; - @Schema(description = "费用生成模式:system系统生成,manual手动生成") + @Schema(description = "费用生成模式:system系统生成,manual账单导入生成") private String feeGenerationMode; @Schema(description = "合同主文件JSON") diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ProjectApplyVO.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ProjectApplyVO.java index 8ac54d1..0b6fa77 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ProjectApplyVO.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/vo/ProjectApplyVO.java @@ -80,6 +80,10 @@ public class ProjectApplyVO extends ProjectApply { @Schema(description = "是否仅查询可用于临时额度申请的项目") private Boolean temporaryCreditLimitSelectable; + @TableField(exist = false) + @Schema(description = "是否仅查询可用于合同选择的项目(含临时项目与正式审批通过项目)") + private Boolean contractSelectable; + @TableField(exist = false) @Schema(description = "资金使用风险等级:high、medium、none") private String fundUseRisk; diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ContractManageExcel.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ContractManageExcel.java index 2802c14..f55fb68 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ContractManageExcel.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/excel/ContractManageExcel.java @@ -23,6 +23,7 @@ package org.springblade.transport.excel; 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.ContentRowHeight; import cn.idev.excel.annotation.write.style.HeadRowHeight; @@ -30,6 +31,7 @@ import lombok.Data; import java.io.Serial; import java.io.Serializable; +import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; @@ -77,6 +79,8 @@ public class ContractManageExcel implements Serializable { private String contractStage; @ExcelProperty("审核状态") private String approvalStatus; + @ExcelProperty("归档状态") + private String archiveStatus; @ExcelProperty("结算币种") private String settlementCurrency; @ExcelProperty("结算方式") @@ -87,6 +91,15 @@ public class ContractManageExcel implements Serializable { private Integer copyCount; @ExcelProperty("回款账期(天)") private Integer paymentDays; + @ExcelProperty("合同金额") + @NumberFormat("0.00") + private BigDecimal contractAmount; + @ExcelProperty("是否范本") + private Integer templateFlag; + @ExcelProperty("原件合同编号") + private String originalContractNo; + @ExcelProperty("是否电子章") + private Integer electronicSealFlag; @ExcelProperty("当前节点") private String currentNode; @ExcelProperty("当前处理人") diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java index 1c37acb..51dc7c5 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ContractManageServiceImpl.java @@ -71,9 +71,13 @@ public class ContractManageServiceImpl extends BaseServiceImpl { + 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 files = parseJsonArray(contractFileJson); + if (files.isEmpty()) { + return TransportBusinessSupport.trimToNull(contractFileJson); + } + List> marked = new ArrayList<>(); + for (Object item : files) { + if (item instanceof Map map) { + Map next = new LinkedHashMap<>((Map) 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 originalFiles = parseJsonArray(originalJson); + List requestFiles = parseJsonArray(requestJson); + Map> originalApproved = new LinkedHashMap<>(); + for (Object item : originalFiles) { + if (!(item instanceof Map map)) { + continue; + } + Map file = new LinkedHashMap<>((Map) map); + if (isAttachmentApproved(file)) { + originalApproved.put(attachmentKey(file), file); + } + } + Set requestKeys = new HashSet<>(); + List> merged = new ArrayList<>(); + for (Object item : requestFiles) { + if (!(item instanceof Map map)) { + continue; + } + Map file = new LinkedHashMap<>((Map) map); + String key = attachmentKey(file); + requestKeys.add(key); + Map approvedOriginal = originalApproved.get(key); + if (approvedOriginal != null) { + // 已审核通过附件不允许改删,保留原记录。 + merged.add(approvedOriginal); + continue; + } + file.put("approved", true); + merged.add(file); + } + for (Map.Entry> entry : originalApproved.entrySet()) { + if (!requestKeys.contains(entry.getKey())) { + throw new ServiceException("已审核通过的合同文件不允许删除"); + } + } + return merged.isEmpty() ? null : JsonUtil.toJson(merged); + } + + private boolean isAttachmentApproved(Map 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) { @@ -490,6 +596,7 @@ public class ContractManageServiceImpl extends BaseServiceImpl 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) { @@ -515,11 +641,8 @@ public class ContractManageServiceImpl extends BaseServiceImpl 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())) { List riskRecords = baseMapper.selectList(queryWrapper).stream() .map(record -> ProjectApplyWrapper.build().entityVO(record)) diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java index 3e8c377..d5f0187 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ReceivablePayableDetailServiceImpl.java @@ -102,7 +102,7 @@ public class ReceivablePayableDetailServiceImpl private static final String SOURCE_MASTER_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 FEE_SOURCE_AUTO = "自动生成"; private static final String FEE_SOURCE_MANUAL = "手动录入"; @@ -900,7 +900,6 @@ public class ReceivablePayableDetailServiceImpl if (!(conditionValue instanceof Map condition) || !hasConfiguredMatchCondition(condition)) return true; return matchesLocation(condition, "origin", waybill.getDepartureAddressId(), waybill.getDepartureName(), waybill.getDepartureAddress()) && matchesLocation(condition, "destination", waybill.getArrivalAddressId(), waybill.getArrivalName(), waybill.getArrivalAddress()) - && matchesCondition(condition.get("transportMode"), waybill.getTransportType()) && matchesCargo(condition, waybill); } @@ -909,7 +908,6 @@ public class ReceivablePayableDetailServiceImpl || !isBlank(condition.get("originCode")) || !isBlank(condition.get("destination")) || !isBlank(condition.get("destinationCode")) - || !isBlank(condition.get("transportMode")) || !isBlank(condition.get("cargoType")) || !isBlank(condition.get("cargoTypeCode")) || !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 (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) { - BigDecimal rangeBase = base; - return range(ranges, rangeBase).map(r -> { - BigDecimal minimum = decimal(r.get("minimumBillingWeight")); - if (minimum.signum() <= 0) minimum = decimal(rule.get("minimumBillingWeight")); - BigDecimal effectiveBase = applyMinimum(rangeBase, element, minimum, waybill); - return decimal(r.get("unitPrice")).multiply(effectiveBase); + List> sortedRanges = ranges.stream() + .sorted(Comparator.comparing(item -> decimal(item.get("lowerLimit")))) + .toList(); + return range(sortedRanges, base).map(matchedRange -> { + boolean firstTier = !sortedRanges.isEmpty() && matchedRange == sortedRanges.get(0); + 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); } if ("阶梯单价".equals(type)) { diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/ContractManageWrapper.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/ContractManageWrapper.java index 6d03a37..b4571b9 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/ContractManageWrapper.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/wrapper/ContractManageWrapper.java @@ -71,6 +71,7 @@ public class ContractManageWrapper extends BaseEntityWrapper "草稿"; case "reviewing" -> "审批中"; case "rejected" -> "已驳回"; + case "withdrawn" -> "已撤回"; case "approved" -> "审批通过"; case "change_reviewing" -> "变更审批中"; case "change_rejected" -> "变更驳回"; diff --git a/doc/sql/transport/blade_contract_manage_archive_status_20260914.sql b/doc/sql/transport/blade_contract_manage_archive_status_20260914.sql new file mode 100644 index 0000000..28fc3d0 --- /dev/null +++ b/doc/sql/transport/blade_contract_manage_archive_status_20260914.sql @@ -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` = ''; diff --git a/doc/sql/transport/blade_contract_manage_basic_fields_20260914.sql b/doc/sql/transport/blade_contract_manage_basic_fields_20260914.sql new file mode 100644 index 0000000..762ab4a --- /dev/null +++ b/doc/sql/transport/blade_contract_manage_basic_fields_20260914.sql @@ -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`; diff --git a/doc/sql/transport/blade_project_contract_management.sql b/doc/sql/transport/blade_project_contract_management.sql index caa7ebd..2d72069 100644 --- a/doc/sql/transport/blade_project_contract_management.sql +++ b/doc/sql/transport/blade_project_contract_management.sql @@ -139,8 +139,13 @@ CREATE TABLE `blade_contract_manage` ( `settlement_currency` varchar(50) DEFAULT NULL COMMENT '结算币种', `invoice_cycle` 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 '合同阶段', `approval_status` varchar(50) DEFAULT NULL COMMENT '审核状态', + `archive_status` varchar(50) DEFAULT '未归档' COMMENT '归档状态:未归档/已归档', `current_node` varchar(100) DEFAULT NULL COMMENT '当前节点', `current_processor` varchar(100) DEFAULT NULL COMMENT '当前处理人', `approved_time` datetime DEFAULT NULL COMMENT '审核通过时间',