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.DTO; using adas_core.Domain.Models.Masters; using adas_core.Domain.Models.MongoModels; using adas_core.Domain.Models.Responses; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using MongoDB.Bson; namespace adas_core.Application.Services; public class AdmissionService( ILogger logger, ISubscribersService subscribersService, IAdmissionRepository admissionRepository, IClientMessageService clientMessageService, IUnitService unitService, IPatientService patientService, IPointOfCareService pointOfCareService, IDisplayService displayService, IDischargeService dischargeService, IPatientArchiveRepository patientArchiveRepository, IHttpContextAccessor httpContextAccessor, ILocalAuditService auditService, IMasterListServiceFactory masterListServiceFactory) : IAdmissionService { // Auditory logs public async Task DeleteAdmissionAsync(Admission admission) { await DeleteAdmissionByIdAsync(admission.Id); } public async Task DeleteAdmissionByIdAsync(ObjectId admissionId) { var admissionAux = await admissionRepository.FindById(admissionId); if (admissionAux == null) { logger.LogInformation("Error deleting Admission not found, id: {AdmissionId} ", admissionId); return; } await admissionRepository.Delete(admissionId); if (admissionAux.PointOfCareId.HasValue) { var poc = await pointOfCareService.GetInfo(admissionAux.PointOfCareId.Value); if (poc != null && poc.AdmissionId == admissionId) { poc.AdmissionId = null; poc.Admission = null; await pointOfCareService.Update(poc); if (poc.Status != StatusEnum.PointOfCare.Locked && poc.Status != StatusEnum.PointOfCare.InUse) await pointOfCareService.SetPointOfCareStatus(poc.Id, StatusEnum.PointOfCare.Available); } } logger.LogInformation("Admission id: {AdmissionId} DELETED ", admissionId); SendAdmissionBroadcast(admissionAux, OperationType.DeleteAdmission); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, admissionAux, null); } public async Task DeleteAdmissionsByUnitId(ObjectId unitId) { _ = await admissionRepository.DeleteAdmissionsByUnitId(unitId); } public async Task GetAdmissionByIdAsync(ObjectId admissionId) { var result = await admissionRepository.FindById(admissionId); if (result?.PointOfCareId != null) { var poc = await pointOfCareService.GetInfo(result.PointOfCareId.Value, null,false); result.PatientLocation = new PatientLocation(poc?.UnitName, poc?.Bed, poc?.Room); } return result; } public async Task> GetAdmissionsAsync() { var resultList = await admissionRepository.FindAll() ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); var admissionsAsync = resultList.ToList(); foreach (var admission in admissionsAsync) if (admission.PointOfCareId != null) { var poc = await pointOfCareService.GetInfo(admission.PointOfCareId.Value, null,false); admission.PatientLocation = new PatientLocation(poc?.UnitName, poc?.Bed, poc?.Room); } return admissionsAsync; } public async Task InsertAdmission(Admission admission) { var admissionAux = await admissionRepository.FindByNhc(admission.Nhc); if (admissionAux != null) throw new ConflictException(HttpEnum.ErrorMessage.BadRequestDuplicateData); PointOfCare? pointOfCare = null; //Bloqueamos el pointOfCare en el caso de que lo tenga asignado if (admission.PointOfCareId.HasValue) { pointOfCare = await pointOfCareService.GetInfo(admission.PointOfCareId.Value) ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); //pointOfCare.AdmissionId = admission.Id; //pointOfCare.Admission = admission; admission.PatientLocation = new PatientLocation(pointOfCare.UnitName, pointOfCare.Bed, pointOfCare.Room); } var insertedAdmission = await admissionRepository.InsertOneAsyncAndReturn(admission) ?? throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed); if (pointOfCare is { AdmissionId: null }) { pointOfCare.Admission = insertedAdmission; pointOfCare.AdmissionId = insertedAdmission.Id; if (pointOfCare.Status == StatusEnum.PointOfCare.Available) pointOfCare.Status = StatusEnum.PointOfCare.Reserved; await pointOfCareService.Update(pointOfCare); } SendAdmissionBroadcast(insertedAdmission, OperationType.NewAdmission); // Obtener información el usuario autenticado await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, insertedAdmission); return insertedAdmission; } public async Task UpdateAdmissionAsync(Admission admission) { var oldAdmission = await admissionRepository.FindById(admission.Id); if (oldAdmission == null) return; if (oldAdmission.PointOfCareId.HasValue) { var pocOld = await pointOfCareService.GetInfo(oldAdmission.PointOfCareId.Value, null,false); if (pocOld != null) oldAdmission.PatientLocation = new PatientLocation(pocOld.UnitName, pocOld.Bed, pocOld.Room); } if (admission.PointOfCareId.HasValue) { var poc = await pointOfCareService.GetInfo(admission.PointOfCareId.Value, null,false); if (poc != null) admission.PatientLocation = new PatientLocation(poc.UnitName, poc.Bed, poc.Room); } await admissionRepository.Update(admission); await HandlePointOfCareChange(admission, oldAdmission); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldAdmission, admission); SendAdmissionBroadcast(admission, OperationType.UpdateAdmission); } public async Task AdmitPatient(Admission admission, bool isNew = false) { if (admission.PointOfCareId == null) { logger.LogError("PointOfCare is required. Admission: {Admission}", admission); return; } var unit = await unitService.FindById(admission.UnitId); if (unit == null) { logger.LogError("Unit {Name} not found. Unit Id: ", admission.UnitId); return; } var pointOfCare = await pointOfCareService.FindById(admission.PointOfCareId.Value); if (pointOfCare == null) { logger.LogError("Point of Care not found. Patient not created. {Admission}", admission); return; } Patient patient = new() { PointOfCareId = pointOfCare.Id, UnitId = pointOfCare.UnitId, PointOfCare = pointOfCare, UnitString = pointOfCare.UnitName, Bed = pointOfCare.Bed, Room = pointOfCare.Room, PatientNumber = admission.Nhc, AdmTime = DateTime.UtcNow, Person = admission.Person, CreationDate = DateTime.UtcNow, DischargeStatus = pointOfCare.Unit?.DischargeStatusList?.Options.FirstOrDefault(), Origin = admission.Origin, OriginAux = admission.OriginAux, Diagnosis = admission.Diagnosis, DiagnosisAux = admission.DiagnosisAux, Allergies = admission.Allergies, Insulation = admission.Insulation, LanguageBarrier = admission.LanguageBarrier, PassiveSitting = admission.PassiveSitting, Altable = new OptionList { Name = "NotAltable", IconDefault = "icNotAltable", OptionType = "NotAltable" }, Visits = pointOfCare.Unit?.VisitOptionList?.Options.FirstOrDefault(), AccessControl = pointOfCare.Unit?.AccessControlList?.Options.FirstOrDefault(), Location = new PatientLocation ( unit.Name, pointOfCare.Bed, pointOfCare.Room ) }; if (unit.AltableOptionListId is not null) { var list = await masterListServiceFactory.GetMasterListById(MasterListType.AltableOptionList, unit.AltableOptionListId.Value, LocaleEnum.Default) as AltableOptionList; var listOpt = list?.Options.FirstOrDefault(c => c.OptionType == "NotAltable"); if (listOpt != null) patient.Altable = listOpt; } await patientService.Insert(patient); await SetPointOfCareStatus(pointOfCare.Id, StatusEnum.PointOfCare.InUse); if (!isNew) await DeleteAdmissionAsync(admission); if (admission.Insulation != null) await patientService.UpdatePatientMasterList( patient.Id, MasterListType.InsulationList, [admission.Insulation], null, null); if (admission.Allergies != null) await patientService.UpdatePatientMasterList( patient.Id, MasterListType.AllergyList, admission.Allergies, null, null); if (admission.Diagnosis != null) await patientService.UpdatePatientMasterList( patient.Id, MasterListType.DiagnosisList, [admission.Diagnosis], null, null); if (admission.Origin != null) await patientService.UpdatePatientMasterList( patient.Id, MasterListType.OriginList, [admission.Origin], null, null); if (admission.LanguageBarrier != null) await patientService.UpdatePatientMasterList( patient.Id, MasterListType.LanguageBarrierList, admission.LanguageBarrier, null, null); if (admission.PassiveSitting != null) await patientService.UpdatePatientMasterList( patient.Id, MasterListType.PassiveSittingList, [admission.PassiveSitting], null, null); } public async Task ReturnPatientToAdmissions(ObjectId patientId) { var patient = await patientService.FindById(patientId); if (patient == null) { logger.LogError("Error returning the patient Id: {Id} to admission", patientId); return; } var unit = await unitService.FindById(patient.UnitId); if (unit == null) { logger.LogError("Unit not found by Id. {Name}", patient.UnitId); return; } if (patient.PointOfCareId != null) { var poc = await pointOfCareService.FindById(patient.PointOfCareId.Value); Admission admission = new() { Nhc = patient.PatientNumber ?? string.Empty, PointOfCareId = poc?.Id, UnitId = unit.Id, Person = patient.Person ?? new Person(), //No debería ser null en este punto, pero así quito el warning Origin = patient.Origin, OriginAux = patient.OriginAux, Diagnosis = patient.Diagnosis, PassiveSitting = patient.PassiveSitting, DiagnosisAux = patient.DiagnosisAux, Allergies = patient.Allergies, Insulation = patient.Insulation, LanguageBarrier = patient.LanguageBarrier, AdmissionDate = patient.AdmTime ?? DateTime.UtcNow, PatientLocation = patient.Location }; await InsertAdmission(admission); } var dis = await dischargeService.GetDischargeByPatientId(patient.Id); if (dis != null) await dischargeService.DeleteDischargeByIdAsync(dis.Id); await patientService.ArchivePatient(patient); pointOfCareService.CheckNextAdmission(patient.PointOfCareId); } // Used for temporal beds like PUSHED public async Task ReturnPatientToAdmissions(ObjectId patientId, Admission adm) { var patient = await patientService.FindById(patientId); if (patient == null) { logger.LogError("Error returning the patient Id: {Id} to admission", patientId); return; } var unit = await unitService.FindById(adm.UnitId); if (unit == null) { logger.LogError("Unit not found by Id. {Name}", patient.UnitId); return; } if (adm.PointOfCareId != null) { var poc = await pointOfCareService.FindById(adm.PointOfCareId.Value); Admission admission = new() { Nhc = patient.PatientNumber ?? string.Empty, PointOfCareId = poc?.Id, UnitId = unit.Id, Person = patient.Person ?? new Person(), //No debería ser null en este punto, pero así quito el warning Origin = patient.Origin, OriginAux = patient.OriginAux, Diagnosis = patient.Diagnosis, DiagnosisAux = patient.DiagnosisAux, Allergies = patient.Allergies, PassiveSitting = patient.PassiveSitting, Insulation = patient.Insulation, LanguageBarrier = patient.LanguageBarrier, AdmissionDate = patient.AdmTime ?? DateTime.UtcNow, PatientLocation = patient.Location }; await InsertAdmission(admission); } var dis = await dischargeService.GetDischargeByPatientId(patient.Id); if (dis != null) await dischargeService.DeleteDischargeByIdAsync(dis.Id); await patientService.ArchivePatient(patient); pointOfCareService.CheckNextAdmission(patient.PointOfCareId); } public async Task> GetAdmissionByLocation(PatientLocation location) { try { return await admissionRepository.FindByLocation(location); } catch (Exception e) { logger.LogError("Error getting admission by patient location {Location} exception: {Ex}", location, e.Message); return []; } } public async Task> GetAdmissionByPointOfCareId(ObjectId pocId) { try { var result = await admissionRepository.FindByPointOfCareId(pocId); foreach (var admission in result) { var poc = await pointOfCareService.GetInfo(pocId, null,false); if (poc != null) admission.PatientLocation = new PatientLocation(poc.UnitName, poc.Bed, poc.Room); } return result; } catch (Exception e) { logger.LogError("Error getting admission by patient PocId {PocId} exception: {Ex}", pocId, e.Message); return []; } } public async Task> GetAdmissionByPointOfCareIdAndLocale(ObjectId pocId, LocaleEnum locale) { var admissions = await GetAdmissionByPointOfCareId(pocId); // Ejecutar todas las traducciones en paralelo var translatedAdmissions = await Task.WhenAll( admissions.Select(adm => GetAdmissionWithLocale(adm, locale)) ); return translatedAdmissions.ToList(); } public async Task> GetAdmissionByUnitIdWithOutPoC(ObjectId unitId) { try { return await admissionRepository.GetAdmissionByUnitIdWithOutPoC(unitId); } catch (Exception e) { logger.LogError("Error getting admission by unitId with out PoCId {UnitId} exception: {Ex}", unitId, e.Message); return []; } } public async Task CountAdmissionsByUnitId(ObjectId unitId) { try { return await admissionRepository.CountByUnitId(unitId); } catch (Exception e) { logger.LogError("Error getting admission by unitId with out PoCId {UnitId} exception: {Ex}", unitId, e.Message); return 0; } } public async Task SearchByPatientNumberAndDistinctUnit(string patientNumber, ObjectId unitId) { var patient = await patientService.FindByPatientNumber(patientNumber); if (patient != null) { if (patient.PointOfCareId.HasValue) patient.PointOfCare = await pointOfCareService.FindById(patient.PointOfCareId.Value); if (patient.UnitId.HasValue) { var uni = await unitService.FindById(patient.UnitId.Value); if (uni != null) patient.UnitString = uni.Name; } } var archivePatient = await patientArchiveRepository.SearchByPatientNumberAndDistinctUnit(patientNumber, unitId); var admission = await admissionRepository.SearchByPatientNumberAndDistinctUnit(patientNumber, unitId); var result = new PatientSearch(patient, archivePatient, admission, patient == null && archivePatient != null, patient != null || archivePatient != null || admission != null); return result; } public Task GetAdmissionByPatientNumber(string patientNumber) { return admissionRepository.FindByNhc(patientNumber); } public async Task UpdatePatientMasterListItemChange(UpdateOptionMasterListDto opt, IEnumerable unitList, string typeName) { var unitIds = unitList.Select(x => x.Id).ToList(); var oldAdmissionList = await admissionRepository.FindByUnitIds(unitIds); var admissionUpdatedList = await admissionRepository.UpdateMasterListOption(unitIds, opt, typeName); foreach (var admission in admissionUpdatedList) { var oldAdmission = oldAdmissionList.Find(adm => adm.Id == admission.Id); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldAdmission!, admission); SendAdmissionBroadcast(admission, OperationType.UpdateAdmission); } } public async Task DeletePatientMasterListItem(OptionList opt, IEnumerable unitList, string typeName) { var unitIds = unitList.Select(x => x.Id).ToList(); var oldAdmissionList = await admissionRepository.FindByUnitIds(unitIds); var admissionUpdatedList = await admissionRepository.DeleteMasterListOption(unitIds, opt, typeName); foreach (var admission in admissionUpdatedList) { var admissionUpdated = await GetAdmissionByIdAsync(admission.Id); var oldAdmission = oldAdmissionList.Find(adm => adm.Id == admission.Id); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldAdmission!, admissionUpdated); if (admissionUpdated != null) SendAdmissionBroadcast(admissionUpdated, OperationType.UpdateAdmission); } } public async Task SaveRequest(ApiRequest apiRequest) { try { if (apiRequest.Admission == null) return; switch (apiRequest.Type) { case "NewAdmission": { if (string.IsNullOrEmpty(apiRequest.Admission.Nhc) || apiRequest.Admission.Origin == null || apiRequest.Admission.Diagnosis == null) { logger.LogDebug( "Error saving admission api request. Some values are required. Admission: {Admission}", apiRequest.Admission); return; } await InsertAdmission(apiRequest.Admission); break; } case "UpdateAdmission": { if (string.IsNullOrEmpty(apiRequest.Admission.Nhc) || apiRequest.Admission.Origin == null || apiRequest.Admission.Diagnosis == null) { logger.LogDebug( "Error updating admission api request. Some values are required. Admission: {Admission}", apiRequest.Admission); return; } await UpdateAdmissionAsync(apiRequest.Admission); break; } case "DeleteAdmission": { await DeleteAdmissionAsync(apiRequest.Admission); break; } } } catch (Exception ex) { logger.LogError("Exception updating admission {Admission} . Exception: {Ex}", apiRequest.Admission, ex.Message); throw; } } public Task SaveRequestAsync(ApiRequest apiRequest) { return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest); })); } private async Task HandlePointOfCareChange(Admission admission, Admission oldAdmission) { // Check if PointOfCare has changed. if (admission.PointOfCareId == oldAdmission.PointOfCareId) return; // Disable new PointOfCare if it exists. if (admission.PointOfCareId.HasValue) { var newPoc = await pointOfCareService.GetInfo(admission.PointOfCareId.Value); if (newPoc == null) return; var patientOnNewPoc = await patientService.FindByPointOfCareId(newPoc.Id); if (newPoc.AdmissionId == null && newPoc.Status != StatusEnum.PointOfCare.Locked) { newPoc.Admission = admission; newPoc.AdmissionId = admission.Id; newPoc.Status = patientOnNewPoc != null ? StatusEnum.PointOfCare.InUse : StatusEnum.PointOfCare.Reserved; await pointOfCareService.Update(newPoc); } if (newPoc.Status == StatusEnum.PointOfCare.Available) pointOfCareService.CheckNextAdmission(newPoc.Id); } // Enable previous PointOfCare if it exists. if (oldAdmission.PointOfCareId.HasValue) { var oldPoc = await pointOfCareService.GetInfo(oldAdmission.PointOfCareId.Value); if (oldPoc != null) { var patientOnOldPoc = await patientService.FindByPointOfCareId(oldPoc.Id); if (oldPoc.AdmissionId == oldAdmission.Id && oldPoc.Status != StatusEnum.PointOfCare.Locked) { oldPoc.Admission = null; oldPoc.AdmissionId = null; oldPoc.Status = patientOnOldPoc != null ? StatusEnum.PointOfCare.InUse : StatusEnum.PointOfCare.Available; await pointOfCareService.Update(oldPoc); } } if (oldPoc is { Status: StatusEnum.PointOfCare.Available }) pointOfCareService.CheckNextAdmission(oldPoc.Id); } } private async Task SetPointOfCareStatus(ObjectId pointOfCareId, StatusEnum.PointOfCare status) { var pointOfCare = await pointOfCareService.FindById(pointOfCareId); if (pointOfCare == null) return; await pointOfCareService.SetPointOfCareStatus(pointOfCareId, status); } private async void SendAdmissionBroadcast(Admission admission, OperationType operation) { try { if (admission.PointOfCareId == null) await SendAdmissionByUnitId(admission, operation); else await SendAdmissionBroadcastByPoC(admission, operation); } catch (Exception ex) { logger.LogError("Exception sending admission broadcast. Operation type: {Op}. Exception: {Ex}", operation.ToString(), ex.Message); } } private async Task SendAdmissionBroadcastByPoC(Admission admission, OperationType operation) { if (!admission.PointOfCareId.HasValue) { logger.LogError("Error sending admission broadcast. PointOfCare id {Id} not found", admission.PointOfCareId); return; } var pointOfCare = await pointOfCareService.FindById(admission.PointOfCareId.Value); if (pointOfCare == null) { logger.LogError("Error sending admission broadcast. PointOfCare id {Id} not found", admission.PointOfCareId); return; } var pocSubscribers = subscribersService.GetSubscribers().Where(s => s.LocationIds.Contains(admission.PointOfCareId.Value)).GroupBy(h => h.Locale); foreach (var group in pocSubscribers) { var locale = group.Key ?? LocaleEnum.Default; IEnumerable subscribers = group; foreach (var subscriber in subscribers) { var admissionWithLocale = await GetAdmissionWithLocale(admission, locale); _ = clientMessageService.SendAsync(subscriber.Id, operation, admissionWithLocale); } } } private async Task SendAdmissionByUnitId(Admission admission, OperationType operation) { if (admission.UnitId == ObjectId.Empty) { logger.LogError("Error sending admission broadcast. UnitId is null or empty {Admission}", admission); return; } var displays = await displayService.GetByUnitId(admission.UnitId); var displayIds = displays.Select(c => c.Id).ToList(); var unitSubscribers = subscribersService.GetSubscribers().Where(s => s.DisplayId != null && displayIds.Contains(s.DisplayId.Value)).GroupBy(h => h.Locale); foreach (var group in unitSubscribers) { var locale = group.Key ?? LocaleEnum.Default; IEnumerable subscribers = group; foreach (var subscriber in subscribers) { var admissionWithLocale = await GetAdmissionWithLocale(admission, locale); _ = clientMessageService.SendAsync(subscriber.Id, operation, admissionWithLocale); } } } private async Task GetAdmissionWithLocale(Admission admission, LocaleEnum locale) { var unit = await unitService.FindById(admission.UnitId); if (unit == null) return admission; if (admission.Origin?.Name != null && unit.OriginListId.HasValue) { if (await masterListServiceFactory.GetMasterListById(MasterListType.OriginList, unit.OriginListId.Value, locale) is OriginList list) { var listOrigin = list.Options.FirstOrDefault(c => c.Id == admission.Origin.Id)?.Name; if (listOrigin != null) admission.Origin.Name = listOrigin; } } if (admission.Diagnosis?.Name != null && unit.DiagnosisListId.HasValue) { if (await masterListServiceFactory.GetMasterListById(MasterListType.DiagnosisList, unit.DiagnosisListId.Value, locale) is DiagnosisList list) { var listDiagnosis = list.Options.FirstOrDefault(c => c.Id == admission.Diagnosis.Id)?.Name; if (listDiagnosis != null) admission.Diagnosis.Name = listDiagnosis; } } if (admission.Insulation?.Name != null && unit.InsulationListId.HasValue) { if (await masterListServiceFactory.GetMasterListById(MasterListType.InsulationList, unit.InsulationListId.Value, locale) is InsulationList list) { var listInsulation = list.Options.FirstOrDefault(c => c.Id == admission.Insulation.Id)?.Name; if (listInsulation != null) admission.Insulation.Name = listInsulation; } } return admission; } }