feat(sms): Transport recipient PhoneNumber + SmsConfig round-trip (S10)

This commit is contained in:
Joseph Doherty
2026-06-19 10:39:12 -04:00
parent f0c69aad83
commit cdfd0ffbd2
6 changed files with 438 additions and 10 deletions
@@ -210,7 +210,9 @@ public sealed class ArtifactDiff
incoming.Recipients,
e => e.Name,
i => i.Name,
(e, i) => e.EmailAddress == i.EmailAddress,
// S10: a recipient is unchanged only when BOTH contacts match — a phone
// number added/changed on an existing recipient is a real diff.
(e, i) => e.EmailAddress == i.EmailAddress && e.PhoneNumber == i.PhoneNumber,
"Recipients",
changes);
@@ -2411,7 +2411,7 @@ public sealed class BundleImporter : IBundleImporter
existing.Recipients.Clear();
foreach (var r in dto.Recipients)
{
existing.Recipients.Add(new NotificationRecipient(r.Name, r.EmailAddress));
existing.Recipients.Add(BuildRecipient(r));
}
await _notificationRepo.UpdateNotificationListAsync(existing, ct).ConfigureAwait(false);
await _auditService.LogAsync(user, "Update", "NotificationList", existing.Id.ToString(), existing.Name,
@@ -2438,11 +2438,29 @@ public sealed class BundleImporter : IBundleImporter
var list = new NotificationList(overrideName ?? dto.Name) { Type = dto.Type };
foreach (var r in dto.Recipients)
{
list.Recipients.Add(new NotificationRecipient(r.Name, r.EmailAddress));
list.Recipients.Add(BuildRecipient(r));
}
return list;
}
// S10: reconstruct a recipient carrying whichever contact(s) the bundle holds.
// Email-only and SMS-only round-trip exactly; a recipient with both keeps both.
// Build via the email ctor when an address is present (so the historical
// non-null-email path is unchanged) and additively restore the phone number;
// otherwise fall back to the SMS factory for phone-only recipients.
private static NotificationRecipient BuildRecipient(NotificationRecipientDto r)
{
if (r.EmailAddress is not null)
{
return new NotificationRecipient(r.Name, r.EmailAddress)
{
PhoneNumber = r.PhoneNumber,
};
}
return NotificationRecipient.ForSms(r.Name, r.PhoneNumber ?? string.Empty);
}
private async Task ApplySmtpConfigsAsync(
IReadOnlyList<SmtpConfigDto> dtos,
Dictionary<(string, string), ImportResolution> map,