diff --git a/IRaCIS.Core.API/Controllers/ExtraController.cs b/IRaCIS.Core.API/Controllers/ExtraController.cs index 7a3b3504d..7e8c111be 100644 --- a/IRaCIS.Core.API/Controllers/ExtraController.cs +++ b/IRaCIS.Core.API/Controllers/ExtraController.cs @@ -30,6 +30,8 @@ using IRaCIS.Core.Application.Helper; using Microsoft.Extensions.Options; using IRaCIS.Core.Application.Contracts; using LoginReturnDTO = IRaCIS.Application.Contracts.LoginReturnDTO; +using DocumentFormat.OpenXml.Spreadsheet; +using AutoMapper.QueryableExtensions; namespace IRaCIS.Api.Controllers { @@ -53,8 +55,8 @@ namespace IRaCIS.Api.Controllers /// /// [HttpGet, Route("doctor/getDetail/{doctorId:guid}")] - - public async Task> GetDoctorDetail([FromServices] IAttachmentService attachmentService, [FromServices] IDoctorService _doctorService, + + public async Task> GetDoctorDetail([FromServices] IAttachmentService attachmentService, [FromServices] IDoctorService _doctorService, [FromServices] IEducationService _educationService, [FromServices] ITrialExperienceService _trialExperienceService, [FromServices] IResearchPublicationService _researchPublicationService, [FromServices] IVacationService _vacationService, Guid doctorId) @@ -66,7 +68,7 @@ namespace IRaCIS.Api.Controllers var doctorDetail = new DoctorDetailDTO { - AuditView =await _doctorService.GetAuditState(doctorId), + AuditView = await _doctorService.GetAuditState(doctorId), BasicInfoView = await _doctorService.GetBasicInfo(doctorId), EmploymentView = await _doctorService.GetEmploymentInfo(doctorId), AttachmentList = await attachmentService.GetAttachments(doctorId), @@ -77,7 +79,7 @@ namespace IRaCIS.Api.Controllers TrialExperienceView = await _trialExperienceService.GetTrialExperience(doctorId), ResearchPublicationView = await _researchPublicationService.GetResearchPublication(doctorId), - SpecialtyView =await _doctorService.GetSpecialtyInfo(doctorId), + SpecialtyView = await _doctorService.GetSpecialtyInfo(doctorId), InHoliday = (await _vacationService.OnVacation(doctorId)).IsSuccess, IntoGroupInfo = _doctorService.GetDoctorIntoGroupInfo(doctorId), SowList = sowList, @@ -96,80 +98,31 @@ namespace IRaCIS.Api.Controllers /// 系统用户登录接口[New] [HttpPost, Route("user/login")] [AllowAnonymous] - public async Task> Login(UserLoginDTO loginUser, [FromServices] IEasyCachingProvider provider, [FromServices] IUserService _userService, - [FromServices] ITokenService _tokenService, - - [FromServices] IReadingImageTaskService readingImageTaskService, - [FromServices] IConfiguration configuration) + public async Task Login(UserLoginDTO loginUser, + [FromServices] IEasyCachingProvider provider, + [FromServices] IUserService _userService, + [FromServices] ITokenService _tokenService, + [FromServices] IReadingImageTaskService readingImageTaskService, + IOptionsMonitor _verifyConfig, + IMailVerificationService _mailVerificationService) { - - var returnModel = await _userService.Login(loginUser.UserName, loginUser.Password); - - if (returnModel.IsSuccess) + //MFA 邮箱验证 前端传递用户Id 和MFACode + if (loginUser.UserId != null && _verifyConfig.CurrentValue.OpenLoginMFA) { - #region GRPC 调用鉴权中心,因为服务器IIS问题 http/2 故而没法使用 + Guid userId = (Guid)loginUser.UserId; - ////重试策略 - //var defaultMethodConfig = new MethodConfig - //{ - // Names = { MethodName.Default }, - // RetryPolicy = new RetryPolicy - // { - // MaxAttempts = 3, - // InitialBackoff = TimeSpan.FromSeconds(1), - // MaxBackoff = TimeSpan.FromSeconds(5), - // BackoffMultiplier = 1.5, - // RetryableStatusCodes = { Grpc.Core.StatusCode.Unavailable } - // } - //}; + //验证MFA 编码是否有问题 ,前端要拆开,自己调用验证的逻辑 + //await _userService.VerifyMFACodeAsync(userId, loginUser.MFACode); - //#region unable to trust the certificate then the gRPC client can be configured to ignore the invalid certificate + //var loginUser = await _userRepository.Where(u => u.UserName.Equals(userName) && u.Password == password).ProjectTo(_mapper.ConfigurationProvider).FirstOrDefaultAsync(); - //var httpHandler = new HttpClientHandler(); - //// Return `true` to allow certificates that are untrusted/invalid - //httpHandler.ServerCertificateCustomValidationCallback = - // HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; + var basicInfo = await _userService.GetUserBasicInfo(userId, loginUser.Password); + var loginReturn = new LoginReturnDTO() { BasicInfo = basicInfo }; - //////这一句是让grpc支持本地 http 如果本地访问部署在服务器上,那么是访问不成功的 - //AppContext.SetSwitch( - // "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); + loginReturn.JWTStr = _tokenService.GetToken(IRaCISClaims.Create(loginReturn.BasicInfo)); - //#endregion - - - - //var grpcAdress = configuration.GetValue("GrpcAddress"); - ////var grpcAdress = "http://localhost:7200"; - - //var channel = GrpcChannel.ForAddress(grpcAdress, new GrpcChannelOptions - //{ - // HttpHandler = httpHandler, - // ServiceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } } - - //}); - ////var channel = GrpcChannel.ForAddress(grpcAdress); - //var grpcClient = new TokenGrpcService.TokenGrpcServiceClient(channel); - - //var userInfo = returnModel.Data.BasicInfo; - - //var tokenResponse = grpcClient.GetUserToken(new GetTokenReuqest() - //{ - // Id = userInfo.Id.ToString(), - // ReviewerCode = userInfo.ReviewerCode, - // IsAdmin = userInfo.IsAdmin, - // RealName = userInfo.RealName, - // UserTypeEnumInt = (int)userInfo.UserTypeEnum, - // UserTypeShortName = userInfo.UserTypeShortName, - // UserName = userInfo.UserName - //}); - - //returnModel.Data.JWTStr = tokenResponse.Token; - - #endregion - - returnModel.Data.JWTStr = _tokenService.GetToken(IRaCISClaims.Create(returnModel.Data.BasicInfo)); // 创建一个 CookieOptions 对象,用于设置 Cookie 的属性 var option = new CookieOptions @@ -180,20 +133,161 @@ namespace IRaCIS.Api.Controllers Secure = false // 确保 cookie 只能通过 HTTPS 访问 }; - HttpContext.Response.Cookies.Append("access_token", returnModel.Data.JWTStr, option); + HttpContext.Response.Cookies.Append("access_token", loginReturn.JWTStr, option); + + // 验证阅片休息时间 + await readingImageTaskService.ResetReadingRestTime(userId); + + await provider.SetAsync(userId.ToString(), loginReturn.JWTStr, TimeSpan.FromDays(7)); + + await provider.SetAsync($"{userId.ToString()}_Online", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), TimeSpan.FromMinutes(_verifyConfig.CurrentValue.AutoLoginOutMinutes)); + + return ResponseOutput.Ok(loginReturn); + + } + else + { + var returnModel = await _userService.Login(loginUser.UserName, loginUser.Password); + + if (returnModel.IsSuccess) + { + #region GRPC 调用鉴权中心,因为服务器IIS问题 http/2 故而没法使用 + + ////重试策略 + //var defaultMethodConfig = new MethodConfig + //{ + // Names = { MethodName.Default }, + // RetryPolicy = new RetryPolicy + // { + // MaxAttempts = 3, + // InitialBackoff = TimeSpan.FromSeconds(1), + // MaxBackoff = TimeSpan.FromSeconds(5), + // BackoffMultiplier = 1.5, + // RetryableStatusCodes = { Grpc.Core.StatusCode.Unavailable } + // } + //}; + + //#region unable to trust the certificate then the gRPC client can be configured to ignore the invalid certificate + + //var httpHandler = new HttpClientHandler(); + //// Return `true` to allow certificates that are untrusted/invalid + //httpHandler.ServerCertificateCustomValidationCallback = + // HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; + + + //////这一句是让grpc支持本地 http 如果本地访问部署在服务器上,那么是访问不成功的 + //AppContext.SetSwitch( + // "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); + + //#endregion + + + + //var grpcAdress = configuration.GetValue("GrpcAddress"); + ////var grpcAdress = "http://localhost:7200"; + + //var channel = GrpcChannel.ForAddress(grpcAdress, new GrpcChannelOptions + //{ + // HttpHandler = httpHandler, + // ServiceConfig = new ServiceConfig { MethodConfigs = { defaultMethodConfig } } + + //}); + ////var channel = GrpcChannel.ForAddress(grpcAdress); + //var grpcClient = new TokenGrpcService.TokenGrpcServiceClient(channel); + + //var userInfo = returnModel.Data.BasicInfo; + + //var tokenResponse = grpcClient.GetUserToken(new GetTokenReuqest() + //{ + // Id = userInfo.Id.ToString(), + // ReviewerCode = userInfo.ReviewerCode, + // IsAdmin = userInfo.IsAdmin, + // RealName = userInfo.RealName, + // UserTypeEnumInt = (int)userInfo.UserTypeEnum, + // UserTypeShortName = userInfo.UserTypeShortName, + // UserName = userInfo.UserName + //}); + + //returnModel.Data.JWTStr = tokenResponse.Token; + + #endregion + + var userId = returnModel.Data.BasicInfo.Id; + + if (_verifyConfig.CurrentValue.OpenLoginMFA) + { + //发版屏蔽 + + returnModel.Data.JWTStr = _tokenService.GetToken(IRaCISClaims.Create(returnModel.Data.BasicInfo)); + + //MFA 发送邮件 + + returnModel.Data.IsMFA = true; + + var email = returnModel.Data.BasicInfo.EMail; + + #region 隐藏Email + // 找到 "@" 符号的位置 + int atIndex = email.IndexOf('@'); + + // 替换 "@" 符号前的中间两位为星号 + string visiblePart = email.Substring(0, atIndex); + + int startIndex = (visiblePart.Length - 2) / 2; + + // 替换中间两位字符为星号 + string hiddenPartBeforeAt = visiblePart.Substring(0, startIndex) + "**" + visiblePart.Substring(startIndex + 2); + + string afterAt = email.Substring(atIndex + 1); + + // 组合隐藏和可见部分 + string hiddenEmail = hiddenPartBeforeAt + "@" + afterAt; + #endregion + + returnModel.Data.BasicInfo.EMail = hiddenEmail; + + await _userService.SendMFAEmail(userId); + + } + else + { + returnModel.Data.JWTStr = _tokenService.GetToken(IRaCISClaims.Create(returnModel.Data.BasicInfo)); + + // 创建一个 CookieOptions 对象,用于设置 Cookie 的属性 + var option = new CookieOptions + { + Expires = DateTime.Now.AddMonths(1), // 设置过期时间为 30 分钟之后 + HttpOnly = false, // 确保 cookie 只能通过 HTTP 访问 + SameSite = Microsoft.AspNetCore.Http.SameSiteMode.None, // 设置 SameSite 属性 + Secure = false // 确保 cookie 只能通过 HTTPS 访问 + }; + + HttpContext.Response.Cookies.Append("access_token", returnModel.Data.JWTStr, option); + + + + // 验证阅片休息时间 + await readingImageTaskService.ResetReadingRestTime(returnModel.Data.BasicInfo.Id); + + await provider.SetAsync(userId.ToString(), returnModel.Data.JWTStr, TimeSpan.FromDays(7)); + + await provider.SetAsync($"{userId.ToString()}_Online", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), TimeSpan.FromMinutes(_verifyConfig.CurrentValue.AutoLoginOutMinutes)); + } + + } + return returnModel; } - var userId = returnModel.Data.BasicInfo.Id.ToString(); - //provider.Set(userId, userId, TimeSpan.FromMinutes(AppSettings.LoginExpiredTimeSpan)); - // 验证阅片休息时间 - await readingImageTaskService.ResetReadingRestTime(returnModel.Data.BasicInfo.Id); - await provider.SetAsync(userId.ToString(), returnModel.Data.JWTStr, TimeSpan.FromDays(7)); - return returnModel; + + + } + + [HttpGet, Route("imageShare/ShareImage")] [AllowAnonymous] public IResponseOutput ShareImage([FromServices] ITokenService _tokenService) @@ -223,7 +317,7 @@ namespace IRaCIS.Api.Controllers var ossOptions = serviceOption.AliyunOSS; - return ResponseOutput.Ok(new ObjectStoreDTO() { ObjectStoreUse = serviceOption.ObjectStoreUse, MinIO = serviceOption.MinIO ,AliyunOSS= serviceOption.AliyunOSS,AWS=serviceOption.AWS }); + return ResponseOutput.Ok(new ObjectStoreDTO() { ObjectStoreUse = serviceOption.ObjectStoreUse, MinIO = serviceOption.MinIO, AliyunOSS = serviceOption.AliyunOSS, AWS = serviceOption.AWS }); #region 临时token 屏蔽 //IClientProfile profile = DefaultProfile.GetProfile(ossOptions.RegionId, ossOptions.AccessKeyId, ossOptions.AccessKeySecret); @@ -264,19 +358,19 @@ namespace IRaCIS.Api.Controllers #endregion } - else if(Enum.TryParse(serviceOption.ObjectStoreUse, out var parsedValue) && parsedValue == ObjectStoreUse.MinIO) + else if (Enum.TryParse(serviceOption.ObjectStoreUse, out var parsedValue) && parsedValue == ObjectStoreUse.MinIO) { - return ResponseOutput.Ok(new ObjectStoreDTO() { ObjectStoreUse=serviceOption.ObjectStoreUse,MinIO=serviceOption.MinIO, AWS = serviceOption.AWS }); + return ResponseOutput.Ok(new ObjectStoreDTO() { ObjectStoreUse = serviceOption.ObjectStoreUse, MinIO = serviceOption.MinIO, AWS = serviceOption.AWS }); } else { return ResponseOutput.Ok(new ObjectStoreDTO() { ObjectStoreUse = serviceOption.ObjectStoreUse, MinIO = serviceOption.MinIO, AWS = serviceOption.AWS }); } - + } - [HttpGet("user/GenerateSTS")] - public IResponseOutput GenerateSTS([FromServices]IOptionsMonitor options ) + [HttpGet("user/GenerateSTS")] + public IResponseOutput GenerateSTS([FromServices] IOptionsMonitor options) { var ossOptions = options.CurrentValue; @@ -304,9 +398,9 @@ namespace IRaCIS.Api.Controllers SecurityToken = response.Credentials.SecurityToken, Expiration = response.Credentials.Expiration, - Region = ossOptions.region , - BucketName = ossOptions.bucketName , - ViewEndpoint = ossOptions.viewEndpoint , + Region = ossOptions.region, + BucketName = ossOptions.bucketName, + ViewEndpoint = ossOptions.viewEndpoint, }; @@ -318,12 +412,12 @@ namespace IRaCIS.Api.Controllers [HttpGet("User/UserRedirect")] [AllowAnonymous] - public async Task UserRedirect([FromServices] IRepository _userRepository, string url ,[FromServices]ILogger _logger) + public async Task UserRedirect([FromServices] IRepository _userRepository, string url, [FromServices] ILogger _logger) { var decodeUrl = System.Web.HttpUtility.UrlDecode(url); - var userId = decodeUrl.Substring(decodeUrl.IndexOf("UserId=") + "UserId=".Length , 36) ; + var userId = decodeUrl.Substring(decodeUrl.IndexOf("UserId=") + "UserId=".Length, 36); var token = decodeUrl.Substring(decodeUrl.IndexOf("access_token=") + "access_token=".Length); @@ -331,12 +425,12 @@ namespace IRaCIS.Api.Controllers var domainStrList = decodeUrl.Split("/").ToList().Take(3).ToList(); - var errorUrl = domainStrList[0]+"//"+ domainStrList[2]+ "/error"; + var errorUrl = domainStrList[0] + "//" + domainStrList[2] + "/error"; - if (!await _userRepository.AnyAsync(t => t.Id == Guid.Parse(userId) && t.EmailToken == token && t.IsFirstAdd)) + if (!await _userRepository.AnyAsync(t => t.Id == Guid.Parse(userId) && t.EmailToken == token && t.IsFirstAdd)) { - decodeUrl = errorUrl+ $"?lang={lang}&ErrorMessage={System.Web.HttpUtility.UrlEncode(lang=="zh"? "您的初始化链接已过期": "Error!The initialization link has expired. Return")} "; + decodeUrl = errorUrl + $"?lang={lang}&ErrorMessage={System.Web.HttpUtility.UrlEncode(lang == "zh" ? "您的初始化链接已过期" : "Error!The initialization link has expired. Return")} "; } return Redirect(decodeUrl); diff --git a/IRaCIS.Core.API/IRaCIS.Core.API.xml b/IRaCIS.Core.API/IRaCIS.Core.API.xml index 3580ca96a..ddbe96072 100644 --- a/IRaCIS.Core.API/IRaCIS.Core.API.xml +++ b/IRaCIS.Core.API/IRaCIS.Core.API.xml @@ -29,7 +29,7 @@ - + 系统用户登录接口[New] diff --git a/IRaCIS.Core.API/appsettings.Test_IRC.json b/IRaCIS.Core.API/appsettings.Test_IRC.json index 9e91ec0c2..e27b7194c 100644 --- a/IRaCIS.Core.API/appsettings.Test_IRC.json +++ b/IRaCIS.Core.API/appsettings.Test_IRC.json @@ -60,7 +60,10 @@ "LoginMaxFailCount": 5, "LoginFailLockMinutes": 1, - "AutoLoginOutMinutes": 1 + + "AutoLoginOutMinutes": 1, + + "OpenLoginMFA": false }, "SystemEmailSendConfig": { diff --git a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml index d49fea38a..b4aa35307 100644 --- a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml +++ b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml @@ -853,11 +853,22 @@ 后台托管服务的方式运行 - + + + 受试者随机阅片,任务进行随机编号 + 进入阅片任务前,随机挑选出该受试者的一个任务,然后给该任务一个编号,编号给的逻辑是:TimePoint Ran+ 已阅任务数量+1 + 根据当前受试者该标准已阅任务数量(生效失效的任务都算,考虑重阅,最后编号不重复) 第一个就是TimePoint Ran1,后面依次随机挑选出来的阅片序号依次递增 + + + + + + 获取该受试者任务上传列表(展示已上传情况) + @@ -13254,6 +13265,22 @@ + + + 发送MFA 验证邮件 + + + + + + + 验证MFA 邮件 + + + + + + 用户登陆 diff --git a/IRaCIS.Core.Application/Service/Allocation/VisitTaskHelpeService.cs b/IRaCIS.Core.Application/Service/Allocation/VisitTaskHelpeService.cs index 976ebf9d4..cdb8a02e2 100644 --- a/IRaCIS.Core.Application/Service/Allocation/VisitTaskHelpeService.cs +++ b/IRaCIS.Core.Application/Service/Allocation/VisitTaskHelpeService.cs @@ -155,6 +155,8 @@ namespace IRaCIS.Core.Application.Service await _visitTaskRepository.SaveChangesAsync(); } + + //基于标准签名 产生任务 (或者手动选择某些访视生成该标准的任务) public async Task BaseCritrionGenerateVisitTask(Guid trialId, Guid confirmedTrialReadingCriterionId, bool? isManualSelectVisit = null, List? subjectVisitIdList = null) { @@ -197,26 +199,29 @@ namespace IRaCIS.Core.Application.Service foreach (var subjectVisit in subjectGroup.SubjectVisitList) { - var blindTaskName = string.Empty; var isNeedClinicalDataSign = IsNeedClinicalDataSign(ReadingCategory.Visit, subjectVisit.IsBaseLine, trialReadingCriterionConfig.TrialReadingCriterionId, clinicalDataConfirmList); var isClinicalDataSign = IsClinicalDataSign(ReadingCategory.Visit, subjectVisit.IsBaseLine, trialReadingCriterionConfig.TrialReadingCriterionId, clinicalDataConfirmList, subjectVisit.Id, trialId); - if (visitNumList.IndexOf(subjectVisit.VisitNum) == 0) + var blindTaskName = string.Empty; + + if (trialReadingCriterionConfig.IsReadingTaskViewInOrder == ReadingOrder.InOrder) { - blindTaskName = visitBlindConfig.BlindBaseLineName; - } - else - { - if (trialReadingCriterionConfig.IsReadingTaskViewInOrder == ReadingOrder.InOrder) + if (visitNumList.IndexOf(subjectVisit.VisitNum) == 0 && subjectVisit.VisitNum==0) { - blindTaskName = visitBlindConfig.BlindFollowUpPrefix + " " + visitNumList.IndexOf(subjectVisit.VisitNum); + blindTaskName = visitBlindConfig.BlindBaseLineName; } else { - blindTaskName = visitBlindConfig.BlindFollowUpPrefix; + blindTaskName = visitBlindConfig.BlindFollowUpPrefix + " " + visitNumList.IndexOf(subjectVisit.VisitNum); + } } + else + { + blindTaskName ="Timepoint"; + } + //每个访视 根据项目配置生成任务 双审生成两个 @@ -661,17 +666,16 @@ namespace IRaCIS.Core.Application.Service var assignConfigList = await _subjectUserRepository.Where(t => t.TrialId == trialId && t.TrialReadingCriterionId == trialReadingCriterionConfig.TrialReadingCriterionId && t.SubjectId == subjectVisit.SubjectId && t.OrignalSubjectUserId == null && t.IsConfirmed).Select(u => new { u.DoctorUserId, u.ArmEnum }).ToListAsync(); - - var blindTaskName = string.Empty; - var isNeedClinicalDataSign = IsNeedClinicalDataSign(ReadingCategory.Visit, subjectVisit.IsBaseLine, trialReadingCriterionConfig.TrialReadingCriterionId, clinicalDataConfirmList); var isClinicalDataSign = IsClinicalDataSign(ReadingCategory.Visit, subjectVisit.IsBaseLine, trialReadingCriterionConfig.TrialReadingCriterionId, clinicalDataConfirmList, subjectVisit.Id, trialId); var isFrontTaskNeedSignButNotSign = await _visitTaskRepository.AnyAsync(t => t.TrialReadingCriterionId == trialReadingCriterionId && t.SubjectId == subjectVisit.SubjectId && t.TaskState == TaskState.Effect && t.VisitTaskNum < subjectVisit.VisitNum && t.IsNeedClinicalDataSign == true && t.IsClinicalDataSign == false); + var blindTaskName = string.Empty; + if (trialReadingCriterionConfig.IsReadingTaskViewInOrder == ReadingOrder.InOrder) { - if (visitNumList.IndexOf(subjectVisit.VisitNum) == 0) + if (visitNumList.IndexOf(subjectVisit.VisitNum) == 0 && subjectVisit.VisitNum == 0) { blindTaskName = visitBlindConfig.BlindBaseLineName; } @@ -683,7 +687,7 @@ namespace IRaCIS.Core.Application.Service } else { - blindTaskName = visitBlindConfig.BlindFollowUpPrefix; + blindTaskName = "Timepoint"; } diff --git a/IRaCIS.Core.Application/Service/Allocation/VisitTaskService.cs b/IRaCIS.Core.Application/Service/Allocation/VisitTaskService.cs index f0a826372..4450a2779 100644 --- a/IRaCIS.Core.Application/Service/Allocation/VisitTaskService.cs +++ b/IRaCIS.Core.Application/Service/Allocation/VisitTaskService.cs @@ -105,21 +105,22 @@ namespace IRaCIS.Core.Application.Service.Allocation var list = await _repository.Where(t => t.TrialId == trialId && t.IsConfirm) .OrderBy(t => t.ShowOrder) - .Select(t => new TrialReadingCriterionDto() { - TrialReadingCriterionId = t.Id, + .Select(t => new TrialReadingCriterionDto() + { + TrialReadingCriterionId = t.Id, IsAutoCreate = t.IsAutoCreate, - IsAdditionalAssessment = t.IsAdditionalAssessment, + IsAdditionalAssessment = t.IsAdditionalAssessment, TrialReadingCriterionName = t.CriterionName, - CriterionType = t.CriterionType, - ReadingType = t.ReadingType, - ReadingInfoSignTime = t.ReadingInfoSignTime , - IsReadingPeriod= t.IsReadingPeriod, - IsArbitrationReading=t.IsArbitrationReading, - IsGlobalReading=t.IsGlobalReading, - IsOncologyReading=t.IsOncologyReading, - ImageDownloadEnum=t.ImageDownloadEnum, - ImageUploadEnum=t.ImageUploadEnum, - IsReadingTaskViewInOrder= t.IsReadingTaskViewInOrder + CriterionType = t.CriterionType, + ReadingType = t.ReadingType, + ReadingInfoSignTime = t.ReadingInfoSignTime, + IsReadingPeriod = t.IsReadingPeriod, + IsArbitrationReading = t.IsArbitrationReading, + IsGlobalReading = t.IsGlobalReading, + IsOncologyReading = t.IsOncologyReading, + ImageDownloadEnum = t.ImageDownloadEnum, + ImageUploadEnum = t.ImageUploadEnum, + IsReadingTaskViewInOrder = t.IsReadingTaskViewInOrder }) .ToListAsync(); @@ -154,12 +155,12 @@ namespace IRaCIS.Core.Application.Service.Allocation .WhereIf(querySubjectAssign.DoctorUserId != null, t => t.SubjectDoctorList.Any(t => t.DoctorUserId == querySubjectAssign.DoctorUserId && t.TrialReadingCriterionId == querySubjectAssign.TrialReadingCriterionId)) .WhereIf(!string.IsNullOrEmpty(querySubjectAssign.SubjectCode), t => t.Code.Contains(querySubjectAssign.SubjectCode)) //未分配 - .WhereIf(querySubjectAssign.SubjectAllocateState == 0, t => !t.SubjectDoctorList.Any(t => t.AssignTime!=null && t.TrialReadingCriterionId == querySubjectAssign.TrialReadingCriterionId)) - //已分配 + .WhereIf(querySubjectAssign.SubjectAllocateState == 0, t => !t.SubjectDoctorList.Any(t => t.AssignTime != null && t.TrialReadingCriterionId == querySubjectAssign.TrialReadingCriterionId)) + //已分配 .WhereIf(querySubjectAssign.SubjectAllocateState == 1, t => t.SubjectDoctorList.Any(t => t.AssignTime != null && t.TrialReadingCriterionId == querySubjectAssign.TrialReadingCriterionId)) - .WhereIf(querySubjectAssign.ArmList.Count >0 , t => !querySubjectAssign.ArmList.Except(t.SubjectDoctorList.Where(t => t.AssignTime != null && t.TrialReadingCriterionId == querySubjectAssign.TrialReadingCriterionId).Select(c => c.ArmEnum)).Any() ) - + .WhereIf(querySubjectAssign.ArmList.Count > 0, t => !querySubjectAssign.ArmList.Except(t.SubjectDoctorList.Where(t => t.AssignTime != null && t.TrialReadingCriterionId == querySubjectAssign.TrialReadingCriterionId).Select(c => c.ArmEnum)).Any()) + .WhereIf(isAddtinoarlCriterion, t => t.SubjectCriteriaEvaluationList.Where(t => t.TrialReadingCriterionId == querySubjectAssign.TrialReadingCriterionId).Any(t => t.IsJoinEvaluation)) @@ -173,7 +174,7 @@ namespace IRaCIS.Core.Application.Service.Allocation var criterionConfig = (await _trialReadingCriterionRepository.Where(x => x.Id == querySubjectAssign.TrialReadingCriterionId).Select(x => new { x.ReadingTool, x.IsReadingTaskViewInOrder, x.ReadingType, x.IsArbitrationReading, x.IsOncologyReading, x.IsGlobalReading }).FirstOrDefaultAsync()).IfNullThrowException(); - return ResponseOutput.Ok (pageList, criterionConfig); + return ResponseOutput.Ok(pageList, criterionConfig); } /// @@ -307,7 +308,7 @@ namespace IRaCIS.Core.Application.Service.Allocation var subjectId = cancelCommand.CancelList.First().SubjectId; - await _repository.AddAsync(new SubjectCanceDoctor() { SubjectId = subjectId, Note = cancelCommand.Note,TrialReadingCriterionId=cancelCommand.TrialReadingCriterionId }); + await _repository.AddAsync(new SubjectCanceDoctor() { SubjectId = subjectId, Note = cancelCommand.Note, TrialReadingCriterionId = cancelCommand.TrialReadingCriterionId }); await _visitTaskRepository.SaveChangesAsync(); @@ -757,12 +758,12 @@ namespace IRaCIS.Core.Application.Service.Allocation /// /// [HttpPost] - public async Task>> GetReadingTaskList(VisitTaskQuery queryVisitTask) + public async Task>> GetReadingTaskList(VisitTaskQuery queryVisitTask) { var visitTaskQueryable = _visitTaskRepository.Where(t => t.TrialId == queryVisitTask.TrialId && t.IsAnalysisCreate == false) //.Where(t => t.IsAnalysisCreate == false && t.DoctorUserId != null) - .WhereIf(queryVisitTask.IsEffect ==true, t => t.TaskState ==TaskState.Effect || t.TaskState == TaskState.Freeze) + .WhereIf(queryVisitTask.IsEffect == true, t => t.TaskState == TaskState.Effect || t.TaskState == TaskState.Freeze) .WhereIf(queryVisitTask.IsEffect == false, t => t.TaskState == TaskState.Adbandon || t.TaskState == TaskState.HaveReturned) .WhereIf(queryVisitTask.TaskState != null, t => t.TaskState == queryVisitTask.TaskState) @@ -784,9 +785,9 @@ namespace IRaCIS.Core.Application.Service.Allocation .WhereIf(!string.IsNullOrEmpty(queryVisitTask.SubjectCode), t => (t.Subject.Code.Contains(queryVisitTask.SubjectCode) && t.IsAnalysisCreate == false) || (t.BlindSubjectCode.Contains(queryVisitTask.SubjectCode) && t.IsAnalysisCreate)) .WhereIf(queryVisitTask.BeginAllocateDate != null, t => t.AllocateTime > queryVisitTask.BeginAllocateDate) .WhereIf(queryVisitTask.EndAllocateDate != null, t => t.AllocateTime < queryVisitTask.EndAllocateDate) - .WhereIf(queryVisitTask.BeginSignTime != null, t => t.SignTime > queryVisitTask.BeginSignTime) - .WhereIf(queryVisitTask.EndSignTime != null, t => t.SignTime < queryVisitTask.EndSignTime) - .ProjectTo(_mapper.ConfigurationProvider); + .WhereIf(queryVisitTask.BeginSignTime != null, t => t.SignTime > queryVisitTask.BeginSignTime) + .WhereIf(queryVisitTask.EndSignTime != null, t => t.SignTime < queryVisitTask.EndSignTime) + .ProjectTo(_mapper.ConfigurationProvider); var defalutSortArray = new string[] { nameof(VisitTask.IsUrgent) + " desc", nameof(VisitTask.SubjectId), nameof(VisitTask.VisitTaskNum) }; @@ -794,7 +795,7 @@ namespace IRaCIS.Core.Application.Service.Allocation var trialTaskConfig = _trialRepository.Where(t => t.Id == queryVisitTask.TrialId).ProjectTo(_mapper.ConfigurationProvider).FirstOrDefault(); - return ResponseOutput.Ok (pageList, trialTaskConfig); + return ResponseOutput.Ok(pageList, trialTaskConfig); } @@ -826,13 +827,13 @@ namespace IRaCIS.Core.Application.Service.Allocation .WhereIf(queryVisitTask.ReadingCategory != null, t => t.OriginalReReadingTask.ReadingCategory == queryVisitTask.ReadingCategory) .WhereIf(!string.IsNullOrEmpty(queryVisitTask.RequestReReadingReason), t => t.RequestReReadingReason.Contains(queryVisitTask.RequestReReadingReason)) - + .WhereIf(queryVisitTask.RequestReReadingResultEnum != null, t => t.RequestReReadingResultEnum == queryVisitTask.RequestReReadingResultEnum) .WhereIf(!string.IsNullOrEmpty(queryVisitTask.TrialSiteCode), t => (t.OriginalReReadingTask.BlindTrialSiteCode.Contains(queryVisitTask.TrialSiteCode!) && t.OriginalReReadingTask.IsAnalysisCreate) || (t.OriginalReReadingTask.Subject.TrialSite.TrialSiteCode.Contains(queryVisitTask.TrialSiteCode!) && t.OriginalReReadingTask.IsAnalysisCreate == false)) .WhereIf(!string.IsNullOrEmpty(queryVisitTask.TaskName), t => t.OriginalReReadingTask.TaskName.Contains(queryVisitTask.TaskName) || t.OriginalReReadingTask.TaskBlindName.Contains(queryVisitTask.TaskName)) - .WhereIf(!string.IsNullOrEmpty(queryVisitTask.SubjectCode), t => ((t.OriginalReReadingTask.Subject.Code.Contains(queryVisitTask.SubjectCode)|| t.OriginalReReadingTask.Subject.MedicalNo.Contains(queryVisitTask.SubjectCode)) && t.OriginalReReadingTask.IsAnalysisCreate == false) || (t.OriginalReReadingTask.BlindSubjectCode.Contains(queryVisitTask.SubjectCode) && t.OriginalReReadingTask.IsAnalysisCreate)) + .WhereIf(!string.IsNullOrEmpty(queryVisitTask.SubjectCode), t => ((t.OriginalReReadingTask.Subject.Code.Contains(queryVisitTask.SubjectCode) || t.OriginalReReadingTask.Subject.MedicalNo.Contains(queryVisitTask.SubjectCode)) && t.OriginalReReadingTask.IsAnalysisCreate == false) || (t.OriginalReReadingTask.BlindSubjectCode.Contains(queryVisitTask.SubjectCode) && t.OriginalReReadingTask.IsAnalysisCreate)) .WhereIf(queryVisitTask.BeginAllocateDate != null, t => t.OriginalReReadingTask.AllocateTime > queryVisitTask.BeginAllocateDate) .WhereIf(queryVisitTask.EndAllocateDate != null, t => t.OriginalReReadingTask.AllocateTime < queryVisitTask.EndAllocateDate!.Value.AddDays(1)) @@ -870,12 +871,12 @@ namespace IRaCIS.Core.Application.Service.Allocation .Where(t => t.RequestReReadingType == RequestReReadingType.DocotorApply) .Where(t => t.OriginalReReadingTask.DoctorUserId == _userInfo.Id) .Where(t => t.OriginalReReadingTask.TrialId == queryVisitTask.TrialId) - .WhereIf(queryVisitTask.RequestReReadingResultEnum != null, t => t.RequestReReadingResultEnum == queryVisitTask.RequestReReadingResultEnum) - .WhereIf(queryVisitTask.RootReReadingTaskId != null, t => t.RootReReadingTaskId == queryVisitTask.RootReReadingTaskId || t.OriginalReReadingTaskId == queryVisitTask.RootReReadingTaskId) + .WhereIf(queryVisitTask.RequestReReadingResultEnum != null, t => t.RequestReReadingResultEnum == queryVisitTask.RequestReReadingResultEnum) + .WhereIf(queryVisitTask.RootReReadingTaskId != null, t => t.RootReReadingTaskId == queryVisitTask.RootReReadingTaskId || t.OriginalReReadingTaskId == queryVisitTask.RootReReadingTaskId) .WhereIf(!string.IsNullOrEmpty(queryVisitTask.TaskCode), t => t.OriginalReReadingTask.TaskCode.Contains(queryVisitTask.TaskCode!) || t.RootReReadingTask.TaskCode.Contains(queryVisitTask.TaskCode!)) .WhereIf(queryVisitTask.TaskState != null, t => t.OriginalReReadingTask.TaskState == queryVisitTask.TaskState) - .WhereIf(queryVisitTask.ReadingCategory != null, t => t.OriginalReReadingTask.ReadingCategory == queryVisitTask.ReadingCategory) - .WhereIf(queryVisitTask.ReReadingApplyState != null, t => t.OriginalReReadingTask.ReReadingApplyState == queryVisitTask.ReReadingApplyState) + .WhereIf(queryVisitTask.ReadingCategory != null, t => t.OriginalReReadingTask.ReadingCategory == queryVisitTask.ReadingCategory) + .WhereIf(queryVisitTask.ReReadingApplyState != null, t => t.OriginalReReadingTask.ReReadingApplyState == queryVisitTask.ReReadingApplyState) .WhereIf(queryVisitTask.TrialSiteId != null, t => t.OriginalReReadingTask.Subject.TrialSiteId == queryVisitTask.TrialSiteId) .WhereIf(queryVisitTask.SubjectId != null, t => t.OriginalReReadingTask.SubjectId == queryVisitTask.SubjectId) .WhereIf(queryVisitTask.IsUrgent != null, t => t.OriginalReReadingTask.IsUrgent == queryVisitTask.IsUrgent) @@ -943,14 +944,15 @@ namespace IRaCIS.Core.Application.Service.Allocation /// /// [HttpPost] - public async Task<(PageOutput, object)> GetIRUnReadSubjectTaskList(IRUnReadSubjectQuery iRUnReadSubjectQuery) + public async Task>> GetIRUnReadSubjectTaskList(IRUnReadSubjectQuery inQuery) { - var trialId = iRUnReadSubjectQuery.TrialId; + var trialId = inQuery.TrialId; + var subjectCode = inQuery.SubjectCode; - var trialReadingCriterionId = iRUnReadSubjectQuery.TrialReadingCriterionId; + var trialReadingCriterionId = inQuery.TrialReadingCriterionId; - var criterionConfig = await _trialReadingCriterionRepository.Where(x => x.Id == iRUnReadSubjectQuery.TrialReadingCriterionId).FirstNotNullAsync(); + var criterionConfig = await _trialReadingCriterionRepository.Where(x => x.Id == inQuery.TrialReadingCriterionId).FirstNotNullAsync(); var readingTool = criterionConfig.ReadingTool; var isReadingTaskViewInOrder = criterionConfig.IsReadingTaskViewInOrder; @@ -964,13 +966,13 @@ namespace IRaCIS.Core.Application.Service.Allocation { TrialId = trialId, TrialReadingCriterionId = trialReadingCriterionId, - SubjectCode = iRUnReadSubjectQuery.SubjectCode, + SubjectCode = inQuery.SubjectCode, Page = new PageInput() { - PageIndex = iRUnReadSubjectQuery.PageIndex, - PageSize = iRUnReadSubjectQuery.PageSize, - Asc = iRUnReadSubjectQuery.Asc, - SortField = iRUnReadSubjectQuery.SortField, + PageIndex = inQuery.PageIndex, + PageSize = inQuery.PageSize, + Asc = inQuery.Asc, + SortField = inQuery.SortField, } @@ -984,15 +986,15 @@ namespace IRaCIS.Core.Application.Service.Allocation var result = new PageOutput() { - PageSize = iRUnReadSubjectQuery.PageSize, - PageIndex = iRUnReadSubjectQuery.PageIndex, + PageSize = inQuery.PageSize, + PageIndex = inQuery.PageIndex, TotalCount = totalCount, CurrentPageData = currentPageData, }; // 封装的方法有问题 //var result = await visitQuery.ToPagedListAsync(iRUnReadSubjectQuery.PageIndex, iRUnReadSubjectQuery.PageSize, String.IsNullOrEmpty(iRUnReadSubjectQuery.SortField) ? nameof(IRUnReadSubjectView.SubjectId) : iRUnReadSubjectQuery.SortField, iRUnReadSubjectQuery.Asc); - return (result, new + return ResponseOutput.Ok(result, new { RandomReadInfo = new IRUnReadOutDto(), IsReadingTaskViewInOrder = isReadingTaskViewInOrder, @@ -1004,26 +1006,15 @@ namespace IRaCIS.Core.Application.Service.Allocation CriterionType = criterionConfig.CriterionType, }); } - else + else if(isReadingTaskViewInOrder == ReadingOrder.SubjectRandom) { - var taskQuery = _visitTaskRepository.Where(x => x.TrialId == iRUnReadSubjectQuery.TrialId && x.DoctorUserId == _userInfo.Id && x.TaskState == TaskState.Effect && x.TrialReadingCriterionId == trialReadingCriterionId) - // .Where(x=>x.Subject.ClinicalDataList.Any(c => c.IsSign && (c.ReadingId == x.SouceReadModuleId || c.ReadingId == x.SourceSubjectVisitId))) - .Where(x => !x.Subject.IsDeleted).Where(x => (x.IsNeedClinicalDataSign && x.IsClinicalDataSign) || !x.IsNeedClinicalDataSign); - - IRUnReadOutDto iRUnReadOut = new IRUnReadOutDto() - { - FinishJudgeTaskCount = await taskQuery.Where(x => x.ReadingCategory == ReadingCategory.Judge && x.ReadingTaskState == ReadingTaskState.HaveSigned).CountAsync(), - FinishTaskCount = await taskQuery.Where(x => x.ReadingCategory != ReadingCategory.Judge && x.ReadingTaskState == ReadingTaskState.HaveSigned).CountAsync(), - SuggesteFinishedTime = await taskQuery.Where(x => x.ReadingTaskState != ReadingTaskState.HaveSigned).MaxAsync(x => x.SuggesteFinishedTime), - UnReadJudgeTaskCount = await taskQuery.Where(x => x.ReadingCategory == ReadingCategory.Judge && x.ReadingTaskState != ReadingTaskState.HaveSigned).CountAsync(), - UnReadTaskCount = await taskQuery.Where(x => x.ReadingCategory != ReadingCategory.Judge && x.ReadingTaskState != ReadingTaskState.HaveSigned).CountAsync(), - }; - - - - var visitGroupQuery = taskQuery.GroupBy(x => new { x.SubjectId, x.Subject.Code, x.BlindSubjectCode }); + var critrion = await _trialReadingCriterionRepository.FindAsync(trialReadingCriterionId); + var visitQuery = _visitTaskRepository.Where(x => x.TrialId == trialId && x.DoctorUserId == _userInfo.Id && x.TaskState == TaskState.Effect) + + .WhereIf(!string.IsNullOrEmpty(subjectCode), t => (t.Subject.Code.Contains(subjectCode!) && t.IsAnalysisCreate == false) || (t.BlindSubjectCode.Contains(subjectCode!) && t.IsAnalysisCreate)); + var visitGroupQuery = visitQuery.GroupBy(x => new { x.SubjectId, x.Subject.Code, x.BlindSubjectCode }); var visitTaskQuery = visitGroupQuery.Select(x => new IRUnReadSubjectView() { @@ -1033,22 +1024,17 @@ namespace IRaCIS.Core.Application.Service.Allocation SuggesteFinishedTime = x.Where(y => y.TrialReadingCriterionId == trialReadingCriterionId && y.ReadingTaskState != ReadingTaskState.HaveSigned).Min(x => x.SuggesteFinishedTime), //未读任务量 - UnReadTaskCount = x.Where(y => y.TrialReadingCriterionId == trialReadingCriterionId && y.ReadingTaskState != ReadingTaskState.HaveSigned).Count(), - - //未读 里可读任务量 - UnReadCanReadTaskCount = x.Where(y => y.TrialReadingCriterionId == trialReadingCriterionId && y.ReadingTaskState != ReadingTaskState.HaveSigned && y.IsFrontTaskNeedSignButNotSign == false && (y.IsNeedClinicalDataSign == false || y.IsClinicalDataSign == true) - //不能对包含聚合或子查询的表达式执行聚合函数 - //&& !x.Any(t => t.ReadingTaskState != ReadingTaskState.HaveSigned && t.IsNeedClinicalDataSign == true && t.IsClinicalDataSign == false && t.VisitTaskNum y.TrialReadingCriterionId == trialReadingCriterionId && y.ReadingTaskState != ReadingTaskState.HaveSigned).Count(), + UrgentCount = x.Where(y => y.TrialReadingCriterionId == trialReadingCriterionId && y.ReadingTaskState != ReadingTaskState.HaveSigned).Where(x => x.IsUrgent).Count(), //已读任务量 HaveReadTaskCount = x.Where(y => y.TrialReadingCriterionId == trialReadingCriterionId && y.ReadingTaskState == ReadingTaskState.HaveSigned).Count(), ExistReadingApply = x.Any(y => (y.ReReadingApplyState == ReReadingApplyState.DocotorHaveApplyed && y.TrialReadingCriterionId == trialReadingCriterionId) || y.ReReadingApplyState == ReReadingApplyState.TrialGroupHaveApplyed), - - //查出所有未读的 未读的可读的 在这个列表基础上 过滤下 y.IsFrontTaskNeedSignButNotSign==false && (y.IsNeedClinicalDataSign == false || y.IsClinicalDataSign == true) 这样容易排错 确认这三个字段是否维护有误 - UnReadTaskList = x.Where(y => y.TrialReadingCriterionId == trialReadingCriterionId && y.ReadingTaskState != ReadingTaskState.HaveSigned).OrderBy(x => x.VisitTaskNum) + + UnReadCanReadTaskList = x.Where(y => y.TrialReadingCriterionId == trialReadingCriterionId && y.ReadingTaskState != ReadingTaskState.HaveSigned) + .OrderBy(x => x.VisitTaskNum) .Select(u => new IRUnreadTaskView() { Id = u.Id, @@ -1065,22 +1051,48 @@ namespace IRaCIS.Core.Application.Service.Allocation IsClinicalDataSign = u.IsClinicalDataSign, IsFrontTaskNeedSignButNotSign = u.IsFrontTaskNeedSignButNotSign }) - .ToList(), + .ToList() }).Where(x => x.UnReadCanReadTaskCount > 0); + var pageList = await visitTaskQuery.ToPagedListAsync(inQuery.PageIndex, inQuery.PageSize, string.IsNullOrWhiteSpace(inQuery.SortField)? nameof(IRUnReadSubjectView.UnReadCanReadTaskCount) : inQuery.SortField, inQuery.Asc ); - var totalCount = await visitGroupQuery.CountAsync(); - var currentPageData = await visitTaskQuery.ToListAsync(); + + return ResponseOutput.Ok(pageList, new + { + RandomReadInfo = new IRUnReadOutDto(), + IsReadingTaskViewInOrder = isReadingTaskViewInOrder, + ReadingTool = readingTool, + IseCRFShowInDicomReading = criterionConfig.IseCRFShowInDicomReading, + IsReadingShowSubjectInfo = criterionConfig.IsReadingShowSubjectInfo, + IsReadingShowPreviousResults = criterionConfig.IsReadingShowPreviousResults, + DigitPlaces = criterionConfig.DigitPlaces, + CriterionType = criterionConfig.CriterionType, + }); + } + else + { + var taskQuery = _visitTaskRepository.Where(x => x.TrialId == inQuery.TrialId && x.DoctorUserId == _userInfo.Id && x.TaskState == TaskState.Effect && x.TrialReadingCriterionId == trialReadingCriterionId) + // .Where(x=>x.Subject.ClinicalDataList.Any(c => c.IsSign && (c.ReadingId == x.SouceReadModuleId || c.ReadingId == x.SourceSubjectVisitId))) + .Where(x => !x.Subject.IsDeleted).Where(x => (x.IsNeedClinicalDataSign && x.IsClinicalDataSign) || !x.IsNeedClinicalDataSign); + + IRUnReadOutDto iRUnReadOut = new IRUnReadOutDto() + { + FinishJudgeTaskCount = await taskQuery.Where(x => x.ReadingCategory == ReadingCategory.Judge && x.ReadingTaskState == ReadingTaskState.HaveSigned).CountAsync(), + FinishTaskCount = await taskQuery.Where(x => x.ReadingCategory != ReadingCategory.Judge && x.ReadingTaskState == ReadingTaskState.HaveSigned).CountAsync(), + SuggesteFinishedTime = await taskQuery.Where(x => x.ReadingTaskState != ReadingTaskState.HaveSigned).MaxAsync(x => x.SuggesteFinishedTime), + UnReadJudgeTaskCount = await taskQuery.Where(x => x.ReadingCategory == ReadingCategory.Judge && x.ReadingTaskState != ReadingTaskState.HaveSigned).CountAsync(), + UnReadTaskCount = await taskQuery.Where(x => x.ReadingCategory != ReadingCategory.Judge && x.ReadingTaskState != ReadingTaskState.HaveSigned).CountAsync(), + }; var result = new PageOutput() { - PageSize = iRUnReadSubjectQuery.PageSize, - PageIndex = iRUnReadSubjectQuery.PageIndex, - TotalCount = totalCount, - CurrentPageData = currentPageData, + PageSize = inQuery.PageSize, + PageIndex = inQuery.PageIndex, + TotalCount = 0, + CurrentPageData = null, }; - return (result, new + return ResponseOutput.Ok(result, new { IsReadingTaskViewInOrder = isReadingTaskViewInOrder, RandomReadInfo = iRUnReadOut, @@ -1114,8 +1126,7 @@ namespace IRaCIS.Core.Application.Service.Allocation var critrion = await _trialReadingCriterionRepository.FindAsync(trialReadingCriterionId); - var visitQuery = _visitTaskRepository.Where(x => x.TrialId == inDto.TrialId && x.DoctorUserId == _userInfo.Id - && x.TaskState == TaskState.Effect /*&& x.TrialReadingCriterionId== inDto.TrialReadingCriterionId*/) + var visitQuery = _visitTaskRepository.Where(x => x.TrialId == inDto.TrialId && x.DoctorUserId == _userInfo.Id && x.TaskState == TaskState.Effect) .WhereIf(inDto.SubjectId!=null,x=>x.SubjectId==inDto.SubjectId) //前序 不存在 未生成任务的访视 @@ -1151,34 +1162,34 @@ namespace IRaCIS.Core.Application.Service.Allocation ).Count(), - UnReadCanReadTaskList= x.Where(y => y.TrialReadingCriterionId == trialReadingCriterionId && y.ReadingTaskState != ReadingTaskState.HaveSigned) - .Where(y => y.IsFrontTaskNeedSignButNotSign == false && (y.IsNeedClinicalDataSign == false || y.IsClinicalDataSign == true)) - .OrderBy(x => x.VisitTaskNum) - .Select(u => new IRUnreadTaskView() - { - Id = u.Id, - IsUrgent = u.IsUrgent, - VisitNum = u.VisitTaskNum, - TaskBlindName = u.TaskBlindName, - VisistId = u.SourceSubjectVisitId, - SuggesteFinishedTime = u.SuggesteFinishedTime, - ReadingCategory = u.ReadingCategory, - IsAnalysisCreate = u.IsAnalysisCreate, - ArmEnum = u.ArmEnum, - TrialReadingCriterionId = u.TrialReadingCriterionId, - IsNeedClinicalDataSign = u.IsNeedClinicalDataSign, - IsClinicalDataSign = u.IsClinicalDataSign, - IsFrontTaskNeedSignButNotSign = u.IsFrontTaskNeedSignButNotSign - }) - .ToList(), + UnReadCanReadTaskList = x.Where(y => y.TrialReadingCriterionId == trialReadingCriterionId && y.ReadingTaskState != ReadingTaskState.HaveSigned) + .Where(y => y.IsFrontTaskNeedSignButNotSign == false && (y.IsNeedClinicalDataSign == false || y.IsClinicalDataSign == true)) + .OrderBy(x => x.VisitTaskNum) + .Select(u => new IRUnreadTaskView() + { + Id = u.Id, + IsUrgent = u.IsUrgent, + VisitNum = u.VisitTaskNum, + TaskBlindName = u.TaskBlindName, + VisistId = u.SourceSubjectVisitId, + SuggesteFinishedTime = u.SuggesteFinishedTime, + ReadingCategory = u.ReadingCategory, + IsAnalysisCreate = u.IsAnalysisCreate, + ArmEnum = u.ArmEnum, + TrialReadingCriterionId = u.TrialReadingCriterionId, + IsNeedClinicalDataSign = u.IsNeedClinicalDataSign, + IsClinicalDataSign = u.IsClinicalDataSign, + IsFrontTaskNeedSignButNotSign = u.IsFrontTaskNeedSignButNotSign + }) + .ToList(), - UrgentCount = x.Where(y => y.TrialReadingCriterionId == trialReadingCriterionId && y.ReadingTaskState != ReadingTaskState.HaveSigned) - .Where(y => y.IsFrontTaskNeedSignButNotSign == false && (y.IsNeedClinicalDataSign == false || y.IsClinicalDataSign == true)) - .Where(x=>x.IsUrgent).Count(), + UrgentCount = x.Where(y => y.TrialReadingCriterionId == trialReadingCriterionId && y.ReadingTaskState != ReadingTaskState.HaveSigned) + .Where(y => y.IsFrontTaskNeedSignButNotSign == false && (y.IsNeedClinicalDataSign == false || y.IsClinicalDataSign == true)) + .Where(x => x.IsUrgent).Count(), - //已读任务量 - HaveReadTaskCount = x.Where(y => y.TrialReadingCriterionId == trialReadingCriterionId && y.ReadingTaskState == ReadingTaskState.HaveSigned).Count(), + //已读任务量 + HaveReadTaskCount = x.Where(y => y.TrialReadingCriterionId == trialReadingCriterionId && y.ReadingTaskState == ReadingTaskState.HaveSigned).Count(), ExistReadingApply = x.Any(y => (y.ReReadingApplyState == ReReadingApplyState.DocotorHaveApplyed && y.TrialReadingCriterionId == trialReadingCriterionId) || y.ReReadingApplyState == ReReadingApplyState.TrialGroupHaveApplyed), @@ -1211,7 +1222,7 @@ namespace IRaCIS.Core.Application.Service.Allocation var result = new List(); - var propName = string.IsNullOrWhiteSpace(inDto.Page!.SortField) ? "UnReadCanReadTaskCount" : inDto.Page.SortField; + var propName = string.IsNullOrWhiteSpace(inDto.Page!.SortField) ? nameof(IRUnReadSubjectView.UnReadCanReadTaskCount) : inDto.Page.SortField; var visitTaskOrderQuery = inDto.Page.Asc ? visitTaskQuery.OrderBy(propName) : visitTaskQuery.OrderBy(propName + " desc"); if (inDto.Page != null) @@ -1276,14 +1287,14 @@ namespace IRaCIS.Core.Application.Service.Allocation var baseLineTaskList = await _visitTaskRepository.Where(t => t.TrialId == command.TrialId && t.TrialReadingCriterionId == command.TrialReadingCriterionId && t.DoctorUserId == _userInfo.Id && t.TaskState == TaskState.Effect && t.ReadingCategory == ReadingCategory.Visit && t.ReadingTaskState == ReadingTaskState.HaveSigned && t.SourceSubjectVisit.IsBaseLine == true).ToListAsync(); - var judegeList= await _visitTaskRepository.Where(t => t.TrialId == command.TrialId && t.TrialReadingCriterionId == command.TrialReadingCriterionId && t.DoctorUserId == _userInfo.Id + var judegeList = await _visitTaskRepository.Where(t => t.TrialId == command.TrialId && t.TrialReadingCriterionId == command.TrialReadingCriterionId && t.DoctorUserId == _userInfo.Id && t.TaskState == TaskState.Effect && t.ReadingCategory == ReadingCategory.Judge && t.ReadingTaskState == ReadingTaskState.HaveSigned).ToListAsync(); foreach (var item in judegeList) { if (!baseLineTaskList.Any(t => t.SubjectId == item.SubjectId)) { - baseLineTaskList.Add(item); + baseLineTaskList.Add(item); } } @@ -2191,8 +2202,8 @@ namespace IRaCIS.Core.Application.Service.Allocation foreach (var item in readingTableAnswerRowInfoList) { - var originalVisitTaskId= item.VisitTaskId; - var originalFristAddTaskId= item.FristAddTaskId; + var originalVisitTaskId = item.VisitTaskId; + var originalFristAddTaskId = item.FristAddTaskId; item.Id = NewId.NextSequentialGuid(); item.VisitTaskId = newTask.Id; diff --git a/IRaCIS.Core.Application/Service/Common/MailService.cs b/IRaCIS.Core.Application/Service/Common/MailService.cs index 4ebc148eb..3acfa9ebd 100644 --- a/IRaCIS.Core.Application/Service/Common/MailService.cs +++ b/IRaCIS.Core.Application/Service/Common/MailService.cs @@ -21,6 +21,8 @@ namespace IRaCIS.Application.Services Task SiteSurveyRejectEmail(MimeMessage messageToSend); + Task SenMFAVerifyEmail(Guid userId, string userName, string emailAddress, int verificationCode); + Task SendMailEditEmail(Guid userId, string userName, string emailAddress, int verificationCode); Task AnolymousSendEmailForResetAccount(string emailAddress, int verificationCode); @@ -91,6 +93,66 @@ namespace IRaCIS.Application.Services return str; } + //MFA + public async Task SenMFAVerifyEmail(Guid userId, string userName, string emailAddress, int verificationCode) + { + var messageToSend = new MimeMessage(); + //发件地址 + messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail)); + //收件地址 + messageToSend.To.Add(new MailboxAddress(userName, emailAddress)); + //主题 + //---[来自{0}] 关于MFA邮箱验证的提醒 + messageToSend.Subject = _localizer["Mail_EmailMFATopic", _userInfo.IsEn_Us ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN]; + + var builder = new BodyBuilder(); + + + var pathToFile = _hostEnvironment.WebRootPath + + Path.DirectorySeparatorChar.ToString() + + "EmailTemplate" + + Path.DirectorySeparatorChar.ToString() + //+ "UserOptCommon.html"; + + (_userInfo.IsEn_Us ? "UserOptCommon_US.html" : "UserOptCommon.html"); + + using (StreamReader SourceReader = System.IO.File.OpenText(pathToFile)) + { + var templateInfo = SourceReader.ReadToEnd(); + + + builder.HtmlBody = string.Format(ReplaceCompanyName(templateInfo), + + userName, + _localizer["Mail_MFAEmail"], + verificationCode + ); + } + + + messageToSend.Body = builder.ToMessageBody(); + + + + EventHandler sucessHandle = (sender, args) => + { + // args.Response + var code = verificationCode.ToString(); + _ = _verificationCodeRepository.AddAsync(new VerificationCode() + { + CodeType = 0, + HasSend = true, + Code = code, + UserId = userId, + ExpirationTime = DateTime.Now.AddMinutes(3) + }).Result; + _ = _verificationCodeRepository.SaveChangesAsync().Result; + + }; + + + await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig, sucessHandle); + } + //重置邮箱 public async Task SendMailEditEmail(Guid userId, string userName, string emailAddress, int verificationCode) { diff --git a/IRaCIS.Core.Application/Service/ImageAndDoc/DTO/DownloadAndUploadDTO.cs b/IRaCIS.Core.Application/Service/ImageAndDoc/DTO/DownloadAndUploadDTO.cs index 744f8dbc5..6f3910b2d 100644 --- a/IRaCIS.Core.Application/Service/ImageAndDoc/DTO/DownloadAndUploadDTO.cs +++ b/IRaCIS.Core.Application/Service/ImageAndDoc/DTO/DownloadAndUploadDTO.cs @@ -21,6 +21,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc.DTO public string TaskName { get; set; } + public bool IsImageFilter { get; set; } public string CriterionModalitys { get; set; } public Guid? SourceSubjectVisitId { get; set; } diff --git a/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs b/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs index fda43c6ae..890a66eef 100644 --- a/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs +++ b/IRaCIS.Core.Application/Service/ImageAndDoc/DownloadAndUploadService.cs @@ -12,6 +12,7 @@ using Medallion.Threading; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; +using System.Data; using System.IO.Compression; using System.Linq; using System.Text; @@ -28,12 +29,13 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc { private readonly IEasyCachingProvider _provider; private readonly IRepository _systemAnonymizationRepository; + private readonly IRepository _visitTaskRepository; private readonly IRepository _subjectVisitRepository; private readonly IOSSService _oSSService; private readonly IRepository _studyMonitorRepository; private readonly IDistributedLockProvider _distributedLockProvider; public DownloadAndUploadService(IEasyCachingProvider provider, IRepository systemAnonymizationRepository, IRepository subjectVisitRepository, IOSSService oSSService, - IRepository studyMonitorRepository, IDistributedLockProvider distributedLockProvider) + IRepository studyMonitorRepository, IDistributedLockProvider distributedLockProvider, IRepository visitTaskRepository) { _systemAnonymizationRepository = systemAnonymizationRepository; @@ -43,16 +45,62 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc _studyMonitorRepository = studyMonitorRepository; _distributedLockProvider = distributedLockProvider; _provider = provider; + _visitTaskRepository = visitTaskRepository; + } + + /// + /// 受试者随机阅片,任务进行随机编号 + /// 进入阅片任务前,随机挑选出该受试者的一个任务,然后给该任务一个编号,编号给的逻辑是:TimePoint Ran+ 已阅任务数量+1 + /// 根据当前受试者该标准已阅任务数量(生效失效的任务都算,考虑重阅,最后编号不重复) 第一个就是TimePoint Ran1,后面依次随机挑选出来的阅片序号依次递增 + /// + /// + /// + /// + public async Task SubejctRandomReadingTaskNameDeal(Guid subjectId, Guid trialReadingCriterionId) + { + //subject 随机阅片 才处理任务编号 + if (_visitTaskRepository.Any(t => t.SubjectId == subjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.TrialReadingCriterion.IsReadingTaskViewInOrder == ReadingOrder.SubjectRandom)) + { + //找到 非一致性分析,未签名,状态正常的 并且任务名称是TimePoint的 任务 + var needDealTaskList = await _visitTaskRepository.Where(t => t.SubjectId == subjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.IsAnalysisCreate == false && t.DoctorUserId==_userInfo.Id + && t.ReadingTaskState != ReadingTaskState.HaveSigned && t.TaskBlindName == "Timepoint" && t.ReadingCategory == ReadingCategory.Visit + && (t.TaskState == TaskState.Effect || t.TaskState == TaskState.Freeze), true).ToListAsync(); + + if (needDealTaskList.Count>0) + { + //已完成的访视任务数量(包含重阅的) + var haveFinishedTaskCount = await _visitTaskRepository.CountAsync(t => t.SubjectId == subjectId && t.TrialReadingCriterionId == trialReadingCriterionId && t.IsAnalysisCreate == false + && t.ReadingTaskState == ReadingTaskState.HaveSigned && t.ReadingCategory == ReadingCategory.Visit); + + //随机赋值编号 比如要处理5个任务,实例化一个包含1-5的数组,每次随机取出一个 + + List availableNumbers = Enumerable.Range(haveFinishedTaskCount + 1, needDealTaskList.Count).ToList(); + Random rng = new Random(); + foreach (var visitTask in needDealTaskList) + { + int randomIndex = rng.Next(availableNumbers.Count); + + visitTask.TaskBlindName = $"Timepoint Ran {availableNumbers[randomIndex]}"; + + availableNumbers.RemoveAt(randomIndex); + } + await _visitTaskRepository.SaveChangesAsync(); + } + } + return ResponseOutput.Ok(); } /// /// 获取该受试者任务上传列表(展示已上传情况) /// /// + /// /// - public async Task>> GetSubjectImageUploadList(Guid subjectId) + public async Task>> GetSubjectImageUploadList(Guid subjectId,Guid trialReadingCriterionId) { - var query = _repository.Where(t => t.SubjectId == subjectId && t.SourceSubjectVisitId != null && t.DoctorUserId == _userInfo.Id) + await SubejctRandomReadingTaskNameDeal(subjectId, trialReadingCriterionId); + + var query = _repository.Where(t => t.SubjectId == subjectId &&t.TrialReadingCriterionId== trialReadingCriterionId && t.SourceSubjectVisitId != null && t.DoctorUserId == _userInfo.Id) .Select(u => new SubjectImageUploadDTO() { VisitTaskId = u.Id, @@ -63,9 +111,11 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc TrialSiteId = u.Subject.TrialSiteId, - CriterionModalitys= u.TrialReadingCriterion.CriterionModalitys, + IsImageFilter = u.TrialReadingCriterion.IsImageFilter, - SubjectCode = u.IsSelfAnalysis == true ? u.BlindSubjectCode : u.Subject.Code , + CriterionModalitys = u.TrialReadingCriterion.CriterionModalitys, + + SubjectCode = u.IsSelfAnalysis == true ? u.BlindSubjectCode : u.Subject.Code, TaskBlindName = u.TaskBlindName, TaskName = u.TaskName, @@ -313,7 +363,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc else { - var studyId = IdentifierHelper.CreateGuid(incommand.Study.StudyInstanceUid, incommand.TrialId.ToString(), findOriginStudy.VisitTaskId.ToString()); + var studyId = IdentifierHelper.CreateGuid(incommand.Study.StudyInstanceUid, incommand.TrialId.ToString(), findOriginStudy.VisitTaskId.ToString()); var study = await _repository.FirstOrDefaultAsync(t => t.Id == studyId); @@ -324,7 +374,7 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc //特殊处理逻辑 study.Modalities = string.Join("、", incommand.Study.SeriesList.Select(t => t.Modality).Union(study.Modalities.Split("、", StringSplitOptions.RemoveEmptyEntries)).Distinct()); SpecialArchiveStudyDeal(study); - modalitys = study.Modalities; + modalitys = study.Modalities; // 少了整个序列 @@ -535,50 +585,59 @@ namespace IRaCIS.Core.Application.Service.ImageAndDoc if (isAnonymize) { + //受试者随机阅片,需要匿名化检查时间 DicomFile dicomFile = await DicomFile.OpenAsync(destinationPath, Encoding.Default); - DicomDataset dataset = dicomFile.Dataset; + dataset.AddOrUpdate(DicomTag.StudyDate, string.Empty); + dataset.AddOrUpdate(DicomTag.StudyTime, string.Empty); - foreach (var item in addOrUpdateFixedFieldList) - { + #region 前端已经匿名化,不需要做相关tag匿名化 + //DicomFile dicomFile = await DicomFile.OpenAsync(destinationPath, Encoding.Default); - var dicomTag = new DicomTag(Convert.ToUInt16(item.Group, 16), Convert.ToUInt16(item.Element, 16)); + //DicomDataset dataset = dicomFile.Dataset; - dataset.AddOrUpdate(dicomTag, item.ReplaceValue); - } + //foreach (var item in addOrUpdateFixedFieldList) + //{ - foreach (var item in ircFieldList) - { + // var dicomTag = new DicomTag(Convert.ToUInt16(item.Group, 16), Convert.ToUInt16(item.Element, 16)); - var dicomTag = new DicomTag(Convert.ToUInt16(item.Group, 16), Convert.ToUInt16(item.Element, 16)); + // dataset.AddOrUpdate(dicomTag, item.ReplaceValue); + //} - if (dicomTag == DicomTag.ClinicalTrialProtocolID) - { - dataset.AddOrUpdate(dicomTag, subjectAndVisitInfo.TrialCode); + //foreach (var item in ircFieldList) + //{ - } - if (dicomTag == DicomTag.ClinicalTrialSiteID) - { - //dataset.AddOrUpdate(dicomTag, subjectAndVisitInfo.TrialSiteCode); + // var dicomTag = new DicomTag(Convert.ToUInt16(item.Group, 16), Convert.ToUInt16(item.Element, 16)); - } - if (dicomTag == DicomTag.ClinicalTrialSubjectID) - { - dataset.AddOrUpdate(dicomTag, subjectAndVisitInfo.SubjectCode); + // if (dicomTag == DicomTag.ClinicalTrialProtocolID) + // { + // dataset.AddOrUpdate(dicomTag, subjectAndVisitInfo.TrialCode); - } - if (dicomTag == DicomTag.ClinicalTrialTimePointID) - { - dataset.AddOrUpdate(dicomTag, subjectAndVisitInfo.VisitNum.ToString()); + // } + // if (dicomTag == DicomTag.ClinicalTrialSiteID) + // { + // //dataset.AddOrUpdate(dicomTag, subjectAndVisitInfo.TrialSiteCode); - } - if (dicomTag == DicomTag.PatientID) - { - dataset.AddOrUpdate(dicomTag, subjectAndVisitInfo.TrialCode + "_" + subjectAndVisitInfo.SubjectCode); + // } + // if (dicomTag == DicomTag.ClinicalTrialSubjectID) + // { + // dataset.AddOrUpdate(dicomTag, subjectAndVisitInfo.SubjectCode); - } + // } + // if (dicomTag == DicomTag.ClinicalTrialTimePointID) + // { + // dataset.AddOrUpdate(dicomTag, subjectAndVisitInfo.VisitNum.ToString()); + + // } + // if (dicomTag == DicomTag.PatientID) + // { + // dataset.AddOrUpdate(dicomTag, subjectAndVisitInfo.TrialCode + "_" + subjectAndVisitInfo.SubjectCode); + + // } + + //} + #endregion - } } #endregion } diff --git a/IRaCIS.Core.Application/Service/Management/DTO/UserModel.cs b/IRaCIS.Core.Application/Service/Management/DTO/UserModel.cs index 24fb5e16f..a54ec9ad4 100644 --- a/IRaCIS.Core.Application/Service/Management/DTO/UserModel.cs +++ b/IRaCIS.Core.Application/Service/Management/DTO/UserModel.cs @@ -21,6 +21,8 @@ namespace IRaCIS.Application.Contracts { public string UserName { get; set; } = string.Empty; public string Password { get; set; } = string.Empty; + + public Guid? UserId { get; set; } } public class LoginReturnDTO @@ -28,6 +30,8 @@ namespace IRaCIS.Application.Contracts public UserBasicInfo BasicInfo { get; set; } = new UserBasicInfo(); public string JWTStr { get; set; }=string.Empty; + public bool IsMFA { get; set; } = false; + } public class UserBasicInfo @@ -59,7 +63,7 @@ namespace IRaCIS.Application.Contracts public string PermissionStr { get; set; } = String.Empty; - + public string EMail { get; set; } = string.Empty; public bool IsFirstAdd { get; set; } public bool IsReviewer { get; set; } = false; diff --git a/IRaCIS.Core.Application/Service/Management/Interface/IUserService.cs b/IRaCIS.Core.Application/Service/Management/Interface/IUserService.cs index cc44a25ed..873fced43 100644 --- a/IRaCIS.Core.Application/Service/Management/Interface/IUserService.cs +++ b/IRaCIS.Core.Application/Service/Management/Interface/IUserService.cs @@ -10,12 +10,16 @@ namespace IRaCIS.Application.Services Task GetUser(Guid id); Task> GetUserList(UserListQueryDTO param); Task> Login(string userName, string password); + Task VerifyMFACodeAsync(Guid userId, string Code); + + Task SendMFAEmail(Guid userId); + Task GetUserBasicInfo(Guid userId,string pwd); Task ModifyPassword(EditPasswordCommand editPwModel); Task ResetPassword(Guid userId); Task UpdateUser(UserCommand model); Task UpdateUserState(Guid userId, UserStateEnum state); - + //Task SendVerificationCode(string emailOrPhone, VerifyType verificationType, bool isReviewer = false); //Task SetNewPassword(ResetPasswordCommand resetPwdModel); } diff --git a/IRaCIS.Core.Application/Service/Management/UserService.cs b/IRaCIS.Core.Application/Service/Management/UserService.cs index 6490c9bfb..ab4a221c2 100644 --- a/IRaCIS.Core.Application/Service/Management/UserService.cs +++ b/IRaCIS.Core.Application/Service/Management/UserService.cs @@ -15,6 +15,9 @@ using Medallion.Threading; using EasyCaching.Core; using IRaCIS.Core.Application.Contracts; using LoginReturnDTO = IRaCIS.Application.Contracts.LoginReturnDTO; +using IRaCIS.Core.Application.Auth; +using BeetleX.Redis.Commands; +using IRaCIS.Core.Domain.Models; namespace IRaCIS.Application.Services { @@ -22,17 +25,17 @@ namespace IRaCIS.Application.Services public class UserService : BaseService, IUserService { private readonly IRepository _userRepository; - - private readonly IMailVerificationService _mailVerificationService; + + private readonly IMailVerificationService _mailVerificationService; private readonly IRepository _verificationCodeRepository; private readonly IRepository _doctorRepository; private readonly IRepository _userTrialRepository; private readonly IRepository _userLogRepository; - private readonly IRepository _userPassWordLogRepository; - private readonly IDistributedLockProvider _distributedLockProvider; + private readonly IRepository _userPassWordLogRepository; + private readonly IDistributedLockProvider _distributedLockProvider; private readonly IEasyCachingProvider _cache; - private readonly IReadingImageTaskService _readingImageTaskService; - private readonly IOptionsMonitor _verifyConfig; + private readonly IReadingImageTaskService _readingImageTaskService; + private readonly IOptionsMonitor _verifyConfig; public UserService(IRepository userRepository, @@ -41,20 +44,20 @@ namespace IRaCIS.Application.Services IRepository verificationCodeRepository, IRepository doctorRepository, IEasyCachingProvider cache, - IReadingImageTaskService readingImageTaskService, - IRepository userTrialRepository, + IReadingImageTaskService readingImageTaskService, + IRepository userTrialRepository, IOptionsMonitor verifyConfig, IRepository userLogRepository, - IRepository userPassWordLogRepository + IRepository userPassWordLogRepository , IDistributedLockProvider distributedLockProvider) { _userLogRepository = userLogRepository; - this._userPassWordLogRepository = userPassWordLogRepository; - _verifyConfig = verifyConfig; + this._userPassWordLogRepository = userPassWordLogRepository; + _verifyConfig = verifyConfig; _cache = cache; - this._readingImageTaskService = readingImageTaskService; - _userRepository = userRepository; + this._readingImageTaskService = readingImageTaskService; + _userRepository = userRepository; _mailVerificationService = mailVerificationService; _verificationCodeRepository = verificationCodeRepository; _doctorRepository = doctorRepository; @@ -95,45 +98,45 @@ namespace IRaCIS.Application.Services private async Task VerifyUserPwdAsync(Guid userId, string newPwd, string? oldPwd = null) { - //var dbUser = (await _userRepository.FirstOrDefaultAsync(t => t.Id == userId)).IfNullThrowException(); + //var dbUser = (await _userRepository.FirstOrDefaultAsync(t => t.Id == userId)).IfNullThrowException(); - if (oldPwd != null && oldPwd == newPwd) - { - //---新密码与旧密码相同。 - throw new BusinessValidationFailedException(_localizer["User_NewOldPwdSame"]); - } + if (oldPwd != null && oldPwd == newPwd) + { + //---新密码与旧密码相同。 + throw new BusinessValidationFailedException(_localizer["User_NewOldPwdSame"]); + } - var dbUser = (await _userRepository.Where(t => t.Id == userId).FirstOrDefaultAsync()).IfNullThrowException(); + var dbUser = (await _userRepository.Where(t => t.Id == userId).FirstOrDefaultAsync()).IfNullThrowException(); - if (oldPwd != null && dbUser.Password != oldPwd) - { - //---旧密码验证失败。 - throw new BusinessValidationFailedException(_localizer["User_OldPwdInvalid"]); - } + if (oldPwd != null && dbUser.Password != oldPwd) + { + //---旧密码验证失败。 + throw new BusinessValidationFailedException(_localizer["User_OldPwdInvalid"]); + } - if (dbUser.Password == newPwd) - { - //---新密码与旧密码相同。 - throw new BusinessValidationFailedException(_localizer["User_NewOldPwdSame"]); - } + if (dbUser.Password == newPwd) + { + //---新密码与旧密码相同。 + throw new BusinessValidationFailedException(_localizer["User_NewOldPwdSame"]); + } var passWordList = await _userPassWordLogRepository.Where(x => x.UserId == userId).OrderByDescending(x => x.CreateTime).Take(2).ToListAsync(); if (passWordList.Any(x => x.PassWord == newPwd)) { - throw new BusinessValidationFailedException(_localizer["User_PassWordRepeat"]); - } + throw new BusinessValidationFailedException(_localizer["User_PassWordRepeat"]); + } if (oldPwd != null) { - await _userPassWordLogRepository.AddAsync(new UserPassWordLog() - { + await _userPassWordLogRepository.AddAsync(new UserPassWordLog() + { + + CreateTime = DateTime.Now, + PassWord = oldPwd, + UserId = userId, + }); + } - CreateTime = DateTime.Now, - PassWord = oldPwd, - UserId = userId, - }); - } - await _userRepository.BatchUpdateNoTrackingAsync(x => x.Id == userId, x => new User() { @@ -142,7 +145,7 @@ namespace IRaCIS.Application.Services await _userPassWordLogRepository.SaveChangesAsync(); - await Task.CompletedTask; + await Task.CompletedTask; } @@ -306,7 +309,7 @@ namespace IRaCIS.Application.Services { await _mailVerificationService.AdminResetPwdSendEmailAsync(userId, pwd); } - catch (Exception ) + catch (Exception) { //---请检查邮箱地址或者联系维护人员, 邮件发送失败, 未能创建账户成功 throw new BusinessValidationFailedException(_localizer["User_CreateFailed"]); @@ -319,7 +322,7 @@ namespace IRaCIS.Application.Services IsFirstAdd = true }); - await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, LoginUserId = _userInfo.Id, OptUserId=userId, OptType = UserOptType.ResetPassword }, true); + await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, LoginUserId = _userInfo.Id, OptUserId = userId, OptType = UserOptType.ResetPassword }, true); return ResponseOutput.Ok(); } @@ -403,7 +406,7 @@ namespace IRaCIS.Application.Services } } - var list = await _userRepository.Where(t => t.EMail == email && t.Status== UserStateEnum.Enable).Select(t => new UserAccountDto() { UserId = t.Id, UserName = t.UserName, UserRealName = t.FullName, UserType = t.UserTypeRole.UserTypeShortName }).ToListAsync(); + var list = await _userRepository.Where(t => t.EMail == email && t.Status == UserStateEnum.Enable).Select(t => new UserAccountDto() { UserId = t.Id, UserName = t.UserName, UserRealName = t.FullName, UserType = t.UserTypeRole.UserTypeShortName }).ToListAsync(); @@ -431,7 +434,7 @@ namespace IRaCIS.Application.Services IsFirstAdd = false }); - await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, LoginUserId = userId, OptUserId = userId,LoginPassword=newPwd, OptType = UserOptType.UnloginModifyPasswoed }, true); + await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, LoginUserId = userId, OptUserId = userId, LoginPassword = newPwd, OptType = UserOptType.UnloginModifyPasswoed }, true); return ResponseOutput.Result(success); @@ -467,7 +470,7 @@ namespace IRaCIS.Application.Services IsFirstAdd = false }); - await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, LoginUserId = _userInfo.Id, OptUserId= _userInfo.Id, OptType = UserOptType.LoginModifyPassword }, true); + await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, LoginUserId = _userInfo.Id, OptUserId = _userInfo.Id, OptType = UserOptType.LoginModifyPassword }, true); return ResponseOutput.Result(success); @@ -555,11 +558,11 @@ namespace IRaCIS.Application.Services var success = await _userRepository.SaveChangesAsync(); } - + await _mailVerificationService.AddUserSendEmailAsync(saveItem.Id, userAddModel.BaseUrl, userAddModel.RouteUrl); - return ResponseOutput.Ok( new UserAddedReturnDTO { Id = saveItem.Id, UserCode = saveItem.UserCode }); + return ResponseOutput.Ok(new UserAddedReturnDTO { Id = saveItem.Id, UserCode = saveItem.UserCode }); } @@ -589,7 +592,7 @@ namespace IRaCIS.Application.Services user.OrganizationName = AppSettings.DefaultInternalOrganizationName; } - await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, LoginUserId = _userInfo.Id, OptUserId= model.Id , OptType = UserOptType.UpdateUser }, true); + await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, LoginUserId = _userInfo.Id, OptUserId = model.Id, OptType = UserOptType.UpdateUser }, true); var success = await _userRepository.SaveChangesAsync(); @@ -611,7 +614,7 @@ namespace IRaCIS.Application.Services return ResponseOutput.NotOk(_localizer["User_InProject"]); } - await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, LoginUserId = _userInfo.Id, OptUserId= userId, OptType = UserOptType.DeleteUser }, true); + await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, LoginUserId = _userInfo.Id, OptUserId = userId, OptType = UserOptType.DeleteUser }, true); var success = await _userRepository.BatchDeleteNoTrackingAsync(t => t.Id == userId); @@ -629,7 +632,7 @@ namespace IRaCIS.Application.Services public async Task UpdateUserState(Guid userId, UserStateEnum state) { - await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, LoginUserId = _userInfo.Id, OptUserId = userId, OptType = state==UserStateEnum.Enable? UserOptType.AccountEnable: UserOptType.AccountLocked }, true); + await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, LoginUserId = _userInfo.Id, OptUserId = userId, OptType = state == UserStateEnum.Enable ? UserOptType.AccountEnable : UserOptType.AccountLocked }, true); var success = await _userRepository.BatchUpdateNoTrackingAsync(u => u.Id == userId, t => new User { @@ -639,7 +642,70 @@ namespace IRaCIS.Application.Services } + public async Task GetUserBasicInfo(Guid userId, string pwd) + { + var info = await _userRepository.Where(u => u.Id == userId && u.Password==pwd).ProjectTo(_mapper.ConfigurationProvider).FirstNotNullAsync(); + return info; + } + + /// + /// 发送MFA 验证邮件 + /// + /// + /// + [AllowAnonymous] + public async Task SendMFAEmail(Guid userId) + { + var userInfo = await _userRepository.Where(u => u.Id == userId).Select(t => new { t.FullName, t.EMail }).FirstOrDefaultAsync(); + + int verificationCode = new Random().Next(100000, 1000000); + + await _mailVerificationService.SenMFAVerifyEmail(userId, userInfo.FullName, userInfo.EMail, verificationCode); + + return ResponseOutput.Ok(); + } + + /// + /// 验证MFA 邮件 + /// + /// + /// + /// + /// + [AllowAnonymous] + public async Task VerifyMFACodeAsync(Guid userId, string Code) + { + var verificationRecord = await _repository.GetQueryable().OrderByDescending(x => x.ExpirationTime).Where(t => t.UserId == userId && t.Code == Code && t.CodeType == VerifyType.Email).FirstOrDefaultAsync(); + VerifyEmialGetDoctorInfoOutDto result = new VerifyEmialGetDoctorInfoOutDto(); + + //检查数据库是否存在该验证码 + if (verificationRecord == null) + { + await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, LoginUserId = userId, OptUserId = userId, OptType = UserOptType.MFALoginFail }, true); + //---验证码错误。 + throw new BusinessValidationFailedException(_localizer["TrialSiteSurvey_WrongVerificationCode"]); + } + else + { + //检查验证码是否失效 + if (verificationRecord.ExpirationTime < DateTime.Now) + { + await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, LoginUserId = userId, OptUserId = userId, OptType = UserOptType.MFALoginFail }, true); + //---验证码已经过期。 + throw new BusinessValidationFailedException(_localizer["TrialSiteSurvey_ExpiredVerificationCode"]); + + + } + else //验证码正确 并且 没有超时 + { + await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, LoginUserId = userId, OptUserId = userId, OptType = UserOptType.MFALogin }, true); + + } + } + + return ResponseOutput.Ok(); + } /// /// 用户登陆 @@ -690,14 +756,12 @@ namespace IRaCIS.Application.Services failCount++; _cache.Set(cacheKey, failCount, TimeSpan.FromMinutes(lockoutMinutes)); - var errorPwdUserId = await _userRepository.Where(u => u.UserName==userName).Select(t=>t.Id).FirstOrDefaultAsync(); + var errorPwdUserId = await _userRepository.Where(u => u.UserName == userName).Select(t => t.Id).FirstOrDefaultAsync(); await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, LoginUserId = errorPwdUserId, OptUserId = errorPwdUserId, LoginFaildName = userName, LoginPassword = password, OptType = UserOptType.AccountOrPasswordError }, true); return ResponseOutput.NotOk(_localizer["User_CheckNameOrPw"], new LoginReturnDTO()); - - } if (loginUser.Status == 0) @@ -712,26 +776,26 @@ namespace IRaCIS.Application.Services if (loginUser.LastChangePassWordTime != null && DateTime.Now.AddDays(-90) > loginUser.LastChangePassWordTime.Value) { loginUser.LoginState = 1; - } + } //登录成功 清除缓存 _cache.Set(cacheKey, 0, TimeSpan.FromMinutes(lockoutMinutes)); - - if (loginUser.LastLoginIP != string.Empty) + + if (loginUser.LastLoginIP != string.Empty) { // 与上一次IP不一致 if (loginUser.LastLoginIP != _userInfo.IP) { - loginUser.LoginState = 2; - } - + loginUser.LoginState = 2; + } - } - await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, LoginUserId = loginUser.Id, OptUserId = loginUser.Id, OptType = UserOptType.Login }, true); + } + + await _userLogRepository.AddAsync(new UserLog() { IP = _userInfo.IP, LoginUserId = loginUser.Id, OptUserId = loginUser.Id, OptType = UserOptType.Login }, true); userLoginReturnModel.BasicInfo = loginUser; @@ -743,21 +807,13 @@ namespace IRaCIS.Application.Services }); - } + } - await _userRepository.BatchUpdateNoTrackingAsync(x => x.Id == loginUser.Id, x => new User() - { - LastLoginIP = _userInfo.IP - - }); - - - // 登录 清除缓存 - //_cache.Remove(userLoginReturnModel.BasicInfo.Id.ToString()); - - var userId = loginUser.Id; - await _cache.SetAsync($"{userId.ToString()}_Online", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), TimeSpan.FromMinutes(_verifyConfig.CurrentValue.AutoLoginOutMinutes)); + await _userRepository.BatchUpdateNoTrackingAsync(x => x.Id == loginUser.Id, x => new User() + { + LastLoginIP = _userInfo.IP + }); return ResponseOutput.Ok(userLoginReturnModel); @@ -766,12 +822,12 @@ namespace IRaCIS.Application.Services [HttpPost] public async Task> GetUserLogList(UserLogQuery inQuery) { - DateTime? trialCreateTime = inQuery.TrialId != null ?_repository.Where(t=>t.Id==inQuery.TrialId).Select(t=>t.CreateTime).FirstOrDefault() : null; + DateTime? trialCreateTime = inQuery.TrialId != null ? _repository.Where(t => t.Id == inQuery.TrialId).Select(t => t.CreateTime).FirstOrDefault() : null; var userLogQueryable = _userLogRepository .WhereIf(inQuery.TrialId != null, t => t.LoginUser.UserTrials.Any(c => c.TrialId == inQuery.TrialId && (c.UserId == t.LoginUserId || c.UserId == t.OptUserId))) - .WhereIf(trialCreateTime != null, t => t.CreateTime>= trialCreateTime) + .WhereIf(trialCreateTime != null, t => t.CreateTime >= trialCreateTime) .WhereIf(inQuery.OptType != null, t => t.OptType == inQuery.OptType) .WhereIf(inQuery.UserId != null, t => t.LoginUserId == inQuery.UserId) .WhereIf(inQuery.BeginDate != null, t => t.CreateTime >= inQuery.BeginDate) diff --git a/IRaCIS.Core.Application/Service/TrialSiteUser/DTO/TrialConfigDTO.cs b/IRaCIS.Core.Application/Service/TrialSiteUser/DTO/TrialConfigDTO.cs index 9236b19a1..8232ce32c 100644 --- a/IRaCIS.Core.Application/Service/TrialSiteUser/DTO/TrialConfigDTO.cs +++ b/IRaCIS.Core.Application/Service/TrialSiteUser/DTO/TrialConfigDTO.cs @@ -344,6 +344,8 @@ namespace IRaCIS.Core.Application.Contracts { public string TrialModalitys { get; set;} + public bool IsImageFilter { get; set; } + /// /// 项目ID @@ -506,6 +508,7 @@ namespace IRaCIS.Core.Application.Contracts public ReadingImageUpload? ImageUploadEnum { get; set; } + public Guid TrialReadingCriterionId { get; set; } } @@ -864,6 +867,7 @@ namespace IRaCIS.Core.Application.Contracts public class SetCriterionReadingInfoInDto { + public bool IsImageFilter { get; set; } public string CriterionModalitys { get; set; } public ReadingImageDownload? ImageDownloadEnum { get; set; } diff --git a/IRaCIS.Core.Application/Service/TrialSiteUser/TrialConfigService.cs b/IRaCIS.Core.Application/Service/TrialSiteUser/TrialConfigService.cs index dabf10d1e..ef9c33a6d 100644 --- a/IRaCIS.Core.Application/Service/TrialSiteUser/TrialConfigService.cs +++ b/IRaCIS.Core.Application/Service/TrialSiteUser/TrialConfigService.cs @@ -317,12 +317,12 @@ namespace IRaCIS.Core.Application #endregion - var trialModalitys = _readingQuestionCriterionTrialRepository.Where(t => t.Id == inDto.TrialReadingCriterionId).Select(t => t.Trial.Modalitys).FirstOrDefault(); + //var trialModalitys = _readingQuestionCriterionTrialRepository.Where(t => t.Id == inDto.TrialReadingCriterionId).Select(t => t.Trial.Modalitys).FirstOrDefault(); var systemCriterion = await _readingQuestionCriterionSystemRepository.Where(x => x.Id == trialCriterion.ReadingQuestionCriterionSystemId).FirstNotNullAsync(); await _readingQuestionCriterionTrialRepository.BatchUpdateNoTrackingAsync(x => x.Id == inDto.TrialReadingCriterionId, x => new ReadingQuestionCriterionTrial() { - CriterionModalitys= trialModalitys, + //CriterionModalitys= trialModalitys, IsOncologyReading = systemCriterion.IsOncologyReading, IsSystemSetOncology = systemCriterion.IsOncologyReading, IseCRFShowInDicomReading = systemCriterion.IseCRFShowInDicomReading, @@ -604,6 +604,7 @@ namespace IRaCIS.Core.Application await _readingQuestionCriterionTrialRepository.UpdatePartialFromQueryAsync(inDto.TrialReadingCriterionId, x => new ReadingQuestionCriterionTrial() { + IsImageFilter=inDto.IsImageFilter, ImageDownloadEnum = inDto.ImageDownloadEnum, ImageUploadEnum = inDto.ImageUploadEnum, CriterionModalitys = inDto.CriterionModalitys, @@ -953,7 +954,7 @@ namespace IRaCIS.Core.Application trialInfo.UpdateTime = DateTime.Now; - await _readingQuestionCriterionTrialRepository.BatchUpdateNoTrackingAsync(t => t.TrialId == trialConfig.TrialId && t.IsSigned == false, u => new ReadingQuestionCriterionTrial() { CriterionModalitys = trialConfig.Modalitys }); + //await _readingQuestionCriterionTrialRepository.BatchUpdateNoTrackingAsync(t => t.TrialId == trialConfig.TrialId && t.IsSigned == false, u => new ReadingQuestionCriterionTrial() { CriterionModalitys = trialConfig.Modalitys }); return ResponseOutput.Ok(await _repository.SaveChangesAsync()); } @@ -1039,7 +1040,8 @@ namespace IRaCIS.Core.Application if (trialConfig.TrialCriterionIds.Contains(item.Id)) { item.IsConfirm = true; - item.CriterionModalitys = trialInfo.Modalitys; + + //item.CriterionModalitys = trialInfo.Modalitys; } else { diff --git a/IRaCIS.Core.Application/Service/TrialSiteUser/_MapConfig.cs b/IRaCIS.Core.Application/Service/TrialSiteUser/_MapConfig.cs index e0a46a7f5..5546e2036 100644 --- a/IRaCIS.Core.Application/Service/TrialSiteUser/_MapConfig.cs +++ b/IRaCIS.Core.Application/Service/TrialSiteUser/_MapConfig.cs @@ -249,7 +249,8 @@ namespace IRaCIS.Core.Application.Service CreateMap() - .ForMember(t => t.TrialModalitys, u => u.MapFrom(c => c.Trial.Modalitys)); + .ForMember(t => t.TrialModalitys, u => u.MapFrom(c => c.Trial.Modalitys)) + .ForMember(t => t.TrialReadingCriterionId, u => u.MapFrom(c => c.Id)); CreateMap() diff --git a/IRaCIS.Core.Domain/Management/UserLog.cs b/IRaCIS.Core.Domain/Management/UserLog.cs index 631eb51d2..9ed33bfb2 100644 --- a/IRaCIS.Core.Domain/Management/UserLog.cs +++ b/IRaCIS.Core.Domain/Management/UserLog.cs @@ -93,7 +93,11 @@ namespace IRaCIS.Core.Domain.Models DeleteUser=10, - UpdateUser=11 + UpdateUser=11, + + MFALogin=12, + + MFALoginFail=13, } } diff --git a/IRaCIS.Core.Domain/Reading/ReadingCriterion/ReadingQuestionCriterionTrial.cs b/IRaCIS.Core.Domain/Reading/ReadingCriterion/ReadingQuestionCriterionTrial.cs index 0f69ee9c4..c27d34b15 100644 --- a/IRaCIS.Core.Domain/Reading/ReadingCriterion/ReadingQuestionCriterionTrial.cs +++ b/IRaCIS.Core.Domain/Reading/ReadingCriterion/ReadingQuestionCriterionTrial.cs @@ -279,6 +279,8 @@ namespace IRaCIS.Core.Domain.Models public ReadingImageDownload? ImageDownloadEnum { get; set; } public ReadingImageUpload? ImageUploadEnum { get; set; } + + public bool IsImageFilter { get; set; } } public enum ReadingImageDownload diff --git a/IRaCIS.Core.Domain/_Config/_AppSettings.cs b/IRaCIS.Core.Domain/_Config/_AppSettings.cs index 0b7018505..679d6d101 100644 --- a/IRaCIS.Core.Domain/_Config/_AppSettings.cs +++ b/IRaCIS.Core.Domain/_Config/_AppSettings.cs @@ -22,6 +22,8 @@ namespace IRaCIS.Core.Domain.Share public int LoginFailLockMinutes { get; set; } public int AutoLoginOutMinutes { get; set; } + + public bool OpenLoginMFA { get; set; } } public class SystemEmailSendConfig