using IRaCIS.Core.Application.Filter; using Microsoft.AspNetCore.Mvc; using IRaCIS.Core.Application.Contracts.Dicom.DTO; using Microsoft.AspNetCore.Authorization; using IRaCIS.Core.Domain.Share; using IRaCIS.Core.Application.Contracts; using IRaCIS.Core.Application.Interfaces; using IRaCIS.Core.Infrastructure; using IRaCIS.Core.Application.Auth; using IRaCIS.Core.Infra.EFCore.Common; using MassTransit; namespace IRaCIS.Core.Application.Services { [ApiExplorerSettings(GroupName = "Trial")] public class SubjectVisitService : BaseService, ISubjectVisitService { private readonly IRepository _subjectVisitRepository; private readonly IRepository _clinicalDataTrialSetRepository; private readonly IRepository _readingClinicalDataRepository; private readonly IRepository _readModuleRepository; private readonly IRepository _trialRepository; private readonly IRepository _readingPeriodSetRepository; private readonly IRepository _noneDicomStudyRepository; private readonly IRepository _dicomInstanceRepository; private readonly IRepository _visitTaskRepository; private readonly IRepository _readingTableAnswerRowInfoRepository; private readonly IRepository _noneDicomStudyFileRepository; private readonly IRepository _readingPeriodPlanRepository; private readonly IRepository _subjectRepository; private readonly IRepository _trialReadingCriterionRepository; private readonly IRepository _subjectCriteriaEvaluationVisitStudyFilterRepository; public SubjectVisitService(IRepository subjectVisitRepository, IRepository clinicalDataTrialSetRepository, IRepository readingClinicalDataRepository, IRepository readModuleRepository, IRepository trialRepository, IRepository readingPeriodSetRepository, IRepository noneDicomStudyRepository, IRepository dicomInstanceRepository, IRepository visitTaskRepository, IRepository readingTableAnswerRowInfoRepository, IRepository noneDicomStudyFileRepository, IRepository readingPeriodPlanRepository, IRepository subjectRepository, IRepository trialReadingCriterionRepository, IRepository subjectCriteriaEvaluationVisitStudyFilterRepository ) { _subjectVisitRepository = subjectVisitRepository; this._clinicalDataTrialSetRepository = clinicalDataTrialSetRepository; this._readingClinicalDataRepository = readingClinicalDataRepository; this._readModuleRepository = readModuleRepository; this._trialRepository = trialRepository; this._readingPeriodSetRepository = readingPeriodSetRepository; this._noneDicomStudyRepository = noneDicomStudyRepository; this._dicomInstanceRepository = dicomInstanceRepository; this._visitTaskRepository = visitTaskRepository; this._readingTableAnswerRowInfoRepository = readingTableAnswerRowInfoRepository; this._noneDicomStudyFileRepository = noneDicomStudyFileRepository; this._readingPeriodPlanRepository = readingPeriodPlanRepository; _subjectRepository = subjectRepository; _trialReadingCriterionRepository = trialReadingCriterionRepository; _subjectCriteriaEvaluationVisitStudyFilterRepository = subjectCriteriaEvaluationVisitStudyFilterRepository; } [HttpPost] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [UnitOfWork] //[Authorize(Policy = IRaCISPolicy.CRC)] public async Task> AddOrUpdateSV(SubjectVisitCommand svCommand) { var verifyExp1 = new EntityVerifyExp() { VerifyExp = t => t.VisitNum == svCommand.VisitNum && t.SubjectId == svCommand.SubjectId, //---该受试者的访视计划中已经包含一个具有相同访视号的访视。 VerifyMsg = _localizer["Visit_DuplicateVisitNo"] }; var verifyExp2 = new EntityVerifyExp() { VerifyExp = t => t.SubjectId == svCommand.SubjectId && t.IsFinalVisit, //---该受试者已经有访视设置为末次访视,不允许将当前访视设置为末次访视。 VerifyMsg = _localizer["Visit_LastVisitNoChange"], IsVerify = svCommand.IsFinalVisit }; var verifyExp3 = new EntityVerifyExp() { VerifyExp = t => t.SubjectId == svCommand.SubjectId && t.VisitName == svCommand.VisitName, //---该受试者的访视计划中已经包含一个具有相同访视名称的访视。 VerifyMsg = _localizer["Visit_DuplicateVisitName"] }; svCommand.BlindName = "B" + ((int)(svCommand.VisitNum * 10)).ToString("D3"); svCommand.VisitExecuted = svCommand.IsLostVisit ? VisitExecutedEnum.Executed : svCommand.VisitExecuted; SubjectVisit? dbBeforeEntity = null; //Add if (svCommand.Id == null) { //设置末次评估后,不允许添加计划外访视 if (svCommand.InPlan == false) { if (await _subjectVisitRepository.AnyAsync(t => t.SubjectId == svCommand.SubjectId && t.IsFinalVisit)) { //---设置末次评估后,不允许添加计划外访视。 throw new BusinessValidationFailedException(_localizer["Visit_NoExtraVisitAfterLast"]); } if (await _repository.AnyAsync(t => t.SubjectId == svCommand.SubjectId && t.TaskState == TaskState.Effect && t.VisitTaskNum > svCommand.VisitNum && t.SignTime != null && t.TrialReadingCriterion.IsReadingTaskViewInOrder)) { //---该受试者后续访视已有任务完成阅片(有序阅片标准),不允许在此添加,如果确实需要,请回退 throw new BusinessValidationFailedException(_localizer["Visit_FinishedTasksNoAdd"]); } } dbBeforeEntity = await _subjectVisitRepository.InsertFromDTOAsync(svCommand, false, verifyExp1, verifyExp2, verifyExp3); //var cRCClinicalDataIds = await _clinicalDataTrialSetRepository.Where(x => x.TrialId == svCommand.TrialId && x.UploadRole == UploadRole.CRC && x.IsConfirm && x.ClinicalDataLevel == ClinicalLevel.SubjectVisit) //.Select(x => x.Id).ToListAsync(); //List readingClinicals = cRCClinicalDataIds.Select(x => new ReadingClinicalData() //{ // ClinicalDataTrialSetId = x, // IsVisit = true, // SubjectId = svCommand.SubjectId, // ReadingId = dbBeforeEntity.Id, // TrialId = svCommand.TrialId //}).ToList(); //await _readingClinicalDataRepository.AddRangeAsync(readingClinicals); } else { dbBeforeEntity = await _subjectVisitRepository.UpdateFromDTOAsync(svCommand, false, false, verifyExp1, verifyExp2, verifyExp3); if (svCommand.PDState != dbBeforeEntity.PDState && dbBeforeEntity.SubmitState == SubmitStateEnum.Submitted) { //---当前访视影像提交后,不允许修改PD确认状态。 throw new BusinessValidationFailedException(_localizer["Visit_NoPDStatusChangeAfterSubmission"]); } if (svCommand.PDState != dbBeforeEntity.PDState && dbBeforeEntity.RequestBackState == RequestBackStateEnum.PM_AgressBack) { //---当前访视影像提交后,不允许修改PD确认状态。 throw new BusinessValidationFailedException(_localizer["Visit_NoPDStatusChangeAfterSubmission"]); } if (svCommand.IsLostVisit) { if (await _subjectVisitRepository.AnyAsync(t => t.Id == svCommand.Id && t.SubmitState == SubmitStateEnum.ToSubmit)) { //---当前访视已经有影像上传,不允许设置为失访。 throw new BusinessValidationFailedException(_localizer["Visit_UploadedNoLost"]); } } dbBeforeEntity = await _subjectVisitRepository.UpdateFromDTOAsync(svCommand, true, false, verifyExp1, verifyExp2, verifyExp3); } //更新受试者 访视基准日期 是否入组确认 if (svCommand.SubjectFirstGiveMedicineTime != null && svCommand.IsBaseLine) { await _subjectRepository.UpdatePartialFromQueryAsync(svCommand.SubjectId, t => new Subject() { FirstGiveMedicineTime = svCommand.SubjectFirstGiveMedicineTime }); } await _subjectVisitRepository.SaveChangesAsync(); return ResponseOutput.Ok(dbBeforeEntity.Id.ToString()); } [HttpPut("{trialId:guid}/{subjectVisitId:guid}/{isUrgent:bool}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] //[Authorize(Policy = IRaCISPolicy.PM_IQC)] public async Task SetSubjectVisitUrgent(Guid subjectVisitId, bool isUrgent) { await _subjectVisitRepository.UpdatePartialFromQueryAsync(subjectVisitId, u => new SubjectVisit() { IsUrgent = isUrgent }, true); return ResponseOutput.Ok(); } [HttpDelete, Route("{trialId:guid}/{id:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] //[Authorize(Policy = IRaCISPolicy.CRC)] public async Task DeleteSV(Guid id) { if (await _repository.AnyAsync(t => t.SubjectVisitId == id)) { //---当前访视已经有影像上传,不允许删除。 return ResponseOutput.NotOk(_localizer["Visit_UploadedNoDelete"]); } if (await _subjectVisitRepository.AnyAsync(t => t.Id == id && t.InPlan)) { //---计划内的访视不允许删除。 return ResponseOutput.NotOk(_localizer["Visit_PlanVisitNoDelete"]); } if (await _subjectVisitRepository.AnyAsync(t => t.OutPlanPreviousVisitId == id)) { //---当前访视已经被设置为另一访视的上一访视,不允许删除。 return ResponseOutput.NotOk(_localizer["Visit_PreviousVisitNoDelete"]); } await _subjectVisitRepository.DeleteFromQueryAsync(s => s.Id == id, true); return ResponseOutput.Ok(); } /// /// 获取访视下的Dicom 检查信息 分所有的, 阅片的 不阅片 isReading : 0 查询所有 1 查询仅仅阅片的 /// /// /// /// /// [HttpGet, Route("{trialId:guid}/{sujectVisitId:guid}/{isReading}")] [AllowAnonymous] public async Task> GetVisitStudyList(Guid trialId, Guid sujectVisitId, int isReading) { var studyList = await _repository.Where(t => t.TrialId == trialId && t.SubjectVisitId == sujectVisitId).Select(k => new VisitStudyDTO() { InstanceCount = k.InstanceCount, Modalities = k.Modalities, SeriesCount = k.SeriesCount, StudyCode = k.StudyCode, StudyId = k.Id }).ToListAsync(); var studyIds = studyList.Select(t => t.StudyId).ToList(); var instanceList = await _repository.Where(t => studyIds.Contains(t.StudyId)) .Select(t => new { t.SeriesId, t.Id, t.InstanceNumber, t.Path, t.NumberOfFrames,t.HtmlPath }).ToListAsync(); foreach (var t in studyList) { t.SeriesList = await _repository.Where(s => s.StudyId == t.StudyId) .WhereIf(isReading == 1, s => s.IsReading).OrderBy(s => s.SeriesNumber).ThenBy(s => s.SeriesTime) .ProjectTo(_mapper.ConfigurationProvider).ToListAsync(); t.SeriesList.ForEach(series => { series.InstanceList = instanceList.Where(t => t.SeriesId == series.Id).OrderBy(t => t.InstanceNumber).Select(k => k.Id).ToList(); series.InstanceHtmlPathList = instanceList.Where(t => t.SeriesId == series.Id && t.HtmlPath != string.Empty).OrderBy(t => t.InstanceNumber).Select(k => k.HtmlPath).ToList(); //处理多帧 series.InstancePathList = instanceList.OrderBy(t => t.InstanceNumber).Where(s => s.SeriesId == series.Id) .SelectMany(u => { if (u.NumberOfFrames > 1) { var pathList = new List(); for (int i = 1; i <= u.NumberOfFrames; i++) { pathList.Add(u.Path + "?frame=" + (i - 1)); } return pathList; } else { return new List { u.Path }; } }) .ToList(); } ); //设置为阅片与否 不更改数据库检查 的instance数量 和 SeriesCount 所以这里要实时统计 t.SeriesCount = t.SeriesList.Count; t.InstanceCount = t.SeriesList.SelectMany(t => t.InstanceList).Count(); } return studyList; //return ResponseOutput.Ok(studyList.Where(t => t.SeriesList.Count > 0).ToList()); } /// /// 获取访视下的Dicom 检查信息 分所有的, 阅片的 不阅片 isReading : 0 查询所有 1 查询仅仅阅片的 /// /// /// [HttpPost] public async Task> GetReadingVisitStudyList(GetReadingVisitStudyListIndto indto) { var result = new List(); var thisRowinfo = await _readingTableAnswerRowInfoRepository.Where(x => x.VisitTaskId == indto.VisitTaskId && x.StudyId != null).OrderBy(x => x.ReadingQuestionTrial.ShowOrder).ThenBy(x => x.RowIndex).Select(x => new { x.ReadingQuestionTrial.ShowOrder, x.RowIndex, x.SeriesId, x.StudyId, x.InstanceId, }).ToListAsync(); var taskInfo = await _visitTaskRepository.Where(x => x.Id == indto.VisitTaskId).FirstNotNullAsync(); if (taskInfo.ReadingTaskState == ReadingTaskState.HaveSigned) { var thisStudyIds = thisRowinfo.OrderBy(x => x.ShowOrder).ThenBy(x => x.RowIndex).Select(x => x.StudyId).Distinct().ToList(); var thisSeriesIdIds = thisRowinfo.Where(x => x.SeriesId != null).OrderBy(x => x.ShowOrder).ThenBy(x => x.RowIndex).Select(x => x.SeriesId).Distinct().ToList(); if (thisRowinfo.Count > 0) { var thisVisitTaskStudy = await _repository.Where(t => thisStudyIds.Contains(t.Id)).Select(k => new VisitStudyDTO() { InstanceCount = k.InstanceCount, SeriesCount = k.SeriesCount, StudyId = k.Id, IsCriticalSequence = true, }).FirstOrDefaultAsync(); if (thisVisitTaskStudy != null) { thisVisitTaskStudy.StudyId = default(Guid); var item = await _repository.Where(s => thisSeriesIdIds.Contains(s.Id)).OrderBy(s => s.SeriesNumber). ThenBy(s => s.SeriesTime) .ProjectTo(_mapper.ConfigurationProvider).FirstOrDefaultAsync(); if (item != null) { item.SeriesInstanceUid = string.Empty; item.InstanceList = thisRowinfo.Where(y => y.InstanceId != null).OrderBy(x => x.ShowOrder).ThenBy(x => x.RowIndex).Select(y => y.InstanceId!.Value).Distinct().ToList(); var tempInstanceList = await _repository.Where(t => item.InstanceList.Contains(t.Id)).OrderBy(t => t.InstanceNumber) .Select(t => new TempInstance { Id = t.Id, Path = t.Path, NumberOfFrames = t.NumberOfFrames, InstanceNumber = t.InstanceNumber }).ToListAsync(); tempInstanceList.ForEach(x => { var item = thisRowinfo.FirstOrDefault(y => y.InstanceId == x.Id); if (item != null) { x.ShowOrder = item.ShowOrder; x.RowIndex = item.RowIndex; } }); item.InstancePathList = tempInstanceList.OrderBy(x => x.ShowOrder).ThenBy(x => x.RowIndex).SelectMany(u => { if (u.NumberOfFrames > 1) { var pathList = new List(); for (int i = 1; i <= u.NumberOfFrames; i++) { pathList.Add(u.Path + "?frame=" + (i - 1)); } return pathList; } else { return new List { u.Path }; } }) .ToList(); item.InstanceCount = item.InstanceList.Count; item.Description = "Key Series"; var modalityList = await _repository.Where(s => thisSeriesIdIds.Contains(s.Id)).OrderBy(s => s.SeriesNumber). ThenBy(s => s.SeriesTime).Select(x => x.Modality).Distinct().ToListAsync(); ; item.Modality = string.Join(",", modalityList); thisVisitTaskStudy.SeriesList.Add(item); thisVisitTaskStudy.SeriesCount = thisVisitTaskStudy.SeriesList.Count; } result.Add(thisVisitTaskStudy); } } } //如果是手动生成的标准,需要过滤检查和序列数据 var isManualGenerate = await _trialReadingCriterionRepository.AnyAsync(t => t.Id == taskInfo.TrialReadingCriterionId && t.IsAutoCreate == false); var studyList = await _repository.Where(t => t.TrialId == indto.TrialId && t.SubjectVisitId == indto.SujectVisitId) .WhereIf(isManualGenerate,t=>t.SubjectCriteriaEvaluationVisitStudyFilterList.Any(t=>t.TrialReadingCriterionId==taskInfo.TrialReadingCriterionId && t.IsConfirmed &&t.IsReading )) .Select(k => new VisitStudyDTO() { InstanceCount = k.InstanceCount, Modalities = k.Modalities, SeriesCount = k.SeriesCount, StudyCode = k.StudyCode, StudyId = k.Id, }).ToListAsync(); var studyIds = studyList.Select(t => t.StudyId).ToList(); var instanceList = await _repository.Where(t => studyIds.Contains(t.StudyId)) .Select(t => new { t.SeriesId, t.Id, t.InstanceNumber, t.Path, t.NumberOfFrames,t.WindowCenter,t.WindowWidth,t.HtmlPath }).ToListAsync(); List seriesLists = await _repository.Where(s => studyIds.Contains(s.StudyId) /*&& s.IsReading*/) .WhereIf(isManualGenerate==false, t => t.IsReading) .WhereIf(isManualGenerate, t => t.SubjectCriteriaEvaluationVisitStudyFilterList.Any(t => t.TrialReadingCriterionId == taskInfo.TrialReadingCriterionId && t.IsConfirmed && t.IsReading)) .OrderBy(s => s.SeriesNumber). ThenBy(s => s.SeriesTime) .ProjectTo(_mapper.ConfigurationProvider).ToListAsync(); foreach (var t in studyList) { t.SeriesList = seriesLists.Where(s => s.StudyId == t.StudyId).OrderBy(s => s.SeriesNumber). ThenBy(s => s.SeriesTime).ToList(); t.SeriesList.ForEach(series => { series.InstanceList = instanceList.Where(t => t.SeriesId == series.Id).OrderBy(t => t.InstanceNumber).Select(k => k.Id).ToList(); series.InstanceHtmlPathList = instanceList.Where(t => t.SeriesId == series.Id && t.HtmlPath!=string.Empty).OrderBy(t => t.InstanceNumber).Select(k => k.HtmlPath).ToList(); //处理多帧 series.InstancePathList = instanceList.Where(s => s.SeriesId == series.Id).OrderBy(t => t.InstanceNumber) .SelectMany(u => { if (u.NumberOfFrames > 1) { var pathList = new List(); for (int i = 1; i <= u.NumberOfFrames; i++) { pathList.Add(u.Path + "?frame=" + (i - 1)); } return pathList; } else { return new List { u.Path }; } }) .ToList(); series.WindowWidth = instanceList.FirstOrDefault()!.WindowWidth; series.WindowCenter = instanceList.FirstOrDefault()!.WindowCenter; }); //设置为阅片与否 不更改数据库检查 的instance数量 和 SeriesCount 所以这里要实时统计 t.SeriesCount = t.SeriesList.Count; t.InstanceCount = t.SeriesList.SelectMany(t => t.InstanceList).Count(); } // 非Dicom var noDicomList = await _noneDicomStudyRepository.Where(x => x.TrialId == indto.TrialId && x.SubjectVisitId == indto.SujectVisitId).ToListAsync(); List noDicomStudyList = noDicomList.Select(x => new VisitStudyDTO() { InstanceCount = x.FileCount, StudyId = x.Id, Modalities = x.Modality, SeriesCount = 1, StudyCode = x.StudyCode, IsDicom = false, }).ToList(); foreach (var item in noDicomStudyList) { var nodicom = noDicomList.Where(x => x.Id == item.StudyId).First(); var instanceCount = await _noneDicomStudyFileRepository.Where(x => x.NoneDicomStudyId == item.StudyId).CountAsync(); if (instanceCount == 0) { item.SeriesList = new List(); } else { item.SeriesList = new List() { new DicomSeriesDTO (){ IsDicom=false, Id=item.StudyId, InstanceCount=instanceCount, Modality=item.Modalities, StudyId=item.StudyId, TrialId=nodicom.TrialId, SiteId=nodicom.SiteId, SubjectVisitId=nodicom.SubjectVisitId, SubjectId=nodicom.SubjectId, SeriesNumber=1, NoneDicomFileFirstFile=await _noneDicomStudyFileRepository.Where(x=>x.NoneDicomStudyId==item.StudyId).Select(x=>x.Path).FirstOrDefaultAsync(), } }; } } if (studyList == null || studyList.Count == 0) { studyList = new List(); } studyList.AddRange(noDicomStudyList); studyList.ForEach(x => { x.SeriesList.ForEach(y => { y.IsBeMark = thisRowinfo.Any(z => z.SeriesId == y.Id); }); }); result.AddRange(studyList); result = result.Where(x => x.SeriesCount > 0).ToList(); return result; } /// /// 设置受试者访视已执行 也就是将studyUploaded状态置为true 为了那些没有影像 人工设置准备 /// /// /// [HttpPut("{trialId:guid}/{subjectVisitId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [Obsolete] public async Task SetSVExecuted(Guid subjectVisitId) { await _subjectVisitRepository.UpdatePartialFromQueryAsync(subjectVisitId, u => new SubjectVisit() { VisitExecuted = VisitExecutedEnum.Executed }, true); return ResponseOutput.Ok(); } } }