fix: Optional OCR-Assisted Draft Pre-Fill

This commit is contained in:
voltsrage
2026-06-28 01:48:15 +08:00
parent c160c5f912
commit c26ef22670
16 changed files with 186 additions and 69 deletions
@@ -129,31 +129,42 @@ public class DigitizationBatchesController : ControllerBase
var batch = await _batches.GetByIdAsync(id);
var presignedUrl = await _storage.GetPresignedUrlAsync(batch.DocumentRef);
var userId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
var cutoff = DateTimeOffset.UtcNow.AddMinutes(-5);
var recentAccess = await _db.DigitizationEvents.AnyAsync(e =>
e.BatchId == id &&
e.EventType == DigitizationEventType.DocumentAccessed &&
e.ActorUserId == userId &&
e.OccurredAt >= cutoff);
if (!recentAccess)
{
_db.DigitizationEvents.Add(new DigitizationEvent
{
Id = Guid.NewGuid(),
BatchId = id,
EventType = DigitizationEventType.DocumentAccessed,
ActorUserId = userId,
OccurredAt = DateTimeOffset.UtcNow
});
await _db.SaveChangesAsync();
}
await RecordDocumentAccessAsync(id);
return Ok(ApiResponse<BatchDetailResponse>.Ok(
BatchDetailResponse.FromEntity(batch, presignedUrl)));
}
/// <summary>
/// Streams the scanned document for in-app viewing (same auth and audit as GET batch).
/// Proxied through the API so the workstation can render PDFs without cross-origin iframe issues.
/// </summary>
[HttpGet("{id:guid}/document")]
[Produces("application/pdf", "image/jpeg", "image/png", "application/octet-stream")]
[ProducesResponseType(typeof(FileResult), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status404NotFound)]
public async Task<IActionResult> GetDocument(Guid id)
{
var batch = await _batches.GetByIdAsync(id);
if (batch.DocumentRef == "live-capture")
return NotFound(ApiResponse<object>.Fail(
404, "This batch has no scanned document.", "NO_DOCUMENT"));
var scanned = await _db.ScannedDocuments
.AsNoTracking()
.FirstOrDefaultAsync(d => d.BatchId == id);
var contentType = scanned?.ContentType ?? "application/octet-stream";
await RecordDocumentAccessAsync(id);
var stream = await _storage.DownloadAsync(batch.DocumentRef);
Response.Headers.CacheControl = "private, max-age=300";
return File(stream, contentType);
}
/// <summary>
/// Lists batches with optional filters and pagination.
/// </summary>
@@ -265,4 +276,27 @@ public class DigitizationBatchesController : ControllerBase
var result = await _batchEventService.GetEventsAsync(id, afterCursor, pageSize);
return Ok(ApiResponse<CursorPagedResult<BatchEventResponse>>.Ok(result));
}
private async Task RecordDocumentAccessAsync(Guid batchId)
{
var userId = Guid.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!);
var cutoff = DateTimeOffset.UtcNow.AddMinutes(-5);
var recentAccess = await _db.DigitizationEvents.AnyAsync(e =>
e.BatchId == batchId &&
e.EventType == DigitizationEventType.DocumentAccessed &&
e.ActorUserId == userId &&
e.OccurredAt >= cutoff);
if (recentAccess) return;
_db.DigitizationEvents.Add(new DigitizationEvent
{
Id = Guid.NewGuid(),
BatchId = batchId,
EventType = DigitizationEventType.DocumentAccessed,
ActorUserId = userId,
OccurredAt = DateTimeOffset.UtcNow
});
await _db.SaveChangesAsync();
}
}