rama creada apartir de master en j

This commit is contained in:
jrojas
2026-06-26 10:29:23 +02:00
parent 319fd3dfb0
commit c1517fda87
2810 changed files with 1927392 additions and 25392 deletions
@@ -13,6 +13,18 @@ using MongoDB.Driver;
namespace adas_core.Application.Services;
/// <summary>
/// Provides business logic for managing patient treatments, including insertion,
/// update, deletion, archiving, pagination, and broadcasting of treatment events
/// to subscribed clients.
/// </summary>
/// <remarks>
/// This service orchestrates interactions between the treatment repository, the
/// patient service, configuration/calculation observation services, and audit
/// services. It also handles inbound HL7-like pharmacy/treatment messages
/// (<c>OMP_O09</c>, <c>ORM_O01</c>, <c>RAS_O17</c>) and emits broadcasts to
/// subscribers based on the patient's point of care.
/// </remarks>
public class TreatmentService(
ITreatmentRepository treatmentRepository,
ITreatmentArchiveRepository treatmentArchiveRepository,
@@ -27,11 +39,29 @@ public class TreatmentService(
ILocalAuditService auditService)
: ITreatmentService
{
/// <summary>
/// Retrieves all treatments associated with the specified patient identifier.
/// </summary>
/// <param name="id">The <see cref="ObjectId"/> of the patient.</param>
/// <returns>
/// A task that represents the asynchronous operation. The task result contains
/// an <see cref="IEnumerable{T}"/> of <see cref="PatientTreatment"/> for the patient.
/// </returns>
public async Task<IEnumerable<PatientTreatment>> GetTreatmentsByPatientId(ObjectId id)
{
return await treatmentRepository.GetByPatientId(id);
}
/// <summary>
/// Inserts a new patient treatment after applying mapping/calculation rules
/// and creates an audit log entry.
/// </summary>
/// <param name="treatment">The <see cref="PatientTreatment"/> to insert.</param>
/// <returns>A task that represents the asynchronous insert operation.</returns>
/// <remarks>
/// If the treatment cannot be mapped, it is silently ignored. A broadcast
/// is dispatched asynchronously upon successful insertion.
/// </remarks>
public async Task Insert(PatientTreatment treatment)
{
logger.LogDebug("Insert {treatment}", treatment);
@@ -44,6 +74,20 @@ public class TreatmentService(
}
}
/// <summary>
/// Deletes all treatments belonging to the specified patient and creates
/// an audit log entry capturing the deleted state.
/// </summary>
/// <param name="id">The <see cref="ObjectId"/> of the patient whose treatments will be deleted.</param>
/// <returns>
/// A task that represents the asynchronous operation. The task result is
/// <see langword="true"/> if the deletion succeeds or there is nothing to delete;
/// otherwise the method throws.
/// </returns>
/// <exception cref="ConflictException">
/// Thrown when the patient has no treatments to delete (no record was found)
/// or when the underlying delete operation fails.
/// </exception>
public async Task<bool> DeleteByPatientId(ObjectId id)
{
var treatmentToDelete = await FindByPatientId(id) ??
@@ -59,11 +103,22 @@ public class TreatmentService(
return result;
}
/// <summary>
/// Archives all treatments associated with the supplied patient.
/// </summary>
/// <param name="patient">The <see cref="Patient"/> whose treatments will be archived.</param>
/// <returns>A task that represents the asynchronous archive operation.</returns>
public async Task Archive(Patient patient)
{
await ArchiveByPatientId(patient.Id);
}
/// <summary>
/// Copies all treatments of the specified patient into the treatment archive
/// repository and subsequently removes them from the primary repository.
/// </summary>
/// <param name="id">The <see cref="ObjectId"/> of the patient whose treatments will be archived.</param>
/// <returns>A task that represents the asynchronous archive operation.</returns>
public async Task ArchiveByPatientId(ObjectId id)
{
logger.LogDebug("ArchiveB Treatments by Patient Id PatientId {id}", id);
@@ -77,6 +132,18 @@ public class TreatmentService(
await DeleteByPatientId(id);
}
/// <summary>
/// Updates an existing treatment and writes an audit log entry comparing the
/// previous and updated values.
/// </summary>
/// <param name="patientTreatment">The <see cref="PatientTreatment"/> containing the updated values.</param>
/// <returns>
/// A task that represents the asynchronous operation. The task result is
/// <see langword="true"/> if the update succeeds.
/// </returns>
/// <exception cref="ConflictException">
/// Thrown when the treatment does not exist or the update operation fails.
/// </exception>
public async Task<bool> UpdateTreatment(PatientTreatment patientTreatment)
{
var oldTreatment = await treatmentRepository.GetById(patientTreatment.Id) ??
@@ -91,26 +158,69 @@ public class TreatmentService(
return result;
}
/// <summary>
/// Returns an asynchronous cursor over all treatments of the given patient.
/// </summary>
/// <param name="patientId">The <see cref="ObjectId"/> of the patient.</param>
/// <returns>
/// A task that represents the asynchronous operation. The task result contains
/// an <see cref="IAsyncCursor{T}"/> of <see cref="PatientTreatment"/>.
/// </returns>
public async Task<IAsyncCursor<PatientTreatment>> FindByPatientIdAsync(ObjectId patientId)
{
return await treatmentRepository.FindByPatientIdAsync(patientId);
}
/// <summary>
/// Returns all treatments of the given patient as an enumerable.
/// </summary>
/// <param name="patientId">The <see cref="ObjectId"/> of the patient.</param>
/// <returns>
/// A task that represents the asynchronous operation. The task result contains
/// an <see cref="IEnumerable{T}"/> of <see cref="PatientTreatment"/>.
/// </returns>
public async Task<IEnumerable<PatientTreatment>> FindByPatientId(ObjectId patientId)
{
return await treatmentRepository.FindByPatientId(patientId);
}
/// <summary>
/// Returns all bolus treatments of the specified patient.
/// </summary>
/// <param name="patientId">The <see cref="ObjectId"/> of the patient.</param>
/// <returns>
/// A task that represents the asynchronous operation. The task result contains
/// a <see cref="List{T}"/> of <see cref="PatientTreatment"/> representing the bolus treatments.
/// </returns>
public async Task<List<PatientTreatment>> GetBolusTreatments(ObjectId patientId)
{
return await treatmentRepository.FindBolusTreatments(patientId);
}
/// <summary>
/// Asynchronous entry point that delegates to <see cref="SaveRequest(ApiRequest)"/>.
/// </summary>
/// <param name="apiRequest">The inbound <see cref="ApiRequest"/> to process.</param>
/// <returns>A task that represents the asynchronous save operation.</returns>
public async Task SaveRequestAsync(ApiRequest apiRequest)
{
await SaveRequest(apiRequest);
}
/// <summary>
/// Processes an inbound API request and persists the corresponding treatments
/// for the referenced patient.
/// </summary>
/// <param name="apiRequest">The <see cref="ApiRequest"/> to process.</param>
/// <returns>A task that represents the asynchronous save operation.</returns>
/// <exception cref="ApiRequestException">
/// Thrown when the patient number is missing or the request type is not supported.
/// </exception>
/// <remarks>
/// Supported message types are <c>OMP_O09</c>, <c>ORM_O01</c> and <c>RAS_O17</c>.
/// The method also enforces the unit's <c>AutoAdt</c> configuration when the
/// request does not originate from a panel.
/// </remarks>
public async Task SaveRequest(ApiRequest apiRequest)
{
if (string.IsNullOrEmpty(apiRequest.PatientNumber))
@@ -186,6 +296,17 @@ public class TreatmentService(
/*
* Return active treatments of patient PatientType active = NW, canceled= DC
*/
/// <summary>
/// Returns the active (non-canceled, in-range) treatments of the specified patient,
/// keeping the most recent active order per placer-order identifier.
/// </summary>
/// <param name="id">The <see cref="ObjectId"/> of the patient.</param>
/// <returns>
/// A task that represents the asynchronous operation. The task result contains
/// an <see cref="IEnumerable{T}"/> of nullable <see cref="PatientTreatment"/>
/// representing the active treatments. Treatments marked as <c>Dc</c> or
/// outside their validity window are excluded.
/// </returns>
public async Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
{
var treatments = await treatmentRepository.GetByPatientId(id);
@@ -200,6 +321,15 @@ public class TreatmentService(
return activeTreatments;
}
/// <summary>
/// Updates every occurrence of an identifier (e.g. a referenced <see cref="ObjectId"/>)
/// in stored treatments from <paramref name="oldId"/> to <paramref name="id"/>
/// and broadcasts the changes to subscribers.
/// </summary>
/// <param name="nameId">The name of the field that holds the identifier.</param>
/// <param name="id">The new <see cref="ObjectId"/> value.</param>
/// <param name="oldId">The previous <see cref="ObjectId"/> value being replaced.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
{
await treatmentRepository.UpdateManyObjectId(nameId, id, oldId);
@@ -208,7 +338,14 @@ public class TreatmentService(
foreach (var treat in treatments) _ = SendBroadcast(treat, OperationType.UpdateTreatment);
}
/// <summary>
/// Returns a paginated list of treatments based on the supplied filter.
/// </summary>
/// <param name="filter">The <see cref="PaginationFilter"/> containing page number and page size.</param>
/// <returns>
/// A task that represents the asynchronous operation. The task result contains
/// a <see cref="PaginationResponse{T}"/> of <see cref="PatientTreatment"/>.
/// </returns>
public async Task<PaginationResponse<PatientTreatment>> GetPaginatedTreatments(PaginationFilter filter)
{
var result = treatmentRepository.GetPaginatedTreatments(filter);
@@ -225,12 +362,29 @@ public class TreatmentService(
return new PaginationResponse<PatientTreatment>(dataList, filter.PageNumber, filter.PageSize, count);
}
/// <summary>
/// Returns the active treatments of a patient that match the specified placer order.
/// </summary>
/// <param name="patientId">The <see cref="ObjectId"/> of the patient.</param>
/// <param name="order">The placer-order identifier to filter by.</param>
/// <returns>
/// A task that represents the asynchronous operation. The task result contains
/// a <see cref="List{T}"/> of <see cref="PatientTreatment"/>.
/// </returns>
public async Task<List<PatientTreatment>> GetActiveTreatmentsByPatientIdAndOrder(ObjectId patientId, string order)
{
return await treatmentRepository.GetActiveTreatmentsByPatientIdAndOrder(patientId, order);
}
/// <summary>
/// Deletes a treatment by its identifier and writes an audit log entry
/// capturing the deleted state.
/// </summary>
/// <param name="id">The <see cref="ObjectId"/> of the treatment to delete.</param>
/// <returns>A task that represents the asynchronous delete operation.</returns>
/// <exception cref="ConflictException">
/// Thrown when the treatment does not exist.
/// </exception>
public async Task DeleteById(ObjectId id)
{
var oldTreatment = await treatmentRepository.GetById(id) ??
@@ -239,6 +393,20 @@ public class TreatmentService(
await treatmentRepository.DeleteAsync(id);
}
/// <summary>
/// Applies the calculated-observations and configuration-observations
/// mapping pipeline to a treatment.
/// </summary>
/// <param name="treatment">The <see cref="PatientTreatment"/> to map.</param>
/// <returns>
/// A task that represents the asynchronous operation. The task result contains
/// the mapped <see cref="PatientTreatment"/>, or <see langword="null"/> when
/// the treatment is ignored by the configuration mapping.
/// </returns>
/// <remarks>
/// If the configuration-observation service returns <see langword="null"/>,
/// the treatment is considered ignored and a debug entry is logged.
/// </remarks>
private async Task<PatientTreatment?> MapTreatment(PatientTreatment treatment)
{
var treatment2 = await calculatedObservationsService.Value.Map(treatment);
@@ -249,7 +417,17 @@ public class TreatmentService(
return treatment;
}
/// <summary>
/// Sends a broadcast message to all subscribers whose location list includes
/// the patient's point of care.
/// </summary>
/// <param name="treatment">The <see cref="PatientTreatment"/> to broadcast.</param>
/// <param name="operationType">The <see cref="OperationType"/> describing the change.</param>
/// <returns>A task that represents the asynchronous broadcast operation.</returns>
/// <remarks>
/// If the patient cannot be located or has no <c>PointOfCareId</c>, the
/// broadcast is skipped.
/// </remarks>
private async Task SendBroadcast(PatientTreatment treatment, OperationType operationType)
{
var patient = await patientService.FindById(treatment.PatientId);
@@ -264,6 +442,16 @@ public class TreatmentService(
await clientMessageService.SendAsync(subscriber.Id, operationType, treatment);
}
/// <summary>
/// Determines whether a treatment is currently active (not canceled and
/// within its start/end time window).
/// </summary>
/// <param name="treatment">The <see cref="PatientTreatment"/> to evaluate.</param>
/// <returns>
/// <see langword="true"/> if the treatment has a placer order, is not
/// discontinued (<see cref="OrderControlType.Dc"/>) and the current UTC
/// time lies within its validity window; otherwise, <see langword="false"/>.
/// </returns>
private static bool IsValidTreatment(PatientTreatment treatment)
{
if (treatment.PlacerOrder == null)
@@ -278,6 +466,18 @@ public class TreatmentService(
return treatment.OrderControl != OrderControlType.Dc;
}
/// <summary>
/// Returns the most recent active treatment from a group of treatments
/// sharing the same placer-order identifier.
/// </summary>
/// <param name="group">
/// A group of <see cref="PatientTreatment"/> instances sharing the same
/// placer-order entity identifier.
/// </param>
/// <returns>
/// The most recent treatment whose <see cref="OrderControlType"/> is
/// <c>Nw</c> or <c>Xo</c>, or <see langword="null"/> if none qualifies.
/// </returns>
private static PatientTreatment? GetMostRecentActiveTreatment(IGrouping<string?, PatientTreatment> group)
{
return group