using Microsoft.AspNetCore.Http; using OpenAuth.App.BaseApp.Base; using OpenAuth.App.Interface; using OpenAuth.Repository; using OpenAuth.Repository.Domain; using SqlSugar; namespace workflow; public class WorkflowEngineApp : SqlSugarBaseApp { private readonly ISqlSugarClient _sqlSugar; public WorkflowEngineApp(ISugarUnitOfWork unitWork, ISimpleClient repository, IAuth auth) : base(unitWork, repository, auth) { _sqlSugar = Repository.AsSugarClient(); } #region 1. 发起流程(完整实现,事务保障) public long InitiateFlow(long userId, string userName, InitiateFlowRequest request) { if (string.IsNullOrEmpty(request.FlowCode) || string.IsNullOrEmpty(request.BusinessNo)) throw new Exception("流程编码和业务编号不能为空"); var instanceId = 0L; try { // 1. 查询流程模板 var template = _sqlSugar.Queryable() .Where(t => t.FlowCode == request.FlowCode && t.IsEnabled) .First(); if (template == null) throw new Exception("流程模板不存在或未启用"); // 2. 查询开始节点 var startNode = _sqlSugar.Queryable() .Where(n => n.TemplateId == template.TemplateId && n.NodeType == "Start") .First(); if (startNode == null) throw new Exception("流程开始节点不存在"); // 3. 插入流程实例(返回自增主键) var instance = new FlowInstance { TemplateId = template.TemplateId, FlowCode = template.FlowCode, BusinessNo = request.BusinessNo, Status = "Submitted", CurrentNodeId = startNode.NodeId, InitiatorId = userId, InitiatorName = userName, CreateTime = DateTime.Now }; instanceId = _sqlSugar.Insertable(instance).ExecuteReturnIdentity(); // 4. 插入开始节点工作项(直接标记为已完成) var workitem = new FlowWorkitem { InstanceId = instanceId, NodeId = startNode.NodeId, NodeName = startNode.NodeName, HandlerId = userId, HandlerName = userName, Status = "Done", ReceiveTime = DateTime.Now, HandleTime = DateTime.Now, Comment = "流程发起成功" }; _sqlSugar.Insertable(workitem).ExecuteCommand(); // 5. 保存流程变量(含附件) var attachments = SaveAttachments(request.Attachments); var variables = new List { new FlowVariable { InstanceId = instanceId, VarKey = "Title", VarValue = request.Title }, new FlowVariable { InstanceId = instanceId, VarKey = "BusinessType", VarValue = request.BusinessType }, new FlowVariable { InstanceId = instanceId, VarKey = "AttachmentPaths", VarValue = attachments }, new FlowVariable { InstanceId = instanceId, VarKey = "AcceptTime", VarValue = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") } }; _sqlSugar.Insertable(variables).ExecuteCommand(); // 6. 流转到下一个节点(区县→市局) FlowToNextNode(instanceId, startNode, userId, userName, "流程发起成功"); _sqlSugar.Ado.CommitTran(); } catch (Exception ex) { _sqlSugar.Ado.RollbackTran(); throw new Exception("发起流程失败:" + ex.Message); } return instanceId; } #endregion #region 2. 处理工作项(中转/审核/汇总/退回,完整实现) public bool HandleWorkitem(long userId, string userName, HandleWorkitemRequest request) { if (request.WorkitemId <= 0) throw new Exception("工作项ID无效"); _sqlSugar.Ado.BeginTran(); try { // 1. 查询工作项(仅待办可处理) var workitem = _sqlSugar.Queryable() .Where(w => w.WorkitemId == request.WorkitemId && w.Status == "ToDo") .First(); if (workitem == null) throw new Exception("工作项不存在或已处理"); // 2. 查询流程实例和当前节点 var instance = _sqlSugar.Queryable() .Where(i => i.InstanceId == workitem.InstanceId) .First(); var currentNode = _sqlSugar.Queryable() .Where(n => n.NodeId == workitem.NodeId) .First(); if (instance == null || currentNode == null) throw new Exception("流程实例或节点不存在"); // 3. 更新工作项为已完成 workitem.Status = "Done"; workitem.HandleTime = DateTime.Now; workitem.Comment = request.Comment ?? "处理完成"; _sqlSugar.Updateable(workitem).ExecuteCommand(); // 4. 并行审核节点特殊处理(保存审核结果) if (currentNode.NodeType == "Parallel") { if (string.IsNullOrEmpty(request.AuditResult) || string.IsNullOrEmpty(request.DeptName)) throw new Exception("并行审核需提供审核结果和科室名称"); var parallelAudit = new FlowParallelAudit { InstanceId = instance.InstanceId, NodeId = currentNode.NodeId, DeptName = request.DeptName, AuditResult = request.AuditResult, AuditComment = request.Comment, AuditorId = userId, AuditorName = userName, AuditTime = DateTime.Now }; _sqlSugar.Insertable(parallelAudit).ExecuteCommand(); // 5. 判断是否全部审核完成,更新流程状态 if (SummarizeParallelAudit(instance.InstanceId)) { instance.Status = "Summarized"; _sqlSugar.Updateable(instance).ExecuteCommand(); } } // 6. 流转到下一个节点(非结束节点) if (currentNode.NodeType != "End") { FlowToNextNode(instance.InstanceId, currentNode, userId, userName, workitem.Comment); } else { // 结束节点:标记流程完成 instance.Status = "Completed"; instance.FinishTime = DateTime.Now; instance.CurrentNodeId = currentNode.NodeId; _sqlSugar.Updateable(instance).ExecuteCommand(); } _sqlSugar.Ado.CommitTran(); return true; } catch (Exception ex) { _sqlSugar.Ado.RollbackTran(); throw new Exception("处理工作项失败:" + ex.Message); } } #endregion #region 3. 汇总并行审核结果(判断是否全部通过) public bool SummarizeParallelAudit(long instanceId) { if (instanceId <= 0) return false; // 1. 查询该流程所有并行审核结果 var auditResults = _sqlSugar.Queryable() .Where(a => a.InstanceId == instanceId) .ToList(); if (!auditResults.Any()) return false; // 2. 查询并行节点对应的角色用户数(判断是否全部审核) var parallelNode = _sqlSugar.Queryable() .Where(n => n.NodeId == auditResults.First().NodeId && n.NodeType == "Parallel") .First(); var sql = $"select u.* from sys_user u left join sys_userrole r on u.\"Id\" = r.\"UserId\" where r.\"RoleId\" = {parallelNode.RoleId}"; var roleUserCount = _sqlSugar.SqlQueryable(sql).Count(); // 3. 全部用户审核完成,且无驳回,返回true return auditResults.Count == roleUserCount && !auditResults.Any(a => a.AuditResult == "Reject"); } #endregion #region 4. 流程查询(我的待办/我的已办,完整实现) public List QueryMyToDo(long userId) { // 1. 查询用户待办工作项 var toDoWorkitems = _sqlSugar.Queryable() .Where(w => w.HandlerId == userId && w.Status == "ToDo") .ToList(); // 2. 组装返回结果 var result = new List(); foreach (var workitem in toDoWorkitems) { var instance = _sqlSugar.Queryable() .Where(i => i.InstanceId == workitem.InstanceId) .First(); if (instance == null) continue; var title = _sqlSugar.Queryable() // InstanceId .Where(v => v.InstanceId == instance.InstanceId && v.VarKey == "Title") .First()?.VarValue ?? string.Empty; result.Add(new FlowQueryResult { InstanceId = instance.InstanceId, BusinessNo = instance.BusinessNo, Title = title, NodeName = workitem.NodeName, Status = instance.Status, CreateTime = instance.CreateTime, InitiatorName = instance.InitiatorName }); } return result; } public List QueryMyDone(long userId) { // 1. 查询用户已办工作项(去重流程实例) var doneInstanceIds = _sqlSugar.Queryable() .Where(w => w.HandlerId == userId && w.Status == "Done") .Select(w => w.InstanceId) .Distinct() .ToList(); // 2. 组装返回结果 var result = new List(); foreach (var instanceId in doneInstanceIds) { var instance = _sqlSugar.Queryable() .Where(i => i.InstanceId == instanceId) .First(); if (instance == null) continue; var title = _sqlSugar.Queryable() .Where(v => v.InstanceId == instance.InstanceId && v.VarKey == "Title") .First()?.VarValue ?? string.Empty; var lastWorkitem = _sqlSugar.Queryable() .Where(w => w.InstanceId == instanceId && w.Status == "Done") .OrderByDescending(w => w.HandleTime) .First(); result.Add(new FlowQueryResult { InstanceId = instance.InstanceId, BusinessNo = instance.BusinessNo, Title = title, NodeName = lastWorkitem?.NodeName ?? string.Empty, Status = instance.Status, CreateTime = instance.CreateTime, InitiatorName = instance.InitiatorName }); } return result; } #endregion #region 5. 辅助方法(流转节点/保存附件,完整实现) public FlowInstance GetFlowInstanceDetail(long instanceId) { return _sqlSugar.Queryable() .Where(i => i.InstanceId == instanceId) .First(); } /// /// 流转到下一个节点(核心流转逻辑) /// private void FlowToNextNode(long instanceId, FlowNode currentNode, long userId, string userName, string comment) { var nextNodeIds = currentNode.NextNodeIdList; if (!nextNodeIds.Any()) return; var instance = _sqlSugar.Queryable() .Where(i => i.InstanceId == instanceId) .First(); if (instance == null) throw new Exception("流程实例不存在"); foreach (var nextNodeId in nextNodeIds) { var nextNode = _sqlSugar.Queryable() .Where(n => n.NodeId == nextNodeId) .First(); if (nextNode == null) continue; // 1. 更新流程实例当前节点和状态 instance.CurrentNodeId = nextNode.NodeId; instance.Status = GetFlowStatusByNodeType(nextNode.NodeType); _sqlSugar.Updateable(instance).ExecuteCommand(); // 2. 创建下一步工作项 var nextWorkitem = new FlowWorkitem { InstanceId = instanceId, NodeId = nextNode.NodeId, NodeName = nextNode.NodeName, HandlerId = nextNode.RoleId == 0 ? userId : GetRoleFirstUserId(nextNode.RoleId), HandlerName = nextNode.RoleId == 0 ? userName : GetRoleFirstUserName(nextNode.RoleId), Status = "ToDo", ReceiveTime = DateTime.Now, Comment = comment }; _sqlSugar.Insertable(nextWorkitem).ExecuteCommand(); // 3. 分支节点特殊处理(自动判断,归档/退回) if (nextNode.NodeType == "Branch") { var isAllPass = SummarizeParallelAudit(instanceId); var targetNodeIds = nextNode.NextNodeIdList; if (targetNodeIds.Count < 2) throw new Exception("分支节点需配置2个下一步节点"); var targetNodeId = isAllPass ? targetNodeIds[0] : targetNodeIds[1]; var targetNode = _sqlSugar.Queryable() .Where(n => n.NodeId == targetNodeId) .First(); if (targetNode == null) continue; // 创建分支节点工作项 var branchWorkitem = new FlowWorkitem { InstanceId = instanceId, NodeId = targetNode.NodeId, NodeName = targetNode.NodeName, HandlerId = userId, HandlerName = userName, Status = targetNode.NodeType == "End" ? "Done" : "ToDo", ReceiveTime = DateTime.Now, HandleTime = targetNode.NodeType == "End" ? DateTime.Now : null, Comment = isAllPass ? "全部审核通过,流程归档" : "部分审核不通过,退回区县修改" }; _sqlSugar.Insertable(branchWorkitem).ExecuteCommand(); // 更新流程最终状态 instance.CurrentNodeId = targetNode.NodeId; instance.Status = targetNode.NodeType == "End" ? "Completed" : "Rejected"; instance.FinishTime = targetNode.NodeType == "End" ? DateTime.Now : null; _sqlSugar.Updateable(instance).ExecuteCommand(); } } } /// /// 根据节点类型获取流程状态 /// private string GetFlowStatusByNodeType(string nodeType) { return nodeType switch { "Common" => "Forwarded", "Parallel" => "Auditing", "Branch" => "Summarized", "End" => "Completed", _ => "Submitted" }; } /// /// 保存附件(简化实现,自动创建目录) /// private string SaveAttachments(List attachments) { if (attachments == null || !attachments.Any()) return string.Empty; var attachmentPaths = new List(); var uploadDir = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/Attachments"); // 创建附件目录(不存在则创建) if (!Directory.Exists(uploadDir)) Directory.CreateDirectory(uploadDir); 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/{fileName}"); } return string.Join(",", attachmentPaths); } /// /// 获取角色下第一个用户ID /// private long GetRoleFirstUserId(long roleId) { var sql = $"select u.* from sys_user u left join sys_userrole r on u.\"Id\" = r.\"UserId\" where r.\"RoleId\" = {roleId}"; return _sqlSugar.SqlQueryable(sql).First().Id; } /// /// 获取角色下第一个用户名称 /// private string GetRoleFirstUserName(long roleId) { var sql = $"select u.* from sys_user u left join sys_userrole r on u.\"Id\" = r.\"UserId\" where r.\"RoleId\" = {roleId}"; var user = _sqlSugar.SqlQueryable(sql).First(); return user?.Name ?? string.Empty; } #endregion #region 新增:个人维度 - 拟办查询与认领 public List QueryMyDraft(long userId) { // 1. 先获取当前用户的所有角色ID var userRoleIds = _sqlSugar.Queryable() .Where(ur => ur.UserId == userId) .Select(ur => ur.RoleId) .ToList(); if (!userRoleIds.Any()) return new List(); // 2. 查询当前用户角色对应的拟办工作项(HandlerId为null,状态为ToDo) var draftWorkitems = _sqlSugar.Queryable((w, n) => new JoinQueryInfos( JoinType.Inner, w.NodeId == n.NodeId)) .Where((w, n) => w.HandlerId == null && w.Status == "ToDo" && userRoleIds.Contains(n.RoleId)) .Select((w, n) => w) .ToList(); // 3. 组装返回结果 var result = new List(); foreach (var workitem in draftWorkitems) { var instance = _sqlSugar.Queryable() .Where(i => i.InstanceId == workitem.InstanceId) .First(); if (instance == null) continue; var title = _sqlSugar.Queryable() .Where(v => v.InstanceId == instance.InstanceId && v.VarKey == "Title") .First()?.VarValue ?? string.Empty; result.Add(new FlowQueryResult { InstanceId = instance.InstanceId, BusinessNo = instance.BusinessNo, Title = title, NodeName = workitem.NodeName, Status = instance.Status, CreateTime = instance.CreateTime, InitiatorName = instance.InitiatorName }); } return result; } public bool ClaimDraftWorkitem(long workitemId, long userId, string userName) { if (workitemId <= 0 || userId <= 0) throw new Exception("工作项ID和用户ID无效"); _sqlSugar.Ado.BeginTran(); try { // 1. 查询拟办工作项(必须是未分配、待办状态) var workitem = _sqlSugar.Queryable() .Where(w => w.WorkitemId == workitemId && w.HandlerId == null && w.Status == "ToDo") .First(); if (workitem == null) throw new Exception("拟办工作项不存在或已被认领"); // 2. 更新工作项,分配给当前用户(转为待办) workitem.HandlerId = userId; workitem.HandlerName = userName; _sqlSugar.Updateable(workitem).ExecuteCommand(); _sqlSugar.Ado.CommitTran(); return true; } catch (Exception ex) { _sqlSugar.Ado.RollbackTran(); throw new Exception("认领拟办工作项失败:" + ex.Message); } } #endregion #region 新增:流程维度 - 未办结/已完成查询 public List QueryUnfinishedFlows(long userId) { // 1. 查询当前用户有权限查看的未办结流程(Status != Completed) // 简化:此处查询所有未办结,实际可按用户角色/发起人过滤 var unfinishedInstances = _sqlSugar.Queryable((i, t) => new JoinQueryInfos( JoinType.Inner, i.TemplateId == t.TemplateId)) .Where((i, t) => i.Status != "Completed") .Select((i, t) => new FlowInstanceQueryResult { InstanceId = i.InstanceId, BusinessNo = i.BusinessNo, FlowName = t.FlowName, Status = i.Status, InitiatorName = i.InitiatorName, CreateTime = i.CreateTime, FinishTime = i.FinishTime }) .ToList(); // 2. 补充标题和当前节点名称 foreach (var instance in unfinishedInstances) { // 补充标题 instance.Title = _sqlSugar.Queryable() .Where(v => v.InstanceId == instance.InstanceId && v.VarKey == "Title") .First()?.VarValue ?? string.Empty; // 补充当前节点名称 instance.CurrentNodeName = _sqlSugar.Queryable() .Where(n => n.NodeId == _sqlSugar.Queryable() .Where(i => i.InstanceId == instance.InstanceId) .Select(i => i.CurrentNodeId) .First()) .First()?.NodeName ?? string.Empty; } return unfinishedInstances; } public List QueryCompletedFlows(long userId) { // 1. 查询当前用户有权限查看的已完成流程(Status = Completed) var completedInstances = _sqlSugar.Queryable((i, t) => new JoinQueryInfos( JoinType.Inner, i.TemplateId == t.TemplateId)) .Where((i, t) => i.Status == "Completed") .Select((i, t) => new FlowInstanceQueryResult { InstanceId = i.InstanceId, BusinessNo = i.BusinessNo, FlowName = t.FlowName, Status = i.Status, InitiatorName = i.InitiatorName, CreateTime = i.CreateTime, FinishTime = i.FinishTime }) .ToList(); // 2. 补充标题和当前节点名称 foreach (var instance in completedInstances) { instance.Title = _sqlSugar.Queryable() .Where(v => v.InstanceId == instance.InstanceId && v.VarKey == "Title") .First()?.VarValue ?? string.Empty; instance.CurrentNodeName = _sqlSugar.Queryable() .Where(n => n.NodeId == _sqlSugar.Queryable() .Where(i => i.InstanceId == instance.InstanceId) .Select(i => i.CurrentNodeId) .First()) .First()?.NodeName ?? string.Empty; } return completedInstances; } #endregion }