using IRaCIS.Core.Domain.Share; using System.Text; using Microsoft.AspNetCore.Hosting; using IRaCIS.Core.Infrastructure; using Medallion.Threading; using FellowOakDicom; using FellowOakDicom.Imaging.Codec; using System.Data; using IRaCIS.Core.Domain.Models; using FellowOakDicom.Network; using IRaCIS.Core.SCP.Service; using IRaCIS.Core.Infra.EFCore; using MassTransit; using Microsoft.AspNetCore.Mvc; using IRaCIS.Core.Infrastructure.Extention; using Microsoft.EntityFrameworkCore; namespace IRaCIS.Core.SCP.Service { public interface IPatientStudyService { Task AutoBindingPatientStudyVisitAsync(List scpStudyIdList); } [ApiExplorerSettings(GroupName = "Trial")] public class PatientStudyService : BaseService, IPatientStudyService { private readonly IRepository _studySubjectVisitRepository; private readonly IRepository _subjectPatientRepository; private readonly IRepository _trialRepository; private readonly IRepository _patientRepository; private readonly IRepository _studyRepository; private readonly IRepository _subjectRepository; private readonly IRepository _subjectVisitRepository; private readonly IDistributedLockProvider _distributedLockProvider; public PatientStudyService(IRepository studySubjectVisitRepository, IRepository studyRepository, IRepository subjectPatientRepository, IRepository trialRepository, IRepository patientRepository, IRepository subjectRepository, IRepository subjectVisitRepository, IDistributedLockProvider distributedLockProvider) { _studySubjectVisitRepository = studySubjectVisitRepository; _studyRepository = studyRepository; _subjectPatientRepository = subjectPatientRepository; _trialRepository = trialRepository; _patientRepository = patientRepository; _subjectRepository = subjectRepository; _subjectVisitRepository = subjectVisitRepository; _distributedLockProvider = distributedLockProvider; } public class AuToBindingStudyInfo { public Guid SCPStudyId { get; set; } public DateTime? StudyTime { get; set; } } private async Task DealAutoBindingStudyAsync(Guid trialId, Guid subjectId, List studyList, decimal? startBindVisitNum = null) { //自动创建访视 和检查绑定 //1. 查询已存在的访视 var subjectAllVisitList = await _subjectVisitRepository.Where(t => t.SubjectId == subjectId) .Select(t => new { t.SubjectId, SubjectVisitId = t.Id, t.SubmitState, VisitNum = t.VisitNum, MaxStudyTime = t.SCPStudySubjectVisitList.Max(t => t.SCPStudy.StudyTime), MinStudyTime = t.SCPStudySubjectVisitList.Min(t => t.SCPStudy.StudyTime) }) .ToListAsync(); //2、获取项目配置 var trialconfig = _trialRepository.Where(t => t.Id == trialId).Select(t => new { t.BlindBaseLineName, t.BlindFollowUpPrefix }).FirstOrDefault(); //3、 未提交的最小的访视号 从这个访视开始绑定 var subjectMaxVisitNum = startBindVisitNum == null ? subjectAllVisitList.Where(t => t.SubmitState != SubmitStateEnum.Submitted).MinOrDefault(t => t.VisitNum) : startBindVisitNum.Value; List<(int VisitCount, Guid SCPStudyId)> visits = new List<(int, Guid)>(); int visitCount = 0; DateTime? lastVisitTime = null; foreach (var study in studyList) { if (lastVisitTime == null || (study.StudyTime - lastVisitTime.Value).Value.TotalDays >= 15) { // 当前时间点与上一个访视时间点间隔大于等于 15 天,需要建立一个新的访视 visitCount++; visits.Add((visitCount, study.SCPStudyId)); } else { visits.Add((visitCount, study.SCPStudyId)); } lastVisitTime = study.StudyTime; } //4、生成访视 并且绑定 for (int i = 0; i < visitCount; i++) { var bindSubjectVisitId = Guid.Empty; var bindVisitNum = i + subjectMaxVisitNum; var existSubjectVisit = subjectAllVisitList.FirstOrDefault(t => t.SubjectId == subjectId && t.VisitNum == bindVisitNum); if (existSubjectVisit == null) { bindSubjectVisitId = NewId.NextGuid(); //基线 if (bindVisitNum == 0) { await _subjectVisitRepository.AddAsync(new SubjectVisit() { TrialId = trialId, SubjectId = subjectId, VisitName = trialconfig.BlindBaseLineName, VisitNum = bindVisitNum, Id = bindSubjectVisitId, SubmitState = SubmitStateEnum.ToSubmit, IsBaseLine = true }); } else { await _subjectVisitRepository.AddAsync(new SubjectVisit() { TrialId = trialId, SubjectId = subjectId, VisitName = trialconfig.BlindFollowUpPrefix + $" {(int)bindVisitNum}", VisitNum = bindVisitNum, Id = bindSubjectVisitId, SubmitState = SubmitStateEnum.ToSubmit }); } } else { bindSubjectVisitId = existSubjectVisit.SubjectVisitId; } var currentVisitStudyList = visits.Where(t => t.VisitCount == (i + 1)).ToList(); foreach (var item in currentVisitStudyList) { //访视状态为未提交才绑定 if (!subjectAllVisitList.Any(t => t.SubjectId == subjectId && t.SubjectVisitId == bindSubjectVisitId && t.SubmitState == SubmitStateEnum.Submitted)) { var find = await _subjectVisitRepository.FindAsync(bindSubjectVisitId); find.SubmitState = SubmitStateEnum.ToSubmit; await _studySubjectVisitRepository.AddAsync(new SCPStudySubjectVisit() { TrialId = trialId, SubjectVisitId = bindSubjectVisitId, SCPStudyId = item.SCPStudyId, SubjectId = subjectId }); } } } await _subjectPatientRepository.SaveChangesAsync(); } /// /// 传输完成后,自动给检查绑定访视 /// /// /// [HttpPost] public async Task AutoBindingPatientStudyVisitAsync(List scpStudyIdList) { //一个检查 可能绑定到不同的项目的不同subject 有的该检查已绑定访视,有的该检查绑定了访视 var query = from scpStudy in _studyRepository.Where(t => scpStudyIdList.Contains(t.Id)) join subjectPatient in _subjectPatientRepository.AsQueryable() on scpStudy.PatientId equals subjectPatient.PatientId select new { subjectPatient.Subject.Status, subjectPatient.Subject.TrialId, subjectPatient.SubjectId, subjectPatient.PatientId, SCPStudyId = scpStudy.Id, scpStudy.StudyTime }; var list = query.ToList(); if (list.Count > 0) { var subjectIdList = list.Select(t => t.SubjectId).ToList(); var allSubjectVisitList = await _subjectVisitRepository.Where(t => subjectIdList.Contains(t.SubjectId)) .Select(t => new { t.SubjectId, SubjectVisitId = t.Id, t.SubmitState, VisitNum = t.VisitNum, MaxStudyTime = t.SCPStudySubjectVisitList.Max(t => t.SCPStudy.StudyTime), MinStudyTime = t.SCPStudySubjectVisitList.Min(t => t.SCPStudy.StudyTime) }) .ToListAsync(); foreach (var g in list.GroupBy(t => new { t.SubjectId, t.TrialId, t.Status })) { var subjectId = g.Key.SubjectId; var trialId = g.Key.TrialId; //访视结束,那么就不处理 if (g.Key.Status == SubjectStatus.EndOfVisit) { continue; } // 预先处理1: 数据库可能有已存在的subject 患者绑定,在这里要一起考虑绑定 var dbPatientIdList = _subjectPatientRepository.Where(t => t.SubjectId == subjectId).Select(t => t.PatientId).ToList(); // 预先处理2: 删除未提交的所有绑定的检查记录,所有检查一起考虑绑定 await _studySubjectVisitRepository.BatchDeleteNoTrackingAsync(t => t.SubjectId == subjectId && t.SubjectVisit.SubmitState != SubmitStateEnum.Submitted); //预处理3 找到该subjecct 已提交的访视的最大检查时间,绑定的检查时间要比这个时间要大 var maxStudyTime = allSubjectVisitList.Where(t => t.SubjectId == subjectId && t.SubmitState == SubmitStateEnum.Submitted).MaxOrDefault(t => t.MaxStudyTime); // 预处理4: 处理需要绑定的检查 //获取 该受试者绑定患者已存在的检查,考虑要生成多少个访视,去除已提交的检查 var studyList = await _studyRepository.Where(t => dbPatientIdList.Contains(t.PatientId) && !t.SCPStudySubjectVisitList.Any(t => t.SubjectId == subjectId && t.SubjectVisit.SubmitState == SubmitStateEnum.Submitted)) .WhereIf(maxStudyTime != null, t => t.StudyTime > maxStudyTime) .Select(t => new AuToBindingStudyInfo { SCPStudyId = t.Id, StudyTime = t.StudyTime }).OrderBy(t => t.StudyTime).ToListAsync(); await DealAutoBindingStudyAsync(trialId, subjectId, studyList); } await _subjectVisitRepository.SaveChangesAsync(); } //将检查设置为传输结束 await _studyRepository.BatchUpdateNoTrackingAsync(t => scpStudyIdList.Contains(t.Id), u => new SCPStudy() { IsUploadFinished = true }); return ResponseOutput.Ok(); } } }