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.
This commit is contained in:
+21
-21
@@ -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/`
|
||||
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
using JdeScoping.Core.Models;
|
||||
using JdeScoping.Core.Models.Quality;
|
||||
|
||||
namespace JdeScoping.Core.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for fetching data from CMS source system.
|
||||
/// </summary>
|
||||
public interface ICmsDataSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets MIS data from CMS as an async stream.
|
||||
/// </summary>
|
||||
/// <param name="minimumDt">Minimum update timestamp for incremental fetch. Null for full fetch.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
IAsyncEnumerable<MisData> GetMisDataAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for fetching data from JDE (JD Edwards) source system.
|
||||
/// </summary>
|
||||
public interface IJdeDataSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets work orders from JDE as an async stream.
|
||||
/// </summary>
|
||||
/// <param name="minimumDt">Minimum update timestamp for incremental fetch. Null for full fetch.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
IAsyncEnumerable<WorkOrder> GetWorkOrdersAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets lot usage records from JDE as an async stream.
|
||||
/// </summary>
|
||||
/// <param name="minimumDt">Minimum update timestamp for incremental fetch. Null for full fetch.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
IAsyncEnumerable<LotUsage> GetLotUsagesAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets lots from JDE as an async stream.
|
||||
/// </summary>
|
||||
/// <param name="minimumDt">Minimum update timestamp for incremental fetch. Null for full fetch.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
IAsyncEnumerable<Lot> GetLotsAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets items from JDE as an async stream.
|
||||
/// </summary>
|
||||
/// <param name="minimumDt">Minimum update timestamp for incremental fetch. Null for full fetch.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
IAsyncEnumerable<Item> GetItemsAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets work centers from JDE as an async stream.
|
||||
/// </summary>
|
||||
/// <param name="minimumDt">Minimum update timestamp for incremental fetch. Null for full fetch.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
IAsyncEnumerable<WorkCenter> GetWorkCentersAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets profit centers from JDE as an async stream.
|
||||
/// </summary>
|
||||
/// <param name="minimumDt">Minimum update timestamp for incremental fetch. Null for full fetch.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
IAsyncEnumerable<ProfitCenter> GetProfitCentersAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets JDE users as an async stream.
|
||||
/// </summary>
|
||||
/// <param name="minimumDt">Minimum update timestamp for incremental fetch. Null for full fetch.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
IAsyncEnumerable<JdeUser> GetUsersAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets branches from JDE as an async stream.
|
||||
/// </summary>
|
||||
/// <param name="minimumDt">Minimum update timestamp for incremental fetch. Null for full fetch.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
IAsyncEnumerable<Branch> GetBranchesAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -13,44 +13,4 @@ public partial interface ILotFinderRepository
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Latest data updates.</returns>
|
||||
Task<List<DataUpdate>> GetLastDataUpdatesAsync(CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets table schema specification for dynamic SQL generation.
|
||||
/// </summary>
|
||||
/// <param name="tableName">Table name.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Table specification with columns and primary key.</returns>
|
||||
Task<TableSpec> GetTableSpecAsync(string tableName, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds all indices on a table with fillfactor of 95.
|
||||
/// Table name is validated against whitelist for SQL injection prevention.
|
||||
/// </summary>
|
||||
/// <param name="tableName">Table name (must be in whitelist).</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if table name is not in whitelist.</exception>
|
||||
Task RebuildIndicesAsync(string tableName, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Post-processes imported MIS data to set obsolete dates.
|
||||
/// </summary>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task PostProcessMisDataAsync(CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Performs bulk insert of records into a table.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Record type.</typeparam>
|
||||
/// <param name="tableName">Target table name.</param>
|
||||
/// <param name="records">Records to insert.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Number of records inserted.</returns>
|
||||
Task<int> BulkInsertAsync<T>(string tableName, IEnumerable<T> records, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Truncates a table, removing all records.
|
||||
/// </summary>
|
||||
/// <param name="tableName">Table name.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task TruncateTableAsync(string tableName, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
@@ -43,14 +43,6 @@ public partial interface ILotFinderRepository
|
||||
/// <returns>Top 25 matching work centers.</returns>
|
||||
Task<List<WorkCenter>> SearchWorkCentersAsync(string filter, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Looks up work centers by codes.
|
||||
/// </summary>
|
||||
/// <param name="codes">Work center codes to match.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Matching work centers.</returns>
|
||||
Task<List<WorkCenter>> LookupWorkCentersAsync(List<string> codes, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Searches profit centers by code or description.
|
||||
/// </summary>
|
||||
@@ -59,14 +51,6 @@ public partial interface ILotFinderRepository
|
||||
/// <returns>Top 25 matching profit centers.</returns>
|
||||
Task<List<ProfitCenter>> SearchProfitCentersAsync(string filter, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Looks up profit centers by codes.
|
||||
/// </summary>
|
||||
/// <param name="codes">Profit center codes to match.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Matching profit centers.</returns>
|
||||
Task<List<ProfitCenter>> LookupProfitCentersAsync(List<string> codes, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Searches users by user ID, full name, or address number.
|
||||
/// </summary>
|
||||
@@ -75,14 +59,6 @@ public partial interface ILotFinderRepository
|
||||
/// <returns>Top 25 matching users.</returns>
|
||||
Task<List<JdeUser>> SearchUsersAsync(string filter, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Looks up users by user IDs or address numbers.
|
||||
/// </summary>
|
||||
/// <param name="userIds">User IDs or address numbers to match.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Matching users.</returns>
|
||||
Task<List<JdeUser>> LookupUsersAsync(List<string> userIds, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Looks up lots by lot number and item number.
|
||||
/// </summary>
|
||||
|
||||
@@ -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
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Generated search ID.</returns>
|
||||
Task<int> SubmitSearchAsync(Search search, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the status of a search.
|
||||
/// </summary>
|
||||
/// <param name="id">Search ID.</param>
|
||||
/// <param name="status">New status.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task UpdateSearchStatusAsync(int id, SearchStatus status, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Stores the Excel results for a completed search.
|
||||
/// </summary>
|
||||
/// <param name="id">Search ID.</param>
|
||||
/// <param name="results">Excel file bytes.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
Task UpdateSearchResultsAsync(int id, byte[] results, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
namespace JdeScoping.Core.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Processor for executing user search requests.
|
||||
/// </summary>
|
||||
public interface ISearchProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Processes a queued search request.
|
||||
/// </summary>
|
||||
Task ProcessSearchAsync(int searchId, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the next pending search to process.
|
||||
/// </summary>
|
||||
Task<int?> GetNextPendingSearchAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
namespace JdeScoping.Core.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Processor for data synchronization from JDE/CMS to local cache.
|
||||
/// </summary>
|
||||
public interface IUpdateProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Runs a mass (full) data refresh.
|
||||
/// </summary>
|
||||
Task RunMassRefreshAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Runs a daily incremental data refresh.
|
||||
/// </summary>
|
||||
Task RunDailyRefreshAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Runs an hourly data refresh.
|
||||
/// </summary>
|
||||
Task RunHourlyRefreshAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -38,8 +38,6 @@ public static class DataAccessDependencyInjection
|
||||
|
||||
// Register repositories as scoped (per-request lifetime)
|
||||
services.AddScoped<ILotFinderRepository, LotFinderRepository>();
|
||||
services.AddScoped<IJdeRepository, JdeRepository>();
|
||||
services.AddScoped<ICmsRepository, CmsRepository>();
|
||||
|
||||
// Register SqlKata compiler (singleton, thread-safe)
|
||||
services.AddSingleton<SqlServerCompiler>();
|
||||
@@ -74,8 +72,6 @@ public static class DataAccessDependencyInjection
|
||||
|
||||
// Register repositories as scoped (per-request lifetime)
|
||||
services.AddScoped<ILotFinderRepository, LotFinderRepository>();
|
||||
services.AddScoped<IJdeRepository, JdeRepository>();
|
||||
services.AddScoped<ICmsRepository, CmsRepository>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
using JdeScoping.Core.Models;
|
||||
using JdeScoping.Core.Models.Quality;
|
||||
|
||||
namespace JdeScoping.DataAccess.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Repository for accessing CMS Oracle database.
|
||||
/// </summary>
|
||||
public interface ICmsRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets Manufacturing Information System (MIS) data from CMS database.
|
||||
/// Uses MisDataTimeoutSeconds timeout due to complex 10-table JOIN.
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming MIS data records.</returns>
|
||||
IAsyncEnumerable<MisData> GetMisDataAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
using JdeScoping.Core.Models.Inventory;
|
||||
|
||||
namespace JdeScoping.DataAccess.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Inventory (lots) operations for JDE Oracle repository.
|
||||
/// </summary>
|
||||
public partial interface IJdeRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets lot master data from production schema, optionally filtered by last update.
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming lots.</returns>
|
||||
IAsyncEnumerable<Lot> GetLotsAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets lot usage (cardex) transactions from production schema, optionally filtered by last update.
|
||||
/// Uses special LotUsageTimeoutSeconds timeout due to large dataset.
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming lot usages.</returns>
|
||||
IAsyncEnumerable<LotUsage> GetLotUsagesAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets lot usage transactions from archive schema.
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming archived lot usages.</returns>
|
||||
IAsyncEnumerable<LotUsage> GetLotUsagesArchiveAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets lot location tracking from JDE Stage view.
|
||||
/// Uses JDE Stage connection.
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming lot locations.</returns>
|
||||
IAsyncEnumerable<LotLocation> GetLotLocationsAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Reference data operations for JDE Oracle repository.
|
||||
/// </summary>
|
||||
public partial interface IJdeRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets item master data from production schema, optionally filtered by last update.
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming items.</returns>
|
||||
IAsyncEnumerable<Item> GetItemsAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets user/operator data from production schema.
|
||||
/// Note: Incremental filtering not supported for users (full sync always).
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Ignored (full sync always).</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming users.</returns>
|
||||
IAsyncEnumerable<JdeUser> GetUsersAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets branch business units from production schema (type code 'BP').
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming branches.</returns>
|
||||
IAsyncEnumerable<Branch> GetBranchesAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets profit center business units from production schema (type code 'I3').
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming profit centers.</returns>
|
||||
IAsyncEnumerable<ProfitCenter> GetProfitCentersAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets work center business units from production schema (type code 'WC').
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming work centers.</returns>
|
||||
IAsyncEnumerable<WorkCenter> GetWorkCentersAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets status codes from JDE Stage view.
|
||||
/// Uses JDE Stage connection.
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming status codes.</returns>
|
||||
IAsyncEnumerable<StatusCode> GetStatusCodesAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets function codes from production schema.
|
||||
/// Note: Does not support incremental filtering (full sync always).
|
||||
/// </summary>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming function codes.</returns>
|
||||
IAsyncEnumerable<FunctionCode> GetFunctionCodesAsync(CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets organization hierarchy (work center to profit center mapping) from production schema.
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming org hierarchy records.</returns>
|
||||
IAsyncEnumerable<OrgHierarchy> GetOrgHierarchyAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets item routing master data from production schema.
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming route masters.</returns>
|
||||
IAsyncEnumerable<RouteMaster> GetRouteMastersAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
using JdeScoping.Core.Models.WorkOrders;
|
||||
|
||||
namespace JdeScoping.DataAccess.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Work order operations for JDE Oracle repository.
|
||||
/// </summary>
|
||||
public partial interface IJdeRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets work orders from production schema, optionally filtered by last update.
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming work orders.</returns>
|
||||
IAsyncEnumerable<WorkOrder> GetWorkOrdersAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets work orders from archive schema.
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming archived work orders.</returns>
|
||||
IAsyncEnumerable<WorkOrder> GetWorkOrdersArchiveAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets work order steps from production schema, optionally filtered by last update.
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming work order steps.</returns>
|
||||
IAsyncEnumerable<WorkOrderStep> GetWorkOrderStepsAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets work order steps from archive schema.
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming archived work order steps.</returns>
|
||||
IAsyncEnumerable<WorkOrderStep> GetWorkOrderStepsArchiveAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets work order time transactions from production schema, optionally filtered by last update.
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming work order times.</returns>
|
||||
IAsyncEnumerable<WorkOrderTime> GetWorkOrderTimesAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets work order time transactions from archive schema.
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming archived work order times.</returns>
|
||||
IAsyncEnumerable<WorkOrderTime> GetWorkOrderTimesArchiveAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets work order routing transactions from production schema, optionally filtered by last update.
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming work order routings.</returns>
|
||||
IAsyncEnumerable<WorkOrderRouting> GetWorkOrderRoutingsAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets work order component usage from production schema, optionally filtered by last update.
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming work order components.</returns>
|
||||
IAsyncEnumerable<WorkOrderComponent> GetWorkOrderComponentsAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Gets work order component usage from archive schema.
|
||||
/// </summary>
|
||||
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
|
||||
/// <param name="ct">Cancellation token.</param>
|
||||
/// <returns>Streaming archived work order components.</returns>
|
||||
IAsyncEnumerable<WorkOrderComponent> GetWorkOrderComponentsArchiveAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace JdeScoping.DataAccess.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Repository for accessing JDE Oracle database.
|
||||
/// All methods return IAsyncEnumerable for memory-efficient streaming.
|
||||
/// </summary>
|
||||
public partial interface IJdeRepository
|
||||
{
|
||||
}
|
||||
@@ -25,11 +25,6 @@ public class DataAccessOptions
|
||||
/// </summary>
|
||||
public int MisDataTimeoutSeconds { get; set; } = 60000;
|
||||
|
||||
/// <summary>
|
||||
/// Timeout for index rebuild operations in seconds.
|
||||
/// </summary>
|
||||
public int RebuildIndexTimeoutSeconds { get; set; } = 600;
|
||||
|
||||
/// <summary>
|
||||
/// JDE production schema name (e.g., PRODDTA).
|
||||
/// </summary>
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
namespace JdeScoping.DataAccess.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// SQL query constants for the CMS Oracle database.
|
||||
/// </summary>
|
||||
public static class CmsQueries
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets Manufacturing Information System (MIS) data from CMS database.
|
||||
/// Complex 10-table JOIN through CMS schema (INFODBA).
|
||||
/// </summary>
|
||||
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')";
|
||||
|
||||
/// <summary>
|
||||
/// Gets MIS data updated since specified date from CMS database.
|
||||
/// </summary>
|
||||
public const string SqlGetMisDataFiltered = SqlGetMisData + @"
|
||||
AND Status.PDATE_RELEASED >= :lastUpdateDT";
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
namespace JdeScoping.DataAccess.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// Inventory related SQL queries (Lots, Lot Usages, Lot Locations, Items)
|
||||
/// </summary>
|
||||
public static partial class JdeQueries
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all lots from production schema.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Gets lots updated since specified date from production schema.
|
||||
/// </summary>
|
||||
public const string SqlGetLotsFiltered = SqlGetLots + @"
|
||||
AND (lot.IOUPMJ > :dateUpdated OR
|
||||
(lot.IOUPMJ = :dateUpdated AND lot.IOTDAY >= :timeUpdated))";
|
||||
|
||||
/// <summary>
|
||||
/// Gets all lot usages (cardex) from production schema.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Gets lot usages updated since specified date from production schema.
|
||||
/// </summary>
|
||||
public const string SqlGetLotUsagesFiltered = SqlGetLotUsages + @"
|
||||
AND (lu.ILTRDJ > :dateUpdated OR
|
||||
(lu.ILTRDJ = :dateUpdated AND lu.ILTDAY >= :timeUpdated))";
|
||||
|
||||
/// <summary>
|
||||
/// Gets all lot usages from archive schema.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Gets all lot locations from production schema.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Gets lot locations updated since specified date from production schema.
|
||||
/// </summary>
|
||||
public const string SqlGetLotLocationsFiltered = SqlGetLotLocations + @"
|
||||
AND (il.LIUPMJ > :dateUpdated OR
|
||||
(il.LIUPMJ = :dateUpdated AND il.LITDAY >= :timeUpdated))";
|
||||
|
||||
/// <summary>
|
||||
/// Gets all items from production schema.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Gets items updated since specified date from production schema.
|
||||
/// </summary>
|
||||
public const string SqlGetItemsFiltered = SqlGetItems + @"
|
||||
AND (pn.IMUPMJ > :dateUpdated OR
|
||||
(pn.IMUPMJ = :dateUpdated AND pn.IMTDAY >= :timeUpdated))";
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
namespace JdeScoping.DataAccess.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// Lookup related SQL queries (Status Codes, Function Codes)
|
||||
/// </summary>
|
||||
public static partial class JdeQueries
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all work order status codes from production schema.
|
||||
/// Status codes are stored in UDC table F0005 with SY='00' and RT='SS'.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Gets status codes updated since specified date from production schema.
|
||||
/// </summary>
|
||||
public const string SqlGetStatusCodesFiltered = SqlGetStatusCodes + @"
|
||||
AND (sc.DRUPMJ > :dateUpdated OR
|
||||
(sc.DRUPMJ = :dateUpdated AND sc.DRUPMT >= :timeUpdated))";
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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";
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
namespace JdeScoping.DataAccess.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// Organization related SQL queries (Users, Business Units, Org Hierarchy, Route Masters)
|
||||
/// </summary>
|
||||
public static partial class JdeQueries
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all JDE users from production schema.
|
||||
/// Uses CTE to get unique users with most recent address book record.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Gets all business units of specified type from production schema.
|
||||
/// Type codes: 'WC' = Work Center, 'PC' = Profit Center, 'BR' = Branch
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Gets business units of specified type updated since specified date from production schema.
|
||||
/// </summary>
|
||||
public const string SqlGetBusinessUnitsFiltered = SqlGetBusinessUnits + @"
|
||||
AND (wc.MCUPMJ > :dateUpdated OR
|
||||
(wc.MCUPMJ = :dateUpdated AND wc.MCUPMT >= :timeUpdated))";
|
||||
|
||||
/// <summary>
|
||||
/// Gets all organization hierarchy records from production schema.
|
||||
/// Maps work centers to profit centers and branches.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Gets org hierarchy records updated since specified date from production schema.
|
||||
/// </summary>
|
||||
public const string SqlGetOrgHierarchyFiltered = SqlGetOrgHierarchy + @"
|
||||
AND (oh.IWUPMJ > :dateUpdated OR
|
||||
(oh.IWUPMJ = :dateUpdated AND oh.IWTDAY >= :timeUpdated))";
|
||||
|
||||
/// <summary>
|
||||
/// Gets all route masters from production schema.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Gets route masters updated since specified date from production schema.
|
||||
/// </summary>
|
||||
public const string SqlGetRouteMastersFiltered = SqlGetRouteMasters + @"
|
||||
AND (rm.IRUPMJ > :dateUpdated OR
|
||||
(rm.IRUPMJ = :dateUpdated AND rm.IRTDAY >= :timeUpdated))";
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
namespace JdeScoping.DataAccess.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// Work order related SQL queries (Work Orders, Steps, Times, Routings, Components)
|
||||
/// </summary>
|
||||
public static partial class JdeQueries
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all work orders from production schema.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Gets work orders updated since specified date from production schema.
|
||||
/// </summary>
|
||||
public const string SqlGetWorkordersFiltered = SqlGetWorkorders + @"
|
||||
WHERE (wo.WAUPMJ > :dateUpdated OR
|
||||
(wo.WAUPMJ = :dateUpdated AND wo.WATDAY >= :timeUpdated))";
|
||||
|
||||
/// <summary>
|
||||
/// Gets all work orders from archive schema.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Gets work order steps from production schema.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Gets work order steps updated since specified date from production schema.
|
||||
/// </summary>
|
||||
public const string SqlGetWorkorderStepsFiltered = SqlGetWorkorderSteps + @"
|
||||
AND (wos.WLUPMJ > :dateUpdated OR
|
||||
(wos.WLUPMJ = :dateUpdated AND wos.WLTDAY >= :timeUpdated))";
|
||||
|
||||
/// <summary>
|
||||
/// Gets work order steps from archive schema.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Gets work order time transactions from production schema.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Gets work order times updated since specified date from production schema.
|
||||
/// </summary>
|
||||
public const string SqlGetWorkorderTimesFiltered = SqlGetWorkorderTimes + @"
|
||||
AND (wot.WTUPMJ > :dateUpdated OR
|
||||
(wot.WTUPMJ = :dateUpdated AND wot.WTTDAY >= :timeUpdated))";
|
||||
|
||||
/// <summary>
|
||||
/// Gets work order time transactions from archive schema.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Gets work order routing transactions from production schema.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Gets work order routings updated since specified date from production schema.
|
||||
/// </summary>
|
||||
public const string SqlGetWorkorderRoutingsFiltered = SqlGetWorkorderRoutings + @"
|
||||
AND (woz.SZUPMJ > :dateUpdated OR
|
||||
(woz.SZUPMJ = :dateUpdated AND woz.SZTDAY >= :timeUpdated))";
|
||||
|
||||
/// <summary>
|
||||
/// Gets work order component usage from production schema.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Gets work order components updated since specified date from production schema.
|
||||
/// </summary>
|
||||
public const string SqlGetWorkorderComponentsFiltered = SqlGetWorkorderComponents + @"
|
||||
AND (woc.WMUPMJ > :dateUpdated OR
|
||||
(woc.WMUPMJ = :dateUpdated AND woc.WMTDAY >= :timeUpdated))";
|
||||
|
||||
/// <summary>
|
||||
/// Gets work order component usage from archive schema.
|
||||
/// </summary>
|
||||
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";
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace JdeScoping.DataAccess.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// SQL query constants for the JDE Oracle database.
|
||||
/// Schema placeholders ({ProductionSchema}, {ArchiveSchema}, {StageSchema}) are replaced at runtime.
|
||||
/// </summary>
|
||||
public static partial class JdeQueries
|
||||
{
|
||||
}
|
||||
@@ -24,88 +24,4 @@ public static partial class LotFinderQueries
|
||||
cte.NumberRecords
|
||||
FROM DU_CTE cte
|
||||
WHERE cte.RN = 1";
|
||||
|
||||
/// <summary>
|
||||
/// Gets column metadata for a table.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Gets primary key columns for a table.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Rebuilds all indices on a table. Use string.Format to inject table name.
|
||||
/// </summary>
|
||||
public const string SqlRebuildIndices = "ALTER INDEX ALL ON {0} REBUILD WITH (FILLFACTOR = 95);";
|
||||
|
||||
/// <summary>
|
||||
/// Post-processing script to set MIS data obsoletion dates.
|
||||
/// </summary>
|
||||
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;";
|
||||
|
||||
/// <summary>
|
||||
/// Inserts a new data update tracking record.
|
||||
/// </summary>
|
||||
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);";
|
||||
|
||||
/// <summary>
|
||||
/// Completes a data update tracking record.
|
||||
/// </summary>
|
||||
public const string SqlCompleteDataUpdate = @"
|
||||
UPDATE dbo.DataUpdate
|
||||
SET EndDT = @endDT,
|
||||
WasSuccessful = @wasSuccessful,
|
||||
NumberRecords = @numberRecords
|
||||
WHERE ID = @id";
|
||||
}
|
||||
|
||||
@@ -52,17 +52,6 @@ public static partial class LotFinderQueries
|
||||
wc.Description LIKE '%' + @filter + '%'
|
||||
ORDER BY wc.Code";
|
||||
|
||||
/// <summary>
|
||||
/// Looks up work centers by codes using STRING_SPLIT.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Searches profit centers by code or description.
|
||||
/// </summary>
|
||||
@@ -76,17 +65,6 @@ public static partial class LotFinderQueries
|
||||
pc.Description LIKE '%' + @filter + '%'
|
||||
ORDER BY pc.Code";
|
||||
|
||||
/// <summary>
|
||||
/// Looks up profit centers by codes using STRING_SPLIT.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Searches users by user ID, full name, or address number.
|
||||
/// </summary>
|
||||
@@ -102,19 +80,6 @@ public static partial class LotFinderQueries
|
||||
CAST(u.AddressNumber AS VARCHAR(10)) LIKE '%' + @filter + '%'
|
||||
ORDER BY u.UserID, u.FullName";
|
||||
|
||||
/// <summary>
|
||||
/// Looks up users by user IDs or address numbers using STRING_SPLIT.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Looks up lots by lot number and item number using OPENJSON.
|
||||
/// </summary>
|
||||
|
||||
@@ -55,22 +55,4 @@ public static partial class LotFinderQueries
|
||||
SELECT s.Results
|
||||
FROM dbo.Search AS s
|
||||
WHERE s.ID = @id";
|
||||
|
||||
/// <summary>
|
||||
/// Updates search status.
|
||||
/// </summary>
|
||||
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";
|
||||
|
||||
/// <summary>
|
||||
/// Updates search results.
|
||||
/// </summary>
|
||||
public const string SqlUpdateSearchResults = @"
|
||||
UPDATE dbo.Search
|
||||
SET Results = @results
|
||||
WHERE ID = @id";
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Repository implementation for the CMS Oracle database.
|
||||
/// </summary>
|
||||
public class CmsRepository : ICmsRepository
|
||||
{
|
||||
private readonly IDbConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<CmsRepository> _logger;
|
||||
private readonly IOptions<DataAccessOptions> _options;
|
||||
private const string RepositoryName = "CmsRepository";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CmsRepository"/> class.
|
||||
/// </summary>
|
||||
public CmsRepository(
|
||||
IDbConnectionFactory connectionFactory,
|
||||
ILogger<CmsRepository> logger,
|
||||
IOptions<DataAccessOptions> options)
|
||||
{
|
||||
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<MisData> 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<MisData>(
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Inventory (lots) operations for JDE Oracle repository.
|
||||
/// </summary>
|
||||
public partial class JdeRepository
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<Lot> 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<Lot>(
|
||||
sql, parameters, nameof(GetLotsAsync), "SQL_GET_LOTS", ct))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<LotUsage> 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<LotUsage>(
|
||||
sql, parameters, nameof(GetLotUsagesAsync), "SQL_GET_LOT_USAGES", ct,
|
||||
_options.Value.LotUsageTimeoutSeconds))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<LotUsage> 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<LotUsage>(
|
||||
sql, null, nameof(GetLotUsagesArchiveAsync), "SQL_GET_LOT_USAGES_ARCHIVE", ct,
|
||||
_options.Value.LotUsageTimeoutSeconds))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<LotLocation> 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<LotLocation>(
|
||||
sql, parameters, nameof(GetLotLocationsAsync), "SQL_GET_LOT_LOCATIONS", ct))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Reference data operations for JDE Oracle repository.
|
||||
/// </summary>
|
||||
public partial class JdeRepository
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<Item> 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<Item>(
|
||||
sql, parameters, nameof(GetItemsAsync), "SQL_GET_ITEMS", ct))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<JdeUser> 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<JdeUser>(
|
||||
sql, null, nameof(GetUsersAsync), "SQL_GET_USERS", ct))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<Branch> 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<Branch>(
|
||||
sql, parameters, nameof(GetBranchesAsync), "SQL_GET_BUSINESS_UNITS", ct))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<ProfitCenter> 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<ProfitCenter>(
|
||||
sql, parameters, nameof(GetProfitCentersAsync), "SQL_GET_BUSINESS_UNITS", ct))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<WorkCenter> 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<WorkCenter>(
|
||||
sql, parameters, nameof(GetWorkCentersAsync), "SQL_GET_BUSINESS_UNITS", ct))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<StatusCode> 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<StatusCode>(
|
||||
sql, parameters, nameof(GetStatusCodesAsync), "SQL_GET_STATUS_CODES", ct))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<FunctionCode> GetFunctionCodesAsync(
|
||||
[EnumeratorCancellation] CancellationToken ct = default)
|
||||
{
|
||||
var sql = ApplySchemaReplacements(JdeQueries.SqlGetFunctionCodes);
|
||||
|
||||
await foreach (var item in StreamQueryAsync<FunctionCode>(
|
||||
sql, null, nameof(GetFunctionCodesAsync), "SQL_GET_FUNCTION_CODES", ct))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<OrgHierarchy> 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<OrgHierarchy>(
|
||||
sql, parameters, nameof(GetOrgHierarchyAsync), "SQL_GET_ORG_HIERARCHY", ct))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<RouteMaster> 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<RouteMaster>(
|
||||
sql, parameters, nameof(GetRouteMastersAsync), "SQL_GET_ROUTE_MASTERS", ct))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Work order operations for JDE Oracle repository.
|
||||
/// </summary>
|
||||
public partial class JdeRepository
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<WorkOrder> 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<WorkOrder>(
|
||||
sql, parameters, nameof(GetWorkOrdersAsync), "SQL_GET_WORKORDERS", ct))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<WorkOrder> GetWorkOrdersArchiveAsync(
|
||||
DateTime? lastUpdateDt = null,
|
||||
[EnumeratorCancellation] CancellationToken ct = default)
|
||||
{
|
||||
var sql = ApplySchemaReplacements(JdeQueries.SqlGetWorkordersArchive);
|
||||
|
||||
await foreach (var item in StreamQueryAsync<WorkOrder>(
|
||||
sql, null, nameof(GetWorkOrdersArchiveAsync), "SQL_GET_WORKORDERS_ARCHIVE", ct))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<WorkOrderStep> 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<WorkOrderStep>(
|
||||
sql, parameters, nameof(GetWorkOrderStepsAsync), "SQL_GET_WORKORDER_STEPS", ct))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<WorkOrderStep> GetWorkOrderStepsArchiveAsync(
|
||||
DateTime? lastUpdateDt = null,
|
||||
[EnumeratorCancellation] CancellationToken ct = default)
|
||||
{
|
||||
var sql = ApplySchemaReplacements(JdeQueries.SqlGetWorkorderStepsArchive);
|
||||
|
||||
await foreach (var item in StreamQueryAsync<WorkOrderStep>(
|
||||
sql, null, nameof(GetWorkOrderStepsArchiveAsync), "SQL_GET_WORKORDER_STEPS_ARCHIVE", ct))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<WorkOrderTime> 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<WorkOrderTime>(
|
||||
sql, parameters, nameof(GetWorkOrderTimesAsync), "SQL_GET_WORKORDER_TIMES", ct))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<WorkOrderTime> GetWorkOrderTimesArchiveAsync(
|
||||
DateTime? lastUpdateDt = null,
|
||||
[EnumeratorCancellation] CancellationToken ct = default)
|
||||
{
|
||||
var sql = ApplySchemaReplacements(JdeQueries.SqlGetWorkorderTimesArchive);
|
||||
|
||||
await foreach (var item in StreamQueryAsync<WorkOrderTime>(
|
||||
sql, null, nameof(GetWorkOrderTimesArchiveAsync), "SQL_GET_WORKORDER_TIMES_ARCHIVE", ct))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<WorkOrderRouting> 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<WorkOrderRouting>(
|
||||
sql, parameters, nameof(GetWorkOrderRoutingsAsync), "SQL_GET_WORKORDER_ROUTINGS", ct))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<WorkOrderComponent> 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<WorkOrderComponent>(
|
||||
sql, parameters, nameof(GetWorkOrderComponentsAsync), "SQL_GET_WORKORDER_COMPONENTS", ct))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<WorkOrderComponent> GetWorkOrderComponentsArchiveAsync(
|
||||
DateTime? lastUpdateDt = null,
|
||||
[EnumeratorCancellation] CancellationToken ct = default)
|
||||
{
|
||||
var sql = ApplySchemaReplacements(JdeQueries.SqlGetWorkorderComponentsArchive);
|
||||
|
||||
await foreach (var item in StreamQueryAsync<WorkOrderComponent>(
|
||||
sql, null, nameof(GetWorkOrderComponentsArchiveAsync), "SQL_GET_WORKORDER_COMPONENTS_ARCHIVE", ct))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Repository implementation for the JDE Oracle database.
|
||||
/// </summary>
|
||||
public partial class JdeRepository : IJdeRepository
|
||||
{
|
||||
private readonly IDbConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<JdeRepository> _logger;
|
||||
private readonly IOptions<DataAccessOptions> _options;
|
||||
private const string RepositoryName = "JdeRepository";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JdeRepository"/> class.
|
||||
/// </summary>
|
||||
public JdeRepository(
|
||||
IDbConnectionFactory connectionFactory,
|
||||
ILogger<JdeRepository> logger,
|
||||
IOptions<DataAccessOptions> 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<T> StreamQueryAsync<T>(
|
||||
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<T>(
|
||||
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<T> StreamQueryFromStageAsync<T>(
|
||||
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<T>(
|
||||
sql,
|
||||
parameters,
|
||||
commandTimeout: timeout,
|
||||
buffered: false);
|
||||
|
||||
foreach (var item in results)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (connection != null)
|
||||
{
|
||||
await connection.DisposeAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<TableSpec> 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<ColumnSpec>(
|
||||
LotFinderQueries.SqlGetTableColumns,
|
||||
new { name = tableName },
|
||||
commandTimeout: _options.Value.DefaultTimeoutSeconds);
|
||||
tableSpec.Columns.AddRange(columns);
|
||||
|
||||
// Load primary key
|
||||
var pkColumns = await connection.QueryAsync<string>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<int> BulkInsertAsync<T>(string tableName, IEnumerable<T> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,32 +113,6 @@ public partial class LotFinderRepository
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<WorkCenter>> LookupWorkCentersAsync(List<string> 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<WorkCenter>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<ProfitCenter>> SearchProfitCentersAsync(string filter, CancellationToken ct = default)
|
||||
{
|
||||
@@ -163,32 +137,6 @@ public partial class LotFinderRepository
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<ProfitCenter>> LookupProfitCentersAsync(List<string> 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<ProfitCenter>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<JdeUser>> SearchUsersAsync(string filter, CancellationToken ct = default)
|
||||
{
|
||||
@@ -213,32 +161,6 @@ public partial class LotFinderRepository
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<JdeUser>> LookupUsersAsync(List<string> 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<JdeUser>(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<Lot>> LookupLotsAsync(List<LotViewModel> lots, CancellationToken ct = default)
|
||||
{
|
||||
|
||||
@@ -152,49 +152,4 @@ public partial class LotFinderRepository
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<DataAccessOptions> _options;
|
||||
private const string RepositoryName = "LotFinderRepository";
|
||||
|
||||
/// <summary>
|
||||
/// Valid table names for index rebuild operations (SQL injection whitelist).
|
||||
/// </summary>
|
||||
private static readonly HashSet<string> 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"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LotFinderRepository"/> class.
|
||||
/// </summary>
|
||||
@@ -83,41 +66,4 @@ public partial class LotFinderRepository : ILotFinderRepository
|
||||
// SQL Server timeout error number: -2
|
||||
return ex.Number == -2;
|
||||
}
|
||||
|
||||
private static DataTable ToDataTable<T>(List<T> 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<IOptions<DataSyncOptions>>();
|
||||
_ = provider.GetRequiredService<IOptions<ExcelExportOptions>>();
|
||||
_ = provider.GetRequiredService<IOptions<SearchProcessingOptions>>();
|
||||
_ = provider.GetRequiredService<IOptions<DataSourceOptions>>();
|
||||
|
||||
// Validate data source services
|
||||
_ = provider.GetRequiredService<IJdeDataSource>();
|
||||
_ = provider.GetRequiredService<ICmsDataSource>();
|
||||
|
||||
Console.WriteLine("Service validation completed successfully.");
|
||||
}
|
||||
|
||||
@@ -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<DataSourceOptions>(
|
||||
configuration.GetSection(DataSourceOptions.SectionName));
|
||||
services.Configure<LdapOptions>(
|
||||
configuration.GetSection(LdapOptions.SectionName));
|
||||
|
||||
// Register data sources based on configuration
|
||||
var dataSourceOptions = configuration
|
||||
.GetSection(DataSourceOptions.SectionName)
|
||||
.Get<DataSourceOptions>();
|
||||
|
||||
if (dataSourceOptions?.UseFileDataSource == true)
|
||||
{
|
||||
services.AddScoped<IJdeDataSource, JdeFileDataSource>();
|
||||
services.AddScoped<ICmsDataSource, CmsFileDataSource>();
|
||||
}
|
||||
else
|
||||
{
|
||||
services.AddScoped<IJdeDataSource, JdeOracleDataSource>();
|
||||
services.AddScoped<ICmsDataSource, CmsOracleDataSource>();
|
||||
}
|
||||
|
||||
// Register auth service based on configuration
|
||||
var ldapOptions = configuration
|
||||
.GetSection(LdapOptions.SectionName)
|
||||
|
||||
@@ -7,13 +7,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.1" />
|
||||
<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.26.0" />
|
||||
<PackageReference Include="System.DirectoryServices.Protocols" Version="10.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
namespace JdeScoping.Infrastructure.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration options for data source selection (Oracle vs file-based).
|
||||
/// </summary>
|
||||
public class DataSourceOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration section name in appsettings.json.
|
||||
/// </summary>
|
||||
public const string SectionName = "DataSource";
|
||||
|
||||
/// <summary>
|
||||
/// Use file-based data sources instead of Oracle for development.
|
||||
/// </summary>
|
||||
public bool UseFileDataSource { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Directory containing JSON data files for file-based data source.
|
||||
/// </summary>
|
||||
public string FileDirectory { get; set; } = "DevData";
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// File-based CMS data source for development/testing.
|
||||
/// </summary>
|
||||
public class CmsFileDataSource : ICmsDataSource
|
||||
{
|
||||
private readonly string _dataDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CmsFileDataSource"/> class.
|
||||
/// </summary>
|
||||
public CmsFileDataSource(IOptions<DataSourceOptions> options)
|
||||
{
|
||||
_dataDirectory = options.Value.FileDirectory;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<MisData> 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<List<MisData>>(json) ?? [];
|
||||
|
||||
foreach (var item in items.Where(m => !minimumDt.HasValue || (m.ReleaseDate.HasValue && m.ReleaseDate.Value >= minimumDt)))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Oracle-based CMS data source for production use.
|
||||
/// </summary>
|
||||
public class CmsOracleDataSource : ICmsDataSource
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CmsOracleDataSource"/> class.
|
||||
/// </summary>
|
||||
public CmsOracleDataSource(IConfiguration configuration)
|
||||
{
|
||||
_connectionString = configuration.GetConnectionString("CMS")
|
||||
?? throw new InvalidOperationException("CMS connection string not configured");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<MisData> 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<MisData>(sql, new { MinDate = minimumDt });
|
||||
foreach (var item in results)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// File-based JDE data source for development/testing.
|
||||
/// </summary>
|
||||
public class JdeFileDataSource : IJdeDataSource
|
||||
{
|
||||
private readonly string _dataDirectory;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JdeFileDataSource"/> class.
|
||||
/// </summary>
|
||||
public JdeFileDataSource(IOptions<DataSourceOptions> options)
|
||||
{
|
||||
_dataDirectory = options.Value.FileDirectory;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<WorkOrder> GetWorkOrdersAsync(
|
||||
DateTime? minimumDt = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
var items = await LoadFromFileAsync<WorkOrder>("workorders.json", cancellationToken);
|
||||
foreach (var item in items.Where(wo => !minimumDt.HasValue || (wo.LastUpdateDt.HasValue && wo.LastUpdateDt.Value >= minimumDt)))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<LotUsage> GetLotUsagesAsync(
|
||||
DateTime? minimumDt = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
var items = await LoadFromFileAsync<LotUsage>("lotusages.json", cancellationToken);
|
||||
foreach (var item in items.Where(lu => !minimumDt.HasValue || (lu.LastUpdateDt.HasValue && lu.LastUpdateDt.Value >= minimumDt)))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<Lot> GetLotsAsync(
|
||||
DateTime? minimumDt = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
var items = await LoadFromFileAsync<Lot>("lots.json", cancellationToken);
|
||||
foreach (var item in items.Where(l => !minimumDt.HasValue || (l.LastUpdateDt.HasValue && l.LastUpdateDt.Value >= minimumDt)))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<Item> GetItemsAsync(
|
||||
DateTime? minimumDt = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
var items = await LoadFromFileAsync<Item>("items.json", cancellationToken);
|
||||
foreach (var item in items.Where(i => !minimumDt.HasValue || (i.LastUpdateDt.HasValue && i.LastUpdateDt.Value >= minimumDt)))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<WorkCenter> GetWorkCentersAsync(
|
||||
DateTime? minimumDt = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
var items = await LoadFromFileAsync<WorkCenter>("workcenters.json", cancellationToken);
|
||||
foreach (var item in items.Where(wc => !minimumDt.HasValue || (wc.LastUpdateDt.HasValue && wc.LastUpdateDt.Value >= minimumDt)))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<ProfitCenter> GetProfitCentersAsync(
|
||||
DateTime? minimumDt = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
var items = await LoadFromFileAsync<ProfitCenter>("profitcenters.json", cancellationToken);
|
||||
foreach (var item in items.Where(pc => !minimumDt.HasValue || (pc.LastUpdateDt.HasValue && pc.LastUpdateDt.Value >= minimumDt)))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<JdeUser> GetUsersAsync(
|
||||
DateTime? minimumDt = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
var items = await LoadFromFileAsync<JdeUser>("users.json", cancellationToken);
|
||||
foreach (var item in items.Where(u => !minimumDt.HasValue || (u.LastUpdateDt.HasValue && u.LastUpdateDt.Value >= minimumDt)))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<Branch> GetBranchesAsync(
|
||||
DateTime? minimumDt = null,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
var items = await LoadFromFileAsync<Branch>("branches.json", cancellationToken);
|
||||
foreach (var item in items)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<T>> LoadFromFileAsync<T>(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<List<T>>(json) ?? [];
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Oracle-based JDE data source for production use.
|
||||
/// </summary>
|
||||
public class JdeOracleDataSource : IJdeDataSource
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JdeOracleDataSource"/> class.
|
||||
/// </summary>
|
||||
public JdeOracleDataSource(IConfiguration configuration)
|
||||
{
|
||||
_connectionString = configuration.GetConnectionString("JDE")
|
||||
?? throw new InvalidOperationException("JDE connection string not configured");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<WorkOrder> 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<WorkOrder>(sql, new { MinDate = minimumDt });
|
||||
foreach (var item in results)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<LotUsage> 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<LotUsage>(sql, new { MinDate = minimumDt });
|
||||
foreach (var item in results)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<Lot> 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<Lot>(sql, new { MinDate = minimumDt });
|
||||
foreach (var item in results)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<Item> 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<Item>(sql, new { MinDate = minimumDt });
|
||||
foreach (var item in results)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<WorkCenter> 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<WorkCenter>(sql, new { MinDate = minimumDt });
|
||||
foreach (var item in results)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<ProfitCenter> 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<ProfitCenter>(sql);
|
||||
foreach (var item in results)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<JdeUser> 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<JdeUser>(sql, new { MinDate = minimumDt });
|
||||
foreach (var item in results)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async IAsyncEnumerable<Branch> 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<Branch>(sql);
|
||||
foreach (var item in results)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for CmsRepository.
|
||||
/// </summary>
|
||||
public class CmsRepositoryTests
|
||||
{
|
||||
private readonly IDbConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<CmsRepository> _logger;
|
||||
private readonly IOptions<DataAccessOptions> _options;
|
||||
|
||||
public CmsRepositoryTests()
|
||||
{
|
||||
_connectionFactory = Substitute.For<IDbConnectionFactory>();
|
||||
_logger = Substitute.For<ILogger<CmsRepository>>();
|
||||
_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<ArgumentNullException>(
|
||||
() => new CmsRepository(null!, _logger, _options))
|
||||
.ParamName.ShouldBe("connectionFactory");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_NullLogger_ThrowsArgumentNullException()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(
|
||||
() => new CmsRepository(_connectionFactory, null!, _options))
|
||||
.ParamName.ShouldBe("logger");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_NullOptions_ThrowsArgumentNullException()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(
|
||||
() => 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<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "CMS"));
|
||||
|
||||
var repository = new CmsRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<ConnectionException>(
|
||||
async () =>
|
||||
{
|
||||
await foreach (var _ in repository.GetMisDataAsync())
|
||||
{
|
||||
}
|
||||
});
|
||||
|
||||
ex.DataSource.ShouldBe("CMS");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetMisDataAsync_UsesCmsConnection()
|
||||
{
|
||||
// Arrange
|
||||
_connectionFactory.CreateCmsConnectionAsync(Arg.Any<CancellationToken>())
|
||||
.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<CancellationToken>());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cancellation Tests
|
||||
|
||||
[Fact]
|
||||
public async Task GetMisDataAsync_CancellationRequested_ThrowsOperationCanceledException()
|
||||
{
|
||||
// Arrange
|
||||
using var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
|
||||
_connectionFactory.CreateCmsConnectionAsync(Arg.Any<CancellationToken>())
|
||||
.ThrowsAsync(new OperationCanceledException(cts.Token));
|
||||
|
||||
var repository = new CmsRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<OperationCanceledException>(
|
||||
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<CancellationToken>())
|
||||
.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<ConnectionException>(
|
||||
async () =>
|
||||
{
|
||||
await foreach (var _ in repository.GetMisDataAsync(lastUpdate))
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetMisDataAsync_WithoutLastUpdateDT_UsesFullQuery()
|
||||
{
|
||||
// Arrange
|
||||
_connectionFactory.CreateCmsConnectionAsync(Arg.Any<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "CMS"));
|
||||
|
||||
var repository = new CmsRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<ConnectionException>(
|
||||
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
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for JdeRepository.
|
||||
/// </summary>
|
||||
public class JdeRepositoryTests
|
||||
{
|
||||
private readonly IDbConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<JdeRepository> _logger;
|
||||
private readonly IOptions<DataAccessOptions> _options;
|
||||
|
||||
public JdeRepositoryTests()
|
||||
{
|
||||
_connectionFactory = Substitute.For<IDbConnectionFactory>();
|
||||
_logger = Substitute.For<ILogger<JdeRepository>>();
|
||||
_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<ArgumentNullException>(
|
||||
() => new JdeRepository(null!, _logger, _options))
|
||||
.ParamName.ShouldBe("connectionFactory");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_NullLogger_ThrowsArgumentNullException()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(
|
||||
() => new JdeRepository(_connectionFactory, null!, _options))
|
||||
.ParamName.ShouldBe("logger");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_NullOptions_ThrowsArgumentNullException()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(
|
||||
() => 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<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<ConnectionException>(
|
||||
async () =>
|
||||
{
|
||||
await foreach (var _ in repository.GetWorkOrdersAsync())
|
||||
{
|
||||
}
|
||||
});
|
||||
|
||||
ex.DataSource.ShouldBe("JDE");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetWorkOrdersArchiveAsync_ConnectionFails_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_connectionFactory.CreateJdeConnectionAsync(Arg.Any<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<ConnectionException>(
|
||||
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<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<ConnectionException>(
|
||||
async () =>
|
||||
{
|
||||
await foreach (var _ in repository.GetWorkOrderStepsAsync())
|
||||
{
|
||||
}
|
||||
});
|
||||
|
||||
ex.DataSource.ShouldBe("JDE");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetWorkOrderStepsArchiveAsync_ConnectionFails_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_connectionFactory.CreateJdeConnectionAsync(Arg.Any<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<ConnectionException>(
|
||||
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<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<ConnectionException>(
|
||||
async () =>
|
||||
{
|
||||
await foreach (var _ in repository.GetWorkOrderTimesAsync())
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetWorkOrderTimesArchiveAsync_ConnectionFails_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_connectionFactory.CreateJdeConnectionAsync(Arg.Any<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<ConnectionException>(
|
||||
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<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<ConnectionException>(
|
||||
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<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<ConnectionException>(
|
||||
async () =>
|
||||
{
|
||||
await foreach (var _ in repository.GetWorkOrderComponentsAsync())
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetWorkOrderComponentsArchiveAsync_ConnectionFails_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_connectionFactory.CreateJdeConnectionAsync(Arg.Any<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<ConnectionException>(
|
||||
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<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<ConnectionException>(
|
||||
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<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<ConnectionException>(
|
||||
async () =>
|
||||
{
|
||||
await foreach (var _ in repository.GetLotUsagesAsync())
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetLotUsagesArchiveAsync_ConnectionFails_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_connectionFactory.CreateJdeConnectionAsync(Arg.Any<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<ConnectionException>(
|
||||
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<CancellationToken>())
|
||||
.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<ConnectionException>(
|
||||
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<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<ConnectionException>(
|
||||
async () =>
|
||||
{
|
||||
await foreach (var _ in repository.GetItemsAsync())
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetUsersAsync_ConnectionFails_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_connectionFactory.CreateJdeConnectionAsync(Arg.Any<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<ConnectionException>(
|
||||
async () =>
|
||||
{
|
||||
await foreach (var _ in repository.GetUsersAsync())
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetBranchesAsync_ConnectionFails_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_connectionFactory.CreateJdeConnectionAsync(Arg.Any<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<ConnectionException>(
|
||||
async () =>
|
||||
{
|
||||
await foreach (var _ in repository.GetBranchesAsync())
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetProfitCentersAsync_ConnectionFails_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_connectionFactory.CreateJdeConnectionAsync(Arg.Any<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<ConnectionException>(
|
||||
async () =>
|
||||
{
|
||||
await foreach (var _ in repository.GetProfitCentersAsync())
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetWorkCentersAsync_ConnectionFails_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_connectionFactory.CreateJdeConnectionAsync(Arg.Any<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<ConnectionException>(
|
||||
async () =>
|
||||
{
|
||||
await foreach (var _ in repository.GetWorkCentersAsync())
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetStatusCodesAsync_UsesJdeStageConnection()
|
||||
{
|
||||
// Arrange
|
||||
_connectionFactory.CreateJdeStageConnectionAsync(Arg.Any<CancellationToken>())
|
||||
.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<ConnectionException>(
|
||||
async () =>
|
||||
{
|
||||
await foreach (var _ in repository.GetStatusCodesAsync())
|
||||
{
|
||||
}
|
||||
});
|
||||
|
||||
ex.DataSource.ShouldBe("JDEStage");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetFunctionCodesAsync_ConnectionFails_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_connectionFactory.CreateJdeConnectionAsync(Arg.Any<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<ConnectionException>(
|
||||
async () =>
|
||||
{
|
||||
await foreach (var _ in repository.GetFunctionCodesAsync())
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetOrgHierarchyAsync_ConnectionFails_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_connectionFactory.CreateJdeConnectionAsync(Arg.Any<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<ConnectionException>(
|
||||
async () =>
|
||||
{
|
||||
await foreach (var _ in repository.GetOrgHierarchyAsync())
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetRouteMastersAsync_ConnectionFails_ThrowsConnectionException()
|
||||
{
|
||||
// Arrange
|
||||
_connectionFactory.CreateJdeConnectionAsync(Arg.Any<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<ConnectionException>(
|
||||
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<CancellationToken>())
|
||||
.ThrowsAsync(new OperationCanceledException(cts.Token));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<OperationCanceledException>(
|
||||
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<CancellationToken>())
|
||||
.ThrowsAsync(new OperationCanceledException(cts.Token));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<OperationCanceledException>(
|
||||
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<CancellationToken>())
|
||||
.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<ConnectionException>(
|
||||
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<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "JDE"));
|
||||
|
||||
var repository = new JdeRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
await Should.ThrowAsync<ConnectionException>(
|
||||
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
|
||||
}
|
||||
@@ -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<ILogger<LotFinderRepository>>();
|
||||
_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<CancellationToken>())
|
||||
.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<QueryException>(
|
||||
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<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection", "LotFinderDB"));
|
||||
|
||||
await Should.ThrowAsync<QueryException>(
|
||||
async () => await repository.RebuildIndicesAsync(tableName));
|
||||
}
|
||||
else
|
||||
{
|
||||
var ex = await Should.ThrowAsync<ArgumentException>(
|
||||
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<CancellationToken>())
|
||||
.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<QueryException>(
|
||||
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<ArgumentException>(
|
||||
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<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection", "LotFinderDB"));
|
||||
|
||||
var repository = new LotFinderRepository(_connectionFactory, _logger, _options);
|
||||
var records = new List<Item>();
|
||||
|
||||
// Act & Assert - should throw QueryException (wrapped ConnectionException), not ArgumentException
|
||||
await Should.ThrowAsync<QueryException>(
|
||||
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<Item>();
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<ArgumentException>(
|
||||
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<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB"));
|
||||
|
||||
var repository = new LotFinderRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<QueryException>(
|
||||
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<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB"));
|
||||
|
||||
var repository = new LotFinderRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<QueryException>(
|
||||
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<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB"));
|
||||
|
||||
var repository = new LotFinderRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<QueryException>(
|
||||
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<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB"));
|
||||
|
||||
var repository = new LotFinderRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<QueryException>(
|
||||
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<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB"));
|
||||
|
||||
var repository = new LotFinderRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<QueryException>(
|
||||
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<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB"));
|
||||
|
||||
var repository = new LotFinderRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<QueryException>(
|
||||
async () => await repository.GetTableSpecAsync("Item"));
|
||||
|
||||
ex.QueryName.ShouldBe("SQL_GET_TABLE_COLUMNS");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PostProcessMisDataAsync_ConnectionFails_ThrowsQueryException()
|
||||
{
|
||||
// Arrange
|
||||
_connectionFactory.CreateLotFinderConnectionAsync(Arg.Any<CancellationToken>())
|
||||
.ThrowsAsync(new ConnectionException("Test connection error", "LotFinderDB"));
|
||||
|
||||
var repository = new LotFinderRepository(_connectionFactory, _logger, _options);
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Should.ThrowAsync<QueryException>(
|
||||
async () => await repository.PostProcessMisDataAsync());
|
||||
|
||||
ex.QueryName.ShouldBe("SQL_POSTPROCESS_MISDATA");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Cancellation Tests
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using JdeScoping.DataAccess.Models.Results;
|
||||
using JdeScoping.Core.Models.SearchResults;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user