using adas_core.Application.Exceptions; using adas_core.Application.Repositories.Interfaces; using adas_core.Application.Services.Interfaces; using adas_core.Domain.Enums; using adas_core.Domain.Models; using adas_core.Domain.Models.AppSettings; using adas_core.Domain.Models.Filter; 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; namespace adas_core.Application.Services; public class PointOfCareService( ILogger logger, IPointOfCareRepository pointOfCareRepository, Lazy patientService, Lazy unitService, ISubscribersService subscribersService, Lazy clientMessageService, Lazy admissionService, IHttpContextAccessor httpContextAccessor, ILocalAuditService auditService, ICacheService cacheService, IOptions cacheSettings) : IPointOfCareService { private readonly CacheSettings? _cacheSettings = cacheSettings.Value ; public async Task InsertPointOfCare(PointOfCare pointOfCare) { try { var unit = await unitService.Value.FindById(pointOfCare.UnitId); if (unit == null) { logger.LogError("Unit id: {unitId} Not Found. PointOfCare not inserted", pointOfCare.UnitId); return null; } pointOfCare.UnitId = unit.Id; await pointOfCareRepository.InsertOneAsync(pointOfCare); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, pointOfCare); return await FindById(pointOfCare.Id); } catch (Exception ex) { logger.LogError("Exception inserting pointOfCare: {pointOfCare}, Exception: {ex}", pointOfCare, ex); return null; } } public async Task Delete(ObjectId id) { var poc = await FindById(id); if (poc is not { AdmissionId: null }) return; await pointOfCareRepository.Delete(id); await cacheService.DeleteByPatternAsync(CacheKeys.PointOfCareBase(id)); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, poc, null); } public async Task DeletePoCsByUnitId(ObjectId unitId) { await pointOfCareRepository.DeleteManyByUnitId(unitId); await cacheService.DeleteByPatternAsync(CacheKeyPatterns.ForEntity(CacheEnum.EntityType.PontOfCare)); } public async Task> FindAllIdCamerasInUse() { return await pointOfCareRepository.FindAllIdCamerasInUse(); } public async Task> FindAllIdRelaysInUse() { return await pointOfCareRepository.FindAllIdRelaysInUse(); } public async Task> FindAllIdBeaconsInUse() { return await pointOfCareRepository.FindAllIdBeaconsInUse(); } public async Task?> FindAllByUnitIdWithDevices(ObjectId unitId) { var result = await pointOfCareRepository.FindAllByUnitIdWithDevices(unitId); return result ?? []; } public async Task Update(PointOfCare pointOfCare) { var oldPoc = await pointOfCareRepository.FindById(pointOfCare.Id); await pointOfCareRepository.Update(pointOfCare); var updatedPoc = await GetInfo(pointOfCare.Id); await cacheService.DeleteByPatternAsync(CacheKeys.PointOfCareBase(pointOfCare.Id)); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldPoc, updatedPoc); if (updatedPoc == null) return null; SendPointOfCareBroadcast(updatedPoc, OperationType.UpdatedPointOfCare); return updatedPoc; } public async Task UpdateUnit(ObjectId id, Unit unit) { var poc = await FindById(id); if (poc == null) return; await pointOfCareRepository.UpdateUnitId(id, unit); await cacheService.DeleteByPatternAsync(CacheKeys.PointOfCareBase(id)); var newPoc = await FindById(id); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, poc, newPoc!); SendPointOfCareBroadcast(poc, OperationType.UpdatedPointOfCare); } public async Task> GetAll() { var c = await pointOfCareRepository.GetAll(); return c ?? []; } public async Task> GetAllConfigs() { var c = await pointOfCareRepository.GetAllConfigs(); return c ?? []; } public async Task> GetAllLocationInfo() { var c = await pointOfCareRepository.GetAllLocationInfo(); return c ?? []; } public async Task> GetPaginatedPoCs(PaginationFilter filter) { var result = pointOfCareRepository.GetPaginatedPoCs(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 UpdateConfiguration(ObjectId id, PointOfCareConfiguration configuration) { var old = await pointOfCareRepository.GetPoCConfiguration(id) ?? throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed); await pointOfCareRepository.UpdateConfiguration(id, configuration); await cacheService.DeleteByPatternAsync(CacheKeys.PointOfCareBase(id)); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, old.Configuration, configuration); } public async Task FindById(ObjectId id) { return await pointOfCareRepository.FindByIdAllConfig(id); } public async Task FindByIdAllConfig(ObjectId id) { return await pointOfCareRepository.FindByIdAllConfig(id); } public async Task?> FindAllByUnitId(ObjectId unit) { var result = await pointOfCareRepository.FindAllByUnitId(unit); return result ?? []; } public async Task> FindByUnitAndStatus(ObjectId unitId, StatusEnum.PointOfCare poc, bool excludeVirtual = false) { return await pointOfCareRepository.FindByUnitAndStatus(unitId, poc, excludeVirtual); } /// /// Check nex admission when patient has exit from poc /// /// PoC id public async void CheckNextAdmission(ObjectId? patientLocation) { try { if (patientLocation == null) return; var pocToCheck = await GetInfo(patientLocation.Value,null); if (pocToCheck != null && pocToCheck.Status != StatusEnum.PointOfCare.Locked) { if (pocToCheck.AdmissionId != null) { pocToCheck.Status = StatusEnum.PointOfCare.Reserved; } else { // Sabemos que puede ser una lista, pero limitamos directamente a 1 elemento var next = await admissionService.Value.GetAdmissionByPointOfCareId(pocToCheck.Id); var adm = next.FirstOrDefault(); if (adm != null) { pocToCheck.Admission = adm; pocToCheck.AdmissionId = adm.Id; pocToCheck.Status = StatusEnum.PointOfCare.Reserved; } else { pocToCheck.Status = StatusEnum.PointOfCare.Available; } } await Update(pocToCheck); } } catch (Exception e) { logger.LogError("Error checking next admission for PointOfCare {pocId}: {message}", patientLocation, e.Message); } } public async Task FindByBedAndUnitId(string? bed, ObjectId? unitId) { if (unitId == null || string.IsNullOrEmpty(bed)) return null; return await pointOfCareRepository.FindByBedAndUnitId(bed, unitId.Value); } public async Task UpdateRelayConfig(PointOfCare poc) { var oldPoc = await pointOfCareRepository.GetPoCConfiguration(poc.Id); if (poc.Configuration?.RelayIdList != null) { await pointOfCareRepository.UpdateRelayConfig(poc.Id, poc.Configuration.RelayIdList); await cacheService.DeleteByPatternAsync(CacheKeys.PointOfCareBase(poc.Id)); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldPoc, poc); } } public async Task?> FindByRoom(string room) { return await pointOfCareRepository.FindByRoom(room); } public async Task?> FindByBed(string bed) { return await pointOfCareRepository.FindByBed(bed); } public async Task> FindAllByUnitIds(List unitIds) { var filter = Builders.Filter.In(p => p.UnitId, unitIds); return await pointOfCareRepository.FindByFilter(filter); } public async Task GetInfo(ObjectId id, bool fillPatientData = true) { var poc = await FindById(id); if (poc == null) return null; var unit = await unitService.Value.FindById(poc.UnitId); if (unit is { Name: not null }) poc.UnitName = unit.Name; if (fillPatientData) { var patient = await patientService.Value.GetByPointOfCare(poc); if (patient != null) { poc.Patientid = patient.Id; poc.Patient = patient; } if (poc.AdmissionId != null) { var admission = await admissionService.Value.GetAdmissionByIdAsync(poc.AdmissionId.Value); if (admission != null) poc.Admission = admission; } } return poc; } public async Task GetInfo(ObjectId id, LocaleEnum? locale, bool fillPatientData = true, CancellationToken ct = default) { var (key, ttl) = CacheKeys.PointOfCareBaseKeyWithTtl(_cacheSettings, id); var poc = await cacheService.GetOrSetObjectAsync(key, () => pointOfCareRepository.FindById(id), ttl, ct); if (poc == null || !fillPatientData) return poc; var unit = await unitService.Value.FindById(poc.UnitId); if (unit?.Name != null) poc.UnitName = unit.Name; var patient = locale != null ? await patientService.Value.GetByPointOfCareAndLocale(poc, unit, locale) : await patientService.Value.GetByPointOfCare(poc); if (patient != null) { poc.Patientid = patient.Id; poc.Patient = patient; } if (poc.AdmissionId == null) return poc; var admission = await admissionService.Value.GetAdmissionByIdAsync(poc.AdmissionId.Value); if (admission != null) poc.Admission = admission; return poc; } public async Task FindPoCByPatientId(ObjectId patientId) { var patient = await patientService.Value.FindById(patientId); if (patient is { PointOfCareId: not null }) return await FindById(patient.PointOfCareId.Value); return null; } public async Task FindPoCByPatientNumber(string patientNumber) { var patient = await patientService.Value.FindByPatientNumber(patientNumber); if (patient is { PointOfCareId: not null }) return await FindById(patient.PointOfCareId.Value); return null; } public async Task CountPoCsByUnitId(ObjectId unitId) { return await pointOfCareRepository.CountByUnitId(unitId); } public async Task CountVirtualPoCsByUnitId(ObjectId unitId) { return await pointOfCareRepository.CountVirtualsByUnitId(unitId); } public async Task SetPointOfCareStatus(ObjectId id, StatusEnum.PointOfCare status) { var pointOfCare = await GetInfo(id,null); if (pointOfCare == null) return; pointOfCare.Status = status; await pointOfCareRepository.Update(pointOfCare); await cacheService.DeleteByPatternAsync(CacheKeys.PointOfCareBase(id)); var oldPoc = await auditService.DeepCopyAsync(pointOfCare); if (oldPoc != null) await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldPoc, pointOfCare); // await _patientService.Value.UpdatePatientAltable() SendPointOfCareBroadcast(pointOfCare, OperationType.UpdatedPointOfCare); } public async Task FindPoCByPatientLocation(PatientLocation patientLocation) { return await pointOfCareRepository.FindByPatientLocation(patientLocation); } private void SendPointOfCareBroadcast(PointOfCare pointOfCare, OperationType operation) { try { var subscribers = subscribersService.GetSubscribers() .Where(s => s.LocationIds.Any(c => c == pointOfCare.Id)).ToList(); foreach (var subscriber in subscribers) _ = clientMessageService.Value.SendAsync(subscriber.Id, operation, pointOfCare); } catch (Exception ex) { logger.LogError("Exception sending PoC broadcast. Operation type: {op}. Exception: {ex}", operation.ToString(), ex); } } }