From 2262c39f60f6dd275a49811af09f27a92f583fdf Mon Sep 17 00:00:00 2001 From: b2894lxlx <517289602@qq.com> Date: Sun, 13 Sep 2026 01:01:09 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../transport/pojo/entity/ProjectApply.java | 4 + .../transport/pojo/vo/ProjectApplyVO.java | 21 ++ .../controller/ProjectApplyController.java | 17 + .../service/IProjectApplyService.java | 3 + .../service/impl/ProjectApplyServiceImpl.java | 311 +++++++++++++++++- ...e_project_apply_change_record_20260912.sql | 3 + .../blade_project_contract_management.sql | 1 + 7 files changed, 356 insertions(+), 4 deletions(-) create mode 100644 doc/sql/transport/blade_project_apply_change_record_20260912.sql diff --git a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ProjectApply.java b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ProjectApply.java index 8ba12f4..5526da8 100644 --- a/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ProjectApply.java +++ b/blade-service-api/blade-transport-api/src/main/java/org/springblade/transport/pojo/entity/ProjectApply.java @@ -170,6 +170,10 @@ public class ProjectApply extends TenantEntity { @Schema(description = "项目附件JSON") private String attachmentsJson; + @Schema(description = "变更记录JSON") + @TableField(value = "change_record_json", insertStrategy = FieldStrategy.ALWAYS, updateStrategy = FieldStrategy.ALWAYS) + private String changeRecordJson; + @Schema(description = "审批状态") private String approvalStatus; 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 8431d07..8ac54d1 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 @@ -29,6 +29,7 @@ import lombok.EqualsAndHashCode; import org.springblade.transport.pojo.entity.ProjectApply; import java.io.Serial; +import java.math.BigDecimal; /** * 项目立项视图实体类 @@ -79,4 +80,24 @@ public class ProjectApplyVO extends ProjectApply { @Schema(description = "是否仅查询可用于临时额度申请的项目") private Boolean temporaryCreditLimitSelectable; + @TableField(exist = false) + @Schema(description = "资金使用风险等级:high、medium、none") + private String fundUseRisk; + + @TableField(exist = false) + @Schema(description = "资金使用风险名称") + private String fundUseRiskName; + + @TableField(exist = false) + @Schema(description = "资金使用率(百分比)") + private BigDecimal fundUseRate; + + @TableField(exist = false) + @Schema(description = "已使用资金金额") + private BigDecimal usedFundLimit; + + @TableField(exist = false) + @Schema(description = "风险计算额度基数") + private BigDecimal maxFundLimit; + } diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ProjectApplyController.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ProjectApplyController.java index 41e399d..4cb6918 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ProjectApplyController.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/controller/ProjectApplyController.java @@ -48,6 +48,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.List; +import java.util.Map; /** * 项目立项 控制器 @@ -70,6 +71,22 @@ public class ProjectApplyController extends BladeController { return R.data(projectApplyService.detail(id)); } + @GetMapping("/fund-risk-stats") + @ApiOperationSupport(order = 2) + @Operation(summary = "资金使用风险统计", description = "传入项目筛选条件") + public R> fundRiskStats(ProjectApplyVO projectApply) { + return R.data(projectApplyService.fundRiskStats(projectApply)); + } + + @GetMapping("/change-record/detail") + @ApiOperationSupport(order = 2) + @Operation(summary = "变更记录详情", description = "传入项目ID和变更记录序号") + public R> changeRecordDetail( + @Parameter(description = "项目ID", required = true) @RequestParam Long id, + @Parameter(description = "变更记录序号,从0开始", required = true) @RequestParam Integer recordIndex) { + return R.data(projectApplyService.changeRecordDetail(id, recordIndex)); + } + @GetMapping("/list") @ApiOperationSupport(order = 2) @Operation(summary = "分页", description = "传入projectApply") diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IProjectApplyService.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IProjectApplyService.java index ee3cd16..6facb4c 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IProjectApplyService.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/IProjectApplyService.java @@ -29,6 +29,7 @@ import org.springblade.transport.pojo.entity.ProjectApply; import org.springblade.transport.pojo.vo.ProjectApplyVO; import java.util.List; +import java.util.Map; /** * 项目立项 服务类 @@ -39,6 +40,8 @@ public interface IProjectApplyService extends BaseService { IPage selectProjectApplyPage(IPage page, ProjectApplyVO projectApply); ProjectApplyVO detail(Long id); + Map fundRiskStats(ProjectApplyVO projectApply); + Map changeRecordDetail(Long id, Integer recordIndex); boolean saveDraft(ProjectApply projectApply); boolean submit(ProjectApply projectApply); boolean submitApproval(Long id); diff --git a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ProjectApplyServiceImpl.java b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ProjectApplyServiceImpl.java index 08802b5..fcc93ae 100644 --- a/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ProjectApplyServiceImpl.java +++ b/blade-service/blade-transport/src/main/java/org/springblade/transport/service/impl/ProjectApplyServiceImpl.java @@ -25,16 +25,24 @@ package org.springblade.transport.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.RequiredArgsConstructor; import org.springblade.core.log.exception.ServiceException; import org.springblade.core.mp.base.BaseServiceImpl; import org.springblade.core.secure.utils.AuthUtil; +import org.springblade.core.tool.jackson.JsonUtil; import org.springblade.core.tool.utils.BeanUtil; import org.springblade.core.tool.utils.Func; import org.springblade.system.cache.UserCache; import org.springblade.system.pojo.entity.Dept; import org.springblade.transport.excel.ProjectApplyExcel; import org.springblade.transport.mapper.ProjectApplyMapper; +import org.springblade.transport.mapper.FormalSettlementMapper; +import org.springblade.transport.mapper.ReceiptClaimSettlementMapper; +import org.springblade.transport.mapper.TemporaryCreditLimitMapper; import org.springblade.transport.pojo.entity.ProjectApply; +import org.springblade.transport.pojo.entity.FormalSettlement; +import org.springblade.transport.pojo.entity.ReceiptClaimSettlement; +import org.springblade.transport.pojo.entity.TemporaryCreditLimit; import org.springblade.transport.pojo.vo.ProjectApplyVO; import org.springblade.transport.service.IProjectApplyService; import org.springblade.transport.support.TransportBusinessSupport; @@ -47,9 +55,14 @@ import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; /** * 项目立项 服务实现类 @@ -57,6 +70,7 @@ import java.util.Objects; * @author Chill */ @Service +@RequiredArgsConstructor public class ProjectApplyServiceImpl extends BaseServiceImpl implements IProjectApplyService { private static final String STATUS_DRAFT = "draft"; @@ -68,15 +82,21 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl selectProjectApplyPage(IPage page, ProjectApplyVO projectApply) { IPage entityPage = page(page, buildQuery(projectApply)); IPage voPage = ProjectApplyWrapper.build().pageVO(entityPage); + fillFundUseRisk(voPage.getRecords()); voPage.getRecords().forEach(this::fillReadonly); return voPage; } @@ -89,25 +109,64 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl fundRiskStats(ProjectApplyVO projectApply) { + projectApply.setFundUseRisk(null); + List records = list(buildQuery(projectApply)).stream() + .map(record -> ProjectApplyWrapper.build().entityVO(record)) + .toList(); + fillFundUseRisk(records); + Map stats = new LinkedHashMap<>(); + stats.put("high", (int) records.stream().filter(item -> "high".equals(item.getFundUseRisk())).count()); + stats.put("medium", (int) records.stream().filter(item -> "medium".equals(item.getFundUseRisk())).count()); + stats.put("none", (int) records.stream().filter(item -> "none".equals(item.getFundUseRisk())).count()); + return stats; + } + + @Override + public Map changeRecordDetail(Long id, Integer recordIndex) { + if (recordIndex == null || recordIndex < 0) { + throw new ServiceException("变更记录序号不能为空"); + } + ProjectApply projectApply = loadExists(id); + List> records = parseChangeRecords(projectApply.getChangeRecordJson()); + if (recordIndex >= records.size()) { + throw new ServiceException("变更记录不存在"); + } + return records.get(recordIndex); + } + @Override @Transactional(rollbackFor = Exception.class) public boolean saveDraft(ProjectApply projectApply) { - prepare(projectApply); + ProjectApply beforeRecord = null; if (Func.isNotEmpty(projectApply.getId())) { - loadEditable(projectApply.getId(), true); + beforeRecord = loadEditable(projectApply.getId(), true); } + prepare(projectApply); prepareCreateOrUpdate(projectApply); projectApply.setApprovalStatus(resolveEditableStatus(projectApply)); + if (beforeRecord != null) { + projectApply.setChangeRecordJson(beforeRecord.getChangeRecordJson()); + } + if (beforeRecord != null && Objects.equals(beforeRecord.getApprovalStatus(), STATUS_DRAFT)) { + appendProjectChangeRecord(projectApply, CHANGE_TYPE_DRAFT, null, STATUS_DRAFT, "草稿", + buildProjectChangeSnapshot(beforeRecord), buildProjectChangeSnapshot(projectApply)); + } return saveOrUpdate(projectApply); } @Override @Transactional(rollbackFor = Exception.class) public boolean submit(ProjectApply projectApply) { + ProjectApply beforeRecord = null; + if (Func.isNotEmpty(projectApply.getId())) { + beforeRecord = loadEditable(projectApply.getId(), true); + } prepare(projectApply); validateSubmit(projectApply); - if (Func.isNotEmpty(projectApply.getId())) { - loadEditable(projectApply.getId(), true); + if (beforeRecord != null) { + projectApply.setChangeRecordJson(beforeRecord.getChangeRecordJson()); } prepareCreateOrUpdate(projectApply); projectApply.setApprovalStatus(STATUS_REVIEWING); @@ -186,10 +245,13 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl beforeData = buildProjectChangeSnapshot(oldRecord); prepare(projectApply); copyChangeFields(oldRecord, projectApply); validateSubmit(oldRecord); validateChangeLength(oldRecord); + Map afterData = buildProjectChangeSnapshot(oldRecord); + appendProjectChangeRecord(oldRecord, CHANGE_TYPE_PROJECT, oldRecord.getChangeReason(), "saved", "已保存", beforeData, afterData); return updateById(oldRecord); } @@ -197,10 +259,14 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl beforeData = buildProjectChangeSnapshot(oldRecord); prepare(projectApply); copyChangeFields(oldRecord, projectApply); validateSubmit(oldRecord); validateChange(oldRecord, projectApply.getChangeType()); + Map afterData = buildProjectChangeSnapshot(oldRecord); + appendProjectChangeRecord(oldRecord, projectApply.getChangeType(), oldRecord.getChangeReason(), + STATUS_CHANGE_REVIEWING, "变更审批中", beforeData, afterData); oldRecord.setApprovalStatus(STATUS_CHANGE_REVIEWING); oldRecord.setCurrentNode(projectApply.getChangeType() + "审批"); oldRecord.setCurrentProcessor("待处理"); @@ -271,7 +337,10 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl riskRecords = baseMapper.selectList(queryWrapper).stream() + .map(record -> ProjectApplyWrapper.build().entityVO(record)) + .toList(); + fillFundUseRisk(riskRecords); + List matchedIds = riskRecords.stream() + .filter(record -> Objects.equals(record.getFundUseRisk(), projectApply.getFundUseRisk())) + .map(ProjectApply::getId) + .toList(); + queryWrapper.in(ProjectApply::getId, matchedIds.isEmpty() ? List.of(-1L) : matchedIds); + } return queryWrapper; } + private void fillFundUseRisk(List records) { + if (records == null || records.isEmpty()) { + return; + } + List projectIds = records.stream() + .map(ProjectApply::getId) + .filter(Objects::nonNull) + .distinct() + .toList(); + if (projectIds.isEmpty()) { + return; + } + + Map approvedTemporaryLimits = new HashMap<>(); + temporaryCreditLimitMapper.selectList(Wrappers.lambdaQuery() + .in(TemporaryCreditLimit::getProjectId, projectIds) + .eq(TemporaryCreditLimit::getApprovalStatus, STATUS_SETTLEMENT_APPROVED) + .eq(TemporaryCreditLimit::getIsDeleted, 0)) + .forEach(item -> approvedTemporaryLimits.merge(item.getProjectId(), nonNegative(item.getApplyLimit()), BigDecimal::add)); + + List approvedReceivables = formalSettlementMapper.selectList(Wrappers.lambdaQuery() + .in(FormalSettlement::getProjectId, projectIds) + .eq(FormalSettlement::getSettlementType, "receivable") + .eq(FormalSettlement::getApprovalStatus, STATUS_SETTLEMENT_APPROVED) + .eq(FormalSettlement::getIsDeleted, 0)); + Map settlementProjects = approvedReceivables.stream() + .filter(item -> item.getId() != null && item.getProjectId() != null) + .collect(Collectors.toMap(FormalSettlement::getId, FormalSettlement::getProjectId, (left, right) -> left)); + Map usedFundLimits = new HashMap<>(); + if (!settlementProjects.isEmpty()) { + receiptClaimSettlementMapper.selectList(Wrappers.lambdaQuery() + .in(ReceiptClaimSettlement::getFormalSettlementId, settlementProjects.keySet()) + .eq(ReceiptClaimSettlement::getStatus, 1) + .eq(ReceiptClaimSettlement::getIsDeleted, 0)) + .forEach(item -> { + Long projectId = settlementProjects.get(item.getFormalSettlementId()); + if (projectId != null) { + usedFundLimits.merge(projectId, nonNegative(item.getAllocatedReceiptAmount()), BigDecimal::add); + } + }); + } + + records.forEach(project -> { + BigDecimal projectLimit = nonNegative(project.getFundLimit()); + BigDecimal temporaryLimit = approvedTemporaryLimits.getOrDefault(project.getId(), BigDecimal.ZERO); + BigDecimal maxFundLimit = projectLimit.max(temporaryLimit); + BigDecimal usedFundLimit = usedFundLimits.getOrDefault(project.getId(), BigDecimal.ZERO); + BigDecimal fundUseRate = maxFundLimit.signum() == 0 + ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP) + : usedFundLimit.multiply(BigDecimal.valueOf(100)).divide(maxFundLimit, 2, RoundingMode.HALF_UP); + String risk = fundUseRate.compareTo(new BigDecimal("90")) >= 0 ? "high" + : fundUseRate.compareTo(new BigDecimal("80")) >= 0 ? "medium" : "none"; + project.setUsedFundLimit(usedFundLimit); + project.setMaxFundLimit(maxFundLimit); + project.setFundUseRate(fundUseRate); + project.setFundUseRisk(risk); + project.setFundUseRiskName("high".equals(risk) ? "高风险" : "medium".equals(risk) ? "中风险" : "无风险"); + }); + } + + private BigDecimal nonNegative(BigDecimal value) { + return value == null || value.signum() < 0 ? BigDecimal.ZERO : value; + } + private void prepareCreateOrUpdate(ProjectApply projectApply) { if (Func.isEmpty(projectApply.getId())) { // 新增项目不产生变更记录,避免客户端误传变更字段导致记录回显。 @@ -414,6 +558,146 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl buildProjectChangeSnapshot(ProjectApply projectApply) { + Map data = new LinkedHashMap<>(); + data.put("projectType", projectApply.getProjectType()); + data.put("projectName", projectApply.getProjectName()); + data.put("projectShortName", projectApply.getProjectShortName()); + data.put("businessDeptName", projectApply.getBusinessDeptName()); + data.put("undertakeDeptName", projectApply.getUndertakeDeptName()); + data.put("projectSource", projectApply.getProjectSource()); + data.put("sourceRemark", projectApply.getSourceRemark()); + data.put("fundLimit", projectApply.getFundLimit()); + data.put("receivableLimit", projectApply.getReceivableLimit()); + data.put("receivableDays", projectApply.getReceivableDays()); + data.put("paymentDays", projectApply.getPaymentDays()); + data.put("cargoType", projectApply.getCargoType()); + data.put("cargoQuantity", projectApply.getCargoQuantity()); + data.put("businessStartDate", projectApply.getBusinessStartDate()); + data.put("businessEndDate", projectApply.getBusinessEndDate()); + data.put("transportRoute", projectApply.getTransportRoute()); + data.put("transportType", projectApply.getTransportType()); + data.put("businessType", projectApply.getBusinessType()); + data.put("businessMode", projectApply.getBusinessMode()); + data.put("projectScale", projectApply.getProjectScale()); + data.put("settlementMode", projectApply.getSettlementMode()); + data.put("estimatedProfit", projectApply.getEstimatedProfit()); + data.put("profitRate", projectApply.getProfitRate()); + data.put("fundDemand", projectApply.getFundDemand()); + data.put("handlerUserName", projectApply.getHandlerUserName()); + data.put("principalUserName", projectApply.getPrincipalUserName()); + data.put("customerNames", projectApply.getCustomerNames()); + data.put("carrierNames", projectApply.getCarrierNames()); + data.put("situationRemark", normalizeProjectSnapshotJson(projectApply.getSituationRemark())); + data.put("attachmentsJson", normalizeProjectSnapshotJson(projectApply.getAttachmentsJson())); + return data; + } + + private String normalizeProjectSnapshotJson(String value) { + if (Func.isEmpty(value)) { + return null; + } + String text = value.trim(); + try { + Object parsed = JsonUtil.parse(text, Object.class); + if (parsed instanceof List list && list.isEmpty()) { + return null; + } + if (parsed instanceof Map map && map.values().stream().allMatch(this::isEmptyProjectSnapshotValue)) { + return null; + } + } catch (Exception ignored) { + // 非JSON文本按原值参与差异比较。 + } + return text; + } + + private boolean isEmptyProjectSnapshotValue(Object value) { + if (value == null) { + return true; + } + if (value instanceof String text) { + return text.isBlank(); + } + if (value instanceof List list) { + return list.isEmpty(); + } + if (value instanceof Map map) { + return map.isEmpty(); + } + return false; + } + + private void appendProjectChangeRecord(ProjectApply projectApply, String changeType, String changeReason, + String status, String statusName, Map beforeData, + Map afterData) { + retainChangedSnapshotFields(beforeData, afterData); + if (beforeData.isEmpty()) { + return; + } + List> records = parseChangeRecords(projectApply.getChangeRecordJson()); + Map 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", buildProjectChangeContent(beforeData, afterData)); + record.put("changeReason", TransportBusinessSupport.trimToNull(changeReason)); + record.put("status", status); + record.put("statusName", statusName); + record.put("changedFields", new ArrayList<>(beforeData.keySet())); + record.put("beforeData", beforeData); + record.put("afterData", afterData); + records.add(record); + projectApply.setChangeRecordJson(JsonUtil.toJson(records)); + } + + private void retainChangedSnapshotFields(Map beforeData, Map afterData) { + List unchangedFields = beforeData.entrySet().stream() + .filter(entry -> Objects.equals(entry.getValue(), afterData.get(entry.getKey()))) + .map(Map.Entry::getKey) + .toList(); + unchangedFields.forEach(field -> { + beforeData.remove(field); + afterData.remove(field); + }); + } + + private String buildProjectChangeContent(Map beforeData, Map afterData) { + return beforeData.keySet().stream() + .map(field -> "【" + projectChangeFieldLabel(field) + "】从【" + + formatProjectChangeValue(beforeData.get(field)) + "】调整为【" + + formatProjectChangeValue(afterData.get(field)) + "】") + .collect(Collectors.joining(";")); + } + + private String projectChangeFieldLabel(String field) { + Map labels = Map.ofEntries( + Map.entry("projectType", "项目类型"), Map.entry("projectName", "项目名称"), + Map.entry("projectShortName", "项目简称"), Map.entry("businessDeptName", "业务部门"), + Map.entry("undertakeDeptName", "承办部门"), Map.entry("projectSource", "项目由来"), + Map.entry("sourceRemark", "项目由来说明"), Map.entry("fundLimit", "项目资金使用额度"), + Map.entry("receivableLimit", "项目应收账款额度"), Map.entry("receivableDays", "应收账款回款期限"), + Map.entry("paymentDays", "回款账期"), Map.entry("cargoType", "货物类型"), + Map.entry("cargoQuantity", "预估货物数量"), Map.entry("businessStartDate", "业务周期起"), + Map.entry("businessEndDate", "业务周期止"), Map.entry("transportRoute", "运输线路"), + Map.entry("transportType", "运输类型"), Map.entry("businessType", "业务类型"), + Map.entry("businessMode", "业务模式"), Map.entry("projectScale", "项目规模"), + Map.entry("settlementMode", "结算方式"), Map.entry("estimatedProfit", "预计利润"), + Map.entry("profitRate", "利润率"), Map.entry("fundDemand", "履约保证金"), + Map.entry("handlerUserName", "项目经办人"), Map.entry("principalUserName", "项目负责人"), + Map.entry("customerNames", "客户名称"), Map.entry("carrierNames", "下游承运商"), + Map.entry("situationRemark", "项目情况说明"), Map.entry("attachmentsJson", "项目材料")); + return labels.getOrDefault(field, field); + } + + private String formatProjectChangeValue(Object value) { + if (value == null || (value instanceof String text && text.isBlank())) { + return "空"; + } + return String.valueOf(value); + } + private void validateDraft(ProjectApply projectApply) { TransportBusinessSupport.validateRequired(projectApply.getProjectType(), "请选择项目类型"); TransportBusinessSupport.validateRequired(projectApply.getProjectName(), "请输入项目名称"); @@ -563,6 +847,25 @@ public class ProjectApplyServiceImpl extends BaseServiceImpl> parseChangeRecords(String value) { + if (Func.isEmpty(value)) { + return new ArrayList<>(); + } + try { + Object records = JsonUtil.parse(value, Object.class); + if (records instanceof List list) { + return (List>) (List) list; + } + if (records instanceof Map map) { + return new ArrayList<>(List.of((Map) map)); + } + } catch (Exception ignored) { + // 历史记录JSON损坏时从空列表继续,避免影响项目保存。 + } + return new ArrayList<>(); + } + private void fillReadonly(ProjectApplyVO projectApplyVO) { projectApplyVO.setReadonly(!canCurrentUserOperate(projectApplyVO)); } diff --git a/doc/sql/transport/blade_project_apply_change_record_20260912.sql b/doc/sql/transport/blade_project_apply_change_record_20260912.sql new file mode 100644 index 0000000..6c497a3 --- /dev/null +++ b/doc/sql/transport/blade_project_apply_change_record_20260912.sql @@ -0,0 +1,3 @@ +-- 项目立项增加变更记录JSON +ALTER TABLE `blade_project_apply` + ADD COLUMN `change_record_json` text DEFAULT NULL COMMENT '变更记录JSON' AFTER `attachments_json`; diff --git a/doc/sql/transport/blade_project_contract_management.sql b/doc/sql/transport/blade_project_contract_management.sql index 07920d2..caa7ebd 100644 --- a/doc/sql/transport/blade_project_contract_management.sql +++ b/doc/sql/transport/blade_project_contract_management.sql @@ -49,6 +49,7 @@ CREATE TABLE `blade_project_apply` ( `carrier_json` text DEFAULT NULL COMMENT '承运商信息JSON', `situation_remark` text DEFAULT NULL COMMENT '项目情况说明', `attachments_json` text DEFAULT NULL COMMENT '项目附件JSON', + `change_record_json` text DEFAULT NULL COMMENT '变更记录JSON', `approval_status` varchar(50) DEFAULT NULL COMMENT '审批状态', `current_node` varchar(100) DEFAULT NULL COMMENT '当前节点', `current_processor` varchar(100) DEFAULT NULL COMMENT '当前处理人',