WorkflowApplication.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. using Hotline.FlowEngine;
  2. using Hotline.FlowEngine.Definitions;
  3. using Hotline.FlowEngine.Workflows;
  4. using Hotline.Identity.Accounts;
  5. using Hotline.Identity.Roles;
  6. using Hotline.SeedData;
  7. using Hotline.Settings;
  8. using Hotline.Share.Dtos.FlowEngine;
  9. using Hotline.Share.Enums.FlowEngine;
  10. using Hotline.Share.Enums.Identity;
  11. using Hotline.Users;
  12. using SqlSugar;
  13. using XF.Domain.Authentications;
  14. using XF.Domain.Dependency;
  15. using XF.Domain.Entities;
  16. using XF.Domain.Exceptions;
  17. namespace Hotline.Application.FlowEngine;
  18. public class WorkflowApplication : IWorkflowApplication, IScopeDependency
  19. {
  20. private readonly IDefinitionDomainService _definitionDomainService;
  21. private readonly IWorkflowDomainService _workflowDomainService;
  22. private readonly IWorkflowRepository _workflowRepository;
  23. private readonly IWorkflowAssignRepository _workflowAssignRepository;
  24. private readonly IUserRepository _userRepository;
  25. private readonly IAccountRepository _accountRepository;
  26. private readonly ISystemOrganizeRepository _organizeRepository;
  27. private readonly IRoleRepository _roleRepository;
  28. private readonly IUserDomainService _userDomainService;
  29. private readonly IAccountDomainService _accountDomainService;
  30. private readonly ISessionContext _sessionContext;
  31. public WorkflowApplication(
  32. IDefinitionDomainService definitionDomainService,
  33. IWorkflowDomainService workflowDomainService,
  34. IWorkflowRepository workflowRepository,
  35. IWorkflowAssignRepository workflowAssignRepository,
  36. IUserRepository userRepository,
  37. IAccountRepository accountRepository,
  38. ISystemOrganizeRepository organizeRepository,
  39. IRoleRepository roleRepository,
  40. ISessionContext sessionContext)
  41. {
  42. _definitionDomainService = definitionDomainService;
  43. _workflowDomainService = workflowDomainService;
  44. _workflowRepository = workflowRepository;
  45. _workflowAssignRepository = workflowAssignRepository;
  46. _userRepository = userRepository;
  47. _accountRepository = accountRepository;
  48. _organizeRepository = organizeRepository;
  49. _roleRepository = roleRepository;
  50. _sessionContext = sessionContext;
  51. }
  52. public async Task StartWorkflowAsync(StartWorkflowDto dto, string? externalId = null, CancellationToken cancellationToken = default)
  53. {
  54. if (string.IsNullOrEmpty(dto.DefinitionCode) && string.IsNullOrEmpty(dto.DefinitionModuleCode))
  55. throw new UserFriendlyException("非法参数");
  56. var definition = string.IsNullOrEmpty(dto.DefinitionCode)
  57. ? await _definitionDomainService.GetLastVersionByModuleCodeAsync(dto.DefinitionModuleCode, cancellationToken)
  58. : await _definitionDomainService.GetLastVersionAsync(dto.DefinitionCode, cancellationToken);
  59. if (definition == null)
  60. throw new UserFriendlyException("无效模板名称");
  61. if (definition.Status is EDefinitionStatus.Temporary or EDefinitionStatus.Disable)
  62. throw new UserFriendlyException("该模板不可用");
  63. var nextStepBoxDefine = _workflowDomainService.GetStepBoxDefine(definition, dto.NextStepCode);
  64. var workflow = await _workflowDomainService.CreateWorkflowAsync(definition, dto.Title, externalId, cancellationToken);
  65. var isStartCountersign = nextStepBoxDefine.CouldPrevStartCountersign(dto.NextHandlers.Count);
  66. var flowAssignMode = await GetFlowAssignModeAsync(nextStepBoxDefine, isStartCountersign, dto.NextHandlers, cancellationToken);
  67. await _workflowDomainService.StartAsync(workflow, dto, nextStepBoxDefine, isStartCountersign, flowAssignMode, cancellationToken);
  68. //更新接办部门(详情页面展示)
  69. await AddOrUpdateAssignAsync(workflow, dto, nextStepBoxDefine, cancellationToken);
  70. }
  71. /// <summary>
  72. /// 流转至下一节点(节点办理)
  73. /// </summary>
  74. /// <param name="dto"></param>
  75. /// <param name="cancellationToken"></param>
  76. /// <returns></returns>
  77. public async Task NextAsync(NextWorkflowDto dto, CancellationToken cancellationToken)
  78. {
  79. var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, true, true, withCountersigns: true, cancellationToken: cancellationToken);
  80. var nextStepBoxDefine = _workflowDomainService.GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
  81. //需求:按角色选择办理人可以不选,表示该角色下所有人都可以办理,同时依据配置:是否本部门人办理显示待选办理人。角色下只要一人办理即可(即:角色下不发起会签)
  82. if (nextStepBoxDefine.HandlerType != EHandlerType.Role && !dto.NextHandlers.Any())
  83. throw new UserFriendlyException("未指定节点处理者");
  84. //下一节点为结束节点时,无办理人等参数,只有办理意见即可
  85. var isOutOfCallCenter = nextStepBoxDefine.StepType is not EStepType.End
  86. && await CheckIfFlowOutOfCallCenterAsync(nextStepBoxDefine, dto.NextMainHandler, cancellationToken);
  87. var isStartCountersign = nextStepBoxDefine.CouldPrevStartCountersign(dto.NextHandlers.Count);
  88. var flowAssignMode = await GetFlowAssignModeAsync(nextStepBoxDefine, isStartCountersign, dto.NextHandlers, cancellationToken);
  89. await _workflowDomainService.NextAsync(workflow, dto, nextStepBoxDefine, isOutOfCallCenter, isStartCountersign, flowAssignMode, cancellationToken);
  90. //更新接办部门(详情页面展示)
  91. await AddOrUpdateAssignAsync(workflow, dto, nextStepBoxDefine, cancellationToken);
  92. }
  93. /// <summary>
  94. /// 撤回至任意节点
  95. /// </summary>
  96. public async Task RecallAsync(RecallDto dto, CancellationToken cancellationToken)
  97. {
  98. var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, true, true, cancellationToken: cancellationToken);
  99. var targetStepDefine = _workflowDomainService.GetStepBoxDefine(workflow.Definition, dto.TargetStepCode);
  100. var isStartCountersign = targetStepDefine.CouldPrevStartCountersign(dto.NextHandlers.Count);
  101. var flowAssignMode = await GetFlowAssignModeAsync(targetStepDefine, isStartCountersign, dto.NextHandlers, cancellationToken);
  102. await _workflowDomainService.RecallAsync(workflow, dto, targetStepDefine, isStartCountersign, flowAssignMode, cancellationToken);
  103. }
  104. /// <summary>
  105. /// 跳转至任意节点
  106. /// </summary>
  107. public async Task JumpAsync(RecallDto dto, CancellationToken cancellationToken)
  108. {
  109. var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, true, true, cancellationToken: cancellationToken);
  110. var targetStepDefine = _workflowDomainService.GetStepBoxDefine(workflow.Definition, dto.TargetStepCode);
  111. var isStartCountersign = targetStepDefine.CouldPrevStartCountersign(dto.NextHandlers.Count);
  112. var flowAssignMode = await GetFlowAssignModeAsync(targetStepDefine, isStartCountersign, dto.NextHandlers, cancellationToken);
  113. await _workflowDomainService.JumpAsync(workflow, dto, targetStepDefine, isStartCountersign, flowAssignMode, cancellationToken);
  114. }
  115. public async Task<IReadOnlyList<KeyValuePair<string, string>>> GetStartOptionsAsync(string moduleCode, CancellationToken cancellationToken)
  116. {
  117. var definition =
  118. await _definitionDomainService.GetLastVersionByModuleCodeAsync(moduleCode, cancellationToken);
  119. if (definition == null)
  120. throw new UserFriendlyException($"无效模块编码, modeuleCode: {moduleCode}", "无效模块编码");
  121. if (definition.Status is EDefinitionStatus.Temporary or EDefinitionStatus.Disable)
  122. throw new UserFriendlyException("该模板不可用");
  123. var startStep = definition.Steps.FirstOrDefault(d => d.StepType == EStepType.Start);
  124. if (startStep == null)
  125. throw new UserFriendlyException("未正确配置开始节点");
  126. var nextStepDefines = definition.FindSteps(startStep.NextSteps);
  127. return nextStepDefines.Select(d => new KeyValuePair<string, string>(d.Code, d.Name)).ToList();
  128. }
  129. /// <summary>
  130. /// 根据节点配置查询待选参数
  131. /// </summary>
  132. /// <param name="stepDefine"></param>
  133. /// <param name="cancellationToken"></param>
  134. /// <returns></returns>
  135. /// <exception cref="ArgumentOutOfRangeException"></exception>
  136. public async Task<IReadOnlyList<KeyValuePair<string, string>>> GetNextStepOptionsAsync(StepDefine stepDefine, CancellationToken cancellationToken)
  137. {
  138. if (stepDefine.StepType == EStepType.End) return new List<KeyValuePair<string, string>>();
  139. switch (stepDefine.HandlerType)
  140. {
  141. case EHandlerType.AssignUser:
  142. var users = await _userRepository.QueryAsync(d =>
  143. !d.IsDeleted &&
  144. stepDefine.HandlerClassifies.Select(d => d.Id).Contains(d.Id));
  145. return users.Select(d => new KeyValuePair<string, string>(d.Id, d.Name)).ToList();
  146. case EHandlerType.AssignOrg:
  147. var orgs = await _organizeRepository.QueryAsync(d =>
  148. d.IsEnable &&
  149. stepDefine.HandlerClassifies.Select(d => d.Id).Contains(d.OrgCode));
  150. return orgs.Select(d => new KeyValuePair<string, string>(d.OrgCode, d.OrgName))
  151. .ToList();
  152. case EHandlerType.Role:
  153. var roles = await _roleRepository.Queryable()
  154. .Includes(d => d.Accounts.Where(x => !x.IsDeleted && x.Status == EAccountStatus.Normal).ToList(), x => x.User)
  155. .Where(d => stepDefine.HandlerClassifies.Select(d => d.Id).Contains(d.Name))
  156. .ToListAsync();
  157. var users1 = roles.SelectMany(d => d.Accounts).Select(d => d.User);
  158. if (stepDefine.OnlySelfOrg ?? false)
  159. users1 = users1.Where(d => d.OrgCode == _sessionContext.RequiredOrgCode);
  160. return users1.Select(d => new KeyValuePair<string, string>(d.Id, d.Name)).ToList();
  161. case EHandlerType.OrgLevel:
  162. //当前操作人所属部门的下级部门并且属于配置orgLevel的部门
  163. var levels = stepDefine.HandlerClassifies.Select(d => d.Id).Select(d => int.Parse(d));
  164. var orgs1 = await _organizeRepository.QueryAsync(d =>
  165. d.IsEnable && d.OrgCode.StartsWith(_sessionContext.RequiredOrgCode) &&
  166. levels.Contains(d.OrgLevel));
  167. return orgs1.Select(d => new KeyValuePair<string, string>(d.OrgCode, d.OrgName))
  168. .ToList();
  169. case EHandlerType.OrgType:
  170. var types = stepDefine.HandlerClassifies.Select(d => d.Id)
  171. .Select(d => Enum.Parse<EOrgType>(d));
  172. var org2 = await _organizeRepository.QueryAsync(d =>
  173. d.IsEnable && d.OrgCode.StartsWith(_sessionContext.RequiredOrgCode) &&
  174. types.Contains(d.OrgType));
  175. return org2.Select(d => new KeyValuePair<string, string>(d.OrgCode, d.OrgName))
  176. .ToList();
  177. default:
  178. throw new ArgumentOutOfRangeException();
  179. }
  180. }
  181. /// <summary>
  182. /// 查询指派办理人的处理方式及实际办理人
  183. /// </summary>
  184. public async Task<FlowAssignMode> GetFlowAssignModeAsync(StepDefine stepDefine, bool isStartCountersign, List<IdName> handlers, CancellationToken cancellationToken)
  185. {
  186. if (stepDefine.StepType is EStepType.Start or EStepType.End) return new();
  187. switch (stepDefine.HandlerType)
  188. {
  189. case EHandlerType.Role:
  190. if (!handlers.Any())
  191. {
  192. var roles = await _roleRepository.Queryable()
  193. .Includes(d => d.Accounts, x => x.User)
  194. .Where(d => stepDefine.HandlerClassifies.Select(d => d.Id).Contains(d.Name))
  195. .ToListAsync();
  196. handlers = roles.SelectMany(d => d.Accounts).Distinct().Select(d => new IdName(d.Id, d.User.Name)).ToList();
  197. }
  198. return FlowAssignMode.Create(EFlowAssignType.User, handlers, isStartCountersign);
  199. case EHandlerType.OrgLevel:
  200. case EHandlerType.OrgType:
  201. case EHandlerType.AssignOrg:
  202. return FlowAssignMode.Create(EFlowAssignType.Org, handlers, isStartCountersign);
  203. case EHandlerType.AssignUser:
  204. return FlowAssignMode.Create(EFlowAssignType.User, handlers, isStartCountersign);
  205. default:
  206. throw new ArgumentOutOfRangeException();
  207. }
  208. }
  209. #region private
  210. private async Task<IReadOnlyList<NextStepOptions>> GetNextStepOptionsAsync(IReadOnlyList<StepDefine> nextStepDefines)
  211. {
  212. //todo 性能问题
  213. var items = new List<NextStepOptions>();
  214. foreach (var nextStepDefine in nextStepDefines)
  215. {
  216. var options = new NextStepOptions
  217. {
  218. Code = nextStepDefine.Code,
  219. Name = nextStepDefine.Name,
  220. };
  221. if (nextStepDefine.StepType != EStepType.End)
  222. {
  223. switch (nextStepDefine.HandlerType)
  224. {
  225. case EHandlerType.AssignUser:
  226. var users = await _userRepository.QueryAsync(d =>
  227. !d.IsDeleted &&
  228. nextStepDefine.HandlerClassifies.Select(d => d.Id).Contains(d.Id));
  229. options.NextSteps = users.Select(d => new KeyValuePair<string, string>(d.Id, d.Name)).ToList();
  230. break;
  231. case EHandlerType.AssignOrg:
  232. var orgs = await _organizeRepository.QueryAsync(d =>
  233. d.IsEnable &&
  234. nextStepDefine.HandlerClassifies.Select(d => d.Id).Contains(d.OrgCode));
  235. options.NextSteps = orgs.Select(d => new KeyValuePair<string, string>(d.OrgCode, d.OrgName))
  236. .ToList();
  237. break;
  238. case EHandlerType.Role:
  239. var roles = await _roleRepository.Queryable()
  240. .Includes(d => d.Accounts.Where(x => !x.IsDeleted && x.Status == EAccountStatus.Normal).ToList(), x => x.User)
  241. .Where(d => nextStepDefine.HandlerClassifies.Select(d => d.Id).Contains(d.Name))
  242. .ToListAsync();
  243. var users1 = roles.SelectMany(d => d.Accounts).Select(d => d.User);
  244. if (nextStepDefine.OnlySelfOrg ?? false)
  245. users1 = users1.Where(d => d.OrgCode == _sessionContext.RequiredOrgCode);
  246. options.NextSteps = users1.Select(d => new KeyValuePair<string, string>(d.Id, d.Name)).ToList();
  247. break;
  248. case EHandlerType.OrgLevel:
  249. //当前操作人所属部门的下级部门并且属于配置orgLevel的部门
  250. var levels = nextStepDefine.HandlerClassifies.Select(d => d.Id).Select(d => int.Parse(d));
  251. var orgs1 = await _organizeRepository.QueryAsync(d =>
  252. d.IsEnable && d.OrgCode.StartsWith(_sessionContext.RequiredOrgCode) &&
  253. levels.Contains(d.OrgLevel));
  254. options.NextSteps = orgs1.Select(d => new KeyValuePair<string, string>(d.OrgCode, d.OrgName))
  255. .ToList();
  256. break;
  257. case EHandlerType.OrgType:
  258. var types = nextStepDefine.HandlerClassifies.Select(d => d.Id)
  259. .Select(d => Enum.Parse<EOrgType>(d));
  260. var org2 = await _organizeRepository.QueryAsync(d =>
  261. d.IsEnable && d.OrgCode.StartsWith(_sessionContext.RequiredOrgCode) &&
  262. types.Contains(d.OrgType));
  263. options.NextSteps = org2.Select(d => new KeyValuePair<string, string>(d.OrgCode, d.OrgName))
  264. .ToList();
  265. break;
  266. default:
  267. throw new ArgumentOutOfRangeException();
  268. }
  269. }
  270. items.Add(options);
  271. }
  272. return items;
  273. }
  274. /// <summary>
  275. /// 更新接办部门
  276. /// </summary>
  277. private async Task AddOrUpdateAssignAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine, CancellationToken cancellationToken)
  278. {
  279. if (nextStepBoxDefine.StepType is EStepType.Normal)
  280. {
  281. await _workflowAssignRepository.RemoveAsync(d =>
  282. d.WorkflowId == workflow.Id && d.OrgCode == _sessionContext.RequiredOrgCode,
  283. cancellationToken: cancellationToken);
  284. var assigns = new List<WorkflowAssign>();
  285. switch (nextStepBoxDefine.HandlerType)
  286. {
  287. case EHandlerType.Role:
  288. if (dto.NextHandlers.Any())
  289. {
  290. //选了handler,handler为userId
  291. var users1 = await _userRepository.Queryable()
  292. .Includes(d => d.Organization)
  293. .Where(d => dto.NextHandlers.Select(x => x.Id).Contains(d.Id))
  294. .ToListAsync();
  295. assigns = users1.Select(d => WorkflowAssign.Create(workflow.Id, d.OrgCode, d.Organization.OrgName))
  296. .ToList();
  297. }
  298. else
  299. {
  300. //如果模板配置为本部门办理,则为当前办理人orgCode,非本部门办理:属于该角色的所有用户所属部门
  301. if (nextStepBoxDefine.OnlySelfOrg.HasValue && nextStepBoxDefine.OnlySelfOrg.Value)
  302. {
  303. assigns = new List<WorkflowAssign>
  304. {
  305. WorkflowAssign.Create(workflow.Id, _sessionContext.RequiredOrgCode, _sessionContext.OrgName)
  306. };
  307. }
  308. else
  309. {
  310. var accounts = await _accountRepository.Queryable()
  311. .Includes(d => d.User, d => d.Organization)
  312. .Where(d => dto.NextHandlers.Select(d => d.Id).Contains(d.Name))
  313. .ToListAsync();
  314. assigns = accounts.Select(d => d.User.Organization).Select(d =>
  315. WorkflowAssign.Create(workflow.Id, d.OrgCode, d.OrgName)).ToList();
  316. }
  317. }
  318. break;
  319. case EHandlerType.OrgLevel:
  320. case EHandlerType.OrgType:
  321. case EHandlerType.AssignOrg:
  322. assigns = dto.NextHandlers.Select(d => WorkflowAssign.Create(workflow.Id, d.Id, d.Name)).ToList();
  323. break;
  324. case EHandlerType.AssignUser:
  325. //指定人所属部门
  326. var users = await _userRepository.Queryable()
  327. .Includes(d => d.Organization)
  328. .Where(d => dto.NextHandlers.Select(d => d.Id).Contains(d.Id))
  329. .ToListAsync();
  330. assigns = users.Select(d => WorkflowAssign.Create(workflow.Id, d.OrgCode, d.Organization.OrgName))
  331. .ToList();
  332. break;
  333. default:
  334. throw new ArgumentOutOfRangeException();
  335. }
  336. if (assigns.Any())
  337. await _workflowAssignRepository.AddRangeAsync(assigns, cancellationToken);
  338. }
  339. }
  340. private async Task<bool> CheckIfFlowOutOfCallCenterAsync(StepDefine nextStepBoxDefine, string mainHandler, CancellationToken CancellationToken)
  341. {
  342. //current is center & next is not center return true
  343. var isFromCallCenter = IsOrgFromCallCenter(_sessionContext.RequiredOrgCode);
  344. if (!isFromCallCenter) return false;
  345. var isOutCallCenter = await CheckNextStepHandlerIsOutCallCenterAsync(nextStepBoxDefine, mainHandler, CancellationToken);
  346. return isFromCallCenter && isOutCallCenter;
  347. }
  348. private async Task<bool> CheckNextStepHandlerIsOutCallCenterAsync(StepDefine nextStepBoxDefine, string mainHandler, CancellationToken cancellationToken)
  349. {
  350. if (nextStepBoxDefine.HandlerType is EHandlerType.AssignUser or EHandlerType.Role)
  351. {
  352. //mainHandler is userId
  353. var handler = await _userRepository.GetAsync(mainHandler, cancellationToken);
  354. if (handler == null)
  355. throw new UserFriendlyException($"mainHandler未找到对应User, handler: {mainHandler}");
  356. if (string.IsNullOrEmpty(handler.OrgCode))
  357. throw UserFriendlyException.SameMessage($"该用户未配置所属部门, userId: {mainHandler}");
  358. return !IsOrgFromCallCenter(handler.OrgCode);
  359. }
  360. else
  361. {
  362. //mainHandler is orgCode
  363. return !IsOrgFromCallCenter(mainHandler);
  364. }
  365. }
  366. /// <summary>
  367. /// 是否为呼叫中心
  368. /// </summary>
  369. /// <returns></returns>
  370. private bool IsOrgFromCallCenter(string orgCode) =>
  371. string.CompareOrdinal(orgCode, OrgSeedData.CallCenterCode) == 0;
  372. #endregion
  373. }