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;
///
/// 通知服务 定时消息
///
[DynamicApi(Area = "app")]
public class NotificationScheduledService : BaseService, IDynamicApi
{
private readonly ILogger _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 logger,
PersonnelQualificationService personnelQualificationService)
{
_logger = logger;
_workOrderService = workOrderService;
_userService = userService;
_notificationService = notificationService;
_personnelQualificationService = personnelQualificationService;
}
///
/// 待提交任务提醒
///
///
[HttpGet]
[AllowAnonymous]
public async Task 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();
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
{
["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 { 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 { 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 });
}
///
/// 下周计划与请假提醒
///
///
[HttpGet]
[AllowAnonymous]
public async Task 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
{
["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();
var emailTargets = new List();
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();
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 });
}
///
/// 任务分配提醒(周五提醒排班管理员进行排班)
///
///
[HttpGet]
[AllowAnonymous]
public async Task 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
{
["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();
var emailTargets = new List();
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();
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 });
}
///
/// 人员资质到期预警
///
///
[HttpGet]
[AllowAnonymous]
public async Task 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();
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
{
["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 { 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 { 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 });
}
///
/// 统一对外:工作任务操作通知(供 WorkOrderIntegrationOperationService 调用)
///
/// Modify/Delete/Cancel
/// 任务ID
/// 操作者用户ID(可空)
/// 操作者名称(可空)
/// 备注(可空)
///
[HttpPost]
[AllowAnonymous]
public async Task SendWorkOrderOperationNotificationAsync(
string operationType,
long taskId,
long? operatorUserId = null,
string operatorName = null,
string remarks = null,
IList designatedPersonnelIds = null,
IList 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
{
["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();
var emailTargets = new List();
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();
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 Render(string template, Dictionary 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 GetLastSentAt(long settingId, long personnelId, string businessType)
{
try
{
var histories = await _notificationService.GetNotificationHistoryPageAsync(new ZhonTai.Admin.Core.Dto.PageInput
{
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 GetNotificationSettingCache(string key)
{
var settings = await _notificationService.GetNotificationSettingListAsync(true);
return settings.FirstOrDefault(m => m.NotificationName == key);
}
private async Task> GetNotificationPersonListAsync(NotificationSettingOutput? setting = null,
List? otherPersons = null)
{
var notificationPersonList = new List();
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;
}
}