WorkflowController.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. using Hotline.Application.FlowEngine;
  2. using Hotline.FlowEngine.Definitions;
  3. using Hotline.FlowEngine.Workflows;
  4. using Hotline.Identity.Roles;
  5. using Hotline.Permissions;
  6. using Hotline.Repository.SqlSugar.Extensions;
  7. using Hotline.Settings;
  8. using Hotline.Share.Dtos;
  9. using Hotline.Share.Dtos.FlowEngine;
  10. using Hotline.Share.Enums.FlowEngine;
  11. using Hotline.Users;
  12. using MapsterMapper;
  13. using Microsoft.AspNetCore.Mvc;
  14. using SqlSugar;
  15. using Hotline.FlowEngine.WfModules;
  16. using Microsoft.AspNetCore.Authorization;
  17. using XF.Domain.Authentications;
  18. using XF.Domain.Exceptions;
  19. using XF.Utility.EnumExtensions;
  20. using System.Reflection;
  21. using XF.Domain.Repository;
  22. namespace Hotline.Api.Controllers;
  23. /// <summary>
  24. /// 工作流管理
  25. /// </summary>
  26. public class WorkflowController : BaseController
  27. {
  28. private readonly IDefinitionDomainService _definitionDomainService;
  29. private readonly IRepository<Definition> _definitionRepository;
  30. private readonly IWorkflowApplication _workflowApplication;
  31. private readonly IWorkflowDomainService _workflowDomainService;
  32. private readonly IWorkflowRepository _workflowRepository;
  33. private readonly IRepository<User> _userRepository;
  34. private readonly ISystemOrganizeRepository _organizeRepository;
  35. private readonly IRepository<Role> _roleRepository;
  36. private readonly ISystemDomainService _systemDomainService;
  37. private readonly IWfModuleDomainService _wfModuleDomainService;
  38. private readonly IRepository<WorkflowModule> _wfModuleRepository;
  39. private readonly ISessionContext _sessionContext;
  40. private readonly IMapper _mapper;
  41. public WorkflowController(
  42. IDefinitionDomainService definitionDomainService,
  43. IRepository<Definition> definitionRepository,
  44. IWorkflowApplication workflowApplication,
  45. IWorkflowDomainService workflowDomainService,
  46. IWorkflowRepository workflowRepository,
  47. IRepository<User> userRepository,
  48. ISystemOrganizeRepository organizeRepository,
  49. IRepository<Role> roleRepository,
  50. ISystemDomainService systemDomainService,
  51. IWfModuleDomainService wfModuleDomainService,
  52. IRepository<WorkflowModule> wfModuleRepository,
  53. ISessionContext sessionContext,
  54. IMapper mapper)
  55. {
  56. _definitionDomainService = definitionDomainService;
  57. _definitionRepository = definitionRepository;
  58. _workflowApplication = workflowApplication;
  59. _workflowDomainService = workflowDomainService;
  60. _workflowRepository = workflowRepository;
  61. _userRepository = userRepository;
  62. _organizeRepository = organizeRepository;
  63. _roleRepository = roleRepository;
  64. _systemDomainService = systemDomainService;
  65. _wfModuleDomainService = wfModuleDomainService;
  66. _wfModuleRepository = wfModuleRepository;
  67. _sessionContext = sessionContext;
  68. _mapper = mapper;
  69. }
  70. /// <summary>
  71. /// 分页查询最新版本号的模板
  72. /// </summary>
  73. /// <param name="dto"></param>
  74. /// <returns></returns>
  75. [HttpGet("definition/latest")]
  76. public async Task<PagedDto<DefinitionDto>> QueryDefinitionLatest([FromQuery] QueryDefinitionDto dto)
  77. {
  78. var query2 = await _definitionRepository.Queryable()
  79. .WhereIF(dto.Status.HasValue, d => d.Status == dto.Status)
  80. .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Code.Contains(dto.Keyword) || d.Name.Contains(dto.Keyword))
  81. .Select(d => new { i = SqlFunc.RowNumber($"{d.Version} desc", d.Code), d })
  82. .MergeTable()
  83. .Where(d => d.i == 1)
  84. .ToListAsync();
  85. var items = query2.Select(d => d.d).ToList();
  86. return new PagedDto<DefinitionDto>(query2.Count, _mapper.Map<IReadOnlyList<DefinitionDto>>(items));
  87. }
  88. /// <summary>
  89. /// 分页查询流程模板
  90. /// </summary>
  91. /// <param name="dto"></param>
  92. /// <returns></returns>
  93. [Permission(EPermission.FlowDefinitionQuery)]
  94. [HttpGet("definition")]
  95. public async Task<PagedDto<DefinitionDto>> QueryDefinitions([FromQuery] QueryDefinitionDto dto)
  96. {
  97. #region old version:只查询草稿、禁用以及已启用模板的最新版本
  98. ////todo 数据量大需重构
  99. //var query1 = await _definitionRepository.Queryable()
  100. // .Where(d => d.Status == EDefinitionStatus.Temporary)
  101. // .ToListAsync();
  102. //var query2 = await _definitionRepository.Queryable()
  103. // .Where(d => d.Status != EDefinitionStatus.Temporary)
  104. // .Select(d => new { i = SqlFunc.RowNumber($"{d.Version} desc", d.Code), d })
  105. // .MergeTable()
  106. // .Where(d => d.i == 1)
  107. // .ToListAsync();
  108. //var query = query1.Union(query2.Select(d => d.d));
  109. //var total = query.Count();
  110. //var items = query
  111. // .OrderBy(d => d.Status)
  112. // .ThenByDescending(d => d.CreationTime)
  113. // .Skip(dto.Skip())
  114. // .Take(dto.PageSize)
  115. // .ToList();
  116. #endregion
  117. var (total, items) = await _definitionRepository.Queryable()
  118. .WhereIF(dto.Status.HasValue, d => d.Status == dto.Status)
  119. .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Code.Contains(dto.Keyword!) || d.Name.Contains(dto.Keyword!))
  120. .OrderBy(d => d.Status)
  121. .OrderBy(d => d.Code)
  122. .OrderByDescending(d => d.Version)
  123. .ToPagedListAsync(dto, HttpContext.RequestAborted);
  124. return new PagedDto<DefinitionDto>(total, _mapper.Map<IReadOnlyList<DefinitionDto>>(items));
  125. }
  126. /// <summary>
  127. /// 查询流程模板
  128. /// </summary>
  129. /// <param name="id"></param>
  130. /// <returns></returns>
  131. [Permission(EPermission.GetFlow)]
  132. [HttpGet("definition/{id}")]
  133. public async Task<DefinitionDto> GetDefinition(string id)
  134. {
  135. var definition = await _definitionRepository.GetAsync(id, HttpContext.RequestAborted);
  136. if (definition == null) return new();
  137. return _mapper.Map<DefinitionDto>(definition);
  138. }
  139. /// <summary>
  140. /// 新增流程模板草稿
  141. /// </summary>
  142. /// <param name="dto"></param>
  143. /// <returns></returns>
  144. [Permission(EPermission.FlowDefinitionAdd)]
  145. [HttpPost("definition")]
  146. public async Task<string> AddDefinition([FromBody] AddDefinitionDto dto)
  147. {
  148. return await _definitionDomainService.AddAsync(dto, HttpContext.RequestAborted);
  149. }
  150. /// <summary>
  151. /// 更新流程模板草稿
  152. /// </summary>
  153. /// <param name="dto"></param>
  154. /// <returns></returns>
  155. [Permission(EPermission.FlowDefinitionUpdate)]
  156. [HttpPut("definition")]
  157. public async Task UpdateDefinition([FromBody] UpdateDefinitionDto dto)
  158. {
  159. var definition = await _definitionRepository.GetAsync(dto.Id, HttpContext.RequestAborted);
  160. if (definition == null)
  161. throw UserFriendlyException.SameMessage("无效模板编号");
  162. if (definition.Status == EDefinitionStatus.Temporary)
  163. {
  164. _mapper.Map(dto, definition);
  165. await _definitionRepository.UpdateAsync(definition, HttpContext.RequestAborted);
  166. }
  167. else
  168. {
  169. var newDefinition = _mapper.Map<Definition>(dto);
  170. await _definitionRepository.AddAsync(newDefinition, HttpContext.RequestAborted);
  171. }
  172. }
  173. /// <summary>
  174. /// 删除草稿
  175. /// </summary>
  176. /// <param name="id"></param>
  177. /// <returns></returns>
  178. /// <exception cref="UserFriendlyException"></exception>
  179. [Permission(EPermission.FlowDefinitionRemove)]
  180. [HttpDelete("definition/{id}")]
  181. public async Task RemoveDefinition(string id)
  182. {
  183. var definition = await _definitionRepository.GetAsync(id, HttpContext.RequestAborted);
  184. if (definition == null) return;
  185. if (definition.Status != EDefinitionStatus.Temporary)
  186. throw new UserFriendlyException("已发布模板不能删除");
  187. await _definitionRepository.RemoveAsync(id, false, HttpContext.RequestAborted);
  188. }
  189. /// <summary>
  190. /// 发布(列表操作)
  191. /// </summary>
  192. /// <returns></returns>
  193. [Permission(EPermission.FlowDefinitionPublish)]
  194. [HttpPost("definition/{id}/publish")]
  195. public async Task Publish(string id)
  196. {
  197. await _definitionDomainService.PublishAsync(id, HttpContext.RequestAborted);
  198. }
  199. /// <summary>
  200. /// 发布(保存并发布)
  201. /// </summary>
  202. /// <returns></returns>
  203. [Obsolete]
  204. [Permission(EPermission.FlowDefinitionPublish)]
  205. [HttpPost("definition/publish")]
  206. public async Task Publish([FromBody] AddDefinitionDto dto)
  207. {
  208. await _definitionDomainService.PublishAsync(dto, HttpContext.RequestAborted);
  209. }
  210. /// <summary>
  211. /// 分页查询流程
  212. /// </summary>
  213. /// <param name="dto"></param>
  214. /// <returns></returns>
  215. [Permission(EPermission.FlowQuery)]
  216. [HttpGet]
  217. public async Task<PagedDto<WorkflowDto>> QueryPaged([FromQuery] QueryWorkflowPagedDto dto)
  218. {
  219. var (total, items) = await _workflowRepository.Queryable()
  220. .WhereIF(!string.IsNullOrEmpty(dto.ModuleCode), d => d.ModuleCode == dto.ModuleCode)
  221. .WhereIF(!string.IsNullOrEmpty(dto.Keyword), d => d.Id.Contains(dto.Keyword!) || d.Title.Contains(dto.Keyword!))
  222. .OrderByDescending(d => d.CreationTime)
  223. .ToPagedListAsync(dto, HttpContext.RequestAborted);
  224. return new PagedDto<WorkflowDto>(total, _mapper.Map<IReadOnlyList<WorkflowDto>>(items));
  225. }
  226. /// <summary>
  227. /// 查询流程办理下一步可选节点
  228. /// </summary>
  229. [HttpGet("{workflowId}/nextsteps")]
  230. public async Task<DefinedStepDto> GetNextStepDefine(string workflowId)
  231. {
  232. var workflow = await _workflowDomainService.GetWorkflowAsync(workflowId, true, true,
  233. cancellationToken: HttpContext.RequestAborted);
  234. var current = _workflowDomainService.FindCurrentStep(workflow);
  235. var nextStepDefines = workflow.Definition.FindStepDefines(current.StepBox.NextSteps.Select(d => d.Code));
  236. if (current.StepBox.PathPolicy is not EPathPolicy.None && current.StepBox.NextSteps.Count > 1)
  237. _workflowDomainService.NextStepDefineFilter(current.StepBox.PathPolicy, nextStepDefines);
  238. return new DefinedStepDto
  239. {
  240. Id = workflow.DefinitionId,
  241. Steps = _mapper.Map<IReadOnlyList<StepBasicDto>>(nextStepDefines),//nextStepDefines.Select(d => new KeyValuePair<string, string>(d.Code, d.Name)).ToList(),
  242. ExpiredTime = workflow.ExpiredTime,
  243. Components = current.StepBox.Components,
  244. };
  245. }
  246. /// <summary>
  247. /// 查询流程下一节点待选配置
  248. /// </summary>
  249. [HttpGet("step-options")]
  250. public async Task<NextStepOptionDto> GetNextStepOptions([FromQuery] QueryNextStepOptionDto dto)
  251. {
  252. var definition = await _definitionRepository.GetAsync(dto.DefineId, HttpContext.RequestAborted);
  253. if (definition == null)
  254. throw new UserFriendlyException("无效DefineId");
  255. var defineStep = definition.FindStepDefine(dto.Code);
  256. if (defineStep is null)
  257. throw UserFriendlyException.SameMessage("未查询到对应节点配置");
  258. return await _workflowApplication.GetNextStepOptionsAsync(defineStep, HttpContext.RequestAborted);
  259. }
  260. /// <summary>
  261. /// 办理节点
  262. /// </summary>
  263. [Permission(EPermission.FlowNext)]
  264. [HttpPost("next")]
  265. public async Task Next([FromBody] NextWorkflowDto dto)
  266. {
  267. await _workflowApplication.NextAsync(dto, HttpContext.RequestAborted);
  268. }
  269. /// <summary>
  270. /// 退回(返回前一节点)
  271. /// </summary>
  272. [Permission(EPermission.FlowPrevious)]
  273. [HttpPost("previous")]
  274. public async Task Previous([FromBody] PreviousWorkflowDto dto)
  275. {
  276. var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId, withSteps: true,
  277. cancellationToken: HttpContext.RequestAborted);
  278. await _workflowDomainService.PreviousAsync(workflow, dto, HttpContext.RequestAborted);
  279. }
  280. /// <summary>
  281. /// 获取撤回可选节点
  282. /// </summary>
  283. /// <param name="workflowId"></param>
  284. /// <returns></returns>
  285. [Permission(EPermission.FlowRecall)]
  286. [HttpGet("{workflowId}/recall")]
  287. public async Task<DefinedStepDto> GetRecallSteps(string workflowId)
  288. {
  289. var workflow = await _workflowDomainService.GetWorkflowAsync(workflowId, true, true, cancellationToken: HttpContext.RequestAborted);
  290. var stepCodes = workflow.StepBoxes.Where(d => d.StepType != EStepType.Start && d.StepType != EStepType.End)
  291. .Select(d => d.Code).ToList();
  292. var nextStepDefines = workflow.Definition.FindStepDefines(stepCodes);
  293. return new DefinedStepDto
  294. {
  295. Id = workflow.DefinitionId,
  296. Steps = _mapper.Map<IReadOnlyList<StepBasicDto>>(nextStepDefines)//nextStepDefines.Select(d => new KeyValuePair<string, string>(d.Code, d.Name)).ToList()
  297. };
  298. }
  299. /// <summary>
  300. /// 撤回至任意节点
  301. /// </summary>
  302. /// <param name="dto"></param>
  303. /// <returns></returns>
  304. [Permission(EPermission.FlowRecall)]
  305. [HttpPost("recall")]
  306. public async Task Recall([FromBody] RecallDto dto)
  307. {
  308. await _workflowApplication.RecallAsync(dto, HttpContext.RequestAborted);
  309. }
  310. /// <summary>
  311. /// 获取跳转可选节点
  312. /// </summary>
  313. /// <param name="workflowId"></param>
  314. /// <returns></returns>
  315. [HttpGet("{workflowId}/jump")]
  316. public async Task<DefinedStepDto> GetJumpSteps(string workflowId)
  317. {
  318. var workflow = await _workflowDomainService.GetWorkflowAsync(workflowId, true, true, cancellationToken: HttpContext.RequestAborted);
  319. var steps = workflow.Definition.Steps.Where(d => d.StepType != EStepType.Start && d.StepType != EStepType.End).ToList();
  320. var nextStepDefines = workflow.Definition.FindStepDefines(steps.Select(d => d.Code));
  321. return new DefinedStepDto
  322. {
  323. Id = workflow.DefinitionId,
  324. Steps = _mapper.Map<IReadOnlyList<StepBasicDto>>(nextStepDefines)//nextStepDefines.Select(d => new KeyValuePair<string, string>(d.Code, d.Name)).ToList()
  325. };
  326. }
  327. /// <summary>
  328. /// 跳转至任意节点
  329. /// </summary>
  330. /// <param name="dto"></param>
  331. /// <returns></returns>
  332. [Permission(EPermission.FlowJump)]
  333. [HttpPost("jump")]
  334. public async Task Jump([FromBody] RecallDto dto)
  335. {
  336. await _workflowApplication.JumpAsync(dto, HttpContext.RequestAborted);
  337. }
  338. /// <summary>
  339. /// 获取重办可选节点
  340. /// </summary>
  341. /// <param name="workflowId"></param>
  342. /// <returns></returns>
  343. [HttpGet("{workflowId}/redo")]
  344. public async Task<DefinedStepDto> GetRedoSteps(string workflowId)
  345. {
  346. var workflow = await _workflowDomainService.GetWorkflowAsync(workflowId, true, true, cancellationToken: HttpContext.RequestAborted);
  347. var steps = workflow.Definition.Steps.Where(d => d.StepType is EStepType.Normal && d.IsOrg()).ToList();
  348. var nextStepDefines = workflow.Definition.FindStepDefines(steps.Select(d => d.Code));
  349. return new DefinedStepDto
  350. {
  351. Id = workflow.DefinitionId,
  352. Steps = _mapper.Map<IReadOnlyList<StepBasicDto>>(nextStepDefines)//nextStepDefines.Select(d => new KeyValuePair<string, string>(d.Code, d.Name)).ToList()
  353. };
  354. }
  355. /// <summary>
  356. /// 终止流程
  357. /// </summary>
  358. [Permission(EPermission.FlowTerminate)]
  359. [HttpPost("terminate")]
  360. public async Task Terminate([FromBody] TerminateDto dto)
  361. {
  362. await _workflowDomainService.TerminateAsync(dto, HttpContext.RequestAborted);
  363. }
  364. /// <summary>
  365. /// 撤销流程
  366. /// </summary>
  367. [HttpPost("cancel")]
  368. public async Task Cancel([FromBody] CancelDto dto)
  369. {
  370. //todo
  371. await _workflowDomainService.CancelAsync(dto, HttpContext.RequestAborted);
  372. }
  373. /// <summary>
  374. /// 补充
  375. /// </summary>
  376. /// <param name="dto"></param>
  377. /// <returns></returns>
  378. [Permission(EPermission.FlowSupplement)]
  379. [HttpPost("supplement")]
  380. public async Task Supplement([FromBody] SupplementDto dto)
  381. {
  382. var workflow = await _workflowDomainService.GetWorkflowAsync(dto.WorkflowId);
  383. await _workflowDomainService.SupplementAsync(workflow, dto, HttpContext.RequestAborted);
  384. }
  385. /// <summary>
  386. /// 查询办理类型参数
  387. /// </summary>
  388. [HttpGet("handlerclassify/{handlerType}")]
  389. public async Task<List<KeyValuePair<string, string>>> GetHandlerClassifies(EHandlerType handlerType)
  390. {
  391. switch (handlerType)
  392. {
  393. case EHandlerType.Role:
  394. var roles = await _roleRepository.QueryAsync();
  395. return roles.Select(d => new KeyValuePair<string, string>(d.Name, d.DisplayName)).ToList();
  396. case EHandlerType.OrgLevel:
  397. var orgs1 = await _systemDomainService.QueryOrgLevelStringOptionsAsync(HttpContext.RequestAborted);
  398. return orgs1.ToList();
  399. case EHandlerType.OrgType:
  400. return EnumExts.GetDescriptions<EOrgType>().Select(d => new KeyValuePair<string, string>(d.Key.ToString(), d.Value)).ToList();
  401. case EHandlerType.AssignOrg:
  402. var orgs = await _organizeRepository.GetOrgJson();
  403. return orgs.Select(d => new KeyValuePair<string, string>(d.OrgCode, d.OrgName)).ToList();
  404. case EHandlerType.AssignUser:
  405. default:
  406. throw new ArgumentOutOfRangeException(nameof(handlerType), handlerType, null);
  407. }
  408. }
  409. /// <summary>
  410. /// 查询流程流转记录
  411. /// </summary>
  412. /// <param name="workflowId"></param>
  413. /// <returns></returns>
  414. [HttpGet("{workflowId}/traces")]
  415. public async Task<WorkflowDto> GetWorkflowTraces(string workflowId)
  416. {
  417. var workflow = await _workflowDomainService.GetWorkflowAsync(workflowId, withTraces: true,
  418. cancellationToken: HttpContext.RequestAborted);
  419. workflow.Traces = workflow.Traces.Where(d => !string.IsNullOrEmpty(d.AcceptUserId)).ToList();
  420. return _mapper.Map<WorkflowDto>(workflow);
  421. }
  422. [HttpGet("base-data")]
  423. public async Task<dynamic> BaseData()
  424. {
  425. return new
  426. {
  427. ModuleOptions = WorkflowModuleConsts.AllModules.Select(d => new KeyValuePair<string, string>(d.Code, d.Name)),
  428. HandlerTypeOptions = EnumExts.GetDescriptions<EHandlerType>(),
  429. CountersignMode = EnumExts.GetDescriptions<ECountersignMode>().Where(d => d.Key != 1),
  430. BusinessPropertyOptions = EnumExts.GetDescriptions<EBusinessProperty>(),
  431. StepPropertiesOptions = await _systemDomainService.GetSysDicDataByCodeAsync(SysDicTypeConsts.WorkflowStepComponent, HttpContext.RequestAborted),
  432. PathPolicyOptions = EnumExts.GetDescriptions<EPathPolicy>(),
  433. };
  434. }
  435. /// <summary>
  436. /// 持久化新增工作流业务
  437. /// </summary>
  438. /// <returns></returns>
  439. [AllowAnonymous]
  440. [HttpGet("wfmodule/persistence")]
  441. public async Task PersistenceWfModule()
  442. {
  443. await _wfModuleDomainService.PersistenceModulesAsync(HttpContext.RequestAborted);
  444. }
  445. /// <summary>
  446. /// 查询所有工作流模块
  447. /// </summary>
  448. /// <returns></returns>
  449. [HttpGet("wfmodules")]
  450. public async Task<IReadOnlyList<WorkflowModule>> QueryWfModules()
  451. {
  452. return await _wfModuleRepository.Queryable()
  453. .Includes(d => d.Definition)
  454. .ToListAsync();
  455. }
  456. /// <summary>
  457. /// 为工作流业务匹配或取消流程模板
  458. /// </summary>
  459. /// <param name="dto"></param>
  460. /// <returns></returns>
  461. [HttpPut("wfmodule/match")]
  462. public async Task MatchDefinition([FromBody] MatchDefinitionDto dto)
  463. {
  464. if (string.IsNullOrEmpty(dto.DefinitionId))
  465. {
  466. //取消当前已配置模板
  467. await _wfModuleDomainService.MatchDefinitionAsync(dto, HttpContext.RequestAborted);
  468. }
  469. else
  470. {
  471. var definition = await _definitionRepository.GetAsync(dto.DefinitionId);
  472. if (definition == null)
  473. throw UserFriendlyException.SameMessage("无效模板编号");
  474. if (definition.Status != EDefinitionStatus.Enable)
  475. throw UserFriendlyException.SameMessage("该模板未发布");
  476. await _wfModuleDomainService.MatchDefinitionAsync(dto, HttpContext.RequestAborted);
  477. }
  478. }
  479. }