Initial commit: JDE Scoping Tool migration project
Set up repository with legacy .NET Framework 4.8 source (OLD/), new .NET 10 Blazor solution (NEW/), OpenSpec specifications, documentation, and project configuration.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
namespace JdeScoping.DataSync.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for a single data source table sync.
|
||||
/// </summary>
|
||||
public class DataSourceConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Target table name in SQL Server cache.
|
||||
/// </summary>
|
||||
public required string TableName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Source system: "JDE" or "CMS".
|
||||
/// </summary>
|
||||
public required string SourceSystem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Source data identifier (e.g., "WORKORDER", "LOTUSAGE").
|
||||
/// </summary>
|
||||
public string SourceData { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Name of IDataFetcher implementation type (without generic suffix).
|
||||
/// </summary>
|
||||
public required string FetcherTypeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional IPostProcessor implementation type name.
|
||||
/// </summary>
|
||||
public string? PostProcessorTypeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this data source is enabled for sync.
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Mass sync schedule configuration.
|
||||
/// </summary>
|
||||
public ScheduleConfig MassConfig { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Daily incremental sync configuration.
|
||||
/// </summary>
|
||||
public ScheduleConfig DailyConfig { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Hourly incremental sync configuration.
|
||||
/// </summary>
|
||||
public ScheduleConfig HourlyConfig { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedule configuration for a sync type (Mass/Daily/Hourly).
|
||||
/// </summary>
|
||||
public class ScheduleConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether this schedule is enabled.
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Interval in minutes between syncs.
|
||||
/// </summary>
|
||||
public int IntervalMinutes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to truncate the table before syncing (mass updates only).
|
||||
/// </summary>
|
||||
public bool PrepurgeData { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to rebuild indexes after syncing (mass updates only).
|
||||
/// </summary>
|
||||
public bool ReIndexData { get; set; } = false;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace JdeScoping.DataSync.Configuration;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration options for the data synchronization service.
|
||||
/// </summary>
|
||||
public class DataSyncOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration section name in appsettings.json.
|
||||
/// </summary>
|
||||
public const string SectionName = "DataSync";
|
||||
|
||||
/// <summary>
|
||||
/// Time between schedule checks (default: 1 minute).
|
||||
/// </summary>
|
||||
public TimeSpan CheckInterval { get; set; } = TimeSpan.FromMinutes(1);
|
||||
|
||||
/// <summary>
|
||||
/// Maximum parallel sync operations (default: 8).
|
||||
/// </summary>
|
||||
[Range(1, 32)]
|
||||
public int MaxDegreeOfParallelism { get; set; } = 8;
|
||||
|
||||
/// <summary>
|
||||
/// Records per batch for streaming (default: 1,000,000).
|
||||
/// </summary>
|
||||
[Range(1000, 10_000_000)]
|
||||
public int BatchSize { get; set; } = 1_000_000;
|
||||
|
||||
/// <summary>
|
||||
/// Rows per bulk copy batch (default: 10,000).
|
||||
/// </summary>
|
||||
[Range(100, 100_000)]
|
||||
public int BulkCopyBatchSize { get; set; } = 10_000;
|
||||
|
||||
/// <summary>
|
||||
/// Multiplier for lookback window (default: 3).
|
||||
/// </summary>
|
||||
[Range(1, 10)]
|
||||
public int LookbackMultiplier { get; set; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Days to retain DataUpdate history (default: 30).
|
||||
/// </summary>
|
||||
[Range(1, 365)]
|
||||
public int PurgeRetentionDays { get; set; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Timeout in seconds for sync operations (default: 3600 = 1 hour).
|
||||
/// </summary>
|
||||
[Range(60, 86400)]
|
||||
public int SyncTimeoutSeconds { get; set; } = 3600;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the data sync service is enabled (default: true).
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Per-table data source configurations.
|
||||
/// </summary>
|
||||
public List<DataSourceConfig> DataSources { get; set; } = [];
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
using System.Linq.Expressions;
|
||||
using JdeScoping.Core.Models.Organization;
|
||||
using JdeScoping.DataSync.Contracts;
|
||||
|
||||
namespace JdeScoping.DataSync.Configuration.MergeConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Merge configuration for Branch entities.
|
||||
/// </summary>
|
||||
public sealed class BranchMergeConfiguration : IMergeConfiguration<Branch>
|
||||
{
|
||||
public string TableName => "Branch";
|
||||
|
||||
public Expression<Func<Branch, object>> MatchOn =>
|
||||
x => x.Code;
|
||||
|
||||
public Expression<Func<Branch, object>>? UpdateColumns =>
|
||||
x => x.Description;
|
||||
|
||||
public Expression<Func<Branch, Branch, bool>>? UpdateWhen =>
|
||||
(src, tgt) => src.LastUpdateDt > tgt.LastUpdateDt;
|
||||
|
||||
public Expression<Func<Branch, object>>? InsertColumns => null;
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
using System.Linq.Expressions;
|
||||
using JdeScoping.Core.Models.Inventory;
|
||||
using JdeScoping.DataSync.Contracts;
|
||||
|
||||
namespace JdeScoping.DataSync.Configuration.MergeConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Merge configuration for Item entities.
|
||||
/// </summary>
|
||||
public sealed class ItemMergeConfiguration : IMergeConfiguration<Item>
|
||||
{
|
||||
public string TableName => "Item";
|
||||
|
||||
public Expression<Func<Item, object>> MatchOn =>
|
||||
x => x.ShortItemNumber;
|
||||
|
||||
public Expression<Func<Item, object>>? UpdateColumns =>
|
||||
x => new
|
||||
{
|
||||
x.ItemNumber,
|
||||
x.Description,
|
||||
x.PlanningFamily,
|
||||
x.StockingType
|
||||
};
|
||||
|
||||
public Expression<Func<Item, Item, bool>>? UpdateWhen =>
|
||||
(src, tgt) => src.LastUpdateDt > tgt.LastUpdateDt;
|
||||
|
||||
public Expression<Func<Item, object>>? InsertColumns => null;
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
using System.Linq.Expressions;
|
||||
using JdeScoping.Core.Models.Organization;
|
||||
using JdeScoping.DataSync.Contracts;
|
||||
|
||||
namespace JdeScoping.DataSync.Configuration.MergeConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Merge configuration for JdeUser entities.
|
||||
/// </summary>
|
||||
public sealed class JdeUserMergeConfiguration : IMergeConfiguration<JdeUser>
|
||||
{
|
||||
public string TableName => "JdeUser";
|
||||
|
||||
public Expression<Func<JdeUser, object>> MatchOn =>
|
||||
x => x.AddressNumber;
|
||||
|
||||
public Expression<Func<JdeUser, object>>? UpdateColumns =>
|
||||
x => new { x.UserId, x.FullName };
|
||||
|
||||
public Expression<Func<JdeUser, JdeUser, bool>>? UpdateWhen =>
|
||||
(src, tgt) => src.LastUpdateDt > tgt.LastUpdateDt;
|
||||
|
||||
public Expression<Func<JdeUser, object>>? InsertColumns => null;
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
using System.Linq.Expressions;
|
||||
using JdeScoping.Core.Models.Inventory;
|
||||
using JdeScoping.DataSync.Contracts;
|
||||
|
||||
namespace JdeScoping.DataSync.Configuration.MergeConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Merge configuration for Lot entities.
|
||||
/// </summary>
|
||||
public sealed class LotMergeConfiguration : IMergeConfiguration<Lot>
|
||||
{
|
||||
public string TableName => "Lot";
|
||||
|
||||
public Expression<Func<Lot, object>> MatchOn =>
|
||||
x => new { x.LotNumber, x.BranchCode };
|
||||
|
||||
public Expression<Func<Lot, object>>? UpdateColumns =>
|
||||
x => new
|
||||
{
|
||||
x.ShortItemNumber,
|
||||
x.ItemNumber,
|
||||
x.SupplierCode,
|
||||
x.StatusCode,
|
||||
x.Memo1,
|
||||
x.Memo2,
|
||||
x.Memo3
|
||||
};
|
||||
|
||||
public Expression<Func<Lot, Lot, bool>>? UpdateWhen =>
|
||||
(src, tgt) => src.LastUpdateDt > tgt.LastUpdateDt;
|
||||
|
||||
public Expression<Func<Lot, object>>? InsertColumns => null;
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
using System.Linq.Expressions;
|
||||
using JdeScoping.Core.Models.Inventory;
|
||||
using JdeScoping.DataSync.Contracts;
|
||||
|
||||
namespace JdeScoping.DataSync.Configuration.MergeConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Merge configuration for LotUsage entities.
|
||||
/// </summary>
|
||||
public sealed class LotUsageMergeConfiguration : IMergeConfiguration<LotUsage>
|
||||
{
|
||||
public string TableName => "LotUsage";
|
||||
|
||||
public Expression<Func<LotUsage, object>> MatchOn =>
|
||||
x => x.UniqueId;
|
||||
|
||||
public Expression<Func<LotUsage, object>>? UpdateColumns =>
|
||||
x => new
|
||||
{
|
||||
x.WorkOrderNumber,
|
||||
x.LotNumber,
|
||||
x.BranchCode,
|
||||
x.ShortItemNumber,
|
||||
x.Quantity
|
||||
};
|
||||
|
||||
public Expression<Func<LotUsage, LotUsage, bool>>? UpdateWhen =>
|
||||
(src, tgt) => src.LastUpdateDt > tgt.LastUpdateDt;
|
||||
|
||||
public Expression<Func<LotUsage, object>>? InsertColumns => null;
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
using System.Linq.Expressions;
|
||||
using JdeScoping.Core.Models.Quality;
|
||||
using JdeScoping.DataSync.Contracts;
|
||||
|
||||
namespace JdeScoping.DataSync.Configuration.MergeConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Merge configuration for MisData entities.
|
||||
/// </summary>
|
||||
public sealed class MisDataMergeConfiguration : IMergeConfiguration<MisData>
|
||||
{
|
||||
public string TableName => "MisData";
|
||||
|
||||
public Expression<Func<MisData, object>> MatchOn =>
|
||||
x => new { x.ItemNumber, x.BranchCode, x.SequenceNumber, x.MisNumber, x.CharNumber };
|
||||
|
||||
public Expression<Func<MisData, object>>? UpdateColumns =>
|
||||
x => new
|
||||
{
|
||||
x.RevId,
|
||||
x.TestDescription,
|
||||
x.SamplingType,
|
||||
x.SamplingValue,
|
||||
x.ToolsGauges,
|
||||
x.WorkInstructions,
|
||||
x.Status,
|
||||
x.ReleaseDate
|
||||
};
|
||||
|
||||
// MisData doesn't have LastUpdateDt, so always update on match
|
||||
public Expression<Func<MisData, MisData, bool>>? UpdateWhen => null;
|
||||
|
||||
public Expression<Func<MisData, object>>? InsertColumns => null;
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
using System.Linq.Expressions;
|
||||
using JdeScoping.Core.Models.Organization;
|
||||
using JdeScoping.DataSync.Contracts;
|
||||
|
||||
namespace JdeScoping.DataSync.Configuration.MergeConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Merge configuration for ProfitCenter entities.
|
||||
/// </summary>
|
||||
public sealed class ProfitCenterMergeConfiguration : IMergeConfiguration<ProfitCenter>
|
||||
{
|
||||
public string TableName => "ProfitCenter";
|
||||
|
||||
public Expression<Func<ProfitCenter, object>> MatchOn =>
|
||||
x => x.Code;
|
||||
|
||||
public Expression<Func<ProfitCenter, object>>? UpdateColumns =>
|
||||
x => x.Description;
|
||||
|
||||
public Expression<Func<ProfitCenter, ProfitCenter, bool>>? UpdateWhen =>
|
||||
(src, tgt) => src.LastUpdateDt > tgt.LastUpdateDt;
|
||||
|
||||
public Expression<Func<ProfitCenter, object>>? InsertColumns => null;
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
using System.Linq.Expressions;
|
||||
using JdeScoping.Core.Models.Organization;
|
||||
using JdeScoping.DataSync.Contracts;
|
||||
|
||||
namespace JdeScoping.DataSync.Configuration.MergeConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Merge configuration for WorkCenter entities.
|
||||
/// </summary>
|
||||
public sealed class WorkCenterMergeConfiguration : IMergeConfiguration<WorkCenter>
|
||||
{
|
||||
public string TableName => "WorkCenter";
|
||||
|
||||
public Expression<Func<WorkCenter, object>> MatchOn =>
|
||||
x => x.Code;
|
||||
|
||||
public Expression<Func<WorkCenter, object>>? UpdateColumns =>
|
||||
x => x.Description;
|
||||
|
||||
public Expression<Func<WorkCenter, WorkCenter, bool>>? UpdateWhen =>
|
||||
(src, tgt) => src.LastUpdateDt > tgt.LastUpdateDt;
|
||||
|
||||
public Expression<Func<WorkCenter, object>>? InsertColumns => null;
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
using System.Linq.Expressions;
|
||||
using JdeScoping.Core.Models.WorkOrders;
|
||||
using JdeScoping.DataSync.Contracts;
|
||||
|
||||
namespace JdeScoping.DataSync.Configuration.MergeConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Merge configuration for WorkOrder entities.
|
||||
/// </summary>
|
||||
public sealed class WorkOrderMergeConfiguration : IMergeConfiguration<WorkOrder>
|
||||
{
|
||||
public string TableName => "WorkOrder";
|
||||
|
||||
public Expression<Func<WorkOrder, object>> MatchOn =>
|
||||
x => new { x.WorkOrderNumber, x.BranchCode };
|
||||
|
||||
public Expression<Func<WorkOrder, object>>? UpdateColumns =>
|
||||
x => new
|
||||
{
|
||||
x.LotNumber,
|
||||
x.ItemNumber,
|
||||
x.ShortItemNumber,
|
||||
x.ParentWorkOrderNumber,
|
||||
x.OrderQuantity,
|
||||
x.HeldQuantity,
|
||||
x.ShippedQuantity,
|
||||
x.StatusCode,
|
||||
x.StatusCodeUpdateDt,
|
||||
x.IssueDate,
|
||||
x.StartDate,
|
||||
x.RoutingType
|
||||
};
|
||||
|
||||
public Expression<Func<WorkOrder, WorkOrder, bool>>? UpdateWhen =>
|
||||
(src, tgt) => src.LastUpdateDt > tgt.LastUpdateDt;
|
||||
|
||||
public Expression<Func<WorkOrder, object>>? InsertColumns => null; // All columns
|
||||
}
|
||||
Reference in New Issue
Block a user