diff --git a/NEW/src/JdeScoping.DataSync/Configuration/ScheduleConfig.cs b/NEW/src/JdeScoping.DataSync/Configuration/ScheduleConfig.cs
new file mode 100644
index 0000000..b78a121
--- /dev/null
+++ b/NEW/src/JdeScoping.DataSync/Configuration/ScheduleConfig.cs
@@ -0,0 +1,98 @@
+namespace JdeScoping.DataSync.Configuration;
+
+///
+/// Configuration for a single schedule type (Mass/Daily/Hourly).
+///
+public record ScheduleConfig
+{
+ ///
+ /// Whether this schedule is enabled.
+ ///
+ public bool Enabled { get; init; } = true;
+
+ ///
+ /// Interval in minutes between syncs.
+ ///
+ public int IntervalMinutes { get; init; }
+
+ ///
+ /// Whether to truncate the table before import (full reload).
+ ///
+ public bool PrePurge { get; init; }
+
+ ///
+ /// Whether to rebuild indexes after import.
+ ///
+ public bool ReIndex { get; init; }
+
+ ///
+ /// Condition for updating existing rows (e.g., "src.LastUpdateDt > tgt.LastUpdateDt").
+ ///
+ public string? UpdateWhen { get; init; }
+
+ ///
+ /// Merges this config with defaults. Non-null/non-default values in this config override defaults.
+ ///
+ public ScheduleConfig MergeWith(ScheduleConfig defaults)
+ {
+ return new ScheduleConfig
+ {
+ Enabled = Enabled,
+ IntervalMinutes = IntervalMinutes > 0 ? IntervalMinutes : defaults.IntervalMinutes,
+ PrePurge = PrePurge || defaults.PrePurge,
+ ReIndex = ReIndex || defaults.ReIndex,
+ UpdateWhen = UpdateWhen ?? defaults.UpdateWhen
+ };
+ }
+}
+
+///
+/// Default schedule configurations for all pipelines.
+///
+public record ScheduleDefaults
+{
+ ///
+ /// Default Mass schedule config (weekly, full reload).
+ ///
+ public ScheduleConfig Mass { get; init; } = new()
+ {
+ Enabled = true,
+ IntervalMinutes = 10080, // Weekly
+ PrePurge = true,
+ ReIndex = true
+ };
+
+ ///
+ /// Default Daily schedule config (incremental merge).
+ ///
+ public ScheduleConfig Daily { get; init; } = new()
+ {
+ Enabled = true,
+ IntervalMinutes = 1440, // Daily
+ PrePurge = false,
+ ReIndex = false,
+ UpdateWhen = "src.LastUpdateDt > tgt.LastUpdateDt"
+ };
+
+ ///
+ /// Default Hourly schedule config (incremental merge).
+ ///
+ public ScheduleConfig Hourly { get; init; } = new()
+ {
+ Enabled = true,
+ IntervalMinutes = 60, // Hourly
+ PrePurge = false,
+ ReIndex = false,
+ UpdateWhen = "src.LastUpdateDt > tgt.LastUpdateDt"
+ };
+}
+
+///
+/// Per-pipeline schedule overrides.
+///
+public record PipelineSchedules
+{
+ public ScheduleConfig? Mass { get; init; }
+ public ScheduleConfig? Daily { get; init; }
+ public ScheduleConfig? Hourly { get; init; }
+}
diff --git a/NEW/tests/JdeScoping.DataSync.Tests/Configuration/ScheduleConfigTests.cs b/NEW/tests/JdeScoping.DataSync.Tests/Configuration/ScheduleConfigTests.cs
new file mode 100644
index 0000000..cce1209
--- /dev/null
+++ b/NEW/tests/JdeScoping.DataSync.Tests/Configuration/ScheduleConfigTests.cs
@@ -0,0 +1,176 @@
+using JdeScoping.DataSync.Configuration;
+using Shouldly;
+
+namespace JdeScoping.DataSync.Tests.Configuration;
+
+public class ScheduleConfigTests
+{
+ [Fact]
+ public void ScheduleConfig_DefaultValues_AreCorrect()
+ {
+ var config = new ScheduleConfig();
+
+ config.Enabled.ShouldBeTrue();
+ config.IntervalMinutes.ShouldBe(0);
+ config.PrePurge.ShouldBeFalse();
+ config.ReIndex.ShouldBeFalse();
+ config.UpdateWhen.ShouldBeNull();
+ }
+
+ [Fact]
+ public void ScheduleConfig_WithValues_StoresCorrectly()
+ {
+ var config = new ScheduleConfig
+ {
+ Enabled = false,
+ IntervalMinutes = 60,
+ PrePurge = true,
+ ReIndex = true,
+ UpdateWhen = "src.LastUpdateDt > tgt.LastUpdateDt"
+ };
+
+ config.Enabled.ShouldBeFalse();
+ config.IntervalMinutes.ShouldBe(60);
+ config.PrePurge.ShouldBeTrue();
+ config.ReIndex.ShouldBeTrue();
+ config.UpdateWhen.ShouldBe("src.LastUpdateDt > tgt.LastUpdateDt");
+ }
+
+ [Fact]
+ public void ScheduleDefaults_HasCorrectDefaultValues()
+ {
+ var defaults = new ScheduleDefaults();
+
+ defaults.Mass.ShouldNotBeNull();
+ defaults.Daily.ShouldNotBeNull();
+ defaults.Hourly.ShouldNotBeNull();
+ }
+
+ [Fact]
+ public void ScheduleDefaults_Mass_HasCorrectValues()
+ {
+ var defaults = new ScheduleDefaults();
+
+ defaults.Mass.Enabled.ShouldBeTrue();
+ defaults.Mass.IntervalMinutes.ShouldBe(10080); // Weekly
+ defaults.Mass.PrePurge.ShouldBeTrue();
+ defaults.Mass.ReIndex.ShouldBeTrue();
+ defaults.Mass.UpdateWhen.ShouldBeNull();
+ }
+
+ [Fact]
+ public void ScheduleDefaults_Daily_HasCorrectValues()
+ {
+ var defaults = new ScheduleDefaults();
+
+ defaults.Daily.Enabled.ShouldBeTrue();
+ defaults.Daily.IntervalMinutes.ShouldBe(1440); // Daily
+ defaults.Daily.PrePurge.ShouldBeFalse();
+ defaults.Daily.ReIndex.ShouldBeFalse();
+ defaults.Daily.UpdateWhen.ShouldBe("src.LastUpdateDt > tgt.LastUpdateDt");
+ }
+
+ [Fact]
+ public void ScheduleDefaults_Hourly_HasCorrectValues()
+ {
+ var defaults = new ScheduleDefaults();
+
+ defaults.Hourly.Enabled.ShouldBeTrue();
+ defaults.Hourly.IntervalMinutes.ShouldBe(60); // Hourly
+ defaults.Hourly.PrePurge.ShouldBeFalse();
+ defaults.Hourly.ReIndex.ShouldBeFalse();
+ defaults.Hourly.UpdateWhen.ShouldBe("src.LastUpdateDt > tgt.LastUpdateDt");
+ }
+
+ [Fact]
+ public void PipelineSchedules_AllPropertiesNullable()
+ {
+ var schedules = new PipelineSchedules();
+
+ schedules.Mass.ShouldBeNull();
+ schedules.Daily.ShouldBeNull();
+ schedules.Hourly.ShouldBeNull();
+ }
+
+ [Fact]
+ public void PipelineSchedules_WithValues_StoresCorrectly()
+ {
+ var schedules = new PipelineSchedules
+ {
+ Mass = new ScheduleConfig { PrePurge = true },
+ Daily = new ScheduleConfig { Enabled = true },
+ Hourly = new ScheduleConfig { Enabled = false }
+ };
+
+ schedules.Mass.ShouldNotBeNull();
+ schedules.Mass!.PrePurge.ShouldBeTrue();
+ schedules.Daily.ShouldNotBeNull();
+ schedules.Daily!.Enabled.ShouldBeTrue();
+ schedules.Hourly.ShouldNotBeNull();
+ schedules.Hourly!.Enabled.ShouldBeFalse();
+ }
+
+ [Fact]
+ public void MergeWith_WhenConfigHasNoOverrides_ReturnsDefaultValues()
+ {
+ var config = new ScheduleConfig();
+ var defaults = new ScheduleConfig
+ {
+ Enabled = true,
+ IntervalMinutes = 60,
+ PrePurge = false,
+ ReIndex = false,
+ UpdateWhen = "src.LastUpdateDt > tgt.LastUpdateDt"
+ };
+
+ var merged = config.MergeWith(defaults);
+
+ merged.IntervalMinutes.ShouldBe(60);
+ merged.UpdateWhen.ShouldBe("src.LastUpdateDt > tgt.LastUpdateDt");
+ }
+
+ [Fact]
+ public void MergeWith_WhenConfigHasOverrides_UsesOverrideValues()
+ {
+ var config = new ScheduleConfig
+ {
+ IntervalMinutes = 120,
+ PrePurge = true,
+ UpdateWhen = "custom condition"
+ };
+ var defaults = new ScheduleConfig
+ {
+ IntervalMinutes = 60,
+ PrePurge = false,
+ UpdateWhen = "default condition"
+ };
+
+ var merged = config.MergeWith(defaults);
+
+ merged.IntervalMinutes.ShouldBe(120);
+ merged.PrePurge.ShouldBeTrue();
+ merged.UpdateWhen.ShouldBe("custom condition");
+ }
+
+ [Fact]
+ public void MergeWith_PreservesEnabledFromConfig()
+ {
+ var config = new ScheduleConfig { Enabled = false };
+ var defaults = new ScheduleConfig { Enabled = true };
+
+ var merged = config.MergeWith(defaults);
+
+ merged.Enabled.ShouldBeFalse();
+ }
+
+ [Fact]
+ public void MergeWith_WhenIntervalZero_UsesDefaultInterval()
+ {
+ var config = new ScheduleConfig { IntervalMinutes = 0 };
+ var defaults = new ScheduleConfig { IntervalMinutes = 1440 };
+
+ var merged = config.MergeWith(defaults);
+
+ merged.IntervalMinutes.ShouldBe(1440);
+ }
+}