WorkflowApplication.cs 18 KB

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