9 小时以前 44683931d3ca09cf2a062b6ef6cffb4624a18c4d
feat(approve): 支持审批流程多选部门功能

- 在 ApproveProcess 和 ApproveGetAndUpdateVo 中添加 approveDeptIdArray 和 approveDeptNameArray 字段
- 创建 approve_process_dept 关联表存储审批流程与多部门关系
- 实现多部门审批流程的新增、查询、更新和列表展示功能
- 添加部门参数验证和重复数据处理逻辑
- 更新采购和销售台账服务以支持多部门审批
- 添加多选部门的技术文档和数据库迁移脚本
已添加2个文件
已修改6个文件
216 ■■■■■ 文件已修改
doc/approve_process_multi_department.sql 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/approve_process_multi_department.md 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/pojo/ApproveProcess.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/vo/ApproveGetAndUpdateVo.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/vo/ApproveProcessVO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/approve_process_multi_department.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
-- å®¡æ‰¹æµç¨‹å¤šéƒ¨é—¨å…³è”
create table if not exists approve_process_dept
(
    id                 bigint auto_increment primary key,
    approve_process_id bigint       not null comment '审批流程主键ID',
    approve_dept_id    bigint       not null comment '部门ID',
    approve_dept_name  varchar(255) not null comment '部门名称快照',
    dept_order         int          not null default 1 comment '部门顺序',
    delete_flag        int          not null default 0 comment '逻辑删除标记',
    create_time        datetime     null comment '创建时间',
    unique key uk_approve_process_dept (approve_process_id, approve_dept_id),
    key idx_approve_process_id (approve_process_id),
    key idx_approve_dept_id (approve_dept_id)
) comment '审批流程部门关联表';
-- åŽ†å²å•éƒ¨é—¨å®¡æ‰¹æ•°æ®è¿ç§»ä¸ºå…³è”è®°å½•
insert into approve_process_dept (approve_process_id, approve_dept_id, approve_dept_name, dept_order, delete_flag, create_time)
select p.id, p.approve_dept_id, p.approve_dept_name, 1, 0, now()
from approve_process p
where p.approve_dept_id is not null
  and not exists (
      select 1 from approve_process_dept d
      where d.approve_process_id = p.id
        and d.approve_dept_id = p.approve_dept_id
  );
docs/approve_process_multi_department.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,98 @@
# å®¡æ‰¹æµç¨‹å¤šé€‰éƒ¨é—¨
## æ¶‰åŠé¡µé¢
- å®¡æ‰¹æµç¨‹æ–°å¢žé¡µé¢
- å®¡æ‰¹æµç¨‹è¯¦æƒ…页面
- å®¡æ‰¹æµç¨‹ç¼–辑页面
- å®¡æ‰¹æµç¨‹åˆ—表页面
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/approveProcess/add` | æ–°å¢žå®¡æ‰¹æµç¨‹ï¼Œæ”¯æŒå¤šä¸ªå®¡æ‰¹éƒ¨é—¨ |
| GET | `/approveProcess/get` | æŸ¥è¯¢å®¡æ‰¹è¯¦æƒ…,返回部门数组 |
| GET | `/approveProcess/list` | æŸ¥è¯¢å®¡æ‰¹åˆ—表,返回部门数组 |
| POST | `/approveProcess/update` | ç¼–辑审批流程部门和审批人 |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| `approveDeptIdArray` | `Array<Long>` | æ˜¯ | å®¡æ‰¹éƒ¨é—¨ ID æ•°ç»„,按数组顺序保存;数组优先于旧的 `approveDeptId` |
| `approveDeptId` | `Long` | å¦ | æ—§ç‰ˆå•部门参数,未传数组时兼容使用 |
| `approveDeptName` | `String` | å¦ | éƒ¨é—¨åç§°ç”±åŽç«¯æ ¹æ®éƒ¨é—¨ ID æŸ¥è¯¢ï¼Œå‰ç«¯ä¸åº”作为部门数据源 |
| `approveUser` | `Long` | æ˜¯ | ç”³è¯·äºº ID |
| `approveUserIds` | `String` | æ˜¯ | å®¡æ‰¹äºº ID,多个 ID ä½¿ç”¨é€—号分隔 |
| `approveReason` | `String` | æ˜¯ | å®¡æ‰¹ç†ç”± |
| `approveType` | `Integer` | æ˜¯ | å®¡æ‰¹ç±»åž‹ |
**响应:**
详情和列表中的审批流程对象新增:
```json
{
  "approveDeptId": 208,
  "approveDeptName": "湟水峡农业,其他部门",
  "approveDeptIdArray": [208, 209],
  "approveDeptNameArray": ["湟水峡农业", "其他部门"]
}
```
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. éƒ¨é—¨é€‰æ‹©æŽ§ä»¶
将审批部门选择控件改为多选,并提交 ID æ•°ç»„:
```html
<el-select v-model="form.approveDeptIdArray" multiple clearable>
  <el-option
    v-for="dept in deptOptions"
    :key="dept.deptId"
    :label="dept.deptName"
    :value="dept.deptId"
  />
</el-select>
```
### 2. data æ•°æ®
```js
data() {
  return {
    form: {
      approveDeptIdArray: [],
    },
  }
}
```
### 3. æäº¤ä¸Žå›žæ˜¾
```js
submitForm() {
  if (!this.form.approveDeptIdArray.length) {
    this.$message.error('请选择审批部门')
    return
  }
  addApprove(this.form)
}
loadDetail(id) {
  getApprove(id).then(({ data }) => {
    this.form.approveDeptIdArray = data.approveDeptIdArray || []
  })
}
```
列表展示可优先使用 `approveDeptNameArray`,兼容旧数据时使用 `approveDeptName`。
## æ³¨æ„äº‹é¡¹
- å¤šéƒ¨é—¨æ•°æ®ç”±åŽç«¯å…³è”表保存,前端不要拼接部门 ID æˆ–名称。
- `approveDeptIdArray` ä¸ºç©ºæ—¶åŽç«¯ä¼šæ‹’绝新增;旧版单部门请求仍可兼容。
- å®¡æ ¸ä¸­çš„审批流程不允许修改审批部门。
- å®¡æ‰¹èŠ‚ç‚¹ä»æŒ‰å®¡æ‰¹äººåˆ›å»ºï¼Œå¤šä¸ªéƒ¨é—¨ä¸ä¼šé‡å¤åˆ›å»ºå®¡æ‰¹èŠ‚ç‚¹ã€‚
src/main/java/com/ruoyi/approve/pojo/ApproveProcess.java
@@ -63,6 +63,12 @@
    @Excel(name = "申请部门")
    private String approveDeptName;
    @TableField(exist = false)
    private List<Long> approveDeptIdArray;
    @TableField(exist = false)
    private List<String> approveDeptNameArray;
    /**
     * å®¡æ‰¹ç”¨æˆ·ids
     */
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
@@ -8,9 +8,11 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.approve.mapper.ApproveNodeMapper;
import com.ruoyi.approve.mapper.ApproveProcessDeptMapper;
import com.ruoyi.approve.mapper.ApproveProcessMapper;
import com.ruoyi.approve.pojo.ApproveNode;
import com.ruoyi.approve.pojo.ApproveProcess;
import com.ruoyi.approve.pojo.ApproveProcessDept;
import com.ruoyi.approve.service.IApproveNodeService;
import com.ruoyi.approve.service.IApproveProcessService;
import com.ruoyi.approve.utils.DailyRedisCounter;
@@ -52,7 +54,10 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@Service
@@ -73,6 +78,8 @@
    @Autowired
    private  ApproveProcessMapper approveProcessMapper;
    @Autowired
    private ApproveProcessDeptMapper approveProcessDeptMapper;
    @Autowired
    private  TempFileServiceImpl tempFileService;
    @Autowired
    private  CommonFileMapper commonFileMapper;
@@ -84,7 +91,11 @@
    @Override
    public void addApprove(ApproveProcessVO approveProcessVO) throws Exception {
        SysUser sysUser = sysUserMapper.selectUserById(approveProcessVO.getApproveUser());
        SysDept sysDept = sysDeptMapper.selectDeptById(approveProcessVO.getApproveDeptId());
        List<Long> approveDeptIds = normalizeDeptIds(approveProcessVO.getApproveDeptIdArray(), approveProcessVO.getApproveDeptId());
        List<SysDept> sysDepts = sysDeptMapper.selectBatchIds(approveDeptIds);
        Map<Long, SysDept> deptMap = sysDepts.stream().collect(Collectors.toMap(SysDept::getDeptId, dept -> dept));
        if (deptMap.size() != approveDeptIds.size()) throw new RuntimeException("部门不存在");
        SysDept sysDept = deptMap.get(approveDeptIds.get(0));
        String[] split = approveProcessVO.getApproveUserIds().split(",");
        List<Long> longList = Arrays.stream(split)
                .map(Long::valueOf)  // å°†æ¯ä¸ª String è½¬æ¢ä¸º Long
@@ -104,8 +115,10 @@
        approveProcess.setApproveId(no);
        approveProcess.setApproveUser(approveProcessVO.getApproveUser());
        approveProcess.setApproveUserName(sysUser.getNickName());
        approveProcess.setApproveDeptId(approveProcessVO.getApproveDeptId());
        approveProcess.setApproveDeptName(sysDept.getDeptName());
        approveProcess.setApproveDeptId(approveDeptIds.get(0));
        approveProcess.setApproveDeptName(approveDeptIds.stream().map(deptMap::get).map(SysDept::getDeptName).collect(Collectors.joining(",")));
        approveProcess.setApproveDeptIdArray(approveDeptIds);
        approveProcess.setApproveDeptNameArray(approveDeptIds.stream().map(deptMap::get).map(SysDept::getDeptName).collect(Collectors.toList()));
        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());
@@ -118,7 +131,7 @@
        approveProcess.setApproveDelete(0);
        approveProcess.setApproveType(approveProcessVO.getApproveType());
        approveProcess.setCreateTime(LocalDateTime.now());
        approveProcess.setTenantId(approveProcessVO.getApproveDeptId());
        approveProcess.setTenantId(approveDeptIds.get(0));
        approveProcess.setApproveUserIds(approveProcessVO.getApproveUserIds());
        approveProcess.setApproveUserCurrentId(longList.get(0));
        approveProcess.setApproveTitle(ObjectUtils.isNotEmpty(approveProcessVO.getApproveTitle()) ? approveProcessVO.getApproveTitle() : "");
@@ -135,8 +148,9 @@
            approveProcessMapper.updateById(approveProcess1);
        }
        save(approveProcess);
        saveProcessDepartments(approveProcess.getId(), approveDeptIds, deptMap);
        //初始化审批节点
        approveNodeService.initApproveNodes(approveProcessVO.getApproveUserIds(), no, approveProcessVO.getApproveDeptId());
        approveNodeService.initApproveNodes(approveProcessVO.getApproveUserIds(), no, approveDeptIds.get(0));
        // é™„件绑定
        tempFileService.migrateTempFilesToFormal(approveProcess.getId(), approveProcessVO.getTempFileIds(), FileNameType.ApproveProcess.getValue());
        /*消息通知*/
@@ -173,6 +187,7 @@
    @Override
    public IPage<ApproveProcess> listAll(Page page, ApproveProcess approveProcess) {
        IPage<ApproveProcess> approveProcessIPage = approveProcessMapper.listPage(page, approveProcess);
        fillDepartmentArrays(approveProcessIPage.getRecords());
        List<ApproveProcess> records = approveProcessIPage.getRecords();
        for (ApproveProcess record : records) {
@@ -301,6 +316,7 @@
        one.setCommonFileList(commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
                .eq(CommonFile::getCommonId, one.getId())
                .eq(CommonFile::getType, FileNameType.ApproveProcess.getValue())));
        fillDepartmentArrays(Arrays.asList(one));
        return one;
    }
@@ -379,6 +395,18 @@
            approve.setApproveUserCurrentName(sysUsers.stream().filter(user -> user.getUserId().equals(Long.parseLong(split[0]))).collect(Collectors.toList()).get(0).getNickName());
        }
        if (approveGetAndUpdateVo.getApproveDeptIdArray() != null || approveGetAndUpdateVo.getApproveDeptId() != null) {
            if (approve.getApproveStatus() == 1) throw new RuntimeException("审核中不可以修改审批部门");
            List<Long> deptIds = normalizeDeptIds(approveGetAndUpdateVo.getApproveDeptIdArray(), approveGetAndUpdateVo.getApproveDeptId());
            List<SysDept> departments = sysDeptMapper.selectBatchIds(deptIds);
            Map<Long, SysDept> deptMap = departments.stream().collect(Collectors.toMap(SysDept::getDeptId, dept -> dept));
            if (deptMap.size() != deptIds.size()) throw new RuntimeException("部门不存在");
            approve.setApproveDeptId(deptIds.get(0));
            approve.setApproveDeptName(deptIds.stream().map(deptMap::get).map(SysDept::getDeptName).collect(Collectors.joining(",")));
            approve.setTenantId(deptIds.get(0));
            approveProcessDeptMapper.delete(Wrappers.<ApproveProcessDept>lambdaUpdate().eq(ApproveProcessDept::getApproveProcessId, approve.getId()));
            saveProcessDepartments(approve.getId(), deptIds, deptMap);
        }
        updateById(approve);
        //修改审批人
        // å…ˆåˆ é™¤ åŽæ–°å¢ž
@@ -424,6 +452,45 @@
    }
    private List<Long> normalizeDeptIds(List<Long> deptIds, Long deptId) {
        List<Long> ids = deptIds == null || deptIds.isEmpty() ? (deptId == null ? new ArrayList<>() : Arrays.asList(deptId)) : deptIds;
        if (ids.isEmpty()) throw new RuntimeException("请选择审批部门");
        Set<Long> uniqueIds = new LinkedHashSet<>(ids);
        if (uniqueIds.size() != ids.size() || uniqueIds.stream().anyMatch(id -> id == null || id <= 0)) {
            throw new RuntimeException("审批部门参数不正确");
        }
        return new ArrayList<>(uniqueIds);
    }
    private void saveProcessDepartments(Long processId, List<Long> deptIds, Map<Long, SysDept> deptMap) {
        for (int i = 0; i < deptIds.size(); i++) {
            ApproveProcessDept processDept = new ApproveProcessDept();
            processDept.setApproveProcessId(processId);
            processDept.setApproveDeptId(deptIds.get(i));
            processDept.setApproveDeptName(deptMap.get(deptIds.get(i)).getDeptName());
            processDept.setDeptOrder(i + 1);
            processDept.setDeleteFlag(0);
            processDept.setCreateTime(LocalDateTime.now());
            approveProcessDeptMapper.insert(processDept);
        }
    }
    private void fillDepartmentArrays(List<ApproveProcess> processes) {
        for (ApproveProcess process : processes) {
            List<ApproveProcessDept> departments = approveProcessDeptMapper.selectList(Wrappers.<ApproveProcessDept>lambdaQuery()
                    .eq(ApproveProcessDept::getApproveProcessId, process.getId())
                    .eq(ApproveProcessDept::getDeleteFlag, 0)
                    .orderByAsc(ApproveProcessDept::getDeptOrder));
            if (departments.isEmpty()) {
                process.setApproveDeptIdArray(process.getApproveDeptId() == null ? new ArrayList<>() : Arrays.asList(process.getApproveDeptId()));
                process.setApproveDeptNameArray(process.getApproveDeptName() == null ? new ArrayList<>() : Arrays.asList(process.getApproveDeptName()));
            } else {
                process.setApproveDeptIdArray(departments.stream().map(ApproveProcessDept::getApproveDeptId).collect(Collectors.toList()));
                process.setApproveDeptNameArray(departments.stream().map(ApproveProcessDept::getApproveDeptName).collect(Collectors.toList()));
            }
        }
    }
    //审批类型获取(与前端页面对应)
    private String approveProcessType(Integer approveType) {
        switch (approveType) {
src/main/java/com/ruoyi/approve/vo/ApproveGetAndUpdateVo.java
@@ -27,6 +27,10 @@
    private String approveDeptName;
    private List<Long> approveDeptIdArray;
    private Long approveDeptId;
    private Long approveUser;
    private String approveTime;
src/main/java/com/ruoyi/approve/vo/ApproveProcessVO.java
@@ -29,6 +29,10 @@
     */
    private Long approveDeptId;
    /**
     * å®¡æ‰¹éƒ¨é—¨id列表
     */
    private List<Long> approveDeptIdArray;
    /**
     * å®¡æ‰¹æ—¶é—´
     */
    private String approveTime;
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
@@ -801,6 +801,7 @@
        ApproveProcessVO approveProcessVO = new ApproveProcessVO();
        approveProcessVO.setApproveType(5);
        approveProcessVO.setApproveDeptId(loginUser.getCurrentDeptId());
        approveProcessVO.setApproveDeptIdArray(Collections.singletonList(loginUser.getCurrentDeptId()));
        approveProcessVO.setApproveReason(purchaseLedger.getPurchaseContractNumber());
        approveProcessVO.setApproveUserIds(purchaseLedger.getApproveUserIds());
        approveProcessVO.setApproveUser(loginUser.getUserId());
src/main/java/com/ruoyi/sales/service/impl/SalesLedgerServiceImpl.java
@@ -755,6 +755,7 @@
        ApproveProcessVO approveProcessVO = new ApproveProcessVO();
        approveProcessVO.setApproveType(9);
        approveProcessVO.setApproveDeptId(loginUser.getDeptIds()[0]);
        approveProcessVO.setApproveDeptIdArray(Collections.singletonList(loginUser.getDeptIds()[0]));
        approveProcessVO.setApproveReason(salesLedger.getSalesContractNo());
        approveProcessVO.setApproveUserIds(salesLedger.getApproveUserIds());
        approveProcessVO.setApproveUser(loginUser.getUserId());