Skip to content

Commit

Permalink
Merge pull request #87 from Worth-NL/cleanup/PartyOrganization
Browse files Browse the repository at this point in the history
Cleanup/party organization
  • Loading branch information
Thomas-M-Krystyan authored Oct 28, 2024
2 parents ed8853d + e06cea2 commit 10abfce
Show file tree
Hide file tree
Showing 27 changed files with 1,056 additions and 448 deletions.
521 changes: 415 additions & 106 deletions Documentation/OMC - Documentation.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion EventsHandler/Api/EventsHandler/Constants/DefaultValues.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ internal static class ApiController
{
internal const string Route = "[controller]";

internal const string Version = "1.110";
internal const string Version = "1.113";
}
#endregion

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// © 2024, Worth Systems.

using EventsHandler.Constants;
using System.Buffers.Text;
using System.Diagnostics.CodeAnalysis;
using System.IO.Compression;
using System.Text;
using EventsHandler.Constants;

namespace EventsHandler.Extensions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ public enum IdTypes
/// The BSN (citizen service number) type of the <see cref="Identification"/>.
/// </summary>
[JsonPropertyName("bsn")]
Bsn = 1
Bsn = 1,

/// <summary>
/// The KVK (Chamber of Commerce number) type of the <see cref="Identification"/>.
/// </summary>
[JsonPropertyName("kvk")]
Kvk = 2
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,118 +52,90 @@ public PartyResults()
internal readonly (PartyResult, DistributionChannels, string EmailAddress, string PhoneNumber)
Party(WebApiConfiguration configuration)
{
// Validation #1: Results
if (this.Results.IsNullOrEmpty())
{
throw new HttpRequestException(Resources.HttpRequest_ERROR_EmptyPartiesResults);
}

string fallbackEmailAddress = string.Empty;
string fallbackPhoneNumber = string.Empty;
PartyResult fallbackEmailOwningParty = default;
PartyResult fallbackPhoneOwningParty = default;
DistributionChannels distributionChannel = default;
string fallbackEmailAddress = string.Empty;
string fallbackPhoneNumber = string.Empty;

// Determine which party result should be returned and match the data
foreach (PartyResult party in this.Results)
foreach (PartyResult partyResult in this.Results)
{
// VALIDATION: Addresses
if (party.Expansion.DigitalAddresses.IsNullOrEmpty())
// Validation #2: Addresses
if (partyResult.Expansion.DigitalAddresses.IsNullOrEmpty())
{
continue; // Do not waste time on processing party data which would be for 100% invalid
}

Guid prefDigitalAddressId = party.PreferredDigitalAddress.Id;

// Looking which digital address should be used
foreach (DigitalAddressLong digitalAddress in party.Expansion.DigitalAddresses)
// Determine which address is preferred
if (IsPreferredFound(configuration, partyResult,
ref fallbackEmailOwningParty, ref fallbackPhoneOwningParty, ref distributionChannel,
ref fallbackEmailAddress, ref fallbackPhoneNumber))
{
// Recognize what type of digital address it is
DistributionChannels distributionChannel =
DetermineDistributionChannel(digitalAddress, configuration);

// VALIDATION: Distribution channel
if (distributionChannel is DistributionChannels.Unknown)
{
continue; // Any digital address couldn't be found
}

(string emailAddress, string phoneNumber) =
DetermineDigitalAddresses(digitalAddress, distributionChannel);

// VALIDATION: e-mail and phone number
if (emailAddress.IsNullOrEmpty() && phoneNumber.IsNullOrEmpty())
{
continue; // Empty results cannot be used anyway
}

// 1. This address is the preferred one and should be prioritized
if (prefDigitalAddressId != Guid.Empty &&
prefDigitalAddressId == digitalAddress.Id)
{
return (party, distributionChannel, emailAddress, phoneNumber);
}

// 2a. This is one of many other addresses to be checked
if (fallbackEmailAddress.IsNullOrEmpty() && // Only the first encountered one matters
emailAddress.IsNotNullOrEmpty())
{
fallbackEmailAddress = emailAddress;
fallbackEmailOwningParty = party;

continue; // The e-mail address always has priority over the phone number.
// If any e-mail address was found during this run then the phone
// number doesn't matter anymore since it will not be returned anyway
}

if (fallbackPhoneNumber.IsNullOrEmpty() && // Only the first encountered one matters
phoneNumber.IsNotNullOrEmpty())
{
fallbackPhoneNumber = phoneNumber;
fallbackPhoneOwningParty = party;
}
return (partyResult, distributionChannel, fallbackEmailAddress, fallbackPhoneNumber);
}
}

// 2b. FALLBACK APPROACH: If the party's preferred address couldn't be determined
// the email address has priority and the first encountered one should be returned
if (fallbackEmailAddress.IsNotNullOrEmpty())
{
return (fallbackEmailOwningParty, DistributionChannels.Email,
EmailAddress: fallbackEmailAddress, PhoneNumber: string.Empty);
}

// 2c. FALLBACK APPROACH: If the email also couldn't be determined then alternatively
// the first encountered telephone number (for SMS) should be returned instead
if (fallbackPhoneNumber.IsNotNullOrEmpty())
{
return (fallbackPhoneOwningParty, DistributionChannels.Sms,
EmailAddress: string.Empty, PhoneNumber: fallbackPhoneNumber);
}

// 2d. In the case of worst possible scenario, that preferred address couldn't be determined
// neither any existing email address nor telephone number, then process can't be finished
throw new HttpRequestException(Resources.HttpRequest_ERROR_NoDigitalAddresses);
// Pick any matching address
return GetMatchingContactDetails(
fallbackEmailOwningParty, fallbackPhoneOwningParty,
fallbackEmailAddress, fallbackPhoneNumber);
}

// TODO: Probably most of this code if not all can be reused by both methods
/// <inheritdoc cref="Party(WebApiConfiguration)"/>
internal static (PartyResult, DistributionChannels, string EmailAddress, string PhoneNumber)
Party(PartyResult partyResult, WebApiConfiguration configuration)
Party(WebApiConfiguration configuration, PartyResult partyResult)
{
string fallbackEmailAddress = string.Empty;
string fallbackPhoneNumber = string.Empty;
// Validation #1: Addresses
if (partyResult.Expansion.DigitalAddresses.IsNullOrEmpty())
{
throw new HttpRequestException(Resources.HttpRequest_ERROR_NoDigitalAddresses);
}

PartyResult fallbackEmailOwningParty = default;
PartyResult fallbackPhoneOwningParty = default;
DistributionChannels distributionChannel = default;
string fallbackEmailAddress = string.Empty;
string fallbackPhoneNumber = string.Empty;

// Determine which address is preferred
if (IsPreferredFound(configuration, partyResult,
ref fallbackEmailOwningParty, ref fallbackPhoneOwningParty, ref distributionChannel,
ref fallbackEmailAddress, ref fallbackPhoneNumber))
{
return (partyResult, distributionChannel, fallbackEmailAddress, fallbackPhoneNumber);
}

// Pick any matching address
return GetMatchingContactDetails(
fallbackEmailOwningParty, fallbackPhoneOwningParty,
fallbackEmailAddress, fallbackPhoneNumber);
}

Guid prefDigitalAddressId = partyResult.PreferredDigitalAddress.Id;
#region Helper methods
// NOTE: Checks preferred contact address
private static bool IsPreferredFound(WebApiConfiguration configuration, PartyResult party,
ref PartyResult fallbackEmailOwningParty,
ref PartyResult fallbackPhoneOwningParty,
ref DistributionChannels distributionChannel,
ref string fallbackEmailAddress,
ref string fallbackPhoneNumber)
{
Guid prefDigitalAddressId = party.PreferredDigitalAddress.Id;

// Looking which digital address should be used
foreach (DigitalAddressLong digitalAddress in partyResult.Expansion.DigitalAddresses)
foreach (DigitalAddressLong digitalAddress in party.Expansion.DigitalAddresses)
{
// Recognize what type of digital address it is
DistributionChannels distributionChannel =
DetermineDistributionChannel(digitalAddress, configuration);
distributionChannel = DetermineDistributionChannel(digitalAddress, configuration);

// VALIDATION: Distribution channel
// Validation #1: Distribution channel
if (distributionChannel is DistributionChannels.Unknown)
{
continue; // Any digital address couldn't be found
Expand All @@ -172,7 +144,7 @@ internal static (PartyResult, DistributionChannels, string EmailAddress, string
(string emailAddress, string phoneNumber) =
DetermineDigitalAddresses(digitalAddress, distributionChannel);

// VALIDATION: e-mail and phone number
// Validation #2: E-mail and phone number
if (emailAddress.IsNullOrEmpty() && phoneNumber.IsNullOrEmpty())
{
continue; // Empty results cannot be used anyway
Expand All @@ -182,51 +154,65 @@ internal static (PartyResult, DistributionChannels, string EmailAddress, string
if (prefDigitalAddressId != Guid.Empty &&
prefDigitalAddressId == digitalAddress.Id)
{
return (partyResult, distributionChannel, emailAddress, phoneNumber);
fallbackEmailOwningParty = party;
fallbackEmailAddress = emailAddress;

fallbackPhoneOwningParty = party;
fallbackPhoneNumber = phoneNumber;

return true; // Preferred address is found
}

// 2a. This is one of many other addresses to be checked
// 2a. This is one of many other addresses to be checked (e-mail has priority)
if (fallbackEmailAddress.IsNullOrEmpty() && // Only the first encountered one matters
emailAddress.IsNotNullOrEmpty())
{
fallbackEmailAddress = emailAddress;
fallbackEmailOwningParty = partyResult;
fallbackEmailOwningParty = party;

continue; // The e-mail address always has priority over the phone number.
// If any e-mail address was found during this run then the phone
// number doesn't matter anymore since it will not be returned anyway
}

// 2b. This address is not preferred but could be the only which was found as matching
if (fallbackPhoneNumber.IsNullOrEmpty() && // Only the first encountered one matters
phoneNumber.IsNotNullOrEmpty())
{
fallbackPhoneNumber = phoneNumber;
fallbackPhoneOwningParty = partyResult;
fallbackPhoneOwningParty = party;
}
}

// 2b. FALLBACK APPROACH: If the party's preferred address couldn't be determined
return false;
}

// NOTE: Checks alternative contact addresses
private static (PartyResult, DistributionChannels, string EmailAddress, string PhoneNumber) GetMatchingContactDetails(
PartyResult fallbackEmailOwningParty, PartyResult fallbackPhoneOwningParty,
string fallbackEmailAddress, string fallbackPhoneNumber)
{
// 3a. FALLBACK APPROACH: If the party's preferred address couldn't be determined
// the email address has priority and the first encountered one should be returned
if (fallbackEmailAddress.IsNotNullOrEmpty())
{
return (fallbackEmailOwningParty, DistributionChannels.Email,
EmailAddress: fallbackEmailAddress, PhoneNumber: string.Empty);
}

// 2c. FALLBACK APPROACH: If the email also couldn't be determined then alternatively
// 3b. FALLBACK APPROACH: If the email also couldn't be determined then alternatively
// the first encountered telephone number (for SMS) should be returned instead
if (fallbackPhoneNumber.IsNotNullOrEmpty())
{
return (fallbackPhoneOwningParty, DistributionChannels.Sms,
EmailAddress: string.Empty, PhoneNumber: fallbackPhoneNumber);
}

// 2d. In the case of worst possible scenario, that preferred address couldn't be determined
// 3c. In the case of worst possible scenario, that preferred address couldn't be determined
// neither any existing email address nor telephone number, then process can't be finished
throw new HttpRequestException(Resources.HttpRequest_ERROR_NoDigitalAddresses);
}

#region Helper methods
/// <summary>
/// Checks if the value from generic JSON property "Type" is
/// matching to the predefined names of digital address types.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public struct CaseRole : IJsonSerializable
[JsonInclude]
[JsonPropertyName("betrokkene")] // ENG: Involved party
[JsonPropertyOrder(0)]
public Uri InvolvedPartyUri { get; internal set; } = DefaultValues.Models.EmptyUri;
public Uri InvolvedPartyUri { get; internal set; } = DefaultValues.Models.EmptyUri; // NOTE: Might be missing for Citizens

/// <summary>
/// The general description of the <see cref="CaseRole"/> which includes the "initiator role" of the <see cref="Case"/>.
Expand All @@ -33,10 +33,10 @@ public struct CaseRole : IJsonSerializable
/// <summary>
/// The data subject identification which includes details about a single citizen related to this <see cref="Case"/>.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
[JsonInclude]
[JsonPropertyName("betrokkeneIdentificatie")] // ENG: Data subject (party) identification
[JsonPropertyOrder(2)]
public PartyData Party { get; internal set; }
public PartyData? Party { get; internal set; } = new PartyData(); // NOTE: Might be missing for Organizations

/// <summary>
/// Initializes a new instance of the <see cref="CaseRole"/> struct.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using EventsHandler.Mapping.Models.Interfaces;
using EventsHandler.Properties;
using EventsHandler.Services.Settings.Configuration;
using Microsoft.IdentityModel.Tokens;
using System.Text.Json.Serialization;

Expand Down Expand Up @@ -43,23 +44,30 @@ public CaseRoles()
}

/// <summary>
/// Gets the most recent (last) <see cref="OpenZaak.CaseRole"/>.
/// Gets the desired <see cref="OpenZaak.CaseRole"/>.
/// </summary>
/// <value>
/// The single role.
/// </value>
/// <exception cref="HttpRequestException"/> // TODO: KeyNotFoundException
internal readonly CaseRole CaseRole
/// <exception cref="HttpRequestException"/>
/// <exception cref="KeyNotFoundException"/>
internal readonly CaseRole CaseRole(WebApiConfiguration configuration)
{
get
if (this.Results.IsNullOrEmpty())
{
if (this.Results.IsNullOrEmpty())
throw new HttpRequestException(Resources.HttpRequest_ERROR_EmptyCaseRoles);
}

foreach (CaseRole caseRole in this.Results)
{
if (caseRole.InitiatorRole == configuration.AppSettings.Variables.InitiatorRole())
{
throw new HttpRequestException(Resources.HttpRequest_ERROR_EmptyCaseRoles);
return caseRole;
}

return this.Results[^1]; // TODO: Get party by initiator role
}

// Zero initiator results were found (there is no initiator)
throw new HttpRequestException(Resources.HttpRequest_ERROR_MissingInitiatorRole);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public CaseRoles()
}

/// <summary>
/// Gets the <see cref="OpenZaak.CaseRole"/> with matching "initiator role" set.
/// Gets the desired <see cref="OpenZaak.CaseRole"/>.
/// </summary>
/// <returns>
/// The single role.
Expand All @@ -53,7 +53,6 @@ public CaseRoles()
/// <exception cref="KeyNotFoundException"/>
internal readonly CaseRole CaseRole(WebApiConfiguration configuration)
{
// Response does not contain any results (check notification or project configuration)
if (this.Results.IsNullOrEmpty())
{
throw new HttpRequestException(Resources.HttpRequest_ERROR_EmptyCaseRoles);
Expand Down
Loading

0 comments on commit 10abfce

Please sign in to comment.