2 天以前 f350465db70d6f9db9494483ee0cec7793a9007b
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
@@ -3,110 +3,136 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.approve.bean.vo.ApproveGetAndUpdateVo;
import com.ruoyi.approve.bean.vo.ApproveProcessConfigNodeVo;
import com.ruoyi.approve.bean.vo.ApproveProcessVO;
import com.ruoyi.approve.mapper.ApproveNodeMapper;
import com.ruoyi.approve.mapper.ApproveProcessMapper;
import com.ruoyi.approve.pojo.ApproveNode;
import com.ruoyi.approve.pojo.ApproveProcess;
import com.ruoyi.approve.service.ApproveProcessConfigNodeService;
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.ApproveProcessVO;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.enums.FileNameType;
import com.ruoyi.common.utils.OrderUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.other.service.impl.TempFileServiceImpl;
import com.ruoyi.project.system.domain.SysDept;
import com.ruoyi.project.system.domain.SysNotice;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysDeptMapper;
import com.ruoyi.project.system.mapper.SysUserMapper;
import lombok.AllArgsConstructor;
import com.ruoyi.project.system.service.ISysNoticeService;
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import com.ruoyi.sales.mapper.CommonFileMapper;
import com.ruoyi.sales.mapper.ShippingInfoMapper;
import com.ruoyi.sales.pojo.CommonFile;
import com.ruoyi.sales.pojo.ShippingInfo;
import com.ruoyi.sales.service.impl.CommonFileServiceImpl;
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.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.text.ParseException;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
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 static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    private final SysDeptMapper sysDeptMapper;
    private final ApproveNodeServiceImpl approveNodeService;
    private final IApproveNodeService approveNodeService;
    private final SysUserMapper sysUserMapper;
    private final ApproveProcessMapper approveProcessMapper;
    private final TempFileServiceImpl tempFileService;
    private final CommonFileMapper commonFileMapper;
    private final CommonFileServiceImpl commonFileService;
    private final ISysNoticeService sysNoticeService;
    private final PurchaseLedgerMapper purchaseLedgerMapper;
    private final ShippingInfoMapper shippingInfoMapper;
    private final ApproveNodeMapper approveNodeMapper;
    private final ApproveProcessConfigNodeService approveProcessConfigNodeService;
    @Override
    public void addApprove(ApproveProcessVO approveProcessVO) throws ParseException {
        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
    public Long addApprove(ApproveProcessVO approveProcessVO) throws Exception {
        SysUser sysUser = SecurityUtils.getLoginUser().getUser();
        SysDept sysDept = sysDeptMapper.selectDeptById(SecurityUtils.getLoginUser().getCurrentDeptId());
        List<ApproveProcessConfigNodeVo> list = approveProcessConfigNodeService.listNode(approveProcessVO.getApproveType());
        // 按节点顺序分组(同节点多审批人)
        List<List<ApproveProcessConfigNodeVo>> groups = groupByOrder(list);
        if (groups.isEmpty()) throw new RuntimeException("添加审批节点和人员");
        List<ApproveProcessConfigNodeVo> firstGroup = groups.get(0);
        List<Long> nodeIds = list.stream()
                .map(ApproveProcessConfigNodeVo::getApproverId)
                .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;
        if (nodeIds.isEmpty()) throw new RuntimeException("添加审批节点和人员");
        List<SysUser> sysUsers = sysUserMapper.selectUserByIds(nodeIds);
        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());
        String no = OrderUtils.countAfterServiceTodayByCreateTime(approveProcessMapper, "CGSP");
        approveProcess.setApproveId(no);
        approveProcess.setApproveUser(sysUser.getUserId());
        approveProcess.setApproveUserName(sysUser.getNickName());
        approveProcess.setApproveDeptId(approveProcessVO.getApproveDeptId());
        approveProcess.setApproveDeptId(sysDept.getDeptId());
        approveProcess.setApproveUserIds(nodeIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
        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.setApproveTime(StringUtils.isEmpty(approveProcessVO.getApproveTime()) ? new Date(): dateFormat.parse(approveProcessVO.getApproveTime()));
        approveProcess.setApproveReason(approveProcessVO.getApproveReason());
        approveProcess.setApproveOverTime(null);
        approveProcess.setDeviceRepairId(approveProcessVO.getDeviceRepairId());
        approveProcess.setMaintenancePrice(approveProcessVO.getMaintenancePrice());
        approveProcess.setPrice(approveProcessVO.getPrice());
        approveProcess.setStartDate(approveProcessVO.getStartDate());
        approveProcess.setEndDate(approveProcessVO.getEndDate());
        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());
        setFirstNodeApprovers(approveProcess, firstGroup, sysUsers);
        // 设置状态为重新提交
        if(approveProcessVO.getId() != null){
        if (approveProcessVO.getId() != null) {
            ApproveProcess approveProcess1 = approveProcessMapper.selectById(approveProcessVO.getId());
            approveProcess1.setApproveStatus(4);
            approveProcessMapper.updateById(approveProcess1);
        }
        // 节点结构快照(供编辑重建审批节点时保分组与模式)
        approveProcess.setApproveNodeConfig(buildNodeConfig(list));
        save(approveProcess);
        //初始化审批节点
        approveNodeService.initApproveNodes(approveProcessVO.getApproveUserIds(),approveID,approveProcessVO.getApproveDeptId());
        //初始化审批节点(按配置节点分组,支持同节点多审批人/或签会签)
        approveNodeService.initApproveNodes(list, no, approveProcessVO.getApproveDeptId());
        // 附件绑定
        tempFileService.migrateTempFilesToFormal(approveProcess.getId(), approveProcessVO.getTempFileIds(), FileNameType.ApproveProcess.getValue());
        /*消息通知(通知第一节点全部审批人)*/
        notifyApproveUsers(approveProcess, firstGroupUserIds(approveProcess));
        return approveProcess.getId();
    }
    @Override
    public List<SysDept> selectDeptListByDeptIds(Long[] deptIds) {
        List<SysDept> sysDeptList =new ArrayList<SysDept>();
        List<SysDept> sysDeptList = new ArrayList<SysDept>();
        for (Long deptId : deptIds) {
            SysDept sysDept = sysDeptMapper.selectDeptById(deptId);
            sysDeptList.add(sysDept);
@@ -114,104 +140,424 @@
        return sysDeptList;
    }
    @Override
    public IPage<ApproveProcess> listAll(Page page,ApproveProcess approveProcess) {
        IPage<ApproveProcess> approveProcessIPage = approveProcessMapper.listPage(page,approveProcess);
    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> allFiles = new ArrayList<>();
            //  采购审批查询
            if (record.getApproveType() == 5) {
                String contractNo = record.getApproveReason();
                PurchaseLedger ledger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>()
                        .eq(PurchaseLedger::getPurchaseContractNumber, contractNo)
                        .last("limit 1"));
                if (ledger != null) {
                    allFiles = commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
                            .eq(CommonFile::getCommonId, ledger.getId())
                            .eq(CommonFile::getType, FileNameType.PURCHASE.getValue()));
                }
            }
            //  发货审批查询
            else if (record.getApproveType() == 7) {
                String reason = record.getApproveReason(); // 格式为 "xx:...-..."
                if (StringUtils.hasText(reason) && reason.contains(":")) {
                    // 提取冒号后面的发货单号
                    String shippingNo = reason.split(":")[1];
                    // 根据发货单号查询发货台账记录
                    ShippingInfo shippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>()
                            .eq(ShippingInfo::getShippingNo, shippingNo)
                            .last("limit 1"));
                    if (shippingInfo != null) {
                        // 使用发货台账的 销售台账ID 去查附件
                        allFiles = commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
                                .eq(CommonFile::getCommonId, shippingInfo.getSalesLedgerId())
                                .eq(CommonFile::getType, FileNameType.SALE.getValue()));
                    }
                }
            }
            //  查询审批单自身的附件
            else {
                allFiles = commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
                        .eq(CommonFile::getCommonId, record.getId())
                        .eq(CommonFile::getType, FileNameType.ApproveProcess.getValue()));
            }
            record.setCommonFileList(allFiles);
        }
        return approveProcessIPage;
    }
    @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 delByIds(List<Long> ids) {
        for (Long approveId : ids) {
            //  逻辑删除审批流程
            update(new UpdateWrapper<ApproveProcess>()
                    .lambda()
                    .set(ApproveProcess::getApproveDelete, 1)
                    .eq(ApproveProcess::getId, approveId));
            // 删除对应的附件
            commonFileService.deleteByBusinessId(approveId, FileNameType.ApproveProcess.getValue());
            ApproveProcess approveProcess = approveProcessMapper.selectById(approveId);
            //  删除审批节点
            approveNodeService.delApproveNodeByApproveId(approveProcess.getApproveId());
            //  只查最新一条审批流程
            ApproveProcess latestProcess = approveProcessMapper.selectOne(
                    new LambdaQueryWrapper<ApproveProcess>()
                            .eq(ApproveProcess::getApproveId, approveId)
                            .orderByDesc(ApproveProcess::getCreateTime)
                            .last("LIMIT 1"));
            if (latestProcess == null) {
                continue;
            }
            //  删除对应的消息通知
            sysNoticeService.remove(new LambdaQueryWrapper<SysNotice>()
                    .eq(SysNotice::getNoticeTitle, approveProcessType(latestProcess.getApproveType()))
                    .eq(SysNotice::getSenderId, latestProcess.getApproveUser())
                    .apply("CAST(notice_content AS CHAR) LIKE CONCAT('%', {0}, '%')", latestProcess.getApproveId()));
        }
    }
    @Override
    public void delApprove(List<String> ids) {
        for (String approveId : ids) {
            ApproveProcess approveProcess = approveProcessMapper.selectOne(new LambdaQueryWrapper<ApproveProcess>()
                    .eq(ApproveProcess::getApproveId, approveId)
                    .eq(ApproveProcess::getApproveDelete, 0)
                    .last("LIMIT 1"));
            //  逻辑删除审批流程
            update(new UpdateWrapper<ApproveProcess>()
                    .lambda()
                    .set(ApproveProcess::getApproveDelete, 1)
                    .eq(ApproveProcess::getApproveId, approveId));
            // 删除对应的附件
            commonFileService.deleteByBusinessId(approveProcess.getId(), FileNameType.ApproveProcess.getValue());
            //  删除审批节点
            approveNodeService.delApproveNodeByApproveId(approveId);
            //  只查最新一条审批流程
            ApproveProcess latestProcess = approveProcessMapper.selectOne(
                    new LambdaQueryWrapper<ApproveProcess>()
                            .eq(ApproveProcess::getApproveId, approveId)
                            .orderByDesc(ApproveProcess::getCreateTime)
                            .last("LIMIT 1"));
            if (latestProcess == null) {
                continue;
            }
            //  删除对应的消息通知
            sysNoticeService.remove(new LambdaQueryWrapper<SysNotice>()
                            .eq(SysNotice::getNoticeTitle, approveProcessType(latestProcess.getApproveType()))
                            .eq(SysNotice::getSenderId, latestProcess.getApproveUser())
                            .apply("CAST(notice_content AS CHAR) LIKE CONCAT('%', {0}, '%')", latestProcess.getApproveId()));
        }
    }
    @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);
        ApproveProcess one = approveProcessMapper.selectList(Wrappers.<ApproveProcess>lambdaQuery()
                .eq(ApproveProcess::getApproveId,id)
                .eq(ApproveProcess::getApproveDelete,0)).get(0);
        one.setCommonFileList(commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
                .eq(CommonFile::getCommonId, one.getId())
                .eq(CommonFile::getType, FileNameType.ApproveProcess.getValue())));
        return one;
    }
    private final ApproveNodeMapper approveNodeMapper;
    @Override
    public void updateApproveProcessStatus(ApproveNode approveNode,Integer status) {
    // 报价审批编辑审核人
    public void updateApproveUser(ApproveGetAndUpdateVo approveGetAndUpdateVo) {
        LambdaQueryWrapper<ApproveProcess> approveProcessLambdaQueryWrapper = new LambdaQueryWrapper<>();
        approveProcessLambdaQueryWrapper.eq(ApproveProcess::getApproveId, approveNode.getApproveProcessId())
                .eq(ApproveProcess::getApproveDelete, 0)
                .eq(ApproveProcess::getTenantId, SecurityUtils.getLoginUser().getTenantId())
        approveProcessLambdaQueryWrapper.eq(ApproveProcess::getApproveType, approveGetAndUpdateVo.getApproveType())
                .eq(ApproveProcess::getApproveReason, approveGetAndUpdateVo.getApproveReason())
                .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)
                .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(approveNode.getApproveNodeUserId());
            approveProcess.setApproveUserCurrentName(approveNode1.getApproveNodeUser());
        }
        updateById(approveProcess);
    }
    @Override
    public void updateByApproveId(ApproveGetAndUpdateVo approveGetAndUpdateVo) {
        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){
        if (approveProcess == null) throw new RuntimeException("请选择审批人");
        String newIds = approveGetAndUpdateVo.getApproveUserIds();
        if (newIds == null || newIds.trim().isEmpty()) {
            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(sysUsers.get(0).getUserId());
            approve.setApproveUserCurrentName(sysUsers.get(0).getNickName());
        List<Long> newIdList = parseUserIds(newIds);
        List<SysUser> sysUsers = sysUserMapper.selectUserByIds(newIdList);
        if (CollectionUtils.isEmpty(sysUsers)) throw new RuntimeException("请选择审批人");
        approveProcess.setApproveUserIds(newIds);
        approveProcess.setApproveUserNames(sysUsers.stream().map(SysUser::getNickName).collect(Collectors.joining(",")));
        if (approveGetAndUpdateVo.getApproveStatus() != null) {
            approveProcess.setApproveStatus(approveGetAndUpdateVo.getApproveStatus());
        }
        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);
        List<ApproveNode> list = approveNodeMapper.selectList(approveNodeLambdaQueryWrapper);
        int i = 0;
        for (ApproveNode approveNode : list) {
            int finalI = i;
            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++;
        // 先删除 后新增(优先按 approve_node_config 快照保分组与模式)
        approveNodeMapper.delete(new LambdaQueryWrapper<ApproveNode>()
                .eq(ApproveNode::getApproveProcessId, approveProcess.getApproveId())
                .eq(ApproveNode::getDeleteFlag, 0));
        rebuildApproveNodes(approveProcess, newIds);
        // 审核中不可以编辑审核人
        if (approveProcess.getApproveStatus() != 1) {
            refreshCurrentApprovers(approveProcess);
        } else {
            approveProcessMapper.updateById(approveProcess);
        }
        /*消息通知(通知第一节点全部审批人)*/
        notifyApproveUsers(approveProcess, firstGroupUserIds(approveProcess));
    }
    @Override
    public void updateByApproveId(ApproveGetAndUpdateVo approveGetAndUpdateVo) throws IOException {
        ApproveProcess approve = approveProcessMapper.selectById(approveGetAndUpdateVo.getId());
        Integer oldStatus = approve.getApproveStatus();
        BeanUtils.copyProperties(approveGetAndUpdateVo, approve);
        // BeanUtils 会拷贝 null,防止把审批状态覆盖为空
        if (approveGetAndUpdateVo.getApproveStatus() == null) {
            approve.setApproveStatus(oldStatus);
        }
        approve.setApproveReason(approveGetAndUpdateVo.getApproveReason());
        String newIds = approveGetAndUpdateVo.getApproveUserIds();
        if (newIds == null || newIds.trim().isEmpty()) {
            throw new RuntimeException("请选择审批人");
        }
        List<Long> newIdList = parseUserIds(newIds);
        List<SysUser> sysUsers = sysUserMapper.selectUserByIds(newIdList);
        if (CollectionUtils.isEmpty(sysUsers)) throw new RuntimeException("请选择审批人");
        SysUser sysUser = sysUserMapper.selectUserById(approveGetAndUpdateVo.getApproveUser());
        if (sysUser == null) throw new RuntimeException("申请人不存在");
        approve.setApproveUserName(sysUser.getNickName());
        approve.setApproveUser(sysUser.getUserId());
        approve.setApproveUserIds(newIds);
        approve.setApproveUserNames(sysUsers.stream().map(SysUser::getNickName).collect(Collectors.joining(",")));
        //修改审批人:先删除后新增(优先按 approve_node_config 快照保分组与模式)
        approveNodeMapper.delete(new LambdaQueryWrapper<ApproveNode>()
                .eq(ApproveNode::getApproveProcessId, approve.getApproveId())
                .eq(ApproveNode::getDeleteFlag, 0));
        rebuildApproveNodes(approve, newIds);
        // 审核中不可以编辑审核人
        if (approve.getApproveStatus() != 1) {
            refreshCurrentApprovers(approve);
        } else {
            approveProcessMapper.updateById(approve);
        }
        tempFileService.migrateTempFilesToFormal(approve.getId(), approveGetAndUpdateVo.getTempFileIds(), FileNameType.ApproveProcess.getValue());
        /*消息通知(通知第一节点全部审批人)*/
        notifyApproveUsers(approve, firstGroupUserIds(approve));
    }
    /**
     * 按节点顺序分组(保序),同节点多审批人归入一组
     */
    private List<List<ApproveProcessConfigNodeVo>> groupByOrder(List<ApproveProcessConfigNodeVo> list) {
        Map<Integer, List<ApproveProcessConfigNodeVo>> map = new LinkedHashMap<>();
        for (ApproveProcessConfigNodeVo vo : list) {
            if (vo.getApproverId() == null) {
                continue;
            }
            int order = vo.getNodeOrder() == null ? map.size() + 1 : vo.getNodeOrder();
            map.computeIfAbsent(order, k -> new ArrayList<>()).add(vo);
        }
        return new ArrayList<>(map.values());
    }
    /**
     * 设置流程首节点待审人(多值逗号分隔,兼容旧的单值字段)
     */
    private void setFirstNodeApprovers(ApproveProcess approveProcess, List<ApproveProcessConfigNodeVo> firstGroup, List<SysUser> sysUsers) {
        List<Long> ids = firstGroup.stream()
                .map(ApproveProcessConfigNodeVo::getApproverId)
                .distinct()
                .collect(Collectors.toList());
        Map<Long, String> nameMap = sysUsers.stream()
                .collect(Collectors.toMap(SysUser::getUserId, SysUser::getNickName, (a, b) -> a));
        List<String> names = ids.stream().map(nameMap::get).filter(Objects::nonNull).collect(Collectors.toList());
        approveProcess.setApproveUserCurrentIds(ids.stream().map(String::valueOf).collect(Collectors.joining(",")));
        approveProcess.setApproveUserCurrentNames(String.join(",", names));
        if (!ids.isEmpty()) {
            approveProcess.setApproveUserCurrentId(ids.get(0));
            approveProcess.setApproveUserCurrentName(names.isEmpty() ? null : names.get(0));
        }
    }
    /**
     * 节点结构快照JSON:[{nodeOrder,mode,userIds,userNames}],供编辑重建审批节点
     */
    private String buildNodeConfig(List<ApproveProcessConfigNodeVo> list) {
        try {
            List<Map<String, Object>> nodes = new ArrayList<>();
            for (List<ApproveProcessConfigNodeVo> group : groupByOrder(list)) {
                String mode = group.get(0).getApproveMode();
                if (mode == null || mode.isEmpty()) {
                    mode = "or";
                }
                List<Long> ids = group.stream().map(ApproveProcessConfigNodeVo::getApproverId).distinct().collect(Collectors.toList());
                List<SysUser> users = sysUserMapper.selectUserByIds(ids);
                Map<String, Object> node = new LinkedHashMap<>();
                node.put("nodeOrder", group.get(0).getNodeOrder());
                node.put("mode", mode);
                node.put("userIds", ids.stream().map(String::valueOf).collect(Collectors.joining(",")));
                node.put("userNames", users.stream().map(SysUser::getNickName).collect(Collectors.joining(",")));
                nodes.add(node);
            }
            return OBJECT_MAPPER.writeValueAsString(nodes);
        } catch (Exception e) {
            return null;
        }
    }
    private List<String> splitIds(String ids) {
        if (ids == null || ids.trim().isEmpty()) {
            return new ArrayList<>();
        }
        return Arrays.stream(ids.split(",")).map(String::trim).filter(s -> !s.isEmpty()).collect(Collectors.toList());
    }
    private List<Long> parseUserIds(String ids) {
        return splitIds(ids).stream().map(Long::parseLong).collect(Collectors.toList());
    }
    /**
     * 重建审批节点:优先按 approve_node_config 快照保分组与模式重建,
     * 新选的扁平审批人串按节点槽位顺序依次填充;无快照则回退扁平逻辑(一节点一人 or)
     */
    private void rebuildApproveNodes(ApproveProcess approveProcess, String approveUserIds) {
        List<ApproveProcessConfigNodeVo> config = null;
        if (StringUtils.hasText(approveProcess.getApproveNodeConfig())) {
            config = buildConfigFromSnapshot(approveProcess.getApproveNodeConfig(), approveUserIds);
        }
        if (config == null) {
            approveNodeService.initApproveNodes(approveUserIds, approveProcess.getApproveId(), approveProcess.getTenantId());
        } else {
            approveNodeService.initApproveNodes(config, approveProcess.getApproveId(), approveProcess.getTenantId());
        }
    }
    private List<ApproveProcessConfigNodeVo> buildConfigFromSnapshot(String snapshotJson, String approveUserIds) {
        List<String> newIds = splitIds(approveUserIds);
        try {
            List<Map<String, Object>> nodes = OBJECT_MAPPER.readValue(snapshotJson,
                    new TypeReference<List<Map<String, Object>>>() {});
            List<ApproveProcessConfigNodeVo> config = new ArrayList<>();
            int cursor = 0;
            for (Map<String, Object> node : nodes) {
                Object orderObj = node.get("nodeOrder");
                int nodeOrder = orderObj instanceof Number ? ((Number) orderObj).intValue() : 0;
                String mode = node.get("mode") == null ? "or" : String.valueOf(node.get("mode"));
                List<String> snapshotIds = splitIds(node.get("userIds") == null ? null : String.valueOf(node.get("userIds")));
                int slot = Math.max(1, snapshotIds.size());
                for (int j = 0; j < slot; j++) {
                    String uid = cursor < newIds.size() ? newIds.get(cursor++) : (j < snapshotIds.size() ? snapshotIds.get(j) : null);
                    if (uid == null) {
                        continue;
                    }
                    ApproveProcessConfigNodeVo vo = new ApproveProcessConfigNodeVo();
                    vo.setNodeOrder(nodeOrder);
                    vo.setApproverId(Long.parseLong(uid));
                    vo.setApproveMode(mode);
                    config.add(vo);
                }
            }
            // 新选审批人若多于快照槽位,追加到最后一个节点
            while (cursor < newIds.size()) {
                ApproveProcessConfigNodeVo vo = new ApproveProcessConfigNodeVo();
                vo.setNodeOrder(nodes.isEmpty() ? 1 : ((Number) nodes.get(nodes.size() - 1).get("nodeOrder")).intValue());
                vo.setApproverId(Long.parseLong(newIds.get(cursor++)));
                vo.setApproveMode("or");
                config.add(vo);
            }
            return config;
        } catch (Exception e) {
            return null;
        }
    }
    /**
     * 刷新流程当前审批人为第一节点待审人(用于编辑后重建)
     */
    private void refreshCurrentApprovers(ApproveProcess approveProcess) {
        List<ApproveNode> first = approveNodeMapper.selectList(new LambdaQueryWrapper<ApproveNode>()
                .eq(ApproveNode::getApproveProcessId, approveProcess.getApproveId())
                .eq(ApproveNode::getApproveNodeOrder, 1)
                .eq(ApproveNode::getDeleteFlag, 0)
                .orderByAsc(ApproveNode::getId));
        if (first.isEmpty()) {
            approveProcess.setApproveUserCurrentIds(null);
            approveProcess.setApproveUserCurrentNames(null);
            approveProcess.setApproveUserCurrentId(null);
            approveProcess.setApproveUserCurrentName(null);
        } else {
            approveProcess.setApproveUserCurrentIds(first.stream()
                    .map(n -> String.valueOf(n.getApproveNodeUserId())).collect(Collectors.joining(",")));
            approveProcess.setApproveUserCurrentNames(first.stream()
                    .map(ApproveNode::getApproveNodeUser).collect(Collectors.joining(",")));
            approveProcess.setApproveUserCurrentId(first.get(0).getApproveNodeUserId());
            approveProcess.setApproveUserCurrentName(first.get(0).getApproveNodeUser());
        }
        approveProcessMapper.updateById(approveProcess);
    }
    private List<Long> firstGroupUserIds(ApproveProcess approveProcess) {
        List<ApproveNode> first = approveNodeMapper.selectList(new LambdaQueryWrapper<ApproveNode>()
                .eq(ApproveNode::getApproveProcessId, approveProcess.getApproveId())
                .eq(ApproveNode::getApproveNodeOrder, 1)
                .eq(ApproveNode::getDeleteFlag, 0)
                .orderByAsc(ApproveNode::getId));
        return first.stream().map(ApproveNode::getApproveNodeUserId).distinct().collect(Collectors.toList());
    }
    /**
     * 通知第一节点全部待审人
     */
    private void notifyApproveUsers(ApproveProcess approveProcess, List<Long> userIds) {
        if (userIds == null || userIds.isEmpty()) {
            return;
        }
        List<Long> ids = userIds.stream().distinct().collect(Collectors.toList());
        if (approveProcess.getApproveType() == 8) {
            sysNoticeService.simpleNoticeByUser(approveProcessType(approveProcess.getApproveType()),
                    approveProcess.getApproveId() + "流程编号的审批需要您审核!!!!!",
                    ids,
                    "/safeProduction/safeWorkApproval?approveType=" + approveProcess.getApproveType() + "&approveId=" + approveProcess.getApproveId());
        } else {
            sysNoticeService.simpleNoticeByUser(approveProcessType(approveProcess.getApproveType()),
                    approveProcess.getApproveId() + "流程编号的审批需要您审核!!!!!",
                    ids,
                    "/collaborativeApproval/approvalProcess?approveType=" + approveProcess.getApproveType() + "&approveId=" + approveProcess.getApproveId());
        }
    }
    //审批类型获取(与前端页面对应)
    private String approveProcessType(Integer approveType) {
        switch (approveType) {
            case 1:
                return "公出管理";
            case 2:
                return "请假管理";
            case 3:
                return "出差管理";
            case 4:
                return "报销管理";
            case 5:
                return "采购审批";
            case 6:
                return "报价审批";
            case 7:
                return "发货审批";
            case 8:
                return "危险作业审批";
            case 9:
                return "用印审批";
        }
        return null;
    }
}