using System.Security.Claims; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; /// /// Batch verification and rejection endpoints. /// Enforces separation of duties: the user who entered the data cannot verify /// or reject the same batch. All transitions write DigitizationEvents. /// [ApiController] [Route("api/v1/digitization-batches")] [Produces("application/json")] [Authorize] public class VerificationController : ControllerBase { private readonly IVerificationService _verification; public VerificationController(IVerificationService verification) { _verification = verification; } /// /// Verifies a batch that is in PendingVerification status. /// Requires field-level checks. If passed is true, transitions to Verified /// or AwaitingClinicalApproval based on site configuration for the batch type. /// If passed is false, transitions to Rejected with field check errors as the reason. /// Returns 409 SEPARATION_OF_DUTIES_VIOLATION if the verifier is the same user /// who entered the data. /// /// The batch ID to verify. /// Verification request with field checks and pass/fail. [HttpPost("{id:guid}/verify")] [Authorize(Roles = "VERIFIER,CLINICAL_APPROVER,ADMINISTRATOR")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status409Conflict)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status422UnprocessableEntity)] public async Task Verify(Guid id, [FromBody] VerifyBatchRequest request) { var verifierUserId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!); var batch = await _verification.VerifyAsync(id, request, verifierUserId); return Ok(ApiResponse.Ok(BatchDetailResponse.FromEntity(batch))); } /// /// Rejects a batch that is in PendingVerification or AwaitingClinicalApproval status. /// Requires a rejection reason (minimum 10 characters). The batch returns to the /// entry work queue for re-entry by a data entry clerk. /// Returns 409 SEPARATION_OF_DUTIES_VIOLATION if the rejector is the same user /// who entered the data (for PendingVerification status only). /// /// The batch ID to reject. /// Rejection request with required reason. [HttpPost("{id:guid}/reject")] [Authorize(Roles = "VERIFIER,CLINICAL_APPROVER,ADMINISTRATOR")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status409Conflict)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status422UnprocessableEntity)] public async Task Reject(Guid id, [FromBody] RejectBatchRequest request) { var actorUserId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!); var batch = await _verification.RejectAsync(id, request, actorUserId); return Ok(ApiResponse.Ok(BatchDetailResponse.FromEntity(batch))); } }