Skip to content

Commit

Permalink
Email and SmsPreferred support in Altinn.Notifications.Core (#581)
Browse files Browse the repository at this point in the history
* core logic implemented

* fixed code smells

* orderRequestService unit tests

* added test case

* added PreferredChannelProcessingServiceTests

* fixed code smells

* fixed pr comments
  • Loading branch information
acn-sbuad authored Aug 5, 2024
1 parent e6e59c6 commit 5369952
Show file tree
Hide file tree
Showing 16 changed files with 1,245 additions and 48 deletions.
12 changes: 11 additions & 1 deletion src/Altinn.Notifications.Core/Enums/NotificationChannel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,15 @@ public enum NotificationChannel
/// <summary>
/// The selected channel for the notification is SMS.
/// </summary>
Sms
Sms,

/// <summary>
/// The selected channel for the notification is email and to use SMS if email is not available.
/// </summary>
EmailPreferred,

/// <summary>
/// The selected channel for the notification is SMS and to use email if SMS is not available.
/// </summary>
SmsPreferred
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public static void AddCoreServices(this IServiceCollection services, IConfigurat
.AddSingleton<IOrderProcessingService, OrderProcessingService>()
.AddSingleton<IEmailOrderProcessingService, EmailOrderProcessingService>()
.AddSingleton<ISmsOrderProcessingService, SmsOrderProcessingService>()
.AddSingleton<IPreferredChannelProcessingService, PreferredChannelProcessingService>()
.AddSingleton<IGetOrderService, GetOrderService>()
.AddSingleton<IOrderRequestService, OrderRequestService>()
.AddSingleton<IEmailNotificationSummaryService, EmailNotificationSummaryService>()
Expand Down
111 changes: 110 additions & 1 deletion src/Altinn.Notifications.Core/Services/ContactPointService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Altinn.Notifications.Core.Helpers;
using Altinn.Notifications.Core.Enums;
using Altinn.Notifications.Core.Helpers;
using Altinn.Notifications.Core.Integrations;
using Altinn.Notifications.Core.Models;
using Altinn.Notifications.Core.Models.Address;
Expand Down Expand Up @@ -84,6 +85,114 @@ await AugmentRecipients(
});
}

/// <inheritdoc/>
public async Task AddPreferredContactPoints(NotificationChannel channel, List<Recipient> recipients, string? resourceId)
{
await AugmentRecipients(
recipients,
resourceId,
(recipient, userContactPoints) =>
{
if (channel == NotificationChannel.EmailPreferred)
{
AddPreferredOrFallbackContactPoint(
recipient,
userContactPoints.Email,
userContactPoints.MobileNumber,
email => new EmailAddressPoint(email),
mobile => new SmsAddressPoint(mobile));
}
else if (channel == NotificationChannel.SmsPreferred)
{
AddPreferredOrFallbackContactPoint(
recipient,
userContactPoints.MobileNumber,
userContactPoints.Email,
mobile => new SmsAddressPoint(mobile),
email => new EmailAddressPoint(email));
}

return recipient;
},
(recipient, orgContactPoints) =>
{
if (channel == NotificationChannel.EmailPreferred)
{
AddPreferredOrFallbackContactPointList(
recipient,
orgContactPoints.EmailList,
orgContactPoints.MobileNumberList,
e => new EmailAddressPoint(e),
m => new SmsAddressPoint(m));

foreach (var userContact in orgContactPoints.UserContactPoints)
{
AddPreferredOrFallbackContactPoint(
recipient,
userContact.Email,
userContact.MobileNumber,
email => new EmailAddressPoint(email),
mobile => new SmsAddressPoint(mobile));
}
}
else if (channel == NotificationChannel.SmsPreferred)
{
AddPreferredOrFallbackContactPointList(
recipient,
orgContactPoints.MobileNumberList,
orgContactPoints.EmailList,
m => new SmsAddressPoint(m),
e => new EmailAddressPoint(e));

foreach (var userContact in orgContactPoints.UserContactPoints)
{
AddPreferredOrFallbackContactPoint(
recipient,
userContact.MobileNumber,
userContact.Email,
mobile => new SmsAddressPoint(mobile),
email => new EmailAddressPoint(email));
}
}

return recipient;
});
}

private static void AddPreferredOrFallbackContactPointList<TPreferred, TFallback>(
Recipient recipient,
List<TPreferred> preferredList,
List<TFallback> fallbackList,
Func<TPreferred, IAddressPoint> preferredSelector,
Func<TFallback, IAddressPoint> fallbackSelector)
{
if (preferredList.Count > 0)
{
recipient.AddressInfo.AddRange(preferredList.Select(preferredSelector).ToList());
}
else
{
recipient.AddressInfo.AddRange(fallbackList.Select(fallbackSelector).ToList());
}
}

private static void AddPreferredOrFallbackContactPoint<TPreferred, TFallback>(
Recipient recipient,
TPreferred preferredContact,
TFallback fallbackContact,
Func<TPreferred, IAddressPoint> preferredSelector,
Func<TFallback, IAddressPoint> fallbackSelector)
{
if (!string.IsNullOrEmpty(preferredContact?.ToString()))
{
recipient.AddressInfo.Add(preferredSelector(preferredContact));
}
else if (!string.IsNullOrEmpty(fallbackContact?.ToString()))
{
recipient.AddressInfo.Add(fallbackSelector(fallbackContact));
}
}

private async Task<List<Recipient>> AugmentRecipients(
List<Recipient> recipients,
string? resourceId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ public async Task ProcessOrder(NotificationOrder order)

await _contactPointService.AddEmailContactPoints(recipientsWithoutEmail, order.ResourceId);

await ProcessOrderWithoutAddressLookup(order, recipients);
}

/// <inheritdoc/>
public async Task ProcessOrderWithoutAddressLookup(NotificationOrder order, List<Recipient> recipients)
{
foreach (Recipient recipient in recipients)
{
await _emailService.CreateNotification(order.Id, order.RequestedSendTime, recipient, order.IgnoreReservation ?? false);
Expand All @@ -52,9 +58,15 @@ public async Task ProcessOrderRetry(NotificationOrder order)

await _contactPointService.AddEmailContactPoints(recipientsWithoutEmail, order.ResourceId);

await ProcessOrderRetryWithoutAddressLookup(order, recipients);
}

/// <inheritdoc/>
public async Task ProcessOrderRetryWithoutAddressLookup(NotificationOrder order, List<Recipient> recipients)
{
List<EmailRecipient> emailRecipients = await _emailNotificationRepository.GetRecipients(order.Id);

foreach (Recipient recipient in order.Recipients)
foreach (Recipient recipient in recipients)
{
EmailAddressPoint? addressPoint = recipient.AddressInfo.Find(a => a.AddressType == AddressType.Email) as EmailAddressPoint;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Altinn.Notifications.Core.Models;
using Altinn.Notifications.Core.Enums;
using Altinn.Notifications.Core.Models;

namespace Altinn.Notifications.Core.Services.Interfaces
{
Expand All @@ -24,5 +25,15 @@ public interface IContactPointService
/// <returns>The list of recipients augumented with SMS address points where available</returns>
/// <remarks>Implementation alters the recipient reference object directly</remarks>
public Task AddSmsContactPoints(List<Recipient> recipients, string? resourceId);

/// <summary>
/// Looks up and adds the SMS contact points for recipients based on their national identity number or organization number
/// </summary>
/// <param name="channel">The notification channel specifying which channel is preferred</param>
/// <param name="recipients">List of recipients to retrieve contact points for</param>
/// <param name="resourceId">The resource to find contact points in relation to</param>
/// <returns>The list of recipients augumented with SMS address points where available</returns>
/// <remarks>Implementation alters the recipient reference object directly</remarks>
public Task AddPreferredContactPoints(NotificationChannel channel, List<Recipient> recipients, string? resourceId);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Altinn.Notifications.Core.Models.Orders;
using Altinn.Notifications.Core.Models;
using Altinn.Notifications.Core.Models.Orders;

namespace Altinn.Notifications.Core.Services.Interfaces;

Expand All @@ -13,7 +14,19 @@ public interface IEmailOrderProcessingService
public Task ProcessOrder(NotificationOrder order);

/// <summary>
/// Retry processing of an order
/// Processes a notification order for the provided list of recipients
/// without looking up additional recipient data
/// </summary>
public Task ProcessOrderWithoutAddressLookup(NotificationOrder order, List<Recipient> recipients);

/// <summary>
/// Retry processing of a notification order
/// </summary>
public Task ProcessOrderRetry(NotificationOrder order);

/// <summary>
/// Retryprocessing of a notification order for the provided list of recipients
/// without looking up additional recipient data
/// </summary>
public Task ProcessOrderRetryWithoutAddressLookup(NotificationOrder order, List<Recipient> recipients);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Altinn.Notifications.Core.Models.Orders;

namespace Altinn.Notifications.Core.Services.Interfaces;

/// <summary>
/// Interface for the order processing service speficic to email or sms preferred orders
/// </summary>
public interface IPreferredChannelProcessingService
{
/// <summary>
/// Processes a notification order
/// </summary>
public Task ProcessOrder(NotificationOrder order);

/// <summary>
/// Retry processing of an order
/// </summary>
public Task ProcessOrderRetry(NotificationOrder order);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Altinn.Notifications.Core.Models.Orders;
using Altinn.Notifications.Core.Models;
using Altinn.Notifications.Core.Models.Orders;

namespace Altinn.Notifications.Core.Services.Interfaces;

Expand All @@ -12,8 +13,20 @@ public interface ISmsOrderProcessingService
/// </summary>
public Task ProcessOrder(NotificationOrder order);

/// <summary>
/// Processes a notification order for the provided list of recipients
/// without looking up additional recipient data
/// </summary>
public Task ProcessOrderWithoutAddressLookup(NotificationOrder order, List<Recipient> recipients);

/// <summary>
/// Retry processing of an order
/// </summary>
public Task ProcessOrderRetry(NotificationOrder order);

/// <summary>
/// Retryprocessing of a notification order for the provided list of recipients
/// without looking up additional recipient data
/// </summary>
public Task ProcessOrderRetryWithoutAddressLookup(NotificationOrder order, List<Recipient> recipients);
}
11 changes: 11 additions & 0 deletions src/Altinn.Notifications.Core/Services/OrderProcessingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class OrderProcessingService : IOrderProcessingService
private readonly IOrderRepository _orderRepository;
private readonly IEmailOrderProcessingService _emailProcessingService;
private readonly ISmsOrderProcessingService _smsProcessingService;
private readonly IPreferredChannelProcessingService _preferredChannelProcessingService;
private readonly IConditionClient _conditionClient;
private readonly IKafkaProducer _producer;
private readonly string _pastDueOrdersTopic;
Expand All @@ -33,6 +34,7 @@ public OrderProcessingService(
IOrderRepository orderRepository,
IEmailOrderProcessingService emailProcessingService,
ISmsOrderProcessingService smsProcessingService,
IPreferredChannelProcessingService preferredChannelProcessingService,
IConditionClient conditionClient,
IKafkaProducer producer,
IOptions<KafkaSettings> kafkaSettings,
Expand All @@ -41,6 +43,7 @@ public OrderProcessingService(
_orderRepository = orderRepository;
_emailProcessingService = emailProcessingService;
_smsProcessingService = smsProcessingService;
_preferredChannelProcessingService = preferredChannelProcessingService;
_conditionClient = conditionClient;
_producer = producer;
_pastDueOrdersTopic = kafkaSettings.Value.PastDueOrdersTopicName;
Expand Down Expand Up @@ -89,6 +92,10 @@ public async Task ProcessOrder(NotificationOrder order)
case NotificationChannel.Sms:
await _smsProcessingService.ProcessOrder(order);
break;
case NotificationChannel.EmailPreferred:
case NotificationChannel.SmsPreferred:
await _preferredChannelProcessingService.ProcessOrder(order);
break;
}

await _orderRepository.SetProcessingStatus(order.Id, OrderProcessingStatus.Completed);
Expand All @@ -113,6 +120,10 @@ public async Task ProcessOrderRetry(NotificationOrder order)
case NotificationChannel.Sms:
await _smsProcessingService.ProcessOrderRetry(order);
break;
case NotificationChannel.EmailPreferred:
case NotificationChannel.SmsPreferred:
await _preferredChannelProcessingService.ProcessOrderRetry(order);
break;
}

await _orderRepository.SetProcessingStatus(order.Id, OrderProcessingStatus.Completed);
Expand Down
Loading

0 comments on commit 5369952

Please sign in to comment.