Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for notification orders requiring organization lookup #497

Merged
merged 13 commits into from
May 7, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

<ItemGroup>
<PackageReference Include="Confluent.Kafka" Version="2.3.0" />
<PackageReference Include="libphonenumber-csharp" Version="8.13.34" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
Expand Down
52 changes: 52 additions & 0 deletions src/Altinn.Notifications.Core/Helpers/MobileNumberHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using PhoneNumbers;

namespace Altinn.Notifications.Core.Helpers
{
/// <summary>
/// Helper class for all mobile number related actions
/// </summary>
public static class MobileNumberHelper
{
/// <summary>
/// Checks if number contains country code, if not it adds the country code for Norway if number starts with 4 or 9
/// </summary>
/// <remarks>
/// This method does not validate the number, only ensures that it has a country code.
/// </remarks>
public static string EnsureCountryCodeIfValidNumber(string mobileNumber)
{
if (mobileNumber.StartsWith("00"))
{
mobileNumber = "+" + mobileNumber.Remove(0, 2);
}
else if (mobileNumber.Length == 8 && (mobileNumber[0] == '9' || mobileNumber[0] == '4'))
{
mobileNumber = "+47" + mobileNumber;
}

return mobileNumber;
}

/// <summary>
/// Validated as mobile number based on the Altinn 2 regex
/// </summary>
/// <param name="mobileNumber">The string to validate as an mobile number</param>
/// <returns>A boolean indicating that the mobile number is valid or not</returns>
public static bool IsValidMobileNumber(string? mobileNumber)
{
if (string.IsNullOrEmpty(mobileNumber) || (!mobileNumber.StartsWith('+') && !mobileNumber.StartsWith("00")))
{
return false;
}

if (mobileNumber.StartsWith("00"))
{
mobileNumber = "+" + mobileNumber.Remove(0, 2);
}

PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.GetInstance();
PhoneNumber phoneNumber = phoneNumberUtil.Parse(mobileNumber, null);
return phoneNumberUtil.IsValidNumber(phoneNumber);
}
}
}
7 changes: 0 additions & 7 deletions src/Altinn.Notifications.Core/Integrations/IProfileClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,4 @@ public interface IProfileClient
/// <param name="nationalIdentityNumbers">A list of national identity numbers to look up contact points for</param>
/// <returns>A list of contact points for the provided national identity numbers </returns>
public Task<List<UserContactPoints>> GetUserContactPoints(List<string> nationalIdentityNumbers);

/// <summary>
/// Retrieves contact point availability for a list of users corresponding to a list of national identity numbers
/// </summary>
/// <param name="nationalIdentityNumbers">A list of national identity numbers to look up contact point availability for</param>
/// <returns>A list of <see cref="UserContactPointAvailability"/> for the provided national identity numbers </returns>
public Task<List<UserContactPointAvailability>> GetUserContactPointAvailabilities(List<string> nationalIdentityNumbers);
}
17 changes: 17 additions & 0 deletions src/Altinn.Notifications.Core/Integrations/IRegisterClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Altinn.Notifications.Core.Models.ContactPoints;

namespace Altinn.Notifications.Core.Integrations
{
/// <summary>
/// Interface describing a client for the register service
/// </summary>
public interface IRegisterClient
{
/// <summary>
/// Retrieves contact points for a list of organizations
/// </summary>
/// <param name="organizationNumbers">A list of organization numbers to look up contact points for</param>
/// <returns>A list of <see cref="OrganizationContactPoints"/> for the provided organizations</returns>
public Task<List<OrganizationContactPoints>> GetOrganizationContactPoints(List<string> organizationNumbers);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Altinn.Notifications.Core.Models.ContactPoints;

/// <summary>
/// Class describing the contact points for an organization
/// </summary>
public class OrganizationContactPoints
{
/// <summary>
/// Gets or sets the organization number for the organization
/// </summary>
public string OrganizationNumber { get; set; } = string.Empty;

/// <summary>
/// Gets or sets a list of official mobile numbers
/// </summary>
public List<string> MobileNumberList { get; set; } = [];

/// <summary>
/// Gets or sets a list of official email addresses
/// </summary>
public List<string> EmailList { get; set; } = [];
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class MonthlyNotificationMetrics
public int Year { get; set; }

/// <summary>
/// A list of metrics per organisation
/// A list of metrics per organization
/// </summary>
public List<MetricsForOrg> Metrics { get; set; } = [];
}
8 changes: 4 additions & 4 deletions src/Altinn.Notifications.Core/Models/Recipient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ namespace Altinn.Notifications.Core.Models;
public class Recipient
{
/// <summary>
/// Gets the recipient's organisation number
/// Gets the recipient's organization number
/// </summary>
public string? OrganisationNumber { get; set; } = null;
public string? OrganizationNumber { get; set; } = null;

/// <summary>
/// Gets the recipient's national identity number
Expand All @@ -32,9 +32,9 @@ public class Recipient
/// <summary>
/// Initializes a new instance of the <see cref="Recipient"/> class.
/// </summary>
public Recipient(List<IAddressPoint> addressInfo, string? organisationNumber = null, string? nationalIdentityNumber = null)
public Recipient(List<IAddressPoint> addressInfo, string? organizationNumber = null, string? nationalIdentityNumber = null)
{
OrganisationNumber = organisationNumber;
OrganizationNumber = organizationNumber;
NationalIdentityNumber = nationalIdentityNumber;
AddressInfo = addressInfo;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ namespace Altinn.Notifications.Core.Models.Recipients;
public class EmailRecipient
{
/// <summary>
/// Gets or sets the recipient's organisation number
/// Gets or sets the recipient's organization number
/// </summary>
public string? OrganisationNumber { get; set; } = null;
public string? OrganizationNumber { get; set; } = null;

/// <summary>
/// Gets or sets the recipient's national identity number
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ namespace Altinn.Notifications.Core.Models.Recipients;
public class SmsRecipient
{
/// <summary>
/// Gets or sets the recipient's organisation number
/// Gets or sets the recipient's organization number
/// </summary>
public string? OrganisationNumber { get; set; } = null;
public string? OrganizationNumber { get; set; } = null;

/// <summary>
/// Gets or sets the recipient's national identity number
Expand Down
104 changes: 71 additions & 33 deletions 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.Integrations;
using Altinn.Notifications.Core.Helpers;
using Altinn.Notifications.Core.Integrations;
using Altinn.Notifications.Core.Models;
using Altinn.Notifications.Core.Models.Address;
using Altinn.Notifications.Core.Models.ContactPoints;
Expand All @@ -12,13 +13,15 @@ namespace Altinn.Notifications.Core.Services
public class ContactPointService : IContactPointService
{
private readonly IProfileClient _profileClient;
private readonly IRegisterClient _registerClient;

/// <summary>
/// Initializes a new instance of the <see cref="ContactPointService"/> class.
/// </summary>
public ContactPointService(IProfileClient profile)
public ContactPointService(IProfileClient profile, IRegisterClient register)
{
_profileClient = profile;
_registerClient = register;
}

/// <inheritdoc/>
Expand All @@ -33,6 +36,11 @@ await AugmentRecipients(
recipient.AddressInfo.Add(new EmailAddressPoint(userContactPoints.Email));
}

return recipient;
},
(recipient, orgContactPoints) =>
{
recipient.AddressInfo.AddRange(orgContactPoints.EmailList.Select(e => new EmailAddressPoint(e)).ToList());
return recipient;
});
}
Expand All @@ -49,21 +57,29 @@ await AugmentRecipients(
recipient.AddressInfo.Add(new SmsAddressPoint(userContactPoints.MobileNumber));
}

return recipient;
},
(recipient, orgContactPoints) =>
{
recipient.AddressInfo.AddRange(orgContactPoints.MobileNumberList.Select(m => new SmsAddressPoint(m)).ToList());
return recipient;
});
}

/// <inheritdoc/>
public async Task<List<UserContactPointAvailability>> GetContactPointAvailability(List<Recipient> recipients)
{
return await LookupContactPointAvailability(recipients);
}

private async Task<List<Recipient>> AugmentRecipients(List<Recipient> recipients, Func<Recipient, UserContactPoints, Recipient> createContactPoint)
private async Task<List<Recipient>> AugmentRecipients(
List<Recipient> recipients,
Func<Recipient, UserContactPoints, Recipient> createUserContactPoint,
Func<Recipient, OrganizationContactPoints, Recipient> createOrgContactPoint)
{
List<Recipient> augmentedRecipients = [];

List<UserContactPoints> userContactPointsList = await LookupContactPoints(recipients);
var userLookupTask = LookupPersonContactPoints(recipients);
var orgLookupTask = LookupOrganizationContactPoints(recipients);
await Task.WhenAll(userLookupTask, orgLookupTask);

List<UserContactPoints> userContactPointsList = userLookupTask.Result;
List<OrganizationContactPoints> organizationContactPointList = orgLookupTask.Result;

foreach (Recipient recipient in recipients)
{
if (!string.IsNullOrEmpty(recipient.NationalIdentityNumber))
Expand All @@ -74,51 +90,73 @@ private async Task<List<Recipient>> AugmentRecipients(List<Recipient> recipients
if (userContactPoints != null)
{
recipient.IsReserved = userContactPoints.IsReserved;
augmentedRecipients.Add(createContactPoint(recipient, userContactPoints));
augmentedRecipients.Add(createUserContactPoint(recipient, userContactPoints));
}
}
else if (!string.IsNullOrEmpty(recipient.OrganizationNumber))
{
OrganizationContactPoints? organizationContactPoints = organizationContactPointList!
.Find(o => o.OrganizationNumber == recipient.OrganizationNumber);

if (organizationContactPoints != null)
{
augmentedRecipients.Add(createOrgContactPoint(recipient, organizationContactPoints));
}
}
}

return augmentedRecipients;
}

private async Task<List<UserContactPoints>> LookupContactPoints(List<Recipient> recipients)
private async Task<List<UserContactPoints>> LookupPersonContactPoints(List<Recipient> recipients)
{
List<UserContactPoints> userContactPoints = new();
List<string> nins = recipients
.Where(r => !string.IsNullOrEmpty(r.NationalIdentityNumber))
.Select(r => r.NationalIdentityNumber!)
.ToList();

Task<List<UserContactPoints>> ninLookupTask = nins.Count > 0
? _profileClient.GetUserContactPoints(nins)
: Task.FromResult(new List<UserContactPoints>());
if (nins.Count == 0)
{
return [];
}

await Task.WhenAll(ninLookupTask);
List<UserContactPoints> contactPoints = await _profileClient.GetUserContactPoints(nins);

userContactPoints.AddRange(ninLookupTask.Result);
contactPoints.ForEach(contactPoint =>
{
contactPoint.MobileNumber = MobileNumberHelper.EnsureCountryCodeIfValidNumber(contactPoint.MobileNumber);
});

return userContactPoints;
return contactPoints;
}

private async Task<List<UserContactPointAvailability>> LookupContactPointAvailability(List<Recipient> recipients)
private async Task<List<OrganizationContactPoints>> LookupOrganizationContactPoints(List<Recipient> recipients)
{
List<UserContactPointAvailability> contactPointAvailabilityList = new();

List<string> nins = recipients
.Where(r => !string.IsNullOrEmpty(r.NationalIdentityNumber))
.Select(r => r.NationalIdentityNumber!)
.ToList();

Task<List<UserContactPointAvailability>> ninLookupTask = nins.Count > 0
? _profileClient.GetUserContactPointAvailabilities(nins)
: Task.FromResult(new List<UserContactPointAvailability>());
/* the output from this function should include an AUHTORIZED list of user registered contact points if notification has a service affiliation
will require the extension of the OrganizationContactPoints class */
List<string> orgNos = recipients
.Where(r => !string.IsNullOrEmpty(r.OrganizationNumber))
.Select(r => r.OrganizationNumber!)
.ToList();

if (orgNos.Count == 0)
{
return [];
}
acn-sbuad marked this conversation as resolved.
Show resolved Hide resolved

await Task.WhenAll(ninLookupTask);
List<OrganizationContactPoints> contactPoints = await _registerClient.GetOrganizationContactPoints(orgNos);

contactPointAvailabilityList.AddRange(ninLookupTask.Result);
contactPoints.ForEach(contactPoint =>
{
contactPoint.MobileNumberList = contactPoint.MobileNumberList
.Select(mobileNumber =>
{
return MobileNumberHelper.EnsureCountryCodeIfValidNumber(mobileNumber);
})
.ToList();
});

return contactPointAvailabilityList;
return contactPoints;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public async Task CreateNotification(Guid orderId, DateTime requestedSendTime, R

EmailRecipient emailRecipient = new()
{
OrganisationNumber = recipient.OrganisationNumber,
OrganizationNumber = recipient.OrganizationNumber,
NationalIdentityNumber = recipient.NationalIdentityNumber,
ToAddress = addressPoint?.EmailAddress ?? string.Empty,
IsReserved = recipient.IsReserved
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public async Task ProcessOrderRetry(NotificationOrder order)

if (!emailRecipients.Exists(er =>
er.NationalIdentityNumber == recipient.NationalIdentityNumber
&& er.OrganisationNumber == recipient.OrganisationNumber
&& er.OrganizationNumber == recipient.OrganizationNumber
&& er.ToAddress == addressPoint?.EmailAddress))
{
await _emailService.CreateNotification(order.Id, order.RequestedSendTime, recipient, order.IgnoreReservation);
Expand Down
Loading
Loading