Creado apartir del commit b888de6c92056de294456f581a56e25e734d4ab7 de develop
This commit is contained in:
@@ -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 configuraci�n 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
Reference in New Issue
Block a user