Files
adas-core/adas-core.Application/Services/MasterListService.cs
T
2026-06-26 10:29:23 +02:00

1054 lines
53 KiB
C#

using adas_core.Application.Exceptions;
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.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.Masters;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Services;
/// <summary>
/// Provides a generic service implementation for managing <see cref="MasterList"/> entities of type <typeparamref name="T"/>.
/// Acts as the concrete implementation of the <see cref="IMasterListService{T}"/> contract for master list operations.
/// </summary>
/// <typeparam name="T">The type of master list entity managed by the service. Must derive from <see cref="MasterList"/> and expose a public parameterless constructor.</typeparam>
public class MasterListService<T> : IMasterListService<T> where T : MasterList, new()
{
private readonly Lazy<IAdmissionService> _admissionService;
private readonly string? _assetsDirectory;
private readonly ILocalAuditService _auditService;
private readonly Lazy<IClientMessageService> _clientMessageService;
private readonly Lazy<IDischargeService> _dischargeService;
private readonly Lazy<IDisplayService> _displayService;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<MasterListService<T>> _logger;
private readonly Lazy<IPatientService> _patientService;
private readonly IServiceProvider _serviceProvider;
private readonly ISubscribersService _subscribersService;
private readonly Lazy<IUnitService> _unitService;
public MasterListService(
ILogger<MasterListService<T>> logger,
IServiceProvider serviceProvider,
Lazy<IClientMessageService> clientMessageService,
ISubscribersService subscribersService,
Lazy<IUnitService> unitService,
Lazy<IDisplayService> displayService,
Lazy<IPatientService> patientService,
Lazy<IDischargeService> dischargeService,
Lazy<IAdmissionService> admissionService,
IOptions<ApiSettings> apiSettings,
IHttpContextAccessor httpContextAccessor,
ILocalAuditService auditService)
{
_logger = logger;
_serviceProvider = serviceProvider;
_clientMessageService = clientMessageService;
_subscribersService = subscribersService;
_unitService = unitService;
_displayService = displayService;
_patientService = patientService;
_dischargeService = dischargeService;
_admissionService = admissionService;
_httpContextAccessor = httpContextAccessor;
_auditService = auditService;
if (apiSettings.Value.PathToDisplayAssets != null)
_assetsDirectory = Path.Combine(apiSettings.Value.PathToDisplayAssets);
}
/// <summary>
/// Deletes a master list identified by the specified identifier. If the master list is not found, the operation is skipped and logged; if the master list is in use, a conflict exception is thrown.
/// </summary>
/// <param name="id">The identifier of the master list to delete.</param>
/// <exception cref="ConflictException">Thrown when the master list is currently in use and cannot be deleted.</exception>
public async Task DeleteMasterListById(ObjectId id)
{
var repository = GetRepository();
var masterList = await repository.FindById(id, LocaleEnum.Default);
if (masterList == null)
{
_logger.LogInformation("Error deleting masterList not found, id: {id} ", id);
return;
}
if (await IsInUseCount(masterList) > 0) throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
await repository.Delete(id);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, masterList, null);
await SendMasterListBroadcast(masterList, OperationType.DeleteMasterList);
}
// public async Task<bool> AddOptionToMasterList(ObjectId id, OptionList opt)
// {
// try
// {
// var repository = GetRepository();
// var result = await repository.AddOptionToMasterList(id, opt);
// if (result)
// {
// var masterList = await repository.FindById(id);
// await SendMasterListBroadcast(masterList, OperationType.UpdateMasterList);
// await SendMasterListItemBroadcast(masterList,opt, opt, OperationType.AddMasterListItem);
//
// }
//
// return result;
//
// }
// catch (Exception ex)
// {
// _logger.LogError("Error AddOptionToMasterList {name}. Exception: {ex}", typeof(T).Name, ex);
// return false;
// }
// }
/// <summary>
/// Adds a filter option to an existing master list, records the change in the audit log, and broadcasts the update to subscribed clients.
/// </summary>
/// <param name="id">The identifier of the master list to which the option will be added.</param>
/// <param name="opt">The filter option element to add to the master list.</param>
/// <returns>The resulting <see cref="OptionList"/> entry if the option is successfully added; <c>null</c> if the master list cannot be found, no result is produced, or an error is encountered during the operation.</returns>
public async Task<OptionList?> AddOptionToMasterList(ObjectId id, FilterOptionListElement opt)
{
try
{
var repository = GetRepository();
//TODO: LOCALE
var oldMasterList = await repository.FindById(id, opt.Locale);
var result = await repository.AddOptionToMasterList(id, opt);
if (result != null)
{
//TODO: LOCALE
var masterList = await repository.FindById(id);
if (masterList == null)
return null;
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldMasterList,
masterList);
await SendMasterListBroadcast(masterList, OperationType.UpdateMasterList);
await SendMasterListItemBroadcast(masterList, result, result, OperationType.AddMasterListItem);
}
return result;
}
catch (Exception ex)
{
_logger.LogError("Error AddOptionToMasterList {name}. Exception: {ex}", typeof(T).Name, ex);
return null;
}
}
/// <summary>
/// Updates an option within the master list for the specified locale, and when the update succeeds, creates an audit log entry, broadcasts the change, and propagates the update to the affected patient item list. Returns <c>null</c> if the update fails, the result is <c>null</c>, or an exception is thrown during processing.
/// </summary>
/// <param name="id">The identifier of the master list whose option will be updated.</param>
/// <param name="opt">The option to be applied to the master list.</param>
/// <param name="typeName">The type name used when propagating the update to the patient item list.</param>
/// <param name="locale">The locale used for the master list option update and retrieval of the updated entity for auditing.</param>
/// <returns>The updated <see cref="OptionList"/>, or <c>null</c> if the update failed, the result was <c>null</c>, or an exception occurred.</returns>
public async Task<OptionList?> UpdateMasterListOption(ObjectId id, OptionList opt, string typeName,
LocaleEnum locale)
{
try
{
var repository = GetRepository();
//TODO: LOCALE
var oldMasterList = await repository.FindById(id);
var oldOption = oldMasterList?.Options.FirstOrDefault(o => o.Id == opt.Id);
var result = await repository.UpdateMasterListOption(id, opt, locale);
if (result != null)
{
//TODO: LOCALE
var masterList = await repository.FindById(id, LocaleEnum.Default) ?? new T();
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldMasterList,
masterList);
await SendMasterListItemBroadcast(masterList, opt, oldOption, OperationType.UpdateMasterListItem);
await UpdatePatientItemList(id,
new UpdateOptionMasterListDto
{ OldOption = oldOption, UpdatedOption = opt, OptionId = opt.Id.ToString() }, typeName);
}
return result;
}
catch (Exception ex)
{
_logger.LogError("Error UpdateMasterListOption {name}. Exception: {ex}", typeof(T).Name, ex);
return null;
}
}
/// <summary>
/// Updates a master list option, performing audit logging, broadcasting the change, and updating the patient item list when successful. Returns <c>null</c> if the option cannot be found or an error occurs.
/// </summary>
/// <param name="id">The identifier of the master list containing the option to update.</param>
/// <param name="opt">The option containing the new values to apply.</param>
/// <param name="typeName">The name of the type used when updating the associated patient item list.</param>
/// <returns>The updated <see cref="OptionList"/> if the operation succeeds, or <c>null</c> if the master list is not found or an exception is thrown.</returns>
public async Task<OptionList?> UpdateMasterListOption(ObjectId id, OptionList opt, string typeName)
{
try
{
var repository = GetRepository();
//TODO: LOCALE
var oldMasterList = await repository.FindById(id);
var oldOption = oldMasterList?.Options.FirstOrDefault(o => o.Id == opt.Id);
var result = await repository.UpdateMasterListOption(id, opt);
if (result != null)
{
//TODO: LOCALE
var masterList = await repository.FindById(id, LocaleEnum.Default) ?? new T();
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldMasterList,
masterList);
await SendMasterListItemBroadcast(masterList, opt, oldOption, OperationType.UpdateMasterListItem);
await UpdatePatientItemList(id,
new UpdateOptionMasterListDto
{ OldOption = oldOption, UpdatedOption = opt, OptionId = opt.Id.ToString() }, typeName);
}
return result;
}
catch (Exception ex)
{
_logger.LogError("Error UpdateMasterListOption {name}. Exception: {ex}", typeof(T).Name, ex);
return null;
}
}
/// <summary>
/// Updates a full master list option, records the change in the audit log, notifies subscribers via broadcast, and propagates the update to the associated patient item list.
/// Returns <c>null</c> if the underlying repository update fails or an exception is encountered, in which case the error is logged.
/// Uses the default locale when retrieving the updated master list for audit purposes (locale handling is pending).
/// </summary>
/// <param name="id">The identifier of the master list that contains the option to update.</param>
/// <param name="opt">The option with the new values to apply to the master list.</param>
/// <param name="typeName">The name of the entity type used when updating the related patient item list.</param>
/// <returns>The updated <see cref="OptionList"/> when the operation succeeds; <c>null</c> when the update fails or an error is caught.</returns>
public async Task<OptionList?> UpdateFullMasterListOption(ObjectId id, OptionList opt, string typeName)
{
try
{
var repository = GetRepository();
//TODO: LOCALE
var oldMasterList = await repository.FindById(id);
var oldOption = oldMasterList?.Options.FirstOrDefault(o => o.Id == opt.Id);
var result = await repository.UpdateFullMasterListOption(id, opt);
if (result != null)
{
//TODO: LOCALE
var masterList = await repository.FindById(id, LocaleEnum.Default) ?? new T();
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldMasterList,
masterList);
await SendMasterListItemBroadcast(masterList, opt, oldOption, OperationType.UpdateMasterListItem);
await UpdatePatientItemList(id,
new UpdateOptionMasterListDto
{ OldOption = oldOption, UpdatedOption = opt, OptionId = opt.Id.ToString() }, typeName);
}
return result;
}
catch (Exception ex)
{
_logger.LogError("Error UpdateMasterListOption {name}. Exception: {ex}", typeof(T).Name, ex);
return null;
}
}
/// <summary>
/// Deletes an option from a master list by its identifier. On success, cascades the change to related patient item lists, creates an audit log entry, and broadcasts update and deletion events; returns <c>false</c> if the deletion fails, the master list or option is not found, or an error is logged.
/// </summary>
/// <param name="id">The identifier of the master list that contains the option.</param>
/// <param name="optId">The identifier of the option to remove from the master list.</param>
/// <param name="typeName">The name of the type used when cascading the deletion to related patient item lists.</param>
/// <returns>A task that resolves to <c>true</c> when the option is successfully deleted and the side-effects are applied; otherwise, <c>false</c>.</returns>
public async Task<bool> DeleteMasterListOption(ObjectId id, ObjectId optId, string typeName)
{
try
{
var repository = GetRepository();
var oldMasterList = await repository.FindById(id, LocaleEnum.Default);
var opt = oldMasterList?.Options.First(c => c.Id == optId);
var result = await repository.DeleteMasterListOption(id, optId);
if (result && opt != null)
{
var masterList = await repository.FindById(id, LocaleEnum.Default) ?? new T();
await DeletePatientItemList(id, opt, typeName);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldMasterList,
masterList);
await SendMasterListBroadcast(masterList, OperationType.UpdateMasterList);
await SendMasterListItemBroadcast(masterList, opt, opt, OperationType.DeleteMasterListItem);
return true;
}
return false;
}
catch (Exception ex)
{
_logger.LogError("Error DeleteMasterListOption {name}. Exception: {ex}", typeof(T).Name, ex);
return false;
}
}
/// <summary>
/// Updates the option details of a master list identified by the given identifier, records an audit log entry for the change, and broadcasts the update. Returns the updated details, or <c>null</c> if the update fails or an error occurs.
/// </summary>
/// <param name="id">The identifier of the master list whose option details will be updated.</param>
/// <param name="opt">The new option details to apply to the master list.</param>
/// <returns>A task that yields the updated <see cref="UpdateMasterListDetailsDto"/> on success, or <c>null</c> when the update fails or an exception is caught and logged.</returns>
public async Task<UpdateMasterListDetailsDto?> UpdateOptionDetailsToMasterList(ObjectId id,
UpdateMasterListDetailsDto opt)
{
try
{
var repository = GetRepository();
var oldMasterList = await repository.FindById(id);
var result = await repository.UpdateOptionDetailsToMasterList(id, opt);
if (result != null)
{
var masterList = await repository.FindById(id) ?? new T();
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldMasterList,
masterList);
await SendMasterListBroadcast(masterList, OperationType.UpdateMasterList);
}
return result;
}
catch (Exception ex)
{
_logger.LogError("Error UpdateMasterListOption {name}. Exception: {ex}", typeof(T).Name, ex);
return null;
}
}
/// <summary>
/// Updates the name of an existing master list identified by the specified id, records an audit log entry for the change, and broadcasts the update to subscribers.
/// Returns <c>false</c> if the update fails or an exception is encountered, in which case the error is logged.
/// </summary>
/// <param name="id">The identifier of the master list to rename.</param>
/// <param name="name">The new name to assign to the master list.</param>
/// <returns>A task that resolves to <c>true</c> if the master list name was updated successfully; otherwise, <c>false</c>.</returns>
public async Task<bool> UpdateMasterListName(ObjectId id, string name)
{
try
{
var repository = GetRepository();
var oldMasterList = await repository.FindById(id, LocaleEnum.Default);
var result = await repository.UpdateMasterListName(id, name);
if (result)
{
var masterList = await repository.FindById(id, LocaleEnum.Default) ?? new T();
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldMasterList,
masterList);
await SendMasterListBroadcast(masterList, OperationType.UpdateMasterList);
}
return result;
}
catch (Exception ex)
{
_logger.LogError("Error UpdateMasterListOption {name}. Exception: {ex}", typeof(T).Name, ex);
return false;
}
}
/// <summary>
/// Updates the description of a master list identified by the specified id, creating an audit log entry and broadcasting the change when the update succeeds.
/// </summary>
/// <param name="id">The identifier of the master list to update.</param>
/// <param name="name">The new description to apply to the master list.</param>
/// <returns>A task that resolves to <c>true</c> if the update succeeded; otherwise, <c>false</c> when the update fails or an exception is caught and logged.</returns>
public async Task<bool> UpdateMasterListDescription(ObjectId id, string name)
{
try
{
var repository = GetRepository();
var oldMasterList = await repository.FindById(id, LocaleEnum.Default);
var result = await repository.UpdateMasterListDescription(id, name);
if (result)
{
var masterList = await repository.FindById(id, LocaleEnum.Default) ?? new T();
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldMasterList,
masterList);
await SendMasterListBroadcast(masterList, OperationType.UpdateMasterList);
}
return result;
}
catch (Exception ex)
{
_logger.LogError("Error UpdateMasterListOption {name}. Exception: {ex}", typeof(T).Name, ex);
return false;
}
}
/// <summary>
/// Removes the specified option from a master list identified by its id, auditing the change and broadcasting the update and deleted item to subscribers.
/// </summary>
/// <param name="id">The identifier of the master list from which the option will be removed.</param>
/// <param name="oldOpt">The option to remove from the master list.</param>
/// <returns>A task that resolves to <c>true</c> if the option was successfully removed; <c>false</c> if the operation failed or an error occurred.</returns>
public async Task<bool> RemoveMasterListOption(ObjectId id, OptionList oldOpt)
{
try
{
var repository = GetRepository();
var oldMasterList = await repository.FindById(id, LocaleEnum.Default);
var result = await repository.RemoveMasterListOption(id, oldOpt);
if (result)
{
var masterList = await repository.FindById(id, LocaleEnum.Default) ?? new T();
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldMasterList,
masterList);
await SendMasterListBroadcast(masterList, OperationType.UpdateMasterList);
await SendMasterListItemBroadcast(masterList, oldOpt, oldOpt, OperationType.DeleteMasterListItem);
}
return result;
}
catch (Exception ex)
{
_logger.LogError("Error UpdateMasterListOption {name}. Exception: {ex}", typeof(T).Name, ex);
return false;
}
}
/// <summary>
/// Retrieves all master list entries of the generic type from the underlying repository.
/// If an exception occurs during retrieval, the error is logged and an empty collection is returned as a fallback.
/// </summary>
/// <returns>A task that represents the asynchronous operation. The task result contains all retrieved entries, or an empty collection if an error occurs.</returns>
public async Task<IEnumerable<T>> GetAllMasterList()
{
try
{
var repository = GetRepository();
return await repository.GetAll();
}
catch (Exception ex)
{
_logger.LogError("Error getting all {name}. Exception: {ex}", typeof(T).Name, ex);
return [];
}
}
/// <summary>
/// Retrieves all master list entries without applying any optional filters or parameters.
/// If an error occurs, the exception is logged and an empty collection is returned as a fallback.
/// </summary>
/// <returns>A task that represents the asynchronous operation, containing a collection of <see cref="MasterListDto"/> items, or an empty collection if an error is encountered.</returns>
public async Task<IEnumerable<MasterListDto>> GetAllMasterListWithoutOptions()
{
try
{
var repository = GetRepository();
return await repository.GetAllWithoutOptions();
}
catch (Exception ex)
{
_logger.LogError("Error getting all {name}. Exception: {ex}", typeof(T).Name, ex);
return [];
}
}
/// <summary>
/// Retrieves an <see cref="OptionList"/> item that matches the specified master identifier, option identifier, and locale.
/// Returns <c>null</c> and logs the error if the lookup fails.
/// </summary>
/// <param name="masterId">The identifier of the master item that owns the option.</param>
/// <param name="optionId">The identifier of the specific option to locate.</param>
/// <param name="locale">The locale used to resolve the localized option item.</param>
/// <returns>A <see cref="Task{T}"/> containing the matching <see cref="OptionList"/>, or <c>null</c> if no item is found or an error occurs.</returns>
public async Task<OptionList?> FindOptionItemById(ObjectId masterId, ObjectId optionId, LocaleEnum locale)
{
try
{
var repository = GetRepository();
return await repository.FindOptionItemById(masterId, optionId, locale);
}
catch (Exception ex)
{
_logger.LogError("Error getting all {name}. Exception: {ex}", typeof(T).Name, ex);
return null;
}
}
/// <summary>
/// Retrieves all master list entries, maps each one to a paginated DTO enriched with its in-use status, and returns the resulting collection.
/// Logs the error and returns an empty collection when the retrieval or mapping process fails.
/// </summary>
/// <param name="request">The pagination filter whose page size is applied when building each result entry.</param>
/// <returns>A task that yields an enumerable of <see cref="MasterListWithPaginatedOptionsDto"/> containing the mapped master lists, or an empty enumerable if an error occurs.</returns>
public async Task<IEnumerable<MasterListWithPaginatedOptionsDto>> GetAllMasterListWithPaginatedOptions(
PaginationFilter request)
{
try
{
var repository = GetRepository();
var masterLists = await repository.GetAll();
List<MasterListWithPaginatedOptionsDto> results = [];
foreach (var masterList in masterLists)
{
var units = await IsInUse(masterList);
var list = new MasterListWithPaginatedOptionsDto(masterList, request.PageSize, units);
results.Add(list);
}
return results;
}
catch (Exception ex)
{
_logger.LogError("Error getting all {name}. Exception: {ex}", typeof(T).Name, ex);
return [];
}
}
/// <summary>
/// Asynchronously retrieves the total count of all records in the master list for the current entity type.
/// Returns 0 if an error occurs while accessing the repository.
/// </summary>
/// <returns>A task that represents the asynchronous operation. The task result contains the total count of records, or 0 if an error was encountered.</returns>
public async Task<int> GetAllMasterListCount()
{
try
{
var repository = GetRepository();
return await repository.Count();
}
catch (Exception ex)
{
_logger.LogError("Error getting all {name}. Exception: {ex}", typeof(T).Name, ex);
return 0;
}
}
/// <summary>
/// Retrieves a master list by its unique identifier, optionally filtered by locale.
/// Logs and returns null if an error occurs during the lookup.
/// </summary>
/// <param name="id">The unique identifier of the master list to retrieve.</param>
/// <param name="locale">The optional locale used to localize the retrieved master list.</param>
/// <returns>The master list of type <typeparamref name="T"/> if found; otherwise, null.</returns>
public async Task<T?> GetMasterListById(ObjectId id, LocaleEnum? locale)
{
try
{
var repository = GetRepository();
return await repository.FindById(id, locale);
}
catch (Exception ex)
{
_logger.LogError("Error getting by id: {id}. Exception: {ex}", id, ex);
return null;
}
}
/// <summary>
/// Retrieves a master list by its identifier and returns it with paginated options, including whether the list is currently in use.
/// Returns null when the master list is not found or when an error occurs during retrieval.
/// </summary>
/// <param name="id">The unique identifier of the master list to retrieve.</param>
/// <param name="request">The pagination filter that determines the page size for the returned options.</param>
/// <returns>A task that yields a <see cref="MasterListWithPaginatedOptionsDto"/> when the master list is found; otherwise, null.</returns>
public async Task<MasterListWithPaginatedOptionsDto?> GetMasterListByIdWithPaginatedOptions(ObjectId id,
PaginationFilter request)
{
try
{
var repository = GetRepository();
var list = await repository.FindById(id);
if (list != null)
{
var units = await IsInUse(list);
return new MasterListWithPaginatedOptionsDto(list, request.PageSize, units);
}
return null;
}
catch (Exception ex)
{
_logger.LogError("Error getting by id: {id}. Exception: {ex}", id, ex);
return null;
}
}
/// <summary>
/// Retrieves the names of all options associated with a master list identified by the specified id using the default locale.
/// Returns an empty list if the master list is not found or if an error occurs while retrieving the data.
/// </summary>
/// <param name="id">The identifier of the master list whose option names are to be retrieved.</param>
/// <returns>A list of option names for the found master list, or an empty list if the list is not found or an error is encountered.</returns>
public async Task<List<string>> GetMasterListOptionsNamesById(ObjectId id)
{
try
{
var repository = GetRepository();
var list = await repository.FindById(id, LocaleEnum.Default);
var nameList = new List<string>();
list?.Options.ForEach(o => nameList.Add(o.Name));
return nameList;
}
catch (Exception ex)
{
_logger.LogError("Error getting by id: {id}. Exception: {ex}", id, ex);
return [];
}
}
/// <summary>
/// Retrieves a master list of options filtered by the specified identifier and an optional text search term.
/// Returns an empty list if an error occurs during retrieval.
/// </summary>
/// <param name="id">The identifier used to locate the master list in the repository.</param>
/// <param name="textSearch">The optional text search term used to filter the results; may be null.</param>
/// <returns>A task that represents the asynchronous operation, containing the list of <see cref="OptionList"/> entries that match the specified identifier and text search criteria.</returns>
public async Task<List<OptionList>> GetMasterListByIdAndTextSearch(ObjectId id, string? textSearch)
{
try
{
var repository = GetRepository();
return await repository.GetMasterListByIdAndTextSearchContaining(id, textSearch);
}
catch (Exception ex)
{
_logger.LogError("Error getting by id: {id}. Exception: {ex}", id, ex);
return [];
}
}
/// <summary>
/// Retrieves a master list of <see cref="OptionList"/> items from the repository using the specified identifier and search filter options.
/// </summary>
/// <param name="id">The identifier used to locate the master list.</param>
/// <param name="filterOption">The filter options applied to narrow the search within the master list.</param>
/// <returns>A task that represents the asynchronous operation, containing a list of <see cref="OptionList"/> items matching the given criteria.</returns>
public async Task<List<OptionList>> GetMasterListByIdAndSearchOptions(ObjectId id,
FilterOptionListElement filterOption)
{
var repository = GetRepository();
return await repository.GetMasterListByIdAndSearchOptions(id, filterOption);
}
/// <summary>
/// Retrieves a master list entry by its name. Returns the matching item, or null if no entry is found or if an error occurs while querying the repository.
/// </summary>
/// <param name="name">The name of the master list entry to look up.</param>
/// <returns>A task that resolves to the matching item of type T, or null when no item is found or the lookup fails.</returns>
public async Task<T?> GetMasterListByName(string name)
{
try
{
var repository = GetRepository();
var result = await repository.FindByName(name);
return result;
}
catch (Exception ex)
{
_logger.LogError("Error getting by name: {name}. Exception: {ex}", name, ex);
return null;
}
}
/// <summary>
/// Retrieves a paginated master list based on the specified pagination filter, applying skip and limit operations according to the page number and page size.
/// </summary>
/// <param name="filter">The pagination filter containing the page number and page size used to determine the subset of records to return.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains a <see cref="PaginationResponse{T}"/> with the paginated data, the current page number, the page size, and the total document count.</returns>
public async Task<PaginationResponse<T>> GetPaginatedMasterList(PaginationFilter filter)
{
var repository = GetRepository();
var result = repository.GetPaginatedMasterList(filter);
var count = await result.CountDocumentsAsync();
var data = await result.Skip((filter.PageNumber - 1) * filter.PageSize)
.Limit(filter.PageSize)
.ToCursorAsync();
var dataList = await data.ToListAsync();
return new PaginationResponse<T>(dataList, filter.PageNumber, filter.PageSize, count);
}
/// <summary>
/// Retrieves a paginated collection of master list items, each enriched with a paginated set of associated options and a usage indicator.
/// Applies the supplied <paramref name="listFilter"/> to paginate the master lists and the <paramref name="optionFilter"/> to size the embedded options within each list DTO.
/// </summary>
/// <param name="listFilter">Pagination parameters (page number and page size) used to slice the master list result set.</param>
/// <param name="optionFilter">Pagination parameters whose page size is applied to the options associated with each returned master list item.</param>
/// <returns>A <see cref="Task{TResult}"/> containing a <see cref="PaginationResponse{T}"/> of <see cref="MasterListWithPaginatedOptionsDto"/> with the requested page of master lists, their paginated options, the current page metadata, and the total document count.</returns>
public async Task<PaginationResponse<MasterListWithPaginatedOptionsDto>> GetPaginatedMasterListWithPaginatedOptions(
PaginationFilter listFilter, PaginationFilter optionFilter)
{
var repository = GetRepository();
var result = repository.GetPaginatedMasterList(listFilter);
var count = await result.CountDocumentsAsync();
var data = await result.Skip((listFilter.PageNumber - 1) * listFilter.PageSize)
.Limit(listFilter.PageSize)
.ToCursorAsync();
var dataList = await data.ToListAsync();
List<MasterListWithPaginatedOptionsDto> results = [];
foreach (var list in dataList)
{
var units = await IsInUse(list);
var listDto = new MasterListWithPaginatedOptionsDto(list, optionFilter.PageSize, units);
results.Add(listDto);
}
return new PaginationResponse<MasterListWithPaginatedOptionsDto>(results, listFilter.PageNumber,
listFilter.PageSize, count);
}
/// <summary>
/// Retrieves a paginated subset of options for the specified list, applying the page number and page size from the filter after fetching the full result set from the repository.
/// </summary>
/// <param name="filter">The pagination filter that defines the page number and page size to apply to the results.</param>
/// <param name="listId">The identifier of the list whose options are being retrieved.</param>
/// <returns>A <see cref="PaginationResponse{OptionList}"/> containing the requested page of options, the current page metadata, and the total count of available options.</returns>
public async Task<PaginationResponse<OptionList>> GetPaginatedOptions(PaginationFilter filter, ObjectId listId)
{
var repository = GetRepository();
var result = await repository.GetPaginatedOptions(filter, listId);
var count = result.Count;
var data = result.Skip((filter.PageNumber - 1) * filter.PageSize)
.Take(filter.PageSize)
.ToList();
return new PaginationResponse<OptionList>(data, filter.PageNumber, filter.PageSize, count);
}
/// <summary>
/// Inserts a new master list item, broadcasts the change to subscribers, and records an audit log entry for the operation.
/// </summary>
/// <param name="item">The master list entity to insert.</param>
/// <returns>The inserted entity retrieved from the repository, or <c>null</c> if the operation fails.</returns>
public async Task<T?> InsertMasterList(T item)
{
try
{
var repository = GetRepository();
await repository.InsertOneAsync(item);
await SendMasterListBroadcast(item, OperationType.NewMasterList);
var result = await repository.FindById(item.Id);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, result);
return result;
}
catch (Exception ex)
{
_logger.LogError("Error inserting {name}: {item}. Exception: {ex}", typeof(T).Name, item, ex);
return null;
}
}
/// <summary>
/// Updates an existing item in the master list, broadcasts the update operation, and creates an audit log entry comparing the old and new state.
/// Returns <c>null</c> if the update fails, logging the error internally.
/// </summary>
/// <param name="item">The item to update in the master list.</param>
/// <returns>The updated item retrieved from the repository, or <c>null</c> if an error occurred during the update.</returns>
public async Task<T?> UpdateMasterList(T item)
{
try
{
var repository = GetRepository();
var oldItem = repository.FindById(item.Id, LocaleEnum.Default);
await repository.Update(item);
await SendMasterListBroadcast(item, OperationType.UpdateMasterList);
var result = await repository.FindById(item.Id, LocaleEnum.Default);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldItem, result);
return result;
}
catch (Exception ex)
{
_logger.LogError("Error updating {name}: {item}. Exception: {ex}", typeof(T).Name, item, ex);
return null;
}
}
/// <summary>
/// Retrieves the identifier of a master list associated with a given unit, based on the relationship between two master list types.
/// Looks up the unit that contains the first master list reference (<paramref name="masterListType1"/>) and returns the identifier of the secondary associated list defined by <paramref name="masterListType2"/>.
/// Returns <c>null</c> when no unit is found for the given list, or when <paramref name="masterListType2"/> does not map to a known associated list.
/// </summary>
/// <param name="id">The identifier of the master list used to locate the associated unit.</param>
/// <param name="masterListType1">The master list type used to find the unit (e.g., the primary list reference on the unit).</param>
/// <param name="masterListType2">The master list type that determines which associated list identifier is returned from the resolved unit.</param>
/// <returns>A task that yields the associated <see cref="ObjectId"/> when a matching list exists, or <c>null</c> when no unit is found or the type is not mapped.</returns>
public async Task<ObjectId?> GetAssociatedList(ObjectId id, MasterListType masterListType1,
MasterListType masterListType2)
{
var repository = GetRepository();
await repository.FindById(id, LocaleEnum.Default);
var units = await _unitService.Value.FindUnitsByMasterListId(id, masterListType1);
var unitArray = units as Unit[] ?? (units ?? []).ToArray();
if (!unitArray.Any()) return null;
var unit = unitArray.First();
switch (masterListType2)
{
case MasterListType.AltableOptionList: return unit.AltableOptionListId;
case MasterListType.AllergyList: return unit.AllergyListId;
case MasterListType.DestinationList: return unit.DestinationListId;
case MasterListType.DiagnosisList: return unit.DiagnosisListId;
case MasterListType.DischargeStatusList: return unit.DischargeStatusListId;
case MasterListType.DoctorList: return unit.DoctorListId;
case MasterListType.DoctorTypeList: return unit.DoctorTypeListId;
case MasterListType.InternalDestinationList: return unit.InternalDestinationListId;
case MasterListType.InsulationList: return unit.InsulationListId;
case MasterListType.LanguageBarrierList: return unit.LanguageBarrierListId;
case MasterListType.PassiveSittingList: return unit.PassiveSittingListId;
case MasterListType.GenericList: return unit.GenericListId;
case MasterListType.MobilityOptionList: return unit.MobilityOptionListId;
case MasterListType.OriginList: return unit.OriginListId;
case MasterListType.PatientStatusList: return unit.PatientStatusListId;
case MasterListType.ProcedureList: return unit.ProcedureListId;
case MasterListType.TestList: return unit.TestListId;
case MasterListType.ServiceList: return unit.ServiceListId;
case MasterListType.TherapeuticCeilingList: return unit.TherapeuticCeilingListId;
case MasterListType.TreatmentList: return unit.TreatmentListId;
case MasterListType.VisitOptionList: return unit.VisitOptionListId;
case MasterListType.AccessControlList: return unit.AccessControlListId;
}
return null;
}
/// <summary>
/// Retrieves the names of all options associated with the list identified by the specified identifier, using the default locale. Returns an empty list if no list is found for the given identifier.
/// </summary>
/// <param name="id">The identifier of the list whose options should be retrieved.</param>
/// <returns>A list of option names belonging to the matching list, or an empty list if the list is not found.</returns>
public async Task<List<string>> GetOptionsOfList(ObjectId id)
{
var repository = GetRepository();
var list = await repository.FindById(id, LocaleEnum.Default);
List<string> options = [];
if (list == null) return options;
foreach (var item in list.Options) options.Add(item.Name);
return options;
}
/// <summary>
/// Resolves and returns the master list repository instance for the current entity type from the dependency injection service provider.
/// </summary>
/// <returns>The <see cref="IMasterListRepository{T}"/> instance retrieved from the configured <see cref="IServiceProvider"/>.</returns>
private IMasterListRepository<T> GetRepository()
{
return _serviceProvider.GetRequiredService<IMasterListRepository<T>>();
}
/// <summary>
/// Propagates an update of a master list option to the patient, discharge, and admission services for all units that reference the given master list identifier.
/// </summary>
/// <param name="id">The identifier of the master list whose dependent units and related records need to be updated.</param>
/// <param name="opt">The update payload describing the master list change to apply to the related patient records.</param>
/// <param name="typeName">The name of the master list type used to scope the update across the affected services.</param>
private async Task UpdatePatientItemList(ObjectId id, UpdateOptionMasterListDto opt, string typeName)
{
// Necesito saber que unidades tienen el id de lista que estamos modificando
var unitList = await _unitService.Value.FindUnitsByMasterListId(id);
// Que pacientes dentro de esa/s unidades tienen el valor antiguo de la lista que estamos modificando
var units = unitList as Unit[] ?? unitList.ToArray();
await _patientService.Value.UpdatePatientMasterListItemChange(opt, units, typeName);
await _dischargeService.Value.UpdatePatientMasterListItemChange(opt, units, typeName);
await _admissionService.Value.UpdatePatientMasterListItemChange(opt, units, typeName);
}
/// <summary>
/// Deletes a master list item across all patients, discharges, and admissions that belong to units referencing the specified master list identifier.
/// </summary>
/// <param name="id">The master list identifier used to locate the units that reference it.</param>
/// <param name="opt">The option list containing the item to be removed from the affected records.</param>
/// <param name="typeName">The type name of the master list item to delete.</param>
private async Task DeletePatientItemList(ObjectId id, OptionList opt, string typeName)
{
// Necesito saber que unidades tienen el id de lista que estamos modificando
var unitList = await _unitService.Value.FindUnitsByMasterListId(id);
// Que pacientes dentro de esa/s unidades tienen el valor antiguo de la lista que estamos modificando
var units = unitList as Unit[] ?? unitList.ToArray();
await _patientService.Value.DeletePatientMasterListItem(opt, units, typeName);
await _dischargeService.Value.DeletePatientMasterListItem(opt, units, typeName);
await _admissionService.Value.DeletePatientMasterListItem(opt, units, typeName);
}
/// <summary>
/// Asynchronously saves the specified API request by offloading the save operation to a background task.
/// </summary>
/// <param name="apiRequest">The API request to be saved.</param>
public Task SaveRequestAsync(ApiRequest apiRequest)
{
return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest); }));
}
/// <summary>
/// Saves the specified API request.
/// </summary>
/// <param name="apiRequest">The API request to save.</param>
public Task SaveRequest(ApiRequest apiRequest)
{
return Task.CompletedTask;
}
/// <summary>
/// Broadcasts a master list change to all subscribers linked to the displays of the units associated with the given master list.
/// Returns early if the master list type cannot be parsed as a <see cref="MasterListType"/> or if no related units are found, and logs any exception encountered while sending the notifications.
/// </summary>
/// <param name="masterList">The master list entity triggering the broadcast; its runtime type name is used to determine the master list category.</param>
/// <param name="operation">The operation performed on the master list, which is sent to each subscriber along with the localized master list options.</param>
private async Task SendMasterListBroadcast(T masterList, OperationType operation)
{
try
{
var type = masterList.GetType();
var masterListTypeName = type.Name;
if (!Enum.TryParse<MasterListType>(masterListTypeName, out var masterListType))
return;
var units = await _unitService.Value.FindUnitsByMasterListId(masterList.Id, masterListType);
if (units == null)
return;
List<ObjectId> diplayIds = [];
foreach (var unit in units)
{
var displayList = await _displayService.Value.GetByUnitId(unit.Id);
displayList.ForEach(d => diplayIds.Add(d.Id));
}
var subscribers = _subscribersService.GetSubscribers()
.Where(s => s.DisplayId.HasValue && diplayIds.Contains(s.DisplayId.Value)).ToList();
foreach (var subscriber in subscribers)
_ = _clientMessageService.Value.SendAsync(subscriber.Id, operation,
masterList.ReturnMasterListOptionsInLocaleIfExist(subscriber.Locale));
}
catch (Exception ex)
{
_logger.LogError("Exception sending masterList broadcast. Operation type: {op}. Exception: {ex}",
operation.ToString(), ex);
}
}
/// <summary>
/// Sends a broadcast notification to all subscribers linked to a master list when an option list item is added, updated, or deleted. The message payload is tailored per operation: add includes the new item, delete includes the deleted item, and update includes both. Returns early if the master list type cannot be parsed from the type name or if no related units are found.
/// </summary>
/// <param name="masterList">The master list entity whose type and identifier are used to locate related units and subscribers.</param>
/// <param name="updatedItem">The new or modified option list item, included in add and update operations.</param>
/// <param name="oldItem">The previous option list item, included in delete and update operations.</param>
/// <param name="operation">The operation type that determines the structure of the broadcast message sent to subscribers.</param>
private async Task SendMasterListItemBroadcast(T masterList, OptionList? updatedItem, OptionList? oldItem,
OperationType operation)
{
try
{
var type = masterList.GetType();
var masterListTypeName = type.Name;
if (!Enum.TryParse<MasterListType>(masterListTypeName, out var masterListType))
return;
var units = await _unitService.Value.FindUnitsByMasterListId(masterList.Id, masterListType);
if (units == null)
return;
List<ObjectId> diplayIds = [];
foreach (var unit in units)
{
var displayList = await _displayService.Value.GetByUnitId(unit.Id);
displayList.ForEach(d => diplayIds.Add(d.Id));
}
var subscribers = _subscribersService.GetSubscribers()
.Where(s => s.DisplayId.HasValue && diplayIds.Contains(s.DisplayId.Value)).ToList();
object message;
switch (operation)
{
case OperationType.AddMasterListItem:
message = new
{
newItem = updatedItem,
listType = masterListTypeName,
name = masterList.Name
};
break;
case OperationType.DeleteMasterListItem:
message = new
{
deletedItem = oldItem,
listType = masterListTypeName,
name = masterList.Name
};
break;
default:
message = new
{
updatedItem,
oldItem,
listType = masterListTypeName,
name = masterList.Name
};
break;
}
foreach (var subscriber in subscribers)
_ = _clientMessageService.Value.SendAsync(subscriber.Id, operation, message);
}
catch (Exception ex)
{
_logger.LogError("Exception sending masterList broadcast. Operation type: {op}. Exception: {ex}",
operation.ToString(), ex);
}
}
/// <summary>
/// Retrieves the units that are associated with the specified master list and maps them to a collection of <see cref="UnitInfoDto"/> objects, returning an empty list when no units are found.
/// </summary>
/// <param name="list">The master list whose associated units should be retrieved. Its identifier and list type are used to look up the related units.</param>
/// <returns>A task that represents the asynchronous operation, containing a list of <see cref="UnitInfoDto"/> instances for the units linked to the specified list, or an empty list if none exist.</returns>
private async Task<List<UnitInfoDto>> IsInUse(T list)
{
var units = await _unitService.Value.FindUnitsByMasterListId(list.Id, list.ListType);
List<UnitInfoDto> unitInfoDtos = [];
var unitArray = units as Unit[] ?? (units ?? []).ToArray();
if (units != null && unitArray.Any())
foreach (var unit in unitArray)
{
var unitInfoDto = new UnitInfoDto(unit);
unitInfoDtos.Add(unitInfoDto);
}
return unitInfoDtos;
}
/// <summary>
/// Asynchronously retrieves the count of units associated with the specified master list, indicating how many records are currently in use.
/// </summary>
/// <param name="list">The master list entity whose associated unit count is being queried.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the number of units referencing the master list.</returns>
private async Task<long> IsInUseCount(T list)
{
return await _unitService.Value.CountUnitsByMasterListId(list.Id, list.ListType);
}
}