using Microsoft.AspNetCore.Http; using OpenAuth.App.BaseApp.Base; using OpenAuth.App.FormScheme.FormHelpers; using OpenAuth.App.Interface; using OpenAuth.Repository; using OpenAuth.Repository.Domain; using OpenAuth.Repository.Domain.workflow; using SqlSugar; namespace OpenAuth.App.workflow; /// /// 完整工作流引擎(适配:区县→执法监督科→5个审核科会签→汇总归档/退回) /// 包含:流程发起/处理 + 拟办/待办/已办/未办结/已完成/全部事项查询 /// public class WorkflowEngineApp : SqlSugarBaseApp { private readonly ISqlSugarClient _sqlSugar; private readonly BusinessNoGenerator _businessNoGenerator; private readonly string prefix = "wfjszl"; 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 { // 步骤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("流程开始节点【区县提交】不存在,请配置节点"); // 是否存在拟办数据 if (!string.IsNullOrEmpty(requestDto.BusinessNo) && _sqlSugar .Queryable() .Any(a => a.BusinessNumber == requestDto.BusinessNo)) { // 开启事务(基于UnitWork,保障数据一致性) UnitWork.Db.Ado.BeginTran(); // 关于直接提交拟办,并提交,并未保存的处理 var illegalUpdate = new IllegalConstructionAssessment() { Id = requestDto.BusinessNo, UpdateTime = DateTime.Now, Status = "Submitted" }; // 字段是否有更新 if (!string.IsNullOrEmpty(requestDto.Title)) { illegalUpdate.Title = requestDto.Title; } if (!requestDto.Attachments.IsEmpty()) { var attachmentPaths = string.Join(",", requestDto.Attachments); illegalUpdate.Attachments = attachmentPaths; } if (requestDto.Type != null) { illegalUpdate.Type = requestDto.Type; } // 方案1:明确指定要更新的列,避免主键冲突 _sqlSugar.Updateable(illegalUpdate) .IgnoreNullColumns().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).IgnoreNullColumns().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).IgnoreNullColumns().ExecuteCommand(); } else { // 取号 var businessNo = _businessNoGenerator.GenerateBusinessNo(prefix); requestDto.BusinessNo = businessNo; // 开启事务(基于UnitWork,保障数据一致性) UnitWork.Db.Ado.BeginTran(); var illegal = new IllegalConstructionAssessment() { Id = requestDto.BusinessNo, Title = requestDto.Title, BusinessNumber = requestDto.BusinessNo, Type = requestDto.Type, CreateTime = DateTime.Now, CreateUser = userName, UpdateTime = DateTime.Now, }; if (!requestDto.Attachments.IsEmpty()) { var attachmentPaths = string.Join(",", requestDto.Attachments); illegal.Attachments = attachmentPaths; } _sqlSugar.Insertable(illegal).ExecuteCommand(); // 步骤2:查询流程开始节点(区县提交) // 步骤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); } // 避免驳回提交数据影响 _sqlSugar.Deleteable().Where(a => a.InstanceId == instanceId).ExecuteCommand(); var flowVariables = new List { new() { InstanceId = instanceId, VarKey = "Title", VarValue = requestDto.Title }, new() { InstanceId = instanceId, VarKey = "SubmitTime", VarValue = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") }, new() { InstanceId = instanceId, VarKey = "Type", VarValue = requestDto.Type.ToString() }, }; if (!requestDto.Attachments.IsEmpty()) { var attachmentPaths = string.Join(",", requestDto.Attachments); var x = new ZyFlowVariable() { InstanceId = instanceId, VarKey = "AttachmentPaths", VarValue = attachmentPaths }; flowVariables.Add(x); } _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.HandlerId = userId; workitem.HandlerName = userName; 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(); _sqlSugar.Updateable() .SetColumns(t => new ZyFlowWorkitem() { Status = "Done" }) .Where(t => t.WorkitemId == hasNextWorkItem.WorkitemId).ExecuteCommand(); // 判断是归档还是驳回 ProcessSummaryNode(flowInstance, nextNode, userId, userName); }*/ break; // 分支节点:汇总(归档/退回区县) case "Branch" when currentNode.NodeName == "汇总判断": ProcessSummaryNode(flowInstance, currentNode, userId, userName, requestDto); 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 ??= 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.InitiatorId == userId) .Select((t, i, n, w) => new FlowQuerySingleResultDto { workitemId = w.WorkitemId, InstanceId = i.InstanceId, NodeName = n.NodeName, Status = w.Status, // 工作项状态 BusinessNo = t.BusinessNumber, Title = t.Title, Type = t.Type, 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 只有拟办 及已办 // 当时监督科用户时:工作项状态ToDo 且用户角色能处理的节点 且 handler_id == null // 当时审核科用户时:handler_id 必须等于用户id // todo 思考: 当角色存在多个用户时是否有问题? 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) .LeftJoin((w, n, i) => w.InstanceId == i.InstanceId) .LeftJoin((w, n, i, t) => i.BusinessNo == t.BusinessNumber) .Where((w, n, i, t) => nodeIdList.Contains(w.NodeId) && w.Status == "ToDo") .Where((w, n, i, t) => w.HandlerId == null || w.HandlerId == userId) .Select((w, n, i, t) => new FlowQuerySingleResultDto { workitemId = w.WorkitemId, InstanceId = i.InstanceId, BusinessNo = t.BusinessNumber, Title = t.Title, Type = t.Type, NodeName = n.NodeName, Status = SqlFunc.IIF(i.Status == "Rejected", "Rejected", w.Status), WorkStatus = i.Status, CreateTime = i.CreateTime, InitiatorName = i.InitiatorName, NodeType = n.NodeType }); // 分页查询 var totalCount = toDoQuery.Count(); var toDoWorkItems = toDoQuery .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToList(); return new PageQueryResultDto { Page = pageIndex, Limit = pageSize, Total = totalCount, TotalPages = (int)Math.Ceiling((double)totalCount / pageSize), Items = toDoWorkItems }; } /// /// 我的已办(当前用户已处理完成的事项) /// /// 当前用户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() .LeftJoin((w, n) => w.NodeId == n.NodeId) .LeftJoin((w, n, i) => w.InstanceId == i.InstanceId) .LeftJoin((w, n, i, t) => i.BusinessNo == t.BusinessNumber) .Where((w, n, i, t) => w.Status == "Done") .Where((w, n, i, t) => w.HandlerId == userId) .Select((w, n, i, t) => new FlowQuerySingleResultDto { workitemId = w.WorkitemId, InstanceId = i.InstanceId, BusinessNo = t.BusinessNumber, Title = t.Title, Type = t.Type, NodeName = n.NodeName, Status = w.Status, WorkStatus = i.Status, CreateTime = i.CreateTime, InitiatorName = i.InitiatorName }); // 分页处理实例ID var dataList = doneInstanceIds .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToList(); var totalCount = doneInstanceIds.Count(); 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"(未完成) // Draft(拟办)、Submitted(已提交)、Forwarded(已转发)、Auditing(审核中)、Summarized(已汇总)、Completed(已完成)、Rejected(已驳回) var doneInstanceIds = _sqlSugar .Queryable() .LeftJoin((w, n) => w.NodeId == n.NodeId) .LeftJoin((w, n, i) => w.InstanceId == i.InstanceId) .LeftJoin((w, n, i, t) => i.BusinessNo == t.BusinessNumber) .Where((w, n, i, t) => w.Status == "Done" && i.Status != "Completed") .Where((w, n, i, t) => w.HandlerId == userId) .Select((w, n, i, t) => new FlowQuerySingleResultDto { workitemId = w.WorkitemId, InstanceId = i.InstanceId, BusinessNo = t.BusinessNumber, Title = t.Title, Type = t.Type, NodeName = n.NodeName, Status = i.Status, WorkStatus = i.Status, CreateTime = i.CreateTime, InitiatorName = i.InitiatorName }); // 分页处理实例ID var pagedInstanceIds = doneInstanceIds .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToList(); var totalCount = doneInstanceIds.Count(); return new PageQueryResultDto { Page = pageIndex, Limit = pageSize, Total = totalCount, TotalPages = (int)Math.Ceiling((double)totalCount / pageSize), Items = pagedInstanceIds }; } /// /// 我的已完成(当前用户参与过、流程已归档完成的事项) /// /// 当前用户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() .LeftJoin((w, n) => w.NodeId == n.NodeId) .LeftJoin((w, n, i) => w.InstanceId == i.InstanceId) .LeftJoin((w, n, i, t) => i.BusinessNo == t.BusinessNumber) .Where((w, n, i, t) => w.Status == "Done" && i.Status == "Completed") .Where((w, n, i, t) => w.HandlerId == userId) .Select((w, n, i, t) => new FlowQuerySingleResultDto { workitemId = w.WorkitemId, InstanceId = i.InstanceId, BusinessNo = t.BusinessNumber, Title = t.Title, Type = t.Type, NodeName = n.NodeName ?? "流程归档", Status = i.Status, WorkStatus = i.Status, CreateTime = i.CreateTime, InitiatorName = i.InitiatorName }); // 分页处理实例ID var dataList = completedInstanceIds .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToList(); var totalCount = completedInstanceIds.Count(); 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 || 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).IgnoreNullColumns().ExecuteCommand(); // 并行节点(5个审核科):为每个审核科创建工作项 if (nextNode.NodeType == "Parallel") { // todo 转发到哪些部门 CreateAuditDeptWorkitem(instanceId, nextNode); } // 普通节点/分支节点:创建单个工作项 else { //var userInfo = GetRoleFirstUserInfo(nextNode.RoleId); // 执法监督科角色可能存在多个用户,不指定处理人 var nextWorkitem = new ZyFlowWorkitem { InstanceId = instanceId, NodeId = nextNode.NodeId, NodeName = nextNode.NodeName, //HandlerId = userInfo.userId, //HandlerName = userInfo.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, HandleWorkitemRequestDto requestDto) { // 步骤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个后续节点(归档/退回区县)"); var isAllPass = requestDto.AuditResult == "Pass"; // 步骤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 = userId, HandlerName = userName, Status = "Done", ReceiveTime = DateTime.Now, HandleTime = DateTime.Now, Comment = isAllPass ? requestDto.Comment : "审核不通过,退回区县修改补充材料" }; _sqlSugar.Insertable(summaryWorkitem).ExecuteCommand(); // 步骤5:更新流程实例最终状态 flowInstance.CurrentNodeId = targetNode.NodeId; flowInstance.Status = isAllPass ? "Completed" : "Rejected"; flowInstance.FinishTime = isAllPass ? DateTime.Now : null; _sqlSugar.Updateable(flowInstance).ExecuteCommand(); // 表单状态更新 var illegalConstructionAssessmentUpdate = new IllegalConstructionAssessment() { Id = flowInstance.BusinessNo, Status = isAllPass ? "Completed" : "Rejected" }; //_sqlSugar.Updateable(illegalConstructionAssessmentUpdate).IgnoreNullColumns().ExecuteCommand(); _sqlSugar.Updateable() .SetColumns(t => new IllegalConstructionAssessment() { Status = isAllPass ? "Completed" : "Rejected" }) .Where(t => t.Id == flowInstance.BusinessNo).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 bool SaveDraft(InitiateFlowRequestDto requestDto) { try { if (string.IsNullOrEmpty(requestDto.BusinessNo)) { var businessNo = _businessNoGenerator.GenerateBusinessNo(prefix); UnitWork.Db.Ado.BeginTran(); var user = _auth.GetCurrentUser().User; var illegalConstructionAssessment = new IllegalConstructionAssessment { Id = businessNo, Title = requestDto.Title, BusinessNumber = businessNo, AcceptanceTime = DateTime.Now, Status = "Draft", Type = requestDto.Type, CreateTime = DateTime.Now, CreateUser = user.Name, UpdateTime = DateTime.Now, CreateUserId = user.Id }; if (!requestDto.Attachments.IsEmpty()) { var attachmentPaths = string.Join(",", requestDto.Attachments); illegalConstructionAssessment.Attachments = attachmentPaths; } _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 = 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(); return true; } else { UnitWork.Db.Ado.BeginTran(); var illegalConstructionAssessment = _sqlSugar.Queryable() .Where(i => i.Id == requestDto.BusinessNo) .First(); if (illegalConstructionAssessment == null) { throw new Exception($"该拟办不存在"); } var illegalConstructionAssessmentUpdate = new IllegalConstructionAssessment { Id = requestDto.BusinessNo, Title = requestDto.Title, AcceptanceTime = DateTime.Now, //Status = "Draft", Type = requestDto.Type, UpdateTime = DateTime.Now, }; if (!requestDto.Attachments.IsEmpty()) { var attachmentPaths = string.Join(",", requestDto.Attachments); illegalConstructionAssessment.Attachments = attachmentPaths; } _sqlSugar.Updateable(illegalConstructionAssessmentUpdate).IgnoreNullColumns().ExecuteCommand(); return true; } } catch (Exception ex) { UnitWork.Db.Ado.RollbackTran(); throw; } finally { UnitWork.Db.Ado.CommitTran(); } } public dynamic Detail(string businessNo) { var instance = _sqlSugar .Queryable() .Where(i => i.BusinessNo == businessNo) .First(); if (instance == null) { throw new Exception($"流程实例不存在"); } var workItems = _sqlSugar .Queryable() .LeftJoin((w, a) => w.InstanceId == a.InstanceId && w.HandlerId == a.AuditorId) .LeftJoin((w, a, n) => w.NodeId == n.NodeId) .Where(w => w.InstanceId == instance.InstanceId) .OrderByDescending(w => w.WorkitemId) .Select((w, a, n) => new ZyFlowWorkitem { WorkitemId = w.WorkitemId, Comment = w.Comment, AuditComment = a.AuditComment, // 并行节点审核意见 AuditResult = a.AuditResult, Status = w.Status, ReceiveTime = w.ReceiveTime, HandleTime = w.HandleTime, HandlerName = w.HandlerName, HandlerId = w.HandlerId, NodeName = w.NodeName, NodeType = n.NodeType }) .ToList(); var illegalConstructionAssessment = _sqlSugar .Queryable() .Where(i => i.Id == businessNo) .First(); illegalConstructionAssessment.Workitems = workItems; return illegalConstructionAssessment; } public dynamic GetUserInfoWithDept(long workitemId) { // todo 查询下一节点绑定的角色,获取角色下所有用户及部门信息 var workitem = _sqlSugar .Queryable() .Where(w => w.WorkitemId == workitemId) .First(); if (workitem == null) { throw new Exception($"工作项不存在"); } var node = _sqlSugar .Queryable() .Where(n => n.NodeId == workitem.NodeId) .First(); var nodeStr = node.NextNodeIds; var firstNode = long.Parse(nodeStr.Split(",")[0]); var nextNode = _sqlSugar .Queryable() .Where(n => n.NodeId == firstNode) .First(); var roleId = nextNode.RoleId; // var (userId, userName, deptName) = GetRoleFirstUserInfo(roleId); var usrInfo = _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 == roleId) .Select((u, ur, uo, o) => new { UserId = u.Id, UserName = u.Name, Dept = o.Name }).ToList(); return usrInfo; } public dynamic ListSupervisionOrg() { // 监督 // 取得roleId // 取得角色下的用户及部门信息,部门信息去重 var orgs = _sqlSugar .Queryable() .LeftJoin((o, uo) => o.Id == uo.OrgId) .LeftJoin((o, uo, ur) => uo.UserId == ur.UserId) .LeftJoin((o, uo, ur, r) => ur.RoleId == r.Id) .Where((o, uo, ur, r) => r.Name == "审核") .Select((o, uo, ur, r) => new SysOrg() { Name = o.Name, Id = o.Id }) .ToList(); return orgs; } }