using IRaCIS.Core.Application.Contracts; using IRaCIS.Core.Application.Contracts.DTO; using IRaCIS.Core.Application.Filter; using IRaCIS.Core.Domain.Share; using MediatR; using Microsoft.AspNetCore.Mvc; using System.Data; using Microsoft.AspNetCore.Authorization; using Newtonsoft.Json; using IRaCIS.Core.Infrastructure; using IRaCIS.Core.Application.Service.Inspection.DTO; using Nito.AsyncEx; using System.ComponentModel.DataAnnotations; using IRaCIS.Core.Application.Auth; using IRaCIS.Core.Application.Helper; using IRaCIS.Core.Application.ViewModel; using IRaCIS.Core.Domain.Models; using IRaCIS.Core.Application.Service; namespace IRaCIS.Core.Application.Image.QA { [ApiExplorerSettings(GroupName = "Image")] public class QCOperationService : BaseService, IQCOperationService { private readonly IRepository _subjectVisitRepository; private readonly IRepository _qcChallengeRepository; private readonly IRepository _dicomStudyRepository; private readonly IRepository _dicomSeriesrepository; private readonly IRepository _subjectRepository; private readonly IRepository _readingClinicalDataRepository; private readonly IRepository _qCChallengeDialogrepository; private readonly IRepository _checkChallengeDialogrepository; private readonly IRepository _trialRepository; private readonly IRepository _visitTaskRepository; private readonly IVisitTaskHelpeService _IVisitTaskHelpeService; private readonly AsyncLock _mutex = new AsyncLock(); public QCOperationService(IRepository subjectVisitRepository, IRepository qcChallengeRepository, IRepository trialRepository, IRepository visitTaskRepository, IRepository dicomStudyRepository, IRepository dicomSeriesrepository, IRepository subjectRepository, IRepository readingClinicalDataRepository, IRepository qCChallengeDialogrepository, IRepository checkChallengeDialogrepository, IVisitTaskHelpeService visitTaskHelpeService ) { _subjectVisitRepository = subjectVisitRepository; _qcChallengeRepository = qcChallengeRepository; _dicomStudyRepository = dicomStudyRepository; this._dicomSeriesrepository = dicomSeriesrepository; this._subjectRepository = subjectRepository; this._readingClinicalDataRepository = readingClinicalDataRepository; this._qCChallengeDialogrepository = qCChallengeDialogrepository; this._checkChallengeDialogrepository = checkChallengeDialogrepository; _trialRepository = trialRepository; this._visitTaskRepository = visitTaskRepository; _IVisitTaskHelpeService = visitTaskHelpeService; } #region QC质疑 以及回复 关闭 [HttpGet("{trialId:guid}/{subjectVisitId:guid}/{currentQCType:int}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [Authorize(Policy = IRaCISPolicy.IQC)] public async Task VerifyQCCanAddChallenge(Guid subjectVisitId, [FromRoute] CurrentQC currentQCType) { await VerifyIsCanQCAsync(null, subjectVisitId); if (!await _repository.AnyAsync(t => t.SubjectVisitId == subjectVisitId && t.CurrentQCEnum == currentQCType)) { return ResponseOutput.NotOk("请先核查图像,并保存审核问题,然后再发质疑。"); } return ResponseOutput.Ok(); } /// /// 添加和更新质疑 /// /// /// /// /// /// [HttpPost("{trialId:guid}/{trialQCProcess:int}/{currentQCType:int}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [Authorize(Policy = IRaCISPolicy.IQC)] public async Task AddOrUpdateQCChallenge(QCChallengeCommand qaQuestionCommand, Guid trialId, [FromRoute] TrialQCProcess trialQCProcess, [FromRoute] CurrentQC currentQCType) { await VerifyIsCanQCAsync(null, qaQuestionCommand.SubjectVisitId); if (qaQuestionCommand.Id == null) { if (await _qcChallengeRepository.AnyAsync(t => t.IsClosed == false && t.SubjectVisitId == qaQuestionCommand.SubjectVisitId && t.ReuploadEnum == QCChanllengeReuploadEnum.QCAgreeUpload)) { throw new BusinessValidationFailedException("当前检查批次未关闭的质疑已设置了同意CRC重传影像。请在CRC完成影像重传后,先关闭原质疑,再添加新的质疑。"); } var trialConfig = (await _trialRepository.Where(t => t.Id == trialId).Select(t => new { TrialId = t.Id, t.QCProcessEnum, t.IsImageConsistencyVerification }).FirstOrDefaultAsync()).IfNullThrowException(); using (await _mutex.LockAsync()) { //获取编号 var code = _qcChallengeRepository.Where(t => t.TrialId == trialId).Select(t => t.Code).DefaultIfEmpty().Max(); var qcChallenge = _mapper.Map(qaQuestionCommand); qcChallenge.QCProcessEnum = trialConfig.QCProcessEnum; qcChallenge.CurrentQCEnum = currentQCType; qcChallenge.TrialId = trialId; qcChallenge.Code = code + 1; qcChallenge.UserTypeEnum = (UserTypeEnum)_userInfo.UserTypeEnumInt; qcChallenge.ChallengeCode = AppSettings.GetCodeStr(qcChallenge.Code, nameof(QCChallenge)); qcChallenge = await _qcChallengeRepository.AddAsync(qcChallenge, true); return ResponseOutput.Ok(qcChallenge.Id); } } else { await _repository.UpdateFromDTOAsync(qaQuestionCommand, true); return ResponseOutput.Ok(); } } /// /// 关闭质疑,什么情况下允许? /// /// [HttpPut] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [UnitOfWork] [Authorize(Policy = IRaCISPolicy.IQC)] public async Task CloseQCChallenge(CloseQCChallengeInDto input) { if (_userInfo.UserTypeEnumInt == (int)UserTypeEnum.IQC) { await VerifyIsCanQCAsync(null, input.subjectVisitId); } var dbQCChallenge = (await _qcChallengeRepository.FirstOrDefaultAsync(t => t.Id == input.qcChallengeId)).IfNullThrowException(); if (dbQCChallenge.ReuploadEnum == QCChanllengeReuploadEnum.CRCRequestReupload || dbQCChallenge.ReuploadEnum == QCChanllengeReuploadEnum.QCAgreeUpload) { throw new BusinessValidationFailedException("CRC已申请重传或者QC同意重传,不允许关闭该质疑。请在QC拒绝重传申请或者CRC设置重传影像后,再关闭质疑。"); } #region 之前 dbQCChallenge.CloseResonEnum = input.closeEnum; dbQCChallenge.IsClosed = true; dbQCChallenge.ClosedTime = DateTime.Now; dbQCChallenge.DialogList.Add(new QCChallengeDialog() { SubjectVisitId = dbQCChallenge.SubjectVisitId, UserTypeEnum = (UserTypeEnum)_userInfo.UserTypeEnumInt, QCChallengeId = dbQCChallenge.Id, TalkContent = "关闭原因: " + input.closeReason }); var success = await _qcChallengeRepository.SaveChangesAsync(); #endregion return ResponseOutput.Result(success); } /// /// 删除QC质疑记录 /// /// [HttpDelete("{trialId:guid}/{subjectVisitId:guid}/{qcChallengeId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [Authorize(Policy = IRaCISPolicy.IQC)] public async Task DeleteQCChallenge(Guid qcChallengeId) { if (await _repository.AnyAsync(t => t.QCChallengeId == qcChallengeId)) { ResponseOutput.NotOk("当前QC质疑已经回复。"); } var qaRecord = (await _qcChallengeRepository.FirstOrDefaultAsync(t => t.Id == qcChallengeId)).IfNullThrowException(); await _qcChallengeRepository.DeleteAsync(qaRecord); var success1 = await _repository.SaveChangesAsync(); return ResponseOutput.Result(success1 /*|| success2 || success3*/); } /// /// 针对 某条QC质疑 添加回复 /// /// /// [HttpPost("{trialId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [Authorize(Policy = IRaCISPolicy.CRC_IQC)] public async Task AddQCChallengeReply(QADialogCommand qaDialogCommand) { var qaReply = _mapper.Map(qaDialogCommand); await _qCChallengeDialogrepository.AddAsync(qaReply); qaReply.UserTypeEnum = (UserTypeEnum)_userInfo.UserTypeEnumInt; var dbQCChallenge = (await _repository.FirstOrDefaultAsync(t => t.Id == qaDialogCommand.QCChallengeId)).IfNullThrowException(); dbQCChallenge.LatestMsgTime = DateTime.Now; dbQCChallenge.LatestReplyUserId = _userInfo.Id; var success = await _repository.SaveChangesAsync(); return ResponseOutput.Result(success, qaReply); } #endregion #region 一致性核查 /// /// 一致性核查 质疑的添加/回复 /// /// /// [HttpPost("{trialId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task AddCheckChallengeReply(CheckChallengeDialogCommand checkDialogCommand) { //修改一致性核查 质疑状态 var sv = (await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == checkDialogCommand.SubjectVisitId)).IfNullThrowException(); var qaReply = _mapper.Map(checkDialogCommand); qaReply.UserTypeEnum = (UserTypeEnum)_userInfo.UserTypeEnumInt; await _repository.AddAsync(qaReply); if (_userInfo.UserTypeEnumInt == (int)UserTypeEnum.ClinicalResearchCoordinator) { sv.CheckChallengeState = CheckChanllengeTypeEnum.CRCWaitPMReply; } else if (_userInfo.UserTypeEnumInt == (int)UserTypeEnum.ProjectManager || _userInfo.UserTypeEnumInt != (int)UserTypeEnum.APM) { sv.CheckChallengeState = CheckChanllengeTypeEnum.PMWaitCRCReply; } else { throw new BusinessValidationFailedException("您没有权限回复一致性核查对话。"); } var success = await _repository.SaveChangesAsync(); return ResponseOutput.Result(success, qaReply.Id); } /// /// 关闭 一致性核查质疑 /// /// [HttpPut("{trialId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task CloseCheckChallenge(CloseCheckChallengeDto input) { var sv = (await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == input.subjectVisitId)).IfNullThrowException(); if (sv.RequestBackState == RequestBackStateEnum.CRC_RequestBack) { ResponseOutput.NotOk("当前检查批次处于申请回退状态, 不允许关闭质疑。"); } sv.CloseTheReason = input.CloseCheckChallenge; sv.CheckChallengeState = CheckChanllengeTypeEnum.Closed; await _checkChallengeDialogrepository.AddAsync(new CheckChallengeDialog() { SubjectVisitId = input.subjectVisitId, TalkContent = "原因:" + input.CloseCheckChallenge, UserTypeEnum = (UserTypeEnum)_userInfo.UserTypeEnumInt, }); await _repository.SaveChangesAsync(); return ResponseOutput.Ok(sv); } /// /// 手动设置一致性核查通过 /// /// [HttpPut("{trialId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [UnitOfWork] public async Task SetCheckPass(SetCheckPassDt data) { //if (_userInfo.UserTypeEnumInt != (int)UserTypeEnum.ProjectManager && _userInfo.UserTypeEnumInt != (int)UserTypeEnum.APM) //{ // ResponseOutput.NotOk("您不是PM/APM,不允许设置一致性核查通过"); //} var sv = (await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == data.Id)).IfNullThrowException(); if (sv.RequestBackState == RequestBackStateEnum.PM_AgressBack) { ResponseOutput.NotOk("当前检查批次处于回退状态,不允许设置一致性核查通过"); } if (sv.CheckChallengeState != CheckChanllengeTypeEnum.Closed && sv.AuditState == AuditStateEnum.QCPassed) { ResponseOutput.NotOk("请先关闭一致性核查质疑后,再设置一致性核查通过。"); } sv.CheckUserId = _userInfo.Id; sv.CheckState = CheckStateEnum.CVPassed; sv.ReadingStatus = ReadingStatusEnum.TaskAllocate; sv.ForwardState = ForwardStateEnum.ToForward; sv.ManualPassReason = data.ManualPassReason; sv.CheckPassedTime = DateTime.Now; await _subjectVisitRepository.SaveChangesAsync(); return ResponseOutput.Result(true); } /// /// CRC 请求回退 /// /// /// [HttpPut("{trialId:guid}/{subjectVisitId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task CRCRequstCheckBack(Guid subjectVisitId) { var sv = (await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == subjectVisitId)).IfNullThrowException(); if (sv.CheckState == CheckStateEnum.CVPassed) { return ResponseOutput.NotOk("当前检查批次已通过一致性核查,不允许申请回退"); } var QCChallengeId = await _qcChallengeRepository.Where(x => x.SubjectVisitId == subjectVisitId).Select(x => x.Id).FirstOrDefaultAsync(); await _checkChallengeDialogrepository.AddAsync(new CheckChallengeDialog() { SubjectVisitId = subjectVisitId, UserTypeEnum = (UserTypeEnum)_userInfo.UserTypeEnumInt, TalkContent = "CRC申请回退" }); if (sv.RequestBackState == RequestBackStateEnum.NotRequest || sv.RequestBackState == RequestBackStateEnum.PM_NotAgree) { sv.RequestBackState = RequestBackStateEnum.CRC_RequestBack; await _subjectVisitRepository.SaveChangesAsync(); } else { return ResponseOutput.NotOk("其他CRC已申请处理,请刷新页面"); } return ResponseOutput.Ok(); } [HttpPut("{trialId:guid}/{subjectVisitId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task RejectCheckBack(Guid subjectVisitId) { //if (_userInfo.UserTypeEnumInt != (int)UserTypeEnum.ProjectManager && _userInfo.UserTypeEnumInt != (int)UserTypeEnum.APM) //{ // return ResponseOutput.NotOk(" 只有PM/APM具有操作权限!"); //} var sv = (await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == subjectVisitId)).IfNullThrowException(); if (sv.RequestBackState == RequestBackStateEnum.CRC_RequestBack) { sv.RequestBackState = RequestBackStateEnum.PM_NotAgree; await _repository.AddAsync(new CheckChallengeDialog() { SubjectVisitId = subjectVisitId, TalkContent = "PM/APM拒绝一致性核查回退", UserTypeEnum = (UserTypeEnum)_userInfo.UserTypeEnumInt }); await _subjectVisitRepository.SaveChangesAsync(); return ResponseOutput.Ok(); } else { return ResponseOutput.NotOk("您不具备操作权限。"); } } /// /// 一致性核查 回退 对话记录不清除 只允许PM回退 [签名 不需要对] /// /// [HttpPut("{trialId:guid}/{subjectVisitId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [UnitOfWork] public async Task CheckBack(Guid subjectVisitId) { //if (_userInfo.UserTypeEnumInt != (int)UserTypeEnum.ProjectManager && _userInfo.UserTypeEnumInt != (int)UserTypeEnum.APM) //{ // return ResponseOutput.NotOk("只有PM/APM具有操作权限!"); //} var sv = (await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == subjectVisitId)).IfNullThrowException(); if (sv.CheckState == CheckStateEnum.CVPassed || sv.CheckState == CheckStateEnum.ToCheck) { return ResponseOutput.NotOk("当前检查批次还未进行核查或者核查已通过,不允许设置回退。"); } //sv.CheckChallengeState = (int)CheckChanllengeTypeEnum.None; //sv.CheckState = CheckStateEnum.None; //sv.ChallengeState = (int)ChallengeStateEnum.No; sv.AuditState = AuditStateEnum.None; sv.SubmitState = SubmitStateEnum.ToSubmit; sv.ReadingStatus = ReadingStatusEnum.ImageNotSubmit; //回退后,回退状态恢复 sv.RequestBackState = RequestBackStateEnum.NotRequest; sv.IsCheckBack = true; sv.CheckBackTime = DateTime.Now; sv.CheckState = CheckStateEnum.None; sv.CheckChallengeState = CheckChanllengeTypeEnum.None; sv.SVENDTC = null; sv.SVSTDTC = null; sv.PreliminaryAuditTime = null; sv.SubmitTime = null; sv.ReviewAuditTime = null; sv.CurrentActionUserExpireTime = null; sv.IsTake = false; sv.CurrentActionUserId = null; sv.PreliminaryAuditUserId = null; sv.ReviewAuditUserId = null; //var success1 = _studyRepository.Delete(t => t.SubjectVisitId == subjectVisitId); //var succeess2 = _instanceRepository.Delete(t => t.SubjectVisitId == subjectVisitId); //var success3 = _seriesRepository.Delete(t => t.SubjectVisitId == subjectVisitId); //_qcChallengeRepository.Delete(t => t.SubjectVisitId == subjectVisitId); //_qcChallengeDialogRepository.Delete(t => t.SubjectVisitId == subjectVisitId); //_checkChallengeDialogRepository.Delete(t => t.SubjectVisitId == subjectVisitId); await _repository.AddAsync(new CheckChallengeDialog() { SubjectVisitId = subjectVisitId, TalkContent = "PM/APM同意一致性核查回退。", UserTypeEnum = (UserTypeEnum)_userInfo.UserTypeEnumInt }); await _repository.BatchDeleteAsync(t => t.SubjectVisitId == subjectVisitId); await _repository.BatchDeleteAsync(t => t.DicomSerie.IsDeleted); await _repository.BatchDeleteAsync(t => t.IsDeleted); var success = await _subjectVisitRepository.SaveChangesAsync(); return ResponseOutput.Result(success); } #endregion #region QC 核对问题 操作检查各种操作 /// /// 添加或者更新 QC核对问题列表 两个人不能同时操作,就算意外进去了,提交数据,也不会覆盖前一个人数据, 后台已经做好判断 /// [HttpPost("{trialId:guid}/{subjectVisitId:guid}/{trialQCProcess:int}/{currentQCType:int}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task AddOrUpdateQCQuestionAnswerList(QCQuestionAnswerCommand[] qcQuestionAnswerCommands, Guid trialId, Guid subjectVisitId, [FromRoute] TrialQCProcess trialQCProcess, [FromRoute] CurrentQC currentQCType) { //验证是否能操作 await VerifyIsCanQCAsync(null, subjectVisitId); //更新 if (qcQuestionAnswerCommands.Any(t => t.Id != null)) { #region 先删除再添加 //await _repository.DeleteFromQueryAsync(t => t.SubjectVisitId == subjectVisitId && t.QCProcessEnum == trialQCProcess && t.CurrentQCEnum == currentQCType); //var addlist = _mapper.Map>(qcQuestionAnswerCommands); //addlist.ForEach(t => { t.TrialId = trialId; t.SubjectVisitId = subjectVisitId; t.CurrentQCEnum = currentQCType; t.QCProcessEnum = trialQCProcess; }); //await _repository.AddRangeAsync(addlist); #endregion #region 先查询再更新 var questionAnswerList = await _repository.Where(t => t.SubjectVisitId == subjectVisitId && t.QCProcessEnum == trialQCProcess && t.CurrentQCEnum == currentQCType, true).ToListAsync(); qcQuestionAnswerCommands.ToList().ForEach(t => { var temp = questionAnswerList.FirstOrDefault(u => u.Id == t.Id); if (temp != null) { temp.Answer = t.Answer; //temp.ChildAnswer = t.ChildAnswer; } }); //Automapper 映射有问题 automapper 会把Guid? 类型的null 值转为 guid.Empty 导致映射错误 //_mapper.Map(qcQuestionAnswerCommands, questionAnswerList); #endregion return ResponseOutput.Ok(await _repository.SaveChangesAsync()); } else { var addlist = _mapper.Map>(qcQuestionAnswerCommands); addlist.ForEach(t => { t.TrialId = trialId; t.SubjectVisitId = subjectVisitId; t.CurrentQCEnum = currentQCType; t.QCProcessEnum = trialQCProcess; }); await _repository.AddRangeAsync(addlist); return ResponseOutput.Result(await _repository.SaveChangesAsync()); } } /// /// 1、设置为不读片,2 设置为读片(取消 先前设置为不读片) 4 设置为删除(数据库记录软删除) 5 恢复为未删除 /// /// /// /// /// /// [HttpPut("{trialId:guid}/{subjectVisitId:guid}/{studyId:guid}/{seriesId:guid}/{state:int}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task SetSeriesState(Guid subjectVisitId, Guid studyId, Guid seriesId, int state) { await VerifyIsCanQCAsync(null, subjectVisitId); var series = (await _repository.Where(t => t.Id == seriesId, true).IgnoreQueryFilters().FirstOrDefaultAsync()).IfNullThrowException(); if (state == 1) { series.IsReading = false; } else if (state == 2) { series.IsReading = true; } else if (state == 4) { series.IsDeleted = true; var study = (await _repository.Where(t => t.Id == studyId, true).IgnoreQueryFilters().FirstOrDefaultAsync()).IfNullThrowException(); var instanceIdList = await _repository.Where(t => t.SeriesId == seriesId).Select(t => t.Id).ToListAsync(); //instanceIdList.ForEach(t => //{ // var path = _dicomFileStoreHelper.GetInstanceFilePath(study, seriesId, t.ToString()); // if (System.IO.File.Exists(path)) // { // File.Delete(path); // } //}); study.InstanceCount = study.InstanceCount - instanceIdList.Count; study.SeriesCount = study.SeriesCount - 1; study.IsDeleted = study.SeriesCount == 0; } else if (state == 5) { series.IsDeleted = false; var study = (await _repository.Where(t => t.Id == studyId, true).IgnoreQueryFilters().FirstOrDefaultAsync()).IfNullThrowException(); var instanceIdList = await _repository.Where(t => t.SeriesId == seriesId).Select(t => t.Id).ToListAsync(); study.InstanceCount = study.InstanceCount + instanceIdList.Count; study.SeriesCount = study.SeriesCount + 1; study.IsDeleted = study.SeriesCount == 0; } return ResponseOutput.Ok(await _repository.SaveChangesAsync()); } /// ///type 1 :study 2: series 3:非dicom QC修改检查部位和 拍片类型 /// /// [HttpPost("{trialId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task UpdateModality(UpdateModalityCommand updateModalityCommand) { if (_userInfo.UserTypeEnumInt == (int)UserTypeEnum.IQC) { await VerifyIsCanQCAsync(null, updateModalityCommand.SubjectVisitId); } else { if (await _subjectVisitRepository.AnyAsync(t => t.Id == updateModalityCommand.SubjectVisitId && t.SubmitState == SubmitStateEnum.Submitted)) { throw new BusinessValidationFailedException("提交之后,不允许修改!"); } } DateTime time = DateTime.Now.AddMilliseconds(500); if (updateModalityCommand.Type == 1) { var studyId = updateModalityCommand.Id; var study = (await _repository.FirstOrDefaultAsync(t => t.Id == studyId)).IfNullThrowException(); study.BodyPartForEdit = updateModalityCommand.BodyPart; //study.Modalities = updateModalityCommand.Modality; study.ModalityForEdit = updateModalityCommand.Modality; await _repository.BatchUpdateAsync(t => t.StudyId == studyId, r => new DicomSeries() { BodyPartForEdit = updateModalityCommand.BodyPart, Modality = updateModalityCommand.Modality }); } else if (updateModalityCommand.Type == 2) { var seriesId = updateModalityCommand.Id; var series = (await _repository.FirstOrDefaultAsync(t => t.Id == seriesId)).IfNullThrowException(); series.BodyPartForEdit = updateModalityCommand.BodyPart; } else if (updateModalityCommand.Type == 3) { } await _repository.SaveChangesAsync(); return ResponseOutput.Ok(); } /// /// 验证是否质疑都关闭了 可以审核通过和不通过 /// /// /// [HttpGet("{trialId:guid}/{subjectVisitId:guid}")] public async Task VerifyCanQCPassedOrFailed(Guid subjectVisitId) { await VerifyIsCanQCAsync(null, subjectVisitId); if (await _repository.AnyAsync(t => t.SubjectVisitId == subjectVisitId && t.IsClosed == false)) { return ResponseOutput.NotOk("当前检查批次有质疑未关闭,不允许该操作"); } return ResponseOutput.Ok(); } /// /// 删除检查列表 不能用delete方法 传递数组 /// /// /// /// /// SeriesCount [HttpPost, Route("{trialId:guid}/{subjectVisitId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [UnitOfWork] public async Task DeleteStudyList(Guid[] ids, Guid subjectVisitId, Guid trialId) { //提交了 但是IQC同意的时候 是可以删除的 | 普通提交后也不能删除 if (await _subjectVisitRepository.AnyAsync(t => t.Id == subjectVisitId && t.SubmitState == SubmitStateEnum.Submitted && (!t.QCChallengeList.Any(u => u.ReuploadEnum == QCChanllengeReuploadEnum.QCAgreeUpload)))) { return ResponseOutput.NotOk("CRC已经提交,不允许删除。"); } var waitDeleteStudyList = await _dicomStudyRepository.Where(x => ids.Contains(x.Id)).ToListAsync(); foreach (var study in waitDeleteStudyList) { var id = study.Id; await _dicomStudyRepository.DeleteAsync(study); //var DicomSeriess = await _repository.GetQueryable().Where(t => t.StudyId == id).Select(x => new //{ // x.StudyId, // x.SubjectId, // x.SiteId, // x.TrialId, // x.Id, // x.SubjectVisitId, // x.SeriesTime, // x.IsReading, // x.InstanceCount, // x.SeriesNumber, // StudyCode = x.DicomStudy.StudyCode, // Modalities = x.DicomStudy.Modalities, //}).ToListAsync(); var succeess2 = await _repository.BatchDeleteAsync(t => t.StudyId == id); var success3 = await _dicomSeriesrepository.BatchDeleteNoTrackingAsync(t => t.StudyId == id); //var success3 = await _dicomSeriesrepository.DeleteFromQueryAsync(t => t.StudyId == id, true); //var success4 = await _repository.BatchDeleteAsync(t => t.StudyId == id); //删除 物理文件 var instancePathList = await _repository.Where(t => t.StudyId == id) .Select(t => t.Path).ToListAsync(); instancePathList.ForEach(path => { var physicalPath = FileStoreHelper.GetPhysicalFilePath(_hostEnvironment, path); if (System.IO.File.Exists(physicalPath)) { File.Delete(physicalPath); } }); //var instanceIdList = await _repository.Where(t => t.StudyId == id) // .Select(t => new { InstanceId = t.Id, t.SeriesId, t.StudyId, t.SubjectId, t.SiteId }).ToListAsync(); //instanceIdList.ForEach(t => //{ // var dicomStudy = new DicomStudy() { Id = t.StudyId, SubjectId = t.SubjectId, TrialId = trialId, SiteId = t.SiteId, SubjectVisitId = subjectVisitId }; // var (physicalPath, relativePath) = // FileStoreHelper.GetDicomInstanceFilePath(_hostEnvironment, dicomStudy.TrialId, dicomStudy.SiteId, dicomStudy.SubjectId, dicomStudy.SubjectVisitId, dicomStudy.Id, t.InstanceId); // if (System.IO.File.Exists(physicalPath)) // { // File.Delete(physicalPath); // } //}); } await _subjectVisitRepository.SaveChangesAsync(); return ResponseOutput.Ok(); } #endregion [HttpGet] public async Task IsQCCanOpt([FromQuery, Required] Guid subjectVisitId) { await VerifyIsCanQCAsync(null, subjectVisitId); return ResponseOutput.Ok(); } private async Task VerifyIsCanQCAsync(SubjectVisit? subjectVisit, Guid? sujectVisitId = null) { if (sujectVisitId != null) { subjectVisit = (await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == sujectVisitId)).IfNullThrowException(); } if (subjectVisit!.CurrentActionUserId != _userInfo.Id) { throw new BusinessValidationFailedException("您不是该质控任务当前领取人,没有操作权限!"); } } #region 临床数据签名 领取、 设置紧急、RequestToQC QC通过、不通过 /// 替换当前领取人 [HttpPut("{trialId:guid}/{subjectVisitId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task ReplaceQCTaskActionUser(Guid trialId, Guid subjectVisitId) { var dbSubjectVisit = (await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == subjectVisitId)).IfNullThrowException(); if (dbSubjectVisit.CurrentActionUserId == null && dbSubjectVisit.IsTake == false) { return ResponseOutput.NotOk("当前检查批次的影像质控任务已被原领取人释放。您可以通过“领取”获得", ApiResponseCodeEnum.NeedTips); } dbSubjectVisit.IsTake = true; dbSubjectVisit.CurrentActionUserId = _userInfo.Id; dbSubjectVisit.CurrentActionUserExpireTime = DateTime.Now.AddHours(1); var success = await _subjectVisitRepository.SaveChangesAsync(); return ResponseOutput.Result(success); } /// /// 手动领取 或者取消 QC任务 /// /// /// /// true 获取 false是取消领取 /// [HttpPut("{trialId:guid}/{subjectVisitId:guid}/{obtaionOrCancel:bool}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task ObtainOrCancelQCTask(Guid trialId, Guid subjectVisitId, bool obtaionOrCancel) { var dbSubjectVisit = (await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == subjectVisitId)).IfNullThrowException(); var trialConfig = await _trialRepository .Select(t => new { TrialId = t.Id, t.QCProcessEnum, t.IsImageConsistencyVerification }) .FirstOrDefaultAsync(t => t.TrialId == trialId) .IfNullThrowException(); if (obtaionOrCancel) { if (dbSubjectVisit.CurrentActionUserId != null && dbSubjectVisit.IsTake) { //throw new BusinessValidationFailedException("当前检查批次已被领取,不允许领取"); return ResponseOutput.NotOk("当前检查批次的影像质控任务已被其他QC领取,不允许领取", ApiResponseCodeEnum.NeedTips); } if (await _subjectVisitRepository.AnyAsync(t => t.Trial.QCQuestionConfirmedUserId == null && t.Id == subjectVisitId)) { return ResponseOutput.NotOk("请先配置影像质控审核问题,再领取影像质控任务", ApiResponseCodeEnum.NeedTips); } //if (await _subjectVisitRepository.AnyAsync(t => t.IsTake && // t.SubjectId != dbSubjectVisit.SubjectId && // t.CurrentActionUserId == _userInfo.Id && // t.TrialId == dbSubjectVisit.TrialId // )) //{ // return ResponseOutput.NotOk("您已经领取了其他患者,完成后才允许领取新的患者"); //} #region 处理验证 if (trialConfig.QCProcessEnum == TrialQCProcess.NotAudit) { return ResponseOutput.NotOk("项目配置为不审,没有领取QC Task"); } else if (trialConfig.QCProcessEnum == TrialQCProcess.SingleAudit) { if (dbSubjectVisit.PreliminaryAuditUserId == _userInfo.Id) { return ResponseOutput.NotOk("初审已通过,不能继续领取"); } if (dbSubjectVisit.SubmitState == SubmitStateEnum.Submitted && dbSubjectVisit.AuditState == AuditStateEnum.ToAudit) { dbSubjectVisit.AuditState = AuditStateEnum.InPrimaryQC; } if (dbSubjectVisit.SubmitState == SubmitStateEnum.Submitted && dbSubjectVisit.AuditState == AuditStateEnum.InPrimaryQC) { //单审QC 释放后 也是可以领取的 } else { return ResponseOutput.NotOk("项目配置为单审,不满足SubmmitState:已提交 或者 AuditState:待审核/审核中, 不允许领取,请刷新界面"); } } else if (trialConfig.QCProcessEnum == TrialQCProcess.DoubleAudit) { if (dbSubjectVisit.PreliminaryAuditUserId == _userInfo.Id) { return ResponseOutput.NotOk("复审不能和初审是同一个人"); } //提交 并且初审通过 那么领取后进入 复审中 if (dbSubjectVisit.SubmitState == SubmitStateEnum.Submitted && dbSubjectVisit.AuditState == AuditStateEnum.PrimaryQCPassed) { dbSubjectVisit.AuditState = AuditStateEnum.InSecondaryQC; } else if (dbSubjectVisit.SubmitState == SubmitStateEnum.Submitted && dbSubjectVisit.AuditState == AuditStateEnum.ToAudit) { dbSubjectVisit.AuditState = AuditStateEnum.InPrimaryQC; } else if (dbSubjectVisit.SubmitState == SubmitStateEnum.Submitted && (dbSubjectVisit.AuditState == AuditStateEnum.InPrimaryQC || dbSubjectVisit.AuditState == AuditStateEnum.InSecondaryQC)) { //初审中 复审中 领取也是ok的 其他人接着做 } else { return ResponseOutput.NotOk("项目配置为复审,不满足提交状态:已提交 或者 审核状态:待审核/QC中, 不允许领取,请刷新界面"); } } #endregion dbSubjectVisit.IsTake = true; dbSubjectVisit.CurrentActionUserId = _userInfo.Id; dbSubjectVisit.CurrentActionUserExpireTime = DateTime.Now.AddHours(1); //启动定时任务 1h后处理 //BackgroundJob.Schedule(t => t.CancelQCObtaion(subjectVisitId, DateTime.Now), TimeSpan.FromHours(1)); } else { if (dbSubjectVisit!.CurrentActionUserId != _userInfo.Id) { return ResponseOutput.NotOk("您不是该质控任务当前领取人,没有操作权限!", ApiResponseCodeEnum.NeedTips); } if (trialConfig.QCProcessEnum == TrialQCProcess.NotAudit) { return ResponseOutput.NotOk("项目配置影像质控为不审,不需要取消任务功能"); } else if (trialConfig.QCProcessEnum == TrialQCProcess.SingleAudit) { if (dbSubjectVisit.SubmitState == SubmitStateEnum.Submitted && dbSubjectVisit.AuditState == AuditStateEnum.InPrimaryQC) { dbSubjectVisit.AuditState = AuditStateEnum.ToAudit; } else { return ResponseOutput.NotOk("当前检查批次影像质控任务没有当前领取人,不能释放。"); } } else if (trialConfig.QCProcessEnum == TrialQCProcess.DoubleAudit) { //提交 并且初审通过 那么领取后进入 复审中 if (dbSubjectVisit.SubmitState == SubmitStateEnum.Submitted && dbSubjectVisit.AuditState == AuditStateEnum.InSecondaryQC) { dbSubjectVisit.AuditState = AuditStateEnum.PrimaryQCPassed; } else if (dbSubjectVisit.SubmitState == SubmitStateEnum.Submitted && dbSubjectVisit.AuditState == AuditStateEnum.InPrimaryQC) { dbSubjectVisit.AuditState = AuditStateEnum.ToAudit; } else { return ResponseOutput.NotOk("当前检查批次影像质控任务没有当前领取人, 不能释放"); } } dbSubjectVisit.IsTake = false; dbSubjectVisit.CurrentActionUserId = null; dbSubjectVisit.CurrentActionUserExpireTime = null; } var success = await _subjectVisitRepository.SaveChangesAsync(); return ResponseOutput.Result(success); } [HttpPost] public async Task VerifyCRCRequestToQC(CRCRequestToQCCommand cRCRequestToQCCommand) { var dbSubjectVisitList = await _subjectVisitRepository.Where(t => cRCRequestToQCCommand.SubjectVisitIds.Contains(t.Id), true).ProjectTo(_mapper.ConfigurationProvider).ToListAsync(); //普通提交 if (dbSubjectVisitList.Count() == 1) { var sv = dbSubjectVisitList[0]; var nameList = await _subjectVisitRepository.Where(t => t.SubjectId == sv.SubjectId && t.SubmitState != SubmitStateEnum.Submitted && t.VisitNum < sv.VisitNum && t.IsLostVisit == false).Select(t => t.VisitName).ToListAsync() ?? new List(); if (sv.PDState == PDStateEnum.PDProgress) { if (nameList.Count() > 0) { return ResponseOutput.NotOk($"当前检查批次要求进行疾病进展确认。请在提交当前检查批次前,先处理未提交的前序检查批次:{string.Join('、', nameList)}。", 1, ApiResponseCodeEnum.NeedTips); } } else { if (nameList.Count() > 0) { return ResponseOutput.NotOk($"在提交当前检查批次后,请尽快处理尚未提交的前序检查批次:{string.Join('、', nameList)}。", 0, ApiResponseCodeEnum.NeedTips); } } } //同时要根据项目有没有配置Subject 级别临床数据 if (dbSubjectVisitList.Any(t => t.IsBaseLine && !t.IsHaveClinicalData) && await _repository.AnyAsync(t=>t.TrialId== cRCRequestToQCCommand.TrialId && t.ClinicalDataLevel==ClinicalLevel.Subject)) { return ResponseOutput.NotOk($"基线没有临床数据,确认提交?", 0, ApiResponseCodeEnum.NeedTips); } return ResponseOutput.Ok(); } /// /// CRC RequestToQC 批量提交 [需要签名 不需要对] /// /// [HttpPost] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task CRCRequestToQC(CRCRequestToQCCommand cRCRequestToQCCommand) { var trialId = cRCRequestToQCCommand.TrialId; var trialConfig = (await _trialRepository .Select(t => new { TrialId = t.Id, t.QCProcessEnum, t.IsImageConsistencyVerification, t.IsUrgent, t.IsHaveFirstGiveMedicineDate, t.ClinicalInformationTransmissionEnum }) .FirstOrDefaultAsync(t => t.TrialId == cRCRequestToQCCommand.TrialId)).IfNullThrowException(); //找到符合配置的标准 确认的并且签名的、双重,有序,阅片期仲裁 var trialReadingCriterionIdList = _repository.Where(t => t.TrialId == cRCRequestToQCCommand.TrialId && t.IsConfirm && t.ReadingInfoSignTime != null //&& t.ReadingType == ReadingMethod.Double && t.ArbitrationRule == ArbitrationRule.Reading && t.IsReadingTaskViewInOrder && t.IsReadingPeriod).Select(t => t.Id).ToList(); var trialInOrderCriterionIdList = _repository.Where(t => t.TrialId == cRCRequestToQCCommand.TrialId && t.IsConfirm && t.ReadingInfoSignTime != null //&& t.ReadingType == ReadingMethod.Double && t.ArbitrationRule == ArbitrationRule.Reading && t.IsReadingTaskViewInOrder).Select(t => t.Id).ToList(); var dbSubjectVisitList = await _subjectVisitRepository.Where(t => cRCRequestToQCCommand.SubjectVisitIds.Contains(t.Id), true).Include(t => t.Subject).ToListAsync(); if (dbSubjectVisitList.Any(t => t.SubmitState == SubmitStateEnum.None)) { return ResponseOutput.NotOk("有检查批次未上传任何Dicom/非Dicom影像数据,不允许提交"); } //单个提交提示信息 if (dbSubjectVisitList.Count() == 1 && dbSubjectVisitList.First().SubmitState == SubmitStateEnum.Submitted) { return ResponseOutput.NotOk("当前检查批次的影像数据,已经由其他CRC提交。", 3, ApiResponseCodeEnum.NeedTips); } else if (dbSubjectVisitList.Any(t => t.SubmitState == SubmitStateEnum.Submitted)) { return ResponseOutput.NotOk("当前批量提交检查批次的影像数据,其中部分已由其他CRC提交。", 3, ApiResponseCodeEnum.NeedTips); } //获取确认的临床数据配置 var clinicalDataConfirmList = _repository.Where(t => t.TrialId == trialId && t.IsConfirm).ToList(); // 别人未提交的 foreach (var dbSubjectVisit in dbSubjectVisitList) { //基线不验证 if (trialConfig.IsHaveFirstGiveMedicineDate && !dbSubjectVisit.IsBaseLine && dbSubjectVisit.Subject.FirstGiveMedicineTime == null) { return ResponseOutput.NotOk("项目配置了需要填写检查批次基准日期。但是患者没有填写检查批次基准日期,不允许提交"); } //基线 且配置了临床数据 if (trialConfig.ClinicalInformationTransmissionEnum != 0 /*&& dbSubjectVisit.IsBaseLine*//*&&dbSubjectVisit.ClinicalDataSignUserId==null*/) { ////找到需要确认的PDF 临床数据 如果没有CRC 没有上传 那么就添加一条没有文件的记录 //var crcNeedConfirmClinicalDataSetList = clinicalDataConfirmList.Where(t => t.UploadRole == UploadRole.CRC && t.ClinicalUploadType == ClinicalUploadType.PDF).ToList(); //// 找到CRC 已经自己添加的临床PDF数据文件 //var crcAddClinicalDataIdList = _readingClinicalDataRepository.Where(t => t.ReadingId == dbSubjectVisit.Id && t.ClinicalDataTrialSet.UploadRole == UploadRole.CRC && t.ClinicalDataTrialSet.ClinicalUploadType == ClinicalUploadType.PDF) // .Select(t => new { t.ClinicalDataTrialSetId }).ToList(); //foreach (var crcNeedConfirmClinicalDataSet in crcNeedConfirmClinicalDataSetList) //{ // if (!crcAddClinicalDataIdList.Any(t => t.ClinicalDataTrialSetId == crcNeedConfirmClinicalDataSet.Id)) // { // await _repository.AddAsync(new ReadingClinicalData() // { // TrialId = dbSubjectVisit.TrialId, // SubjectId = dbSubjectVisit.SubjectId, // ReadingId = dbSubjectVisit.Id, // ClinicalDataTrialSetId = crcNeedConfirmClinicalDataSet.Id, // IsVisit = true, // IsSign = true, // ReadingClinicalDataState = ReadingClinicalDataStatus.HaveSigned // }); // } //} //已确认临床数据完整性 dbSubjectVisit.IsConfirmedClinicalData = true; // CRC 上传的基线数据签名 await _readingClinicalDataRepository.UpdatePartialFromQueryAsync(x => x.ClinicalDataTrialSet.UploadRole == UploadRole.CRC && x.ReadingId == dbSubjectVisit.Id && x.IsSign==false, x => new ReadingClinicalData() { IsSign = true, ReadingClinicalDataState = ReadingClinicalDataStatus.HaveSigned }); //var signSuccess = await _repository.UpdateFromQueryAsync(t => t.Id == cRCRequestToQCCommand.SignId, u => new TrialSign() { IsCompleted = true }); ////现在修改为 提交时 设置签名信息 //dbSubjectVisit.ClinicalDataSignUserId = _userInfo.Id; //dbSubjectVisit.ClinicalDataSignTime = DateTime.Now; //那么没有录入 不允许提交 //if (!await _repository.AnyAsync(t => t.PreviousHistoryList.Any() || t.PreviousOtherList.Any() || t.PreviousSurgeryList.Any())) //{ // return ResponseOutput.NotOk("没有临床数据,不允许提交"); //} //return ResponseOutput.NotOk("没有签名临床数据,不允许提交"); } var maxVisit = await _subjectVisitRepository.Where(t => t.SubjectId == dbSubjectVisit.SubjectId && t.SubmitState == SubmitStateEnum.Submitted) .OrderByDescending(t => t.VisitNum).Select(t => new { t.Id, t.VisitNum }).FirstOrDefaultAsync(); //修改患者最新检查批次 dbSubjectVisit.Subject.LatestSubjectVisitId = maxVisit == null ? dbSubjectVisit.Id : maxVisit.VisitNum < dbSubjectVisit.VisitNum ? dbSubjectVisit.Id : maxVisit.Id; //var maxVisitNum = maxVisit == null ? dbSubjectVisit.VisitNum : maxVisit.VisitNum < dbSubjectVisit.VisitNum ? dbSubjectVisit.VisitNum : maxVisit.VisitNum; ////判断是否有缺失影像 //dbSubjectVisit.Subject.IsMissingImages = await _subjectVisitRepository.AnyAsync(t => (t.VisitNum < maxVisitNum && t.SubmitState != SubmitStateEnum.Submitted && t.IsLostVisit == false)); //项目或者Subject IsUrgent 提交时 检查批次也设置为紧急 if (trialConfig.IsUrgent || dbSubjectVisit.Subject.IsUrgent || (dbSubjectVisit.PDState == PDStateEnum.PDProgress && !dbSubjectVisit.IsBaseLine) || (dbSubjectVisit.IsEnrollmentConfirm && dbSubjectVisit.IsBaseLine)) { if (dbSubjectVisit.PDState == PDStateEnum.PDProgress) { await _subjectVisitRepository.BatchUpdateNoTrackingAsync(x => x.SubjectId == dbSubjectVisit.SubjectId && x.VisitNum <= dbSubjectVisit.VisitNum, x => new SubjectVisit() { IsUrgent = true }); await _visitTaskRepository.BatchUpdateNoTrackingAsync(x => trialInOrderCriterionIdList.Contains(x.TrialReadingCriterionId)&& x.SubjectId == dbSubjectVisit.SubjectId&& dbSubjectVisit.VisitNum<= x.VisitTaskNum && x.VisitTaskNum < Math.Ceiling(dbSubjectVisit.VisitNum+0.01m) // 当前的检查批次 全局 裁判 及之前 全都加急 && x.ReadingTaskState != ReadingTaskState.HaveSigned && x.TaskState == TaskState.Effect, x => new VisitTask() { IsUrgent = true, TaskUrgentType = TaskUrgentType.PDProgress, IsCanEditUrgentState=false, }); await _visitTaskRepository.BatchUpdateNoTrackingAsync(x => trialInOrderCriterionIdList.Contains(x.TrialReadingCriterionId) && x.SubjectId == dbSubjectVisit.SubjectId && x.VisitTaskNum < dbSubjectVisit.VisitNum // 当前的检查批次 全局 裁判 及之前 全都加急 && x.ReadingTaskState != ReadingTaskState.HaveSigned && x.TaskState == TaskState.Effect, x => new VisitTask() { IsUrgent = true, TaskUrgentType = TaskUrgentType.Other, TaskUrgentRemake= "后续检查批次设为pd", IsCanEditUrgentState = false, }); } else if (dbSubjectVisit.IsEnrollmentConfirm) { await _subjectVisitRepository.BatchUpdateNoTrackingAsync(x => x.Id == dbSubjectVisit.Id, x => new SubjectVisit() { IsUrgent = true }); await _visitTaskRepository.BatchUpdateNoTrackingAsync(x => trialInOrderCriterionIdList.Contains(x.TrialReadingCriterionId) && x.VisitTaskNum>= dbSubjectVisit.VisitNum && x.VisitTaskNum < Math.Ceiling(dbSubjectVisit.VisitNum + 0.01m) // 当前的检查批次 全局 裁判 全都加急 && x.ReadingTaskState != ReadingTaskState.HaveSigned && x.TaskState == TaskState.Effect, x => new VisitTask() { IsUrgent = true, TaskUrgentType = TaskUrgentType.EnrollmentConfirm, IsCanEditUrgentState = false, }); } else { await _visitTaskRepository.BatchUpdateNoTrackingAsync(x => trialInOrderCriterionIdList.Contains(x.TrialReadingCriterionId) && x.VisitTaskNum >= dbSubjectVisit.VisitNum && x.VisitTaskNum < Math.Ceiling(dbSubjectVisit.VisitNum + 0.01m) // 当前的检查批次 全局 裁判 全都加急 && x.ReadingTaskState != ReadingTaskState.HaveSigned && x.TaskState == TaskState.Effect, x => new VisitTask() { IsUrgent = true, TaskUrgentType = TaskUrgentType.VisitUrgent, }); } dbSubjectVisit.IsUrgent = true; //PD确认的紧急会把前面所有未QC完成的检查批次均标记为紧急 var previosSVlist = await _subjectVisitRepository.Where(t => t.SubjectId == dbSubjectVisit.SubjectId && t.VisitNum < dbSubjectVisit.VisitNum && t.IsUrgent == false && t.SubmitState == SubmitStateEnum.Submitted, true).ToListAsync(); previosSVlist.ForEach(t => { t.IsUrgent = true; }); } if (dbSubjectVisit.SubmitState == SubmitStateEnum.ToSubmit) { dbSubjectVisit.SubmitState = SubmitStateEnum.Submitted; dbSubjectVisit.SubmitTime = DateTime.Now; dbSubjectVisit.SubmitUserId = _userInfo.Id; //维护统一状态 dbSubjectVisit.ReadingStatus = ReadingStatusEnum.ImageQuality; } //不审 直接QC通过 可能一致性核查 也可能不一致性核查 if (trialConfig.QCProcessEnum == TrialQCProcess.NotAudit) { dbSubjectVisit.AuditState = AuditStateEnum.QCPassed; //维护统一状态 dbSubjectVisit.ReadingStatus = ReadingStatusEnum.ConsistencyCheck; // 不一致性核查 就CVPassed ToForward 否则就是待核查 dbSubjectVisit.CheckState = trialConfig.IsImageConsistencyVerification ? CheckStateEnum.ToCheck : CheckStateEnum.CVPassed; //维护统一状态 dbSubjectVisit.ReadingStatus = trialConfig.IsImageConsistencyVerification ? ReadingStatusEnum.ConsistencyCheck : ReadingStatusEnum.TaskAllocate; if (dbSubjectVisit.CheckState == CheckStateEnum.CVPassed) { dbSubjectVisit.CheckPassedTime = DateTime.Now; } dbSubjectVisit.ForwardState = trialConfig.IsImageConsistencyVerification ? ForwardStateEnum.None : ForwardStateEnum.ToForward; } else if (trialConfig.QCProcessEnum == TrialQCProcess.SingleAudit) { dbSubjectVisit.AuditState = AuditStateEnum.ToAudit; } else if (trialConfig.QCProcessEnum == TrialQCProcess.DoubleAudit) { dbSubjectVisit.AuditState = AuditStateEnum.ToAudit; } //非基线设置为PD的话 或者设置为末次检查批次 根据配置自动生成阅片期 if (!dbSubjectVisit.IsBaseLine && (dbSubjectVisit.PDState == PDStateEnum.PDProgress || dbSubjectVisit.IsFinalVisit) ) { //该标准需要添加阅片期 foreach (var trialReadingCriterionId in trialReadingCriterionIdList) { //不存在该阅片期 (重传、回退都会重新提交) if (!await _repository.Where(t => t.SubjectVisitId == dbSubjectVisit.Id && t.ReadingSetType == ReadingSetType.ImageReading && t.TrialReadingCriterionId == trialReadingCriterionId).AnyAsync()) { var newReadModule = await _repository.AddAsync(new ReadModule() { TrialReadingCriterionId = trialReadingCriterionId, ReadingSetType = ReadingSetType.ImageReading, SubjectVisitId = dbSubjectVisit.Id, ModuleName = $"G-{dbSubjectVisit.VisitName}", ModuleType = ModuleTypeEnum.Global, IsUrgent = dbSubjectVisit.IsUrgent, TrialId = dbSubjectVisit.TrialId, SubjectId = dbSubjectVisit.SubjectId }); } } } } var success = await _repository.SaveChangesAsync(); return ResponseOutput.Ok(success); } /// /// 设置QC 通过或者不通过 7:QC failed 8:QC passed [签名 不需要对] /// /// /// /// /// [HttpPost("{trialId:guid}/{subjectVisitId:guid}/{auditState:int}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [UnitOfWork] public async Task QCPassedOrFailed(Guid trialId, Guid subjectVisitId, [FromRoute] AuditStateEnum auditState) { if (!await _repository.AnyAsync(t => t.TrialId == trialId && t.UserId == _userInfo.Id)) { return ResponseOutput.NotOk("您已经被移出项目,没有操作权限。"); } if (auditState == AuditStateEnum.QCPassed) { //判断质疑是否都关闭了 if (await _repository.AnyAsync(t => t.SubjectVisitId == subjectVisitId && t.IsClosed == false)) { return ResponseOutput.NotOk("当前检查批次有影像质控质疑未关闭,不能进行此操作。"); } } var trialConfig = await _trialRepository .Select(t => new { TrialId = t.Id, t.QCProcessEnum, t.IsImageConsistencyVerification }) .FirstOrDefaultAsync(t => t.TrialId == trialId) .IfNullThrowException(); var dbSubjectVisit = (await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == subjectVisitId)).IfNullThrowException(); await VerifyIsCanQCAsync(dbSubjectVisit); //删除 软删除的物理文件 var instancePathList = await _repository.Where(t => t.DicomSerie.IsDeleted && t.SubjectVisitId == subjectVisitId) .Select(t => t.Path).ToListAsync(); //维护统一状态 dbSubjectVisit.ReadingStatus = ReadingStatusEnum.ConsistencyCheck; //有人QC Passed if (auditState == AuditStateEnum.QCPassed) { //判断 QC流程 不审 单审 双审 if (trialConfig.QCProcessEnum == TrialQCProcess.NotAudit) { return ResponseOutput.NotOk("项目配置影像质控为不审,不需要设置为影像质控通过。"); } else if (trialConfig.QCProcessEnum == TrialQCProcess.SingleAudit) { if (dbSubjectVisit.AuditState == AuditStateEnum.InPrimaryQC) { if (!await _repository.AnyAsync(t => t.SubjectVisitId == subjectVisitId && t.CurrentQCEnum == CurrentQC.First)) { return ResponseOutput.NotOk("影像质控审核问题没有保存,不能进行此操作。"); } // 单审 dbSubjectVisit.AuditState = AuditStateEnum.QCPassed; dbSubjectVisit.CheckState = trialConfig.IsImageConsistencyVerification ? CheckStateEnum.ToCheck : CheckStateEnum.CVPassed; dbSubjectVisit.ForwardState = trialConfig.IsImageConsistencyVerification ? ForwardStateEnum.None : ForwardStateEnum.ToForward; dbSubjectVisit.PreliminaryAuditUserId = _userInfo.Id; dbSubjectVisit.PreliminaryAuditTime = DateTime.Now; //维护统一状态 dbSubjectVisit.ReadingStatus = trialConfig.IsImageConsistencyVerification ? ReadingStatusEnum.ConsistencyCheck : ReadingStatusEnum.TaskAllocate; //删除影像 instancePathList.ForEach(path => { var physicalPath = FileStoreHelper.GetPhysicalFilePath(_hostEnvironment, path); //var dicomStudy = new DicomStudy() { Id = t.StudyId, SubjectId = t.SubjectId, TrialId = trialId, SiteId = t.SiteId, SubjectVisitId = subjectVisitId }; //var (physicalPath, relativePath) = // FileStoreHelper.GetDicomInstanceFilePath(_hostEnvironment, dicomStudy.TrialId, dicomStudy.SiteId, dicomStudy.SubjectId, dicomStudy.SubjectVisitId, dicomStudy.Id, t.InstanceId); if (System.IO.File.Exists(physicalPath)) { File.Delete(physicalPath); } }); } else { return ResponseOutput.NotOk("项目配置影像质控为单审,当前检查批次影像质控任务不能从当前审核状态变更到 审核通过。"); } } else if (trialConfig.QCProcessEnum == TrialQCProcess.DoubleAudit) { // 双审 如果当前db 状态是 InPrimaryQC 当前操作为 QCPassed 那么设置为 PrimaryQCPassed if (dbSubjectVisit.AuditState == AuditStateEnum.InPrimaryQC) { if (!await _repository.AnyAsync(t => t.SubjectVisitId == subjectVisitId && t.CurrentQCEnum == CurrentQC.First)) { return ResponseOutput.NotOk("影像质控审核问题没有保存,不能进行此操作。"); } dbSubjectVisit.AuditState = AuditStateEnum.PrimaryQCPassed; dbSubjectVisit.PreliminaryAuditUserId = _userInfo.Id; dbSubjectVisit.PreliminaryAuditTime = DateTime.Now; } else if (dbSubjectVisit.AuditState == AuditStateEnum.InSecondaryQC) { if (!await _repository.AnyAsync(t => t.SubjectVisitId == subjectVisitId && t.CurrentQCEnum == CurrentQC.Second)) { return ResponseOutput.NotOk("影像质控审核问题没有保存,不能进行此操作。"); } dbSubjectVisit.AuditState = AuditStateEnum.QCPassed; dbSubjectVisit.CheckState = trialConfig.IsImageConsistencyVerification ? CheckStateEnum.ToCheck : CheckStateEnum.CVPassed; dbSubjectVisit.ForwardState = trialConfig.IsImageConsistencyVerification ? ForwardStateEnum.None : ForwardStateEnum.ToForward; dbSubjectVisit.ReviewAuditUserId = _userInfo.Id; dbSubjectVisit.ReviewAuditTime = DateTime.Now; //维护统一状态 dbSubjectVisit.ReadingStatus = trialConfig.IsImageConsistencyVerification ? ReadingStatusEnum.ConsistencyCheck : ReadingStatusEnum.TaskAllocate; //删除影像 instancePathList.ForEach(path => { var physicalPath = FileStoreHelper.GetPhysicalFilePath(_hostEnvironment, path); if (System.IO.File.Exists(physicalPath)) { File.Delete(physicalPath); } }); } else { return ResponseOutput.NotOk($"项目配置影像质控为双审。当前审核状态为 {dbSubjectVisit.AuditState},不能变更到 审核通过。"); } } } else if (auditState == AuditStateEnum.QCFailed) { //判断 QC流程 不审 单审 双审 if (trialConfig.QCProcessEnum == TrialQCProcess.NotAudit) { return ResponseOutput.NotOk("项目配置影像质控为不审,不允许设置影像质控终止。"); } else if (trialConfig.QCProcessEnum == TrialQCProcess.SingleAudit) { // 单审 if (dbSubjectVisit.AuditState == AuditStateEnum.InPrimaryQC) { dbSubjectVisit.AuditState = AuditStateEnum.QCFailed; dbSubjectVisit.PreliminaryAuditUserId = _userInfo.Id; } else { return ResponseOutput.NotOk("项目配置影像质控为单审,当前审核状态不为 InPrimaryQC,不能变更到 审核终止"); } } else if (trialConfig.QCProcessEnum == TrialQCProcess.DoubleAudit) { // 双审 if (dbSubjectVisit.AuditState == AuditStateEnum.InPrimaryQC) { dbSubjectVisit.AuditState = AuditStateEnum.QCFailed; dbSubjectVisit.PreliminaryAuditUserId = _userInfo.Id; } else if (dbSubjectVisit.AuditState == AuditStateEnum.InSecondaryQC) { dbSubjectVisit.AuditState = AuditStateEnum.QCFailed; dbSubjectVisit.ReviewAuditUserId = _userInfo.Id; } else { return ResponseOutput.NotOk($"项目配置影像质控为双审,当前审核状态为 {dbSubjectVisit.AuditState},不能变更到 审核终止"); } } await _qcChallengeRepository.UpdatePartialFromQueryAsync(t => t.IsClosed == false&& t.SubjectVisitId==dbSubjectVisit.Id, u => new QCChallenge() { IsClosed = true, ClosedTime=DateTime.Now, CloseResonEnum = QCChallengeCloseEnum.Unresolvable }); } dbSubjectVisit.Auditor = _userInfo.Id; dbSubjectVisit.IsTake = false; dbSubjectVisit.CurrentActionUserId = null; dbSubjectVisit.CurrentActionUserExpireTime = null; await _repository.SaveChangesAsync(); return ResponseOutput.Result(true); } /// /// 设置、取消 检查批次紧急 /// /// /// /// /// [HttpPut("{trialId:guid}/{subjectVisitId:guid}/{setOrCancel:bool}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] [Authorize(Policy = IRaCISPolicy.IQC)] public async Task SetVisitUrgent(Guid trialId, Guid subjectVisitId, bool setOrCancel) { var sv = (await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == subjectVisitId)).IfNullThrowException(); sv.IsUrgent = setOrCancel; var success = await _repository.SaveChangesAsync(); return ResponseOutput.Ok(); } #endregion #region 重传、重传完设置 /// 验证质疑对话页面 是否可以 跳转到重传页面 进行重传 [HttpGet("{trialId:guid}/{qcChallengeId:guid}")] public async Task VerifyReuploadIsCanJump(Guid trialId, Guid qcChallengeId) { var qcChallenge = (await _qcChallengeRepository.FirstOrDefaultAsync(t => t.Id == qcChallengeId)).IfNullThrowException(); if (qcChallenge.ReuploadEnum != QCChanllengeReuploadEnum.QCAgreeUpload) { throw new BusinessValidationFailedException("当前重传状态不为QC同意重传,不允许重传"); } return ResponseOutput.Ok(); } /// /// QA设置 同意重传 /// /// /// /// [HttpPut("{trialId:guid}/{subjectVisitId:guid}/{qcChallengeId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task SetNeedReupload(Guid trialId, Guid qcChallengeId) { //获取项目配置 var trialConfig = await _repository.Where(t => t.Id == trialId).Select(t => new { TrialId = t.Id, t.QCProcessEnum, t.IsImageConsistencyVerification }) .FirstOrDefaultAsync().IfNullThrowException(); if (trialConfig.QCProcessEnum == TrialQCProcess.NotAudit) { return ResponseOutput.NotOk("不审操作,不会有需要重传的操作!"); } var qcChallenge = (await _qcChallengeRepository.FirstOrDefaultAsync(t => t.Id == qcChallengeId)).IfNullThrowException(); var sv = (await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == qcChallenge.SubjectVisitId)).IfNullThrowException(); await VerifyIsCanQCAsync(sv); if (qcChallenge.ReuploadEnum != QCChanllengeReuploadEnum.CRCRequestReupload) { throw new BusinessValidationFailedException("当前重传状态不为CRC申请重传,不允许设置同意重传"); } if (await _qcChallengeRepository.CountAsync(t => t.ReuploadEnum == QCChanllengeReuploadEnum.QCAgreeUpload && t.SubjectVisitId == qcChallenge.SubjectVisitId && t.IsClosed == false) >= 1) { return ResponseOutput.NotOk("当前检查批次,有一个未关闭的质疑 QC设置了同意重传,CRC还未完成上传,当前不允许再次设置"); } qcChallenge.ReuploadEnum = QCChanllengeReuploadEnum.QCAgreeUpload; qcChallenge.LatestMsgTime = DateTime.Now; qcChallenge.LatestReplyUserId = _userInfo.Id; await _subjectVisitRepository.BatchUpdateNoTrackingAsync(t => t.Id == qcChallenge.SubjectVisitId, c => new SubjectVisit() { IsQCConfirmedReupload = true }); qcChallenge.DialogList.Add(new QCChallengeDialog() { SubjectVisitId = qcChallenge.SubjectVisitId, UserTypeEnum = (UserTypeEnum)_userInfo.UserTypeEnumInt, QCChallengeId = qcChallenge.Id, TalkContent = "QC同意重传" }); //双审 并且是2QC 那么需要回退到1QC 讲1QC数据清除 if (trialConfig.QCProcessEnum == TrialQCProcess.DoubleAudit && await _repository.AnyAsync(t => t.Id == qcChallengeId && t.SubjectVisit.AuditState == AuditStateEnum.InSecondaryQC)) { // //一致性核查质疑状态 // sv.CheckChallengeState = CheckChanllengeTypeEnum.None; //// 一致性核查状态 // sv.CheckState = CheckStateEnum.None; // 审核状态 sv.AuditState = AuditStateEnum.InPrimaryQC; sv.CurrentActionUserExpireTime = DateTime.Now.AddHours(1); sv.CurrentActionUserId = _userInfo.Id; //BackgroundJob.Schedule(t => t.CancelQCObtaion(qcChallenge.SubjectVisitId, DateTime.Now), TimeSpan.FromHours(1)); sv.IsTake = true; sv.PreliminaryAuditUserId = null; sv.ReviewAuditUserId = null; //删除1QC 填写的问题答案 await _repository.BatchDeleteAsync(t => t.SubjectVisitId == qcChallenge.SubjectVisitId && t.CurrentQCEnum == CurrentQC.First); //2QC 数据变为1QC await _repository.BatchUpdateAsync(t => t.SubjectVisitId == qcChallenge.SubjectVisitId && t.CurrentQCEnum == CurrentQC.Second, k => new QCChallenge() { CurrentQCEnum = CurrentQC.First }); await _repository.BatchUpdateAsync(t => t.SubjectVisitId == qcChallenge.SubjectVisitId && t.CurrentQCEnum == CurrentQC.Second, k => new TrialQCQuestionAnswer() { CurrentQCEnum = CurrentQC.First }); } var success = await _repository.SaveChangesAsync(); return ResponseOutput.Result(success); } /// /// CRC 设置已经重传完成 [需要签名 不需要对] /// /// [HttpPost] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task SetReuploadFinished(CRCReuploadFinishedCommand cRCReuploadFinishedCommand) { var qcChallenge = (await _qcChallengeRepository.FirstOrDefaultAsync(t => t.Id == cRCReuploadFinishedCommand.QCChallengeId)).IfNullThrowException(); if (qcChallenge.ReuploadEnum != QCChanllengeReuploadEnum.QCAgreeUpload) { throw new BusinessValidationFailedException("当前重传状态不为QC同意重传,不允许设置重传完成"); } var subjectVisitId = qcChallenge.SubjectVisitId; if (await _subjectVisitRepository.Where(t => t.Id == subjectVisitId).SelectMany(t => t.StudyList).CountAsync() == 0 && await _subjectVisitRepository.Where(t => t.Id == subjectVisitId).SelectMany(t => t.NoneDicomStudyList).SelectMany(u => u.NoneDicomFileList).CountAsync() == 0) { throw new BusinessValidationFailedException("当前没有影像,不允许设置重传完成"); } var trialConfig = await _trialRepository .Select(t => new { TrialId = t.Id, t.QCProcessEnum, t.IsImageConsistencyVerification, t.IsUrgent, t.IsHaveFirstGiveMedicineDate, t.ClinicalInformationTransmissionEnum }) .Where(t => t.TrialId == cRCReuploadFinishedCommand.TrialId).FirstOrDefaultAsync(); qcChallenge.ReuploadEnum = QCChanllengeReuploadEnum.CRCReuploaded; qcChallenge.ReUploadedTime = DateTime.Now; qcChallenge.ReUploadUserId = _userInfo.Id; qcChallenge.ReUploader = _userInfo.RealName; qcChallenge.LatestMsgTime = DateTime.Now; qcChallenge.LatestReplyUserId = _userInfo.Id; var dbSubjectVisit = await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == qcChallenge.SubjectVisitId).IfNullThrowException(); await _subjectVisitRepository.BatchUpdateNoTrackingAsync(t => t.Id == qcChallenge.SubjectVisitId, c => new SubjectVisit() { IsQCConfirmedReupload = false }); qcChallenge.DialogList.Add(new QCChallengeDialog() { SubjectVisitId = qcChallenge.SubjectVisitId, UserTypeEnum = (UserTypeEnum)_userInfo.UserTypeEnumInt, QCChallengeId = qcChallenge.Id, TalkContent = "CRC已重传完成" }); //基线 且配置了临床数据 if (trialConfig.ClinicalInformationTransmissionEnum != 0 && dbSubjectVisit.IsBaseLine) { //已确认临床数据完整性 dbSubjectVisit.IsConfirmedClinicalData = true; //var signSuccess = await _repository.BatchUpdateAsync(t => t.Id == cRCReuploadFinishedCommand.SignId, u => new TrialSign() { IsCompleted = true }); } var success = await _subjectVisitRepository.SaveChangesAsync(); return ResponseOutput.Result(success); } [HttpPut("{trialId:guid}/{subjectVisitId:guid}/{qcChallengeId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task CRCRequestReUpload(Guid qcChallengeId) { var qcChallenge = (await _qcChallengeRepository.FirstOrDefaultAsync(t => t.Id == qcChallengeId)).IfNullThrowException(); if (qcChallenge.ReuploadEnum != QCChanllengeReuploadEnum.None && qcChallenge.ReuploadEnum != QCChanllengeReuploadEnum.CRCReuploaded) { throw new BusinessValidationFailedException("当前质疑重传状态不为初始状态|CRC重传完成状态,不允许申请重传"); } if (qcChallenge.ReuploadEnum == QCChanllengeReuploadEnum.CRCReuploaded) { qcChallenge.ReUploadedTime = null; } qcChallenge.LatestMsgTime = DateTime.Now; qcChallenge.LatestReplyUserId = _userInfo.Id; qcChallenge.ReuploadEnum = QCChanllengeReuploadEnum.CRCRequestReupload; qcChallenge.DialogList.Add(new QCChallengeDialog() { SubjectVisitId = qcChallenge.SubjectVisitId, UserTypeEnum = (UserTypeEnum)_userInfo.UserTypeEnumInt, QCChallengeId = qcChallenge.Id, TalkContent = "CRC申请重传/上传影像" }); var isSuccess = await _qcChallengeRepository.SaveChangesAsync(); return ResponseOutput.Result(isSuccess); } #endregion /// /// 上传界面 更新患者检查批次基准日期 是否入组确认,以及检查批次 是否PD进展 /// /// /// [HttpPut("{trialId:guid}")] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task UpdateSubjectAndSVInfo(UploadSubjectAndVisitCommand command) { var dbSubjectVisit = (await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == command.SubjectVisitId)).IfNullThrowException(); dbSubjectVisit.PDState = command.PDState; if (command.IsEnrollmentConfirm != null) { if (await _subjectVisitRepository.Where(t => t.Id == command.SubjectVisitId) .AnyAsync(t => t.SubmitState == SubmitStateEnum.Submitted && t.IsEnrollmentConfirm != command.IsEnrollmentConfirm)) { return ResponseOutput.NotOk("该检查批次已提交,不能修改入组确认状态"); } if (await _subjectVisitRepository.Where(t => t.Id == command.SubjectVisitId) .AnyAsync(t => t.IsEnrollmentConfirm != command.IsEnrollmentConfirm && t.RequestBackState == RequestBackStateEnum.PM_AgressBack)) { return ResponseOutput.NotOk("该检查批次为回退检查批次,不允许修改PD确认状态"); } dbSubjectVisit.IsEnrollmentConfirm = command.IsEnrollmentConfirm.Value; } if (command.SubjectFirstGiveMedicineTime != null) { await _subjectRepository.UpdatePartialFromQueryAsync(command.SubjectId, u => new Subject() { FirstGiveMedicineTime = command.SubjectFirstGiveMedicineTime, }, true); } await _subjectVisitRepository.SaveChangesAsync(); return ResponseOutput.Ok(); } } }