mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 11:53:12 +00:00
fix(vendors): complete directory contact fallbacks
This commit is contained in:
parent
8e749047a3
commit
bf7d1fcdf5
4 changed files with 265 additions and 5 deletions
|
|
@ -245,6 +245,7 @@ public class VendorDataServiceTests
|
|||
[Theory]
|
||||
[InlineData("trade")]
|
||||
[InlineData("location")]
|
||||
[InlineData("address-location")]
|
||||
[InlineData("job-bucket")]
|
||||
public void BuildDirectoryQuery_TranslatesRepresentativeFiltersForSqlServer(string filter)
|
||||
{
|
||||
|
|
@ -255,6 +256,7 @@ public class VendorDataServiceTests
|
|||
{
|
||||
"trade" => service.BuildDirectoryQuery(trades: new[] { "Plumbing" }),
|
||||
"location" => service.BuildDirectoryQuery(locations: new[] { "Chicago, IL" }),
|
||||
"address-location" => service.BuildDirectoryQuery(locations: new[] { "123 Main St" }),
|
||||
_ => service.BuildDirectoryQuery(jobBuckets: new[] { "50-99" })
|
||||
};
|
||||
|
||||
|
|
@ -265,6 +267,8 @@ public class VendorDataServiceTests
|
|||
sql.Should().Contain("TradeSpecialties");
|
||||
if (filter == "location")
|
||||
sql.Should().Contain("City");
|
||||
if (filter == "address-location")
|
||||
sql.Should().Contain("Address");
|
||||
if (filter == "job-bucket")
|
||||
sql.Should().Contain("UNION");
|
||||
}
|
||||
|
|
@ -472,9 +476,197 @@ public class VendorDataServiceTests
|
|||
locationRow.State.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDirectoryPagedAsync_UsesCanonicalCompanyAddressAndCompanyPhoneWithVendorFallback()
|
||||
{
|
||||
await using var context = NewContext();
|
||||
var fullCompany = new VendorCompany
|
||||
{
|
||||
Name = "Full Co",
|
||||
NormalizedName = "full co",
|
||||
Address = "100 Company Way",
|
||||
CompanyPhone = "(555) 111-2222"
|
||||
};
|
||||
var blankCompany = new VendorCompany
|
||||
{
|
||||
Name = "Blank Co",
|
||||
NormalizedName = "blank co",
|
||||
Address = " ",
|
||||
CompanyPhone = ""
|
||||
};
|
||||
context.VendorCompanies.AddRange(fullCompany, blankCompany);
|
||||
context.Vendors.AddRange(
|
||||
new Vendor
|
||||
{
|
||||
CompanyName = "Full Co",
|
||||
ContactName = "Full Tech",
|
||||
Phone = "(555) 222-3333",
|
||||
CompanyPhone = "(555) 999-0000",
|
||||
Address = "9 Old Street",
|
||||
IsActive = true,
|
||||
IsDeleted = false,
|
||||
Company = fullCompany
|
||||
},
|
||||
new Vendor
|
||||
{
|
||||
CompanyName = "Blank Co",
|
||||
ContactName = "Blank Tech",
|
||||
CompanyPhone = "(555) 333-4444",
|
||||
Address = "200 Vendor Way",
|
||||
IsActive = true,
|
||||
IsDeleted = false,
|
||||
Company = blankCompany
|
||||
},
|
||||
new Vendor
|
||||
{
|
||||
CompanyName = "Legacy Co",
|
||||
ContactName = "Unlinked Tech",
|
||||
CompanyPhone = "(555) 777-8888",
|
||||
Address = "300 Legacy Way",
|
||||
IsActive = true,
|
||||
IsDeleted = false,
|
||||
CompanyId = null
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = new VendorDataService(context);
|
||||
var (items, totalCount) = await service.GetDirectoryPagedAsync(1, 50, isActive: true);
|
||||
|
||||
totalCount.Should().Be(3);
|
||||
var fullTech = items.Single(i => i.ContactName == "Full Tech");
|
||||
fullTech.Address.Should().Be("100 Company Way");
|
||||
fullTech.CompanyPhone.Should().Be("(555) 111-2222");
|
||||
fullTech.Phone.Should().Be("(555) 222-3333");
|
||||
|
||||
var blankTech = items.Single(i => i.ContactName == "Blank Tech");
|
||||
blankTech.Address.Should().Be("200 Vendor Way");
|
||||
blankTech.CompanyPhone.Should().Be("(555) 333-4444");
|
||||
|
||||
var legacy = items.Single(i => i.ContactName == "Unlinked Tech");
|
||||
legacy.Address.Should().Be("300 Legacy Way");
|
||||
legacy.CompanyPhone.Should().Be("(555) 777-8888");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDirectoryPagedAsync_SearchIncludesProjectedAddress()
|
||||
{
|
||||
await using var context = NewContext();
|
||||
var company = new VendorCompany
|
||||
{
|
||||
Name = "Addr Co",
|
||||
NormalizedName = "addr co",
|
||||
Address = "123 Main St"
|
||||
};
|
||||
context.VendorCompanies.Add(company);
|
||||
context.Vendors.Add(new Vendor
|
||||
{
|
||||
CompanyName = "Addr Co",
|
||||
ContactName = "Pat",
|
||||
Address = "9 Old Street",
|
||||
IsActive = true,
|
||||
IsDeleted = false,
|
||||
Company = company
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = new VendorDataService(context);
|
||||
|
||||
var search = await service.GetDirectoryPagedAsync(1, 50, search: "123 main", isActive: true);
|
||||
search.TotalCount.Should().Be(1);
|
||||
search.Items.Single().ContactName.Should().Be("Pat");
|
||||
|
||||
var stale = await service.GetDirectoryPagedAsync(1, 50, search: "9 old street", isActive: true);
|
||||
stale.TotalCount.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDirectoryPagedAsync_LocationFilterFallsBackToExactAddressWhenCityStateMissing()
|
||||
{
|
||||
await using var context = NewContext();
|
||||
var addressOnlyCompany = new VendorCompany
|
||||
{
|
||||
Name = "Addr Only Co",
|
||||
NormalizedName = "addr only co",
|
||||
Address = "123 Main St"
|
||||
};
|
||||
var soloCompany = new VendorCompany
|
||||
{
|
||||
Name = "Solo Co",
|
||||
NormalizedName = "solo co",
|
||||
Address = "500 Solo Way"
|
||||
};
|
||||
context.VendorCompanies.AddRange(addressOnlyCompany, soloCompany);
|
||||
context.Vendors.Add(new Vendor
|
||||
{
|
||||
CompanyName = "Addr Only Co",
|
||||
ContactName = "Pat",
|
||||
IsActive = true,
|
||||
IsDeleted = false,
|
||||
Company = addressOnlyCompany
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = new VendorDataService(context);
|
||||
|
||||
var technicianRow = await service.GetDirectoryPagedAsync(
|
||||
1, 50, isActive: true, locations: new[] { "123 Main St" });
|
||||
technicianRow.TotalCount.Should().Be(1);
|
||||
technicianRow.Items.Single().ContactName.Should().Be("Pat");
|
||||
|
||||
var companyOnlyRow = await service.GetDirectoryPagedAsync(
|
||||
1, 50, isActive: true, locations: new[] { "500 Solo Way" });
|
||||
companyOnlyRow.TotalCount.Should().Be(1);
|
||||
companyOnlyRow.Items.Single().CompanyName.Should().Be("Solo Co");
|
||||
|
||||
var partial = await service.GetDirectoryPagedAsync(
|
||||
1, 50, isActive: true, locations: new[] { "123 Main" });
|
||||
partial.TotalCount.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDirectoryPagedAsync_LocationLabelPrefersCityStateOverAddress()
|
||||
{
|
||||
await using var context = NewContext();
|
||||
context.Vendors.AddRange(
|
||||
new Vendor
|
||||
{
|
||||
CompanyName = "City Co",
|
||||
ContactName = "City Tech",
|
||||
City = "Austin",
|
||||
Address = "999 Address Way",
|
||||
IsActive = true,
|
||||
IsDeleted = false
|
||||
},
|
||||
new Vendor
|
||||
{
|
||||
CompanyName = "State Co",
|
||||
ContactName = "State Tech",
|
||||
State = "TX",
|
||||
Address = "888 Address Way",
|
||||
IsActive = true,
|
||||
IsDeleted = false
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = new VendorDataService(context);
|
||||
|
||||
var byCity = await service.GetDirectoryPagedAsync(1, 50, isActive: true, locations: new[] { "Austin" });
|
||||
byCity.TotalCount.Should().Be(1);
|
||||
byCity.Items.Single().CompanyName.Should().Be("City Co");
|
||||
|
||||
var byState = await service.GetDirectoryPagedAsync(1, 50, isActive: true, locations: new[] { "TX" });
|
||||
byState.TotalCount.Should().Be(1);
|
||||
byState.Items.Single().CompanyName.Should().Be("State Co");
|
||||
|
||||
var bySuppressedAddress = await service.GetDirectoryPagedAsync(
|
||||
1, 50, isActive: true, locations: new[] { "999 Address Way" });
|
||||
bySuppressedAddress.TotalCount.Should().Be(0);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("trade")]
|
||||
[InlineData("location")]
|
||||
[InlineData("address-location")]
|
||||
[InlineData("job-bucket")]
|
||||
public void BuildDirectoryUnionQuery_TranslatesToSqlServerUnion(string filter)
|
||||
{
|
||||
|
|
@ -485,6 +677,7 @@ public class VendorDataServiceTests
|
|||
{
|
||||
"trade" => service.BuildDirectoryUnionQuery(trades: new[] { "Plumbing" }),
|
||||
"location" => service.BuildDirectoryUnionQuery(locations: new[] { "Chicago, IL" }),
|
||||
"address-location" => service.BuildDirectoryUnionQuery(locations: new[] { "123 Main St" }),
|
||||
_ => service.BuildDirectoryUnionQuery(jobBuckets: new[] { "under-50" })
|
||||
};
|
||||
|
||||
|
|
@ -496,10 +689,25 @@ public class VendorDataServiceTests
|
|||
sql.Should().Contain("TradeSpecialties");
|
||||
if (filter == "location")
|
||||
sql.Should().Contain("City");
|
||||
if (filter == "address-location")
|
||||
sql.Should().Contain("Address");
|
||||
if (filter == "job-bucket")
|
||||
sql.Should().Contain("TotalJobs");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildDirectoryUnionQuery_TranslatesAddressSearchForSqlServer()
|
||||
{
|
||||
using var context = NewSqlServerContext();
|
||||
var service = new VendorDataService(context);
|
||||
|
||||
var sql = service.BuildDirectoryUnionQuery(search: "123 main").ToQueryString();
|
||||
|
||||
sql.Should().Contain("SELECT");
|
||||
sql.Should().Contain("UNION");
|
||||
sql.Should().Contain("Address");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetCompaniesForFacetsAsync_IncludesZeroTechnicianCompanyForActiveAndNullStatus()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -682,6 +682,34 @@ public class VendorServiceTests
|
|||
facets.Trades.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetFacetsAsync_LocationsIncludeAddressOnlyCompaniesWithAddressLabel()
|
||||
{
|
||||
var data = new Mock<IVendorDataService>();
|
||||
data.Setup(x => x.GetCompaniesForFacetsAsync(null, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new List<VendorCompany>
|
||||
{
|
||||
new() { Id = 1, Name = "Address Only Co", Address = "789 Warehouse Row" },
|
||||
new() { Id = 2, Name = "City State Co", Address = "12 Ignored Way", City = "Denver", State = "CO" },
|
||||
new() { Id = 3, Name = "Nowhere Co" }
|
||||
});
|
||||
data.Setup(x => x.GetActiveTradesAsync(It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new List<Trade>());
|
||||
|
||||
var facets = await NewService(data).GetFacetsAsync(null, CancellationToken.None);
|
||||
|
||||
facets.Locations.Select(l => l.Label).Should().BeEquivalentTo(
|
||||
new[] { "789 Warehouse Row", "Denver, CO" });
|
||||
|
||||
var addressOnly = facets.Locations.Single(l => l.Label == "789 Warehouse Row");
|
||||
addressOnly.City.Should().BeNull();
|
||||
addressOnly.State.Should().BeNull();
|
||||
|
||||
var cityState = facets.Locations.Single(l => l.Label == "Denver, CO");
|
||||
cityState.City.Should().Be("Denver");
|
||||
cityState.State.Should().Be("CO");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetFacetsAsync_CompaniesAreUniqueAndOrderedCaseInsensitively()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -197,9 +197,15 @@ namespace SeaHaven.DataServices.Implementation
|
|||
? v.Company.Email
|
||||
: v.Email,
|
||||
Phone = v.Phone,
|
||||
CompanyPhone = v.CompanyPhone,
|
||||
CompanyPhone = v.Company != null &&
|
||||
v.Company.CompanyPhone != null && v.Company.CompanyPhone.Trim() != ""
|
||||
? v.Company.CompanyPhone
|
||||
: v.CompanyPhone,
|
||||
PreferredContact = v.PreferredContact,
|
||||
Address = v.Address,
|
||||
Address = v.Company != null &&
|
||||
v.Company.Address != null && v.Company.Address.Trim() != ""
|
||||
? v.Company.Address
|
||||
: v.Address,
|
||||
City = v.Company != null &&
|
||||
((v.Company.City != null && v.Company.City.Trim() != "") ||
|
||||
(v.Company.State != null && v.Company.State.Trim() != ""))
|
||||
|
|
@ -291,6 +297,9 @@ namespace SeaHaven.DataServices.Implementation
|
|||
(row.TradeSpecialties ?? "").ToLower().Contains(normalizedSearch) ||
|
||||
(row.City ?? "").ToLower().Contains(normalizedSearch) ||
|
||||
(row.State ?? "").ToLower().Contains(normalizedSearch) ||
|
||||
(((row.City == null || row.City.Trim() == "") &&
|
||||
(row.State == null || row.State.Trim() == "")) &&
|
||||
(row.Address ?? "").ToLower().Contains(normalizedSearch)) ||
|
||||
(row.Zip ?? "").ToLower().Contains(normalizedSearch));
|
||||
}
|
||||
|
||||
|
|
@ -500,14 +509,22 @@ namespace SeaHaven.DataServices.Implementation
|
|||
{
|
||||
var city = Expression.Property(vendor, nameof(Vendor.City));
|
||||
var state = Expression.Property(vendor, nameof(Vendor.State));
|
||||
var address = Expression.Property(vendor, nameof(Vendor.Address));
|
||||
var cityBlank = IsBlank(city);
|
||||
var stateBlank = IsBlank(state);
|
||||
var addressBlank = IsBlank(address);
|
||||
|
||||
var cityCommaState = Concat(Concat(city, Expression.Constant(", ")), state);
|
||||
|
||||
var onlyState = Expression.Condition(stateBlank, Expression.Constant(string.Empty), state);
|
||||
var cityOrBoth = Expression.Condition(stateBlank, city, cityCommaState);
|
||||
return Expression.Condition(cityBlank, onlyState, cityOrBoth);
|
||||
var cityStateLabel = Expression.Condition(cityBlank, onlyState, cityOrBoth);
|
||||
|
||||
// Exact Location label: City/State whenever either structured value is
|
||||
// present, otherwise the (projected) Address. The blank guard keeps the
|
||||
// label non-null so the caller's ToLower never sees a null reference.
|
||||
var addressLabel = Expression.Condition(addressBlank, Expression.Constant(string.Empty), address);
|
||||
return Expression.Condition(Expression.AndAlso(cityBlank, stateBlank), addressLabel, cityStateLabel);
|
||||
}
|
||||
|
||||
private static Expression IsBlank(Expression value)
|
||||
|
|
|
|||
|
|
@ -681,13 +681,20 @@ namespace SeaHaven.Services.Implementation
|
|||
|
||||
var trades = canonicalTrades.Select(t => t.Name).ToList();
|
||||
|
||||
// Exact Location label rule (mirrors the directory filter): City/State
|
||||
// whenever either structured value is present, otherwise Address.
|
||||
// Address-only companies are therefore represented in the facets.
|
||||
var locations = companies
|
||||
.Where(c => !string.IsNullOrWhiteSpace(c.City) || !string.IsNullOrWhiteSpace(c.State))
|
||||
.Where(c => !string.IsNullOrWhiteSpace(c.City)
|
||||
|| !string.IsNullOrWhiteSpace(c.State)
|
||||
|| !string.IsNullOrWhiteSpace(c.Address))
|
||||
.Select(c => new VendorFacetLocationDTO
|
||||
{
|
||||
City = c.City,
|
||||
State = c.State,
|
||||
Label = string.Join(", ", new[] { c.City, c.State }.Where(value => !string.IsNullOrWhiteSpace(value)))
|
||||
Label = !string.IsNullOrWhiteSpace(c.City) || !string.IsNullOrWhiteSpace(c.State)
|
||||
? string.Join(", ", new[] { c.City, c.State }.Where(value => !string.IsNullOrWhiteSpace(value)))
|
||||
: c.Address
|
||||
})
|
||||
.DistinctBy(location => location.Label, StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(location => location.Label);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue