Regelsteuerung grafisch und per C# Templates

This commit is contained in:
2026-04-16 08:47:13 +02:00
parent d02f4abb57
commit a25e5900c7
11 changed files with 205 additions and 14 deletions
@@ -95,6 +95,7 @@ public class ConfigTransferService : IConfigTransferService
SourceField = r.SourceField,
TargetField = r.TargetField,
TransformationType = r.TransformationType,
RuleScope = r.RuleScope,
Argument = r.Argument,
SortOrder = r.SortOrder,
IsActive = r.IsActive
@@ -265,6 +266,7 @@ public class ConfigTransferService : IConfigTransferService
SourceField = r.SourceField,
TargetField = r.TargetField,
TransformationType = r.TransformationType,
RuleScope = r.RuleScope,
Argument = r.Argument,
SortOrder = r.SortOrder,
IsActive = r.IsActive
@@ -71,6 +71,7 @@ public class DatabaseInitializationService : IDatabaseInitializationService
AddColumnIfMissing(db, "ExportSettings", "LocalConsolidatedExportFolder", "TEXT NOT NULL DEFAULT ''");
AddColumnIfMissing(db, "ExportLogs", "FilePath", "TEXT NOT NULL DEFAULT ''");
EnsureTransformationTable(db);
AddColumnIfMissing(db, "FieldTransformationRules", "RuleScope", "TEXT NOT NULL DEFAULT 'Value'");
EnsureSapSourceTable(db);
EnsureSapJoinTable(db);
EnsureSapFieldMappingTable(db);
@@ -440,6 +441,7 @@ CREATE TABLE IF NOT EXISTS FieldTransformationRules (
SourceField TEXT NOT NULL,
TargetField TEXT NOT NULL,
TransformationType TEXT NOT NULL,
RuleScope TEXT NOT NULL DEFAULT 'Value',
Argument TEXT NOT NULL DEFAULT '',
SortOrder INTEGER NOT NULL DEFAULT 0,
IsActive INTEGER NOT NULL DEFAULT 1
@@ -0,0 +1,10 @@
using TrafagSalesExporter.Models;
namespace TrafagSalesExporter.Services;
public interface IRecordTransformationStrategy
{
string TransformationType { get; }
string Description => string.Empty;
void Transform(SalesRecord record, FieldTransformationRule rule);
}
@@ -0,0 +1,14 @@
namespace TrafagSalesExporter.Services;
public interface ITransformationCatalog
{
IReadOnlyList<TransformationCatalogItem> GetAll();
IReadOnlyList<TransformationCatalogItem> GetByScope(string ruleScope);
}
public sealed class TransformationCatalogItem
{
public string Key { get; init; } = string.Empty;
public string RuleScope { get; init; } = string.Empty;
public string Description { get; init; } = string.Empty;
}
@@ -3,5 +3,6 @@ namespace TrafagSalesExporter.Services;
public interface ITransformationStrategy
{
string TransformationType { get; }
string Description => string.Empty;
object? Transform(object? sourceValue, string? argument);
}
@@ -5,15 +5,17 @@ namespace TrafagSalesExporter.Services;
public class RecordTransformationService : IRecordTransformationService
{
private static readonly Dictionary<string, PropertyInfo> PropertyMap = typeof(SalesRecord)
internal static readonly Dictionary<string, PropertyInfo> PropertyMap = typeof(SalesRecord)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.ToDictionary(p => p.Name, p => p, StringComparer.OrdinalIgnoreCase);
private readonly IReadOnlyDictionary<string, ITransformationStrategy> _strategies;
private readonly IReadOnlyDictionary<string, IRecordTransformationStrategy> _recordStrategies;
public RecordTransformationService(IEnumerable<ITransformationStrategy> strategies)
public RecordTransformationService(IEnumerable<ITransformationStrategy> strategies, IEnumerable<IRecordTransformationStrategy> recordStrategies)
{
_strategies = strategies.ToDictionary(s => s.TransformationType, StringComparer.OrdinalIgnoreCase);
_recordStrategies = recordStrategies.ToDictionary(s => s.TransformationType, StringComparer.OrdinalIgnoreCase);
}
public void Apply(List<SalesRecord> records, IEnumerable<FieldTransformationRule> rules)
@@ -32,6 +34,13 @@ public class RecordTransformationService : IRecordTransformationService
private void ApplyRule(SalesRecord record, FieldTransformationRule rule)
{
if (string.Equals(rule.RuleScope, "Record", StringComparison.OrdinalIgnoreCase))
{
if (_recordStrategies.TryGetValue(rule.TransformationType, out var recordStrategy))
recordStrategy.Transform(record, rule);
return;
}
if (!PropertyMap.TryGetValue(rule.SourceField, out var sourceProp)) return;
if (!PropertyMap.TryGetValue(rule.TargetField, out var targetProp)) return;
@@ -43,7 +52,7 @@ public class RecordTransformationService : IRecordTransformationService
SetPropertyValue(record, targetProp, result);
}
private static void SetPropertyValue(SalesRecord record, PropertyInfo property, object? value)
internal static void SetPropertyValue(SalesRecord record, PropertyInfo property, object? value)
{
try
{
@@ -0,0 +1,33 @@
namespace TrafagSalesExporter.Services;
public class TransformationCatalog : ITransformationCatalog
{
private readonly IReadOnlyList<TransformationCatalogItem> _items;
public TransformationCatalog(IEnumerable<ITransformationStrategy> valueStrategies, IEnumerable<IRecordTransformationStrategy> recordStrategies)
{
_items = valueStrategies
.Select(x => new TransformationCatalogItem
{
Key = x.TransformationType,
RuleScope = "Value",
Description = x.Description
})
.Concat(recordStrategies.Select(x => new TransformationCatalogItem
{
Key = x.TransformationType,
RuleScope = "Record",
Description = x.Description
}))
.OrderBy(x => x.RuleScope, StringComparer.OrdinalIgnoreCase)
.ThenBy(x => x.Key, StringComparer.OrdinalIgnoreCase)
.ToList();
}
public IReadOnlyList<TransformationCatalogItem> GetAll() => _items;
public IReadOnlyList<TransformationCatalogItem> GetByScope(string ruleScope)
=> _items
.Where(x => string.Equals(x.RuleScope, ruleScope, StringComparison.OrdinalIgnoreCase))
.ToList();
}
@@ -1,38 +1,46 @@
using TrafagSalesExporter.Models;
namespace TrafagSalesExporter.Services;
public sealed class CopyTransformationStrategy : ITransformationStrategy
{
public string TransformationType => "Copy";
public string Description => "Kopiert Source nach Target.";
public object? Transform(object? sourceValue, string? argument) => sourceValue;
}
public sealed class UppercaseTransformationStrategy : ITransformationStrategy
{
public string TransformationType => "Uppercase";
public string Description => "Wandelt Text in Grossbuchstaben.";
public object? Transform(object? sourceValue, string? argument) => sourceValue?.ToString()?.ToUpperInvariant();
}
public sealed class LowercaseTransformationStrategy : ITransformationStrategy
{
public string TransformationType => "Lowercase";
public string Description => "Wandelt Text in Kleinbuchstaben.";
public object? Transform(object? sourceValue, string? argument) => sourceValue?.ToString()?.ToLowerInvariant();
}
public sealed class PrefixTransformationStrategy : ITransformationStrategy
{
public string TransformationType => "Prefix";
public string Description => "Stellt Argument vor den Source-Wert.";
public object? Transform(object? sourceValue, string? argument) => $"{argument}{sourceValue}";
}
public sealed class SuffixTransformationStrategy : ITransformationStrategy
{
public string TransformationType => "Suffix";
public string Description => "Haengt Argument an den Source-Wert.";
public object? Transform(object? sourceValue, string? argument) => $"{sourceValue}{argument}";
}
public sealed class ReplaceTransformationStrategy : ITransformationStrategy
{
public string TransformationType => "Replace";
public string Description => "Ersetzt in Text mit Syntax alt=>neu.";
public object? Transform(object? sourceValue, string? argument)
{
@@ -54,5 +62,54 @@ public sealed class ReplaceTransformationStrategy : ITransformationStrategy
public sealed class ConstantTransformationStrategy : ITransformationStrategy
{
public string TransformationType => "Constant";
public string Description => "Setzt das Target auf einen konstanten Wert aus Argument.";
public object? Transform(object? sourceValue, string? argument) => argument;
}
public sealed class FirstNonEmptyRecordTransformationStrategy : IRecordTransformationStrategy
{
public string TransformationType => "FirstNonEmpty";
public string Description => "Record-Strategie: setzt Target aus dem ersten nicht-leeren Feld aus Argument, z.B. CustomerName|SupplierName|Name.";
public void Transform(SalesRecord record, FieldTransformationRule rule)
{
if (string.IsNullOrWhiteSpace(rule.TargetField) || string.IsNullOrWhiteSpace(rule.Argument))
return;
var propertyMap = RecordTransformationService.PropertyMap;
if (!propertyMap.TryGetValue(rule.TargetField, out var targetProperty))
return;
var sourceFields = rule.Argument
.Split(['|', ',', ';'], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
foreach (var sourceField in sourceFields)
{
if (!propertyMap.TryGetValue(sourceField, out var sourceProperty))
continue;
var value = sourceProperty.GetValue(record);
if (IsMeaningfulValue(value))
{
RecordTransformationService.SetPropertyValue(record, targetProperty, value);
return;
}
}
}
private static bool IsMeaningfulValue(object? value)
{
if (value is null)
return false;
if (value is string text)
return !string.IsNullOrWhiteSpace(text);
if (value is DateTime date)
return date != default;
if (value is decimal decimalNumber)
return decimalNumber != 0m;
if (value is int intNumber)
return intNumber != 0;
return true;
}
}