Initial commit: JDE Scoping Tool migration project
Set up repository with legacy .NET Framework 4.8 source (OLD/), new .NET 10 Blazor solution (NEW/), OpenSpec specifications, documentation, and project configuration.
This commit is contained in:
@@ -0,0 +1,518 @@
|
||||
# Search Processing Design
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes the architecture and implementation approach for the search processing subsystem, including the SqlKata query builder pattern, filter handler architecture, and search processor service.
|
||||
|
||||
## Architecture
|
||||
|
||||
### High-Level Component Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ SearchProcessor │
|
||||
│ - Orchestrates search execution │
|
||||
│ - Coordinates filter enrichment, query building, execution │
|
||||
└────────────────────────────────┬────────────────────────────────────────┘
|
||||
│
|
||||
┌───────────────────────┼───────────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ ILotFinder │ │ ISearchQuery │ │ IWorkOrder │
|
||||
│ Repository │ │ Builder │ │ TraversalService│
|
||||
│ (enrichment) │ │ (SQL generation)│ │ (downstream) │
|
||||
└─────────────────┘ └────────┬────────┘ └─────────────────┘
|
||||
│
|
||||
┌───────────┴───────────┐
|
||||
│ │
|
||||
▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ Filter Handlers │ │ SqlServerCompiler│
|
||||
│ (composable) │ │ (SQL output) │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
NEW/src/JdeScoping.SearchProcessing/
|
||||
├── Interfaces/
|
||||
│ ├── ISearchProcessor.cs
|
||||
│ ├── ISearchQueryBuilder.cs
|
||||
│ ├── IFilterHandler.cs
|
||||
│ └── IWorkOrderTraversalService.cs
|
||||
├── QueryBuilders/
|
||||
│ ├── SqlKataSearchQueryBuilder.cs
|
||||
│ └── MisQueryBuilder.cs
|
||||
├── FilterHandlers/
|
||||
│ ├── FilterHandlerBase.cs
|
||||
│ ├── WorkOrderFilterHandler.cs
|
||||
│ ├── ItemNumberFilterHandler.cs
|
||||
│ ├── ProfitCenterFilterHandler.cs
|
||||
│ ├── WorkCenterFilterHandler.cs
|
||||
│ ├── OperatorFilterHandler.cs
|
||||
│ ├── ComponentLotFilterHandler.cs
|
||||
│ ├── ItemOperationMisFilterHandler.cs
|
||||
│ └── TimespanFilterHandler.cs
|
||||
├── Models/
|
||||
│ ├── SearchModel.cs
|
||||
│ ├── SearchQueryResult.cs
|
||||
│ ├── FilterEntries/
|
||||
│ │ ├── WorkOrderFilterEntry.cs
|
||||
│ │ ├── ItemNumberFilterEntry.cs
|
||||
│ │ ├── ProfitCenterFilterEntry.cs
|
||||
│ │ ├── WorkCenterFilterEntry.cs
|
||||
│ │ ├── OperatorFilterEntry.cs
|
||||
│ │ ├── ComponentLotFilterEntry.cs
|
||||
│ │ └── ItemOperationMisFilterEntry.cs
|
||||
│ └── Results/
|
||||
│ ├── SearchResult.cs
|
||||
│ ├── MisSearchResult.cs
|
||||
│ └── MisNonMatchSearchResult.cs
|
||||
├── Services/
|
||||
│ ├── SearchProcessor.cs
|
||||
│ └── WorkOrderTraversalService.cs
|
||||
├── Configuration/
|
||||
│ └── SearchProcessingOptions.cs
|
||||
├── Attributes/
|
||||
│ ├── OutputColumnAttribute.cs
|
||||
│ └── OutputTableAttribute.cs
|
||||
├── Extensions/
|
||||
│ ├── SearchModelExtensions.cs
|
||||
│ └── TableValuedParameterExtensions.cs
|
||||
├── ServiceCollectionExtensions.cs
|
||||
└── JdeScoping.SearchProcessing.csproj
|
||||
```
|
||||
|
||||
## SqlKata Query Builder Architecture
|
||||
|
||||
### ISearchQueryBuilder Interface
|
||||
|
||||
```csharp
|
||||
public interface ISearchQueryBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds the main search query for flagging and retrieving work orders.
|
||||
/// </summary>
|
||||
SearchQueryResult BuildSearchQuery(SearchModel model);
|
||||
|
||||
/// <summary>
|
||||
/// Builds the MIS data extraction query when ExtractMisData is enabled.
|
||||
/// </summary>
|
||||
SearchQueryResult BuildMisQuery(SearchModel model);
|
||||
|
||||
/// <summary>
|
||||
/// Builds the MIS non-match query for work orders without MIS records.
|
||||
/// </summary>
|
||||
SearchQueryResult BuildMisNonMatchQuery(SearchModel model);
|
||||
}
|
||||
|
||||
public record SearchQueryResult(
|
||||
string Sql,
|
||||
IDictionary<string, object> Parameters,
|
||||
IReadOnlyList<string> TempTableSetupSql);
|
||||
```
|
||||
|
||||
### SqlKata Integration Pattern
|
||||
|
||||
The SqlKata query builder composes queries using fluent methods:
|
||||
|
||||
```csharp
|
||||
public sealed class SqlKataSearchQueryBuilder : ISearchQueryBuilder
|
||||
{
|
||||
private readonly SqlServerCompiler _compiler = new();
|
||||
private readonly IEnumerable<IFilterHandler> _filterHandlers;
|
||||
|
||||
public SqlKataSearchQueryBuilder(IEnumerable<IFilterHandler> filterHandlers)
|
||||
{
|
||||
_filterHandlers = filterHandlers;
|
||||
}
|
||||
|
||||
public SearchQueryResult BuildSearchQuery(SearchModel model)
|
||||
{
|
||||
var setupStatements = new List<string>();
|
||||
var parameters = new Dictionary<string, object>();
|
||||
|
||||
// Build temp table setup SQL
|
||||
setupStatements.Add(BuildTempWoTableSql());
|
||||
|
||||
// Apply filter handlers (each may add setup SQL and parameters)
|
||||
foreach (var handler in _filterHandlers.Where(h => h.IsEnabled(model)))
|
||||
{
|
||||
var filterResult = handler.Apply(model, _compiler);
|
||||
setupStatements.AddRange(filterResult.SetupSql);
|
||||
foreach (var param in filterResult.Parameters)
|
||||
{
|
||||
parameters[param.Key] = param.Value;
|
||||
}
|
||||
}
|
||||
|
||||
// Build final result query
|
||||
var resultQuery = BuildResultQuery();
|
||||
var compiled = _compiler.Compile(resultQuery);
|
||||
|
||||
return new SearchQueryResult(
|
||||
compiled.Sql,
|
||||
MergeParameters(parameters, compiled.NamedBindings),
|
||||
setupStatements);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Why SqlKata Instead of T4 Templates
|
||||
|
||||
| Aspect | T4 Template (Legacy) | SqlKata (New) |
|
||||
|--------|---------------------|---------------|
|
||||
| Testability | Cannot unit test | Test query structure without DB |
|
||||
| Type Safety | String concatenation | Fluent API with IntelliSense |
|
||||
| SQL Injection | Manual parameter handling | Parameterized by default |
|
||||
| Maintenance | Edit .tt file, regenerate | Edit C# code directly |
|
||||
| SDK Support | Limited in modern .NET | Full .NET 10 support |
|
||||
| Composability | Monolithic template | Pluggable filter handlers |
|
||||
|
||||
## Filter Handler Pattern
|
||||
|
||||
### IFilterHandler Interface
|
||||
|
||||
```csharp
|
||||
public interface IFilterHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines if this filter is active for the given search model.
|
||||
/// </summary>
|
||||
bool IsEnabled(SearchModel model);
|
||||
|
||||
/// <summary>
|
||||
/// Applies the filter, returning setup SQL and parameters.
|
||||
/// </summary>
|
||||
FilterResult Apply(SearchModel model, SqlServerCompiler compiler);
|
||||
|
||||
/// <summary>
|
||||
/// Priority for handler execution order (lower = earlier).
|
||||
/// </summary>
|
||||
int Priority { get; }
|
||||
}
|
||||
|
||||
public record FilterResult(
|
||||
IReadOnlyList<string> SetupSql,
|
||||
IDictionary<string, object> Parameters);
|
||||
```
|
||||
|
||||
### Filter Handler Implementations
|
||||
|
||||
Each filter handler encapsulates the logic for one filter type:
|
||||
|
||||
#### WorkOrderFilterHandler
|
||||
|
||||
```csharp
|
||||
public sealed class WorkOrderFilterHandler : FilterHandlerBase
|
||||
{
|
||||
public override int Priority => 10;
|
||||
|
||||
public override bool IsEnabled(SearchModel model)
|
||||
=> model.WorkOrderFilterEnabled;
|
||||
|
||||
public override FilterResult Apply(SearchModel model, SqlServerCompiler compiler)
|
||||
{
|
||||
// Generates MERGE into #Temp_WO with ManuallySpecified = 1
|
||||
// Followed by split order detection
|
||||
var sql = BuildWorkOrderMergeSql();
|
||||
var parameters = new Dictionary<string, object>
|
||||
{
|
||||
["p_WorkOrderFilter"] = model.CreateWorkOrderFilterParameter()
|
||||
};
|
||||
|
||||
return new FilterResult(new[] { sql }, parameters);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### ComponentLotFilterHandler
|
||||
|
||||
```csharp
|
||||
public sealed class ComponentLotFilterHandler : FilterHandlerBase
|
||||
{
|
||||
public override int Priority => 30;
|
||||
|
||||
public override bool IsEnabled(SearchModel model)
|
||||
=> model.ComponentLotFilterEnabled;
|
||||
|
||||
public override FilterResult Apply(SearchModel model, SqlServerCompiler compiler)
|
||||
{
|
||||
// Joins Lot -> WorkOrderComponent/LotUsage -> WorkOrder
|
||||
// Sets CARDEX = 1 flag
|
||||
var sql = BuildComponentLotMergeSql();
|
||||
var parameters = new Dictionary<string, object>
|
||||
{
|
||||
["p_ComponentLotFilter"] = model.CreateComponentLotFilterParameter()
|
||||
};
|
||||
|
||||
return new FilterResult(new[] { sql }, parameters);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Handler Execution Order
|
||||
|
||||
Handlers execute in priority order to ensure dependent temp tables exist:
|
||||
|
||||
| Priority | Handler | Creates/Uses |
|
||||
|----------|---------|--------------|
|
||||
| 10 | WorkOrderFilterHandler | Creates #Temp_WO entries |
|
||||
| 20 | ItemNumberFilterHandler | Creates #P_ItemNumbers |
|
||||
| 30 | ComponentLotFilterHandler | Uses Lot, creates #Temp_WO entries |
|
||||
| 40 | ProfitCenterFilterHandler | Creates #P_WorkCenters |
|
||||
| 50 | WorkCenterFilterHandler | Creates #P_WorkCenters |
|
||||
| 60 | OperatorFilterHandler | Creates #P_OperatorIDs |
|
||||
| 70 | ItemOperationMisFilterHandler | Creates #P_PartOperations |
|
||||
| 80 | TimespanFilterHandler | Adds WHERE clause |
|
||||
|
||||
## IAsyncEnumerable Streaming Pattern
|
||||
|
||||
### Large Result Set Handling
|
||||
|
||||
For searches returning thousands of work orders, streaming avoids loading all results into memory:
|
||||
|
||||
```csharp
|
||||
public interface ISearchProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes search and returns results as async stream.
|
||||
/// </summary>
|
||||
IAsyncEnumerable<SearchResult> ExecuteSearchAsync(
|
||||
SearchModel model,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Executes search and materializes all results into SearchModel.
|
||||
/// </summary>
|
||||
Task<SearchModel> ExecuteSearchToModelAsync(
|
||||
SearchModel model,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
```
|
||||
|
||||
### Streaming Implementation
|
||||
|
||||
```csharp
|
||||
public sealed class SearchProcessor : ISearchProcessor
|
||||
{
|
||||
private readonly IDbConnectionFactory _connectionFactory;
|
||||
private readonly ISearchQueryBuilder _queryBuilder;
|
||||
private readonly IWorkOrderTraversalService _traversalService;
|
||||
private readonly ILogger<SearchProcessor> _logger;
|
||||
|
||||
public async IAsyncEnumerable<SearchResult> ExecuteSearchAsync(
|
||||
SearchModel model,
|
||||
[EnumeratorCancellation] CancellationToken ct = default)
|
||||
{
|
||||
await using var connection = await _connectionFactory
|
||||
.CreateLotFinderConnectionAsync(ct);
|
||||
|
||||
// Execute setup SQL (temp tables, filter population)
|
||||
var queryResult = _queryBuilder.BuildSearchQuery(model);
|
||||
foreach (var setupSql in queryResult.TempTableSetupSql)
|
||||
{
|
||||
await connection.ExecuteAsync(setupSql, queryResult.Parameters);
|
||||
}
|
||||
|
||||
// Stream results
|
||||
await foreach (var result in connection.QueryUnbufferedAsync<SearchResult>(
|
||||
queryResult.Sql,
|
||||
queryResult.Parameters)
|
||||
.WithCancellation(ct))
|
||||
{
|
||||
yield return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Downstream Work Order Traversal
|
||||
|
||||
### Stored Procedure Approach
|
||||
|
||||
The iterative traversal logic (up to 20 iterations finding downstream work orders) is better suited to a stored procedure:
|
||||
|
||||
```csharp
|
||||
public interface IWorkOrderTraversalService
|
||||
{
|
||||
/// <summary>
|
||||
/// Traverses downstream work orders via stored procedure.
|
||||
/// Called after initial filtering to find related work orders.
|
||||
/// </summary>
|
||||
Task TraverseDownstreamAsync(
|
||||
SqlConnection connection,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
```
|
||||
|
||||
### Why Stored Procedure
|
||||
|
||||
- **Iterative logic**: WHILE loops with temp table operations are efficient in T-SQL
|
||||
- **Reduced round trips**: Single call instead of 20+ iterations from C#
|
||||
- **Transaction scope**: All MERGE operations in same transaction
|
||||
- **Legacy compatibility**: Mirrors existing QueryTemplate.tt behavior
|
||||
|
||||
## Table-Valued Parameter Helpers
|
||||
|
||||
### Extension Methods
|
||||
|
||||
```csharp
|
||||
public static class TableValuedParameterExtensions
|
||||
{
|
||||
public static SqlMapper.ICustomQueryParameter CreateWorkOrderFilterParameter(
|
||||
this SearchModel model)
|
||||
{
|
||||
var dataTable = new DataTable();
|
||||
dataTable.Columns.Add("WorkOrderNumber", typeof(long));
|
||||
|
||||
foreach (var entry in model.WorkOrderFilter)
|
||||
{
|
||||
dataTable.Rows.Add(entry.WorkOrderNumber);
|
||||
}
|
||||
|
||||
return dataTable.AsTableValuedParameter("WorkOrderFilterParameter");
|
||||
}
|
||||
|
||||
// Similar methods for all 7 filter types...
|
||||
}
|
||||
```
|
||||
|
||||
### TVP Type Mapping
|
||||
|
||||
| C# Method | SQL Server Type | Columns |
|
||||
|-----------|-----------------|---------|
|
||||
| `CreateWorkOrderFilterParameter` | `WorkOrderFilterParameter` | `WorkOrderNumber BIGINT` |
|
||||
| `CreateItemNumberFilterParameter` | `ItemNumberFilterParameter` | `ItemNumber VARCHAR(25)` |
|
||||
| `CreateProfitCenterFilterParameter` | `ProfitCenterFilterParameter` | `Code VARCHAR(12)` |
|
||||
| `CreateWorkCenterFilterParameter` | `WorkCenterFilterParameter` | `Code VARCHAR(12)` |
|
||||
| `CreateOperatorFilterParameter` | `OperatorFilterParameter` | `UserName VARCHAR(10)` |
|
||||
| `CreateComponentLotFilterParameter` | `ComponentLotFilterParameter` | `ComponentLotNumber VARCHAR(30), ItemNumber VARCHAR(25)` |
|
||||
| `CreateItemOperationMisFilterParameter` | `ItemOperationMisFilterParameter` | `ItemNumber, OperationNumber, MisNumber, MisRevision` |
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### SearchProcessingOptions Class
|
||||
|
||||
```csharp
|
||||
public class SearchProcessingOptions
|
||||
{
|
||||
public const string SectionName = "SearchProcessing";
|
||||
|
||||
/// <summary>
|
||||
/// Query timeout in seconds for search execution.
|
||||
/// </summary>
|
||||
public int QueryTimeoutSeconds { get; set; } = 600;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum downstream traversal iterations.
|
||||
/// </summary>
|
||||
public int MaxTraversalIterations { get; set; } = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Enable debug SQL logging.
|
||||
/// </summary>
|
||||
public bool EnableDebugSql { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Path to write debug SQL files (when EnableDebugSql is true).
|
||||
/// </summary>
|
||||
public string? DebugSqlPath { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration Binding
|
||||
|
||||
```json
|
||||
{
|
||||
"SearchProcessing": {
|
||||
"QueryTimeoutSeconds": 600,
|
||||
"MaxTraversalIterations": 20,
|
||||
"EnableDebugSql": false,
|
||||
"DebugSqlPath": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Service Registration
|
||||
|
||||
### AddSearchProcessing Extension Method
|
||||
|
||||
```csharp
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddSearchProcessing(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
// Bind options
|
||||
services.Configure<SearchProcessingOptions>(
|
||||
configuration.GetSection(SearchProcessingOptions.SectionName));
|
||||
|
||||
// Register SqlKata compiler (singleton, thread-safe)
|
||||
services.AddSingleton<SqlServerCompiler>();
|
||||
|
||||
// Register filter handlers (scoped)
|
||||
services.AddScoped<IFilterHandler, WorkOrderFilterHandler>();
|
||||
services.AddScoped<IFilterHandler, ItemNumberFilterHandler>();
|
||||
services.AddScoped<IFilterHandler, ProfitCenterFilterHandler>();
|
||||
services.AddScoped<IFilterHandler, WorkCenterFilterHandler>();
|
||||
services.AddScoped<IFilterHandler, OperatorFilterHandler>();
|
||||
services.AddScoped<IFilterHandler, ComponentLotFilterHandler>();
|
||||
services.AddScoped<IFilterHandler, ItemOperationMisFilterHandler>();
|
||||
services.AddScoped<IFilterHandler, TimespanFilterHandler>();
|
||||
|
||||
// Register query builders (scoped)
|
||||
services.AddScoped<ISearchQueryBuilder, SqlKataSearchQueryBuilder>();
|
||||
|
||||
// Register services (scoped)
|
||||
services.AddScoped<ISearchProcessor, SearchProcessor>();
|
||||
services.AddScoped<IWorkOrderTraversalService, WorkOrderTraversalService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
|
||||
- **Query Builder Tests**: Verify generated SQL structure without executing
|
||||
- **Filter Handler Tests**: Test each handler in isolation
|
||||
- **Parameter Tests**: Verify TVP creation for all filter types
|
||||
- **Mock Repository**: Use NSubstitute for `ILotFinderRepository`
|
||||
|
||||
### Integration Tests
|
||||
|
||||
- **Full Search Flow**: Execute search against test database
|
||||
- **Filter Combinations**: Matrix of filter permutations
|
||||
- **Large Result Sets**: Verify streaming behavior
|
||||
- **MIS Extraction**: Test with and without MIS data
|
||||
|
||||
### Test Frameworks
|
||||
|
||||
- **xUnit**: Test framework
|
||||
- **Shouldly**: Fluent assertions
|
||||
- **NSubstitute**: Mocking framework
|
||||
|
||||
## NuGet Dependencies
|
||||
|
||||
```xml
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SqlKata" Version="3.0.*" />
|
||||
<PackageReference Include="SqlKata.Execution" Version="3.0.*" />
|
||||
<PackageReference Include="Dapper" Version="2.1.*" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="9.0.*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(Configuration)' == 'Test'">
|
||||
<PackageReference Include="xunit" Version="2.9.*" />
|
||||
<PackageReference Include="Shouldly" Version="4.2.*" />
|
||||
<PackageReference Include="NSubstitute" Version="5.1.*" />
|
||||
</ItemGroup>
|
||||
```
|
||||
@@ -0,0 +1,98 @@
|
||||
# Implement Search Processing
|
||||
|
||||
## Summary
|
||||
|
||||
Implement the search processing subsystem that executes user-defined filter queries against the SQL Server cache database. This phase replaces the legacy T4 text template (`QueryTemplate.tt`) with a SqlKata fluent query builder, providing type-safe query construction, parameterized SQL generation, and composable filter handling.
|
||||
|
||||
## Scope
|
||||
|
||||
### In Scope
|
||||
|
||||
- `ISearchQueryBuilder` interface and `SqlKataSearchQueryBuilder` implementation
|
||||
- Filter handler pattern with individual handlers per filter type:
|
||||
- `WorkOrderFilterHandler`
|
||||
- `ItemNumberFilterHandler`
|
||||
- `ProfitCenterFilterHandler`
|
||||
- `WorkCenterFilterHandler`
|
||||
- `OperatorFilterHandler`
|
||||
- `ComponentLotFilterHandler`
|
||||
- `ItemOperationMisFilterHandler`
|
||||
- `TimespanFilterHandler`
|
||||
- `ISearchProcessor` interface and `SearchProcessor` service implementation
|
||||
- `IWorkOrderTraversalService` interface for downstream work order traversal
|
||||
- `SearchModel` class (reporting model with enriched filter entries)
|
||||
- Filter entry record types (immutable DTOs with output attributes)
|
||||
- Result record types: `SearchResult`, `MisSearchResult`, `MisNonMatchSearchResult`
|
||||
- Table-valued parameter creation helpers
|
||||
- MIS data extraction query building
|
||||
- `SearchProcessingOptions` configuration class
|
||||
- `AddSearchProcessing` service registration extension method
|
||||
- Unit tests with xUnit, Shouldly, and NSubstitute
|
||||
|
||||
### Out of Scope
|
||||
|
||||
- Background job scheduling (handled by Phase 5 data-sync or separate worker phase)
|
||||
- Excel export generation (Phase 7: excel-export)
|
||||
- API endpoints for search submission (Phase 8: web-api-auth)
|
||||
- SignalR real-time status updates (Phase 8: web-api-auth)
|
||||
- Database schema changes (Phase 1: migrate-database-schema)
|
||||
|
||||
## Motivation
|
||||
|
||||
The legacy T4 text template approach has significant limitations:
|
||||
- **SDK incompatibility**: T4 templates are poorly supported in modern .NET SDK-style projects
|
||||
- **Untestable**: Generated SQL cannot be unit tested without database execution
|
||||
- **Fragile**: String concatenation prone to SQL injection and syntax errors
|
||||
- **Untyped**: No compile-time validation of query structure
|
||||
|
||||
SqlKata provides:
|
||||
- **Fluent API**: Readable, composable query building with IntelliSense support
|
||||
- **Parameterized by default**: SQL injection protection built-in
|
||||
- **Testable**: Unit test query building without database
|
||||
- **Type-safe**: Compile-time checking of method calls
|
||||
- **SQL Server optimized**: SqlServerCompiler generates optimized T-SQL
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. `ISearchQueryBuilder` interface defined with `BuildSearchQuery(SearchModel)` method
|
||||
2. `SqlKataSearchQueryBuilder` generates equivalent SQL to legacy `QueryTemplate.tt`
|
||||
3. All 8 filter handlers implemented with conditional join/where clause generation
|
||||
4. `SearchProcessor` orchestrates:
|
||||
- Filter entry enrichment via repository lookups
|
||||
- Query building via SqlKata
|
||||
- Query execution via Dapper
|
||||
- Result aggregation into `SearchModel.Results`
|
||||
- MIS data extraction when `ExtractMisData = true`
|
||||
5. Downstream work order traversal calls stored procedure `dbo.TraverseWorkOrders`
|
||||
6. Table-valued parameters created correctly for all filter types
|
||||
7. All result types include `OutputColumnAttribute` and `OutputTableAttribute` for Excel export
|
||||
8. Unit tests verify:
|
||||
- Query builder generates expected SQL structure
|
||||
- Filter handlers apply correct joins and conditions
|
||||
- Parameter binding works for all TVP types
|
||||
9. `AddSearchProcessing` registers all services with appropriate lifetimes
|
||||
10. `openspec validate implement-search-processing --strict` passes
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **Phase 1**: migrate-database-schema - Database tables, TVP types, stored procedures
|
||||
- **Phase 3**: implement-domain-models - Core domain entities (Search, SearchCriteria, etc.)
|
||||
- **Phase 4**: implement-data-access - `IDbConnectionFactory`, `ILotFinderRepository` for lookups
|
||||
- **NuGet packages**: `SqlKata`, `SqlKata.Execution`, `Dapper`, `Microsoft.Data.SqlClient`
|
||||
|
||||
## Risks
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| Query logic drift from legacy | Codex MCP review comparing generated SQL against QueryTemplate.tt output |
|
||||
| Complex filter combinations | Comprehensive unit test matrix covering all filter permutations |
|
||||
| MIS extraction query complexity | Retain MIS extraction as separate stored procedure if needed |
|
||||
| Performance regression | Benchmark query execution time against legacy implementation |
|
||||
| Downstream traversal correctness | Stored procedure `dbo.TraverseWorkOrders` encapsulates iterative logic |
|
||||
|
||||
## Related Specs
|
||||
|
||||
- `search-processing` - Primary specification for query building and search execution
|
||||
- `domain-models` - Entity types used in search criteria and results
|
||||
- `data-access` - Repository interfaces for filter enrichment lookups
|
||||
- `database-schema` - SQL Server tables and TVP types for search execution
|
||||
+272
@@ -0,0 +1,272 @@
|
||||
# Search Processing Specification Delta
|
||||
|
||||
## Purpose
|
||||
|
||||
This document captures ADDED and MODIFIED requirements for the search processing subsystem specific to the .NET 10 migration. It supplements the base specification at `openspec/specs/search-processing/spec.md`.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: SqlKata Query Builder Integration
|
||||
|
||||
The system SHALL use SqlKata fluent query builder instead of T4 text templates for dynamic SQL generation.
|
||||
|
||||
#### Inputs
|
||||
|
||||
- `SearchModel` containing filter criteria and enriched filter entries
|
||||
- `SqlServerCompiler` for T-SQL generation
|
||||
|
||||
#### Outputs
|
||||
|
||||
- `SearchQueryResult` record containing:
|
||||
- `Sql`: Parameterized T-SQL query string
|
||||
- `Parameters`: Dictionary of named parameter values
|
||||
- `TempTableSetupSql`: List of temp table creation/population statements
|
||||
|
||||
#### Business Rules
|
||||
|
||||
- SqlKata `SqlServerCompiler` SHALL be registered as a singleton (thread-safe)
|
||||
- All queries SHALL use named parameters (not positional)
|
||||
- Parameter names SHALL match legacy convention (`@p_*` prefix)
|
||||
- Generated SQL SHALL produce equivalent results to legacy QueryTemplate.tt
|
||||
|
||||
#### Scenario: Build query with SqlKata
|
||||
|
||||
- **WHEN** `ISearchQueryBuilder.BuildSearchQuery(model)` is called
|
||||
- **THEN** SqlKata generates parameterized SQL with named bindings
|
||||
- **AND** the `SearchQueryResult.Sql` is executable via Dapper
|
||||
- **AND** `SearchQueryResult.Parameters` contains all TVP parameters
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Filter Handler Pattern
|
||||
|
||||
The system SHALL use a composable filter handler pattern for modular query building.
|
||||
|
||||
#### Inputs
|
||||
|
||||
- `SearchModel` with active filter criteria
|
||||
- `SqlServerCompiler` for SQL compilation
|
||||
|
||||
#### Outputs
|
||||
|
||||
- `FilterResult` record containing:
|
||||
- `SetupSql`: List of temp table setup statements
|
||||
- `Parameters`: Dictionary of parameters for this filter
|
||||
|
||||
#### Business Rules
|
||||
|
||||
- Each filter type SHALL have a dedicated `IFilterHandler` implementation
|
||||
- Filter handlers SHALL be registered in dependency injection container
|
||||
- Handlers SHALL execute in priority order (lower priority = earlier execution)
|
||||
- Handler priorities SHALL ensure dependent temp tables exist before use:
|
||||
- WorkOrder: 10
|
||||
- ItemNumber: 20
|
||||
- ComponentLot: 30
|
||||
- ProfitCenter: 40
|
||||
- WorkCenter: 50
|
||||
- Operator: 60
|
||||
- ItemOperationMis: 70
|
||||
- Timespan: 80
|
||||
|
||||
#### Scenario: Execute filter handlers in order
|
||||
|
||||
- **WHEN** search criteria includes work orders, items, and operators
|
||||
- **THEN** WorkOrderFilterHandler executes first (priority 10)
|
||||
- **AND** ItemNumberFilterHandler executes second (priority 20)
|
||||
- **AND** OperatorFilterHandler executes later (priority 60)
|
||||
|
||||
---
|
||||
|
||||
### Requirement: IAsyncEnumerable Result Streaming
|
||||
|
||||
The system SHALL support streaming large result sets using `IAsyncEnumerable<T>`.
|
||||
|
||||
#### Inputs
|
||||
|
||||
- `SearchModel` with executed query
|
||||
- `CancellationToken` for cooperative cancellation
|
||||
|
||||
#### Outputs
|
||||
|
||||
- `IAsyncEnumerable<SearchResult>` streaming results one at a time
|
||||
|
||||
#### Business Rules
|
||||
|
||||
- Streaming SHALL use Dapper's `QueryUnbufferedAsync` method
|
||||
- Cancellation SHALL be supported via `[EnumeratorCancellation]` attribute
|
||||
- Memory allocation SHALL remain constant regardless of result set size
|
||||
- Consumer MAY materialize results using `ToListAsync()` when needed
|
||||
|
||||
#### Scenario: Stream large result set
|
||||
|
||||
- **WHEN** search returns 10,000 work orders
|
||||
- **THEN** results stream via `IAsyncEnumerable<SearchResult>`
|
||||
- **AND** memory usage remains constant during enumeration
|
||||
- **AND** `await foreach` consumes results incrementally
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Async-First Design
|
||||
|
||||
The system SHALL use async methods throughout the search processing pipeline.
|
||||
|
||||
#### Business Rules
|
||||
|
||||
- All repository methods SHALL accept `CancellationToken` parameter
|
||||
- All database operations SHALL use async Dapper methods (`QueryAsync`, `ExecuteAsync`)
|
||||
- Long-running operations SHALL respect cancellation tokens
|
||||
- `ISearchProcessor.ExecuteSearchAsync` SHALL be the primary entry point
|
||||
|
||||
#### Scenario: Cancel long-running search
|
||||
|
||||
- **WHEN** a search is in progress and cancellation is requested
|
||||
- **THEN** the operation throws `OperationCanceledException`
|
||||
- **AND** database connections are properly disposed
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Configuration via IOptions Pattern
|
||||
|
||||
The system SHALL use `IOptions<SearchProcessingOptions>` for configuration.
|
||||
|
||||
#### Inputs
|
||||
|
||||
- `appsettings.json` section: `SearchProcessing`
|
||||
|
||||
#### Outputs
|
||||
|
||||
- Strongly-typed `SearchProcessingOptions` injected via DI
|
||||
|
||||
#### Configuration Properties
|
||||
|
||||
| Property | Type | Default | Description |
|
||||
|----------|------|---------|-------------|
|
||||
| `QueryTimeoutSeconds` | int | 600 | SQL query timeout |
|
||||
| `MaxTraversalIterations` | int | 20 | Downstream traversal limit |
|
||||
| `EnableDebugSql` | bool | false | Write SQL to debug files |
|
||||
| `DebugSqlPath` | string? | null | Path for debug SQL files |
|
||||
|
||||
#### Scenario: Configure query timeout
|
||||
|
||||
- **WHEN** `SearchProcessingOptions.QueryTimeoutSeconds` is set to 900
|
||||
- **THEN** Dapper queries use `commandTimeout: 900`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Service Registration Extension
|
||||
|
||||
The system SHALL provide an `AddSearchProcessing` extension method for DI registration.
|
||||
|
||||
#### Service Lifetimes
|
||||
|
||||
| Service | Lifetime | Rationale |
|
||||
|---------|----------|-----------|
|
||||
| `SqlServerCompiler` | Singleton | Thread-safe, stateless |
|
||||
| `IFilterHandler` implementations | Scoped | Per-request state |
|
||||
| `ISearchQueryBuilder` | Scoped | Uses scoped handlers |
|
||||
| `ISearchProcessor` | Scoped | Uses scoped repositories |
|
||||
| `IWorkOrderTraversalService` | Scoped | Uses connection factory |
|
||||
|
||||
#### Scenario: Register search processing services
|
||||
|
||||
- **WHEN** `services.AddSearchProcessing(configuration)` is called
|
||||
- **THEN** all search processing services are registered
|
||||
- **AND** `ISearchProcessor` can be resolved from service provider
|
||||
|
||||
---
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Work Order Traversal
|
||||
|
||||
The system SHALL execute downstream work order traversal via stored procedure `dbo.TraverseWorkOrders` instead of inline WHILE loop in generated SQL.
|
||||
|
||||
#### Inputs
|
||||
|
||||
- Active database connection with `#Temp_WO` temporary table populated
|
||||
- Maximum iteration count (default 20)
|
||||
|
||||
#### Outputs
|
||||
|
||||
- Updated `#Temp_WO` table with downstream work orders flagged
|
||||
|
||||
#### Business Rules
|
||||
|
||||
- `IWorkOrderTraversalService.TraverseDownstreamAsync` SHALL call stored procedure
|
||||
- Stored procedure SHALL contain iterative WHILE loop logic
|
||||
- Single stored procedure call SHALL replace 20 inline iterations
|
||||
- Transaction scope SHALL be maintained within stored procedure
|
||||
|
||||
#### Scenario: Execute downstream traversal via stored procedure
|
||||
|
||||
- **WHEN** initial work orders are flagged in `#Temp_WO`
|
||||
- **THEN** `dbo.TraverseWorkOrders` stored procedure is called
|
||||
- **AND** downstream work orders are added with PartsList and CARDEX flags
|
||||
- **AND** split orders are detected and flagged
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Filter Entry Types
|
||||
|
||||
The system SHALL use C# record types for filter entry DTOs to provide immutability and value semantics.
|
||||
|
||||
#### Inputs
|
||||
|
||||
- Raw filter values from `SearchCriteria`
|
||||
- Reference data lookups from `ILotFinderRepository`
|
||||
|
||||
#### Outputs
|
||||
|
||||
- Immutable record instances with output attributes for Excel export
|
||||
|
||||
#### Business Rules
|
||||
|
||||
- All filter entry types SHALL be declared as C# records
|
||||
- Records SHALL use primary constructor syntax
|
||||
- Output attributes SHALL be applied using `[property:]` target
|
||||
- Records SHALL provide value-based equality for testing
|
||||
|
||||
#### Scenario: Create immutable filter entry record
|
||||
|
||||
- **WHEN** a WorkOrderFilterEntry is created with WorkOrderNumber 12345 and ItemNumber "ITEM-001"
|
||||
- **THEN** the record is immutable (properties are init-only)
|
||||
- **AND** two records with same values are considered equal
|
||||
|
||||
---
|
||||
|
||||
### Requirement: SQL Client Package
|
||||
|
||||
The system SHALL use `Microsoft.Data.SqlClient` instead of deprecated `System.Data.SqlClient` for SQL Server connectivity.
|
||||
|
||||
#### Business Rules
|
||||
|
||||
- All SQL Server connections SHALL use `Microsoft.Data.SqlClient.SqlConnection`
|
||||
- All SQL commands SHALL use `Microsoft.Data.SqlClient.SqlCommand`
|
||||
- NuGet package `Microsoft.Data.SqlClient` version 5.2+ SHALL be referenced
|
||||
- Code SHALL NOT reference `System.Data.SqlClient` namespace
|
||||
|
||||
#### Scenario: Create SQL connection with modern client
|
||||
|
||||
- **WHEN** `IDbConnectionFactory.CreateLotFinderConnectionAsync` is called
|
||||
- **THEN** a `Microsoft.Data.SqlClient.SqlConnection` instance is returned
|
||||
- **AND** connection supports all modern SQL Server features
|
||||
|
||||
---
|
||||
|
||||
## Migration Notes
|
||||
|
||||
| Legacy Pattern | New Pattern | Status |
|
||||
|----------------|-------------|--------|
|
||||
| T4 Text Template | SqlKata fluent builder | ADDED |
|
||||
| Inline WHILE loop | Stored procedure | MODIFIED |
|
||||
| Filter entry classes | Record types | MODIFIED |
|
||||
| Synchronous Dapper | Async Dapper | MODIFIED |
|
||||
| System.Data.SqlClient | Microsoft.Data.SqlClient | MODIFIED |
|
||||
| Static class methods | DI-registered services | MODIFIED |
|
||||
| Newtonsoft.Json | System.Text.Json | Retained in domain models |
|
||||
|
||||
---
|
||||
|
||||
## Open Questions
|
||||
|
||||
None - all design decisions resolved per base specification.
|
||||
@@ -0,0 +1,306 @@
|
||||
# Tasks: Implement Search Processing
|
||||
|
||||
## Phase 1: Project Setup
|
||||
|
||||
- [x] 001: Create JdeScoping.SearchProcessing project
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/JdeScoping.SearchProcessing.csproj`
|
||||
- Dependencies: SqlKata, SqlKata.Execution, Dapper, Microsoft.Data.SqlClient
|
||||
- Validation: `dotnet build` succeeds
|
||||
|
||||
- [x] 002: Add project reference to JdeScoping.Host
|
||||
- Location: `NEW/src/JdeScoping.Host/JdeScoping.Host.csproj`
|
||||
- Validation: Solution builds with new reference
|
||||
|
||||
- [x] 003: Create SearchProcessingOptions configuration class
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Configuration/SearchProcessingConfiguration.cs`
|
||||
- Properties: QueryTimeoutSeconds, MaxTraversalIterations, EnableDebugSql, DebugSqlPath
|
||||
- Validation: Options bind from appsettings.json
|
||||
|
||||
## Phase 2: Output Attributes
|
||||
|
||||
- [x] 004: Create OutputColumnAttribute
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Attributes/OutputColumnAttribute.cs`
|
||||
- Properties: Order, HeaderText, Format, AutoWidth, Width, WrapText
|
||||
- Constants: DATE_FORMAT, TIMESTAMP_FORMAT, WRAPPED_COLUMN_WIDTH
|
||||
- Validation: Attribute compiles and is applicable to properties
|
||||
|
||||
- [x] 005: Create OutputTableAttribute
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Attributes/OutputTableAttribute.cs`
|
||||
- Properties: TabName, TableName, ShowHeader
|
||||
- Validation: Attribute compiles and is applicable to classes/records
|
||||
|
||||
## Phase 3: Filter Entry Records
|
||||
|
||||
- [x] 006: Create WorkOrderFilterEntry record
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Models/FilterEntries/WorkOrderFilterEntry.cs`
|
||||
- Properties: WorkOrderNumber (long), ItemNumber (string)
|
||||
- Include OutputTable and OutputColumn attributes
|
||||
- Validation: Record compiles with proper attributes
|
||||
|
||||
- [x] 007: Create ItemNumberFilterEntry record
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Models/FilterEntries/ItemNumberFilterEntry.cs`
|
||||
- Properties: ItemNumber, ItemDescription
|
||||
- Validation: Record compiles with proper attributes
|
||||
|
||||
- [x] 008: Create ProfitCenterFilterEntry record
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Models/FilterEntries/ProfitCenterFilterEntry.cs`
|
||||
- Properties: Code, Description
|
||||
- Validation: Record compiles with proper attributes
|
||||
|
||||
- [x] 009: Create WorkCenterFilterEntry record
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Models/FilterEntries/WorkCenterFilterEntry.cs`
|
||||
- Properties: Code, Description
|
||||
- Validation: Record compiles with proper attributes
|
||||
|
||||
- [x] 010: Create OperatorFilterEntry record
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Models/FilterEntries/OperatorFilterEntry.cs`
|
||||
- Properties: AddressNumber (long), UserID, FullName
|
||||
- Validation: Record compiles with proper attributes
|
||||
|
||||
- [x] 011: Create ComponentLotFilterEntry record
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Models/FilterEntries/ComponentLotFilterEntry.cs`
|
||||
- Properties: LotNumber, ItemNumber
|
||||
- Validation: Record compiles with proper attributes
|
||||
|
||||
- [x] 012: Create ItemOperationMisFilterEntry record
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Models/FilterEntries/ItemOperationMisFilterEntry.cs`
|
||||
- Properties: ItemNumber, OperationNumber, MisNumber, MisRevision
|
||||
- Validation: Record compiles with proper attributes
|
||||
|
||||
## Phase 4: Result Records
|
||||
|
||||
- [x] 013: Create SearchResult record
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Models/Results/SearchResult.cs`
|
||||
- Source: `OLD/WorkerService/Models/Reporting/SearchResult.cs`
|
||||
- Include all OutputColumn attributes matching legacy
|
||||
- Include computed InclusionReason property
|
||||
- Validation: All 18 output columns present with correct attributes
|
||||
|
||||
- [x] 014: Create MisSearchResult record
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Models/Results/MisSearchResult.cs`
|
||||
- Source: `OLD/WorkerService/Models/Reporting/MisSearchResult.cs`
|
||||
- Validation: All MIS-related columns present
|
||||
|
||||
- [x] 015: Create MisNonMatchSearchResult record
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Models/Results/MisNonMatchSearchResult.cs`
|
||||
- Source: `OLD/WorkerService/Models/Reporting/MisNonMatchSearchResult.cs`
|
||||
- Include: WasJobStepAdded, MatchedJobStepNumber columns
|
||||
- Validation: All non-match columns present
|
||||
|
||||
## Phase 5: SearchModel and Extensions
|
||||
|
||||
- [x] 016: Create SearchModel class
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Models/SearchModel.cs`
|
||||
- Source: `OLD/WorkerService/Models/Reporting/SearchModel.cs`
|
||||
- Include all filter collections and computed *Enabled properties
|
||||
- Include Results, MisResults, MisNonMatchResults collections
|
||||
- Validation: All filter enabled properties work correctly
|
||||
|
||||
- [x] 017: Create SearchQueryResult record
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Models/SearchQueryResult.cs`
|
||||
- Properties: Sql, Parameters, TempTableSetupSql
|
||||
- Validation: Record compiles
|
||||
|
||||
- [x] 018: Create TableValuedParameterExtensions
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Extensions/TableValuedParameterExtensions.cs`
|
||||
- Methods: Create*FilterParameter for all 7 filter types
|
||||
- Source: `OLD/WorkerService/Helpers/SearchModelHelpers.cs`
|
||||
- Validation: All TVP methods compile and match legacy schema
|
||||
|
||||
- [x] 019: Create SearchModelExtensions
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Extensions/SearchModelExtensions.cs`
|
||||
- Methods: ShouldSearchSteps, ToSearchModel (from Search entity)
|
||||
- Source: `OLD/WorkerService/Helpers/SearchModelHelpers.cs`
|
||||
- Validation: ShouldSearchSteps logic matches legacy
|
||||
|
||||
## Phase 6: Query Builder Interfaces
|
||||
|
||||
- [x] 020: Create IFilterHandler interface
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Interfaces/IFilterHandler.cs`
|
||||
- Methods: IsEnabled, Apply, Priority property
|
||||
- Validation: Interface compiles
|
||||
|
||||
- [x] 021: Create FilterResult record
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Models/FilterResult.cs`
|
||||
- Properties: SetupSql (IReadOnlyList<string>), Parameters (IDictionary)
|
||||
- Validation: Record compiles
|
||||
|
||||
- [x] 022: Create ISearchQueryBuilder interface
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Interfaces/ISearchQueryBuilder.cs`
|
||||
- Methods: BuildSearchQuery, BuildMisQuery, BuildMisNonMatchQuery
|
||||
- Validation: Interface compiles
|
||||
|
||||
## Phase 7: Filter Handlers
|
||||
|
||||
- [x] 023: Create FilterHandlerBase abstract class
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/FilterHandlers/FilterHandlerBase.cs`
|
||||
- Common functionality for all handlers
|
||||
- Validation: Abstract class compiles
|
||||
|
||||
- [x] 024: Create WorkOrderFilterHandler
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/FilterHandlers/WorkOrderFilterHandler.cs`
|
||||
- Source: QueryTemplate.tt lines 26-96
|
||||
- Generates: MERGE #Temp_WO with ManuallySpecified flag, split order detection
|
||||
- Validation: Unit test verifies SQL structure
|
||||
|
||||
- [x] 025: Create ItemNumberFilterHandler
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/FilterHandlers/ItemNumberFilterHandler.cs`
|
||||
- Source: QueryTemplate.tt lines 48-64
|
||||
- Generates: #P_ItemNumbers temp table
|
||||
- Validation: Unit test verifies SQL structure
|
||||
|
||||
- [x] 026: Create ProfitCenterFilterHandler
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/FilterHandlers/ProfitCenterFilterHandler.cs`
|
||||
- Source: QueryTemplate.tt lines 65-89
|
||||
- Generates: #P_WorkCenters temp table via OrgHierarchy join
|
||||
- Validation: Unit test verifies SQL structure
|
||||
|
||||
- [x] 027: Create WorkCenterFilterHandler
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/FilterHandlers/WorkCenterFilterHandler.cs`
|
||||
- Source: QueryTemplate.tt lines 90-102
|
||||
- Generates: MERGE into #P_WorkCenters
|
||||
- Validation: Unit test verifies SQL structure
|
||||
|
||||
- [x] 028: Create OperatorFilterHandler
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/FilterHandlers/OperatorFilterHandler.cs`
|
||||
- Source: QueryTemplate.tt lines 147-172
|
||||
- Generates: #P_OperatorIDs temp table with JdeUser lookup
|
||||
- Validation: Unit test verifies SQL structure
|
||||
|
||||
- [x] 029: Create ComponentLotFilterHandler
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/FilterHandlers/ComponentLotFilterHandler.cs`
|
||||
- Source: QueryTemplate.tt lines 103-146
|
||||
- Generates: WorkOrderComponent/LotUsage joins, CARDEX flag
|
||||
- Validation: Unit test verifies SQL structure
|
||||
|
||||
- [x] 030: Create ItemOperationMisFilterHandler
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/FilterHandlers/ItemOperationMisFilterHandler.cs`
|
||||
- Source: QueryTemplate.tt lines 173-198
|
||||
- Generates: #P_PartOperations temp table
|
||||
- Validation: Unit test verifies SQL structure
|
||||
|
||||
- [x] 031: Create TimespanFilterHandler
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/FilterHandlers/TimespanFilterHandler.cs`
|
||||
- Source: QueryTemplate.tt lines 254-258
|
||||
- Generates: WHERE clause with @p_MinimumDT/@p_MaximumDT
|
||||
- Business rule: Requires BOTH min and max for combined condition
|
||||
- Validation: Unit test verifies SQL structure
|
||||
|
||||
## Phase 8: Query Builder Implementation
|
||||
|
||||
- [x] 032: Create SqlKataSearchQueryBuilder
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/QueryBuilders/SqlKataSearchQueryBuilder.cs`
|
||||
- Orchestrates filter handlers
|
||||
- Builds #Temp_WO setup, downstream traversal call, final SELECT
|
||||
- Source: QueryTemplate.tt full template
|
||||
- Validation: Generated SQL matches legacy structure
|
||||
|
||||
- [x] 033: Create MisQueryBuilder
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/QueryBuilders/MisQueryBuilder.cs`
|
||||
- Builds MIS extraction queries (MIS_CTE, #TempMisData)
|
||||
- Source: QueryTemplate.tt lines 353-482
|
||||
- Note: MIS extraction does NOT join #Temp_WO
|
||||
- Validation: Generated SQL matches legacy structure
|
||||
|
||||
## Phase 9: Services
|
||||
|
||||
- [x] 034: Create ISearchProcessor interface
|
||||
- Note: Existing interface at `NEW/src/JdeScoping.Core/Interfaces/ISearchProcessor.cs`
|
||||
- Methods: ExecuteSearchAsync (IAsyncEnumerable), ExecuteSearchToModelAsync
|
||||
- Validation: Interface compiles
|
||||
|
||||
- [x] 035: Create IWorkOrderTraversalService interface
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Interfaces/IWorkOrderTraversalService.cs`
|
||||
- Method: TraverseDownstreamAsync
|
||||
- Validation: Interface compiles
|
||||
|
||||
- [x] 036: Create WorkOrderTraversalService
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Services/WorkOrderTraversalService.cs`
|
||||
- Calls dbo.TraverseWorkOrders stored procedure
|
||||
- Executes WHILE loop logic for downstream work orders
|
||||
- Source: QueryTemplate.tt lines 285-349
|
||||
- Validation: Service compiles, stored procedure exists
|
||||
|
||||
- [x] 037: Create SearchProcessor service
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/Services/SearchProcessor.cs`
|
||||
- Constructor: IDbConnectionFactory, ISearchQueryBuilder, IWorkOrderTraversalService, ILotFinderRepository, ILogger
|
||||
- Implements full search flow:
|
||||
1. Enrich filter entries via repository lookups
|
||||
2. Build query via SqlKata
|
||||
3. Execute temp table setup
|
||||
4. Call downstream traversal
|
||||
5. Execute result query
|
||||
6. Optionally extract MIS data
|
||||
- Validation: Service compiles, all dependencies injected
|
||||
|
||||
## Phase 10: Service Registration
|
||||
|
||||
- [x] 038: Create ServiceCollectionExtensions
|
||||
- Location: `NEW/src/JdeScoping.SearchProcessing/ServiceCollectionExtensions.cs`
|
||||
- Method: AddSearchProcessing(IServiceCollection, IConfiguration)
|
||||
- Registers: Options, SqlServerCompiler (singleton), filter handlers (scoped), services (scoped)
|
||||
- Validation: Services resolve correctly at runtime
|
||||
|
||||
- [x] 039: Register search processing in Program.cs
|
||||
- Location: `NEW/src/JdeScoping.Host/Program.cs`
|
||||
- Note: Updated to use SearchProcessing module's AddSearchProcessing; renamed Core's method to AddSearchProcessingOptions
|
||||
- Validation: Application starts without DI errors
|
||||
|
||||
## Phase 11: Unit Tests
|
||||
|
||||
- [x] 040: Create test project
|
||||
- Location: `NEW/tests/JdeScoping.SearchProcessing.Tests/JdeScoping.SearchProcessing.Tests.csproj`
|
||||
- Dependencies: xUnit, Shouldly, NSubstitute
|
||||
- Validation: Test project builds
|
||||
|
||||
- [x] 041: Write WorkOrderFilterHandler tests
|
||||
- Location: `NEW/tests/JdeScoping.SearchProcessing.Tests/FilterHandlers/WorkOrderFilterHandlerTests.cs`
|
||||
- Test: IsEnabled returns correct value
|
||||
- Test: Generated SQL contains MERGE and ManuallySpecified
|
||||
- Validation: All tests pass (7 tests)
|
||||
|
||||
- [x] 042: Write ComponentLotFilterHandler tests
|
||||
- Location: `NEW/tests/JdeScoping.SearchProcessing.Tests/FilterHandlers/ComponentLotFilterHandlerTests.cs`
|
||||
- Test: IsEnabled returns correct value
|
||||
- Test: Generated SQL contains WorkOrderComponent join
|
||||
- Test: CARDEX flag is set (not PartsList)
|
||||
- Validation: All tests pass (9 tests)
|
||||
|
||||
- [x] 043: Write SqlKataSearchQueryBuilder tests
|
||||
- Location: `NEW/tests/JdeScoping.SearchProcessing.Tests/QueryBuilders/SqlKataSearchQueryBuilderTests.cs`
|
||||
- Test: Empty filters produces minimal query
|
||||
- Test: Single filter produces correct structure
|
||||
- Test: Multiple filters combine correctly
|
||||
- Validation: All tests pass (10 tests)
|
||||
|
||||
- [x] 044: Write TableValuedParameterExtensions tests
|
||||
- Location: `NEW/tests/JdeScoping.SearchProcessing.Tests/Extensions/TableValuedParameterExtensionsTests.cs`
|
||||
- Test: Each Create*Parameter method produces correct DataTable schema
|
||||
- Test: Empty collections produce empty DataTables
|
||||
- Validation: All tests pass (17 tests)
|
||||
|
||||
- [x] 045: Write SearchResult tests
|
||||
- Location: `NEW/tests/JdeScoping.SearchProcessing.Tests/Models/SearchResultTests.cs`
|
||||
- Test: InclusionReason returns correct values for all flag combinations
|
||||
- Test: ManuallySpecified takes priority over Flagged
|
||||
- Test: Unknown returned when no flags set
|
||||
- Validation: All tests pass (21 tests)
|
||||
|
||||
## Phase 12: Verification
|
||||
|
||||
- [x] 046: Run full test suite
|
||||
- Command: `dotnet test NEW/tests/JdeScoping.SearchProcessing.Tests/`
|
||||
- Validation: All 64 tests pass
|
||||
|
||||
- [x] 047: Verify solution builds
|
||||
- Command: `dotnet build NEW/JdeScoping.sln`
|
||||
- Validation: No errors or warnings
|
||||
|
||||
- [x] 048: Run OpenSpec validation
|
||||
- Command: `openspec validate implement-search-processing --strict`
|
||||
- Validation: No validation errors - "Change 'implement-search-processing' is valid"
|
||||
|
||||
- [x] 049: Codex MCP review of query builder output
|
||||
- SqlKataSearchQueryBuilder generates SQL matching legacy QueryTemplate.tt structure
|
||||
- Key features verified: #Temp_WO creation, MERGE statements, filter handler priority ordering
|
||||
- Validation: SQL structure matches legacy implementation
|
||||
Reference in New Issue
Block a user