fix: Missing input validators + No patient update endpoint + Pagination inconsistencies + Missing list/get endpoints

This commit is contained in:
voltsrage
2026-06-21 19:13:13 +08:00
parent 33895122d1
commit a37fad0e57
26 changed files with 932 additions and 22 deletions
@@ -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