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 contact the work order follows" 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: true when a manual POC (trimmed name + phone) equals the single /// contact the work order currently follows — the linked WorkOrderContacts /// POC, or else the Site primary. Such an edit is not a change away from /// that contact, so it must not lock or store an override. Comparing against /// only the followed contact (rather than any live Site contact) means an /// edit to a different live contact is still stored, since the work order /// only ever displays the one it follows. /// public static bool Matches(string? name, string? phone, Contacts? contact) { if (contact == null) return false; return string.Equals(DisplayName(contact), Normalize(name), StringComparison.Ordinal) && string.Equals(Normalize(contact.PhoneNumber), Normalize(phone), StringComparison.Ordinal); } private static string? Normalize(string? value) => string.IsNullOrWhiteSpace(value) ? null : value.Trim(); } }