Files
adas-core/adas-core.Infrastructure/Repositories/PointOfCareRepository.cs
T
2026-06-26 10:29:23 +02:00

761 lines
37 KiB
C#

using adas_core.Application.Repositories.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using Serilog;
using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils;
namespace adas_core.Infrastructure.Repositories;
/// <summary>
/// Represents a MongoDB-backed repository for <see cref="PointOfCare"/> entities, providing the persistence operations defined by the <see cref="IPointOfCareRepository"/> interface.
/// </summary>
public class PointOfCareRepository : MongoRepository<PointOfCare>, IPointOfCareRepository
{
private readonly ApiSettings _apiSettings;
public PointOfCareRepository(IOptions<ApiSettings>? apiSettings, IMongoDatabase database) : base(database)
{
if (apiSettings != null)
_apiSettings = apiSettings.Value;
else
throw new ArgumentNullException(nameof(apiSettings));
}
/// <summary>
/// Retrieves the collection name used for Locations operations from the API settings configuration.
/// </summary>
/// <returns>The configured Locations collection name retrieved from the API settings.</returns>
public override string GetCollectionName()
{
return _apiSettings.Locations;
}
/// <summary>
/// Asynchronously inserts a single <see cref="PointOfCare"/> record, adding error logging around the base insertion behavior.
/// If the base insert operation fails, the exception is written to the console and logged, then rethrown to preserve the original failure.
/// </summary>
/// <param name="pointOfCare">The <see cref="PointOfCare"/> entity to insert.</param>
public override async Task InsertOneAsync(PointOfCare pointOfCare)
{
try
{
await base.InsertOneAsync(pointOfCare);
}
catch (Exception e)
{
Console.WriteLine(e);
Log.Error("Exception trying to insert pointOfCare: {pointOfCare}. Exception {e}", pointOfCare, e);
throw;
}
}
/// <summary>
/// Deletes the <see cref="PointOfCare"/> entity matching the specified <paramref name="id"/> from the underlying collection.
/// Any exception encountered during the delete operation is logged and rethrown to the caller.
/// </summary>
/// <param name="id">The unique identifier of the <see cref="PointOfCare"/> entity to remove.</param>
public async Task Delete(ObjectId id)
{
try
{
var filter = Builders<PointOfCare>.Filter.Eq(x => x.Id, id);
await Collection.DeleteOneAsync(filter, null);
}
catch (Exception e)
{
Log.Error("Exception trying to delete pointOfCare: {id}. Exception {e}", id, e);
throw;
}
}
/// <summary>
/// Deletes all <see cref="PointOfCare"/> records associated with the specified unit identifier.
/// Returns <c>true</c> if the deletion completes successfully, or <c>false</c> if an exception is caught and logged.
/// </summary>
/// <param name="unitId">The identifier of the unit whose related <see cref="PointOfCare"/> records should be removed.</param>
/// <returns>A task that resolves to <c>true</c> when the records are deleted, or <c>false</c> when an error occurs during the operation.</returns>
public async Task<bool> DeleteManyByUnitId(ObjectId unitId)
{
try
{
var filter = Builders<PointOfCare>.Filter.Where(p => p.UnitId == unitId);
await Collection.DeleteManyAsync(filter);
return true;
}
catch (Exception e)
{
Log.Error(e.Message);
return false;
}
}
/// <summary>
/// Updates an existing PointOfCare entity asynchronously in the data store.
/// </summary>
/// <param name="pointOfCare">The PointOfCare entity to update, identified by its Id.</param>
public async Task Update(PointOfCare pointOfCare)
{
try
{
await UpdateOneAsync(pointOfCare.Id, pointOfCare);
}
catch (Exception e)
{
Log.Error("Exception trying to update pointOfCare: {pointOfCare}. Exception {e}", pointOfCare, e);
throw;
}
}
/// <summary>
/// Updates the unit name and unit identifier of a PointOfCare document identified by the specified id.
/// </summary>
/// <param name="id">The ObjectId of the PointOfCare document to update.</param>
/// <param name="unit">The Unit object whose Name and Id values will be set on the matching document.</param>
public async Task UpdateUnitId(ObjectId id, Unit unit)
{
var filterBuilder = Builders<PointOfCare>.Filter;
var filter = filterBuilder.Eq(p => p.Id, id);
var update = Builders<PointOfCare>.Update
.Set(p => p.UnitName, unit.Name)
.Set(p => p.UnitId, unit.Id);
await Collection.UpdateOneAsync(filter, update);
}
/// <summary>
/// Updates the relay configuration of a Point of Care by replacing its relay identifier list
/// with the identifiers extracted from the provided relay configuration entries.
/// </summary>
/// <param name="pocId">The unique identifier of the Point of Care whose relay configuration will be updated.</param>
/// <param name="relayConfig">The collection of relays whose identifiers will be stored as the new relay configuration.</param>
public async Task UpdateRelayConfig(ObjectId pocId, List<Relay> relayConfig)
{
var filterBuilder = Builders<PointOfCare>.Filter;
var filter = filterBuilder.Eq(p => p.Id, pocId);
var relayIds = relayConfig.Select(r => r.Id).ToList();
var update = Builders<PointOfCare>.Update
.Set(p => p.Configuration!.RelayIdList, relayIds);
await Collection.UpdateOneAsync(filter, update);
}
/// <summary>
/// Updates the relay configuration of a specific point of care by replacing its associated relay identifier list.
/// </summary>
/// <param name="pocId">The identifier of the point of care whose relay configuration will be updated.</param>
/// <param name="relayConfig">The new list of relay identifiers to assign to the point of care's configuration.</param>
public async Task UpdateRelayConfig(ObjectId pocId, List<ObjectId> relayConfig)
{
var filterBuilder = Builders<PointOfCare>.Filter;
var filter = filterBuilder.Eq(p => p.Id, pocId);
var update = Builders<PointOfCare>.Update
.Set(p => p.Configuration!.RelayIdList, relayConfig);
await Collection.UpdateOneAsync(filter, update);
}
/// <summary>
/// Updates the <see cref="PointOfCareConfiguration"/> of an existing <see cref="PointOfCare"/> entity identified by the specified <paramref name="id"/>, setting only the configuration field via a partial update.
/// </summary>
/// <param name="id">The unique identifier of the <see cref="PointOfCare"/> entity to update.</param>
/// <param name="configuration">The new configuration values to apply to the entity.</param>
public async Task UpdateConfiguration(ObjectId id, PointOfCareConfiguration configuration)
{
var filterBuilder = Builders<PointOfCare>.Filter;
var filter = filterBuilder.Eq(p => p.Id, id);
var update = Builders<PointOfCare>.Update
.Set(p => p.Configuration, configuration);
await Collection.UpdateOneAsync(filter, update);
}
/// <summary>
/// Retrieves a <see cref="PointOfCare"/> entity by its unique identifier from the underlying collection.
/// Returns <c>null</c> if no matching entity is found or if an error occurs during the lookup.
/// </summary>
/// <param name="id">The unique <see cref="ObjectId"/> of the point of care to retrieve.</param>
/// <returns>A <see cref="PointOfCare"/> if a matching record is found; otherwise, <c>null</c>.</returns>
public async Task<PointOfCare?> FindById(ObjectId id)
{
try
{
var filter = Builders<PointOfCare>.Filter.Eq(p => p.Id, id);
return await Collection.Find(filter).FirstOrDefaultAsync();
}
catch (Exception ex)
{
Log.Error("Error searching pointOfCare by id: {id}. Exception: {ex}", id, ex);
return null;
}
}
/// <summary>
/// Retrieves the collection of <see cref="PointOfCare"/> entries associated with the specified unit and status. When <paramref name="excludeVirtual"/> is <c>true</c>, virtual entries such as Pushed, Unknown, Deleted, NoBed, Cancelled, Recovered, and Moved are excluded from the results. If an error occurs during the lookup, an empty collection is returned and the exception is logged.
/// </summary>
/// <param name="unitId">The identifier of the unit whose point of care entries will be searched.</param>
/// <param name="status">The point of care status used to filter the results.</param>
/// <param name="excludeVirtual">When <c>true</c>, filters out entries whose bed matches one of the known <see cref="VirtualPointOfCare"/> values; otherwise virtual entries are included.</param>
/// <returns>A task that resolves to an <see cref="IEnumerable{PointOfCare}"/> containing the matching entries, or an empty collection if the query fails.</returns>
public async Task<IEnumerable<PointOfCare>> FindByUnitAndStatus(ObjectId unitId, StatusEnum.PointOfCare status,
bool excludeVirtual = false)
{
try
{
var filterBuilder = Builders<PointOfCare>.Filter;
var filter = filterBuilder.And(
filterBuilder.Eq(p => p.UnitId, unitId),
filterBuilder.Eq(p => p.Status, status)
);
if (excludeVirtual)
filter = filterBuilder.And(
filter,
filterBuilder.Ne(p => p.Bed, VirtualPointOfCare.Pushed.ToString()),
filterBuilder.Ne(p => p.Bed, VirtualPointOfCare.Unknown.ToString()),
filterBuilder.Ne(p => p.Bed, VirtualPointOfCare.Deleted.ToString()),
filterBuilder.Ne(p => p.Bed, VirtualPointOfCare.NoBed.ToString()),
filterBuilder.Ne(p => p.Bed, VirtualPointOfCare.Cancelled.ToString()),
filterBuilder.Ne(p => p.Bed, VirtualPointOfCare.Recovered.ToString()),
filterBuilder.Ne(p => p.Bed, VirtualPointOfCare.Moved.ToString())
);
var result = await Collection.FindAsync(filter);
return result.ToEnumerable();
}
catch (Exception ex)
{
Log.Error("Error searching pointOfCares by Unit: {unit} and status: {}. Exception: {ex}", unitId.ToString(),
status.ToString(), ex);
return new List<PointOfCare>();
}
}
public async Task<PointOfCare?> FindByBedAndUnitId(string? bed, ObjectId unitId)
{
try
{
if (string.IsNullOrEmpty(bed)) return null;
var filterBuilder = Builders<PointOfCare>.Filter;
List<FilterDefinition<PointOfCare>> filters =
[
filterBuilder.Eq(p => p.Bed, bed),
filterBuilder.Eq(p => p.UnitId, unitId)
// Combina todos los filtros en uno solo usando el operador & si hay más de un filtro, de lo contrario, usa un filtro vacío.
];
// Combina todos los filtros en uno solo usando el operador & si hay más de un filtro, de lo contrario, usa un filtro vacío.
var combinedFilter = filters.Count > 0
? filters.Aggregate((current, next) => current & next)
: filterBuilder.Empty;
var result = await Collection.Find(combinedFilter).Limit(1).FirstOrDefaultAsync();
return result;
}
catch (Exception ex)
{
Log.Error("Error searching pointOfCares by patient location unitId: {unitId}, bed: {bed}. Exception: {ex}",
unitId, bed, ex);
return null;
}
}
/// <summary>
/// Retrieves all points of care associated with the specified unit. If an error occurs during the search, the exception is logged and <c>null</c> is returned.
/// </summary>
/// <param name="unit">The identifier of the unit whose points of care should be retrieved.</param>
/// <returns>A task that yields a collection of <see cref="PointOfCare"/> instances matching the given unit, or <c>null</c> if the search fails.</returns>
public async Task<IEnumerable<PointOfCare>?> FindAllByUnitId(ObjectId unit)
{
try
{
var filter = Builders<PointOfCare>.Filter.Eq(p => p.UnitId, unit);
var result = await Collection.FindAsync(filter);
return result.ToEnumerable();
}
catch (Exception ex)
{
Log.Error("Error searching pointOfCares by Unit: {unit}. Exception: {ex}", unit, ex);
return null;
}
}
/// <summary>
/// Asynchronously retrieves all <see cref="PointOfCare"/> entities that match the specified room.
/// Returns <see langword="null"/> if an error occurs while querying the collection.
/// </summary>
/// <param name="room">The room identifier used to filter the point-of-care records.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains a collection of matching <see cref="PointOfCare"/> entries, or <see langword="null"/> if the search fails.</returns>
public async Task<IEnumerable<PointOfCare>?> FindByRoom(string room)
{
try
{
var filter = Builders<PointOfCare>.Filter.Eq(p => p.Room, room);
var result = await Collection.FindAsync(filter);
return result.ToEnumerable();
}
catch (Exception ex)
{
Log.Error("Error searching pointOfCares by room: {room}. Exception: {ex}", room, ex);
return null;
}
}
/// <summary>
/// Asynchronously finds all <see cref="PointOfCare"/> records matching the specified bed.
/// Returns null if an error occurs during the search.
/// </summary>
/// <param name="bed">The bed identifier used to filter the point of care records.</param>
/// <returns>A task containing an enumerable collection of matching <see cref="PointOfCare"/> records, or null if an error occurs.</returns>
public async Task<IEnumerable<PointOfCare>?> FindByBed(string bed)
{
try
{
var filter = Builders<PointOfCare>.Filter.Eq(p => p.Bed, bed);
var result = await Collection.FindAsync(filter);
return result.ToEnumerable();
}
catch (Exception ex)
{
Log.Error("Error searching pointOfCares by bed: {bed}. Exception: {ex}", bed, ex);
return null;
}
}
/// <summary>
/// Finds a list of <see cref="PointOfCare"/> documents matching the specified filter, optionally applying a projection to shape the returned fields.
/// When a projection is provided, only the projected fields are returned; otherwise, the full documents are returned.
/// </summary>
/// <param name="filter">The MongoDB filter definition used to match the desired <see cref="PointOfCare"/> documents.</param>
/// <param name="projection">An optional projection definition to limit or transform the fields returned in each document. When <c>null</c>, the full documents are returned.</param>
/// <returns>A task that represents the asynchronous operation, containing a list of <see cref="PointOfCare"/> documents that match the filter.</returns>
public async Task<List<PointOfCare>> FindByFilter(FilterDefinition<PointOfCare> filter,
ProjectionDefinition<PointOfCare>? projection = null)
{
if (projection != null)
return await Collection.Find(filter).Project<PointOfCare>(projection).ToListAsync();
return await Collection.Find(filter).ToListAsync();
}
/// <summary>
/// Asynchronously counts the number of <see cref="PointOfCare"/> documents associated with the specified unit, excluding entries whose bed is a virtual or non-representative value (Pushed, Unknown, Deleted, NoBed, Cancelled, UnitData, Recovered, or Moved).
/// </summary>
/// <param name="unitId">The identifier of the unit whose point-of-care entries should be counted.</param>
/// <returns>A <see cref="Task{Int64}"/> that resolves to the number of matching point-of-care documents for the given unit.</returns>
public async Task<long> CountByUnitId(ObjectId unitId)
{
try
{
var filterBuilder = Builders<PointOfCare>.Filter;
var excludedBeds = new[]
{
VirtualPointOfCare.Pushed.ToString(),
VirtualPointOfCare.Unknown.ToString(),
VirtualPointOfCare.Deleted.ToString(),
VirtualPointOfCare.NoBed.ToString(),
VirtualPointOfCare.Cancelled.ToString(),
VirtualPointOfCare.UnitData.ToString(),
VirtualPointOfCare.Recovered.ToString(),
VirtualPointOfCare.Moved.ToString()
};
var filter = filterBuilder.And(
filterBuilder.Eq(p => p.UnitId, unitId),
filterBuilder.Nin(p => p.Bed, excludedBeds)
);
return await Collection.CountDocumentsAsync(filter);
}
catch (Exception e)
{
Log.Logger.Error(e.Message);
throw;
}
}
public async Task<long> CountVirtualsByUnitId(ObjectId unitId)
{
try
{
var filterBuilder = Builders<PointOfCare>.Filter;
// Lista de estados que NO quieres contar
var excludedBeds = new[]
{
VirtualPointOfCare.Pushed.ToString(),
VirtualPointOfCare.Unknown.ToString(),
VirtualPointOfCare.Deleted.ToString(),
VirtualPointOfCare.NoBed.ToString(),
VirtualPointOfCare.Cancelled.ToString(),
VirtualPointOfCare.Recovered.ToString(),
VirtualPointOfCare.UnitData.ToString(),
VirtualPointOfCare.Moved.ToString()
};
// Filtramos por UnitId Y que el Bed NO esté en la lista de excluidos
var filter = filterBuilder.And(
filterBuilder.Eq(p => p.UnitId, unitId),
filterBuilder.In(p => p.Bed, excludedBeds)
);
return await Collection.CountDocumentsAsync(filter);
}
catch (Exception e)
{
Log.Logger.Error(e.Message);
throw;
}
}
/// <summary>
/// Retrieves the <see cref="PointOfCare"/> configuration identified by the specified <paramref name="pocId"/>, projecting only the identifier and configuration fields.
/// Returns <c>null</c> when no matching point of care is found.
/// </summary>
/// <param name="pocId">The unique identifier of the point of care whose configuration is being requested.</param>
/// <returns>A <see cref="Task{PointOfCare}"/> containing the matching <see cref="PointOfCare"/> with its configuration, or <c>null</c> if no record exists for the given identifier.</returns>
public async Task<PointOfCare?> GetPoCConfiguration(ObjectId pocId)
{
var filter = Builders<PointOfCare>.Filter.Eq(p => p.Id, pocId);
var projection = Builders<PointOfCare>.Projection
.Include(p => p.Id)
.Include(p => p.Configuration);
return await Collection.Find(filter).Project<PointOfCare>(projection).FirstOrDefaultAsync();
}
/// <summary>
/// Retrieves all <see cref="PointOfCare"/> records from the underlying data store using an empty filter.
/// </summary>
/// <returns>A <see cref="Task{TResult}"/> containing a <see cref="List{T}"/> of <see cref="PointOfCare"/> items, or <c>null</c> when no results are returned.</returns>
public async Task<List<PointOfCare>?> GetAll()
{
var filter = Builders<PointOfCare>.Filter.Empty;
return await Collection.Find(filter).ToListAsync();
}
/// <summary>
/// Retrieves a <see cref="PointOfCare"/> by its identifier, enriching the result with the full configuration details by performing lookups against the light beacons, cameras, and relays collections.
/// Returns <c>null</c> when no matching point of care is found.
/// </summary>
/// <param name="id">The unique identifier of the point of care to retrieve.</param>
/// <returns>A task that represents the asynchronous operation, containing the matching <see cref="PointOfCare"/> with its resolved beacon, camera, and relay configuration, or <c>null</c> if not found.</returns>
public async Task<PointOfCare?> FindByIdAllConfig(ObjectId id)
{
return await Collection.Aggregate()
.Match(c=>c.Id == id)
.Lookup(_apiSettings.LightBeacons, "configuration.beaconIdList", "_id", "beacons")
.Lookup(_apiSettings.Cameras, "configuration.cameraIdList", "_id", "cameras")
.Lookup(_apiSettings.Relays, "configuration.relayIdList", "_id", "relays")
.Project<PointOfCare>(new BsonDocument
{
{ "_id", 1 },
{ "room", 1 },
{ "bed", 1 },
{ "hall", 1 },
{ "unitId", 1 },
{ "status", 1 },
{ "admissionId", 1 },
{ "configuration", new BsonDocument
{
{ "beaconList", "$beacons" },
{ "cameraList", "$cameras" },
{ "relayList", "$relays" },
{ "beaconIdList", "$configuration.beaconIdList" },
{ "cameraIdList", "$configuration.cameraIdList" },
{ "relayIdList", "$configuration.relayIdList" },
{ "type", "$configuration.type" },
{ "id", "$configuration.id" }
}
}
}).FirstOrDefaultAsync();
}
/// <summary>
/// Obtiene de forma asíncrona todos los identificadores únicos de cámaras que están
/// vinculados a algún PointOfCare.
/// </summary>
/// <remarks>
/// Se retorna un <see cref="HashSet{ObjectId}"/> para optimizar la búsqueda de pertenencia (Contains) en el servicio.
/// Mientras que una <see cref="List{T}"/> requiere un tiempo de búsqueda lineal $O(n)$, el HashSet utiliza
/// una tabla hash que permite verificar si una cámara está en uso en tiempo constante $O(1)$.
/// Esto es crítico para mantener el rendimiento al comparar los IDs de la página actual
/// contra el total de cámaras en uso, independientemente del volumen de datos.
/// </remarks>
/// <returns>Un conjunto hash con los <see cref="ObjectId"/> de las cámaras en uso.</returns>
public async Task<HashSet<ObjectId>> FindAllIdCamerasInUse()
{
var distinctIds = await Collection
.DistinctAsync<ObjectId>("configuration.cameraIdList", Builders<PointOfCare>.Filter.Empty);
var list = await distinctIds.ToListAsync();
return new HashSet<ObjectId>(list);
}
/// <summary>
/// Retrieves all distinct beacon identifiers currently referenced by any point-of-care configuration.
/// </summary>
/// <returns>A set containing the unique <see cref="ObjectId"/> values of beacons in use across all point-of-care records.</returns>
public async Task<HashSet<ObjectId>> FindAllIdBeaconsInUse()
{
var distinctIds = await Collection
.DistinctAsync<ObjectId>("configuration.beaconIdList", Builders<PointOfCare>.Filter.Empty);
var list = await distinctIds.ToListAsync();
return new HashSet<ObjectId>(list);
}
/// <summary>
/// Retrieves all points of care associated with the specified unit, including their related beacons, cameras, and relays resolved through MongoDB lookups and projected to the PointOfCare model.
/// </summary>
/// <param name="unitId">The identifier of the unit used to filter the points of care.</param>
/// <returns>A task that returns a collection of PointOfCare with their associated device lists populated, or null if an error occurs during the query.</returns>
public async Task<IEnumerable<PointOfCare>?> FindAllByUnitIdWithDevices(ObjectId unitId)
{
try
{
var pipeline = Collection.Aggregate()
// 1. Filtramos primero por UnitId (muy importante para rendimiento)
.Match(p => p.UnitId == unitId)
// 2. Realizamos los Lookups usando las colecciones desde settings
.Lookup(_apiSettings.LightBeacons, "configuration.beaconIdList", "_id", "beacons")
.Lookup(_apiSettings.Cameras, "configuration.cameraIdList", "_id", "cameras")
.Lookup(_apiSettings.Relays, "configuration.relayIdList", "_id", "relays")
// 3. Proyectamos para que coincida exactamente con tu modelo C#
.Project<PointOfCare>(new BsonDocument
{
{ "_id", 1 },
{ "room", 1 },
{ "bed", 1 },
{ "hall", 1 },
{ "unitId", 1 },
{ "status", 1 },
{ "admissionId", 1 },
{ "configuration", new BsonDocument
{
// Mapeamos los arrays temporales a las propiedades de la clase
{ "beaconList", "$beacons" },
{ "cameraList", "$cameras" },
{ "relayList", "$relays" },
{ "beaconIdList", "$configuration.beaconIdList" },
{ "cameraIdList", "$configuration.cameraIdList" },
{ "relayIdList", "$configuration.relayIdList" },
{ "type", "$configuration.type" },
{ "id", "$configuration.id" }
}
}
});
return await pipeline.ToListAsync();
}
catch (Exception ex)
{
Log.Error("Error searching pointOfCares by Unit: {unit}. Exception: {ex}", unitId, ex);
return null;
}
}
/// <summary>
/// Retrieves the set of distinct relay identifiers currently referenced by any <see cref="PointOfCare"/> configuration in the collection.
/// </summary>
/// <returns>A <see cref="HashSet{T}"/> of <see cref="ObjectId"/> values containing all unique relay IDs found across the <c>configuration.relayIdList</c> field of every document.</returns>
public async Task<HashSet<ObjectId>> FindAllIdRelaysInUse()
{
var distinctIds = await Collection
.DistinctAsync<ObjectId>("configuration.relayIdList", Builders<PointOfCare>.Filter.Empty);
var list = await distinctIds.ToListAsync();
return new HashSet<ObjectId>(list);
}
/// <summary>
/// Retrieves all point-of-care configurations, joining each configuration with its associated light beacons, cameras, and relays through MongoDB lookups and projecting the combined result into a flattened <see cref="PointOfCare"/> structure.
/// </summary>
/// <returns>A task that resolves to a list of <see cref="PointOfCare"/> objects enriched with the corresponding beacon, camera, and relay details, or <c>null</c> when no configurations are found.</returns>
public async Task<List<PointOfCare>?> GetAllConfigs()
{
var pipeline = Collection.Aggregate()
.Lookup(
_apiSettings.LightBeacons,
"configuration.beaconIdList",
"_id",
"beacons"
)
.Lookup(
_apiSettings.Cameras,
"configuration.cameraIdList",
"_id",
"cameras"
)
.Lookup(
_apiSettings.Relays,
"configuration.relayIdList",
"_id",
"relays"
)
.Project<PointOfCare>(new BsonDocument
{
{ "_id", 1 },
{ "room", 1 },
{ "bed", 1 },
{ "hall", 1 },
{ "unitId", 1 },
{ "status", 1 },
{ "admissionId", 1 },
{ "configuration", new BsonDocument
{
{ "beaconList", "$beacons" },
{ "cameraList", "$cameras" },
{ "relayList", "$relays" },
{ "type", "$configuration.type" },
{ "id", "$configuration.id" }
}
}
});
return await pipeline.ToListAsync();
}
/// <summary>
/// Retrieves all point of care location records from the collection, projecting only the essential location fields (Id, UnitId, Room, and Bed).
/// Uses an empty filter to return every record and returns a nullable list that may be null if no results are found.
/// </summary>
/// <returns>A task that represents the asynchronous operation, containing a list of <see cref="PointOfCare"/> objects with the projected fields, or <c>null</c> if no matching records exist.</returns>
public async Task<List<PointOfCare>?> GetAllLocationInfo()
{
var filter = Builders<PointOfCare>.Filter.Empty;
var projection = Builders<PointOfCare>.Projection
.Include(p => p.Id)
.Include(p => p.UnitId)
.Include(p => p.Room)
.Include(p => p.Bed);
var result = await Collection.Find(filter).Project<PointOfCare>(projection).ToListAsync();
return result;
}
/// <summary>
/// Retrieves a paginated, filtered query of <see cref="PointOfCare"/> records, supporting optional filtering by unit (by id or name) and status, sorted by id descending.
/// </summary>
/// <param name="filter">The pagination and filtering criteria. If <c>FilteredRequest</c> is null, no additional filters are applied.</param>
/// <returns>An <see cref="IFindFluent{PointOfCare, PointOfCare}"/> representing the resulting MongoDB query with the applied filters and sort.</returns>
public IFindFluent<PointOfCare, PointOfCare> GetPaginatedPoCs(PaginationFilter filter)
{
var filterBuilder = Builders<PointOfCare>.Filter;
var sort = Builders<PointOfCare>.Sort.Descending("_id");
var filters = new List<FilterDefinition<PointOfCare>>();
if (filter.FilteredRequest == null) return CreateFindFluent(filters, sort);
// Verifica UnitName contiene el valor de FilteredRequest.Text
if (!string.IsNullOrEmpty(filter.FilteredRequest?.UnitId) &&
ObjectId.TryParse(filter.FilteredRequest.UnitId, out var unitId))
filters.Add(filterBuilder.Eq(d => d.UnitId, unitId));
else if (!string.IsNullOrEmpty(filter.FilteredRequest?.UnitName))
filters.Add(filterBuilder.Eq(d => d.Unit!.Name, filter.FilteredRequest?.UnitName));
if (filter.FilteredRequest?.PointOfCareStatus != null)
{
var statusFilter = filter.FilteredRequest.PointOfCareStatus;
filters.Add(filterBuilder.Eq(p => p.Status, statusFilter));
}
return CreateFindFluent(filters, sort);
}
//Deprecated PatientLocation by UnitName
public async Task<PointOfCare?> FindByPatientLocation(PatientLocation patientLocation)
{
try
{
var filterBuilder = Builders<PointOfCare>.Filter;
List<FilterDefinition<PointOfCare>> filters = [];
if (!string.IsNullOrEmpty(patientLocation.UnitName))
filters.Add(filterBuilder.Eq(p => p.UnitName, patientLocation.UnitName));
if (!string.IsNullOrEmpty(patientLocation.Bed))
filters.Add(filterBuilder.Eq(p => p.Bed, patientLocation.Bed));
if (!string.IsNullOrEmpty(patientLocation.Room))
filters.Add(filterBuilder.Eq(p => p.Room, patientLocation.Room));
// Combina todos los filtros en uno solo usando el operador & si hay más de un filtro, de lo contrario, usa un filtro vacío.
var combinedFilter = filters.Count > 0
? filters.Aggregate((current, next) => current & next)
: filterBuilder.Empty;
var result = await Collection.Find(combinedFilter).Limit(1).FirstOrDefaultAsync();
return result;
}
catch (Exception ex)
{
Log.Error("Error searching pointOfCares by patient location: {bed}. Exception: {ex}", patientLocation, ex);
return null;
}
}
/// <summary>
/// Creates MongoDB indexes for the <see cref="PointOfCare"/> collection on the <c>unitId</c>, <c>room</c>, and <c>bed</c> fields, using background creation and non-unique constraints, to optimize query performance.
/// </summary>
public override async Task CreateIndexes()
{
var options = new CreateIndexOptions { Background = true, Unique = false };
var indexes = new List<CreateIndexModel<PointOfCare>>
{
new("{ unitId: 1 }", options),
new("{ room: 1 }", options),
new("{ bed: 1 }", options)
};
await MongoUtils.EnsureIndexes(Collection, indexes);
}
/// <summary>
/// Updates the status of an existing point-of-care record identified by its unique identifier.
/// </summary>
/// <param name="id">The unique identifier of the point-of-care record to update.</param>
/// <param name="status">The new status value to apply to the point-of-care record.</param>
public async Task UpdateStatus(ObjectId id, StatusEnum.PointOfCare status)
{
var filterBuilder = Builders<PointOfCare>.Filter;
var filter = filterBuilder.Eq(p => p.Id, id);
var update = Builders<PointOfCare>.Update
.Set(p => p.Status, status);
await Collection.UpdateOneAsync(filter, update);
}
/// <summary>
/// Builds a queryable <see cref="IFindFluent{TDocument, TProjection}"/> for <see cref="PointOfCare"/> by combining the provided filters with an AND and applying the given sort. When no filters are supplied, an empty filter is used so that all documents are returned.
/// </summary>
/// <param name="filters">The list of filter definitions to be combined; an empty list results in no filtering.</param>
/// <param name="sort">The sort definition applied to the resulting query.</param>
/// <returns>An <see cref="IFindFluent{PointOfCare, PointOfCare}"/> representing the filtered and sorted query.</returns>
private IFindFluent<PointOfCare, PointOfCare> CreateFindFluent(List<FilterDefinition<PointOfCare>> filters,
SortDefinition<PointOfCare> sort)
{
var combinedFilter = filters.Any()
? Builders<PointOfCare>.Filter.And(filters)
: Builders<PointOfCare>.Filter.Empty; // Filtra todo si no hay filtros
return Collection.Find(combinedFilter).Sort(sort);
}
}