@page "/settings" @using Microsoft.EntityFrameworkCore @using TrafagSalesExporter.Data @using TrafagSalesExporter.Models @using TrafagSalesExporter.Services @inject IDbContextFactory DbFactory @inject ISharePointUploadService SpService @inject TimerBackgroundService TimerService @inject IHanaQueryService HanaService @inject ISapGatewayService SapGatewayService @inject IConfigTransferService ConfigTransferService @inject IExchangeRateImportService ExchangeRateImportService @inject IJSRuntime JS @inject ISnackbar Snackbar Settings Settings Konfiguration Import/Export Wenn deaktiviert, bleiben Passwörter und Secrets beim Export leer. Beim Import ohne Secrets werden bestehende Secrets auf dem Zielsystem beibehalten. @(_exportingConfig ? "Exportiere..." : "Konfiguration exportieren") @(_importingConfig ? "Importiere..." : "Konfiguration importieren") @* SharePoint Config *@ SharePoint Konfiguration Speichern @if (_testingSp) { @("Teste...") } else { @("SharePoint Verbindung testen") } @if (!string.IsNullOrWhiteSpace(_sharePointTestPreview)) {
Test Preview
@_sharePointTestPreview
}
Zentrale Quellsystem-Zugangsdaten Diese Zugangsdaten werden pro Quellsystem als Standard verwendet. Ein Standort kann sie bei Bedarf mit eigenen Overrides überschreiben. SAP @if (_testingSystems.Contains("SAP")) { @("Teste...") } else { @("SAP testen") } BI1 @if (_testingSystems.Contains("BI1")) { @("Teste...") } else { @("BI1 testen") } SAGE @if (_testingSystems.Contains("SAGE")) { @("Teste...") } else { @("SAGE testen") } Speichern Wechselkurse Diese Kurstabelle wird von der Transformation ConvertCurrency verwendet. Gleiche Waehrung rechnet automatisch mit Faktor 1. Kurs hinzufuegen @(_refreshingExchangeRates ? "Aktualisiere ECB-Kurse..." : "Refresh Kurse") Kurse speichern Von Nach Kurs Gueltig ab Gueltig bis Notiz Aktiv @* Export Settings *@ Export Einstellungen Schreibt zusätzliche technische Fortschrittsmeldungen für HANA- und SAP-Lesevorgänge ins Dashboard und in die Logs. Speichern @* Filename Preview *@ Dateiname Vorschau Sales_{"{TSC}"}_{DateTime.Now:yyyy-MM-dd}.xlsx Beispiel: Sales_TRFR_@(DateTime.Now.ToString("yyyy-MM-dd")).xlsx @code { private SharePointConfig _spConfig = new(); private ExportSettings _exportSettings = new(); private bool _testingSp; private bool _includeSecretsInExport; private bool _exportingConfig; private bool _importingConfig; private bool _refreshingExchangeRates; private string _sharePointTestPreview = string.Empty; private List _exchangeRates = []; private readonly HashSet _testingSystems = []; protected override async Task OnInitializedAsync() { using var db = await DbFactory.CreateDbContextAsync(); _spConfig = await db.SharePointConfigs.FirstOrDefaultAsync() ?? new SharePointConfig(); _exportSettings = await db.ExportSettings.FirstOrDefaultAsync() ?? new ExportSettings(); _exchangeRates = await db.CurrencyExchangeRates .OrderBy(x => x.FromCurrency) .ThenBy(x => x.ToCurrency) .ThenByDescending(x => x.ValidFrom) .ToListAsync(); } private async Task SaveSharePoint() { using var db = await DbFactory.CreateDbContextAsync(); var existing = await db.SharePointConfigs.FirstOrDefaultAsync(); if (existing is null) { db.SharePointConfigs.Add(_spConfig); } else { existing.SiteUrl = _spConfig.SiteUrl; existing.ExportFolder = _spConfig.ExportFolder; existing.CentralExportFolder = _spConfig.CentralExportFolder; existing.TenantId = _spConfig.TenantId; existing.ClientId = _spConfig.ClientId; existing.ClientSecret = _spConfig.ClientSecret; } await db.SaveChangesAsync(); Snackbar.Add("SharePoint Konfiguration gespeichert", Severity.Success); } private async Task TestSharePoint() { _testingSp = true; try { var tenantId = NormalizeConfigValue(_spConfig.TenantId); var clientId = NormalizeConfigValue(_spConfig.ClientId); var clientSecret = NormalizeConfigValue(_spConfig.ClientSecret); var siteUrl = NormalizeConfigValue(_spConfig.SiteUrl); _sharePointTestPreview = BuildSharePointTestPreview(tenantId, clientId, clientSecret, siteUrl); await SpService.TestConnectionAsync( tenantId, clientId, clientSecret, siteUrl); Snackbar.Add("SharePoint Verbindung erfolgreich!", Severity.Success); } catch (Exception ex) { Snackbar.Add($"Verbindung fehlgeschlagen: {ex.Message}", Severity.Error); } finally { _testingSp = false; } } private async Task SaveExportSettings() { using var db = await DbFactory.CreateDbContextAsync(); var existing = await db.ExportSettings.FirstOrDefaultAsync(); if (existing is null) { db.ExportSettings.Add(_exportSettings); } else { existing.DateFilter = _exportSettings.DateFilter; existing.TimerHour = _exportSettings.TimerHour; existing.TimerMinute = _exportSettings.TimerMinute; existing.TimerEnabled = _exportSettings.TimerEnabled; existing.DebugLoggingEnabled = _exportSettings.DebugLoggingEnabled; existing.LocalSiteExportFolder = _exportSettings.LocalSiteExportFolder; existing.LocalConsolidatedExportFolder = _exportSettings.LocalConsolidatedExportFolder; existing.SapUsername = _exportSettings.SapUsername; existing.SapPassword = _exportSettings.SapPassword; existing.Bi1Username = _exportSettings.Bi1Username; existing.Bi1Password = _exportSettings.Bi1Password; existing.SageUsername = _exportSettings.SageUsername; existing.SagePassword = _exportSettings.SagePassword; } await db.SaveChangesAsync(); TimerService.Recalculate(); Snackbar.Add("Export Einstellungen gespeichert", Severity.Success); } private void AddExchangeRate() { _exchangeRates.Add(new CurrencyExchangeRate { FromCurrency = "USD", ToCurrency = "EUR", Rate = 1m, ValidFrom = DateTime.Today, IsActive = true }); } private void RemoveExchangeRate(CurrencyExchangeRate rate) { _exchangeRates.Remove(rate); } private async Task SaveExchangeRates() { using var db = await DbFactory.CreateDbContextAsync(); var existingRates = await db.CurrencyExchangeRates.ToListAsync(); if (existingRates.Count > 0) db.CurrencyExchangeRates.RemoveRange(existingRates); db.CurrencyExchangeRates.AddRange(_exchangeRates.Select(rate => new CurrencyExchangeRate { FromCurrency = NormalizeConfigValue(rate.FromCurrency).ToUpperInvariant(), ToCurrency = NormalizeConfigValue(rate.ToCurrency).ToUpperInvariant(), Rate = rate.Rate, ValidFrom = rate.ValidFrom.Date, ValidTo = rate.ValidTo?.Date, Notes = NormalizeConfigValue(rate.Notes), IsActive = rate.IsActive }).Where(rate => !string.IsNullOrWhiteSpace(rate.FromCurrency) && !string.IsNullOrWhiteSpace(rate.ToCurrency) && rate.Rate > 0m)); await db.SaveChangesAsync(); _exchangeRates = await db.CurrencyExchangeRates .OrderBy(x => x.FromCurrency) .ThenBy(x => x.ToCurrency) .ThenByDescending(x => x.ValidFrom) .ToListAsync(); Snackbar.Add("Wechselkurse gespeichert", Severity.Success); } private async Task RefreshEcbRates() { if (_refreshingExchangeRates) return; _refreshingExchangeRates = true; try { var result = await ExchangeRateImportService.RefreshEcbRatesAsync(); _exchangeRates = await LoadExchangeRatesAsync(); Snackbar.Add($"ECB-Kurse aktualisiert: {result.ImportedCount} Kurse vom {result.RateDate:yyyy-MM-dd}.", Severity.Success); } catch (Exception ex) { Snackbar.Add($"ECB-Kursimport fehlgeschlagen: {ex.Message}", Severity.Error); } finally { _refreshingExchangeRates = false; } } private async Task ExportConfiguration() { if (_exportingConfig) return; _exportingConfig = true; try { var json = await ConfigTransferService.ExportJsonAsync(_includeSecretsInExport); var suffix = _includeSecretsInExport ? "with-secrets" : "without-secrets"; var fileName = $"trafag-config-{DateTime.UtcNow:yyyyMMdd-HHmmss}-{suffix}.json"; await JS.InvokeVoidAsync("trafagDownload.saveTextFile", fileName, json, "application/json;charset=utf-8"); Snackbar.Add("Konfiguration exportiert", Severity.Success); } catch (Exception ex) { Snackbar.Add($"Export fehlgeschlagen: {ex.Message}", Severity.Error); } finally { _exportingConfig = false; } } private async Task ImportConfiguration(InputFileChangeEventArgs args) { if (_importingConfig) return; _importingConfig = true; try { var file = args.File; await using var stream = file.OpenReadStream(5 * 1024 * 1024); using var reader = new StreamReader(stream); var json = await reader.ReadToEndAsync(); await ConfigTransferService.ImportJsonAsync(json); using var db = await DbFactory.CreateDbContextAsync(); _spConfig = await db.SharePointConfigs.FirstOrDefaultAsync() ?? new SharePointConfig(); _exportSettings = await db.ExportSettings.FirstOrDefaultAsync() ?? new ExportSettings(); _exchangeRates = await db.CurrencyExchangeRates .OrderBy(x => x.FromCurrency) .ThenBy(x => x.ToCurrency) .ThenByDescending(x => x.ValidFrom) .ToListAsync(); TimerService.Recalculate(); Snackbar.Add("Konfiguration importiert", Severity.Success); } catch (Exception ex) { Snackbar.Add($"Import fehlgeschlagen: {ex.Message}", Severity.Error); } finally { _importingConfig = false; } } 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)) return; try { var username = GetCentralUsername(sourceSystem); var password = GetCentralPassword(sourceSystem); if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password)) { Snackbar.Add($"Für {sourceSystem} sind keine zentralen Zugangsdaten gepflegt.", Severity.Warning); return; } using var db = await DbFactory.CreateDbContextAsync(); var site = await db.Sites .Include(s => s.HanaServer) .Where(s => (string.IsNullOrWhiteSpace(s.SourceSystem) ? "SAP" : s.SourceSystem) == sourceSystem) .OrderBy(s => s.Land) .FirstOrDefaultAsync(); if (site?.HanaServer is null) { Snackbar.Add($"Kein Standort mit Quellsystem {sourceSystem} und HANA-Verbindung gefunden.", Severity.Warning); return; } var testServer = new HanaServer { Name = $"{sourceSystem} Central Test", Host = site.HanaServer.Host, Port = site.HanaServer.Port, Username = username.Trim(), Password = password.Trim(), DatabaseName = site.HanaServer.DatabaseName, UseSsl = site.HanaServer.UseSsl, ValidateCertificate = site.HanaServer.ValidateCertificate, AdditionalParams = site.HanaServer.AdditionalParams }; var result = await Task.Run(() => HanaService.TestConnectionDetailed(testServer)); if (result.Success) { Snackbar.Add($"{sourceSystem}: Verbindung erfolgreich über Standort '{site.Land}'.", Severity.Success); } else { Snackbar.Add($"{sourceSystem}: {result.ExceptionType} - {result.ErrorMessage}", Severity.Error); } } finally { _testingSystems.Remove(sourceSystem); } } 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 { "BI1" => _exportSettings.Bi1Username, "SAGE" => _exportSettings.SageUsername, _ => _exportSettings.SapUsername }; private string GetCentralPassword(string sourceSystem) => sourceSystem switch { "BI1" => _exportSettings.Bi1Password, "SAGE" => _exportSettings.SagePassword, _ => _exportSettings.SapPassword }; private static string NormalizeConfigValue(string? value) => value?.Trim() ?? string.Empty; private static string BuildSharePointTestPreview(string tenantId, string clientId, string clientSecret, string siteUrl) { var maskedSecret = string.IsNullOrEmpty(clientSecret) ? "" : $"{new string('*', Math.Min(clientSecret.Length, 8))} (len={clientSecret.Length})"; return string.Join(Environment.NewLine, [ $"Tenant ID: {tenantId}", $"Client ID: {clientId}", $"Client Secret: {maskedSecret}", $"Site URL: {siteUrl}" ]); } private async Task> LoadExchangeRatesAsync() { using var db = await DbFactory.CreateDbContextAsync(); return await db.CurrencyExchangeRates .OrderBy(x => x.FromCurrency) .ThenBy(x => x.ToCurrency) .ThenByDescending(x => x.ValidFrom) .ToListAsync(); } }