using System.Diagnostics; using adas_core.Application.Exceptions; using adas_core.Application.Repositories.Interfaces; using adas_core.Application.Services.Interfaces; using adas_core.Application.Subscriptions; 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.GroupedObservations; using adas_core.Domain.Models.Masters; using adas_core.Domain.Models.MongoModels; using adas_core.Domain.Models.Responses; using adas_core.Domain.Utils; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MongoDB.Bson; using MongoDB.Driver; using Serilog; using Patient = adas_core.Domain.Models.MongoModels.Patient; namespace adas_core.Application.Services; public class PatientService : IPatientService { private readonly Lazy _admissionService; private readonly IAppointmentService _appointmentService; private readonly ILocalAuditService _auditService; private readonly IClientMessageService _clientMessageService; private readonly bool _createPatientWithAdtA08; private readonly bool _createPatientWithLocation; private readonly bool _createPatientWithOru; private readonly bool _cretatePatientWithoutPatientNumber; private readonly IDiagnosisService _diagnosisService; private readonly IDischargeService _dischargeService; private readonly IDisplayConfigService _displayConfigService; private readonly IDisplayService _displayService; private readonly bool _findPatientByLocationWithoutId; private readonly IGroupedObservationService _groupedObservationService; private readonly IHttpContextAccessor _httpContextAccessor; private readonly string _iccaFacility; private readonly ListSettings _listSettings; private readonly ILogger _logger; private readonly IMasterListServiceFactory _masterListServiceFactory; private readonly Lazy _observationService; private readonly OnArchiveAction _onArchiveAction; private readonly IPatientArchiveRepository _patientArchiveRepository; private readonly IPatientCarePlanService _patientCarePlanService; private readonly IPatientRepository _patientRepository; private readonly IPoCMappingService _pocMappingService; private readonly IPointOfCareService _pointOfCareService; private readonly Lazy _pumpService; private readonly bool _pushPatientWithOru; private readonly IRecordingAlertService _recordingAlertService; private readonly List _sendingFacility; private readonly ISubscriberGroupedService _subscriberGroupedService; private readonly ISubscribersService _subscribersService; private readonly Lazy _treatmentService; private readonly IUnitService _unitService; private readonly bool _updatePatientDataWithAdtA02; private readonly bool _updatePatientDataWithOru; private readonly bool _updatePatientLocationWithOru; public PatientService( IPatientRepository patientRepository, IPatientArchiveRepository patientArchiveRepository, Lazy observationService, Lazy treatmentService, IPoCMappingService pocMappingService, IDiagnosisService diagnosisService, IAppointmentService appointmentService, Lazy pumpService, IRecordingAlertService recordingAlertService, IDischargeService dischargeService, IOptions apiSettings, IOptions listSettings, ILogger logger, IClientMessageService clientMessageService, ISubscribersService subscribersService, ISubscriberGroupedService subscriberGroupedService, IUnitService unitService, IDisplayService displayService, IPointOfCareService pointOfCareService, Lazy admissionService, IDisplayConfigService displayConfigService, IGroupedObservationService groupedObservationService, IPatientCarePlanService patientCarePlanService, IHttpContextAccessor httpContextAccessor, ILocalAuditService auditService, IMasterListServiceFactory masterListServiceFactory) { _patientRepository = patientRepository; _patientArchiveRepository = patientArchiveRepository; _observationService = observationService; _treatmentService = treatmentService; _diagnosisService = diagnosisService; _appointmentService = appointmentService; _pumpService = pumpService; _recordingAlertService = recordingAlertService; _logger = logger; _clientMessageService = clientMessageService; _subscribersService = subscribersService; _subscriberGroupedService = subscriberGroupedService; _pocMappingService = pocMappingService; _dischargeService = dischargeService; _unitService = unitService; _displayService = displayService; _pointOfCareService = pointOfCareService; _admissionService = admissionService; _displayConfigService = displayConfigService; _groupedObservationService = groupedObservationService; _patientCarePlanService = patientCarePlanService; _httpContextAccessor = httpContextAccessor; _auditService = auditService; _masterListServiceFactory = masterListServiceFactory; _ = Enum.TryParse(apiSettings.Value.OnArchivePatientAction, out _onArchiveAction); _createPatientWithOru = apiSettings.Value.CreatePatientWithOru; _pushPatientWithOru = apiSettings.Value.PushPatientWithOru; _createPatientWithLocation = apiSettings.Value.CreatePatientWithLocation; _updatePatientLocationWithOru = apiSettings.Value.UpdatePatientLocationWithOru; _updatePatientDataWithOru = apiSettings.Value.UpdatePatientDataWithOru; _findPatientByLocationWithoutId = apiSettings.Value.FindPatientByLocationWithoutId; _iccaFacility = apiSettings.Value.IccaFacility ?? string.Empty; _sendingFacility = apiSettings.Value.SendingFacility ?? []; _cretatePatientWithoutPatientNumber = apiSettings.Value.CreatePatientWithoutPatientNumber; _createPatientWithAdtA08 = apiSettings.Value.CreatePatientWithAdtA08; _updatePatientDataWithAdtA02 = apiSettings.Value.UpdatePatientDataWithAdtA02; _listSettings = listSettings.Value; } public async Task Insert(Patient patient) { if (!CheckPatient(patient)) return; await _patientRepository.InsertOneAsync(patient); _ = SendNewPatientBroadcast(patient); } public async Task FindByPatientIdWithLocale(string patientId, LocaleEnum localeEnum) { ObjectId.TryParse(patientId, out var id); var patient = await FindById(id); if (patient?.UnitId == null) return null; var unit = await _unitService.FindById(patient.UnitId); if (unit == null) return patient; var patientWithLocale = await _masterListServiceFactory.GetPatientTraslated(unit, localeEnum, patient); return patientWithLocale; } public async Task InsertAsync(Patient patient) { if (!CheckPatient(patient)) return; await _patientRepository.InsertOneAsync(patient); await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, patient); _ = SendNewPatientBroadcast(patient); } public async Task Move(Patient patient, ObjectId newPocId, ObjectId oldPocId) { var newPoc = await _pointOfCareService.GetInfo(newPocId); if (newPoc == null) { _logger.LogError("Unable to move patient {PatientId} to new poc {NewPocId} is null", patient.Id, newPocId); return false; } if (newPoc.Status != StatusEnum.PointOfCare.InUse && newPoc.Status != StatusEnum.PointOfCare.Locked) { var oldPoc = await _pointOfCareService.GetInfo(oldPocId); if (oldPoc == null) { _logger.LogError("Unable to move patient {PatientId} from old poc {OldPocId} is null", patient.Id, oldPocId); return false; } patient.Location = new PatientLocation(newPoc.UnitName, newPoc.Bed, newPoc.Room); patient.Bed = newPoc.Bed; patient.UnitString = newPoc.UnitName; patient.Room = newPoc.Room; patient.PointOfCareId = newPoc.Id; patient.UnitId = newPoc.UnitId; newPoc.Patient = patient; await Update(patient); await _pointOfCareService.SetPointOfCareStatus(newPoc.Id, StatusEnum.PointOfCare.InUse); await _pointOfCareService.SetPointOfCareStatus(oldPoc.Id, StatusEnum.PointOfCare.Available); _pointOfCareService.CheckNextAdmission(oldPoc.Id); var currentDischarge = await _dischargeService.GetDischargeByPatientId(patient.Id); if (currentDischarge != null) { _dischargeService.SendDischargeBroadcast(currentDischarge, OperationType.DeleteDischarge); currentDischarge.PatientLocation = patient.Location; currentDischarge.PointOfCareId = newPoc.Id; currentDischarge.UnitId = newPoc.UnitId; currentDischarge.Patient = patient; await _dischargeService.UpdateDischargeAsync(currentDischarge); } // Enviar en base a la config de display que tenga cada subscriptor _ = SendLastObsToSubscriberByPoc(newPoc, patient); // Enviar las grupadas dependiendo del config del subscriptor _ = SendLastGroupedObsToSubscriberByPoc(newPoc.Id, oldPoc.Id, patient); return true; } _logger.LogError("Unable to move patient {PatientId} to new poc {NewPocId} is in use or locked", patient.Id, newPocId); return false; } public async Task FindById(ObjectId id, bool withLocation = false) { if (id == ObjectId.Empty) return null; var patient = await _patientRepository.FindById(id); if (withLocation && patient != null) { if (patient.PointOfCareId.HasValue) { var poc = await _pointOfCareService.FindById(patient.PointOfCareId.Value) ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); patient.Bed = poc.Bed; patient.Room = poc.Room; } if (patient.UnitId.HasValue) { var unit = await _unitService.FindById(patient.UnitId.Value); patient.UnitString = unit?.Name; } } // var lastObs = await _observationService.Value.FindLastObservations(patient.Id,1); // if (lastObs.Any()) // { // patient.LastObservationDate = lastObs.First().Time; // } // TODO: check if it can be cached or served faster return patient; } public async Task FindByLocation(PatientLocation? location) { if (location?.UnitName == null || location.Bed == null) return null; var unit = await _unitService.FindByName(location.UnitName); if (unit == null) return null; var poc = await _pointOfCareService.FindByBedAndUnitId(location.Bed, unit.Id); if (poc == null) return null; return await FindByUnitAndPocId(unit.Id, poc.Id); } public async Task FindByPointOfCareId(ObjectId pocId) { return await _patientRepository.FindByPointOfCareId(pocId); } public async Task CountPatientsByUnitId(ObjectId unitId) { return await _patientRepository.CountByUnitId(unitId); } public async Task ArchivePatient(Patient patient) { patient.DisTime ??= DateTime.UtcNow; //var sections = sectionService.FindByPatient(patient.id); var pocId = patient.PointOfCareId; _logger.LogDebug("{OnArchiveAction} patient {Patient}", _onArchiveAction, patient); switch (_onArchiveAction) { case OnArchiveAction.Archive: default: patient.ArchiveDate = DateTime.UtcNow; await _patientArchiveRepository.InsertOneAsync(patient); break; case OnArchiveAction.Delete: //No hay archivado break; } await _patientRepository.Delete(patient.Id); await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, patient, null); await ArchivePatientData(patient.Id); if (pocId.HasValue) { _ = SendDeletePatientBroadcast(patient.Id, pocId.Value); //cambiamos el estado del punto de cuidado a disponible await _pointOfCareService.SetPointOfCareStatus(pocId.Value, StatusEnum.PointOfCare.Available); } } public async Task ArchivePatientData(ObjectId patientid) { _logger.LogDebug("Archiving patient data action: {onArchiveAction} : {patientid}", _onArchiveAction, patientid); switch (_onArchiveAction) { case OnArchiveAction.Archive: default: await _observationService.Value.ArchiveByPatientId(patientid); await _treatmentService.Value.ArchiveByPatientId(patientid); await _diagnosisService.ArchiveByPatientId(patientid); await _appointmentService.ArchiveByPatientId(patientid); await _pumpService.Value.ArchiveByPatientId(patientid); await _recordingAlertService.ArchiveByPatientId(patientid); await _patientCarePlanService.ArchiveByPatientId(patientid); break; case OnArchiveAction.Delete: await _observationService.Value.DeleteByPatientId(patientid); await _treatmentService.Value.DeleteByPatientId(patientid); await _diagnosisService.DeleteByPatientId(patientid); await _appointmentService.DeleteByPatientId(patientid); await _pumpService.Value.DeleteByPatientId(patientid); await _recordingAlertService.DeleteByPatientId(patientid); break; } } public async Task Update(Patient patient) { var oldPatient = await _patientRepository.FindById(patient.Id); try { _logger.LogDebug("Update {patient}", patient); //To solve multi "sin cama" from ICCADB making duplicated keys error on mongodb. if (patient.Location is { Bed: "Sin cama" }) patient.Location.Bed = ObjectId.GenerateNewId().ToString(); if (string.IsNullOrEmpty(patient.PatientNumber)) //si permite crear pacientes sin patientNumber le asignamos el patient.Id patient.PatientNumber = !_cretatePatientWithoutPatientNumber ? patient.Id.ToString() : string.Empty; await _patientRepository.Update(patient); if (oldPatient != null) await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldPatient, patient); _ = SendPatientUpdateBroadcast(patient); } catch (Exception e) { _logger.LogError("error updating patient: {patient} exception: {e.Message}", patient, e.Message); } } public async Task UpdateLocation(ObjectId id, PatientLocation? location) { try { if (location == null) { _logger.LogError("PointOfCare is null. Not updated location for patient id: {id} ", id); return; } _logger.LogDebug("Update location id: {id} data: {location}", id, location); var patient = await _patientRepository.FindById(id); var auxPatient = await _auditService.DeepCopyAsync(patient); if (patient == null) { _logger.LogError("person is null. Not updated location for patient id: {id} ", id); return; } if (patient.PointOfCareId == null) { _logger.LogError("person pocId or unitId is null. Not updated location for patient id: {id} ", id); return; } var oldPoc = await _pointOfCareService.FindById(patient.PointOfCareId.Value); if (oldPoc == null) { _logger.LogError("person old poc not found {pocId}. Not updated location for patient id: {id} ", patient.PointOfCareId.Value, id); return; } var isRecovered = Enum.TryParse(oldPoc.Bed, out _); //"DELETED".Equals(patient.Location.UnitName); //var oldLocation =new PatientLocation(oldUnit?.Name, oldPoc.Bed, oldPoc.Room); if (isRecovered) patient.DisTime = null; var newUnit = await _unitService.FindByName(location.UnitName); var newPoc = await _pointOfCareService.FindByBedAndUnitId(location.Bed, newUnit?.Id); if (newUnit == null || newPoc == null) { _logger.LogError("person new poc not found {pocId}. Not updated location for patient id: {id} ", patient.PointOfCareId.Value, id); return; } patient.Location = new PatientLocation(newUnit.Name, newPoc.Bed, newPoc.Room); patient.PointOfCareId = newPoc.Id; patient.UnitId = newUnit.Id; await _patientRepository.UpdateOneAsync(patient.Id, patient); await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, auxPatient, patient); if (isRecovered) await SendNewPatientBroadcast(patient); var pocListToNotify = new List { newPoc.Id }; await _pointOfCareService.SetPointOfCareStatus(newPoc.Id, StatusEnum.PointOfCare.InUse); pocListToNotify.Add(oldPoc.Id); _pointOfCareService.CheckNextAdmission(oldPoc.Id); //Prio location to advertise both locations of movement between sections _logger.LogDebug( "SendPatientLocationBroadcast id: {id} location: {location} newSection: {newSection}, oldSection: {oldSection}", id, location, newPoc.Location.ToString(), oldPoc.Location.ToString()); if (!isRecovered) _ = SendPatientLocationBroadcast(id, location, pocListToNotify); if (oldPoc.Id != newPoc.Id) CheckLocationForWsSubscriber(location, id); } catch (Exception ex) { _logger.LogError("error updating location for patient id: {id} exception: {exMessage}", id, ex.Message); } } public async Task UpdateAttendingDoctor(ObjectId id, Person doctor) { var oldPatient = await _patientRepository.FindById(id) ?? throw new ConflictException(HttpEnum.ErrorMessage.ConflictOperationIncompleted); _logger.LogDebug("Update attending doctor id: {id} data: {doctor}", id, doctor); await _patientRepository.UpdateAttendingDoctor(id, doctor); var newPatient = await _patientRepository.FindById(id) ?? throw new ConflictException(HttpEnum.ErrorMessage.ConflictOperationIncompleted); await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldPatient, newPatient); _ = SendPatientAttendingDoctorBroadcast(id, doctor); } public async Task UpdatePatientData(ObjectId id, string patientNumber, Person data, bool updatePatientNumber = true) { var oldPatient = await _patientRepository.FindById(id) ?? throw new ConflictException(HttpEnum.ErrorMessage.ConflictOperationIncompleted); _logger.LogDebug("Update person data id: {id} number: {patientNumber} data: {data}", id, patientNumber, data); await _patientRepository.UpdatePatientData(id, patientNumber, data, updatePatientNumber); var newPatient = await _patientRepository.FindById(id) ?? throw new ConflictException(HttpEnum.ErrorMessage.ConflictOperationIncompleted); await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldPatient, newPatient); _ = SendPatientDataBroadcast(id, data); } public async Task UpdatePatientData(ObjectId id, string patientNumber, Patient patient, bool updatePatientNumber = true) { _logger.LogDebug("Update person data id: {id} number: {patientNumber} patient: {patient}", id, patientNumber, patient); if (patient.Person != null) { var oldPatient = await _patientRepository.FindById(id) ?? throw new ConflictException(HttpEnum.ErrorMessage.ConflictOperationIncompleted); await _patientRepository.UpdatePatientData(id, patientNumber, patient.Person, updatePatientNumber); var newPatient = await _patientRepository.FindById(id) ?? throw new ConflictException(HttpEnum.ErrorMessage.ConflictOperationIncompleted); await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldPatient, newPatient); } _ = SendPatientBroadcast(patient); } public async Task FindByPatientId(string patientId, bool withLocation = false) { var patient = await _patientRepository.FindByPatientId(patientId); if (withLocation && patient != null) { if (patient.PointOfCareId.HasValue) { var poc = await _pointOfCareService.FindById(patient.PointOfCareId.Value); patient.Bed = poc?.Bed; patient.Room = poc?.Room; } if (patient.UnitId.HasValue) { var unit = await _unitService.FindById(patient.UnitId.Value); patient.UnitString = unit?.Name; } } return patient; } public async Task FindByPatientNumber(string patientNumber, bool withLocation = false) { var patient = await _patientRepository.FindByPatientNumber(patientNumber); if (withLocation && patient != null) { if (patient.PointOfCareId.HasValue) { var poc = await _pointOfCareService.FindById(patient.PointOfCareId.Value); patient.Bed = poc?.Bed; patient.Room = poc?.Room; } if (patient.UnitId.HasValue) { var unit = await _unitService.FindById(patient.UnitId.Value); patient.UnitString = unit?.Name; } } return patient; } public async Task FindByPatientNumberArchived(string patientNumber) { return await _patientArchiveRepository.FindByPatientNumber(patientNumber); } public async Task ArchivePatientWithoutObservationsSinceDate(DateTime date) { try { // Observations not updated since date, archive their patients and data var ptimes = (await _observationService.Value.FindAllLastPatientObservationTime()) .Where(o => date > o.Value).ToDictionary(o => o.Key, o => o.Value); (await _pumpService.Value.FindAllLastPatientObservationTime()).Where(o => date > o.Value) .Where(o => !ptimes.ContainsKey(o.Key) || ptimes[o.Key] > o.Value).ToList() .ForEach(o => ptimes[o.Key] = o.Value); //ptimes.ToList().Sort( (o1, o2) => o1.Value.CompareTo(o2.Value)); var foundPatients = new HashSet(); var notFoundPatients = new HashSet(); foreach (var o in ptimes) { var patient = await FindById(o.Key); if (patient != null) foundPatients.Add(patient); else notFoundPatients.Add(o.Key); } var notUpdatedPatients = await _patientRepository.FindPatientsNotUpdatedSince(date); foreach (var notUpdatedPatient in notUpdatedPatients.Where(p => p.IsInActivePoC())) { var obsPatient = await _observationService.Value.FindLastObservations(notUpdatedPatient.Id); var pumpPatient = await _pumpService.Value.FindLastPumpObservations(notUpdatedPatient.Id); if (!foundPatients.Contains(notUpdatedPatient) && obsPatient.Count == 0 && pumpPatient.Count == 0) foundPatients.Add(notUpdatedPatient); } if (foundPatients.Count > 0) { _logger.LogDebug("Patients found for archive: {foundPatientsCount}", foundPatients.Count); foreach (var patient in foundPatients) try { await ArchivePatient(patient); } catch (Exception ex) { _logger.LogError( "ERROR trying to ArchivePatient {patientId} because {exMessage}", patient.Id, ex.Message); } } if (notFoundPatients.Count > 0) { _logger.LogWarning("Patients not found in patient collection: {notFoundPatientsCount}", notFoundPatients.Count); foreach (var patientId in notFoundPatients) { _logger.LogWarning("- Deleting orphan data for patient: {patientId}", patientId); try { await ArchivePatientData(patientId); } catch (Exception ex) { _logger.LogError( "ERROR trying to ArchivePatientData {patientId} because {exMessage}", patientId, ex.Message); } } } } catch (Exception ex) { _logger.LogError( "ERROR trying to retrieve patients without observations in date range because {exMessage}", ex.Message); throw; } } public async Task ArchiveDischargedPatients(int hoursBeforeArchive) { var disBeforeDate = DateTime.Now.AddHours(-hoursBeforeArchive); try { var dischargedPatients = (await _patientRepository.FindDischargedPatients()) .Where(patient => patient.DisTime != null && patient.DisTime.Value < disBeforeDate ).ToList(); _logger.LogDebug( "ArchiveDischargedPatients older than {HoursBeforeArchive} hours ({DisBeforeDate}). Found {Count} patients for archiving", hoursBeforeArchive, disBeforeDate, dischargedPatients.Count); foreach (var patient in dischargedPatients) await ArchivePatient(patient); } catch (Exception ex) { _logger.LogError("Error archiving discharged patients {Message}", ex.Message); } } public async Task FindPatient(string? patientId, string? patientNumber, PatientLocation? location, bool findPatientByLocation = false) { Patient? patient = null; if (!string.IsNullOrEmpty(patientNumber)) patient = await FindByPatientNumber(patientNumber, true); if (patient != null) return patient; if (!string.IsNullOrEmpty(patientId)) patient = await FindByPatientId(patientId, true); if (patient != null) return patient; // No buscamos por localización por defecto if (!_findPatientByLocationWithoutId && !findPatientByLocation) return patient; //if location pointOfCare and bed != null if (location is { UnitName: not null, Bed: not null }) { var loc = await _pocMappingService.Map(location); patient = await FindByLocation(loc); if(patient is not null && loc is not null) patient.Location = loc; } return patient; } public async Task GetByPointOfCare(PointOfCare item, bool observations = false, List? filterObservations = null) { try { return await _patientRepository.FindByPointOfCareId(item.Id); } catch (Exception ex) { Debug.WriteLine("ERROR: " + ex.Message); return null; } } public async Task GetByPointOfCareAndLocale(PointOfCare item, Unit? unit, LocaleEnum? localeEnum) { try { var patient = await _patientRepository.FindByPointOfCareId(item.Id); if (unit == null || localeEnum == null) return patient; return await _masterListServiceFactory.GetPatientTraslated(unit, localeEnum.Value, patient); } catch (Exception ex) { Debug.WriteLine("ERROR: " + ex.Message); return null; } } public async Task GetBox(PointOfCare poc, bool observations = false, List? filterObservations = null) { Box response = new(); Patient? patient = null; try { patient = await _patientRepository.FindByPointOfCareId(poc.Id); } catch (Exception ex) { Debug.WriteLine("ERROR: " + ex.Message); } response.PointOfCare = poc.UnitName; response.Bed = poc.Bed; response.IsVisible = true; if (patient == null) { response.HasPatient = false; return response; } response.HasPatient = true; response.Patientid = patient.Id; response.Patient = patient; response.AttendingDoctor = patient.AttendingDoctor; if (!observations) return response; var obs = await _observationService.Value.FindLastObservations(patient.Id, 2, filterObservations); obs.ForEach(Action); response.Observations = obs; return response; void Action(PatientObservation o) { _observationService.Value.MapObservationsByName(o); } } public Task SendNewPatientBroadcast(Patient patient) { var subscribers = new List(); // var section = await _sectionService.FindByPointOfCare(patient.UnitString); // if (section != null) // { // subscribers.AddRange(_subscribersService.GetSubscribers().Where(s => // (s.SubscriptionType == SubscriptionType.Box && s.Box == patient.Bed && s.Section == section.Id) || // (s.SubscriptionType == SubscriptionType.Section && s.Section == section.Id)).ToList()); // } subscribers.AddRange(_subscribersService.GetSubscribers().Where(s => !s.LocationIds.IsNullOrEmpty() && s.LocationIds.Any(c => c == patient.PointOfCareId )).ToList()); foreach (var subscriber in subscribers) _ = _clientMessageService.SendAsync(subscriber.Id, OperationType.NewPatient, patient); return Task.CompletedTask; } public async Task SendPatientUpdateBroadcast(Patient patient) { if (!patient.PointOfCareId.HasValue) { _logger.LogError("patient pointOfCareId is null. Not sent patient id: {id} ", patient.Id); return; } var subscribersGroup = _subscribersService.GetSubscribers().Where(s => s.LocationIds.Any(c => c == patient.PointOfCareId.Value)).GroupBy(h => h.Locale); var unit = await _unitService.FindById(patient.UnitId); foreach (var group in subscribersGroup) { var locale = group.Key ?? LocaleEnum.Default; IEnumerable subscribers = group; foreach (var subscriber in subscribers) { var patientWithLocale = await _masterListServiceFactory.GetPatientTraslated(unit, locale, patient); _ = _clientMessageService.SendAsync(subscriber.Id, OperationType.UpdatePatient, patientWithLocale); } } } public Task SaveRequestAsync(ApiRequest apiRequest) { return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest); })); } public async Task SaveRequest(ApiRequest apiRequest) { if (string.IsNullOrEmpty(apiRequest.PatientNumber) && string.IsNullOrEmpty(apiRequest.Location?.UnitName) && apiRequest.Type != "ICCA") { _logger.LogDebug("person and PointOfCare are nulls"); throw new ApiRequestException("person and PointOfCare are nulls"); } _logger.LogDebug("patientNumber: {apiRequestpatientNumber} location: {apiRequestlocation}", apiRequest.PatientNumber, apiRequest.Location); if (apiRequest.Location != null) { var originalPointOfCare = apiRequest.Location; apiRequest.Location = await _pocMappingService.Map(originalPointOfCare); if (apiRequest.Location == null) { _logger.LogWarning("PointOfCare not found in Mapping list: {originalPointOfCare}", originalPointOfCare.ToString()); return; } } if (apiRequest.OldLocation != null) { var originalOldPointOfCare = apiRequest.OldLocation; apiRequest.OldLocation = await _pocMappingService.Map(originalOldPointOfCare); } _logger.LogDebug("RequestType: {apiRequesttype}", apiRequest.Type); var unitConfig = await _unitService.FindByName(apiRequest.Location?.UnitName); Unit? oldUnitConfig = null; if (apiRequest.OldLocation != null) { var originalOldLocation = await _pocMappingService.Map(apiRequest.OldLocation); if (originalOldLocation != null) oldUnitConfig = await _unitService.FindByName(apiRequest.OldLocation?.UnitName); } if (apiRequest.Type != "ICCA") { if (apiRequest.PatientNumber != null && unitConfig == null && oldUnitConfig == null) { var pat = await FindByPatientNumber(apiRequest.PatientNumber); if (pat is { UnitId: not null }) unitConfig = await _unitService.FindById(pat.UnitId); } if (!Hl7Utils.ManageAutoAdt(unitConfig, oldUnitConfig, _logger, "ADT")) return; } //TODO Crear para el adt5 try { Patient? patient = null; switch (apiRequest.Type) { //* ADT EVENTS case "ADT_A01": case "ADT_A05": { //* A01 Admit/visit notification //* A05 Pre-admit a patient _logger.LogDebug("NEW ADT {apiRequestType} PATIENT NUMBER: {apiRequestPatientNumber}", apiRequest.Type, apiRequest.PatientNumber ?? "NULL"); patient = await ProcessAdtPatientAdmit(apiRequest); break; } case "ADT_A02": { //* A02 Transfer a patient if (apiRequest.Location != null) apiRequest.Location = await _pocMappingService.Map(apiRequest.Location); patient = await FindPatient(apiRequest.PatientId, apiRequest.PatientNumber, apiRequest.OldLocation); await ProcessAdtMovePatient(apiRequest, patient); break; } case "ADT_A03": { // * A03 Discharge/end visit // *Ya no archiva, archiva el job del Scheduler service pasado el tiempo establecido por el hospital en SinceDischargeTimeToArchive // se le mueve a una cama temporal. _logger.LogDebug("NEW ADT {apiRequestType} PATIENT NUMBER: {apiRequestPatientNumber}", apiRequest.Type, apiRequest.PatientNumber); if (apiRequest.Location != null) apiRequest.Location = await _pocMappingService.Map(apiRequest.Location); patient = await FindPatient(apiRequest.PatientId, apiRequest.PatientNumber, apiRequest.Location); if (patient != null) await RemovePatientAndSendToVirtualPoc(apiRequest, patient, VirtualPointOfCare.Deleted); else _logger.LogDebug("ADT A03 not found patient number {number}", apiRequest.PatientNumber); break; } case "ADT_A04": { // * A04 Register a patient if (apiRequest.Location != null) apiRequest.Location = await _pocMappingService.Map(apiRequest.Location); patient = await FindPatient(apiRequest.PatientId, apiRequest.PatientNumber, apiRequest.Location); if (patient == null) patient = await CreatePatientFromRequest(apiRequest); else _logger.LogDebug("ADT A04 not found patient number {number}", apiRequest.PatientNumber); break; } case "ADT_A08": { //* A08 Update patient information patient = await FindPatient(apiRequest.PatientId, apiRequest.PatientNumber, apiRequest.Location); if (patient != null) { await UpdatePatientFromRequest(patient, apiRequest, true); await Update(patient); } else { _logger.LogDebug("ADT A08 not found by patient number {number}", apiRequest.PatientNumber); if (_createPatientWithAdtA08) { _logger.LogDebug( "ProcessAdtPatientAdmit ADT_A08 Because CreatePatientWithADT_A08 is true {apiRequestpatientNumber}", apiRequest.PatientNumber); await ProcessAdtPatientAdmit(apiRequest); } } break; } // A11 Cancel admit/ visit notification/ // Remove patient case "ADT_A11": { var patientNumber = apiRequest.PatientNumber; if (patientNumber == null) { _logger.LogError("ADT A11 Without patient number {number}", apiRequest.PatientNumber); } else { patient = await _patientRepository.FindByPatientNumber(patientNumber); if (patient != null) await RemovePatientAndSendToVirtualPoc(apiRequest, patient, VirtualPointOfCare.Cancelled); else _logger.LogDebug("ADT A11 not found patient number {number}", apiRequest.PatientNumber); } break; } //* A12 Cancel patient transfer. //* Se mueve el patientNumber que viene a la cama que indica, si hay ya un paciente en esa cama ese paciente se mueve a una temporal case "ADT_A12": { var patientNumber = apiRequest.PatientNumber; if (apiRequest.Location != null) apiRequest.Location = await _pocMappingService.Map(apiRequest.Location); if (patientNumber == null) { _logger.LogError("ADT A12 Without patient number {number}", apiRequest.PatientNumber); } else { patient = await _patientRepository.FindByPatientNumber(patientNumber); await ProcessAdtMovePatient(apiRequest, patient); } break; } //* A13 Cancel discharge/end visit //* Recuperamos el paciente, seteamos ArchiveDate a null.Lo movemos a la cama que nos indique. case "ADT_A13": { if (apiRequest.Location != null) apiRequest.Location = await _pocMappingService.Map(apiRequest.Location); var patientNumber = apiRequest.PatientNumber; if (patientNumber == null) { _logger.LogError("ADT A13 Without patient number {number}", apiRequest.PatientNumber); } else { patient = await _patientRepository.FindByPatientNumber(patientNumber); if (patient != null) { if ("".Equals(apiRequest.Location?.Bed?.Replace("\"\"", ""))) { var unit = await _unitService.FindById(patient.UnitId); apiRequest.Location = new PatientLocation(unit?.Name, VirtualPointOfCare.Recovered.ToString(), VirtualPointOfCare.Recovered.ToString()); } await ProcessAdtMovePatient(apiRequest, patient); } else { _logger.LogDebug("ADT A13 not found patient number {number}", apiRequest.PatientNumber); } } break; } case "ADT_A31": { //* ADT_A31 - Update Person Information patient = await FindPatient(apiRequest.PatientId, apiRequest.PatientNumber, apiRequest.Location); if (patient != null) { await UpdatePatientFromRequest(patient, apiRequest, true); await Update(patient); } else { _logger.LogDebug("ADT A31 not found patient number {number}", apiRequest.PatientNumber); } break; } //ADT_A39 - Merge Person - Patient Id //ADT_A40 - Merge patient - Patient Identifier List case "ADT_A39": case "ADT_A40": { if (apiRequest.Location != null) apiRequest.Location = await _pocMappingService.Map(apiRequest.Location); var patientNumber = apiRequest.PatientNumber; if (patientNumber == null) { _logger.LogError("ADT A39 Without patient number {number}", apiRequest.PatientNumber); } else { patient = await _patientRepository.FindByPatientNumber(patientNumber); if (patient != null && !string.IsNullOrEmpty(apiRequest.OldPatientNumber)) await MergePatient(patient, apiRequest.OldPatientNumber); else _logger.LogDebug("ADT A39 not found patient number {number}", apiRequest.PatientNumber); } break; } //ADT A_47: actualización de lista de ids case "ADT_A47": { //Para el A47 no envía necesariamente el el location //Buscamos el paciente por patientNumber if (!string.IsNullOrEmpty(apiRequest.PatientNumber)) patient = await _patientRepository.FindByPatientNumber(apiRequest.PatientNumber); if (patient == null) { _logger.LogError("{type}: Patient not found in apiRequest: {apirequest}", apiRequest.Type, apiRequest.ToString()); break; } _logger.LogDebug("Updating patient from request. Patient: {patient} ", patient.ToString()); UpdatePatientFromRequestToIdList(patient, apiRequest); _logger.LogDebug("Updating patient. Patient: {patient}", patient.ToString()); await Update(patient); _logger.LogDebug("updated patient: {patientId}", patient.Id); break; } //ADT A_44: actualización de patient number case "ADT_A44": { //Cambiar el patientNumber var oldPatientNumber = apiRequest.OldPatientNumber; if (oldPatientNumber == null) { if (apiRequest.Location == null) { //A44 no tiene ni oldPatientNumber ni ubicación //No se procesa _logger.LogError("{type} Without old Patient Number and location ", apiRequest.Type); break; } //Buscamos al paciente por su localización patient = await _patientRepository.FindByLocation(apiRequest.Location); } else { patient = await _patientRepository.FindByPatientNumber(oldPatientNumber); } if (patient == null) { _logger.LogDebug("ADT A44 not found patient"); break; } _logger.LogDebug("Updating patient from request. Patient: {patient} ", patient.ToString()); await UpdatePatientFromRequest(patient, apiRequest); _logger.LogDebug("Updating patient. Patient: {patient}", patient.ToString()); await Update(patient); _logger.LogDebug("updated patient: {patientId}", patient.Id); break; } //* A06 Change an outpatient to an inpatient //* A07 Change an inpatient to an outpatient //* A08 Update patient information //* A09 Patient departing - tracking //* A10 Patient arriving - tracking //* A12 Cancel transfer //* A14 Pending admit //* A15 Pending transfer //* A16 Pending discharge //* A17 Swap patients //* A18 Merge patient information //* A19 QRY/ADR - Patient query //* A20 Bed status update case "ADT_A06": case "ADT_A07": case "ADT_A09": case "ADT_A10": case "ADT_A14": case "ADT_A15": case "ADT_A16": case "ADT_A17": case "ADT_A18": case "ADT_A19": case "ADT_A20": _logger.LogWarning("{apiRequesttype} is not valid for Patients", apiRequest.Type); break; //For icca db sync case "ICCA": _ = ProcessIccaSync(apiRequest); break; default: throw new ApiRequestException("ApiRequest type " + apiRequest.Type + " is not valid for Patients"); } if (patient != null) { if (apiRequest.Observations != null && apiRequest.Observations.Any()) _observationService.Value.ProcessObservations(apiRequest.Observations, patient, apiRequest.MessageTime, apiRequest.ObservationData); if (apiRequest.Diagnosis != null && apiRequest.Diagnosis.Any()) _ = _diagnosisService.ProcessDiagnosis(apiRequest.Diagnosis, patient, apiRequest.MessageTime); if (apiRequest.Appointments != null && apiRequest.Appointments.Any()) _ = _appointmentService.ProcessApiRequest(apiRequest, patient); } } catch (Exception ex) { _logger.LogError("ERROR saving patient: {exMessage} trace: {exStackTrace}", ex.Message, ex.StackTrace); } } public async Task CreatePatientFromRequest(ApiRequest apiRequest, bool ignoreLocation = false) { var patient = new Patient { Id = ObjectId.GenerateNewId(), CreationDate = DateTime.UtcNow }; await UpdatePatientFromRequest(patient, apiRequest, ignoreLocation); await _patientRepository.InsertOneAsync(patient); await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, patient); return patient; } public async Task FindPatientByApiRequest(ApiRequest apiRequest) { Patient? patient = null; if (apiRequest.Location != null) { var originalPointOfCare = apiRequest.Location; apiRequest.Location = await _pocMappingService.Map(originalPointOfCare); if (apiRequest.Location == null) { _logger.LogDebug("PointOfCare not found in Mapping list: {OriginalPointOfCare} ", originalPointOfCare); return patient; } } _logger.LogDebug("RequestType: {apiRequest}", apiRequest.Type); try { switch (apiRequest.Type) { //* ORU_R01 - Unsolicited transmission of an observation message //* ORU_R40 - Unsolicited transmission of an alert observation message //* AÑADIDO. SI EL FACILITY DEL API REQUEST NO ES EL DE ICCA BUSCA SOLO POR POINT OF CARE Y BED NO POR PATIENT ID. case "AlarisPump": case "ORU_R01": case "ORU_R42": case "ORU_R40": _logger.LogDebug("patientNumber: {apiRequestpatientNumber} location: {apiRequestlocation}", apiRequest.PatientNumber, apiRequest.Location); // SEARCH PATIENT || SendingFacility.Contains(admRequest.facility) if (!string.IsNullOrWhiteSpace(apiRequest.PatientNumber) && !string.IsNullOrWhiteSpace(apiRequest.Facility) && (apiRequest.Facility.Equals(_iccaFacility) || apiRequest.Facility.Equals("obs"))) { // SEARCH PATIENT BY PATIENT NUMBER _logger.LogDebug("SEARCH PATIENT BY PATIENT NUMBER"); patient = await FindByPatientNumber(apiRequest.PatientNumber, true); if (patient == null) // PATIENT NOT FOUND OR ARCHIVED _logger.LogDebug("PATIENT NOT FOUND OR ARCHIVED"); } if (patient == null) { _logger.LogDebug("PATIENT {apiRequestpatient}", apiRequest.Patient); _logger.LogDebug("PATIENT NUMBER {apiRequestpatientNumber}", apiRequest.PatientNumber); _logger.LogDebug("PATIENT LOCATION {apiRequestlocation} EMPTY {apiRequestlocationIsEmpty}", apiRequest.Location, apiRequest.Location?.ToStr() ?? "null"); if (!string.IsNullOrEmpty(apiRequest.PatientNumber) && !string.IsNullOrWhiteSpace(apiRequest.Facility) && (apiRequest.Facility.Equals(_iccaFacility) || _sendingFacility.Contains(apiRequest .Facility))) { _logger.LogDebug("PATIENT LOCATION Find person by PatientNumber"); var patientExists = await _patientRepository.FindByPatientNumber(apiRequest.PatientNumber); if (patientExists != null) { patient = patientExists; } else { _logger.LogDebug("PATIENT LOCATION IS EMPTY.Not found Patient by PatientNumber"); if (_createPatientWithOru) { if (apiRequest.Location == null || apiRequest.Location.IsEmpty()) { _logger.LogDebug( "PATIENT LOCATION IS EMPTY. Generate temporal bed because createPatientWithoutLocation is TRUE"); apiRequest.Location = new PatientLocation( VirtualPointOfCare.Unknown.ToString(), VirtualPointOfCare.Unknown.ToString()); } if (!apiRequest.Location.IsEmpty() && string.IsNullOrEmpty(apiRequest.Location.Bed)) apiRequest.Location.Bed = VirtualPointOfCare.Unknown.ToString(); patient = new Patient { Id = ObjectId.GenerateNewId(), PatientNumber = apiRequest.PatientNumber, Person = apiRequest.Patient, AttendingDoctor = apiRequest.AttendingDoctor, Location = apiRequest.Location }; var patientLocation = await FindPatient(null, null, apiRequest.Location, true); if (patientLocation != null) { _logger.LogDebug( "PATIENT LOCATION IS OCUPED, Created UNKNOW location {patient}.", patient); var pocUnknown = await _pointOfCareService.FindByBedAndUnitId( VirtualPointOfCare.Unknown.ToString(), patientLocation.UnitId); patient.UnitId = patientLocation.UnitId; patient.PointOfCareId = pocUnknown?.Id; var un = await _unitService.FindById(patientLocation.UnitId); patient.Location = new PatientLocation( un?.Name ?? VirtualPointOfCare.Unknown.ToString(), pocUnknown?.Bed ?? VirtualPointOfCare.Unknown.ToString()); } else { var unit = await _unitService.FindByName(apiRequest.Location.UnitName); var poc = await _pointOfCareService.FindByBedAndUnitId(apiRequest.Location.Bed, unit?.Id); if (unit != null && poc != null) { patient.UnitId = poc.UnitId; patient.PointOfCareId = poc.Id; patient.Location = new PatientLocation( unit.Name, poc.Bed); } else { patient.Location = new PatientLocation( VirtualPointOfCare.Unknown.ToString(), VirtualPointOfCare.Unknown.ToString()); } } _logger.LogDebug( "CREATE PATIENT BECAUSE createPatientWithORU CONFIG IS TRUE {patient}", patient); await Insert(patient); } } } else { patient = await FindPatient(null, apiRequest.PatientNumber, apiRequest.Location, true); if (!string.IsNullOrWhiteSpace(apiRequest.PatientNumber) || (Enum.TryParse(apiRequest.Location?.Bed, out var dataPoC) && dataPoC == VirtualPointOfCare.UnitData)) { if (patient == null) { if (_createPatientWithLocation) { _logger.LogDebug( "PATIENT LOCATION NOT IS EMPTY. Generate patient because createPatientWithLocation is TRUE"); patient = new Patient { Id = ObjectId.GenerateNewId() }; await UpdatePatientFromRequest(patient, apiRequest); _logger.LogDebug( "CREATE PATIENT BECAUSE CreatePatientWithLocation CONFIG IS TRUE {patient}", patient); await Insert(patient); } } else { if (_pushPatientWithOru && apiRequest.PatientNumber != patient.PatientNumber) { var admTime = patient.AdmTime; var msgTime = apiRequest.MessageTime; //Si la fecha de admisión es anterior < a la fecha del mensaje (con un patient number distinto) // estamos recibiendo orus para un paciente que no tenemos aun insertado en esa localización entonces lo creamos desplazando al que está actualmente if (admTime < msgTime) { _logger.LogDebug( "PATIENT LOCATION IS NOT EMPTY AND PATIENT NUMBER IS NOT THE SAME. Generate patient because CreatePatientWithORU is TRUE AND ADMTIME IS OLDER THAN MSGTIME AND PUSH OLD PATIENT {patient}", patient); var unit = await _unitService.FindById(patient.UnitId); await UpdateLocation(patient.Id, new PatientLocation(unit?.Name, VirtualPointOfCare.Pushed.ToString())); patient = new Patient { Id = ObjectId.GenerateNewId() }; await UpdatePatientFromRequest(patient, apiRequest); _logger.LogDebug( "CREATE PATIENT BECAUSE CreatePatientWithORU CONFIG IS TRUE {patient}", patient); await Insert(patient); } } } } } } else { // PATIENT FOUND BY PATIENT NUMBER _logger.LogDebug("PATIENT FOUND BY PATIENT NUMBER"); if (apiRequest.Location is { Bed: not null } && !apiRequest.Location.Equals(patient.Location)) { //Al cambiar de localizacion set active nueva localizacion set innactive old localizacion // PATIENT LOCATION HAS CHANGED _logger.LogDebug("PATIENT LOCATION HAS CHANGED"); if (_updatePatientLocationWithOru) { // UPDATE PATIENT LOCATION BECAUSE updatePatientLocationWithORU CONFIG IS TRUE _logger.LogDebug( "UPDATE PATIENT LOCATION BECAUSE updatePatientLocationWithORU CONFIG IS TRUE"); patient.Location = apiRequest.Location; var patientExistsInLocation = await FindByLocation(apiRequest.Location); if (patientExistsInLocation != null && patient.PatientNumber != patientExistsInLocation.PatientNumber) { var unit = await _unitService.FindById(patientExistsInLocation.UnitId); var newLocation = new PatientLocation(unit?.Name, VirtualPointOfCare.Unknown.ToString()); await UpdateLocation(patientExistsInLocation.Id, newLocation); } // ONLY ADTS archive if (patientExistsInLocation != null) patientService.ArchivePatient(patientExistsInLocation); await ProcessUpdatePatientLocationWithOru(patient, apiRequest); } } } if (_updatePatientDataWithOru && patient != null) { if (apiRequest.AttendingDoctor != null && !apiRequest.AttendingDoctor.Equals(patient.AttendingDoctor)) { // PATIENT DOCTOR CHANGED _logger.LogDebug("PATIENT FOUND BY LOCATION"); patient.AttendingDoctor = apiRequest.AttendingDoctor; await UpdateAttendingDoctor(patient.Id, apiRequest.AttendingDoctor); } if (apiRequest.Patient != null && !apiRequest.Patient.Equals(patient.Person)) { // PATIENT DATA CHANGED _logger.LogDebug("PATIENT DATA CHANGED"); if (apiRequest.Patient?.BirthDate != null) { patient.Person ??= new Person(); patient.Person.BirthDate = apiRequest.Patient.BirthDate; } if (apiRequest.Patient?.Language != null) { patient.Person ??= new Person(); patient.Person.Language = apiRequest.Patient.Language; } if (apiRequest.Patient?.FirstName != null) { patient.Person ??= new Person(); patient.Person.FirstName = apiRequest.Patient.FirstName; } if (apiRequest.Patient?.SecondName != null) { patient.Person ??= new Person(); patient.Person.SecondName = apiRequest.Patient.SecondName; } if (apiRequest.Patient?.LastName != null) { patient.Person ??= new Person(); patient.Person.LastName = apiRequest.Patient.LastName; } if (apiRequest.Patient?.Ids is { Count: > 0 }) { patient.Person ??= new Person(); patient.Person.SetIds(apiRequest.Patient.Ids, apiRequest.MessageTime); } //bool updatePatientNumber = false; //patient.patient = admRequest.patient; //Solo actualizamos patientNumber de paciente si es de ICCA el mensaje. En central puede haber patient numbers incorrectos. if (apiRequest.PatientNumber is { Length: > 0 } && !string.IsNullOrWhiteSpace(apiRequest.Facility) && apiRequest.Facility.Equals(_iccaFacility)) patient.PatientNumber = apiRequest.PatientNumber; //UpdatePatientData(patient.id, patient.patientNumber, patient.patient); if (patient.PatientNumber != null) await UpdatePatientData(patient.Id, patient.PatientNumber, patient); } } break; } } catch (Exception ex) { _logger.LogError("ERROR SAVING REQUEST: Exception: {exMessage} ", ex.Message); return null; } return patient; } public async Task> FindAll(bool withLocation = false) { var patients = await _patientRepository.FindAll(); if (!withLocation) return patients; foreach (var patient in patients) { if (patient.PointOfCareId.HasValue) { var poc = await _pointOfCareService.FindById(patient.PointOfCareId.Value); patient.Bed = poc?.Bed; patient.Room = poc?.Room; } if (patient.UnitId.HasValue) { var unit = await _unitService.FindById(patient.UnitId); patient.UnitString = unit?.Name; } } return patients; } public async Task> FindInActivePoC() { return await _patientRepository.FindInActivePoC(); } public async Task> FindInInactivePoC() { return await _patientRepository.FindInInactivePoC(); } public async Task> GetPaginatedPatients(PaginationFilter filter) { var result = _patientRepository.GetPaginatedPatients(filter); var count = await result.CountDocumentsAsync(); var data = await result.Skip((filter.PageNumber - 1) * filter.PageSize) .Limit(filter.PageSize) .ToCursorAsync(); var dataList = await data.ToListAsync(); return new PaginationResponse(dataList, filter.PageNumber, filter.PageSize, count); } public async Task FindByUnitAndPocId(ObjectId unit, ObjectId pointOfCare) { return await _patientRepository.FindByUnitAndPocId(unit, pointOfCare); } public async Task> FindByPointOfCare(string pointOfCare) { return await _patientRepository.FindByPointOfCare(pointOfCare); } public async Task MergePatient(Patient patient, string oldPatientNumber) { var oldLocation = patient.PointOfCareId; // sectionService.FindByPatient(patient.id); var oldPatient = await FindByPatientNumber(oldPatientNumber); if (oldPatient == null) { _logger.LogDebug( "the patient has not been merged: {oldPatientNumber} in patient {patientpatientId} , old patient not found ", oldPatientNumber, patient.PatientId); return; } var oldId = oldPatient.Id; _logger.LogDebug("merge patient action: {oldPatientNumber} in {patientpatientId}", oldPatientNumber, patient.PatientId); //TODO: TO CHECK _ = _observationService.Value.UpdateManyObjectId("patientid", patient.Id, oldId); _ = _treatmentService.Value.UpdateManyObjectId("patientid", patient.Id, oldId); _ = _diagnosisService.UpdateManyObjectId("patientid", patient.Id, oldId); _ = _appointmentService.UpdateManyObjectId("patientid", patient.Id, oldId); _ = _pumpService.Value.UpdateManyObjectId("patientid", patient.Id, oldId); _ = _recordingAlertService.UpdateManyObjectId("patientid", patient.Id, oldId); _ = _patientCarePlanService.UpdateManyObjectId("patientId", patient.Id, oldId); _ = _patientRepository.Delete(oldId); if (oldLocation.HasValue) { await UpdatePatientFromMerge(patient, oldPatient); await SendDeletePatientBroadcast(patient.Id, oldLocation.Value); _pointOfCareService.CheckNextAdmission(oldPatient.PointOfCareId); } } public async Task DischargeInactivePatients(DateTime sinceDate, int hoursBeforeArchive) { try { GlobalData.AddData("isCheckingInactivePatients", true); await ArchiveDischargedPatients(hoursBeforeArchive); await ArchiveNotUpdatedPatientsInInactivePoCSince(hoursBeforeArchive); await ArchivePatientWithoutObservationsSinceDate(sinceDate); } catch (Exception ex) { _logger.LogError("Error discharging inactive patients, error: {exMessage}, trace: {exStackTrace}", ex.Message, ex.StackTrace); } GlobalData.AddData("isCheckingInactivePatients", false); } public async Task UpdateOne(Patient updatedPatient) { var oldPatient = await _patientRepository.FindById(updatedPatient.Id); var patient = await _patientRepository.UpdateOne(updatedPatient); await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldPatient, patient); return patient; } public async Task UpdatePatientAltable(ObjectId patientId, OptionList altable, User? user) { var patient = await _patientRepository.FindByPatientId(patientId); var oldPatient = await _auditService.DeepCopyAsync(patient); if (patient == null) { _logger.LogError("Patient id: {id} not found", patientId); return null; } var aux = patient.Altable; patient.Altable = altable; await _patientRepository.Update(patient); if (oldPatient != null) await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldPatient, patient); await SendPatientBroadcast(patient); await GenerateNurseObsAndInsert(_listSettings.AltableOptionList.ManualObservationName!, [altable], patient.Id, user); //si son iguales no actualiza if (altable.Name.Equals(aux?.Name)) return patient; if (!patient.PointOfCareId.HasValue) return patient; var poc = await _pointOfCareService.FindById(patient.PointOfCareId.Value); //Si antes era NotAltable y cambia se crea un registro en discharge if (aux?.OptionType is null or "NotAltable") { var discharge = await _dischargeService.GetDischargeByPatientId(patient.Id); var unit = await _unitService.FindById(patient.UnitId); if (discharge == null) { var dischargeToInsert = new Discharge { DischargeDate = DateTime.UtcNow, Patient = patient, PatientId = patientId, PatientLocation = new PatientLocation(unit?.Name, poc?.Bed, poc?.Room), PointOfCareId = poc?.Id, UnitId = poc?.UnitId }; await _dischargeService.InsertDischarge(dischargeToInsert); } else { discharge.PatientId = patientId; discharge.Patient = patient; discharge.PatientLocation = new PatientLocation(unit?.Name, poc?.Bed, poc?.Room); discharge.PointOfCareId = poc?.Id; discharge.UnitId = poc?.UnitId; await _dischargeService.UpdateDischargeAsync(discharge); } } //Si pasa a ser NotAltable borramos registro en discharge if ("NotAltable".Equals(altable.OptionType)) { var discharge = await _dischargeService.GetDischargeByPatientId(patient.Id); if (discharge != null) await _dischargeService.DeleteDischargeAsync(discharge); } return patient; } /// /// Delete discharge and archive patient by id. /// /// /// /// public async Task ExitPatientById(ObjectId id, bool archivePatient = true) { try { var patient = await FindById(id); if (patient == null) { _logger.LogError("Patient not found by id: {id}", id); return; } var discharge = await _dischargeService.GetDischargeByPatientId(patient.Id); if (discharge != null) { await _dischargeService.DeleteDischargeAsync(discharge); _logger.LogInformation("Discharge deleted. {discharge}", discharge); } if (archivePatient) await ArchivePatient(patient); _logger.LogInformation("ExitPatientById: {id}", id); _pointOfCareService.CheckNextAdmission(patient.PointOfCareId); } catch (Exception ex) { _logger.LogError("Exception exiting patient. Exception:{ex}", ex); throw; } } public async Task UpdatePatientMasterList(ObjectId patientId, MasterListType typeName, List updatedOptions, User? user, List? carePlanLog) { try { var patient = await FindById(patientId); if (patient == null) { _logger.LogError("Error updating Patient Master List. Patient not found by Id:{id} ", patientId); return null; } switch (typeName) { case MasterListType.TreatmentList: await GenerateNurseCarePlanAndInsert(MasterListType.TreatmentList, carePlanLog, patient, user); patient.Treatment = updatedOptions; break; case MasterListType.AllergyList: patient.Allergies = updatedOptions; await GenerateNurseObsAndInsert(_listSettings.AllergyList.ManualObservationName!, updatedOptions, patient.Id, user); break; case MasterListType.ProcedureList: await GenerateNurseCarePlanAndInsert(MasterListType.ProcedureList, carePlanLog, patient, user); patient.Procedures = updatedOptions; break; case MasterListType.TestList: await GenerateNurseCarePlanAndInsert(MasterListType.TestList, carePlanLog, patient, user); patient.Tests = updatedOptions; break; case MasterListType.DoctorList: patient.Doctors = updatedOptions; await GenerateNurseObsAndInsert(_listSettings.DoctorList.ManualObservationName!, updatedOptions, patient.Id, user); break; case MasterListType.LanguageBarrierList: patient.LanguageBarrier = updatedOptions; await GenerateNurseObsAndInsert(_listSettings.LanguageBarrierList.ManualObservationName!, updatedOptions, patient.Id, user); break; case MasterListType.MobilityOptionList: patient.Mobility = updatedOptions.FirstOrDefault(); await GenerateNurseObsAndInsert(_listSettings.MobilityOptionList.ManualObservationName!, updatedOptions, patient.Id, user); break; case MasterListType.OriginList: patient.Origin = updatedOptions.FirstOrDefault(); await GenerateNurseObsAndInsert(_listSettings.OriginList.ManualObservationName!, updatedOptions, patient.Id, user); break; case MasterListType.PatientStatusList: patient.PatientStatus = updatedOptions.FirstOrDefault(); await GenerateNurseObsAndInsert(_listSettings.PatientStatusList.ManualObservationName!, updatedOptions, patient.Id, user); break; case MasterListType.TherapeuticCeilingList: patient.TherapeuticCeiling = updatedOptions.FirstOrDefault(); await GenerateNurseObsAndInsert(_listSettings.TherapeuticCeilingList.ManualObservationName!, updatedOptions, patient.Id, user); break; case MasterListType.VisitOptionList: patient.Visits = updatedOptions.FirstOrDefault(); await GenerateNurseObsAndInsert(_listSettings.VisitOptionList.ManualObservationName!, updatedOptions, patient.Id, user); break; case MasterListType.AccessControlList: patient.AccessControl = updatedOptions.FirstOrDefault(); await GenerateNurseObsAndInsert(_listSettings.AccessControlList.ManualObservationName!, updatedOptions, patient.Id, user); break; case MasterListType.AltableOptionList: patient.Altable = updatedOptions.FirstOrDefault(); await GenerateNurseObsAndInsert(_listSettings.AltableOptionList.ManualObservationName!, updatedOptions, patient.Id, user); break; case MasterListType.DiagnosisList: patient.Diagnosis = updatedOptions.FirstOrDefault(); await GenerateNurseObsAndInsert(_listSettings.DiagnosisList.ManualObservationName!, updatedOptions, patient.Id, user); break; case MasterListType.DischargeStatusList: patient.DischargeStatus = updatedOptions.FirstOrDefault(); await GenerateNurseObsAndInsert(_listSettings.DischargeStatusList.ManualObservationName!, updatedOptions, patient.Id, user); break; case MasterListType.InsulationList: patient.Insulation = updatedOptions.FirstOrDefault(); await GenerateNurseObsAndInsert(_listSettings.InsulationList.ManualObservationName!, updatedOptions, patient.Id, user); break; case MasterListType.PassiveSittingList: patient.PassiveSitting = updatedOptions.FirstOrDefault(); await GenerateNurseObsAndInsert(_listSettings.PassiveSittingList.ManualObservationName!, updatedOptions, patient.Id, user); break; case MasterListType.GenericList: await GenerateNurseObsAndInsert(_listSettings.GenericList.ManualObservationName!, updatedOptions, patient.Id, user); break; default: await GenerateNurseObsAndInsert(updatedOptions.FirstOrDefault()?.Name ?? "UNKNOWNOBS", updatedOptions, patient.Id, user); break; } await Update(patient); return patient; } catch (Exception ex) { _logger.LogError("Exception updating patient id:{id}. Data: {type} . Exception: {ex}", patientId, typeName, ex); return null; } } public async Task GenerateNurseCarePlanAndInsert(MasterListType carePlanType, List? options, Patient patient, User? user) { OptionList? addedItems = null; OptionList? removedItems = null; OptionList? updatedItems = null; OptionList? option = null; switch (carePlanType) { case MasterListType.ProcedureList: var patientProcedures = patient.Procedures ?? []; // Encuentra elementos agregados: están en optionsList pero no en patientProcedures addedItems = options?.FirstOrDefault(opt => patientProcedures.All(proc => proc.Name != opt.Name)); if (addedItems != null) { option = addedItems; break; } // Encuentra elementos actualizados: están en ambas listas pero tienen diferencias en sus propiedades updatedItems = options? .FirstOrDefault(opt => patientProcedures.Any(proc => proc.Name == opt.Name && !proc.AreEqualExcludeDescription(proc, opt))); if (updatedItems != null) { option = updatedItems; option.OptionType = "procedure"; } // Encuentra elementos eliminados: están en patientProcedures y en optionsList pero tienen una descripción if (options?.FirstOrDefault()?.Description != null) { removedItems = options.FirstOrDefault(); if (removedItems != null) removedItems.OptionType = "procedure"; } break; case MasterListType.TestList: var patientTests = patient.Tests ?? []; // Encuentra elementos agregados: están en optionsList pero no en patientProcedures addedItems = options?.FirstOrDefault(opt => patientTests.All(test => test.Name != opt.Name)); if (addedItems != null) { option = addedItems; break; } // Encuentra elementos actualizados: están en ambas listas pero tienen diferencias en sus propiedades updatedItems = options? .FirstOrDefault(opt => patientTests.Any(test => test.Name == opt.Name && !test.AreEqualExcludeDescription(test, opt))); if (updatedItems != null) { option = updatedItems; option.OptionType = "test"; } // Encuentra elementos eliminados: están en patientProcedures y en optionsList pero tienen una descripción if (options?.FirstOrDefault()?.Description != null) { removedItems = options.FirstOrDefault(); if (removedItems != null) removedItems.OptionType = "test"; } break; case MasterListType.TreatmentList: var patientTreatment = patient.Treatment ?? []; // Encuentra elementos agregados: están en optionsList pero no en patientProcedures addedItems = options?.FirstOrDefault(opt => patientTreatment.All(proc => proc.Name != opt.Name)); if (addedItems != null) { option = addedItems; option.OptionType = "treatment"; break; } // Encuentra elementos actualizados: están en ambas listas pero tienen diferencias en sus propiedades updatedItems = options? .FirstOrDefault(opt => patientTreatment.Any(proc => proc.Name == opt.Name && !proc.AreEqualExcludeDescription(proc, opt))); if (updatedItems != null) { option = updatedItems; option.OptionType = "treatment"; } // Encuentra elementos eliminados: están en patientProcedures y en optionsList pero tienen una descripción if (options?.FirstOrDefault()?.Description != null) { removedItems = options.FirstOrDefault(); if (removedItems != null) removedItems.OptionType = "treatment"; } break; } ActionsEnum.CrudAction? action = addedItems != null ? ActionsEnum.CrudAction.Create : updatedItems != null ? ActionsEnum.CrudAction.Update : removedItems?.Description is "Archive" ? ActionsEnum.CrudAction.Archive : ActionsEnum.CrudAction.Delete; var patietCarePlan = new PatientCarePlan { PatientId = patient.Id, PatientNumber = patient.PatientNumber, PointOfCareId = patient.PointOfCareId, Action = action, CarePlan = action is ActionsEnum.CrudAction.Delete or ActionsEnum.CrudAction.Archive ? removedItems : option, UserId = user?.Id, Description = action == ActionsEnum.CrudAction.Delete && string.IsNullOrEmpty(removedItems?.Description) ? "No reason given" : removedItems?.Description }; await _patientCarePlanService.InsertOneAsync(patietCarePlan); if (action != ActionsEnum.CrudAction.Delete && action != ActionsEnum.CrudAction.Archive && removedItems != null) { var secondaryAction = removedItems.Description is "Archive" ? ActionsEnum.CrudAction.Archive : ActionsEnum.CrudAction.Delete; var patietCarePlanSecondaryAction = new PatientCarePlan { PatientId = patient.Id, PatientNumber = patient.PatientNumber, PointOfCareId = patient.PointOfCareId, Action = secondaryAction, CarePlan = removedItems, UserId = user?.Id, Description = secondaryAction == ActionsEnum.CrudAction.Delete && string.IsNullOrEmpty(option?.Description) ? "No reason given" : option?.Description }; await _patientCarePlanService.InsertOneAsync(patietCarePlanSecondaryAction); } } public async Task UpdatePatientIncomingData(ObjectId patientId, Patient person) { var oldPatient = await _patientRepository.FindByPatientId(patientId) ?? throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed); var pat = await _patientRepository.UpdatePatientIncomingData(patientId, person) ?? throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed); await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldPatient, pat); await SendPatientBroadcast(pat); } public async Task UpdatePatientIncomingData(ObjectId patientId, Patient person, PatientIncomeData patientIncomeData, User user) { await UpdatePatientIncomingData(patientId, person); var obs = new PatientObservation { Id = ObjectId.GenerateNewId(), Name = "PatientIncomingData", Value = patientIncomeData, PatientId = patientId, Time = DateTime.Now, UserId = user.Id }; await _observationService.Value.InsertNurseObservation(obs); } public async Task UpdatePatientMasterListItemChange(UpdateOptionMasterListDto opt, IEnumerable unitList, string typeName) { // Filtrar paciente por unidad y valor del item de la lista var unitIds = unitList.Select(x => x.Id).ToList(); var oldPatientList = await _patientRepository.GetPatientsByUnitIds(unitIds, typeName); var patientUpdatedList = await _patientRepository.UpdateMasterListOption(unitIds, opt, typeName); foreach (var patient in patientUpdatedList) { var oldPatient = oldPatientList.Find(p => p.Id == patient.Id); await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldPatient, patient); await SendPatientBroadcast(patient); } } public async Task DeletePatientMasterListItem(OptionList opt, IEnumerable unitList, string typeName) { var unitIds = unitList.Select(x => x.Id).ToList(); var patientUpdatedList = await _patientRepository.DeleteMasterListOption(unitIds, opt, typeName); foreach (var patient in patientUpdatedList) { var patientUpdated = await FindById(patient.Id); if (patientUpdated == null) continue; await SendPatientBroadcast(patientUpdated); var obsName = GetManualObservationName(typeName); var obs = await _observationService.Value.FindLastObservationsByField(patientUpdated.Id, new List { new() { Name = obsName } }); if (obs.FirstOrDefault() != null) { var newObs = obs.First().Value as List; newObs?.RemoveAll(c => c.Id == opt.Id); await GenerateNurseObsAndInsert(obsName, newObs, patient.Id, null); } } } public async Task UpdatePatientDemographicData(ObjectId patientId, Patient person, User? user) { var oldPatient = await _patientRepository.FindByPatientId(patientId) ?? throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed); var pat = await _patientRepository.UpdatePatientDemographicData(patientId, person); if (pat != null) { await UpdatePatientMasterList(pat.Id, MasterListType.AllergyList, pat.Allergies??[], user, null); var lbl = pat.LanguageBarrier != null ? pat.LanguageBarrier : null; var dl = pat.Diagnosis != null ? new List { pat.Diagnosis } : null; await UpdatePatientMasterList(pat.Id, MasterListType.LanguageBarrierList, lbl??[], user, null); await UpdatePatientMasterList(pat.Id, MasterListType.DiagnosisList, dl??[], user, null); await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldPatient, pat); await SendPatientBroadcast(pat); } } public Task SearchByPatientNumberAndDistinctUnit(string patientNumber, ObjectId unitId) { return _patientRepository.SearchByPatientNumberAndDistinctUnit(patientNumber, unitId); } public async Task> FindAllPatientWithFinishedProcedures(int archiveProcedureEndDateAfterMinutes) { return await _patientRepository.FindAllPatientWithFinishedProcedures(archiveProcedureEndDateAfterMinutes); } public async Task> FindAllPatientWithFinishedTest(int archiveTestEndDateAfterMinutes) { return await _patientRepository.FindAllPatientWithFinishedTests(archiveTestEndDateAfterMinutes); } public async Task> FindAllPatientWithFinishedTreatment(int archiveTreatmentEndDateAfterMinutes) { return await _patientRepository.FindAllPatientWithFinishedTreatment(archiveTreatmentEndDateAfterMinutes); } private bool CheckPatient(Patient patient) { //patient.AdmTime ??= DateTime.UtcNow; _logger.LogDebug("Inserting patient with patientNumber: {PatientpatientNumber} {Patient}", patient.PatientNumber, patient); if (patient.PointOfCareId == null && patient.UnitId == null) { _logger.LogWarning( "Not Inserted patient with patientNumber: {PatientpatientNumber}. PointOfCare is null {Patient}", patient.PatientNumber, patient); return false; } if (string.IsNullOrEmpty(patient.PatientNumber)) { _logger.LogWarning("person with patientNumber null or empty: {Patient}", patient); if (!_cretatePatientWithoutPatientNumber) { _logger.LogWarning( "CretatePatientWithoutPatientNumber: {CretatePatientWithoutPatientNumber}: person not inserted: {Patient} ", _cretatePatientWithoutPatientNumber, patient.Id); return false; } } return true; } private async Task SendLastGroupedObsToSubscriberByPoc(ObjectId newPoc, ObjectId? oldPoc, Patient patient) { // Enviar y rellenar los subscriptores que necesitan las observaciones agrupadas para este paciente // Obtén la lista de suscriptores var relevantSubscribers = _subscribersService.GetByPocId(newPoc); // Filtra y selecciona los elementos con DisplayId único var filteredSubscribers = relevantSubscribers .GroupBy(subscriber => subscriber.DisplayId) // Agrupa por DisplayId .Where(group => group.Key != null) // Asegurar de que DisplayId no sea null .Select(group => group.First()) // Selecciona el primer elemento de cada grupo .ToList(); // Convierte el resultado a una lista foreach (var sub in filteredSubscribers) if (sub.DisplayId.HasValue) { var display = await _displayService.GetById(sub.DisplayId.Value); if (display is { DisplayConfigId: not null }) { var config = await _displayConfigService.GetById(display.DisplayConfigId.Value); var configGroupField = config.GroupedFieldList; if (configGroupField != null) foreach (var groupedField in configGroupField) { var groupedObservation = await _groupedObservationService.GenerateGroupedObservation( patient.Id, groupedField); _ = _clientMessageService.SendAsync(sub.Id, OperationType.GroupedObservation, groupedObservation); _subscriberGroupedService.CheckOnSubscriptionGroup(groupedField, patient.Id, "Romance Standard Time", sub.Id, groupedObservation); } } } // En este punto los subscriptores que necesitan las obs agrupadas para este paciente ya las tienen // Y la lista de subscriptorGrouped ya tiene la id del nuevo subscriptor // Ahora habria que ver que subscriptores ya no necesitan las agrupadas para este paciente if (oldPoc.HasValue) { var relevantSubscribersToDelete = _subscribersService.GetByPocId(oldPoc.Value); var relevantSubscribersToDeleteFiltered = relevantSubscribersToDelete.Where(sub => !sub.LocationIds.Contains(newPoc)); foreach (var wsSubscriber in relevantSubscribersToDeleteFiltered) _subscriberGroupedService.RemoveWsSubscriberPatientIdAndWsId(patient.Id.ToString(), wsSubscriber.Id); } } private async Task SendLastObsToSubscriberByPoc(PointOfCare newPoc, Patient patient) { // Obtén la lista de suscriptores var relevantSubscribers = _subscribersService.GetByPocId(newPoc.Id); // Filtra y selecciona los elementos con DisplayId único // var filteredSubscribers = relevantSubscribers // .GroupBy(subscriber => subscriber.DisplayId) // Agrupa por DisplayId // .Where(group => group.Key != null) // Asegúrate de que DisplayId no sea null // .Select(group => group.First()) // Selecciona el primer elemento de cada grupo // .ToList(); // Convierte el resultado a una lista foreach (var sub in relevantSubscribers) if (sub.DisplayId.HasValue) { var display = await _displayService.GetById(sub.DisplayId.Value); if (display is { DisplayConfigId: not null }) { var config = await _displayConfigService.GetById(display.DisplayConfigId.Value); var observations = await _observationService.Value.FindLastObservationsByField(patient.Id, config.FieldList); if (observations.Count > 0) { observations = observations.OrderBy(p => p.Time).ToList(); foreach (var observation in observations) { var obs = await _observationService.Value.MapObservationsByName(observation); if (obs != null) _ = _clientMessageService.SendAsync(sub.Id, OperationType.Observation, obs); } } } } } private async Task UpdateLocation(ObjectId id, ObjectId unitId, ObjectId pocId) { try { _logger.LogDebug("Update location id: {id}", id); var patient = await _patientRepository.FindById(id); var oldPatient = await _auditService.DeepCopyAsync(patient); if (patient is not { PointOfCareId: not null }) { _logger.LogError("person is null. Not updated location for patient id: {id} ", id); return; } var newPoc = await _pointOfCareService.FindById(pocId); var newUnit = await _unitService.FindById(unitId); // El valor de bed: 04 daria como resultado NO_BED por eso comprobamos que no sea un int !int.TryParse(newPoc.Bed, out _) var isMoveToVirtualPoc = newPoc != null && !int.TryParse(newPoc.Bed, out _) && Enum.TryParse(newPoc.Bed, out _); var oldLocation = await _pointOfCareService.FindById(patient.PointOfCareId.Value); var virtualPocOldLocation = VirtualPointOfCare.Unknown; var comesFromVirtualPoc = oldLocation != null && !int.TryParse(oldLocation.Bed, out _) && Enum.TryParse(oldLocation.Bed, out virtualPocOldLocation); if (comesFromVirtualPoc) { var isRecovered = VirtualPointOfCare.Deleted.Equals(virtualPocOldLocation); // Añadir log warning if (isRecovered) { _logger.LogWarning( "Patient is recovered on UpdateLocation patientId: {id}, oldLocation: {oldLocation}, newLocation: {newPoc}", patient.Id, oldLocation?.Location.ToString(), newPoc?.Location.ToString()); patient.DisTime = null; } } patient.PointOfCareId = pocId; patient.UnitId = unitId; var bed = newPoc?.Bed; var room = newPoc?.Room; var location = new PatientLocation(newUnit?.Name, bed, room); patient.Location = location; if (isMoveToVirtualPoc && !comesFromVirtualPoc) patient.Altable = new OptionList { IconDefault = "icNotAltable", OptionType = "NotAltable", Name = "NotAltable" }; await _patientRepository.UpdateOneAsync(patient.Id, patient); if (oldPatient != null) await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldPatient, patient); if (isMoveToVirtualPoc && !comesFromVirtualPoc) { await ExitPatientById(patient.Id, false); _pointOfCareService.CheckNextAdmission(oldLocation?.Id); } if (!isMoveToVirtualPoc && comesFromVirtualPoc) await _pointOfCareService.SetPointOfCareStatus(pocId, StatusEnum.PointOfCare.InUse); //Prio location to advertise both locations of movement between sections _logger.LogDebug( "Move patient: {id} from oldPocId: {oldPocId}, to newPocId: {newPocId}", id, oldLocation != null ? oldLocation.Id : "NULL", pocId); if (!isMoveToVirtualPoc && !comesFromVirtualPoc && oldLocation != null) { await _pointOfCareService.SetPointOfCareStatus(pocId, StatusEnum.PointOfCare.Available); await Move(patient, pocId, oldLocation.Id); } else { _logger.LogDebug( "Patient: {id} not moved because isMoveToVirtualPoc: {isMoveToVirtualPoc}, comesFromVirtualPoc: {comesFromVirtualPoc}, oldLocationIsNotNull: {oldLocation}", id, isMoveToVirtualPoc, comesFromVirtualPoc, oldLocation != null); } //TODO: Revisar // CheckLocationForWsSubscriber(location, id); } catch (Exception ex) { _logger.LogError("error updating location for patient id: {id} exception: {exMessage}", id, ex.Message); } } /// /// If location is updated and pointOfCare are DELETED or PUSHED /// Ws subscriber for GropedObs dont need to keep notificated /// Else could be that patient from Section A move to Section B /// clients from the section that is moved dont need to keep notified /// and other client will ask for grouped obs once receive SendPatientLocationBroadcast /// /// New location where patient is getting updated /// Object Id for patient private void CheckLocationForWsSubscriber(PatientLocation? newLocation, ObjectId? patientId) { if (patientId == null || newLocation == null) return; try { if (newLocation.Bed == VirtualPointOfCare.Pushed.ToString() || newLocation.Bed == VirtualPointOfCare.Deleted.ToString() || newLocation.Bed == VirtualPointOfCare.Moved.ToString()) _subscriberGroupedService.RemoveGroupedObsByPatientId(patientId.Value.ToString()); else _subscriberGroupedService.RemoveWsSubscriberByLocation(patientId.Value.ToString(), newLocation); } catch (Exception ex) { Log.Error( $"ERROR DELETING PATIENT MOVEMENT. person Id: {patientId} New location: {newLocation.ToStr()}. Exception message: {ex.Message} ex: {ex}"); } } /// method ArchiveNotUpdatedPatientsSince archive all patients who have not updated since in /// hoursBeforeArchive /// . /// public async Task ArchiveNotUpdatedPatientsInInactivePoCSince(int hoursBeforeArchive) { _logger.LogDebug( "Archiving not updated and in inactive PoC patients since older than {hoursBeforeArchive} hours", hoursBeforeArchive); try { // Patients not updated since date var dischargedPatients = await _patientRepository.FindPatientsNotUpdatedSince(DateTime.Now.AddHours(-hoursBeforeArchive)); foreach (var patient in dischargedPatients.Where(p => !p.IsInActivePoC())) await ArchivePatient(patient); } catch (Exception ex) { _logger.LogError("Error archiving not updated and in inactive PoC patients {exMessage}", ex); } } public Task SendPatientLocationBroadcast(object patient, PatientLocation location, List? pocs) { if (pocs == null || pocs.Count == 0) return Task.CompletedTask; var subscribers = _subscribersService.GetSubscribers().Where (s => s.LocationIds.Any(pocs.Contains) ).ToList(); subscribers.ForEach(Action); return Task.CompletedTask; void Action(WsSubscriber subscriber) { _clientMessageService.SendAsync(subscriber.Id, OperationType.PatientLocation, new Dictionary { { "patient", patient }, { "location", location } }); } } private async Task SendPatientAttendingDoctorBroadcast(ObjectId patientid, Person attendingDoctor) { var patient = await FindById(patientid); if (patient == null) return; var poc = await _pointOfCareService.FindPoCByPatientId(patient.Id); if (poc == null) return; var subscribers = _subscribersService.GetSubscribers().Where(s => s.LocationIds.Any(sb => sb == poc.Id)) .ToList(); subscribers.ForEach(Action); return; void Action(WsSubscriber subscriber) { _clientMessageService.SendAsync(subscriber.Id, OperationType.PatientAttendingDoctor, new Dictionary { { "id", patientid.ToString() }, { "attendingDoctor", attendingDoctor } }); } } private async Task SendPatientDataBroadcast(ObjectId patientid, Person data) { var patient = await FindById(patientid); if (patient == null) return; //var section = await _sectionService.FindByPointOfCare(patient.UnitString); //if (section == null) return; var subscribers = _subscribersService.GetSubscribers().Where(s => s.LocationIds.Any(c => c == patient.PointOfCareId)); foreach (var subscriber in subscribers) _ = _clientMessageService.SendAsync(subscriber.Id, OperationType.PatientData, new Dictionary { { "id", patientid.ToString() }, { "data", data } }); } private async Task SendPatientBroadcast(Patient patient) { var subscribersGroup = _subscribersService.GetSubscribers() .Where(s => patient.PointOfCareId != null && s.LocationIds.Any(c => c == patient.PointOfCareId.Value)).GroupBy(h => h.Locale); var unit = await _unitService.FindById(patient.UnitId); foreach (var group in subscribersGroup) { var locale = group.Key ?? LocaleEnum.Default; IEnumerable subscribers = group; foreach (var subscriber in subscribers) { var patientWithLocale = await _masterListServiceFactory.GetPatientTraslated(unit, locale, patient); _ = _clientMessageService.SendAsync(subscriber.Id, OperationType.UpdatePatient, patientWithLocale); } } } private Task SendDeletePatientBroadcast(ObjectId patientid, ObjectId pocId) { var subscribers = _subscribersService.GetSubscribers().Where(s => s.LocationIds.Any(x => x == pocId)).ToList(); _logger.LogDebug("Remove grouped obs for patientId: {patientid} on location: {location}", patientid, pocId); _subscriberGroupedService.RemoveGroupedObsByPatientId(patientid.ToString()); subscribers.ForEach(Action); return Task.CompletedTask; void Action(WsSubscriber subscriber) { _clientMessageService.SendAsync(subscriber.Id, OperationType.DeletePatient, new Dictionary { { "id", patientid.ToString() } }); } } private void UpdatePatientFromRequestToIdList(Patient patient, ApiRequest apiRequest) { if (apiRequest.AdmTime.HasValue) patient.AdmTime = apiRequest.AdmTime; if (!string.IsNullOrEmpty(apiRequest.PatientId)) patient.PatientId = apiRequest.PatientId; if (apiRequest.Patient != null && !apiRequest.Patient.IsEmpty()) { // Log action to describe patient.Person ??= new Person(); patient.Person.BirthDate = apiRequest.Patient.BirthDate; patient.Person.Language = apiRequest.Patient.Language; patient.Person.FirstName = apiRequest.Patient.FirstName; patient.Person.Gender = apiRequest.Patient.Gender; patient.Person.LastName = apiRequest.Patient.LastName; patient.Person.SecondName = apiRequest.Patient.SecondName; } if (apiRequest.Patient?.Ids != null) { patient.Person ??= new Person(); patient.Person.SetIds(apiRequest.Patient.Ids, apiRequest.MessageTime == DateTime.MinValue ? DateTime.UtcNow : apiRequest.MessageTime); } if (apiRequest.DisTime.HasValue) patient.DisTime = apiRequest.DisTime; patient.UpdateDate = DateTime.UtcNow; } private async Task RemovePatientAndSendToVirtualPoc(ApiRequest apiRequest, Patient patient, VirtualPointOfCare virtualPocEnum) { var virtualPoc = await _pointOfCareService.FindByBedAndUnitId(virtualPocEnum.ToString(), patient.UnitId); if (virtualPoc == null) _logger.LogError( "Error getting virtual poc {virtualPocEnum} PATIENT: {patientId} on unit: {patientUnitId} continue with autogenerate pocId", virtualPocEnum.ToString(), patient.Id, patient.UnitId); var uni = await _unitService.FindById(patient.UnitId); var oldLocation = patient.PointOfCareId; patient.DisTime = apiRequest.DisTime ?? DateTime.UtcNow; patient.PointOfCareId = virtualPoc?.Id ?? new ObjectId(); patient.UnitId = virtualPoc?.UnitId ?? patient.UnitId; patient.Location = new PatientLocation(uni?.Name, virtualPoc?.Bed ?? $"Error on {virtualPocEnum.ToString()}", virtualPoc?.Room ?? $"Error on {virtualPocEnum.ToString()}"); await _patientRepository.UpdateOneAsync(patient.Id, patient); _logger.LogDebug("{virtualPocEnum} PATIENT: {patientId} ", virtualPoc?.ToString(), patient.Id); await ExitPatientById(patient.Id, false); if (virtualPoc != null && oldLocation.HasValue) _ = SendLastGroupedObsToSubscriberByPoc(virtualPoc.Id, oldLocation.Value, patient); _pointOfCareService.CheckNextAdmission(oldLocation); } // * To ensure db sync with ICCA we receive their patients data table and sync with our patients. // * GetByCodeSysAndCode all boxes from all sections and match with iccaPatients private async Task ProcessIccaSync(ApiRequest apiRequest) { //var sections = await _sectionService.GetAll(); // var boxes = sections.Where(box => box.Items != null).SelectMany(s => s.Items.SelectMany(d => d.Boxes)) // ?.ToList(); //var boxes = sections.SelectMany(s => s.items.SelectMany(d => d.boxes)).ToList(); var pocList = await _pointOfCareService.GetAllLocationInfo(); var unitList = await _unitService.GetAll(); foreach (var pointOfCare in pocList) pointOfCare.Unit = unitList.FirstOrDefault(u => u.Id == pointOfCare.UnitId); var iccaPatientsFromOtherLocations = apiRequest.IccaPatients?.Select(x => x.DeepCopy()).ToList(); foreach (var poc in pocList) { if (poc.Unit == null) continue; var iccaPatientOnBox = apiRequest.IccaPatients?.FirstOrDefault(i => i.Location.Bed == poc.Bed && i.Location.UnitName == poc.Unit.Name); if (iccaPatientOnBox != null) iccaPatientsFromOtherLocations?.RemoveAll(s => s.PatientNumber == iccaPatientOnBox.PatientNumber); //Find actual patient in poc var actualPatient = await FindByPointOfCareId(poc.Id); // 1 Exists in ICCA and not in our DB => create it and if any other patient is on bed move it to Temporal. if (iccaPatientOnBox != null && actualPatient == null && iccaPatientOnBox.PatientNumber != null) { //check if patient exists to only move var patientInOtherBox = await FindByPatientNumber(iccaPatientOnBox.PatientNumber); if (patientInOtherBox != null) await UpdateLocation(patientInOtherBox.Id, new PatientLocation(poc.Unit.Name, poc.Bed, poc.Room)); else await Insert(new Patient { AdmTime = iccaPatientOnBox.AdmitTime, PatientId = iccaPatientOnBox.PatientId, PatientNumber = iccaPatientOnBox.PatientNumber, Location = new PatientLocation(poc.Unit.Name, poc.Bed, poc.Room), UnitId = poc.UnitId, PointOfCareId = poc.Id }); } // 2 Exists in ICCA and in our DB => equal patientNumber same person, continue else move actual and create new from icca else if (iccaPatientOnBox != null && actualPatient != null) { if (iccaPatientOnBox.PatientNumber == actualPatient.PatientNumber) { //They are the same do anything. } else { await UpdateLocation(actualPatient.Id, new PatientLocation(poc.Unit.Name, VirtualPointOfCare.Moved.ToString(), VirtualPointOfCare.Moved.ToString())); await Insert(new Patient { AdmTime = iccaPatientOnBox.AdmitTime, PatientId = iccaPatientOnBox.PatientId, PatientNumber = iccaPatientOnBox.PatientNumber, Location = new PatientLocation(poc.Unit.Name, poc.Bed, poc.Room), UnitId = poc.UnitId, PointOfCareId = poc.Id }); } } // 3 Exists in our DB and not in ICCA => move it to temporal bed to match patients on layout boxes. else if (iccaPatientOnBox == null && actualPatient != null) { //move it to another bed await UpdateLocation(actualPatient.Id, new PatientLocation(poc.Unit.Name, VirtualPointOfCare.Unknown.ToString(), VirtualPointOfCare.Unknown.ToString())); } } if (iccaPatientsFromOtherLocations == null) return; foreach (var patientOutOfConfigBoxes in iccaPatientsFromOtherLocations) { //Patients from others PointsOfCares or box not in our configured sections. we save them if (patientOutOfConfigBoxes.PatientNumber == null) continue; //check exists var patientExists = await FindByPatientNumber(patientOutOfConfigBoxes.PatientNumber); if (patientExists != null) continue; var patientLocation = await FindByLocation(patientOutOfConfigBoxes.Location); //move it to another bed if (patientLocation != null) { var unitName = unitList.FirstOrDefault(u => u.Id == patientLocation.UnitId); await UpdateLocation(patientLocation.Id, new PatientLocation(unitName?.Name ?? "UNKNOWN", VirtualPointOfCare.Unknown.ToString(), VirtualPointOfCare.Unknown.ToString())); } var pocUnknown = pocList.FirstOrDefault(u => u.Bed == VirtualPointOfCare.Unknown.ToString()); var locationFromRequest = pocList.FirstOrDefault(u => u.Bed == patientOutOfConfigBoxes.Location.Bed && u.Unit?.Name == patientOutOfConfigBoxes.Location.UnitName); await Insert(new Patient { AdmTime = patientOutOfConfigBoxes.AdmitTime, PatientId = patientOutOfConfigBoxes.PatientId, PatientNumber = patientOutOfConfigBoxes.PatientNumber, PointOfCareId = locationFromRequest?.Id ?? pocUnknown?.Id ?? ObjectId.GenerateNewId(), UnitId = locationFromRequest?.UnitId ?? pocUnknown?.UnitId ?? ObjectId.GenerateNewId() }); } } /*For adt_a02 || adt_a12 */ private async Task ProcessAdtMovePatient(ApiRequest apiRequest, Patient? patient) { try { var patientExistsInLocation = await FindByLocation(apiRequest.Location); if (patient is { PatientNumber: not null } && apiRequest.Patient != null && patient.PatientNumber == apiRequest.PatientNumber && _updatePatientDataWithAdtA02) { _logger.LogDebug( "Debg MovePatient, UpdatePatientDataWithADT_A02 is enabled, patient number found {pn} and apirequest patient number {pnApirequest} are the same update demographic data", patient.PatientNumber, apiRequest.PatientNumber); //await UpdatePatientFromRequest(patient, apiRequest, true); await UpdatePatientData(patient.Id, patient.PatientNumber, apiRequest.Patient, false); } if (patientExistsInLocation != null) { if (patientExistsInLocation.PatientNumber != apiRequest.PatientNumber) { _logger.LogDebug( "Debug MovePatient, patient to move is null but patientExistsInLocation is not null moving patient: {patientExistsInLocation}, to pointOfCare MOVED", patientExistsInLocation); var movedLocationPoc = await _pointOfCareService.FindByBedAndUnitId( VirtualPointOfCare.Moved.ToString(), patientExistsInLocation.UnitId); if (movedLocationPoc != null) await UpdateLocation(patientExistsInLocation.Id, movedLocationPoc.UnitId, movedLocationPoc.Id); } else { // Demographic data only return; } } if (patient != null) { //to solve problems when empty beds with not scapped "" if (apiRequest.Location != null) { var bed = apiRequest.Location.Bed?.Replace("\"", ""); apiRequest.Location.Bed = string.IsNullOrEmpty(bed) ? VirtualPointOfCare.Unknown.ToString() : bed; if (apiRequest.Location.Bed == VirtualPointOfCare.Unknown.ToString()) { var unknownLocationPoc = await _pointOfCareService.FindByBedAndUnitId( VirtualPointOfCare.Unknown.ToString(), patient.UnitId); if (unknownLocationPoc != null) await UpdateLocation(patient.Id, unknownLocationPoc.UnitId, unknownLocationPoc.Id); } else { var mapLocation = await _pocMappingService.Map(apiRequest.Location); var newLocationUnit = await _unitService.FindByName(apiRequest.Location.UnitName); var newLocationPoc = await _pointOfCareService.FindByBedAndUnitId(mapLocation?.Bed, newLocationUnit?.Id); if (newLocationPoc != null) { await UpdateLocation(patient.Id, newLocationPoc.UnitId, newLocationPoc.Id); } else { var unknownLocationPoc = await _pointOfCareService.FindByBedAndUnitId( VirtualPointOfCare.Unknown.ToString(), patient.UnitId); if (unknownLocationPoc != null) { await UpdateLocation(patient.Id, unknownLocationPoc.UnitId, unknownLocationPoc.Id); _logger.LogError( "Unable to find new location on ProcessAdtMovePatient location: {Unit} {Bed}", apiRequest.Location.UnitName, apiRequest.Location?.Bed); } } } } else { _logger.LogDebug("Move patient to null location transfering patient to UNKNOWN bed"); var unknownLocationPoc = await _pointOfCareService.FindByBedAndUnitId( VirtualPointOfCare.Unknown.ToString(), patient.UnitId); var unitUnknowPoc = await _unitService.FindById(patient.UnitId); if (unknownLocationPoc == null || unitUnknowPoc == null) { _logger.LogError( "Unable to find new location on ProcessAdtMovePatient location: Unit: {Unit}, Bed: {Bed}", unitUnknowPoc?.Name ?? "UNKNOWN", VirtualPointOfCare.Unknown.ToString()); return; } apiRequest.Location = new PatientLocation(unitUnknowPoc.Name, unknownLocationPoc.Bed, unknownLocationPoc.Room); await UpdateLocation(patient.Id, unitUnknowPoc.Id, unknownLocationPoc.Id); } _logger.LogDebug( "Debug MovePatient, patient not null to move: {patient} Bed: {apiRequestLocationBed}, PointOfCare: {apiRequestLocationPointOfCare}", patient, apiRequest.Location?.Bed, apiRequest.Location?.UnitName); } else { // if (patientExistsInLocation != null) // { // if (patientExistsInLocation.PatientNumber != apiRequest.PatientNumber) // { // _logger.LogDebug( // "Debug MovePatient, patient to move is null but patientExistsInLocation is not null moving patient: {patientExistsInLocation}, to pointOfCare MOVED", // patientExistsInLocation); // // await UpdateLocation(patientExistsInLocation.Id,movedLocationPoc.UnitId, movedLocationPoc.Id); // } // } patient = await CreatePatientFromRequest(apiRequest); if (patient != null) { var newLocationUnit = await _unitService.FindByName(apiRequest.Location?.UnitName); var newLocationPoc = await _pointOfCareService.FindByBedAndUnitId(apiRequest.Location?.Bed, newLocationUnit?.Id); if (newLocationPoc == null) { _logger.LogError("Unable to find new location on ProcessAdtMovePatient location: {Unit} {Bed}", apiRequest.Location?.UnitName, apiRequest.Location?.Bed); return; } _logger.LogDebug( "person to move was null on ADT_A02 newPatient: {patient}, proceed to send notification to front", patient); await SendNewPatientBroadcast(patient); await _pointOfCareService.SetPointOfCareStatus(newLocationPoc.Id, StatusEnum.PointOfCare.InUse); //_pointOfCareService.CheckNextAdmission(newLocationPoc.Id); } } } catch (Exception e) { _logger.LogError("Error on ProcessAdtMovePatient trace: {eStackTrace}, message: {eMessage}", e.StackTrace, e.Message); } } /// /// Process new patient. try to find him by /// /// or /// /// if exits and was not discharged use him. /// If not insert new one in patients table /// /// /// The Inserted patient or null. private async Task ProcessAdtPatientAdmit(ApiRequest apiRequest) { Patient? patient; try { if (apiRequest.Location != null) apiRequest.Location = await _pocMappingService.Map(apiRequest.Location); patient = await FindPatient(apiRequest.PatientId, apiRequest.PatientNumber, apiRequest.Location); //Null ADTA01 var unit = await _unitService.FindByName(apiRequest.Location?.UnitName); if (unit == null) { _logger.LogDebug("Process admit patient to unit name: {UnitName} NOT FOUND", apiRequest.Location?.UnitName); return null; } var poc = await _pointOfCareService.FindByBedAndUnitId(apiRequest.Location?.Bed, unit.Id); if (poc == null) { _logger.LogDebug("Process admit patient to bed name: {BedName} NOT FOUND", apiRequest.Location?.Bed); return null; } var patientExistsInLocation = await FindByUnitAndPocId(unit.Id, poc.Id); //if previous patient exists is moved to PUSHED if (patientExistsInLocation != null) { _logger.LogDebug("person Exists InLocation: {PatientExistsInLocation}", patientExistsInLocation.ToStr()); if (patient == null || patientExistsInLocation.PatientNumber != patient.PatientNumber) { _logger.LogDebug("previous patient: {PatientExistsInLocationId} moved to PUSHED ", patientExistsInLocation.Id); _logger.LogDebug( "Remove grouped obs for patientId: {ApiRequestPatientId} patient exist on location for new patient location: {PatientExistsInLocationLocation}, traceId: {ApiRequestTraceId}, traceMessageId: {ApiRequestTraceMessageId}", apiRequest.PatientId ?? "NULL", patientExistsInLocation.Location, apiRequest.TraceId ?? "NULL", apiRequest.TraceMessageId ?? "NULL"); var pocPushed = await _pointOfCareService.FindByBedAndUnitId(VirtualPointOfCare.Pushed.ToString(), unit.Id); if (pocPushed != null) { await UpdateLocation(patientExistsInLocation.Id, unit.Id, pocPushed.Id); _ = SendLastGroupedObsToSubscriberByPoc(pocPushed.Id, poc.Id, patientExistsInLocation); } else { _logger.LogError( "Unable to update location for existing patient: {Patient} on poc: {VirtualPoc} and unit: {Unit}", patientExistsInLocation.Id.ToString(), VirtualPointOfCare.Pushed.ToString(), unit.Id.ToString()); } } //patient already exists and is the same again, do nothing else if (patient.PatientNumber == patientExistsInLocation.PatientNumber && patient.Location.Bed == patientExistsInLocation.Location.Bed && patient.Location.UnitName == patientExistsInLocation.Location.UnitName) { _logger.LogDebug("patient: {PatientId} already exist, ignoring ADT", patient.Id); return null; } } _logger.LogDebug("person dsTime:{PatientDisTime}", patient?.DisTime?.ToString() ?? "NULL"); if (patient is not { DisTime: null }) //If new patient, it returns true because patient not contains a field disTime == null { var creationDate = patient?.CreationDate ?? DateTime.UtcNow; if (patient != null) { patient.CreationDate = creationDate; patient.UnitId = unit.Id; patient.DisTime = null; patient.PointOfCareId = poc.Id; patient.Altable = new OptionList { IconDefault = "icNotAltable", Name = "NotAltable", OptionType = "NotAltable" }; } else { patient = new Patient { Id = ObjectId.GenerateNewId(), CreationDate = creationDate, UnitId = unit.Id, DisTime = null, PointOfCareId = poc.Id, Altable = new OptionList { IconDefault = "icNotAltable", Name = "NotAltable", OptionType = "NotAltable" } }; } } _logger.LogDebug("Updating patient from request. person: {Patient} ", patient.ToStr()); await UpdatePatientFromRequest(patient, apiRequest); if (patient.PatientNumber != null) { var patientInAdmission = await _admissionService.Value.GetAdmissionByPatientNumber(patient.PatientNumber); if (patientInAdmission != null) { await MergePatientWithAdmission(patient, patientInAdmission); await _admissionService.Value.DeleteAdmissionByIdAsync(patientInAdmission.Id); } } _logger.LogDebug("Inserting patient. person: {Patient}", patient.ToStr()); await Update(patient); _ = SendNewPatientBroadcast(patient); await _pointOfCareService.SetPointOfCareStatus(poc.Id, StatusEnum.PointOfCare.InUse); // Enviar en base a la config de display que tenga cada subscriptor _ = SendLastObsToSubscriberByPoc(poc, patient); // Enviar las grupadas dependiendo del config del subscriptor _ = SendLastGroupedObsToSubscriberByPoc(poc.Id, null, patient); _logger.LogDebug("Updated patient: {PatientId}", patient.Id); } catch (Exception e) { _logger.LogError("Exception processing patient admission : {EMessage}", e); return null; } return patient; } private async Task MergePatientWithAdmission(Patient patient, Admission patientInAdmission) { patient.Insulation = patientInAdmission.Insulation; if (patient.Insulation != null) await GenerateNurseObsAndInsert(_listSettings.InsulationList.ManualObservationName!, [patient.Insulation], patient.Id, null); patient.Allergies = patientInAdmission.Allergies; if (patient.Allergies != null) await GenerateNurseObsAndInsert(_listSettings.AllergyList.ManualObservationName!, patient.Allergies, patient.Id, null); patient.Diagnosis = patientInAdmission.Diagnosis; if (patient.Diagnosis != null) await GenerateNurseObsAndInsert(_listSettings.DiagnosisList.ManualObservationName!, [patient.Diagnosis], patient.Id, null); patient.DiagnosisAux = patientInAdmission.DiagnosisAux; patient.LanguageBarrier = patientInAdmission.LanguageBarrier; if (patient.LanguageBarrier != null) await GenerateNurseObsAndInsert(_listSettings.LanguageBarrierList.ManualObservationName!, patient.LanguageBarrier, patient.Id, null); patient.Origin = patientInAdmission.Origin; if (patient.Origin != null) await GenerateNurseObsAndInsert(_listSettings.OriginList.ManualObservationName!, [patient.Origin], patient.Id, null); patient.OriginAux = patientInAdmission.OriginAux; } private async Task UpdatePatientFromRequest(Patient patient, ApiRequest apiRequest, bool ignoreLocation = false) { if (apiRequest.AdmTime.HasValue) patient.AdmTime = apiRequest.AdmTime; if (!string.IsNullOrEmpty(apiRequest.PatientId)) patient.PatientId = apiRequest.PatientId; if (apiRequest.Patient != null && !apiRequest.Patient.IsEmpty()) { // Log action to describe patient.Person ??= new Person(); patient.Person.BirthDate = apiRequest.Patient.BirthDate; patient.Person.Language = apiRequest.Patient.Language; patient.Person.FirstName = apiRequest.Patient.FirstName; patient.Person.Gender = apiRequest.Patient.Gender; patient.Person.LastName = apiRequest.Patient.LastName; patient.Person.SecondName = apiRequest.Patient.SecondName; } var patientNumberChanged = true; if (!string.IsNullOrEmpty(apiRequest.PatientNumber)) { // Log action to describe patientNumberChanged = patient.PatientNumber != apiRequest.PatientNumber; patient.PatientNumber = apiRequest.PatientNumber; } if (patientNumberChanged && apiRequest.Patient?.Ids != null) { patient.Person ??= new Person(); patient.Person.SetIds(apiRequest.Patient.Ids, apiRequest.MessageTime); } var unit = await _unitService.FindByName(apiRequest.Location?.UnitName); if (unit == null && !ignoreLocation) _logger.LogWarning( "WARNING updating patient from request, unit not found by name: {UnitName} for patientId: {PatientId} apiRequest: {Request}", apiRequest.Location?.UnitName ?? "", patient.Id, apiRequest.ToString()); if (apiRequest.AttendingDoctor != null && !apiRequest.AttendingDoctor.IsEmpty()) patient.AttendingDoctor = apiRequest.AttendingDoctor; if (apiRequest.Location != null && !apiRequest.Location.IsEmpty() && apiRequest.Location.Bed != null && apiRequest.Location.UnitName != null && !ignoreLocation && unit != null) { var poc = await _pointOfCareService.FindByBedAndUnitId(apiRequest.Location.Bed, unit.Id); if (poc != null) { patient.Location = new PatientLocation(unit.Name, poc.Bed, poc.Room); patient.UnitId = unit.Id; patient.PointOfCareId = poc.Id; } else { _logger.LogError( "Error updating patient from request finding poc by bed name: {BedName} and unitId: {UnitId} not found request: {ApiRequest}", apiRequest.Location?.Bed, unit.Id, apiRequest); } } //To solve multi "sin cama" from ICCADB making duplicated keys error on mongodb. if (apiRequest.Location != null && patient.Location.Bed != null && (patient.Location.Bed.ToLower().Equals("sin cama") || patient.Location.Bed == string.Empty) && unit != null) { // Log action to describe var poc = await _pointOfCareService.FindByBedAndUnitId(VirtualPointOfCare.NoBed.ToString(), unit.Id); if (poc != null) { patient.Location = new PatientLocation(unit.Name, poc.Bed, poc.Room); patient.UnitId = unit.Id; patient.PointOfCareId = poc.Id; } else { _logger.LogError( "Error updating patient from request with bed empty finding poc by bed name: {BedName} and unitId: {UnitId} not found request: {ApiRequest}", apiRequest.Location.Bed, unit.Id, apiRequest); } } if (apiRequest.DisTime.HasValue) patient.DisTime = apiRequest.DisTime; patient.UpdateDate = DateTime.UtcNow; } private async Task ProcessUpdatePatientLocationWithOru(Patient patient, ApiRequest apiRequest) { await UpdateLocation(patient.Id, apiRequest.Location); _ = SendPatientUpdateBroadcast(patient); } public async Task UpdatePatientFromMerge(Patient patient, Patient oldPatient) { var listObsName = _masterListServiceFactory.StringNurseObs(); var obs = await _observationService.Value.FindLastObservations(patient.Id, 1, listObsName); patient.Visits = obs.FirstOrDefault(o => o.Name == _listSettings.VisitOptionList.ManualObservationName)?.Value is List visitList ? visitList.FirstOrDefault() : null; patient.AccessControl = obs.FirstOrDefault(o => o.Name == _listSettings.AccessControlList.ManualObservationName)?.Value is List accessControList ? accessControList.FirstOrDefault() : null; patient.TherapeuticCeiling = obs.FirstOrDefault(o => o.Name == _listSettings.TherapeuticCeilingList.ManualObservationName)?.Value is List ceilingList ? ceilingList.FirstOrDefault() : null; patient.Mobility = obs.FirstOrDefault(o => o.Name == _listSettings.MobilityOptionList.ManualObservationName)?.Value is List mobilityList ? mobilityList.FirstOrDefault() : null; patient.PassiveSitting = obs.FirstOrDefault(o => o.Name == _listSettings.PassiveSittingList.ManualObservationName)?.Value is List passiveList ? passiveList.FirstOrDefault() : null; patient.LanguageBarrier = obs.FirstOrDefault(o => o.Name == _listSettings.LanguageBarrierList.ManualObservationName)?.Value as List; patient.Insulation = obs.FirstOrDefault(o => o.Name == _listSettings.InsulationList.ManualObservationName)?.Value is List insulationList ? insulationList.FirstOrDefault() : null; patient.Doctors = obs.FirstOrDefault(o => o.Name == _listSettings.DoctorList.ManualObservationName) ?.Value as List; patient.Allergies = obs.FirstOrDefault(o => o.Name == _listSettings.AllergyList.ManualObservationName)?.Value as List; patient.Altable = obs.FirstOrDefault(o => o.Name == _listSettings.AltableOptionList.ManualObservationName)?.Value is List altableList ? altableList.FirstOrDefault() : null; var obsPatientIncomingData = (PatientIncomeData?)obs.FirstOrDefault(o => o.Name == "PatientIncomingData")?.Value; if (obsPatientIncomingData != null) { patient.AdmTime = obsPatientIncomingData.AdmTime; patient.Origin ??= obsPatientIncomingData.Origin; patient.OriginAux ??= obsPatientIncomingData.OriginAux; patient.Diagnosis ??= obsPatientIncomingData.Diagnosis; patient.DiagnosisAux ??= obsPatientIncomingData.DiagnosisAux; } if (patient.Treatment != null) { var missingTreatments = oldPatient.Treatment? .Where(t => patient.Treatment.All(p => p.Name != t.Name)) .ToList(); if (missingTreatments != null) patient.Treatment.AddRange(missingTreatments); } else if (oldPatient.Treatment != null) { patient.Treatment = oldPatient.Treatment; } if (patient.Procedures != null) { var missingProcedures = oldPatient.Procedures? .Where(t => patient.Procedures.All(p => p.Name != t.Name)) .ToList(); if (missingProcedures != null) patient.Procedures.AddRange(missingProcedures); } else if (oldPatient.Procedures != null) { patient.Procedures = oldPatient.Procedures; } if (patient.Tests != null) { var missingTests = oldPatient.Tests? .Where(t => patient.Tests.All(p => p.Name != t.Name)) .ToList(); if (missingTests != null) patient.Tests.AddRange(missingTests); } else if (oldPatient.Tests != null) { patient.Tests = oldPatient.Tests; } await _patientRepository.Update(patient); await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldPatient, patient); await SendPatientBroadcast(patient); } private async Task GenerateNurseObsAndInsert(string name, List? value, ObjectId patientId, User? user) { var obs = GenerateObs( name, value ?? [], patientId); obs.UserId = user?.Id; await _observationService.Value.InsertNurseObservation(obs); } private PatientObservation GenerateObs(string name, List? value, ObjectId patientId) { var valueList = value ?? []; return new PatientObservation { Name = name, Value = valueList, PatientId = patientId, Time = DateTime.Now, InsertMode = ObservationEnum.InsertMode.Manual }; } public string GetManualObservationName(string masterListNameString) { // 1. Obtener el tipo de la clase ListSettings. var settingsType = typeof(ListSettings); // 2. Obtener la propiedad con el nombre que coincide con el string del enum. // El 'masterListNameString' debe coincidir exactamente con el nombre de la propiedad. var propertyInfo = settingsType.GetProperty(masterListNameString); if (propertyInfo == null) // La propiedad no se encontró (el string no coincide con un nombre de propiedad). return $"Error: Propiedad '{masterListNameString}' no encontrada en ListSettings."; // 3. Obtener la instancia de ListSettingItem (el valor) de esa propiedad, // usando la instancia 'settings' proporcionada. if (propertyInfo.GetValue(_listSettings) is not ListSettingItem listSettingItem) // Esto no debería suceder si la clase ListSettings está bien definida, // pero es una buena práctica comprobarlo. return "Error: El valor de la propiedad no es un ListSettingItem."; // 4. Devolver el valor de ManualObservationName. return listSettingItem.ManualObservationName!; } private enum OnArchiveAction { Delete, Archive } }