From 1618b6664deca5e2d2cddf2f7a02f7c481ca9718 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Wed, 7 Jan 2026 05:04:49 -0500 Subject: [PATCH] refactor: remove unused CMS/JDE repositories and data sources Remove legacy JDE and CMS direct-access code that is no longer used: - Delete ICmsDataSource, IJdeDataSource interfaces - Delete ISearchProcessor, IUpdateProcessor interfaces - Delete IJdeRepository and ICmsRepository (all partials) - Delete JdeRepository and CmsRepository implementations - Delete JdeQueries and CmsQueries - Delete JdeFileDataSource, JdeOracleDataSource - Delete CmsFileDataSource, CmsOracleDataSource - Remove unused methods from LotFinderRepository interfaces - Delete associated unit tests (CmsRepositoryTests, JdeRepositoryTests) All data sync now uses ETL pipelines via DataSync project. --- DATA_SYNC/DataSyncReport.md | 42 +- .../Interfaces/ICmsDataSource.cs | 17 - .../Interfaces/IJdeDataSource.cs | 68 - .../ILotFinderRepository.DataSync.cs | 40 - .../ILotFinderRepository.Lookups.cs | 24 - .../ILotFinderRepository.SearchManagement.cs | 17 - .../Interfaces/ISearchProcessor.cs | 17 - .../Interfaces/IUpdateProcessor.cs | 22 - .../DependencyInjection.cs | 4 - .../Interfaces/ICmsRepository.cs | 19 - .../Interfaces/IJdeRepository.Inventory.cs | 43 - .../IJdeRepository.ReferenceData.cs | 86 - .../Interfaces/IJdeRepository.WorkOrders.cs | 81 - .../Interfaces/IJdeRepository.cs | 9 - .../Options/DataAccessOptions.cs | 5 - .../Queries/CmsQueries.cs | 45 - .../Queries/JdeQueries.Inventory.cs | 113 -- .../Queries/JdeQueries.Lookup.cs | 50 - .../Queries/JdeQueries.Organization.cs | 95 -- .../Queries/JdeQueries.WorkOrders.cs | 234 --- .../Queries/JdeQueries.cs | 9 - .../Queries/LotFinderQueries.DataSync.cs | 84 - .../Queries/LotFinderQueries.Lookups.cs | 35 - .../LotFinderQueries.SearchManagement.cs | 18 - .../Repositories/CmsRepository.cs | 83 - .../Repositories/JdeRepository.Inventory.cs | 94 -- .../JdeRepository.ReferenceData.cs | 191 --- .../Repositories/JdeRepository.WorkOrders.cs | 173 -- .../Repositories/JdeRepository.cs | 113 -- .../LotFinderRepository.DataSync.cs | 164 -- .../LotFinderRepository.Lookups.cs | 78 - .../LotFinderRepository.SearchManagement.cs | 45 - .../Repositories/LotFinderRepository.cs | 54 - NEW/src/JdeScoping.Host/Program.cs | 8 - .../DependencyInjection.cs | 20 - .../JdeScoping.Infrastructure.csproj | 2 - .../Options/DataSourceOptions.cs | 22 - .../Sources/Cms/CmsFileDataSource.cs | 43 - .../Sources/Cms/CmsOracleDataSource.cs | 46 - .../Sources/Jde/JdeFileDataSource.cs | 133 -- .../Sources/Jde/JdeOracleDataSource.cs | 180 --- .../CmsRepositoryTests.cs | 228 --- .../JdeRepositoryTests.cs | 674 -------- .../LotFinderRepositoryTests.cs | 272 +--- .../Models/SearchResultTests.cs | 2 +- ...-06-fluent-excel-mapping-implementation.md | 1432 +++++++++++++++++ .../definitions/workorderstep-curr-15.json | 7 + .../definitions/workorderstep-curr-19.json | 7 + .../definitions/workorderstep-curr-22.json | 7 + .../definitions/workorderstep-curr-3.json | 7 + .../definitions/workorderstep-curr-5.json | 7 + .../definitions/workorderstep-curr-7.json | 7 + 52 files changed, 1497 insertions(+), 3779 deletions(-) delete mode 100644 NEW/src/JdeScoping.Core/Interfaces/ICmsDataSource.cs delete mode 100644 NEW/src/JdeScoping.Core/Interfaces/IJdeDataSource.cs delete mode 100644 NEW/src/JdeScoping.Core/Interfaces/ISearchProcessor.cs delete mode 100644 NEW/src/JdeScoping.Core/Interfaces/IUpdateProcessor.cs delete mode 100644 NEW/src/JdeScoping.DataAccess/Interfaces/ICmsRepository.cs delete mode 100644 NEW/src/JdeScoping.DataAccess/Interfaces/IJdeRepository.Inventory.cs delete mode 100644 NEW/src/JdeScoping.DataAccess/Interfaces/IJdeRepository.ReferenceData.cs delete mode 100644 NEW/src/JdeScoping.DataAccess/Interfaces/IJdeRepository.WorkOrders.cs delete mode 100644 NEW/src/JdeScoping.DataAccess/Interfaces/IJdeRepository.cs delete mode 100644 NEW/src/JdeScoping.DataAccess/Queries/CmsQueries.cs delete mode 100644 NEW/src/JdeScoping.DataAccess/Queries/JdeQueries.Inventory.cs delete mode 100644 NEW/src/JdeScoping.DataAccess/Queries/JdeQueries.Lookup.cs delete mode 100644 NEW/src/JdeScoping.DataAccess/Queries/JdeQueries.Organization.cs delete mode 100644 NEW/src/JdeScoping.DataAccess/Queries/JdeQueries.WorkOrders.cs delete mode 100644 NEW/src/JdeScoping.DataAccess/Queries/JdeQueries.cs delete mode 100644 NEW/src/JdeScoping.DataAccess/Repositories/CmsRepository.cs delete mode 100644 NEW/src/JdeScoping.DataAccess/Repositories/JdeRepository.Inventory.cs delete mode 100644 NEW/src/JdeScoping.DataAccess/Repositories/JdeRepository.ReferenceData.cs delete mode 100644 NEW/src/JdeScoping.DataAccess/Repositories/JdeRepository.WorkOrders.cs delete mode 100644 NEW/src/JdeScoping.DataAccess/Repositories/JdeRepository.cs delete mode 100644 NEW/src/JdeScoping.Infrastructure/Options/DataSourceOptions.cs delete mode 100644 NEW/src/JdeScoping.Infrastructure/Sources/Cms/CmsFileDataSource.cs delete mode 100644 NEW/src/JdeScoping.Infrastructure/Sources/Cms/CmsOracleDataSource.cs delete mode 100644 NEW/src/JdeScoping.Infrastructure/Sources/Jde/JdeFileDataSource.cs delete mode 100644 NEW/src/JdeScoping.Infrastructure/Sources/Jde/JdeOracleDataSource.cs delete mode 100644 NEW/tests/JdeScoping.DataAccess.Tests/CmsRepositoryTests.cs delete mode 100644 NEW/tests/JdeScoping.DataAccess.Tests/JdeRepositoryTests.cs create mode 100644 PLANS/2026-01-06-fluent-excel-mapping-implementation.md create mode 100644 Tools/DbExporter/definitions/workorderstep-curr-15.json create mode 100644 Tools/DbExporter/definitions/workorderstep-curr-19.json create mode 100644 Tools/DbExporter/definitions/workorderstep-curr-22.json create mode 100644 Tools/DbExporter/definitions/workorderstep-curr-3.json create mode 100644 Tools/DbExporter/definitions/workorderstep-curr-5.json create mode 100644 Tools/DbExporter/definitions/workorderstep-curr-7.json diff --git a/DATA_SYNC/DataSyncReport.md b/DATA_SYNC/DataSyncReport.md index 0c146d7..126248e 100644 --- a/DATA_SYNC/DataSyncReport.md +++ b/DATA_SYNC/DataSyncReport.md @@ -39,33 +39,33 @@ This document describes all data synchronization imports from the legacy JDE Sco | # | Import Name | Source | Dest Table | Mass | Daily | Hourly | Filter | Cache File | Notes | |---|-------------|--------|------------|------|-------|--------|--------|------------|-------| -| 1 | WorkOrder | JDE | WorkOrder_Curr | Yes | Yes | Yes | Yes | `workorder_curr.json.zstd` | | -| 2 | LotUsage | JDE | LotUsage_Curr | Yes | Yes | Yes | Yes | `lotusage_curr.json.zstd` | | -| 3 | Item | JDE | Item | Yes | Yes | Yes | Yes | `item.json.zstd` | | -| 4 | Lot | JDE | Lot | Yes | Yes | Yes | Yes | `lot.json.zstd` | | -| 5 | WorkOrderTime | JDE | WorkOrderTime_Curr | Yes | Yes | Yes | Yes | `workordertime_curr.json.zstd` | | -| 6 | WorkOrderComponent | JDE | WorkOrderComponent_Curr | Yes | Yes | Yes | Yes | `workordercomponent_curr.json.zstd` | | -| 7 | WorkOrderStep | JDE | WorkOrderStep_Curr | Yes | Yes | Yes | Yes | `workorderstep_curr.json.zstd` | | -| 8 | WorkOrderRouting | JDE | WorkOrderRouting | Yes | Yes | Yes | Yes | `workorderrouting.json.zstd` | | -| 9 | Branch | JDE | Branch | Yes | Yes | Yes | Yes | `branch.json.zstd` | typeCode='BP' | -| 10 | ProfitCenter | JDE | ProfitCenter | Yes | Yes | Yes | Yes | `profitcenter.json.zstd` | typeCode='I3' | -| 11 | WorkCenter | JDE | WorkCenter | Yes | Yes | Yes | Yes | `workcenter.json.zstd` | typeCode='WC' | +| 1 | WorkOrder | JDE | WorkOrder_Curr | Yes | Yes | Yes | Yes | `workorder_curr.pb.zstd` | | +| 2 | LotUsage | JDE | LotUsage_Curr | Yes | Yes | Yes | Yes | `lotusage_curr.pb.zstd` | | +| 3 | Item | JDE | Item | Yes | Yes | Yes | Yes | `item.pb.zstd` | | +| 4 | Lot | JDE | Lot | Yes | Yes | Yes | Yes | `lot.pb.zstd` | | +| 5 | WorkOrderTime | JDE | WorkOrderTime_Curr | Yes | Yes | Yes | Yes | `workordertime_curr.pb.zstd` | | +| 6 | WorkOrderComponent | JDE | WorkOrderComponent_Curr | Yes | Yes | Yes | Yes | `workordercomponent_curr.pb.zstd` | | +| 7 | WorkOrderStep | JDE | WorkOrderStep_Curr | Yes | Yes | Yes | Yes | `workorderstep_curr.pb.zstd` | | +| 8 | WorkOrderRouting | JDE | WorkOrderRouting | Yes | Yes | Yes | Yes | `workorderrouting.pb.zstd` | | +| 9 | Branch | JDE | Branch | Yes | Yes | Yes | Yes | `branch.pb.zstd` | typeCode='BP' | +| 10 | ProfitCenter | JDE | ProfitCenter | Yes | Yes | Yes | Yes | `profitcenter.pb.zstd` | typeCode='I3' | +| 11 | WorkCenter | JDE | WorkCenter | Yes | Yes | Yes | Yes | `workcenter.pb.zstd` | typeCode='WC' | | 12 | StatusCode | JDE | StatusCode | Yes | Yes | Yes | Yes | *(none)* | GIW connection | -| 13 | JdeUser | JDE | JdeUser | Yes | Yes | Yes | No | `jdeuser.json.zstd` | Same query both | -| 14 | OrgHierarchy | JDE | OrgHierarchy | Yes | Yes | Yes | Yes | `orghierarchy.json.zstd` | | -| 15 | RouteMaster | JDE | RouteMaster | Yes | Yes | Yes | Yes | `routemaster.json.zstd` | | -| 16 | FunctionCode | JDE | FunctionCode | Yes | Yes | Yes | No | `functioncode.json.zstd` | Always full reload | -| 17 | MisData | CMS | MisData | Yes | Yes | No | Yes | `misdata.json.zstd` | Hourly disabled | +| 13 | JdeUser | JDE | JdeUser | Yes | Yes | Yes | No | `jdeuser.pb.zstd` | Same query both | +| 14 | OrgHierarchy | JDE | OrgHierarchy | Yes | Yes | Yes | Yes | `orghierarchy.pb.zstd` | | +| 15 | RouteMaster | JDE | RouteMaster | Yes | Yes | Yes | Yes | `routemaster.pb.zstd` | | +| 16 | FunctionCode | JDE | FunctionCode | Yes | Yes | Yes | No | `functioncode.pb.zstd` | Always full reload | +| 17 | MisData | CMS | MisData | Yes | Yes | No | Yes | `misdata.pb.zstd` | Hourly disabled | ### Archive Syncs (5) - ALL DISABLED | # | Import Name | Source | Dest Table | Mass | Daily | Hourly | Filter | Cache File | Notes | |---|-------------|--------|------------|------|-------|--------|--------|------------|-------| -| 18 | WorkOrder_Archive | JDE | WorkOrder_Hist | No | No | No | No | `workorder_hist.json.zstd` | DISABLED | -| 19 | LotUsage_Archive | JDE | LotUsage_Hist | No | No | No | No | `lotusage_hist.json.zstd` | DISABLED | -| 20 | WorkOrderTime_Archive | JDE | WorkOrderTime_Hist | No | No | No | No | `workordertime_hist.json.zstd` | DISABLED | -| 21 | WorkOrderComponent_Archive | JDE | WorkOrderComponent_Hist | No | No | No | No | `workordercomponent_hist.json.zstd` | DISABLED | -| 22 | WorkOrderStep_Archive | JDE | WorkOrderStep_Hist | No | No | No | No | `workorderstep_hist.json.zstd` | DISABLED | +| 18 | WorkOrder_Archive | JDE | WorkOrder_Hist | No | No | No | No | `workorder_hist.pb.zstd` | DISABLED | +| 19 | LotUsage_Archive | JDE | LotUsage_Hist | No | No | No | No | `lotusage_hist.pb.zstd` | DISABLED | +| 20 | WorkOrderTime_Archive | JDE | WorkOrderTime_Hist | No | No | No | No | `workordertime_hist.pb.zstd` | DISABLED | +| 21 | WorkOrderComponent_Archive | JDE | WorkOrderComponent_Hist | No | No | No | No | `workordercomponent_hist.pb.zstd` | DISABLED | +| 22 | WorkOrderStep_Archive | JDE | WorkOrderStep_Hist | No | No | No | No | `workorderstep_hist.pb.zstd` | DISABLED | **Cache File Location:** `CACHED_DB_FILES/` diff --git a/NEW/src/JdeScoping.Core/Interfaces/ICmsDataSource.cs b/NEW/src/JdeScoping.Core/Interfaces/ICmsDataSource.cs deleted file mode 100644 index da32249..0000000 --- a/NEW/src/JdeScoping.Core/Interfaces/ICmsDataSource.cs +++ /dev/null @@ -1,17 +0,0 @@ -using JdeScoping.Core.Models; -using JdeScoping.Core.Models.Quality; - -namespace JdeScoping.Core.Interfaces; - -/// -/// Interface for fetching data from CMS source system. -/// -public interface ICmsDataSource -{ - /// - /// Gets MIS data from CMS as an async stream. - /// - /// Minimum update timestamp for incremental fetch. Null for full fetch. - /// Cancellation token. - IAsyncEnumerable GetMisDataAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default); -} diff --git a/NEW/src/JdeScoping.Core/Interfaces/IJdeDataSource.cs b/NEW/src/JdeScoping.Core/Interfaces/IJdeDataSource.cs deleted file mode 100644 index 1ee2c1a..0000000 --- a/NEW/src/JdeScoping.Core/Interfaces/IJdeDataSource.cs +++ /dev/null @@ -1,68 +0,0 @@ -using JdeScoping.Core.Models; -using JdeScoping.Core.Models.Inventory; -using JdeScoping.Core.Models.Organization; -using JdeScoping.Core.Models.WorkOrders; - -namespace JdeScoping.Core.Interfaces; - -/// -/// Interface for fetching data from JDE (JD Edwards) source system. -/// -public interface IJdeDataSource -{ - /// - /// Gets work orders from JDE as an async stream. - /// - /// Minimum update timestamp for incremental fetch. Null for full fetch. - /// Cancellation token. - IAsyncEnumerable GetWorkOrdersAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default); - - /// - /// Gets lot usage records from JDE as an async stream. - /// - /// Minimum update timestamp for incremental fetch. Null for full fetch. - /// Cancellation token. - IAsyncEnumerable GetLotUsagesAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default); - - /// - /// Gets lots from JDE as an async stream. - /// - /// Minimum update timestamp for incremental fetch. Null for full fetch. - /// Cancellation token. - IAsyncEnumerable GetLotsAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default); - - /// - /// Gets items from JDE as an async stream. - /// - /// Minimum update timestamp for incremental fetch. Null for full fetch. - /// Cancellation token. - IAsyncEnumerable GetItemsAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default); - - /// - /// Gets work centers from JDE as an async stream. - /// - /// Minimum update timestamp for incremental fetch. Null for full fetch. - /// Cancellation token. - IAsyncEnumerable GetWorkCentersAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default); - - /// - /// Gets profit centers from JDE as an async stream. - /// - /// Minimum update timestamp for incremental fetch. Null for full fetch. - /// Cancellation token. - IAsyncEnumerable GetProfitCentersAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default); - - /// - /// Gets JDE users as an async stream. - /// - /// Minimum update timestamp for incremental fetch. Null for full fetch. - /// Cancellation token. - IAsyncEnumerable GetUsersAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default); - - /// - /// Gets branches from JDE as an async stream. - /// - /// Minimum update timestamp for incremental fetch. Null for full fetch. - /// Cancellation token. - IAsyncEnumerable GetBranchesAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default); -} diff --git a/NEW/src/JdeScoping.Core/Interfaces/ILotFinderRepository.DataSync.cs b/NEW/src/JdeScoping.Core/Interfaces/ILotFinderRepository.DataSync.cs index 980755f..4d3a369 100644 --- a/NEW/src/JdeScoping.Core/Interfaces/ILotFinderRepository.DataSync.cs +++ b/NEW/src/JdeScoping.Core/Interfaces/ILotFinderRepository.DataSync.cs @@ -13,44 +13,4 @@ public partial interface ILotFinderRepository /// Cancellation token. /// Latest data updates. Task> GetLastDataUpdatesAsync(CancellationToken ct = default); - - /// - /// Gets table schema specification for dynamic SQL generation. - /// - /// Table name. - /// Cancellation token. - /// Table specification with columns and primary key. - Task GetTableSpecAsync(string tableName, CancellationToken ct = default); - - /// - /// Rebuilds all indices on a table with fillfactor of 95. - /// Table name is validated against whitelist for SQL injection prevention. - /// - /// Table name (must be in whitelist). - /// Cancellation token. - /// Thrown if table name is not in whitelist. - Task RebuildIndicesAsync(string tableName, CancellationToken ct = default); - - /// - /// Post-processes imported MIS data to set obsolete dates. - /// - /// Cancellation token. - Task PostProcessMisDataAsync(CancellationToken ct = default); - - /// - /// Performs bulk insert of records into a table. - /// - /// Record type. - /// Target table name. - /// Records to insert. - /// Cancellation token. - /// Number of records inserted. - Task BulkInsertAsync(string tableName, IEnumerable records, CancellationToken ct = default); - - /// - /// Truncates a table, removing all records. - /// - /// Table name. - /// Cancellation token. - Task TruncateTableAsync(string tableName, CancellationToken ct = default); } diff --git a/NEW/src/JdeScoping.Core/Interfaces/ILotFinderRepository.Lookups.cs b/NEW/src/JdeScoping.Core/Interfaces/ILotFinderRepository.Lookups.cs index 072b1f3..c4932c6 100644 --- a/NEW/src/JdeScoping.Core/Interfaces/ILotFinderRepository.Lookups.cs +++ b/NEW/src/JdeScoping.Core/Interfaces/ILotFinderRepository.Lookups.cs @@ -43,14 +43,6 @@ public partial interface ILotFinderRepository /// Top 25 matching work centers. Task> SearchWorkCentersAsync(string filter, CancellationToken ct = default); - /// - /// Looks up work centers by codes. - /// - /// Work center codes to match. - /// Cancellation token. - /// Matching work centers. - Task> LookupWorkCentersAsync(List codes, CancellationToken ct = default); - /// /// Searches profit centers by code or description. /// @@ -59,14 +51,6 @@ public partial interface ILotFinderRepository /// Top 25 matching profit centers. Task> SearchProfitCentersAsync(string filter, CancellationToken ct = default); - /// - /// Looks up profit centers by codes. - /// - /// Profit center codes to match. - /// Cancellation token. - /// Matching profit centers. - Task> LookupProfitCentersAsync(List codes, CancellationToken ct = default); - /// /// Searches users by user ID, full name, or address number. /// @@ -75,14 +59,6 @@ public partial interface ILotFinderRepository /// Top 25 matching users. Task> SearchUsersAsync(string filter, CancellationToken ct = default); - /// - /// Looks up users by user IDs or address numbers. - /// - /// User IDs or address numbers to match. - /// Cancellation token. - /// Matching users. - Task> LookupUsersAsync(List userIds, CancellationToken ct = default); - /// /// Looks up lots by lot number and item number. /// diff --git a/NEW/src/JdeScoping.Core/Interfaces/ILotFinderRepository.SearchManagement.cs b/NEW/src/JdeScoping.Core/Interfaces/ILotFinderRepository.SearchManagement.cs index b6a0600..14de87c 100644 --- a/NEW/src/JdeScoping.Core/Interfaces/ILotFinderRepository.SearchManagement.cs +++ b/NEW/src/JdeScoping.Core/Interfaces/ILotFinderRepository.SearchManagement.cs @@ -1,4 +1,3 @@ -using JdeScoping.Core.Models.Enums; using JdeScoping.Core.Models.Search; namespace JdeScoping.Core.Interfaces; @@ -46,20 +45,4 @@ public partial interface ILotFinderRepository /// Cancellation token. /// Generated search ID. Task SubmitSearchAsync(Search search, CancellationToken ct = default); - - /// - /// Updates the status of a search. - /// - /// Search ID. - /// New status. - /// Cancellation token. - Task UpdateSearchStatusAsync(int id, SearchStatus status, CancellationToken ct = default); - - /// - /// Stores the Excel results for a completed search. - /// - /// Search ID. - /// Excel file bytes. - /// Cancellation token. - Task UpdateSearchResultsAsync(int id, byte[] results, CancellationToken ct = default); } diff --git a/NEW/src/JdeScoping.Core/Interfaces/ISearchProcessor.cs b/NEW/src/JdeScoping.Core/Interfaces/ISearchProcessor.cs deleted file mode 100644 index c4e5e69..0000000 --- a/NEW/src/JdeScoping.Core/Interfaces/ISearchProcessor.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace JdeScoping.Core.Interfaces; - -/// -/// Processor for executing user search requests. -/// -public interface ISearchProcessor -{ - /// - /// Processes a queued search request. - /// - Task ProcessSearchAsync(int searchId, CancellationToken cancellationToken = default); - - /// - /// Gets the next pending search to process. - /// - Task GetNextPendingSearchAsync(CancellationToken cancellationToken = default); -} diff --git a/NEW/src/JdeScoping.Core/Interfaces/IUpdateProcessor.cs b/NEW/src/JdeScoping.Core/Interfaces/IUpdateProcessor.cs deleted file mode 100644 index 22f79e6..0000000 --- a/NEW/src/JdeScoping.Core/Interfaces/IUpdateProcessor.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace JdeScoping.Core.Interfaces; - -/// -/// Processor for data synchronization from JDE/CMS to local cache. -/// -public interface IUpdateProcessor -{ - /// - /// Runs a mass (full) data refresh. - /// - Task RunMassRefreshAsync(CancellationToken cancellationToken = default); - - /// - /// Runs a daily incremental data refresh. - /// - Task RunDailyRefreshAsync(CancellationToken cancellationToken = default); - - /// - /// Runs an hourly data refresh. - /// - Task RunHourlyRefreshAsync(CancellationToken cancellationToken = default); -} diff --git a/NEW/src/JdeScoping.DataAccess/DependencyInjection.cs b/NEW/src/JdeScoping.DataAccess/DependencyInjection.cs index eb0f0c8..618b246 100644 --- a/NEW/src/JdeScoping.DataAccess/DependencyInjection.cs +++ b/NEW/src/JdeScoping.DataAccess/DependencyInjection.cs @@ -38,8 +38,6 @@ public static class DataAccessDependencyInjection // Register repositories as scoped (per-request lifetime) services.AddScoped(); - services.AddScoped(); - services.AddScoped(); // Register SqlKata compiler (singleton, thread-safe) services.AddSingleton(); @@ -74,8 +72,6 @@ public static class DataAccessDependencyInjection // Register repositories as scoped (per-request lifetime) services.AddScoped(); - services.AddScoped(); - services.AddScoped(); return services; } diff --git a/NEW/src/JdeScoping.DataAccess/Interfaces/ICmsRepository.cs b/NEW/src/JdeScoping.DataAccess/Interfaces/ICmsRepository.cs deleted file mode 100644 index 2dd4943..0000000 --- a/NEW/src/JdeScoping.DataAccess/Interfaces/ICmsRepository.cs +++ /dev/null @@ -1,19 +0,0 @@ -using JdeScoping.Core.Models; -using JdeScoping.Core.Models.Quality; - -namespace JdeScoping.DataAccess.Interfaces; - -/// -/// Repository for accessing CMS Oracle database. -/// -public interface ICmsRepository -{ - /// - /// Gets Manufacturing Information System (MIS) data from CMS database. - /// Uses MisDataTimeoutSeconds timeout due to complex 10-table JOIN. - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming MIS data records. - IAsyncEnumerable GetMisDataAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); -} diff --git a/NEW/src/JdeScoping.DataAccess/Interfaces/IJdeRepository.Inventory.cs b/NEW/src/JdeScoping.DataAccess/Interfaces/IJdeRepository.Inventory.cs deleted file mode 100644 index 594b04b..0000000 --- a/NEW/src/JdeScoping.DataAccess/Interfaces/IJdeRepository.Inventory.cs +++ /dev/null @@ -1,43 +0,0 @@ -using JdeScoping.Core.Models.Inventory; - -namespace JdeScoping.DataAccess.Interfaces; - -/// -/// Inventory (lots) operations for JDE Oracle repository. -/// -public partial interface IJdeRepository -{ - /// - /// Gets lot master data from production schema, optionally filtered by last update. - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming lots. - IAsyncEnumerable GetLotsAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); - - /// - /// Gets lot usage (cardex) transactions from production schema, optionally filtered by last update. - /// Uses special LotUsageTimeoutSeconds timeout due to large dataset. - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming lot usages. - IAsyncEnumerable GetLotUsagesAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); - - /// - /// Gets lot usage transactions from archive schema. - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming archived lot usages. - IAsyncEnumerable GetLotUsagesArchiveAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); - - /// - /// Gets lot location tracking from JDE Stage view. - /// Uses JDE Stage connection. - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming lot locations. - IAsyncEnumerable GetLotLocationsAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); -} diff --git a/NEW/src/JdeScoping.DataAccess/Interfaces/IJdeRepository.ReferenceData.cs b/NEW/src/JdeScoping.DataAccess/Interfaces/IJdeRepository.ReferenceData.cs deleted file mode 100644 index 9fa329e..0000000 --- a/NEW/src/JdeScoping.DataAccess/Interfaces/IJdeRepository.ReferenceData.cs +++ /dev/null @@ -1,86 +0,0 @@ -using JdeScoping.Core.Models; -using JdeScoping.Core.Models.Inventory; -using JdeScoping.Core.Models.Lookup; -using JdeScoping.Core.Models.Organization; - -namespace JdeScoping.DataAccess.Interfaces; - -/// -/// Reference data operations for JDE Oracle repository. -/// -public partial interface IJdeRepository -{ - /// - /// Gets item master data from production schema, optionally filtered by last update. - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming items. - IAsyncEnumerable GetItemsAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); - - /// - /// Gets user/operator data from production schema. - /// Note: Incremental filtering not supported for users (full sync always). - /// - /// Ignored (full sync always). - /// Cancellation token. - /// Streaming users. - IAsyncEnumerable GetUsersAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); - - /// - /// Gets branch business units from production schema (type code 'BP'). - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming branches. - IAsyncEnumerable GetBranchesAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); - - /// - /// Gets profit center business units from production schema (type code 'I3'). - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming profit centers. - IAsyncEnumerable GetProfitCentersAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); - - /// - /// Gets work center business units from production schema (type code 'WC'). - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming work centers. - IAsyncEnumerable GetWorkCentersAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); - - /// - /// Gets status codes from JDE Stage view. - /// Uses JDE Stage connection. - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming status codes. - IAsyncEnumerable GetStatusCodesAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); - - /// - /// Gets function codes from production schema. - /// Note: Does not support incremental filtering (full sync always). - /// - /// Cancellation token. - /// Streaming function codes. - IAsyncEnumerable GetFunctionCodesAsync(CancellationToken ct = default); - - /// - /// Gets organization hierarchy (work center to profit center mapping) from production schema. - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming org hierarchy records. - IAsyncEnumerable GetOrgHierarchyAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); - - /// - /// Gets item routing master data from production schema. - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming route masters. - IAsyncEnumerable GetRouteMastersAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); -} diff --git a/NEW/src/JdeScoping.DataAccess/Interfaces/IJdeRepository.WorkOrders.cs b/NEW/src/JdeScoping.DataAccess/Interfaces/IJdeRepository.WorkOrders.cs deleted file mode 100644 index 0289375..0000000 --- a/NEW/src/JdeScoping.DataAccess/Interfaces/IJdeRepository.WorkOrders.cs +++ /dev/null @@ -1,81 +0,0 @@ -using JdeScoping.Core.Models.WorkOrders; - -namespace JdeScoping.DataAccess.Interfaces; - -/// -/// Work order operations for JDE Oracle repository. -/// -public partial interface IJdeRepository -{ - /// - /// Gets work orders from production schema, optionally filtered by last update. - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming work orders. - IAsyncEnumerable GetWorkOrdersAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); - - /// - /// Gets work orders from archive schema. - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming archived work orders. - IAsyncEnumerable GetWorkOrdersArchiveAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); - - /// - /// Gets work order steps from production schema, optionally filtered by last update. - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming work order steps. - IAsyncEnumerable GetWorkOrderStepsAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); - - /// - /// Gets work order steps from archive schema. - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming archived work order steps. - IAsyncEnumerable GetWorkOrderStepsArchiveAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); - - /// - /// Gets work order time transactions from production schema, optionally filtered by last update. - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming work order times. - IAsyncEnumerable GetWorkOrderTimesAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); - - /// - /// Gets work order time transactions from archive schema. - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming archived work order times. - IAsyncEnumerable GetWorkOrderTimesArchiveAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); - - /// - /// Gets work order routing transactions from production schema, optionally filtered by last update. - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming work order routings. - IAsyncEnumerable GetWorkOrderRoutingsAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); - - /// - /// Gets work order component usage from production schema, optionally filtered by last update. - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming work order components. - IAsyncEnumerable GetWorkOrderComponentsAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); - - /// - /// Gets work order component usage from archive schema. - /// - /// Optional cutoff for incremental sync. - /// Cancellation token. - /// Streaming archived work order components. - IAsyncEnumerable GetWorkOrderComponentsArchiveAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default); -} diff --git a/NEW/src/JdeScoping.DataAccess/Interfaces/IJdeRepository.cs b/NEW/src/JdeScoping.DataAccess/Interfaces/IJdeRepository.cs deleted file mode 100644 index aea0f0c..0000000 --- a/NEW/src/JdeScoping.DataAccess/Interfaces/IJdeRepository.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace JdeScoping.DataAccess.Interfaces; - -/// -/// Repository for accessing JDE Oracle database. -/// All methods return IAsyncEnumerable for memory-efficient streaming. -/// -public partial interface IJdeRepository -{ -} diff --git a/NEW/src/JdeScoping.DataAccess/Options/DataAccessOptions.cs b/NEW/src/JdeScoping.DataAccess/Options/DataAccessOptions.cs index 39d3c02..2b18f01 100644 --- a/NEW/src/JdeScoping.DataAccess/Options/DataAccessOptions.cs +++ b/NEW/src/JdeScoping.DataAccess/Options/DataAccessOptions.cs @@ -25,11 +25,6 @@ public class DataAccessOptions /// public int MisDataTimeoutSeconds { get; set; } = 60000; - /// - /// Timeout for index rebuild operations in seconds. - /// - public int RebuildIndexTimeoutSeconds { get; set; } = 600; - /// /// JDE production schema name (e.g., PRODDTA). /// diff --git a/NEW/src/JdeScoping.DataAccess/Queries/CmsQueries.cs b/NEW/src/JdeScoping.DataAccess/Queries/CmsQueries.cs deleted file mode 100644 index 3b923aa..0000000 --- a/NEW/src/JdeScoping.DataAccess/Queries/CmsQueries.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace JdeScoping.DataAccess.Queries; - -/// -/// SQL query constants for the CMS Oracle database. -/// -public static class CmsQueries -{ - /// - /// Gets Manufacturing Information System (MIS) data from CMS database. - /// Complex 10-table JOIN through CMS schema (INFODBA). - /// - public const string SqlGetMisData = @" - SELECT DISTINCT - mis.P_PART_NUMBER AS ItemNumber, - mis.P_OPERATION_NUMBER AS SequenceNumber, - item.PITEM_ID AS MISNumber, - itemrev.PITEM_REVISION_ID AS RevID, - TRIM(mis.P_SITE) AS BranchCode, - zim_test_details.P_SEQ_NUMBER AS CharNumber, - zim_test_details.P_TEST_DESC AS TestDescription, - zim_test_details.P_SAMPL_TYPE AS SamplingType, - zim_test_details.P_SAMPL_VALUE AS SamplingValue, - zim_test_details.P_TOOLS AS ToolsGauges, - zim_test_details.P_WORK_INTR AS WorkInstructions, - Status.PNAME AS Status, - Status.PDATE_RELEASED AS ReleaseDate - FROM INFODBA.PITEM item - INNER JOIN INFODBA.PITEMREVISION itemrev ON (item.PUID = itemrev.RITEMS_TAGU) - INNER JOIN INFODBA.PRELEASE_STATUS_LIST listing ON (itemrev.PUID = listing.PUID) - INNER JOIN INFODBA.PRELEASESTATUS Status ON (listing.PVALU_0 = Status.PUID) - INNER JOIN INFODBA.PIMANRELATION imanrel ON (itemrev.PUID = imanrel.RPRIMARY_OBJECTU) - INNER JOIN INFODBA.PFORM form ON (imanrel.RSECONDARY_OBJECTU = form.PUID) - INNER JOIN INFODBA.PZIMMERMISDETAILS zim_mis ON (form.RDATA_FILEU = zim_mis.PUID) - INNER JOIN INFODBA.P_TEST_DETAILS test_details ON (zim_mis.PUID = test_details.PUID) - INNER JOIN INFODBA.P_PART_ASSOCIATION ppa ON (ppa.PUID = test_details.PUID) - INNER JOIN INFODBA.PMISDATAOBJECT mis ON (mis.PUID = ppa.PVALU_0) - INNER JOIN INFODBA.PZIMTESTDETAILS zim_test_details ON (test_details.PVALU_0 = zim_test_details.PUID) - WHERE Status.PNAME IN ('Current', 'BackLevel')"; - - /// - /// Gets MIS data updated since specified date from CMS database. - /// - public const string SqlGetMisDataFiltered = SqlGetMisData + @" - AND Status.PDATE_RELEASED >= :lastUpdateDT"; -} diff --git a/NEW/src/JdeScoping.DataAccess/Queries/JdeQueries.Inventory.cs b/NEW/src/JdeScoping.DataAccess/Queries/JdeQueries.Inventory.cs deleted file mode 100644 index f4c7cbf..0000000 --- a/NEW/src/JdeScoping.DataAccess/Queries/JdeQueries.Inventory.cs +++ /dev/null @@ -1,113 +0,0 @@ -namespace JdeScoping.DataAccess.Queries; - -/// -/// Inventory related SQL queries (Lots, Lot Usages, Lot Locations, Items) -/// -public static partial class JdeQueries -{ - /// - /// Gets all lots from production schema. - /// - public const string SqlGetLots = @" - SELECT TRIM(lot.IOLOTN) AS LotNumber, - TRIM(lot.IOMCU) AS BranchCode, - lot.IOITM AS ShortItemNumber, - TRIM(lot.IOLITM) AS ItemNumber, - lot.IOVEND AS SupplierCode, - lot.IOLOTS AS StatusCode, - TRIM(lot.IOLOT1) AS Memo1, - TRIM(lot.IOLOT2) AS Memo2, - TRIM(lot.IOLOT3) AS Memo3, - lot.IOUPMJ AS LastUpdateDate, - lot.IOTDAY AS LastUpdateTime - FROM {ProductionSchema}.F4108 lot - WHERE TRIM(lot.IOLOTN) IS NOT NULL AND - TRIM(lot.IOMCU) IS NOT NULL"; - - /// - /// Gets lots updated since specified date from production schema. - /// - public const string SqlGetLotsFiltered = SqlGetLots + @" - AND (lot.IOUPMJ > :dateUpdated OR - (lot.IOUPMJ = :dateUpdated AND lot.IOTDAY >= :timeUpdated))"; - - /// - /// Gets all lot usages (cardex) from production schema. - /// - public const string SqlGetLotUsages = @" - SELECT lu.ILUKID AS UniqueId, - lu.ILDOCO AS WorkOrderNumber, - TRIM(lu.ILLOTN) AS LotNumber, - TRIM(lu.ILMCU) AS BranchCode, - lu.ILITM AS ShortItemNumber, - lu.ILTRQT AS Quantity, - lu.ILTRDJ AS LastUpdateDate, - lu.ILTDAY AS LastUpdateTime - FROM {ProductionSchema}.F4111 lu - WHERE lu.ILDCT = 'IM' AND - TRIM(lu.ILLOTN) IS NOT NULL"; - - /// - /// Gets lot usages updated since specified date from production schema. - /// - public const string SqlGetLotUsagesFiltered = SqlGetLotUsages + @" - AND (lu.ILTRDJ > :dateUpdated OR - (lu.ILTRDJ = :dateUpdated AND lu.ILTDAY >= :timeUpdated))"; - - /// - /// Gets all lot usages from archive schema. - /// - public const string SqlGetLotUsagesArchive = @" - SELECT lu.ILUKID AS UniqueId, - lu.ILDOCO AS WorkOrderNumber, - TRIM(lu.ILLOTN) AS LotNumber, - TRIM(lu.ILMCU) AS BranchCode, - lu.ILITM AS ShortItemNumber, - lu.ILTRQT AS Quantity, - lu.ILTRDJ AS LastUpdateDate, - lu.ILTDAY AS LastUpdateTime - FROM {ArchiveSchema}.F4111 lu - WHERE lu.ILDCT = 'IM' AND - TRIM(lu.ILLOTN) IS NOT NULL"; - - /// - /// Gets all lot locations from production schema. - /// - public const string SqlGetLotLocations = @" - SELECT TRIM(il.LILOTN) AS LotNumber, - il.LIITM AS ShortItemNumber, - TRIM(il.LIMCU) AS BranchCode, - COALESCE(TRIM(il.LILOCN), ' ') AS Location, - il.LIUPMJ AS LastUpdateDate, - il.LITDAY AS LastUpdateTime - FROM {ProductionSchema}.F41021 il - WHERE TRIM(il.LILOTN) IS NOT NULL"; - - /// - /// Gets lot locations updated since specified date from production schema. - /// - public const string SqlGetLotLocationsFiltered = SqlGetLotLocations + @" - AND (il.LIUPMJ > :dateUpdated OR - (il.LIUPMJ = :dateUpdated AND il.LITDAY >= :timeUpdated))"; - - /// - /// Gets all items from production schema. - /// - public const string SqlGetItems = @" - SELECT pn.IMITM AS ShortItemNumber, - TRIM(pn.IMLITM) AS ItemNumber, - TRIM(pn.IMDSC1) AS Description, - TRIM(pn.IMPRP4) AS PlanningFamily, - TRIM(pn.IMSTKT) AS StockingType, - pn.IMUPMJ AS LastUpdateDate, - pn.IMTDAY AS LastUpdateTime - FROM {ProductionSchema}.F4101 pn - WHERE TRIM(pn.IMLITM) IS NOT NULL"; - - /// - /// Gets items updated since specified date from production schema. - /// - public const string SqlGetItemsFiltered = SqlGetItems + @" - AND (pn.IMUPMJ > :dateUpdated OR - (pn.IMUPMJ = :dateUpdated AND pn.IMTDAY >= :timeUpdated))"; -} diff --git a/NEW/src/JdeScoping.DataAccess/Queries/JdeQueries.Lookup.cs b/NEW/src/JdeScoping.DataAccess/Queries/JdeQueries.Lookup.cs deleted file mode 100644 index 8d001f6..0000000 --- a/NEW/src/JdeScoping.DataAccess/Queries/JdeQueries.Lookup.cs +++ /dev/null @@ -1,50 +0,0 @@ -namespace JdeScoping.DataAccess.Queries; - -/// -/// Lookup related SQL queries (Status Codes, Function Codes) -/// -public static partial class JdeQueries -{ - /// - /// Gets all work order status codes from production schema. - /// Status codes are stored in UDC table F0005 with SY='00' and RT='SS'. - /// - public const string SqlGetStatusCodes = @" - SELECT TRIM(sc.DRKY) AS Code, - TRIM(sc.DRDL01) AS Description, - sc.DRUPMJ AS LastUpdateDate, - sc.DRUPMT AS LastUpdateTime - FROM {ProductionSchema}.F0005 sc - WHERE TRIM(sc.DRSY) = '00' AND - sc.DRRT = 'SS' AND - TRIM(sc.DRKY) IS NOT NULL"; - - /// - /// Gets status codes updated since specified date from production schema. - /// - public const string SqlGetStatusCodesFiltered = SqlGetStatusCodes + @" - AND (sc.DRUPMJ > :dateUpdated OR - (sc.DRUPMJ = :dateUpdated AND sc.DRUPMT >= :timeUpdated))"; - - /// - /// Gets all function codes from production schema. - /// Function codes are stored in F00192 (MES codes table). - /// Uses LISTAGG to concatenate multiple descriptions for same code. - /// - public const string SqlGetFunctionCodes = @" - SELECT Code, - TRIM(LISTAGG(Description, ' ') WITHIN GROUP(ORDER BY Description) || - CASE WHEN MAX(total_lengthb) > 4000 THEN '...' ELSE '' END) AS Description, - SYSDATE AS LastUpdateDt - FROM ( - SELECT TRIM(fc.CFKY) AS Code, - TRIM(ASCIISTR(fc.CFDS80)) AS Description, - SUM(LENGTHB(TRIM(fc.CFDS80))+1) OVER(PARTITION BY TRIM(fc.CFKY) ORDER BY TRIM(fc.CFDS80)) - 1 cumul_lengthb, - SUM(LENGTHB(TRIM(fc.CFDS80))+1) OVER(PARTITION BY TRIM(fc.CFKY)) - 1 total_lengthb, - COUNT(*) OVER(PARTITION BY TRIM(fc.CFKY)) num_values - FROM {ProductionSchema}.F00192 fc - WHERE TRIM(fc.CFKY) IS NOT NULL - ) - WHERE total_lengthb <= 4000 OR cumul_lengthb <= 4000 - length('...') - GROUP BY Code"; -} diff --git a/NEW/src/JdeScoping.DataAccess/Queries/JdeQueries.Organization.cs b/NEW/src/JdeScoping.DataAccess/Queries/JdeQueries.Organization.cs deleted file mode 100644 index ddd7ca6..0000000 --- a/NEW/src/JdeScoping.DataAccess/Queries/JdeQueries.Organization.cs +++ /dev/null @@ -1,95 +0,0 @@ -namespace JdeScoping.DataAccess.Queries; - -/// -/// Organization related SQL queries (Users, Business Units, Org Hierarchy, Route Masters) -/// -public static partial class JdeQueries -{ - /// - /// Gets all JDE users from production schema. - /// Uses CTE to get unique users with most recent address book record. - /// - public const string SqlGetUsers = @" - WITH USER_CTE AS ( - SELECT ab.ABAN8 AS AddressNumber, - TRIM(pro.ULUSER) AS UserId, - TRIM(ab.ABALPH) AS FullName, - ab.ABUPMJ AS LastUpdateDate, - ab.ABUPMT AS LastUpdateTime, - ROW_NUMBER() OVER (PARTITION BY ab.ABAN8 ORDER BY ab.ABUPMJ DESC, ab.ABUPMT DESC) RN - FROM {ProductionSchema}.F0101 ab - LEFT OUTER JOIN {ProductionSchema}.F0092 pro ON (ab.ABAN8 = pro.ULAN8) - WHERE ab.ABATE = 'Y' - ) - SELECT AddressNumber, - UserId, - FullName, - LastUpdateDate, - LastUpdateTime - FROM USER_CTE - WHERE RN = 1"; - - /// - /// Gets all business units of specified type from production schema. - /// Type codes: 'WC' = Work Center, 'PC' = Profit Center, 'BR' = Branch - /// - public const string SqlGetBusinessUnits = @" - SELECT TRIM(wc.MCMCU) AS Code, - TRIM(wc.MCDL01) AS Description, - wc.MCUPMJ AS LastUpdateDate, - wc.MCUPMT AS LastUpdateTime - FROM {ProductionSchema}.F0006 wc - WHERE wc.MCSTYL = :typeCode"; - - /// - /// Gets business units of specified type updated since specified date from production schema. - /// - public const string SqlGetBusinessUnitsFiltered = SqlGetBusinessUnits + @" - AND (wc.MCUPMJ > :dateUpdated OR - (wc.MCUPMJ = :dateUpdated AND wc.MCUPMT >= :timeUpdated))"; - - /// - /// Gets all organization hierarchy records from production schema. - /// Maps work centers to profit centers and branches. - /// - public const string SqlGetOrgHierarchy = @" - SELECT TRIM(oh.IWMCUW) AS ProfitCenterCode, - TRIM(oh.IWMCU) AS WorkCenterCode, - TRIM(oh.IWMMCU) AS BranchCode, - oh.IWUPMJ AS LastUpdateDate, - oh.IWTDAY AS LastUpdateTime - FROM {ProductionSchema}.F30006 oh - WHERE TRIM(oh.IWMCU) IS NOT NULL AND - TRIM(oh.IWMMCU) IS NOT NULL"; - - /// - /// Gets org hierarchy records updated since specified date from production schema. - /// - public const string SqlGetOrgHierarchyFiltered = SqlGetOrgHierarchy + @" - AND (oh.IWUPMJ > :dateUpdated OR - (oh.IWUPMJ = :dateUpdated AND oh.IWTDAY >= :timeUpdated))"; - - /// - /// Gets all route masters from production schema. - /// - public const string SqlGetRouteMasters = @" - SELECT TRIM(rm.IRMMCU) AS BranchCode, - TRIM(rm.IRKITL) AS ItemNumber, - TRIM(rm.IRTRT) AS RoutingType, - rm.IROPSQ / 10.0 AS SequenceNumber, - TRIM(rm.IRURRF) AS FunctionCode, - TRIM(rm.IRMCU) AS WorkCenterCode, - rm.IREFFF AS StartDateDate, - rm.IREFFT AS EndDateDate, - rm.IRUPMJ AS LastUpdateDate, - rm.IRTDAY AS LastUpdateTime - FROM {ProductionSchema}.F3003 rm - WHERE TRIM(rm.IRKITL) IS NOT NULL"; - - /// - /// Gets route masters updated since specified date from production schema. - /// - public const string SqlGetRouteMastersFiltered = SqlGetRouteMasters + @" - AND (rm.IRUPMJ > :dateUpdated OR - (rm.IRUPMJ = :dateUpdated AND rm.IRTDAY >= :timeUpdated))"; -} diff --git a/NEW/src/JdeScoping.DataAccess/Queries/JdeQueries.WorkOrders.cs b/NEW/src/JdeScoping.DataAccess/Queries/JdeQueries.WorkOrders.cs deleted file mode 100644 index eeeb090..0000000 --- a/NEW/src/JdeScoping.DataAccess/Queries/JdeQueries.WorkOrders.cs +++ /dev/null @@ -1,234 +0,0 @@ -namespace JdeScoping.DataAccess.Queries; - -/// -/// Work order related SQL queries (Work Orders, Steps, Times, Routings, Components) -/// -public static partial class JdeQueries -{ - /// - /// Gets all work orders from production schema. - /// - public const string SqlGetWorkorders = @" - SELECT wo.WADOCO AS WorkOrderNumber, - TRIM(wo.WAMMCU) AS BranchCode, - TRIM(wo.WALOTN) AS LotNumber, - TRIM(wo.WALITM) AS ItemNumber, - wo.WAITM AS ShortItemNumber, - TRIM(wo.WAPARS) AS ParentWorkOrderNumber, - wo.WAUORG / 100.0 AS OrderQuantity, - wo.WASOBK / 100.0 AS HeldQuantity, - wo.WASOQS / 100.0 AS ShippedQuantity, - TRIM(wo.WASRST) AS StatusCode, - CASE wo.WADCG WHEN 0 THEN TO_DATE('1900-01-01', 'YYYY-MM-DD') - ELSE TO_DATE(wo.WADCG+1900000,'YYYYDDD') END AS StatusCodeUpdateDT, - CASE wo.WATRDJ WHEN 0 THEN TO_DATE('1900-01-01', 'YYYY-MM-DD') - ELSE TO_DATE(wo.WATRDJ+1900000,'YYYYDDD') END AS IssueDate, - CASE wo.WASTRT WHEN 0 THEN TO_DATE('1900-01-01', 'YYYY-MM-DD') - ELSE TO_DATE(wo.WASTRT+1900000,'YYYYDDD') END AS StartDate, - TRIM(wo.WATRT) AS RoutingType, - wo.WAUPMJ AS LastUpdateDate, - wo.WATDAY AS LastUpdateTime - FROM {ProductionSchema}.F4801 wo"; - - /// - /// Gets work orders updated since specified date from production schema. - /// - public const string SqlGetWorkordersFiltered = SqlGetWorkorders + @" - WHERE (wo.WAUPMJ > :dateUpdated OR - (wo.WAUPMJ = :dateUpdated AND wo.WATDAY >= :timeUpdated))"; - - /// - /// Gets all work orders from archive schema. - /// - public const string SqlGetWorkordersArchive = @" - SELECT wo.WADOCO AS WorkOrderNumber, - TRIM(wo.WAMMCU) AS BranchCode, - TRIM(wo.WALOTN) AS LotNumber, - TRIM(wo.WALITM) AS ItemNumber, - wo.WAITM AS ShortItemNumber, - TRIM(wo.WAPARS) AS ParentWorkOrderNumber, - wo.WAUORG / 100.0 AS OrderQuantity, - wo.WASOBK / 100.0 AS HeldQuantity, - wo.WASOQS / 100.0 AS ShippedQuantity, - TRIM(wo.WASRST) AS StatusCode, - CASE wo.WADCG WHEN 0 THEN TO_DATE('1900-01-01', 'YYYY-MM-DD') - ELSE TO_DATE(wo.WADCG+1900000,'YYYYDDD') END AS StatusCodeUpdateDT, - CASE wo.WATRDJ WHEN 0 THEN TO_DATE('1900-01-01', 'YYYY-MM-DD') - ELSE TO_DATE(wo.WATRDJ+1900000,'YYYYDDD') END AS IssueDate, - CASE wo.WASTRT WHEN 0 THEN TO_DATE('1900-01-01', 'YYYY-MM-DD') - ELSE TO_DATE(wo.WASTRT+1900000,'YYYYDDD') END AS StartDate, - TRIM(wo.WATRT) AS RoutingType, - wo.WAUPMJ AS LastUpdateDate, - wo.WATDAY AS LastUpdateTime - FROM {ArchiveSchema}.F4801 wo"; - - /// - /// Gets work order steps from production schema. - /// - public const string SqlGetWorkorderSteps = @" - SELECT wos.WLDOCO AS WorkOrderNumber, - wos.WLOPSQ/10 AS StepNumber, - TRIM(wos.WLMCU) AS WorkCenterCode, - TRIM(wos.WLMMCU) AS BranchCode, - TRIM(wos.WLDSC1) AS StepDescription, - TRIM(mes.CFDS80) AS FunctionOperationDescription, - wos.WLOPSC AS StepTypeCode, - CASE wos.WLSTRT WHEN 0 THEN NULL - ELSE TO_DATE(wos.WLSTRT+1900000,'YYYYDDD') END AS StartDT, - CASE wos.WLSTRX WHEN 0 THEN NULL - ELSE TO_DATE(wos.WLSTRX+1900000,'YYYYDDD') END AS EndDT, - TRIM(wos.WLURRF) AS FunctionCode, - wos.WLSOCN / 100.0 AS ScrappedQuantity, - wos.WLUPMJ AS LastUpdateDate, - wos.WLTDAY AS LastUpdateTime - FROM {ProductionSchema}.F3112 wos - LEFT OUTER JOIN {ProductionSchema}.F00192 mes ON (wos.WLURRF = mes.CFKY) - WHERE TRIM(wos.WLMCU) IS NOT NULL AND - TRIM(wos.WLMMCU) IS NOT NULL"; - - /// - /// Gets work order steps updated since specified date from production schema. - /// - public const string SqlGetWorkorderStepsFiltered = SqlGetWorkorderSteps + @" - AND (wos.WLUPMJ > :dateUpdated OR - (wos.WLUPMJ = :dateUpdated AND wos.WLTDAY >= :timeUpdated))"; - - /// - /// Gets work order steps from archive schema. - /// - public const string SqlGetWorkorderStepsArchive = @" - SELECT wos.WLDOCO AS WorkOrderNumber, - wos.WLOPSQ/10 AS StepNumber, - TRIM(wos.WLMCU) AS WorkCenterCode, - TRIM(wos.WLMMCU) AS BranchCode, - TRIM(wos.WLDSC1) AS StepDescription, - TRIM(mes.CFDS80) AS FunctionOperationDescription, - wos.WLOPSC AS StepTypeCode, - CASE wos.WLSTRT WHEN 0 THEN NULL - ELSE TO_DATE(wos.WLSTRT+1900000,'YYYYDDD') END AS StartDT, - CASE wos.WLSTRX WHEN 0 THEN NULL - ELSE TO_DATE(wos.WLSTRX+1900000,'YYYYDDD') END AS EndDT, - TRIM(wos.WLURRF) AS FunctionCode, - wos.WLSOCN / 100.0 AS ScrappedQuantity, - wos.WLUPMJ AS LastUpdateDate, - wos.WLTDAY AS LastUpdateTime - FROM {ArchiveSchema}.F3112 wos - LEFT OUTER JOIN {ProductionSchema}.F00192 mes ON (wos.WLURRF = mes.CFKY) - WHERE TRIM(wos.WLMCU) IS NOT NULL AND - TRIM(wos.WLMMCU) IS NOT NULL"; - - /// - /// Gets work order time transactions from production schema. - /// - public const string SqlGetWorkorderTimes = @" - SELECT wot.WTUKID AS UniqueID, - wot.WTDOCO AS WorkOrderNumber, - wot.WTOPSQ/10 AS StepNumber, - TRIM(wot.WTMCU) AS WorkCenterCode, - TRIM(wot.WTMMCU) AS BranchCode, - wot.WTAN8 AS AddressNumber, - CASE wot.WTDGL WHEN 0 THEN NULL - ELSE TO_DATE(wot.WTDGL+1900000,'YYYYDDD') END AS GlDate, - wot.WTUPMJ AS LastUpdateDate, - wot.WTTDAY AS LastUpdateTime - FROM {ProductionSchema}.F31122 wot - WHERE TRIM(wot.WTMCU) IS NOT NULL AND - TRIM(wot.WTMMCU) IS NOT NULL"; - - /// - /// Gets work order times updated since specified date from production schema. - /// - public const string SqlGetWorkorderTimesFiltered = SqlGetWorkorderTimes + @" - AND (wot.WTUPMJ > :dateUpdated OR - (wot.WTUPMJ = :dateUpdated AND wot.WTTDAY >= :timeUpdated))"; - - /// - /// Gets work order time transactions from archive schema. - /// - public const string SqlGetWorkorderTimesArchive = @" - SELECT wot.WTUKID AS UniqueID, - wot.WTDOCO AS WorkOrderNumber, - wot.WTOPSQ/10 AS StepNumber, - TRIM(wot.WTMCU) AS WorkCenterCode, - TRIM(wot.WTMMCU) AS BranchCode, - wot.WTAN8 AS AddressNumber, - CASE wot.WTDGL WHEN 0 THEN NULL - ELSE TO_DATE(wot.WTDGL+1900000,'YYYYDDD') END AS GlDate, - wot.WTUPMJ AS LastUpdateDate, - wot.WTTDAY AS LastUpdateTime - FROM {ArchiveSchema}.F31122 wot - WHERE TRIM(wot.WTMCU) IS NOT NULL AND - TRIM(wot.WTMMCU) IS NOT NULL"; - - /// - /// Gets work order routing transactions from production schema. - /// - public const string SqlGetWorkorderRoutings = @" - SELECT TRIM(woz.SZEDUS) AS UserID, - TRIM(woz.SZEDBT) AS BatchNumber, - TRIM(woz.SZEDTN) AS TransactionNumber, - woz.SZEDLN AS LineNumber, - woz.SZOPSQ / 10.0 AS StepNumber, - TRIM(woz.SZMCU) AS WorkCenterCode, - woz.SZDOCO AS WorkOrderNumber, - TRIM(woz.SZTRT) AS RoutingType, - TRIM(woz.SZMMCU) AS BranchCode, - TRIM(woz.SZDSC1) AS StepDescription, - TRIM(woz.SZURRF) AS FunctionCode, - woz.SZTRDJ AS TransactionDate_Date, - woz.SZUPMJ AS LastUpdateDate, - woz.SZTDAY AS LastUpdateTime - FROM {ProductionSchema}.F3112Z1 woz - WHERE woz.SZTYTN = 'JDERTG' AND - woz.SZDRIN = '2' AND - woz.SZTNAC = '02' AND - woz.SZPID = 'ER31410' AND - TRIM(woz.SZEDUS) IS NOT NULL AND - TRIM(woz.SZEDBT) IS NOT NULL AND - TRIM(woz.SZEDTN) IS NOT NULL AND - TRIM(woz.SZMCU) IS NOT NULL"; - - /// - /// Gets work order routings updated since specified date from production schema. - /// - public const string SqlGetWorkorderRoutingsFiltered = SqlGetWorkorderRoutings + @" - AND (woz.SZUPMJ > :dateUpdated OR - (woz.SZUPMJ = :dateUpdated AND woz.SZTDAY >= :timeUpdated))"; - - /// - /// Gets work order component usage from production schema. - /// - public const string SqlGetWorkorderComponents = @" - SELECT woc.WMUKID AS UniqueID, - woc.WMDOCO AS WorkOrderNumber, - TRIM(woc.WMLOTN) AS LotNumber, - TRIM(woc.WMCMCU) AS BranchCode, - woc.WMCPIT AS ShortItemNumber, - woc.WMTRQT / 100.0 AS Quantity, - woc.WMUPMJ AS LastUpdateDate, - woc.WMTDAY AS LastUpdateTime - FROM {ProductionSchema}.F3111 woc - WHERE TRIM(woc.WMLOTN) IS NOT NULL"; - - /// - /// Gets work order components updated since specified date from production schema. - /// - public const string SqlGetWorkorderComponentsFiltered = SqlGetWorkorderComponents + @" - AND (woc.WMUPMJ > :dateUpdated OR - (woc.WMUPMJ = :dateUpdated AND woc.WMTDAY >= :timeUpdated))"; - - /// - /// Gets work order component usage from archive schema. - /// - public const string SqlGetWorkorderComponentsArchive = @" - SELECT woc.WMUKID AS UniqueID, - woc.WMDOCO AS WorkOrderNumber, - TRIM(woc.WMLOTN) AS LotNumber, - TRIM(woc.WMCMCU) AS BranchCode, - woc.WMCPIT AS ShortItemNumber, - woc.WMTRQT / 100.0 AS Quantity, - woc.WMUPMJ AS LastUpdateDate, - woc.WMTDAY AS LastUpdateTime - FROM {ArchiveSchema}.F3111 woc - WHERE TRIM(woc.WMLOTN) IS NOT NULL"; -} diff --git a/NEW/src/JdeScoping.DataAccess/Queries/JdeQueries.cs b/NEW/src/JdeScoping.DataAccess/Queries/JdeQueries.cs deleted file mode 100644 index 1ac341a..0000000 --- a/NEW/src/JdeScoping.DataAccess/Queries/JdeQueries.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace JdeScoping.DataAccess.Queries; - -/// -/// SQL query constants for the JDE Oracle database. -/// Schema placeholders ({ProductionSchema}, {ArchiveSchema}, {StageSchema}) are replaced at runtime. -/// -public static partial class JdeQueries -{ -} diff --git a/NEW/src/JdeScoping.DataAccess/Queries/LotFinderQueries.DataSync.cs b/NEW/src/JdeScoping.DataAccess/Queries/LotFinderQueries.DataSync.cs index b602bb0..07614d5 100644 --- a/NEW/src/JdeScoping.DataAccess/Queries/LotFinderQueries.DataSync.cs +++ b/NEW/src/JdeScoping.DataAccess/Queries/LotFinderQueries.DataSync.cs @@ -24,88 +24,4 @@ public static partial class LotFinderQueries cte.NumberRecords FROM DU_CTE cte WHERE cte.RN = 1"; - - /// - /// Gets column metadata for a table. - /// - public const string SqlGetTableColumns = @" - SELECT c.name AS Name, - CASE t2.name - WHEN 'varchar' THEN 'VARCHAR(' + CAST(c.max_length AS VARCHAR(10)) + ')' - WHEN 'decimal' THEN 'DECIMAL(' + CAST(c.precision AS VARCHAR(4)) + ',' + CAST(c.scale AS VARCHAR(4)) + ')' - ELSE UPPER(t2.name) - END AS Definition - FROM sys.columns c - INNER JOIN sys.types AS t2 ON (c.system_type_id = t2.system_type_id) - INNER JOIN sys.tables t ON (c.object_id = t.object_id) - WHERE t.name = @name - ORDER BY c.column_id"; - - /// - /// Gets primary key columns for a table. - /// - public const string SqlGetTablePrimaryKey = @" - SELECT COLUMN_NAME AS Name - FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE - WHERE OBJECTPROPERTY(OBJECT_ID(CONSTRAINT_SCHEMA + '.' + QUOTENAME(CONSTRAINT_NAME)), 'IsPrimaryKey') = 1 AND - TABLE_NAME = @name - ORDER BY ORDINAL_POSITION"; - - /// - /// Rebuilds all indices on a table. Use string.Format to inject table name. - /// - public const string SqlRebuildIndices = "ALTER INDEX ALL ON {0} REBUILD WITH (FILLFACTOR = 95);"; - - /// - /// Post-processing script to set MIS data obsoletion dates. - /// - public const string SqlPostprocessMisData = @" - SET ANSI_WARNINGS OFF; - - WITH cte AS ( - SELECT md.MisNumber, md.RevID, md.Status, MIN(md.ReleaseDate) Released - FROM dbo.MisData AS md - GROUP BY md.MisNumber, md.RevID, md.Status - ) - UPDATE dbo.MisData - SET ObsoleteDate = bl.Released - FROM cte bl - WHERE MisData.MisNumber = bl.MisNumber AND - MisData.RevID = bl.RevID AND - MisData.Status = 'Current' AND - bl.Status = 'BackLevel'; - - WITH cte AS ( - SELECT md.MisNumber, md.RevID, md.Status, MIN(md.ReleaseDate) Released - FROM dbo.MisData AS md - GROUP BY md.MisNumber, md.RevID, md.Status - ) - UPDATE dbo.MisData - SET ObsoleteDate = (SELECT TOP 1 nl.Released - FROM cte nl - WHERE MisData.MisNumber = nl.MisNumber AND - MisData.RevID < nl.RevID AND - MisData.Status = nl.Status - ORDER BY nl.RevID) - WHERE ObsoleteDate IS NULL; - - ALTER INDEX [PK_MisData] ON [dbo].[MisData] REBUILD;"; - - /// - /// Inserts a new data update tracking record. - /// - public const string SqlInsertDataUpdate = @" - INSERT INTO dbo.DataUpdate (SourceSystem, SourceData, TableName, StartDT, UpdateType) - VALUES (@sourceSystem, @sourceData, @tableName, @startDT, @updateType); - SELECT CAST(SCOPE_IDENTITY() AS BIGINT);"; - - /// - /// Completes a data update tracking record. - /// - public const string SqlCompleteDataUpdate = @" - UPDATE dbo.DataUpdate - SET EndDT = @endDT, - WasSuccessful = @wasSuccessful, - NumberRecords = @numberRecords - WHERE ID = @id"; } diff --git a/NEW/src/JdeScoping.DataAccess/Queries/LotFinderQueries.Lookups.cs b/NEW/src/JdeScoping.DataAccess/Queries/LotFinderQueries.Lookups.cs index 5986551..d92003e 100644 --- a/NEW/src/JdeScoping.DataAccess/Queries/LotFinderQueries.Lookups.cs +++ b/NEW/src/JdeScoping.DataAccess/Queries/LotFinderQueries.Lookups.cs @@ -52,17 +52,6 @@ public static partial class LotFinderQueries wc.Description LIKE '%' + @filter + '%' ORDER BY wc.Code"; - /// - /// Looks up work centers by codes using STRING_SPLIT. - /// - public const string SqlLookupWorkCenters = @" - SELECT wc.Code, - wc.Description, - wc.LastUpdateDT - FROM dbo.WorkCenter AS wc - WHERE wc.Code IN (SELECT LTRIM(RTRIM(value)) FROM STRING_SPLIT(@workCenterCodes, ',')) - ORDER BY wc.Code"; - /// /// Searches profit centers by code or description. /// @@ -76,17 +65,6 @@ public static partial class LotFinderQueries pc.Description LIKE '%' + @filter + '%' ORDER BY pc.Code"; - /// - /// Looks up profit centers by codes using STRING_SPLIT. - /// - public const string SqlLookupProfitCenters = @" - SELECT pc.Code, - pc.Description, - pc.LastUpdateDT - FROM dbo.ProfitCenter AS pc - WHERE pc.Code IN (SELECT LTRIM(RTRIM(value)) FROM STRING_SPLIT(@profitCenterCodes, ',')) - ORDER BY pc.Code"; - /// /// Searches users by user ID, full name, or address number. /// @@ -102,19 +80,6 @@ public static partial class LotFinderQueries CAST(u.AddressNumber AS VARCHAR(10)) LIKE '%' + @filter + '%' ORDER BY u.UserID, u.FullName"; - /// - /// Looks up users by user IDs or address numbers using STRING_SPLIT. - /// - public const string SqlLookupUsers = @" - SELECT u.AddressNumber, - u.UserID, - u.FullName, - u.LastUpdateDT - FROM dbo.JdeUser AS u - WHERE u.UserID IN (SELECT LTRIM(RTRIM(value)) FROM STRING_SPLIT(@userIds, ',')) - OR CAST(u.AddressNumber AS VARCHAR(20)) IN (SELECT LTRIM(RTRIM(value)) FROM STRING_SPLIT(@userIds, ',')) - ORDER BY u.UserID"; - /// /// Looks up lots by lot number and item number using OPENJSON. /// diff --git a/NEW/src/JdeScoping.DataAccess/Queries/LotFinderQueries.SearchManagement.cs b/NEW/src/JdeScoping.DataAccess/Queries/LotFinderQueries.SearchManagement.cs index 5205c21..80f01ca 100644 --- a/NEW/src/JdeScoping.DataAccess/Queries/LotFinderQueries.SearchManagement.cs +++ b/NEW/src/JdeScoping.DataAccess/Queries/LotFinderQueries.SearchManagement.cs @@ -55,22 +55,4 @@ public static partial class LotFinderQueries SELECT s.Results FROM dbo.Search AS s WHERE s.ID = @id"; - - /// - /// Updates search status. - /// - public const string SqlUpdateSearchStatus = @" - UPDATE dbo.Search - SET Status = @status, - StartDT = CASE WHEN @status = 2 THEN GETUTCDATE() ELSE StartDT END, - EndDT = CASE WHEN @status >= 3 THEN GETUTCDATE() ELSE EndDT END - WHERE ID = @id"; - - /// - /// Updates search results. - /// - public const string SqlUpdateSearchResults = @" - UPDATE dbo.Search - SET Results = @results - WHERE ID = @id"; } diff --git a/NEW/src/JdeScoping.DataAccess/Repositories/CmsRepository.cs b/NEW/src/JdeScoping.DataAccess/Repositories/CmsRepository.cs deleted file mode 100644 index 69dbf95..0000000 --- a/NEW/src/JdeScoping.DataAccess/Repositories/CmsRepository.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System.Runtime.CompilerServices; -using Dapper; -using JdeScoping.Core.Models; -using JdeScoping.Core.Models.Quality; -using JdeScoping.DataAccess.Options; -using JdeScoping.DataAccess.Interfaces; -using JdeScoping.DataAccess.Queries; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Oracle.ManagedDataAccess.Client; - -namespace JdeScoping.DataAccess.Repositories; - -/// -/// Repository implementation for the CMS Oracle database. -/// -public class CmsRepository : ICmsRepository -{ - private readonly IDbConnectionFactory _connectionFactory; - private readonly ILogger _logger; - private readonly IOptions _options; - private const string RepositoryName = "CmsRepository"; - - /// - /// Initializes a new instance of the class. - /// - public CmsRepository( - IDbConnectionFactory connectionFactory, - ILogger logger, - IOptions options) - { - _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _options = options ?? throw new ArgumentNullException(nameof(options)); - } - - /// - public async IAsyncEnumerable GetMisDataAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = lastUpdateDt.HasValue - ? CmsQueries.SqlGetMisDataFiltered - : CmsQueries.SqlGetMisData; - - var parameters = lastUpdateDt.HasValue - ? new { lastUpdateDT = lastUpdateDt.Value } - : null; - - OracleConnection? connection = null; - try - { - connection = await _connectionFactory.CreateCmsConnectionAsync(ct); - - // Use Query with buffered: false for streaming - var results = connection.Query( - sql, - parameters, - commandTimeout: _options.Value.MisDataTimeoutSeconds, - buffered: false); - - foreach (var item in results) - { - ct.ThrowIfCancellationRequested(); - - // Convert ReleaseDate to local time if present - if (item.ReleaseDate.HasValue) - { - item.ReleaseDate = item.ReleaseDate.Value.ToLocalTime(); - } - - yield return item; - } - } - finally - { - if (connection != null) - { - await connection.DisposeAsync(); - } - } - } -} diff --git a/NEW/src/JdeScoping.DataAccess/Repositories/JdeRepository.Inventory.cs b/NEW/src/JdeScoping.DataAccess/Repositories/JdeRepository.Inventory.cs deleted file mode 100644 index 6c3e130..0000000 --- a/NEW/src/JdeScoping.DataAccess/Repositories/JdeRepository.Inventory.cs +++ /dev/null @@ -1,94 +0,0 @@ -using System.Runtime.CompilerServices; -using JdeScoping.Core.Helpers; -using JdeScoping.Core.Models.Inventory; -using JdeScoping.DataAccess.Queries; - -namespace JdeScoping.DataAccess.Repositories; - -/// -/// Inventory (lots) operations for JDE Oracle repository. -/// -public partial class JdeRepository -{ - /// - public async IAsyncEnumerable GetLotsAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements( - lastUpdateDt.HasValue - ? JdeQueries.SqlGetLotsFiltered - : JdeQueries.SqlGetLots); - - var parameters = lastUpdateDt.HasValue - ? new { dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() } - : null; - - await foreach (var item in StreamQueryAsync( - sql, parameters, nameof(GetLotsAsync), "SQL_GET_LOTS", ct)) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetLotUsagesAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements( - lastUpdateDt.HasValue - ? JdeQueries.SqlGetLotUsagesFiltered - : JdeQueries.SqlGetLotUsages); - - var parameters = lastUpdateDt.HasValue - ? new { dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() } - : null; - - // Use special lot usage timeout due to large dataset - await foreach (var item in StreamQueryAsync( - sql, parameters, nameof(GetLotUsagesAsync), "SQL_GET_LOT_USAGES", ct, - _options.Value.LotUsageTimeoutSeconds)) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetLotUsagesArchiveAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements(JdeQueries.SqlGetLotUsagesArchive); - - // Use special lot usage timeout due to large dataset - await foreach (var item in StreamQueryAsync( - sql, null, nameof(GetLotUsagesArchiveAsync), "SQL_GET_LOT_USAGES_ARCHIVE", ct, - _options.Value.LotUsageTimeoutSeconds)) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetLotLocationsAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements( - lastUpdateDt.HasValue - ? JdeQueries.SqlGetLotLocationsFiltered - : JdeQueries.SqlGetLotLocations); - - var parameters = lastUpdateDt.HasValue - ? new { lastUpdateDT = lastUpdateDt.Value } - : null; - - // Use JDE Stage connection for lot locations - await foreach (var item in StreamQueryFromStageAsync( - sql, parameters, nameof(GetLotLocationsAsync), "SQL_GET_LOT_LOCATIONS", ct)) - { - yield return item; - } - } -} diff --git a/NEW/src/JdeScoping.DataAccess/Repositories/JdeRepository.ReferenceData.cs b/NEW/src/JdeScoping.DataAccess/Repositories/JdeRepository.ReferenceData.cs deleted file mode 100644 index 2d6fa28..0000000 --- a/NEW/src/JdeScoping.DataAccess/Repositories/JdeRepository.ReferenceData.cs +++ /dev/null @@ -1,191 +0,0 @@ -using System.Runtime.CompilerServices; -using JdeScoping.Core.Helpers; -using JdeScoping.Core.Models; -using JdeScoping.Core.Models.Inventory; -using JdeScoping.Core.Models.Lookup; -using JdeScoping.Core.Models.Organization; -using JdeScoping.DataAccess.Queries; - -namespace JdeScoping.DataAccess.Repositories; - -/// -/// Reference data operations for JDE Oracle repository. -/// -public partial class JdeRepository -{ - /// - public async IAsyncEnumerable GetItemsAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements( - lastUpdateDt.HasValue - ? JdeQueries.SqlGetItemsFiltered - : JdeQueries.SqlGetItems); - - var parameters = lastUpdateDt.HasValue - ? new { dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() } - : null; - - await foreach (var item in StreamQueryAsync( - sql, parameters, nameof(GetItemsAsync), "SQL_GET_ITEMS", ct)) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetUsersAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - // Users always do full sync (incremental not supported) - var sql = ApplySchemaReplacements(JdeQueries.SqlGetUsers); - - await foreach (var item in StreamQueryAsync( - sql, null, nameof(GetUsersAsync), "SQL_GET_USERS", ct)) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetBranchesAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements( - lastUpdateDt.HasValue - ? JdeQueries.SqlGetBusinessUnitsFiltered - : JdeQueries.SqlGetBusinessUnits); - - var parameters = lastUpdateDt.HasValue - ? new { typeCode = "BP", dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() } - : new { typeCode = "BP", dateUpdated = 0, timeUpdated = 0 }; - - await foreach (var item in StreamQueryAsync( - sql, parameters, nameof(GetBranchesAsync), "SQL_GET_BUSINESS_UNITS", ct)) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetProfitCentersAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements( - lastUpdateDt.HasValue - ? JdeQueries.SqlGetBusinessUnitsFiltered - : JdeQueries.SqlGetBusinessUnits); - - var parameters = lastUpdateDt.HasValue - ? new { typeCode = "I3", dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() } - : new { typeCode = "I3", dateUpdated = 0, timeUpdated = 0 }; - - await foreach (var item in StreamQueryAsync( - sql, parameters, nameof(GetProfitCentersAsync), "SQL_GET_BUSINESS_UNITS", ct)) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetWorkCentersAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements( - lastUpdateDt.HasValue - ? JdeQueries.SqlGetBusinessUnitsFiltered - : JdeQueries.SqlGetBusinessUnits); - - var parameters = lastUpdateDt.HasValue - ? new { typeCode = "WC", dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() } - : new { typeCode = "WC", dateUpdated = 0, timeUpdated = 0 }; - - await foreach (var item in StreamQueryAsync( - sql, parameters, nameof(GetWorkCentersAsync), "SQL_GET_BUSINESS_UNITS", ct)) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetStatusCodesAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements( - lastUpdateDt.HasValue - ? JdeQueries.SqlGetStatusCodesFiltered - : JdeQueries.SqlGetStatusCodes); - - var parameters = lastUpdateDt.HasValue - ? new { lastUpdateDT = lastUpdateDt.Value } - : null; - - // Use JDE Stage connection for status codes - await foreach (var item in StreamQueryFromStageAsync( - sql, parameters, nameof(GetStatusCodesAsync), "SQL_GET_STATUS_CODES", ct)) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetFunctionCodesAsync( - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements(JdeQueries.SqlGetFunctionCodes); - - await foreach (var item in StreamQueryAsync( - sql, null, nameof(GetFunctionCodesAsync), "SQL_GET_FUNCTION_CODES", ct)) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetOrgHierarchyAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements( - lastUpdateDt.HasValue - ? JdeQueries.SqlGetOrgHierarchyFiltered - : JdeQueries.SqlGetOrgHierarchy); - - var parameters = lastUpdateDt.HasValue - ? new { dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() } - : null; - - await foreach (var item in StreamQueryAsync( - sql, parameters, nameof(GetOrgHierarchyAsync), "SQL_GET_ORG_HIERARCHY", ct)) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetRouteMastersAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements( - lastUpdateDt.HasValue - ? JdeQueries.SqlGetRouteMastersFiltered - : JdeQueries.SqlGetRouteMasters); - - var parameters = lastUpdateDt.HasValue - ? new { dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() } - : null; - - await foreach (var item in StreamQueryAsync( - sql, parameters, nameof(GetRouteMastersAsync), "SQL_GET_ROUTE_MASTERS", ct)) - { - yield return item; - } - } -} diff --git a/NEW/src/JdeScoping.DataAccess/Repositories/JdeRepository.WorkOrders.cs b/NEW/src/JdeScoping.DataAccess/Repositories/JdeRepository.WorkOrders.cs deleted file mode 100644 index 6f5aba4..0000000 --- a/NEW/src/JdeScoping.DataAccess/Repositories/JdeRepository.WorkOrders.cs +++ /dev/null @@ -1,173 +0,0 @@ -using System.Runtime.CompilerServices; -using JdeScoping.Core.Helpers; -using JdeScoping.Core.Models.WorkOrders; -using JdeScoping.DataAccess.Queries; - -namespace JdeScoping.DataAccess.Repositories; - -/// -/// Work order operations for JDE Oracle repository. -/// -public partial class JdeRepository -{ - /// - public async IAsyncEnumerable GetWorkOrdersAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements( - lastUpdateDt.HasValue - ? JdeQueries.SqlGetWorkordersFiltered - : JdeQueries.SqlGetWorkorders); - - var parameters = lastUpdateDt.HasValue - ? new { dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() } - : null; - - await foreach (var item in StreamQueryAsync( - sql, parameters, nameof(GetWorkOrdersAsync), "SQL_GET_WORKORDERS", ct)) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetWorkOrdersArchiveAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements(JdeQueries.SqlGetWorkordersArchive); - - await foreach (var item in StreamQueryAsync( - sql, null, nameof(GetWorkOrdersArchiveAsync), "SQL_GET_WORKORDERS_ARCHIVE", ct)) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetWorkOrderStepsAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements( - lastUpdateDt.HasValue - ? JdeQueries.SqlGetWorkorderStepsFiltered - : JdeQueries.SqlGetWorkorderSteps); - - var parameters = lastUpdateDt.HasValue - ? new { dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() } - : null; - - await foreach (var item in StreamQueryAsync( - sql, parameters, nameof(GetWorkOrderStepsAsync), "SQL_GET_WORKORDER_STEPS", ct)) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetWorkOrderStepsArchiveAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements(JdeQueries.SqlGetWorkorderStepsArchive); - - await foreach (var item in StreamQueryAsync( - sql, null, nameof(GetWorkOrderStepsArchiveAsync), "SQL_GET_WORKORDER_STEPS_ARCHIVE", ct)) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetWorkOrderTimesAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements( - lastUpdateDt.HasValue - ? JdeQueries.SqlGetWorkorderTimesFiltered - : JdeQueries.SqlGetWorkorderTimes); - - var parameters = lastUpdateDt.HasValue - ? new { dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() } - : null; - - await foreach (var item in StreamQueryAsync( - sql, parameters, nameof(GetWorkOrderTimesAsync), "SQL_GET_WORKORDER_TIMES", ct)) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetWorkOrderTimesArchiveAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements(JdeQueries.SqlGetWorkorderTimesArchive); - - await foreach (var item in StreamQueryAsync( - sql, null, nameof(GetWorkOrderTimesArchiveAsync), "SQL_GET_WORKORDER_TIMES_ARCHIVE", ct)) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetWorkOrderRoutingsAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements( - lastUpdateDt.HasValue - ? JdeQueries.SqlGetWorkorderRoutingsFiltered - : JdeQueries.SqlGetWorkorderRoutings); - - var parameters = lastUpdateDt.HasValue - ? new { dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() } - : null; - - await foreach (var item in StreamQueryAsync( - sql, parameters, nameof(GetWorkOrderRoutingsAsync), "SQL_GET_WORKORDER_ROUTINGS", ct)) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetWorkOrderComponentsAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements( - lastUpdateDt.HasValue - ? JdeQueries.SqlGetWorkorderComponentsFiltered - : JdeQueries.SqlGetWorkorderComponents); - - var parameters = lastUpdateDt.HasValue - ? new { dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() } - : null; - - await foreach (var item in StreamQueryAsync( - sql, parameters, nameof(GetWorkOrderComponentsAsync), "SQL_GET_WORKORDER_COMPONENTS", ct)) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetWorkOrderComponentsArchiveAsync( - DateTime? lastUpdateDt = null, - [EnumeratorCancellation] CancellationToken ct = default) - { - var sql = ApplySchemaReplacements(JdeQueries.SqlGetWorkorderComponentsArchive); - - await foreach (var item in StreamQueryAsync( - sql, null, nameof(GetWorkOrderComponentsArchiveAsync), "SQL_GET_WORKORDER_COMPONENTS_ARCHIVE", ct)) - { - yield return item; - } - } -} diff --git a/NEW/src/JdeScoping.DataAccess/Repositories/JdeRepository.cs b/NEW/src/JdeScoping.DataAccess/Repositories/JdeRepository.cs deleted file mode 100644 index 21bb460..0000000 --- a/NEW/src/JdeScoping.DataAccess/Repositories/JdeRepository.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System.Runtime.CompilerServices; -using Dapper; -using JdeScoping.DataAccess.Options; -using JdeScoping.DataAccess.Interfaces; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using Oracle.ManagedDataAccess.Client; - -namespace JdeScoping.DataAccess.Repositories; - -/// -/// Repository implementation for the JDE Oracle database. -/// -public partial class JdeRepository : IJdeRepository -{ - private readonly IDbConnectionFactory _connectionFactory; - private readonly ILogger _logger; - private readonly IOptions _options; - private const string RepositoryName = "JdeRepository"; - - /// - /// Initializes a new instance of the class. - /// - public JdeRepository( - IDbConnectionFactory connectionFactory, - ILogger logger, - IOptions options) - { - _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - _options = options ?? throw new ArgumentNullException(nameof(options)); - } - - private string ApplySchemaReplacements(string sql) - { - return sql - .Replace("{ProductionSchema}", _options.Value.ProductionSchema) - .Replace("{ArchiveSchema}", _options.Value.ArchiveSchema) - .Replace("{StageSchema}", _options.Value.StageSchema); - } - - private async IAsyncEnumerable StreamQueryAsync( - string sql, - object? parameters, - string operation, - string queryName, - [EnumeratorCancellation] CancellationToken ct, - int? timeoutSeconds = null) - { - OracleConnection? connection = null; - try - { - connection = await _connectionFactory.CreateJdeConnectionAsync(ct); - var timeout = timeoutSeconds ?? _options.Value.DefaultTimeoutSeconds; - - // Use Query with buffered: false for streaming - var results = connection.Query( - sql, - parameters, - commandTimeout: timeout, - buffered: false); - - foreach (var item in results) - { - ct.ThrowIfCancellationRequested(); - yield return item; - } - } - finally - { - if (connection != null) - { - await connection.DisposeAsync(); - } - } - } - - private async IAsyncEnumerable StreamQueryFromStageAsync( - string sql, - object? parameters, - string operation, - string queryName, - [EnumeratorCancellation] CancellationToken ct, - int? timeoutSeconds = null) - { - OracleConnection? connection = null; - try - { - connection = await _connectionFactory.CreateJdeStageConnectionAsync(ct); - var timeout = timeoutSeconds ?? _options.Value.DefaultTimeoutSeconds; - - // Use Query with buffered: false for streaming - var results = connection.Query( - sql, - parameters, - commandTimeout: timeout, - buffered: false); - - foreach (var item in results) - { - ct.ThrowIfCancellationRequested(); - yield return item; - } - } - finally - { - if (connection != null) - { - await connection.DisposeAsync(); - } - } - } -} diff --git a/NEW/src/JdeScoping.DataAccess/Repositories/LotFinderRepository.DataSync.cs b/NEW/src/JdeScoping.DataAccess/Repositories/LotFinderRepository.DataSync.cs index 7b9d9ac..237234c 100644 --- a/NEW/src/JdeScoping.DataAccess/Repositories/LotFinderRepository.DataSync.cs +++ b/NEW/src/JdeScoping.DataAccess/Repositories/LotFinderRepository.DataSync.cs @@ -1,8 +1,6 @@ -using System.Data; using Dapper; using JdeScoping.Core.Models.Infrastructure; using JdeScoping.DataAccess.Queries; -using Microsoft.Data.SqlClient; namespace JdeScoping.DataAccess.Repositories; @@ -33,166 +31,4 @@ public partial class LotFinderRepository throw; } } - - /// - public async Task GetTableSpecAsync(string tableName, CancellationToken ct = default) - { - const string operation = nameof(GetTableSpecAsync); - try - { - var tableSpec = new TableSpec(tableName); - - await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct); - - // Load columns - var columns = await connection.QueryAsync( - LotFinderQueries.SqlGetTableColumns, - new { name = tableName }, - commandTimeout: _options.Value.DefaultTimeoutSeconds); - tableSpec.Columns.AddRange(columns); - - // Load primary key - var pkColumns = await connection.QueryAsync( - LotFinderQueries.SqlGetTablePrimaryKey, - new { name = tableName }, - commandTimeout: _options.Value.DefaultTimeoutSeconds); - foreach (var columnName in pkColumns) - { - var column = tableSpec.GetColumn(columnName); - if (column != null) - { - tableSpec.PrimaryKey.Add(column); - } - } - - return tableSpec; - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - LogAndThrow(ex, operation, "SQL_GET_TABLE_COLUMNS"); - throw; - } - } - - /// - public async Task RebuildIndicesAsync(string tableName, CancellationToken ct = default) - { - const string operation = nameof(RebuildIndicesAsync); - - // Validate table name against whitelist (SQL injection prevention) - if (!ValidTableNames.Contains(tableName)) - { - throw new ArgumentException($"Invalid table name: {tableName}", nameof(tableName)); - } - - try - { - await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct); - var sql = $"ALTER INDEX ALL ON [{tableName}] REBUILD WITH (FILLFACTOR = 95)"; - await connection.ExecuteAsync(sql, commandTimeout: _options.Value.RebuildIndexTimeoutSeconds); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - LogAndThrow(ex, operation, "SQL_REBUILD_INDICES"); - throw; - } - } - - /// - public async Task PostProcessMisDataAsync(CancellationToken ct = default) - { - const string operation = nameof(PostProcessMisDataAsync); - try - { - await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct); - await connection.ExecuteAsync( - LotFinderQueries.SqlPostprocessMisData, - commandTimeout: _options.Value.DefaultTimeoutSeconds); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - LogAndThrow(ex, operation, "SQL_POSTPROCESS_MISDATA"); - throw; - } - } - - /// - public async Task BulkInsertAsync(string tableName, IEnumerable records, CancellationToken ct = default) - { - const string operation = nameof(BulkInsertAsync); - - // Validate table name against whitelist - if (!ValidTableNames.Contains(tableName)) - { - throw new ArgumentException($"Invalid table name: {tableName}", nameof(tableName)); - } - - try - { - await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct); - - // Use SqlBulkCopy for efficient bulk insert - using var bulkCopy = new SqlBulkCopy(connection) - { - DestinationTableName = $"dbo.[{tableName}]", - BulkCopyTimeout = _options.Value.DefaultTimeoutSeconds - }; - - // Convert records to DataTable - var recordList = records.ToList(); - var dataTable = ToDataTable(recordList); - - await bulkCopy.WriteToServerAsync(dataTable, ct); - return recordList.Count; - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - LogAndThrow(ex, operation, "BulkInsert"); - throw; - } - } - - /// - public async Task TruncateTableAsync(string tableName, CancellationToken ct = default) - { - const string operation = nameof(TruncateTableAsync); - - // Validate table name against whitelist - if (!ValidTableNames.Contains(tableName)) - { - throw new ArgumentException($"Invalid table name: {tableName}", nameof(tableName)); - } - - try - { - await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct); - var sql = $"TRUNCATE TABLE dbo.[{tableName}]"; - await connection.ExecuteAsync(sql, commandTimeout: _options.Value.DefaultTimeoutSeconds); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - LogAndThrow(ex, operation, "TruncateTable"); - throw; - } - } } diff --git a/NEW/src/JdeScoping.DataAccess/Repositories/LotFinderRepository.Lookups.cs b/NEW/src/JdeScoping.DataAccess/Repositories/LotFinderRepository.Lookups.cs index 45f7b8b..1f915fa 100644 --- a/NEW/src/JdeScoping.DataAccess/Repositories/LotFinderRepository.Lookups.cs +++ b/NEW/src/JdeScoping.DataAccess/Repositories/LotFinderRepository.Lookups.cs @@ -113,32 +113,6 @@ public partial class LotFinderRepository } } - /// - public async Task> LookupWorkCentersAsync(List codes, CancellationToken ct = default) - { - const string operation = nameof(LookupWorkCentersAsync); - try - { - var workCenterCodesCsv = string.Join(",", codes); - - await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct); - var result = await connection.QueryAsync( - LotFinderQueries.SqlLookupWorkCenters, - new { workCenterCodes = workCenterCodesCsv }, - commandTimeout: _options.Value.DefaultTimeoutSeconds); - return result.ToList(); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - LogAndThrow(ex, operation, "SQL_LOOKUP_WORK_CENTERS"); - throw; - } - } - /// public async Task> SearchProfitCentersAsync(string filter, CancellationToken ct = default) { @@ -163,32 +137,6 @@ public partial class LotFinderRepository } } - /// - public async Task> LookupProfitCentersAsync(List codes, CancellationToken ct = default) - { - const string operation = nameof(LookupProfitCentersAsync); - try - { - var profitCenterCodesCsv = string.Join(",", codes); - - await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct); - var result = await connection.QueryAsync( - LotFinderQueries.SqlLookupProfitCenters, - new { profitCenterCodes = profitCenterCodesCsv }, - commandTimeout: _options.Value.DefaultTimeoutSeconds); - return result.ToList(); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - LogAndThrow(ex, operation, "SQL_LOOKUP_PROFIT_CENTERS"); - throw; - } - } - /// public async Task> SearchUsersAsync(string filter, CancellationToken ct = default) { @@ -213,32 +161,6 @@ public partial class LotFinderRepository } } - /// - public async Task> LookupUsersAsync(List userIds, CancellationToken ct = default) - { - const string operation = nameof(LookupUsersAsync); - try - { - var userIdsCsv = string.Join(",", userIds); - - await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct); - var result = await connection.QueryAsync( - LotFinderQueries.SqlLookupUsers, - new { userIds = userIdsCsv }, - commandTimeout: _options.Value.DefaultTimeoutSeconds); - return result.ToList(); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - LogAndThrow(ex, operation, "SQL_LOOKUP_USERS"); - throw; - } - } - /// public async Task> LookupLotsAsync(List lots, CancellationToken ct = default) { diff --git a/NEW/src/JdeScoping.DataAccess/Repositories/LotFinderRepository.SearchManagement.cs b/NEW/src/JdeScoping.DataAccess/Repositories/LotFinderRepository.SearchManagement.cs index 87b62ef..56a6e3b 100644 --- a/NEW/src/JdeScoping.DataAccess/Repositories/LotFinderRepository.SearchManagement.cs +++ b/NEW/src/JdeScoping.DataAccess/Repositories/LotFinderRepository.SearchManagement.cs @@ -152,49 +152,4 @@ public partial class LotFinderRepository } } - /// - public async Task UpdateSearchStatusAsync(int id, SearchStatus status, CancellationToken ct = default) - { - const string operation = nameof(UpdateSearchStatusAsync); - try - { - await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct); - await connection.ExecuteAsync( - LotFinderQueries.SqlUpdateSearchStatus, - new { id, status = (int)status }, - commandTimeout: _options.Value.DefaultTimeoutSeconds); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - LogAndThrow(ex, operation, "SQL_UPDATE_SEARCH_STATUS"); - throw; - } - } - - /// - public async Task UpdateSearchResultsAsync(int id, byte[] results, CancellationToken ct = default) - { - const string operation = nameof(UpdateSearchResultsAsync); - try - { - await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct); - await connection.ExecuteAsync( - LotFinderQueries.SqlUpdateSearchResults, - new { id, results }, - commandTimeout: _options.Value.DefaultTimeoutSeconds); - } - catch (OperationCanceledException) - { - throw; - } - catch (Exception ex) - { - LogAndThrow(ex, operation, "SQL_UPDATE_SEARCH_RESULTS"); - throw; - } - } } diff --git a/NEW/src/JdeScoping.DataAccess/Repositories/LotFinderRepository.cs b/NEW/src/JdeScoping.DataAccess/Repositories/LotFinderRepository.cs index 6881f2e..b10a670 100644 --- a/NEW/src/JdeScoping.DataAccess/Repositories/LotFinderRepository.cs +++ b/NEW/src/JdeScoping.DataAccess/Repositories/LotFinderRepository.cs @@ -1,4 +1,3 @@ -using System.Data; using JdeScoping.Core.Interfaces; using JdeScoping.DataAccess.Options; using JdeScoping.DataAccess.Exceptions; @@ -19,22 +18,6 @@ public partial class LotFinderRepository : ILotFinderRepository private readonly IOptions _options; private const string RepositoryName = "LotFinderRepository"; - /// - /// Valid table names for index rebuild operations (SQL injection whitelist). - /// - private static readonly HashSet ValidTableNames = new(StringComparer.OrdinalIgnoreCase) - { - "Branch", "DataUpdate", "FunctionCode", "Item", "JdeUser", - "Lot", "LotLocation", "LotUsage_Curr", "LotUsage_Hist", - "MisData", "OrgHierarchy", "ProfitCenter", "RouteMaster", - "Search", "StatusCode", "WorkCenter", - "WorkOrder_Curr", "WorkOrder_Hist", - "WorkOrderComponent_Curr", "WorkOrderComponent_Hist", - "WorkOrderRouting", - "WorkOrderStep_Curr", "WorkOrderStep_Hist", - "WorkOrderTime_Curr", "WorkOrderTime_Hist" - }; - /// /// Initializes a new instance of the class. /// @@ -83,41 +66,4 @@ public partial class LotFinderRepository : ILotFinderRepository // SQL Server timeout error number: -2 return ex.Number == -2; } - - private static DataTable ToDataTable(List items) - { - var dataTable = new DataTable(); - var properties = typeof(T).GetProperties() - .Where(p => p.CanRead && IsSupportedType(p.PropertyType)) - .ToArray(); - - foreach (var prop in properties) - { - var type = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType; - dataTable.Columns.Add(prop.Name, type); - } - - foreach (var item in items) - { - var row = dataTable.NewRow(); - foreach (var prop in properties) - { - var value = prop.GetValue(item); - row[prop.Name] = value ?? DBNull.Value; - } - dataTable.Rows.Add(row); - } - - return dataTable; - } - - private static bool IsSupportedType(Type type) - { - var underlyingType = Nullable.GetUnderlyingType(type) ?? type; - return underlyingType.IsPrimitive - || underlyingType == typeof(string) - || underlyingType == typeof(DateTime) - || underlyingType == typeof(decimal) - || underlyingType == typeof(Guid); - } } diff --git a/NEW/src/JdeScoping.Host/Program.cs b/NEW/src/JdeScoping.Host/Program.cs index 6ebbe58..3d351e3 100644 --- a/NEW/src/JdeScoping.Host/Program.cs +++ b/NEW/src/JdeScoping.Host/Program.cs @@ -1,9 +1,7 @@ using JdeScoping.Api; -using JdeScoping.Core.Interfaces; using JdeScoping.DataAccess.Options; using JdeScoping.DataSync.Options; using JdeScoping.ExcelIO.Options; -using JdeScoping.Infrastructure.Options; using JdeScoping.Database; using Microsoft.Extensions.Options; @@ -72,11 +70,5 @@ static void ValidateServices(IServiceProvider services) _ = provider.GetRequiredService>(); _ = provider.GetRequiredService>(); _ = provider.GetRequiredService>(); - _ = provider.GetRequiredService>(); - - // Validate data source services - _ = provider.GetRequiredService(); - _ = provider.GetRequiredService(); - Console.WriteLine("Service validation completed successfully."); } diff --git a/NEW/src/JdeScoping.Infrastructure/DependencyInjection.cs b/NEW/src/JdeScoping.Infrastructure/DependencyInjection.cs index 8647db9..b0f1b44 100644 --- a/NEW/src/JdeScoping.Infrastructure/DependencyInjection.cs +++ b/NEW/src/JdeScoping.Infrastructure/DependencyInjection.cs @@ -3,8 +3,6 @@ using JdeScoping.Core.Options; using JdeScoping.Infrastructure.Auth; using JdeScoping.Infrastructure.Options; using JdeScoping.Infrastructure.Security; -using JdeScoping.Infrastructure.Sources.Cms; -using JdeScoping.Infrastructure.Sources.Jde; using Microsoft.Extensions.Configuration; namespace Microsoft.Extensions.DependencyInjection; @@ -25,27 +23,9 @@ public static class InfrastructureDependencyInjection IConfiguration configuration) { // Bind configuration - services.Configure( - configuration.GetSection(DataSourceOptions.SectionName)); services.Configure( configuration.GetSection(LdapOptions.SectionName)); - // Register data sources based on configuration - var dataSourceOptions = configuration - .GetSection(DataSourceOptions.SectionName) - .Get(); - - if (dataSourceOptions?.UseFileDataSource == true) - { - services.AddScoped(); - services.AddScoped(); - } - else - { - services.AddScoped(); - services.AddScoped(); - } - // Register auth service based on configuration var ldapOptions = configuration .GetSection(LdapOptions.SectionName) diff --git a/NEW/src/JdeScoping.Infrastructure/JdeScoping.Infrastructure.csproj b/NEW/src/JdeScoping.Infrastructure/JdeScoping.Infrastructure.csproj index 05cda31..786e26e 100644 --- a/NEW/src/JdeScoping.Infrastructure/JdeScoping.Infrastructure.csproj +++ b/NEW/src/JdeScoping.Infrastructure/JdeScoping.Infrastructure.csproj @@ -7,13 +7,11 @@ - - diff --git a/NEW/src/JdeScoping.Infrastructure/Options/DataSourceOptions.cs b/NEW/src/JdeScoping.Infrastructure/Options/DataSourceOptions.cs deleted file mode 100644 index 3e39b30..0000000 --- a/NEW/src/JdeScoping.Infrastructure/Options/DataSourceOptions.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace JdeScoping.Infrastructure.Options; - -/// -/// Configuration options for data source selection (Oracle vs file-based). -/// -public class DataSourceOptions -{ - /// - /// Configuration section name in appsettings.json. - /// - public const string SectionName = "DataSource"; - - /// - /// Use file-based data sources instead of Oracle for development. - /// - public bool UseFileDataSource { get; set; } = false; - - /// - /// Directory containing JSON data files for file-based data source. - /// - public string FileDirectory { get; set; } = "DevData"; -} diff --git a/NEW/src/JdeScoping.Infrastructure/Sources/Cms/CmsFileDataSource.cs b/NEW/src/JdeScoping.Infrastructure/Sources/Cms/CmsFileDataSource.cs deleted file mode 100644 index 7c67719..0000000 --- a/NEW/src/JdeScoping.Infrastructure/Sources/Cms/CmsFileDataSource.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Runtime.CompilerServices; -using System.Text.Json; -using JdeScoping.Core.Interfaces; -using JdeScoping.Core.Models; -using JdeScoping.Core.Models.Quality; -using JdeScoping.Infrastructure.Options; -using Microsoft.Extensions.Options; - -namespace JdeScoping.Infrastructure.Sources.Cms; - -/// -/// File-based CMS data source for development/testing. -/// -public class CmsFileDataSource : ICmsDataSource -{ - private readonly string _dataDirectory; - - /// - /// Initializes a new instance of the class. - /// - public CmsFileDataSource(IOptions options) - { - _dataDirectory = options.Value.FileDirectory; - } - - /// - public async IAsyncEnumerable GetMisDataAsync( - DateTime? minimumDt = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - var filePath = Path.Combine(_dataDirectory, "misdata.json"); - if (!File.Exists(filePath)) - yield break; - - var json = await File.ReadAllTextAsync(filePath, cancellationToken); - var items = JsonSerializer.Deserialize>(json) ?? []; - - foreach (var item in items.Where(m => !minimumDt.HasValue || (m.ReleaseDate.HasValue && m.ReleaseDate.Value >= minimumDt))) - { - yield return item; - } - } -} diff --git a/NEW/src/JdeScoping.Infrastructure/Sources/Cms/CmsOracleDataSource.cs b/NEW/src/JdeScoping.Infrastructure/Sources/Cms/CmsOracleDataSource.cs deleted file mode 100644 index d2c7c97..0000000 --- a/NEW/src/JdeScoping.Infrastructure/Sources/Cms/CmsOracleDataSource.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Runtime.CompilerServices; -using Dapper; -using JdeScoping.Core.Interfaces; -using JdeScoping.Core.Models; -using JdeScoping.Core.Models.Quality; -using Microsoft.Extensions.Configuration; -using Oracle.ManagedDataAccess.Client; - -namespace JdeScoping.Infrastructure.Sources.Cms; - -/// -/// Oracle-based CMS data source for production use. -/// -public class CmsOracleDataSource : ICmsDataSource -{ - private readonly string _connectionString; - - /// - /// Initializes a new instance of the class. - /// - public CmsOracleDataSource(IConfiguration configuration) - { - _connectionString = configuration.GetConnectionString("CMS") - ?? throw new InvalidOperationException("CMS connection string not configured"); - } - - /// - public async IAsyncEnumerable GetMisDataAsync( - DateTime? minimumDt = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - await using var connection = new OracleConnection(_connectionString); - await connection.OpenAsync(cancellationToken); - - // TODO: Implement actual CMS query for MIS data - var sql = minimumDt.HasValue - ? "SELECT * FROM MISDATA WHERE RELEASE_DATE >= :MinDate" - : "SELECT * FROM MISDATA"; - - var results = await connection.QueryAsync(sql, new { MinDate = minimumDt }); - foreach (var item in results) - { - yield return item; - } - } -} diff --git a/NEW/src/JdeScoping.Infrastructure/Sources/Jde/JdeFileDataSource.cs b/NEW/src/JdeScoping.Infrastructure/Sources/Jde/JdeFileDataSource.cs deleted file mode 100644 index 77bb346..0000000 --- a/NEW/src/JdeScoping.Infrastructure/Sources/Jde/JdeFileDataSource.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System.Runtime.CompilerServices; -using System.Text.Json; -using JdeScoping.Core.Interfaces; -using JdeScoping.Core.Models; -using JdeScoping.Core.Models.Inventory; -using JdeScoping.Core.Models.Organization; -using JdeScoping.Core.Models.WorkOrders; -using JdeScoping.Infrastructure.Options; -using Microsoft.Extensions.Options; - -namespace JdeScoping.Infrastructure.Sources.Jde; - -/// -/// File-based JDE data source for development/testing. -/// -public class JdeFileDataSource : IJdeDataSource -{ - private readonly string _dataDirectory; - - /// - /// Initializes a new instance of the class. - /// - public JdeFileDataSource(IOptions options) - { - _dataDirectory = options.Value.FileDirectory; - } - - /// - public async IAsyncEnumerable GetWorkOrdersAsync( - DateTime? minimumDt = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - var items = await LoadFromFileAsync("workorders.json", cancellationToken); - foreach (var item in items.Where(wo => !minimumDt.HasValue || (wo.LastUpdateDt.HasValue && wo.LastUpdateDt.Value >= minimumDt))) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetLotUsagesAsync( - DateTime? minimumDt = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - var items = await LoadFromFileAsync("lotusages.json", cancellationToken); - foreach (var item in items.Where(lu => !minimumDt.HasValue || (lu.LastUpdateDt.HasValue && lu.LastUpdateDt.Value >= minimumDt))) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetLotsAsync( - DateTime? minimumDt = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - var items = await LoadFromFileAsync("lots.json", cancellationToken); - foreach (var item in items.Where(l => !minimumDt.HasValue || (l.LastUpdateDt.HasValue && l.LastUpdateDt.Value >= minimumDt))) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetItemsAsync( - DateTime? minimumDt = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - var items = await LoadFromFileAsync("items.json", cancellationToken); - foreach (var item in items.Where(i => !minimumDt.HasValue || (i.LastUpdateDt.HasValue && i.LastUpdateDt.Value >= minimumDt))) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetWorkCentersAsync( - DateTime? minimumDt = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - var items = await LoadFromFileAsync("workcenters.json", cancellationToken); - foreach (var item in items.Where(wc => !minimumDt.HasValue || (wc.LastUpdateDt.HasValue && wc.LastUpdateDt.Value >= minimumDt))) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetProfitCentersAsync( - DateTime? minimumDt = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - var items = await LoadFromFileAsync("profitcenters.json", cancellationToken); - foreach (var item in items.Where(pc => !minimumDt.HasValue || (pc.LastUpdateDt.HasValue && pc.LastUpdateDt.Value >= minimumDt))) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetUsersAsync( - DateTime? minimumDt = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - var items = await LoadFromFileAsync("users.json", cancellationToken); - foreach (var item in items.Where(u => !minimumDt.HasValue || (u.LastUpdateDt.HasValue && u.LastUpdateDt.Value >= minimumDt))) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetBranchesAsync( - DateTime? minimumDt = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - var items = await LoadFromFileAsync("branches.json", cancellationToken); - foreach (var item in items) - { - yield return item; - } - } - - private async Task> LoadFromFileAsync(string fileName, CancellationToken cancellationToken) - { - var filePath = Path.Combine(_dataDirectory, fileName); - if (!File.Exists(filePath)) - return []; - - var json = await File.ReadAllTextAsync(filePath, cancellationToken); - return JsonSerializer.Deserialize>(json) ?? []; - } -} diff --git a/NEW/src/JdeScoping.Infrastructure/Sources/Jde/JdeOracleDataSource.cs b/NEW/src/JdeScoping.Infrastructure/Sources/Jde/JdeOracleDataSource.cs deleted file mode 100644 index 120f665..0000000 --- a/NEW/src/JdeScoping.Infrastructure/Sources/Jde/JdeOracleDataSource.cs +++ /dev/null @@ -1,180 +0,0 @@ -using System.Runtime.CompilerServices; -using Dapper; -using JdeScoping.Core.Interfaces; -using JdeScoping.Core.Models; -using JdeScoping.Core.Models.Inventory; -using JdeScoping.Core.Models.Organization; -using JdeScoping.Core.Models.WorkOrders; -using Microsoft.Extensions.Configuration; -using Oracle.ManagedDataAccess.Client; - -namespace JdeScoping.Infrastructure.Sources.Jde; - -/// -/// Oracle-based JDE data source for production use. -/// -public class JdeOracleDataSource : IJdeDataSource -{ - private readonly string _connectionString; - - /// - /// Initializes a new instance of the class. - /// - public JdeOracleDataSource(IConfiguration configuration) - { - _connectionString = configuration.GetConnectionString("JDE") - ?? throw new InvalidOperationException("JDE connection string not configured"); - } - - /// - public async IAsyncEnumerable GetWorkOrdersAsync( - DateTime? minimumDt = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - await using var connection = new OracleConnection(_connectionString); - await connection.OpenAsync(cancellationToken); - - // TODO: Implement actual JDE query with proper column mapping - var sql = minimumDt.HasValue - ? "SELECT * FROM F4801 WHERE UPMJ >= :MinDate" - : "SELECT * FROM F4801"; - - var results = await connection.QueryAsync(sql, new { MinDate = minimumDt }); - foreach (var item in results) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetLotUsagesAsync( - DateTime? minimumDt = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - await using var connection = new OracleConnection(_connectionString); - await connection.OpenAsync(cancellationToken); - - // TODO: Implement actual JDE query - var sql = minimumDt.HasValue - ? "SELECT * FROM F4111 WHERE UPMJ >= :MinDate" - : "SELECT * FROM F4111"; - - var results = await connection.QueryAsync(sql, new { MinDate = minimumDt }); - foreach (var item in results) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetLotsAsync( - DateTime? minimumDt = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - await using var connection = new OracleConnection(_connectionString); - await connection.OpenAsync(cancellationToken); - - var sql = minimumDt.HasValue - ? "SELECT * FROM F4108 WHERE UPMJ >= :MinDate" - : "SELECT * FROM F4108"; - - var results = await connection.QueryAsync(sql, new { MinDate = minimumDt }); - foreach (var item in results) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetItemsAsync( - DateTime? minimumDt = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - await using var connection = new OracleConnection(_connectionString); - await connection.OpenAsync(cancellationToken); - - var sql = minimumDt.HasValue - ? "SELECT * FROM F4101 WHERE UPMJ >= :MinDate" - : "SELECT * FROM F4101"; - - var results = await connection.QueryAsync(sql, new { MinDate = minimumDt }); - foreach (var item in results) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetWorkCentersAsync( - DateTime? minimumDt = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - await using var connection = new OracleConnection(_connectionString); - await connection.OpenAsync(cancellationToken); - - var sql = minimumDt.HasValue - ? "SELECT * FROM F30006 WHERE UPMJ >= :MinDate" - : "SELECT * FROM F30006"; - - var results = await connection.QueryAsync(sql, new { MinDate = minimumDt }); - foreach (var item in results) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetProfitCentersAsync( - DateTime? minimumDt = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - await using var connection = new OracleConnection(_connectionString); - await connection.OpenAsync(cancellationToken); - - // TODO: Implement actual query for profit centers - var sql = "SELECT * FROM F0006"; - - var results = await connection.QueryAsync(sql); - foreach (var item in results) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetUsersAsync( - DateTime? minimumDt = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - await using var connection = new OracleConnection(_connectionString); - await connection.OpenAsync(cancellationToken); - - var sql = minimumDt.HasValue - ? "SELECT * FROM F0092 WHERE UPMJ >= :MinDate" - : "SELECT * FROM F0092"; - - var results = await connection.QueryAsync(sql, new { MinDate = minimumDt }); - foreach (var item in results) - { - yield return item; - } - } - - /// - public async IAsyncEnumerable GetBranchesAsync( - DateTime? minimumDt = null, - [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - await using var connection = new OracleConnection(_connectionString); - await connection.OpenAsync(cancellationToken); - - // TODO: Implement actual query for branches - var sql = "SELECT * FROM F0101"; - - var results = await connection.QueryAsync(sql); - foreach (var item in results) - { - yield return item; - } - } -} diff --git a/NEW/tests/JdeScoping.DataAccess.Tests/CmsRepositoryTests.cs b/NEW/tests/JdeScoping.DataAccess.Tests/CmsRepositoryTests.cs deleted file mode 100644 index d21eb10..0000000 --- a/NEW/tests/JdeScoping.DataAccess.Tests/CmsRepositoryTests.cs +++ /dev/null @@ -1,228 +0,0 @@ -using JdeScoping.DataAccess.Options; -using JdeScoping.DataAccess.Exceptions; -using JdeScoping.DataAccess.Interfaces; -using JdeScoping.DataAccess.Repositories; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using NSubstitute; -using NSubstitute.ExceptionExtensions; -using Shouldly; -using Xunit; - -namespace JdeScoping.DataAccess.Tests; - -/// -/// Unit tests for CmsRepository. -/// -public class CmsRepositoryTests -{ - private readonly IDbConnectionFactory _connectionFactory; - private readonly ILogger _logger; - private readonly IOptions _options; - - public CmsRepositoryTests() - { - _connectionFactory = Substitute.For(); - _logger = Substitute.For>(); - _options = Microsoft.Extensions.Options.Options.Create(new DataAccessOptions - { - DefaultTimeoutSeconds = 30, - MisDataTimeoutSeconds = 60000 - }); - } - - #region Constructor Tests - - [Fact] - public void Constructor_NullConnectionFactory_ThrowsArgumentNullException() - { - // Act & Assert - Should.Throw( - () => new CmsRepository(null!, _logger, _options)) - .ParamName.ShouldBe("connectionFactory"); - } - - [Fact] - public void Constructor_NullLogger_ThrowsArgumentNullException() - { - // Act & Assert - Should.Throw( - () => new CmsRepository(_connectionFactory, null!, _options)) - .ParamName.ShouldBe("logger"); - } - - [Fact] - public void Constructor_NullOptions_ThrowsArgumentNullException() - { - // Act & Assert - Should.Throw( - () => new CmsRepository(_connectionFactory, _logger, null!)) - .ParamName.ShouldBe("options"); - } - - [Fact] - public void Constructor_ValidParameters_CreatesInstance() - { - // Act - var repository = new CmsRepository(_connectionFactory, _logger, _options); - - // Assert - repository.ShouldNotBeNull(); - } - - #endregion - - #region GetMisDataAsync Tests - - [Fact] - public async Task GetMisDataAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateCmsConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "CMS")); - - var repository = new CmsRepository(_connectionFactory, _logger, _options); - - // Act & Assert - var ex = await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetMisDataAsync()) - { - } - }); - - ex.DataSource.ShouldBe("CMS"); - } - - [Fact] - public async Task GetMisDataAsync_UsesCmsConnection() - { - // Arrange - _connectionFactory.CreateCmsConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "CMS")); - - var repository = new CmsRepository(_connectionFactory, _logger, _options); - - // Act - try - { - await foreach (var _ in repository.GetMisDataAsync()) - { - } - } - catch (ConnectionException) - { - // Expected - } - - // Assert - verify correct connection factory method was called - await _connectionFactory.Received(1).CreateCmsConnectionAsync(Arg.Any()); - } - - #endregion - - #region Cancellation Tests - - [Fact] - public async Task GetMisDataAsync_CancellationRequested_ThrowsOperationCanceledException() - { - // Arrange - using var cts = new CancellationTokenSource(); - cts.Cancel(); - - _connectionFactory.CreateCmsConnectionAsync(Arg.Any()) - .ThrowsAsync(new OperationCanceledException(cts.Token)); - - var repository = new CmsRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetMisDataAsync(ct: cts.Token)) - { - } - }); - } - - #endregion - - #region Incremental Sync Tests - - [Fact] - public async Task GetMisDataAsync_WithLastUpdateDT_UsesFilteredQuery() - { - // Arrange - var lastUpdate = new DateTime(2024, 1, 15, 10, 30, 0); - _connectionFactory.CreateCmsConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "CMS")); - - var repository = new CmsRepository(_connectionFactory, _logger, _options); - - // Act & Assert - this just verifies the method accepts the parameter - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetMisDataAsync(lastUpdate)) - { - } - }); - } - - [Fact] - public async Task GetMisDataAsync_WithoutLastUpdateDT_UsesFullQuery() - { - // Arrange - _connectionFactory.CreateCmsConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "CMS")); - - var repository = new CmsRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetMisDataAsync()) - { - } - }); - } - - #endregion - - #region Timeout Configuration Tests - - [Fact] - public void Constructor_UsesMisDataTimeout() - { - // Arrange - var customOptions = Microsoft.Extensions.Options.Options.Create(new DataAccessOptions - { - MisDataTimeoutSeconds = 999999 - }); - - // Act - var repository = new CmsRepository(_connectionFactory, _logger, customOptions); - - // Assert - repository.ShouldNotBeNull(); - // The timeout value is internal, verified through behavior - } - - [Fact] - public void Constructor_DefaultMisDataTimeout_Is60000Seconds() - { - // Arrange - var defaultOptions = Microsoft.Extensions.Options.Options.Create(new DataAccessOptions()); - - // Act - var repository = new CmsRepository(_connectionFactory, _logger, defaultOptions); - - // Assert - repository.ShouldNotBeNull(); - // Default timeout of 60000 seconds is verified implicitly - } - - #endregion -} diff --git a/NEW/tests/JdeScoping.DataAccess.Tests/JdeRepositoryTests.cs b/NEW/tests/JdeScoping.DataAccess.Tests/JdeRepositoryTests.cs deleted file mode 100644 index da1bd82..0000000 --- a/NEW/tests/JdeScoping.DataAccess.Tests/JdeRepositoryTests.cs +++ /dev/null @@ -1,674 +0,0 @@ -using JdeScoping.DataAccess.Options; -using JdeScoping.DataAccess.Exceptions; -using JdeScoping.DataAccess.Interfaces; -using JdeScoping.DataAccess.Repositories; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; -using NSubstitute; -using NSubstitute.ExceptionExtensions; -using Shouldly; -using Xunit; - -namespace JdeScoping.DataAccess.Tests; - -/// -/// Unit tests for JdeRepository. -/// -public class JdeRepositoryTests -{ - private readonly IDbConnectionFactory _connectionFactory; - private readonly ILogger _logger; - private readonly IOptions _options; - - public JdeRepositoryTests() - { - _connectionFactory = Substitute.For(); - _logger = Substitute.For>(); - _options = Microsoft.Extensions.Options.Options.Create(new DataAccessOptions - { - DefaultTimeoutSeconds = 30, - LotUsageTimeoutSeconds = 60, - ProductionSchema = "PRODDTA", - ArchiveSchema = "ARCDTAPD", - StageSchema = "JDESTAGE" - }); - } - - #region Constructor Tests - - [Fact] - public void Constructor_NullConnectionFactory_ThrowsArgumentNullException() - { - // Act & Assert - Should.Throw( - () => new JdeRepository(null!, _logger, _options)) - .ParamName.ShouldBe("connectionFactory"); - } - - [Fact] - public void Constructor_NullLogger_ThrowsArgumentNullException() - { - // Act & Assert - Should.Throw( - () => new JdeRepository(_connectionFactory, null!, _options)) - .ParamName.ShouldBe("logger"); - } - - [Fact] - public void Constructor_NullOptions_ThrowsArgumentNullException() - { - // Act & Assert - Should.Throw( - () => new JdeRepository(_connectionFactory, _logger, null!)) - .ParamName.ShouldBe("options"); - } - - [Fact] - public void Constructor_ValidParameters_CreatesInstance() - { - // Act - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Assert - repository.ShouldNotBeNull(); - } - - #endregion - - #region Schema Replacement Tests - Work Orders - - [Fact] - public async Task GetWorkOrdersAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - var ex = await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetWorkOrdersAsync()) - { - } - }); - - ex.DataSource.ShouldBe("JDE"); - } - - [Fact] - public async Task GetWorkOrdersArchiveAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - var ex = await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetWorkOrdersArchiveAsync()) - { - } - }); - - ex.DataSource.ShouldBe("JDE"); - } - - #endregion - - #region Schema Replacement Tests - Work Order Steps - - [Fact] - public async Task GetWorkOrderStepsAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - var ex = await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetWorkOrderStepsAsync()) - { - } - }); - - ex.DataSource.ShouldBe("JDE"); - } - - [Fact] - public async Task GetWorkOrderStepsArchiveAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetWorkOrderStepsArchiveAsync()) - { - } - }); - } - - #endregion - - #region Schema Replacement Tests - Work Order Times - - [Fact] - public async Task GetWorkOrderTimesAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetWorkOrderTimesAsync()) - { - } - }); - } - - [Fact] - public async Task GetWorkOrderTimesArchiveAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetWorkOrderTimesArchiveAsync()) - { - } - }); - } - - #endregion - - #region Schema Replacement Tests - Work Order Routings - - [Fact] - public async Task GetWorkOrderRoutingsAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetWorkOrderRoutingsAsync()) - { - } - }); - } - - #endregion - - #region Schema Replacement Tests - Work Order Components - - [Fact] - public async Task GetWorkOrderComponentsAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetWorkOrderComponentsAsync()) - { - } - }); - } - - [Fact] - public async Task GetWorkOrderComponentsArchiveAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetWorkOrderComponentsArchiveAsync()) - { - } - }); - } - - #endregion - - #region Schema Replacement Tests - Lots - - [Fact] - public async Task GetLotsAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetLotsAsync()) - { - } - }); - } - - #endregion - - #region Schema Replacement Tests - Lot Usages - - [Fact] - public async Task GetLotUsagesAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetLotUsagesAsync()) - { - } - }); - } - - [Fact] - public async Task GetLotUsagesArchiveAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetLotUsagesArchiveAsync()) - { - } - }); - } - - #endregion - - #region JDE Stage Connection Tests - Lot Locations - - [Fact] - public async Task GetLotLocationsAsync_UsesJdeStageConnection() - { - // Arrange - _connectionFactory.CreateJdeStageConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDEStage")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - verify it uses Stage connection - var ex = await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetLotLocationsAsync()) - { - } - }); - - ex.DataSource.ShouldBe("JDEStage"); - } - - #endregion - - #region Reference Data Tests - - [Fact] - public async Task GetItemsAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetItemsAsync()) - { - } - }); - } - - [Fact] - public async Task GetUsersAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetUsersAsync()) - { - } - }); - } - - [Fact] - public async Task GetBranchesAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetBranchesAsync()) - { - } - }); - } - - [Fact] - public async Task GetProfitCentersAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetProfitCentersAsync()) - { - } - }); - } - - [Fact] - public async Task GetWorkCentersAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetWorkCentersAsync()) - { - } - }); - } - - [Fact] - public async Task GetStatusCodesAsync_UsesJdeStageConnection() - { - // Arrange - _connectionFactory.CreateJdeStageConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDEStage")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - verify it uses Stage connection - var ex = await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetStatusCodesAsync()) - { - } - }); - - ex.DataSource.ShouldBe("JDEStage"); - } - - [Fact] - public async Task GetFunctionCodesAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetFunctionCodesAsync()) - { - } - }); - } - - [Fact] - public async Task GetOrgHierarchyAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetOrgHierarchyAsync()) - { - } - }); - } - - [Fact] - public async Task GetRouteMastersAsync_ConnectionFails_ThrowsConnectionException() - { - // Arrange - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetRouteMastersAsync()) - { - } - }); - } - - #endregion - - #region Cancellation Tests - - [Fact] - public async Task GetWorkOrdersAsync_CancellationRequested_ThrowsOperationCanceledException() - { - // Arrange - using var cts = new CancellationTokenSource(); - cts.Cancel(); - - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new OperationCanceledException(cts.Token)); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetWorkOrdersAsync(ct: cts.Token)) - { - } - }); - } - - [Fact] - public async Task GetLotUsagesAsync_CancellationRequested_ThrowsOperationCanceledException() - { - // Arrange - using var cts = new CancellationTokenSource(); - cts.Cancel(); - - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new OperationCanceledException(cts.Token)); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetLotUsagesAsync(ct: cts.Token)) - { - } - }); - } - - #endregion - - #region Incremental Sync Tests - - [Fact] - public async Task GetWorkOrdersAsync_WithLastUpdateDT_UsesFilteredQuery() - { - // Arrange - var lastUpdate = new DateTime(2024, 1, 15, 10, 30, 0); - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - this just verifies the method accepts the parameter - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetWorkOrdersAsync(lastUpdate)) - { - } - }); - } - - [Fact] - public async Task GetLotsAsync_WithLastUpdateDT_UsesFilteredQuery() - { - // Arrange - var lastUpdate = new DateTime(2024, 1, 15, 10, 30, 0); - _connectionFactory.CreateJdeConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "JDE")); - - var repository = new JdeRepository(_connectionFactory, _logger, _options); - - // Act & Assert - await Should.ThrowAsync( - async () => - { - await foreach (var _ in repository.GetLotsAsync(lastUpdate)) - { - } - }); - } - - #endregion - - #region Options Configuration Tests - - [Fact] - public void Constructor_UsesConfiguredSchemas() - { - // Arrange - var customOptions = Microsoft.Extensions.Options.Options.Create(new DataAccessOptions - { - ProductionSchema = "CUSTOM_PROD", - ArchiveSchema = "CUSTOM_ARC", - StageSchema = "CUSTOM_STG" - }); - - // Act - var repository = new JdeRepository(_connectionFactory, _logger, customOptions); - - // Assert - repository.ShouldNotBeNull(); - // The schema values are internal, verified through integration tests - } - - [Fact] - public void Constructor_UsesConfiguredTimeouts() - { - // Arrange - var customOptions = Microsoft.Extensions.Options.Options.Create(new DataAccessOptions - { - DefaultTimeoutSeconds = 120, - LotUsageTimeoutSeconds = 999999 - }); - - // Act - var repository = new JdeRepository(_connectionFactory, _logger, customOptions); - - // Assert - repository.ShouldNotBeNull(); - // The timeout values are internal, verified through integration tests - } - - #endregion -} diff --git a/NEW/tests/JdeScoping.DataAccess.Tests/LotFinderRepositoryTests.cs b/NEW/tests/JdeScoping.DataAccess.Tests/LotFinderRepositoryTests.cs index cab82c1..20037f7 100644 --- a/NEW/tests/JdeScoping.DataAccess.Tests/LotFinderRepositoryTests.cs +++ b/NEW/tests/JdeScoping.DataAccess.Tests/LotFinderRepositoryTests.cs @@ -1,6 +1,3 @@ -using JdeScoping.Core.Models; -using JdeScoping.Core.Models.Enums; -using JdeScoping.Core.Models.Inventory; using JdeScoping.Core.Models.Search; using JdeScoping.Core.ViewModels; using JdeScoping.DataAccess.Options; @@ -31,8 +28,7 @@ public class LotFinderRepositoryTests _logger = Substitute.For>(); _options = Microsoft.Extensions.Options.Options.Create(new DataAccessOptions { - DefaultTimeoutSeconds = 30, - RebuildIndexTimeoutSeconds = 60 + DefaultTimeoutSeconds = 30 }); } @@ -77,160 +73,6 @@ public class LotFinderRepositoryTests #endregion - #region RebuildIndicesAsync - Table Name Validation Tests - - [Theory] - [InlineData("Branch")] - [InlineData("DataUpdate")] - [InlineData("FunctionCode")] - [InlineData("Item")] - [InlineData("JdeUser")] - [InlineData("Lot")] - [InlineData("LotLocation")] - [InlineData("LotUsage_Curr")] - [InlineData("LotUsage_Hist")] - [InlineData("MisData")] - [InlineData("OrgHierarchy")] - [InlineData("ProfitCenter")] - [InlineData("RouteMaster")] - [InlineData("Search")] - [InlineData("StatusCode")] - [InlineData("WorkCenter")] - [InlineData("WorkOrder_Curr")] - [InlineData("WorkOrder_Hist")] - [InlineData("WorkOrderComponent_Curr")] - [InlineData("WorkOrderComponent_Hist")] - [InlineData("WorkOrderRouting")] - [InlineData("WorkOrderStep_Curr")] - [InlineData("WorkOrderStep_Hist")] - [InlineData("WorkOrderTime_Curr")] - [InlineData("WorkOrderTime_Hist")] - public async Task RebuildIndicesAsync_ValidTableName_DoesNotThrowArgumentException(string tableName) - { - // Arrange - expect connection exception since we have no real connection - _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection", "LotFinderDB")); - - var repository = new LotFinderRepository(_connectionFactory, _logger, _options); - - // Act & Assert - should throw QueryException (wrapped ConnectionException), not ArgumentException - var ex = await Should.ThrowAsync( - async () => await repository.RebuildIndicesAsync(tableName)); - - ex.QueryName.ShouldBe("SQL_REBUILD_INDICES"); - } - - [Theory] - [InlineData("InvalidTable")] - [InlineData("DropTable")] - [InlineData("Users")] - [InlineData("sys.tables")] - [InlineData("'; DROP TABLE Users; --")] - [InlineData("WorkOrder")] - [InlineData("branch")] // Case-insensitive should still work - public async Task RebuildIndicesAsync_InvalidTableName_ThrowsArgumentException(string tableName) - { - // Arrange - var repository = new LotFinderRepository(_connectionFactory, _logger, _options); - - // Act & Assert - // Note: "branch" is case-insensitive match for "Branch", so it should NOT throw - if (tableName.Equals("branch", StringComparison.OrdinalIgnoreCase)) - { - // Case-insensitive match - will try to connect and throw QueryException - _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection", "LotFinderDB")); - - await Should.ThrowAsync( - async () => await repository.RebuildIndicesAsync(tableName)); - } - else - { - var ex = await Should.ThrowAsync( - async () => await repository.RebuildIndicesAsync(tableName)); - - ex.ParamName.ShouldBe("tableName"); - ex.Message.ShouldContain($"Invalid table name: {tableName}"); - } - } - - #endregion - - #region TruncateTableAsync - Table Name Validation Tests - - [Theory] - [InlineData("Branch")] - [InlineData("Item")] - [InlineData("WorkOrder_Curr")] - public async Task TruncateTableAsync_ValidTableName_DoesNotThrowArgumentException(string tableName) - { - // Arrange - expect connection exception since we have no real connection - _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection", "LotFinderDB")); - - var repository = new LotFinderRepository(_connectionFactory, _logger, _options); - - // Act & Assert - should throw QueryException (wrapped ConnectionException), not ArgumentException - await Should.ThrowAsync( - async () => await repository.TruncateTableAsync(tableName)); - } - - [Theory] - [InlineData("InvalidTable")] - [InlineData("'; DELETE FROM Users; --")] - public async Task TruncateTableAsync_InvalidTableName_ThrowsArgumentException(string tableName) - { - // Arrange - var repository = new LotFinderRepository(_connectionFactory, _logger, _options); - - // Act & Assert - var ex = await Should.ThrowAsync( - async () => await repository.TruncateTableAsync(tableName)); - - ex.ParamName.ShouldBe("tableName"); - ex.Message.ShouldContain($"Invalid table name: {tableName}"); - } - - #endregion - - #region BulkInsertAsync - Table Name Validation Tests - - [Theory] - [InlineData("Branch")] - [InlineData("Item")] - public async Task BulkInsertAsync_ValidTableName_DoesNotThrowArgumentException(string tableName) - { - // Arrange - expect connection exception since we have no real connection - _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection", "LotFinderDB")); - - var repository = new LotFinderRepository(_connectionFactory, _logger, _options); - var records = new List(); - - // Act & Assert - should throw QueryException (wrapped ConnectionException), not ArgumentException - await Should.ThrowAsync( - async () => await repository.BulkInsertAsync(tableName, records)); - } - - [Theory] - [InlineData("InvalidTable")] - [InlineData("'; TRUNCATE TABLE Users; --")] - public async Task BulkInsertAsync_InvalidTableName_ThrowsArgumentException(string tableName) - { - // Arrange - var repository = new LotFinderRepository(_connectionFactory, _logger, _options); - var records = new List(); - - // Act & Assert - var ex = await Should.ThrowAsync( - async () => await repository.BulkInsertAsync(tableName, records)); - - ex.ParamName.ShouldBe("tableName"); - ex.Message.ShouldContain($"Invalid table name: {tableName}"); - } - - #endregion - #region Connection Exception Handling Tests [Fact] @@ -315,38 +157,6 @@ public class LotFinderRepositoryTests ex.QueryName.ShouldBe(SqlObjects.SubmitSearch); } - [Fact] - public async Task UpdateSearchStatusAsync_ConnectionFails_ThrowsQueryException() - { - // Arrange - _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); - - var repository = new LotFinderRepository(_connectionFactory, _logger, _options); - - // Act & Assert - var ex = await Should.ThrowAsync( - async () => await repository.UpdateSearchStatusAsync(1, SearchStatus.Running)); - - ex.QueryName.ShouldBe("SQL_UPDATE_SEARCH_STATUS"); - } - - [Fact] - public async Task UpdateSearchResultsAsync_ConnectionFails_ThrowsQueryException() - { - // Arrange - _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); - - var repository = new LotFinderRepository(_connectionFactory, _logger, _options); - - // Act & Assert - var ex = await Should.ThrowAsync( - async () => await repository.UpdateSearchResultsAsync(1, [1, 2, 3])); - - ex.QueryName.ShouldBe("SQL_UPDATE_SEARCH_RESULTS"); - } - #endregion #region Reference Data Lookup Exception Handling Tests @@ -415,22 +225,6 @@ public class LotFinderRepositoryTests ex.QueryName.ShouldBe("SQL_SEARCH_WORK_CENTERS"); } - [Fact] - public async Task LookupWorkCentersAsync_ConnectionFails_ThrowsQueryException() - { - // Arrange - _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); - - var repository = new LotFinderRepository(_connectionFactory, _logger, _options); - - // Act & Assert - var ex = await Should.ThrowAsync( - async () => await repository.LookupWorkCentersAsync(["WC01"])); - - ex.QueryName.ShouldBe("SQL_LOOKUP_WORK_CENTERS"); - } - [Fact] public async Task SearchProfitCentersAsync_ConnectionFails_ThrowsQueryException() { @@ -447,22 +241,6 @@ public class LotFinderRepositoryTests ex.QueryName.ShouldBe("SQL_SEARCH_PROFIT_CENTERS"); } - [Fact] - public async Task LookupProfitCentersAsync_ConnectionFails_ThrowsQueryException() - { - // Arrange - _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); - - var repository = new LotFinderRepository(_connectionFactory, _logger, _options); - - // Act & Assert - var ex = await Should.ThrowAsync( - async () => await repository.LookupProfitCentersAsync(["PC01"])); - - ex.QueryName.ShouldBe("SQL_LOOKUP_PROFIT_CENTERS"); - } - [Fact] public async Task SearchUsersAsync_ConnectionFails_ThrowsQueryException() { @@ -479,22 +257,6 @@ public class LotFinderRepositoryTests ex.QueryName.ShouldBe("SQL_SEARCH_USERS"); } - [Fact] - public async Task LookupUsersAsync_ConnectionFails_ThrowsQueryException() - { - // Arrange - _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); - - var repository = new LotFinderRepository(_connectionFactory, _logger, _options); - - // Act & Assert - var ex = await Should.ThrowAsync( - async () => await repository.LookupUsersAsync(["USER01"])); - - ex.QueryName.ShouldBe("SQL_LOOKUP_USERS"); - } - [Fact] public async Task LookupLotsAsync_ConnectionFails_ThrowsQueryException() { @@ -532,38 +294,6 @@ public class LotFinderRepositoryTests ex.QueryName.ShouldBe("SQL_GET_LAST_DATA_UPDATES"); } - [Fact] - public async Task GetTableSpecAsync_ConnectionFails_ThrowsQueryException() - { - // Arrange - _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); - - var repository = new LotFinderRepository(_connectionFactory, _logger, _options); - - // Act & Assert - var ex = await Should.ThrowAsync( - async () => await repository.GetTableSpecAsync("Item")); - - ex.QueryName.ShouldBe("SQL_GET_TABLE_COLUMNS"); - } - - [Fact] - public async Task PostProcessMisDataAsync_ConnectionFails_ThrowsQueryException() - { - // Arrange - _connectionFactory.CreateLotFinderConnectionAsync(Arg.Any()) - .ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB")); - - var repository = new LotFinderRepository(_connectionFactory, _logger, _options); - - // Act & Assert - var ex = await Should.ThrowAsync( - async () => await repository.PostProcessMisDataAsync()); - - ex.QueryName.ShouldBe("SQL_POSTPROCESS_MISDATA"); - } - #endregion #region Cancellation Tests diff --git a/NEW/tests/JdeScoping.DataAccess.Tests/Models/SearchResultTests.cs b/NEW/tests/JdeScoping.DataAccess.Tests/Models/SearchResultTests.cs index 3a2792c..deb6a70 100644 --- a/NEW/tests/JdeScoping.DataAccess.Tests/Models/SearchResultTests.cs +++ b/NEW/tests/JdeScoping.DataAccess.Tests/Models/SearchResultTests.cs @@ -1,4 +1,4 @@ -using JdeScoping.DataAccess.Models.Results; +using JdeScoping.Core.Models.SearchResults; using Shouldly; using Xunit; diff --git a/PLANS/2026-01-06-fluent-excel-mapping-implementation.md b/PLANS/2026-01-06-fluent-excel-mapping-implementation.md new file mode 100644 index 0000000..92fcf30 --- /dev/null +++ b/PLANS/2026-01-06-fluent-excel-mapping-implementation.md @@ -0,0 +1,1432 @@ +# Fluent Excel Mapping Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Replace attribute-based Excel column configuration with fluent mapping classes, moving result models to Core as pure POCOs. + +**Architecture:** External metadata configuration pattern (like Entity Framework Fluent API). Core models remain pure domain objects without presentation concerns. ExcelIO owns all Excel-specific configuration via fluent map classes. + +**Tech Stack:** C# records, expression trees for property access, ClosedXML for Excel generation. + +--- + +## Phase 1: Create Mapping Infrastructure in ExcelIO + +### Task 1: Create ColumnDefinition class + +**Files:** +- Create: `src/JdeScoping.ExcelIO/Mapping/ColumnDefinition.cs` + +**Step 1: Create the Mapping directory** + +```bash +mkdir -p src/JdeScoping.ExcelIO/Mapping +``` + +**Step 2: Write ColumnDefinition class** + +```csharp +namespace JdeScoping.ExcelIO.Mapping; + +/// +/// Defines how a property maps to an Excel column. +/// +public sealed class ColumnDefinition +{ + /// Property name for debugging/error messages. + public required string PropertyName { get; init; } + + /// Compiled delegate to get property value from object. + public required Func ValueGetter { get; init; } + + /// Property type for formatting decisions. + public required Type PropertyType { get; init; } + + /// Column display order (lower = leftmost). + public int Order { get; set; } + + /// Column header text. + public string HeaderText { get; set; } = string.Empty; + + /// Excel number format string. + public string Format { get; set; } = "@"; + + /// Auto-size column width. + public bool AutoWidth { get; set; } = true; + + /// Manual column width (when AutoWidth = false). + public double Width { get; set; } + + /// Enable text wrapping. + public bool WrapText { get; set; } +} +``` + +**Step 3: Verify file compiles** + +Run: `dotnet build src/JdeScoping.ExcelIO` +Expected: Success + +**Step 4: Commit** + +```bash +git add src/JdeScoping.ExcelIO/Mapping/ColumnDefinition.cs +git commit -m "feat(ExcelIO): add ColumnDefinition for fluent mapping" +``` + +--- + +### Task 2: Create ColumnBuilder fluent API + +**Files:** +- Create: `src/JdeScoping.ExcelIO/Mapping/ColumnBuilder.cs` + +**Step 1: Write ColumnBuilder class** + +```csharp +namespace JdeScoping.ExcelIO.Mapping; + +/// +/// Fluent builder for configuring Excel column properties. +/// +/// The model type being mapped. +/// The property type. +public sealed class ColumnBuilder +{ + private readonly ColumnDefinition _definition; + + internal ColumnBuilder(ColumnDefinition definition) + { + _definition = definition; + } + + /// Sets the column display order. + public ColumnBuilder Order(int order) + { + _definition.Order = order; + return this; + } + + /// Sets the column header text. + public ColumnBuilder Header(string text) + { + _definition.HeaderText = text; + return this; + } + + /// Sets the Excel number format. + public ColumnBuilder Format(string format) + { + _definition.Format = format; + return this; + } + + /// Sets a fixed column width (disables auto-width). + public ColumnBuilder Width(double width) + { + _definition.AutoWidth = false; + _definition.Width = width; + return this; + } + + /// Enables text wrapping for the column. + public ColumnBuilder WrapText(bool wrap = true) + { + _definition.WrapText = wrap; + return this; + } +} +``` + +**Step 2: Verify file compiles** + +Run: `dotnet build src/JdeScoping.ExcelIO` +Expected: Success + +**Step 3: Commit** + +```bash +git add src/JdeScoping.ExcelIO/Mapping/ColumnBuilder.cs +git commit -m "feat(ExcelIO): add ColumnBuilder fluent API" +``` + +--- + +### Task 3: Create ExcelClassMap base class + +**Files:** +- Create: `src/JdeScoping.ExcelIO/Mapping/ExcelClassMap.cs` + +**Step 1: Write ExcelClassMap and IExcelClassMap** + +```csharp +using System.Linq.Expressions; + +namespace JdeScoping.ExcelIO.Mapping; + +/// +/// Interface for Excel class maps (non-generic access). +/// +public interface IExcelClassMap +{ + /// The type this map applies to. + Type MappedType { get; } + + /// Excel table name (for named ranges). + string? TableName { get; } + + /// Worksheet tab name. + string? TabName { get; } + + /// Ordered column definitions. + IReadOnlyList Columns { get; } +} + +/// +/// Base class for defining Excel column mappings via fluent API. +/// +/// The model type to map. +public abstract class ExcelClassMap : IExcelClassMap +{ + private readonly List _columns = []; + + /// + public Type MappedType => typeof(T); + + /// + public string? TableName { get; private set; } + + /// + public string? TabName { get; private set; } + + /// + public IReadOnlyList Columns => + _columns.OrderBy(c => c.Order).ThenBy(c => c.PropertyName).ToList(); + + /// + /// Configures the table and tab names for this model. + /// + protected void Table(string tableName, string tabName) + { + TableName = tableName; + TabName = tabName; + } + + /// + /// Maps a property to an Excel column. + /// + protected ColumnBuilder Map(Expression> property) + { + var memberExpr = property.Body as MemberExpression + ?? throw new ArgumentException("Expression must be a property access", nameof(property)); + + var propertyName = memberExpr.Member.Name; + var compiled = property.Compile(); + + var definition = new ColumnDefinition + { + PropertyName = propertyName, + PropertyType = typeof(TProperty), + ValueGetter = obj => compiled((T)obj), + HeaderText = propertyName // Default to property name + }; + + _columns.Add(definition); + return new ColumnBuilder(definition); + } +} +``` + +**Step 2: Verify file compiles** + +Run: `dotnet build src/JdeScoping.ExcelIO` +Expected: Success + +**Step 3: Commit** + +```bash +git add src/JdeScoping.ExcelIO/Mapping/ExcelClassMap.cs +git commit -m "feat(ExcelIO): add ExcelClassMap base class with fluent Map method" +``` + +--- + +### Task 4: Create ExcelMapRegistry + +**Files:** +- Create: `src/JdeScoping.ExcelIO/Mapping/ExcelMapRegistry.cs` + +**Step 1: Write ExcelMapRegistry class** + +```csharp +namespace JdeScoping.ExcelIO.Mapping; + +/// +/// Registry for Excel class maps, used for DI and lookup. +/// +public sealed class ExcelMapRegistry +{ + private readonly Dictionary _maps = new(); + + /// + /// Registers a map for a type. + /// + public void Register(ExcelClassMap map) + { + _maps[typeof(T)] = map; + } + + /// + /// Gets the map for a type. + /// + public IExcelClassMap GetMap() => GetMap(typeof(T)); + + /// + /// Gets the map for a type. + /// + public IExcelClassMap GetMap(Type type) + { + if (_maps.TryGetValue(type, out var map)) + return map; + + throw new InvalidOperationException( + $"No Excel map registered for type {type.Name}. " + + $"Register a map using ExcelMapRegistry.Register()."); + } + + /// + /// Checks if a map exists for a type. + /// + public bool HasMap() => HasMap(typeof(T)); + + /// + /// Checks if a map exists for a type. + /// + public bool HasMap(Type type) => _maps.ContainsKey(type); +} +``` + +**Step 2: Verify file compiles** + +Run: `dotnet build src/JdeScoping.ExcelIO` +Expected: Success + +**Step 3: Commit** + +```bash +git add src/JdeScoping.ExcelIO/Mapping/ExcelMapRegistry.cs +git commit -m "feat(ExcelIO): add ExcelMapRegistry for DI integration" +``` + +--- + +### Task 5: Create ExcelFormats constants + +**Files:** +- Create: `src/JdeScoping.ExcelIO/Mapping/ExcelFormats.cs` + +**Step 1: Write ExcelFormats class** + +```csharp +namespace JdeScoping.ExcelIO.Mapping; + +/// +/// Standard Excel format strings for column configuration. +/// +public static class ExcelFormats +{ + /// Text format (default). + public const string Text = "@"; + + /// Date format: MM/dd/yyyy + public const string Date = "[$-409]MM/dd/yyyy;@"; + + /// Timestamp format: m/d/yy h:mm AM/PM + public const string Timestamp = "[$-409]m/d/yy h:mm AM/PM;@"; + + /// Default width for wrapped text columns. + public const double WrappedColumnWidth = 65; +} +``` + +**Step 2: Verify file compiles** + +Run: `dotnet build src/JdeScoping.ExcelIO` +Expected: Success + +**Step 3: Commit** + +```bash +git add src/JdeScoping.ExcelIO/Mapping/ExcelFormats.cs +git commit -m "feat(ExcelIO): add ExcelFormats constants" +``` + +--- + +### Task 6: Write unit tests for mapping infrastructure + +**Files:** +- Create: `tests/JdeScoping.ExcelIO.Tests/Mapping/ExcelClassMapTests.cs` + +**Step 1: Write test class** + +```csharp +using JdeScoping.ExcelIO.Mapping; +using Shouldly; +using Xunit; + +namespace JdeScoping.ExcelIO.Tests.Mapping; + +public class ExcelClassMapTests +{ + // Test model + private sealed class TestModel + { + public int Id { get; init; } + public string Name { get; init; } = string.Empty; + public DateTime CreatedAt { get; init; } + } + + // Test map + private sealed class TestModelMap : ExcelClassMap + { + public TestModelMap() + { + Table("Test_Table", "Test Tab"); + + Map(x => x.Id).Order(10).Header("ID Number"); + Map(x => x.Name).Order(20).Header("Full Name"); + Map(x => x.CreatedAt).Order(30).Header("Created").Format(ExcelFormats.Timestamp); + } + } + + [Fact] + public void MappedType_ReturnsCorrectType() + { + var map = new TestModelMap(); + map.MappedType.ShouldBe(typeof(TestModel)); + } + + [Fact] + public void Table_SetsTableAndTabName() + { + var map = new TestModelMap(); + map.TableName.ShouldBe("Test_Table"); + map.TabName.ShouldBe("Test Tab"); + } + + [Fact] + public void Columns_ReturnsOrderedColumns() + { + var map = new TestModelMap(); + var columns = map.Columns; + + columns.Count.ShouldBe(3); + columns[0].PropertyName.ShouldBe("Id"); + columns[1].PropertyName.ShouldBe("Name"); + columns[2].PropertyName.ShouldBe("CreatedAt"); + } + + [Fact] + public void Map_SetsHeaderText() + { + var map = new TestModelMap(); + var columns = map.Columns; + + columns[0].HeaderText.ShouldBe("ID Number"); + columns[1].HeaderText.ShouldBe("Full Name"); + columns[2].HeaderText.ShouldBe("Created"); + } + + [Fact] + public void Map_SetsFormat() + { + var map = new TestModelMap(); + var columns = map.Columns; + + columns[2].Format.ShouldBe(ExcelFormats.Timestamp); + } + + [Fact] + public void ValueGetter_ExtractsPropertyValue() + { + var map = new TestModelMap(); + var columns = map.Columns; + var testObj = new TestModel { Id = 42, Name = "Test", CreatedAt = new DateTime(2024, 1, 15) }; + + columns[0].ValueGetter(testObj).ShouldBe(42); + columns[1].ValueGetter(testObj).ShouldBe("Test"); + columns[2].ValueGetter(testObj).ShouldBe(new DateTime(2024, 1, 15)); + } +} + +public class ExcelMapRegistryTests +{ + private sealed class DummyModel { public int Id { get; init; } } + private sealed class DummyMap : ExcelClassMap + { + public DummyMap() { Map(x => x.Id).Order(1).Header("ID"); } + } + + [Fact] + public void Register_AndGetMap_ReturnsMap() + { + var registry = new ExcelMapRegistry(); + var map = new DummyMap(); + + registry.Register(map); + var retrieved = registry.GetMap(); + + retrieved.ShouldBe(map); + } + + [Fact] + public void GetMap_UnregisteredType_ThrowsInvalidOperationException() + { + var registry = new ExcelMapRegistry(); + + Should.Throw(() => registry.GetMap()); + } + + [Fact] + public void HasMap_RegisteredType_ReturnsTrue() + { + var registry = new ExcelMapRegistry(); + registry.Register(new DummyMap()); + + registry.HasMap().ShouldBeTrue(); + } + + [Fact] + public void HasMap_UnregisteredType_ReturnsFalse() + { + var registry = new ExcelMapRegistry(); + + registry.HasMap().ShouldBeFalse(); + } +} +``` + +**Step 2: Run tests** + +Run: `dotnet test tests/JdeScoping.ExcelIO.Tests --filter "FullyQualifiedName~ExcelClassMapTests|FullyQualifiedName~ExcelMapRegistryTests"` +Expected: All tests pass + +**Step 3: Commit** + +```bash +git add tests/JdeScoping.ExcelIO.Tests/Mapping/ExcelClassMapTests.cs +git commit -m "test(ExcelIO): add unit tests for fluent mapping infrastructure" +``` + +--- + +## Phase 2: Move Models to Core + +### Task 7: Create SearchResults models in Core + +**Files:** +- Create: `src/JdeScoping.Core/Models/SearchResults/SearchResult.cs` +- Create: `src/JdeScoping.Core/Models/SearchResults/MisSearchResult.cs` +- Create: `src/JdeScoping.Core/Models/SearchResults/MisNonMatchSearchResult.cs` +- Create: `src/JdeScoping.Core/Models/SearchResults/SearchModel.cs` + +**Step 1: Create directory** + +```bash +mkdir -p src/JdeScoping.Core/Models/SearchResults +``` + +**Step 2: Write SearchResult.cs (pure POCO, no attributes)** + +```csharp +namespace JdeScoping.Core.Models.SearchResults; + +/// +/// JDE search result - work order with current status. +/// +public sealed class SearchResult +{ + public long WorkOrderNumber { get; init; } + public string WorkOrderBranchCode { get; init; } = string.Empty; + public string LotNumber { get; init; } = string.Empty; + public string ItemNumber { get; init; } = string.Empty; + public string PlanningFamily { get; init; } = string.Empty; + public string StockingType { get; init; } = string.Empty; + public decimal OrderQuantity { get; init; } + public decimal HeldQuantity { get; init; } + public decimal ScrappedQuantity { get; init; } + public decimal ShippedQuantity { get; init; } + public string StepBranchCode { get; init; } = string.Empty; + public decimal StepNumber { get; init; } + public string StepDescription { get; init; } = string.Empty; + public string FunctionOperationDescription { get; init; } = string.Empty; + public DateTime StepUpdateDt { get; init; } + public string StatusCode { get; init; } = string.Empty; + public string StatusDescription { get; init; } = string.Empty; + public DateTime? StatusUpdateDt { get; init; } + + // Inclusion flags + public bool ManuallySpecified { get; init; } + public bool SplitOrder { get; init; } + public bool Cardex { get; init; } + public bool PartsList { get; init; } + public bool Flagged { get; init; } + + /// + /// Computed reason why this work order was included in results. + /// + public string InclusionReason => (ManuallySpecified, Flagged, Cardex, PartsList, SplitOrder) switch + { + (true, _, _, _, _) => "ManuallySpecified", + (_, true, _, _, _) => "Flagged", + (_, _, true, true, _) => "ComponentUsage (CARDEX + Parts List)", + (_, _, true, false, _) => "ComponentUsage (CARDEX)", + (_, _, false, true, _) => "ComponentUsage (Parts List)", + (_, _, _, _, true) => "Split order", + _ => "UNKNOWN" + }; +} +``` + +**Step 3: Write MisSearchResult.cs** + +```csharp +namespace JdeScoping.Core.Models.SearchResults; + +/// +/// MIS (Manufacturing Instruction Sheet) data result. +/// +public sealed class MisSearchResult +{ + public string ItemNumber { get; init; } = string.Empty; + public string ItemDescription { get; init; } = string.Empty; + public string SequenceNumber { get; init; } = string.Empty; + public string MisNumber { get; init; } = string.Empty; + public string RevId { get; init; } = string.Empty; + public string Status { get; init; } = string.Empty; + public DateTime? ReleaseDate { get; init; } + public string BranchCode { get; init; } = string.Empty; + public decimal JobStepSequenceNumber { get; init; } + public decimal? MatchedSequenceNumber { get; init; } + public bool RoutingMatch { get; init; } + public bool MasterMatch { get; init; } + public string FunctionOperationDescription { get; init; } = string.Empty; + public string CharNumber { get; init; } = string.Empty; + public string TestDescription { get; init; } = string.Empty; + public string SamplingType { get; init; } = string.Empty; + public string SamplingValue { get; init; } = string.Empty; + public string ToolsGauges { get; init; } = string.Empty; + public string WorkInstructions { get; init; } = string.Empty; +} +``` + +**Step 4: Write MisNonMatchSearchResult.cs** + +```csharp +namespace JdeScoping.Core.Models.SearchResults; + +/// +/// MIS non-match investigation result. +/// +public sealed class MisNonMatchSearchResult +{ + public string WorkCenterCode { get; init; } = string.Empty; + public long WorkOrderNumber { get; init; } + public DateTime WorkOrderStartDate { get; init; } + public decimal JobStepNumber { get; init; } + public string JobStepDescription { get; init; } = string.Empty; + public DateTime? JobStepEndDate { get; init; } + public string FunctionCode { get; init; } = string.Empty; + public bool WasJobStepAdded { get; init; } + public decimal? MatchedJobStepNumber { get; init; } + public string ItemNumber { get; init; } = string.Empty; + public string ItemDescription { get; init; } = string.Empty; + public string RoutingType { get; init; } = string.Empty; +} +``` + +**Step 5: Write SearchModel.cs** + +```csharp +namespace JdeScoping.Core.Models.SearchResults; + +/// +/// Aggregates search metadata and results for export. +/// +public class SearchModel +{ + public int Id { get; set; } + public string UserName { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public DateTime? SubmitDt { get; set; } + public DateTime? StartDt { get; set; } + public DateTime? EndDt { get; set; } + public bool ExtractMisData { get; set; } + + public List Results { get; set; } = []; + public List MisResults { get; set; } = []; + public List MisNonMatchResults { get; set; } = []; +} +``` + +**Step 6: Verify Core compiles** + +Run: `dotnet build src/JdeScoping.Core` +Expected: Success + +**Step 7: Commit** + +```bash +git add src/JdeScoping.Core/Models/SearchResults/ +git commit -m "feat(Core): add SearchResults models as pure POCOs" +``` + +--- + +### Task 8: Create fluent maps in ExcelIO + +**Files:** +- Create: `src/JdeScoping.ExcelIO/Mapping/Maps/SearchResultMap.cs` +- Create: `src/JdeScoping.ExcelIO/Mapping/Maps/MisSearchResultMap.cs` +- Create: `src/JdeScoping.ExcelIO/Mapping/Maps/MisNonMatchSearchResultMap.cs` + +**Step 1: Create Maps directory** + +```bash +mkdir -p src/JdeScoping.ExcelIO/Mapping/Maps +``` + +**Step 2: Write SearchResultMap.cs** + +```csharp +using JdeScoping.Core.Models.SearchResults; + +namespace JdeScoping.ExcelIO.Mapping.Maps; + +/// +/// Excel column mapping for SearchResult. +/// +public sealed class SearchResultMap : ExcelClassMap +{ + public SearchResultMap() + { + Table("Search_Results", "Search Results"); + + Map(x => x.WorkOrderNumber).Order(10).Header("Work Order Number"); + Map(x => x.WorkOrderBranchCode).Order(20).Header("Work Order Branch Code"); + Map(x => x.LotNumber).Order(30).Header("Lot Number"); + Map(x => x.ItemNumber).Order(40).Header("Item Number"); + Map(x => x.PlanningFamily).Order(50).Header("Planning Family"); + Map(x => x.StockingType).Order(55).Header("Stocking Type"); + Map(x => x.OrderQuantity).Order(60).Header("Order Quantity"); + Map(x => x.HeldQuantity).Order(70).Header("Held Quantity"); + Map(x => x.ScrappedQuantity).Order(80).Header("Scrapped Quantity"); + Map(x => x.ShippedQuantity).Order(90).Header("Shipped Quantity"); + Map(x => x.StepBranchCode).Order(100).Header("Operation Step Branch Code"); + Map(x => x.StepNumber).Order(110).Header("Operation Step"); + Map(x => x.StepDescription).Order(120).Header("Operation Step Description"); + Map(x => x.FunctionOperationDescription).Order(130).Header("Function Operation Description"); + Map(x => x.StepUpdateDt).Order(140).Header("Operation Step Update Timestamp").Format(ExcelFormats.Timestamp); + Map(x => x.StatusCode).Order(150).Header("Status Code"); + Map(x => x.StatusDescription).Order(160).Header("Status Description"); + Map(x => x.StatusUpdateDt).Order(170).Header("Status Update Timestamp").Format(ExcelFormats.Date); + Map(x => x.InclusionReason).Order(180).Header("Inclusion Reason"); + } +} +``` + +**Step 3: Write MisSearchResultMap.cs** + +```csharp +using JdeScoping.Core.Models.SearchResults; + +namespace JdeScoping.ExcelIO.Mapping.Maps; + +/// +/// Excel column mapping for MisSearchResult. +/// +public sealed class MisSearchResultMap : ExcelClassMap +{ + public MisSearchResultMap() + { + Table("MIS_Info", "MIS Info"); + + Map(x => x.ItemNumber).Order(10).Header("Item Number"); + Map(x => x.SequenceNumber).Order(20).Header("MIS Job Step Sequence Number"); + Map(x => x.MisNumber).Order(30).Header("MIS Number"); + Map(x => x.RevId).Order(40).Header("MIS Revision"); + Map(x => x.ItemDescription).Order(50).Header("Item Description"); + Map(x => x.Status).Order(60).Header("MIS Release Status"); + Map(x => x.ReleaseDate).Order(70).Header("MIS Release Date").Format(ExcelFormats.Timestamp); + Map(x => x.BranchCode).Order(80).Header("Branch Code"); + Map(x => x.JobStepSequenceNumber).Order(90).Header("Job Step Sequence Number"); + Map(x => x.MatchedSequenceNumber).Order(100).Header("Matched Sequence Number"); + Map(x => x.RoutingMatch).Order(110).Header("Matched to F3112Z1?"); + Map(x => x.MasterMatch).Order(120).Header("Matched to F3003?"); + Map(x => x.FunctionOperationDescription).Order(130).Header("Function Operation Description"); + Map(x => x.CharNumber).Order(140).Header("Char Number"); + Map(x => x.TestDescription).Order(150).Header("Test Description").Width(ExcelFormats.WrappedColumnWidth).WrapText(); + Map(x => x.SamplingType).Order(160).Header("Sampling Type"); + Map(x => x.SamplingValue).Order(170).Header("Sampling Value"); + Map(x => x.ToolsGauges).Order(180).Header("Tools & Gauges").Width(ExcelFormats.WrappedColumnWidth).WrapText(); + Map(x => x.WorkInstructions).Order(190).Header("Work Instructions").Width(ExcelFormats.WrappedColumnWidth).WrapText(); + } +} +``` + +**Step 4: Write MisNonMatchSearchResultMap.cs** + +```csharp +using JdeScoping.Core.Models.SearchResults; + +namespace JdeScoping.ExcelIO.Mapping.Maps; + +/// +/// Excel column mapping for MisNonMatchSearchResult. +/// +public sealed class MisNonMatchSearchResultMap : ExcelClassMap +{ + public MisNonMatchSearchResultMap() + { + Table("Investigation", "Investigation"); + + Map(x => x.WorkCenterCode).Order(10).Header("Work Center Code"); + Map(x => x.WorkOrderNumber).Order(20).Header("Work Order Number"); + Map(x => x.WorkOrderStartDate).Order(30).Header("Work Order Start Date").Format(ExcelFormats.Date); + Map(x => x.JobStepNumber).Order(40).Header("Job Step Number"); + Map(x => x.JobStepDescription).Order(50).Header("Function Operation Description"); + Map(x => x.JobStepEndDate).Order(60).Header("Job Step End Date").Format(ExcelFormats.Date); + Map(x => x.FunctionCode).Order(70).Header("Function Code"); + Map(x => x.WasJobStepAdded).Order(75).Header("Was Job Step Added?"); + Map(x => x.MatchedJobStepNumber).Order(76).Header("Matched Job Step Number"); + Map(x => x.ItemNumber).Order(80).Header("Item Number"); + Map(x => x.ItemDescription).Order(90).Header("Item Description"); + Map(x => x.RoutingType).Order(100).Header("Routing Type"); + } +} +``` + +**Step 5: Verify ExcelIO compiles** + +Run: `dotnet build src/JdeScoping.ExcelIO` +Expected: Success + +**Step 6: Commit** + +```bash +git add src/JdeScoping.ExcelIO/Mapping/Maps/ +git commit -m "feat(ExcelIO): add fluent maps for SearchResult models" +``` + +--- + +## Phase 3: Create FluentTableWriter + +### Task 9: Create FluentTableWriter + +**Files:** +- Create: `src/JdeScoping.ExcelIO/Generators/FluentTableWriter.cs` + +**Step 1: Write FluentTableWriter class** + +```csharp +using ClosedXML.Excel; +using JdeScoping.ExcelIO.Formatting; +using JdeScoping.ExcelIO.Mapping; + +namespace JdeScoping.ExcelIO.Generators; + +/// +/// Writes Excel tables using fluent mapping configuration. +/// +public sealed class FluentTableWriter +{ + private readonly ExcelMapRegistry _registry; + + public FluentTableWriter(ExcelMapRegistry registry) + { + _registry = registry; + } + + /// + /// Writes a table to the worksheet using the registered map for type T. + /// + public IXLTable? WriteTable( + IXLWorksheet worksheet, + int startRow, + int startCol, + IEnumerable data, + string? tableNameOverride = null, + bool showHeader = false, + string? headerText = null) + { + var map = _registry.GetMap(); + var columns = map.Columns; + var tableName = tableNameOverride ?? map.TableName ?? typeof(T).Name; + var header = headerText ?? map.TabName ?? string.Empty; + + if (columns.Count == 0) + return null; + + var dataList = data.ToList(); + var baseRow = startRow; + + // Write merged header if requested + if (showHeader && !string.IsNullOrEmpty(header)) + { + var mergedHeaderRange = worksheet.Range(baseRow, startCol, baseRow, startCol + columns.Count - 1); + HeaderFormatter.ApplyHeaderFormat(mergedHeaderRange, header, merge: true); + baseRow++; + } + + // Write column headers + var col = startCol; + foreach (var column in columns) + { + var cell = worksheet.Cell(baseRow, col); + HeaderFormatter.ApplyHeaderFormat(cell, column.HeaderText); + + // Pre-set column formatting + worksheet.Column(col).Style.Alignment.WrapText = column.WrapText; + if (!column.AutoWidth) + { + worksheet.Column(col).Width = column.Width; + } + + col++; + } + + // Write data rows + var row = baseRow + 1; + foreach (var item in dataList) + { + col = startCol; + foreach (var column in columns) + { + var value = column.ValueGetter(item!); + worksheet.Cell(row, col).Value = ConvertToXlValue(value); + col++; + } + row++; + } + + // Handle empty data case + if (dataList.Count == 0) + { + row = baseRow + 1; + } + + // Create table range + var dataRange = worksheet.Range( + baseRow, startCol, + baseRow + dataList.Count, startCol + columns.Count - 1); + + // Create table + var table = dataRange.CreateTable(tableName); + table.Theme = XLTableTheme.TableStyleLight18; + table.ShowTotalsRow = false; + + // Apply column formatting + col = startCol; + var tableStartRow = table.RangeAddress.FirstAddress.RowNumber; + var tableEndRow = table.RangeAddress.LastAddress.RowNumber; + + foreach (var column in columns) + { + // Apply number format + worksheet.Range(tableStartRow, col, tableEndRow, col) + .Style.NumberFormat.Format = column.Format; + + // Apply column width + if (column.WrapText && !column.AutoWidth) + { + worksheet.Column(col).Width = column.Width; + } + else if (column.AutoWidth) + { + worksheet.Column(col).AdjustToContents(); + worksheet.Column(col).Width *= ExcelFormats.DataPaddingFactor; + } + else + { + worksheet.Column(col).Width = column.Width; + } + + col++; + } + + return table; + } + + private static XLCellValue ConvertToXlValue(object? value) + { + return value switch + { + null => Blank.Value, + string s => s, + int i => i, + long l => l, + decimal d => d, + double dbl => dbl, + float f => f, + DateTime dt => dt, + bool b => b, + _ => value.ToString() ?? string.Empty + }; + } +} +``` + +**Step 2: Add DataPaddingFactor to ExcelFormats if missing** + +Check if `ExcelFormats.DataPaddingFactor` exists in `Formatting/ExcelFormats.cs`. If not, add it to `Mapping/ExcelFormats.cs`: + +```csharp +/// Multiplier for auto-width columns. +public const double DataPaddingFactor = 1.1; +``` + +**Step 3: Verify ExcelIO compiles** + +Run: `dotnet build src/JdeScoping.ExcelIO` +Expected: Success + +**Step 4: Commit** + +```bash +git add src/JdeScoping.ExcelIO/Generators/FluentTableWriter.cs +git add src/JdeScoping.ExcelIO/Mapping/ExcelFormats.cs +git commit -m "feat(ExcelIO): add FluentTableWriter using map registry" +``` + +--- + +### Task 10: Register maps in DependencyInjection + +**Files:** +- Modify: `src/JdeScoping.ExcelIO/DependencyInjection.cs` + +**Step 1: Update DependencyInjection.cs** + +Add imports and registry registration: + +```csharp +using JdeScoping.ExcelIO.Mapping; +using JdeScoping.ExcelIO.Mapping.Maps; +``` + +Add to `AddExcelIO` method: + +```csharp +// Register Excel map registry with all maps +services.AddSingleton(sp => +{ + var registry = new ExcelMapRegistry(); + registry.Register(new SearchResultMap()); + registry.Register(new MisSearchResultMap()); + registry.Register(new MisNonMatchSearchResultMap()); + return registry; +}); + +// Register fluent table writer +services.AddSingleton(); +``` + +**Step 2: Verify ExcelIO compiles** + +Run: `dotnet build src/JdeScoping.ExcelIO` +Expected: Success + +**Step 3: Commit** + +```bash +git add src/JdeScoping.ExcelIO/DependencyInjection.cs +git commit -m "feat(ExcelIO): register ExcelMapRegistry and FluentTableWriter in DI" +``` + +--- + +## Phase 4: Migrate ExcelExportService and DataAccess + +### Task 11: Update ExcelExportService to use Core models and FluentTableWriter + +**Files:** +- Modify: `src/JdeScoping.ExcelIO/ExcelExportService.cs` + +**Step 1: Update imports** + +Replace: +```csharp +using JdeScoping.ExcelIO.Models.Reporting; +``` + +With: +```csharp +using JdeScoping.Core.Models.SearchResults; +``` + +**Step 2: Replace AttributeTableWriter with FluentTableWriter** + +Change constructor and field: +```csharp +private readonly FluentTableWriter _tableWriter; + +public ExcelExportService( + ILogger logger, + IOptions options, + CriteriaSheetGenerator criteriaGenerator, + FluentTableWriter tableWriter) +``` + +**Step 3: Update GenerateResultsSheet to use map for tab name** + +```csharp +private void GenerateResultsSheet(XLWorkbook workbook, List results) +{ + var map = _registry.GetMap(); + var tabName = map.TabName ?? "Search Results"; + + var worksheet = workbook.Worksheets.Add(tabName); + var table = _tableWriter.WriteTable(worksheet, 1, 1, results); + + // ... rest unchanged +} +``` + +Inject `ExcelMapRegistry _registry` in constructor and update all sheet generation methods similarly. + +**Step 4: Verify build** + +Run: `dotnet build src/JdeScoping.ExcelIO` +Expected: Success + +**Step 5: Commit** + +```bash +git add src/JdeScoping.ExcelIO/ExcelExportService.cs +git commit -m "refactor(ExcelIO): migrate ExcelExportService to Core models and FluentTableWriter" +``` + +--- + +### Task 12: Update DataAccess to use Core models + +**Files:** +- Modify: `src/JdeScoping.DataAccess/Services/SearchProcessor.cs` + +**Step 1: Update imports** + +Replace: +```csharp +using JdeScoping.DataAccess.Models; +using JdeScoping.DataAccess.Models.Results; +``` + +With: +```csharp +using JdeScoping.Core.Models.SearchResults; +using JdeScoping.DataAccess.Models; // Keep for SearchQueryResult +``` + +**Step 2: Verify build** + +Run: `dotnet build src/JdeScoping.DataAccess` +Expected: Success + +**Step 3: Commit** + +```bash +git add src/JdeScoping.DataAccess/Services/SearchProcessor.cs +git commit -m "refactor(DataAccess): use Core.Models.SearchResults" +``` + +--- + +### Task 13: Update CriteriaSheetGenerator to use Core models + +**Files:** +- Modify: `src/JdeScoping.ExcelIO/Generators/CriteriaSheetGenerator.cs` + +**Step 1: Update imports** + +Replace: +```csharp +using JdeScoping.ExcelIO.Models.Reporting; +``` + +With: +```csharp +using JdeScoping.Core.Models.SearchResults; +``` + +**Step 2: Verify build** + +Run: `dotnet build src/JdeScoping.ExcelIO` +Expected: Success + +**Step 3: Commit** + +```bash +git add src/JdeScoping.ExcelIO/Generators/CriteriaSheetGenerator.cs +git commit -m "refactor(ExcelIO): update CriteriaSheetGenerator to use Core models" +``` + +--- + +## Phase 5: Delete Duplicate Code + +### Task 14: Delete DataAccess Models and Attributes + +**Files:** +- Delete: `src/JdeScoping.DataAccess/Models/Results/` (entire directory) +- Delete: `src/JdeScoping.DataAccess/Models/SearchModel.cs` +- Delete: `src/JdeScoping.DataAccess/Attributes/` (entire directory) + +**Step 1: Verify no remaining usages** + +Run: +```bash +grep -r "JdeScoping.DataAccess.Models.Results" src/ +grep -r "JdeScoping.DataAccess.Attributes" src/ +``` +Expected: No matches (or only in files being deleted) + +**Step 2: Delete files** + +```bash +rm -rf src/JdeScoping.DataAccess/Models/Results/ +rm src/JdeScoping.DataAccess/Models/SearchModel.cs +rm -rf src/JdeScoping.DataAccess/Attributes/ +``` + +**Step 3: Verify build** + +Run: `dotnet build` +Expected: Success + +**Step 4: Commit** + +```bash +git add -A +git commit -m "refactor(DataAccess): remove duplicate Models and Attributes (now in Core/ExcelIO)" +``` + +--- + +### Task 15: Delete ExcelIO duplicate Models/Reporting + +**Files:** +- Delete: `src/JdeScoping.ExcelIO/Models/Reporting/SearchResult.cs` +- Delete: `src/JdeScoping.ExcelIO/Models/Reporting/MisSearchResult.cs` +- Delete: `src/JdeScoping.ExcelIO/Models/Reporting/MisNonMatchSearchResult.cs` +- Delete: `src/JdeScoping.ExcelIO/Models/Reporting/SearchModel.cs` + +Note: Keep filter entry models (`*FilterEntry.cs`, `TimespanFilter.cs`) as they're specific to ExcelIO criteria sheet. + +**Step 1: Verify no remaining usages of old models** + +Run: +```bash +grep -r "JdeScoping.ExcelIO.Models.Reporting.SearchResult" src/ +grep -r "JdeScoping.ExcelIO.Models.Reporting.MisSearchResult" src/ +grep -r "JdeScoping.ExcelIO.Models.Reporting.SearchModel" src/ +``` +Expected: No matches + +**Step 2: Delete files** + +```bash +rm src/JdeScoping.ExcelIO/Models/Reporting/SearchResult.cs +rm src/JdeScoping.ExcelIO/Models/Reporting/MisSearchResult.cs +rm src/JdeScoping.ExcelIO/Models/Reporting/MisNonMatchSearchResult.cs +rm src/JdeScoping.ExcelIO/Models/Reporting/SearchModel.cs +``` + +**Step 3: Verify build** + +Run: `dotnet build` +Expected: Success + +**Step 4: Commit** + +```bash +git add -A +git commit -m "refactor(ExcelIO): remove duplicate result models (now in Core)" +``` + +--- + +### Task 16: Delete old attribute-based infrastructure from ExcelIO + +**Files:** +- Delete: `src/JdeScoping.ExcelIO/Attributes/OutputColumnAttribute.cs` +- Delete: `src/JdeScoping.ExcelIO/Attributes/OutputTableAttribute.cs` +- Delete: `src/JdeScoping.ExcelIO/Helpers/OutputColumnCache.cs` +- Delete: `src/JdeScoping.ExcelIO/Models/OutputColumn.cs` +- Delete: `src/JdeScoping.ExcelIO/Generators/AttributeTableWriter.cs` + +**Step 1: Verify no remaining usages** + +Run: +```bash +grep -r "OutputColumnAttribute" src/ +grep -r "OutputTableAttribute" src/ +grep -r "OutputColumnCache" src/ +grep -r "AttributeTableWriter" src/ +``` +Expected: No matches in source files (tests may still reference) + +**Step 2: Delete files** + +```bash +rm src/JdeScoping.ExcelIO/Attributes/OutputColumnAttribute.cs +rm src/JdeScoping.ExcelIO/Attributes/OutputTableAttribute.cs +rm src/JdeScoping.ExcelIO/Helpers/OutputColumnCache.cs +rm src/JdeScoping.ExcelIO/Models/OutputColumn.cs +rm src/JdeScoping.ExcelIO/Generators/AttributeTableWriter.cs +rmdir src/JdeScoping.ExcelIO/Attributes 2>/dev/null || true +rmdir src/JdeScoping.ExcelIO/Helpers 2>/dev/null || true +``` + +**Step 3: Update DependencyInjection.cs to remove old registrations** + +Remove these lines: +```csharp +services.AddSingleton(); +services.AddSingleton(); +``` + +**Step 4: Verify build** + +Run: `dotnet build` +Expected: Success + +**Step 5: Commit** + +```bash +git add -A +git commit -m "refactor(ExcelIO): remove attribute-based table writer infrastructure" +``` + +--- + +## Phase 6: Update Tests + +### Task 17: Update ExcelIO tests + +**Files:** +- Modify/Delete tests that reference old attribute-based infrastructure +- Update tests to use Core models + +**Step 1: Run existing tests to identify failures** + +Run: `dotnet test tests/JdeScoping.ExcelIO.Tests` +Expected: Some failures from removed types + +**Step 2: Update test files to use Core models** + +Update imports in test files from: +```csharp +using JdeScoping.ExcelIO.Models.Reporting; +``` +To: +```csharp +using JdeScoping.Core.Models.SearchResults; +``` + +**Step 3: Remove or update tests for deleted infrastructure** + +- Delete `AttributeTableWriterTests.cs` if it exists +- Delete `OutputColumnCacheTests.cs` if it exists +- Update any integration tests to use new FluentTableWriter + +**Step 4: Run all tests** + +Run: `dotnet test` +Expected: All tests pass + +**Step 5: Commit** + +```bash +git add -A +git commit -m "test: update ExcelIO tests for fluent mapping" +``` + +--- + +### Task 18: Update DataAccess tests + +**Files:** +- Update tests that reference old DataAccess models + +**Step 1: Update imports in test files** + +Update from: +```csharp +using JdeScoping.DataAccess.Models.Results; +``` +To: +```csharp +using JdeScoping.Core.Models.SearchResults; +``` + +**Step 2: Run all tests** + +Run: `dotnet test` +Expected: All tests pass + +**Step 3: Commit** + +```bash +git add -A +git commit -m "test: update DataAccess tests for Core models" +``` + +--- + +## Phase 7: Final Verification + +### Task 19: Full build and test + +**Step 1: Clean build** + +Run: `dotnet clean && dotnet build` +Expected: Success with 0 errors, 0 warnings + +**Step 2: Run all tests** + +Run: `dotnet test` +Expected: All tests pass + +**Step 3: Verify no duplicate types** + +Run: +```bash +grep -r "class SearchResult" src/ --include="*.cs" | grep -v "ExcelClassMap" +grep -r "class MisSearchResult" src/ --include="*.cs" | grep -v "ExcelClassMap" +``` +Expected: Only one definition of each in Core + +**Step 4: Final commit** + +```bash +git add -A +git commit -m "refactor: complete fluent Excel mapping migration" +``` + +--- + +## Summary + +| Before | After | +|--------|-------| +| Models duplicated in DataAccess + ExcelIO | Single source of truth in Core | +| Attributes on models (presentation in domain) | Fluent maps in ExcelIO (separation of concerns) | +| OutputColumnAttribute, OutputTableAttribute | ExcelClassMap, ColumnBuilder | +| OutputColumnCache (reflection) | ExcelMapRegistry (explicit registration) | +| AttributeTableWriter | FluentTableWriter | + +**Files Created:** 12 +**Files Deleted:** ~15 +**Net reduction:** ~3 files, significantly cleaner architecture diff --git a/Tools/DbExporter/definitions/workorderstep-curr-15.json b/Tools/DbExporter/definitions/workorderstep-curr-15.json new file mode 100644 index 0000000..abed451 --- /dev/null +++ b/Tools/DbExporter/definitions/workorderstep-curr-15.json @@ -0,0 +1,7 @@ +{ + "providerType": "SqlServer", + "connectionString": "Server=localhost,1434;Database=ScopingTool;User Id=sa;Password=ScopingTool_SA_2024Dev;TrustServerCertificate=true", + "query": "SELECT * FROM dbo.WorkOrderStep_Curr", + "outputPath": "./output/workorderstep-curr-15.pb.zstd", + "compressionLevel": 15 +} diff --git a/Tools/DbExporter/definitions/workorderstep-curr-19.json b/Tools/DbExporter/definitions/workorderstep-curr-19.json new file mode 100644 index 0000000..9cd5b01 --- /dev/null +++ b/Tools/DbExporter/definitions/workorderstep-curr-19.json @@ -0,0 +1,7 @@ +{ + "providerType": "SqlServer", + "connectionString": "Server=localhost,1434;Database=ScopingTool;User Id=sa;Password=ScopingTool_SA_2024Dev;TrustServerCertificate=true", + "query": "SELECT * FROM dbo.WorkOrderStep_Curr", + "outputPath": "./output/workorderstep-curr-19.pb.zstd", + "compressionLevel": 19 +} diff --git a/Tools/DbExporter/definitions/workorderstep-curr-22.json b/Tools/DbExporter/definitions/workorderstep-curr-22.json new file mode 100644 index 0000000..afaa0f9 --- /dev/null +++ b/Tools/DbExporter/definitions/workorderstep-curr-22.json @@ -0,0 +1,7 @@ +{ + "providerType": "SqlServer", + "connectionString": "Server=localhost,1434;Database=ScopingTool;User Id=sa;Password=ScopingTool_SA_2024Dev;TrustServerCertificate=true", + "query": "SELECT * FROM dbo.WorkOrderStep_Curr", + "outputPath": "./output/workorderstep-curr-22.pb.zstd", + "compressionLevel": 22 +} diff --git a/Tools/DbExporter/definitions/workorderstep-curr-3.json b/Tools/DbExporter/definitions/workorderstep-curr-3.json new file mode 100644 index 0000000..7742399 --- /dev/null +++ b/Tools/DbExporter/definitions/workorderstep-curr-3.json @@ -0,0 +1,7 @@ +{ + "providerType": "SqlServer", + "connectionString": "Server=localhost,1434;Database=ScopingTool;User Id=sa;Password=ScopingTool_SA_2024Dev;TrustServerCertificate=true", + "query": "SELECT * FROM dbo.WorkOrderStep_Curr", + "outputPath": "./output/workorderstep-curr-3.pb.zstd", + "compressionLevel": 3 +} diff --git a/Tools/DbExporter/definitions/workorderstep-curr-5.json b/Tools/DbExporter/definitions/workorderstep-curr-5.json new file mode 100644 index 0000000..f50092c --- /dev/null +++ b/Tools/DbExporter/definitions/workorderstep-curr-5.json @@ -0,0 +1,7 @@ +{ + "providerType": "SqlServer", + "connectionString": "Server=localhost,1434;Database=ScopingTool;User Id=sa;Password=ScopingTool_SA_2024Dev;TrustServerCertificate=true", + "query": "SELECT * FROM dbo.WorkOrderStep_Curr", + "outputPath": "./output/workorderstep-curr-5.pb.zstd", + "compressionLevel": 5 +} diff --git a/Tools/DbExporter/definitions/workorderstep-curr-7.json b/Tools/DbExporter/definitions/workorderstep-curr-7.json new file mode 100644 index 0000000..13e3712 --- /dev/null +++ b/Tools/DbExporter/definitions/workorderstep-curr-7.json @@ -0,0 +1,7 @@ +{ + "providerType": "SqlServer", + "connectionString": "Server=localhost,1434;Database=ScopingTool;User Id=sa;Password=ScopingTool_SA_2024Dev;TrustServerCertificate=true", + "query": "SELECT * FROM dbo.WorkOrderStep_Curr", + "outputPath": "./output/workorderstep-curr-7.pb.zstd", + "compressionLevel": 7 +}