1794 lines
76 KiB
C#
1794 lines
76 KiB
C#
using Hangfire;
|
||
using IRaCIS.Core.Application.MediatR.CommandAndQueries;
|
||
using IRaCIS.Core.Application.BackGroundJob;
|
||
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.Net.Http.Headers;
|
||
using Microsoft.AspNetCore.Http;
|
||
using MiniExcelLibs;
|
||
using ExcelDataReader;
|
||
using System.Text;
|
||
using System.Data;
|
||
using IRaCIS.Core.Infra.EFCore;
|
||
using Microsoft.AspNetCore.Hosting;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
using WinSCP;
|
||
using Magicodes.ExporterAndImporter.Excel;
|
||
using Newtonsoft.Json;
|
||
using Magicodes.ExporterAndImporter.Csv;
|
||
|
||
namespace IRaCIS.Core.Application.Image.QA
|
||
{
|
||
[ApiExplorerSettings(GroupName = "Image")]
|
||
public class QCOperationService : BaseService, IQCOperationService
|
||
{
|
||
|
||
private readonly DicomFileStoreHelper _dicomFileStoreHelper;
|
||
private readonly IRepository<SubjectVisit> _subjectVisitRepository;
|
||
private readonly IRepository<Trial> _trialRepository;
|
||
private object _locker = new object();
|
||
|
||
public QCOperationService(DicomFileStoreHelper dicomFileStoreHelper, IRepository<SubjectVisit> subjectVisitRepository,
|
||
IRepository<Trial> trialRepository
|
||
)
|
||
{
|
||
_dicomFileStoreHelper = dicomFileStoreHelper;
|
||
_subjectVisitRepository = subjectVisitRepository;
|
||
this._trialRepository = trialRepository;
|
||
}
|
||
|
||
#region QC质疑 以及回复 关闭
|
||
|
||
[HttpPost("{trialId:guid}/{subjectVisitId:guid}/{currentQCType:int}")]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
public async Task<IResponseOutput> VerifyQCCanAddChallenge(Guid subjectVisitId, [FromRoute] CurrentQC currentQCType)
|
||
{
|
||
if (!await _repository.AnyAsync<TrialQCQuestionAnswer>(t => t.SubjectVisitId == subjectVisitId && t.CurrentQCEnum == currentQCType))
|
||
{
|
||
return ResponseOutput.NotOk("必须填写审核问题,并保存,否则不允许添加质疑");
|
||
}
|
||
return ResponseOutput.Ok();
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加和更新质疑
|
||
/// </summary>
|
||
/// <param name="qaQuestionCommand"></param>
|
||
/// <param name="trialId"></param>
|
||
/// <param name="trialQCProcess"></param>
|
||
/// <param name="currentQCType"></param>
|
||
/// <returns></returns>
|
||
[HttpPost("{trialId:guid}/{trialQCProcess:int}/{currentQCType:int}")]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
[Authorize(Policy = "ImageQCPolicy")]
|
||
public async Task<IResponseOutput> AddOrUpdateQCChallenge(QCChallengeCommand qaQuestionCommand, Guid trialId, [FromRoute] TrialQCProcess trialQCProcess, [FromRoute] CurrentQC currentQCType)
|
||
{
|
||
|
||
if (qaQuestionCommand.Id == null)
|
||
{
|
||
if (await _repository.AnyAsync<QCChallenge>(t => t.IsClosed == false && t.SubjectVisitId == qaQuestionCommand.SubjectVisitId && t.ReuploadEnum == QCChanllengeReuploadEnum.QCAgreeUpload))
|
||
{
|
||
return ResponseOutput.NotOk("当前访视有未关闭的 同意CRC上传的质疑,不允许再次添加质疑");
|
||
}
|
||
|
||
QCChallenge? qcChallenge = null;
|
||
var success = false;
|
||
lock (_locker)
|
||
{
|
||
var trialConfig = _repository.Where<Trial>(t => t.Id == trialId).Select(t => new { TrialId = t.Id, t.QCProcessEnum, t.IsImageConsistencyVerification }).FirstOrDefault();
|
||
|
||
if (trialConfig == null) return Null404NotFound(trialConfig);
|
||
|
||
#region 处理访视状态变更
|
||
//var dbSubjectVisit = _subjectVisitRepository.FirstOrDefault(t => t.Id == qaQuestionCommand.SubjectVisitId);
|
||
|
||
//if (trialConfig.QCProcessEnum == TrialQCProcess.NotAudit)
|
||
//{
|
||
// return ResponseOutput.NotOk("项目配置为不审,不允许添加QA质疑 ");
|
||
//}
|
||
//else if (trialConfig.QCProcessEnum == TrialQCProcess.SingleAudit)
|
||
//{
|
||
// if ((int)dbSubjectVisit.AuditState == (int)SubjectVisitStateEnum.Submitted)
|
||
// {
|
||
// dbSubjectVisit.AuditState = AuditStateEnum.InPrimaryQC;
|
||
// }
|
||
// else
|
||
// {
|
||
// return ResponseOutput.NotOk($"项目配置为单审,当前审核状态不为{SubjectVisitStateEnum.Submitted},不允许添加QA质疑 ");
|
||
// }
|
||
//}
|
||
//else if (trialConfig.QCProcessEnum == TrialQCProcess.DoubleAudit)
|
||
//{
|
||
// if ((int)dbSubjectVisit.AuditState == (int)SubjectVisitStateEnum.Submitted)
|
||
// {
|
||
// dbSubjectVisit.AuditState = AuditStateEnum.InPrimaryQC;
|
||
// }
|
||
// else if ((int)dbSubjectVisit.AuditState == (int)SubjectVisitStateEnum.PrimaryQCPassed)
|
||
// {
|
||
// dbSubjectVisit.AuditState = AuditStateEnum.InSecondaryQC;
|
||
// }
|
||
// else
|
||
// {
|
||
// return ResponseOutput.NotOk($"项目配置为双审,访视状态为 {SubjectVisitStateEnum.Submitted}或{SubjectVisitStateEnum.PrimaryQCPassed}才允许添加QA质疑 ");
|
||
// }
|
||
//}
|
||
|
||
#endregion
|
||
|
||
//获取编号
|
||
var code = _repository.Where<QCChallenge>(t => t.TrialId == trialId).Select(t => t.ChallengeCode).DefaultIfEmpty().Max();
|
||
|
||
qcChallenge = _mapper.Map<QCChallenge>(qaQuestionCommand);
|
||
|
||
qcChallenge.QCProcessEnum = trialConfig.QCProcessEnum;
|
||
qcChallenge.CurrentQCEnum = currentQCType;
|
||
qcChallenge.TrialId = trialId;
|
||
qcChallenge.CreateUser = _userInfo.RealName;
|
||
qcChallenge.ChallengeCode = code + 1;
|
||
qcChallenge.UserTypeEnum = (UserTypeEnum)_userInfo.UserTypeEnumInt;
|
||
_ = _repository.AddAsync(qcChallenge).Result;
|
||
|
||
success = _repository.SaveChangesAsync().Result;
|
||
}
|
||
|
||
|
||
//分开两个事务 处理访视质疑状态
|
||
await DealChallengeState(qcChallenge.SubjectVisitId);
|
||
|
||
return ResponseOutput.Result(success, qcChallenge.Id);
|
||
|
||
#region 添加的时候把记录给留存
|
||
//var templateItems = _mapper.Map<List<QARecordTemplateItemDetail>>(visitQaCommand.QATrialTemplateItemList);
|
||
//templateItems.ForEach(u =>
|
||
//{
|
||
// u.IQA = _userInfo.RealName;
|
||
// u.IQACreateTime = DateTime.Now;
|
||
// u.IQANote = visitQaCommand.QARecord.Note;
|
||
// u.IQADeadline = visitQaCommand.QARecord.DeadlineTime;
|
||
// u.QARecordId = qaRecord.Id;
|
||
// qaRecord.QARecordTemplateItemDetailList.Add(u);
|
||
//});
|
||
|
||
////添加了QA记录 引用了QA模板后就不允许删除和编辑,需要维护项目模板状态 传统做法
|
||
//var qaTrailTemplate = _qaTrailTemplateRepository.FirstOrDefault(t => t.Id == visitQaCommand.QARecord.QATrialTemplateId);
|
||
//qaTrailTemplate.Status = QATrialTemplateStatus.HasQuote;
|
||
#endregion
|
||
|
||
}
|
||
else
|
||
{
|
||
|
||
var qcChallenge = await _repository.FirstOrDefaultAsync<QCChallenge>(t => t.Id == qaQuestionCommand.Id);
|
||
|
||
if (qcChallenge == null) return Null404NotFound(qcChallenge);
|
||
|
||
_mapper.Map(qaQuestionCommand, qcChallenge);
|
||
|
||
var success = await _repository.SaveChangesAsync();
|
||
|
||
await DealChallengeState(qcChallenge.SubjectVisitId);
|
||
|
||
|
||
return ResponseOutput.Result(success);
|
||
|
||
}
|
||
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 关闭质疑,什么情况下允许?
|
||
/// </summary>
|
||
/// <param name="qcChallengeId"></param>
|
||
/// <param name="subjectVisitId"></param>
|
||
/// <param name="closeEnum"></param>
|
||
/// <param name="closeReason"></param>
|
||
/// <returns></returns>
|
||
[HttpPost("{trialId:guid}/{qcChallengeId:guid}/{subjectVisitId:guid}/{closeEnum}/{closeReason}")]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
//[Authorize(Policy = "ImageQCPolicy")]
|
||
public async Task<IResponseOutput> CloseQCChallenge(Guid qcChallengeId, Guid subjectVisitId, [FromRoute] QCChallengeCloseEnum closeEnum, [FromRoute] string closeReason)
|
||
{
|
||
|
||
var dbQCChallenge = await _repository.FirstOrDefaultAsync<QCChallenge>(t => t.Id == qcChallengeId);
|
||
|
||
if (dbQCChallenge == null) return Null404NotFound(dbQCChallenge);
|
||
|
||
if (dbQCChallenge.ReuploadEnum == QCChanllengeReuploadEnum.CRCRequestReupload || dbQCChallenge.ReuploadEnum == QCChanllengeReuploadEnum.QCAgreeUpload)
|
||
{
|
||
return ResponseOutput.NotOk("CRC申请重传的/QC同意重传的质疑,在CRC设置重传完成前不允许关闭质疑");
|
||
}
|
||
|
||
dbQCChallenge.CloseResonEnum = closeEnum;
|
||
|
||
dbQCChallenge.IsClosed = true;
|
||
|
||
dbQCChallenge.ClosedTime = DateTime.Now;
|
||
|
||
|
||
dbQCChallenge.DialogList.Add(new QCChallengeDialog()
|
||
{
|
||
SubjectVisitId = dbQCChallenge.SubjectVisitId,
|
||
UserTypeEnum = (UserTypeEnum)_userInfo.UserTypeEnumInt,
|
||
QCChallengeId = dbQCChallenge.Id,
|
||
TalkContent = "关闭原因: " + closeReason
|
||
});
|
||
|
||
var success = await _repository.SaveChangesAsync();
|
||
|
||
await DealChallengeState(subjectVisitId);
|
||
|
||
return ResponseOutput.Result(success);
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 访视级别统计 质疑最新的状态
|
||
/// </summary>
|
||
/// <param name="subjectVisitId"></param>
|
||
private async Task DealChallengeState(Guid subjectVisitId)
|
||
{
|
||
var sv = await _repository.FirstOrDefaultAsync<SubjectVisit>(t => t.Id == subjectVisitId);
|
||
|
||
|
||
var closedStateList = await _repository.Where<QCChallenge>(t => t.SubjectVisitId == subjectVisitId).Select(t => t.IsClosed).ToListAsync();
|
||
|
||
if (closedStateList.Count == 0)
|
||
{
|
||
sv.ChallengeState = ChallengeStateEnum.No;
|
||
}
|
||
else if (closedStateList.All(t => t is true))
|
||
{
|
||
sv.ChallengeState = ChallengeStateEnum.HaveAndAllClosed;
|
||
}
|
||
else
|
||
{
|
||
sv.ChallengeState = ChallengeStateEnum.HaveAndHaveNotClosed;
|
||
}
|
||
await _repository.SaveChangesAsync();
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 删除QC质疑记录
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[HttpDelete("{qcChallengeId:guid}/{trialId:guid}")]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
[Authorize(Policy = "ImageQCPolicy")]
|
||
public async Task<IResponseOutput> DeleteQCChallenge(Guid qcChallengeId)
|
||
{
|
||
|
||
if (await _repository.AnyAsync<QCChallengeDialog>(t => t.QCChallengeId == qcChallengeId))
|
||
{
|
||
ResponseOutput.NotOk("this QC Challenge Has been replied ");
|
||
}
|
||
|
||
var qaRecord = await _repository.FirstOrDefaultAsync<QCChallenge>(t => t.Id == qcChallengeId);
|
||
|
||
if (qaRecord == null) return Null404NotFound(qaRecord);
|
||
|
||
await _repository.DeleteAsync(qaRecord);
|
||
|
||
|
||
var success1 = await _repository.SaveChangesAsync();
|
||
|
||
return ResponseOutput.Result(success1 /*|| success2 || success3*/);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 针对 某条QC质疑 添加回复
|
||
/// </summary>
|
||
/// <param name="qaDialogCommand"></param>
|
||
/// <returns></returns>
|
||
[HttpPost("{trialId:guid}")]
|
||
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
[Authorize(Policy = "ImageQCPolicy")]
|
||
public async Task<IResponseOutput> AddQCChallengeReply(QADialogCommand qaDialogCommand)
|
||
{
|
||
var qaReply = _mapper.Map<QCChallengeDialog>(qaDialogCommand);
|
||
|
||
await _repository.AddAsync(qaReply);
|
||
|
||
qaReply.CreateUser = _userInfo.RealName;
|
||
qaReply.UserTypeEnum = (UserTypeEnum)_userInfo.UserTypeEnumInt;
|
||
|
||
var dbQCChallenge = await _repository.FirstOrDefaultAsync<QCChallenge>(t => t.Id == qaDialogCommand.QCChallengeId);
|
||
|
||
|
||
if (dbQCChallenge == null) return Null404NotFound(dbQCChallenge);
|
||
|
||
dbQCChallenge.LatestMsgTime = DateTime.Now;
|
||
|
||
|
||
dbQCChallenge.LatestReplyUserId = _userInfo.Id;
|
||
|
||
|
||
var success = await _repository.SaveChangesAsync();
|
||
|
||
|
||
return ResponseOutput.Result(success, qaReply);
|
||
}
|
||
|
||
#endregion
|
||
|
||
|
||
#region 一致性核查
|
||
|
||
/// <summary>
|
||
/// 一致性核查 质疑的添加/回复
|
||
/// </summary>
|
||
/// <param name="checkDialogCommand"></param>
|
||
/// <returns></returns>
|
||
[HttpPost("{trialId:guid}")]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
[Authorize(Policy = "ImageCheckPolicy")]
|
||
public async Task<IResponseOutput<CheckChallengeDialog>> AddCheckChallengeReply(CheckChallengeDialogCommand checkDialogCommand)
|
||
{
|
||
var qaReply = _mapper.Map<CheckChallengeDialog>(checkDialogCommand);
|
||
|
||
qaReply.CreateUser = _userInfo.RealName;
|
||
qaReply.UserTypeEnum = (UserTypeEnum)_userInfo.UserTypeEnumInt;
|
||
|
||
await _repository.AddAsync(qaReply);
|
||
|
||
//修改一致性核查 质疑状态
|
||
var sv = await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == checkDialogCommand.SubjectVisitId);
|
||
|
||
|
||
if (_userInfo.UserTypeEnumInt == (int)UserTypeEnum.ClinicalResearchCoordinator)
|
||
{
|
||
sv.CheckChallengeState = CheckChanllengeTypeEnum.CRCWaitPMReply;
|
||
|
||
}
|
||
else if (_userInfo.UserTypeEnumInt == (int)UserTypeEnum.ProjectManager)
|
||
{
|
||
sv.CheckChallengeState = CheckChanllengeTypeEnum.PMWaitCRCReply;
|
||
}
|
||
else
|
||
{
|
||
throw new Exception("一致性核查对话操作用户 只允许 CRC/PM");
|
||
}
|
||
|
||
var success = await _repository.SaveChangesAsync();
|
||
|
||
return ResponseOutput.Result(success, qaReply);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 关闭 一致性核查质疑
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[HttpPut("{trialId:guid}/{subjectVisitId:guid}")]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
public async Task<IResponseOutput> CloseCheckChallenge(Guid subjectVisitId)
|
||
{
|
||
|
||
var sv = await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == subjectVisitId);
|
||
|
||
if (sv == null) return Null404NotFound(sv);
|
||
|
||
if (sv.RequestBackState == RequestBackStateEnum.PM_AgressBack)
|
||
{
|
||
ResponseOutput.NotOk("执行一致性核查的访视 不允许关闭质疑!");
|
||
}
|
||
|
||
sv.CheckChallengeState = CheckChanllengeTypeEnum.Closed;
|
||
|
||
await _repository.SaveChangesAsync();
|
||
|
||
return ResponseOutput.Ok(sv);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 手动设置一致性核查通过
|
||
/// </summary>
|
||
/// <param name="subjectVisitId"></param>
|
||
/// <param name="signId"></param>
|
||
/// <returns></returns>
|
||
[HttpPut("{trialId:guid}/{signId:guid}/{subjectVisitId:guid}")]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
public async Task<IResponseOutput> SetCheckPass(Guid subjectVisitId, Guid signId)
|
||
{
|
||
if (_userInfo.UserTypeEnumInt != (int)UserTypeEnum.ProjectManager)
|
||
{
|
||
ResponseOutput.NotOk("只允许PM 手动设置一致性核查通过");
|
||
}
|
||
|
||
var sv = await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == subjectVisitId);
|
||
|
||
if (sv == null) return Null404NotFound(sv);
|
||
|
||
if (sv.RequestBackState == RequestBackStateEnum.PM_AgressBack)
|
||
{
|
||
ResponseOutput.NotOk("当前是PM同意回退,不允许设置一致性核查通过");
|
||
}
|
||
|
||
if (sv.CheckChallengeState != CheckChanllengeTypeEnum.Closed && sv.AuditState == AuditStateEnum.QCPassed)
|
||
{
|
||
ResponseOutput.NotOk("一致性核查质疑未关闭/审核状态不是通过,不允许设置一致性核查通过");
|
||
}
|
||
|
||
sv.CheckState = CheckStateEnum.CVPassed;
|
||
|
||
sv.ForwardState = ForwardStateEnum.ToForward;
|
||
|
||
sv.CheckPassedTime = DateTime.Now;
|
||
|
||
await _repository.SaveChangesAsync();
|
||
|
||
var signSuccess = await _repository.UpdateFromQueryAsync<TrialSign>(t => t.Id == signId, u => new TrialSign() { IsCompleted = true });
|
||
|
||
return ResponseOutput.Result(signSuccess);
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// CRC 请求回退
|
||
/// </summary>
|
||
/// <param name="subjectVisitId"></param>
|
||
/// <returns></returns>
|
||
[HttpPut("{trialId:guid}/{subjectVisitId:guid}")]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
public async Task<IResponseOutput> CRCRequstCheckBack(Guid subjectVisitId)
|
||
{
|
||
var sv = await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == subjectVisitId);
|
||
|
||
|
||
if (sv == null) return Null404NotFound(sv);
|
||
|
||
if (sv.CheckState == CheckStateEnum.CVPassed)
|
||
{
|
||
return ResponseOutput.NotOk("核查通过的数据不允许申请回退");
|
||
}
|
||
await _subjectVisitRepository.UpdateFromQueryAsync(t => t.Id == subjectVisitId, u => new SubjectVisit() { RequestBackState = RequestBackStateEnum.CRC_RequestBack });
|
||
|
||
return ResponseOutput.Ok();
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// 一致性核查 回退 对话记录不清除 只允许PM回退
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[HttpPut("{trialId:guid}/{signId:guid}/{subjectVisitId:guid}")]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
public async Task<IResponseOutput> CheckBack(Guid subjectVisitId, Guid signId)
|
||
{
|
||
if (_userInfo.UserTypeEnumInt != (int)UserTypeEnum.ProjectManager)
|
||
{
|
||
return ResponseOutput.NotOk(" 只允许PM 回退!");
|
||
}
|
||
|
||
var sv = await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == subjectVisitId);
|
||
|
||
if (sv == null) return Null404NotFound(sv);
|
||
|
||
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.RequestBackState = RequestBackStateEnum.NotRequest;
|
||
sv.IsCheckBack = true;
|
||
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执行了一致性核查回退", UserTypeEnum = (UserTypeEnum)_userInfo.UserTypeEnumInt });
|
||
|
||
await _repository.DeleteFromQueryAsync<TrialQCQuestionAnswer>(t => t.SubjectVisitId == subjectVisitId);
|
||
|
||
var success = await _repository.SaveChangesAsync();
|
||
|
||
var signSuccess = await _repository.UpdateFromQueryAsync<TrialSign>(t => t.Id == signId, u => new TrialSign() { IsCompleted = true });
|
||
|
||
return ResponseOutput.Result(signSuccess && success);
|
||
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 一致性核查 excel上传 支持三种格式
|
||
/// </summary>
|
||
/// <param name="file"></param>
|
||
/// <param name="_mediator"></param>
|
||
/// <param name="trialId"></param>
|
||
/// <param name="_hostEnvironment"></param>
|
||
/// <returns></returns>
|
||
[HttpPost("{trialId:guid}")]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
public async Task<IResponseOutput> UploadVisitCheckExcel(IFormFile file, [FromServices] IMediator _mediator, Guid trialId, [FromServices] IWebHostEnvironment _hostEnvironment)
|
||
{
|
||
if (_userInfo.UserTypeEnumInt != (int)UserTypeEnum.ProjectManager)
|
||
{
|
||
return ResponseOutput.NotOk("只允许PM 操作");
|
||
}
|
||
|
||
var rootPath = Directory.GetParent(_hostEnvironment.ContentRootPath.TrimEnd('\\')).IfNullThrowException().FullName;
|
||
|
||
var fileName = ContentDispositionHeaderValue.Parse(file.ContentDisposition).FileName.ToString().Trim('"').ToLower();
|
||
if (!fileName.EndsWith(".xlsx") && !fileName.EndsWith(".csv") && !fileName.EndsWith(".xls"))
|
||
{
|
||
return ResponseOutput.NotOk("只允许Excel、 CSV!");
|
||
}
|
||
|
||
//上传根路径
|
||
string uploadFolderPath = Path.Combine(rootPath, "UploadFile", "Check");
|
||
|
||
if (!Directory.Exists(uploadFolderPath))
|
||
{
|
||
Directory.CreateDirectory(uploadFolderPath);
|
||
}
|
||
|
||
//存放核对表
|
||
var filePath = Path.Combine(uploadFolderPath, DateTime.Now.ToString("yyyy-MM-dd-hh-mm-ss") + fileName);
|
||
using (FileStream fs = System.IO.File.Create(filePath))
|
||
{
|
||
await file.CopyToAsync(fs);
|
||
await fs.FlushAsync();
|
||
}
|
||
|
||
//获取Excel表内容
|
||
var config = new MiniExcelLibs.Csv.CsvConfiguration()
|
||
{
|
||
StreamReaderFunc = (stream) => new StreamReader(stream, Encoding.GetEncoding("gb2312"))
|
||
};
|
||
|
||
var etcCheckList = new List<CheckViewModel>();
|
||
|
||
|
||
|
||
#region MiniExcel 需要自己验证数据格式规范
|
||
|
||
//if (fileName.EndsWith(".csv"))
|
||
//{
|
||
// //因为csv 需要加配置文件 不然都是null
|
||
// etcCheckList = MiniExcel.Query<CheckViewModel>(filePath, null, configuration: config).ToList();
|
||
//}
|
||
//else if (fileName.EndsWith(".xlsx"))
|
||
//{
|
||
//
|
||
|
||
|
||
// etcCheckList = MiniExcel.Query<CheckViewModel>(filePath).ToList();
|
||
//}
|
||
|
||
#endregion
|
||
|
||
//Magicodes 支持自定义特性验证
|
||
if (fileName.EndsWith(".xlsx"))
|
||
{
|
||
var Importer = new ExcelImporter();
|
||
|
||
var import = await Importer.Import<CheckViewModel>(File.OpenRead(filePath));
|
||
|
||
if (import.Exception != null) return ResponseOutput.NotOk(import.Exception.ToString());
|
||
|
||
if (import.RowErrors.Count > 0) return ResponseOutput.NotOk(JsonConvert.SerializeObject(import.RowErrors));
|
||
|
||
if (import.TemplateErrors.Count > 0) return ResponseOutput.NotOk(JsonConvert.SerializeObject(import.TemplateErrors));
|
||
|
||
etcCheckList = import.Data.ToList();
|
||
}
|
||
else if (fileName.EndsWith(".csv"))
|
||
{
|
||
|
||
//因为csv 需要加配置文件 不然都是null
|
||
etcCheckList = MiniExcel.Query<CheckViewModel>(filePath, null, configuration: config).ToList();
|
||
|
||
//var Importer = new CsvImporter();
|
||
|
||
//var import = await Importer.Import<CheckViewModel>(File.OpenRead(filePath));
|
||
|
||
//if (import.Exception != null) return ResponseOutput.NotOk(import.Exception.ToString());
|
||
|
||
//if (import.RowErrors.Count > 0) return ResponseOutput.NotOk(JsonConvert.SerializeObject(import.RowErrors));
|
||
|
||
//if (import.TemplateErrors.Count > 0) return ResponseOutput.NotOk(JsonConvert.SerializeObject(import.TemplateErrors));
|
||
|
||
//etcCheckList = import.Data.ToList();
|
||
}
|
||
//ExcelReaderFactory 需要自己验证数据 并且从固定列取数据
|
||
else
|
||
{
|
||
//为了支持 xls 引入新的组件库
|
||
using (var stream = System.IO.File.Open(filePath, FileMode.Open, FileAccess.Read))
|
||
{
|
||
// Auto-detect format, supports:
|
||
// - Binary Excel files (2.0-2003 format; *.xls)
|
||
// - OpenXml Excel files (2007 format; *.xlsx, *.xlsb)
|
||
using (var reader = ExcelReaderFactory.CreateReader(stream))
|
||
{
|
||
|
||
// 2. Use the AsDataSet extension method
|
||
var dateset = reader.AsDataSet();
|
||
|
||
foreach (DataRow col in dateset.Tables[0].Rows)
|
||
{
|
||
|
||
etcCheckList.Add(new CheckViewModel()
|
||
{
|
||
SiteCode = col[0].ToString(),
|
||
SubjectCode = col[1].ToString(),
|
||
VisitName = col[2].ToString(),
|
||
StudyDate = col[3].ToString(),
|
||
Modality = col[4].ToString(),
|
||
});
|
||
}
|
||
|
||
etcCheckList.Remove(etcCheckList[0]);
|
||
|
||
// The result of each spreadsheet is in result.Tables
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
//etcCheckList = etcCheckList.Where(t=>)
|
||
|
||
if (etcCheckList == null || etcCheckList.Count == 0)
|
||
{
|
||
return ResponseOutput.NotOk("请上传规定格式的Excel文件,保证有有效数据!");
|
||
}
|
||
else
|
||
{
|
||
//处理Excel 有时只是清除某些行的数据 读取也会读到数据,只是数据是null 后面处理的时候转为字符串为报错
|
||
etcCheckList.ForEach(t =>
|
||
{
|
||
t.Modality = t.Modality ?? string.Empty;
|
||
t.SiteCode = t.SiteCode ?? string.Empty;
|
||
t.SubjectCode = t.SubjectCode ?? string.Empty;
|
||
t.VisitName = t.VisitName ?? string.Empty;
|
||
t.StudyDate = t.StudyDate ?? string.Empty;
|
||
});
|
||
|
||
var dt = DateTime.Now;
|
||
|
||
etcCheckList = etcCheckList.Where(t => !(t.Modality == string.Empty && t.SiteCode == string.Empty && t.SubjectCode == string.Empty && t.VisitName == string.Empty && t.StudyDate == string.Empty && DateTime.TryParse(t.StudyDate, out dt))).ToList();
|
||
|
||
if (etcCheckList.Count == 0)
|
||
{
|
||
return ResponseOutput.NotOk("请上传规定格式的Excel文件,保证有有效数据!");
|
||
}
|
||
|
||
}
|
||
|
||
await _mediator.Send(new ConsistencyVerificationRequest() { ETCList = etcCheckList, TrialId = trialId });
|
||
|
||
return ResponseOutput.Ok();
|
||
|
||
}
|
||
|
||
#endregion
|
||
|
||
|
||
#region QC 核对问题 操作检查各种操作
|
||
|
||
/// <summary>
|
||
/// 添加或者更新 QC核对问题列表 两个人不能同时操作,就算意外进去了,提交数据,也不会覆盖前一个人数据, 后台已经做好判断
|
||
/// </summary>
|
||
[HttpPost("{trialId:guid}/{subjectVisitId:guid}/{trialQCProcess:int}/{currentQCType:int}")]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
public async Task<IResponseOutput> AddOrUpdateQCQuestionAnswerList(QCQuestionAnswerCommand[] qcQuestionAnswerCommands, Guid trialId, Guid subjectVisitId, [FromRoute] TrialQCProcess trialQCProcess, [FromRoute] CurrentQC currentQCType, [FromServices] IServiceProvider serviceProvider)
|
||
{
|
||
//验证是否能操作
|
||
var verifyResult = await VerifyQCCanOpt(subjectVisitId);
|
||
|
||
if (!verifyResult.IsSuccess)
|
||
{
|
||
return verifyResult;
|
||
}
|
||
|
||
//更新
|
||
if (qcQuestionAnswerCommands.Any(t => t.Id != null))
|
||
{
|
||
|
||
#region 先删除再添加
|
||
|
||
//await _repository.DeleteFromQueryAsync<TrialQCQuestionAnswer>(t => t.SubjectVisitId == subjectVisitId && t.QCProcessEnum == trialQCProcess && t.CurrentQCEnum == currentQCType);
|
||
|
||
//var addlist = _mapper.Map<List<TrialQCQuestionAnswer>>(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<TrialQCQuestionAnswer>(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<List<TrialQCQuestionAnswer>>(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());
|
||
}
|
||
|
||
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 1、设置为不读片,2 设置为读片(取消 先前设置为不读片) 4 设置为删除(数据库记录软删除) 5 恢复为未删除
|
||
/// </summary>
|
||
/// <param name="subjectVisitId"></param>
|
||
/// <param name="studyId"></param>
|
||
/// <param name="seriesId"></param>
|
||
/// <param name="state"></param>
|
||
/// <returns></returns>
|
||
[HttpPut("{trialId:guid}/{subjectVisitId:guid}/{studyId:guid}/{seriesId:guid}/{state:int}")]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
public async Task<IResponseOutput> SetSeriesState(Guid subjectVisitId, Guid studyId, Guid seriesId, int state)
|
||
{
|
||
//验证是否能操作
|
||
var verifyResult = await VerifyQCCanOpt(subjectVisitId);
|
||
|
||
if (!verifyResult.IsSuccess)
|
||
{
|
||
return verifyResult;
|
||
}
|
||
|
||
var series = await _repository.Where<DicomSeries>(t => t.Id == seriesId, true).IgnoreQueryFilters().FirstOrDefaultAsync();
|
||
|
||
if (series == null) return Null404NotFound(series);
|
||
|
||
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<DicomStudy>(t => t.Id == studyId, true).IgnoreQueryFilters().FirstOrDefaultAsync();
|
||
if (study == null) return Null404NotFound(study);
|
||
|
||
var instanceIdList = await _repository.Where<DicomInstance>(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<DicomStudy>(t => t.Id == studyId, true).IgnoreQueryFilters().FirstOrDefaultAsync();
|
||
|
||
if (study == null) return Null404NotFound(study);
|
||
|
||
var instanceIdList = await _repository.Where<DicomInstance>(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());
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
///type 1 :study 2: series 3:非dicom QC修改检查部位和 拍片类型
|
||
/// </summary>
|
||
/// <param name="id"></param>
|
||
/// <param name="type"></param>
|
||
/// <param name="modality"></param>
|
||
/// <param name="bodyPart"></param>
|
||
/// <returns></returns>
|
||
[HttpPut("{trialId:guid}/{id:guid}/{type:int}")]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
public async Task<IResponseOutput> UpdateModality(Guid id, int type, [FromQuery] string modality, [FromQuery] string bodyPart)
|
||
{
|
||
if (type == 1)
|
||
{
|
||
var study = await _repository.FirstOrDefaultAsync<DicomStudy>(t => t.Id == id);
|
||
if (study == null) return Null404NotFound(study);
|
||
|
||
study.BodyPartForEdit = bodyPart;
|
||
study.Modalities = modality;
|
||
await _repository.UpdateFromQueryAsync<DicomSeries>(t => t.StudyId == id, r => new DicomSeries() { BodyPartForEdit = bodyPart, Modality = modality });
|
||
|
||
}
|
||
else if (type == 2)
|
||
{
|
||
var series = await _repository.FirstOrDefaultAsync<DicomSeries>(t => t.Id == id);
|
||
if (series == null) return Null404NotFound(series);
|
||
series.BodyPartForEdit = bodyPart;
|
||
}
|
||
else if (type == 3)
|
||
{
|
||
|
||
}
|
||
await _repository.SaveChangesAsync();
|
||
|
||
return ResponseOutput.Ok();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证是否质疑都关闭了 可以审核通过和不通过
|
||
/// </summary>
|
||
/// <param name="subjectVisitId"></param>
|
||
/// <returns></returns>
|
||
[HttpGet("{subjectVisitId:guid}")]
|
||
public async Task<IResponseOutput> VerifyCanQCPassedOrFailed(Guid subjectVisitId)
|
||
{
|
||
|
||
if (await _repository.AnyAsync<QCChallenge>(t => t.SubjectVisitId == subjectVisitId && t.IsClosed == false))
|
||
{
|
||
return ResponseOutput.NotOk("有质疑未关闭,不允许该操作");
|
||
}
|
||
return ResponseOutput.Ok();
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 删除检查列表
|
||
/// </summary>
|
||
/// <param name="ids"></param>
|
||
/// <param name="subjectVisitId"></param>
|
||
/// <param name="trialId"></param>
|
||
/// <returns></returns>
|
||
[HttpPost, Route("{trialId:guid}/{subjectVisitId:guid}")]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
public async Task<IResponseOutput> DeleteStudyList(Guid[] ids, Guid subjectVisitId, Guid trialId)
|
||
{
|
||
//提交了 但是IQC同意的时候 是可以删除的
|
||
if (await _repository.AnyAsync<SubjectVisit>(t => t.Id == subjectVisitId && t.SubmitState == SubmitStateEnum.Submitted &&
|
||
(!t.QCChallengeList.Any(u => u.ReuploadEnum == QCChanllengeReuploadEnum.QCAgreeUpload))))
|
||
{
|
||
return ResponseOutput.NotOk("CRC Has Submited Image,can not delete");
|
||
}
|
||
|
||
|
||
#region will calls error wried
|
||
//ids.ToList().ForEach(async id =>
|
||
//{
|
||
// var success1 = await _repository.DeleteFromQueryAsync<DicomStudy>(t => t.Id == id);
|
||
// var succeess2 = await _repository.DeleteFromQueryAsync<DicomInstance>(t => t.StudyId == id);
|
||
// var success3 = await _repository.DeleteFromQueryAsync<DicomSeries>(t => t.StudyId == id);
|
||
//});
|
||
#endregion
|
||
|
||
foreach (var id in ids)
|
||
{
|
||
var success1 = await _repository.DeleteFromQueryAsync<DicomStudy>(t => t.Id == id);
|
||
var succeess2 = await _repository.DeleteFromQueryAsync<DicomInstance>(t => t.StudyId == id);
|
||
var success3 = await _repository.DeleteFromQueryAsync<DicomSeries>(t => t.StudyId == id);
|
||
|
||
//删除 物理文件
|
||
|
||
var instanceIdList = await _repository.Where<DicomInstance>(t => t.StudyId == id)
|
||
.Select(t => new { InstanceId = t.Id, t.SeriesId, t.StudyId, t.SubjectId, t.SiteId }).ToListAsync();
|
||
|
||
instanceIdList.ForEach(t =>
|
||
{
|
||
var path = _dicomFileStoreHelper.GetInstanceFilePath(new DicomStudy() { Id = t.StudyId, SubjectId = t.SubjectId, TrialId = trialId, SiteId = t.SiteId, SubjectVisitId = subjectVisitId }, t.SeriesId, t.InstanceId.ToString());
|
||
|
||
if (System.IO.File.Exists(path))
|
||
{
|
||
File.Delete(path);
|
||
}
|
||
|
||
});
|
||
}
|
||
|
||
|
||
|
||
//一个访视下面有多个检查,所以需要检测 没有的时候才清空 非dicom 是检查文件 不是表记录
|
||
if (await _repository.CountAsync<DicomStudy>(t => t.SubjectVisitId == subjectVisitId) == 0 && await _repository.CountAsync<NoneDicomStudyFile>(t => t.NoneDicomStudy.SubjectVisitId == subjectVisitId) == 0)
|
||
{
|
||
await _repository.UpdateFromQueryAsync<SubjectVisit>(t => t.Id == subjectVisitId && t.SubmitState == SubmitStateEnum.ToSubmit,
|
||
u => new SubjectVisit() { VisitExecuted = 0, SVENDTC = null, SVSTDTC = null, SubmitState = SubmitStateEnum.None });
|
||
|
||
//_qaNoticeRepository.Delete(t => t.SubjectVisitId == subjectVisitId);
|
||
//_qaNoticeUserRepository.Delete(t => t.SubjectVisitId == subjectVisitId);
|
||
//_qcChallengeRepository.Delete(t => t.SubjectVisitId == subjectVisitId);
|
||
//_qcChallengeDialogRepository.Delete(t => t.SubjectVisitId == subjectVisitId);
|
||
//_trialQCQuestionAnswerRepository.Delete(t => t.SubjectVisitId == subjectVisitId);
|
||
//_checkChallengeDialogRepository.Delete(t => t.SubjectVisitId == subjectVisitId);
|
||
}
|
||
|
||
|
||
var svTime = await _repository.Where<SubjectVisit>(t => t.Id == subjectVisitId).Select(t => new
|
||
{
|
||
DicomStudyMinStudyTime = t.StudyList.Min(t => (DateTime?)t.StudyTime),
|
||
DicomStudyMaxStudyTime = t.StudyList.Max(t => (DateTime?)t.StudyTime),
|
||
NoneDicomStudyMinStudyTime = t.NoneDicomStudyList.Min(t => (DateTime?)t.ImageDate),
|
||
NoneDicomStudyMaxStudyTime = t.NoneDicomStudyList.Max(t => (DateTime?)t.ImageDate)
|
||
}).FirstOrDefaultAsync().IfNullThrowException();
|
||
|
||
var minArray = new DateTime?[] { svTime.DicomStudyMinStudyTime, svTime.NoneDicomStudyMinStudyTime };
|
||
var maxArray = new DateTime?[] { svTime.DicomStudyMaxStudyTime, svTime.NoneDicomStudyMaxStudyTime };
|
||
|
||
await _repository.UpdateFromQueryAsync<SubjectVisit>(t => t.Id == subjectVisitId, u => new SubjectVisit()
|
||
{
|
||
EarliestScanDate = minArray.Min(),
|
||
|
||
LatestScanDate = maxArray.Max()
|
||
});
|
||
|
||
return ResponseOutput.Ok();
|
||
}
|
||
|
||
#endregion
|
||
|
||
|
||
|
||
#region 临床数据签名 领取、 设置紧急、RequestToQC QC通过、不通过
|
||
//[HttpPut("{trialId:guid}/{subjectVisitId:guid}")]
|
||
//public async Task<IResponseOutput> SignClinicalData(Guid trialId, Guid subjectVisitId)
|
||
//{
|
||
// await _repository.UpdateFromQueryAsync<SubjectVisit>(t => t.Id == subjectVisitId, u => new SubjectVisit() { ClinicalDataSignTime = DateTime.Now, ClinicalDataSignUserId = _userInfo.Id });
|
||
|
||
// return ResponseOutput.Ok();
|
||
//}
|
||
|
||
|
||
/// <summary>
|
||
/// 手动领取 或者取消 QC任务
|
||
/// </summary>
|
||
/// <param name="trialId"></param>
|
||
/// <param name="subjectVisitId"></param>
|
||
/// <param name="obtaionOrCancel">true 获取 false是取消领取</param>
|
||
/// <returns></returns>
|
||
[HttpPost("{trialId:guid}/{subjectVisitId:guid}/{obtaionOrCancel:bool}")]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
public async Task<IResponseOutput> ObtainOrCancelQCTask(Guid trialId, Guid subjectVisitId, bool obtaionOrCancel)
|
||
{
|
||
|
||
var dbSubjectVisit = await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == subjectVisitId);
|
||
|
||
if (dbSubjectVisit == null) return Null404NotFound(dbSubjectVisit);
|
||
|
||
if (obtaionOrCancel)
|
||
{
|
||
|
||
if (await _subjectVisitRepository.AnyAsync(t => t.Trial.QCQuestionConfirmedUserId == null && t.Id == subjectVisitId))
|
||
{
|
||
return ResponseOutput.NotOk("QC问题未确认,不允许领取任务");
|
||
}
|
||
|
||
|
||
if (await _subjectVisitRepository.AnyAsync(t => t.IsTake && t.SubjectId != dbSubjectVisit.SubjectId && t.CurrentActionUserId == _userInfo.Id && t.TrialId == dbSubjectVisit.TrialId))
|
||
{
|
||
return ResponseOutput.NotOk("您已经领取了其他受试者,完成后才允许领取新的受试者");
|
||
}
|
||
|
||
#region 处理验证
|
||
|
||
var trialConfig = await _trialRepository
|
||
.Select(t => new { TrialId = t.Id, t.QCProcessEnum, t.IsImageConsistencyVerification })
|
||
.FirstOrDefaultAsync(t => t.TrialId == trialId)
|
||
.IfNullThrowException();
|
||
|
||
if (dbSubjectVisit.IsTake)
|
||
{
|
||
return ResponseOutput.NotOk("当前已被领取,不允许领取");
|
||
}
|
||
|
||
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<IObtainTaskAutoCancelJob>(t => t.CancelQCObtaion(subjectVisitId, DateTime.Now), TimeSpan.FromHours(1));
|
||
}
|
||
else
|
||
{
|
||
dbSubjectVisit.IsTake = false;
|
||
|
||
dbSubjectVisit.CurrentActionUserId = null;
|
||
|
||
|
||
dbSubjectVisit.CurrentActionUserExpireTime = null;
|
||
|
||
}
|
||
var success = await _repository.SaveChangesAsync();
|
||
return ResponseOutput.Result(success);
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// CRC RequestToQC 批量提交
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[HttpPost]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
public async Task<IResponseOutput> CRCRequestToQC(CRCRequestToQCCommand cRCRequestToQCCommand)
|
||
{
|
||
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);
|
||
|
||
if (trialConfig == null) return Null404NotFound(trialConfig);
|
||
|
||
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数据 不允许提交");
|
||
}
|
||
|
||
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*/)
|
||
{
|
||
//已确认临床数据完整性
|
||
dbSubjectVisit.IsConfirmedClinicalData = true;
|
||
|
||
var signSuccess = await _repository.UpdateFromQueryAsync<TrialSign>(t => t.Id == cRCRequestToQCCommand.SignId, u => new TrialSign() { IsCompleted = true });
|
||
|
||
|
||
////现在修改为 提交时 设置签名信息
|
||
//dbSubjectVisit.ClinicalDataSignUserId = _userInfo.Id;
|
||
//dbSubjectVisit.ClinicalDataSignTime = DateTime.Now;
|
||
|
||
//那么没有录入 不允许提交
|
||
//if (!await _repository.AnyAsync<SubjectVisit>(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))
|
||
{
|
||
dbSubjectVisit.IsUrgent = true;
|
||
}
|
||
|
||
|
||
if (dbSubjectVisit.SubmitState == SubmitStateEnum.ToSubmit || dbSubjectVisit.SubmitState == SubmitStateEnum.Submitted)
|
||
{
|
||
dbSubjectVisit.SubmitState = SubmitStateEnum.Submitted;
|
||
dbSubjectVisit.SubmitTime = DateTime.Now;
|
||
|
||
}
|
||
|
||
//不审 直接QC通过 可能一致性核查 也可能不一致性核查
|
||
if (trialConfig.QCProcessEnum == TrialQCProcess.NotAudit)
|
||
{
|
||
dbSubjectVisit.AuditState = AuditStateEnum.QCPassed;
|
||
|
||
// 不一致性核查 就CVPassed ToForward 否则就是待核查
|
||
dbSubjectVisit.CheckState = trialConfig.IsImageConsistencyVerification ? CheckStateEnum.ToCheck : CheckStateEnum.CVPassed;
|
||
|
||
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;
|
||
|
||
|
||
}
|
||
|
||
|
||
}
|
||
|
||
|
||
var success = await _repository.SaveChangesAsync();
|
||
|
||
|
||
return ResponseOutput.Result(success);
|
||
|
||
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置QC 通过或者不通过 7:QC failed 8:QC passed
|
||
/// </summary>
|
||
/// <param name="trialId"></param>
|
||
/// <param name="subjectVisitId"></param>
|
||
/// <param name="signId"></param>
|
||
/// <param name="auditState"></param>
|
||
/// <returns></returns>
|
||
[HttpPost("{trialId:guid}/{subjectVisitId:guid}/{signId:guid}/{auditState:int}")]
|
||
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
public async Task<IResponseOutput> QCPassedOrFailed(Guid trialId, Guid subjectVisitId, Guid signId, [FromRoute] AuditStateEnum auditState)
|
||
{
|
||
|
||
if (!await _repository.AnyAsync<TrialUser>(t => t.TrialId == trialId && t.UserId == _userInfo.Id))
|
||
{
|
||
return ResponseOutput.NotOk("您已经被移出项目,不允许该操作");
|
||
}
|
||
|
||
//判断质疑是否都关闭了
|
||
if (await _repository.AnyAsync<QCChallenge>(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);
|
||
|
||
if (dbSubjectVisit == null) return Null404NotFound(dbSubjectVisit);
|
||
|
||
//有人QC Passed
|
||
if (auditState == AuditStateEnum.QCPassed)
|
||
{
|
||
//判断 QC流程 不审 单审 双审
|
||
|
||
if (trialConfig.QCProcessEnum == TrialQCProcess.NotAudit)
|
||
{
|
||
return ResponseOutput.NotOk("不审状态下,不允许设置为QC Passed");
|
||
|
||
}
|
||
else if (trialConfig.QCProcessEnum == TrialQCProcess.SingleAudit)
|
||
{
|
||
if (dbSubjectVisit.AuditState == AuditStateEnum.InPrimaryQC)
|
||
{
|
||
if (!await _repository.AnyAsync<TrialQCQuestionAnswer>(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.IsTake = false;
|
||
dbSubjectVisit.CurrentActionUserExpireTime = null;
|
||
|
||
|
||
}
|
||
else
|
||
{
|
||
return ResponseOutput.NotOk("项目配置为单审 当前审核状态不为 InPrimaryQC,不能变更到 QCPassed");
|
||
}
|
||
|
||
}
|
||
else if (trialConfig.QCProcessEnum == TrialQCProcess.DoubleAudit)
|
||
{
|
||
|
||
// 双审 如果当前db 状态是 InPrimaryQC 当前操作为 QCPassed 那么设置为 PrimaryQCPassed
|
||
if (dbSubjectVisit.AuditState == AuditStateEnum.InPrimaryQC)
|
||
{
|
||
|
||
if (!await _repository.AnyAsync<TrialQCQuestionAnswer>(t => t.SubjectVisitId == subjectVisitId && t.CurrentQCEnum == CurrentQC.First))
|
||
{
|
||
return ResponseOutput.NotOk("必须填写审核问题,并保存,否则不允许提交");
|
||
}
|
||
|
||
dbSubjectVisit.AuditState = AuditStateEnum.PrimaryQCPassed;
|
||
dbSubjectVisit.PreliminaryAuditUserId = _userInfo.Id;
|
||
dbSubjectVisit.PreliminaryAuditTime = DateTime.Now;
|
||
|
||
dbSubjectVisit.IsTake = false;
|
||
dbSubjectVisit.CurrentActionUserExpireTime = null;
|
||
}
|
||
else if (dbSubjectVisit.AuditState == AuditStateEnum.InSecondaryQC)
|
||
{
|
||
|
||
if (!await _repository.AnyAsync<TrialQCQuestionAnswer>(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.IsTake = false;
|
||
dbSubjectVisit.CurrentActionUserExpireTime = null;
|
||
|
||
}
|
||
else
|
||
{
|
||
return ResponseOutput.NotOk($"项目配置为双审 当前审核状态为 {dbSubjectVisit.AuditState},不能变更到 QCPassed");
|
||
}
|
||
}
|
||
}
|
||
|
||
else if (auditState == AuditStateEnum.QCFailed)
|
||
{
|
||
//判断 QC流程 不审 单审 双审
|
||
|
||
if (trialConfig.QCProcessEnum == TrialQCProcess.NotAudit)
|
||
{
|
||
return ResponseOutput.NotOk("不审状态下,不允许设置为QC Failed");
|
||
}
|
||
else if (trialConfig.QCProcessEnum == TrialQCProcess.SingleAudit)
|
||
{
|
||
|
||
if (dbSubjectVisit.AuditState == AuditStateEnum.InPrimaryQC)
|
||
{
|
||
// 单审
|
||
dbSubjectVisit.AuditState = AuditStateEnum.QCFailed;
|
||
}
|
||
else
|
||
{
|
||
return ResponseOutput.NotOk("项目配置为单审 当前审核状态不为 InPrimaryQC,不能变更到 QCFailed");
|
||
}
|
||
}
|
||
else if (trialConfig.QCProcessEnum == TrialQCProcess.DoubleAudit)
|
||
{
|
||
// 双审
|
||
if (dbSubjectVisit.AuditState == AuditStateEnum.InPrimaryQC || dbSubjectVisit.AuditState == AuditStateEnum.InSecondaryQC)
|
||
{
|
||
dbSubjectVisit.AuditState = AuditStateEnum.QCFailed;
|
||
}
|
||
else
|
||
{
|
||
return ResponseOutput.NotOk($"项目配置为双审 当前审核状态为 {dbSubjectVisit.AuditState},不能变更到 QCFailed");
|
||
}
|
||
|
||
|
||
}
|
||
}
|
||
|
||
//删除 软删除的物理文件
|
||
|
||
var instanceIdList = await _repository.Where<DicomInstance>(t => t.DicomSerie.IsDeleted && t.SubjectVisitId == subjectVisitId)
|
||
.Select(t => new { InstanceId = t.Id, t.SeriesId, t.StudyId, t.SubjectId, t.SiteId }).ToListAsync();
|
||
|
||
instanceIdList.ForEach(t =>
|
||
{
|
||
var path = _dicomFileStoreHelper.GetInstanceFilePath(new DicomStudy() { Id = t.StudyId, SubjectId = t.SubjectId, TrialId = trialId, SiteId = t.SiteId, SubjectVisitId = subjectVisitId }, t.SeriesId, t.InstanceId.ToString());
|
||
|
||
if (System.IO.File.Exists(path))
|
||
{
|
||
File.Delete(path);
|
||
}
|
||
|
||
});
|
||
|
||
await _repository.SaveChangesAsync();
|
||
|
||
var success = await _repository.UpdateFromQueryAsync<TrialSign>(t => t.Id == signId, u => new TrialSign() { IsCompleted = true });
|
||
|
||
return ResponseOutput.Result(success);
|
||
|
||
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 设置、取消 访视紧急
|
||
/// </summary>
|
||
/// <param name="trialId"></param>
|
||
/// <param name="subjectVisitId"></param>
|
||
/// <param name="setOrCancel"></param>
|
||
/// <returns></returns>
|
||
[HttpPut("{trialId:guid}/{subjectVisitId:guid}/{setOrCancel:bool}")]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
public async Task<IResponseOutput> SetVisitUrgent(Guid trialId, Guid subjectVisitId, bool setOrCancel)
|
||
{
|
||
var sv = await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == subjectVisitId);
|
||
if (sv == null) return Null404NotFound(sv);
|
||
|
||
sv.IsUrgent = setOrCancel;
|
||
var success = await _repository.SaveChangesAsync();
|
||
return ResponseOutput.Ok();
|
||
}
|
||
|
||
#endregion
|
||
|
||
|
||
#region 重传、重传完设置
|
||
|
||
/// <summary>
|
||
/// QA设置 需要重传
|
||
/// </summary>
|
||
/// <param name="trialId"></param>
|
||
/// <param name="signId"></param>
|
||
/// <param name="qcChallengeId"></param>
|
||
/// <returns></returns>
|
||
[HttpPut("{trialId:guid}/{qcChallengeId:guid}/{signId:guid}")]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
public async Task<IResponseOutput> SetNeedReupload(Guid trialId, Guid signId, Guid qcChallengeId)
|
||
{
|
||
if (_userInfo.UserTypeEnumInt != (int)UserTypeEnum.IQC)
|
||
{
|
||
return ResponseOutput.NotOk("重传 只允许QA 设置!");
|
||
}
|
||
|
||
//获取项目配置
|
||
var trialConfig = await _repository.Where<Trial>(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 _repository.FirstOrDefaultAsync<QCChallenge>(t => t.Id == qcChallengeId);
|
||
if (qcChallenge == null) return Null404NotFound(qcChallenge);
|
||
|
||
if (await _repository.CountAsync<QCChallenge>(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.UpdateFromQueryAsync(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<QCChallenge>(t => t.Id == qcChallengeId && t.SubjectVisit.AuditState == AuditStateEnum.InSecondaryQC))
|
||
{
|
||
|
||
var sv = await _subjectVisitRepository.FirstOrDefaultAsync(t => t.Id == qcChallenge.SubjectVisitId);
|
||
|
||
if (sv == null) return Null404NotFound(sv);
|
||
|
||
// //一致性核查质疑状态
|
||
// sv.CheckChallengeState = CheckChanllengeTypeEnum.None;
|
||
//// 一致性核查状态
|
||
// sv.CheckState = CheckStateEnum.None;
|
||
// 审核状态
|
||
sv.AuditState = AuditStateEnum.InPrimaryQC;
|
||
|
||
sv.CurrentActionUserExpireTime = DateTime.Now.AddHours(1);
|
||
sv.CurrentActionUserId = _userInfo.Id;
|
||
BackgroundJob.Schedule<IObtainTaskAutoCancelJob>(t => t.CancelQCObtaion(qcChallenge.SubjectVisitId, DateTime.Now), TimeSpan.FromHours(1));
|
||
sv.IsTake = true;
|
||
|
||
sv.PreliminaryAuditUserId = null;
|
||
sv.ReviewAuditUserId = null;
|
||
|
||
|
||
//删除1QC 填写的问题答案
|
||
|
||
await _repository.DeleteFromQueryAsync<TrialQCQuestionAnswer>(t => t.SubjectVisitId == qcChallenge.SubjectVisitId && t.CurrentQCEnum == CurrentQC.First);
|
||
|
||
//2QC 数据变为1QC
|
||
await _repository.UpdateFromQueryAsync<QCChallenge>(t => t.SubjectVisitId == qcChallenge.SubjectVisitId && t.CurrentQCEnum == CurrentQC.Second, k => new QCChallenge() { CurrentQCEnum = CurrentQC.First });
|
||
await _repository.UpdateFromQueryAsync<TrialQCQuestionAnswer>(t => t.SubjectVisitId == qcChallenge.SubjectVisitId && t.CurrentQCEnum == CurrentQC.Second, k => new TrialQCQuestionAnswer() { CurrentQCEnum = CurrentQC.First });
|
||
|
||
}
|
||
|
||
|
||
var success = await _repository.SaveChangesAsync();
|
||
|
||
var signSuccess = await _repository.UpdateFromQueryAsync<TrialSign>(t => t.Id == signId, u => new TrialSign() { IsCompleted = true });
|
||
|
||
return ResponseOutput.Result(success && signSuccess);
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// CRC 设置已经重传完成
|
||
/// </summary>
|
||
|
||
/// <returns></returns>
|
||
[HttpPost]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
public async Task<IResponseOutput> SetReuploadFinished(CRCReuploadFinishedCommand cRCReuploadFinishedCommand)
|
||
{
|
||
if (_userInfo.UserTypeEnumInt != (int)UserTypeEnum.ClinicalResearchCoordinator)
|
||
{
|
||
return ResponseOutput.NotOk("重传完成 只允许CRC 设置!");
|
||
}
|
||
|
||
var trialConfig = await _trialRepository
|
||
.Select(t => new { TrialId = t.Id, t.QCProcessEnum, t.IsImageConsistencyVerification, t.IsUrgent, t.IsHaveFirstGiveMedicineDate, t.ClinicalInformationTransmissionEnum })
|
||
.FirstOrDefaultAsync(t => t.TrialId == cRCReuploadFinishedCommand.TrialId);
|
||
|
||
|
||
var qcChallenge = await _repository.FirstOrDefaultAsync<QCChallenge>(t => t.Id == cRCReuploadFinishedCommand.QCChallengeId);
|
||
|
||
if (qcChallenge == null) return Null404NotFound(qcChallenge);
|
||
|
||
qcChallenge.ReuploadEnum = QCChanllengeReuploadEnum.CRCReuploaded;
|
||
|
||
qcChallenge.ReUploadedTime = DateTime.Now;
|
||
|
||
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.UpdateFromQueryAsync(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.UpdateFromQueryAsync<TrialSign>(t => t.Id == cRCReuploadFinishedCommand.SignId, u => new TrialSign() { IsCompleted = true });
|
||
|
||
|
||
}
|
||
|
||
var success = await _repository.SaveChangesAsync();
|
||
|
||
|
||
return ResponseOutput.Ok(success);
|
||
|
||
}
|
||
|
||
|
||
|
||
[HttpPut("{trialId:guid}/{qcChallengeId:guid}")]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
public async Task<IResponseOutput> CRCRequestReUpload(Guid qcChallengeId)
|
||
{
|
||
var qcChallenge = await _repository.FirstOrDefaultAsync<QCChallenge>(t => t.Id == qcChallengeId);
|
||
|
||
if (qcChallenge == null) return Null404NotFound(qcChallenge);
|
||
|
||
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申请重传/上传影像"
|
||
});
|
||
|
||
await _repository.SaveChangesAsync();
|
||
|
||
return ResponseOutput.Ok();
|
||
}
|
||
|
||
#endregion
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 上传界面 更新受试者首次给药日期 是否入组确认,以及访视 是否PD进展
|
||
/// </summary>
|
||
/// <param name="command"></param>
|
||
/// <returns></returns>
|
||
[HttpPut("{trialId:guid}")]
|
||
[TypeFilter(typeof(TrialResourceFilter))]
|
||
public async Task<IResponseOutput> UpdateSubjectAndSVInfo(UploadSubjectAndVisitCommand command)
|
||
{
|
||
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("CRC已提交了 不能修改入组确认状态");
|
||
}
|
||
|
||
if (await _subjectVisitRepository.Where(t => t.Id == command.SubjectVisitId)
|
||
.AnyAsync(t => t.IsEnrollmentConfirm != command.IsEnrollmentConfirm && t.RequestBackState == RequestBackStateEnum.PM_AgressBack))
|
||
{
|
||
return ResponseOutput.NotOk("回退的访视,不允许修改PD确认状态");
|
||
}
|
||
|
||
await _repository.UpdateFromQueryAsync<SubjectVisit>(t => t.Id == command.SubjectVisitId, u => new SubjectVisit()
|
||
{
|
||
IsEnrollmentConfirm = command.IsEnrollmentConfirm.Value,
|
||
});
|
||
}
|
||
|
||
if (command.SubjectFirstGiveMedicineTime != null)
|
||
{
|
||
await _repository.UpdateFromQueryAsync<Subject>(t => t.Id == command.SubjectId, u => new Subject()
|
||
{
|
||
FirstGiveMedicineTime = command.SubjectFirstGiveMedicineTime,
|
||
|
||
});
|
||
}
|
||
|
||
////受试者基线 入组确认 或者访视PD 进展 默认加急
|
||
//await _repository.UpdateFromQueryAsync<SubjectVisit>(t => t.Id == command.SubjectVisitId, u => new SubjectVisit()
|
||
//{
|
||
// PDState = command.PDState,
|
||
// IsUrgent = (command.IsEnrollmentConfirm == true) || (command.PDState == PDStateEnum.PDProgress)
|
||
|
||
//});
|
||
|
||
return ResponseOutput.Ok();
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 验证QC是否可以操作 数据库查询判断当前QC执行人和登陆的用户是否一致
|
||
/// </summary>
|
||
/// <param name="subjectVisitId"></param>
|
||
/// <returns></returns>
|
||
private async Task<IResponseOutput> VerifyQCCanOpt(Guid subjectVisitId)
|
||
{
|
||
var optUser = await _subjectVisitRepository.Where(t => t.Id == subjectVisitId).FirstOrDefaultAsync();
|
||
|
||
if (optUser == null) return Null404NotFound(optUser);
|
||
|
||
if (optUser.CurrentActionUserId != _userInfo.Id)
|
||
{
|
||
return ResponseOutput.NotOk("其他QC正在进行审核,当前操作不允许");
|
||
}
|
||
|
||
return ResponseOutput.Ok();
|
||
}
|
||
|
||
|
||
|
||
[HttpGet("{trialId:guid}/{subjectVisitId:guid}")]
|
||
public async Task<IResponseOutput> ForwardSVDicomImage(Guid subjectVisitId, [FromServices] DicomFileStoreHelper _dicomFileStoreHelper)
|
||
{
|
||
|
||
|
||
var info = await _subjectVisitRepository.Where(t => t.Id == subjectVisitId).ProjectTo<DicomTrialSiteSubjectInfo>(_mapper.ConfigurationProvider).FirstOrDefaultAsync();
|
||
|
||
var targetPath = Path.Combine("/IMPORT-IMAGES", info.TrialCode + "_" + info.SubjectCode + "_" + info.VisitName);
|
||
|
||
var path = _dicomFileStoreHelper.GetSubjectVisitPath(info.TrialId, info.SiteId, info.SubjectId, info.SubjectVisitId);
|
||
|
||
try
|
||
{
|
||
// 主机及端口信息后面可以改到 配置文件
|
||
SessionOptions sessionOptions = new SessionOptions
|
||
{
|
||
Protocol = Protocol.Sftp,
|
||
PortNumber = 8022,
|
||
HostName = "CS-690-sftp.mint-imaging.com",
|
||
UserName = "zdong",
|
||
Password = "Everest@2021",
|
||
//GiveUpSecurityAndAcceptAnySshHostKey = true,
|
||
SshHostKeyFingerprint = @"ecdsa-sha2-nistp384 384 59gkjJ5lMwv3jsB8Wz2B35tBAIor5pSd8PcJYtoamPo="
|
||
};
|
||
|
||
using (Session session = new Session())
|
||
{
|
||
session.Open(sessionOptions);
|
||
if (!session.FileExists(targetPath))
|
||
{
|
||
session.CreateDirectory(targetPath);
|
||
}
|
||
|
||
var files = (new DirectoryInfo(path)).GetFiles();
|
||
|
||
foreach (var file in files)
|
||
{
|
||
if (file.Extension.Contains("dcm"))
|
||
{
|
||
string remoteFilePath =
|
||
RemotePath.TranslateLocalPathToRemote(file.FullName, path, targetPath);
|
||
|
||
var result = session.PutFiles(file.FullName, remoteFilePath, false);
|
||
|
||
if (!result.IsSuccess)
|
||
{
|
||
await _subjectVisitRepository.UpdateFromQueryAsync(t => t.Id == subjectVisitId,
|
||
u => new SubjectVisit() { ForwardState = ForwardStateEnum.ForwardFailed });
|
||
|
||
return ResponseOutput.NotOk("Forward Failed");
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
}
|
||
|
||
}
|
||
catch (Exception e)
|
||
{
|
||
await _subjectVisitRepository.UpdateFromQueryAsync(t => t.Id == subjectVisitId,
|
||
u => new SubjectVisit() { ForwardState = ForwardStateEnum.ForwardFailed });
|
||
|
||
}
|
||
|
||
await _subjectVisitRepository.UpdateFromQueryAsync(t => t.Id == subjectVisitId,
|
||
u => new SubjectVisit() { ForwardState = ForwardStateEnum.Forwarded });
|
||
|
||
return ResponseOutput.Ok();
|
||
}
|
||
|
||
}
|
||
}
|