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))
|
if (string.IsNullOrWhiteSpace(search))
|
||||||
return BadRequest(new { message = "Search term is required" });
|
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();
|
var searchUpper = search.Trim().ToUpper();
|
||||||
|
|
||||||
// Get nest headers - filter by year only if specified
|
// Get nest headers - filter by year only if specified
|
||||||
@@ -191,14 +237,15 @@ public class NestsController : ControllerBase
|
|||||||
|
|
||||||
if (!nests.Any())
|
if (!nests.Any())
|
||||||
{
|
{
|
||||||
return Ok(new PartSearchResponse
|
return new PartSearchResponse
|
||||||
{
|
{
|
||||||
SearchTerm = search,
|
SearchTerm = search,
|
||||||
Year = year,
|
Year = year,
|
||||||
TotalMatches = 0,
|
TotalMatches = 0,
|
||||||
TotalNests = 0,
|
TotalNests = 0,
|
||||||
|
ResultsReturned = 0,
|
||||||
Results = []
|
Results = []
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var nestKeys = nests.Select(n => (n.NestName, n.CopyID)).ToHashSet();
|
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)))
|
.Where(p => nestKeys.Contains((p.NestName, p.CopyID)))
|
||||||
.ToList();
|
.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
|
// Group by nest and part name
|
||||||
var nestLookup = nests.ToDictionary(n => (n.NestName, n.CopyID));
|
var nestLookup = nests.ToDictionary(n => (n.NestName, n.CopyID));
|
||||||
|
|
||||||
@@ -229,6 +292,9 @@ public class NestsController : ControllerBase
|
|||||||
.Select(g =>
|
.Select(g =>
|
||||||
{
|
{
|
||||||
var nest = nestLookup[(g.Key.NestName, g.Key.CopyID)];
|
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
|
return new PartSearchResult
|
||||||
{
|
{
|
||||||
PartName = g.Key.Drawing ?? "",
|
PartName = g.Key.Drawing ?? "",
|
||||||
@@ -238,6 +304,7 @@ public class NestsController : ControllerBase
|
|||||||
Comments = nest.Comments ?? "",
|
Comments = nest.Comments ?? "",
|
||||||
MaterialNumber = int.TryParse(nest.Material, out var num) ? num : 0,
|
MaterialNumber = int.TryParse(nest.Material, out var num) ? num : 0,
|
||||||
MaterialGrade = nest.MatGrade ?? "",
|
MaterialGrade = nest.MatGrade ?? "",
|
||||||
|
MaterialDescription = materialDesc,
|
||||||
Thickness = nest.MatThick,
|
Thickness = nest.MatThick,
|
||||||
DateProgrammed = nest.DateProgrammed!.Value,
|
DateProgrammed = nest.DateProgrammed!.Value,
|
||||||
DateLastModified = nest.ModifiedDate ?? 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;
|
var limitedResults = limit > 0 ? allResults.Take(limit).ToList() : allResults;
|
||||||
|
|
||||||
return Ok(new PartSearchResponse
|
return new PartSearchResponse
|
||||||
{
|
{
|
||||||
SearchTerm = search,
|
SearchTerm = search,
|
||||||
Year = year,
|
Year = year,
|
||||||
@@ -260,7 +327,7 @@ public class NestsController : ControllerBase
|
|||||||
TotalNests = allResults.Select(r => r.NestName).Distinct().Count(),
|
TotalNests = allResults.Select(r => r.NestName).Distinct().Count(),
|
||||||
ResultsReturned = limitedResults.Count,
|
ResultsReturned = limitedResults.Count,
|
||||||
Results = limitedResults
|
Results = limitedResults
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string?> GetNestPathAsync(string nestName, int? year = null)
|
private async Task<string?> GetNestPathAsync(string nestName, int? year = null)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ public class PartSearchResult
|
|||||||
public required string Comments { get; set; }
|
public required string Comments { get; set; }
|
||||||
public int MaterialNumber { get; set; }
|
public int MaterialNumber { get; set; }
|
||||||
public required string MaterialGrade { get; set; }
|
public required string MaterialGrade { get; set; }
|
||||||
|
public required string MaterialDescription { get; set; }
|
||||||
public double Thickness { get; set; }
|
public double Thickness { get; set; }
|
||||||
public DateTime DateProgrammed { get; set; }
|
public DateTime DateProgrammed { get; set; }
|
||||||
public DateTime DateLastModified { get; set; }
|
public DateTime DateLastModified { get; set; }
|
||||||
@@ -26,3 +27,19 @@ public class PartSearchResponse
|
|||||||
public int ResultsReturned { get; set; }
|
public int ResultsReturned { get; set; }
|
||||||
public List<PartSearchResult> Results { 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