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.Filter; using Microsoft.Extensions.Options; using MongoDB.Bson; using MongoDB.Driver; using Serilog; using MongoUtils = adas_core.Infrastructure.Utils.MongoUtils; namespace adas_core.Infrastructure.Repositories; /// /// Represents a MongoDB-backed repository for entities, inheriting common data access functionality from and implementing the contract. /// public class TreatmentRepository : MongoRepository, ITreatmentRepository { private readonly ApiSettings _apiSettings; public TreatmentRepository(IOptions apiSettings, IMongoDatabase database) : base(database) { if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings)); _apiSettings = apiSettings.Value; } //For testing /// /// Retrieves the collection name for patients treatments, using the value configured in API settings or falling back to the default "patients_treatments" when the setting is not specified. /// /// The configured patients treatments collection name, or "patients_treatments" if no setting is defined. public override string GetCollectionName() { return _apiSettings.PatientsTreatments ?? "patients_treatments"; } /// /// Retrieves all patient treatment records associated with the specified patient identifier. /// /// The unique identifier of the patient whose treatments are being queried. /// A collection of records matching the specified patient identifier. public async Task> GetByPatientId(ObjectId patientId) { var filter = Builders.Filter.Eq(ob => ob.PatientId, patientId); var result = await Collection.FindAsync(filter); return result.ToEnumerable(); } /// /// Retrieves a patient treatment record from the database that matches the specified identifier. /// /// The unique identifier of the patient treatment to locate. /// An asynchronous cursor containing the patient treatment matching the provided identifier. public async Task> GetById(ObjectId id) { var filter = Builders.Filter.Eq(ob => ob.Id, id); var result = await Collection.FindAsync(filter); return result; } /// /// Inserts a patient treatment record, defaulting the order time to the current UTC time when it is not already set. /// /// The patient treatment to insert. public override async Task InsertOneAsync(PatientTreatment treatment) { treatment.OrderTime ??= DateTime.UtcNow; await base.InsertOneAsync(treatment); } /// /// Deletes a patient treatment record from the database by its unique identifier. /// /// The unique identifier of the patient treatment to remove. public new async Task DeleteAsync(ObjectId id) { var filter = Builders.Filter.Eq(ob => ob.Id, id); await Collection.DeleteOneAsync(filter); } /// /// Retrieves all patient treatment records associated with the specified patient identifier from the MongoDB collection. /// /// The unique ObjectId of the patient whose treatment records should be returned. /// An of containing the matching treatment records. public async Task> FindByPatientIdAsync(ObjectId patientId) { var filter = Builders.Filter.Eq(ob => ob.PatientId, patientId); return await Collection.FindAsync(filter); } /// /// Deletes all records associated with the specified patient identifier. /// Returns true when the delete operation completes, and false if an exception is encountered, in which case the error is logged. /// /// The identifier of the patient whose treatment records should be removed. /// A task that resolves to true on successful deletion, or false if the operation failed due to an exception. public async Task DeleteByPatientId(ObjectId patientId) { try { var filter = Builders.Filter.Eq(po => po.PatientId, patientId); await Collection.DeleteManyAsync(filter); return true; } catch (Exception ex) { Log.Error(ex.ToString()); return false; } } /// /// Updates an existing patient treatment record asynchronously, returning a boolean indicating success or failure. /// If the update operation throws an exception, the error is logged and the method returns false instead of propagating the exception. /// /// The patient treatment entity containing the updated information, identified by its Id. /// true if the update succeeds; otherwise, false if an exception occurs during the operation. public async Task Update(PatientTreatment treatment) { try { await UpdateOneAsync(treatment.Id, treatment); return true; } catch (Exception ex) { Log.Error(ex.ToString()); return false; } } /// /// Retrieves all bolus treatments associated with the specified patient, filtering for records that have at least one entry in their RequestedGiveCodesStatus array. /// /// The unique identifier of the patient whose bolus treatments are being queried. /// A task that represents the asynchronous operation, containing a list of documents matching the patient and having a non-empty RequestedGiveCodesStatus. public async Task> FindBolusTreatments(ObjectId patientId) { var builder = Builders.Filter; var filter = builder.And( builder.Eq(t => t.PatientId, patientId), builder.Exists(t => t.RequestedGiveCodesStatus), builder.SizeGt(t => t.RequestedGiveCodesStatus, 0) ); var result = await Collection.FindAsync(filter); return await result.ToListAsync(); } /// /// Retrieves the list of patient treatments associated with the specified patient and placer order identifier, /// limited to treatments whose order control is set to New (Nw) or Xo. /// /// The identifier of the patient whose treatments will be searched. /// The entity identifier of the placer order used to match treatments. /// A task that represents the asynchronous operation. The task result contains the list of matching entries. public async Task> GetActiveTreatmentsByPatientIdAndOrder(ObjectId patientId, string order) { var builder = Builders.Filter; var filter = builder.And( builder.Or( builder.Eq(t => t.OrderControl, OrderControlType.Nw), builder.Eq(t => t.OrderControl, OrderControlType.Xo) ), builder.Eq(t => t.PatientId, patientId), builder.And( builder.Ne(t => t.PlacerOrder, null), // Verifica que no sea nulo builder.Eq(t => t.PlacerOrder!.EntityIdentifier, order) ) ); var result = await Collection.FindAsync(filter); return await result.ToListAsync(); } /// /// Updates the identifier of a related entity by replacing the old object identifier with the new one across multiple records. /// /// The name of the field or relationship whose object identifier should be updated. /// The new object identifier to replace the old one with. /// The existing object identifier that should be replaced. public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId) { await UpdateManyObjectIdAsync(nameId, id, oldId); } /// /// Asynchronously retrieves all patient treatment records associated with the specified patient identifier from the underlying data store. /// /// The unique of the patient whose treatments are being queried. /// A task that represents the asynchronous operation, containing an with the matching treatment records. Returns an empty sequence if no treatments are found. public async Task> FindByPatientId(ObjectId patientId) { var filter = Builders.Filter.Eq(ob => ob.PatientId, patientId); var result = await Collection.FindAsync(filter); return result.ToEnumerable(); } public IFindFluent GetPaginatedTreatments(PaginationFilter filter) { var filterBuilder = Builders.Filter; var sort = Builders.Sort.Descending("orderTime"); var filters = new List>(); if (filter.FilteredRequest == null) { AddDefaultTimeFilters(filters, filter, filterBuilder); return CreateFindFluent(filters, sort); } var requestFilter = filter.FilteredRequest; if (requestFilter.PatientId != null && ObjectId.TryParse(requestFilter.PatientId, out var patientObjectId)) filters.Add(filterBuilder.Eq(t => t.PatientId, patientObjectId)); if (requestFilter.StartDate != null) filters.Add(filterBuilder.Gt(t => t.OrderTime, requestFilter.StartDate)); if (requestFilter.EndDate != null) filters.Add(filterBuilder.Lt(t => t.OrderTime, requestFilter.EndDate)); if (string.IsNullOrWhiteSpace(requestFilter.Text)) { //TODO falta definir la búsqueda por texto } if (requestFilter.ActiveTreatments) AddActiveTreatmentFilters(filters, filterBuilder); else AddDefaultTimeFilters(filters, filter, filterBuilder); return CreateFindFluent(filters, sort); } /// /// Ensures the required MongoDB indexes exist for the collection, creating a non-unique background index on the patientid field to optimize query performance without blocking other database operations. /// public override async Task CreateIndexes() { var options = new CreateIndexOptions { Background = true, Unique = false }; var indexes = new List> { new("{ patientid: 1 }", options) }; await MongoUtils.EnsureIndexes(Collection, indexes); } /// /// Adds filter definitions to identify active patient treatments, including those with a placer order identifier, a valid time range relative to the current time, and an order control type of New or Change Order (excluding Discontinue). /// /// The collection of filter definitions to which the active treatment criteria will be appended. /// The builder used to construct the individual filter conditions combined for the active treatment logic. private static void AddActiveTreatmentFilters(List> filters, FilterDefinitionBuilder filterBuilder) { var currentTime = DateTime.UtcNow; filters.Add( filterBuilder.And( filterBuilder.Ne(t => t.PlacerOrder, null), filterBuilder.Ne(t => t.PlacerOrder!.EntityIdentifier, null) ) ); filters.Add( filterBuilder.Or( filterBuilder.Eq(t => t.StartTime, null), filterBuilder.Lte(t => t.StartTime, currentTime) ) ); filters.Add( filterBuilder.Or( filterBuilder.Eq(t => t.EndTime, null), filterBuilder.Gte(t => t.EndTime, currentTime) ) ); filters.Add(filterBuilder.Ne(t => t.OrderControl, OrderControlType.Dc)); filters.Add( filterBuilder.Or( filterBuilder.Eq(t => t.OrderControl, OrderControlType.Nw), filterBuilder.Eq(t => t.OrderControl, OrderControlType.Xo) ) ); } /// /// Adds default time-range filter conditions to the specified filters list, including patient treatments that have no start or end time set. /// Treatments with a null start time are always included, and those with a set start time must be after the configured start date (defaulting to if not provided). /// Treatments with a null end time are always included, and those with a set end time must be before the configured end date (defaulting to if not provided). /// /// The list of filter definitions to which the start and end time filters will be added. /// The pagination filter containing the optional start and end date values from the request. /// The filter definition builder used to construct the MongoDB filter expressions. private void AddDefaultTimeFilters(List> filters, PaginationFilter filter, FilterDefinitionBuilder filterBuilder) { filters.Add( filterBuilder.Or( filterBuilder.Eq(t => t.StartTime, null), filterBuilder.Gt(p => p.StartTime, filter.FilteredRequest?.StartDate ?? DateTime.MinValue) ) ); filters.Add( filterBuilder.Or( filterBuilder.Eq(t => t.EndTime, null), filterBuilder.Lt(p => p.EndTime, filter.FilteredRequest?.EndDate ?? DateTime.MaxValue) ) ); } /// /// Creates a MongoDB fluent find query for by combining the provided filters with a logical AND /// and applying the specified sort definition. When no filters are supplied, an empty filter is used to match all documents. /// /// The list of filter definitions to combine; if empty, an empty filter is used instead. /// The sort definition to apply to the query results. /// An representing the configured find query with the combined filter and sort applied. private IFindFluent CreateFindFluent( List> filters, SortDefinition sort) { var combinedFilter = filters.Any() ? Builders.Filter.And(filters) : Builders.Filter.Empty; // Filtra todo si no hay filtros return Collection.Find(combinedFilter).Sort(sort); } }