WorkflowApplication.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. using Hotline.FlowEngine.Definitions;
  2. using Hotline.FlowEngine.Workflows;
  3. using Hotline.Identity.Accounts;
  4. using Hotline.Identity.Roles;
  5. using Hotline.SeedData;
  6. using Hotline.Settings;
  7. using Hotline.Share.Dtos.FlowEngine;
  8. using Hotline.Share.Enums.FlowEngine;
  9. using Hotline.Users;
  10. using SqlSugar;
  11. using XF.Domain.Authentications;
  12. using XF.Domain.Dependency;
  13. using XF.Domain.Entities;
  14. using XF.Domain.Exceptions;
  15. namespace Hotline.Application.FlowEngine;
  16. public class WorkflowApplication : IWorkflowApplication, IScopeDependency
  17. {
  18. private readonly IDefinitionDomainService _definitionDomainService;
  19. private readonly IWorkflowDomainService _workflowDomainService;
  20. private readonly IWorkflowRepository _workflowRepository;
  21. private readonly IWorkflowAssignRepository _workflowAssignRepository;
  22. private readonly IUserRepository _userRepository;
  23. private readonly IAccountRepository _accountRepository;
  24. private readonly ISystemOrganizeRepository _organizeRepository;
  25. private readonly IRoleRepository _roleRepository;
  26. private readonly IUserDomainService _userDomainService;
  27. private readonly IAccountDomainService _accountDomainService;
  28. private readonly ISessionContext _sessionContext;
  29. public WorkflowApplication(
  30. IDefinitionDomainService definitionDomainService,
  31. IWorkflowDomainService workflowDomainService,
  32. IWorkflowRepository workflowRepository,
  33. IWorkflowAssignRepository workflowAssignRepository,
  34. IUserRepository userRepository,
  35. IAccountRepository accountRepository,
  36. ISystemOrganizeRepository organizeRepository,
  37. IRoleRepository roleRepository,
  38. ISessionContext sessionContext)
  39. {
  40. _definitionDomainService = definitionDomainService;
  41. _workflowDomainService = workflowDomainService;
  42. _workflowRepository = workflowRepository;
  43. _workflowAssignRepository = workflowAssignRepository;
  44. _userRepository = userRepository;
  45. _accountRepository = accountRepository;
  46. _organizeRepository = organizeRepository;
  47. _roleRepository = roleRepository;
  48. _sessionContext = sessionContext;
  49. }
  50. public async Task<string> StartWorkflowAsync(StartWorkflowDto dto, string externalId, CancellationToken cancellationToken = default)
  51. {
  52. var definition = await _definitionDomainService.GetLastVersionDefinitionAsync(dto.DefinitionCode, cancellationToken);
  53. if (definition == null)
  54. throw new UserFriendlyException("无效模板名称");
  55. if (definition.Status != EDefinitionStatus.Enable)
  56. throw new UserFriendlyException("该模板不可用");
  57. var exists = await _workflowDomainService.ExsitsAsync(definition.ModuleCode, externalId, cancellationToken);
  58. if (exists)
  59. throw UserFriendlyException.SameMessage($"该工单已发起过{WorkflowModule.Modules[definition.ModuleCode]}流程");
  60. var nextStepBoxDefine = _workflowDomainService.GetStepBoxDefine(definition, dto.NextStepCode);
  61. var workflow = await _workflowDomainService.CreateWorkflowAsync(definition, dto.Title, externalId, cancellationToken);
  62. await _workflowDomainService.StartAsync(workflow, dto, nextStepBoxDefine, cancellationToken);
  63. //更新接办部门(详情页面展示)
  64. await AddOrUpdateAssignAsync(workflow, dto, nextStepBoxDefine, cancellationToken);
  65. return workflow.Id;
  66. }
  67. /// <summary>
  68. /// 流转至下一节点(节点办理)
  69. /// </summary>
  70. /// <param name="dto"></param>
  71. /// <param name="cancellationToken"></param>
  72. /// <returns></returns>
  73. public async Task NextAsync(NextWorkflowDto dto, CancellationToken cancellationToken)
  74. {
  75. var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, true, true, cancellationToken: cancellationToken);
  76. var nextStepBoxDefine = _workflowDomainService.GetStepBoxDefine(workflow.Definition, dto.NextStepCode);
  77. var isOutOfCallCenter =
  78. await CheckIfFlowOutOfCallCenterAsync(nextStepBoxDefine, dto.NextMainHandler, cancellationToken);
  79. var isStartCountersign = nextStepBoxDefine.IsStartCountersign(dto.Handlers.Count);
  80. await _workflowDomainService.NextAsync(workflow, dto, nextStepBoxDefine, isOutOfCallCenter, isStartCountersign, cancellationToken);
  81. //更新接办部门(详情页面展示)
  82. await AddOrUpdateAssignAsync(workflow, dto, nextStepBoxDefine, cancellationToken);
  83. }
  84. /// <summary>
  85. /// 查询流程下一节点配置参数
  86. /// </summary>
  87. /// <param name="moduleCode"></param>
  88. /// <param name="externalId"></param>
  89. /// <returns></returns>
  90. public async Task<IReadOnlyList<NextStepOptions>> GetNextStepOptionsAsync(string moduleCode, string externalId, CancellationToken cancellationToken)
  91. {
  92. var workflow = await _workflowDomainService.GetWorkflowAsync(moduleCode, externalId, true, true, cancellationToken: cancellationToken);
  93. if (workflow == null)
  94. throw new UserFriendlyException($"未查询到流程, moduleCode:{moduleCode}, externalId: {externalId}", "未查询到该流程");
  95. return await GetNextStepOptionsAsync(workflow, cancellationToken);
  96. }
  97. public async Task<IReadOnlyList<NextStepOptions>> GetNextStepOptionsAsync(Workflow workflow, CancellationToken cancellationToken)
  98. {
  99. var nextStepDefines = _workflowDomainService.GetNextStepOptions(workflow, cancellationToken);
  100. //todo 性能问题
  101. var items = new List<NextStepOptions>();
  102. foreach (var nextStepDefine in nextStepDefines)
  103. {
  104. var options = new NextStepOptions
  105. {
  106. Code = nextStepDefine.Code,
  107. Name = nextStepDefine.Name,
  108. };
  109. switch (nextStepDefine.HandlerType)
  110. {
  111. case EHandlerType.AssignUser:
  112. var users = await _userRepository.QueryAsync(d => nextStepDefine.HandlerClassifies.Select(d => d.Id).Contains(d.Id));
  113. options.NextSteps = users.Select(d => new KeyValuePair<string, string>(d.Id, d.Name)).ToList();
  114. break;
  115. case EHandlerType.AssignOrg:
  116. var orgs = await _organizeRepository.QueryAsync(d => nextStepDefine.HandlerClassifies.Select(d => d.Id).Contains(d.OrgCode));
  117. options.NextSteps = orgs.Select(d => new KeyValuePair<string, string>(d.OrgCode, d.OrgName)).ToList();
  118. break;
  119. case EHandlerType.Role:
  120. var roles = await _roleRepository.Queryable().Includes(d => d.Accounts, d => d.User)
  121. .Where(d => nextStepDefine.HandlerClassifies.Select(d => d.Id).Contains(d.Name)).ToListAsync();
  122. var users1 = roles.SelectMany(d => d.Accounts).Select(d => d.User);
  123. if (nextStepDefine.OnlySelfOrg ?? false)
  124. users1 = users1.Where(d => d.OrgCode == _sessionContext.RequiredOrgCode);
  125. options.NextSteps = users1.Select(d => new KeyValuePair<string, string>(d.Id, d.Name)).ToList();
  126. break;
  127. case EHandlerType.OrgLevel:
  128. //当前操作人所属部门的下级部门并且属于配置orgLevel的部门
  129. var levels = nextStepDefine.HandlerClassifies.Select(d => d.Id).Select(d => int.Parse(d));
  130. var orgs1 = await _organizeRepository.QueryAsync(d =>
  131. d.IsEnable && d.OrgCode.StartsWith(_sessionContext.RequiredOrgCode) &&
  132. levels.Contains(d.OrgLevel));
  133. options.NextSteps = orgs1.Select(d => new KeyValuePair<string, string>(d.OrgCode, d.OrgName)).ToList();
  134. break;
  135. case EHandlerType.OrgType:
  136. var types = nextStepDefine.HandlerClassifies.Select(d => d.Id).Select(d => Enum.Parse<EOrgType>(d));
  137. var org2 = await _organizeRepository.QueryAsync(d =>
  138. d.IsEnable && d.OrgCode.StartsWith(_sessionContext.RequiredOrgCode) &&
  139. types.Contains(d.OrgType));
  140. options.NextSteps = org2.Select(d => new KeyValuePair<string, string>(d.OrgCode, d.OrgName)).ToList();
  141. break;
  142. default:
  143. throw new ArgumentOutOfRangeException();
  144. }
  145. items.Add(options);
  146. }
  147. return items;
  148. }
  149. /// <summary>
  150. /// 指派下一节点办理人
  151. /// </summary>
  152. public async Task AssignAsync<TEntity>(TEntity entity, EFlowAssignType flowAssignType, IEnumerable<string> handlers, CancellationToken cancellationToken)
  153. where TEntity : IWorkflow
  154. {
  155. switch (flowAssignType)
  156. {
  157. case EFlowAssignType.Org:
  158. case EFlowAssignType.User:
  159. entity.Assign(flowAssignType, handlers);
  160. break;
  161. case EFlowAssignType.Role:
  162. var roles = await _roleRepository.Queryable()
  163. .Includes(d => d.Accounts)
  164. .Where(d => handlers.Contains(d.Name))
  165. .ToListAsync();
  166. var userIds = roles.SelectMany(d => d.Accounts).Select(d => d.Id);
  167. entity.Assign(EFlowAssignType.User, userIds);
  168. break;
  169. default:
  170. throw new ArgumentOutOfRangeException(nameof(flowAssignType), flowAssignType, null);
  171. }
  172. }
  173. #region private
  174. /// <summary>
  175. /// 更新接办部门
  176. /// </summary>
  177. private async Task AddOrUpdateAssignAsync(Workflow workflow, BasicWorkflowDto dto, StepDefine nextStepBoxDefine, CancellationToken cancellationToken)
  178. {
  179. if (nextStepBoxDefine.StepType is EStepType.Normal)
  180. {
  181. await _workflowAssignRepository.RemoveAsync(d =>
  182. d.WorkflowId == workflow.Id && d.OrgCode == _sessionContext.RequiredOrgCode,
  183. cancellationToken: cancellationToken);
  184. var assigns = new List<WorkflowAssign>();
  185. switch (nextStepBoxDefine.HandlerType)
  186. {
  187. case EHandlerType.Role:
  188. if (dto.Handlers.Any())
  189. {
  190. //选了handler,handler为userId
  191. var users1 = await _userRepository.Queryable()
  192. .Includes(d => d.Organization)
  193. .Where(d => dto.Handlers.Select(d => d.Id).Contains(d.Id))
  194. .ToListAsync();
  195. assigns = users1.Select(d => WorkflowAssign.Create(workflow.Id, d.OrgCode, d.Organization.OrgName))
  196. .ToList();
  197. }
  198. else
  199. {
  200. //如果模板配置为本部门办理,则为当前办理人orgCode,非本部门办理:属于该角色的所有用户所属部门
  201. if (nextStepBoxDefine.OnlySelfOrg.HasValue && nextStepBoxDefine.OnlySelfOrg.Value)
  202. {
  203. assigns = new List<WorkflowAssign>
  204. {
  205. WorkflowAssign.Create(workflow.Id, _sessionContext.RequiredOrgCode, _sessionContext.OrgName)
  206. };
  207. }
  208. else
  209. {
  210. var accounts = await _accountRepository.Queryable()
  211. .Includes(d => d.User, d => d.Organization)
  212. .Where(d => dto.Handlers.Select(d => d.Id).Contains(d.Name))
  213. .ToListAsync();
  214. assigns = accounts.Select(d => d.User.Organization).Select(d =>
  215. WorkflowAssign.Create(workflow.Id, d.OrgCode, d.OrgName)).ToList();
  216. }
  217. }
  218. break;
  219. case EHandlerType.OrgLevel:
  220. case EHandlerType.OrgType:
  221. case EHandlerType.AssignOrg:
  222. assigns = dto.Handlers.Select(d => WorkflowAssign.Create(workflow.Id, d.Id, d.Name)).ToList();
  223. break;
  224. case EHandlerType.AssignUser:
  225. //指定人所属部门
  226. var users = await _userRepository.Queryable()
  227. .Includes(d => d.Organization)
  228. .Where(d => dto.Handlers.Select(d => d.Id).Contains(d.Id))
  229. .ToListAsync();
  230. assigns = users.Select(d => WorkflowAssign.Create(workflow.Id, d.OrgCode, d.Organization.OrgName))
  231. .ToList();
  232. break;
  233. default:
  234. throw new ArgumentOutOfRangeException();
  235. }
  236. if (assigns.Any())
  237. await _workflowAssignRepository.AddRangeAsync(assigns, cancellationToken);
  238. }
  239. }
  240. private async Task<bool> CheckIfFlowOutOfCallCenterAsync(StepDefine nextStepBoxDefine, string mainHandler, CancellationToken CancellationToken)
  241. {
  242. //current is center & next is not center return true
  243. var isFromCallCenter = IsOrgFromCallCenter(_sessionContext.RequiredOrgCode);
  244. if (!isFromCallCenter) return false;
  245. var isOutCallCenter = await CheckNextStepHandlerIsOutCallCenterAsync(nextStepBoxDefine, mainHandler, CancellationToken);
  246. return isFromCallCenter && isOutCallCenter;
  247. }
  248. private async Task<bool> CheckNextStepHandlerIsOutCallCenterAsync(StepDefine nextStepBoxDefine, string mainHandler, CancellationToken cancellationToken)
  249. {
  250. if (nextStepBoxDefine.HandlerType is EHandlerType.AssignUser or EHandlerType.Role)
  251. {
  252. //mainHandler is userId
  253. var handler = await _userRepository.GetAsync(mainHandler, cancellationToken);
  254. if (handler == null)
  255. throw new UserFriendlyException($"mainHandler未找到对应User, handler: {mainHandler}");
  256. if (string.IsNullOrEmpty(handler.OrgCode))
  257. throw UserFriendlyException.SameMessage($"该用户未配置所属部门, userId: {mainHandler}");
  258. return !IsOrgFromCallCenter(handler.OrgCode);
  259. }
  260. else
  261. {
  262. //mainHandler is orgCode
  263. return !IsOrgFromCallCenter(mainHandler);
  264. }
  265. }
  266. /// <summary>
  267. /// 是否为呼叫中心部门
  268. /// </summary>
  269. /// <returns></returns>
  270. private bool IsOrgFromCallCenter(string orgCode) => orgCode.StartsWith(OrgSeedData.CallCenterCode);
  271. #endregion
  272. }