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();
return new VisitPlanView()
{
VisitPlanList = list,
TimePointsPerPatient = trial.TimePointsPerPatient,
VisitPlanConfirmed = trial.VisitPlanConfirmed,
IsHaveFirstGiveMedicineDate = trial.IsHaveFirstGiveMedicineDate,
//SubjectHasAdded = _subjectVisitRepository.Any(t => t.TrialId == trialId)
};
}
/// <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)]
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)]
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}")]
[TrialAudit(AuditType.TrialAudit, AuditOptType.DeleteTrialVisitPlanItem)]
[TypeFilter(typeof(TrialResourceFilter))]
[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);
}
}
}