using Admin.NET.Plugin.AiDOP.Dto.S8; using Admin.NET.Plugin.AiDOP.Infrastructure; using Admin.NET.Plugin.AiDOP.Service.S8; namespace Admin.NET.Plugin.AiDOP.Controllers.S8; [ApiController] [Route("api/aidop/s8/exceptions")] [NonUnify] public class AdoS8ExceptionsController : ControllerBase { private readonly S8ExceptionService _exceptionSvc; private readonly S8TaskFlowService _taskFlowSvc; private readonly S8DecisionService _decisionSvc; private readonly S8TrustedScopeResolver _scope; public AdoS8ExceptionsController( S8ExceptionService exceptionSvc, S8TaskFlowService taskFlowSvc, S8DecisionService decisionSvc, S8TrustedScopeResolver scope) { _exceptionSvc = exceptionSvc; _taskFlowSvc = taskFlowSvc; _decisionSvc = decisionSvc; _scope = scope; } [HttpGet] public async Task GetPagedAsync([FromQuery] AdoS8ExceptionQueryDto q) { // S8-TENANT-FACTORY-P0-CLOSURE-1:作用域一律服务端解析,覆盖客户端传入值。 var scope = await _scope.ResolveAsync(); q.TenantId = scope.TenantId; q.FactoryId = scope.FactoryId; var (total, list) = await _exceptionSvc.GetPagedAsync(q); return Ok(new { total, page = q.Page, pageSize = q.PageSize, list }); } [HttpGet("filter-options")] public async Task GetFilterOptionsAsync([FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1) { // Compatibility-only. Security scope is resolved server-side. _ = tenantId; _ = factoryId; var scope = await _scope.ResolveAsync(); return Ok(await _exceptionSvc.GetFilterOptionsAsync(scope.TenantId, scope.FactoryId)); } [HttpGet("{id:long}")] public async Task GetDetailAsync(long id, [FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1) { // Compatibility-only. Security scope is resolved server-side. _ = tenantId; _ = factoryId; var scope = await _scope.ResolveAsync(); var detail = await _exceptionSvc.GetDetailAsync(id, scope.TenantId, scope.FactoryId); return detail == null ? NotFound() : Ok(detail); } [HttpGet("{id:long}/timeline")] public async Task GetTimelineAsync(long id) { if (!await IsInScopeAsync(id)) return NotFound(); return Ok(await _decisionSvc.GetTimelineAsync(id)); } [HttpGet("{id:long}/decisions")] public async Task GetDecisionsAsync(long id) { if (!await IsInScopeAsync(id)) return NotFound(); return Ok(await _decisionSvc.GetDecisionsAsync(id)); } [HttpGet("{id:long}/evidences")] public async Task GetEvidencesAsync(long id) { if (!await IsInScopeAsync(id)) return NotFound(); return Ok(await _decisionSvc.GetEvidencesAsync(id)); } /// 子资源归属由父异常派生;不在可信作用域内一律按「不存在」处理,不泄露他租户资源是否存在。 private async Task IsInScopeAsync(long exceptionId) { var scope = await _scope.ResolveAsync(); return await _decisionSvc.IsExceptionInScopeAsync(exceptionId, scope.TenantId, scope.FactoryId); } [HttpPost("{id:long}/claim")] public async Task ClaimAsync(long id, [FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1, [FromBody] AdoS8ClaimBody? body = null) { _ = tenantId; _ = factoryId; try { var scope = await _scope.ResolveAsync(); var e = await _taskFlowSvc.ClaimAsync(id, scope.TenantId, scope.FactoryId, body?.AssigneeId ?? 0, body?.Remark); return Ok(new { id = e.Id, status = e.Status }); } catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); } } [HttpPost("{id:long}/transfer")] public async Task TransferAsync(long id, [FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1, [FromBody] AdoS8TransferDto? body = null) { _ = tenantId; _ = factoryId; try { var scope = await _scope.ResolveAsync(); var e = await _taskFlowSvc.TransferAsync(id, scope.TenantId, scope.FactoryId, body?.AssigneeId ?? 0, body?.Remark); return Ok(new { id = e.Id, assigneeId = e.AssigneeId }); } catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); } } [HttpPost("{id:long}/start-progress")] public async Task StartProgressAsync(long id, [FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1, [FromBody] AdoS8CommentDto? body = null) { _ = tenantId; _ = factoryId; try { var scope = await _scope.ResolveAsync(); var e = await _taskFlowSvc.StartProgressAsync(id, scope.TenantId, scope.FactoryId, body?.Remark); return Ok(new { id = e.Id, status = e.Status }); } catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); } } [HttpPost("{id:long}/upgrade")] public async Task UpgradeAsync(long id, [FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1, [FromBody] AdoS8CommentDto? body = null) { _ = tenantId; _ = factoryId; try { var scope = await _scope.ResolveAsync(); var e = await _taskFlowSvc.UpgradeAsync(id, scope.TenantId, scope.FactoryId, body?.Remark); return Ok(new { id = e.Id, status = e.Status }); } catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); } } [HttpPost("{id:long}/reject")] public async Task RejectAsync(long id, [FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1, [FromBody] AdoS8CommentDto? body = null) { _ = tenantId; _ = factoryId; try { var scope = await _scope.ResolveAsync(); var e = await _taskFlowSvc.RejectAsync(id, scope.TenantId, scope.FactoryId, body?.Remark); return Ok(new { id = e.Id, status = e.Status }); } catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); } } [HttpPost("{id:long}/submit-verification")] public async Task SubmitVerificationAsync(long id, [FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1, [FromBody] AdoS8SubmitVerificationDto? body = null) { _ = tenantId; _ = factoryId; try { var scope = await _scope.ResolveAsync(); var e = await _taskFlowSvc.SubmitVerificationAsync( id, scope.TenantId, scope.FactoryId, body?.VerifierId ?? 0, body?.Remark); return Ok(new { id = e.Id, status = e.Status, verifierId = e.VerifierId }); } catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); } } [HttpPost("{id:long}/approve-verification")] public async Task ApproveVerificationAsync(long id, [FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1, [FromBody] AdoS8ApproveVerificationDto? body = null) { _ = tenantId; _ = factoryId; try { var scope = await _scope.ResolveAsync(); var e = await _taskFlowSvc.ApproveVerificationAsync(id, scope.TenantId, scope.FactoryId, body?.Remark); return Ok(new { id = e.Id, status = e.Status }); } catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); } } [HttpPost("{id:long}/reject-verification")] public async Task RejectVerificationAsync(long id, [FromQuery] long tenantId = 1, [FromQuery] long factoryId = 1, [FromBody] AdoS8RejectVerificationDto? body = null) { _ = tenantId; _ = factoryId; try { var scope = await _scope.ResolveAsync(); var e = await _taskFlowSvc.RejectVerificationAsync(id, scope.TenantId, scope.FactoryId, body?.Remark ?? string.Empty); return Ok(new { id = e.Id, status = e.Status }); } catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); } } [HttpPost("{id:long}/comment")] public async Task CommentAsync(long id, [FromBody] AdoS8CommentDto? body = null) { try { var scope = await _scope.ResolveAsync(); await _taskFlowSvc.CommentAsync(id, scope.TenantId, scope.FactoryId, body?.Remark); return Ok(new { id }); } catch (S8BizException ex) { return BadRequest(new { message = ex.Message }); } } } public class AdoS8ClaimBody { public long AssigneeId { get; set; } public string? Remark { get; set; } }