zxl
2025-12-05 3777ec066316f3c1d3ca36e2cae5b1e05bac4f6e
首页
13个文件已修改
2个文件已添加
800 ■■■■■ 已修改文件
business/src/main/java/com/ycl/controller/IndexHomeController.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/domain/form/ProjectProgressStatisticsForm.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/domain/vo/CheckPointVO.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/domain/vo/TaskInfoVo.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/mapper/ProjectInfoMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/service/IndexHomeService.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/service/ProjectProcessService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/service/impl/FlowTaskServiceImpl.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/service/impl/IndexHomeServiceImpl.java 635 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/service/impl/ProjectProcessServiceImpl.java 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/task/FlowableTask.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/resources/mapper/ProjectInfoMapper.xml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
flowable/src/main/java/com/ycl/domain/entity/ProcessCoding.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
flowable/src/main/resources/mapper/ProcessCodingMapper.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
start/src/main/resources/application-dev.yml 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
business/src/main/java/com/ycl/controller/IndexHomeController.java
@@ -5,9 +5,7 @@
import com.ycl.domain.form.ProjectProgressStatisticsForm;
import com.ycl.service.IndexHomeService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
/**
 * nongtou-project-java
@@ -44,7 +42,8 @@
     * 项目进度统计
     * @return
     */
    Result projectTaskStatus(ProjectProgressStatisticsForm form){
    @PostMapping("/taskStatus")
    Result projectTaskStatus(@RequestBody ProjectProgressStatisticsForm form){
        return indexHomeService.projectTaskStatus(form);
    }
@@ -61,7 +60,13 @@
     * 项目推进卡点
     * @return
     */
    @GetMapping("/getProjectAdvanceCheckPoint")
    Result projectAdvanceCheckPoint(){
        return indexHomeService.projectAdvanceCheckPoint();
    }
    @GetMapping("/getSelect")
    Result getProjectSelectList(){
        return indexHomeService.getProjectSelectList();
    }
}
business/src/main/java/com/ycl/domain/form/ProjectProgressStatisticsForm.java
@@ -21,7 +21,7 @@
@NoArgsConstructor
public class ProjectProgressStatisticsForm extends AbsForm {
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM")
    private Date startTime;
business/src/main/java/com/ycl/domain/vo/CheckPointVO.java
New file
@@ -0,0 +1,25 @@
package com.ycl.domain.vo;
import com.ycl.system.domain.base.AbsVo;
import lombok.Data;
import org.flowable.bpmn.model.Task;
import java.util.Date;
import java.util.List;
/**
 * nongtou-project-java
 *
 * @author : zxl
 * @date : 2025-12-02 15:42
 **/
@Data
public class CheckPointVO extends AbsVo {
    private String projectName;//项目名
    private String processDefinitionId;
    private String processInstanceId;
    private String processName;
    private String deployId;//流程部署id
}
business/src/main/java/com/ycl/domain/vo/TaskInfoVo.java
New file
@@ -0,0 +1,26 @@
package com.ycl.domain.vo;
import lombok.Data;
import java.util.Date;
/**
 * nongtou-project-java
 *
 * @author : zxl
 * @date : 2025-12-03 14:35
 **/
@Data
public class TaskInfoVo  {
    private String id;
    private String taskName;//任务名
    private String taskType;
    private Date startTime;//开始时间
    private Date endTime;//截至时间
    private String totalOverTime;//超时时间
    private Long overTimeCount;//超时次数
    private CheckPointVO checkPointInfo;
    private CustomerTaskVO customerTaskInfo;
}
business/src/main/java/com/ycl/mapper/ProjectInfoMapper.java
@@ -6,9 +6,10 @@
import com.ycl.common.annotation.DataScope;
import com.ycl.common.core.domain.BaseEntity;
import com.ycl.domain.entity.ProjectInfo;
import com.ycl.domain.entity.ProjectInvestmentFunding;
import com.ycl.domain.excel.ProjectExcelTemplate;
import com.ycl.domain.query.ProjectInfoQuery;
import com.ycl.domain.vo.ProjectInfoVO;
import com.ycl.domain.vo.ProjectInvestmentFundingVO;
import com.ycl.domain.vo.ProjectVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -53,6 +54,6 @@
     * @return
     */
    @DataScope(deptAlias = "d")
    List<ProjectInfoVO> getProjectInfoAndFunding(BaseEntity params);
    List<ProjectInvestmentFundingVO> getProjectInfoAndFunding(BaseEntity params);
}
business/src/main/java/com/ycl/service/IndexHomeService.java
@@ -1,7 +1,6 @@
package com.ycl.service;
import com.ycl.common.annotation.DataScope;
import com.ycl.common.base.Result;
import com.ycl.domain.form.ProjectProgressStatisticsForm;
@@ -40,4 +39,6 @@
     * @return
     */
    Result projectAdvanceCheckPoint();
    Result getProjectSelectList();
}
business/src/main/java/com/ycl/service/ProjectProcessService.java
@@ -11,6 +11,7 @@
import com.ycl.domain.vo.CustomerTaskVO;
import com.ycl.domain.vo.IndexMsgCountVO;
import com.ycl.system.domain.base.AbsQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import java.util.List;
@@ -149,6 +150,8 @@
    Result getProcessMsg(AbsQuery query);
    List<HistoricTaskInstance> distinctHisTask(List<HistoricTaskInstance> hisTaskList);
    /**
     * 获取首页容缺任务
     *
business/src/main/java/com/ycl/service/impl/FlowTaskServiceImpl.java
@@ -175,6 +175,7 @@
            }
        }
        taskService.addComment(taskId, task.getProcessInstanceId(), FlowComment.SUBMIT.getType(), "完成提交");
        //判断是否是委派任务
        if (DelegationState.PENDING.equals(task.getDelegationState())) {
            taskService.resolveTask(taskId, newV);
        } else {
@@ -1513,7 +1514,7 @@
    /**
     * 流程节点表单
     *
     * 点击办理进入
     * @param taskId 流程任务编号
     * @return
     */
@@ -1627,7 +1628,9 @@
    @Override
    public AjaxResult detail(String processInsId, String taskId) {
        //查询运行时流程实例(含流程变量)
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().includeProcessVariables().processInstanceId(processInsId).singleResult();
        log.info("打印运行时:{}",processInstance);
        // 流程变量
        Map<String, Object> parameters = new HashMap<>();
        if (Objects.isNull(processInstance)) {
@@ -1637,7 +1640,7 @@
            if (Objects.isNull(historicProcessInstance)) {
                throw new RuntimeException("流程不存在");
            }
            //流程未结束 查询流程已完成的结点任务
            List<HistoricTaskInstance> hisTasks = historyService.createHistoricTaskInstanceQuery()
                    .taskId(taskId)
                    .finished()
@@ -1648,7 +1651,9 @@
            if (CollectionUtils.isNotEmpty(hisTasks) && Objects.isNull(hisTasks.get(0))) {
                throw new RuntimeException("该任务不存在");
            }
            //获得最新的任务节点
            HistoricTaskInstance hisTask = hisTasks.get(0);
            //获得参数
            parameters = historicProcessInstance.getProcessVariables();
            List<FormDetailVO> beforeNodes = this.getBeforeNodeForm(parameters,
                    hisTask.getFormKey(),
@@ -1657,12 +1662,14 @@
                    hisTask.getTaskDefinitionKey(),
                    Boolean.TRUE, Boolean.TRUE);
            List<FormDetailVO> dataList = new ArrayList<>(2);
            // 按执行流ID分组前置节点
            Map<String, List<FormDetailVO>> map = new HashMap<>(2);
            beforeNodes.stream().forEach(node -> {
                if (node.getCurrent()) {
                    node.setTaskId(taskId);
                    dataList.add(node);
                } else {
                    //前置节点:查询对应的最新历史任务
                    List<HistoricTaskInstance> beforeTasks = historyService.createHistoricTaskInstanceQuery()
                            .processInstanceId(hisTask.getProcessInstanceId())
                            .finished()
@@ -1670,6 +1677,7 @@
                            .orderByTaskCreateTime()
                            .desc()
                            .list();
                    if (CollectionUtils.isNotEmpty(beforeTasks) && Objects.nonNull(beforeTasks.get(0))) {
                        node.setTaskId(beforeTasks.get(0).getId());
                        List<FormDetailVO> l = map.get(beforeTasks.get(0));
@@ -1689,7 +1697,9 @@
            }
            List<DoFormDetailVO> vos = dataList.stream().map(node -> {
                if (node.getCurrent()) {
                    // 调用日志服务,查询该任务是否处于挂起状态(传入任务ID和流程实例ID)
                    if (processLogService.taskIsHangup(taskId, hisTask.getProcessInstanceId())) {
                        //若挂起,给节点设置“挂起”状态(前端会展示挂起标识)
                        node.setTaskStatus(TaskStatusEnum.HANGUP);
                    }
                }
@@ -1812,16 +1822,19 @@
        // 这里只需要查自身以及上一个节点(如果并行的有多个)的表单数据
        List<FormDetailVO> beforeNodes = taskCommonService.getBeforeNodeDefInfo(processDefId, processDefKey, sysFormService, Boolean.TRUE);
        // 非当前节点 或 需要当前节点数据
        List<String> beforeNodeDefIds = beforeNodes.stream().filter(item -> !item.getCurrent() || currentNeedData).map(FormDetailVO::getBeforeNodeDefId).collect(Collectors.toList());
        // 处理每个表单的数据
        for (FormDetailVO formDetailVO : beforeNodes) {
            // 跳过“当前节点且不需要其数据”的场景 并且 currentNeedData不需要当前节点数据的情况
            if (formDetailVO.getCurrent() && !currentNeedData) {
                // 当前节点的表单也要处理ip问题
                continue;  // 跳过当前节点,因为当前节点在获取前置节点时已经设置过了(但表单数据没有给)
            }
            Map<String, Object> newP = new HashMap<>();
            // 存储当前节点的表单数据
            if (CollectionUtils.isNotEmpty(beforeNodeDefIds)) {
                for (String key : parameters.keySet()) {
                    // 过滤拿到目标表单数据,将目标表单数据放到新map中
@@ -1834,7 +1847,7 @@
                    }
                }
            }
            //获得前置节点定义id,即任务key
            Object form = newP.get(formDetailVO.getBeforeNodeDefId() + "&" + ProcessConstants.TASK_FORM_KEY);
            if (Objects.nonNull(form)) {
                JSONObject formJson = JSONObject.parseObject(JSON.toJSONString(form));
@@ -1853,11 +1866,12 @@
                            options.put("uploadURL", String.format("http://%s:10076/common/upload", this.targetIp));
                        }
                    }
                    // 更新表单结构(已处理禁用和上传地址)
                    formJson.put(ProcessConstants.WIDGET_LIST, oldFields);
                    newP.put(ProcessConstants.TASK_FORM_KEY, formJson);
                    // 删除原始的“节点ID&TASK_FORM_KEY”键(避免冗余)
                    newP.remove(formDetailVO.getBeforeNodeDefId() + "&" + ProcessConstants.TASK_FORM_KEY);
                    // 处理已经上传的文件的ip地址
                    // 已上传文件的访问IP地址(前端预览/下载时指向正确地址)
                    for (String s : newP.keySet()) {
                        if (ProcessConstants.TASK_FORM_KEY.equals(s)) {
                            continue;
business/src/main/java/com/ycl/service/impl/IndexHomeServiceImpl.java
@@ -7,24 +7,48 @@
import com.ycl.common.base.Result;
import com.ycl.common.constant.ProcessOverTimeConstants;
import com.ycl.common.core.domain.BaseEntity;
import com.ycl.common.enums.business.CodingRulerCodeTypeEnum;
import com.ycl.common.enums.business.CodingRulerStatusEnum;
import com.ycl.common.enums.business.ProjectCategoryEnum;
import com.ycl.common.enums.business.ProjectStatusEnum;
import com.ycl.common.core.domain.entity.SysDept;
import com.ycl.common.core.domain.entity.SysRole;
import com.ycl.common.core.domain.entity.SysUser;
import com.ycl.common.enums.business.*;
import com.ycl.common.utils.SecurityUtils;
import com.ycl.domain.entity.ProcessCoding;
import com.ycl.domain.entity.ProcessLog;
import com.ycl.domain.entity.ProjectInfo;
import com.ycl.domain.entity.ProjectProcess;
import com.ycl.domain.form.ProjectProgressStatisticsForm;
import com.ycl.domain.vo.ProjectInfoVO;
import com.ycl.domain.vo.ProjectVO;
import com.ycl.domain.vo.*;
import com.ycl.factory.FlowServiceFactory;
import com.ycl.mapper.ProcessCodingMapper;
import com.ycl.mapper.ProcessLogMapper;
import com.ycl.mapper.ProjectInfoMapper;
import com.ycl.mapper.ProjectProcessMapper;
import com.ycl.service.IndexHomeService;
import com.ycl.service.ProjectInfoService;
import com.ycl.service.ProjectProcessService;
import com.ycl.system.mapper.SysDeptMapper;
import com.ycl.system.service.ISysDeptService;
import com.ycl.system.service.ISysRoleService;
import com.ycl.system.service.ISysUserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.identitylink.api.IdentityLink;
import org.flowable.identitylink.api.IdentityLinkInfo;
import org.flowable.identitylink.api.IdentityLinkType;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.text.DecimalFormat;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.stream.Collectors;
@@ -37,33 +61,27 @@
 **/
@Service
@RequiredArgsConstructor
public class IndexHomeServiceImpl implements IndexHomeService {
@Slf4j
public class IndexHomeServiceImpl extends FlowServiceFactory implements IndexHomeService {
    private final SysDeptMapper sysDeptMapper;
    private final ProjectInfoMapper projectInfoMapper;
    private final ProjectProcessService projectProcessService;
    private final ProcessLogMapper processLogMapper;
    private final ProcessCodingMapper processCodingMapper;
    private final ISysUserService sysUserService;
    private final ISysDeptService sysDeptService;
    private final ISysRoleService sysRoleService;
    @Override
    public Result projectCodingStatusCount() {
        //权限控制
        Long userId = SecurityUtils.getUserId();
        List<ProjectInfo> list;
        if (SecurityUtils.isAdmin(userId)){
            //查询全部
            list = new LambdaQueryChainWrapper<>(projectInfoMapper)
                    .eq(ProjectInfo::getDeleted, Boolean.FALSE)
                    .eq(ProjectInfo::getUsedStatus, 2) //审核通过
                    .list();
        }else{
            String ancestors = sysDeptMapper.selectAncestors(userId);
            String[] ancestorArr = ancestors.split(",");
            List<String> ancestorList = Arrays.stream(ancestorArr).collect(Collectors.toList());
            ancestorList.add(SecurityUtils.getDeptId() + "");
            //获得本单位以及其子单位deptId;
            list = new LambdaQueryChainWrapper<>(projectInfoMapper)
                    .eq(ProjectInfo::getDeleted, Boolean.FALSE)
                    .eq(ProjectInfo::getUsedStatus, 2) //审核通过
                    .in(ProjectInfo::getProjectOwnerUnit, ancestorList)
                    .list();
        }
        List<ProjectInfo> loginUserOwnProjectInfo = getLoginUserOwnProjectInfo();
@@ -73,13 +91,13 @@
        map.put(ProcessOverTimeConstants.YELLOW,0);
        map.put(ProcessOverTimeConstants.RED,0);
        map.put("total",0);
        if (CollectionUtils.isEmpty(list)) {
        if (CollectionUtils.isEmpty(loginUserOwnProjectInfo)) {
            //返回默认值
            return Result.ok().data(map);
        }
        Map<String, List<ProjectInfo>> collect = list.stream()
        Map<String, List<ProjectInfo>> collect = loginUserOwnProjectInfo.stream()
                .filter(project -> project.getCoding() != null) // 过滤coding为null的情况
                .collect(Collectors.groupingBy(ProjectInfo::getCoding));
@@ -93,7 +111,7 @@
            map.put(ProcessOverTimeConstants.RED, collect.get(ProcessOverTimeConstants.RED).size());
        }
        map.put("total", list.size());
        map.put("total", loginUserOwnProjectInfo.size());
        return Result.ok().data(map);
    }
@@ -115,29 +133,566 @@
                finish+=1;
            }
        }
        Map<String,Integer> map = new HashMap<>();
        map.put(ProjectCategoryEnum.RESERVE.getCode(),reserve);
        map.put(ProjectCategoryEnum.PREVIOUS.getCode(),previous);
        map.put(ProjectCategoryEnum.IMPLEMENT.getCode(),implement);
        map.put(ProjectCategoryEnum.FINISH.getCode(),finish);
        Map<String,Object> map = new HashMap<>();
        Integer[] yData = new Integer[]{
                projectVOS.size(),
                reserve,
                previous,
                implement,
                finish
        };
        String[] xData = new String[]{
          "在库", "储备","前期","实施","竣工"
        };
        map.put("xData",xData);
        map.put("yData",yData);
        return Result.ok().data(map);
    }
    public List<ProjectInfo> getLoginUserOwnProjectInfo(){
        //权限控制
        Long userId = SecurityUtils.getUserId();
        List<ProjectInfo> list;
        if (SecurityUtils.isAdmin(userId)){
            //查询全部
            list = new LambdaQueryChainWrapper<>(projectInfoMapper)
                    .eq(ProjectInfo::getDeleted, Boolean.FALSE)
                    .eq(ProjectInfo::getUsedStatus, 2)//审核通过
                    .orderBy(true, true, ProjectInfo::getId)
                    .list();
        }else{
            String ancestors = sysDeptMapper.selectAncestors(userId);
            String[] ancestorArr = ancestors.split(",");
            List<String> ancestorList = Arrays.stream(ancestorArr).collect(Collectors.toList());
            ancestorList.add(SecurityUtils.getDeptId() + "");
            //获得本单位以及其子单位deptId;
            list = new LambdaQueryChainWrapper<>(projectInfoMapper)
                    .eq(ProjectInfo::getDeleted, Boolean.FALSE)
                    .eq(ProjectInfo::getUsedStatus, 2) //审核通过
                    .in(ProjectInfo::getProjectOwnerUnit, ancestorList)
                    .orderBy(true, true, ProjectInfo::getId)
                    .list();
        }
        return list;
    }
    @Override
    public Result projectTaskStatus(ProjectProgressStatisticsForm form) {
        return null;
        //获得是项目
        List<ProjectInfo> loginUserOwnProjectInfo = getLoginUserOwnProjectInfo();
        if (CollectionUtils.isEmpty(loginUserOwnProjectInfo)) {
            return Result.ok().data(loginUserOwnProjectInfo);
        }
        Map<String, String> projectMap = loginUserOwnProjectInfo.stream()
                .collect(Collectors.toMap(
                        projectInfo -> String.valueOf(projectInfo.getId()),          // key: 项目ID
                        ProjectInfo::getProjectName,          // value: 项目对象本身
                        (existing, replacement) -> existing  // 处理重复ID的冲突策略(保留原有值)
                ));
        //获得项目ids
        List<Long> ids = loginUserOwnProjectInfo.stream().map(ProjectInfo::getId).collect(Collectors.toList());
        //更具项目ids获得所有对应流程信息 ProjectProcess  流程定义ID processDefId;流程实例id processInsId;
        List<ProjectProcess> projectProcessList = new LambdaQueryChainWrapper<>(projectProcessService.getBaseMapper())
                .eq(ProjectProcess::getDeleted, Boolean.FALSE)
                .in(ProjectProcess::getProjectId, ids).list();
        Date statisticsTime = form.getStartTime();
        if (statisticsTime == null) {
            statisticsTime = new Date(); // 如果未传入时间,默认使用当前时间
        }
        LocalDate statisticsLocalDate = statisticsTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        LocalDate firstDayOfMonth = statisticsLocalDate.with(TemporalAdjusters.firstDayOfMonth());
        LocalDate lastDayOfMonth = statisticsLocalDate.with(TemporalAdjusters.lastDayOfMonth());
        Date monthStart = Date.from(firstDayOfMonth.atStartOfDay(ZoneId.systemDefault()).toInstant());
        Date monthEnd = Date.from(lastDayOfMonth.atTime(23, 59, 59).atZone(ZoneId.systemDefault()).toInstant());
        // 存储每个流程实例的统计结果
        List<Map<String, Object>> processStatisticsList = new ArrayList<>();
        // 获得流程日志表中 任务信息 按流程实列id分组
        Map<String, List<ProcessLog>> groupMap = new LambdaQueryChainWrapper<>(processLogMapper)
                .eq(ProcessLog::getDeleted, Boolean.FALSE)
                .in(ProcessLog::getProjectId, ids).list()
                .stream().collect(Collectors.groupingBy(ProcessLog::getProcessInsId));
        for (ProjectProcess projectProcess : projectProcessList) {
            String processInstanceId = projectProcess.getProcessInsId();
            if (processInstanceId == null) {
                continue;
            }
            // 单个流程的统计结果
            Map<String, Object> processResult = new HashMap<>();
            processResult.put("id",projectProcess.getProjectId());
            processResult.put("projectName", projectMap.get(projectProcess.getProjectId()));
            int completedCount = 0;      // 已完成任务数
            int inProgressCount = 0;     // 进行中任务数
            int notStartedCount = 0;     // 未开始任务数
            try {
                // 1. 获取该流程实例的所有已完成任务
                List<HistoricTaskInstance> completedTasks = historyService.createHistoricTaskInstanceQuery()
                        .processInstanceId(processInstanceId)
                        .finished()
                        .list();
                // 去重历史信息 排除相同的任务key保留最新的任务
                completedTasks = projectProcessService.distinctHisTask(completedTasks);
                // 需要排除已完成任务中被驳回的任务数
                List<ProcessLog> taskLogs = groupMap.get(projectProcess.getProcessInsId());
                if ("144".equals(projectProcess.getProjectId())) {
                    for (ProcessLog processLog : taskLogs) {
                        System.out.println("打印信息");
                        System.out.println(processLog.toString());
                    }
                }
                if (!CollectionUtils.isEmpty(taskLogs)) {
                    //排除掉所有被驳回的任务
                    // 获取所有被驳回任务的ID列表
                    List<String> rejectedTaskIds = taskLogs.stream()
                            .filter(log -> {
                                // 判断是否为驳回类型的日志(根据你的枚举值调整)
                                return ProcessLogEventTypeEnum.REJECT.equals(log.getEventType());
                            })
                            .map(ProcessLog::getTaskId) // 假设日志中有taskId字段关联任务
                            .filter(Objects::nonNull)   // 过滤掉null的taskId
                            .collect(Collectors.toList());
                    // 排除被驳回的任务,生成新的已完成任务集合
                    // 将过滤后的集合赋值给原变量
                    completedTasks = completedTasks.stream()
                            .filter(task -> !rejectedTaskIds.contains(task.getId())) //保留不包含驳回的taskId
                            .collect(Collectors.toList());
                }
                // 本月最后一天完成的 属于已完成 after 之后 before 之前
                for (HistoricTaskInstance task : completedTasks) {
                    Date completionTime = task.getEndTime();
                    if (completionTime != null &&
                            completionTime.before(monthEnd)) {
                        completedCount++;
                    }
                }
                // 获得上一步未覆盖到查询条件的元素 完成时间是在本月之后
                List<HistoricTaskInstance> excessCompletedTasks  = completedTasks.stream().filter(
                        task -> { Date endTime = task.getEndTime();
                           return endTime != null && endTime.after(monthEnd);
                }).collect(Collectors.toList());
                // 循环完成时间是在本月之后的
                for (HistoricTaskInstance task : excessCompletedTasks) {
                    Date createTime = task.getCreateTime();
                    Date endTime = task.getEndTime();
                    // 本月创建,完成在本月之后 属于进行中
                    if (createTime != null && endTime != null) {
                        if (createTime.before(monthEnd) && createTime.after(monthStart) && endTime.after(monthEnd)) {
                            inProgressCount++;
                        }
                    }
                }
                // 2. 获取该流程实例的所有进行中任务
                List<Task> activeTasks = taskService.createTaskQuery()
                        .processInstanceId(processInstanceId)
                        .active()
                        .list();
                for (Task task : activeTasks) {
                    Date createTime = task.getCreateTime();
                    // 进行中的任务 判断创建时间是否在下个月之前 如果是本月则加1
                    if (createTime != null && createTime.before(monthEnd)) {
                        inProgressCount++;
                    }
                }
                // 5. 统计未开始任务(获取流程定义的总任务数 - 已统计的任务数)
                ProcessInstance processInstance = runtimeService.createProcessInstanceQuery()
                        .processInstanceId(processInstanceId)
                        .singleResult();
                if (processInstance != null) {
                    BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());
                    Process mainProcess = bpmnModel.getMainProcess();
                    List<UserTask> userTasks = mainProcess.findFlowElementsOfType(UserTask.class);
                    int totalUserTaskCount = userTasks.size();
                    // 未开始任务数 = 总任务数 - 已完成 - 进行中
                    notStartedCount = totalUserTaskCount - completedCount - inProgressCount;
                    // 确保未开始任务数不为负数
                    if (notStartedCount < 0) {
                        notStartedCount = 0;
                    }
                }
            } catch (Exception e) {
                // 异常处理
                notStartedCount = 0;
            }
            // 设置当前流程的统计结果
            int totalTasks = completedCount + inProgressCount + notStartedCount;
            double completedPercent = totalTasks == 0 ? 0 : (completedCount * 100.0 / totalTasks);
            double inProgressPercent = totalTasks == 0 ? 0 : (inProgressCount * 100.0 / totalTasks);
            double notStartedPercent = totalTasks == 0 ? 0 : (notStartedCount * 100.0 / totalTasks);
            //四舍五入
            completedPercent = Math.round(completedPercent * 10) / 10.0;
            inProgressPercent = Math.round(inProgressPercent * 10) / 10.0;
            notStartedPercent = Math.round(notStartedPercent * 10) / 10.0;
            //保留一位
            DecimalFormat df = new DecimalFormat("#.#");
            // 格式化结果(可选:保留为字符串或转换为Double)
            String completedFormatted = df.format(completedPercent);
            String inProgressFormatted = df.format(inProgressPercent);
            String notStartedFormatted = df.format(notStartedPercent);
            processResult.put("completed", completedFormatted);
            processResult.put("co", completedCount);
            processResult.put("co2", inProgressCount);
            processResult.put("co3", notStartedCount);
            processResult.put("inProgress", inProgressFormatted);
            processResult.put("notStarted", notStartedFormatted);
            processResult.put("totalTasks",totalTasks) ;
            // 添加到结果列表
            processStatisticsList.add(processResult);
        }
        return Result.ok().data(processStatisticsList);
    }
    @Override
    @DataScope(deptAlias = "d")
    public Result projectFundingStatus( ) {
        List<ProjectInvestmentFundingVO> projectInfoAndFunding = projectInfoMapper.getProjectInfoAndFunding(new BaseEntity());
        //排除空数据
        projectInfoAndFunding = projectInfoAndFunding.stream().filter(Objects::nonNull).collect(Collectors.toList());
        List<ProjectInfoVO> projectInfoAndFunding = projectInfoMapper.getProjectInfoAndFunding(new BaseEntity());
        return Result.ok().data(projectInfoAndFunding);
    }
    @Override
    public Result projectAdvanceCheckPoint() {
        return null;
        List<ProjectInfo> loginUserOwnProjectInfo = getLoginUserOwnProjectInfo();
        if (CollectionUtils.isEmpty(loginUserOwnProjectInfo)) {
            return Result.ok().data(loginUserOwnProjectInfo);
        }
        //获得项目ids
        List<Long> ids = loginUserOwnProjectInfo.stream().map(ProjectInfo::getId).collect(Collectors.toList());
        List<ProjectProcess> projectProcessList = new LambdaQueryChainWrapper<>(projectProcessService.getBaseMapper())
                .eq(ProjectProcess::getDeleted, Boolean.FALSE)
                .in(ProjectProcess::getProjectId, ids).list();
        //流程实例id集合
        List<String> targetProcessInsIds = projectProcessList.stream()
                .map(ProjectProcess::getProcessInsId)
                .distinct()
                .filter(defId -> defId != null && !defId.trim().isEmpty())
                .collect(Collectors.toList());
        //组装项目流程信息 项目id为key
        Map<String,CheckPointVO> map = new HashMap<>();
        for (ProjectInfo projectInfo : loginUserOwnProjectInfo){
            CheckPointVO checkPointVO = new CheckPointVO();
            checkPointVO.setId(projectInfo.getId());
            checkPointVO.setProjectName(projectInfo.getProjectName());
            map.put(String.valueOf(projectInfo.getId()),checkPointVO);
        }
        //组装流程信息
        for (ProjectProcess projectProcess : projectProcessList) {
            CheckPointVO checkPointVO = map.get(projectProcess.getProjectId());
            checkPointVO.setProcessDefinitionId(projectProcess.getProcessDefId());
            checkPointVO.setProcessInstanceId(projectProcess.getProcessInsId());
            //获得流程部署id
            ProcessInstance process = runtimeService.createProcessInstanceQuery()
                    .processInstanceId(projectProcess.getProcessInsId()).singleResult();
            if (Objects.nonNull(process)) {
                checkPointVO.setDeployId(process.getDeploymentId()); // 运行中流程直接取部署ID
                checkPointVO.setProcessName(process.getProcessDefinitionName());
            } else {
                // 2. 运行中流程不存在(已结束),查历史流程实例
                HistoricProcessInstance hisProcess = historyService.createHistoricProcessInstanceQuery()
                        .processInstanceId(projectProcess.getProcessInsId()).singleResult();
                // 历史流程取部署ID
                checkPointVO.setDeployId(hisProcess.getDeploymentId());
                checkPointVO.setProcessName(hisProcess.getProcessDefinitionName());
            }
            map.put(projectProcess.getProjectId(),checkPointVO);
        }
        // 转换为list后按 流程实例id分组
        List<CheckPointVO> checkPointList = map.values().stream()
                .collect(Collectors.toList());
        // 获得组装好的 流程map 键为流程实例id,
        Map<String, CheckPointVO> processInfoMap = checkPointList.stream()
                .filter(Objects::nonNull) // 避免null元素导致异常(可选,按需保留)
                .filter(vo -> vo.getProcessInstanceId() != null) // 过滤流程定义ID为null的无效数据
                .collect(Collectors.toMap(
                        CheckPointVO::getProcessInstanceId, // key:流程定义ID(替换为你的实际方法名)
                        vo -> vo, // value:原始CheckPointVO对象
                        (oldVal, newVal) -> newVal // 冲突策略:存在重复ID时保留后者(按需调整)
                ));
        List<Task> allRunningTasks  = taskService.createTaskQuery().active().list();
        List<Task> targetRunningTasks = allRunningTasks.stream()
                // 关键匹配:任务的流程定义ID 存在于 projectProcessList 的流程定义ID列表中
                .filter(task -> targetProcessInsIds.contains(task.getProcessInstanceId()))
                .collect(Collectors.toList());
        List<TaskInfoVo> list = new ArrayList<>();
        // 按流程实例id分组
        if (!CollectionUtils.isEmpty(targetRunningTasks)){
            //查询到所有
            List<String> taskIds = targetRunningTasks.stream().map(Task::getId).collect(Collectors.toList());
            // 任务运行时不在 list中,可能任务没有设置超时时间
            List<ProcessCoding> processCodingList = new LambdaQueryChainWrapper<>(processCodingMapper)
                    .eq(ProcessCoding::getDeleted, Boolean.FALSE)
                    .in(ProcessCoding::getTaskId, taskIds)
                    .list();
            //按taskId转换为map方面下面获取
            if (!CollectionUtils.isEmpty(processCodingList)){
                Map<String, ProcessCoding> processCodingMap = processCodingList.stream()
                        .filter(Objects::nonNull) // 避免null元素导致NPE(可选保留)
                        .filter(vo -> vo.getTaskId() != null) // 过滤taskId为null的无效数据
                        .collect(Collectors.toMap(
                                ProcessCoding::getTaskId, // key:仍为taskId
                                vo -> vo, // value:原始ProcessCoding对象
                                // 冲突策略:比较创建时间,保留更新的那个
                                (oldVal, newVal) -> {
                                    // 获取两者创建时间(根据实际方法名调整,如getGmtCreate()
                                    Date oldCreateTime = oldVal.getGmtCreate();
                                    Date newCreateTime = newVal.getGmtCreate();
                                    // 处理创建时间为null的情况(优先级:有时间>无时间,都无则保留后者)
                                    if (oldCreateTime == null) return newVal;
                                    if (newCreateTime == null) return oldVal;
                                    // 比较时间戳,返回更大的(更新的)对象
                                    return newCreateTime.getTime() > oldCreateTime.getTime() ? newVal : oldVal;
                                }
                        ));
                for (Task task : targetRunningTasks) {
                    TaskInfoVo taskVo = new TaskInfoVo();
                    taskVo.setId(task.getId());
                    taskVo.setTaskName(task.getName());
                    taskVo.setStartTime(task.getCreateTime());
                    taskVo.setEndTime(task.getDueDate());
//                    taskVo.setTaskType();
                    List<Long> handlerIds = new ArrayList<>(2);
                    List<String> handlerNames = new ArrayList<>(2);
                    List<Long> handlerUnitIds = new ArrayList<>(2);
                    List<String> handlerUnitNames = new ArrayList<>(2);
                    List<String> promoterNames = new ArrayList<>(2);
                    List<String> promoterUnitNames = new ArrayList<>(2);
                    CustomerTaskVO customerTaskVO = new CustomerTaskVO();
                    customerTaskVO.setHandlerId(handlerIds);
                    customerTaskVO.setHandlerName(handlerNames);
                    customerTaskVO.setHandlerUnitId(handlerUnitIds);
                    customerTaskVO.setHandlerUnitName(handlerUnitNames);
                    customerTaskVO.setPromoterName(promoterNames);
                    customerTaskVO.setPromoterUnitName(promoterUnitNames);
                    // 流程处理人信息
                    List<IdentityLink> identityLinksForTask = taskService.getIdentityLinksForTask(task.getId());
                    for (IdentityLinkInfo identityLink : identityLinksForTask) {
                        // 绑定的是用户,查出用户姓名、部门
                        if (StringUtils.isNotBlank(identityLink.getUserId())) {
                            // 处理变量表达式,运行中的任务无需再处理表达式了,flowable已经自动根据变量设置了
                            customerTaskVO.setHandlerType(HandlerTypeEnum.USER);
                            SysUser sysUser = sysUserService.selectUserById(Long.parseLong(identityLink.getUserId()));
                            if (Objects.nonNull(sysUser)) {
                                customerTaskVO.getHandlerId().add(sysUser.getUserId());
                                customerTaskVO.getHandlerName().add(this.getUserShowName(sysUser));
                                if (Objects.nonNull(sysUser.getDept())) {
                                    customerTaskVO.getHandlerUnitId().add(sysUser.getDept().getDeptId());
                                    customerTaskVO.getHandlerUnitName().add(sysUser.getDept().getDeptName());
                                    customerTaskVO.getPromoterName().add(this.getUserShowName(sysUser));
                                    String[] str = sysUser.getDept().getAncestors().split(",");
                                    if (str.length >= 4){
                                        customerTaskVO.getPromoterUnitName().add(sysUser.getDept().getParentName() +"-"+sysUser.getDept().getDeptName());
                                    }else {
                                        customerTaskVO.getPromoterUnitName().add(sysUser.getDept().getDeptName());
                                    }
                                }
                            }
                            // 绑定的是角色或者部门
                        } else if (StringUtils.isNotBlank(identityLink.getGroupId())) {
                            if (identityLink.getGroupId().startsWith("dept")) {   // 部门的id是加了前缀的如:dept:1
                                customerTaskVO.setHandlerType(HandlerTypeEnum.DEPT);
                                String[] split = identityLink.getGroupId().split(":");
                                if (split.length > 1) {
                                    // 部门
                                    SysDept dept = sysDeptService.selectDeptById(Long.parseLong(split[1]));
                                    if (Objects.nonNull(dept)) {
                                        customerTaskVO.getHandlerUnitId().add(dept.getDeptId());
                                        customerTaskVO.getHandlerUnitName().add(dept.getDeptName());
                                        customerTaskVO.getPromoterName().add(this.getDeptLeaderShowName(dept));
                                        customerTaskVO.getPromoterUnitName().add(this.setDeptNameWithParentName(dept));
                                    }
                                }
                            } else {
                                customerTaskVO.setHandlerType(HandlerTypeEnum.ROLE);
                                SysRole role = sysRoleService.selectRoleById(Long.parseLong(identityLink.getGroupId()));
                                if (Objects.nonNull(role)) {
                                    customerTaskVO.getHandlerUnitId().add(Long.parseLong(identityLink.getGroupId()));
                                    customerTaskVO.getHandlerUnitName().add(role.getRoleName());
                                }
                            }
                        }
                        this.distinctVo(customerTaskVO);
                    }
                    taskVo.setCustomerTaskInfo(customerTaskVO);
                    //组装任务信息
                    CheckPointVO checkPointVO = processInfoMap.get(task.getProcessInstanceId());
                    if (checkPointVO != null) {
                       taskVo.setCheckPointInfo(checkPointVO);
                    }
                    //超时时间和次数在 processCodingList中  这里需要更具taskId来获得对应信息
                    ProcessCoding processCoding = processCodingMap.get(task.getId());
                    //设置超时信息
                    if (processCoding != null) {
                        String overTimeTotalStr = processCoding.getOverTimeTotal();
                        long overTimeTotal = 0L;
                        if (StringUtils.isNotBlank(overTimeTotalStr)) {
                            try {
                                overTimeTotal = Long.parseLong(overTimeTotalStr.trim());
                            } catch (NumberFormatException e) {
                            }
                        }
                        String overTimeDesc = convertHoursToDayHourStr(overTimeTotal);
                        taskVo.setTotalOverTime(overTimeDesc);
                        // 2. 提取红码阈值 如: 0-22 天-小时 并转换为小时
                        String redTimeStr = processCoding.getRedTime();
                        Long redTimeSec = getTime(redTimeStr);
                        Double redTimeHour = null;
                        if (redTimeSec != null && redTimeSec > 0) {
                            redTimeHour = redTimeSec / 3600.0;
                        }
                        long overTimeCount = 0L;
                        // 判断超时次数
                        if (overTimeTotal > 0 && redTimeHour != null && redTimeHour > 0) {
                            overTimeCount = (long) (overTimeTotal / redTimeHour);
                        }
                        taskVo.setOverTimeCount(overTimeCount);
                    }else {
                        //为null 说明未配置超时时间 默认未超时
                        taskVo.setOverTimeCount(0L);
                        taskVo.setTotalOverTime("0小时");
                    }
                    list.add(taskVo);
                }
            }
        }
        return Result.ok().data(list);
    }
    private String convertHoursToDayHourStr(long totalHours) {
        if (totalHours < 0) {
            return "0小时"; // 防御性处理负数场景
        }
        long days = totalHours / 24; // 计算天数
        long hours = totalHours % 24; // 计算剩余小时数
        StringBuilder sb = new StringBuilder();
        if (days > 0) {
            sb.append(days).append("天");
        }
        // 小时数无论是否为0,都展示(也可根据业务调整:如0小时时不展示)
        if (hours > 0 || days == 0) {
            sb.append(hours).append("小时");
        }
        return sb.toString();
    }
    // 对部门负责人集合信息进行去重
    private void distinctVo(CustomerTaskVO vo) {
        vo.setHandlerId(vo.getHandlerId().stream().distinct().collect(Collectors.toList()));
        vo.setHandlerName(vo.getHandlerName().stream().distinct().collect(Collectors.toList()));
        vo.setHandlerUnitId(vo.getHandlerUnitId().stream().distinct().collect(Collectors.toList()));
        vo.setHandlerUnitName(vo.getHandlerUnitName().stream().distinct().collect(Collectors.toList()));
    }
    //拼接用户昵称以及电话
    private String getUserShowName(SysUser user) {
        return user.getNickName() + (StringUtils.isNotBlank(user.getPhonenumber()) ? "(" + user.getPhonenumber() + ")" : "");
    }
    //拼接部门负责人以及电话
    private String getDeptLeaderShowName(SysDept dept) {
        return dept.getLeader() + (StringUtils.isNotBlank(dept.getPhone()) ? "(" + dept.getPhone() + ")" : "");
    }
    //拼接上级部门以及本部门名称
    private String setDeptNameWithParentName(SysDept dept) {
        String[] str = dept.getAncestors().split(",");
        if (str.length >= 4){
            return dept.getParentName() + "  /  " + dept.getDeptName();
        }else {
            return dept.getDeptName();
        }
    }
    private Long getTime(String timeStr) {
        Long time = null;
        if (StringUtils.isNotBlank(timeStr)) {
            String[] timeArr = timeStr.split("-");
            // 解析天数和小时数
            int days = Integer.parseInt(timeArr[0]);
            int hours = 0;
            if (timeArr.length > 1) {
                hours = Integer.parseInt(timeArr[1]);
            }
            time = (days * 24L + hours) * 3600L;
//            //分-秒
//            time= (days * 60L) + hours;
        }
        return time;
    }
    @Override
    public Result getProjectSelectList() {
        List<ProjectInfo> loginUserOwnProjectInfo = getLoginUserOwnProjectInfo();
        if (CollectionUtils.isEmpty(loginUserOwnProjectInfo)) {
            return Result.ok().data(loginUserOwnProjectInfo);
        }
        Map<String, String> projectMap = loginUserOwnProjectInfo.stream()
                .collect(Collectors.toMap(
                        projectInfo -> String.valueOf(projectInfo.getId()),          // key: 项目ID
                        ProjectInfo::getProjectName,          // value: 项目对象本身
                        (existing, replacement) -> existing,  // 处理重复ID的冲突策略(保留原有值)
                        LinkedHashMap::new
                ));
        return Result.ok().data(projectMap);
    }
}
business/src/main/java/com/ycl/service/impl/ProjectProcessServiceImpl.java
@@ -8,7 +8,6 @@
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.ycl.common.constant.ProcessConstants;
import com.ycl.common.constant.ProcessOverTimeConstants;
import com.ycl.common.core.domain.AjaxResult;
import com.ycl.common.core.domain.entity.SysDept;
import com.ycl.common.core.domain.entity.SysDictData;
import com.ycl.common.core.domain.entity.SysRole;
@@ -199,6 +198,7 @@
    public Result startProcess(ProjectProcessForm form) {
        Long unitId = null;
        if (ProjectProcessTypeEnum.PROJECT.equals(form.getProjectType())) {
            //获得项目信息
            ProjectInfo project = new LambdaQueryChainWrapper<>(projectInfoMapper)
                    .eq(ProjectInfo::getId, form.getProjectId())
                    .one();
@@ -207,6 +207,7 @@
            }
            unitId = project.getProjectOwnerUnit();
        } else if (ProjectProcessTypeEnum.ENGINEERING.equals(form.getProjectType())) {
            //获得工程信息
            ProjectEngineering projectEngineering = new LambdaQueryChainWrapper<>(projectEngineeringMapper)
                    .eq(ProjectEngineering::getId, form.getProjectId())
                    .one();
@@ -219,7 +220,9 @@
        if (Objects.isNull(dept)) {
            throw new RuntimeException("业主单位不存在");
        }
        //启动流程
        String processInsId = this.startPro(form.getProjectId(), form.getProcessDefId(), dept.getDeptId());
        //插入项目流程信息表
        ProjectProcess entity = new ProjectProcess();
        entity.setProjectId(form.getProjectId());
        entity.setProcessDefId(form.getProcessDefId());
@@ -242,7 +245,7 @@
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(processDefId)
                .latestVersion().singleResult();
                .latestVersion().singleResult();// 查询最新版本的流程定义 流程定义对象
        if (Objects.nonNull(processDefinition) && processDefinition.isSuspended()) {
            throw new RuntimeException("该流程已被挂起,请先激活流程");
        }
@@ -254,6 +257,7 @@
        // 将该项目的申请人(业主方)作为流程中某些环节的处理人
        variables.put(ProcessConstants.DATA_LAUNCH, "dept:" + createBy);
        variables.put("a", 1);
        //将参数放入flowable流程引擎并启动流程
        ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefId, projectId + "", variables);
        return processInstance.getId();
    }
@@ -736,6 +740,7 @@
        if (Objects.isNull(task)) {
            throw new RuntimeException("未在运行任务中找到该任务,无法执行转办操作");
        }
        //通过IdentityLink(身份链接)获取任务的原处理人 / 组,分两种情况清理:
        List<IdentityLink> identityLinksForTask = taskService.getIdentityLinksForTask(task.getId());
        // 转办之前的处理人
        List<String> beforeHandlerIds = new ArrayList<>(2);
@@ -1057,7 +1062,7 @@
     * @param result
     */
    public void getTodoTaskList(String projectId, String processInsId, String taskName, int pageSize, int pageNum, Result result) {
        //TODO 看看如何获得人员信息的
        TaskQuery taskQuery = taskService.createTaskQuery()
                .active()
                .processInstanceId(processInsId)
@@ -1720,7 +1725,8 @@
     * @param hisTaskList
     * @return
     */
    private List<HistoricTaskInstance> distinctHisTask(List<HistoricTaskInstance> hisTaskList) {
    @Override
    public List<HistoricTaskInstance> distinctHisTask(List<HistoricTaskInstance> hisTaskList) {
        Map<String, HistoricTaskInstance> uniqueTasks = new HashMap<>();
        for (HistoricTaskInstance task : hisTaskList) {
            String taskDefinitionKey = task.getTaskDefinitionKey();
business/src/main/java/com/ycl/task/FlowableTask.java
@@ -26,6 +26,7 @@
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -123,9 +124,28 @@
                String status = GREEN; // 默认状态为绿色
                String overtimeStatus = NORMAL;
                Long overtimeDurationSec = null; // 超时时长(秒,原始值)
                Double overtimeDurationHour = null; // 超时时长(小时,转换后)
                if (redTime != null && redTime != 0 && durationTime >= redTime) {
                    status = RED; // 如果超过红色时间阈值,则表明该任务超时
                    overtimeStatus = OVERTIME;
                    overtimeDurationSec = durationTime - redTime;
                    // 2. 转换为小时(保留1位小数,避免小数过多)
                    overtimeDurationHour = Math.round((overtimeDurationSec / 3600.0) * 10) / 10.0;
                    // 时间格式化(Java标准API,无依赖)
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    String formattedStartTime = sdf.format(startTime);
                    String formattedNow = sdf.format(now);
                    // 转换红码阈值为小时(可选,日志更易读)
                    Double redTimeHour = Math.round((redTime / 3600.0) * 10) / 10.0;
                    Double durationTimeHour = Math.round((durationTime / 3600.0) * 10) / 10.0;
                    // 打印超时时长(小时单位)
                    log.info("任务超时预警:任务ID[{}],流程实例ID[{}],计时起点[{}],当前时间[{}],红码阈值[{}小时],实际有效耗时[{}小时],超时时长[{}小时]",
                            taskId, task.getProcessInstanceId(),
                            formattedStartTime, formattedNow,
                            redTimeHour, durationTimeHour, overtimeDurationHour);
                }
//                else if (yellowTime != null && yellowTime != 0 && durationTime >= yellowTime) {
//                    status = YELLOW; // 否则,如果超过黄色时间阈值,则状态为黄色
@@ -141,6 +161,7 @@
//                    overtimeStatus = WILLOVERTIME; // 如果临期(固定超时前12小时为临期)
//                }
                map.get(status).add(task.getProcessInstanceId());
                processCoding.setOverTimeTotal(String.valueOf(overtimeDurationHour));
                processCoding.setStatus(status);
                processCoding.setOvertimeStatus(overtimeStatus);
                processCoding.setStartTaskTime(task.getCreateTime());
business/src/main/resources/mapper/ProjectInfoMapper.xml
@@ -250,10 +250,10 @@
    </select>
    <select id="getProjectInfoAndFunding" parameterType="com.ycl.common.core.domain.BaseEntity" resultMap="resultMap">
        SELECT TPI.*,TPIF.total_investment,TPIF.principal,TPIF.government_investment_total,
    <select id="getProjectInfoAndFunding" parameterType="com.ycl.common.core.domain.BaseEntity" resultType="com.ycl.domain.vo.ProjectInvestmentFundingVO">
        SELECT TPIF.project_id,TPIF.total_investment,TPIF.principal,TPIF.government_investment_total,
               TPIF.central_investment_total,TPIF.provincial_investment_total,TPIF.city_investment_total,TPIF.county_investment_total,
               TPIF.other_investment_total
               TPIF.other_investment_total,TPIF.enterprise_self_raised_total,TPIF.foreign_investment_total,TPIF.bank_loan,TPIF.domestic_loan_total
        FROM t_project_info TPI
        LEFT JOIN t_project_investment_funding TPIF ON TPI.id = TPIF.project_id and TPIF.deleted = 0
        LEFT JOIN sys_dept d ON TPI.project_owner_unit = d.dept_id
flowable/src/main/java/com/ycl/domain/entity/ProcessCoding.java
@@ -57,4 +57,7 @@
    @TableField("start_task_time")
    private Date startTaskTime;
    @TableField("overtime_total")
    private String overTimeTotal;
}
flowable/src/main/resources/mapper/ProcessCodingMapper.xml
@@ -97,6 +97,12 @@
                    when id=#{item.id} then #{item.startTaskTime}
                </foreach>
            </trim>
            <trim prefix="overtime_total =case" suffix="end"><!-- 最后一个字段不加逗号,避免suffixOverrides无法移除 -->
                <foreach collection="list" item="item">
                    <!-- 未超时则设为null,已超时则存入小时数(保留1位小数) -->
                    when id=#{item.id} then #{item.overTimeTotal}
                </foreach>
            </trim>
        </trim>
        where id in
        <foreach collection="list" index="index" item="item" separator="," open="(" close=")">
start/src/main/resources/application-dev.yml
@@ -19,13 +19,14 @@
    # redis 配置
    redis:
        # 地址
        host: 42.193.1.25
        host: 127.0.0.1
        # 端口,默认为6379
        port: 6379
        # 数据库索引
        database: 2
        # 密码
        password: ycl2018
#        password: ycl2018
        password:
        # 连接超时时间
        timeout: 10s
        lettuce:
@@ -45,9 +46,13 @@
            # 主库数据源
            master:
#                url: jdbc:mysql://42.193.1.25:3306/project_management?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
                url: jdbc:mysql://42.193.1.25:3306/project_management_test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowMultiQueries=true
#                url: jdbc:mysql://42.193.1.25:3306/project_management_test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowMultiQueries=true
                url: jdbc:mysql://127.0.0.1:3306/project_management?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true&allowMultiQueries=true
                username: root
                password: 321$YcYl@1970!
#                password: 321$YcYl@1970!
                password: 123456
            # 从库数据源
            slave:
                # 从数据源开关/默认关闭
@@ -117,3 +122,4 @@
# 用于替换动态表单的文件上传ip(更换服务器ip没调整)
targetIp: 119.6.246.90