1135 lines
55 KiB
C#
1135 lines
55 KiB
C#
using adas_core.Application.Repositories.Interfaces;
|
|
using adas_core.Domain.Enums;
|
|
using adas_core.Domain.Models;
|
|
using adas_core.Domain.Models.AppSettings;
|
|
using adas_core.Domain.Models.DTO;
|
|
using adas_core.Domain.Models.Filter;
|
|
using adas_core.Domain.Models.Masters;
|
|
using adas_core.Domain.Models.MongoModels;
|
|
using Microsoft.Extensions.Options;
|
|
using MongoDB.Bson;
|
|
using MongoDB.Driver;
|
|
using Serilog;
|
|
using System.Text.RegularExpressions;
|
|
using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
|
|
|
|
namespace adas_core.Infrastructure.Repositories;
|
|
|
|
/// <summary>
|
|
/// Provides a MongoDB-backed repository implementation for managing patient data, exposing patient-specific data access operations defined by the IPatientRepository contract.
|
|
/// </summary>
|
|
public class PatientRepository : MongoRepository<Patient>, IPatientRepository
|
|
{
|
|
private readonly ApiSettings _apiSettings;
|
|
|
|
public PatientRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
|
|
{
|
|
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
|
|
_apiSettings = apiSettings.Value;
|
|
} //For testing
|
|
|
|
// public PatientRepository(IOptions<ApiSettings> apiSettings, IOptions<DatabaseSettings> dbSetting) :
|
|
// base(dbSetting)
|
|
// {
|
|
// if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
|
|
// _apiSettings = apiSettings.Value;
|
|
// }
|
|
|
|
/// <summary>
|
|
/// Gets the collection name for patients, returning the configured value from API settings
|
|
/// or falling back to the default "patients" if the setting is null.
|
|
/// </summary>
|
|
/// <returns>The configured patients collection name, or "patients" as a default when the setting is not set.</returns>
|
|
public override string GetCollectionName()
|
|
{
|
|
return _apiSettings.Patients ?? "patients";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously finds a patient by their unique identifier in the underlying collection.
|
|
/// Returns <c>null</c> if no matching patient is found or if an error occurs while querying, with the error being logged.
|
|
/// </summary>
|
|
/// <param name="id">The unique identifier of the patient to locate.</param>
|
|
/// <returns>A <see cref="Patient"/> instance matching the provided id, or <c>null</c> if not found or on error.</returns>
|
|
public async Task<Patient?> FindById(ObjectId id)
|
|
{
|
|
try
|
|
{
|
|
var filter = Builders<Patient>.Filter.Eq(p => p.Id, id);
|
|
var result = await Collection.FindAsync(filter);
|
|
|
|
return await result.FirstOrDefaultAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error("Error searching patient by id: {id}. Exception: {ex}", id, ex);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves a <see cref="Patient"/> matching the specified point of care identifier.
|
|
/// Returns the first matching patient, or <c>null</c> if no patient is found or an error occurs while querying the data store.
|
|
/// </summary>
|
|
/// <param name="id">The point of care identifier used to locate the patient.</param>
|
|
/// <returns>A <see cref="Patient"/> instance if a match is found; otherwise, <c>null</c>.</returns>
|
|
public async Task<Patient?> FindByPointOfCareId(ObjectId id)
|
|
{
|
|
try
|
|
{
|
|
var filter = Builders<Patient>.Filter.Eq(p => p.PointOfCareId, id);
|
|
var result = await Collection.FindAsync(filter);
|
|
|
|
return await result.FirstOrDefaultAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error("Error searching patient by id: {id}. Exception: {ex}", id, ex);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds a patient by the specified unit identifier and point of care identifier.
|
|
/// Returns the first matching patient, or null if no match is found or if an error occurs during the search.
|
|
/// </summary>
|
|
/// <param name="unit">The identifier of the unit to filter by.</param>
|
|
/// <param name="pointOfCare">The identifier of the point of care to filter by.</param>
|
|
/// <returns>The first matching <see cref="Patient"/>, or null if no patient is found or an error is encountered.</returns>
|
|
public async Task<Patient> FindByUnitAndPocId(ObjectId unit, ObjectId pointOfCare)
|
|
{
|
|
try
|
|
{
|
|
var filter = Builders<Patient>.Filter.And(
|
|
Builders<Patient>.Filter.Eq(p => p.PointOfCareId, pointOfCare),
|
|
Builders<Patient>.Filter.Eq(p => p.UnitId, unit)
|
|
);
|
|
var result = await Collection.FindAsync(filter);
|
|
|
|
return await result.FirstOrDefaultAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error("Error searching patient by unitId: {unitid} pointOfCareid: {id}. Exception: {ex}", unit,
|
|
pointOfCare, ex);
|
|
return null!;
|
|
}
|
|
}
|
|
|
|
//Deprecated
|
|
/// <summary>
|
|
/// Finds a patient based on the provided location information. Returns null if the location is null or no matching patient is found. When both unit name and bed are specified, the search filters by both criteria; otherwise, it falls back to filtering by bed only, or returns the first available patient if neither is provided.
|
|
/// </summary>
|
|
/// <param name="location">The location information used to locate the patient. Can be null.</param>
|
|
/// <returns>A <see cref="Patient"/> matching the location criteria, or null if no match is found or the location is null.</returns>
|
|
public async Task<Patient?> FindByLocation(PatientLocation? location)
|
|
{
|
|
if (location == null)
|
|
return null;
|
|
// Primero encontrar la unidad
|
|
|
|
var filterBuilder = Builders<Patient>.Filter;
|
|
var filter = filterBuilder.Empty;
|
|
|
|
if (!string.IsNullOrEmpty(location.UnitName) && !string.IsNullOrEmpty(location.Bed))
|
|
filter = filterBuilder.And(
|
|
filterBuilder.Eq(p => p.UnitString, location.UnitName),
|
|
filterBuilder.Eq(p => p.Bed, location.Bed)
|
|
);
|
|
else if (!string.IsNullOrEmpty(location.Bed)) filter = filterBuilder.Eq(p => p.Bed, location.Bed);
|
|
|
|
var result = await Collection.Find(filter).Limit(1).FirstOrDefaultAsync();
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Asynchronously inserts a new <see cref="Patient"/> record, setting its creation date to the current UTC time. If the insertion fails but an existing patient with the same patient number is found at the same location, the exception is logged as a warning; otherwise, the error is logged and rethrown.
|
|
/// </summary>
|
|
/// <param name="patient">The <see cref="Patient"/> entity to insert into the data store.</param>
|
|
public override async Task InsertOneAsync(Patient patient)
|
|
{
|
|
try
|
|
{
|
|
patient.CreationDate = DateTime.UtcNow;
|
|
await base.InsertOneAsync(patient);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Console.WriteLine(e);
|
|
|
|
var patientAux = await FindByLocation(patient.Location);
|
|
if (patientAux != null && patientAux.PatientNumber == patient.PatientNumber)
|
|
{
|
|
Log.Warning("Exception trying to insert an existing patient: {patient}. Exception {e}", patient, e);
|
|
}
|
|
else
|
|
{
|
|
Log.Error("Exception trying to insert patient: {patient}. Exception {e}", patient, e);
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates an existing patient record, refreshing the update timestamp to the current UTC time before persisting the changes.
|
|
/// </summary>
|
|
/// <param name="patient">The patient entity containing the updated information to be saved.</param>
|
|
public async Task Update(Patient patient)
|
|
{
|
|
patient.UpdateDate = DateTime.UtcNow;
|
|
await UpdateOneAsync(patient.Id, patient);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes a patient from the collection that matches the specified identifier.
|
|
/// </summary>
|
|
/// <param name="id">The unique identifier of the patient to remove.</param>
|
|
public async Task Delete(ObjectId id)
|
|
{
|
|
var filter = Builders<Patient>.Filter.Eq(x => x.Id, id);
|
|
await Collection.DeleteOneAsync(filter, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the location of an existing patient. If no patient is found for the given identifier, the operation is skipped and a warning is logged.
|
|
/// </summary>
|
|
/// <param name="id">The unique identifier of the patient whose location will be updated.</param>
|
|
/// <param name="location">The new location to assign to the patient.</param>
|
|
public async Task UpdateLocation(ObjectId id, PatientLocation location)
|
|
{
|
|
var patient = await FindById(id);
|
|
if (patient == null)
|
|
{
|
|
Log.Warning("Patient not found for updating location: {id}", id);
|
|
return;
|
|
}
|
|
|
|
patient.UpdateDate = DateTime.UtcNow;
|
|
patient.Location = location;
|
|
await UpdateOneAsync(patient.Id, patient);
|
|
/*var filterBuilder = Builders<Patient>.Filter;
|
|
var filter = filterBuilder.Eq(p => p.Id, id);
|
|
|
|
var update = Builders<Patient>.Update
|
|
.Set(p => p.Bed, location?.Bed)
|
|
.Set(p => p.UnitString, location?.UnitName)
|
|
.Set(p => p.UpdateDate, DateTime.UtcNow)
|
|
.AddToSet(p => p.HistoricalLocations, new KeyValuePair<string, PatientLocation>(DateTime.UtcNow.ToString("o"), location));
|
|
|
|
var result = await Collection.UpdateOneAsync(filter, update);
|
|
|
|
Log.Debug("Update location result: {result}", result);*/
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the point of care (location) for the specified patient, also refreshing the modification timestamp to the current UTC time.
|
|
/// If no patient is found for the given identifier, a warning is logged and the method returns without making any changes.
|
|
/// </summary>
|
|
/// <param name="id">The unique identifier of the patient whose location will be updated.</param>
|
|
/// <param name="location">The new point of care (location) identifier to assign to the patient.</param>
|
|
public async Task UpdateLocation(ObjectId id, ObjectId location)
|
|
{
|
|
var patient = await FindById(id);
|
|
if (patient == null)
|
|
{
|
|
Log.Warning("Patient not found for updating location: {id}", id);
|
|
return;
|
|
}
|
|
|
|
patient.UpdateDate = DateTime.UtcNow;
|
|
patient.PointOfCareId = location;
|
|
await UpdateOneAsync(patient.Id, patient);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the attending doctor of a patient identified by the specified identifier and refreshes the update timestamp to the current UTC date and time.
|
|
/// </summary>
|
|
/// <param name="id">The unique identifier of the patient whose attending doctor is being updated.</param>
|
|
/// <param name="attendingDoctor">The new attending doctor to assign to the patient.</param>
|
|
public async Task UpdateAttendingDoctor(ObjectId id, Person attendingDoctor)
|
|
{
|
|
var update = Builders<Patient>.Update
|
|
.Set(p => p.AttendingDoctor, attendingDoctor)
|
|
.Set(p => p.UpdateDate, DateTime.UtcNow);
|
|
|
|
await Collection.UpdateOneAsync(p => p.Id == id, update);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the personal data and update timestamp of an existing patient identified by the supplied id.
|
|
/// When <paramref name="updatePatientNumber"/> is true (the default), the patient number is also updated; otherwise only the person data and update date are persisted.
|
|
/// </summary>
|
|
/// <param name="id">The unique identifier of the patient to update.</param>
|
|
/// <param name="patientNumber">The new patient number to apply when <paramref name="updatePatientNumber"/> is true.</param>
|
|
/// <param name="data">The new <see cref="Person"/> information to store for the patient.</param>
|
|
/// <param name="updatePatientNumber">Indicates whether the patient number should be updated as part of the operation; defaults to true.</param>
|
|
public async Task UpdatePatientData(ObjectId id, string patientNumber, Person data, bool updatePatientNumber = true)
|
|
{
|
|
var filterBuilder = Builders<Patient>.Filter;
|
|
var updateBuilder = Builders<Patient>.Update.Set(p => p.Person, data).Set(p => p.UpdateDate, DateTime.UtcNow);
|
|
|
|
var filter = filterBuilder.Eq(p => p.Id, id);
|
|
var update = updateBuilder;
|
|
|
|
if (updatePatientNumber) update = update.Set(p => p.PatientNumber, patientNumber);
|
|
|
|
await Collection.UpdateOneAsync(filter, update);
|
|
}
|
|
|
|
|
|
public async Task<Patient?> FindByPatientNumber(string patientNumber)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(patientNumber)) return null;
|
|
|
|
//Último paciente admitido
|
|
var patient = await Collection.Find(Builders<Patient>.Filter.And(
|
|
Builders<Patient>.Filter.Eq(p => p.DisTime, null),
|
|
Builders<Patient>.Filter.Eq(p => p.PatientNumber, patientNumber)
|
|
))
|
|
.Sort(Builders<Patient>.Sort.Descending(p => p.AdmTime))
|
|
.Limit(1)
|
|
.FirstOrDefaultAsync();
|
|
|
|
|
|
//Último con fecha de alta más reciente
|
|
patient ??= await Collection.Find(Builders<Patient>.Filter.And(
|
|
Builders<Patient>.Filter.Ne(p => p.DisTime, null),
|
|
Builders<Patient>.Filter.Eq(p => p.PatientNumber, patientNumber)
|
|
))
|
|
.Sort(Builders<Patient>.Sort.Descending(p => p.DisTime))
|
|
.Limit(1)
|
|
.FirstOrDefaultAsync();
|
|
|
|
return patient;
|
|
}
|
|
|
|
public async Task<Patient?> SearchByPatientNumberAndDistinctUnit(string patientNumber, ObjectId unitId)
|
|
{
|
|
try
|
|
{
|
|
if (string.IsNullOrWhiteSpace(patientNumber)) return null;
|
|
|
|
// Paciente ubicado en un PoC pero en diferente unidad
|
|
var patient = await Collection.Find(Builders<Patient>.Filter.And(
|
|
Builders<Patient>.Filter.Eq(p => p.PatientNumber, patientNumber),
|
|
Builders<Patient>.Filter.Ne(p => p.UnitId, unitId)
|
|
)).ToListAsync();
|
|
// Si hay mas de una coincidencia devolvemos null por que puede no estar completo el patientNumber
|
|
return patient.Count > 1 ? null : patient.FirstOrDefault();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Error(
|
|
"Error Search By Patient Number And Distinct Unit on patient repository patientNumber: {patientNumber}, unitId: {unitId}, Excepción: {e}",
|
|
patientNumber, unitId, e.Message);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async Task<List<Patient>> FindAllPatientWithFinishedProcedures(int archiveProcedureEndDateAfterMinutes)
|
|
{
|
|
var currentDateTime = DateTime.UtcNow;
|
|
|
|
// Filtro para documentos que contienen al menos un procedimiento con opción "procedure"
|
|
var filter = Builders<Patient>.Filter.ElemMatch(
|
|
x => x.Procedures,
|
|
procedure =>
|
|
//procedure.OptionType == "procedure" &&
|
|
procedure.EndDate.HasValue
|
|
);
|
|
|
|
// Ejecutar la consulta inicial y traer los documentos
|
|
var patientsWithProcedures = await Collection.Find(filter).ToListAsync();
|
|
|
|
// Aplicar el filtro adicional en memoria
|
|
var patientsWithFinishedProcedures = patientsWithProcedures.Where(patient =>
|
|
patient.Procedures != null &&
|
|
patient.Procedures.Any(procedure =>
|
|
procedure is
|
|
{
|
|
//OptionType: "procedure",
|
|
EndDate: not null
|
|
} &&
|
|
procedure.EndDate.Value.AddMinutes(archiveProcedureEndDateAfterMinutes) < currentDateTime
|
|
)
|
|
).ToList();
|
|
|
|
return patientsWithFinishedProcedures;
|
|
}
|
|
|
|
public async Task<List<Patient>> FindAllPatientWithFinishedTests(int archiveTestEndDateAfterMinutes)
|
|
{
|
|
var currentDateTime = DateTime.UtcNow;
|
|
|
|
// Filtro para documentos que contienen al menos un procedimiento con opción "procedure"
|
|
var filter = Builders<Patient>.Filter.ElemMatch(
|
|
x => x.Tests,
|
|
procedure => //procedure.OptionType == "test" &&
|
|
procedure.EndDate.HasValue
|
|
);
|
|
|
|
// Ejecutar la consulta inicial y traer los documentos
|
|
var patientsWithTests = await Collection.Find(filter).ToListAsync();
|
|
|
|
// Aplicar el filtro adicional en memoria
|
|
var patientsWithFinishedTests = patientsWithTests.Where(patient =>
|
|
patient.Tests != null &&
|
|
patient.Tests.Any(procedure =>
|
|
procedure is
|
|
{
|
|
//OptionType: "test",
|
|
EndDate: not null
|
|
} &&
|
|
procedure.EndDate.Value.AddMinutes(archiveTestEndDateAfterMinutes) < currentDateTime
|
|
)
|
|
).ToList();
|
|
|
|
return patientsWithFinishedTests;
|
|
}
|
|
|
|
public async Task<List<Patient>> FindAllPatientWithFinishedTreatment(int archiveTreatmentEndDateAfterMinutes)
|
|
{
|
|
var currentDateTime = DateTime.UtcNow;
|
|
|
|
// Filtro para documentos que contienen al menos un procedimiento con opción "procedure"
|
|
var filter = Builders<Patient>.Filter.ElemMatch(
|
|
x => x.Treatment,
|
|
treatment => treatment.EndDate.HasValue
|
|
);
|
|
|
|
// Ejecutar la consulta inicial y traer los documentos
|
|
var patientsWithTreatment = await Collection.Find(filter).ToListAsync();
|
|
|
|
// Aplicar el filtro adicional en memoria
|
|
var patientsWithFinishedTreatments = patientsWithTreatment.Where(patient =>
|
|
patient.Treatment != null &&
|
|
patient.Treatment.Any(treatment =>
|
|
treatment.EndDate.HasValue &&
|
|
treatment.EndDate.Value.AddMinutes(archiveTreatmentEndDateAfterMinutes) < currentDateTime
|
|
)
|
|
).ToList();
|
|
|
|
return patientsWithFinishedTreatments;
|
|
}
|
|
|
|
public async Task<List<Patient>> UpdateMasterListOption(List<ObjectId> unitIds, UpdateOptionMasterListDto opt,
|
|
string typeName)
|
|
{
|
|
var isParsed = Enum.TryParse<MasterListType>(typeName, out var parsedTypeName);
|
|
if (isParsed)
|
|
{
|
|
// Tener en cuenta los datos auxiliares ya que pueden ser texto libre o asignarse el establecido en la lista
|
|
// originAux / diagnosisAux modificar en caso de ser el mismo que el padre
|
|
// Notificar a todos los fronts con el nuevo valor de cada paciente por el id de los poc's afectados(?)
|
|
var filterUnit = Builders<Patient>.Filter.In("unitId", unitIds);
|
|
switch (parsedTypeName)
|
|
{
|
|
case MasterListType.DiagnosisList:
|
|
|
|
var diagnosisFilter = Builders<Patient>.Filter.Eq(
|
|
"diagnosis.name", opt.OldOption?.Name
|
|
);
|
|
var filterUpdateDiagnosis = Builders<Patient>.Filter.And(filterUnit, diagnosisFilter);
|
|
var updateDiagnosis = Builders<Patient>.Update
|
|
.Set("diagnosis.name", opt.UpdatedOption?.Name)
|
|
.Set("diagnosis.description", opt.UpdatedOption?.Description);
|
|
await Collection.UpdateManyAsync(filterUpdateDiagnosis, updateDiagnosis);
|
|
|
|
await Collection.UpdateManyAsync(Builders<Patient>.Filter.And(filterUnit,
|
|
Builders<Patient>.Filter.Eq(
|
|
"diagnosisAux", opt.OldOption?.Name
|
|
)), Builders<Patient>.Update
|
|
.Set("diagnosisAux", opt.UpdatedOption?.Name));
|
|
|
|
// Filtro para devolver los documentos actualizados (OR entre diagnosis.name y diagnosisAux)
|
|
var diagnosisFilterToReturn =
|
|
Builders<Patient>.Filter.Eq("diagnosis.name", opt.UpdatedOption?.Name);
|
|
var diagnosisAuxFilterToReturn =
|
|
Builders<Patient>.Filter.Eq("diagnosisAux", opt.UpdatedOption?.Name);
|
|
var filterToReturn = Builders<Patient>.Filter.And(
|
|
filterUnit,
|
|
Builders<Patient>.Filter.Or(diagnosisFilterToReturn, diagnosisAuxFilterToReturn)
|
|
);
|
|
|
|
// Devolver los documentos actualizados
|
|
var updatedDocuments = await Collection.Find(filterToReturn).ToListAsync();
|
|
return updatedDocuments;
|
|
case MasterListType.DoctorList:
|
|
// Filtro para encontrar el elemento en el array `doctors` que coincida con el nombre
|
|
var doctorFilter = Builders<Patient>.Filter.ElemMatch(
|
|
"doctors",
|
|
Builders<Patient>.Filter.Eq("name", opt.OldOption?.Name)
|
|
);
|
|
var filterUpdateDoctor = Builders<Patient>.Filter.And(filterUnit, doctorFilter);
|
|
|
|
// Actualizar todos los elementos de la lista `doctors` que coincidan
|
|
var updateDoctor = Builders<Patient>.Update.Set(
|
|
"doctors.$[nameElem].name", opt.UpdatedOption?.Name
|
|
);
|
|
|
|
// Definir filtros únicos para cada campo utilizado
|
|
var arrayFilters = new List<ArrayFilterDefinition>
|
|
{
|
|
new BsonDocumentArrayFilterDefinition<BsonDocument>(
|
|
new BsonDocument("nameElem.name", opt.OldOption?.Name))
|
|
};
|
|
|
|
// Agregar filtro para `optionType` solo si es necesario
|
|
if (!string.IsNullOrEmpty(opt.OldOption?.OptionType))
|
|
{
|
|
updateDoctor = updateDoctor.Set(
|
|
"doctors.$[typeElem].optionType", opt.UpdatedOption?.OptionType
|
|
);
|
|
|
|
arrayFilters.Add(
|
|
new BsonDocumentArrayFilterDefinition<BsonDocument>(
|
|
new BsonDocument("typeElem.optionType", opt.OldOption.OptionType)));
|
|
}
|
|
|
|
var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters };
|
|
|
|
// Ejecutar la actualización
|
|
await Collection.UpdateManyAsync(filterUpdateDoctor, updateDoctor, updateOptions);
|
|
|
|
// Devolver los documentos actualizados
|
|
var doctorFilterToReturn = Builders<Patient>.Filter.ElemMatch(
|
|
"doctors",
|
|
Builders<Patient>.Filter.Eq("name", opt.UpdatedOption?.Name)
|
|
);
|
|
|
|
var filterToReturnDoctor = Builders<Patient>.Filter.And(filterUnit, doctorFilterToReturn);
|
|
|
|
var updatedDoctorDocuments = await Collection.Find(filterToReturnDoctor).ToListAsync();
|
|
|
|
return updatedDoctorDocuments;
|
|
case MasterListType.InsulationList:
|
|
break;
|
|
case MasterListType.OriginList:
|
|
var originFilter = Builders<Patient>.Filter.Eq(
|
|
"origin.name", opt.OldOption?.Name
|
|
);
|
|
var filterUpdateOrigin = Builders<Patient>.Filter.And(filterUnit, originFilter);
|
|
var updateOrigin = Builders<Patient>.Update
|
|
.Set("origin.name", opt.UpdatedOption?.Name);
|
|
await Collection.UpdateManyAsync(filterUpdateOrigin, updateOrigin);
|
|
|
|
await Collection.UpdateManyAsync(Builders<Patient>.Filter.And(filterUnit,
|
|
Builders<Patient>.Filter.Eq(
|
|
"originAux", opt.OldOption?.Name
|
|
)), Builders<Patient>.Update
|
|
.Set("originAux", opt.UpdatedOption?.Name));
|
|
|
|
// Filtro para devolver los documentos actualizados (OR entre diagnosis.name y diagnosisAux)
|
|
var originFilterToReturn = Builders<Patient>.Filter.Eq("origin.name", opt.UpdatedOption?.Name);
|
|
var originAuxFilterToReturn = Builders<Patient>.Filter.Eq("originAux", opt.UpdatedOption?.Name);
|
|
var originfilterToReturn = Builders<Patient>.Filter.And(
|
|
filterUnit,
|
|
Builders<Patient>.Filter.Or(originFilterToReturn, originAuxFilterToReturn)
|
|
);
|
|
|
|
// Devolver los documentos actualizados
|
|
var updatedDocumentsorigin = await Collection.Find(originfilterToReturn).ToListAsync();
|
|
return updatedDocumentsorigin;
|
|
|
|
case MasterListType.DoctorTypeList:
|
|
case MasterListType.AllergyList:
|
|
case MasterListType.DestinationList:
|
|
case MasterListType.ProcedureList:
|
|
case MasterListType.ServiceList:
|
|
case MasterListType.TreatmentList:
|
|
case MasterListType.AltableOptionList:
|
|
case MasterListType.DischargeStatusList:
|
|
case MasterListType.InternalDestinationList:
|
|
case MasterListType.LanguageBarrierList:
|
|
case MasterListType.MobilityOptionList:
|
|
case MasterListType.PassiveSittingList:
|
|
case MasterListType.PatientStatusList:
|
|
case MasterListType.TherapeuticCeilingList:
|
|
case MasterListType.VisitOptionList:
|
|
case MasterListType.AccessControlList:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves a list of patients whose unit identifier is contained in the specified collection of unit identifiers.
|
|
/// Validates the provided type name against the MasterListType enumeration; if the type name cannot be parsed, an empty list is returned.
|
|
/// </summary>
|
|
/// <param name="unitIds">The list of unit identifiers used to filter patients.</param>
|
|
/// <param name="typeName">The name of the type to validate against the MasterListType enumeration.</param>
|
|
/// <returns>A task that represents the asynchronous operation. The task result contains a list of Patient objects matching the specified unit identifiers, or an empty list if the type name is invalid.</returns>
|
|
public async Task<List<Patient>> GetPatientsByUnitIds(List<ObjectId> unitIds, string typeName)
|
|
{
|
|
var isParsed = Enum.TryParse<MasterListType>(typeName, out _);
|
|
if (!isParsed) return [];
|
|
|
|
var filterUnit = Builders<Patient>.Filter.In("unitId", unitIds);
|
|
return await Collection.Find(filterUnit).ToListAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously counts the number of patients associated with the specified unit identifier.
|
|
/// Returns 0 and logs the error if an exception occurs during the operation.
|
|
/// </summary>
|
|
/// <param name="unitId">The unit identifier used to filter patients.</param>
|
|
/// <returns>The number of patients matching the specified unit identifier, or 0 if an error occurs.</returns>
|
|
public async Task<long> CountByUnitId(ObjectId unitId)
|
|
{
|
|
try
|
|
{
|
|
var filter = Builders<Patient>.Filter.Eq(p => p.UnitId, unitId);
|
|
return await Collection.CountDocumentsAsync(filter);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Logger.Error(e.Message);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
public async Task<IEnumerable<Patient>> DeleteMasterListOption(List<ObjectId> unitIds, OptionList opt,
|
|
string typeName)
|
|
{
|
|
var isParsed = Enum.TryParse<MasterListType>(typeName, out var parsedTypeName);
|
|
if (isParsed)
|
|
{
|
|
// Tener en cuenta los datos auxiliares ya que pueden ser texto libre o asignarse el establecido en la lista
|
|
// originAux / diagnosisAux modificar en caso de ser el mismo que el padre
|
|
// Notificar a todos los fronts con el nuevo valor de cada paciente por el id de los poc's afectados(?)
|
|
var filterUnit = Builders<Patient>.Filter.In("unitId", unitIds);
|
|
switch (parsedTypeName)
|
|
{
|
|
case MasterListType.DiagnosisList:
|
|
|
|
// Filtro para devolver los documentos actualizados (OR entre diagnosis.name y diagnosisAux)
|
|
var diagnosisFilterToReturn = Builders<Patient>.Filter.Eq("diagnosis.name", opt.Name);
|
|
var diagnosisAuxFilterToReturn = Builders<Patient>.Filter.Eq("diagnosisAux", opt.Name);
|
|
var filterToReturn = Builders<Patient>.Filter.And(
|
|
filterUnit,
|
|
Builders<Patient>.Filter.Or(diagnosisFilterToReturn, diagnosisAuxFilterToReturn)
|
|
);
|
|
|
|
// Devolver los documentos actualizados
|
|
var updatedDocuments = await Collection.Find(filterToReturn).ToListAsync();
|
|
|
|
var diagnosisFilter = Builders<Patient>.Filter.Eq(
|
|
"diagnosis.name", opt.Name
|
|
);
|
|
var filterUpdateDiagnosis = Builders<Patient>.Filter.And(filterUnit, diagnosisFilter);
|
|
var updateDiagnosis = Builders<Patient>.Update
|
|
.Set(x => x.Diagnosis, null);
|
|
await Collection.UpdateManyAsync(filterUpdateDiagnosis, updateDiagnosis);
|
|
|
|
await Collection.UpdateManyAsync(Builders<Patient>.Filter.And(filterUnit,
|
|
Builders<Patient>.Filter.Eq(
|
|
"diagnosisAux", opt.Name
|
|
)), Builders<Patient>.Update
|
|
.Set(x => x.DiagnosisAux, null));
|
|
|
|
|
|
return updatedDocuments;
|
|
case MasterListType.DoctorList:
|
|
// Devolvemos los datos
|
|
var doctorFilterToReturn = Builders<Patient>.Filter.ElemMatch(
|
|
"doctors",
|
|
Builders<Patient>.Filter.Eq("_id", opt.Id)
|
|
);
|
|
var filterToReturnDoctor = Builders<Patient>.Filter.And(filterUnit, doctorFilterToReturn);
|
|
var updatedDoctorDocuments = await Collection.Find(filterToReturnDoctor).ToListAsync();
|
|
|
|
// Filtro para encontrar el elemento en el array `doctors` que coincida con el nombre
|
|
var doctorFilter = Builders<Patient>.Filter.ElemMatch(
|
|
"doctors",
|
|
Builders<Patient>.Filter.Eq("_id", opt.Id)
|
|
);
|
|
var filterUpdateDoctor = Builders<Patient>.Filter.And(filterUnit, doctorFilter);
|
|
|
|
// Actualización del campo `name` en el array `doctors`
|
|
var updateDoctor = Builders<Patient>.Update.PullFilter(
|
|
"doctors", Builders<BsonDocument>.Filter.And(
|
|
Builders<BsonDocument>.Filter.Eq("_id", opt.Id)
|
|
));
|
|
|
|
// Ejecutar la actualización
|
|
await Collection.UpdateManyAsync(filterUpdateDoctor, updateDoctor);
|
|
|
|
return updatedDoctorDocuments;
|
|
case MasterListType.InsulationList:
|
|
break;
|
|
case MasterListType.OriginList:
|
|
// Filtro para devolver los documentos actualizados (OR entre diagnosis.name y diagnosisAux)
|
|
var originFilterToReturn = Builders<Patient>.Filter.Eq("origin.name", opt.Name);
|
|
var originAuxFilterToReturn = Builders<Patient>.Filter.Eq("originAux", opt.Name);
|
|
var filterOriginToReturn = Builders<Patient>.Filter.And(
|
|
filterUnit,
|
|
Builders<Patient>.Filter.Or(originFilterToReturn, originAuxFilterToReturn)
|
|
);
|
|
|
|
// Devolver los documentos actualizados
|
|
var updatedDocumentsOrigin = await Collection.Find(filterOriginToReturn).ToListAsync();
|
|
|
|
var originFilter = Builders<Patient>.Filter.Eq(
|
|
"origin.name", opt.Name
|
|
);
|
|
var filterUpdateOrigin = Builders<Patient>.Filter.And(filterUnit, originFilter);
|
|
var updateOrigin = Builders<Patient>.Update
|
|
.Set(x => x.Diagnosis, null);
|
|
await Collection.UpdateManyAsync(filterUpdateOrigin, updateOrigin);
|
|
|
|
await Collection.UpdateManyAsync(Builders<Patient>.Filter.And(filterUnit,
|
|
Builders<Patient>.Filter.Eq(
|
|
"originAux", opt.Name
|
|
)), Builders<Patient>.Update
|
|
.Set(x => x.OriginAux, null));
|
|
|
|
|
|
return updatedDocumentsOrigin;
|
|
|
|
case MasterListType.DoctorTypeList:
|
|
case MasterListType.AllergyList:
|
|
case MasterListType.DestinationList:
|
|
case MasterListType.ProcedureList:
|
|
case MasterListType.ServiceList:
|
|
case MasterListType.TreatmentList:
|
|
case MasterListType.AltableOptionList:
|
|
case MasterListType.DischargeStatusList:
|
|
case MasterListType.InternalDestinationList:
|
|
case MasterListType.LanguageBarrierList:
|
|
case MasterListType.MobilityOptionList:
|
|
case MasterListType.PassiveSittingList:
|
|
case MasterListType.PatientStatusList:
|
|
case MasterListType.TherapeuticCeilingList:
|
|
case MasterListType.VisitOptionList:
|
|
case MasterListType.AccessControlList:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return new List<Patient>();
|
|
}
|
|
|
|
public async Task<Patient?> FindByPatientId(string patientId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(patientId)) return null;
|
|
|
|
//Último paciente admitido
|
|
var patient = await Collection.Find(p => !p.DisTime.HasValue && patientId == p.PatientId)
|
|
.Sort(Builders<Patient>.Sort.Descending(p => p.AdmTime)).Limit(1).FirstOrDefaultAsync();
|
|
|
|
//Último con fecha de alta más reciente
|
|
patient ??= await Collection.Find(p => p.DisTime.HasValue && patientId == p.PatientId)
|
|
.Sort(Builders<Patient>.Sort.Descending(p => p.DisTime)).Limit(1).FirstOrDefaultAsync();
|
|
|
|
return patient;
|
|
}
|
|
|
|
public async Task<Patient?> FindByPatientId(ObjectId patientId)
|
|
{
|
|
//Último paciente admitido
|
|
var patient = await Collection.Find(p => !p.DisTime.HasValue && patientId == p.Id)
|
|
.Sort(Builders<Patient>.Sort.Descending(p => p.AdmTime)).Limit(1).FirstOrDefaultAsync();
|
|
|
|
//Último con fecha de alta más reciente
|
|
patient ??= await Collection.Find(p => p.DisTime.HasValue && patientId == p.Id)
|
|
.Sort(Builders<Patient>.Sort.Descending(p => p.DisTime)).Limit(1).FirstOrDefaultAsync();
|
|
|
|
return patient;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves all <see cref="Patient"/> records from the data store.
|
|
/// </summary>
|
|
/// <returns>A task that represents the asynchronous operation, containing a list of all patients.</returns>
|
|
public async Task<List<Patient>> FindAll()
|
|
{
|
|
return (await Collection.FindAsync(Builders<Patient>.Filter.Empty)).ToList();
|
|
//return (await Collection.FindAsync(_ => true)).ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously retrieves all patients associated with the specified point of care from the collection.
|
|
/// </summary>
|
|
/// <param name="pointOfCare">The unit or point of care used to filter the patients.</param>
|
|
/// <returns>A task containing a list of <see cref="Patient"/> objects whose unit matches the specified point of care; an empty list is returned when no matches are found.</returns>
|
|
public async Task<List<Patient>> FindByPointOfCare(string pointOfCare)
|
|
{
|
|
var filterBuilder = Builders<Patient>.Filter;
|
|
var filter = filterBuilder.Eq(pa => pa.UnitString, pointOfCare);
|
|
|
|
var result = await Collection.FindAsync(filter);
|
|
|
|
return await result.ToListAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously retrieves all patients associated with the specified point of care.
|
|
/// </summary>
|
|
/// <param name="pointOfCare">The identifier of the point of care used to filter the patient records.</param>
|
|
/// <returns>A task that represents the asynchronous operation, containing a list of patients matching the specified point of care.</returns>
|
|
public async Task<List<Patient>> FindByPointOfCare(ObjectId pointOfCare)
|
|
{
|
|
var filterBuilder = Builders<Patient>.Filter;
|
|
var filter = filterBuilder.Eq(pa => pa.PointOfCareId, pointOfCare);
|
|
|
|
var result = await Collection.FindAsync(filter);
|
|
|
|
return await result.ToListAsync();
|
|
}
|
|
|
|
public async Task<List<Patient>> FindInActivePoC()
|
|
{
|
|
var virtualPointOfCareValues = Enum.GetValues(typeof(VirtualPointOfCare)).Cast<VirtualPointOfCare>()
|
|
.Select(p => p.ToString())
|
|
.ToList();
|
|
|
|
//var filterBuilder = Builders<Patient>.Filter;
|
|
|
|
// Define el pipeline de agregación
|
|
var pipeline = new[]
|
|
{
|
|
new BsonDocument("$lookup", new BsonDocument
|
|
{
|
|
{ "from", "pointOfCares" }, // Colección de PointOfCare
|
|
{ "localField", "pointOfCareId" },
|
|
{ "foreignField", "_id" },
|
|
{ "as", "pointOfCareInfo" }
|
|
}),
|
|
new BsonDocument("$unwind", "$pointOfCareInfo"),
|
|
new BsonDocument("$match", new BsonDocument
|
|
{
|
|
{ "pointOfCareInfo.bed", new BsonDocument("$nin", new BsonArray(virtualPointOfCareValues)) }
|
|
})
|
|
};
|
|
|
|
var result = await Collection.Aggregate<Patient>(pipeline).ToListAsync();
|
|
return result;
|
|
}
|
|
|
|
|
|
public async Task<List<Patient>> FindInInactivePoC()
|
|
{
|
|
var virtualPointOfCareValues = Enum.GetValues(typeof(VirtualPointOfCare)).Cast<VirtualPointOfCare>()
|
|
.Select(p => p.ToString())
|
|
.ToList();
|
|
|
|
//var filterBuilder = Builders<Patient>.Filter;
|
|
|
|
// Define el pipeline de agregación
|
|
var pipeline = new[]
|
|
{
|
|
new BsonDocument("$lookup", new BsonDocument
|
|
{
|
|
{ "from", "pointOfCares" }, // Colección de PointOfCare
|
|
{ "localField", "pointOfCareId" },
|
|
{ "foreignField", "_id" },
|
|
{ "as", "pointOfCareInfo" }
|
|
}),
|
|
new BsonDocument("$unwind", "$pointOfCareInfo"),
|
|
new BsonDocument("$match", new BsonDocument
|
|
{
|
|
{ "pointOfCareInfo.bed", new BsonDocument("$in", new BsonArray(virtualPointOfCareValues)) }
|
|
})
|
|
};
|
|
|
|
var result = await Collection.Aggregate<Patient>(pipeline).ToListAsync();
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves a paginated, sorted (by admission time descending) queryable of patients, applying optional
|
|
/// filters for text search across patient names and patient number (and by identifier when the text parses
|
|
/// as an ObjectId), time range, unit, and point of care, and falling back to an unfiltered query when no
|
|
/// filtered request is provided.
|
|
/// </summary>
|
|
/// <param name="filter">The pagination and filtering criteria, including optional text, time range, unit, and point of care filters.</param>
|
|
/// <returns>A find fluent for the filtered and sorted patient query.</returns>
|
|
public IFindFluent<Patient, Patient> GetPaginatedPatients(PaginationFilter filter)
|
|
{
|
|
var filterBuilder = Builders<Patient>.Filter;
|
|
var sort = Builders<Patient>.Sort.Descending("admTime");
|
|
var filters = new List<FilterDefinition<Patient>>();
|
|
|
|
if (filter.FilteredRequest != null)
|
|
{
|
|
AddDefaultFilters(filters, filter, filterBuilder);
|
|
// return CreateFindFluent(filters, sort);
|
|
}
|
|
|
|
var requestFilter = filter.FilteredRequest;
|
|
|
|
if(requestFilter == null) return CreateFindFluent(filters, sort);
|
|
|
|
AddTimeFilters(requestFilter, filters, filterBuilder);
|
|
|
|
if (!string.IsNullOrEmpty(filter.FilteredRequest?.Text))
|
|
{
|
|
var textFilter = filter.FilteredRequest.Text;
|
|
var textFilterEscaped = Regex.Escape(textFilter);
|
|
|
|
var orFilters = new List<FilterDefinition<Patient>>
|
|
{
|
|
filterBuilder.Regex(p => p.Person!.FirstName, new BsonRegularExpression(textFilterEscaped, "i")),
|
|
filterBuilder.Regex(p => p.Person!.SecondName, new BsonRegularExpression(textFilterEscaped, "i")),
|
|
filterBuilder.Regex(p => p.Person!.LastName, new BsonRegularExpression(textFilterEscaped, "i")),
|
|
filterBuilder.Regex(p => p.PatientNumber, new BsonRegularExpression(textFilterEscaped, "i"))
|
|
};
|
|
|
|
if (ObjectId.TryParse(textFilter, out var id)) orFilters.Add(filterBuilder.Eq("_id", id));
|
|
|
|
filters.Add(filterBuilder.Or(orFilters));
|
|
}
|
|
|
|
if (filter.FilteredRequest?.UnitId != null && ObjectId.TryParse(filter.FilteredRequest.UnitId, out var unitId))
|
|
filters.Add(filterBuilder.Eq("unitId", unitId));
|
|
if (filter.FilteredRequest?.PocId != null && ObjectId.TryParse(filter.FilteredRequest.PocId, out var pocId))
|
|
filters.Add(filterBuilder.Eq("pointOfCareId", pocId));
|
|
return CreateFindFluent(filters, sort);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Retrieves all patients whose <c>UpdateDate</c> is older than the specified date or has never been set (null), identifying records that have not been updated since the given threshold.
|
|
/// </summary>
|
|
/// <param name="date">The cutoff date; patients with an <c>UpdateDate</c> strictly earlier than this value, or with no <c>UpdateDate</c> set, will be returned.</param>
|
|
/// <returns>A task that resolves to a list of <see cref="Patient"/> instances matching the filter criteria.</returns>
|
|
public async Task<List<Patient>> FindPatientsNotUpdatedSince(DateTime date)
|
|
{
|
|
var filter = Builders<Patient>.Filter.Or(
|
|
Builders<Patient>.Filter.Lt(p => p.UpdateDate, date),
|
|
Builders<Patient>.Filter.Eq(p => p.UpdateDate, null)
|
|
);
|
|
return await Collection.Find(filter).ToListAsync();
|
|
|
|
//return await Collection.Find(p => p.UpdateDate < date).ToListAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves all patients whose discharge time (<see cref="Patient.DisTime"/>) has been recorded,
|
|
/// indicating they have been discharged. If an error occurs during the database operation,
|
|
/// the exception is logged and an empty list is returned instead of propagating the failure.
|
|
/// </summary>
|
|
/// <returns>A task that resolves to a list of discharged <see cref="Patient"/> records,
|
|
/// or an empty list if the operation fails.</returns>
|
|
public async Task<List<Patient>> FindDischargedPatients()
|
|
{
|
|
try
|
|
{
|
|
var filter = Builders<Patient>.Filter.Ne(p => p.DisTime, null);
|
|
var cursor = await Collection.FindAsync(filter);
|
|
var patients = await cursor.ToListAsync();
|
|
|
|
return patients;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error("Error finding discharged patients {exMessage}", ex.Message);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates an existing patient record in the data store, refreshing the modification timestamp, and returns the updated document.
|
|
/// Returns <c>null</c> when no patient matching the provided identifier is found.
|
|
/// </summary>
|
|
/// <param name="updatedPatient">The patient entity containing the updated values and the identifier of the record to modify.</param>
|
|
/// <returns>The updated <see cref="Patient"/> after the modification is applied, or <c>null</c> if no matching document exists.</returns>
|
|
public async Task<Patient?> UpdateOne(Patient updatedPatient)
|
|
{
|
|
var filter = Builders<Patient>.Filter.Eq("_id", updatedPatient.Id);
|
|
|
|
var update = Builders<Patient>.Update
|
|
.Set(p => p.PatientNumber, updatedPatient.PatientNumber)
|
|
.Set(p => p.Person, updatedPatient.Person)
|
|
.Set(p => p.UpdateDate, DateTime.UtcNow);
|
|
|
|
return await Collection.FindOneAndUpdateAsync(filter, update,
|
|
new FindOneAndUpdateOptions<Patient, Patient> { ReturnDocument = ReturnDocument.After });
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the incoming data fields of an existing patient in the database, refreshing the update timestamp.
|
|
/// Returns the updated patient document, or <c>null</c> if no patient matches the specified identifier.
|
|
/// </summary>
|
|
/// <param name="patientId">The unique identifier of the patient to update.</param>
|
|
/// <param name="person">The patient object containing the new values for the incoming data fields.</param>
|
|
/// <returns>The updated <see cref="Patient"/> document after the modification, or <c>null</c> if no document was found.</returns>
|
|
public async Task<Patient?> UpdatePatientIncomingData(ObjectId patientId, Patient person)
|
|
{
|
|
var filter = Builders<Patient>.Filter.Eq("_id", patientId);
|
|
var update = Builders<Patient>.Update
|
|
.Set(p => p.OriginAux, person.OriginAux)
|
|
.Set(p => p.DiagnosisAux, person.DiagnosisAux)
|
|
.Set(p => p.Diagnosis, person.Diagnosis)
|
|
.Set(p => p.UpdateDate, DateTime.UtcNow)
|
|
.Set(p => p.AdmTime, person.AdmTime)
|
|
.Set(p => p.Origin, person.Origin);
|
|
|
|
return await Collection.FindOneAndUpdateAsync(filter, update,
|
|
new FindOneAndUpdateOptions<Patient, Patient> { ReturnDocument = ReturnDocument.After });
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the demographic and clinical information of an existing patient, including allergies, language barrier, diagnosis, and person data, while setting the update timestamp to the current UTC time.
|
|
/// Returns the updated patient document after the modification has been applied.
|
|
/// </summary>
|
|
/// <param name="patientId">The unique identifier of the patient whose data will be updated.</param>
|
|
/// <param name="person">The patient object containing the new demographic and clinical values to persist.</param>
|
|
/// <returns>The updated <see cref="Patient"/> document, or <c>null</c> if no patient with the specified identifier was found.</returns>
|
|
public async Task<Patient?> UpdatePatientDemographicData(ObjectId patientId, Patient person)
|
|
{
|
|
var filter = Builders<Patient>.Filter.Eq("_id", patientId);
|
|
var update = Builders<Patient>.Update
|
|
.Set(p => p.UpdateDate, DateTime.UtcNow)
|
|
.Set(p => p.Allergies, person.Allergies)
|
|
.Set(p => p.LanguageBarrier, person.LanguageBarrier)
|
|
.Set(p => p.DiagnosisAux, person.DiagnosisAux)
|
|
.Set(p => p.Diagnosis, person.Diagnosis)
|
|
.Set(p => p.Person, person.Person);
|
|
|
|
|
|
return await Collection.FindOneAndUpdateAsync(filter, update,
|
|
new FindOneAndUpdateOptions<Patient, Patient> { ReturnDocument = ReturnDocument.After });
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Creates MongoDB indexes for the Patient collection, including a unique index on the <c>patientNumber</c> field and a non-unique index on the <c>admTime</c> field, using background index creation.
|
|
/// </summary>
|
|
public override async Task CreateIndexes()
|
|
{
|
|
var options = new CreateIndexOptions<Patient> { Background = true, Unique = false };
|
|
var optionsUq = new CreateIndexOptions<Patient>
|
|
{
|
|
Background = true,
|
|
Unique = true
|
|
//PartialFilterExpression = Builders<Patient>.Filter.Exists(p => p.PointOfCareId) &
|
|
// Builders<Patient>.Filter.Exists(p => p.UnitId)
|
|
};
|
|
var indexes = new List<CreateIndexModel<Patient>>
|
|
{
|
|
new("{ patientNumber: 1 }", optionsUq),
|
|
new("{ admTime: 1 }", options)
|
|
//new("{ pointOfCareId: 1, unitId: 1 }", optionsUq),
|
|
//new("{ pointOfCareId: 1 }", optionsUq)
|
|
};
|
|
|
|
await MongoUtils.EnsureIndexes(Collection, indexes);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Appends time-range filter definitions to the provided filter list based on the request criteria, covering admission time, discharge time, last observation time, creation date, update date, and birth date. For birth date filtering, an existence filter on the related Person entity is added first to ensure the range conditions are only applied when Person is not null.
|
|
/// </summary>
|
|
/// <param name="requestFilter">The request containing the optional start and end time values used to build each range filter.</param>
|
|
/// <param name="filters">The collection of filter definitions to which the constructed filters are added.</param>
|
|
/// <param name="filterBuilder">The builder used to create the Gte and Lte filter definitions for each time range.</param>
|
|
private static void AddTimeFilters(FilteredRequest requestFilter, List<FilterDefinition<Patient>> filters,
|
|
FilterDefinitionBuilder<Patient> filterBuilder)
|
|
{
|
|
// Filter by admission time
|
|
if (requestFilter.StartAdmTime.HasValue)
|
|
filters.Add(filterBuilder.Gte(p => p.AdmTime, requestFilter.StartAdmTime.Value));
|
|
if (requestFilter.EndAdmTime.HasValue)
|
|
filters.Add(filterBuilder.Lte(p => p.AdmTime, requestFilter.EndAdmTime.Value));
|
|
|
|
// Filter by discharge time
|
|
if (requestFilter.StartDischargeTime.HasValue)
|
|
filters.Add(filterBuilder.Gte(p => p.DisTime, requestFilter.StartDischargeTime.Value));
|
|
if (requestFilter.EndDischargeTime.HasValue)
|
|
filters.Add(filterBuilder.Lte(p => p.DisTime, requestFilter.EndDischargeTime.Value));
|
|
|
|
// Filter by last observation time
|
|
if (requestFilter.StartLastObsTime.HasValue)
|
|
filters.Add(filterBuilder.Gte(p => p.LastObservationDate, requestFilter.StartLastObsTime.Value));
|
|
if (requestFilter.EndLastObsTime.HasValue)
|
|
filters.Add(filterBuilder.Lte(p => p.LastObservationDate, requestFilter.EndLastObsTime.Value));
|
|
|
|
// Filter by creation date
|
|
if (requestFilter.StartCreationDateTime.HasValue)
|
|
filters.Add(filterBuilder.Gte(p => p.CreationDate, requestFilter.StartCreationDateTime.Value));
|
|
if (requestFilter.EndCreationDateTime.HasValue)
|
|
filters.Add(filterBuilder.Lte(p => p.CreationDate, requestFilter.EndCreationDateTime.Value));
|
|
|
|
// Filter by update date
|
|
if (requestFilter.StartUpDateTime.HasValue)
|
|
filters.Add(filterBuilder.Gte(p => p.UpdateDate, requestFilter.StartUpDateTime.Value));
|
|
if (requestFilter.EndUpDateTime.HasValue)
|
|
filters.Add(filterBuilder.Lte(p => p.UpdateDate, requestFilter.EndUpDateTime.Value));
|
|
|
|
// Filter by birth date (with Person null check)
|
|
if (requestFilter.StartBirthDate.HasValue || requestFilter.EndBirthDate.HasValue)
|
|
{
|
|
// Check if Person is not null
|
|
filters.Add(filterBuilder.Exists(p => p.Person));
|
|
|
|
// Apply birth date filters only if Person is not null
|
|
if (requestFilter.StartBirthDate.HasValue)
|
|
filters.Add(filterBuilder.Gte(p => p.Person!.BirthDate, requestFilter.StartBirthDate.Value));
|
|
if (requestFilter.EndBirthDate.HasValue)
|
|
filters.Add(filterBuilder.Lte(p => p.Person!.BirthDate, requestFilter.EndBirthDate.Value));
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Asynchronously retrieves all patients associated with the specified point of care identifier.
|
|
/// </summary>
|
|
/// <param name="pointOfCare">The ObjectId of the point of care used to filter the patients.</param>
|
|
/// <returns>A task that represents the asynchronous operation, containing a list of patients matching the specified point of care.</returns>
|
|
public async Task<List<Patient>> FindAllByPointOfCareId(ObjectId pointOfCare)
|
|
{
|
|
var filterBuilder = Builders<Patient>.Filter;
|
|
var filter = filterBuilder.Eq(pa => pa.PointOfCareId, pointOfCare);
|
|
|
|
var result = await Collection.FindAsync(filter);
|
|
|
|
return await result.ToListAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds default filter definitions to the specified filters list based on the provided pagination filter.
|
|
/// Filters by <see cref="Patient.Id"/> when <c>PatientId</c> is supplied and successfully parsed as an <see cref="ObjectId"/>; otherwise, filters by <c>PatientNumber</c> when it is supplied.
|
|
/// </summary>
|
|
/// <param name="filters">The list of filter definitions to which the new filter will be appended.</param>
|
|
/// <param name="filter">The pagination filter containing the request criteria used to build the default filter.</param>
|
|
/// <param name="filterBuilder">The builder used to construct the MongoDB filter definitions for the <see cref="Patient"/> entity.</param>
|
|
private void AddDefaultFilters(List<FilterDefinition<Patient>> filters, PaginationFilter filter,
|
|
FilterDefinitionBuilder<Patient> filterBuilder)
|
|
{
|
|
// Verifica si PatientId tiene valor
|
|
if (filter.FilteredRequest?.PatientId != null)
|
|
{
|
|
if(ObjectId.TryParse(filter.FilteredRequest.PatientId, out var patientId))
|
|
filters.Add(
|
|
filterBuilder.Eq(p => p.Id, patientId)
|
|
);
|
|
}
|
|
|
|
// Verifica si PatientNumber tiene valor
|
|
else if (filter.FilteredRequest?.PatientNumber != null)
|
|
filters.Add(
|
|
filterBuilder.Eq(p => p.PatientNumber, filter.FilteredRequest.PatientNumber)
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a MongoDB find fluent query for <see cref="Patient"/> documents by combining the provided filters with a logical AND, or applying no filter when none are supplied, and applying the specified sort order.
|
|
/// </summary>
|
|
/// <param name="filters">The list of filter definitions to combine; when empty, an empty filter (match-all) is used.</param>
|
|
/// <param name="sort">The sort definition to apply to the query results.</param>
|
|
/// <returns>An <see cref="IFindFluent{TSource, TDocument}"/> configured with the combined filter and sort.</returns>
|
|
private IFindFluent<Patient, Patient> CreateFindFluent(List<FilterDefinition<Patient>> filters,
|
|
SortDefinition<Patient> sort)
|
|
{
|
|
var combinedFilter = filters.Any()
|
|
? Builders<Patient>.Filter.And(filters)
|
|
: Builders<Patient>.Filter.Empty;
|
|
|
|
return Collection.Find(combinedFilter).Sort(sort);
|
|
}
|
|
} |