Files
adas-core/adas-core.Application/Services/DischargeService.cs
T

553 lines
30 KiB
C#

using System.Reflection;
using adas_core.Application.Exceptions;
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.Interfaces;
using adas_core.Application.Subscriptions;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.Masters;
using adas_core.Domain.Models.MongoModels;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
namespace adas_core.Application.Services;
/// <summary>
/// Provides the concrete implementation of the <see cref="IDischargeService"/> contract,
/// encapsulating the business logic required to process discharge operations.
/// </summary>
/// <remarks>
/// This service acts as the default in-memory or infrastructure-backed implementation
/// of the discharge operations defined by <see cref="IDischargeService"/>.
/// </remarks>
/// <!-- aidoc:v1 sig=61bd286 -->
public class DischargeService : IDischargeService
{
private readonly ILocalAuditService _auditService;
private readonly IClientMessageService _clientMessageService;
private readonly IDischargeRepository _dischargeRepository;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<DischargeService> _logger;
private readonly IMasterListServiceFactory _masterListServiceFactory;
private readonly Lazy<IPatientService> _patientServiceLazy;
private readonly IPointOfCareService _pointOfCareService;
private readonly ISubscribersService _subscribersService;
private readonly IUnitService _unitService;
/// <summary>
/// Initializes a new instance of the <see cref="DischargeService"/> class, which coordinates discharge-related operations by injecting its required collaborators into private fields for logging, persistence, patient access, messaging, auditing, and unit/master list resolution.
/// </summary>
/// <param name="logger">The <see cref="ILogger{TCategoryName}"/> used to record diagnostic information for the <see cref="DischargeService"/>.</param>
/// <param name="subscribersService">The <see cref="ISubscribersService"/> used to manage subscribers tied to discharge events.</param>
/// <param name="dischargeRepository">The <see cref="IDischargeRepository"/> used to persist and retrieve discharge records.</param>
/// <param name="patientServiceLazy">The <see cref="Lazy{T}"/> wrapping <see cref="IPatientService"/> to defer patient service resolution.</param>
/// <param name="clientMessageService">The <see cref="IClientMessageService"/> used to send client-facing messages.</param>
/// <param name="pointOfCareService">The <see cref="IPointOfCareService"/> used to interact with point-of-care operations.</param>
/// <param name="httpContextAccessor">The <see cref="IHttpContextAccessor"/> used to access the current HTTP context.</param>
/// <param name="auditService">The <see cref="ILocalAuditService"/> used to record local audit entries.</param>
/// <param name="unitService">The <see cref="IUnitService"/> used to look up unit-related information.</param>
/// <param name="masterListServiceFactory">The <see cref="IMasterListServiceFactory"/> used to create master list services on demand.</param>
/// <!-- aidoc:v1 sig=5a98484 body=4f2b947 -->
public DischargeService(ILogger<DischargeService> logger,
ISubscribersService subscribersService,
IDischargeRepository dischargeRepository,
Lazy<IPatientService> patientServiceLazy,
IClientMessageService clientMessageService,
IPointOfCareService pointOfCareService,
IHttpContextAccessor httpContextAccessor,
ILocalAuditService auditService,
IUnitService unitService,
IMasterListServiceFactory masterListServiceFactory)
{
_logger = logger;
_subscribersService = subscribersService;
_dischargeRepository = dischargeRepository;
_patientServiceLazy = patientServiceLazy;
_clientMessageService = clientMessageService;
_pointOfCareService = pointOfCareService;
_pointOfCareService = pointOfCareService;
_httpContextAccessor = httpContextAccessor;
_auditService = auditService;
_unitService = unitService;
_masterListServiceFactory = masterListServiceFactory;
}
/// <summary>
/// Deletes a discharge record asynchronously. The method is intended to validate that the patient
/// can be discharged (requiring both medical and administrative discharge values and an allowed
/// discharge status), and otherwise falls back to deleting the discharge by its identifier.
/// </summary>
/// <param name="discharge">The discharge entity to be deleted.</param>
/// <!-- aidoc-review:v1 severity=high kind=mentions_removed_behavior
/// "The summary describes validation of MedicalDischarge, AdminDischarge, and discharge status, but the entire validation block is commented out; the method only calls DeleteDischargeByIdAsync." -->
/// <!-- aidoc-review:v1 severity=high kind=stale_summary
/// "The documented behavior of validating and 'falling back' to deletion by id does not match the actual code, which unconditionally deletes by id." -->
public async Task DeleteDischargeAsync(Discharge discharge)
{
//var patient = await _patientServiceLazy.Value.FindById(discharge.PatientId);
//if (!discharge.MedicalDischarge.HasValue || !discharge.AdminDischarge.HasValue
// // || DischargeStatusType.NoAltable.ToString().Equals(patient?.DischargeStatus?.OptionType)
// )
//{
// _logger.LogError("The patient cannot be discharged");
// return;
//}
await DeleteDischargeByIdAsync(discharge.Id);
}
/// <summary>
/// Deletes a discharge record by its identifier. If the discharge is not found, an error is logged and the operation is skipped; otherwise the record is removed, an audit log entry is created, and a delete broadcast is sent.
/// </summary>
/// <param name="dischargeId">The unique identifier of the discharge to delete.</param>
/// <!-- aidoc:v1 sig=0e0285f body=b646cb5 -->
public async Task DeleteDischargeByIdAsync(ObjectId dischargeId)
{
try
{
var dischargeAux = await _dischargeRepository.FindById(dischargeId);
if (dischargeAux == null)
{
_logger.LogError("Error Discharge not found, id: {discharge} ", dischargeId);
return;
}
await _dischargeRepository.Delete(dischargeId);
_logger.LogInformation("Discharge id: {dischargeId} DELETED ", dischargeId);
//var patient = await _patientServiceLazy.Value.FindById(dischargeAux.PatientId);
// if (patient != null)
// await _patientServiceLazy.Value.ArchivePatient(patient);
// else
// _logger.LogError("Patient not found on Discharge id: {discharge} ", dischargeId);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, dischargeAux, null);
SendDischargeBroadcast(dischargeAux, OperationType.DeleteDischarge);
}
catch (Exception ex)
{
_logger.LogError("Exception deleting discharge id:{admission} . Exception: {ex}", dischargeId, ex);
}
}
/// <summary>
/// Asynchronously counts the number of discharge records associated with the specified unit identifier.
/// </summary>
/// <param name="unitId">The identifier of the unit whose discharge records will be counted.</param>
/// <returns>A task representing the asynchronous operation, containing the total number of discharges for the given unit.</returns>
/// <!-- aidoc:v1 sig=da93389 body=a6241d6 -->
public async Task<long> CountDischargesByUnitId(ObjectId unitId)
{
return await _dischargeRepository.CountByUnitId(unitId);
}
/// <summary>
/// Retrieves a discharge by its identifier, enriching the result with patient location details
/// when an associated point of care is available.
/// </summary>
/// <param name="dischargeId">The unique identifier of the discharge to retrieve.</param>
/// <returns>The matching <see cref="Discharge"/>, populated with <see cref="PatientLocation"/>
/// information if a point of care is linked; otherwise the discharge as stored.</returns>
/// <exception cref="NotFoundException">Thrown when no discharge is found for the specified
/// <paramref name="dischargeId"/>.</exception>
/// <!-- aidoc:v1 sig=12c2e8b body=3015bf8 -->
public async Task<Discharge?> GetDischargeByIdAsync(ObjectId dischargeId)
{
var result = await _dischargeRepository.FindById(dischargeId) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
if (result.PointOfCareId == null)
return result;
var poc = await _pointOfCareService.GetInfo(result.PointOfCareId.Value, null, false);
result.PatientLocation = new PatientLocation(poc?.UnitName, poc?.Bed, poc?.Room);
return result;
}
/// <summary>
/// Asynchronously retrieves all discharge records from the repository.
/// </summary>
/// <returns>A task that represents the asynchronous operation, containing an enumerable collection of <see cref="Discharge"/> records.</returns>
/// <!-- aidoc:v1 sig=f526f77 body=05958d1 -->
public async Task<IEnumerable<Discharge>> GetDischargesAsync()
{
return await _dischargeRepository.FindAll();
}
/// <summary>
/// Retrieves the discharge record associated with the specified patient identifier, enriching it with patient location details when a point of care is linked.
/// </summary>
/// <param name="patientId">The unique identifier of the patient whose discharge record should be retrieved.</param>
/// <returns>A <see cref="Discharge"/> object populated with patient location information when a point of care is associated, or <c>null</c> if no discharge is found or an error occurs.</returns>
/// <!-- aidoc:v1 sig=9c55b11 body=305bfa7 -->
public async Task<Discharge?> GetDischargeByPatientId(ObjectId patientId)
{
try
{
var discharge = await _dischargeRepository.GetByPatientId(patientId);
if (discharge == null)
_logger.LogError("Discharge not found by patient Id {id}", patientId);
if (discharge is { PointOfCareId: not null })
{
var poc = await _pointOfCareService.GetInfo(discharge.PointOfCareId.Value, null, false);
discharge.PatientLocation = new PatientLocation(poc?.UnitName, poc?.Bed, poc?.Room);
}
return discharge;
}
catch (Exception ex)
{
_logger.LogError("Exception getting discharge by patient id: {id} . Exception: {ex}", patientId, ex);
return null;
}
}
/// <summary>
/// Inserts a new discharge record into the repository, verifies its persistence, logs the operation, and broadcasts a notification.
/// Throws a <see cref="ConflictException"/> if the discharge cannot be retrieved after insertion, indicating a creation failure.
/// </summary>
/// <param name="discharge">The discharge entity to be inserted.</param>
/// <returns>The persisted <see cref="Discharge"/> entity retrieved from the repository after insertion.</returns>
/// <exception cref="ConflictException">Thrown when the inserted discharge cannot be found by its identifier, indicating that the creation failed.</exception>
/// <!-- aidoc:v1 sig=5b13f28 body=682f5de -->
public async Task<Discharge?> InsertDischarge(Discharge discharge)
{
await _dischargeRepository.InsertOneAsync(discharge);
var dischargeAux = await _dischargeRepository.FindById(discharge.Id) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);
_logger.LogInformation("Discharge: {discharge} INSERTED", discharge);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, dischargeAux);
SendDischargeBroadcast(discharge, OperationType.NewDischarge);
return dischargeAux;
}
/// <summary>
/// Asynchronously retrieves the <see cref="Discharge"/> associated with the specified patient location.
/// Returns <c>null</c> if an exception occurs while accessing the underlying repository.
/// </summary>
/// <param name="location">The patient location used to look up the associated discharge record.</param>
/// <returns>A <see cref="Discharge"/> if one is found for the given location; otherwise, <c>null</c> when an error occurs.</returns>
/// <!-- aidoc:v1 sig=d6f8db9 body=ac06f0e -->
public async Task<Discharge?> GetDischargeByLocation(PatientLocation location)
{
try
{
return await _dischargeRepository.GetDischargeByLocation(location);
}
catch (Exception e)
{
_logger.LogError($"Unable to get discharge by location on service Exception: {e}");
return null;
}
}
/// <summary>
/// Retrieves a discharge record by the specified point of care identifier and enriches it with patient location details.
/// If the discharge is not found, an information message is logged; when a related point of care is available, its unit, bed, and room are mapped to the discharge's <see cref="PatientLocation"/>.
/// On failure, the error is logged and <c>null</c> is returned.
/// </summary>
/// <param name="poc">The point of care identifier used to look up the discharge record.</param>
/// <returns>A <see cref="Discharge"/> with the patient location populated when available, or <c>null</c> if the discharge is not found or an error occurs.</returns>
/// <!-- aidoc:v1 sig=add61b9 body=16738ca -->
public async Task<Discharge?> GetDischargeByPointOfCareId(ObjectId poc)
{
try
{
var discharge = await _dischargeRepository.GetDischargeByPointOfCareId(poc);
if (discharge == null)
_logger.LogInformation("Discharge not found by PointOfCareId {id}", poc);
if (discharge is { PointOfCareId: not null })
{
var pocInfo = await _pointOfCareService.GetInfo(discharge.PointOfCareId.Value, null, false);
discharge.PatientLocation = new PatientLocation(pocInfo?.UnitName, pocInfo?.Bed, pocInfo?.Room);
}
return discharge;
}
catch (Exception e)
{
_logger.LogError($"Unable to get discharge by location on service Exception: {e}");
return null;
}
}
/// <summary>
/// Retrieves a discharge record by its point of care (location) identifier and applies locale-specific data using the associated unit.
/// Returns the discharge as-is if no associated unit is found, or <c>null</c> if no discharge exists for the given location.
/// </summary>
/// <param name="location">The ObjectId identifying the point of care (location) whose discharge record should be retrieved.</param>
/// <param name="dataLocale">The locale used to localize the discharge data when the associated unit is found.</param>
/// <returns>A <see cref="Task{Discharge}"/> containing the localized discharge, the unmodified discharge when its unit cannot be found, or <c>null</c> when no discharge exists for the specified location.</returns>
/// <!-- aidoc:v1 sig=4f9546e body=52d6c63 -->
public async Task<Discharge?> GetDischargeByPointOfCareIdWithLocale(ObjectId location, LocaleEnum dataLocale)
{
var discharge = await GetDischargeByPointOfCareId(location);
if (discharge == null) return null;
var unit = await _unitService.FindById(discharge.UnitId);
if (unit == null) return discharge;
var dischargeWithLocale = await GetDischargeWithLocale(unit, discharge, dataLocale);
return dischargeWithLocale;
}
/// <summary>
/// Updates an existing discharge record, auditing the change and broadcasting the update. Throws a conflict exception if the discharge cannot be found by its identifier.
/// </summary>
/// <param name="discharge">The discharge entity to be updated.</param>
/// <exception cref="ConflictException">Thrown when no discharge is found with the specified identifier, preventing the update from proceeding.</exception>
/// <!-- aidoc:v1 sig=93dfb9f body=eba9c68 -->
public async Task UpdateDischargeAsync(Discharge discharge)
{
var oldDischarge = await GetDischargeByIdAsync(discharge.Id) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
await _dischargeRepository.Update(discharge);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldDischarge, discharge);
SendDischargeBroadcast(discharge, OperationType.UpdateDischarge);
_logger.LogInformation("Discharge: {discharge} UPDATED", discharge);
}
/// <summary>
/// Processes an <see cref="ApiRequest"/> for patient discharge operations. Based on <paramref name="apiRequest"/>.Type it handles three cases: "NewDischarge" inserts a new discharge, creates an audit log, and sends a discharge broadcast (only when the patient status is "Altable"); "UpdateDischarge" updates the existing discharge; "DeleteDischarge" deletes the discharge (only when the patient status is "NoAltable"). The method returns early and logs an error when the discharge or patient is null, when the patient cannot be found, or when the corresponding discharge-status validation fails.
/// </summary>
/// <param name="apiRequest">The API request containing the discharge payload and the operation type to perform.</param>
/// <!-- aidoc:v1 sig=229d8cd body=acbd395 -->
/// <!-- aidoc-review:v1 severity=medium kind=stale_summary
/// "The summary does not mention that the patient is always updated via _patientServiceLazy.Value.Update(apiRequest.Discharge.Patient) before the switch on apiRequest.Type is evaluated." -->
/// <!-- aidoc-review:v1 severity=medium kind=wrong_summary
/// "The summary states the method 'logs an error when the discharge or patient is null', but when apiRequest.Discharge?.Patient is null the method simply returns silently without logging anything." -->
public async Task SaveRequest(ApiRequest apiRequest)
{
try
{
if (apiRequest.Discharge?.Patient == null) return;
var patientId = apiRequest.Discharge.Patient.Id;
var patient = await _patientServiceLazy.Value.FindById(patientId);
if (patient == null)
{
_logger.LogError("Error discharging patient id: {id} NOT FOUND", patientId);
return;
}
await _patientServiceLazy.Value.Update(apiRequest.Discharge.Patient);
switch (apiRequest.Type)
{
case "NewDischarge":
{
//TODO: ver qué tipos llegan
if (!"Altable".Equals(apiRequest.Discharge.Patient.DischargeStatus?.OptionType))
{
_logger.LogError("Error discharging. Patient not altable: {patient}", patient);
return;
}
await _dischargeRepository.InsertOneAsync(apiRequest.Discharge);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null,
apiRequest.Discharge);
SendDischargeBroadcast(apiRequest.Discharge, OperationType.NewDischarge);
break;
}
case "UpdateDischarge":
{
await GetDischargeByIdAsync(apiRequest.Discharge.Id);
await UpdateDischargeAsync(apiRequest.Discharge);
break;
}
case "DeleteDischarge":
{
if (StatusEnum.Discharge.NoAltable.ToString().Equals(patient.DischargeStatus?.OptionType))
{
_logger.LogError("Error deleting discharge. Patient altable: {patient}", patient);
return;
}
await DeleteDischargeAsync(apiRequest.Discharge);
break;
}
}
}
catch (Exception ex)
{
_logger.LogError("Exception processing discharge api request {discharge} . Exception: {ex}",
apiRequest.Discharge, ex);
}
}
/// <summary>
/// Asynchronously persists the specified API request by executing the save operation on a background thread.
/// </summary>
/// <param name="apiRequest">The API request to save.</param>
/// <returns>A task that completes when the request has been saved.</returns>
/// <!-- aidoc:v1 sig=a3706aa body=c8d77ba -->
public Task SaveRequestAsync(ApiRequest apiRequest)
{
return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest); }));
}
// Revisar
/// <summary>
/// Sends a discharge broadcast to all subscribers associated with the discharge's point of care, grouped by their locale. Validates that the point of care identifier is present; logs and returns early if it is null. Exceptions during the broadcast are caught and logged without rethrowing.
/// </summary>
/// <param name="discharge">The discharge whose point of care is used to locate matching subscribers and whose data is sent in the broadcast.</param>
/// <param name="operation">The operation type associated with the outgoing message sent to each subscriber.</param>
/// <!-- aidoc:v1 sig=f5450d6 body=93e2063 -->
public async void SendDischargeBroadcast(Discharge discharge, OperationType operation)
{
try
{
if (discharge.PointOfCareId == null)
{
_logger.LogError("Error sending discharge broadcast. Unit name is null or empty {discharge} .",
discharge);
return;
}
var subscribersGroup = _subscribersService.GetSubscribers()
.Where(s => s.LocationIds.Any(c => c == discharge.PointOfCareId)).GroupBy(h => h.Locale)
.ToList();
var unit = await _unitService.FindById(discharge.UnitId);
foreach (var group in subscribersGroup)
{
var locale = group.Key ?? LocaleEnum.Default;
IEnumerable<WsSubscriber> subscribers = group;
foreach (var subscriber in subscribers)
{
var dischargeWithLocale = await GetDischargeWithLocale(unit, discharge, locale);
_ = _clientMessageService.SendAsync(subscriber.Id, operation, dischargeWithLocale);
}
}
}
catch (Exception ex)
{
_logger.LogError("Exception sending discharge broadcast. Operation type: {op}. Exception: {ex}",
operation.ToString(), ex);
}
}
/// <summary>
/// Updates the master list option for the specified units and type, then records an audit log entry and broadcasts the change for each affected discharge.
/// </summary>
/// <param name="opt">The master list update options to apply to the discharges.</param>
/// <param name="unitList">The collection of units whose associated discharges will be updated.</param>
/// <param name="typeName">The name of the master list type used to target the update.</param>
/// <!-- aidoc:v1 sig=b539b7b body=09153b7 -->
public async Task UpdatePatientMasterListItemChange(UpdateOptionMasterListDto opt, IEnumerable<Unit> unitList,
string typeName)
{
var unitIds = unitList.Select(x => x.Id).ToList();
await _dischargeRepository.GetDischargesByUnitIds(unitIds);
var dischargeUpdatedList = await _dischargeRepository.UpdateMasterListOption(unitIds, opt, typeName);
var updatedList = dischargeUpdatedList as Discharge[] ?? dischargeUpdatedList.ToArray();
foreach (var discharge in updatedList)
{
var oldDischarge = updatedList.FirstOrDefault(dis => dis.Id == discharge.Id);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldDischarge!, discharge);
SendDischargeBroadcast(discharge, OperationType.UpdateDischarge);
}
}
/// <summary>
/// Deletes a patient master list option for the specified units and type, then processes the resulting updated discharge records by creating audit log entries and broadcasting update notifications (only when the updated discharge record is found).
/// </summary>
/// <param name="opt">The option list entry to be removed from the patient master list.</param>
/// <param name="unitList">The collection of units whose identifiers are used to scope the deletion.</param>
/// <param name="typeName">The name of the master list type/category to which the option belongs.</param>
/// <!-- aidoc-review:v1 severity=low kind=wrong_summary
/// "Summary's parenthetical '(only when the updated discharge record is found)' reads as applying to both the audit log creation and the broadcasting, but in the code the audit log is always created for every discharge while only the broadcast is gated by the dischargeUpdated != null check." -->
public async Task DeletePatientMasterListItem(OptionList opt, IEnumerable<Unit> unitList, string typeName)
{
var unitIds = unitList.Select(x => x.Id).ToList();
var patientUpdatedList = await _dischargeRepository.DeleteMasterListOption(unitIds, opt, typeName);
foreach (var discharge in patientUpdatedList)
{
var dischargeUpdated = await GetDischargeByIdAsync(discharge.Id);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, discharge, null);
if (dischargeUpdated != null)
SendDischargeBroadcast(discharge, OperationType.UpdateDischarge);
}
}
/// <summary>
/// Asynchronously deletes all discharge records associated with the specified unit identifier by delegating the operation to the discharge repository.
/// </summary>
/// <param name="unitId">The unique identifier of the unit whose discharge records should be removed.</param>
/// <!-- aidoc:v1 sig=0d2c782 body=6f86d40 -->
public async Task DeleteDischargesByUnitId(ObjectId unitId)
{
await _dischargeRepository.DeleteByUnitId(unitId);
}
/// <summary>
/// Returns the given <paramref name="discharge"/> with its configurable option names translated according to the specified <paramref name="locale"/>.
/// When <paramref name="unit"/> is null or the locale is <see cref="LocaleEnum.Default"/>, the discharge is returned unchanged.
/// Otherwise, the configured service and destination options are looked up in the locale-specific master lists and their <c>Name</c> values are updated; fields without a matching list, missing properties, null values, or without a translation are left untouched.
/// </summary>
/// <param name="unit">Source of the master list identifiers used to resolve locale-specific options; when null, no translation is performed.</param>
/// <param name="discharge">Discharge instance whose option names may be translated in place.</param>
/// <param name="locale">Target locale used to load the appropriate master list; when set to <see cref="LocaleEnum.Default"/>, the discharge is returned without changes.</param>
/// <returns>The same <paramref name="discharge"/> instance, with translated option names when a matching locale-specific entry is found.</returns>
/// <!-- aidoc:v1 sig=f16a2d5 body=4eee788 -->
private async Task<Discharge> GetDischargeWithLocale(Unit? unit, Discharge discharge, LocaleEnum locale)
{
if (unit == null)
return discharge;
if (locale == LocaleEnum.Default)
return discharge;
// Campos del discharge que deben traducirse
var listMap = new List<(string field, ObjectId? listId, MasterListType type)>
{
("serviceOption", unit.ServiceListId, MasterListType.ServiceList),
("destinationOption", unit.DestinationListId, MasterListType.DestinationList)
};
foreach (var (field, listId, masterListType) in listMap)
{
if (listId == null)
continue;
// Obtener propiedad desde DISCHARGE, no Patient
var prop = typeof(Discharge).GetProperty(
field,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (prop == null)
continue;
var propValue = prop.GetValue(discharge);
if (propValue == null)
continue;
// Cargar master list traducida según locale
var listObj = await _masterListServiceFactory
.GetMasterListById(masterListType, listId.Value, locale);
if (listObj == null)
continue;
var master = listObj as dynamic;
IEnumerable<OptionList> masterOptions = master.Options;
// El campo puede ser OptionList simple
if (propValue is OptionList { Id: not null } option)
{
var translated = masterOptions.FirstOrDefault(o => o.Id == option.Id);
if (translated != null) option.Name = translated.Name; // Solo traducimos el nombre
}
}
return discharge;
}
}