Creado apartir del commit b888de6c92056de294456f581a56e25e734d4ab7 de develop

This commit is contained in:
jrojas
2026-06-26 09:52:35 +02:00
commit 319fd3dfb0
746 changed files with 106610 additions and 0 deletions
@@ -0,0 +1,372 @@
using adas_core.Application.Exceptions;
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.MongoModels;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using Serilog;
using Patient = adas_core.Domain.Models.MongoModels.Patient;
namespace adas_core.Application.Services;
public class AdminPanelService(
IOptions<ApiSettings> apiSettings,
IPatientService patientService,
IConfigObservationService configObservationService,
IMedicineService medicineService,
IPointOfCareService pocService,
IUnitService unitService,
ILogger<AdminPanelService> logger,
IAdmissionService admissionService,
IAuthService authService,
IDischargeService dischargeService,
IDisplayService displayService)
: IAdminPanelService
{
private readonly List<string> _defaultIdRecord = apiSettings.Value.DefaultIdRecord ?? [];
#region Patient
public async Task<bool> ArchivePatient(Patient patient)
{
try
{
await patientService.ArchivePatient(patient);
logger.LogDebug("archived patientid {patientid} from ADMPanel", patient.Id);
return true;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
public async Task<Patient?> CreatePatient(AdmPanelRequest admRequest)
{
var patient = new Patient
{
Id = ObjectId.GenerateNewId()
};
await UpdateNewPatient(patient, admRequest);
await patientService.Insert(patient);
logger.LogDebug("Inserted {patientid} from ADMPanel", patient.Id);
return patient;
}
private async Task UpdateNewPatient(Patient patient, AdmPanelRequest admRequest)
{
if (!string.IsNullOrEmpty(admRequest.PatientNumber)) patient.PatientNumber = admRequest.PatientNumber;
if (admRequest.AdmTime.HasValue) patient.AdmTime = admRequest.AdmTime;
if (admRequest.Patient != null && !admRequest.Patient.IsEmptyDontCheckIds())
patient.Person = admRequest.Patient;
if (admRequest.Patient != null && (admRequest.Patient.Ids == null || admRequest.Patient.Ids.Count == 0))
{
if (patient.Person is { Ids: null }) patient.Person.Ids = new Dictionary<string, string>();
foreach (var item in _defaultIdRecord) patient.Person?.Ids?.Add(item, admRequest?.PatientNumber ?? "null");
}
patient.Person?.SetIds(patient.Person?.Ids ?? new Dictionary<string, string>());
if (admRequest is { UnitId: not null })
{
var unit = await unitService.FindById(admRequest.UnitId);
patient.UnitId = unit?.Id ?? admRequest.UnitId;
patient.UnitString = unit?.Name;
if (admRequest is { PointOfCareId: not null })
{
//El paciente existe en la localizacion no te dejo insertarlo
var patientInLocation = await patientService.FindByPointOfCareId(admRequest.PointOfCareId.Value);
if (patientInLocation != null)
{
logger.LogError(
"trying to insert patientid {patientid} in to location already in use. PointOfcareId: {poc}",
patient.Id, patient.PointOfCareId);
throw new Exception($"patient already in location: {patient.Location}");
}
var poc = await pocService.FindById(admRequest.PointOfCareId.Value);
if (poc == null)
{
logger.LogError(
"trying to insert patientid {patientid} in to location not found. PointOfcareId: {poc}",
patient.Id, admRequest.PointOfCareId);
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
}
await pocService.SetPointOfCareStatus(poc.Id, StatusEnum.PointOfCare.InUse);
patient.Location = new PatientLocation
(
bed: poc.Bed,
room: poc.Room,
unitName: unit?.Name
);
patient.PointOfCareId = poc.Id;
}
else
{
var pocUnknown =
await pocService.FindByBedAndUnitId(VirtualPointOfCare.Unknown.ToString(), patient.UnitId);
patient.PointOfCareId = pocUnknown?.Id;
}
patient.UpdateDate = DateTime.UtcNow;
}
}
public async Task<Patient?> FindPatientById(ObjectId id)
{
return await patientService.FindById(id) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
}
public async Task<Patient?> FindPatientByLocation(PatientLocation location)
{
return await patientService.FindByLocation(location);
}
public async Task<Patient?> FindPatientByPatientNumber(string patientNumber)
{
return await patientService.FindByPatientNumber(patientNumber);
}
public async Task<bool> UpdatePatientData(AdmPanelRequest request, Patient oldPatient)
{
//traer el paciente que se quiere actualizar con los valores que tenga en la base de datos a una variable
//actualizar unicamente los campos que tengan que ver con los datos del paciente
if (request.PatientNumber == null || request.Patient == null || request.Patient.IsEmptyDontCheckIds())
{
logger.LogError("Patient Data not updated. Old Patient:{oldPatient}. Api Request {request}", oldPatient,
request);
throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
}
var patientNumberChanged = oldPatient.PatientNumber != request.PatientNumber;
await patientService.UpdatePatientData(oldPatient.Id, request.PatientNumber, request.Patient,
patientNumberChanged);
return true;
}
public async Task<bool> UpdatePatientLocation(AdmPanelRequest request)
{
var patient = await patientService.FindByLocation(request.OldLocation);
var patientExistsInLocation = await patientService.FindByLocation(request.Location);
//Si ya hay un paciente en esa localización
if (patientExistsInLocation != null)
//Si hay un paciente distinto en la nueva localización movemos al anterior
if (patient != null && patientExistsInLocation.PatientNumber != patient.PatientNumber)
await patientService.UpdateLocation(patientExistsInLocation.Id,
new PatientLocation(VirtualPointOfCare.Pushed.ToString(), ObjectId.GenerateNewId().ToString()));
if (patient != null)
{
if (request.Location?.Bed == null || string.IsNullOrEmpty(request.Location.Bed))
request.Location = new PatientLocation(VirtualPointOfCare.Pushed.ToString(),
ObjectId.GenerateNewId().ToString());
//to solve problems when empty beds with not scapped ""
request.Location.Bed = request.Location?.Bed?.Replace("\"", "");
await patientService.UpdateLocation(patient.Id, request.Location);
patient = await patientService.FindById(patient.Id);
if (patient != null) await patientService.Update(patient);
}
else
{
patient = new Patient
{
Id = ObjectId.GenerateNewId()
};
await patientService.Insert(patient);
}
return true;
}
public async Task<Patient?> FindPatient(AdmPanelRequest request)
{
var findByLocation = !request.Location?.IsFullEmpty();
return await patientService.FindPatient(request.PatientId, request.PatientNumber, request.Location,
findByLocation ?? false) ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
}
public async Task<Patient?> FindByLocation(PatientLocation location)
{
return await patientService.FindByLocation(location) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
}
#endregion
#region ConfigObservations
public async Task<bool> CreateConfig(ConfigObservation configObservation)
{
_ = await configObservationService.CreateConfig(configObservation) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);
return true;
}
public async Task<bool> UpdateConfig(ConfigObservation configObservation)
{
_ = await configObservationService.UpdateConfig(configObservation) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
return true;
}
public async Task<bool> DeleteConfigObservationItem(ObjectId id)
{
_ = await configObservationService.RemoveConfigItem(id) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
return true;
}
#endregion
#region Unit
public async Task<Unit?> InsertUnit(Unit unit)
{
//Insertamos la unidad y creamos los PoCs por defecto para esa unidad
var result = await unitService.InsertOne(unit);
if (result != null)
{
// Por cada valor del enum VirtualPointOfCare, creamos un PointOfCare asociado a la unidad
foreach (var pocEnum in Enum.GetValues(typeof(VirtualPointOfCare)).Cast<VirtualPointOfCare>())
{
var poc = new PointOfCare
{
UnitId = result.Id,
Status = StatusEnum.PointOfCare.Available,
Room = pocEnum.ToString(),
Bed = pocEnum.ToString()
};
var insertedPoc = await pocService.InsertPointOfCare(poc);
if (insertedPoc != null)
{
result.PointOfCareIds ??= [];
result.PointOfCareIds.Add(insertedPoc.Id);
}
}
// Actualizamos la unidad con los nuevos PointOfCareIds
await unitService.UpdateUnit(result);
}
return result;
}
public async Task<UnitInfoDto?> GetUnitDependencyDto(Unit unit)
{
try
{
return new UnitInfoDto(unit)
{
Admissions = await admissionService.CountAdmissionsByUnitId(unit.Id),
Discharges = await dischargeService.CountDischargesByUnitId(unit.Id),
Displays = await displayService.CountDisplaysByUnitId(unit.Id),
Patients = await patientService.CountPatientsByUnitId(unit.Id),
PointOfCares = await pocService.CountPoCsByUnitId(unit.Id),
VirtualPointOfCares = await pocService.CountVirtualPoCsByUnitId(unit.Id)
};
}
catch (Exception e)
{
logger.LogError(e.Message);
return null;
}
}
public async Task<bool> DeleteUnitById(ObjectId unitId)
{
try
{
var unit = await unitService.FindById(unitId);
if (unit == null) throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
//No se puede borrar una unidad que est en uso
var patients = await patientService.CountPatientsByUnitId(unitId);
if (patients > 0)
//Si tiene pacientes no se permite borrado
throw new ConflictException(HttpEnum.ErrorMessage.ConflictResourceInUse);
//Borramos admisiones
await admissionService.DeleteAdmissionsByUnitId(unitId);
//discharges
await dischargeService.DeleteDischargesByUnitId(unitId);
//Borramos Authorizations
await authService.DeleteByUnitId(unitId);
//Displays
await displayService.DeleteDisplaysByUnitId(unitId);
//PointOfCares
await pocService.DeletePoCsByUnitId(unitId);
//Una vez borrados los recursos asociados a la unidad, borramos la unidad
await unitService.DeleteUnitById(unit);
return true;
}
catch (Exception e)
{
Log.Error(e.Message);
return false;
}
}
#endregion
#region Medicine
public async Task<Medicine?> GetMedicineById(ObjectId medicineId)
{
return await medicineService.GetMedicineById(medicineId) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
}
public async Task<Medicine?> PostMedicine(Medicine medicine)
{
var newMedicine = await medicineService.PostMedicine(medicine) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);
return newMedicine;
}
public async Task<Medicine?> UpdateMedicine(Medicine medicine)
{
var updatedMedicine = await medicineService.UpdateMedicine(medicine) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
return updatedMedicine;
}
public async Task<bool> DeleteMedicineById(string medicineId)
{
if (!ObjectId.TryParse(medicineId, out var objectId))
throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestInvalidFormat);
await medicineService.DeleteMedicineById(objectId);
_ = await medicineService.GetMedicineById(ObjectId.Parse(medicineId)) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
return true;
}
#endregion
}
@@ -0,0 +1,744 @@
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 adas_core.Domain.Models.Responses;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
namespace adas_core.Application.Services;
public class AdmissionService(
ILogger<AdmissionService> logger,
ISubscribersService subscribersService,
IAdmissionRepository admissionRepository,
IClientMessageService clientMessageService,
IUnitService unitService,
IPatientService patientService,
IPointOfCareService pointOfCareService,
IDisplayService displayService,
IDischargeService dischargeService,
IPatientArchiveRepository patientArchiveRepository,
IHttpContextAccessor httpContextAccessor,
ILocalAuditService auditService,
IMasterListServiceFactory masterListServiceFactory)
: IAdmissionService
{
// Auditory logs
public async Task DeleteAdmissionAsync(Admission admission)
{
await DeleteAdmissionByIdAsync(admission.Id);
}
public async Task DeleteAdmissionByIdAsync(ObjectId admissionId)
{
var admissionAux = await admissionRepository.FindById(admissionId);
if (admissionAux == null)
{
logger.LogInformation("Error deleting Admission not found, id: {AdmissionId} ", admissionId);
return;
}
await admissionRepository.Delete(admissionId);
if (admissionAux.PointOfCareId.HasValue)
{
var poc = await pointOfCareService.GetInfo(admissionAux.PointOfCareId.Value);
if (poc != null && poc.AdmissionId == admissionId)
{
poc.AdmissionId = null;
poc.Admission = null;
await pointOfCareService.Update(poc);
if (poc.Status != StatusEnum.PointOfCare.Locked && poc.Status != StatusEnum.PointOfCare.InUse)
await pointOfCareService.SetPointOfCareStatus(poc.Id, StatusEnum.PointOfCare.Available);
}
}
logger.LogInformation("Admission id: {AdmissionId} DELETED ", admissionId);
SendAdmissionBroadcast(admissionAux, OperationType.DeleteAdmission);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, admissionAux, null);
}
public async Task DeleteAdmissionsByUnitId(ObjectId unitId)
{
_ = await admissionRepository.DeleteAdmissionsByUnitId(unitId);
}
public async Task<Admission?> GetAdmissionByIdAsync(ObjectId admissionId)
{
var result = await admissionRepository.FindById(admissionId);
if (result?.PointOfCareId != null)
{
var poc = await pointOfCareService.GetInfo(result.PointOfCareId.Value, null,false);
result.PatientLocation = new PatientLocation(poc?.UnitName, poc?.Bed, poc?.Room);
}
return result;
}
public async Task<IEnumerable<Admission>> GetAdmissionsAsync()
{
var resultList = await admissionRepository.FindAll() ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
var admissionsAsync = resultList.ToList();
foreach (var admission in admissionsAsync)
if (admission.PointOfCareId != null)
{
var poc = await pointOfCareService.GetInfo(admission.PointOfCareId.Value, null,false);
admission.PatientLocation = new PatientLocation(poc?.UnitName, poc?.Bed, poc?.Room);
}
return admissionsAsync;
}
public async Task<Admission?> InsertAdmission(Admission admission)
{
var admissionAux = await admissionRepository.FindByNhc(admission.Nhc);
if (admissionAux != null) throw new ConflictException(HttpEnum.ErrorMessage.BadRequestDuplicateData);
PointOfCare? pointOfCare = null;
//Bloqueamos el pointOfCare en el caso de que lo tenga asignado
if (admission.PointOfCareId.HasValue)
{
pointOfCare = await pointOfCareService.GetInfo(admission.PointOfCareId.Value) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
//pointOfCare.AdmissionId = admission.Id;
//pointOfCare.Admission = admission;
admission.PatientLocation = new PatientLocation(pointOfCare.UnitName, pointOfCare.Bed, pointOfCare.Room);
}
var insertedAdmission = await admissionRepository.InsertOneAsyncAndReturn(admission) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);
if (pointOfCare is { AdmissionId: null })
{
pointOfCare.Admission = insertedAdmission;
pointOfCare.AdmissionId = insertedAdmission.Id;
if (pointOfCare.Status == StatusEnum.PointOfCare.Available)
pointOfCare.Status = StatusEnum.PointOfCare.Reserved;
await pointOfCareService.Update(pointOfCare);
}
SendAdmissionBroadcast(insertedAdmission, OperationType.NewAdmission);
// Obtener información el usuario autenticado
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, insertedAdmission);
return insertedAdmission;
}
public async Task UpdateAdmissionAsync(Admission admission)
{
var oldAdmission = await admissionRepository.FindById(admission.Id);
if (oldAdmission == null) return;
if (oldAdmission.PointOfCareId.HasValue)
{
var pocOld = await pointOfCareService.GetInfo(oldAdmission.PointOfCareId.Value, null,false);
if (pocOld != null)
oldAdmission.PatientLocation = new PatientLocation(pocOld.UnitName, pocOld.Bed, pocOld.Room);
}
if (admission.PointOfCareId.HasValue)
{
var poc = await pointOfCareService.GetInfo(admission.PointOfCareId.Value, null,false);
if (poc != null) admission.PatientLocation = new PatientLocation(poc.UnitName, poc.Bed, poc.Room);
}
await admissionRepository.Update(admission);
await HandlePointOfCareChange(admission, oldAdmission);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldAdmission, admission);
SendAdmissionBroadcast(admission, OperationType.UpdateAdmission);
}
public async Task AdmitPatient(Admission admission, bool isNew = false)
{
if (admission.PointOfCareId == null)
{
logger.LogError("PointOfCare is required. Admission: {Admission}", admission);
return;
}
var unit = await unitService.FindById(admission.UnitId);
if (unit == null)
{
logger.LogError("Unit {Name} not found. Unit Id: ", admission.UnitId);
return;
}
var pointOfCare = await pointOfCareService.FindById(admission.PointOfCareId.Value);
if (pointOfCare == null)
{
logger.LogError("Point of Care not found. Patient not created. {Admission}", admission);
return;
}
Patient patient = new()
{
PointOfCareId = pointOfCare.Id,
UnitId = pointOfCare.UnitId,
PointOfCare = pointOfCare,
UnitString = pointOfCare.UnitName,
Bed = pointOfCare.Bed,
Room = pointOfCare.Room,
PatientNumber = admission.Nhc,
AdmTime = DateTime.UtcNow,
Person = admission.Person,
CreationDate = DateTime.UtcNow,
DischargeStatus = pointOfCare.Unit?.DischargeStatusList?.Options.FirstOrDefault(),
Origin = admission.Origin,
OriginAux = admission.OriginAux,
Diagnosis = admission.Diagnosis,
DiagnosisAux = admission.DiagnosisAux,
Allergies = admission.Allergies,
Insulation = admission.Insulation,
LanguageBarrier = admission.LanguageBarrier,
PassiveSitting = admission.PassiveSitting,
Altable = new OptionList
{
Name = "NotAltable",
IconDefault = "icNotAltable",
OptionType = "NotAltable"
},
Visits = pointOfCare.Unit?.VisitOptionList?.Options.FirstOrDefault(),
AccessControl = pointOfCare.Unit?.AccessControlList?.Options.FirstOrDefault(),
Location = new PatientLocation
(
unit.Name,
pointOfCare.Bed,
pointOfCare.Room
)
};
if (unit.AltableOptionListId is not null)
{
var list = await masterListServiceFactory.GetMasterListById(MasterListType.AltableOptionList,
unit.AltableOptionListId.Value, LocaleEnum.Default) as AltableOptionList;
var listOpt = list?.Options.FirstOrDefault(c => c.OptionType == "NotAltable");
if (listOpt != null)
patient.Altable = listOpt;
}
await patientService.Insert(patient);
await SetPointOfCareStatus(pointOfCare.Id, StatusEnum.PointOfCare.InUse);
if (!isNew)
await DeleteAdmissionAsync(admission);
if (admission.Insulation != null)
await patientService.UpdatePatientMasterList(
patient.Id,
MasterListType.InsulationList,
[admission.Insulation],
null, null);
if (admission.Allergies != null)
await patientService.UpdatePatientMasterList(
patient.Id,
MasterListType.AllergyList,
admission.Allergies,
null, null);
if (admission.Diagnosis != null)
await patientService.UpdatePatientMasterList(
patient.Id,
MasterListType.DiagnosisList,
[admission.Diagnosis],
null, null);
if (admission.Origin != null)
await patientService.UpdatePatientMasterList(
patient.Id,
MasterListType.OriginList,
[admission.Origin],
null, null);
if (admission.LanguageBarrier != null)
await patientService.UpdatePatientMasterList(
patient.Id,
MasterListType.LanguageBarrierList,
admission.LanguageBarrier,
null, null);
if (admission.PassiveSitting != null)
await patientService.UpdatePatientMasterList(
patient.Id,
MasterListType.PassiveSittingList,
[admission.PassiveSitting],
null, null);
}
public async Task ReturnPatientToAdmissions(ObjectId patientId)
{
var patient = await patientService.FindById(patientId);
if (patient == null)
{
logger.LogError("Error returning the patient Id: {Id} to admission", patientId);
return;
}
var unit = await unitService.FindById(patient.UnitId);
if (unit == null)
{
logger.LogError("Unit not found by Id. {Name}", patient.UnitId);
return;
}
if (patient.PointOfCareId != null)
{
var poc = await pointOfCareService.FindById(patient.PointOfCareId.Value);
Admission admission = new()
{
Nhc = patient.PatientNumber ?? string.Empty,
PointOfCareId = poc?.Id,
UnitId = unit.Id,
Person = patient.Person ??
new Person(), //No debería ser null en este punto, pero así quito el warning
Origin = patient.Origin,
OriginAux = patient.OriginAux,
Diagnosis = patient.Diagnosis,
PassiveSitting = patient.PassiveSitting,
DiagnosisAux = patient.DiagnosisAux,
Allergies = patient.Allergies,
Insulation = patient.Insulation,
LanguageBarrier = patient.LanguageBarrier,
AdmissionDate = patient.AdmTime ?? DateTime.UtcNow,
PatientLocation = patient.Location
};
await InsertAdmission(admission);
}
var dis = await dischargeService.GetDischargeByPatientId(patient.Id);
if (dis != null) await dischargeService.DeleteDischargeByIdAsync(dis.Id);
await patientService.ArchivePatient(patient);
pointOfCareService.CheckNextAdmission(patient.PointOfCareId);
}
// Used for temporal beds like PUSHED
public async Task ReturnPatientToAdmissions(ObjectId patientId, Admission adm)
{
var patient = await patientService.FindById(patientId);
if (patient == null)
{
logger.LogError("Error returning the patient Id: {Id} to admission", patientId);
return;
}
var unit = await unitService.FindById(adm.UnitId);
if (unit == null)
{
logger.LogError("Unit not found by Id. {Name}", patient.UnitId);
return;
}
if (adm.PointOfCareId != null)
{
var poc = await pointOfCareService.FindById(adm.PointOfCareId.Value);
Admission admission = new()
{
Nhc = patient.PatientNumber ?? string.Empty,
PointOfCareId = poc?.Id,
UnitId = unit.Id,
Person = patient.Person ??
new Person(), //No debería ser null en este punto, pero así quito el warning
Origin = patient.Origin,
OriginAux = patient.OriginAux,
Diagnosis = patient.Diagnosis,
DiagnosisAux = patient.DiagnosisAux,
Allergies = patient.Allergies,
PassiveSitting = patient.PassiveSitting,
Insulation = patient.Insulation,
LanguageBarrier = patient.LanguageBarrier,
AdmissionDate = patient.AdmTime ?? DateTime.UtcNow,
PatientLocation = patient.Location
};
await InsertAdmission(admission);
}
var dis = await dischargeService.GetDischargeByPatientId(patient.Id);
if (dis != null) await dischargeService.DeleteDischargeByIdAsync(dis.Id);
await patientService.ArchivePatient(patient);
pointOfCareService.CheckNextAdmission(patient.PointOfCareId);
}
public async Task<List<Admission>> GetAdmissionByLocation(PatientLocation location)
{
try
{
return await admissionRepository.FindByLocation(location);
}
catch (Exception e)
{
logger.LogError("Error getting admission by patient location {Location} exception: {Ex}", location,
e.Message);
return [];
}
}
public async Task<List<Admission>> GetAdmissionByPointOfCareId(ObjectId pocId)
{
try
{
var result = await admissionRepository.FindByPointOfCareId(pocId);
foreach (var admission in result)
{
var poc = await pointOfCareService.GetInfo(pocId, null,false);
if (poc != null) admission.PatientLocation = new PatientLocation(poc.UnitName, poc.Bed, poc.Room);
}
return result;
}
catch (Exception e)
{
logger.LogError("Error getting admission by patient PocId {PocId} exception: {Ex}", pocId, e.Message);
return [];
}
}
public async Task<List<Admission>> GetAdmissionByPointOfCareIdAndLocale(ObjectId pocId, LocaleEnum locale)
{
var admissions = await GetAdmissionByPointOfCareId(pocId);
// Ejecutar todas las traducciones en paralelo
var translatedAdmissions = await Task.WhenAll(
admissions.Select(adm => GetAdmissionWithLocale(adm, locale))
);
return translatedAdmissions.ToList();
}
public async Task<List<Admission>> GetAdmissionByUnitIdWithOutPoC(ObjectId unitId)
{
try
{
return await admissionRepository.GetAdmissionByUnitIdWithOutPoC(unitId);
}
catch (Exception e)
{
logger.LogError("Error getting admission by unitId with out PoCId {UnitId} exception: {Ex}", unitId,
e.Message);
return [];
}
}
public async Task<long> CountAdmissionsByUnitId(ObjectId unitId)
{
try
{
return await admissionRepository.CountByUnitId(unitId);
}
catch (Exception e)
{
logger.LogError("Error getting admission by unitId with out PoCId {UnitId} exception: {Ex}", unitId,
e.Message);
return 0;
}
}
public async Task<PatientSearch?> SearchByPatientNumberAndDistinctUnit(string patientNumber, ObjectId unitId)
{
var patient = await patientService.FindByPatientNumber(patientNumber);
if (patient != null)
{
if (patient.PointOfCareId.HasValue)
patient.PointOfCare = await pointOfCareService.FindById(patient.PointOfCareId.Value);
if (patient.UnitId.HasValue)
{
var uni = await unitService.FindById(patient.UnitId.Value);
if (uni != null)
patient.UnitString = uni.Name;
}
}
var archivePatient =
await patientArchiveRepository.SearchByPatientNumberAndDistinctUnit(patientNumber, unitId);
var admission = await admissionRepository.SearchByPatientNumberAndDistinctUnit(patientNumber, unitId);
var result = new PatientSearch(patient, archivePatient, admission,
patient == null && archivePatient != null,
patient != null || archivePatient != null || admission != null);
return result;
}
public Task<Admission?> GetAdmissionByPatientNumber(string patientNumber)
{
return admissionRepository.FindByNhc(patientNumber);
}
public async Task UpdatePatientMasterListItemChange(UpdateOptionMasterListDto opt, IEnumerable<Unit> unitList,
string typeName)
{
var unitIds = unitList.Select(x => x.Id).ToList();
var oldAdmissionList = await admissionRepository.FindByUnitIds(unitIds);
var admissionUpdatedList = await admissionRepository.UpdateMasterListOption(unitIds, opt, typeName);
foreach (var admission in admissionUpdatedList)
{
var oldAdmission = oldAdmissionList.Find(adm => adm.Id == admission.Id);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldAdmission!, admission);
SendAdmissionBroadcast(admission, OperationType.UpdateAdmission);
}
}
public async Task DeletePatientMasterListItem(OptionList opt, IEnumerable<Unit> unitList, string typeName)
{
var unitIds = unitList.Select(x => x.Id).ToList();
var oldAdmissionList = await admissionRepository.FindByUnitIds(unitIds);
var admissionUpdatedList = await admissionRepository.DeleteMasterListOption(unitIds, opt, typeName);
foreach (var admission in admissionUpdatedList)
{
var admissionUpdated = await GetAdmissionByIdAsync(admission.Id);
var oldAdmission = oldAdmissionList.Find(adm => adm.Id == admission.Id);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldAdmission!,
admissionUpdated);
if (admissionUpdated != null)
SendAdmissionBroadcast(admissionUpdated, OperationType.UpdateAdmission);
}
}
public async Task SaveRequest(ApiRequest apiRequest)
{
try
{
if (apiRequest.Admission == null)
return;
switch (apiRequest.Type)
{
case "NewAdmission":
{
if (string.IsNullOrEmpty(apiRequest.Admission.Nhc) ||
apiRequest.Admission.Origin == null ||
apiRequest.Admission.Diagnosis == null)
{
logger.LogDebug(
"Error saving admission api request. Some values are required. Admission: {Admission}",
apiRequest.Admission);
return;
}
await InsertAdmission(apiRequest.Admission);
break;
}
case "UpdateAdmission":
{
if (string.IsNullOrEmpty(apiRequest.Admission.Nhc) ||
apiRequest.Admission.Origin == null ||
apiRequest.Admission.Diagnosis == null)
{
logger.LogDebug(
"Error updating admission api request. Some values are required. Admission: {Admission}",
apiRequest.Admission);
return;
}
await UpdateAdmissionAsync(apiRequest.Admission);
break;
}
case "DeleteAdmission":
{
await DeleteAdmissionAsync(apiRequest.Admission);
break;
}
}
}
catch (Exception ex)
{
logger.LogError("Exception updating admission {Admission} . Exception: {Ex}", apiRequest.Admission,
ex.Message);
throw;
}
}
public Task SaveRequestAsync(ApiRequest apiRequest)
{
return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest); }));
}
private async Task HandlePointOfCareChange(Admission admission, Admission oldAdmission)
{
// Check if PointOfCare has changed.
if (admission.PointOfCareId == oldAdmission.PointOfCareId) return;
// Disable new PointOfCare if it exists.
if (admission.PointOfCareId.HasValue)
{
var newPoc = await pointOfCareService.GetInfo(admission.PointOfCareId.Value);
if (newPoc == null) return;
var patientOnNewPoc = await patientService.FindByPointOfCareId(newPoc.Id);
if (newPoc.AdmissionId == null && newPoc.Status != StatusEnum.PointOfCare.Locked)
{
newPoc.Admission = admission;
newPoc.AdmissionId = admission.Id;
newPoc.Status = patientOnNewPoc != null
? StatusEnum.PointOfCare.InUse
: StatusEnum.PointOfCare.Reserved;
await pointOfCareService.Update(newPoc);
}
if (newPoc.Status == StatusEnum.PointOfCare.Available)
pointOfCareService.CheckNextAdmission(newPoc.Id);
}
// Enable previous PointOfCare if it exists.
if (oldAdmission.PointOfCareId.HasValue)
{
var oldPoc = await pointOfCareService.GetInfo(oldAdmission.PointOfCareId.Value);
if (oldPoc != null)
{
var patientOnOldPoc = await patientService.FindByPointOfCareId(oldPoc.Id);
if (oldPoc.AdmissionId == oldAdmission.Id && oldPoc.Status != StatusEnum.PointOfCare.Locked)
{
oldPoc.Admission = null;
oldPoc.AdmissionId = null;
oldPoc.Status = patientOnOldPoc != null
? StatusEnum.PointOfCare.InUse
: StatusEnum.PointOfCare.Available;
await pointOfCareService.Update(oldPoc);
}
}
if (oldPoc is { Status: StatusEnum.PointOfCare.Available })
pointOfCareService.CheckNextAdmission(oldPoc.Id);
}
}
private async Task SetPointOfCareStatus(ObjectId pointOfCareId, StatusEnum.PointOfCare status)
{
var pointOfCare = await pointOfCareService.FindById(pointOfCareId);
if (pointOfCare == null) return;
await pointOfCareService.SetPointOfCareStatus(pointOfCareId, status);
}
private async void SendAdmissionBroadcast(Admission admission, OperationType operation)
{
try
{
if (admission.PointOfCareId == null)
await SendAdmissionByUnitId(admission, operation);
else
await SendAdmissionBroadcastByPoC(admission, operation);
}
catch (Exception ex)
{
logger.LogError("Exception sending admission broadcast. Operation type: {Op}. Exception: {Ex}",
operation.ToString(), ex.Message);
}
}
private async Task SendAdmissionBroadcastByPoC(Admission admission, OperationType operation)
{
if (!admission.PointOfCareId.HasValue)
{
logger.LogError("Error sending admission broadcast. PointOfCare id {Id} not found",
admission.PointOfCareId);
return;
}
var pointOfCare = await pointOfCareService.FindById(admission.PointOfCareId.Value);
if (pointOfCare == null)
{
logger.LogError("Error sending admission broadcast. PointOfCare id {Id} not found",
admission.PointOfCareId);
return;
}
var pocSubscribers = subscribersService.GetSubscribers().Where(s =>
s.LocationIds.Contains(admission.PointOfCareId.Value)).GroupBy(h => h.Locale);
foreach (var group in pocSubscribers)
{
var locale = group.Key ?? LocaleEnum.Default;
IEnumerable<WsSubscriber> subscribers = group;
foreach (var subscriber in subscribers)
{
var admissionWithLocale = await GetAdmissionWithLocale(admission, locale);
_ = clientMessageService.SendAsync(subscriber.Id, operation, admissionWithLocale);
}
}
}
private async Task SendAdmissionByUnitId(Admission admission, OperationType operation)
{
if (admission.UnitId == ObjectId.Empty)
{
logger.LogError("Error sending admission broadcast. UnitId is null or empty {Admission}", admission);
return;
}
var displays = await displayService.GetByUnitId(admission.UnitId);
var displayIds = displays.Select(c => c.Id).ToList();
var unitSubscribers = subscribersService.GetSubscribers().Where(s =>
s.DisplayId != null && displayIds.Contains(s.DisplayId.Value)).GroupBy(h => h.Locale);
foreach (var group in unitSubscribers)
{
var locale = group.Key ?? LocaleEnum.Default;
IEnumerable<WsSubscriber> subscribers = group;
foreach (var subscriber in subscribers)
{
var admissionWithLocale = await GetAdmissionWithLocale(admission, locale);
_ = clientMessageService.SendAsync(subscriber.Id, operation, admissionWithLocale);
}
}
}
private async Task<Admission> GetAdmissionWithLocale(Admission admission, LocaleEnum locale)
{
var unit = await unitService.FindById(admission.UnitId);
if (unit == null) return admission;
if (admission.Origin?.Name != null && unit.OriginListId.HasValue)
{
if (await masterListServiceFactory.GetMasterListById(MasterListType.OriginList,
unit.OriginListId.Value, locale) is OriginList list)
{
var listOrigin = list.Options.FirstOrDefault(c => c.Id == admission.Origin.Id)?.Name;
if (listOrigin != null)
admission.Origin.Name = listOrigin;
}
}
if (admission.Diagnosis?.Name != null && unit.DiagnosisListId.HasValue)
{
if (await masterListServiceFactory.GetMasterListById(MasterListType.DiagnosisList,
unit.DiagnosisListId.Value, locale) is DiagnosisList list)
{
var listDiagnosis = list.Options.FirstOrDefault(c => c.Id == admission.Diagnosis.Id)?.Name;
if (listDiagnosis != null)
admission.Diagnosis.Name = listDiagnosis;
}
}
if (admission.Insulation?.Name != null && unit.InsulationListId.HasValue)
{
if (await masterListServiceFactory.GetMasterListById(MasterListType.InsulationList,
unit.InsulationListId.Value, locale) is InsulationList list)
{
var listInsulation = list.Options.FirstOrDefault(c => c.Id == admission.Insulation.Id)?.Name;
if (listInsulation != null)
admission.Insulation.Name = listInsulation;
}
}
return admission;
}
}
@@ -0,0 +1,969 @@
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.GroupedObservations;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using Serilog;
using Patient = adas_core.Domain.Models.MongoModels.Patient;
namespace adas_core.Application.Services;
public class AlarmService : IAlarmService
{
private readonly IAlarmRepository _alarmRepository;
private readonly IOptions<ApiSettings> _apiSettings;
private readonly ILocalAuditService _auditService;
private readonly Lazy<ICalculatedObservationsService> _calculatedObservationsService;
private readonly IClientMessageService _clientMessageService;
private readonly IConfigObservationService _configObservationService;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly Lazy<ILightBeaconService> _lightBeaconService;
private readonly ILogger<AlarmService> _logger;
private readonly Lazy<IObservationService> _observationService;
private readonly IPatientService _patientService;
private readonly IPointOfCareService _pocService;
private readonly Lazy<IRecordingService> _recordingService;
private readonly List<PatientObservation> _relayAlarmList = [];
private readonly Lazy<IRelayService> _relayService;
private readonly SemaphoreSlim
_semaphore = new(1, 1); // Semáforo para evitar la ejecución simultánea del temporizador
private readonly ISubscribersService _subscribersService;
private readonly IUnitService _unitService;
//private readonly string _url;
private List<PatientObservation> _beaconAlarmList = [];
private TimeSpan _interval;
public AlarmService(IAlarmRepository alarmRepository,
ILogger<AlarmService> logger,
IPatientService patientService,
IConfigObservationService configObservationService,
Lazy<IObservationService> observationService,
IClientMessageService clientMessageService,
ISubscribersService subscribersService,
Lazy<ICalculatedObservationsService> calculatedObservationsService,
Lazy<ILightBeaconService> lightBeaconService,
Lazy<IRecordingService> recordingService,
Lazy<IRelayService> relayService,
IOptions<ApiSettings> apiSettings,
IUnitService unitService,
IPointOfCareService pocService,
IHttpContextAccessor httpContextAccessor,
ILocalAuditService auditService,
bool startTimer = true
)
{
_alarmRepository = alarmRepository;
_logger = logger;
_patientService = patientService;
_configObservationService = configObservationService;
_observationService = observationService;
_clientMessageService = clientMessageService;
_subscribersService = subscribersService;
_calculatedObservationsService = calculatedObservationsService;
_lightBeaconService = lightBeaconService;
_recordingService = recordingService;
_relayService = relayService;
_apiSettings = apiSettings;
_unitService = unitService;
_pocService = pocService;
_httpContextAccessor = httpContextAccessor;
_auditService = auditService;
if (apiSettings == null) throw new Exception("ApiSettings must be defined");
if (startTimer) StartTimer();
}
public async Task SaveRequest(ApiRequest apiRequest)
{
if (
string.IsNullOrEmpty(apiRequest.PatientNumber) &&
string.IsNullOrEmpty(apiRequest.Location?.UnitName)
)
{
_logger.LogDebug("Patient and PointOfCare are nulls");
throw new InvalidFormatException(HttpEnum.ErrorMessage.BadRequestMissingParameters);
}
var patient = await _patientService.FindPatientByApiRequest(apiRequest);
if (patient == null)
{
// NO PATIENTS OR LOCATIONS WERE FOUND
_logger.LogWarning(
"Observation for patient: {apiRequestpatientNumber} with location: {apiRequestlocation} not found, ignoring",
apiRequest.PatientNumber, apiRequest.Location);
return;
}
var unitConfig = await _unitService.FindById(patient.UnitId);
if (!Hl7Utils.ManageAutoAdt(unitConfig, null, _logger, "ORU Alarm")) return;
switch (apiRequest.Type)
{
/*
* ORU_R40 - Unsolicited transmission of an alert observation message
*/
case "ORU_R40": // UNSOLICITED ALERT OBSERVATION
// OBSERVATIONS
if (apiRequest.Observation != null && apiRequest.Observations?.FirstOrDefault() == null)
apiRequest.Observations = [apiRequest.Observation];
//var obrcode = apiRequest.ObservationData?.Code ?? "";
if (!apiRequest.Alarms.IsNullOrEmpty())
await ProcessAlarmObservations(apiRequest.Alarms ?? [], apiRequest.Observations ?? [],
patient, apiRequest.ObservationData?.Time ?? apiRequest.MessageTime,
apiRequest.ObservationData);
break;
default:
_logger.LogWarning("ApiRequest type {apiRequesttype} sis not valid for Observations",
apiRequest.Type);
throw new ApiRequestException("ApiRequest type " + apiRequest.Type +
" is not valid for Observations");
}
}
public async Task SaveRequestAsync(ApiRequest apiRequest)
{
await SaveRequest(apiRequest);
}
public async Task<PatientObservationAlarm?> MapObservation(PatientObservationAlarm obs, bool onlyByName = false)
{
try
{
_logger.LogTrace("Mapping config Observation obs: {obs} onlyByName: {onlyByName}", obs, onlyByName);
var obs2 = await _configObservationService.Map(obs, onlyByName);
if (obs2 == null)
{
_logger.LogTrace("Mapping obs2 {obs}: Ignored", obs);
return null;
}
_logger.LogTrace("Mapping _configObservationService.Map obs2: {obs2}", obs2);
var obs3 = await _calculatedObservationsService.Value.Map(obs2, onlyByName);
if (obs3 == null)
{
_logger.LogTrace("Mapping obs3 {obs2}: Ignored", obs2);
return null;
}
return obs3;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error Mapping Observation, Ignoring Observation: {obs} Exception:{ex}", obs,
ex.Message);
return null;
}
}
public async Task<List<PatientObservationAlarm>> FindLastObservationsByField(ObjectId patientId,
List<Field>? filterObservations = null)
{
var result =
await _alarmRepository.AggregatedPatientLastObservationsByField(patientId, filterObservations);
return result;
}
public async Task<List<PatientObservationAlarm>> FindLastValuesNotExpired(ObjectId patientId,
List<Field> filterObservations, List<ConfigObservation> configAlarm)
{
var result =
await _alarmRepository.AggregatedPatientNotExpiredObservationsByField(patientId, filterObservations,
configAlarm);
return result;
}
public async Task<PatientObservationAlarm?> MapObservationsByName(PatientObservationAlarm obs)
{
return await _configObservationService.Map(obs, true);
}
public async Task ProcessAlarmObservations(List<PatientObservationAlarm> alarmObservations,
List<PatientObservation> observations, Patient patient,
DateTime messageTime, ObservationData? observationData = null)
{
_logger.LogDebug(
"Patient: {patientid} PoC {patientpointOfCare} {patientbed} msg {messageTime} INSERTING {observationsCount} OBSERVATIONS",
patient.Id, patient.UnitId, patient.PointOfCareId, messageTime, alarmObservations.Count);
ParentDataClass? parentData = null;
if (observationData != null)
parentData = new ParentDataClass
{
Code = observationData.Code,
CodingSystem = observationData.CodingSystem,
Name = observationData.Text
};
var listToInsert = new List<PatientObservationAlarm>();
foreach (var obs in alarmObservations)
{
obs.ParentData = parentData;
obs.MessageTime = messageTime;
obs.PatientId = patient.Id;
obs.Patient = patient;
obs.Id = ObjectId.GenerateNewId();
var intObsTime = new DateTimeOffset(obs.Time).ToUnixTimeSeconds();
if (obs.Time == DateTime.MinValue || intObsTime <= 10)
obs.Time = DateTime.UtcNow;
var intMessageTime = new DateTimeOffset(obs.MessageTime).ToUnixTimeSeconds();
if (obs.MessageTime == DateTime.MinValue || intMessageTime <= 10)
obs.MessageTime = DateTime.UtcNow;
if (obs.Value.ToString() == "System.Object")
{
obs.Value = obs.Event?.ToString()??string.Empty;
}
_logger.LogDebug(
"Patient: {patientId} PoC {patientpointOfCare} {patientbed} msg {messageTime} INSERTING ALARM OBSERAVTION {obs}",
patient.Id, patient.PointOfCare, patient.Bed, messageTime, obs);
listToInsert.Add(obs);
}
//listToInsert.ForEach(async obs => await InsertObservation(obs));
foreach (var alarmToInsert in listToInsert)
{
var alarmData =
observations.FirstOrDefault(obs => obs.Value.ToString() == alarmToInsert.Value.ToString());
if (alarmData != null)
{
alarmToInsert.Code = alarmData.Code;
alarmToInsert.Name = alarmData.Code;
alarmToInsert.CodingSystem = alarmData.CodingSystem;
}
else
{
alarmToInsert.CodingSystem = parentData?.CodingSystem?? "MDIL-ALARM";
alarmToInsert.Code = alarmToInsert.Priority.ToString();
alarmToInsert.Name = alarmToInsert.Priority switch
{
AlarmEnum.ObservationAlarmPriority.Ph => "RedAlarm_Ph",
AlarmEnum.ObservationAlarmPriority.Pm => "YellowAlarm_Pm",
AlarmEnum.ObservationAlarmPriority.Pl => "BlueAlarm_Pl",
_ => "",
};
}
if (!alarmToInsert.Sources.IsNullOrEmpty())
{
var apiRequestObs = new ApiRequest
{
Type = "ORU_R01",
MessageTime = messageTime,
ObservationData = observationData
};
var obsToInsert = new List<PatientObservation>();
alarmToInsert.Sources?.ForEach(async void (c) =>
{
try
{
var obs = new PatientObservation
{
Code = c.Code,
Name = c.OriginalName,
CodingSystem = c.CodeSystem,
Units = c.Units,
Value = c.Value?.ToString() ?? "No value",
Time = alarmToInsert.Time,
Result = c.Result
};
var obs2 = await _calculatedObservationsService.Value
.MapSourceAlarm(obs, alarmToInsert);
obsToInsert.Add(obs2);
}
catch (Exception e)
{
_logger.LogError(
"Error processing source observation {source} for alarm {alarm}. Exception: {ex}",
c, alarmToInsert, e);
}
});
apiRequestObs.Observations = obsToInsert;
apiRequestObs.Location = patient.Location;
apiRequestObs.Patient = patient.Person;
apiRequestObs.PatientNumber = patient.PatientNumber;
await _observationService.Value.SaveRequestAsync(apiRequestObs);
}
await InsertObservation(alarmToInsert);
}
}
private async Task InsertObservation(PatientObservationAlarm obs, bool persistObs = true, bool mapObs = true)
{
try
{
var obs2 = obs;
//only will be false if the obs comes from the inner refactor job
if (mapObs) obs2 = await MapObservation(obs2, onlyByName: true);
if (obs2 == null)
{
_logger.LogDebug("Mapped observation returns null. Ignored {obs}", obs);
}
else
{
if (obs2.Persist.HasValue && !obs2.Persist.Value) persistObs = false;
if (persistObs)
{
_logger.LogDebug("Mapped {obs2}", obs2);
await _alarmRepository.InsertOneAsync(obs2);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, obs2);
}
_logger.LogDebug("Inserted {obs2}", obs2);
await SendObsBroadcast(obs2);
}
}
catch (Exception ex)
{
_logger.LogError("Error Inserting observation {obs}. Excepcion; {ex} ", obs, ex);
}
}
private async Task SendObsBroadcast(BasePatientObservation obs)
{
if (obs.Name == null) return;
const OperationType type = OperationType.Alarm;
var patient = obs.Patient ?? await _patientService.FindById(obs.PatientId);
if (patient == null)
{
_logger.LogDebug("Not patient on bd to sendOnBroadcastObs: {obspatientid}", obs.PatientId);
return;
}
_logger.LogTrace(
"sending obs name: {obsname} to patient id: {patientid}, PointOfCare: {patientpointOfCare} {patientbed}",
obs.Name, patient.Id, patient.UnitId, patient.Bed);
var subscribers = _subscribersService.GetSubscribers().Where(s =>
!s.LocationIds.IsNullOrEmpty() && s.LocationIds.Any(c =>
c == patient.PointOfCareId
)).ToList();
foreach (var subscriber in subscribers)
{
_logger.LogTrace("sending obs name: {obsname} to subscriber id: {subscriberId}", obs.Name,
subscriber.Id);
await _clientMessageService.SendAsync(subscriber.Id, type, obs);
}
}
#region activación de alarmas con balizas, relé y grabaciones
public async Task CheckObservationAlarm(PatientObservation obs)
{
//ConfigObservations
var configs = await _configObservationService.Get(new PatientObservation
{
Name = obs.Name,
PatientId = obs.PatientId
}
);
if (configs?.CreateObservation == null)
return;
var obsValue = obs.Value.ToString() ?? string.Empty;
var observationsToCreate = configs.CreateObservation
.Where(c => obsValue.ToUpper().Contains(c.RequiredValue?.ToString()?.ToUpper() ?? string.Empty))
.ToList();
foreach (var obsConfig in observationsToCreate)
{
var create = false;
if (obsConfig.Preconditions == null)
create = true;
else
foreach (var preCondition in obsConfig.Preconditions)
{
if (preCondition.Name == null)
continue;
var obsWithConditions =
await _observationService.Value.FindLastObservations(obs.PatientId, 1,
[preCondition.Name]);
if (!obsWithConditions.Any())
continue;
var foundObs = obsWithConditions.FirstOrDefault();
//Descartamos la observación si ha expirado
if (foundObs == null || (obsConfig.Expires.HasValue &&
foundObs.Time.AddSeconds(obsConfig.Expires.Value) < DateTime.UtcNow))
continue;
var foundObsStr = foundObs.Value.ToString();
var requiredValueStr = preCondition.RequiredValue?.ToString();
if (string.IsNullOrEmpty(requiredValueStr) ||
(foundObsStr != null && foundObsStr.Contains(requiredValueStr)))
{
create = true;
break;
}
}
if (create)
{
var newObservation = CreateNewObservation(obs, obsConfig, StatusEnum.Type.Alert);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, newObservation);
newObservation = await CheckAlarmConfig(newObservation);
var obsName = newObservation.Name ?? string.Empty;
_ = SendAlarm(newObservation, obsName, null, AlarmEnum.Severity.None, AlarmEnum.Type.Auto);
_ = _observationService.Value.InsertObservation(newObservation);
}
}
}
private static PatientObservation CreateNewObservation(PatientObservation obs, ConfigObservation config,
StatusEnum.Type type)
{
return new PatientObservation
{
CodingSystem = config.CodingSystem,
Code = config.Code,
Name = config.Name,
Value = obs.Value,
PatientId = obs.PatientId,
Time = obs.Time,
Alarm = config.Alarm,
Status = type
};
}
private async Task<PatientObservation> CheckAlarmConfig(PatientObservation pobs)
{
var configObs = await _configObservationService.Get(new PatientObservation
{
Name = pobs.Name,
PatientId = pobs.PatientId
});
pobs.Alarm = configObs?.Alarm ?? null;
return pobs;
}
/// <summary>
/// Sends a new alarm
/// </summary>
/// <param name="obs">Observation to generate the alarm</param>
/// <param name="name">Name of the alarm</param>
/// <param name="code">Code of the alarm for the recording</param>
/// <param name="severity">Severity of the alarm for the recording</param>
/// <param name="type"></param>
/// <exception cref="ArgumentOutOfRangeException"></exception>
/// <returns>New alarm created</returns>
public async Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code, AlarmEnum.Severity severity,
AlarmEnum.Type type)
{
try
{
var poc = await _pocService.FindPoCByPatientId(obs.PatientId);
switch (obs.Time.Kind)
{
// Convert obs.Time to UTC if it's not already
case DateTimeKind.Local:
obs.Time = obs.Time.ToUniversalTime();
break;
case DateTimeKind.Unspecified:
_logger.LogWarning("obs.Time has unspecified kind. Assuming it to be UTC.");
obs.Time = DateTime.SpecifyKind(obs.Time, DateTimeKind.Utc);
break;
case DateTimeKind.Utc:
break;
default:
throw new ArgumentOutOfRangeException();
}
//ConfigObservations
var configObs = await _configObservationService.Get(new PatientObservation
{
Name = obs.Name,
PatientId = obs.PatientId
}
);
if (configObs == null)
return;
if (configObs is { Alarm.Enabled: true })
{
obs.Alarm = configObs.Alarm;
var now = DateTime.UtcNow;
if (obs.Expired || (obs.Expires.HasValue && obs.Time.AddSeconds(obs.Expires.Value) < now))
return;
//En la prioridad de las alarmas 1 máxima prioridad
if (configObs.Alarm.Beacon is { Enabled: true })
try
{
if (obs.Time.AddSeconds(configObs.Alarm.Beacon.EndAfter) >= now)
{
var patient = obs.Patient ?? await _patientService.FindById(obs.PatientId);
if (patient != null)
{
_logger.LogDebug("PatientId: {nObsPatientid}. Send Beacon alarmName {beaconColor}",
obs.PatientId, configObs.Alarm.Beacon.BeaconColor);
if (!_beaconAlarmList.Any(o =>
o.PatientId == obs.PatientId && o.Alarm?.Priority < obs.Alarm.Priority))
{
_ = SendBeaconCode(configObs.Alarm.Beacon.BeaconColor, patient);
lock (_beaconAlarmList)
{
_beaconAlarmList.Add(obs);
}
}
}
}
else
{
_logger.LogDebug(
"PatientId: {nObsPatientid}.Beacon is Expired. EndAfter {endAfter} Time: {obsTime}",
obs.PatientId, configObs.Alarm.Beacon.EndAfter, obs.Time);
}
}
catch (Exception ex)
{
_logger.LogDebug("Exception sending beacon code for patient {patientId}. Exception: {ex}",
obs.PatientId, ex);
throw;
}
if (configObs.Alarm.Recording is { Enabled: true })
try
{
if (obs.Time.AddSeconds(configObs.Alarm.Recording.EndAfter) >= now)
{
//if(severity == AlarmSeverity.NONE)
severity = configObs.Alarm.Recording.Severity;
var strValue = obs.Value.ToString();
if (strValue == null)
{
_logger.LogError("Observation value to string is null observation:{nObs}", obs);
return;
}
_logger.LogDebug("PatientId: {nObsPatientid}. Send Recording {nObsName}", obs.PatientId,
obs.Name);
if (code == null && Enum.TryParse<AlarmEnum.Name>(configObs.Alarm.Name, out var result))
code = result;
_ = StartRecording(obs.PatientId, obs.Time, configObs.Alarm.Recording, code, severity,
strValue, configObs.Alarm.Recording.EndAfter, type);
}
else
{
_logger.LogDebug(
"PatientId: {nObsPatientid}.Recording is Expired. EndAfter {endAfter} Time: {obsTime}",
obs.PatientId, configObs.Alarm.Recording.EndAfter, obs.Time);
}
}
catch (Exception ex)
{
_logger.LogDebug(
"Exception sending Alarm Recording for patient {patientId}. Exception: {ex}",
obs.PatientId, ex);
throw;
}
if (configObs.Alarm.OpenDoor is { Enabled: true })
try
{
if (obs.Time.AddSeconds(configObs.Alarm.OpenDoor.EndAfter) >= now)
{
_logger.LogDebug("PatientId: {nObsPatientid}. Open door observation {obsName}",
obs.PatientId, obs.Name);
lock (_relayAlarmList)
{
if (!_relayAlarmList.Any(o =>
o.PatientId == obs.PatientId && o.Alarm?.Priority < obs.Alarm.Priority))
{
PointOfCareConfiguration? poCSettings = null;
if (poc is { Configuration.RelayIdList: not null })
poCSettings = poc.Configuration;
var status = _relayService.Value.GetRelayByTypeInList(poCSettings?.RelayIdList,
RelayEnum.Type.Door).FirstOrDefault()?.ManualRelayStatus;
if (status is RelayEnum.Status.Off)
_ = RelayPowerOn(obs.PatientId, RelayEnum.Type.Door);
lock (_relayAlarmList)
{
_relayAlarmList.Add(obs);
}
}
}
}
else
{
_logger.LogDebug(
"PatientId: {nObsPatientid}.Open door is Expired. EndAfter {endAfter} Time: {obsTime}",
obs.PatientId, configObs.Alarm.OpenDoor.EndAfter, obs.Time);
}
}
catch (Exception ex)
{
_logger.LogDebug("Exception opening door for patient {patientId}. Exception: {ex}",
obs.PatientId, ex);
throw;
}
}
}
catch (Exception ex)
{
Log.Error("Exception sending alarm: {exMessage}", ex.Message);
}
}
public async Task CalculateAlarmTest(BasePatientObservationValue source, string name)
{
if (source is not PatientObservation obs) return;
//ConfigObservations
var configObs = await _configObservationService.Get(source, true);
if (configObs is { Alarm.Enabled: true })
{
obs.Alarm = configObs.Alarm;
string? alarmSeverityStr = null;
if (configObs.Alarm.Beacon is { Enabled: true })
{
var patient = await _patientService.FindById(obs.PatientId);
if (patient == null)
return;
_logger.LogDebug("PatientId: {nObsPatientid}. Send Beacon alarmName {beaconColor}",
obs.PatientId, configObs.Alarm.Beacon.BeaconColor);
_ = SendBeaconCode(configObs.Alarm.Beacon.BeaconColor, patient);
}
if (configObs.Alarm.Recording is { Enabled: true })
{
_logger.LogDebug("PatientId: {nObsPatientid}. Send Recording {nObsName}", obs.PatientId,
obs.Name);
if (!string.IsNullOrEmpty(alarmSeverityStr) &&
Enum.TryParse(alarmSeverityStr, out AlarmEnum.Severity alarmSeverity))
_ = StartRecording(obs.PatientId, obs.Time, configObs.Alarm.Recording, AlarmEnum.Name.Test,
alarmSeverity, "test description", configObs.Alarm.Recording.EndAfter);
else
_ = StartRecording(obs.PatientId, obs.Time, configObs.Alarm.Recording, AlarmEnum.Name.Test,
AlarmEnum.Severity.Yellow, "test description", configObs.Alarm.Recording.EndAfter);
}
if (configObs.Alarm.OpenDoor is { Enabled: true })
{
_logger.LogDebug("PatientId: {nObsPatientid}. Open door", obs.PatientId);
_ = RelayPowerOn(obs.PatientId, RelayEnum.Type.Door);
}
}
}
private Task SendBeaconCode(AlarmEnum.BeaconColor color, Patient patient)
{
if (!patient.PointOfCareId.HasValue)
{
_logger.LogError("Try to sen beacon code, but no PointOfCareId id is present in the Patient {Patient}",
patient.ToString());
return Task.CompletedTask;
}
switch (color)
{
case AlarmEnum.BeaconColor.Blue:
_lightBeaconService.Value.SendColor(patient.PointOfCareId.Value, LightBeaconColor.Blue);
break;
case AlarmEnum.BeaconColor.Yellow:
_lightBeaconService.Value.SendColor(patient.PointOfCareId.Value, LightBeaconColor.Yellow);
break;
case AlarmEnum.BeaconColor.Red:
_lightBeaconService.Value.SendColor(patient.PointOfCareId.Value, LightBeaconColor.Red);
break;
case AlarmEnum.BeaconColor.None:
_lightBeaconService.Value.SendColor(patient.PointOfCareId.Value, LightBeaconColor.Off);
break;
}
return Task.CompletedTask;
}
/// <summary>
/// </summary>
/// <param name="patientId"></param>
/// <param name="eventTime">Hora de la observación</param>
/// <param name="recording"></param>
/// <param name="alarmName"></param>
/// <param name="alarmDescription"></param>
/// <param name="endAfter"></param>
/// <param name="severity"></param>
/// <param name="type"></param>
private async Task StartRecording(ObjectId patientId, DateTime eventTime, AlarmItem? recording,
AlarmEnum.Name? alarmName, AlarmEnum.Severity severity, string? alarmDescription, int? endAfter,
AlarmEnum.Type type = AlarmEnum.Type.Manual)
{
try
{
var patient = await _patientService.FindById(patientId);
if (patient is not { PointOfCareId: not null }) return;
var poc = await _pocService.FindByIdAllConfig(patient.PointOfCareId.Value);
if (poc == null)
return;
//30 minutos antes y después de la fecha de la observación
var startTime = recording != null ? eventTime.AddSeconds(-recording.StartBefore) : eventTime;
var endDate = endAfter.HasValue ? eventTime.AddSeconds(endAfter.Value) : (DateTime?)null;
await _recordingService.Value.SendRecordingDataToQueue(patient, poc, startTime, endDate,
eventTime, alarmName, severity, alarmDescription, true, type);
}
catch (Exception ex)
{
_logger.LogError(
"Error Starting recording from patientId: {patientId}. eventTime: {eventTime}. AlarmItem: {recording}. alarmSeverity: {alarmSeverity} Error: {ex}",
patientId, eventTime, recording, severity, ex);
}
}
private async Task RelayPowerOn(ObjectId patientId, RelayEnum.Type type)
{
try
{
var patient = await _patientService.FindById(patientId);
if (patient is not { PointOfCareId: not null })
{
Log.Error("can not power on relay because patient: {patientId} not found", patientId);
return;
}
var poc = await _pocService.FindById(patient.PointOfCareId.Value);
if (poc?.Configuration == null) return;
var relayConfig = _relayService.Value.GetRelayByTypeInList(poc.Configuration.RelayIdList, type).FirstOrDefault();
if (relayConfig != null) await _relayService.Value.PowerOn(relayConfig);
}
catch (Exception ex)
{
_logger.LogError("Error Relay Power On from patientId: {patientId}. Error: {ex}", patientId, ex);
}
}
private async void StartTimer()
{
try
{
var pocList = await _pocService.GetAllLocationInfo();
_interval = TimeSpan.FromSeconds(_apiSettings.Value.ExpireAlertIntervalSeconds);
_ = new Timer(async void (_) =>
{
try
{
await _semaphore.WaitAsync(); // Esperar a adquirir el semáforo antes de ejecutar el temporizador
await CheckExpiredAlarms(pocList);
}
catch (Exception e)
{
_logger.LogError("Error in timer execution: {message}", e.Message);
//throw new Exception("Error in timer execution", e);
}
finally
{
_semaphore.Release(); // Liberar el semáforo después de ejecutar el temporizador
}
}, null, TimeSpan.Zero, _interval);
}
catch (Exception e)
{
_logger.LogError("Error starting timer: {message}", e.Message);
//throw new Exception("Error starting timer", e);
}
}
private async Task CheckExpiredAlarms(List<PointOfCare> pocList)
{
_logger.LogTrace("Checking Expired Alarms Started");
// Iniciar ambas tareas de forma asincrónica
var checkBeaconsTask = CheckExpiredBeaconsAsync(pocList);
var checkRelayTask = CheckExpiredRelayAsync();
// Esperar a que ambas tareas completen
await Task.WhenAll(checkBeaconsTask, checkRelayTask);
_logger.LogTrace("Checking Expired Alarms Finished");
}
private readonly SemaphoreSlim _beaconListSemaphore = new(1, 1);
private async Task CheckExpiredBeaconsAsync(List<PointOfCare> pocList)
{
await _beaconListSemaphore.WaitAsync();
try
{
if (!_beaconAlarmList.Any())
//var pocList = await _pocService.GetAllLocationInfo();
if (pocList.Any())
{
var tasks = pocList.Select(async poc =>
{
await _lightBeaconService.Value.SendColor(poc, LightBeaconColor.Off);
});
await Task.WhenAll(tasks);
return;
}
}
finally
{
_beaconListSemaphore.Release();
}
var now = DateTime.UtcNow;
List<PatientObservation> updatedList = [];
List<Task> ledTasks = [];
await _beaconListSemaphore.WaitAsync();
try
{
foreach (var obsGroup in _beaconAlarmList.GroupBy(o => o.PatientId))
{
var nonExpiredObs = obsGroup.Where(obs =>
obs.Alarm is { Beacon: not null } &&
(obs.Time.Kind == DateTimeKind.Utc ? obs.Time : obs.Time.ToUniversalTime())
.AddSeconds(obs.Alarm.Beacon.EndAfter) >= now
).ToList();
if (!nonExpiredObs.Any())
{
var patient = obsGroup.FirstOrDefault()?.Patient;
if (patient is { PointOfCareId: not null })
ledTasks.Add(_lightBeaconService.Value.SendColor(patient.PointOfCareId.Value,
LightBeaconColor.Off));
}
else
{
updatedList.AddRange(nonExpiredObs);
}
}
_beaconAlarmList = updatedList;
}
finally
{
_beaconListSemaphore.Release();
}
await Task.WhenAll(ledTasks);
}
private async Task CheckExpiredRelayAsync()
{
var now = DateTime.UtcNow;
IEnumerable<IGrouping<ObjectId, PatientObservation>> groupedByPatientId;
lock (_relayAlarmList)
{
groupedByPatientId = _relayAlarmList.GroupBy(o => o.PatientId);
}
foreach (var obsGroup in groupedByPatientId)
{
var patient = await _patientService.FindById(obsGroup.Key);
if (patient is not { PointOfCareId: null })
continue;
var nonExpiredObs = obsGroup.Where(obs =>
obs.Alarm is { OpenDoor: not null } &&
(obs.Time.Kind == DateTimeKind.Utc ? obs.Time : obs.Time.ToUniversalTime()).AddSeconds(
obs.Alarm.OpenDoor.EndAfter) >= now
).ToList();
// Apagar el LED si no hay observaciones no expiradas
if (!nonExpiredObs.Any())
{
//buscamos en PoCSettings si está activado de forma manual
var pocSettings = await _pocService.FindById(patient.PointOfCareId!.Value);
var relays = _relayService.Value.GetRelayInList(pocSettings?.Configuration?.RelayIdList);
foreach (var relay in relays)
{
// Verificamos si NO tiene un estado manual activo (On)
// Si el estado es null o es diferente de On, lo apagamos
if (relay.ManualRelayStatus != null && relay.ManualRelayStatus != RelayEnum.Status.On)
{
await _relayService.Value.PowerOff(relay);
}
}
}
// Reemplazar la lista original con las observaciones no expiradas
lock (_relayAlarmList)
{
_relayAlarmList.RemoveAll(obs => obs.PatientId == obsGroup.Key);
_relayAlarmList.AddRange(nonExpiredObs);
}
}
}
}
#endregion
@@ -0,0 +1,17 @@
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.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Services;
public class AlertValuesService(IConfigObservationRepository alertValueRepository) : IAlertValuesService
{
public async Task<ConfigObservation?> FindByKey(ObjectId key)
{
return await alertValueRepository.FindById(key) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
}
}
@@ -0,0 +1,362 @@
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.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
using Patient = adas_core.Domain.Models.MongoModels.Patient;
namespace adas_core.Application.Services;
public class AppointmentService(
IAppointmentRepository appointmentRepository,
IAppointmentArchiveRepository appointmentArchiveRepository,
Lazy<IPatientService> patientService,
Lazy<IObservationService> observationService,
IDiagnosisService diagnosisService,
IUnitService unitService,
IOptions<ApiSettings> apiSettings,
IOptions<CacheSettings> cacheSettings,
ILogger<AppointmentService> logger,
IHttpContextAccessor httpContextAccessor,
ILocalAuditService auditService,
IPointOfCareService pointOfCareService,
ISubscribersService subscribersService,
IClientMessageService clientMessageService,
ICacheService cacheService)
: IAppointmentService
{
private readonly bool _createPatientWithSiu = apiSettings.Value.CreatePatientWithSiu;
private readonly CacheSettings? _cacheSettings = cacheSettings.Value ;
public async Task SaveRequest(ApiRequest apiRequest)
{
if (string.IsNullOrEmpty(apiRequest.PatientNumber))
{
logger.LogDebug("Patient is null");
throw new ApiRequestException("Patient number is null");
}
logger.LogDebug("patientNumber: {apiRequestPatientNumber} location: {apiRequestLocation}",
apiRequest.PatientNumber, apiRequest.Location);
logger.LogDebug("RequestType: {apiRequestType}", apiRequest.Type);
var patient = await patientService.Value.FindByPatientNumber(apiRequest.PatientNumber);
if (patient == null)
{
// PATIENT NOT FOUND
logger.LogWarning("Patient not Found. {patientNumber}", apiRequest.PatientNumber);
if (!_createPatientWithSiu) return; // IGNORE
patient = await patientService.Value.CreatePatientFromRequest(apiRequest, true);
}
await ProcessApiRequest(apiRequest, patient);
if (patient != null)
{
if (apiRequest is { Observations: not null, ObservationData: not null })
observationService.Value.ProcessObservations(apiRequest.Observations, patient,
apiRequest.MessageTime, apiRequest.ObservationData);
else
logger.LogWarning("Observations not Found. {patientNumber} ", patient.PatientNumber);
if (apiRequest.Diagnosis != null)
_ = diagnosisService.ProcessDiagnosis(apiRequest.Diagnosis, patient, apiRequest.MessageTime);
else
logger.LogWarning("Diagnosis not Found. {patientNumber}", patient.PatientNumber);
}
}
public async Task ProcessApiRequest(ApiRequest apiRequest, Patient? patient)
{
if (patient == null)
return;
var unitConfig = await unitService.FindById(patient.UnitId);
if (!Hl7Utils.ManageAutoAdt(unitConfig, null, logger, "ORU")) return;
apiRequest.Appointments ??= [];
if (apiRequest.Appointment != null) apiRequest.Appointments.Add(apiRequest.Appointment);
//apiRequest.appointments.ForEach(ap =>
foreach (var ap in apiRequest.Appointments)
{
if (ap.VisitNumber == null || patient.Person == null) return;
ap.PatientId = patient.Id;
ap.Patient = patient.Person;
var apdb = ap.VisitNumber != null
? await appointmentRepository.FindByPatientAndVisitNumber(ap.PatientId, ap.VisitNumber)
: null;
apdb ??= await appointmentRepository.FindByPatientAndReason(ap.PatientId, ap.AppointmentReason);
if (apdb != null && DateTime.Compare(apdb.UpdateTime, apiRequest.MessageTime) > 0) return;
// TODO: Aux
var oldAp = await auditService.DeepCopyAsync(ap);
switch (apiRequest.Type)
{
//* SIU_S12 - Notification of new appointment booking
//* SIU_S13 - Notification of Appointment Rescheduling
//* SIU_S14 - Notification of Appointment Modification
//* SIU_S18 - Notification of Addition of Service/Resource on Appointment
//* SIU_S19 - Notification of Modification of Service/Resource on Appointment
//* SIU_S20 - Notification of Cancellation of Service/Resource on Appointment
//* SIU_S21 - Notification of Discontinuation of Service/Resource on Appointment
//* SIU_S22 - Notification of Deletion of Service/Resource on Appointment
case "SIU_S12":
case "SIU_S13":
case "SIU_S14":
case "SIU_S18":
case "SIU_S19":
case "SIU_S20":
case "SIU_S21":
case "SIU_S22":
ap.UpdateTime = apiRequest.MessageTime;
if (apdb != null)
{
ap.Id = apdb.Id;
ap.CreateTime = apdb.CreateTime;
ap.AppointmentOperationType = OperationType.UpdatedAppointment;
ap.ApplyResourceGroups(apdb);
await appointmentRepository.Update(ap);
// Invalidar CACHE (colección completa)
await cacheService.DeleteObjectAsync(CacheKeys.PatientAppointmentsToday(ap.PatientId));
//SendBroadcast(ap, OperationType.updatedAppointment);
}
else
{
ap.CreateTime = apiRequest.MessageTime;
ap.AppointmentOperationType = OperationType.NewAppointment;
ap.ApplyResourceGroups();
await appointmentRepository.InsertOneAsync(ap);
//SendBroadcast(ap, OperationType.newAppointment);
}
break;
//* SIU_S15 - Notification of Appointment Cancellation
//* SIU_S16 - Notification of Appointment Discontinuation
//* SIU_S17 - Notification of Appointment Deletion
case "SIU_S15":
case "SIU_S16":
case "SIU_S17":
ap.UpdateTime = apiRequest.MessageTime;
if (apdb != null)
{
ap.Id = apdb.Id;
ap.CreateTime = apdb.CreateTime;
ap.AppointmentOperationType = OperationType.CanceledAppointment;
ap.ApplyResourceGroups(apdb);
await appointmentRepository.Update(ap);
await cacheService.DeleteObjectAsync(CacheKeys.PatientAppointmentsToday(ap.PatientId));
}
else
{
ap.CreateTime = apiRequest.MessageTime;
ap.AppointmentOperationType = OperationType.CanceledAppointment;
ap.ApplyResourceGroups();
await appointmentRepository.InsertOneAsync(ap);
}
//SendBroadcast(ap, OperationType.canceledAppointment);
break;
//* SIU_S23 - Notification of Blocked Schedule Time Slot(S)
//* SIU_S24 - Notification of Opened (un-blocked) Schedule Time Slot(s)
//* SIU_S26 - Notification That Patient Did Not Show Up for Scheduled Appointment
default:
ap.UpdateTime = apiRequest.MessageTime;
if (apdb != null)
{
ap.Id = apdb.Id;
ap.CreateTime = apdb.CreateTime;
//ap.appointmentOperationType = OperationType.updatedAppointment;
ap.ApplyResourceGroups(apdb);
await appointmentRepository.Update(ap);
await cacheService.DeleteObjectAsync(CacheKeys.PatientAppointmentsToday(ap.PatientId));
//SendBroadcast(ap, ap.appointmentOperationType);
}
else
{
ap.CreateTime = apiRequest.MessageTime;
//ap.appointmentOperationType = OperationType.newAppointment;
ap.ApplyResourceGroups();
await appointmentRepository.InsertOneAsync(ap);
//SendBroadcast(ap, ap.appointmentOperationType);
}
break;
}
if (apdb != null)
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, apdb, ap);
else await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, ap);
if (ap.AppointmentOperationType.HasValue)
await SendBroadcast(ap, ap.AppointmentOperationType);
}
}
public Task SaveRequestAsync(ApiRequest apiRequest)
{
return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest); }));
}
public async Task Archive(Patient patient)
{
await ArchiveByPatientId(patient.Id);
}
public async Task ArchiveByPatientId(ObjectId id)
{
logger.LogDebug("Archive Appointments by patientId {id}", id);
using (var cursor = await FindByPatientIdAsync(id))
{
while (await cursor.MoveNextAsync())
foreach (var current in cursor.Current)
await appointmentArchiveRepository.InsertOneAsync(current);
}
await DeleteByPatientId(id);
}
public async Task<List<PatientAppointment>> GetByPatient(ObjectId patientId)
{
return await appointmentRepository.GetByPatient(patientId);
}
public async Task<List<PatientAppointment>> GetTodayByPatient(
ObjectId patientId,
CancellationToken ct = default)
{
// Obtener clave + TTL según CacheSettings
var (key, ttl) = CacheKeys.PatientAppointmentsTodayKeyWithTtl(_cacheSettings, patientId);
// Cachear la lista RAW de citas del paciente (sin filtrar)
var allAppointments = await cacheService.GetOrSetObjectAsync(
key,
async () =>
{
// 1 - Consultar todas las citas del paciente
var list = await appointmentRepository.GetByPatient(patientId);
// 2 - Devuelve RAW (List<PatientAppointment>), nada filtrado
return list;
},
ttl,
ct);
// 3 - Ahora filtramos solo las de "hoy"
var today = DateTime.UtcNow.Date;
var todayAppointments = allAppointments
.Where(a => a.Timings.Any(t =>
t.StartTime.HasValue &&
t.StartTime.Value.Date == today))
.ToList();
return todayAppointments;
}
public async Task<List<PatientAppointment>> GetTodayByPoc(
ObjectId pocId,
CancellationToken ct = default)
{
var poc = await pointOfCareService.FindById(pocId);
if(poc == null) return [];
// Obtener clave + TTL según CacheSettings
var (key, ttl) = CacheKeys.PocAppointmentsTodayKeyWithTtl(_cacheSettings, pocId);
// Cachear la lista RAW de citas del pointOfCare (sin filtrar)
var allAppointments = await cacheService.GetOrSetObjectAsync(
key,
async () =>
{
// 1 - Consultar todas las citas del pointOfCare
var list = await appointmentRepository.FindByPoC(poc);
// 2 - Devuelve RAW (List<PatientAppointment>), nada filtrado
return list;
},
ttl,
ct);
// 3 - Ahora filtramos solo las de "hoy"
var today = DateTime.UtcNow.Date;
var todayAppointments = allAppointments
.Where(a => a.Timings.Any(t =>
t.StartTime.HasValue &&
t.StartTime.Value.Date == today))
.ToList();
return todayAppointments;
}
public Task<IAsyncCursor<PatientAppointment>> FindByPatientIdAsync(ObjectId patientId)
{
return appointmentRepository.FindByPatientIdAsync(patientId);
}
public async Task<List<PatientAppointment>> FindByLocation(PatientLocation location)
{
return await appointmentRepository.FindByLocation(location);
}
public async Task DeleteByPatientId(ObjectId id)
{
var patientApp = await FindByPatientIdAsync(id);
logger.LogDebug("Delete Appointments by Patient Id {id}", id);
await appointmentRepository.DeleteByPatientId(id);
// Invalidar CACHE (colección completa)
await cacheService.DeleteByPatternAsync(CacheKeyPatterns.ForEntity(CacheEnum.EntityType.Appointments));
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, patientApp, null);
}
public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
{
await appointmentRepository.UpdateManyObjectId(nameId, id, oldId);
}
private async Task SendBroadcast(PatientAppointment appointment, OperationType? operationType)
{
//RECORRE LOS DIFERENTES LOCATIONS DE LA CITA
foreach (var resourceGroup in appointment.ResourceGroups)
if (resourceGroup.Locations != null)
foreach (var location in resourceGroup.Locations)
// TODO
if (!string.IsNullOrEmpty(location.UnitName) && !string.IsNullOrEmpty(location.Bed))
{
var unit = await unitService.FindByName(location.UnitName);
if (unit == null) continue;
var poc = await pointOfCareService.FindByBedAndUnitId(location.Bed, unit.Id);
if (poc == null) continue;
var subscribers = subscribersService.GetSubscribers().Where(s =>
!s.LocationIds.IsNullOrEmpty() && s.LocationIds.Any(c =>
c == poc.Id
)).ToList();
foreach (var subscriber in subscribers)
_ = clientMessageService.SendAsync(subscriber.Id, operationType, appointment);
}
}
}
@@ -0,0 +1,62 @@
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Models.MongoModels;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
namespace adas_core.Application.Services;
public class ArchivePatientCarePlanService(
IArchivePatientCarePlanRepository archivedPatientRepository,
ILogger<ArchivePatientCarePlanService> logger,
IHttpContextAccessor httpContextAccessor,
ILocalAuditService auditService)
: IArchivePatientCarePlanService
{
#region Create
public async Task<PatientCarePlan?> InsertOneAsync(PatientCarePlan patient)
{
await archivedPatientRepository.InsertOneAsync(patient);
logger.LogInformation(
"Inserted archived patient care plan for patientId: {PatientId}, patientNumber: {PatientNumber}",
patient.PatientId, patient.PatientNumber);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, patient);
return patient;
}
public async Task InsertManyAsync(List<PatientCarePlan> patientCarePla)
{
await archivedPatientRepository.InsertManyAsync(patientCarePla);
logger.LogInformation("Inserted {Count} archived patient care plans", patientCarePla.Count);
foreach (var patient in patientCarePla)
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, patient);
}
#endregion
#region Read
public async Task<List<PatientCarePlan>?> FindByPatientId(ObjectId patientId)
{
return await archivedPatientRepository.FindByPatientId(patientId);
}
public async Task<List<PatientCarePlan>?> FindByPatientId(string patientId)
{
return await archivedPatientRepository.FindByPatientId(patientId);
}
public async Task<List<PatientCarePlan>?> FindByPatientNumber(string patientId)
{
return await archivedPatientRepository.FindByPatientNumber(patientId);
}
public Task<List<PatientCarePlan>> FindAll()
{
return archivedPatientRepository.FindAll();
}
#endregion
}
@@ -0,0 +1,15 @@
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Models;
using MongoDB.Bson;
namespace adas_core.Application.Services;
public class ArchivePatientObservationsService(IObservationArchiveRepository archivedPatientObservationService)
: IArchivedPatientObservationService
{
public async Task<List<PatientObservation>> FindArchivedPatientObservationsFromPatient(ObjectId patientId)
{
return await archivedPatientObservationService.FindAllFromPatient(patientId);
}
}
@@ -0,0 +1,13 @@
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Models.MongoModels;
namespace adas_core.Application.Services;
public class ArchivedPatientService(IPatientArchiveRepository archivedPatientRepository) : IArchivedPatientService
{
public async Task<List<Patient>> FindAllPatients()
{
return await archivedPatientRepository.FindAll();
}
}
@@ -0,0 +1,15 @@
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Models;
using MongoDB.Bson;
namespace adas_core.Application.Services;
public class ArchivedPatientTreatmentService(ITreatmentArchiveRepository archivedPatientTreatmentService)
: IArchivedPatientTreatmentService
{
public async Task<List<PatientTreatment>> FindAllPatientTreatmentsByPatient(ObjectId patientId)
{
return await archivedPatientTreatmentService.FindAllFromPatient(patientId);
}
}
@@ -0,0 +1,137 @@
using System.Text;
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using adas_core.Domain.Utils;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using Newtonsoft.Json;
namespace adas_core.Application.Services;
public class AuthService : IAuthService
{
private readonly IAuthorityRepository _authorityRepository;
private readonly ILogger<AuthService> _logger;
private readonly RecordingSettings _recordingSettings;
//private LoginResponse? _loginResponse;
public AuthService(IOptions<RecordingSettings> recordingSettings, ILogger<AuthService> logger,
IAuthorityRepository authorityRepository)
{
_recordingSettings = recordingSettings.Value ??
throw new Exception("RecordingSettings must be defined on appSettings");
_logger = logger;
_authorityRepository = authorityRepository;
_ = InstanceAuthUtils();
}
public async Task<LoginResponse?> GetLoginResponse()
{
try
{
if (_recordingSettings.RecordingApiUrl.IsEmpty())
{
_logger.LogInformation("Url not defined to get token on AuthUtils...");
return null;
}
var user = new
{
Username = _recordingSettings.RecordingOrApiClientId,
Password = _recordingSettings.RecordingOrApiClientSecret
};
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
};
var client = new HttpClient(handler);
var json = JsonConvert.SerializeObject(user);
var data = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync($"{_recordingSettings.RecordingApiUrl}/users/login", data);
var respToken = response.IsSuccessStatusCode ? await response.Content.ReadAsStringAsync() : null;
if (respToken != null)
{
var ton = JsonConvert.DeserializeObject<LoginResponse>(respToken);
if (ton != null && !ton.Token.IsEmpty())
{
AuthUtils.Instance.LoginResponse = ton;
return ton;
}
}
return null;
}
catch (Exception e)
{
_logger.LogError("Error trying to get token: exception: {eMessage}", e.Message);
return null;
}
}
public async Task<string> GetToken()
{
var loginResponse = AuthUtils.Instance.GetLoginResponse();
if (!loginResponse.Token.IsEmptyOrWhiteSpace() && !loginResponse.IsExpired()) return loginResponse.Token;
var resp = await GetLoginResponse();
if (resp != null) return resp.Token;
_logger.LogWarning("Failed Getting token check recordingSettings for user and pass");
return "";
}
public async Task<List<Authorization>> GetByUnitId(ObjectId unitId)
{
return await _authorityRepository.GetByUnitId(unitId);
}
public async Task<List<Authorization>> GetUserAuthorities(ObjectId id)
{
return await _authorityRepository.GetUserAuthorities(id);
}
public async Task<bool> DeleteByUnitId(ObjectId unitId)
{
return await _authorityRepository.DeleteAllAuthoritiesByUnit(unitId);
}
public async Task<bool> DeleteByDisplayId(ObjectId displayId)
{
return await _authorityRepository.DeleteAllAuthoritiesByDisplay(displayId);
}
private async Task InstanceAuthUtils()
{
if (_recordingSettings.RecordingApiUrl.IsEmpty())
{
_logger.LogInformation(
"RecordingOrApiUrl not defined on appsettings RecordingSettings AuthUtils not instanciated");
return;
}
if (AuthUtils.Instance.GetLoginResponse().Token.IsEmpty() || AuthUtils.Instance.GetLoginResponse().IsExpired())
{
_logger.LogInformation("Token is not generated or is expired sending request for new token");
var newLoginResponse = await GetLoginResponse();
if (newLoginResponse == null)
{
_logger.LogError("Token request is null check recordingSettings for user, pass and authorities");
}
else
{
_logger.LogInformation("New Token generated at UTC DATE: {DateTime} exires at: {newLoginResponse}",
DateTime.UtcNow, newLoginResponse.Expiration);
AuthUtils.Instance.LoginResponse = newLoginResponse;
}
}
}
}
@@ -0,0 +1,111 @@
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Utils;
using MongoDB.Bson;
namespace adas_core.Application.Services.Caching
{
/// <summary>
/// Orquestador de caché. Selecciona el backend (Redis, InMemory, None)
/// según CacheSettings y la entidad del key.
/// Implementa ICacheService y delega en el backend elegido.
/// </summary>
public class CacheDispatcher(
RedisService redis,
CacheService memory,
NoCacheService noop,
CacheSettings cacheSettings)
: ICacheService
{
// Selección de backend
private ICacheService SelectBackend(string key)
{
var entity = CacheKeyClassifier.Classify(key);
var mode = entity switch
{
CacheEnum.EntityType.Patients => cacheSettings.Patients,
CacheEnum.EntityType.Displays => cacheSettings.Displays,
CacheEnum.EntityType.PumpObservations => cacheSettings.PumpObservations,
CacheEnum.EntityType.Appointments => cacheSettings.Appointments,
CacheEnum.EntityType.GroupedObservations => cacheSettings.GroupedObservations,
_ => CacheEnum.Mode.Cache
};
return mode switch
{
CacheEnum.Mode.Redis => redis,
CacheEnum.Mode.Cache => memory,
_ => noop
};
}
// Para GroupedObservations generamos la misma clave compuesta que el resto de servicios,
// de modo que el clasificador y la política de TTL funcionen igual.
private static string BuildGroupedKey(GroupedField gf, ObjectId patientId)
=> $"GroupedObs:{patientId}:{gf.Name}";
private ICacheService SelectBackend(GroupedField groupedField, ObjectId patientId)
=> SelectBackend(BuildGroupedKey(groupedField, patientId));
// GetOrSet (KEY string)
public Task<T> GetOrSetObjectAsync<T>(
string key,
Func<Task<T>> factory,
TimeSpan? ttl = null,
CancellationToken cancellationToken = default)
=> SelectBackend(key).GetOrSetObjectAsync(key, factory, ttl, cancellationToken);
public Task<string?> GetOrSetValueAsync(
string key,
Func<Task<string>> loader,
TimeSpan? ttl = null)
=> SelectBackend(key).GetOrSetValueAsync(key, loader, ttl);
// GetOrSet (GroupedField + PatientId)
public Task<T> GetOrSetObjectAsync<T>(
GroupedField groupedField,
ObjectId patientId,
Func<Task<T>> factory,
TimeSpan? ttl = null,
CancellationToken cancellationToken = default)
=> SelectBackend(groupedField, patientId)
.GetOrSetObjectAsync(groupedField, patientId, factory, ttl, cancellationToken);
// Set/Get básicos
public void SetValue(string key, string value)
=> SelectBackend(key).SetValue(key, value);
public string? GetValue(string key)
=> SelectBackend(key).GetValue(key);
public Task<T?> GetObjectAsync<T>(string key, bool upd = true)
=> SelectBackend(key).GetObjectAsync<T>(key, upd);
public Task SetObjectAsync<T>(string key, T obj, bool upd = true)
=> SelectBackend(key).SetObjectAsync(key, obj, upd);
public Task<T?> GetObjectAsync<T>(string key, TimeSpan? ttl, bool upd)
=> SelectBackend(key).GetObjectAsync<T>(key, ttl, upd);
public Task SetObjectAsync<T>(string key, T obj, TimeSpan? ttl, bool upd)
=> SelectBackend(key).SetObjectAsync(key, obj, ttl, upd);
public Task DeleteObjectAsync(string key)
=> SelectBackend(key).DeleteObjectAsync(key);
public async Task<long> DeleteByPatternAsync(string pattern)
=> await redis.DeleteByPatternAsync(pattern) + await memory.DeleteByPatternAsync(pattern);
public void CleanCache()
{
memory.CleanCache();
redis.CleanCache();
}
}
}
@@ -0,0 +1,145 @@
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Models.GroupedObservations;
using MongoDB.Bson;
using System.Collections.Concurrent;
namespace adas_core.Application.Services.Caching
{
/// <summary>
/// Implementación de caché en memoria con soporte de locking seguro
/// mediante LockManagerService + InMemoryLockProvider.
/// Compatible con la interfaz ICacheService incluyendo GetOrSet.
/// </summary>
public class CacheService(LockManagerService lockManager) : ICacheService
{
private readonly ConcurrentDictionary<string, object> _mem = new();
// HELPERS
private static string BuildGroupedKey(GroupedField gf, ObjectId patientId)
=> $"GroupedObs:{patientId}:{gf.Name}";
// GET OR SET (string key)
public async Task<T> GetOrSetObjectAsync<T>(
string key,
Func<Task<T>> factory,
TimeSpan? ttl = null,
CancellationToken cancellationToken = default)
{
// FAST PATH
if (_mem.TryGetValue(key, out var existing))
return (T)existing;
// LOCKED PATH
return await lockManager.WithLockAsync(
$"getorset:{key}",
TimeSpan.FromSeconds(5),
async () =>
{
if (_mem.TryGetValue(key, out var again))
return (T)again;
var created = await factory();
if (created != null!)
_mem[key] = created;
return created!;
},
cancellationToken);
}
public async Task<string?> GetOrSetValueAsync(
string key,
Func<Task<string>> loader,
TimeSpan? ttlOverride = null)
{
var result = await GetOrSetObjectAsync(key, loader, ttlOverride);
return (string?)result;
}
// GET OR SET (GroupedField + patientId)
public async Task<T> GetOrSetObjectAsync<T>(
GroupedField groupedField,
ObjectId patientId,
Func<Task<T>> factory,
TimeSpan? ttl = null,
CancellationToken cancellationToken = default)
{
var key = BuildGroupedKey(groupedField, patientId);
if (_mem.TryGetValue(key, out var existing))
return (T)existing;
return await lockManager.WithLockAsync(
$"getorset:{key}",
TimeSpan.FromSeconds(5),
async () =>
{
if (_mem.TryGetValue(key, out var again))
return (T)again;
var created = await factory();
if (created != null!)
_mem[key] = created;
return created!;
},
cancellationToken);
}
// GET / SET
public void SetValue(string key, string value)
=> _mem[key] = value;
public string? GetValue(string key)
=> _mem.TryGetValue(key, out var v) ? v.ToString() : null;
public Task<T?> GetObjectAsync<T>(string key, bool updateExpiration = true)
{
return Task.FromResult(
_mem.TryGetValue(key, out var v) ? (T?)v : default
);
}
public Task SetObjectAsync<T>(string key, T obj, bool updateExpiration = true)
{
_mem[key] = obj!;
return Task.CompletedTask;
}
public Task<T?> GetObjectAsync<T>(string key, TimeSpan? ttlOverride, bool upd)
=> GetObjectAsync<T>(key, upd);
public Task SetObjectAsync<T>(string key, T obj, TimeSpan? ttlOverride, bool upd)
=> SetObjectAsync(key, obj, upd);
// DELETE / CLEAN
public Task DeleteObjectAsync(string key)
{
_mem.TryRemove(key, out _);
return Task.CompletedTask;
}
public Task<long> DeleteByPatternAsync(string pattern)
{
var p = pattern.Replace("*", "");
var keys = _mem.Keys.Where(k => k.Contains(p)).ToList();
long removed = 0;
foreach (var k in keys)
if (_mem.TryRemove(k, out _))
removed++;
return Task.FromResult(removed);
}
public void CleanCache() => _mem.Clear();
}
}
@@ -0,0 +1,62 @@
using System.Collections.Concurrent;
using adas_core.Application.Services.Interfaces;
namespace adas_core.Application.Services.Caching
{
/// <summary>
/// Implementación local del sistema de locking por clave.
/// Se basa en SemaphoreSlim y solo controla concurrencia DENTRO del proceso.
/// Para CacheService (in-memory).
/// </summary>
public class InMemoryLockProvider : ILockProvider
{
private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new(StringComparer.Ordinal);
/// <summary>
/// Crea u obtiene un semáforo asociado a la clave.
/// </summary>
private SemaphoreSlim GetOrCreate(string key)
{
return _locks.GetOrAdd(key, _ => new SemaphoreSlim(1, 1));
}
/// <summary>
/// Intenta adquirir el lock por clave.
/// </summary>
public async Task<bool> AcquireAsync(string key, TimeSpan timeout)
{
var sem = GetOrCreate(key);
try
{
return await sem.WaitAsync(timeout).ConfigureAwait(false);
}
catch (ObjectDisposedException)
{
// Si el semáforo se eliminó entre medio, creamos uno nuevo.
_locks.TryRemove(key, out _);
return await GetOrCreate(key).WaitAsync(timeout).ConfigureAwait(false);
}
}
/// <summary>
/// Libera el lock (si existe y no está ya liberado).
/// </summary>
public Task ReleaseAsync(string key)
{
if (!_locks.TryGetValue(key, out var sem))
return Task.CompletedTask;
try
{
sem.Release();
}
catch (SemaphoreFullException)
{
// Idempotencia: ignoramos exceso de releases
}
return Task.CompletedTask;
}
}
}
@@ -0,0 +1,93 @@
using adas_core.Application.Services.Interfaces;
using Microsoft.Extensions.Logging;
namespace adas_core.Application.Services.Caching
{
/// <summary>
/// Servicio que ejecuta acciones dentro de una sección crítica controlada por lock.
/// CacheService lo usará para evitar condiciones de carrera en GetOrSet.
/// Funciona igual para locks locales o distribuidos.
/// </summary>
public class LockManagerService(
ILogger<LockManagerService> logger,
ILockProvider provider)
{
/// <summary>
/// Ejecuta una función que devuelve un valor bajo un lock por clave.
/// </summary>
public async Task<T> WithLockAsync<T>(
string key,
TimeSpan timeout,
Func<Task<T>> action,
CancellationToken cancellationToken = default)
{
logger.LogDebug(
"LockManager — intentando adquirir lock para key={Key} timeout={TimeoutMs}ms",
key, timeout.TotalMilliseconds);
var acquired = false;
try
{
acquired = await provider.AcquireAsync(key, timeout).ConfigureAwait(false);
if (!acquired)
{
logger.LogWarning(
"LockManager — timeout al adquirir lock para key={Key} tras {TimeoutMs}ms",
key, timeout.TotalMilliseconds);
throw new TimeoutException($"No se pudo adquirir el lock para '{key}'");
}
logger.LogDebug(
"LockManager — lock adquirido correctamente para key={Key}",
key);
return await action().ConfigureAwait(false);
}
finally
{
if (acquired)
{
await SafeReleaseAsync(key);
}
}
}
/// <summary>
/// Version Task (sin valor).
/// </summary>
public Task WithLockAsync(
string key,
TimeSpan timeout,
Func<Task> action,
CancellationToken cancellationToken = default)
{
return WithLockAsync<object?>(
key,
timeout,
async () =>
{
await action();
return null;
},
cancellationToken);
}
/// <summary>
/// Libera el lock y registra posibles errores sin interrumpir el flujo.
/// </summary>
private async Task SafeReleaseAsync(string key)
{
try
{
await provider.ReleaseAsync(key).ConfigureAwait(false);
logger.LogDebug("LockManager — key={Key} lock deleted ", key);
}
catch (Exception ex)
{
logger.LogError(ex, "LockManager — key={Key} error deleting lock", key);
}
}
}
}
@@ -0,0 +1,97 @@
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Models.GroupedObservations;
using MongoDB.Bson;
namespace adas_core.Application.Services.Caching
{
/// <summary>
/// Implementación nula de ICacheService.
/// No almacena nada, no devuelve nada y no interfiere con el flujo.
/// Se usa cuando el CacheMode es "None".
/// </summary>
public class NoCacheService : ICacheService
{
public void SetValue(string key, string value)
{
// No hacer nada
}
public string? GetValue(string key)
{
return null;
}
public Task<T?> GetObjectAsync<T>(string key, bool updateExpiration = true)
{
return Task.FromResult<T?>(default);
}
public Task SetObjectAsync<T>(string key, T obj, bool updateExpiration = true)
{
return Task.CompletedTask;
}
public Task<T?> GetObjectAsync<T>(string key, TimeSpan? ttlOverride, bool updateExpiration)
{
return Task.FromResult<T?>(default);
}
public Task SetObjectAsync<T>(string key, T obj, TimeSpan? ttlOverride, bool updateExpiration)
{
return Task.CompletedTask;
}
public Task<long> DeleteByPatternAsync(string pattern)
{
return Task.FromResult(0L);
}
public Task DeleteObjectAsync(string key)
{
return Task.CompletedTask;
}
public void CleanCache()
{
// Nada que limpiar
}
// ============================================================
// GET OR SET - STRING KEY
// ============================================================
public async Task<T> GetOrSetObjectAsync<T>(
string key,
Func<Task<T>> factory,
TimeSpan? ttl = null,
CancellationToken cancellationToken = default)
{
// En modo NONE no hay caché → siempre ejecutar factory
return await factory();
}
public async Task<string?> GetOrSetValueAsync(
string key,
Func<Task<string>> loader,
TimeSpan? ttl = null)
{
var result = await loader();
return (string?)result;
}
// ============================================================
// GET OR SET - GroupedField + patientId
// ============================================================
public async Task<T> GetOrSetObjectAsync<T>(
GroupedField groupedField,
ObjectId patientId,
Func<Task<T>> factory,
TimeSpan? ttl = null,
CancellationToken cancellationToken = default)
{
// Igual que arriba: en modo NONE no hay caché
return await factory();
}
}
}
@@ -0,0 +1,67 @@
using StackExchange.Redis;
using System.Collections.Concurrent;
using adas_core.Application.Services.Interfaces;
namespace adas_core.Application.Services.Caching
{
/// <summary>
/// Lock distribuido en Redis. Usa token por adquisición (owner)
/// y liberación segura con script Lua: borra la key solo si el valor coincide.
/// </summary>
public class RedisLockProvider(Func<IDatabase?> getDatabase) : ILockProvider
{
private readonly string _prefix = "lock:";
private readonly TimeSpan _ttl = TimeSpan.FromSeconds(5);
private readonly TimeSpan _retryDelay = TimeSpan.FromMilliseconds(50);
private static readonly string LuaReleaseScript = @"
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end";
private readonly ConcurrentDictionary<string, string> _tokens =
new(StringComparer.Ordinal);
public async Task<bool> AcquireAsync(string key, TimeSpan timeout)
{
var redis = getDatabase();
if (redis is null) return false;
var redisKey = (RedisKey)(_prefix + key);
var token = Guid.NewGuid().ToString("N");
var end = DateTime.UtcNow.Add(timeout);
while (DateTime.UtcNow < end)
{
if (await redis.StringSetAsync(redisKey, token, _ttl, When.NotExists))
{
_tokens[key] = token;
return true;
}
await Task.Delay(_retryDelay);
}
return false;
}
public async Task ReleaseAsync(string key)
{
if (!_tokens.TryRemove(key, out var token))
return;
var redis = getDatabase();
if (redis is null) return;
var redisKey = (RedisKey)(_prefix + key);
await redis.ScriptEvaluateAsync(
LuaReleaseScript,
[redisKey],
[token]
);
}
}
}
@@ -0,0 +1,275 @@
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Utils;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.GroupedObservations;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using MongoDB.Bson;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
using StackExchange.Redis;
namespace adas_core.Application.Services.Caching
{
public class RedisService : ICacheService
{
private readonly ILogger<RedisService> _logger;
private readonly CacheSettings _cacheSettings;
private readonly LockManagerService _lockManager;
private ConnectionMultiplexer? _connection;
private IDatabase? _database;
private IServer? _server;
public IDatabase? Database => _database;
private bool _isRedisAvailable;
public RedisService(
IOptions<CacheSettings> options,
ILogger<RedisService> logger,
LockManagerService lockManager)
{
_cacheSettings = options.Value;
_logger = logger;
_lockManager = lockManager;
if (!string.IsNullOrEmpty(_cacheSettings.Redis.ConnectionString))
_ = InitializeRedisConnectionAsync();
}
// GET OR SET (string key)
public async Task<T> GetOrSetObjectAsync<T>(
string key,
Func<Task<T>> factory,
TimeSpan? ttl = null,
CancellationToken cancellationToken = default)
{
if (!_isRedisAvailable)
return await factory();
var direct = await GetObjectAsync<T>(key);
if (direct is not null)
return direct;
return await _lockManager.WithLockAsync(
$"getorset:{key}",
TimeSpan.FromSeconds(5),
async () =>
{
var again = await GetObjectAsync<T>(key);
if (again is not null)
return again;
var created = await factory();
if (created != null)
await SetObjectAsync(key, created, ttl, true);
return created!;
},
cancellationToken);
}
public async Task<string?> GetOrSetValueAsync(
string key,
Func<Task<string>> loader,
TimeSpan? ttl = null)
{
if (!_isRedisAvailable)
return await loader();
var direct = GetValue(key);
if (direct is not null)
return direct;
return await _lockManager.WithLockAsync(
$"getorset:{key}",
TimeSpan.FromSeconds(5),
async () =>
{
var again = GetValue(key);
if (again is not null)
return again;
var created = await loader();
SetValue(key, created);
return created;
});
}
// GET OR SET (GroupedField + patientId)
private static string BuildGroupedKey(GroupedField gf, ObjectId patientId)
=> $"GroupedObs:{patientId}:{gf.Name}";
public async Task<T> GetOrSetObjectAsync<T>(
GroupedField groupedField,
ObjectId patientId,
Func<Task<T>> factory,
TimeSpan? ttl = null,
CancellationToken cancellationToken = default)
{
var key = BuildGroupedKey(groupedField, patientId);
if (!_isRedisAvailable)
return await factory();
var direct = await GetObjectAsync<T>(key);
if (direct is not null)
return direct;
return await _lockManager.WithLockAsync(
$"getorset:{key}",
TimeSpan.FromSeconds(5),
async () =>
{
var again = await GetObjectAsync<T>(key);
if (again is not null)
return again;
var created = await factory();
if (created != null)
await SetObjectAsync(key, created, ttl, true);
return created!;
},
cancellationToken);
}
// BASIC OPERATIONS
public void SetValue(string key, string value)
=> _database?.StringSet(key, value, GetEntityTtl(key), true);
public string? GetValue(string key)
{
var val = _database?.StringGet(key);
if (val.HasValue && ShouldRenewTtl(key))
_database?.KeyExpire(key, GetEntityTtl(key));
return val;
}
public async Task<T?> GetObjectAsync<T>(string key, bool updateExpiration = true)
{
if (!_isRedisAvailable)
return default;
var json = await _database!.StringGetAsync(key);
if (json.IsNullOrEmpty)
return default;
if (updateExpiration)
_database!.KeyExpire(key, GetEntityTtl(key));
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Converters = new List<JsonConverter> { new StringEnumConverter(), new ObjectIdConverter() }
};
try
{
return JsonConvert.DeserializeObject<T>(json!, settings);
}
catch (Exception e)
{
_logger.LogError("Error deserializing object in RedisService {e}",e.ToString());
throw new Exception($"Error deserializing object in RedisService {e}", e);
}
}
public async Task SetObjectAsync<T>(
string key,
T obj,
bool updateExpiration = true)
=> await SetObjectAsync(key, obj, null, updateExpiration);
public async Task SetObjectAsync<T>(
string key,
T obj,
TimeSpan? ttlOverride,
bool updateExpiration)
{
if (!_isRedisAvailable)
return;
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver(),
Converters = new List<JsonConverter> { new StringEnumConverter(), new ObjectIdConverter() }
};
var json = JsonConvert.SerializeObject(obj, settings);
await _database!.StringSetAsync(key, json, ttlOverride ?? GetEntityTtl(key), true);
}
public async Task DeleteObjectAsync(string key)
{
if (_isRedisAvailable)
await _database!.KeyDeleteAsync(key);
}
public async Task<long> DeleteByPatternAsync(string pattern)
{
if (!_isRedisAvailable || _server == null)
return 0;
var keys = _server.Keys(pattern: pattern).ToArray();
foreach (var key in keys)
await _database!.KeyDeleteAsync(key);
return keys.Length;
}
public void CleanCache()
=> _server?.FlushDatabase();
// TTL
private TimeSpan? GetEntityTtl(string key)
{
var entity = CacheKeyClassifier.Classify(key);
var ttl = _cacheSettings.Redis.Ttl;
int? seconds = entity switch
{
CacheEnum.EntityType.Patients => ttl.PatientsSeconds,
CacheEnum.EntityType.Displays => ttl.DisplaysSeconds,
_ => ttl.GlobalSeconds
};
return seconds > 0 ? TimeSpan.FromSeconds(seconds.Value) : null;
}
private bool ShouldRenewTtl(string key)
=> GetEntityTtl(key) != null;
// INITIALIZATION
private async Task InitializeRedisConnectionAsync()
{
_isRedisAvailable = false;
try
{
if (_cacheSettings.Redis.ConnectionString == null)
return;
_connection = await ConnectionMultiplexer.ConnectAsync(_cacheSettings.Redis.ConnectionString);
_database = _connection.GetDatabase();
_server = _connection.GetServer(_cacheSettings.Redis.ConnectionString);
_isRedisAvailable = true;
}
catch(Exception ex)
{
_logger.LogError( "Error Initializing Redis Connection. Exception:{ex}", ex.Message);
}
}
public Task<T?> GetObjectAsync<T>(string key, TimeSpan? ttlOverride, bool updateExpiration)
=> GetObjectAsync<T>(key, updateExpiration);
}
}
@@ -0,0 +1,143 @@
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.Pumps;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
namespace adas_core.Application.Services;
public class CalculatedObservationsService : ICalculatedObservationsService
{
private static ICalculatedObservations? _service;
private readonly ILogger<CalculatedObservationsService>? _logger;
public CalculatedObservationsService(
IOptions<ApiSettings> apiSettings,
IServiceProvider serviceProvider,
ILogger<CalculatedObservationsService> logger)
{
_logger = logger;
var customize = apiSettings.Value.Customize;
if (customize == null)
{
_logger.LogWarning("Not customization specified");
_service = new DefaultCalculatedObservations();
return;
}
var calculatedObservations = "adas_core.Application.Customizations." + customize + ".CalculatedObservations";
if (string.IsNullOrEmpty(customize))
{
_service = new DefaultCalculatedObservations();
}
else
{
var type = Type.GetType(calculatedObservations);
if (type == null)
{
_logger.LogWarning(
$"Type {calculatedObservations} not found for specification. Using DefaultCalculatedObservations");
_service = new DefaultCalculatedObservations();
return;
}
var ctor = Type.GetType(calculatedObservations)?.GetConstructor([typeof(IServiceProvider)]);
if (ctor == null)
{
_logger.LogWarning(
$"Constructor not found for type {calculatedObservations} accepts one parameter with type IServiceProvider. Using DefaultCalculatedObservations");
_service = new DefaultCalculatedObservations();
return;
}
_service = (ICalculatedObservations)ctor.Invoke([serviceProvider]);
}
}
public virtual async Task<PatientObservation?> Map(PatientObservation obs, bool onlyByName = false)
{
if (_service == null) return obs;
var result = await _service.Map(obs, onlyByName);
if (result == null)
_logger?.LogDebug("Mapping calculated via service {serviceFullName}. {obs} Ignored",
_service.GetType().FullName, obs);
return result;
}
public virtual async Task<PatientObservationAlarm?> Map(PatientObservationAlarm obs, bool onlyByName = false)
{
if (_service == null) return obs;
var result = await _service.Map(obs, onlyByName);
if (result == null)
_logger?.LogDebug("Mapping calculated via service {serviceFullName}. {obs} Ignored",
_service.GetType().FullName, obs);
return result;
}
public virtual async Task<PumpObservation?> Map(PumpObservation obs)
{
if (_service == null) return null;
var result = await _service.Map(obs);
return result;
}
public async Task<PatientRecordingAlert?> Map(PatientRecordingAlert obs)
{
if (_service == null) return obs;
var result = await _service.Map(obs);
if (result == null)
_logger?.LogDebug("Mapping calculated via service {serviceFullName}. {obs} Ignored",
_service.GetType().FullName, obs);
return result;
}
public async Task<PatientTreatment?> Map(PatientTreatment treatment)
{
if (_service == null) return treatment;
var result = await _service.Map(treatment);
return result;
}
public virtual async Task<PatientDiagnosis?> Map(PatientDiagnosis diagnosis)
{
if (_service == null) return diagnosis;
var result = await _service.Map(diagnosis);
return result;
}
public virtual async Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
{
if (_service != null) await _service.CalculateMedicineObservation(activeMedicines, patientId);
}
public async Task CalculateBolusOpiates(ObjectId patientId)
{
if (_service != null) await _service.CalculateActiveBolus(patientId);
}
public virtual async Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
{
if (_service != null) return await _service.GetActiveTreatmentsByPatient(id);
return new List<PatientTreatment?>();
}
public virtual async Task<List<PatientObservation>> MapList(List<PatientObservation> listToInsert)
{
if (_service != null) return await _service.PreMapList(listToInsert);
return [];
}
public virtual async Task<PatientObservation> MapSourceAlarm(PatientObservation observation,
PatientObservationAlarm observationAlarm)
{
if (_service != null) return await _service.MapSourceAlarm(observation, observationAlarm);
return observation;
}
}
@@ -0,0 +1,99 @@
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Services;
public class CameraService(ILogger<CameraService> logger, ICameraRepository cameraRepository, IPointOfCareService pointOfCareService) : ICameraService
{
private ICameraRepository _cameraRepository = cameraRepository;
public Task<Camera?> GetById(ObjectId relayId)
{
return _cameraRepository.GetById(relayId);
}
public List<Camera> GetCameraInList(List<ObjectId> configurationRelayList)
{
return _cameraRepository.GetCameraInList(configurationRelayList);
}
public async Task<PaginationResponse<Camera>> GetPaginatedCameras(PaginationFilter filter)
{
var usedCameraIds = await pointOfCareService.FindAllIdCamerasInUse();
var fluentQuery = _cameraRepository.GetPaginatedCameras(filter);
if (filter.FilteredRequest?.InUse != null)
{
bool filterInUse = filter.FilteredRequest.InUse.Value;
var filterBuilder = Builders<Camera>.Filter;
var idFilter = filterInUse
? filterBuilder.In(c => c.Id, usedCameraIds)
: filterBuilder.Not(filterBuilder.In(c => c.Id, usedCameraIds));
fluentQuery.Filter = filterBuilder.And(fluentQuery.Filter, idFilter);
}
var count = await fluentQuery.CountDocumentsAsync();
var data = await fluentQuery
.Skip((filter.PageNumber - 1) * filter.PageSize)
.Limit(filter.PageSize)
.ToListAsync();
if(data == null) return new PaginationResponse<Camera>([], filter.PageNumber, filter.PageSize, count);
foreach (var camera in data)
{
if (camera == null) continue;
bool isInUse = usedCameraIds.Contains(camera.Id);
// Asignación mediante reflexión para el private set
camera.GetType().GetProperty(nameof(Camera.InUse))
?.SetValue(camera, isInUse);
}
return new PaginationResponse<Camera>(data, filter.PageNumber, filter.PageSize, count);
}
public async Task<Camera?> InsertCamera(Camera camera)
{
if (camera.Name == null) throw new Exception("Camera name cannot be null");
var cameraFound = await _cameraRepository.GetByName(camera.Name);
if(cameraFound != null) throw new Exception($"Camera with name {camera.Name} already exists");
return await _cameraRepository.InsertOneCamera(camera);
}
public async Task<Camera?> UpdateCameraById(ObjectId objectId, Camera camera)
{
return await _cameraRepository.UpdateCameraAsync(objectId, camera);
}
public async Task<bool> DeleteCamera(ObjectId objectId)
{
try
{
var cameraToDelete = await _cameraRepository.GetById(objectId);
if(cameraToDelete == null) return false;
await _cameraRepository.DeleteAsync(cameraToDelete.Id);
return true;
}
catch (Exception e)
{
logger.LogError(e, e.Message);
return false;
}
}
public async Task<List<Camera>> GetSearchByNameCameras(string textToSearch)
{
return await _cameraRepository.GetSearchByNameCameras(textToSearch);
}
}
@@ -0,0 +1,642 @@
using System.Collections.Concurrent;
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.GroupedObservations;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Pumps;
using adas_core.Domain.Models.Responses;
using adas_core.Domain.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.CodeAnalysis.CSharp.Scripting;
using Microsoft.CodeAnalysis.Scripting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
namespace adas_core.Application.Services;
public class ConfigObservationService : IConfigObservationService
{
private static readonly ConcurrentDictionary<ObjectId, ConfigObservationCached> CachedConfigObservations = new();
private static readonly ConcurrentDictionary<string, ConfigObservationKeyCached>
CachedConfigObservationKeys = new();
private readonly IOptions<ApiSettings> _apiSettings;
private readonly ILocalAuditService _auditService;
private readonly IConfigObservationRepository _configObservationRepository;
private readonly RetentionPolicy _defaultRetentionPolicy;
private readonly int _defaultRetentionPolicyValue;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly bool _ignoreUnknownTreatment;
private readonly ILogger<ConfigObservationService> _logger;
private readonly int? _refreshTimeout;
private readonly IUnitService _unitService;
private readonly ICacheService _cacheService;
private readonly CacheSettings? _cacheSettings;
private bool IgnoreUnknownObservation =>
_apiSettings.Value.ConfigObservation?.IgnoreUnknownObservation ?? false;
public ConfigObservationService(
IConfigObservationRepository configObservationRepository,
IOptions<ApiSettings> apiSettings,
IOptions<CacheSettings> cacheSettings,
ILogger<ConfigObservationService> logger,
IUnitService unitService,
IHttpContextAccessor httpContextAccessor,
ILocalAuditService auditService,
ICacheService cacheService
)
{
_cacheService = cacheService;
_cacheSettings = cacheSettings.Value;
_configObservationRepository = configObservationRepository;
_apiSettings = apiSettings;
_logger = logger;
_unitService = unitService;
_refreshTimeout = _apiSettings.Value.ConfigObservation?.Refresh;
_ignoreUnknownTreatment = _apiSettings.Value.ConfigObservation?.IgnoreUnknownTreatment ?? false;
_defaultRetentionPolicyValue = _apiSettings.Value.RetentionPolicyValue;
_defaultRetentionPolicy = RetentionPolicy.NoDelete;
_httpContextAccessor = httpContextAccessor;
_auditService = auditService;
}
public async Task<ICollection<ConfigObservation>> GetAllConfigs(CancellationToken ct = default)
{
var (key, ttl) = CacheKeys.ConfigObservationsAllKeyWithTtl(_cacheSettings);
var result = await _cacheService.GetOrSetObjectAsync(
key,
async () => await _configObservationRepository.FindAll(),
ttl,
ct);
return result;
}
public async Task<ConfigObservationDto> GetAllCompact()
{
var count = await _configObservationRepository.Count();
return new ConfigObservationDto { ItemCount = count };
}
public async Task<PaginationResponse<ConfigObservation>> GetPaginatedItems(PaginationFilter filter)
{
var result = await _configObservationRepository.GetPaginatedItems(filter);
var count = await _configObservationRepository.Count();
return new PaginationResponse<ConfigObservation>(result.ToList(), filter.PageNumber, filter.PageSize,
count);
}
public async Task<ConfigObservation?> GetConfigById(ObjectId id)
{
return await _configObservationRepository.FindById(id) ?? null;
}
public async Task<List<string>> GetConfigNames(string id)
{
return await _configObservationRepository.GetConfigNames(id);
}
public async Task<List<string>> GetConfigNames()
{
return await _configObservationRepository.GetConfigNames();
}
public async Task<ObservatitonRetentionResult?> RetentionActions<T>(T obs) where T : BasePatientObservation
{
var conf = await Get(obs);
return conf is { RetentionPolicy: not null } ?
new ObservatitonRetentionResult(conf.RetentionPolicy.Value, conf.RetentionPolicyValue) :
new ObservatitonRetentionResult(RetentionPolicy.NoDelete, null);
}
public async Task<StatusEnum.Type> GroupedObservationStatus(GroupedField groupedField,
GroupedObservationEnum.Result result,
string name, object value, double? min, double? max)
{
var conf = await Get(name);
if (conf == null) return StatusEnum.Type.Ok;
PatientObservation? mapObs;
if (conf.Grouped != null && groupedField.Group != null &&
conf.Grouped.TryGetValue(groupedField.Group, out var grp))
{
mapObs = await MapConf(new PatientObservation { Name = name, Value = value, Min = min, Max = max }, grp);
if (mapObs != null) return mapObs.Status;
}
if (conf.Grouped != null && conf.Grouped.ContainsKey(result.ToString()))
{
mapObs = await MapConf(new PatientObservation { Name = name, Value = value, Min = min, Max = max },
conf.Grouped[result.ToString()]);
if (mapObs != null) return mapObs.Status;
}
mapObs = await MapConf(new PatientObservation { Name = name, Value = value, Min = min, Max = max }, conf);
if (mapObs != null) return mapObs.Status;
return StatusEnum.Type.Ok;
}
public async Task<T?> Map<T>(T obs, bool onlyByName = false) where T : BasePatientObservation
{
var conf = onlyByName ? await Get(obs, onlyByName) : await Get(obs);
if (conf == null)
{
_logger.LogDebug("Ignore Unknown Observation. {Name} {Code} {CodingSystem}", obs.Name, obs.Code,
obs.CodingSystem);
return IgnoreUnknownObservation ? null : obs;
}
if (string.IsNullOrEmpty(conf.Name))
{
_logger.LogError("Config Name is null or empty. {conf}", conf);
return null;
}
return await MapConf(obs, conf);
}
public async Task<ConfigObservation?> RemoveConfigItem(ObjectId id)
{
var item = await _configObservationRepository.FindById(id);
if (item == null) return null;
var deleted = await _configObservationRepository.Delete(id);
// Invalidar CACHE (colección completa)
await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ForEntity(CacheEnum.EntityType.ConfigObservations));
return deleted;
}
public async Task<PatientTreatment?> Map(PatientTreatment treatment)
{
ConfigObservation? conf = null;
foreach (var requestGiveCode in treatment.RequestedGiveCodes)
conf = string.IsNullOrEmpty(requestGiveCode.CodingSystem) &&
string.IsNullOrEmpty(requestGiveCode.Identifier)
? await Get(requestGiveCode.Text)
: await GetByCodeSysAndCode(requestGiveCode.CodingSystem, requestGiveCode.Identifier);
if (conf == null) return _ignoreUnknownTreatment ? null : treatment;
return conf.Name == null ? null : treatment;
}
public async Task<ConfigObservation?> Get<T>(T obs, bool onlyByName = false)
where T : BasePatientObservation
{
var items = await GetAllConfigs();
if (items.Count == 0) return null;
if (onlyByName)
{
var configItem = items.FirstOrDefault(i => i.Name == obs.Name);
if (configItem != null) return await Process(configItem);
_logger.LogError("Error getting Config observation Item. Observation: {obs}", obs);
return null;
}
ConfigObservation? item = null;
if ((!string.IsNullOrEmpty(obs.Code) && !string.IsNullOrEmpty(obs.CodingSystem)) ||
obs.ParentData is { Code: not null, CodingSystem: not null })
foreach (var obsConfig in items)
{
if (obs.Code != null && obs.Code != obsConfig.Code)
continue;
if (obsConfig.CodingSystem != null && obs.CodingSystem != obsConfig.CodingSystem)
continue;
if (obsConfig.OriginalName != null && obs.Name != obsConfig.OriginalName)
continue;
if (obsConfig.ParentCode != null && obs.ParentData?.Code != obsConfig.ParentCode)
continue;
if (obsConfig.ParentCodingSystem != null &&
obs.ParentData?.CodingSystem != obsConfig.ParentCodingSystem)
continue;
if (obsConfig.ParentName != null && obs.ParentData?.Name != obsConfig.ParentName)
continue;
if (obsConfig.OriginalName == null && obsConfig.Name != null && obs.Name != null &&
obs.Name.Contains("Alarm") && obs.Name != obsConfig.Name)
continue;
if (obsConfig.Code == null && obsConfig.CodingSystem == null && obsConfig.ParentCode == null &&
obsConfig.ParentCodingSystem == null && obs.Name != null && !obs.Name.Contains("Alarm"))
continue;
if (obsConfig.Code == null &&
obsConfig is { CodingSystem: not null, ParentCode: null, ParentCodingSystem: null } &&
obs.Name != null && !obs.Name.Contains("Alarm"))
continue;
if (obsConfig.Code == null && obsConfig.CodingSystem == null && obsConfig.ParentCode == null &&
obsConfig.ParentCodingSystem != null && obs.Name != null && !obs.Name.Contains("Alarm"))
continue;
if (obsConfig.Code == null &&
obsConfig is { CodingSystem: not null, ParentCode: null, ParentCodingSystem: not null } &&
obs.Name != null && !obs.Name.Contains("Alarm"))
continue;
item = obsConfig;
break;
}
else item = items.FirstOrDefault(i => i.Name == obs.Name);
return item != null ? await Process(item) : null;
}
// public async Task<List<ConfigObservation>> GetAlarmWithRecordingConfig(ObjectId patientId)
// {
// var configObservationId = await GetConfigObservationKeyFromPatientId(patientId);
// var configObservation = await GetConfig(configObservationId);
// return configObservation?.Items
// .Where(i => i is { CodingSystem: "ADAS_ALARM", Alarm.Recording.Enabled: true })
// .ToList() ?? [];
// }
public async Task<ConfigObservation?> GetByCodeSysAndCode(string? codingSystem, string? code)
{
var filteredResult = await _configObservationRepository.GetByCodeSysAndCode(codingSystem, code);
if (filteredResult != null) return await Process(filteredResult);
_logger.LogWarning("Config observation item not found. CodingSystem: {codingSystem} code: {code} ",
codingSystem ?? "null", code ?? "null");
return null;
}
public async Task<ConfigObservation?> Get(string? name)
{
if (string.IsNullOrEmpty(name)) return null;
var result = await GetConfigByName(name);
if (result == null)
{
// _logger.LogWarning(
// "Config observation item not found. name: {name} configObservationId: {configObservationId} ",
// name, name);
return null;
}
return await Process(result);
}
public async Task<ConfigObservation?> UpdateConfig(ConfigObservation configObservationItem)
{
// if (!configObservationItem.Id.HasValue)
// return await _configObservationRepository.InsertOneAsyncAndReturn(configObservationItem);
var configObservation = await _configObservationRepository.FindById(configObservationItem.Id) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
var updatedConfig = await _configObservationRepository.Update(configObservation);
// Invalidar CACHE (colección completa)
await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ForEntity(CacheEnum.EntityType.ConfigObservations));
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, configObservation,
updatedConfig!);
return updatedConfig;
}
public async Task<IEnumerable<ConfigObservation>?> GetConfigObservationItem(string name)
{
var configObservationItems = await _configObservationRepository.FindAllByName(name);
if (configObservationItems.Count != 0)
return configObservationItems;
_logger.LogWarning("Cant get config item, config not found, name: {name} ", name);
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
}
public async Task<ConfigObservation?> GetSingleConfigObservationItem(string? code, string? codingSystem,
string? name, string? originalName)
{
var matchingItem =
await _configObservationRepository.GetSingleConfigObservationItem(code, codingSystem, name, originalName);
if (matchingItem == null) throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundNoMatches);
return matchingItem;
}
public async Task<bool> DeleteSingleConfigObservationItem(ConfigObservation configObservationItem)
{
var configObservation = await _configObservationRepository.FindById(configObservationItem.Id) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
var auxConfigObservation = await _auditService.DeepCopyAsync(configObservation);
_ = await _configObservationRepository.DeleteAsync(configObservation.Id) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
// Invalidar CACHE (colección completa)
await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ForEntity(CacheEnum.EntityType.ConfigObservations));
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, auxConfigObservation,
configObservation);
return true;
}
public async Task<IEnumerable<ConfigObservation>> GetConfigObservationItemsByName(string name)
{
return await _configObservationRepository.FindAllByName(name);
}
public async Task<ConfigObservation?> CreateConfig(ConfigObservation configObservation)
{
var existing = await _configObservationRepository.FindById(configObservation.Id);
if (existing != null)
throw new BadRequestException(HttpEnum.ErrorMessage.ConflictCreationFailed);
await _configObservationRepository.InsertOneAsyncAndReturn(configObservation);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, configObservation);
return configObservation;
}
public async Task<ConfigObservation?> RemoveConfigItem(string itemName)
{
var configObservation = await GetConfigByName(itemName) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
var auxConfigObservation = await GetConfigByName(itemName) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, auxConfigObservation,
configObservation);
var result = await _configObservationRepository.Delete(configObservation.Id!);
// Invalidar CACHE (colección completa)
await _cacheService.DeleteByPatternAsync(CacheKeyPatterns.ForEntity(CacheEnum.EntityType.ConfigObservations));
return result;
}
public async Task<ConfigObservation?> GetConfig(ObjectId configObservationId)
{
RefreshCachedConfigObservations();
if (CachedConfigObservations.TryGetValue(configObservationId, out var cached)
&& DateTime.Now <= cached.NextRefresh)
return cached.ConfigObservation;
var config = await _configObservationRepository.FindById(configObservationId);
cached = new ConfigObservationCached
{
ConfigObservation = config ?? new ConfigObservation(),
NextRefresh = _refreshTimeout.HasValue ? DateTime.Now.AddSeconds(_refreshTimeout.Value) : DateTime.MinValue
};
CachedConfigObservations[configObservationId] = cached;
return cached.ConfigObservation;
}
public async Task<ConfigObservation?> GetConfigByName(string name)
{
if (string.IsNullOrWhiteSpace(name)) return null;
RefreshCachedConfigObservations();
var cachedItem = CachedConfigObservations.Values
.FirstOrDefault(cached =>
DateTime.Now <= cached.NextRefresh &&
cached.ConfigObservation is { Name: not null } &&
cached.ConfigObservation.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
if (cachedItem != null) return cachedItem.ConfigObservation;
var config = await _configObservationRepository.FindByName(name);
if (config == null) return null;
var newCachedItem = new ConfigObservationCached
{
ConfigObservation = config,
NextRefresh = _refreshTimeout.HasValue
? DateTime.Now.AddSeconds(_refreshTimeout.Value)
: DateTime.MinValue
};
CachedConfigObservations[config.Id!] = newCachedItem;
return config;
}
private void RefreshCachedConfigObservations()
{
if (!_refreshTimeout.HasValue) return;
// Use ConcurrentDictionary's thread-safe features to identify and remove expired keys
var keysToRemove = CachedConfigObservations
.Where(k => DateTime.Now > k.Value.NextRefresh)
.Select(k => k.Key)
.ToList();
foreach (var key in keysToRemove) CachedConfigObservations.TryRemove(key, out _);
}
private Task<ConfigObservation> Process(ConfigObservation confItem)
{
confItem.RetentionPolicy ??= _defaultRetentionPolicy;
if (confItem.RetentionPolicy != RetentionPolicy.NoDelete && !confItem.RetentionPolicyValue.HasValue)
{
confItem.RetentionPolicyValue = _defaultRetentionPolicyValue;
if (confItem.RetentionPolicyValue <= 0) confItem.RetentionPolicyValue = null;
}
else
{
confItem.RetentionPolicyValue = null;
}
return Task.FromResult(confItem);
}
private void RefreshCachedConfigObservationKeys()
{
try
{
if (!_refreshTimeout.HasValue) return;
// Use ConcurrentDictionary's thread-safe features to identify and remove expired keys
var keysToRemove = CachedConfigObservationKeys
.Where(k => DateTime.Now > k.Value.NextRefresh)
.Select(k => k.Key)
.ToList();
foreach (var key in keysToRemove) CachedConfigObservationKeys.TryRemove(key, out _);
}
catch (Exception ex)
{
_logger.LogError("Error refreshing cached config observation keys. Exception: {ex}", ex);
}
}
private async Task<T?> MapConf<T>(T obs, ConfigObservation conf) where T : BasePatientObservation
{
_logger.LogTrace("Mapping observation: {obs}", obs);
obs.Name = conf.Name;
if (obs is PatientObservation)
{
var noCalculateStatusWithCodingSystem = _apiSettings.Value.NoCalculateStatusWithCodingSystem;
if (noCalculateStatusWithCodingSystem != null &&
obs.CodingSystem == noCalculateStatusWithCodingSystem) return obs;
if (obs is not PatientObservation pobs)
return obs;
if (conf.MaxAlert != null && (conf.ForceAlert || !pobs.Max.HasValue)) pobs.Max = conf.MaxAlert;
if (conf.MinAlert.HasValue && (conf.ForceAlert || !pobs.Min.HasValue)) pobs.Min = conf.MinAlert;
if (conf.MaxWarn.HasValue && (conf.ForceWarn || !pobs.MaxWarn.HasValue)) pobs.MaxWarn = conf.MaxWarn;
if (conf.MinWarn.HasValue && (conf.ForceWarn || !pobs.MinWarn.HasValue)) pobs.MinWarn = conf.MinWarn;
if (conf.LevelCondition != null)
try
{
var evalCondition = await CSharpScript.EvaluateAsync(conf.LevelCondition, globals: pobs);
if (int.TryParse(evalCondition.ToString(), out var evalInt))
pobs.Level = evalInt;
else
_logger.LogError("Error evaluating condition: {evalCondition} for obs: {pobs}",
evalCondition, pobs);
}
catch (CompilationErrorException e)
{
_logger.LogError("Error evaluating expression for obs: {obs} error: {diagnostics}", obs,
e.Diagnostics);
}
catch (Exception e)
{
_logger.LogError("Error evaluating expression for obs: {obs} error: {e}", obs, e);
}
pobs.Status = StatusEnum.Type.Ok;
if (pobs.Value.IsNumber())
{
if (pobs.Min.HasValue && pobs.Min > pobs.Value.ToDouble() &&
conf.Alert is null or '<')
{
pobs.Status = StatusEnum.Type.Alert;
pobs.AlertColor = conf.AlertColor;
}
else if (pobs.Max.HasValue && pobs.Max < pobs.Value.ToDouble() &&
conf.Alert is null or '>')
{
pobs.Status = StatusEnum.Type.Alert;
pobs.AlertColor = conf.AlertColor;
}
else if (pobs.MinWarn.HasValue && pobs.MinWarn > pobs.Value.ToDouble() &&
conf.Alert is null or '<')
{
pobs.Status = StatusEnum.Type.Warning;
pobs.WarnColor = conf.WarnColor;
}
else if (pobs.MaxWarn.HasValue && pobs.MaxWarn < pobs.Value.ToDouble() &&
conf.Alert is null or '>')
{
pobs.Status = StatusEnum.Type.Warning;
pobs.WarnColor = conf.WarnColor;
}
}
else if (pobs.Value is string)
{
if (conf.AlertValues != null && conf.AlertValues.Any() && conf.AlertValues.Contains(pobs.Value))
pobs.Status = StatusEnum.Type.Alert;
else if (conf.WarningValues != null && conf.WarningValues.Any() &&
conf.WarningValues.Contains(pobs.Value)) pobs.Status = StatusEnum.Type.Warning;
}
if (conf.Expires is > 0)
{
pobs.Expires = conf.Expires;
//check if already is expired
var expireTime = obs.Time.ToLocalTime().AddSeconds(Convert.ToDouble(conf.Expires));
pobs.Expired = DateTime.Now.CompareTo(expireTime) > 0;
}
pobs.ShowOnExpired = conf.ShowOnExpired;
if (conf.ColorOnExpired != null) pobs.ColorOnExpired = conf.ColorOnExpired;
if (conf.Persist != null)
pobs.Persist = conf.Persist;
if (conf.UiConfiguration != null && conf.UiConfiguration.Any()) pobs.UiConfiguration = conf.UiConfiguration;
if (conf.Alarm != null) pobs.Alarm = conf.Alarm;
if (conf.TimeFromMessageTime)
if (pobs.MessageTime.CompareTo(DateTime.MinValue) != 0)
pobs.Time = pobs.MessageTime;
if (pobs.Units == null || conf.ForceUnits) pobs.Units = conf.Units;
if (conf.CreateObservation != null) pobs.CreateObservation = conf.CreateObservation;
pobs.CheckObservations = conf.CheckObservations;
}
if (obs is PatientObservationAlarm)
{
if (obs is not PatientObservationAlarm pobs)
return obs;
if (conf.Expires is > 0)
{
pobs.Expires = conf.Expires;
//check if already is expired
var expireTime = obs.Time.ToLocalTime().AddSeconds(Convert.ToDouble(conf.Expires));
pobs.Expired = DateTime.Now.CompareTo(expireTime) > 0;
}
if (conf.Persist != null)
pobs.Persist = conf.Persist;
if (conf.Alarm != null) pobs.AlarmConfig = conf.Alarm;
if (conf.AlertColor != null) pobs.AlertColor = conf.AlertColor;
if (conf.TimeFromMessageTime)
if (pobs.MessageTime.CompareTo(DateTime.MinValue) != 0)
pobs.Time = pobs.MessageTime;
if (pobs.Units == null || conf.ForceUnits) pobs.Units = conf.Units;
if (conf.CreateObservation != null) pobs.CreateObservation = conf.CreateObservation;
pobs.CheckObservations = conf.CheckObservations;
}
_logger.LogTrace("Observation mapped: {obs}", obs);
return obs;
}
private class ConfigObservationCached
{
public DateTime NextRefresh { get; set; }
public ConfigObservation? ConfigObservation { get; set; }
}
private class ConfigObservationKeyCached
{
public DateTime NextRefresh { get; set; }
public string? Key { get; set; }
}
}
@@ -0,0 +1,163 @@
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.MongoModels;
using adas_core.Domain.Models.Pumps;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace adas_core.Application.Services;
public class ConfigPumpsService(
IConfigPumpsRepository configPumpsRepository,
IOptions<ApiSettings> apiSettings,
ILogger<ConfigPumpsService> logger,
IHttpContextAccessor httpContextAccessor,
ILocalAuditService auditService)
: IConfigPumpsService
{
private static ConfigPumps? _config;
private static DateTime _nextRefresh = DateTime.MinValue;
private readonly bool _configPumpsRequired = apiSettings.Value.ConfigPumpsRequired;
private readonly string _key = apiSettings.Value.ConfigPumpsKey ?? "PV1";
private readonly int? _refreshTimeout = apiSettings.Value.ConfigObservation?.Refresh;
public async Task<PumpObservation> Map(PumpObservation obs)
{
if (!_configPumpsRequired) return obs;
var conf = !string.IsNullOrEmpty(obs.AlarmType.ToString())
? await Get(obs.AlarmType.ToString() ?? string.Empty)
: null;
if (conf == null) return obs;
return await MapConf(obs, conf);
}
public async Task<List<ConfigPumps>?> GetAllPumpConfigs()
{
return await configPumpsRepository.GetAllConfigs();
}
public async Task<ConfigPumps?> GetPumpConfigById(string id)
{
return await configPumpsRepository.FindById(id);
}
public async Task<List<ConfigPumpItem>?> GetConfigItems(string id)
{
var result = await configPumpsRepository.FindById(id);
return result?.Items;
}
public async Task<ConfigPumps?> UpdatePumpConfig(ConfigPumps pumpConfig)
{
var oldPumpConfig = configPumpsRepository.FindById(pumpConfig.Id);
var newPumpConfig = await configPumpsRepository.UpdateConfig(pumpConfig) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldPumpConfig, newPumpConfig);
return newPumpConfig;
}
public async Task<ConfigPumps?> InsertPumpConfig(ConfigPumps pumpConfig)
{
try
{
await configPumpsRepository.InsertOneAsync(pumpConfig);
var newPumpConfig = await configPumpsRepository.FindById(pumpConfig.Id) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, newPumpConfig);
return newPumpConfig;
}
catch (Exception ex)
{
logger.LogError("ERROR inserting config_pumps: {key}. Exception: {exMessage} ", pumpConfig.Id, ex.Message);
return null;
}
}
public async Task<bool> DeletePumpConfig(ConfigPumps config)
{
try
{
var result = await configPumpsRepository.DeleteConfig(config);
if (result)
{
logger.LogError("ERROR deleting config_pumps: {key}. ", config.Id);
return false;
}
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, config, null);
return true;
}
catch (Exception ex)
{
logger.LogError("ERROR deleting config_pumps: {key}. Exception: {exMessage} ", config.Id, ex.Message);
return false;
}
}
public async Task<ObservatitonRetentionResult?> RetentionActions(PumpObservation obs)
{
//TODO sacarlo de la configuración específica de Bombas
var conf = await Get(obs);
return conf is { RetentionPolicy: not null } ?
new ObservatitonRetentionResult(conf.RetentionPolicy.Value, conf.RetentionPolicyValue) :
new ObservatitonRetentionResult(RetentionPolicy.NoDelete, null);
}
private static Task<PumpObservation> MapConf(PumpObservation obs, ConfigPumpItem conf)
{
if (conf.UiConfiguration != null && conf.UiConfiguration.Count != 0)
obs.UiConfiguration = conf.UiConfiguration;
return Task.FromResult(obs);
}
private async Task<ConfigPumpItem?> Get(string alarmType)
{
if (!Enum.TryParse(alarmType, out PumpEnum.AlarmType alarmTypeParsed))
return null;
var result = await GetConfig();
return result?.Items?.FirstOrDefault(i => i.AlarmType == alarmTypeParsed);
}
private async Task<ConfigPumpItem?> Get(PumpObservation pobs)
{
var config = await GetConfig();
return config?.Items?.FirstOrDefault(i => i.Type == pobs.MessageType);
}
public async Task<List<ConfigPumpItem>?> Get()
{
var result = await GetConfig();
return result?.Items;
}
private async Task<ConfigPumps?> GetConfig()
{
try
{
if (_config != null && DateTime.Now <= _nextRefresh)
return _config;
_config = await configPumpsRepository.FindById(_key);
_nextRefresh = _refreshTimeout.HasValue
? DateTime.Now.AddSeconds(_refreshTimeout.Value)
: DateTime.MinValue;
return _config;
}
catch (Exception ex)
{
logger.LogError("ERROR READ config_pumps: {key}. Exception: {exMessage} ", _key, ex.Message);
return null;
}
}
}
@@ -0,0 +1,119 @@
using System.Reflection;
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Pumps;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace adas_core.Application.Services;
public class ConfigUnitsService(
IConfigUnitsRepository configUnitsRepository,
IOptions<ApiSettings> apiSettings,
ILogger<ConfigUnitsService> logger)
: IConfigUnitsService
{
private static ConfigUnits? _config;
private static DateTime _nextRefresh = DateTime.MinValue;
private readonly bool _configUnitsRequired = apiSettings.Value.ConfigUnitsRequired;
private readonly string _key = apiSettings.Value.ConfigUnitsKey ?? "PV1";
private readonly int? _refreshTimeout = apiSettings.Value.ConfigObservation?.Refresh;
public async Task<T> Map<T>(T obs) where T : BasePatientObservation
{
if (!_configUnitsRequired) return obs;
var conf = !string.IsNullOrEmpty(obs.Units) ? await Get(obs.Units) : null;
return conf == null ? obs : MapConf(obs, conf);
}
public async Task<PumpObservation> Map(PumpObservation obs)
{
if (!_configUnitsRequired) return obs;
//Para cada propiedad de la observación que sea del tipo PumpValue llama a GetByCodeSysAndCode(PumpValue)
await MapPumpValues(obs);
// var conf = !string.IsNullOrEmpty(obs.Units) ? await Get(obs.Units) : null;
// return conf == null ? obs : MapConf(obs, conf);
return obs;
}
/// <summary>
/// Recorre recursivamente las propiedades del objeto para encontrar y convertir PumpValues.
/// </summary>
private async Task MapPumpValues(object? targetObject)
{
if (targetObject == null) return;
var properties = targetObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var property in properties)
{
var propertyValue = property.GetValue(targetObject);
if (propertyValue == null) continue;
if (property.PropertyType == typeof(CommonPumpTypes.PumpValue))
{
var pumpValue = (CommonPumpTypes.PumpValue)propertyValue;
if (string.IsNullOrEmpty(pumpValue.Units)) continue;
var conf = await Get(pumpValue.Units);
if (conf != null) pumpValue.Units = conf.Value;
}
// En este caso Syringe es una clase que tienen un pumpValue
else if (property.PropertyType.IsClass && property.PropertyType != typeof(string))
{
// Llamada recursiva para inspeccionar las propiedades anidadas
await MapPumpValues(propertyValue);
}
}
}
private T MapConf<T>(T obs, ConfigUnitItem conf) where T : BasePatientObservation
{
if (obs is not PatientObservation pobs) return obs;
logger.LogDebug("Mapping config Unit obs: {obs} to units: {conf}", obs, conf.Value);
pobs.Units = conf.Value;
return obs;
}
public async Task<ConfigUnitItem?> Get(string code)
{
var result = await GetConfig();
return result?.Items?.FirstOrDefault(i => i.Code == code);
}
private async Task<ConfigUnits?> GetConfig()
{
try
{
if (_config != null && DateTime.Now <= _nextRefresh) return _config;
_config = await configUnitsRepository.FindById(_key);
_nextRefresh = _refreshTimeout.HasValue
? DateTime.Now.AddSeconds(_refreshTimeout.Value)
: DateTime.MinValue;
return _config;
}
catch (Exception ex)
{
logger.LogError(ex, "ERROR READ config_units: {_key}: {ex}", _key, ex.Message);
return null;
}
}
}
@@ -0,0 +1,65 @@
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.Pumps;
using MongoDB.Bson;
namespace adas_core.Application.Services;
public class DefaultCalculatedObservations : ICalculatedObservations
{
public Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId)
{
return Task.CompletedTask;
}
public Task CalculateActiveBolus(ObjectId patientId)
{
return Task.CompletedTask;
}
public Task<T?> Map<T>(T obs, bool onlyByName) where T : BasePatientObservation
{
return Task.FromResult(obs)!;
}
public Task<PatientTreatment> Map(PatientTreatment treatment)
{
return Task.FromResult(treatment);
}
public Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id)
{
return Task.FromResult<IEnumerable<PatientTreatment?>>(new List<PatientTreatment?>());
}
public Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis)
{
return Task.FromResult(diagnosis);
}
public Task<PumpObservation> Map(PumpObservation pumpObservation)
{
throw new NotImplementedException();
}
public Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation)
{
return Task.FromResult<PatientObservation?>(newObservation);
}
public Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToInsert)
{
return Task.FromResult(listToInsert);
}
public Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert)
{
return Task.FromResult(obs);
}
public Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code)
{
throw new NotImplementedException();
}
}
@@ -0,0 +1,246 @@
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.DTO;
using adas_core.Domain.Models.MongoModels;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
namespace adas_core.Application.Services;
public class DeviceService : IDeviceService
{
private readonly IDeviceRepository _deviceRepository;
private readonly IObservationService _observationService;
private readonly IAlarmService _alarmService;
private readonly IConfigObservationService _configObservationService;
private readonly IPointOfCareService _pointOfCareService;
private readonly ILogger<DeviceService> _logger;
public DeviceService(
IDeviceRepository deviceRepository,
IPointOfCareService pointOfCareService,
ILogger<DeviceService> logger,
IObservationService observationService,
IConfigObservationService configObservationService,
IAlarmService alarmService)
{
_deviceRepository = deviceRepository;
_pointOfCareService = pointOfCareService;
_logger = logger;
_observationService = observationService;
_configObservationService = configObservationService;
_alarmService = alarmService;
}
public Device ToEntity(DeviceDto dto)
{
return new Device()
{
DeviceType = dto.DeviceType,
MacAddr = dto.MacAddr,
SerialNumber = dto.SerialNumber,
Name = dto.Name,
Battery = dto.Battery,
Color = dto.Color,
Connected = dto.Connected,
Ready = dto.Ready,
Uuid = dto.Uuid,
Key = dto.Key,
CreatedAt = dto.CreatedAt,
UpdatedAt = dto.UpdatedAt,
PointOfCareIds = dto.PointOfCareIds ?? new List<ObjectId>(),
Settings = dto.Settings ?? new DeviceSettings()
};
}
public async Task<Device?> Create(DeviceDto deviceDto)
{
var device = ToEntity(deviceDto);
device.CreatedAt = DateTime.UtcNow;
device.UpdatedAt = DateTime.UtcNow;
await _deviceRepository.InsertOneAsync(device);
return device;
}
public async Task<bool> Delete(ObjectId objectId)
{
return await _deviceRepository.DeleteAsync(objectId) != null;
}
public async Task<Device?> Update(DeviceDto deviceDto)
{
var device = ToEntity(deviceDto);
device.UpdatedAt = DateTime.UtcNow;
await _deviceRepository.UpdateOneAsync(device.Id, device);
return device;
}
public async Task<Device?> ReceiveEvent(DeviceDto deviceDto)
{
Device? deviceExist = null;
if (!string.IsNullOrEmpty(deviceDto.MacAddr))
{
deviceExist = await _deviceRepository.FindByMacAddr(deviceDto.MacAddr);
}
if (deviceExist == null && deviceDto.SerialNumber != null)
{
deviceExist = await _deviceRepository.FindBySerialNumber(deviceDto.SerialNumber);
}
if (deviceExist == null && deviceDto.Uuid != null)
{
deviceExist = await _deviceRepository.FindByUuid(deviceDto.Uuid);
}
if (deviceExist == null && deviceDto.Key != null)
{
deviceExist = await _deviceRepository.FindByKey(deviceDto.Key);
}
if (deviceExist == null)
{
deviceExist = ToEntity(deviceDto);
deviceExist.CreatedAt = DateTime.UtcNow;
deviceExist.UpdatedAt = DateTime.UtcNow;
await _deviceRepository.InsertOneAsync(deviceExist);
}
else
{
await _deviceRepository.UpdateDeviceStats(deviceExist.Id, deviceDto);
}
switch (deviceDto.DeviceType)
{
case DeviceType.Unknown:
break;
case DeviceType.Button:
await ManageDeviceButton(deviceExist, deviceDto);
break;
}
return deviceExist;
}
private async Task ManageDeviceButton(Device deviceExist, DeviceDto deviceDto)
{
if (deviceExist.Settings?.Action?.Type != null && deviceDto.Event?.ClickType != null)
{
switch (deviceExist.Settings.Action.Type)
{
case DeviceActionType.SendObs:
await SendObservationOnAction(deviceExist.Settings.Action, deviceDto.Event.ClickType.Value, deviceExist.PointOfCareIds);
break;
case DeviceActionType.SendAlarm:
await SendAlarmOnAction(deviceExist.Settings.Action, deviceDto.Event.ClickType.Value, deviceExist.PointOfCareIds);
break;
}
}
}
private async Task SendAlarmOnAction(
DeviceAction settingsAction,
ClickType eventClickType,
List<ObjectId> deviceExistPointOfCareIds)
{
var configObs = await _configObservationService.GetConfigById(settingsAction.ConfigObservationId);
if(configObs == null) return;
var obsData = new ObservationData
{
Code = configObs.Code,
CodingSystem = configObs.CodingSystem,
Time = DateTime.UtcNow,
Text = configObs.Name,
};
var obs = new PatientObservationAlarm
{
InactivationState = new InactivationState{ Visual = AlarmEnum.AudioVideoState.Enabled, Audio = AlarmEnum.AudioVideoState.Enabled},
MessageTime = DateTime.UtcNow,
Persist = true,
Code = obsData.Code,
CodingSystem = obsData.CodingSystem,
Name = configObs.Name,
Time = DateTime.UtcNow
};
foreach (var pocId in deviceExistPointOfCareIds)
{
var data = await _pointOfCareService.GetInfo(pocId, null, true);
if (data != null && data.Patient?.Id != null)
{
obs.PatientId = data.Patient.Id;
obs.Patient = data.Patient;
switch (eventClickType)
{
case ClickType.SingleClick:
if(settingsAction.ValueOnSingleClick == null) return;
obs.Value = settingsAction.ValueOnSingleClick;
break;
case ClickType.DoubleClick:
if(settingsAction.ValueOnDoubleClick == null) return;
obs.Value = settingsAction.ValueOnDoubleClick;
break;
case ClickType.Hold:
if(settingsAction.ValueOnHoldClick == null) return;
obs.Value = settingsAction.ValueOnHoldClick;
break;
}
// Process Obs on service
await _alarmService.ProcessAlarmObservations(new List<PatientObservationAlarm>{obs},new List<PatientObservation>(){new (){Name = configObs.Name, Value = obs.Value, Code = obsData.Code, CodingSystem = obsData.CodingSystem}}, data.Patient, DateTime.UtcNow, obsData);
}
}
}
private async Task SendObservationOnAction(
DeviceAction settingsAction,
ClickType eventClickType,
List<ObjectId> deviceExistPointOfCareIds)
{
var configObs = await _configObservationService.GetConfigById(settingsAction.ConfigObservationId);
if(configObs == null) return;
var obsData = new ObservationData
{
Code = configObs.Code,
CodingSystem = configObs.CodingSystem,
Time = DateTime.UtcNow,
Text = configObs.Name,
};
var obs = new PatientObservation
{
MessageTime = DateTime.UtcNow,
Persist = true,
Code = obsData.Code,
CodingSystem = obsData.CodingSystem,
Name = configObs.Name,
Time = DateTime.UtcNow
};
foreach (var pocId in deviceExistPointOfCareIds)
{
var data = await _pointOfCareService.GetInfo(pocId, null, true);
if (data != null && data.Patient?.Id != null)
{
obs.PatientId = data.Patient.Id;
obs.Patient = data.Patient;
switch (eventClickType)
{
case ClickType.SingleClick:
if(settingsAction.ValueOnSingleClick == null) return;
obs.Value = settingsAction.ValueOnSingleClick;
break;
case ClickType.DoubleClick:
if(settingsAction.ValueOnDoubleClick == null) return;
obs.Value = settingsAction.ValueOnDoubleClick;
break;
case ClickType.Hold:
if(settingsAction.ValueOnHoldClick == null) return;
obs.Value = settingsAction.ValueOnHoldClick;
break;
}
// Process Obs on service
_observationService.ProcessObservations(new List<PatientObservation>{obs}, data.Patient, DateTime.UtcNow, obsData);
}
}
}
}
@@ -0,0 +1,363 @@
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.AppSettings;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Services;
public class DiagnosisService : IDiagnosisService
{
private readonly ILocalAuditService _auditService;
private readonly Lazy<ICalculatedObservationsService> _calculatedObservations;
private readonly IClientMessageService _clientMessageService;
private readonly IDiagnosisArchiveRepository _diagnosisArchiveRepository;
private readonly List<string> _diagnosisCode = [];
private readonly IDiagnosisRepository _diagnosisRepository;
private readonly string _diagnosisSystem;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<DiagnosisService> _logger;
private readonly Lazy<IPatientService> _patientService;
private readonly ISubscribersService _subscribersService;
private readonly IUnitService _unitService;
public DiagnosisService(
Lazy<IPatientService> patientService,
IOptions<ApiSettings> apiSettings,
IDiagnosisRepository diagnosisRepository,
IDiagnosisArchiveRepository diagnosisArchiveRepository,
ILogger<DiagnosisService> logger,
IClientMessageService clientMessageService,
ISubscribersService subscribersService,
Lazy<ICalculatedObservationsService> calculatedObservations,
IHttpContextAccessor httpContextAccessor,
ILocalAuditService auditService,
IUnitService unitService)
{
_patientService = patientService;
_diagnosisRepository = diagnosisRepository;
_diagnosisArchiveRepository = diagnosisArchiveRepository;
_logger = logger;
_clientMessageService = clientMessageService;
_subscribersService = subscribersService;
_calculatedObservations = calculatedObservations;
_unitService = unitService;
_httpContextAccessor = httpContextAccessor;
_auditService = auditService;
_diagnosisSystem = apiSettings.Value.DiagnosisSystem ?? "CUSTOM";
if (apiSettings.Value.DiagnosisCode.Any())
_diagnosisCode = apiSettings.Value.DiagnosisCode;
}
// private async Task SendBroadcast(PatientDiagnosis diagnosis)
// {
// var patient = await _patientService.Value.FindById(diagnosis.PatientId);
// if (patient == null) return;
//
//
// var subscribers = _subscribersService.GetSubscribers().Where(s =>
// (s.SubscriptionType == SubscriptionType.Box && s.Box == patient.Bed &&
// s.Section == patient.UnitString) ||
// (s.SubscriptionType == SubscriptionType.Section && s.Section == patient.UnitString)).ToList();
// subscribers.ForEach(Action);
// return;
//
// async void Action(WsSubscriber subscriber) =>
// await _clientMessageService.SendAsync(subscriber.Id, OperationType.Diagnosis, diagnosis);
// }
public async Task Archive(Patient patient)
{
await ArchiveByPatientId(patient.Id);
}
public async Task ArchiveByPatientId(ObjectId id)
{
_logger.LogDebug("Archive Diagnoses by Patient Id {id}", id);
using (var cursor = await FindByPatientIdAsync(id))
{
while (await cursor.MoveNextAsync())
foreach (var current in cursor.Current)
await _diagnosisArchiveRepository.InsertOneAsync(current);
}
await DeleteByPatientId(id);
}
public async Task DeleteByPatientId(ObjectId id)
{
_logger.LogDebug("Delete Diagnoses by Patient Id {id}", id);
var oldPatient = await _diagnosisRepository.GetByPatient(id);
await _diagnosisRepository.DeleteByPatientId(id);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, oldPatient, null);
}
public async Task<List<PatientDiagnosis>> GetByPatientId(ObjectId patientId)
{
var diagnosis = await _diagnosisRepository.GetByPatient(patientId);
return diagnosis;
}
public Task SaveRequest(ApiRequest apiRequest)
{
return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest, null); }));
}
public Task SaveRequestAsync(ApiRequest apiRequest)
{
return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest, null); }));
}
public async Task ProcessDiagnosisObservation(ApiRequest apiRequest, Patient patient)
{
_logger.LogDebug("INSERT DiagnosisObservation from obsservation");
var time = apiRequest.ObservationData?.Time;
if (time == null)
_logger.LogWarning("Processing diagnosis without time. Set Datetime now {DateTimeNow}. ", DateTime.Now);
var obs = new PatientDiagnosis
{
CodingSystem = _diagnosisSystem,
Time = time ?? DateTime.Now,
PatientId = patient.Id,
MessageTime = apiRequest.MessageTime
};
if (apiRequest.Observations == null)
{
_logger.LogError("ApiRequest Observations null. ");
return;
}
for (var i = 0; i <= apiRequest.Observations.Count - 1; i++)
{
var value = apiRequest.Observations[i].Value;
var strValue = value.ToString() ?? "null";
switch (apiRequest.Observations[i].Code)
{
case "272099008":
obs.Description = strValue;
break;
case "1000000013":
obs.Label = strValue;
break;
case "1000000014":
obs.Code = strValue;
break;
case "394731006":
obs.State = strValue;
break;
case "272125009":
obs.Category = strValue;
break;
case "398201009":
if (DateTime.TryParse(apiRequest.Observations[i].Value.ToString(), out var startTime))
obs.StartTime = startTime;
break;
case "397898000":
if (DateTime.TryParse(apiRequest.Observations[i].Value.ToString(), out var endTime))
obs.EndTime = endTime;
break;
}
}
await InsertDiagnosis(obs);
}
public async Task SaveRequest(ApiRequest apiRequest, Patient? patient)
{
if (patient == null)
{
_logger.LogDebug("message:ApiRequest Diagnosis");
if (string.IsNullOrEmpty(apiRequest.PatientNumber) &&
string.IsNullOrEmpty(apiRequest.Location?.UnitName))
{
_logger.LogDebug("person and PointOfCare are nulls");
throw new ApiRequestException("person and PointOfCare are nulls");
}
_logger.LogDebug("patientNumber: {apiRequestpatientNumber} location: {apiRequestlocation}",
apiRequest.PatientNumber, apiRequest.Location);
_logger.LogDebug("RequestType: {apiRequesttype}", apiRequest.Type);
patient = await _patientService.Value.FindPatientByApiRequest(apiRequest);
}
if (patient == null)
{
// NO PATIENTS OR LOCATIONS WERE FOUND
_logger.LogWarning(
"Observation for patient: {apiRequestpatientNumber} with location: {apiRequestlocation} not found, ignoring",
apiRequest.PatientNumber, apiRequest.Location);
return;
}
var unitConfig = await _unitService.FindById(patient.UnitId);
if (!Hl7Utils.ManageAutoAdt(unitConfig, null, _logger, "ORU Diagnosis")) return;
switch (apiRequest.Type)
{
//* ORU_R01 - Unsolicited transmission of an observation message
//* ORU_R40 - Unsolicited transmission of an alert observation message
case "ORU_R01":
case "ORU_R40":
// OBSERVATIONS
if (apiRequest.Observation != null && apiRequest.Observations?.FirstOrDefault() == null)
apiRequest.Observations = [apiRequest.Observation];
if (apiRequest.Observations != null)
{
var obrcode = apiRequest.ObservationData?.Code;
if (apiRequest.ObservationData?.Value != null)
apiRequest.Observations.Add(new PatientObservation
{ Value = apiRequest.ObservationData.Value });
if (obrcode != null && _diagnosisCode.Contains(obrcode))
_ = ProcessDiagnosisObservation(apiRequest, patient);
}
break;
default:
_logger.LogWarning("ApiRequest type {apiRequesttype} sis not valid for Diagnosis",
apiRequest.Type);
throw new ApiRequestException("ApiRequest type " + apiRequest.Type +
" is not valid for Diagnosis");
}
}
public async Task ProcessDiagnosis(List<PatientDiagnosis> diagnosis, Patient patient, DateTime messageTime)
{
_logger.LogDebug("INSERT DiagnosisObservation from diagnosis");
foreach (var d in diagnosis)
{
d.PatientId = patient.Id;
d.Time = messageTime;
await InsertDiagnosis(d);
}
}
public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
{
await _diagnosisRepository.UpdateManyObjectId(nameId, id, oldId);
}
public async Task<List<PatientDiagnosis>> GetByPatient(ObjectId id)
{
return await _diagnosisRepository.GetByPatient(id);
}
public async Task Insert(PatientDiagnosis diagnosis)
{
_logger.LogDebug("Insert {diagnosis}", diagnosis);
var diag = await MapDiagnosis(diagnosis);
if (diag != null)
{
await _diagnosisRepository.InsertOneAsync(diag);
await SendBroadcast(diag);
}
}
private async Task<PatientDiagnosis?> MapDiagnosis(PatientDiagnosis diagnosis)
{
var diagnosis2 = await _calculatedObservations.Value.Map(diagnosis);
if (diagnosis2 == null) _logger.LogDebug("Mapping {diagnosis}: Ignored", diagnosis2);
return diagnosis2;
}
private async Task SendBroadcast(PatientDiagnosis diagnosis)
{
var patient = await _patientService.Value.FindById(diagnosis.PatientId);
if (patient == null) return;
var displaySubscribers = _subscribersService.GetSubscribers().Where(s =>
!s.Locations.IsNullOrEmpty() && s.Locations.Any(c =>
c.UnitName == patient.Location.UnitName &&
c.Bed == patient.Location.Bed &&
c.Room == patient.Location.Room
)).ToList();
displaySubscribers.ForEach(Action);
return;
void Action(WsSubscriber subscriber)
{
_clientMessageService.SendAsync(subscriber.Id, OperationType.Diagnosis, diagnosis);
}
}
public Task<IAsyncCursor<PatientDiagnosis>> FindByPatientIdAsync(ObjectId patientId)
{
return _diagnosisRepository.FindByPatientIdAsync(patientId);
}
public async Task InsertDiagnosis(PatientDiagnosis diagnosis)
{
try
{
_logger.LogDebug("Insert {diagnosis}", diagnosis);
var diag = await MapDiagnosis(diagnosis);
if (diag == null)
{
_logger.LogError("Mepped diagnosis is null. Diagnosis: {diagnosis}", diagnosis);
}
else
{
var dgdb = await _diagnosisRepository.FindByPatientIdAndCode(diag.PatientId, diag.Code,
diag.CodingSystem);
if (dgdb != null)
{
var auxDgdb = dgdb;
diag.Id = dgdb.Id;
diag.Time = dgdb.Time;
diag.UpdateDate = diagnosis.Time;
await _diagnosisRepository.UpdateOneAsync(diag.Id, diag);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, auxDgdb, dgdb);
}
else
{
await _diagnosisRepository.InsertOneAsync(diagnosis);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, diagnosis);
}
_ = SendBroadcast(diagnosis);
}
}
catch (Exception ex)
{
_logger.LogError("Error inserting diagnosis. Diagnosis: {diagnosis}. Exception:{ex}", diagnosis, ex);
}
}
}
@@ -0,0 +1,400 @@
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;
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;
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;
}
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);
}
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);
}
}
public async Task<long> CountDischargesByUnitId(ObjectId unitId)
{
return await _dischargeRepository.CountByUnitId(unitId);
}
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;
}
public async Task<IEnumerable<Discharge>> GetDischargesAsync()
{
return await _dischargeRepository.FindAll();
}
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;
}
}
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;
}
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;
}
}
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;
}
}
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;
}
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);
}
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);
}
}
public Task SaveRequestAsync(ApiRequest apiRequest)
{
return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest); }));
}
// Revisar
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);
}
}
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);
}
}
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);
}
}
public async Task DeleteDischargesByUnitId(ObjectId unitId)
{
await _dischargeRepository.DeleteByUnitId(unitId);
}
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;
}
}
@@ -0,0 +1,608 @@
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.DTO.Display;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using adas_core.Domain.Utils;
using Microsoft.AspNetCore.Http;
using MongoDB.Bson;
using MongoDB.Driver;
using Newtonsoft.Json;
using Serilog;
using DisplayConfig = adas_core.Domain.Models.MongoModels.DisplayConfig;
namespace adas_core.Application.Services;
public class DisplayConfigService(
IDisplayConfigRepository displayConfigRepository,
Lazy<IDisplayService> displayService,
ISubscribersService subscribersService,
IClientMessageService clientMessageService,
IHttpContextAccessor httpContextAccessor,
ILocalAuditService auditService,
IMasterListServiceFactory masterListServiceFactory,
IDisplayCardConfigRepository displayCardConfigRepository,
IDisplayDetailConfigRepository displayDetailConfigRepository,
IDisplayChartConfigRepository displayChartRepository)
: IDisplayConfigService
{
public async Task<List<DisplayConfig>> GetAll()
{
var result = await displayConfigRepository.GetAll();
var resultToReturn = new List<DisplayConfig>();
foreach (var config in result)
{
var c = await AddDisplaySectionMinimal(config);
if (c != null) resultToReturn.Add(c);
}
return resultToReturn;
}
public async Task<PaginationResponse<DisplayConfigMinimalResponse>> GetAllCompactPaginated(PaginationFilter filter)
{
var result = displayConfigRepository.GetAllPaginated(filter);
var count = await result.CountDocumentsAsync();
var resultToReturn = new List<DisplayConfigMinimalResponse>();
var data = await result.Skip((filter.PageNumber - 1) * filter.PageSize)
.Limit(filter.PageSize)
.ToCursorAsync();
var dataList = await data.ToListAsync();
foreach (var config in dataList)
{
var isInUse = await displayService.Value.IsDisplayConfigInUse(config.Id);
resultToReturn.Add(new DisplayConfigMinimalResponse(config, isInUse));
}
return new PaginationResponse<DisplayConfigMinimalResponse>(resultToReturn, filter.PageNumber, filter.PageSize,
count);
}
public async Task<List<DisplayConfig>> GetByType(DisplayConfigEnums.DisplayType type)
{
var result = await displayConfigRepository.GetByType(type);
var resultToReturn = new List<DisplayConfig>();
foreach (var config in result)
{
var c = await AddDisplaySectionMinimal(config);
if (c != null) resultToReturn.Add(c);
}
return resultToReturn;
}
public async Task<DisplayConfig> GetById(ObjectId id)
{
return await AddDisplaySectionMinimal(await displayConfigRepository.GetById(id)) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
}
public async Task<DisplayConfig?> GetById(ObjectId? configId, ObjectId unitId,
DisplayConfigEnums.DisplayType displayType)
{
if (configId.HasValue)
{
DisplayConfig? currentConfig;
DisplayConfig? defaultConfig;
try
{
currentConfig = await GetById(configId.Value);
}
catch (NotFoundException)
{
currentConfig = null;
}
try
{
defaultConfig = await GetDefaultByUnitIdAndType(unitId, displayType);
}
catch (NotFoundException)
{
defaultConfig = null;
}
if (defaultConfig != null && currentConfig != null) return currentConfig.MergeConfig(defaultConfig);
return defaultConfig ?? currentConfig;
}
return await GetDefaultByUnitIdAndType(unitId, displayType);
}
public async Task<DisplayConfig?> InsertOne(DisplayConfig config)
{
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, config);
return await displayConfigRepository.InsertOneAsyncAndReturn(config);
}
public async Task<DisplayConfig?> InsertOneMinimal(CreateDisplayConfigDto config)
{
DisplayConfig newDisplayConfig;
if (config.Type == DisplayConfigEnums.DisplayType.DisplayNurse)
newDisplayConfig = new DisplayNurse
{
Hospital = config.Hospital,
Type = config.Type
};
else
newDisplayConfig = new DisplayConfig
{
Hospital = config.Hospital,
Type = config.Type
};
newDisplayConfig.Id = ObjectId.GenerateNewId();
var result = await displayConfigRepository.InsertOneAsyncAndReturn(newDisplayConfig);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, newDisplayConfig);
return result;
}
public async Task<DisplayConfig> InsertOneTest()
{
var d = new DisplayNurse
{
Type = DisplayConfigEnums.DisplayType.DisplayNurse
};
await displayConfigRepository.InsertOneAsyncAndReturn(d);
var e = new SmartDisplay
{
Type = DisplayConfigEnums.DisplayType.SmartDisplay
};
await displayConfigRepository.InsertOneAsyncAndReturn(e);
return d;
}
public async Task<DisplayConfig?> UpdateConfig(ObjectId displayConfigId, object newDisplayConfig)
{
var baseType = JsonConvert.DeserializeObject<DisplayConfigDto>(newDisplayConfig.ToString()!);
// var cardConfigUpdate = await UpdateDisplayCardConfig(displayConfigId, baseType);
var oldDisplayConfig = await displayConfigRepository.GetById(displayConfigId);
switch (baseType!.Type)
{
case DisplayConfigEnums.DisplayType.SmartDisplay:
var smartConfigToReturn = await displayConfigRepository.UpdateSmartDisplay(displayConfigId,
JsonConvert.DeserializeObject<SmartDisplay>(newDisplayConfig.ToString()!));
if (smartConfigToReturn != null)
{
// smartConfigToReturn.CardConfig = cardConfigUpdate;
SendSmartDisplayConfigBroadcast(displayConfigId, smartConfigToReturn,
oldDisplayConfig as SmartDisplay);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
newDisplayConfig);
return smartConfigToReturn;
}
break;
case DisplayConfigEnums.DisplayType.DisplayNurse:
var obsNurseList = masterListServiceFactory.StringNurseObs();
var displayConfigUpdate =
JsonConvert.DeserializeObject<DisplayNurseDto>(newDisplayConfig.ToString()!);
var nurseConfigToReturn =
await displayConfigRepository.UpdateDisplayNurse(displayConfigId, displayConfigUpdate,
obsNurseList);
if (nurseConfigToReturn != null)
{
// nurseConfigToReturn.CardConfig = cardConfigUpdate;
SendDisplayConfigBroadcast(displayConfigId, OperationType.UpdateDisplayConfig, nurseConfigToReturn);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
nurseConfigToReturn);
return nurseConfigToReturn;
}
break;
default:
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundNoMatches);
}
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
}
public Task<bool> UpdateFieldList(ObjectId objectIdConfigDisplay, List<Field> fields)
{
return displayConfigRepository.UpdateFieldList(objectIdConfigDisplay, fields);
}
public async Task<bool> UpdateConfigColor(ObjectId objectIdConfigDisplay, ColorConfig colorConfig)
{
var oldDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
if (oldDisplayConfig.ColorConfig == null)
{
oldDisplayConfig.ColorConfig = new ColorConfig();
await displayConfigRepository.UpdateOneAsync(objectIdConfigDisplay, oldDisplayConfig);
}
var result = await displayConfigRepository.UpdateConfigColor(objectIdConfigDisplay, colorConfig);
var newDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
newDisplayConfig);
return result;
}
public async Task<bool> UpdateHeaderConfig(ObjectId objectIdConfigDisplay, HeaderConfig headerConfig)
{
var oldDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
var result = await displayConfigRepository.UpdateHeaderConfig(objectIdConfigDisplay, headerConfig);
var newDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
newDisplayConfig);
return result;
}
public async Task<bool> UpdateSetHomeBanner(ObjectId objectIdConfigDisplay, List<BannerItem> bannerItems)
{
var oldDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
var result = await displayConfigRepository.UpdateSetHomeBanner(objectIdConfigDisplay, bannerItems);
var newDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
newDisplayConfig);
return result;
}
public async Task<bool> UpdateBaseConfig(DisplayConfig baseConfig)
{
var oldDisplayConfig = await displayConfigRepository.GetById(baseConfig.Id) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
var result = await displayConfigRepository.UpdateBaseConfig(baseConfig.Id, baseConfig);
var newDisplayConfig = await displayConfigRepository.GetById(baseConfig.Id) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
newDisplayConfig);
return result;
}
public async Task<bool> UpdateDisplayConfigHospitalName(ObjectId objectIdConfigDisplay, string name)
{
var oldDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
var result = await displayConfigRepository.UpdateDisplayConfigHospitalName(objectIdConfigDisplay, name);
var newDisplayConfig = await displayConfigRepository.GetById(objectIdConfigDisplay) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplayConfig,
newDisplayConfig);
return result;
}
public async Task<bool> DeleteDisplayConfig(ObjectId objectIdConfigDisplay)
{
var displays = await displayService.Value.GetByConfigId(objectIdConfigDisplay);
if (displays.Count > 0)
{
var type = displays.First().Type;
var defaultConfig = await GetDefaultConfig(type) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
foreach (var display in displays)
_ = await displayService.Value.UpdateConfigId(display, defaultConfig.Id) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
}
var result = await displayConfigRepository.DeleteDisplayConfig(objectIdConfigDisplay);
if (result != null)
{
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, result, null);
return true;
}
return false;
}
public async Task<List<DisplayConfigLocationDto>> GetDisplayConfigLocations(ObjectId objectIdConfigDisplay)
{
return await displayService.Value.GetDisplayConfigLocations(objectIdConfigDisplay);
}
public async Task<List<DisplayConfigMinimalResponse>> GetAllCompact()
{
return await displayConfigRepository.GetAllCompact();
}
public async Task<DisplayConfig?> InsertOneWithTemplate(string objectId,
DisplayConfigEnums.DisplayType configType, string? configHospital)
{
if (ObjectId.TryParse(objectId, out var objectIdConfigDisplay))
{
var template = await GetById(objectIdConfigDisplay);
switch (configType)
{
case DisplayConfigEnums.DisplayType.StandarDisplay:
if (template is StandarDisplay standardTemplate)
{
var standarConfigg = new StandarDisplay
{ Hospital = configHospital, Type = DisplayConfigEnums.DisplayType.StandarDisplay };
standarConfigg.MergeConfig(standardTemplate);
await InsertOne(standarConfigg);
return standarConfigg;
}
break;
case DisplayConfigEnums.DisplayType.DisplayNurse:
if (template is DisplayNurse nurseTemplate)
{
var nurseConfig = new DisplayNurse
{ Hospital = configHospital, Type = DisplayConfigEnums.DisplayType.DisplayNurse };
nurseConfig.MergeConfig(nurseTemplate);
await InsertOne(nurseConfig);
return nurseConfig;
}
break;
case DisplayConfigEnums.DisplayType.SmartDisplay:
if (template is SmartDisplay smartTemplate)
{
var smartConfig = new SmartDisplay
{ Hospital = configHospital, Type = DisplayConfigEnums.DisplayType.DisplayNurse };
smartConfig.MergeConfig(smartTemplate);
await InsertOne(smartConfig);
return smartConfig;
}
break;
default:
return null;
}
}
return null;
}
public async Task<bool> UpdateCardConfig(CardConfig baseConfig)
{
var result = await displayCardConfigRepository.UpdateOne(baseConfig);
if (result.Changes > 0)
{
var displayConfigs = await displayConfigRepository.GetAllByCardConfigIdAndRotating(baseConfig.Id);
foreach (var displayConfig in displayConfigs)
{
await displayConfigRepository.UpdateDisplayNurse(displayConfig,
new DisplayNurseDto() { CardConfig = result.Data }, masterListServiceFactory.StringNurseObs());
}
var displays = await displayConfigRepository.GetAllByCardConfigId(baseConfig.Id);
foreach (var display in displays)
SendDisplayConfigBroadcast(display, OperationType.UpdateCardDisplayConfig, result.Data);
return true;
}
return false;
}
public async Task<bool> UpdateDetailConfig(CardDetailsConfig baseConfig)
{
var result = await displayDetailConfigRepository.UpdateOne(baseConfig);
var displays = await displayConfigRepository.GetAllByCardDetailConfigId(baseConfig.Id);
foreach (var display in displays)
SendDisplayConfigBroadcast(display, OperationType.UpdateDetailDisplayConfig, result.Data);
if (result.Changes > 0) return true;
return false;
}
public async Task<bool> UpdateChartConfig(ChartConfig baseConfig)
{
var result = await displayChartRepository.UpdateOne(baseConfig);
if (result.Changes > 0) return true;
return false;
}
public async Task<bool> DeleteChartConfig(ObjectId objectIdConfigDisplay)
{
var res = await displayChartRepository.DeleteOne(objectIdConfigDisplay);
if (res != null)
{
await UpdateDeletedChartConfig(objectIdConfigDisplay);
return true;
}
return false;
}
public async Task<ChartConfig?> GetChartConfig(ObjectId objectIdConfigChart)
{
return await displayChartRepository.GetById(objectIdConfigChart);
}
public async Task<CardDetailsConfig?> InsertCardDetailConfig(CreateDisplayConfigCardDto updateDisplayConfigNameDto)
{
if (updateDisplayConfigNameDto.DetailConfig == null) return null;
var result =
await displayDetailConfigRepository.InsertOneAsyncAndReturn(updateDisplayConfigNameDto.DetailConfig);
if (updateDisplayConfigNameDto.DisplayConfigId != null && result != null)
{
var res = await UpdateDetailConfigId(updateDisplayConfigNameDto.DisplayConfigId, result.Id);
if (res)
SendDisplayConfigBroadcast(updateDisplayConfigNameDto.DisplayConfigId.Value,
OperationType.UpdateDetailDisplayConfig, result);
}
return result;
}
public async Task<ChartConfig?> InsertChartConfig(CreateDisplayConfigCardDto updateDisplayConfigNameDto)
{
if (updateDisplayConfigNameDto.ChartConfig == null) return null;
var result = await displayChartRepository.InsertOneAsyncAndReturn(updateDisplayConfigNameDto.ChartConfig);
if (updateDisplayConfigNameDto.DisplayConfigId != null && result != null)
{
var res = await AddChartId(updateDisplayConfigNameDto.DisplayConfigId, result.Id);
if (res)
SendDisplayConfigBroadcast(updateDisplayConfigNameDto.DisplayConfigId.Value,
OperationType.UpdateChartConfig, result);
}
return result;
}
public async Task<CardConfig?> InsertCardConfig(CreateDisplayConfigCardDto updateDisplayConfigNameDto)
{
if(updateDisplayConfigNameDto.CardConfig == null) return null;
var result = await displayCardConfigRepository.InsertOneAsyncAndReturn(updateDisplayConfigNameDto.CardConfig);
if (updateDisplayConfigNameDto.DisplayConfigId != null && result != null)
{
var res = await UpdateCardConfigId(updateDisplayConfigNameDto.DisplayConfigId, result.Id);
if (res)
SendDisplayConfigBroadcast(updateDisplayConfigNameDto.DisplayConfigId.Value,
OperationType.UpdateDetailDisplayConfig, result);
}
return result;
}
public async Task<List<CardConfig>> GetCardConfigAll()
{
return await displayCardConfigRepository.GetAll();
}
public async Task<CardConfig?> GetCardConfigById(ObjectId id)
{
return await displayCardConfigRepository.GetById(id);
}
public async Task<DisplayConfig?> GetDefaultConfig(DisplayConfigEnums.DisplayType type)
{
return await displayConfigRepository.GetDefault(type);
}
private async Task<DisplayConfig?> GetDefaultByUnitIdAndType(ObjectId unitId,
DisplayConfigEnums.DisplayType displayType)
{
return
await displayConfigRepository.GetDefaultByUnitIdAndType(unitId,
displayType); //?? throw new NotFoundException(ErrorMessage.NotFound_ResourceMissing);
}
private async Task<DisplayConfig?> AddDisplaySectionMinimal(DisplayConfig? displayConfig)
{
if (displayConfig == null) return null;
var displayConfigAux = displayConfigRepository.GetById(displayConfig.Id) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
if (displayConfig.Type == DisplayConfigEnums.DisplayType.SmartDisplay &&
!displayConfig.DisplaySectionIdList.IsNullOrEmpty())
{
foreach (var displayId in displayConfig.DisplaySectionIdList)
{
var d = await displayService.Value.GetById(displayId);
if (d != null)
{
var minimalDisplay = new MinimalDisplaySection
{
Id = displayId,
Name = d.Name
};
displayConfig.DisplaySectionList.Add(minimalDisplay);
}
}
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, displayConfigAux,
displayConfig);
}
return displayConfig;
}
private async Task UpdateDeletedChartConfig(ObjectId deletedId)
{
await displayConfigRepository.UpdateDeletedChartConfig(deletedId);
}
private async Task<bool> AddChartId(ObjectId? displayConfigId, ObjectId resultId)
{
var result = await displayConfigRepository.AddChartId(displayConfigId, resultId);
return result;
}
private async Task<bool> UpdateCardConfigId(ObjectId? displayConfigId, ObjectId? resultId)
{
var result = await displayConfigRepository.UpdateCardConfigId(displayConfigId, resultId);
return result;
}
private async Task<bool> UpdateDetailConfigId(ObjectId? displayConfigId, ObjectId? resultId)
{
var result = await displayConfigRepository.UpdateDetailConfigId(displayConfigId, resultId);
return result;
}
private async void SendDisplayConfigBroadcast(ObjectId displayConfigId, OperationType operationType,
object? newDisplayConfig)
{
try
{
var listDisplayId = await displayService.Value.GetByConfigId(displayConfigId);
var listDisplayIdList = listDisplayId.Select(c => c.Id).ToList();
var subscribers = subscribersService.GetSubscribers().Where(s =>
s.DisplayId != null && listDisplayIdList.Contains((ObjectId)s.DisplayId)).ToList();
foreach (var sub in subscribers)
_ = clientMessageService.SendAsync(sub.Id, operationType,
newDisplayConfig);
}
catch (Exception e)
{
Log.Error("Error sending update for DisplayNurse config: {message}", e.Message);
//throw new ConflictException(ErrorMessage.Conflict_UpdateFailed, e);
}
}
private async void SendSmartDisplayConfigBroadcast(ObjectId displayConfigId, SmartDisplay? newDisplayConfig,
SmartDisplay? oldDisplayConfig)
{
try
{
var listDisplayId = await displayService.Value.GetByConfigId(displayConfigId);
var listDisplayIdList = listDisplayId.Select(c => c.Id).ToList();
var subscribers = subscribersService.GetSubscribers().Where(s =>
s.DisplayId != null && listDisplayIdList.Contains((ObjectId)s.DisplayId)).ToList();
if (newDisplayConfig != null && oldDisplayConfig != null)
SendSmartDisplayConfigUpdate(subscribers, oldDisplayConfig, newDisplayConfig);
else Log.Error("Error sending update for SmartDisplay config config is null");
}
catch (Exception e)
{
Log.Error("Error sending update for SmartDisplay config: {message}", e.Message);
//throw new ConflictException(ErrorMessage.Conflict_UpdateFailed, e);
}
finally
{
// Enviar un mensaje a todos los clientes para que actualicen la configuracin del display
var displayConfig = await displayConfigRepository.GetById(displayConfigId);
if (displayConfig != null)
_ = clientMessageService.SendToAllAsync(OperationType.UpdateDisplayConfig, displayConfig);
}
}
private void SendSmartDisplayConfigUpdate(List<WsSubscriber> subscribers,
SmartDisplay? oldDisplayDisplayConfig, SmartDisplay? newDisplayDisplayConfig)
{
// Obtener las propiedades que han cambiado
var differentProperties = oldDisplayDisplayConfig?.GetDifferentProperties(newDisplayDisplayConfig);
// Enviar un mensaje a los clientes por cada propiedad que haya cambiado
if (differentProperties != null)
foreach (var property in differentProperties)
foreach (var sub in subscribers)
_ = clientMessageService.SendAsync(sub.Id, OperationType.UpdateDisplayConfig,
newDisplayDisplayConfig?.GetType().GetProperty(property)?.GetValue(newDisplayDisplayConfig));
}
}
@@ -0,0 +1,712 @@
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.AppSettings;
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.DTO.Display;
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 adas_core.Domain.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Services;
public class DisplayService(
IDisplayRepository displayRepository,
IPointOfCareService pointOfCareService,
Lazy<IUnitService> unitService,
IDisplayConfigService displayConfigService,
ISubscribersService subscribersService,
IClientMessageService clientMessageService,
IUserRepository userRepository,
IAuthService authorityService,
ILogger<DisplayService> logger,
IHttpContextAccessor httpContextAccessor,
ILocalAuditService auditService,
Lazy<IPermissionService> permissionService,
ICacheService cacheService,
IOptions<CacheSettings> cacheSettings)
: IDisplayService
{
private readonly CacheSettings? _cacheSettings = cacheSettings.Value ;
#region Methods
#region Create
public async Task<Display> InsertOne(Display display)
{
var defaultConfig = await displayConfigService.GetDefaultConfig(display.Type) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
display.DisplayConfigId = defaultConfig.Id;
await displayRepository.InsertOneAsync(display);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, display);
return display;
}
public async Task<Display> InsertOneTest()
{
var d = new Display
{
Name = "DisplayTEST",
UnitId = new ObjectId("65ba5f89d5ba8e273cf9cd96"),
DisplayConfigId = new ObjectId("45ba5f89d5ba8e273cf9cd96")
};
await displayRepository.InsertOneAsync(d);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, d);
return d;
}
#endregion
#region Read
public async Task<List<DisplayMinimalDto>> GetAllCompact()
{
var result = await displayRepository.GetAll();
List<DisplayMinimalDto> listToReturn = [];
foreach (var res in result) listToReturn.Add(new DisplayMinimalDto(res));
return listToReturn;
}
public Task<List<Display>> GetAll(string? userName)
{
throw new NotImplementedException();
}
public async Task<PaginationResponse<Display>> GetPaginatedDisplays(PaginationFilter filter)
{
var result = displayRepository.GetPaginatedDisplays(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<Display>(dataList, filter.PageNumber, filter.PageSize, count);
}
public async Task<List<DisplayWithPermissionsDto>> GetAllByUser(string? userName)
{
var start = DateTime.Now;
var listToReturn = new List<DisplayWithPermissionsDto>();
if (userName == null) return listToReturn;
var user = await userRepository.GetByUserAndAuthoritesName(userName);
if (user == null) return listToReturn;
if (user.Authorization == null || user.Authorization.Count == 0)
user.Authorization = await authorityService.GetUserAuthorities(user.Id);
if (user.Authorization == null)
return listToReturn;
foreach (var e in user.Authorization)
if (e.UnitId != null)
{
var isParsed = ObjectId.TryParse(e.UnitId, out var dId);
if (isParsed)
{
var dis = await displayRepository.GetByUnitId(dId);
foreach (var display in dis)
{
var toAdd = await GetInfo(display.Id, userName, user.Authorization,null, false, false, false, false);
if (toAdd != null)
{
var newDto = new DisplayWithPermissionsDto
{
Display = toAdd,
Permissions =
await permissionService.Value.GetPermissionsForUnit(dId.ToString(), user) ??
throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission)
};
listToReturn.Add(newDto);
}
}
}
}
else if (e.DisplayId != null && !FindDisplayInPerms(e.DisplayId, listToReturn))
{
var isParsed = ObjectId.TryParse(e.DisplayId, out var dId);
if (isParsed)
{
var toAdd = await GetInfo(dId, userName, user.Authorization, null, false, false, false, false);
if (toAdd != null)
{
var newDto = new DisplayWithPermissionsDto
{
Display = toAdd,
Permissions = await permissionService.Value.GetPermissionsForDisplay(toAdd, user)
};
listToReturn.Add(newDto);
}
}
}
var end = DateTime.Now;
logger.LogDebug("Finished GetAllByUser Displays for user {user} in {TotalSeconds:F1} seconds", userName,
(end - start).TotalSeconds);
return listToReturn;
}
private static bool FindDisplayInPerms(string displayId, List<DisplayWithPermissionsDto> perms)
{
return perms.Any(p => p.Display != null && p.Display.Id.ToString() == displayId);
}
public async Task<List<Display>> GetByType(DisplayConfigEnums.DisplayType type)
{
var configs = await displayConfigService.GetByType(type);
var listToReturn = new List<Display>();
foreach (var config in configs)
{
var displayToAdd = await displayRepository.GetByConfigId(config.Id);
displayToAdd.ForEach(c => c.DisplayConfig = config);
if (!displayToAdd.IsNullOrEmpty()) listToReturn.AddRange(displayToAdd);
}
return listToReturn;
}
public async Task<List<Display>> GetByPointOfCare(PointOfCare pointOfCare)
{
return await displayRepository.GetByPointOfCare(pointOfCare);
}
public Task<List<Display>> GetByConfigId(ObjectId configId)
{
return displayRepository.GetByConfigId(configId);
}
public Task<List<Display>> GetByCardConfigId(ObjectId configId)
{
return displayRepository.GetByCardConfigId(configId);
}
public async Task<Display?> GetByName(string name)
{
return await displayRepository.GetByName(name) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
}
public async Task<Display?> GetById(ObjectId id)
{
return await displayRepository.GetById(id);
}
public async Task<DisplayWithPermissionsDto> GetByIdWithPermissions(ObjectId id, LocaleEnum localeEnum)
{
var username = JwtHelper.GetUsernameFromPrincipal(httpContextAccessor.HttpContext?.User!) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
var user = await userRepository.GetByUserName(username);
var display = await GetById(id);
if (display == null) throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
foreach (var poc in display.PointOfCareIdList)
{
var c = await pointOfCareService.GetInfo(poc, localeEnum);
if (c != null) display.PointOfCares.Add(c);
}
display.DisplayConfig =
await displayConfigService.GetById(display.DisplayConfigId, display.UnitId, display.Type);
display.DisplayConfig!.DisplaySectionList = [];
try
{
display.DisplayConfig!.DisplaySectionList =
await GetDisplaySectionByUser(display.Type, id, username, user?.Authorization);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
return new DisplayWithPermissionsDto
{
Display = display,
Permissions = await permissionService.Value.GetPermissionsForDisplay(display, user!)
};
}
public async Task<long> CountDisplaysByUnitId(ObjectId unitId)
{
return await displayRepository.CountByUnitId(unitId);
}
public async Task<Display?> GetInfo(ObjectId id,
string? userName,
List<Authorization>? authorizations,
LocaleEnum? locale,
bool fillPointOfCare = true,
bool fillPatientData = false,
bool fillDisplayList = true,
bool fillDisplayConfig = true,
CancellationToken ct = default)
{
var start = DateTime.Now;
Display? display;
if (fillDisplayConfig)
{
// Clave: display con configuración
var (key, ttl) = CacheKeys.DisplayWithConfigKeyWithTtl(_cacheSettings, id);
display = await cacheService.GetOrSetObjectAsync(
key,
async () => await BuildDisplayWithConfig(id, ct),
ttl,
ct);
}
else
{
// Clave: display base
var (key, ttl) = CacheKeys.DisplayBaseKeyWithTtl(_cacheSettings, id);
display = await cacheService.GetOrSetObjectAsync(
key,
async () => await displayRepository.GetById(id),
ttl,
ct);
}
if (display == null) return null;
// PointOfCare (cacheado en su propio servicio)
if (fillPointOfCare)
foreach (var poc in display.PointOfCareIdList)
{
var c = await pointOfCareService.GetInfo(poc, locale, fillPatientData);
if (c != null) display.PointOfCares.Add(c);
}
if (authorizations == null && userName != null)
{
var c = await userRepository.GetByUserAndAuthoritesName(userName);
authorizations = c?.Authorization;
}
// DisplayList (depende de autorizaciones → NO cacheable)
if (fillDisplayList)
if (display.DisplayConfig != null)
display.DisplayConfig.DisplaySectionList =
await GetDisplaySectionByUser(display.Type, id, userName, authorizations);
else
logger.LogError("DISPLAY CONFIG NULL on fill display list");
var end = DateTime.Now;
logger.LogDebug("Finished GetInfo Displays for user {user} in {TotalSeconds:F1} seconds", userName,
(end - start).TotalSeconds);
return display;
}
private async Task<Display?> BuildDisplayWithConfig(ObjectId id, CancellationToken ct)
{
var display = await displayRepository.GetById(id);
if (display == null) return null;
var type = display.Type;
// DisplayConfig
display.DisplayConfig =
await displayConfigService.GetById(display.DisplayConfigId, display.UnitId, type);
if (type != DisplayConfigEnums.DisplayType.SmartDisplay ||
display.DisplayConfig is not SmartDisplay { CardRotatingLayout: not null } smart)
return display;
if(smart.CardRotatingLayout== null)
return display;
foreach (var card in smart.CardRotatingLayout)
card.Data = await displayConfigService.GetCardConfigById(card.DataId)
?? new CardConfig();
return display;
}
public async Task<List<MinimalDisplaySection>> GetDisplaySectionByUser(
DisplayConfigEnums.DisplayType type,
ObjectId? currentDisplay,
string? userName,
List<Authorization>? authorizations)
{
try
{
var listToReturn = new List<MinimalDisplaySection>();
if (userName != null)
{
var user = await userRepository.GetByUserName(userName);
if (user != null)
{
var displayIdByAuthorities = authorizations ?? await authorityService.GetUserAuthorities(user.Id);
foreach (var e in displayIdByAuthorities)
{
var isParsed = ObjectId.TryParse(e.DisplayId, out var dId);
if (isParsed)
{
if (listToReturn.All(c => c.Id != dId))
{
var toAdd = await GetById(dId);
if (toAdd != null)
if (type == toAdd.Type)
{
var minDisSec = new MinimalDisplaySection
{
Name = toAdd.Name,
Id = toAdd.Id,
IsSelected = currentDisplay != null && toAdd.Id == currentDisplay
};
listToReturn.Add(minDisSec);
}
}
}
else
{
var isParsedUnitId = ObjectId.TryParse(e.UnitId, out var uId);
if (isParsedUnitId)
{
var unitDisplays = await GetByUnitId(uId);
foreach (var disp in unitDisplays)
if (type == disp.Type && listToReturn.All(c => c.Id != dId))
{
var minDisSec = new MinimalDisplaySection
{
Name = disp.Name,
Id = disp.Id,
IsSelected = currentDisplay != null && disp.Id == currentDisplay
};
listToReturn.Add(minDisSec);
}
}
}
}
}
}
return listToReturn;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
public async Task<MinimalDisplayListDto> GetAllDisplaySection()
{
var minimalDisplayListDto = new MinimalDisplayListDto();
var listDisplayNurse = await GetByType(DisplayConfigEnums.DisplayType.DisplayNurse);
foreach (var displayForAdmin in listDisplayNurse)
{
var minDisSec = new MinimalDisplaySection
{
Name = displayForAdmin.Name,
Id = displayForAdmin.Id
};
minimalDisplayListDto.DisplayNurse.Add(minDisSec);
}
var listDisplaySmart = await GetByType(DisplayConfigEnums.DisplayType.SmartDisplay);
foreach (var displayForAdmin in listDisplaySmart)
{
var minDisSec = new MinimalDisplaySection
{
Name = displayForAdmin.Name,
Id = displayForAdmin.Id
};
minimalDisplayListDto.SmartDisplay.Add(minDisSec);
}
return minimalDisplayListDto;
}
public async Task<List<Display>> GetByUnitId(ObjectId unitId)
{
return await displayRepository.GetByUnitId(unitId);
}
public async Task<PocAndUnitDto> GetAllAvailablePoc(List<string> displayIds, bool excludeVirtual = false)
{
try
{
var listToReturn = new PocAndUnitDto();
var listObjectId = new List<ObjectId>();
foreach (var displayId in displayIds)
{
var isParsed = ObjectId.TryParse(displayId, out var dId);
if (isParsed)
{
var diplay = await displayRepository.GetById(dId);
if (diplay != null)
listObjectId.Add(diplay.UnitId);
}
}
var distinctObjectIds = listObjectId.Distinct().ToList();
foreach (var distinctObjectId in distinctObjectIds)
{
var unit = await unitService.Value.FindById(distinctObjectId) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
var pocList = await pointOfCareService.FindByUnitAndStatus(distinctObjectId,
StatusEnum.PointOfCare.Available, excludeVirtual);
foreach (var pointOfCare in pocList)
{
var pocAv = new MinimalPocAndUnitDto
{
PocId = pointOfCare.Id,
PocName = pointOfCare.Bed,
UnitId = pointOfCare.UnitId,
UnitName = unit.Name
};
listToReturn.PocList.Add(pocAv);
}
}
return listToReturn;
}
catch (Exception e)
{
logger.LogError("Error GetAllAvailablePoc {Error}", e.Message);
return new PocAndUnitDto();
}
}
public async Task<List<PointOfCare>> GetAllPocsByDisplayId(ObjectId id)
{
try
{
var pocList = new List<PointOfCare>();
var display = await displayRepository.GetById(id);
if (display == null)
return pocList;
foreach (var pocId in display.PointOfCareIdList)
{
var poc = await pointOfCareService.FindById(pocId);
if (poc != null) pocList.Add(poc);
}
return pocList;
}
catch (Exception e)
{
logger.LogError("Error GetAllAvailablePoc {Error}", e.Message);
return [];
}
}
public async Task<List<DisplayConfigLocationDto>> GetDisplayConfigLocations(ObjectId displayConfigId)
{
var displays = await GetByConfigId(displayConfigId);
var locations = new List<DisplayConfigLocationDto>();
foreach (var display in displays)
{
var unit = await unitService.Value.FindById(display.UnitId);
DisplayConfigLocationDto newLocation = new()
{
DisplayName = display.Name,
UnitName = unit?.Name,
};
locations.Add(newLocation);
}
return locations;
}
public async Task<bool> IsDisplayConfigInUse(ObjectId displayConfigId)
{
return await displayRepository.IsDisplayConfigInUse(displayConfigId) > 0;
}
#endregion
#region Update
/*
* En esta actualización se espera una resubscipción al id del display ya que actualizar los PoC conlleva actualizar
* subscrioptor y locations para las observaciones
*/
public async Task<Display?> UpdatePointOfCareList(ObjectId objectId, List<ObjectId> listPocObId)
{
var oldDisplay = await displayRepository.GetById(objectId) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
var displayToReturn = await displayRepository.UpdatePointOfCareList(objectId, listPocObId) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(objectId));
SendDisplayBroadcast(displayToReturn, OperationType.UpdateDisplayPoC);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplay, displayToReturn);
return displayToReturn;
}
public async Task<Display?> UpdateConfig(Display oldDisplay, DisplayConfig? newDisplayConfig)
{
var newDisplayConfigCast = new DisplayConfig();
switch (newDisplayConfig?.Type)
{
case DisplayConfigEnums.DisplayType.DisplayNurse:
newDisplayConfigCast = newDisplayConfig as DisplayNurse;
break;
case DisplayConfigEnums.DisplayType.SmartDisplay:
newDisplayConfigCast = newDisplayConfig as SmartDisplay;
break;
}
if (newDisplayConfigCast != null)
{
var displayToReturn = await displayRepository.UpdateConfig(oldDisplay.Id, newDisplayConfigCast);
if (displayToReturn != null)
{
SendDisplayBroadcast(displayToReturn, OperationType.UpdateDisplayConfig);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplay,
displayToReturn);
}
await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(oldDisplay.Id));
return displayToReturn;
}
return null;
}
public async Task<Display?> UpdateConfigId(Display oldDisplay, ObjectId configId)
{
var displayToReturn = await displayRepository.UpdateConfigId(oldDisplay.Id, configId);
if (displayToReturn != null)
{
await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(oldDisplay.Id));
SendDisplayBroadcast(displayToReturn, OperationType.UpdateDisplayConfig);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldDisplay, displayToReturn);
}
return displayToReturn;
}
public async Task<Display?> UpdateConfigPreset(ObjectId objectIdDisplay, ObjectId objectIdConfigDisplay)
{
var oldConfig = await displayConfigService.GetById(objectIdConfigDisplay);
var result = await displayRepository.UpdateConfigPreset(objectIdDisplay, objectIdConfigDisplay);
await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(objectIdDisplay));
var config = await displayConfigService.GetById(objectIdConfigDisplay);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldConfig, config);
if (result == null || config == null)
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
var subscribers = subscribersService.GetSubscribers().Where(s => s.DisplayId == objectIdDisplay).ToList();
switch (config.Type)
{
case DisplayConfigEnums.DisplayType.DisplayNurse:
SendNurseDisplayBroadcast(subscribers, config as DisplayNurse);
break;
case DisplayConfigEnums.DisplayType.SmartDisplay:
SendSmartDisplayBroadcast(subscribers, config as SmartDisplay);
break;
case DisplayConfigEnums.DisplayType.Unknown:
break;
default:
throw new InvalidFormatException(HttpEnum.ErrorMessage.BadRequestIncorrectType);
}
return result;
}
public async Task<Display?> UpdateName(ObjectId id, string name)
{
var display = await displayRepository.GetById(id) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
var newDisplay = await displayRepository.UpdateName(display, name);
await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(id));
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, display, newDisplay);
return newDisplay;
}
#endregion
#region Delete
public async Task<bool> DeleteDisplay(ObjectId id)
{
var display = await displayRepository.GetById(id) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
await displayRepository.DeleteAsync(id);
// Invalidar CACHE (colección completa)
await cacheService.DeleteByPatternAsync(CacheKeys.DisplayBase(id));
await authorityService.DeleteByDisplayId(id);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, display, null);
return true;
}
public async Task DeleteDisplaysByUnitId(ObjectId unitId)
{
await displayRepository.DeleteManyByUnitId(unitId);
// Invalidar CACHE (colección completa)
await cacheService.DeleteByPatternAsync(CacheKeyPatterns.ForEntity(CacheEnum.EntityType.Displays));
await authorityService.DeleteByUnitId(unitId);
}
#endregion
#region Send Notification
private void SendSmartDisplayBroadcast(List<WsSubscriber> subscribers, SmartDisplay? config)
{
foreach (var subscriber in subscribers)
_ = clientMessageService.SendAsync(subscriber.Id, OperationType.UpdateSmartDisplayConfig, config);
}
private void SendNurseDisplayBroadcast(List<WsSubscriber> subscribers, DisplayNurse? config)
{
foreach (var subscriber in subscribers)
_ = clientMessageService.SendAsync(subscriber.Id, OperationType.UpdateNurseDisplayConfig, config);
}
private void SendDisplayBroadcast(Display display, OperationType operation)
{
var subscribers = subscribersService.GetSubscribers().Where(s =>
s.DisplayId == display.Id).ToList();
switch (operation)
{
case OperationType.UpdateDisplayPoC:
foreach (var subscriber in subscribers)
_ = clientMessageService.SendAsync(subscriber.Id, operation, null);
break;
}
}
#endregion
#endregion
}
@@ -0,0 +1,113 @@
//using Microsoft.AspNetCore.Http;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.DTO;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Serilog;
namespace adas_core.Application.Services;
public class FileService : IFileService
{
private readonly string? _assetsDirectory;
private readonly ILogger<FileService> _logger;
private readonly string? _updateDirectory;
public FileService(
IOptions<ApiSettings> apiSettings,
ILogger<FileService> logger
)
{
_logger = logger;
if (apiSettings.Value.PathUpdateFiles != null)
_updateDirectory = Path.Combine(apiSettings.Value.PathUpdateFiles);
if (apiSettings.Value.PathToDisplayAssets != null)
_assetsDirectory = Path.Combine(apiSettings.Value.PathToDisplayAssets);
}
public async Task<bool> CopyUpdateFiles(ICollection<IFormFile> files)
{
if (string.IsNullOrWhiteSpace(_updateDirectory)) return false;
foreach (var file in files)
{
await using var stream = new FileStream(Path.Combine(_updateDirectory, file.FileName), FileMode.Create);
await file.CopyToAsync(stream);
}
return true;
}
public async Task<bool> UploadAssetFiles(ICollection<IFormFile> files, FileEnum.AssetTheme themeParse)
{
if (string.IsNullOrWhiteSpace(_assetsDirectory)) return false;
var directoryInfo = new DirectoryInfo(Path.Combine(_assetsDirectory, themeParse.ToString()));
if (!directoryInfo.Exists) directoryInfo.Create();
foreach (var file in files)
{
await using var stream =
new FileStream(Path.Combine(_assetsDirectory, themeParse.ToString(), file.FileName), FileMode.Create);
await file.CopyToAsync(stream);
}
return true;
}
public List<AssetDto> GetAllAssetFile(FileEnum.AssetTheme themeParse)
{
try
{
if (string.IsNullOrWhiteSpace(_assetsDirectory)) return [];
var listToReturn = new List<AssetDto>();
var directoryInfo = new DirectoryInfo(Path.Combine(_assetsDirectory, themeParse.ToString()));
if (!directoryInfo.Exists) directoryInfo.Create();
var files = directoryInfo.GetFiles(); // Obtener todos los archivos en el directorio
foreach (var file in files)
{
var assetDto = new AssetDto
{
Name = file.Name,
Extension = file.Extension,
Path = file.FullName // Obtener la ruta completa del archivo
};
listToReturn.Add(assetDto);
}
return listToReturn;
}
catch (Exception e)
{
_logger.LogError("Error while retrieving assets: {eMessage} {eStackTrace}", e.Message, e.StackTrace);
return [];
}
}
public List<string> GetFilesInDirectory(string directoryPath)
{
List<string> fileList = [];
try
{
if (Directory.Exists(directoryPath))
// Obtiene todos los archivos en el directorio
fileList.AddRange(Directory.GetFiles(directoryPath));
else
Log.Warning("La ruta proporcionada no existe: {directoryPath}", directoryPath);
}
catch (Exception ex)
{
Log.Error("Ocurrió un error al buscar archivos: {exMessage}", ex.Message);
}
return fileList;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,116 @@
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.MongoModels;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Services;
public class HistoricalConfigChangesService(
IHistoricalConfigChangesRepository historicalConfigChangesRepository,
ILogger<HistoricalConfigChangesService> logger,
IHttpContextAccessor httpContextAccessor,
ILocalAuditService auditService)
: IHistoricalConfigChangesService
{
private readonly ILogger<HistoricalConfigChangesService> _logger = logger;
public async Task DeleteHistoricalConfigChange(ObjectId id)
{
var filter = Builders<HistoricalConfigChanges>.Filter.Eq("_id", id);
var result = historicalConfigChangesRepository.FindById(id);
await historicalConfigChangesRepository.Collection.DeleteOneAsync(filter);
_logger.LogInformation("Deleted historicalConfigChanges with id: {id}", id);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, result, null);
}
public async Task<HistoricalConfigChanges?> FindLastConfigChanges(DisplayConfigEnums.ConfigTypes configType)
{
var result = await historicalConfigChangesRepository.FindLastHistoricalConfigChangesByType(configType);
return result.FirstOrDefault();
}
public async Task<HistoricalConfigChanges?> Get(ObjectId id)
{
return await historicalConfigChangesRepository.FindById(id);
}
public async Task<ICollection<HistoricalConfigChanges>> GetAll()
{
return await historicalConfigChangesRepository.FindAll();
}
public async Task<ICollection<HistoricalConfigChanges>> GetByType(DisplayConfigEnums.ConfigTypes type, int num = 10)
{
return await historicalConfigChangesRepository.FindLastHistoricalConfigChangesByType(type, num);
}
public async Task<ICollection<HistoricalConfigChanges>> GetByUser(string user,
DisplayConfigEnums.ConfigTypes? configTypes = null, int num = 10)
{
return await historicalConfigChangesRepository.FindLastHistoricalConfigChangesByUser(user, configTypes, num);
}
public async Task<HistoricalConfigChanges?> InsertOne(HistoricalConfigChanges historicalConfigChanges)
{
try
{
var result = await historicalConfigChangesRepository.InsertOneAsync(historicalConfigChanges) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, result);
return await historicalConfigChangesRepository.InsertOneAsync(historicalConfigChanges);
}
catch (Exception ex)
{
_logger.LogError("Exception inserting historicalConfigChanges {changes} exception:{e} ",
historicalConfigChanges.ToJson(), ex);
return null;
}
}
public async Task<HistoricalConfigChanges?> UpdateHistoricalConfigChange(
HistoricalConfigChanges historicalConfigChanges)
{
try
{
var oldHistorical = await historicalConfigChangesRepository.FindById(historicalConfigChanges.Id) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
var result = await historicalConfigChangesRepository.Update(historicalConfigChanges);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldHistorical, result);
return result;
}
catch (Exception ex)
{
_logger.LogError("Exception updating historicalConfigChanges {changes} exception:{e} ",
historicalConfigChanges.ToJson(), ex);
return null;
}
}
public async Task LogConfigChanged(string user, DisplayConfigEnums.ConfigTypes configType, string newConfig,
string oldConfig)
{
HistoricalConfigChanges historicalConfigChanges = new()
{
ConfigType = configType,
Time = DateTime.Now,
Username = user,
OldConfig = oldConfig,
NewConfig = newConfig
};
var result = await InsertOne(historicalConfigChanges);
if (result == null)
_logger.LogError("Error logging config changes. newConfig: {newConfig}, oldConfig: {oldConfig}",
historicalConfigChanges.NewConfig, historicalConfigChanges.OldConfig);
else
_logger.LogDebug("Config changes logged. newConfig: {newConfig}, oldConfig: {oldConfig}",
historicalConfigChanges.NewConfig, historicalConfigChanges.OldConfig);
}
}
@@ -0,0 +1,9 @@
using adas_core.Domain.Models;
namespace adas_core.Application.Services.Interfaces;
public interface IApiRequestService
{
Task SaveRequestAsync(ApiRequest apiRequest);
Task SaveRequest(ApiRequest apiRequest);
}
@@ -0,0 +1,46 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IAdminPanelService
{
Task<bool> DeleteUnitById(ObjectId unitId);
Task<Unit?> InsertUnit(Unit unit);
#region Patient
Task<Patient?> CreatePatient(AdmPanelRequest apiRequest);
Task<Patient?> FindPatientByLocation(PatientLocation location);
Task<Patient?> FindPatientByPatientNumber(string patientNumber);
Task<Patient?> FindPatientById(ObjectId id);
//List<person> FindAllPatient();
Task<Patient?> FindPatient(AdmPanelRequest request);
Task<UnitInfoDto?> GetUnitDependencyDto(Unit unit);
Task<bool> UpdatePatientLocation(AdmPanelRequest request);
Task<bool> UpdatePatientData(AdmPanelRequest request, Patient oldPatient);
Task<bool> ArchivePatient(Patient patient);
#endregion
#region ConfigObservations
Task<bool> CreateConfig(ConfigObservation configObservation);
Task<bool> UpdateConfig(ConfigObservation configObservation);
Task<bool> DeleteConfigObservationItem(ObjectId id);
#endregion
#region Medicienes
Task<Medicine?> GetMedicineById(ObjectId medicineId);
Task<Medicine?> PostMedicine(Medicine medicine);
Task<Medicine?> UpdateMedicine(Medicine medicine);
Task<bool> DeleteMedicineById(string medicineId);
#endregion
}
@@ -0,0 +1,34 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.Masters;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IAdmissionService : IApiRequestService
{
Task<Admission?> GetAdmissionByIdAsync(ObjectId admissionId);
Task DeleteAdmissionByIdAsync(ObjectId admissionId);
Task DeleteAdmissionAsync(Admission admission);
Task DeleteAdmissionsByUnitId(ObjectId unitId);
Task UpdateAdmissionAsync(Admission admission);
Task<IEnumerable<Admission>> GetAdmissionsAsync();
Task<Admission?> InsertAdmission(Admission admission);
Task AdmitPatient(Admission admission, bool isNew = false);
Task ReturnPatientToAdmissions(ObjectId patientId);
Task ReturnPatientToAdmissions(ObjectId patientId, Admission adm);
Task<List<Admission>> GetAdmissionByLocation(PatientLocation location);
Task<List<Admission>> GetAdmissionByPointOfCareId(ObjectId id);
Task<List<Admission>> GetAdmissionByPointOfCareIdAndLocale(ObjectId pocId, LocaleEnum locale);
Task<List<Admission>> GetAdmissionByUnitIdWithOutPoC(ObjectId unitId);
Task<long> CountAdmissionsByUnitId(ObjectId unitId);
Task<PatientSearch?> SearchByPatientNumberAndDistinctUnit(string patientNumber, ObjectId unitId);
Task<Admission?> GetAdmissionByPatientNumber(string patientNumber);
Task UpdatePatientMasterListItemChange(UpdateOptionMasterListDto opt, IEnumerable<Unit> unitList, string typeName);
Task DeletePatientMasterListItem(OptionList opt, IEnumerable<Unit> unitList, string typeName);
}
@@ -0,0 +1,30 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IAlarmService : IApiRequestService
{
public Task<List<PatientObservationAlarm>> FindLastObservationsByField(ObjectId patientId,
List<Field>? filterObservations = null);
Task<List<PatientObservationAlarm>> FindLastValuesNotExpired(ObjectId patientId, List<Field> dataAlarmfields,
List<ConfigObservation> configAlarm);
public Task<PatientObservationAlarm?> MapObservation(PatientObservationAlarm obs, bool onlyByName = false);
public Task<PatientObservationAlarm?> MapObservationsByName(PatientObservationAlarm obs);
Task CalculateAlarmTest(BasePatientObservationValue source, string name);
Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code, AlarmEnum.Severity severity,
AlarmEnum.Type type);
Task CheckObservationAlarm(PatientObservation obs4);
Task ProcessAlarmObservations(List<PatientObservationAlarm> alarmObservations,
List<PatientObservation> observations, Patient patient,
DateTime messageTime, ObservationData? observationData = null);
}
@@ -0,0 +1,9 @@
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IAlertValuesService
{
Task<ConfigObservation?> FindByKey(ObjectId key);
}
@@ -0,0 +1,20 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Services.Interfaces;
public interface IAppointmentService : IApiRequestService
{
Task<List<PatientAppointment>> FindByLocation(PatientLocation location);
Task<List<PatientAppointment>> GetByPatient(ObjectId patientId);
Task<List<PatientAppointment>> GetTodayByPatient(ObjectId patientId, CancellationToken ct = default);
Task<List<PatientAppointment>> GetTodayByPoc(ObjectId pocId, CancellationToken ct = default);
Task<IAsyncCursor<PatientAppointment>> FindByPatientIdAsync(ObjectId patientId);
Task Archive(Patient patient);
Task ArchiveByPatientId(ObjectId id);
Task DeleteByPatientId(ObjectId id);
Task ProcessApiRequest(ApiRequest apiRequest, Patient patient);
Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId);
}
@@ -0,0 +1,21 @@
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IArchivePatientCarePlanService
{
Task<List<PatientCarePlan>?> FindByPatientId(ObjectId id);
Task<List<PatientCarePlan>?> FindByPatientId(string id);
Task<List<PatientCarePlan>?> FindByPatientNumber(string id);
Task<List<PatientCarePlan>> FindAll();
// Task<PatientCarePlan?> UpdateTreatment(PatientCarePlan patientCarePla, List<OptionList> options);
// Task<PatientCarePlan?> UpdateProcedure(PatientCarePlan patientCarePla, List<OptionList> options);
Task<PatientCarePlan?> InsertOneAsync(PatientCarePlan patientCarePla);
Task InsertManyAsync(List<PatientCarePlan> patientCarePla);
// Task Update(PatientCarePlan oldPatientCarePla, PatientCarePlan newPatientCarePla);
// Task<PatientCarePlan?> FindByIds(ObjectId oldPatientCarePlaId, string? oldPatientCarePlaPatientId, string? oldPatientCarePlaPatientNumber);
}
@@ -0,0 +1,9 @@
using adas_core.Domain.Models;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IArchivedPatientObservationService
{
public Task<List<PatientObservation>> FindArchivedPatientObservationsFromPatient(ObjectId patientId);
}
@@ -0,0 +1,8 @@
using adas_core.Domain.Models.MongoModels;
namespace adas_core.Application.Services.Interfaces;
public interface IArchivedPatientService
{
public Task<List<Patient>> FindAllPatients();
}
@@ -0,0 +1,9 @@
using adas_core.Domain.Models;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IArchivedPatientTreatmentService
{
Task<List<PatientTreatment>> FindAllPatientTreatmentsByPatient(ObjectId patientId);
}
@@ -0,0 +1,15 @@
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IAuthService
{
Task<LoginResponse?> GetLoginResponse();
Task<string> GetToken();
Task<List<Authorization>> GetByUnitId(ObjectId unitId);
Task<bool> DeleteByDisplayId(ObjectId displayId);
Task<bool> DeleteByUnitId(ObjectId unitId);
Task<List<Authorization>> GetUserAuthorities(ObjectId id);
}
@@ -0,0 +1,39 @@
using adas_core.Domain.Models.GroupedObservations;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces
{
public interface ICacheService
{
//Métodos básicos
void SetValue(string key, string value);
string? GetValue(string key);
Task<T?> GetObjectAsync<T>(string key, bool updateExpiration = true);
Task SetObjectAsync<T>(string key, T obj, bool updateExpiration = true);
Task<T?> GetObjectAsync<T>(string key, TimeSpan? ttlOverride, bool updateExpiration);
Task SetObjectAsync<T>(string key, T obj, TimeSpan? ttlOverride, bool updateExpiration);
Task DeleteObjectAsync(string key);
Task<long> DeleteByPatternAsync(string pattern);
void CleanCache();
// Métodos para transparencia y gestión de locks
// GetOrSet (string key)
Task<string?> GetOrSetValueAsync(string key, Func<Task<string>> loader, TimeSpan? ttlOverride = null);
Task<T> GetOrSetObjectAsync<T>(
string key,
Func<Task<T>> factory,
TimeSpan? ttl = null,
CancellationToken cancellationToken = default);
// GetOrSet especializado para GroupedObservations
Task<T> GetOrSetObjectAsync<T>(
GroupedField groupedField,
ObjectId patientId,
Func<Task<T>> factory,
TimeSpan? ttl = null,
CancellationToken cancellationToken = default);
}
}
@@ -0,0 +1,27 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.Pumps;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface ICalculatedObservations
{
Task<T?> Map<T>(T obs, bool onlyByName = false) where T : BasePatientObservation;
Task<PatientTreatment> Map(PatientTreatment treatment);
Task<PatientDiagnosis> Map(PatientDiagnosis diagnosis);
Task<PumpObservation> Map(PumpObservation pumpObservation);
Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId);
Task CalculateActiveBolus(ObjectId patientId);
Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id);
Task<PatientObservation?> FixTimeInconsistencyWithLast(PatientObservation newObservation);
Task<List<PatientObservation>> PreMapList(List<PatientObservation> listToInsert);
Task<PatientObservation> MapSourceAlarm(PatientObservation obs, PatientObservationAlarm alarmToInsert);
Task SendAlarm(PatientObservation obs, string name, AlarmEnum.Name? code);
}
@@ -0,0 +1,20 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.Pumps;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface ICalculatedObservationsService
{
Task<PatientObservation?> Map(PatientObservation obs, bool onlyByName = false);
Task<PatientObservationAlarm?> Map(PatientObservationAlarm obs, bool onlyByName = false);
Task<PatientTreatment?> Map(PatientTreatment treatment);
Task<PumpObservation?> Map(PumpObservation obs);
Task<PatientRecordingAlert?> Map(PatientRecordingAlert obs);
Task<PatientDiagnosis?> Map(PatientDiagnosis obs);
Task<List<PatientObservation>> MapList(List<PatientObservation> listToInsert);
Task CalculateBolusOpiates(ObjectId patientId);
Task CalculateMedicineObservation(List<Medicine> activeMedicines, ObjectId patientId);
Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id);
Task<PatientObservation> MapSourceAlarm(PatientObservation observation, PatientObservationAlarm observationAlarm);
}
@@ -0,0 +1,17 @@
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface ICameraService
{
Task<Camera?> GetById(ObjectId relayId);
List<Camera> GetCameraInList(List<ObjectId> configurationRelayList);
Task<PaginationResponse<Camera>> GetPaginatedCameras(PaginationFilter request);
Task<Camera?> InsertCamera(Camera camera);
Task<Camera?> UpdateCameraById(ObjectId objectId, Camera camera);
Task<bool> DeleteCamera(ObjectId objectId);
Task<List<Camera>> GetSearchByNameCameras(string textToSearch);
}
@@ -0,0 +1,13 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.SignalR;
namespace adas_core.Application.Services.Interfaces;
public interface IClientMessageService
{
Task SendAsync(string receiverId, OperationType? type, object? msg);
Task SendToAllAsync(OperationType type, object? msg);
Task ProcessMessage(Message msg, string contextConnectionId);
void SendUpdateMessageToBoxes(List<PatientLocation> boxes);
}
@@ -0,0 +1,44 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Pumps;
using adas_core.Domain.Models.Responses;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IConfigObservationService
{
Task<ConfigObservation?> GetByCodeSysAndCode(string codingSystem, string code);
Task<ConfigObservation?> Get(string name);
Task<ConfigObservation?> Get<T>(T obs, bool onlyByName = false) where T : BasePatientObservation;
Task<ObservatitonRetentionResult?> RetentionActions<T>(T obs) where T : BasePatientObservation;
Task<T?> Map<T>(T obs, bool onlyByName = false) where T : BasePatientObservation;
Task<PatientTreatment?> Map(PatientTreatment treatment);
Task<StatusEnum.Type> GroupedObservationStatus(GroupedField groupedField, GroupedObservationEnum.Result result,
string name, object value, double? min, double? max);
Task<ICollection<ConfigObservation>> GetAllConfigs(CancellationToken ct = default);
Task<PaginationResponse<ConfigObservation>> GetPaginatedItems(PaginationFilter filter);
Task<ConfigObservationDto> GetAllCompact();
Task<ConfigObservation?> GetConfigById(ObjectId id);
Task<List<string>> GetConfigNames(string id);
Task<List<string>> GetConfigNames();
Task<ConfigObservation?> UpdateConfig(ConfigObservation configObservation);
Task<ConfigObservation?> CreateConfig(ConfigObservation configObservation);
Task<ConfigObservation?> RemoveConfigItem(string itemName);
Task<ConfigObservation?> RemoveConfigItem(ObjectId id);
Task<IEnumerable<ConfigObservation>?> GetConfigObservationItem(string name);
Task<ConfigObservation?> GetSingleConfigObservationItem(string? code, string? codingSystem, string? name,
string? originalName);
Task<bool> DeleteSingleConfigObservationItem(ConfigObservation configObservationItem);
Task<IEnumerable<ConfigObservation>> GetConfigObservationItemsByName(string name);
}
@@ -0,0 +1,18 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Pumps;
namespace adas_core.Application.Services.Interfaces;
public interface IConfigPumpsService
{
Task<PumpObservation> Map(PumpObservation obs);
Task<List<ConfigPumps>?> GetAllPumpConfigs();
Task<List<ConfigPumpItem>?> GetConfigItems(string id);
Task<ConfigPumps?> GetPumpConfigById(string id);
Task<ConfigPumps?> UpdatePumpConfig(ConfigPumps pumpConfig);
Task<ConfigPumps?> InsertPumpConfig(ConfigPumps pumpConfig);
Task<bool> DeletePumpConfig(ConfigPumps config);
Task<ObservatitonRetentionResult?> RetentionActions(PumpObservation obs);
}
@@ -0,0 +1,10 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.Pumps;
namespace adas_core.Application.Services.Interfaces;
public interface IConfigUnitsService
{
Task<T> Map<T>(T obs) where T : BasePatientObservation;
Task<PumpObservation> Map(PumpObservation obs);
}
@@ -0,0 +1,13 @@
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IDeviceService
{
Task<Device?> Create(DeviceDto device);
Task<bool> Delete(ObjectId objectId);
Task<Device?> Update(DeviceDto device);
Task<Device?> ReceiveEvent(DeviceDto device);
}
@@ -0,0 +1,17 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IDiagnosisService : IApiRequestService
{
Task<List<PatientDiagnosis>> GetByPatientId(ObjectId patientId);
Task DeleteByPatientId(ObjectId id);
Task Archive(Patient patient);
Task ArchiveByPatientId(ObjectId id);
Task ProcessDiagnosisObservation(ApiRequest apiRequest, Patient patient);
Task SaveRequest(ApiRequest apiRequest, Patient patient);
Task ProcessDiagnosis(List<PatientDiagnosis> diagnosis, Patient patient, DateTime messageTime);
Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId);
}
@@ -0,0 +1,27 @@
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 MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IDischargeService : IApiRequestService
{
Task<Discharge?> GetDischargeByIdAsync(ObjectId dischargeId);
Task<long> CountDischargesByUnitId(ObjectId unitId);
Task DeleteDischargeByIdAsync(ObjectId dischargeId);
Task DeleteDischargeAsync(Discharge discharge);
Task UpdateDischargeAsync(Discharge discharge);
Task<IEnumerable<Discharge>> GetDischargesAsync();
Task<Discharge?> InsertDischarge(Discharge discharge);
Task<Discharge?> GetDischargeByLocation(PatientLocation location);
Task<Discharge?> GetDischargeByPatientId(ObjectId patientId);
Task<Discharge?> GetDischargeByPointOfCareId(ObjectId location);
Task<Discharge?> GetDischargeByPointOfCareIdWithLocale(ObjectId location, LocaleEnum dataLocale);
void SendDischargeBroadcast(Discharge discharge, OperationType operation);
Task UpdatePatientMasterListItemChange(UpdateOptionMasterListDto opt, IEnumerable<Unit> unitList, string typeName);
Task DeletePatientMasterListItem(OptionList opt, IEnumerable<Unit> unitList, string typeName);
Task DeleteDischargesByUnitId(ObjectId unitId);
}
@@ -0,0 +1,46 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models.DTO.Display;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IDisplayConfigService
{
Task<List<DisplayConfig>> GetAll();
Task<PaginationResponse<DisplayConfigMinimalResponse>> GetAllCompactPaginated(PaginationFilter request);
Task<List<DisplayConfig>> GetByType(DisplayConfigEnums.DisplayType type);
Task<DisplayConfig> GetById(ObjectId id);
Task<DisplayConfig?> GetById(ObjectId? configId, ObjectId unitId, DisplayConfigEnums.DisplayType displayType);
Task<DisplayConfig?> InsertOne(DisplayConfig config);
Task<DisplayConfig?> InsertOneMinimal(CreateDisplayConfigDto config);
Task<DisplayConfig> InsertOneTest();
Task<DisplayConfig?> UpdateConfig(ObjectId displayConfigId, object newDisplayConfig);
Task<bool> UpdateFieldList(ObjectId objectIdConfigDisplay, List<Field> fields);
Task<bool> UpdateConfigColor(ObjectId objectIdConfigDisplay, ColorConfig colorConfigDto);
Task<bool> UpdateHeaderConfig(ObjectId objectIdConfigDisplay, HeaderConfig headerConfig);
Task<bool> UpdateSetHomeBanner(ObjectId objectIdConfigDisplay, List<BannerItem> bannerItems);
Task<bool> UpdateBaseConfig(DisplayConfig baseConfig);
Task<bool> UpdateDisplayConfigHospitalName(ObjectId objectIdConfigDisplay, string name);
Task<bool> DeleteDisplayConfig(ObjectId objectIdConfigDisplay);
Task<DisplayConfig?> GetDefaultConfig(DisplayConfigEnums.DisplayType type);
Task<List<DisplayConfigLocationDto>> GetDisplayConfigLocations(ObjectId objectIdConfigDisplay);
Task<List<DisplayConfigMinimalResponse>> GetAllCompact();
Task<DisplayConfig?> InsertOneWithTemplate(string objectId,
DisplayConfigEnums.DisplayType configType, string? configHospital);
Task<bool> UpdateCardConfig(CardConfig baseConfig);
Task<CardConfig?> InsertCardConfig(CreateDisplayConfigCardDto updateDisplayConfigNameDto);
Task<List<CardConfig>> GetCardConfigAll();
Task<CardConfig?> GetCardConfigById(ObjectId id);
Task<bool> UpdateDetailConfig(CardDetailsConfig baseConfig);
Task<CardDetailsConfig?> InsertCardDetailConfig(CreateDisplayConfigCardDto updateDisplayConfigNameDto);
Task<ChartConfig?> InsertChartConfig(CreateDisplayConfigCardDto updateDisplayConfigNameDto);
Task<bool> UpdateChartConfig(ChartConfig baseConfig);
Task<bool> DeleteChartConfig(ObjectId objectIdConfigDisplay);
Task<ChartConfig?> GetChartConfig(ObjectId objectIdConfigChart);
}
@@ -0,0 +1,63 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.DTO.Display;
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 MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IDisplayService
{
//Task<List<DisplayWithPermissionsDto>> GetAll(string? userName, List<Authorization> displayIdByAuthorities);
Task<List<DisplayMinimalDto>> GetAllCompact();
Task<List<DisplayWithPermissionsDto>> GetAllByUser(string? userName);
Task<List<Display>> GetByType(DisplayConfigEnums.DisplayType type);
Task<List<Display>> GetByPointOfCare(PointOfCare pointOfCare);
Task<List<Display>> GetByConfigId(ObjectId configId);
Task<List<Display>> GetByCardConfigId(ObjectId configId);
// Task<List<Display>> GetByUser();
Task<Display?> GetByName(string name);
Task<Display?> GetById(ObjectId id);
Task<DisplayWithPermissionsDto> GetByIdWithPermissions(ObjectId id, LocaleEnum localeEnum);
Task<long> CountDisplaysByUnitId(ObjectId unitId);
Task<Display?> GetInfo(
ObjectId id,
string? userName,
List<Authorization>? authorizations,
LocaleEnum? locale,
bool fillPointOfCare = true,
bool fillPatientData = false,
bool fillDisplayList = true,
bool fillDisplayConfig = true,
CancellationToken ct = default
);
Task<List<MinimalDisplaySection>> GetDisplaySectionByUser(
DisplayConfigEnums.DisplayType type,
ObjectId? currentDisplay,
string? userName,
List<Authorization>? authorizations);
Task<MinimalDisplayListDto> GetAllDisplaySection();
Task<List<Display>> GetByUnitId(ObjectId id);
Task<PocAndUnitDto> GetAllAvailablePoc(List<string> displayIds, bool excludeVirtual = false);
Task<List<PointOfCare>> GetAllPocsByDisplayId(ObjectId id);
Task<Display> InsertOne(Display display);
Task<Display> InsertOneTest();
Task<Display?> UpdateConfig(Display oldDisplay, DisplayConfig? newDisplayConfig);
Task<Display?> UpdateConfigId(Display oldDisplay, ObjectId configId);
Task<Display?> UpdatePointOfCareList(ObjectId objectId, List<ObjectId> listPocObId);
Task<Display?> UpdateConfigPreset(ObjectId objectIdDisplay, ObjectId objectIdConfigDisplay);
Task<Display?> UpdateName(ObjectId id, string name);
Task<PaginationResponse<Display>> GetPaginatedDisplays(PaginationFilter filter);
Task<bool> DeleteDisplay(ObjectId id);
Task<List<DisplayConfigLocationDto>> GetDisplayConfigLocations(ObjectId displayConfigId);
Task<bool> IsDisplayConfigInUse(ObjectId displayConfigId);
Task DeleteDisplaysByUnitId(ObjectId unitId);
}
@@ -0,0 +1,16 @@
//using Microsoft.AspNetCore.Http;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.DTO;
using Microsoft.AspNetCore.Http;
namespace adas_core.Application.Services.Interfaces;
public interface IFileService
{
Task<bool> CopyUpdateFiles(ICollection<IFormFile> files);
Task<bool> UploadAssetFiles(ICollection<IFormFile> files, FileEnum.AssetTheme themeParse);
List<AssetDto> GetAllAssetFile(FileEnum.AssetTheme themeParse);
List<string> GetFilesInDirectory(string directoryPath);
}
@@ -0,0 +1,26 @@
using adas_core.Application.Subscriptions;
using adas_core.Domain.Models;
using adas_core.Domain.Models.GroupedObservations;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IGroupedObservationService
{
Task<GroupedObservation> GenerateGroupedObservation(ObjectId id, GroupedField groupedField,
string timeZoneId = "Romance Standard Time", bool cacheIsChecked = false, CancellationToken ct = default);
Task<GroupedObservation> GenerateGroupedObservation(ObjectId obsPatientId, GroupedField groupedField,
List<GroupedObservation.GroupedObservationObs> wsgLastGroupedObservationObs, PatientObservation obs,
string timeZoneId = "Romance Standard Time");
Task<List<PatientObservation>> FindLastObservations(ObjectId patientId, int num = 2,
List<string>? filterObservations = null);
Task<GroupedObservation> CreateNextEmptyObs(WsSubscriberGrouped ws);
/*
*
List<BsonDocument> CalculateShiftObservations(List<BsonDocument> shiftGroupObservations, GroupedField groupedField);
List<BsonDocument> GenerateShiftObservations(List<BsonDocument> result, GroupedField groupedField);
*/
}
@@ -0,0 +1,25 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IHistoricalConfigChangesService
{
Task<ICollection<HistoricalConfigChanges>> GetAll();
Task<HistoricalConfigChanges?> Get(ObjectId id);
Task<ICollection<HistoricalConfigChanges>> GetByType(DisplayConfigEnums.ConfigTypes type, int num);
Task<ICollection<HistoricalConfigChanges>> GetByUser(string user, DisplayConfigEnums.ConfigTypes? configTypes,
int num);
Task<HistoricalConfigChanges?> FindLastConfigChanges(DisplayConfigEnums.ConfigTypes configTypes);
Task<HistoricalConfigChanges?> InsertOne(HistoricalConfigChanges historicalConfigChanges);
Task<HistoricalConfigChanges?> UpdateHistoricalConfigChange(HistoricalConfigChanges historicalConfigChanges);
Task DeleteHistoricalConfigChange(ObjectId id);
Task LogConfigChanged(string user, DisplayConfigEnums.ConfigTypes configType, string newConfig, string oldConfig);
}
@@ -0,0 +1,28 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface ILightBeaconService
{
Task SendColor(ObjectId pocId, LightBeaconColor color);
Task SendColor(PointOfCare pocId, LightBeaconColor color);
Task SendBeaconBroadcast(PointOfCare poc, LightBeaconColor color);
Task SendBeaconBroadcast(ObjectId poc, LightBeaconColor color);
Task PowerOffLed(ObjectId pocId);
Task PowerOffLed(PointOfCare poc);
void GenerateColorAlert(PatientObservation obs);
//TODO refactor, one patient can have multiple beacons
public Task<LightBeaconColor> GetColor(ObjectId pocId);
public Task<LightBeaconColor> GetColor(PointOfCare poc);
Task<LightBeacon?> UpdateOne(LightBeacon beacon);
Task<LightBeacon?> InsertOne(LightBeacon beacon);
Task<PaginationResponse<LightBeacon>> GetPaginatedBeacons(PaginationFilter request);
Task<List<LightBeacon>> GetSearchByName(string textToSearch);
}
@@ -0,0 +1,9 @@
using System.Security.Claims;
namespace adas_core.Application.Services.Interfaces;
public interface ILocalAuditService
{
Task CreateAuditLogAsync(ClaimsPrincipal? user, object? dataOriginal, object? dataModified, string? reason = null);
Task<T?> DeepCopyAsync<T>(T data);
}
@@ -0,0 +1,20 @@
namespace adas_core.Application.Services.Interfaces
{
/// <summary>
/// Contrato genérico para un proveedor de locks por clave.
/// CacheService usará una implementación local en memoria.
/// RedisService podría usar una distribuida (si fuera necesario).
/// </summary>
public interface ILockProvider
{
/// <summary>
/// Intenta adquirir el lock para una clave dentro del timeout.
/// </summary>
Task<bool> AcquireAsync(string key, TimeSpan timeout);
/// <summary>
/// Libera el lock para la clave (idempotente).
/// </summary>
Task ReleaseAsync(string key);
}
}
@@ -0,0 +1,46 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.Masters;
using adas_core.Domain.Models.Responses;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IMasterListService<T> where T : MasterList
{
Task<IEnumerable<T>> GetAllMasterList();
Task<IEnumerable<MasterListDto>> GetAllMasterListWithoutOptions();
Task<IEnumerable<MasterListWithPaginatedOptionsDto>> GetAllMasterListWithPaginatedOptions(PaginationFilter request);
Task<T?> GetMasterListById(ObjectId id, LocaleEnum? locale);
Task<MasterListWithPaginatedOptionsDto?> GetMasterListByIdWithPaginatedOptions(ObjectId id,
PaginationFilter request);
Task<List<string>> GetMasterListOptionsNamesById(ObjectId id);
Task<List<OptionList>> GetMasterListByIdAndTextSearch(ObjectId id, string? textSearch);
Task<List<OptionList>> GetMasterListByIdAndSearchOptions(ObjectId id, FilterOptionListElement filterOption);
Task<T?> GetMasterListByName(string name);
Task<T?> InsertMasterList(T item);
Task<T?> UpdateMasterList(T item);
Task DeleteMasterListById(ObjectId id);
Task<OptionList?> AddOptionToMasterList(ObjectId id, FilterOptionListElement opt);
Task<OptionList?> UpdateMasterListOption(ObjectId id, OptionList opt, string typeName, LocaleEnum locale);
Task<OptionList?> UpdateFullMasterListOption(ObjectId id, OptionList opt, string typeName);
Task<OptionList?> UpdateMasterListOption(ObjectId id, OptionList opt, string typeName);
Task<OptionList?> FindOptionItemById(ObjectId masterId, ObjectId optionId, LocaleEnum locale);
Task<UpdateMasterListDetailsDto?> UpdateOptionDetailsToMasterList(ObjectId id, UpdateMasterListDetailsDto opt);
Task<bool> UpdateMasterListName(ObjectId id, string name);
Task<bool> UpdateMasterListDescription(ObjectId id, string name);
Task<bool> RemoveMasterListOption(ObjectId id, OptionList oldOpt);
Task<int> GetAllMasterListCount();
Task<PaginationResponse<T>> GetPaginatedMasterList(PaginationFilter filter);
Task<bool> DeleteMasterListOption(ObjectId id, ObjectId optId, string typeName);
Task<PaginationResponse<MasterListWithPaginatedOptionsDto>> GetPaginatedMasterListWithPaginatedOptions(
PaginationFilter listFilter, PaginationFilter optionsFilter);
Task<PaginationResponse<OptionList>> GetPaginatedOptions(PaginationFilter filter, ObjectId listId);
Task<ObjectId?> GetAssociatedList(ObjectId id, MasterListType masterListType1, MasterListType masterListType2);
Task<List<string>> GetOptionsOfList(ObjectId id);
}
@@ -0,0 +1,23 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models.Masters;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IMasterListServiceFactory
{
object GetService(Type serviceType);
object GetService(MasterListType serviceName);
object? GetTypedMasterList(MasterListType masterListType, MasterList masterList);
Type GetMasterListSpecificType(MasterListType masterListType);
Task<object?> InsertMasterList(MasterListType masterListType, MasterList masterList);
Task<object?> UpdateMasterList(MasterListType masterListType, MasterList masterList);
Task<object?> GetMasterListById(MasterListType masterListType, ObjectId masterListId, LocaleEnum? dataLocale);
List<string> StringNurseObs();
Task<Patient?> GetPatientTraslated(Unit? unit, LocaleEnum? locale, Patient? patient);
Task<object?> GetMasterListOptionById(MasterListType masterListType, ObjectId masterListId,
ObjectId masterListOptionId,
LocaleEnum? dataLocale);
}
@@ -0,0 +1,25 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.Responses;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IMedicineService
{
Task<Medicine?> GetByCode(string code);
Task<List<Medicine>> GetByCodeOrNote(List<string> codeNote);
Task<Medicine?> GetByName(string name);
Task<IEnumerable<Medicine>> GetMedicinesOfTreatments(IEnumerable<PatientTreatment?> treatments);
Task<IEnumerable<Medicine>> GetActiveMedicinesByPatient(ObjectId patientId);
Task<PaginationResponse<Medicine>> GetPaginatedMedicines(PaginationFilter filter);
Task<List<Medicine>> GetAll();
Task<Medicine?> GetMedicineById(ObjectId medicineId);
Task<Medicine?> PostMedicine(Medicine medicine);
Task<Medicine?> UpdateMedicine(Medicine medicine);
Task DeleteMedicineById(ObjectId medicineId);
Task<List<string>> GetAllTypes();
Task<List<string>> GetAllGroups();
Task<List<string>> GetAllNames();
Task<List<string>> GetAllCodes();
}
@@ -0,0 +1,19 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface INoticeService
{
Task DeleteNoticeAsync(Notice notice);
Task DeleteNoticeByIdAsync(ObjectId noticeId);
Task<Notice?> GetNoticeByIdAsync(ObjectId noticeId);
Task<IEnumerable<Notice>?> GetNoticeByTypeAsync(string noticeType);
Task<IEnumerable<Notice>> GetNoticesAsync();
Task<Notice?> InsertNotice(Notice notice);
Task UpdateNoticeAsync(Notice notice);
Task SaveRequest(ApiRequest apiRequest);
Task SaveRequestAsync(ApiRequest apiRequest);
Task<IEnumerable<Notice>?> GetNoticesByDisplayId(ObjectId displayId);
}
@@ -0,0 +1,12 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Models.MongoModels;
namespace adas_core.Application.Services.Interfaces;
public interface IObservationDemoService
{
Task<List<PatientObservation>> GenerateObservationByField(Patient patient, List<Field> dataFields);
Task<GroupedObservation> GenerateGroupedObservation(Patient patient, GroupedField groupedField);
Task<List<PatientObservationAlarm>> GenerateAlarmByField(Patient patient, List<Field> dataAlarmfields);
}
@@ -0,0 +1,85 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Services.Interfaces;
public interface IObservationService : IApiRequestService
{
//REMOVE
/*
List<PatientObservation> FindLastObservations(ObjectId patientId, string codingSystem, string code, int num = 2);
*/
Task<IAsyncCursor<PatientObservation>> FindByPatientIdAsync(ObjectId patientId);
Task<IAsyncCursor<PatientObservation>> FindByPatientIdAndCodingSystemAsync(ObjectId patientId, string codingSystem,
string name);
Task<List<PatientObservation>> FindLastObservations(ObjectId patientId, int num = 2,
List<string>? filterObservations = null);
Task UpdateExpiredObservations(List<PatientObservation> expiredObservations);
Task ExpireObservationsAndRecalculateAsync();
Task ExpireAlertsAndPowerOffAsync();
Task<List<PatientObservation>> FindLatestUniqueValuesByName(ObjectId patientId, string name, int? expires);
Task<List<PatientObservation>> FindLastObservationsByField(ObjectId patientId,
List<Field>? filterObservations = null, bool mapped = true, CancellationToken ct = default);
Task<List<PatientObservation?>> FindLastIntravenousLinesObservationByLocation(ObjectId patientId);
Task InsertObservation(PatientObservation patientObservation, bool persistObs = true, bool mapObs = true);
Task<bool> InsertIfChanged(string name, PatientObservation observation, bool persistObs = true, bool mapObs = true);
Task InsertNurseObservation(PatientObservation obs);
Task DeleteByPatientId(ObjectId id);
Task Archive(PatientObservation observation);
Task<PatientObservation?> MapObservation(PatientObservation obs, bool onlyByName = false);
Task<Dictionary<ObjectId, DateTime>> FindAllLastPatientObservationTime();
Task<PatientObservation?> MapObservationsByName(PatientObservation obs);
Task Archive(Patient patient);
Task ArchiveByPatientId(ObjectId id);
Task UpdateObservation(PatientObservation observation);
Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId);
Task SendObsBroadcast(BasePatientObservation obs);
Task SendObsBroadcast(List<PatientObservation> obs, PatientLocation location);
Task SendObsBroadcast(List<PatientObservation> obs, ObjectId pocId);
Task<PatientObservation?> FindLastBeforeDate(ObjectId patientId, DateTime date, string? obsName);
Task<List<PatientObservation>?> FindAnyWithSameDate(ObjectId patientId, DateTime date, string? obsName);
//TODO To implement
Task<List<PatientObservation>> FindAllBeforeDate(ObjectId patientId, DateTime date,
List<string>? filterObservations = null);
//TODO To implement
Task<List<PatientObservation>> FindAllAfterDate(ObjectId patientId, DateTime date,
List<string>? filterObservations = null);
Task<List<PatientObservation>> FindAllBetweenDates(ObjectId patientId, DateTime? startDate, DateTime? endDate,
List<string>? filterObservations = null, bool fromArchived = false, PaginationFilter? filter = null);
Task<IEnumerable<PatientObservation>> FindLastNotExpiredObservatonsByPatient(ObjectId patientId, string name,
int? endAfter = null, int? num = null);
Task CheckAndExpireObservations();
IAsyncEnumerable<PatientObservation> FindNotExpiredObservationsShouldBeExpired();
void ProcessObservations(List<PatientObservation> observations, Patient patient, DateTime messageTime,
ObservationData? observationData = null);
Task ExpireObservations();
Task InsertSimpleObservation(PatientObservation observation);
Task<PaginationResponse<PatientObservation>> GetPaginatedObservations(PaginationFilter filter);
Task SaveRequestNurseObsAsync(ApiRequest request);
}
@@ -0,0 +1,17 @@
using adas_core.Domain.Models.Masters;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IPatientCarePlanService
{
Task<List<PatientCarePlan>> FindByPatientId(ObjectId patientId);
Task<List<PatientCarePlan>> FindByUserId(ObjectId userId);
Task<List<PatientCarePlan>> FindAll();
Task InsertOneAsync(PatientCarePlan patientCarePlan);
Task ArchiveCarePlanFromJob(Patient patientWithFinishedProcedure, List<OptionList> itemsToArchive);
Task ArchiveByPatientId(ObjectId patientid);
Task UpdateManyObjectId(string patientid, ObjectId patientId, ObjectId oldId);
}
@@ -0,0 +1,84 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
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 MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IPatientService : IApiRequestService
{
Task<Patient?> FindByPatientId(string patientId, bool withLocation = false);
Task<Patient?> FindByPatientIdWithLocale(string patientId, LocaleEnum localeEnum);
Task<Patient?> FindByPatientNumberArchived(string patientNumber);
Task<Patient?> FindByPatientNumber(string patientNumber, bool withLocation = false);
Task<Patient?> FindByLocation(PatientLocation? location);
Task<Patient?> FindByPointOfCareId(ObjectId pocId);
Task<Patient?> FindByUnitAndPocId(ObjectId unit, ObjectId pointOfCare);
Task<List<Patient>> FindByPointOfCare(string pointOfCare);
Task<long> CountPatientsByUnitId(ObjectId unitId);
Task<Patient?> FindPatient(string? patientId, string? patientNumber, PatientLocation? location,
bool findByLocation = false);
Task Insert(Patient patient);
Task InsertAsync(Patient patient);
Task ArchivePatient(Patient patient);
Task ArchivePatientData(ObjectId patientid);
Task MergePatient(Patient patient, string oldPatienNumber);
Task UpdateLocation(ObjectId id, PatientLocation? location);
Task UpdateAttendingDoctor(ObjectId id, Person doctor);
Task UpdatePatientData(ObjectId id, string patientNumber, Person data, bool updatePatientNumber = true);
Task UpdatePatientData(ObjectId id, string patientNumber, Patient patient, bool updatePatientNumber = true);
Task Update(Patient patient);
Task<bool> Move(Patient patient, ObjectId newPocId, ObjectId oldPocId);
Task<Patient?> FindById(ObjectId id, bool withLocation = false);
Task<Box?> GetBox(PointOfCare poc, bool observations = false, List<string>? filterObservations = null);
Task<Patient?> CreatePatientFromRequest(ApiRequest apiRequest, bool ignoreLocation = false);
Task<Patient?> FindPatientByApiRequest(ApiRequest apiRequest);
Task ArchivePatientWithoutObservationsSinceDate(DateTime date);
Task ArchiveDischargedPatients(int hoursBeforeArchive);
Task<List<Patient>> FindAll(bool withLocation = false);
Task<PaginationResponse<Patient>> GetPaginatedPatients(PaginationFilter filter);
Task DischargeInactivePatients(DateTime sinceDate, int hoursBeforeArchive);
Task<Patient?> UpdateOne(Patient updatedPatient);
Task SendNewPatientBroadcast(Patient patient);
Task SendPatientUpdateBroadcast(Patient patient);
Task<List<Patient>> FindInActivePoC();
Task<List<Patient>> FindInInactivePoC();
Task<Patient?> GetByPointOfCare(PointOfCare item, bool observations = false,
List<string>? filterObservations = null);
Task<Patient?> GetByPointOfCareAndLocale(PointOfCare item, Unit? unit, LocaleEnum? localeEnum);
Task<Patient?> UpdatePatientAltable(ObjectId patientId, OptionList altable, User? user);
Task ExitPatientById(ObjectId id, bool archivePatient = true);
Task<Patient?> UpdatePatientMasterList(ObjectId patientId, MasterListType typeName,
List<OptionList> updatedOptions,
User? user, List<OptionList>? carePlanLog);
Task GenerateNurseCarePlanAndInsert(MasterListType carePlanType, List<OptionList>? options,
Patient patient, User? user);
Task UpdatePatientIncomingData(ObjectId patientId, Patient person);
Task UpdatePatientDemographicData(ObjectId patientId, Patient person, User? user);
Task<Patient?> SearchByPatientNumberAndDistinctUnit(string patientNumber, ObjectId unitId);
Task<List<Patient>> FindAllPatientWithFinishedProcedures(int archiveProcedureEndDateAfterMinutes);
Task<List<Patient>> FindAllPatientWithFinishedTest(int archiveTestEndDateAfterMinutes);
Task<List<Patient>> FindAllPatientWithFinishedTreatment(int archiveTreatmentEndDateAfterMinutes);
Task UpdatePatientIncomingData(ObjectId patientId, Patient person, PatientIncomeData personDataChange,
User user);
Task UpdatePatientMasterListItemChange(UpdateOptionMasterListDto opt, IEnumerable<Unit> unitList,
string typeName);
Task DeletePatientMasterListItem(OptionList opt, IEnumerable<Unit> unitList, string typeName);
}
@@ -0,0 +1,22 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.MongoModels;
namespace adas_core.Application.Services.Interfaces;
public interface IPermissionService
{
public Task<DisplayPermissionTypes> GetPermissionsForDisplay(Display display, User user);
public Task<DisplayPermissionTypes> GetPermissionsForUnit(string unitId, User user);
public PanelPermissionTypes GetPermissionsForPanel(User user);
public Task<PanelPermissionTypes> GetPermissionsForPanel(string user);
Task<bool> HasAccessToDisplay(string username, PermissionEnum.RolesType role,
PermissionEnum.SourcePermissionsEnum source, string displayId);
Task<bool> HasAccessToUnit(string username, PermissionEnum.RolesType role,
PermissionEnum.SourcePermissionsEnum source, string unitId);
Task<bool> HasAccessToPanel(string username, PermissionEnum.RolesType role,
PermissionEnum.SourcePermissionsEnum source);
}
@@ -0,0 +1,8 @@
using adas_core.Domain.Models;
namespace adas_core.Application.Services.Interfaces;
public interface IPoCMappingService
{
Task<PatientLocation?> Map(PatientLocation original);
}
@@ -0,0 +1,58 @@
using adas_core.Domain.Enums;
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 MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IPointOfCareService
{
Task Delete(ObjectId id);
Task<PointOfCare?> Update(PointOfCare pointOfCare);
Task UpdateUnit(ObjectId id, Unit unit);
Task<List<PointOfCare>> GetAll();
Task<List<PointOfCare>> GetAllConfigs();
Task<List<PointOfCare>> GetAllLocationInfo();
Task UpdateConfiguration(ObjectId id, PointOfCareConfiguration configuration);
Task<PointOfCare?> FindById(ObjectId id);
Task<PointOfCare?> FindByIdAllConfig(ObjectId id);
Task<IEnumerable<PointOfCare>?> FindAllByUnitId(ObjectId unit);
Task<IEnumerable<PointOfCare>?> FindByRoom(string room);
Task<IEnumerable<PointOfCare>?> FindByBed(string bed);
Task<List<PointOfCare>> FindAllByUnitIds(List<ObjectId> unitIds);
Task<PointOfCare?> GetInfo(ObjectId id, LocaleEnum? locale = null, bool fillPatientData = true, CancellationToken ct = default);
Task<PointOfCare?> InsertPointOfCare(PointOfCare pointOfCare);
Task<PointOfCare?> FindPoCByPatientId(ObjectId patientId);
Task SetPointOfCareStatus(ObjectId id, StatusEnum.PointOfCare status);
Task<IEnumerable<PointOfCare>> FindByUnitAndStatus(ObjectId unitId, StatusEnum.PointOfCare poc,
bool excludeVirtual = false);
void CheckNextAdmission(ObjectId? patientLocation);
Task<PointOfCare?> FindByBedAndUnitId(string? bed, ObjectId? unitId);
Task UpdateRelayConfig(PointOfCare poc);
Task<PointOfCare?> FindPoCByPatientNumber(string patientNumber);
Task<long> CountPoCsByUnitId(ObjectId unitId);
Task<long> CountVirtualPoCsByUnitId(ObjectId unitId);
Task<PaginationResponse<PointOfCare>> GetPaginatedPoCs(PaginationFilter filter);
Task DeletePoCsByUnitId(ObjectId unitId);
Task<HashSet<ObjectId>> FindAllIdCamerasInUse();
Task<HashSet<ObjectId>> FindAllIdRelaysInUse();
Task<HashSet<ObjectId>> FindAllIdBeaconsInUse();
Task<IEnumerable<PointOfCare>?> FindAllByUnitIdWithDevices(ObjectId unitId);
}
@@ -0,0 +1,9 @@
namespace adas_core.Application.Services.Interfaces;
public interface IPublisherService
{
Task<bool> CreateQueue(string queueName);
Task<bool> SendMessageError(object obj, string queueName);
Task<bool> SendMessage(object obj, string queueName);
Task<bool> SendMessage(string message, string queueName);
}
@@ -0,0 +1,40 @@
using MongoDB.Bson;
using adas_core.Domain.Models;
using adas_core.Domain.Models.Pumps;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
namespace adas_core.Application.Services.Interfaces;
public interface IPumpService: IApiRequestService
{
// Insert manual
Task InsertPumpObservation(PumpObservation obs);
// Mapping
Task<PumpObservation?> MapPumpObservation(PumpObservation obs);
// Consultas por paciente
Task<List<PumpObservation>> FindLastPumpObservations(ObjectId patientId, int num = 1);
Task<Dictionary<ObjectId, DateTime>> FindAllLastPatientObservationTime();
// Gestión de configuración
Task<List<ConfigPumpItem>?> GetItemsById(string id);
Task<List<ConfigPumps>?> GetAllPumpConfig();
Task<ConfigPumps?> GetPumpConfigsById(string id);
Task<ConfigPumps?> UpdatePumpConfig(ConfigPumps config);
Task<ConfigPumps?> InsertPumpConfig(ConfigPumps config);
Task<bool> DeletePumpConfig(ConfigPumps config);
// Paginación
Task<PaginationResponse<PumpObservation>?> GetPaginatedPump(PaginationFilter filter);
// Archivado / limpieza
Task DeleteByPatientId(ObjectId id);
Task Archive(Patient patient);
Task ArchiveByPatientId(ObjectId id);
// Mantenimiento ids
Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId);
}
@@ -0,0 +1,15 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.MongoModels;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IRecordingAlertService : IApiRequestService
{
Task<List<PatientRecordingAlert>> FindLastRecordingAlert(ObjectId patientId, int num = 2);
Task DeleteByPatientId(ObjectId id);
Task Archive(Patient patient);
Task ArchiveByPatientId(ObjectId id);
Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId);
}
@@ -0,0 +1,23 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.DTO;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Recording;
using Patient = adas_core.Domain.Models.MongoModels.Patient;
namespace adas_core.Application.Services.Interfaces;
public interface IRecordingService : IApiRequestService
{
Task<bool> SendCancelRecordingToRecordingApi(Patient patient, PointOfCare poc);
Task SendRecordingDataToQueue(Patient patient, PointOfCare poc, DateTime? date, DateTime? endDate,
DateTime? eventDate, AlarmEnum.Name? alarmName, AlarmEnum.Severity severity, string? alarmDescription,
bool start = true, AlarmEnum.Type type = AlarmEnum.Type.Manual);
Task SendRecordingData(Patient patient, PointOfCare poc, ManualRecording manualRecording, bool start);
Task<bool> SendAutoRecordingData(Patient patient, PointOfCare poc, RecordingData automaticRecording);
Task<List<RecordingData>?> GetRecordings(int roomName);
}
@@ -0,0 +1,24 @@
using adas_core.Domain.Enums;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IRelayService
{
Task<RelayEnum.Status> CheckRelayStatus(Relay relay);
Task<RelayEnum.Status> CheckRelayStatus(ObjectId relayId);
Task PowerOn(Relay relay);
Task PowerOff(Relay relay);
Task SetManualRelay(RelayEnum.Status status, ObjectId pocId, RelayEnum.Type type);
Task<Relay?> GetById(ObjectId relay);
List<Relay> GetRelayInList(List<ObjectId>? relayList);
List<Relay> GetRelayByTypeInList(List<ObjectId>? configurationRelayList, RelayEnum.Type type);
Task<PaginationResponse<Relay>> GetPaginatedRelays(PaginationFilter request);
Task<Relay?> InsertRelay(Relay request);
Task<Relay?> UpdateRelayById(ObjectId objectId, Relay relay);
}
@@ -0,0 +1,10 @@
using adas_core.Domain.Models.SystemAlerts;
namespace adas_core.Application.Services.Interfaces;
public interface ISendAlertService
{
Task<List<Queue>> GetQueues();
List<Performance> GetPerformance();
Task<List<ApiClients>> GetApiClients();
}
@@ -0,0 +1,8 @@
using adas_core.Domain.Models.MongoModels;
namespace adas_core.Application.Services.Interfaces;
public interface IServiceConfigService
{
Task<ServiceConfig?> Get(string id);
}
@@ -0,0 +1,21 @@
using adas_core.Application.Subscriptions;
using adas_core.Domain.Models;
using adas_core.Domain.Models.GroupedObservations;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface ISubscriberGroupedService
{
List<WsSubscriberGrouped> GetGrouped();
void RemoveGroupedObsByPatientId(string patientId);
void RemoveWsSubscriberByLocation(string patientId, PatientLocation? newLocation);
void RemoveWsSubscriberPatientIdAndWsId(string patientId, string wsIdToRemove);
List<string> CheckEmptySubscriberGroup(WsSubscriberGrouped wsl);
void AddSubscriberGrouped(WsSubscriberGrouped wsSubscriberGrouped);
void CheckOnSubscriptionGroup(GroupedField groupedField, ObjectId patientId, string timeZoneId, string connectionId,
GroupedObservation go);
void UpdateLastGroupedObsInGroup(string wsgHashCode, GroupedObservation newGroupedObservation);
}
@@ -0,0 +1,13 @@
using adas_core.Application.Subscriptions;
using MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface ISubscribersService
{
List<WsSubscriber> GetSubscribers();
WsSubscriber? GetById(string contextConnectionId);
List<WsSubscriber> GetByPocId(ObjectId pocId);
int RemoveConnectionById(string contextConnectionId);
void AddSubscriber(WsSubscriber subscriber);
}
@@ -0,0 +1,26 @@
using adas_core.Domain.Models;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Services.Interfaces;
public interface ITreatmentService : IApiRequestService
{
Task Insert(PatientTreatment treatment);
Task<bool> DeleteByPatientId(ObjectId id);
Task DeleteById(ObjectId id);
Task ArchiveByPatientId(ObjectId id);
Task Archive(Patient patient);
Task<bool> UpdateTreatment(PatientTreatment patientTreatment);
Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId);
Task<IEnumerable<PatientTreatment>> GetTreatmentsByPatientId(ObjectId id);
Task<IEnumerable<PatientTreatment?>> GetActiveTreatmentsByPatient(ObjectId id);
Task<IAsyncCursor<PatientTreatment>> FindByPatientIdAsync(ObjectId patientId);
Task<IEnumerable<PatientTreatment>> FindByPatientId(ObjectId patientId);
Task<List<PatientTreatment>> GetBolusTreatments(ObjectId patientId);
Task<PaginationResponse<PatientTreatment>> GetPaginatedTreatments(PaginationFilter filter);
Task<List<PatientTreatment>> GetActiveTreatmentsByPatientIdAndOrder(ObjectId patientId, string order);
}
@@ -0,0 +1,44 @@
using adas_core.Domain.Enums;
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 MongoDB.Bson;
namespace adas_core.Application.Services.Interfaces;
public interface IUnitService
{
Task<List<Unit>> GetAll(bool withPocs = false);
Task<List<UnitInfoDto>> GetAllCompact();
Task<UnitInfoDto> GetOneCompact(ObjectId id);
// Task<Unit?> GetByCodeSysAndCode(string unit);
Task<Unit?> GetByName(string unit);
// Task<Unit?> GetByPointOfCare(PointOfCare pointOfCare);
Task<Unit?> GetInfo(ObjectId id, LocaleEnum? dataLocale, bool fillLists = true, bool withPoCs = false);
Task<Unit?> GetInfo(ObjectId id, bool withPoCs = true, bool withDevices = true);
Task<Unit?> FindByPatientId(ObjectId patientId);
// Task<List<Unit>?> FindByLocation(PatientLocation location);
Task<Unit?> FindById(ObjectId? id);
Task<Unit?> FindByName(string? name);
Task<Unit?> FindByUnitNameOrPocName(string? name, string? pocName);
//Task<Unit?> FindByPointOfCare(PointOfCare pointOfCare);
Task<Unit?> InsertOne(Unit unit);
Task<Unit?> UpdateUnit(Unit unit);
Task<IEnumerable<Unit>?> FindUnitsByMasterListId(ObjectId masterListId, MasterListType masterListType);
Task<long> CountUnitsByMasterListId(ObjectId masterListId, MasterListType masterListType);
Task<IEnumerable<Unit>> FindUnitsByMasterListId(ObjectId masterListId);
Task<Unit?> UpdateUnitMasterList(UpdateUnitIdListDto updateUnitListDto);
Task<bool> UpdateConfiguration(ObjectId unitIdParsed, UnitConfiguration unitConfiguration);
Task<bool> DeleteUnitById(Unit unit);
Task<Unit?> UpdateUnitInfo(ObjectId unitId, string name, string title, string? configObsId = null);
Task<PaginationResponse<Unit>> GetPaginatedUnits(PaginationFilter filter, bool withPoCs);
}
@@ -0,0 +1,56 @@
using System.Security.Claims;
using adas_core.Application.Services.Interfaces;
using audit_logs.Services.Interfaces;
using Microsoft.Extensions.Logging;
namespace adas_core.Application.Services;
public class LocalAuditService(
IAuditService auditService,
ILogger<LocalAuditService> logger)
: ILocalAuditService
{
public async Task CreateAuditLogAsync(ClaimsPrincipal? user, object? dataOriginal, object? dataModified,
string? reason)
{
try
{
if (reason != null)
{
await auditService.CreateAuditLogAsync(user, dataOriginal!, dataModified!, reason);
}
else
{
_ = user?.FindFirst(ClaimTypes.Name)?.Value ?? "Not specified";
await auditService.CreateAuditLogAsync(user, dataOriginal!, dataModified!);
}
}
catch (Exception ex)
{
logger.LogError(ex.Message);
}
}
public async Task<T?> DeepCopyAsync<T>(T data)
{
try
{
return await auditService.DeepCopyAsync(data);
}
catch (Exception ex)
{
logger.LogWarning("Error copying object, trying with JsonDeepCopyAsync. Exception: {Message}", ex.Message);
try
{
var copy = await auditService.JsonDeepCopyAsync(data);
return copy;
}
catch (Exception e)
{
logger.LogError(e, "Error in fallback copy method. Exception: {Message}", e.Message);
return default;
}
}
}
}
@@ -0,0 +1,825 @@
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;
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);
}
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;
// }
// }
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;
}
}
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;
}
}
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;
}
}
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;
}
}
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;
}
}
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;
}
}
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;
}
}
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;
}
}
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;
}
}
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 [];
}
}
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 [];
}
}
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;
}
}
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 [];
}
}
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;
}
}
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;
}
}
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;
}
}
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 [];
}
}
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 [];
}
}
public async Task<List<OptionList>> GetMasterListByIdAndSearchOptions(ObjectId id,
FilterOptionListElement filterOption)
{
var repository = GetRepository();
return await repository.GetMasterListByIdAndSearchOptions(id, filterOption);
}
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;
}
}
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);
}
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);
}
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);
}
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;
}
}
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;
}
}
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;
}
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;
}
private IMasterListRepository<T> GetRepository()
{
return _serviceProvider.GetRequiredService<IMasterListRepository<T>>();
}
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);
}
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);
}
public Task SaveRequestAsync(ApiRequest apiRequest)
{
return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest); }));
}
public Task SaveRequest(ApiRequest apiRequest)
{
return Task.CompletedTask;
}
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);
}
}
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);
}
}
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;
}
private async Task<long> IsInUseCount(T list)
{
return await _unitService.Value.CountUnitsByMasterListId(list.Id, list.ListType);
}
}
@@ -0,0 +1,299 @@
using System.Reflection;
using adas_core.Application.Exceptions;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.Masters;
using adas_core.Domain.Models.MongoModels;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
namespace adas_core.Application.Services;
public class MasterListServiceFactory(
IServiceProvider serviceProvider,
ILogger<MasterListServiceFactory> logger,
IOptions<ListSettings> listSettings)
: IMasterListServiceFactory
{
private readonly IOptions<ListSettings> _listSettings = listSettings;
private readonly ILogger<MasterListServiceFactory> _logger = logger;
// Obtiene el servicio basado en MasterListType
public object GetService(MasterListType serviceName)
{
var typeParameter = Type.GetType($"adas_core.Domain.Models.Masters.{serviceName}, adas-core.Domain") ??
throw new BadRequestException(HttpEnum.ErrorMessage.BadRequestInvalidFormat);
var genericTypeDef = typeof(MasterListService<>);
var specificServiceType = genericTypeDef.MakeGenericType(typeParameter) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
return GetService(specificServiceType);
}
// Obtiene el servicio basado en un Type genérico
public object GetService(Type serviceType)
{
// Verifica si el tipo es genérico y está basado en MasterList
if (serviceType.IsGenericType && serviceType.GetGenericTypeDefinition() == typeof(MasterListService<>))
{
// Obtiene el tipo genérico base
var itemType = serviceType.GenericTypeArguments[0];
// Construye el tipo del servicio genérico con base en el tipo T
var specificServiceType = typeof(IMasterListService<>).MakeGenericType(itemType);
// Obtiene el servicio utilizando el tipo construido
var service = serviceProvider.GetService(specificServiceType);
return service ??
throw new InvalidOperationException(
$"No se pudo resolver el servicio de tipo {serviceType.FullName}");
}
_logger.LogError("El tipo de servicio {serviceType} no es compatible.", serviceType.FullName);
throw new InvalidOperationException($"El tipo de servicio {serviceType.FullName} no es compatible.");
}
public Type GetMasterListSpecificType(MasterListType masterListType)
{
// mapear cada valor de MasterListType a su respectivo tipo específico
return masterListType switch
{
MasterListType.AltableOptionList => typeof(AltableOptionList),
MasterListType.AllergyList => typeof(AllergyList),
MasterListType.DestinationList => typeof(DestinationList),
MasterListType.DiagnosisList => typeof(DiagnosisList),
MasterListType.DischargeStatusList => typeof(DischargeStatusList),
MasterListType.DoctorList => typeof(DoctorList),
MasterListType.DoctorTypeList => typeof(DoctorTypeList),
MasterListType.InternalDestinationList => typeof(InternalDestinationList),
MasterListType.InsulationList => typeof(InsulationList),
MasterListType.MobilityOptionList => typeof(MobilityOptionList),
MasterListType.OriginList => typeof(OriginList),
MasterListType.PatientStatusList => typeof(PatientStatusList),
MasterListType.ProcedureList => typeof(ProcedureList),
MasterListType.TestList => typeof(TestList),
MasterListType.ServiceList => typeof(ServiceList),
MasterListType.TherapeuticCeilingList => typeof(TherapeuticCeilingList),
MasterListType.PassiveSittingList => typeof(PassiveSittingList),
MasterListType.GenericList => typeof(GenericList),
MasterListType.TreatmentList => typeof(TreatmentList),
MasterListType.VisitOptionList => typeof(VisitOptionList),
MasterListType.AccessControlList => typeof(AccessControlList),
MasterListType.LanguageBarrierList => typeof(LanguageBarrierList),
_ => typeof(MasterList)
};
}
public object? GetTypedMasterList(MasterListType masterListType, MasterList masterList)
{
// Obtiene el tipo específico de MasterList basado en masterListType
var specificType = GetMasterListSpecificType(masterListType);
// Utiliza reflexión para crear una instancia del tipo específico
var specificMasterList = Activator.CreateInstance(specificType);
if (specificMasterList == null)
return null;
// Realiza el mapeo de propiedades de masterList a specificMasterList
MapMasterListProperties(masterList, specificMasterList);
return specificMasterList;
}
public async Task<object?> InsertMasterList(MasterListType masterListType, MasterList masterList)
{
dynamic service = GetService(masterListType) ??
throw new InvalidOperationException("Service not found for type " + masterListType);
var typedMasterList = GetTypedMasterList(masterListType, masterList) ??
throw new InvalidOperationException("Failed to convert MasterList to specific type.");
// Reflexión para invocar el método InsertMasterList
var method = service.GetType().GetMethod("InsertMasterList", new[] { typedMasterList.GetType() }) ??
throw new InvalidOperationException("Method InsertMasterList not found.");
var task = (Task)method.Invoke(service, new[] { typedMasterList });
await task.ConfigureAwait(false);
var resultProperty = task.GetType().GetProperty("Result");
return resultProperty?.GetValue(task);
}
public async Task<object?> UpdateMasterList(MasterListType masterListType, MasterList masterList)
{
dynamic service = GetService(masterListType) ??
throw new InvalidOperationException("Service not found for type " + masterListType);
var typedMasterList = GetTypedMasterList(masterListType, masterList) ??
throw new InvalidOperationException("Failed to convert MasterList to specific type.");
// Reflexión para invocar el método InsertMasterList
var method = service.GetType().GetMethod("UpdateMasterList", new[] { typedMasterList.GetType() }) ??
throw new InvalidOperationException("Method UpdateMasterList not found.");
var task = (Task)method.Invoke(service, new[] { typedMasterList });
await task.ConfigureAwait(false);
var resultProperty = task.GetType().GetProperty("Result");
return resultProperty?.GetValue(task);
}
public async Task<object?> GetMasterListById(MasterListType masterListType, ObjectId masterListId,
LocaleEnum? dataLocale)
{
dynamic service = GetService(masterListType) ??
throw new InvalidOperationException("Service not found for type " + masterListType);
// Reflexión para invocar el método InsertMasterList
var method =
service.GetType().GetMethod("GetMasterListById", new[] { typeof(ObjectId), typeof(LocaleEnum?) }) ??
throw new InvalidOperationException("Method GetMasterListById not found.");
var task = (Task)method.Invoke(service, new object[] { masterListId, dataLocale! });
await task.ConfigureAwait(false);
var resultProperty = task.GetType().GetProperty("Result");
return resultProperty?.GetValue(task);
}
public async Task<object?> GetMasterListOptionById(MasterListType masterListType, ObjectId masterListId,
ObjectId masterListOptionId,
LocaleEnum? dataLocale)
{
dynamic service = GetService(masterListType) ??
throw new InvalidOperationException("Service not found for type " + masterListType);
// Reflexión para invocar el método InsertMasterList
var method =
service.GetType().GetMethod("FindOptionItemById",
new[] { typeof(ObjectId), typeof(ObjectId), typeof(LocaleEnum) }) ??
throw new InvalidOperationException("Method GetMasterListById not found.");
var task = (Task)method.Invoke(service,
new object[] { masterListId, masterListOptionId, dataLocale ?? LocaleEnum.Default });
await task.ConfigureAwait(false);
var resultProperty = task.GetType().GetProperty("Result");
return resultProperty?.GetValue(task);
}
public List<string> StringNurseObs()
{
var stringNurseObs = new List<string>();
// 1. Obtener la instancia configurada de ListSettings
var listSettingsInstance = _listSettings.Value;
var listSettingsType = typeof(ListSettings);
// 2. Obtener todas las propiedades públicas de la instancia de ListSettings
// Estas propiedades son las que están inicializadas con ListSettingItem
var properties = listSettingsType.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in properties)
{
// 3. Leer el valor de la propiedad (que es un ListSettingItem) de la instancia configurada
// prop.GetValue(listSettingsInstance) devuelve el ListSettingItem que contiene el ManualObservationName
// 4. Extraer el ManualObservationName
if (prop.GetValue(listSettingsInstance) is ListSettingItem listSettingItem && !string.IsNullOrEmpty(listSettingItem.ManualObservationName))
stringNurseObs.Add(listSettingItem.ManualObservationName);
}
// 5. Añadir otras observaciones fijas
// (Añadir observaciones referentes a las acciones de NursePlan)
stringNurseObs.Add("PatientIncomingData");
return stringNurseObs;
}
public async Task<Patient?> GetPatientTraslated(Unit? unit, LocaleEnum? locale, Patient? patient)
{
if (patient == null)
return null;
// Si es Default, devolvemos tal cual
if (locale == LocaleEnum.Default || unit == null)
return patient;
var listMap = new List<(string field, ObjectId? listId, MasterListType type)>
{
("origin", unit.OriginListId, MasterListType.OriginList),
("diagnosis", unit.DiagnosisListId, MasterListType.DiagnosisList),
("visits", unit.VisitOptionListId, MasterListType.VisitOptionList),
("insulation", unit.InsulationListId, MasterListType.InsulationList),
("languageBarrier", unit.LanguageBarrierListId, MasterListType.LanguageBarrierList),
("allergies", unit.AllergyListId, MasterListType.AllergyList),
("therapeuticCeiling", unit.TherapeuticCeilingListId, MasterListType.TherapeuticCeilingList),
("tests", unit.TestListId, MasterListType.TestList),
("procedures", unit.ProcedureListId, MasterListType.ProcedureList),
("treatment", unit.TreatmentListId, MasterListType.TreatmentList)
};
foreach (var (field, listId, masterListType) in listMap)
{
if (listId == null)
continue;
// Obtener propiedad del paciente (origin, diagnosis, procedures…)
var prop = typeof(Patient).GetProperty(
field,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (prop == null)
continue;
var propValue = prop.GetValue(patient);
if (propValue == null)
continue;
// var master = listObj as dynamic;
// IEnumerable<OptionList> masterOptions = master.Options;
// ---------------------------------------------------------
// CASO 1 → OptionList simple
// ---------------------------------------------------------
if (propValue is OptionList single)
{
if (single.Id != null)
{
var translated =
await GetMasterListOptionById(masterListType, listId.Value, single.Id.Value, locale);
if (translated is OptionList translatedSingle)
single.Name = translatedSingle.Name;
}
continue;
}
// ---------------------------------------------------------
// CASO 2 → Lista de OptionList (procedures, tests, treatment…)
// ---------------------------------------------------------
if (propValue is IEnumerable<OptionList> list)
foreach (var item in list)
{
if (item.Id == null)
continue;
var translated = await GetMasterListOptionById(masterListType, listId.Value, item.Id.Value, locale);
if (translated is OptionList translatedSingle)
item.Name = translatedSingle.Name;
}
}
return patient;
}
private void MapMasterListProperties(MasterList source, object destination)
{
// Obtiene las propiedades del tipo de origen y destino
var sourceProps = source.GetType().GetProperties();
var destProps = destination.GetType().GetProperties();
foreach (var sourceProp in sourceProps)
foreach (var destProp in destProps)
if (destProp.Name == sourceProp.Name && destProp.PropertyType == sourceProp.PropertyType)
{
// Asigna el valor de la propiedad de origen a la propiedad de destino
destProp.SetValue(destination, sourceProp.GetValue(source));
break;
}
}
}
@@ -0,0 +1,273 @@
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.Filter;
using adas_core.Domain.Models.Responses;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Services;
public class MedicineService(
IMedicineRepository medicineRepository,
ITreatmentService treatmentService,
IOptions<ApiSettings> apiSettings,
ILogger<MedicineService> logger,
IHttpContextAccessor httpContextAccessor,
ILocalAuditService auditService)
: IMedicineService
{
private readonly List<string> _notesIndicatingMedication = apiSettings.Value.NotesIndicatingMedication ?? [];
public async Task<List<Medicine>> GetAll()
{
return await medicineRepository.GetAll();
}
public async Task<Medicine?> GetByCode(string code)
{
return await medicineRepository.GetMedicine(code);
}
public async Task<List<Medicine>> GetByCodeOrNote(List<string> codeNote)
{
return await medicineRepository.GetMedicineByCodeOrNote(codeNote);
}
public async Task<Medicine?> GetByName(string name)
{
return await medicineRepository.GetMedicineByName(name);
}
public async Task<IEnumerable<Medicine>> GetMedicinesOfTreatments(IEnumerable<PatientTreatment?> treatments)
{
var totalMedicines = new List<Medicine>();
try
{
foreach (var treatment in treatments)
{
if (treatment == null)
continue;
var medicines = new List<Medicine>();
foreach (var code in treatment.RequestedGiveCodes)
{
var medicineList = await medicineRepository.GetMedicineByCodeOrNote([code.Identifier]);
var medicine = medicineList.FirstOrDefault();
//Cant retrieve from medicine list check for parental nutrition and if is not and any note indicate that is medication it is added
//to collection
if (medicine == null)
{
var parentalNutritionMedicine = await CalculateParentalNutritionMedicine(treatment);
if (parentalNutritionMedicine != null)
{
medicines.Add(parentalNutritionMedicine);
continue;
}
var isMedicine = treatment.Notes.Any(n => _notesIndicatingMedication.Contains(n.Comment));
if (isMedicine)
medicines.Add(new Medicine
{
Codes = [code.Identifier],
Name = code.Text
});
}
else if (!medicine.Type.Contains("Nutrition"))
{
medicines.Add(medicine);
}
}
if (!string.IsNullOrEmpty(treatment.RequestedGiveTreatment))
try
{
medicines =
[
new Medicine
{
Name = treatment.RequestedGiveTreatment,
Type = medicines.FindAll(t => t.Type.Any())
.Select(m => m.Type.Aggregate((x, y) => x + "," + y)).Distinct()
.ToList(),
Codes = medicines.FindAll(t => t.Codes.Any())
.Select(m => m.Codes.Aggregate((x, y) => x + "," + y)).Distinct()
.ToList(),
Group = medicines.FindAll(t => t.Group.Any())
.Select(m => m.Group.Aggregate((x, y) => x + "," + y)).Distinct()
.ToList(),
Notes = medicines.FindAll(t => t.Notes.Any())
.Select(m => m.Notes.Aggregate((x, y) => x + "," + y)).Distinct()
.ToList()
}
];
}
catch (Exception ex)
{
logger.LogError("Error Getting Medicines from Treatment. {medicines} . Exception {ex}",
string.Join(",", medicines), ex);
}
totalMedicines.AddRange(medicines);
}
}
catch (Exception e)
{
logger.LogError("Error get Medicines Of Treatments: {eMessage} {eStackTrace}", e.Message, e.StackTrace);
}
return totalMedicines.AsEnumerable();
}
public async Task<IEnumerable<Medicine>> GetActiveMedicinesByPatient(ObjectId patientId)
{
var activeTreatments = await treatmentService.GetActiveTreatmentsByPatient(patientId);
return await GetMedicinesOfTreatments(activeTreatments);
}
public async Task<PaginationResponse<Medicine>> GetPaginatedMedicines(PaginationFilter filter)
{
var result = medicineRepository.GetPaginatedMedicines(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<Medicine>(dataList, filter.PageNumber, filter.PageSize, count);
}
public async Task<Medicine?> GetMedicineById(ObjectId medicineId)
{
return await medicineRepository.GetMedicineById(medicineId) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
}
public async Task<Medicine?> PostMedicine(Medicine medicine)
{
var result = await medicineRepository.PostMedicine(medicine) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, result);
return result;
}
public async Task<Medicine?> UpdateMedicine(Medicine medicine)
{
var oldMedicine = GetMedicineById(medicine.Id);
var newMedicine = await medicineRepository.UpdateMedicine(medicine);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldMedicine, newMedicine);
return newMedicine;
}
public async Task DeleteMedicineById(ObjectId medicineId)
{
var oldMedicine = GetMedicineById(medicineId);
await medicineRepository.DeleteMedicineById(medicineId);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldMedicine, null);
}
public async Task<List<string>> GetAllTypes()
{
try
{
var bsonDocuments = await medicineRepository.GetDistinctFieldDataQuery("type").ToListAsync();
var result = bsonDocuments.Select(doc => doc["type"].AsString).ToList();
return result;
}
catch (Exception)
{
return [];
}
}
public async Task<List<string>> GetAllGroups()
{
try
{
var bsonDocuments = await medicineRepository.GetDistinctFieldDataQuery("group").ToListAsync();
var result = bsonDocuments.Select(doc => doc["group"].AsString).ToList();
return result;
}
catch (Exception)
{
return [];
}
}
public async Task<List<string>> GetAllNames()
{
try
{
var bsonDocuments = await medicineRepository.GetDistinctFieldDataQuery("name").ToListAsync();
var result = bsonDocuments.Select(doc => doc["name"].AsString).ToList();
return result;
}
catch (Exception)
{
return [];
}
}
public async Task<List<string>> GetAllCodes()
{
try
{
var bsonDocuments = await medicineRepository.GetDistinctFieldDataQuery("codes").ToListAsync();
var result = bsonDocuments.Select(doc => doc["codes"].AsString).ToList();
return result;
}
catch (Exception)
{
return [];
}
}
//Exclusive for H12O, not in calculatedObservations of H12O to dont repeat code in multiple places.
private Task<Medicine?> CalculateParentalNutritionMedicine(PatientTreatment treatment)
{
Medicine? medicine = null;
try
{
if (treatment is { Notes: not null } && treatment.Notes.FirstOrDefault(n => n.Comment == "NPT") != null)
{
medicine = new Medicine
{
Name = treatment.Notes.FirstOrDefault(n => n.CommentType == "formularybaseformulation")?.Comment ??
"UNKNOWN"
};
if (treatment.Notes is { Count: > 0 }) medicine.Notes = treatment.Notes.Select(t => t.Comment).ToList();
if (treatment.Notes.FirstOrDefault(n =>
n.Comment is "LÍPIDOS NEONATALES AL 20%" or "LÍPIDOS NEONATALES AL 20% CON...") != null)
medicine.Type = [MedicineEnum.Types.ParenteralNutritionLipids.ToString()];
else
medicine.Type = [MedicineEnum.Types.ParenteralNutrition.ToString()];
}
}
catch (Exception e)
{
logger.LogError("Error calculate Parental Nutrition Medicine: {eMessage} {eStackTrace}", e.Message,
e.StackTrace);
}
return Task.FromResult(medicine);
}
}
@@ -0,0 +1,198 @@
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.MongoModels;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
namespace adas_core.Application.Services;
public class NoticeService(
ILogger<NoticeService> logger,
ISubscribersService subscribersService,
INoticeRepository noticeRepository,
IClientMessageService clientMessageService,
IDisplayService displayService,
IHttpContextAccessor httpContextAccessor,
ILocalAuditService auditService)
: INoticeService
{
public async Task DeleteNoticeAsync(Notice notice)
{
await DeleteNoticeByIdAsync(notice.Id);
}
public async Task DeleteNoticeByIdAsync(ObjectId noticeId)
{
try
{
var noticeAux = await noticeRepository.FindById(noticeId);
if (noticeAux == null)
{
logger.LogInformation("Error deleting Notice not found, id: {noticeId} NOT DELETED ", noticeId);
return;
}
await noticeRepository.Delete(noticeId);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, noticeAux, null);
logger.LogInformation("Notice id: {noticeId} DELETED ", noticeId);
SendNoticeBroadcast(noticeAux, OperationType.DeleteNotice);
}
catch (Exception ex)
{
logger.LogError("Exception deleting notice id:{notice} . Exception: {ex}", noticeId, ex);
}
}
public async Task<Notice?> GetNoticeByIdAsync(ObjectId noticeId)
{
try
{
return await noticeRepository.FindById(noticeId);
}
catch (Exception ex)
{
logger.LogError("Exception deleting notice id:{notice} . Exception: {ex}", noticeId, ex);
return null;
}
}
public async Task<IEnumerable<Notice>?> GetNoticeByTypeAsync(string noticeType)
{
return await noticeRepository.FindByType(noticeType) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
}
public async Task<IEnumerable<Notice>> GetNoticesAsync()
{
return await noticeRepository.FindAll();
}
public async Task<Notice?> InsertNotice(Notice notice)
{
try
{
notice.Id = new ObjectId();
await noticeRepository.InsertOneAsync(notice);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, notice);
logger.LogInformation("Notice: {notice} INSERTED", notice);
SendNoticeBroadcast(notice, OperationType.NewNotice);
return notice;
}
catch (Exception ex)
{
logger.LogError("Exception inserting notice {notice} . Exception: {ex}", notice, ex);
return null;
}
}
public async Task UpdateNoticeAsync(Notice notice)
{
try
{
var auxNotice = await noticeRepository.FindById(notice.Id) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
await noticeRepository.Update(notice);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, auxNotice, notice);
SendNoticeBroadcast(notice, OperationType.UpdateNotice);
}
catch (Exception ex)
{
logger.LogError("Exception updating notice {notice} . Exception: {ex}", notice, ex);
}
}
public async Task SaveRequest(ApiRequest apiRequest)
{
try
{
if (apiRequest.Notice == null)
return;
switch (apiRequest.Type)
{
case "NewNotice":
{
if (string.IsNullOrEmpty(apiRequest.Notice.Description))
{
logger.LogDebug("Error saving notice api request. Some values are required. Notice: {notice}",
apiRequest.Notice);
return;
}
await InsertNotice(apiRequest.Notice);
break;
}
case "UpdateNotice":
{
if (string.IsNullOrEmpty(apiRequest.Notice.Description))
{
logger.LogDebug("Error updating notice api request. Some values are required. Notice: {notice}",
apiRequest.Notice);
return;
}
await UpdateNoticeAsync(apiRequest.Notice);
break;
}
case "DeleteNotice":
{
await DeleteNoticeAsync(apiRequest.Notice);
break;
}
}
}
catch (Exception ex)
{
logger.LogError("Exception updating notice {notice} . Exception: {ex}", apiRequest.Notice, ex);
}
}
public Task SaveRequestAsync(ApiRequest apiRequest)
{
return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest); }));
}
public async Task<IEnumerable<Notice>?> GetNoticesByDisplayId(ObjectId displayId)
{
try
{
return await noticeRepository.FindByDisplayId(displayId);
}
catch (Exception e)
{
logger.LogError("Unable to get notice by unit on service Exception: {e}", e);
return null;
}
}
private async void SendNoticeBroadcast(Notice notice, OperationType operation)
{
try
{
var display = await displayService.GetById(notice.DisplayId);
if (display == null)
{
logger.LogError("Error sending notice broadcast. display is null or empty. Notice: {notice}", notice);
return;
}
var subscribers = subscribersService.GetSubscribers().Where(s => s.DisplayId == display.Id).ToList();
foreach (var subscriber in subscribers)
_ = clientMessageService.SendAsync(subscriber.Id, operation, notice);
}
catch (Exception ex)
{
logger.LogError("Exception sending notice broadcast. Operation type: {op}. Exception: {ex}",
operation.ToString(), ex);
}
}
}
@@ -0,0 +1,271 @@
using System.Collections;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Utils;
namespace adas_core.Application.Services;
public class ObservationDemoService(
IConfigObservationService configObservationService,
Lazy<IAlarmService> alarmService)
: IObservationDemoService
{
public async Task<List<PatientObservationAlarm>> GenerateAlarmByField(Patient patient, List<Field> dataAlarmfields)
{
var listToReturn = new List<PatientObservationAlarm>();
foreach (var field in dataAlarmfields)
{
var sendingRandom = new Random();
if (sendingRandom.Next(0, 2) == 0 || string.IsNullOrEmpty(field.Name))
continue;
var firstCof = await configObservationService.GetConfigObservationItemsByName(field.Name);
var conf = firstCof.FirstOrDefault();
if (conf == null)
continue;
var alarmObs = new PatientObservationAlarm
{
Patient = patient,
PatientId = patient.Id,
AlarmConfig = conf.Alarm,
InactivationState = new InactivationState
{
Audio = AlarmEnum.AudioVideoState.Enabled, Acknowledge = true,
Visual = AlarmEnum.AudioVideoState.Enabled
},
EventPhase = AlarmEnum.EventPhase.Continue,
Type = AlarmEnum.ObservationAlarmType.Sp,
Expires = conf.Expires,
State = AlarmEnum.ObservationAlarmState.Active,
Time = DateTime.UtcNow,
Persist = false,
MessageTime = DateTime.UtcNow,
Code = conf.Code,
PriorityLevel = conf.DemoConfig?.PriorityAlarm,
Name = conf.Name,
ParentData = new ParentDataClass
{
Code = conf.ParentCode,
Name = conf.ParentName,
CodingSystem = conf.CodingSystem
},
Units = conf.Units,
Value = GenerateValueRandom(conf, null) ?? 0
};
var obs = await alarmService.Value.MapObservationsByName(alarmObs);
if (obs != null)
listToReturn.Add(obs);
}
return listToReturn;
}
public async Task<List<PatientObservation>> GenerateObservationByField(Patient patient, List<Field> dataFields)
{
var listToReturn = new List<PatientObservation>();
var rnd = new Random();
foreach (var field in dataFields)
{
if (string.IsNullOrEmpty(field.Name))
continue;
var conf = await configObservationService.GetConfigObservationItemsByName(field.Name);
var firstCof = conf.FirstOrDefault();
if (firstCof == null)
continue;
var patientObs = new PatientObservation
{
Patient = patient,
PatientId = patient.Id,
Max = firstCof.MaxAlert,
Min = firstCof.MinAlert,
MaxWarn = firstCof.MaxWarn,
MinWarn = firstCof.MinWarn,
ShowOnExpired = firstCof.ShowOnExpired,
Expires = firstCof.Expires,
UiConfiguration = firstCof.UiConfiguration,
MessageTime = DateTime.UtcNow,
Code = firstCof.Code,
ColorOnExpired = firstCof.ColorOnExpired,
AlertColor = firstCof.AlertColor,
WarnColor = firstCof.WarnColor,
CodingSystem = firstCof.CodingSystem,
Alarm = firstCof.Alarm,
InsertMode = firstCof.InsertMode,
Name = field.Name,
Time = DateTime.UtcNow,
ParentData = new ParentDataClass
{
Code = firstCof.ParentCode,
Name = firstCof.ParentName,
CodingSystem = firstCof.CodingSystem
},
Units = firstCof.Units,
Value = GenerateValueRandom(firstCof, null) ?? 0
};
var result = await configObservationService.Map(patientObs, true);
if (firstCof.DemoConfig?.RequireInitDate == true && result != null)
{
var rndRestNmbHours = rnd.Next(3, 23);
var initDate = DateTime.UtcNow.AddHours(-rndRestNmbHours);
// Si se requiere fecha de fin, devolvemos un objeto con ambas
if (firstCof.DemoConfig?.RequireEndDate == true)
{
result.Time = initDate;
result.EndTime = initDate.AddHours(1);
}
// Si no, solo devolvemos la fecha inicial
result.Time = initDate;
}
if (result != null)
listToReturn.Add(result);
}
return listToReturn;
}
public async Task<GroupedObservation> GenerateGroupedObservation(Patient patient, GroupedField groupedField)
{
if (groupedField.Names.IsNullOrEmpty() && groupedField.Name != null) groupedField.Names.Add(groupedField.Name);
var rnd = new Random();
var startTime = DateTime.UtcNow;
var go = new GroupedObservation
{
Name = groupedField.Name,
PatientId = patient.Id,
Group = groupedField.Group,
Observations = []
};
foreach (var groupedFieldName in groupedField.Names)
{
var conf = await configObservationService.GetConfigObservationItemsByName(groupedFieldName);
var firstCof = conf.FirstOrDefault();
if (firstCof == null)
continue;
foreach (var result in groupedField.Result)
{
object? prevValue = null;
for (var i = 0; i <= groupedField.Max; i++)
{
var time = startTime;
time = groupedField.Regularity switch
{
GroupedObservationEnum.Regularity.Day => time.AddDays(-i),
GroupedObservationEnum.Regularity.Minute => time.AddMinutes(-i),
GroupedObservationEnum.Regularity.Second => time.AddSeconds(-i),
_ => time.AddHours(-i)
};
var val = new GroupedObservation.GroupedObservationObs
{
Name = groupedFieldName,
MaxAlert = firstCof?.MaxAlert,
MinAlert = firstCof?.MinAlert,
Time = time,
IsFilled = false
};
var value = new GroupedObservation.GroupedObservationObsValue(
GenerateValueRandom(firstCof, prevValue) ?? rnd.Next(0, 30), time);
prevValue = value.Value;
var propertyInfo = val.GetType().GetProperty(result.ToString());
if (propertyInfo != null && propertyInfo.CanWrite) propertyInfo.SetValue(val, value);
var valueType = await configObservationService.GroupedObservationStatus(
groupedField,
result,
groupedFieldName,
val,
firstCof?.MaxAlert,
firstCof?.MinAlert
);
value.Type = valueType;
go.Observations.Add(
val
);
}
if (firstCof?.DemoConfig?.RequireInitDate.HasValue == true && firstCof.DemoConfig.RequireInitDate.Value)
break;
}
}
return go;
}
private static object? GenerateValueRandom(ConfigObservation? firstCof, object? prevValue)
{
if (firstCof?.DemoConfig == null)
return "-";
// 1. Determinar cuántos valores necesitamos
var count = firstCof.DemoConfig?.ValueIsArray == true ? firstCof.DemoConfig?.RandomCount ?? 1 : 1;
var results = new List<object>();
for (var i = 0; i < count; i++)
{
var singleValue = PickSingleValue(firstCof.DemoConfig, prevValue);
if (singleValue != null)
results.Add(singleValue);
}
// 2. Si no es un array, devolvemos solo el primer elemento
return firstCof.DemoConfig?.ValueIsArray == true ? results : results.FirstOrDefault();
}
private static object? PickSingleValue(DemoConfig? config, object? prevValue)
{
if (config == null) return null;
var rnd = new Random();
// Prioridad 1: Selección de una lista de opciones (SetInValue)
if (config is { SetInValue: true, ValueOption: not null })
{
if (config.ValueOption is List<object> { Count: > 0 } options)
{
var index = rnd.Next(options.Count);
return options.ElementAt(index);
}
// Caso especial si ValueOption llega como un JArray o lista genérica
if (config.ValueOption is IEnumerable list)
{
var tempList = list.Cast<object>().ToList();
return tempList[rnd.Next(tempList.Count)];
}
}
// Prioridad 2: Generación numérica por rango (SetRandomValue)
if (config.SetRandomValue == false)
return null;
// 1. Extraemos los límites primero para usarlos en cualquier caso
var minLimit = config.MinValue ?? 0;
var maxLimit = config.MaxValue ?? 100;
if (prevValue is not int values)
// Caso base: Si no hay valor previo, generamos uno aleatorio dentro del rango permitido
return rnd.Next(minLimit, maxLimit + 1);
if (values == 0) values = 1;
// 2. Calculamos la variación (entre 5% y 10%)
var porcentaje = rnd.Next(5, 10) / 100.0;
var direccion = rnd.Next(0, 2) == 0 ? -1 : 1;
// 3. Calculamos el valor base redondeado
var calculado = (int)Math.Round(values + values * porcentaje * direccion);
// 4. Forzamos que esté dentro del rango [minLimit, maxLimit]
return Math.Clamp(calculado, minLimit, maxLimit);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,129 @@
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
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;
public class PatientCarePlanService : IPatientCarePlanService
{
private readonly IArchivePatientCarePlanService _archivePatientCarePlanService;
private readonly ILocalAuditService _auditService;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<PatientCarePlanService> _logger;
private readonly IPatientCarePlanRepository _patientCarePlanRepository;
private readonly IUserRepository _userRepository;
public PatientCarePlanService(
ILogger<PatientCarePlanService> logger,
IPatientCarePlanRepository patientCarePlanRepository,
IUserRepository userRepository,
IArchivePatientCarePlanService archivePatientCarePlanService,
IHttpContextAccessor httpContextAccessor,
ILocalAuditService auditService)
{
_logger = logger;
_patientCarePlanRepository = patientCarePlanRepository;
_userRepository = userRepository;
_archivePatientCarePlanService = archivePatientCarePlanService;
_httpContextAccessor = httpContextAccessor;
_auditService = auditService;
_httpContextAccessor = httpContextAccessor;
}
public async Task<List<PatientCarePlan>> FindByPatientId(ObjectId patientId)
{
try
{
return await _patientCarePlanRepository.FindByPatientId(patientId);
}
catch (Exception e)
{
_logger.LogError("Error finding Patient care plan by patientId: {PatientId} message: {Message}", patientId,
e.Message);
return [];
}
}
public async Task<List<PatientCarePlan>> FindByUserId(ObjectId userId)
{
try
{
return await _patientCarePlanRepository.FindByUserId(userId);
}
catch (Exception e)
{
_logger.LogError("Error finding Patient care plan by userId: {UserId} message: {Message}", userId,
e.Message);
return [];
}
}
public async Task<List<PatientCarePlan>> FindAll()
{
try
{
return await _patientCarePlanRepository.FindAll();
}
catch (Exception e)
{
_logger.LogError("Error finding all Patient care plan message: {Message}", e.Message);
return [];
}
}
public async Task InsertOneAsync(PatientCarePlan patientCarePlan)
{
try
{
patientCarePlan.Time = DateTime.UtcNow;
await _patientCarePlanRepository.InsertOneAsync(patientCarePlan);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, null, patientCarePlan);
}
catch (Exception e)
{
_logger.LogError("Error InsertOneAsync Patient care plan message: {Message} trace: {Trace}", e.Message,
e.StackTrace);
}
}
public async Task ArchiveCarePlanFromJob(Patient patientWithFinishedProcedure, List<OptionList> itemsToArchive)
{
var systemUser = await _userRepository.GetOrCreateSystemUser();
foreach (var item in itemsToArchive)
{
var itemToArchive = new PatientCarePlan
{
PatientId = patientWithFinishedProcedure.Id,
PatientNumber = patientWithFinishedProcedure.PatientNumber,
PointOfCareId = patientWithFinishedProcedure.PointOfCareId,
UserId = systemUser.Id,
CarePlan = item,
Description = "Archive item expired from job",
Action = ActionsEnum.CrudAction.Archive,
Time = DateTime.UtcNow
};
await _patientCarePlanRepository.InsertOneAsync(itemToArchive);
}
}
public async Task ArchiveByPatientId(ObjectId patientid)
{
var carePlansToArchive = await FindByPatientId(patientid);
await _archivePatientCarePlanService.InsertManyAsync(carePlansToArchive);
foreach (var item in carePlansToArchive)
{
await _patientCarePlanRepository.DeleteAsync(item.Id);
await _auditService.CreateAuditLogAsync(_httpContextAccessor.HttpContext?.User!, item, null);
}
}
public async Task UpdateManyObjectId(string patientid, ObjectId patientId, ObjectId oldId)
{
await _patientCarePlanRepository.UpdateManyObjectId(patientid, patientId, oldId);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,264 @@
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.AppSettings;
using adas_core.Domain.Models.MongoModels;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
namespace adas_core.Application.Services;
public class PermissionService(
IOptions<PermissionSettings> permissionsConfig,
ILogger<PermissionService> logger,
Lazy<IDisplayService> displayService,
Lazy<IUserRepository> userRepository,
Lazy<IAuthorityRepository> authorityRepository)
: IPermissionService
{
private readonly ILogger<PermissionService> _logger = logger;
private readonly PermissionSettings _permissionsConfig = permissionsConfig.Value;
public async Task<DisplayPermissionTypes> GetPermissionsForDisplay(Display display, User user)
{
var authorities = user.Authorization;
if (authorities == null)
{
var a = await userRepository.Value.GetByUserAndAuthoritesName(user.UserName);
authorities = a?.Authorization;
user.Authorization = authorities;
}
if (authorities == null) throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission);
foreach (var auth in authorities)
if (display.Id.ToString().Equals(auth.DisplayId) || display.UnitId.ToString().Equals(auth.UnitId))
{
_logger.LogInformation("User {user} has role {role} for display {displayId} or unit {unitId}",
user.UserName, auth.Rol, auth.DisplayId, auth.UnitId);
//var unitPerms = await GetPermissionsForUnit(display.UnitId.ToString(), user);
switch ((PermissionEnum.RolesType?)Enum.Parse(typeof(PermissionEnum.RolesType),
auth.Rol ?? string.Empty))
{
case PermissionEnum.RolesType.AuthGuest:
// return (await CheckPermissions(_permissionsConfig.Guest.Display, display.UnitId.ToString()));
return _permissionsConfig.Guest.Display;
case PermissionEnum.RolesType.AuthAdmin:
// return await CheckPermissions(_permissionsConfig.Admin.Display, display.UnitId.ToString());
return _permissionsConfig.Admin.Display;
case PermissionEnum.RolesType.AuthDeveloper:
// return await CheckPermissions(_permissionsConfig.Developer.Display, display.UnitId.ToString());
return _permissionsConfig.Developer.Display;
case PermissionEnum.RolesType.AuthDoctorchief:
// return await CheckPermissions(_permissionsConfig.DoctorChief.Display, display.UnitId.ToString());
return _permissionsConfig.DoctorChief.Display;
case PermissionEnum.RolesType.AuthDoctor:
// return await CheckPermissions(_permissionsConfig.Doctor.Display, display.UnitId.ToString());
return _permissionsConfig.Doctor.Display;
case PermissionEnum.RolesType.AuthNursesupervisor:
// return await CheckPermissions(_permissionsConfig.NursingSupervisor.Display, display.UnitId.ToString());
return _permissionsConfig.NursingSupervisor.Display;
case PermissionEnum.RolesType.AuthNurse:
// return await CheckPermissions(_permissionsConfig.Nurse.Display, display.UnitId.ToString());
return _permissionsConfig.Nurse.Display;
}
}
//return _permissionsConfig.NoPermissions.Display;
throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission);
}
public Task<DisplayPermissionTypes> GetPermissionsForUnit(string unitId, User user)
{
var authorities = user.Authorization;
if (authorities == null) throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission);
foreach (var auth in authorities)
if (auth.UnitId != null && auth.UnitId.Equals(unitId))
{
_logger.LogInformation("User {user} has role {role} for unit {unitId}", user.UserName, auth.Rol,
auth.UnitId);
switch ((PermissionEnum.RolesType?)Enum.Parse(typeof(PermissionEnum.RolesType),
auth.Rol ?? string.Empty))
{
case PermissionEnum.RolesType.AuthGuest:
// return await CheckPermissions(_permissionsConfig.Guest.Unit, auth.UnitId);
return Task.FromResult(_permissionsConfig.Guest.Unit);
case PermissionEnum.RolesType.AuthAdmin:
// return await CheckPermissions(_permissionsConfig.Admin.Unit, auth.UnitId);
return Task.FromResult(_permissionsConfig.Admin.Unit);
case PermissionEnum.RolesType.AuthDeveloper:
// return await CheckPermissions(_permissionsConfig.Developer.Unit, auth.UnitId);
return Task.FromResult(_permissionsConfig.Developer.Unit);
case PermissionEnum.RolesType.AuthDoctorchief:
// return await CheckPermissions(_permissionsConfig.DoctorChief.Unit, auth.UnitId);
return Task.FromResult(_permissionsConfig.DoctorChief.Unit);
case PermissionEnum.RolesType.AuthDoctor:
// return await CheckPermissions(_permissionsConfig.Doctor.Unit, auth.UnitId);
return Task.FromResult(_permissionsConfig.Doctor.Unit);
case PermissionEnum.RolesType.AuthNursesupervisor:
// return await CheckPermissions(_permissionsConfig.NursingSupervisor.Unit, auth.UnitId);
return Task.FromResult(_permissionsConfig.NursingSupervisor.Unit);
case PermissionEnum.RolesType.AuthNurse:
// return await CheckPermissions(_permissionsConfig.Nurse.Unit, auth.UnitId);
return Task.FromResult(_permissionsConfig.Nurse.Unit);
}
}
throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission);
}
/*
private async Task<DisplayPermissionTypes?> CheckPermissions(DisplayPermissionTypes guestUnit, string authUnitId)
{
var isParsed = ObjectId.TryParse(authUnitId, out var unitId);
if(isParsed)
{
var unit = await uniService.Value.FindById(unitId);
var unitConfiguration = unit?.Configuration;
if (unitConfiguration != null)
{
guestUnit.Admissions.Execute = unitConfiguration.ManualAdmit;
guestUnit.Discharges.Execute = unitConfiguration.ManualDischarge;
guestUnit.DemographicData.Create = unitConfiguration.ManualEdit;
guestUnit.DemographicData.Delete = unitConfiguration.ManualEdit;
guestUnit.DemographicData.Update = unitConfiguration.ManualEdit;
guestUnit.DemographicData.Execute = unitConfiguration.ManualEdit;
}
}
_logger.LogInformation("Permissions for unit {unitId}: {@permissions}", authUnitId, guestUnit);
return guestUnit;
}
*/
public async Task<PanelPermissionTypes> GetPermissionsForPanel(string user)
{
var userFound = await userRepository.Value.GetByUserName(user);
if(userFound == null) throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission);
userFound.Authorization = await authorityRepository.Value.GetUserAuthorities(userFound.Id);
return GetPermissionsForPanel(userFound);
}
public PanelPermissionTypes GetPermissionsForPanel(User user)
{
var authorities = user.Authorization;
if (authorities == null) throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission);
foreach (var auth in authorities)
if (auth.PanelAuthorization)
{
_logger.LogInformation("User {user} has role {role} for panel", user.UserName, auth.Rol);
switch ((PermissionEnum.RolesType?)Enum.Parse(typeof(PermissionEnum.RolesType),
auth.Rol ?? string.Empty))
{
case PermissionEnum.RolesType.AuthGuest:
return _permissionsConfig.Guest.Panel;
case PermissionEnum.RolesType.AuthAdmin:
return _permissionsConfig.Admin.Panel;
case PermissionEnum.RolesType.AuthDeveloper:
return _permissionsConfig.Developer.Panel;
case PermissionEnum.RolesType.AuthDoctorchief:
return _permissionsConfig.DoctorChief.Panel;
case PermissionEnum.RolesType.AuthDoctor:
return _permissionsConfig.Doctor.Panel;
case PermissionEnum.RolesType.AuthNursesupervisor:
return _permissionsConfig.NursingSupervisor.Panel;
case PermissionEnum.RolesType.AuthNurse:
return _permissionsConfig.Nurse.Panel;
}
}
throw new ForbbidenException(HttpEnum.ErrorMessage.ForbiddenNoPermission);
}
public async Task<bool> HasAccessToDisplay(string username, PermissionEnum.RolesType userRole,
PermissionEnum.SourcePermissionsEnum source, string displayId)
{
if (!ObjectId.TryParse(displayId, out var displayObjectId)) return false;
var display = await displayService.Value.GetById(displayObjectId);
var user = await userRepository.Value.GetByUserName(username);
if (user == null || display == null) return false;
var authorities = await authorityRepository.Value.GetUserAuthorities(user.Id);
user.Authorization = authorities;
return SearchRoleInDisplay(display, authorities, userRole);
}
public async Task<bool> HasAccessToUnit(string username, PermissionEnum.RolesType userRole,
PermissionEnum.SourcePermissionsEnum source, string unitId)
{
var user = await userRepository.Value.GetByUserName(username);
if (user == null) return false;
var authorities = await authorityRepository.Value.GetUserAuthorities(user.Id);
user.Authorization = authorities;
return SearchRoleInUnit(unitId, authorities, userRole);
}
public async Task<bool> HasAccessToPanel(string username, PermissionEnum.RolesType userRole,
PermissionEnum.SourcePermissionsEnum source)
{
var user = await userRepository.Value.GetByUserName(username);
if (user == null) return false;
var authorities = await authorityRepository.Value.GetUserAuthorities(user.Id);
user.Authorization = authorities;
return SearchRoleInPanel(authorities, userRole);
}
private static bool SearchRoleInDisplay(Display display, List<Authorization>? authorities,
PermissionEnum.RolesType role)
{
if (authorities is not { Count: > 0 }) return false;
return (
from auth in authorities
let authRole =
(PermissionEnum.RolesType?)Enum.Parse(typeof(PermissionEnum.RolesType), auth.Rol ?? string.Empty)
where display.Id.ToString().Equals(auth.DisplayId) || display.UnitId.ToString().Equals(auth.UnitId)
let result = SearchRoleInUnit(display.UnitId.ToString(), authorities, role)
where result || authRole == role
select authRole
).Any();
}
private static bool SearchRoleInUnit(string unitId, List<Authorization>? authorities, PermissionEnum.RolesType role)
{
if (authorities == null)
return false;
foreach (var auth in authorities)
{
var authRole =
(PermissionEnum.RolesType?)Enum.Parse(typeof(PermissionEnum.RolesType), auth.Rol ?? string.Empty);
if (auth.UnitId != null && auth.UnitId.Equals(unitId) && authRole == role) return true;
}
return false;
}
private static bool SearchRoleInPanel(List<Authorization>? authorities, PermissionEnum.RolesType role)
{
if (authorities == null)
return false;
return (
from auth in authorities
let authRole =
(PermissionEnum.RolesType?)Enum.Parse(typeof(PermissionEnum.RolesType), auth.Rol ?? string.Empty)
where auth.PanelAuthorization && authRole == role
select auth
).Any();
}
}
@@ -0,0 +1,60 @@
using adas_core.Application.Repositories.Interfaces;
using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Models;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.MongoModels;
using Microsoft.Extensions.Options;
namespace adas_core.Application.Services;
public class PoCMappingService : IPoCMappingService
{
private readonly string _key;
private readonly bool _mappingRequired;
private readonly IPoCMappingRepository _pocMappingRepository;
private readonly int? _refreshTimeout;
private PoCMapping? _mapping;
private DateTime _nextRefresh = DateTime.MinValue;
public PoCMappingService(IPoCMappingRepository pocMappingRepository, IOptions<ApiSettings> apiSettings)
{
_pocMappingRepository = pocMappingRepository;
_refreshTimeout = apiSettings.Value.PointOfCareMapping?.Refresh;
_mappingRequired = apiSettings.Value.PointOfCareMapping?.Required ?? _mappingRequired;
_key = apiSettings.Value.PointOfCareMapping?.Key ?? "PV1";
}
/// <summary>
/// Maps a received location with bed and pointOfCare to the equivalence in the PocMapping table if it finds the value,
/// otherwise it returns null
/// </summary>
/// <param name="original">location</param>
/// <returns>mapped location</returns>
public async Task<PatientLocation?> Map(PatientLocation original)
{
var pocMapping = await GetMapping();
var poc = pocMapping?.PointOfCares.FindAll(p => p.OriginalPoC == original.UnitName);
var pocWithBed = poc?.FirstOrDefault(p => p.Beds.Any(bed => bed[0] == original.Bed));
var newBed = pocWithBed?.Beds.Where(bed => bed[0] == original.Bed).Select(bed => bed[1]).FirstOrDefault();
if (string.IsNullOrEmpty(newBed)) return _mappingRequired ? null : original;
return new PatientLocation(pocWithBed!.NewPoC, newBed);
}
/// <summary>
/// Return mapping from cache of database based on refreshTime
/// </summary>
/// <returns></returns>
private async Task<PoCMapping?> GetMapping()
{
if (_mapping == null || DateTime.Now > _nextRefresh)
{
_mapping = await _pocMappingRepository.FindByKey(_key);
_nextRefresh = _refreshTimeout.HasValue
? DateTime.Now.AddSeconds(_refreshTimeout.Value)
: DateTime.MaxValue;
}
return _mapping;
}
}
@@ -0,0 +1,398 @@
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.Filter;
using adas_core.Domain.Models.Masters;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Responses;
using adas_core.Domain.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using MongoDB.Driver;
namespace adas_core.Application.Services;
public class PointOfCareService(
ILogger<PointOfCareService> logger,
IPointOfCareRepository pointOfCareRepository,
Lazy<IPatientService> patientService,
Lazy<IUnitService> unitService,
ISubscribersService subscribersService,
Lazy<IClientMessageService> clientMessageService,
Lazy<IAdmissionService> admissionService,
IHttpContextAccessor httpContextAccessor,
ILocalAuditService auditService,
ICacheService cacheService,
IOptions<CacheSettings> cacheSettings)
: IPointOfCareService
{
private readonly CacheSettings? _cacheSettings = cacheSettings.Value ;
public async Task<PointOfCare?> InsertPointOfCare(PointOfCare pointOfCare)
{
try
{
var unit = await unitService.Value.FindById(pointOfCare.UnitId);
if (unit == null)
{
logger.LogError("Unit id: {unitId} Not Found. PointOfCare not inserted", pointOfCare.UnitId);
return null;
}
pointOfCare.UnitId = unit.Id;
await pointOfCareRepository.InsertOneAsync(pointOfCare);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, pointOfCare);
return await FindById(pointOfCare.Id);
}
catch (Exception ex)
{
logger.LogError("Exception inserting pointOfCare: {pointOfCare}, Exception: {ex}", pointOfCare, ex);
return null;
}
}
public async Task Delete(ObjectId id)
{
var poc = await FindById(id);
if (poc is not { AdmissionId: null }) return;
await pointOfCareRepository.Delete(id);
await cacheService.DeleteByPatternAsync(CacheKeys.PointOfCareBase(id));
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, poc, null);
}
public async Task DeletePoCsByUnitId(ObjectId unitId)
{
await pointOfCareRepository.DeleteManyByUnitId(unitId);
await cacheService.DeleteByPatternAsync(CacheKeyPatterns.ForEntity(CacheEnum.EntityType.PontOfCare));
}
public async Task<HashSet<ObjectId>> FindAllIdCamerasInUse()
{
return await pointOfCareRepository.FindAllIdCamerasInUse();
}
public async Task<HashSet<ObjectId>> FindAllIdRelaysInUse()
{
return await pointOfCareRepository.FindAllIdRelaysInUse();
}
public async Task<HashSet<ObjectId>> FindAllIdBeaconsInUse()
{
return await pointOfCareRepository.FindAllIdBeaconsInUse();
}
public async Task<IEnumerable<PointOfCare>?> FindAllByUnitIdWithDevices(ObjectId unitId)
{
var result = await pointOfCareRepository.FindAllByUnitIdWithDevices(unitId);
return result ?? [];
}
public async Task<PointOfCare?> Update(PointOfCare pointOfCare)
{
var oldPoc = await pointOfCareRepository.FindById(pointOfCare.Id);
await pointOfCareRepository.Update(pointOfCare);
var updatedPoc = await GetInfo(pointOfCare.Id);
await cacheService.DeleteByPatternAsync(CacheKeys.PointOfCareBase(pointOfCare.Id));
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldPoc, updatedPoc);
if (updatedPoc == null) return null;
SendPointOfCareBroadcast(updatedPoc, OperationType.UpdatedPointOfCare);
return updatedPoc;
}
public async Task UpdateUnit(ObjectId id, Unit unit)
{
var poc = await FindById(id);
if (poc == null)
return;
await pointOfCareRepository.UpdateUnitId(id, unit);
await cacheService.DeleteByPatternAsync(CacheKeys.PointOfCareBase(id));
var newPoc = await FindById(id);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, poc, newPoc!);
SendPointOfCareBroadcast(poc, OperationType.UpdatedPointOfCare);
}
public async Task<List<PointOfCare>> GetAll()
{
var c = await pointOfCareRepository.GetAll();
return c ?? [];
}
public async Task<List<PointOfCare>> GetAllConfigs()
{
var c = await pointOfCareRepository.GetAllConfigs();
return c ?? [];
}
public async Task<List<PointOfCare>> GetAllLocationInfo()
{
var c = await pointOfCareRepository.GetAllLocationInfo();
return c ?? [];
}
public async Task<PaginationResponse<PointOfCare>> GetPaginatedPoCs(PaginationFilter filter)
{
var result = pointOfCareRepository.GetPaginatedPoCs(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<PointOfCare>(dataList, filter.PageNumber, filter.PageSize, count);
}
public async Task UpdateConfiguration(ObjectId id, PointOfCareConfiguration configuration)
{
var old = await pointOfCareRepository.GetPoCConfiguration(id) ??
throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed);
await pointOfCareRepository.UpdateConfiguration(id, configuration);
await cacheService.DeleteByPatternAsync(CacheKeys.PointOfCareBase(id));
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, old.Configuration,
configuration);
}
public async Task<PointOfCare?> FindById(ObjectId id)
{
return await pointOfCareRepository.FindByIdAllConfig(id);
}
public async Task<PointOfCare?> FindByIdAllConfig(ObjectId id)
{
return await pointOfCareRepository.FindByIdAllConfig(id);
}
public async Task<IEnumerable<PointOfCare>?> FindAllByUnitId(ObjectId unit)
{
var result = await pointOfCareRepository.FindAllByUnitId(unit);
return result ?? [];
}
public async Task<IEnumerable<PointOfCare>> FindByUnitAndStatus(ObjectId unitId, StatusEnum.PointOfCare poc,
bool excludeVirtual = false)
{
return await pointOfCareRepository.FindByUnitAndStatus(unitId, poc, excludeVirtual);
}
/// <summary>
/// Check nex admission when patient has exit from poc
/// </summary>
/// <param name="patientLocation">PoC id</param>
public async void CheckNextAdmission(ObjectId? patientLocation)
{
try
{
if (patientLocation == null) return;
var pocToCheck = await GetInfo(patientLocation.Value,null);
if (pocToCheck != null && pocToCheck.Status != StatusEnum.PointOfCare.Locked)
{
if (pocToCheck.AdmissionId != null)
{
pocToCheck.Status = StatusEnum.PointOfCare.Reserved;
}
else
{
// Sabemos que puede ser una lista, pero limitamos directamente a 1 elemento
var next = await admissionService.Value.GetAdmissionByPointOfCareId(pocToCheck.Id);
var adm = next.FirstOrDefault();
if (adm != null)
{
pocToCheck.Admission = adm;
pocToCheck.AdmissionId = adm.Id;
pocToCheck.Status = StatusEnum.PointOfCare.Reserved;
}
else
{
pocToCheck.Status = StatusEnum.PointOfCare.Available;
}
}
await Update(pocToCheck);
}
}
catch (Exception e)
{
logger.LogError("Error checking next admission for PointOfCare {pocId}: {message}", patientLocation,
e.Message);
}
}
public async Task<PointOfCare?> FindByBedAndUnitId(string? bed, ObjectId? unitId)
{
if (unitId == null || string.IsNullOrEmpty(bed)) return null;
return await pointOfCareRepository.FindByBedAndUnitId(bed, unitId.Value);
}
public async Task UpdateRelayConfig(PointOfCare poc)
{
var oldPoc = await pointOfCareRepository.GetPoCConfiguration(poc.Id);
if (poc.Configuration?.RelayIdList != null)
{
await pointOfCareRepository.UpdateRelayConfig(poc.Id, poc.Configuration.RelayIdList);
await cacheService.DeleteByPatternAsync(CacheKeys.PointOfCareBase(poc.Id));
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldPoc, poc);
}
}
public async Task<IEnumerable<PointOfCare>?> FindByRoom(string room)
{
return await pointOfCareRepository.FindByRoom(room);
}
public async Task<IEnumerable<PointOfCare>?> FindByBed(string bed)
{
return await pointOfCareRepository.FindByBed(bed);
}
public async Task<List<PointOfCare>> FindAllByUnitIds(List<ObjectId> unitIds)
{
var filter = Builders<PointOfCare>.Filter.In(p => p.UnitId, unitIds);
return await pointOfCareRepository.FindByFilter(filter);
}
public async Task<PointOfCare?> GetInfo(ObjectId id, bool fillPatientData = true)
{
var poc = await FindById(id);
if (poc == null) return null;
var unit = await unitService.Value.FindById(poc.UnitId);
if (unit is { Name: not null })
poc.UnitName = unit.Name;
if (fillPatientData)
{
var patient = await patientService.Value.GetByPointOfCare(poc);
if (patient != null)
{
poc.Patientid = patient.Id;
poc.Patient = patient;
}
if (poc.AdmissionId != null)
{
var admission = await admissionService.Value.GetAdmissionByIdAsync(poc.AdmissionId.Value);
if (admission != null)
poc.Admission = admission;
}
}
return poc;
}
public async Task<PointOfCare?> GetInfo(ObjectId id, LocaleEnum? locale, bool fillPatientData = true,
CancellationToken ct = default)
{
var (key, ttl) = CacheKeys.PointOfCareBaseKeyWithTtl(_cacheSettings, id);
var poc = await cacheService.GetOrSetObjectAsync(key,
() => pointOfCareRepository.FindById(id),
ttl, ct);
if (poc == null || !fillPatientData)
return poc;
var unit = await unitService.Value.FindById(poc.UnitId);
if (unit?.Name != null)
poc.UnitName = unit.Name;
var patient = locale != null
? await patientService.Value.GetByPointOfCareAndLocale(poc, unit, locale)
: await patientService.Value.GetByPointOfCare(poc);
if (patient != null)
{
poc.Patientid = patient.Id;
poc.Patient = patient;
}
if (poc.AdmissionId == null) return poc;
var admission = await admissionService.Value.GetAdmissionByIdAsync(poc.AdmissionId.Value);
if (admission != null)
poc.Admission = admission;
return poc;
}
public async Task<PointOfCare?> FindPoCByPatientId(ObjectId patientId)
{
var patient = await patientService.Value.FindById(patientId);
if (patient is { PointOfCareId: not null })
return await FindById(patient.PointOfCareId.Value);
return null;
}
public async Task<PointOfCare?> FindPoCByPatientNumber(string patientNumber)
{
var patient = await patientService.Value.FindByPatientNumber(patientNumber);
if (patient is { PointOfCareId: not null })
return await FindById(patient.PointOfCareId.Value);
return null;
}
public async Task<long> CountPoCsByUnitId(ObjectId unitId)
{
return await pointOfCareRepository.CountByUnitId(unitId);
}
public async Task<long> CountVirtualPoCsByUnitId(ObjectId unitId)
{
return await pointOfCareRepository.CountVirtualsByUnitId(unitId);
}
public async Task SetPointOfCareStatus(ObjectId id, StatusEnum.PointOfCare status)
{
var pointOfCare = await GetInfo(id,null);
if (pointOfCare == null) return;
pointOfCare.Status = status;
await pointOfCareRepository.Update(pointOfCare);
await cacheService.DeleteByPatternAsync(CacheKeys.PointOfCareBase(id));
var oldPoc = await auditService.DeepCopyAsync(pointOfCare);
if (oldPoc != null)
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldPoc, pointOfCare);
// await _patientService.Value.UpdatePatientAltable()
SendPointOfCareBroadcast(pointOfCare, OperationType.UpdatedPointOfCare);
}
public async Task<PointOfCare?> FindPoCByPatientLocation(PatientLocation patientLocation)
{
return await pointOfCareRepository.FindByPatientLocation(patientLocation);
}
private void SendPointOfCareBroadcast(PointOfCare pointOfCare, OperationType operation)
{
try
{
var subscribers = subscribersService.GetSubscribers()
.Where(s => s.LocationIds.Any(c => c == pointOfCare.Id)).ToList();
foreach (var subscriber in subscribers)
_ = clientMessageService.Value.SendAsync(subscriber.Id, operation, pointOfCare);
}
catch (Exception ex)
{
logger.LogError("Exception sending PoC broadcast. Operation type: {op}. Exception: {ex}",
operation.ToString(), ex);
}
}
}
@@ -0,0 +1,610 @@
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.AppSettings;
using adas_core.Domain.Models.Filter;
using adas_core.Domain.Models.MongoModels;
using adas_core.Domain.Models.Pumps;
using adas_core.Domain.Models.Responses;
using adas_core.Domain.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MongoDB.Bson;
using Patient = adas_core.Domain.Models.MongoModels.Patient;
namespace adas_core.Application.Services
{
/// <summary>
/// Servicio maestro de gestión de bombas
/// - Procesa ApiRequest (HL7/Alaris transformado)
/// - Histórico clínico (pump_observations)
/// - Histórico de alarmas (pump_alarm_events)
/// - Alarmas activas (pump_alarm_state)
/// - Snapshot (pump_state)
/// - Broadcast de snapshots (PumpState + PumpAlarmState)
/// </summary>
public class PumpService(
IPumpObservationRepository pumpObservationRepository,
IPumpStateRepository pumpStateRepo,
IPumpAlarmEventRepository alarmEventRepo,
IPumpAlarmStateRepository alarmStateRepo,
IPumpArchiveRepository pumpArchiveRepo,
IPatientService patientService,
IConfigPumpsService configPumpsService,
IOptions<ApiSettings> apiSettings,
ILogger<PumpService> logger,
ISubscribersService subscribersService,
IClientMessageService clientMessageService,
Lazy<ICalculatedObservationsService> calculatedObservationsService,
IHttpContextAccessor httpContextAccessor,
ILocalAuditService auditService,
IConfigUnitsService configUnitsService)
: IPumpService
{
// Settings
private readonly int _pumpExpiresSeconds = apiSettings.Value.PumpExpiresSeconds;
private readonly bool _sendPumpsZero = apiSettings.Value.SendPumpsZero;
// ======================================================================
// ENTRYPOINT
// ======================================================================
public async Task SaveRequest(ApiRequest req)
{
// Normalizar single vs list (Alaris puede mandar 1 sola)
if (req.PumpObservation != null && (req.PumpObservations == null || req.PumpObservations.Count == 0))
req.PumpObservations = [req.PumpObservation];
if (req.PumpObservations == null || req.PumpObservations.Count == 0)
{
logger.LogWarning("ApiRequest contains 0 PumpObservations");
return;
}
// Busca paciente (lookup/create) a partir de PatientNumber / Patient / PatientId string
var resolvedPatient = await patientService.FindPatientByApiRequest(req);
var foundPatientId = resolvedPatient?.Id;
foreach (var pobs in req.PumpObservations)
{
UpdatePatientFromRequest(req, pobs, foundPatientId);
// Regla Alaris: si el origen es AlarisPump y no hay PatientId -> descartar
if (string.Equals(req.Type, "AlarisPump", StringComparison.OrdinalIgnoreCase) &&
pobs.PatientId == null)
{
logger.LogWarning("AlarisPump: Observation descartada por ausencia de PatientId. DeviceId={device}", pobs.DeviceId);
continue;
}
if (pobs.Time == DateTime.MinValue)
pobs.Time = DateTime.UtcNow;
try
{
// 1) Procesar por tipo HL7 o según ObservationType (Alaris)
switch (req.Type)
{
case "ORU_R01": // PCD-01
case "ORU_R42": // PCD-10
pobs.MessageType = PumpEnum.PumpMessageType.Observation;
await ProcessObservation(pobs);
break;
case "ORU_R40": // PCD-04
pobs.MessageType = PumpEnum.PumpMessageType.Alarm;
await ProcessAlarm(pobs);
break;
case "AlarisPump":
if (pobs.MessageType == PumpEnum.PumpMessageType.Alarm)
await ProcessAlarm(pobs);
else
await ProcessObservation(pobs);
break;
default:
logger.LogWarning("Tipo de request desconocido para PumpService: {type}", req.Type);
break;
}
// 2) Snapshot de bomba
var state = await UpdatePumpState(pobs);
// 3) Alarmas activas del dispositivo
var activeAlarms = await alarmStateRepo.FindAllActiveByDeviceAsync(pobs.DeviceId!);
// 4) Broadcast de snapshots (PumpState + todas las PumpAlarmState)
await SendSnapshotsBroadcast(state, activeAlarms, pobs.PatientId ?? foundPatientId, req);
// 5) Retención (sobre histórico de observaciones) - reglas
await DoRetentionActions(pobs);
}
catch (Exception ex)
{
logger.LogError(ex, "Error procesando observation/alarm DeviceId={deviceId}", pobs.DeviceId);
}
}
}
// ======================================================================
// OBSERVACIONES (PCD-01 / PCD-10)
// ======================================================================
private async Task ProcessObservation(PumpObservation obs)
{
logger.LogDebug("Insertando OBSERVATION DeviceId={dev} Time={time}", obs.DeviceId, obs.Time);
obs.Id = ObjectId.GenerateNewId();
obs.Expires = _pumpExpiresSeconds;
var mapped = await MapPumpObservation(obs);
if (mapped != null)
{
await pumpObservationRepository.InsertAsync(mapped);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, mapped);
}
}
// ======================================================================
// ALARMAS (PCD-04)
// ======================================================================
private async Task ProcessAlarm(PumpObservation obs)
{
logger.LogDebug("Insertando ALARM DeviceId={dev}, Phase={phase}, Type={type}",
obs.DeviceId, obs.EventPhase, obs.AlarmType);
var alarmEvent = new PumpAlarmEvent
{
Id = ObjectId.GenerateNewId(),
DeviceId = obs.DeviceId,
RackId = obs.RackId,
DeviceTypeMdc = obs.DeviceTypeMdc,
DeviceIp = obs.DeviceIp,
PillarAssembly = obs.PillarAssembly,
PillarRackSlot = obs.PillarRackSlot,
Time = obs.Time,
InfusionId = obs.InfusionId,
AlarmType = obs.AlarmType,
AlarmTypeMdc = obs.AlarmTypeMdc,
AlarmDescription = obs.AlarmDescription,
AlarmPriority = obs.AlarmPriority,
AlarmState = obs.AlarmState,
AlarmInactivationState = obs.AlarmInactivationState,
EventPhase = obs.EventPhase,
AlertSourceMdc = obs.AlertSourceMdc,
PatientId = obs.PatientId
};
await alarmEventRepo.InsertAsync(alarmEvent);
await UpdateAlarmState(obs);
}
private async Task UpdateAlarmState(PumpObservation obs)
{
if (obs.EventPhase == null) return;
var phaseLower = obs.EventPhase.Value.ToString().ToLowerInvariant();
// Cierre
if (phaseLower == "end")
{
await alarmStateRepo.RemoveAsync(obs.DeviceId!, obs.AlarmType, obs.AlarmTypeMdc);
return;
}
// Start/continue → upsert
var state = new PumpAlarmState
{
Id = ObjectId.GenerateNewId(),
DeviceId = obs.DeviceId,
AlarmType = obs.AlarmType,
AlarmCodeMdc = obs.AlarmTypeMdc,
AlarmDescription = obs.AlarmDescription,
AlarmPriority = obs.AlarmPriority,
AlarmState = obs.AlarmState,
LastPhase = obs.EventPhase,
FirstSeen = DateTime.UtcNow,
LastUpdated = DateTime.UtcNow,
AlertSourceMdc = obs.AlertSourceMdc,
InfusionId = obs.InfusionId,
PatientId = obs.PatientId
};
await alarmStateRepo.UpsertActiveAsync(state);
}
// ======================================================================
// SNAPSHOT (devuelve el PumpState actualizado)
// ======================================================================
private async Task<PumpState> UpdatePumpState(PumpObservation obs)
{
var current = await pumpStateRepo.FindByDeviceIdAsync(obs.DeviceId!)
?? new PumpState
{
Id = ObjectId.GenerateNewId(),
DeviceId = obs.DeviceId
};
MergePumpState(current, obs);
current.LastUpdated = DateTime.UtcNow;
await pumpStateRepo.UpsertAsync(current);
return current;
}
private static void MergePumpState(PumpState state, PumpObservation obs)
{
// Identidad / físico
if (!string.IsNullOrWhiteSpace(obs.RackId)) state.RackId = obs.RackId;
if (!string.IsNullOrWhiteSpace(obs.DeviceTypeMdc)) state.DeviceTypeMdc = obs.DeviceTypeMdc;
if (!string.IsNullOrWhiteSpace(obs.DeviceIp)) state.DeviceIp = obs.DeviceIp;
if (!string.IsNullOrWhiteSpace(obs.PillarAssembly)) state.PillarAssembly = obs.PillarAssembly;
if (!string.IsNullOrWhiteSpace(obs.PillarRackSlot)) state.PillarRackSlot = obs.PillarRackSlot;
// Infusión
if (!string.IsNullOrWhiteSpace(obs.InfusionId)) state.InfusionId = obs.InfusionId;
// Estado
if (obs.InfusingStatus != null)
{
state.InfusingStatus = obs.InfusingStatus;
state.IsInfusing = obs.IsInfusing ?? false;
}
if (obs.Status != null) state.Status = obs.Status;
if (obs.PumpMode != null) state.PumpMode = obs.PumpMode;
if (!string.IsNullOrWhiteSpace(obs.ActiveSourceInfo)) state.ActiveSourceInfo = obs.ActiveSourceInfo;
if (!string.IsNullOrWhiteSpace(obs.InfusionModeDetail)) state.InfusionModeDetail = obs.InfusionModeDetail;
if (!string.IsNullOrWhiteSpace(obs.NotDeliveringReason)) state.NotDeliveringReason = obs.NotDeliveringReason;
if (!string.IsNullOrWhiteSpace(obs.Source)) state.Source = obs.Source;
// Métricas
if (HasValue(obs.FlowFluid)) state.FlowFluid = obs.FlowFluid;
if (HasValue(obs.Rate)) state.Rate = obs.Rate;
if (HasValue(obs.VolumeInfused)) state.VolumeInfused = obs.VolumeInfused;
if (HasValue(obs.FluidDelivTotal)) state.FluidDelivTotal = obs.FluidDelivTotal;
if (HasValue(obs.FluidDelivTotalSet)) state.FluidDelivTotalSet = obs.FluidDelivTotalSet;
if (HasValue(obs.VolumeRemaining)) state.VolumeRemaining = obs.VolumeRemaining;
if (HasValue(obs.Vtbi)) state.Vtbi = obs.Vtbi;
if (HasValue(obs.TimeRemaining)) state.TimeRemaining = obs.TimeRemaining;
if (HasValue(obs.TimeProgrammed)) state.TimeProgrammed = obs.TimeProgrammed;
// Medicación
if (!string.IsNullOrWhiteSpace(obs.DrugName)) state.DrugName = obs.DrugName;
if (!string.IsNullOrWhiteSpace(obs.DrugId)) state.DrugId = obs.DrugId;
if (HasValue(obs.Concentration)) state.Concentration = obs.Concentration;
if (HasValue(obs.DoseRate)) state.DoseRate = obs.DoseRate;
if (HasValue(obs.DrugAmount)) state.DrugAmount = obs.DrugAmount;
if (HasValue(obs.DrugDoseDelivered)) state.DrugDoseDelivered = obs.DrugDoseDelivered;
if (HasValue(obs.PatientWeight)) state.PatientWeight = obs.PatientWeight;
if (obs.Syringe != null) state.Syringe = obs.Syringe;
// Eventos / Alarmas resumen
if (obs.Event != null) state.Event = obs.Event;
if (obs.EventPhase != null) state.EventPhase = obs.EventPhase;
if (obs.AlarmType != null) state.AlarmType = obs.AlarmType;
if (!string.IsNullOrWhiteSpace(obs.AlarmDescription)) state.AlarmDescription = obs.AlarmDescription;
if (!string.IsNullOrWhiteSpace(obs.AlarmState)) state.AlarmState = obs.AlarmState;
if (!string.IsNullOrWhiteSpace(obs.AlarmInactivationState)) state.AlarmInactivationState = obs.AlarmInactivationState;
if (!string.IsNullOrWhiteSpace(obs.AlarmPriority)) state.AlarmPriority = obs.AlarmPriority;
if (!string.IsNullOrWhiteSpace(obs.AlarmTypeMdc)) state.AlarmCodeMdc = obs.AlarmTypeMdc;
// Paciente
state.PatientId = obs.PatientId;
}
private static bool HasValue(CommonPumpTypes.PumpValue? v) => v is { Value: not null };
// MAP
public async Task<PumpObservation?> MapPumpObservation(PumpObservation obs)
{
var obs2 = await configPumpsService.Map(obs);
var obs3 = await configUnitsService.Map(obs2);
var obs4 = await calculatedObservationsService.Value.Map(obs3);
if (obs4 == null)
logger.LogDebug("Mapping ignorado para obs");
return obs3;
}
//Métodos
public Task SaveRequestAsync(ApiRequest req) => SaveRequest(req);
// Últimas N observaciones por paciente
public async Task<List<PumpObservation>> FindLastPumpObservations(ObjectId patientId, int num = 1)
{
var list = await pumpObservationRepository.FindByPatientId(patientId);
if (list is List<PumpObservation> pumpObservations)
return pumpObservations is { Count: 0 }
? []
: pumpObservations.OrderByDescending(x => x.Time).Take(num).ToList();
return [];
}
// Última fecha de observación por paciente (para todos los pacientes)
public async Task<Dictionary<ObjectId, DateTime>> FindAllLastPatientObservationTime()
{
return await pumpObservationRepository.FindAllLastPatientObservationTimeAsync();
}
// Borrado completo por paciente (observaciones + alarmas + alarmState)
public async Task DeleteByPatientId(ObjectId id)
{
logger.LogDebug("Delete Pump data by Patient Id {id}", id);
await pumpObservationRepository.DeleteByPatientId(id);
await alarmEventRepo.DeleteByPatientId(id);
await alarmStateRepo.DeleteByPatientId(id);
}
// Archivo → por entidad paciente
public async Task Archive(Patient patient) => await ArchiveByPatientId(patient.Id);
// Archivo → por PatientId (mueve a archive_pumpobservations y elimina del activo)
public async Task ArchiveByPatientId(ObjectId id)
{
var list = await pumpObservationRepository.FindByPatientId(id);
var pumpObservations = list.ToList();
if (pumpObservations.Count != 0)
await pumpArchiveRepo.InsertManyAsync(pumpObservations);
await DeleteByPatientId(id);
logger.LogDebug("Archived Pump observations & deleted active data by Patient Id {id}", id);
}
// Actualización masiva
public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
{
if (string.IsNullOrWhiteSpace(nameId))
throw new ArgumentException("nameId no puede ser nulo o vacío.", nameof(nameId));
var updatedObs = await pumpObservationRepository.UpdateManyObjectIdByFieldAsync(nameId, id, oldId);
// actualizar también alarmas activas e históricas
_ = await alarmEventRepo.UpdateManyObjectIdByFiledNameAsync(nameId, id, oldId);
_ = await alarmStateRepo.UpdateManyObjectIdByFieldNameAsync(nameId, id, oldId);
logger.LogInformation(
"UpdateManyObjectId completado. Campo={field}, oldId={oldId}, newId={newId}. Obs actualizadas={obsUpdated}",
nameId, oldId, id, updatedObs);
// Si quieres auditar el cambio:
await auditService.CreateAuditLogAsync(
httpContextAccessor.HttpContext?.User!,
new { Field = nameId, OldId = oldId, NewId = id, Scope = "PumpObservation" },
null);
}
// ConfigPumpsService
public async Task<List<ConfigPumpItem>?> GetItemsById(string id)
=> await configPumpsService.GetConfigItems(id);
public async Task<List<ConfigPumps>?> GetAllPumpConfig()
=> await configPumpsService.GetAllPumpConfigs()
?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
public async Task<ConfigPumps?> GetPumpConfigsById(string id)
=> await configPumpsService.GetPumpConfigById(id)
?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
public async Task<ConfigPumps?> UpdatePumpConfig(ConfigPumps config)
{
var oldConfig = await configPumpsService.GetPumpConfigById(config.Id);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldConfig, config);
return await configPumpsService.UpdatePumpConfig(config)
?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
}
public async Task<ConfigPumps?> InsertPumpConfig(ConfigPumps config)
{
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, config);
return await configPumpsService.InsertPumpConfig(config)
?? throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed);
}
public async Task<bool> DeletePumpConfig(ConfigPumps config)
{
var result = await configPumpsService.DeletePumpConfig(config);
if (!result) throw new ConflictException(HttpEnum.ErrorMessage.ConflictDeleteFailed);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, config, null);
return result;
}
// Paginación de observaciones
public async Task<PaginationResponse<PumpObservation>?> GetPaginatedPump(PaginationFilter filter)
{
// Implementación compatible sin nuevos métodos en los repos:
// 1) Si llega PatientId, paginamos en memoria desde FindByPatientId.
// 2) Si llega DeviceId, usamos FindByDeviceIdAsync y paginamos en memoria.
// 3) Si no hay filtro, devolvemos vacío para evitar lecturas completas.
var page = filter.PageNumber <= 0 ? 1 : filter.PageNumber;
var size = filter.PageSize <= 0 ? 20 : filter.PageSize;
var fr = filter.FilteredRequest;
if (fr == null) return new PaginationResponse<PumpObservation>([], page, size, 0);
List<PumpObservation> all;
if (!string.IsNullOrWhiteSpace(fr.PatientId))
{
if (!ObjectId.TryParse(fr.PatientId, out var patientId))
return new PaginationResponse<PumpObservation>([], page, size, 0);
var list = await pumpObservationRepository.FindByPatientId(patientId);
all = list.ToList();
if (fr.StartDate.HasValue)
all = all.Where(o => o.Time >= fr.StartDate.Value).ToList();
if (fr.EndDate.HasValue)
all = all.Where(o => o.Time <= fr.EndDate.Value).ToList();
}
else if (!string.IsNullOrWhiteSpace(fr.DeviceId))
{
var found = await pumpObservationRepository.FindByDeviceIdAsync(fr.DeviceId, fr.StartDate, fr.EndDate);
all = found.ToList();
}
else
{
all = [];
}
var count = all.Count;
var pageData = all
.OrderByDescending(o => o.Time)
.Skip((page - 1) * size)
.Take(size)
.ToList();
return new PaginationResponse<PumpObservation>(pageData, page, size, count);
}
// Inserción manual
public async Task InsertPumpObservation(PumpObservation obs)
{
logger.LogDebug("Insert {obs}", obs);
obs.Id = ObjectId.GenerateNewId();
if (obs.Time == DateTime.MinValue) obs.Time = DateTime.UtcNow;
var obsMapped = await MapPumpObservation(obs);
if (obsMapped != null)
{
await pumpObservationRepository.InsertAsync(obsMapped);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, obsMapped);
if (!_sendPumpsZero && obsMapped.Number == 0) return;
// Tras inserción manual, actualizar y emitir snapshots
var state = await UpdatePumpState(obsMapped);
var activeAlarms = await alarmStateRepo.FindAllActiveByDeviceAsync(obsMapped.DeviceId!);
await SendSnapshotsBroadcast(state, activeAlarms, obsMapped.PatientId, null);
await DoRetentionActions(obsMapped);
}
}
private async Task DoRetentionActions(PumpObservation obs)
{
var result = await configPumpsService.RetentionActions(obs);
if (result is not { RetentionPolicyValue: not null })
return;
switch (result.RetentionPolicy)
{
case RetentionPolicy.DeleteOlderDays:
{
var removed = await pumpObservationRepository.DeleteOlderThanDaysAsync(
result.RetentionPolicyValue.Value);
logger.LogInformation(
"Retention DeleteOlderDays: {removed} deleted (>{days} days)",
removed, result.RetentionPolicyValue);
break;
}
case RetentionPolicy.DeleteOlderNumber:
{
var removed = await pumpObservationRepository.DeleteKeepLastNAsync(
result.RetentionPolicyValue.Value);
logger.LogInformation(
"Retention DeleteOlderNumber: {removed} deleted (keeping {max})",
removed, result.RetentionPolicyValue);
break;
}
case RetentionPolicy.NoDelete:
case RetentionPolicy.DeleteOlderSeconds:
default:
logger.LogWarning("Unknown retention policy: {policy}", result.RetentionPolicy);
break;
}
}
// BROADCAST SNAPSHOTS: PumpState + PumpAlarmState (activos)
private async Task SendSnapshotsBroadcast(
PumpState state,
IEnumerable<PumpAlarmState> activeAlarms,
ObjectId? patientId,
ApiRequest? req)
{
var subscribers = new List<WsSubscriber>();
if (patientId != null)
{
var patient = await patientService.FindById(patientId.Value);
if (patient != null)
{
subscribers = subscribersService.GetSubscribers()
.Where(s => !s.Locations.IsNullOrEmpty()
&& s.Locations.Any(c =>
c.UnitName == patient.Location.UnitName &&
c.Bed == patient.Location.Bed &&
c.Room == patient.Location.Room))
.ToList();
}
}
else if (req?.Location != null)
{
var loc = req.Location;
subscribers = subscribersService.GetSubscribers()
.Where(s => !s.Locations.IsNullOrEmpty()
&& s.Locations.Any(c =>
c.UnitName == loc.UnitName &&
c.Bed == loc.Bed &&
c.Room == loc.Room))
.ToList();
}
if (subscribers.Count == 0) return;
// Enviar PumpState
foreach (var sub in subscribers)
await clientMessageService.SendAsync(sub.Id, OperationType.Pump, state);
// Enviar todas las PumpAlarmState activas
foreach (var alarm in activeAlarms)
{
foreach (var sub in subscribers)
await clientMessageService.SendAsync(sub.Id, OperationType.PumpAlarm, alarm);
}
}
private static void UpdatePatientFromRequest(ApiRequest req, PumpObservation obs, ObjectId? foundPatientId)
{
if (obs.PatientId == null && foundPatientId != null)
obs.PatientId = foundPatientId;
if (obs.PatientId != null || string.IsNullOrWhiteSpace(req.PatientId)) return;
if (ObjectId.TryParse(req.PatientId, out var parsed))
obs.PatientId = parsed;
}
}
}
@@ -0,0 +1,170 @@
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.MongoModels;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
namespace adas_core.Application.Services;
public class RecordingAlertService(
Lazy<IPatientService> patientService,
IConfigObservationService configObservationService,
IRecordingAlertRepository recordingAlertRepository,
IRecordingAlertArchiveRepository recordingAlertArchiveRepository,
ILogger<RecordingAlertService> logger,
IClientMessageService clientMessageService,
ISubscribersService subscribersService,
IHttpContextAccessor httpContextAccessor,
ILocalAuditService auditService)
: IRecordingAlertService
{
public async Task Archive(Patient patient)
{
await ArchiveByPatientId(patient.Id);
}
public async Task ArchiveByPatientId(ObjectId id)
{
logger.LogDebug("Archive Recording Alerts by Patient Id {id}", id);
using (var cursor = await recordingAlertRepository.FindByPatientIdAsync(id))
{
while (await cursor.MoveNextAsync())
foreach (var current in cursor.Current)
await recordingAlertArchiveRepository.InsertOneAsync(current);
}
await DeleteByPatientId(id);
}
public async Task DeleteByPatientId(ObjectId id)
{
logger.LogDebug("Delete Recording Alerts by Patient Id {id}", id);
await recordingAlertRepository.DeleteByPatientId(id);
}
public async Task<List<PatientRecordingAlert>> FindLastRecordingAlert(ObjectId patientId, int num = 2)
{
return await recordingAlertRepository.AggregatedPatientLastObservations(patientId, num);
}
public Task SaveRequestAsync(ApiRequest apiRequest)
{
return Task.FromResult(Task.Run(async () => { await SaveRequest(apiRequest); }));
}
public async Task SaveRequest(ApiRequest apiRequest)
{
if (string.IsNullOrEmpty(apiRequest.PatientNumber) && string.IsNullOrEmpty(apiRequest.Location?.UnitName) &&
string.IsNullOrEmpty(apiRequest.Location?.Bed))
{
logger.LogDebug("Patient and Location are nulls in apiRequest RecordingAlerts");
return;
}
logger.LogDebug("patientNumber: {apiRequestpatientNumber} location: {apiRequestlocation}",
apiRequest.PatientNumber, apiRequest.Location);
logger.LogDebug("RequestType: {apiRequesttype}", apiRequest.Type);
switch (apiRequest.Type)
{
/*
* ORU_R01 - Unsolicited transmission of an observation message
* ORU_R40 - Unsolicited transmission of an alert observation message
*/
case "ORU_R01":
case "ORU_R40":
case "RecordingAlert":
var patient = await patientService.Value.FindPatientByApiRequest(apiRequest);
if (patient == null)
{
// NO PATIENTS OR LOCATIONS WERE FOUND
logger.LogWarning(
"Observation for patient: {apiRequestpatientNumber} with location: {apiRequestlocation} not found, ignoring",
apiRequest.PatientNumber, apiRequest.Location);
return;
}
// Recording Alerts
if (apiRequest.RecordingAlert != null && apiRequest.RecordingAlerts?.FirstOrDefault() == null)
apiRequest.RecordingAlerts = [apiRequest.RecordingAlert];
if (apiRequest.RecordingAlerts != null)
{
logger.LogDebug("INSERT {apiRequestrecordingAlerts} OBSERVATIONS",
apiRequest.RecordingAlerts.Count);
foreach (var obs in apiRequest.RecordingAlerts)
{
obs.PatientId = patient.Id;
obs.Id = ObjectId.GenerateNewId();
if (obs.Time == DateTime.MinValue) obs.Time = DateTime.UtcNow;
await InsertRecordingAlert(obs);
}
}
break;
default:
logger.LogWarning("ApiRequest type {apiRequesttype} sis not valid for Observations", apiRequest.Type);
throw new ApiRequestException("ApiRequest type " + apiRequest.Type + " is not valid for Observations");
}
}
public async Task UpdateManyObjectId(string nameId, ObjectId id, ObjectId oldId)
{
await recordingAlertRepository.UpdateManyObjectId(nameId, id, oldId);
}
private async Task InsertRecordingAlert(PatientRecordingAlert recAlert)
{
logger.LogDebug("Insert {recAlert}", recAlert);
await recordingAlertRepository.InsertOneAsync(recAlert);
await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, recAlert);
await SendObsBroadcast(recAlert);
await DoRetentionActions(recAlert);
}
private async Task SendObsBroadcast(BasePatientObservation recAlert)
{
if (recAlert.Name == null) return;
var type = recAlert is PatientRecordingAlert ? OperationType.RecordingAlert : OperationType.Observation;
var patient = await patientService.Value.FindById(recAlert.PatientId);
if (patient?.PointOfCareId == null) return;
var subscribers = subscribersService.GetSubscribers()
.Where(s => s.LocationIds.Contains(patient.PointOfCareId.Value)).ToList();
foreach (var subscriber in subscribers) await clientMessageService.SendAsync(subscriber.Id, type, recAlert);
}
private async Task DoRetentionActions(PatientRecordingAlert recAlert)
{
var result = await configObservationService.RetentionActions(recAlert);
if (result is not { RetentionPolicyValue: not null } || recAlert.Name == null) return;
switch (result.RetentionPolicy)
{
case RetentionPolicy.DeleteOlderDays:
await recordingAlertRepository.DeleteOlderDaysAsync(recAlert.Name, result.RetentionPolicyValue.Value);
break;
case RetentionPolicy.DeleteOlderNumber:
await recordingAlertRepository.DeleteOlderNumberAsync(recAlert.Name, result.RetentionPolicyValue.Value);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
@@ -0,0 +1,441 @@
using System.Net;
using System.Net.Http.Headers;
using System.Text;
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.MongoModels;
using adas_core.Domain.Models.Recording;
using adas_core.Domain.Models.Responses;
using adas_core.Domain.Utils;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Serilog;
using Patient = adas_core.Domain.Models.MongoModels.Patient;
namespace adas_core.Application.Services;
public class RecordingService : IRecordingService
{
private readonly IAuthService _authService;
private readonly IClientMessageService _clientMessageService;
private readonly HttpClient _httpClient;
private readonly ILogger<RecordingService> _logger;
private readonly Lazy<IPatientService> _patientService;
private readonly IPublisherService _publisherService;
private readonly RabbitMqSettings _rabbitMqSettings;
private readonly RecordingSettings _recordingSettings;
private readonly bool _startRecordingWithoutPatientNumber;
private readonly ISubscribersService _subscribersService;
private readonly string? _url;
private AccessGrant? _accessGrant;
public RecordingService(IOptions<RabbitMqSettings> rabbitMqSettings,
IOptions<RecordingSettings> recordingSettings,
ILogger<RecordingService> logger,
IHttpClientFactory httpClientFactory,
IPublisherService publisherService,
IAuthService authService,
IOptions<ApiSettings> apiSettings, IClientMessageService clientMessageService,
ISubscribersService subscribersService,
Lazy<IPatientService> patientService)
{
_logger = logger;
_httpClient = httpClientFactory.CreateClient();
_accessGrant ??= new AccessGrant();
_rabbitMqSettings = rabbitMqSettings.Value;
_recordingSettings = recordingSettings.Value ??
throw new Exception("RecordingSettings must be defined on appSettings");
_httpClient.Timeout = new TimeSpan(0, 0, _recordingSettings.HttpClientTimeout);
_url = _recordingSettings.RecordingApiUrl;
_publisherService = publisherService;
_authService = authService;
_clientMessageService = clientMessageService;
_subscribersService = subscribersService;
_patientService = patientService;
RecordingQueueName = _rabbitMqSettings.RecordingQueue;
ErrorRecordingQueueName = $"{RecordingQueueName}_Error";
_startRecordingWithoutPatientNumber = apiSettings.Value.StartRecordingWithoutPatientNumber;
}
private string RecordingQueueName { get; }
private string ErrorRecordingQueueName { get; }
public async Task<bool> SendCancelRecordingToRecordingApi(Patient patient, PointOfCare poc)
{
var token = await _authService.GetToken();
if (string.IsNullOrEmpty(_url) || string.IsNullOrEmpty(token))
throw new Exception(
"send recording data to recording api require RecordingOrApiUrl defined and valid token");
var body = JsonConvert.SerializeObject(GenerateRecordingData(patient, poc, DateTime.MinValue,
null, null, null, null, AlarmEnum.Severity.None, ""));
var request =
new HttpRequestMessage(HttpMethod.Post,
$"{_url}/videos/delete-video-in-progress") //las fechas no se mandan
{
Content = new StringContent(body, Encoding.UTF8)
};
request.Headers.TryAddWithoutValidation("Authorization", $"Bearer {token}");
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await _httpClient.SendAsync(request);
_logger.LogDebug(
"send cancel to recording api status code:{responseStatusCode} url: {url} body: {body}",
response.StatusCode, _url, body);
return response.IsSuccessStatusCode;
}
public async Task SendRecordingData(Patient patient, PointOfCare poc, ManualRecording manualRecording,
bool start)
{
await SendRecordingDataToQueue(patient, poc, manualRecording.Recording?.StartRecordingTime,
manualRecording.Recording?.StopRecordingTime, null, null, AlarmEnum.Severity.None, null, start);
}
public async Task<bool> SendAutoRecordingData(Patient patient, PointOfCare poc, RecordingData automaticRecording)
{
try
{
if (!Enum.TryParse<AlarmEnum.Name>(automaticRecording.AlarmName, out var alarmName))
return false;
await SendRecordingDataToQueue(patient, poc, automaticRecording.StartRecordingTime,
automaticRecording.StopRecordingTime, automaticRecording.EventTime,
alarmName, automaticRecording.Severity, automaticRecording.AlarmDescription,
type: AlarmEnum.Type.Auto);
return true;
}
catch (Exception ex)
{
Log.Error("Exception sending automatic recording data. Exception: {ex}", ex);
return false;
}
}
public async Task SendRecordingDataToQueue(Patient patient, PointOfCare poc, DateTime? date, DateTime? endDate,
DateTime? eventDate, AlarmEnum.Name? alarmName, AlarmEnum.Severity severity, string? alarmDescription,
bool start = true, AlarmEnum.Type type = AlarmEnum.Type.Manual)
{
if (!_startRecordingWithoutPatientNumber && string.IsNullOrEmpty(patient.PatientNumber))
{
_logger.LogError(
"Error sending recording data to queue. Patient number is null or empty. PatientId: {patientId}",
patient.PatientId);
return;
}
if (poc.Status != StatusEnum.PointOfCare.InUse)
throw new Exception($"Box does not contain patients:->{JsonConvert.SerializeObject(poc)}");
if (string.IsNullOrEmpty(alarmDescription)) alarmDescription = "UNKNOWN";
var retryCount = 1;
while (retryCount <= _rabbitMqSettings.MaxRetries)
{
try
{
ApiRequest recordingRequest = new()
{
Recording = GenerateRecordingData(patient, poc, date, endDate, eventDate, null, alarmName,
severity, alarmDescription, type)
};
_logger.LogDebug("Send recording data patient {patient}", patient);
await _publisherService.SendMessage(recordingRequest, RecordingQueueName);
if (recordingRequest.Recording != null)
_ = SendRecordingBroadcast([recordingRequest.Recording]);
return;
}
catch (HttpRequestException ce)
{
retryCount++;
_logger.LogError("Connection error sending recording data to queue. {ce} Retrying...", ce.Message);
}
catch (Exception e)
{
_logger.LogError("Error sending recording data to queue.{eError} {eMessage} ", e, e.Message);
await _publisherService.SendMessage(e.Message, ErrorRecordingQueueName);
throw;
}
if (retryCount == _rabbitMqSettings.MaxRetries)
await _publisherService.SendMessage("Error: Connection retries exceeded.", ErrorRecordingQueueName);
}
}
public async Task<List<RecordingData>?> GetRecordings(int roomId)
{
var retryCount = 1;
while (retryCount <= _rabbitMqSettings.MaxRetries)
try
{
var token = await _authService.GetToken();
if (string.IsNullOrEmpty(_recordingSettings.RecordingApiUrl) || string.IsNullOrEmpty(token))
{
Log.Warning(
"Not recording url defined at recordingSettings or empty token, ignoring recordings");
return null;
}
var request = new HttpRequestMessage(HttpMethod.Get,
$"{_recordingSettings.RecordingApiUrl}/videos/allInProgress/{roomId}");
request.Headers.TryAddWithoutValidation("Authorization", $"Bearer {token}");
var response = await _httpClient.SendAsync(request);
_logger.LogDebug(
"GetByCodeSysAndCode recordings to recording api status code:{responseStatusCode} url: {url} body",
response.StatusCode, _url);
var responseContent = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
if (response.StatusCode == HttpStatusCode.Unauthorized)
{
_accessGrant = null;
_logger.LogError(
"error connecting to RecordingOrApi url:{requestRequestUri} errorCode: {responseStatusCode} error {responseContent} token: {}",
request.RequestUri, response.StatusCode, response.Content,
_accessGrant?.AccessToken ?? "not exist");
}
else if (response.StatusCode == HttpStatusCode.NotFound)
{
return [];
}
else
{
_logger.LogError(
"error connecting to RecordingOrApi url:{requestRequestUri} errorCode: {responseStatusCode} error {responseContent}",
request.RequestUri, response.StatusCode, response.Content);
return [];
}
if (retryCount == _rabbitMqSettings.MaxRetries)
await _publisherService.SendMessage("Error: Connection retries exceeded.",
ErrorRecordingQueueName);
return [];
}
var records = JsonConvert.DeserializeObject<List<RecordingData>>(responseContent);
return records;
}
catch (HttpRequestException ce)
{
retryCount++;
_logger.LogError(
"Connection error sending recording data to queue. {ce} Retrying... {retryCount} of {maxRetrys}",
ce.Message, retryCount, _rabbitMqSettings.MaxRetries);
//return new List<RecordingData>();
}
catch (Exception e)
{
_logger.LogError("Error getting recordings room: {roomId} error: {eMessage} ", roomId, e.Message);
await _publisherService.SendMessage(e.Message, ErrorRecordingQueueName);
return [];
}
if (retryCount == _rabbitMqSettings.MaxRetries)
{
await _publisherService.SendMessage("Error: Connection retries exceeded.", ErrorRecordingQueueName);
return null;
}
return [];
}
public async Task SaveRequestAsync(ApiRequest apiRequest)
{
var retryCount = 1;
while (retryCount <= _rabbitMqSettings.MaxRetries)
{
try
{
var token = await _authService.GetToken();
_logger.LogDebug("sending recording to Recording or api");
var recordingRequest = apiRequest.Recording;
var request = new HttpRequestMessage(HttpMethod.Post,
$"{_recordingSettings.RecordingApiUrl}/videos/save-recording-data")
{
Content = new StringContent(JsonConvert.SerializeObject(recordingRequest), Encoding.UTF8)
};
request.Headers.TryAddWithoutValidation("Authorization", $"Bearer {token}");
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await _httpClient.SendAsync(request);
_logger.LogDebug("Save Request to recording api status code:{responseStatusCode}",
response.StatusCode);
var responseContent = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
if (response.StatusCode == HttpStatusCode.Unauthorized) _accessGrant = null;
_logger.LogError(
"error connecting to RecordingOrApi url:{requestRequestUri} errorCode: {responseStatusCode} error {responseContent}",
request.RequestUri, response.StatusCode, response.Content);
retryCount++;
}
else
{
// Response y en Data esta el videoDTO
var resp = JsonConvert.DeserializeObject<Response<VideoDto>>(responseContent);
var video = resp?.Data;
if (video == null)
return;
RecordingData recording = new()
{
StartRecordingTime = video.StartDate,
StopRecordingTime = video.EndDate,
EventTime = video.Date,
RoomId = video.RoomId,
AlarmType = video.Alarm,
Store = video.Store,
Status = video.VideoStatus.ToString(),
Patient = new Domain.Models.Recording.Patient
{
FirstName = apiRequest.Patient?.FirstName ??
apiRequest.Recording?.Patient?.FirstName ?? string.Empty,
Id = apiRequest.PatientNumber ?? apiRequest.Recording?.Patient?.Id ?? string.Empty,
LastName = apiRequest.Patient?.LastName ??
apiRequest.Recording?.Patient?.LastName ?? string.Empty
}
};
_ = SendRecordingBroadcast([recording]);
return;
}
}
catch (HttpRequestException ce)
{
retryCount++;
_logger.LogError("Connection error sending recording data to queue. {ce} Retrying...", ce.Message);
await _publisherService.SendMessage(apiRequest, ErrorRecordingQueueName);
}
catch (Exception e)
{
_logger.LogError("Error sending recording request to Recording or {eMessage}", e.Message);
await _publisherService.SendMessage(apiRequest, ErrorRecordingQueueName);
await Task.FromException(e);
}
if (retryCount > _rabbitMqSettings.MaxRetries)
await _publisherService.SendMessage(
$"Error: Connection retries exceeded. for api request: {apiRequest}", ErrorRecordingQueueName);
}
}
public Task SaveRequest(ApiRequest apiRequest)
{
//return Task.CompletedTask;
throw new NotImplementedException();
}
private RecordingData? GenerateRecordingData(Patient patient, PointOfCare poc, DateTime? startDate,
DateTime? endDate, DateTime? eventTime, int? minToExpired, AlarmEnum.Name? alarmName,
AlarmEnum.Severity severity,
string alarmDescription, AlarmEnum.Type alarm = AlarmEnum.Type.Manual)
{
if (patient.PatientNumber == null)
{
_logger.LogError("Error Generating Recording Data . Patient number is null: {patient}", patient);
return null;
}
var roomId = poc.Configuration?.Id;
if (roomId == null)
{
_logger.LogError("Error Generating Recording Data on roomId");
return null;
}
var patientRecording = new Domain.Models.Recording.Patient
{
Id = patient.PatientNumber,
FirstName = patient.Person?.FirstName ?? "X",
LastName = $"{patient.Person?.SecondName} {patient.Person?.LastName}".TrimEnd()
};
if (!int.TryParse(roomId.ToString(), out var roomIdParsed))
{
_logger.LogError("Error Generating Recording Data on roomId parse: {roomId}", roomId);
return null;
}
var recordingData = new RecordingData
{
Patient = patientRecording,
RoomId = roomIdParsed,
StartRecordingTime = startDate,
StopRecordingTime =
endDate ?? (minToExpired != null ? DateTime.UtcNow.AddMinutes(minToExpired.Value) : null),
EventTime = eventTime,
AlarmType = alarm,
Severity = severity,
AlarmName = alarmName.HasValue ? EnumUtils.GetDescription(alarmName) : null,
AlarmDescription = alarmDescription,
Retry = 0
};
recordingData.GenerateStore();
return recordingData;
}
private async Task SendRecordingBroadcast(List<RecordingData> recording)
{
if (!recording.IsNullOrEmpty())
{
var patientNumber = recording[0].Patient?.Id;
if (string.IsNullOrEmpty(patientNumber))
{
_logger.LogError("Box not found by RoomId {roomId}", recording[0].RoomId);
return;
}
var patient = await _patientService.Value.FindByPatientNumber(patientNumber);
if (patient == null)
return;
var type = OperationType.RecordingStatus;
var subscribers = _subscribersService.GetSubscribers().Where(s =>
s.SubscriptionType == SubscriptionEnum.WsType.Box && s.Box == patient.Location.Bed &&
s.Section == patient.Location.UnitName).ToList();
foreach (var subscriber in subscribers)
await _clientMessageService.SendAsync(subscriber.Id, type, recording);
}
}
protected static string Base64Encode(string plainText)
{
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
return Convert.ToBase64String(plainTextBytes);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,30 @@
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.MongoModels;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
namespace adas_core.Application.Services;
public class ServiceConfigService(
IServiceConfigRepository serviceConfigRepository,
ILogger<ServiceConfigService> logger)
: IServiceConfigService
{
private readonly ILogger<ServiceConfigService> _logger = logger;
public async Task<ServiceConfig?> Get(string id)
{
var result = await serviceConfigRepository.FindById(id);
if (result == null && ObjectId.TryParse(id, out var oid))
result = await serviceConfigRepository.FindById(oid) ??
throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing);
_logger.LogDebug("ServiceConfig {id} found: {result}", id, result != null);
return result;
}
}

Some files were not shown because too many files have changed in this diff Show More