irc-netcore-api/IRaCIS.Core.Application/Service/Visit/SubjectVisitService.cs

586 lines
26 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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