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
@@ -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);
}
}