From 7e36bb42256e21deb59c2bfe44ca470f4f57e9ce Mon Sep 17 00:00:00 2001
From: Joseph Doherty
Date: Mon, 19 Jan 2026 00:13:12 -0500
Subject: [PATCH] refactor: remove unused classes and consolidate ViewModels in
Core
Remove 9 unused types from Core (duplicate extension classes, TableSpec, ColumnSpec, LotLocation), move ComponentLotViewModel and OperatorViewModel from Client to Core, and refactor DataSync.Dev to use pipeline-based configuration. Fix Login.razor to use UserInfoDto directly.
---
.../Controllers/AuthController.cs | 11 +-
.../FileIOController.ComponentLots.cs | 2 +-
.../Controllers/FileIOController.Items.cs | 2 +-
.../FileIOController.WorkOrders.cs | 2 +-
.../Controllers/PipelineController.cs | 22 +-
NEW/src/JdeScoping.Api/Hubs/StatusHub.cs | 12 +-
.../Services/SearchNotificationService.cs | 6 +-
.../Auth/AuthStateProvider.cs | 10 +-
.../Auth/IUserStorageService.cs | 6 +-
.../Auth/UserStorageService.cs | 8 +-
.../Admin/PipelineScheduleSection.razor | 89 +++++++-
.../Components/Admin/SqlQueryModal.razor | 35 ++-
.../Extensions/ViewModelMappingExtensions.cs | 2 +
.../JdeScoping.Client/Models/SearchUpdate.cs | 15 --
.../JdeScoping.Client/Models/StatusUpdate.cs | 10 -
.../Models/UserInfoViewModel.cs | 38 ----
.../Models/ValidCombination.cs | 54 ++---
.../Pages/Admin/PipelineViewer.razor | 90 ++------
NEW/src/JdeScoping.Client/Pages/Login.razor | 11 +-
.../JdeScoping.Client/Pages/SearchEdit.razor | 2 +-
.../JdeScoping.Client/Pages/SearchQueue.razor | 4 +-
.../JdeScoping.Client/Pages/Searches.razor | 2 +-
.../Services/AuthApiClient.cs | 6 +-
.../JdeScoping.Client/Services/AuthService.cs | 14 +-
.../Services/HubConnectionService.cs | 14 +-
.../Services/IHubConnectionService.cs | 8 +-
NEW/src/JdeScoping.Client/_Imports.razor | 2 +
.../ApiContracts/Auth/UserInfoDto.cs | 54 +++++
.../ApiContracts/IAuthApiClient.cs | 4 +-
.../Pipelines/PipelineConfigDto.cs | 6 +-
.../Pipelines/PipelineExecutionDto.cs | 2 +-
.../Pipelines/PipelineStatusDto.cs | 2 +-
.../ApiContracts/SignalR/SearchUpdateDto.cs | 60 +++++
.../ApiContracts/SignalR/StatusUpdateDto.cs | 28 +++
.../Extensions/ItemExtensions.cs | 35 ---
.../Extensions/JdeUserExtensions.cs | 36 ---
.../Extensions/LotExtensions.cs | 35 ---
.../Extensions/ProfitCenterExtensions.cs | 35 ---
.../Extensions/WorkCenterExtensions.cs | 35 ---
.../Extensions/WorkOrderExtensions.cs | 35 ---
.../Models/Auth/LoginResultModel.cs | 4 +-
.../Models/Infrastructure/ColumnSpec.cs | 35 ---
.../Models/Infrastructure/TableSpec.cs | 83 -------
.../Models/Inventory/LotLocation.cs | 32 ---
.../ViewModels}/ComponentLotViewModel.cs | 2 +-
.../ViewModels}/OperatorViewModel.cs | 2 +-
.../JdeScoping.DataSync.Dev/BranchDevEtl.cs | 29 ---
.../Configuration/DevPipelinesRoot.cs | 58 +++++
.../Contracts/IDevEtlPipelineFactory.cs | 29 +++
.../JdeScoping.DataSync.Dev/DevEtlRegistry.cs | 86 +------
.../FunctionCodeDevEtl.cs | 29 ---
NEW/src/JdeScoping.DataSync.Dev/ItemDevEtl.cs | 29 ---
.../JdeScoping.DataSync.Dev.csproj | 11 +
.../JdeScoping.DataSync.Dev/JdeUserDevEtl.cs | 29 ---
NEW/src/JdeScoping.DataSync.Dev/LotDevEtl.cs | 29 ---
.../LotUsageCurrDevEtl.cs | 29 ---
.../LotUsageHistDevEtl.cs | 29 ---
.../JdeScoping.DataSync.Dev/MisDataDevEtl.cs | 29 ---
.../Options/DevPipelineOptions.cs | 18 ++
.../OrgHierarchyDevEtl.cs | 29 ---
.../Pipelines/dev-pipelines.json | 96 ++++++++
.../ProfitCenterDevEtl.cs | 29 ---
.../RouteMasterDevEtl.cs | 29 ---
.../Services/DevEtlPipelineFactory.cs | 123 ++++++++++
.../WorkCenterDevEtl.cs | 29 ---
.../WorkOrderComponentCurrDevEtl.cs | 29 ---
.../WorkOrderComponentHistDevEtl.cs | 29 ---
.../WorkOrderCurrDevEtl.cs | 29 ---
.../WorkOrderHistDevEtl.cs | 29 ---
.../WorkOrderRoutingDevEtl.cs | 29 ---
.../WorkOrderStepCurrDevEtl.cs | 29 ---
.../WorkOrderStepHistDevEtl.cs | 29 ---
.../WorkOrderTimeCurrDevEtl.cs | 29 ---
.../WorkOrderTimeHistDevEtl.cs | 29 ---
.../Generators/FluentTableWriter.cs | 2 +-
.../Controllers/AuthControllerTests.cs | 3 +-
.../Hubs/StatusHubTests.cs | 12 +-
.../Services/AuthApiClientTests.cs | 8 +-
.../BranchDevEtlTests.cs | 177 ---------------
.../DevEtlPipelineFactoryTests.cs | 210 ++++++++++++++++++
.../DevEtlRegistryTests.cs | 101 +++++++++
.../FunctionCodeDevEtlTests.cs | 96 --------
.../ItemDevEtlTests.cs | 96 --------
.../JdeUserDevEtlTests.cs | 96 --------
.../OrgHierarchyDevEtlTests.cs | 166 --------------
.../ProfitCenterDevEtlTests.cs | 117 ----------
.../RouteMasterDevEtlTests.cs | 96 --------
.../WorkCenterDevEtlTests.cs | 117 ----------
.../Services/SearchRepositoryTests.cs | 2 +-
89 files changed, 1049 insertions(+), 2282 deletions(-)
delete mode 100644 NEW/src/JdeScoping.Client/Models/SearchUpdate.cs
delete mode 100644 NEW/src/JdeScoping.Client/Models/StatusUpdate.cs
delete mode 100644 NEW/src/JdeScoping.Client/Models/UserInfoViewModel.cs
create mode 100644 NEW/src/JdeScoping.Core/ApiContracts/Auth/UserInfoDto.cs
create mode 100644 NEW/src/JdeScoping.Core/ApiContracts/SignalR/SearchUpdateDto.cs
create mode 100644 NEW/src/JdeScoping.Core/ApiContracts/SignalR/StatusUpdateDto.cs
delete mode 100644 NEW/src/JdeScoping.Core/Extensions/ItemExtensions.cs
delete mode 100644 NEW/src/JdeScoping.Core/Extensions/JdeUserExtensions.cs
delete mode 100644 NEW/src/JdeScoping.Core/Extensions/LotExtensions.cs
delete mode 100644 NEW/src/JdeScoping.Core/Extensions/ProfitCenterExtensions.cs
delete mode 100644 NEW/src/JdeScoping.Core/Extensions/WorkCenterExtensions.cs
delete mode 100644 NEW/src/JdeScoping.Core/Extensions/WorkOrderExtensions.cs
delete mode 100644 NEW/src/JdeScoping.Core/Models/Infrastructure/ColumnSpec.cs
delete mode 100644 NEW/src/JdeScoping.Core/Models/Infrastructure/TableSpec.cs
delete mode 100644 NEW/src/JdeScoping.Core/Models/Inventory/LotLocation.cs
rename NEW/src/{JdeScoping.Client/Models => JdeScoping.Core/ViewModels}/ComponentLotViewModel.cs (90%)
rename NEW/src/{JdeScoping.Client/Models => JdeScoping.Core/ViewModels}/OperatorViewModel.cs (89%)
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/BranchDevEtl.cs
create mode 100644 NEW/src/JdeScoping.DataSync.Dev/Configuration/DevPipelinesRoot.cs
create mode 100644 NEW/src/JdeScoping.DataSync.Dev/Contracts/IDevEtlPipelineFactory.cs
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/FunctionCodeDevEtl.cs
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/ItemDevEtl.cs
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/JdeUserDevEtl.cs
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/LotDevEtl.cs
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/LotUsageCurrDevEtl.cs
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/LotUsageHistDevEtl.cs
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/MisDataDevEtl.cs
create mode 100644 NEW/src/JdeScoping.DataSync.Dev/Options/DevPipelineOptions.cs
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/OrgHierarchyDevEtl.cs
create mode 100644 NEW/src/JdeScoping.DataSync.Dev/Pipelines/dev-pipelines.json
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/ProfitCenterDevEtl.cs
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/RouteMasterDevEtl.cs
create mode 100644 NEW/src/JdeScoping.DataSync.Dev/Services/DevEtlPipelineFactory.cs
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/WorkCenterDevEtl.cs
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/WorkOrderComponentCurrDevEtl.cs
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/WorkOrderComponentHistDevEtl.cs
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/WorkOrderCurrDevEtl.cs
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/WorkOrderHistDevEtl.cs
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/WorkOrderRoutingDevEtl.cs
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/WorkOrderStepCurrDevEtl.cs
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/WorkOrderStepHistDevEtl.cs
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/WorkOrderTimeCurrDevEtl.cs
delete mode 100644 NEW/src/JdeScoping.DataSync.Dev/WorkOrderTimeHistDevEtl.cs
delete mode 100644 NEW/tests/JdeScoping.DataSync.Dev.Tests/BranchDevEtlTests.cs
create mode 100644 NEW/tests/JdeScoping.DataSync.Dev.Tests/DevEtlPipelineFactoryTests.cs
create mode 100644 NEW/tests/JdeScoping.DataSync.Dev.Tests/DevEtlRegistryTests.cs
delete mode 100644 NEW/tests/JdeScoping.DataSync.Dev.Tests/FunctionCodeDevEtlTests.cs
delete mode 100644 NEW/tests/JdeScoping.DataSync.Dev.Tests/ItemDevEtlTests.cs
delete mode 100644 NEW/tests/JdeScoping.DataSync.Dev.Tests/JdeUserDevEtlTests.cs
delete mode 100644 NEW/tests/JdeScoping.DataSync.Dev.Tests/OrgHierarchyDevEtlTests.cs
delete mode 100644 NEW/tests/JdeScoping.DataSync.Dev.Tests/ProfitCenterDevEtlTests.cs
delete mode 100644 NEW/tests/JdeScoping.DataSync.Dev.Tests/RouteMasterDevEtlTests.cs
delete mode 100644 NEW/tests/JdeScoping.DataSync.Dev.Tests/WorkCenterDevEtlTests.cs
diff --git a/NEW/src/JdeScoping.Api/Controllers/AuthController.cs b/NEW/src/JdeScoping.Api/Controllers/AuthController.cs
index 6b3180a..c8bd356 100644
--- a/NEW/src/JdeScoping.Api/Controllers/AuthController.cs
+++ b/NEW/src/JdeScoping.Api/Controllers/AuthController.cs
@@ -2,6 +2,7 @@ using System.Security.Claims;
using System.Text.Json;
using JdeScoping.Api.Extensions;
using JdeScoping.Core.ApiContracts;
+using JdeScoping.Core.ApiContracts.Auth;
using JdeScoping.Core.Interfaces;
using JdeScoping.Core.Models;
using JdeScoping.Core.Models.Auth;
@@ -98,7 +99,8 @@ public class AuthController : ApiControllerBase
new AuthenticationProperties { IsPersistent = false });
_logger.LogInformation("User {Username} logged in successfully", loginModel.Username);
- return Ok(new LoginResultModel(true, null, result.User));
+ var userDto = UserInfoDto.FromUserInfo(result.User!);
+ return Ok(new LoginResultModel(true, null, userDto));
}
///
@@ -122,10 +124,11 @@ public class AuthController : ApiControllerBase
/// User info on success, 401 if not authenticated
[HttpGet("me")]
[Authorize]
- [ProducesResponseType(typeof(UserInfo), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(UserInfoDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
- public ActionResult GetCurrentUser()
+ public ActionResult GetCurrentUser()
{
- return Ok(CurrentUser);
+ // CurrentUser is guaranteed non-null due to [Authorize] attribute
+ return Ok(UserInfoDto.FromUserInfo(CurrentUser!));
}
}
diff --git a/NEW/src/JdeScoping.Api/Controllers/FileIOController.ComponentLots.cs b/NEW/src/JdeScoping.Api/Controllers/FileIOController.ComponentLots.cs
index 71b1271..f0c283f 100644
--- a/NEW/src/JdeScoping.Api/Controllers/FileIOController.ComponentLots.cs
+++ b/NEW/src/JdeScoping.Api/Controllers/FileIOController.ComponentLots.cs
@@ -31,7 +31,7 @@ public partial class FileIOController
try
{
- using var stream = file.OpenReadStream();
+ await using var stream = file.OpenReadStream();
var lotViewModels = _parserService.ParseComponentLots(stream);
var lots = await _repository.LookupLotsAsync(lotViewModels, ct);
diff --git a/NEW/src/JdeScoping.Api/Controllers/FileIOController.Items.cs b/NEW/src/JdeScoping.Api/Controllers/FileIOController.Items.cs
index 3faa22f..ea7f545 100644
--- a/NEW/src/JdeScoping.Api/Controllers/FileIOController.Items.cs
+++ b/NEW/src/JdeScoping.Api/Controllers/FileIOController.Items.cs
@@ -31,7 +31,7 @@ public partial class FileIOController
try
{
- using var stream = file.OpenReadStream();
+ await using var stream = file.OpenReadStream();
var itemNumbers = _parserService.ParseItems(stream);
var items = await _repository.LookupItemsAsync(itemNumbers, ct);
diff --git a/NEW/src/JdeScoping.Api/Controllers/FileIOController.WorkOrders.cs b/NEW/src/JdeScoping.Api/Controllers/FileIOController.WorkOrders.cs
index 198b531..7cdf61b 100644
--- a/NEW/src/JdeScoping.Api/Controllers/FileIOController.WorkOrders.cs
+++ b/NEW/src/JdeScoping.Api/Controllers/FileIOController.WorkOrders.cs
@@ -31,7 +31,7 @@ public partial class FileIOController
try
{
- using var stream = file.OpenReadStream();
+ await using var stream = file.OpenReadStream();
var workOrderNumbers = _parserService.ParseWorkOrders(stream);
var workOrders = await _repository.LookupWorkordersAsync(workOrderNumbers, ct);
diff --git a/NEW/src/JdeScoping.Api/Controllers/PipelineController.cs b/NEW/src/JdeScoping.Api/Controllers/PipelineController.cs
index e7ae136..c102ab5 100644
--- a/NEW/src/JdeScoping.Api/Controllers/PipelineController.cs
+++ b/NEW/src/JdeScoping.Api/Controllers/PipelineController.cs
@@ -149,10 +149,14 @@ public class PipelineController : ControllerBase
matchCols,
config.Destination.ExcludeFromUpdate?.ToList());
+ // Mass uses massQuery with no parameters; Daily/Hourly use query with parameters
+ var parameters = config.Source.Parameters?.Select(p => new PipelineParameterDto(
+ p.Key, p.Value.Format, p.Value.Source)).ToList() ?? [];
+
var schedules = new PipelineSchedulesDto(
- MapSchedule(config.Schedules?.Mass, defaults.Mass),
- MapSchedule(config.Schedules?.Daily, defaults.Daily),
- MapSchedule(config.Schedules?.Hourly, defaults.Hourly));
+ MapSchedule(config.Schedules?.Mass, defaults.Mass, config.Source.MassQuery, [], config.PreScripts, config.PostScripts),
+ MapSchedule(config.Schedules?.Daily, defaults.Daily, config.Source.Query, parameters, config.PreScripts, config.PostScripts),
+ MapSchedule(config.Schedules?.Hourly, defaults.Hourly, config.Source.Query, parameters, config.PreScripts, config.PostScripts));
return new PipelineConfigDto(
name,
@@ -167,7 +171,11 @@ public class PipelineController : ControllerBase
private static PipelineScheduleDto MapSchedule(
ScheduleConfig? config,
- ScheduleConfig defaults)
+ ScheduleConfig defaults,
+ string? query,
+ List parameters,
+ List? preScripts,
+ List? postScripts)
{
return new PipelineScheduleDto(
config?.Enabled ?? defaults.Enabled,
@@ -176,7 +184,11 @@ public class PipelineController : ControllerBase
config?.ReIndex ?? defaults.ReIndex,
config?.IntervalMinutes > 0 && config.IntervalMinutes != defaults.IntervalMinutes,
config?.PrePurge != null && config.PrePurge != defaults.PrePurge,
- config?.ReIndex != null && config.ReIndex != defaults.ReIndex);
+ config?.ReIndex != null && config.ReIndex != defaults.ReIndex,
+ query,
+ parameters,
+ preScripts,
+ postScripts);
}
private static ScheduleConfig? GetScheduleConfig(
diff --git a/NEW/src/JdeScoping.Api/Hubs/StatusHub.cs b/NEW/src/JdeScoping.Api/Hubs/StatusHub.cs
index 2df17f8..6d8af25 100644
--- a/NEW/src/JdeScoping.Api/Hubs/StatusHub.cs
+++ b/NEW/src/JdeScoping.Api/Hubs/StatusHub.cs
@@ -1,6 +1,4 @@
-using JdeScoping.Core.Models;
-using JdeScoping.Core.Models.Infrastructure;
-using JdeScoping.Core.Models.Search;
+using JdeScoping.Core.ApiContracts.SignalR;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
@@ -11,7 +9,7 @@ namespace JdeScoping.Api.Hubs;
///
public class StatusHub : Hub
{
- private static StatusUpdate _cachedStatus = new()
+ private static StatusUpdateDto _cachedStatus = new()
{
Message = "Unknown",
Timestamp = DateTime.UtcNow
@@ -29,7 +27,7 @@ public class StatusHub : Hub
/// Caches the update and broadcasts to all clients.
///
/// Status update to broadcast
- public async Task SetStatus(StatusUpdate statusUpdate)
+ public async Task SetStatus(StatusUpdateDto statusUpdate)
{
_cachedStatus = statusUpdate;
await Clients.All.SendAsync("statusUpdate", statusUpdate);
@@ -40,7 +38,7 @@ public class StatusHub : Hub
/// Called by clients to get initial cached status on connection.
///
/// The most recent status update
- public StatusUpdate GetCachedStatus()
+ public StatusUpdateDto GetCachedStatus()
{
return _cachedStatus;
}
@@ -49,7 +47,7 @@ public class StatusHub : Hub
/// Called by controllers/services to broadcast search updates.
///
/// Search update to broadcast
- public async Task PublishSearchUpdate(SearchUpdate searchUpdate)
+ public async Task PublishSearchUpdate(SearchUpdateDto searchUpdate)
{
await Clients.All.SendAsync("searchUpdate", searchUpdate);
_logger.LogDebug("Search update published: ID={Id}, Status={Status}", searchUpdate.Id, searchUpdate.Status);
diff --git a/NEW/src/JdeScoping.Api/Services/SearchNotificationService.cs b/NEW/src/JdeScoping.Api/Services/SearchNotificationService.cs
index 4c63e81..45e6d52 100644
--- a/NEW/src/JdeScoping.Api/Services/SearchNotificationService.cs
+++ b/NEW/src/JdeScoping.Api/Services/SearchNotificationService.cs
@@ -1,6 +1,6 @@
using JdeScoping.Api.Hubs;
+using JdeScoping.Core.ApiContracts.SignalR;
using JdeScoping.Core.Interfaces;
-using JdeScoping.Core.Models.Infrastructure;
using JdeScoping.Core.Models.Search;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
@@ -34,7 +34,7 @@ public class SearchNotificationService : ISearchNotificationService
{
try
{
- var update = new SearchUpdate(search);
+ var update = SearchUpdateDto.FromSearch(search);
await _hubContext.Clients.All.SendAsync("searchUpdate", update, ct);
_logger.LogDebug(
"Search update notification sent: Id={SearchId}, Status={Status}",
@@ -56,7 +56,7 @@ public class SearchNotificationService : ISearchNotificationService
{
try
{
- var update = new StatusUpdate(status);
+ var update = StatusUpdateDto.Create(status);
await _hubContext.Clients.All.SendAsync("statusUpdate", update, ct);
_logger.LogDebug("Status notification sent: {Status}", status);
}
diff --git a/NEW/src/JdeScoping.Client/Auth/AuthStateProvider.cs b/NEW/src/JdeScoping.Client/Auth/AuthStateProvider.cs
index 918b761..ce39beb 100644
--- a/NEW/src/JdeScoping.Client/Auth/AuthStateProvider.cs
+++ b/NEW/src/JdeScoping.Client/Auth/AuthStateProvider.cs
@@ -1,6 +1,6 @@
using System.Net.Http.Json;
using System.Security.Claims;
-using JdeScoping.Client.Models;
+using JdeScoping.Core.ApiContracts.Auth;
using Microsoft.AspNetCore.Components.Authorization;
namespace JdeScoping.Client.Auth;
@@ -46,14 +46,14 @@ public class AuthStateProvider : AuthenticationStateProvider
/// Validates the current session by calling /api/auth/me.
/// Returns null if not authenticated.
///
- private async Task ValidateSessionAsync()
+ private async Task ValidateSessionAsync()
{
try
{
var response = await _httpClient.GetAsync("api/auth/me");
if (response.IsSuccessStatusCode)
{
- return await response.Content.ReadFromJsonAsync();
+ return await response.Content.ReadFromJsonAsync();
}
}
catch
@@ -67,7 +67,7 @@ public class AuthStateProvider : AuthenticationStateProvider
///
/// Creates an authenticated state from user info.
///
- private static AuthenticationState CreateAuthState(UserInfoViewModel user)
+ private static AuthenticationState CreateAuthState(UserInfoDto user)
{
var claims = new List
{
@@ -87,7 +87,7 @@ public class AuthStateProvider : AuthenticationStateProvider
///
/// Called after successful login to update auth state.
///
- public async Task MarkUserAsAuthenticated(UserInfoViewModel user)
+ public async Task MarkUserAsAuthenticated(UserInfoDto user)
{
await _userStorage.SetUserAsync(user);
NotifyAuthenticationStateChanged(Task.FromResult(CreateAuthState(user)));
diff --git a/NEW/src/JdeScoping.Client/Auth/IUserStorageService.cs b/NEW/src/JdeScoping.Client/Auth/IUserStorageService.cs
index 9e3028e..1cc9eb5 100644
--- a/NEW/src/JdeScoping.Client/Auth/IUserStorageService.cs
+++ b/NEW/src/JdeScoping.Client/Auth/IUserStorageService.cs
@@ -1,4 +1,4 @@
-using JdeScoping.Client.Models;
+using JdeScoping.Core.ApiContracts.Auth;
namespace JdeScoping.Client.Auth;
@@ -12,12 +12,12 @@ public interface IUserStorageService
///
/// Gets the stored user info.
///
- Task GetUserAsync();
+ Task GetUserAsync();
///
/// Stores the user info.
///
- Task SetUserAsync(UserInfoViewModel user);
+ Task SetUserAsync(UserInfoDto user);
///
/// Removes the stored user info.
diff --git a/NEW/src/JdeScoping.Client/Auth/UserStorageService.cs b/NEW/src/JdeScoping.Client/Auth/UserStorageService.cs
index adb229f..11cf690 100644
--- a/NEW/src/JdeScoping.Client/Auth/UserStorageService.cs
+++ b/NEW/src/JdeScoping.Client/Auth/UserStorageService.cs
@@ -1,5 +1,5 @@
using System.Text.Json;
-using JdeScoping.Client.Models;
+using JdeScoping.Core.ApiContracts.Auth;
using Microsoft.JSInterop;
namespace JdeScoping.Client.Auth;
@@ -19,7 +19,7 @@ public class UserStorageService : IUserStorageService
_jsRuntime = jsRuntime;
}
- public async Task GetUserAsync()
+ public async Task GetUserAsync()
{
try
{
@@ -29,7 +29,7 @@ public class UserStorageService : IUserStorageService
return null;
}
- return JsonSerializer.Deserialize(json, new JsonSerializerOptions
+ return JsonSerializer.Deserialize(json, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
});
@@ -40,7 +40,7 @@ public class UserStorageService : IUserStorageService
}
}
- public async Task SetUserAsync(UserInfoViewModel user)
+ public async Task SetUserAsync(UserInfoDto user)
{
var json = JsonSerializer.Serialize(user);
await _jsRuntime.InvokeVoidAsync("jdeScopingInterop.setSessionStorage", UserKey, json);
diff --git a/NEW/src/JdeScoping.Client/Components/Admin/PipelineScheduleSection.razor b/NEW/src/JdeScoping.Client/Components/Admin/PipelineScheduleSection.razor
index 3e5a23a..1786111 100644
--- a/NEW/src/JdeScoping.Client/Components/Admin/PipelineScheduleSection.razor
+++ b/NEW/src/JdeScoping.Client/Components/Admin/PipelineScheduleSection.razor
@@ -51,16 +51,42 @@
- @if (!string.IsNullOrWhiteSpace(QueryPreview))
+ @if (Config.Parameters?.Count > 0)
+ {
+ Parameters
+
+ @foreach (var param in Config.Parameters)
+ {
+ - @param.Name: @(param.Format ?? "default") (source: @param.Source)
+ }
+
+ }
+
+ @if (!string.IsNullOrWhiteSpace(Config.Query))
{
Query
-
- @QueryPreview
-
- @if (!string.IsNullOrWhiteSpace(FullQuery))
+ @FormatSql(Config.Query)
+ }
+
+ @if (Config.PreScripts?.Count > 0)
+ {
+ Pre-Scripts (@Config.PreScripts.Count)
+ @for (int i = 0; i < Config.PreScripts.Count; i++)
{
-
+ var script = Config.PreScripts[i];
+ Script @(i + 1):
+ @FormatSql(script)
+ }
+ }
+
+ @if (Config.PostScripts?.Count > 0)
+ {
+ Post-Scripts (@Config.PostScripts.Count)
+ @for (int i = 0; i < Config.PostScripts.Count; i++)
+ {
+ var script = Config.PostScripts[i];
+ Script @(i + 1):
+ @FormatSql(script)
}
}
}
@@ -69,9 +95,6 @@
@code {
[Parameter] public UpdateTypes ScheduleType { get; set; }
[Parameter] public PipelineScheduleDto? Config { get; set; }
- [Parameter] public string? QueryPreview { get; set; }
- [Parameter] public string? FullQuery { get; set; }
- [Parameter] public EventCallback OnViewQuery { get; set; }
private static string GetScheduleTypeName(UpdateTypes type) => type switch
{
@@ -89,4 +112,50 @@
return $"{minutes / 60} hour(s) ({minutes} min)";
return $"{minutes} minutes";
}
+
+ private static string FormatSql(string? sql)
+ {
+ if (string.IsNullOrWhiteSpace(sql))
+ return "";
+
+ // Format SELECT columns - put each column on its own line
+ var result = FormatSelectColumns(sql);
+
+ // Add line breaks before major clauses
+ result = result
+ .Replace(" FROM ", "\nFROM ")
+ .Replace(" WHERE ", "\nWHERE ")
+ .Replace(" AND ", "\n AND ")
+ .Replace(" OR ", "\n OR ")
+ .Replace(" LEFT ", "\nLEFT ")
+ .Replace(" RIGHT ", "\nRIGHT ")
+ .Replace(" INNER ", "\nINNER ")
+ .Replace(" OUTER ", "\nOUTER ")
+ .Replace(" JOIN ", " JOIN\n ")
+ .Replace(" ORDER BY ", "\nORDER BY ")
+ .Replace(" GROUP BY ", "\nGROUP BY ")
+ .Replace(" HAVING ", "\nHAVING ");
+
+ return result.Trim();
+ }
+
+ private static string FormatSelectColumns(string sql)
+ {
+ var selectIndex = sql.IndexOf("SELECT", StringComparison.OrdinalIgnoreCase);
+ var fromIndex = sql.IndexOf(" FROM ", StringComparison.OrdinalIgnoreCase);
+
+ if (selectIndex < 0 || fromIndex < 0 || fromIndex <= selectIndex)
+ return sql;
+
+ var beforeSelect = sql[..selectIndex];
+ var selectKeyword = sql.Substring(selectIndex, 6);
+ var columnsStart = selectIndex + 6;
+ var columns = sql[columnsStart..fromIndex];
+ var afterColumns = sql[fromIndex..];
+
+ var columnList = columns.Split(',');
+ var formattedColumns = string.Join(",\n ", columnList.Select(c => c.Trim()));
+
+ return $"{beforeSelect}{selectKeyword} {formattedColumns}{afterColumns}";
+ }
}
diff --git a/NEW/src/JdeScoping.Client/Components/Admin/SqlQueryModal.razor b/NEW/src/JdeScoping.Client/Components/Admin/SqlQueryModal.razor
index 9f69f13..c0bbdfc 100644
--- a/NEW/src/JdeScoping.Client/Components/Admin/SqlQueryModal.razor
+++ b/NEW/src/JdeScoping.Client/Components/Admin/SqlQueryModal.razor
@@ -90,9 +90,11 @@
if (string.IsNullOrWhiteSpace(sql))
return "";
- // Basic SQL formatting - add line breaks before major clauses
- return sql
- .Replace(" SELECT ", "\nSELECT ")
+ // Format SELECT columns - put each column on its own line
+ var result = FormatSelectColumns(sql);
+
+ // Add line breaks before major clauses
+ result = result
.Replace(" FROM ", "\nFROM ")
.Replace(" WHERE ", "\nWHERE ")
.Replace(" AND ", "\n AND ")
@@ -104,8 +106,31 @@
.Replace(" JOIN ", " JOIN\n ")
.Replace(" ORDER BY ", "\nORDER BY ")
.Replace(" GROUP BY ", "\nGROUP BY ")
- .Replace(" HAVING ", "\nHAVING ")
- .Trim();
+ .Replace(" HAVING ", "\nHAVING ");
+
+ return result.Trim();
+ }
+
+ private static string FormatSelectColumns(string sql)
+ {
+ // Find SELECT and FROM positions (case-insensitive)
+ var selectIndex = sql.IndexOf("SELECT", StringComparison.OrdinalIgnoreCase);
+ var fromIndex = sql.IndexOf(" FROM ", StringComparison.OrdinalIgnoreCase);
+
+ if (selectIndex < 0 || fromIndex < 0 || fromIndex <= selectIndex)
+ return sql;
+
+ var beforeSelect = sql[..selectIndex];
+ var selectKeyword = sql.Substring(selectIndex, 6); // "SELECT"
+ var columnsStart = selectIndex + 6;
+ var columns = sql[columnsStart..fromIndex];
+ var afterColumns = sql[fromIndex..];
+
+ // Split columns by comma and rejoin with newlines
+ var columnList = columns.Split(',');
+ var formattedColumns = string.Join(",\n ", columnList.Select(c => c.Trim()));
+
+ return $"{beforeSelect}{selectKeyword} {formattedColumns}{afterColumns}";
}
private async Task CopyToClipboard()
diff --git a/NEW/src/JdeScoping.Client/Extensions/ViewModelMappingExtensions.cs b/NEW/src/JdeScoping.Client/Extensions/ViewModelMappingExtensions.cs
index d8c1bd1..f0fa859 100644
--- a/NEW/src/JdeScoping.Client/Extensions/ViewModelMappingExtensions.cs
+++ b/NEW/src/JdeScoping.Client/Extensions/ViewModelMappingExtensions.cs
@@ -1,6 +1,7 @@
using JdeScoping.Client.Models;
using JdeScoping.Core.Models.Enums;
using JdeScoping.Core.Models.Search;
+using JdeScoping.Core.ViewModels;
using CoreSearch = JdeScoping.Core.ViewModels.SearchViewModel;
using CoreItem = JdeScoping.Core.ViewModels.ItemViewModel;
using CoreWorkOrder = JdeScoping.Core.ViewModels.WorkOrderViewModel;
@@ -9,6 +10,7 @@ using CoreWorkCenter = JdeScoping.Core.ViewModels.WorkCenterViewModel;
using CoreLot = JdeScoping.Core.ViewModels.LotViewModel;
using CorePartOp = JdeScoping.Core.ViewModels.PartOperationViewModel;
using CoreJdeUser = JdeScoping.Core.ViewModels.JdeUserViewModel;
+using SearchViewModel = JdeScoping.Client.Models.SearchViewModel;
namespace JdeScoping.Client.Extensions;
diff --git a/NEW/src/JdeScoping.Client/Models/SearchUpdate.cs b/NEW/src/JdeScoping.Client/Models/SearchUpdate.cs
deleted file mode 100644
index 2aab613..0000000
--- a/NEW/src/JdeScoping.Client/Models/SearchUpdate.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-namespace JdeScoping.Client.Models;
-
-///
-/// SignalR message for search status updates.
-///
-public record SearchUpdate
-{
- public int Id { get; init; }
- public string Name { get; init; } = string.Empty;
- public string UserName { get; init; } = string.Empty;
- public string Status { get; init; } = string.Empty;
- public DateTime? SubmitDt { get; init; }
- public DateTime? StartDt { get; init; }
- public DateTime? EndDt { get; init; }
-}
diff --git a/NEW/src/JdeScoping.Client/Models/StatusUpdate.cs b/NEW/src/JdeScoping.Client/Models/StatusUpdate.cs
deleted file mode 100644
index a0fcda6..0000000
--- a/NEW/src/JdeScoping.Client/Models/StatusUpdate.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace JdeScoping.Client.Models;
-
-///
-/// SignalR message for processor status updates.
-///
-public record StatusUpdate
-{
- public string Message { get; init; } = string.Empty;
- public DateTime? Timestamp { get; init; }
-}
diff --git a/NEW/src/JdeScoping.Client/Models/UserInfoViewModel.cs b/NEW/src/JdeScoping.Client/Models/UserInfoViewModel.cs
deleted file mode 100644
index a4d5750..0000000
--- a/NEW/src/JdeScoping.Client/Models/UserInfoViewModel.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-namespace JdeScoping.Client.Models;
-
-///
-/// Client-side view model for authenticated user information.
-/// Mirrors the server-side UserInfo model returned by /api/auth/login and /api/auth/me.
-///
-public class UserInfoViewModel
-{
- ///
- /// User's login username.
- ///
- public string Username { get; set; } = string.Empty;
-
- ///
- /// User's first name.
- ///
- public string FirstName { get; set; } = string.Empty;
-
- ///
- /// User's last name.
- ///
- public string LastName { get; set; } = string.Empty;
-
- ///
- /// User's display name (computed on server, provided here for convenience).
- ///
- public string DisplayName { get; set; } = string.Empty;
-
- ///
- /// User's organization title.
- ///
- public string Title { get; set; } = string.Empty;
-
- ///
- /// User's email address.
- ///
- public string EmailAddress { get; set; } = string.Empty;
-}
diff --git a/NEW/src/JdeScoping.Client/Models/ValidCombination.cs b/NEW/src/JdeScoping.Client/Models/ValidCombination.cs
index 5727cfc..b5bb4b9 100644
--- a/NEW/src/JdeScoping.Client/Models/ValidCombination.cs
+++ b/NEW/src/JdeScoping.Client/Models/ValidCombination.cs
@@ -6,17 +6,17 @@ namespace JdeScoping.Client.Models;
///
public class ValidCombination
{
- public int Id { get; init; }
- public string Name { get; init; } = string.Empty;
- public bool Timespan { get; init; }
- public bool WorkOrder { get; init; }
- public bool ItemNumber { get; init; }
- public bool ProfitCenter { get; init; }
- public bool WorkCenter { get; init; }
- public bool ComponentLot { get; init; }
- public bool Operator { get; init; }
- public bool ItemOperationMis { get; init; }
- public bool ExtractMis { get; init; }
+ public int Id { get; private init; }
+ public string Name { get; private init; } = string.Empty;
+ public bool Timespan { get; private init; }
+ public bool WorkOrder { get; private init; }
+ public bool ItemNumber { get; private init; }
+ public bool ProfitCenter { get; private init; }
+ public bool WorkCenter { get; private init; }
+ public bool ComponentLot { get; private init; }
+ public bool Operator { get; private init; }
+ public bool ItemOperationMis { get; private init; }
+ public bool ExtractMis { get; private init; }
///
/// Checks if the given filter flags match this combination.
@@ -48,7 +48,7 @@ public class ValidCombination
///
public static IReadOnlyList GetAll() =>
[
- new ValidCombination
+ new()
{
Id = 10,
Name = "Work Order",
@@ -62,7 +62,7 @@ public class ValidCombination
ItemOperationMis = false,
ExtractMis = false
},
- new ValidCombination
+ new()
{
Id = 20,
Name = "Component Lot",
@@ -76,7 +76,7 @@ public class ValidCombination
ItemOperationMis = false,
ExtractMis = false
},
- new ValidCombination
+ new()
{
Id = 30,
Name = "Time Span + Profit Center",
@@ -90,7 +90,7 @@ public class ValidCombination
ItemOperationMis = false,
ExtractMis = false
},
- new ValidCombination
+ new()
{
Id = 40,
Name = "Time Span + Work Center",
@@ -104,7 +104,7 @@ public class ValidCombination
ItemOperationMis = false,
ExtractMis = false
},
- new ValidCombination
+ new()
{
Id = 50,
Name = "Time Span + Operator",
@@ -118,7 +118,7 @@ public class ValidCombination
ItemOperationMis = false,
ExtractMis = false
},
- new ValidCombination
+ new()
{
Id = 60,
Name = "Time Span + Profit Center + Item Number",
@@ -132,7 +132,7 @@ public class ValidCombination
ItemOperationMis = false,
ExtractMis = false
},
- new ValidCombination
+ new()
{
Id = 70,
Name = "Time Span + Profit Center + Item/Operation/MIS",
@@ -146,7 +146,7 @@ public class ValidCombination
ItemOperationMis = true,
ExtractMis = false
},
- new ValidCombination
+ new()
{
Id = 80,
Name = "Time Span + Profit Center + Work Order + Item/Operation/MIS",
@@ -160,7 +160,7 @@ public class ValidCombination
ItemOperationMis = true,
ExtractMis = false
},
- new ValidCombination
+ new()
{
Id = 90,
Name = "Time Span + Profit Center + Extract MIS",
@@ -174,7 +174,7 @@ public class ValidCombination
ItemOperationMis = false,
ExtractMis = true
},
- new ValidCombination
+ new()
{
Id = 100,
Name = "Time Span + Work Center + Item Number",
@@ -188,7 +188,7 @@ public class ValidCombination
ItemOperationMis = false,
ExtractMis = false
},
- new ValidCombination
+ new()
{
Id = 110,
Name = "Time Span + Work Center + Extract MIS",
@@ -202,7 +202,7 @@ public class ValidCombination
ItemOperationMis = false,
ExtractMis = true
},
- new ValidCombination
+ new()
{
Id = 120,
Name = "Time Span + Work Center + Item/Operation/MIS",
@@ -216,7 +216,7 @@ public class ValidCombination
ItemOperationMis = true,
ExtractMis = false
},
- new ValidCombination
+ new()
{
Id = 130,
Name = "Time Span + Work Center + Work Order + Item/Operation/MIS",
@@ -230,7 +230,7 @@ public class ValidCombination
ItemOperationMis = true,
ExtractMis = false
},
- new ValidCombination
+ new()
{
Id = 140,
Name = "Time Span + Item Number",
@@ -244,7 +244,7 @@ public class ValidCombination
ItemOperationMis = false,
ExtractMis = false
},
- new ValidCombination
+ new()
{
Id = 150,
Name = "Time Span + Work Center + Operator",
@@ -258,7 +258,7 @@ public class ValidCombination
ItemOperationMis = false,
ExtractMis = false
},
- new ValidCombination
+ new()
{
Id = 160,
Name = "Time Span + Profit Center + Operator",
diff --git a/NEW/src/JdeScoping.Client/Pages/Admin/PipelineViewer.razor b/NEW/src/JdeScoping.Client/Pages/Admin/PipelineViewer.razor
index a72a552..7549519 100644
--- a/NEW/src/JdeScoping.Client/Pages/Admin/PipelineViewer.razor
+++ b/NEW/src/JdeScoping.Client/Pages/Admin/PipelineViewer.razor
@@ -107,7 +107,7 @@ else if (_config is not null)
-
+
Source
Connection:
@@ -127,21 +127,11 @@ else if (_config is not null)
break;
}
- @if (_config.Source.Parameters.Count > 0)
- {
- Parameters:
-
- @foreach (var param in _config.Source.Parameters)
- {
- - @param.Name (@(param.Format ?? "default"))
- }
-
- }
-
+
Destination
Table: @_config.Destination.Table
@@ -151,67 +141,36 @@ else if (_config is not null)
@if (_config.Destination.MatchColumns?.Count > 0)
{
- Match Columns: @string.Join(", ", _config.Destination.MatchColumns)
+ Match Columns:
+
+ @foreach (var col in _config.Destination.MatchColumns)
+ {
+ - @col
+ }
+
}
@if (_config.Destination.ExcludeFromUpdate?.Count > 0)
{
- Exclude: @string.Join(", ", _config.Destination.ExcludeFromUpdate)
- }
-
-
-
-
-
-
- Scripts
- Pre-Scripts: @_config.PreScriptCount
- Post-Scripts: @_config.PostScriptCount
- @if (_config.PreScripts?.Count > 0)
- {
- Pre-Scripts:
- @for (int i = 0; i < _config.PreScripts.Count; i++)
- {
- var script = _config.PreScripts[i];
- var index = i + 1;
-
-
-
- }
- }
- @if (_config.PostScripts?.Count > 0)
- {
- Post-Scripts:
- @for (int i = 0; i < _config.PostScripts.Count; i++)
- {
- var script = _config.PostScripts[i];
- var index = i + 1;
-
-
-
- }
+ Exclude:
+
+ @foreach (var col in _config.Destination.ExcludeFromUpdate)
+ {
+ - @col
+ }
+
}
-
+
-
+
-
+
}
-
-
@code {
private List _pipelineNames = [];
private string? _selectedPipeline;
@@ -220,10 +179,6 @@ else if (_config is not null)
private List _statuses = [];
private List _executions = [];
- private bool _showSqlModal;
- private string? _sqlModalTitle;
- private string? _sqlModalContent;
-
protected override async Task OnInitializedAsync()
{
var result = await PipelineApi.GetPipelineNamesAsync();
@@ -271,13 +226,6 @@ else if (_config is not null)
}
}
- private void ShowSqlModal(string title, string sql)
- {
- _sqlModalTitle = $"{title} - {_selectedPipeline}";
- _sqlModalContent = sql;
- _showSqlModal = true;
- }
-
private static string FormatDuration(TimeSpan? duration)
{
if (!duration.HasValue) return "-";
diff --git a/NEW/src/JdeScoping.Client/Pages/Login.razor b/NEW/src/JdeScoping.Client/Pages/Login.razor
index 60821da..c798b79 100644
--- a/NEW/src/JdeScoping.Client/Pages/Login.razor
+++ b/NEW/src/JdeScoping.Client/Pages/Login.razor
@@ -74,16 +74,7 @@
if (loginResult.Success && loginResult.User is not null)
{
// Notify auth state provider of successful login
- var userViewModel = new JdeScoping.Client.Models.UserInfoViewModel
- {
- Username = loginResult.User.Username,
- FirstName = loginResult.User.FirstName,
- LastName = loginResult.User.LastName,
- DisplayName = loginResult.User.DisplayName,
- EmailAddress = loginResult.User.EmailAddress,
- Title = loginResult.User.Title
- };
- _ = AuthStateProvider.MarkUserAsAuthenticated(userViewModel);
+ _ = AuthStateProvider.MarkUserAsAuthenticated(loginResult.User);
var returnUrl = string.IsNullOrEmpty(ReturnUrl) ? "/" : ReturnUrl;
NavigationManager.NavigateTo(returnUrl);
diff --git a/NEW/src/JdeScoping.Client/Pages/SearchEdit.razor b/NEW/src/JdeScoping.Client/Pages/SearchEdit.razor
index 7c37af9..ca7d087 100644
--- a/NEW/src/JdeScoping.Client/Pages/SearchEdit.razor
+++ b/NEW/src/JdeScoping.Client/Pages/SearchEdit.razor
@@ -315,7 +315,7 @@ else
await HubConnection.StartAsync();
}
- private void HandleSearchUpdate(SearchUpdate update)
+ private void HandleSearchUpdate(SearchUpdateDto update)
{
if (update.Id == _search.Id)
{
diff --git a/NEW/src/JdeScoping.Client/Pages/SearchQueue.razor b/NEW/src/JdeScoping.Client/Pages/SearchQueue.razor
index 28099c4..58a7ebc 100644
--- a/NEW/src/JdeScoping.Client/Pages/SearchQueue.razor
+++ b/NEW/src/JdeScoping.Client/Pages/SearchQueue.razor
@@ -120,7 +120,7 @@ else
}
}
- private void HandleSearchUpdate(SearchUpdate update)
+ private void HandleSearchUpdate(SearchUpdateDto update)
{
InvokeAsync(() =>
{
@@ -162,7 +162,7 @@ else
});
}
- private void HandleStatusUpdate(StatusUpdate update)
+ private void HandleStatusUpdate(StatusUpdateDto update)
{
InvokeAsync(() =>
{
diff --git a/NEW/src/JdeScoping.Client/Pages/Searches.razor b/NEW/src/JdeScoping.Client/Pages/Searches.razor
index fd352a6..af8119e 100644
--- a/NEW/src/JdeScoping.Client/Pages/Searches.razor
+++ b/NEW/src/JdeScoping.Client/Pages/Searches.razor
@@ -93,7 +93,7 @@ else
await HubConnection.StartAsync();
}
- private void HandleSearchUpdate(SearchUpdate update)
+ private void HandleSearchUpdate(SearchUpdateDto update)
{
InvokeAsync(() =>
{
diff --git a/NEW/src/JdeScoping.Client/Services/AuthApiClient.cs b/NEW/src/JdeScoping.Client/Services/AuthApiClient.cs
index 03407e3..1350f13 100644
--- a/NEW/src/JdeScoping.Client/Services/AuthApiClient.cs
+++ b/NEW/src/JdeScoping.Client/Services/AuthApiClient.cs
@@ -1,6 +1,6 @@
using JdeScoping.Core.ApiContracts;
+using JdeScoping.Core.ApiContracts.Auth;
using JdeScoping.Core.ApiContracts.Results;
-using JdeScoping.Core.Models;
using JdeScoping.Core.Models.Auth;
namespace JdeScoping.Client.Services;
@@ -21,6 +21,6 @@ public class AuthApiClient : ApiClientBase, IAuthApiClient
public Task> LogoutAsync(CancellationToken ct = default)
=> PostAsync(ApiRoutes.Auth.Logout, ct);
- public Task> GetCurrentUserAsync(CancellationToken ct = default)
- => GetAsync(ApiRoutes.Auth.Me, ct);
+ public Task> GetCurrentUserAsync(CancellationToken ct = default)
+ => GetAsync(ApiRoutes.Auth.Me, ct);
}
diff --git a/NEW/src/JdeScoping.Client/Services/AuthService.cs b/NEW/src/JdeScoping.Client/Services/AuthService.cs
index f589f4d..3642fdf 100644
--- a/NEW/src/JdeScoping.Client/Services/AuthService.cs
+++ b/NEW/src/JdeScoping.Client/Services/AuthService.cs
@@ -1,6 +1,5 @@
using System.Net.Http.Json;
using JdeScoping.Client.Auth;
-using JdeScoping.Client.Models;
using JdeScoping.Core.Models.Auth;
namespace JdeScoping.Client.Services;
@@ -43,17 +42,8 @@ public class AuthService : IAuthService
if (result.Success && result.User is not null)
{
- // Notify auth state provider of the login
- var userViewModel = new UserInfoViewModel
- {
- Username = result.User.Username,
- FirstName = result.User.FirstName,
- LastName = result.User.LastName,
- DisplayName = result.User.DisplayName,
- EmailAddress = result.User.EmailAddress,
- Title = result.User.Title
- };
- await _authStateProvider.MarkUserAsAuthenticated(userViewModel);
+ // LoginResultModel.User is already UserInfoDto - pass directly
+ await _authStateProvider.MarkUserAsAuthenticated(result.User);
}
return result;
diff --git a/NEW/src/JdeScoping.Client/Services/HubConnectionService.cs b/NEW/src/JdeScoping.Client/Services/HubConnectionService.cs
index 61b5ba3..1e48266 100644
--- a/NEW/src/JdeScoping.Client/Services/HubConnectionService.cs
+++ b/NEW/src/JdeScoping.Client/Services/HubConnectionService.cs
@@ -1,4 +1,4 @@
-using JdeScoping.Client.Models;
+using JdeScoping.Core.ApiContracts.SignalR;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.SignalR.Client;
@@ -13,8 +13,8 @@ public class HubConnectionService : IHubConnectionService, IAsyncDisposable
private readonly NavigationManager _navigationManager;
private HubConnection? _hubConnection;
- public event Action? OnSearchUpdate;
- public event Action? OnStatusUpdate;
+ public event Action? OnSearchUpdate;
+ public event Action? OnStatusUpdate;
public bool IsConnected => _hubConnection?.State == HubConnectionState.Connected;
@@ -43,12 +43,12 @@ public class HubConnectionService : IHubConnectionService, IAsyncDisposable
])
.Build();
- _hubConnection.On("searchUpdate", update =>
+ _hubConnection.On("searchUpdate", update =>
{
OnSearchUpdate?.Invoke(update);
});
- _hubConnection.On("statusUpdate", update =>
+ _hubConnection.On("statusUpdate", update =>
{
OnStatusUpdate?.Invoke(update);
});
@@ -92,7 +92,7 @@ public class HubConnectionService : IHubConnectionService, IAsyncDisposable
}
}
- public async Task GetCachedStatusAsync()
+ public async Task GetCachedStatusAsync()
{
if (_hubConnection == null || _hubConnection.State != HubConnectionState.Connected)
{
@@ -101,7 +101,7 @@ public class HubConnectionService : IHubConnectionService, IAsyncDisposable
try
{
- return await _hubConnection.InvokeAsync("GetCachedStatus");
+ return await _hubConnection.InvokeAsync("GetCachedStatus");
}
catch (Exception ex)
{
diff --git a/NEW/src/JdeScoping.Client/Services/IHubConnectionService.cs b/NEW/src/JdeScoping.Client/Services/IHubConnectionService.cs
index 44bcbe0..7e35f5b 100644
--- a/NEW/src/JdeScoping.Client/Services/IHubConnectionService.cs
+++ b/NEW/src/JdeScoping.Client/Services/IHubConnectionService.cs
@@ -1,4 +1,4 @@
-using JdeScoping.Client.Models;
+using JdeScoping.Core.ApiContracts.SignalR;
namespace JdeScoping.Client.Services;
@@ -10,12 +10,12 @@ public interface IHubConnectionService
///
/// Event fired when a search update is received.
///
- event Action? OnSearchUpdate;
+ event Action? OnSearchUpdate;
///
/// Event fired when a processor status update is received.
///
- event Action? OnStatusUpdate;
+ event Action? OnStatusUpdate;
///
/// Starts the SignalR connection.
@@ -30,7 +30,7 @@ public interface IHubConnectionService
///
/// Gets the cached processor status from the server.
///
- Task GetCachedStatusAsync();
+ Task GetCachedStatusAsync();
///
/// Gets the current connection state.
diff --git a/NEW/src/JdeScoping.Client/_Imports.razor b/NEW/src/JdeScoping.Client/_Imports.razor
index d336ab8..a99a000 100644
--- a/NEW/src/JdeScoping.Client/_Imports.razor
+++ b/NEW/src/JdeScoping.Client/_Imports.razor
@@ -21,5 +21,7 @@
@using JdeScoping.Client.Models
@using JdeScoping.Client.Pages
@using JdeScoping.Client.Services
+@using JdeScoping.Core.ApiContracts.Auth
+@using JdeScoping.Core.ApiContracts.SignalR
@using JdeScoping.Core.ViewModels
@using ClientSearchViewModel = JdeScoping.Client.Models.SearchViewModel
diff --git a/NEW/src/JdeScoping.Core/ApiContracts/Auth/UserInfoDto.cs b/NEW/src/JdeScoping.Core/ApiContracts/Auth/UserInfoDto.cs
new file mode 100644
index 0000000..16b9b19
--- /dev/null
+++ b/NEW/src/JdeScoping.Core/ApiContracts/Auth/UserInfoDto.cs
@@ -0,0 +1,54 @@
+using JdeScoping.Core.Models;
+
+namespace JdeScoping.Core.ApiContracts.Auth;
+
+///
+/// API response DTO for authenticated user information.
+///
+public record UserInfoDto
+{
+ ///
+ /// User's login username.
+ ///
+ public string Username { get; init; } = string.Empty;
+
+ ///
+ /// User's first name.
+ ///
+ public string FirstName { get; init; } = string.Empty;
+
+ ///
+ /// User's last name.
+ ///
+ public string LastName { get; init; } = string.Empty;
+
+ ///
+ /// User's display name (computed on server).
+ ///
+ public string DisplayName { get; init; } = string.Empty;
+
+ ///
+ /// User's organization title.
+ ///
+ public string Title { get; init; } = string.Empty;
+
+ ///
+ /// User's email address.
+ ///
+ public string EmailAddress { get; init; } = string.Empty;
+
+ ///
+ /// Creates a UserInfoDto from a UserInfo entity.
+ ///
+ /// The UserInfo entity to convert.
+ /// A new UserInfoDto instance.
+ public static UserInfoDto FromUserInfo(UserInfo userInfo) => new()
+ {
+ Username = userInfo.Username,
+ FirstName = userInfo.FirstName,
+ LastName = userInfo.LastName,
+ DisplayName = userInfo.DisplayName,
+ Title = userInfo.Title,
+ EmailAddress = userInfo.EmailAddress
+ };
+}
diff --git a/NEW/src/JdeScoping.Core/ApiContracts/IAuthApiClient.cs b/NEW/src/JdeScoping.Core/ApiContracts/IAuthApiClient.cs
index 1e5221f..7570a1d 100644
--- a/NEW/src/JdeScoping.Core/ApiContracts/IAuthApiClient.cs
+++ b/NEW/src/JdeScoping.Core/ApiContracts/IAuthApiClient.cs
@@ -1,5 +1,5 @@
+using JdeScoping.Core.ApiContracts.Auth;
using JdeScoping.Core.ApiContracts.Results;
-using JdeScoping.Core.Models;
using JdeScoping.Core.Models.Auth;
namespace JdeScoping.Core.ApiContracts;
@@ -19,5 +19,5 @@ public interface IAuthApiClient
Task> LogoutAsync(CancellationToken ct = default);
/// Gets the current authenticated user's information.
- Task> GetCurrentUserAsync(CancellationToken ct = default);
+ Task> GetCurrentUserAsync(CancellationToken ct = default);
}
diff --git a/NEW/src/JdeScoping.Core/ApiContracts/Pipelines/PipelineConfigDto.cs b/NEW/src/JdeScoping.Core/ApiContracts/Pipelines/PipelineConfigDto.cs
index 8fa41b4..00c72bf 100644
--- a/NEW/src/JdeScoping.Core/ApiContracts/Pipelines/PipelineConfigDto.cs
+++ b/NEW/src/JdeScoping.Core/ApiContracts/Pipelines/PipelineConfigDto.cs
@@ -44,4 +44,8 @@ public record PipelineScheduleDto(
bool ReIndex,
bool IntervalIsOverride,
bool PrePurgeIsOverride,
- bool ReIndexIsOverride);
+ bool ReIndexIsOverride,
+ string? Query,
+ List Parameters,
+ List? PreScripts,
+ List? PostScripts);
diff --git a/NEW/src/JdeScoping.Core/ApiContracts/Pipelines/PipelineExecutionDto.cs b/NEW/src/JdeScoping.Core/ApiContracts/Pipelines/PipelineExecutionDto.cs
index 02fdd95..b0edc98 100644
--- a/NEW/src/JdeScoping.Core/ApiContracts/Pipelines/PipelineExecutionDto.cs
+++ b/NEW/src/JdeScoping.Core/ApiContracts/Pipelines/PipelineExecutionDto.cs
@@ -1,6 +1,6 @@
namespace JdeScoping.Core.ApiContracts.Pipelines;
-using JdeScoping.Core.Models.Enums;
+using Models.Enums;
///
/// Pipeline execution history.
diff --git a/NEW/src/JdeScoping.Core/ApiContracts/Pipelines/PipelineStatusDto.cs b/NEW/src/JdeScoping.Core/ApiContracts/Pipelines/PipelineStatusDto.cs
index 3586b27..8488db5 100644
--- a/NEW/src/JdeScoping.Core/ApiContracts/Pipelines/PipelineStatusDto.cs
+++ b/NEW/src/JdeScoping.Core/ApiContracts/Pipelines/PipelineStatusDto.cs
@@ -1,6 +1,6 @@
namespace JdeScoping.Core.ApiContracts.Pipelines;
-using JdeScoping.Core.Models.Enums;
+using Models.Enums;
///
/// Pipeline schedule status for each update type.
diff --git a/NEW/src/JdeScoping.Core/ApiContracts/SignalR/SearchUpdateDto.cs b/NEW/src/JdeScoping.Core/ApiContracts/SignalR/SearchUpdateDto.cs
new file mode 100644
index 0000000..bfa3de4
--- /dev/null
+++ b/NEW/src/JdeScoping.Core/ApiContracts/SignalR/SearchUpdateDto.cs
@@ -0,0 +1,60 @@
+using JdeScoping.Core.Models.Search;
+
+namespace JdeScoping.Core.ApiContracts.SignalR;
+
+///
+/// SignalR message DTO for search status updates.
+///
+public record SearchUpdateDto
+{
+ ///
+ /// Search PK ID.
+ ///
+ public int Id { get; init; }
+
+ ///
+ /// User-friendly name for the search.
+ ///
+ public string Name { get; init; } = string.Empty;
+
+ ///
+ /// User name of user that submitted the search.
+ ///
+ public string UserName { get; init; } = string.Empty;
+
+ ///
+ /// Current search status (serialized as string).
+ ///
+ public string Status { get; init; } = string.Empty;
+
+ ///
+ /// Timestamp search was submitted.
+ ///
+ public DateTime? SubmitDt { get; init; }
+
+ ///
+ /// Timestamp search was started.
+ ///
+ public DateTime? StartDt { get; init; }
+
+ ///
+ /// Timestamp search was completed.
+ ///
+ public DateTime? EndDt { get; init; }
+
+ ///
+ /// Creates a SearchUpdateDto from a Search entity.
+ ///
+ /// The Search entity to convert.
+ /// A new SearchUpdateDto instance.
+ public static SearchUpdateDto FromSearch(Search search) => new()
+ {
+ Id = search.Id,
+ Name = search.Name,
+ UserName = search.UserName,
+ Status = search.Status.ToString(),
+ SubmitDt = search.SubmitDt,
+ StartDt = search.StartDt,
+ EndDt = search.EndDt
+ };
+}
diff --git a/NEW/src/JdeScoping.Core/ApiContracts/SignalR/StatusUpdateDto.cs b/NEW/src/JdeScoping.Core/ApiContracts/SignalR/StatusUpdateDto.cs
new file mode 100644
index 0000000..85fdf1f
--- /dev/null
+++ b/NEW/src/JdeScoping.Core/ApiContracts/SignalR/StatusUpdateDto.cs
@@ -0,0 +1,28 @@
+namespace JdeScoping.Core.ApiContracts.SignalR;
+
+///
+/// SignalR message DTO for processor status updates.
+///
+public record StatusUpdateDto
+{
+ ///
+ /// Status message to display.
+ ///
+ public string Message { get; init; } = string.Empty;
+
+ ///
+ /// Timestamp when message was generated.
+ ///
+ public DateTime? Timestamp { get; init; }
+
+ ///
+ /// Creates a StatusUpdateDto with the current timestamp.
+ ///
+ /// Status message.
+ /// A new StatusUpdateDto instance.
+ public static StatusUpdateDto Create(string message) => new()
+ {
+ Message = message,
+ Timestamp = DateTime.UtcNow
+ };
+}
diff --git a/NEW/src/JdeScoping.Core/Extensions/ItemExtensions.cs b/NEW/src/JdeScoping.Core/Extensions/ItemExtensions.cs
deleted file mode 100644
index dab18cd..0000000
--- a/NEW/src/JdeScoping.Core/Extensions/ItemExtensions.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using JdeScoping.Core.Models;
-using JdeScoping.Core.Models.Inventory;
-using JdeScoping.Core.ViewModels;
-
-namespace JdeScoping.Core.Extensions;
-
-///
-/// Extension methods for Item entity
-///
-public static class ItemExtensions
-{
- ///
- /// Converts an Item entity to its ViewModel projection
- ///
- /// Item entity to convert
- /// ItemViewModel projection
- public static ItemViewModel ToViewModel(this Item item)
- {
- return new ItemViewModel
- {
- ItemNumber = item.ItemNumber,
- Description = item.Description
- };
- }
-
- ///
- /// Converts a collection of Item entities to ViewModels
- ///
- /// Collection of Item entities
- /// Collection of ItemViewModel projections
- public static IEnumerable ToViewModels(this IEnumerable- items)
- {
- return items.Select(i => i.ToViewModel());
- }
-}
diff --git a/NEW/src/JdeScoping.Core/Extensions/JdeUserExtensions.cs b/NEW/src/JdeScoping.Core/Extensions/JdeUserExtensions.cs
deleted file mode 100644
index d22ed51..0000000
--- a/NEW/src/JdeScoping.Core/Extensions/JdeUserExtensions.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using JdeScoping.Core.Models;
-using JdeScoping.Core.Models.Organization;
-using JdeScoping.Core.ViewModels;
-
-namespace JdeScoping.Core.Extensions;
-
-///
-/// Extension methods for JdeUser entity
-///
-public static class JdeUserExtensions
-{
- ///
- /// Converts a JdeUser entity to its ViewModel projection
- ///
- /// JdeUser entity to convert
- /// JdeUserViewModel projection
- public static JdeUserViewModel ToViewModel(this JdeUser jdeUser)
- {
- return new JdeUserViewModel
- {
- AddressNumber = jdeUser.AddressNumber,
- UserId = jdeUser.UserId,
- FullName = jdeUser.FullName
- };
- }
-
- ///
- /// Converts a collection of JdeUser entities to ViewModels
- ///
- /// Collection of JdeUser entities
- /// Collection of JdeUserViewModel projections
- public static IEnumerable ToViewModels(this IEnumerable jdeUsers)
- {
- return jdeUsers.Select(u => u.ToViewModel());
- }
-}
diff --git a/NEW/src/JdeScoping.Core/Extensions/LotExtensions.cs b/NEW/src/JdeScoping.Core/Extensions/LotExtensions.cs
deleted file mode 100644
index 6f76c20..0000000
--- a/NEW/src/JdeScoping.Core/Extensions/LotExtensions.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using JdeScoping.Core.Models;
-using JdeScoping.Core.Models.Inventory;
-using JdeScoping.Core.ViewModels;
-
-namespace JdeScoping.Core.Extensions;
-
-///
-/// Extension methods for Lot entity
-///
-public static class LotExtensions
-{
- ///
- /// Converts a Lot entity to its ViewModel projection
- ///
- /// Lot entity to convert
- /// LotViewModel projection
- public static LotViewModel ToViewModel(this Lot lot)
- {
- return new LotViewModel
- {
- LotNumber = lot.LotNumber,
- ItemNumber = lot.ItemNumber
- };
- }
-
- ///
- /// Converts a collection of Lot entities to ViewModels
- ///
- /// Collection of Lot entities
- /// Collection of LotViewModel projections
- public static IEnumerable ToViewModels(this IEnumerable lots)
- {
- return lots.Select(l => l.ToViewModel());
- }
-}
diff --git a/NEW/src/JdeScoping.Core/Extensions/ProfitCenterExtensions.cs b/NEW/src/JdeScoping.Core/Extensions/ProfitCenterExtensions.cs
deleted file mode 100644
index 23dd3d2..0000000
--- a/NEW/src/JdeScoping.Core/Extensions/ProfitCenterExtensions.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using JdeScoping.Core.Models;
-using JdeScoping.Core.Models.Organization;
-using JdeScoping.Core.ViewModels;
-
-namespace JdeScoping.Core.Extensions;
-
-///
-/// Extension methods for ProfitCenter entity
-///
-public static class ProfitCenterExtensions
-{
- ///
- /// Converts a ProfitCenter entity to its ViewModel projection
- ///
- /// ProfitCenter entity to convert
- /// ProfitCenterViewModel projection
- public static ProfitCenterViewModel ToViewModel(this ProfitCenter profitCenter)
- {
- return new ProfitCenterViewModel
- {
- Code = profitCenter.Code,
- Description = profitCenter.Description
- };
- }
-
- ///
- /// Converts a collection of ProfitCenter entities to ViewModels
- ///
- /// Collection of ProfitCenter entities
- /// Collection of ProfitCenterViewModel projections
- public static IEnumerable ToViewModels(this IEnumerable profitCenters)
- {
- return profitCenters.Select(pc => pc.ToViewModel());
- }
-}
diff --git a/NEW/src/JdeScoping.Core/Extensions/WorkCenterExtensions.cs b/NEW/src/JdeScoping.Core/Extensions/WorkCenterExtensions.cs
deleted file mode 100644
index ca057fc..0000000
--- a/NEW/src/JdeScoping.Core/Extensions/WorkCenterExtensions.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using JdeScoping.Core.Models;
-using JdeScoping.Core.Models.Organization;
-using JdeScoping.Core.ViewModels;
-
-namespace JdeScoping.Core.Extensions;
-
-///
-/// Extension methods for WorkCenter entity
-///
-public static class WorkCenterExtensions
-{
- ///
- /// Converts a WorkCenter entity to its ViewModel projection
- ///
- /// WorkCenter entity to convert
- /// WorkCenterViewModel projection
- public static WorkCenterViewModel ToViewModel(this WorkCenter workCenter)
- {
- return new WorkCenterViewModel
- {
- Code = workCenter.Code,
- Description = workCenter.Description
- };
- }
-
- ///
- /// Converts a collection of WorkCenter entities to ViewModels
- ///
- /// Collection of WorkCenter entities
- /// Collection of WorkCenterViewModel projections
- public static IEnumerable ToViewModels(this IEnumerable workCenters)
- {
- return workCenters.Select(wc => wc.ToViewModel());
- }
-}
diff --git a/NEW/src/JdeScoping.Core/Extensions/WorkOrderExtensions.cs b/NEW/src/JdeScoping.Core/Extensions/WorkOrderExtensions.cs
deleted file mode 100644
index 3aa3bf9..0000000
--- a/NEW/src/JdeScoping.Core/Extensions/WorkOrderExtensions.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using JdeScoping.Core.Models;
-using JdeScoping.Core.Models.WorkOrders;
-using JdeScoping.Core.ViewModels;
-
-namespace JdeScoping.Core.Extensions;
-
-///
-/// Extension methods for WorkOrder entity
-///
-public static class WorkOrderExtensions
-{
- ///
- /// Converts a WorkOrder entity to its ViewModel projection
- ///
- /// WorkOrder entity to convert
- /// WorkOrderViewModel projection
- public static WorkOrderViewModel ToViewModel(this WorkOrder workOrder)
- {
- return new WorkOrderViewModel
- {
- WorkOrderNumber = workOrder.WorkOrderNumber,
- ItemNumber = workOrder.ItemNumber
- };
- }
-
- ///
- /// Converts a collection of WorkOrder entities to ViewModels
- ///
- /// Collection of WorkOrder entities
- /// Collection of WorkOrderViewModel projections
- public static IEnumerable ToViewModels(this IEnumerable workOrders)
- {
- return workOrders.Select(wo => wo.ToViewModel());
- }
-}
diff --git a/NEW/src/JdeScoping.Core/Models/Auth/LoginResultModel.cs b/NEW/src/JdeScoping.Core/Models/Auth/LoginResultModel.cs
index 4b73ceb..5f054f9 100644
--- a/NEW/src/JdeScoping.Core/Models/Auth/LoginResultModel.cs
+++ b/NEW/src/JdeScoping.Core/Models/Auth/LoginResultModel.cs
@@ -1,4 +1,4 @@
-using JdeScoping.Core.Models;
+using JdeScoping.Core.ApiContracts.Auth;
namespace JdeScoping.Core.Models.Auth;
@@ -11,4 +11,4 @@ namespace JdeScoping.Core.Models.Auth;
public record LoginResultModel(
bool Success,
string? ErrorMessage,
- UserInfo? User);
+ UserInfoDto? User);
diff --git a/NEW/src/JdeScoping.Core/Models/Infrastructure/ColumnSpec.cs b/NEW/src/JdeScoping.Core/Models/Infrastructure/ColumnSpec.cs
deleted file mode 100644
index d0d3b1c..0000000
--- a/NEW/src/JdeScoping.Core/Models/Infrastructure/ColumnSpec.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-namespace JdeScoping.Core.Models.Infrastructure;
-
-///
-/// Database column specification
-///
-public class ColumnSpec
-{
- ///
- /// Column name
- ///
- public string Name { get; set; } = string.Empty;
-
- ///
- /// Column definition (SQL type and constraints)
- ///
- public string Definition { get; set; } = string.Empty;
-
- ///
- /// Default constructor
- ///
- public ColumnSpec()
- {
- }
-
- ///
- /// Constructor with name and definition
- ///
- /// Column name
- /// Column definition
- public ColumnSpec(string name, string definition)
- {
- Name = name;
- Definition = definition;
- }
-}
diff --git a/NEW/src/JdeScoping.Core/Models/Infrastructure/TableSpec.cs b/NEW/src/JdeScoping.Core/Models/Infrastructure/TableSpec.cs
deleted file mode 100644
index dde045f..0000000
--- a/NEW/src/JdeScoping.Core/Models/Infrastructure/TableSpec.cs
+++ /dev/null
@@ -1,83 +0,0 @@
-namespace JdeScoping.Core.Models.Infrastructure;
-
-///
-/// Database table specification for dynamic SQL generation
-///
-public class TableSpec
-{
- ///
- /// Table name
- ///
- public string Name { get; set; } = string.Empty;
-
- ///
- /// Temporary table name (computed as # + Name)
- ///
- public string TempTableName => $"#{Name}";
-
- ///
- /// Table columns
- ///
- public List Columns { get; set; } = [];
-
- ///
- /// Table columns that form the primary key
- ///
- public List PrimaryKey { get; set; } = [];
-
- ///
- /// Default constructor
- ///
- public TableSpec()
- {
- }
-
- ///
- /// Constructor with table name
- ///
- /// Table name
- public TableSpec(string name)
- {
- Name = name;
- }
-
- ///
- /// Generates SQL for creating an index on the primary key (stub)
- ///
- /// SQL CREATE INDEX statement
- public string GenerateIndex()
- {
- // Stub implementation - to be expanded based on spec
- return string.Empty;
- }
-
- ///
- /// Generates SQL for dropping the table (stub)
- ///
- /// SQL DROP TABLE statement
- public string GenerateDrop()
- {
- // Stub implementation - to be expanded based on spec
- return string.Empty;
- }
-
- ///
- /// Generates SQL for creating the table (stub)
- ///
- /// SQL CREATE TABLE statement
- public string GenerateCreate()
- {
- // Stub implementation - to be expanded based on spec
- return string.Empty;
- }
-
- ///
- /// Gets a column specification by name (stub)
- ///
- /// Name of column to find
- /// ColumnSpec or null if not found
- public ColumnSpec? GetColumn(string columnName)
- {
- return Columns.Find(c => c.Name == columnName);
- }
-}
diff --git a/NEW/src/JdeScoping.Core/Models/Inventory/LotLocation.cs b/NEW/src/JdeScoping.Core/Models/Inventory/LotLocation.cs
deleted file mode 100644
index 8ae2d3c..0000000
--- a/NEW/src/JdeScoping.Core/Models/Inventory/LotLocation.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-namespace JdeScoping.Core.Models.Inventory;
-
-///
-/// JDE lot location entity
-///
-public class LotLocation
-{
- ///
- /// Lot unique number
- ///
- public string LotNumber { get; set; } = string.Empty;
-
- ///
- /// Short item number
- ///
- public long ShortItemNumber { get; set; }
-
- ///
- /// Business unit unique code
- ///
- public string BranchCode { get; set; } = string.Empty;
-
- ///
- /// Location code where lot is located
- ///
- public string Location { get; set; } = string.Empty;
-
- ///
- /// Timestamp of last update to record
- ///
- public DateTime? LastUpdateDt { get; set; }
-}
diff --git a/NEW/src/JdeScoping.Client/Models/ComponentLotViewModel.cs b/NEW/src/JdeScoping.Core/ViewModels/ComponentLotViewModel.cs
similarity index 90%
rename from NEW/src/JdeScoping.Client/Models/ComponentLotViewModel.cs
rename to NEW/src/JdeScoping.Core/ViewModels/ComponentLotViewModel.cs
index 44bc803..27fde67 100644
--- a/NEW/src/JdeScoping.Client/Models/ComponentLotViewModel.cs
+++ b/NEW/src/JdeScoping.Core/ViewModels/ComponentLotViewModel.cs
@@ -1,4 +1,4 @@
-namespace JdeScoping.Client.Models;
+namespace JdeScoping.Core.ViewModels;
///
/// View model for component lot filter.
diff --git a/NEW/src/JdeScoping.Client/Models/OperatorViewModel.cs b/NEW/src/JdeScoping.Core/ViewModels/OperatorViewModel.cs
similarity index 89%
rename from NEW/src/JdeScoping.Client/Models/OperatorViewModel.cs
rename to NEW/src/JdeScoping.Core/ViewModels/OperatorViewModel.cs
index f2c632f..f5b43bd 100644
--- a/NEW/src/JdeScoping.Client/Models/OperatorViewModel.cs
+++ b/NEW/src/JdeScoping.Core/ViewModels/OperatorViewModel.cs
@@ -1,4 +1,4 @@
-namespace JdeScoping.Client.Models;
+namespace JdeScoping.Core.ViewModels;
///
/// View model for operator filter.
diff --git a/NEW/src/JdeScoping.DataSync.Dev/BranchDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/BranchDevEtl.cs
deleted file mode 100644
index ebd3803..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/BranchDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the Branch table.
-///
-public static class BranchDevEtl
-{
- public static readonly string TableName = "Branch";
- public static readonly string CacheFileName = "branch.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/Configuration/DevPipelinesRoot.cs b/NEW/src/JdeScoping.DataSync.Dev/Configuration/DevPipelinesRoot.cs
new file mode 100644
index 0000000..aa7e8b0
--- /dev/null
+++ b/NEW/src/JdeScoping.DataSync.Dev/Configuration/DevPipelinesRoot.cs
@@ -0,0 +1,58 @@
+namespace JdeScoping.DataSync.Dev.Configuration;
+
+///
+/// Root configuration for development ETL pipelines.
+///
+public record DevPipelinesRoot(
+ DevPipelineSettings? Settings,
+ Dictionary Pipelines)
+{
+ public DevPipelineSettings EffectiveSettings => Settings ?? new DevPipelineSettings();
+}
+
+///
+/// Settings for development pipeline execution.
+///
+public record DevPipelineSettings
+{
+ ///
+ /// Size categories for parallel/sequential execution.
+ /// Very large tables run sequentially to avoid IO contention.
+ ///
+ public SizeCategories? SizeCategories { get; init; }
+}
+
+///
+/// Table size categorization for execution strategy.
+///
+public record SizeCategories
+{
+ /// Small tables (less than 1 MB) - run in parallel.
+ public List? Small { get; init; }
+
+ /// Medium tables (1-20 MB) - run in parallel.
+ public List? Medium { get; init; }
+
+ /// Large tables (20-200 MB) - run in parallel.
+ public List? Large { get; init; }
+
+ /// Very large tables (200+ MB) - run sequentially.
+ public List? VeryLarge { get; init; }
+}
+
+///
+/// Configuration for a single development ETL pipeline.
+///
+public record DevPipelineConfig(
+ DevSourceConfig Source,
+ DevDestinationConfig Destination);
+
+///
+/// Source configuration for a development pipeline (file-based).
+///
+public record DevSourceConfig(string FileName);
+
+///
+/// Destination configuration for a development pipeline.
+///
+public record DevDestinationConfig(string Table);
diff --git a/NEW/src/JdeScoping.DataSync.Dev/Contracts/IDevEtlPipelineFactory.cs b/NEW/src/JdeScoping.DataSync.Dev/Contracts/IDevEtlPipelineFactory.cs
new file mode 100644
index 0000000..f6ca78d
--- /dev/null
+++ b/NEW/src/JdeScoping.DataSync.Dev/Contracts/IDevEtlPipelineFactory.cs
@@ -0,0 +1,29 @@
+using JdeScoping.DataSync.Etl.Pipeline;
+
+namespace JdeScoping.DataSync.Dev.Contracts;
+
+///
+/// Factory for creating development ETL pipelines from JSON configuration.
+///
+public interface IDevEtlPipelineFactory
+{
+ ///
+ /// Gets the list of available table names.
+ ///
+ IEnumerable GetAvailableTables();
+
+ ///
+ /// Creates a pipeline for the specified table.
+ ///
+ /// The table name.
+ /// The directory containing cache files.
+ /// The configured ETL pipeline.
+ EtlPipeline GetPipeline(string tableName, string cacheDirectory);
+
+ ///
+ /// Checks if a table is categorized as very large (should run sequentially).
+ ///
+ /// The table name.
+ /// True if the table is very large.
+ bool IsVeryLargeTable(string tableName);
+}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/DevEtlRegistry.cs b/NEW/src/JdeScoping.DataSync.Dev/DevEtlRegistry.cs
index 5559dae..4daa683 100644
--- a/NEW/src/JdeScoping.DataSync.Dev/DevEtlRegistry.cs
+++ b/NEW/src/JdeScoping.DataSync.Dev/DevEtlRegistry.cs
@@ -1,6 +1,5 @@
using System.Collections.Concurrent;
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Pipeline;
+using JdeScoping.DataSync.Dev.Contracts;
using JdeScoping.DataSync.Etl.Results;
using Microsoft.Extensions.Logging;
@@ -8,69 +7,20 @@ namespace JdeScoping.DataSync.Dev;
///
/// Registry for development ETL pipelines that load from cached protobuf files.
+/// Uses JSON configuration via IDevEtlPipelineFactory.
///
public class DevEtlRegistry
{
- private readonly IDbConnectionFactory _connectionFactory;
+ private readonly IDevEtlPipelineFactory _pipelineFactory;
private readonly string _cacheDirectory;
private readonly ILogger? _logger;
- private readonly Dictionary> _pipelineFactories = new(StringComparer.OrdinalIgnoreCase)
- {
- // Small tables (< 1 MB)
- [BranchDevEtl.TableName] = (factory, cacheDir) =>
- BranchDevEtl.Create(factory, Path.Combine(cacheDir, BranchDevEtl.CacheFileName)),
- [OrgHierarchyDevEtl.TableName] = (factory, cacheDir) =>
- OrgHierarchyDevEtl.Create(factory, Path.Combine(cacheDir, OrgHierarchyDevEtl.CacheFileName)),
- [WorkCenterDevEtl.TableName] = (factory, cacheDir) =>
- WorkCenterDevEtl.Create(factory, Path.Combine(cacheDir, WorkCenterDevEtl.CacheFileName)),
- [ProfitCenterDevEtl.TableName] = (factory, cacheDir) =>
- ProfitCenterDevEtl.Create(factory, Path.Combine(cacheDir, ProfitCenterDevEtl.CacheFileName)),
- // Medium tables (1-20 MB)
- [JdeUserDevEtl.TableName] = (factory, cacheDir) =>
- JdeUserDevEtl.Create(factory, Path.Combine(cacheDir, JdeUserDevEtl.CacheFileName)),
- [FunctionCodeDevEtl.TableName] = (factory, cacheDir) =>
- FunctionCodeDevEtl.Create(factory, Path.Combine(cacheDir, FunctionCodeDevEtl.CacheFileName)),
- [ItemDevEtl.TableName] = (factory, cacheDir) =>
- ItemDevEtl.Create(factory, Path.Combine(cacheDir, ItemDevEtl.CacheFileName)),
- [RouteMasterDevEtl.TableName] = (factory, cacheDir) =>
- RouteMasterDevEtl.Create(factory, Path.Combine(cacheDir, RouteMasterDevEtl.CacheFileName)),
- // Large tables (20-200 MB)
- [LotDevEtl.TableName] = (factory, cacheDir) =>
- LotDevEtl.Create(factory, Path.Combine(cacheDir, LotDevEtl.CacheFileName)),
- [MisDataDevEtl.TableName] = (factory, cacheDir) =>
- MisDataDevEtl.Create(factory, Path.Combine(cacheDir, MisDataDevEtl.CacheFileName)),
- [WorkOrderCurrDevEtl.TableName] = (factory, cacheDir) =>
- WorkOrderCurrDevEtl.Create(factory, Path.Combine(cacheDir, WorkOrderCurrDevEtl.CacheFileName)),
- [WorkOrderHistDevEtl.TableName] = (factory, cacheDir) =>
- WorkOrderHistDevEtl.Create(factory, Path.Combine(cacheDir, WorkOrderHistDevEtl.CacheFileName)),
- [LotUsageHistDevEtl.TableName] = (factory, cacheDir) =>
- LotUsageHistDevEtl.Create(factory, Path.Combine(cacheDir, LotUsageHistDevEtl.CacheFileName)),
- [WorkOrderComponentHistDevEtl.TableName] = (factory, cacheDir) =>
- WorkOrderComponentHistDevEtl.Create(factory, Path.Combine(cacheDir, WorkOrderComponentHistDevEtl.CacheFileName)),
- // Very large tables (200+ MB)
- [WorkOrderStepHistDevEtl.TableName] = (factory, cacheDir) =>
- WorkOrderStepHistDevEtl.Create(factory, Path.Combine(cacheDir, WorkOrderStepHistDevEtl.CacheFileName)),
- [WorkOrderComponentCurrDevEtl.TableName] = (factory, cacheDir) =>
- WorkOrderComponentCurrDevEtl.Create(factory, Path.Combine(cacheDir, WorkOrderComponentCurrDevEtl.CacheFileName)),
- [WorkOrderRoutingDevEtl.TableName] = (factory, cacheDir) =>
- WorkOrderRoutingDevEtl.Create(factory, Path.Combine(cacheDir, WorkOrderRoutingDevEtl.CacheFileName)),
- [LotUsageCurrDevEtl.TableName] = (factory, cacheDir) =>
- LotUsageCurrDevEtl.Create(factory, Path.Combine(cacheDir, LotUsageCurrDevEtl.CacheFileName)),
- [WorkOrderStepCurrDevEtl.TableName] = (factory, cacheDir) =>
- WorkOrderStepCurrDevEtl.Create(factory, Path.Combine(cacheDir, WorkOrderStepCurrDevEtl.CacheFileName)),
- [WorkOrderTimeHistDevEtl.TableName] = (factory, cacheDir) =>
- WorkOrderTimeHistDevEtl.Create(factory, Path.Combine(cacheDir, WorkOrderTimeHistDevEtl.CacheFileName)),
- [WorkOrderTimeCurrDevEtl.TableName] = (factory, cacheDir) =>
- WorkOrderTimeCurrDevEtl.Create(factory, Path.Combine(cacheDir, WorkOrderTimeCurrDevEtl.CacheFileName)),
- };
-
public DevEtlRegistry(
- IDbConnectionFactory connectionFactory,
+ IDevEtlPipelineFactory pipelineFactory,
string cacheDirectory,
ILogger? logger = null)
{
- _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
+ _pipelineFactory = pipelineFactory ?? throw new ArgumentNullException(nameof(pipelineFactory));
if (string.IsNullOrWhiteSpace(cacheDirectory))
throw new ArgumentException("Cache directory is required.", nameof(cacheDirectory));
@@ -82,21 +32,13 @@ public class DevEtlRegistry
_logger = logger;
}
- public IEnumerable GetAvailableTables() => _pipelineFactories.Keys;
-
- public EtlPipeline GetPipeline(string tableName)
- {
- if (!_pipelineFactories.TryGetValue(tableName, out var factory))
- throw new ArgumentException($"No pipeline registered for table '{tableName}'.", nameof(tableName));
-
- return factory(_connectionFactory, _cacheDirectory);
- }
+ public IEnumerable GetAvailableTables() => _pipelineFactory.GetAvailableTables();
public async Task RunAsync(string tableName, CancellationToken cancellationToken = default)
{
_logger?.LogInformation("Running dev ETL for {TableName}", tableName);
- var pipeline = GetPipeline(tableName);
+ var pipeline = _pipelineFactory.GetPipeline(tableName, _cacheDirectory);
var result = await pipeline.ExecuteAsync(cancellationToken);
if (result.Success)
@@ -138,10 +80,10 @@ public class DevEtlRegistry
// Separate tables by size - run very large ones sequentially at the end
var smallMediumTables = GetAvailableTables()
- .Where(t => !IsVeryLargeTable(t))
+ .Where(t => !_pipelineFactory.IsVeryLargeTable(t))
.ToList();
var veryLargeTables = GetAvailableTables()
- .Where(IsVeryLargeTable)
+ .Where(t => _pipelineFactory.IsVeryLargeTable(t))
.ToList();
_logger?.LogInformation(
@@ -175,14 +117,4 @@ public class DevEtlRegistry
return results.ToList();
}
-
- ///
- /// Identifies very large tables that should be loaded sequentially to avoid IO contention.
- ///
- private static bool IsVeryLargeTable(string tableName) =>
- tableName.Contains("WorkOrderTime", StringComparison.OrdinalIgnoreCase) ||
- tableName.Contains("WorkOrderStep", StringComparison.OrdinalIgnoreCase) ||
- tableName.Contains("WorkOrderRouting", StringComparison.OrdinalIgnoreCase) ||
- tableName.Contains("WorkOrderComponent", StringComparison.OrdinalIgnoreCase) ||
- tableName.Contains("LotUsage", StringComparison.OrdinalIgnoreCase);
}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/FunctionCodeDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/FunctionCodeDevEtl.cs
deleted file mode 100644
index 4d1fa28..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/FunctionCodeDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the FunctionCode table.
-///
-public static class FunctionCodeDevEtl
-{
- public static readonly string TableName = "FunctionCode";
- public static readonly string CacheFileName = "functioncode.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/ItemDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/ItemDevEtl.cs
deleted file mode 100644
index 3d98e36..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/ItemDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the Item table.
-///
-public static class ItemDevEtl
-{
- public static readonly string TableName = "Item";
- public static readonly string CacheFileName = "item.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/JdeScoping.DataSync.Dev.csproj b/NEW/src/JdeScoping.DataSync.Dev/JdeScoping.DataSync.Dev.csproj
index 7c67a0a..dce71c4 100644
--- a/NEW/src/JdeScoping.DataSync.Dev/JdeScoping.DataSync.Dev.csproj
+++ b/NEW/src/JdeScoping.DataSync.Dev/JdeScoping.DataSync.Dev.csproj
@@ -6,6 +6,11 @@
enable
+
+
+
+
+
@@ -14,4 +19,10 @@
+
+
+ PreserveNewest
+
+
+
diff --git a/NEW/src/JdeScoping.DataSync.Dev/JdeUserDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/JdeUserDevEtl.cs
deleted file mode 100644
index 3529e7d..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/JdeUserDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the JdeUser table.
-///
-public static class JdeUserDevEtl
-{
- public static readonly string TableName = "JdeUser";
- public static readonly string CacheFileName = "jdeuser.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/LotDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/LotDevEtl.cs
deleted file mode 100644
index c21a31f..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/LotDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the Lot table.
-///
-public static class LotDevEtl
-{
- public static readonly string TableName = "Lot";
- public static readonly string CacheFileName = "lot.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/LotUsageCurrDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/LotUsageCurrDevEtl.cs
deleted file mode 100644
index 4a8199c..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/LotUsageCurrDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the LotUsage_Curr table.
-///
-public static class LotUsageCurrDevEtl
-{
- public static readonly string TableName = "LotUsage_Curr";
- public static readonly string CacheFileName = "lotusage_curr.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/LotUsageHistDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/LotUsageHistDevEtl.cs
deleted file mode 100644
index 80fc338..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/LotUsageHistDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the LotUsage_Hist table.
-///
-public static class LotUsageHistDevEtl
-{
- public static readonly string TableName = "LotUsage_Hist";
- public static readonly string CacheFileName = "lotusage_hist.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/MisDataDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/MisDataDevEtl.cs
deleted file mode 100644
index 4bf73e6..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/MisDataDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the MisData table.
-///
-public static class MisDataDevEtl
-{
- public static readonly string TableName = "MisData";
- public static readonly string CacheFileName = "misdata.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/Options/DevPipelineOptions.cs b/NEW/src/JdeScoping.DataSync.Dev/Options/DevPipelineOptions.cs
new file mode 100644
index 0000000..69e06db
--- /dev/null
+++ b/NEW/src/JdeScoping.DataSync.Dev/Options/DevPipelineOptions.cs
@@ -0,0 +1,18 @@
+namespace JdeScoping.DataSync.Dev.Options;
+
+///
+/// Configuration options for development ETL pipelines.
+///
+public class DevPipelineOptions
+{
+ ///
+ /// Configuration section name.
+ ///
+ public const string SectionName = "DevPipelines";
+
+ ///
+ /// Path to the dev-pipelines.json configuration file.
+ /// Relative to the assembly directory.
+ ///
+ public string ConfigPath { get; set; } = "Pipelines/dev-pipelines.json";
+}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/OrgHierarchyDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/OrgHierarchyDevEtl.cs
deleted file mode 100644
index a3f8aad..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/OrgHierarchyDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the OrgHierarchy table.
-///
-public static class OrgHierarchyDevEtl
-{
- public static readonly string TableName = "OrgHierarchy";
- public static readonly string CacheFileName = "orghierarchy.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/Pipelines/dev-pipelines.json b/NEW/src/JdeScoping.DataSync.Dev/Pipelines/dev-pipelines.json
new file mode 100644
index 0000000..39ff7b3
--- /dev/null
+++ b/NEW/src/JdeScoping.DataSync.Dev/Pipelines/dev-pipelines.json
@@ -0,0 +1,96 @@
+{
+ "settings": {
+ "sizeCategories": {
+ "small": ["Branch", "OrgHierarchy", "WorkCenter", "ProfitCenter"],
+ "medium": ["JdeUser", "FunctionCode", "Item", "RouteMaster"],
+ "large": ["Lot", "MisData", "WorkOrder_Curr", "WorkOrder_Hist", "LotUsage_Hist", "WorkOrderComponent_Hist"],
+ "veryLarge": ["WorkOrderStep_Hist", "WorkOrderComponent_Curr", "WorkOrderRouting", "LotUsage_Curr", "WorkOrderStep_Curr", "WorkOrderTime_Hist", "WorkOrderTime_Curr"]
+ }
+ },
+ "pipelines": {
+ "Branch": {
+ "source": { "fileName": "branch.pb.zstd" },
+ "destination": { "table": "Branch" }
+ },
+ "OrgHierarchy": {
+ "source": { "fileName": "orghierarchy.pb.zstd" },
+ "destination": { "table": "OrgHierarchy" }
+ },
+ "WorkCenter": {
+ "source": { "fileName": "workcenter.pb.zstd" },
+ "destination": { "table": "WorkCenter" }
+ },
+ "ProfitCenter": {
+ "source": { "fileName": "profitcenter.pb.zstd" },
+ "destination": { "table": "ProfitCenter" }
+ },
+ "JdeUser": {
+ "source": { "fileName": "jdeuser.pb.zstd" },
+ "destination": { "table": "JdeUser" }
+ },
+ "FunctionCode": {
+ "source": { "fileName": "functioncode.pb.zstd" },
+ "destination": { "table": "FunctionCode" }
+ },
+ "Item": {
+ "source": { "fileName": "item.pb.zstd" },
+ "destination": { "table": "Item" }
+ },
+ "RouteMaster": {
+ "source": { "fileName": "routemaster.pb.zstd" },
+ "destination": { "table": "RouteMaster" }
+ },
+ "Lot": {
+ "source": { "fileName": "lot.pb.zstd" },
+ "destination": { "table": "Lot" }
+ },
+ "MisData": {
+ "source": { "fileName": "misdata.pb.zstd" },
+ "destination": { "table": "MisData" }
+ },
+ "WorkOrder_Curr": {
+ "source": { "fileName": "workorder_curr.pb.zstd" },
+ "destination": { "table": "WorkOrder_Curr" }
+ },
+ "WorkOrder_Hist": {
+ "source": { "fileName": "workorder_hist.pb.zstd" },
+ "destination": { "table": "WorkOrder_Hist" }
+ },
+ "LotUsage_Curr": {
+ "source": { "fileName": "lotusage_curr.pb.zstd" },
+ "destination": { "table": "LotUsage_Curr" }
+ },
+ "LotUsage_Hist": {
+ "source": { "fileName": "lotusage_hist.pb.zstd" },
+ "destination": { "table": "LotUsage_Hist" }
+ },
+ "WorkOrderComponent_Curr": {
+ "source": { "fileName": "workordercomponent_curr.pb.zstd" },
+ "destination": { "table": "WorkOrderComponent_Curr" }
+ },
+ "WorkOrderComponent_Hist": {
+ "source": { "fileName": "workordercomponent_hist.pb.zstd" },
+ "destination": { "table": "WorkOrderComponent_Hist" }
+ },
+ "WorkOrderStep_Curr": {
+ "source": { "fileName": "workorderstep_curr.pb.zstd" },
+ "destination": { "table": "WorkOrderStep_Curr" }
+ },
+ "WorkOrderStep_Hist": {
+ "source": { "fileName": "workorderstep_hist.pb.zstd" },
+ "destination": { "table": "WorkOrderStep_Hist" }
+ },
+ "WorkOrderTime_Curr": {
+ "source": { "fileName": "workordertime_curr.pb.zstd" },
+ "destination": { "table": "WorkOrderTime_Curr" }
+ },
+ "WorkOrderTime_Hist": {
+ "source": { "fileName": "workordertime_hist.pb.zstd" },
+ "destination": { "table": "WorkOrderTime_Hist" }
+ },
+ "WorkOrderRouting": {
+ "source": { "fileName": "workorderrouting.pb.zstd" },
+ "destination": { "table": "WorkOrderRouting" }
+ }
+ }
+}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/ProfitCenterDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/ProfitCenterDevEtl.cs
deleted file mode 100644
index e11e55a..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/ProfitCenterDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the ProfitCenter table.
-///
-public static class ProfitCenterDevEtl
-{
- public static readonly string TableName = "ProfitCenter";
- public static readonly string CacheFileName = "profitcenter.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/RouteMasterDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/RouteMasterDevEtl.cs
deleted file mode 100644
index 1fe8651..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/RouteMasterDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the RouteMaster table.
-///
-public static class RouteMasterDevEtl
-{
- public static readonly string TableName = "RouteMaster";
- public static readonly string CacheFileName = "routemaster.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/Services/DevEtlPipelineFactory.cs b/NEW/src/JdeScoping.DataSync.Dev/Services/DevEtlPipelineFactory.cs
new file mode 100644
index 0000000..7dcb357
--- /dev/null
+++ b/NEW/src/JdeScoping.DataSync.Dev/Services/DevEtlPipelineFactory.cs
@@ -0,0 +1,123 @@
+using System.Text.Json;
+using JdeScoping.DataAccess.Interfaces;
+using JdeScoping.DataSync.Dev.Configuration;
+using JdeScoping.DataSync.Dev.Contracts;
+using JdeScoping.DataSync.Dev.Options;
+using JdeScoping.DataSync.Dev.Sources;
+using JdeScoping.DataSync.Etl.Destinations;
+using JdeScoping.DataSync.Etl.Pipeline;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace JdeScoping.DataSync.Dev.Services;
+
+///
+/// Factory for creating development ETL pipelines from JSON configuration.
+///
+public class DevEtlPipelineFactory : IDevEtlPipelineFactory
+{
+ private static readonly JsonSerializerOptions JsonOptions = new()
+ {
+ PropertyNameCaseInsensitive = true,
+ ReadCommentHandling = JsonCommentHandling.Skip,
+ AllowTrailingCommas = true
+ };
+
+ private readonly IDbConnectionFactory _connectionFactory;
+ private readonly ILogger _logger;
+ private readonly DevPipelinesRoot _config;
+ private readonly HashSet _veryLargeTables;
+
+ ///
+ /// Creates a new development pipeline factory.
+ ///
+ public DevEtlPipelineFactory(
+ IDbConnectionFactory connectionFactory,
+ IOptions options,
+ ILogger logger)
+ {
+ ArgumentNullException.ThrowIfNull(connectionFactory);
+ ArgumentNullException.ThrowIfNull(options);
+ ArgumentNullException.ThrowIfNull(logger);
+
+ _connectionFactory = connectionFactory;
+ _logger = logger;
+ _config = LoadPipelineConfigs(options.Value.ConfigPath);
+ _veryLargeTables = BuildVeryLargeTableSet();
+ }
+
+ ///
+ /// Creates a new development pipeline factory with pre-loaded config (for testing).
+ ///
+ internal DevEtlPipelineFactory(
+ IDbConnectionFactory connectionFactory,
+ DevPipelinesRoot config,
+ ILogger logger)
+ {
+ ArgumentNullException.ThrowIfNull(connectionFactory);
+ ArgumentNullException.ThrowIfNull(config);
+ ArgumentNullException.ThrowIfNull(logger);
+
+ _connectionFactory = connectionFactory;
+ _logger = logger;
+ _config = config;
+ _veryLargeTables = BuildVeryLargeTableSet();
+ }
+
+ ///
+ public IEnumerable GetAvailableTables() => _config.Pipelines.Keys;
+
+ ///
+ public bool IsVeryLargeTable(string tableName) =>
+ _veryLargeTables.Contains(tableName);
+
+ ///
+ public EtlPipeline GetPipeline(string tableName, string cacheDirectory)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(tableName);
+ ArgumentException.ThrowIfNullOrWhiteSpace(cacheDirectory);
+
+ if (!_config.Pipelines.TryGetValue(tableName, out var pipelineConfig))
+ {
+ throw new InvalidOperationException(
+ $"No pipeline configured for table: {tableName}. " +
+ $"Available tables: {string.Join(", ", _config.Pipelines.Keys)}");
+ }
+
+ var cacheFilePath = Path.Combine(cacheDirectory, pipelineConfig.Source.FileName);
+
+ return new EtlPipelineBuilder()
+ .WithName($"{tableName}_Dev")
+ .WithSource(new ProtobufZstdFileSource(cacheFilePath))
+ .WithDestination(new DbBulkImportDestination(_connectionFactory, pipelineConfig.Destination.Table))
+ .WithLogger(_logger)
+ .Build();
+ }
+
+ private DevPipelinesRoot LoadPipelineConfigs(string configPath)
+ {
+ var assemblyDir = Path.GetDirectoryName(typeof(DevEtlPipelineFactory).Assembly.Location)!;
+ var fullPath = Path.Combine(assemblyDir, configPath);
+
+ if (!File.Exists(fullPath))
+ {
+ throw new FileNotFoundException(
+ $"Dev pipeline config not found: {fullPath}. " +
+ "Ensure the config file is included in the build output.");
+ }
+
+ var json = File.ReadAllText(fullPath);
+ var root = JsonSerializer.Deserialize(json, JsonOptions)
+ ?? throw new InvalidOperationException("Failed to deserialize dev pipeline config: result was null.");
+
+ return root;
+ }
+
+ private HashSet BuildVeryLargeTableSet()
+ {
+ var veryLarge = _config.EffectiveSettings.SizeCategories?.VeryLarge;
+ return veryLarge != null
+ ? new HashSet(veryLarge, StringComparer.OrdinalIgnoreCase)
+ : new HashSet(StringComparer.OrdinalIgnoreCase);
+ }
+}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/WorkCenterDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/WorkCenterDevEtl.cs
deleted file mode 100644
index 501ca28..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/WorkCenterDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the WorkCenter table.
-///
-public static class WorkCenterDevEtl
-{
- public static readonly string TableName = "WorkCenter";
- public static readonly string CacheFileName = "workcenter.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/WorkOrderComponentCurrDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/WorkOrderComponentCurrDevEtl.cs
deleted file mode 100644
index 0868249..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/WorkOrderComponentCurrDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the WorkOrderComponent_Curr table.
-///
-public static class WorkOrderComponentCurrDevEtl
-{
- public static readonly string TableName = "WorkOrderComponent_Curr";
- public static readonly string CacheFileName = "workordercomponent_curr.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/WorkOrderComponentHistDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/WorkOrderComponentHistDevEtl.cs
deleted file mode 100644
index ca0888b..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/WorkOrderComponentHistDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the WorkOrderComponent_Hist table.
-///
-public static class WorkOrderComponentHistDevEtl
-{
- public static readonly string TableName = "WorkOrderComponent_Hist";
- public static readonly string CacheFileName = "workordercomponent_hist.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/WorkOrderCurrDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/WorkOrderCurrDevEtl.cs
deleted file mode 100644
index 839b6b6..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/WorkOrderCurrDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the WorkOrder_Curr table.
-///
-public static class WorkOrderCurrDevEtl
-{
- public static readonly string TableName = "WorkOrder_Curr";
- public static readonly string CacheFileName = "workorder_curr.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/WorkOrderHistDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/WorkOrderHistDevEtl.cs
deleted file mode 100644
index 8c5d146..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/WorkOrderHistDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the WorkOrder_Hist table.
-///
-public static class WorkOrderHistDevEtl
-{
- public static readonly string TableName = "WorkOrder_Hist";
- public static readonly string CacheFileName = "workorder_hist.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/WorkOrderRoutingDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/WorkOrderRoutingDevEtl.cs
deleted file mode 100644
index 6b1c5f1..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/WorkOrderRoutingDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the WorkOrderRouting table.
-///
-public static class WorkOrderRoutingDevEtl
-{
- public static readonly string TableName = "WorkOrderRouting";
- public static readonly string CacheFileName = "workorderrouting.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/WorkOrderStepCurrDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/WorkOrderStepCurrDevEtl.cs
deleted file mode 100644
index 5bebbb5..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/WorkOrderStepCurrDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the WorkOrderStep_Curr table.
-///
-public static class WorkOrderStepCurrDevEtl
-{
- public static readonly string TableName = "WorkOrderStep_Curr";
- public static readonly string CacheFileName = "workorderstep_curr.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/WorkOrderStepHistDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/WorkOrderStepHistDevEtl.cs
deleted file mode 100644
index 92ebd87..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/WorkOrderStepHistDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the WorkOrderStep_Hist table.
-///
-public static class WorkOrderStepHistDevEtl
-{
- public static readonly string TableName = "WorkOrderStep_Hist";
- public static readonly string CacheFileName = "workorderstep_hist.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/WorkOrderTimeCurrDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/WorkOrderTimeCurrDevEtl.cs
deleted file mode 100644
index ae2d735..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/WorkOrderTimeCurrDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the WorkOrderTime_Curr table.
-///
-public static class WorkOrderTimeCurrDevEtl
-{
- public static readonly string TableName = "WorkOrderTime_Curr";
- public static readonly string CacheFileName = "workordertime_curr.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.DataSync.Dev/WorkOrderTimeHistDevEtl.cs b/NEW/src/JdeScoping.DataSync.Dev/WorkOrderTimeHistDevEtl.cs
deleted file mode 100644
index 3c290d5..0000000
--- a/NEW/src/JdeScoping.DataSync.Dev/WorkOrderTimeHistDevEtl.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Etl.Destinations;
-using JdeScoping.DataSync.Etl.Pipeline;
-using JdeScoping.DataSync.Dev.Sources;
-
-namespace JdeScoping.DataSync.Dev;
-
-///
-/// Development ETL pipeline for the WorkOrderTime_Hist table.
-///
-public static class WorkOrderTimeHistDevEtl
-{
- public static readonly string TableName = "WorkOrderTime_Hist";
- public static readonly string CacheFileName = "workordertime_hist.pb.zstd";
-
- public static EtlPipeline Create(IDbConnectionFactory connectionFactory, string cacheFilePath)
- {
- ArgumentNullException.ThrowIfNull(connectionFactory);
-
- if (string.IsNullOrWhiteSpace(cacheFilePath))
- throw new ArgumentException("Cache file path is required.", nameof(cacheFilePath));
-
- return new EtlPipelineBuilder()
- .WithName($"{TableName}_Dev")
- .WithSource(new ProtobufZstdFileSource(cacheFilePath))
- .WithDestination(new DbBulkImportDestination(connectionFactory, TableName))
- .Build();
- }
-}
diff --git a/NEW/src/JdeScoping.ExcelIO/Generators/FluentTableWriter.cs b/NEW/src/JdeScoping.ExcelIO/Generators/FluentTableWriter.cs
index 14636f8..f1e67ce 100644
--- a/NEW/src/JdeScoping.ExcelIO/Generators/FluentTableWriter.cs
+++ b/NEW/src/JdeScoping.ExcelIO/Generators/FluentTableWriter.cs
@@ -113,7 +113,7 @@ public sealed class FluentTableWriter
else if (column.AutoWidth)
{
worksheet.Column(col).AdjustToContents();
- worksheet.Column(col).Width *= Formatting.ExcelFormats.DataPaddingFactor;
+ worksheet.Column(col).Width *= ExcelFormats.DataPaddingFactor;
}
else
{
diff --git a/NEW/tests/JdeScoping.Api.Tests/Controllers/AuthControllerTests.cs b/NEW/tests/JdeScoping.Api.Tests/Controllers/AuthControllerTests.cs
index 66047f1..96a9082 100644
--- a/NEW/tests/JdeScoping.Api.Tests/Controllers/AuthControllerTests.cs
+++ b/NEW/tests/JdeScoping.Api.Tests/Controllers/AuthControllerTests.cs
@@ -4,6 +4,7 @@ using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using JdeScoping.Api.Controllers;
+using JdeScoping.Core.ApiContracts.Auth;
using JdeScoping.Core.Interfaces;
using JdeScoping.Core.Models;
using JdeScoping.Core.Models.Auth;
@@ -180,7 +181,7 @@ public class AuthControllerTests
// Assert
result.Result.ShouldBeOfType();
var okResult = (OkObjectResult)result.Result!;
- var user = okResult.Value.ShouldBeOfType();
+ var user = okResult.Value.ShouldBeOfType();
user.Username.ShouldBe("testuser");
}
diff --git a/NEW/tests/JdeScoping.Api.Tests/Hubs/StatusHubTests.cs b/NEW/tests/JdeScoping.Api.Tests/Hubs/StatusHubTests.cs
index 345a637..28cf249 100644
--- a/NEW/tests/JdeScoping.Api.Tests/Hubs/StatusHubTests.cs
+++ b/NEW/tests/JdeScoping.Api.Tests/Hubs/StatusHubTests.cs
@@ -1,8 +1,5 @@
using JdeScoping.Api.Hubs;
-using JdeScoping.Core.Models;
-using JdeScoping.Core.Models.Enums;
-using JdeScoping.Core.Models.Infrastructure;
-using JdeScoping.Core.Models.Search;
+using JdeScoping.Core.ApiContracts.SignalR;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using NSubstitute;
@@ -36,7 +33,7 @@ public class StatusHubTests
var clientsProperty = typeof(Hub).GetProperty("Clients");
clientsProperty?.SetValue(_hub, hubClients);
- var statusUpdate = new StatusUpdate
+ var statusUpdate = new StatusUpdateDto
{
Message = "Processing",
Timestamp = DateTime.UtcNow
@@ -93,13 +90,12 @@ public class StatusHubTests
var clientsProperty = typeof(Hub).GetProperty("Clients");
clientsProperty?.SetValue(_hub, hubClients);
- var searchUpdate = new SearchUpdate
+ var searchUpdate = new SearchUpdateDto
{
Id = 42,
UserName = "testuser",
Name = "Test Search",
- Status = SearchStatus.Running,
- Timestamp = DateTime.UtcNow
+ Status = "Running"
};
// Act
diff --git a/NEW/tests/JdeScoping.Client.Tests/Services/AuthApiClientTests.cs b/NEW/tests/JdeScoping.Client.Tests/Services/AuthApiClientTests.cs
index bc41210..af46a6e 100644
--- a/NEW/tests/JdeScoping.Client.Tests/Services/AuthApiClientTests.cs
+++ b/NEW/tests/JdeScoping.Client.Tests/Services/AuthApiClientTests.cs
@@ -2,8 +2,8 @@ using System.Net;
using System.Text.Json;
using JdeScoping.Client.Services;
using JdeScoping.Core.ApiContracts;
+using JdeScoping.Core.ApiContracts.Auth;
using JdeScoping.Core.ApiContracts.Results;
-using JdeScoping.Core.Models;
using JdeScoping.Core.Models.Auth;
using RichardSzalay.MockHttp;
using Shouldly;
@@ -70,7 +70,7 @@ public class AuthApiClientTests
public async Task GetCurrentUserAsync_CallsCorrectRoute()
{
// Arrange
- var user = new UserInfo { Username = "testuser", FirstName = "Test", LastName = "User" };
+ var user = new UserInfoDto { Username = "testuser", FirstName = "Test", LastName = "User" };
var request = _mockHttp.Expect(HttpMethod.Get, $"http://localhost/{ApiRoutes.Auth.Me}")
.Respond("application/json", JsonSerializer.Serialize(user));
@@ -103,7 +103,7 @@ public class AuthApiClientTests
public async Task LoginAsync_Success_ReturnsLoginResult()
{
// Arrange
- var user = new UserInfo { Username = "testuser", FirstName = "Test", LastName = "User" };
+ var user = new UserInfoDto { Username = "testuser", FirstName = "Test", LastName = "User" };
var loginResult = new LoginResultModel(true, null, user);
_mockHttp.When(HttpMethod.Post, "*")
.Respond("application/json", JsonSerializer.Serialize(loginResult));
@@ -135,7 +135,7 @@ public class AuthApiClientTests
public async Task GetCurrentUserAsync_Success_ReturnsUserInfo()
{
// Arrange
- var userInfo = new UserInfo { Username = "testuser", FirstName = "Test", LastName = "User" };
+ var userInfo = new UserInfoDto { Username = "testuser", FirstName = "Test", LastName = "User" };
_mockHttp.When(HttpMethod.Get, "*")
.Respond("application/json", JsonSerializer.Serialize(userInfo));
diff --git a/NEW/tests/JdeScoping.DataSync.Dev.Tests/BranchDevEtlTests.cs b/NEW/tests/JdeScoping.DataSync.Dev.Tests/BranchDevEtlTests.cs
deleted file mode 100644
index 991c9ad..0000000
--- a/NEW/tests/JdeScoping.DataSync.Dev.Tests/BranchDevEtlTests.cs
+++ /dev/null
@@ -1,177 +0,0 @@
-using Dapper;
-using JdeScoping.DataAccess;
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Dev;
-using Microsoft.Data.SqlClient;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging.Abstractions;
-using NSubstitute;
-using Shouldly;
-
-namespace JdeScoping.DataSync.Dev.Tests;
-
-///
-/// Integration tests for Branch development ETL.
-/// Requires: Local SQL Server, CACHED_DB_FILES directory with branch.json.zstd
-///
-public class BranchDevEtlTests : IAsyncLifetime
-{
- private readonly string _connectionString;
- private readonly string _cacheDirectory;
- private readonly IDbConnectionFactory _connectionFactory;
-
- public BranchDevEtlTests()
- {
- // Load configuration
- var config = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile("appsettings.json", optional: true)
- .AddEnvironmentVariables()
- .Build();
-
- _connectionString = config.GetConnectionString("LotFinderDB")
- ?? throw new InvalidOperationException("LotFinderDB connection string not configured.");
-
- _cacheDirectory = config["DevEtl:CacheDirectory"]
- ?? Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..", "CACHED_DB_FILES");
-
- _connectionFactory = new DbConnectionFactory(config, NullLogger.Instance);
- }
-
- public async Task InitializeAsync()
- {
- // Ensure Branch table is empty before test
- await using var connection = new SqlConnection(_connectionString);
- await connection.OpenAsync();
- await connection.ExecuteAsync("TRUNCATE TABLE dbo.Branch");
- }
-
- public Task DisposeAsync() => Task.CompletedTask;
-
- [Fact]
- public void Create_ReturnsValidPipeline()
- {
- // Arrange
- var cacheFilePath = Path.Combine(_cacheDirectory, BranchDevEtl.CacheFileName);
- if (!File.Exists(cacheFilePath))
- {
- // Skip test if cache file doesn't exist
- return;
- }
-
- // Act
- var pipeline = BranchDevEtl.Create(_connectionFactory, cacheFilePath);
-
- // Assert
- pipeline.ShouldNotBeNull();
- pipeline.PipelineName.ShouldBe("Branch_Dev");
- }
-
- [Fact]
- public async Task Execute_LoadsBranchData()
- {
- // Arrange
- var cacheFilePath = Path.Combine(_cacheDirectory, BranchDevEtl.CacheFileName);
- if (!File.Exists(cacheFilePath))
- {
- // Skip test if cache file doesn't exist
- return;
- }
-
- var pipeline = BranchDevEtl.Create(_connectionFactory, cacheFilePath);
-
- // Act
- var result = await pipeline.ExecuteAsync();
-
- // Assert
- result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
- result.TotalRows.ShouldBeGreaterThan(0, "Should load at least one row");
-
- // Verify data in database
- await using var connection = new SqlConnection(_connectionString);
- await connection.OpenAsync();
- var count = await connection.ExecuteScalarAsync("SELECT COUNT(*) FROM dbo.Branch");
-
- count.ShouldBe((int)result.TotalRows, "Database row count should match pipeline result");
- }
-
- [Fact]
- public async Task Registry_RunAsync_LoadsBranch()
- {
- // Arrange
- if (!Directory.Exists(_cacheDirectory))
- {
- // Skip test if cache directory doesn't exist
- return;
- }
-
- var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory);
-
- // Act
- var result = await registry.RunAsync("Branch");
-
- // Assert
- result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
- result.TotalRows.ShouldBeGreaterThan(0);
- }
-
- [Fact]
- public void Create_WithNullConnectionFactory_ThrowsArgumentNullException()
- {
- // Arrange
- var cacheFilePath = Path.Combine(_cacheDirectory, BranchDevEtl.CacheFileName);
-
- // Act & Assert
- Should.Throw(() => BranchDevEtl.Create(null!, cacheFilePath));
- }
-
- [Fact]
- public void Create_WithEmptyCacheFilePath_ThrowsArgumentException()
- {
- // Arrange
- var mockFactory = Substitute.For();
-
- // Act & Assert
- Should.Throw(() => BranchDevEtl.Create(mockFactory, string.Empty));
- }
-
- [Fact]
- public void Create_WithNonExistentCacheFile_ThrowsFileNotFoundException()
- {
- // Arrange
- var mockFactory = Substitute.For();
- var nonExistentPath = "/nonexistent/path/branch.json.zstd";
-
- // Act & Assert
- Should.Throw(() => BranchDevEtl.Create(mockFactory, nonExistentPath));
- }
-
- [Fact]
- public void DevEtlRegistry_WithNonExistentCacheDirectory_ThrowsDirectoryNotFoundException()
- {
- // Arrange
- var mockFactory = Substitute.For();
- var nonExistentPath = "/nonexistent/cache/directory";
-
- // Act & Assert
- Should.Throw(() => new DevEtlRegistry(mockFactory, nonExistentPath));
- }
-
- [Fact]
- public void DevEtlRegistry_GetAvailableTables_IncludesBranch()
- {
- // Arrange
- if (!Directory.Exists(_cacheDirectory))
- {
- return;
- }
-
- var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory);
-
- // Act
- var tables = registry.GetAvailableTables().ToList();
-
- // Assert
- tables.ShouldContain("Branch");
- }
-}
diff --git a/NEW/tests/JdeScoping.DataSync.Dev.Tests/DevEtlPipelineFactoryTests.cs b/NEW/tests/JdeScoping.DataSync.Dev.Tests/DevEtlPipelineFactoryTests.cs
new file mode 100644
index 0000000..5e0ccc2
--- /dev/null
+++ b/NEW/tests/JdeScoping.DataSync.Dev.Tests/DevEtlPipelineFactoryTests.cs
@@ -0,0 +1,210 @@
+using Dapper;
+using JdeScoping.DataAccess;
+using JdeScoping.DataAccess.Interfaces;
+using JdeScoping.DataSync.Dev.Configuration;
+using JdeScoping.DataSync.Dev.Contracts;
+using JdeScoping.DataSync.Dev.Options;
+using JdeScoping.DataSync.Dev.Services;
+using JdeScoping.DataSync.Etl.Pipeline;
+using Microsoft.Data.SqlClient;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
+using NSubstitute;
+using Shouldly;
+
+namespace JdeScoping.DataSync.Dev.Tests;
+
+///
+/// Tests for DevEtlPipelineFactory.
+///
+public class DevEtlPipelineFactoryTests
+{
+ private readonly IDbConnectionFactory _connectionFactory;
+ private readonly ILogger _logger;
+ private readonly string _cacheDirectory;
+
+ public DevEtlPipelineFactoryTests()
+ {
+ var config = new ConfigurationBuilder()
+ .SetBasePath(Directory.GetCurrentDirectory())
+ .AddJsonFile("appsettings.json", optional: true)
+ .AddEnvironmentVariables()
+ .Build();
+
+ _connectionFactory = new DbConnectionFactory(config, NullLogger.Instance);
+ _logger = NullLogger.Instance;
+ _cacheDirectory = config["DevEtl:CacheDirectory"]
+ ?? Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..", "CACHED_DB_FILES");
+ }
+
+ [Fact]
+ public void Constructor_WithValidConfig_LoadsPipelines()
+ {
+ // Arrange
+ var pipelines = new Dictionary
+ {
+ ["TestTable"] = new(new DevSourceConfig("test.pb.zstd"), new DevDestinationConfig("TestTable"))
+ };
+ var config = new DevPipelinesRoot(null, pipelines);
+
+ // Act
+ var factory = new DevEtlPipelineFactory(_connectionFactory, config, _logger);
+
+ // Assert
+ factory.GetAvailableTables().ShouldContain("TestTable");
+ }
+
+ [Fact]
+ public void GetAvailableTables_Returns21Tables()
+ {
+ // Arrange
+ var factory = CreateFactoryFromConfig();
+
+ // Act
+ var tables = factory.GetAvailableTables().ToList();
+
+ // Assert
+ tables.Count.ShouldBe(21);
+ tables.ShouldContain("Branch");
+ tables.ShouldContain("WorkOrder_Curr");
+ tables.ShouldContain("LotUsage_Curr");
+ }
+
+ [Fact]
+ public void IsVeryLargeTable_ReturnsTrueForVeryLargeTables()
+ {
+ // Arrange
+ var factory = CreateFactoryFromConfig();
+
+ // Act & Assert
+ factory.IsVeryLargeTable("WorkOrderTime_Curr").ShouldBeTrue();
+ factory.IsVeryLargeTable("WorkOrderStep_Curr").ShouldBeTrue();
+ factory.IsVeryLargeTable("LotUsage_Curr").ShouldBeTrue();
+ }
+
+ [Fact]
+ public void IsVeryLargeTable_ReturnsFalseForSmallTables()
+ {
+ // Arrange
+ var factory = CreateFactoryFromConfig();
+
+ // Act & Assert
+ factory.IsVeryLargeTable("Branch").ShouldBeFalse();
+ factory.IsVeryLargeTable("Item").ShouldBeFalse();
+ factory.IsVeryLargeTable("JdeUser").ShouldBeFalse();
+ }
+
+ [Fact]
+ public void GetPipeline_WithValidTable_ReturnsPipeline()
+ {
+ // Arrange
+ var factory = CreateFactoryFromConfig();
+
+ // Skip if cache directory doesn't exist
+ if (!Directory.Exists(_cacheDirectory))
+ return;
+
+ var cacheFilePath = Path.Combine(_cacheDirectory, "branch.pb.zstd");
+ if (!File.Exists(cacheFilePath))
+ return;
+
+ // Act
+ var pipeline = factory.GetPipeline("Branch", _cacheDirectory);
+
+ // Assert
+ pipeline.ShouldNotBeNull();
+ pipeline.PipelineName.ShouldBe("Branch_Dev");
+ }
+
+ [Fact]
+ public void GetPipeline_WithInvalidTable_ThrowsInvalidOperationException()
+ {
+ // Arrange
+ var factory = CreateFactoryFromConfig();
+
+ // Act & Assert
+ Should.Throw(() => factory.GetPipeline("NonExistentTable", _cacheDirectory));
+ }
+
+ [Fact]
+ public void GetPipeline_WithNullTableName_ThrowsArgumentException()
+ {
+ // Arrange
+ var factory = CreateFactoryFromConfig();
+
+ // Act & Assert
+ Should.Throw(() => factory.GetPipeline(null!, _cacheDirectory));
+ }
+
+ [Fact]
+ public void GetPipeline_WithEmptyCacheDirectory_ThrowsArgumentException()
+ {
+ // Arrange
+ var factory = CreateFactoryFromConfig();
+
+ // Act & Assert
+ Should.Throw(() => factory.GetPipeline("Branch", string.Empty));
+ }
+
+ [Fact]
+ public void Constructor_WithNullConnectionFactory_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var config = new DevPipelinesRoot(null, new Dictionary());
+
+ // Act & Assert
+ Should.Throw(() => new DevEtlPipelineFactory(null!, config, _logger));
+ }
+
+ [Fact]
+ public void Constructor_WithNullConfig_ThrowsArgumentNullException()
+ {
+ // Act & Assert
+ Should.Throw(() => new DevEtlPipelineFactory(_connectionFactory, (DevPipelinesRoot)null!, _logger));
+ }
+
+ private DevEtlPipelineFactory CreateFactoryFromConfig()
+ {
+ // Load actual config from JSON file
+ var settings = new DevPipelineSettings
+ {
+ SizeCategories = new SizeCategories
+ {
+ Small = ["Branch", "OrgHierarchy", "WorkCenter", "ProfitCenter"],
+ Medium = ["JdeUser", "FunctionCode", "Item", "RouteMaster"],
+ Large = ["Lot", "MisData", "WorkOrder_Curr", "WorkOrder_Hist", "LotUsage_Hist", "WorkOrderComponent_Hist"],
+ VeryLarge = ["WorkOrderStep_Hist", "WorkOrderComponent_Curr", "WorkOrderRouting", "LotUsage_Curr", "WorkOrderStep_Curr", "WorkOrderTime_Hist", "WorkOrderTime_Curr"]
+ }
+ };
+
+ var pipelines = new Dictionary
+ {
+ ["Branch"] = new(new DevSourceConfig("branch.pb.zstd"), new DevDestinationConfig("Branch")),
+ ["OrgHierarchy"] = new(new DevSourceConfig("orghierarchy.pb.zstd"), new DevDestinationConfig("OrgHierarchy")),
+ ["WorkCenter"] = new(new DevSourceConfig("workcenter.pb.zstd"), new DevDestinationConfig("WorkCenter")),
+ ["ProfitCenter"] = new(new DevSourceConfig("profitcenter.pb.zstd"), new DevDestinationConfig("ProfitCenter")),
+ ["JdeUser"] = new(new DevSourceConfig("jdeuser.pb.zstd"), new DevDestinationConfig("JdeUser")),
+ ["FunctionCode"] = new(new DevSourceConfig("functioncode.pb.zstd"), new DevDestinationConfig("FunctionCode")),
+ ["Item"] = new(new DevSourceConfig("item.pb.zstd"), new DevDestinationConfig("Item")),
+ ["RouteMaster"] = new(new DevSourceConfig("routemaster.pb.zstd"), new DevDestinationConfig("RouteMaster")),
+ ["Lot"] = new(new DevSourceConfig("lot.pb.zstd"), new DevDestinationConfig("Lot")),
+ ["MisData"] = new(new DevSourceConfig("misdata.pb.zstd"), new DevDestinationConfig("MisData")),
+ ["WorkOrder_Curr"] = new(new DevSourceConfig("workorder_curr.pb.zstd"), new DevDestinationConfig("WorkOrder_Curr")),
+ ["WorkOrder_Hist"] = new(new DevSourceConfig("workorder_hist.pb.zstd"), new DevDestinationConfig("WorkOrder_Hist")),
+ ["LotUsage_Curr"] = new(new DevSourceConfig("lotusage_curr.pb.zstd"), new DevDestinationConfig("LotUsage_Curr")),
+ ["LotUsage_Hist"] = new(new DevSourceConfig("lotusage_hist.pb.zstd"), new DevDestinationConfig("LotUsage_Hist")),
+ ["WorkOrderComponent_Curr"] = new(new DevSourceConfig("workordercomponent_curr.pb.zstd"), new DevDestinationConfig("WorkOrderComponent_Curr")),
+ ["WorkOrderComponent_Hist"] = new(new DevSourceConfig("workordercomponent_hist.pb.zstd"), new DevDestinationConfig("WorkOrderComponent_Hist")),
+ ["WorkOrderStep_Curr"] = new(new DevSourceConfig("workorderstep_curr.pb.zstd"), new DevDestinationConfig("WorkOrderStep_Curr")),
+ ["WorkOrderStep_Hist"] = new(new DevSourceConfig("workorderstep_hist.pb.zstd"), new DevDestinationConfig("WorkOrderStep_Hist")),
+ ["WorkOrderTime_Curr"] = new(new DevSourceConfig("workordertime_curr.pb.zstd"), new DevDestinationConfig("WorkOrderTime_Curr")),
+ ["WorkOrderTime_Hist"] = new(new DevSourceConfig("workordertime_hist.pb.zstd"), new DevDestinationConfig("WorkOrderTime_Hist")),
+ ["WorkOrderRouting"] = new(new DevSourceConfig("workorderrouting.pb.zstd"), new DevDestinationConfig("WorkOrderRouting"))
+ };
+
+ var config = new DevPipelinesRoot(settings, pipelines);
+ return new DevEtlPipelineFactory(_connectionFactory, config, _logger);
+ }
+}
diff --git a/NEW/tests/JdeScoping.DataSync.Dev.Tests/DevEtlRegistryTests.cs b/NEW/tests/JdeScoping.DataSync.Dev.Tests/DevEtlRegistryTests.cs
new file mode 100644
index 0000000..c694753
--- /dev/null
+++ b/NEW/tests/JdeScoping.DataSync.Dev.Tests/DevEtlRegistryTests.cs
@@ -0,0 +1,101 @@
+using JdeScoping.DataSync.Dev.Configuration;
+using JdeScoping.DataSync.Dev.Contracts;
+using JdeScoping.DataSync.Dev.Services;
+using JdeScoping.DataSync.Etl.Pipeline;
+using JdeScoping.DataSync.Etl.Results;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using NSubstitute;
+using Shouldly;
+
+namespace JdeScoping.DataSync.Dev.Tests;
+
+///
+/// Tests for DevEtlRegistry.
+///
+public class DevEtlRegistryTests : IDisposable
+{
+ private readonly IDevEtlPipelineFactory _mockFactory;
+ private readonly ILogger _logger;
+ private readonly string _tempDirectory;
+
+ public DevEtlRegistryTests()
+ {
+ _mockFactory = Substitute.For();
+ _logger = NullLogger.Instance;
+
+ // Create a temp directory for tests
+ _tempDirectory = Path.Combine(Path.GetTempPath(), $"DevEtlRegistryTests_{Guid.NewGuid():N}");
+ Directory.CreateDirectory(_tempDirectory);
+ }
+
+ [Fact]
+ public void Constructor_WithValidArguments_Succeeds()
+ {
+ // Arrange
+ _mockFactory.GetAvailableTables().Returns(new[] { "Branch" });
+
+ // Act
+ var registry = new DevEtlRegistry(_mockFactory, _tempDirectory, _logger);
+
+ // Assert
+ registry.ShouldNotBeNull();
+ }
+
+ [Fact]
+ public void Constructor_WithNullFactory_ThrowsArgumentNullException()
+ {
+ // Act & Assert
+ Should.Throw(() => new DevEtlRegistry(null!, _tempDirectory, _logger));
+ }
+
+ [Fact]
+ public void Constructor_WithEmptyCacheDirectory_ThrowsArgumentException()
+ {
+ // Act & Assert
+ Should.Throw(() => new DevEtlRegistry(_mockFactory, string.Empty, _logger));
+ }
+
+ [Fact]
+ public void Constructor_WithNonExistentCacheDirectory_ThrowsDirectoryNotFoundException()
+ {
+ // Arrange
+ var nonExistentPath = "/nonexistent/cache/directory";
+
+ // Act & Assert
+ Should.Throw(() => new DevEtlRegistry(_mockFactory, nonExistentPath, _logger));
+ }
+
+ [Fact]
+ public void GetAvailableTables_DelegatesToFactory()
+ {
+ // Arrange
+ var expectedTables = new[] { "Branch", "Item", "Lot" };
+ _mockFactory.GetAvailableTables().Returns(expectedTables);
+
+ var registry = new DevEtlRegistry(_mockFactory, _tempDirectory, _logger);
+
+ // Act
+ var tables = registry.GetAvailableTables().ToList();
+
+ // Assert
+ tables.ShouldBe(expectedTables);
+ _mockFactory.Received(1).GetAvailableTables();
+ }
+
+ public void Dispose()
+ {
+ // Cleanup temp directory
+ if (Directory.Exists(_tempDirectory))
+ {
+ try
+ {
+ Directory.Delete(_tempDirectory, recursive: true);
+ }
+ catch
+ {
+ // Ignore cleanup errors
+ }
+ }
+ }
+}
diff --git a/NEW/tests/JdeScoping.DataSync.Dev.Tests/FunctionCodeDevEtlTests.cs b/NEW/tests/JdeScoping.DataSync.Dev.Tests/FunctionCodeDevEtlTests.cs
deleted file mode 100644
index 44af0f9..0000000
--- a/NEW/tests/JdeScoping.DataSync.Dev.Tests/FunctionCodeDevEtlTests.cs
+++ /dev/null
@@ -1,96 +0,0 @@
-using Dapper;
-using JdeScoping.DataAccess;
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Dev;
-using Microsoft.Data.SqlClient;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging.Abstractions;
-using NSubstitute;
-using Shouldly;
-
-namespace JdeScoping.DataSync.Dev.Tests;
-
-///
-/// Integration tests for FunctionCode development ETL.
-///
-public class FunctionCodeDevEtlTests : IAsyncLifetime
-{
- private readonly string _connectionString;
- private readonly string _cacheDirectory;
- private readonly IDbConnectionFactory _connectionFactory;
-
- public FunctionCodeDevEtlTests()
- {
- var config = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile("appsettings.json", optional: true)
- .AddEnvironmentVariables()
- .Build();
-
- _connectionString = config.GetConnectionString("LotFinderDB")
- ?? throw new InvalidOperationException("LotFinderDB connection string not configured.");
-
- _cacheDirectory = config["DevEtl:CacheDirectory"]
- ?? Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..", "CACHED_DB_FILES");
-
- _connectionFactory = new DbConnectionFactory(config, NullLogger.Instance);
- }
-
- public async Task InitializeAsync()
- {
- await using var connection = new SqlConnection(_connectionString);
- await connection.OpenAsync();
- await connection.ExecuteAsync("TRUNCATE TABLE dbo.FunctionCode");
- }
-
- public Task DisposeAsync() => Task.CompletedTask;
-
- [Fact]
- public void Create_ReturnsValidPipeline()
- {
- var cacheFilePath = Path.Combine(_cacheDirectory, FunctionCodeDevEtl.CacheFileName);
- if (!File.Exists(cacheFilePath)) return;
-
- var pipeline = FunctionCodeDevEtl.Create(_connectionFactory, cacheFilePath);
-
- pipeline.ShouldNotBeNull();
- pipeline.PipelineName.ShouldBe("FunctionCode_Dev");
- }
-
- [Fact]
- public async Task Execute_LoadsData()
- {
- var cacheFilePath = Path.Combine(_cacheDirectory, FunctionCodeDevEtl.CacheFileName);
- if (!File.Exists(cacheFilePath)) return;
-
- var pipeline = FunctionCodeDevEtl.Create(_connectionFactory, cacheFilePath);
- var result = await pipeline.ExecuteAsync();
-
- result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
- result.TotalRows.ShouldBeGreaterThan(0);
-
- await using var connection = new SqlConnection(_connectionString);
- await connection.OpenAsync();
- var count = await connection.ExecuteScalarAsync("SELECT COUNT(*) FROM dbo.FunctionCode");
- count.ShouldBe((int)result.TotalRows);
- }
-
- [Fact]
- public async Task Registry_RunAsync_LoadsTable()
- {
- if (!Directory.Exists(_cacheDirectory)) return;
-
- var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory);
- var result = await registry.RunAsync("FunctionCode");
-
- result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
- result.TotalRows.ShouldBeGreaterThan(0);
- }
-
- [Fact]
- public void Create_WithNullConnectionFactory_ThrowsArgumentNullException()
- {
- var cacheFilePath = Path.Combine(_cacheDirectory, FunctionCodeDevEtl.CacheFileName);
- Should.Throw(() => FunctionCodeDevEtl.Create(null!, cacheFilePath));
- }
-}
diff --git a/NEW/tests/JdeScoping.DataSync.Dev.Tests/ItemDevEtlTests.cs b/NEW/tests/JdeScoping.DataSync.Dev.Tests/ItemDevEtlTests.cs
deleted file mode 100644
index a4a0b3e..0000000
--- a/NEW/tests/JdeScoping.DataSync.Dev.Tests/ItemDevEtlTests.cs
+++ /dev/null
@@ -1,96 +0,0 @@
-using Dapper;
-using JdeScoping.DataAccess;
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Dev;
-using Microsoft.Data.SqlClient;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging.Abstractions;
-using NSubstitute;
-using Shouldly;
-
-namespace JdeScoping.DataSync.Dev.Tests;
-
-///
-/// Integration tests for Item development ETL.
-///
-public class ItemDevEtlTests : IAsyncLifetime
-{
- private readonly string _connectionString;
- private readonly string _cacheDirectory;
- private readonly IDbConnectionFactory _connectionFactory;
-
- public ItemDevEtlTests()
- {
- var config = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile("appsettings.json", optional: true)
- .AddEnvironmentVariables()
- .Build();
-
- _connectionString = config.GetConnectionString("LotFinderDB")
- ?? throw new InvalidOperationException("LotFinderDB connection string not configured.");
-
- _cacheDirectory = config["DevEtl:CacheDirectory"]
- ?? Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..", "CACHED_DB_FILES");
-
- _connectionFactory = new DbConnectionFactory(config, NullLogger.Instance);
- }
-
- public async Task InitializeAsync()
- {
- await using var connection = new SqlConnection(_connectionString);
- await connection.OpenAsync();
- await connection.ExecuteAsync("TRUNCATE TABLE dbo.Item");
- }
-
- public Task DisposeAsync() => Task.CompletedTask;
-
- [Fact]
- public void Create_ReturnsValidPipeline()
- {
- var cacheFilePath = Path.Combine(_cacheDirectory, ItemDevEtl.CacheFileName);
- if (!File.Exists(cacheFilePath)) return;
-
- var pipeline = ItemDevEtl.Create(_connectionFactory, cacheFilePath);
-
- pipeline.ShouldNotBeNull();
- pipeline.PipelineName.ShouldBe("Item_Dev");
- }
-
- [Fact]
- public async Task Execute_LoadsData()
- {
- var cacheFilePath = Path.Combine(_cacheDirectory, ItemDevEtl.CacheFileName);
- if (!File.Exists(cacheFilePath)) return;
-
- var pipeline = ItemDevEtl.Create(_connectionFactory, cacheFilePath);
- var result = await pipeline.ExecuteAsync();
-
- result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
- result.TotalRows.ShouldBeGreaterThan(0);
-
- await using var connection = new SqlConnection(_connectionString);
- await connection.OpenAsync();
- var count = await connection.ExecuteScalarAsync("SELECT COUNT(*) FROM dbo.Item");
- count.ShouldBe((int)result.TotalRows);
- }
-
- [Fact]
- public async Task Registry_RunAsync_LoadsTable()
- {
- if (!Directory.Exists(_cacheDirectory)) return;
-
- var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory);
- var result = await registry.RunAsync("Item");
-
- result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
- result.TotalRows.ShouldBeGreaterThan(0);
- }
-
- [Fact]
- public void Create_WithNullConnectionFactory_ThrowsArgumentNullException()
- {
- var cacheFilePath = Path.Combine(_cacheDirectory, ItemDevEtl.CacheFileName);
- Should.Throw(() => ItemDevEtl.Create(null!, cacheFilePath));
- }
-}
diff --git a/NEW/tests/JdeScoping.DataSync.Dev.Tests/JdeUserDevEtlTests.cs b/NEW/tests/JdeScoping.DataSync.Dev.Tests/JdeUserDevEtlTests.cs
deleted file mode 100644
index 7b780f0..0000000
--- a/NEW/tests/JdeScoping.DataSync.Dev.Tests/JdeUserDevEtlTests.cs
+++ /dev/null
@@ -1,96 +0,0 @@
-using Dapper;
-using JdeScoping.DataAccess;
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Dev;
-using Microsoft.Data.SqlClient;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging.Abstractions;
-using NSubstitute;
-using Shouldly;
-
-namespace JdeScoping.DataSync.Dev.Tests;
-
-///
-/// Integration tests for JdeUser development ETL.
-///
-public class JdeUserDevEtlTests : IAsyncLifetime
-{
- private readonly string _connectionString;
- private readonly string _cacheDirectory;
- private readonly IDbConnectionFactory _connectionFactory;
-
- public JdeUserDevEtlTests()
- {
- var config = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile("appsettings.json", optional: true)
- .AddEnvironmentVariables()
- .Build();
-
- _connectionString = config.GetConnectionString("LotFinderDB")
- ?? throw new InvalidOperationException("LotFinderDB connection string not configured.");
-
- _cacheDirectory = config["DevEtl:CacheDirectory"]
- ?? Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..", "CACHED_DB_FILES");
-
- _connectionFactory = new DbConnectionFactory(config, NullLogger.Instance);
- }
-
- public async Task InitializeAsync()
- {
- await using var connection = new SqlConnection(_connectionString);
- await connection.OpenAsync();
- await connection.ExecuteAsync("TRUNCATE TABLE dbo.JdeUser");
- }
-
- public Task DisposeAsync() => Task.CompletedTask;
-
- [Fact]
- public void Create_ReturnsValidPipeline()
- {
- var cacheFilePath = Path.Combine(_cacheDirectory, JdeUserDevEtl.CacheFileName);
- if (!File.Exists(cacheFilePath)) return;
-
- var pipeline = JdeUserDevEtl.Create(_connectionFactory, cacheFilePath);
-
- pipeline.ShouldNotBeNull();
- pipeline.PipelineName.ShouldBe("JdeUser_Dev");
- }
-
- [Fact]
- public async Task Execute_LoadsData()
- {
- var cacheFilePath = Path.Combine(_cacheDirectory, JdeUserDevEtl.CacheFileName);
- if (!File.Exists(cacheFilePath)) return;
-
- var pipeline = JdeUserDevEtl.Create(_connectionFactory, cacheFilePath);
- var result = await pipeline.ExecuteAsync();
-
- result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
- result.TotalRows.ShouldBeGreaterThan(0);
-
- await using var connection = new SqlConnection(_connectionString);
- await connection.OpenAsync();
- var count = await connection.ExecuteScalarAsync("SELECT COUNT(*) FROM dbo.JdeUser");
- count.ShouldBe((int)result.TotalRows);
- }
-
- [Fact]
- public async Task Registry_RunAsync_LoadsTable()
- {
- if (!Directory.Exists(_cacheDirectory)) return;
-
- var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory);
- var result = await registry.RunAsync("JdeUser");
-
- result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
- result.TotalRows.ShouldBeGreaterThan(0);
- }
-
- [Fact]
- public void Create_WithNullConnectionFactory_ThrowsArgumentNullException()
- {
- var cacheFilePath = Path.Combine(_cacheDirectory, JdeUserDevEtl.CacheFileName);
- Should.Throw(() => JdeUserDevEtl.Create(null!, cacheFilePath));
- }
-}
diff --git a/NEW/tests/JdeScoping.DataSync.Dev.Tests/OrgHierarchyDevEtlTests.cs b/NEW/tests/JdeScoping.DataSync.Dev.Tests/OrgHierarchyDevEtlTests.cs
deleted file mode 100644
index dc880b6..0000000
--- a/NEW/tests/JdeScoping.DataSync.Dev.Tests/OrgHierarchyDevEtlTests.cs
+++ /dev/null
@@ -1,166 +0,0 @@
-using Dapper;
-using JdeScoping.DataAccess;
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Dev;
-using Microsoft.Data.SqlClient;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging.Abstractions;
-using NSubstitute;
-using Shouldly;
-
-namespace JdeScoping.DataSync.Dev.Tests;
-
-///
-/// Integration tests for OrgHierarchy development ETL.
-/// Requires: Local SQL Server, CACHED_DB_FILES directory with orghierarchy.json.zstd
-///
-public class OrgHierarchyDevEtlTests : IAsyncLifetime
-{
- private readonly string _connectionString;
- private readonly string _cacheDirectory;
- private readonly IDbConnectionFactory _connectionFactory;
-
- public OrgHierarchyDevEtlTests()
- {
- // Load configuration
- var config = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile("appsettings.json", optional: true)
- .AddEnvironmentVariables()
- .Build();
-
- _connectionString = config.GetConnectionString("LotFinderDB")
- ?? throw new InvalidOperationException("LotFinderDB connection string not configured.");
-
- _cacheDirectory = config["DevEtl:CacheDirectory"]
- ?? Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..", "CACHED_DB_FILES");
-
- _connectionFactory = new DbConnectionFactory(config, NullLogger.Instance);
- }
-
- public async Task InitializeAsync()
- {
- // Ensure OrgHierarchy table is empty before test
- await using var connection = new SqlConnection(_connectionString);
- await connection.OpenAsync();
- await connection.ExecuteAsync("TRUNCATE TABLE dbo.OrgHierarchy");
- }
-
- public Task DisposeAsync() => Task.CompletedTask;
-
- [Fact]
- public void Create_ReturnsValidPipeline()
- {
- // Arrange
- var cacheFilePath = Path.Combine(_cacheDirectory, OrgHierarchyDevEtl.CacheFileName);
- if (!File.Exists(cacheFilePath))
- {
- // Skip test if cache file doesn't exist
- return;
- }
-
- // Act
- var pipeline = OrgHierarchyDevEtl.Create(_connectionFactory, cacheFilePath);
-
- // Assert
- pipeline.ShouldNotBeNull();
- pipeline.PipelineName.ShouldBe("OrgHierarchy_Dev");
- }
-
- [Fact]
- public async Task Execute_LoadsOrgHierarchyData()
- {
- // Arrange
- var cacheFilePath = Path.Combine(_cacheDirectory, OrgHierarchyDevEtl.CacheFileName);
- if (!File.Exists(cacheFilePath))
- {
- // Skip test if cache file doesn't exist
- return;
- }
-
- var pipeline = OrgHierarchyDevEtl.Create(_connectionFactory, cacheFilePath);
-
- // Act
- var result = await pipeline.ExecuteAsync();
-
- // Assert
- result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
- result.TotalRows.ShouldBeGreaterThan(0, "Should load at least one row");
-
- // Verify data in database
- await using var connection = new SqlConnection(_connectionString);
- await connection.OpenAsync();
- var count = await connection.ExecuteScalarAsync("SELECT COUNT(*) FROM dbo.OrgHierarchy");
-
- count.ShouldBe((int)result.TotalRows, "Database row count should match pipeline result");
- }
-
- [Fact]
- public async Task Registry_RunAsync_LoadsOrgHierarchy()
- {
- // Arrange
- if (!Directory.Exists(_cacheDirectory))
- {
- // Skip test if cache directory doesn't exist
- return;
- }
-
- var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory);
-
- // Act
- var result = await registry.RunAsync("OrgHierarchy");
-
- // Assert
- result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
- result.TotalRows.ShouldBeGreaterThan(0);
- }
-
- [Fact]
- public void Create_WithNullConnectionFactory_ThrowsArgumentNullException()
- {
- // Arrange
- var cacheFilePath = Path.Combine(_cacheDirectory, OrgHierarchyDevEtl.CacheFileName);
-
- // Act & Assert
- Should.Throw(() => OrgHierarchyDevEtl.Create(null!, cacheFilePath));
- }
-
- [Fact]
- public void Create_WithEmptyCacheFilePath_ThrowsArgumentException()
- {
- // Arrange
- var mockFactory = Substitute.For();
-
- // Act & Assert
- Should.Throw(() => OrgHierarchyDevEtl.Create(mockFactory, string.Empty));
- }
-
- [Fact]
- public void Create_WithNonExistentCacheFile_ThrowsFileNotFoundException()
- {
- // Arrange
- var mockFactory = Substitute.For();
- var nonExistentPath = "/nonexistent/path/orghierarchy.json.zstd";
-
- // Act & Assert
- Should.Throw(() => OrgHierarchyDevEtl.Create(mockFactory, nonExistentPath));
- }
-
- [Fact]
- public void DevEtlRegistry_GetAvailableTables_IncludesOrgHierarchy()
- {
- // Arrange
- if (!Directory.Exists(_cacheDirectory))
- {
- return;
- }
-
- var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory);
-
- // Act
- var tables = registry.GetAvailableTables().ToList();
-
- // Assert
- tables.ShouldContain("OrgHierarchy");
- }
-}
diff --git a/NEW/tests/JdeScoping.DataSync.Dev.Tests/ProfitCenterDevEtlTests.cs b/NEW/tests/JdeScoping.DataSync.Dev.Tests/ProfitCenterDevEtlTests.cs
deleted file mode 100644
index 2cc5c10..0000000
--- a/NEW/tests/JdeScoping.DataSync.Dev.Tests/ProfitCenterDevEtlTests.cs
+++ /dev/null
@@ -1,117 +0,0 @@
-using Dapper;
-using JdeScoping.DataAccess;
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Dev;
-using Microsoft.Data.SqlClient;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging.Abstractions;
-using NSubstitute;
-using Shouldly;
-
-namespace JdeScoping.DataSync.Dev.Tests;
-
-///
-/// Integration tests for ProfitCenter development ETL.
-/// Requires: Local SQL Server, CACHED_DB_FILES directory with profitcenter.json.zstd
-///
-public class ProfitCenterDevEtlTests : IAsyncLifetime
-{
- private readonly string _connectionString;
- private readonly string _cacheDirectory;
- private readonly IDbConnectionFactory _connectionFactory;
-
- public ProfitCenterDevEtlTests()
- {
- var config = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile("appsettings.json", optional: true)
- .AddEnvironmentVariables()
- .Build();
-
- _connectionString = config.GetConnectionString("LotFinderDB")
- ?? throw new InvalidOperationException("LotFinderDB connection string not configured.");
-
- _cacheDirectory = config["DevEtl:CacheDirectory"]
- ?? Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..", "CACHED_DB_FILES");
-
- _connectionFactory = new DbConnectionFactory(config, NullLogger.Instance);
- }
-
- public async Task InitializeAsync()
- {
- await using var connection = new SqlConnection(_connectionString);
- await connection.OpenAsync();
- await connection.ExecuteAsync("TRUNCATE TABLE dbo.ProfitCenter");
- }
-
- public Task DisposeAsync() => Task.CompletedTask;
-
- [Fact]
- public void Create_ReturnsValidPipeline()
- {
- var cacheFilePath = Path.Combine(_cacheDirectory, ProfitCenterDevEtl.CacheFileName);
- if (!File.Exists(cacheFilePath)) return;
-
- var pipeline = ProfitCenterDevEtl.Create(_connectionFactory, cacheFilePath);
-
- pipeline.ShouldNotBeNull();
- pipeline.PipelineName.ShouldBe("ProfitCenter_Dev");
- }
-
- [Fact]
- public async Task Execute_LoadsProfitCenterData()
- {
- var cacheFilePath = Path.Combine(_cacheDirectory, ProfitCenterDevEtl.CacheFileName);
- if (!File.Exists(cacheFilePath)) return;
-
- var pipeline = ProfitCenterDevEtl.Create(_connectionFactory, cacheFilePath);
-
- var result = await pipeline.ExecuteAsync();
-
- result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
- result.TotalRows.ShouldBeGreaterThan(0, "Should load at least one row");
-
- await using var connection = new SqlConnection(_connectionString);
- await connection.OpenAsync();
- var count = await connection.ExecuteScalarAsync("SELECT COUNT(*) FROM dbo.ProfitCenter");
-
- count.ShouldBe((int)result.TotalRows);
- }
-
- [Fact]
- public async Task Registry_RunAsync_LoadsProfitCenter()
- {
- if (!Directory.Exists(_cacheDirectory)) return;
-
- var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory);
- var result = await registry.RunAsync("ProfitCenter");
-
- result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
- result.TotalRows.ShouldBeGreaterThan(0);
- }
-
- [Fact]
- public void Create_WithNullConnectionFactory_ThrowsArgumentNullException()
- {
- var cacheFilePath = Path.Combine(_cacheDirectory, ProfitCenterDevEtl.CacheFileName);
- Should.Throw(() => ProfitCenterDevEtl.Create(null!, cacheFilePath));
- }
-
- [Fact]
- public void Create_WithEmptyCacheFilePath_ThrowsArgumentException()
- {
- var mockFactory = Substitute.For();
- Should.Throw(() => ProfitCenterDevEtl.Create(mockFactory, string.Empty));
- }
-
- [Fact]
- public void DevEtlRegistry_GetAvailableTables_IncludesProfitCenter()
- {
- if (!Directory.Exists(_cacheDirectory)) return;
-
- var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory);
- var tables = registry.GetAvailableTables().ToList();
-
- tables.ShouldContain("ProfitCenter");
- }
-}
diff --git a/NEW/tests/JdeScoping.DataSync.Dev.Tests/RouteMasterDevEtlTests.cs b/NEW/tests/JdeScoping.DataSync.Dev.Tests/RouteMasterDevEtlTests.cs
deleted file mode 100644
index c7fb45c..0000000
--- a/NEW/tests/JdeScoping.DataSync.Dev.Tests/RouteMasterDevEtlTests.cs
+++ /dev/null
@@ -1,96 +0,0 @@
-using Dapper;
-using JdeScoping.DataAccess;
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Dev;
-using Microsoft.Data.SqlClient;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging.Abstractions;
-using NSubstitute;
-using Shouldly;
-
-namespace JdeScoping.DataSync.Dev.Tests;
-
-///
-/// Integration tests for RouteMaster development ETL.
-///
-public class RouteMasterDevEtlTests : IAsyncLifetime
-{
- private readonly string _connectionString;
- private readonly string _cacheDirectory;
- private readonly IDbConnectionFactory _connectionFactory;
-
- public RouteMasterDevEtlTests()
- {
- var config = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile("appsettings.json", optional: true)
- .AddEnvironmentVariables()
- .Build();
-
- _connectionString = config.GetConnectionString("LotFinderDB")
- ?? throw new InvalidOperationException("LotFinderDB connection string not configured.");
-
- _cacheDirectory = config["DevEtl:CacheDirectory"]
- ?? Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..", "CACHED_DB_FILES");
-
- _connectionFactory = new DbConnectionFactory(config, NullLogger.Instance);
- }
-
- public async Task InitializeAsync()
- {
- await using var connection = new SqlConnection(_connectionString);
- await connection.OpenAsync();
- await connection.ExecuteAsync("TRUNCATE TABLE dbo.RouteMaster");
- }
-
- public Task DisposeAsync() => Task.CompletedTask;
-
- [Fact]
- public void Create_ReturnsValidPipeline()
- {
- var cacheFilePath = Path.Combine(_cacheDirectory, RouteMasterDevEtl.CacheFileName);
- if (!File.Exists(cacheFilePath)) return;
-
- var pipeline = RouteMasterDevEtl.Create(_connectionFactory, cacheFilePath);
-
- pipeline.ShouldNotBeNull();
- pipeline.PipelineName.ShouldBe("RouteMaster_Dev");
- }
-
- [Fact]
- public async Task Execute_LoadsData()
- {
- var cacheFilePath = Path.Combine(_cacheDirectory, RouteMasterDevEtl.CacheFileName);
- if (!File.Exists(cacheFilePath)) return;
-
- var pipeline = RouteMasterDevEtl.Create(_connectionFactory, cacheFilePath);
- var result = await pipeline.ExecuteAsync();
-
- result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
- result.TotalRows.ShouldBeGreaterThan(0);
-
- await using var connection = new SqlConnection(_connectionString);
- await connection.OpenAsync();
- var count = await connection.ExecuteScalarAsync("SELECT COUNT(*) FROM dbo.RouteMaster");
- count.ShouldBe((int)result.TotalRows);
- }
-
- [Fact]
- public async Task Registry_RunAsync_LoadsTable()
- {
- if (!Directory.Exists(_cacheDirectory)) return;
-
- var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory);
- var result = await registry.RunAsync("RouteMaster");
-
- result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
- result.TotalRows.ShouldBeGreaterThan(0);
- }
-
- [Fact]
- public void Create_WithNullConnectionFactory_ThrowsArgumentNullException()
- {
- var cacheFilePath = Path.Combine(_cacheDirectory, RouteMasterDevEtl.CacheFileName);
- Should.Throw(() => RouteMasterDevEtl.Create(null!, cacheFilePath));
- }
-}
diff --git a/NEW/tests/JdeScoping.DataSync.Dev.Tests/WorkCenterDevEtlTests.cs b/NEW/tests/JdeScoping.DataSync.Dev.Tests/WorkCenterDevEtlTests.cs
deleted file mode 100644
index 2b59d91..0000000
--- a/NEW/tests/JdeScoping.DataSync.Dev.Tests/WorkCenterDevEtlTests.cs
+++ /dev/null
@@ -1,117 +0,0 @@
-using Dapper;
-using JdeScoping.DataAccess;
-using JdeScoping.DataAccess.Interfaces;
-using JdeScoping.DataSync.Dev;
-using Microsoft.Data.SqlClient;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging.Abstractions;
-using NSubstitute;
-using Shouldly;
-
-namespace JdeScoping.DataSync.Dev.Tests;
-
-///
-/// Integration tests for WorkCenter development ETL.
-/// Requires: Local SQL Server, CACHED_DB_FILES directory with workcenter.json.zstd
-///
-public class WorkCenterDevEtlTests : IAsyncLifetime
-{
- private readonly string _connectionString;
- private readonly string _cacheDirectory;
- private readonly IDbConnectionFactory _connectionFactory;
-
- public WorkCenterDevEtlTests()
- {
- var config = new ConfigurationBuilder()
- .SetBasePath(Directory.GetCurrentDirectory())
- .AddJsonFile("appsettings.json", optional: true)
- .AddEnvironmentVariables()
- .Build();
-
- _connectionString = config.GetConnectionString("LotFinderDB")
- ?? throw new InvalidOperationException("LotFinderDB connection string not configured.");
-
- _cacheDirectory = config["DevEtl:CacheDirectory"]
- ?? Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..", "CACHED_DB_FILES");
-
- _connectionFactory = new DbConnectionFactory(config, NullLogger.Instance);
- }
-
- public async Task InitializeAsync()
- {
- await using var connection = new SqlConnection(_connectionString);
- await connection.OpenAsync();
- await connection.ExecuteAsync("TRUNCATE TABLE dbo.WorkCenter");
- }
-
- public Task DisposeAsync() => Task.CompletedTask;
-
- [Fact]
- public void Create_ReturnsValidPipeline()
- {
- var cacheFilePath = Path.Combine(_cacheDirectory, WorkCenterDevEtl.CacheFileName);
- if (!File.Exists(cacheFilePath)) return;
-
- var pipeline = WorkCenterDevEtl.Create(_connectionFactory, cacheFilePath);
-
- pipeline.ShouldNotBeNull();
- pipeline.PipelineName.ShouldBe("WorkCenter_Dev");
- }
-
- [Fact]
- public async Task Execute_LoadsWorkCenterData()
- {
- var cacheFilePath = Path.Combine(_cacheDirectory, WorkCenterDevEtl.CacheFileName);
- if (!File.Exists(cacheFilePath)) return;
-
- var pipeline = WorkCenterDevEtl.Create(_connectionFactory, cacheFilePath);
-
- var result = await pipeline.ExecuteAsync();
-
- result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
- result.TotalRows.ShouldBeGreaterThan(0, "Should load at least one row");
-
- await using var connection = new SqlConnection(_connectionString);
- await connection.OpenAsync();
- var count = await connection.ExecuteScalarAsync("SELECT COUNT(*) FROM dbo.WorkCenter");
-
- count.ShouldBe((int)result.TotalRows);
- }
-
- [Fact]
- public async Task Registry_RunAsync_LoadsWorkCenter()
- {
- if (!Directory.Exists(_cacheDirectory)) return;
-
- var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory);
- var result = await registry.RunAsync("WorkCenter");
-
- result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
- result.TotalRows.ShouldBeGreaterThan(0);
- }
-
- [Fact]
- public void Create_WithNullConnectionFactory_ThrowsArgumentNullException()
- {
- var cacheFilePath = Path.Combine(_cacheDirectory, WorkCenterDevEtl.CacheFileName);
- Should.Throw(() => WorkCenterDevEtl.Create(null!, cacheFilePath));
- }
-
- [Fact]
- public void Create_WithEmptyCacheFilePath_ThrowsArgumentException()
- {
- var mockFactory = Substitute.For();
- Should.Throw(() => WorkCenterDevEtl.Create(mockFactory, string.Empty));
- }
-
- [Fact]
- public void DevEtlRegistry_GetAvailableTables_IncludesWorkCenter()
- {
- if (!Directory.Exists(_cacheDirectory)) return;
-
- var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory);
- var tables = registry.GetAvailableTables().ToList();
-
- tables.ShouldContain("WorkCenter");
- }
-}
diff --git a/NEW/tests/JdeScoping.DataSync.Tests/Services/SearchRepositoryTests.cs b/NEW/tests/JdeScoping.DataSync.Tests/Services/SearchRepositoryTests.cs
index 80132ce..990e0f8 100644
--- a/NEW/tests/JdeScoping.DataSync.Tests/Services/SearchRepositoryTests.cs
+++ b/NEW/tests/JdeScoping.DataSync.Tests/Services/SearchRepositoryTests.cs
@@ -66,7 +66,7 @@ public class SearchRepositoryTests
var repository = new SearchRepository(_connectionFactory, _logger);
// Assert
- repository.ShouldBeAssignableTo();
+ repository.ShouldBeAssignableTo();
}
[Fact]