feature: Corrections and Supersession
This commit is contained in:
@@ -14,28 +14,36 @@ public class DigitizationBatchesController : ControllerBase
|
||||
{
|
||||
private readonly IBatchService _batches;
|
||||
private readonly IDocumentStorageService _storage;
|
||||
private readonly IPromotionService _promotion;
|
||||
|
||||
private static readonly HashSet<string> _allowedMimeTypes = new()
|
||||
{
|
||||
"application/pdf", "image/jpeg", "image/png"
|
||||
};
|
||||
|
||||
public DigitizationBatchesController(IBatchService batches, IDocumentStorageService storage)
|
||||
public DigitizationBatchesController(
|
||||
IBatchService batches,
|
||||
IDocumentStorageService storage,
|
||||
IPromotionService promotion)
|
||||
{
|
||||
_batches = batches;
|
||||
_storage = storage;
|
||||
_promotion = promotion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uploads a scanned document and creates a new digitization batch.
|
||||
/// When supersedesBatchId is provided, the batch is treated as a correction
|
||||
/// that will supersede the erroneous promoted batch upon its own promotion.
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[Consumes("multipart/form-data")]
|
||||
[ProducesResponseType(typeof(ApiResponse<BatchDetailResponse>), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status409Conflict)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status422UnprocessableEntity)]
|
||||
[RequestSizeLimit(25 * 1024 * 1024)]
|
||||
[Consumes("multipart/form-data")]
|
||||
public async Task<IActionResult> Create([FromForm] CreateBatchForm form)
|
||||
{
|
||||
if (form.File is null || form.File.Length == 0)
|
||||
@@ -45,17 +53,19 @@ public class DigitizationBatchesController : ControllerBase
|
||||
return BadRequest(ApiResponse<object>.Fail(400,
|
||||
"Accepted formats: PDF, JPEG, PNG.", "INVALID_MIME_TYPE"));
|
||||
|
||||
var req = form.ToMetadata();
|
||||
var parsedBatchType = BatchTypeExtensions.FromDbString(req.BatchType.ToUpperInvariant());
|
||||
var parsedTrack = string.IsNullOrEmpty(req.Track)
|
||||
var parsedBatchType = BatchTypeExtensions.FromDbString(form.BatchType.ToUpperInvariant());
|
||||
var parsedTrack = string.IsNullOrEmpty(form.Track)
|
||||
? BatchTrack.Backfill
|
||||
: BatchTrackExtensions.FromDbString(req.Track.ToUpperInvariant());
|
||||
: BatchTrackExtensions.FromDbString(form.Track.ToUpperInvariant());
|
||||
var actorUserId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
|
||||
|
||||
using var stream = form.File.OpenReadStream();
|
||||
var batch = await _batches.CreateAsync(
|
||||
stream, form.File.ContentType, parsedBatchType, parsedTrack, req.PatientId, actorUserId);
|
||||
return StatusCode(201, ApiResponse<BatchDetailResponse>.Created(BatchDetailResponse.FromEntity(batch)));
|
||||
var result = await _batches.CreateAsync(
|
||||
stream, form.File.ContentType, parsedBatchType, parsedTrack,
|
||||
form.PatientId, form.SupersedesBatchId, actorUserId);
|
||||
|
||||
return StatusCode(201, ApiResponse<BatchDetailResponse>.Created(
|
||||
BatchDetailResponse.FromEntity(result.Batch, supersession: result.Supersession)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -115,4 +125,21 @@ public class DigitizationBatchesController : ControllerBase
|
||||
var batch = await _batches.AssignAsync(id, req.EntryClerkUserId, actorUserId);
|
||||
return Ok(ApiResponse<BatchDetailResponse>.Ok(BatchDetailResponse.FromEntity(batch)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Promotes an approved batch to live clinical data. For correction batches,
|
||||
/// marks the original batch's live observations as superseded (append-only).
|
||||
/// </summary>
|
||||
[HttpPost("{id:guid}/promote")]
|
||||
[Authorize(Roles = "CLINICAL_APPROVER,ADMINISTRATOR")]
|
||||
[ProducesResponseType(typeof(ApiResponse<PromotionResult>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status409Conflict)]
|
||||
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status422UnprocessableEntity)]
|
||||
public async Task<IActionResult> Promote(Guid id)
|
||||
{
|
||||
var actorUserId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
|
||||
var result = await _promotion.PromoteAsync(id, actorUserId);
|
||||
return Ok(ApiResponse<PromotionResult>.Ok(result));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user