mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 08:23:12 +00:00
fix(vendors): resolve list location from company address when city/state blank
The Vendors directory renders Location from City/State only, but the roster form keeps its City/State inputs hidden, so vendors captured through the UI store their location in the free-text Address field and the column falls back to an em dash while the detail drawer shows the value. Resolve location through a single rule -- structured City/State when present, otherwise the free-text Address -- and apply it consistently so the column, search, the location facet and the location filter cannot disagree. - Directory rows fall back to the linked company's Address when the technician has none, matching the Email precedence already established for these rows. - BuildLocationLabelBody, which backs both the filter and the facet label, falls back to Address when City/State are blank. - Directory search matches Address so a row is findable by what it displays. - The locations facet offers address-only companies as filter options.
This commit is contained in:
parent
8e749047a3
commit
8086b6c85f
4 changed files with 183 additions and 4 deletions
|
|
@ -472,6 +472,127 @@ public class VendorDataServiceTests
|
|||
locationRow.State.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDirectoryPagedAsync_UsesCompanyAddressWhenTechnicianAddressBlank()
|
||||
{
|
||||
await using var context = NewContext();
|
||||
var company = new VendorCompany
|
||||
{
|
||||
Name = "Address Co",
|
||||
NormalizedName = "address co",
|
||||
Address = "Santa Monica, CA"
|
||||
};
|
||||
context.VendorCompanies.Add(company);
|
||||
context.Vendors.AddRange(
|
||||
new Vendor
|
||||
{
|
||||
CompanyName = "Address Co",
|
||||
ContactName = "Blank Address",
|
||||
Address = " ",
|
||||
IsActive = true,
|
||||
IsDeleted = false,
|
||||
Company = company
|
||||
},
|
||||
new Vendor
|
||||
{
|
||||
CompanyName = "Address Co",
|
||||
ContactName = "Own Address",
|
||||
Address = "1 Technician Way",
|
||||
IsActive = true,
|
||||
IsDeleted = false,
|
||||
Company = company
|
||||
},
|
||||
new Vendor
|
||||
{
|
||||
CompanyName = "Unlinked Co",
|
||||
ContactName = "Unlinked",
|
||||
Address = "9 Legacy Road",
|
||||
IsActive = true,
|
||||
IsDeleted = false,
|
||||
CompanyId = null
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = new VendorDataService(context);
|
||||
var (items, _) = await service.GetDirectoryPagedAsync(1, 50, isActive: true);
|
||||
|
||||
items.Single(i => i.ContactName == "Blank Address").Address.Should().Be("Santa Monica, CA");
|
||||
items.Single(i => i.ContactName == "Own Address").Address.Should().Be("1 Technician Way");
|
||||
items.Single(i => i.ContactName == "Unlinked").Address.Should().Be("9 Legacy Road");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDirectoryPagedAsync_SearchAndLocationFilterFallBackToAddressWhenCityStateBlank()
|
||||
{
|
||||
await using var context = NewContext();
|
||||
var company = new VendorCompany
|
||||
{
|
||||
Name = "Vinewood LLC",
|
||||
NormalizedName = "vinewood llc",
|
||||
Address = "Santa Monica, CA"
|
||||
};
|
||||
context.VendorCompanies.Add(company);
|
||||
context.Vendors.Add(new Vendor
|
||||
{
|
||||
CompanyName = "Vinewood LLC",
|
||||
ContactName = "Trevor",
|
||||
IsActive = true,
|
||||
IsDeleted = false,
|
||||
Company = company
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = new VendorDataService(context);
|
||||
var search = await service.GetDirectoryPagedAsync(1, 50, search: "santa monica", isActive: true);
|
||||
var location = await service.GetDirectoryPagedAsync(
|
||||
1,
|
||||
50,
|
||||
isActive: true,
|
||||
locations: new[] { "Santa Monica, CA" });
|
||||
|
||||
search.Items.Should().ContainSingle(i => i.ContactName == "Trevor");
|
||||
location.Items.Should().ContainSingle(i => i.ContactName == "Trevor");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDirectoryPagedAsync_StructuredCityStateStillWinsOverAddress()
|
||||
{
|
||||
await using var context = NewContext();
|
||||
var company = new VendorCompany
|
||||
{
|
||||
Name = "Both Co",
|
||||
NormalizedName = "both co",
|
||||
Address = "Santa Monica, CA",
|
||||
City = "Chicago",
|
||||
State = "IL"
|
||||
};
|
||||
context.VendorCompanies.Add(company);
|
||||
context.Vendors.Add(new Vendor
|
||||
{
|
||||
CompanyName = "Both Co",
|
||||
ContactName = "Sam",
|
||||
IsActive = true,
|
||||
IsDeleted = false,
|
||||
Company = company
|
||||
});
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
var service = new VendorDataService(context);
|
||||
var byStructured = await service.GetDirectoryPagedAsync(
|
||||
1,
|
||||
50,
|
||||
isActive: true,
|
||||
locations: new[] { "Chicago, IL" });
|
||||
var byAddress = await service.GetDirectoryPagedAsync(
|
||||
1,
|
||||
50,
|
||||
isActive: true,
|
||||
locations: new[] { "Santa Monica, CA" });
|
||||
|
||||
byStructured.Items.Should().ContainSingle(i => i.ContactName == "Sam");
|
||||
byAddress.Items.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("trade")]
|
||||
[InlineData("location")]
|
||||
|
|
|
|||
|
|
@ -682,6 +682,45 @@ public class VendorServiceTests
|
|||
facets.Trades.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetFacetsAsync_LocationFallsBackToAddressWhenCityAndStateAreBlank()
|
||||
{
|
||||
var data = new Mock<IVendorDataService>();
|
||||
data.Setup(x => x.GetCompaniesForFacetsAsync(true, It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new List<VendorCompany>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = 1,
|
||||
Name = "Vinewood LLC",
|
||||
Address = " Santa Monica, CA ",
|
||||
City = null,
|
||||
State = " "
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = 2,
|
||||
Name = "Structured Co",
|
||||
Address = "Ignored Addr",
|
||||
City = "Chicago",
|
||||
State = "IL"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = 3,
|
||||
Name = "No Location Co"
|
||||
}
|
||||
});
|
||||
data.Setup(x => x.GetActiveTradesAsync(It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new List<Trade>());
|
||||
|
||||
var facets = await NewService(data).GetFacetsAsync(true, CancellationToken.None);
|
||||
|
||||
var labels = facets.Locations.Select(l => l.Label).ToList();
|
||||
labels.Should().BeEquivalentTo(new[] { "Chicago, IL", "Santa Monica, CA" });
|
||||
labels.Should().NotContain("Ignored Addr");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetFacetsAsync_CompaniesAreUniqueAndOrderedCaseInsensitively()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -199,7 +199,11 @@ namespace SeaHaven.DataServices.Implementation
|
|||
Phone = v.Phone,
|
||||
CompanyPhone = v.CompanyPhone,
|
||||
PreferredContact = v.PreferredContact,
|
||||
Address = v.Address,
|
||||
Address = v.Address != null && v.Address.Trim() != ""
|
||||
? v.Address
|
||||
: v.Company != null
|
||||
? 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 +295,7 @@ namespace SeaHaven.DataServices.Implementation
|
|||
(row.TradeSpecialties ?? "").ToLower().Contains(normalizedSearch) ||
|
||||
(row.City ?? "").ToLower().Contains(normalizedSearch) ||
|
||||
(row.State ?? "").ToLower().Contains(normalizedSearch) ||
|
||||
(row.Address ?? "").ToLower().Contains(normalizedSearch) ||
|
||||
(row.Zip ?? "").ToLower().Contains(normalizedSearch));
|
||||
}
|
||||
|
||||
|
|
@ -507,7 +512,14 @@ namespace SeaHaven.DataServices.Implementation
|
|||
|
||||
var onlyState = Expression.Condition(stateBlank, Expression.Constant(string.Empty), state);
|
||||
var cityOrBoth = Expression.Condition(stateBlank, city, cityCommaState);
|
||||
return Expression.Condition(cityBlank, onlyState, cityOrBoth);
|
||||
var structured = Expression.Condition(cityBlank, onlyState, cityOrBoth);
|
||||
|
||||
// Vendors captured through the roster form carry their location in the
|
||||
// free-text Address field, so fall back to it when City/State are blank.
|
||||
// Display, search, facet and filter all resolve location through here.
|
||||
var address = Expression.Property(vendor, nameof(Vendor.Address));
|
||||
var addressOrEmpty = Expression.Coalesce(address, Expression.Constant(string.Empty));
|
||||
return Expression.Condition(IsBlank(structured), addressOrEmpty, structured);
|
||||
}
|
||||
|
||||
private static Expression IsBlank(Expression value)
|
||||
|
|
|
|||
|
|
@ -681,13 +681,20 @@ namespace SeaHaven.Services.Implementation
|
|||
|
||||
var trades = canonicalTrades.Select(t => t.Name).ToList();
|
||||
|
||||
// Location resolves to structured City/State when present and otherwise to the
|
||||
// free-text Address, matching the directory column and the location filter, so
|
||||
// a company only reachable by Address is still offered as a filter option.
|
||||
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!.Trim()
|
||||
})
|
||||
.DistinctBy(location => location.Label, StringComparer.OrdinalIgnoreCase)
|
||||
.OrderBy(location => location.Label);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue