mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-10-04 12:32:10 +00:00
fix(vendors): preserve technician list contract
This commit is contained in:
parent
3eb56a690b
commit
35b647c0f2
7 changed files with 371 additions and 15 deletions
|
|
@ -39,7 +39,7 @@ public class VendorControllerTests
|
||||||
var service = new Mock<IVendorService>();
|
var service = new Mock<IVendorService>();
|
||||||
using var cancellation = new CancellationTokenSource();
|
using var cancellation = new CancellationTokenSource();
|
||||||
var token = cancellation.Token;
|
var token = cancellation.Token;
|
||||||
service.Setup(x => x.GetVendorDirectoryPagedAsync(
|
service.Setup(x => x.GetVendorTechnicianDirectoryPagedAsync(
|
||||||
1,
|
1,
|
||||||
10,
|
10,
|
||||||
"clayton",
|
"clayton",
|
||||||
|
|
@ -53,7 +53,7 @@ public class VendorControllerTests
|
||||||
|
|
||||||
await NewController(service).GetVendorList(search: "clayton", cancellationToken: token);
|
await NewController(service).GetVendorList(search: "clayton", cancellationToken: token);
|
||||||
|
|
||||||
service.Verify(x => x.GetVendorDirectoryPagedAsync(
|
service.Verify(x => x.GetVendorTechnicianDirectoryPagedAsync(
|
||||||
1,
|
1,
|
||||||
10,
|
10,
|
||||||
"clayton",
|
"clayton",
|
||||||
|
|
@ -63,6 +63,175 @@ public class VendorControllerTests
|
||||||
It.IsAny<IReadOnlyCollection<string>?>(),
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
It.IsAny<IReadOnlyCollection<string>?>(),
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
token), Times.Once);
|
token), Times.Once);
|
||||||
|
service.Verify(x => x.GetVendorCompanyDirectoryPagedAsync(
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<string?>(),
|
||||||
|
It.IsAny<bool?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<CancellationToken>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetVendorList_ReturnsIndividualTechnicianRowsWithoutGroupedTechnicians()
|
||||||
|
{
|
||||||
|
var service = new Mock<IVendorService>();
|
||||||
|
service.Setup(x => x.GetVendorTechnicianDirectoryPagedAsync(
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<string?>(),
|
||||||
|
It.IsAny<bool?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(new PagedResult<VendorDirectoryItemDTO>
|
||||||
|
{
|
||||||
|
Items = new List<VendorDirectoryItemDTO>
|
||||||
|
{
|
||||||
|
new() { Id = 7, CompanyId = 3, CompanyName = "Gateway Plumbing", ContactName = "Tech A" },
|
||||||
|
new() { Id = 9, CompanyId = 3, CompanyName = "Gateway Plumbing", ContactName = "Tech B" }
|
||||||
|
},
|
||||||
|
TotalCount = 2,
|
||||||
|
Page = 1,
|
||||||
|
PageSize = 10
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = await NewController(service).GetVendorList();
|
||||||
|
|
||||||
|
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var paged = ok.Value.Should().BeOfType<Pagination_DTO>().Subject;
|
||||||
|
var rows = ((System.Collections.IEnumerable)paged.Data!).Cast<object>().ToList();
|
||||||
|
|
||||||
|
// Legacy contract: same-company technicians stay as distinct top-level rows
|
||||||
|
// carrying their own vendor ids, and the response shape has no grouped
|
||||||
|
// Technicians property.
|
||||||
|
rows.Should().HaveCount(2);
|
||||||
|
rows.Select(r => r.GetType().GetProperty("Id")!.GetValue(r)).Should().BeEquivalentTo(new[] { 7, 9 });
|
||||||
|
rows.Should().OnlyContain(r => r.GetType().GetProperty("Technicians") == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetVendorList_PreservesLegacyPageSizeAboveGroupedDirectoryBound()
|
||||||
|
{
|
||||||
|
var service = new Mock<IVendorService>();
|
||||||
|
service.Setup(x => x.GetVendorTechnicianDirectoryPagedAsync(
|
||||||
|
1,
|
||||||
|
500,
|
||||||
|
It.IsAny<string?>(),
|
||||||
|
It.IsAny<bool?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<CancellationToken>()))
|
||||||
|
.ReturnsAsync(new PagedResult<VendorDirectoryItemDTO> { TotalCount = 420, Page = 1, PageSize = 500 });
|
||||||
|
|
||||||
|
var result = await NewController(service).GetVendorList(page: 1, pageSize: 500);
|
||||||
|
|
||||||
|
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var paged = ok.Value.Should().BeOfType<Pagination_DTO>().Subject;
|
||||||
|
paged.PageSize.Should().Be(500);
|
||||||
|
paged.TotalPages.Should().Be(1);
|
||||||
|
service.VerifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetVendorDirectoryList_DelegatesToCompanyDirectoryServiceWithNestedTechnicians()
|
||||||
|
{
|
||||||
|
var service = new Mock<IVendorService>();
|
||||||
|
using var cancellation = new CancellationTokenSource();
|
||||||
|
var token = cancellation.Token;
|
||||||
|
service.Setup(x => x.GetVendorCompanyDirectoryPagedAsync(
|
||||||
|
1,
|
||||||
|
10,
|
||||||
|
"clayton",
|
||||||
|
true,
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
token))
|
||||||
|
.ReturnsAsync(new PagedResult<VendorDirectoryItemDTO>
|
||||||
|
{
|
||||||
|
Items = new List<VendorDirectoryItemDTO>
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Id = 9,
|
||||||
|
CompanyId = 3,
|
||||||
|
CompanyName = "Gateway Plumbing",
|
||||||
|
Email = "company@gatewayplumbing.example",
|
||||||
|
TotalJobs = 6,
|
||||||
|
Technicians = new List<VendorDirectoryTechnicianDTO>
|
||||||
|
{
|
||||||
|
new() { Id = 7, ContactName = "Tech A", Email = "tech.a@example.com" },
|
||||||
|
new() { Id = 9, ContactName = "Tech B", Email = "tech.b@example.com" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TotalCount = 1,
|
||||||
|
Page = 1,
|
||||||
|
PageSize = 10
|
||||||
|
});
|
||||||
|
|
||||||
|
var result = await NewController(service).GetVendorDirectoryList(search: "clayton", cancellationToken: token);
|
||||||
|
|
||||||
|
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||||
|
var paged = ok.Value.Should().BeOfType<Pagination_DTO>().Subject;
|
||||||
|
var company = ((System.Collections.IEnumerable)paged.Data!).Cast<object>().Single();
|
||||||
|
|
||||||
|
// SH-281: company row with nested technicians; top-level Email stays the
|
||||||
|
// company email while technician emails stay on the nested rows.
|
||||||
|
company.GetType().GetProperty("Email")!.GetValue(company).Should().Be("company@gatewayplumbing.example");
|
||||||
|
var technicians = (System.Collections.IEnumerable)company.GetType().GetProperty("Technicians")!.GetValue(company)!;
|
||||||
|
technicians.Cast<object>().Select(t => t.GetType().GetProperty("Id")!.GetValue(t))
|
||||||
|
.Should().BeEquivalentTo(new[] { 7, 9 });
|
||||||
|
technicians.Cast<object>().Select(t => t.GetType().GetProperty("Email")!.GetValue(t))
|
||||||
|
.Should().BeEquivalentTo(new[] { "tech.a@example.com", "tech.b@example.com" });
|
||||||
|
|
||||||
|
service.Verify(x => x.GetVendorCompanyDirectoryPagedAsync(
|
||||||
|
1,
|
||||||
|
10,
|
||||||
|
"clayton",
|
||||||
|
true,
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
token), Times.Once);
|
||||||
|
service.Verify(x => x.GetVendorTechnicianDirectoryPagedAsync(
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<int>(),
|
||||||
|
It.IsAny<string?>(),
|
||||||
|
It.IsAny<bool?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<CancellationToken>()), Times.Never);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void VendorDirectoryEndpoints_AdvertiseDistinctCompatibilityAndCompanyRoutes()
|
||||||
|
{
|
||||||
|
var legacyRoutes = typeof(VendorController)
|
||||||
|
.GetMethod(nameof(VendorController.GetVendorList))!
|
||||||
|
.GetCustomAttributes(typeof(HttpGetAttribute), inherit: false)
|
||||||
|
.Cast<HttpGetAttribute>()
|
||||||
|
.Select(attribute => attribute.Template);
|
||||||
|
var companyRoutes = typeof(VendorController)
|
||||||
|
.GetMethod(nameof(VendorController.GetVendorDirectoryList))!
|
||||||
|
.GetCustomAttributes(typeof(HttpGetAttribute), inherit: false)
|
||||||
|
.Cast<HttpGetAttribute>()
|
||||||
|
.Select(attribute => attribute.Template);
|
||||||
|
|
||||||
|
legacyRoutes.Should().BeEquivalentTo("GetVendorList");
|
||||||
|
companyRoutes.Should().BeEquivalentTo("GetVendorDirectoryList");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -152,7 +152,7 @@ public class VendorServiceTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetVendorDirectoryPaged_ForwardsCancellationToken()
|
public async Task GetVendorCompanyDirectoryPaged_ForwardsCancellationTokenAndMapsNestedTechnicians()
|
||||||
{
|
{
|
||||||
var data = new Mock<IVendorDataService>();
|
var data = new Mock<IVendorDataService>();
|
||||||
using var cancellation = new CancellationTokenSource();
|
using var cancellation = new CancellationTokenSource();
|
||||||
|
|
@ -175,6 +175,7 @@ public class VendorServiceTests
|
||||||
CompanyId = 3,
|
CompanyId = 3,
|
||||||
CompanyName = "Gateway Plumbing",
|
CompanyName = "Gateway Plumbing",
|
||||||
ContactName = "Primary Tech",
|
ContactName = "Primary Tech",
|
||||||
|
Email = "company@gatewayplumbing.example",
|
||||||
TotalJobs = 4,
|
TotalJobs = 4,
|
||||||
Technicians = new[]
|
Technicians = new[]
|
||||||
{
|
{
|
||||||
|
|
@ -190,14 +191,16 @@ public class VendorServiceTests
|
||||||
}
|
}
|
||||||
}.AsEnumerable().ToList(), 1));
|
}.AsEnumerable().ToList(), 1));
|
||||||
|
|
||||||
var result = await NewService(data).GetVendorDirectoryPagedAsync(
|
var result = await NewService(data).GetVendorCompanyDirectoryPagedAsync(
|
||||||
1,
|
1,
|
||||||
12,
|
12,
|
||||||
"clayton",
|
"clayton",
|
||||||
true,
|
true,
|
||||||
cancellationToken: token);
|
cancellationToken: token);
|
||||||
|
|
||||||
result.Items.Single().Technicians.Should().ContainSingle()
|
var group = result.Items.Single();
|
||||||
|
group.Email.Should().Be("company@gatewayplumbing.example");
|
||||||
|
group.Technicians.Should().ContainSingle()
|
||||||
.Which.Email.Should().Be("primary@example.com");
|
.Which.Email.Should().Be("primary@example.com");
|
||||||
|
|
||||||
data.Verify(x => x.GetCompanyDirectoryPagedAsync(
|
data.Verify(x => x.GetCompanyDirectoryPagedAsync(
|
||||||
|
|
@ -213,7 +216,7 @@ public class VendorServiceTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetVendorDirectoryPaged_BoundsPageInputsBeforeBuildingTheGroupKeyPredicate()
|
public async Task GetVendorCompanyDirectoryPaged_BoundsPageInputsBeforeBuildingTheGroupKeyPredicate()
|
||||||
{
|
{
|
||||||
var data = new Mock<IVendorDataService>();
|
var data = new Mock<IVendorDataService>();
|
||||||
data.Setup(x => x.GetCompanyDirectoryPagedAsync(
|
data.Setup(x => x.GetCompanyDirectoryPagedAsync(
|
||||||
|
|
@ -228,13 +231,62 @@ public class VendorServiceTests
|
||||||
It.IsAny<CancellationToken>()))
|
It.IsAny<CancellationToken>()))
|
||||||
.ReturnsAsync((new List<VendorCompanyGroup>(), 0));
|
.ReturnsAsync((new List<VendorCompanyGroup>(), 0));
|
||||||
|
|
||||||
var result = await NewService(data).GetVendorDirectoryPagedAsync(0, 500);
|
var result = await NewService(data).GetVendorCompanyDirectoryPagedAsync(0, 500);
|
||||||
|
|
||||||
result.Page.Should().Be(1);
|
result.Page.Should().Be(1);
|
||||||
result.PageSize.Should().Be(100);
|
result.PageSize.Should().Be(100);
|
||||||
data.VerifyAll();
|
data.VerifyAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetVendorTechnicianDirectoryPaged_ForwardsRawPaginationAndCancellationToken()
|
||||||
|
{
|
||||||
|
var data = new Mock<IVendorDataService>();
|
||||||
|
using var cancellation = new CancellationTokenSource();
|
||||||
|
var token = cancellation.Token;
|
||||||
|
data.Setup(x => x.GetDirectoryPagedAsync(
|
||||||
|
2,
|
||||||
|
500,
|
||||||
|
"clayton",
|
||||||
|
true,
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
token))
|
||||||
|
.ReturnsAsync((new[]
|
||||||
|
{
|
||||||
|
new VendorDirectoryRow { VendorId = 7, CompanyId = 3, CompanyName = "Gateway Plumbing", ContactName = "Tech A" },
|
||||||
|
new VendorDirectoryRow { VendorId = 9, CompanyId = 3, CompanyName = "Gateway Plumbing", ContactName = "Tech B" }
|
||||||
|
}.AsEnumerable().ToList(), 2));
|
||||||
|
|
||||||
|
var result = await NewService(data).GetVendorTechnicianDirectoryPagedAsync(
|
||||||
|
2,
|
||||||
|
500,
|
||||||
|
"clayton",
|
||||||
|
true,
|
||||||
|
cancellationToken: token);
|
||||||
|
|
||||||
|
// Legacy pagination semantics: page and pageSize (including 500) pass through
|
||||||
|
// unclamped, and same-company technicians stay as distinct rows.
|
||||||
|
result.Page.Should().Be(2);
|
||||||
|
result.PageSize.Should().Be(500);
|
||||||
|
result.Items.Should().HaveCount(2);
|
||||||
|
result.Items.Select(v => v.Id).Should().BeEquivalentTo(new[] { 7, 9 });
|
||||||
|
result.Items.Should().OnlyContain(v => v.Technicians.Count == 0);
|
||||||
|
|
||||||
|
data.Verify(x => x.GetDirectoryPagedAsync(
|
||||||
|
2,
|
||||||
|
500,
|
||||||
|
"clayton",
|
||||||
|
true,
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
It.IsAny<IReadOnlyCollection<string>?>(),
|
||||||
|
token), Times.Once);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task GetVendorById_CountsDistinctHistoricalWorkOrders()
|
public async Task GetVendorById_CountsDistinctHistoricalWorkOrders()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,9 @@ namespace Api.SeaHavenIndustries.Controllers
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SH-198 legacy per-technician directory contract. Existing consumers depend
|
||||||
|
// on one row per technician (no grouped Technicians) and on raw pagination
|
||||||
|
// semantics, including pageSize values above the grouped-directory bound.
|
||||||
[HttpGet("GetVendorList")]
|
[HttpGet("GetVendorList")]
|
||||||
public async Task<IActionResult> GetVendorList(
|
public async Task<IActionResult> GetVendorList(
|
||||||
string? search = "",
|
string? search = "",
|
||||||
|
|
@ -38,7 +41,65 @@ namespace Api.SeaHavenIndustries.Controllers
|
||||||
[FromQuery] string[]? jobBuckets = null,
|
[FromQuery] string[]? jobBuckets = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var pagedResult = await _vendorService.GetVendorDirectoryPagedAsync(
|
var pagedResult = await _vendorService.GetVendorTechnicianDirectoryPagedAsync(
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
search,
|
||||||
|
isActive,
|
||||||
|
companies,
|
||||||
|
trades,
|
||||||
|
locations,
|
||||||
|
jobBuckets,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
var data = pagedResult.Items.Select(v => new
|
||||||
|
{
|
||||||
|
v.Id,
|
||||||
|
CompanyName = v.CompanyName,
|
||||||
|
v.CompanyId,
|
||||||
|
v.ContactName,
|
||||||
|
v.Email,
|
||||||
|
v.Phone,
|
||||||
|
v.CompanyPhone,
|
||||||
|
v.PreferredContact,
|
||||||
|
v.Address,
|
||||||
|
v.City,
|
||||||
|
v.State,
|
||||||
|
Zip = v.Zipcode,
|
||||||
|
v.TradeSpecialties,
|
||||||
|
v.GoogleMapsUrl,
|
||||||
|
v.Notes,
|
||||||
|
v.IsActive,
|
||||||
|
v.TotalJobs
|
||||||
|
});
|
||||||
|
|
||||||
|
return Ok(new Pagination_DTO
|
||||||
|
{
|
||||||
|
Data = data,
|
||||||
|
PageNumber = page,
|
||||||
|
PageSize = pageSize,
|
||||||
|
TotalCount = pagedResult.TotalCount,
|
||||||
|
TotalPages = (int)Math.Ceiling(pagedResult.TotalCount / (double)pageSize)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// SH-281 company-grouped directory: one row per company with the full
|
||||||
|
// status-eligible technician roster nested under Technicians and bounded
|
||||||
|
// page sizes. Top-level Email is the company email; technician emails stay
|
||||||
|
// on the nested rows.
|
||||||
|
[HttpGet("GetVendorDirectoryList")]
|
||||||
|
public async Task<IActionResult> GetVendorDirectoryList(
|
||||||
|
string? search = "",
|
||||||
|
int page = 1,
|
||||||
|
int pageSize = 10,
|
||||||
|
bool? isActive = true,
|
||||||
|
[FromQuery] string[]? companies = null,
|
||||||
|
[FromQuery] string[]? trades = null,
|
||||||
|
[FromQuery] string[]? locations = null,
|
||||||
|
[FromQuery] string[]? jobBuckets = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var pagedResult = await _vendorService.GetVendorCompanyDirectoryPagedAsync(
|
||||||
page,
|
page,
|
||||||
pageSize,
|
pageSize,
|
||||||
search,
|
search,
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,9 @@ namespace SeaHaven.DataServices.Interfaces
|
||||||
IReadOnlyCollection<string>? locations = null,
|
IReadOnlyCollection<string>? locations = null,
|
||||||
IReadOnlyCollection<string>? jobBuckets = null);
|
IReadOnlyCollection<string>? jobBuckets = null);
|
||||||
|
|
||||||
// Compatibility path for existing per-technician consumers and behavior
|
// Compatibility path for existing per-technician consumers (GetVendorList)
|
||||||
// tests. Vendor/GetVendorList uses the company-grouped path below.
|
// and behavior tests. GetVendorDirectoryList uses the company-grouped path
|
||||||
|
// below.
|
||||||
Task<(IReadOnlyList<VendorDirectoryRow> Items, int TotalCount)> GetDirectoryPagedAsync(
|
Task<(IReadOnlyList<VendorDirectoryRow> Items, int TotalCount)> GetDirectoryPagedAsync(
|
||||||
int page,
|
int page,
|
||||||
int pageSize,
|
int pageSize,
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,11 @@ namespace SeaHaven.Services.DTOs
|
||||||
public bool IsActive { get; set; }
|
public bool IsActive { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// SH-281 company directory row. Id is the primary technician's vendor id (null
|
// Shared directory row. On the SH-198 legacy per-technician path (GetVendorList)
|
||||||
// for company-only groups that have zero technicians); CompanyId is populated for
|
// Id is the technician's own vendor id and Technicians stays empty — the legacy
|
||||||
|
// response shape does not project it. On the SH-281 company-grouped path
|
||||||
|
// (GetVendorDirectoryList) Id is the primary technician's vendor id (null for
|
||||||
|
// company-only groups that have zero technicians); CompanyId is populated for
|
||||||
// linked and company-only groups. Technicians carries the full status-eligible
|
// linked and company-only groups. Technicians carries the full status-eligible
|
||||||
// roster of the company group.
|
// roster of the company group.
|
||||||
public class VendorDirectoryItemDTO
|
public class VendorDirectoryItemDTO
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,63 @@ namespace SeaHaven.Services.Implementation
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<PagedResult<VendorDirectoryItemDTO>> GetVendorDirectoryPagedAsync(
|
// SH-198 legacy per-technician directory contract (GetVendorList): raw
|
||||||
|
// pagination semantics are part of the compatibility surface, so pageSize
|
||||||
|
// is forwarded unclamped.
|
||||||
|
public async Task<PagedResult<VendorDirectoryItemDTO>> GetVendorTechnicianDirectoryPagedAsync(
|
||||||
|
int page,
|
||||||
|
int pageSize,
|
||||||
|
string? search = null,
|
||||||
|
bool? isActive = true,
|
||||||
|
IReadOnlyCollection<string>? companies = null,
|
||||||
|
IReadOnlyCollection<string>? trades = null,
|
||||||
|
IReadOnlyCollection<string>? locations = null,
|
||||||
|
IReadOnlyCollection<string>? jobBuckets = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var (items, totalCount) = await _vendorDataService.GetDirectoryPagedAsync(
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
|
search,
|
||||||
|
isActive,
|
||||||
|
companies,
|
||||||
|
trades,
|
||||||
|
locations,
|
||||||
|
jobBuckets,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
return new PagedResult<VendorDirectoryItemDTO>
|
||||||
|
{
|
||||||
|
Items = items.Select(MapToDirectoryItem).ToList(),
|
||||||
|
TotalCount = totalCount,
|
||||||
|
Page = page,
|
||||||
|
PageSize = pageSize
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static VendorDirectoryItemDTO MapToDirectoryItem(VendorDirectoryRow row) => new()
|
||||||
|
{
|
||||||
|
Id = row.VendorId,
|
||||||
|
CompanyId = row.CompanyId == 0 ? null : row.CompanyId,
|
||||||
|
CompanyName = row.CompanyName,
|
||||||
|
ContactName = row.ContactName,
|
||||||
|
Email = row.Email,
|
||||||
|
Phone = row.Phone,
|
||||||
|
CompanyPhone = row.CompanyPhone,
|
||||||
|
PreferredContact = row.PreferredContact,
|
||||||
|
Address = row.Address,
|
||||||
|
City = row.City,
|
||||||
|
State = row.State,
|
||||||
|
Zipcode = row.Zip,
|
||||||
|
TradeSpecialties = row.TradeSpecialties,
|
||||||
|
GoogleMapsUrl = row.GoogleMapsUrl,
|
||||||
|
Notes = row.Notes,
|
||||||
|
IsActive = row.IsActive,
|
||||||
|
TotalJobs = row.TotalJobs
|
||||||
|
};
|
||||||
|
|
||||||
|
// SH-281 company-grouped directory (GetVendorDirectoryList).
|
||||||
|
public async Task<PagedResult<VendorDirectoryItemDTO>> GetVendorCompanyDirectoryPagedAsync(
|
||||||
int page,
|
int page,
|
||||||
int pageSize,
|
int pageSize,
|
||||||
string? search = null,
|
string? search = null,
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,22 @@ namespace SeaHaven.Services.Interfaces
|
||||||
IReadOnlyCollection<string>? locations = null,
|
IReadOnlyCollection<string>? locations = null,
|
||||||
IReadOnlyCollection<string>? jobBuckets = null);
|
IReadOnlyCollection<string>? jobBuckets = null);
|
||||||
|
|
||||||
// SH-198 vendor directory: per-technician rows UNION company-only rows.
|
// SH-198 legacy vendor directory contract backing GetVendorList: one row per
|
||||||
Task<PagedResult<VendorDirectoryItemDTO>> GetVendorDirectoryPagedAsync(
|
// technician (per-technician rows UNION company-only rows), raw pagination.
|
||||||
|
Task<PagedResult<VendorDirectoryItemDTO>> GetVendorTechnicianDirectoryPagedAsync(
|
||||||
|
int page,
|
||||||
|
int pageSize,
|
||||||
|
string? search = null,
|
||||||
|
bool? isActive = true,
|
||||||
|
IReadOnlyCollection<string>? companies = null,
|
||||||
|
IReadOnlyCollection<string>? trades = null,
|
||||||
|
IReadOnlyCollection<string>? locations = null,
|
||||||
|
IReadOnlyCollection<string>? jobBuckets = null,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
// SH-281 company directory backing GetVendorDirectoryList: company-grouped
|
||||||
|
// rows with nested technicians and bounded page sizes.
|
||||||
|
Task<PagedResult<VendorDirectoryItemDTO>> GetVendorCompanyDirectoryPagedAsync(
|
||||||
int page,
|
int page,
|
||||||
int pageSize,
|
int pageSize,
|
||||||
string? search = null,
|
string? search = null,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue