feature: FHIR R4 Inbound Facade

This commit is contained in:
voltsrage
2026-06-21 13:53:38 +08:00
parent 9f96f54007
commit a43db52813
42 changed files with 2873 additions and 3 deletions
@@ -15,11 +15,16 @@ public class EncounterService : IEncounterService
private readonly AppDbContext _db;
private readonly IQsofaService _qsofa;
private readonly IExternalIdentifierService _identifiers;
public EncounterService(AppDbContext db, IQsofaService qsofa)
public EncounterService(
AppDbContext db,
IQsofaService qsofa,
IExternalIdentifierService identifiers)
{
_db = db;
_qsofa = qsofa;
_identifiers = identifiers;
}
public async Task<Encounter> GetByIdAsync(Guid id)
@@ -194,4 +199,89 @@ public class EncounterService : IEncounterService
return new { encounterId, events = timeline };
}
public async Task<Encounter> OpenOrUpdateByIdentifierAsync(FhirEncounterUpsertRequest req)
{
var existingId = await _identifiers.ResolveInternalIdAsync(
ExternalResourceType.Encounter, req.IdentifierSystem, req.IdentifierValue);
if (existingId.HasValue)
{
var existingEncounter = await _db.Encounters
.Include(e => e.Patient)
.FirstOrDefaultAsync(e => e.Id == existingId.Value)
?? throw new NotFoundException("Encounter not found.", "ENCOUNTER_NOT_FOUND");
existingEncounter.Department = req.Department;
existingEncounter.AttendingPhysician = req.AttendingPhysician;
existingEncounter.RoomBed = req.RoomBed;
existingEncounter.AdmissionReason = req.AdmissionReason;
if (existingEncounter.Status != req.TargetStatus)
{
await TransitionStatusAsync(
existingEncounter.Id, req.TargetStatus, req.DischargeDiagnosis);
await _db.Entry(existingEncounter).ReloadAsync();
}
return existingEncounter;
}
// Create new encounter — mirrors PatientService.OpenEncounterAsync
var patient = await _db.Patients.FindAsync(req.PatientId);
if (patient is null)
throw new NotFoundException("Patient not found.", "PATIENT_NOT_FOUND");
var encounter = new Encounter
{
Id = Guid.NewGuid(),
PatientId = req.PatientId,
EncounterType = req.EncounterType,
Status = EncounterStatus.Active,
Department = req.Department,
AttendingPhysician = req.AttendingPhysician,
RoomBed = req.RoomBed,
AdmissionReason = req.AdmissionReason,
AdmittedAt = req.AdmittedAt ?? DateTimeOffset.UtcNow,
CreatedAt = DateTimeOffset.UtcNow
};
_db.Encounters.Add(encounter);
_db.OutboxEvents.Add(new OutboxEvent
{
Id = Guid.NewGuid(),
Topic = "encounter.status.changed",
Payload = JsonSerializer.Serialize(new
{
encounterId = encounter.Id,
patientId = patient.Id,
mrn = patient.Mrn,
patientName = $"{patient.FirstName} {patient.LastName}",
previousStatus = (string?)null,
newStatus = encounter.Status.ToDbString(),
department = encounter.Department.ToDbString(),
attendingPhysician = encounter.AttendingPhysician,
roomBed = encounter.RoomBed,
admissionReason = encounter.AdmissionReason,
admittedAt = encounter.AdmittedAt,
changedAt = DateTimeOffset.UtcNow
}),
PartitionKey = encounter.Id.ToString(),
CreatedAt = DateTimeOffset.UtcNow
});
await _db.SaveChangesAsync();
await _identifiers.LinkAsync(
ExternalResourceType.Encounter,
encounter.Id,
req.IdentifierSystem,
req.IdentifierValue);
if (req.TargetStatus == EncounterStatus.Discharged)
await TransitionStatusAsync(encounter.Id, EncounterStatus.Discharged, req.DischargeDiagnosis);
return encounter;
}
}