添加项目文件。

This commit is contained in:
DK
2022-03-28 15:27:40 +08:00
parent b620fcb0cf
commit dc78fd1a09
898 changed files with 173053 additions and 0 deletions
@@ -0,0 +1,163 @@
using System;
using IRaCIS.Core.Infrastructure.Extention;
using IRaCIS.Core.Domain.Share;
using System.ComponentModel.DataAnnotations;
namespace IRaCIS.Application.Contracts
{
public class SubjectCommand
{
public Guid? Id { get; set; }
public string Code { get; set; } = String.Empty;
public int? Age { get; set; }
public string Sex { get; set; } = string.Empty;
public Guid SiteId { get; set; }
public Guid TrialId { get; set; }
public string MedicalNo { get; set; } = string.Empty;
public string ShortName { get; set; } = string.Empty;
public string Height { get; set; } = string.Empty;
public string Weight { get; set; } = string.Empty;
public DateTime? BirthDate { get; set; }
public DateTime? SignDate { get; set; }
public bool IsUrgent { get; set; }
public DateTime? FirstGiveMedicineTime { get; set; }
//public DateTime? OutEnrollmentTime { get; set; }
//public DateTime? VisitOverTime { get; set; }
//public SubjectStatus Status { get; set; }
//public string Reason { get; set; } = string.Empty;
}
public class SubjectStatusChangeCommand
{
[NotDefault]
public Guid SubjectId { get; set; }
public SubjectStatus Status { get; set; }
public DateTime? OutEnrollmentTime { get; set; }
public DateTime? VisitOverTime { get; set; }
public string Reason { get; set; } = string.Empty;
public Guid? FinalSubjectVisitId { get; set; }
}
public class TrialSubjectConfig
{
public string SubjectCodeRule { get; set; } = string.Empty;
public bool IsNoticeSubjectCodeRule { get; set; }
public bool IsHaveFirstGiveMedicineDate { get; set; }
public bool IsEnrollementQualificationConfirm { get; set; }
public bool IsHaveSubjectAge { get; set; }
public string OutEnrollmentVisitName { get; set; } = string.Empty;
public bool VisitPlanConfirmed { get; set; }
public bool IsSubjectSexView { get; set; } = false;
public bool IsSubjectExpeditedView { get; set; } = false;
public bool IsSubjectSecondCodeView { get; set; } = false;
}
public class SubjectQueryView: SubjectCommand
{
public DateTime? OutEnrollmentTime { get; set; }
public DateTime? VisitOverTime { get; set; }
public SubjectStatus Status { get; set; }
public string Reason { get; set; } = string.Empty;
//public int? StudyCount { get; set; }
//public int? VisitCount { get; set; }
//public int? PlanVisitCount { get; set; }
//public int? InPlanDicomStudyUploadCount { get; set; }
//public int? OutPlanDicomStudyUploadCount { get; set; }
//public int? InPlanNoneDicomStudyUploadCount { get; set; }
//public int? OutPlanNoneDicomStudyUploadCount { get; set; }
public Guid? FinalSubjectVisitId { get; set; }
public int? TotalVisitCount => OutPlanVisitCount + InPlanVisitCount;
public int? TotalVisitSubmmitCount => OutPlanVisitSubmmitCount + InPlanVisitSubmmitCount;
public int? OutPlanVisitCount { get; set; }
public int? OutPlanVisitSubmmitCount { get; set; }
public int? InPlanVisitCount { get; set; }
public int? InPlanVisitSubmmitCount { get; set; }
public int? MissingSubmmitCount { get; set; }
public int? LostVisitCount { get; set; }
public DateTime? CreateTime { get; set; }
public DateTime? UpdateTime { get; set; }
public string SiteName { get; set; } = string.Empty;
public string TrialSiteCode { get; set; } = string.Empty;
public string LatestVisitName { get; set; } = string.Empty;
public string LatestBlindName { get; set; } = string.Empty;
public bool IsMissingImages => MissingSubmmitCount > 0;
public Guid LatestSubmitSubjectVisitId { get; set; }
}
public class SubjectQueryParam : PageInput
{
[NotDefault]
public Guid TrialId { get; set; }
public string Code { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string Sex { get; set; } = string.Empty;
public Guid? SiteId { get; set; }
public SubjectStatus? Status { get; set; }
}
public class SubjectSelect
{
public Guid SubjectId { get; set; }
public string Name => LastName + " / " + FirstName;
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string Code { get; set; } = string.Empty;
public string Sex { get; set; } = string.Empty;
public int? Age { get; set; }
public SubjectStatus Status { get; set; }
public DateTime? FirstGiveMedicineTime { get; set; }
public string MRN { get; set; } = string.Empty;
}
}
@@ -0,0 +1,85 @@
using IRaCIS.Core.Infrastructure.Extention;
namespace IRaCIS.Application.Contracts
{
public class VisitStageSelectDTO
{
public Guid Id { get; set; }
public decimal VisitNum { get; set; }
public string VisitName { get; set; } = String.Empty;
public int VisitDay { get; set; }
public bool IsBaseLine { get; set; } = false;
public string AnonymousVisitName { get; set; } = string.Empty;
public int VisitWindowLeft { get; set; }
public int VisitWindowRight { get; set; }
}
public class VisitStageDTO : VisitPlanCommand
{
public DateTime CreateTime { get; set; }
}
public class VisitPlanView
{
public List<VisitStageDTO> VisitPlanList = new List<VisitStageDTO>();
public decimal TimePointsPerPatient { get; set; }
public bool VisitPlanConfirmed { get; set; }
public bool IsHaveFirstGiveMedicineDate { get; set; } = true;
//public bool SubjectHasAdded { get; set; }
}
public class VisitPlanCommand
{
//public string BlindName => "B" + (VisitNum * 10).ToString("D3");
public string BlindName { get; set; } = String.Empty;
public bool IsConfirmed { get; set; } = false;
public Guid? Id { get; set; }
public Guid TrialId { get; set; }
public decimal VisitNum { get; set; }
public string VisitName { get; set; } = String.Empty;
public int VisitDay { get; set; }
public string Description { get; set; } = String.Empty;
public bool NeedGlobal { get; set; } = false;
public bool IsBaseLine { get; set; } = false;
public string AnonymousVisitName { get; set; } = string.Empty;
public int VisitWindowLeft { get; set; }
public int VisitWindowRight { get; set; }
}
public class VisitPlanQueryDTO : PageInput
{
public Guid TrialId { get; set; } = Guid.Empty;
public string Keyword { get; set; } = string.Empty;
}
public class VisitPlanInfluenceSubjectVisitStatDTO
{
public Guid Id { get; set; }
public Guid CreateUserId { get; set; }
public string CreateUser { get; set; } = String.Empty;
public DateTime CreateTime { get; set; }
public int InconsistentCount { get; set; }
}
}
@@ -0,0 +1,419 @@
using IRaCIS.Core.Infrastructure.Extention;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Application.Contracts.Dicom.DTO;
using Newtonsoft.Json;
using System.ComponentModel.DataAnnotations;
namespace IRaCIS.Core.Application.Contracts
{
public class SubjectVisitCommand
{
public bool IsFinalVisit { get; set; }
public string BlindName { get; set; } = string.Empty;
public Guid? Id { get; set; }
public Guid TrialId { get; set; }
public Guid SubjectId { get; set; }
public Guid SiteId { get; set; }
public bool InPlan { get; set; } = true;
public VisitExecutedEnum VisitExecuted { get; set; }
public decimal VisitNum { get; set; }
public string VisitName { get; set; } = string.Empty;
public int? VisitDay { get; set; }
public string SVUPDES { get; set; } = string.Empty;
public bool IsBaseLine { get; set; } = false;
public int VisitWindowLeft { get; set; }
public int VisitWindowRight { get; set; }
public bool IsLostVisit { get; set; }
public PDStateEnum? PDState { get; set; }
public bool? IsEnrollmentConfirm { get; set; }
public DateTime? SubjectFirstGiveMedicineTime { get; set; }
//public bool IsOutEnromentVisit { get; set; }
public Guid? OutPlanPreviousVisitId { get; set; }
//public bool IsUrgent { get; set; }
}
public class SubjectVisitDTO : SubjectVisitCommand
{
public SubjectStatus SubjectStatus { get; set; }
}
public class SubjectVisitNewDTO : SubjectVisitDTO
{
public TrialQCProcess QCProcessEnum { get; set; }
public CheckStateEnum CheckState { get; set; }
public SubmitStateEnum SubmitState { get; set; }
public AuditStateEnum AuditState { get; set; }
public string CurrentActionUser { get; set; } = String.Empty;
public DateTime? CurrentActionUserExpireTime { get; set; }
public string PreliminaryAuitUser { get; set; } = String.Empty;
public string ReviewAuitUser { get; set; } = String.Empty;
public DateTime? ReviewAuitTime { get; set; }
public DateTime? PreliminaryAuditTime { get; set; }
public int? StudyCount { get; set; }
public String TrialSiteCode { get; set; } = String.Empty;
public DateTime UpdateTime { get; set; }
public string SubjectSex { get; set; } = String.Empty;
public int SubjectAge { get; set; }
public string SubjectCode { get; set; } = String.Empty;
[JsonIgnore]
public string SubjectName => LastName + " / " + FirstName;
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string SiteName { get; set; } = String.Empty;
public string SiteCode { get; set; } = String.Empty;
}
public class SubjectVisitSearchDTO : PageInput
{
public string SubjectInfo { get; set; } = String.Empty;
public string VisitPlanInfo { get; set; } = String.Empty;
public Guid? SubjectId { get; set; }
public Guid TrialId { get; set; }
public Guid? SiteId { get; set; }
}
public class SubjectVisitSelectItem
{
public Guid Id { get; set; }
public decimal VisitNum { get; set; }
public string VisitName { get; set; } = String.Empty;
public int VisitExecuted { get; set; }
}
public class SubjectVisitSelectDTO
{
public Guid GuidId { get; set; } = Guid.NewGuid();
public Guid SubjectVisitId { get; set; } = Guid.Empty;
public decimal VisitNum { get; set; }
public string VisitName { get; set; } = string.Empty;
public string SVUPDES { get; set; } = string.Empty;
public int? VisitDay { get; set; }
//public Guid VisitStageId { get; set; } = Guid.Empty;
public DateTime? SVSTDTC { get; set; }
public DateTime? SVENDTC { get; set; }
//public bool IsSubjectVisit { get; set; } = false;
public VisitExecutedEnum VisitExecuted { get; set; }
public List<SubjectVisitStudyDTO> StudyList = new List<SubjectVisitStudyDTO>();
}
public class SubjectVisitStudyDTO
{
public Guid SubjectVisitId { get; set; }
public Guid StudyId { get; set; }
public string StudyCode { get; set; } = String.Empty;
public string Modalities { get; set; } = String.Empty;
public int Status { get; set; }
}
public class VisitStudyDTO
{
public Guid StudyId { get; set; }
public string StudyCode { get; set; } = String.Empty;
public string Modalities { get; set; } = String.Empty;
public int SeriesCount { get; set; }
public int InstanceCount { get; set; }
public List<DicomSeriesDTO> SeriesList { get; set; } = new List<DicomSeriesDTO>();
}
public class ArchiveStudyCommand
{
public Guid? AbandonStudyId { get; set; }
public Guid SubjectVisitId { get; set; }
public string StudyInstanceUid { get; set; } = String.Empty;
//public Guid TrialId { get; set; }
//public Guid SiteId { get; set; }
//public Guid SubjectId { get; set; }
//public DateTime? SVSTDTC { get; set; }
//public DateTime? SVENDTC { get; set; }
//public string DTFPath { get; set; }
//public string FileRealName { get; set; }
}
public class UploadDTFCommand
{
public Guid TrialId { get; set; }
public Guid SiteId { get; set; }
public Guid SubjectId { get; set; }
public Guid SubjectVisitId { get; set; }
}
public class StudyCommand
{
public Guid Id { get; set; } = Guid.Empty;
public Guid TrialId { get; set; }
public Guid SiteId { get; set; }
public Guid SubjectId { get; set; }
public Guid SubjectVisitId { get; set; }
}
public class StudyEditCommand : StudyCommand
{
public Guid VisitStageId { get; set; }
public DateTime? SVSTDTC { get; set; }
public DateTime? SVENDTC { get; set; }
public string Comment { get; set; } = String.Empty;
}
public class StudyStatDTO
{
public int StudyCount { get; set; }
public decimal VisitNum { get; set; }
public string VisitName { get; set; } = String.Empty;
public int VisitDay { get; set; }
public string Description { get; set; } = String.Empty;
public Guid TrialId { get; set; }
public Guid SubjectVisitId { get; set; }
}
public class DistributeReviewerStudyStatusDTO
{
public string NameCN { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
public string ReviewerCode { get; set; } = String.Empty;
public string StudyCode { get; set; } = String.Empty;
public int Status { get; set; }
}
public class StudyStatusQueryDTO : PageInput
{
public Guid TrialId { get; set; }
public Guid? ReviewerId { get; set; }
public int? StudyStatus { get; set; }
}
public class VerifyStudyUploadResult
{
public string StudyInstanceUid { get; set; } = String.Empty;
public VerifyStudyDto? StudyInfo { get; set; }
public bool AllowUpload { get; set; } = false;
public bool AllowReUpload { get; set; } = false;
public string ErrorMesseage { get; set; } = String.Empty;
}
public class VerifyUploadOrReupload
{
[NotDefault]
public Guid TrialId { get; set; }
[NotDefault]
public Guid SubjectId { get; set; }
[NotDefault]
public Guid SubjectVisitId { get; set; }
public decimal VisitNum { get; set; }
public List<VerifyUploadStudyBasicInfo> StudyInstanceUidList { get; set; } = new List<VerifyUploadStudyBasicInfo>();
}
public class VerifyUploadStudyBasicInfo
{
public string StudyInstanceUid { get; set; } = String.Empty;
public DateTime StudyDate { get; set; }
}
public class VerifyStudyDto
{
public Guid Id { get; set; }
public string StudyCode { get; set; } = String.Empty;
public Guid SubjectId { get; set; }
public Guid SubjectVisitId { get; set; }
public string SubjectCode { get; set; } = String.Empty;
public string VisitName { get; set; } = String.Empty;
public string SubjectName => LastName + " / " + FirstName;
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public DateTime StudyTime { get; set; }
}
public class StudyDTO
{
public Guid SubjectVisitId { get; set; }
public decimal VisitNum { get; set; }
public string VisitName { get; set; } = String.Empty;
public int? VisitDay { get; set; }
public string SVUPDES { get; set; } = String.Empty;
public DateTime? SVSTDTC { get; set; }
public DateTime? SVENDTC { get; set; }
public Guid Id { get; set; }
public Guid TrialId { get; set; }
public string StudyCode { get; set; } = String.Empty;
public int StudyStatus { get; set; }
public DateTime? StudyDate { get; set; }
public string Modalities { get; set; } = String.Empty;
public int SeriesCount { get; set; }
public int InstanceCount { get; set; }
public bool IsDoubleReview { get; set; }
public string StudyDescription { get; set; } = String.Empty;
public string BodyPartExamined { get; set; } = String.Empty;
public string Comment { get; set; } = String.Empty;
public DateTime UpdateTime { get; set; }
public DateTime? UploadedTime { get; set; }
public DateTime? DeadlineTime { get; set; }
//public List<DistributeReviewer> DistributeReviewers { get; set; }=new List<DistributeReviewer>();
public Guid SiteId { get; set; }
public string SiteName { get; set; } = String.Empty;
public string InstitutionName { get; set; } = String.Empty;
public string StudyId { get; set; } = String.Empty;//这个Id是dicom文件中的Id
public Guid SubjectId { get; set; }
public string SubjectCode { get; set; } = String.Empty;
public string SubjectName => LastName + " / " + FirstName;
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public int? SubjectAge { get; set; }
public SubjectStatus SubjectStatus { get; set; }
public string SubjectSex { get; set; } = String.Empty;
public string SubjectMedicalNo { get; set; } = string.Empty;
public string PatientId { get; set; } = String.Empty;
public string PatientName { get; set; } = String.Empty;
public string PatientAge { get; set; } = String.Empty;
public string PatientSex { get; set; } = String.Empty;
public string AccessionNumber { get; set; } = string.Empty;
public string PatientBirthDate { get; set; } = string.Empty;
//public int? NeedDealMessageCount { get; set; }
//public int? TotalMessageCount { get; set; }
public int? DTFCount { get; set; }
public string UploaderFirstName { get; set; } = String.Empty;
public string UploaderLastName { get; set; } = String.Empty;
}
public class DistributeReviewer
{
public Guid StudyId { get; set; }
public Guid ReviewerId { get; set; }
public string NameCN { get; set; } = String.Empty;
public string Name { get; set; } = String.Empty;
public int Status { get; set; }
}
public class StudyQueryDTO : PageInput
{
public Guid? SubjectId { get; set; }
public Guid? SiteId { get; set; }
public Guid TrialId { get; set; }
public string SubjectInfo { get; set; } = String.Empty;
public string VisitPlanInfo { get; set; }=String.Empty;
public Guid? SubjectVisitId { get; set; }
public DateTime? StudyTimeBegin { get; set; }
public DateTime? StudyTimeEnd { get; set; }
public DateTime? UpdateTimeBegin { get; set; }
public DateTime? UpdateTimeEnd { get; set; }
}
}
@@ -0,0 +1,12 @@
using IRaCIS.Application.Contracts;
using Microsoft.AspNetCore.Mvc;
namespace IRaCIS.Application.Interfaces
{
public interface ISubjectService
{
Task<IResponseOutput> AddOrUpdateSubject([FromBody] SubjectCommand subjectCommand);
Task<IResponseOutput> DeleteSubject(Guid id);
Task<IResponseOutput<PageOutput<SubjectQueryView>, TrialSubjectConfig>> GetSubjectList(SubjectQueryParam param);
}
}
@@ -0,0 +1,12 @@
using IRaCIS.Core.Application.Contracts;
namespace IRaCIS.Core.Application.Interfaces
{
public interface ISubjectVisitService
{
Task<IResponseOutput> AddOrUpdateSV(SubjectVisitCommand svCommand);
Task<IResponseOutput> DeleteSV(Guid id);
Task<List<VisitStudyDTO>> GetVisitStudyList(Guid trialId, Guid sujectVisitId, int isReading);
Task<IResponseOutput> SetSVExecuted(Guid subjectVisitId);
}
}
@@ -0,0 +1,18 @@
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Infra.EFCore;
using Microsoft.AspNetCore.Mvc;
namespace IRaCIS.Application.Interfaces
{
public interface IVisitPlanService
{
Task<IResponseOutput> AddOrUpdateVisitStage(VisitPlanCommand visitPlan);
Task<IResponseOutput> ConfirmTrialVisitPlan(Guid trialId, [FromServices] IRepository<VisitPlanInfluenceStat> _influnceStatRepository);
Task<IResponseOutput> DeleteVisitStage(Guid id);
Task<IActionResult> DownloadInflunceStudyList(Guid visitPlanInfluenceStatId, [FromServices] IRepository<VisitPlanInfluenceStudy> _influnceRepository);
Task<List<VisitPlanInfluenceSubjectVisitStatDTO>> GetInfluenceHistoryList(Guid trialId, [FromServices] IRepository<VisitPlanInfluenceStat> _influnceStatRepository);
Task<PageOutput<VisitStageDTO>> GetTrialVisitStageList(VisitPlanQueryDTO param);
Task<IEnumerable<VisitStageSelectDTO>> GetTrialVisitStageSelect(Guid trialId);
Task<VisitPlanView> GetVisitStageList(Guid trialId);
}
}
@@ -0,0 +1,252 @@
using IRaCIS.Application.Interfaces;
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Application.Filter;
using IRaCIS.Core.Domain.Share;
using Microsoft.AspNetCore.Mvc;
namespace IRaCIS.Application.Services
{
[ApiExplorerSettings(GroupName = "Trial")]
public class SubjectService : BaseService, ISubjectService
{
private readonly IRepository<Subject> _subjectRepository;
public SubjectService(IRepository<Subject> subjectRepository)
{
_subjectRepository = subjectRepository;
}
/// <summary>
/// 添加或更新受试者信息[New]
/// </summary>
/// <param name="subjectCommand">state:1-访视中,2-出组。0-全部</param>
/// <returns></returns>
[TrialAudit(AuditType.SubjectAudit, AuditOptType.AddOrUpdateSubject)]
[TypeFilter(typeof(TrialResourceFilter))]
public async Task<IResponseOutput> AddOrUpdateSubject([FromBody] SubjectCommand subjectCommand)
{
if (await _repository.AnyAsync<Trial>(t => t.Id == subjectCommand.TrialId && !t.VisitPlanConfirmed))
{
return ResponseOutput.NotOk("The trial visit plan has not been confirmed yet.Please contact the project manager to confirm the visit plan before adding subject.");
}
var verifyExp1 = new EntityVerifyExp<Subject>()
{
VerifyExp = u => u.Code == subjectCommand.Code && u.TrialId == subjectCommand.TrialId,
VerifyMsg = "A subjects with the same subject ID already existed in this trial."
};
var mapedSubject = await _subjectRepository.InsertOrUpdateAsync(subjectCommand, false, verifyExp1/*, verifyExp2*/);
if (subjectCommand.Id == null) //insert
{
var IsEnrollementQualificationConfirm = await _repository.Where<Trial>(t => t.Id == mapedSubject.TrialId).Select(u => u.IsEnrollementQualificationConfirm).FirstOrDefaultAsync();
//添加受试者的时候,获取访视计划列表,添加到受试者访视表。
var visitPlan = await _repository.Where<VisitStage>(t => t.TrialId == subjectCommand.TrialId).ToListAsync();
var svlist = _mapper.Map<List<SubjectVisit>>(visitPlan);
svlist.ForEach(t =>
{
t.TrialId = subjectCommand.TrialId;
t.SiteId = subjectCommand.SiteId;
t.IsEnrollmentConfirm = t.IsBaseLine ? IsEnrollementQualificationConfirm : false;
});
mapedSubject.SubjectVisitList = svlist;
}
else //update
{
//变更site
if (mapedSubject.SiteId != subjectCommand.SiteId)
{
await _repository.UpdateFromQueryAsync<SubjectVisit>(t => t.SubjectId == mapedSubject.Id,
u => new SubjectVisit() { SiteId = mapedSubject.SiteId });
await _repository.UpdateFromQueryAsync<DicomStudy>(t => t.SubjectId == mapedSubject.Id,
u => new DicomStudy() { SiteId = mapedSubject.SiteId });
};
////如果访视结束了 需要删除计划外未执行的访视
//if (mapedSubject.Status == SubjectStatus.EndOfVisit)
//{
// await _repository.DeleteFromQueryAsync<SubjectVisit>(t => t.VisitExecuted == VisitExecutedEnum.UnExecuted && t.SubjectId == mapedSubject.Id && t.InPlan == false);
//}
////如果是出组了 将受试者未执行的 设置为不可用
//if (mapedSubject.Status == SubjectStatus.OutOfEnrollment)
//{
// await _repository.UpdateFromQueryAsync<SubjectVisit>(t => t.SubjectId == mapedSubject.Id && t.VisitExecuted == VisitExecutedEnum.UnExecuted, u => new SubjectVisit() { VisitExecuted = VisitExecutedEnum.Unavailable });
//}
}
await _repository.SaveChangesAsync();
return ResponseOutput.Ok(mapedSubject.Id.ToString());
}
[HttpPut("{trialId:guid}")]
public async Task<IResponseOutput> UpdateSubjectStatus(SubjectStatusChangeCommand subjectStatusChangeCommand)
{
var subject = await _subjectRepository.FirstOrDefaultAsync(t => t.Id == subjectStatusChangeCommand.SubjectId);
if (subject == null) return Null404NotFound(subject);
_mapper.Map(subjectStatusChangeCommand, subject);
////如果访视结束了 需要删除计划外未执行的访视
//if (subjectStatusChangeCommand.Status == SubjectStatus.EndOfVisit)
//{
// await _repository.DeleteFromQueryAsync<SubjectVisit>(t => t.VisitExecuted == VisitExecutedEnum.UnExecuted && t.SubjectId == subject.Id && t.InPlan == false);
//}
//如果是出组了
if (subjectStatusChangeCommand.Status == SubjectStatus.OutOfVisit)
{
if (subjectStatusChangeCommand.FinalSubjectVisitId != null)
{
if (await _repository.AnyAsync<SubjectVisit>(t => t.SubjectId == subjectStatusChangeCommand.SubjectId && t.IsFinalVisit))
{
return ResponseOutput.NotOk("该受试者已经有访视设置为末次访视,不允许将该访视设置为末次访视");
}
var sv = await _repository.FirstOrDefaultAsync<SubjectVisit>(t => t.Id == subjectStatusChangeCommand.FinalSubjectVisitId).IfNullThrowException();
if (await _repository.AnyAsync<SubjectVisit>(t => t.SubjectId == subjectStatusChangeCommand.SubjectId && t.VisitNum > sv.VisitNum && t.SubmitState == SubmitStateEnum.ToSubmit))
{
return ResponseOutput.NotOk("该受试者此访视后有影像上传,该访视不允许设置为末次访视");
}
sv.IsFinalVisit = true;
//末次访视后的 访视设置为不可用
await _repository.UpdateFromQueryAsync<SubjectVisit>(t => t.SubjectId == subjectStatusChangeCommand.SubjectId && t.VisitNum > sv.VisitNum, u => new SubjectVisit() { VisitExecuted = VisitExecutedEnum.Unavailable });
}
//将受试者未执行的 设置为不可用
await _repository.UpdateFromQueryAsync<SubjectVisit>(t => t.SubjectId == subject.Id && t.VisitExecuted == VisitExecutedEnum.UnExecuted, u => new SubjectVisit() { VisitExecuted = VisitExecutedEnum.Unavailable });
}
else
{
}
await _repository.SaveChangesAsync();
return ResponseOutput.Ok();
}
[Obsolete]
[HttpGet("{trialId:guid}/{subjectId:guid}/{finalSubjectVisitId:guid}")]
public async Task<IResponseOutput> VerifySubjectFinalVisit(Guid subjectId, Guid finalSubjectVisitId)
{
if (await _repository.AnyAsync<SubjectVisit>(t => t.SubjectId == subjectId && t.IsFinalVisit))
{
return ResponseOutput.NotOk("该受试者已经有访视设置为末次访视,不允许将该访视设置为末次访视");
}
var sv = (await _repository.FirstOrDefaultAsync<SubjectVisit>(t => t.Id == finalSubjectVisitId)).IfNullThrowException();
if (await _repository.AnyAsync<SubjectVisit>(t => t.SubjectId == subjectId && t.VisitNum > sv.VisitNum && t.SubmitState == SubmitStateEnum.ToSubmit))
{
return ResponseOutput.NotOk("该受试者此访视后有影像上传,该访视不允许设置为末次访视");
}
return ResponseOutput.Ok();
}
[HttpDelete("{id:guid}/{trialId:guid}")]
[TrialAudit(AuditType.SubjectAudit, AuditOptType.DeleteSubject)]
[TypeFilter(typeof(TrialResourceFilter))]
public async Task<IResponseOutput> DeleteSubject(Guid id)
{
if (await _repository.AnyAsync<SubjectVisit>(u => u.SubjectId == id && u.VisitExecuted == VisitExecutedEnum.Executed))
{
return ResponseOutput.NotOk("This subject has executed a visit with uploading study images,and couldn't be deleted.");
}
var isSuccess = await _subjectRepository.DeleteFromQueryAsync(u => u.Id == id);
await _repository.DeleteFromQueryAsync<SubjectVisit>(u => u.SubjectId == id);
return ResponseOutput.Result(isSuccess);
}
/// <summary> 分页获取受试者列表[New] </summary>
/// /// <param name="param">state:1-访视中,2-出组。0-全部</param>
[HttpPost]
public async Task<IResponseOutput<PageOutput<SubjectQueryView>, TrialSubjectConfig>> GetSubjectList(SubjectQueryParam param)
{
var subjectQuery = _subjectRepository.Where(u => u.TrialId == param.TrialId)
.WhereIf(!string.IsNullOrWhiteSpace(param.Code), t => t.Code.Contains(param.Code))
.WhereIf(!string.IsNullOrWhiteSpace(param.Name), t => t.ShortName.Contains(param.Name))
.WhereIf(!string.IsNullOrWhiteSpace(param.Sex), t => t.Sex.Contains(param.Sex))
.WhereIf(param.Status != null, t => t.Status == param.Status)
.WhereIf(param.SiteId != null, t => t.SiteId == param.SiteId)
// CRC 只负责他管理site的受试者
.WhereIf(_userInfo.UserTypeEnumInt == (int)UserTypeEnum.ClinicalResearchCoordinator, t => t.TrialSite.CRCUserList.Any(t => t.UserId == _userInfo.Id))
.ProjectTo<SubjectQueryView>(_mapper.ConfigurationProvider);
var pageList = await subjectQuery.ToPagedListAsync(param.PageIndex, param.PageSize, param.SortField == string.Empty ? "Code" : param.SortField, param.Asc);
var trialConfig = await _repository.Where<Trial>(t => t.Id == param.TrialId).ProjectTo<TrialSubjectConfig>(_mapper.ConfigurationProvider).FirstOrDefaultAsync().IfNullThrowException();
return ResponseOutput.Ok(pageList, trialConfig);
}
/// <summary>
/// 计划外访视 获取受试者选择下拉框列表
/// </summary>
[HttpGet("{siteId:guid}/{trialId:guid}")]
public List<SubjectSelect> GetSubjectListBySiteId(Guid siteId, Guid trialId)
{
var query = _subjectRepository.Where(t => t.SiteId == siteId && t.TrialId == trialId && t.Status == SubjectStatus.OnVisit).Select(u => new SubjectSelect()
{
Code = u.Code,
FirstName = u.FirstName,
LastName = u.LastName,
Sex = u.Sex,
SubjectId = u.Id,
Age = u.Age,
MRN = u.MedicalNo,
Status = u.Status,
FirstGiveMedicineTime = u.FirstGiveMedicineTime
});
return query.ToList();
}
}
}
@@ -0,0 +1,276 @@
using IRaCIS.Core.Infrastructure.ExpressionExtend;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Application.Filter;
using Microsoft.AspNetCore.Mvc;
using IRaCIS.Core.Application.Contracts.Dicom.DTO;
using Microsoft.AspNetCore.Authorization;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.Interfaces;
namespace IRaCIS.Core.Application.Services
{
[ApiExplorerSettings(GroupName = "Trial")]
public class SubjectVisitService : BaseService, ISubjectVisitService
{
private readonly IRepository<SubjectVisit> _subjectVisitRepository;
public SubjectVisitService(IRepository<SubjectVisit> subjectVisitRepository)
{
_subjectVisitRepository = subjectVisitRepository;
}
[HttpPost]
[TypeFilter(typeof(TrialResourceFilter))]
[UnitOfWork]
public async Task<IResponseOutput> AddOrUpdateSV(SubjectVisitCommand svCommand)
{
var verifyExp1 = new EntityVerifyExp<SubjectVisit>()
{
VerifyExp = t => t.VisitNum == svCommand.VisitNum && t.SubjectId == svCommand.SubjectId,
VerifyMsg = "This subject's visit plan already contains a visit with the same visitnum."
};
var verifyExp2 = new EntityVerifyExp<SubjectVisit>()
{
VerifyExp = t => t.SubjectId == svCommand.SubjectId && t.IsFinalVisit,
VerifyMsg = "该受试者已经有访视设置为末次访视,不允许将该访视设置为末次访视",
IsVerify=svCommand.IsFinalVisit
};
#region
//var verifyExp2 = new EntityVerifyExp<SubjectVisit>()
//{
// verifyType = VerifyEnum.OnlyUpdate,
// VerifyExp = t => t.StudyList.Any(k => k.SubjectVisitId == svCommand.Id) && svCommand.SVSTDTC == null,
// VerifyMsg = "This visit is associated with the uploaded study images, and the visit start date is not allowed to be modified."
//};
//if (needDealEntity.SVSTDTC == null && needDealEntity.VisitExecuted == 1)
//{
// return ResponseOutput.NotOk("VisitExecuted must have Start Date ");
//}
//if (needDealEntity.IsOutEnromentVisit)
//{
// needDealEntity.VisitNum = _subjectVisitRepository.Where(t => t.SubjectId == svCommand.SubjectId).Max(t => t.VisitNum) + (decimal)0.1;
//}
//没有EndOfVisit
//if (svCommand.IsFinalVisit)
//{
// //if (svCommand.InPlan)
// //{
// await _repository.UpdateFromQueryAsync<Subject>(t => t.Id == svCommand.SubjectId, u => new Subject() { Status = SubjectStatus.EndOfVisit });
// //}
// //else
// //{
// // return ResponseOutput.NotOk("计划外访视不允许设置为最后一次访视");
// //}
//}
#endregion
svCommand.BlindName = "B" + ((int)(svCommand.VisitNum * 10)).ToString("D3");
var subject = (await _repository.FirstOrDefaultAsync<Subject>(t => t.Id == svCommand.SubjectId)).IfNullThrowException();
//不管是否 都是执行,只是失访的时候 设置Subject状态为失去访视
//if (svCommand.IsLostVisit)
//{
// svCommand.VisitExecuted = VisitExecutedEnum.Executed;
// subject.IsMissingImages = true;
//}
//else
//{
// svCommand.VisitExecuted = VisitExecutedEnum.Executed;
//}
svCommand.VisitExecuted = svCommand.IsLostVisit ? VisitExecutedEnum.Executed : svCommand.VisitExecuted;
//设置末次评估后,不允许添加计划外访视
if (svCommand.InPlan == false && svCommand.Id == null)
{
if (await _subjectVisitRepository.AnyAsync(t => t.SubjectId == svCommand.SubjectId && t.IsFinalVisit))
{
return ResponseOutput.NotOk("设置末次评估后,不允许添加计划外访视");
}
}
//更新的时候,返回的是数据库此时的实体数据
var needDealEntity = await _subjectVisitRepository.InsertOrUpdateAsync(svCommand, false, verifyExp1, verifyExp2);
if (svCommand.PDState != needDealEntity.PDState && svCommand.Id != null && needDealEntity.SubmitState == SubmitStateEnum.Submitted)
{
return ResponseOutput.NotOk("CRC提交后,不允许修改PD确认状态");
}
if (svCommand.PDState != needDealEntity.PDState && svCommand.Id != null && needDealEntity.RequestBackState == RequestBackStateEnum.PM_AgressBack)
{
return ResponseOutput.NotOk("回退的访视,不允许修改PD确认状态");
}
if (svCommand.IsFinalVisit)
{
if (await _subjectVisitRepository.AnyAsync(t => t.SubjectId == svCommand.SubjectId && t.VisitNum > svCommand.VisitNum && t.SubmitState == SubmitStateEnum.ToSubmit))
{
return ResponseOutput.NotOk("该受试者此访视后有影像上传,该访视不允许设置为末次访视");
}
subject.Status = SubjectStatus.OutOfVisit;
//末次访视后的 访视设置为不可用
await _subjectVisitRepository.UpdateFromQueryAsync(t => t.SubjectId == svCommand.SubjectId && t.VisitNum > svCommand.VisitNum, u => new SubjectVisit() { VisitExecuted = VisitExecutedEnum.Unavailable });
}
else
{
//回退
subject.Status = SubjectStatus.OnVisit;
await _subjectVisitRepository.UpdateFromQueryAsync(t => t.SubjectId == svCommand.SubjectId && t.VisitExecuted == VisitExecutedEnum.Unavailable, u => new SubjectVisit() { VisitExecuted = VisitExecutedEnum.UnExecuted });
}
//更新受试者 首次给药日期 是否入组确认
if (svCommand.SubjectFirstGiveMedicineTime != null)
{
subject.FirstGiveMedicineTime = svCommand.SubjectFirstGiveMedicineTime;
}
if (svCommand.IsEnrollmentConfirm != null)
{
await _subjectVisitRepository.UpdateFromQueryAsync(t => t.SubjectId == svCommand.SubjectId && t.IsBaseLine , u => new SubjectVisit() { IsEnrollmentConfirm = svCommand.IsEnrollmentConfirm.Value });
//needDealEntity.IsUrgent = true;
}
await _repository.SaveChangesAsync();
// 保存数据后,重新算下是否缺失影像 应对状态撤回
//if (svCommand.IsLostVisit == false)
//{
// var currentSubmittedVisitNum = await _repository.Where<SubjectVisit>(t => t.SubmitState == SubmitStateEnum.Submitted).MaxAsync(t => t.VisitNum);
// var isMissing = await _repository.AnyAsync<SubjectVisit>(t => (t.VisitNum < currentSubmittedVisitNum && t.SubmitState != SubmitStateEnum.Submitted) || t.IsLostVisit);
// await _repository.UpdateFromQueryAsync<Subject>(t => t.Id == svCommand.SubjectId, u => new Subject()
// {
// IsMissingImages = isMissing
// });
//}
return ResponseOutput.Ok();
}
[HttpPut("{trialId:guid}/{subjectVisitId:guid}/{isUrgent:bool}")]
public async Task<IResponseOutput> SetSubjectVisitUrgent(Guid subjectVisitId, bool isUrgent)
{
await _subjectVisitRepository.UpdateFromQueryAsync(t => t.Id == subjectVisitId, u => new SubjectVisit() { IsUrgent = isUrgent });
return ResponseOutput.Ok();
}
[HttpDelete, Route("{id:guid}/{trialId:guid}")]
[TrialAudit(AuditType.SubjectAudit, AuditOptType.DeleteSubjectOutPlanVisit)]
[TypeFilter(typeof(TrialResourceFilter))]
public async Task<IResponseOutput> DeleteSV(Guid id)
{
if (await _repository.AnyAsync<DicomStudy>(t => t.SubjectVisitId == id))
{
return ResponseOutput.NotOk("This visit is associated with the uploaded study images and couldn't be deleted.");
}
if (await _subjectVisitRepository.AnyAsync(t => t.Id == id && t.InPlan))
{
return ResponseOutput.NotOk("This visit is InPlan and couldn't be deleted.");
}
return ResponseOutput.Result(await _repository.DeleteFromQueryAsync<SubjectVisit>(s => s.Id == id));
}
/// <summary>
/// 设置受试者访视已执行 也就是将studyUploaded状态置为true 为了那些没有影像 人工设置准备
/// </summary>
/// <param name="subjectVisitId"></param>
/// <returns></returns>
[HttpPost("{subjectVisitId:guid}/{trialId:guid}")]
[TrialAudit(AuditType.SubjectAudit, AuditOptType.SetSVExecuted)]
[TypeFilter(typeof(TrialResourceFilter))]
public async Task<IResponseOutput> SetSVExecuted(Guid subjectVisitId)
{
return ResponseOutput.Result(await _subjectVisitRepository.UpdateFromQueryAsync(s => s.Id == subjectVisitId, u => new SubjectVisit() { VisitExecuted = VisitExecutedEnum.Executed }));
}
/// <summary>
/// 获取访视下的Dicom 检查信息 分所有的, 阅片的 不阅片 isReading : 0 查询所有 1 查询仅仅阅片的
/// </summary>
/// <param name="trialId"></param>
/// <param name="sujectVisitId"></param>
/// <param name="isReading"></param>
/// <returns></returns>
[HttpGet, Route("{trialId:guid}/{sujectVisitId:guid}/{isReading}")]
[AllowAnonymous]
public async Task<List<VisitStudyDTO>> GetVisitStudyList(Guid trialId, Guid sujectVisitId, int isReading)
{
var studyList = await _repository.Where<DicomStudy>(t => t.TrialId == trialId && t.SubjectVisitId == sujectVisitId).Select(k => new VisitStudyDTO()
{
InstanceCount = k.InstanceCount,
Modalities = k.Modalities,
SeriesCount = k.SeriesCount,
StudyCode = k.StudyCode,
StudyId = k.Id
}).ToListAsync();
var studyIds = studyList.Select(t => t.StudyId).ToList();
var instanceList = await _repository.Where<DicomInstance>(t => studyIds.Contains(t.StudyId))
.Select(t => new { t.SeriesId, t.Id, t.InstanceNumber }).ToListAsync();
foreach (var t in studyList)
{
t.SeriesList = await _repository.Where<DicomSeries>(s => s.StudyId == t.StudyId)
.WhereIf(isReading == 1, s => s.IsReading).OrderBy(s => s.SeriesNumber).
ThenBy(s => s.SeriesTime)
.ProjectTo<DicomSeriesDTO>(_mapper.ConfigurationProvider).ToListAsync();
t.SeriesList.ForEach(series => series.InstanceList = instanceList.Where(t => t.SeriesId == series.Id).OrderBy(t => t.InstanceNumber).Select(k => k.Id).ToList());
//设置为阅片与否 不更改数据库检查 的instance数量 和 SeriesCount 所以这里要实时统计
t.SeriesCount = t.SeriesList.Count();
t.InstanceCount = t.SeriesList.SelectMany(t => t.InstanceList).Count();
}
return studyList;
//return ResponseOutput.Ok(studyList.Where(t => t.SeriesList.Count > 0).ToList());
}
}
}
@@ -0,0 +1,437 @@
using IRaCIS.Core.Infrastructure.ExpressionExtend;
using IRaCIS.Application.Interfaces;
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Infra.EFCore;
using IRaCIS.Core.Application.Filter;
using Microsoft.AspNetCore.Mvc;
using IRaCIS.Core.Domain.Share;
using IRaCIS.Core.Application.MediatR.CommandAndQueries;
using Magicodes.ExporterAndImporter.Core;
using Magicodes.ExporterAndImporter.Excel;
using Magicodes.ExporterAndImporter.Excel.AspNetCore;
namespace IRaCIS.Application.Services
{
[ApiExplorerSettings(GroupName = "Trial")]
public class VisitPlanService : BaseService, IVisitPlanService
{
private readonly IRepository<VisitStage> _visitStageRepository;
private readonly IRepository<Trial> _trialRepository;
public VisitPlanService(IRepository<VisitStage> visitStageRepository,IRepository<Trial> trialRepository)
{
_visitStageRepository = visitStageRepository;
_trialRepository = trialRepository;
}
///暂时不用
/// <summary> 获取项目访视计划</summary>
public async Task<PageOutput<VisitStageDTO>> GetTrialVisitStageList(VisitPlanQueryDTO param)
{
var visitStageQuery = _visitStageRepository.Where(u => u.TrialId == param.TrialId)
.WhereIf(!string.IsNullOrWhiteSpace(param.Keyword), t => t.VisitName.Contains(param.Keyword))
.ProjectTo<VisitStageDTO>(_mapper.ConfigurationProvider);
return await visitStageQuery.ToPagedListAsync(param.PageIndex, param.PageSize, "CreateTime", param.Asc);
}
/// <summary> 根据项目Id,获取项目访视计划(不分页)[New]</summary>
[HttpGet("{trialId:guid}")]
public async Task<VisitPlanView> GetVisitStageList(Guid trialId)
{
var query = _visitStageRepository.Where(u => u.TrialId == trialId)
.ProjectTo<VisitStageDTO>(_mapper.ConfigurationProvider).OrderBy(t => t.VisitNum);
var list = await query.ToListAsync();
var trial = await _repository.FirstOrDefaultAsync<Trial>(t => t.Id == trialId).IfNullThrowException();
return new VisitPlanView()
{
VisitPlanList = list,
TimePointsPerPatient = trial.TimePointsPerPatient,
VisitPlanConfirmed = trial.VisitPlanConfirmed,
IsHaveFirstGiveMedicineDate = trial.IsHaveFirstGiveMedicineDate,
//SubjectHasAdded = _subjectVisitRepository.Any(t => t.TrialId == trialId)
};
}
/// <summary>
/// 获取访视计划下拉框列表
/// </summary>
/// <param name="trialId"></param>
/// <returns></returns>
[HttpGet("{trialId:guid}")]
public async Task<IEnumerable<VisitStageSelectDTO>> GetTrialVisitStageSelect(Guid trialId)
{
var query = _visitStageRepository.Where(u => u.TrialId == trialId)
.ProjectTo<VisitStageSelectDTO>(_mapper.ConfigurationProvider).OrderBy(t => t.VisitNum);
var list = await query.ToListAsync();
return list;
}
/// <summary> 添加或更新访视计划某项[New]</summary>
[HttpPost]
[UnitOfWork]
public async Task<IResponseOutput> AddOrUpdateVisitStage(VisitPlanCommand visitPlan)
{
if (!await _repository.Where<Trial>(t => t.Id == visitPlan.TrialId).IgnoreQueryFilters().AnyAsync(t => t.TrialStatusStr == StaticData.TrialOngoing || t.TrialStatusStr == StaticData.TrialInitializing))
{
return ResponseOutput.NotOk(" only in Initializing or Ongoing State can operate ");
}
var visitPlanList = await _visitStageRepository.Where(t => t.TrialId == visitPlan.TrialId)
.Select(t => new { t.Id, t.VisitNum, t.VisitDay }).OrderBy(t => t.VisitNum).ToListAsync();
//更新的时候,需要排除自己
if (visitPlan.Id != null)
{
visitPlanList = visitPlanList.Where(t => t.Id != visitPlan.Id).ToList();
}
if (visitPlanList.Any(t => t.VisitNum < visitPlan.VisitNum))
{
//比当前 visitNum小的 visitDay的最大值 还小 不允许添加
if (visitPlan.VisitDay <= visitPlanList.Where(t => t.VisitNum < visitPlan.VisitNum).Select(t => t.VisitDay).Max())
{
return ResponseOutput.NotOk("For the visit plan, the VisitDay with a larger VisitNum should be larger than the VisitDay with a smaller VisitNum.");
}
}
if (visitPlanList.Any(t => t.VisitNum > visitPlan.VisitNum))
{
if (visitPlan.VisitDay >= visitPlanList.Where(t => t.VisitNum > visitPlan.VisitNum).Select(t => t.VisitDay).Min())
{
return ResponseOutput.NotOk("For the visit plan, the VisitDay with a larger VisitNum should be larger than the VisitDay with a smaller VisitNum.");
}
}
if (visitPlan.Id == Guid.Empty || visitPlan.Id == null)//add
{
if (await _visitStageRepository.AnyAsync(t => t.TrialId == visitPlan.TrialId && (t.VisitName == visitPlan.VisitName || t.VisitNum == visitPlan.VisitNum)))
{
return ResponseOutput.NotOk("A visit with the same VisitName/VisitNum already existed in the current visit plan.");
}
if (await _visitStageRepository.AnyAsync(t => t.TrialId == visitPlan.TrialId && t.IsBaseLine) && visitPlan.IsBaseLine)
{
return ResponseOutput.NotOk("A visit already is baseline in the current visit plan.");
}
//已添加受试者 都不存在该新增的计划名称 那么该项目所有受试者都增加一个访视记录
if (!await _repository.AnyAsync<SubjectVisit>(t => t.VisitName == visitPlan.VisitName && t.TrialId == visitPlan.TrialId))
{
var subjectSVS = await _repository.Where<SubjectVisit>(t => t.TrialId == visitPlan.TrialId).Select(t => new { t.SubjectId, t.SiteId,t.IsFinalVisit }).Distinct().ToListAsync();
var subjects = subjectSVS.Select(t => new { t.SubjectId, t.SiteId }).Distinct().ToList();
foreach (var subject in subjects)
{
var svItem = _mapper.Map<SubjectVisit>(visitPlan);
svItem.SiteId = subject.SiteId;
svItem.SubjectId = subject.SubjectId;
//设置了末次访视,那么加访视计划的时候,设置为不可用
if (subjectSVS.Any(t => t.SubjectId == svItem.SubjectId && t.IsFinalVisit))
{
svItem.VisitExecuted = VisitExecutedEnum.Unavailable;
}
await _repository.AddAsync(svItem);
}
}
var visitPlanItem = _mapper.Map<VisitStage>(visitPlan);
visitPlanItem.BlindName = "B" + ((int)visitPlanItem.VisitNum * 10).ToString("D3");
var result = await _repository.AddAsync(visitPlanItem);
//更新项目访视计划状态为未确认
await _repository.UpdateFromQueryAsync<Trial>(u => u.Id == visitPlan.TrialId, t => new Trial() { VisitPlanConfirmed = false });
await _repository.SaveChangesAsync();
return ResponseOutput.Ok(result.Id.ToString());
}
else//update
{
if (await _visitStageRepository.AnyAsync(t => t.TrialId == visitPlan.TrialId && (t.VisitName == visitPlan.VisitName || t.VisitNum == visitPlan.VisitNum) && t.Id != visitPlan.Id))
{
return ResponseOutput.NotOk("A visit with the same VisitName/VisitNum already existed in the current visit plan.");
}
if (await _visitStageRepository.AnyAsync(t => t.TrialId == visitPlan.TrialId && t.IsBaseLine && t.Id != visitPlan.Id) && visitPlan.IsBaseLine)
{
return ResponseOutput.NotOk("A visit already is baseline in the current visit plan.");
}
var stage = await _visitStageRepository.FirstOrDefaultAsync(t => t.Id == visitPlan.Id);
if (stage == null) return Null404NotFound(stage);
//修改是否是基线
if (stage.IsBaseLine && stage.IsBaseLine != visitPlan.IsBaseLine)
{
if (await _repository.Where<SubjectVisit>(t => t.TrialId == visitPlan.TrialId).AnyAsync(v => v.IsBaseLine && v.SubmitState >= SubmitStateEnum.ToSubmit))
{
return ResponseOutput.NotOk("有CRC已经为基线上传了影像数据,不允许修改基线");
}
}
_mapper.Map(visitPlan, stage);
stage.BlindName = "B" + ((int)visitPlan.VisitNum * 10).ToString("D3");
//更新项目访视计划状态为未确认
await _repository.UpdateFromQueryAsync<Trial>(u => u.Id == visitPlan.TrialId, t => new Trial() { VisitPlanConfirmed = false });
await _repository.SaveChangesAsync();
return ResponseOutput.Ok();
}
}
/// <summary> 删除项目计划某一项[New]</summary>
[HttpDelete("{id:guid}/{trialId:guid}")]
[TrialAudit(AuditType.TrialAudit, AuditOptType.DeleteTrialVisitPlanItem)]
[TypeFilter(typeof(TrialResourceFilter))]
public async Task<IResponseOutput> DeleteVisitStage(Guid id)
{
var visitPlan = await _visitStageRepository.FirstOrDefaultAsync(t => t.Id == id);
if (visitPlan == null) return Null404NotFound(visitPlan);
if (await _repository.AnyAsync<SubjectVisit>(t => t.VisitName == visitPlan.VisitName && t.TrialId == visitPlan.TrialId && t.VisitExecuted == VisitExecutedEnum.Executed))
{
return ResponseOutput.NotOk("The visit plan has been assigned to the subjects and executed.");
}
await _repository.DeleteFromQueryAsync<SubjectVisit>(t => t.TrialId == visitPlan.TrialId && t.VisitName == visitPlan.VisitName);
var result = await _visitStageRepository.DeleteFromQueryAsync(u => u.Id == id);
return ResponseOutput.Result(result);
}
[UnitOfWork]
[HttpPost("{trialId:guid}")]
[TrialAudit(AuditType.TrialAudit, AuditOptType.ConfirmTrialVisitPlan)]
public async Task<IResponseOutput> ConfirmTrialVisitPlan(Guid trialId, [FromServices] IRepository<VisitPlanInfluenceStat> _influnceStatRepository)
{
if (!await _trialRepository.AnyAsync(t => t.Id == trialId &&( t.TrialStatusStr==StaticData.TrialInitializing || t.TrialStatusStr == StaticData.TrialOngoing)))
{
return ResponseOutput.NotOk("仅仅在项目初始化或者进行中时,才允许修改确认");
}
if (!await _visitStageRepository.AnyAsync(t => t.TrialId == trialId && t.IsBaseLine))
{
return ResponseOutput.NotOk("没有基线,不允许确认");
}
if (!await _trialRepository.AnyAsync(t => t.Id == trialId && t.IsTrialBasicLogicConfirmed && t.IsTrialProcessConfirmed && t.IsTrialUrgentConfirmed))
{
return ResponseOutput.NotOk("项目配置未确认,不允许确认访视计划");
}
var svList = await _visitStageRepository.Where(t => t.TrialId == trialId).Select(u => new { u.VisitDay, u.IsBaseLine }).ToListAsync();
if (svList.Min(t => t.VisitDay) != svList.Where(t => t.IsBaseLine).FirstOrDefault()?.VisitDay)
{
return ResponseOutput.NotOk("基线VisitDay 不是最小的, 不允许确认");
}
//更新项目访视计划状态为已确认
await _trialRepository.UpdateFromQueryAsync(u => u.Id == trialId, t => new Trial() { VisitPlanConfirmed = true });
//找到访视计划修改的Item
var changedList = await _visitStageRepository.Where(t => t.TrialId == trialId && t.IsConfirmed == false)
.Select(t => new { t.Trial.IsHaveFirstGiveMedicineDate, t.Id, t.VisitName, t.VisitWindowLeft, t.VisitWindowRight, t.VisitDay, t.VisitNum, t.IsBaseLine }).ToListAsync();
//访视计划 整体状态变更为 确认
await _visitStageRepository.UpdateFromQueryAsync(u => u.TrialId == trialId, t => new VisitStage() { IsConfirmed = true });
var stat = new VisitPlanInfluenceStat() { TrialId = trialId };
foreach (var changedItem in changedList)
{
//找到该项目 访视已经执行,并且配置了有首次给药日期 并且更新后超窗的访视,要把超窗之前的值也要查询出来
var qcPassedVisitList = await _repository.Where<SubjectVisit>(t => t.TrialId == trialId
&& t.VisitExecuted == VisitExecutedEnum.Executed
&& t.AuditState == AuditStateEnum.QCPassed
&& t.Trial.IsHaveFirstGiveMedicineDate == true
&& t.VisitStageId == changedItem.Id
&& t.Subject.FirstGiveMedicineTime != null
).Select(k => new
{
SubjectVisitId = k.Id,
SelfWindowLeft = k.Subject.FirstGiveMedicineTime!.Value.AddDays(k.VisitDay + k.VisitWindowLeft),
SelfWindowRight = k.Subject.FirstGiveMedicineTime!.Value.AddDays(k.VisitDay + k.VisitWindowRight + 1).AddSeconds(-1),
NowWindowLeft = k.Subject.FirstGiveMedicineTime!.Value.AddDays(changedItem.VisitDay + changedItem.VisitWindowLeft),
NowWindowRight = k.Subject.FirstGiveMedicineTime!.Value.AddDays(changedItem.VisitDay + changedItem.VisitWindowRight + 1).AddSeconds(-1),
NoneDicomStudyList =
k.NoneDicomStudyList //之前是查询调整之后超窗的 现在调整前超窗 调整后 没超窗的也要记录
//.Where(study => study.ImageDate <k.Subject.FirstGiveMedicineTime.Value.AddDays(changedItem.VisitDay + changedItem.VisitWindowLeft??0)
//|| study.ImageDate >k.Subject.FirstGiveMedicineTime.Value.AddDays(changedItem.VisitDay + changedItem.VisitWindowRight??0 + 1).AddSeconds(-1))
.Select(t => new { NoneDicomStudyId = t.Id, t.Modality, StudyTime = t.ImageDate }),
DicomStudyList = k.StudyList
//.Where(study => study.StudyTime <k.Subject.FirstGiveMedicineTime.Value.AddDays(changedItem.VisitDay + changedItem.VisitWindowLeft??0)
// || study.StudyTime >k.Subject.FirstGiveMedicineTime.Value.AddDays(changedItem.VisitDay + changedItem.VisitWindowRight??0 + 1).AddSeconds(-1))
.Select(t => new { StudyId = t.Id, Modality = t.Modalities, t.StudyTime })
}).ToListAsync();
foreach (var visit in qcPassedVisitList)
{
//找到本身没有超窗的数据 修改后超窗的
visit.DicomStudyList.Where(t => (t.StudyTime > visit.SelfWindowLeft && t.StudyTime < visit.SelfWindowRight) && (t.StudyTime < visit.NowWindowLeft || t.StudyTime > visit.NowWindowRight)).ForEach(t =>
{
stat.InconsistentCount++;
stat.InfluenceStudyList.Add(new VisitPlanInfluenceStudy()
{
IsOverWindowNowNotOverWindow = false,
Modality = t.Modality,
SubjectVisitId = visit.SubjectVisitId,
StudyId = t.StudyId,
IsDicomStudy = true,
StudyTime = t.StudyTime,
TrialId = trialId,
HistoryWindow = visit.SelfWindowLeft.ToString("yyyy-MM-dd") + " ~ " + visit.SelfWindowRight.ToString("yyyy-MM-dd"),
NowWindow = visit.NowWindowLeft.ToString("yyyy-MM-dd") + " ~ " + visit.NowWindowRight.ToString("yyyy-MM-dd")
});
});
visit.NoneDicomStudyList.Where(t => (t.StudyTime > visit.SelfWindowLeft && t.StudyTime < visit.SelfWindowRight) && (t.StudyTime < visit.NowWindowLeft || t.StudyTime > visit.NowWindowRight)).ForEach(t =>
{
stat.InconsistentCount++;
stat.InfluenceStudyList.Add(new VisitPlanInfluenceStudy()
{
IsOverWindowNowNotOverWindow = false,
Modality = t.Modality,
SubjectVisitId = visit.SubjectVisitId,
StudyId = t.NoneDicomStudyId,
IsDicomStudy = false,
StudyTime = t.StudyTime,
TrialId = trialId,
HistoryWindow = visit.SelfWindowLeft.ToString("yyyy-MM-dd") + " ~ " + visit.SelfWindowRight.ToString("yyyy-MM-dd"),
NowWindow = visit.NowWindowLeft.ToString("yyyy-MM-dd") + " ~ " + visit.NowWindowRight.ToString("yyyy-MM-dd")
});
});
//本身超窗 修改后没超窗的
visit.DicomStudyList.Where(t => (t.StudyTime < visit.SelfWindowLeft || t.StudyTime > visit.SelfWindowRight) && (t.StudyTime > visit.NowWindowLeft && t.StudyTime < visit.NowWindowRight)).ForEach(t =>
{
stat.InconsistentCount++;
stat.InfluenceStudyList.Add(new VisitPlanInfluenceStudy()
{
IsOverWindowNowNotOverWindow = true,
Modality = t.Modality,
SubjectVisitId = visit.SubjectVisitId,
StudyId = t.StudyId,
IsDicomStudy = true,
StudyTime = t.StudyTime,
TrialId = trialId,
HistoryWindow = visit.SelfWindowLeft.ToString("yyyy-MM-dd") + " ~ " + visit.SelfWindowRight.ToString("yyyy-MM-dd"),
NowWindow = visit.NowWindowLeft.ToString("yyyy-MM-dd") + " ~ " + visit.NowWindowRight.ToString("yyyy-MM-dd")
});
});
visit.NoneDicomStudyList.Where(t => (t.StudyTime < visit.SelfWindowLeft || t.StudyTime > visit.SelfWindowRight) && (t.StudyTime > visit.NowWindowLeft && t.StudyTime < visit.NowWindowRight)).ForEach(t =>
{
stat.InconsistentCount++;
stat.InfluenceStudyList.Add(new VisitPlanInfluenceStudy()
{
IsOverWindowNowNotOverWindow = true,
Modality = t.Modality,
SubjectVisitId = visit.SubjectVisitId,
StudyId = t.NoneDicomStudyId,
IsDicomStudy = false,
StudyTime = t.StudyTime,
TrialId = trialId,
HistoryWindow = visit.SelfWindowLeft.ToString("yyyy-MM-dd") + " ~ " + visit.SelfWindowRight.ToString("yyyy-MM-dd"),
NowWindow = visit.NowWindowLeft.ToString("yyyy-MM-dd") + " ~ " + visit.NowWindowRight.ToString("yyyy-MM-dd")
});
});
}
//变更某一访视计划Item 受试者访视相关字段
await _repository.UpdateFromQueryAsync<SubjectVisit>(t => t.TrialId == trialId && t.VisitStageId == changedItem.Id, k => new SubjectVisit()
{
IsBaseLine = changedItem.IsBaseLine,
VisitName = changedItem.VisitName,
VisitNum = changedItem.VisitNum,
VisitDay = changedItem.VisitDay,
VisitWindowLeft = changedItem.VisitWindowLeft,
VisitWindowRight = changedItem.VisitWindowRight
});
}
await _repository.AddAsync(stat);
await _repository.SaveChangesAsync();
return ResponseOutput.Ok();
}
[HttpGet("{trialId:guid}")]
public async Task<List<VisitPlanInfluenceSubjectVisitStatDTO>> GetInfluenceHistoryList(Guid trialId, [FromServices] IRepository<VisitPlanInfluenceStat> _influnceStatRepository)
{
var list = await _influnceStatRepository.Where(t => t.TrialId == trialId).ProjectTo<VisitPlanInfluenceSubjectVisitStatDTO>(_mapper.ConfigurationProvider).OrderByDescending(t => t.CreateTime).ToListAsync();
return list;
}
[HttpGet("{visitPlanInfluenceStatId:guid}")]
public async Task<IActionResult> DownloadInflunceStudyList(Guid visitPlanInfluenceStatId, [FromServices] IRepository<VisitPlanInfluenceStudy> _influnceRepository)
{
var list = _influnceRepository.Where(t => t.VisitPlanInfluenceStatId == visitPlanInfluenceStatId)
.ProjectTo<VisitPlanInfluenceSubjectVisitDTO>(_mapper.ConfigurationProvider).ToList();
IExporter exporter = new ExcelExporter();
var result = await exporter.ExportAsByteArray(list);
return new XlsxFileResult(bytes: result, fileDownloadName: $"检查导出_{DateTime.Now.ToString("yyyy-MM-dd:hh:mm:ss")}.xlsx");
}
}
}
@@ -0,0 +1,95 @@
using AutoMapper;
using IRaCIS.Application.Contracts;
using IRaCIS.Core.Application.Contracts;
using IRaCIS.Core.Application.MediatR.CommandAndQueries;
using IRaCIS.Core.Domain.Share;
namespace IRaCIS.Core.Application.Service
{
public class VisitMapConfig : Profile
{
public VisitMapConfig()
{
CreateMap<VisitPlanCommand, VisitStage>();
CreateMap<VisitPlanCommand, SubjectVisit>();
CreateMap<SubjectCommand, Subject>();
CreateMap<VisitStage, VisitStageDTO>();
CreateMap<VisitStage, VisitStageSelectDTO>();
CreateMap<VisitStage, SubjectVisit>()
.ForMember(d => d.Id, t => t.Ignore())
.ForMember(d => d.VisitStageId, t =>t.MapFrom(u=>u.Id));
CreateMap<VisitPlanInfluenceStat, VisitPlanInfluenceSubjectVisitStatDTO>()
.ForMember(d => d.CreateUser, u => u.MapFrom(g => g.CreateUser.LastName + " / " + g.CreateUser.FirstName));
CreateMap<SubjectStatusChangeCommand, Subject>();
CreateMap<VisitStage, VisitStageDTO>();
CreateMap<Subject, SubjectQueryView>()
.ForMember(d => d.SiteName, u => u.MapFrom(s => s.TrialSite.Site.SiteName))
.ForMember(d => d.TrialSiteCode, u => u.MapFrom(s => s.TrialSite.TrialSiteCode))
.ForMember(d => d.LatestBlindName, u => u.MapFrom(s => s.LatestSubjectVisit.BlindName))
.ForMember(d => d.LatestVisitName, u => u.MapFrom(s => s.LatestSubjectVisit.VisitName))
//.ForMember(d => d.IsSubjectSexView, u => u.MapFrom(s => s.Trial.IsSubjectSexView))
//.ForMember(d => d.IsSubjectExpeditedView, u => u.MapFrom(s => s.Trial.IsSubjectExpeditedView))
//不能对包含聚合或子查询的表达式执行聚合函数
//.ForMember(d => d.InPlanStudyCount, u => u.MapFrom(s => s.SubjectVisitList.Where(t => t.InPlan).Sum(k => k.StudyList.Count())))
//.ForMember(d => d.OutPlanStudyCount, u => u.MapFrom(s => s.SubjectVisitList.Where(t => t.InPlan==false).Sum(k => k.StudyList.Count())))
//.ForMember(d => d.InPlanDicomStudyUploadCount, u => u.MapFrom(s => s.SubjectVisitList.Where(t => t.InPlan).SelectMany(k => k.StudyList).Count()))
//.ForMember(d => d.OutPlanDicomStudyUploadCount, u => u.MapFrom(s => s.SubjectVisitList.Where(t => t.InPlan == false).SelectMany(k => k.StudyList).Count()))
//.ForMember(d => d.InPlanNoneDicomStudyUploadCount, u => u.MapFrom(s => s.SubjectVisitList.Where(t => t.InPlan).SelectMany(k => k.NoneDicomStudyList).Count()))
//.ForMember(d => d.OutPlanNoneDicomStudyUploadCount, u => u.MapFrom(s => s.SubjectVisitList.Where(t => t.InPlan == false).SelectMany(k => k.NoneDicomStudyList).Count()))
.ForMember(d => d.FinalSubjectVisitId, u => u.MapFrom(s => s.SubjectVisitList.Where(t => t.IsFinalVisit).Select(c=>(Guid?) c.Id).FirstOrDefault()))
.ForMember(d => d.InPlanVisitCount, u => u.MapFrom(s => s.SubjectVisitList.Count(t => t.InPlan)))
.ForMember(d => d.OutPlanVisitCount, u => u.MapFrom(s => s.SubjectVisitList.Count(t => t.InPlan == false)))
//执行不一定上传了 可能是失访 实际执行过了
.ForMember(d => d.MissingSubmmitCount, u => u.MapFrom(s => s.SubjectVisitList.Count(t => t.VisitNum <s.LatestSubjectVisit.VisitNum && t.SubmitState != SubmitStateEnum.Submitted && t.IsLostVisit == false)))
.ForMember(d => d.InPlanVisitSubmmitCount, u => u.MapFrom(s => s.SubjectVisitList.Count(t => t.SubmitState == SubmitStateEnum.Submitted && t.InPlan == true)))
.ForMember(d => d.LostVisitCount, u => u.MapFrom(s => s.SubjectVisitList.Count(t => t.IsLostVisit)))
.ForMember(d => d.InPlanVisitSubmmitCount, u => u.MapFrom(s => s.SubjectVisitList.Count(t => t.SubmitState == SubmitStateEnum.Submitted && t.InPlan == true)))
.ForMember(d => d.OutPlanVisitSubmmitCount, u => u.MapFrom(s => s.SubjectVisitList.Count(t => t.SubmitState == SubmitStateEnum.Submitted && t.InPlan == false)));
// .ForMember(d => d.InPlanVisitSubmmitCount, u => u.MapFrom(s => s.SubjectVisitList.Count(t => t.SubmitState>=SubmitStateEnum.ToSubmit && t.InPlan == true)))
//.ForMember(d => d.OutPlanVisitSubmmitCount, u => u.MapFrom(s => s.SubjectVisitList.Count(t => t.SubmitState >= SubmitStateEnum.ToSubmit && t.InPlan == false)));
//.ForMember(d => d.InPlanVisitUploadCount, u => u.MapFrom(s => s.SubjectVisitList.Count(t => t.VisitExecuted == VisitExecutedEnum.Executed && t.InPlan)))
//.ForMember(d => d.OutPlanVisitUploadCount, u => u.MapFrom(s => s.SubjectVisitList.Count(t => t.VisitExecuted == VisitExecutedEnum.Executed && t.InPlan == false)));
//审计信息 这里不用IncludeMembers 也可以识别 是以导航属性名称开头
// 还有 外键? COALESCE([t0].[SubjectId], '00000000-0000-0000-0000-000000000000') 因为destination 是Guid
CreateMap<TrialAudit, AuditDTO>()
.ForMember(d => d.SubjectName, u => u.MapFrom(s => s.Subject.LastName + " / " + s.Subject.FirstName))
.ForMember(d => d.SubjectCode, u => u.MapFrom(s => s.Subject.Code));
CreateMap<SubjectVisitCommand, SubjectVisit>().ForAllMembers(opt => opt.Condition((src, dest, srcMember) => srcMember != null));
CreateMap<SubjectVisit, DicomTrialSiteSubjectInfo>().IncludeMembers(t=>t.Trial)
.ForMember(d => d.SiteName, u => u.MapFrom(s => s.Site.SiteName))
.ForMember(d => d.SiteCode, u => u.MapFrom(s => s.Site.SiteCode))
.ForMember(d => d.SubjectVisitId, u => u.MapFrom(s => s.Id))
.ForMember(d => d.TrialSiteCode, u => u.MapFrom(s => s.TrialSite.TrialSiteCode))
.ForMember(d => d.TrialCode, u => u.MapFrom(s => s.Trial.TrialCode))
.ForMember(d => d.Sponsor, u => u.MapFrom(s => s.Trial.Sponsor.SponsorName));
CreateMap<Trial, DicomTrialSiteSubjectInfo>();
CreateMap<VisitPlanInfluenceStudy, VisitPlanInfluenceSubjectVisitDTO>()
.ForMember(d => d.SubjectVisitId, u => u.MapFrom(s => s.Id))
.ForMember(d => d.SubjectCode, u => u.MapFrom(s => s.SubjectVisit.Subject.Code))
.ForMember(d => d.VisitName, u => u.MapFrom(s => s.SubjectVisit.VisitName))
.ForMember(d => d.TrialSiteCode, u => u.MapFrom(s => s.SubjectVisit.TrialSite.TrialSiteCode));
}
}
}