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:
Joseph Doherty
2026-01-07 05:04:49 -05:00
parent 6952f686fa
commit 1618b6664d
52 changed files with 1497 additions and 3779 deletions
@@ -1,17 +0,0 @@
using JdeScoping.Core.Models;
using JdeScoping.Core.Models.Quality;
namespace JdeScoping.Core.Interfaces;
/// <summary>
/// Interface for fetching data from CMS source system.
/// </summary>
public interface ICmsDataSource
{
/// <summary>
/// Gets MIS data from CMS as an async stream.
/// </summary>
/// <param name="minimumDt">Minimum update timestamp for incremental fetch. Null for full fetch.</param>
/// <param name="cancellationToken">Cancellation token.</param>
IAsyncEnumerable<MisData> GetMisDataAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default);
}
@@ -1,68 +0,0 @@
using JdeScoping.Core.Models;
using JdeScoping.Core.Models.Inventory;
using JdeScoping.Core.Models.Organization;
using JdeScoping.Core.Models.WorkOrders;
namespace JdeScoping.Core.Interfaces;
/// <summary>
/// Interface for fetching data from JDE (JD Edwards) source system.
/// </summary>
public interface IJdeDataSource
{
/// <summary>
/// Gets work orders from JDE as an async stream.
/// </summary>
/// <param name="minimumDt">Minimum update timestamp for incremental fetch. Null for full fetch.</param>
/// <param name="cancellationToken">Cancellation token.</param>
IAsyncEnumerable<WorkOrder> GetWorkOrdersAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default);
/// <summary>
/// Gets lot usage records from JDE as an async stream.
/// </summary>
/// <param name="minimumDt">Minimum update timestamp for incremental fetch. Null for full fetch.</param>
/// <param name="cancellationToken">Cancellation token.</param>
IAsyncEnumerable<LotUsage> GetLotUsagesAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default);
/// <summary>
/// Gets lots from JDE as an async stream.
/// </summary>
/// <param name="minimumDt">Minimum update timestamp for incremental fetch. Null for full fetch.</param>
/// <param name="cancellationToken">Cancellation token.</param>
IAsyncEnumerable<Lot> GetLotsAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default);
/// <summary>
/// Gets items from JDE as an async stream.
/// </summary>
/// <param name="minimumDt">Minimum update timestamp for incremental fetch. Null for full fetch.</param>
/// <param name="cancellationToken">Cancellation token.</param>
IAsyncEnumerable<Item> GetItemsAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default);
/// <summary>
/// Gets work centers from JDE as an async stream.
/// </summary>
/// <param name="minimumDt">Minimum update timestamp for incremental fetch. Null for full fetch.</param>
/// <param name="cancellationToken">Cancellation token.</param>
IAsyncEnumerable<WorkCenter> GetWorkCentersAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default);
/// <summary>
/// Gets profit centers from JDE as an async stream.
/// </summary>
/// <param name="minimumDt">Minimum update timestamp for incremental fetch. Null for full fetch.</param>
/// <param name="cancellationToken">Cancellation token.</param>
IAsyncEnumerable<ProfitCenter> GetProfitCentersAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default);
/// <summary>
/// Gets JDE users as an async stream.
/// </summary>
/// <param name="minimumDt">Minimum update timestamp for incremental fetch. Null for full fetch.</param>
/// <param name="cancellationToken">Cancellation token.</param>
IAsyncEnumerable<JdeUser> GetUsersAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default);
/// <summary>
/// Gets branches from JDE as an async stream.
/// </summary>
/// <param name="minimumDt">Minimum update timestamp for incremental fetch. Null for full fetch.</param>
/// <param name="cancellationToken">Cancellation token.</param>
IAsyncEnumerable<Branch> GetBranchesAsync(DateTime? minimumDt = null, CancellationToken cancellationToken = default);
}
@@ -13,44 +13,4 @@ public partial interface ILotFinderRepository
/// <param name="ct">Cancellation token.</param>
/// <returns>Latest data updates.</returns>
Task<List<DataUpdate>> GetLastDataUpdatesAsync(CancellationToken ct = default);
/// <summary>
/// Gets table schema specification for dynamic SQL generation.
/// </summary>
/// <param name="tableName">Table name.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Table specification with columns and primary key.</returns>
Task<TableSpec> GetTableSpecAsync(string tableName, CancellationToken ct = default);
/// <summary>
/// Rebuilds all indices on a table with fillfactor of 95.
/// Table name is validated against whitelist for SQL injection prevention.
/// </summary>
/// <param name="tableName">Table name (must be in whitelist).</param>
/// <param name="ct">Cancellation token.</param>
/// <exception cref="ArgumentException">Thrown if table name is not in whitelist.</exception>
Task RebuildIndicesAsync(string tableName, CancellationToken ct = default);
/// <summary>
/// Post-processes imported MIS data to set obsolete dates.
/// </summary>
/// <param name="ct">Cancellation token.</param>
Task PostProcessMisDataAsync(CancellationToken ct = default);
/// <summary>
/// Performs bulk insert of records into a table.
/// </summary>
/// <typeparam name="T">Record type.</typeparam>
/// <param name="tableName">Target table name.</param>
/// <param name="records">Records to insert.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Number of records inserted.</returns>
Task<int> BulkInsertAsync<T>(string tableName, IEnumerable<T> records, CancellationToken ct = default);
/// <summary>
/// Truncates a table, removing all records.
/// </summary>
/// <param name="tableName">Table name.</param>
/// <param name="ct">Cancellation token.</param>
Task TruncateTableAsync(string tableName, CancellationToken ct = default);
}
@@ -43,14 +43,6 @@ public partial interface ILotFinderRepository
/// <returns>Top 25 matching work centers.</returns>
Task<List<WorkCenter>> SearchWorkCentersAsync(string filter, CancellationToken ct = default);
/// <summary>
/// Looks up work centers by codes.
/// </summary>
/// <param name="codes">Work center codes to match.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Matching work centers.</returns>
Task<List<WorkCenter>> LookupWorkCentersAsync(List<string> codes, CancellationToken ct = default);
/// <summary>
/// Searches profit centers by code or description.
/// </summary>
@@ -59,14 +51,6 @@ public partial interface ILotFinderRepository
/// <returns>Top 25 matching profit centers.</returns>
Task<List<ProfitCenter>> SearchProfitCentersAsync(string filter, CancellationToken ct = default);
/// <summary>
/// Looks up profit centers by codes.
/// </summary>
/// <param name="codes">Profit center codes to match.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Matching profit centers.</returns>
Task<List<ProfitCenter>> LookupProfitCentersAsync(List<string> codes, CancellationToken ct = default);
/// <summary>
/// Searches users by user ID, full name, or address number.
/// </summary>
@@ -75,14 +59,6 @@ public partial interface ILotFinderRepository
/// <returns>Top 25 matching users.</returns>
Task<List<JdeUser>> SearchUsersAsync(string filter, CancellationToken ct = default);
/// <summary>
/// Looks up users by user IDs or address numbers.
/// </summary>
/// <param name="userIds">User IDs or address numbers to match.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Matching users.</returns>
Task<List<JdeUser>> LookupUsersAsync(List<string> userIds, CancellationToken ct = default);
/// <summary>
/// Looks up lots by lot number and item number.
/// </summary>
@@ -1,4 +1,3 @@
using JdeScoping.Core.Models.Enums;
using JdeScoping.Core.Models.Search;
namespace JdeScoping.Core.Interfaces;
@@ -46,20 +45,4 @@ public partial interface ILotFinderRepository
/// <param name="ct">Cancellation token.</param>
/// <returns>Generated search ID.</returns>
Task<int> SubmitSearchAsync(Search search, CancellationToken ct = default);
/// <summary>
/// Updates the status of a search.
/// </summary>
/// <param name="id">Search ID.</param>
/// <param name="status">New status.</param>
/// <param name="ct">Cancellation token.</param>
Task UpdateSearchStatusAsync(int id, SearchStatus status, CancellationToken ct = default);
/// <summary>
/// Stores the Excel results for a completed search.
/// </summary>
/// <param name="id">Search ID.</param>
/// <param name="results">Excel file bytes.</param>
/// <param name="ct">Cancellation token.</param>
Task UpdateSearchResultsAsync(int id, byte[] results, CancellationToken ct = default);
}
@@ -1,17 +0,0 @@
namespace JdeScoping.Core.Interfaces;
/// <summary>
/// Processor for executing user search requests.
/// </summary>
public interface ISearchProcessor
{
/// <summary>
/// Processes a queued search request.
/// </summary>
Task ProcessSearchAsync(int searchId, CancellationToken cancellationToken = default);
/// <summary>
/// Gets the next pending search to process.
/// </summary>
Task<int?> GetNextPendingSearchAsync(CancellationToken cancellationToken = default);
}
@@ -1,22 +0,0 @@
namespace JdeScoping.Core.Interfaces;
/// <summary>
/// Processor for data synchronization from JDE/CMS to local cache.
/// </summary>
public interface IUpdateProcessor
{
/// <summary>
/// Runs a mass (full) data refresh.
/// </summary>
Task RunMassRefreshAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Runs a daily incremental data refresh.
/// </summary>
Task RunDailyRefreshAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Runs an hourly data refresh.
/// </summary>
Task RunHourlyRefreshAsync(CancellationToken cancellationToken = default);
}
@@ -38,8 +38,6 @@ public static class DataAccessDependencyInjection
// Register repositories as scoped (per-request lifetime)
services.AddScoped<ILotFinderRepository, LotFinderRepository>();
services.AddScoped<IJdeRepository, JdeRepository>();
services.AddScoped<ICmsRepository, CmsRepository>();
// Register SqlKata compiler (singleton, thread-safe)
services.AddSingleton<SqlServerCompiler>();
@@ -74,8 +72,6 @@ public static class DataAccessDependencyInjection
// Register repositories as scoped (per-request lifetime)
services.AddScoped<ILotFinderRepository, LotFinderRepository>();
services.AddScoped<IJdeRepository, JdeRepository>();
services.AddScoped<ICmsRepository, CmsRepository>();
return services;
}
@@ -1,19 +0,0 @@
using JdeScoping.Core.Models;
using JdeScoping.Core.Models.Quality;
namespace JdeScoping.DataAccess.Interfaces;
/// <summary>
/// Repository for accessing CMS Oracle database.
/// </summary>
public interface ICmsRepository
{
/// <summary>
/// Gets Manufacturing Information System (MIS) data from CMS database.
/// Uses MisDataTimeoutSeconds timeout due to complex 10-table JOIN.
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming MIS data records.</returns>
IAsyncEnumerable<MisData> GetMisDataAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
}
@@ -1,43 +0,0 @@
using JdeScoping.Core.Models.Inventory;
namespace JdeScoping.DataAccess.Interfaces;
/// <summary>
/// Inventory (lots) operations for JDE Oracle repository.
/// </summary>
public partial interface IJdeRepository
{
/// <summary>
/// Gets lot master data from production schema, optionally filtered by last update.
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming lots.</returns>
IAsyncEnumerable<Lot> GetLotsAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
/// <summary>
/// Gets lot usage (cardex) transactions from production schema, optionally filtered by last update.
/// Uses special LotUsageTimeoutSeconds timeout due to large dataset.
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming lot usages.</returns>
IAsyncEnumerable<LotUsage> GetLotUsagesAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
/// <summary>
/// Gets lot usage transactions from archive schema.
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming archived lot usages.</returns>
IAsyncEnumerable<LotUsage> GetLotUsagesArchiveAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
/// <summary>
/// Gets lot location tracking from JDE Stage view.
/// Uses JDE Stage connection.
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming lot locations.</returns>
IAsyncEnumerable<LotLocation> GetLotLocationsAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
}
@@ -1,86 +0,0 @@
using JdeScoping.Core.Models;
using JdeScoping.Core.Models.Inventory;
using JdeScoping.Core.Models.Lookup;
using JdeScoping.Core.Models.Organization;
namespace JdeScoping.DataAccess.Interfaces;
/// <summary>
/// Reference data operations for JDE Oracle repository.
/// </summary>
public partial interface IJdeRepository
{
/// <summary>
/// Gets item master data from production schema, optionally filtered by last update.
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming items.</returns>
IAsyncEnumerable<Item> GetItemsAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
/// <summary>
/// Gets user/operator data from production schema.
/// Note: Incremental filtering not supported for users (full sync always).
/// </summary>
/// <param name="lastUpdateDt">Ignored (full sync always).</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming users.</returns>
IAsyncEnumerable<JdeUser> GetUsersAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
/// <summary>
/// Gets branch business units from production schema (type code 'BP').
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming branches.</returns>
IAsyncEnumerable<Branch> GetBranchesAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
/// <summary>
/// Gets profit center business units from production schema (type code 'I3').
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming profit centers.</returns>
IAsyncEnumerable<ProfitCenter> GetProfitCentersAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
/// <summary>
/// Gets work center business units from production schema (type code 'WC').
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming work centers.</returns>
IAsyncEnumerable<WorkCenter> GetWorkCentersAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
/// <summary>
/// Gets status codes from JDE Stage view.
/// Uses JDE Stage connection.
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming status codes.</returns>
IAsyncEnumerable<StatusCode> GetStatusCodesAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
/// <summary>
/// Gets function codes from production schema.
/// Note: Does not support incremental filtering (full sync always).
/// </summary>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming function codes.</returns>
IAsyncEnumerable<FunctionCode> GetFunctionCodesAsync(CancellationToken ct = default);
/// <summary>
/// Gets organization hierarchy (work center to profit center mapping) from production schema.
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming org hierarchy records.</returns>
IAsyncEnumerable<OrgHierarchy> GetOrgHierarchyAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
/// <summary>
/// Gets item routing master data from production schema.
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming route masters.</returns>
IAsyncEnumerable<RouteMaster> GetRouteMastersAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
}
@@ -1,81 +0,0 @@
using JdeScoping.Core.Models.WorkOrders;
namespace JdeScoping.DataAccess.Interfaces;
/// <summary>
/// Work order operations for JDE Oracle repository.
/// </summary>
public partial interface IJdeRepository
{
/// <summary>
/// Gets work orders from production schema, optionally filtered by last update.
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming work orders.</returns>
IAsyncEnumerable<WorkOrder> GetWorkOrdersAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
/// <summary>
/// Gets work orders from archive schema.
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming archived work orders.</returns>
IAsyncEnumerable<WorkOrder> GetWorkOrdersArchiveAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
/// <summary>
/// Gets work order steps from production schema, optionally filtered by last update.
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming work order steps.</returns>
IAsyncEnumerable<WorkOrderStep> GetWorkOrderStepsAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
/// <summary>
/// Gets work order steps from archive schema.
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming archived work order steps.</returns>
IAsyncEnumerable<WorkOrderStep> GetWorkOrderStepsArchiveAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
/// <summary>
/// Gets work order time transactions from production schema, optionally filtered by last update.
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming work order times.</returns>
IAsyncEnumerable<WorkOrderTime> GetWorkOrderTimesAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
/// <summary>
/// Gets work order time transactions from archive schema.
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming archived work order times.</returns>
IAsyncEnumerable<WorkOrderTime> GetWorkOrderTimesArchiveAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
/// <summary>
/// Gets work order routing transactions from production schema, optionally filtered by last update.
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming work order routings.</returns>
IAsyncEnumerable<WorkOrderRouting> GetWorkOrderRoutingsAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
/// <summary>
/// Gets work order component usage from production schema, optionally filtered by last update.
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming work order components.</returns>
IAsyncEnumerable<WorkOrderComponent> GetWorkOrderComponentsAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
/// <summary>
/// Gets work order component usage from archive schema.
/// </summary>
/// <param name="lastUpdateDt">Optional cutoff for incremental sync.</param>
/// <param name="ct">Cancellation token.</param>
/// <returns>Streaming archived work order components.</returns>
IAsyncEnumerable<WorkOrderComponent> GetWorkOrderComponentsArchiveAsync(DateTime? lastUpdateDt = null, CancellationToken ct = default);
}
@@ -1,9 +0,0 @@
namespace JdeScoping.DataAccess.Interfaces;
/// <summary>
/// Repository for accessing JDE Oracle database.
/// All methods return IAsyncEnumerable for memory-efficient streaming.
/// </summary>
public partial interface IJdeRepository
{
}
@@ -25,11 +25,6 @@ public class DataAccessOptions
/// </summary>
public int MisDataTimeoutSeconds { get; set; } = 60000;
/// <summary>
/// Timeout for index rebuild operations in seconds.
/// </summary>
public int RebuildIndexTimeoutSeconds { get; set; } = 600;
/// <summary>
/// JDE production schema name (e.g., PRODDTA).
/// </summary>
@@ -1,45 +0,0 @@
namespace JdeScoping.DataAccess.Queries;
/// <summary>
/// SQL query constants for the CMS Oracle database.
/// </summary>
public static class CmsQueries
{
/// <summary>
/// Gets Manufacturing Information System (MIS) data from CMS database.
/// Complex 10-table JOIN through CMS schema (INFODBA).
/// </summary>
public const string SqlGetMisData = @"
SELECT DISTINCT
mis.P_PART_NUMBER AS ItemNumber,
mis.P_OPERATION_NUMBER AS SequenceNumber,
item.PITEM_ID AS MISNumber,
itemrev.PITEM_REVISION_ID AS RevID,
TRIM(mis.P_SITE) AS BranchCode,
zim_test_details.P_SEQ_NUMBER AS CharNumber,
zim_test_details.P_TEST_DESC AS TestDescription,
zim_test_details.P_SAMPL_TYPE AS SamplingType,
zim_test_details.P_SAMPL_VALUE AS SamplingValue,
zim_test_details.P_TOOLS AS ToolsGauges,
zim_test_details.P_WORK_INTR AS WorkInstructions,
Status.PNAME AS Status,
Status.PDATE_RELEASED AS ReleaseDate
FROM INFODBA.PITEM item
INNER JOIN INFODBA.PITEMREVISION itemrev ON (item.PUID = itemrev.RITEMS_TAGU)
INNER JOIN INFODBA.PRELEASE_STATUS_LIST listing ON (itemrev.PUID = listing.PUID)
INNER JOIN INFODBA.PRELEASESTATUS Status ON (listing.PVALU_0 = Status.PUID)
INNER JOIN INFODBA.PIMANRELATION imanrel ON (itemrev.PUID = imanrel.RPRIMARY_OBJECTU)
INNER JOIN INFODBA.PFORM form ON (imanrel.RSECONDARY_OBJECTU = form.PUID)
INNER JOIN INFODBA.PZIMMERMISDETAILS zim_mis ON (form.RDATA_FILEU = zim_mis.PUID)
INNER JOIN INFODBA.P_TEST_DETAILS test_details ON (zim_mis.PUID = test_details.PUID)
INNER JOIN INFODBA.P_PART_ASSOCIATION ppa ON (ppa.PUID = test_details.PUID)
INNER JOIN INFODBA.PMISDATAOBJECT mis ON (mis.PUID = ppa.PVALU_0)
INNER JOIN INFODBA.PZIMTESTDETAILS zim_test_details ON (test_details.PVALU_0 = zim_test_details.PUID)
WHERE Status.PNAME IN ('Current', 'BackLevel')";
/// <summary>
/// Gets MIS data updated since specified date from CMS database.
/// </summary>
public const string SqlGetMisDataFiltered = SqlGetMisData + @"
AND Status.PDATE_RELEASED >= :lastUpdateDT";
}
@@ -1,113 +0,0 @@
namespace JdeScoping.DataAccess.Queries;
/// <summary>
/// Inventory related SQL queries (Lots, Lot Usages, Lot Locations, Items)
/// </summary>
public static partial class JdeQueries
{
/// <summary>
/// Gets all lots from production schema.
/// </summary>
public const string SqlGetLots = @"
SELECT TRIM(lot.IOLOTN) AS LotNumber,
TRIM(lot.IOMCU) AS BranchCode,
lot.IOITM AS ShortItemNumber,
TRIM(lot.IOLITM) AS ItemNumber,
lot.IOVEND AS SupplierCode,
lot.IOLOTS AS StatusCode,
TRIM(lot.IOLOT1) AS Memo1,
TRIM(lot.IOLOT2) AS Memo2,
TRIM(lot.IOLOT3) AS Memo3,
lot.IOUPMJ AS LastUpdateDate,
lot.IOTDAY AS LastUpdateTime
FROM {ProductionSchema}.F4108 lot
WHERE TRIM(lot.IOLOTN) IS NOT NULL AND
TRIM(lot.IOMCU) IS NOT NULL";
/// <summary>
/// Gets lots updated since specified date from production schema.
/// </summary>
public const string SqlGetLotsFiltered = SqlGetLots + @"
AND (lot.IOUPMJ > :dateUpdated OR
(lot.IOUPMJ = :dateUpdated AND lot.IOTDAY >= :timeUpdated))";
/// <summary>
/// Gets all lot usages (cardex) from production schema.
/// </summary>
public const string SqlGetLotUsages = @"
SELECT lu.ILUKID AS UniqueId,
lu.ILDOCO AS WorkOrderNumber,
TRIM(lu.ILLOTN) AS LotNumber,
TRIM(lu.ILMCU) AS BranchCode,
lu.ILITM AS ShortItemNumber,
lu.ILTRQT AS Quantity,
lu.ILTRDJ AS LastUpdateDate,
lu.ILTDAY AS LastUpdateTime
FROM {ProductionSchema}.F4111 lu
WHERE lu.ILDCT = 'IM' AND
TRIM(lu.ILLOTN) IS NOT NULL";
/// <summary>
/// Gets lot usages updated since specified date from production schema.
/// </summary>
public const string SqlGetLotUsagesFiltered = SqlGetLotUsages + @"
AND (lu.ILTRDJ > :dateUpdated OR
(lu.ILTRDJ = :dateUpdated AND lu.ILTDAY >= :timeUpdated))";
/// <summary>
/// Gets all lot usages from archive schema.
/// </summary>
public const string SqlGetLotUsagesArchive = @"
SELECT lu.ILUKID AS UniqueId,
lu.ILDOCO AS WorkOrderNumber,
TRIM(lu.ILLOTN) AS LotNumber,
TRIM(lu.ILMCU) AS BranchCode,
lu.ILITM AS ShortItemNumber,
lu.ILTRQT AS Quantity,
lu.ILTRDJ AS LastUpdateDate,
lu.ILTDAY AS LastUpdateTime
FROM {ArchiveSchema}.F4111 lu
WHERE lu.ILDCT = 'IM' AND
TRIM(lu.ILLOTN) IS NOT NULL";
/// <summary>
/// Gets all lot locations from production schema.
/// </summary>
public const string SqlGetLotLocations = @"
SELECT TRIM(il.LILOTN) AS LotNumber,
il.LIITM AS ShortItemNumber,
TRIM(il.LIMCU) AS BranchCode,
COALESCE(TRIM(il.LILOCN), ' ') AS Location,
il.LIUPMJ AS LastUpdateDate,
il.LITDAY AS LastUpdateTime
FROM {ProductionSchema}.F41021 il
WHERE TRIM(il.LILOTN) IS NOT NULL";
/// <summary>
/// Gets lot locations updated since specified date from production schema.
/// </summary>
public const string SqlGetLotLocationsFiltered = SqlGetLotLocations + @"
AND (il.LIUPMJ > :dateUpdated OR
(il.LIUPMJ = :dateUpdated AND il.LITDAY >= :timeUpdated))";
/// <summary>
/// Gets all items from production schema.
/// </summary>
public const string SqlGetItems = @"
SELECT pn.IMITM AS ShortItemNumber,
TRIM(pn.IMLITM) AS ItemNumber,
TRIM(pn.IMDSC1) AS Description,
TRIM(pn.IMPRP4) AS PlanningFamily,
TRIM(pn.IMSTKT) AS StockingType,
pn.IMUPMJ AS LastUpdateDate,
pn.IMTDAY AS LastUpdateTime
FROM {ProductionSchema}.F4101 pn
WHERE TRIM(pn.IMLITM) IS NOT NULL";
/// <summary>
/// Gets items updated since specified date from production schema.
/// </summary>
public const string SqlGetItemsFiltered = SqlGetItems + @"
AND (pn.IMUPMJ > :dateUpdated OR
(pn.IMUPMJ = :dateUpdated AND pn.IMTDAY >= :timeUpdated))";
}
@@ -1,50 +0,0 @@
namespace JdeScoping.DataAccess.Queries;
/// <summary>
/// Lookup related SQL queries (Status Codes, Function Codes)
/// </summary>
public static partial class JdeQueries
{
/// <summary>
/// Gets all work order status codes from production schema.
/// Status codes are stored in UDC table F0005 with SY='00' and RT='SS'.
/// </summary>
public const string SqlGetStatusCodes = @"
SELECT TRIM(sc.DRKY) AS Code,
TRIM(sc.DRDL01) AS Description,
sc.DRUPMJ AS LastUpdateDate,
sc.DRUPMT AS LastUpdateTime
FROM {ProductionSchema}.F0005 sc
WHERE TRIM(sc.DRSY) = '00' AND
sc.DRRT = 'SS' AND
TRIM(sc.DRKY) IS NOT NULL";
/// <summary>
/// Gets status codes updated since specified date from production schema.
/// </summary>
public const string SqlGetStatusCodesFiltered = SqlGetStatusCodes + @"
AND (sc.DRUPMJ > :dateUpdated OR
(sc.DRUPMJ = :dateUpdated AND sc.DRUPMT >= :timeUpdated))";
/// <summary>
/// Gets all function codes from production schema.
/// Function codes are stored in F00192 (MES codes table).
/// Uses LISTAGG to concatenate multiple descriptions for same code.
/// </summary>
public const string SqlGetFunctionCodes = @"
SELECT Code,
TRIM(LISTAGG(Description, ' ') WITHIN GROUP(ORDER BY Description) ||
CASE WHEN MAX(total_lengthb) > 4000 THEN '...' ELSE '' END) AS Description,
SYSDATE AS LastUpdateDt
FROM (
SELECT TRIM(fc.CFKY) AS Code,
TRIM(ASCIISTR(fc.CFDS80)) AS Description,
SUM(LENGTHB(TRIM(fc.CFDS80))+1) OVER(PARTITION BY TRIM(fc.CFKY) ORDER BY TRIM(fc.CFDS80)) - 1 cumul_lengthb,
SUM(LENGTHB(TRIM(fc.CFDS80))+1) OVER(PARTITION BY TRIM(fc.CFKY)) - 1 total_lengthb,
COUNT(*) OVER(PARTITION BY TRIM(fc.CFKY)) num_values
FROM {ProductionSchema}.F00192 fc
WHERE TRIM(fc.CFKY) IS NOT NULL
)
WHERE total_lengthb <= 4000 OR cumul_lengthb <= 4000 - length('...')
GROUP BY Code";
}
@@ -1,95 +0,0 @@
namespace JdeScoping.DataAccess.Queries;
/// <summary>
/// Organization related SQL queries (Users, Business Units, Org Hierarchy, Route Masters)
/// </summary>
public static partial class JdeQueries
{
/// <summary>
/// Gets all JDE users from production schema.
/// Uses CTE to get unique users with most recent address book record.
/// </summary>
public const string SqlGetUsers = @"
WITH USER_CTE AS (
SELECT ab.ABAN8 AS AddressNumber,
TRIM(pro.ULUSER) AS UserId,
TRIM(ab.ABALPH) AS FullName,
ab.ABUPMJ AS LastUpdateDate,
ab.ABUPMT AS LastUpdateTime,
ROW_NUMBER() OVER (PARTITION BY ab.ABAN8 ORDER BY ab.ABUPMJ DESC, ab.ABUPMT DESC) RN
FROM {ProductionSchema}.F0101 ab
LEFT OUTER JOIN {ProductionSchema}.F0092 pro ON (ab.ABAN8 = pro.ULAN8)
WHERE ab.ABATE = 'Y'
)
SELECT AddressNumber,
UserId,
FullName,
LastUpdateDate,
LastUpdateTime
FROM USER_CTE
WHERE RN = 1";
/// <summary>
/// Gets all business units of specified type from production schema.
/// Type codes: 'WC' = Work Center, 'PC' = Profit Center, 'BR' = Branch
/// </summary>
public const string SqlGetBusinessUnits = @"
SELECT TRIM(wc.MCMCU) AS Code,
TRIM(wc.MCDL01) AS Description,
wc.MCUPMJ AS LastUpdateDate,
wc.MCUPMT AS LastUpdateTime
FROM {ProductionSchema}.F0006 wc
WHERE wc.MCSTYL = :typeCode";
/// <summary>
/// Gets business units of specified type updated since specified date from production schema.
/// </summary>
public const string SqlGetBusinessUnitsFiltered = SqlGetBusinessUnits + @"
AND (wc.MCUPMJ > :dateUpdated OR
(wc.MCUPMJ = :dateUpdated AND wc.MCUPMT >= :timeUpdated))";
/// <summary>
/// Gets all organization hierarchy records from production schema.
/// Maps work centers to profit centers and branches.
/// </summary>
public const string SqlGetOrgHierarchy = @"
SELECT TRIM(oh.IWMCUW) AS ProfitCenterCode,
TRIM(oh.IWMCU) AS WorkCenterCode,
TRIM(oh.IWMMCU) AS BranchCode,
oh.IWUPMJ AS LastUpdateDate,
oh.IWTDAY AS LastUpdateTime
FROM {ProductionSchema}.F30006 oh
WHERE TRIM(oh.IWMCU) IS NOT NULL AND
TRIM(oh.IWMMCU) IS NOT NULL";
/// <summary>
/// Gets org hierarchy records updated since specified date from production schema.
/// </summary>
public const string SqlGetOrgHierarchyFiltered = SqlGetOrgHierarchy + @"
AND (oh.IWUPMJ > :dateUpdated OR
(oh.IWUPMJ = :dateUpdated AND oh.IWTDAY >= :timeUpdated))";
/// <summary>
/// Gets all route masters from production schema.
/// </summary>
public const string SqlGetRouteMasters = @"
SELECT TRIM(rm.IRMMCU) AS BranchCode,
TRIM(rm.IRKITL) AS ItemNumber,
TRIM(rm.IRTRT) AS RoutingType,
rm.IROPSQ / 10.0 AS SequenceNumber,
TRIM(rm.IRURRF) AS FunctionCode,
TRIM(rm.IRMCU) AS WorkCenterCode,
rm.IREFFF AS StartDateDate,
rm.IREFFT AS EndDateDate,
rm.IRUPMJ AS LastUpdateDate,
rm.IRTDAY AS LastUpdateTime
FROM {ProductionSchema}.F3003 rm
WHERE TRIM(rm.IRKITL) IS NOT NULL";
/// <summary>
/// Gets route masters updated since specified date from production schema.
/// </summary>
public const string SqlGetRouteMastersFiltered = SqlGetRouteMasters + @"
AND (rm.IRUPMJ > :dateUpdated OR
(rm.IRUPMJ = :dateUpdated AND rm.IRTDAY >= :timeUpdated))";
}
@@ -1,234 +0,0 @@
namespace JdeScoping.DataAccess.Queries;
/// <summary>
/// Work order related SQL queries (Work Orders, Steps, Times, Routings, Components)
/// </summary>
public static partial class JdeQueries
{
/// <summary>
/// Gets all work orders from production schema.
/// </summary>
public const string SqlGetWorkorders = @"
SELECT wo.WADOCO AS WorkOrderNumber,
TRIM(wo.WAMMCU) AS BranchCode,
TRIM(wo.WALOTN) AS LotNumber,
TRIM(wo.WALITM) AS ItemNumber,
wo.WAITM AS ShortItemNumber,
TRIM(wo.WAPARS) AS ParentWorkOrderNumber,
wo.WAUORG / 100.0 AS OrderQuantity,
wo.WASOBK / 100.0 AS HeldQuantity,
wo.WASOQS / 100.0 AS ShippedQuantity,
TRIM(wo.WASRST) AS StatusCode,
CASE wo.WADCG WHEN 0 THEN TO_DATE('1900-01-01', 'YYYY-MM-DD')
ELSE TO_DATE(wo.WADCG+1900000,'YYYYDDD') END AS StatusCodeUpdateDT,
CASE wo.WATRDJ WHEN 0 THEN TO_DATE('1900-01-01', 'YYYY-MM-DD')
ELSE TO_DATE(wo.WATRDJ+1900000,'YYYYDDD') END AS IssueDate,
CASE wo.WASTRT WHEN 0 THEN TO_DATE('1900-01-01', 'YYYY-MM-DD')
ELSE TO_DATE(wo.WASTRT+1900000,'YYYYDDD') END AS StartDate,
TRIM(wo.WATRT) AS RoutingType,
wo.WAUPMJ AS LastUpdateDate,
wo.WATDAY AS LastUpdateTime
FROM {ProductionSchema}.F4801 wo";
/// <summary>
/// Gets work orders updated since specified date from production schema.
/// </summary>
public const string SqlGetWorkordersFiltered = SqlGetWorkorders + @"
WHERE (wo.WAUPMJ > :dateUpdated OR
(wo.WAUPMJ = :dateUpdated AND wo.WATDAY >= :timeUpdated))";
/// <summary>
/// Gets all work orders from archive schema.
/// </summary>
public const string SqlGetWorkordersArchive = @"
SELECT wo.WADOCO AS WorkOrderNumber,
TRIM(wo.WAMMCU) AS BranchCode,
TRIM(wo.WALOTN) AS LotNumber,
TRIM(wo.WALITM) AS ItemNumber,
wo.WAITM AS ShortItemNumber,
TRIM(wo.WAPARS) AS ParentWorkOrderNumber,
wo.WAUORG / 100.0 AS OrderQuantity,
wo.WASOBK / 100.0 AS HeldQuantity,
wo.WASOQS / 100.0 AS ShippedQuantity,
TRIM(wo.WASRST) AS StatusCode,
CASE wo.WADCG WHEN 0 THEN TO_DATE('1900-01-01', 'YYYY-MM-DD')
ELSE TO_DATE(wo.WADCG+1900000,'YYYYDDD') END AS StatusCodeUpdateDT,
CASE wo.WATRDJ WHEN 0 THEN TO_DATE('1900-01-01', 'YYYY-MM-DD')
ELSE TO_DATE(wo.WATRDJ+1900000,'YYYYDDD') END AS IssueDate,
CASE wo.WASTRT WHEN 0 THEN TO_DATE('1900-01-01', 'YYYY-MM-DD')
ELSE TO_DATE(wo.WASTRT+1900000,'YYYYDDD') END AS StartDate,
TRIM(wo.WATRT) AS RoutingType,
wo.WAUPMJ AS LastUpdateDate,
wo.WATDAY AS LastUpdateTime
FROM {ArchiveSchema}.F4801 wo";
/// <summary>
/// Gets work order steps from production schema.
/// </summary>
public const string SqlGetWorkorderSteps = @"
SELECT wos.WLDOCO AS WorkOrderNumber,
wos.WLOPSQ/10 AS StepNumber,
TRIM(wos.WLMCU) AS WorkCenterCode,
TRIM(wos.WLMMCU) AS BranchCode,
TRIM(wos.WLDSC1) AS StepDescription,
TRIM(mes.CFDS80) AS FunctionOperationDescription,
wos.WLOPSC AS StepTypeCode,
CASE wos.WLSTRT WHEN 0 THEN NULL
ELSE TO_DATE(wos.WLSTRT+1900000,'YYYYDDD') END AS StartDT,
CASE wos.WLSTRX WHEN 0 THEN NULL
ELSE TO_DATE(wos.WLSTRX+1900000,'YYYYDDD') END AS EndDT,
TRIM(wos.WLURRF) AS FunctionCode,
wos.WLSOCN / 100.0 AS ScrappedQuantity,
wos.WLUPMJ AS LastUpdateDate,
wos.WLTDAY AS LastUpdateTime
FROM {ProductionSchema}.F3112 wos
LEFT OUTER JOIN {ProductionSchema}.F00192 mes ON (wos.WLURRF = mes.CFKY)
WHERE TRIM(wos.WLMCU) IS NOT NULL AND
TRIM(wos.WLMMCU) IS NOT NULL";
/// <summary>
/// Gets work order steps updated since specified date from production schema.
/// </summary>
public const string SqlGetWorkorderStepsFiltered = SqlGetWorkorderSteps + @"
AND (wos.WLUPMJ > :dateUpdated OR
(wos.WLUPMJ = :dateUpdated AND wos.WLTDAY >= :timeUpdated))";
/// <summary>
/// Gets work order steps from archive schema.
/// </summary>
public const string SqlGetWorkorderStepsArchive = @"
SELECT wos.WLDOCO AS WorkOrderNumber,
wos.WLOPSQ/10 AS StepNumber,
TRIM(wos.WLMCU) AS WorkCenterCode,
TRIM(wos.WLMMCU) AS BranchCode,
TRIM(wos.WLDSC1) AS StepDescription,
TRIM(mes.CFDS80) AS FunctionOperationDescription,
wos.WLOPSC AS StepTypeCode,
CASE wos.WLSTRT WHEN 0 THEN NULL
ELSE TO_DATE(wos.WLSTRT+1900000,'YYYYDDD') END AS StartDT,
CASE wos.WLSTRX WHEN 0 THEN NULL
ELSE TO_DATE(wos.WLSTRX+1900000,'YYYYDDD') END AS EndDT,
TRIM(wos.WLURRF) AS FunctionCode,
wos.WLSOCN / 100.0 AS ScrappedQuantity,
wos.WLUPMJ AS LastUpdateDate,
wos.WLTDAY AS LastUpdateTime
FROM {ArchiveSchema}.F3112 wos
LEFT OUTER JOIN {ProductionSchema}.F00192 mes ON (wos.WLURRF = mes.CFKY)
WHERE TRIM(wos.WLMCU) IS NOT NULL AND
TRIM(wos.WLMMCU) IS NOT NULL";
/// <summary>
/// Gets work order time transactions from production schema.
/// </summary>
public const string SqlGetWorkorderTimes = @"
SELECT wot.WTUKID AS UniqueID,
wot.WTDOCO AS WorkOrderNumber,
wot.WTOPSQ/10 AS StepNumber,
TRIM(wot.WTMCU) AS WorkCenterCode,
TRIM(wot.WTMMCU) AS BranchCode,
wot.WTAN8 AS AddressNumber,
CASE wot.WTDGL WHEN 0 THEN NULL
ELSE TO_DATE(wot.WTDGL+1900000,'YYYYDDD') END AS GlDate,
wot.WTUPMJ AS LastUpdateDate,
wot.WTTDAY AS LastUpdateTime
FROM {ProductionSchema}.F31122 wot
WHERE TRIM(wot.WTMCU) IS NOT NULL AND
TRIM(wot.WTMMCU) IS NOT NULL";
/// <summary>
/// Gets work order times updated since specified date from production schema.
/// </summary>
public const string SqlGetWorkorderTimesFiltered = SqlGetWorkorderTimes + @"
AND (wot.WTUPMJ > :dateUpdated OR
(wot.WTUPMJ = :dateUpdated AND wot.WTTDAY >= :timeUpdated))";
/// <summary>
/// Gets work order time transactions from archive schema.
/// </summary>
public const string SqlGetWorkorderTimesArchive = @"
SELECT wot.WTUKID AS UniqueID,
wot.WTDOCO AS WorkOrderNumber,
wot.WTOPSQ/10 AS StepNumber,
TRIM(wot.WTMCU) AS WorkCenterCode,
TRIM(wot.WTMMCU) AS BranchCode,
wot.WTAN8 AS AddressNumber,
CASE wot.WTDGL WHEN 0 THEN NULL
ELSE TO_DATE(wot.WTDGL+1900000,'YYYYDDD') END AS GlDate,
wot.WTUPMJ AS LastUpdateDate,
wot.WTTDAY AS LastUpdateTime
FROM {ArchiveSchema}.F31122 wot
WHERE TRIM(wot.WTMCU) IS NOT NULL AND
TRIM(wot.WTMMCU) IS NOT NULL";
/// <summary>
/// Gets work order routing transactions from production schema.
/// </summary>
public const string SqlGetWorkorderRoutings = @"
SELECT TRIM(woz.SZEDUS) AS UserID,
TRIM(woz.SZEDBT) AS BatchNumber,
TRIM(woz.SZEDTN) AS TransactionNumber,
woz.SZEDLN AS LineNumber,
woz.SZOPSQ / 10.0 AS StepNumber,
TRIM(woz.SZMCU) AS WorkCenterCode,
woz.SZDOCO AS WorkOrderNumber,
TRIM(woz.SZTRT) AS RoutingType,
TRIM(woz.SZMMCU) AS BranchCode,
TRIM(woz.SZDSC1) AS StepDescription,
TRIM(woz.SZURRF) AS FunctionCode,
woz.SZTRDJ AS TransactionDate_Date,
woz.SZUPMJ AS LastUpdateDate,
woz.SZTDAY AS LastUpdateTime
FROM {ProductionSchema}.F3112Z1 woz
WHERE woz.SZTYTN = 'JDERTG' AND
woz.SZDRIN = '2' AND
woz.SZTNAC = '02' AND
woz.SZPID = 'ER31410' AND
TRIM(woz.SZEDUS) IS NOT NULL AND
TRIM(woz.SZEDBT) IS NOT NULL AND
TRIM(woz.SZEDTN) IS NOT NULL AND
TRIM(woz.SZMCU) IS NOT NULL";
/// <summary>
/// Gets work order routings updated since specified date from production schema.
/// </summary>
public const string SqlGetWorkorderRoutingsFiltered = SqlGetWorkorderRoutings + @"
AND (woz.SZUPMJ > :dateUpdated OR
(woz.SZUPMJ = :dateUpdated AND woz.SZTDAY >= :timeUpdated))";
/// <summary>
/// Gets work order component usage from production schema.
/// </summary>
public const string SqlGetWorkorderComponents = @"
SELECT woc.WMUKID AS UniqueID,
woc.WMDOCO AS WorkOrderNumber,
TRIM(woc.WMLOTN) AS LotNumber,
TRIM(woc.WMCMCU) AS BranchCode,
woc.WMCPIT AS ShortItemNumber,
woc.WMTRQT / 100.0 AS Quantity,
woc.WMUPMJ AS LastUpdateDate,
woc.WMTDAY AS LastUpdateTime
FROM {ProductionSchema}.F3111 woc
WHERE TRIM(woc.WMLOTN) IS NOT NULL";
/// <summary>
/// Gets work order components updated since specified date from production schema.
/// </summary>
public const string SqlGetWorkorderComponentsFiltered = SqlGetWorkorderComponents + @"
AND (woc.WMUPMJ > :dateUpdated OR
(woc.WMUPMJ = :dateUpdated AND woc.WMTDAY >= :timeUpdated))";
/// <summary>
/// Gets work order component usage from archive schema.
/// </summary>
public const string SqlGetWorkorderComponentsArchive = @"
SELECT woc.WMUKID AS UniqueID,
woc.WMDOCO AS WorkOrderNumber,
TRIM(woc.WMLOTN) AS LotNumber,
TRIM(woc.WMCMCU) AS BranchCode,
woc.WMCPIT AS ShortItemNumber,
woc.WMTRQT / 100.0 AS Quantity,
woc.WMUPMJ AS LastUpdateDate,
woc.WMTDAY AS LastUpdateTime
FROM {ArchiveSchema}.F3111 woc
WHERE TRIM(woc.WMLOTN) IS NOT NULL";
}
@@ -1,9 +0,0 @@
namespace JdeScoping.DataAccess.Queries;
/// <summary>
/// SQL query constants for the JDE Oracle database.
/// Schema placeholders ({ProductionSchema}, {ArchiveSchema}, {StageSchema}) are replaced at runtime.
/// </summary>
public static partial class JdeQueries
{
}
@@ -24,88 +24,4 @@ public static partial class LotFinderQueries
cte.NumberRecords
FROM DU_CTE cte
WHERE cte.RN = 1";
/// <summary>
/// Gets column metadata for a table.
/// </summary>
public const string SqlGetTableColumns = @"
SELECT c.name AS Name,
CASE t2.name
WHEN 'varchar' THEN 'VARCHAR(' + CAST(c.max_length AS VARCHAR(10)) + ')'
WHEN 'decimal' THEN 'DECIMAL(' + CAST(c.precision AS VARCHAR(4)) + ',' + CAST(c.scale AS VARCHAR(4)) + ')'
ELSE UPPER(t2.name)
END AS Definition
FROM sys.columns c
INNER JOIN sys.types AS t2 ON (c.system_type_id = t2.system_type_id)
INNER JOIN sys.tables t ON (c.object_id = t.object_id)
WHERE t.name = @name
ORDER BY c.column_id";
/// <summary>
/// Gets primary key columns for a table.
/// </summary>
public const string SqlGetTablePrimaryKey = @"
SELECT COLUMN_NAME AS Name
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE OBJECTPROPERTY(OBJECT_ID(CONSTRAINT_SCHEMA + '.' + QUOTENAME(CONSTRAINT_NAME)), 'IsPrimaryKey') = 1 AND
TABLE_NAME = @name
ORDER BY ORDINAL_POSITION";
/// <summary>
/// Rebuilds all indices on a table. Use string.Format to inject table name.
/// </summary>
public const string SqlRebuildIndices = "ALTER INDEX ALL ON {0} REBUILD WITH (FILLFACTOR = 95);";
/// <summary>
/// Post-processing script to set MIS data obsoletion dates.
/// </summary>
public const string SqlPostprocessMisData = @"
SET ANSI_WARNINGS OFF;
WITH cte AS (
SELECT md.MisNumber, md.RevID, md.Status, MIN(md.ReleaseDate) Released
FROM dbo.MisData AS md
GROUP BY md.MisNumber, md.RevID, md.Status
)
UPDATE dbo.MisData
SET ObsoleteDate = bl.Released
FROM cte bl
WHERE MisData.MisNumber = bl.MisNumber AND
MisData.RevID = bl.RevID AND
MisData.Status = 'Current' AND
bl.Status = 'BackLevel';
WITH cte AS (
SELECT md.MisNumber, md.RevID, md.Status, MIN(md.ReleaseDate) Released
FROM dbo.MisData AS md
GROUP BY md.MisNumber, md.RevID, md.Status
)
UPDATE dbo.MisData
SET ObsoleteDate = (SELECT TOP 1 nl.Released
FROM cte nl
WHERE MisData.MisNumber = nl.MisNumber AND
MisData.RevID < nl.RevID AND
MisData.Status = nl.Status
ORDER BY nl.RevID)
WHERE ObsoleteDate IS NULL;
ALTER INDEX [PK_MisData] ON [dbo].[MisData] REBUILD;";
/// <summary>
/// Inserts a new data update tracking record.
/// </summary>
public const string SqlInsertDataUpdate = @"
INSERT INTO dbo.DataUpdate (SourceSystem, SourceData, TableName, StartDT, UpdateType)
VALUES (@sourceSystem, @sourceData, @tableName, @startDT, @updateType);
SELECT CAST(SCOPE_IDENTITY() AS BIGINT);";
/// <summary>
/// Completes a data update tracking record.
/// </summary>
public const string SqlCompleteDataUpdate = @"
UPDATE dbo.DataUpdate
SET EndDT = @endDT,
WasSuccessful = @wasSuccessful,
NumberRecords = @numberRecords
WHERE ID = @id";
}
@@ -52,17 +52,6 @@ public static partial class LotFinderQueries
wc.Description LIKE '%' + @filter + '%'
ORDER BY wc.Code";
/// <summary>
/// Looks up work centers by codes using STRING_SPLIT.
/// </summary>
public const string SqlLookupWorkCenters = @"
SELECT wc.Code,
wc.Description,
wc.LastUpdateDT
FROM dbo.WorkCenter AS wc
WHERE wc.Code IN (SELECT LTRIM(RTRIM(value)) FROM STRING_SPLIT(@workCenterCodes, ','))
ORDER BY wc.Code";
/// <summary>
/// Searches profit centers by code or description.
/// </summary>
@@ -76,17 +65,6 @@ public static partial class LotFinderQueries
pc.Description LIKE '%' + @filter + '%'
ORDER BY pc.Code";
/// <summary>
/// Looks up profit centers by codes using STRING_SPLIT.
/// </summary>
public const string SqlLookupProfitCenters = @"
SELECT pc.Code,
pc.Description,
pc.LastUpdateDT
FROM dbo.ProfitCenter AS pc
WHERE pc.Code IN (SELECT LTRIM(RTRIM(value)) FROM STRING_SPLIT(@profitCenterCodes, ','))
ORDER BY pc.Code";
/// <summary>
/// Searches users by user ID, full name, or address number.
/// </summary>
@@ -102,19 +80,6 @@ public static partial class LotFinderQueries
CAST(u.AddressNumber AS VARCHAR(10)) LIKE '%' + @filter + '%'
ORDER BY u.UserID, u.FullName";
/// <summary>
/// Looks up users by user IDs or address numbers using STRING_SPLIT.
/// </summary>
public const string SqlLookupUsers = @"
SELECT u.AddressNumber,
u.UserID,
u.FullName,
u.LastUpdateDT
FROM dbo.JdeUser AS u
WHERE u.UserID IN (SELECT LTRIM(RTRIM(value)) FROM STRING_SPLIT(@userIds, ','))
OR CAST(u.AddressNumber AS VARCHAR(20)) IN (SELECT LTRIM(RTRIM(value)) FROM STRING_SPLIT(@userIds, ','))
ORDER BY u.UserID";
/// <summary>
/// Looks up lots by lot number and item number using OPENJSON.
/// </summary>
@@ -55,22 +55,4 @@ public static partial class LotFinderQueries
SELECT s.Results
FROM dbo.Search AS s
WHERE s.ID = @id";
/// <summary>
/// Updates search status.
/// </summary>
public const string SqlUpdateSearchStatus = @"
UPDATE dbo.Search
SET Status = @status,
StartDT = CASE WHEN @status = 2 THEN GETUTCDATE() ELSE StartDT END,
EndDT = CASE WHEN @status >= 3 THEN GETUTCDATE() ELSE EndDT END
WHERE ID = @id";
/// <summary>
/// Updates search results.
/// </summary>
public const string SqlUpdateSearchResults = @"
UPDATE dbo.Search
SET Results = @results
WHERE ID = @id";
}
@@ -1,83 +0,0 @@
using System.Runtime.CompilerServices;
using Dapper;
using JdeScoping.Core.Models;
using JdeScoping.Core.Models.Quality;
using JdeScoping.DataAccess.Options;
using JdeScoping.DataAccess.Interfaces;
using JdeScoping.DataAccess.Queries;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Oracle.ManagedDataAccess.Client;
namespace JdeScoping.DataAccess.Repositories;
/// <summary>
/// Repository implementation for the CMS Oracle database.
/// </summary>
public class CmsRepository : ICmsRepository
{
private readonly IDbConnectionFactory _connectionFactory;
private readonly ILogger<CmsRepository> _logger;
private readonly IOptions<DataAccessOptions> _options;
private const string RepositoryName = "CmsRepository";
/// <summary>
/// Initializes a new instance of the <see cref="CmsRepository"/> class.
/// </summary>
public CmsRepository(
IDbConnectionFactory connectionFactory,
ILogger<CmsRepository> logger,
IOptions<DataAccessOptions> options)
{
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_options = options ?? throw new ArgumentNullException(nameof(options));
}
/// <inheritdoc/>
public async IAsyncEnumerable<MisData> GetMisDataAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = lastUpdateDt.HasValue
? CmsQueries.SqlGetMisDataFiltered
: CmsQueries.SqlGetMisData;
var parameters = lastUpdateDt.HasValue
? new { lastUpdateDT = lastUpdateDt.Value }
: null;
OracleConnection? connection = null;
try
{
connection = await _connectionFactory.CreateCmsConnectionAsync(ct);
// Use Query with buffered: false for streaming
var results = connection.Query<MisData>(
sql,
parameters,
commandTimeout: _options.Value.MisDataTimeoutSeconds,
buffered: false);
foreach (var item in results)
{
ct.ThrowIfCancellationRequested();
// Convert ReleaseDate to local time if present
if (item.ReleaseDate.HasValue)
{
item.ReleaseDate = item.ReleaseDate.Value.ToLocalTime();
}
yield return item;
}
}
finally
{
if (connection != null)
{
await connection.DisposeAsync();
}
}
}
}
@@ -1,94 +0,0 @@
using System.Runtime.CompilerServices;
using JdeScoping.Core.Helpers;
using JdeScoping.Core.Models.Inventory;
using JdeScoping.DataAccess.Queries;
namespace JdeScoping.DataAccess.Repositories;
/// <summary>
/// Inventory (lots) operations for JDE Oracle repository.
/// </summary>
public partial class JdeRepository
{
/// <inheritdoc/>
public async IAsyncEnumerable<Lot> GetLotsAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(
lastUpdateDt.HasValue
? JdeQueries.SqlGetLotsFiltered
: JdeQueries.SqlGetLots);
var parameters = lastUpdateDt.HasValue
? new { dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() }
: null;
await foreach (var item in StreamQueryAsync<Lot>(
sql, parameters, nameof(GetLotsAsync), "SQL_GET_LOTS", ct))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<LotUsage> GetLotUsagesAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(
lastUpdateDt.HasValue
? JdeQueries.SqlGetLotUsagesFiltered
: JdeQueries.SqlGetLotUsages);
var parameters = lastUpdateDt.HasValue
? new { dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() }
: null;
// Use special lot usage timeout due to large dataset
await foreach (var item in StreamQueryAsync<LotUsage>(
sql, parameters, nameof(GetLotUsagesAsync), "SQL_GET_LOT_USAGES", ct,
_options.Value.LotUsageTimeoutSeconds))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<LotUsage> GetLotUsagesArchiveAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(JdeQueries.SqlGetLotUsagesArchive);
// Use special lot usage timeout due to large dataset
await foreach (var item in StreamQueryAsync<LotUsage>(
sql, null, nameof(GetLotUsagesArchiveAsync), "SQL_GET_LOT_USAGES_ARCHIVE", ct,
_options.Value.LotUsageTimeoutSeconds))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<LotLocation> GetLotLocationsAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(
lastUpdateDt.HasValue
? JdeQueries.SqlGetLotLocationsFiltered
: JdeQueries.SqlGetLotLocations);
var parameters = lastUpdateDt.HasValue
? new { lastUpdateDT = lastUpdateDt.Value }
: null;
// Use JDE Stage connection for lot locations
await foreach (var item in StreamQueryFromStageAsync<LotLocation>(
sql, parameters, nameof(GetLotLocationsAsync), "SQL_GET_LOT_LOCATIONS", ct))
{
yield return item;
}
}
}
@@ -1,191 +0,0 @@
using System.Runtime.CompilerServices;
using JdeScoping.Core.Helpers;
using JdeScoping.Core.Models;
using JdeScoping.Core.Models.Inventory;
using JdeScoping.Core.Models.Lookup;
using JdeScoping.Core.Models.Organization;
using JdeScoping.DataAccess.Queries;
namespace JdeScoping.DataAccess.Repositories;
/// <summary>
/// Reference data operations for JDE Oracle repository.
/// </summary>
public partial class JdeRepository
{
/// <inheritdoc/>
public async IAsyncEnumerable<Item> GetItemsAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(
lastUpdateDt.HasValue
? JdeQueries.SqlGetItemsFiltered
: JdeQueries.SqlGetItems);
var parameters = lastUpdateDt.HasValue
? new { dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() }
: null;
await foreach (var item in StreamQueryAsync<Item>(
sql, parameters, nameof(GetItemsAsync), "SQL_GET_ITEMS", ct))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<JdeUser> GetUsersAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
// Users always do full sync (incremental not supported)
var sql = ApplySchemaReplacements(JdeQueries.SqlGetUsers);
await foreach (var item in StreamQueryAsync<JdeUser>(
sql, null, nameof(GetUsersAsync), "SQL_GET_USERS", ct))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<Branch> GetBranchesAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(
lastUpdateDt.HasValue
? JdeQueries.SqlGetBusinessUnitsFiltered
: JdeQueries.SqlGetBusinessUnits);
var parameters = lastUpdateDt.HasValue
? new { typeCode = "BP", dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() }
: new { typeCode = "BP", dateUpdated = 0, timeUpdated = 0 };
await foreach (var item in StreamQueryAsync<Branch>(
sql, parameters, nameof(GetBranchesAsync), "SQL_GET_BUSINESS_UNITS", ct))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<ProfitCenter> GetProfitCentersAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(
lastUpdateDt.HasValue
? JdeQueries.SqlGetBusinessUnitsFiltered
: JdeQueries.SqlGetBusinessUnits);
var parameters = lastUpdateDt.HasValue
? new { typeCode = "I3", dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() }
: new { typeCode = "I3", dateUpdated = 0, timeUpdated = 0 };
await foreach (var item in StreamQueryAsync<ProfitCenter>(
sql, parameters, nameof(GetProfitCentersAsync), "SQL_GET_BUSINESS_UNITS", ct))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<WorkCenter> GetWorkCentersAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(
lastUpdateDt.HasValue
? JdeQueries.SqlGetBusinessUnitsFiltered
: JdeQueries.SqlGetBusinessUnits);
var parameters = lastUpdateDt.HasValue
? new { typeCode = "WC", dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() }
: new { typeCode = "WC", dateUpdated = 0, timeUpdated = 0 };
await foreach (var item in StreamQueryAsync<WorkCenter>(
sql, parameters, nameof(GetWorkCentersAsync), "SQL_GET_BUSINESS_UNITS", ct))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<StatusCode> GetStatusCodesAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(
lastUpdateDt.HasValue
? JdeQueries.SqlGetStatusCodesFiltered
: JdeQueries.SqlGetStatusCodes);
var parameters = lastUpdateDt.HasValue
? new { lastUpdateDT = lastUpdateDt.Value }
: null;
// Use JDE Stage connection for status codes
await foreach (var item in StreamQueryFromStageAsync<StatusCode>(
sql, parameters, nameof(GetStatusCodesAsync), "SQL_GET_STATUS_CODES", ct))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<FunctionCode> GetFunctionCodesAsync(
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(JdeQueries.SqlGetFunctionCodes);
await foreach (var item in StreamQueryAsync<FunctionCode>(
sql, null, nameof(GetFunctionCodesAsync), "SQL_GET_FUNCTION_CODES", ct))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<OrgHierarchy> GetOrgHierarchyAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(
lastUpdateDt.HasValue
? JdeQueries.SqlGetOrgHierarchyFiltered
: JdeQueries.SqlGetOrgHierarchy);
var parameters = lastUpdateDt.HasValue
? new { dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() }
: null;
await foreach (var item in StreamQueryAsync<OrgHierarchy>(
sql, parameters, nameof(GetOrgHierarchyAsync), "SQL_GET_ORG_HIERARCHY", ct))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<RouteMaster> GetRouteMastersAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(
lastUpdateDt.HasValue
? JdeQueries.SqlGetRouteMastersFiltered
: JdeQueries.SqlGetRouteMasters);
var parameters = lastUpdateDt.HasValue
? new { dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() }
: null;
await foreach (var item in StreamQueryAsync<RouteMaster>(
sql, parameters, nameof(GetRouteMastersAsync), "SQL_GET_ROUTE_MASTERS", ct))
{
yield return item;
}
}
}
@@ -1,173 +0,0 @@
using System.Runtime.CompilerServices;
using JdeScoping.Core.Helpers;
using JdeScoping.Core.Models.WorkOrders;
using JdeScoping.DataAccess.Queries;
namespace JdeScoping.DataAccess.Repositories;
/// <summary>
/// Work order operations for JDE Oracle repository.
/// </summary>
public partial class JdeRepository
{
/// <inheritdoc/>
public async IAsyncEnumerable<WorkOrder> GetWorkOrdersAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(
lastUpdateDt.HasValue
? JdeQueries.SqlGetWorkordersFiltered
: JdeQueries.SqlGetWorkorders);
var parameters = lastUpdateDt.HasValue
? new { dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() }
: null;
await foreach (var item in StreamQueryAsync<WorkOrder>(
sql, parameters, nameof(GetWorkOrdersAsync), "SQL_GET_WORKORDERS", ct))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<WorkOrder> GetWorkOrdersArchiveAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(JdeQueries.SqlGetWorkordersArchive);
await foreach (var item in StreamQueryAsync<WorkOrder>(
sql, null, nameof(GetWorkOrdersArchiveAsync), "SQL_GET_WORKORDERS_ARCHIVE", ct))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<WorkOrderStep> GetWorkOrderStepsAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(
lastUpdateDt.HasValue
? JdeQueries.SqlGetWorkorderStepsFiltered
: JdeQueries.SqlGetWorkorderSteps);
var parameters = lastUpdateDt.HasValue
? new { dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() }
: null;
await foreach (var item in StreamQueryAsync<WorkOrderStep>(
sql, parameters, nameof(GetWorkOrderStepsAsync), "SQL_GET_WORKORDER_STEPS", ct))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<WorkOrderStep> GetWorkOrderStepsArchiveAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(JdeQueries.SqlGetWorkorderStepsArchive);
await foreach (var item in StreamQueryAsync<WorkOrderStep>(
sql, null, nameof(GetWorkOrderStepsArchiveAsync), "SQL_GET_WORKORDER_STEPS_ARCHIVE", ct))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<WorkOrderTime> GetWorkOrderTimesAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(
lastUpdateDt.HasValue
? JdeQueries.SqlGetWorkorderTimesFiltered
: JdeQueries.SqlGetWorkorderTimes);
var parameters = lastUpdateDt.HasValue
? new { dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() }
: null;
await foreach (var item in StreamQueryAsync<WorkOrderTime>(
sql, parameters, nameof(GetWorkOrderTimesAsync), "SQL_GET_WORKORDER_TIMES", ct))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<WorkOrderTime> GetWorkOrderTimesArchiveAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(JdeQueries.SqlGetWorkorderTimesArchive);
await foreach (var item in StreamQueryAsync<WorkOrderTime>(
sql, null, nameof(GetWorkOrderTimesArchiveAsync), "SQL_GET_WORKORDER_TIMES_ARCHIVE", ct))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<WorkOrderRouting> GetWorkOrderRoutingsAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(
lastUpdateDt.HasValue
? JdeQueries.SqlGetWorkorderRoutingsFiltered
: JdeQueries.SqlGetWorkorderRoutings);
var parameters = lastUpdateDt.HasValue
? new { dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() }
: null;
await foreach (var item in StreamQueryAsync<WorkOrderRouting>(
sql, parameters, nameof(GetWorkOrderRoutingsAsync), "SQL_GET_WORKORDER_ROUTINGS", ct))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<WorkOrderComponent> GetWorkOrderComponentsAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(
lastUpdateDt.HasValue
? JdeQueries.SqlGetWorkorderComponentsFiltered
: JdeQueries.SqlGetWorkorderComponents);
var parameters = lastUpdateDt.HasValue
? new { dateUpdated = lastUpdateDt.Value.ToJdeDate(), timeUpdated = lastUpdateDt.Value.ToJdeTime() }
: null;
await foreach (var item in StreamQueryAsync<WorkOrderComponent>(
sql, parameters, nameof(GetWorkOrderComponentsAsync), "SQL_GET_WORKORDER_COMPONENTS", ct))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<WorkOrderComponent> GetWorkOrderComponentsArchiveAsync(
DateTime? lastUpdateDt = null,
[EnumeratorCancellation] CancellationToken ct = default)
{
var sql = ApplySchemaReplacements(JdeQueries.SqlGetWorkorderComponentsArchive);
await foreach (var item in StreamQueryAsync<WorkOrderComponent>(
sql, null, nameof(GetWorkOrderComponentsArchiveAsync), "SQL_GET_WORKORDER_COMPONENTS_ARCHIVE", ct))
{
yield return item;
}
}
}
@@ -1,113 +0,0 @@
using System.Runtime.CompilerServices;
using Dapper;
using JdeScoping.DataAccess.Options;
using JdeScoping.DataAccess.Interfaces;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Oracle.ManagedDataAccess.Client;
namespace JdeScoping.DataAccess.Repositories;
/// <summary>
/// Repository implementation for the JDE Oracle database.
/// </summary>
public partial class JdeRepository : IJdeRepository
{
private readonly IDbConnectionFactory _connectionFactory;
private readonly ILogger<JdeRepository> _logger;
private readonly IOptions<DataAccessOptions> _options;
private const string RepositoryName = "JdeRepository";
/// <summary>
/// Initializes a new instance of the <see cref="JdeRepository"/> class.
/// </summary>
public JdeRepository(
IDbConnectionFactory connectionFactory,
ILogger<JdeRepository> logger,
IOptions<DataAccessOptions> options)
{
_connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_options = options ?? throw new ArgumentNullException(nameof(options));
}
private string ApplySchemaReplacements(string sql)
{
return sql
.Replace("{ProductionSchema}", _options.Value.ProductionSchema)
.Replace("{ArchiveSchema}", _options.Value.ArchiveSchema)
.Replace("{StageSchema}", _options.Value.StageSchema);
}
private async IAsyncEnumerable<T> StreamQueryAsync<T>(
string sql,
object? parameters,
string operation,
string queryName,
[EnumeratorCancellation] CancellationToken ct,
int? timeoutSeconds = null)
{
OracleConnection? connection = null;
try
{
connection = await _connectionFactory.CreateJdeConnectionAsync(ct);
var timeout = timeoutSeconds ?? _options.Value.DefaultTimeoutSeconds;
// Use Query with buffered: false for streaming
var results = connection.Query<T>(
sql,
parameters,
commandTimeout: timeout,
buffered: false);
foreach (var item in results)
{
ct.ThrowIfCancellationRequested();
yield return item;
}
}
finally
{
if (connection != null)
{
await connection.DisposeAsync();
}
}
}
private async IAsyncEnumerable<T> StreamQueryFromStageAsync<T>(
string sql,
object? parameters,
string operation,
string queryName,
[EnumeratorCancellation] CancellationToken ct,
int? timeoutSeconds = null)
{
OracleConnection? connection = null;
try
{
connection = await _connectionFactory.CreateJdeStageConnectionAsync(ct);
var timeout = timeoutSeconds ?? _options.Value.DefaultTimeoutSeconds;
// Use Query with buffered: false for streaming
var results = connection.Query<T>(
sql,
parameters,
commandTimeout: timeout,
buffered: false);
foreach (var item in results)
{
ct.ThrowIfCancellationRequested();
yield return item;
}
}
finally
{
if (connection != null)
{
await connection.DisposeAsync();
}
}
}
}
@@ -1,8 +1,6 @@
using System.Data;
using Dapper;
using JdeScoping.Core.Models.Infrastructure;
using JdeScoping.DataAccess.Queries;
using Microsoft.Data.SqlClient;
namespace JdeScoping.DataAccess.Repositories;
@@ -33,166 +31,4 @@ public partial class LotFinderRepository
throw;
}
}
/// <inheritdoc/>
public async Task<TableSpec> GetTableSpecAsync(string tableName, CancellationToken ct = default)
{
const string operation = nameof(GetTableSpecAsync);
try
{
var tableSpec = new TableSpec(tableName);
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
// Load columns
var columns = await connection.QueryAsync<ColumnSpec>(
LotFinderQueries.SqlGetTableColumns,
new { name = tableName },
commandTimeout: _options.Value.DefaultTimeoutSeconds);
tableSpec.Columns.AddRange(columns);
// Load primary key
var pkColumns = await connection.QueryAsync<string>(
LotFinderQueries.SqlGetTablePrimaryKey,
new { name = tableName },
commandTimeout: _options.Value.DefaultTimeoutSeconds);
foreach (var columnName in pkColumns)
{
var column = tableSpec.GetColumn(columnName);
if (column != null)
{
tableSpec.PrimaryKey.Add(column);
}
}
return tableSpec;
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
LogAndThrow(ex, operation, "SQL_GET_TABLE_COLUMNS");
throw;
}
}
/// <inheritdoc/>
public async Task RebuildIndicesAsync(string tableName, CancellationToken ct = default)
{
const string operation = nameof(RebuildIndicesAsync);
// Validate table name against whitelist (SQL injection prevention)
if (!ValidTableNames.Contains(tableName))
{
throw new ArgumentException($"Invalid table name: {tableName}", nameof(tableName));
}
try
{
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
var sql = $"ALTER INDEX ALL ON [{tableName}] REBUILD WITH (FILLFACTOR = 95)";
await connection.ExecuteAsync(sql, commandTimeout: _options.Value.RebuildIndexTimeoutSeconds);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
LogAndThrow(ex, operation, "SQL_REBUILD_INDICES");
throw;
}
}
/// <inheritdoc/>
public async Task PostProcessMisDataAsync(CancellationToken ct = default)
{
const string operation = nameof(PostProcessMisDataAsync);
try
{
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
await connection.ExecuteAsync(
LotFinderQueries.SqlPostprocessMisData,
commandTimeout: _options.Value.DefaultTimeoutSeconds);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
LogAndThrow(ex, operation, "SQL_POSTPROCESS_MISDATA");
throw;
}
}
/// <inheritdoc/>
public async Task<int> BulkInsertAsync<T>(string tableName, IEnumerable<T> records, CancellationToken ct = default)
{
const string operation = nameof(BulkInsertAsync);
// Validate table name against whitelist
if (!ValidTableNames.Contains(tableName))
{
throw new ArgumentException($"Invalid table name: {tableName}", nameof(tableName));
}
try
{
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
// Use SqlBulkCopy for efficient bulk insert
using var bulkCopy = new SqlBulkCopy(connection)
{
DestinationTableName = $"dbo.[{tableName}]",
BulkCopyTimeout = _options.Value.DefaultTimeoutSeconds
};
// Convert records to DataTable
var recordList = records.ToList();
var dataTable = ToDataTable(recordList);
await bulkCopy.WriteToServerAsync(dataTable, ct);
return recordList.Count;
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
LogAndThrow(ex, operation, "BulkInsert");
throw;
}
}
/// <inheritdoc/>
public async Task TruncateTableAsync(string tableName, CancellationToken ct = default)
{
const string operation = nameof(TruncateTableAsync);
// Validate table name against whitelist
if (!ValidTableNames.Contains(tableName))
{
throw new ArgumentException($"Invalid table name: {tableName}", nameof(tableName));
}
try
{
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
var sql = $"TRUNCATE TABLE dbo.[{tableName}]";
await connection.ExecuteAsync(sql, commandTimeout: _options.Value.DefaultTimeoutSeconds);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
LogAndThrow(ex, operation, "TruncateTable");
throw;
}
}
}
@@ -113,32 +113,6 @@ public partial class LotFinderRepository
}
}
/// <inheritdoc/>
public async Task<List<WorkCenter>> LookupWorkCentersAsync(List<string> codes, CancellationToken ct = default)
{
const string operation = nameof(LookupWorkCentersAsync);
try
{
var workCenterCodesCsv = string.Join(",", codes);
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
var result = await connection.QueryAsync<WorkCenter>(
LotFinderQueries.SqlLookupWorkCenters,
new { workCenterCodes = workCenterCodesCsv },
commandTimeout: _options.Value.DefaultTimeoutSeconds);
return result.ToList();
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
LogAndThrow(ex, operation, "SQL_LOOKUP_WORK_CENTERS");
throw;
}
}
/// <inheritdoc/>
public async Task<List<ProfitCenter>> SearchProfitCentersAsync(string filter, CancellationToken ct = default)
{
@@ -163,32 +137,6 @@ public partial class LotFinderRepository
}
}
/// <inheritdoc/>
public async Task<List<ProfitCenter>> LookupProfitCentersAsync(List<string> codes, CancellationToken ct = default)
{
const string operation = nameof(LookupProfitCentersAsync);
try
{
var profitCenterCodesCsv = string.Join(",", codes);
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
var result = await connection.QueryAsync<ProfitCenter>(
LotFinderQueries.SqlLookupProfitCenters,
new { profitCenterCodes = profitCenterCodesCsv },
commandTimeout: _options.Value.DefaultTimeoutSeconds);
return result.ToList();
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
LogAndThrow(ex, operation, "SQL_LOOKUP_PROFIT_CENTERS");
throw;
}
}
/// <inheritdoc/>
public async Task<List<JdeUser>> SearchUsersAsync(string filter, CancellationToken ct = default)
{
@@ -213,32 +161,6 @@ public partial class LotFinderRepository
}
}
/// <inheritdoc/>
public async Task<List<JdeUser>> LookupUsersAsync(List<string> userIds, CancellationToken ct = default)
{
const string operation = nameof(LookupUsersAsync);
try
{
var userIdsCsv = string.Join(",", userIds);
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
var result = await connection.QueryAsync<JdeUser>(
LotFinderQueries.SqlLookupUsers,
new { userIds = userIdsCsv },
commandTimeout: _options.Value.DefaultTimeoutSeconds);
return result.ToList();
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
LogAndThrow(ex, operation, "SQL_LOOKUP_USERS");
throw;
}
}
/// <inheritdoc/>
public async Task<List<Lot>> LookupLotsAsync(List<LotViewModel> lots, CancellationToken ct = default)
{
@@ -152,49 +152,4 @@ public partial class LotFinderRepository
}
}
/// <inheritdoc/>
public async Task UpdateSearchStatusAsync(int id, SearchStatus status, CancellationToken ct = default)
{
const string operation = nameof(UpdateSearchStatusAsync);
try
{
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
await connection.ExecuteAsync(
LotFinderQueries.SqlUpdateSearchStatus,
new { id, status = (int)status },
commandTimeout: _options.Value.DefaultTimeoutSeconds);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
LogAndThrow(ex, operation, "SQL_UPDATE_SEARCH_STATUS");
throw;
}
}
/// <inheritdoc/>
public async Task UpdateSearchResultsAsync(int id, byte[] results, CancellationToken ct = default)
{
const string operation = nameof(UpdateSearchResultsAsync);
try
{
await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(ct);
await connection.ExecuteAsync(
LotFinderQueries.SqlUpdateSearchResults,
new { id, results },
commandTimeout: _options.Value.DefaultTimeoutSeconds);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
LogAndThrow(ex, operation, "SQL_UPDATE_SEARCH_RESULTS");
throw;
}
}
}
@@ -1,4 +1,3 @@
using System.Data;
using JdeScoping.Core.Interfaces;
using JdeScoping.DataAccess.Options;
using JdeScoping.DataAccess.Exceptions;
@@ -19,22 +18,6 @@ public partial class LotFinderRepository : ILotFinderRepository
private readonly IOptions<DataAccessOptions> _options;
private const string RepositoryName = "LotFinderRepository";
/// <summary>
/// Valid table names for index rebuild operations (SQL injection whitelist).
/// </summary>
private static readonly HashSet<string> ValidTableNames = new(StringComparer.OrdinalIgnoreCase)
{
"Branch", "DataUpdate", "FunctionCode", "Item", "JdeUser",
"Lot", "LotLocation", "LotUsage_Curr", "LotUsage_Hist",
"MisData", "OrgHierarchy", "ProfitCenter", "RouteMaster",
"Search", "StatusCode", "WorkCenter",
"WorkOrder_Curr", "WorkOrder_Hist",
"WorkOrderComponent_Curr", "WorkOrderComponent_Hist",
"WorkOrderRouting",
"WorkOrderStep_Curr", "WorkOrderStep_Hist",
"WorkOrderTime_Curr", "WorkOrderTime_Hist"
};
/// <summary>
/// Initializes a new instance of the <see cref="LotFinderRepository"/> class.
/// </summary>
@@ -83,41 +66,4 @@ public partial class LotFinderRepository : ILotFinderRepository
// SQL Server timeout error number: -2
return ex.Number == -2;
}
private static DataTable ToDataTable<T>(List<T> items)
{
var dataTable = new DataTable();
var properties = typeof(T).GetProperties()
.Where(p => p.CanRead && IsSupportedType(p.PropertyType))
.ToArray();
foreach (var prop in properties)
{
var type = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
dataTable.Columns.Add(prop.Name, type);
}
foreach (var item in items)
{
var row = dataTable.NewRow();
foreach (var prop in properties)
{
var value = prop.GetValue(item);
row[prop.Name] = value ?? DBNull.Value;
}
dataTable.Rows.Add(row);
}
return dataTable;
}
private static bool IsSupportedType(Type type)
{
var underlyingType = Nullable.GetUnderlyingType(type) ?? type;
return underlyingType.IsPrimitive
|| underlyingType == typeof(string)
|| underlyingType == typeof(DateTime)
|| underlyingType == typeof(decimal)
|| underlyingType == typeof(Guid);
}
}
-8
View File
@@ -1,9 +1,7 @@
using JdeScoping.Api;
using JdeScoping.Core.Interfaces;
using JdeScoping.DataAccess.Options;
using JdeScoping.DataSync.Options;
using JdeScoping.ExcelIO.Options;
using JdeScoping.Infrastructure.Options;
using JdeScoping.Database;
using Microsoft.Extensions.Options;
@@ -72,11 +70,5 @@ static void ValidateServices(IServiceProvider services)
_ = provider.GetRequiredService<IOptions<DataSyncOptions>>();
_ = provider.GetRequiredService<IOptions<ExcelExportOptions>>();
_ = provider.GetRequiredService<IOptions<SearchProcessingOptions>>();
_ = provider.GetRequiredService<IOptions<DataSourceOptions>>();
// Validate data source services
_ = provider.GetRequiredService<IJdeDataSource>();
_ = provider.GetRequiredService<ICmsDataSource>();
Console.WriteLine("Service validation completed successfully.");
}
@@ -3,8 +3,6 @@ using JdeScoping.Core.Options;
using JdeScoping.Infrastructure.Auth;
using JdeScoping.Infrastructure.Options;
using JdeScoping.Infrastructure.Security;
using JdeScoping.Infrastructure.Sources.Cms;
using JdeScoping.Infrastructure.Sources.Jde;
using Microsoft.Extensions.Configuration;
namespace Microsoft.Extensions.DependencyInjection;
@@ -25,27 +23,9 @@ public static class InfrastructureDependencyInjection
IConfiguration configuration)
{
// Bind configuration
services.Configure<DataSourceOptions>(
configuration.GetSection(DataSourceOptions.SectionName));
services.Configure<LdapOptions>(
configuration.GetSection(LdapOptions.SectionName));
// Register data sources based on configuration
var dataSourceOptions = configuration
.GetSection(DataSourceOptions.SectionName)
.Get<DataSourceOptions>();
if (dataSourceOptions?.UseFileDataSource == true)
{
services.AddScoped<IJdeDataSource, JdeFileDataSource>();
services.AddScoped<ICmsDataSource, CmsFileDataSource>();
}
else
{
services.AddScoped<IJdeDataSource, JdeOracleDataSource>();
services.AddScoped<ICmsDataSource, CmsOracleDataSource>();
}
// Register auth service based on configuration
var ldapOptions = configuration
.GetSection(LdapOptions.SectionName)
@@ -7,13 +7,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Options" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.1" />
<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="23.26.0" />
<PackageReference Include="System.DirectoryServices.Protocols" Version="10.0.1" />
</ItemGroup>
@@ -1,22 +0,0 @@
namespace JdeScoping.Infrastructure.Options;
/// <summary>
/// Configuration options for data source selection (Oracle vs file-based).
/// </summary>
public class DataSourceOptions
{
/// <summary>
/// Configuration section name in appsettings.json.
/// </summary>
public const string SectionName = "DataSource";
/// <summary>
/// Use file-based data sources instead of Oracle for development.
/// </summary>
public bool UseFileDataSource { get; set; } = false;
/// <summary>
/// Directory containing JSON data files for file-based data source.
/// </summary>
public string FileDirectory { get; set; } = "DevData";
}
@@ -1,43 +0,0 @@
using System.Runtime.CompilerServices;
using System.Text.Json;
using JdeScoping.Core.Interfaces;
using JdeScoping.Core.Models;
using JdeScoping.Core.Models.Quality;
using JdeScoping.Infrastructure.Options;
using Microsoft.Extensions.Options;
namespace JdeScoping.Infrastructure.Sources.Cms;
/// <summary>
/// File-based CMS data source for development/testing.
/// </summary>
public class CmsFileDataSource : ICmsDataSource
{
private readonly string _dataDirectory;
/// <summary>
/// Initializes a new instance of the <see cref="CmsFileDataSource"/> class.
/// </summary>
public CmsFileDataSource(IOptions<DataSourceOptions> options)
{
_dataDirectory = options.Value.FileDirectory;
}
/// <inheritdoc/>
public async IAsyncEnumerable<MisData> GetMisDataAsync(
DateTime? minimumDt = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var filePath = Path.Combine(_dataDirectory, "misdata.json");
if (!File.Exists(filePath))
yield break;
var json = await File.ReadAllTextAsync(filePath, cancellationToken);
var items = JsonSerializer.Deserialize<List<MisData>>(json) ?? [];
foreach (var item in items.Where(m => !minimumDt.HasValue || (m.ReleaseDate.HasValue && m.ReleaseDate.Value >= minimumDt)))
{
yield return item;
}
}
}
@@ -1,46 +0,0 @@
using System.Runtime.CompilerServices;
using Dapper;
using JdeScoping.Core.Interfaces;
using JdeScoping.Core.Models;
using JdeScoping.Core.Models.Quality;
using Microsoft.Extensions.Configuration;
using Oracle.ManagedDataAccess.Client;
namespace JdeScoping.Infrastructure.Sources.Cms;
/// <summary>
/// Oracle-based CMS data source for production use.
/// </summary>
public class CmsOracleDataSource : ICmsDataSource
{
private readonly string _connectionString;
/// <summary>
/// Initializes a new instance of the <see cref="CmsOracleDataSource"/> class.
/// </summary>
public CmsOracleDataSource(IConfiguration configuration)
{
_connectionString = configuration.GetConnectionString("CMS")
?? throw new InvalidOperationException("CMS connection string not configured");
}
/// <inheritdoc/>
public async IAsyncEnumerable<MisData> GetMisDataAsync(
DateTime? minimumDt = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await using var connection = new OracleConnection(_connectionString);
await connection.OpenAsync(cancellationToken);
// TODO: Implement actual CMS query for MIS data
var sql = minimumDt.HasValue
? "SELECT * FROM MISDATA WHERE RELEASE_DATE >= :MinDate"
: "SELECT * FROM MISDATA";
var results = await connection.QueryAsync<MisData>(sql, new { MinDate = minimumDt });
foreach (var item in results)
{
yield return item;
}
}
}
@@ -1,133 +0,0 @@
using System.Runtime.CompilerServices;
using System.Text.Json;
using JdeScoping.Core.Interfaces;
using JdeScoping.Core.Models;
using JdeScoping.Core.Models.Inventory;
using JdeScoping.Core.Models.Organization;
using JdeScoping.Core.Models.WorkOrders;
using JdeScoping.Infrastructure.Options;
using Microsoft.Extensions.Options;
namespace JdeScoping.Infrastructure.Sources.Jde;
/// <summary>
/// File-based JDE data source for development/testing.
/// </summary>
public class JdeFileDataSource : IJdeDataSource
{
private readonly string _dataDirectory;
/// <summary>
/// Initializes a new instance of the <see cref="JdeFileDataSource"/> class.
/// </summary>
public JdeFileDataSource(IOptions<DataSourceOptions> options)
{
_dataDirectory = options.Value.FileDirectory;
}
/// <inheritdoc/>
public async IAsyncEnumerable<WorkOrder> GetWorkOrdersAsync(
DateTime? minimumDt = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var items = await LoadFromFileAsync<WorkOrder>("workorders.json", cancellationToken);
foreach (var item in items.Where(wo => !minimumDt.HasValue || (wo.LastUpdateDt.HasValue && wo.LastUpdateDt.Value >= minimumDt)))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<LotUsage> GetLotUsagesAsync(
DateTime? minimumDt = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var items = await LoadFromFileAsync<LotUsage>("lotusages.json", cancellationToken);
foreach (var item in items.Where(lu => !minimumDt.HasValue || (lu.LastUpdateDt.HasValue && lu.LastUpdateDt.Value >= minimumDt)))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<Lot> GetLotsAsync(
DateTime? minimumDt = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var items = await LoadFromFileAsync<Lot>("lots.json", cancellationToken);
foreach (var item in items.Where(l => !minimumDt.HasValue || (l.LastUpdateDt.HasValue && l.LastUpdateDt.Value >= minimumDt)))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<Item> GetItemsAsync(
DateTime? minimumDt = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var items = await LoadFromFileAsync<Item>("items.json", cancellationToken);
foreach (var item in items.Where(i => !minimumDt.HasValue || (i.LastUpdateDt.HasValue && i.LastUpdateDt.Value >= minimumDt)))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<WorkCenter> GetWorkCentersAsync(
DateTime? minimumDt = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var items = await LoadFromFileAsync<WorkCenter>("workcenters.json", cancellationToken);
foreach (var item in items.Where(wc => !minimumDt.HasValue || (wc.LastUpdateDt.HasValue && wc.LastUpdateDt.Value >= minimumDt)))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<ProfitCenter> GetProfitCentersAsync(
DateTime? minimumDt = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var items = await LoadFromFileAsync<ProfitCenter>("profitcenters.json", cancellationToken);
foreach (var item in items.Where(pc => !minimumDt.HasValue || (pc.LastUpdateDt.HasValue && pc.LastUpdateDt.Value >= minimumDt)))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<JdeUser> GetUsersAsync(
DateTime? minimumDt = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var items = await LoadFromFileAsync<JdeUser>("users.json", cancellationToken);
foreach (var item in items.Where(u => !minimumDt.HasValue || (u.LastUpdateDt.HasValue && u.LastUpdateDt.Value >= minimumDt)))
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<Branch> GetBranchesAsync(
DateTime? minimumDt = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var items = await LoadFromFileAsync<Branch>("branches.json", cancellationToken);
foreach (var item in items)
{
yield return item;
}
}
private async Task<List<T>> LoadFromFileAsync<T>(string fileName, CancellationToken cancellationToken)
{
var filePath = Path.Combine(_dataDirectory, fileName);
if (!File.Exists(filePath))
return [];
var json = await File.ReadAllTextAsync(filePath, cancellationToken);
return JsonSerializer.Deserialize<List<T>>(json) ?? [];
}
}
@@ -1,180 +0,0 @@
using System.Runtime.CompilerServices;
using Dapper;
using JdeScoping.Core.Interfaces;
using JdeScoping.Core.Models;
using JdeScoping.Core.Models.Inventory;
using JdeScoping.Core.Models.Organization;
using JdeScoping.Core.Models.WorkOrders;
using Microsoft.Extensions.Configuration;
using Oracle.ManagedDataAccess.Client;
namespace JdeScoping.Infrastructure.Sources.Jde;
/// <summary>
/// Oracle-based JDE data source for production use.
/// </summary>
public class JdeOracleDataSource : IJdeDataSource
{
private readonly string _connectionString;
/// <summary>
/// Initializes a new instance of the <see cref="JdeOracleDataSource"/> class.
/// </summary>
public JdeOracleDataSource(IConfiguration configuration)
{
_connectionString = configuration.GetConnectionString("JDE")
?? throw new InvalidOperationException("JDE connection string not configured");
}
/// <inheritdoc/>
public async IAsyncEnumerable<WorkOrder> GetWorkOrdersAsync(
DateTime? minimumDt = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await using var connection = new OracleConnection(_connectionString);
await connection.OpenAsync(cancellationToken);
// TODO: Implement actual JDE query with proper column mapping
var sql = minimumDt.HasValue
? "SELECT * FROM F4801 WHERE UPMJ >= :MinDate"
: "SELECT * FROM F4801";
var results = await connection.QueryAsync<WorkOrder>(sql, new { MinDate = minimumDt });
foreach (var item in results)
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<LotUsage> GetLotUsagesAsync(
DateTime? minimumDt = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await using var connection = new OracleConnection(_connectionString);
await connection.OpenAsync(cancellationToken);
// TODO: Implement actual JDE query
var sql = minimumDt.HasValue
? "SELECT * FROM F4111 WHERE UPMJ >= :MinDate"
: "SELECT * FROM F4111";
var results = await connection.QueryAsync<LotUsage>(sql, new { MinDate = minimumDt });
foreach (var item in results)
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<Lot> GetLotsAsync(
DateTime? minimumDt = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await using var connection = new OracleConnection(_connectionString);
await connection.OpenAsync(cancellationToken);
var sql = minimumDt.HasValue
? "SELECT * FROM F4108 WHERE UPMJ >= :MinDate"
: "SELECT * FROM F4108";
var results = await connection.QueryAsync<Lot>(sql, new { MinDate = minimumDt });
foreach (var item in results)
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<Item> GetItemsAsync(
DateTime? minimumDt = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await using var connection = new OracleConnection(_connectionString);
await connection.OpenAsync(cancellationToken);
var sql = minimumDt.HasValue
? "SELECT * FROM F4101 WHERE UPMJ >= :MinDate"
: "SELECT * FROM F4101";
var results = await connection.QueryAsync<Item>(sql, new { MinDate = minimumDt });
foreach (var item in results)
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<WorkCenter> GetWorkCentersAsync(
DateTime? minimumDt = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await using var connection = new OracleConnection(_connectionString);
await connection.OpenAsync(cancellationToken);
var sql = minimumDt.HasValue
? "SELECT * FROM F30006 WHERE UPMJ >= :MinDate"
: "SELECT * FROM F30006";
var results = await connection.QueryAsync<WorkCenter>(sql, new { MinDate = minimumDt });
foreach (var item in results)
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<ProfitCenter> GetProfitCentersAsync(
DateTime? minimumDt = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await using var connection = new OracleConnection(_connectionString);
await connection.OpenAsync(cancellationToken);
// TODO: Implement actual query for profit centers
var sql = "SELECT * FROM F0006";
var results = await connection.QueryAsync<ProfitCenter>(sql);
foreach (var item in results)
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<JdeUser> GetUsersAsync(
DateTime? minimumDt = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await using var connection = new OracleConnection(_connectionString);
await connection.OpenAsync(cancellationToken);
var sql = minimumDt.HasValue
? "SELECT * FROM F0092 WHERE UPMJ >= :MinDate"
: "SELECT * FROM F0092";
var results = await connection.QueryAsync<JdeUser>(sql, new { MinDate = minimumDt });
foreach (var item in results)
{
yield return item;
}
}
/// <inheritdoc/>
public async IAsyncEnumerable<Branch> GetBranchesAsync(
DateTime? minimumDt = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
await using var connection = new OracleConnection(_connectionString);
await connection.OpenAsync(cancellationToken);
// TODO: Implement actual query for branches
var sql = "SELECT * FROM F0101";
var results = await connection.QueryAsync<Branch>(sql);
foreach (var item in results)
{
yield return item;
}
}
}