diff --git a/NEW/src/JdeScoping.DataAccess/Interfaces/ISearchQueryBuilder.cs b/NEW/src/JdeScoping.DataAccess/Interfaces/ISearchQueryBuilder.cs
index b02fb1a..6fd46cc 100644
--- a/NEW/src/JdeScoping.DataAccess/Interfaces/ISearchQueryBuilder.cs
+++ b/NEW/src/JdeScoping.DataAccess/Interfaces/ISearchQueryBuilder.cs
@@ -3,28 +3,29 @@ using JdeScoping.DataAccess.Models;
namespace JdeScoping.DataAccess.Interfaces;
///
-/// Interface for building search queries using SqlKata.
+/// Builds SQL queries for search operations.
+/// Uses SQL extraction functions to retrieve criteria from the Search table.
///
public interface ISearchQueryBuilder
{
///
- /// Builds the main search query for flagging and retrieving work orders.
+ /// Builds the main search query using extraction functions.
///
- /// The search model containing filter criteria.
- /// The compiled query result with SQL, parameters, and setup statements.
- SearchQueryResult BuildSearchQuery(SearchModel model);
+ /// The search ID to extract criteria from.
+ /// Query result with SQL and parameters.
+ SearchQueryResult BuildSearchQuery(int searchId);
///
- /// Builds the MIS data extraction query when ExtractMisData is enabled.
+ /// Builds the MIS data extraction query.
///
- /// The search model containing filter criteria.
- /// The compiled query result for MIS extraction.
- SearchQueryResult BuildMisQuery(SearchModel model);
+ /// The search ID to extract criteria from.
+ /// Query result with SQL and parameters.
+ SearchQueryResult BuildMisQuery(int searchId);
///
- /// Builds the MIS non-match query for work orders without MIS records.
+ /// Builds the MIS non-match extraction query.
///
- /// The search model containing filter criteria.
- /// The compiled query result for MIS non-match extraction.
- SearchQueryResult BuildMisNonMatchQuery(SearchModel model);
+ /// The search ID.
+ /// Query result with SQL and parameters.
+ SearchQueryResult BuildMisNonMatchQuery(int searchId);
}
diff --git a/NEW/src/JdeScoping.DataAccess/QueryBuilders/SqlKataSearchQueryBuilder.cs b/NEW/src/JdeScoping.DataAccess/QueryBuilders/SqlKataSearchQueryBuilder.cs
index c94d428..34969a3 100644
--- a/NEW/src/JdeScoping.DataAccess/QueryBuilders/SqlKataSearchQueryBuilder.cs
+++ b/NEW/src/JdeScoping.DataAccess/QueryBuilders/SqlKataSearchQueryBuilder.cs
@@ -1,4 +1,3 @@
-using JdeScoping.DataAccess.Extensions;
using JdeScoping.DataAccess.Interfaces;
using JdeScoping.DataAccess.Models;
using SqlKata.Compilers;
@@ -7,83 +6,75 @@ namespace JdeScoping.DataAccess.QueryBuilders;
///
/// SqlKata-based implementation of ISearchQueryBuilder.
-/// Builds SQL queries using a fluent query builder pattern.
+/// Generates SQL that uses extraction functions to retrieve criteria from Search.Criteria JSON.
///
public sealed class SqlKataSearchQueryBuilder : ISearchQueryBuilder
{
private readonly SqlServerCompiler _compiler;
- private readonly IEnumerable _filterHandlers;
///
/// Initializes a new instance of SqlKataSearchQueryBuilder.
///
/// The SqlKata SQL Server compiler.
- /// Collection of filter handlers.
- public SqlKataSearchQueryBuilder(
- SqlServerCompiler compiler,
- IEnumerable filterHandlers)
+ public SqlKataSearchQueryBuilder(SqlServerCompiler compiler)
{
_compiler = compiler;
- _filterHandlers = filterHandlers.OrderBy(h => h.Priority);
}
///
- public SearchQueryResult BuildSearchQuery(SearchModel model)
+ public SearchQueryResult BuildSearchQuery(int searchId)
{
var setupStatements = new List();
- var parameters = new Dictionary();
+ var parameters = new Dictionary
+ {
+ ["SearchId"] = searchId
+ };
- // 1. Create the #Temp_WO temp table
+ // 1. Validate the search criteria
+ setupStatements.Add("EXEC dbo.usp_ValidateSearchCriteria @SearchId;");
+
+ // 2. Create the #Temp_WO temp table
setupStatements.Add(BuildTempWoTableSql());
- // 2. Apply filter handlers in priority order
- 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;
- }
- }
+ // 3. Create filter temp tables and populate from extraction functions
+ setupStatements.Add(BuildWorkOrderFilterSetup());
+ setupStatements.Add(BuildItemNumberFilterSetup());
+ setupStatements.Add(BuildWorkCenterFilterSetup());
+ setupStatements.Add(BuildOperatorFilterSetup());
+ setupStatements.Add(BuildComponentLotFilterSetup());
+ setupStatements.Add(BuildPartOperationsFilterSetup());
- // 3. Build step-based flagging query if needed
- if (model.ShouldSearchSteps())
- {
- setupStatements.Add(BuildStepFlaggingQuery(model));
- }
+ // 4. Build step-based flagging query
+ setupStatements.Add(BuildStepFlaggingQuery());
- // 4. Build the final result SELECT query
+ // 5. Build the final result SELECT query
var resultSql = BuildResultQuery();
return new SearchQueryResult(resultSql, parameters, setupStatements);
}
///
- public SearchQueryResult BuildMisQuery(SearchModel model)
+ public SearchQueryResult BuildMisQuery(int searchId)
{
- // MIS query is delegated to MisQueryBuilder
- // This is a placeholder - full implementation would generate MIS extraction SQL
- var parameters = new Dictionary();
-
- if (model.MinimumDt.HasValue)
+ var parameters = new Dictionary
{
- parameters["p_MinimumDT"] = model.MinimumDt.Value;
- }
- if (model.MaximumDt.HasValue)
- {
- parameters["p_MaximumDT"] = model.MaximumDt.Value;
- }
+ ["SearchId"] = searchId
+ };
- var sql = BuildMisExtractionQuery(model);
+ var sql = BuildMisExtractionQuery();
return new SearchQueryResult(sql, parameters, []);
}
///
- public SearchQueryResult BuildMisNonMatchQuery(SearchModel model)
+ public SearchQueryResult BuildMisNonMatchQuery(int searchId)
{
+ var parameters = new Dictionary
+ {
+ ["SearchId"] = searchId
+ };
+
var sql = BuildMisNonMatchExtractionQuery();
- return new SearchQueryResult(sql, new Dictionary(), []);
+ return new SearchQueryResult(sql, parameters, []);
}
private static string BuildTempWoTableSql()
@@ -110,147 +101,204 @@ public sealed class SqlKataSearchQueryBuilder : ISearchQueryBuilder
""";
}
- private string BuildStepFlaggingQuery(SearchModel model)
+ private static string BuildWorkOrderFilterSetup()
{
- // Build the complex step-based flagging query dynamically
- var queryParts = new List();
+ return """
+ --Setup work order filter from extraction function
+ IF OBJECT_ID('tempdb.dbo.#P_WorkOrders', 'U') IS NOT NULL
+ DROP TABLE #P_WorkOrders;
+ CREATE TABLE #P_WorkOrders (WorkOrderNumber BIGINT NOT NULL PRIMARY KEY);
+ INSERT INTO #P_WorkOrders (WorkOrderNumber)
+ SELECT WorkOrderNumber FROM dbo.fn_GetSearchWorkOrders(@SearchId);
- // First query part - WorkOrderStep based
- queryParts.Add(BuildStepSubquery(model, includeWorkOrderTime: true));
+ --Add manually specified work order numbers to flagged list
+ IF EXISTS (SELECT 1 FROM #P_WorkOrders)
+ BEGIN
+ WITH WOP_CTE AS(
+ SELECT DISTINCT wo.WorkOrderNumber,
+ wo.LotNumber,
+ wo.BranchCode,
+ wo.ShortItemNumber
+ FROM dbo.WorkOrder AS wo INNER JOIN
+ #P_WorkOrders AS pwof ON (wo.WorkOrderNumber = pwof.WorkOrderNumber)
+ )
+ MERGE #Temp_WO AS TARGET
+ USING WOP_CTE AS SOURCE
+ ON (TARGET.WorkOrderNumber = SOURCE.WorkOrderNumber)
+ WHEN MATCHED THEN
+ UPDATE SET TARGET.ManuallySpecified = 1
+ WHEN NOT MATCHED BY TARGET THEN
+ INSERT(WorkOrderNumber, LotNumber, BranchCode, ShortItemNumber, ManuallySpecified)
+ VALUES(SOURCE.WorkOrderNumber, SOURCE.LotNumber, SOURCE.BranchCode, SOURCE.ShortItemNumber, 1);
- // Second query part - WorkOrderTime only (if not filtering by MIS)
- if (!model.ItemOperationMisFilterEnabled)
- {
- queryParts.Add(BuildTimeOnlySubquery(model));
- }
-
- var unionQuery = string.Join("\r\n UNION \r\n", queryParts);
-
- return $"""
- --Query data
- WITH LU_WO AS(
- SELECT DISTINCT
- step.WorkOrderNumber,
- step.LotNumber,
- step.BranchCode,
- step.ShortItemNumber
- FROM (
- {unionQuery}
- ) step
- )
- MERGE INTO #Temp_WO AS TARGET
- USING LU_WO AS SOURCE
- ON (TARGET.WorkOrderNumber = SOURCE.WorkOrderNumber AND TARGET.BranchCode = SOURCE.BranchCode)
- WHEN MATCHED THEN
- UPDATE SET TARGET.Flagged = 1
- WHEN NOT MATCHED BY TARGET THEN
- INSERT(WorkOrderNumber, LotNumber, BranchCode, ShortItemNumber, Flagged)
- VALUES(SOURCE.WorkOrderNumber, SOURCE.LotNumber, SOURCE.BranchCode, SOURCE.ShortItemNumber, 1);
+ --Add any work orders split from flagged work orders
+ WITH SP_WO AS
+ (
+ SELECT DISTINCT wo.WorkOrderNumber,
+ wo.LotNumber,
+ wo.BranchCode,
+ wo.ShortItemNumber
+ FROM dbo.WorkOrder AS wo INNER JOIN
+ #Temp_WO AS tw_o ON (wo.ParentWorkOrderNumber = CAST(tw_o.WorkOrderNumber AS VARCHAR(8)) AND wo.BranchCode = tw_o.BranchCode)
+ )
+ MERGE #Temp_WO AS TARGET
+ USING SP_WO AS SOURCE
+ ON (TARGET.WorkOrderNumber = SOURCE.WorkOrderNumber AND TARGET.BranchCode = SOURCE.BranchCode)
+ WHEN MATCHED THEN
+ UPDATE SET TARGET.SplitOrder = 1
+ WHEN NOT MATCHED BY TARGET THEN
+ INSERT (WorkOrderNumber, LotNumber, BranchCode, ShortItemNumber, SplitOrder)
+ VALUES (SOURCE.WorkOrderNumber, SOURCE.LotNumber, SOURCE.BranchCode, SOURCE.ShortItemNumber, 1);
+ END
""";
}
- private string BuildStepSubquery(SearchModel model, bool includeWorkOrderTime)
+ private static string BuildItemNumberFilterSetup()
{
- var joins = new List();
- var whereClause = "";
-
- // Item number filter join
- if (model.ItemNumberFilterEnabled)
- {
- joins.Add(" #P_ItemNumbers p_in ON (wo.ItemNumber = p_in.ItemNumber) INNER JOIN");
- }
-
- // Base WorkOrderStep join
- joins.Add(" dbo.WorkOrderStep wos ON (wo.WorkOrderNumber = wos.WorkOrderNumber AND wo.BranchCode = wos.BranchCode) LEFT OUTER JOIN");
- joins.Add(" dbo.WorkOrderTime wot ON (wos.WorkOrderNumber = wot.WorkOrderNumber AND wos.StepNumber = wot.StepNumber AND wos.BranchCode = wot.BranchCode)");
-
- // Work center filter join
- if (model.ProfitCenterFilterEnabled || model.WorkCenterFilterEnabled)
- {
- joins.Add(" INNER JOIN \r\n #P_WorkCenters p_wc ON (wos.WorkCenterCode = p_wc.Code)");
- }
-
- // Operator filter join
- if (model.OperatorFilterEnabled)
- {
- joins.Add(" INNER JOIN \r\n #P_OperatorIDs p_oi ON (wot.AddressNumber = p_oi.AddressNumber)");
- }
-
- // MIS filter join (uses CROSS APPLY)
- if (model.ItemOperationMisFilterEnabled)
- {
- joins.Add("""
- CROSS APPLY
- dbo.MatchMIS(wo.WorkOrderNumber, wo.ItemNumber, wo.BranchCode, wo.RoutingType, wo.IssueDate, wos.WorkCenterCode,
- wos.StepNumber, wos.EndDT, wos.FunctionCode, wos.FunctionOperationDescription) AS mm INNER JOIN
- #P_PartOperations p_po on (mm.ItemNumber = p_po.ItemNumber AND mm.MisSequenceNumber = p_po.OperationNumber AND mm.MisNumber = p_po.MisNumber AND mm.RevID = p_po.MisRevision)
- """);
- }
-
- // Timespan WHERE clause
- if (model.TimespanFilterEnabled)
- {
- whereClause = """
- WHERE (wos.EndDT <= @p_MaximumDT AND wos.EndDT >= @p_MinimumDT) OR
- (wot.GlDate <= @p_MaximumDT AND wot.GlDate >= @p_MinimumDT)
- """;
- }
-
- return $"""
- SELECT DISTINCT
- wo.WorkOrderNumber,
- COALESCE(wo.LotNumber, CAST(wo.WorkOrderNumber AS VARCHAR(8))) AS LotNumber,
- wo.BranchCode,
- wo.ShortItemNumber
- FROM dbo.WorkOrder wo INNER JOIN
- {string.Join("\r\n", joins)}
- {whereClause}
+ return """
+ --Setup item number filter from extraction function
+ IF OBJECT_ID('tempdb.dbo.#P_ItemNumbers', 'U') IS NOT NULL
+ DROP TABLE #P_ItemNumbers;
+ CREATE TABLE #P_ItemNumbers (ItemNumber VARCHAR(128) NOT NULL PRIMARY KEY);
+ INSERT INTO #P_ItemNumbers (ItemNumber)
+ SELECT ItemNumber FROM dbo.fn_GetSearchItemNumbers(@SearchId);
""";
}
- private string BuildTimeOnlySubquery(SearchModel model)
+ private static string BuildWorkCenterFilterSetup()
{
- var joins = new List();
- var whereClause = "";
+ return """
+ --Setup work center filter from extraction functions (combines profit centers and work centers)
+ IF OBJECT_ID('tempdb.dbo.#P_WorkCenters', 'U') IS NOT NULL
+ DROP TABLE #P_WorkCenters;
+ CREATE TABLE #P_WorkCenters (Code VARCHAR(12) NOT NULL PRIMARY KEY);
- // Item number filter join
- if (model.ItemNumberFilterEnabled)
- {
- joins.Add(" #P_ItemNumbers p_in ON (wo.ItemNumber = p_in.ItemNumber) INNER JOIN");
- }
+ --Insert from profit centers (joined with WorkCenter table)
+ INSERT INTO #P_WorkCenters (Code)
+ SELECT DISTINCT wc.WorkCenterCode
+ FROM dbo.fn_GetSearchProfitCenters(@SearchId) pc
+ INNER JOIN dbo.WorkCenter wc ON pc.Code = wc.ProfitCenterCode
+ WHERE NOT EXISTS (SELECT 1 FROM #P_WorkCenters WHERE Code = wc.WorkCenterCode);
- // Base WorkOrderTime join
- joins.Add(" dbo.WorkOrderTime wot ON (wo.WorkOrderNumber = wot.WorkOrderNumber AND wo.BranchCode = wot.BranchCode)");
+ --Insert from work centers directly
+ INSERT INTO #P_WorkCenters (Code)
+ SELECT Code
+ FROM dbo.fn_GetSearchWorkCenters(@SearchId)
+ WHERE NOT EXISTS (SELECT 1 FROM #P_WorkCenters WHERE Code = Code);
+ """;
+ }
- // Work center filter join
- if (model.ProfitCenterFilterEnabled || model.WorkCenterFilterEnabled)
- {
- joins.Add(" INNER JOIN \r\n #P_WorkCenters p_wc ON (wot.WorkCenterCode = p_wc.Code)");
- }
+ private static string BuildOperatorFilterSetup()
+ {
+ return """
+ --Setup operator filter from extraction function
+ IF OBJECT_ID('tempdb.dbo.#P_OperatorIDs', 'U') IS NOT NULL
+ DROP TABLE #P_OperatorIDs;
+ CREATE TABLE #P_OperatorIDs (AddressNumber BIGINT NOT NULL PRIMARY KEY);
+ INSERT INTO #P_OperatorIDs (AddressNumber)
+ SELECT DISTINCT u.AddressNumber
+ FROM dbo.fn_GetSearchOperatorIDs(@SearchId) op
+ INNER JOIN dbo.JdeUser u ON op.OperatorID = u.UserId;
+ """;
+ }
- // Operator filter join
- if (model.OperatorFilterEnabled)
- {
- joins.Add(" INNER JOIN \r\n #P_OperatorIDs p_oi ON (wot.AddressNumber = p_oi.AddressNumber)");
- }
+ private static string BuildComponentLotFilterSetup()
+ {
+ return """
+ --Setup component lot filter from extraction function
+ IF OBJECT_ID('tempdb.dbo.#P_ComponentLots', 'U') IS NOT NULL
+ DROP TABLE #P_ComponentLots;
+ CREATE TABLE #P_ComponentLots (
+ LotNumber VARCHAR(30) NOT NULL,
+ ItemNumber VARCHAR(128) NOT NULL,
+ PRIMARY KEY (LotNumber, ItemNumber)
+ );
+ INSERT INTO #P_ComponentLots (LotNumber, ItemNumber)
+ SELECT LotNumber, ItemNumber FROM dbo.fn_GetSearchComponentLots(@SearchId);
+ """;
+ }
- // Timespan WHERE clause (only if both min and max are present)
- if (model.MinimumDt.HasValue && model.MaximumDt.HasValue)
- {
- whereClause = """
- WHERE (wot.GlDate <= @p_MaximumDT AND wot.GlDate >= @p_MinimumDT)
- """;
- }
+ private static string BuildPartOperationsFilterSetup()
+ {
+ return """
+ --Setup part operations filter from extraction function
+ IF OBJECT_ID('tempdb.dbo.#P_PartOperations', 'U') IS NOT NULL
+ DROP TABLE #P_PartOperations;
+ CREATE TABLE #P_PartOperations (
+ ItemNumber VARCHAR(128) NOT NULL,
+ OperationNumber VARCHAR(10) NOT NULL,
+ MisNumber VARCHAR(10) NULL,
+ MisRevision VARCHAR(10) NULL,
+ PRIMARY KEY (ItemNumber, OperationNumber)
+ );
+ INSERT INTO #P_PartOperations (ItemNumber, OperationNumber, MisNumber, MisRevision)
+ SELECT ItemNumber, OperationNumber, MisNumber, MisRevision
+ FROM dbo.fn_GetSearchPartOperations(@SearchId);
+ """;
+ }
- return $"""
- SELECT DISTINCT
- wo.WorkOrderNumber,
- COALESCE(wo.LotNumber, CAST(wo.WorkOrderNumber AS VARCHAR(8))) AS LotNumber,
- wo.BranchCode,
- wo.ShortItemNumber
- FROM dbo.WorkOrder wo INNER JOIN
- {string.Join("\r\n", joins)}
- {whereClause}
+ private static string BuildStepFlaggingQuery()
+ {
+ // Build the complex step-based flagging query dynamically using extraction functions
+ return """
+ --Query data using extraction functions for date filtering
+ DECLARE @MinDt DATETIME2(7) = dbo.fn_GetSearchMinimumDt(@SearchId);
+ DECLARE @MaxDt DATETIME2(7) = dbo.fn_GetSearchMaximumDt(@SearchId);
+ DECLARE @HasItemFilter BIT = CASE WHEN EXISTS(SELECT 1 FROM #P_ItemNumbers) THEN 1 ELSE 0 END;
+ DECLARE @HasWorkCenterFilter BIT = CASE WHEN EXISTS(SELECT 1 FROM #P_WorkCenters) THEN 1 ELSE 0 END;
+ DECLARE @HasOperatorFilter BIT = CASE WHEN EXISTS(SELECT 1 FROM #P_OperatorIDs) THEN 1 ELSE 0 END;
+ DECLARE @HasPartOpsFilter BIT = CASE WHEN EXISTS(SELECT 1 FROM #P_PartOperations) THEN 1 ELSE 0 END;
+ DECLARE @HasTimespanFilter BIT = CASE WHEN @MinDt IS NOT NULL OR @MaxDt IS NOT NULL THEN 1 ELSE 0 END;
+ DECLARE @ShouldSearchSteps BIT = CASE WHEN @HasTimespanFilter = 1 OR @HasWorkCenterFilter = 1 OR @HasOperatorFilter = 1 THEN 1 ELSE 0 END;
+
+ IF @ShouldSearchSteps = 1
+ BEGIN
+ WITH LU_WO AS(
+ SELECT DISTINCT
+ wo.WorkOrderNumber,
+ COALESCE(wo.LotNumber, CAST(wo.WorkOrderNumber AS VARCHAR(8))) AS LotNumber,
+ wo.BranchCode,
+ wo.ShortItemNumber
+ FROM dbo.WorkOrder wo
+ LEFT OUTER JOIN #P_ItemNumbers p_in ON (@HasItemFilter = 0 OR wo.ItemNumber = p_in.ItemNumber)
+ INNER JOIN dbo.WorkOrderStep wos ON (wo.WorkOrderNumber = wos.WorkOrderNumber AND wo.BranchCode = wos.BranchCode)
+ LEFT OUTER JOIN dbo.WorkOrderTime wot ON (wos.WorkOrderNumber = wot.WorkOrderNumber AND wos.StepNumber = wot.StepNumber AND wos.BranchCode = wot.BranchCode)
+ LEFT OUTER JOIN #P_WorkCenters p_wc ON (@HasWorkCenterFilter = 0 OR wos.WorkCenterCode = p_wc.Code)
+ LEFT OUTER JOIN #P_OperatorIDs p_oi ON (@HasOperatorFilter = 0 OR wot.AddressNumber = p_oi.AddressNumber)
+ WHERE (@HasItemFilter = 0 OR p_in.ItemNumber IS NOT NULL)
+ AND (@HasWorkCenterFilter = 0 OR p_wc.Code IS NOT NULL)
+ AND (@HasOperatorFilter = 0 OR p_oi.AddressNumber IS NOT NULL)
+ AND (@HasTimespanFilter = 0 OR
+ (wos.EndDT <= @MaxDt AND wos.EndDT >= @MinDt) OR
+ (wot.GlDate <= @MaxDt AND wot.GlDate >= @MinDt))
+
+ UNION
+
+ SELECT DISTINCT
+ wo.WorkOrderNumber,
+ COALESCE(wo.LotNumber, CAST(wo.WorkOrderNumber AS VARCHAR(8))) AS LotNumber,
+ wo.BranchCode,
+ wo.ShortItemNumber
+ FROM dbo.WorkOrder wo
+ LEFT OUTER JOIN #P_ItemNumbers p_in ON (@HasItemFilter = 0 OR wo.ItemNumber = p_in.ItemNumber)
+ INNER JOIN dbo.WorkOrderTime wot ON (wo.WorkOrderNumber = wot.WorkOrderNumber AND wo.BranchCode = wot.BranchCode)
+ LEFT OUTER JOIN #P_WorkCenters p_wc ON (@HasWorkCenterFilter = 0 OR wot.WorkCenterCode = p_wc.Code)
+ LEFT OUTER JOIN #P_OperatorIDs p_oi ON (@HasOperatorFilter = 0 OR wot.AddressNumber = p_oi.AddressNumber)
+ WHERE (@HasItemFilter = 0 OR p_in.ItemNumber IS NOT NULL)
+ AND (@HasWorkCenterFilter = 0 OR p_wc.Code IS NOT NULL)
+ AND (@HasOperatorFilter = 0 OR p_oi.AddressNumber IS NOT NULL)
+ AND @HasPartOpsFilter = 0
+ AND (@HasTimespanFilter = 0 OR (wot.GlDate <= @MaxDt AND wot.GlDate >= @MinDt))
+ )
+ MERGE INTO #Temp_WO AS TARGET
+ USING LU_WO AS SOURCE
+ ON (TARGET.WorkOrderNumber = SOURCE.WorkOrderNumber AND TARGET.BranchCode = SOURCE.BranchCode)
+ WHEN MATCHED THEN
+ UPDATE SET TARGET.Flagged = 1
+ WHEN NOT MATCHED BY TARGET THEN
+ INSERT(WorkOrderNumber, LotNumber, BranchCode, ShortItemNumber, Flagged)
+ VALUES(SOURCE.WorkOrderNumber, SOURCE.LotNumber, SOURCE.BranchCode, SOURCE.ShortItemNumber, 1);
+ END
""";
}
@@ -304,7 +352,7 @@ public sealed class SqlKataSearchQueryBuilder : ISearchQueryBuilder
""";
}
- private static string BuildMisExtractionQuery(SearchModel model)
+ private static string BuildMisExtractionQuery()
{
return """
--Get MIS search results
diff --git a/NEW/src/JdeScoping.DataAccess/Services/SearchProcessor.cs b/NEW/src/JdeScoping.DataAccess/Services/SearchProcessor.cs
index 6c3c9ae..740ec09 100644
--- a/NEW/src/JdeScoping.DataAccess/Services/SearchProcessor.cs
+++ b/NEW/src/JdeScoping.DataAccess/Services/SearchProcessor.cs
@@ -57,8 +57,8 @@ public sealed class SearchProcessor
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
- // Build the search query
- var queryResult = _queryBuilder.BuildSearchQuery(model);
+ // Build the search query using searchId only
+ var queryResult = _queryBuilder.BuildSearchQuery(model.Id);
if (_options.EnableDebugSql && !string.IsNullOrEmpty(_options.DebugSqlPath))
{
@@ -111,8 +111,8 @@ public sealed class SearchProcessor
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
- // Build the search query
- var queryResult = _queryBuilder.BuildSearchQuery(model);
+ // Build the search query using searchId only
+ var queryResult = _queryBuilder.BuildSearchQuery(model.Id);
if (_options.EnableDebugSql && !string.IsNullOrEmpty(_options.DebugSqlPath))
{
@@ -183,7 +183,7 @@ public sealed class SearchProcessor
}
// Execute MIS result query
- var misQueryResult = _queryBuilder.BuildMisQuery(model);
+ var misQueryResult = _queryBuilder.BuildMisQuery(model.Id);
var misResults = await connection.QueryAsync(
misQueryResult.Sql,
misQueryResult.Parameters,
@@ -193,7 +193,7 @@ public sealed class SearchProcessor
_logger.LogDebug("Found {MisResultCount} MIS results", model.MisResults.Count);
// Execute MIS non-match query
- var misNonMatchQueryResult = _queryBuilder.BuildMisNonMatchQuery(model);
+ var misNonMatchQueryResult = _queryBuilder.BuildMisNonMatchQuery(model.Id);
var misNonMatchResults = await connection.QueryAsync(
misNonMatchQueryResult.Sql,
misNonMatchQueryResult.Parameters,
diff --git a/NEW/tests/JdeScoping.DataAccess.Tests/QueryBuilders/SqlKataSearchQueryBuilderTests.cs b/NEW/tests/JdeScoping.DataAccess.Tests/QueryBuilders/SqlKataSearchQueryBuilderTests.cs
index 6d5c724..c479684 100644
--- a/NEW/tests/JdeScoping.DataAccess.Tests/QueryBuilders/SqlKataSearchQueryBuilderTests.cs
+++ b/NEW/tests/JdeScoping.DataAccess.Tests/QueryBuilders/SqlKataSearchQueryBuilderTests.cs
@@ -1,9 +1,5 @@
-using JdeScoping.DataAccess.FilterHandlers;
using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataAccess.Models;
-using JdeScoping.DataAccess.Models.FilterEntries;
using JdeScoping.DataAccess.QueryBuilders;
-using NSubstitute;
using Shouldly;
using SqlKata.Compilers;
using Xunit;
@@ -18,221 +14,173 @@ public sealed class SqlKataSearchQueryBuilderTests
private readonly SqlServerCompiler _compiler = new();
[Fact]
- public void BuildSearchQuery_WithEmptyFilters_ProducesMinimalQuery()
+ public void BuildSearchQuery_WithSearchId_ProducesValidQuery()
{
// Arrange
- var handlers = Array.Empty();
- var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
- var model = new SearchModel();
+ var builder = new SqlKataSearchQueryBuilder(_compiler);
+ var searchId = 123;
// Act
- var result = builder.BuildSearchQuery(model);
+ var result = builder.BuildSearchQuery(searchId);
// Assert
result.ShouldNotBeNull();
result.Sql.ShouldNotBeNullOrEmpty();
result.TempTableSetupSql.ShouldNotBeEmpty();
+ result.Parameters.ShouldContainKey("SearchId");
+ result.Parameters["SearchId"].ShouldBe(searchId);
+ }
- // Should contain temp table creation
+ [Fact]
+ public void BuildSearchQuery_ContainsTempTableCreation()
+ {
+ // Arrange
+ var builder = new SqlKataSearchQueryBuilder(_compiler);
+
+ // Act
+ var result = builder.BuildSearchQuery(1);
+
+ // Assert
var setupSql = string.Join("\n", result.TempTableSetupSql);
setupSql.ShouldContain("#Temp_WO");
setupSql.ShouldContain("CREATE TABLE");
}
[Fact]
- public void BuildSearchQuery_WithEmptyFilters_ResultSqlContainsSelect()
+ public void BuildSearchQuery_ContainsValidationCall()
{
// Arrange
- var handlers = Array.Empty();
- var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
- var model = new SearchModel();
+ var builder = new SqlKataSearchQueryBuilder(_compiler);
// Act
- var result = builder.BuildSearchQuery(model);
+ var result = builder.BuildSearchQuery(1);
+
+ // Assert
+ var setupSql = string.Join("\n", result.TempTableSetupSql);
+ setupSql.ShouldContain("usp_ValidateSearchCriteria");
+ setupSql.ShouldContain("@SearchId");
+ }
+
+ [Fact]
+ public void BuildSearchQuery_UsesExtractionFunctions()
+ {
+ // Arrange
+ var builder = new SqlKataSearchQueryBuilder(_compiler);
+
+ // Act
+ var result = builder.BuildSearchQuery(1);
+
+ // Assert
+ var setupSql = string.Join("\n", result.TempTableSetupSql);
+
+ // Should use extraction functions instead of TVPs
+ setupSql.ShouldContain("fn_GetSearchWorkOrders(@SearchId)");
+ setupSql.ShouldContain("fn_GetSearchItemNumbers(@SearchId)");
+ setupSql.ShouldContain("fn_GetSearchProfitCenters(@SearchId)");
+ setupSql.ShouldContain("fn_GetSearchWorkCenters(@SearchId)");
+ setupSql.ShouldContain("fn_GetSearchOperatorIDs(@SearchId)");
+ setupSql.ShouldContain("fn_GetSearchComponentLots(@SearchId)");
+ setupSql.ShouldContain("fn_GetSearchPartOperations(@SearchId)");
+ setupSql.ShouldContain("fn_GetSearchMinimumDt(@SearchId)");
+ setupSql.ShouldContain("fn_GetSearchMaximumDt(@SearchId)");
+ }
+
+ [Fact]
+ public void BuildSearchQuery_CreatesFilterTempTables()
+ {
+ // Arrange
+ var builder = new SqlKataSearchQueryBuilder(_compiler);
+
+ // Act
+ var result = builder.BuildSearchQuery(1);
+
+ // Assert
+ var setupSql = string.Join("\n", result.TempTableSetupSql);
+
+ // Should create filter temp tables
+ setupSql.ShouldContain("#P_WorkOrders");
+ setupSql.ShouldContain("#P_ItemNumbers");
+ setupSql.ShouldContain("#P_WorkCenters");
+ setupSql.ShouldContain("#P_OperatorIDs");
+ setupSql.ShouldContain("#P_ComponentLots");
+ setupSql.ShouldContain("#P_PartOperations");
+ }
+
+ [Fact]
+ public void BuildSearchQuery_ResultSqlContainsSelect()
+ {
+ // Arrange
+ var builder = new SqlKataSearchQueryBuilder(_compiler);
+
+ // Act
+ var result = builder.BuildSearchQuery(1);
// Assert
result.Sql.ShouldContain("SELECT");
+ result.Sql.ShouldContain("WorkOrderNumber");
}
[Fact]
- public void BuildSearchQuery_WithSingleFilter_ProducesCorrectStructure()
+ public void BuildSearchQuery_ContainsStepFlaggingQuery()
{
// Arrange
- var workOrderHandler = new WorkOrderFilterHandler();
- var handlers = new IFilterHandler[] { workOrderHandler };
- var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
- var model = new SearchModel
- {
- WorkOrderFilter =
- [
- new WorkOrderFilterEntry { WorkOrderNumber = 12345 }
- ]
- };
+ var builder = new SqlKataSearchQueryBuilder(_compiler);
// Act
- var result = builder.BuildSearchQuery(model);
+ var result = builder.BuildSearchQuery(1);
// Assert
- result.ShouldNotBeNull();
- result.TempTableSetupSql.ShouldNotBeEmpty();
- result.Parameters.ShouldContainKey("p_WorkOrderFilter");
-
- var setupSql = string.Join("\n", result.TempTableSetupSql);
- // Should have temp table creation and work order merge
- setupSql.ShouldContain("#Temp_WO");
- setupSql.ShouldContain("MERGE");
- }
-
- [Fact]
- public void BuildSearchQuery_WithMultipleFilters_CombinesCorrectly()
- {
- // Arrange
- var workOrderHandler = new WorkOrderFilterHandler();
- var itemNumberHandler = new ItemNumberFilterHandler();
- var handlers = new IFilterHandler[] { workOrderHandler, itemNumberHandler };
- var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
- var model = new SearchModel
- {
- WorkOrderFilter =
- [
- new WorkOrderFilterEntry { WorkOrderNumber = 12345 }
- ],
- ItemNumberFilter =
- [
- new ItemNumberFilterEntry { ItemNumber = "ABC123" }
- ]
- };
-
- // Act
- var result = builder.BuildSearchQuery(model);
-
- // Assert
- result.ShouldNotBeNull();
- result.Parameters.ShouldContainKey("p_WorkOrderFilter");
- result.Parameters.ShouldContainKey("p_ItemNumberFilter");
-
- var setupSql = string.Join("\n", result.TempTableSetupSql);
- setupSql.ShouldContain("#Temp_WO");
- setupSql.ShouldContain("#P_ItemNumbers");
- }
-
- [Fact]
- public void BuildSearchQuery_HandlersAreAppliedInPriorityOrder()
- {
- // Arrange
- var lowPriorityHandler = Substitute.For();
- lowPriorityHandler.Priority.Returns(100);
- lowPriorityHandler.IsEnabled(Arg.Any()).Returns(true);
- lowPriorityHandler.Apply(Arg.Any(), Arg.Any())
- .Returns(new FilterResult(["-- LOW PRIORITY SQL"], new Dictionary()));
-
- var highPriorityHandler = Substitute.For();
- highPriorityHandler.Priority.Returns(1);
- highPriorityHandler.IsEnabled(Arg.Any()).Returns(true);
- highPriorityHandler.Apply(Arg.Any(), Arg.Any())
- .Returns(new FilterResult(["-- HIGH PRIORITY SQL"], new Dictionary()));
-
- // Pass handlers in reverse priority order to verify sorting
- var handlers = new[] { lowPriorityHandler, highPriorityHandler };
- var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
- var model = new SearchModel();
-
- // Act
- var result = builder.BuildSearchQuery(model);
-
- // Assert
- var setupSql = string.Join("\n", result.TempTableSetupSql);
- var highIndex = setupSql.IndexOf("-- HIGH PRIORITY SQL", StringComparison.Ordinal);
- var lowIndex = setupSql.IndexOf("-- LOW PRIORITY SQL", StringComparison.Ordinal);
-
- highIndex.ShouldBeGreaterThan(-1);
- lowIndex.ShouldBeGreaterThan(-1);
- highIndex.ShouldBeLessThan(lowIndex);
- }
-
- [Fact]
- public void BuildSearchQuery_DisabledHandlersAreSkipped()
- {
- // Arrange
- var enabledHandler = Substitute.For();
- enabledHandler.Priority.Returns(1);
- enabledHandler.IsEnabled(Arg.Any()).Returns(true);
- enabledHandler.Apply(Arg.Any(), Arg.Any())
- .Returns(new FilterResult(["-- ENABLED"], new Dictionary()));
-
- var disabledHandler = Substitute.For();
- disabledHandler.Priority.Returns(2);
- disabledHandler.IsEnabled(Arg.Any()).Returns(false);
-
- var handlers = new[] { enabledHandler, disabledHandler };
- var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
- var model = new SearchModel();
-
- // Act
- var result = builder.BuildSearchQuery(model);
-
- // Assert
- var setupSql = string.Join("\n", result.TempTableSetupSql);
- setupSql.ShouldContain("-- ENABLED");
-
- // Apply should never be called on disabled handler
- disabledHandler.DidNotReceive().Apply(Arg.Any(), Arg.Any());
- }
-
- [Fact]
- public void BuildSearchQuery_WithTimespanFilter_IncludesStepFlagging()
- {
- // Arrange
- var handlers = Array.Empty();
- var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
- var model = new SearchModel
- {
- MinimumDt = DateTime.Now.AddDays(-30),
- MaximumDt = DateTime.Now
- };
-
- // Act
- var result = builder.BuildSearchQuery(model);
-
- // Assert
- // When ShouldSearchSteps returns true, step flagging query is added
var setupSql = string.Join("\n", result.TempTableSetupSql);
setupSql.ShouldContain("LU_WO");
setupSql.ShouldContain("Flagged");
+ setupSql.ShouldContain("MERGE");
}
[Fact]
public void BuildMisQuery_ReturnsValidResult()
{
// Arrange
- var handlers = Array.Empty();
- var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
- var model = new SearchModel();
+ var builder = new SqlKataSearchQueryBuilder(_compiler);
// Act
- var result = builder.BuildMisQuery(model);
+ var result = builder.BuildMisQuery(1);
// Assert
result.ShouldNotBeNull();
result.Sql.ShouldNotBeNullOrEmpty();
result.Sql.ShouldContain("#TempMisData");
+ result.Parameters.ShouldContainKey("SearchId");
}
[Fact]
public void BuildMisNonMatchQuery_ReturnsValidResult()
{
// Arrange
- var handlers = Array.Empty();
- var builder = new SqlKataSearchQueryBuilder(_compiler, handlers);
- var model = new SearchModel();
+ var builder = new SqlKataSearchQueryBuilder(_compiler);
// Act
- var result = builder.BuildMisNonMatchQuery(model);
+ var result = builder.BuildMisNonMatchQuery(1);
// Assert
result.ShouldNotBeNull();
result.Sql.ShouldNotBeNullOrEmpty();
result.Sql.ShouldContain("WasJobStepAdded");
result.Sql.ShouldContain("MatchedJobStepNumber");
+ result.Parameters.ShouldContainKey("SearchId");
+ }
+
+ [Fact]
+ public void BuildSearchQuery_DifferentSearchIds_ProduceDifferentParameters()
+ {
+ // Arrange
+ var builder = new SqlKataSearchQueryBuilder(_compiler);
+
+ // Act
+ var result1 = builder.BuildSearchQuery(100);
+ var result2 = builder.BuildSearchQuery(200);
+
+ // Assert
+ result1.Parameters["SearchId"].ShouldBe(100);
+ result2.Parameters["SearchId"].ShouldBe(200);
}
}