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; 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) { _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; } [HttpPost] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [UnitOfWork] public async Task> AddOrUpdateSV(SubjectVisitCommand svCommand) { var verifyExp1 = new EntityVerifyExp() { VerifyExp = t => t.VisitNum == svCommand.VisitNum && t.SubjectId == svCommand.SubjectId, //"该患者的检查批次中,在选择的上一检查后已存在计划外的的检查批次,请重新选择上一检查。" VerifyMsg = _localizer["SubjectVisitServiece_ExistOtherInCurrent"] }; var verifyExp2 = new EntityVerifyExp() { VerifyExp = t => t.SubjectId == svCommand.SubjectId && t.IsFinalVisit, // VerifyMsg = "该患者已经有检查批次设置为末次检查批次,不允许将当前检查批次设置为末次检查批次。", IsVerify = svCommand.IsFinalVisit }; var verifyExp3 = new EntityVerifyExp() { VerifyExp = t => t.SubjectId == svCommand.SubjectId && t.VisitName == svCommand.VisitName, //"该患者的检查批次中已经包含一个具有相同名称的检查批次。" VerifyMsg = _localizer["SubjectVisitServiece_ExistName"] }; 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("设置末次评估后,不允许添加计划外检查批次。"); } 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("该患者后续检查批次已有任务完成阅片(有序阅片标准),不允许在此添加,如果确实需要,请回退"); } } dbBeforeEntity = await _subjectVisitRepository.InsertFromDTOAsync(svCommand, false, verifyExp2, verifyExp3,verifyExp1); //var cRCClinicalDataIds = await _clinicalDataTrialSetRepository.Where(x => x.TrialId == svCommand.TrialId && x.UploadRole == UploadRole.IC && 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) { throw new BusinessValidationFailedException("当前检查批次影像提交后,不允许修改PD确认状态。"); } if (svCommand.PDState != dbBeforeEntity.PDState && dbBeforeEntity.RequestBackState == RequestBackStateEnum.PM_AgressBack) { throw new BusinessValidationFailedException("当前检查批次为回退的检查批次,不允许修改PD确认状态。"); } if (svCommand.IsLostVisit) { if (await _subjectVisitRepository.AnyAsync(t => t.Id == svCommand.Id && t.SubmitState == SubmitStateEnum.ToSubmit)) { throw new BusinessValidationFailedException("当前检查批次已经有有影像上传,不允许设置为失访。"); } } 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" })] 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" })] public async Task DeleteSV(Guid id) { if (await _repository.AnyAsync(t => t.SubjectVisitId == id)) { //"当前检查批次已经有影像上传,不允许删除。" return ResponseOutput.NotOk(_localizer["SubjectVisitServiece_ExistImage"]); } if (await _subjectVisitRepository.AnyAsync(t => t.Id == id && t.InPlan)) { return ResponseOutput.NotOk("计划内的检查批次不允许删除。"); } if (await _subjectVisitRepository.AnyAsync(t => t.OutPlanPreviousVisitId == id)) { //"当前检查批次已经被设置为另一检查批次的上一检查批次,不允许删除。" return ResponseOutput.NotOk(_localizer["SubjectVisitServiece_HaveSetBeforeBatch"]); } 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 }).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.InstancePathList = instanceList.Where(t => t.SeriesId == series.Id).OrderBy(t => t.InstanceNumber).Select(k => k.Path).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(); thisVisitTaskStudy.SeriesList.Add(item); thisVisitTaskStudy.SeriesCount = thisVisitTaskStudy.SeriesList.Count(); } result.Add(thisVisitTaskStudy); } } } var studyList = await _repository.Where(t => t.TrialId == indto.TrialId && t.SubjectVisitId == indto.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.WindowCenter,t.WindowWidth }).ToListAsync(); List seriesLists = await _repository.Where(s => studyIds.Contains(s.StudyId) && s.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.InstancePathList = instanceList.Where(t => t.SeriesId == series.Id).OrderBy(t => t.InstanceNumber).Select(k => k.Path).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).FirstOrDefault(); item.SeriesList = new List() { new DicomSeriesDTO (){ IsDicom=false, Id=item.StudyId, InstanceCount=await _noneDicomStudyFileRepository.Where(x=>x.NoneDicomStudyId==item.StudyId).CountAsync(), Modality=item.Modalities, StudyId=item.StudyId, TrialId=nodicom.TrialId, SiteId=nodicom.SiteId, SubjectVisitId=nodicom.SubjectVisitId, SubjectId=nodicom.SubjectId, SeriesNumber=string.Empty, 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; //return ResponseOutput.Ok(studyList.Where(t => t.SeriesList.Count > 0).ToList()); } /// /// 设置患者检查批次已执行 也就是将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(); } } }