fix: Missing input validators + No patient update endpoint + Pagination inconsistencies + Missing list/get endpoints
This commit is contained in:
@@ -195,7 +195,7 @@ public class AnalyticsService : IAnalyticsService
|
||||
b.Filter(filters.ToArray());
|
||||
})
|
||||
)
|
||||
.From(page * pageSize)
|
||||
.From((page - 1) * pageSize)
|
||||
.Size(pageSize));
|
||||
|
||||
if (!resp.IsValidResponse)
|
||||
|
||||
@@ -4,5 +4,6 @@ public interface IPatientService
|
||||
Task<PagedResult<Patient>> ListAsync(string? q, int page, int pageSize);
|
||||
Task<Patient> GetByIdAsync(Guid id);
|
||||
Task<Encounter> OpenEncounterAsync(Guid patientId, OpenEncounterRequest req);
|
||||
Task<Patient> UpdateAsync(Guid id, UpdatePatientRequest req);
|
||||
Task<Patient> RegisterOrUpdateByIdentifierAsync(FhirPatientUpsertRequest req);
|
||||
}
|
||||
@@ -2,4 +2,5 @@ public interface IQsofaService
|
||||
{
|
||||
Task<QsofaCurrentResponse> GetCurrentAsync(Guid encounterId);
|
||||
Task<int> GetActiveCriteriaCountAsync(Guid encounterId);
|
||||
Task<CursorPage<ClinicalAlert>> GetHistoryAsync(Guid encounterId, int limit, string? cursor);
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ public interface ISepsisBundleService
|
||||
Guid encounterId, Guid triggeringAlertId, AlertType alertType, CancellationToken ct = default);
|
||||
Task<SepsisBundle?> GetCurrentByEncounterAsync(Guid encounterId);
|
||||
Task<SepsisBundle> GetByIdAsync(Guid id);
|
||||
Task<PagedResult<SepsisBundle>> ListAsync(SepsisBundleComplianceStatus? status, int page, int pageSize);
|
||||
Task OnOrderResultedAsync(Guid orderId, CancellationToken ct = default);
|
||||
}
|
||||
@@ -90,6 +90,44 @@ public class PatientService : IPatientService
|
||||
return new PagedResult<Patient>(patients, page, pageSize, total);
|
||||
}
|
||||
|
||||
public async Task<Patient> UpdateAsync(Guid id, UpdatePatientRequest req)
|
||||
{
|
||||
var patient = await _db.Patients.FindAsync(id)
|
||||
?? throw new NotFoundException("Patient not found.", "PATIENT_NOT_FOUND");
|
||||
|
||||
var before = new
|
||||
{
|
||||
patient.FirstName, patient.LastName, patient.DateOfBirth,
|
||||
patient.Gender, patient.BloodType, patient.Allergies,
|
||||
patient.EmergencyContactName, patient.EmergencyContactPhone
|
||||
};
|
||||
|
||||
if (req.FirstName is not null) patient.FirstName = req.FirstName;
|
||||
if (req.LastName is not null) patient.LastName = req.LastName;
|
||||
if (req.DateOfBirth is not null) patient.DateOfBirth = req.DateOfBirth.Value;
|
||||
if (req.Gender is not null) patient.Gender = req.Gender;
|
||||
if (req.BloodType is not null) patient.BloodType = req.BloodType;
|
||||
if (req.Allergies is not null) patient.Allergies = req.Allergies;
|
||||
if (req.EmergencyContactName is not null) patient.EmergencyContactName = req.EmergencyContactName;
|
||||
if (req.EmergencyContactPhone is not null) patient.EmergencyContactPhone = req.EmergencyContactPhone;
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
await _audit.WriteAsync(
|
||||
AuditAction.PatientUpdated,
|
||||
"Patient",
|
||||
patient.Id,
|
||||
previousValue: before,
|
||||
newValue: new
|
||||
{
|
||||
patient.FirstName, patient.LastName, patient.DateOfBirth,
|
||||
patient.Gender, patient.BloodType, patient.Allergies,
|
||||
patient.EmergencyContactName, patient.EmergencyContactPhone
|
||||
});
|
||||
|
||||
return patient;
|
||||
}
|
||||
|
||||
public async Task<Patient> GetByIdAsync(Guid id)
|
||||
{
|
||||
var patient = await _db.Patients
|
||||
|
||||
@@ -26,6 +26,28 @@ public class QsofaService : IQsofaService
|
||||
return QsofaCalculator.CountActiveCriteria(values);
|
||||
}
|
||||
|
||||
public async Task<CursorPage<ClinicalAlert>> GetHistoryAsync(
|
||||
Guid encounterId, int limit, string? cursor)
|
||||
{
|
||||
limit = Math.Clamp(limit, 1, 100);
|
||||
|
||||
var query = _db.ClinicalAlerts
|
||||
.AsNoTracking()
|
||||
.Where(a => a.EncounterId == encounterId
|
||||
&& (a.AlertType == AlertType.QsofaScreen || a.AlertType == AlertType.QsofaWarning));
|
||||
|
||||
var items = await query
|
||||
.OrderByDescending(a => a.TriggeredAt)
|
||||
.ThenByDescending(a => a.Id)
|
||||
.Take(limit + 1)
|
||||
.ToListAsync();
|
||||
|
||||
var hasMore = items.Count > limit;
|
||||
if (hasMore) items.RemoveAt(limit);
|
||||
|
||||
return new CursorPage<ClinicalAlert>(items, null, hasMore);
|
||||
}
|
||||
|
||||
private async Task EnsureEncounterExistsAsync(Guid encounterId)
|
||||
{
|
||||
var exists = await _db.Encounters.AnyAsync(e => e.Id == encounterId);
|
||||
|
||||
@@ -122,6 +122,29 @@ public class SepsisBundleService : ISepsisBundleService
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public async Task<PagedResult<SepsisBundle>> ListAsync(
|
||||
SepsisBundleComplianceStatus? status, int page, int pageSize)
|
||||
{
|
||||
pageSize = Math.Clamp(pageSize, 1, 100);
|
||||
|
||||
var query = _db.SepsisBundles
|
||||
.AsNoTracking()
|
||||
.Include(b => b.Elements)
|
||||
.AsQueryable();
|
||||
|
||||
if (status.HasValue)
|
||||
query = query.Where(b => b.ComplianceStatus == status.Value);
|
||||
|
||||
var total = await query.CountAsync();
|
||||
var items = await query
|
||||
.OrderByDescending(b => b.RecognizedAt)
|
||||
.Skip((page - 1) * pageSize)
|
||||
.Take(pageSize)
|
||||
.ToListAsync();
|
||||
|
||||
return new PagedResult<SepsisBundle>(items, page, pageSize, total);
|
||||
}
|
||||
|
||||
public async Task OnOrderResultedAsync(Guid orderId, CancellationToken ct = default)
|
||||
{
|
||||
var element = await _db.SepsisBundleElements
|
||||
|
||||
Reference in New Issue
Block a user