WorkflowApplication.cs 19 KB

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