Creado apartir del commit b888de6c92056de294456f581a56e25e734d4ab7 de develop

This commit is contained in:
jrojas
2026-06-26 09:52:35 +02:00
commit 319fd3dfb0
746 changed files with 106610 additions and 0 deletions
@@ -0,0 +1,614 @@
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;
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));
}
public override string GetCollectionName()
{
return _apiSettings.Locations;
}
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;
}
}
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;
}
}
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;
}
}
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;
}
}
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);
}
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);
}
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);
}
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);
}
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;
}
}
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;
}
}
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;
}
}
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;
}
}
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;
}
}
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();
}
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;
}
}
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();
}
public async Task<List<PointOfCare>?> GetAll()
{
var filter = Builders<PointOfCare>.Filter.Empty;
return await Collection.Find(filter).ToListAsync();
}
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);
}
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);
}
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;
}
}
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);
}
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();
}
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;
}
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;
}
}
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);
}
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);
}
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);
}
}