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; public class PatientRepository : MongoRepository, IPatientRepository { private readonly ApiSettings _apiSettings; public PatientRepository(IOptions apiSettings, IMongoDatabase database) : base(database) { if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings)); _apiSettings = apiSettings.Value; } //For testing // public PatientRepository(IOptions apiSettings, IOptions dbSetting) : // base(dbSetting) // { // if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings)); // _apiSettings = apiSettings.Value; // } public override string GetCollectionName() { return _apiSettings.Patients ?? "patients"; } public async Task FindById(ObjectId id) { try { var filter = Builders.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; } } public async Task FindByPointOfCareId(ObjectId id) { try { var filter = Builders.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; } } public async Task FindByUnitAndPocId(ObjectId unit, ObjectId pointOfCare) { try { var filter = Builders.Filter.And( Builders.Filter.Eq(p => p.PointOfCareId, pointOfCare), Builders.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 public async Task FindByLocation(PatientLocation? location) { if (location == null) return null; // Primero encontrar la unidad var filterBuilder = Builders.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; } 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; } } } public async Task Update(Patient patient) { patient.UpdateDate = DateTime.UtcNow; await UpdateOneAsync(patient.Id, patient); } public async Task Delete(ObjectId id) { var filter = Builders.Filter.Eq(x => x.Id, id); await Collection.DeleteOneAsync(filter, null); } 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.Filter; var filter = filterBuilder.Eq(p => p.Id, id); var update = Builders.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(DateTime.UtcNow.ToString("o"), location)); var result = await Collection.UpdateOneAsync(filter, update); Log.Debug("Update location result: {result}", result);*/ } 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); } public async Task UpdateAttendingDoctor(ObjectId id, Person attendingDoctor) { var update = Builders.Update .Set(p => p.AttendingDoctor, attendingDoctor) .Set(p => p.UpdateDate, DateTime.UtcNow); await Collection.UpdateOneAsync(p => p.Id == id, update); } public async Task UpdatePatientData(ObjectId id, string patientNumber, Person data, bool updatePatientNumber = true) { var filterBuilder = Builders.Filter; var updateBuilder = Builders.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 FindByPatientNumber(string patientNumber) { if (string.IsNullOrWhiteSpace(patientNumber)) return null; //Último paciente admitido var patient = await Collection.Find(Builders.Filter.And( Builders.Filter.Eq(p => p.DisTime, null), Builders.Filter.Eq(p => p.PatientNumber, patientNumber) )) .Sort(Builders.Sort.Descending(p => p.AdmTime)) .Limit(1) .FirstOrDefaultAsync(); //Último con fecha de alta más reciente patient ??= await Collection.Find(Builders.Filter.And( Builders.Filter.Ne(p => p.DisTime, null), Builders.Filter.Eq(p => p.PatientNumber, patientNumber) )) .Sort(Builders.Sort.Descending(p => p.DisTime)) .Limit(1) .FirstOrDefaultAsync(); return patient; } public async Task 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.Filter.And( Builders.Filter.Eq(p => p.PatientNumber, patientNumber), Builders.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> FindAllPatientWithFinishedProcedures(int archiveProcedureEndDateAfterMinutes) { var currentDateTime = DateTime.UtcNow; // Filtro para documentos que contienen al menos un procedimiento con opción "procedure" var filter = Builders.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> FindAllPatientWithFinishedTests(int archiveTestEndDateAfterMinutes) { var currentDateTime = DateTime.UtcNow; // Filtro para documentos que contienen al menos un procedimiento con opción "procedure" var filter = Builders.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> FindAllPatientWithFinishedTreatment(int archiveTreatmentEndDateAfterMinutes) { var currentDateTime = DateTime.UtcNow; // Filtro para documentos que contienen al menos un procedimiento con opción "procedure" var filter = Builders.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> UpdateMasterListOption(List unitIds, UpdateOptionMasterListDto opt, string typeName) { var isParsed = Enum.TryParse(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.Filter.In("unitId", unitIds); switch (parsedTypeName) { case MasterListType.DiagnosisList: var diagnosisFilter = Builders.Filter.Eq( "diagnosis.name", opt.OldOption?.Name ); var filterUpdateDiagnosis = Builders.Filter.And(filterUnit, diagnosisFilter); var updateDiagnosis = Builders.Update .Set("diagnosis.name", opt.UpdatedOption?.Name) .Set("diagnosis.description", opt.UpdatedOption?.Description); await Collection.UpdateManyAsync(filterUpdateDiagnosis, updateDiagnosis); await Collection.UpdateManyAsync(Builders.Filter.And(filterUnit, Builders.Filter.Eq( "diagnosisAux", opt.OldOption?.Name )), Builders.Update .Set("diagnosisAux", opt.UpdatedOption?.Name)); // Filtro para devolver los documentos actualizados (OR entre diagnosis.name y diagnosisAux) var diagnosisFilterToReturn = Builders.Filter.Eq("diagnosis.name", opt.UpdatedOption?.Name); var diagnosisAuxFilterToReturn = Builders.Filter.Eq("diagnosisAux", opt.UpdatedOption?.Name); var filterToReturn = Builders.Filter.And( filterUnit, Builders.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.Filter.ElemMatch( "doctors", Builders.Filter.Eq("name", opt.OldOption?.Name) ); var filterUpdateDoctor = Builders.Filter.And(filterUnit, doctorFilter); // Actualizar todos los elementos de la lista `doctors` que coincidan var updateDoctor = Builders.Update.Set( "doctors.$[nameElem].name", opt.UpdatedOption?.Name ); // Definir filtros únicos para cada campo utilizado var arrayFilters = new List { new BsonDocumentArrayFilterDefinition( 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( 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.Filter.ElemMatch( "doctors", Builders.Filter.Eq("name", opt.UpdatedOption?.Name) ); var filterToReturnDoctor = Builders.Filter.And(filterUnit, doctorFilterToReturn); var updatedDoctorDocuments = await Collection.Find(filterToReturnDoctor).ToListAsync(); return updatedDoctorDocuments; case MasterListType.InsulationList: break; case MasterListType.OriginList: var originFilter = Builders.Filter.Eq( "origin.name", opt.OldOption?.Name ); var filterUpdateOrigin = Builders.Filter.And(filterUnit, originFilter); var updateOrigin = Builders.Update .Set("origin.name", opt.UpdatedOption?.Name); await Collection.UpdateManyAsync(filterUpdateOrigin, updateOrigin); await Collection.UpdateManyAsync(Builders.Filter.And(filterUnit, Builders.Filter.Eq( "originAux", opt.OldOption?.Name )), Builders.Update .Set("originAux", opt.UpdatedOption?.Name)); // Filtro para devolver los documentos actualizados (OR entre diagnosis.name y diagnosisAux) var originFilterToReturn = Builders.Filter.Eq("origin.name", opt.UpdatedOption?.Name); var originAuxFilterToReturn = Builders.Filter.Eq("originAux", opt.UpdatedOption?.Name); var originfilterToReturn = Builders.Filter.And( filterUnit, Builders.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 []; } public async Task> GetPatientsByUnitIds(List unitIds, string typeName) { var isParsed = Enum.TryParse(typeName, out _); if (!isParsed) return []; var filterUnit = Builders.Filter.In("unitId", unitIds); return await Collection.Find(filterUnit).ToListAsync(); } public async Task CountByUnitId(ObjectId unitId) { try { var filter = Builders.Filter.Eq(p => p.UnitId, unitId); return await Collection.CountDocumentsAsync(filter); } catch (Exception e) { Log.Logger.Error(e.Message); return 0; } } public async Task> DeleteMasterListOption(List unitIds, OptionList opt, string typeName) { var isParsed = Enum.TryParse(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.Filter.In("unitId", unitIds); switch (parsedTypeName) { case MasterListType.DiagnosisList: // Filtro para devolver los documentos actualizados (OR entre diagnosis.name y diagnosisAux) var diagnosisFilterToReturn = Builders.Filter.Eq("diagnosis.name", opt.Name); var diagnosisAuxFilterToReturn = Builders.Filter.Eq("diagnosisAux", opt.Name); var filterToReturn = Builders.Filter.And( filterUnit, Builders.Filter.Or(diagnosisFilterToReturn, diagnosisAuxFilterToReturn) ); // Devolver los documentos actualizados var updatedDocuments = await Collection.Find(filterToReturn).ToListAsync(); var diagnosisFilter = Builders.Filter.Eq( "diagnosis.name", opt.Name ); var filterUpdateDiagnosis = Builders.Filter.And(filterUnit, diagnosisFilter); var updateDiagnosis = Builders.Update .Set(x => x.Diagnosis, null); await Collection.UpdateManyAsync(filterUpdateDiagnosis, updateDiagnosis); await Collection.UpdateManyAsync(Builders.Filter.And(filterUnit, Builders.Filter.Eq( "diagnosisAux", opt.Name )), Builders.Update .Set(x => x.DiagnosisAux, null)); return updatedDocuments; case MasterListType.DoctorList: // Devolvemos los datos var doctorFilterToReturn = Builders.Filter.ElemMatch( "doctors", Builders.Filter.Eq("_id", opt.Id) ); var filterToReturnDoctor = Builders.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.Filter.ElemMatch( "doctors", Builders.Filter.Eq("_id", opt.Id) ); var filterUpdateDoctor = Builders.Filter.And(filterUnit, doctorFilter); // Actualización del campo `name` en el array `doctors` var updateDoctor = Builders.Update.PullFilter( "doctors", Builders.Filter.And( Builders.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.Filter.Eq("origin.name", opt.Name); var originAuxFilterToReturn = Builders.Filter.Eq("originAux", opt.Name); var filterOriginToReturn = Builders.Filter.And( filterUnit, Builders.Filter.Or(originFilterToReturn, originAuxFilterToReturn) ); // Devolver los documentos actualizados var updatedDocumentsOrigin = await Collection.Find(filterOriginToReturn).ToListAsync(); var originFilter = Builders.Filter.Eq( "origin.name", opt.Name ); var filterUpdateOrigin = Builders.Filter.And(filterUnit, originFilter); var updateOrigin = Builders.Update .Set(x => x.Diagnosis, null); await Collection.UpdateManyAsync(filterUpdateOrigin, updateOrigin); await Collection.UpdateManyAsync(Builders.Filter.And(filterUnit, Builders.Filter.Eq( "originAux", opt.Name )), Builders.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(); } public async Task 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.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.Sort.Descending(p => p.DisTime)).Limit(1).FirstOrDefaultAsync(); return patient; } public async Task FindByPatientId(ObjectId patientId) { //Último paciente admitido var patient = await Collection.Find(p => !p.DisTime.HasValue && patientId == p.Id) .Sort(Builders.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.Sort.Descending(p => p.DisTime)).Limit(1).FirstOrDefaultAsync(); return patient; } public async Task> FindAll() { return (await Collection.FindAsync(Builders.Filter.Empty)).ToList(); //return (await Collection.FindAsync(_ => true)).ToList(); } public async Task> FindByPointOfCare(string pointOfCare) { var filterBuilder = Builders.Filter; var filter = filterBuilder.Eq(pa => pa.UnitString, pointOfCare); var result = await Collection.FindAsync(filter); return await result.ToListAsync(); } public async Task> FindByPointOfCare(ObjectId pointOfCare) { var filterBuilder = Builders.Filter; var filter = filterBuilder.Eq(pa => pa.PointOfCareId, pointOfCare); var result = await Collection.FindAsync(filter); return await result.ToListAsync(); } public async Task> FindInActivePoC() { var virtualPointOfCareValues = Enum.GetValues(typeof(VirtualPointOfCare)).Cast() .Select(p => p.ToString()) .ToList(); //var filterBuilder = Builders.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(pipeline).ToListAsync(); return result; } public async Task> FindInInactivePoC() { var virtualPointOfCareValues = Enum.GetValues(typeof(VirtualPointOfCare)).Cast() .Select(p => p.ToString()) .ToList(); //var filterBuilder = Builders.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(pipeline).ToListAsync(); return result; } public IFindFluent GetPaginatedPatients(PaginationFilter filter) { var filterBuilder = Builders.Filter; var sort = Builders.Sort.Descending("admTime"); var filters = new List>(); 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> { 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); } public async Task> FindPatientsNotUpdatedSince(DateTime date) { var filter = Builders.Filter.Or( Builders.Filter.Lt(p => p.UpdateDate, date), Builders.Filter.Eq(p => p.UpdateDate, null) ); return await Collection.Find(filter).ToListAsync(); //return await Collection.Find(p => p.UpdateDate < date).ToListAsync(); } public async Task> FindDischargedPatients() { try { var filter = Builders.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 []; } } public async Task UpdateOne(Patient updatedPatient) { var filter = Builders.Filter.Eq("_id", updatedPatient.Id); var update = Builders.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 { ReturnDocument = ReturnDocument.After }); } public async Task UpdatePatientIncomingData(ObjectId patientId, Patient person) { var filter = Builders.Filter.Eq("_id", patientId); var update = Builders.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 { ReturnDocument = ReturnDocument.After }); } public async Task UpdatePatientDemographicData(ObjectId patientId, Patient person) { var filter = Builders.Filter.Eq("_id", patientId); var update = Builders.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 { ReturnDocument = ReturnDocument.After }); } public override async Task CreateIndexes() { var options = new CreateIndexOptions { Background = true, Unique = false }; var optionsUq = new CreateIndexOptions { Background = true, Unique = true //PartialFilterExpression = Builders.Filter.Exists(p => p.PointOfCareId) & // Builders.Filter.Exists(p => p.UnitId) }; var indexes = new List> { new("{ patientNumber: 1 }", optionsUq), new("{ admTime: 1 }", options) //new("{ pointOfCareId: 1, unitId: 1 }", optionsUq), //new("{ pointOfCareId: 1 }", optionsUq) }; await MongoUtils.EnsureIndexes(Collection, indexes); } private static void AddTimeFilters(FilteredRequest requestFilter, List> filters, FilterDefinitionBuilder 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)); } } public async Task> FindAllByPointOfCareId(ObjectId pointOfCare) { var filterBuilder = Builders.Filter; var filter = filterBuilder.Eq(pa => pa.PointOfCareId, pointOfCare); var result = await Collection.FindAsync(filter); return await result.ToListAsync(); } private void AddDefaultFilters(List> filters, PaginationFilter filter, FilterDefinitionBuilder 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) ); } private IFindFluent CreateFindFluent(List> filters, SortDefinition sort) { var combinedFilter = filters.Any() ? Builders.Filter.And(filters) : Builders.Filter.Empty; return Collection.Find(combinedFilter).Sort(sort); } }