FlowEngineService.cs 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142
  1. using System.Text.Json;
  2. using Admin.NET.Core.Service;
  3. namespace Admin.NET.Plugin.ApprovalFlow.Service;
  4. /// <summary>
  5. /// 流程推进引擎 — 核心状态机
  6. /// 不暴露为 API,由其他 Service 内部调用
  7. /// </summary>
  8. public class FlowEngineService : ITransient
  9. {
  10. private readonly SqlSugarRepository<ApprovalFlow> _flowRep;
  11. private readonly SqlSugarRepository<ApprovalFlowInstance> _instanceRep;
  12. private readonly SqlSugarRepository<ApprovalFlowTask> _taskRep;
  13. private readonly SqlSugarRepository<ApprovalFlowLog> _logRep;
  14. private readonly SqlSugarRepository<SysUserRole> _userRoleRep;
  15. private readonly SqlSugarRepository<SysUser> _userRep;
  16. private readonly SqlSugarRepository<SysOrg> _orgRep;
  17. private readonly SqlSugarRepository<ApprovalFlowDelegate> _delegateRep;
  18. private readonly SqlSugarRepository<ApprovalFlowCompletedNode> _completedNodeRep;
  19. private readonly UserManager _userManager;
  20. private readonly SysOrgService _sysOrgService;
  21. private readonly FlowNotifyService _notifyService;
  22. public FlowEngineService(
  23. SqlSugarRepository<ApprovalFlow> flowRep,
  24. SqlSugarRepository<ApprovalFlowInstance> instanceRep,
  25. SqlSugarRepository<ApprovalFlowTask> taskRep,
  26. SqlSugarRepository<ApprovalFlowLog> logRep,
  27. SqlSugarRepository<SysUserRole> userRoleRep,
  28. SqlSugarRepository<SysUser> userRep,
  29. SqlSugarRepository<SysOrg> orgRep,
  30. SqlSugarRepository<ApprovalFlowDelegate> delegateRep,
  31. SqlSugarRepository<ApprovalFlowCompletedNode> completedNodeRep,
  32. UserManager userManager,
  33. SysOrgService sysOrgService,
  34. FlowNotifyService notifyService)
  35. {
  36. _flowRep = flowRep;
  37. _instanceRep = instanceRep;
  38. _taskRep = taskRep;
  39. _logRep = logRep;
  40. _userRoleRep = userRoleRep;
  41. _userRep = userRep;
  42. _orgRep = orgRep;
  43. _delegateRep = delegateRep;
  44. _completedNodeRep = completedNodeRep;
  45. _userManager = userManager;
  46. _sysOrgService = sysOrgService;
  47. _notifyService = notifyService;
  48. }
  49. /// <summary>
  50. /// 机构数据权限依赖实体的 OrgId;插入时必须与当前用户机构一致,否则非超管在「我发起的/待办」中查不到(OrgId=0 会被过滤)。
  51. /// </summary>
  52. private async Task<long> ResolveOrgIdForNewFlowEntityAsync()
  53. {
  54. var oid = _userManager.OrgId;
  55. if (oid > 0) return oid;
  56. var list = await _sysOrgService.GetUserOrgIdList();
  57. return list is { Count: > 0 } ? list[0] : 0;
  58. }
  59. // ═══════════════════════════════════════════
  60. // 核心生命周期
  61. // ═══════════════════════════════════════════
  62. /// <summary>
  63. /// 发起流程
  64. /// </summary>
  65. public async Task<long> StartFlow(StartFlowInput input)
  66. {
  67. var flow = await _flowRep.AsQueryable()
  68. .Where(u => u.BizType == input.BizType && u.IsPublished && !u.IsDelete)
  69. .OrderByDescending(u => u.Version)
  70. .FirstAsync() ?? throw Oops.Oh($"未找到业务类型 [{input.BizType}] 的已发布流程定义");
  71. if (string.IsNullOrWhiteSpace(flow.FlowJson))
  72. throw Oops.Oh("流程定义的 FlowJson 为空,请先设计流程图");
  73. var flowData = JsonSerializer.Deserialize<ApprovalFlowItem>(flow.FlowJson)
  74. ?? throw Oops.Oh("FlowJson 反序列化失败");
  75. var orgId = await ResolveOrgIdForNewFlowEntityAsync();
  76. var instance = new ApprovalFlowInstance
  77. {
  78. FlowId = flow.Id,
  79. FlowVersion = flow.Version,
  80. BizType = input.BizType,
  81. BizId = input.BizId,
  82. BizNo = input.BizNo,
  83. Title = input.Title ?? $"{flow.Name}-{input.BizNo}",
  84. InitiatorId = _userManager.UserId,
  85. InitiatorName = _userManager.RealName,
  86. Status = FlowInstanceStatusEnum.Running,
  87. FlowJsonSnapshot = flow.FlowJson,
  88. StartTime = DateTime.Now,
  89. OrgId = orgId,
  90. };
  91. var startNode = flowData.Nodes.FirstOrDefault(n =>
  92. n.Type is "bpmn:startEvent" or "start-node")
  93. ?? throw Oops.Oh("流程图中未找到开始节点");
  94. await _instanceRep.InsertAsync(instance);
  95. await WriteLog(instance.Id, null, startNode.Id, FlowLogActionEnum.Submit, input.Comment);
  96. // 开始节点登记为已完成,随后沿出边推进(兼容首节点即为并行网关 Fork 的拓扑)
  97. await MarkNodeCompleted(instance.Id, startNode);
  98. var firstOutgoing = flowData.Edges.Where(e => e.SourceNodeId == startNode.Id).Select(e => e.TargetNodeId).ToList();
  99. if (firstOutgoing.Count == 0)
  100. throw Oops.Oh("流程图开始节点未连接任何后继节点");
  101. foreach (var target in firstOutgoing)
  102. {
  103. await ProcessNextNode(instance, flowData, target);
  104. }
  105. await InvokeHandler(input.BizType, h => h.OnFlowStarted(input.BizId, instance.Id));
  106. return instance.Id;
  107. }
  108. /// <summary>
  109. /// 同意
  110. /// </summary>
  111. public async Task Approve(long taskId, string? comment)
  112. {
  113. var task = await GetPendingTask(taskId);
  114. task.Status = FlowTaskStatusEnum.Approved;
  115. task.Comment = comment;
  116. task.ActionTime = DateTime.Now;
  117. await _taskRep.AsUpdateable(task).ExecuteCommandAsync();
  118. // P1-6 审批代理:同步取消配对的本人/代理任务
  119. await CancelPairedDelegateTask(task);
  120. await WriteLog(task.InstanceId, taskId, task.NodeId, FlowLogActionEnum.Approve, comment);
  121. var instance = await _instanceRep.GetByIdAsync(task.InstanceId)
  122. ?? throw Oops.Oh("流程实例不存在");
  123. if (await IsNodeCompleted(instance, task.NodeId))
  124. {
  125. await InvokeHandler(instance.BizType,
  126. h => h.OnNodeCompleted(instance.BizId, task.NodeId, task.NodeName ?? ""));
  127. var flowData = DeserializeFlowJson(instance.FlowJsonSnapshot);
  128. await AdvanceToNext(instance, flowData, task.NodeId);
  129. }
  130. }
  131. /// <summary>
  132. /// 拒绝
  133. /// </summary>
  134. public async Task Reject(long taskId, string? comment)
  135. {
  136. var task = await GetPendingTask(taskId);
  137. task.Status = FlowTaskStatusEnum.Rejected;
  138. task.Comment = comment;
  139. task.ActionTime = DateTime.Now;
  140. await _taskRep.AsUpdateable(task).ExecuteCommandAsync();
  141. // Reject 导致流程终止:取消整个实例所有剩余 Pending 任务(含并行分支、配对代理任务等)
  142. await CancelAllPendingTasks(task.InstanceId, task.Id);
  143. var instance = await _instanceRep.GetByIdAsync(task.InstanceId)
  144. ?? throw Oops.Oh("流程实例不存在");
  145. instance.Status = FlowInstanceStatusEnum.Rejected;
  146. instance.EndTime = DateTime.Now;
  147. await _instanceRep.AsUpdateable(instance).ExecuteCommandAsync();
  148. await WriteLog(instance.Id, taskId, task.NodeId, FlowLogActionEnum.Reject, comment);
  149. await InvokeHandler(instance.BizType,
  150. h => h.OnFlowCompleted(instance.BizId, FlowInstanceStatusEnum.Rejected));
  151. await _notifyService.NotifyFlowCompleted(instance.InitiatorId, instance.Id, instance.Title, FlowInstanceStatusEnum.Rejected);
  152. }
  153. // ═══════════════════════════════════════════
  154. // 扩展操作
  155. // ═══════════════════════════════════════════
  156. /// <summary>
  157. /// 转办
  158. /// </summary>
  159. public async Task Transfer(long taskId, long targetUserId, string? comment)
  160. {
  161. var task = await GetPendingTask(taskId);
  162. task.Status = FlowTaskStatusEnum.Transferred;
  163. task.Comment = comment;
  164. task.ActionTime = DateTime.Now;
  165. task.TransferToId = targetUserId;
  166. await _taskRep.AsUpdateable(task).ExecuteCommandAsync();
  167. var targetUser = await _userRep.GetByIdAsync(targetUserId);
  168. var instForOrg = await _instanceRep.GetByIdAsync(task.InstanceId);
  169. var newTask = new ApprovalFlowTask
  170. {
  171. InstanceId = task.InstanceId,
  172. NodeId = task.NodeId,
  173. NodeName = task.NodeName,
  174. AssigneeId = targetUserId,
  175. AssigneeName = targetUser?.RealName,
  176. Status = FlowTaskStatusEnum.Pending,
  177. OrgId = instForOrg?.OrgId ?? 0,
  178. };
  179. await _taskRep.InsertAsync(newTask);
  180. await WriteLog(task.InstanceId, taskId, task.NodeId, FlowLogActionEnum.Transfer,
  181. $"{comment} → 转办给 {targetUser?.RealName}");
  182. var instance = await _instanceRep.GetByIdAsync(task.InstanceId);
  183. await _notifyService.NotifyTransferred(targetUserId, task.InstanceId, instance?.Title ?? "", _userManager.RealName);
  184. }
  185. /// <summary>
  186. /// 撤回(发起人撤回)
  187. /// </summary>
  188. public async Task Withdraw(long instanceId)
  189. {
  190. var instance = await _instanceRep.GetByIdAsync(instanceId)
  191. ?? throw Oops.Oh("流程实例不存在");
  192. if (instance.InitiatorId != _userManager.UserId)
  193. throw Oops.Oh("只有发起人可以撤回");
  194. if (instance.Status != FlowInstanceStatusEnum.Running)
  195. throw Oops.Oh("当前流程状态不允许撤回");
  196. var pendingTasks = await _taskRep.AsQueryable()
  197. .Where(t => t.InstanceId == instanceId && t.Status == FlowTaskStatusEnum.Pending)
  198. .ToListAsync();
  199. var doneTasks = await _taskRep.AsQueryable()
  200. .Where(t => t.InstanceId == instanceId &&
  201. t.Status != FlowTaskStatusEnum.Pending &&
  202. t.Status != FlowTaskStatusEnum.Cancelled)
  203. .CountAsync();
  204. if (doneTasks > 0)
  205. throw Oops.Oh("已有人审批过,不可撤回");
  206. var cancelledUserIds = pendingTasks.Select(t => t.AssigneeId).Distinct().ToList();
  207. foreach (var t in pendingTasks)
  208. {
  209. t.Status = FlowTaskStatusEnum.Cancelled;
  210. t.ActionTime = DateTime.Now;
  211. }
  212. await _taskRep.AsUpdateable(pendingTasks).ExecuteCommandAsync();
  213. instance.Status = FlowInstanceStatusEnum.Cancelled;
  214. instance.EndTime = DateTime.Now;
  215. await _instanceRep.AsUpdateable(instance).ExecuteCommandAsync();
  216. await WriteLog(instanceId, null, instance.CurrentNodeId, FlowLogActionEnum.Withdraw, null);
  217. await InvokeHandler(instance.BizType,
  218. h => h.OnFlowCompleted(instance.BizId, FlowInstanceStatusEnum.Cancelled));
  219. await _notifyService.NotifyWithdrawn(cancelledUserIds, instanceId, instance.Title, instance.InitiatorName);
  220. }
  221. /// <summary>
  222. /// 退回上一步
  223. /// </summary>
  224. public async Task ReturnToPrev(long taskId, string? comment)
  225. {
  226. var task = await GetPendingTask(taskId);
  227. var instance = await _instanceRep.GetByIdAsync(task.InstanceId)
  228. ?? throw Oops.Oh("流程实例不存在");
  229. var flowData = DeserializeFlowJson(instance.FlowJsonSnapshot);
  230. var prevNodeId = FindPrevUserTaskNodeId(flowData, task.NodeId);
  231. if (prevNodeId == null)
  232. throw Oops.Oh("已是第一个审批节点,无法退回");
  233. await CancelPendingTasks(task.InstanceId, task.NodeId);
  234. task.Status = FlowTaskStatusEnum.Returned;
  235. task.Comment = comment;
  236. task.ActionTime = DateTime.Now;
  237. await _taskRep.AsUpdateable(task).ExecuteCommandAsync();
  238. instance.CurrentNodeId = prevNodeId;
  239. await _instanceRep.AsUpdateable(instance).ExecuteCommandAsync();
  240. await CreateTasksForNode(instance, flowData, prevNodeId);
  241. await WriteLog(instance.Id, taskId, task.NodeId, FlowLogActionEnum.Return, comment);
  242. var returnedTasks = await _taskRep.AsQueryable()
  243. .Where(t => t.InstanceId == instance.Id && t.NodeId == prevNodeId && t.Status == FlowTaskStatusEnum.Pending)
  244. .ToListAsync();
  245. var returnedUserIds = returnedTasks.Select(t => t.AssigneeId).Distinct().ToList();
  246. await _notifyService.NotifyReturned(returnedUserIds, instance.Id, instance.Title, _userManager.RealName);
  247. }
  248. /// <summary>
  249. /// 加签
  250. /// </summary>
  251. public async Task AddSign(long taskId, long targetUserId, string? comment)
  252. {
  253. var task = await GetPendingTask(taskId);
  254. var targetUser = await _userRep.GetByIdAsync(targetUserId);
  255. var instForAddSign = await _instanceRep.GetByIdAsync(task.InstanceId);
  256. var newTask = new ApprovalFlowTask
  257. {
  258. InstanceId = task.InstanceId,
  259. NodeId = task.NodeId,
  260. NodeName = task.NodeName,
  261. AssigneeId = targetUserId,
  262. AssigneeName = targetUser?.RealName,
  263. Status = FlowTaskStatusEnum.Pending,
  264. IsAddSign = true,
  265. AddSignById = _userManager.UserId,
  266. OrgId = instForAddSign?.OrgId ?? 0,
  267. };
  268. await _taskRep.InsertAsync(newTask);
  269. await WriteLog(task.InstanceId, taskId, task.NodeId, FlowLogActionEnum.AddSign,
  270. $"{comment} → 加签给 {targetUser?.RealName}");
  271. var instance = await _instanceRep.GetByIdAsync(task.InstanceId);
  272. await _notifyService.NotifyAddSign(targetUserId, task.InstanceId, instance?.Title ?? "", _userManager.RealName);
  273. }
  274. /// <summary>
  275. /// 手动升级 — 当前审批人主动将任务升级到更高层级
  276. /// </summary>
  277. public async Task Escalate(long taskId, string? comment)
  278. {
  279. var task = await GetPendingTask(taskId);
  280. var instance = await _instanceRep.GetByIdAsync(task.InstanceId)
  281. ?? throw Oops.Oh("流程实例不存在");
  282. var flowData = DeserializeFlowJson(instance.FlowJsonSnapshot);
  283. var node = flowData.Nodes.FirstOrDefault(n => n.Id == task.NodeId)
  284. ?? throw Oops.Oh("节点不存在");
  285. var props = node.Properties;
  286. if (props?.EnableManualEscalation != true
  287. || string.IsNullOrWhiteSpace(props.EscalationApproverType)
  288. || string.IsNullOrWhiteSpace(props.EscalationApproverIds))
  289. throw Oops.Oh("该节点未配置升级目标,无法升级");
  290. task.Status = FlowTaskStatusEnum.Escalated;
  291. task.Comment = comment;
  292. task.ActionTime = DateTime.Now;
  293. await _taskRep.AsUpdateable(task).ExecuteCommandAsync();
  294. await CancelPendingTasks(task.InstanceId, task.NodeId, task.Id);
  295. var escalationApprovers = await ResolveApprovers(
  296. new FlowProperties
  297. {
  298. ApproverType = props.EscalationApproverType,
  299. ApproverIds = props.EscalationApproverIds,
  300. ApproverNames = props.EscalationApproverNames,
  301. },
  302. instance.InitiatorId);
  303. if (escalationApprovers.Count == 0)
  304. throw Oops.Oh("升级目标审批人列表为空");
  305. var newTasks = escalationApprovers.Select(a => new ApprovalFlowTask
  306. {
  307. InstanceId = instance.Id,
  308. NodeId = task.NodeId,
  309. NodeName = task.NodeName,
  310. AssigneeId = a.userId,
  311. AssigneeName = a.userName,
  312. Status = FlowTaskStatusEnum.Pending,
  313. OrgId = instance.OrgId,
  314. }).ToList();
  315. await _taskRep.AsInsertable(newTasks).ExecuteCommandAsync();
  316. var targetNames = string.Join(", ", escalationApprovers.Select(a => a.userName));
  317. await WriteLog(instance.Id, taskId, task.NodeId, FlowLogActionEnum.Escalate,
  318. $"{comment} → 升级给 {targetNames}");
  319. var targetUserIds = escalationApprovers.Select(a => a.userId).Distinct().ToList();
  320. await _notifyService.NotifyEscalated(targetUserIds, instance.Id, instance.Title,
  321. _userManager.RealName, task.NodeName);
  322. }
  323. /// <summary>
  324. /// 催办
  325. /// </summary>
  326. public async Task Urge(long instanceId)
  327. {
  328. var instance = await _instanceRep.GetByIdAsync(instanceId)
  329. ?? throw Oops.Oh("流程实例不存在");
  330. if (instance.Status != FlowInstanceStatusEnum.Running)
  331. throw Oops.Oh("当前流程不在审批中");
  332. await WriteLog(instanceId, null, instance.CurrentNodeId, FlowLogActionEnum.Urge, "催办");
  333. var pendingTasks = await _taskRep.AsQueryable()
  334. .Where(t => t.InstanceId == instanceId && t.Status == FlowTaskStatusEnum.Pending)
  335. .ToListAsync();
  336. var userIds = pendingTasks.Select(t => t.AssigneeId).Distinct().ToList();
  337. await _notifyService.NotifyUrge(userIds, instanceId, instance.Title);
  338. }
  339. // ═══════════════════════════════════════════
  340. // 超时自动处理(由 FlowTimeoutJob 调用,无 UserManager 上下文)
  341. // ═══════════════════════════════════════════
  342. /// <summary>
  343. /// 扫描所有 Pending 且已超过 timeoutHours 的任务,按节点 timeoutAction 执行对应动作。
  344. /// 与 FlowTimeoutJob 扫描等价,抽出公共方法以便管理员接口立即触发(运维/E2E 测试用)。
  345. /// 返回本次处理的任务条数。
  346. /// </summary>
  347. public async Task<int> ScanTimeoutTasks(CancellationToken stoppingToken = default)
  348. {
  349. var pendingTasks = await _taskRep.Context.Queryable<ApprovalFlowTask>()
  350. .InnerJoin<ApprovalFlowInstance>((t, i) => t.InstanceId == i.Id)
  351. .Where((t, i) => t.Status == FlowTaskStatusEnum.Pending
  352. && i.Status == FlowInstanceStatusEnum.Running)
  353. .Select((t, i) => new
  354. {
  355. TaskId = t.Id,
  356. TaskCreatedAt = t.CreateTime,
  357. NodeId = t.NodeId,
  358. FlowJsonSnapshot = i.FlowJsonSnapshot,
  359. })
  360. .ToListAsync();
  361. var now = DateTime.Now;
  362. var processed = 0;
  363. foreach (var item in pendingTasks)
  364. {
  365. if (stoppingToken.IsCancellationRequested) break;
  366. if (string.IsNullOrWhiteSpace(item.FlowJsonSnapshot)) continue;
  367. ApprovalFlowItem? flowData;
  368. try { flowData = JsonSerializer.Deserialize<ApprovalFlowItem>(item.FlowJsonSnapshot); }
  369. catch { continue; }
  370. var node = flowData?.Nodes?.FirstOrDefault(n => n.Id == item.NodeId);
  371. var props = node?.Properties;
  372. if (props?.TimeoutHours == null || props.TimeoutHours <= 0) continue;
  373. if (string.IsNullOrWhiteSpace(props.TimeoutAction)) continue;
  374. var deadline = item.TaskCreatedAt.AddHours(props.TimeoutHours.Value);
  375. if (now < deadline) continue;
  376. // Notify 动作幂等:已记录过 AutoTimeout 日志的跳过,避免每轮重复催办
  377. if (props.TimeoutAction == "Notify")
  378. {
  379. var alreadyNotified = await _logRep.AsQueryable()
  380. .AnyAsync(log => log.TaskId == item.TaskId
  381. && log.Action == FlowLogActionEnum.AutoTimeout);
  382. if (alreadyNotified) continue;
  383. }
  384. try
  385. {
  386. await HandleTimeoutTask(item.TaskId);
  387. processed++;
  388. }
  389. catch
  390. {
  391. // 单任务异常隔离:吞掉继续下一个,由 Job/Admin API 层统一记日志
  392. }
  393. }
  394. return processed;
  395. }
  396. /// <summary>
  397. /// 处理单个超时任务(由定时任务调用)
  398. /// </summary>
  399. public async Task HandleTimeoutTask(long taskId)
  400. {
  401. var task = await _taskRep.GetByIdAsync(taskId);
  402. if (task == null || task.Status != FlowTaskStatusEnum.Pending) return;
  403. var instance = await _instanceRep.GetByIdAsync(task.InstanceId);
  404. if (instance == null || instance.Status != FlowInstanceStatusEnum.Running) return;
  405. var flowData = DeserializeFlowJson(instance.FlowJsonSnapshot);
  406. var node = flowData.Nodes.FirstOrDefault(n => n.Id == task.NodeId);
  407. var props = node?.Properties;
  408. if (props == null) return;
  409. switch (props.TimeoutAction)
  410. {
  411. case "Notify":
  412. await _notifyService.NotifyTimeout(
  413. new List<long> { task.AssigneeId }, instance.Id, instance.Title);
  414. await WriteSystemLog(instance.Id, task.Id, task.NodeId,
  415. FlowLogActionEnum.AutoTimeout, "审批超时,已发送提醒通知");
  416. break;
  417. case "AutoApprove":
  418. await AutoApproveTask(task, instance);
  419. break;
  420. case "AutoReject":
  421. await AutoRejectTask(task, instance);
  422. break;
  423. case "AutoEscalate":
  424. await AutoEscalateTask(task, props, instance);
  425. break;
  426. }
  427. }
  428. private async Task AutoApproveTask(ApprovalFlowTask task, ApprovalFlowInstance instance)
  429. {
  430. task.Status = FlowTaskStatusEnum.Approved;
  431. task.Comment = "系统自动通过(超时)";
  432. task.ActionTime = DateTime.Now;
  433. await _taskRep.AsUpdateable(task).ExecuteCommandAsync();
  434. await WriteSystemLog(instance.Id, task.Id, task.NodeId,
  435. FlowLogActionEnum.AutoTimeout, "审批超时,系统自动通过");
  436. if (await IsNodeCompleted(instance, task.NodeId))
  437. {
  438. await InvokeHandler(instance.BizType,
  439. h => h.OnNodeCompleted(instance.BizId, task.NodeId, task.NodeName ?? ""));
  440. var flowData = DeserializeFlowJson(instance.FlowJsonSnapshot);
  441. await AdvanceToNext(instance, flowData, task.NodeId);
  442. }
  443. }
  444. private async Task AutoRejectTask(ApprovalFlowTask task, ApprovalFlowInstance instance)
  445. {
  446. task.Status = FlowTaskStatusEnum.Rejected;
  447. task.Comment = "系统自动拒绝(超时)";
  448. task.ActionTime = DateTime.Now;
  449. await _taskRep.AsUpdateable(task).ExecuteCommandAsync();
  450. await CancelPendingTasks(task.InstanceId, task.NodeId, task.Id);
  451. instance.Status = FlowInstanceStatusEnum.Rejected;
  452. instance.EndTime = DateTime.Now;
  453. await _instanceRep.AsUpdateable(instance).ExecuteCommandAsync();
  454. await WriteSystemLog(instance.Id, task.Id, task.NodeId,
  455. FlowLogActionEnum.AutoTimeout, "审批超时,系统自动拒绝");
  456. await InvokeHandler(instance.BizType,
  457. h => h.OnFlowCompleted(instance.BizId, FlowInstanceStatusEnum.Rejected));
  458. await _notifyService.NotifyFlowCompleted(instance.InitiatorId, instance.Id,
  459. instance.Title, FlowInstanceStatusEnum.Rejected);
  460. }
  461. private async Task AutoEscalateTask(ApprovalFlowTask task, FlowProperties nodeProps, ApprovalFlowInstance instance)
  462. {
  463. if (string.IsNullOrWhiteSpace(nodeProps.EscalationApproverType)
  464. || string.IsNullOrWhiteSpace(nodeProps.EscalationApproverIds))
  465. return;
  466. task.Status = FlowTaskStatusEnum.Escalated;
  467. task.Comment = "系统自动升级(超时)";
  468. task.ActionTime = DateTime.Now;
  469. await _taskRep.AsUpdateable(task).ExecuteCommandAsync();
  470. await CancelPendingTasks(task.InstanceId, task.NodeId, task.Id);
  471. var approvers = await ResolveApprovers(
  472. new FlowProperties
  473. {
  474. ApproverType = nodeProps.EscalationApproverType,
  475. ApproverIds = nodeProps.EscalationApproverIds,
  476. },
  477. instance.InitiatorId);
  478. if (approvers.Count == 0) return;
  479. var newTasks = approvers.Select(a => new ApprovalFlowTask
  480. {
  481. InstanceId = instance.Id,
  482. NodeId = task.NodeId,
  483. NodeName = task.NodeName,
  484. AssigneeId = a.userId,
  485. AssigneeName = a.userName,
  486. Status = FlowTaskStatusEnum.Pending,
  487. OrgId = instance.OrgId,
  488. }).ToList();
  489. await _taskRep.AsInsertable(newTasks).ExecuteCommandAsync();
  490. var targetNames = string.Join(", ", approvers.Select(a => a.userName));
  491. await WriteSystemLog(instance.Id, task.Id, task.NodeId,
  492. FlowLogActionEnum.AutoTimeout, $"审批超时,自动升级给 {targetNames}");
  493. var targetUserIds = approvers.Select(a => a.userId).Distinct().ToList();
  494. await _notifyService.NotifyEscalated(targetUserIds, instance.Id, instance.Title,
  495. "系统", task.NodeName);
  496. }
  497. private async Task WriteSystemLog(long instanceId, long? taskId, string? nodeId,
  498. FlowLogActionEnum action, string? comment)
  499. {
  500. await _logRep.InsertAsync(new ApprovalFlowLog
  501. {
  502. InstanceId = instanceId,
  503. TaskId = taskId,
  504. NodeId = nodeId,
  505. Action = action,
  506. OperatorId = 0,
  507. OperatorName = "系统",
  508. Comment = comment,
  509. });
  510. }
  511. // ═══════════════════════════════════════════
  512. // 内部引擎方法
  513. // ═══════════════════════════════════════════
  514. /// <summary>
  515. /// 推进到下一节点。支持并行网关(Fork / Join)。
  516. /// 行为约定:
  517. /// - 进入本方法前,调用方(如 <see cref="Approve"/>)应已将 <paramref name="currentNodeId"/>(userTask)标记为完成节点;
  518. /// 本方法会将途经的网关节点也写入 <see cref="ApprovalFlowCompletedNode"/>。
  519. /// - 并行网关 Fork(出边&gt;=2):沿每条出边递归推进;
  520. /// 并行网关 Join(入边&gt;=2):校验所有前驱节点是否都在"已完成"集合中,
  521. /// 任一尚未完成则**静默等待**(不报错、不推进),由后续分支完成后再次触发 Join 校验。
  522. /// </summary>
  523. private async Task AdvanceToNext(ApprovalFlowInstance instance, ApprovalFlowItem flowData, string currentNodeId)
  524. {
  525. // 当前节点可能是 userTask(完成记录由 Approve 写入)或网关(入口处已写入);此处统一确保幂等入库
  526. var currentNode = flowData.Nodes.FirstOrDefault(n => n.Id == currentNodeId);
  527. await MarkNodeCompleted(instance.Id, currentNode);
  528. var outgoingEdges = flowData.Edges.Where(e => e.SourceNodeId == currentNodeId).ToList();
  529. if (outgoingEdges.Count == 0)
  530. {
  531. await CompleteInstance(instance, FlowInstanceStatusEnum.Approved);
  532. return;
  533. }
  534. // 非并行网关场景:当前节点一般只有 1 条出边
  535. foreach (var edge in outgoingEdges)
  536. {
  537. await ProcessNextNode(instance, flowData, edge.TargetNodeId);
  538. }
  539. }
  540. /// <summary>
  541. /// 处理某个"下一节点"。根据节点类型分发:
  542. /// - endEvent:所有分支任务都结束时触发实例完成
  543. /// - exclusiveGateway:按条件选择分支
  544. /// - parallelGateway:Fork 并行分发;Join 等待所有前驱完成
  545. /// - userTask / 其他:创建任务
  546. /// </summary>
  547. private async Task ProcessNextNode(ApprovalFlowInstance instance, ApprovalFlowItem flowData, string nextNodeId)
  548. {
  549. var nextNode = flowData.Nodes.FirstOrDefault(n => n.Id == nextNodeId);
  550. if (nextNode == null)
  551. {
  552. await CompleteInstance(instance, FlowInstanceStatusEnum.Approved);
  553. return;
  554. }
  555. if (nextNode.Type is "bpmn:endEvent" or "end-node")
  556. {
  557. // 所有并行分支都已结束(无其它 Pending 任务)才真正完成实例
  558. var hasOtherPending = await _taskRep.AsQueryable()
  559. .AnyAsync(t => t.InstanceId == instance.Id && t.Status == FlowTaskStatusEnum.Pending);
  560. if (hasOtherPending) return;
  561. await CompleteInstance(instance, FlowInstanceStatusEnum.Approved);
  562. return;
  563. }
  564. if (nextNode.Type is "bpmn:exclusiveGateway")
  565. {
  566. await MarkNodeCompleted(instance.Id, nextNode);
  567. var bizData = await GetBizData(instance.BizType, instance.BizId);
  568. var targetNodeId = EvaluateGateway(nextNode.Properties?.Conditions, flowData, nextNode.Id, bizData);
  569. instance.CurrentNodeId = targetNodeId;
  570. await _instanceRep.AsUpdateable(instance).UpdateColumns(i => new { i.CurrentNodeId }).ExecuteCommandAsync();
  571. await ProcessNextNode(instance, flowData, targetNodeId);
  572. return;
  573. }
  574. if (nextNode.Type is "bpmn:parallelGateway")
  575. {
  576. var incoming = flowData.Edges.Where(e => e.TargetNodeId == nextNode.Id).Select(e => e.SourceNodeId).ToList();
  577. var outgoing = flowData.Edges.Where(e => e.SourceNodeId == nextNode.Id).Select(e => e.TargetNodeId).ToList();
  578. // Join 语义:入边 >= 2,需等所有前驱都已完成
  579. if (incoming.Count >= 2)
  580. {
  581. var completedSet = await GetCompletedNodeIdSet(instance.Id);
  582. if (!incoming.All(p => completedSet.Contains(p)))
  583. {
  584. // 未汇合,静默等待后续分支抵达
  585. return;
  586. }
  587. }
  588. // Fork 或 Join 通过:标记网关完成,沿所有出边推进
  589. await MarkNodeCompleted(instance.Id, nextNode);
  590. foreach (var target in outgoing)
  591. {
  592. await ProcessNextNode(instance, flowData, target);
  593. }
  594. return;
  595. }
  596. // userTask 或其他:创建任务
  597. instance.CurrentNodeId = nextNodeId;
  598. await _instanceRep.AsUpdateable(instance).UpdateColumns(i => new { i.CurrentNodeId }).ExecuteCommandAsync();
  599. await CreateTasksForNode(instance, flowData, nextNodeId);
  600. }
  601. /// <summary>
  602. /// 标记节点已完成(幂等:重复写入被唯一索引拦截后忽略)
  603. /// </summary>
  604. private async Task MarkNodeCompleted(long instanceId, ApprovalFlowNodeItem? node)
  605. {
  606. if (node == null) return;
  607. try
  608. {
  609. await _completedNodeRep.InsertAsync(new ApprovalFlowCompletedNode
  610. {
  611. InstanceId = instanceId,
  612. NodeId = node.Id,
  613. NodeName = node.Properties?.NodeName ?? node.Text?.Value,
  614. NodeType = node.Type,
  615. CompletedTime = DateTime.Now,
  616. });
  617. }
  618. catch
  619. {
  620. // 并发场景下可能触发唯一索引冲突,忽略(已存在即可)
  621. }
  622. }
  623. /// <summary>
  624. /// 查询实例已完成节点 Id 集合
  625. /// </summary>
  626. private async Task<HashSet<string>> GetCompletedNodeIdSet(long instanceId)
  627. {
  628. var ids = await _completedNodeRep.AsQueryable()
  629. .Where(c => c.InstanceId == instanceId)
  630. .Select(c => c.NodeId)
  631. .ToListAsync();
  632. return new HashSet<string>(ids);
  633. }
  634. private async Task CompleteInstance(ApprovalFlowInstance instance, FlowInstanceStatusEnum status)
  635. {
  636. // 幂等:并行分支同时到达 end 时避免重复完成
  637. var latest = await _instanceRep.GetByIdAsync(instance.Id);
  638. if (latest == null || latest.Status != FlowInstanceStatusEnum.Running) return;
  639. instance.Status = status;
  640. instance.EndTime = DateTime.Now;
  641. await _instanceRep.AsUpdateable(instance)
  642. .UpdateColumns(i => new { i.Status, i.EndTime })
  643. .ExecuteCommandAsync();
  644. await InvokeHandler(instance.BizType,
  645. h => h.OnFlowCompleted(instance.BizId, status));
  646. await _notifyService.NotifyFlowCompleted(instance.InitiatorId, instance.Id, instance.Title, status);
  647. }
  648. private async Task CreateTasksForNode(ApprovalFlowInstance instance, ApprovalFlowItem flowData, string nodeId)
  649. {
  650. var node = flowData.Nodes.FirstOrDefault(n => n.Id == nodeId)
  651. ?? throw Oops.Oh($"FlowJson 中未找到节点 [{nodeId}]");
  652. var approvers = await ResolveApprovers(node.Properties, instance.InitiatorId);
  653. if (approvers.Count == 0)
  654. throw Oops.Oh($"节点 [{node.Properties?.NodeName ?? nodeId}] 未配置审批人或审批人列表为空");
  655. var nodeName = node.Properties?.NodeName ?? node.Text?.Value;
  656. var tasks = approvers.Select(a => new ApprovalFlowTask
  657. {
  658. InstanceId = instance.Id,
  659. NodeId = nodeId,
  660. NodeName = nodeName,
  661. AssigneeId = a.userId,
  662. AssigneeName = a.userName,
  663. Status = FlowTaskStatusEnum.Pending,
  664. OrgId = instance.OrgId,
  665. }).ToList();
  666. // P1-6 审批代理:为每个原审批人检查是否存在有效代理,是则并行创建一条代理任务
  667. var delegateTasks = new List<ApprovalFlowTask>();
  668. foreach (var a in approvers)
  669. {
  670. var del = await FindEffectiveDelegate(a.userId, instance.BizType);
  671. if (del == null) continue;
  672. // 代理人和原审批人不能重复,代理人也不能是原审批人列表中其他人(避免同一人两条任务)
  673. if (approvers.Any(x => x.userId == del.DelegateUserId)) continue;
  674. delegateTasks.Add(new ApprovalFlowTask
  675. {
  676. InstanceId = instance.Id,
  677. NodeId = nodeId,
  678. NodeName = nodeName,
  679. AssigneeId = del.DelegateUserId,
  680. AssigneeName = del.DelegateUserName,
  681. Status = FlowTaskStatusEnum.Pending,
  682. OrgId = instance.OrgId,
  683. IsDelegate = true,
  684. DelegateForUserId = a.userId,
  685. DelegateForUserName = a.userName,
  686. });
  687. }
  688. var allTasks = tasks.Concat(delegateTasks).ToList();
  689. await _taskRep.AsInsertable(allTasks).ExecuteCommandAsync();
  690. var assigneeIds = allTasks.Select(t => t.AssigneeId).Distinct().ToList();
  691. await _notifyService.NotifyNewTask(assigneeIds, instance.Id, instance.Title, nodeName);
  692. }
  693. /// <summary>
  694. /// 查找指定用户当前生效的审批代理(时间窗口内 + 已启用 + BizType 匹配或全局)
  695. /// </summary>
  696. private async Task<ApprovalFlowDelegate?> FindEffectiveDelegate(long userId, string? bizType)
  697. {
  698. var now = DateTime.Now;
  699. return await _delegateRep.AsQueryable()
  700. .Where(d => d.UserId == userId
  701. && d.IsEnabled
  702. && d.StartTime <= now
  703. && d.EndTime >= now
  704. && (string.IsNullOrEmpty(d.BizType) || d.BizType == bizType))
  705. .OrderBy(d => d.BizType == null ? 1 : 0) // 优先匹配指定 BizType 的代理
  706. .OrderByDescending(d => d.CreateTime)
  707. .FirstAsync();
  708. }
  709. /// <summary>
  710. /// 取消与已完成任务配对的代理任务(P1-6)
  711. /// - 本人任务完成:取消对应的代理任务
  712. /// - 代理任务完成:取消对应的本人任务
  713. /// </summary>
  714. private async Task CancelPairedDelegateTask(ApprovalFlowTask completedTask)
  715. {
  716. ApprovalFlowTask? paired;
  717. if (completedTask.IsDelegate)
  718. {
  719. var originalUserId = completedTask.DelegateForUserId ?? 0;
  720. if (originalUserId == 0) return;
  721. paired = await _taskRep.AsQueryable()
  722. .Where(t => t.InstanceId == completedTask.InstanceId
  723. && t.NodeId == completedTask.NodeId
  724. && t.Status == FlowTaskStatusEnum.Pending
  725. && t.AssigneeId == originalUserId
  726. && !t.IsDelegate)
  727. .FirstAsync();
  728. }
  729. else
  730. {
  731. paired = await _taskRep.AsQueryable()
  732. .Where(t => t.InstanceId == completedTask.InstanceId
  733. && t.NodeId == completedTask.NodeId
  734. && t.Status == FlowTaskStatusEnum.Pending
  735. && t.IsDelegate
  736. && t.DelegateForUserId == completedTask.AssigneeId)
  737. .FirstAsync();
  738. }
  739. if (paired == null) return;
  740. paired.Status = FlowTaskStatusEnum.Cancelled;
  741. paired.ActionTime = DateTime.Now;
  742. await _taskRep.AsUpdateable(paired).ExecuteCommandAsync();
  743. }
  744. private async Task<List<(long userId, string userName)>> ResolveApprovers(FlowProperties? props, long initiatorId)
  745. {
  746. if (props == null || string.IsNullOrWhiteSpace(props.ApproverType))
  747. return new List<(long, string)>();
  748. var approverType = props.ApproverType;
  749. if (approverType == nameof(ApproverTypeEnum.Initiator))
  750. {
  751. var initiator = await _userRep.GetByIdAsync(initiatorId);
  752. return initiator != null
  753. ? new List<(long, string)> { (initiator.Id, initiator.RealName ?? "") }
  754. : new List<(long, string)>();
  755. }
  756. if (string.IsNullOrWhiteSpace(props.ApproverIds))
  757. return new List<(long, string)>();
  758. var ids = props.ApproverIds.Split(',', StringSplitOptions.RemoveEmptyEntries)
  759. .Select(s => long.TryParse(s.Trim(), out var v) ? v : 0).Where(id => id > 0).ToList();
  760. if (approverType == nameof(ApproverTypeEnum.SpecificUser))
  761. {
  762. var users = await _userRep.AsQueryable()
  763. .Where(u => ids.Contains(u.Id)).ToListAsync();
  764. return users.Select(u => (u.Id, u.RealName ?? "")).ToList();
  765. }
  766. if (approverType == nameof(ApproverTypeEnum.Role))
  767. {
  768. var userIds = await _userRoleRep.AsQueryable()
  769. .Where(ur => ids.Contains(ur.RoleId))
  770. .Select(ur => ur.UserId)
  771. .ToListAsync();
  772. var users = await _userRep.AsQueryable()
  773. .Where(u => userIds.Contains(u.Id)).ToListAsync();
  774. return users.Select(u => (u.Id, u.RealName ?? "")).ToList();
  775. }
  776. if (approverType == nameof(ApproverTypeEnum.Department))
  777. {
  778. var users = await _userRep.AsQueryable()
  779. .Where(u => ids.Contains(u.OrgId)).ToListAsync();
  780. return users.Select(u => (u.Id, u.RealName ?? "")).ToList();
  781. }
  782. if (approverType == nameof(ApproverTypeEnum.DepartmentLeader))
  783. {
  784. var initiator = await _userRep.GetByIdAsync(initiatorId);
  785. if (initiator == null || initiator.OrgId <= 0)
  786. return new List<(long, string)>();
  787. var org = await _orgRep.GetByIdAsync(initiator.OrgId);
  788. if (org?.DirectorId != null && org.DirectorId > 0)
  789. {
  790. var director = await _userRep.GetByIdAsync(org.DirectorId.Value);
  791. if (director != null)
  792. return new List<(long, string)> { (director.Id, director.RealName ?? "") };
  793. }
  794. if (initiator.ManagerUserId != null && initiator.ManagerUserId > 0)
  795. {
  796. var manager = await _userRep.GetByIdAsync(initiator.ManagerUserId.Value);
  797. if (manager != null)
  798. return new List<(long, string)> { (manager.Id, manager.RealName ?? "") };
  799. }
  800. return new List<(long, string)>();
  801. }
  802. return new List<(long, string)>();
  803. }
  804. /// <summary>
  805. /// 评估排他网关 — 依次尝试各非默认分支的条件表达式,首个匹配的获胜;
  806. /// 全不匹配则走默认分支;无默认则走第一条出边
  807. /// 支持简单比较表达式:variable op value(op: ==,!=,>,>=,&lt;,&lt;=)
  808. /// </summary>
  809. private string EvaluateGateway(List<GatewayCondition>? conditions, ApprovalFlowItem flowData, string gatewayNodeId, Dictionary<string, object>? bizData)
  810. {
  811. if (conditions != null && conditions.Count > 0 && bizData != null && bizData.Count > 0)
  812. {
  813. foreach (var cond in conditions.Where(c => !c.IsDefault))
  814. {
  815. if (!string.IsNullOrWhiteSpace(cond.Expression) && EvalSimpleExpression(cond.Expression, bizData))
  816. return cond.TargetNodeId;
  817. }
  818. var defaultBranch = conditions.FirstOrDefault(c => c.IsDefault);
  819. if (defaultBranch != null)
  820. return defaultBranch.TargetNodeId;
  821. }
  822. else if (conditions != null && conditions.Count > 0)
  823. {
  824. var defaultBranch = conditions.FirstOrDefault(c => c.IsDefault);
  825. if (defaultBranch != null) return defaultBranch.TargetNodeId;
  826. return conditions.First().TargetNodeId;
  827. }
  828. var edge = flowData.Edges.FirstOrDefault(e => e.SourceNodeId == gatewayNodeId);
  829. return edge?.TargetNodeId ?? throw Oops.Oh("排他网关没有出边");
  830. }
  831. /// <summary>
  832. /// 简单表达式求值:支持 "field op value" 格式(如 "urgent == 1", "customLevel >= 3", "amount > 10000")
  833. /// 多条件用 &amp;&amp; 连接
  834. /// </summary>
  835. private static bool EvalSimpleExpression(string expression, Dictionary<string, object> bizData)
  836. {
  837. var parts = expression.Split("&&", StringSplitOptions.TrimEntries);
  838. foreach (var part in parts)
  839. {
  840. if (!EvalSingleComparison(part.Trim(), bizData))
  841. return false;
  842. }
  843. return true;
  844. }
  845. private static bool EvalSingleComparison(string expr, Dictionary<string, object> bizData)
  846. {
  847. string[] ops = { ">=", "<=", "!=", "==", ">", "<" };
  848. foreach (var op in ops)
  849. {
  850. var idx = expr.IndexOf(op, StringComparison.Ordinal);
  851. if (idx < 0) continue;
  852. var fieldName = expr[..idx].Trim();
  853. var valueStr = expr[(idx + op.Length)..].Trim().Trim('"', '\'');
  854. if (!bizData.TryGetValue(fieldName, out var fieldValue))
  855. return false;
  856. if (decimal.TryParse(fieldValue?.ToString(), out var numLeft) && decimal.TryParse(valueStr, out var numRight))
  857. {
  858. return op switch
  859. {
  860. "==" => numLeft == numRight,
  861. "!=" => numLeft != numRight,
  862. ">" => numLeft > numRight,
  863. ">=" => numLeft >= numRight,
  864. "<" => numLeft < numRight,
  865. "<=" => numLeft <= numRight,
  866. _ => false,
  867. };
  868. }
  869. var strLeft = fieldValue?.ToString() ?? "";
  870. return op switch
  871. {
  872. "==" => strLeft.Equals(valueStr, StringComparison.OrdinalIgnoreCase),
  873. "!=" => !strLeft.Equals(valueStr, StringComparison.OrdinalIgnoreCase),
  874. _ => false,
  875. };
  876. }
  877. return false;
  878. }
  879. private async Task<Dictionary<string, object>?> GetBizData(string bizType, long bizId)
  880. {
  881. var handlers = App.GetServices<IFlowBizHandler>();
  882. var handler = handlers?.FirstOrDefault(h => h.BizType == bizType);
  883. if (handler == null) return null;
  884. return await handler.GetBizData(bizId);
  885. }
  886. private string? FindNextNodeId(ApprovalFlowItem flowData, string currentNodeId)
  887. {
  888. var edge = flowData.Edges.FirstOrDefault(e => e.SourceNodeId == currentNodeId);
  889. return edge?.TargetNodeId;
  890. }
  891. private string? FindPrevUserTaskNodeId(ApprovalFlowItem flowData, string currentNodeId)
  892. {
  893. var inEdge = flowData.Edges.FirstOrDefault(e => e.TargetNodeId == currentNodeId);
  894. if (inEdge == null) return null;
  895. var prevNode = flowData.Nodes.FirstOrDefault(n => n.Id == inEdge.SourceNodeId);
  896. if (prevNode == null) return null;
  897. if (prevNode.Type is "bpmn:userTask" or "user-node" or "task-node")
  898. return prevNode.Id;
  899. // 递归跳过网关等非用户任务节点
  900. return FindPrevUserTaskNodeId(flowData, prevNode.Id);
  901. }
  902. private async Task<bool> IsNodeCompleted(ApprovalFlowInstance instance, string nodeId)
  903. {
  904. var flowData = DeserializeFlowJson(instance.FlowJsonSnapshot);
  905. var node = flowData.Nodes.FirstOrDefault(n => n.Id == nodeId);
  906. var mode = node?.Properties?.MultiApproveMode;
  907. if (mode == nameof(MultiApproveModeEnum.All))
  908. {
  909. var pendingCount = await _taskRep.AsQueryable()
  910. .Where(t => t.InstanceId == instance.Id && t.NodeId == nodeId && t.Status == FlowTaskStatusEnum.Pending)
  911. .CountAsync();
  912. return pendingCount == 0;
  913. }
  914. // 默认或签(Any):一人通过即完成,取消其他 Pending
  915. await CancelPendingTasks(instance.Id, nodeId);
  916. return true;
  917. }
  918. private async Task CancelPendingTasks(long instanceId, string nodeId, long? excludeTaskId = null)
  919. {
  920. var tasks = await _taskRep.AsQueryable()
  921. .Where(t => t.InstanceId == instanceId && t.NodeId == nodeId && t.Status == FlowTaskStatusEnum.Pending)
  922. .WhereIF(excludeTaskId.HasValue, t => t.Id != excludeTaskId!.Value)
  923. .ToListAsync();
  924. foreach (var t in tasks)
  925. {
  926. t.Status = FlowTaskStatusEnum.Cancelled;
  927. t.ActionTime = DateTime.Now;
  928. }
  929. if (tasks.Count > 0)
  930. await _taskRep.AsUpdateable(tasks).ExecuteCommandAsync();
  931. }
  932. /// <summary>
  933. /// 取消整个实例下所有剩余 Pending 任务(Reject 时跨并行分支使用)
  934. /// </summary>
  935. private async Task CancelAllPendingTasks(long instanceId, long? excludeTaskId = null)
  936. {
  937. var tasks = await _taskRep.AsQueryable()
  938. .Where(t => t.InstanceId == instanceId && t.Status == FlowTaskStatusEnum.Pending)
  939. .WhereIF(excludeTaskId.HasValue, t => t.Id != excludeTaskId!.Value)
  940. .ToListAsync();
  941. foreach (var t in tasks)
  942. {
  943. t.Status = FlowTaskStatusEnum.Cancelled;
  944. t.ActionTime = DateTime.Now;
  945. }
  946. if (tasks.Count > 0)
  947. await _taskRep.AsUpdateable(tasks).ExecuteCommandAsync();
  948. }
  949. private async Task<ApprovalFlowTask> GetPendingTask(long taskId)
  950. {
  951. var task = await _taskRep.GetByIdAsync(taskId)
  952. ?? throw Oops.Oh("审批任务不存在");
  953. if (task.Status != FlowTaskStatusEnum.Pending)
  954. throw Oops.Oh("该任务已处理");
  955. if (task.AssigneeId != _userManager.UserId)
  956. throw Oops.Oh("当前用户不是该任务的审批人");
  957. return task;
  958. }
  959. private async Task WriteLog(long instanceId, long? taskId, string? nodeId, FlowLogActionEnum action, string? comment)
  960. {
  961. await _logRep.InsertAsync(new ApprovalFlowLog
  962. {
  963. InstanceId = instanceId,
  964. TaskId = taskId,
  965. NodeId = nodeId,
  966. Action = action,
  967. OperatorId = _userManager.UserId,
  968. OperatorName = _userManager.RealName,
  969. Comment = comment,
  970. });
  971. }
  972. private static ApprovalFlowItem DeserializeFlowJson(string? json)
  973. {
  974. if (string.IsNullOrWhiteSpace(json))
  975. throw Oops.Oh("FlowJson 快照为空");
  976. return JsonSerializer.Deserialize<ApprovalFlowItem>(json)
  977. ?? throw Oops.Oh("FlowJson 反序列化失败");
  978. }
  979. private async Task InvokeHandler(string bizType, Func<IFlowBizHandler, Task> action)
  980. {
  981. var handlers = App.GetServices<IFlowBizHandler>();
  982. var handler = handlers?.FirstOrDefault(h => h.BizType == bizType);
  983. if (handler != null)
  984. await action(handler);
  985. }
  986. }