StepBasicEntity.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. using Hotline.Share.Dtos;
  2. using Hotline.Share.Dtos.File;
  3. using Hotline.Share.Dtos.FlowEngine;
  4. using Hotline.Share.Dtos.FlowEngine.Definition;
  5. using Hotline.Share.Dtos.FlowEngine.Workflow;
  6. using Hotline.Share.Enums.FlowEngine;
  7. using Hotline.Share.Enums.Order;
  8. using SqlSugar;
  9. using XF.Domain.Entities;
  10. using XF.Domain.Repository;
  11. namespace Hotline.FlowEngine.Workflows;
  12. public abstract class StepBasicEntity : CreationEntity
  13. {
  14. public string WorkflowId { get; set; }
  15. /// <summary>
  16. /// 业务唯一标识
  17. /// </summary>
  18. public string? ExternalId { get; set; }
  19. #region 业务模块(冗余)
  20. public string? ModuleId { get; set; }
  21. public string? ModuleName { get; set; }
  22. public string? ModuleCode { get; set; }
  23. #endregion
  24. /// <summary>
  25. /// 是否转办
  26. /// </summary>
  27. public bool? IsForwarded { get; set; }
  28. /// <summary>
  29. /// 流程指派类型
  30. /// </summary>
  31. public EFlowAssignType? FlowAssignType { get; set; }
  32. /// <summary>
  33. /// 该节点指派办理对象(依据不同指派方式可能为:orgId或userId),该字段subStep才会存在,stepBox不存在
  34. /// 采用list类型,兼容或签
  35. /// </summary>
  36. [SugarColumn(ColumnDataType = "json", IsJson = true)]
  37. public List<Kv> Handlers { get; set; } = new();
  38. /// <summary>
  39. /// 上一节点办理时,nextStepCode下拉框中选中的值
  40. /// config模式:当前节点的difinition.code, dynamic模式:x级部门办理的x:int
  41. /// </summary>
  42. public string? PrevChosenStepCode { get; set; }
  43. /// <summary>
  44. /// 是否实际办理过该工单
  45. /// </summary>
  46. public bool IsActualHandled { get; set; }
  47. /// <summary>
  48. /// 节点超期状态
  49. /// </summary>
  50. public EExpiredStatus? ExpiredStatus { get; set; }
  51. /// <summary>
  52. /// 配置下一步节点 & 谁被选中
  53. /// </summary>
  54. [SugarColumn(ColumnDataType = "json", IsJson = true)]
  55. public List<StepSimple>? NextSteps { get; set; } = new();
  56. /// <summary>
  57. /// 前一级节点Id,会签汇总节点无此字段(因可能有多个上级来源)
  58. /// </summary>
  59. public string? PrevStepId { get; set; }
  60. public string? PrevStepCode { get; set; }
  61. /// <summary>
  62. /// 主办
  63. /// </summary>
  64. [SugarColumn(DefaultValue = "f")]
  65. public bool IsMain { get; set; }
  66. /// <summary>
  67. /// 原生节点(区别动态生成)
  68. /// </summary>
  69. [SugarColumn(DefaultValue = "f")]
  70. public bool IsOrigin { get; set; }
  71. /// <summary>
  72. /// 节点办理状态
  73. /// </summary>
  74. public EWorkflowStepStatus Status { get; set; }
  75. /// <summary>
  76. /// 备注
  77. /// </summary>
  78. [SugarColumn(ColumnDescription = "备注", ColumnDataType = "varchar(5000)")]
  79. public string? Remark { get; set; }
  80. #region 会签
  81. /// <summary>
  82. /// 会签id(或外层会签的id)
  83. /// </summary>
  84. public string? CountersignId { get; set; }
  85. /// <summary>
  86. /// 节点处于会签流程中的位置(区别直接办理会签和会签内非会签节点)
  87. /// outer属于特殊会签
  88. /// 最顶层的发起会签节点为none
  89. /// </summary>
  90. [SugarColumn(DefaultValue = "0")]
  91. public ECountersignPosition CountersignPosition { get; set; }
  92. /// <summary>
  93. /// 会签直属办理节点(弃用)
  94. /// </summary>
  95. [SugarColumn(ColumnDataType = "json", IsJson = true)]
  96. public List<CountersignStep>? CountersignSteps { get; set; } = new();
  97. #region 发起会签节点特有
  98. /// <summary>
  99. /// 发起会签生成会签Id(不发起会签节点无此字段)
  100. /// </summary>
  101. public string? StartCountersignId { get; set; }
  102. /// <summary>
  103. /// 发起的会签是否汇总
  104. /// </summary>
  105. [SugarColumn(DefaultValue = "f")]
  106. public bool IsStartedCountersignEnd { get; set; }
  107. #endregion
  108. #region 会签汇总节点特有
  109. /// <summary>
  110. /// 是否为会签汇总节点
  111. /// </summary>
  112. [SugarColumn(DefaultValue = "f")]
  113. public bool IsCountersignEndStep { get; set; }
  114. /// <summary>
  115. /// 开启会签节点id(会签汇总节点对应会签发起节点id)
  116. /// </summary>
  117. public string? CountersignStartStepId { get; set; }
  118. #endregion
  119. #endregion
  120. #region 接办
  121. /// <summary>
  122. /// 接办人
  123. /// </summary>
  124. public string? AcceptorId { get; set; }
  125. public string? AcceptorName { get; set; }
  126. /// <summary>
  127. /// 接办人部门code
  128. /// </summary>
  129. public string? AcceptorOrgId { get; set; }
  130. public string? AcceptorOrgName { get; set; }
  131. /// <summary>
  132. /// 接办人部门行政区划代码
  133. /// </summary>
  134. public string? AcceptorOrgAreaCode { get; set; }
  135. /// <summary>
  136. /// 接办人部门行政区划名称
  137. /// </summary>
  138. public string? AcceptorOrgAreaName { get; set; }
  139. /// <summary>
  140. /// 接办时间
  141. /// </summary>
  142. public DateTime? AcceptTime { get; set; }
  143. #endregion
  144. #region 办理
  145. /// <summary>
  146. /// 办理人
  147. /// </summary>
  148. public string? HandlerId { get; set; }
  149. public string? HandlerName { get; set; }
  150. /// <summary>
  151. /// 办理人部门code
  152. /// </summary>
  153. public string? HandlerOrgId { get; set; }
  154. public bool? HandlerOrgIsCenter { get; set; }
  155. /// <summary>
  156. /// 办理人部门名称
  157. /// </summary>
  158. public string? HandlerOrgName { get; set; }
  159. /// <summary>
  160. /// 办理人部门行政区划代码
  161. /// </summary>
  162. public string? HandlerOrgAreaCode { get; set; }
  163. /// <summary>
  164. /// 办理人部门行政区划名称
  165. /// </summary>
  166. public string? HandlerOrgAreaName { get; set; }
  167. /// <summary>
  168. /// 办理完成时间
  169. /// </summary>
  170. public DateTime? HandleTime { get; set; }
  171. /// <summary>
  172. /// 角色id(如果指派给角色)
  173. /// </summary>
  174. public string? RoleId { get; set; }
  175. public string? RoleName { get; set; }
  176. #endregion
  177. #region Definition
  178. public string Name { get; set; }
  179. /// <summary>
  180. /// 模板内唯一
  181. /// </summary>
  182. public string Code { get; set; }
  183. public EStepType StepType { get; init; }
  184. /// <summary>
  185. /// 节点业务类型
  186. /// </summary>
  187. public EBusinessType BusinessType { get; set; }
  188. /// <summary>
  189. /// 办理人类型
  190. /// </summary>
  191. public EHandlerType HandlerType { get; set; }
  192. /// <summary>
  193. /// 是否有否决按钮
  194. /// </summary>
  195. public bool CanReject { get; set; }
  196. /// <summary>
  197. /// 执行模式(自动与否)
  198. /// 只有普通节点支持自动,会签、动态节点均不支持自动
  199. /// </summary>
  200. public EExecuteMode ExecuteMode { get; set; }
  201. #region 会签相关配置
  202. /// <summary>
  203. /// 是否支持发起会签(即使支持发起,当下一节点为汇总或结束节点时亦不可发起)
  204. /// </summary>
  205. public bool CanStartCountersign { get; set; }
  206. /// <summary>
  207. /// 会签策略
  208. /// </summary>
  209. public ECountersignPolicy? CountersignPolicy { get; set; }
  210. #endregion
  211. #region 依据配置生成节点的方式
  212. /// <summary>
  213. /// 实例化模式
  214. /// </summary>
  215. public EInstanceMode InstanceMode { get; set; }
  216. /// <summary>
  217. /// 动态实例化策略(多次模式才有)
  218. /// </summary>
  219. public EDynamicPolicy? InstancePolicy { get; set; }
  220. /// <summary>
  221. /// 到此标记终止动态实例化(多次模式才有)
  222. /// <remarks>
  223. /// 按直属部门重复既保存orgLevel:int
  224. /// </remarks>
  225. /// </summary>
  226. public string? TerminalDynamicMark { get; set; }
  227. #endregion
  228. /// <summary>
  229. /// 标签
  230. /// </summary>
  231. public string? Tag { get; set; }
  232. #endregion
  233. #region 办理参数
  234. #region 办理时赋值
  235. /// <summary>
  236. /// (下一节点办理人)根据审批者类型不同,此字段为不同内容
  237. /// <example>
  238. /// 部门等级/分类为:orgCodes, 角色为:userIds
  239. /// </example>
  240. /// </summary>
  241. [SugarColumn(ColumnDataType = "json", IsJson = true)]
  242. public List<FlowStepHandler> NextHandlers { get; set; } = new();
  243. /// <summary>
  244. /// 下一节点主办,(NextHandlers其中一个, 如果不是会签则只有一个)
  245. /// </summary>
  246. public string? NextMainHandler { get; set; }
  247. /// <summary>
  248. /// 下一节点code(stepBox无值)
  249. /// </summary>
  250. public string? NextStepCode { get; set; }
  251. /// <summary>
  252. /// 是否短信通知
  253. /// </summary>
  254. public bool IsSms { get; set; }
  255. /// <summary>
  256. /// 办理意见
  257. /// </summary>
  258. [SugarColumn(Length = 8000)]
  259. public string? Opinion { get; set; }
  260. /// <summary>
  261. /// 附件
  262. /// </summary>
  263. [SugarColumn(ColumnDataType = "json", IsJson = true, IsNullable = true)]
  264. public List<FileJson>? FileJson { get; set; }
  265. /// <summary>
  266. /// 发起会签
  267. /// </summary>
  268. public bool IsStartCountersign { get; set; }
  269. #endregion
  270. #region 创建时赋值
  271. /// <summary>
  272. /// 节点期满时间
  273. /// </summary>
  274. public DateTime? StepExpiredTime { get; set; }
  275. /// nextStep 信息
  276. /// <summary>
  277. /// 是否回到会签发起节点汇总
  278. /// </summary>
  279. public bool BackToCountersignEnd { get; set; }
  280. public EFlowDirection? FlowDirection { get; set; }
  281. #endregion
  282. #endregion
  283. [Navigate(NavigateType.ManyToOne, nameof(WorkflowId))]
  284. public Workflow Workflow { get; set; }
  285. #region method
  286. /// <summary>
  287. /// 开启会签
  288. /// </summary>
  289. public void StartCountersign(string startCountersignId)
  290. {
  291. IsStartCountersign = true;
  292. StartCountersignId = startCountersignId;
  293. IsStartedCountersignEnd = false;
  294. }
  295. public bool HasAccepted() => !string.IsNullOrEmpty(AcceptorId);
  296. public void Accept(
  297. string userId, string? userName,
  298. string orgId, string? orgName,
  299. string? orgAreaCode, string? orgAreaName)
  300. {
  301. if (string.IsNullOrEmpty(AcceptorId))
  302. {
  303. AcceptorId = userId;
  304. AcceptorName = userName;
  305. }
  306. if (string.IsNullOrEmpty(AcceptorOrgId))
  307. AcceptorOrgId = orgId;
  308. if (string.IsNullOrEmpty(AcceptorOrgName))
  309. AcceptorOrgName = orgName;
  310. if (string.IsNullOrEmpty(AcceptorOrgAreaCode))
  311. AcceptorOrgAreaCode = orgAreaCode;
  312. if (string.IsNullOrEmpty(AcceptorOrgAreaName))
  313. AcceptorOrgAreaName = orgAreaName;
  314. AcceptTime = DateTime.Now;
  315. Status = EWorkflowStepStatus.WaitForHandle;
  316. }
  317. public void Handle(
  318. string userId, string? userName,
  319. string orgId, string? orgName,
  320. string? orgAreaCode, string? orgAreaName,
  321. bool orgIsCenter, string opinion,
  322. string? nextStepCode = null)
  323. {
  324. if (!HasAccepted())
  325. Accept(userId, userName, orgId, orgName, orgAreaCode, orgAreaName);
  326. Status = EWorkflowStepStatus.Handled;
  327. if (string.IsNullOrEmpty(HandlerId))
  328. {
  329. HandlerId = userId;
  330. HandlerName = userName;
  331. }
  332. if (string.IsNullOrEmpty(HandlerOrgId))
  333. HandlerOrgId = orgId;
  334. if (string.IsNullOrEmpty(HandlerOrgName))
  335. HandlerOrgName = orgName;
  336. if (string.IsNullOrEmpty(HandlerOrgAreaCode))
  337. HandlerOrgAreaCode = orgAreaCode;
  338. if (string.IsNullOrEmpty(HandlerOrgAreaName))
  339. HandlerOrgAreaName = orgAreaName;
  340. HandlerOrgIsCenter ??= orgIsCenter;
  341. HandleTime = DateTime.Now;
  342. if (!string.IsNullOrEmpty(opinion))
  343. Opinion = opinion;
  344. ExpiredStatus = HandleTime > StepExpiredTime
  345. ? EExpiredStatus.Expired
  346. : EExpiredStatus.Normal;
  347. if (!IsInCountersign()
  348. && InstanceMode is EInstanceMode.Config
  349. && StepType is not EStepType.End)
  350. {
  351. var step = NextSteps.FirstOrDefault(d => d.Code == nextStepCode);
  352. if (step != null)
  353. step.Selected = true;
  354. }
  355. }
  356. /// <summary>
  357. /// 指派
  358. /// </summary>
  359. public void Assign(
  360. string? handlerId, string? handlerName,
  361. string? handlerOrgId, string? handlerOrgName,
  362. string? roleId = null, string? roleName = null)
  363. {
  364. HandlerId = handlerId;
  365. HandlerName = handlerName;
  366. HandlerOrgId = handlerOrgId;
  367. HandlerOrgName = handlerOrgName;
  368. RoleId = roleId;
  369. RoleName = roleName;
  370. }
  371. /// <summary>
  372. /// 是否处于会签流程中(不包含顶层发起会签节点)
  373. /// </summary>
  374. public bool IsInCountersign() => CountersignPosition != ECountersignPosition.None;
  375. public Kv GetHandler()
  376. {
  377. return FlowAssignType switch
  378. {
  379. EFlowAssignType.Org => new Kv(HandlerOrgId, HandlerOrgName),
  380. EFlowAssignType.User => new Kv(HandlerId, HandlerName),
  381. EFlowAssignType.Role => new Kv(RoleId, RoleName),
  382. EFlowAssignType.OrgAndRole => new Kv(RoleId, $"{HandlerOrgName} - {RoleName}"),
  383. _ => throw new ArgumentOutOfRangeException()
  384. };
  385. }
  386. public FlowStepHandler GetWorkflowStepHandler()
  387. {
  388. switch (FlowAssignType)
  389. {
  390. case EFlowAssignType.Org:
  391. return new FlowStepHandler
  392. {
  393. Key = HandlerOrgId,
  394. Value = HandlerOrgName,
  395. UserId = HandlerId,
  396. Username = HandlerName,
  397. OrgId = HandlerOrgId,
  398. OrgName = HandlerOrgName,
  399. RoleId = RoleId,
  400. RoleName = RoleName
  401. };
  402. case EFlowAssignType.User:
  403. return new FlowStepHandler
  404. {
  405. Key = HandlerId,
  406. Value = HandlerName,
  407. UserId = HandlerId,
  408. Username = HandlerName,
  409. OrgId = HandlerOrgId,
  410. OrgName = HandlerOrgName,
  411. RoleId = RoleId,
  412. RoleName = RoleName
  413. };
  414. case EFlowAssignType.Role:
  415. return new FlowStepHandler
  416. {
  417. Key = RoleId,
  418. Value = RoleName,
  419. UserId = HandlerId,
  420. Username = HandlerName,
  421. OrgId = HandlerOrgId,
  422. OrgName = HandlerOrgName,
  423. RoleId = RoleId,
  424. RoleName = RoleName
  425. };
  426. default:
  427. throw new ArgumentOutOfRangeException();
  428. }
  429. }
  430. #endregion
  431. }
  432. /*
  433. feature:
  434. 1. step增加字段记录roleId, roleName
  435. 2. 指派时为对应办理对象赋值,办理时为所有办理对象字段赋值
  436. 3. 配置办理对象为角色时,如果未指定办理人则指派给角色办理
  437. 4. thk从默认派单池中分配给对应办理人时指定办理对象
  438. refactor:
  439. 1. step增加字段记录发起会签或是或签
  440. 2. status增加或签无需办理状态
  441. 3. 办理节点时判断是否立即结束会签或者等全部节点办完再结束
  442. */