Consolidate mapping and finance configuration
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
using System.Globalization;
|
||||
using Sap.Data.Hana;
|
||||
using TrafagSalesExporter.Models;
|
||||
|
||||
@@ -9,10 +8,12 @@ public class HanaQueryService : IHanaQueryService
|
||||
private const string TscParameterName = "tsc";
|
||||
private const string DateFilterParameterName = "dateFilter";
|
||||
private readonly IAppEventLogService _appEventLogService;
|
||||
private readonly IMappedSalesRecordComposer _composer;
|
||||
|
||||
public HanaQueryService(IAppEventLogService appEventLogService)
|
||||
public HanaQueryService(IAppEventLogService appEventLogService, IMappedSalesRecordComposer composer)
|
||||
{
|
||||
_appEventLogService = appEventLogService;
|
||||
_composer = composer;
|
||||
}
|
||||
|
||||
public async Task<List<SalesRecord>> GetSalesRecordsAsync(HanaServer server,
|
||||
@@ -244,22 +245,7 @@ public class HanaQueryService : IHanaQueryService
|
||||
$"Alias={source.Alias} | Tabelle/View={source.EntitySet} | Zeilen={sourceRows[source.Alias].Count}");
|
||||
}
|
||||
|
||||
var primarySource = activeSources.FirstOrDefault(s => s.IsPrimary) ?? activeSources.First();
|
||||
var composedRows = sourceRows[primarySource.Alias]
|
||||
.Select(r => PrefixRow(primarySource.Alias, r))
|
||||
.ToList();
|
||||
|
||||
foreach (var join in joins.Where(j => j.IsActive).OrderBy(j => j.SortOrder).ThenBy(j => j.Id))
|
||||
{
|
||||
if (!sourceRows.TryGetValue(join.RightAlias, out var rightRows))
|
||||
continue;
|
||||
|
||||
composedRows = ApplyLeftJoin(composedRows, join.LeftAlias, join.LeftKeys, join.RightAlias, join.RightKeys, rightRows);
|
||||
}
|
||||
|
||||
return composedRows
|
||||
.Select(row => MapToSalesRecord(site, row, mappings))
|
||||
.ToList();
|
||||
return _composer.Compose(site, activeSources, joins, mappings, sourceRows, "HANA");
|
||||
}
|
||||
|
||||
private async Task<List<SalesRecord>> ReadRecordsAsync(HanaConnection connection, string query, string tsc, DateTime dateFilter, string land, string queryName, CancellationToken cancellationToken)
|
||||
@@ -377,155 +363,6 @@ public class HanaQueryService : IHanaQueryService
|
||||
return Convert.ToInt32(count) > 0;
|
||||
}
|
||||
|
||||
private static Dictionary<string, object?> PrefixRow(string alias, Dictionary<string, object?> row)
|
||||
=> row.ToDictionary(kvp => $"{alias}.{kvp.Key}", kvp => kvp.Value, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
private static List<Dictionary<string, object?>> ApplyLeftJoin(
|
||||
List<Dictionary<string, object?>> leftRows,
|
||||
string leftAlias,
|
||||
string leftKeys,
|
||||
string rightAlias,
|
||||
string rightKeys,
|
||||
List<Dictionary<string, object?>> rightRows)
|
||||
{
|
||||
var leftKeyParts = SplitKeys(leftKeys);
|
||||
var rightKeyParts = SplitKeys(rightKeys);
|
||||
if (leftKeyParts.Count == 0 || leftKeyParts.Count != rightKeyParts.Count)
|
||||
return leftRows;
|
||||
|
||||
var rightLookup = rightRows
|
||||
.GroupBy(r => BuildKey(r, rightKeyParts))
|
||||
.ToDictionary(g => g.Key, g => g.ToList(), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
var results = new List<Dictionary<string, object?>>();
|
||||
foreach (var leftRow in leftRows)
|
||||
{
|
||||
var leftKey = BuildKey(leftRow, leftAlias, leftKeyParts);
|
||||
if (rightLookup.TryGetValue(leftKey, out var matches) && matches.Count > 0)
|
||||
{
|
||||
foreach (var match in matches)
|
||||
{
|
||||
var merged = new Dictionary<string, object?>(leftRow, StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var kvp in PrefixRow(rightAlias, match))
|
||||
merged[kvp.Key] = kvp.Value;
|
||||
results.Add(merged);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
results.Add(leftRow);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static SalesRecord MapToSalesRecord(Site site, Dictionary<string, object?> row, IReadOnlyList<SapFieldMapping> mappings)
|
||||
{
|
||||
var record = new SalesRecord
|
||||
{
|
||||
ExtractionDate = DateTime.UtcNow,
|
||||
Tsc = site.TSC,
|
||||
Land = site.Land,
|
||||
DocumentType = "HANA"
|
||||
};
|
||||
|
||||
foreach (var mapping in mappings.Where(m => m.IsActive).OrderBy(m => m.SortOrder).ThenBy(m => m.Id))
|
||||
{
|
||||
var value = EvaluateExpression(row, mapping.SourceExpression);
|
||||
ApplyValue(record, mapping.TargetField, value);
|
||||
}
|
||||
|
||||
if (record.ExtractionDate == default)
|
||||
record.ExtractionDate = DateTime.UtcNow;
|
||||
if (string.IsNullOrWhiteSpace(record.Tsc))
|
||||
record.Tsc = site.TSC;
|
||||
if (string.IsNullOrWhiteSpace(record.Land))
|
||||
record.Land = site.Land;
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
private static object? EvaluateExpression(Dictionary<string, object?> row, string expression)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(expression))
|
||||
return null;
|
||||
|
||||
var value = expression.Trim();
|
||||
if (value.StartsWith('='))
|
||||
return value[1..];
|
||||
|
||||
if (row.TryGetValue(value, out var direct))
|
||||
return direct;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void ApplyValue(SalesRecord record, string targetField, object? value)
|
||||
{
|
||||
var property = typeof(SalesRecord).GetProperty(targetField);
|
||||
if (property is null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
if (property.PropertyType == typeof(string))
|
||||
{
|
||||
property.SetValue(record, value?.ToString() ?? string.Empty);
|
||||
return;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(int))
|
||||
{
|
||||
if (int.TryParse(value?.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out var intValue))
|
||||
property.SetValue(record, intValue);
|
||||
return;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(decimal))
|
||||
{
|
||||
if (decimal.TryParse(value?.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out var decimalValue))
|
||||
property.SetValue(record, decimalValue);
|
||||
return;
|
||||
}
|
||||
|
||||
if (property.PropertyType == typeof(DateTime?) || property.PropertyType == typeof(DateTime))
|
||||
{
|
||||
if (TryParseDate(value?.ToString(), out var date))
|
||||
property.SetValue(record, date);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Invalid field mappings should not stop the remaining row mapping.
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryParseDate(string? value, out DateTime date)
|
||||
{
|
||||
date = default;
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return false;
|
||||
|
||||
return DateTime.TryParse(value.Trim(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out date)
|
||||
|| DateTime.TryParse(value.Trim(), CultureInfo.GetCultureInfo("de-CH"), DateTimeStyles.AssumeLocal, out date);
|
||||
}
|
||||
|
||||
private static string BuildKey(Dictionary<string, object?> row, IReadOnlyList<string> keys)
|
||||
=> string.Join("||", keys.Select(k => NormalizeKeyValue(row.TryGetValue(k, out var value) ? value : null)));
|
||||
|
||||
private static string BuildKey(Dictionary<string, object?> row, string alias, IReadOnlyList<string> keys)
|
||||
=> string.Join("||", keys.Select(k =>
|
||||
{
|
||||
row.TryGetValue($"{alias}.{k}", out var value);
|
||||
return NormalizeKeyValue(value);
|
||||
}));
|
||||
|
||||
private static string NormalizeKeyValue(object? value) => value?.ToString()?.Trim() ?? string.Empty;
|
||||
|
||||
private static List<string> SplitKeys(string keys)
|
||||
=> keys.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList();
|
||||
|
||||
private static string GetInvoiceQuery(string schema)
|
||||
{
|
||||
var schemaPrefix = BuildSchemaPrefix(schema);
|
||||
|
||||
Reference in New Issue
Block a user