793 lines
31 KiB
C#
793 lines
31 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Threading.Tasks;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
using Microsoft.Extensions.Logging;
|
||
using NPP.SmartSchedue.Api.Contracts.Core.Enums;
|
||
using NPP.SmartSchedue.Api.Contracts.Services.Notification;
|
||
using NPP.SmartSchedue.Api.Contracts.Services.Notification.Input;
|
||
using NPP.SmartSchedue.Api.Contracts.Services.Notification.Output;
|
||
using NPP.SmartSchedue.Api.Contracts.Services.Work;
|
||
using NPP.SmartSchedue.Api.Services.Personnel;
|
||
using ZhonTai.Admin.Core.GrpcServices;
|
||
using ZhonTai.Admin.Services;
|
||
using ZhonTai.DynamicApi;
|
||
using ZhonTai.DynamicApi.Attributes;
|
||
|
||
namespace NPP.SmartSchedue.Api.Services.Notification;
|
||
|
||
/// <summary>
|
||
/// 通知服务 定时消息
|
||
/// </summary>
|
||
[DynamicApi(Area = "app")]
|
||
public class NotificationScheduledService : BaseService, IDynamicApi
|
||
{
|
||
private readonly ILogger<NotificationScheduledService> _logger;
|
||
private readonly IWorkOrderService _workOrderService;
|
||
private readonly INotificationService _notificationService;
|
||
private readonly IUserGrpcService _userService;
|
||
private readonly PersonnelQualificationService _personnelQualificationService;
|
||
|
||
public NotificationScheduledService(IWorkOrderService workOrderService,
|
||
INotificationService notificationService,
|
||
IUserGrpcService userService,
|
||
ILogger<NotificationScheduledService> logger,
|
||
PersonnelQualificationService personnelQualificationService)
|
||
{
|
||
_logger = logger;
|
||
_workOrderService = workOrderService;
|
||
_userService = userService;
|
||
_notificationService = notificationService;
|
||
_personnelQualificationService = personnelQualificationService;
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 待提交任务提醒
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[HttpGet]
|
||
[AllowAnonymous]
|
||
public async Task<IActionResult> CheckWorkOrderByPendingSubmit()
|
||
{
|
||
var setting = await GetNotificationSettingCache("任务待提交提醒");
|
||
if (setting == null || !setting.IsEnabled)
|
||
{
|
||
_logger.LogWarning("任务待提交提醒未查询到有效的通知配置或已禁用");
|
||
return new OkObjectResult(new { success = true, sent = 0, skipped = 0 });
|
||
}
|
||
|
||
if (!IsWithinTimeWindow(setting, DateTime.Now))
|
||
{
|
||
_logger.LogInformation("当前不在通知时间窗内,跳过发送");
|
||
return new OkObjectResult(new { success = true, sent = 0, skipped = 0 });
|
||
}
|
||
|
||
var recipientIds = await GetNotificationPersonListAsync(setting);
|
||
if (recipientIds == null || recipientIds.Count == 0)
|
||
{
|
||
_logger.LogInformation("人员组无成员,跳过发送");
|
||
return new OkObjectResult(new { success = true, sent = 0, skipped = 0 });
|
||
}
|
||
|
||
var pendingByCreator = await _workOrderService.GetPendingSubmitCountByCreatorAsync();
|
||
if (pendingByCreator == null || pendingByCreator.Count == 0)
|
||
{
|
||
_logger.LogInformation("无待提交任务,跳过发送");
|
||
return new OkObjectResult(new { success = true, sent = 0 });
|
||
}
|
||
|
||
var intersectRecipients = pendingByCreator.Distinct().ToList();
|
||
if (intersectRecipients.Count == 0)
|
||
{
|
||
_logger.LogInformation("人员组成员中无待提交任务创建人,跳过发送");
|
||
return new OkObjectResult(new { success = true, sent = 0 });
|
||
}
|
||
|
||
var now = DateTime.Now;
|
||
var sendInputs = new List<SendNotificationInput>();
|
||
|
||
foreach (var personId in intersectRecipients)
|
||
{
|
||
var lastSentAt = await GetLastSentAt(setting.Id, personId.Key, "WorkOrderPendingSubmit");
|
||
if (!ShouldResendForPerson(setting, now, lastSentAt))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
var count = pendingByCreator.TryGetValue(personId.Key, out var c) ? c : 0;
|
||
if (count <= 0) continue;
|
||
|
||
var variables = new Dictionary<string, string>
|
||
{
|
||
["UserId"] = personId.ToString(),
|
||
["PendingCount"] = count.ToString(),
|
||
["Now"] = now.ToString("yyyy-MM-dd HH:mm"),
|
||
["count"] = count.ToString(),
|
||
};
|
||
|
||
var sysTitle = string.IsNullOrWhiteSpace(setting.SystemMessageTitleTemplate)
|
||
? "任务待提交提醒"
|
||
: await Render(setting.SystemMessageTitleTemplate, variables);
|
||
|
||
var sysContent = string.IsNullOrWhiteSpace(setting.SystemMessageContentTemplate)
|
||
? $"您有{count}个任务处于待提交状态,请及时处理"
|
||
: await Render(setting.SystemMessageContentTemplate, variables);
|
||
|
||
if (setting.IsSystemMessageEnabled)
|
||
{
|
||
sendInputs.Add(new SendNotificationInput
|
||
{
|
||
SettingId = setting.Id,
|
||
NotificationType = NotificationTypeEnum.SystemMessage,
|
||
Title = sysTitle,
|
||
Content = sysContent,
|
||
RecipientPersonnelIds = new List<long> { personId.Key },
|
||
BusinessType = "WorkOrderPendingSubmit",
|
||
BusinessId = null,
|
||
BusinessData = "",
|
||
SendImmediately = true
|
||
});
|
||
}
|
||
|
||
if (setting.IsEmailEnabled)
|
||
{
|
||
var emailSubject = string.IsNullOrWhiteSpace(setting.EmailSubjectTemplate)
|
||
? sysTitle
|
||
: await Render(setting.EmailSubjectTemplate, variables);
|
||
var emailContent = string.IsNullOrWhiteSpace(setting.EmailContentTemplate)
|
||
? sysContent
|
||
: await Render(setting.EmailContentTemplate, variables);
|
||
|
||
sendInputs.Add(new SendNotificationInput
|
||
{
|
||
SettingId = setting.Id,
|
||
NotificationType = NotificationTypeEnum.Email,
|
||
Title = emailSubject,
|
||
Content = emailContent,
|
||
RecipientPersonnelIds = new List<long> { personId.Key },
|
||
BusinessType = "WorkOrderPendingSubmit",
|
||
BusinessId = null,
|
||
BusinessData = "",
|
||
SendImmediately = true
|
||
});
|
||
}
|
||
}
|
||
|
||
int sent = 0;
|
||
foreach (var input in sendInputs)
|
||
{
|
||
var result = await _notificationService.SendNotificationAsync(input);
|
||
sent += result.SuccessCount;
|
||
}
|
||
|
||
return new OkObjectResult(new { success = true, sent, targeted = intersectRecipients.Count });
|
||
}
|
||
|
||
/// <summary>
|
||
/// 下周计划与请假提醒
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[HttpGet]
|
||
[AllowAnonymous]
|
||
public async Task<IActionResult> CheckNextWeekPlanAndLeaveReminder()
|
||
{
|
||
var setting = await GetNotificationSettingCache("下周计划与请假提醒");
|
||
if (setting == null || !setting.IsEnabled)
|
||
{
|
||
_logger.LogWarning("下周计划与请假提醒未查询到有效的通知配置或已禁用");
|
||
return new OkObjectResult(new { success = true, sent = 0, skipped = 0 });
|
||
}
|
||
|
||
var now = DateTime.Now;
|
||
if (!IsWithinTimeWindow(setting, now))
|
||
{
|
||
_logger.LogInformation("当前不在通知时间窗内,跳过发送");
|
||
return new OkObjectResult(new { success = true, sent = 0, skipped = 0 });
|
||
}
|
||
|
||
var recipients = await GetNotificationPersonListAsync(setting);
|
||
if (recipients == null || recipients.Count == 0)
|
||
{
|
||
_logger.LogInformation("人员组无成员,跳过发送");
|
||
return new OkObjectResult(new { success = true, sent = 0, skipped = 0 });
|
||
}
|
||
|
||
var nextWeekStart = now.Date.AddDays(7 - (int)now.DayOfWeek + 1);
|
||
var nextWeekEnd = nextWeekStart.AddDays(6);
|
||
var variables = new Dictionary<string, string>
|
||
{
|
||
["StartDate"] = nextWeekStart.ToString("yyyy-MM-dd"),
|
||
["EndDate"] = nextWeekEnd.ToString("yyyy-MM-dd"),
|
||
["Now"] = now.ToString("yyyy-MM-dd HH:mm")
|
||
};
|
||
|
||
var sysTitle = string.IsNullOrWhiteSpace(setting.SystemMessageTitleTemplate)
|
||
? "下周计划与请假提醒"
|
||
: await Render(setting.SystemMessageTitleTemplate, variables);
|
||
|
||
var sysContent = string.IsNullOrWhiteSpace(setting.SystemMessageContentTemplate)
|
||
? $"请提交下周({variables["StartDate"]} ~ {variables["EndDate"]})的工作计划与请假安排"
|
||
: await Render(setting.SystemMessageContentTemplate, variables);
|
||
|
||
var emailSubject = string.IsNullOrWhiteSpace(setting.EmailSubjectTemplate)
|
||
? sysTitle
|
||
: await Render(setting.EmailSubjectTemplate, variables);
|
||
|
||
var emailContent = string.IsNullOrWhiteSpace(setting.EmailContentTemplate)
|
||
? sysContent
|
||
: await Render(setting.EmailContentTemplate, variables);
|
||
|
||
// 收集需发送目标并按类型合并为批量收件人,减少发送次数
|
||
var systemTargets = new List<long>();
|
||
var emailTargets = new List<long>();
|
||
|
||
foreach (var pid in recipients)
|
||
{
|
||
var lastSentAt = await GetLastSentAt(setting.Id, pid, "NextWeekPlanAndLeave");
|
||
if (!ShouldResendForPerson(setting, now, lastSentAt))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (setting.IsSystemMessageEnabled)
|
||
{
|
||
systemTargets.Add(pid);
|
||
}
|
||
|
||
if (setting.IsEmailEnabled)
|
||
{
|
||
emailTargets.Add(pid);
|
||
}
|
||
}
|
||
|
||
var sendInputs = new List<SendNotificationInput>();
|
||
if (setting.IsSystemMessageEnabled && systemTargets.Count > 0)
|
||
{
|
||
sendInputs.Add(new SendNotificationInput
|
||
{
|
||
SettingId = setting.Id,
|
||
NotificationType = NotificationTypeEnum.SystemMessage,
|
||
Title = sysTitle,
|
||
Content = sysContent,
|
||
RecipientPersonnelIds = systemTargets.Distinct().ToList(),
|
||
BusinessType = "NextWeekPlanAndLeave",
|
||
BusinessId = null,
|
||
BusinessData = "",
|
||
SendImmediately = true
|
||
});
|
||
}
|
||
|
||
if (setting.IsEmailEnabled && emailTargets.Count > 0)
|
||
{
|
||
sendInputs.Add(new SendNotificationInput
|
||
{
|
||
SettingId = setting.Id,
|
||
NotificationType = NotificationTypeEnum.Email,
|
||
Title = emailSubject,
|
||
Content = emailContent,
|
||
RecipientPersonnelIds = emailTargets.Distinct().ToList(),
|
||
BusinessType = "NextWeekPlanAndLeave",
|
||
BusinessId = null,
|
||
BusinessData = "",
|
||
SendImmediately = true
|
||
});
|
||
}
|
||
|
||
int sent = 0;
|
||
foreach (var input in sendInputs)
|
||
{
|
||
var result = await _notificationService.SendGroupNotificationAsync(input);
|
||
sent += result.SuccessCount;
|
||
}
|
||
|
||
// targeted 返回真实被筛选后的人数
|
||
var targeted = systemTargets.Union(emailTargets).Distinct().Count();
|
||
return new OkObjectResult(new { success = true, sent, targeted });
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 任务分配提醒(周五提醒排班管理员进行排班)
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[HttpGet]
|
||
[AllowAnonymous]
|
||
public async Task<IActionResult> CheckFridayScheduleAssignmentReminder()
|
||
{
|
||
// 1. 读取通知配置
|
||
var setting = await GetNotificationSettingCache("任务分配提醒-周五排班");
|
||
if (setting == null || !setting.IsEnabled)
|
||
{
|
||
_logger.LogWarning("任务分配提醒-周五排班 未查询到有效的通知配置或已禁用");
|
||
return new OkObjectResult(new { success = true, sent = 0, skipped = 0 });
|
||
}
|
||
|
||
// 2. 基于时间窗与星期校验(建议由调度保证周五触发,此处再保护一次)
|
||
var now = DateTime.Now;
|
||
if (now.DayOfWeek != DayOfWeek.Friday)
|
||
{
|
||
_logger.LogInformation("今天不是周五,跳过发送");
|
||
return new OkObjectResult(new { success = true, sent = 0, skipped = 0 });
|
||
}
|
||
if (!IsWithinTimeWindow(setting, now))
|
||
{
|
||
_logger.LogInformation("当前不在通知时间窗内,跳过发送");
|
||
return new OkObjectResult(new { success = true, sent = 0, skipped = 0 });
|
||
}
|
||
|
||
// 3. 获取人员组成员(排班管理员)
|
||
var recipients = await GetNotificationPersonListAsync(setting);
|
||
if (recipients == null || recipients.Count == 0)
|
||
{
|
||
_logger.LogInformation("人员组无成员,跳过发送");
|
||
return new OkObjectResult(new { success = true, sent = 0, skipped = 0 });
|
||
}
|
||
|
||
// 4. 变量渲染
|
||
var fridayDate = now.Date.ToString("yyyy-MM-dd");
|
||
var weekOfYear = System.Globalization.ISOWeek.GetWeekOfYear(now);
|
||
var variables = new Dictionary<string, string>
|
||
{
|
||
["Now"] = now.ToString("yyyy-MM-dd HH:mm"),
|
||
["FridayDate"] = fridayDate,
|
||
["WeekOfYear"] = weekOfYear.ToString()
|
||
};
|
||
|
||
var sysTitle = string.IsNullOrWhiteSpace(setting.SystemMessageTitleTemplate)
|
||
? "任务分配提醒"
|
||
: await Render(setting.SystemMessageTitleTemplate, variables);
|
||
|
||
var sysContent = string.IsNullOrWhiteSpace(setting.SystemMessageContentTemplate)
|
||
? $"今天是周五({fridayDate}),请及时完成下周的排班任务分配。"
|
||
: await Render(setting.SystemMessageContentTemplate, variables);
|
||
|
||
var emailSubject = string.IsNullOrWhiteSpace(setting.EmailSubjectTemplate)
|
||
? sysTitle
|
||
: await Render(setting.EmailSubjectTemplate, variables);
|
||
|
||
var emailContent = string.IsNullOrWhiteSpace(setting.EmailContentTemplate)
|
||
? sysContent
|
||
: await Render(setting.EmailContentTemplate, variables);
|
||
|
||
// 5. 频率控制 + 组发
|
||
var systemTargets = new List<long>();
|
||
var emailTargets = new List<long>();
|
||
|
||
foreach (var pid in recipients)
|
||
{
|
||
var lastSentAt = await GetLastSentAt(setting.Id, pid, "FridayScheduleAssignmentReminder");
|
||
if (!ShouldResendForPerson(setting, now, lastSentAt))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (setting.IsSystemMessageEnabled) systemTargets.Add(pid);
|
||
if (setting.IsEmailEnabled) emailTargets.Add(pid);
|
||
}
|
||
|
||
var sendInputs = new List<SendNotificationInput>();
|
||
if (setting.IsSystemMessageEnabled && systemTargets.Count > 0)
|
||
{
|
||
sendInputs.Add(new SendNotificationInput
|
||
{
|
||
SettingId = setting.Id,
|
||
NotificationType = NotificationTypeEnum.SystemMessage,
|
||
Title = sysTitle,
|
||
Content = sysContent,
|
||
RecipientPersonnelIds = systemTargets.Distinct().ToList(),
|
||
BusinessType = "FridayScheduleAssignmentReminder",
|
||
BusinessId = null,
|
||
BusinessData = "",
|
||
SendImmediately = true
|
||
});
|
||
}
|
||
|
||
if (setting.IsEmailEnabled && emailTargets.Count > 0)
|
||
{
|
||
sendInputs.Add(new SendNotificationInput
|
||
{
|
||
SettingId = setting.Id,
|
||
NotificationType = NotificationTypeEnum.Email,
|
||
Title = emailSubject,
|
||
Content = emailContent,
|
||
RecipientPersonnelIds = emailTargets.Distinct().ToList(),
|
||
BusinessType = "FridayScheduleAssignmentReminder",
|
||
BusinessId = null,
|
||
BusinessData = "",
|
||
SendImmediately = true
|
||
});
|
||
}
|
||
|
||
// 6. 发送
|
||
int sent = 0;
|
||
foreach (var input in sendInputs)
|
||
{
|
||
// 与“下周计划与请假提醒”保持一致,组发
|
||
var result = await _notificationService.SendGroupNotificationAsync(input);
|
||
sent += result.SuccessCount;
|
||
}
|
||
|
||
var targeted = systemTargets.Union(emailTargets).Distinct().Count();
|
||
return new OkObjectResult(new { success = true, sent, targeted });
|
||
}
|
||
|
||
/// <summary>
|
||
/// 人员资质到期预警
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
[HttpGet]
|
||
[AllowAnonymous]
|
||
public async Task<IActionResult> CheckPersonnelQualificationExpiryReminder()
|
||
{
|
||
var setting = await GetNotificationSettingCache("人员资质到期预警");
|
||
if (setting == null || !setting.IsEnabled)
|
||
{
|
||
_logger.LogWarning("人员资质到期预警未查询到有效的通知配置或已禁用");
|
||
return new OkObjectResult(new { success = true, sent = 0, skipped = 0 });
|
||
}
|
||
|
||
var now = DateTime.Now;
|
||
if (!IsWithinTimeWindow(setting, now))
|
||
{
|
||
_logger.LogInformation("当前不在通知时间窗内,跳过发送");
|
||
return new OkObjectResult(new { success = true, sent = 0, skipped = 0 });
|
||
}
|
||
|
||
// // 人员范围:通知配置的人员组
|
||
// var recipients = await GetNotificationPersonListAsync(setting);
|
||
// if (recipients == null || recipients.Count == 0)
|
||
// {
|
||
// _logger.LogInformation("人员组无成员,跳过发送");
|
||
// return new OkObjectResult(new { success = true, sent = 0, skipped = 0 });
|
||
// }
|
||
|
||
// 查询这些人员的预警期内即将到期的资质
|
||
var expiring = await _personnelQualificationService.GetExpiringAsync(DateTime.Today, null, includeExpired: false);
|
||
if (expiring == null || expiring.Count == 0)
|
||
{
|
||
_logger.LogInformation("无资质到期预警数据,跳过发送");
|
||
return new OkObjectResult(new { success = true, sent = 0, targeted = 0 });
|
||
}
|
||
|
||
// 按人员聚合
|
||
var grouped = expiring.GroupBy(x => x.PersonnelId)
|
||
.ToDictionary(g => g.Key, g => g.ToList());
|
||
|
||
var sendInputs = new List<SendNotificationInput>();
|
||
int targeted = 0;
|
||
|
||
foreach (var pid in grouped.Distinct())
|
||
{
|
||
if (!grouped.TryGetValue(pid.Key, out var items) || items.Count == 0)
|
||
continue;
|
||
|
||
var lastSentAt = await GetLastSentAt(setting.Id, pid.Key, "PersonnelQualificationExpiry");
|
||
if (!ShouldResendForPerson(setting, now, lastSentAt))
|
||
continue;
|
||
|
||
targeted++;
|
||
|
||
// 组装模板变量
|
||
var personName = items.FirstOrDefault()?.PersonnelName ?? "";
|
||
var count = items.Count;
|
||
var itemLines = items
|
||
.OrderBy(i => i.DaysLeft)
|
||
.Select(i =>
|
||
{
|
||
var d = i.ExpiryDate?.ToString("yyyy-MM-dd") ?? "-";
|
||
return $"{i.QualificationName} 将于 {d} 到期,剩余 {i.DaysLeft} 天";
|
||
});
|
||
|
||
var itemsText = string.Join("; ", itemLines);
|
||
|
||
var variables = new Dictionary<string, string>
|
||
{
|
||
["UserId"] = pid.ToString(),
|
||
["PersonnelName"] = personName,
|
||
["Count"] = count.ToString(),
|
||
["Items"] = itemsText,
|
||
["Now"] = now.ToString("yyyy-MM-dd HH:mm")
|
||
};
|
||
|
||
var sysTitle = string.IsNullOrWhiteSpace(setting.SystemMessageTitleTemplate)
|
||
? "人员资质到期预警"
|
||
: await Render(setting.SystemMessageTitleTemplate, variables);
|
||
|
||
var sysContent = string.IsNullOrWhiteSpace(setting.SystemMessageContentTemplate)
|
||
? $"您有{count}条资质即将到期:{itemsText}"
|
||
: await Render(setting.SystemMessageContentTemplate, variables);
|
||
|
||
if (setting.IsSystemMessageEnabled)
|
||
{
|
||
sendInputs.Add(new SendNotificationInput
|
||
{
|
||
SettingId = setting.Id,
|
||
NotificationType = NotificationTypeEnum.SystemMessage,
|
||
Title = sysTitle,
|
||
Content = sysContent,
|
||
RecipientPersonnelIds = new List<long> { pid.Key },
|
||
BusinessType = "PersonnelQualificationExpiry",
|
||
BusinessId = null,
|
||
BusinessData = "",
|
||
SendImmediately = true
|
||
});
|
||
}
|
||
|
||
if (setting.IsEmailEnabled)
|
||
{
|
||
var emailSubject = string.IsNullOrWhiteSpace(setting.EmailSubjectTemplate)
|
||
? sysTitle
|
||
: await Render(setting.EmailSubjectTemplate, variables);
|
||
var emailContent = string.IsNullOrWhiteSpace(setting.EmailContentTemplate)
|
||
? sysContent
|
||
: await Render(setting.EmailContentTemplate, variables);
|
||
|
||
sendInputs.Add(new SendNotificationInput
|
||
{
|
||
SettingId = setting.Id,
|
||
NotificationType = NotificationTypeEnum.Email,
|
||
Title = emailSubject,
|
||
Content = emailContent,
|
||
RecipientPersonnelIds = new List<long> { pid.Key },
|
||
BusinessType = "PersonnelQualificationExpiry",
|
||
BusinessId = null,
|
||
BusinessData = "",
|
||
SendImmediately = true
|
||
});
|
||
}
|
||
}
|
||
|
||
int sent = 0;
|
||
foreach (var input in sendInputs)
|
||
{
|
||
var result = await _notificationService.SendNotificationAsync(input);
|
||
sent += result.SuccessCount;
|
||
}
|
||
|
||
return new OkObjectResult(new { success = true, sent, targeted });
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 统一对外:工作任务操作通知(供 WorkOrderIntegrationOperationService 调用)
|
||
/// </summary>
|
||
/// <param name="operationType">Modify/Delete/Cancel</param>
|
||
/// <param name="taskId">任务ID</param>
|
||
/// <param name="operatorUserId">操作者用户ID(可空)</param>
|
||
/// <param name="operatorName">操作者名称(可空)</param>
|
||
/// <param name="remarks">备注(可空)</param>
|
||
/// <returns></returns>
|
||
[HttpPost]
|
||
[AllowAnonymous]
|
||
public async Task<IActionResult> SendWorkOrderOperationNotificationAsync(
|
||
string operationType,
|
||
long taskId,
|
||
long? operatorUserId = null,
|
||
string operatorName = null,
|
||
string remarks = null,
|
||
IList<long> designatedPersonnelIds = null,
|
||
IList<string> designatedPersonnelNames = null)
|
||
{
|
||
// 1. 读取统一通知配置
|
||
var setting = await GetNotificationSettingCache("工作任务操作通知");
|
||
if (setting == null || !setting.IsEnabled)
|
||
{
|
||
_logger.LogWarning("工作任务操作通知 未查询到有效的通知配置或已禁用");
|
||
return new OkObjectResult(new { success = true, sent = 0, skipped = 0 });
|
||
}
|
||
|
||
var now = DateTime.Now;
|
||
if (!IsWithinTimeWindow(setting, now))
|
||
{
|
||
_logger.LogInformation("当前不在通知时间窗内,跳过发送");
|
||
return new OkObjectResult(new { success = true, sent = 0, skipped = 0 });
|
||
}
|
||
|
||
// 2. 收件人
|
||
var recipients = await GetNotificationPersonListAsync(setting);
|
||
recipients.AddRange(designatedPersonnelIds);
|
||
recipients = recipients.Distinct().ToList();
|
||
|
||
if (recipients == null || recipients.Count == 0)
|
||
{
|
||
_logger.LogInformation("人员组无成员,跳过发送");
|
||
return new OkObjectResult(new { success = true, sent = 0, skipped = 0 });
|
||
}
|
||
|
||
// 3. 业务类型区分,便于历史频率控制
|
||
string businessType = operationType?.ToLower() switch
|
||
{
|
||
"modify" => "WorkOrderModified",
|
||
"delete" => "WorkOrderDeleted",
|
||
"cancel" => "WorkOrderCancelled",
|
||
_ => "WorkOrderOperation"
|
||
};
|
||
|
||
// 4. 模板变量
|
||
var variables = new Dictionary<string, string>
|
||
{
|
||
["OperationType"] = operationType ?? string.Empty,
|
||
["TaskId"] = taskId.ToString(),
|
||
["OperatorUserId"] = operatorUserId?.ToString() ?? string.Empty,
|
||
["OperatorName"] = operatorName ?? string.Empty,
|
||
["Remarks"] = remarks ?? string.Empty,
|
||
["Now"] = now.ToString("yyyy-MM-dd HH:mm"),
|
||
["DesignatedPersonnelIds"] = designatedPersonnelIds != null ? string.Join(",", designatedPersonnelIds) : string.Empty,
|
||
["DesignatedPersonnelNames"] = designatedPersonnelNames != null ? string.Join(",", designatedPersonnelNames) : string.Empty
|
||
};
|
||
|
||
var sysTitle = string.IsNullOrWhiteSpace(setting.SystemMessageTitleTemplate)
|
||
? $"任务{operationType}通知"
|
||
: await Render(setting.SystemMessageTitleTemplate, variables);
|
||
|
||
var sysContent = string.IsNullOrWhiteSpace(setting.SystemMessageContentTemplate)
|
||
? $"任务({taskId})已执行{operationType}操作。{(string.IsNullOrEmpty(remarks) ? "" : "备注:" + remarks)}"
|
||
: await Render(setting.SystemMessageContentTemplate, variables);
|
||
|
||
var emailSubject = string.IsNullOrWhiteSpace(setting.EmailSubjectTemplate)
|
||
? sysTitle
|
||
: await Render(setting.EmailSubjectTemplate, variables);
|
||
|
||
var emailContent = string.IsNullOrWhiteSpace(setting.EmailContentTemplate)
|
||
? sysContent
|
||
: await Render(setting.EmailContentTemplate, variables);
|
||
|
||
// 5. 频率控制 + 组发
|
||
var systemTargets = new List<long>();
|
||
var emailTargets = new List<long>();
|
||
|
||
foreach (var pid in recipients)
|
||
{
|
||
var lastSentAt = await GetLastSentAt(setting.Id, pid, businessType);
|
||
if (!ShouldResendForPerson(setting, now, lastSentAt))
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (setting.IsSystemMessageEnabled) systemTargets.Add(pid);
|
||
if (setting.IsEmailEnabled) emailTargets.Add(pid);
|
||
}
|
||
|
||
var sendInputs = new List<SendNotificationInput>();
|
||
if (setting.IsSystemMessageEnabled && systemTargets.Count > 0)
|
||
{
|
||
sendInputs.Add(new SendNotificationInput
|
||
{
|
||
SettingId = setting.Id,
|
||
NotificationType = NotificationTypeEnum.SystemMessage,
|
||
Title = sysTitle,
|
||
Content = sysContent,
|
||
RecipientPersonnelIds = systemTargets.Distinct().ToList(),
|
||
BusinessType = businessType,
|
||
BusinessId = taskId,
|
||
BusinessData = System.Text.Json.JsonSerializer.Serialize(new {
|
||
OperationType = operationType, TaskId = taskId, OperatorUserId = operatorUserId, OperatorName = operatorName, Remarks = remarks
|
||
}),
|
||
SendImmediately = true
|
||
});
|
||
}
|
||
|
||
if (setting.IsEmailEnabled && emailTargets.Count > 0)
|
||
{
|
||
sendInputs.Add(new SendNotificationInput
|
||
{
|
||
SettingId = setting.Id,
|
||
NotificationType = NotificationTypeEnum.Email,
|
||
Title = emailSubject,
|
||
Content = emailContent,
|
||
RecipientPersonnelIds = emailTargets.Distinct().ToList(),
|
||
BusinessType = businessType,
|
||
BusinessId = taskId,
|
||
BusinessData = System.Text.Json.JsonSerializer.Serialize(new {
|
||
OperationType = operationType, TaskId = taskId, OperatorUserId = operatorUserId, OperatorName = operatorName, Remarks = remarks
|
||
}),
|
||
SendImmediately = true
|
||
});
|
||
}
|
||
|
||
int sent = 0;
|
||
foreach (var input in sendInputs)
|
||
{
|
||
var result = await _notificationService.SendGroupNotificationAsync(input);
|
||
sent += result.SuccessCount;
|
||
}
|
||
|
||
var targeted = systemTargets.Union(emailTargets).Distinct().Count();
|
||
return new OkObjectResult(new { success = true, sent, targeted, businessType });
|
||
}
|
||
|
||
private async Task<string> Render(string template, Dictionary<string, string> variables)
|
||
{
|
||
var isValid = await _notificationService.ValidateTemplateAsync(template);
|
||
if (!isValid) return template;
|
||
if (variables == null || variables.Count == 0) return template;
|
||
var result = template;
|
||
foreach (var kv in variables)
|
||
{
|
||
var placeholder = $"{{{kv.Key}}}";
|
||
result = result.Replace(placeholder, kv.Value ?? string.Empty, StringComparison.Ordinal);
|
||
}
|
||
return result;
|
||
}
|
||
|
||
private async Task<DateTime?> GetLastSentAt(long settingId, long personnelId, string businessType)
|
||
{
|
||
try
|
||
{
|
||
var histories = await _notificationService.GetNotificationHistoryPageAsync(new ZhonTai.Admin.Core.Dto.PageInput<NotificationHistoryPageInput>
|
||
{
|
||
CurrentPage = 0,
|
||
PageSize = 10000,
|
||
Filter = new NotificationHistoryPageInput
|
||
{
|
||
NotificationSettingId = settingId,
|
||
RecipientPersonnelId = personnelId,
|
||
BusinessType = businessType
|
||
}
|
||
});
|
||
|
||
var item = histories.List.FirstOrDefault();
|
||
return item?.ActualSendTime ?? item?.PlannedSendTime;
|
||
}
|
||
catch
|
||
{
|
||
return null;
|
||
}
|
||
}
|
||
|
||
private async Task<NotificationSettingOutput> GetNotificationSettingCache(string key)
|
||
{
|
||
var settings = await _notificationService.GetNotificationSettingListAsync(true);
|
||
return settings.FirstOrDefault(m => m.NotificationName == key);
|
||
}
|
||
|
||
private async Task<List<long>> GetNotificationPersonListAsync(NotificationSettingOutput? setting = null,
|
||
List<long>? otherPersons = null)
|
||
{
|
||
var notificationPersonList = new List<long>();
|
||
|
||
if (setting != null)
|
||
{
|
||
notificationPersonList.AddRange(await _notificationService.GetPersonnelGroupMembersAsync(setting.PersonnelGroupId));
|
||
}
|
||
|
||
if (otherPersons != null && otherPersons.Count > 0)
|
||
{
|
||
notificationPersonList.AddRange(otherPersons);
|
||
}
|
||
|
||
return notificationPersonList.Distinct().ToList();
|
||
}
|
||
|
||
private bool ShouldResendForPerson(NotificationSettingOutput setting, DateTime now, DateTime? lastSentAt)
|
||
{
|
||
if (!IsWithinTimeWindow(setting, now)) return false;
|
||
if (setting.FrequencyType == NotificationFrequencyEnum.Once)
|
||
{
|
||
return lastSentAt == null;
|
||
}
|
||
|
||
if (setting.FrequencyType == NotificationFrequencyEnum.FixedInterval)
|
||
{
|
||
if (lastSentAt == null) return true;
|
||
var interval = setting.IntervalMinutes.GetValueOrDefault(0);
|
||
if (interval <= 0) return false;
|
||
return now - lastSentAt.Value >= TimeSpan.FromMinutes(interval);
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
private bool IsWithinTimeWindow(NotificationSettingOutput setting, DateTime now)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(setting.StartTime) || string.IsNullOrWhiteSpace(setting.EndTime)) return true;
|
||
if (!TimeOnly.TryParse(setting.StartTime, out var start)) return true;
|
||
if (!TimeOnly.TryParse(setting.EndTime, out var end)) return true;
|
||
var t = TimeOnly.FromDateTime(now);
|
||
return t >= start && t < end;
|
||
}
|
||
} |