diff --git a/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs b/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs index c6415aea9..d5fbdd663 100644 --- a/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs +++ b/IRaCIS.Core.API/Controllers/UploadDownLoadController.cs @@ -513,7 +513,11 @@ namespace IRaCIS.Core.API.Controllers [Authorize(Policy = IRaCISPolicy.PM_APM)] public async Task UploadVisitCheckExcel(Guid trialId ) { - + if (_repository.Where(t => t.TrialId == trialId && t.IsEnable).Count() <2) + { + return ResponseOutput.NotOk("能参与读片的医生数量必须大于2"); + } + var (serverFilePath, relativePath, fileName) = (string.Empty, string.Empty, string.Empty); await FileUploadAsync(async (realFileName) => diff --git a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml index 3529b86ad..09e99d02f 100644 --- a/IRaCIS.Core.Application/IRaCIS.Core.Application.xml +++ b/IRaCIS.Core.Application/IRaCIS.Core.Application.xml @@ -2492,12 +2492,12 @@ - + 处理 访视 末次评估 会影响Subject 状态 - + 处理 访视 末次评估 会影响Subject 状态 diff --git a/IRaCIS.Core.Application/Service/Allocation/DTO/TaskAllocationRuleViewModel.cs b/IRaCIS.Core.Application/Service/Allocation/DTO/TaskAllocationRuleViewModel.cs index ee1cbdaa5..806207274 100644 --- a/IRaCIS.Core.Application/Service/Allocation/DTO/TaskAllocationRuleViewModel.cs +++ b/IRaCIS.Core.Application/Service/Allocation/DTO/TaskAllocationRuleViewModel.cs @@ -29,8 +29,31 @@ namespace IRaCIS.Core.Application.ViewModel public int? SelfTaskCount { get; set; } + + public List ArmList { get; set; } + } + public class AllocateInfo + { + public int? TotalTaskCount { get; set; } + + public int? SelfTaskCount { get; set; } + + public int PlanReadingRatio { get; set; } + + public Guid DoctorUserId { get; set; } + + public List ArmList { get; set; } + + //public double? TargetCount => TotalTaskCount * PlanReadingRatio * 0.01; + + //public double? Diff => TargetCount - SelfTaskCount; + + + } + + ///TaskAllocationRuleQuery 列表查询参数模型 public class TaskAllocationRuleQuery { diff --git a/IRaCIS.Core.Application/Service/Allocation/_MapConfig.cs b/IRaCIS.Core.Application/Service/Allocation/_MapConfig.cs index b35b35e45..088df5811 100644 --- a/IRaCIS.Core.Application/Service/Allocation/_MapConfig.cs +++ b/IRaCIS.Core.Application/Service/Allocation/_MapConfig.cs @@ -16,10 +16,19 @@ namespace IRaCIS.Core.Application.Service .ForMember(o => o.UserName, t => t.MapFrom(u => u.DoctorUser.UserName)) .ForMember(o => o.FullName, t => t.MapFrom(u => u.DoctorUser.FullName)) .ForMember(o => o.UserTypeShortName, t => t.MapFrom(u => u.DoctorUser.UserTypeRole.UserTypeShortName)) + .ForMember(o => o.ArmList, t => t.MapFrom(u => u.VisitTaskList.Select(t=>t.ArmEnum).Distinct())) .ForMember(o => o.TotalTaskCount, t => t.MapFrom(u => u.Trial.VisitTaskList.Count())) .ForMember(o => o.SelfTaskCount, t => t.MapFrom(u => u.Trial.VisitTaskList.Count(t=>t.DoctorUserId==u.DoctorUserId))); - + CreateMap() + .ForMember(o => o.ArmList, t => t.MapFrom(u => u.VisitTaskList.Select(t => t.ArmEnum).Distinct())) + .ForMember(o => o.TotalTaskCount, t => t.MapFrom(u => u.Trial.VisitTaskList.Count())) + + .ForMember(o => o.SelfTaskCount, t => t.MapFrom(u => u.Trial.VisitTaskList.Count(t => t.DoctorUserId == u.DoctorUserId))); + + + + CreateMap() .ForMember(o => o.SiteId, t => t.MapFrom(u => u.Subject.SiteId)) .ForMember(o => o.TrialSiteCode, t => t.MapFrom(u => u.Subject.TrialSite.TrialSiteCode)) diff --git a/IRaCIS.Core.Application/Service/QC/DTO/PreviousHistoryViewModel.cs b/IRaCIS.Core.Application/Service/QC/DTO/PreviousHistoryViewModel.cs index 7fdf9fe66..de1af6bb8 100644 --- a/IRaCIS.Core.Application/Service/QC/DTO/PreviousHistoryViewModel.cs +++ b/IRaCIS.Core.Application/Service/QC/DTO/PreviousHistoryViewModel.cs @@ -66,10 +66,10 @@ namespace IRaCIS.Core.Application.Contracts public string Path { get; set; } = String.Empty; public string FileName { get; set; } = String.Empty; public string Position { get; set; } = String.Empty; - //public Guid SubjectId { get; set; } + public Guid CreateUserId { get; set; } - public string FullFilePath { get; set; } = String.Empty; + public string FullFilePath => Path; } diff --git a/IRaCIS.Core.Application/Service/QC/DTO/PreviousOtherViewModel.cs b/IRaCIS.Core.Application/Service/QC/DTO/PreviousOtherViewModel.cs index 392b53d18..a57697c3c 100644 --- a/IRaCIS.Core.Application/Service/QC/DTO/PreviousOtherViewModel.cs +++ b/IRaCIS.Core.Application/Service/QC/DTO/PreviousOtherViewModel.cs @@ -24,7 +24,7 @@ namespace IRaCIS.Core.Application.Contracts //public Guid SubjectId { get; set; } public Guid CreateUserId { get; set; } - public string FullFilePath { get; set; } = String.Empty; + public string FullFilePath => Path; } ///PreviousOtherQuery 列表查询参数模型 diff --git a/IRaCIS.Core.Application/Service/QC/DTO/PreviousSurgeryViewModel.cs b/IRaCIS.Core.Application/Service/QC/DTO/PreviousSurgeryViewModel.cs index 6d77fd230..ed7d71f2a 100644 --- a/IRaCIS.Core.Application/Service/QC/DTO/PreviousSurgeryViewModel.cs +++ b/IRaCIS.Core.Application/Service/QC/DTO/PreviousSurgeryViewModel.cs @@ -22,7 +22,7 @@ namespace IRaCIS.Core.Application.Contracts //public Guid SubjectId { get; set; } public Guid CreateUserId { get; set; } - public string FullFilePath { get; set; } = string.Empty; + public string FullFilePath => Path; } ///PreviousSurgeryQuery 列表查询参数模型 @@ -55,7 +55,7 @@ namespace IRaCIS.Core.Application.Contracts public string FileName { get; set; } = string.Empty; public Guid CreateUserId { get; set; } - public string FullFilePath { get; set; } = string.Empty; + public string FullFilePath => Path; /// diff --git a/IRaCIS.Core.Application/Triggers/SubjectVisitCheckPassedTrigger.cs b/IRaCIS.Core.Application/Triggers/SubjectVisitCheckPassedTrigger.cs new file mode 100644 index 000000000..3d7163ed0 --- /dev/null +++ b/IRaCIS.Core.Application/Triggers/SubjectVisitCheckPassedTrigger.cs @@ -0,0 +1,186 @@ +using AutoMapper; +using EasyCaching.Core; +using EntityFrameworkCore.Triggered; +using IRaCIS.Core.Application.ViewModel; +using IRaCIS.Core.Domain.Share; +using IRaCIS.Core.Infrastructure; + +namespace IRaCIS.Core.Application.Triggers +{ + /// + /// 处理 访视 末次评估 会影响Subject 状态 + /// + public class SubjectVisitCheckPassedTrigger : IAfterSaveTrigger + { + private readonly IRepository _subjectVisitRepository; + private readonly IRepository _visitTaskRepository; + + private readonly IRepository _trialRepository; + private readonly IRepository _taskAllocationRuleRepository; + + private readonly IEasyCachingProvider _provider; + + private readonly IMapper _mapper; + + public SubjectVisitCheckPassedTrigger(IRepository subjectVisitRepository, IRepository visitTaskRepository, + IRepository trialRepository, + IRepository taskAllocationRuleRepository, + IEasyCachingProvider provider, + IMapper mapper) + { + _subjectVisitRepository = subjectVisitRepository; + _visitTaskRepository = visitTaskRepository; + _provider = provider; + _mapper = mapper; + _taskAllocationRuleRepository = taskAllocationRuleRepository; + _trialRepository = trialRepository; + } + + public async Task AfterSave(ITriggerContext context, CancellationToken cancellationToken) + { + + var subjectVisit = context.Entity; + + + if (context.ChangeType == ChangeType.Modified) + { + + // 一致性核查通过 生成读片任务 + if (context.UnmodifiedEntity?.CheckState != subjectVisit.CheckState && subjectVisit.CheckState == CheckStateEnum.CVPassed) + { + + var dbMaxCode = _visitTaskRepository.Where(t => t.TrialId == subjectVisit.TrialId).Select(t => t.Code).DefaultIfEmpty().Max(); + + var cacheMaxCodeInt = _provider.Get($"{subjectVisit.TrialId }_{ StaticData.CacheKey.TaskMaxCode}").Value; + + int currentMaxCodeInt = cacheMaxCodeInt > dbMaxCode ? cacheMaxCodeInt : dbMaxCode; + + _provider.Set($"{subjectVisit.TrialId }_{ StaticData.CacheKey.TaskMaxCode}", currentMaxCodeInt + 2, TimeSpan.FromMinutes(30)); + + var trialConfig = (await _trialRepository.Where(t => t.Id == subjectVisit.TrialId).Select(t => new { TrialId = t.Id, t.ReadingType, t.TaskAllocateDefaultState, t.TaskAllocateObjEnum }).FirstOrDefaultAsync()).IfNullThrowException(); + + //统计当前医生分配信息 有效医生数量必须大于2 已做验证 + var allocateStat = _taskAllocationRuleRepository.Where(t => t.TrialId == subjectVisit.TrialId && t.IsEnable).ProjectTo(_mapper.ConfigurationProvider).ToList(); + + if (trialConfig.ReadingType == ReadingMethod.Double) + { + + //每个访视 根据项目配置生成任务 双审生成两个 + var task1 = await _visitTaskRepository.AddAsync(new VisitTask() + { + TrialId = subjectVisit.TrialId, + SubjectId = subjectVisit.SubjectId, + IsUrgent = subjectVisit.IsUrgent, + TaskBlindName = subjectVisit.BlindName, + TaskName = subjectVisit.VisitName, + CheckPassedTime = subjectVisit.CheckPassedTime, + ArmEnum = 1,//特殊 + Code = currentMaxCodeInt + 1, + TaskCode = AppSettings.GetCodeStr(currentMaxCodeInt + 1, nameof(VisitTask)), + ReadingCategory = ReadingCategory.Visit + }); + + var task2 = await _visitTaskRepository.AddAsync(new VisitTask() + { + TrialId = subjectVisit.TrialId, + SubjectId = subjectVisit.SubjectId, + IsUrgent = subjectVisit.IsUrgent, + TaskBlindName = subjectVisit.BlindName, + TaskName = subjectVisit.VisitName, + CheckPassedTime = subjectVisit.CheckPassedTime, + ArmEnum = 2,//特殊 + Code = currentMaxCodeInt + 2, + TaskCode = AppSettings.GetCodeStr(currentMaxCodeInt + 2, nameof(VisitTask)), + ReadingCategory = ReadingCategory.Visit + }); + + + if (trialConfig.TaskAllocateObjEnum == TaskAllocateObj.Subject) + { + + //该Subject 之前是否有已分配的 如果改变配置 可能会出现 一个Subject 分配的同一个医生 有的在Arm1 有的在Arm2 + var allocateSubjectArmList = _visitTaskRepository.Where(t => t.SubjectId == subjectVisit.SubjectId && t.TrialId == subjectVisit.TrialId && t.DoctorUserId != null).Select(t => new { t.DoctorUserId, t.ArmEnum }).Distinct().ToList(); + + //初次自动分配 + if (allocateSubjectArmList.Count == 0) + { + //排除已经入选其他Arm的阅片人 + + //是否有做过Arm1的医生 + if (allocateStat.Any(t => t.ArmList.Any(t => t == 1))) + { + //找到最优的需要分配任务的医生 + task1.DoctorUserId = allocateStat.Where(t => t.ArmList.Any(t => t == 1)).OrderByDescending(t => t.TotalTaskCount * t.PlanReadingRatio * 0.01 - t.TotalTaskCount).FirstOrDefault().DoctorUserId; + + } + else + { + task1.DoctorUserId = allocateStat.OrderByDescending(t => t.TotalTaskCount * t.PlanReadingRatio * 0.01 - t.TotalTaskCount).FirstOrDefault().DoctorUserId; + } + + //是否有做过Arm2的医生 + if (allocateStat.Any(t => t.ArmList.Any(t => t == 2))) + { + //找到最优的需要分配任务的医生 + task2.DoctorUserId = allocateStat.Where(t => t.ArmList.Any(t => t == 2)).OrderByDescending(t => t.TotalTaskCount * t.PlanReadingRatio * 0.01 - t.TotalTaskCount).FirstOrDefault().DoctorUserId; + } + else + { + task2.DoctorUserId = allocateStat.OrderByDescending(t => t.TotalTaskCount * t.PlanReadingRatio * 0.01 - t.TotalTaskCount).Skip(1).FirstOrDefault().DoctorUserId; + } + + } + else + { + if (allocateSubjectArmList.GroupBy(t => t.DoctorUserId).Any(g => g.Count() == 2)) + { + throw new BusinessValidationFailedException("请确认是否改了配置,导致同一受试者 分配给同一个医生 在不同的Arm,无法完成自动分配"); + } + + //手动分配的时候 如果只分配了Arm1 没有分配Arm2 就会有问题 + if (allocateSubjectArmList.Any(t => t.ArmEnum == 1) && allocateSubjectArmList.Any(t => t.ArmEnum == 2)) + { + throw new BusinessValidationFailedException("请确认是否改了配置,或者手动分配时,只分配了一个Arm "); + } + + //分配给对应Arm的人 + task1.DoctorUserId = allocateSubjectArmList.FirstOrDefault(t => t.ArmEnum == 1).DoctorUserId; + task2.DoctorUserId = allocateSubjectArmList.FirstOrDefault(t => t.ArmEnum == 2).DoctorUserId; + } + + + } + + } + else if (trialConfig.ReadingType == ReadingMethod.Single) + { + await _visitTaskRepository.AddAsync(new VisitTask() + { + TrialId = subjectVisit.TrialId, + SubjectId = subjectVisit.SubjectId, + IsUrgent = subjectVisit.IsUrgent, + TaskBlindName = subjectVisit.BlindName, + TaskName = subjectVisit.VisitName, + CheckPassedTime = subjectVisit.CheckPassedTime, + ArmEnum = 0, //特殊 + Code = currentMaxCodeInt + 1, + TaskCode = AppSettings.GetCodeStr(currentMaxCodeInt + 1, nameof(VisitTask)), + ReadingCategory = ReadingCategory.Visit + }); + + } + + + await _visitTaskRepository.SaveChangesAsync(); + } + + } + + } + + + + + + } +} \ No newline at end of file diff --git a/IRaCIS.Core.Application/Triggers/SubjectVisitPassedTrigger.cs b/IRaCIS.Core.Application/Triggers/SubjectVisitPassedTrigger.cs deleted file mode 100644 index b96a3b41d..000000000 --- a/IRaCIS.Core.Application/Triggers/SubjectVisitPassedTrigger.cs +++ /dev/null @@ -1,87 +0,0 @@ -using EasyCaching.Core; -using EntityFrameworkCore.Triggered; -using IRaCIS.Core.Domain.Share; -using IRaCIS.Core.Infrastructure; - -namespace IRaCIS.Core.Application.Triggers -{ - /// - /// 处理 访视 末次评估 会影响Subject 状态 - /// - public class SubjectVisitPassedTrigger : IAfterSaveTrigger - { - private readonly IRepository _subjectVisitRepository; - private readonly IRepository _visitTaskRepository; - - private readonly IEasyCachingProvider _provider; - - public SubjectVisitPassedTrigger(IRepository subjectVisitRepository, IRepository visitTaskRepository, IEasyCachingProvider provider) - { - _subjectVisitRepository = subjectVisitRepository; - _visitTaskRepository = visitTaskRepository; - _provider = provider; - } - - public async Task AfterSave(ITriggerContext context, CancellationToken cancellationToken) - { - - var subjectVisit = context.Entity; - - - if (context.ChangeType == ChangeType.Modified) - { - - // 一致性核查通过 生成读片任务 - if (context.UnmodifiedEntity?.CheckState != subjectVisit.CheckState && subjectVisit.CheckState == CheckStateEnum.CVPassed) - { - - var dbMaxCode = _visitTaskRepository.Where(t => t.TrialId == subjectVisit.TrialId).Select(t => t.Code).DefaultIfEmpty().Max(); - - var cacheMaxCodeInt = _provider.Get($"{subjectVisit.TrialId }_{ StaticData.CacheKey.TaskMaxCode}").Value; - - int currentMaxCodeInt = cacheMaxCodeInt > dbMaxCode ? cacheMaxCodeInt : dbMaxCode; - - _provider.Set($"{subjectVisit.TrialId }_{ StaticData.CacheKey.TaskMaxCode}", currentMaxCodeInt + 2, TimeSpan.FromMinutes(30)); - - - await _visitTaskRepository.AddAsync(new VisitTask() - { - TrialId = subjectVisit.TrialId, - SubjectId = subjectVisit.SubjectId, - IsUrgent = subjectVisit.IsUrgent, - TaskBlindName = subjectVisit.BlindName, - TaskName = subjectVisit.VisitName, - CheckPassedTime = subjectVisit.CheckPassedTime, - ArmEnum = 1, - Code = currentMaxCodeInt + 1, - TaskCode = AppSettings.GetCodeStr(currentMaxCodeInt + 1, nameof(VisitTask)), - ReadingCategory = ReadingCategory.Visit - }); - - await _visitTaskRepository.AddAsync(new VisitTask() - { - TrialId = subjectVisit.TrialId, - SubjectId = subjectVisit.SubjectId, - IsUrgent = subjectVisit.IsUrgent, - TaskBlindName = subjectVisit.BlindName, - TaskName = subjectVisit.VisitName, - CheckPassedTime = subjectVisit.CheckPassedTime, - ArmEnum = 2, - Code = currentMaxCodeInt + 2, - TaskCode = AppSettings.GetCodeStr(currentMaxCodeInt + 2, nameof(VisitTask)), - ReadingCategory = ReadingCategory.Visit - }); - - await _visitTaskRepository.SaveChangesAsync(); - } - - } - - } - - - - - - } -} \ No newline at end of file diff --git a/IRaCIS.Core.Domain.Share/Allocation/AllocationRelation.cs b/IRaCIS.Core.Domain.Share/Allocation/AllocationRelation.cs index b389d3a25..e9940f651 100644 --- a/IRaCIS.Core.Domain.Share/Allocation/AllocationRelation.cs +++ b/IRaCIS.Core.Domain.Share/Allocation/AllocationRelation.cs @@ -26,7 +26,9 @@ namespace IRaCIS.Core.Domain.Share Allocated = 2, } - + /// + /// 分配对象 + /// public enum TaskAllocateObj { Subject=0, @@ -34,9 +36,10 @@ namespace IRaCIS.Core.Domain.Share SubjectVisit=1 } + //分配默认状态 public enum TaskAllocateDefaultState { - //默认值 看是否需要项目初始化时就给默认值 + //默认值 看是否需要项目初始化时就给默认值 1 或者2 None =0, //预分配 @@ -46,4 +49,13 @@ namespace IRaCIS.Core.Domain.Share Allocated = 2, } + public enum ReadingMethod + { + Single=1, + + Double=2, + + Special=3 + } + } diff --git a/IRaCIS.Core.Domain/Allocation/TaskAllocationRule.cs b/IRaCIS.Core.Domain/Allocation/TaskAllocationRule.cs index e8b386872..c9dcacb63 100644 --- a/IRaCIS.Core.Domain/Allocation/TaskAllocationRule.cs +++ b/IRaCIS.Core.Domain/Allocation/TaskAllocationRule.cs @@ -7,6 +7,8 @@ using System; using IRaCIS.Core.Domain.Share; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Collections.Generic; + namespace IRaCIS.Core.Domain.Models { /// @@ -51,8 +53,11 @@ namespace IRaCIS.Core.Domain.Models [Required] public int PlanReadingRatio { get; set; } + + public Guid DoctorUserId { get; set; } + [ForeignKey("DoctorUserId")] public User DoctorUser { get; set; } @@ -65,6 +70,11 @@ namespace IRaCIS.Core.Domain.Models public string Note { get; set; } = string.Empty; + + + + public List VisitTaskList { get; set; } = new List(); + ///// ///// Arm 组 ///// diff --git a/IRaCIS.Core.Domain/Allocation/VisitTask.cs b/IRaCIS.Core.Domain/Allocation/VisitTask.cs index 02b47455c..aea2b9d9c 100644 --- a/IRaCIS.Core.Domain/Allocation/VisitTask.cs +++ b/IRaCIS.Core.Domain/Allocation/VisitTask.cs @@ -111,9 +111,12 @@ namespace IRaCIS.Core.Domain.Models /// DoctorUserId /// public Guid? DoctorUserId { get; set; } + [ForeignKey("DoctorUserId")] public User DoctorUser { get; set; } + public TaskAllocationRule TaskAllocationRule { get; set; } + } } diff --git a/IRaCIS.Core.Domain/Trial/Trial.cs b/IRaCIS.Core.Domain/Trial/Trial.cs index 2e0d3abfb..8607c2aca 100644 --- a/IRaCIS.Core.Domain/Trial/Trial.cs +++ b/IRaCIS.Core.Domain/Trial/Trial.cs @@ -208,7 +208,7 @@ namespace IRaCIS.Core.Domain.Models public int ReadingMode { get; set; } = 1; //Ƭ - public int ReadingType { get; set; } = 2; + public ReadingMethod ReadingType { get; set; } = ReadingMethod.Double; public bool IsGlobalReading { get; set; } = true; diff --git a/IRaCIS.Core.Infra.EFCore/Context/IRaCISDBContext.cs b/IRaCIS.Core.Infra.EFCore/Context/IRaCISDBContext.cs index f1fec40fc..9c0718c28 100644 --- a/IRaCIS.Core.Infra.EFCore/Context/IRaCISDBContext.cs +++ b/IRaCIS.Core.Infra.EFCore/Context/IRaCISDBContext.cs @@ -88,6 +88,8 @@ namespace IRaCIS.Core.Infra.EFCore //modelBuilder.HasDbFunction(typeof(DbContext).GetMethod(nameof(GetTableList))); + modelBuilder.Entity().HasMany(t => t.VisitTaskList).WithOne(t => t.TaskAllocationRule).HasForeignKey(t=>t.DoctorUserId).HasPrincipalKey(u=>u.DoctorUserId); + modelBuilder.Entity().HasMany(t => t.ChildList).WithOne(t => t.Parent); if (_userInfo.IsEn_Us) {