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 /// /// Deletes the specified admission by delegating to the delete operation using the admission's identifier. /// /// The admission entity to delete, identified by its . public async Task DeleteAdmissionAsync(Admission admission) { await DeleteAdmissionByIdAsync(admission.Id); } /// /// Deletes an admission identified by the given id. If the admission is not found, the operation is skipped and logged; otherwise the admission is removed, any associated point of care is detached (clearing its AdmissionId and Admission) and set to Available when not currently Locked or InUse, a delete broadcast is sent, and an audit log entry is created. /// /// The identifier of the admission to delete. 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); } /// /// Asynchronously deletes all admissions associated with the specified unit identifier by delegating the operation to the admission repository. /// /// The unique identifier of the unit whose admissions should be removed. public async Task DeleteAdmissionsByUnitId(ObjectId unitId) { _ = await admissionRepository.DeleteAdmissionsByUnitId(unitId); } /// /// Retrieves an admission by its identifier and, when a point of care is associated, enriches the result with the patient's location (unit, bed, and room) obtained from the point of care service. Returns null if the admission cannot be found. /// /// The unique identifier of the admission to retrieve. /// The matching with its populated when applicable, or null if no admission is found. 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; } /// /// Asynchronously retrieves all admissions and enriches each one with its associated point of care information (unit, bed, and room) when available. /// /// A task that represents the asynchronous operation. The task result contains a collection of objects with patient location details populated for those linked to a point of care. /// Thrown when the admission repository returns no results. 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; } /// /// Inserts a new admission, preventing duplicates by NHC and optionally linking it to a Point of Care. /// When a Point of Care is assigned, its information is used to populate the patient location and, if free, it is reserved and associated with the newly created admission. /// /// The admission to insert, optionally including a PointOfCareId to associate with a care location. /// The newly inserted , or null if no result is produced. /// Thrown when an admission with the same NHC already exists, or when the insertion fails to return a result. /// Thrown when the specified Point of Care does not exist. 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; } /// /// Updates an existing admission record, enriching its patient location details from the linked point of care on both the prior and incoming states, and records the change via audit log and broadcast. /// If the admission is not found, the method returns without making changes; point of care lookups are only applied when a PointOfCareId is present and yields a result. /// /// The admission entity containing the updated information to persist. 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); } /// /// Admits a patient based on the provided , creating a new assigned to the specified point of care, marking the point of care as in use, and updating related master lists (insulation, allergies, diagnosis, origin, language barrier, passive sitting) when present. If the point of care id, unit, or point of care cannot be resolved, the operation is skipped after logging an error. When is false, the originating admission record is deleted after the patient is inserted. /// /// The admission data used to create the patient and populate location, diagnosis, allergies, and other attributes. /// When false, the admission record is deleted after a successful patient insertion; when true, the admission is retained. 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); } /// /// Returns a patient to the admissions workflow by creating a new admission record, removing any existing discharge, and archiving the patient. Validates that the patient and its associated unit exist before proceeding, and only builds the admission when a point of care is assigned. /// /// The identifier of the patient to be returned to admissions. 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 /// /// Returns a patient to the admissions flow by creating a new admission record from the patient's existing data, removing any prior discharge, and archiving the patient. /// /// The unique identifier of the patient to be returned to admissions. /// The admission context used to resolve the unit and point of care for the new admission record. 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); } /// /// Retrieves a list of admissions for the specified patient location. If an error occurs during retrieval, the error is logged and an empty list is returned. /// /// The patient location used to filter admissions. /// A task that represents the asynchronous operation. The task result contains a list of admissions matching the specified location, or an empty list if an error occurs. 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 []; } } /// /// Retrieves a list of admissions associated with the specified point of care identifier, enriching each admission with its patient location information when available. /// /// The identifier of the point of care whose admissions should be retrieved. /// A task representing the asynchronous operation, containing the list of admissions for the given point of care, or an empty list if an error occurs. 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 []; } } /// /// Retrieves all admissions associated with the specified point of care and applies translations according to the given locale in parallel. /// /// The identifier of the point of care whose admissions will be retrieved. /// The locale used to translate the admission fields. /// A task that represents the asynchronous operation, containing a list of admissions with their fields translated to the specified locale. 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(); } /// /// Retrieves admissions associated with the specified unit, excluding those linked to a Point of Care (PoC). /// If an error occurs during retrieval, the exception is logged and an empty list is returned as a fallback. /// /// The identifier of the unit whose admissions (without PoC) are being requested. /// A task that returns a list of objects for the given unit, or an empty list if an error occurs. 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 []; } } /// /// Asynchronously counts the number of admissions associated with the specified unit identifier by delegating to the admission repository. /// Returns 0 and logs the error if the repository operation fails, ensuring the method does not propagate exceptions to the caller. /// /// The identifier of the unit whose admissions should be counted. /// A task that represents the asynchronous operation. The task result contains the number of admissions for the given unit, or 0 if an error occurs. 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; } } /// /// Searches for a patient by patient number, enriching the current patient record with its point of care and unit name when available, and combines it with archived patient and admission lookups scoped to the specified unit. /// /// The unique patient number used as the primary search key. /// The identifier of the unit used to filter the archived patient and admission searches. /// A aggregating the current patient, archived patient, and admission data, including flags indicating whether the patient exists only in the archive and whether any of the three sources returned a result. 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; } /// /// Retrieves the admission record associated with the specified patient clinical record number (NHC) from the admission repository. /// /// The patient's clinical record number (NHC) used to look up the admission. /// A task that resolves to the matching if found, or null when no admission exists for the given patient number. public Task GetAdmissionByPatientNumber(string patientNumber) { return admissionRepository.FindByNhc(patientNumber); } /// /// Updates the master list option for admissions associated with the specified units, records an audit log entry for each modified admission, and broadcasts the updates. /// /// The master list update options to apply to the matching admissions. /// The collection of units whose admissions are affected by the update. /// The name of the master list type being modified. 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); } } /// /// Deletes a master list option from patient admissions associated with the specified units and type, records an audit log entry for each affected admission, and broadcasts the admission update when the updated admission is found. /// /// The master list option to remove from the admissions. /// The collection of units whose admissions will be processed for the deletion. /// The name of the option type being deleted. 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); } } /// /// Processes an admission API request by performing the appropriate action based on the request type: inserts a new admission, updates an existing one, or deletes it. /// Required fields (Nhc, Origin, and Diagnosis) are validated before insert and update operations, and the method exits early when the admission or any required value is missing. /// /// The API request containing the admission payload and the operation type to execute. 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; } } /// /// Asynchronously saves the specified API request by scheduling the underlying save operation on a background task. /// /// The API request to persist. /// A task that represents the asynchronous save operation. public Task SaveRequestAsync(ApiRequest apiRequest) { return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest); })); } /// /// Handles the PointOfCare change when an admission is updated, transferring the assignment from the old PointOfCare to the new one. Updates the status of both PointOfCares (e.g., Reserved, InUse, Available) based on patient occupancy, locks, and admission association, and checks for the next pending admission whenever a PointOfCare becomes available. /// /// The current admission containing the updated PointOfCare identifier. /// The previous admission state used to identify the original PointOfCare to release. 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); } } /// /// Updates the status of the specified point of care, performing a lookup by identifier first. If no point of care is found, the method returns without applying any change. /// /// The identifier of the point of care whose status will be updated. /// The new status to assign to the point of care. private async Task SetPointOfCareStatus(ObjectId pointOfCareId, StatusEnum.PointOfCare status) { var pointOfCare = await pointOfCareService.FindById(pointOfCareId); if (pointOfCare == null) return; await pointOfCareService.SetPointOfCareStatus(pointOfCareId, status); } /// /// Sends an admission broadcast message by routing to the appropriate sender based on whether a point-of-care identifier is set. /// Falls back to unit-based delivery when no point-of-care is available; logs and swallows any errors encountered during dispatch. /// /// The admission record to broadcast. /// The operation type associated with the broadcast. 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); } } /// /// Sends an admission broadcast to subscribers associated with the admission's Point of Care, grouped and translated by locale. /// Logs an error and returns early if the admission has no Point of Care id or the Point of Care cannot be found. /// /// The admission whose broadcast is being sent; its Point of Care is used to select subscribers and locale-specific content. /// The type of operation to send to the subscribers. 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); } } } /// /// Sends an admission broadcast to all WebSocket subscribers associated with displays in the admission's unit, grouped by locale so each subscriber receives a localized copy. If the admission has an empty unit id, the broadcast is skipped and an error is logged. /// /// The admission to broadcast, which supplies the target unit identifier. /// The operation type associated with the broadcast message. 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); } } } /// /// Localizes the 's Origin, Diagnosis, and Insulation names by resolving them /// against locale-specific master lists associated with the admission's unit. If the unit is not found, /// or any of the referenced master list lookups fail or contain no matching option, the original /// admission values are preserved as a fallback. /// /// The admission whose reference names will be updated with localized values. /// The locale used to retrieve the appropriate master list translations. /// The same instance with its localized reference names applied when available. 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; } }