diff --git a/OpenAuth.App/WorkflowEngineApp.cs b/OpenAuth.App/WorkflowEngineApp.cs new file mode 100644 index 0000000..5d1b5b7 --- /dev/null +++ b/OpenAuth.App/WorkflowEngineApp.cs @@ -0,0 +1,636 @@ +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 +} \ No newline at end of file diff --git a/OpenAuth.Repository/Domain/workflow/ZyWorkflow.cs b/OpenAuth.Repository/Domain/workflow/ZyWorkflow.cs new file mode 100644 index 0000000..22d96da --- /dev/null +++ b/OpenAuth.Repository/Domain/workflow/ZyWorkflow.cs @@ -0,0 +1,245 @@ +using Microsoft.AspNetCore.Http; +using SqlSugar; + +namespace workflow; + +#region 流程核心实体(映射 zy_ 前缀表) + +[SugarTable("zy_flow_template", TableDescription = "流程模板表")] +public class FlowTemplate +{ + [SugarColumn(IsPrimaryKey = true, ColumnName = "template_id", IsIdentity = true, ColumnDescription = "模板主键ID")] + public long TemplateId { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "flow_name", ColumnDescription = "流程名称")] + public string FlowName { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "flow_code", ColumnDescription = "流程编码")] + public string FlowCode { get; set; } + + [SugarColumn(DefaultValue = "true", ColumnName = "is_enabled", ColumnDescription = "是否启用")] + public bool IsEnabled { get; set; } + + [SugarColumn(DefaultValue = "CURRENT_TIMESTAMP", ColumnName = "create_time", ColumnDescription = "创建时间")] + public DateTime CreateTime { get; set; } + + [SugarColumn(IsNullable = true, ColumnName = "remark", ColumnDescription = "备注信息")] + public string Remark { get; set; } +} + +[SugarTable("zy_flow_node", TableDescription = "流程节点表")] +public class FlowNode +{ + [SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnName = "node_id", ColumnDescription = "节点主键ID")] + public long NodeId { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "template_id", ColumnDescription = "关联模板ID")] + public long TemplateId { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "node_name", ColumnDescription = "节点名称")] + public string NodeName { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "node_type", + ColumnDescription = "节点类型:Start/Common/Parallel/Branch/End")] + public string NodeType { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "role_id", ColumnDescription = "关联角色ID")] + public long RoleId { get; set; } + + [SugarColumn(IsNullable = true, ColumnName = "next_node_ids", ColumnDescription = "下一步节点ID列表(逗号分隔)")] + public string NextNodeIds { get; set; } + + [SugarColumn(DefaultValue = "true", ColumnName = "is_backable", ColumnDescription = "是否可退回")] + public bool IsBackable { get; set; } + + [SugarColumn(DefaultValue = "0", ColumnName = "sort_no", ColumnDescription = "节点排序号")] + public int SortNo { get; set; } + + [SugarColumn(IsIgnore = true)] + public List NextNodeIdList => string.IsNullOrEmpty(NextNodeIds) + ? new List() + : NextNodeIds.Split(',').Select(long.Parse).ToList(); +} + +[SugarTable("zy_flow_instance", TableDescription = "流程实例表")] +public class FlowInstance +{ + [SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnName = "instance_id", ColumnDescription = "实例主键ID")] + public long InstanceId { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "template_id", ColumnDescription = "关联模板ID")] + public long TemplateId { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "flow_code", ColumnDescription = "流程编码")] + public string FlowCode { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "business_no", ColumnDescription = "业务编号")] + public string BusinessNo { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "status", + ColumnDescription = "流程状态:Submitted/Forwarded/Auditing/Summarized/Completed/Rejected")] + public string Status { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "current_node_id", ColumnDescription = "当前节点ID")] + public long CurrentNodeId { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "initiator_id", ColumnDescription = "发起人用户ID")] + public long InitiatorId { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "initiator_name", ColumnDescription = "发起人名称")] + public string InitiatorName { get; set; } + + [SugarColumn(DefaultValue = "CURRENT_TIMESTAMP", ColumnName = "create_time", ColumnDescription = "发起时间")] + public DateTime CreateTime { get; set; } + + [SugarColumn(IsNullable = true, ColumnName = "finish_time", ColumnDescription = "流程结束时间")] + public DateTime? FinishTime { get; set; } +} + +[SugarTable("zy_flow_workitem", TableDescription = "流程工作项表")] +public class FlowWorkitem +{ + [SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnName = "workitem_id", ColumnDescription = "工作项主键ID")] + public long WorkitemId { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "instance_id", ColumnDescription = "关联实例ID")] + public long InstanceId { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "node_id", ColumnDescription = "关联节点ID")] + public long NodeId { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "node_name", ColumnDescription = "节点名称(冗余)")] + public string NodeName { get; set; } + + [SugarColumn(IsNullable = true, ColumnName = "handler_id", ColumnDescription = "处理人用户ID")] + public long? HandlerId { get; set; } + + [SugarColumn(IsNullable = true, ColumnName = "handler_name", ColumnDescription = "处理人名称")] + public string HandlerName { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "status", ColumnDescription = "工作项状态:ToDo/Done/Rejected")] + public string Status { get; set; } + + [SugarColumn(DefaultValue = "CURRENT_TIMESTAMP", ColumnName = "receive_time", ColumnDescription = "接收时间")] + public DateTime ReceiveTime { get; set; } + + [SugarColumn(IsNullable = true, ColumnName = "handle_time", ColumnDescription = "处理时间")] + public DateTime? HandleTime { get; set; } + + [SugarColumn(IsNullable = true, ColumnName = "comment", ColumnDescription = "处理意见/备注")] + public string Comment { get; set; } +} + +[SugarTable("zy_flow_variable", TableDescription = "流程变量表")] +public class FlowVariable +{ + [SugarColumn(IsPrimaryKey = true, ColumnName = "var_id", IsIdentity = true, ColumnDescription = "变量主键ID")] + public long VarId { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "instance_id", ColumnDescription = "关联实例ID")] + public long InstanceId { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "var_key", ColumnDescription = "变量键")] + public string VarKey { get; set; } + + [SugarColumn(IsNullable = true, ColumnName = "var_value", ColumnDataType = "text", ColumnDescription = "变量值")] + public string VarValue { get; set; } +} + +[SugarTable("zy_flow_parallel_audit", TableDescription = "并行审核结果表")] +public class FlowParallelAudit +{ + [SugarColumn(IsPrimaryKey = true, IsIdentity = true, ColumnName = "audit_id", ColumnDescription = "审核结果主键ID")] + public long AuditId { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "instance_id", ColumnDescription = "关联实例ID")] + public long InstanceId { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "node_id", ColumnDescription = "关联节点ID")] + public long NodeId { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "dept_name", ColumnDescription = "审核科室名称")] + public string DeptName { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "audit_result", ColumnDescription = "审核结果:Pass/Reject")] + public string AuditResult { get; set; } + + [SugarColumn(IsNullable = true, ColumnName = "audit_comment", ColumnDescription = "审核意见")] + public string AuditComment { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "auditor_id", ColumnDescription = "审核人用户ID")] + public long AuditorId { get; set; } + + [SugarColumn(IsNullable = false, ColumnName = "auditor_name", ColumnDescription = "审核人名称")] + public string AuditorName { get; set; } + + [SugarColumn(DefaultValue = "CURRENT_TIMESTAMP", ColumnName = "audit_time", ColumnDescription = "审核时间")] + public DateTime AuditTime { get; set; } +} + +#endregion + +#region 前后端交互模型(无数据库映射) + +public class InitiateFlowRequest +{ + public string FlowCode { get; set; } + public string BusinessNo { get; set; } + public string Title { get; set; } + public string BusinessType { get; set; } + public List Attachments { get; set; } +} + +public class HandleWorkitemRequest +{ + public long WorkitemId { get; set; } + public string Comment { get; set; } + public string AuditResult { get; set; } + public string DeptName { get; set; } +} + +public class FlowQueryResult +{ + public long InstanceId { get; set; } + public string BusinessNo { get; set; } + public string Title { get; set; } + public string NodeName { get; set; } + public string Status { get; set; } + public DateTime CreateTime { get; set; } + public string InitiatorName { get; set; } +} + +public class FlowInstanceQueryResult +{ + public long InstanceId { get; set; } + public string BusinessNo { get; set; } + public string Title { get; set; } + public string FlowName { get; set; } + public string CurrentNodeName { get; set; } + public string Status { get; set; } + public string InitiatorName { get; set; } + public DateTime CreateTime { get; set; } + public DateTime? FinishTime { get; set; } +} + +public class AssessmentFlowUnionResult +{ + // 表单字段 + public string FormId { get; set; } + public string Title { get; set; } + public string BusinessNumber { get; set; } + public string Type { get; set; } + public string Attachments { get; set; } + public DateTime? AcceptanceTime { get; set; } + + // 流程字段 + public long FlowInstanceId { get; set; } + public string FlowName { get; set; } + public string CurrentNodeName { get; set; } + public string FlowStatus { get; set; } + public string InitiatorName { get; set; } + public DateTime FlowCreateTime { get; set; } + public DateTime? FlowFinishTime { get; set; } +} + +#endregion \ No newline at end of file diff --git a/OpenAuth.WebApi/Controllers/ServerController/ApiResult.cs b/OpenAuth.WebApi/Controllers/ServerController/ApiResult.cs new file mode 100644 index 0000000..db0caf1 --- /dev/null +++ b/OpenAuth.WebApi/Controllers/ServerController/ApiResult.cs @@ -0,0 +1,51 @@ +namespace OpenAuth.WebApi.Controllers.ServerController; + +/// +/// 统一API返回结果 +/// +/// +public class ApiResult +{ + public bool Success { get; set; } + public string Message { get; set; } + public T Data { get; set; } + public int TotalCount { get; set; } // 分页总条数 + + public static ApiResult Ok(T data, int totalCount = 0, string message = "操作成功") + { + return new ApiResult + { + Success = true, + Message = message, + Data = data, + TotalCount = totalCount + }; + } + + public static ApiResult Error(string message = "操作失败") + { + return new ApiResult + { + Success = false, + Message = message, + Data = default(T), + TotalCount = 0 + }; + } +} + +/// +/// 分页请求基类 +/// +public class PageRequest +{ + /// + /// 页码(默认第1页) + /// + public int PageIndex { get; set; } = 1; + + /// + /// 每页条数(默认10条) + /// + public int PageSize { get; set; } = 10; +} \ No newline at end of file diff --git a/OpenAuth.WebApi/Controllers/ServerController/CustomWorkflowController.cs b/OpenAuth.WebApi/Controllers/ServerController/CustomWorkflowController.cs new file mode 100644 index 0000000..c867b1a --- /dev/null +++ b/OpenAuth.WebApi/Controllers/ServerController/CustomWorkflowController.cs @@ -0,0 +1,197 @@ +using Microsoft.AspNetCore.Mvc; +using OpenAuth.App.Interface; +using workflow; + +namespace OpenAuth.WebApi.Controllers.ServerController; + +[ApiController] +[Route("api/[controller]")] +public class CustomWorkflowController : ControllerBase +{ + private readonly WorkflowEngineApp _workflowEngineApp; + private readonly IAuth _auth; + + public CustomWorkflowController(WorkflowEngineApp workflowEngineApp, IAuth auth) + { + _workflowEngineApp = workflowEngineApp; + _auth = auth; + } + + /// + /// 发起违法建设认定流程 + /// + [HttpPost("initiate")] + public IActionResult InitiateFlow([FromForm] InitiateFlowRequest request) + { + try + { + // 1. 获取当前用户信息 + var user = _auth.GetCurrentUser().User; + if (!long.TryParse(user.Id.ToString(), out long currentUserId)) + return Ok(ApiResult.Error("用户ID格式错误")); + + // 2. 调用工作流服务发起流程 + var instanceId = _workflowEngineApp.InitiateFlow(currentUserId, user.Name, request); + + return Ok(ApiResult.Ok(instanceId, message: "流程发起成功")); + } + catch (Exception ex) + { + return Ok(ApiResult.Error(ex.Message)); + } + } + + /// + /// 处理工作项(中转、审核、汇总) + /// + [HttpPost("handle-workitem")] + public IActionResult HandleWorkitem([FromBody] HandleWorkitemRequest request) + { + try + { + var user = _auth.GetCurrentUser().User; + // 1. 获取当前用户信息 + var userIdStr = user.Id.ToString(); + var userName = user.Name; + if (!long.TryParse(userIdStr, out long currentUserId)) + return Ok(ApiResult.Error("用户ID格式错误")); + + // 2. 调用工作流服务处理工作项 + var result = _workflowEngineApp.HandleWorkitem(currentUserId, userName, request); + + return Ok(ApiResult.Ok(result, message: "工作项处理成功")); + } + catch (Exception ex) + { + return Ok(ApiResult.Error(ex.Message)); + } + } + + // 拟办、待办、已办、未办结、已完成、项目列表 + // // ToDo/Done/Rejected + /// + /// 查询我的待办 + /// + [HttpGet("my-todo")] + public IActionResult QueryMyToDo() + { + try + { + var user = _auth.GetCurrentUser().User; + var userIdStr = user.Id.ToString(); + if (!long.TryParse(userIdStr, out long currentUserId)) + return Ok(ApiResult>.Error("用户ID格式错误")); + var result = _workflowEngineApp.QueryMyToDo(currentUserId); + return Ok(ApiResult>.Ok(result, result.Count)); + } + catch (Exception ex) + { + return Ok(ApiResult>.Error(ex.Message)); + } + } + + /// + /// 查询我的已办 + /// + [HttpGet("my-done")] + public IActionResult QueryMyDone() + { + try + { + var user = _auth.GetCurrentUser().User; + var userIdStr = user.Id.ToString(); + if (!long.TryParse(userIdStr, out long currentUserId)) + return Ok(ApiResult>.Error("用户ID格式错误")); + var result = _workflowEngineApp.QueryMyDone(currentUserId); + + return Ok(ApiResult>.Ok(result, result.Count)); + } + catch (Exception ex) + { + return Ok(ApiResult>.Error(ex.Message)); + } + } + + #region 新增:拟办/未办结/已完成 API 接口 + + /// + /// 查询我的拟办(需认领) + /// + [HttpGet("my-draft")] + public IActionResult QueryMyDraft() + { + try + { + var user = _auth.GetCurrentUser().User; + var result = _workflowEngineApp.QueryMyDraft(user.Id); + + return Ok(new { Code = 200, Msg = "查询拟办成功", Data = result, Total = result.Count }); + } + catch (Exception ex) + { + return Ok(new { Code = 500, Msg = ex.Message }); + } + } + + /// + /// 认领拟办工作项 + /// + [HttpPost("claim-draft")] + public IActionResult ClaimDraft([FromBody] long workitemId) + { + try + { + var user = _auth.GetCurrentUser().User; + var userId = user.Id; + var userName = user.Name; + var result = _workflowEngineApp.ClaimDraftWorkitem(workitemId, userId, userName); + return Ok(new { Code = 200, Msg = "认领拟办成功", Data = result }); + } + catch (Exception ex) + { + return Ok(new { Code = 500, Msg = ex.Message }); + } + } + + /// + /// 查询所有未办结流程 + /// + [HttpGet("unfinished-flows")] + public IActionResult QueryUnfinishedFlows() + { + try + { + var user = _auth.GetCurrentUser().User; + var userId = user.Id; + var result = _workflowEngineApp.QueryUnfinishedFlows(userId); + + return Ok(new { Code = 200, Msg = "查询未办结流程成功", Data = result, Total = result.Count }); + } + catch (Exception ex) + { + return Ok(new { Code = 500, Msg = ex.Message }); + } + } + + /// + /// 查询所有已完成流程 + /// + [HttpGet("completed-flows")] + public IActionResult QueryCompletedFlows() + { + try + { + var user = _auth.GetCurrentUser().User; + var userId = user.Id; + var result = _workflowEngineApp.QueryCompletedFlows(userId); + + return Ok(new { Code = 200, Msg = "查询已完成流程成功", Data = result, Total = result.Count }); + } + catch (Exception ex) + { + return Ok(new { Code = 500, Msg = ex.Message }); + } + } + + #endregion +} \ No newline at end of file diff --git a/OpenAuth.WebApi/OpenAuth.WebApi.csproj b/OpenAuth.WebApi/OpenAuth.WebApi.csproj index 817aa5c..d246182 100644 --- a/OpenAuth.WebApi/OpenAuth.WebApi.csproj +++ b/OpenAuth.WebApi/OpenAuth.WebApi.csproj @@ -25,6 +25,7 @@ + @@ -79,7 +80,7 @@ - +