迁移HIR 初步提交

Test_HIR_Net8
hang 2024-11-12 16:50:28 +08:00
parent c158d53a39
commit 75bf9b394b
30 changed files with 20148 additions and 421 deletions

View File

@ -24,7 +24,39 @@ using IRaCIS.Core.Infrastructure;
namespace IRaCIS.Core.SCP.Service namespace IRaCIS.Core.SCP.Service
{ {
/// <summary>
/// 后台托管服务的方式运行
/// </summary>
//public class CStoreSCPHostedService : IHostedService
//{
// private readonly ILogger<CStoreSCPHostedService> _logger;
// private readonly IDicomServerFactory _dicomServerFactory;
// private IDicomServer? _server;
// public CStoreSCPHostedService(ILogger<CStoreSCPHostedService> logger, IDicomServerFactory dicomServerFactory)
// {
// _logger = logger ?? throw new ArgumentNullException(nameof(logger));
// _dicomServerFactory = dicomServerFactory ?? throw new ArgumentNullException(nameof(dicomServerFactory));
// }
// public async Task StartAsync(CancellationToken cancellationToken)
// {
// _logger.LogInformation("Starting DICOM server");
// _server = _dicomServerFactory.Create<CStoreSCPService>(104);
// _logger.LogInformation("DICOM server is running");
// }
// public Task StopAsync(CancellationToken cancellationToken)
// {
// if (_server != null)
// {
// _server.Stop();
// _server.Dispose();
// _server = null;
// }
// return Task.CompletedTask;
// }
//}
public class DicomSCPServiceOption public class DicomSCPServiceOption
{ {
public List<string> CalledAEList { get; set; } public List<string> CalledAEList { get; set; }
@ -43,11 +75,6 @@ namespace IRaCIS.Core.SCP.Service
private SCPImageUpload _upload { get; set; } private SCPImageUpload _upload { get; set; }
private Guid _trialId { get; set; }
private Guid _trialSiteId { get; set; }
private static readonly DicomTransferSyntax[] _acceptedTransferSyntaxes = new DicomTransferSyntax[] private static readonly DicomTransferSyntax[] _acceptedTransferSyntaxes = new DicomTransferSyntax[]
{ {
@ -58,21 +85,21 @@ namespace IRaCIS.Core.SCP.Service
private static readonly DicomTransferSyntax[] _acceptedImageTransferSyntaxes = new DicomTransferSyntax[] private static readonly DicomTransferSyntax[] _acceptedImageTransferSyntaxes = new DicomTransferSyntax[]
{ {
// Lossless // Lossless
DicomTransferSyntax.JPEGLSLossless, //1.2.840.10008.1.2.4.80 DicomTransferSyntax.JPEGLSLossless,
DicomTransferSyntax.JPEG2000Lossless, //1.2.840.10008.1.2.4.90 DicomTransferSyntax.JPEG2000Lossless,
DicomTransferSyntax.JPEGProcess14SV1, //1.2.840.10008.1.2.4.70 DicomTransferSyntax.JPEGProcess14SV1,
DicomTransferSyntax.JPEGProcess14, //1.2.840.10008.1.2.4.57 JPEG Lossless, Non-Hierarchical (Process 14) DicomTransferSyntax.JPEGProcess14,
DicomTransferSyntax.RLELossless, //1.2.840.10008.1.2.5 DicomTransferSyntax.RLELossless,
// Lossy // Lossy
DicomTransferSyntax.JPEGLSNearLossless,//1.2.840.10008.1.2.4.81" DicomTransferSyntax.JPEGLSNearLossless,
DicomTransferSyntax.JPEG2000Lossy, //1.2.840.10008.1.2.4.91 DicomTransferSyntax.JPEG2000Lossy,
DicomTransferSyntax.JPEGProcess1, //1.2.840.10008.1.2.4.50 DicomTransferSyntax.JPEGProcess1,
DicomTransferSyntax.JPEGProcess2_4, //1.2.840.10008.1.2.4.51 DicomTransferSyntax.JPEGProcess2_4,
// Uncompressed // Uncompressed
DicomTransferSyntax.ExplicitVRLittleEndian, //1.2.840.10008.1.2.1 DicomTransferSyntax.ExplicitVRLittleEndian,
DicomTransferSyntax.ExplicitVRBigEndian, //1.2.840.10008.1.2.2 DicomTransferSyntax.ExplicitVRBigEndian,
DicomTransferSyntax.ImplicitVRLittleEndian //1.2.840.10008.1.2 DicomTransferSyntax.ImplicitVRLittleEndian
}; };
@ -94,48 +121,18 @@ namespace IRaCIS.Core.SCP.Service
_serviceProvider = (IServiceProvider)this.UserState; _serviceProvider = (IServiceProvider)this.UserState;
var option = _serviceProvider.GetService<IOptionsMonitor<DicomSCPServiceOption>>().CurrentValue;
var _trialDicomAERepository = _serviceProvider.GetService<IRepository<TrialDicomAE>>();
var calledAEList = option.CalledAEList;
var trialDicomAEList = _trialDicomAERepository.Select(t => new { t.CalledAE, t.TrialId }).ToList(); if (!calledAEList.Contains(association.CalledAE))
var trialCalledAEList = trialDicomAEList.Select(t => t.CalledAE).ToList();
Log.Logger.Information("当前系统配置:", string.Join('|', trialDicomAEList)); //if (association.CalledAE != "STORESCP")
var findCalledAE = trialDicomAEList.Where(t => t.CalledAE == association.CalledAE).FirstOrDefault();
var isCanReceiveIamge = false;
if (findCalledAE != null)
{
_trialId = findCalledAE.TrialId;
var _trialSiteDicomAERepository = _serviceProvider.GetService<IRepository<TrialSiteDicomAE>>();
var findTrialSiteAE = _trialSiteDicomAERepository.Where(t => t.CallingAE == association.CallingAE && t.TrialId==_trialId).FirstOrDefault();
if (findTrialSiteAE != null)
{
_trialSiteId = findTrialSiteAE.TrialSiteId;
isCanReceiveIamge = true;
}
}
if (association.CallingAE == "test-callingAE")
{
isCanReceiveIamge = true;
}
if (!trialCalledAEList.Contains(association.CalledAE) || isCanReceiveIamge == false)
{ {
Log.Logger.Warning($"拒绝CallingAE:{association.CallingAE} CalledAE:{association.CalledAE}的连接"); Log.Logger.Warning($"拒绝CalledAE:{association.CalledAE}的连接");
return SendAssociationRejectAsync( return SendAssociationRejectAsync(
DicomRejectResult.Permanent, DicomRejectResult.Permanent,
@ -155,8 +152,6 @@ namespace IRaCIS.Core.SCP.Service
} }
} }
return SendAssociationAcceptAsync(association); return SendAssociationAcceptAsync(association);
} }
@ -172,15 +167,7 @@ namespace IRaCIS.Core.SCP.Service
_upload.EndTime = DateTime.Now; _upload.EndTime = DateTime.Now;
_upload.StudyCount = _SCPStudyIdList.Count; _upload.StudyCount = _SCPStudyIdList.Count;
await _SCPImageUploadRepository.AddAsync(_upload,true);
await _SCPImageUploadRepository.AddAsync(_upload, true);
var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
//将检查设置为传输结束
await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
await _studyRepository.SaveChangesAndClearAllTrackingAsync();
await SendAssociationReleaseResponseAsync(); await SendAssociationReleaseResponseAsync();
} }
@ -188,9 +175,11 @@ namespace IRaCIS.Core.SCP.Service
private async Task DataMaintenanceAsaync() private async Task DataMaintenanceAsaync()
{ {
Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE}传输结束开始维护数据处理检查Modality"); Log.Logger.Warning($"CallingAE:{Association.CallingAE} CalledAE:{Association.CalledAE}传输结束开始维护数据处理检查Modality 以及自动创建访视,绑定检查");
var patientStudyService = _serviceProvider.GetService<IPatientStudyService>();
await patientStudyService.AutoBindingPatientStudyVisitAsync(_SCPStudyIdList);
//处理检查Modality //处理检查Modality
var _dictionaryRepository = _serviceProvider.GetService<IRepository<Dictionary>>(); var _dictionaryRepository = _serviceProvider.GetService<IRepository<Dictionary>>();
@ -242,11 +231,11 @@ namespace IRaCIS.Core.SCP.Service
//奇怪的bug 上传的时候用王捷修改的影像会关闭重新连接导致检查id 丢失,然后状态不一致 //奇怪的bug 上传的时候用王捷修改的影像会关闭重新连接导致检查id 丢失,然后状态不一致
if (exception == null) if (exception == null)
{ {
//var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>(); var _studyRepository = _serviceProvider.GetService<IRepository<SCPStudy>>();
////将检查设置为传输结束 //将检查设置为传输结束
//await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true }); await _studyRepository.BatchUpdateNoTrackingAsync(t => _SCPStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
//await _studyRepository.SaveChangesAndClearAllTrackingAsync(); await _studyRepository.SaveChangesAndClearAllTrackingAsync();
} }
Log.Logger.Warning($"连接关闭 {exception?.Message} {exception?.InnerException?.Message}"); Log.Logger.Warning($"连接关闭 {exception?.Message} {exception?.InnerException?.Message}");
@ -262,9 +251,8 @@ namespace IRaCIS.Core.SCP.Service
string seriesInstanceUid = request.Dataset.GetString(DicomTag.SeriesInstanceUID); string seriesInstanceUid = request.Dataset.GetString(DicomTag.SeriesInstanceUID);
string sopInstanceUid = request.Dataset.GetString(DicomTag.SOPInstanceUID); string sopInstanceUid = request.Dataset.GetString(DicomTag.SOPInstanceUID);
//Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid, trialId.ToString()); Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid);
Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, _trialId.ToString()); Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid);
Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid, _trialId.ToString());
var ossService = _serviceProvider.GetService<IOSSService>(); var ossService = _serviceProvider.GetService<IOSSService>();
@ -274,7 +262,7 @@ namespace IRaCIS.Core.SCP.Service
var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>(); var _distributedLockProvider = _serviceProvider.GetService<IDistributedLockProvider>();
var storeRelativePath = string.Empty; var storeRelativePath = string.Empty;
var ossFolderPath = $"{_trialId}/Image/PACS/{_trialSiteId}{studyInstanceUid}"; var ossFolderPath = $"Dicom/{studyInstanceUid}";
long fileSize = 0; long fileSize = 0;
@ -307,7 +295,7 @@ namespace IRaCIS.Core.SCP.Service
{ {
try try
{ {
var scpStudyId = await dicomArchiveService.ArchiveDicomFileAsync(request.Dataset, _trialId, _trialSiteId, storeRelativePath, Association.CallingAE, Association.CalledAE,fileSize); var scpStudyId = await dicomArchiveService.ArchiveDicomFileAsync(request.Dataset, storeRelativePath, Association.CallingAE, Association.CalledAE,fileSize);
if (!_SCPStudyIdList.Contains(scpStudyId)) if (!_SCPStudyIdList.Contains(scpStudyId))
{ {
@ -336,10 +324,14 @@ namespace IRaCIS.Core.SCP.Service
series.ImageResizePath = seriesPath; series.ImageResizePath = seriesPath;
//await _seriesRepository.BatchUpdateNoTrackingAsync(t => t.Id == seriesId, u => new SCPSeries() { ImageResizePath = seriesPath });
} }
} }
await _seriesRepository.SaveChangesAsync(); await _seriesRepository.SaveChangesAsync();
} }
catch (Exception ex) catch (Exception ex)
@ -355,7 +347,7 @@ namespace IRaCIS.Core.SCP.Service
//监控信息设置 //监控信息设置
_upload.FileCount++; _upload.FileCount++;
_upload.FileSize = _upload.FileSize + fileSize; _upload.FileSize= _upload.FileSize+ fileSize;
return new DicomCStoreResponse(request, DicomStatus.Success); return new DicomCStoreResponse(request, DicomStatus.Success);
} }

View File

@ -11,7 +11,6 @@ using FellowOakDicom.Network;
using IRaCIS.Core.SCP.Service; using IRaCIS.Core.SCP.Service;
using IRaCIS.Core.Infra.EFCore; using IRaCIS.Core.Infra.EFCore;
using MassTransit; using MassTransit;
using System.Runtime.Intrinsics.X86;
using Serilog.Sinks.File; using Serilog.Sinks.File;
namespace IRaCIS.Core.SCP.Service namespace IRaCIS.Core.SCP.Service
@ -52,18 +51,18 @@ namespace IRaCIS.Core.SCP.Service
/// <param name="dataset"></param> /// <param name="dataset"></param>
/// <returns></returns> /// <returns></returns>
/// <exception cref="NotImplementedException"></exception> /// <exception cref="NotImplementedException"></exception>
public async Task<Guid> ArchiveDicomFileAsync(DicomDataset dataset, Guid trialId, Guid trialSiteId, string fileRelativePath, string callingAE, string calledAE,long fileSize) public async Task<Guid> ArchiveDicomFileAsync(DicomDataset dataset, string fileRelativePath, string callingAE, string calledAE, long fileSize)
{ {
string studyInstanceUid = dataset.GetString(DicomTag.StudyInstanceUID); string studyInstanceUid = dataset.GetString(DicomTag.StudyInstanceUID);
string seriesInstanceUid = dataset.GetString(DicomTag.SeriesInstanceUID); string seriesInstanceUid = dataset.GetString(DicomTag.SeriesInstanceUID);
string sopInstanceUid = dataset.GetString(DicomTag.SOPInstanceUID); string sopInstanceUid = dataset.GetString(DicomTag.SOPInstanceUID);
string patientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID,string.Empty); string patientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty);
//Guid patientId= IdentifierHelper.CreateGuid(patientIdStr); //Guid patientId= IdentifierHelper.CreateGuid(patientIdStr);
Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid,trialId.ToString()); Guid studyId = IdentifierHelper.CreateGuid(studyInstanceUid);
Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, trialId.ToString()); Guid seriesId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid);
Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid, trialId.ToString()); Guid instanceId = IdentifierHelper.CreateGuid(studyInstanceUid, seriesInstanceUid, sopInstanceUid);
var isStudyNeedAdd = false; var isStudyNeedAdd = false;
var isSeriesNeedAdd = false; var isSeriesNeedAdd = false;
@ -74,11 +73,12 @@ namespace IRaCIS.Core.SCP.Service
//using (@lock.Acquire()) //using (@lock.Acquire())
{ {
var findPatient = await _patientRepository.FirstOrDefaultAsync(t => t.PatientIdStr == patientIdStr /*&& t.TrialSiteId==trialSiteId*/ ); var findPatient = await _patientRepository.FirstOrDefaultAsync(t => t.PatientIdStr == patientIdStr);
var findStudy = await _studyRepository.FirstOrDefaultAsync(t=>t.Id== studyId); var findStudy = await _studyRepository.FirstOrDefaultAsync(t => t.Id == studyId);
var findSerice = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId); var findSerice = await _seriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId);
var findInstance = await _instanceRepository.FirstOrDefaultAsync(t => t.Id == instanceId); var findInstance = await _instanceRepository.FirstOrDefaultAsync(t => t.Id == instanceId);
DateTime? studyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.StudyDate).Add(dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.StudyTime).TimeOfDay); DateTime? studyTime = dataset.GetSingleValueOrDefault(DicomTag.StudyDate, string.Empty) == string.Empty ? null : dataset.GetSingleValue<DateTime>(DicomTag.StudyDate).Add(dataset.GetSingleValueOrDefault(DicomTag.StudyTime, string.Empty) == string.Empty ? TimeSpan.Zero : dataset.GetSingleValue<DateTime>(DicomTag.StudyTime).TimeOfDay);
//先传输了修改了患者编号的又传输了没有修改患者编号的导致后传输的没有修改患者编号的下面的检查为0 //先传输了修改了患者编号的又传输了没有修改患者编号的导致后传输的没有修改患者编号的下面的检查为0
@ -90,8 +90,6 @@ namespace IRaCIS.Core.SCP.Service
findPatient = new SCPPatient() findPatient = new SCPPatient()
{ {
Id = NewId.NextSequentialGuid(), Id = NewId.NextSequentialGuid(),
//TrialId=trialId,
//TrialSiteId=trialSiteId,
PatientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty), PatientIdStr = dataset.GetSingleValueOrDefault(DicomTag.PatientID, string.Empty),
PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty), PatientName = dataset.GetSingleValueOrDefault(DicomTag.PatientName, string.Empty),
PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty), PatientAge = dataset.GetSingleValueOrDefault(DicomTag.PatientAge, string.Empty),
@ -117,20 +115,6 @@ namespace IRaCIS.Core.SCP.Service
{ {
findPatient.PatientBirthDate = birthDateStr; findPatient.PatientBirthDate = birthDateStr;
DateTime birthDate;
if (findPatient.PatientAge == string.Empty && studyTime.HasValue && DateTime.TryParse(findPatient.PatientBirthDate,out birthDate))
{
var patientAge = studyTime.Value.Year - birthDate.Year;
// 如果生日还未到,年龄减去一岁
if (studyTime.Value < birthDate.AddYears(patientAge))
{
patientAge--;
}
findPatient.PatientAge = patientAge.ToString();
}
} }
else else
{ {
@ -163,8 +147,6 @@ namespace IRaCIS.Core.SCP.Service
PatientId = findPatient.Id, PatientId = findPatient.Id,
Id = studyId, Id = studyId,
//TrialId = trialId,
//TrialSiteId = trialSiteId,
StudyInstanceUid = studyInstanceUid, StudyInstanceUid = studyInstanceUid,
StudyTime = studyTime, StudyTime = studyTime,
Modalities = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty), Modalities = dataset.GetSingleValueOrDefault(DicomTag.Modality, string.Empty),
@ -273,8 +255,7 @@ namespace IRaCIS.Core.SCP.Service
Path = fileRelativePath, Path = fileRelativePath,
FileSize= fileSize, FileSize = fileSize,
}; };
++findStudy.InstanceCount; ++findStudy.InstanceCount;
@ -304,7 +285,7 @@ namespace IRaCIS.Core.SCP.Service
} }
else else
{ {
await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == instanceId, u => new SCPInstance() { Path = fileRelativePath,FileSize=fileSize }); await _instanceRepository.BatchUpdateNoTrackingAsync(t => t.Id == instanceId, u => new SCPInstance() { Path = fileRelativePath });
} }
await _studyRepository.SaveChangesAsync(); await _studyRepository.SaveChangesAsync();

View File

@ -5,7 +5,7 @@ namespace IRaCIS.Core.SCP.Service
{ {
public interface IDicomArchiveService public interface IDicomArchiveService
{ {
Task<Guid> ArchiveDicomFileAsync(DicomDataset dicomDataset,Guid trialId,Guid trialSiteId, string fileRelativePath,string callingAE,string calledAE,long fileSize); Task<Guid> ArchiveDicomFileAsync(DicomDataset dicomDataset,string fileRelativePath,string callingAE,string calledAE,long fileSize);
} }
} }

View File

@ -0,0 +1,249 @@
using IRaCIS.Core.Domain.Share;
using System.Text;
using Microsoft.AspNetCore.Hosting;
using IRaCIS.Core.Infrastructure;
using Medallion.Threading;
using FellowOakDicom;
using FellowOakDicom.Imaging.Codec;
using System.Data;
using IRaCIS.Core.Domain.Models;
using FellowOakDicom.Network;
using IRaCIS.Core.SCP.Service;
using IRaCIS.Core.Infra.EFCore;
using MassTransit;
using Microsoft.AspNetCore.Mvc;
using IRaCIS.Core.Infrastructure.Extention;
using Microsoft.EntityFrameworkCore;
namespace IRaCIS.Core.SCP.Service
{
public interface IPatientStudyService
{
Task<IResponseOutput> AutoBindingPatientStudyVisitAsync(List<Guid> scpStudyIdList);
}
[ApiExplorerSettings(GroupName = "Trial")]
public class PatientStudyService : BaseService, IPatientStudyService
{
private readonly IRepository<SCPStudySubjectVisit> _studySubjectVisitRepository;
private readonly IRepository<SubjectPatient> _subjectPatientRepository;
private readonly IRepository<Trial> _trialRepository;
private readonly IRepository<SCPPatient> _patientRepository;
private readonly IRepository<SCPStudy> _studyRepository;
private readonly IRepository<Subject> _subjectRepository;
private readonly IRepository<SubjectVisit> _subjectVisitRepository;
private readonly IDistributedLockProvider _distributedLockProvider;
public PatientStudyService(IRepository<SCPStudySubjectVisit> studySubjectVisitRepository, IRepository<SCPStudy> studyRepository, IRepository<SubjectPatient> subjectPatientRepository, IRepository<Trial> trialRepository, IRepository<SCPPatient> patientRepository, IRepository<Subject> subjectRepository, IRepository<SubjectVisit> subjectVisitRepository, IDistributedLockProvider distributedLockProvider)
{
_studySubjectVisitRepository = studySubjectVisitRepository;
_studyRepository = studyRepository;
_subjectPatientRepository = subjectPatientRepository;
_trialRepository = trialRepository;
_patientRepository = patientRepository;
_subjectRepository = subjectRepository;
_subjectVisitRepository = subjectVisitRepository;
_distributedLockProvider = distributedLockProvider;
}
public class AuToBindingStudyInfo
{
public Guid SCPStudyId { get; set; }
public DateTime? StudyTime { get; set; }
}
private async Task DealAutoBindingStudyAsync(Guid trialId, Guid subjectId, List<AuToBindingStudyInfo> studyList, decimal? startBindVisitNum = null)
{
//自动创建访视 和检查绑定
//1. 查询已存在的访视
var subjectAllVisitList = await _subjectVisitRepository.Where(t => t.SubjectId == subjectId)
.Select(t => new
{
t.SubjectId,
SubjectVisitId = t.Id,
t.SubmitState,
VisitNum = t.VisitNum,
MaxStudyTime = t.SCPStudySubjectVisitList.Max(t => t.SCPStudy.StudyTime),
MinStudyTime = t.SCPStudySubjectVisitList.Min(t => t.SCPStudy.StudyTime)
})
.ToListAsync();
//2、获取项目配置
var trialconfig = _trialRepository.Where(t => t.Id == trialId).Select(t => new { t.BlindBaseLineName, t.BlindFollowUpPrefix }).FirstOrDefault();
//3、 未提交的最小的访视号 从这个访视开始绑定
var subjectMaxVisitNum = startBindVisitNum == null ? subjectAllVisitList.Where(t => t.SubmitState != SubmitStateEnum.Submitted).MinOrDefault(t => t.VisitNum) : startBindVisitNum.Value;
List<(int VisitCount, Guid SCPStudyId)> visits = new List<(int, Guid)>();
int visitCount = 0;
DateTime? lastVisitTime = null;
foreach (var study in studyList)
{
if (lastVisitTime == null || (study.StudyTime - lastVisitTime.Value).Value.TotalDays >= 15)
{
// 当前时间点与上一个访视时间点间隔大于等于 15 天,需要建立一个新的访视
visitCount++;
visits.Add((visitCount, study.SCPStudyId));
}
else
{
visits.Add((visitCount, study.SCPStudyId));
}
lastVisitTime = study.StudyTime;
}
//4、生成访视 并且绑定
for (int i = 0; i < visitCount; i++)
{
var bindSubjectVisitId = Guid.Empty;
var bindVisitNum = i + subjectMaxVisitNum;
var existSubjectVisit = subjectAllVisitList.FirstOrDefault(t => t.SubjectId == subjectId && t.VisitNum == bindVisitNum);
if (existSubjectVisit == null)
{
bindSubjectVisitId = NewId.NextGuid();
//基线
if (bindVisitNum == 0)
{
await _subjectVisitRepository.AddAsync(new SubjectVisit() { TrialId = trialId, SubjectId = subjectId, VisitName = trialconfig.BlindBaseLineName, VisitNum = bindVisitNum, Id = bindSubjectVisitId, SubmitState = SubmitStateEnum.ToSubmit, IsBaseLine = true });
}
else
{
await _subjectVisitRepository.AddAsync(new SubjectVisit() { TrialId = trialId, SubjectId = subjectId, VisitName = trialconfig.BlindFollowUpPrefix + $" {(int)bindVisitNum}", VisitNum = bindVisitNum, Id = bindSubjectVisitId, SubmitState = SubmitStateEnum.ToSubmit });
}
}
else
{
bindSubjectVisitId = existSubjectVisit.SubjectVisitId;
}
var currentVisitStudyList = visits.Where(t => t.VisitCount == (i + 1)).ToList();
foreach (var item in currentVisitStudyList)
{
//访视状态为未提交才绑定
if (!subjectAllVisitList.Any(t => t.SubjectId == subjectId && t.SubjectVisitId == bindSubjectVisitId && t.SubmitState == SubmitStateEnum.Submitted))
{
var find = await _subjectVisitRepository.FindAsync(bindSubjectVisitId);
find.SubmitState = SubmitStateEnum.ToSubmit;
await _studySubjectVisitRepository.AddAsync(new SCPStudySubjectVisit() { TrialId = trialId, SubjectVisitId = bindSubjectVisitId, SCPStudyId = item.SCPStudyId, SubjectId = subjectId });
}
}
}
await _subjectPatientRepository.SaveChangesAsync();
}
/// <summary>
/// 传输完成后,自动给检查绑定访视
/// </summary>
/// <param name="inCommand"></param>
/// <returns></returns>
[HttpPost]
public async Task<IResponseOutput> AutoBindingPatientStudyVisitAsync(List<Guid> scpStudyIdList)
{
//一个检查 可能绑定到不同的项目的不同subject 有的该检查已绑定访视,有的该检查绑定了访视
var query = from scpStudy in _studyRepository.Where(t => scpStudyIdList.Contains(t.Id))
join subjectPatient in _subjectPatientRepository.AsQueryable()
on scpStudy.PatientId equals subjectPatient.PatientId
select new
{
subjectPatient.Subject.Status,
subjectPatient.Subject.TrialId,
subjectPatient.SubjectId,
subjectPatient.PatientId,
SCPStudyId = scpStudy.Id,
scpStudy.StudyTime
};
var list = query.ToList();
if (list.Count > 0)
{
var subjectIdList = list.Select(t => t.SubjectId).ToList();
var allSubjectVisitList = await _subjectVisitRepository.Where(t => subjectIdList.Contains(t.SubjectId))
.Select(t => new { t.SubjectId, SubjectVisitId = t.Id, t.SubmitState, VisitNum = t.VisitNum, MaxStudyTime = t.SCPStudySubjectVisitList.Max(t => t.SCPStudy.StudyTime), MinStudyTime = t.SCPStudySubjectVisitList.Min(t => t.SCPStudy.StudyTime) })
.ToListAsync();
foreach (var g in list.GroupBy(t => new { t.SubjectId, t.TrialId, t.Status }))
{
var subjectId = g.Key.SubjectId;
var trialId = g.Key.TrialId;
//访视结束,那么就不处理
if (g.Key.Status == SubjectStatus.EndOfVisit)
{
continue;
}
// 预先处理1 数据库可能有已存在的subject 患者绑定,在这里要一起考虑绑定
var dbPatientIdList = _subjectPatientRepository.Where(t => t.SubjectId == subjectId).Select(t => t.PatientId).ToList();
// 预先处理2 删除未提交的所有绑定的检查记录,所有检查一起考虑绑定
await _studySubjectVisitRepository.BatchDeleteNoTrackingAsync(t => t.SubjectId == subjectId && t.SubjectVisit.SubmitState != SubmitStateEnum.Submitted);
//预处理3 找到该subjecct 已提交的访视的最大检查时间,绑定的检查时间要比这个时间要大
var maxStudyTime = allSubjectVisitList.Where(t => t.SubjectId == subjectId && t.SubmitState == SubmitStateEnum.Submitted).MaxOrDefault(t => t.MaxStudyTime);
// 预处理4 处理需要绑定的检查
//获取 该受试者绑定患者已存在的检查,考虑要生成多少个访视,去除已提交的检查
var studyList = await _studyRepository.Where(t => dbPatientIdList.Contains(t.PatientId)
&& !t.SCPStudySubjectVisitList.Any(t => t.SubjectId == subjectId && t.SubjectVisit.SubmitState == SubmitStateEnum.Submitted))
.WhereIf(maxStudyTime != null, t => t.StudyTime > maxStudyTime)
.Select(t => new AuToBindingStudyInfo { SCPStudyId = t.Id, StudyTime = t.StudyTime }).OrderBy(t => t.StudyTime).ToListAsync();
await DealAutoBindingStudyAsync(trialId, subjectId, studyList);
}
await _subjectVisitRepository.SaveChangesAsync();
}
//将检查设置为传输结束
await _studyRepository.BatchUpdateNoTrackingAsync(t => scpStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true });
return ResponseOutput.Ok();
}
}
}

View File

@ -0,0 +1,79 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ObjectStoreService": {
"ObjectStoreUse": "MinIO",
"AliyunOSS": {
"regionId": "cn-shanghai",
"endpoint": "https://oss-cn-shanghai.aliyuncs.com",
"accessKeyId": "LTAI5tKvzs7ed3UfSpNk3xwQ",
"accessKeySecret": "zTIceGEShlZDGnLrCFfIGFE7TXVRio",
"bucketName": "zy-sir-test-store",
"roleArn": "acs:ram::1899121822495495:role/oss-upload",
"viewEndpoint": "https://zy-sir-test-store.oss-cn-shanghai.aliyuncs.com",
"region": "oss-cn-shanghai"
},
"MinIO": {
"endPoint": "106.14.89.110",
"port": "9001",
"useSSL": false,
"accessKey": "fbStsVYCIPKHQneeqMwD",
"secretKey": "TzgvyA3zGXMUnpilJNUlyMYHfosl1hBMl6lxPmjy",
"bucketName": "hir-test",
"viewEndpoint": "http://106.14.89.110:9001/hir-test/"
},
"AWS": {
"endPoint": "s3.us-east-1.amazonaws.com",
"useSSL": false,
"accessKey": "AKIAZQ3DRSOHFPJJ6FEU",
"secretKey": "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",
"bucketName": "ei-irc-test-store",
"viewEndpoint": "https://ei-irc-test-store.s3.amazonaws.com/"
}
},
"ConnectionStrings": {
"RemoteNew": "Server=106.14.89.110,1435;Database=Test_HIR_New;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
"Hangfire": "Server=106.14.89.110,1435;Database=Test_HIR_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
},
"BasicSystemConfig": {
"OpenUserComplexPassword": false,
"OpenSignDocumentBeforeWork": false,
"OpenTrialRelationDelete": true,
"OpenLoginLimit": false,
"LoginMaxFailCount": 5,
"LoginFailLockMinutes": 30
},
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"FromEmail": "test-study@extimaging.com",
"FromName": "Test_Study",
"AuthorizationCode": "zhanying123",
"SiteUrl": "http://study.test.extimaging.com/login"
},
"DicomSCPServiceConfig": {
"CalledAEList": [
"STORESCP",
"Value1",
"Value2",
"Value3"
],
"ServerPort": 11112
}
}

View File

@ -24,52 +24,15 @@
}, },
"applicationUrl": "http://localhost:6100" "applicationUrl": "http://localhost:6100"
}, },
"IRaCIS.Test_IRC_PGSQL": { "IRaCIS.HIR_IRC": {
"commandName": "Project", "commandName": "Project",
"launchBrowser": true, "launchBrowser": true,
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Test_IRC_PGSQL" "ASPNETCORE_ENVIRONMENT": "Test_HIR"
},
"applicationUrl": "http://localhost:6100"
},
"IRaCIS.Event_IRC": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Event_IRC"
},
"applicationUrl": "http://localhost:6100"
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"publishAllPorts": true
},
"IRaCIS.Uat_IRC": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Uat_IRC"
},
"applicationUrl": "http://localhost:6100"
},
"IRaCIS.Prod_IRC": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Prod_IRC"
},
"applicationUrl": "http://localhost:6100"
},
"IRaCIS.US_Uat_IRC": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "US_Uat_IRC"
}, },
"applicationUrl": "http://localhost:6100" "applicationUrl": "http://localhost:6100"
} }
} }
} }

View File

@ -1,5 +1,6 @@
using IP2Region.Net.Abstractions; using IP2Region.Net.Abstractions;
using IP2Region.Net.XDB; using IP2Region.Net.XDB;
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Application.BackGroundJob; using IRaCIS.Core.Application.BackGroundJob;
using IRaCIS.Core.Application.Helper; using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Application.Service; using IRaCIS.Core.Application.Service;
@ -30,6 +31,7 @@ public static class ServiceCollectionSetup
services.AddOptions().Configure<IRCEncreptOption>(_configuration.GetSection("EncrypteResponseConfig")); services.AddOptions().Configure<IRCEncreptOption>(_configuration.GetSection("EncrypteResponseConfig"));
services.AddOptions().Configure<SystemPacsConfig>(_configuration.GetSection("SystemPacsConfig")); services.AddOptions().Configure<SystemPacsConfig>(_configuration.GetSection("SystemPacsConfig"));
services.Configure<IRaCISBasicConfigOption>(_configuration.GetSection("IRaCISBasicConfig")); services.Configure<IRaCISBasicConfigOption>(_configuration.GetSection("IRaCISBasicConfig"));
services.AddOptions().Configure<SystemHospitalOption>(_configuration.GetSection("SystemHospitalConfig"));
services.Configure<ServiceVerifyConfigOption>(_configuration.GetSection("BasicSystemConfig")); services.Configure<ServiceVerifyConfigOption>(_configuration.GetSection("BasicSystemConfig"));

View File

@ -0,0 +1,77 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ObjectStoreService": {
"ObjectStoreUse": "MinIO",
"AliyunOSS": {
"regionId": "cn-shanghai",
"endpoint": "https://oss-cn-shanghai.aliyuncs.com",
"accessKeyId": "LTAI5tKvzs7ed3UfSpNk3xwQ",
"accessKeySecret": "zTIceGEShlZDGnLrCFfIGFE7TXVRio",
"bucketName": "zy-sir-test-store",
"roleArn": "acs:ram::1899121822495495:role/oss-upload",
"viewEndpoint": "https://zy-sir-test-store.oss-cn-shanghai.aliyuncs.com",
"region": "oss-cn-shanghai"
},
"MinIO": {
"endPoint": "hir-oss.test.extimaging.com",
"port": "443",
"useSSL": true,
//"endPoint": "106.14.89.110",
//"port": "9001",
//"useSSL": false,
"accessKey": "fbStsVYCIPKHQneeqMwD",
"secretKey": "TzgvyA3zGXMUnpilJNUlyMYHfosl1hBMl6lxPmjy",
"bucketName": "hir-test",
//"viewEndpoint": "https://hir.test.extimaging.com/oss/hir-test"
"viewEndpoint": "https://hir-oss.test.extimaging.com/hir-test"
},
"AWS": {
"endPoint": "s3.us-east-1.amazonaws.com",
"useSSL": false,
"accessKey": "AKIAZQ3DRSOHFPJJ6FEU",
"secretKey": "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",
"bucketName": "ei-irc-test-store",
"viewEndpoint": "https://ei-irc-test-store.s3.amazonaws.com/"
}
},
"ConnectionStrings": {
"RemoteNew": "Server=106.14.89.110,1435;Database=Test_HIR_New;User ID=sa;Password=xc@123456;TrustServerCertificate=true",
"Hangfire": "Server=106.14.89.110,1435;Database=Test_HIR_Hangfire;User ID=sa;Password=xc@123456;TrustServerCertificate=true"
},
"BasicSystemConfig": {
"OpenUserComplexPassword": false,
"OpenSignDocumentBeforeWork": false,
"OpenTrialRelationDelete": true,
"OpenLoginLimit": false,
"LoginMaxFailCount": 5,
"LoginFailLockMinutes": 30,
"AESKey": "HIR_System_AES_Key_Info"
},
"SystemHospitalConfig": {
"HospitalCode": "EI",
"HospitalLogoPath": "/System/GeneralDocuments/1716453306898_图片2.png",
"TrialKeepCount": 60,
"HospitalName": "上海展影医疗科技有限公司",
"HospitalAliasName": "展影医疗",
"Country": "中国",
"City": "上海",
"Province": "上海",
"Address": "上海市杨浦区国泰路复旦科技园",
"Phone": "021-60702575",
"IsCanConnectInternet": false
},
"SystemEmailSendConfig": {
"Port": 465,
"Host": "smtp.qiye.aliyun.com",
"FromEmail": "test-study@extimaging.com",
"FromName": "Test_HIR",
"AuthorizationCode": "zhanying123",
"SiteUrl": "http://hir.test.extimaging.com/login"
}
}

View File

@ -1,8 +1,11 @@
using IRaCIS.Core.Application.BusinessFilter; using IRaCIS.Application.Contracts;
using IRaCIS.Core.Application.BusinessFilter;
using IRaCIS.Core.Application.Helper; using IRaCIS.Core.Application.Helper;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -13,7 +16,9 @@ using static IRaCIS.Core.Domain.Share.StaticData;
namespace IRaCIS.Core.Application.Filter; namespace IRaCIS.Core.Application.Filter;
public class TrialGlobalLimitActionFilter(IFusionCache _fusionCache, IUserInfo _userInfo, IRepository<Trial> _trialRepository) : IAsyncActionFilter public class TrialGlobalLimitActionFilter(IFusionCache _fusionCache, IUserInfo _userInfo, IRepository<Trial> _trialRepository,
IOptionsMonitor<ServiceVerifyConfigOption> _basicSystemConfigConfig,
IOptionsMonitor<SystemHospitalOption> _hospitalOption, IStringLocalizer _localizer) : IAsyncActionFilter
{ {
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{ {
@ -113,9 +118,9 @@ public class TrialGlobalLimitActionFilter(IFusionCache _fusionCache, IUserInfo _
trialIdStr = matchResult.Value; trialIdStr = matchResult.Value;
var trialStatusStr = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7)); var trialInfo = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7));
if (string.IsNullOrWhiteSpace(trialStatusStr)) if (string.IsNullOrWhiteSpace(trialInfo?.TrialStatusStr))
{ {
//数据库 检查该项目Id不对 //数据库 检查该项目Id不对
@ -145,22 +150,78 @@ public class TrialGlobalLimitActionFilter(IFusionCache _fusionCache, IUserInfo _
//通过path 或者body 找到trialId 了 //通过path 或者body 找到trialId 了
if (!string.IsNullOrWhiteSpace(trialIdStr)) if (!string.IsNullOrWhiteSpace(trialIdStr))
{ {
var trialStatusStr = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7)); var trialInfo = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7));
var trialStatusStr = string.Empty;
// 这里是统一拦截 项目有关的操作允许情况(特殊的地方比如项目配置有的在多种状态初始化ongoing都可以操作有的仅仅在Initializing还有 项目添加和更新,不走这里,特殊处理,不然在这里显得很乱,判断是哪个接口) if (trialInfo != null)
if (trialStatusStr == StaticData.TrialState.TrialOngoing || optActions.Any(t => t == TrialOpt.BeforeOngoingCantOpt))
{ {
trialStatusStr = trialInfo.TrialStatusStr;
await next(); var activationCode = trialInfo.AuthorizationEncrypt;
if (string.IsNullOrEmpty(activationCode))
{
//context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["项目未进行授权之前,不能进行操作"]));
}
else
{
var decodedText = string.Empty;
try
{
//解析加密信息
decodedText = Cryptography.DecryptString(activationCode, _basicSystemConfigConfig.CurrentValue.AESKey, "Trial_AuthorizationEncrypt");
}
catch (Exception)
{
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["该授权码与该项目不匹配"]));
return;
}
var hospitalCode = _hospitalOption.CurrentValue.HospitalCode;
var authInfo = JsonConvert.DeserializeObject<TrialAuthorizationInfo>(decodedText);
if (authInfo != null)
{
if (authInfo.TrialCode != trialInfo.TrialCode || authInfo.CreateUserId != trialInfo.CreateUserId || authInfo.TrialId != trialInfo.TrialId
|| authInfo.HospitalCode != hospitalCode || trialInfo.CriterionTypeList.Except(authInfo.CriterionTypeList).Count() != 0)
{
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["您的操作被禁止,系统检测到该项目授权码与该项目授权配置信息不一致,请还原项目授权配置信息!"]));
return;
}
if (DateTime.Now > authInfo.AuthorizationDeadLineDate.Value.AddDays(15))
{
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["当前时间已经超过项目授权截止时间半个月,请重新获取项目授权后再进行操作!"]));
return;
}
}
else
{
//context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["该授权码与该项目不匹配"]));
}
}
// 这里是统一拦截 项目有关的操作允许情况(特殊的地方比如项目配置有的在多种状态初始化ongoing都可以操作有的仅仅在Initializing还有 项目添加和更新,不走这里,特殊处理,不然在这里显得很乱,判断是哪个接口)
if (trialStatusStr == StaticData.TrialState.TrialOngoing || optActions.Any(t => t == TrialOpt.BeforeOngoingCantOpt))
{
await next();
}
// 项目停止、或者完成 不允许操作
else
{
//---本次请求被配置规则拦截:项目状态处于进行中时,才允许操作,若此处逻辑有误,请联系开发人员修改
context.Result = new JsonResult(ResponseOutput.NotOk(I18n.T("TrialResource_InterceptedProjectStatusRule")));
}
} }
// 项目停止、或者完成 不允许操作
else
{
//---本次请求被配置规则拦截:项目状态处于进行中时,才允许操作,若此处逻辑有误,请联系开发人员修改
context.Result = new JsonResult(ResponseOutput.NotOk(I18n.T("TrialResource_InterceptedProjectStatusRule")));
}
} }
//添加项目 签名系统文档的时候 不做拦截 但是更新项目 签名项目文档的时候需要拦截 //添加项目 签名系统文档的时候 不做拦截 但是更新项目 签名项目文档的时候需要拦截

View File

@ -1,196 +0,0 @@
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Domain.Share;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Localization;
using System.Text.RegularExpressions;
using ZiggyCreatures.Caching.Fusion;
using static IRaCIS.Core.Domain.Share.StaticData;
namespace IRaCIS.Core.Application.Filter;
/// <summary>
/// 主要为了 处理项目结束 锁库,不允许操作
/// </summary>
public class TrialResourceFilter : Attribute, IAsyncResourceFilter
{
private readonly IUserInfo _userInfo;
private readonly IFusionCache _fusionCache;
public IStringLocalizer _localizer;
private readonly IRepository<Trial> _trialRepository;
private readonly List<string> _trialOptList = new List<string>();
public TrialResourceFilter(IFusionCache fusionCache, IRepository<Trial> trialRepository, IStringLocalizer localizer, IUserInfo userInfo, string trialOpt = null, string trialOpt2 = null, string trialOpt3 = null)
{
_fusionCache = fusionCache;
_userInfo = userInfo;
_localizer = localizer;
_trialRepository = trialRepository;
if (!string.IsNullOrWhiteSpace(trialOpt)) _trialOptList.Add(trialOpt.Trim());
if (!string.IsNullOrWhiteSpace(trialOpt2)) _trialOptList.Add(trialOpt2.Trim());
if (!string.IsNullOrWhiteSpace(trialOpt3)) _trialOptList.Add(trialOpt3.Trim());
}
//优先选择异步的方法
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
// var typeFilter = context.ActionDescriptor.EndpointMetadata.Where(t => t.GetType() == typeof(TypeFilterAttribute)).Select(t => (TypeFilterAttribute)t).ToList().FirstOrDefault();
//var _trialOptList= typeFilter.Arguments.Select(t => t.ToString()).ToList();
// 获取当前请求的 Host 信息
var requestHost = context.HttpContext.Request.Host;
// 检查请求是否来自 localhost:6100
if (requestHost.Host == "localhost" && (requestHost.Port == 6100|| requestHost.Port==3305))
{
await next.Invoke();
return;
}
#region 处理新的用户类型,不能操作项目相关接口
// 后期列举出具体的类型,其他任何用户类型,都不允许操作
if (_userInfo.UserTypeEnumInt == (int)UserTypeEnum.CRA && _userInfo.RequestUrl.ToLower() != "TrialDocument/userConfirm".ToLower())
{
//---对不起,您的账户没有操作权限。
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["TrialResource_NoAccessPermission"]));
return;
}
#endregion
//TrialId 传递的途径多种可能在path 可能在body 可能在数组中也可能在对象中可能就在url
var trialIdStr = string.Empty;
if (!string.IsNullOrWhiteSpace(context.HttpContext.Request.Query["trialId"]))
{
trialIdStr = context.HttpContext.Request.Query["trialId"];
}
//先尝试从path中取TrialId
else if (context.RouteData.Values.Keys.Any(t => t.Contains("trialId")))
{
var index = context.RouteData.Values.Keys.ToList().IndexOf("trialId");
trialIdStr = context.RouteData.Values.Values.ToList()[index] as string;
}
else if (context.HttpContext.Request.Headers["Referer"].ToString().Contains("trialId"))
{
var headerStr = context.HttpContext.Request.Headers["Referer"].ToString();
var trialIdIndex = headerStr.IndexOf("trialId");
var matchResult = Regex.Match(headerStr.Substring(trialIdIndex), @"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}");
if (matchResult.Success)
{
trialIdStr = matchResult.Value;
}
else
{
//---正则取请求Refer 中trialId 失败,请联系开发人员核查
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["TrialResource_ReferTrialIdFailed"]));
}
}
else
{
#region body 中取数据
//设置可以多次读
context.HttpContext.Request.EnableBuffering();
var reader = new StreamReader(context.HttpContext.Request.Body);
var contentFromBody = await reader.ReadToEndAsync();
//读取后,流的位置还原
context.HttpContext.Request.Body.Seek(0, SeekOrigin.Begin);
//context.HttpContext.Request.Body.Position = 0;
//找到参数位置在字符串中的索引
var trialIdIndex = contentFromBody.IndexOf("\"TrialId\"", StringComparison.OrdinalIgnoreCase);
if (trialIdIndex > -1)
{
// (?<="trialId" *: *").*?(?=",)
//使用正则 [0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}
var matchResult = Regex.Match(contentFromBody.Substring(trialIdIndex), @"[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}");
if (matchResult.Success)
{
//有可能匹配错误 "trialId":"","documentId":"b8180000-3e2c-0016-9fe0-08da33f96236" 从缓存里面验证下
trialIdStr = matchResult.Value;
var trialStatusStr = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7));
if (string.IsNullOrWhiteSpace(trialStatusStr))
{
//数据库 检查该项目Id不对
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["TrialResource_ReferTrialIdFailed"]));
return;
}
}
else
{
//---正则取请求Refer 中trialId 失败,请联系开发人员核查
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["TrialResource_ReferTrialIdFailed"]));
return;
}
//使用字符串取 如果是swagger 可能有时取的不对 因为空格的原因
//trialIdStr = contentFromBody.Substring(trialIdIndex + "TrialId".Length + 4, 3
}
#endregion
}
//通过path 或者body 找到trialId 了
if (!string.IsNullOrWhiteSpace(trialIdStr))
{
var trialStatusStr = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7));
// 这里是统一拦截 项目有关的操作允许情况(特殊的地方比如项目配置有的在多种状态初始化ongoing都可以操作有的仅仅在Initializing还有 项目添加和更新,不走这里,特殊处理,不然在这里显得很乱,判断是哪个接口)
if (trialStatusStr == StaticData.TrialState.TrialOngoing || _trialOptList.Any(t => t == TrialOpt.BeforeOngoingCantOpt))
{
await next.Invoke();
}
// 项目停止、或者完成 不允许操作
else
{
//---本次请求被配置规则拦截:项目状态处于进行中时,才允许操作,若此处逻辑有误,请联系开发人员修改
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["TrialResource_InterceptedProjectStatusRule"]));
}
}
//添加项目 签名系统文档的时候 不做拦截 但是更新项目 签名项目文档的时候需要拦截
else if (_trialOptList.Any(t => t == TrialOpt.AddOrUpdateTrial || t == TrialOpt.SignSystemDocNoTrialId))
{
await next.Invoke();
}
else
{
//如果项目相关接口没有传递trialId 会来到这里,提醒,以便修改
//---该接口参数中,没有传递项目编号,请核对。
context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["TrialResource_MissingProjectNumber"]));
}
}
}

View File

@ -1,6 +1,9 @@
using IRaCIS.Core.Application.Helper; using IRaCIS.Application.Contracts;
using IRaCIS.Core.Application.Helper;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -24,7 +27,9 @@ public class TrialGlobalLimitAttribute : Attribute
public class TrialGlobalLimitEndpointFilter(IFusionCache _fusionCache, IUserInfo _userInfo, IRepository<Trial> _trialRepository) : IEndpointFilter public class TrialGlobalLimitEndpointFilter(IFusionCache _fusionCache, IUserInfo _userInfo, IRepository<Trial> _trialRepository,
IOptionsMonitor<ServiceVerifyConfigOption> _basicSystemConfigConfig,
IOptionsMonitor<SystemHospitalOption> _hospitalOption, IStringLocalizer _localizer) : IEndpointFilter
{ {
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next) public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
@ -121,9 +126,9 @@ public class TrialGlobalLimitEndpointFilter(IFusionCache _fusionCache, IUserInfo
trialIdStr = matchResult.Value; trialIdStr = matchResult.Value;
var trialStatusStr = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7)); var trialInfo = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7));
if (string.IsNullOrWhiteSpace(trialStatusStr)) if (string.IsNullOrWhiteSpace(trialInfo?.TrialStatusStr))
{ {
//数据库 检查该项目Id不对 //数据库 检查该项目Id不对
@ -151,22 +156,78 @@ public class TrialGlobalLimitEndpointFilter(IFusionCache _fusionCache, IUserInfo
//通过path 或者body 找到trialId 了 //通过path 或者body 找到trialId 了
if (!string.IsNullOrWhiteSpace(trialIdStr)) if (!string.IsNullOrWhiteSpace(trialIdStr))
{ {
var trialStatusStr = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7)); var trialInfo = await _fusionCache.GetOrSetAsync(CacheKeys.Trial(trialIdStr), _ => CacheHelper.GetTrialStatusAsync(Guid.Parse(trialIdStr), _trialRepository), TimeSpan.FromDays(7));
var trialStatusStr = string.Empty;
// 这里是统一拦截 项目有关的操作允许情况(特殊的地方比如项目配置有的在多种状态初始化ongoing都可以操作有的仅仅在Initializing还有 项目添加和更新,不走这里,特殊处理,不然在这里显得很乱,判断是哪个接口)
if (trialStatusStr == StaticData.TrialState.TrialOngoing || optActions.Any(t => t == TrialOpt.BeforeOngoingCantOpt)) if (trialInfo != null)
{ {
trialStatusStr = trialInfo.TrialStatusStr;
return await next(context); var activationCode = trialInfo.AuthorizationEncrypt;
if (string.IsNullOrEmpty(activationCode))
{
//context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["项目未进行授权之前,不能进行操作"]));
}
else
{
var decodedText = string.Empty;
try
{
//解析加密信息
decodedText = Cryptography.DecryptString(activationCode, _basicSystemConfigConfig.CurrentValue.AESKey, "Trial_AuthorizationEncrypt");
}
catch (Exception)
{
return Results.Json(ResponseOutput.NotOk(_localizer["该授权码与该项目不匹配"]));
}
var hospitalCode = _hospitalOption.CurrentValue.HospitalCode;
var authInfo = JsonConvert.DeserializeObject<TrialAuthorizationInfo>(decodedText);
if (authInfo != null)
{
if (authInfo.TrialCode != trialInfo.TrialCode || authInfo.CreateUserId != trialInfo.CreateUserId || authInfo.TrialId != trialInfo.TrialId
|| authInfo.HospitalCode != hospitalCode || trialInfo.CriterionTypeList.Except(authInfo.CriterionTypeList).Count() != 0)
{
return Results.Json(ResponseOutput.NotOk(_localizer["您的操作被禁止,系统检测到该项目授权码与该项目授权配置信息不一致,请还原项目授权配置信息!"]));
}
if (DateTime.Now > authInfo.AuthorizationDeadLineDate.Value.AddDays(15))
{
return Results.Json(ResponseOutput.NotOk(_localizer["当前时间已经超过项目授权截止时间半个月,请重新获取项目授权后再进行操作!"]));
}
}
else
{
//context.Result = new JsonResult(ResponseOutput.NotOk(_localizer["该授权码与该项目不匹配"]));
}
}
// 这里是统一拦截 项目有关的操作允许情况(特殊的地方比如项目配置有的在多种状态初始化ongoing都可以操作有的仅仅在Initializing还有 项目添加和更新,不走这里,特殊处理,不然在这里显得很乱,判断是哪个接口)
if (trialStatusStr == StaticData.TrialState.TrialOngoing || optActions.Any(t => t == TrialOpt.BeforeOngoingCantOpt))
{
return await next(context);
}
// 项目停止、或者完成 不允许操作
else
{
//---本次请求被配置规则拦截:项目状态处于进行中时,才允许操作,若此处逻辑有误,请联系开发人员修改
return Results.Json(ResponseOutput.NotOk(I18n.T("TrialResource_InterceptedProjectStatusRule")));
}
} }
// 项目停止、或者完成 不允许操作
else
{
//---本次请求被配置规则拦截:项目状态处于进行中时,才允许操作,若此处逻辑有误,请联系开发人员修改
return Results.Json(ResponseOutput.NotOk(I18n.T("TrialResource_InterceptedProjectStatusRule")));
}
} }
//添加项目 签名系统文档的时候 不做拦截 但是更新项目 签名项目文档的时候需要拦截 //添加项目 签名系统文档的时候 不做拦截 但是更新项目 签名项目文档的时候需要拦截

View File

@ -1,4 +1,6 @@
namespace IRaCIS.Core.Application.Helper; using IRaCIS.Application.Contracts;
namespace IRaCIS.Core.Application.Helper;
public static class CacheKeys public static class CacheKeys
@ -60,9 +62,10 @@ public static class CacheKeys
public static class CacheHelper public static class CacheHelper
{ {
public static async Task<string?> GetTrialStatusAsync(Guid trialId, IRepository<Trial> _trialRepository) public static async Task<TrialCacheInfo> GetTrialStatusAsync(Guid trialId, IRepository<Trial> _trialRepository)
{ {
var statusStr = await _trialRepository.Where(t => t.Id == trialId, ignoreQueryFilters: true).Select(t => t.TrialStatusStr).FirstOrDefaultAsync(); var statusStr = await _trialRepository.Where(t => t.Id == trialId, ignoreQueryFilters: true).Select(t => new TrialCacheInfo { TrialId = t.Id, TrialStatusStr = t.TrialStatusStr, CriterionTypes = t.CriterionTypes, AuthorizationEncrypt = t.AuthorizationEncrypt, AuthorizationDate = t.AuthorizationDate, CreateUserId = t.CreateUserId, TrialCode = t.TrialCode })
.FirstOrDefaultAsync();
return statusStr; return statusStr;
} }

View File

@ -344,11 +344,32 @@
访视读片任务 访视读片任务
</summary> </summary>
</member> </member>
<member name="M:IRaCIS.Core.Application.Service.Allocation.VisitTaskService.#ctor(IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.VisitTask},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Trial},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SubjectVisit},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TaskAllocationRule},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Subject},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SubjectUser},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadModule},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.VisitTaskReReading},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TaskMedicalReview},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingClinicalData},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SubjectCriteriaEvaluation},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SubjectCriteriaEvaluationVisitFilter},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingQuestionCriterionTrial},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingQuestionCriterionTrial},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingTaskQuestionAnswer},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.DicomInstance},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.DicomSeries},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SubjectCanceDoctor},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingTaskQuestionMark},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingTableAnswerRowInfo},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingCustomTag},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TaskInfluence},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialQCQuestionAnswer},Microsoft.Extensions.Logging.ILogger{IRaCIS.Core.Application.Service.Allocation.VisitTaskService},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SubjectCriteriaEvaluationVisitStudyFilter},AutoMapper.IMapper,IRaCIS.Core.Domain.Share.IUserInfo,Microsoft.Extensions.Localization.IStringLocalizer)"> <member name="M:IRaCIS.Core.Application.Service.Allocation.VisitTaskService.#ctor(IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.VisitTask},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Trial},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SubjectVisit},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TaskAllocationRule},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.Subject},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SubjectUser},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadModule},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.VisitTaskReReading},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TaskMedicalReview},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingClinicalData},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SubjectCriteriaEvaluation},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SubjectCriteriaEvaluationVisitFilter},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingQuestionCriterionTrial},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingQuestionCriterionTrial},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingTaskQuestionAnswer},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.DicomInstance},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.DicomSeries},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SubjectCanceDoctor},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingTaskQuestionMark},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingTableAnswerRowInfo},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingCustomTag},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TaskInfluence},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.TrialQCQuestionAnswer},Microsoft.Extensions.Logging.ILogger{IRaCIS.Core.Application.Service.Allocation.VisitTaskService},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.PIAudit},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.ReadingQuestionTrial},IRaCIS.Core.Infra.EFCore.IRepository{IRaCIS.Core.Domain.Models.SubjectCriteriaEvaluationVisitStudyFilter},AutoMapper.IMapper,IRaCIS.Core.Domain.Share.IUserInfo,Microsoft.Extensions.Localization.IStringLocalizer)">
<summary> <summary>
访视读片任务 访视读片任务
</summary> </summary>
</member> </member>
<member name="M:IRaCIS.Core.Application.Service.Allocation.VisitTaskService.PIAuditTask(IRaCIS.Core.Application.ViewModel.PIAuditTaskCommand)">
<summary>
new- 首次审核 后续编辑审核
</summary>
<param name="incommand"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.Allocation.VisitTaskService.PIAuditTaskReply(IRaCIS.Core.Application.ViewModel.PIAuditTaskReplyCommand)">
<summary>
new- 回复审核内容
</summary>
<param name="incommand"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.Allocation.VisitTaskService.GetPIAuditDialogList(IRaCIS.Core.Application.ViewModel.PIAuditDialogQuery)">
<summary>
new- 获取审核对话列表
</summary>
<param name="inQuery"></param>
<returns></returns>
</member>
<member name="M:IRaCIS.Core.Application.Service.Allocation.VisitTaskService.SetTaskUrgent(IRaCIS.Application.Contracts.SetTaskUrgentInDto)"> <member name="M:IRaCIS.Core.Application.Service.Allocation.VisitTaskService.SetTaskUrgent(IRaCIS.Application.Contracts.SetTaskUrgentInDto)">
<summary> <summary>
设置任务加急 设置任务加急
@ -12980,11 +13001,6 @@
参考处理链接: https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/error-handling?view=aspnetcore-8.0 参考处理链接: https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/error-handling?view=aspnetcore-8.0
</summary> </summary>
</member> </member>
<member name="T:IRaCIS.Core.Application.Filter.TrialResourceFilter">
<summary>
主要为了 处理项目结束 锁库,不允许操作
</summary>
</member>
<member name="M:IRaCIS.Core.Application.Helper.CacheKeys.UserLoginError(System.String)"> <member name="M:IRaCIS.Core.Application.Helper.CacheKeys.UserLoginError(System.String)">
<summary> <summary>
用户登录错误 限制登录 用户登录错误 限制登录

View File

@ -3,6 +3,7 @@
// 生成时间 2022-06-07 14:10:54 // 生成时间 2022-06-07 14:10:54
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。 // 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
//-------------------------------------------------------------------- //--------------------------------------------------------------------
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Domain.Share; using IRaCIS.Core.Domain.Share;
using Newtonsoft.Json; using Newtonsoft.Json;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
@ -10,6 +11,101 @@ using System.ComponentModel.DataAnnotations;
namespace IRaCIS.Core.Application.ViewModel namespace IRaCIS.Core.Application.ViewModel
{ {
public class PIReaingTaskView : ReadingTaskView
{
public Guid? FirstAuditUserId { get; set; }
public string FirstAuditUserName { get; set; }
public DateTime? FirstAuditTime { get; set; }
public Guid? LatestReplyUserId { get; set; }
public string LatestReplyUserName { get; set; }
public DateTime? LatestReplyTime { get; set; }
}
public class PIAuditTaskEnrollOrPdCommand
{
[NotDefault]
public Guid VisitTaskId { get; set; }
public bool? IsEnrollment { get; set; }
public bool? IsPDConfirm { get; set; }
}
public class PIAuditTaskCommand
{
[NotDefault]
public Guid VisitTaskId { get; set; }
public string NotAgreeReason { get; set; }
public string PIAuditNote { get; set; } = string.Empty;
public List<string> PIAuditImagePathList { get; set; }
public PIAuditState PIAuditState { get; set; }
}
public class PIAuditTaskReplyCommand
{
[NotDefault]
public Guid VisitTaskId { get; set; }
public string ReplyContent { get; set; } = string.Empty;
}
public class ClinicalDataDialog
{
public Guid Id { get; set; }
public DateTime CreateTime { get; set; }
public string Content { get; set; }
}
public class PIAuditDialogQuery
{
[NotDefault]
public Guid VisitTaskId { get; set; }
}
public class PIAuditDialogListView
{
public Guid VisitTaskId { get; set; }
public string NotAgreeReason { get; set; }
public string PIAuditNote { get; set; } = string.Empty;
public List<string> PIAuditImagePathList { get; set; }
public PIAuditState? PIAuditState { get; set; }
public string ReplyContent { get; set; } = string.Empty;
public bool? IsEnrollment { get; set; }
public bool? IsPDConfirm { get; set; }
public bool IsCurrentUser { get; set; }
public Guid CreateUserId { get; set; }
public string CreateUserName { get; set; }
public DateTime CreateTime { get; set; }
public UserTypeEnum UserTypeEnum { get; set; }
}
public class PIReadingResult
{
public Guid QuestionId { get; set; }
public string Answer { get; set; }
}
public class VisitTaskViewBasic public class VisitTaskViewBasic
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
@ -214,6 +310,12 @@ namespace IRaCIS.Core.Application.ViewModel
/// </summary> /// </summary>
public bool IsManualGeneration { get; set; } public bool IsManualGeneration { get; set; }
//public bool IsAfterConvertedTask { get; set; } //public bool IsAfterConvertedTask { get; set; }
public List<PIReadingResult> PIReadingResultList { get; set; }
public List<PatientBasicInfo> PatientList { get; set; }
} }
@ -399,6 +501,10 @@ namespace IRaCIS.Core.Application.ViewModel
public string? SubjectCode { get; set; } = null; public string? SubjectCode { get; set; } = null;
public string? PatientName { get; set; }
public string? PatientIdStr { get; set; }
} }
public class VisitTaskQuery : PageInput public class VisitTaskQuery : PageInput
@ -465,6 +571,33 @@ namespace IRaCIS.Core.Application.ViewModel
public string? RequestReReadingReason { get; set; } public string? RequestReReadingReason { get; set; }
public ExportResult? ReadingExportType { get; set; } public ExportResult? ReadingExportType { get; set; }
#region HIR
public PIAuditState? PIAuditState { get; set; }
public string? FirstAuditUserName { get; set; }
public bool? IsWaitPIAudit { get; set; }
public string? LatestReplyUserName { get; set; }
public DateTime? FirstAuditTimeBegin { get; set; }
public DateTime? FirstAuditTimeEnd { get; set; }
public DateTime? LatestReplyTimeBegin { get; set; }
public DateTime? LatestReplyTimeEnd { get; set; }
public string? PatientName { get; set; }
public string? PatientIdStr { get; set; }
public string? PatientSex { get; set; }
#endregion
} }

View File

@ -8,10 +8,12 @@ using IRaCIS.Application.Contracts;
using IRaCIS.Core.Application.Contracts; using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Filter; using IRaCIS.Core.Application.Filter;
using IRaCIS.Core.Application.ViewModel; using IRaCIS.Core.Application.ViewModel;
using IRaCIS.Core.Domain.Models;
using IRaCIS.Core.Domain.Share; using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Domain.Share.Reading; using IRaCIS.Core.Domain.Share.Reading;
using IRaCIS.Core.Infra.EFCore.Common; using IRaCIS.Core.Infra.EFCore.Common;
using IRaCIS.Core.Infrastructure; using IRaCIS.Core.Infrastructure;
using IRaCIS.Core.Infrastructure.Extention;
using MassTransit; using MassTransit;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -48,10 +50,196 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
IRepository<TaskInfluence> _taskInfluenceRepository, IRepository<TaskInfluence> _taskInfluenceRepository,
IRepository<TrialQCQuestionAnswer> _trialQCQuestionAnswerRepository, IRepository<TrialQCQuestionAnswer> _trialQCQuestionAnswerRepository,
ILogger<VisitTaskService> _logger, ILogger<VisitTaskService> _logger,
IRepository<PIAudit> _PIAuditRepository,
IRepository<ReadingQuestionTrial> _readingQuestionTrialRepository,
IRepository<SubjectCriteriaEvaluationVisitStudyFilter> _subjectCriteriaEvaluationVisitStudyFilterRepository, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer) : BaseService, IVisitTaskService IRepository<SubjectCriteriaEvaluationVisitStudyFilter> _subjectCriteriaEvaluationVisitStudyFilterRepository, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer) : BaseService, IVisitTaskService
{ {
#region HIR PI 相关接口
private IQueryable<VisitTask> GetReadingTaskQueryable(VisitTaskQuery inQuery)
{
var trialConfig = _trialRepository.Where(t => t.Id == inQuery.TrialId).Select(t => new { t.EnrollConfirmDefaultUserType, t.PDProgressDefaultUserType }).FirstOrDefault();
var visitTaskQueryable = _visitTaskRepository.Where(t => t.TrialId == inQuery.TrialId && t.IsAnalysisCreate == false)
.WhereIf(inQuery.TaskState != null, t => t.TaskState == inQuery.TaskState)
.WhereIf(inQuery.TrialSiteId != null, t => t.Subject.TrialSiteId == inQuery.TrialSiteId)
.WhereIf(inQuery.SubjectId != null, t => t.SubjectId == inQuery.SubjectId)
.WhereIf(inQuery.IsUrgent != null, t => t.IsUrgent == inQuery.IsUrgent)
.WhereIf(inQuery.DoctorUserId != null, t => t.DoctorUserId == inQuery.DoctorUserId)
.WhereIf(inQuery.ReadingCategory != null, t => t.ReadingCategory == inQuery.ReadingCategory)
.WhereIf(inQuery.ReadingTaskState != null, t => t.ReadingTaskState == inQuery.ReadingTaskState)
.WhereIf(inQuery.TaskAllocationState != null, t => t.TaskAllocationState == inQuery.TaskAllocationState)
.WhereIf(inQuery.TrialReadingCriterionId != null, t => t.TrialReadingCriterionId == inQuery.TrialReadingCriterionId)
.WhereIf(inQuery.ReReadingApplyState != null, t => t.ReReadingApplyState == inQuery.ReReadingApplyState)
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.PatientIdStr), t => t.Subject.SubjectPatientList.Any(t => t.Patient.PatientIdStr.Contains(inQuery.PatientIdStr)))
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.PatientName), t => t.Subject.SubjectPatientList.Any(t => t.Patient.PatientName.Contains(inQuery.PatientName)))
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.PatientSex), t => t.Subject.SubjectPatientList.Any(t => t.Patient.PatientSex.Contains(inQuery.PatientSex)))
.WhereIf(inQuery.CompleteClinicalDataEnum == CompleteClinicalDataEnum.Complete, t => t.IsClinicalDataSign && t.IsNeedClinicalDataSign == true)
.WhereIf(inQuery.CompleteClinicalDataEnum == CompleteClinicalDataEnum.NotComplete, t => t.IsClinicalDataSign == false && t.IsNeedClinicalDataSign == true)
.WhereIf(inQuery.CompleteClinicalDataEnum == CompleteClinicalDataEnum.NA, t => t.IsNeedClinicalDataSign == false)
.WhereIf(!string.IsNullOrEmpty(inQuery.TrialSiteCode), t => (t.BlindTrialSiteCode.Contains(inQuery.TrialSiteCode!) && t.IsAnalysisCreate) || (t.Subject.TrialSite.TrialSiteCode.Contains(inQuery.TrialSiteCode!) && t.IsAnalysisCreate == false))
.WhereIf(!string.IsNullOrEmpty(inQuery.TaskName), t => t.TaskName.Contains(inQuery.TaskName) || t.TaskBlindName.Contains(inQuery.TaskName))
.WhereIf(!string.IsNullOrEmpty(inQuery.SubjectCode), t => (t.Subject.Code.Contains(inQuery.SubjectCode) && t.IsAnalysisCreate == false) || (t.BlindSubjectCode.Contains(inQuery.SubjectCode) && t.IsAnalysisCreate))
.WhereIf(inQuery.BeginAllocateDate != null, t => t.AllocateTime > inQuery.BeginAllocateDate)
.WhereIf(inQuery.EndAllocateDate != null, t => t.AllocateTime < inQuery.EndAllocateDate!.Value.AddDays(1))
.WhereIf(trialConfig?.EnrollConfirmDefaultUserType == UserTypeEnum.MIM && trialConfig?.PDProgressDefaultUserType != UserTypeEnum.MIM && _userInfo.UserTypeEnumInt == (int)UserTypeEnum.MIM, t => t.SourceSubjectVisit.IsBaseLine == true)
.WhereIf(trialConfig?.EnrollConfirmDefaultUserType != UserTypeEnum.MIM && trialConfig?.PDProgressDefaultUserType == UserTypeEnum.MIM && _userInfo.UserTypeEnumInt == (int)UserTypeEnum.MIM, t => t.SourceSubjectVisit.IsBaseLine == false)
.WhereIf(inQuery.IsWaitPIAudit == true, t => t.FirstAuditUserId == null)
.WhereIf(inQuery.IsWaitPIAudit == false, t => t.FirstAuditUserId != null)
.WhereIf(inQuery.PIAuditState != null, t => t.PIAuditState == inQuery.PIAuditState)
.WhereIf(inQuery.FirstAuditTimeBegin != null, t => t.AllocateTime > inQuery.FirstAuditTimeBegin)
.WhereIf(inQuery.FirstAuditTimeEnd != null, t => t.AllocateTime < inQuery.FirstAuditTimeEnd)
.WhereIf(inQuery.LatestReplyTimeBegin != null, t => t.AllocateTime > inQuery.LatestReplyTimeBegin)
.WhereIf(inQuery.LatestReplyTimeEnd != null, t => t.AllocateTime < inQuery.LatestReplyTimeEnd);
return visitTaskQueryable;
}
[HttpPost]
public async Task<IResponseOutput<PageOutput<PIReaingTaskView>>> GetPIReadingAuditList(VisitTaskQuery queryVisitTask)
{
var visitTaskQueryable = GetReadingTaskQueryable(queryVisitTask)
.ProjectTo<PIReaingTaskView>(_mapper.ConfigurationProvider);
var defalutSortArray = new string[] { nameof(VisitTask.IsUrgent) + " desc", nameof(VisitTask.SubjectId), nameof(VisitTask.VisitTaskNum) };
var pageList = await visitTaskQueryable.ToPagedListAsync(queryVisitTask, defalutSortArray);
var trialTaskConfig = _trialRepository.Where(t => t.Id == queryVisitTask.TrialId).ProjectTo<TrialUrgentConfig>(_mapper.ConfigurationProvider).FirstOrDefault();
var questionList = _readingQuestionTrialRepository.Where(t => t.TrialId == queryVisitTask.TrialId && t.ReadingQuestionCriterionTrialId == queryVisitTask.TrialReadingCriterionId)
.Where(t => t.IsJudgeQuestion == true)
.Select(t => new { QuestionId = t.Id, QuestionName = _userInfo.IsEn_Us ? t.QuestionEnName : t.QuestionName, t.DictionaryCode }).ToList();
trialTaskConfig!.OtherObj = questionList;
return ResponseOutput.Ok(pageList, trialTaskConfig);
}
/// <summary>
///new- 首次审核 后续编辑审核
/// </summary>
/// <param name="incommand"></param>
/// <returns></returns>
[HttpPost]
public async Task<IResponseOutput> PIAuditTask(PIAuditTaskCommand incommand)
{
//需要设置首次审核时间 审核人
var visitTask = await _visitTaskRepository.FirstOrDefaultAsync(t => t.Id == incommand.VisitTaskId);
var isFirstAudit = visitTask.FirstAuditUserId == null;
visitTask.NotAgreeReason = incommand.NotAgreeReason;
visitTask.PIAuditNote = incommand.PIAuditNote;
visitTask.PIAuditImagePath = string.Join('|', incommand.PIAuditImagePathList);
visitTask.PIAuditState = incommand.PIAuditState;
visitTask.LatestReplyUserId = _userInfo.Id;
visitTask.LatestReplyTime = DateTime.Now;
visitTask.IsEnrollment = incommand.PIAuditState == PIAuditState.PINotAgree ? null : visitTask.IsEnrollment;
visitTask.IsPDConfirm = incommand.PIAuditState == PIAuditState.PINotAgree ? null : visitTask.IsPDConfirm;
if (isFirstAudit)
{
visitTask.FirstAuditUserId = _userInfo.Id;
visitTask.FirstAuditTime = DateTime.Now;
}
//发送对话
var addDialig = _mapper.Map<PIAudit>(incommand);
addDialig.PIAuditState = incommand.PIAuditState;
addDialig.PIAuditImagePath = visitTask.PIAuditImagePath;
await _PIAuditRepository.AddAsync(addDialig);
await _visitTaskRepository.SaveChangesAsync();
return ResponseOutput.Ok();
}
[HttpPost]
[UnitOfWork]
public async Task<IResponseOutput> PIAuditTaskEnrollOrPD(PIAuditTaskEnrollOrPdCommand incommand, [FromServices] IEmailSendService emailSendService)
{
var visitTask = await _visitTaskRepository.FirstOrDefaultAsync(t => t.Id == incommand.VisitTaskId);
visitTask.IsEnrollment = incommand.IsEnrollment != null ? incommand.IsEnrollment : visitTask.IsEnrollment;
visitTask.IsPDConfirm = incommand.IsPDConfirm != null ? incommand.IsPDConfirm : visitTask.IsPDConfirm;
visitTask.LatestReplyUserId = _userInfo.Id;
visitTask.LatestReplyTime = DateTime.Now;
await _visitTaskRepository.SaveChangesAsync();
//await emailSendService.SendEnrollOrPdEmail(visitTask.Id, incommand.IsEnrollment, incommand.IsPDConfirm);
return ResponseOutput.Ok();
}
/// <summary>
///new- 回复审核内容
/// </summary>
/// <param name="incommand"></param>
/// <returns></returns>
public async Task<IResponseOutput> PIAuditTaskReply(PIAuditTaskReplyCommand incommand)
{
await _PIAuditRepository.AddAsync(new PIAudit() { ReplyContent = incommand.ReplyContent, VisitTaskId = incommand.VisitTaskId });
await _PIAuditRepository.SaveChangesAsync();
return ResponseOutput.Ok();
}
/// <summary>
/// new- 获取审核对话列表
/// </summary>
/// <param name="inQuery"></param>
/// <returns></returns>
[HttpPost]
public async Task<List<PIAuditDialogListView>> GetPIAuditDialogList(PIAuditDialogQuery inQuery)
{
var list = await _PIAuditRepository.Where(t => t.VisitTaskId == inQuery.VisitTaskId).OrderBy(t => t.CreateTime).ProjectTo<PIAuditDialogListView>(_mapper.ConfigurationProvider).ToListAsync();
foreach (var item in list)
{
item.IsCurrentUser = item.CreateUserId == _userInfo.Id;
}
return list;
}
public async Task<IResponseOutput> AddClinicalDataDialog(Guid visiTaskId, string content,
[FromServices] IRepository<SubjectVisitClinicalDialog> subjectVisitClinicalDialogRepository,
[FromServices] IEmailSendService emailSendService)
{
var taskInfo = await _visitTaskRepository.Where(t => t.Id == visiTaskId).Select(t => new { t.SourceSubjectVisitId, t.Subject.Code, t.SourceSubjectVisit.VisitName, t.Trial.ResearchProgramNo, t.Trial.TrialCode }).FirstOrDefaultAsync();
await subjectVisitClinicalDialogRepository.AddAsync(new SubjectVisitClinicalDialog() { SubjectVisitId = (Guid)taskInfo.SourceSubjectVisitId, Content = content }, true);
await emailSendService.SendClinicalDataQuestionAsync(visiTaskId, content);
return ResponseOutput.Ok();
}
public async Task<List<ClinicalDataDialog>> GetClinicalDataDialog(Guid visiTaskId,
[FromServices] IRepository<SubjectVisitClinicalDialog> _subjectVisitClinicalDialogRepository)
{
var subjectVisitId = await _visitTaskRepository.Where(t => t.Id == visiTaskId).Select(t => t.SourceSubjectVisitId).FirstOrDefaultAsync();
var list = _subjectVisitClinicalDialogRepository.Where(t => t.SubjectVisitId == subjectVisitId).ProjectTo<ClinicalDataDialog>(_mapper.ConfigurationProvider).OrderByDescending(t => t.CreateTime).ToList();
return list;
}
#endregion
/// <summary> /// <summary>
/// 设置任务加急 /// 设置任务加急
@ -1001,9 +1189,53 @@ public class VisitTaskService(IRepository<VisitTask> _visitTaskRepository,
var critrion = await _trialReadingCriterionRepository.FindAsync(trialReadingCriterionId); var critrion = await _trialReadingCriterionRepository.FindAsync(trialReadingCriterionId);
var readingDivisionEnum = critrion.ReadingDivisionEnum;
var piReadingScopenEnum = critrion.PIReadingScopenEnum;
if (critrion.IsReadingTaskViewInOrder == ReadingOrder.InOrder) if (critrion.IsReadingTaskViewInOrder == ReadingOrder.InOrder)
{ {
var visitQuery = _visitTaskRepository.Where(x => x.TrialId == inQuery.TrialId && x.DoctorUserId == _userInfo.Id && x.TaskState == TaskState.Effect) var visitQuery = _visitTaskRepository.Where(x => x.TrialId == inQuery.TrialId && x.DoctorUserId == _userInfo.Id && x.TaskState == TaskState.Effect)
.WhereIf(inQuery.SubjectId != null, t => t.SubjectId == inQuery.SubjectId)
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.PatientIdStr), t => t.Subject.SubjectPatientList.Any(t => t.Patient.PatientIdStr.Contains(inQuery.PatientIdStr)))
.WhereIf(!string.IsNullOrWhiteSpace(inQuery.PatientName), t => t.Subject.SubjectPatientList.Any(t => t.Patient.PatientName.Contains(inQuery.PatientName)))
//PI 读基线的时候subject 如果PI基线没阅片完SR就不能看
.WhereIf(_userInfo.UserTypeEnumInt == (int)UserTypeEnum.SR && piReadingScopenEnum == PIReadingScopenEnum.AllBaseline && readingDivisionEnum == ReadingDivisionEnum.PIandSR,
t => t.Subject.SubjectVisitTaskList.Any(c => c.SourceSubjectVisit.IsBaseLine == true && c.ReadingTaskState == ReadingTaskState.HaveSigned && c.TaskState == TaskState.Effect && c.TrialReadingCriterionId == trialReadingCriterionId))
//PI 读随访的时候, subject 如果SR基线没阅片完PI就不能看
.WhereIf(_userInfo.UserTypeEnumInt == (int)UserTypeEnum.PI && piReadingScopenEnum == PIReadingScopenEnum.AllVisit && readingDivisionEnum == ReadingDivisionEnum.PIandSR,
t => t.Subject.SubjectVisitTaskList.Any(c => c.SourceSubjectVisit.IsBaseLine == true && c.ReadingTaskState == ReadingTaskState.HaveSigned && c.TaskState == TaskState.Effect && c.TrialReadingCriterionId == trialReadingCriterionId))
//没有site概念
//.WhereIf(_userInfo.UserTypeEnumInt == (int)UserTypeEnum.SR, t => t.Subject.TrialSite.CRCUserList.Any(u => u.UserId == _userInfo.Id))
// 仅仅SR阅片 PI 没有任务列表
.WhereIf(readingDivisionEnum == ReadingDivisionEnum.OnlySR && _userInfo.UserTypeEnumInt == (int)UserTypeEnum.PI, t => t.TrialId == Guid.Empty)
//PI查看 PI阅片所有基线
.WhereIf(readingDivisionEnum == ReadingDivisionEnum.PIandSR && _userInfo.UserTypeEnumInt == (int)UserTypeEnum.PI && piReadingScopenEnum == PIReadingScopenEnum.AllBaseline,
t => t.SourceSubjectVisit.IsBaseLine == true)
// PI查看 PI 阅片所有
.WhereIf(readingDivisionEnum == ReadingDivisionEnum.PIandSR && _userInfo.UserTypeEnumInt == (int)UserTypeEnum.PI && piReadingScopenEnum == PIReadingScopenEnum.AllBaselineandVisit,
t => true)
// PI查看 PI 阅片访视
.WhereIf(readingDivisionEnum == ReadingDivisionEnum.PIandSR && _userInfo.UserTypeEnumInt == (int)UserTypeEnum.PI && piReadingScopenEnum == PIReadingScopenEnum.AllVisit,
t => t.SourceSubjectVisit.IsBaseLine == false)
//SR查看 PI阅片所有基线
.WhereIf(readingDivisionEnum == ReadingDivisionEnum.PIandSR && _userInfo.UserTypeEnumInt == (int)UserTypeEnum.SR && piReadingScopenEnum == PIReadingScopenEnum.AllBaseline,
t => t.SourceSubjectVisit.IsBaseLine == false)
//SR查看 PI 阅片所有
.WhereIf(readingDivisionEnum == ReadingDivisionEnum.PIandSR && _userInfo.UserTypeEnumInt == (int)UserTypeEnum.SR && piReadingScopenEnum == PIReadingScopenEnum.AllBaselineandVisit,
t => t.TrialId == Guid.Empty)
//SR查看 PI 阅片访视
.WhereIf(readingDivisionEnum == ReadingDivisionEnum.PIandSR && _userInfo.UserTypeEnumInt == (int)UserTypeEnum.SR && piReadingScopenEnum == PIReadingScopenEnum.AllVisit,
t => t.SourceSubjectVisit.IsBaseLine == true)
.WhereIf(inQuery.SubjectId != null, x => x.SubjectId == inQuery.SubjectId) .WhereIf(inQuery.SubjectId != null, x => x.SubjectId == inQuery.SubjectId)
//前序 不存在 未生成任务的访视 //前序 不存在 未生成任务的访视

View File

@ -10,6 +10,21 @@ namespace IRaCIS.Core.Application.Service
public AllocationConfig() public AllocationConfig()
{ {
#region HIR
CreateMap<VisitTask, PIReaingTaskView>().IncludeBase<VisitTask, ReadingTaskView>()
.ForMember(o => o.FirstAuditUserName, t => t.MapFrom(u => u.FirstAuditUser.UserName))
.ForMember(o => o.LatestReplyUserName, t => t.MapFrom(u => u.LatestReplyUser.UserName))
.ForMember(d => d.PatientList, u => u.MapFrom(s => s.Subject.SubjectPatientList));
CreateMap<PIAuditTaskCommand, PIAudit>();
CreateMap<PIAudit, PIAuditDialogListView>()
.ForMember(o => o.CreateUserName, t => t.MapFrom(u => u.CreateUser.UserName))
.ForMember(o => o.UserTypeEnum, t => t.MapFrom(u => u.CreateUser.UserTypeEnum))
//.ForMember(o => o.PIAuditState, t => t.MapFrom(u => u.VisitTask.PIAuditState))
;
#endregion
CreateMap<ReadingTableAnswerRowInfo, ConvertedRowInfo>() CreateMap<ReadingTableAnswerRowInfo, ConvertedRowInfo>()
.ForMember(d => d.OriginalId, u => u.MapFrom(s => s.Id)); .ForMember(d => d.OriginalId, u => u.MapFrom(s => s.Id));
@ -321,6 +336,7 @@ namespace IRaCIS.Core.Application.Service
CreateMap<ReadingClinicalDataPDF, ReadingConsistentClinicalDataPDF>().ReverseMap(); CreateMap<ReadingClinicalDataPDF, ReadingConsistentClinicalDataPDF>().ReverseMap();
} }
} }

View File

@ -1,4 +1,5 @@
using IRaCIS.Core.Application.Helper; using IRaCIS.Application.Contracts;
using IRaCIS.Core.Application.Helper;
using IRaCIS.Core.Domain.Share; using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infrastructure; using IRaCIS.Core.Infrastructure;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -9,7 +10,11 @@ namespace IRaCIS.Core.Application.Service
public interface IEmailSendService public interface IEmailSendService
{ {
Task SendEnrollOrPdEmail(Guid visitTaskId, bool? isEnrollment, bool? isPDConfirm); Task SendEnrollOrPdEmail(Guid visitTaskId, bool? isEnrollment, bool? isPDConfirm);
Task SendClinicalDataQuestionAsync(Guid visitTaskId, string content);
Task SendPIAuditResultAsync(Guid visitTaskId);
Task<(TrialEmailNoticeConfig?, SMTPEmailConfig?)> BuildEmailConfig(Guid trialId, EmailBusinessScenario businessScenario, Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc, Guid? siteId = null, Guid? trialReadingCriterionId = null); Task<(TrialEmailNoticeConfig?, SMTPEmailConfig?)> BuildEmailConfig(Guid trialId, EmailBusinessScenario businessScenario, Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc, Guid? siteId = null, Guid? trialReadingCriterionId = null);
} }
@ -18,6 +23,7 @@ namespace IRaCIS.Core.Application.Service
IRepository<TrialUser> _trialUserRepository, IRepository<TrialUser> _trialUserRepository,
IRepository<VisitTask> _visitTaskRepository, IRepository<VisitTask> _visitTaskRepository,
IRepository<TrialSiteUser> _trialSiteUserRepository, IRepository<TrialSiteUser> _trialSiteUserRepository,
IRepository<Dictionary> _dictionaryRepository,
IOptionsMonitor<SystemEmailSendConfig> _SystemEmailSendConfig, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer) : BaseService, IEmailSendService IOptionsMonitor<SystemEmailSendConfig> _SystemEmailSendConfig, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer) : BaseService, IEmailSendService
{ {
@ -98,6 +104,71 @@ namespace IRaCIS.Core.Application.Service
} }
//临床数据质询
public async Task SendClinicalDataQuestionAsync(Guid visitTaskId, string content)
{
var isEn_us = _userInfo.IsEn_Us;
var info = await _visitTaskRepository.Where(t => t.Id == visitTaskId, ignoreQueryFilters: true).Select(t => new { t.TrialId, t.Trial.ResearchProgramNo, t.Trial.TrialCode, t.SourceSubjectVisit.VisitName, t.Subject.TrialSiteId, t.Subject.Code }).FirstOrDefaultAsync();
Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc = trialEmailConfig =>
{
var topicStr = string.Format(isEn_us ? trialEmailConfig.EmailTopic : trialEmailConfig.EmailTopicCN, info.ResearchProgramNo, info.Code, info.VisitName);
var htmlBodyStr = string.Format(isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN,
EmailNamePlaceholder, info.ResearchProgramNo, info.Code, info.VisitName, _userInfo.UserName, content, _SystemEmailSendConfig.CurrentValue.SiteUrl);
return (topicStr, htmlBodyStr, isEn_us, null);
};
await SendTrialEmailAsync(info.TrialId, EmailBusinessScenario.ClinicalDataQuestion, topicAndHtmlFunc, info.TrialSiteId);
}
public async Task SendPIAuditResultAsync(Guid visitTaskId)
{
var isEn_us = _userInfo.IsEn_Us;
var info = await _visitTaskRepository.Where(t => t.Id == visitTaskId, ignoreQueryFilters: true).Select(t => new { t.TrialId, t.Trial.ResearchProgramNo, t.Trial.TrialCode, t.SourceSubjectVisit.VisitName, t.Subject.TrialSiteId, t.Subject.Code }).FirstOrDefaultAsync();
var answerList = await _visitTaskRepository.Where(t => t.Id == visitTaskId, ignoreQueryFilters: true).SelectMany(t => t.ReadingTaskQuestionAnswerList).Where(t => t.ReadingQuestionTrial.IsJudgeQuestion == true).Select(t => new { QuestionName = isEn_us ? t.ReadingQuestionTrial.QuestionEnName : t.ReadingQuestionTrial.QuestionName, t.ReadingQuestionTrial.DictionaryCode, t.Answer }).ToListAsync();
var template = " <div style=\"margin-left: 2ch;\"> {0}: {1} </div>";
var needTranslateDicNameList = answerList.Where(t => !string.IsNullOrEmpty(t.DictionaryCode)).Select(t => t.DictionaryCode).ToList();
var searchList = await _dictionaryRepository.Where(t => needTranslateDicNameList.ToArray().Contains(t.Parent.Code) && t.ParentId != null && t.IsEnable).ProjectTo<BasicDicSelectCopy>(_mapper.ConfigurationProvider, new { isEn_Us = _userInfo.IsEn_Us }).ToListAsync();
var translateDataList = searchList.GroupBy(t => t.ParentCode).ToDictionary(g => g.Key, g => g.OrderBy(t => t.ShowOrder).ToList());
Func<bool, string, string, string> transFunc = (bool isNeedTranslate, string dicCode, string answer) =>
{
if (isNeedTranslate && translateDataList.ContainsKey(dicCode))
{
var result = translateDataList[dicCode].Where(t => t.Code.ToLower() == answer.ToLower()).Select(t => isEn_us ? t.Value : t.ValueCN).FirstOrDefault() ?? answer;
return result;
}
else
{
return answer;
}
};
var piResult = string.Join(' ', answerList.Select(t => string.Format(template, t.QuestionName, transFunc(!string.IsNullOrEmpty(t.DictionaryCode), t.DictionaryCode, t.Answer))));
Func<TrialEmailNoticeConfig, (string topicStr, string htmlBodyStr, bool isEn_us, Guid? onlyToUserId)> topicAndHtmlFunc = trialEmailConfig =>
{
var topicStr = string.Format(isEn_us ? trialEmailConfig.EmailTopic : trialEmailConfig.EmailTopicCN, info.ResearchProgramNo, info.Code, info.VisitName);
var htmlBodyStr = string.Format(isEn_us ? trialEmailConfig.EmailHtmlContent : trialEmailConfig.EmailHtmlContentCN,
EmailNamePlaceholder, info.ResearchProgramNo, info.Code, info.VisitName, _userInfo.UserName, piResult, _SystemEmailSendConfig.CurrentValue.SiteUrl);
return (topicStr, htmlBodyStr, isEn_us, null);
};
await SendTrialEmailAsync(info.TrialId, EmailBusinessScenario.PIAuditResutl, topicAndHtmlFunc, info.TrialSiteId);
}

View File

@ -27,7 +27,7 @@ namespace IRaCIS.Core.Application.Service
IRepository<Trial> _trialRepository, IRepository<Trial> _trialRepository,
IOptionsMonitor<ServiceVerifyConfigOption> _verifyConfig, IOptionsMonitor<ServiceVerifyConfigOption> _verifyConfig,
IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig, IOptionsMonitor<SystemEmailSendConfig> systemEmailConfig,
IOptionsMonitor<SystemHospitalOption> _systemHospitalConfig,
ISearcher _searcher, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer, IFusionCache _fusionCache) : BaseService, IUserService ISearcher _searcher, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer, IFusionCache _fusionCache) : BaseService, IUserService
{ {
@ -276,10 +276,14 @@ namespace IRaCIS.Core.Application.Service
public async Task<IResponseOutput> ResetPassword(Guid userId) public async Task<IResponseOutput> ResetPassword(Guid userId)
{ {
var pwd = IRCEmailPasswordHelper.GenerateRandomPassword(10); //var pwd = IRCEmailPasswordHelper.GenerateRandomPassword(10);
var pwd = "123456";
await _mailVerificationService.AdminResetPwdSendEmailAsync(userId, pwd); if (_systemHospitalConfig.CurrentValue.IsCanConnectInternet)
{
await _mailVerificationService.AdminResetPwdSendEmailAsync(userId, pwd);
}
await _userRepository.UpdatePartialFromQueryAsync(userId, u => new User() await _userRepository.UpdatePartialFromQueryAsync(userId, u => new User()
{ {
@ -348,34 +352,48 @@ namespace IRaCIS.Core.Application.Service
[HttpGet("{email}/{verifyCode}")] [HttpGet("{email}/{verifyCode}")]
public async Task<List<UserAccountDto>> VerifyAnonymousVerifyCode(string email, string verifyCode) public async Task<List<UserAccountDto>> VerifyAnonymousVerifyCode(string email, string verifyCode)
{ {
var verificationRecord = await _verificationCodeRepository if (_systemHospitalConfig.CurrentValue.IsCanConnectInternet)
.Where(t => t.UserId == Guid.Empty && t.Code == verifyCode && t.CodeType == VerifyType.Email && t.EmailOrPhone == email).OrderByDescending(t => t.CreateTime).FirstOrDefaultAsync();
//检查数据库是否存在该验证码
if (verificationRecord == null)
{ {
var verificationRecord = await _verificationCodeRepository
.Where(t => t.UserId == Guid.Empty && t.Code == verifyCode && t.CodeType == VerifyType.Email && t.EmailOrPhone == email).OrderByDescending(t => t.CreateTime).FirstOrDefaultAsync();
//---验证码错误。 //检查数据库是否存在该验证码
throw new BusinessValidationFailedException(_localizer["User_VerificationCodeError"]); if (verificationRecord == null)
{
//---验证码错误。
throw new BusinessValidationFailedException(_localizer["User_VerificationCodeError"]);
}
else
{
//检查验证码是否失效
if (verificationRecord.ExpirationTime < DateTime.Now)
{
//---验证码已经过期。
throw new BusinessValidationFailedException(_localizer["User_VerificationCodeExpired"]);
}
else //验证码正确 并且 没有超时
{
//删除验证码历史记录
await _verificationCodeRepository.BatchDeleteNoTrackingAsync(t => t.Id == verificationRecord.Id);
}
}
} }
else else
{ {
//检查验证码是否失效 var isPass = _userRepository.Where(t => t.EMail == email).Any(t => t.CheckCode == verifyCode);
if (verificationRecord.ExpirationTime < DateTime.Now)
{
//---验证码已经过期。 if (!isPass)
throw new BusinessValidationFailedException(_localizer["User_VerificationCodeExpired"]);
}
else //验证码正确 并且 没有超时
{ {
throw new BusinessValidationFailedException(_localizer["User_VerificationCodeError"]);
//删除验证码历史记录
await _verificationCodeRepository.BatchDeleteNoTrackingAsync(t => t.Id == verificationRecord.Id);
} }
} }
var list = await _userRepository.Where(t => t.EMail == email && t.Status == UserStateEnum.Enable).Select(t => new UserAccountDto() { UserId = t.Id, UserName = t.UserName, UserRealName = t.FullName, UserType = t.UserTypeRole.UserTypeShortName }).ToListAsync();
var list = await _userRepository.Where(t => t.EMail == email).Select(t => new UserAccountDto() { UserId = t.Id, UserName = t.UserName, UserRealName = t.FullName, UserType = t.UserTypeRole.UserTypeShortName }).ToListAsync();
@ -531,8 +549,11 @@ namespace IRaCIS.Core.Application.Service
var success = await _userRepository.SaveChangesAsync(); var success = await _userRepository.SaveChangesAsync();
} }
if (_systemHospitalConfig.CurrentValue.IsCanConnectInternet)
{
await _mailVerificationService.AddUserSendEmailAsync(saveItem.Id, userAddModel.BaseUrl, userAddModel.RouteUrl);
await _mailVerificationService.AddUserSendEmailAsync(saveItem.Id, userAddModel.BaseUrl, userAddModel.RouteUrl); }
return ResponseOutput.Ok(new UserAddedReturnDTO { Id = saveItem.Id, UserCode = saveItem.UserCode }); return ResponseOutput.Ok(new UserAddedReturnDTO { Id = saveItem.Id, UserCode = saveItem.UserCode });

View File

@ -244,7 +244,7 @@ namespace IRaCIS.Core.Application.Service
/// <param name="inDto"></param> /// <param name="inDto"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost] [HttpPost]
[TypeFilter(typeof(TrialResourceFilter))] [TrialGlobalLimit("AfterStopCannNotOpt")]
public async Task<IResponseOutput> AddOrUpdateReadingMedicineTrialQuestion(ReadingMedicineTrialQuestionAddOrEdit inDto) public async Task<IResponseOutput> AddOrUpdateReadingMedicineTrialQuestion(ReadingMedicineTrialQuestionAddOrEdit inDto)
{ {
var existsQuery = _readingMedicineTrialQuestionRepository var existsQuery = _readingMedicineTrialQuestionRepository

View File

@ -315,7 +315,12 @@ namespace IRaCIS.Core.Application.Contracts
public bool IsPDProgressView { get; set; } public bool IsPDProgressView { get; set; }
public UserTypeEnum? EnrollConfirmDefaultUserType { get; set; }
public UserTypeEnum? PDProgressDefaultUserType { get; set; }
public object? OtherObj { get; set; }
} }

View File

@ -42,6 +42,8 @@ namespace IRaCIS.Core.Application
IRepository<Enroll> _enrollRepository, IRepository<Enroll> _enrollRepository,
IRepository<TrialStateChange> _trialStateChangeRepository, IRepository<TrialStateChange> _trialStateChangeRepository,
IRepository<ReadingTableQuestionTrial> _readingTableQuestionTrialRepository, IRepository<ReadingTableQuestionTrial> _readingTableQuestionTrialRepository,
IOptionsMonitor<ServiceVerifyConfigOption> _basicSystemConfigConfig,
IOptionsMonitor<SystemHospitalOption> _hospitalOption,
IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer, IFusionCache _fusionCache) : BaseService, ITrialConfigService IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer, IFusionCache _fusionCache) : BaseService, ITrialConfigService
{ {
@ -1174,15 +1176,57 @@ namespace IRaCIS.Core.Application
return ResponseOutput.NotOk(_localizer["TrialConfig_ProjectNotStarted"]); return ResponseOutput.NotOk(_localizer["TrialConfig_ProjectNotStarted"]);
} }
//if (trialStatusStr != StaticData.TrialState.TrialOngoing)
//{
// trial.TrialFinishTime = DateTime.Now;
//}
trial.TrialStateChangeList.Add(new TrialStateChange() { OriginState = trial.TrialStatusStr, NowState = trialStatusStr, Reason = reason ?? String.Empty, TrialId = trialId }); trial.TrialStateChangeList.Add(new TrialStateChange() { OriginState = trial.TrialStatusStr, NowState = trialStatusStr, Reason = reason ?? String.Empty, TrialId = trialId });
trial.TrialStatusStr = trialStatusStr; trial.TrialStatusStr = trialStatusStr;
//项目启动,需要添加项目标准,设置默认数据
if (trialStatusStr.Contains(StaticData.TrialState.TrialOngoing))
{
//配置的要同步的系统标准
var criterionTypeList = trial.CriterionTypeList;
//删除项目项目已有的,之前查询接口,每次会自动添加,属实恶心
await _readingQuestionCriterionTrialRepository.BatchDeleteNoTrackingAsync(x => x.TrialId == trialId && x.IsConfirm == false);
var trialCriterionTypes = _readingQuestionCriterionTrialRepository.Where(x => x.TrialId == trialId)
.Select(x => x.CriterionType);
var needAddCriterionList = await _readingQuestionCriterionSystemRepository.Where(x => x.IsEnable && criterionTypeList.Contains(x.CriterionType) && !trialCriterionTypes.Contains(x.CriterionType)).ProjectTo<ReadingQuestionCriterionTrial>(_mapper.ConfigurationProvider).ToListAsync();
needAddCriterionList.ForEach(x =>
{
x.TrialId = trialId;
x.ReadingQuestionCriterionSystemId = x.Id;
x.Id = NewId.NextGuid();
x.IsAutoCreate =/* x.CriterionType == CriterionType.RECIST1Pointt1_MB ? false :*/ true;
//对应勾选确认
x.IsConfirm = true;
//对应签名
x.IsSigned = true;
x.ReadingInfoSignTime = DateTime.Now;
});
await _readingQuestionCriterionTrialRepository.AddRangeAsync(needAddCriterionList);
await _readingQuestionCriterionTrialRepository.SaveChangesAsync();
//同步标准数据
foreach (var item in needAddCriterionList)
{
await AsyncTrialCriterionDictionary(new AsyncTrialCriterionDictionaryInDto() { TrialReadingCriterionId = item.Id });
}
}
//Paused、 添加工总量 算医生读片中 //Paused、 添加工总量 算医生读片中
if (trialStatusStr.Contains(StaticData.TrialState.TrialCompleted)) if (trialStatusStr.Contains(StaticData.TrialState.TrialCompleted))
@ -1196,7 +1240,39 @@ namespace IRaCIS.Core.Application
} }
await _fusionCache.SetAsync(CacheKeys.Trial(trial.Id.ToString()), trialStatusStr, TimeSpan.FromDays(7)); #region 自动激活一个月
var hospitalCode = _hospitalOption.CurrentValue.HospitalCode;
var hospitalName = _hospitalOption.CurrentValue.HospitalName;
var deadLineDate = DateTime.Now.Date.AddMonths(1).AddDays(1).AddSeconds(-1);
var authorizationInfo = new TrialAuthorizationInfo()
{
ActiveTime = DateTime.Now,
PurchaseDuration = 1,
CriterionTypeList = trial.CriterionTypeList,
HospitalCode = hospitalCode,
HospitalName = hospitalName,
CreateUserId = trial.CreateUserId,
TrialId = trial.Id,
TrialCode = trial.TrialCode,
AuthorizationDeadLineDate = deadLineDate,
ActiveDeadLineDate = DateTime.Now.Date.AddDays(8).AddSeconds(-1)
};
var newActivationCode = Cryptography.EncryptString($"{JsonConvert.SerializeObject(authorizationInfo)}", _basicSystemConfigConfig.CurrentValue.AESKey, "Trial_AuthorizationEncrypt");
trial.AuthorizationEncrypt = newActivationCode;
trial.AuthorizationDate = deadLineDate;
//await _trialRepository.BatchUpdateNoTrackingAsync(t => t.Id == trialId, u => new Trial() { AuthorizationEncrypt = newActivationCode, AuthorizationDate = deadLineDate });
#endregion
var caheInfo = new TrialCacheInfo() { TrialId = trial.Id, TrialStatusStr = trial.TrialStatusStr, CriterionTypes = trial.CriterionTypes, AuthorizationEncrypt = trial.AuthorizationEncrypt, AuthorizationDate = trial.AuthorizationDate, CreateUserId = trial.CreateUserId, TrialCode = trial.TrialCode };
await _fusionCache.SetAsync(CacheKeys.Trial(trial.Id.ToString()), caheInfo, TimeSpan.FromDays(7));
await _trialRepository.SaveChangesAsync(); await _trialRepository.SaveChangesAsync();

View File

@ -196,8 +196,8 @@ namespace IRaCIS.Application.Services
[FromServices] IOptionsMonitor<SystemEmailSendConfig> _systemEmailSendConfig, [FromServices] IOptionsMonitor<SystemEmailSendConfig> _systemEmailSendConfig,
[FromServices] IOptionsMonitor<ServiceVerifyConfigOption> _basicSystemConfigConfig, [FromServices] IOptionsMonitor<ServiceVerifyConfigOption> _basicSystemConfigConfig,
[FromServices] IOptionsMonitor<SystemHospitalOption> _systemHospitalOption, [FromServices] IOptionsMonitor<SystemHospitalOption> _systemHospitalOption,
IRepository<TrialDictionary> _trialDictionaryRepository, [FromServices] IRepository<TrialDictionary> _trialDictionaryRepository,
IRepository<TrialUser> _trialUserRepository, [FromServices] IRepository<TrialUser> _trialUserRepository,
[FromServices] IFusionCache _provider) [FromServices] IFusionCache _provider)
{ {
var code = _systemHospitalOption.CurrentValue.HospitalCode; var code = _systemHospitalOption.CurrentValue.HospitalCode;
@ -840,7 +840,7 @@ namespace IRaCIS.Application.Services
#region 受试者管理 #region 受试者管理
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [TrialGlobalLimit("AfterStopCannNotOpt")]
[HttpPost] [HttpPost]
public async Task<IResponseOutput<string>> AddOrUpdateSubject([FromBody] AddOrUpdateSubjectCommand subjectCommand) public async Task<IResponseOutput<string>> AddOrUpdateSubject([FromBody] AddOrUpdateSubjectCommand subjectCommand)
{ {
@ -1136,7 +1136,7 @@ namespace IRaCIS.Application.Services
/// <param name="inCommand"></param> /// <param name="inCommand"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost] [HttpPost]
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [TrialGlobalLimit("AfterStopCannNotOpt")]
public async Task<IResponseOutput> AutoBindingPatientStudyVisit(AutoBindingPatientStudyVisitCommand inCommand) public async Task<IResponseOutput> AutoBindingPatientStudyVisit(AutoBindingPatientStudyVisitCommand inCommand)
{ {
//找到已绑定患者,但是没绑定检查的 新来的检查->现在换成未提交的 //找到已绑定患者,但是没绑定检查的 新来的检查->现在换成未提交的
@ -1216,7 +1216,7 @@ namespace IRaCIS.Application.Services
/// <param name="inCommand"></param> /// <param name="inCommand"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost] [HttpPost]
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [TrialGlobalLimit("AfterStopCannNotOpt")]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> AddSubjectPatientBinding(AddSubjectPatientCommand inCommand, [FromServices] IRepository<SubjectVisit> _subjectVisitReposiotry) public async Task<IResponseOutput> AddSubjectPatientBinding(AddSubjectPatientCommand inCommand, [FromServices] IRepository<SubjectVisit> _subjectVisitReposiotry)
{ {
@ -1317,7 +1317,7 @@ namespace IRaCIS.Application.Services
/// <returns></returns> /// <returns></returns>
[HttpDelete] [HttpDelete]
[UnitOfWork] [UnitOfWork]
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [TrialGlobalLimit("AfterStopCannNotOpt")]
public async Task<IResponseOutput> DeleteSubjectPatientBinding(DeleteSubejctPatientCommand inCommand, public async Task<IResponseOutput> DeleteSubjectPatientBinding(DeleteSubejctPatientCommand inCommand,
@ -1432,7 +1432,7 @@ namespace IRaCIS.Application.Services
/// <param name="inCommandList"></param> /// <param name="inCommandList"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost] [HttpPost]
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [TrialGlobalLimit("AfterStopCannNotOpt")]
[UnitOfWork] [UnitOfWork]
[Obsolete] [Obsolete]
public async Task<IResponseOutput> AddSubjectPatientStudyBinding(List<AddSubjectPatientStudyVisitCommand> inCommandList) public async Task<IResponseOutput> AddSubjectPatientStudyBinding(List<AddSubjectPatientStudyVisitCommand> inCommandList)
@ -1506,7 +1506,7 @@ namespace IRaCIS.Application.Services
/// </summary> /// </summary>
/// <param name="inCommand"></param> /// <param name="inCommand"></param>
/// <returns></returns> /// <returns></returns>
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [TrialGlobalLimit("AfterStopCannNotOpt")]
[UnitOfWork] [UnitOfWork]
public async Task<IResponseOutput> UpdateSubjectVisitStudyBinding(UpdateSubjectVisitStudyBindingCommand inCommand) public async Task<IResponseOutput> UpdateSubjectVisitStudyBinding(UpdateSubjectVisitStudyBindingCommand inCommand)
{ {
@ -1674,7 +1674,7 @@ namespace IRaCIS.Application.Services
/// <returns></returns> /// <returns></returns>
[HttpPost] [HttpPost]
[UnitOfWork] [UnitOfWork]
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [TrialGlobalLimit("AfterStopCannNotOpt")]
public async Task<IResponseOutput> SubmitVisitStudyBinding(SubmitVisitStudyBindingCommand inCommand, public async Task<IResponseOutput> SubmitVisitStudyBinding(SubmitVisitStudyBindingCommand inCommand,
[FromServices] IOptionsMonitor<ServiceVerifyConfigOption> _basicSystemConfigConfig, [FromServices] IOptionsMonitor<ServiceVerifyConfigOption> _basicSystemConfigConfig,
[FromServices] IRepository _repository [FromServices] IRepository _repository
@ -1967,7 +1967,7 @@ namespace IRaCIS.Application.Services
/// <param name="_subjectVisitReposiotry"></param> /// <param name="_subjectVisitReposiotry"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost] [HttpPost]
[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [TrialGlobalLimit("AfterStopCannNotOpt")]
public async Task<IResponseOutput> AddOrUpdateSubjectVisit(AddOrUpdateSubjectVisitCommand incommand, [FromServices] IRepository<SubjectVisit> _subjectVisitReposiotry) public async Task<IResponseOutput> AddOrUpdateSubjectVisit(AddOrUpdateSubjectVisitCommand incommand, [FromServices] IRepository<SubjectVisit> _subjectVisitReposiotry)
{ {
//var verifyExp1 = new EntityVerifyExp<SubjectVisit>() //var verifyExp1 = new EntityVerifyExp<SubjectVisit>()

View File

@ -0,0 +1,62 @@
using AutoMapper;
using EntityFrameworkCore.Triggered;
using IRaCIS.Core.Application.Service;
using IRaCIS.Core.Application.ViewModel;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Infrastructure;
using System.Linq.Expressions;
namespace IRaCIS.Core.Application.Triggers
{
public class VisitTaskIAfterSignTrigger : IAfterSaveTrigger<VisitTask>,IBeforeSaveTrigger<VisitTask>
{
private readonly IRepository<VisitTask> _visitTaskRepository;
private readonly IRepository<Subject> _subjectRepository;
private readonly IUserInfo _userInfo;
private readonly IEmailSendService _emailSendService;
public VisitTaskIAfterSignTrigger(IRepository<VisitTask> visitTaskRepository, IRepository<Subject> subjectRepository, IUserInfo userInfo, IEmailSendService emailSendService)
{
_visitTaskRepository = visitTaskRepository;
_subjectRepository = subjectRepository;
_userInfo = userInfo;
_emailSendService = emailSendService;
}
public async Task AfterSave(ITriggerContext<VisitTask> context, CancellationToken cancellationToken)
{
var visitTask = context.Entity;
if (visitTask.SignTime != null && visitTask.ReadingTaskState == ReadingTaskState.HaveSigned )
{
//任务阅片完成 自动释放
await _visitTaskRepository.BatchUpdateNoTrackingAsync(t => t.SubjectId == visitTask.SubjectId && t.TrialReadingCriterionId==visitTask.TrialReadingCriterionId && t.ReadingTaskState != ReadingTaskState.HaveSigned, t=>new VisitTask() { SubjectCriterionClaimUserId=null});
}
}
public async Task BeforeSave(ITriggerContext<VisitTask> context, CancellationToken cancellationToken)
{
var visitTask = context.Entity;
if (context.ChangeType == ChangeType.Modified && visitTask.ReadingTaskState==ReadingTaskState.HaveSigned && visitTask.ReadingTaskState != context.UnmodifiedEntity.ReadingTaskState && _userInfo.UserTypeEnumInt == (int)UserTypeEnum.PI)
{
visitTask.PIAuditState = PIAuditState.PIAgree;
}
if (context.ChangeType == ChangeType.Modified && visitTask.PIAuditState!=context.UnmodifiedEntity.PIAuditState && visitTask.PIAuditState==PIAuditState.PIAgree)
{
await _emailSendService.SendPIAuditResultAsync(visitTask.Id);
}
}
}
}

View File

@ -54,6 +54,10 @@ namespace IRaCIS.Core.Domain.Share
QCToCRCImageQuestion = 7, QCToCRCImageQuestion = 7,
ClinicalDataQuestion = 8,
PIAuditResutl = 9,
MFALogin = 10, MFALogin = 10,

View File

@ -0,0 +1,32 @@
//--------------------------------------------------------------------
// 此代码由T4模板自动生成 byzhouhang 20210918
// 生成时间 2023-08-22 16:56:15
// 对此文件的更改可能会导致不正确的行为,并且如果重新生成代码,这些更改将会丢失。
using System;
using IRaCIS.Core.Domain.Share;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace IRaCIS.Core.Domain.Models
{
[Table("SubjectVisitClinicalDialog")]
public class SubjectVisitClinicalDialog : BaseAddAuditEntity
{
/// <summary>
/// SubjectVisitId
/// </summary>
[Required]
public Guid SubjectVisitId { get; set; }
/// <summary>
/// Content
/// </summary>
[Required]
public string Content { get; set; }
}
}

View File

@ -123,6 +123,63 @@ namespace IRaCIS.Core.Infra.EFCore.Common
public async Task InsertAddEntitys(List<EntityEntry> entitys) public async Task InsertAddEntitys(List<EntityEntry> entitys)
{ {
#region HIR
#region PI 审核
foreach (var item in entitys.Where(x => x.Entity.GetType() == typeof(PIAudit)))
{
var type = GetEntityAuditOpt(item);
var entity = item.Entity as PIAudit;
await InsertInspection<PIAudit>(item.Entity as PIAudit, type, x => new InspectionConvertDTO()
{
ObjectRelationParentId = entity.VisitTaskId,
IsDistinctionInterface = true
});
}
foreach (var item in entitys.Where(x => x.Entity.GetType() == typeof(SubjectVisitClinicalDialog)))
{
var type = GetEntityAuditOpt(item);
var entity = item.Entity as SubjectVisitClinicalDialog;
await InsertInspection<SubjectVisitClinicalDialog>(item.Entity as SubjectVisitClinicalDialog, type, x => new InspectionConvertDTO()
{
ObjectRelationParentId = entity.SubjectVisitId,
IsDistinctionInterface = false
});
}
// 受试者关联
foreach (var item in entitys.Where(x => x.Entity.GetType() == typeof(SubjectPatient)))
{
var type = GetEntityAuditOpt(item);
var entity = item.Entity as SubjectPatient;
var sCPPatient = await _dbContext.SCPPatient.AsNoTracking().Where(x => x.Id == entity.PatientId).FirstOrDefaultAsync();
var subjectCode = await _dbContext.Subject.AsNoTracking().Where(x => x.Id == entity.SubjectId).Select(x => x.Code).FirstOrDefaultAsync();
await InsertInspection<SubjectPatient>(entity, type, x => new InspectionConvertDTO()
{
SubjectId = x.SubjectId,
//项目的信息 找离的最近的项目稽查信息
ObjectRelationParentId = entity.SubjectId,
ObjectRelationParentId2 = entity.PatientId,
}, new
{
SubjectCode = subjectCode,
PatientName = sCPPatient.PatientName,
PatientIdStr = sCPPatient.PatientIdStr,
});
}
#endregion
#endregion
// 项目 // 项目
foreach (var item in entitys.Where(x => x.Entity.GetType() == typeof(Trial))) foreach (var item in entitys.Where(x => x.Entity.GetType() == typeof(Trial)))
{ {

View File

@ -589,7 +589,7 @@ public class IRaCISDBContext : DbContext
public virtual DbSet<SCPStudySubjectVisit> SCPStudySubjectVisit { get; set; } public virtual DbSet<SCPStudySubjectVisit> SCPStudySubjectVisit { get; set; }
public virtual DbSet<SubjectVisitClinicalDialog> SubjectVisitClinicalDialog { get; set; }
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,48 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace IRaCIS.Core.Infra.EFCore.Migrations
{
/// <inheritdoc />
public partial class AddSecond : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "SubjectVisitClinicalDialog",
columns: table => new
{
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
SubjectVisitId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
Content = table.Column<string>(type: "nvarchar(400)", maxLength: 400, nullable: false),
CreateUserId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
CreateTime = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_SubjectVisitClinicalDialog", x => x.Id);
table.ForeignKey(
name: "FK_SubjectVisitClinicalDialog_User_CreateUserId",
column: x => x.CreateUserId,
principalTable: "User",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_SubjectVisitClinicalDialog_CreateUserId",
table: "SubjectVisitClinicalDialog",
column: "CreateUserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "SubjectVisitClinicalDialog");
}
}
}

View File

@ -8925,6 +8925,32 @@ namespace IRaCIS.Core.Infra.EFCore.Migrations
}); });
}); });
modelBuilder.Entity("IRaCIS.Core.Domain.Models.SubjectVisitClinicalDialog", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("Content")
.IsRequired()
.HasMaxLength(400)
.HasColumnType("nvarchar(400)");
b.Property<DateTime>("CreateTime")
.HasColumnType("datetime2");
b.Property<Guid>("CreateUserId")
.HasColumnType("uniqueidentifier");
b.Property<Guid>("SubjectVisitId")
.HasColumnType("uniqueidentifier");
b.HasKey("Id");
b.HasIndex("CreateUserId");
b.ToTable("SubjectVisitClinicalDialog");
});
modelBuilder.Entity("IRaCIS.Core.Domain.Models.SystemAnonymization", b => modelBuilder.Entity("IRaCIS.Core.Domain.Models.SystemAnonymization", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@ -16594,6 +16620,17 @@ namespace IRaCIS.Core.Infra.EFCore.Migrations
b.Navigation("TrialSite"); b.Navigation("TrialSite");
}); });
modelBuilder.Entity("IRaCIS.Core.Domain.Models.SubjectVisitClinicalDialog", b =>
{
b.HasOne("IRaCIS.Core.Domain.Models.User", "CreateUser")
.WithMany()
.HasForeignKey("CreateUserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("CreateUser");
});
modelBuilder.Entity("IRaCIS.Core.Domain.Models.SystemAnonymization", b => modelBuilder.Entity("IRaCIS.Core.Domain.Models.SystemAnonymization", b =>
{ {
b.HasOne("IRaCIS.Core.Domain.Models.User", "CreateUser") b.HasOne("IRaCIS.Core.Domain.Models.User", "CreateUser")