irc-netcore-api/IRaCIS.Core.Application/Service/TrialSiteUser/TrialService.cs

506 lines
24 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

using IRaCIS.Application.Interfaces;
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Domain.Share;
using EasyCaching.Core;
using IRaCIS.Core.Application.Filter;
using IRaCIS.Core.Infra.EFCore;
using MassTransit;
using Microsoft.AspNetCore.Mvc;
using Panda.DynamicWebApi.Attributes;
using IRaCIS.Core.Infrastructure;
namespace IRaCIS.Application.Services
{
[ApiExplorerSettings(GroupName = "Trial")]
public class TrialService : BaseService, ITrialService
{
private readonly IEasyCachingProvider _provider;
private readonly IRepository<Trial> _trialRepository;
private readonly IRepository<TrialUser> _trialUserRepository;
public bool TrialExpeditedChange { get; set; } = false;
public TrialService(IEasyCachingProvider provider,IRepository<Trial> trialRepository,
IRepository<TrialUser> trialUserRepository
)
{
_provider = provider;
_trialRepository = trialRepository;
this._trialUserRepository = trialUserRepository;
}
/// <summary>
/// 分页获取临床项目列表 默认后台加急状态为3 查所有的
/// </summary>
/// <param name="searchParam"></param>
/// <returns></returns>
[HttpPost]
public async Task<PageOutput<TrialDetailDTO>> GetTrialList(TrialQueryDTO searchParam)
{
var multiModalityIdSelectCount = searchParam.ModalityIds.Count;
var multiCriteriaSelectCount = searchParam.CriterionIds.Count;
var multiReviewTypeSelectCount = searchParam.ReviewTypeIds.Count;
var query = _trialRepository.AsQueryable().IgnoreQueryFilters()
.WhereIf(!string.IsNullOrEmpty(searchParam.TrialStatusStr), o => o.TrialStatusStr.Contains(searchParam.TrialStatusStr))
.WhereIf(searchParam.SponsorId != null, o => o.SponsorId == searchParam.SponsorId)
.WhereIf(searchParam.Expedited != null, o => o.Expedited == searchParam.Expedited)
.WhereIf(!string.IsNullOrEmpty(searchParam.Code), o => o.TrialCode.Contains(searchParam.Code))
.WhereIf(!string.IsNullOrWhiteSpace(searchParam.Indication), o => o.Indication.Contains(searchParam.Indication))
.WhereIf(!string.IsNullOrEmpty(searchParam.ResearchProgramNo), o => o.ResearchProgramNo.Contains(searchParam.ResearchProgramNo))
.WhereIf(!string.IsNullOrWhiteSpace(searchParam.ExperimentName), o => o.ExperimentName.Contains(searchParam.ExperimentName))
.WhereIf(searchParam.PhaseId != null, o => o.PhaseId == searchParam.PhaseId)
.WhereIf(searchParam.DeclarationTypeId != null, o => o.PhaseId == searchParam.DeclarationTypeId)
.WhereIf(searchParam.IndicationTypeId != null, o => o.PhaseId == searchParam.IndicationTypeId)
.WhereIf(searchParam.CROId != null, o => o.CROId == searchParam.CROId)
.WhereIf(searchParam.BeginDate != null, o => o.CreateTime >= searchParam.BeginDate)
.WhereIf(searchParam.EndDate != null, o => o.CreateTime <= searchParam.EndDate)
.WhereIf(searchParam.AttendedReviewerType != null, o => o.AttendedReviewerType == searchParam.AttendedReviewerType)
.WhereIf(multiModalityIdSelectCount > 0, t => t.TrialDicList.Count(t => t.KeyName == StaticData.Modality) == multiModalityIdSelectCount)
.WhereIf(multiCriteriaSelectCount > 0, t => t.TrialDicList.Count(t => t.KeyName == StaticData.Criterion) == multiCriteriaSelectCount)
.WhereIf(multiReviewTypeSelectCount > 0, t => t.TrialDicList.Count(t => t.KeyName == StaticData.ReviewType) == multiReviewTypeSelectCount)
.WhereIf(_userInfo.UserTypeEnumInt != (int)UserTypeEnum.SuperAdmin, t => t.TrialUserList.Any(t => t.UserId == _userInfo.Id))
.WhereIf(_userInfo.UserTypeEnumInt != (int)UserTypeEnum.SuperAdmin && _userInfo.UserTypeEnumInt != (int)UserTypeEnum.ProjectManager && _userInfo.UserTypeEnumInt != (int)UserTypeEnum.APM, t => t.IsDeleted == false)
.ProjectTo<TrialDetailDTO>(_mapper.ConfigurationProvider, new { userTypeEnumInt = _userInfo.UserTypeEnumInt, userId = _userInfo.Id });
return await query.ToPagedListAsync(searchParam.PageIndex, searchParam.PageSize, string.IsNullOrWhiteSpace(searchParam.SortField) ? "CreateTime" : searchParam.SortField, searchParam.Asc);
}
//过滤废除的项目
public async Task<List<TrialSelectDTO>> GetTrialSelect()
{
return await _trialRepository.AsQueryable()
.WhereIf(_userInfo.UserTypeEnumInt != (int)UserTypeEnum.SuperAdmin, t => t.TrialUserList.Any(t => t.UserId == _userInfo.Id))
.WhereIf(_userInfo.UserTypeEnumInt != (int)UserTypeEnum.SuperAdmin && _userInfo.UserTypeEnumInt != (int)UserTypeEnum.ProjectManager && _userInfo.UserTypeEnumInt != (int)UserTypeEnum.APM, t => t.IsDeleted == false)
.ProjectTo<TrialSelectDTO>(_mapper.ConfigurationProvider).ToListAsync();
}
/// <summary>
/// 获取项目基本信息
/// </summary>
/// <param name="projectId"></param>
/// <returns></returns>
[HttpGet("{projectId:guid}")]
public async Task<TrialDetailDTO> GetTrialInfoAndLockState(Guid projectId)
{
return (await _trialRepository.Where(o => o.Id == projectId).IgnoreQueryFilters().ProjectTo<TrialDetailDTO>(_mapper.ConfigurationProvider).FirstOrDefaultAsync()).IfNullThrowException();
}
[NonDynamicMethod]
public async Task<int> GetTrialMaxState(Guid trialId)
{
return await _repository.Where<EnrollDetail>(t => t.TrialId == trialId).MaxAsync(u => (int?)u.EnrollStatus) ?? 0;
}
[HttpGet("{trialId:guid}")]
public async Task<TrialAndTrialStateVieModel> GetTrialInfoAndMaxTrialState(Guid trialId)
{
return new TrialAndTrialStateVieModel()
{
TrialView = await GetTrialInfoAndLockState(trialId),
TrialMaxState = await GetTrialMaxState(trialId)
};
}
[HttpGet("{trialId:guid}")]
public async Task<int> GetTrialExpeditedState(Guid trialId)
{
var trial = await _trialRepository.FirstOrDefaultAsync(u => u.Id == trialId).IfNullThrowConvertException();
return trial.Expedited;
}
/// <summary>
/// 添加项目
/// </summary>
/// <param name="trialAddModel"></param>
/// <returns></returns>
[NonDynamicMethod]
public virtual async Task<IResponseOutput<Trial>> AddOrUpdateTrial(TrialCommand trialAddModel)
{
// 到时候 策略授权 统一改 归类
if (!(_userInfo.UserTypeEnumInt == (int)UserTypeEnum.APM || _userInfo.UserTypeEnumInt == (int)UserTypeEnum.ProjectManager || _userInfo.UserTypeEnumInt == (int)UserTypeEnum.SuperAdmin))
{
throw new BusinessValidationFailedException("仅PM/APM可以操作!");
}
if (trialAddModel.Id == Guid.Empty || trialAddModel.Id == null)
{
if (await _trialRepository.AnyAsync(u => u.TrialCode == trialAddModel.TrialCode))
{
throw new BusinessValidationFailedException("Same Trial ID already exists.");
}
var dbMaxCode = await _trialRepository.Where(t => t.CreateTime.Year == DateTime.Now.Year && t.TrialType == trialAddModel.TrialType).Select(t => t.Code).DefaultIfEmpty().MaxAsync();
var currentYearMaxCodeNext = dbMaxCode + 1;
//var test = _trialRepository.Where(t => t.CreateTime.Year == DateTime.Now.Year + 1).Select(t => t.Code).DefaultIfEmpty(1).ToList();
var trial = _mapper.Map<Trial>(trialAddModel);
//trial.Id = NewId.NextGuid();
var yearStr = DateTime.Now.Year.ToString();
trial.Code = currentYearMaxCodeNext;
trial.TrialCode = (trial.TrialType == 1 ? yearStr.Substring(yearStr.Length - 2) : "T0") + trial.TrialCode + currentYearMaxCodeNext.ToString("D3");
//多选信息
trialAddModel.ModalityIds.ForEach(modalityId => trial.TrialDicList.Add(new TrialDictionary() { DictionaryId = modalityId, KeyName = StaticData.Modality, TrialId = trial.Id }));
trialAddModel.CriterionIds.ForEach(criterionId => trial.TrialDicList.Add(new TrialDictionary() { DictionaryId = criterionId, KeyName = StaticData.Criterion, TrialId = trial.Id }));
trialAddModel.ReviewTypeIds.ForEach(ReviewTypeId => trial.TrialDicList.Add(new TrialDictionary() { DictionaryId = ReviewTypeId, KeyName = StaticData.ReviewType, TrialId = trial.Id }));
//添加项目后 项目状态变更为申请下载简历
trial.TrialEnrollStatus = (int)TrialEnrollStatus.ChooseDoctor;
//trial.TrialStatusStr = StaticData.TrialInitializing;
//状态变更详细表
trial.ClinicalTrialProjectDetails.Add(new TrialStatusDetail() { TrialId = trial.Id, TrialStatus = (int)TrialEnrollStatus.ChooseDoctor });
trial = await _trialRepository.AddAsync(trial);
//如果是PM 则需要将该人员添加到 运维人员表
//添加运维人员PM
await _repository.AddAsync(new TrialUser() { TrialId = trial.Id, UserId = _userInfo.Id ,JoinTime = DateTime.Now});
// 添加扩展信息表记录
await _repository.AddAsync(new TrialPaymentPrice() { TrialId = trial.Id });
//添加访视
await _repository.AddAsync(new VisitStage { TrialId = trial.Id, VisitNum = 0, BlindName = "B" + 0.ToString("D3"), VisitDay = 1, VisitName = "Baseline", IsBaseLine = true });
await _repository.AddAsync(new VisitStage { TrialId = trial.Id, VisitNum = 1, BlindName = "B" + 10.ToString("D3"), VisitDay = 30, VisitName = "Visit 1" });
var success = await _repository.SaveChangesAsync();
_provider.Set(trial.Id.ToString(), StaticData.TrialInitializing, TimeSpan.FromDays(7));
return ResponseOutput.Ok(trial);
}
else
{
var updateModel = trialAddModel;
if (!await _repository.AnyAsync<Trial>(u => u.Id == trialAddModel.Id && (u.TrialStatusStr == StaticData.TrialInitializing || u.TrialStatusStr == StaticData.TrialOngoing)))
{
throw new BusinessValidationFailedException("项目初始化或者进行中时 才允许操作");
}
// 判断项目Id 是否已经存在
if (await _repository.AnyAsync<Trial>(u => u.TrialCode == updateModel.TrialCode && u.Id != updateModel.Id))
{
throw new BusinessValidationFailedException("Same Trial ID already exists.");
}
var trial = await _repository.FirstOrDefaultAsync<Trial>(t => t.Id == updateModel.Id);
//删除中间表 Title对应的记录
await _repository.BatchDeleteAsync<TrialDictionary>(t => t.TrialId == updateModel.Id);
//重新插入新的 Title记录
updateModel.ModalityIds.ForEach(modalityId => trial.TrialDicList.Add(new TrialDictionary() { DictionaryId = modalityId, KeyName = StaticData.Modality, TrialId = trial.Id }));
updateModel.CriterionIds.ForEach(criterionId => trial.TrialDicList.Add(new TrialDictionary() { DictionaryId = criterionId, KeyName = StaticData.Criterion, TrialId = trial.Id }));
updateModel.ReviewTypeIds.ForEach(ReviewTypeId => trial.TrialDicList.Add(new TrialDictionary() { DictionaryId = ReviewTypeId, KeyName = StaticData.ReviewType, TrialId = trial.Id }));
if (updateModel.Expedited != trial.Expedited && updateModel.Expedited != null)
{
TrialExpeditedChange = true;
await TrialExpeditedStatusChange(trial.Id, trial.Expedited, (int)updateModel.Expedited);
}
_mapper.Map(updateModel, trial);
var success = await _repository.SaveChangesAsync();
return ResponseOutput.Ok(trial);
}
}
// TODO: 需要优化,嵌套两层 switch case
[NonDynamicMethod]
private async Task TrialExpeditedStatusChange(Guid trialId, int oldState, int newState)
{
switch (oldState)
{
case (int)TrialExpedited.None:
switch (newState)
{
case (int)TrialExpedited.ExpeditedIn24H:
await _repository.BatchUpdateAsync<Workload>(t => t.IsLock == false && t.TrialId == trialId, u => new Workload()
{
TimepointIn24H = u.Timepoint,
AdjudicationIn24H = u.Adjudication,
Timepoint = 0,
Adjudication = 0
});
break;
case (int)TrialExpedited.ExpeditedIn48H:
await _repository.BatchUpdateAsync<Workload>(t => t.IsLock == false && t.TrialId == trialId, u => new Workload()
{
TimepointIn48H = u.Timepoint,
AdjudicationIn48H = u.Adjudication,
Timepoint = 0,
Adjudication = 0
});
break;
}
//_workloadRepository.Update(t => t.IsLock == false && t.TrialId == trialId, u => new Workload()
//{
// Timepoint = 0,
// Adjudication = 0
//});
break;
case (int)TrialExpedited.ExpeditedIn24H:
switch (newState)
{
case (int)TrialExpedited.None:
await _repository.BatchUpdateAsync<Workload>(t => t.IsLock == false && t.TrialId == trialId, u => new Workload()
{
Timepoint = u.TimepointIn24H,
Adjudication = u.AdjudicationIn24H,
TimepointIn24H = 0,
AdjudicationIn24H = 0
});
break;
case (int)TrialExpedited.ExpeditedIn48H:
await _repository.BatchUpdateAsync<Workload>(t => t.IsLock == false && t.TrialId == trialId, u => new Workload()
{
TimepointIn48H = u.TimepointIn24H,
AdjudicationIn48H = u.AdjudicationIn24H,
TimepointIn24H = 0,
AdjudicationIn24H = 0
});
break;
}
//_workloadRepository.Update(t => t.IsLock == false && t.TrialId == trialId, u => new Workload()
//{
// TimepointIn24H = 0,
// AdjudicationIn24H = 0
//});
break;
case (int)TrialExpedited.ExpeditedIn48H:
switch (newState)
{
case (int)TrialExpedited.None:
await _repository.BatchUpdateAsync<Workload>(t => t.IsLock == false && t.TrialId == trialId, u => new Workload()
{
Timepoint = u.TimepointIn48H,
Adjudication = u.AdjudicationIn48H,
TimepointIn48H = 0,
AdjudicationIn48H = 0
});
break;
case (int)TrialExpedited.ExpeditedIn24H:
await _repository.BatchUpdateAsync<Workload>(t => t.IsLock == false && t.TrialId == trialId, u => new Workload()
{
TimepointIn24H = u.TimepointIn48H,
AdjudicationIn24H = u.AdjudicationIn48H,
TimepointIn48H = 0,
AdjudicationIn48H = 0
});
break;
}
//_workloadRepository.Update(t => t.IsLock == false && t.TrialId == trialId, u => new Workload()
//{
// TimepointIn48H = 0,
// AdjudicationIn48H = 0
//});
break;
}
}
/// <summary> 删除临床项目 </summary>
/// <param name="trialId">临床试验项目Id</param>
[HttpDelete, Route("{trialId:guid}")]
[TypeFilter(typeof(TrialResourceFilter))]
public async Task<IResponseOutput> DeleteTrial(Guid trialId)
{
var trial = await _trialRepository.FirstOrDefaultAsync(u => u.Id == trialId);
if (trial == null) return Null404NotFound(trial);
if (trial.VisitPlanConfirmed)
{
return ResponseOutput.NotOk("Trial访视计划已经确认无法删除");
}
if (await _repository.AnyAsync<Enroll>(u => u.TrialId == trialId))
{
return ResponseOutput.NotOk("该Trial有医生入组或在入组流程中无法删除");
}
if (await _repository.AnyAsync<TrialSite>(u => u.TrialId == trialId))
{
return ResponseOutput.NotOk("该Trial下面有Site无法删除");
}
//PM 可以删除项目 仅仅在没有site 参与者只有他自己的时候
if (_userInfo.UserTypeEnumInt == (int)UserTypeEnum.ProjectManager)
{
//参与者仅有他自己时,可以删除
if (await _trialUserRepository.CountAsync(t => t.TrialId == trialId) == 1)
{
var success1 = await _repository.BatchDeleteAsync<Trial>(o => o.Id == trialId) ||
await _repository.BatchDeleteAsync<TrialUser>(t => t.TrialId == trialId) ||
await _repository.BatchDeleteAsync<TrialDictionary>(t => t.TrialId == trialId);
return ResponseOutput.Result(success1);
}
}
if (await _repository.AnyAsync<TrialUser>(u => u.TrialId == trialId))
{
return ResponseOutput.NotOk("该Trial下面有参与者无法删除");
}
var success = await _repository.BatchDeleteAsync<Trial>(o => o.Id == trialId) ||
await _repository.BatchDeleteAsync<TrialUser>(t => t.TrialId == trialId) ||
await _repository.BatchDeleteAsync<TrialDictionary>(t => t.TrialId == trialId) ||
await _repository.BatchDeleteAsync<TrialSiteUser>(t => t.TrialId == trialId);
return ResponseOutput.Result(success);
}
[HttpPost]
public async Task<PageOutput<TrialDetailDTO>> GetReviewerTrialListByEnrollmentStatus(TrialByStatusQueryDTO param)
{
var query = _trialRepository.AsQueryable()
.WhereIf(param.Status == 5, t => t.EnrollList.Any(u => u.EnrollStatus == (int)EnrollStatus.HasCommittedToCRO))
.WhereIf(param.Status == 8, t => t.EnrollList.Any(u => u.EnrollStatus == (int)EnrollStatus.InviteIntoGroup))
.WhereIf(param.Status == 10, t => t.EnrollList.Any(u => u.EnrollStatus == (int)EnrollStatus.DoctorReading))
.WhereIf(param.Status == 14, t => t.EnrollList.Any(u => u.EnrollStatus == (int)EnrollStatus.Finished))
.ProjectTo<TrialDetailDTO>(_mapper.ConfigurationProvider, new { userTypeEnumInt = _userInfo.UserTypeEnumInt, userId = _userInfo.Id });
return await query.ToPagedListAsync(param.PageIndex, param.PageSize, string.IsNullOrWhiteSpace(param.SortField) ? "CreateTime" : param.SortField, param.Asc);
}
/// <summary>
/// 根据项目Id 获取医生Id用于出发计算费用
/// </summary>
public async Task<List<Guid>> GetTrialEnrollmentReviewerIds(Guid trialId)
{
return await _repository.Where<Enroll>(u => u.TrialId == trialId &&
u.EnrollStatus >= (int)EnrollStatus.DoctorReading).Select(u => u.DoctorId).Distinct().ToListAsync();
}
[HttpPost("{trialId:guid}/{confirmOrCancel:bool}")]
[TrialAudit(AuditType.TrialAudit, AuditOptType.ConfirmTrialVisitPlan)]
[TypeFilter(typeof(TrialResourceFilter))]
public async Task<IResponseOutput> ConfirmTrialVisitPlan(Guid trialId, bool confirmOrCancel = true)
{
//取消的时候要做验证 确认的话,前端操作多次也没关系
if (!confirmOrCancel)
{
if (await _repository.AnyAsync<Subject>(t => t.TrialId == trialId))
{
return ResponseOutput.NotOk("VisitPlan has been generated for some subjects,can not revert editable state");
}
}
await _trialRepository.BatchUpdateNoTrackingAsync(u => u.Id == trialId, t => new Trial() { VisitPlanConfirmed = confirmOrCancel });
await _repository.BatchUpdateAsync<VisitStage>(u => u.Id == trialId, t => new VisitStage() { IsConfirmed = true });
return ResponseOutput.Ok();
}
#region 医生用户接口
/// <summary> 分页获取医生参与的临床实验项目列表(查询条件)</summary>
/// <returns></returns>
[HttpPost]
public async Task<PageOutput<TrialDetailDTO>> GetTrialListByReviewer(ReviewerTrialQueryDTO searchModel)
{
var query = _trialRepository
.WhereIf(searchModel.EnrollStatus != null, o => (int)searchModel.EnrollStatus! == 10 ? o.EnrollList.Any(o => o.EnrollStatus >= 10 && o.EnrollStatus <= 13 && o.DoctorId == _userInfo.Id) : o.EnrollList.Any(o => o.EnrollStatus == searchModel.EnrollStatus && o.DoctorId == _userInfo.Id))
.WhereIf(searchModel.Expedited != null, o => o.Expedited == searchModel.Expedited)
.WhereIf(!string.IsNullOrEmpty(searchModel.Code), o => o.TrialCode.Contains(searchModel.Code))
.WhereIf(!string.IsNullOrWhiteSpace(searchModel.Indication), o => o.Indication.Contains(searchModel.Indication))
.WhereIf(_userInfo.UserTypeEnumInt != (int)UserTypeEnum.SuperAdmin, t => t.TrialUserList.Any(t => t.UserId == _userInfo.Id))
.ProjectTo<TrialDetailDTO>(_mapper.ConfigurationProvider, new { userTypeEnumInt = _userInfo.UserTypeEnumInt, userId = _userInfo.Id });
return await query.ToPagedListAsync(searchModel.PageIndex, searchModel.PageSize, string.IsNullOrWhiteSpace(searchModel.SortField) ? "CreateTime" : searchModel.SortField, searchModel.Asc);
}
/// <summary>
/// 医生确认入组或拒绝入组
/// </summary>
/// <param name="trialId">项目Id</param>
/// <param name="status">9-拒绝入组10-确认入组</param>
/// <returns></returns>
[HttpPost("{trialId:guid}/{status:int}")]
[TypeFilter(typeof(TrialResourceFilter))]
public async Task<IResponseOutput> UpdateEnrollStatus(Guid trialId, int status)
{
await _repository.AddAsync(new EnrollDetail()
{
DoctorId = _userInfo.Id,
TrialId = trialId,
EnrollStatus = status,
OptUserType = (int)SystemUserType.DoctorUser,
});
return ResponseOutput.Result(await _repository.BatchUpdateAsync<Enroll>(u => u.TrialId == trialId && u.DoctorId == _userInfo.Id, e => new Enroll
{
EnrollStatus = status
}));
}
#endregion
}
}