S8TaskFlowService.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. using Admin.NET.Plugin.AiDOP.Entity.S0.Warehouse;
  2. using Admin.NET.Plugin.AiDOP.Entity.S8;
  3. using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
  4. using Admin.NET.Plugin.ApprovalFlow.Service;
  5. namespace Admin.NET.Plugin.AiDOP.Service.S8;
  6. public class S8TaskFlowService : ITransient
  7. {
  8. private readonly SqlSugarRepository<AdoS8Exception> _rep;
  9. private readonly SqlSugarRepository<AdoS8ExceptionTimeline> _timelineRep;
  10. private readonly SqlSugarRepository<AdoS0EmployeeMaster> _employeeRep;
  11. private readonly FlowEngineService _flowEngine;
  12. private readonly UserManager _userManager;
  13. public S8TaskFlowService(
  14. SqlSugarRepository<AdoS8Exception> rep,
  15. SqlSugarRepository<AdoS8ExceptionTimeline> timelineRep,
  16. SqlSugarRepository<AdoS0EmployeeMaster> employeeRep,
  17. FlowEngineService flowEngine,
  18. UserManager userManager)
  19. {
  20. _rep = rep;
  21. _timelineRep = timelineRep;
  22. _employeeRep = employeeRep;
  23. _flowEngine = flowEngine;
  24. _userManager = userManager;
  25. }
  26. public async Task<AdoS8Exception> ClaimAsync(long id, long tenantId, long factoryId, long assigneeId, string? remark)
  27. {
  28. if (assigneeId <= 0) throw new S8BizException("认领需指定处理人 AssigneeId");
  29. var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
  30. if (!S8StatusRules.IsAllowedTransition(e.Status, "ASSIGNED"))
  31. throw new S8BizException($"状态 {e.Status} 不可认领");
  32. var fromStatus = e.Status;
  33. e.Status = "ASSIGNED";
  34. e.AssigneeId = assigneeId;
  35. e.AssignedAt = DateTime.Now;
  36. e.UpdatedAt = DateTime.Now;
  37. await _rep.AsTenant().UseTranAsync(async () =>
  38. {
  39. await _rep.UpdateAsync(e);
  40. await InsertTimelineAsync(e.Id, "CLAIM", "认领", fromStatus, "ASSIGNED", assigneeId, null, remark);
  41. }, ex => throw ex);
  42. return e;
  43. }
  44. public async Task<AdoS8Exception> TransferAsync(long id, long tenantId, long factoryId, long newAssigneeId, string? remark)
  45. {
  46. var allowedStatuses = new HashSet<string> { "ASSIGNED", "IN_PROGRESS" };
  47. if (newAssigneeId <= 0) throw new S8BizException("转派目标无效");
  48. var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
  49. if (e.ActiveFlowInstanceId.HasValue)
  50. throw new S8BizException("审批进行中不可转派");
  51. if (e.Status == "ESCALATED")
  52. throw new S8BizException("升级审批中不可转派");
  53. if (S8StatusRules.IsTerminal(e.Status))
  54. throw new S8BizException("已关闭不可转派");
  55. if (!allowedStatuses.Contains(e.Status))
  56. throw new S8BizException($"状态 {e.Status} 不可转派");
  57. e.AssigneeId = newAssigneeId;
  58. e.UpdatedAt = DateTime.Now;
  59. await _rep.AsTenant().UseTranAsync(async () =>
  60. {
  61. await _rep.UpdateAsync(e);
  62. await InsertTimelineAsync(e.Id, "TRANSFER", "转派", e.Status, e.Status, newAssigneeId, null, remark);
  63. }, ex => throw ex);
  64. return e;
  65. }
  66. public async Task<AdoS8Exception> StartProgressAsync(long id, long tenantId, long factoryId, string? remark)
  67. {
  68. var currentUserId = GetCurrentUserId();
  69. var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
  70. if (!S8StatusRules.IsAllowedTransition(e.Status, "IN_PROGRESS"))
  71. throw new S8BizException($"状态 {e.Status} 不可开始处理");
  72. var fromStatus = e.Status;
  73. e.Status = "IN_PROGRESS";
  74. e.UpdatedAt = DateTime.Now;
  75. await _rep.AsTenant().UseTranAsync(async () =>
  76. {
  77. await _rep.UpdateAsync(e);
  78. await InsertTimelineAsync(e.Id, "START_PROGRESS", "开始处理", fromStatus, "IN_PROGRESS", currentUserId, null, remark);
  79. }, ex => throw ex);
  80. return e;
  81. }
  82. public async Task<AdoS8Exception> UpgradeAsync(long id, long tenantId, long factoryId, string? remark)
  83. {
  84. var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
  85. if (e.ActiveFlowInstanceId.HasValue)
  86. throw new S8BizException("该异常已有进行中的审批流程,请等待审批完成");
  87. if (!S8StatusRules.IsAllowedTransition(e.Status, "ESCALATED"))
  88. throw new S8BizException($"状态 {e.Status} 不可升级");
  89. await _flowEngine.StartFlow(new StartFlowInput
  90. {
  91. BizType = "EXCEPTION_ESCALATION",
  92. BizId = e.Id,
  93. Title = $"异常升级审批 - {e.ExceptionCode}",
  94. Comment = remark,
  95. BizData = new Dictionary<string, object>
  96. {
  97. ["severity"] = e.Severity,
  98. ["sceneCode"] = e.SceneCode,
  99. ["priorityLevel"] = e.PriorityLevel,
  100. }
  101. });
  102. // 状态和时间线由 ExceptionEscalationBizHandler.OnFlowStarted 回调更新
  103. return await LoadAsync(id, tenantId, factoryId) ?? e;
  104. }
  105. public async Task<AdoS8Exception> RejectAsync(long id, long tenantId, long factoryId, string? remark)
  106. {
  107. var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
  108. if (!S8StatusRules.IsAllowedTransition(e.Status, "REJECTED"))
  109. throw new S8BizException($"状态 {e.Status} 不可驳回");
  110. var from = e.Status;
  111. e.Status = "REJECTED";
  112. e.UpdatedAt = DateTime.Now;
  113. await _rep.AsTenant().UseTranAsync(async () =>
  114. {
  115. await _rep.UpdateAsync(e);
  116. await InsertTimelineAsync(e.Id, "REJECT", "驳回", from, "REJECTED", null, null, remark);
  117. }, ex => throw ex);
  118. return e;
  119. }
  120. public async Task<AdoS8Exception> CloseAsync(long id, long tenantId, long factoryId, string? remark)
  121. {
  122. var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
  123. if (e.ActiveFlowInstanceId.HasValue)
  124. throw new S8BizException("该异常已有进行中的审批流程,请等待审批完成");
  125. if (!S8StatusRules.IsAllowedTransition(e.Status, "CLOSED"))
  126. throw new S8BizException($"状态 {e.Status} 不可关闭");
  127. await _flowEngine.StartFlow(new StartFlowInput
  128. {
  129. BizType = "EXCEPTION_CLOSURE",
  130. BizId = e.Id,
  131. Title = $"异常关闭确认 - {e.ExceptionCode}",
  132. Comment = remark,
  133. BizData = new Dictionary<string, object>
  134. {
  135. ["sceneCode"] = e.SceneCode,
  136. }
  137. });
  138. // 状态和时间线由 ExceptionClosureBizHandler.OnFlowStarted 回调更新
  139. return await LoadAsync(id, tenantId, factoryId) ?? e;
  140. }
  141. public async Task<AdoS8Exception> SubmitVerificationAsync(
  142. long id, long tenantId, long factoryId,
  143. long verifierId, string? remark)
  144. {
  145. var currentUserId = GetCurrentUserId();
  146. var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
  147. await EnsureCurrentUserIsOperatorAsync(e.AssigneeId, currentUserId,
  148. "只有当前处理人才能提交复检(或当前账号未绑定员工主数据)");
  149. if (verifierId <= 0)
  150. throw new S8BizException("请选择检验人");
  151. if (!S8StatusRules.IsAllowedTransition(e.Status, "PENDING_VERIFICATION"))
  152. throw new S8BizException($"状态 {e.Status} 不可提交复检");
  153. var from = e.Status;
  154. e.Status = "PENDING_VERIFICATION";
  155. e.VerifierId = verifierId;
  156. e.VerificationAssignedAt = DateTime.Now;
  157. e.UpdatedAt = DateTime.Now;
  158. await _rep.AsTenant().UseTranAsync(async () =>
  159. {
  160. await _rep.UpdateAsync(e);
  161. await InsertTimelineAsync(e.Id, "VERIFY_SUBMITTED", "提交复检", from, "PENDING_VERIFICATION",
  162. currentUserId, null, remark);
  163. }, ex => throw ex);
  164. return e;
  165. }
  166. public async Task<AdoS8Exception> ApproveVerificationAsync(
  167. long id, long tenantId, long factoryId,
  168. string? remark)
  169. {
  170. var currentUserId = GetCurrentUserId();
  171. var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
  172. await EnsureCurrentUserIsOperatorAsync(e.VerifierId, currentUserId,
  173. "只有指定检验人才能检验通过(或当前账号未绑定员工主数据)");
  174. if (!S8StatusRules.IsAllowedTransition(e.Status, "RESOLVED"))
  175. throw new S8BizException($"状态 {e.Status} 不可检验通过");
  176. var from = e.Status;
  177. e.Status = "RESOLVED";
  178. e.VerifiedAt = DateTime.Now;
  179. e.VerificationResult = "APPROVED";
  180. e.VerificationRemark = remark;
  181. e.UpdatedAt = DateTime.Now;
  182. await _rep.AsTenant().UseTranAsync(async () =>
  183. {
  184. await _rep.UpdateAsync(e);
  185. await InsertTimelineAsync(e.Id, "VERIFY_APPROVED", "检验通过", from, "RESOLVED",
  186. currentUserId, null, remark);
  187. }, ex => throw ex);
  188. return e;
  189. }
  190. public async Task<AdoS8Exception> RejectVerificationAsync(
  191. long id, long tenantId, long factoryId,
  192. string remark)
  193. {
  194. var currentUserId = GetCurrentUserId();
  195. var e = await LoadAsync(id, tenantId, factoryId) ?? throw new S8BizException("异常不存在");
  196. await EnsureCurrentUserIsOperatorAsync(e.VerifierId, currentUserId,
  197. "只有指定检验人才能检验退回(或当前账号未绑定员工主数据)");
  198. if (!S8StatusRules.IsAllowedTransition(e.Status, "IN_PROGRESS"))
  199. throw new S8BizException($"状态 {e.Status} 不可检验退回");
  200. if (string.IsNullOrWhiteSpace(remark))
  201. throw new S8BizException("检验退回必须填写退回原因");
  202. var from = e.Status;
  203. e.Status = "IN_PROGRESS";
  204. e.VerifiedAt = DateTime.Now;
  205. e.VerificationResult = "REJECTED";
  206. e.VerificationRemark = remark;
  207. e.UpdatedAt = DateTime.Now;
  208. await _rep.AsTenant().UseTranAsync(async () =>
  209. {
  210. await _rep.UpdateAsync(e);
  211. await InsertTimelineAsync(e.Id, "VERIFY_REJECTED", "检验退回", from, "IN_PROGRESS",
  212. currentUserId, null, remark);
  213. }, ex => throw ex);
  214. return e;
  215. }
  216. public async Task CommentAsync(long id, string? remark)
  217. {
  218. var e = await _rep.GetFirstAsync(x => x.Id == id && !x.IsDeleted)
  219. ?? throw new S8BizException("异常不存在");
  220. await InsertTimelineAsync(e.Id, "COMMENT", "补充说明", e.Status, e.Status, null, null, remark);
  221. }
  222. private Task<AdoS8Exception?> LoadAsync(long id, long tenantId, long factoryId) =>
  223. _rep.GetFirstAsync(x => x.Id == id && x.TenantId == tenantId && x.FactoryId == factoryId && !x.IsDeleted);
  224. // 统一复用框架登录上下文,避免业务身份继续信任前端传参。
  225. private long GetCurrentUserId()
  226. {
  227. var currentUserId = _userManager.UserId;
  228. if (currentUserId <= 0)
  229. throw new S8BizException("未获取到当前登录用户");
  230. return currentUserId;
  231. }
  232. // 把异常上的处理人/检验人(employeeId) 经 EmployeeMaster.SysUserId 解析到系统账号 ID。
  233. private async Task<long?> GetEmployeeSysUserIdAsync(long? employeeId)
  234. {
  235. if (!employeeId.HasValue || employeeId.Value <= 0) return null;
  236. var emp = await _employeeRep.GetFirstAsync(x => x.Id == employeeId.Value);
  237. return emp?.SysUserId;
  238. }
  239. // 鉴权统一入口:要求当前登录用户必须是 employeeId 解析后的 SysUserId。
  240. private async Task EnsureCurrentUserIsOperatorAsync(long? employeeId, long currentUserId, string failMessage)
  241. {
  242. var ownerSysUserId = await GetEmployeeSysUserIdAsync(employeeId);
  243. if (ownerSysUserId != currentUserId)
  244. throw new S8BizException(failMessage);
  245. }
  246. private async Task InsertTimelineAsync(long exceptionId, string code, string label, string? from, string? to,
  247. long? operatorId, string? operatorName, string? remark) =>
  248. await _timelineRep.InsertAsync(new AdoS8ExceptionTimeline
  249. {
  250. ExceptionId = exceptionId,
  251. ActionCode = code,
  252. ActionLabel = label,
  253. FromStatus = from,
  254. ToStatus = to,
  255. OperatorId = operatorId,
  256. OperatorName = operatorName,
  257. ActionRemark = remark,
  258. CreatedAt = DateTime.Now
  259. });
  260. }