This commit is contained in:
2026-04-14 10:54:52 +02:00
parent df90a4a172
commit 36a22202bf
14 changed files with 678 additions and 78 deletions
@@ -0,0 +1,36 @@
using System.Net.Http.Headers;
using System.Text;
using Microsoft.Data.Sqlite;
var conn = new SqliteConnection(@"Data Source=C:\Users\koi\source\repos\Ai\TrafagSalesExporter\trafag_exporter.db");
await conn.OpenAsync();
string sapUsername = "", sapPassword = "";
var cmd = conn.CreateCommand();
cmd.CommandText = "select SapUsername, SapPassword from ExportSettings limit 1";
using (var r = await cmd.ExecuteReaderAsync())
{
if (await r.ReadAsync())
{
sapUsername = r.IsDBNull(0) ? "" : r.GetString(0);
sapPassword = r.IsDBNull(1) ? "" : r.GetString(1);
}
}
if (string.IsNullOrWhiteSpace(sapUsername) || string.IsNullOrWhiteSpace(sapPassword)) throw new Exception("Central SAP credentials missing");
var serviceUrl = @"http://travt762.sap.trafag.com:8000/sap/opu/odata/sap/ZPOWERBI_EINKAUF_SRV/";
using var client = new HttpClient();
client.Timeout = TimeSpan.FromSeconds(20);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{sapUsername}:{sapPassword}")));
foreach (var url in new[]{ serviceUrl, serviceUrl + "" })
{
Console.WriteLine($"URL|{url}");
using var response = await client.GetAsync(url);
Console.WriteLine($"STATUS|{(int)response.StatusCode}|{response.ReasonPhrase}");
foreach (var header in response.Headers)
Console.WriteLine($"HEADER|{header.Key}|{string.Join(",", header.Value)}");
foreach (var header in response.Content.Headers)
Console.WriteLine($"HEADER|{header.Key}|{string.Join(",", header.Value)}");
var body = await response.Content.ReadAsStringAsync();
Console.WriteLine("BODY_START");
Console.WriteLine(body.Length > 5000 ? body[..5000] : body);
Console.WriteLine("BODY_END");
}
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Data.Sqlite" Version="8.0.11" />
</ItemGroup>
</Project>
@@ -7,6 +7,7 @@
@inject ISharePointUploadService SpService @inject ISharePointUploadService SpService
@inject TimerBackgroundService TimerService @inject TimerBackgroundService TimerService
@inject IHanaQueryService HanaService @inject IHanaQueryService HanaService
@inject ISapGatewayService SapGatewayService
@inject ISnackbar Snackbar @inject ISnackbar Snackbar
<PageTitle>Settings</PageTitle> <PageTitle>Settings</PageTitle>
@@ -240,6 +241,17 @@
} }
private async Task TestCentralCredentials(string sourceSystem) private async Task TestCentralCredentials(string sourceSystem)
{
if (sourceSystem == "SAP")
{
await TestCentralSapCredentials();
return;
}
await TestCentralHanaCredentials(sourceSystem);
}
private async Task TestCentralHanaCredentials(string sourceSystem)
{ {
if (!_testingSystems.Add(sourceSystem)) if (!_testingSystems.Add(sourceSystem))
return; return;
@@ -297,6 +309,49 @@
} }
} }
private async Task TestCentralSapCredentials()
{
const string sourceSystem = "SAP";
if (!_testingSystems.Add(sourceSystem))
return;
try
{
var username = GetCentralUsername(sourceSystem);
var password = GetCentralPassword(sourceSystem);
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
{
Snackbar.Add("Für SAP sind keine zentralen Gateway-Zugangsdaten gepflegt.", Severity.Warning);
return;
}
using var db = await DbFactory.CreateDbContextAsync();
var site = await db.Sites
.Where(s => (string.IsNullOrWhiteSpace(s.SourceSystem) ? "SAP" : s.SourceSystem) == sourceSystem
&& !string.IsNullOrWhiteSpace(s.SapServiceUrl))
.OrderBy(s => s.Land)
.FirstOrDefaultAsync();
if (site is null)
{
Snackbar.Add("Kein SAP-Standort mit Service URL gefunden.", Severity.Warning);
return;
}
await SapGatewayService.TestConnectionAsync(site.SapServiceUrl, username.Trim(), password.Trim());
Snackbar.Add($"SAP: Gateway-Verbindung erfolgreich über Standort '{site.Land}'.", Severity.Success);
}
catch (Exception ex)
{
Snackbar.Add($"SAP: {ex.Message}", Severity.Error);
}
finally
{
_testingSystems.Remove(sourceSystem);
}
}
private string GetCentralUsername(string sourceSystem) => sourceSystem switch private string GetCentralUsername(string sourceSystem) => sourceSystem switch
{ {
"BI1" => _exportSettings.Bi1Username, "BI1" => _exportSettings.Bi1Username,
@@ -1,9 +1,12 @@
@page "/standorte" @page "/standorte"
@using Microsoft.EntityFrameworkCore @using Microsoft.EntityFrameworkCore
@using System.Text.Json
@using TrafagSalesExporter.Data @using TrafagSalesExporter.Data
@using TrafagSalesExporter.Models
@using TrafagSalesExporter.Services @using TrafagSalesExporter.Services
@inject IDbContextFactory<AppDbContext> DbFactory @inject IDbContextFactory<AppDbContext> DbFactory
@inject IHanaQueryService HanaService @inject IHanaQueryService HanaService
@inject ISapGatewayService SapGatewayService
@inject ISnackbar Snackbar @inject ISnackbar Snackbar
@inject IDialogService DialogService @inject IDialogService DialogService
@@ -71,7 +74,7 @@
<MudTh>TSC</MudTh> <MudTh>TSC</MudTh>
<MudTh>Schema</MudTh> <MudTh>Schema</MudTh>
<MudTh>Quellsystem</MudTh> <MudTh>Quellsystem</MudTh>
<MudTh>Host</MudTh> <MudTh>Quelle</MudTh>
<MudTh>Aktiv</MudTh> <MudTh>Aktiv</MudTh>
<MudTh>Aktionen</MudTh> <MudTh>Aktionen</MudTh>
</HeaderContent> </HeaderContent>
@@ -80,7 +83,7 @@
<MudTd>@context.TSC</MudTd> <MudTd>@context.TSC</MudTd>
<MudTd>@context.Schema</MudTd> <MudTd>@context.Schema</MudTd>
<MudTd>@context.SourceSystem</MudTd> <MudTd>@context.SourceSystem</MudTd>
<MudTd>@GetServerNode(context.HanaServer)</MudTd> <MudTd>@GetConnectionTarget(context)</MudTd>
<MudTd> <MudTd>
@if (context.IsActive) @if (context.IsActive)
{ {
@@ -122,8 +125,8 @@
HelperText="Optional, z.B. sslCryptoProvider=openssl;communicationTimeout=0" /> HelperText="Optional, z.B. sslCryptoProvider=openssl;communicationTimeout=0" />
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<MudButton OnClick="() => _serverDialogVisible = false">Abbrechen</MudButton> <MudButton OnClick="CloseServerDialog">Abbrechen</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveServer">Speichern</MudButton> <MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveServer" Disabled="_savingServer">Speichern</MudButton>
</DialogActions> </DialogActions>
</MudDialog> </MudDialog>
@@ -138,7 +141,7 @@
<MudSelect @bind-Value="_editingSite.SourceSystem" Label="Quellsystem" Required> <MudSelect @bind-Value="_editingSite.SourceSystem" Label="Quellsystem" Required>
@foreach (var system in _sourceSystems) @foreach (var system in _sourceSystems)
{ {
<MudSelectItem Value="system">@system</MudSelectItem> <MudSelectItem Value="@system">@system</MudSelectItem>
} }
</MudSelect> </MudSelect>
<MudTextField @bind-Value="_editingSite.UsernameOverride" Label="Username Override" <MudTextField @bind-Value="_editingSite.UsernameOverride" Label="Username Override"
@@ -149,6 +152,43 @@
<MudDivider Class="my-4" /> <MudDivider Class="my-4" />
@if (IsSapSite())
{
<MudText Typo="Typo.h6" Class="mb-2">SAP Gateway</MudText>
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Outlined" Class="mb-3">
Die Service-URL zeigt auf den OData-Service. Die verfügbaren Entity Sets werden nur per Knopfdruck aktualisiert und lokal zwischengespeichert.
</MudAlert>
<MudTextField @bind-Value="_editingSite.SapServiceUrl" Label="SAP Service URL" Required
HelperText="z.B. http://server:8000/sap/opu/odata/sap/ZPOWERBI_EINKAUF_SRV/" />
<MudStack Row Spacing="2" Class="mb-3">
<MudButton Variant="Variant.Outlined" Color="Color.Info" OnClick="RefreshSapEntitySets"
StartIcon="@Icons.Material.Filled.Refresh" Disabled="_refreshingSapEntitySets">
@if (_refreshingSapEntitySets)
{
<MudProgressCircular Size="Size.Small" Indeterminate Class="mr-2" />
@("Lade...")
}
else
{
@("Quellen refreshen")
}
</MudButton>
@if (_editingSite.SapEntitySetsRefreshedAtUtc.HasValue)
{
<MudText Typo="Typo.caption" Class="mt-2">
Letzter Refresh: @_editingSite.SapEntitySetsRefreshedAtUtc.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")
</MudText>
}
</MudStack>
<MudSelect @bind-Value="_editingSite.SapEntitySet" Label="SAP Entity Set" Required>
@foreach (var entitySet in _sapEntitySetsCache)
{
<MudSelectItem Value="@entitySet">@entitySet</MudSelectItem>
}
</MudSelect>
}
else
{
<MudText Typo="Typo.h6" Class="mb-2">HANA-Verbindung</MudText> <MudText Typo="Typo.h6" Class="mb-2">HANA-Verbindung</MudText>
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Outlined" Class="mb-3"> <MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Outlined" Class="mb-3">
Host, Port und technische HANA-Parameter kommen von dieser Verbindung. Username und Password hier dienen nur noch als Fallback für bestehende Einträge. Host, Port und technische HANA-Parameter kommen von dieser Verbindung. Username und Password hier dienen nur noch als Fallback für bestehende Einträge.
@@ -168,10 +208,11 @@
Disabled="!_editingSiteServer.UseSsl" /> Disabled="!_editingSiteServer.UseSsl" />
<MudTextField @bind-Value="_editingSiteServer.AdditionalParams" Label="Zusätzliche Parameter" <MudTextField @bind-Value="_editingSiteServer.AdditionalParams" Label="Zusätzliche Parameter"
HelperText="Optional, z.B. sslCryptoProvider=openssl;communicationTimeout=0" /> HelperText="Optional, z.B. sslCryptoProvider=openssl;communicationTimeout=0" />
}
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<MudButton OnClick="() => _siteDialogVisible = false">Abbrechen</MudButton> <MudButton OnClick="CloseSiteDialog" Disabled="_savingSite">Abbrechen</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveSite">Speichern</MudButton> <MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveSite" Disabled="_savingSite || _refreshingSapEntitySets">Speichern</MudButton>
</DialogActions> </DialogActions>
</MudDialog> </MudDialog>
@@ -180,11 +221,15 @@
private readonly Dictionary<int, ConnectionTestResult> _connectionStatus = new(); private readonly Dictionary<int, ConnectionTestResult> _connectionStatus = new();
private List<HanaServer> _servers = new(); private List<HanaServer> _servers = new();
private List<Site> _sites = new(); private List<Site> _sites = new();
private List<string> _sapEntitySetsCache = [];
private HanaServer _editingServer = new(); private HanaServer _editingServer = new();
private Site _editingSite = new(); private Site _editingSite = new();
private HanaServer _editingSiteServer = new(); private HanaServer _editingSiteServer = new();
private bool _serverDialogVisible; private bool _serverDialogVisible;
private bool _siteDialogVisible; private bool _siteDialogVisible;
private bool _refreshingSapEntitySets;
private bool _savingServer;
private bool _savingSite;
private readonly DialogOptions _dialogOptions = new() { MaxWidth = MaxWidth.Small, FullWidth = true }; private readonly DialogOptions _dialogOptions = new() { MaxWidth = MaxWidth.Small, FullWidth = true };
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
@@ -212,6 +257,12 @@
} }
private async Task SaveServer() private async Task SaveServer()
{
if (_savingServer)
return;
_savingServer = true;
try
{ {
using var db = await DbFactory.CreateDbContextAsync(); using var db = await DbFactory.CreateDbContextAsync();
if (_editingServer.Id == 0) if (_editingServer.Id == 0)
@@ -240,6 +291,11 @@
await LoadDataAsync(); await LoadDataAsync();
Snackbar.Add("Server gespeichert", Severity.Success); Snackbar.Add("Server gespeichert", Severity.Success);
} }
finally
{
_savingServer = false;
}
}
private async Task DeleteServer(HanaServer server) private async Task DeleteServer(HanaServer server)
{ {
@@ -294,6 +350,7 @@
SourceSystem = "SAP", SourceSystem = "SAP",
HanaServerId = 0 HanaServerId = 0
}; };
_sapEntitySetsCache = [];
_editingSiteServer = CreateDefaultSiteServer(); _editingSiteServer = CreateDefaultSiteServer();
_siteDialogVisible = true; _siteDialogVisible = true;
} }
@@ -310,8 +367,13 @@
SourceSystem = string.IsNullOrWhiteSpace(site.SourceSystem) ? "SAP" : site.SourceSystem, SourceSystem = string.IsNullOrWhiteSpace(site.SourceSystem) ? "SAP" : site.SourceSystem,
UsernameOverride = site.UsernameOverride, UsernameOverride = site.UsernameOverride,
PasswordOverride = site.PasswordOverride, PasswordOverride = site.PasswordOverride,
SapServiceUrl = site.SapServiceUrl,
SapEntitySet = site.SapEntitySet,
SapEntitySetsCache = site.SapEntitySetsCache,
SapEntitySetsRefreshedAtUtc = site.SapEntitySetsRefreshedAtUtc,
IsActive = site.IsActive IsActive = site.IsActive
}; };
_sapEntitySetsCache = ParseSapEntitySets(site.SapEntitySetsCache);
_editingSiteServer = site.HanaServer is null _editingSiteServer = site.HanaServer is null
? CreateDefaultSiteServer(site) ? CreateDefaultSiteServer(site)
: CloneServer(site.HanaServer); : CloneServer(site.HanaServer);
@@ -319,13 +381,20 @@
} }
private async Task SaveSite() private async Task SaveSite()
{
if (_savingSite)
return;
_savingSite = true;
try
{ {
using var db = await DbFactory.CreateDbContextAsync(); using var db = await DbFactory.CreateDbContextAsync();
var serverId = await SaveOrCreateSiteServerAsync(db); var serverId = IsSapSite() ? (int?)null : await SaveOrCreateSiteServerAsync(db);
_editingSite.HanaServerId = serverId;
_editingSite.SapEntitySetsCache = SerializeSapEntitySets(_sapEntitySetsCache);
if (_editingSite.Id == 0) if (_editingSite.Id == 0)
{ {
_editingSite.HanaServerId = serverId;
db.Sites.Add(_editingSite); db.Sites.Add(_editingSite);
} }
else else
@@ -340,6 +409,10 @@
existing.SourceSystem = _editingSite.SourceSystem; existing.SourceSystem = _editingSite.SourceSystem;
existing.UsernameOverride = _editingSite.UsernameOverride; existing.UsernameOverride = _editingSite.UsernameOverride;
existing.PasswordOverride = _editingSite.PasswordOverride; existing.PasswordOverride = _editingSite.PasswordOverride;
existing.SapServiceUrl = _editingSite.SapServiceUrl;
existing.SapEntitySet = _editingSite.SapEntitySet;
existing.SapEntitySetsCache = _editingSite.SapEntitySetsCache;
existing.SapEntitySetsRefreshedAtUtc = _editingSite.SapEntitySetsRefreshedAtUtc;
existing.IsActive = _editingSite.IsActive; existing.IsActive = _editingSite.IsActive;
} }
} }
@@ -349,6 +422,15 @@
await LoadDataAsync(); await LoadDataAsync();
Snackbar.Add("Standort gespeichert", Severity.Success); Snackbar.Add("Standort gespeichert", Severity.Success);
} }
catch (Exception ex)
{
Snackbar.Add($"Speichern fehlgeschlagen: {ex.Message}", Severity.Error);
}
finally
{
_savingSite = false;
}
}
private async Task DeleteSite(Site site) private async Task DeleteSite(Site site)
{ {
@@ -379,6 +461,15 @@
return server.Host.Contains(':', StringComparison.Ordinal) ? server.Host : $"{server.Host}:{server.Port}"; return server.Host.Contains(':', StringComparison.Ordinal) ? server.Host : $"{server.Host}:{server.Port}";
} }
private static string GetConnectionTarget(Site site)
{
var sourceSystem = string.IsNullOrWhiteSpace(site.SourceSystem) ? "SAP" : site.SourceSystem;
if (string.Equals(sourceSystem, "SAP", StringComparison.OrdinalIgnoreCase))
return string.IsNullOrWhiteSpace(site.SapServiceUrl) ? "-" : site.SapServiceUrl;
return GetServerNode(site.HanaServer);
}
private HanaServer CreateDefaultSiteServer(Site? site = null) private HanaServer CreateDefaultSiteServer(Site? site = null)
{ {
var label = !string.IsNullOrWhiteSpace(site?.Land) ? site!.Land : site?.TSC; var label = !string.IsNullOrWhiteSpace(site?.Land) ? site!.Land : site?.TSC;
@@ -416,6 +507,8 @@
: _editingSiteServer.Name.Trim(); : _editingSiteServer.Name.Trim();
_editingSite.UsernameOverride = _editingSite.UsernameOverride.Trim(); _editingSite.UsernameOverride = _editingSite.UsernameOverride.Trim();
_editingSite.PasswordOverride = _editingSite.PasswordOverride.Trim(); _editingSite.PasswordOverride = _editingSite.PasswordOverride.Trim();
_editingSite.SapServiceUrl = _editingSite.SapServiceUrl.Trim();
_editingSite.SapEntitySet = _editingSite.SapEntitySet.Trim();
_editingSiteServer.Host = _editingSiteServer.Host.Trim(); _editingSiteServer.Host = _editingSiteServer.Host.Trim();
_editingSiteServer.Username = _editingSiteServer.Username.Trim(); _editingSiteServer.Username = _editingSiteServer.Username.Trim();
_editingSiteServer.DatabaseName = _editingSiteServer.DatabaseName.Trim(); _editingSiteServer.DatabaseName = _editingSiteServer.DatabaseName.Trim();
@@ -461,4 +554,82 @@
await db.SaveChangesAsync(); await db.SaveChangesAsync();
return existingServer.Id; return existingServer.Id;
} }
private bool IsSapSite() => string.Equals(_editingSite.SourceSystem, "SAP", StringComparison.OrdinalIgnoreCase);
private async Task RefreshSapEntitySets()
{
if (_refreshingSapEntitySets)
return;
_refreshingSapEntitySets = true;
try
{
if (string.IsNullOrWhiteSpace(_editingSite.SapServiceUrl))
throw new InvalidOperationException("SAP Service URL muss gesetzt sein.");
using var db = await DbFactory.CreateDbContextAsync();
var settings = await db.ExportSettings.FirstOrDefaultAsync() ?? new();
var username = string.IsNullOrWhiteSpace(_editingSite.UsernameOverride) ? settings.SapUsername : _editingSite.UsernameOverride;
var password = string.IsNullOrWhiteSpace(_editingSite.PasswordOverride) ? settings.SapPassword : _editingSite.PasswordOverride;
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
throw new InvalidOperationException("Für SAP sind weder zentrale Zugangsdaten noch Standort-Overrides gesetzt.");
var entitySets = await SapGatewayService.GetEntitySetsAsync(_editingSite.SapServiceUrl, username.Trim(), password.Trim());
_sapEntitySetsCache = entitySets;
_editingSite.SapEntitySetsCache = SerializeSapEntitySets(entitySets);
_editingSite.SapEntitySetsRefreshedAtUtc = DateTime.UtcNow;
if (!string.IsNullOrWhiteSpace(_editingSite.SapEntitySet) &&
!_sapEntitySetsCache.Contains(_editingSite.SapEntitySet, StringComparer.OrdinalIgnoreCase))
{
_editingSite.SapEntitySet = string.Empty;
}
Snackbar.Add($"{entitySets.Count} SAP Entity Sets geladen.", Severity.Success);
}
catch (Exception ex)
{
Snackbar.Add(ex.Message, Severity.Error);
}
finally
{
_refreshingSapEntitySets = false;
}
}
private void CloseServerDialog()
{
if (_savingServer)
return;
_serverDialogVisible = false;
}
private void CloseSiteDialog()
{
if (_savingSite || _refreshingSapEntitySets)
return;
_siteDialogVisible = false;
}
private static List<string> ParseSapEntitySets(string json)
{
if (string.IsNullOrWhiteSpace(json))
return [];
try
{
return JsonSerializer.Deserialize<List<string>>(json) ?? [];
}
catch
{
return [];
}
}
private static string SerializeSapEntitySets(List<string> entitySets)
=> JsonSerializer.Serialize(entitySets);
} }
+9 -1
View File
@@ -7,7 +7,7 @@ public class Site
{ {
public int Id { get; set; } public int Id { get; set; }
public int HanaServerId { get; set; } public int? HanaServerId { get; set; }
[ForeignKey(nameof(HanaServerId))] [ForeignKey(nameof(HanaServerId))]
public HanaServer? HanaServer { get; set; } public HanaServer? HanaServer { get; set; }
@@ -28,5 +28,13 @@ public class Site
public string PasswordOverride { get; set; } = string.Empty; public string PasswordOverride { get; set; } = string.Empty;
public string SapServiceUrl { get; set; } = string.Empty;
public string SapEntitySet { get; set; } = string.Empty;
public string SapEntitySetsCache { get; set; } = string.Empty;
public DateTime? SapEntitySetsRefreshedAtUtc { get; set; }
public bool IsActive { get; set; } = true; public bool IsActive { get; set; } = true;
} }
+1
View File
@@ -16,6 +16,7 @@ builder.Services.AddDbContextFactory<AppDbContext>(options =>
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>();
builder.Services.AddSingleton<ISapGatewayService, SapGatewayService>();
builder.Services.AddSingleton<ITransformationStrategy, CopyTransformationStrategy>(); builder.Services.AddSingleton<ITransformationStrategy, CopyTransformationStrategy>();
builder.Services.AddSingleton<ITransformationStrategy, UppercaseTransformationStrategy>(); builder.Services.AddSingleton<ITransformationStrategy, UppercaseTransformationStrategy>();
builder.Services.AddSingleton<ITransformationStrategy, LowercaseTransformationStrategy>(); builder.Services.AddSingleton<ITransformationStrategy, LowercaseTransformationStrategy>();
@@ -24,6 +24,7 @@ public class DatabaseInitializationService : IDatabaseInitializationService
private static void EnsureSchema(AppDbContext db) private static void EnsureSchema(AppDbContext db)
{ {
EnsureSitesTableSupportsOptionalHanaServer(db);
AddColumnIfMissing(db, "HanaServers", "DatabaseName", "TEXT NOT NULL DEFAULT ''"); AddColumnIfMissing(db, "HanaServers", "DatabaseName", "TEXT NOT NULL DEFAULT ''");
AddColumnIfMissing(db, "HanaServers", "UseSsl", "INTEGER NOT NULL DEFAULT 0"); AddColumnIfMissing(db, "HanaServers", "UseSsl", "INTEGER NOT NULL DEFAULT 0");
AddColumnIfMissing(db, "HanaServers", "ValidateCertificate", "INTEGER NOT NULL DEFAULT 0"); AddColumnIfMissing(db, "HanaServers", "ValidateCertificate", "INTEGER NOT NULL DEFAULT 0");
@@ -31,6 +32,10 @@ public class DatabaseInitializationService : IDatabaseInitializationService
AddColumnIfMissing(db, "Sites", "SourceSystem", "TEXT NOT NULL DEFAULT 'SAP'"); AddColumnIfMissing(db, "Sites", "SourceSystem", "TEXT NOT NULL DEFAULT 'SAP'");
AddColumnIfMissing(db, "Sites", "UsernameOverride", "TEXT NOT NULL DEFAULT ''"); AddColumnIfMissing(db, "Sites", "UsernameOverride", "TEXT NOT NULL DEFAULT ''");
AddColumnIfMissing(db, "Sites", "PasswordOverride", "TEXT NOT NULL DEFAULT ''"); AddColumnIfMissing(db, "Sites", "PasswordOverride", "TEXT NOT NULL DEFAULT ''");
AddColumnIfMissing(db, "Sites", "SapServiceUrl", "TEXT NOT NULL DEFAULT ''");
AddColumnIfMissing(db, "Sites", "SapEntitySet", "TEXT NOT NULL DEFAULT ''");
AddColumnIfMissing(db, "Sites", "SapEntitySetsCache", "TEXT NOT NULL DEFAULT ''");
AddColumnIfMissing(db, "Sites", "SapEntitySetsRefreshedAtUtc", "TEXT NULL");
AddColumnIfMissing(db, "ExportSettings", "SapUsername", "TEXT NOT NULL DEFAULT ''"); AddColumnIfMissing(db, "ExportSettings", "SapUsername", "TEXT NOT NULL DEFAULT ''");
AddColumnIfMissing(db, "ExportSettings", "SapPassword", "TEXT NOT NULL DEFAULT ''"); AddColumnIfMissing(db, "ExportSettings", "SapPassword", "TEXT NOT NULL DEFAULT ''");
AddColumnIfMissing(db, "ExportSettings", "Bi1Username", "TEXT NOT NULL DEFAULT ''"); AddColumnIfMissing(db, "ExportSettings", "Bi1Username", "TEXT NOT NULL DEFAULT ''");
@@ -40,6 +45,104 @@ public class DatabaseInitializationService : IDatabaseInitializationService
EnsureTransformationTable(db); EnsureTransformationTable(db);
} }
private static void EnsureSitesTableSupportsOptionalHanaServer(AppDbContext db)
{
var conn = db.Database.GetDbConnection();
if (conn.State != ConnectionState.Open)
conn.Open();
var hanaServerIdIsRequired = false;
{
using var pragma = conn.CreateCommand();
pragma.CommandText = "PRAGMA table_info(Sites)";
using var reader = pragma.ExecuteReader();
while (reader.Read())
{
if (string.Equals(reader["name"]?.ToString(), "HanaServerId", StringComparison.OrdinalIgnoreCase))
{
hanaServerIdIsRequired = Convert.ToInt32(reader["notnull"]) == 1;
break;
}
}
}
if (!hanaServerIdIsRequired)
return;
using var disableFk = conn.CreateCommand();
disableFk.CommandText = "PRAGMA foreign_keys = OFF;";
disableFk.ExecuteNonQuery();
using var transaction = conn.BeginTransaction();
using (var rename = conn.CreateCommand())
{
rename.Transaction = transaction;
rename.CommandText = "ALTER TABLE Sites RENAME TO Sites_old;";
rename.ExecuteNonQuery();
}
using (var create = conn.CreateCommand())
{
create.Transaction = transaction;
create.CommandText = @"
CREATE TABLE Sites (
Id INTEGER NOT NULL CONSTRAINT PK_Sites PRIMARY KEY AUTOINCREMENT,
HanaServerId INTEGER NULL,
Schema TEXT NOT NULL,
TSC TEXT NOT NULL,
Land TEXT NOT NULL,
SourceSystem TEXT NOT NULL DEFAULT 'SAP',
UsernameOverride TEXT NOT NULL DEFAULT '',
PasswordOverride TEXT NOT NULL DEFAULT '',
SapServiceUrl TEXT NOT NULL DEFAULT '',
SapEntitySet TEXT NOT NULL DEFAULT '',
SapEntitySetsCache TEXT NOT NULL DEFAULT '',
SapEntitySetsRefreshedAtUtc TEXT NULL,
IsActive INTEGER NOT NULL,
CONSTRAINT FK_Sites_HanaServers_HanaServerId FOREIGN KEY (HanaServerId) REFERENCES HanaServers (Id)
);";
create.ExecuteNonQuery();
}
using (var copy = conn.CreateCommand())
{
copy.Transaction = transaction;
copy.CommandText = @"
INSERT INTO Sites (
Id, HanaServerId, Schema, TSC, Land, SourceSystem,
UsernameOverride, PasswordOverride, SapServiceUrl, SapEntitySet,
SapEntitySetsCache, SapEntitySetsRefreshedAtUtc, IsActive
)
SELECT
Id, HanaServerId, Schema, TSC, Land,
COALESCE(SourceSystem, 'SAP'),
COALESCE(UsernameOverride, ''),
COALESCE(PasswordOverride, ''),
COALESCE(SapServiceUrl, ''),
COALESCE(SapEntitySet, ''),
COALESCE(SapEntitySetsCache, ''),
SapEntitySetsRefreshedAtUtc,
IsActive
FROM Sites_old;";
copy.ExecuteNonQuery();
}
using (var drop = conn.CreateCommand())
{
drop.Transaction = transaction;
drop.CommandText = "DROP TABLE Sites_old;";
drop.ExecuteNonQuery();
}
transaction.Commit();
using var enableFk = conn.CreateCommand();
enableFk.CommandText = "PRAGMA foreign_keys = ON;";
enableFk.ExecuteNonQuery();
}
private static void AddColumnIfMissing(AppDbContext db, string table, string column, string type) private static void AddColumnIfMissing(AppDbContext db, string table, string column, string type)
{ {
var conn = db.Database.GetDbConnection(); var conn = db.Database.GetDbConnection();
@@ -23,6 +23,16 @@ public class ExcelExportService : IExcelExportService
return fullPath; return fullPath;
} }
public string CreateGenericExcelFile(string outputDirectory, string filePrefix, DateTime fileDate, string worksheetName, IReadOnlyList<IReadOnlyDictionary<string, object?>> rows)
{
Directory.CreateDirectory(outputDirectory);
var safePrefix = string.IsNullOrWhiteSpace(filePrefix) ? "Export" : filePrefix.Trim();
var fileName = $"{safePrefix}_{fileDate:yyyy-MM-dd}.xlsx";
var fullPath = Path.Combine(outputDirectory, fileName);
WriteGenericWorkbook(fullPath, worksheetName, rows);
return fullPath;
}
private static void WriteWorkbook(string fullPath, List<SalesRecord> records) private static void WriteWorkbook(string fullPath, List<SalesRecord> records)
{ {
using var workbook = new XLWorkbook(); using var workbook = new XLWorkbook();
@@ -99,4 +109,35 @@ public class ExcelExportService : IExcelExportService
ws.Columns().AdjustToContents(); ws.Columns().AdjustToContents();
workbook.SaveAs(fullPath); workbook.SaveAs(fullPath);
} }
private static void WriteGenericWorkbook(string fullPath, string worksheetName, IReadOnlyList<IReadOnlyDictionary<string, object?>> rows)
{
using var workbook = new XLWorkbook();
var sheetName = string.IsNullOrWhiteSpace(worksheetName) ? "Export" : worksheetName.Trim();
var ws = workbook.Worksheets.Add(sheetName.Length > 31 ? sheetName[..31] : sheetName);
var headers = rows
.SelectMany(r => r.Keys)
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
for (var i = 0; i < headers.Count; i++)
{
ws.Cell(1, i + 1).Value = headers[i];
ws.Cell(1, i + 1).Style.Font.Bold = true;
}
for (var rowIndex = 0; rowIndex < rows.Count; rowIndex++)
{
var row = rows[rowIndex];
for (var colIndex = 0; colIndex < headers.Count; colIndex++)
{
row.TryGetValue(headers[colIndex], out var value);
ws.Cell(rowIndex + 2, colIndex + 1).Value = value?.ToString() ?? string.Empty;
}
}
ws.Columns().AdjustToContents();
workbook.SaveAs(fullPath);
}
} }
@@ -70,7 +70,6 @@ public class ExportOrchestrationService
private async Task<SiteExportResult?> ExportSiteAsync(Site site) private async Task<SiteExportResult?> ExportSiteAsync(Site site)
{ {
if (site.HanaServer is null) return null;
SiteExportResult? result = null; SiteExportResult? result = null;
lock (_lock) lock (_lock)
@@ -6,4 +6,5 @@ public interface IExcelExportService
{ {
string CreateExcelFile(string outputDirectory, string tsc, DateTime fileDate, List<SalesRecord> records); string CreateExcelFile(string outputDirectory, string tsc, DateTime fileDate, List<SalesRecord> records);
string CreateConsolidatedExcelFile(string outputDirectory, DateTime fileDate, List<SalesRecord> records); string CreateConsolidatedExcelFile(string outputDirectory, DateTime fileDate, List<SalesRecord> records);
string CreateGenericExcelFile(string outputDirectory, string filePrefix, DateTime fileDate, string worksheetName, IReadOnlyList<IReadOnlyDictionary<string, object?>> rows);
} }
@@ -0,0 +1,8 @@
namespace TrafagSalesExporter.Services;
public interface ISapGatewayService
{
Task TestConnectionAsync(string serviceUrl, string username, string password, CancellationToken cancellationToken = default);
Task<List<string>> GetEntitySetsAsync(string serviceUrl, string username, string password, CancellationToken cancellationToken = default);
Task<List<Dictionary<string, object?>>> GetEntityRowsAsync(string serviceUrl, string entitySet, string username, string password, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,140 @@
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Xml.Linq;
namespace TrafagSalesExporter.Services;
public class SapGatewayService : ISapGatewayService
{
private static readonly XNamespace AppNs = "http://www.w3.org/2007/app";
private static readonly XNamespace EdmNs = "http://docs.oasis-open.org/odata/ns/edm";
public async Task TestConnectionAsync(string serviceUrl, string username, string password, CancellationToken cancellationToken = default)
{
using var client = CreateClient(username, password);
using var response = await client.GetAsync(BuildServiceUri(serviceUrl), cancellationToken);
response.EnsureSuccessStatusCode();
}
public async Task<List<string>> GetEntitySetsAsync(string serviceUrl, string username, string password, CancellationToken cancellationToken = default)
{
using var client = CreateClient(username, password);
var baseUrl = BuildServiceUri(serviceUrl);
var entitySets = await TryReadEntitySetsFromServiceRootAsync(client, baseUrl, cancellationToken);
if (entitySets.Count > 0)
return entitySets;
return await ReadEntitySetsFromMetadataAsync(client, baseUrl, cancellationToken);
}
public async Task<List<Dictionary<string, object?>>> GetEntityRowsAsync(string serviceUrl, string entitySet, string username, string password, CancellationToken cancellationToken = default)
{
using var client = CreateClient(username, password);
var requestUrl = $"{BuildServiceUri(serviceUrl)}{entitySet}?$format=json";
using var response = await client.GetAsync(requestUrl, cancellationToken);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync(cancellationToken);
using var document = JsonDocument.Parse(json);
if (!document.RootElement.TryGetProperty("d", out var dNode))
return [];
if (!dNode.TryGetProperty("results", out var resultsNode) || resultsNode.ValueKind != JsonValueKind.Array)
return [];
var rows = new List<Dictionary<string, object?>>();
foreach (var item in resultsNode.EnumerateArray())
{
var row = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
foreach (var property in item.EnumerateObject())
{
row[property.Name] = ConvertJsonValue(property.Value);
}
rows.Add(row);
}
return rows;
}
private static HttpClient CreateClient(string username, string password)
{
var client = new HttpClient();
client.Timeout = TimeSpan.FromSeconds(15);
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/atomsvc+xml"));
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml"));
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}")));
return client;
}
private static string BuildServiceUri(string serviceUrl)
{
var trimmed = serviceUrl.Trim();
if (string.IsNullOrWhiteSpace(trimmed))
throw new InvalidOperationException("SAP Service URL darf nicht leer sein.");
var entityPathMarker = "/sap/opu/odata/sap/";
var markerIndex = trimmed.IndexOf(entityPathMarker, StringComparison.OrdinalIgnoreCase);
if (markerIndex >= 0)
{
var servicePath = trimmed[(markerIndex + entityPathMarker.Length)..].Trim('/');
var parts = servicePath.Split('/', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length > 1)
{
trimmed = $"{trimmed[..(markerIndex + entityPathMarker.Length)]}{parts[0]}/";
}
}
return trimmed.EndsWith('/') ? trimmed : $"{trimmed}/";
}
private static async Task<List<string>> TryReadEntitySetsFromServiceRootAsync(HttpClient client, string baseUrl, CancellationToken cancellationToken)
{
using var response = await client.GetAsync(baseUrl, cancellationToken);
response.EnsureSuccessStatusCode();
var xml = await response.Content.ReadAsStringAsync(cancellationToken);
var document = XDocument.Parse(xml);
return document
.Descendants(AppNs + "collection")
.Select(x => x.Attribute("href")?.Value ?? string.Empty)
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(x => x, StringComparer.OrdinalIgnoreCase)
.ToList();
}
private static async Task<List<string>> ReadEntitySetsFromMetadataAsync(HttpClient client, string baseUrl, CancellationToken cancellationToken)
{
using var response = await client.GetAsync($"{baseUrl}$metadata", cancellationToken);
response.EnsureSuccessStatusCode();
var xml = await response.Content.ReadAsStringAsync(cancellationToken);
var document = XDocument.Parse(xml);
return document
.Descendants(EdmNs + "EntitySet")
.Select(x => x.Attribute("Name")?.Value ?? string.Empty)
.Where(x => !string.IsNullOrWhiteSpace(x))
.Distinct(StringComparer.OrdinalIgnoreCase)
.OrderBy(x => x, StringComparer.OrdinalIgnoreCase)
.ToList();
}
private static object? ConvertJsonValue(JsonElement element) => element.ValueKind switch
{
JsonValueKind.String => element.GetString(),
JsonValueKind.Number => element.ToString(),
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Null => null,
_ => element.ToString()
};
}
@@ -9,6 +9,7 @@ public class SiteExportService : ISiteExportService
{ {
private readonly IDbContextFactory<AppDbContext> _dbFactory; private readonly IDbContextFactory<AppDbContext> _dbFactory;
private readonly IHanaQueryService _hanaService; private readonly IHanaQueryService _hanaService;
private readonly ISapGatewayService _sapGatewayService;
private readonly IExcelExportService _excelService; private readonly IExcelExportService _excelService;
private readonly ISharePointUploadService _sharePointService; private readonly ISharePointUploadService _sharePointService;
private readonly IRecordTransformationService _transformationService; private readonly IRecordTransformationService _transformationService;
@@ -17,6 +18,7 @@ public class SiteExportService : ISiteExportService
public SiteExportService( public SiteExportService(
IDbContextFactory<AppDbContext> dbFactory, IDbContextFactory<AppDbContext> dbFactory,
IHanaQueryService hanaService, IHanaQueryService hanaService,
ISapGatewayService sapGatewayService,
IExcelExportService excelService, IExcelExportService excelService,
ISharePointUploadService sharePointService, ISharePointUploadService sharePointService,
IRecordTransformationService transformationService, IRecordTransformationService transformationService,
@@ -24,6 +26,7 @@ public class SiteExportService : ISiteExportService
{ {
_dbFactory = dbFactory; _dbFactory = dbFactory;
_hanaService = hanaService; _hanaService = hanaService;
_sapGatewayService = sapGatewayService;
_excelService = excelService; _excelService = excelService;
_sharePointService = sharePointService; _sharePointService = sharePointService;
_transformationService = transformationService; _transformationService = transformationService;
@@ -32,9 +35,6 @@ public class SiteExportService : ISiteExportService
public async Task<SiteExportResult> ExportAsync(Site site, Action<string>? updateStatus = null) 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 sw = Stopwatch.StartNew();
var log = new ExportLog var log = new ExportLog
{ {
@@ -49,22 +49,44 @@ public class SiteExportService : ISiteExportService
using var db = await _dbFactory.CreateDbContextAsync(); using var db = await _dbFactory.CreateDbContextAsync();
var settings = await db.ExportSettings.FirstOrDefaultAsync() ?? new ExportSettings(); var settings = await db.ExportSettings.FirstOrDefaultAsync() ?? new ExportSettings();
var spConfig = await db.SharePointConfigs.FirstOrDefaultAsync(); var spConfig = await db.SharePointConfigs.FirstOrDefaultAsync();
var exportServer = BuildEffectiveServer(site, settings); var outputDir = Path.Combine(AppContext.BaseDirectory, "output");
var sourceSystem = NormalizeSourceSystem(site.SourceSystem);
var records = new List<SalesRecord>();
string filePath;
if (sourceSystem == "SAP")
{
var credentials = ResolveCredentials(site, settings, sourceSystem);
if (string.IsNullOrWhiteSpace(site.SapServiceUrl))
throw new InvalidOperationException($"Standort '{site.Land}' hat keine SAP Service URL.");
if (string.IsNullOrWhiteSpace(site.SapEntitySet))
throw new InvalidOperationException($"Standort '{site.Land}' hat kein SAP Entity Set ausgewählt.");
updateStatus?.Invoke("SAP Gateway Abfrage...");
var rows = await _sapGatewayService.GetEntityRowsAsync(site.SapServiceUrl, site.SapEntitySet, credentials.Username, credentials.Password);
updateStatus?.Invoke("Excel erstellen...");
filePath = _excelService.CreateGenericExcelFile(outputDir, $"SAP_{site.TSC}_{site.SapEntitySet}", DateTime.UtcNow.Date, site.SapEntitySet, rows);
log.RowCount = rows.Count;
}
else
{
var exportServer = BuildEffectiveServer(site, settings, sourceSystem);
updateStatus?.Invoke("HANA Abfrage..."); updateStatus?.Invoke("HANA Abfrage...");
var records = await Task.Run(() => _hanaService.GetSalesRecords( records = await Task.Run(() => _hanaService.GetSalesRecords(
exportServer, site.Schema, site.TSC, site.Land, settings.DateFilter)); exportServer, site.Schema, site.TSC, site.Land, settings.DateFilter));
updateStatus?.Invoke("Transformationen anwenden..."); updateStatus?.Invoke("Transformationen anwenden...");
var rules = await db.FieldTransformationRules var rules = await db.FieldTransformationRules
.Where(r => r.IsActive && r.SourceSystem == (string.IsNullOrWhiteSpace(site.SourceSystem) ? "SAP" : site.SourceSystem)) .Where(r => r.IsActive && r.SourceSystem == sourceSystem)
.OrderBy(r => r.SortOrder) .OrderBy(r => r.SortOrder)
.ToListAsync(); .ToListAsync();
_transformationService.Apply(records, rules); _transformationService.Apply(records, rules);
updateStatus?.Invoke("Excel erstellen..."); updateStatus?.Invoke("Excel erstellen...");
var outputDir = Path.Combine(AppContext.BaseDirectory, "output"); filePath = _excelService.CreateExcelFile(outputDir, site.TSC, DateTime.UtcNow.Date, records);
var filePath = _excelService.CreateExcelFile(outputDir, site.TSC, DateTime.UtcNow.Date, records); log.RowCount = records.Count;
}
var fileName = Path.GetFileName(filePath); var fileName = Path.GetFileName(filePath);
if (spConfig is not null && if (spConfig is not null &&
@@ -80,12 +102,11 @@ public class SiteExportService : ISiteExportService
sw.Stop(); sw.Stop();
log.Status = "OK"; log.Status = "OK";
log.RowCount = records.Count;
log.FileName = fileName; log.FileName = fileName;
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, records.Count, sw.Elapsed.TotalSeconds); site.Land, site.TSC, log.RowCount, sw.Elapsed.TotalSeconds);
return new SiteExportResult return new SiteExportResult
{ {
@@ -113,14 +134,12 @@ public class SiteExportService : ISiteExportService
} }
} }
private static HanaServer BuildEffectiveServer(Site site, ExportSettings settings) private static HanaServer BuildEffectiveServer(Site site, ExportSettings settings, string sourceSystem)
{ {
if (site.HanaServer is null) if (site.HanaServer is null)
throw new InvalidOperationException($"Standort '{site.Land}' hat keinen HANA-Server."); throw new InvalidOperationException($"Standort '{site.Land}' hat keinen HANA-Server.");
var sourceSystem = string.IsNullOrWhiteSpace(site.SourceSystem) ? "SAP" : site.SourceSystem.Trim().ToUpperInvariant(); var credentials = ResolveCredentials(site, settings, sourceSystem);
var inheritedUsername = GetCentralUsername(sourceSystem, settings);
var inheritedPassword = GetCentralPassword(sourceSystem, settings);
return new HanaServer return new HanaServer
{ {
@@ -128,8 +147,8 @@ public class SiteExportService : ISiteExportService
Name = site.HanaServer.Name, Name = site.HanaServer.Name,
Host = site.HanaServer.Host, Host = site.HanaServer.Host,
Port = site.HanaServer.Port, Port = site.HanaServer.Port,
Username = FirstNonEmpty(site.UsernameOverride, inheritedUsername, site.HanaServer.Username), Username = FirstNonEmpty(credentials.Username, site.HanaServer.Username),
Password = FirstNonEmpty(site.PasswordOverride, inheritedPassword, site.HanaServer.Password), Password = FirstNonEmpty(credentials.Password, site.HanaServer.Password),
DatabaseName = site.HanaServer.DatabaseName, DatabaseName = site.HanaServer.DatabaseName,
UseSsl = site.HanaServer.UseSsl, UseSsl = site.HanaServer.UseSsl,
ValidateCertificate = site.HanaServer.ValidateCertificate, ValidateCertificate = site.HanaServer.ValidateCertificate,
@@ -137,6 +156,10 @@ public class SiteExportService : ISiteExportService
}; };
} }
private static (string Username, string Password) ResolveCredentials(Site site, ExportSettings settings, string sourceSystem)
=> (FirstNonEmpty(site.UsernameOverride, GetCentralUsername(sourceSystem, settings)),
FirstNonEmpty(site.PasswordOverride, GetCentralPassword(sourceSystem, settings)));
private static string GetCentralUsername(string sourceSystem, ExportSettings settings) => sourceSystem switch private static string GetCentralUsername(string sourceSystem, ExportSettings settings) => sourceSystem switch
{ {
"BI1" => settings.Bi1Username, "BI1" => settings.Bi1Username,
@@ -151,6 +174,9 @@ public class SiteExportService : ISiteExportService
_ => settings.SapPassword _ => settings.SapPassword
}; };
private static string NormalizeSourceSystem(string? sourceSystem)
=> string.IsNullOrWhiteSpace(sourceSystem) ? "SAP" : sourceSystem.Trim().ToUpperInvariant();
private static string FirstNonEmpty(params string[] values) private static string FirstNonEmpty(params string[] values)
{ {
foreach (var value in values) foreach (var value in values)
View File