AdoS8ExceptionsController.cs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
  2. using Admin.NET.Plugin.AiDOP.Const.S8;
  3. using Admin.NET.Plugin.AiDOP.Dto.S8;
  4. using Admin.NET.Plugin.AiDOP.Infrastructure;
  5. using Admin.NET.Plugin.AiDOP.Service.S8;
  6. namespace Admin.NET.Plugin.AiDOP.Controllers.S8;
  7. [ApiController]
  8. [Route("api/aidop/s8/exceptions")]
  9. [NonUnify]
  10. public class AdoS8ExceptionsController : ControllerBase
  11. {
  12. private readonly S8ExceptionService _exceptionSvc;
  13. private readonly S8TaskFlowService _taskFlowSvc;
  14. private readonly S8DecisionService _decisionSvc;
  15. private readonly S8TrustedScopeResolver _scope;
  16. public AdoS8ExceptionsController(
  17. S8ExceptionService exceptionSvc,
  18. S8TaskFlowService taskFlowSvc,
  19. S8DecisionService decisionSvc,
  20. S8TrustedScopeResolver scope)
  21. {
  22. _exceptionSvc = exceptionSvc;
  23. _taskFlowSvc = taskFlowSvc;
  24. _decisionSvc = decisionSvc;
  25. _scope = scope;
  26. }
  27. [HttpGet]
  28. [S8Permission(S8PermissionCatalog.ExceptionRead)]
  29. public async Task<IActionResult> GetPagedAsync([FromQuery] AdoS8ExceptionQueryDto q)
  30. {
  31. // S8-TENANT-FACTORY-P0-CLOSURE-1:作用域一律服务端解析,覆盖客户端传入值。
  32. var scope = await _scope.ResolveAsync();
  33. q.TenantId = scope.TenantId;
  34. var (total, list) = await _exceptionSvc.GetPagedAsync(q);
  35. return Ok(new { total, page = q.Page, pageSize = q.PageSize, list });
  36. }
  37. [HttpGet("filter-options")]
  38. [S8Permission(S8PermissionCatalog.ExceptionRead)]
  39. public async Task<IActionResult> GetFilterOptionsAsync([FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1)
  40. {
  41. // Compatibility-only. Security scope is resolved server-side.
  42. _ = tenantId; _ = factoryId;
  43. var scope = await _scope.ResolveAsync();
  44. return Ok(await _exceptionSvc.GetFilterOptionsAsync(scope.TenantId));
  45. }
  46. [HttpGet("{id:long}")]
  47. [S8Permission(S8PermissionCatalog.ExceptionRead)]
  48. public async Task<IActionResult> GetDetailAsync(long id, [FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1)
  49. {
  50. // Compatibility-only. Security scope is resolved server-side.
  51. _ = tenantId; _ = factoryId;
  52. var scope = await _scope.ResolveAsync();
  53. var detail = await _exceptionSvc.GetDetailAsync(id, scope.TenantId);
  54. return detail == null ? NotFound() : Ok(detail);
  55. }
  56. [HttpGet("{id:long}/timeline")]
  57. [S8Permission(S8PermissionCatalog.ExceptionRead)]
  58. public async Task<IActionResult> GetTimelineAsync(long id)
  59. {
  60. if (!await IsInScopeAsync(id)) return NotFound();
  61. return Ok(await _decisionSvc.GetTimelineAsync(id));
  62. }
  63. [HttpGet("{id:long}/decisions")]
  64. [S8Permission(S8PermissionCatalog.ExceptionRead)]
  65. public async Task<IActionResult> GetDecisionsAsync(long id)
  66. {
  67. if (!await IsInScopeAsync(id)) return NotFound();
  68. return Ok(await _decisionSvc.GetDecisionsAsync(id));
  69. }
  70. [HttpGet("{id:long}/evidences")]
  71. [S8Permission(S8PermissionCatalog.ExceptionRead)]
  72. public async Task<IActionResult> GetEvidencesAsync(long id)
  73. {
  74. if (!await IsInScopeAsync(id)) return NotFound();
  75. return Ok(await _decisionSvc.GetEvidencesAsync(id));
  76. }
  77. /// <summary>子资源归属由父异常派生;不在可信作用域内一律按「不存在」处理,不泄露他租户资源是否存在。</summary>
  78. private async Task<bool> IsInScopeAsync(long exceptionId)
  79. {
  80. var scope = await _scope.ResolveAsync();
  81. return await _decisionSvc.IsExceptionInScopeAsync(exceptionId, scope.TenantId);
  82. }
  83. [HttpPost("{id:long}/claim")]
  84. [S8Permission(S8PermissionCatalog.ExceptionClaim)]
  85. /// <summary>
  86. /// 认领。<b>处理人恒为当前登录账号</b>,请求体只带备注 ——
  87. /// 「替别人指派」是 <c>POST {id}/transfer</c>(权限位 <c>ExceptionAssign</c>)。
  88. /// 已被他人抢先认领时返回 400「该异常已被其他人员认领」。
  89. /// </summary>
  90. public async Task<IActionResult> ClaimAsync(long id, [FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1,
  91. [FromBody] AdoS8ClaimBody? body = null)
  92. {
  93. _ = tenantId; _ = factoryId;
  94. try
  95. {
  96. var scope = await _scope.ResolveAsync();
  97. var e = await _taskFlowSvc.ClaimAsync(id, scope.TenantId, body?.Remark);
  98. return Ok(new { id = e.Id, status = e.Status, assigneeUserId = e.AssigneeUserId });
  99. }
  100. catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); }
  101. }
  102. [HttpPost("{id:long}/transfer")]
  103. [S8Permission(S8PermissionCatalog.ExceptionAssign)]
  104. public async Task<IActionResult> TransferAsync(long id, [FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1,
  105. [FromBody] AdoS8TransferDto? body = null)
  106. {
  107. _ = tenantId; _ = factoryId;
  108. try
  109. {
  110. var scope = await _scope.ResolveAsync();
  111. var e = await _taskFlowSvc.TransferAsync(id, scope.TenantId, body?.AssigneeUserId ?? 0, body?.Remark);
  112. return Ok(new { id = e.Id, assigneeUserId = e.AssigneeUserId });
  113. }
  114. catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); }
  115. }
  116. [HttpPost("{id:long}/start-progress")]
  117. [S8Permission(S8PermissionCatalog.ExceptionStart)]
  118. public async Task<IActionResult> StartProgressAsync(long id, [FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1,
  119. [FromBody] AdoS8CommentDto? body = null)
  120. {
  121. _ = tenantId; _ = factoryId;
  122. try
  123. {
  124. var scope = await _scope.ResolveAsync();
  125. var e = await _taskFlowSvc.StartProgressAsync(id, scope.TenantId, body?.Remark);
  126. return Ok(new { id = e.Id, status = e.Status });
  127. }
  128. catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); }
  129. }
  130. [HttpPost("{id:long}/upgrade")]
  131. [S8Permission(S8PermissionCatalog.ExceptionUpgrade)]
  132. public async Task<IActionResult> UpgradeAsync(long id, [FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1,
  133. [FromBody] AdoS8CommentDto? body = null)
  134. {
  135. _ = tenantId; _ = factoryId;
  136. try
  137. {
  138. var scope = await _scope.ResolveAsync();
  139. var e = await _taskFlowSvc.UpgradeAsync(id, scope.TenantId, body?.Remark);
  140. return Ok(new { id = e.Id, status = e.Status });
  141. }
  142. catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); }
  143. }
  144. [HttpPost("{id:long}/reject")]
  145. [S8Permission(S8PermissionCatalog.ExceptionReject)]
  146. public async Task<IActionResult> RejectAsync(long id, [FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1,
  147. [FromBody] AdoS8CommentDto? body = null)
  148. {
  149. _ = tenantId; _ = factoryId;
  150. try
  151. {
  152. var scope = await _scope.ResolveAsync();
  153. var e = await _taskFlowSvc.RejectAsync(id, scope.TenantId, body?.Remark);
  154. return Ok(new { id = e.Id, status = e.Status });
  155. }
  156. catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); }
  157. }
  158. [HttpPost("{id:long}/submit-verification")]
  159. [S8Permission(S8PermissionCatalog.VerificationSubmit)]
  160. public async Task<IActionResult> SubmitVerificationAsync(long id,
  161. [FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1,
  162. [FromBody] AdoS8SubmitVerificationDto? body = null)
  163. {
  164. _ = tenantId; _ = factoryId;
  165. try
  166. {
  167. var scope = await _scope.ResolveAsync();
  168. var e = await _taskFlowSvc.SubmitVerificationAsync(
  169. id, scope.TenantId, body?.VerifierUserId ?? 0, body?.Remark);
  170. return Ok(new { id = e.Id, status = e.Status, verifierUserId = e.VerifierUserId });
  171. }
  172. catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); }
  173. }
  174. [HttpPost("{id:long}/approve-verification")]
  175. [S8Permission(S8PermissionCatalog.VerificationApprove)]
  176. public async Task<IActionResult> ApproveVerificationAsync(long id,
  177. [FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1,
  178. [FromBody] AdoS8ApproveVerificationDto? body = null)
  179. {
  180. _ = tenantId; _ = factoryId;
  181. try
  182. {
  183. var scope = await _scope.ResolveAsync();
  184. var e = await _taskFlowSvc.ApproveVerificationAsync(id, scope.TenantId, body?.Remark);
  185. return Ok(new { id = e.Id, status = e.Status });
  186. }
  187. catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); }
  188. }
  189. [HttpPost("{id:long}/reject-verification")]
  190. [S8Permission(S8PermissionCatalog.VerificationReject)]
  191. public async Task<IActionResult> RejectVerificationAsync(long id,
  192. [FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1,
  193. [FromBody] AdoS8RejectVerificationDto? body = null)
  194. {
  195. _ = tenantId; _ = factoryId;
  196. try
  197. {
  198. var scope = await _scope.ResolveAsync();
  199. var e = await _taskFlowSvc.RejectVerificationAsync(id, scope.TenantId, body?.Remark ?? string.Empty);
  200. return Ok(new { id = e.Id, status = e.Status });
  201. }
  202. catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); }
  203. }
  204. [HttpPost("{id:long}/comment")]
  205. [S8Permission(S8PermissionCatalog.ExceptionComment)]
  206. public async Task<IActionResult> CommentAsync(long id, [FromBody] AdoS8CommentDto? body = null)
  207. {
  208. try
  209. {
  210. var scope = await _scope.ResolveAsync();
  211. await _taskFlowSvc.CommentAsync(id, scope.TenantId, body?.Remark);
  212. return Ok(new { id });
  213. }
  214. catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); }
  215. }
  216. }
  217. /// <summary>
  218. /// 认领请求体。<b>刻意不含处理人字段</b> —— 处理人由服务端从登录上下文取,
  219. /// 留一个"服务端会忽略"的 assignee 字段只会让调用方以为自己能替别人认领。
  220. /// </summary>
  221. public class AdoS8ClaimBody
  222. {
  223. public string? Remark { get; set; }
  224. }