liding
5 天以前 dc5e5c07549b493e21c11dc7929c32ea9fd732b0
协同办公系统模块优化
已添加8个文件
已修改13个文件
1324 ■■■■■ 文件已修改
src/main/java/com/ruoyi/approve/controller/ApproveFlowDefinitionController.java 159 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/ApproveProcessController.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/enums/ApproveType.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/mapper/ApproveFlowDefinitionMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/mapper/ApproveFlowNodeDefinitionMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/ApproveFlowDefinition.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/ApproveFlowNodeDefinition.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/ApproveLog.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/ApproveNode.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/ApproveProcess.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/IApproveNodeService.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/IApproveProcessService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java 320 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java 380 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/vo/ApproveFlowDefinitionVO.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/vo/ApproveGetAndUpdateVo.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/vo/ApproveNodeVO.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/vo/ApproveProcessVO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/approve/ApproveLogMapper.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/approve/ApproveNodeMapper.xml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/approve/ApproveProcessMapper.xml 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/controller/ApproveFlowDefinitionController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,159 @@
package com.ruoyi.approve.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.approve.mapper.ApproveFlowDefinitionMapper;
import com.ruoyi.approve.mapper.ApproveFlowNodeDefinitionMapper;
import com.ruoyi.approve.mapper.ApproveProcessMapper;
import com.ruoyi.approve.pojo.ApproveFlowDefinition;
import com.ruoyi.approve.pojo.ApproveFlowNodeDefinition;
import com.ruoyi.approve.pojo.ApproveProcess;
import com.ruoyi.approve.vo.ApproveFlowDefinitionVO;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.web.domain.AjaxResult;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
@RestController
@RequestMapping("/approveFlowDefinition")
public class ApproveFlowDefinitionController {
    private final ApproveFlowDefinitionMapper definitionMapper;
    private final ApproveFlowNodeDefinitionMapper nodeMapper;
    private final ApproveProcessMapper processMapper;
    public ApproveFlowDefinitionController(ApproveFlowDefinitionMapper definitionMapper,
                                           ApproveFlowNodeDefinitionMapper nodeMapper,
                                           ApproveProcessMapper processMapper) {
        this.definitionMapper = definitionMapper;
        this.nodeMapper = nodeMapper;
        this.processMapper = processMapper;
    }
    @GetMapping("/list")
    public AjaxResult list(Integer approveType) {
        LambdaQueryWrapper<ApproveFlowDefinition> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ApproveFlowDefinition::getTenantId, SecurityUtils.getLoginUser().getTenantId())
                .eq(ApproveFlowDefinition::getDeleteFlag, 0)
                .eq(approveType != null, ApproveFlowDefinition::getApproveType, approveType)
                .orderByDesc(ApproveFlowDefinition::getIsDefault)
                .orderByDesc(ApproveFlowDefinition::getUpdateTime);
        return AjaxResult.success(definitionMapper.selectList(wrapper));
    }
    @GetMapping("/{id}")
    public AjaxResult get(@PathVariable Long id) {
        ApproveFlowDefinition definition = getOwned(id);
        ApproveFlowDefinitionVO result = new ApproveFlowDefinitionVO();
        result.setDefinition(definition);
        result.setNodes(nodeMapper.selectList(new LambdaQueryWrapper<ApproveFlowNodeDefinition>()
                .eq(ApproveFlowNodeDefinition::getFlowDefinitionId, id)
                .eq(ApproveFlowNodeDefinition::getTenantId, definition.getTenantId())
                .eq(ApproveFlowNodeDefinition::getDeleteFlag, 0)
                .orderByAsc(ApproveFlowNodeDefinition::getNodeOrder)));
        return AjaxResult.success(result);
    }
    @PostMapping
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult add(@RequestBody ApproveFlowDefinitionVO request) {
        saveDefinition(request, false);
        return AjaxResult.success();
    }
    @PutMapping("/{id}")
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult update(@PathVariable Long id, @RequestBody ApproveFlowDefinitionVO request) {
        getOwned(id);
        request.getDefinition().setId(id);
        saveDefinition(request, true);
        return AjaxResult.success();
    }
    @PutMapping("/{id}/enabled")
    public AjaxResult enabled(@PathVariable Long id, @RequestParam Integer enabled) {
        ApproveFlowDefinition definition = getOwned(id);
        definition.setEnabled(enabled == null || enabled == 0 ? 0 : 1);
        definition.setUpdateUser(SecurityUtils.getUserId());
        definition.setUpdateTime(LocalDateTime.now());
        definitionMapper.updateById(definition);
        return AjaxResult.success();
    }
    @DeleteMapping("/{id}")
    public AjaxResult delete(@PathVariable Long id) {
        ApproveFlowDefinition definition = getOwned(id);
        Long activeCount = processMapper.selectCount(new LambdaQueryWrapper<ApproveProcess>()
                .eq(ApproveProcess::getTenantId, definition.getTenantId())
                .eq(ApproveProcess::getFlowDefinitionId, id)
                .eq(ApproveProcess::getApproveDelete, 0)
                .in(ApproveProcess::getApproveStatus, 0, 1));
        if (activeCount != null && activeCount > 0) throw new RuntimeException("该流程仍有进行中的审批,不能删除");
        definition.setDeleteFlag(1);
        definition.setUpdateUser(SecurityUtils.getUserId());
        definition.setUpdateTime(LocalDateTime.now());
        definitionMapper.updateById(definition);
        return AjaxResult.success();
    }
    private void saveDefinition(ApproveFlowDefinitionVO request, boolean update) {
        if (request == null || request.getDefinition() == null) throw new RuntimeException("流程定义不能为空");
        List<ApproveFlowNodeDefinition> nodes = request.getNodes() == null ? Collections.emptyList() : request.getNodes();
        if (nodes.isEmpty()) throw new RuntimeException("审批流程至少包含一个节点");
        Long tenantId = SecurityUtils.getLoginUser().getTenantId();
        Long userId = SecurityUtils.getUserId();
        ApproveFlowDefinition definition = request.getDefinition();
        definition.setTenantId(tenantId);
        definition.setUpdateUser(userId);
        definition.setUpdateTime(LocalDateTime.now());
        definition.setDeleteFlag(0);
        if (definition.getEnabled() == null) definition.setEnabled(1);
        if (definition.getIsDefault() == null) definition.setIsDefault(0);
        if (definition.getIsDefault() == 1) {
            List<ApproveFlowDefinition> oldDefaults = definitionMapper.selectList(new LambdaQueryWrapper<ApproveFlowDefinition>()
                    .eq(ApproveFlowDefinition::getTenantId, tenantId)
                    .eq(ApproveFlowDefinition::getApproveType, definition.getApproveType())
                    .eq(ApproveFlowDefinition::getIsDefault, 1)
                    .eq(ApproveFlowDefinition::getDeleteFlag, 0));
            oldDefaults.stream().filter(item -> !item.getId().equals(definition.getId())).forEach(item -> {
                item.setIsDefault(0);
                definitionMapper.updateById(item);
            });
        }
        if (update) {
            definitionMapper.updateById(definition);
            nodeMapper.delete(new LambdaQueryWrapper<ApproveFlowNodeDefinition>()
                    .eq(ApproveFlowNodeDefinition::getFlowDefinitionId, definition.getId())
                    .eq(ApproveFlowNodeDefinition::getTenantId, tenantId));
        } else {
            definition.setCreateUser(userId);
            definition.setCreateTime(LocalDateTime.now());
            definitionMapper.insert(definition);
        }
        int order = 1;
        for (ApproveFlowNodeDefinition node : nodes) {
            node.setId(null);
            node.setFlowDefinitionId(definition.getId());
            node.setNodeOrder(order++);
            node.setTenantId(tenantId);
            node.setCreateUser(userId);
            node.setUpdateUser(userId);
            node.setCreateTime(LocalDateTime.now());
            node.setUpdateTime(LocalDateTime.now());
            node.setDeleteFlag(0);
            if (node.getConditionOperator() == null) node.setConditionOperator("ALWAYS");
            nodeMapper.insert(node);
        }
    }
    private ApproveFlowDefinition getOwned(Long id) {
        ApproveFlowDefinition definition = definitionMapper.selectOne(new LambdaQueryWrapper<ApproveFlowDefinition>()
                .eq(ApproveFlowDefinition::getId, id)
                .eq(ApproveFlowDefinition::getTenantId, SecurityUtils.getLoginUser().getTenantId())
                .eq(ApproveFlowDefinition::getDeleteFlag, 0));
        if (definition == null) throw new RuntimeException("流程定义不存在");
        return definition;
    }
}
src/main/java/com/ruoyi/approve/controller/ApproveProcessController.java
@@ -2,7 +2,6 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.pojo.AccountExpense;
import com.ruoyi.approve.pojo.ApproveProcess;
import com.ruoyi.approve.service.IApproveProcessService;
import com.ruoyi.approve.vo.ApproveGetAndUpdateVo;
@@ -20,7 +19,6 @@
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.ParseException;
import java.util.List;
@RestController
@@ -115,7 +113,7 @@
    @DeleteMapping("/deleteIds")
    @ApiOperation(value = "删除审批")
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult deleteIds(@RequestBody Long[] ids) {
    public AjaxResult deleteIds(@RequestBody String[] ids) {
        if (ids == null || ids.length == 0) {
            return AjaxResult.warn("参数不能为空");
        }
@@ -123,42 +121,34 @@
        return AjaxResult.success("操作成功");
    }
    @ApiOperation(value = "公出管理导出")
    @ApiOperation(value = "自由协同审批导出")
    @PostMapping("/exportOne")
    public void exportOne(HttpServletResponse response) {
        List<ApproveProcess> accountExpenses = approveProcessService.list(new LambdaQueryWrapper<ApproveProcess>()
                .eq(ApproveProcess::getApproveDelete, 0)
                .eq(ApproveProcess::getApproveType, 1));
        List<ApproveProcess> accountExpenses = exportList(1);
        ExcelUtil<ApproveProcess> util = new ExcelUtil<ApproveProcess>(ApproveProcess.class);
        util.exportExcel(response, accountExpenses, "公出管理导出");
        util.exportExcel(response, accountExpenses, "自由协同审批表");
    }
    @ApiOperation(value = "请假管理导出")
    @PostMapping("/exportTwo")
    public void exportTwo(HttpServletResponse response) {
        List<ApproveProcess> accountExpenses = approveProcessService.list(new LambdaQueryWrapper<ApproveProcess>()
                .eq(ApproveProcess::getApproveDelete, 0)
                .eq(ApproveProcess::getApproveType, 2));
        List<ApproveProcess> accountExpenses = exportList(2);
        ExcelUtil<ApproveProcess> util = new ExcelUtil<ApproveProcess>(ApproveProcess.class);
        util.exportExcel(response, accountExpenses, "请假管理导出");
    }
    @ApiOperation(value = "出差管理导出")
    @ApiOperation(value = "销售合同审批导出")
    @PostMapping("/exportThree")
    public void exportThree(HttpServletResponse response) {
        List<ApproveProcess> accountExpenses = approveProcessService.list(new LambdaQueryWrapper<ApproveProcess>()
                .eq(ApproveProcess::getApproveDelete, 0)
                .eq(ApproveProcess::getApproveType, 3));
        List<ApproveProcess> accountExpenses = exportList(3);
        ExcelUtil<ApproveProcess> util = new ExcelUtil<ApproveProcess>(ApproveProcess.class);
        util.exportExcel(response, accountExpenses, "出差管理导出");
        util.exportExcel(response, accountExpenses, "销售合同审批表");
    }
    @ApiOperation(value = "报销管理导出")
    @PostMapping("/exportFour")
    public void exportFour(HttpServletResponse response) {
        List<ApproveProcess> accountExpenses = approveProcessService.list(new LambdaQueryWrapper<ApproveProcess>()
                .eq(ApproveProcess::getApproveDelete, 0)
                .eq(ApproveProcess::getApproveType, 4));
        List<ApproveProcess> accountExpenses = exportList(4);
        ExcelUtil<ApproveProcess> util = new ExcelUtil<ApproveProcess>(ApproveProcess.class);
        util.exportExcel(response, accountExpenses, "报销管理导出");
    }
@@ -166,20 +156,37 @@
    @ApiOperation(value = "采购申请导出")
    @PostMapping("/exportFive")
    public void exportFive(HttpServletResponse response) {
        List<ApproveProcess> accountExpenses = approveProcessService.list(new LambdaQueryWrapper<ApproveProcess>()
                .eq(ApproveProcess::getApproveDelete, 0)
                .eq(ApproveProcess::getApproveType, 5));
        List<ApproveProcess> accountExpenses = exportList(5);
        ExcelUtil<ApproveProcess> util = new ExcelUtil<ApproveProcess>(ApproveProcess.class);
        util.exportExcel(response, accountExpenses, "采购申请导出");
        util.exportExcel(response, accountExpenses, "采购计划审批表");
    }
    @ApiOperation(value = "协同审批导出")
    @PostMapping("/exportZero")
    public void exportZero(HttpServletResponse response) {
        List<ApproveProcess> accountExpenses = approveProcessService.list(new LambdaQueryWrapper<ApproveProcess>()
                        .eq(ApproveProcess::getApproveDelete, 0)
                .eq(ApproveProcess::getApproveType, 0));
        List<ApproveProcess> accountExpenses = exportList(1);
        ExcelUtil<ApproveProcess> util = new ExcelUtil<ApproveProcess>(ApproveProcess.class);
        util.exportExcel(response, accountExpenses, "协同审批导出");
        util.exportExcel(response, accountExpenses, "自由协同审批表");
    }
    @ApiOperation(value = "报价审批导出")
    @PostMapping("/exportSix")
    public void exportSix(HttpServletResponse response) {
        ExcelUtil<ApproveProcess> util = new ExcelUtil<>(ApproveProcess.class);
        util.exportExcel(response, exportList(6), "报价审批表");
    }
    @ApiOperation(value = "出库审批导出")
    @PostMapping("/exportSeven")
    public void exportSeven(HttpServletResponse response) {
        ExcelUtil<ApproveProcess> util = new ExcelUtil<>(ApproveProcess.class);
        util.exportExcel(response, exportList(7), "出库审批表");
    }
    private List<ApproveProcess> exportList(int type) {
        return approveProcessService.list(new LambdaQueryWrapper<ApproveProcess>()
                .eq(ApproveProcess::getApproveDelete, 0)
                .eq(ApproveProcess::getApproveType, type)
                .eq(ApproveProcess::getTenantId, SecurityUtils.getLoginUser().getTenantId()));
    }
}
src/main/java/com/ruoyi/approve/enums/ApproveType.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package com.ruoyi.approve.enums;
import java.util.Arrays;
public enum ApproveType {
    FREE_COLLABORATION(1, "自由协同审批"),
    LEAVE(2, "请假管理"),
    SALES_CONTRACT(3, "销售审批"),
    REIMBURSEMENT(4, "报销审批"),
    PURCHASE_PLAN(5, "采购审批"),
    QUOTATION(6, "报价审批"),
    OUTBOUND(7, "出库审批");
    private final int code;
    private final String label;
    ApproveType(int code, String label) {
        this.code = code;
        this.label = label;
    }
    public int getCode() {
        return code;
    }
    public String getLabel() {
        return label;
    }
    public static ApproveType fromCode(Integer code) {
        return Arrays.stream(values()).filter(item -> item.code == code).findFirst()
                .orElseThrow(() -> new IllegalArgumentException("不支持的审批类型"));
    }
}
src/main/java/com/ruoyi/approve/mapper/ApproveFlowDefinitionMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
package com.ruoyi.approve.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.approve.pojo.ApproveFlowDefinition;
public interface ApproveFlowDefinitionMapper extends BaseMapper<ApproveFlowDefinition> {
}
src/main/java/com/ruoyi/approve/mapper/ApproveFlowNodeDefinitionMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,7 @@
package com.ruoyi.approve.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.approve.pojo.ApproveFlowNodeDefinition;
public interface ApproveFlowNodeDefinitionMapper extends BaseMapper<ApproveFlowNodeDefinition> {
}
src/main/java/com/ruoyi/approve/pojo/ApproveFlowDefinition.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,50 @@
package com.ruoyi.approve.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("approve_flow_definition")
public class ApproveFlowDefinition {
    @ApiModelProperty(value = "流程定义ID")
    @TableId(type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "流程名称")
    private String flowName;
    @ApiModelProperty(value = "审批类型")
    private Integer approveType;
    @ApiModelProperty(value = "启用状态:0停用,1启用")
    private Integer enabled;
    @ApiModelProperty(value = "是否默认流程:0否,1是")
    private Integer isDefault;
    @ApiModelProperty(value = "流程备注")
    private String remark;
    @ApiModelProperty(value = "租户ID")
    private Long tenantId;
    @ApiModelProperty(value = "创建用户ID")
    private Long createUser;
    @ApiModelProperty(value = "更新用户ID")
    private Long updateUser;
    @ApiModelProperty(value = "创建时间")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "更新时间")
    private LocalDateTime updateTime;
    @ApiModelProperty(value = "删除标志:0正常,1删除")
    private Integer deleteFlag;
}
src/main/java/com/ruoyi/approve/pojo/ApproveFlowNodeDefinition.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package com.ruoyi.approve.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("approve_flow_node_definition")
public class ApproveFlowNodeDefinition {
    @ApiModelProperty(value = "流程节点定义ID")
    @TableId(type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "流程定义ID")
    private Long flowDefinitionId;
    @ApiModelProperty(value = "节点顺序")
    private Integer nodeOrder;
    @ApiModelProperty(value = "节点名称")
    private String nodeName;
    @ApiModelProperty(value = "审批人类型")
    private String approverType;
    @ApiModelProperty(value = "审批人配置值")
    private String approverValue;
    @ApiModelProperty(value = "条件字段")
    private String conditionField;
    @ApiModelProperty(value = "条件操作符")
    private String conditionOperator;
    @ApiModelProperty(value = "条件值")
    private String conditionValue;
    @ApiModelProperty(value = "租户ID")
    private Long tenantId;
    @ApiModelProperty(value = "创建用户ID")
    private Long createUser;
    @ApiModelProperty(value = "更新用户ID")
    private Long updateUser;
    @ApiModelProperty(value = "创建时间")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "更新时间")
    private LocalDateTime updateTime;
    @ApiModelProperty(value = "删除标志:0正常,1删除")
    private Integer deleteFlag;
}
src/main/java/com/ruoyi/approve/pojo/ApproveLog.java
@@ -4,6 +4,7 @@
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@@ -20,38 +21,57 @@
    /**
     *
     */
    @ApiModelProperty(value = "主键ID")
    @TableId(type = IdType.AUTO)
    private Long id;
    /**
     * å®¡æ‰¹id
     */
    @ApiModelProperty(value = "审批日志业务ID")
    private Long approveId;
    @ApiModelProperty(value = "审批流程主键")
    private Long approveProcessId;
    @ApiModelProperty(value = "审批节点主键")
    private Long approveNodeId;
    /**
     * å®¡æ‰¹èŠ‚ç‚¹é¡ºåº
     */
    @ApiModelProperty(value = "审批节点顺序")
    private Integer approveNodeOrder;
    /**
     * å®¡æ‰¹äººid
     */
    @ApiModelProperty(value = "审批用户ID")
    private Long approveUser;
    @ApiModelProperty(value = "审批用户名称")
    private String approveUserName;
    /**
     * å®¡æ‰¹æ—¶é—´
     */
    @ApiModelProperty(value = "审批时间")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date approveTime;
    /**
     * å®¡æ‰¹çŠ¶æ€
     */
    @ApiModelProperty(value = "审批状态")
    private Integer approveStatus;
    /**
     * å®¡æ‰¹å¤‡æ³¨
     */
    @ApiModelProperty(value = "审批备注")
    private String approveRemark;
}
    @ApiModelProperty(value = "租户ID")
    private Long tenantId;
}
src/main/java/com/ruoyi/approve/pojo/ApproveNode.java
@@ -35,39 +35,47 @@
    /**
     * 
     */
    @ApiModelProperty(value = "主键ID")
    private Long id;
    /**
     * å®¡æ‰¹ç¼–号
     */
    @ApiModelProperty(value = "审批流程编号")
    private String approveProcessId;
    /**
     * å®¡æ‰¹èŠ‚ç‚¹é¡ºåº
     */
    @ApiModelProperty(value = "审批节点顺序")
    private Integer approveNodeOrder;
    /**
     * å®¡æ‰¹èŠ‚ç‚¹ç”¨æˆ·åå­—
     */
    @ApiModelProperty(value = "审批节点用户名称")
    private String approveNodeUser;
    /**
     * å®¡æ‰¹èŠ‚ç‚¹ç”¨æˆ·id
     */
    @ApiModelProperty(value = "审批节点用户ID")
    private Long approveNodeUserId;
    /**
     * å®¡æ‰¹èŠ‚ç‚¹æ—¶é—´
     */
    @ApiModelProperty(value = "审批节点时间")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date approveNodeTime;
    /**
     * å®¡æ‰¹èŠ‚ç‚¹çŠ¶æ€:1同意,2拒绝,0尚未审核(默认为0)
     */
    @ApiModelProperty(value = "审批节点状态:0尚未审核,1同意,2拒绝")
    private Integer approveNodeStatus;
    @ApiModelProperty(value = "是否为第一个节点")
    @TableField(exist = false)
    private Boolean isFirst;
@@ -75,12 +83,14 @@
    /**
     * æ˜¯å¦ä¸ºæœ€åŽä¸€æ­¥
     */
    @ApiModelProperty(value = "是否为最后一个节点")
    @TableField(exist = false)
    private Boolean isLast;
    /**
     * æ˜¯å¦ä¸ºå½“前审核节点
     */
    @ApiModelProperty(value = "当前用户是否可审核")
    @TableField(exist = false)
    private Boolean isShen = false;
@@ -88,44 +98,67 @@
     * ç§Ÿæˆ·id
     */
//    @TableField(fill = FieldFill.INSERT)
    @ApiModelProperty(value = "租户ID")
    private Long tenantId;
    /**
     * åˆ é™¤æ ‡è®°:0正常,1删除
     */
    @ApiModelProperty(value = "删除标志:0正常,1删除")
    private Integer deleteFlag;
    /**
     * å…¥åº“用户id
     */
    @ApiModelProperty(value = "创建用户ID")
    private Long createUser;
    /**
     * å…¥åº“æ—¶é—´
     */
    @ApiModelProperty(value = "创建时间")
    private LocalDateTime createTime;
    /**
     * ä¿®æ”¹è€…
     */
    @ApiModelProperty(value = "更新用户ID")
    private Long updateUser;
    /**
     * ä¿®æ”¹æ—¶é—´
     */
    @ApiModelProperty(value = "更新时间")
    private LocalDateTime updateTime;
    /**
     * å®¡æ‰¹èŠ‚ç‚¹æ‹’ç»åŽŸå› 
     */
    @ApiModelProperty(value = "审批处理原因")
    private String approveNodeReason;
    /**
     * å®¡æ‰¹èŠ‚ç‚¹å¤‡æ³¨
     */
    @ApiModelProperty(value = "审批节点备注")
    private String approveNodeRemark;
    @ApiModelProperty(value = "审批节点名称")
    private String approveNodeName;
    @ApiModelProperty(value = "条件字段")
    private String conditionField;
    @ApiModelProperty(value = "条件操作符")
    private String conditionOperator;
    @ApiModelProperty(value = "条件值")
    private String conditionValue;
    @ApiModelProperty(value = "是否为当前节点:0否,1是")
    private Integer isCurrent;
    private static final long serialVersionUID = 1L;
}
}
src/main/java/com/ruoyi/approve/pojo/ApproveProcess.java
@@ -4,6 +4,7 @@
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
@@ -24,9 +25,11 @@
    /**
     * 
     */
    @ApiModelProperty(value = "主键ID")
    @TableId(type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "附件列表")
    @TableField(exist = false)
    private List<CommonFile> commonFileList;
@@ -40,6 +43,7 @@
    /**
     * ç”³è¯·äººid
     */
    @ApiModelProperty(value = "申请人ID")
    private Long approveUser;
    /**
@@ -52,6 +56,7 @@
    /**
     * ç”³è¯·éƒ¨é—¨id
     */
    @ApiModelProperty(value = "申请部门ID")
    private Long approveDeptId;
    /**
@@ -64,11 +69,13 @@
    /**
     * å®¡æ‰¹ç”¨æˆ·ids
     */
    @ApiModelProperty(value = "审批用户ID列表,多个以逗号分隔")
    private String approveUserIds;
    /**
     * å®¡æ‰¹ç”¨æˆ·åç§°
     */
    @ApiModelProperty(value = "审批用户名称列表")
    private String approveUserNames;
    /**
@@ -81,6 +88,7 @@
    /**
     * å½“前审批用户ID
     */
    @ApiModelProperty(value = "当前审批用户ID")
    private Long approveUserCurrentId;
    /**
@@ -116,17 +124,20 @@
    /**
     * å®¡æ‰¹æ˜¯å¦åˆ é™¤ï¼š0正常,1删除
     */
    @ApiModelProperty(value = "删除标志:0正常,1删除")
    private Integer approveDelete;
    /**
     * ç§Ÿæˆ·id
     */
//    @TableField(fill = FieldFill.INSERT)
    @ApiModelProperty(value = "租户ID")
    private Long tenantId;
    /**
     * å®¡æ‰¹ç±»åž‹
     */
    @ApiModelProperty(value = "审批类型:1自由协同,2请假,3销售,4报销,5采购,6报价,7出库")
    private Integer approveType;
    /**
@@ -135,12 +146,34 @@
    @ApiModelProperty(value = "审批备注")
    private String approveRemark;
    @ApiModelProperty(value = "关联业务主键")
    private Long businessId;
    @ApiModelProperty(value = "业务编号")
    private String businessNo;
    @ApiModelProperty(value = "审批业务标题")
    private String businessTitle;
    @ApiModelProperty(value = "业务金额")
    private BigDecimal amount;
    @ApiModelProperty(value = "业务数据JSON快照")
    private String businessData;
    @ApiModelProperty(value = "流程定义ID")
    private Long flowDefinitionId;
    /**
     * åˆ›å»ºæ—¶é—´
     */
    @ApiModelProperty(value = "创建时间")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "更新时间")
    private LocalDateTime updateTime;
    private static final long serialVersionUID = 1L;
}
}
src/main/java/com/ruoyi/approve/service/IApproveNodeService.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.approve.pojo.ApproveNode;
import com.ruoyi.approve.vo.ApproveNodeVO;
import java.io.IOException;
import java.util.List;
@@ -9,6 +10,7 @@
public interface IApproveNodeService extends IService<ApproveNode> {
    void initApproveNodes(String approveUserIds,String approveID,Long tenantId);
    void initApproveNodes(List<ApproveNodeVO> nodes, String approveID, Long tenantId);
    /**
     * è¯¦æƒ…
     * @param id
@@ -17,5 +19,5 @@
    void updateApproveNode(ApproveNode approveNode) throws IOException;
    void delApproveNodeByApproveId(Long id);
    void delApproveNodeByApproveId(String id);
}
src/main/java/com/ruoyi/approve/service/IApproveProcessService.java
@@ -27,7 +27,7 @@
    IPage<ApproveProcess> listAll(Page page, ApproveProcess approveProcess);
    void delApprove(Long[] ids);
    void delApprove(String[] ids);
    void updateByApproveId(ApproveGetAndUpdateVo approveGetAndUpdateVo) throws IOException;
src/main/java/com/ruoyi/approve/service/impl/ApproveNodeServiceImpl.java
@@ -3,11 +3,14 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.approve.mapper.ApproveLogMapper;
import com.ruoyi.approve.mapper.ApproveNodeMapper;
import com.ruoyi.approve.mapper.ApproveProcessMapper;
import com.ruoyi.approve.pojo.ApproveLog;
import com.ruoyi.approve.pojo.ApproveNode;
import com.ruoyi.approve.pojo.ApproveProcess;
import com.ruoyi.approve.service.IApproveNodeService;
import com.ruoyi.approve.vo.ApproveNodeVO;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.other.service.impl.TempFileServiceImpl;
@@ -15,14 +18,16 @@
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.sales.mapper.CommonFileMapper;
import com.ruoyi.sales.pojo.CommonFile;
import com.ruoyi.sales.service.impl.CommonFileServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
@@ -30,142 +35,215 @@
@Service
@RequiredArgsConstructor
public class ApproveNodeServiceImpl extends ServiceImpl<ApproveNodeMapper, ApproveNode> implements IApproveNodeService {
    private final ApproveNodeMapper approveNodeMapper;
    private final ApproveProcessMapper approveProcessMapper;
    private final ApproveLogMapper approveLogMapper;
    private final SysUserMapper sysUserMapper;
    private final TempFileServiceImpl tempFileService;
    private final CommonFileMapper fileMapper;
    @Autowired
    private  ApproveNodeMapper approveNodeMapper;
    @Autowired
    private ApproveProcessMapper approveProcessMapper;
    @Autowired
    private SysUserMapper sysUserMapper;
    @Autowired
    private TempFileServiceImpl tempFileService;
    @Autowired
    private CommonFileMapper fileMapper;
    public ApproveProcess getApproveById(String id) {
        LambdaQueryWrapper<ApproveProcess> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ApproveProcess::getApproveId, id);
        queryWrapper.eq(ApproveProcess::getApproveDelete, 0);
        queryWrapper.eq(ApproveProcess::getTenantId, SecurityUtils.getLoginUser().getTenantId());
        queryWrapper.last("limit 1");
        ApproveProcess one = approveProcessMapper.selectOne(queryWrapper);
        return one;
    private ApproveProcess getApproveById(String code) {
        ApproveProcess process = approveProcessMapper.selectOne(new LambdaQueryWrapper<ApproveProcess>()
                .eq(ApproveProcess::getApproveId, code)
                .eq(ApproveProcess::getApproveDelete, 0)
                .eq(ApproveProcess::getTenantId, tenantId())
                .last("limit 1"));
        if (process == null) throw new RuntimeException("审批不存在");
        return process;
    }
    @Override
    public void initApproveNodes(String approveUserIds,String approveID,Long tenantId) {
        Long userId = SecurityUtils.getLoginUser().getUser().getUserId();
        ApproveProcess approve = getApproveById(approveID);
        String[] names = approveUserIds.split(",");
        String approveId = approve.getApproveId();
        for (int i = 0; i < names.length; i++) {
            SysUser sysUser = sysUserMapper.selectUserById(Long.parseLong(names[i]));
            if (sysUser == null) continue;
            ApproveNode approveNode = new ApproveNode();
            approveNode.setApproveProcessId(approveId);
            approveNode.setApproveNodeOrder(i +1);
            approveNode.setApproveNodeUser(sysUser.getNickName());
            approveNode.setApproveNodeUserId(sysUser.getUserId());
            approveNode.setApproveNodeTime(new Date());
            approveNode.setApproveNodeStatus(0);
            approveNode.setTenantId(tenantId);
            approveNode.setDeleteFlag(0);
            approveNode.setCreateUser(userId);
            approveNode.setUpdateUser(userId);
            approveNode.setCreateTime(LocalDateTime.now());
            approveNode.setUpdateTime(LocalDateTime.now());
            approveNodeMapper.insert(approveNode);
    public void initApproveNodes(String approveUserIds, String approveID, Long tenantId) {
        if (!StringUtils.hasText(approveUserIds)) throw new RuntimeException("请选择审批人");
        List<ApproveNodeVO> nodes = new ArrayList<>();
        for (String id : approveUserIds.split(",")) {
            ApproveNodeVO node = new ApproveNodeVO();
            node.setUserId(Long.parseLong(id.trim()));
            node.setConditionOperator("ALWAYS");
            nodes.add(node);
        }
        initApproveNodes(nodes, approveID, tenantId);
    }
    @Override
    public void initApproveNodes(List<ApproveNodeVO> nodes, String approveID, Long ignoredTenantId) {
        if (CollectionUtils.isEmpty(nodes)) throw new RuntimeException("审批流程至少包含一个节点");
        Long tenantId = tenantId();
        Long currentUserId = SecurityUtils.getUserId();
        ApproveProcess process = getApproveById(approveID);
        int order = 1;
        ApproveNode first = null;
        Long previousUserId = null;
        for (ApproveNodeVO request : nodes) {
            if (request.getUserId() == null) throw new RuntimeException("审批人不能为空");
            if (request.getUserId().equals(previousUserId)) throw new RuntimeException("相邻审批节点不能选择同一审批人");
            previousUserId = request.getUserId();
            if (!matchesCondition(process.getAmount(), request)) continue;
            SysUser user = sysUserMapper.selectUserById(request.getUserId());
            if (user == null || !tenantId.equals(user.getTenantId())) throw new RuntimeException("审批人不存在或不属于当前租户");
            ApproveNode node = new ApproveNode();
            node.setApproveProcessId(process.getApproveId());
            node.setApproveNodeOrder(order++);
            node.setApproveNodeName(StringUtils.hasText(request.getNodeName()) ? request.getNodeName() : "审批");
            node.setApproveNodeUser(user.getNickName());
            node.setApproveNodeUserId(user.getUserId());
            node.setApproveNodeStatus(0);
            node.setConditionField(request.getConditionField());
            node.setConditionOperator(StringUtils.hasText(request.getConditionOperator()) ? request.getConditionOperator() : "ALWAYS");
            node.setConditionValue(request.getConditionValue());
            node.setIsCurrent(first == null ? 1 : 0);
            node.setTenantId(tenantId);
            node.setDeleteFlag(0);
            node.setCreateUser(currentUserId);
            node.setUpdateUser(currentUserId);
            node.setCreateTime(LocalDateTime.now());
            node.setUpdateTime(LocalDateTime.now());
            approveNodeMapper.insert(node);
            if (first == null) first = node;
        }
        if (first == null) throw new RuntimeException("没有满足条件的审批节点");
        process.setApproveUserCurrentId(first.getApproveNodeUserId());
        process.setApproveUserCurrentName(first.getApproveNodeUser());
        process.setUpdateTime(LocalDateTime.now());
        approveProcessMapper.updateById(process);
    }
    private boolean matchesCondition(BigDecimal amount, ApproveNodeVO node) {
        String operator = node.getConditionOperator();
        if (!StringUtils.hasText(operator) || "ALWAYS".equalsIgnoreCase(operator)) return true;
        if (!"amount".equals(node.getConditionField())) throw new RuntimeException("审批条件仅支持金额字段");
        if (amount == null || !StringUtils.hasText(node.getConditionValue())) return false;
        try {
            if ("BETWEEN".equalsIgnoreCase(operator)) {
                String[] values = node.getConditionValue().replace("[", "").replace("]", "").split(",");
                if (values.length != 2) throw new RuntimeException("金额区间格式不正确");
                return amount.compareTo(new BigDecimal(values[0].trim())) >= 0
                        && amount.compareTo(new BigDecimal(values[1].trim())) <= 0;
            }
            int compared = amount.compareTo(new BigDecimal(node.getConditionValue()));
            if ("GT".equalsIgnoreCase(operator)) return compared > 0;
            if ("GTE".equalsIgnoreCase(operator)) return compared >= 0;
            if ("LT".equalsIgnoreCase(operator)) return compared < 0;
            if ("LTE".equalsIgnoreCase(operator)) return compared <= 0;
            if ("EQ".equalsIgnoreCase(operator)) return compared == 0;
        } catch (NumberFormatException ex) {
            throw new RuntimeException("审批条件金额格式不正确");
        }
        throw new RuntimeException("不支持的审批条件操作符");
    }
    @Override
    public List<ApproveNode> details(String id) {
        LambdaQueryWrapper<ApproveNode> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ApproveNode::getApproveProcessId, id);
        queryWrapper.eq(ApproveNode::getDeleteFlag, 0);
        queryWrapper.eq(ApproveNode::getTenantId, SecurityUtils.getLoginUser().getTenantId());
        List<ApproveNode> list = list(queryWrapper);
        // æŒ‰ç…§ approveNodeOrder å­—段升序排序
        list.sort(Comparator.comparingInt(ApproveNode::getApproveNodeOrder));
        LambdaQueryWrapper<ApproveProcess> approveProcessLambdaQueryWrapper = new LambdaQueryWrapper<>();
        approveProcessLambdaQueryWrapper.eq(ApproveProcess::getApproveId, id)
                .eq(ApproveProcess::getApproveDelete, 0)
                .eq(ApproveProcess::getTenantId, SecurityUtils.getLoginUser().getTenantId())
                .last("limit 1");
        ApproveProcess approveProcess = approveProcessMapper.selectOne(approveProcessLambdaQueryWrapper);
        if(approveProcess != null && approveProcess.getApproveStatus() == 3){
            return list;
        }
        for (ApproveNode approveNode : list) {
            List<CommonFile> commonFiles = fileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
                    .eq(CommonFile::getCommonId, approveNode.getId())
                    .eq(CommonFile::getType, FileNameType.ApproveNode.getValue()));
            if(!CollectionUtils.isEmpty(commonFiles)){
                approveNode.setUrl(commonFiles.get(0).getUrl());
            }
            if(approveNode.getApproveNodeStatus() == 1){
                continue;
            }
            approveNode.setIsShen(true);
            break;
        }
        return list;
    }
    public void updateApproveProcessStatus(ApproveNode approveNode,Integer status) throws IOException {
        LambdaQueryWrapper<ApproveProcess> approveProcessLambdaQueryWrapper = new LambdaQueryWrapper<>();
        approveProcessLambdaQueryWrapper.eq(ApproveProcess::getApproveId, approveNode.getApproveProcessId())
                .eq(ApproveProcess::getApproveDelete, 0)
                .eq(ApproveProcess::getTenantId, SecurityUtils.getLoginUser().getTenantId())
                .last("limit 1");
        ApproveProcess approveProcess = approveProcessMapper.selectOne(approveProcessLambdaQueryWrapper);
        if(approveProcess == null) throw new RuntimeException("审批不存在");
        LambdaQueryWrapper<ApproveNode> approveNodeLambdaQueryWrapper = new LambdaQueryWrapper<>();
        approveNodeLambdaQueryWrapper.eq(ApproveNode::getApproveProcessId, approveNode.getApproveProcessId())
                .eq(ApproveNode::getApproveNodeOrder, approveNode.getApproveNodeOrder() + 1)
        getApproveById(id);
        List<ApproveNode> nodes = approveNodeMapper.selectList(new LambdaQueryWrapper<ApproveNode>()
                .eq(ApproveNode::getApproveProcessId, id)
                .eq(ApproveNode::getDeleteFlag, 0)
                .eq(ApproveNode::getTenantId, SecurityUtils.getLoginUser().getTenantId())
                .last("limit 1");
        ApproveNode approveNode1 = approveNodeMapper.selectOne(approveNodeLambdaQueryWrapper);
        approveProcess.setApproveStatus(status);
        if(approveNode1 != null){
            approveProcess.setApproveUserCurrentId(approveNode1.getApproveNodeUserId());
            approveProcess.setApproveUserCurrentName(approveNode1.getApproveNodeUser());
                .eq(ApproveNode::getTenantId, tenantId())
                .orderByAsc(ApproveNode::getApproveNodeOrder));
        for (ApproveNode node : nodes) {
            List<CommonFile> files = fileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
                    .eq(CommonFile::getCommonId, node.getId())
                    .eq(CommonFile::getType, FileNameType.ApproveNode.getValue()));
            if (!CollectionUtils.isEmpty(files)) node.setUrl(files.get(0).getUrl());
            node.setIsShen(Integer.valueOf(1).equals(node.getIsCurrent())
                    && node.getApproveNodeUserId().equals(SecurityUtils.getUserId()));
        }
        approveProcessMapper.updateById(approveProcess);
        nodes.sort(Comparator.comparingInt(ApproveNode::getApproveNodeOrder));
        return nodes;
    }
        // ç»‘定附件
        if(!CollectionUtils.isEmpty(approveNode.getTempFileIds()) && approveNode.getApproveNodeStatus() == 1){
            tempFileService.migrateTempFilesToFormal(approveNode.getId(), approveNode.getTempFileIds(), FileNameType.ApproveNode.getValue());
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateApproveNode(ApproveNode request) throws IOException {
        if (request == null || request.getId() == null) throw new RuntimeException("审批节点不能为空");
        if (request.getApproveNodeStatus() == null || (request.getApproveNodeStatus() != 1 && request.getApproveNodeStatus() != 2)) {
            throw new RuntimeException("审批结果不正确");
        }
        if (!StringUtils.hasText(request.getApproveNodeReason())) throw new RuntimeException("审批意见不能为空");
        Long tenantId = tenantId();
        ApproveNode node = approveNodeMapper.selectOne(new LambdaQueryWrapper<ApproveNode>()
                .eq(ApproveNode::getId, request.getId())
                .eq(ApproveNode::getTenantId, tenantId)
                .eq(ApproveNode::getDeleteFlag, 0));
        if (node == null) throw new RuntimeException("审批节点不存在");
        if (!Integer.valueOf(0).equals(node.getApproveNodeStatus()) || !Integer.valueOf(1).equals(node.getIsCurrent())) {
            throw new RuntimeException("该节点已经处理或不是当前审批节点");
        }
        if (!SecurityUtils.getUserId().equals(node.getApproveNodeUserId())) throw new RuntimeException("当前用户无权审批此节点");
        ApproveProcess process = getApproveById(node.getApproveProcessId());
        if (Integer.valueOf(2).equals(process.getApproveStatus()) || Integer.valueOf(3).equals(process.getApproveStatus())) {
            throw new RuntimeException("审批流程已经结束");
        }
        node.setApproveNodeStatus(request.getApproveNodeStatus());
        node.setApproveNodeReason(request.getApproveNodeReason().trim());
        node.setApproveNodeRemark(request.getApproveNodeRemark());
        node.setApproveNodeTime(new Date());
        node.setIsCurrent(0);
        node.setUpdateUser(SecurityUtils.getUserId());
        node.setUpdateTime(LocalDateTime.now());
        approveNodeMapper.updateById(node);
        if (node.getApproveNodeStatus() == 2) {
            process.setApproveStatus(3);
            process.setApproveUserCurrentId(null);
            process.setApproveUserCurrentName(null);
            process.setApproveOverTime(new Date());
        } else {
            ApproveNode next = approveNodeMapper.selectOne(new LambdaQueryWrapper<ApproveNode>()
                    .eq(ApproveNode::getApproveProcessId, node.getApproveProcessId())
                    .eq(ApproveNode::getTenantId, tenantId)
                    .eq(ApproveNode::getDeleteFlag, 0)
                    .eq(ApproveNode::getApproveNodeStatus, 0)
                    .gt(ApproveNode::getApproveNodeOrder, node.getApproveNodeOrder())
                    .orderByAsc(ApproveNode::getApproveNodeOrder)
                    .last("limit 1"));
            if (next == null) {
                process.setApproveStatus(2);
                process.setApproveOverTime(new Date());
                process.setApproveUserCurrentId(null);
                process.setApproveUserCurrentName(null);
            } else {
                next.setIsCurrent(1);
                next.setUpdateTime(LocalDateTime.now());
                approveNodeMapper.updateById(next);
                process.setApproveStatus(1);
                process.setApproveUserCurrentId(next.getApproveNodeUserId());
                process.setApproveUserCurrentName(next.getApproveNodeUser());
            }
        }
        process.setUpdateTime(LocalDateTime.now());
        approveProcessMapper.updateById(process);
        ApproveLog log = new ApproveLog();
        log.setApproveId(process.getId());
        log.setApproveProcessId(process.getId());
        log.setApproveNodeId(node.getId());
        log.setApproveNodeOrder(node.getApproveNodeOrder());
        log.setApproveUser(SecurityUtils.getUserId());
        log.setApproveUserName(node.getApproveNodeUser());
        log.setApproveTime(new Date());
        log.setApproveStatus(node.getApproveNodeStatus());
        log.setApproveRemark(node.getApproveNodeReason());
        log.setTenantId(tenantId);
        approveLogMapper.insert(log);
        if (!CollectionUtils.isEmpty(request.getTempFileIds()) && node.getApproveNodeStatus() == 1) {
            tempFileService.migrateTempFilesToFormal(node.getId(), request.getTempFileIds(), FileNameType.ApproveNode.getValue());
        }
    }
    @Override
    public void updateApproveNode(ApproveNode approveNode) throws IOException {
        // å®¡æ‰¹èŠ‚ç‚¹çŠ¶æ€:1同意,2拒绝,0尚未审核
        switch (approveNode.getApproveNodeStatus()){
            case 1:
                updateApproveProcessStatus(approveNode, Boolean.TRUE.equals(approveNode.getIsLast()) ? 2 : 1);
                break;
            case 2:
                updateApproveProcessStatus(approveNode, 3);
                break;
            default:
                break;
        }
        approveNode.setApproveNodeTime(new Date());
        approveNodeMapper.updateById(approveNode);
    public void delApproveNodeByApproveId(String id) {
        UpdateWrapper<ApproveNode> wrapper = new UpdateWrapper<>();
        wrapper.lambda().set(ApproveNode::getDeleteFlag, 1)
                .set(ApproveNode::getIsCurrent, 0)
                .eq(ApproveNode::getApproveProcessId, id)
                .eq(ApproveNode::getTenantId, tenantId());
        update(wrapper);
    }
    @Override
    public void delApproveNodeByApproveId(Long id) {
        UpdateWrapper<ApproveNode> queryWrapper = new UpdateWrapper<>();
        queryWrapper.lambda().set(ApproveNode::getDeleteFlag, 1)
                .eq(ApproveNode::getApproveProcessId, id);
        update(queryWrapper);
    private Long tenantId() {
        return SecurityUtils.getLoginUser().getTenantId();
    }
}
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
@@ -5,14 +5,22 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.approve.enums.ApproveType;
import com.ruoyi.approve.mapper.ApproveFlowDefinitionMapper;
import com.ruoyi.approve.mapper.ApproveFlowNodeDefinitionMapper;
import com.ruoyi.approve.mapper.ApproveNodeMapper;
import com.ruoyi.approve.mapper.ApproveProcessMapper;
import com.ruoyi.approve.pojo.ApproveFlowDefinition;
import com.ruoyi.approve.pojo.ApproveFlowNodeDefinition;
import com.ruoyi.approve.pojo.ApproveNode;
import com.ruoyi.approve.pojo.ApproveProcess;
import com.ruoyi.approve.service.IApproveNodeService;
import com.ruoyi.approve.service.IApproveProcessService;
import com.ruoyi.approve.utils.DailyRedisCounter;
import com.ruoyi.approve.vo.ApproveGetAndUpdateVo;
import com.ruoyi.approve.vo.ApproveNodeVO;
import com.ruoyi.approve.vo.ApproveProcessVO;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.utils.SecurityUtils;
@@ -22,12 +30,10 @@
import com.ruoyi.project.system.mapper.SysDeptMapper;
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.sales.mapper.CommonFileMapper;
import com.ruoyi.sales.mapper.SalesLedgerMapper;
import com.ruoyi.sales.pojo.CommonFile;
import lombok.AllArgsConstructor;
import com.ruoyi.sales.pojo.SalesLedger;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
@@ -41,7 +47,9 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@@ -49,174 +57,270 @@
public class ApproveProcessServiceImpl extends ServiceImpl<ApproveProcessMapper, ApproveProcess> implements IApproveProcessService {
    private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd");
    private final StringRedisTemplate redisTemplate;
    private final DailyRedisCounter dailyRedisCounter;
    private final SysDeptMapper sysDeptMapper;
    private final IApproveNodeService approveNodeService;
    private final SysUserMapper sysUserMapper;
    private final ApproveProcessMapper approveProcessMapper;
    private final ApproveNodeMapper approveNodeMapper;
    private final TempFileServiceImpl tempFileService;
    private final CommonFileMapper commonFileMapper;
    private final SalesLedgerMapper salesLedgerMapper;
    private final ApproveFlowDefinitionMapper flowDefinitionMapper;
    private final ApproveFlowNodeDefinitionMapper flowNodeDefinitionMapper;
    private final ObjectMapper objectMapper;
    @Override
    public void addApprove(ApproveProcessVO approveProcessVO) throws  Exception {
        SysUser sysUser = sysUserMapper.selectUserById(approveProcessVO.getApproveUser());
        SysDept sysDept = sysDeptMapper.selectDeptById(approveProcessVO.getApproveDeptId());
        String[] split = approveProcessVO.getApproveUserIds().split(",");
        List<Long> longList = Arrays.stream(split)
                .map(Long::valueOf)  // å°†æ¯ä¸ª String è½¬æ¢ä¸º Long
                .collect(Collectors.toList());
        List<SysUser> sysUsers = sysUserMapper.selectUserByIds(longList);
        if(CollectionUtils.isEmpty(sysUsers)) throw new RuntimeException("审核用户不存在");
        if(sysDept == null) throw new RuntimeException("部门不存在");
        if(sysUser == null) throw new RuntimeException("申请人不存在");
        String today = LocalDate.now().format(DATE_FORMAT);
        Long approveId = dailyRedisCounter.incrementAndGetByDb();
        String formattedCount = String.format("%03d", approveId);
        //流程 ID
        String approveID = today + formattedCount;
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        ApproveProcess approveProcess = new ApproveProcess();
        approveProcess.setApproveId(approveID);
        approveProcess.setApproveUser(approveProcessVO.getApproveUser());
        approveProcess.setApproveUserName(sysUser.getNickName());
        approveProcess.setApproveDeptId(approveProcessVO.getApproveDeptId());
        approveProcess.setApproveDeptName(sysDept.getDeptName());
        approveProcess.setApproveUserNames(sysUsers.stream().map(SysUser::getNickName).collect(Collectors.joining(",")));
        approveProcess.setApproveTime(StringUtils.isEmpty(approveProcessVO.getApproveTime()) ? null : dateFormat.parse(approveProcessVO.getApproveTime()));
        approveProcess.setApproveReason(approveProcessVO.getApproveReason());
        approveProcess.setApproveOverTime(null);
        approveProcess.setApproveStatus(0);
        approveProcess.setApproveDelete(0);
        approveProcess.setApproveType(approveProcessVO.getApproveType());
        approveProcess.setCreateTime(LocalDateTime.now());
        approveProcess.setTenantId(approveProcessVO.getApproveDeptId());
        approveProcess.setApproveUserIds(approveProcessVO.getApproveUserIds());
        approveProcess.setApproveUserCurrentId(longList.get(0));
        approveProcess.setApproveUserCurrentName(sysUsers
                .stream()
                .filter(SysUser -> SysUser.getUserId().equals(longList.get(0)))
                .collect(Collectors.toList())
                .get(0)
                .getNickName());
        // è®¾ç½®çŠ¶æ€ä¸ºé‡æ–°æäº¤
        if(approveProcessVO.getId() != null){
            ApproveProcess approveProcess1 = approveProcessMapper.selectById(approveProcessVO.getId());
            approveProcess1.setApproveStatus(4);
            approveProcessMapper.updateById(approveProcess1);
    public void addApprove(ApproveProcessVO request) throws Exception {
        if (request == null || request.getApproveType() == null) throw new RuntimeException("审批类型不能为空");
        ApproveType type = ApproveType.fromCode(request.getApproveType());
        Long tenantId = tenantId();
        SysUser applicant = SecurityUtils.getLoginUser().getUser();
        Long deptId = resolveDeptId(request.getApproveDeptId());
        SysDept dept = sysDeptMapper.selectDeptById(deptId);
        if (dept == null) throw new RuntimeException("申请部门不存在");
        List<ApproveNodeVO> nodes = resolveNodes(request, tenantId);
        List<SysUser> approvers = validateApprovers(nodes, tenantId);
        prepareBusinessData(request, type, tenantId);
        String approveCode = LocalDate.now().format(DATE_FORMAT)
                + String.format("%03d", dailyRedisCounter.incrementAndGetByDb());
        ApproveProcess process = new ApproveProcess();
        process.setApproveId(approveCode);
        process.setApproveUser(applicant.getUserId());
        process.setApproveUserName(applicant.getNickName());
        process.setApproveDeptId(deptId);
        process.setApproveDeptName(dept.getDeptName());
        process.setApproveUserIds(nodes.stream().map(item -> String.valueOf(item.getUserId())).collect(Collectors.joining(",")));
        process.setApproveUserNames(approvers.stream().map(SysUser::getNickName).collect(Collectors.joining(",")));
        process.setApproveTime(parseDate(request.getApproveTime()));
        process.setApproveReason(request.getApproveReason());
        process.setApproveStatus(0);
        process.setApproveDelete(0);
        process.setApproveType(type.getCode());
        process.setTenantId(tenantId);
        process.setBusinessId(request.getBusinessId());
        process.setBusinessNo(request.getBusinessNo());
        process.setBusinessTitle(request.getBusinessTitle());
        process.setAmount(request.getAmount());
        process.setBusinessData(request.getBusinessData());
        process.setFlowDefinitionId(request.getFlowDefinitionId());
        process.setCreateTime(LocalDateTime.now());
        process.setUpdateTime(LocalDateTime.now());
        approveProcessMapper.insert(process);
        approveNodeService.initApproveNodes(nodes, approveCode, tenantId);
        tempFileService.migrateTempFilesToFormal(process.getId(), request.getTempFileIds(), FileNameType.ApproveProcess.getValue());
    }
    private void prepareBusinessData(ApproveProcessVO request, ApproveType type, Long tenantId) throws JsonProcessingException {
        if (type == ApproveType.SALES_CONTRACT) {
            if (request.getBusinessId() == null) throw new RuntimeException("请选择销售合同");
            SalesLedger ledger = salesLedgerMapper.selectById(request.getBusinessId());
            if (ledger == null || !tenantId.equals(ledger.getTenantId())) throw new RuntimeException("销售合同不存在或不属于当前租户");
            Long duplicate = approveProcessMapper.selectCount(new LambdaQueryWrapper<ApproveProcess>()
                    .eq(ApproveProcess::getTenantId, tenantId)
                    .eq(ApproveProcess::getApproveType, ApproveType.SALES_CONTRACT.getCode())
                    .eq(ApproveProcess::getBusinessId, ledger.getId())
                    .in(ApproveProcess::getApproveStatus, Arrays.asList(0, 1))
                    .eq(ApproveProcess::getApproveDelete, 0));
            if (duplicate != null && duplicate > 0) throw new RuntimeException("该销售合同已有待审流程");
            Map<String, Object> snapshot = new LinkedHashMap<>();
            snapshot.put("salesLedgerId", ledger.getId());
            snapshot.put("salesContractNo", ledger.getSalesContractNo());
            snapshot.put("customerContractNo", ledger.getCustomerContractNo());
            snapshot.put("customerName", ledger.getCustomerName());
            snapshot.put("projectName", ledger.getProjectName());
            snapshot.put("salesman", ledger.getSalesman());
            snapshot.put("contractAmount", ledger.getContractAmount());
            snapshot.put("executionDate", ledger.getExecutionDate());
            snapshot.put("paymentMethod", ledger.getPaymentMethod());
            snapshot.put("remarks", ledger.getRemarks());
            request.setBusinessNo(ledger.getSalesContractNo());
            request.setBusinessTitle(StringUtils.hasText(request.getBusinessTitle())
                    ? request.getBusinessTitle() : "销售合同审批-" + ledger.getSalesContractNo());
            request.setAmount(ledger.getContractAmount());
            request.setBusinessData(objectMapper.writeValueAsString(snapshot));
        } else if (type == ApproveType.PURCHASE_PLAN || type == ApproveType.FREE_COLLABORATION) {
            if (!StringUtils.hasText(request.getBusinessTitle())) throw new RuntimeException("审批标题不能为空");
            if (type == ApproveType.PURCHASE_PLAN && request.getAmount() == null) throw new RuntimeException("采购预算金额不能为空");
            if (!StringUtils.hasText(request.getBusinessNo())) {
                String prefix = type == ApproveType.PURCHASE_PLAN ? "CG" : "XT";
                request.setBusinessNo(prefix + LocalDate.now().format(DATE_FORMAT)
                        + String.format("%03d", dailyRedisCounter.incrementAndGetByDb()));
            }
        }
        save(approveProcess);
        //初始化审批节点
        approveNodeService.initApproveNodes(approveProcessVO.getApproveUserIds(),approveID,approveProcessVO.getApproveDeptId());
        // é™„件绑定
        tempFileService.migrateTempFilesToFormal(approveProcess.getId(), approveProcessVO.getTempFileIds(), FileNameType.ApproveProcess.getValue());
    }
    private List<ApproveNodeVO> resolveNodes(ApproveProcessVO request, Long tenantId) {
        if (!CollectionUtils.isEmpty(request.getNodes())) return request.getNodes();
        if (request.getFlowDefinitionId() != null) {
            ApproveFlowDefinition definition = flowDefinitionMapper.selectOne(new LambdaQueryWrapper<ApproveFlowDefinition>()
                    .eq(ApproveFlowDefinition::getId, request.getFlowDefinitionId())
                    .eq(ApproveFlowDefinition::getTenantId, tenantId)
                    .eq(ApproveFlowDefinition::getApproveType, request.getApproveType())
                    .eq(ApproveFlowDefinition::getEnabled, 1)
                    .eq(ApproveFlowDefinition::getDeleteFlag, 0));
            if (definition == null) throw new RuntimeException("审批流程定义不存在或已停用");
            List<ApproveFlowNodeDefinition> definitions = flowNodeDefinitionMapper.selectList(
                    new LambdaQueryWrapper<ApproveFlowNodeDefinition>()
                            .eq(ApproveFlowNodeDefinition::getFlowDefinitionId, definition.getId())
                            .eq(ApproveFlowNodeDefinition::getTenantId, tenantId)
                            .eq(ApproveFlowNodeDefinition::getDeleteFlag, 0)
                            .orderByAsc(ApproveFlowNodeDefinition::getNodeOrder));
            List<ApproveNodeVO> result = new ArrayList<>();
            for (ApproveFlowNodeDefinition item : definitions) {
                if (!"USER".equals(item.getApproverType()) || !StringUtils.hasText(item.getApproverValue())) {
                    throw new RuntimeException("当前预设流程包含尚未指定的审批人");
                }
                ApproveNodeVO node = new ApproveNodeVO();
                node.setUserId(Long.valueOf(item.getApproverValue().split(",")[0].trim()));
                node.setNodeName(item.getNodeName());
                node.setConditionField(item.getConditionField());
                node.setConditionOperator(item.getConditionOperator());
                node.setConditionValue(item.getConditionValue());
                result.add(node);
            }
            if (!result.isEmpty()) return result;
        }
        if (!StringUtils.hasText(request.getApproveUserIds())) throw new RuntimeException("请选择审批人");
        List<ApproveNodeVO> result = new ArrayList<>();
        for (String id : request.getApproveUserIds().split(",")) {
            ApproveNodeVO node = new ApproveNodeVO();
            node.setUserId(Long.valueOf(id.trim()));
            node.setConditionOperator("ALWAYS");
            result.add(node);
        }
        return result;
    }
    private List<SysUser> validateApprovers(List<ApproveNodeVO> nodes, Long tenantId) {
        List<SysUser> users = new ArrayList<>();
        for (ApproveNodeVO node : nodes) {
            SysUser user = sysUserMapper.selectUserById(node.getUserId());
            if (user == null || !tenantId.equals(user.getTenantId())) throw new RuntimeException("审批人不存在或不属于当前租户");
            users.add(user);
        }
        return users;
    }
    @Override
    public List<SysDept> selectDeptListByDeptIds(Long[] deptIds) {
        List<SysDept> sysDeptList =new ArrayList<SysDept>();
        List<SysDept> departments = new ArrayList<>();
        if (deptIds == null) return departments;
        for (Long deptId : deptIds) {
            SysDept sysDept = sysDeptMapper.selectDeptById(deptId);
            sysDeptList.add(sysDept);
            SysDept dept = sysDeptMapper.selectDeptById(deptId);
            if (dept != null) departments.add(dept);
        }
        return sysDeptList;
        return departments;
    }
    @Override
    public IPage<ApproveProcess> listAll(Page page,ApproveProcess approveProcess) {
        IPage<ApproveProcess> approveProcessIPage = approveProcessMapper.listPage(page,approveProcess);
        List<ApproveProcess> records = approveProcessIPage.getRecords();
        for (ApproveProcess record : records) {
            List<CommonFile> commonFiles = commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
                    .eq(CommonFile::getCommonId, record.getId())
                    .eq(CommonFile::getType, FileNameType.ApproveProcess.getValue()));
            record.setCommonFileList(commonFiles);
        }
        return approveProcessIPage;
    public IPage<ApproveProcess> listAll(Page page, ApproveProcess request) {
        request.setTenantId(tenantId());
        IPage<ApproveProcess> result = approveProcessMapper.listPage(page, request);
        result.getRecords().forEach(record -> record.setCommonFileList(commonFileMapper.selectList(
                new LambdaQueryWrapper<CommonFile>()
                        .eq(CommonFile::getCommonId, record.getId())
                        .eq(CommonFile::getType, FileNameType.ApproveProcess.getValue()))));
        return result;
    }
    @Override
    public void delApprove(Long[] ids) {
        for (Long id : ids) {
            UpdateWrapper<ApproveProcess> queryWrapper = new UpdateWrapper<>();
            queryWrapper.lambda().set(ApproveProcess::getApproveDelete, 1)
                    .eq(ApproveProcess::getApproveId, id);
            update(queryWrapper);
            // åˆ é™¤å…³è”的审批节点
            approveNodeService.delApproveNodeByApproveId(id);
    public void delApprove(String[] ids) {
        if (ids == null) return;
        for (String code : ids) {
            ApproveProcess process = findOwnedByCode(code);
            if (!SecurityUtils.getUserId().equals(process.getApproveUser())) throw new RuntimeException("只能删除本人申请");
            if (!(Integer.valueOf(0).equals(process.getApproveStatus()) || Integer.valueOf(3).equals(process.getApproveStatus()))) {
                throw new RuntimeException("审核中的流程不能删除");
            }
            UpdateWrapper<ApproveProcess> wrapper = new UpdateWrapper<>();
            wrapper.lambda().set(ApproveProcess::getApproveDelete, 1)
                    .set(ApproveProcess::getUpdateTime, LocalDateTime.now())
                    .eq(ApproveProcess::getApproveId, code)
                    .eq(ApproveProcess::getTenantId, tenantId());
            update(wrapper);
            approveNodeService.delApproveNodeByApproveId(code);
        }
    }
    @Override
    public ApproveProcess getApproveById(String id) {
        LambdaQueryWrapper<ApproveProcess> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ApproveProcess::getApproveId, id);
        queryWrapper.eq(ApproveProcess::getApproveDelete, 0);
        queryWrapper.eq(ApproveProcess::getTenantId, SecurityUtils.getLoginUser().getTenantId());
        queryWrapper.last("limit 1");
        ApproveProcess one = getOne(queryWrapper);
        one.setCommonFileList(commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
                .eq(CommonFile::getCommonId, one.getId())
        ApproveProcess process = findOwnedByCode(id);
        process.setCommonFileList(commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
                .eq(CommonFile::getCommonId, process.getId())
                .eq(CommonFile::getType, FileNameType.ApproveProcess.getValue())));
        return one;
        return process;
    }
    private final ApproveNodeMapper approveNodeMapper;
    @Override
    public void updateByApproveId(ApproveGetAndUpdateVo approveGetAndUpdateVo) throws IOException {
        ApproveProcess approve = approveProcessMapper.selectById(approveGetAndUpdateVo.getId());
        approve.setApproveUserIds(approveGetAndUpdateVo.getApproveUserIds());
        approve.setApproveReason(approveGetAndUpdateVo.getApproveReason());
        SysUser sysUser = sysUserMapper.selectUserById(approveGetAndUpdateVo.getApproveUser());
        String[] split = approveGetAndUpdateVo.getApproveUserIds().split(",");
        if(split.length == 0){
            throw new RuntimeException("请选择审批人");
    public void updateByApproveId(ApproveGetAndUpdateVo request) throws IOException {
        ApproveProcess process = approveProcessMapper.selectOne(new LambdaQueryWrapper<ApproveProcess>()
                .eq(ApproveProcess::getId, request.getId())
                .eq(ApproveProcess::getTenantId, tenantId())
                .eq(ApproveProcess::getApproveDelete, 0));
        if (process == null) throw new RuntimeException("审批不存在");
        if (!SecurityUtils.getUserId().equals(process.getApproveUser())) throw new RuntimeException("只能编辑本人申请");
        if (!(Integer.valueOf(0).equals(process.getApproveStatus()) || Integer.valueOf(3).equals(process.getApproveStatus()))) {
            throw new RuntimeException("当前审批状态不允许编辑");
        }
        List<SysUser> sysUsers = sysUserMapper.selectUserByIds(Arrays.asList(split).stream().map(Long::parseLong).collect(Collectors.toList()));
        if(CollectionUtils.isEmpty(sysUsers)) throw new RuntimeException("请选择审批人");
        if(sysUser == null) throw new RuntimeException("申请人不存在");
        approve.setApproveUserName(sysUser.getNickName());
        approve.setApproveUser(sysUser.getUserId());
        //审核中不可以编辑审核人
        if(approve.getApproveStatus() != 1){
            approve.setApproveUserCurrentId(Long.parseLong(split[0]));
            approve.setApproveUserCurrentName(sysUsers.stream().filter(user -> user.getUserId().equals(Long.parseLong(split[0]))).collect(Collectors.toList()).get(0).getNickName());
        }
        updateById(approve);
        //修改审批人
        // å…ˆåˆ é™¤ åŽæ–°å¢ž
        LambdaQueryWrapper<ApproveNode> approveNodeLambdaQueryWrapper = new LambdaQueryWrapper<>();
        approveNodeLambdaQueryWrapper.eq(ApproveNode::getApproveProcessId, approve.getApproveId())
                .eq(ApproveNode::getDeleteFlag, 0)
                .eq(ApproveNode::getTenantId, SecurityUtils.getLoginUser().getTenantId())
                .orderByAsc(ApproveNode::getApproveNodeOrder);
        approveNodeMapper.delete(approveNodeLambdaQueryWrapper);
        approveNodeService.initApproveNodes(approveGetAndUpdateVo.getApproveUserIds(),approve.getApproveId(),approve.getTenantId());
        process.setApproveReason(request.getApproveReason());
        process.setBusinessTitle(request.getBusinessTitle());
        process.setAmount(request.getAmount());
        process.setBusinessData(request.getBusinessData());
        process.setUpdateTime(LocalDateTime.now());
        process.setApproveStatus(0);
        process.setApproveOverTime(null);
        approveProcessMapper.updateById(process);
//        int i = 0;
//        for (ApproveNode approveNode : list) {
//            int finalI = i;
//            if(i >= split.length){
//                approveNode.setDeleteFlag(1);
//            }else{
//                List<SysUser> collect = sysUsers.stream().filter(user -> user.getUserId().equals(Long.parseLong(split[finalI]))).collect(Collectors.toList());
//                if(CollectionUtils.isEmpty(collect)){
//                    throw new RuntimeException("请选择正确的审批人");
//                }
//                approveNode.setApproveNodeUserId(collect.get(0).getUserId());
//                approveNode.setApproveNodeUser(collect.get(0).getNickName());
//            }
//            approveNodeMapper.updateById(approveNode);
//            i++;
//        }
        tempFileService.migrateTempFilesToFormal(approve.getId(), approveGetAndUpdateVo.getTempFileIds(), FileNameType.ApproveProcess.getValue());
        List<ApproveNodeVO> nodes = request.getNodes();
        if (CollectionUtils.isEmpty(nodes) && StringUtils.hasText(request.getApproveUserIds())) {
            nodes = new ArrayList<>();
            for (String id : request.getApproveUserIds().split(",")) {
                ApproveNodeVO node = new ApproveNodeVO();
                node.setUserId(Long.valueOf(id.trim()));
                node.setConditionOperator("ALWAYS");
                nodes.add(node);
            }
        }
        if (!CollectionUtils.isEmpty(nodes)) {
            validateApprovers(nodes, tenantId());
            UpdateWrapper<ApproveNode> oldNodes = new UpdateWrapper<>();
            oldNodes.lambda().set(ApproveNode::getDeleteFlag, 1)
                    .set(ApproveNode::getIsCurrent, 0)
                    .eq(ApproveNode::getApproveProcessId, process.getApproveId())
                    .eq(ApproveNode::getTenantId, tenantId());
            approveNodeMapper.update(null, oldNodes);
            approveNodeService.initApproveNodes(nodes, process.getApproveId(), tenantId());
        }
        tempFileService.migrateTempFilesToFormal(process.getId(), request.getTempFileIds(), FileNameType.ApproveProcess.getValue());
    }
    private ApproveProcess findOwnedByCode(String code) {
        ApproveProcess process = approveProcessMapper.selectOne(new LambdaQueryWrapper<ApproveProcess>()
                .eq(ApproveProcess::getApproveId, code)
                .eq(ApproveProcess::getApproveDelete, 0)
                .eq(ApproveProcess::getTenantId, tenantId())
                .last("limit 1"));
        if (process == null) throw new RuntimeException("审批不存在");
        return process;
    }
    private Long resolveDeptId(Long requested) {
        Long[] allowed = SecurityUtils.getDeptId();
        if (requested == null) return SecurityUtils.getLoginUser().getCurrentDeptId();
        if (allowed != null && Arrays.asList(allowed).contains(requested)) return requested;
        Long current = SecurityUtils.getLoginUser().getCurrentDeptId();
        if (requested.equals(current)) return requested;
        throw new RuntimeException("无权使用该申请部门");
    }
    private Date parseDate(String value) throws ParseException {
        return StringUtils.hasText(value) ? new SimpleDateFormat("yyyy-MM-dd").parse(value) : new Date();
    }
    private Long tenantId() {
        return SecurityUtils.getLoginUser().getTenantId();
    }
}
src/main/java/com/ruoyi/approve/vo/ApproveFlowDefinitionVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
package com.ruoyi.approve.vo;
import com.ruoyi.approve.pojo.ApproveFlowDefinition;
import com.ruoyi.approve.pojo.ApproveFlowNodeDefinition;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
public class ApproveFlowDefinitionVO {
    @ApiModelProperty(value = "流程定义")
    private ApproveFlowDefinition definition;
    @ApiModelProperty(value = "流程节点定义列表")
    private List<ApproveFlowNodeDefinition> nodes;
}
src/main/java/com/ruoyi/approve/vo/ApproveGetAndUpdateVo.java
@@ -1,31 +1,61 @@
package com.ruoyi.approve.vo;
import com.ruoyi.sales.pojo.CommonFile;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.math.BigDecimal;
import java.util.List;
@Data
public class ApproveGetAndUpdateVo {
    @ApiModelProperty(value = "临时附件ID列表")
    private List<String> tempFileIds;
    //审批id
    @ApiModelProperty(value = "审批流程编号")
    @NotBlank(message = "流程编号不能为空")
    private String id;
    //申请事由
    @ApiModelProperty(value = "申请事由")
    @NotBlank(message = "申请事由不能为空")
    private String approveReason;
    @ApiModelProperty(value = "审批用户ID列表,多个以逗号分隔")
    private String approveUserIds;
    @ApiModelProperty(value = "申请用户ID")
    private Long approveUser;
    @ApiModelProperty(value = "申请日期,格式:yyyy-MM-dd")
    private String approveTime;
    @ApiModelProperty(value = "审批状态:0待审核,1审核中,2审核完成,3审核未通过,4已重新提交")
    private Integer approveStatus;
    /**
     * å®¡æ‰¹ç±»åž‹
     */
    @ApiModelProperty(value = "审批类型:1自由协同,2请假,3销售,4报销,5采购,6报价,7出库")
    private Integer approveType;
    @ApiModelProperty(value = "关联业务主键")
    private Long businessId;
    @ApiModelProperty(value = "业务编号")
    private String businessNo;
    @ApiModelProperty(value = "审批业务标题")
    private String businessTitle;
    @ApiModelProperty(value = "业务金额")
    private BigDecimal amount;
    @ApiModelProperty(value = "业务数据JSON快照")
    private String businessData;
    @ApiModelProperty(value = "流程定义ID")
    private Long flowDefinitionId;
    @ApiModelProperty(value = "审批节点列表")
    private List<ApproveNodeVO> nodes;
}
src/main/java/com/ruoyi/approve/vo/ApproveNodeVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
package com.ruoyi.approve.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Data
public class ApproveNodeVO {
    @ApiModelProperty(value = "审批用户ID")
    @NotNull(message = "审批人不能为空")
    private Long userId;
    @ApiModelProperty(value = "审批节点名称")
    private String nodeName;
    @ApiModelProperty(value = "条件字段")
    private String conditionField;
    @ApiModelProperty(value = "条件操作符")
    private String conditionOperator;
    @ApiModelProperty(value = "条件值")
    private String conditionValue;
}
src/main/java/com/ruoyi/approve/vo/ApproveProcessVO.java
@@ -1,33 +1,61 @@
package com.ruoyi.approve.vo;
import com.ruoyi.sales.pojo.CommonFile;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.math.BigDecimal;
import java.util.List;
@Data
public class ApproveProcessVO {
    @ApiModelProperty(value = "临时附件ID列表")
    private List<String> tempFileIds;
    @ApiModelProperty(value = "审批流程主键")
    private Long id;
    @ApiModelProperty(value = "申请部门ID")
    private Long approveDeptId;
    @ApiModelProperty(value = "申请日期,格式:yyyy-MM-dd")
    private String approveTime;
    // ç”³è¯·äºº
    @ApiModelProperty(value = "申请用户ID")
    private Long approveUser;
    // å®¡æ‰¹äºº
    @ApiModelProperty(value = "审批用户ID列表,多个以逗号分隔")
    private String approveUserIds;
    @ApiModelProperty(value = "申请事由")
    private String approveReason;
    /**
     * å®¡æ‰¹ç±»åž‹
     */
    @ApiModelProperty(value = "审批类型:1自由协同,2请假,3销售,4报销,5采购,6报价,7出库")
    private Integer approveType;
    @ApiModelProperty(value = "关联业务主键")
    private Long businessId;
    @ApiModelProperty(value = "业务编号")
    private String businessNo;
    @ApiModelProperty(value = "审批业务标题")
    @NotBlank(message = "审批标题不能为空")
    private String businessTitle;
    @ApiModelProperty(value = "业务金额")
    private BigDecimal amount;
    @ApiModelProperty(value = "业务数据JSON快照")
    private String businessData;
    @ApiModelProperty(value = "流程定义ID")
    private Long flowDefinitionId;
    @ApiModelProperty(value = "自定义审批节点列表")
    private List<ApproveNodeVO> nodes;
}
src/main/resources/mapper/approve/ApproveLogMapper.xml
@@ -7,15 +7,19 @@
    <resultMap id="BaseResultMap" type="com.ruoyi.approve.pojo.ApproveLog">
            <id property="id" column="id" />
            <result property="approveId" column="approve_id" />
            <result property="approveProcessId" column="approve_process_id" />
            <result property="approveNodeId" column="approve_node_id" />
            <result property="approveNodeOrder" column="approve_node_order" />
            <result property="approveUser" column="approve_user" />
            <result property="approveUserName" column="approve_user_name" />
            <result property="approveTime" column="approve_time" />
            <result property="approveStatus" column="approve_status" />
            <result property="approveRemark" column="approve_remark" />
            <result property="tenantId" column="tenant_id" />
    </resultMap>
    <sql id="Base_Column_List">
        id,approve_id,approve_node_order,approve_user,approve_time,approve_status,
        approve_remark
        id,approve_id,approve_process_id,approve_node_id,approve_node_order,approve_user,
        approve_user_name,approve_time,approve_status,approve_remark,tenant_id
    </sql>
</mapper>
src/main/resources/mapper/approve/ApproveNodeMapper.xml
@@ -9,16 +9,24 @@
            <result property="approveProcessId" column="approve_process_id" />
            <result property="approveNodeOrder" column="approve_node_order" />
            <result property="approveNodeUser" column="approve_node_user" />
            <result property="approveNodeUserId" column="approve_node_user_id" />
            <result property="approveNodeTime" column="approve_node_time" />
            <result property="approveNodeStatus" column="approve_node_status" />
            <result property="tenantId" column="tenant_id" />
            <result property="deleteFlag" column="delete_flag" />
            <result property="approveNodeReason" column="approve_node_reason" />
            <result property="approveNodeRemark" column="approve_node_remark" />
            <result property="approveNodeName" column="approve_node_name" />
            <result property="conditionField" column="condition_field" />
            <result property="conditionOperator" column="condition_operator" />
            <result property="conditionValue" column="condition_value" />
            <result property="isCurrent" column="is_current" />
    </resultMap>
    <sql id="Base_Column_List">
        id,approve_process_id,approve_node_order,approve_node_user,approve_node_time,approve_node_status,
        tenant_id,delete_flag,approve_node_reason,approve_node_remark
        tenant_id,delete_flag,approve_node_reason,approve_node_remark,approve_node_user_id,
        approve_node_name,condition_field,condition_operator,condition_value,is_current,
        create_user,create_time,update_user,update_time
    </sql>
</mapper>
src/main/resources/mapper/approve/ApproveProcessMapper.xml
@@ -20,12 +20,19 @@
            <result property="tenantId" column="tenant_id" />
            <result property="approveType" column="approve_type" />
            <result property="approveRemark" column="approve_remark" />
            <result property="businessId" column="business_id" />
            <result property="businessNo" column="business_no" />
            <result property="businessTitle" column="business_title" />
            <result property="amount" column="amount" />
            <result property="businessData" column="business_data" />
            <result property="flowDefinitionId" column="flow_definition_id" />
    </resultMap>
    <sql id="Base_Column_List">
        id,approve_id,approve_user,approve_dept_id,approve_dept_name,approve_user_ids,
        approve_user_names,approve_reason,approve_time,approve_over_time,approve_status,
        approve_delete,tenant_id,approve_type,approve_remark
        approve_delete,tenant_id,approve_type,approve_remark,business_id,business_no,
        business_title,amount,business_data,flow_definition_id,create_time,update_time
    </sql>
    <select id="listPage" resultType="com.ruoyi.approve.pojo.ApproveProcess">
        select * from approve_process where approve_delete = 0
@@ -38,5 +45,13 @@
        <if test="req.approveType != null ">
            and approve_type = #{req.approveType}
        </if>
        <if test="req.tenantId != null">
            and tenant_id = #{req.tenantId}
        </if>
        <if test="req.businessNo != null and req.businessNo != ''">
            and (business_no like concat('%',#{req.businessNo},'%')
                 or business_title like concat('%',#{req.businessNo},'%'))
        </if>
        order by create_time desc, id desc
    </select>
</mapper>