Clinical Sync Batch Engine: first commit
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using VigilCare.ClinicalContracts.Sync;
|
||||
|
||||
public class ClinicalSyncService : IClinicalSyncService
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
private readonly ILogger<ClinicalSyncService> _logger;
|
||||
|
||||
public ClinicalSyncService(AppDbContext db, ILogger<ClinicalSyncService> logger)
|
||||
{
|
||||
_db = db;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<ClinicalBatchUploadResponse> UploadBatchAsync(
|
||||
ClinicalSyncBatchRequest request, CancellationToken ct)
|
||||
{
|
||||
var existing = await _db.ClinicalSyncBatches
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(b => b.BatchReference == request.BatchReference, ct);
|
||||
if (existing is not null)
|
||||
return new ClinicalBatchUploadResponse(existing.Id, existing.Status.ToDbString());
|
||||
|
||||
var gateway = await _db.WardGateways
|
||||
.FirstOrDefaultAsync(g => g.Id == request.GatewayId && g.SiteId == request.SiteId, ct)
|
||||
?? throw new NotFoundException("Gateway not found for site.", "GATEWAY_NOT_FOUND");
|
||||
|
||||
var payload = JsonSerializer.Serialize(request);
|
||||
var batch = new ClinicalSyncBatch(gateway.Id, request.SiteId, request.BatchReference, payload);
|
||||
var batchId = Guid.NewGuid();
|
||||
_db.Entry(batch).Property(nameof(ClinicalSyncBatch.Id)).CurrentValue = batchId;
|
||||
|
||||
await using var tx = await _db.Database.BeginTransactionAsync(ct);
|
||||
_db.ClinicalSyncBatches.Add(batch);
|
||||
_db.OutboxEvents.Add(new OutboxEvent
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Topic = ClinicalSyncOptions.BatchReceivedOutboxTopic,
|
||||
Payload = JsonSerializer.Serialize(new
|
||||
{
|
||||
batchId,
|
||||
gatewayId = gateway.Id,
|
||||
siteId = request.SiteId
|
||||
}),
|
||||
PartitionKey = gateway.Id.ToString(),
|
||||
CreatedAt = DateTimeOffset.UtcNow
|
||||
});
|
||||
await _db.SaveChangesAsync(ct);
|
||||
await tx.CommitAsync(ct);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Sync batch {BatchId} received from gateway {GatewayId} — {ObsCount} observations",
|
||||
batch.Id, gateway.Id, request.Observations.Count);
|
||||
|
||||
return new ClinicalBatchUploadResponse(batch.Id, "RECEIVED");
|
||||
}
|
||||
|
||||
public async Task<ClinicalBatchStatusResponse> GetBatchStatusAsync(Guid batchId, CancellationToken ct)
|
||||
{
|
||||
var batch = await _db.ClinicalSyncBatches
|
||||
.AsNoTracking()
|
||||
.Include(b => b.Conflicts)
|
||||
.FirstOrDefaultAsync(b => b.Id == batchId, ct)
|
||||
?? throw new NotFoundException("Sync batch not found.", "BATCH_NOT_FOUND");
|
||||
|
||||
var conflicts = batch.Conflicts.Select(c =>
|
||||
new ClinicalConflictDetail(c.ClientRef, c.ItemType, c.ConflictReason)).ToList();
|
||||
|
||||
return new ClinicalBatchStatusResponse(batch.Id, batch.Status.ToDbString(), conflicts);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<ClinicalSyncHistoryItem>> GetSyncHistoryAsync(
|
||||
Guid siteId, Guid gatewayId, int limit, CancellationToken ct)
|
||||
{
|
||||
var gatewayExists = await _db.WardGateways
|
||||
.AnyAsync(g => g.Id == gatewayId && g.SiteId == siteId, ct);
|
||||
if (!gatewayExists)
|
||||
throw new NotFoundException("Gateway not found for site.", "GATEWAY_NOT_FOUND");
|
||||
|
||||
return await _db.ClinicalSyncBatches
|
||||
.AsNoTracking()
|
||||
.Where(b => b.GatewayId == gatewayId && b.SiteId == siteId)
|
||||
.OrderByDescending(b => b.SubmittedAt)
|
||||
.Take(limit)
|
||||
.Select(b => new ClinicalSyncHistoryItem(
|
||||
b.Id,
|
||||
b.BatchReference,
|
||||
b.Status.ToDbString(),
|
||||
b.Conflicts.Count,
|
||||
b.SubmittedAt))
|
||||
.ToListAsync(ct);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user