identificationOfCultivatedL.../OpenAuth.App/workflow/WorkflowEngineApp.cs

1010 lines
41 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using Microsoft.AspNetCore.Http;
using OpenAuth.App.BaseApp.Base;
using OpenAuth.App.Interface;
using OpenAuth.Repository;
using OpenAuth.Repository.Domain;
using OpenAuth.Repository.Domain.workflow;
using OpenAuth.WebApi.Controllers.ServerController;
using SqlSugar;
namespace OpenAuth.App.workflow;
/// <summary>
/// 完整工作流引擎适配区县→执法监督科→5个审核科会签→汇总归档/退回)
/// 包含:流程发起/处理 + 拟办/待办/已办/未办结/已完成/全部事项查询
/// </summary>
public class WorkflowEngineApp : SqlSugarBaseApp<SysCategoryType, SugarDbContext>
{
private readonly ISqlSugarClient _sqlSugar;
private readonly BusinessNoGenerator _businessNoGenerator;
// 配置5个审核科角色ID需与sys_role表对应自行修改为实际值
private readonly List<long> _auditDeptRoleIds = new()
{
1, // 政策法规科
2, // 详细规划科
3, // 空间规划科
4, // 开发利用科
5 // 工程科
};
// 配置执法监督科角色ID需与sys_role表对应自行修改为实际值
private const long _supervisionDeptRoleId = 6;
public WorkflowEngineApp(ISugarUnitOfWork<SugarDbContext> unitWork, ISimpleClient<SysCategoryType> repository,
IAuth auth, BusinessNoGenerator businessNoGenerator) : base(unitWork, repository, auth)
{
_sqlSugar = Repository.AsSugarClient();
_businessNoGenerator = businessNoGenerator;
}
#region 一、核心业务:流程发起/处理
/// <summary>
/// 发起流程(区县提交→流转至执法监督科)
/// </summary>
/// <param name="userId">发起人ID</param>
/// <param name="userName">发起人姓名</param>
/// <param name="requestDto">发起流程请求参数</param>
/// <returns>流程实例ID</returns>
public long InitiateFlow(long userId, string userName, InitiateFlowRequestDto requestDto)
{
// 参数校验
/*if (string.IsNullOrEmpty(requestDto.FlowCode) || string.IsNullOrEmpty(requestDto.BusinessNo))
throw new Exception("流程编码和业务编号不能为空");*/
if (userId <= 0 || string.IsNullOrEmpty(userName))
throw new Exception("发起人ID和姓名不能为空");
var instanceId = 0L;
try
{
// 开启事务基于UnitWork保障数据一致性
UnitWork.Db.Ado.BeginTran();
// 步骤1查询启用的流程模板违法建设认定流程
var template = _sqlSugar.Queryable<ZyFlowTemplate>()
.Where(t => t.FlowCode == requestDto.FlowCode && t.IsEnabled == true)
.First();
if (template == null)
throw new Exception($"流程模板【{requestDto.FlowCode}】不存在或未启用");
// 步骤2查询流程开始节点区县提交
var startNode = _sqlSugar.Queryable<ZyFlowNode>()
.Where(n => n.TemplateId == template.TemplateId && n.NodeType == "Start" && n.NodeName == "区县提交")
.First();
if (startNode == null)
throw new Exception("流程开始节点【区县提交】不存在,请配置节点");
var prefix = "wfjszl";
var businessNo = _businessNoGenerator.GenerateBusinessNo(prefix);
requestDto.BusinessNo = businessNo;
// 步骤3插入流程实例返回自增主键
var flowInstance = new ZyFlowInstance
{
TemplateId = template.TemplateId,
FlowCode = template.FlowCode,
BusinessNo = requestDto.BusinessNo,
Status = "Submitted", // 已提交
CurrentNodeId = startNode.NodeId,
InitiatorId = userId,
InitiatorName = userName,
CreateTime = DateTime.Now
};
instanceId = _sqlSugar.Insertable(flowInstance).ExecuteReturnIdentity();
// 步骤4插入开始节点工作项直接标记为已完成
// 开始节点无ToDo
var startWorkitem = new ZyFlowWorkitem
{
InstanceId = instanceId,
NodeId = startNode.NodeId,
NodeName = startNode.NodeName,
HandlerId = userId,
HandlerName = userName,
Status = "Done", // 已完成
ReceiveTime = DateTime.Now,
HandleTime = DateTime.Now,
Comment = "区县提交业务材料,流程发起成功"
};
_sqlSugar.Insertable(startWorkitem).ExecuteCommand();
// 步骤5保存流程变量标题、附件路径等
//var attachmentPaths = SaveAttachments(requestDto.Attachments);
var attachmentPaths = string.Join(",", requestDto.Attachments);
var flowVariables = new List<ZyFlowVariable>
{
new() { InstanceId = instanceId, VarKey = "Title", VarValue = requestDto.Title },
new() { InstanceId = instanceId, VarKey = "AttachmentPaths", VarValue = attachmentPaths },
new()
{
InstanceId = instanceId, VarKey = "SubmitTime",
VarValue = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
},
new() { InstanceId = instanceId, VarKey = "Type", VarValue = requestDto.type.ToString() },
};
_sqlSugar.Insertable(flowVariables).ExecuteCommand();
// todo 创建或者更新表单数据
var illegal = new IllegalConstructionAssessment()
{
Id = requestDto.BusinessNo,
Title = requestDto.Title,
BusinessNumber = requestDto.BusinessNo,
Attachments = attachmentPaths,
CreateTime = DateTime.Now,
CreateUser = userName,
UpdateTime = DateTime.Now,
};
if (_sqlSugar
.Queryable<IllegalConstructionAssessment>()
.Where(a => a.BusinessNumber == requestDto.BusinessNo).Count() > 0)
{
// todo 关于状态对于不同角色的人不一样问题
// _sqlSugar.Updateable(illegal).IgnoreColumns().ExecuteCommand();
}
else
{
_sqlSugar.Insertable(illegal).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;
}
/// <summary>
/// 处理工作项(执法监督科转发/审核科会签/汇总处理)
/// </summary>
/// <param name="userId">处理人ID</param>
/// <param name="userName">处理人姓名</param>
/// <param name="requestDto">处理工作项请求参数</param>
/// <returns>处理结果(成功/失败)</returns>
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<ZyFlowWorkitem>()
.Where(w => w.WorkitemId == requestDto.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("流程实例或流程节点不存在");
if (requestDto.AuditResult is "Pass")
{
// 步骤3更新当前工作项为已完成
workitem.Status = "Done";
workitem.HandleTime = DateTime.Now;
workitem.Comment = string.IsNullOrEmpty(requestDto.Comment) ? "处理完成" : requestDto.Comment;
_sqlSugar.Updateable(workitem).ExecuteCommand();
// 生成下一节点数据
// 步骤4按节点类型分支处理核心逻辑
switch (currentNode.NodeType)
{
// 普通节点:执法监督科转发
case "Common" when currentNode.NodeName == "执法监督科转发":
FlowToNextNode(flowInstance.InstanceId, currentNode, userId, userName, workitem.Comment);
break;
// 并行节点5个审核科会签 // todo
case "Parallel" when _auditDeptRoleIds.Contains(currentNode.RoleId):
ProcessParallelAudit(flowInstance.InstanceId, currentNode.NodeId, userId, userName, requestDto);
break;
// 分支节点:汇总(归档/退回区县)
case "Branch" when currentNode.NodeName == "汇总判断":
ProcessSummaryNode(flowInstance, currentNode, userId, userName);
break;
// 结束节点:流程归档完成
case "End":
CompleteFlowInstance(flowInstance);
break;
}
// 提交事务
UnitWork.Db.Ado.CommitTran();
return true;
}
else // Reject todo 考虑驳回之后,怎么回到上一个节点
{
return true;
}
}
catch (Exception ex)
{
// 回滚事务
UnitWork.Db.Ado.RollbackTran();
throw new Exception($"处理工作项失败:{ex.Message}", ex);
}
}
#endregion
#region 二、完整查询:拟办/待办/已办/未办结/已完成/全部事项
/// <summary>
/// 我的拟办(未认领/待分配给当前用户的事项)
/// </summary>
/// <param name="userId">当前用户ID</param>
/// <param name="pageQueryDto">分页参数</param>
/// <returns>分页结果</returns>
public PageQueryResultDto<FlowQuerySingleResultDto> QueryMyDraft(long userId, PageQueryRequestDto pageQueryDto)
{
// 参数初始化
if (userId <= 0)
return new PageQueryResultDto<FlowQuerySingleResultDto>();
pageQueryDto = pageQueryDto ?? new PageQueryRequestDto();
var pageIndex = pageQueryDto.page < 1 ? 1 : pageQueryDto.page;
var pageSize = pageQueryDto.limit < 1 ? 10 : pageQueryDto.limit;
// 拟办不应该是针对个人的吗?
// 拟办逻辑:工作项状态为"Draft"(拟办)、对应角色匹配当前用户、未分配具体处理人
var draftQuery = _sqlSugar
.Queryable<IllegalConstructionAssessment>()
.Where(w => w.Status == "Draft" && w.CreateUserId == userId)
.OrderByDescending(w => w.ReceiveTime);
// 分页查询
var totalCount = draftQuery.Count();
var draftWorkitems = draftQuery
.Skip((pageIndex - 1) * pageSize)
.Take(pageSize)
.ToList();
// 组装返回结果
var dataList = new List<FlowQuerySingleResultDto>();
foreach (var workitem in draftWorkitems)
{
dataList.Add(new FlowQuerySingleResultDto
{
//InstanceId = flowInstance.InstanceId,
BusinessNo = workitem.BusinessNumber,
Title = workitem.Title,
//NodeName = workitem.NodeName,
Status = "Draft", // 拟办状态
CreateTime = workitem.CreateTime,
//InitiatorName = workitem.CreateUser //发起人姓名
});
}
return new PageQueryResultDto<FlowQuerySingleResultDto>
{
Page = pageIndex,
Limit = pageSize,
Total = totalCount,
TotalPages = (int)Math.Ceiling((double)totalCount / pageSize),
Items = dataList
};
}
/// <summary>
/// 我的待办(已分配给当前用户、待处理的事项)
/// </summary>
/// <param name="userId">当前用户ID</param>
/// <param name="pageQueryDto">分页参数</param>
/// <returns>分页结果</returns>
public PageQueryResultDto<FlowQuerySingleResultDto> QueryMyToDo(long userId, PageQueryRequestDto pageQueryDto)
{
// 参数初始化
if (userId <= 0)
return new PageQueryResultDto<FlowQuerySingleResultDto>();
pageQueryDto = pageQueryDto ?? new PageQueryRequestDto();
var pageIndex = pageQueryDto.page < 1 ? 1 : pageQueryDto.page;
var pageSize = pageQueryDto.limit < 1 ? 10 : pageQueryDto.limit;
// 待办逻辑:工作项状态为"ToDo"、处理人ID为当前用户
var nodeIdList = _sqlSugar.Queryable<ZyFlowNode>()
// 核心In(主表字段, 子查询Lambda表达式)
.In(
zy => zy.RoleId, // 主表 zy_flow_node 的 role_id 字段
// 子查询:从 sys_userrole 中查询 UserId=2 的 RoleId
_sqlSugar.Queryable<SysUserRole>()
.Where(ur => ur.UserId == userId) // 条件UserId=2注意这里直接用数值避免字符串拼接
.Select(ur => ur.RoleId) // 子查询只返回 RoleId 字段
)
// 主查询只返回 node_id 字段(提升查询性能,避免查询全表字段)
.Select(zy => zy.NodeId)
// 执行查询,返回 List<long> 结果
.ToList();
var toDoQuery = _sqlSugar
.Queryable<ZyFlowWorkitem>()
.LeftJoin<ZyFlowNode>((w, n) => w.NodeId == n.NodeId)
.Where((w, n) => nodeIdList.Contains(w.NodeId) && w.Status == "ToDo")
.OrderByDescending(w => w.ReceiveTime);
// 分页查询
var totalCount = toDoQuery.Count();
var toDoWorkitems = toDoQuery
.Skip((pageIndex - 1) * pageSize)
.Take(pageSize)
.ToList();
// 组装返回结果
var dataList = new List<FlowQuerySingleResultDto>();
foreach (var workitem in toDoWorkitems)
{
var flowInstance = _sqlSugar.Queryable<ZyFlowInstance>()
.Where(i => i.InstanceId == workitem.InstanceId)
.First();
if (flowInstance == null)
continue;
var zyflowVarList = _sqlSugar.Queryable<ZyFlowVariable>()
.Where(v => v.InstanceId == flowInstance.InstanceId);
var flowTitle = zyflowVarList.Where(v => v.VarKey == "Title").First()?.VarValue;
var type = zyflowVarList.Where(v => v.VarKey == "Type").First()?.VarValue;
dataList.Add(new FlowQuerySingleResultDto
{
workitemId = workitem.WorkitemId,
InstanceId = flowInstance.InstanceId,
BusinessNo = flowInstance.BusinessNo,
Title = flowTitle,
Type = Int16.Parse(type ?? "0"),
NodeName = workitem.NodeName,
Status = flowInstance.Status,
CreateTime = flowInstance.CreateTime,
InitiatorName = flowInstance.InitiatorName
});
}
return new PageQueryResultDto<FlowQuerySingleResultDto>
{
Page = pageIndex,
Limit = pageSize,
Total = totalCount,
TotalPages = (int)Math.Ceiling((double)totalCount / pageSize),
Items = dataList
};
}
/// <summary>
/// 我的已办(当前用户已处理完成的事项)
/// </summary>
/// <param name="userId">当前用户ID</param>
/// <param name="pageQueryDto">分页参数</param>
/// <returns>分页结果</returns>
public PageQueryResultDto<FlowQuerySingleResultDto> QueryMyDone(long userId, PageQueryRequestDto pageQueryDto)
{
// 参数初始化
if (userId <= 0)
return new PageQueryResultDto<FlowQuerySingleResultDto>();
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<ZyFlowWorkitem>()
.Where(w => w.HandlerId == userId && w.Status == "Done")
.Select(w => w.InstanceId)
.Distinct()
.ToList();
if (!doneInstanceIds.Any())
return new PageQueryResultDto<FlowQuerySingleResultDto>();
// 分页处理实例ID
var pagedInstanceIds = doneInstanceIds
.Skip((pageIndex - 1) * pageSize)
.Take(pageSize)
.ToList();
var totalCount = doneInstanceIds.Count;
// 组装返回结果
var dataList = new List<FlowQuerySingleResultDto>();
foreach (var instanceId in pagedInstanceIds)
{
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.HandlerId == userId && w.Status == "Done")
.OrderByDescending(w => w.HandleTime)
.First();
dataList.Add(new FlowQuerySingleResultDto
{
InstanceId = flowInstance.InstanceId,
BusinessNo = flowInstance.BusinessNo,
Title = flowTitle,
NodeName = lastWorkitem?.NodeName ?? string.Empty,
Status = flowInstance.Status,
CreateTime = flowInstance.CreateTime,
InitiatorName = flowInstance.InitiatorName
});
}
return new PageQueryResultDto<FlowQuerySingleResultDto>
{
Page = pageIndex,
Limit = pageSize,
Total = totalCount,
TotalPages = (int)Math.Ceiling((double)totalCount / pageSize),
Items = dataList
};
}
/// <summary>
/// 我的未办结(当前用户参与过、流程尚未完成的事项)
/// </summary>
/// <param name="userId">当前用户ID</param>
/// <param name="pageQueryDto">分页参数</param>
/// <returns>分页结果</returns>
public PageQueryResultDto<FlowQuerySingleResultDto> QueryMyUnfinished(long userId, PageQueryRequestDto pageQueryDto)
{
// 参数初始化
if (userId <= 0)
return new PageQueryResultDto<FlowQuerySingleResultDto>();
pageQueryDto = pageQueryDto ?? new PageQueryRequestDto();
var pageIndex = pageQueryDto.page < 1 ? 1 : pageQueryDto.page;
var pageSize = pageQueryDto.limit < 1 ? 10 : pageQueryDto.limit;
// 未办结逻辑:
// 1. 当前用户参与过(处理过/待处理)
// 2. 流程状态不是"Completed"(未完成)
var unfinishedInstanceIds = _sqlSugar.Queryable<ZyFlowWorkitem, ZyFlowInstance>((w, i) => new JoinQueryInfos(
JoinType.Inner, w.InstanceId == i.InstanceId))
.Where((w, i) => (w.HandlerId == userId || (w.Status == "Draft" && _auditDeptRoleIds.Contains(w.NodeId)))
&& i.Status != "Completed")
.Select((w, i) => i.InstanceId)
.Distinct()
.ToList();
if (!unfinishedInstanceIds.Any())
return new PageQueryResultDto<FlowQuerySingleResultDto>();
// 分页处理实例ID
var pagedInstanceIds = unfinishedInstanceIds
.Skip((pageIndex - 1) * pageSize)
.Take(pageSize)
.ToList();
var totalCount = unfinishedInstanceIds.Count;
// 组装返回结果
var dataList = new List<FlowQuerySingleResultDto>();
foreach (var instanceId in pagedInstanceIds)
{
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 currentNode = _sqlSugar.Queryable<ZyFlowNode>()
.Where(n => n.NodeId == flowInstance.CurrentNodeId)
.First();
dataList.Add(new FlowQuerySingleResultDto
{
InstanceId = flowInstance.InstanceId,
BusinessNo = flowInstance.BusinessNo,
Title = flowTitle,
NodeName = currentNode?.NodeName ?? string.Empty,
Status = flowInstance.Status,
CreateTime = flowInstance.CreateTime,
InitiatorName = flowInstance.InitiatorName
});
}
return new PageQueryResultDto<FlowQuerySingleResultDto>
{
Page = pageIndex,
Limit = pageSize,
Total = totalCount,
TotalPages = (int)Math.Ceiling((double)totalCount / pageSize),
Items = dataList
};
}
/// <summary>
/// 我的已完成(当前用户参与过、流程已归档完成的事项)
/// </summary>
/// <param name="userId">当前用户ID</param>
/// <param name="pageQueryDto">分页参数</param>
/// <returns>分页结果</returns>
public PageQueryResultDto<FlowQuerySingleResultDto> QueryMyCompleted(long userId, PageQueryRequestDto pageQueryDto)
{
// 参数初始化
if (userId <= 0)
return new PageQueryResultDto<FlowQuerySingleResultDto>();
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<ZyFlowWorkitem, ZyFlowInstance>((w, i) => new JoinQueryInfos(
JoinType.Inner, w.InstanceId == i.InstanceId))
.Where((w, i) => (w.HandlerId == userId || (w.Status == "Draft" && _auditDeptRoleIds.Contains(w.NodeId)))
&& i.Status == "Completed")
.Select((w, i) => i.InstanceId)
.Distinct()
.ToList();
if (!completedInstanceIds.Any())
return new PageQueryResultDto<FlowQuerySingleResultDto>();
// 分页处理实例ID
var pagedInstanceIds = completedInstanceIds
.Skip((pageIndex - 1) * pageSize)
.Take(pageSize)
.ToList();
var totalCount = completedInstanceIds.Count;
// 组装返回结果
var dataList = new List<FlowQuerySingleResultDto>();
foreach (var instanceId in pagedInstanceIds)
{
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 lastNode = _sqlSugar.Queryable<ZyFlowNode>()
.Where(n => n.NodeId == flowInstance.CurrentNodeId)
.First();
dataList.Add(new FlowQuerySingleResultDto
{
InstanceId = flowInstance.InstanceId,
BusinessNo = flowInstance.BusinessNo,
Title = flowTitle,
NodeName = lastNode?.NodeName ?? "流程归档",
Status = flowInstance.Status,
CreateTime = flowInstance.CreateTime,
InitiatorName = flowInstance.InitiatorName
});
}
return new PageQueryResultDto<FlowQuerySingleResultDto>
{
Page = pageIndex,
Limit = pageSize,
Total = totalCount,
TotalPages = (int)Math.Ceiling((double)totalCount / pageSize),
Items = dataList
};
}
/// <summary>
/// 我的全部事项(拟办+待办+已办+未办结+已完成,去重整合)
/// </summary>
/// <param name="userId">当前用户ID</param>
/// <param name="pageQueryDto">分页参数</param>
/// <returns>分页结果</returns>
public PageQueryResultDto<FlowQuerySingleResultDto> QueryMyAllItems(long userId, PageQueryRequestDto pageQueryDto)
{
// 参数初始化
if (userId <= 0)
return new PageQueryResultDto<FlowQuerySingleResultDto>();
pageQueryDto = pageQueryDto ?? new PageQueryRequestDto();
var pageIndex = pageQueryDto.page < 1 ? 1 : pageQueryDto.page;
var pageSize = pageQueryDto.limit < 1 ? 10 : pageQueryDto.limit;
var nodeIdList = _sqlSugar.Queryable<ZyFlowNode>()
// 核心In(主表字段, 子查询Lambda表达式)
.In(
zy => zy.RoleId, // 主表 zy_flow_node 的 role_id 字段
// 子查询:从 sys_userrole 中查询 UserId=2 的 RoleId
_sqlSugar.Queryable<SysUserRole>()
.Where(ur => ur.UserId == userId) // 条件UserId=2注意这里直接用数值避免字符串拼接
.Select(ur => ur.RoleId) // 子查询只返回 RoleId 字段
)
// 主查询只返回 node_id 字段(提升查询性能,避免查询全表字段)
.Select(zy => zy.NodeId)
// 执行查询,返回 List<long> 结果
.ToList();
// todo 未包含拟办,需集成表单后实现
var allQuery = _sqlSugar
.Queryable<ZyFlowWorkitem>()
.LeftJoin<ZyFlowNode>((w, n) => w.NodeId == n.NodeId)
.Where((w, n) => nodeIdList.Contains(w.NodeId))
.OrderByDescending(w => w.ReceiveTime);
var totalCount = allQuery.Count();
var toDoWorkitems = allQuery
.Skip((pageIndex - 1) * pageSize)
.Take(pageSize)
.ToList();
// 组装返回结果
var dataList = new List<FlowQuerySingleResultDto>();
foreach (var workitem in toDoWorkitems)
{
var flowInstance = _sqlSugar.Queryable<ZyFlowInstance>()
.Where(i => i.InstanceId == workitem.InstanceId)
.First();
if (flowInstance == null)
continue;
var zyflowVarList = _sqlSugar.Queryable<ZyFlowVariable>()
.Where(v => v.InstanceId == flowInstance.InstanceId);
var flowTitle = zyflowVarList.Where(v => v.VarKey == "Title").First()?.VarValue;
var type = zyflowVarList.Where(v => v.VarKey == "Type").First()?.VarValue;
dataList.Add(new FlowQuerySingleResultDto
{
InstanceId = flowInstance.InstanceId,
BusinessNo = flowInstance.BusinessNo,
Title = flowTitle,
Type = Int16.Parse(type ?? "0"),
NodeName = workitem.NodeName,
Status = flowInstance.Status,
CreateTime = flowInstance.CreateTime,
InitiatorName = flowInstance.InitiatorName
});
}
return new PageQueryResultDto<FlowQuerySingleResultDto>
{
Page = pageIndex,
Limit = pageSize,
Total = totalCount,
TotalPages = (int)Math.Ceiling((double)totalCount / pageSize),
Items = dataList
};
}
#endregion
#region 三、内部核心辅助方法(仅内部调用,不对外暴露)
/// <summary>
/// 流程节点流转核心方法
/// </summary>
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<long>();
if (!nextNodeIds.Any())
return;
// 查询流程实例
var flowInstance = _sqlSugar.Queryable<ZyFlowInstance>()
.Where(i => i.InstanceId == instanceId)
.First();
if (flowInstance == null)
throw new Exception("流程实例不存在");
// 遍历处理每个下一节点
foreach (var nextNodeId in nextNodeIds)
{
var nextNode = _sqlSugar.Queryable<ZyFlowNode>()
.Where(n => n.NodeId == nextNodeId)
.First();
if (nextNode == null)
continue;
// 更新流程实例当前节点和状态
flowInstance.CurrentNodeId = nextNode.NodeId;
flowInstance.Status = GetFlowStatusByNodeType(nextNode.NodeType);
_sqlSugar.Updateable(flowInstance).ExecuteCommand();
// 并行节点5个审核科为每个审核科创建工作项
if (nextNode.NodeType == "Parallel")
{
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();
}
}
}
/// <summary>
/// 处理并行会签(保存结果+判断是否全部完成)
/// </summary>
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))
{
// 步骤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)
{
// 步骤1判断是否全部审核通过
var hasReject = _sqlSugar.Queryable<ZyFlowParallelAudit>()
.Where(a => a.InstanceId == flowInstance.InstanceId)
.Any(a => a.AuditResult != "Pass");
var isAllPass = !hasReject;
// 步骤2解析汇总节点下一节点归档/退回)
var nextNodeIds = summaryNode.NextNodeIds?.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(long.Parse)
.ToList() ?? new List<long>();
if (nextNodeIds.Count < 2)
throw new Exception("汇总节点需配置2个后续节点归档/退回区县)");
// 步骤3确定目标节点通过→归档[下标0],不通过→退回区县[下标1]
var targetNodeId = isAllPass ? nextNodeIds[0] : nextNodeIds[1];
var targetNode = _sqlSugar.Queryable<ZyFlowNode>()
.Where(n => n.NodeId == targetNodeId)
.First();
if (targetNode == null)
throw new Exception($"汇总节点目标节点【{(isAllPass ? "" : "退")}】不存在");
// 步骤4创建目标节点工作项
var summaryWorkitem = new ZyFlowWorkitem
{
InstanceId = flowInstance.InstanceId,
NodeId = targetNode.NodeId,
NodeName = targetNode.NodeName,
HandlerId = isAllPass ? null : flowInstance.InitiatorId,
HandlerName = isAllPass ? "系统自动处理" : flowInstance.InitiatorName,
Status = isAllPass ? "Done" : "ToDo",
ReceiveTime = DateTime.Now,
HandleTime = isAllPass ? DateTime.Now : null,
Comment = isAllPass ? "所有审核科通过,流程归档完成" : "存在审核不通过项,退回区县修改补充材料"
};
_sqlSugar.Insertable(summaryWorkitem).ExecuteCommand();
// 步骤5更新流程实例最终状态
flowInstance.CurrentNodeId = targetNode.NodeId;
flowInstance.Status = isAllPass ? "Completed" : "Rejected";
flowInstance.FinishTime = isAllPass ? DateTime.Now : null;
_sqlSugar.Updateable(flowInstance).ExecuteCommand();
}
/// <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)
{
// 获取科室用户信息
var userInfo = GetRoleFirstUserInfo(roleId);
if (userInfo.userId <= 0 || string.IsNullOrEmpty(userInfo.userName))
throw new Exception($"审核科角色【{roleId}】未配置有效用户");
// 创建工作项
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();
}
/// <summary>
/// 保存附件并返回路径
/// </summary>
private string SaveAttachments(List<IFormFile> 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<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);
// 保存文件
using (var stream = new FileStream(filePath, FileMode.Create))
{
file.CopyTo(stream);
}
// 保存相对路径(用于前端访问)
attachmentPaths.Add($"/Attachments/FlowAttachments/{fileName}");
}
return string.Join(",", attachmentPaths);
}
/// <summary>
/// 根据节点类型获取流程状态
/// </summary>
private string GetFlowStatusByNodeType(string nodeType)
{
return nodeType switch
{
"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>
/// 获取角色下第一个用户的完整信息
/// </summary>
private (long userId, string userName, string deptName) GetRoleFirstUserInfo(long roleId)
{
var userInfo = _sqlSugar
.Queryable<SysOrg, SysUserOrg, SysUser>((o, uo, u) => new JoinQueryInfos(
JoinType.Inner, o.Id == uo.OrgId && uo.UserId == u.Id))
.Select((o, uo, u) => 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
/// <summary>
/// 保存拟办
/// </summary>
/// <param name="requestDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public object SaveDraft(InitiateFlowRequestDto requestDto)
{
requestDto.BusinessNo = _businessNoGenerator.GenerateBusinessNo("WF");
var attachmentPaths = string.Join(",", requestDto.Attachments);
var user = _auth.GetCurrentUser().User;
var illegalConstructionAssessment = new IllegalConstructionAssessment
{
Id = requestDto.BusinessNo,
Title = requestDto.Title,
BusinessNumber = requestDto.BusinessNo,
Attachments = requestDto.Attachments != null ? attachmentPaths : string.Empty,
AcceptanceTime = DateTime.Now,
Status = "Draft",
CreateTime = DateTime.Now,
CreateUser = user.Name,
UpdateTime = DateTime.Now,
CreateUserId = user.Id
};
_sqlSugar.Insertable(illegalConstructionAssessment).ExecuteCommand();
return true;
}
}