using Dapper; using JdeScoping.DataAccess.Interfaces; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Logging; namespace JdeScoping.DataAccess.Services; /// /// Service for traversing downstream work orders iteratively. /// public sealed class WorkOrderTraversalService : IWorkOrderTraversalService { private readonly ILogger _logger; /// /// Initializes a new instance of WorkOrderTraversalService. /// /// The logger. public WorkOrderTraversalService(ILogger logger) { _logger = logger; } /// public async Task TraverseDownstreamAsync( SqlConnection connection, int maxIterations = 20, CancellationToken ct = default) { _logger.LogDebug("Starting downstream work order traversal (max {MaxIterations} iterations)", maxIterations); // The traversal SQL iteratively finds work orders that received material from flagged work orders const string traversalSql = """ --Add downlevel work orders that were issued material from flagged work orders DECLARE @c_MAX_RUNS INT = @p_MaxIterations; DECLARE @v_NumWO INT = -1; DECLARE @v_NewNumWO INT; DECLARE @v_NumRuns INT = 0; WHILE(1 = 1) BEGIN SET @v_NumWO = @v_NewNumWO; --Add any work orders issued material from flagged work orders (parts list) WITH CWO_D AS( SELECT DISTINCT wo.WorkOrderNumber, wo.LotNumber, wo.BranchCode, wo.ShortItemNumber FROM dbo.WorkOrderComponent AS woc INNER JOIN dbo.WorkOrder AS wo ON (woc.WorkOrderNumber = wo.WorkOrderNumber) INNER JOIN #Temp_WO AS t_wo ON (woc.LotNumber = t_wo.LotNumber AND woc.ShortItemNumber = t_wo.ShortItemNumber) ) MERGE #Temp_WO AS TARGET USING CWO_D AS SOURCE ON (TARGET.WorkOrderNumber = SOURCE.WorkOrderNumber) WHEN MATCHED THEN UPDATE SET TARGET.PartsList = 1 WHEN NOT MATCHED THEN INSERT (WorkOrderNumber, LotNumber, BranchCode, ShortItemNumber, PartsList) VALUES (SOURCE.WorkOrderNumber, COALESCE(SOURCE.LotNumber, CAST(SOURCE.WorkOrderNumber AS VARCHAR(8))), SOURCE.BranchCode, SOURCE.ShortItemNumber, 1); --Add any work orders issued material from flagged work orders (CARDEX) WITH CWO_D AS( SELECT DISTINCT wo.WorkOrderNumber, wo.LotNumber, wo.BranchCode, wo.ShortItemNumber FROM dbo.LotUsage AS lu INNER JOIN dbo.WorkOrder AS wo ON (lu.WorkOrderNumber = wo.WorkOrderNumber) INNER JOIN #Temp_WO AS t_wo ON (lu.LotNumber = t_wo.LotNumber AND lu.ShortItemNumber = t_wo.ShortItemNumber) ) MERGE #Temp_WO AS TARGET USING CWO_D AS SOURCE ON (TARGET.WorkOrderNumber = SOURCE.WorkOrderNumber) WHEN MATCHED THEN UPDATE SET TARGET.CARDEX = 1 WHEN NOT MATCHED THEN INSERT (WorkOrderNumber, LotNumber, BranchCode, ShortItemNumber, CARDEX) VALUES (SOURCE.WorkOrderNumber, COALESCE(SOURCE.LotNumber, CAST(SOURCE.WorkOrderNumber AS VARCHAR(8))), 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); SELECT @v_NewNumWO = COUNT(*) FROM #Temp_WO; SET @v_NumRuns = @v_NumRuns + 1; IF(@v_NumWO = @v_NewNumWO OR @v_NumRuns = @c_MAX_RUNS) BEGIN BREAK; END END; SELECT @v_NumRuns AS IterationsCompleted, @v_NewNumWO AS TotalWorkOrders; """; var result = await connection.QuerySingleAsync<(int IterationsCompleted, int TotalWorkOrders)>( traversalSql, new { p_MaxIterations = maxIterations }, commandTimeout: 600); _logger.LogDebug( "Downstream traversal completed in {Iterations} iterations, found {TotalWorkOrders} total work orders", result.IterationsCompleted, result.TotalWorkOrders); } }