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]