feature: Corrections and Supersession
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
public class DigitizationHistoryService : IDigitizationHistoryService
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
|
||||
public DigitizationHistoryService(AppDbContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public async Task<PatientDigitizationHistoryResponse> GetPatientHistoryAsync(Guid patientId)
|
||||
{
|
||||
// Verify the patient exists (has at least one batch)
|
||||
var hasBatches = await _db.DigitizationBatches
|
||||
.AnyAsync(b => b.PatientId == patientId);
|
||||
|
||||
if (!hasBatches)
|
||||
throw new NotFoundException(
|
||||
$"No digitization history found for patient {patientId}.",
|
||||
"PATIENT_HISTORY_NOT_FOUND");
|
||||
|
||||
// Load all batches for this patient with their relationships
|
||||
var batches = await _db.DigitizationBatches
|
||||
.Include(b => b.DraftObservations)
|
||||
.Include(b => b.Events)
|
||||
.ThenInclude(e => e.Actor)
|
||||
.Include(b => b.EnteredByUser)
|
||||
.Include(b => b.VerifiedByUser)
|
||||
.Include(b => b.ApprovedByUser)
|
||||
.Where(b => b.PatientId == patientId)
|
||||
.OrderByDescending(b => b.CreatedAt)
|
||||
.ToListAsync();
|
||||
|
||||
// For each promoted batch, count live vs superseded observations
|
||||
var promotedBatchIds = batches
|
||||
.Where(b => b.Status == BatchStatus.Promoted)
|
||||
.Select(b => b.Id)
|
||||
.ToList();
|
||||
|
||||
var liveObservationCounts = await _db.LiveObservations
|
||||
.Where(o => promotedBatchIds.Contains(o.SourceBatchId))
|
||||
.GroupBy(o => new { o.SourceBatchId, o.IsSuperseded })
|
||||
.Select(g => new
|
||||
{
|
||||
g.Key.SourceBatchId,
|
||||
g.Key.IsSuperseded,
|
||||
Count = g.Count()
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
// Build a lookup: batchId -> (activeCount, supersededCount)
|
||||
var obsCountLookup = promotedBatchIds.ToDictionary(
|
||||
id => id,
|
||||
id =>
|
||||
{
|
||||
var active = liveObservationCounts
|
||||
.FirstOrDefault(x => x.SourceBatchId == id && !x.IsSuperseded)?.Count ?? 0;
|
||||
var superseded = liveObservationCounts
|
||||
.FirstOrDefault(x => x.SourceBatchId == id && x.IsSuperseded)?.Count ?? 0;
|
||||
return (Active: active, Superseded: superseded);
|
||||
});
|
||||
|
||||
// Build a lookup for "has been superseded" — batches that appear as
|
||||
// SupersedesBatchId on another batch that reached Promoted
|
||||
var supersededByLookup = await _db.DigitizationBatches
|
||||
.Where(b => b.SupersedesBatchId.HasValue
|
||||
&& b.Status == BatchStatus.Promoted
|
||||
&& promotedBatchIds.Contains(b.SupersedesBatchId.Value))
|
||||
.ToDictionaryAsync(
|
||||
b => b.SupersedesBatchId!.Value,
|
||||
b => b.Id);
|
||||
|
||||
var entries = batches.Select(b =>
|
||||
{
|
||||
var counts = obsCountLookup.GetValueOrDefault(b.Id, (Active: 0, Superseded: 0));
|
||||
var hasBeenSuperseded = supersededByLookup.ContainsKey(b.Id);
|
||||
supersededByLookup.TryGetValue(b.Id, out var supersededByBatchId);
|
||||
|
||||
return new DigitizationHistoryEntry(
|
||||
BatchId: b.Id,
|
||||
Status: b.Status.ToDbString(),
|
||||
BatchType: b.BatchType.ToDbString(),
|
||||
Track: b.Track.ToDbString(),
|
||||
SupersedesBatchId: b.SupersedesBatchId,
|
||||
IsCorrection: b.SupersedesBatchId.HasValue,
|
||||
HasBeenSuperseded: hasBeenSuperseded,
|
||||
SupersededByBatchId: hasBeenSuperseded ? supersededByBatchId : null,
|
||||
DraftObservationCount: b.DraftObservations.Count,
|
||||
LiveObservationCount: counts.Active,
|
||||
SupersededObservationCount: counts.Superseded,
|
||||
CreatedAt: b.CreatedAt,
|
||||
PromotedAt: b.PromotedAt,
|
||||
PromotionEncounterId: b.PromotionEncounterId,
|
||||
EnteredByUserId: b.EnteredByUserId,
|
||||
EnteredByUserName: b.EnteredByUser?.FullName,
|
||||
VerifiedByUserId: b.VerifiedByUserId,
|
||||
VerifiedByUserName: b.VerifiedByUser?.FullName,
|
||||
ApprovedByUserId: b.ApprovedByUserId,
|
||||
ApprovedByUserName: b.ApprovedByUser?.FullName,
|
||||
AuditTrail: b.Events
|
||||
.OrderBy(e => e.OccurredAt)
|
||||
.Select(e => new DigitizationEventSummary(
|
||||
e.EventType.ToDbString(),
|
||||
e.OccurredAt,
|
||||
e.ActorUserId,
|
||||
e.Actor?.FullName ?? "Unknown",
|
||||
e.MetadataJson))
|
||||
.ToList()
|
||||
);
|
||||
}).ToList();
|
||||
|
||||
return new PatientDigitizationHistoryResponse(
|
||||
PatientId: patientId,
|
||||
TotalBatches: entries.Count,
|
||||
PromotedBatches: entries.Count(e => e.Status == "PROMOTED"),
|
||||
SupersededBatches: entries.Count(e => e.HasBeenSuperseded),
|
||||
PendingBatches: entries.Count(e => e.Status != "PROMOTED" && e.Status != "REJECTED"),
|
||||
Entries: entries
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user