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