feat(nests): add part search endpoint

Add GET /api/nests/parts/search endpoint to search for parts across nests
by part name. Supports filtering by year and customer, with configurable
result limit.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-29 19:35:27 -05:00
parent b3ba888c2e
commit 4d2dd6d985
2 changed files with 145 additions and 0 deletions

View File

@@ -180,6 +180,123 @@ public class NestsController : ControllerBase
return GetNestPath(nestName, year); return GetNestPath(nestName, year);
} }
/// <summary>
/// Search for parts across nests by part name.
/// If year is not specified, searches across all years.
/// </summary>
[HttpGet("parts/search")]
public async Task<ActionResult<PartSearchResponse>> SearchParts(
[FromQuery] string search,
[FromQuery] int? year = null,
[FromQuery] string? customer = null,
[FromQuery] int limit = 100)
{
if (string.IsNullOrWhiteSpace(search))
return BadRequest(new { message = "Search term is required" });
var searchUpper = search.Trim().ToUpper();
// Get nest headers - filter by year only if specified
var nestsQuery = _db.NestHeaders
.Where(n => n.DateProgrammed != null);
if (year.HasValue)
nestsQuery = nestsQuery.Where(n => n.DateProgrammed!.Value.Year == year.Value);
if (!string.IsNullOrWhiteSpace(customer))
nestsQuery = nestsQuery.Where(n => n.CustomerName == customer || n.CustID == customer);
var nests = await nestsQuery
.Select(n => new
{
n.NestName,
n.CopyID,
n.CustomerName,
n.Comments,
n.Material,
n.MatGrade,
n.MatThick,
n.Status,
n.DateProgrammed,
n.ModifiedDate,
n.Application
})
.ToListAsync();
if (!nests.Any())
{
return Ok(new PartSearchResponse
{
SearchTerm = search,
Year = year,
TotalMatches = 0,
TotalNests = 0,
Results = []
});
}
var nestKeys = nests.Select(n => (n.NestName, n.CopyID)).ToHashSet();
// Search for parts matching the search term
var plateDetails = await _db.PlateDetails
.Where(p => p.Drawing != null && p.Drawing.ToUpper().Contains(searchUpper))
.Select(p => new
{
p.NestName,
p.CopyID,
p.Drawing,
p.QtyNstd,
p.QtyReq
})
.ToListAsync();
// Filter to only parts in our target nests
var matchingParts = plateDetails
.Where(p => nestKeys.Contains((p.NestName, p.CopyID)))
.ToList();
// Group by nest and part name
var nestLookup = nests.ToDictionary(n => (n.NestName, n.CopyID));
var allResults = matchingParts
.GroupBy(p => (p.NestName, p.CopyID, p.Drawing))
.Select(g =>
{
var nest = nestLookup[(g.Key.NestName, g.Key.CopyID)];
return new PartSearchResult
{
PartName = g.Key.Drawing ?? "",
NestName = g.Key.NestName,
Status = PepHelper.GetStatus(nest.Status),
Customer = nest.CustomerName ?? "",
Comments = nest.Comments ?? "",
MaterialNumber = int.TryParse(nest.Material, out var num) ? num : 0,
MaterialGrade = nest.MatGrade ?? "",
Thickness = nest.MatThick,
DateProgrammed = nest.DateProgrammed!.Value,
DateLastModified = nest.ModifiedDate ?? nest.DateProgrammed!.Value,
QtyNested = g.Sum(x => x.QtyNstd ?? 0),
QtyRequired = g.Max(x => x.QtyReq ?? 0),
Application = PepHelper.GetApplication(nest.Application)
};
})
.OrderByDescending(r => r.DateProgrammed)
.ThenBy(r => r.PartName)
.ToList();
var limitedResults = limit > 0 ? allResults.Take(limit).ToList() : allResults;
return Ok(new PartSearchResponse
{
SearchTerm = search,
Year = year,
TotalMatches = allResults.Count,
TotalNests = allResults.Select(r => r.NestName).Distinct().Count(),
ResultsReturned = limitedResults.Count,
Results = limitedResults
});
}
private string GetNestPath(string nestName, int year) private string GetNestPath(string nestName, int year)
{ {
var fileName = nestName + ".zip"; var fileName = nestName + ".zip";

View File

@@ -0,0 +1,28 @@
namespace PepApi.Core.Models;
public class PartSearchResult
{
public required string PartName { get; set; }
public required string NestName { get; set; }
public required string Status { get; set; }
public required string Customer { get; set; }
public required string Comments { get; set; }
public int MaterialNumber { get; set; }
public required string MaterialGrade { get; set; }
public double Thickness { get; set; }
public DateTime DateProgrammed { get; set; }
public DateTime DateLastModified { get; set; }
public int QtyNested { get; set; }
public int QtyRequired { get; set; }
public required string Application { get; set; }
}
public class PartSearchResponse
{
public required string SearchTerm { get; set; }
public int? Year { get; set; }
public int TotalMatches { get; set; }
public int TotalNests { get; set; }
public int ResultsReturned { get; set; }
public List<PartSearchResult> Results { get; set; } = [];
}