RefactoringDI
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TrafagSalesExporter.Data;
|
||||
using TrafagSalesExporter.Models;
|
||||
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
public class ConsolidatedExportService : IConsolidatedExportService
|
||||
{
|
||||
private readonly IDbContextFactory<AppDbContext> _dbFactory;
|
||||
private readonly IExcelExportService _excelService;
|
||||
private readonly ISharePointUploadService _sharePointService;
|
||||
|
||||
public ConsolidatedExportService(
|
||||
IDbContextFactory<AppDbContext> dbFactory,
|
||||
IExcelExportService excelService,
|
||||
ISharePointUploadService sharePointService)
|
||||
{
|
||||
_dbFactory = dbFactory;
|
||||
_excelService = excelService;
|
||||
_sharePointService = sharePointService;
|
||||
}
|
||||
|
||||
public async Task<string?> ExportAsync(List<SalesRecord> records)
|
||||
{
|
||||
if (records.Count == 0)
|
||||
return null;
|
||||
|
||||
using var db = await _dbFactory.CreateDbContextAsync();
|
||||
var spConfig = await db.SharePointConfigs.FirstOrDefaultAsync();
|
||||
var outputDir = Path.Combine(AppContext.BaseDirectory, "output");
|
||||
var consolidatedPath = _excelService.CreateConsolidatedExcelFile(
|
||||
outputDir,
|
||||
DateTime.UtcNow.Date,
|
||||
records
|
||||
.OrderBy(r => r.Land)
|
||||
.ThenBy(r => r.Tsc)
|
||||
.ThenByDescending(r => r.InvoiceDate ?? DateTime.MinValue)
|
||||
.ThenBy(r => r.InvoiceNumber)
|
||||
.ThenBy(r => r.PositionOnInvoice)
|
||||
.ToList());
|
||||
|
||||
if (spConfig is not null &&
|
||||
!string.IsNullOrWhiteSpace(spConfig.TenantId) &&
|
||||
!string.IsNullOrWhiteSpace(spConfig.ClientId) &&
|
||||
!string.IsNullOrWhiteSpace(spConfig.ClientSecret))
|
||||
{
|
||||
await _sharePointService.UploadAsync(
|
||||
spConfig.TenantId, spConfig.ClientId, spConfig.ClientSecret,
|
||||
spConfig.SiteUrl, spConfig.ExportFolder, "Alle", consolidatedPath);
|
||||
}
|
||||
|
||||
return consolidatedPath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
using System.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TrafagSalesExporter.Data;
|
||||
using TrafagSalesExporter.Models;
|
||||
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
public class DatabaseInitializationService : IDatabaseInitializationService
|
||||
{
|
||||
private readonly IDbContextFactory<AppDbContext> _dbFactory;
|
||||
|
||||
public DatabaseInitializationService(IDbContextFactory<AppDbContext> dbFactory)
|
||||
{
|
||||
_dbFactory = dbFactory;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
using var db = await _dbFactory.CreateDbContextAsync();
|
||||
await db.Database.EnsureCreatedAsync();
|
||||
EnsureSchema(db);
|
||||
SeedIfEmpty(db);
|
||||
}
|
||||
|
||||
private static void EnsureSchema(AppDbContext db)
|
||||
{
|
||||
AddColumnIfMissing(db, "HanaServers", "DatabaseName", "TEXT NOT NULL DEFAULT ''");
|
||||
AddColumnIfMissing(db, "HanaServers", "UseSsl", "INTEGER NOT NULL DEFAULT 0");
|
||||
AddColumnIfMissing(db, "HanaServers", "ValidateCertificate", "INTEGER NOT NULL DEFAULT 0");
|
||||
AddColumnIfMissing(db, "HanaServers", "AdditionalParams", "TEXT NOT NULL DEFAULT ''");
|
||||
AddColumnIfMissing(db, "Sites", "SourceSystem", "TEXT NOT NULL DEFAULT 'SAP'");
|
||||
EnsureTransformationTable(db);
|
||||
}
|
||||
|
||||
private static void AddColumnIfMissing(AppDbContext db, string table, string column, string type)
|
||||
{
|
||||
var conn = db.Database.GetDbConnection();
|
||||
if (conn.State != ConnectionState.Open)
|
||||
conn.Open();
|
||||
|
||||
var exists = false;
|
||||
using (var cmd = conn.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = $"PRAGMA table_info({table})";
|
||||
using var reader = cmd.ExecuteReader();
|
||||
while (reader.Read())
|
||||
{
|
||||
if (string.Equals(reader["name"]?.ToString(), column, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!exists)
|
||||
{
|
||||
using var alter = conn.CreateCommand();
|
||||
alter.CommandText = $"ALTER TABLE {table} ADD COLUMN {column} {type}";
|
||||
alter.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureTransformationTable(AppDbContext db)
|
||||
{
|
||||
var conn = db.Database.GetDbConnection();
|
||||
if (conn.State != ConnectionState.Open)
|
||||
conn.Open();
|
||||
|
||||
using var cmd = conn.CreateCommand();
|
||||
cmd.CommandText = @"
|
||||
CREATE TABLE IF NOT EXISTS FieldTransformationRules (
|
||||
Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
SourceSystem TEXT NOT NULL DEFAULT 'SAP',
|
||||
SourceField TEXT NOT NULL,
|
||||
TargetField TEXT NOT NULL,
|
||||
TransformationType TEXT NOT NULL,
|
||||
Argument TEXT NOT NULL DEFAULT '',
|
||||
SortOrder INTEGER NOT NULL DEFAULT 0,
|
||||
IsActive INTEGER NOT NULL DEFAULT 1
|
||||
);";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
private static void SeedIfEmpty(AppDbContext db)
|
||||
{
|
||||
if (db.HanaServers.Any())
|
||||
return;
|
||||
|
||||
var serverInternal = new HanaServer { Name = "Internal", Host = "travtrp0", Port = 30015, Username = "", Password = "" };
|
||||
var serverIndia = new HanaServer { Name = "India", Host = "20.197.20.60", Port = 30015, Username = "", Password = "" };
|
||||
db.HanaServers.AddRange(serverInternal, serverIndia);
|
||||
db.SaveChanges();
|
||||
|
||||
db.Sites.AddRange(
|
||||
new Site { HanaServerId = serverInternal.Id, Schema = "fr01_p", TSC = "TRFR", Land = "Frankreich", IsActive = true },
|
||||
new Site { HanaServerId = serverInternal.Id, Schema = "it01_p", TSC = "TRIT", Land = "Italien", IsActive = true },
|
||||
new Site { HanaServerId = serverInternal.Id, Schema = "us01_p", TSC = "TRUS", Land = "USA", IsActive = true },
|
||||
new Site { HanaServerId = serverIndia.Id, Schema = "TRAFAG_LIVE", TSC = "TRIN", Land = "Indien", IsActive = true }
|
||||
);
|
||||
|
||||
db.SharePointConfigs.Add(new SharePointConfig
|
||||
{
|
||||
SiteUrl = "https://trafagag.sharepoint.com/sites/WorldwideBIPlatform",
|
||||
ExportFolder = "/Shared Documents/Exports/",
|
||||
TenantId = "",
|
||||
ClientId = "",
|
||||
ClientSecret = ""
|
||||
});
|
||||
|
||||
db.ExportSettings.Add(new ExportSettings
|
||||
{
|
||||
DateFilter = "2025-01-01",
|
||||
TimerHour = 3,
|
||||
TimerMinute = 0,
|
||||
TimerEnabled = true
|
||||
});
|
||||
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ using TrafagSalesExporter.Models;
|
||||
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
public class ExcelExportService
|
||||
public class ExcelExportService : IExcelExportService
|
||||
{
|
||||
public string CreateExcelFile(string outputDirectory, string tsc, DateTime fileDate, List<SalesRecord> records)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TrafagSalesExporter.Data;
|
||||
using TrafagSalesExporter.Models;
|
||||
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
public class ExportLogService : IExportLogService
|
||||
{
|
||||
private readonly IDbContextFactory<AppDbContext> _dbFactory;
|
||||
|
||||
public ExportLogService(IDbContextFactory<AppDbContext> dbFactory)
|
||||
{
|
||||
_dbFactory = dbFactory;
|
||||
}
|
||||
|
||||
public async Task WriteAsync(ExportLog log)
|
||||
{
|
||||
using var db = await _dbFactory.CreateDbContextAsync();
|
||||
db.ExportLogs.Add(log);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Diagnostics;
|
||||
using TrafagSalesExporter.Data;
|
||||
using TrafagSalesExporter.Models;
|
||||
|
||||
@@ -8,11 +7,9 @@ namespace TrafagSalesExporter.Services;
|
||||
public class ExportOrchestrationService
|
||||
{
|
||||
private readonly IDbContextFactory<AppDbContext> _dbFactory;
|
||||
private readonly HanaQueryService _hanaService;
|
||||
private readonly ExcelExportService _excelService;
|
||||
private readonly SharePointUploadService _sharePointService;
|
||||
private readonly RecordTransformationService _transformationService;
|
||||
private readonly ILogger<ExportOrchestrationService> _logger;
|
||||
private readonly ISiteExportService _siteExportService;
|
||||
private readonly IConsolidatedExportService _consolidatedExportService;
|
||||
private readonly IExportLogService _exportLogService;
|
||||
|
||||
public event Action? OnExportStatusChanged;
|
||||
|
||||
@@ -21,18 +18,14 @@ public class ExportOrchestrationService
|
||||
|
||||
public ExportOrchestrationService(
|
||||
IDbContextFactory<AppDbContext> dbFactory,
|
||||
HanaQueryService hanaService,
|
||||
ExcelExportService excelService,
|
||||
SharePointUploadService sharePointService,
|
||||
RecordTransformationService transformationService,
|
||||
ILogger<ExportOrchestrationService> logger)
|
||||
ISiteExportService siteExportService,
|
||||
IConsolidatedExportService consolidatedExportService,
|
||||
IExportLogService exportLogService)
|
||||
{
|
||||
_dbFactory = dbFactory;
|
||||
_hanaService = hanaService;
|
||||
_excelService = excelService;
|
||||
_sharePointService = sharePointService;
|
||||
_transformationService = transformationService;
|
||||
_logger = logger;
|
||||
_siteExportService = siteExportService;
|
||||
_consolidatedExportService = consolidatedExportService;
|
||||
_exportLogService = exportLogService;
|
||||
}
|
||||
|
||||
public bool IsExporting(int siteId)
|
||||
@@ -64,31 +57,7 @@ public class ExportOrchestrationService
|
||||
consolidatedRecords.AddRange(result.Records);
|
||||
}
|
||||
|
||||
if (consolidatedRecords.Count > 0)
|
||||
{
|
||||
var spConfig = await db.SharePointConfigs.FirstOrDefaultAsync();
|
||||
var outputDir = Path.Combine(AppContext.BaseDirectory, "output");
|
||||
var consolidatedPath = _excelService.CreateConsolidatedExcelFile(
|
||||
outputDir,
|
||||
DateTime.UtcNow.Date,
|
||||
consolidatedRecords
|
||||
.OrderBy(r => r.Land)
|
||||
.ThenBy(r => r.Tsc)
|
||||
.ThenByDescending(r => r.InvoiceDate ?? DateTime.MinValue)
|
||||
.ThenBy(r => r.InvoiceNumber)
|
||||
.ThenBy(r => r.PositionOnInvoice)
|
||||
.ToList());
|
||||
|
||||
if (spConfig is not null &&
|
||||
!string.IsNullOrWhiteSpace(spConfig.TenantId) &&
|
||||
!string.IsNullOrWhiteSpace(spConfig.ClientId) &&
|
||||
!string.IsNullOrWhiteSpace(spConfig.ClientSecret))
|
||||
{
|
||||
await _sharePointService.UploadAsync(
|
||||
spConfig.TenantId, spConfig.ClientId, spConfig.ClientSecret,
|
||||
spConfig.SiteUrl, spConfig.ExportFolder, "Alle", consolidatedPath);
|
||||
}
|
||||
}
|
||||
await _consolidatedExportService.ExportAsync(consolidatedRecords);
|
||||
}
|
||||
|
||||
public async Task ExportSiteByIdAsync(int siteId)
|
||||
@@ -102,6 +71,7 @@ public class ExportOrchestrationService
|
||||
private async Task<SiteExportResult?> ExportSiteAsync(Site site)
|
||||
{
|
||||
if (site.HanaServer is null) return null;
|
||||
SiteExportResult? result = null;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
@@ -110,75 +80,17 @@ public class ExportOrchestrationService
|
||||
}
|
||||
NotifyChanged();
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
var log = new ExportLog
|
||||
{
|
||||
Timestamp = DateTime.Now,
|
||||
SiteId = site.Id,
|
||||
Land = site.Land,
|
||||
TSC = site.TSC
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using var db = await _dbFactory.CreateDbContextAsync();
|
||||
var settings = await db.ExportSettings.FirstOrDefaultAsync() ?? new ExportSettings();
|
||||
var spConfig = await db.SharePointConfigs.FirstOrDefaultAsync();
|
||||
|
||||
UpdateStatus(site.Id, "HANA Abfrage...");
|
||||
var records = await Task.Run(() => _hanaService.GetSalesRecords(
|
||||
site.HanaServer, site.Schema, site.TSC, site.Land, settings.DateFilter));
|
||||
|
||||
UpdateStatus(site.Id, "Transformationen anwenden...");
|
||||
var rules = await db.FieldTransformationRules
|
||||
.Where(r => r.IsActive && r.SourceSystem == (string.IsNullOrWhiteSpace(site.SourceSystem) ? "SAP" : site.SourceSystem))
|
||||
.OrderBy(r => r.SortOrder)
|
||||
.ToListAsync();
|
||||
_transformationService.Apply(records, rules);
|
||||
|
||||
UpdateStatus(site.Id, "Excel erstellen...");
|
||||
var outputDir = Path.Combine(AppContext.BaseDirectory, "output");
|
||||
var filePath = _excelService.CreateExcelFile(outputDir, site.TSC, DateTime.UtcNow.Date, records);
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
|
||||
if (spConfig is not null &&
|
||||
!string.IsNullOrWhiteSpace(spConfig.TenantId) &&
|
||||
!string.IsNullOrWhiteSpace(spConfig.ClientId) &&
|
||||
!string.IsNullOrWhiteSpace(spConfig.ClientSecret))
|
||||
{
|
||||
UpdateStatus(site.Id, "SharePoint Upload...");
|
||||
await _sharePointService.UploadAsync(
|
||||
spConfig.TenantId, spConfig.ClientId, spConfig.ClientSecret,
|
||||
spConfig.SiteUrl, spConfig.ExportFolder, site.Land, filePath);
|
||||
}
|
||||
|
||||
sw.Stop();
|
||||
log.Status = "OK";
|
||||
log.RowCount = records.Count;
|
||||
log.FileName = fileName;
|
||||
log.DurationSeconds = sw.Elapsed.TotalSeconds;
|
||||
|
||||
_logger.LogInformation("Export OK: {Land} ({TSC}) - {Rows} Zeilen in {Duration:F1}s",
|
||||
site.Land, site.TSC, records.Count, sw.Elapsed.TotalSeconds);
|
||||
|
||||
return new SiteExportResult(records, filePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
sw.Stop();
|
||||
log.Status = "Error";
|
||||
log.ErrorMessage = ex.Message;
|
||||
log.FileName = string.Empty;
|
||||
log.DurationSeconds = sw.Elapsed.TotalSeconds;
|
||||
|
||||
_logger.LogError(ex, "Export Fehler: {Land} ({TSC})", site.Land, site.TSC);
|
||||
return null;
|
||||
result = await _siteExportService.ExportAsync(site, status => UpdateStatus(site.Id, status));
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
using var db = await _dbFactory.CreateDbContextAsync();
|
||||
db.ExportLogs.Add(log);
|
||||
await db.SaveChangesAsync();
|
||||
if (result is not null)
|
||||
{
|
||||
await _exportLogService.WriteAsync(result.Log);
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
@@ -201,6 +113,4 @@ public class ExportOrchestrationService
|
||||
{
|
||||
OnExportStatusChanged?.Invoke();
|
||||
}
|
||||
|
||||
private sealed record SiteExportResult(List<SalesRecord> Records, string FilePath);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ using TrafagSalesExporter.Models;
|
||||
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
public class HanaQueryService
|
||||
public class HanaQueryService : IHanaQueryService
|
||||
{
|
||||
public List<SalesRecord> GetSalesRecords(HanaServer server,
|
||||
string schema, string tsc, string land, string dateFilter)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using TrafagSalesExporter.Models;
|
||||
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
public interface IConsolidatedExportService
|
||||
{
|
||||
Task<string?> ExportAsync(List<SalesRecord> records);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
public interface IDatabaseInitializationService
|
||||
{
|
||||
Task InitializeAsync();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using TrafagSalesExporter.Models;
|
||||
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
public interface IExcelExportService
|
||||
{
|
||||
string CreateExcelFile(string outputDirectory, string tsc, DateTime fileDate, List<SalesRecord> records);
|
||||
string CreateConsolidatedExcelFile(string outputDirectory, DateTime fileDate, List<SalesRecord> records);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using TrafagSalesExporter.Models;
|
||||
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
public interface IExportLogService
|
||||
{
|
||||
Task WriteAsync(ExportLog log);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using TrafagSalesExporter.Models;
|
||||
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
public interface IHanaQueryService
|
||||
{
|
||||
List<SalesRecord> GetSalesRecords(HanaServer server, string schema, string tsc, string land, string dateFilter);
|
||||
ConnectionTestResult TestConnectionDetailed(HanaServer server);
|
||||
void TestConnection(HanaServer server);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using TrafagSalesExporter.Models;
|
||||
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
public interface IRecordTransformationService
|
||||
{
|
||||
void Apply(List<SalesRecord> records, IEnumerable<FieldTransformationRule> rules);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
public interface ISharePointUploadService
|
||||
{
|
||||
Task UploadAsync(string tenantId, string clientId, string clientSecret, string siteUrl, string exportFolder, string land, string localFilePath);
|
||||
Task TestConnectionAsync(string tenantId, string clientId, string clientSecret, string siteUrl);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using TrafagSalesExporter.Models;
|
||||
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
public interface ISiteExportService
|
||||
{
|
||||
Task<SiteExportResult> ExportAsync(Site site, Action<string>? updateStatus = null);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
public interface ITransformationStrategy
|
||||
{
|
||||
string TransformationType { get; }
|
||||
object? Transform(object? sourceValue, string? argument);
|
||||
}
|
||||
@@ -3,12 +3,19 @@ using TrafagSalesExporter.Models;
|
||||
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
public class RecordTransformationService
|
||||
public class RecordTransformationService : IRecordTransformationService
|
||||
{
|
||||
private 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;
|
||||
|
||||
public RecordTransformationService(IEnumerable<ITransformationStrategy> strategies)
|
||||
{
|
||||
_strategies = strategies.ToDictionary(s => s.TransformationType, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public void Apply(List<SalesRecord> records, IEnumerable<FieldTransformationRule> rules)
|
||||
{
|
||||
var orderedRules = rules.Where(r => r.IsActive).OrderBy(r => r.SortOrder).ToList();
|
||||
@@ -23,37 +30,19 @@ public class RecordTransformationService
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyRule(SalesRecord record, FieldTransformationRule rule)
|
||||
private void ApplyRule(SalesRecord record, FieldTransformationRule rule)
|
||||
{
|
||||
if (!PropertyMap.TryGetValue(rule.SourceField, out var sourceProp)) return;
|
||||
if (!PropertyMap.TryGetValue(rule.TargetField, out var targetProp)) return;
|
||||
|
||||
var sourceValue = sourceProp.GetValue(record);
|
||||
object? result = rule.TransformationType switch
|
||||
{
|
||||
"Copy" => sourceValue,
|
||||
"Uppercase" => sourceValue?.ToString()?.ToUpperInvariant(),
|
||||
"Lowercase" => sourceValue?.ToString()?.ToLowerInvariant(),
|
||||
"Prefix" => $"{rule.Argument}{sourceValue}",
|
||||
"Suffix" => $"{sourceValue}{rule.Argument}",
|
||||
"Replace" => ApplyReplace(sourceValue?.ToString(), rule.Argument),
|
||||
"Constant" => rule.Argument,
|
||||
_ => sourceValue
|
||||
};
|
||||
object? result = _strategies.TryGetValue(rule.TransformationType, out var strategy)
|
||||
? strategy.Transform(sourceValue, rule.Argument)
|
||||
: sourceValue;
|
||||
|
||||
SetPropertyValue(record, targetProp, result);
|
||||
}
|
||||
|
||||
private static string ApplyReplace(string? input, string? argument)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input)) return string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(argument)) return input;
|
||||
|
||||
var parts = argument.Split("=>", 2, StringSplitOptions.TrimEntries);
|
||||
if (parts.Length != 2) return input;
|
||||
return input.Replace(parts[0], parts[1], StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static void SetPropertyValue(SalesRecord record, PropertyInfo property, object? value)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -3,7 +3,7 @@ using Microsoft.Graph;
|
||||
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
public class SharePointUploadService
|
||||
public class SharePointUploadService : ISharePointUploadService
|
||||
{
|
||||
public async Task UploadAsync(string tenantId, string clientId, string clientSecret,
|
||||
string siteUrl, string exportFolder, string land, string localFilePath)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using TrafagSalesExporter.Models;
|
||||
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
public sealed class SiteExportResult
|
||||
{
|
||||
public required List<SalesRecord> Records { get; init; }
|
||||
public required ExportLog Log { get; init; }
|
||||
public string? FilePath { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Diagnostics;
|
||||
using TrafagSalesExporter.Data;
|
||||
using TrafagSalesExporter.Models;
|
||||
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
public class SiteExportService : ISiteExportService
|
||||
{
|
||||
private readonly IDbContextFactory<AppDbContext> _dbFactory;
|
||||
private readonly IHanaQueryService _hanaService;
|
||||
private readonly IExcelExportService _excelService;
|
||||
private readonly ISharePointUploadService _sharePointService;
|
||||
private readonly IRecordTransformationService _transformationService;
|
||||
private readonly ILogger<SiteExportService> _logger;
|
||||
|
||||
public SiteExportService(
|
||||
IDbContextFactory<AppDbContext> dbFactory,
|
||||
IHanaQueryService hanaService,
|
||||
IExcelExportService excelService,
|
||||
ISharePointUploadService sharePointService,
|
||||
IRecordTransformationService transformationService,
|
||||
ILogger<SiteExportService> logger)
|
||||
{
|
||||
_dbFactory = dbFactory;
|
||||
_hanaService = hanaService;
|
||||
_excelService = excelService;
|
||||
_sharePointService = sharePointService;
|
||||
_transformationService = transformationService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<SiteExportResult> ExportAsync(Site site, Action<string>? updateStatus = null)
|
||||
{
|
||||
if (site.HanaServer is null)
|
||||
throw new InvalidOperationException($"Standort '{site.Land}' hat keinen HANA-Server.");
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
var log = new ExportLog
|
||||
{
|
||||
Timestamp = DateTime.Now,
|
||||
SiteId = site.Id,
|
||||
Land = site.Land,
|
||||
TSC = site.TSC
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using var db = await _dbFactory.CreateDbContextAsync();
|
||||
var settings = await db.ExportSettings.FirstOrDefaultAsync() ?? new ExportSettings();
|
||||
var spConfig = await db.SharePointConfigs.FirstOrDefaultAsync();
|
||||
|
||||
updateStatus?.Invoke("HANA Abfrage...");
|
||||
var records = await Task.Run(() => _hanaService.GetSalesRecords(
|
||||
site.HanaServer, site.Schema, site.TSC, site.Land, settings.DateFilter));
|
||||
|
||||
updateStatus?.Invoke("Transformationen anwenden...");
|
||||
var rules = await db.FieldTransformationRules
|
||||
.Where(r => r.IsActive && r.SourceSystem == (string.IsNullOrWhiteSpace(site.SourceSystem) ? "SAP" : site.SourceSystem))
|
||||
.OrderBy(r => r.SortOrder)
|
||||
.ToListAsync();
|
||||
_transformationService.Apply(records, rules);
|
||||
|
||||
updateStatus?.Invoke("Excel erstellen...");
|
||||
var outputDir = Path.Combine(AppContext.BaseDirectory, "output");
|
||||
var filePath = _excelService.CreateExcelFile(outputDir, site.TSC, DateTime.UtcNow.Date, records);
|
||||
var fileName = Path.GetFileName(filePath);
|
||||
|
||||
if (spConfig is not null &&
|
||||
!string.IsNullOrWhiteSpace(spConfig.TenantId) &&
|
||||
!string.IsNullOrWhiteSpace(spConfig.ClientId) &&
|
||||
!string.IsNullOrWhiteSpace(spConfig.ClientSecret))
|
||||
{
|
||||
updateStatus?.Invoke("SharePoint Upload...");
|
||||
await _sharePointService.UploadAsync(
|
||||
spConfig.TenantId, spConfig.ClientId, spConfig.ClientSecret,
|
||||
spConfig.SiteUrl, spConfig.ExportFolder, site.Land, filePath);
|
||||
}
|
||||
|
||||
sw.Stop();
|
||||
log.Status = "OK";
|
||||
log.RowCount = records.Count;
|
||||
log.FileName = fileName;
|
||||
log.DurationSeconds = sw.Elapsed.TotalSeconds;
|
||||
|
||||
_logger.LogInformation("Export OK: {Land} ({TSC}) - {Rows} Zeilen in {Duration:F1}s",
|
||||
site.Land, site.TSC, records.Count, sw.Elapsed.TotalSeconds);
|
||||
|
||||
return new SiteExportResult
|
||||
{
|
||||
Records = records,
|
||||
Log = log,
|
||||
FilePath = filePath
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
sw.Stop();
|
||||
log.Status = "Error";
|
||||
log.ErrorMessage = ex.Message;
|
||||
log.FileName = string.Empty;
|
||||
log.DurationSeconds = sw.Elapsed.TotalSeconds;
|
||||
|
||||
_logger.LogError(ex, "Export Fehler: {Land} ({TSC})", site.Land, site.TSC);
|
||||
|
||||
return new SiteExportResult
|
||||
{
|
||||
Records = [],
|
||||
Log = log,
|
||||
FilePath = null
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
namespace TrafagSalesExporter.Services;
|
||||
|
||||
public sealed class CopyTransformationStrategy : ITransformationStrategy
|
||||
{
|
||||
public string TransformationType => "Copy";
|
||||
public object? Transform(object? sourceValue, string? argument) => sourceValue;
|
||||
}
|
||||
|
||||
public sealed class UppercaseTransformationStrategy : ITransformationStrategy
|
||||
{
|
||||
public string TransformationType => "Uppercase";
|
||||
public object? Transform(object? sourceValue, string? argument) => sourceValue?.ToString()?.ToUpperInvariant();
|
||||
}
|
||||
|
||||
public sealed class LowercaseTransformationStrategy : ITransformationStrategy
|
||||
{
|
||||
public string TransformationType => "Lowercase";
|
||||
public object? Transform(object? sourceValue, string? argument) => sourceValue?.ToString()?.ToLowerInvariant();
|
||||
}
|
||||
|
||||
public sealed class PrefixTransformationStrategy : ITransformationStrategy
|
||||
{
|
||||
public string TransformationType => "Prefix";
|
||||
public object? Transform(object? sourceValue, string? argument) => $"{argument}{sourceValue}";
|
||||
}
|
||||
|
||||
public sealed class SuffixTransformationStrategy : ITransformationStrategy
|
||||
{
|
||||
public string TransformationType => "Suffix";
|
||||
public object? Transform(object? sourceValue, string? argument) => $"{sourceValue}{argument}";
|
||||
}
|
||||
|
||||
public sealed class ReplaceTransformationStrategy : ITransformationStrategy
|
||||
{
|
||||
public string TransformationType => "Replace";
|
||||
|
||||
public object? Transform(object? sourceValue, string? argument)
|
||||
{
|
||||
var input = sourceValue?.ToString();
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(argument))
|
||||
return input;
|
||||
|
||||
var parts = argument.Split("=>", 2, StringSplitOptions.TrimEntries);
|
||||
if (parts.Length != 2)
|
||||
return input;
|
||||
|
||||
return input.Replace(parts[0], parts[1], StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ConstantTransformationStrategy : ITransformationStrategy
|
||||
{
|
||||
public string TransformationType => "Constant";
|
||||
public object? Transform(object? sourceValue, string? argument) => argument;
|
||||
}
|
||||
Reference in New Issue
Block a user