shoc-backend/Api.SeaHavenIndustries/Controllers/VendorController.cs
Adam Moussa 521bb0fba2 Add zip-to-zip distance calculation for vendor dispatch
- ZipCodeDistance helper with Haversine formula on 33K US zip code centroids
- Loaded as singleton from wwwroot/zipcodes.csv on startup
- Vendor Dropdown endpoint accepts siteZip, returns address and distanceMiles
- Vendors sorted by distance when siteZip provided
- Include locationZip in GetWorkorderById response
2026-04-17 11:56:06 -04:00

207 lines
7.1 KiB
C#

using Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace Api.SeaHavenIndustries.Controllers
{
[Authorize]
[ApiController]
[Route("api/[controller]")]
[Route("api/vendors")]
public class VendorController : Controller
{
private readonly ApplicationDbContext _db;
private readonly Helper.ZipCodeDistance _zipDistance;
public VendorController(ApplicationDbContext db, Helper.ZipCodeDistance zipDistance)
{
_db = db;
_zipDistance = zipDistance;
}
[HttpGet("GetVendorList")]
public IActionResult GetVendorList(string? search = "", int page = 1, int pageSize = 10)
{
var query = _db.Vendors.Where(v => v.IsActive);
if (!string.IsNullOrWhiteSpace(search))
{
var s = search.ToLower();
query = query.Where(v =>
(v.CompanyName ?? "").ToLower().Contains(s) ||
(v.ContactName ?? "").ToLower().Contains(s) ||
(v.Email ?? "").ToLower().Contains(s) ||
(v.TradeSpecialties ?? "").ToLower().Contains(s));
}
var totalCount = query.Count();
var data = query.OrderByDescending(v => v.Id)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(v => new
{
v.Id,
v.CompanyName,
v.ContactName,
v.Email,
v.Phone,
v.TradeSpecialties,
v.IsActive
})
.ToList();
return Ok(new Pagination_DTO
{
Data = data,
PageNumber = page,
PageSize = pageSize,
TotalCount = totalCount,
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize)
});
}
[HttpGet("{id}")]
public async Task<IActionResult> GetVendorById(int id)
{
var vendor = await _db.Vendors.Where(v => v.Id == id).Select(v => new
{
v.Id,
v.CompanyName,
v.ContactName,
v.Email,
v.Phone,
v.Address,
v.City,
v.State,
v.Zip,
v.TradeSpecialties,
v.IsActive
}).FirstOrDefaultAsync();
if (vendor == null)
return NotFound(new Response { Status = "Error", Message = "Vendor not found" });
return Ok(vendor);
}
[HttpPost]
public async Task<IActionResult> Create([FromBody] Vendor_DTO model)
{
try
{
var vendor = new Vendor
{
CompanyName = model.CompanyName,
ContactName = model.ContactName,
Email = model.Email,
Phone = model.Phone,
Address = model.Address,
City = model.City,
State = model.State,
Zip = model.Zip,
TradeSpecialties = model.TradeSpecialties,
IsActive = model.IsActive
};
_db.Vendors.Add(vendor);
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Vendor Created", Status = "200" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
[HttpPut("{id}")]
public async Task<IActionResult> Update(int id, [FromBody] EditVendor_DTO model)
{
try
{
var exist = await _db.Vendors.FindAsync(id);
if (exist == null)
return BadRequest(new Response { Status = "Error", Message = "Vendor not found" });
exist.CompanyName = model.CompanyName;
exist.ContactName = model.ContactName;
exist.Email = model.Email;
exist.Phone = model.Phone;
exist.Address = model.Address;
exist.City = model.City;
exist.State = model.State;
exist.Zip = model.Zip;
exist.TradeSpecialties = model.TradeSpecialties;
exist.IsActive = model.IsActive;
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Vendor Updated", Status = "200" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id)
{
var exist = await _db.Vendors.FindAsync(id);
if (exist == null)
return BadRequest(new Response { Status = "Error", Message = "Vendor not found" });
_db.Vendors.Remove(exist);
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Vendor Deleted", Status = "200" });
}
[HttpGet("Dropdown")]
public async Task<IActionResult> GetDropdown([FromQuery] string? trade = null, [FromQuery] string? siteZip = null)
{
var query = _db.Vendors.Where(v => v.IsActive);
var vendors = await query.OrderBy(v => v.CompanyName)
.Select(v => new
{
v.Id,
v.CompanyName,
v.TradeSpecialties,
v.Address,
v.City,
v.State,
v.Zip
})
.ToListAsync();
var result = vendors.Select(v =>
{
var distance = _zipDistance.GetDistanceMiles(siteZip, v.Zip);
var addr = new[] { v.Address, v.City, v.State, v.Zip }
.Where(s => !string.IsNullOrWhiteSpace(s));
return new
{
v.Id,
v.CompanyName,
v.TradeSpecialties,
address = string.Join(", ", addr),
distanceMiles = distance.HasValue ? Math.Round(distance.Value, 1) : (double?)null
};
}).ToList();
if (!string.IsNullOrWhiteSpace(trade))
{
var tradeMatched = result.Where(v => (v.TradeSpecialties ?? "").Contains(trade)).ToList();
var rest = result.Where(v => !(v.TradeSpecialties ?? "").Contains(trade)).ToList();
result = tradeMatched.Concat(rest).ToList();
}
if (!string.IsNullOrWhiteSpace(siteZip))
{
result = result.OrderBy(v => v.distanceMiles ?? 99999).ToList();
}
return Ok(result);
}
}
}