mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-05 00:12:13 +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]
|
[Theory]
|
||||||
[InlineData("trade")]
|
[InlineData("trade")]
|
||||||
[InlineData("location")]
|
[InlineData("location")]
|
||||||
|
[InlineData("address-location")]
|
||||||
[InlineData("job-bucket")]
|
[InlineData("job-bucket")]
|
||||||
public void BuildDirectoryQuery_TranslatesRepresentativeFiltersForSqlServer(string filter)
|
public void BuildDirectoryQuery_TranslatesRepresentativeFiltersForSqlServer(string filter)
|
||||||
{
|
{
|
||||||
|
|
@ -255,6 +256,7 @@ public class VendorDataServiceTests
|
||||||
{
|
{
|
||||||
"trade" => service.BuildDirectoryQuery(trades: new[] { "Plumbing" }),
|
"trade" => service.BuildDirectoryQuery(trades: new[] { "Plumbing" }),
|
||||||
"location" => service.BuildDirectoryQuery(locations: new[] { "Chicago, IL" }),
|
"location" => service.BuildDirectoryQuery(locations: new[] { "Chicago, IL" }),
|
||||||
|
"address-location" => service.BuildDirectoryQuery(locations: new[] { "123 Main St" }),
|
||||||
_ => service.BuildDirectoryQuery(jobBuckets: new[] { "50-99" })
|
_ => service.BuildDirectoryQuery(jobBuckets: new[] { "50-99" })
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -265,6 +267,8 @@ public class VendorDataServiceTests
|
||||||
sql.Should().Contain("TradeSpecialties");
|
sql.Should().Contain("TradeSpecialties");
|
||||||
if (filter == "location")
|
if (filter == "location")
|
||||||
sql.Should().Contain("City");
|
sql.Should().Contain("City");
|
||||||
|
if (filter == "address-location")
|
||||||
|
sql.Should().Contain("Address");
|
||||||
if (filter == "job-bucket")
|
if (filter == "job-bucket")
|
||||||
sql.Should().Contain("UNION");
|
sql.Should().Contain("UNION");
|
||||||
}
|
}
|
||||||
|
|
@ -472,9 +476,197 @@ public class VendorDataServiceTests
|
||||||
locationRow.State.Should().BeNull();
|
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]
|
[Theory]
|
||||||
[InlineData("trade")]
|
[InlineData("trade")]
|
||||||
[InlineData("location")]
|
[InlineData("location")]
|
||||||
|
[InlineData("address-location")]
|
||||||
[InlineData("job-bucket")]
|
[InlineData("job-bucket")]
|
||||||
public void BuildDirectoryUnionQuery_TranslatesToSqlServerUnion(string filter)
|
public void BuildDirectoryUnionQuery_TranslatesToSqlServerUnion(string filter)
|
||||||
{
|
{
|
||||||
|
|
@ -485,6 +677,7 @@ public class VendorDataServiceTests
|
||||||
{
|
{
|
||||||
"trade" => service.BuildDirectoryUnionQuery(trades: new[] { "Plumbing" }),
|
"trade" => service.BuildDirectoryUnionQuery(trades: new[] { "Plumbing" }),
|
||||||
"location" => service.BuildDirectoryUnionQuery(locations: new[] { "Chicago, IL" }),
|
"location" => service.BuildDirectoryUnionQuery(locations: new[] { "Chicago, IL" }),
|
||||||
|
"address-location" => service.BuildDirectoryUnionQuery(locations: new[] { "123 Main St" }),
|
||||||
_ => service.BuildDirectoryUnionQuery(jobBuckets: new[] { "under-50" })
|
_ => service.BuildDirectoryUnionQuery(jobBuckets: new[] { "under-50" })
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -496,10 +689,25 @@ public class VendorDataServiceTests
|
||||||
sql.Should().Contain("TradeSpecialties");
|
sql.Should().Contain("TradeSpecialties");
|
||||||
if (filter == "location")
|
if (filter == "location")
|
||||||
sql.Should().Contain("City");
|
sql.Should().Contain("City");
|
||||||
|
if (filter == "address-location")
|
||||||
|
sql.Should().Contain("Address");
|
||||||
if (filter == "job-bucket")
|
if (filter == "job-bucket")
|
||||||
sql.Should().Contain("TotalJobs");
|
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]
|
[Fact]
|
||||||
public async Task GetCompaniesForFacetsAsync_IncludesZeroTechnicianCompanyForActiveAndNullStatus()
|
public async Task GetCompaniesForFacetsAsync_IncludesZeroTechnicianCompanyForActiveAndNullStatus()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -682,6 +682,34 @@ public class VendorServiceTests
|
||||||
facets.Trades.Should().BeEmpty();
|
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]
|
[Fact]
|
||||||
public async Task GetFacetsAsync_CompaniesAreUniqueAndOrderedCaseInsensitively()
|
public async Task GetFacetsAsync_CompaniesAreUniqueAndOrderedCaseInsensitively()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -197,9 +197,15 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
? v.Company.Email
|
? v.Company.Email
|
||||||
: v.Email,
|
: v.Email,
|
||||||
Phone = v.Phone,
|
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,
|
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 &&
|
City = v.Company != null &&
|
||||||
((v.Company.City != null && v.Company.City.Trim() != "") ||
|
((v.Company.City != null && v.Company.City.Trim() != "") ||
|
||||||
(v.Company.State != null && v.Company.State.Trim() != ""))
|
(v.Company.State != null && v.Company.State.Trim() != ""))
|
||||||
|
|
@ -291,6 +297,9 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
(row.TradeSpecialties ?? "").ToLower().Contains(normalizedSearch) ||
|
(row.TradeSpecialties ?? "").ToLower().Contains(normalizedSearch) ||
|
||||||
(row.City ?? "").ToLower().Contains(normalizedSearch) ||
|
(row.City ?? "").ToLower().Contains(normalizedSearch) ||
|
||||||
(row.State ?? "").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));
|
(row.Zip ?? "").ToLower().Contains(normalizedSearch));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -500,14 +509,22 @@ namespace SeaHaven.DataServices.Implementation
|
||||||
{
|
{
|
||||||
var city = Expression.Property(vendor, nameof(Vendor.City));
|
var city = Expression.Property(vendor, nameof(Vendor.City));
|
||||||
var state = Expression.Property(vendor, nameof(Vendor.State));
|
var state = Expression.Property(vendor, nameof(Vendor.State));
|
||||||
|
var address = Expression.Property(vendor, nameof(Vendor.Address));
|
||||||
var cityBlank = IsBlank(city);
|
var cityBlank = IsBlank(city);
|
||||||
var stateBlank = IsBlank(state);
|
var stateBlank = IsBlank(state);
|
||||||
|
var addressBlank = IsBlank(address);
|
||||||
|
|
||||||
var cityCommaState = Concat(Concat(city, Expression.Constant(", ")), state);
|
var cityCommaState = Concat(Concat(city, Expression.Constant(", ")), state);
|
||||||
|
|
||||||
var onlyState = Expression.Condition(stateBlank, Expression.Constant(string.Empty), state);
|
var onlyState = Expression.Condition(stateBlank, Expression.Constant(string.Empty), state);
|
||||||
var cityOrBoth = Expression.Condition(stateBlank, city, cityCommaState);
|
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)
|
private static Expression IsBlank(Expression value)
|
||||||
|
|
|
||||||
|
|
@ -681,13 +681,20 @@ namespace SeaHaven.Services.Implementation
|
||||||
|
|
||||||
var trades = canonicalTrades.Select(t => t.Name).ToList();
|
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
|
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
|
.Select(c => new VendorFacetLocationDTO
|
||||||
{
|
{
|
||||||
City = c.City,
|
City = c.City,
|
||||||
State = c.State,
|
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)
|
.DistinctBy(location => location.Label, StringComparer.OrdinalIgnoreCase)
|
||||||
.OrderBy(location => location.Label);
|
.OrderBy(location => location.Label);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue