using System.Security.Claims; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; /// /// Draft data entry endpoints for digitization batches. A data entry clerk /// uses these endpoints to transcribe scanned paper charts into structured data. /// [ApiController] [Route("api/v1/digitization-batches/{batchId:guid}/draft")] [Produces("application/json")] [Authorize] public class DraftController : ControllerBase { private readonly IDraftService _draft; public DraftController(IDraftService draft) => _draft = draft; /// /// Returns the full draft payload for a batch, including patient demographics, /// encounter context, and all observation rows entered so far. /// /// Digitization batch id. /// Complete draft data for the batch. [HttpGet] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] public async Task GetDraft(Guid batchId) { var result = await _draft.GetDraftAsync(batchId); return Ok(ApiResponse.Ok(result)); } /// /// Upserts draft patient demographics for a batch. Creates the patient record /// on the first call; updates it on subsequent calls. Automatically transitions /// the batch to IN_ENTRY on first save. /// /// Digitization batch id. /// Patient demographic fields. /// The upserted patient record. [HttpPut("patient")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status409Conflict)] public async Task UpsertPatient( Guid batchId, [FromBody] UpsertDraftPatientRequest req) { var actorUserId = GetCurrentUserId(); var result = await _draft.UpsertPatientAsync(batchId, req, actorUserId); return Ok(ApiResponse.Ok(result)); } /// /// Upserts draft encounter fields for a batch. Creates the encounter record /// on the first call; updates it on subsequent calls. /// /// Digitization batch id. /// Encounter context fields. /// The upserted encounter record. [HttpPut("encounter")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status409Conflict)] public async Task UpsertEncounter( Guid batchId, [FromBody] UpsertDraftEncounterRequest req) { var actorUserId = GetCurrentUserId(); var result = await _draft.UpsertEncounterAsync(batchId, req, actorUserId); return Ok(ApiResponse.Ok(result)); } /// /// Adds a new observation row to the batch draft. Validates the value against /// plausibility ranges before saving. /// /// Digitization batch id. /// Observation data to add. /// The created observation record. [HttpPost("observations")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status201Created)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status409Conflict)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status422UnprocessableEntity)] public async Task AddObservation( Guid batchId, [FromBody] CreateDraftObservationRequest req) { var actorUserId = GetCurrentUserId(); var result = await _draft.AddObservationAsync(batchId, req, actorUserId); return StatusCode(201, ApiResponse.Created(result)); } /// /// Updates an existing observation row in the batch draft. Re-validates the /// new value against plausibility ranges. /// /// Digitization batch id. /// Observation id to update. /// Updated observation data. /// The updated observation record. [HttpPut("observations/{obsId:guid}")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status409Conflict)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status422UnprocessableEntity)] public async Task UpdateObservation( Guid batchId, Guid obsId, [FromBody] UpdateDraftObservationRequest req) { var actorUserId = GetCurrentUserId(); var result = await _draft.UpdateObservationAsync(batchId, obsId, req, actorUserId); return Ok(ApiResponse.Ok(result)); } /// /// Removes an observation row from the batch draft. /// /// Digitization batch id. /// Observation id to delete. [HttpDelete("observations/{obsId:guid}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status409Conflict)] public async Task DeleteObservation(Guid batchId, Guid obsId) { var actorUserId = GetCurrentUserId(); await _draft.DeleteObservationAsync(batchId, obsId, actorUserId); return NoContent(); } /// /// Validates completeness per batch type and transitions the batch from /// IN_ENTRY to PENDING_VERIFICATION. Returns 422 if required fields are missing. /// /// Digitization batch id. /// The updated batch record. [HttpPost("~/api/v1/digitization-batches/{batchId:guid}/submit-for-verification")] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status409Conflict)] [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status422UnprocessableEntity)] public async Task SubmitForVerification(Guid batchId) { var actorUserId = GetCurrentUserId(); var batch = await _draft.SubmitForVerificationAsync(batchId, actorUserId); return Ok(ApiResponse.Ok(BatchDetailResponse.FromEntity(batch))); } private Guid GetCurrentUserId() => Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!); }