2026-02-04 20:40:22 +08:00
|
|
|
|
using Microsoft.AspNetCore.Http;
|
|
|
|
|
|
using OpenAuth.App.BaseApp.Base;
|
|
|
|
|
|
using OpenAuth.App.Interface;
|
|
|
|
|
|
using OpenAuth.Repository;
|
|
|
|
|
|
using OpenAuth.Repository.Domain;
|
2026-02-04 21:43:24 +08:00
|
|
|
|
using OpenAuth.Repository.Domain.workflow;
|
2026-02-04 20:40:22 +08:00
|
|
|
|
using SqlSugar;
|
|
|
|
|
|
|
|
|
|
|
|
namespace workflow;
|
|
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
using Microsoft.AspNetCore.Http;
|
|
|
|
|
|
using OpenAuth.App.BaseApp.Base;
|
|
|
|
|
|
using OpenAuth.App.Interface;
|
|
|
|
|
|
using OpenAuth.Repository;
|
|
|
|
|
|
using OpenAuth.Repository.Domain;
|
|
|
|
|
|
using SqlSugar;
|
|
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 完整工作流引擎(适配:区县→执法监督科→5个审核科会签→汇总归档/退回)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class WorkflowEngineApp : SqlSugarBaseApp<SysCategoryType, SugarDbContext>
|
2026-02-04 20:40:22 +08:00
|
|
|
|
{
|
|
|
|
|
|
private readonly ISqlSugarClient _sqlSugar;
|
|
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 配置:5个审核科角色ID(需与sys_role表对应,自行修改为实际值)
|
|
|
|
|
|
private readonly List<long> _auditDeptRoleIds = new()
|
2026-02-04 20:40:22 +08:00
|
|
|
|
{
|
2026-02-05 09:08:32 +08:00
|
|
|
|
1, // 政策法规科
|
|
|
|
|
|
2, // 详细规划科
|
|
|
|
|
|
3, // 空间规划科
|
|
|
|
|
|
4, // 开发利用科
|
|
|
|
|
|
5 // 工程科
|
|
|
|
|
|
};
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 配置:执法监督科角色ID(需与sys_role表对应,自行修改为实际值)
|
|
|
|
|
|
private const long _supervisionDeptRoleId = 6;
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
public WorkflowEngineApp(ISugarUnitOfWork<SugarDbContext> unitWork, ISimpleClient<SysCategoryType> repository,
|
|
|
|
|
|
IAuth auth) : base(unitWork, repository, auth)
|
2026-02-04 20:40:22 +08:00
|
|
|
|
{
|
2026-02-05 09:08:32 +08:00
|
|
|
|
_sqlSugar = Repository.AsSugarClient();
|
2026-02-04 21:43:24 +08:00
|
|
|
|
}
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
#region 1. 发起流程(区县提交→流转至执法监督科)
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
public long InitiateFlow(long userId, string userName, InitiateFlowRequest request)
|
2026-02-04 21:43:24 +08:00
|
|
|
|
{
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 参数校验
|
|
|
|
|
|
if (string.IsNullOrEmpty(request.FlowCode) || string.IsNullOrEmpty(request.BusinessNo))
|
|
|
|
|
|
throw new Exception("流程编码和业务编号不能为空");
|
|
|
|
|
|
if (userId <= 0 || string.IsNullOrEmpty(userName))
|
|
|
|
|
|
throw new Exception("发起人ID和姓名不能为空");
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
var instanceId = 0L;
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 开启事务(基于UnitWork,保障数据一致性)
|
|
|
|
|
|
UnitWork.Db.Ado.BeginTran();
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 步骤1:查询启用的流程模板(违法建设认定流程)
|
|
|
|
|
|
var template = _sqlSugar.Queryable<ZyFlowTemplate>()
|
|
|
|
|
|
.Where(t => t.FlowCode == request.FlowCode && t.IsEnabled == true)
|
2026-02-04 20:40:22 +08:00
|
|
|
|
.First();
|
2026-02-05 09:08:32 +08:00
|
|
|
|
if (template == null)
|
|
|
|
|
|
throw new Exception($"流程模板【{request.FlowCode}】不存在或未启用");
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 步骤2:查询流程开始节点(区县提交)
|
|
|
|
|
|
var startNode = _sqlSugar.Queryable<ZyFlowNode>()
|
|
|
|
|
|
.Where(n => n.TemplateId == template.TemplateId && n.NodeType == "Start" && n.NodeName == "区县提交")
|
|
|
|
|
|
.First();
|
|
|
|
|
|
if (startNode == null)
|
|
|
|
|
|
throw new Exception("流程开始节点【区县提交】不存在,请配置节点");
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 步骤3:插入流程实例,返回自增主键
|
|
|
|
|
|
var flowInstance = new ZyFlowInstance
|
2026-02-04 21:43:24 +08:00
|
|
|
|
{
|
2026-02-05 09:08:32 +08:00
|
|
|
|
TemplateId = template.TemplateId,
|
|
|
|
|
|
FlowCode = template.FlowCode,
|
|
|
|
|
|
BusinessNo = request.BusinessNo,
|
|
|
|
|
|
Status = "Submitted", // 已提交
|
|
|
|
|
|
CurrentNodeId = startNode.NodeId,
|
2026-02-04 21:43:24 +08:00
|
|
|
|
InitiatorId = userId,
|
|
|
|
|
|
InitiatorName = userName,
|
|
|
|
|
|
CreateTime = DateTime.Now
|
|
|
|
|
|
};
|
|
|
|
|
|
instanceId = _sqlSugar.Insertable(flowInstance).ExecuteReturnIdentity();
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 步骤4:插入开始节点工作项(直接标记为已完成)
|
|
|
|
|
|
var startWorkitem = new ZyFlowWorkitem
|
2026-02-04 21:43:24 +08:00
|
|
|
|
{
|
|
|
|
|
|
InstanceId = instanceId,
|
2026-02-05 09:08:32 +08:00
|
|
|
|
NodeId = startNode.NodeId,
|
|
|
|
|
|
NodeName = startNode.NodeName,
|
2026-02-04 21:43:24 +08:00
|
|
|
|
HandlerId = userId,
|
|
|
|
|
|
HandlerName = userName,
|
2026-02-05 09:08:32 +08:00
|
|
|
|
Status = "Done", // 已完成
|
|
|
|
|
|
ReceiveTime = DateTime.Now,
|
|
|
|
|
|
HandleTime = DateTime.Now,
|
|
|
|
|
|
Comment = "区县提交业务材料,流程发起成功"
|
|
|
|
|
|
};
|
|
|
|
|
|
_sqlSugar.Insertable(startWorkitem).ExecuteCommand();
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤5:保存流程变量(标题、附件路径等)
|
|
|
|
|
|
var attachmentPaths = SaveAttachments(request.Attachments);
|
|
|
|
|
|
var flowVariables = new List<ZyFlowVariable>
|
|
|
|
|
|
{
|
|
|
|
|
|
new ZyFlowVariable { InstanceId = instanceId, VarKey = "Title", VarValue = request.Title },
|
|
|
|
|
|
new ZyFlowVariable { InstanceId = instanceId, VarKey = "AttachmentPaths", VarValue = attachmentPaths },
|
|
|
|
|
|
new ZyFlowVariable
|
|
|
|
|
|
{
|
|
|
|
|
|
InstanceId = instanceId, VarKey = "SubmitTime",
|
|
|
|
|
|
VarValue = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
|
|
|
|
|
|
}
|
2026-02-04 21:43:24 +08:00
|
|
|
|
};
|
2026-02-05 09:08:32 +08:00
|
|
|
|
_sqlSugar.Insertable(flowVariables).ExecuteCommand();
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 步骤6:核心流转:从区县提交节点流转至执法监督科节点
|
|
|
|
|
|
FlowToNextNode(instanceId, startNode, userId, userName, "区县提交完成,流转至执法监督科");
|
|
|
|
|
|
|
|
|
|
|
|
// 提交事务
|
2026-02-04 21:43:24 +08:00
|
|
|
|
UnitWork.Db.Ado.CommitTran();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
2026-02-04 20:40:22 +08:00
|
|
|
|
{
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 回滚事务
|
2026-02-04 21:43:24 +08:00
|
|
|
|
UnitWork.Db.Ado.RollbackTran();
|
2026-02-05 09:08:32 +08:00
|
|
|
|
throw new Exception($"发起流程失败:{ex.Message}", ex);
|
2026-02-04 20:40:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-04 21:43:24 +08:00
|
|
|
|
return instanceId;
|
2026-02-04 20:40:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
#endregion
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
#region 2. 处理工作项(执法监督科转发/审核科会签/汇总处理)
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
public bool HandleWorkitem(long userId, string userName, HandleWorkitemRequest request)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 参数校验
|
|
|
|
|
|
if (request.WorkitemId <= 0)
|
|
|
|
|
|
throw new Exception("工作项ID无效");
|
|
|
|
|
|
if (userId <= 0 || string.IsNullOrEmpty(userName))
|
|
|
|
|
|
throw new Exception("处理人ID和姓名不能为空");
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-04 21:43:24 +08:00
|
|
|
|
try
|
2026-02-04 20:40:22 +08:00
|
|
|
|
{
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 开启事务
|
2026-02-04 21:43:24 +08:00
|
|
|
|
UnitWork.Db.Ado.BeginTran();
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 步骤1:查询待处理的工作项(仅待办状态可处理)
|
|
|
|
|
|
var workitem = _sqlSugar.Queryable<ZyFlowWorkitem>()
|
|
|
|
|
|
.Where(w => w.WorkitemId == request.WorkitemId && w.Status == "ToDo")
|
|
|
|
|
|
.First();
|
|
|
|
|
|
if (workitem == null)
|
|
|
|
|
|
throw new Exception("工作项不存在、已处理或状态异常");
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤2:查询关联的流程实例和当前节点
|
|
|
|
|
|
var flowInstance = _sqlSugar.Queryable<ZyFlowInstance>()
|
|
|
|
|
|
.Where(i => i.InstanceId == workitem.InstanceId)
|
|
|
|
|
|
.First();
|
|
|
|
|
|
var currentNode = _sqlSugar.Queryable<ZyFlowNode>()
|
|
|
|
|
|
.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(request.Comment) ? "处理完成" : request.Comment;
|
|
|
|
|
|
_sqlSugar.Updateable(workitem).ExecuteCommand();
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤4:按节点类型分支处理核心逻辑
|
|
|
|
|
|
switch (currentNode.NodeType)
|
2026-02-04 20:40:22 +08:00
|
|
|
|
{
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 普通节点:执法监督科转发
|
|
|
|
|
|
case "Common" when currentNode.NodeName == "执法监督科转发":
|
|
|
|
|
|
FlowToNextNode(flowInstance.InstanceId, currentNode, userId, userName, workitem.Comment);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
// 并行节点:5个审核科会签
|
|
|
|
|
|
case "Parallel" when _auditDeptRoleIds.Contains(currentNode.RoleId):
|
|
|
|
|
|
ProcessParallelAudit(flowInstance.InstanceId, currentNode.NodeId, userId, userName, request);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
// 分支节点:汇总(归档/退回区县)
|
|
|
|
|
|
case "Branch" when currentNode.NodeName == "汇总":
|
|
|
|
|
|
ProcessSummaryNode(flowInstance, currentNode, userId, userName);
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
|
|
// 结束节点:流程归档完成
|
|
|
|
|
|
case "End":
|
|
|
|
|
|
CompleteFlowInstance(flowInstance);
|
|
|
|
|
|
break;
|
2026-02-04 20:40:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 提交事务
|
2026-02-04 21:43:24 +08:00
|
|
|
|
UnitWork.Db.Ado.CommitTran();
|
2026-02-05 09:08:32 +08:00
|
|
|
|
return true;
|
2026-02-04 21:43:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
2026-02-04 20:40:22 +08:00
|
|
|
|
{
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 回滚事务
|
2026-02-04 21:43:24 +08:00
|
|
|
|
UnitWork.Db.Ado.RollbackTran();
|
2026-02-05 09:08:32 +08:00
|
|
|
|
throw new Exception($"处理工作项失败:{ex.Message}", ex);
|
2026-02-04 21:43:24 +08:00
|
|
|
|
}
|
2026-02-04 20:40:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
#endregion
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
#region 3. 流程查询(我的待办/我的已办)
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 查询我的待办工作项
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public List<FlowQueryResult> QueryMyToDo(long userId)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (userId <= 0)
|
|
|
|
|
|
return new List<FlowQueryResult>();
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 步骤1:查询当前用户的待办工作项
|
|
|
|
|
|
var toDoWorkitems = _sqlSugar.Queryable<ZyFlowWorkitem>()
|
|
|
|
|
|
.Where(w => w.HandlerId == userId && w.Status == "ToDo")
|
2026-02-04 21:43:24 +08:00
|
|
|
|
.ToList();
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 步骤2:组装返回结果
|
|
|
|
|
|
var queryResults = new List<FlowQueryResult>();
|
|
|
|
|
|
foreach (var workitem in toDoWorkitems)
|
2026-02-04 21:43:24 +08:00
|
|
|
|
{
|
2026-02-05 09:08:32 +08:00
|
|
|
|
var flowInstance = _sqlSugar.Queryable<ZyFlowInstance>()
|
|
|
|
|
|
.Where(i => i.InstanceId == workitem.InstanceId)
|
|
|
|
|
|
.First();
|
|
|
|
|
|
if (flowInstance == null)
|
|
|
|
|
|
continue;
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 查询流程标题
|
|
|
|
|
|
var flowTitle = _sqlSugar.Queryable<ZyFlowVariable>()
|
|
|
|
|
|
.Where(v => v.InstanceId == flowInstance.InstanceId && v.VarKey == "Title")
|
|
|
|
|
|
.First()?.VarValue ?? string.Empty;
|
2026-02-04 21:43:24 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
queryResults.Add(new FlowQueryResult
|
2026-02-04 21:43:24 +08:00
|
|
|
|
{
|
2026-02-05 09:08:32 +08:00
|
|
|
|
InstanceId = flowInstance.InstanceId,
|
|
|
|
|
|
BusinessNo = flowInstance.BusinessNo,
|
|
|
|
|
|
Title = flowTitle,
|
|
|
|
|
|
NodeName = workitem.NodeName,
|
|
|
|
|
|
Status = flowInstance.Status,
|
|
|
|
|
|
CreateTime = flowInstance.CreateTime,
|
|
|
|
|
|
InitiatorName = flowInstance.InitiatorName
|
2026-02-04 21:43:24 +08:00
|
|
|
|
});
|
2026-02-05 09:08:32 +08:00
|
|
|
|
}
|
2026-02-04 21:43:24 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
return queryResults;
|
2026-02-04 20:40:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 查询我的已办工作项
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public List<FlowQueryResult> QueryMyDone(long userId)
|
2026-02-04 20:40:22 +08:00
|
|
|
|
{
|
2026-02-05 09:08:32 +08:00
|
|
|
|
if (userId <= 0)
|
|
|
|
|
|
return new List<FlowQueryResult>();
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤1:查询当前用户已完成的工作项对应的流程实例ID(去重)
|
|
|
|
|
|
var doneInstanceIds = _sqlSugar.Queryable<ZyFlowWorkitem>()
|
|
|
|
|
|
.Where(w => w.HandlerId == userId && w.Status == "Done")
|
|
|
|
|
|
.Select(w => w.InstanceId)
|
|
|
|
|
|
.Distinct()
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤2:组装返回结果
|
|
|
|
|
|
var queryResults = new List<FlowQueryResult>();
|
|
|
|
|
|
foreach (var instanceId in doneInstanceIds)
|
|
|
|
|
|
{
|
|
|
|
|
|
var flowInstance = _sqlSugar.Queryable<ZyFlowInstance>()
|
|
|
|
|
|
.Where(i => i.InstanceId == instanceId)
|
|
|
|
|
|
.First();
|
|
|
|
|
|
if (flowInstance == null)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
// 查询流程标题
|
|
|
|
|
|
var flowTitle = _sqlSugar.Queryable<ZyFlowVariable>()
|
|
|
|
|
|
.Where(v => v.InstanceId == flowInstance.InstanceId && v.VarKey == "Title")
|
|
|
|
|
|
.First()?.VarValue ?? string.Empty;
|
|
|
|
|
|
|
|
|
|
|
|
// 查询最新的已完成工作项
|
|
|
|
|
|
var lastWorkitem = _sqlSugar.Queryable<ZyFlowWorkitem>()
|
|
|
|
|
|
.Where(w => w.InstanceId == instanceId && w.Status == "Done")
|
|
|
|
|
|
.OrderByDescending(w => w.HandleTime)
|
|
|
|
|
|
.First();
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
queryResults.Add(new FlowQueryResult
|
2026-02-04 21:43:24 +08:00
|
|
|
|
{
|
2026-02-05 09:08:32 +08:00
|
|
|
|
InstanceId = flowInstance.InstanceId,
|
|
|
|
|
|
BusinessNo = flowInstance.BusinessNo,
|
|
|
|
|
|
Title = flowTitle,
|
|
|
|
|
|
NodeName = lastWorkitem?.NodeName ?? string.Empty,
|
|
|
|
|
|
Status = flowInstance.Status,
|
|
|
|
|
|
CreateTime = flowInstance.CreateTime,
|
|
|
|
|
|
InitiatorName = flowInstance.InitiatorName
|
2026-02-04 21:43:24 +08:00
|
|
|
|
});
|
2026-02-05 09:08:32 +08:00
|
|
|
|
}
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
return queryResults;
|
2026-02-04 21:43:24 +08:00
|
|
|
|
}
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region 4. 核心辅助方法(流转/会签/汇总/附件等)
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 流程节点流转核心方法
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void FlowToNextNode(long instanceId, ZyFlowNode currentNode, long userId, string userName, string comment)
|
2026-02-04 20:40:22 +08:00
|
|
|
|
{
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 解析下一节点IDs
|
|
|
|
|
|
var nextNodeIds = currentNode.NextNodeIds?.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
|
|
|
|
|
.Select(long.Parse)
|
|
|
|
|
|
.ToList() ?? new List<long>();
|
|
|
|
|
|
if (!nextNodeIds.Any())
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
// 查询流程实例
|
|
|
|
|
|
var flowInstance = _sqlSugar.Queryable<ZyFlowInstance>()
|
|
|
|
|
|
.Where(i => i.InstanceId == instanceId)
|
|
|
|
|
|
.First();
|
|
|
|
|
|
if (flowInstance == null)
|
|
|
|
|
|
throw new Exception("流程实例不存在");
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 遍历处理每个下一节点
|
|
|
|
|
|
foreach (var nextNodeId in nextNodeIds)
|
|
|
|
|
|
{
|
|
|
|
|
|
var nextNode = _sqlSugar.Queryable<ZyFlowNode>()
|
|
|
|
|
|
.Where(n => n.NodeId == nextNodeId)
|
|
|
|
|
|
.First();
|
|
|
|
|
|
if (nextNode == null)
|
|
|
|
|
|
continue;
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 更新流程实例当前节点和状态
|
|
|
|
|
|
flowInstance.CurrentNodeId = nextNode.NodeId;
|
|
|
|
|
|
flowInstance.Status = GetFlowStatusByNodeType(nextNode.NodeType);
|
|
|
|
|
|
_sqlSugar.Updateable(flowInstance).ExecuteCommand();
|
|
|
|
|
|
|
|
|
|
|
|
// 并行节点(5个审核科):为每个审核科创建工作项
|
|
|
|
|
|
if (nextNode.NodeType == "Parallel")
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var roleId in _auditDeptRoleIds)
|
|
|
|
|
|
{
|
|
|
|
|
|
CreateAuditDeptWorkitem(instanceId, nextNode, roleId);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 普通节点/分支节点:创建单个工作项
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
var nextWorkitem = new ZyFlowWorkitem
|
|
|
|
|
|
{
|
|
|
|
|
|
InstanceId = instanceId,
|
|
|
|
|
|
NodeId = nextNode.NodeId,
|
|
|
|
|
|
NodeName = nextNode.NodeName,
|
|
|
|
|
|
HandlerId = userId,
|
|
|
|
|
|
HandlerName = userName,
|
|
|
|
|
|
Status = "ToDo",
|
|
|
|
|
|
ReceiveTime = DateTime.Now,
|
|
|
|
|
|
Comment = comment
|
|
|
|
|
|
};
|
|
|
|
|
|
_sqlSugar.Insertable(nextWorkitem).ExecuteCommand();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-04 21:43:24 +08:00
|
|
|
|
}
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 处理并行会签(保存结果+判断是否全部完成)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void ProcessParallelAudit(long instanceId, long nodeId, long userId, string userName,
|
|
|
|
|
|
HandleWorkitemRequest request)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 校验会签结果
|
|
|
|
|
|
if (string.IsNullOrEmpty(request.AuditResult))
|
|
|
|
|
|
throw new Exception("会签需选择审核结果(Pass/Reject)");
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 步骤1:获取当前用户所属科室名称
|
|
|
|
|
|
var deptName = GetUserDeptName(userId);
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 步骤2:保存会签记录
|
|
|
|
|
|
var parallelAudit = new ZyFlowParallelAudit
|
|
|
|
|
|
{
|
|
|
|
|
|
InstanceId = instanceId,
|
|
|
|
|
|
NodeId = nodeId,
|
|
|
|
|
|
DeptName = deptName,
|
|
|
|
|
|
AuditResult = request.AuditResult,
|
|
|
|
|
|
AuditComment = request.Comment,
|
|
|
|
|
|
AuditorId = userId,
|
|
|
|
|
|
AuditorName = userName,
|
|
|
|
|
|
AuditTime = DateTime.Now
|
|
|
|
|
|
};
|
|
|
|
|
|
_sqlSugar.Insertable(parallelAudit).ExecuteCommand();
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤3:判断是否所有审核科都已完成会签
|
|
|
|
|
|
if (IsAllAuditCompleted(instanceId))
|
|
|
|
|
|
{
|
|
|
|
|
|
// 步骤4:流转至汇总节点
|
|
|
|
|
|
var parallelNode = _sqlSugar.Queryable<ZyFlowNode>()
|
|
|
|
|
|
.Where(n => n.NodeId == nodeId && n.NodeType == "Parallel")
|
|
|
|
|
|
.First();
|
|
|
|
|
|
FlowToNextNode(instanceId, parallelNode, userId, userName, "所有审核科会签完成,流转至汇总节点");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 处理汇总节点(归档/退回区县)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void ProcessSummaryNode(ZyFlowInstance flowInstance, ZyFlowNode summaryNode, long userId, string userName)
|
2026-02-04 21:43:24 +08:00
|
|
|
|
{
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 步骤1:判断是否全部审核通过
|
|
|
|
|
|
var isAllPass = _sqlSugar.Queryable<ZyFlowParallelAudit>()
|
|
|
|
|
|
.Where(a => a.InstanceId == flowInstance.InstanceId)
|
|
|
|
|
|
.Any(a=> a.AuditResult != "Pass");
|
|
|
|
|
|
|
|
|
|
|
|
// todo
|
|
|
|
|
|
// 步骤2:解析汇总节点下一节点(归档/退回)
|
|
|
|
|
|
var nextNodeIds = summaryNode.NextNodeIds?.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
|
|
|
|
|
.Select(long.Parse)
|
|
|
|
|
|
.ToList() ?? new List<long>();
|
|
|
|
|
|
if (nextNodeIds.Count < 2)
|
|
|
|
|
|
throw new Exception("汇总节点需配置2个后续节点(归档/退回区县)");
|
|
|
|
|
|
|
|
|
|
|
|
// 步骤3:确定目标节点(通过→归档,不通过→退回)
|
|
|
|
|
|
var targetNodeId = !isAllPass ? nextNodeIds[0] : nextNodeIds[1];
|
|
|
|
|
|
var targetNode = _sqlSugar.Queryable<ZyFlowNode>()
|
|
|
|
|
|
.Where(n => n.NodeId == targetNodeId)
|
|
|
|
|
|
.First();
|
|
|
|
|
|
if (targetNode == null)
|
|
|
|
|
|
return;
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 步骤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();
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 步骤5:更新流程实例最终状态
|
|
|
|
|
|
flowInstance.CurrentNodeId = targetNode.NodeId;
|
|
|
|
|
|
flowInstance.Status = isAllPass ? "Completed" : "Rejected";
|
|
|
|
|
|
flowInstance.FinishTime = isAllPass ? DateTime.Now : null;
|
|
|
|
|
|
_sqlSugar.Updateable(flowInstance).ExecuteCommand();
|
2026-02-04 20:40:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 完成流程实例(结束节点)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void CompleteFlowInstance(ZyFlowInstance flowInstance)
|
|
|
|
|
|
{
|
|
|
|
|
|
flowInstance.Status = "Completed";
|
|
|
|
|
|
flowInstance.FinishTime = DateTime.Now;
|
|
|
|
|
|
_sqlSugar.Updateable(flowInstance).ExecuteCommand();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 为审核科创建工作项
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private void CreateAuditDeptWorkitem(long instanceId, ZyFlowNode parallelNode, long roleId)
|
2026-02-04 20:40:22 +08:00
|
|
|
|
{
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 获取科室用户信息
|
|
|
|
|
|
var (userId, userName, deptName) = GetRoleFirstUserInfo(roleId);
|
|
|
|
|
|
if (userId <= 0 || string.IsNullOrEmpty(userName))
|
|
|
|
|
|
throw new Exception($"审核科角色【{roleId}】未配置有效用户");
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 创建工作项
|
|
|
|
|
|
var workitem = new ZyFlowWorkitem
|
2026-02-04 20:40:22 +08:00
|
|
|
|
{
|
2026-02-05 09:08:32 +08:00
|
|
|
|
InstanceId = instanceId,
|
|
|
|
|
|
NodeId = parallelNode.NodeId,
|
|
|
|
|
|
NodeName = $"{parallelNode.NodeName}({deptName})",
|
|
|
|
|
|
HandlerId = userId,
|
|
|
|
|
|
HandlerName = userName,
|
|
|
|
|
|
Status = "ToDo",
|
|
|
|
|
|
ReceiveTime = DateTime.Now,
|
|
|
|
|
|
Comment = "请完成违法建设认定相关审核工作"
|
|
|
|
|
|
};
|
|
|
|
|
|
_sqlSugar.Insertable(workitem).ExecuteCommand();
|
|
|
|
|
|
}
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 保存附件并返回路径
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private string SaveAttachments(List<IFormFile> attachments)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (attachments == null || !attachments.Any())
|
|
|
|
|
|
return string.Empty;
|
|
|
|
|
|
|
|
|
|
|
|
// 定义附件存储目录
|
|
|
|
|
|
var uploadDir = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/Attachments/FlowAttachments");
|
|
|
|
|
|
if (!Directory.Exists(uploadDir))
|
|
|
|
|
|
Directory.CreateDirectory(uploadDir);
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
var attachmentPaths = new List<string>();
|
|
|
|
|
|
foreach (var file in attachments)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (file.Length <= 0)
|
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
|
|
// 生成唯一文件名
|
|
|
|
|
|
var fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";
|
|
|
|
|
|
var filePath = Path.Combine(uploadDir, fileName);
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 保存文件
|
|
|
|
|
|
using (var stream = new FileStream(filePath, FileMode.Create))
|
2026-02-04 20:40:22 +08:00
|
|
|
|
{
|
2026-02-05 09:08:32 +08:00
|
|
|
|
file.CopyTo(stream);
|
2026-02-04 21:43:24 +08:00
|
|
|
|
}
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
// 保存相对路径(用于前端访问)
|
|
|
|
|
|
attachmentPaths.Add($"/Attachments/FlowAttachments/{fileName}");
|
|
|
|
|
|
}
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
return string.Join(",", attachmentPaths);
|
|
|
|
|
|
}
|
2026-02-04 20:40:22 +08:00
|
|
|
|
|
2026-02-05 09:08:32 +08:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 根据节点类型获取流程状态
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private string GetFlowStatusByNodeType(string nodeType)
|
|
|
|
|
|
{
|
|
|
|
|
|
return nodeType switch
|
2026-02-04 20:40:22 +08:00
|
|
|
|
{
|
2026-02-05 09:08:32 +08:00
|
|
|
|
"Common" => "Forwarded", // 已转发
|
|
|
|
|
|
"Parallel" => "Auditing", // 审核中
|
|
|
|
|
|
"Branch" => "Summarized", // 已汇总
|
|
|
|
|
|
"End" => "Completed", // 已完成
|
|
|
|
|
|
_ => "Submitted" // 已提交
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 判断所有审核科是否完成会签
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private bool IsAllAuditCompleted(long instanceId)
|
|
|
|
|
|
{
|
|
|
|
|
|
var auditResults = _sqlSugar.Queryable<ZyFlowParallelAudit>()
|
|
|
|
|
|
.Where(a => a.InstanceId == instanceId)
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
// 条件:会签记录数=5个审核科 且 无待审核状态
|
|
|
|
|
|
return auditResults.Count == _auditDeptRoleIds.Count && !auditResults.Any(a => a.AuditResult == "Pending");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取用户所属科室名称
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private string GetUserDeptName(long userId)
|
|
|
|
|
|
{
|
|
|
|
|
|
var deptName = _sqlSugar.Queryable<SysUserOrg, SysOrg>((uo, o) => new JoinQueryInfos(
|
|
|
|
|
|
JoinType.Inner, uo.OrgId == o.Id))
|
|
|
|
|
|
.Where((uo, o) => uo.UserId == userId)
|
|
|
|
|
|
.Select((uo, o) => o.Name)
|
|
|
|
|
|
.First();
|
|
|
|
|
|
|
|
|
|
|
|
return deptName ?? "未知科室";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取角色下第一个用户ID
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private long GetRoleFirstUserId(long roleId)
|
|
|
|
|
|
{
|
|
|
|
|
|
var userInfo = GetRoleFirstUserInfo(roleId);
|
|
|
|
|
|
return userInfo.userId;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取角色下第一个用户姓名
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private string GetRoleFirstUserName(long roleId)
|
|
|
|
|
|
{
|
|
|
|
|
|
var userInfo = GetRoleFirstUserInfo(roleId);
|
|
|
|
|
|
return userInfo.userName;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 获取角色下第一个用户的完整信息
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private (long userId, string userName, string deptName) GetRoleFirstUserInfo(long roleId)
|
|
|
|
|
|
{
|
|
|
|
|
|
var sql = @"
|
|
|
|
|
|
SELECT
|
|
|
|
|
|
u.""Id"" as UserId,
|
|
|
|
|
|
u.""Name"" as UserName,
|
|
|
|
|
|
o.""Name"" as DeptName
|
|
|
|
|
|
FROM sys_user u
|
|
|
|
|
|
INNER JOIN sys_userrole r ON u.""Id"" = r.""UserId""
|
|
|
|
|
|
INNER JOIN sys_userorg uo ON u.""Id"" = uo.""UserId""
|
|
|
|
|
|
INNER JOIN sys_org o ON uo.""OrgId"" = o.""Id""
|
|
|
|
|
|
WHERE r.""RoleId"" = @RoleId
|
|
|
|
|
|
LIMIT 1";
|
|
|
|
|
|
|
|
|
|
|
|
var userInfo = _sqlSugar.SqlQueryable<dynamic>(sql)
|
|
|
|
|
|
.AddParameters(new { RoleId = roleId })
|
|
|
|
|
|
.First();
|
|
|
|
|
|
|
|
|
|
|
|
return (userInfo.UserId, userInfo.UserName ?? string.Empty, userInfo.DeptName ?? string.Empty);
|
2026-02-04 20:40:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
}
|