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 |
|
| # | 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` | |
|
| 1 | WorkOrder | JDE | WorkOrder_Curr | Yes | Yes | Yes | Yes | `workorder_curr.pb.zstd` | |
|
||||||
| 2 | LotUsage | JDE | LotUsage_Curr | Yes | Yes | Yes | Yes | `lotusage_curr.json.zstd` | |
|
| 2 | LotUsage | JDE | LotUsage_Curr | Yes | Yes | Yes | Yes | `lotusage_curr.pb.zstd` | |
|
||||||
| 3 | Item | JDE | Item | Yes | Yes | Yes | Yes | `item.json.zstd` | |
|
| 3 | Item | JDE | Item | Yes | Yes | Yes | Yes | `item.pb.zstd` | |
|
||||||
| 4 | Lot | JDE | Lot | Yes | Yes | Yes | Yes | `lot.json.zstd` | |
|
| 4 | Lot | JDE | Lot | Yes | Yes | Yes | Yes | `lot.pb.zstd` | |
|
||||||
| 5 | WorkOrderTime | JDE | WorkOrderTime_Curr | Yes | Yes | Yes | Yes | `workordertime_curr.json.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.json.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.json.zstd` | |
|
| 7 | WorkOrderStep | JDE | WorkOrderStep_Curr | Yes | Yes | Yes | Yes | `workorderstep_curr.pb.zstd` | |
|
||||||
| 8 | WorkOrderRouting | JDE | WorkOrderRouting | Yes | Yes | Yes | Yes | `workorderrouting.json.zstd` | |
|
| 8 | WorkOrderRouting | JDE | WorkOrderRouting | Yes | Yes | Yes | Yes | `workorderrouting.pb.zstd` | |
|
||||||
| 9 | Branch | JDE | Branch | Yes | Yes | Yes | Yes | `branch.json.zstd` | typeCode='BP' |
|
| 9 | Branch | JDE | Branch | Yes | Yes | Yes | Yes | `branch.pb.zstd` | typeCode='BP' |
|
||||||
| 10 | ProfitCenter | JDE | ProfitCenter | Yes | Yes | Yes | Yes | `profitcenter.json.zstd` | typeCode='I3' |
|
| 10 | ProfitCenter | JDE | ProfitCenter | Yes | Yes | Yes | Yes | `profitcenter.pb.zstd` | typeCode='I3' |
|
||||||
| 11 | WorkCenter | JDE | WorkCenter | Yes | Yes | Yes | Yes | `workcenter.json.zstd` | typeCode='WC' |
|
| 11 | WorkCenter | JDE | WorkCenter | Yes | Yes | Yes | Yes | `workcenter.pb.zstd` | typeCode='WC' |
|
||||||
| 12 | StatusCode | JDE | StatusCode | Yes | Yes | Yes | Yes | *(none)* | GIW connection |
|
| 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 |
|
| 13 | JdeUser | JDE | JdeUser | Yes | Yes | Yes | No | `jdeuser.pb.zstd` | Same query both |
|
||||||
| 14 | OrgHierarchy | JDE | OrgHierarchy | Yes | Yes | Yes | Yes | `orghierarchy.json.zstd` | |
|
| 14 | OrgHierarchy | JDE | OrgHierarchy | Yes | Yes | Yes | Yes | `orghierarchy.pb.zstd` | |
|
||||||
| 15 | RouteMaster | JDE | RouteMaster | Yes | Yes | Yes | Yes | `routemaster.json.zstd` | |
|
| 15 | RouteMaster | JDE | RouteMaster | Yes | Yes | Yes | Yes | `routemaster.pb.zstd` | |
|
||||||
| 16 | FunctionCode | JDE | FunctionCode | Yes | Yes | Yes | No | `functioncode.json.zstd` | Always full reload |
|
| 16 | FunctionCode | JDE | FunctionCode | Yes | Yes | Yes | No | `functioncode.pb.zstd` | Always full reload |
|
||||||
| 17 | MisData | CMS | MisData | Yes | Yes | No | Yes | `misdata.json.zstd` | Hourly disabled |
|
| 17 | MisData | CMS | MisData | Yes | Yes | No | Yes | `misdata.pb.zstd` | Hourly disabled |
|
||||||
|
|
||||||
### Archive Syncs (5) - ALL DISABLED
|
### Archive Syncs (5) - ALL DISABLED
|
||||||
|
|
||||||
| # | Import Name | Source | Dest Table | Mass | Daily | Hourly | Filter | Cache File | Notes |
|
| # | 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 |
|
| 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.json.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.json.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.json.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.json.zstd` | DISABLED |
|
| 22 | WorkOrderStep_Archive | JDE | WorkOrderStep_Hist | No | No | No | No | `workorderstep_hist.pb.zstd` | DISABLED |
|
||||||
|
|
||||||
**Cache File Location:** `CACHED_DB_FILES/`
|
**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>
|
/// <param name="ct">Cancellation token.</param>
|
||||||
/// <returns>Latest data updates.</returns>
|
/// <returns>Latest data updates.</returns>
|
||||||
Task<List<DataUpdate>> GetLastDataUpdatesAsync(CancellationToken ct = default);
|
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>
|
/// <returns>Top 25 matching work centers.</returns>
|
||||||
Task<List<WorkCenter>> SearchWorkCentersAsync(string filter, CancellationToken ct = default);
|
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>
|
/// <summary>
|
||||||
/// Searches profit centers by code or description.
|
/// Searches profit centers by code or description.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -59,14 +51,6 @@ public partial interface ILotFinderRepository
|
|||||||
/// <returns>Top 25 matching profit centers.</returns>
|
/// <returns>Top 25 matching profit centers.</returns>
|
||||||
Task<List<ProfitCenter>> SearchProfitCentersAsync(string filter, CancellationToken ct = default);
|
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>
|
/// <summary>
|
||||||
/// Searches users by user ID, full name, or address number.
|
/// Searches users by user ID, full name, or address number.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -75,14 +59,6 @@ public partial interface ILotFinderRepository
|
|||||||
/// <returns>Top 25 matching users.</returns>
|
/// <returns>Top 25 matching users.</returns>
|
||||||
Task<List<JdeUser>> SearchUsersAsync(string filter, CancellationToken ct = default);
|
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>
|
/// <summary>
|
||||||
/// Looks up lots by lot number and item number.
|
/// Looks up lots by lot number and item number.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using JdeScoping.Core.Models.Enums;
|
|
||||||
using JdeScoping.Core.Models.Search;
|
using JdeScoping.Core.Models.Search;
|
||||||
|
|
||||||
namespace JdeScoping.Core.Interfaces;
|
namespace JdeScoping.Core.Interfaces;
|
||||||
@@ -46,20 +45,4 @@ public partial interface ILotFinderRepository
|
|||||||
/// <param name="ct">Cancellation token.</param>
|
/// <param name="ct">Cancellation token.</param>
|
||||||
/// <returns>Generated search ID.</returns>
|
/// <returns>Generated search ID.</returns>
|
||||||
Task<int> SubmitSearchAsync(Search search, CancellationToken ct = default);
|
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)
|
// Register repositories as scoped (per-request lifetime)
|
||||||
services.AddScoped<ILotFinderRepository, LotFinderRepository>();
|
services.AddScoped<ILotFinderRepository, LotFinderRepository>();
|
||||||
services.AddScoped<IJdeRepository, JdeRepository>();
|
|
||||||
services.AddScoped<ICmsRepository, CmsRepository>();
|
|
||||||
|
|
||||||
// Register SqlKata compiler (singleton, thread-safe)
|
// Register SqlKata compiler (singleton, thread-safe)
|
||||||
services.AddSingleton<SqlServerCompiler>();
|
services.AddSingleton<SqlServerCompiler>();
|
||||||
@@ -74,8 +72,6 @@ public static class DataAccessDependencyInjection
|
|||||||
|
|
||||||
// Register repositories as scoped (per-request lifetime)
|
// Register repositories as scoped (per-request lifetime)
|
||||||
services.AddScoped<ILotFinderRepository, LotFinderRepository>();
|
services.AddScoped<ILotFinderRepository, LotFinderRepository>();
|
||||||
services.AddScoped<IJdeRepository, JdeRepository>();
|
|
||||||
services.AddScoped<ICmsRepository, CmsRepository>();
|
|
||||||
|
|
||||||
return services;
|
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>
|
/// </summary>
|
||||||
public int MisDataTimeoutSeconds { get; set; } = 60000;
|
public int MisDataTimeoutSeconds { get; set; } = 60000;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Timeout for index rebuild operations in seconds.
|
|
||||||
/// </summary>
|
|
||||||
public int RebuildIndexTimeoutSeconds { get; set; } = 600;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// JDE production schema name (e.g., PRODDTA).
|
/// JDE production schema name (e.g., PRODDTA).
|
||||||
/// </summary>
|
/// </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
|
cte.NumberRecords
|
||||||
FROM DU_CTE cte
|
FROM DU_CTE cte
|
||||||
WHERE cte.RN = 1";
|
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 + '%'
|
wc.Description LIKE '%' + @filter + '%'
|
||||||
ORDER BY wc.Code";
|
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>
|
/// <summary>
|
||||||
/// Searches profit centers by code or description.
|
/// Searches profit centers by code or description.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -76,17 +65,6 @@ public static partial class LotFinderQueries
|
|||||||
pc.Description LIKE '%' + @filter + '%'
|
pc.Description LIKE '%' + @filter + '%'
|
||||||
ORDER BY pc.Code";
|
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>
|
/// <summary>
|
||||||
/// Searches users by user ID, full name, or address number.
|
/// Searches users by user ID, full name, or address number.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -102,19 +80,6 @@ public static partial class LotFinderQueries
|
|||||||
CAST(u.AddressNumber AS VARCHAR(10)) LIKE '%' + @filter + '%'
|
CAST(u.AddressNumber AS VARCHAR(10)) LIKE '%' + @filter + '%'
|
||||||
ORDER BY u.UserID, u.FullName";
|
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>
|
/// <summary>
|
||||||
/// Looks up lots by lot number and item number using OPENJSON.
|
/// Looks up lots by lot number and item number using OPENJSON.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -55,22 +55,4 @@ public static partial class LotFinderQueries
|
|||||||
SELECT s.Results
|
SELECT s.Results
|
||||||
FROM dbo.Search AS s
|
FROM dbo.Search AS s
|
||||||
WHERE s.ID = @id";
|
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 Dapper;
|
||||||
using JdeScoping.Core.Models.Infrastructure;
|
using JdeScoping.Core.Models.Infrastructure;
|
||||||
using JdeScoping.DataAccess.Queries;
|
using JdeScoping.DataAccess.Queries;
|
||||||
using Microsoft.Data.SqlClient;
|
|
||||||
|
|
||||||
namespace JdeScoping.DataAccess.Repositories;
|
namespace JdeScoping.DataAccess.Repositories;
|
||||||
|
|
||||||
@@ -33,166 +31,4 @@ public partial class LotFinderRepository
|
|||||||
throw;
|
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/>
|
/// <inheritdoc/>
|
||||||
public async Task<List<ProfitCenter>> SearchProfitCentersAsync(string filter, CancellationToken ct = default)
|
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/>
|
/// <inheritdoc/>
|
||||||
public async Task<List<JdeUser>> SearchUsersAsync(string filter, CancellationToken ct = default)
|
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/>
|
/// <inheritdoc/>
|
||||||
public async Task<List<Lot>> LookupLotsAsync(List<LotViewModel> lots, CancellationToken ct = default)
|
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.Core.Interfaces;
|
||||||
using JdeScoping.DataAccess.Options;
|
using JdeScoping.DataAccess.Options;
|
||||||
using JdeScoping.DataAccess.Exceptions;
|
using JdeScoping.DataAccess.Exceptions;
|
||||||
@@ -19,22 +18,6 @@ public partial class LotFinderRepository : ILotFinderRepository
|
|||||||
private readonly IOptions<DataAccessOptions> _options;
|
private readonly IOptions<DataAccessOptions> _options;
|
||||||
private const string RepositoryName = "LotFinderRepository";
|
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>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="LotFinderRepository"/> class.
|
/// Initializes a new instance of the <see cref="LotFinderRepository"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -83,41 +66,4 @@ public partial class LotFinderRepository : ILotFinderRepository
|
|||||||
// SQL Server timeout error number: -2
|
// SQL Server timeout error number: -2
|
||||||
return ex.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.Api;
|
||||||
using JdeScoping.Core.Interfaces;
|
|
||||||
using JdeScoping.DataAccess.Options;
|
using JdeScoping.DataAccess.Options;
|
||||||
using JdeScoping.DataSync.Options;
|
using JdeScoping.DataSync.Options;
|
||||||
using JdeScoping.ExcelIO.Options;
|
using JdeScoping.ExcelIO.Options;
|
||||||
using JdeScoping.Infrastructure.Options;
|
|
||||||
using JdeScoping.Database;
|
using JdeScoping.Database;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
@@ -72,11 +70,5 @@ static void ValidateServices(IServiceProvider services)
|
|||||||
_ = provider.GetRequiredService<IOptions<DataSyncOptions>>();
|
_ = provider.GetRequiredService<IOptions<DataSyncOptions>>();
|
||||||
_ = provider.GetRequiredService<IOptions<ExcelExportOptions>>();
|
_ = provider.GetRequiredService<IOptions<ExcelExportOptions>>();
|
||||||
_ = provider.GetRequiredService<IOptions<SearchProcessingOptions>>();
|
_ = provider.GetRequiredService<IOptions<SearchProcessingOptions>>();
|
||||||
_ = provider.GetRequiredService<IOptions<DataSourceOptions>>();
|
|
||||||
|
|
||||||
// Validate data source services
|
|
||||||
_ = provider.GetRequiredService<IJdeDataSource>();
|
|
||||||
_ = provider.GetRequiredService<ICmsDataSource>();
|
|
||||||
|
|
||||||
Console.WriteLine("Service validation completed successfully.");
|
Console.WriteLine("Service validation completed successfully.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ using JdeScoping.Core.Options;
|
|||||||
using JdeScoping.Infrastructure.Auth;
|
using JdeScoping.Infrastructure.Auth;
|
||||||
using JdeScoping.Infrastructure.Options;
|
using JdeScoping.Infrastructure.Options;
|
||||||
using JdeScoping.Infrastructure.Security;
|
using JdeScoping.Infrastructure.Security;
|
||||||
using JdeScoping.Infrastructure.Sources.Cms;
|
|
||||||
using JdeScoping.Infrastructure.Sources.Jde;
|
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.DependencyInjection;
|
namespace Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -25,27 +23,9 @@ public static class InfrastructureDependencyInjection
|
|||||||
IConfiguration configuration)
|
IConfiguration configuration)
|
||||||
{
|
{
|
||||||
// Bind configuration
|
// Bind configuration
|
||||||
services.Configure<DataSourceOptions>(
|
|
||||||
configuration.GetSection(DataSourceOptions.SectionName));
|
|
||||||
services.Configure<LdapOptions>(
|
services.Configure<LdapOptions>(
|
||||||
configuration.GetSection(LdapOptions.SectionName));
|
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
|
// Register auth service based on configuration
|
||||||
var ldapOptions = configuration
|
var ldapOptions = configuration
|
||||||
.GetSection(LdapOptions.SectionName)
|
.GetSection(LdapOptions.SectionName)
|
||||||
|
|||||||
@@ -7,13 +7,11 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.1" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.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.Logging.Abstractions" Version="10.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Options" 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="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" />
|
<PackageReference Include="System.DirectoryServices.Protocols" Version="10.0.1" />
|
||||||
</ItemGroup>
|
</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.Models.Search;
|
||||||
using JdeScoping.Core.ViewModels;
|
using JdeScoping.Core.ViewModels;
|
||||||
using JdeScoping.DataAccess.Options;
|
using JdeScoping.DataAccess.Options;
|
||||||
@@ -31,8 +28,7 @@ public class LotFinderRepositoryTests
|
|||||||
_logger = Substitute.For<ILogger<LotFinderRepository>>();
|
_logger = Substitute.For<ILogger<LotFinderRepository>>();
|
||||||
_options = Microsoft.Extensions.Options.Options.Create(new DataAccessOptions
|
_options = Microsoft.Extensions.Options.Options.Create(new DataAccessOptions
|
||||||
{
|
{
|
||||||
DefaultTimeoutSeconds = 30,
|
DefaultTimeoutSeconds = 30
|
||||||
RebuildIndexTimeoutSeconds = 60
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,160 +73,6 @@ public class LotFinderRepositoryTests
|
|||||||
|
|
||||||
#endregion
|
#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
|
#region Connection Exception Handling Tests
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -315,38 +157,6 @@ public class LotFinderRepositoryTests
|
|||||||
ex.QueryName.ShouldBe(SqlObjects.SubmitSearch);
|
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
|
#endregion
|
||||||
|
|
||||||
#region Reference Data Lookup Exception Handling Tests
|
#region Reference Data Lookup Exception Handling Tests
|
||||||
@@ -415,22 +225,6 @@ public class LotFinderRepositoryTests
|
|||||||
ex.QueryName.ShouldBe("SQL_SEARCH_WORK_CENTERS");
|
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]
|
[Fact]
|
||||||
public async Task SearchProfitCentersAsync_ConnectionFails_ThrowsQueryException()
|
public async Task SearchProfitCentersAsync_ConnectionFails_ThrowsQueryException()
|
||||||
{
|
{
|
||||||
@@ -447,22 +241,6 @@ public class LotFinderRepositoryTests
|
|||||||
ex.QueryName.ShouldBe("SQL_SEARCH_PROFIT_CENTERS");
|
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]
|
[Fact]
|
||||||
public async Task SearchUsersAsync_ConnectionFails_ThrowsQueryException()
|
public async Task SearchUsersAsync_ConnectionFails_ThrowsQueryException()
|
||||||
{
|
{
|
||||||
@@ -479,22 +257,6 @@ public class LotFinderRepositoryTests
|
|||||||
ex.QueryName.ShouldBe("SQL_SEARCH_USERS");
|
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]
|
[Fact]
|
||||||
public async Task LookupLotsAsync_ConnectionFails_ThrowsQueryException()
|
public async Task LookupLotsAsync_ConnectionFails_ThrowsQueryException()
|
||||||
{
|
{
|
||||||
@@ -532,38 +294,6 @@ public class LotFinderRepositoryTests
|
|||||||
ex.QueryName.ShouldBe("SQL_GET_LAST_DATA_UPDATES");
|
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
|
#endregion
|
||||||
|
|
||||||
#region Cancellation Tests
|
#region Cancellation Tests
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using JdeScoping.DataAccess.Models.Results;
|
using JdeScoping.Core.Models.SearchResults;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
using Xunit;
|
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