mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 07:13:12 +00:00
feat(locations): add sites options endpoint and vendor contactName (#20)
* fix(migrations): make Phase6 completion slide-over migration idempotent Use IF EXISTS / IF COL_LENGTH guards and explicit EF migration metadata so partial environments can apply Phase 6 safely. * feat(locations): add sites options endpoint and vendor contactName Expose GET /api/locations/sites for the WO create wizard and include vendor contactName on dropdown responses. * fix(work-orders): make stacked tip build for CI quality gates --------- Co-authored-by: Arthur Bassi <arthur.winiarski.ranger@outlook.com>
This commit is contained in:
parent
af1c0d19b4
commit
8c4102b1cf
8 changed files with 163 additions and 0 deletions
109
Api.SeaHavenIndustries.Tests/LocationControllerSitesTests.cs
Normal file
109
Api.SeaHavenIndustries.Tests/LocationControllerSitesTests.cs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
using Api.SeaHavenIndustries.Controllers;
|
||||
using Data.SeaHavenIndustries;
|
||||
using FluentAssertions;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moq;
|
||||
using SeaHaven.DataServices.Implementation;
|
||||
using SeaHaven.Services.DTOs;
|
||||
using SeaHaven.Services.Implementation;
|
||||
using SeaHaven.Services.Validation;
|
||||
using Xunit;
|
||||
|
||||
namespace Api.SeaHavenIndustries.Tests;
|
||||
|
||||
public class LocationControllerSitesTests
|
||||
{
|
||||
private static ApplicationDbContext NewContext()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
||||
.Options;
|
||||
return new ApplicationDbContext(options);
|
||||
}
|
||||
|
||||
private static LocationController NewController(ApplicationDbContext ctx)
|
||||
{
|
||||
var dataService = new LocationDataService(ctx);
|
||||
var service = new LocationService(
|
||||
dataService,
|
||||
Mock.Of<ICreateLocationValidation>(),
|
||||
Mock.Of<IUpdateLocationValidation>());
|
||||
|
||||
var controller = new LocationController(service, dataService)
|
||||
{
|
||||
ControllerContext = new ControllerContext
|
||||
{
|
||||
HttpContext = new DefaultHttpContext()
|
||||
}
|
||||
};
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
private static async Task SeedSitesAsync(ApplicationDbContext ctx)
|
||||
{
|
||||
ctx.Locations.AddRange(
|
||||
new Locations { Id = 1, Name = "STL8", City = "St. Louis", State = "MO" },
|
||||
new Locations { Id = 2, Name = "BK5", City = "Dallas", State = "TX" },
|
||||
new Locations { Id = 3, Name = "IND9", City = "Indianapolis", State = "IN" },
|
||||
new Locations { Id = 4, Name = "DFW8", City = "Dallas", State = "TX" });
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSites_ReturnsIdCodeCityState_OrderedByCode()
|
||||
{
|
||||
await using var ctx = NewContext();
|
||||
await SeedSitesAsync(ctx);
|
||||
var controller = NewController(ctx);
|
||||
|
||||
var result = await controller.GetSites();
|
||||
|
||||
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||
var data = ok.Value.Should().BeAssignableTo<IReadOnlyList<SiteOptionDTO>>().Subject;
|
||||
|
||||
data.Should().HaveCount(4);
|
||||
data.Select(s => s.Code).Should().ContainInOrder("BK5", "DFW8", "IND9", "STL8");
|
||||
data[0].Should().BeEquivalentTo(new SiteOptionDTO
|
||||
{
|
||||
Id = 2,
|
||||
Code = "BK5",
|
||||
City = "Dallas",
|
||||
State = "TX"
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSites_FiltersByCode()
|
||||
{
|
||||
await using var ctx = NewContext();
|
||||
await SeedSitesAsync(ctx);
|
||||
var controller = NewController(ctx);
|
||||
|
||||
var result = await controller.GetSites("bk5");
|
||||
|
||||
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||
var data = ok.Value.Should().BeAssignableTo<IReadOnlyList<SiteOptionDTO>>().Subject;
|
||||
|
||||
data.Should().ContainSingle();
|
||||
data[0].Code.Should().Be("BK5");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetSites_FiltersByCity()
|
||||
{
|
||||
await using var ctx = NewContext();
|
||||
await SeedSitesAsync(ctx);
|
||||
var controller = NewController(ctx);
|
||||
|
||||
var result = await controller.GetSites("Dallas");
|
||||
|
||||
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
|
||||
var data = ok.Value.Should().BeAssignableTo<IReadOnlyList<SiteOptionDTO>>().Subject;
|
||||
|
||||
data.Should().HaveCount(2);
|
||||
data.Select(s => s.Code).Should().BeEquivalentTo("BK5", "DFW8");
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,13 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
_locationDataService = locationDataService;
|
||||
}
|
||||
|
||||
[HttpGet("sites")]
|
||||
public async Task<IActionResult> GetSites(string? search = null)
|
||||
{
|
||||
var data = await _locationService.GetSiteOptionsAsync(search);
|
||||
return Ok(data);
|
||||
}
|
||||
|
||||
[HttpGet("GetLocationList")]
|
||||
public async Task<IActionResult> GetLocationList(string? search = "", int page = 1, int pageSize = 10)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
{
|
||||
v.Id,
|
||||
v.CompanyName,
|
||||
v.ContactName,
|
||||
v.TradeSpecialties,
|
||||
v.Address,
|
||||
v.City,
|
||||
|
|
@ -191,6 +192,7 @@ namespace Api.SeaHavenIndustries.Controllers
|
|||
{
|
||||
v.Id,
|
||||
v.CompanyName,
|
||||
v.ContactName,
|
||||
v.TradeSpecialties,
|
||||
address = string.Join(", ", addr),
|
||||
distanceMiles = distance.HasValue ? Math.Round(distance.Value, 1) : (double?)null
|
||||
|
|
|
|||
|
|
@ -60,6 +60,24 @@ namespace SeaHaven.DataServices.Implementation
|
|||
return (items, totalCount);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<SiteOptionRow>> GetSiteOptionsAsync(string? search = null)
|
||||
{
|
||||
var query = _context.Locations.AsNoTracking();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
var s = search.ToLower();
|
||||
query = query.Where(l =>
|
||||
(l.Name ?? "").ToLower().Contains(s) ||
|
||||
(l.City ?? "").ToLower().Contains(s));
|
||||
}
|
||||
|
||||
return await query
|
||||
.OrderBy(l => l.Name)
|
||||
.Select(l => new SiteOptionRow(l.Id, l.Name, l.City, l.State))
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Locations> AddAsync(Locations location)
|
||||
{
|
||||
location.CreatedDate = DateTime.Now;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ using Data.SeaHavenIndustries;
|
|||
|
||||
namespace SeaHaven.DataServices.Interfaces
|
||||
{
|
||||
public record SiteOptionRow(int Id, string? Code, string? City, string? State);
|
||||
|
||||
public interface ILocationDataService
|
||||
{
|
||||
Task<Locations?> GetByIdAsync(int id);
|
||||
|
|
@ -15,6 +17,8 @@ namespace SeaHaven.DataServices.Interfaces
|
|||
// For addressbook endpoint - returns simplified DTO
|
||||
Task<(IEnumerable<object> Items, int TotalCount)> GetAddressbookPagedAsync(int page, int pageSize, string? search = null);
|
||||
|
||||
Task<IReadOnlyList<SiteOptionRow>> GetSiteOptionsAsync(string? search = null);
|
||||
|
||||
Task<Locations> AddAsync(Locations location);
|
||||
Task UpdateAsync(Locations location);
|
||||
Task DeleteAsync(int id);
|
||||
|
|
|
|||
|
|
@ -36,4 +36,12 @@ namespace SeaHaven.Services.DTOs
|
|||
public string? Zipcode { get; set; }
|
||||
public int? AccountId { get; set; }
|
||||
}
|
||||
|
||||
public class SiteOptionDTO
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string? Code { get; set; }
|
||||
public string? City { get; set; }
|
||||
public string? State { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,20 @@ namespace SeaHaven.Services.Implementation
|
|||
};
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<SiteOptionDTO>> GetSiteOptionsAsync(string? search = null)
|
||||
{
|
||||
var rows = await _locationDataService.GetSiteOptionsAsync(search);
|
||||
return rows
|
||||
.Select(r => new SiteOptionDTO
|
||||
{
|
||||
Id = r.Id,
|
||||
Code = r.Code,
|
||||
City = r.City,
|
||||
State = r.State
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<LocationDTO> CreateLocationAsync(CreateLocationDTO dto, string userId)
|
||||
{
|
||||
var validationResult = await _createValidator.ValidateAsync(dto);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ namespace SeaHaven.Services.Interfaces
|
|||
Task<IEnumerable<LocationDTO>> GetAllLocationsAsync();
|
||||
Task<IEnumerable<LocationDTO>> GetLocationsByAccountIdAsync(int accountId);
|
||||
Task<PagedResult<LocationDTO>> GetLocationsPagedAsync(int page, int pageSize, string? search = null);
|
||||
Task<IReadOnlyList<SiteOptionDTO>> GetSiteOptionsAsync(string? search = null);
|
||||
Task<LocationDTO> CreateLocationAsync(CreateLocationDTO dto, string userId);
|
||||
Task<LocationDTO> UpdateLocationAsync(int id, UpdateLocationDTO dto, string userId);
|
||||
Task DeleteLocationAsync(int id, string userId);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue