feat(nests): add batch part search and material descriptions
- Add POST parts/search/batch endpoint for searching multiple parts at once - Extract SearchPartsInternalAsync helper method to support batch operations - Include material description in part search results by looking up MaterialHeaders - Add BatchPartSearchRequest and BatchPartSearchResponse models - Add ResultsReturned field to single-part search response for consistency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -160,6 +160,52 @@ public class NestsController : ControllerBase
|
||||
if (string.IsNullOrWhiteSpace(search))
|
||||
return BadRequest(new { message = "Search term is required" });
|
||||
|
||||
var result = await SearchPartsInternalAsync(search, year, customer, limit);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Batch search for parts across nests by multiple part names.
|
||||
/// Returns results grouped by search term.
|
||||
/// </summary>
|
||||
[HttpPost("parts/search/batch")]
|
||||
public async Task<ActionResult<BatchPartSearchResponse>> SearchPartsBatch(
|
||||
[FromBody] BatchPartSearchRequest request)
|
||||
{
|
||||
if (request.SearchTerms == null || request.SearchTerms.Count == 0)
|
||||
return BadRequest(new { message = "At least one search term is required" });
|
||||
|
||||
var results = new List<PartSearchResponse>();
|
||||
|
||||
foreach (var searchTerm in request.SearchTerms.Distinct())
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(searchTerm))
|
||||
continue;
|
||||
|
||||
var result = await SearchPartsInternalAsync(
|
||||
searchTerm,
|
||||
request.Year,
|
||||
request.Customer,
|
||||
request.LimitPerTerm);
|
||||
|
||||
results.Add(result);
|
||||
}
|
||||
|
||||
return Ok(new BatchPartSearchResponse
|
||||
{
|
||||
TotalSearchTerms = results.Count,
|
||||
TotalMatches = results.Sum(r => r.TotalMatches),
|
||||
TotalNests = results.SelectMany(r => r.Results).Select(r => r.NestName).Distinct().Count(),
|
||||
Results = results
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<PartSearchResponse> SearchPartsInternalAsync(
|
||||
string search,
|
||||
int? year,
|
||||
string? customer,
|
||||
int limit)
|
||||
{
|
||||
var searchUpper = search.Trim().ToUpper();
|
||||
|
||||
// Get nest headers - filter by year only if specified
|
||||
@@ -191,14 +237,15 @@ public class NestsController : ControllerBase
|
||||
|
||||
if (!nests.Any())
|
||||
{
|
||||
return Ok(new PartSearchResponse
|
||||
return new PartSearchResponse
|
||||
{
|
||||
SearchTerm = search,
|
||||
Year = year,
|
||||
TotalMatches = 0,
|
||||
TotalNests = 0,
|
||||
ResultsReturned = 0,
|
||||
Results = []
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
var nestKeys = nests.Select(n => (n.NestName, n.CopyID)).ToHashSet();
|
||||
@@ -221,6 +268,22 @@ public class NestsController : ControllerBase
|
||||
.Where(p => nestKeys.Contains((p.NestName, p.CopyID)))
|
||||
.ToList();
|
||||
|
||||
// Get material descriptions for materials used in matching nests
|
||||
var materialNumbers = nests
|
||||
.Select(n => n.Material)
|
||||
.Where(m => !string.IsNullOrWhiteSpace(m))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
var materialDescriptions = await _db.MaterialHeaders
|
||||
.Where(m => materialNumbers.Contains(m.Material))
|
||||
.Select(m => new { m.Material, m.Description })
|
||||
.ToListAsync();
|
||||
|
||||
var materialDescriptionLookup = materialDescriptions
|
||||
.GroupBy(m => m.Material)
|
||||
.ToDictionary(g => g.Key, g => g.First().Description);
|
||||
|
||||
// Group by nest and part name
|
||||
var nestLookup = nests.ToDictionary(n => (n.NestName, n.CopyID));
|
||||
|
||||
@@ -229,6 +292,9 @@ public class NestsController : ControllerBase
|
||||
.Select(g =>
|
||||
{
|
||||
var nest = nestLookup[(g.Key.NestName, g.Key.CopyID)];
|
||||
var materialDesc = nest.Material != null && materialDescriptionLookup.TryGetValue(nest.Material, out var desc)
|
||||
? desc
|
||||
: "";
|
||||
return new PartSearchResult
|
||||
{
|
||||
PartName = g.Key.Drawing ?? "",
|
||||
@@ -238,6 +304,7 @@ public class NestsController : ControllerBase
|
||||
Comments = nest.Comments ?? "",
|
||||
MaterialNumber = int.TryParse(nest.Material, out var num) ? num : 0,
|
||||
MaterialGrade = nest.MatGrade ?? "",
|
||||
MaterialDescription = materialDesc,
|
||||
Thickness = nest.MatThick,
|
||||
DateProgrammed = nest.DateProgrammed!.Value,
|
||||
DateLastModified = nest.ModifiedDate ?? nest.DateProgrammed!.Value,
|
||||
@@ -252,7 +319,7 @@ public class NestsController : ControllerBase
|
||||
|
||||
var limitedResults = limit > 0 ? allResults.Take(limit).ToList() : allResults;
|
||||
|
||||
return Ok(new PartSearchResponse
|
||||
return new PartSearchResponse
|
||||
{
|
||||
SearchTerm = search,
|
||||
Year = year,
|
||||
@@ -260,7 +327,7 @@ public class NestsController : ControllerBase
|
||||
TotalNests = allResults.Select(r => r.NestName).Distinct().Count(),
|
||||
ResultsReturned = limitedResults.Count,
|
||||
Results = limitedResults
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private async Task<string?> GetNestPathAsync(string nestName, int? year = null)
|
||||
|
||||
@@ -9,6 +9,7 @@ public class PartSearchResult
|
||||
public required string Comments { get; set; }
|
||||
public int MaterialNumber { get; set; }
|
||||
public required string MaterialGrade { get; set; }
|
||||
public required string MaterialDescription { get; set; }
|
||||
public double Thickness { get; set; }
|
||||
public DateTime DateProgrammed { get; set; }
|
||||
public DateTime DateLastModified { get; set; }
|
||||
@@ -26,3 +27,19 @@ public class PartSearchResponse
|
||||
public int ResultsReturned { get; set; }
|
||||
public List<PartSearchResult> Results { get; set; } = [];
|
||||
}
|
||||
|
||||
public class BatchPartSearchRequest
|
||||
{
|
||||
public required List<string> SearchTerms { get; set; }
|
||||
public string? Customer { get; set; }
|
||||
public int? Year { get; set; }
|
||||
public int LimitPerTerm { get; set; } = 100;
|
||||
}
|
||||
|
||||
public class BatchPartSearchResponse
|
||||
{
|
||||
public int TotalSearchTerms { get; set; }
|
||||
public int TotalMatches { get; set; }
|
||||
public int TotalNests { get; set; }
|
||||
public List<PartSearchResponse> Results { get; set; } = [];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user