using IRaCIS.Application.Interfaces; using IRaCIS.Application.Contracts; using IRaCIS.Core.Application.Filter; using Microsoft.AspNetCore.Mvc; using IRaCIS.Core.Domain.Share; using IRaCIS.Core.Application.MediatR.CommandAndQueries; using Magicodes.ExporterAndImporter.Core; using Magicodes.ExporterAndImporter.Excel; using Magicodes.ExporterAndImporter.Excel.AspNetCore; using IRaCIS.Core.Infrastructure; using Microsoft.AspNetCore.Authorization; using IRaCIS.Core.Application.Auth; namespace IRaCIS.Application.Services { [ApiExplorerSettings(GroupName = "Trial")] public class VisitPlanService : BaseService, IVisitPlanService { private readonly IRepository _visitStageRepository; private readonly IRepository _trialRepository; private readonly IRepository _subjectVisitRepository; private readonly IRepository _influnceRepository; private readonly IRepository _subjectRepository; private readonly IRepository _visitPlanInfluenceStatRepository; public VisitPlanService(IRepository visitStageRepository, IRepository trialRepository, IRepository subjectVisitRepository, IRepository visitPlanInfluenceStudy, IRepository subjectRepository, IRepository visitPlanInfluenceStatRepository) { _visitStageRepository = visitStageRepository; _trialRepository = trialRepository; _subjectVisitRepository = subjectVisitRepository; _influnceRepository = visitPlanInfluenceStudy; _visitPlanInfluenceStatRepository = visitPlanInfluenceStatRepository; _subjectRepository = subjectRepository; } ///暂时不用 /// 获取项目访视计划 [HttpPost] public async Task> GetTrialVisitStageList(VisitPlanQueryDTO param) { var visitStageQuery = _visitStageRepository.AsQueryable(true).Where(u => u.TrialId == param.TrialId) .WhereIf(!string.IsNullOrWhiteSpace(param.Keyword), t => t.VisitName.Contains(param.Keyword)) .ProjectTo(_mapper.ConfigurationProvider); return await visitStageQuery.ToPagedListAsync(param.PageIndex, param.PageSize, "CreateTime", param.Asc); } /// 根据项目Id,获取项目访视计划(不分页)[New] [HttpGet("{trialId:guid}")] public async Task GetVisitStageList(Guid trialId) { var query = _visitStageRepository.AsQueryable(true).Where(u => u.TrialId == trialId) .ProjectTo(_mapper.ConfigurationProvider).OrderBy(t => t.VisitNum); var list = await query.ToListAsync(); var trial = (await _repository.FirstOrDefaultAsync(t => t.Id == trialId)).IfNullThrowException(); var isHaveGeneratedTask = (await _repository.AnyAsync(t => t.TrialId == trialId)); return new VisitPlanView() { VisitPlanList = list, TimePointsPerPatient = trial.TimePointsPerPatient, VisitPlanConfirmed = trial.VisitPlanConfirmed, IsHaveFirstGiveMedicineDate = trial.IsHaveFirstGiveMedicineDate, BlindBaseLineName=trial.BlindBaseLineName, BlindFollowUpPrefix=trial.BlindFollowUpPrefix, IsHaveGeneratedTask= isHaveGeneratedTask, //SubjectHasAdded = _subjectVisitRepository.Any(t => t.TrialId == trialId) }; } [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt", "AfterStopCannNotOpt" })] public async Task UpdateVisitBlindName(VisitBlindNameCommand command) { await _trialRepository.UpdatePartialFromQueryAsync(command.TrialId, t => new Trial() { BlindBaseLineName = command.BlindBaseLineName, BlindFollowUpPrefix = command.BlindFollowUpPrefix }); await _trialRepository.SaveChangesAsync(); return ResponseOutput.Ok(); } /// /// 获取访视计划下拉框列表 /// /// /// [HttpGet("{trialId:guid}")] public async Task> GetTrialVisitStageSelect(Guid trialId) { var query = _visitStageRepository.Where(u => u.TrialId == trialId) .ProjectTo(_mapper.ConfigurationProvider).OrderBy(t => t.VisitNum); var list = await query.ToListAsync(); return list; } /// 添加或更新访视计划某项 [UnitOfWork] [HttpPost] [Authorize(Policy = IRaCISPolicy.PM_APM)] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt", "AfterStopCannNotOpt" })] //[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task AddOrUpdateVisitStage(VisitPlanCommand visitPlan) { if (!await _trialRepository.Where(t => t.Id == visitPlan.TrialId).IgnoreQueryFilters().AnyAsync(t => t.TrialStatusStr == StaticData.TrialState.TrialOngoing || t.TrialStatusStr == StaticData.TrialState.TrialInitializing)) { //---只有当项目状态为:初始化或进行中时,可以操作。 throw new BusinessValidationFailedException(_localizer["VisitPlan_OnlyInitOrOngoing"]); } var visitPlanList = await _visitStageRepository.Where(t => t.TrialId == visitPlan.TrialId, ignoreQueryFilters: true) .Select(t => new { t.Id, t.VisitNum, t.VisitDay }).OrderBy(t => t.VisitNum).ToListAsync(); //更新的时候,需要排除自己 if (visitPlan.Id != null) { visitPlanList = visitPlanList.Where(t => t.Id != visitPlan.Id).ToList(); } if (visitPlanList.Any(t => t.VisitNum < visitPlan.VisitNum)) { //比当前 visitNum小的 visitDay的最大值 还小 不允许添加 if (visitPlan.VisitDay <= visitPlanList.Where(t => t.VisitNum < visitPlan.VisitNum).Select(t => t.VisitDay).Max()) { //---访视计划中,访视号大的访视,其访视间隔也应该比访视号小的访视大。 throw new BusinessValidationFailedException(_localizer["VisitPlan_LargerVisitNumLargerInterval"]); } } if (visitPlanList.Any(t => t.VisitNum > visitPlan.VisitNum)) { if (visitPlan.VisitDay >= visitPlanList.Where(t => t.VisitNum > visitPlan.VisitNum).Select(t => t.VisitDay).Min()) { //---访视计划中,访视号大的计划访视,其访视间隔也应该比访视号小的计划访视大。 throw new BusinessValidationFailedException(_localizer["VisitPlan_LargerPlanNumLargerInterval"]); } } if (visitPlan.Id == Guid.Empty || visitPlan.Id == null)//add { var trial = (await _trialRepository.FirstOrDefaultAsync(t => t.Id == visitPlan.TrialId)).IfNullThrowException(); if (await _visitStageRepository.AnyAsync(t => t.TrialId == visitPlan.TrialId && (t.VisitName == visitPlan.VisitName || t.VisitNum == visitPlan.VisitNum), true)) { //---访视计划中已经存在具有项目访视名称或者访视号的计划访视模板。 throw new BusinessValidationFailedException(_localizer["VisitPlan_ExistNameOrNumTemplate"]); } if (await _visitStageRepository.AnyAsync(t => t.TrialId == visitPlan.TrialId && t.IsBaseLine, true) && visitPlan.IsBaseLine) { //---访视计划中已经存在基线。 throw new BusinessValidationFailedException(_localizer["VisitPlan_ExistBaseline"]); } //不用前端传递的值 visitPlan.BlindName = "B" + ((int)visitPlan.VisitNum * 10).ToString("D3"); var visitPlanItem = await _visitStageRepository.InsertFromDTOAsync(visitPlan, true); return ResponseOutput.Ok(visitPlanItem.Id); } else//update { if (await _visitStageRepository.AnyAsync(t => t.TrialId == visitPlan.TrialId && (t.VisitName == visitPlan.VisitName || t.VisitNum == visitPlan.VisitNum) && t.Id != visitPlan.Id, true)) { //---访视计划中已经存在具有项目访视名称或者访视号的计划访视模板。 throw new BusinessValidationFailedException(_localizer["VisitPlan_ExistNameOrNumTemplate"]); } if (await _visitStageRepository.AnyAsync(t => t.TrialId == visitPlan.TrialId && t.IsBaseLine && t.Id != visitPlan.Id, true) && visitPlan.IsBaseLine) { //---访视计划中已经存在基线。 throw new BusinessValidationFailedException(_localizer["VisitPlan_ExistBaseline"]); } visitPlan.IsConfirmed = false; //不用前端传递的值 visitPlan.BlindName = "B" + ((int)visitPlan.VisitNum * 10).ToString("D3"); //返回的是数据库查询的数据 var stage = await _visitStageRepository.UpdateFromDTOAsync(visitPlan, true); if (stage.IsBaseLine && stage.IsBaseLine != visitPlan.IsBaseLine) { if (await _repository.Where(t => t.TrialId == visitPlan.TrialId).AnyAsync(v => v.IsBaseLine && v.SubmitState >= SubmitStateEnum.ToSubmit)) { //---有受试者的基线已经上传了影像数据,不允许修改基线访视。 throw new BusinessValidationFailedException(_localizer["VisitPlan_ExistBaselineImgNoModify"]); } } return ResponseOutput.Ok(); } } [UnitOfWork] [HttpPost("{trialId:guid}")] [Authorize(Policy = IRaCISPolicy.PM_APM)] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "BeforeOngoingCantOpt", "AfterStopCannNotOpt" })] //[TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task ConfirmTrialVisitPlan(Guid trialId) { if (!await _trialRepository.AnyAsync(t => t.Id == trialId && (t.TrialStatusStr == StaticData.TrialState.TrialInitializing || t.TrialStatusStr == StaticData.TrialState.TrialOngoing))) { //---仅仅在项目初始化或者进行中时,才允许修改确认 return ResponseOutput.NotOk(_localizer["VisitPlan_OnlyInitOrOngoingModifyConfirm"]); } if (!await _visitStageRepository.AnyAsync(t => t.TrialId == trialId && t.IsBaseLine)) { //---没有基线,不允许确认 return ResponseOutput.NotOk(_localizer["VisitPlan_NoBaselineNotConfirm"]); } if (!await _trialRepository.AnyAsync(t => t.Id == trialId && t.IsTrialBasicLogicConfirmed && t.IsTrialProcessConfirmed && t.IsTrialUrgentConfirmed)) { //---项目配置未确认,不允许确认访视计划 return ResponseOutput.NotOk(_localizer["VisitPlan_ConfigNotConfirmNotConfirm"]); } var svList = await _visitStageRepository.Where(t => t.TrialId == trialId).Select(u => new { u.VisitDay, u.IsBaseLine }).ToListAsync(); if (svList.Min(t => t.VisitDay) != svList.Where(t => t.IsBaseLine).FirstOrDefault()?.VisitDay) { //---基线VisitDay 不是最小的, 不允许确认 return ResponseOutput.NotOk(_localizer["VisitPlan_BaselineNotMinDayNotConfirm"]); } //更新项目访视计划状态为已确认 必定生成更新的sql 通过状态改变 触发操作 //await _trialRepository.UpdatePartialNowNoQueryAsync(trialId, t => new Trial() { VisitPlanConfirmed = true }); var trial = (await _trialRepository.FirstOrDefaultAsync(t => t.Id == trialId)).IfNullThrowException(); //确认是否是首次确认 var initTrialVisitPlanConfirmed = trial.VisitPlanConfirmed; trial.VisitPlanConfirmed = true; #region 统一给Subject 增加访视任务 //首次确认时 IsHaveFirstConfirmed都为false 不需要给Subject 加访视 if (initTrialVisitPlanConfirmed) { List subjectVisits = new List(); var addvisitStages = await _visitStageRepository.Where(x => !x.IsHaveFirstConfirmed && x.TrialId == trialId).ToListAsync(); foreach (var visitPlan in addvisitStages) { var subjectSVS = await _subjectVisitRepository.Where(t => t.TrialId == visitPlan.TrialId).Select(t => new { t.SubjectId, t.SiteId, t.IsFinalVisit }).Distinct().ToListAsync(); var subjectList = subjectSVS.Select(t => new { t.SubjectId, t.SiteId }).Distinct().ToList(); foreach (var subject in subjectList) { var svItem = _mapper.Map(visitPlan); svItem.SiteId = subject.SiteId; svItem.SubjectId = subject.SubjectId; //设置了末次访视,那么加访视计划的时候,设置为不可用 if (subjectSVS.Any(t => t.SubjectId == svItem.SubjectId && t.IsFinalVisit)) { svItem.VisitExecuted = VisitExecutedEnum.Unavailable; } //再加一层保险,防止重复 if (!await _subjectVisitRepository.AnyAsync(t => t.VisitStageId == visitPlan.Id && t.SubjectId == subject.SubjectId)) { await _subjectVisitRepository.AddAsync(svItem); } } } await _subjectVisitRepository.AddRangeAsync(subjectVisits); } #endregion #region 访视计划修改 影响检查 //找到访视计划修改的Item var changedList = await _visitStageRepository.Where(t => t.TrialId == trial.Id && t.IsConfirmed == false) .Select(t => new { t.Trial.IsHaveFirstGiveMedicineDate, t.Id, t.VisitName, t.TrialId, t.VisitWindowLeft, t.VisitWindowRight, t.VisitDay, t.VisitNum, t.IsBaseLine, t.BlindName, t.Description, IsConfirmed = true, }).ToListAsync(); var visitPlanInfluenceStat = new VisitPlanInfluenceStat() { TrialId = trial.Id }; foreach (var changedItem in changedList) { //找到该项目 访视已经执行,并且配置了有首次给药日期 并且更新后超窗的访视,要把超窗之前的值也要查询出来 var qcPassedVisitList = await _subjectVisitRepository.Where(t => t.TrialId == trialId && t.VisitExecuted == VisitExecutedEnum.Executed && t.AuditState == AuditStateEnum.QCPassed && t.Trial.IsHaveFirstGiveMedicineDate == true && t.VisitStageId == changedItem.Id && t.Subject.FirstGiveMedicineTime != null ).Select(k => new { SubjectVisitId = k.Id, SelfWindowLeft = k.Subject.FirstGiveMedicineTime!.Value.AddDays(k.VisitDay + k.VisitWindowLeft), SelfWindowRight = k.Subject.FirstGiveMedicineTime!.Value.AddDays(k.VisitDay + k.VisitWindowRight + 1).AddSeconds(-1), NowWindowLeft = k.Subject.FirstGiveMedicineTime!.Value.AddDays(changedItem.VisitDay + changedItem.VisitWindowLeft), NowWindowRight = k.Subject.FirstGiveMedicineTime!.Value.AddDays(changedItem.VisitDay + changedItem.VisitWindowRight + 1).AddSeconds(-1), NoneDicomStudyList = k.NoneDicomStudyList //之前是查询调整之后超窗的 现在调整前超窗 调整后 没超窗的也要记录 //.Where(study => study.ImageDate k.Subject.FirstGiveMedicineTime.Value.AddDays(changedItem.VisitDay + changedItem.VisitWindowRight??0 + 1).AddSeconds(-1)) .Select(t => new { NoneDicomStudyId = t.Id, t.Modality, StudyTime = t.ImageDate }), DicomStudyList = k.StudyList //.Where(study => study.StudyTime k.Subject.FirstGiveMedicineTime.Value.AddDays(changedItem.VisitDay + changedItem.VisitWindowRight??0 + 1).AddSeconds(-1)) .Select(t => new { StudyId = t.Id, Modality = t.Modalities, t.StudyTime }) }).ToListAsync(); foreach (var visit in qcPassedVisitList) { //找到本身没有超窗的数据 修改后超窗的 visit.DicomStudyList.Where(t => (t.StudyTime > visit.SelfWindowLeft && t.StudyTime < visit.SelfWindowRight) && (t.StudyTime < visit.NowWindowLeft || t.StudyTime > visit.NowWindowRight)).ForEach(t => { visitPlanInfluenceStat.InconsistentCount++; visitPlanInfluenceStat.InfluenceStudyList.Add(new VisitPlanInfluenceStudy() { IsOverWindowNowNotOverWindow = false, Modality = t.Modality, SubjectVisitId = visit.SubjectVisitId, StudyId = t.StudyId, IsDicomStudy = true, StudyTime = t.StudyTime, TrialId = trialId, HistoryWindow = visit.SelfWindowLeft.ToString("yyyy-MM-dd") + " ~ " + visit.SelfWindowRight.ToString("yyyy-MM-dd"), NowWindow = visit.NowWindowLeft.ToString("yyyy-MM-dd") + " ~ " + visit.NowWindowRight.ToString("yyyy-MM-dd") }); }); visit.NoneDicomStudyList.Where(t => (t.StudyTime > visit.SelfWindowLeft && t.StudyTime < visit.SelfWindowRight) && (t.StudyTime < visit.NowWindowLeft || t.StudyTime > visit.NowWindowRight)).ForEach(t => { visitPlanInfluenceStat.InconsistentCount++; visitPlanInfluenceStat.InfluenceStudyList.Add(new VisitPlanInfluenceStudy() { IsOverWindowNowNotOverWindow = false, Modality = t.Modality, SubjectVisitId = visit.SubjectVisitId, StudyId = t.NoneDicomStudyId, IsDicomStudy = false, StudyTime = t.StudyTime, TrialId = trialId, HistoryWindow = visit.SelfWindowLeft.ToString("yyyy-MM-dd") + " ~ " + visit.SelfWindowRight.ToString("yyyy-MM-dd"), NowWindow = visit.NowWindowLeft.ToString("yyyy-MM-dd") + " ~ " + visit.NowWindowRight.ToString("yyyy-MM-dd") }); }); //本身超窗 修改后没超窗的 visit.DicomStudyList.Where(t => (t.StudyTime < visit.SelfWindowLeft || t.StudyTime > visit.SelfWindowRight) && (t.StudyTime > visit.NowWindowLeft && t.StudyTime < visit.NowWindowRight)).ForEach(t => { visitPlanInfluenceStat.InconsistentCount++; visitPlanInfluenceStat.InfluenceStudyList.Add(new VisitPlanInfluenceStudy() { IsOverWindowNowNotOverWindow = true, Modality = t.Modality, SubjectVisitId = visit.SubjectVisitId, StudyId = t.StudyId, IsDicomStudy = true, StudyTime = t.StudyTime, TrialId = trialId, HistoryWindow = visit.SelfWindowLeft.ToString("yyyy-MM-dd") + " ~ " + visit.SelfWindowRight.ToString("yyyy-MM-dd"), NowWindow = visit.NowWindowLeft.ToString("yyyy-MM-dd") + " ~ " + visit.NowWindowRight.ToString("yyyy-MM-dd") }); }); visit.NoneDicomStudyList.Where(t => (t.StudyTime < visit.SelfWindowLeft || t.StudyTime > visit.SelfWindowRight) && (t.StudyTime > visit.NowWindowLeft && t.StudyTime < visit.NowWindowRight)).ForEach(t => { visitPlanInfluenceStat.InconsistentCount++; visitPlanInfluenceStat.InfluenceStudyList.Add(new VisitPlanInfluenceStudy() { IsOverWindowNowNotOverWindow = true, Modality = t.Modality, SubjectVisitId = visit.SubjectVisitId, StudyId = t.NoneDicomStudyId, IsDicomStudy = false, StudyTime = t.StudyTime, TrialId = trialId, HistoryWindow = visit.SelfWindowLeft.ToString("yyyy-MM-dd") + " ~ " + visit.SelfWindowRight.ToString("yyyy-MM-dd"), NowWindow = visit.NowWindowLeft.ToString("yyyy-MM-dd") + " ~ " + visit.NowWindowRight.ToString("yyyy-MM-dd") }); }); } //变更某一访视计划Item 受试者访视相关字段 await _subjectVisitRepository.BatchUpdateNoTrackingAsync(t => t.TrialId == trialId && t.VisitStageId == changedItem.Id, k => new SubjectVisit() { IsBaseLine = changedItem.IsBaseLine, VisitName = changedItem.VisitName, VisitNum = changedItem.VisitNum, VisitDay = changedItem.VisitDay, VisitWindowLeft = changedItem.VisitWindowLeft, VisitWindowRight = changedItem.VisitWindowRight }); } await _visitPlanInfluenceStatRepository.AddAsync(visitPlanInfluenceStat, true); #endregion //访视计划 整体状态变更为 确认 await _visitStageRepository.UpdatePartialFromQueryAsync(u => u.TrialId == trialId && u.IsConfirmed == false, t => new VisitStage() { IsConfirmed = true, IsHaveFirstConfirmed = true }); await _visitStageRepository.SaveChangesAsync(); return ResponseOutput.Ok(); } [HttpGet("{trialId:guid}")] public async Task> GetInfluenceHistoryList(Guid trialId, [FromServices] IRepository _influnceStatRepository) { var list = await _influnceStatRepository.Where(t => t.TrialId == trialId).ProjectTo(_mapper.ConfigurationProvider).OrderByDescending(t => t.CreateTime).ToListAsync(); return list; } [HttpGet("{visitPlanInfluenceStatId:guid}")] public async Task DownloadInflunceStudyList(Guid visitPlanInfluenceStatId) { var list = _influnceRepository.Where(t => t.VisitPlanInfluenceStatId == visitPlanInfluenceStatId) .ProjectTo(_mapper.ConfigurationProvider).ToList(); IExporter exporter = new ExcelExporter(); var result = await exporter.ExportAsByteArray(list); //$"检查导出_{DateTime.Now.ToString("yyyy-MM-dd:hh:mm:ss")}.xlsx" return new XlsxFileResult(bytes: result, fileDownloadName: _localizer["VisitPlan_CheckExport", DateTime.Now.ToString("yyyy-MM-dd:hh:mm:ss")]); } /// 删除项目计划某一项 废弃 [HttpDelete("{id:guid}/{trialId:guid}")] [Obsolete] public async Task DeleteVisitStage(Guid id) { var visitPlan = await _visitStageRepository.FirstOrDefaultAsync(t => t.Id == id); if (visitPlan == null) return Null404NotFound(visitPlan); if (await _repository.AnyAsync(t => t.VisitName == visitPlan.VisitName && t.TrialId == visitPlan.TrialId && t.VisitExecuted == VisitExecutedEnum.Executed)) { //---The visit plan has been assigned to the subjects and executed. return ResponseOutput.NotOk(_localizer["VisitPlan_Assigned"]); } await _repository.BatchDeleteAsync(t => t.TrialId == visitPlan.TrialId && t.VisitName == visitPlan.VisitName); var result = await _visitStageRepository.BatchDeleteNoTrackingAsync(u => u.Id == id); return ResponseOutput.Result(result); } } }