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.AppSettings; using adas_core.Domain.Models.DTO; using adas_core.Domain.Models.DTO.Display; 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; /// /// Implements , coordinating display management operations /// across data access (), configuration (), /// authentication (), audit (), /// and caching () concerns. /// /// public class DisplayService( IDisplayRepository displayRepository, IPointOfCareService pointOfCareService, Lazy unitService, IDisplayConfigService displayConfigService, ISubscribersService subscribersService, IClientMessageService clientMessageService, IUserRepository userRepository, IAuthService authorityService, ILogger logger, IHttpContextAccessor httpContextAccessor, ILocalAuditService auditService, Lazy permissionService, ICacheService cacheService, IOptions cacheSettings) : IDisplayService { private readonly CacheSettings? _cacheSettings = cacheSettings.Value; #region Methods #region Create /// /// Inserts a new using the default configuration for its type. /// If no default configuration exists for the display type, a is thrown. /// An audit log entry is created after the display is persisted. /// /// The display to insert. Its DisplayConfigId is assigned from the resolved default configuration. /// The inserted with its DisplayConfigId populated. /// Thrown when no default configuration is found for the specified display type. /// public async Task InsertOne(Display display) { var defaultConfig = await displayConfigService.GetDefaultConfig(display.Type) ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); display.DisplayConfigId = defaultConfig.Id; await displayRepository.InsertOneAsync(display); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, display); return display; } /// /// Inserts a test Display record into the repository and records a corresponding audit log entry using the current HTTP context user. /// /// The newly created entity. /// public async Task InsertOneTest() { var d = new Display { Name = "DisplayTEST", UnitId = new ObjectId("65ba5f89d5ba8e273cf9cd96"), DisplayConfigId = new ObjectId("45ba5f89d5ba8e273cf9cd96") }; await displayRepository.InsertOneAsync(d); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, d); return d; } #endregion #region Read /// /// Retrieves all displays from the repository and maps them to a compact representation. /// /// A task that represents the asynchronous operation, containing a list of with the mapped display data. /// public async Task> GetAllCompact() { var result = await displayRepository.GetAll(); List listToReturn = []; foreach (var res in result) listToReturn.Add(new DisplayMinimalDto(res)); return listToReturn; } /// /// Retrieves all display items, optionally filtered by the specified user name. /// /// The user name used to filter the display items, or to retrieve all items. /// A task that represents the asynchronous operation. The task result contains a list of items. /// The method has not been implemented yet. /// public Task> GetAll(string? userName) { throw new NotImplementedException(); } /// /// Retrieves a paginated list of items along with the total document count, applying page number and page size from the provided filter. /// /// The pagination filter containing the page number and page size used to determine the slice of results to return. /// A containing the requested page of displays, the current page number, the page size, and the total number of documents. /// public async Task> GetPaginatedDisplays(PaginationFilter filter) { var result = displayRepository.GetPaginatedDisplays(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); } /// /// Retrieves all displays accessible to the specified user, together with their associated permissions, by resolving the user's authorizations (both unit-scoped and display-scoped). /// Returns an empty list when the username is null, when the user cannot be found, or when no authorizations are available; throws an exception if permissions for a unit-scoped display cannot be resolved. /// /// The username whose displays should be retrieved; when null, the method returns an empty list. /// A task that yields a list of containing the displays the user can access along with their permissions. /// Thrown when permissions for a unit-scoped display cannot be obtained for the user. /// public async Task> GetAllByUser(string? userName) { var start = DateTime.Now; var listToReturn = new List(); if (userName == null) return listToReturn; var user = await userRepository.GetByUserAndAuthoritesName(userName); if (user == null) return listToReturn; if (user.Authorization == null || user.Authorization.Count == 0) user.Authorization = await authorityService.GetUserAuthorities(user.Id); if (user.Authorization == null) return listToReturn; foreach (var e in user.Authorization) if (e.UnitId != null) { var isParsed = ObjectId.TryParse(e.UnitId, out var dId); if (isParsed) { var dis = await displayRepository.GetByUnitId(dId); foreach (var display in dis) { var toAdd = await GetInfo(display.Id, userName, user.Authorization, null, false, false, false, false); if (toAdd != null) { var newDto = new DisplayWithPermissionsDto { Display = toAdd, Permissions = await permissionService.Value.GetPermissionsForUnit(dId.ToString(), user) ?? throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission) }; listToReturn.Add(newDto); } } } } else if (e.DisplayId != null && !FindDisplayInPerms(e.DisplayId, listToReturn)) { var isParsed = ObjectId.TryParse(e.DisplayId, out var dId); if (isParsed) { var toAdd = await GetInfo(dId, userName, user.Authorization, null, false, false, false, false); if (toAdd != null) { var newDto = new DisplayWithPermissionsDto { Display = toAdd, Permissions = await permissionService.Value.GetPermissionsForDisplay(toAdd, user) }; listToReturn.Add(newDto); } } } var end = DateTime.Now; logger.LogDebug("Finished GetAllByUser Displays for user {user} in {TotalSeconds:F1} seconds", userName, (end - start).TotalSeconds); return listToReturn; } /// /// Determines whether a display with the specified identifier exists in the provided collection of display permissions. /// The check safely skips entries whose Display reference is null before comparing the display identifier. /// /// The identifier of the display to look up, compared as a string. /// The collection of display-with-permissions entries to search through. /// true if a non-null display with a matching identifier is found; otherwise, false. /// private static bool FindDisplayInPerms(string displayId, List perms) { return perms.Any(p => p.Display != null && p.Display.Id.ToString() == displayId); } /// /// Retrieves all displays associated with the specified display type by resolving the matching display configurations and loading their corresponding displays. /// Each returned display is enriched with its parent configuration, and configurations without associated displays are skipped. /// /// The display type used to filter the display configurations. /// A list of displays matching the specified type, each with its related configuration assigned; an empty list is returned when no displays are found. /// public async Task> GetByType(DisplayConfigEnums.DisplayType type) { var configs = await displayConfigService.GetByType(type); var listToReturn = new List(); foreach (var config in configs) { var displayToAdd = await displayRepository.GetByConfigId(config.Id); displayToAdd.ForEach(c => c.DisplayConfig = config); if (!displayToAdd.IsNullOrEmpty()) listToReturn.AddRange(displayToAdd); } return listToReturn; } /// /// Retrieves a list of displays associated with the specified point of care. /// /// The point of care used to filter the displays. /// A task that represents the asynchronous operation. The task result contains the list of displays matching the specified point of care. /// public async Task> GetByPointOfCare(PointOfCare pointOfCare) { return await displayRepository.GetByPointOfCare(pointOfCare); } /// /// Retrieves the list of displays associated with the specified configuration identifier by delegating to the display repository. /// /// The configuration identifier used to look up the associated displays. /// A task that returns the list of objects matching the given configuration identifier. /// public Task> GetByConfigId(ObjectId configId) { return displayRepository.GetByConfigId(configId); } /// /// Retrieves a list of displays associated with the specified card configuration identifier by delegating to the underlying repository. /// /// The unique identifier of the card configuration used to look up the associated displays. /// A task that represents the asynchronous operation, containing a list of objects matching the provided card configuration identifier. /// public Task> GetByCardConfigId(ObjectId configId) { return displayRepository.GetByCardConfigId(configId); } /// /// Retrieves a by its name. Throws a if no matching display is found. /// /// The name of the display to look up. /// The that matches the specified name. /// Thrown when no display is found for the given name. /// public async Task GetByName(string name) { return await displayRepository.GetByName(name) ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); } /// /// Retrieves a by its unique identifier from the repository. /// /// The unique identifier of the display to retrieve. /// The matching if found; otherwise, null. /// public async Task GetById(ObjectId id) { return await displayRepository.GetById(id); } /// /// Retrieves a display by its identifier, enriches it with localized point-of-care information, its display configuration, and the permissions available to the current user. /// /// The unique identifier of the display to retrieve. /// The locale used to localize the related point-of-care information. /// A containing the display and its associated permissions. /// Thrown when the current user cannot be identified from the JWT or when no display is found for the specified . /// public async Task GetByIdWithPermissions(ObjectId id, LocaleEnum localeEnum) { var username = JwtHelper.GetUsernameFromPrincipal(httpContextAccessor.HttpContext?.User!) ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); var user = await userRepository.GetByUserName(username); var display = await GetById(id); if (display == null) throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); foreach (var poc in display.PointOfCareIdList) { var c = await pointOfCareService.GetInfo(poc, localeEnum); if (c != null) display.PointOfCares.Add(c); } display.DisplayConfig = await displayConfigService.GetById(display.DisplayConfigId, display.UnitId, display.Type); display.DisplayConfig!.DisplaySectionList = []; try { display.DisplayConfig!.DisplaySectionList = await GetDisplaySectionByUser(display.Type, id, username, user?.Authorization); } catch (Exception e) { Console.WriteLine(e); throw; } return new DisplayWithPermissionsDto { Display = display, Permissions = await permissionService.Value.GetPermissionsForDisplay(display, user!) }; } /// /// Asynchronously counts the number of displays associated with the specified unit identifier. /// /// The unique identifier of the unit whose displays should be counted. /// A task that represents the asynchronous operation. The task result contains the total number of displays for the given unit. /// public async Task CountDisplaysByUnitId(ObjectId unitId) { return await displayRepository.CountByUnitId(unitId); } /// /// Retrieves display information by id, with optional enrichment of point-of-care, patient data, and section list based on the provided flags. /// Uses cached data when display configuration is requested; otherwise fetches the base display and caches the result. /// Fetches user authorizations from the user repository when not supplied, and logs an error if the display list cannot be populated due to a missing configuration. /// /// Identifier of the display to retrieve. /// Optional user name used to look up authorizations when none are provided. /// Optional pre-resolved authorizations used to filter the display section list. /// Optional locale applied when loading point-of-care data. /// If true, populates the point-of-care entries for the display. /// If true, includes patient data when retrieving point-of-care information. /// If true, populates the display section list filtered by the resolved authorizations. /// If true, retrieves the full display including its configuration (cached); otherwise retrieves the base display. /// Cancellation token to cancel the operation. /// The requested , or null if no display is found for the given id. /// public async Task GetInfo(ObjectId id, string? userName, List? authorizations, LocaleEnum? locale, bool fillPointOfCare = true, bool fillPatientData = false, bool fillDisplayList = true, bool fillDisplayConfig = true, CancellationToken ct = default) { var start = DateTime.Now; Display? display; if (fillDisplayConfig) { // Clave: display con configuración var (key, ttl) = CacheKeys.DisplayWithConfigKeyWithTtl(_cacheSettings, id); display = await cacheService.GetOrSetObjectAsync( key, async () => await BuildDisplayWithConfig(id, ct), ttl, ct); } else { // Clave: display base var (key, ttl) = CacheKeys.DisplayBaseKeyWithTtl(_cacheSettings, id); display = await cacheService.GetOrSetObjectAsync( key, async () => await displayRepository.GetById(id), ttl, ct); } if (display == null) return null; // PointOfCare (cacheado en su propio servicio) if (fillPointOfCare) foreach (var poc in display.PointOfCareIdList) { var c = await pointOfCareService.GetInfo(poc, locale, fillPatientData); if (c != null) display.PointOfCares.Add(c); } if (authorizations == null && userName != null) { var c = await userRepository.GetByUserAndAuthoritesName(userName); authorizations = c?.Authorization; } // DisplayList (depende de autorizaciones → NO cacheable) if (fillDisplayList) if (display.DisplayConfig != null) display.DisplayConfig.DisplaySectionList = await GetDisplaySectionByUser(display.Type, id, userName, authorizations); else logger.LogError("DISPLAY CONFIG NULL on fill display list"); var end = DateTime.Now; logger.LogDebug("Finished GetInfo Displays for user {user} in {TotalSeconds:F1} seconds", userName, (end - start).TotalSeconds); return display; } /// /// Builds a enriched with its display configuration. Returns null when the display is not found, and for instances that have a CardRotatingLayout, resolves and assigns each card's configuration, falling back to an empty when a card configuration cannot be retrieved. /// /// The identifier of the display to load. /// The cancellation token used to cancel the asynchronous operation. /// The with its configuration populated, or null if no display exists for the given . /// private async Task BuildDisplayWithConfig(ObjectId id, CancellationToken ct) { var display = await displayRepository.GetById(id); if (display == null) return null; var type = display.Type; // DisplayConfig display.DisplayConfig = await displayConfigService.GetById(display.DisplayConfigId, display.UnitId, type); if (type != DisplayConfigEnums.DisplayType.SmartDisplay || display.DisplayConfig is not SmartDisplay { CardRotatingLayout: not null } smart) return display; if (smart.CardRotatingLayout == null) return display; foreach (var card in smart.CardRotatingLayout) card.Data = await displayConfigService.GetCardConfigById(card.DataId) ?? new CardConfig(); return display; } /// /// Retrieves the display sections accessible to a specific user, filtered by display type, based on the user's authorities (either provided or fetched from the authority service). /// Supports both direct display references and unit-based references, marks the currently selected display, and returns an empty list if the user is not found or no matching sections exist. /// /// The display type used to filter the returned sections. /// The identifier of the currently selected display, which will be flagged as selected in the result; may be null. /// The username used to look up the user and their authorities; if null, an empty list is returned. /// Optional pre-fetched list of user authorities; when null, authorities are retrieved from the authority service. /// A task that resolves to a list of items accessible to the user and matching the specified display type. /// public async Task> GetDisplaySectionByUser( DisplayConfigEnums.DisplayType type, ObjectId? currentDisplay, string? userName, List? authorizations) { try { var listToReturn = new List(); if (userName != null) { var user = await userRepository.GetByUserName(userName); if (user != null) { var displayIdByAuthorities = authorizations ?? await authorityService.GetUserAuthorities(user.Id); foreach (var e in displayIdByAuthorities) { var isParsed = ObjectId.TryParse(e.DisplayId, out var dId); if (isParsed) { if (listToReturn.All(c => c.Id != dId)) { var toAdd = await GetById(dId); if (toAdd != null) if (type == toAdd.Type) { var minDisSec = new MinimalDisplaySection { Name = toAdd.Name, Id = toAdd.Id, IsSelected = currentDisplay != null && toAdd.Id == currentDisplay }; listToReturn.Add(minDisSec); } } } else { var isParsedUnitId = ObjectId.TryParse(e.UnitId, out var uId); if (isParsedUnitId) { var unitDisplays = await GetByUnitId(uId); foreach (var disp in unitDisplays) if (type == disp.Type && listToReturn.All(c => c.Id != dId)) { var minDisSec = new MinimalDisplaySection { Name = disp.Name, Id = disp.Id, IsSelected = currentDisplay != null && disp.Id == currentDisplay }; listToReturn.Add(minDisSec); } } } } } } return listToReturn; } catch (Exception e) { Console.WriteLine(e); throw; } } /// /// Asynchronously retrieves all display configurations of type DisplayNurse and SmartDisplay, mapping them into minimal display sections and grouping them within a display list DTO. /// /// A task that represents the asynchronous operation. The task result contains a MinimalDisplayListDto with the populated DisplayNurse and SmartDisplay collections. /// public async Task GetAllDisplaySection() { var minimalDisplayListDto = new MinimalDisplayListDto(); var listDisplayNurse = await GetByType(DisplayConfigEnums.DisplayType.DisplayNurse); foreach (var displayForAdmin in listDisplayNurse) { var minDisSec = new MinimalDisplaySection { Name = displayForAdmin.Name, Id = displayForAdmin.Id }; minimalDisplayListDto.DisplayNurse.Add(minDisSec); } var listDisplaySmart = await GetByType(DisplayConfigEnums.DisplayType.SmartDisplay); foreach (var displayForAdmin in listDisplaySmart) { var minDisSec = new MinimalDisplaySection { Name = displayForAdmin.Name, Id = displayForAdmin.Id }; minimalDisplayListDto.SmartDisplay.Add(minDisSec); } return minimalDisplayListDto; } /// /// Retrieves the list of displays associated with the specified unit identifier by delegating to the display repository. /// /// The identifier of the unit whose displays should be returned. /// A task that represents the asynchronous operation, containing the list of objects for the given unit. /// public async Task> GetByUnitId(ObjectId unitId) { return await displayRepository.GetByUnitId(unitId); } /// /// Retrieves all available points of care (POC) and their associated unit information for the specified display identifiers. Invalid display ID strings are silently skipped, virtual points of care can optionally be excluded, and a is thrown when a resolved unit cannot be found; any unexpected error is logged and an empty result is returned. /// /// A list of display identifier strings used to resolve the related units and their available points of care. /// When set to true, virtual points of care are excluded from the result; otherwise, they are included. /// A containing the available points of care and their associated unit details. /// Thrown when a unit associated with one of the resolved display identifiers cannot be found. /// public async Task GetAllAvailablePoc(List displayIds, bool excludeVirtual = false) { try { var listToReturn = new PocAndUnitDto(); var listObjectId = new List(); foreach (var displayId in displayIds) { var isParsed = ObjectId.TryParse(displayId, out var dId); if (isParsed) { var diplay = await displayRepository.GetById(dId); if (diplay != null) listObjectId.Add(diplay.UnitId); } } var distinctObjectIds = listObjectId.Distinct().ToList(); foreach (var distinctObjectId in distinctObjectIds) { var unit = await unitService.Value.FindById(distinctObjectId) ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); var pocList = await pointOfCareService.FindByUnitAndStatus(distinctObjectId, StatusEnum.PointOfCare.Available, excludeVirtual); foreach (var pointOfCare in pocList) { var pocAv = new MinimalPocAndUnitDto { PocId = pointOfCare.Id, PocName = pointOfCare.Bed, UnitId = pointOfCare.UnitId, UnitName = unit.Name }; listToReturn.PocList.Add(pocAv); } } return listToReturn; } catch (Exception e) { logger.LogError("Error GetAllAvailablePoc {Error}", e.Message); return new PocAndUnitDto(); } } /// /// Retrieves all points of care associated with the specified display. Returns an empty list when the display is not found, when no associated points of care exist, or when an error occurs during retrieval. /// /// The ObjectId of the display whose points of care should be retrieved. /// A list of points of care linked to the display, or an empty list if the display cannot be found or if an error is encountered. /// public async Task> GetAllPocsByDisplayId(ObjectId id) { try { var pocList = new List(); var display = await displayRepository.GetById(id); if (display == null) return pocList; foreach (var pocId in display.PointOfCareIdList) { var poc = await pointOfCareService.FindById(pocId); if (poc != null) pocList.Add(poc); } return pocList; } catch (Exception e) { logger.LogError("Error GetAllAvailablePoc {Error}", e.Message); return []; } } /// /// Retrieves the display configuration locations associated with the specified display configuration ID, mapping each display to its corresponding unit name. If a unit cannot be found for a display, the resulting location's unit name will be null. /// /// The identifier of the display configuration whose locations are being retrieved. /// A task that represents the asynchronous operation. The task result contains a list of objects with display and unit information. /// public async Task> GetDisplayConfigLocations(ObjectId displayConfigId) { var displays = await GetByConfigId(displayConfigId); var locations = new List(); foreach (var display in displays) { var unit = await unitService.Value.FindById(display.UnitId); DisplayConfigLocationDto newLocation = new() { DisplayName = display.Name, UnitName = unit?.Name, }; locations.Add(newLocation); } return locations; } /// /// Determines whether the specified display configuration is currently in use by checking if it is referenced by any related entity. /// /// The unique identifier of the display configuration to check. /// true if the display configuration is referenced by at least one entity; otherwise, false. /// public async Task IsDisplayConfigInUse(ObjectId displayConfigId) { return await displayRepository.IsDisplayConfigInUse(displayConfigId) > 0; } #endregion #region Update /* * En esta actualización se espera una resubscipción al id del display ya que actualizar los PoC conlleva actualizar * subscrioptor y locations para las observaciones */ /// /// Updates the point of care list associated with the specified display, invalidating the related cache entries and broadcasting the change to subscribers. /// /// The identifier of the display whose point of care list is being updated. /// The list of point of care object identifiers to assign to the display. /// The updated instance after the point of care list change. /// Thrown when no display exists for the specified . /// Thrown when the point of care list update cannot be persisted. /// public async Task UpdatePointOfCareList(ObjectId objectId, List listPocObId) { var oldDisplay = await displayRepository.GetById(objectId) ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); var displayToReturn = await displayRepository.UpdatePointOfCareList(objectId, listPocObId) ?? throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed); await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(objectId)); SendDisplayBroadcast(displayToReturn, OperationType.UpdateDisplayPoC); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplay, displayToReturn); return displayToReturn; } /// /// Updates the configuration of an existing display by casting the new configuration to its specific type based on , supporting DisplayNurse and SmartDisplay. On a successful update, broadcasts the change, creates an audit log entry, and invalidates the related cache entry. Returns null if the provided configuration type is not supported or the cast results in null. /// /// The existing display whose configuration will be updated. /// The new configuration to apply, or null if no update is provided. /// The updated if the configuration was successfully applied; otherwise, null. /// /// public async Task UpdateConfig(Display oldDisplay, DisplayConfig? newDisplayConfig) { var newDisplayConfigCast = new DisplayConfig(); switch (newDisplayConfig?.Type) { case DisplayConfigEnums.DisplayType.DisplayNurse: newDisplayConfigCast = newDisplayConfig as DisplayNurse; break; case DisplayConfigEnums.DisplayType.SmartDisplay: newDisplayConfigCast = newDisplayConfig as SmartDisplay; break; } if (newDisplayConfigCast != null) { var displayToReturn = await displayRepository.UpdateConfig(oldDisplay.Id, newDisplayConfigCast); if (displayToReturn != null) { SendDisplayBroadcast(displayToReturn, OperationType.UpdateDisplayConfig); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplay, displayToReturn); } await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(oldDisplay.Id)); return displayToReturn; } return null; } /// /// Updates the configuration ID associated with the specified display. On a successful update, the related cache entries are invalidated, a display update broadcast is sent, and an audit log entry is created. /// /// The display whose configuration ID is being updated. /// The new configuration ID to assign to the display. /// The updated display, or null if the display was not found. /// public async Task UpdateConfigId(Display oldDisplay, ObjectId configId) { var displayToReturn = await displayRepository.UpdateConfigId(oldDisplay.Id, configId); if (displayToReturn != null) { await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(oldDisplay.Id)); SendDisplayBroadcast(displayToReturn, OperationType.UpdateDisplayConfig); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplay, displayToReturn); } return displayToReturn; } /// /// Updates the configuration preset associated with the specified display, invalidates the display cache, records an audit log entry, and broadcasts a notification to subscribers based on the resolved display type (DisplayNurse, SmartDisplay, or Unknown). Throws a not-found exception when the update result or configuration cannot be resolved, and an invalid-format exception when the configuration type is not one of the handled types. /// /// The identifier of the display whose configuration preset is being updated. /// The identifier of the new configuration preset to apply to the display. /// The updated entity, or null if the update could not be completed. /// Thrown when the update result or the resolved configuration is null. /// Thrown when the configuration type is not one of the handled display types. /// public async Task UpdateConfigPreset(ObjectId objectIdDisplay, ObjectId objectIdConfigDisplay) { var oldConfig = await displayConfigService.GetById(objectIdConfigDisplay); var result = await displayRepository.UpdateConfigPreset(objectIdDisplay, objectIdConfigDisplay); await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(objectIdDisplay)); var config = await displayConfigService.GetById(objectIdConfigDisplay); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldConfig, config); if (result == null || config == null) throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); var subscribers = subscribersService.GetSubscribers().Where(s => s.DisplayId == objectIdDisplay).ToList(); switch (config.Type) { case DisplayConfigEnums.DisplayType.DisplayNurse: SendNurseDisplayBroadcast(subscribers, config as DisplayNurse); break; case DisplayConfigEnums.DisplayType.SmartDisplay: SendSmartDisplayBroadcast(subscribers, config as SmartDisplay); break; case DisplayConfigEnums.DisplayType.Unknown: break; default: throw new InvalidFormatException(HttpEnum.ErrorMessage.BadRequestIncorrectType); } return result; } /// /// Updates the name of an existing display identified by the given identifier, invalidates the related cache entries, and records an audit log entry for the change. /// /// The unique identifier of the display to update. /// The new name to assign to the display. /// The updated instance, or null if the update could not be performed. /// Thrown when no display is found for the specified . /// public async Task UpdateName(ObjectId id, string name) { var display = await displayRepository.GetById(id) ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); var newDisplay = await displayRepository.UpdateName(display, name); await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(id)); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, display, newDisplay); return newDisplay; } #endregion #region Delete /// /// Deletes a display by its identifier, removing the record, invalidating the related cache, clearing associated authorities, and recording an audit log entry. /// /// The unique identifier of the display to delete. /// A task that resolves to true when the display has been successfully deleted. /// Thrown when no display is found for the specified . /// public async Task DeleteDisplay(ObjectId id) { var display = await displayRepository.GetById(id) ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); await displayRepository.DeleteAsync(id); // Invalidar CACHE (colección completa) await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(id)); await authorityService.DeleteByDisplayId(id); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, display, null); return true; } /// /// Deletes all displays associated with the specified unit identifier, invalidates the displays cache, and removes related authority data for the unit. /// /// The unique identifier of the unit whose displays and related authority data will be removed. /// public async Task DeleteDisplaysByUnitId(ObjectId unitId) { await displayRepository.DeleteManyByUnitId(unitId); // Invalidar CACHE (colección completa) await cacheService.DeleteByPatternAsync(CacheKeyPatterns.ForEntity(CacheEnum.EntityType.Displays)); await authorityService.DeleteByUnitId(unitId); } #endregion #region Send Notification /// /// Sends a smart display configuration update broadcast asynchronously to all specified WebSocket subscribers. /// /// The list of WebSocket subscribers that will receive the smart display configuration update. /// The smart display configuration to broadcast. May be null if no configuration is provided. /// private void SendSmartDisplayBroadcast(List subscribers, SmartDisplay? config) { foreach (var subscriber in subscribers) _ = clientMessageService.SendAsync(subscriber.Id, OperationType.UpdateSmartDisplayConfig, config); } /// /// Broadcasts a nurse display configuration update to all specified WebSocket subscribers by sending an asynchronous update message to each one. /// /// The list of WebSocket subscribers that will receive the nurse display configuration update. /// The nurse display configuration to broadcast, which may be null. /// private void SendNurseDisplayBroadcast(List subscribers, DisplayNurse? config) { foreach (var subscriber in subscribers) _ = clientMessageService.SendAsync(subscriber.Id, OperationType.UpdateNurseDisplayConfig, config); } /// /// Sends a broadcast message to all subscribers associated with the specified display. /// Currently only handles the operation, dispatching an asynchronous notification to each subscriber; other operation types are ignored. /// /// The display whose subscribers will receive the broadcast; used to filter the subscriber list by its identifier. /// The type of operation being broadcast, which determines the action taken on matching subscribers. /// private void SendDisplayBroadcast(Display display, OperationType operation) { var subscribers = subscribersService.GetSubscribers().Where(s => s.DisplayId == display.Id).ToList(); switch (operation) { case OperationType.UpdateDisplayPoC: foreach (var subscriber in subscribers) _ = clientMessageService.SendAsync(subscriber.Id, operation, null); break; } } #endregion #endregion }