diff --git a/IRaCIS.Core.Application/Helper/HangfireJobHelper.cs b/IRaCIS.Core.Application/Helper/HangfireJobHelper.cs index c037fbd05..d57122b86 100644 --- a/IRaCIS.Core.Application/Helper/HangfireJobHelper.cs +++ b/IRaCIS.Core.Application/Helper/HangfireJobHelper.cs @@ -91,6 +91,7 @@ namespace IRaCIS.Core.Application.Helper case EmailBusinessScenario.GeneralTraining_ExpirationNotification: HangfireJobHelper.AddOrUpdateCronJob(jobId, t => t.Send(new SystemDocumentErverDayEvent() { }, default), emailCron); + HangfireJobHelper.AddOrUpdateCronJob(jobId, t => t.Send(new TrialDocumentErverDayEvent() { }, default), emailCron); break; default: diff --git a/IRaCIS.Core.Application/MassTransit/Recurring/Schedule/RecurringEvent.cs b/IRaCIS.Core.Application/MassTransit/Recurring/Schedule/RecurringEvent.cs index 31f3fb943..c9d469d26 100644 --- a/IRaCIS.Core.Application/MassTransit/Recurring/Schedule/RecurringEvent.cs +++ b/IRaCIS.Core.Application/MassTransit/Recurring/Schedule/RecurringEvent.cs @@ -58,3 +58,24 @@ public class SystemDocumentPublishEvent : DomainEvent } +/// +/// 定时提醒 +/// +public class TrialDocumentErverDayEvent : DomainEvent +{ + +} + + +public class TrialDocumentPublishEvent : DomainEvent +{ + public List Ids { get; set; } + + /// + /// 新增的需要发送邮件的用户角色ID列表 + /// 如果为null或空,则发送给所有相关角色 + /// + public List NewUserTypeIds { get; set; } +} + + diff --git a/IRaCIS.Core.Application/MassTransit/Recurring/TrialDocumentConsumer.cs b/IRaCIS.Core.Application/MassTransit/Recurring/TrialDocumentConsumer.cs new file mode 100644 index 000000000..b0e97c149 --- /dev/null +++ b/IRaCIS.Core.Application/MassTransit/Recurring/TrialDocumentConsumer.cs @@ -0,0 +1,292 @@ +using DocumentFormat.OpenXml; +using IRaCIS.Application.Contracts; +using IRaCIS.Core.Application.Contracts; +using IRaCIS.Core.Application.Helper; +using IRaCIS.Core.Application.MassTransit.Consumer; +using IRaCIS.Core.Application.Service.Reading.Dto; +using IRaCIS.Core.Domain.Models; +using MassTransit; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using MimeKit; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reactive.Joins; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace IRaCIS.Core.Application.MassTransit.Recurring +{ + + /// + /// 定时过期提醒 + /// + public class TrialDocumentErverDayEventConsumer( + IRepository _trialReadingCriterionRepository, + IRepository _visitTaskRepository, + IRepository _systemDocumentRepository, + IRepository _identityUserRepository, + IRepository _systemDocConfirmedUserRepository, + IRepository _dictionaryRepository, + IRepository _trialUserRoleRepository, IRepository _trialDocumentRepository, + IRepository _trialRepository, + ISystemDocumentService _systemDocumentService, + IRepository _systemDocNeedConfirmedUserTypeRepository, + IRepository _trialDocNeedConfirmedUserTypeRepository, + IServiceScopeFactory serviceScopeFactory, + IRepository _trialIdentityUserRepository, + IRepository _trialDocConfirmedUserRepository, + IRepository _readingQuestionCriterionTrialRepository, + IRepository _emailNoticeConfigrepository, + + IOptionsMonitor systemEmailConfig) : IConsumer + { + private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue; + + public async Task Consume(ConsumeContext context) + { + + DateTime now = DateTime.Now; + Console.WriteLine("发送定时项目过期提醒"); + var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US; + var trialDocQuery = + from trialDoc in _trialDocumentRepository.AsQueryable(true) + join trialUser in _trialIdentityUserRepository.AsQueryable() on trialDoc.TrialId equals trialUser.TrialId + join confirm in _trialDocConfirmedUserRepository.Where() on + new { trialUser.IdentityUserId, TrialDocumentId = trialDoc.Id } equals new { IdentityUserId = confirm.ConfirmUserId, confirm.TrialDocumentId } into cc + + from confirm in cc.DefaultIfEmpty() + select new TrialSignDocView() + { + TrialCode = trialDoc.Trial.TrialCode, + ResearchProgramNo = trialDoc.Trial.ResearchProgramNo, + ExperimentName = trialDoc.Trial.ExperimentName, + CurrentStaffTrainDays = trialDoc.CurrentStaffTrainDays, + NewStaffTrainDays = trialDoc.NewStaffTrainDays, + Id = trialDoc.Id, + IsSystemDoc = false, + CreateTime = trialDoc.CreateTime, + FullFilePath = trialDoc.Path, + IsDeleted = trialDoc.IsDeleted, + Name = trialDoc.Name, + Path = trialDoc.Path, + FileTypeId = trialDoc.FileTypeId, + UpdateTime = trialDoc.UpdateTime, + SignViewMinimumMinutes = trialDoc.SignViewMinimumMinutes, + //IsConfirmed = confirm.ConfirmTime != null, + ConfirmUserId = confirm.ConfirmUserId, + ConfirmTime = confirm.ConfirmTime, + RealName = trialUser.IdentityUser.FullName, + UserName = trialUser.IdentityUser.UserName, + + IdentityUserTypeList = trialUser.TrialUserRoleList.Select(t => t.UserRole.UserTypeRole.UserTypeShortName).ToList(), + + DocNeedSignUserTypeList = trialDoc.NeedConfirmedUserTypeList.Select(t => t.UserTypeRole.UserTypeShortName).ToList(), + }; + var datalist = await trialDocQuery.IgnoreQueryFilters().Where(t => t.IsDeleted == false && t.ConfirmTime == null) + .ToListAsync(); + datalist = datalist.Where(x => x.SuggestFinishTime != null && x.SuggestFinishTime.Value.Date == DateTime.Now.Date) + .Where(x => x.IsNeedSendEmial).ToList(); + var confirmUserIdList = datalist.Select(t => t.ConfirmUserId).Distinct().ToList(); + var userinfoList = await _identityUserRepository.Where(x => confirmUserIdList.Contains(x.Id)).ToListAsync(); + + Console.WriteLine("发送定时项目过期提醒:人员数量" + userinfoList.Count); + int index = 1; + foreach (var userinfo in userinfoList) + { + try + { + Console.WriteLine($"{index}发送定时过期提醒,邮箱:{userinfo.EMail},姓名{userinfo.UserName}"); + index++; + var messageToSend = new MimeMessage(); + //发件地址 + messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail)); + messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail)); + + + + var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN; + Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input => + { + var topicStr = string.Format(input.topicStr, companyName); + + var htmlBodyStr = string.Format( + CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr), + userinfo.UserName, // 用户名 {0} + _systemEmailConfig.SiteUrl + ); + + return (topicStr, htmlBodyStr); + }; + + var scenario = EmailBusinessScenario.GeneralTraining_ExpirationNotification; + + var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault(); + + if (emailConfig != null) + { + await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc); + + await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig); + } + + + } + catch (Exception) + { + + } + + } + + + } + } + + /// + /// 生效通知 + /// + public class TrialDocumentPublishEventConsumer( + IRepository _trialReadingCriterionRepository, + IRepository _visitTaskRepository, + IRepository _trialDocumentRepository, + IRepository _identityUserRepository, + + IRepository _dictionaryRepository, + IRepository _trialUserRoleRepository, + IRepository _emailNoticeConfigrepository, + + IOptionsMonitor systemEmailConfig) : IConsumer + { + private readonly SystemEmailSendConfig _systemEmailConfig = systemEmailConfig.CurrentValue; + + public async Task Consume(ConsumeContext context) + { + var isEn_US = CultureInfo.CurrentCulture.Name == StaticData.CultureInfo.en_US; + + // 记录是否只发送给新增角色的日志 + if (context.Message.NewUserTypeIds != null && context.Message.NewUserTypeIds.Any()) + { + Console.WriteLine($"只发送给新增项目的角色,角色数量: {context.Message.NewUserTypeIds.Count}"); + } + // 构建查询 + IQueryable systemDocQuery; + + if (context.Message.NewUserTypeIds != null && context.Message.NewUserTypeIds.Any()) + { + // 只查询新增角色的用户 + systemDocQuery = + from trialDoc in _trialDocumentRepository.AsQueryable(false).Where(x => context.Message.Ids.Contains(x.Id)) + from identityUser in _identityUserRepository.AsQueryable(false) + .Where(t => t.Status == UserStateEnum.Enable && + t.UserRoleList.Where(t => t.IsUserRoleDisabled == false) + .Any(t => context.Message.NewUserTypeIds.Contains(t.UserTypeId) && + trialDoc.NeedConfirmedUserTypeList.AsQueryable().Any(c => c.NeedConfirmUserTypeId == t.UserTypeId))) + select new UnionDocumentWithConfirmInfoView() + { + IsSystemDoc = true, + Id = trialDoc.Id, + CreateTime = trialDoc.CreateTime, + IsDeleted = trialDoc.IsDeleted, + SignViewMinimumMinutes = trialDoc.SignViewMinimumMinutes, + Name = trialDoc.Name, + Path = trialDoc.Path, + FileTypeId = trialDoc.FileTypeId, + UpdateTime = trialDoc.UpdateTime, + ConfirmUserId = identityUser.Id, + RealName = identityUser.FullName, + UserName = identityUser.UserName, + IsNeedSendEmial = identityUser.IsZhiZhun, + FullFilePath = trialDoc.Path + }; + } + else + { + // 查询所有相关角色的用户 + systemDocQuery = + from trialDoc in _trialDocumentRepository.AsQueryable(false).Where(x => context.Message.Ids.Contains(x.Id)) + from identityUser in _identityUserRepository.AsQueryable(false) + .Where(t => t.Status == UserStateEnum.Enable && + t.UserRoleList.Where(t => t.IsUserRoleDisabled == false) + .Any(t => trialDoc.NeedConfirmedUserTypeList.AsQueryable().Any(c => c.NeedConfirmUserTypeId == t.UserTypeId))) + select new UnionDocumentWithConfirmInfoView() + { + IsSystemDoc = true, + Id = trialDoc.Id, + CreateTime = trialDoc.CreateTime, + IsDeleted = trialDoc.IsDeleted, + SignViewMinimumMinutes = trialDoc.SignViewMinimumMinutes, + Name = trialDoc.Name, + Path = trialDoc.Path, + FileTypeId = trialDoc.FileTypeId, + UpdateTime = trialDoc.UpdateTime, + ConfirmUserId = identityUser.Id, + RealName = identityUser.FullName, + UserName = identityUser.UserName, + IsNeedSendEmial = identityUser.IsZhiZhun , + FullFilePath = trialDoc.Path + }; + } + var datalist = await systemDocQuery.IgnoreQueryFilters().ToListAsync(); + + var confirmUserIdList = datalist.Select(t => t.ConfirmUserId).Distinct().ToList(); + var userinfoList = await _identityUserRepository.Where(x => confirmUserIdList.Contains(x.Id)).ToListAsync(); + int index = 1; + foreach (var userinfo in userinfoList) + { + string msg = $"{index}项目生效通知,邮箱:{userinfo.EMail},姓名{userinfo.UserName},"; + index++; + try + { + + var messageToSend = new MimeMessage(); + //发件地址 + messageToSend.From.Add(new MailboxAddress(_systemEmailConfig.FromName, _systemEmailConfig.FromEmail)); + messageToSend.To.Add(new MailboxAddress(String.Empty, userinfo.EMail)); + + + + var companyName = isEn_US ? _systemEmailConfig.CompanyShortName : _systemEmailConfig.CompanyShortNameCN; + Func<(string topicStr, string htmlBodyStr), (string topicStr, string htmlBodyStr)> emailConfigFunc = input => + { + var topicStr = string.Format(input.topicStr, companyName); + + var htmlBodyStr = string.Format( + CommonEmailHelper.ReplaceCompanyName(_systemEmailConfig, input.htmlBodyStr), + userinfo.UserName, // 用户名 {0} + _systemEmailConfig.SiteUrl + ); + + return (topicStr, htmlBodyStr); + }; + + var scenario = EmailBusinessScenario.GeneralTraining_EffectiveNotification; + + var emailConfig = _emailNoticeConfigrepository.Where(t => t.BusinessScenarioEnum == scenario && t.IsAutoSend && t.IsEnable).FirstOrDefault(); + + if (emailConfig != null) + { + await CommonEmailHelper.GetEmailSubejctAndHtmlInfoAndBuildAsync(emailConfig, messageToSend, emailConfigFunc); + + await SendEmailHelper.SendEmailAsync(messageToSend, _systemEmailConfig); + msg += "发送成功"; + } + + + } + catch (Exception) + { + msg += "发送失败"; + + } + + + Console.WriteLine(msg); + } + + } + } +} diff --git a/IRaCIS.Core.Application/Service/Document/DTO/SystemDocumentViewModel.cs b/IRaCIS.Core.Application/Service/Document/DTO/SystemDocumentViewModel.cs index 44d22de49..d19563995 100644 --- a/IRaCIS.Core.Application/Service/Document/DTO/SystemDocumentViewModel.cs +++ b/IRaCIS.Core.Application/Service/Document/DTO/SystemDocumentViewModel.cs @@ -365,6 +365,11 @@ namespace IRaCIS.Core.Application.Contracts public List Ids { get; set; } } + public class PublishTrialDocumentInDto + { + public List Ids { get; set; } + } + public class AddOrEditSystemDocument : SystemDocumentAddOrEdit { diff --git a/IRaCIS.Core.Application/Service/Document/DTO/TrialDocumentViewModel.cs b/IRaCIS.Core.Application/Service/Document/DTO/TrialDocumentViewModel.cs index eae1fc362..36d16b663 100644 --- a/IRaCIS.Core.Application/Service/Document/DTO/TrialDocumentViewModel.cs +++ b/IRaCIS.Core.Application/Service/Document/DTO/TrialDocumentViewModel.cs @@ -60,6 +60,24 @@ namespace IRaCIS.Core.Application.Contracts public int SignViewMinimumMinutes { get; set; } + + /// + /// 现有员工培训天数 + /// + public int? CurrentStaffTrainDays { get; set; } + + + /// + /// 新员工培训天数 + /// + public int? NewStaffTrainDays { get; set; } + + /// + /// 是否发布 + /// + + public bool IsPublish { get; set; } = true; + } public class AddOrEditTrialDocument : TrialDocumentAddOrEdit diff --git a/IRaCIS.Core.Application/Service/Document/TrialDocumentService.cs b/IRaCIS.Core.Application/Service/Document/TrialDocumentService.cs index 4798545c7..a2fe41a6b 100644 --- a/IRaCIS.Core.Application/Service/Document/TrialDocumentService.cs +++ b/IRaCIS.Core.Application/Service/Document/TrialDocumentService.cs @@ -6,11 +6,14 @@ using IRaCIS.Core.Application.Contracts; using IRaCIS.Core.Application.Filter; +using IRaCIS.Core.Application.MassTransit.Consumer; using IRaCIS.Core.Domain.Models; using IRaCIS.Core.Domain.Share; +using MassTransit.Mediator; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; using NPOI.SS.Formula.Functions; using System.Linq; using System.Linq.Dynamic.Core; @@ -28,6 +31,7 @@ namespace IRaCIS.Core.Application.Services IRepository _systemDocNeedConfirmedUserTypeRepository, IRepository _trialDocNeedConfirmedUserTypeRepository, IRepository _systemDocumentRepository, + IServiceScopeFactory serviceScopeFactory, IRepository _trialIdentityUserRepository, IRepository _trialUserRoleRepository, IRepository _identityUserRepository, @@ -35,7 +39,34 @@ namespace IRaCIS.Core.Application.Services IRepository _readingQuestionCriterionTrialRepository, IMapper _mapper, IUserInfo _userInfo, IStringLocalizer _localizer) : BaseService, ITrialDocumentService { + /// + /// 发布项目文档 + /// + /// + [HttpPost] + public async Task PublishTrialDocument(PublishTrialDocumentInDto inDto) + { + await _trialDocumentRepository.BatchUpdateNoTrackingAsync(x => inDto.Ids.Contains(x.Id), x => new TrialDocument() + { + IsPublish = true, + IsDeleted = false, + }); + Console.WriteLine("开始 发布项目文档"); + Task.Run(async () => + { + // 创建独立作用域 + using (var scope = serviceScopeFactory.CreateScope()) + { + // 从新作用域解析服务 + var mediator = scope.ServiceProvider.GetRequiredService(); + await mediator.Publish(new SystemDocumentPublishEvent { Ids = inDto.Ids }); + } + }); + + + return ResponseOutput.Result(true); + } /// @@ -74,6 +105,8 @@ namespace IRaCIS.Core.Application.Services TrialCode = trialDoc.Trial.TrialCode, ResearchProgramNo = trialDoc.Trial.ResearchProgramNo, ExperimentName = trialDoc.Trial.ExperimentName, + CurrentStaffTrainDays = trialDoc.CurrentStaffTrainDays, + NewStaffTrainDays= trialDoc.NewStaffTrainDays, Id = trialDoc.Id, IsSystemDoc = false, CreateTime = trialDoc.CreateTime, @@ -944,7 +977,9 @@ namespace IRaCIS.Core.Application.Services var document = (await _trialDocumentRepository.Where(t => t.Id == addOrEditTrialDocument.Id, true).Include(t => t.NeedConfirmedUserTypeList).FirstOrDefaultAsync()).IfNullThrowException(); - + bool beforeIsPublish = document.IsPublish; + bool beforeIsDeleted = document.IsDeleted; + var beforeUserTypeIds = document.NeedConfirmedUserTypeList.Select(x => x.NeedConfirmUserTypeId).ToList(); _mapper.Map(addOrEditTrialDocument, document); @@ -973,7 +1008,37 @@ namespace IRaCIS.Core.Application.Services var success = await _trialDocumentRepository.SaveChangesAsync(); + // 检查是否需要发送邮件给新增的角色 + if (beforeIsPublish && document.IsPublish && !beforeIsDeleted && !document.IsDeleted) + { + // 找出新增的用户角色ID + var newUserTypeIds = addOrEditTrialDocument.NeedConfirmedUserTypeIdList + .Where(id => !beforeUserTypeIds.Contains(id)) + .ToList(); + if (newUserTypeIds.Any() && newUserTypeIds.Count() > 0) + { + // 发送邮件给新增的角色 + Console.WriteLine("开始 发送项目文档更新邮件给新增角色"); + Console.WriteLine(string.Join(",", newUserTypeIds)); + + Task.Run(async () => + { + // 创建独立作用域 + using (var scope = serviceScopeFactory.CreateScope()) + { + // 从新作用域解析服务 + var mediator = scope.ServiceProvider.GetRequiredService(); + // 只发送给新增的角色 + await mediator.Publish(new TrialDocumentPublishEvent + { + Ids = new List { document.Id }, + NewUserTypeIds = newUserTypeIds + }); + } + }); + } + } return ResponseOutput.Ok(document.Id.ToString()); } } diff --git a/IRaCIS.Core.Domain/Document/TrialDocument.cs b/IRaCIS.Core.Domain/Document/TrialDocument.cs index f9de87e4c..f1c54391b 100644 --- a/IRaCIS.Core.Domain/Document/TrialDocument.cs +++ b/IRaCIS.Core.Domain/Document/TrialDocument.cs @@ -28,5 +28,23 @@ public class TrialDocument : BaseFullDeleteAuditEntity public string Description { get; set; } = string.Empty; public int SignViewMinimumMinutes { get; set; } + + + /// + /// 现有员工培训天数 + /// + public int? CurrentStaffTrainDays { get; set; } + + + /// + /// 新员工培训天数 + /// + public int? NewStaffTrainDays { get; set; } + + /// + /// 是否发布 + /// + + public bool IsPublish { get; set; } = true; }