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

993 lines
40 KiB
C#
Raw Normal View History

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-05 09:51:56 +08:00
using OpenAuth.WebApi.Controllers.ServerController;
2026-02-04 20:40:22 +08:00
using SqlSugar;
namespace OpenAuth.App.workflow;
2026-02-05 09:08:32 +08:00
/// <summary>
/// 完整工作流引擎适配区县→执法监督科→5个审核科会签→汇总归档/退回)
2026-02-05 09:51:56 +08:00
/// 包含:流程发起/处理 + 拟办/待办/已办/未办结/已完成/全部事项查询
2026-02-05 09:08:32 +08:00
/// </summary>
public class WorkflowEngineApp : SqlSugarBaseApp<SysCategoryType, SugarDbContext>
2026-02-04 20:40:22 +08:00
{
private readonly ISqlSugarClient _sqlSugar;
private readonly BusinessNoGenerator _businessNoGenerator;
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,BusinessNoGenerator businessNoGenerator) : base(unitWork, repository, auth)
2026-02-04 20:40:22 +08:00
{
2026-02-05 09:08:32 +08:00
_sqlSugar = Repository.AsSugarClient();
_businessNoGenerator = businessNoGenerator;
2026-02-04 21:43:24 +08:00
}
2026-02-04 20:40:22 +08:00
2026-02-05 09:51:56 +08:00
#region 一、核心业务:流程发起/处理
2026-02-04 20:40:22 +08:00
2026-02-05 09:51:56 +08:00
/// <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)
2026-02-04 21:43:24 +08:00
{
2026-02-05 09:08:32 +08:00
// 参数校验
/*if (string.IsNullOrEmpty(requestDto.FlowCode) || string.IsNullOrEmpty(requestDto.BusinessNo))
throw new Exception("流程编码和业务编号不能为空");*/
2026-02-05 09:08:32 +08:00
if (userId <= 0 || string.IsNullOrEmpty(userName))
throw new Exception("发起人ID和姓名不能为空");
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>()
2026-02-05 09:51:56 +08:00
.Where(t => t.FlowCode == requestDto.FlowCode && t.IsEnabled == true)
2026-02-04 20:40:22 +08:00
.First();
2026-02-05 09:08:32 +08:00
if (template == null)
2026-02-05 09:51:56 +08:00
throw new Exception($"流程模板【{requestDto.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("流程开始节点【区县提交】不存在,请配置节点");
var prefix = "wfjszl";
var businessNo = _businessNoGenerator.GenerateBusinessNo(prefix);
requestDto.BusinessNo = businessNo;
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,
2026-02-05 09:51:56 +08:00
BusinessNo = requestDto.BusinessNo,
2026-02-05 09:08:32 +08:00
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-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(requestDto.Attachments);
var attachmentPaths = string.Join(",", requestDto.Attachments);
2026-02-05 09:08:32 +08:00
var flowVariables = new List<ZyFlowVariable>
{
new() { InstanceId = instanceId, VarKey = "Title", VarValue = requestDto.Title },
new() { InstanceId = instanceId, VarKey = "AttachmentPaths", VarValue = attachmentPaths },
new()
2026-02-05 09:08:32 +08:00
{
InstanceId = instanceId, VarKey = "SubmitTime",
VarValue = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
},
new() { InstanceId = instanceId, VarKey = "Type", VarValue = requestDto.type.ToString() },
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
// 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();
}
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:51:56 +08:00
/// <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)
2026-02-05 09:08:32 +08:00
{
// 参数校验
2026-02-05 09:51:56 +08:00
if (requestDto.WorkitemId <= 0)
2026-02-05 09:08:32 +08:00
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>()
2026-02-05 09:51:56 +08:00
.Where(w => w.WorkitemId == requestDto.WorkitemId && w.Status == "ToDo")
2026-02-05 09:08:32 +08:00
.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;
2026-02-05 09:51:56 +08:00
workitem.Comment = string.IsNullOrEmpty(requestDto.Comment) ? "处理完成" : requestDto.Comment;
2026-02-05 09:08:32 +08:00
_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):
2026-02-05 09:51:56 +08:00
ProcessParallelAudit(flowInstance.InstanceId, currentNode.NodeId, userId, userName, requestDto);
2026-02-05 09:08:32 +08:00
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:51:56 +08:00
#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;
2026-02-05 09:51:56 +08:00
var userIdStr = userId.ToString();
// 拟办不应该是针对个人的吗?
2026-02-05 09:51:56 +08:00
// 拟办逻辑:工作项状态为"Draft"(拟办)、对应角色匹配当前用户、未分配具体处理人
var draftQuery = _sqlSugar
.Queryable<IllegalConstructionAssessment>()
.Where(w => w.Status == "Draft" && w.CreateUser == userIdStr)
.OrderByDescending(w => w.ReceiveTime);
2026-02-05 09:51:56 +08:00
// 分页查询
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,
2026-02-05 09:51:56 +08:00
Status = "Draft", // 拟办状态
CreateTime = workitem.CreateTime,
//InitiatorName = workitem.CreateUser //发起人姓名
2026-02-05 09:51:56 +08:00
});
}
return new PageQueryResultDto<FlowQuerySingleResultDto>
{
Page = pageIndex,
Limit = pageSize,
Total = totalCount,
2026-02-05 09:51:56 +08:00
TotalPages = (int)Math.Ceiling((double)totalCount / pageSize),
Items = dataList
2026-02-05 09:51:56 +08:00
};
}
2026-02-04 20:40:22 +08:00
2026-02-05 09:08:32 +08:00
/// <summary>
2026-02-05 09:51:56 +08:00
/// 我的待办(已分配给当前用户、待处理的事项)
2026-02-05 09:08:32 +08:00
/// </summary>
2026-02-05 09:51:56 +08:00
/// <param name="userId">当前用户ID</param>
/// <param name="pageQueryDto">分页参数</param>
/// <returns>分页结果</returns>
public PageQueryResultDto<FlowQuerySingleResultDto> QueryMyToDo(long userId, PageQueryRequestDto pageQueryDto)
2026-02-05 09:08:32 +08:00
{
2026-02-05 09:51:56 +08:00
// 参数初始化
2026-02-05 09:08:32 +08:00
if (userId <= 0)
2026-02-05 09:51:56 +08:00
return new PageQueryResultDto<FlowQuerySingleResultDto>();
pageQueryDto = pageQueryDto ?? new PageQueryRequestDto();
var pageIndex = pageQueryDto.page < 1 ? 1 : pageQueryDto.page;
var pageSize = pageQueryDto.limit < 1 ? 10 : pageQueryDto.limit;
2026-02-05 09:51:56 +08:00
// 待办逻辑:工作项状态为"ToDo"、处理人ID为当前用户
// todo 这种分页不行,这是内存分页要改
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")
2026-02-05 09:51:56 +08:00
.OrderByDescending(w => w.ReceiveTime);
// 分页查询
var totalCount = toDoQuery.Count();
var toDoWorkitems = toDoQuery
.Skip((pageIndex - 1) * pageSize)
.Take(pageSize)
2026-02-04 21:43:24 +08:00
.ToList();
2026-02-04 20:40:22 +08:00
2026-02-05 09:51:56 +08:00
// 组装返回结果
var dataList = new List<FlowQuerySingleResultDto>();
2026-02-05 09:08:32 +08:00
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:51:56 +08:00
dataList.Add(new FlowQuerySingleResultDto
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:51:56 +08:00
return new PageQueryResultDto<FlowQuerySingleResultDto>
{
Page = pageIndex,
Limit = pageSize,
Total = totalCount,
2026-02-05 09:51:56 +08:00
TotalPages = (int)Math.Ceiling((double)totalCount / pageSize),
Items = dataList
2026-02-05 09:51:56 +08:00
};
2026-02-04 20:40:22 +08:00
}
2026-02-05 09:08:32 +08:00
/// <summary>
2026-02-05 09:51:56 +08:00
/// 我的已办(当前用户已处理完成的事项)
2026-02-05 09:08:32 +08:00
/// </summary>
2026-02-05 09:51:56 +08:00
/// <param name="userId">当前用户ID</param>
/// <param name="pageQueryDto">分页参数</param>
/// <returns>分页结果</returns>
public PageQueryResultDto<FlowQuerySingleResultDto> QueryMyDone(long userId, PageQueryRequestDto pageQueryDto)
2026-02-04 20:40:22 +08:00
{
2026-02-05 09:51:56 +08:00
// 参数初始化
2026-02-05 09:08:32 +08:00
if (userId <= 0)
2026-02-05 09:51:56 +08:00
return new PageQueryResultDto<FlowQuerySingleResultDto>();
pageQueryDto = pageQueryDto ?? new PageQueryRequestDto();
var pageIndex = pageQueryDto.page < 1 ? 1 : pageQueryDto.page;
var pageSize = pageQueryDto.limit < 1 ? 10 : pageQueryDto.limit;
2026-02-05 09:08:32 +08:00
2026-02-05 09:51:56 +08:00
// 已办逻辑:工作项状态为"Done"、处理人ID为当前用户去重流程实例
2026-02-05 09:08:32 +08:00
var doneInstanceIds = _sqlSugar.Queryable<ZyFlowWorkitem>()
.Where(w => w.HandlerId == userId && w.Status == "Done")
.Select(w => w.InstanceId)
.Distinct()
.ToList();
2026-02-05 09:51:56 +08:00
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)
2026-02-05 09:08:32 +08:00
{
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>()
2026-02-05 09:51:56 +08:00
.Where(w => w.InstanceId == instanceId && w.HandlerId == userId && w.Status == "Done")
2026-02-05 09:08:32 +08:00
.OrderByDescending(w => w.HandleTime)
.First();
2026-02-04 20:40:22 +08:00
2026-02-05 09:51:56 +08:00
dataList.Add(new FlowQuerySingleResultDto
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:51:56 +08:00
return new PageQueryResultDto<FlowQuerySingleResultDto>
{
Page = pageIndex,
Limit = pageSize,
Total = totalCount,
2026-02-05 09:51:56 +08:00
TotalPages = (int)Math.Ceiling((double)totalCount / pageSize),
Items = dataList
2026-02-05 09:51:56 +08:00
};
}
/// <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;
2026-02-05 09:51:56 +08:00
// 未办结逻辑:
// 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,
2026-02-05 09:51:56 +08:00
TotalPages = (int)Math.Ceiling((double)totalCount / pageSize),
Items = dataList
2026-02-05 09:51:56 +08:00
};
}
/// <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;
2026-02-05 09:51:56 +08:00
// 已完成逻辑:
// 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,
2026-02-05 09:51:56 +08:00
TotalPages = (int)Math.Ceiling((double)totalCount / pageSize),
Items = dataList
2026-02-05 09:51:56 +08:00
};
}
/// <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;
2026-02-05 09:51:56 +08:00
// 全部事项逻辑:当前用户相关的所有流程实例(去重)
var allInstanceIds = _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.InitiatorId == userId)
.Select((w, i) => i.InstanceId)
.Distinct()
.ToList();
if (!allInstanceIds.Any())
return new PageQueryResultDto<FlowQuerySingleResultDto>();
// 分页处理实例ID
var pagedInstanceIds = allInstanceIds
.Skip((pageIndex - 1) * pageSize)
.Take(pageSize)
.ToList();
var totalCount = allInstanceIds.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,
2026-02-05 09:51:56 +08:00
TotalPages = (int)Math.Ceiling((double)totalCount / pageSize),
Items = dataList
2026-02-05 09:51:56 +08:00
};
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-05 09:51:56 +08:00
#region 三、内部核心辅助方法(仅内部调用,不对外暴露)
2026-02-05 09:08:32 +08:00
/// <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,
2026-02-05 09:51:56 +08:00
HandleWorkitemRequestDto requestDto)
2026-02-05 09:08:32 +08:00
{
// 校验会签结果
2026-02-05 09:51:56 +08:00
if (string.IsNullOrEmpty(requestDto.AuditResult))
2026-02-05 09:08:32 +08:00
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,
2026-02-05 09:51:56 +08:00
AuditResult = requestDto.AuditResult,
AuditComment = requestDto.Comment,
2026-02-05 09:08:32 +08:00
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判断是否全部审核通过
2026-02-05 09:51:56 +08:00
var hasReject = _sqlSugar.Queryable<ZyFlowParallelAudit>()
2026-02-05 09:08:32 +08:00
.Where(a => a.InstanceId == flowInstance.InstanceId)
2026-02-05 09:51:56 +08:00
.Any(a => a.AuditResult != "Pass");
var isAllPass = !hasReject;
2026-02-05 09:08:32 +08:00
// 步骤2解析汇总节点下一节点归档/退回)
var nextNodeIds = summaryNode.NextNodeIds?.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(long.Parse)
.ToList() ?? new List<long>();
if (nextNodeIds.Count < 2)
throw new Exception("汇总节点需配置2个后续节点归档/退回区县)");
2026-02-05 09:51:56 +08:00
// 步骤3确定目标节点通过→归档[下标0],不通过→退回区县[下标1]
var targetNodeId = isAllPass ? nextNodeIds[0] : nextNodeIds[1];
2026-02-05 09:08:32 +08:00
var targetNode = _sqlSugar.Queryable<ZyFlowNode>()
.Where(n => n.NodeId == targetNodeId)
.First();
if (targetNode == null)
2026-02-05 09:51:56 +08:00
throw new Exception($"汇总节点目标节点【{(isAllPass ? "" : "退")}】不存在");
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,
2026-02-05 09:51:56 +08:00
Comment = isAllPass ? "所有审核科通过,流程归档完成" : "存在审核不通过项,退回区县修改补充材料"
2026-02-05 09:08:32 +08:00
};
_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
// 获取科室用户信息
2026-02-05 09:51:56 +08:00
var userInfo = GetRoleFirstUserInfo(roleId);
if (userInfo.userId <= 0 || string.IsNullOrEmpty(userInfo.userName))
2026-02-05 09:08:32 +08:00
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,
2026-02-05 09:51:56 +08:00
NodeName = $"{parallelNode.NodeName}{userInfo.deptName}",
HandlerId = userInfo.userId,
HandlerName = userInfo.userName,
2026-02-05 09:08:32 +08:00
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(AppContext.BaseDirectory, "Attachments/FlowAttachments");
2026-02-05 09:08:32 +08:00
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>
/// 获取角色下第一个用户的完整信息
/// </summary>
private (long userId, string userName, string deptName) GetRoleFirstUserInfo(long roleId)
{
2026-02-05 09:51:56 +08:00
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
})
2026-02-05 09:08:32 +08:00
.First();
2026-02-05 09:51:56 +08:00
if (userInfo == null)
return (0, string.Empty, string.Empty);
2026-02-05 09:08:32 +08:00
return (userInfo.UserId, userInfo.UserName ?? string.Empty, userInfo.DeptName ?? string.Empty);
2026-02-04 20:40:22 +08:00
}
#endregion
/// <summary>
/// 保存拟办
/// </summary>
/// <param name="requestDto"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public object SaveDraft(InitiateFlowRequestDto requestDto)
{
var attachmentPaths = string.Join(",", requestDto.Attachments);
var user = _auth.GetCurrentUser().User;
var illegalConstructionAssessment = new IllegalConstructionAssessment
{
// todo 业务编码生成逻辑待完善
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,
};
_sqlSugar.Insertable(illegalConstructionAssessment).ExecuteCommand();
return true;
}
2026-02-04 20:40:22 +08:00
}