namespace SeaHaven.DataServices.Models { public sealed class VendorDirectoryRow { public int? VendorId { get; set; } public int CompanyId { get; set; } public string? CompanyName { get; set; } public string? ContactName { get; set; } public string? Email { get; set; } public string? Phone { get; set; } public string? CompanyPhone { get; set; } public string? PreferredContact { get; set; } public string? Address { get; set; } public string? City { get; set; } public string? State { get; set; } public string? Zip { get; set; } public string? TradeSpecialties { get; set; } public string? GoogleMapsUrl { get; set; } public string? Notes { get; set; } public int? AreaId { get; set; } public bool IsActive { get; set; } public int TotalJobs { get; set; } } // SH-278: Area facet filter for the company directory. AreaIds are already // resolved against the active catalogue; IncludeUnassigned selects groups with no // Area (including unlinked legacy vendors). An empty filter matches nobody. public sealed class VendorAreaFilter { public IReadOnlyCollection AreaIds { get; init; } = Array.Empty(); public bool IncludeUnassigned { get; init; } } // SH-281 company directory: per-row source for the grouped key query. Carries the // company group key parts (CompanyId 0 = unlinked; NameKey groups unlinked legacy // vendors by trimmed case-insensitive CompanyName; VendorKey isolates vendors with // a missing name by vendor identity) plus one 0/1 match flag per facet filter so a // group can be selected when any status-eligible row matches that facet. internal sealed class VendorDirectoryGroupSourceRow { public int? VendorId { get; set; } public int CompanyId { get; set; } public string NameKey { get; set; } = string.Empty; public int VendorKey { get; set; } public int TotalJobs { get; set; } public int SearchFlag { get; set; } public int CompanyFlag { get; set; } public int TradeFlag { get; set; } public int LocationFlag { get; set; } public int AreaFlag { get; set; } } // SH-281: one row per company group after SQL grouping — the bounded key page. internal sealed class VendorCompanyGroupKeyRow { public int CompanyId { get; set; } public string NameKey { get; set; } = string.Empty; public int VendorKey { get; set; } public int? MaxVendorId { get; set; } public int TotalJobs { get; set; } public int SearchMatch { get; set; } public int CompanyMatch { get; set; } public int TradeMatch { get; set; } public int LocationMatch { get; set; } public int AreaMatch { get; set; } } // SH-281: full status-eligible roster row (phase 2) carrying both the vendor's own // technician fields and the linked VendorCompany canonical fields, so group // assembly can apply the existing company-canonical projections in memory without // reapplying search/trade/location filters. internal sealed class VendorDirectoryRosterRow { public int VendorId { get; set; } public int? CompanyId { get; set; } public string? CompanyName { get; set; } public string? CanonicalCompanyName { get; set; } public string? CompanyEmail { get; set; } public string? CompanyPhone { get; set; } public string? CompanyAddress { get; set; } public string? CompanyCity { get; set; } public string? CompanyState { get; set; } public string? CompanyNotes { get; set; } public int? CompanyAreaId { get; set; } public string? CompanyAreaName { get; set; } public string? ContactName { get; set; } public string? Email { get; set; } public string? Phone { get; set; } public string? PreferredContact { get; set; } public string? VendorCompanyPhone { get; set; } public string? Address { get; set; } public string? City { get; set; } public string? State { get; set; } public string? Zip { get; set; } public string? TradeSpecialties { get; set; } public string? GoogleMapsUrl { get; set; } public bool IsActive { get; set; } public int TotalJobs { get; set; } public string NameKey { get; set; } = string.Empty; public int VendorKey { get; set; } } // SH-281: one technician inside a company group. public sealed class VendorCompanyGroupTechnician { public int Id { get; set; } public string? ContactName { get; set; } public string? Email { get; set; } public string? Phone { get; set; } public string? PreferredContact { get; set; } public string? TradeSpecialties { get; set; } public bool IsActive { get; set; } public int TotalJobs { get; set; } } // SH-281 company directory group: linked vendors collapse by real CompanyId; // unlinked legacy vendors group by trimmed case-insensitive CompanyName (vendors // with a missing name stay isolated by vendor identity); companies with zero // non-deleted technicians remain as company-only groups. Top-level technician // fields come from the primary technician (newest VendorId, promoted when a // nonblank search matches a non-primary technician); company fields are // company-canonical as the existing projections intend; TotalJobs is the sum of // every status-eligible technician's distinct work-order count. public sealed class VendorCompanyGroup { public int? Id { get; set; } public int? CompanyId { get; set; } public string? CompanyName { get; set; } public string? ContactName { get; set; } public string? Email { get; set; } public string? Phone { get; set; } public string? CompanyPhone { get; set; } public string? PreferredContact { get; set; } public string? Address { get; set; } public string? City { get; set; } public string? State { get; set; } public string? Zip { get; set; } public string? TradeSpecialties { get; set; } public string? GoogleMapsUrl { get; set; } public string? Notes { get; set; } public int? AreaId { get; set; } public string? AreaName { get; set; } public bool IsActive { get; set; } public int TotalJobs { get; set; } public IReadOnlyList Technicians { get; set; } = Array.Empty(); } }