334 lines
17 KiB
C#
334 lines
17 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.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;
|
|
|
|
/// <summary>
|
|
/// Represents a MongoDB-backed repository for <see cref="PatientTreatment"/> entities, inheriting common data access functionality from <see cref="MongoRepository{T}"/> and implementing the <see cref="ITreatmentRepository"/> contract.
|
|
/// </summary>
|
|
public class TreatmentRepository : MongoRepository<PatientTreatment>, ITreatmentRepository
|
|
{
|
|
private readonly ApiSettings _apiSettings;
|
|
|
|
|
|
public TreatmentRepository(IOptions<ApiSettings> apiSettings, IMongoDatabase database) : base(database)
|
|
{
|
|
if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings));
|
|
_apiSettings = apiSettings.Value;
|
|
} //For testing
|
|
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <returns>The configured patients treatments collection name, or "patients_treatments" if no setting is defined.</returns>
|
|
public override string GetCollectionName()
|
|
{
|
|
return _apiSettings.PatientsTreatments ?? "patients_treatments";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves all patient treatment records associated with the specified patient identifier.
|
|
/// </summary>
|
|
/// <param name="patientId">The unique identifier of the patient whose treatments are being queried.</param>
|
|
/// <returns>A collection of <see cref="PatientTreatment"/> records matching the specified patient identifier.</returns>
|
|
public async Task<IEnumerable<PatientTreatment>> GetByPatientId(ObjectId patientId)
|
|
{
|
|
var filter = Builders<PatientTreatment>.Filter.Eq(ob => ob.PatientId, patientId);
|
|
var result = await Collection.FindAsync(filter);
|
|
return result.ToEnumerable();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves a patient treatment record from the database that matches the specified identifier.
|
|
/// </summary>
|
|
/// <param name="id">The unique identifier of the patient treatment to locate.</param>
|
|
/// <returns>An asynchronous cursor containing the patient treatment matching the provided identifier.</returns>
|
|
public async Task<IAsyncCursor<PatientTreatment>> GetById(ObjectId id)
|
|
{
|
|
var filter = Builders<PatientTreatment>.Filter.Eq(ob => ob.Id, id);
|
|
var result = await Collection.FindAsync(filter);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Inserts a patient treatment record, defaulting the order time to the current UTC time when it is not already set.
|
|
/// </summary>
|
|
/// <param name="treatment">The patient treatment to insert.</param>
|
|
public override async Task InsertOneAsync(PatientTreatment treatment)
|
|
{
|
|
treatment.OrderTime ??= DateTime.UtcNow;
|
|
await base.InsertOneAsync(treatment);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes a patient treatment record from the database by its unique identifier.
|
|
/// </summary>
|
|
/// <param name="id">The unique identifier of the patient treatment to remove.</param>
|
|
public new async Task DeleteAsync(ObjectId id)
|
|
{
|
|
var filter = Builders<PatientTreatment>.Filter.Eq(ob => ob.Id, id);
|
|
await Collection.DeleteOneAsync(filter);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Retrieves all patient treatment records associated with the specified patient identifier from the MongoDB collection.
|
|
/// </summary>
|
|
/// <param name="patientId">The unique ObjectId of the patient whose treatment records should be returned.</param>
|
|
/// <returns>An <see cref="IAsyncCursor{TDocument}"/> of <see cref="PatientTreatment"/> containing the matching treatment records.</returns>
|
|
public async Task<IAsyncCursor<PatientTreatment>> FindByPatientIdAsync(ObjectId patientId)
|
|
{
|
|
var filter = Builders<PatientTreatment>.Filter.Eq(ob => ob.PatientId, patientId);
|
|
return await Collection.FindAsync(filter);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Deletes all <see cref="PatientTreatment"/> records associated with the specified patient identifier.
|
|
/// Returns <c>true</c> when the delete operation completes, and <c>false</c> if an exception is encountered, in which case the error is logged.
|
|
/// </summary>
|
|
/// <param name="patientId">The identifier of the patient whose treatment records should be removed.</param>
|
|
/// <returns>A task that resolves to <c>true</c> on successful deletion, or <c>false</c> if the operation failed due to an exception.</returns>
|
|
public async Task<bool> DeleteByPatientId(ObjectId patientId)
|
|
{
|
|
try
|
|
{
|
|
var filter = Builders<PatientTreatment>.Filter.Eq(po => po.PatientId, patientId);
|
|
await Collection.DeleteManyAsync(filter);
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex.ToString());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="treatment">The patient treatment entity containing the updated information, identified by its <c>Id</c>.</param>
|
|
/// <returns><c>true</c> if the update succeeds; otherwise, <c>false</c> if an exception occurs during the operation.</returns>
|
|
public async Task<bool> Update(PatientTreatment treatment)
|
|
{
|
|
try
|
|
{
|
|
await UpdateOneAsync(treatment.Id, treatment);
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex.ToString());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retrieves all bolus treatments associated with the specified patient, filtering for records that have at least one entry in their <c>RequestedGiveCodesStatus</c> array.
|
|
/// </summary>
|
|
/// <param name="patientId">The unique identifier of the patient whose bolus treatments are being queried.</param>
|
|
/// <returns>A task that represents the asynchronous operation, containing a list of <see cref="PatientTreatment"/> documents matching the patient and having a non-empty <c>RequestedGiveCodesStatus</c>.</returns>
|
|
public async Task<List<PatientTreatment>> FindBolusTreatments(ObjectId patientId)
|
|
{
|
|
var builder = Builders<PatientTreatment>.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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="patientId">The identifier of the patient whose treatments will be searched.</param>
|
|
/// <param name="order">The entity identifier of the placer order used to match treatments.</param>
|
|
/// <returns>A task that represents the asynchronous operation. The task result contains the list of matching <see cref="PatientTreatment"/> entries.</returns>
|
|
public async Task<List<PatientTreatment>> GetActiveTreatmentsByPatientIdAndOrder(ObjectId patientId, string order)
|
|
{
|
|
var builder = Builders<PatientTreatment>.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();
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Updates the identifier of a related entity by replacing the old object identifier with the new one across multiple records.
|
|
/// </summary>
|
|
/// <param name="nameId">The name of the field or relationship whose object identifier should be updated.</param>
|
|
/// <param name="id">The new object identifier to replace the old one with.</param>
|
|
/// <param name="oldId">The existing object identifier that should be replaced.</param>
|
|
public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
|
|
{
|
|
await UpdateManyObjectIdAsync(nameId, id, oldId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously retrieves all patient treatment records associated with the specified patient identifier from the underlying data store.
|
|
/// </summary>
|
|
/// <param name="patientId">The unique <see cref="ObjectId"/> of the patient whose treatments are being queried.</param>
|
|
/// <returns>A task that represents the asynchronous operation, containing an <see cref="IEnumerable{PatientTreatment}"/> with the matching treatment records. Returns an empty sequence if no treatments are found.</returns>
|
|
public async Task<IEnumerable<PatientTreatment>> FindByPatientId(ObjectId patientId)
|
|
{
|
|
var filter = Builders<PatientTreatment>.Filter.Eq(ob => ob.PatientId, patientId);
|
|
var result = await Collection.FindAsync(filter);
|
|
return result.ToEnumerable();
|
|
}
|
|
|
|
public IFindFluent<PatientTreatment, PatientTreatment> GetPaginatedTreatments(PaginationFilter filter)
|
|
{
|
|
var filterBuilder = Builders<PatientTreatment>.Filter;
|
|
var sort = Builders<PatientTreatment>.Sort.Descending("orderTime");
|
|
var filters = new List<FilterDefinition<PatientTreatment>>();
|
|
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensures the required MongoDB indexes exist for the <see cref="PatientTreatment"/> collection, creating a non-unique background index on the <c>patientid</c> field to optimize query performance without blocking other database operations.
|
|
/// </summary>
|
|
public override async Task CreateIndexes()
|
|
{
|
|
var options = new CreateIndexOptions { Background = true, Unique = false };
|
|
var indexes = new List<CreateIndexModel<PatientTreatment>>
|
|
{
|
|
new("{ patientid: 1 }", options)
|
|
};
|
|
|
|
await MongoUtils.EnsureIndexes(Collection, indexes);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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).
|
|
/// </summary>
|
|
/// <param name="filters">The collection of filter definitions to which the active treatment criteria will be appended.</param>
|
|
/// <param name="filterBuilder">The builder used to construct the individual filter conditions combined for the active treatment logic.</param>
|
|
private static void AddActiveTreatmentFilters(List<FilterDefinition<PatientTreatment>> filters,
|
|
FilterDefinitionBuilder<PatientTreatment> 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)
|
|
)
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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 <see cref="DateTime.MinValue"/> 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 <see cref="DateTime.MaxValue"/> if not provided).
|
|
/// </summary>
|
|
/// <param name="filters">The list of filter definitions to which the start and end time filters will be added.</param>
|
|
/// <param name="filter">The pagination filter containing the optional start and end date values from the request.</param>
|
|
/// <param name="filterBuilder">The filter definition builder used to construct the MongoDB filter expressions.</param>
|
|
private void AddDefaultTimeFilters(List<FilterDefinition<PatientTreatment>> filters, PaginationFilter filter,
|
|
FilterDefinitionBuilder<PatientTreatment> 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)
|
|
)
|
|
);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a MongoDB fluent find query for <see cref="PatientTreatment"/> 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.
|
|
/// </summary>
|
|
/// <param name="filters">The list of filter definitions to combine; if empty, an empty filter is used instead.</param>
|
|
/// <param name="sort">The sort definition to apply to the query results.</param>
|
|
/// <returns>An <see cref="IFindFluent{PatientTreatment, PatientTreatment}"/> representing the configured find query with the combined filter and sort applied.</returns>
|
|
private IFindFluent<PatientTreatment, PatientTreatment> CreateFindFluent(
|
|
List<FilterDefinition<PatientTreatment>> filters, SortDefinition<PatientTreatment> sort)
|
|
{
|
|
var combinedFilter = filters.Any()
|
|
? Builders<PatientTreatment>.Filter.And(filters)
|
|
: Builders<PatientTreatment>.Filter.Empty; // Filtra todo si no hay filtros
|
|
return Collection.Find(combinedFilter).Sort(sort);
|
|
}
|
|
} |