using IRaCIS.Application.Interfaces; using IRaCIS.Application.Contracts; using IRaCIS.Core.Application.Filter; using IRaCIS.Core.Domain.Share; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using IRaCIS.Core.Application.Auth; using MassTransit; using Panda.DynamicWebApi.Attributes; using DocumentFormat.OpenXml.Spreadsheet; using AutoMapper.EntityFrameworkCore; using IRaCIS.Core.Domain.Models; using IRaCIS.Core.Application.Service.Reading.Dto; using Microsoft.Extensions.Options; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration.Json; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using SharpCompress.Common; using System.Reactive.Subjects; using Subject = IRaCIS.Core.Domain.Models.Subject; using IRaCIS.Core.Application.ViewModel; using Medallion.Threading; using IRaCIS.Core.Infrastructure; using EasyCaching.Core; using Pipelines.Sockets.Unofficial.Arenas; using IRaCIS.Core.Application.Contracts; using MailKit.Search; using DocumentFormat.OpenXml.Office2010.Excel; using IRaCIS.Core.Application.Contracts.Dicom.DTO; using IRaCIS.Core.Application.Helper; using NPOI.SS.Formula.Functions; using System.Linq; using System.Linq.Dynamic.Core; using System.Text; using DocumentFormat.OpenXml.EMMA; using Azure; using System.IO.Compression; using static IRaCIS.Core.Domain.Share.StaticData; using FellowOakDicom; using DocumentFormat.OpenXml.Office2010.Drawing; using EasyCaching.Core.DistributedLock; using IDistributedLockProvider = Medallion.Threading.IDistributedLockProvider; using DocumentFormat.OpenXml.InkML; namespace IRaCIS.Application.Services { [ApiExplorerSettings(GroupName = "Trial")] public class PatientService( IRepository _trialRepository, IRepository _patientRepository, IRepository _scpStudyRepository, IRepository _subjectRepository, IRepository _subjectVisitRepository, IRepository _dictionaryRepository, IRepository _dicomStudyRepository, IDistributedLockProvider _distributedLockProvider) : BaseService { [HttpGet] public async Task>> GetPatientSeriesList(Guid scpStudyId, [FromServices] IRepository _seriesRepository, [FromServices] IRepository _instanceRepository ) { var seriesList = await _seriesRepository.Where(s => s.StudyId == scpStudyId).OrderBy(s => s.SeriesNumber). ThenBy(s => s.SeriesTime).ThenBy(s => s.CreateTime) .ProjectTo(_mapper.ConfigurationProvider).ToListAsync(); var instanceList = await _instanceRepository.Where(s => s.StudyId == scpStudyId).OrderBy(t => t.SeriesId).ThenBy(t => t.InstanceNumber) .ThenBy(s => s.InstanceTime).ThenBy(s => s.CreateTime) .Select(t => new { t.SeriesId, t.Id, t.Path, t.NumberOfFrames, t.InstanceNumber }).ToListAsync();//.GroupBy(u => u.SeriesId); foreach (var series in seriesList) { series.InstanceInfoList = instanceList.Where(t => t.SeriesId == series.Id).OrderBy(t => t.InstanceNumber).Select(k => new InstanceBasicInfo() { Id = k.Id, NumberOfFrames = k.NumberOfFrames, //HtmlPath = string.Empty, Path = k.Path, InstanceNumber = k.InstanceNumber, }).ToList(); } var study = await _scpStudyRepository.FindAsync(scpStudyId); return ResponseOutput.Ok(seriesList, study); } /// /// scp 影像推送记录表 /// /// /// [HttpPost] public async Task>> GetSCPImageUploadList(SCPImageUploadQuery inQuery) { var query = _repository.Where(t => t.TrialId == inQuery.TrialId) .WhereIf(!string.IsNullOrWhiteSpace(inQuery.CalledAE), t => t.CalledAE.Contains(inQuery.CalledAE)) .WhereIf(!string.IsNullOrWhiteSpace(inQuery.CallingAEIP), t => t.CallingAEIP.Contains(inQuery.CallingAEIP)) .WhereIf(!string.IsNullOrWhiteSpace(inQuery.CallingAE), t => t.CallingAE.Contains(inQuery.CallingAE)) .WhereIf(inQuery.StartTime != null, t => t.StartTime >= inQuery.StartTime) .WhereIf(inQuery.EndTime != null, t => t.EndTime <= inQuery.EndTime) .WhereIf(!string.IsNullOrWhiteSpace(inQuery.TrialSiteKeyInfo), t => t.TrialSite.TrialSiteCode.Contains(inQuery.TrialSiteKeyInfo) || t.TrialSite.TrialSiteAliasName.Contains(inQuery.TrialSiteKeyInfo) || t.TrialSite.TrialSiteName.Contains(inQuery.TrialSiteKeyInfo)) .ProjectTo(_mapper.ConfigurationProvider); var pageList = await query.ToPagedListAsync(inQuery); return ResponseOutput.Ok(pageList); } #region 患者检查管理 /// ///影像检查列表-患者为维度组织 /// /// /// [HttpPost] public async Task>> GetPatientList(PatientTrialQuery inQuery) { #region new ok var query = _patientRepository.Where(t => t.TrialId == inQuery.TrialId) .WhereIf(!string.IsNullOrWhiteSpace(inQuery.PatientIdStr), t => t.PatientIdStr.Contains(inQuery.PatientIdStr)) .WhereIf(!string.IsNullOrWhiteSpace(inQuery.PatientName), t => t.PatientName.Contains(inQuery.PatientName)) .WhereIf(!string.IsNullOrWhiteSpace(inQuery.SubejctCode), t => t.Subject.Code.Contains(inQuery.SubejctCode)) .WhereIf(!string.IsNullOrWhiteSpace(inQuery.TrialSiteKeyInfo), t => t.TrialSite.TrialSiteCode.Contains(inQuery.TrialSiteKeyInfo) || t.TrialSite.TrialSiteAliasName.Contains(inQuery.TrialSiteKeyInfo) || t.TrialSite.TrialSiteName.Contains(inQuery.TrialSiteKeyInfo)) .WhereIf(!string.IsNullOrWhiteSpace(inQuery.CallingAE), t => t.SCPStudyList.Any(t => t.CallingAE == inQuery.CallingAE)) .WhereIf(!string.IsNullOrWhiteSpace(inQuery.CalledAE), t => t.SCPStudyList.Any(t => t.CalledAE == inQuery.CalledAE)) .WhereIf(inQuery.BeginPushTime != null, t => t.LatestPushTime >= inQuery.BeginPushTime) .WhereIf(inQuery.EndPushTime != null, t => t.LatestPushTime <= inQuery.EndPushTime); //foreach (var calledAE in inQuery.CalledAEList) //{ // query = query.Where(t => t.SCPStudyList.Select(c => c.CalledAE).Contains(calledAE)); //} var resultQuery = from patient in query select new PatientSubjectView() { PatientId = patient.Id, PatientBirthDate = patient.PatientBirthDate, CreateTime = patient.CreateTime, CalledAEList = patient.SCPStudyList.Select(t => t.CalledAE).Distinct().ToList(), CallingAEList = patient.SCPStudyList.Select(t => t.CallingAE).Distinct().ToList(), CreateUserId = patient.CreateUserId, UpdateTime = patient.UpdateTime, UpdateUserId = patient.UpdateUserId, EarliestStudyTime = patient.EarliestStudyTime, LatestStudyTime = patient.LatestStudyTime, LatestPushTime = patient.LatestPushTime, PatientAge = patient.PatientAge, PatientName = patient.PatientName, PatientIdStr = patient.PatientIdStr, PatientSex = patient.PatientSex, StudyCount = patient.SCPStudyList.Count(), TrialId = patient.TrialId, SubejctId = patient.SubjectId, SubjectCode = patient.Subject.Code, TrialSiteAliasName = patient.TrialSite.TrialSiteAliasName, TrialSiteCode = patient.TrialSite.TrialSiteCode, TrialSiteName = patient.TrialSite.TrialSiteName }; var pageList = await resultQuery.ToPagedListAsync(inQuery); #endregion return ResponseOutput.Ok(pageList); } /// /// 影像检查列表-> 获取患者的检查列表 /// /// /// [HttpPost] public async Task> GetPatientStudyList(PatientStudyInfoQuery inQuery) { var query = from scpStudy in _scpStudyRepository.Where(t => t.PatientId == inQuery.PatientId) .WhereIf(inQuery.EarliestStudyTime != null, t => t.StudyTime >= inQuery.EarliestStudyTime) .WhereIf(inQuery.LatestStudyTime != null, t => t.StudyTime <= inQuery.LatestStudyTime) .WhereIf(!string.IsNullOrWhiteSpace(inQuery.Modalities), t => t.Modalities.Contains(inQuery.Modalities)) select new PatientStudySimpleView() { Description = scpStudy.Description, CalledAE = scpStudy.CalledAE, CallingAE = scpStudy.CallingAE, InstanceCount = scpStudy.InstanceCount, Modalities = scpStudy.Modalities, PatientId = scpStudy.PatientId, SCPStudyId = scpStudy.Id, SeriesCount = scpStudy.SeriesCount, StudyTime = scpStudy.StudyTime, SubjectVisitId = scpStudy.SubjectVisitId, VisitName = scpStudy.SubjectVisit.VisitName, BlindName = scpStudy.SubjectVisit.BlindName }; //var sortField = string.IsNullOrWhiteSpace(inQuery.SortField) ? nameof(PatientStudySimpleView.StudyTime) : inQuery.SortField; //var orderQuery = inQuery.Asc ? query.OrderBy(sortField) : query.OrderBy(sortField + " desc"); //var list = await orderQuery.ToListAsync(); var pageList = await query.ToPagedListAsync(inQuery, nameof(PatientStudySimpleView.StudyTime)); return pageList; } public async Task> GetDicomCalledAEList(Guid trialId) { var list = await _scpStudyRepository.Where(t => t.TrialId == trialId).Select(t => t.CalledAE).Distinct().ToListAsync(); return list; } public async Task> GetDicomCallingAEList(Guid trialId) { var list = await _scpStudyRepository.Where(t => t.TrialId == trialId).Select(t => t.CallingAE).Distinct().ToListAsync(); return list; } public async Task> GetDicomModalityList(Guid trialId) { var list = await _scpStudyRepository.Where(t => t.TrialId == trialId).SelectMany(t => t.SeriesList).Select(t => t.Modality).Distinct().ToListAsync(); return list; } /// /// 影像访视上传 检查列表 /// /// /// [HttpPost] public async Task> GetVisitPatientStudyFilterList(VisitPatientStudyFilterQuery inQuery) { var trialSiteId = _subjectRepository.Where(t => t.Id == inQuery.SubjectId).Select(t => t.TrialSiteId).FirstOrDefault(); var query = from scpStudy in _scpStudyRepository //未绑定的患者,或者自己已绑定但是未绑定访视的 .Where(t => t.Patient.SubjectId == null || (t.Patient.SubjectId == inQuery.SubjectId && t.SubjectVisitId == null)) //中心 .Where(t => t.TrialSiteId == trialSiteId) .WhereIf(inQuery.EarliestStudyTime != null, t => t.StudyTime >= inQuery.EarliestStudyTime) .WhereIf(inQuery.LatestStudyTime != null, t => t.StudyTime <= inQuery.LatestStudyTime) .WhereIf(!string.IsNullOrWhiteSpace(inQuery.Modalities), t => t.Modalities.Contains(inQuery.Modalities)) .WhereIf(!string.IsNullOrWhiteSpace(inQuery.PatientInfo), t => t.PatientIdStr.Contains(inQuery.PatientInfo) || t.PatientName.Contains(inQuery.PatientInfo) || t.PatientSex.Contains(inQuery.PatientInfo)) select new VisitPatientStudyFilterView() { Description = scpStudy.Description, CalledAE = scpStudy.CalledAE, CallingAE = scpStudy.CallingAE, InstanceCount = scpStudy.InstanceCount, Modalities = scpStudy.Modalities, PatientId = scpStudy.PatientId, SCPStudyId = scpStudy.Id, SeriesCount = scpStudy.SeriesCount, StudyTime = scpStudy.StudyTime, BodyPartExamined = scpStudy.BodyPartExamined, AccessionNumber = scpStudy.AccessionNumber, PatientBirthDate = scpStudy.PatientBirthDate, PatientAge = scpStudy.PatientAge, PatientIdStr = scpStudy.PatientIdStr, PatientName = scpStudy.PatientName, PatientSex = scpStudy.PatientSex, }; var pageList = await query.ToPagedListAsync(inQuery, nameof(PatientStudySimpleView.StudyTime)); return pageList; } public async Task> VerifyPacsImage(VerifyPacsImageCommand inCommand) { var trialId = inCommand.TrialId; var subjectId = inCommand.SubjectId; var isVerifyVisitImageDate = await _trialRepository.Where(t => t.Id == inCommand.TrialId).Select(t => t.IsVerifyVisitImageDate).FirstNotNullAsync(); var result = new List(); var visitList = _subjectVisitRepository.Where(t => t.SubjectId == inCommand.SubjectId).Select(t => new { t.VisitNum, t.EarliestScanDate, t.LatestScanDate, t.Id }).ToList(); var currentVisitNum = visitList.First(t => t.Id == inCommand.SubjectVisitId).VisitNum; var scpStudyList = _scpStudyRepository.Where(t => inCommand.SCPStudyIdList.Contains(t.Id)).Select(t => new { StudyDate = t.StudyTime, t.Id }).ToList(); foreach (var waitUploadItem in scpStudyList) { if (isVerifyVisitImageDate) { //小于当前访视 最近的最晚拍片 var before = visitList.Where(u => u.VisitNum < currentVisitNum).Max(k => k.LatestScanDate); if (before != null && waitUploadItem.StudyDate != null && before > waitUploadItem.StudyDate) { // $"当前访视检查时间{waitUploadItem.StudyDate?.ToString("yyyy-MM-dd")}不能早于前序访视检查时间{before?.ToString("yyyy-MM-dd")},请核对检查数据是否有误", result.Add(new VerifySCPStudyUploadResult() { SCPStudyId = waitUploadItem.Id, ErrorMesseage = _localizer["Study_VisitBeforePrevError", waitUploadItem.StudyDate?.ToString("yyyy-MM-dd")!, before?.ToString("yyyy-MM-dd")!] }); continue; // 跳过当前迭代 } //大于当前访视 最近的最早拍片日期 var after = visitList.Where(u => u.VisitNum > currentVisitNum).Min(k => k.EarliestScanDate); if (after != null && waitUploadItem.StudyDate != null && after < waitUploadItem.StudyDate) { // $"当前访视检查时间{waitUploadItem.StudyDate?.ToString("yyyy-MM-dd")}不能晚于该访视之后的检查时间{after?.ToString("yyyy-MM-dd")},请核对检查数据是否有误" result.Add(new VerifySCPStudyUploadResult() { SCPStudyId = waitUploadItem.Id, ErrorMesseage = _localizer["Study_VisitAfterSubseqError", waitUploadItem.StudyDate?.ToString("yyyy-MM-dd")!, after?.ToString("yyyy-MM-dd")!] }); continue; // 跳过当前迭代 } } var verifyStudyInfo = _dicomStudyRepository.Where(t => t.TrialId == trialId && t.Id == waitUploadItem.Id).ProjectTo(_mapper.ConfigurationProvider).FirstOrDefault(); var currentStudyResult = new VerifySCPStudyUploadResult() { SCPStudyId = waitUploadItem.Id }; //数据库不存在该检查 允许上传 if (verifyStudyInfo == null) { currentStudyResult.AllowUpload = true; } //数据库该项目有该检查 看是否支持重传 else { //是同一个受试者 支持重传 if (verifyStudyInfo.SubjectId == subjectId && verifyStudyInfo.SubjectVisitId == inCommand.SubjectVisitId) { currentStudyResult.AllowReUpload = true; } //不是同一个受试者 else { //有默认值,其实不用写,这里为了好理解 currentStudyResult.AllowUpload = false; currentStudyResult.AllowReUpload = false; //$"此处不可以上传。当前影像检查已经上传给受试者{verifyStudyInfo.SubjectCode}的{verifyStudyInfo.VisitName}" currentStudyResult.ErrorMesseage = _localizer["Study_ImgAlreadyUploaded", verifyStudyInfo.SubjectCode, verifyStudyInfo.VisitName]; } } result.Add(currentStudyResult); } return result; } /// /// 提交 患者检查和访视的绑定 /// /// /// /// /// /// [HttpPost] [UnitOfWork] [TypeFilter(typeof(TrialResourceFilter), Arguments = new object[] { "AfterStopCannNotOpt" })] public async Task SubmitVisitStudyBinding(SubmitVisitStudyBindingCommand inCommand, [FromServices] IRepository _dicomstudyRepository, [FromServices] IRepository _dicomSeriesRepository, [FromServices] IRepository _dicomInstanceRepository) { //这里要做校验 + 同时验证本地系统里面的影像是否存在pacs推过来的 var copyCommand = inCommand.Clone(); copyCommand.SCPStudyIdList = inCommand.SCPStudyIdList.Union(inCommand.ReUploadSCPStudyIdList).ToList(); var verifyResult = await VerifyPacsImage(copyCommand); var allowUploadList = verifyResult.Where(u => u.AllowUpload == true).Select(t => t.SCPStudyId).ToList(); var allowReUploadList = verifyResult.Where(u => u.AllowReUpload == true).Select(t => t.SCPStudyId).ToList(); if (!(inCommand.SCPStudyIdList.All(t => allowUploadList.Contains(t)) && inCommand.ReUploadSCPStudyIdList.All(t => allowReUploadList.Contains(t)))) { throw new BusinessValidationFailedException("对接提示: 前端提交的检查有不能上传的,请刷新页面调用验证接口重试!"); } var subjectId = inCommand.SubjectId; var subjectVisitId = inCommand.SubjectVisitId; var trialId = inCommand.TrialId; var @lock = _distributedLockProvider.CreateLock($"StudyCode"); using (await @lock.AcquireAsync()) { var dbStudyCodeIntMax = _dicomStudyRepository.Where(s => s.TrialId == inCommand.TrialId).Select(t => t.Code).DefaultIfEmpty().Max(); int currentNextCodeInt = dbStudyCodeIntMax + 1; //新增的,上传的 foreach (var scpStudyId in inCommand.SCPStudyIdList) { var find = _scpStudyRepository.Where(t => t.Id == scpStudyId).Select(t => new { SCPStudy = t, t.SeriesList, t.InstanceList }).FirstOrDefault(); if (find != null) { var newStuty = _mapper.Map(find.SCPStudy); await _repository.AddAsync(newStuty); newStuty.SeqId = Guid.Empty; newStuty.Code = currentNextCodeInt; newStuty.StudyCode = AppSettings.GetCodeStr(currentNextCodeInt, nameof(DicomStudy)); newStuty.IsFromPACS = true; newStuty.TrialId = trialId; newStuty.SubjectId = subjectId; newStuty.SubjectVisitId = subjectVisitId; var newSeriesList = _mapper.Map>(find.SeriesList); foreach (var series in newSeriesList) { series.SeqId = Guid.Empty; series.TrialId = trialId; series.SubjectId = subjectId; series.SubjectVisitId = subjectVisitId; } await _repository.AddRangeAsync(newSeriesList); var newInstanceList = _mapper.Map>(find.InstanceList); foreach (var instance in newInstanceList) { instance.SeqId = Guid.Empty; instance.TrialId = trialId; instance.SubjectId = subjectId; instance.SubjectVisitId = subjectVisitId; } await _repository.AddRangeAsync(newInstanceList); } currentNextCodeInt++; await _repository.BatchUpdateAsync(t => t.Id == find.SCPStudy.PatientId, u => new SCPPatient() { SubjectId = subjectId }); await _repository.BatchUpdateAsync(t => t.Id == scpStudyId, u => new SCPStudy() { SubjectVisitId = subjectVisitId }); } foreach (var scpStudyId in inCommand.ReUploadSCPStudyIdList) { var study = await _dicomstudyRepository.FirstOrDefaultAsync(t => t.Id == scpStudyId); var instanceIdList = _dicomInstanceRepository.Where(t => t.Id == scpStudyId).Select(t => t.Id).ToList(); var scpStudy = _scpStudyRepository.Where(t => t.Id == scpStudyId).Include(t => t.SeriesList).ThenInclude(t => t.SCPInstanceList).FirstOrDefault(); //以最后一次为准 study.IsFromPACS = true; //特殊处理逻辑 study.Modalities = string.Join("、", scpStudy.SeriesList.Select(t => t.Modality).Union(study.Modalities.Split("、", StringSplitOptions.RemoveEmptyEntries)).Distinct()); SpecialArchiveStudyDeal(study); // 少了整个序列 //某个序列下少了instance foreach (var seriesItem in scpStudy.SeriesList) { var seriesId = IdentifierHelper.CreateGuid(seriesItem.StudyInstanceUid, seriesItem.SeriesInstanceUid, trialId.ToString()); DicomSeries dicomSeries = await _dicomSeriesRepository.FirstOrDefaultAsync(t => t.Id == seriesId); //判断重复 if (dicomSeries == null) { var series = _mapper.Map(seriesItem); series.SeqId = Guid.Empty; series.TrialId = trialId; series.SubjectId = subjectId; series.SubjectVisitId = subjectVisitId; dicomSeries = await _dicomSeriesRepository.AddAsync(series); foreach (var instanceItem in seriesItem.SCPInstanceList) { var instance = _mapper.Map(instanceItem); instance.SeqId = Guid.Empty; instance.TrialId = trialId; instance.SubjectId = subjectId; instance.SubjectVisitId = subjectVisitId; await _dicomInstanceRepository.AddAsync(instance); } //新的序列 那么 检查的序列数量+1 study.SeriesCount += 1; study.InstanceCount += seriesItem.SCPInstanceList.Count; } else { //该序列掉了instance dicomSeries.InstanceCount += seriesItem.SCPInstanceList.Count; var newInstanceList = seriesItem.SCPInstanceList.Where(t => !instanceIdList.Contains(t.Id)); foreach (var instanceItem in newInstanceList) { var instance = _mapper.Map(instanceItem); instance.SeqId = Guid.Empty; instance.TrialId = trialId; instance.SubjectId = subjectId; instance.SubjectVisitId = subjectVisitId; await _dicomInstanceRepository.AddAsync(instance); } study.InstanceCount += newInstanceList.Count(); } } await _repository.SaveChangesAsync(); await _repository.BatchUpdateAsync(t => t.Id == scpStudy.PatientId, u => new SCPPatient() { SubjectId = subjectId }); await _repository.BatchUpdateAsync(t => t.Id == scpStudyId, u => new SCPStudy() { SubjectVisitId = subjectVisitId }); } } await _repository.SaveChangesAsync(); return ResponseOutput.Ok(); } private void SpecialArchiveStudyDeal(DicomStudy study) { #region 特殊逻辑 if (study.PatientBirthDate.Length == 8) { study.PatientBirthDate = $"{study.PatientBirthDate[0]}{study.PatientBirthDate[1]}{study.PatientBirthDate[2]}{study.PatientBirthDate[3]}-{study.PatientBirthDate[4]}{study.PatientBirthDate[5]}-{study.PatientBirthDate[6]}{study.PatientBirthDate[7]}"; } var dicModalityList = _dictionaryRepository.Where(t => t.Code == "Modality").SelectMany(t => t.ChildList.Select(c => c.Value)).ToList(); var modality = study.Modalities; var modalityForEdit = dicModalityList.Contains(modality) ? modality : String.Empty; if (modality == "MR") { modalityForEdit = "MRI"; } if (modality == "PT") { modalityForEdit = "PET"; } if (modality == "PT、CT") { modalityForEdit = "PET-CT"; } study.ModalityForEdit = modalityForEdit; #endregion } #endregion } }