package com.ruoyi.approve.service.impl; 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.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; import com.ruoyi.other.service.impl.TempFileServiceImpl; import com.ruoyi.project.system.domain.SysDept; import com.ruoyi.project.system.domain.SysUser; 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 com.ruoyi.sales.pojo.SalesLedger; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.io.IOException; import java.text.ParseException; 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.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @Service @RequiredArgsConstructor public class ApproveProcessServiceImpl extends ServiceImpl implements IApproveProcessService { private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd"); 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 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 nodes = resolveNodes(request, tenantId); List 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() .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 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())); } } } private List resolveNodes(ApproveProcessVO request, Long tenantId) { if (!CollectionUtils.isEmpty(request.getNodes())) return request.getNodes(); if (request.getFlowDefinitionId() != null) { ApproveFlowDefinition definition = flowDefinitionMapper.selectOne(new LambdaQueryWrapper() .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 definitions = flowNodeDefinitionMapper.selectList( new LambdaQueryWrapper() .eq(ApproveFlowNodeDefinition::getFlowDefinitionId, definition.getId()) .eq(ApproveFlowNodeDefinition::getTenantId, tenantId) .eq(ApproveFlowNodeDefinition::getDeleteFlag, 0) .orderByAsc(ApproveFlowNodeDefinition::getNodeOrder)); List 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 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 validateApprovers(List nodes, Long tenantId) { List 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 selectDeptListByDeptIds(Long[] deptIds) { List departments = new ArrayList<>(); if (deptIds == null) return departments; for (Long deptId : deptIds) { SysDept dept = sysDeptMapper.selectDeptById(deptId); if (dept != null) departments.add(dept); } return departments; } @Override public IPage listAll(Page page, ApproveProcess request) { request.setTenantId(tenantId()); IPage result = approveProcessMapper.listPage(page, request); result.getRecords().forEach(record -> record.setCommonFileList(commonFileMapper.selectList( new LambdaQueryWrapper() .eq(CommonFile::getCommonId, record.getId()) .eq(CommonFile::getType, FileNameType.ApproveProcess.getValue())))); return result; } @Override 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 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) { ApproveProcess process = findOwnedByCode(id); process.setCommonFileList(commonFileMapper.selectList(new LambdaQueryWrapper() .eq(CommonFile::getCommonId, process.getId()) .eq(CommonFile::getType, FileNameType.ApproveProcess.getValue()))); return process; } @Override public void updateByApproveId(ApproveGetAndUpdateVo request) throws IOException { ApproveProcess process = approveProcessMapper.selectOne(new LambdaQueryWrapper() .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("当前审批状态不允许编辑"); } 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); List 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 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() .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(); } }