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);
}
}