using Data.SeaHavenIndustries; namespace SeaHaven.Services.Helpers { /// /// SH-379 / SH-190: single resolution for "the Site's live contact" used by the /// manual POC override. The same rules back the board projection fallback, the /// completion snapshot fallback, the create-time baseline, and the /// "changing away from the Site's live contact" lock comparison. /// public static class WorkOrderPocSiteContact { /// /// The Site's primary live contact: first non-deleted contact ordered by /// SiteContactOrder then Id. Null when the site has no live contacts. /// public static Contacts? ResolvePrimary(IEnumerable? contacts) => contacts? .Where(c => c.IsDeleted != true) .OrderBy(c => c.SiteContactOrder) .ThenBy(c => c.Id) .FirstOrDefault(); /// Display name matching the board projection: trimmed First + Last. public static string? DisplayName(Contacts? contact) => contact == null ? null : Normalize(((contact.FirstName ?? "") + " " + (contact.LastName ?? "")).Trim()); /// /// SH-190: a manual POC equal to one of the Site's live contacts (trimmed /// name + phone) is not a change away from the Site and must not lock. /// public static bool MatchesAnySiteContact( string? name, string? phone, IEnumerable? contacts) { if (contacts == null) return false; var normalizedName = Normalize(name); var normalizedPhone = Normalize(phone); return contacts .Where(c => c.IsDeleted != true) .Any(c => string.Equals(Normalize(((c.FirstName ?? "") + " " + (c.LastName ?? "")).Trim()), normalizedName, StringComparison.Ordinal) && string.Equals(Normalize(c.PhoneNumber), normalizedPhone, StringComparison.Ordinal)); } private static string? Normalize(string? value) => string.IsNullOrWhiteSpace(value) ? null : value.Trim(); } }