mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 08:23:12 +00:00
The manual POC "follow the site" clear-rule compared the edit against every live Site contact. A work order only ever displays one of them, so editing to a different live contact (Site has Alice primary and Bob; WO shows Alice; edit to Bob) matched, cleared the override, and left the row showing Alice with no audit row written — the SH-379 symptom on a different input. A work order with a linked WorkOrderContacts POC hit the same bug when the dispatcher typed the Site's primary: the override cleared and the linked contact showed instead. Compare the edit against the single contact the work order actually follows — the linked WorkOrderContacts POC, or else the Site primary (ResolvePrimary) — matching the board projection's override -> linked -> site precedence, and store the override whenever the edit differs from it. The create path in WorkOrderBoardCreateService had the same any-contact rule and gets the same fix; a supplied PocContactId that is not a live Site contact leaves no follow target, so the typed POC is stored. Replaces MatchesAnySiteContact with Matches(name, phone, contact); comment and PR-body wording updated to state the followed-contact rule. Adds tests for the second-site-contact edit, the linked-contact-differs edit, and the second-site-contact create case.
51 lines
2.3 KiB
C#
51 lines
2.3 KiB
C#
using Data.SeaHavenIndustries;
|
|
|
|
namespace SeaHaven.Services.Helpers
|
|
{
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public static class WorkOrderPocSiteContact
|
|
{
|
|
/// <summary>
|
|
/// The Site's primary live contact: first non-deleted contact ordered by
|
|
/// SiteContactOrder then Id. Null when the site has no live contacts.
|
|
/// </summary>
|
|
public static Contacts? ResolvePrimary(IEnumerable<Contacts>? contacts)
|
|
=> contacts?
|
|
.Where(c => c.IsDeleted != true)
|
|
.OrderBy(c => c.SiteContactOrder)
|
|
.ThenBy(c => c.Id)
|
|
.FirstOrDefault();
|
|
|
|
/// <summary>Display name matching the board projection: trimmed First + Last.</summary>
|
|
public static string? DisplayName(Contacts? contact)
|
|
=> contact == null
|
|
? null
|
|
: Normalize(((contact.FirstName ?? "") + " " + (contact.LastName ?? "")).Trim());
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|