EI-Image-Viewer-Api/IRaCIS.Core.Application/Service/Visit/VisitPlanService.cs

513 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.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<VisitStage> _visitStageRepository;
private readonly IRepository<Trial> _trialRepository;
private readonly IRepository<SubjectVisit> _subjectVisitRepository;
private readonly IRepository<VisitPlanInfluenceStudy> _influnceRepository;
private readonly IRepository<Subject> _subjectRepository;
private readonly IRepository<VisitPlanInfluenceStat> _visitPlanInfluenceStatRepository;
public VisitPlanService(IRepository<VisitStage> visitStageRepository, IRepository<Trial> trialRepository, IRepository<SubjectVisit> subjectVisitRepository,
IRepository<VisitPlanInfluenceStudy> visitPlanInfluenceStudy, IRepository<Subject> subjectRepository, IRepository<VisitPlanInfluenceStat> visitPlanInfluenceStatRepository)
{
_visitStageRepository = visitStageRepository;
_trialRepository = trialRepository;
_subjectVisitRepository = subjectVisitRepository;
_influnceRepository = visitPlanInfluenceStudy;
_visitPlanInfluenceStatRepository = visitPlanInfluenceStatRepository;
_subjectRepository = subjectRepository;
}
///暂时不用
/// <summary> 获取项目检查批次计划</summary>
[HttpPost]
public async Task<PageOutput<VisitStageDTO>> 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<VisitStageDTO>(_mapper.ConfigurationProvider);
return await visitStageQuery.ToPagedListAsync(param.PageIndex, param.PageSize, "CreateTime", param.Asc);
}
/// <summary> 根据项目Id获取项目检查批次计划(不分页)[New]</summary>
[HttpGet("{trialId:guid}")]
public async Task<VisitPlanView> GetVisitStageList(Guid trialId)
{
var query = _visitStageRepository.AsQueryable(true).Where(u => u.TrialId == trialId)
.ProjectTo<VisitStageDTO>(_mapper.ConfigurationProvider).OrderBy(t => t.VisitNum);
var list = await query.ToListAsync();
var trial = (await _repository.FirstOrDefaultAsync<Trial>(t => t.Id == trialId)).IfNullThrowException();
var isHaveGeneratedTask = (await _repository.AnyAsync<VisitTask>(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<IResponseOutput> UpdateVisitBlindName(VisitBlindNameCommand command)
{
await _trialRepository.UpdatePartialFromQueryAsync(command.TrialId, t => new Trial() { BlindBaseLineName = command.BlindBaseLineName, BlindFollowUpPrefix = command.BlindFollowUpPrefix });
await _trialRepository.SaveChangesAsync();
return ResponseOutput.Ok();
}
/// <summary>
/// 获取检查批次计划下拉框列表
/// </summary>
/// <param name="trialId"></param>
/// <returns></returns>
[HttpGet("{trialId:guid}")]
public async Task<IEnumerable<VisitStageSelectDTO>> GetTrialVisitStageSelect(Guid trialId)
{
var query = _visitStageRepository.Where(u => u.TrialId == trialId)
.ProjectTo<VisitStageSelectDTO>(_mapper.ConfigurationProvider).OrderBy(t => t.VisitNum);
var list = await query.ToListAsync();
return list;
}
/// <summary> 添加或更新检查批次计划某项</summary>
[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<IResponseOutput> 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("只有当项目状态为:初始化或进行中时,可以操作。 ");
}
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("检查批次计划中,检查批次号大的检查批次,其检查批次间隔也应该比检查批次号小的检查批次大。");
}
}
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("检查批次计划中,检查批次号大的计划检查批次,其检查批次间隔也应该比检查批次号小的计划检查批次大。");
}
}
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("检查批次计划中已经存在具有项目检查批次名称或者检查批次号的计划检查批次模板。");
}
if (await _visitStageRepository.AnyAsync(t => t.TrialId == visitPlan.TrialId && t.IsBaseLine, true) && visitPlan.IsBaseLine)
{
throw new BusinessValidationFailedException("检查批次计划中已经存在基线。");
}
//不用前端传递的值
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("检查批次计划中已经存在具有项目检查批次名称或者检查批次号的计划检查批次模板。");
}
if (await _visitStageRepository.AnyAsync(t => t.TrialId == visitPlan.TrialId && t.IsBaseLine && t.Id != visitPlan.Id, true) && visitPlan.IsBaseLine)
{
throw new BusinessValidationFailedException("检查批次计划中已经存在基线。");
}
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<SubjectVisit>(t => t.TrialId == visitPlan.TrialId).AnyAsync(v => v.IsBaseLine && v.SubmitState >= SubmitStateEnum.ToSubmit))
{
throw new BusinessValidationFailedException("有患者的基线已经上传了影像数据,不允许修改基线检查批次。");
}
}
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<IResponseOutput> ConfirmTrialVisitPlan(Guid trialId)
{
if (!await _trialRepository.AnyAsync(t => t.Id == trialId && (t.TrialStatusStr == StaticData.TrialState.TrialInitializing || t.TrialStatusStr == StaticData.TrialState.TrialOngoing)))
{
return ResponseOutput.NotOk("仅仅在项目初始化或者进行中时,才允许修改确认");
}
if (!await _visitStageRepository.AnyAsync(t => t.TrialId == trialId && t.IsBaseLine))
{
return ResponseOutput.NotOk("没有基线,不允许确认");
}
if (!await _trialRepository.AnyAsync(t => t.Id == trialId && t.IsTrialBasicLogicConfirmed && t.IsTrialProcessConfirmed && t.IsTrialUrgentConfirmed))
{
return ResponseOutput.NotOk("项目配置未确认,不允许确认检查批次计划");
}
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)
{
return ResponseOutput.NotOk("基线VisitDay 不是最小的, 不允许确认");
}
//更新项目检查批次计划状态为已确认 必定生成更新的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<SubjectVisit> subjectVisits = new List<SubjectVisit>();
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<SubjectVisit>(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.VisitWindowLeft??0)
//|| 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.VisitWindowLeft??0)
// || 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<List<VisitPlanInfluenceSubjectVisitStatDTO>> GetInfluenceHistoryList(Guid trialId, [FromServices] IRepository<VisitPlanInfluenceStat> _influnceStatRepository)
{
var list = await _influnceStatRepository.Where(t => t.TrialId == trialId).ProjectTo<VisitPlanInfluenceSubjectVisitStatDTO>(_mapper.ConfigurationProvider).OrderByDescending(t => t.CreateTime).ToListAsync();
return list;
}
[HttpGet("{visitPlanInfluenceStatId:guid}")]
public async Task<IActionResult> DownloadInflunceStudyList(Guid visitPlanInfluenceStatId)
{
var list = _influnceRepository.Where(t => t.VisitPlanInfluenceStatId == visitPlanInfluenceStatId)
.ProjectTo<VisitPlanInfluenceSubjectVisitDTO>(_mapper.ConfigurationProvider).ToList();
IExporter exporter = new ExcelExporter();
var result = await exporter.ExportAsByteArray(list);
return new XlsxFileResult(bytes: result, fileDownloadName: $"检查导出_{DateTime.Now.ToString("yyyy-MM-dd:hh:mm:ss")}.xlsx");
}
/// <summary> 删除项目计划某一项 废弃 </summary>
[HttpDelete("{id:guid}/{trialId:guid}")]
[Obsolete]
public async Task<IResponseOutput> DeleteVisitStage(Guid id)
{
var visitPlan = await _visitStageRepository.FirstOrDefaultAsync(t => t.Id == id);
if (visitPlan == null) return Null404NotFound(visitPlan);
if (await _repository.AnyAsync<SubjectVisit>(t => t.VisitName == visitPlan.VisitName && t.TrialId == visitPlan.TrialId && t.VisitExecuted == VisitExecutedEnum.Executed))
{
return ResponseOutput.NotOk("The visit plan has been assigned to the subjects and executed.");
}
await _repository.BatchDeleteAsync<SubjectVisit>(t => t.TrialId == visitPlan.TrialId && t.VisitName == visitPlan.VisitName);
var result = await _visitStageRepository.BatchDeleteNoTrackingAsync(u => u.Id == id);
return ResponseOutput.Result(result);
}
}
}