WorkflowDomainService.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839
  1. using Hotline.FlowEngine.Definitions;
  2. using Hotline.FlowEngine.Notifies;
  3. using Hotline.SeedData;
  4. using Hotline.Share.Dtos.FlowEngine;
  5. using Hotline.Share.Enums.FlowEngine;
  6. using Hotline.Users;
  7. using MapsterMapper;
  8. using MediatR;
  9. using SqlSugar;
  10. using XF.Domain.Authentications;
  11. using XF.Domain.Dependency;
  12. using XF.Domain.Entities;
  13. using XF.Domain.Exceptions;
  14. using XF.Utility.SequentialId;
  15. namespace Hotline.FlowEngine.Workflows
  16. {
  17. public class WorkflowDomainService : IWorkflowDomainService, IScopeDependency
  18. {
  19. private readonly IWorkflowRepository _workflowRepository;
  20. private readonly IWorkflowStepRepository _workflowStepRepository;
  21. private readonly IWorkflowTraceRepository _workflowTraceRepository;
  22. private readonly IWorkflowSupplementRepository _workflowSupplementRepository;
  23. private readonly IWorkflowAssignRepository _workflowAssignRepository;
  24. private readonly ISessionContext _sessionContext;
  25. private readonly IMapper _mapper;
  26. private readonly IMediator _mediator;
  27. public WorkflowDomainService(
  28. IWorkflowRepository workflowRepository,
  29. IWorkflowStepRepository workflowStepRepository,
  30. IWorkflowTraceRepository workflowTraceRepository,
  31. IWorkflowSupplementRepository workflowSupplementRepository,
  32. IWorkflowAssignRepository workflowAssignRepository,
  33. ISessionContext sessionContext,
  34. IMapper mapper,
  35. IMediator mediator)
  36. {
  37. _workflowRepository = workflowRepository;
  38. _workflowStepRepository = workflowStepRepository;
  39. _workflowTraceRepository = workflowTraceRepository;
  40. _workflowSupplementRepository = workflowSupplementRepository;
  41. _workflowAssignRepository = workflowAssignRepository;
  42. _sessionContext = sessionContext;
  43. _mapper = mapper;
  44. _mediator = mediator;
  45. }
  46. public async Task<Workflow> CreateWorkflowAsync(Definition definition, string title, string externalId, CancellationToken cancellationToken)
  47. {
  48. var workflow = new Workflow
  49. {
  50. Title = title,
  51. ModuleId = definition.ModuleId,
  52. ModuleName = definition.ModuleName,
  53. ModuleCode = definition.ModuleCode,
  54. DefinitionId = definition.Id,
  55. Status = EWorkflowStatus.Runnable,
  56. TimeLimit = GetTimeLimit(definition.Code),
  57. ExpiredTime = GenerateExpiredTime(definition.Code),
  58. ExternalId = externalId,
  59. StepBoxes = new(),
  60. Traces = new(),
  61. Definition = definition
  62. };
  63. workflow.AssignTime = workflow.CreationTime;
  64. await _workflowRepository.AddAsync(workflow, cancellationToken);
  65. return workflow;
  66. }
  67. /// <summary>
  68. /// 流程开始
  69. /// </summary>
  70. /// <param name="workflow"></param>
  71. /// <param name="dto"></param>
  72. /// <param name="cancellationToken"></param>
  73. /// <returns></returns>
  74. public async Task StartAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine, CancellationToken cancellationToken)
  75. {
  76. //var nextStepBoxDefine = GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
  77. var isStartCountersign = nextStepBoxDefine.IsStartCountersign(dto.Handlers.Count);
  78. //检查是否支持会签
  79. if (isStartCountersign && nextStepBoxDefine.CountersignMode == ECountersignMode.UnSupport)
  80. throw new UserFriendlyException($"当前节点不支持会签, defineCode: {workflow.Definition.Code}", "当前节点不支持会签");
  81. //1. 如果不是按角色指派,handlers必填 2. 如果按角色指派,handlers可以不选
  82. if (nextStepBoxDefine.HandlerType is not EHandlerType.Role && !dto.Handlers.Any())
  83. throw UserFriendlyException.SameMessage("未指派办理人");
  84. //第二节点的previousId is string.Empty
  85. await CreateStepAsync(workflow, nextStepBoxDefine, dto, cancellationToken: cancellationToken);
  86. //publish
  87. _mediator.Publish(new StartWorkflowNotify(workflow, nextStepBoxDefine, dto, isStartCountersign), cancellationToken);
  88. }
  89. public async Task<Workflow> GetWorkflowAsync(string workflowId,
  90. bool withDefine = false, bool withSteps = false,
  91. bool withTraces = false, bool withSupplements = false,
  92. bool withAssigns = false,
  93. CancellationToken cancellationToken = default)
  94. {
  95. var query = _workflowRepository.Queryable().Where(d => d.Id == workflowId);
  96. return await GetWorkflowAsync(query, withDefine, withSteps, withTraces, withSupplements, withAssigns);
  97. }
  98. public async Task<Workflow> GetWorkflowAsync(string moduleCode, string externalId, bool withDefine = false, bool withSteps = false,
  99. bool withTraces = false, bool withSupplements = false, bool withAssigns = false,
  100. CancellationToken cancellationToken = default)
  101. {
  102. var query = _workflowRepository.Queryable().Where(d => d.ModuleCode == moduleCode && d.ExternalId == externalId);
  103. return await GetWorkflowAsync(query, withDefine, withSteps, withTraces, withSupplements, withAssigns);
  104. }
  105. /// <summary>
  106. /// 受理(接办)
  107. /// </summary>
  108. public async Task AcceptAsync(Workflow workflow, CancellationToken cancellationToken)
  109. {
  110. //工单完成以后查看的场景
  111. if (workflow.Status is not EWorkflowStatus.Runnable) return;
  112. var (currentStepBox, currentStep) = GetUnCompleteStepOrDefault(workflow.StepBoxes, _sessionContext.RequiredOrgCode, _sessionContext.RequiredUserId);
  113. if (currentStep is null) return;
  114. if (currentStep.Status is EWorkflowStepStatus.Accepted) return;
  115. if (currentStep.HandlerType is EHandlerType.AssignUser or EHandlerType.Role)
  116. {
  117. //userId
  118. if (currentStep.HandlerId != _sessionContext.RequiredUserId) return;
  119. }
  120. else
  121. {
  122. //orgId
  123. if (currentStep.HandlerId != _sessionContext.RequiredOrgCode) return;
  124. }
  125. if (currentStep.StepType is EStepType.End)
  126. throw new UserFriendlyException("当前流程已流转到最终步骤");
  127. if (currentStepBox.Status is EWorkflowStepStatus.Assigned)
  128. currentStepBox.Status = EWorkflowStepStatus.Accepted;
  129. currentStep.Accept(_sessionContext.RequiredUserId, _sessionContext.UserName);
  130. //接办时非会签并且有多个接办部门时需更新接办部门
  131. if (!workflow.IsInCountersign())
  132. {
  133. var assigns = await _workflowAssignRepository.QueryAsync(d => d.WorkflowId == workflow.Id);
  134. if (assigns.Count > 1)
  135. {
  136. await _workflowAssignRepository.RemoveRangeAsync(assigns, cancellationToken);
  137. var assign = WorkflowAssign.Create(workflow.Id, _sessionContext.OrgCode, _sessionContext.OrgName);
  138. await _workflowAssignRepository.AddAsync(assign, cancellationToken);
  139. }
  140. }
  141. await AcceptTraceAsync(workflow, currentStepBox, currentStep, cancellationToken);
  142. await _mediator.Publish(new AcceptWorkflowNotify(workflow), cancellationToken);
  143. }
  144. /// <summary>
  145. /// 办理(流转至下一节点)
  146. /// </summary>
  147. public async Task NextAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine, bool isOutOfCallCenter, bool isStartCountersign, CancellationToken cancellationToken)
  148. {
  149. CheckWhetherRunnable(workflow.Status);
  150. #region 办理当前节点
  151. var (currentStepBox, currentStep) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgCode, _sessionContext.RequiredUserId);
  152. if (currentStep.Status is EWorkflowStepStatus.Assigned)
  153. await AcceptAsync(workflow, cancellationToken);
  154. if (currentStep.StepType is EStepType.End)
  155. throw new UserFriendlyException("当前流程已流转到最终步骤");
  156. //检查是否支持会签办理
  157. if (isStartCountersign && nextStepBoxDefine.CountersignMode == ECountersignMode.UnSupport)
  158. throw UserFriendlyException.SameMessage($"下一节点不支持会签办理, code: {currentStep.Code}");
  159. if (isStartCountersign)
  160. currentStep.StartCountersign();
  161. _mapper.Map(dto, currentStep);
  162. //step办理状态
  163. currentStep.StepComplete(
  164. _sessionContext.RequiredUserId, _sessionContext.UserName,
  165. _sessionContext.RequiredOrgCode, _sessionContext.OrgName,
  166. dto.NextStepCode);
  167. //stepBox办理状态
  168. currentStepBox.CheckStepBoxStatusAndUpdate();
  169. var updateSteps = new List<WorkflowStep> { currentStepBox, currentStep };
  170. //结束当前会签流程
  171. if (currentStep.StepType is EStepType.CountersignEnd && currentStep.IsInCountersign)
  172. {
  173. var countersignStartStep = FindCountersignStartStep(workflow, currentStep.CountersignStartCode, currentStep.PrevCountersignId);
  174. if (countersignStartStep.HasStartCountersign)
  175. {
  176. countersignStartStep.CountersignComplete();
  177. updateSteps.Add(countersignStartStep);
  178. }
  179. }
  180. await _workflowStepRepository.UpdateRangeAsync(updateSteps, cancellationToken);
  181. #endregion
  182. #region 处理流程
  183. //检查会签是否结束,并更新当前会签节点字段
  184. var isCountersignOver = false;
  185. if (workflow.IsInCountersign())
  186. {
  187. isCountersignOver = workflow.CheckIfCountersignOver();
  188. if (isCountersignOver)
  189. workflow.EndCountersign();
  190. }
  191. //检查是否流转到流程终点
  192. if (nextStepBoxDefine.StepType is EStepType.End)
  193. {
  194. workflow.Complete();
  195. await _workflowRepository.UpdateAsync(workflow, cancellationToken);
  196. _mediator.Publish(new EndWorkflowNotify(workflow), cancellationToken);
  197. return;
  198. }
  199. //是否从中心流转出去,重新计算expiredTime
  200. if (isOutOfCallCenter)
  201. {
  202. workflow.IsStraight = false;
  203. workflow.ExpiredTime = GenerateExpiredTime(workflow.Definition.Code);
  204. workflow.AssignTime = DateTime.Now;
  205. }
  206. //最终办理意见
  207. if (nextStepBoxDefine.StepType is not EStepType.Normal)
  208. {
  209. workflow.Opinion = dto.Opinion;
  210. }
  211. //创建下一节点
  212. var nextStepBox = await CreateStepAsync(workflow, nextStepBoxDefine, dto, currentStepBox, currentStep, cancellationToken);
  213. //下一节点为汇总节点时,检查下一节点是否可办理
  214. if (nextStepBox.StepType is EStepType.CountersignEnd)
  215. {
  216. if (currentStep.IsInCountersign)
  217. {
  218. var stepCode = currentStep.StepType is EStepType.CountersignEnd
  219. ? currentStep.CountersignStartCode
  220. : currentStep.Code;
  221. var countersignId = string.IsNullOrEmpty(currentStep.TopCountersignId)
  222. ? currentStep.PrevCountersignId
  223. : currentStep.TopCountersignId;
  224. var stepBox = workflow.StepBoxes.First(d => d.Code == stepCode);
  225. var countersignSteps =
  226. stepBox.Steps.Where(d => d.PrevCountersignId == countersignId);
  227. //check all complete or cs complete
  228. var canHandle = true;
  229. foreach (var countersignStep in countersignSteps)
  230. {
  231. if (countersignStep.Status != EWorkflowStepStatus.Completed) break;
  232. if (countersignStep.HasStartCountersign && !countersignStep.IsCountersignComplete.GetValueOrDefault()) break;
  233. }
  234. if (canHandle)
  235. {
  236. await UpdateNextCountersignEndAssignedAsync(nextStepBox, currentStep, cancellationToken);
  237. _mediator.Publish(new CountersignEndAssigned(workflow), cancellationToken);
  238. }
  239. }
  240. else
  241. {
  242. await UpdateNextCountersignEndAssignedAsync(nextStepBox, currentStep, cancellationToken);
  243. _mediator.Publish(new CountersignEndAssigned(workflow), cancellationToken);
  244. }
  245. }
  246. //更新当前节点名称、时间、会签节点code 等字段
  247. workflow.SetWorkflowCurrentStepInfo(isStartCountersign, nextStepBox);
  248. await _workflowRepository.UpdateAsync(workflow, cancellationToken);
  249. #endregion
  250. #region 流转记录
  251. await NextTraceAsync(workflow, dto, currentStep, cancellationToken);
  252. #endregion
  253. _mediator.Publish(new NextStepNotify(workflow, nextStepBoxDefine, dto, isStartCountersign, isCountersignOver));
  254. }
  255. /// <summary>
  256. /// 更新下级汇总节点可办理状态
  257. /// </summary>
  258. /// <param name="nextStepBox"></param>
  259. /// <param name="currentStep"></param>
  260. /// <param name="cancellationToken"></param>
  261. /// <returns></returns>
  262. private async Task UpdateNextCountersignEndAssignedAsync(WorkflowStep nextStepBox, WorkflowStep currentStep, CancellationToken cancellationToken)
  263. {
  264. var countersignId = string.IsNullOrEmpty(currentStep.TopCountersignId)
  265. ? currentStep.PrevCountersignId
  266. : currentStep.TopCountersignId;
  267. var nextStep = nextStepBox.Steps.First(d => d.PrevCountersignId == countersignId);
  268. nextStep.SetAssigned();
  269. await _workflowStepRepository.UpdateAsync(nextStep, cancellationToken);
  270. }
  271. /// <summary>
  272. /// 退回(返回前一节点)
  273. /// </summary>
  274. /// <returns></returns>
  275. public async Task PreviousAsync(Workflow workflow, PreviousWorkflowDto dto, CancellationToken cancellationToken)
  276. {
  277. CheckWhetherRunnable(workflow.Status);
  278. var (currentStepBox, currentStep) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgCode, _sessionContext.RequiredUserId);
  279. if (currentStepBox.StepType is EStepType.Start)
  280. throw UserFriendlyException.SameMessage("当前流程已退回到开始节点");
  281. if (currentStepBox.Steps.Count > 1)
  282. throw UserFriendlyException.SameMessage("会签流程不支持退回");
  283. //update trace
  284. await PreviousTraceAsync(workflow.Id, dto, currentStep, cancellationToken);
  285. //remove workflow.steps
  286. await _workflowStepRepository.RemoveRangeAsync(new List<WorkflowStep> { currentStepBox, currentStep },
  287. cancellationToken);
  288. //todo publish
  289. }
  290. /// <summary>
  291. /// 撤回(返回到之前任意节点)
  292. /// </summary>
  293. public async Task RecallAsync(Workflow workflow, RecallDto dto, CancellationToken cancellationToken)
  294. {
  295. CheckWhetherRunnable(workflow.Status);
  296. var targetStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Code == dto.TargetStepCode);
  297. if (targetStepBox is null)
  298. throw UserFriendlyException.SameMessage("该流程尚未流转至该节点");
  299. await RecallAsync(workflow, dto, targetStepBox, cancellationToken);
  300. //todo publish
  301. }
  302. /// <summary>
  303. /// 跳转(直接将流程跳转至任意节点)
  304. /// </summary>
  305. public async Task JumpAsync(Workflow workflow, RecallDto dto, CancellationToken cancellationToken)
  306. {
  307. CheckWhetherRunnable(workflow.Status);
  308. //update uncompleted traces
  309. await JumpTraceAsync(workflow.Id, dto, cancellationToken);
  310. var targetStepBox = workflow.StepBoxes.FirstOrDefault(d => d.Code == dto.TargetStepCode);
  311. if (targetStepBox == null)
  312. {
  313. var nextStepBoxDefine = GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
  314. var nextStepBox = await CreateStepAsync(workflow, nextStepBoxDefine, dto, cancellationToken: cancellationToken);
  315. await ResetWorkflowCurrentStepInfo(workflow, dto, nextStepBox, cancellationToken);
  316. #region 补充中间节点处理方案
  317. //var completeStepCodes = workflow.StepBoxes.Select(d => d.Code);
  318. //var uncompleteStepDefines = workflow.Definition.Steps.Where(d => !completeStepCodes.Contains(d.Code));
  319. //创建当前节点与目标节点中间节点
  320. //var jumpDto = new BasicWorkflowDto
  321. //{
  322. // Opinion = "跳转补充"
  323. //};
  324. //foreach (var stepDefine in uncompleteStepDefines)
  325. //{
  326. // var previousStepId = lastStepBox.Steps.Count > 1 ? lastStepBox.Id : lastStepBox.Steps.First().Id;
  327. // if (dto.TargetStepCode == stepDefine.Code)
  328. // {
  329. // await CreateStepAsync(workflow, stepDefine, dto, lastStepBox.Id, previousStepId, cancellationToken);
  330. // break;
  331. // }
  332. // //jump业务下,如果当前节点为会签节点,第一个补充节点的subStep.PreviousId无法确定从哪个子节点跳转过来,统一处理为当前节点的stepBox.Id
  333. // lastStepBox = await CreateStepAsync(workflow, stepDefine, dto, lastStepBox.Id, previousStepId, cancellationToken);
  334. //}
  335. #endregion
  336. }
  337. else
  338. {
  339. //返回之前节点
  340. await RecallAsync(workflow, dto, targetStepBox, cancellationToken);
  341. }
  342. //todo publish
  343. }
  344. private async Task ResetWorkflowCurrentStepInfo(Workflow workflow, RecallDto dto, WorkflowStep stepBox, CancellationToken cancellationToken)
  345. {
  346. //更新当前节点名称、时间、会签节点code
  347. workflow.CloseCountersignStatus();
  348. var isCountersign = dto.Handlers.Count > 1;
  349. workflow.SetWorkflowCurrentStepInfo(isCountersign, stepBox);
  350. await _workflowRepository.UpdateAsync(workflow, cancellationToken);
  351. }
  352. /// <summary>
  353. /// 补充
  354. /// </summary>
  355. /// <returns></returns>
  356. public async Task SupplementAsync(Workflow workflow, EndWorkflowDto dto, CancellationToken cancellationToken)
  357. {
  358. CheckWhetherRunnable(workflow.Status);
  359. //todo 检查当前办理人是否为该流程中的办理人
  360. var supplement = _mapper.Map<WorkflowSupplement>(dto);
  361. await _workflowSupplementRepository.AddAsync(supplement, cancellationToken);
  362. }
  363. /// <summary>
  364. /// 终止流程
  365. /// </summary>
  366. public async Task TerminateAsync(string id, CancellationToken cancellationToken)
  367. {
  368. var workflow = await _workflowRepository.GetAsync(id, cancellationToken);
  369. if (workflow == null)
  370. throw UserFriendlyException.SameMessage("无效的流程编号");
  371. workflow.Terminate();
  372. await _workflowRepository.UpdateAsync(workflow, cancellationToken);
  373. //todo publish
  374. _mediator.Publish(new TerminalWorkflowNotify(workflow));
  375. }
  376. /// <summary>
  377. /// 根据stepCode查询流程配置中对应的节点
  378. /// </summary>
  379. public StepDefine GetStepBoxDefine(Definition definition, string stepCode)
  380. {
  381. if (definition == null) throw new ArgumentNullException(nameof(definition));
  382. if (string.IsNullOrEmpty(stepCode)) throw new ArgumentNullException(nameof(stepCode));
  383. var stepDefine = definition.FindStep(stepCode);
  384. if (stepDefine == null)
  385. throw new UserFriendlyException($"未找到流程中对应的节点,DefineCode: {definition.Code}, stepCode: {stepCode}",
  386. "未查询到对应节点");
  387. return stepDefine;
  388. }
  389. /// <summary>
  390. /// 查询当前待办节点的下一级节点配置(办理参数)
  391. /// </summary>
  392. public IReadOnlyList<StepDefine> GetNextStepOptions(Workflow workflow, CancellationToken cancellationToken)
  393. {
  394. var (currentStepBox, _) = GetUnCompleteStep(workflow.StepBoxes, _sessionContext.RequiredOrgCode, _sessionContext.RequiredUserId);
  395. return workflow.Definition.FindSteps(currentStepBox.NextSteps);
  396. }
  397. public async Task<bool> ExsitsAsync(string moduleCode, string externalId, CancellationToken cancellationToken)
  398. {
  399. return await _workflowRepository.Queryable()
  400. .AnyAsync(d => d.ModuleCode == moduleCode && d.ExternalId == externalId);
  401. }
  402. #region private
  403. private async Task<Workflow> GetWorkflowAsync(ISugarQueryable<Workflow> query, bool withDefine, bool withSteps, bool withTraces,
  404. bool withSupplements, bool withAssigns)
  405. {
  406. if (withDefine)
  407. query = query.Includes(d => d.Definition);
  408. if (withSupplements)
  409. query = query.Includes(d => d.Supplements);
  410. if (withAssigns)
  411. query = query.Includes(d => d.Assigns);
  412. var workflow = await query.FirstAsync();
  413. if (workflow is null)
  414. throw new UserFriendlyException("无效workflowId");
  415. if (withSteps)
  416. {
  417. var steps = await _workflowStepRepository.Queryable()
  418. .Where(d => d.WorkflowId == workflow.Id)
  419. .OrderBy(d => d.CreationTime)
  420. .ToTreeAsync(d => d.Steps, d => d.ParentId, null);
  421. workflow.StepBoxes = steps;
  422. }
  423. if (withTraces)
  424. {
  425. var traces = await _workflowTraceRepository.Queryable()
  426. .Where(d => d.WorkflowId == workflow.Id)
  427. .ToTreeAsync(d => d.Traces, d => d.ParentId, null);
  428. workflow.Traces = traces;
  429. }
  430. return workflow;
  431. }
  432. /// <summary>
  433. /// 在stepCode对应的stepBox中找到开启会签流程的节点
  434. /// </summary>
  435. private static WorkflowStep FindCountersignStartStep(Workflow workflow, string startCountersignStepCode, string startCountersignId)
  436. {
  437. var countersignStartStepBox = workflow.StepBoxes.First(d => d.Code == startCountersignStepCode);
  438. var countersignStartStep =
  439. countersignStartStepBox.Steps.First(d => d.StartCountersignId == startCountersignId);
  440. return countersignStartStep;
  441. }
  442. private async Task JumpTraceAsync(string workflowId, RecallDto dto, CancellationToken cancellationToken)
  443. {
  444. //未办理的traces
  445. var uncompleteTraces =
  446. await _workflowTraceRepository.QueryAsync(d =>
  447. d.WorkflowId == workflowId && string.IsNullOrEmpty(d.UserId));
  448. foreach (var trace in uncompleteTraces)
  449. {
  450. trace.Jump(
  451. _sessionContext.RequiredUserId,
  452. _sessionContext.UserName,
  453. _sessionContext.RequiredOrgCode,
  454. _sessionContext.OrgName);
  455. }
  456. await _workflowTraceRepository.UpdateRangeAsync(uncompleteTraces, cancellationToken);
  457. }
  458. private async Task RecallTraceAsync(string workflowId, RecallDto dto, CancellationToken cancellationToken)
  459. {
  460. //未办理的traces
  461. var uncompleteTraces =
  462. await _workflowTraceRepository.QueryAsync(d =>
  463. d.WorkflowId == workflowId && string.IsNullOrEmpty(d.UserId));
  464. foreach (var trace in uncompleteTraces)
  465. {
  466. trace.Recall(
  467. _sessionContext.RequiredUserId,
  468. _sessionContext.UserName,
  469. _sessionContext.RequiredOrgCode,
  470. _sessionContext.OrgName);
  471. }
  472. await _workflowTraceRepository.UpdateRangeAsync(uncompleteTraces, cancellationToken);
  473. }
  474. private async Task PreviousTraceAsync(string workflowId, PreviousWorkflowDto dto, WorkflowStep step, CancellationToken cancellationToken)
  475. {
  476. var trace = await GetWorkflowTraceAsync(workflowId, step.Id, cancellationToken);
  477. _mapper.Map(dto, trace);
  478. trace.Previous(
  479. _sessionContext.RequiredUserId,
  480. _sessionContext.UserName,
  481. _sessionContext.RequiredOrgCode,
  482. _sessionContext.OrgName);
  483. await _workflowTraceRepository.UpdateAsync(trace, cancellationToken);
  484. }
  485. private async Task NextTraceAsync(Workflow workflow, BasicWorkflowDto dto, WorkflowStep step, CancellationToken cancellationToken)
  486. {
  487. var trace = await GetWorkflowTraceAsync(workflow.Id, step.Id, cancellationToken);
  488. _mapper.Map(dto, trace);
  489. _mapper.Map(step, trace);
  490. trace.ExpiredTime = workflow.ExpiredTime;
  491. await _workflowTraceRepository.UpdateAsync(trace, cancellationToken);
  492. }
  493. private async Task AcceptTraceAsync(Workflow workflow, WorkflowStep currentStepBox, WorkflowStep currentStep, CancellationToken cancellationToken)
  494. {
  495. var trace = _mapper.Map<WorkflowTrace>(currentStep);//todo ignore parentId, map stepId
  496. trace.Status = EWorkflowTraceStatus.Normal;
  497. trace.ExpiredTime = workflow.ExpiredTime;
  498. if (!string.IsNullOrEmpty(currentStep.PreviousId) && currentStepBox.Steps.Count > 1)
  499. {
  500. //有会签
  501. var parentTrace = await GetWorkflowTraceAsync(workflow.Id, currentStep.PreviousId, cancellationToken);
  502. trace.ParentId = parentTrace.Id;
  503. }
  504. await _workflowTraceRepository.AddAsync(trace, cancellationToken);
  505. }
  506. private async Task<WorkflowTrace> GetWorkflowTraceAsync(string workflowId, string stepId, CancellationToken cancellationToken)
  507. {
  508. var parentTrace = await _workflowTraceRepository.GetAsync(d =>
  509. d.WorkflowId == workflowId && d.StepId == stepId, cancellationToken);
  510. if (parentTrace == null)
  511. throw new UserFriendlyException($"未找到对应trace, workflowId: {workflowId}, stepId: {stepId}");
  512. return parentTrace;
  513. }
  514. private async Task RecallAsync(Workflow workflow, RecallDto dto, WorkflowStep targetStepBox, CancellationToken cancellationToken)
  515. {
  516. //update uncompleted traces
  517. await RecallTraceAsync(workflow.Id, dto, cancellationToken);
  518. //remove completedSteps include target self
  519. var completeStepBoxes = workflow.StepBoxes.Where(d =>
  520. d.Code == dto.TargetStepCode || d.CreationTime > targetStepBox.CreationTime);
  521. var removeSteps = new List<WorkflowStep>();
  522. foreach (var stepBox in completeStepBoxes)
  523. {
  524. removeSteps.Add(stepBox);
  525. removeSteps.AddRange(stepBox.Steps);
  526. }
  527. await _workflowStepRepository.RemoveRangeAsync(removeSteps, cancellationToken);
  528. //recreate targetStep
  529. var nextStepBoxDefine = GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
  530. await CreateStepAsync(workflow, nextStepBoxDefine, dto, targetStepBox, targetStepBox.Steps.First(), cancellationToken);
  531. //flow manage
  532. if (workflow.IsInCountersign())
  533. {
  534. var currentCountersignStepBox =
  535. workflow.StepBoxes.First(d => d.Code == workflow.CurrentCountersignStepCode);
  536. //目标节点在初始会签节点之前或正好
  537. if (targetStepBox.Code == workflow.CurrentCountersignStepCode || targetStepBox.CreationTime < currentCountersignStepBox.CreationTime)
  538. await ResetWorkflowCurrentStepInfo(workflow, dto, targetStepBox, cancellationToken);
  539. }
  540. }
  541. private static void CheckWhetherRunnable(EWorkflowStatus status)
  542. {
  543. if (status is not EWorkflowStatus.Runnable)
  544. throw new UserFriendlyException("当前流程状态不可继续流转");
  545. }
  546. private async Task<WorkflowStep> CreateStepAsync(Workflow workflow, StepDefine stepBoxDefine, BasicWorkflowDto dto,
  547. WorkflowStep? prevStepBox = null, WorkflowStep? prevStep = null, CancellationToken cancellationToken = default)
  548. {
  549. if (stepBoxDefine.StepType is EStepType.Start or EStepType.End)
  550. throw new UserFriendlyException("开始和结束节点无法创建子节点");
  551. var stepBox = workflow.StepBoxes.FirstOrDefault(d => d.Code == stepBoxDefine.Code);
  552. if (stepBox == null)
  553. {
  554. stepBox = CreateStepBox(stepBoxDefine, dto, prevStepBox?.Id ?? string.Empty);
  555. await _workflowStepRepository.AddAsync(stepBox, cancellationToken);
  556. }
  557. if (stepBoxDefine.StepType is EStepType.CountersignEnd)
  558. {
  559. if (prevStep is null)
  560. throw new UserFriendlyException($"汇总节点的上级节点不能为空节点,workflowId: {workflow.Id}", "创建汇总节点异常");
  561. var countersignId = string.IsNullOrEmpty(prevStep.TopCountersignId)
  562. ? prevStep.PrevCountersignId
  563. : prevStep.TopCountersignId;
  564. var step = stepBox.Steps.FirstOrDefault(d => d.PrevCountersignId == countersignId);
  565. if (step != null) return stepBox;
  566. var countersignStartStep = FindCountersignStartStep(workflow, stepBoxDefine.CountersignStartCode, countersignId);
  567. string? topCountersignId = countersignStartStep.StepType is EStepType.CountersignEnd
  568. ? countersignStartStep.TopCountersignId
  569. : countersignStartStep.IsInCountersign
  570. ? countersignStartStep.PrevCountersignId
  571. : null;
  572. await CreateSubStepsAsync(stepBoxDefine, dto, stepBox, prevStep.Id, EWorkflowStepStatus.Created,
  573. countersignId, topCountersignId, cancellationToken);
  574. }
  575. else
  576. {
  577. if (prevStep is null)
  578. {
  579. //创建流程或特殊处理场景
  580. await CreateSubStepsAsync(stepBoxDefine, dto, stepBox, string.Empty, EWorkflowStepStatus.Assigned,
  581. null, null, cancellationToken);
  582. }
  583. else
  584. {
  585. var prevCountersignId = prevStep.HasStartCountersign
  586. ? prevStep.StartCountersignId
  587. : prevStep.PrevCountersignId;
  588. await CreateSubStepsAsync(stepBoxDefine, dto, stepBox, prevStep.Id, EWorkflowStepStatus.Assigned,
  589. prevCountersignId, null, cancellationToken);
  590. }
  591. }
  592. return stepBox;
  593. }
  594. private async Task CreateSubStepsAsync(
  595. StepDefine stepBoxDefine,
  596. BasicWorkflowDto dto,
  597. WorkflowStep stepBox,
  598. string prevStepId,
  599. EWorkflowStepStatus stepStatus,
  600. string? prevCountersignId = null,
  601. string? topCountersignId = null,
  602. CancellationToken cancellationToken = default)
  603. {
  604. if (stepBoxDefine.HandlerType is EHandlerType.AssignUser or EHandlerType.AssignOrg)
  605. {
  606. var subSteps = CreateSubSteps(stepBox, stepBox.HandlerClassifies, dto.NextMainHandler,
  607. prevStepId, prevCountersignId, topCountersignId, stepStatus);
  608. stepBox.Steps.AddRange(subSteps);
  609. await _workflowStepRepository.AddRangeAsync(subSteps, cancellationToken);
  610. }
  611. else
  612. {
  613. if (!dto.Handlers.Any())
  614. throw new UserFriendlyException("未指定节点处理者");
  615. var subSteps = CreateSubSteps(stepBox, dto.Handlers, dto.NextMainHandler,
  616. prevStepId, prevCountersignId, topCountersignId, stepStatus);
  617. stepBox.Steps.AddRange(subSteps);
  618. await _workflowStepRepository.AddRangeAsync(subSteps, cancellationToken);
  619. }
  620. }
  621. private (WorkflowStep stepBox, WorkflowStep step) GetStep(List<WorkflowStep> stepBoxes, string stepId)
  622. {
  623. foreach (var stepBox in stepBoxes)
  624. {
  625. foreach (var step in stepBox.Steps)
  626. {
  627. if (step.Id == stepId)
  628. return (stepBox, step);
  629. }
  630. }
  631. throw new UserFriendlyException("未找到对应节点");
  632. }
  633. /// <summary>
  634. /// 查询未完成节点
  635. /// </summary>
  636. /// <param name="stepBoxes"></param>
  637. /// <param name="orgCode"></param>
  638. /// <param name="userId"></param>
  639. /// <returns></returns>
  640. private (WorkflowStep, WorkflowStep) GetUnCompleteStep(List<WorkflowStep> stepBoxes, string orgCode, string userId)
  641. {
  642. var (stepBox, step) = GetStep(stepBoxes, orgCode, userId, d => d != EWorkflowStepStatus.Completed);
  643. if (step == null)
  644. throw new UserFriendlyException("未找到对应节点");
  645. return (stepBox, step);
  646. }
  647. private (WorkflowStep, WorkflowStep) GetUnCompleteStepOrDefault(List<WorkflowStep> stepBoxes, string orgCode, string userId) =>
  648. GetStep(stepBoxes, orgCode, userId, d => d != EWorkflowStepStatus.Completed);
  649. private (WorkflowStep, WorkflowStep) GetStep(List<WorkflowStep> stepBoxes, string orgCode, string userId, Func<EWorkflowStepStatus, bool> predicate)
  650. {
  651. if (!stepBoxes.Any()) throw new UserFriendlyException("该流程中暂无节点");
  652. foreach (var stepBox in stepBoxes)
  653. {
  654. foreach (var step in stepBox.Steps)
  655. {
  656. if (predicate(step.Status) && (step.HandlerId == orgCode || step.HandlerId == userId))
  657. return (stepBox, step);
  658. }
  659. }
  660. return new();
  661. }
  662. private WorkflowStep CreateStepBox(StepDefine stepBasic, BasicWorkflowDto dto, string prevStepBoxId)
  663. {
  664. var stepBox = _mapper.Map<WorkflowStep>(stepBasic);
  665. _mapper.Map(dto, stepBox);
  666. stepBox.PreviousId = prevStepBoxId;
  667. return stepBox;
  668. }
  669. private List<WorkflowStep> CreateSubSteps(
  670. WorkflowStep stepBox,
  671. List<IdName> nextHandlers,
  672. string nextMainHandler,
  673. string prevStepId,
  674. string? prevCountersignId,
  675. string? topCountersignId,
  676. EWorkflowStepStatus stepStatus)
  677. {
  678. return nextHandlers.Select(d =>
  679. {
  680. var step = _mapper.Map<WorkflowStep>(stepBox);
  681. step.ParentId = stepBox.Id;
  682. step.HandlerId = d.Id;
  683. step.IsMain = d.Id == nextMainHandler;
  684. step.PreviousId = prevStepId;
  685. step.PrevCountersignId = prevCountersignId;
  686. step.TopCountersignId = topCountersignId;
  687. step.Status = stepStatus;
  688. return step;
  689. }).ToList();
  690. }
  691. /// <summary>
  692. /// 依据配置生成过期时间
  693. /// </summary>
  694. /// <returns></returns>
  695. private DateTime GenerateExpiredTime(string defineCode)
  696. {
  697. //GetConfig(string defineCode).Time
  698. return DateTime.Now.AddDays(7); //todo 依据配置生成, Think about 工作日
  699. }
  700. private string GetTimeLimit(string defineCode)
  701. {
  702. //return GetConfig(string defineCode).Description;
  703. return "7个工作日";
  704. }
  705. //private ConfigInCludeDescriptionAndTime GetConfig(string defineCode)
  706. //{
  707. // throw new NotImplementedException();
  708. //}
  709. #endregion
  710. }
  711. }