using Microsoft.AspNetCore.Http; using OpenAuth.App.BaseApp.Base; using OpenAuth.App.Interface; using OpenAuth.Repository; using OpenAuth.Repository.Domain; using OpenAuth.Repository.Domain.workflow; using OpenAuth.WebApi.Controllers.ServerController; using SqlSugar; namespace OpenAuth.App.workflow; /// /// 完整工作流引擎(适配:区县→执法监督科→5个审核科会签→汇总归档/退回) /// 包含:流程发起/处理 + 拟办/待办/已办/未办结/已完成/全部事项查询 /// public class WorkflowEngineApp : SqlSugarBaseApp { private readonly ISqlSugarClient _sqlSugar; private readonly BusinessNoGenerator _businessNoGenerator; // 配置:5个审核科角色ID(需与sys_role表对应,自行修改为实际值) private readonly List _auditDeptRoleIds = new() { 1, // 政策法规科 2, // 详细规划科 3, // 空间规划科 4, // 开发利用科 5 // 工程科 }; // 配置:执法监督科角色ID(需与sys_role表对应,自行修改为实际值) private const long _supervisionDeptRoleId = 6; public WorkflowEngineApp(ISugarUnitOfWork unitWork, ISimpleClient repository, IAuth auth, BusinessNoGenerator businessNoGenerator) : base(unitWork, repository, auth) { _sqlSugar = Repository.AsSugarClient(); _businessNoGenerator = businessNoGenerator; } #region 一、核心业务:流程发起/处理 /// /// 发起流程(区县提交→流转至执法监督科) /// /// 发起人ID /// 发起人姓名 /// 发起流程请求参数 /// 流程实例ID public long InitiateFlow(long userId, string userName, InitiateFlowRequestDto requestDto) { // 参数校验 /*if (string.IsNullOrEmpty(requestDto.FlowCode) || string.IsNullOrEmpty(requestDto.BusinessNo)) throw new Exception("流程编码和业务编号不能为空");*/ if (userId <= 0 || string.IsNullOrEmpty(userName)) throw new Exception("发起人ID和姓名不能为空"); var instanceId = 0L; try { // 开启事务(基于UnitWork,保障数据一致性) UnitWork.Db.Ado.BeginTran(); // 步骤1:查询启用的流程模板(违法建设认定流程) var template = _sqlSugar.Queryable() .Where(t => t.FlowCode == requestDto.FlowCode && t.IsEnabled == true) .First(); if (template == null) throw new Exception($"流程模板【{requestDto.FlowCode}】不存在或未启用"); // 步骤2:查询流程开始节点(区县提交) var startNode = _sqlSugar.Queryable() .Where(n => n.TemplateId == template.TemplateId && n.NodeType == "Start" && n.NodeName == "区县提交") .First(); if (startNode == null) throw new Exception("流程开始节点【区县提交】不存在,请配置节点"); var prefix = "wfjszl"; var businessNo = _businessNoGenerator.GenerateBusinessNo(prefix); requestDto.BusinessNo = businessNo; // 步骤3:插入流程实例,返回自增主键 var flowInstance = new ZyFlowInstance { TemplateId = template.TemplateId, FlowCode = template.FlowCode, BusinessNo = requestDto.BusinessNo, Status = "Submitted", // 已提交 CurrentNodeId = startNode.NodeId, InitiatorId = userId, InitiatorName = userName, CreateTime = DateTime.Now }; instanceId = _sqlSugar.Insertable(flowInstance).ExecuteReturnIdentity(); // 步骤4:插入开始节点工作项(直接标记为已完成) var startWorkitem = new ZyFlowWorkitem { InstanceId = instanceId, NodeId = startNode.NodeId, NodeName = startNode.NodeName, HandlerId = userId, HandlerName = userName, Status = "Done", // 已完成 ReceiveTime = DateTime.Now, HandleTime = DateTime.Now, Comment = "区县提交业务材料,流程发起成功" }; _sqlSugar.Insertable(startWorkitem).ExecuteCommand(); // 步骤5:保存流程变量(标题、附件路径等) //var attachmentPaths = SaveAttachments(requestDto.Attachments); var attachmentPaths = string.Join(",", requestDto.Attachments); var flowVariables = new List { new() { InstanceId = instanceId, VarKey = "Title", VarValue = requestDto.Title }, new() { InstanceId = instanceId, VarKey = "AttachmentPaths", VarValue = attachmentPaths }, new() { InstanceId = instanceId, VarKey = "SubmitTime", VarValue = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") }, new() { InstanceId = instanceId, VarKey = "Type", VarValue = requestDto.type.ToString() }, }; _sqlSugar.Insertable(flowVariables).ExecuteCommand(); // todo 创建或者更新表单数据 var illegal = new IllegalConstructionAssessment() { Id = requestDto.BusinessNo, Title = requestDto.Title, BusinessNumber = requestDto.BusinessNo, Attachments = attachmentPaths, CreateTime = DateTime.Now, CreateUser = userName, UpdateTime = DateTime.Now, }; if (_sqlSugar .Queryable() .Where(a => a.BusinessNumber == requestDto.BusinessNo).Count() > 0) { // todo 关于状态对于不同角色的人不一样问题 // _sqlSugar.Updateable(illegal).IgnoreColumns().ExecuteCommand(); } else { _sqlSugar.Insertable(illegal).ExecuteCommand(); } // 步骤6:核心流转:从区县提交节点流转至执法监督科节点 FlowToNextNode(instanceId, startNode, userId, userName, "区县提交完成,流转至执法监督科"); // 提交事务 UnitWork.Db.Ado.CommitTran(); } catch (Exception ex) { // 回滚事务 UnitWork.Db.Ado.RollbackTran(); throw new Exception($"发起流程失败:{ex.Message}", ex); } return instanceId; } /// /// 处理工作项(执法监督科转发/审核科会签/汇总处理) /// /// 处理人ID /// 处理人姓名 /// 处理工作项请求参数 /// 处理结果(成功/失败) public bool HandleWorkitem(long userId, string userName, HandleWorkitemRequestDto requestDto) { // 参数校验 if (requestDto.WorkitemId <= 0) throw new Exception("工作项ID无效"); if (userId <= 0 || string.IsNullOrEmpty(userName)) throw new Exception("处理人ID和姓名不能为空"); try { // 开启事务 UnitWork.Db.Ado.BeginTran(); // 步骤1:查询待处理的工作项(仅待办状态可处理) var workitem = _sqlSugar.Queryable() .Where(w => w.WorkitemId == requestDto.WorkitemId && w.Status == "ToDo") .First(); if (workitem == null) throw new Exception("工作项不存在、已处理或状态异常"); // 步骤2:查询关联的流程实例和当前节点 var flowInstance = _sqlSugar.Queryable() .Where(i => i.InstanceId == workitem.InstanceId) .First(); var currentNode = _sqlSugar.Queryable() .Where(n => n.NodeId == workitem.NodeId) .First(); if (flowInstance == null || currentNode == null) throw new Exception("流程实例或流程节点不存在"); // 步骤3:更新当前工作项为已完成 workitem.Status = "Done"; workitem.HandleTime = DateTime.Now; workitem.Comment = string.IsNullOrEmpty(requestDto.Comment) ? "处理完成" : requestDto.Comment; _sqlSugar.Updateable(workitem).ExecuteCommand(); // 步骤4:按节点类型分支处理核心逻辑 switch (currentNode.NodeType) { // 普通节点:执法监督科转发 case "Common" when currentNode.NodeName == "执法监督科转发": FlowToNextNode(flowInstance.InstanceId, currentNode, userId, userName, workitem.Comment); break; // 并行节点:5个审核科会签 case "Parallel" when _auditDeptRoleIds.Contains(currentNode.RoleId): ProcessParallelAudit(flowInstance.InstanceId, currentNode.NodeId, userId, userName, requestDto); break; // 分支节点:汇总(归档/退回区县) case "Branch" when currentNode.NodeName == "汇总": ProcessSummaryNode(flowInstance, currentNode, userId, userName); break; // 结束节点:流程归档完成 case "End": CompleteFlowInstance(flowInstance); break; } // 提交事务 UnitWork.Db.Ado.CommitTran(); return true; } catch (Exception ex) { // 回滚事务 UnitWork.Db.Ado.RollbackTran(); throw new Exception($"处理工作项失败:{ex.Message}", ex); } } #endregion #region 二、完整查询:拟办/待办/已办/未办结/已完成/全部事项 /// /// 我的拟办(未认领/待分配给当前用户的事项) /// /// 当前用户ID /// 分页参数 /// 分页结果 public PageQueryResultDto QueryMyDraft(long userId, PageQueryRequestDto pageQueryDto) { // 参数初始化 if (userId <= 0) return new PageQueryResultDto(); pageQueryDto = pageQueryDto ?? new PageQueryRequestDto(); var pageIndex = pageQueryDto.page < 1 ? 1 : pageQueryDto.page; var pageSize = pageQueryDto.limit < 1 ? 10 : pageQueryDto.limit; var userIdStr = userId.ToString(); // 拟办不应该是针对个人的吗? // 拟办逻辑:工作项状态为"Draft"(拟办)、对应角色匹配当前用户、未分配具体处理人 var draftQuery = _sqlSugar .Queryable() .Where(w => w.Status == "Draft" && w.CreateUser == userIdStr) .OrderByDescending(w => w.ReceiveTime); // 分页查询 var totalCount = draftQuery.Count(); var draftWorkitems = draftQuery .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToList(); // 组装返回结果 var dataList = new List(); foreach (var workitem in draftWorkitems) { dataList.Add(new FlowQuerySingleResultDto { //InstanceId = flowInstance.InstanceId, BusinessNo = workitem.BusinessNumber, Title = workitem.Title, //NodeName = workitem.NodeName, Status = "Draft", // 拟办状态 CreateTime = workitem.CreateTime, //InitiatorName = workitem.CreateUser //发起人姓名 }); } return new PageQueryResultDto { Page = pageIndex, Limit = pageSize, Total = totalCount, TotalPages = (int)Math.Ceiling((double)totalCount / pageSize), Items = dataList }; } /// /// 我的待办(已分配给当前用户、待处理的事项) /// /// 当前用户ID /// 分页参数 /// 分页结果 public PageQueryResultDto QueryMyToDo(long userId, PageQueryRequestDto pageQueryDto) { // 参数初始化 if (userId <= 0) return new PageQueryResultDto(); pageQueryDto = pageQueryDto ?? new PageQueryRequestDto(); var pageIndex = pageQueryDto.page < 1 ? 1 : pageQueryDto.page; var pageSize = pageQueryDto.limit < 1 ? 10 : pageQueryDto.limit; // 待办逻辑:工作项状态为"ToDo"、处理人ID为当前用户 var nodeIdList = _sqlSugar.Queryable() // 核心:In(主表字段, 子查询Lambda表达式) .In( zy => zy.RoleId, // 主表 zy_flow_node 的 role_id 字段 // 子查询:从 sys_userrole 中查询 UserId=2 的 RoleId _sqlSugar.Queryable() .Where(ur => ur.UserId == userId) // 条件:UserId=2(注意:这里直接用数值,避免字符串拼接) .Select(ur => ur.RoleId) // 子查询只返回 RoleId 字段 ) // 主查询只返回 node_id 字段(提升查询性能,避免查询全表字段) .Select(zy => zy.NodeId) // 执行查询,返回 List 结果 .ToList(); var toDoQuery = _sqlSugar .Queryable() .LeftJoin((w, n) => w.NodeId == n.NodeId) .Where((w, n) => nodeIdList.Contains(w.NodeId) && w.Status == "ToDo") .OrderByDescending(w => w.ReceiveTime); // 分页查询 var totalCount = toDoQuery.Count(); var toDoWorkitems = toDoQuery .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToList(); // 组装返回结果 var dataList = new List(); foreach (var workitem in toDoWorkitems) { var flowInstance = _sqlSugar.Queryable() .Where(i => i.InstanceId == workitem.InstanceId) .First(); if (flowInstance == null) continue; var zyflowVarList = _sqlSugar.Queryable() .Where(v => v.InstanceId == flowInstance.InstanceId); var flowTitle = zyflowVarList.Where(v => v.VarKey == "Title").First()?.VarValue; var type = zyflowVarList.Where(v => v.VarKey == "Type").First()?.VarValue; dataList.Add(new FlowQuerySingleResultDto { InstanceId = flowInstance.InstanceId, BusinessNo = flowInstance.BusinessNo, Title = flowTitle, Type = Int16.Parse(type ?? "0"), NodeName = workitem.NodeName, Status = flowInstance.Status, CreateTime = flowInstance.CreateTime, InitiatorName = flowInstance.InitiatorName }); } return new PageQueryResultDto { Page = pageIndex, Limit = pageSize, Total = totalCount, TotalPages = (int)Math.Ceiling((double)totalCount / pageSize), Items = dataList }; } /// /// 我的已办(当前用户已处理完成的事项) /// /// 当前用户ID /// 分页参数 /// 分页结果 public PageQueryResultDto QueryMyDone(long userId, PageQueryRequestDto pageQueryDto) { // 参数初始化 if (userId <= 0) return new PageQueryResultDto(); pageQueryDto = pageQueryDto ?? new PageQueryRequestDto(); var pageIndex = pageQueryDto.page < 1 ? 1 : pageQueryDto.page; var pageSize = pageQueryDto.limit < 1 ? 10 : pageQueryDto.limit; // 已办逻辑:工作项状态为"Done"、处理人ID为当前用户(去重流程实例) var doneInstanceIds = _sqlSugar.Queryable() .Where(w => w.HandlerId == userId && w.Status == "Done") .Select(w => w.InstanceId) .Distinct() .ToList(); if (!doneInstanceIds.Any()) return new PageQueryResultDto(); // 分页处理实例ID var pagedInstanceIds = doneInstanceIds .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToList(); var totalCount = doneInstanceIds.Count; // 组装返回结果 var dataList = new List(); foreach (var instanceId in pagedInstanceIds) { var flowInstance = _sqlSugar.Queryable() .Where(i => i.InstanceId == instanceId) .First(); if (flowInstance == null) continue; var flowTitle = _sqlSugar.Queryable() .Where(v => v.InstanceId == flowInstance.InstanceId && v.VarKey == "Title") .First()?.VarValue ?? string.Empty; var lastWorkitem = _sqlSugar.Queryable() .Where(w => w.InstanceId == instanceId && w.HandlerId == userId && w.Status == "Done") .OrderByDescending(w => w.HandleTime) .First(); dataList.Add(new FlowQuerySingleResultDto { InstanceId = flowInstance.InstanceId, BusinessNo = flowInstance.BusinessNo, Title = flowTitle, NodeName = lastWorkitem?.NodeName ?? string.Empty, Status = flowInstance.Status, CreateTime = flowInstance.CreateTime, InitiatorName = flowInstance.InitiatorName }); } return new PageQueryResultDto { Page = pageIndex, Limit = pageSize, Total = totalCount, TotalPages = (int)Math.Ceiling((double)totalCount / pageSize), Items = dataList }; } /// /// 我的未办结(当前用户参与过、流程尚未完成的事项) /// /// 当前用户ID /// 分页参数 /// 分页结果 public PageQueryResultDto QueryMyUnfinished(long userId, PageQueryRequestDto pageQueryDto) { // 参数初始化 if (userId <= 0) return new PageQueryResultDto(); pageQueryDto = pageQueryDto ?? new PageQueryRequestDto(); var pageIndex = pageQueryDto.page < 1 ? 1 : pageQueryDto.page; var pageSize = pageQueryDto.limit < 1 ? 10 : pageQueryDto.limit; // 未办结逻辑: // 1. 当前用户参与过(处理过/待处理) // 2. 流程状态不是"Completed"(未完成) var unfinishedInstanceIds = _sqlSugar.Queryable((w, i) => new JoinQueryInfos( JoinType.Inner, w.InstanceId == i.InstanceId)) .Where((w, i) => (w.HandlerId == userId || (w.Status == "Draft" && _auditDeptRoleIds.Contains(w.NodeId))) && i.Status != "Completed") .Select((w, i) => i.InstanceId) .Distinct() .ToList(); if (!unfinishedInstanceIds.Any()) return new PageQueryResultDto(); // 分页处理实例ID var pagedInstanceIds = unfinishedInstanceIds .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToList(); var totalCount = unfinishedInstanceIds.Count; // 组装返回结果 var dataList = new List(); foreach (var instanceId in pagedInstanceIds) { var flowInstance = _sqlSugar.Queryable() .Where(i => i.InstanceId == instanceId) .First(); if (flowInstance == null) continue; var flowTitle = _sqlSugar.Queryable() .Where(v => v.InstanceId == flowInstance.InstanceId && v.VarKey == "Title") .First()?.VarValue ?? string.Empty; var currentNode = _sqlSugar.Queryable() .Where(n => n.NodeId == flowInstance.CurrentNodeId) .First(); dataList.Add(new FlowQuerySingleResultDto { InstanceId = flowInstance.InstanceId, BusinessNo = flowInstance.BusinessNo, Title = flowTitle, NodeName = currentNode?.NodeName ?? string.Empty, Status = flowInstance.Status, CreateTime = flowInstance.CreateTime, InitiatorName = flowInstance.InitiatorName }); } return new PageQueryResultDto { Page = pageIndex, Limit = pageSize, Total = totalCount, TotalPages = (int)Math.Ceiling((double)totalCount / pageSize), Items = dataList }; } /// /// 我的已完成(当前用户参与过、流程已归档完成的事项) /// /// 当前用户ID /// 分页参数 /// 分页结果 public PageQueryResultDto QueryMyCompleted(long userId, PageQueryRequestDto pageQueryDto) { // 参数初始化 if (userId <= 0) return new PageQueryResultDto(); pageQueryDto = pageQueryDto ?? new PageQueryRequestDto(); var pageIndex = pageQueryDto.page < 1 ? 1 : pageQueryDto.page; var pageSize = pageQueryDto.limit < 1 ? 10 : pageQueryDto.limit; // 已完成逻辑: // 1. 当前用户参与过(处理过/待处理) // 2. 流程状态是"Completed"(已完成) var completedInstanceIds = _sqlSugar.Queryable((w, i) => new JoinQueryInfos( JoinType.Inner, w.InstanceId == i.InstanceId)) .Where((w, i) => (w.HandlerId == userId || (w.Status == "Draft" && _auditDeptRoleIds.Contains(w.NodeId))) && i.Status == "Completed") .Select((w, i) => i.InstanceId) .Distinct() .ToList(); if (!completedInstanceIds.Any()) return new PageQueryResultDto(); // 分页处理实例ID var pagedInstanceIds = completedInstanceIds .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToList(); var totalCount = completedInstanceIds.Count; // 组装返回结果 var dataList = new List(); foreach (var instanceId in pagedInstanceIds) { var flowInstance = _sqlSugar.Queryable() .Where(i => i.InstanceId == instanceId) .First(); if (flowInstance == null) continue; var flowTitle = _sqlSugar.Queryable() .Where(v => v.InstanceId == flowInstance.InstanceId && v.VarKey == "Title") .First()?.VarValue ?? string.Empty; var lastNode = _sqlSugar.Queryable() .Where(n => n.NodeId == flowInstance.CurrentNodeId) .First(); dataList.Add(new FlowQuerySingleResultDto { InstanceId = flowInstance.InstanceId, BusinessNo = flowInstance.BusinessNo, Title = flowTitle, NodeName = lastNode?.NodeName ?? "流程归档", Status = flowInstance.Status, CreateTime = flowInstance.CreateTime, InitiatorName = flowInstance.InitiatorName }); } return new PageQueryResultDto { Page = pageIndex, Limit = pageSize, Total = totalCount, TotalPages = (int)Math.Ceiling((double)totalCount / pageSize), Items = dataList }; } /// /// 我的全部事项(拟办+待办+已办+未办结+已完成,去重整合) /// /// 当前用户ID /// 分页参数 /// 分页结果 public PageQueryResultDto QueryMyAllItems(long userId, PageQueryRequestDto pageQueryDto) { // 参数初始化 if (userId <= 0) return new PageQueryResultDto(); pageQueryDto = pageQueryDto ?? new PageQueryRequestDto(); var pageIndex = pageQueryDto.page < 1 ? 1 : pageQueryDto.page; var pageSize = pageQueryDto.limit < 1 ? 10 : pageQueryDto.limit; var nodeIdList = _sqlSugar.Queryable() // 核心:In(主表字段, 子查询Lambda表达式) .In( zy => zy.RoleId, // 主表 zy_flow_node 的 role_id 字段 // 子查询:从 sys_userrole 中查询 UserId=2 的 RoleId _sqlSugar.Queryable() .Where(ur => ur.UserId == userId) // 条件:UserId=2(注意:这里直接用数值,避免字符串拼接) .Select(ur => ur.RoleId) // 子查询只返回 RoleId 字段 ) // 主查询只返回 node_id 字段(提升查询性能,避免查询全表字段) .Select(zy => zy.NodeId) // 执行查询,返回 List 结果 .ToList(); // todo 未包含拟办,需集成表单后实现 var allQuery = _sqlSugar .Queryable() .LeftJoin((w, n) => w.NodeId == n.NodeId) .Where((w, n) => nodeIdList.Contains(w.NodeId)) .OrderByDescending(w => w.ReceiveTime); var totalCount = allQuery.Count(); var toDoWorkitems = allQuery .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToList(); // 组装返回结果 var dataList = new List(); foreach (var workitem in toDoWorkitems) { var flowInstance = _sqlSugar.Queryable() .Where(i => i.InstanceId == workitem.InstanceId) .First(); if (flowInstance == null) continue; var zyflowVarList = _sqlSugar.Queryable() .Where(v => v.InstanceId == flowInstance.InstanceId); var flowTitle = zyflowVarList.Where(v => v.VarKey == "Title").First()?.VarValue; var type = zyflowVarList.Where(v => v.VarKey == "Type").First()?.VarValue; dataList.Add(new FlowQuerySingleResultDto { InstanceId = flowInstance.InstanceId, BusinessNo = flowInstance.BusinessNo, Title = flowTitle, Type = Int16.Parse(type ?? "0"), NodeName = workitem.NodeName, Status = flowInstance.Status, CreateTime = flowInstance.CreateTime, InitiatorName = flowInstance.InitiatorName }); } return new PageQueryResultDto { Page = pageIndex, Limit = pageSize, Total = totalCount, TotalPages = (int)Math.Ceiling((double)totalCount / pageSize), Items = dataList }; } #endregion #region 三、内部核心辅助方法(仅内部调用,不对外暴露) /// /// 流程节点流转核心方法 /// private void FlowToNextNode(long instanceId, ZyFlowNode currentNode, long userId, string userName, string comment) { // 解析下一节点IDs var nextNodeIds = currentNode.NextNodeIds?.Split(',', StringSplitOptions.RemoveEmptyEntries) .Select(long.Parse) .ToList() ?? new List(); if (!nextNodeIds.Any()) return; // 查询流程实例 var flowInstance = _sqlSugar.Queryable() .Where(i => i.InstanceId == instanceId) .First(); if (flowInstance == null) throw new Exception("流程实例不存在"); // 遍历处理每个下一节点 foreach (var nextNodeId in nextNodeIds) { var nextNode = _sqlSugar.Queryable() .Where(n => n.NodeId == nextNodeId) .First(); if (nextNode == null) continue; // 更新流程实例当前节点和状态 flowInstance.CurrentNodeId = nextNode.NodeId; flowInstance.Status = GetFlowStatusByNodeType(nextNode.NodeType); _sqlSugar.Updateable(flowInstance).ExecuteCommand(); // 并行节点(5个审核科):为每个审核科创建工作项 if (nextNode.NodeType == "Parallel") { foreach (var roleId in _auditDeptRoleIds) { CreateAuditDeptWorkitem(instanceId, nextNode, roleId); } } // 普通节点/分支节点:创建单个工作项 else { var nextWorkitem = new ZyFlowWorkitem { InstanceId = instanceId, NodeId = nextNode.NodeId, NodeName = nextNode.NodeName, HandlerId = userId, HandlerName = userName, Status = "ToDo", ReceiveTime = DateTime.Now, Comment = comment }; _sqlSugar.Insertable(nextWorkitem).ExecuteCommand(); } } } /// /// 处理并行会签(保存结果+判断是否全部完成) /// private void ProcessParallelAudit(long instanceId, long nodeId, long userId, string userName, HandleWorkitemRequestDto requestDto) { // 校验会签结果 if (string.IsNullOrEmpty(requestDto.AuditResult)) throw new Exception("会签需选择审核结果(Pass/Reject)"); // 步骤1:获取当前用户所属科室名称 var deptName = GetUserDeptName(userId); // 步骤2:保存会签记录 var parallelAudit = new ZyFlowParallelAudit { InstanceId = instanceId, NodeId = nodeId, DeptName = deptName, AuditResult = requestDto.AuditResult, AuditComment = requestDto.Comment, AuditorId = userId, AuditorName = userName, AuditTime = DateTime.Now }; _sqlSugar.Insertable(parallelAudit).ExecuteCommand(); // 步骤3:判断是否所有审核科都已完成会签 if (IsAllAuditCompleted(instanceId)) { // 步骤4:流转至汇总节点 var parallelNode = _sqlSugar.Queryable() .Where(n => n.NodeId == nodeId && n.NodeType == "Parallel") .First(); FlowToNextNode(instanceId, parallelNode, userId, userName, "所有审核科会签完成,流转至汇总节点"); } } /// /// 处理汇总节点(归档/退回区县) /// private void ProcessSummaryNode(ZyFlowInstance flowInstance, ZyFlowNode summaryNode, long userId, string userName) { // 步骤1:判断是否全部审核通过 var hasReject = _sqlSugar.Queryable() .Where(a => a.InstanceId == flowInstance.InstanceId) .Any(a => a.AuditResult != "Pass"); var isAllPass = !hasReject; // 步骤2:解析汇总节点下一节点(归档/退回) var nextNodeIds = summaryNode.NextNodeIds?.Split(',', StringSplitOptions.RemoveEmptyEntries) .Select(long.Parse) .ToList() ?? new List(); if (nextNodeIds.Count < 2) throw new Exception("汇总节点需配置2个后续节点(归档/退回区县)"); // 步骤3:确定目标节点(通过→归档[下标0],不通过→退回区县[下标1]) var targetNodeId = isAllPass ? nextNodeIds[0] : nextNodeIds[1]; var targetNode = _sqlSugar.Queryable() .Where(n => n.NodeId == targetNodeId) .First(); if (targetNode == null) throw new Exception($"汇总节点目标节点【{(isAllPass ? "归档" : "退回区县")}】不存在"); // 步骤4:创建目标节点工作项 var summaryWorkitem = new ZyFlowWorkitem { InstanceId = flowInstance.InstanceId, NodeId = targetNode.NodeId, NodeName = targetNode.NodeName, HandlerId = isAllPass ? null : flowInstance.InitiatorId, HandlerName = isAllPass ? "系统自动处理" : flowInstance.InitiatorName, Status = isAllPass ? "Done" : "ToDo", ReceiveTime = DateTime.Now, HandleTime = isAllPass ? DateTime.Now : null, Comment = isAllPass ? "所有审核科通过,流程归档完成" : "存在审核不通过项,退回区县修改补充材料" }; _sqlSugar.Insertable(summaryWorkitem).ExecuteCommand(); // 步骤5:更新流程实例最终状态 flowInstance.CurrentNodeId = targetNode.NodeId; flowInstance.Status = isAllPass ? "Completed" : "Rejected"; flowInstance.FinishTime = isAllPass ? DateTime.Now : null; _sqlSugar.Updateable(flowInstance).ExecuteCommand(); } /// /// 完成流程实例(结束节点) /// private void CompleteFlowInstance(ZyFlowInstance flowInstance) { flowInstance.Status = "Completed"; flowInstance.FinishTime = DateTime.Now; _sqlSugar.Updateable(flowInstance).ExecuteCommand(); } /// /// 为审核科创建工作项 /// private void CreateAuditDeptWorkitem(long instanceId, ZyFlowNode parallelNode, long roleId) { // 获取科室用户信息 var userInfo = GetRoleFirstUserInfo(roleId); if (userInfo.userId <= 0 || string.IsNullOrEmpty(userInfo.userName)) throw new Exception($"审核科角色【{roleId}】未配置有效用户"); // 创建工作项 var workitem = new ZyFlowWorkitem { InstanceId = instanceId, NodeId = parallelNode.NodeId, NodeName = $"{parallelNode.NodeName}({userInfo.deptName})", HandlerId = userInfo.userId, HandlerName = userInfo.userName, Status = "ToDo", ReceiveTime = DateTime.Now, Comment = "请完成违法建设认定相关审核工作" }; _sqlSugar.Insertable(workitem).ExecuteCommand(); } /// /// 保存附件并返回路径 /// private string SaveAttachments(List attachments) { if (attachments == null || !attachments.Any()) return string.Empty; // 定义附件存储目录 var uploadDir = Path .Combine(AppContext.BaseDirectory, "Attachments/FlowAttachments"); if (!Directory.Exists(uploadDir)) Directory.CreateDirectory(uploadDir); var attachmentPaths = new List(); foreach (var file in attachments) { if (file.Length <= 0) continue; // 生成唯一文件名 var fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}"; var filePath = Path.Combine(uploadDir, fileName); // 保存文件 using (var stream = new FileStream(filePath, FileMode.Create)) { file.CopyTo(stream); } // 保存相对路径(用于前端访问) attachmentPaths.Add($"/Attachments/FlowAttachments/{fileName}"); } return string.Join(",", attachmentPaths); } /// /// 根据节点类型获取流程状态 /// private string GetFlowStatusByNodeType(string nodeType) { return nodeType switch { "Common" => "Forwarded", // 已转发 "Parallel" => "Auditing", // 审核中 "Branch" => "Summarized", // 已汇总 "End" => "Completed", // 已完成 _ => "Submitted" // 已提交 }; } /// /// 判断所有审核科是否完成会签 /// private bool IsAllAuditCompleted(long instanceId) { var auditResults = _sqlSugar.Queryable() .Where(a => a.InstanceId == instanceId) .ToList(); // 条件:会签记录数=5个审核科 且 无待审核状态 return auditResults.Count == _auditDeptRoleIds.Count && !auditResults.Any(a => a.AuditResult == "Pending"); } /// /// 获取用户所属科室名称 /// private string GetUserDeptName(long userId) { var deptName = _sqlSugar.Queryable((uo, o) => new JoinQueryInfos( JoinType.Inner, uo.OrgId == o.Id)) .Where((uo, o) => uo.UserId == userId) .Select((uo, o) => o.Name) .First(); return deptName ?? "未知科室"; } /// /// 获取角色下第一个用户的完整信息 /// private (long userId, string userName, string deptName) GetRoleFirstUserInfo(long roleId) { var userInfo = _sqlSugar .Queryable((o, uo, u) => new JoinQueryInfos( JoinType.Inner, o.Id == uo.OrgId && uo.UserId == u.Id)) .Select((o, uo, u) => new { UserId = u.Id, UserName = u.Name, DeptName = o.Name }) .First(); if (userInfo == null) return (0, string.Empty, string.Empty); return (userInfo.UserId, userInfo.UserName ?? string.Empty, userInfo.DeptName ?? string.Empty); } #endregion /// /// 保存拟办 /// /// /// /// public object SaveDraft(InitiateFlowRequestDto requestDto) { requestDto.BusinessNo = _businessNoGenerator.GenerateBusinessNo("WF"); var attachmentPaths = string.Join(",", requestDto.Attachments); var user = _auth.GetCurrentUser().User; var illegalConstructionAssessment = new IllegalConstructionAssessment { Id = requestDto.BusinessNo, Title = requestDto.Title, BusinessNumber = requestDto.BusinessNo, Attachments = requestDto.Attachments != null ? attachmentPaths : string.Empty, AcceptanceTime = DateTime.Now, Status = "Draft", CreateTime = DateTime.Now, CreateUser = user.Name, UpdateTime = DateTime.Now, }; _sqlSugar.Insertable(illegalConstructionAssessment).ExecuteCommand(); return true; } }