@page "/settings" @rendermode @(Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer) @attribute [Authorize(Policy = TrafagSalesExporter.Security.SecurityPolicies.AdminOnly)] @using TrafagSalesExporter.Models @using TrafagSalesExporter.Services @inject ISettingsPageService SettingsPageActions @inject IJSRuntime JS @inject ISnackbar Snackbar @inject IUiTextService UiText @T("Einstellungen", "Settings") @T("Einstellungen", "Settings") @T("Konfiguration Import/Export", "Import/export configuration") @T("Wenn deaktiviert, bleiben Passwoerter und Secrets beim Export leer. Beim Import ohne Secrets werden bestehende Secrets auf dem Zielsystem beibehalten.", "If disabled, passwords and secrets remain empty during export. When importing without secrets, existing secrets on the target system are kept.") @(_exportingConfig ? T("Exportiere...", "Exporting...") : T("Konfiguration exportieren", "Export configuration")) @(_importingConfig ? T("Importiere...", "Importing...") : T("Konfiguration importieren", "Import configuration")) @* SharePoint Config *@ @T("SharePoint Konfiguration", "SharePoint configuration") @T("Speichern", "Save") @if (_testingSp) { @T("Teste...", "Testing...") } else { @T("SharePoint Verbindung testen", "Test SharePoint connection") } @if (!string.IsNullOrWhiteSpace(_sharePointTestPreview)) {
@T("Testvorschau", "Test preview")
@_sharePointTestPreview
}
@T("Quellsysteme", "Source systems") @T("Diese Zugangsdaten werden pro Quellsystem als Standard verwendet. Ein Standort kann sie bei Bedarf mit eigenen Overrides ueberschreiben.", "These credentials are used as defaults per source system. A site can override them if needed.") @T("Quellsystem hinzufuegen", "Add source system") Code @T("Name", "Name") @T("Anschlussart", "Connection type") @T("Zentrale URL", "Central URL") User @T("Aktiv", "Active") @T("Test", "Test") @context.Code @context.DisplayName @GetConnectionKindLabel(context.ConnectionKind) @GetServiceUrlSummary(context) @GetUsernameSummary(context) @if (context.IsActive) { } else { } @if (!UsesManualImport(context)) { @(_testingSystems.Contains(context.Code) ? T("Teste...", "Testing...") : T("Testen", "Test")) } @T("Quellsysteme speichern", "Save source systems") @(_editingSourceSystem.Id == 0 ? T("Quellsystem hinzufuegen", "Add source system") : T("Quellsystem bearbeiten", "Edit source system")) @foreach (var kind in SourceSystemConnectionKinds.All) { @GetConnectionKindLabel(kind) } @if (UsesSapGateway(_editingSourceSystem)) { } @T("Abbrechen", "Cancel") @T("Uebernehmen", "Apply") @T("Wechselkurse", "Exchange rates") @((MarkupString)T("Diese Kurstabelle wird von der Transformation ConvertCurrency verwendet. Gleiche Waehrung rechnet automatisch mit Faktor 1.", "This rate table is used by the ConvertCurrency transformation. Same-currency conversion automatically uses factor 1.")) @T("Kurs hinzufuegen", "Add rate") @(_refreshingExchangeRates ? T("Aktualisiere ECB-Kurse...", "Refreshing ECB rates...") : T("Refresh Kurse", "Refresh rates")) @T("Kurse speichern", "Save rates") @T("Von", "From") @T("Nach", "To") @T("Kurs", "Rate") @T("Gueltig ab", "Valid from") @T("Gueltig bis", "Valid to") @T("Notiz", "Note") @T("Aktiv", "Active") @* Export Settings *@ @T("Export Einstellungen", "Export settings") @T("PostingDate / Buchungsdatum", "PostingDate / posting date") @T("InvoiceDate / Rechnungsdatum", "InvoiceDate / invoice date") @T("ExtractionDate / Extraktionsdatum", "ExtractionDate / extraction date") @T("Schreibt zusaetzliche technische Fortschrittsmeldungen fuer HANA- und SAP-Lesevorgaenge ins Dashboard und in die Logs.", "Writes additional technical progress messages for HANA and SAP reads to the dashboard and logs.")
@T("Audit-CSV / nachvollziehbarer Datenfluss", "Audit CSV / traceable data flow") @T("Fuer Finance und Wirtschaftspruefung: lesbare Standort-CSV nach Mapping und Konvertierung, optional als Quelle fuer zentrale Auswertungen.", "For finance and auditors: readable site CSV after mapping and conversion, optionally as the source for central analyses.")
@T("Schreibt beim Laenderexport je Standort eine Sales_*.csv mit den transformierten Daten.", "Writes one Sales_*.csv per site during country export with the transformed data.") @T("Dashboard, zentrale Excel-Datei und Finance-Auswertungen lesen die neuesten Standort-CSV-Dateien statt CentralSalesRecords.", "Dashboard, central Excel file and finance analyses read the latest site CSV files instead of CentralSalesRecords.") @((MarkupString)T("Audit-CSV wird immer im gleichen Ordner wie die lokalen Standort-Dateien abgelegt. Der Pfad wird oben bei Lokaler Standardpfad Standort-Dateien gesetzt.", "Audit CSV is always stored in the same folder as the local site files. The path is set above under Local default path for site files."))
@T("Speichern", "Save")
@* Filename Preview *@ @T("Dateiname Vorschau", "Filename preview") Sales_{"{TSC}"}_{DateTime.Now:yyyy-MM-dd}.xlsx @T("Beispiel:", "Example:") Sales_TRFR_@(DateTime.Now.ToString("yyyy-MM-dd")).xlsx @code { private SharePointConfig _spConfig = new(); private ExportSettings _exportSettings = new(); private List _sourceSystems = []; private SourceSystemDefinition _editingSourceSystem = 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 = []; private bool _sourceSystemDialogVisible; private readonly DialogOptions _sourceSystemDialogOptions = new() { MaxWidth = MaxWidth.Small, FullWidth = true }; protected override async Task OnInitializedAsync() { var state = await SettingsPageActions.LoadAsync(); _spConfig = state.SharePointConfig; _exportSettings = state.ExportSettings; _sourceSystems = state.SourceSystems; _exchangeRates = state.ExchangeRates; } private async Task SaveSharePoint() { await SettingsPageActions.SaveSharePointAsync(_spConfig); Snackbar.Add(T("SharePoint Konfiguration gespeichert", "SharePoint configuration saved"), Severity.Success); } private async Task TestSharePoint() { _testingSp = true; try { _sharePointTestPreview = await SettingsPageActions.BuildSharePointTestPreviewAsync(_spConfig); Snackbar.Add(T("SharePoint Verbindung erfolgreich!", "SharePoint connection successful!"), Severity.Success); } catch (Exception ex) { Snackbar.Add($"{T("Verbindung fehlgeschlagen", "Connection failed")}: {ex.Message}", Severity.Error); } finally { _testingSp = false; } } private async Task SaveExportSettings() { await SettingsPageActions.SaveExportSettingsAsync(_exportSettings); Snackbar.Add(T("Export Einstellungen gespeichert", "Export settings saved"), Severity.Success); } private void AddSourceSystem() { _editingSourceSystem = new SourceSystemDefinition { Code = string.Empty, DisplayName = string.Empty, ConnectionKind = SourceSystemConnectionKinds.Hana, IsActive = true }; _sourceSystemDialogVisible = true; } private void EditSourceSystem(SourceSystemDefinition definition) { _editingSourceSystem = new SourceSystemDefinition { Id = definition.Id, Code = definition.Code, DisplayName = definition.DisplayName, ConnectionKind = definition.ConnectionKind, IsActive = definition.IsActive, CentralServiceUrl = definition.CentralServiceUrl, CentralUsername = definition.CentralUsername, CentralPassword = definition.CentralPassword }; _sourceSystemDialogVisible = true; } private void SaveSourceSystemEdit() { _editingSourceSystem.Code = NormalizeSourceSystemCode(_editingSourceSystem.Code); _editingSourceSystem.DisplayName = NormalizeConfigValue(_editingSourceSystem.DisplayName); _editingSourceSystem.ConnectionKind = NormalizeConnectionKind(_editingSourceSystem.ConnectionKind); _editingSourceSystem.CentralServiceUrl = NormalizeConfigValue(_editingSourceSystem.CentralServiceUrl); _editingSourceSystem.CentralUsername = NormalizeConfigValue(_editingSourceSystem.CentralUsername); _editingSourceSystem.CentralPassword = _editingSourceSystem.CentralPassword ?? string.Empty; if (string.IsNullOrWhiteSpace(_editingSourceSystem.Code) || string.IsNullOrWhiteSpace(_editingSourceSystem.DisplayName)) { Snackbar.Add(T("Code und Name fuer das Quellsystem sind Pflicht.", "Code and name are required for the source system."), Severity.Warning); return; } if (_sourceSystems.Any(x => x.Id != _editingSourceSystem.Id && x.Code == _editingSourceSystem.Code)) { Snackbar.Add($"{T("Quellsystem-Code doppelt vorhanden", "Duplicate source-system code")}: {_editingSourceSystem.Code}", Severity.Warning); return; } if (_editingSourceSystem.Id == 0) { _sourceSystems.Add(_editingSourceSystem); } else { var existing = _sourceSystems.FirstOrDefault(x => x.Id == _editingSourceSystem.Id); if (existing is not null) { existing.Code = _editingSourceSystem.Code; existing.DisplayName = _editingSourceSystem.DisplayName; existing.ConnectionKind = _editingSourceSystem.ConnectionKind; existing.IsActive = _editingSourceSystem.IsActive; existing.CentralServiceUrl = _editingSourceSystem.CentralServiceUrl; existing.CentralUsername = _editingSourceSystem.CentralUsername; existing.CentralPassword = _editingSourceSystem.CentralPassword; } } _sourceSystems = _sourceSystems.OrderBy(x => x.Code).ToList(); _sourceSystemDialogVisible = false; } private void CloseSourceSystemDialog() { _sourceSystemDialogVisible = false; } private void RemoveSourceSystem(SourceSystemDefinition definition) { _sourceSystems.Remove(definition); } private async Task SaveSourceSystems() { try { _sourceSystems = await SettingsPageActions.SaveSourceSystemsAsync(_sourceSystems); Snackbar.Add(T("Quellsysteme gespeichert", "Source systems saved"), Severity.Success); } catch (Exception ex) { Snackbar.Add(ex.Message, Severity.Warning); } } 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() { _exchangeRates = await SettingsPageActions.SaveExchangeRatesAsync(_exchangeRates); Snackbar.Add(T("Wechselkurse gespeichert", "Exchange rates saved"), Severity.Success); } private async Task RefreshEcbRates() { if (_refreshingExchangeRates) return; _refreshingExchangeRates = true; try { var result = await SettingsPageActions.RefreshEcbRatesAsync(); _exchangeRates = result.ExchangeRates; Snackbar.Add($"{T("ECB-Kurse aktualisiert", "ECB rates refreshed")}: {result.ImportedCount} {T("Kurse vom", "rates from")} {result.RateDate:yyyy-MM-dd}.", Severity.Success); } catch (Exception ex) { Snackbar.Add($"{T("ECB-Kursimport fehlgeschlagen", "ECB rate import failed")}: {ex.Message}", Severity.Error); } finally { _refreshingExchangeRates = false; } } private async Task ExportConfiguration() { if (_exportingConfig) return; _exportingConfig = true; try { var json = await SettingsPageActions.ExportConfigurationAsync(_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(T("Konfiguration exportiert", "Configuration exported"), Severity.Success); } catch (Exception ex) { Snackbar.Add($"{T("Export fehlgeschlagen", "Export failed")}: {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(); var state = await SettingsPageActions.ImportConfigurationAsync(json); _spConfig = state.SharePointConfig; _exportSettings = state.ExportSettings; _sourceSystems = state.SourceSystems; _exchangeRates = state.ExchangeRates; Snackbar.Add(T("Konfiguration importiert", "Configuration imported"), Severity.Success); } catch (Exception ex) { Snackbar.Add($"{T("Import fehlgeschlagen", "Import failed")}: {ex.Message}", Severity.Error); } finally { _importingConfig = false; } } private async Task TestCentralCredentials(string sourceSystem) { var definition = _sourceSystems.FirstOrDefault(x => string.Equals(x.Code, sourceSystem, StringComparison.OrdinalIgnoreCase)); if (definition is null) { Snackbar.Add($"{T("Quellsystem nicht gefunden", "Source system not found")}: {sourceSystem}", Severity.Warning); return; } if (!_testingSystems.Add(sourceSystem)) return; try { var result = await SettingsPageActions.TestCentralCredentialsAsync(definition); Snackbar.Add(result.Message, result.Success ? Severity.Success : result.Warning ? Severity.Warning : Severity.Error); } finally { _testingSystems.Remove(sourceSystem); } } private static string NormalizeSourceSystemCode(string? code) => Services.SettingsPageService.NormalizeSourceSystemCode(code); private static string NormalizeConnectionKind(string? connectionKind) => Services.SettingsPageService.NormalizeConnectionKind(connectionKind); private static string GetConnectionKindLabel(string connectionKind) => connectionKind switch { SourceSystemConnectionKinds.Hana => "HANA", SourceSystemConnectionKinds.SapGateway => "SAP Gateway", SourceSystemConnectionKinds.ManualExcel => "Manual Excel", _ => connectionKind }; private static bool UsesManualImport(SourceSystemDefinition definition) => string.Equals(definition.ConnectionKind, SourceSystemConnectionKinds.ManualExcel, StringComparison.OrdinalIgnoreCase); private static bool UsesSapGateway(SourceSystemDefinition definition) => string.Equals(definition.ConnectionKind, SourceSystemConnectionKinds.SapGateway, StringComparison.OrdinalIgnoreCase); private static string GetServiceUrlSummary(SourceSystemDefinition definition) => string.IsNullOrWhiteSpace(definition.CentralServiceUrl) ? "-" : definition.CentralServiceUrl; private static string GetUsernameSummary(SourceSystemDefinition definition) => string.IsNullOrWhiteSpace(definition.CentralUsername) ? "-" : definition.CentralUsername; private static string NormalizeConfigValue(string? value) => Services.SettingsPageService.NormalizeConfigValue(value); private string T(string german, string english) => UiText.Text(german, english); }