using Microsoft.AspNetCore.Http; using NetTopologySuite.Operation.Distance; 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; 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}】不存在或未启用"); var startNode = _sqlSugar.Queryable() .Where(n => n.TemplateId == template.TemplateId && n.NodeType == "Start" && n.NodeName == "区县提交") .First(); if (startNode == null) throw new Exception("流程开始节点【区县提交】不存在,请配置节点"); var attachmentPaths = string.Join(",", requestDto.Attachments); if (_sqlSugar .Queryable() .Any(a => a.BusinessNumber == requestDto.BusinessNo)) { var illegalUpdate = new IllegalConstructionAssessment() { Id = requestDto.BusinessNo, UpdateTime = DateTime.Now, Status = "Submitted" }; // 方案1:明确指定要更新的列,避免主键冲突 _sqlSugar.Updateable(illegalUpdate) .UpdateColumns(it => new { it.UpdateTime, it.Status }) .ExecuteCommand(); // 或者方案2:使用 Where 条件更新 // _sqlSugar.Updateable() // .SetColumns(it => new { it.UpdateTime = DateTime.Now, it.Status = "Submitted" }) // .Where(it => it.Id == requestDto.BusinessNo) // .ExecuteCommand(); var flowInstance = _sqlSugar.Queryable() .Where(a => a.BusinessNo == requestDto.BusinessNo).First(); instanceId = flowInstance.InstanceId; var flowInstanceUpdate = new ZyFlowInstance() { InstanceId = instanceId, Status = "Submitted", }; _sqlSugar.Updateable(flowInstanceUpdate).IgnoreColumns().ExecuteCommand(); var flowWorkItem = _sqlSugar.Queryable() .Where(a => a.InstanceId == instanceId && a.NodeId == startNode.NodeId && a.Status == "Draft").First(); if (flowWorkItem == null) { throw new Exception("流程工作项不存在"); } var flowWorkItemUpdate = new ZyFlowWorkitem() { WorkitemId = flowWorkItem.WorkitemId, Status = "Done", HandleTime = DateTime.Now, Comment = "区县提交", }; _sqlSugar.Updateable(flowWorkItemUpdate).IgnoreColumns().ExecuteCommand(); } else { var illegal = new IllegalConstructionAssessment() { Id = requestDto.BusinessNo, Title = requestDto.Title, BusinessNumber = requestDto.BusinessNo, Attachments = attachmentPaths, CreateTime = DateTime.Now, CreateUser = userName, UpdateTime = DateTime.Now, }; _sqlSugar.Insertable(illegal).ExecuteCommand(); // 步骤2:查询流程开始节点(区县提交) 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:插入开始节点工作项(直接标记为已完成) // 开始节点无ToDo 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 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(); // 步骤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 == "执法监督科转发": case "Common": FlowToNextNode(flowInstance.InstanceId, currentNode, userId, userName, workitem.Comment); break; case "Parallel": ProcessParallelAudit(flowInstance.InstanceId, currentNode.NodeId, userId, userName, requestDto); var nextNodeId = long.Parse(currentNode.NextNodeIds.Split(',')[0]); var hasNextWorkItem = _sqlSugar.Queryable() .Where(w => w.InstanceId == flowInstance.InstanceId && w.Status == "ToDo" && w.NodeId == nextNodeId).First(); if (hasNextWorkItem != null) { var nextNode = _sqlSugar.Queryable() .Where(a => a.NodeId == nextNodeId).First(); // 更新脚本节点 var scriptNodeWorkItem = new ZyFlowWorkitem() { WorkitemId = hasNextWorkItem.WorkitemId, Status = "Done" }; _sqlSugar.Updateable(scriptNodeWorkItem).ExecuteCommand(); ProcessSummaryNode(flowInstance, nextNode, userId, userName); } 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; // 拟办不应该是针对个人的吗? // 拟办逻辑:工作项状态为"Draft"(拟办)、对应角色匹配当前用户、未分配具体处理人 var draftQuery = _sqlSugar .Queryable() .LeftJoin((t, i) => t.BusinessNumber == i.BusinessNo) .LeftJoin((t, i, n) => i.CurrentNodeId == n.NodeId) .LeftJoin((t, i, n, w) => i.InstanceId == w.InstanceId) .Where((t, i, n, w) => w.Status == "Draft" && i.InstanceId == userId) .Select((t, i, n, w) => new FlowQuerySingleResultDto { InstanceId = i.InstanceId, BusinessNo = t.BusinessNumber, Title = t.Title, NodeName = n.NodeName, Status = t.Status, // 拟办状态 CreateTime = t.CreateTime, InitiatorName = t.CreateUser //发起人姓名 }); // 分页查询 var totalCount = draftQuery.Count(); var dataList = draftQuery .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToList(); 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") .Where((w, n) => w.HandlerId == null || w.HandlerId == userId) .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 { workitemId = workitem.WorkitemId, InstanceId = flowInstance.InstanceId, BusinessNo = flowInstance.BusinessNo, Title = flowTitle, Type = Int16.Parse(type ?? "0"), NodeName = workitem.NodeName, Status = workitem.Status, WorkStatus = workitem.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 = lastWorkitem?.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") && 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") && 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; // todo 拟办 发起人 InitiatorId 要经手的 角色绑定的节点 handler_id = null 或者 已经手的(handler_id = user ) 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 allQuery = _sqlSugar.Queryable((t, i, n) => new JoinQueryInfos( // 第一个左联:t(违建表) LEFT JOIN i(流程实例表) ON 业务编号匹配 JoinType.Left, t.BusinessNumber == i.BusinessNo, // 第二个左联:i(流程实例表) LEFT JOIN n(流程节点表) ON 当前节点ID匹配 JoinType.Left, i.CurrentNodeId == n.NodeId )) // WHERE条件:严格还原原生SQL的OR+IN子查询逻辑(参数化userId,替换硬编码) .Where((t, i, n) => i.InitiatorId == userId || SqlFunc.Exists( SqlFunc.Subqueryable() .GroupBy(w => w.InstanceId) // 子查询过滤条件:仅修改Contains + 加nodeIdList非空判断 .Where(w => w.HandlerId == userId) // 关键1:用WhereIF判断列表非空,避免IN()语法错误;用SqlFunc.Contains替代原生Contains .WhereIF(nodeIdList != null && nodeIdList.Any(), w => w.HandlerId == null && w.Status == "ToDo" && SqlFunc.ContainsArray(nodeIdList, w.NodeId)) // 主查询与子查询的实例ID关联(原有逻辑,不变) .Where(w => w.InstanceId == i.InstanceId) .Select(w => w.InstanceId) ) ) // 你的指定Select:完全按FlowQuerySingleResultDto赋值,保留原空值处理逻辑 .Select((t, i, n) => new FlowQuerySingleResultDto { InstanceId = i.InstanceId, BusinessNo = i.BusinessNo, Title = t.Title, Type = t.Type, // 保留原空值默认转0的处理 NodeName = n.NodeName, // 左联可能为null,Dto中若需默认值可加??"未知节点" Status = i.Status, CreateTime = i.CreateTime, InitiatorName = i.InitiatorName }); var totalCount = allQuery.Count(); var dataList = allQuery .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToList(); 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") { CreateAuditDeptWorkitem(instanceId, nextNode); } // 普通节点/分支节点:创建单个工作项 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, nodeId)) { // 步骤4:流转至汇总节点 var parallelNode = _sqlSugar.Queryable() .Where(n => n.NodeId == nodeId && n.NodeType == "Parallel") .First(); FlowToNextNode(instanceId, parallelNode, userId, userName, "所有审核科会签完成,流转至汇总节点"); } } private bool IsAllAuditCompleted(long instanceId, long nodeId) { // 有一个 Pending var auditsCount = _sqlSugar.Queryable() .Any(a => a.InstanceId == instanceId && a.NodeId == nodeId && a.Status == "ToDo"); return !auditsCount; } /// /// 处理汇总节点(归档/退回区县) /// 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 == "Reject"); 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) { // 获取科室用户信息 var userInfos = _sqlSugar .Queryable() .LeftJoin((u, ur) => u.Id == ur.UserId) .LeftJoin((u, ur, uo) => u.Id == uo.UserId) .LeftJoin((u, ur, uo, o) => uo.OrgId == o.Id) .Where((u, ur, uo, o) => ur.RoleId == parallelNode.RoleId) .Select((u, ur, uo, o) => new { userName = u.Name, userId = u.Id, deptName = o.Name }) .ToList(); foreach (var userInfo in userInfos) { // 创建工作项 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 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((ur, u, uo, o) => new JoinQueryInfos( JoinType.Inner, ur.UserId == u.Id && u.Id == uo.UserId && uo.OrgId == o.Id)) .Where((ur, u, uo, o) => ur.RoleId == roleId) .Select((ur, u, uo, o) => 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) { try { UnitWork.Db.Ado.BeginTran(); requestDto.BusinessNo = _businessNoGenerator.GenerateBusinessNo("wf"); var hasExist = _sqlSugar.Queryable() .Where(a => a.BusinessNumber == requestDto.BusinessNo) .Any(); if (hasExist) { requestDto.BusinessNo = _businessNoGenerator.GenerateBusinessNo("wf",2); } var businessNo = requestDto.BusinessNo; var attachmentPaths = string.Join(",", requestDto.Attachments); var user = _auth.GetCurrentUser().User; var illegalConstructionAssessment = new IllegalConstructionAssessment { Id = businessNo, Title = requestDto.Title, BusinessNumber = requestDto.BusinessNo, Attachments = requestDto.Attachments != null ? attachmentPaths : string.Empty, AcceptanceTime = DateTime.Now, Status = "Draft", Type = requestDto.type, CreateTime = DateTime.Now, CreateUser = user.Name, UpdateTime = DateTime.Now, CreateUserId = user.Id }; _sqlSugar.Insertable(illegalConstructionAssessment).ExecuteCommand(); 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 flowInstance = new ZyFlowInstance { TemplateId = template.TemplateId, FlowCode = template.FlowCode, BusinessNo = requestDto.BusinessNo, Status = "Draft", // 已提交 CurrentNodeId = startNode.NodeId, InitiatorId = user.Id, InitiatorName = user.Name, CreateTime = DateTime.Now }; var instanceId = _sqlSugar.Insertable(flowInstance).ExecuteReturnIdentity(); // 步骤4:插入开始节点工作项(直接标记为已完成) // 开始节点无ToDo var startWorkitem = new ZyFlowWorkitem { InstanceId = instanceId, NodeId = startNode.NodeId, NodeName = startNode.NodeName, HandlerId = user.Id, HandlerName = user.Name, Status = "Draft", // 已完成 ReceiveTime = DateTime.Now, HandleTime = DateTime.Now, Comment = "区县拟办" }; _sqlSugar.Insertable(startWorkitem).ExecuteCommand(); UnitWork.Db.Ado.CommitTran(); return true; } finally { UnitWork.Db.Ado.RollbackTran(); } } public dynamic Detail(string businessNo) { return _sqlSugar .Queryable() .Where(i => i.Id == businessNo) .First(); } }