DataSourceAdapter-Pattern + SiteExportService schlanker + Page-Services Scoped
- IDataSourceAdapter mit 3 Implementierungen (HANA, SAP_GATEWAY, MANUAL_EXCEL) und DataSourceAdapterResolver ersetzen das if/else auf ConnectionKind. - SiteExportService von 338 auf 187 Zeilen reduziert: Pipeline Resolve -> Fetch -> Transform -> Excel -> Central -> SharePoint. - Page-Services auf Scoped (per Blazor-Circuit); Orchestrator bleibt Singleton fuer geteilten Export-Status.
This commit is contained in:
@@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using MudBlazor.Services;
|
using MudBlazor.Services;
|
||||||
using TrafagSalesExporter.Data;
|
using TrafagSalesExporter.Data;
|
||||||
using TrafagSalesExporter.Services;
|
using TrafagSalesExporter.Services;
|
||||||
|
using TrafagSalesExporter.Services.DataSources;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ builder.Services.AddHttpClient(nameof(ExchangeRateImportService));
|
|||||||
builder.Services.AddDbContextFactory<AppDbContext>(options =>
|
builder.Services.AddDbContextFactory<AppDbContext>(options =>
|
||||||
options.UseSqlite("Data Source=trafag_exporter.db;Default Timeout=60"));
|
options.UseSqlite("Data Source=trafag_exporter.db;Default Timeout=60"));
|
||||||
|
|
||||||
|
// Stateless Infrastruktur- und Connector-Services: Singleton.
|
||||||
builder.Services.AddSingleton<IHanaQueryService, HanaQueryService>();
|
builder.Services.AddSingleton<IHanaQueryService, HanaQueryService>();
|
||||||
builder.Services.AddSingleton<IExcelExportService, ExcelExportService>();
|
builder.Services.AddSingleton<IExcelExportService, ExcelExportService>();
|
||||||
builder.Services.AddSingleton<ISharePointUploadService, SharePointUploadService>();
|
builder.Services.AddSingleton<ISharePointUploadService, SharePointUploadService>();
|
||||||
@@ -36,7 +38,6 @@ builder.Services.AddSingleton<IRecordTransformationService, RecordTransformation
|
|||||||
builder.Services.AddSingleton<IAppEventLogService, AppEventLogService>();
|
builder.Services.AddSingleton<IAppEventLogService, AppEventLogService>();
|
||||||
builder.Services.AddSingleton<IManagementCockpitService, ManagementCockpitService>();
|
builder.Services.AddSingleton<IManagementCockpitService, ManagementCockpitService>();
|
||||||
builder.Services.AddSingleton<IManualExcelImportService, ManualExcelImportService>();
|
builder.Services.AddSingleton<IManualExcelImportService, ManualExcelImportService>();
|
||||||
builder.Services.AddSingleton<ISiteExportService, SiteExportService>();
|
|
||||||
builder.Services.AddSingleton<IConsolidatedExportService, ConsolidatedExportService>();
|
builder.Services.AddSingleton<IConsolidatedExportService, ConsolidatedExportService>();
|
||||||
builder.Services.AddSingleton<IExportLogService, ExportLogService>();
|
builder.Services.AddSingleton<IExportLogService, ExportLogService>();
|
||||||
builder.Services.AddSingleton<ICentralSalesRecordService, CentralSalesRecordService>();
|
builder.Services.AddSingleton<ICentralSalesRecordService, CentralSalesRecordService>();
|
||||||
@@ -44,18 +45,29 @@ builder.Services.AddSingleton<IConfigTransferService, ConfigTransferService>();
|
|||||||
builder.Services.AddSingleton<IDatabaseSchemaMaintenanceService, DatabaseSchemaMaintenanceService>();
|
builder.Services.AddSingleton<IDatabaseSchemaMaintenanceService, DatabaseSchemaMaintenanceService>();
|
||||||
builder.Services.AddSingleton<IDatabaseSeedService, DatabaseSeedService>();
|
builder.Services.AddSingleton<IDatabaseSeedService, DatabaseSeedService>();
|
||||||
builder.Services.AddSingleton<IDatabaseInitializationService, DatabaseInitializationService>();
|
builder.Services.AddSingleton<IDatabaseInitializationService, DatabaseInitializationService>();
|
||||||
builder.Services.AddSingleton<ISettingsPageService, SettingsPageService>();
|
|
||||||
builder.Services.AddSingleton<IStandortePageService, StandortePageService>();
|
|
||||||
builder.Services.AddSingleton<IStandorteSapEditorService, StandorteSapEditorService>();
|
|
||||||
builder.Services.AddSingleton<IManagementCockpitPageService, ManagementCockpitPageService>();
|
|
||||||
builder.Services.AddSingleton<IDashboardPageService, DashboardPageService>();
|
|
||||||
builder.Services.AddSingleton<ILogsPageService, LogsPageService>();
|
|
||||||
builder.Services.AddSingleton<ITransformationsPageService, TransformationsPageService>();
|
|
||||||
builder.Services.AddSingleton<IUiTextService, UiTextService>();
|
builder.Services.AddSingleton<IUiTextService, UiTextService>();
|
||||||
|
|
||||||
|
// Datenquellen-Adapter (Strategy per ConnectionKind).
|
||||||
|
builder.Services.AddSingleton<IDataSourceAdapter, HanaDataSourceAdapter>();
|
||||||
|
builder.Services.AddSingleton<IDataSourceAdapter, SapGatewayDataSourceAdapter>();
|
||||||
|
builder.Services.AddSingleton<IDataSourceAdapter, ManualExcelDataSourceAdapter>();
|
||||||
|
builder.Services.AddSingleton<IDataSourceAdapterResolver, DataSourceAdapterResolver>();
|
||||||
|
builder.Services.AddSingleton<ISiteExportService, SiteExportService>();
|
||||||
|
|
||||||
|
// Orchestrator mit gemeinsamem Status ueber alle Circuits.
|
||||||
builder.Services.AddSingleton<ExportOrchestrationService>();
|
builder.Services.AddSingleton<ExportOrchestrationService>();
|
||||||
builder.Services.AddSingleton<TimerBackgroundService>();
|
builder.Services.AddSingleton<TimerBackgroundService>();
|
||||||
builder.Services.AddHostedService(sp => sp.GetRequiredService<TimerBackgroundService>());
|
builder.Services.AddHostedService(sp => sp.GetRequiredService<TimerBackgroundService>());
|
||||||
|
|
||||||
|
// UI-/Page-Services: Scoped = pro Blazor-Circuit.
|
||||||
|
builder.Services.AddScoped<ISettingsPageService, SettingsPageService>();
|
||||||
|
builder.Services.AddScoped<IStandortePageService, StandortePageService>();
|
||||||
|
builder.Services.AddScoped<IStandorteSapEditorService, StandorteSapEditorService>();
|
||||||
|
builder.Services.AddScoped<IManagementCockpitPageService, ManagementCockpitPageService>();
|
||||||
|
builder.Services.AddScoped<IDashboardPageService, DashboardPageService>();
|
||||||
|
builder.Services.AddScoped<ILogsPageService, LogsPageService>();
|
||||||
|
builder.Services.AddScoped<ITransformationsPageService, TransformationsPageService>();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
using (var scope = app.Services.CreateScope())
|
using (var scope = app.Services.CreateScope())
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using TrafagSalesExporter.Models;
|
||||||
|
|
||||||
|
namespace TrafagSalesExporter.Services.DataSources;
|
||||||
|
|
||||||
|
public sealed class DataSourceAdapterResolver : IDataSourceAdapterResolver
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, IDataSourceAdapter> _adapters;
|
||||||
|
|
||||||
|
public DataSourceAdapterResolver(IEnumerable<IDataSourceAdapter> adapters)
|
||||||
|
{
|
||||||
|
_adapters = adapters.ToDictionary(
|
||||||
|
a => a.ConnectionKind,
|
||||||
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDataSourceAdapter Resolve(string connectionKind)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(connectionKind))
|
||||||
|
connectionKind = SourceSystemConnectionKinds.Hana;
|
||||||
|
|
||||||
|
if (_adapters.TryGetValue(connectionKind, out var adapter))
|
||||||
|
return adapter;
|
||||||
|
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Kein DataSourceAdapter fuer ConnectionKind '{connectionKind}' registriert.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using TrafagSalesExporter.Models;
|
||||||
|
|
||||||
|
namespace TrafagSalesExporter.Services.DataSources;
|
||||||
|
|
||||||
|
internal static class DataSourceCredentials
|
||||||
|
{
|
||||||
|
public static (string Username, string Password) Resolve(Site site, SourceSystemDefinition sourceDefinition)
|
||||||
|
=> (FirstNonEmpty(site.UsernameOverride, sourceDefinition.CentralUsername),
|
||||||
|
FirstNonEmpty(site.PasswordOverride, sourceDefinition.CentralPassword));
|
||||||
|
|
||||||
|
public static string ResolveSapServiceUrl(Site site, SourceSystemDefinition sourceDefinition)
|
||||||
|
=> FirstNonEmpty(site.SapServiceUrl, sourceDefinition.CentralServiceUrl);
|
||||||
|
|
||||||
|
public static string FirstNonEmpty(params string[] values)
|
||||||
|
{
|
||||||
|
foreach (var value in values)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(value))
|
||||||
|
return value.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using TrafagSalesExporter.Models;
|
||||||
|
|
||||||
|
namespace TrafagSalesExporter.Services.DataSources;
|
||||||
|
|
||||||
|
public sealed class DataSourceFetchContext
|
||||||
|
{
|
||||||
|
public required Site Site { get; init; }
|
||||||
|
public required SourceSystemDefinition SourceDefinition { get; init; }
|
||||||
|
public required ExportSettings Settings { get; init; }
|
||||||
|
public SharePointConfig? SharePointConfig { get; init; }
|
||||||
|
public Action<string>? UpdateStatus { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using TrafagSalesExporter.Models;
|
||||||
|
|
||||||
|
namespace TrafagSalesExporter.Services.DataSources;
|
||||||
|
|
||||||
|
public sealed class DataSourceFetchResult
|
||||||
|
{
|
||||||
|
public required List<SalesRecord> Records { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Wenn gesetzt, liefert der Adapter bereits eine Referenz-Datei (z. B. manueller Excel-Import).
|
||||||
|
/// SiteExportService erzeugt dann keine neue Excel-Datei.
|
||||||
|
/// </summary>
|
||||||
|
public string? ReferenceFilePath { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using TrafagSalesExporter.Data;
|
||||||
|
using TrafagSalesExporter.Models;
|
||||||
|
|
||||||
|
namespace TrafagSalesExporter.Services.DataSources;
|
||||||
|
|
||||||
|
public sealed class HanaDataSourceAdapter : IDataSourceAdapter
|
||||||
|
{
|
||||||
|
private readonly IDbContextFactory<AppDbContext> _dbFactory;
|
||||||
|
private readonly IHanaQueryService _hanaService;
|
||||||
|
private readonly IAppEventLogService _appEventLogService;
|
||||||
|
|
||||||
|
public HanaDataSourceAdapter(
|
||||||
|
IDbContextFactory<AppDbContext> dbFactory,
|
||||||
|
IHanaQueryService hanaService,
|
||||||
|
IAppEventLogService appEventLogService)
|
||||||
|
{
|
||||||
|
_dbFactory = dbFactory;
|
||||||
|
_hanaService = hanaService;
|
||||||
|
_appEventLogService = appEventLogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ConnectionKind => SourceSystemConnectionKinds.Hana;
|
||||||
|
|
||||||
|
public async Task<DataSourceFetchResult> FetchAsync(DataSourceFetchContext context)
|
||||||
|
{
|
||||||
|
var site = context.Site;
|
||||||
|
var sourceDefinition = context.SourceDefinition;
|
||||||
|
|
||||||
|
using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
|
var exportServer = await BuildEffectiveServerAsync(db, site, sourceDefinition);
|
||||||
|
|
||||||
|
context.UpdateStatus?.Invoke("HANA Abfrage...");
|
||||||
|
await _appEventLogService.WriteAsync("Export", "HANA Abfrage gestartet",
|
||||||
|
siteId: site.Id, land: site.Land,
|
||||||
|
details: exportServer.GetConnectionStringPreview());
|
||||||
|
|
||||||
|
var records = await Task.Run(() => _hanaService.GetSalesRecords(
|
||||||
|
exportServer, site.Schema, site.TSC, site.Land, context.Settings.DateFilter));
|
||||||
|
|
||||||
|
return new DataSourceFetchResult { Records = records };
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<HanaServer> BuildEffectiveServerAsync(
|
||||||
|
AppDbContext db, Site site, SourceSystemDefinition sourceDefinition)
|
||||||
|
{
|
||||||
|
var centralServer = await db.HanaServers
|
||||||
|
.AsNoTracking()
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.FirstOrDefaultAsync(x => x.SourceSystem == sourceDefinition.Code)
|
||||||
|
?? throw new InvalidOperationException(
|
||||||
|
$"Fuer Quellsystem '{sourceDefinition.Code}' ist keine zentrale HANA-Konfiguration vorhanden.");
|
||||||
|
|
||||||
|
var credentials = DataSourceCredentials.Resolve(site, sourceDefinition);
|
||||||
|
|
||||||
|
return new HanaServer
|
||||||
|
{
|
||||||
|
Id = centralServer.Id,
|
||||||
|
SourceSystem = centralServer.SourceSystem,
|
||||||
|
Name = centralServer.Name,
|
||||||
|
Host = centralServer.Host,
|
||||||
|
Port = centralServer.Port,
|
||||||
|
Username = credentials.Username,
|
||||||
|
Password = credentials.Password,
|
||||||
|
DatabaseName = centralServer.DatabaseName,
|
||||||
|
UseSsl = centralServer.UseSsl,
|
||||||
|
ValidateCertificate = centralServer.ValidateCertificate,
|
||||||
|
AdditionalParams = centralServer.AdditionalParams
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
namespace TrafagSalesExporter.Services.DataSources;
|
||||||
|
|
||||||
|
public interface IDataSourceAdapter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Der Wert aus <see cref="Models.SourceSystemConnectionKinds"/>, den dieser Adapter behandelt.
|
||||||
|
/// </summary>
|
||||||
|
string ConnectionKind { get; }
|
||||||
|
|
||||||
|
Task<DataSourceFetchResult> FetchAsync(DataSourceFetchContext context);
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace TrafagSalesExporter.Services.DataSources;
|
||||||
|
|
||||||
|
public interface IDataSourceAdapterResolver
|
||||||
|
{
|
||||||
|
IDataSourceAdapter Resolve(string connectionKind);
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
using TrafagSalesExporter.Models;
|
||||||
|
|
||||||
|
namespace TrafagSalesExporter.Services.DataSources;
|
||||||
|
|
||||||
|
public sealed class ManualExcelDataSourceAdapter : IDataSourceAdapter
|
||||||
|
{
|
||||||
|
private readonly ISharePointUploadService _sharePointService;
|
||||||
|
private readonly IManualExcelImportService _manualExcelImportService;
|
||||||
|
private readonly IAppEventLogService _appEventLogService;
|
||||||
|
|
||||||
|
public ManualExcelDataSourceAdapter(
|
||||||
|
ISharePointUploadService sharePointService,
|
||||||
|
IManualExcelImportService manualExcelImportService,
|
||||||
|
IAppEventLogService appEventLogService)
|
||||||
|
{
|
||||||
|
_sharePointService = sharePointService;
|
||||||
|
_manualExcelImportService = manualExcelImportService;
|
||||||
|
_appEventLogService = appEventLogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ConnectionKind => SourceSystemConnectionKinds.ManualExcel;
|
||||||
|
|
||||||
|
public async Task<DataSourceFetchResult> FetchAsync(DataSourceFetchContext context)
|
||||||
|
{
|
||||||
|
var site = context.Site;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(site.ManualImportFilePath))
|
||||||
|
throw new InvalidOperationException($"Standort '{site.Land}' hat keine manuelle Excel-Datei.");
|
||||||
|
|
||||||
|
var manualImportPath = site.ManualImportFilePath.Trim();
|
||||||
|
string filePath;
|
||||||
|
string? tempManualImportPath = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(manualImportPath))
|
||||||
|
{
|
||||||
|
filePath = manualImportPath;
|
||||||
|
}
|
||||||
|
else if (LooksLikeSharePointReference(manualImportPath))
|
||||||
|
{
|
||||||
|
var spConfig = context.SharePointConfig
|
||||||
|
?? throw new InvalidOperationException(
|
||||||
|
"Fuer SharePoint-Manuellimport fehlt eine vollstaendige SharePoint-Konfiguration in Settings.");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(spConfig.TenantId) ||
|
||||||
|
string.IsNullOrWhiteSpace(spConfig.ClientId) ||
|
||||||
|
string.IsNullOrWhiteSpace(spConfig.ClientSecret) ||
|
||||||
|
string.IsNullOrWhiteSpace(spConfig.SiteUrl))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
"Fuer SharePoint-Manuellimport fehlt eine vollstaendige SharePoint-Konfiguration in Settings.");
|
||||||
|
}
|
||||||
|
|
||||||
|
context.UpdateStatus?.Invoke("Manuelle Excel von SharePoint laden...");
|
||||||
|
await _appEventLogService.WriteAsync("Export", "Manuelle Excel von SharePoint laden",
|
||||||
|
siteId: site.Id, land: site.Land, details: manualImportPath);
|
||||||
|
|
||||||
|
tempManualImportPath = await _sharePointService.DownloadToTempFileAsync(
|
||||||
|
spConfig.TenantId, spConfig.ClientId, spConfig.ClientSecret,
|
||||||
|
spConfig.SiteUrl, manualImportPath);
|
||||||
|
filePath = manualImportPath;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Die manuelle Excel-Datei wurde nicht gefunden: {manualImportPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var readPath = tempManualImportPath ?? filePath;
|
||||||
|
context.UpdateStatus?.Invoke("Manuelle Excel lesen...");
|
||||||
|
await _appEventLogService.WriteAsync("Export", "Manuelle Excel lesen",
|
||||||
|
siteId: site.Id, land: site.Land, details: filePath);
|
||||||
|
|
||||||
|
var records = await _manualExcelImportService.ReadSalesRecordsAsync(readPath, site);
|
||||||
|
return new DataSourceFetchResult
|
||||||
|
{
|
||||||
|
Records = records,
|
||||||
|
ReferenceFilePath = filePath
|
||||||
|
};
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(tempManualImportPath) && File.Exists(tempManualImportPath))
|
||||||
|
File.Delete(tempManualImportPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool LooksLikeSharePointReference(string path)
|
||||||
|
=> path.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
path.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
path.StartsWith("/Shared Documents/", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
path.StartsWith("Shared Documents/", StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using TrafagSalesExporter.Data;
|
||||||
|
using TrafagSalesExporter.Models;
|
||||||
|
|
||||||
|
namespace TrafagSalesExporter.Services.DataSources;
|
||||||
|
|
||||||
|
public sealed class SapGatewayDataSourceAdapter : IDataSourceAdapter
|
||||||
|
{
|
||||||
|
private readonly IDbContextFactory<AppDbContext> _dbFactory;
|
||||||
|
private readonly ISapCompositionService _sapCompositionService;
|
||||||
|
private readonly IAppEventLogService _appEventLogService;
|
||||||
|
|
||||||
|
public SapGatewayDataSourceAdapter(
|
||||||
|
IDbContextFactory<AppDbContext> dbFactory,
|
||||||
|
ISapCompositionService sapCompositionService,
|
||||||
|
IAppEventLogService appEventLogService)
|
||||||
|
{
|
||||||
|
_dbFactory = dbFactory;
|
||||||
|
_sapCompositionService = sapCompositionService;
|
||||||
|
_appEventLogService = appEventLogService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ConnectionKind => SourceSystemConnectionKinds.SapGateway;
|
||||||
|
|
||||||
|
public async Task<DataSourceFetchResult> FetchAsync(DataSourceFetchContext context)
|
||||||
|
{
|
||||||
|
var site = context.Site;
|
||||||
|
var sourceDefinition = context.SourceDefinition;
|
||||||
|
|
||||||
|
var credentials = DataSourceCredentials.Resolve(site, sourceDefinition);
|
||||||
|
var sapServiceUrl = DataSourceCredentials.ResolveSapServiceUrl(site, sourceDefinition);
|
||||||
|
if (string.IsNullOrWhiteSpace(sapServiceUrl))
|
||||||
|
throw new InvalidOperationException($"Standort '{site.Land}' hat keine SAP Service URL.");
|
||||||
|
|
||||||
|
using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
|
var sapSources = await db.SapSourceDefinitions.Where(s => s.SiteId == site.Id).ToListAsync();
|
||||||
|
var sapJoins = await db.SapJoinDefinitions.Where(j => j.SiteId == site.Id).ToListAsync();
|
||||||
|
var sapMappings = await db.SapFieldMappings.Where(m => m.SiteId == site.Id).ToListAsync();
|
||||||
|
|
||||||
|
if (sapSources.Count == 0)
|
||||||
|
throw new InvalidOperationException($"Standort '{site.Land}' hat keine SAP-Quellen konfiguriert.");
|
||||||
|
if (sapMappings.Count == 0)
|
||||||
|
throw new InvalidOperationException($"Standort '{site.Land}' hat keine SAP-Feldmappings.");
|
||||||
|
|
||||||
|
context.UpdateStatus?.Invoke("SAP Quellen laden...");
|
||||||
|
await _appEventLogService.WriteAsync("Export", "SAP Quellen laden",
|
||||||
|
siteId: site.Id, land: site.Land,
|
||||||
|
details: $"Sources={sapSources.Count} | Mappings={sapMappings.Count}");
|
||||||
|
|
||||||
|
var effectiveSite = CloneSiteWithSapServiceUrl(site, sapServiceUrl);
|
||||||
|
var records = await _sapCompositionService.BuildSalesRecordsAsync(
|
||||||
|
effectiveSite, sapSources, sapJoins, sapMappings,
|
||||||
|
credentials.Username, credentials.Password);
|
||||||
|
|
||||||
|
return new DataSourceFetchResult { Records = records };
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Site CloneSiteWithSapServiceUrl(Site site, string sapServiceUrl)
|
||||||
|
{
|
||||||
|
return new Site
|
||||||
|
{
|
||||||
|
Id = site.Id,
|
||||||
|
HanaServerId = site.HanaServerId,
|
||||||
|
HanaServer = site.HanaServer,
|
||||||
|
Schema = site.Schema,
|
||||||
|
TSC = site.TSC,
|
||||||
|
Land = site.Land,
|
||||||
|
SourceSystem = site.SourceSystem,
|
||||||
|
UsernameOverride = site.UsernameOverride,
|
||||||
|
PasswordOverride = site.PasswordOverride,
|
||||||
|
LocalExportFolderOverride = site.LocalExportFolderOverride,
|
||||||
|
ManualImportFilePath = site.ManualImportFilePath,
|
||||||
|
ManualImportLastUploadedAtUtc = site.ManualImportLastUploadedAtUtc,
|
||||||
|
SapServiceUrl = sapServiceUrl,
|
||||||
|
SapEntitySet = site.SapEntitySet,
|
||||||
|
SapEntitySetsCache = site.SapEntitySetsCache,
|
||||||
|
SapEntitySetsRefreshedAtUtc = site.SapEntitySetsRefreshedAtUtc,
|
||||||
|
IsActive = site.IsActive
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,45 +2,37 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using TrafagSalesExporter.Data;
|
using TrafagSalesExporter.Data;
|
||||||
using TrafagSalesExporter.Models;
|
using TrafagSalesExporter.Models;
|
||||||
|
using TrafagSalesExporter.Services.DataSources;
|
||||||
|
|
||||||
namespace TrafagSalesExporter.Services;
|
namespace TrafagSalesExporter.Services;
|
||||||
|
|
||||||
public class SiteExportService : ISiteExportService
|
public class SiteExportService : ISiteExportService
|
||||||
{
|
{
|
||||||
private readonly IDbContextFactory<AppDbContext> _dbFactory;
|
private readonly IDbContextFactory<AppDbContext> _dbFactory;
|
||||||
private readonly IHanaQueryService _hanaService;
|
private readonly IDataSourceAdapterResolver _dataSourceResolver;
|
||||||
private readonly ISapGatewayService _sapGatewayService;
|
|
||||||
private readonly ISapCompositionService _sapCompositionService;
|
|
||||||
private readonly IExcelExportService _excelService;
|
private readonly IExcelExportService _excelService;
|
||||||
private readonly ISharePointUploadService _sharePointService;
|
private readonly ISharePointUploadService _sharePointService;
|
||||||
private readonly IRecordTransformationService _transformationService;
|
private readonly IRecordTransformationService _transformationService;
|
||||||
private readonly ICentralSalesRecordService _centralSalesRecordService;
|
private readonly ICentralSalesRecordService _centralSalesRecordService;
|
||||||
private readonly IManualExcelImportService _manualExcelImportService;
|
|
||||||
private readonly IAppEventLogService _appEventLogService;
|
private readonly IAppEventLogService _appEventLogService;
|
||||||
private readonly ILogger<SiteExportService> _logger;
|
private readonly ILogger<SiteExportService> _logger;
|
||||||
|
|
||||||
public SiteExportService(
|
public SiteExportService(
|
||||||
IDbContextFactory<AppDbContext> dbFactory,
|
IDbContextFactory<AppDbContext> dbFactory,
|
||||||
IHanaQueryService hanaService,
|
IDataSourceAdapterResolver dataSourceResolver,
|
||||||
ISapGatewayService sapGatewayService,
|
|
||||||
ISapCompositionService sapCompositionService,
|
|
||||||
IExcelExportService excelService,
|
IExcelExportService excelService,
|
||||||
ISharePointUploadService sharePointService,
|
ISharePointUploadService sharePointService,
|
||||||
IRecordTransformationService transformationService,
|
IRecordTransformationService transformationService,
|
||||||
ICentralSalesRecordService centralSalesRecordService,
|
ICentralSalesRecordService centralSalesRecordService,
|
||||||
IManualExcelImportService manualExcelImportService,
|
|
||||||
IAppEventLogService appEventLogService,
|
IAppEventLogService appEventLogService,
|
||||||
ILogger<SiteExportService> logger)
|
ILogger<SiteExportService> logger)
|
||||||
{
|
{
|
||||||
_dbFactory = dbFactory;
|
_dbFactory = dbFactory;
|
||||||
_hanaService = hanaService;
|
_dataSourceResolver = dataSourceResolver;
|
||||||
_sapGatewayService = sapGatewayService;
|
|
||||||
_sapCompositionService = sapCompositionService;
|
|
||||||
_excelService = excelService;
|
_excelService = excelService;
|
||||||
_sharePointService = sharePointService;
|
_sharePointService = sharePointService;
|
||||||
_transformationService = transformationService;
|
_transformationService = transformationService;
|
||||||
_centralSalesRecordService = centralSalesRecordService;
|
_centralSalesRecordService = centralSalesRecordService;
|
||||||
_manualExcelImportService = manualExcelImportService;
|
|
||||||
_appEventLogService = appEventLogService;
|
_appEventLogService = appEventLogService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
}
|
}
|
||||||
@@ -58,167 +50,63 @@ public class SiteExportService : ISiteExportService
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _appEventLogService.WriteAsync("Export", "Export gestartet", siteId: site.Id, land: site.Land,
|
|
||||||
details: $"Quelle={NormalizeSourceSystem(site.SourceSystem)} | TSC={site.TSC}");
|
|
||||||
using var db = await _dbFactory.CreateDbContextAsync();
|
|
||||||
var settings = await db.ExportSettings.FirstOrDefaultAsync() ?? new ExportSettings();
|
|
||||||
var spConfig = await db.SharePointConfigs.FirstOrDefaultAsync();
|
|
||||||
var outputDir = ResolveSiteOutputDirectory(settings, site);
|
|
||||||
var sourceSystem = NormalizeSourceSystem(site.SourceSystem);
|
var sourceSystem = NormalizeSourceSystem(site.SourceSystem);
|
||||||
var sourceDefinition = await db.SourceSystemDefinitions
|
await _appEventLogService.WriteAsync("Export", "Export gestartet",
|
||||||
.AsNoTracking()
|
siteId: site.Id, land: site.Land,
|
||||||
.OrderBy(x => x.Id)
|
details: $"Quelle={sourceSystem} | TSC={site.TSC}");
|
||||||
.FirstOrDefaultAsync(x => x.Code == sourceSystem)
|
|
||||||
?? throw new InvalidOperationException($"Quellsystem '{sourceSystem}' ist nicht konfiguriert.");
|
|
||||||
var records = new List<SalesRecord>();
|
|
||||||
string filePath;
|
|
||||||
|
|
||||||
if (string.Equals(sourceDefinition.ConnectionKind, SourceSystemConnectionKinds.SapGateway, StringComparison.OrdinalIgnoreCase))
|
var (settings, spConfig, sourceDefinition, rules) = await LoadExportConfigAsync(site, sourceSystem);
|
||||||
|
var outputDir = ResolveSiteOutputDirectory(settings, site);
|
||||||
|
|
||||||
|
var adapter = _dataSourceResolver.Resolve(sourceDefinition.ConnectionKind);
|
||||||
|
var fetchResult = await adapter.FetchAsync(new DataSourceFetchContext
|
||||||
{
|
{
|
||||||
var credentials = ResolveCredentials(site, sourceDefinition);
|
Site = site,
|
||||||
var sapServiceUrl = ResolveSapServiceUrl(site, sourceDefinition);
|
SourceDefinition = sourceDefinition,
|
||||||
if (string.IsNullOrWhiteSpace(sapServiceUrl))
|
Settings = settings,
|
||||||
throw new InvalidOperationException($"Standort '{site.Land}' hat keine SAP Service URL.");
|
SharePointConfig = spConfig,
|
||||||
var sapSources = await db.SapSourceDefinitions.Where(s => s.SiteId == site.Id).ToListAsync();
|
UpdateStatus = updateStatus
|
||||||
var sapJoins = await db.SapJoinDefinitions.Where(j => j.SiteId == site.Id).ToListAsync();
|
});
|
||||||
var sapMappings = await db.SapFieldMappings.Where(m => m.SiteId == site.Id).ToListAsync();
|
|
||||||
if (sapSources.Count == 0)
|
|
||||||
throw new InvalidOperationException($"Standort '{site.Land}' hat keine SAP-Quellen konfiguriert.");
|
|
||||||
if (sapMappings.Count == 0)
|
|
||||||
throw new InvalidOperationException($"Standort '{site.Land}' hat keine SAP-Feldmappings.");
|
|
||||||
|
|
||||||
updateStatus?.Invoke("SAP Quellen laden...");
|
var records = fetchResult.Records;
|
||||||
await _appEventLogService.WriteAsync("Export", "SAP Quellen laden", siteId: site.Id, land: site.Land,
|
|
||||||
details: $"Sources={sapSources.Count} | Mappings={sapMappings.Count}");
|
updateStatus?.Invoke("Transformationen anwenden...");
|
||||||
var effectiveSite = CloneSiteWithSapServiceUrl(site, sapServiceUrl);
|
await _appEventLogService.WriteAsync("Export", "Transformationen anwenden",
|
||||||
records = await _sapCompositionService.BuildSalesRecordsAsync(effectiveSite, sapSources, sapJoins, sapMappings, credentials.Username, credentials.Password);
|
siteId: site.Id, land: site.Land,
|
||||||
updateStatus?.Invoke("Transformationen anwenden...");
|
details: $"Records vor Transformation={records.Count}");
|
||||||
await _appEventLogService.WriteAsync("Export", "Transformationen anwenden", siteId: site.Id, land: site.Land,
|
_transformationService.Apply(records, rules);
|
||||||
details: $"Records vor Transformation={records.Count}");
|
|
||||||
var rules = await db.FieldTransformationRules
|
var filePath = fetchResult.ReferenceFilePath;
|
||||||
.Where(r => r.IsActive && r.SourceSystem == sourceSystem)
|
if (string.IsNullOrWhiteSpace(filePath))
|
||||||
.OrderBy(r => r.SortOrder)
|
{
|
||||||
.ToListAsync();
|
|
||||||
_transformationService.Apply(records, rules);
|
|
||||||
updateStatus?.Invoke("Excel erstellen...");
|
updateStatus?.Invoke("Excel erstellen...");
|
||||||
await _appEventLogService.WriteAsync("Export", "Excel erstellen", siteId: site.Id, land: site.Land,
|
await _appEventLogService.WriteAsync("Export", "Excel erstellen",
|
||||||
|
siteId: site.Id, land: site.Land,
|
||||||
details: $"Records={records.Count}");
|
details: $"Records={records.Count}");
|
||||||
filePath = _excelService.CreateExcelFile(outputDir, site.TSC, DateTime.UtcNow.Date, records);
|
filePath = _excelService.CreateExcelFile(outputDir, site.TSC, DateTime.UtcNow.Date, records);
|
||||||
log.RowCount = records.Count;
|
|
||||||
}
|
}
|
||||||
else if (string.Equals(sourceDefinition.ConnectionKind, SourceSystemConnectionKinds.ManualExcel, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(site.ManualImportFilePath))
|
|
||||||
throw new InvalidOperationException($"Standort '{site.Land}' hat keine manuelle Excel-Datei.");
|
|
||||||
string? tempManualImportPath = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var manualImportPath = site.ManualImportFilePath.Trim();
|
|
||||||
if (File.Exists(manualImportPath))
|
|
||||||
{
|
|
||||||
filePath = manualImportPath;
|
|
||||||
}
|
|
||||||
else if (LooksLikeSharePointReference(manualImportPath))
|
|
||||||
{
|
|
||||||
if (spConfig is null ||
|
|
||||||
string.IsNullOrWhiteSpace(spConfig.TenantId) ||
|
|
||||||
string.IsNullOrWhiteSpace(spConfig.ClientId) ||
|
|
||||||
string.IsNullOrWhiteSpace(spConfig.ClientSecret) ||
|
|
||||||
string.IsNullOrWhiteSpace(spConfig.SiteUrl))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Fuer SharePoint-Manuellimport fehlt eine vollstaendige SharePoint-Konfiguration in Settings.");
|
|
||||||
}
|
|
||||||
|
|
||||||
updateStatus?.Invoke("Manuelle Excel von SharePoint laden...");
|
log.RowCount = records.Count;
|
||||||
await _appEventLogService.WriteAsync("Export", "Manuelle Excel von SharePoint laden", siteId: site.Id, land: site.Land,
|
|
||||||
details: manualImportPath);
|
|
||||||
tempManualImportPath = await _sharePointService.DownloadToTempFileAsync(
|
|
||||||
spConfig.TenantId, spConfig.ClientId, spConfig.ClientSecret, spConfig.SiteUrl, manualImportPath);
|
|
||||||
filePath = manualImportPath;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Die manuelle Excel-Datei wurde nicht gefunden: {manualImportPath}");
|
|
||||||
}
|
|
||||||
|
|
||||||
var readPath = tempManualImportPath ?? filePath;
|
|
||||||
updateStatus?.Invoke("Manuelle Excel lesen...");
|
|
||||||
await _appEventLogService.WriteAsync("Export", "Manuelle Excel lesen", siteId: site.Id, land: site.Land,
|
|
||||||
details: filePath);
|
|
||||||
records = await _manualExcelImportService.ReadSalesRecordsAsync(readPath, site);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(tempManualImportPath) && File.Exists(tempManualImportPath))
|
|
||||||
File.Delete(tempManualImportPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateStatus?.Invoke("Transformationen anwenden...");
|
|
||||||
await _appEventLogService.WriteAsync("Export", "Transformationen anwenden", siteId: site.Id, land: site.Land,
|
|
||||||
details: $"Records vor Transformation={records.Count}");
|
|
||||||
var rules = await db.FieldTransformationRules
|
|
||||||
.Where(r => r.IsActive && r.SourceSystem == sourceSystem)
|
|
||||||
.OrderBy(r => r.SortOrder)
|
|
||||||
.ToListAsync();
|
|
||||||
_transformationService.Apply(records, rules);
|
|
||||||
|
|
||||||
log.RowCount = records.Count;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var exportServer = await BuildEffectiveServerAsync(db, site, sourceDefinition);
|
|
||||||
updateStatus?.Invoke("HANA Abfrage...");
|
|
||||||
await _appEventLogService.WriteAsync("Export", "HANA Abfrage gestartet", siteId: site.Id, land: site.Land,
|
|
||||||
details: exportServer.GetConnectionStringPreview());
|
|
||||||
records = await Task.Run(() => _hanaService.GetSalesRecords(
|
|
||||||
exportServer, site.Schema, site.TSC, site.Land, settings.DateFilter));
|
|
||||||
|
|
||||||
updateStatus?.Invoke("Transformationen anwenden...");
|
|
||||||
await _appEventLogService.WriteAsync("Export", "Transformationen anwenden", siteId: site.Id, land: site.Land,
|
|
||||||
details: $"Records vor Transformation={records.Count}");
|
|
||||||
var rules = await db.FieldTransformationRules
|
|
||||||
.Where(r => r.IsActive && r.SourceSystem == sourceSystem)
|
|
||||||
.OrderBy(r => r.SortOrder)
|
|
||||||
.ToListAsync();
|
|
||||||
_transformationService.Apply(records, rules);
|
|
||||||
|
|
||||||
updateStatus?.Invoke("Excel erstellen...");
|
|
||||||
await _appEventLogService.WriteAsync("Export", "Excel erstellen", siteId: site.Id, land: site.Land,
|
|
||||||
details: $"Records={records.Count}");
|
|
||||||
filePath = _excelService.CreateExcelFile(outputDir, site.TSC, DateTime.UtcNow.Date, records);
|
|
||||||
log.RowCount = records.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateStatus?.Invoke("Zentrale Tabelle aktualisieren...");
|
updateStatus?.Invoke("Zentrale Tabelle aktualisieren...");
|
||||||
await _appEventLogService.WriteAsync("Export", "Zentrale Tabelle aktualisieren", siteId: site.Id, land: site.Land,
|
await _appEventLogService.WriteAsync("Export", "Zentrale Tabelle aktualisieren",
|
||||||
|
siteId: site.Id, land: site.Land,
|
||||||
details: $"Records={records.Count}");
|
details: $"Records={records.Count}");
|
||||||
await _centralSalesRecordService.ReplaceForSiteAsync(site, records, updateStatus);
|
await _centralSalesRecordService.ReplaceForSiteAsync(site, records, updateStatus);
|
||||||
|
|
||||||
var fileName = Path.GetFileName(filePath);
|
await UploadToSharePointIfConfiguredAsync(site, spConfig, filePath, updateStatus);
|
||||||
|
|
||||||
if (spConfig is not null &&
|
|
||||||
!string.IsNullOrWhiteSpace(spConfig.TenantId) &&
|
|
||||||
!string.IsNullOrWhiteSpace(spConfig.ClientId) &&
|
|
||||||
!string.IsNullOrWhiteSpace(spConfig.ClientSecret))
|
|
||||||
{
|
|
||||||
updateStatus?.Invoke("SharePoint Upload...");
|
|
||||||
await _appEventLogService.WriteAsync("Export", "SharePoint Upload gestartet", siteId: site.Id, land: site.Land,
|
|
||||||
details: $"{spConfig.SiteUrl} | {spConfig.ExportFolder}");
|
|
||||||
await _sharePointService.UploadAsync(
|
|
||||||
spConfig.TenantId, spConfig.ClientId, spConfig.ClientSecret,
|
|
||||||
spConfig.SiteUrl, spConfig.ExportFolder, site.Land, filePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
log.Status = "OK";
|
log.Status = "OK";
|
||||||
log.FileName = fileName;
|
log.FileName = Path.GetFileName(filePath);
|
||||||
log.FilePath = filePath;
|
log.FilePath = filePath;
|
||||||
log.DurationSeconds = sw.Elapsed.TotalSeconds;
|
log.DurationSeconds = sw.Elapsed.TotalSeconds;
|
||||||
|
|
||||||
_logger.LogInformation("Export OK: {Land} ({TSC}) - {Rows} Zeilen in {Duration:F1}s",
|
_logger.LogInformation("Export OK: {Land} ({TSC}) - {Rows} Zeilen in {Duration:F1}s",
|
||||||
site.Land, site.TSC, log.RowCount, sw.Elapsed.TotalSeconds);
|
site.Land, site.TSC, log.RowCount, sw.Elapsed.TotalSeconds);
|
||||||
await _appEventLogService.WriteAsync("Export", "Export erfolgreich", siteId: site.Id, land: site.Land,
|
await _appEventLogService.WriteAsync("Export", "Export erfolgreich",
|
||||||
details: $"Rows={log.RowCount} | Datei={fileName} | Pfad={filePath} | Dauer={sw.Elapsed.TotalSeconds:F1}s");
|
siteId: site.Id, land: site.Land,
|
||||||
|
details: $"Rows={log.RowCount} | Datei={log.FileName} | Pfad={filePath} | Dauer={sw.Elapsed.TotalSeconds:F1}s");
|
||||||
|
|
||||||
return new SiteExportResult
|
return new SiteExportResult
|
||||||
{
|
{
|
||||||
@@ -237,8 +125,8 @@ public class SiteExportService : ISiteExportService
|
|||||||
log.DurationSeconds = sw.Elapsed.TotalSeconds;
|
log.DurationSeconds = sw.Elapsed.TotalSeconds;
|
||||||
|
|
||||||
_logger.LogError(ex, "Export Fehler: {Land} ({TSC})", site.Land, site.TSC);
|
_logger.LogError(ex, "Export Fehler: {Land} ({TSC})", site.Land, site.TSC);
|
||||||
await _appEventLogService.WriteAsync("Export", "Export fehlgeschlagen", "Error", siteId: site.Id, land: site.Land,
|
await _appEventLogService.WriteAsync("Export", "Export fehlgeschlagen", "Error",
|
||||||
details: ex.ToString());
|
siteId: site.Id, land: site.Land, details: ex.ToString());
|
||||||
|
|
||||||
return new SiteExportResult
|
return new SiteExportResult
|
||||||
{
|
{
|
||||||
@@ -249,90 +137,51 @@ public class SiteExportService : ISiteExportService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<HanaServer> BuildEffectiveServerAsync(AppDbContext db, Site site, SourceSystemDefinition sourceDefinition)
|
private async Task<(ExportSettings settings, SharePointConfig? spConfig, SourceSystemDefinition sourceDefinition, List<FieldTransformationRule> rules)>
|
||||||
|
LoadExportConfigAsync(Site site, string sourceSystem)
|
||||||
{
|
{
|
||||||
var centralServer = await db.HanaServers
|
using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
|
var settings = await db.ExportSettings.FirstOrDefaultAsync() ?? new ExportSettings();
|
||||||
|
var spConfig = await db.SharePointConfigs.FirstOrDefaultAsync();
|
||||||
|
var sourceDefinition = await db.SourceSystemDefinitions
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.OrderBy(x => x.Id)
|
.OrderBy(x => x.Id)
|
||||||
.FirstOrDefaultAsync(x => x.SourceSystem == sourceDefinition.Code);
|
.FirstOrDefaultAsync(x => x.Code == sourceSystem)
|
||||||
|
?? throw new InvalidOperationException($"Quellsystem '{sourceSystem}' ist nicht konfiguriert.");
|
||||||
if (centralServer is null)
|
var rules = await db.FieldTransformationRules
|
||||||
throw new InvalidOperationException($"Fuer Quellsystem '{sourceDefinition.Code}' ist keine zentrale HANA-Konfiguration vorhanden.");
|
.Where(r => r.IsActive && r.SourceSystem == sourceSystem)
|
||||||
|
.OrderBy(r => r.SortOrder)
|
||||||
var credentials = ResolveCredentials(site, sourceDefinition);
|
.ToListAsync();
|
||||||
|
return (settings, spConfig, sourceDefinition, rules);
|
||||||
return new HanaServer
|
|
||||||
{
|
|
||||||
Id = centralServer.Id,
|
|
||||||
SourceSystem = centralServer.SourceSystem,
|
|
||||||
Name = centralServer.Name,
|
|
||||||
Host = centralServer.Host,
|
|
||||||
Port = centralServer.Port,
|
|
||||||
Username = credentials.Username,
|
|
||||||
Password = credentials.Password,
|
|
||||||
DatabaseName = centralServer.DatabaseName,
|
|
||||||
UseSsl = centralServer.UseSsl,
|
|
||||||
ValidateCertificate = centralServer.ValidateCertificate,
|
|
||||||
AdditionalParams = centralServer.AdditionalParams
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (string Username, string Password) ResolveCredentials(Site site, SourceSystemDefinition sourceDefinition)
|
private async Task UploadToSharePointIfConfiguredAsync(
|
||||||
=> (FirstNonEmpty(site.UsernameOverride, sourceDefinition.CentralUsername),
|
Site site, SharePointConfig? spConfig, string filePath, Action<string>? updateStatus)
|
||||||
FirstNonEmpty(site.PasswordOverride, sourceDefinition.CentralPassword));
|
{
|
||||||
|
if (spConfig is null ||
|
||||||
|
string.IsNullOrWhiteSpace(spConfig.TenantId) ||
|
||||||
|
string.IsNullOrWhiteSpace(spConfig.ClientId) ||
|
||||||
|
string.IsNullOrWhiteSpace(spConfig.ClientSecret))
|
||||||
|
return;
|
||||||
|
|
||||||
private static string ResolveSapServiceUrl(Site site, SourceSystemDefinition sourceDefinition)
|
updateStatus?.Invoke("SharePoint Upload...");
|
||||||
=> FirstNonEmpty(site.SapServiceUrl, sourceDefinition.CentralServiceUrl);
|
await _appEventLogService.WriteAsync("Export", "SharePoint Upload gestartet",
|
||||||
|
siteId: site.Id, land: site.Land,
|
||||||
|
details: $"{spConfig.SiteUrl} | {spConfig.ExportFolder}");
|
||||||
|
await _sharePointService.UploadAsync(
|
||||||
|
spConfig.TenantId, spConfig.ClientId, spConfig.ClientSecret,
|
||||||
|
spConfig.SiteUrl, spConfig.ExportFolder, site.Land, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
private static string NormalizeSourceSystem(string? sourceSystem)
|
private static string NormalizeSourceSystem(string? sourceSystem)
|
||||||
=> string.IsNullOrWhiteSpace(sourceSystem) ? "SAP" : sourceSystem.Trim().ToUpperInvariant();
|
=> string.IsNullOrWhiteSpace(sourceSystem) ? "SAP" : sourceSystem.Trim().ToUpperInvariant();
|
||||||
|
|
||||||
private static string FirstNonEmpty(params string[] values)
|
|
||||||
{
|
|
||||||
foreach (var value in values)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(value))
|
|
||||||
return value.Trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string ResolveSiteOutputDirectory(ExportSettings settings, Site site)
|
private static string ResolveSiteOutputDirectory(ExportSettings settings, Site site)
|
||||||
{
|
{
|
||||||
var configured = FirstNonEmpty(site.LocalExportFolderOverride, settings.LocalSiteExportFolder);
|
var configured = DataSourceCredentials.FirstNonEmpty(
|
||||||
|
site.LocalExportFolderOverride, settings.LocalSiteExportFolder);
|
||||||
return string.IsNullOrWhiteSpace(configured)
|
return string.IsNullOrWhiteSpace(configured)
|
||||||
? Path.Combine(AppContext.BaseDirectory, "output")
|
? Path.Combine(AppContext.BaseDirectory, "output")
|
||||||
: configured;
|
: configured;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool LooksLikeSharePointReference(string path)
|
|
||||||
=> path.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
path.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
path.StartsWith("/Shared Documents/", StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
path.StartsWith("Shared Documents/", StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
private static Site CloneSiteWithSapServiceUrl(Site site, string sapServiceUrl)
|
|
||||||
{
|
|
||||||
return new Site
|
|
||||||
{
|
|
||||||
Id = site.Id,
|
|
||||||
HanaServerId = site.HanaServerId,
|
|
||||||
HanaServer = site.HanaServer,
|
|
||||||
Schema = site.Schema,
|
|
||||||
TSC = site.TSC,
|
|
||||||
Land = site.Land,
|
|
||||||
SourceSystem = site.SourceSystem,
|
|
||||||
UsernameOverride = site.UsernameOverride,
|
|
||||||
PasswordOverride = site.PasswordOverride,
|
|
||||||
LocalExportFolderOverride = site.LocalExportFolderOverride,
|
|
||||||
ManualImportFilePath = site.ManualImportFilePath,
|
|
||||||
ManualImportLastUploadedAtUtc = site.ManualImportLastUploadedAtUtc,
|
|
||||||
SapServiceUrl = sapServiceUrl,
|
|
||||||
SapEntitySet = site.SapEntitySet,
|
|
||||||
SapEntitySetsCache = site.SapEntitySetsCache,
|
|
||||||
SapEntitySetsRefreshedAtUtc = site.SapEntitySetsRefreshedAtUtc,
|
|
||||||
IsActive = site.IsActive
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user