refactoring
This commit is contained in:
@@ -173,6 +173,7 @@
|
|||||||
using var db = await DbFactory.CreateDbContextAsync();
|
using var db = await DbFactory.CreateDbContextAsync();
|
||||||
|
|
||||||
var sites = await db.Sites.Include(s => s.HanaServer).Where(s => s.IsActive).ToListAsync();
|
var sites = await db.Sites.Include(s => s.HanaServer).Where(s => s.IsActive).ToListAsync();
|
||||||
|
var sourceSystems = await db.SourceSystemDefinitions.AsNoTracking().ToListAsync();
|
||||||
var logs = await db.ExportLogs
|
var logs = await db.ExportLogs
|
||||||
.GroupBy(l => l.SiteId)
|
.GroupBy(l => l.SiteId)
|
||||||
.Select(g => g.OrderByDescending(l => l.Timestamp).First())
|
.Select(g => g.OrderByDescending(l => l.Timestamp).First())
|
||||||
@@ -190,14 +191,15 @@
|
|||||||
{
|
{
|
||||||
var log = logs.FirstOrDefault(l => l.SiteId == s.Id);
|
var log = logs.FirstOrDefault(l => l.SiteId == s.Id);
|
||||||
latestAppLogsBySite.TryGetValue(s.Id, out var appLog);
|
latestAppLogsBySite.TryGetValue(s.Id, out var appLog);
|
||||||
|
var sourceSystem = sourceSystems.FirstOrDefault(x => string.Equals(x.Code, s.SourceSystem, StringComparison.OrdinalIgnoreCase));
|
||||||
return new DashboardRow
|
return new DashboardRow
|
||||||
{
|
{
|
||||||
SiteId = s.Id,
|
SiteId = s.Id,
|
||||||
Land = s.Land,
|
Land = s.Land,
|
||||||
TSC = s.TSC,
|
TSC = s.TSC,
|
||||||
Schema = s.Schema,
|
Schema = s.Schema,
|
||||||
ServerName = string.Equals(s.SourceSystem, "SAP", StringComparison.OrdinalIgnoreCase)
|
ServerName = string.Equals(sourceSystem?.ConnectionKind, SourceSystemConnectionKinds.SapGateway, StringComparison.OrdinalIgnoreCase)
|
||||||
? (string.IsNullOrWhiteSpace(s.SapServiceUrl) ? "SAP Gateway" : s.SapServiceUrl)
|
? ResolveDashboardSapServiceUrl(s, sourceSystems)
|
||||||
: s.HanaServer?.Name ?? "",
|
: s.HanaServer?.Name ?? "",
|
||||||
LastStatus = log?.Status ?? "",
|
LastStatus = log?.Status ?? "",
|
||||||
RowCount = log?.RowCount ?? 0,
|
RowCount = log?.RowCount ?? 0,
|
||||||
@@ -319,6 +321,15 @@
|
|||||||
OpenFile(row.FilePath);
|
OpenFile(row.FilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string ResolveDashboardSapServiceUrl(Site site, List<SourceSystemDefinition> sourceSystems)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(site.SapServiceUrl))
|
||||||
|
return site.SapServiceUrl;
|
||||||
|
|
||||||
|
var sourceSystem = sourceSystems.FirstOrDefault(x => string.Equals(x.Code, site.SourceSystem, StringComparison.OrdinalIgnoreCase));
|
||||||
|
return string.IsNullOrWhiteSpace(sourceSystem?.CentralServiceUrl) ? "SAP Gateway" : sourceSystem.CentralServiceUrl;
|
||||||
|
}
|
||||||
|
|
||||||
private void OpenFile(string filePath)
|
private void OpenFile(string filePath)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(filePath) || !File.Exists(filePath))
|
if (string.IsNullOrWhiteSpace(filePath) || !File.Exists(filePath))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@page "/settings"
|
@page "/settings"
|
||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
@using TrafagSalesExporter.Data
|
@using TrafagSalesExporter.Data
|
||||||
@using TrafagSalesExporter.Models
|
@using TrafagSalesExporter.Models
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
<MudItem xs="12" md="6">
|
<MudItem xs="12" md="6">
|
||||||
<MudCheckBox @bind-Value="_includeSecretsInExport" Label="Mit Secrets exportieren" />
|
<MudCheckBox @bind-Value="_includeSecretsInExport" Label="Mit Secrets exportieren" />
|
||||||
<MudText Typo="Typo.caption">
|
<MudText Typo="Typo.caption">
|
||||||
Wenn deaktiviert, bleiben Passwörter und Secrets beim Export leer. Beim Import ohne Secrets werden bestehende Secrets auf dem Zielsystem beibehalten.
|
Wenn deaktiviert, bleiben Passwörter und Secrets beim Export leer. Beim Import ohne Secrets werden bestehende Secrets auf dem Zielsystem beibehalten.
|
||||||
</MudText>
|
</MudText>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" md="6">
|
<MudItem xs="12" md="6">
|
||||||
@@ -98,74 +98,102 @@
|
|||||||
</MudGrid>
|
</MudGrid>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|
||||||
<MudText Typo="Typo.h5" Class="mb-2">Zentrale Quellsystem-Zugangsdaten</MudText>
|
<MudText Typo="Typo.h5" Class="mb-2">Quellsysteme</MudText>
|
||||||
<MudPaper Class="pa-4 mb-6" Elevation="1">
|
<MudPaper Class="pa-4 mb-6" Elevation="1">
|
||||||
<MudGrid>
|
<MudGrid>
|
||||||
<MudItem xs="12">
|
<MudItem xs="12">
|
||||||
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Outlined">
|
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Outlined">
|
||||||
Diese Zugangsdaten werden pro Quellsystem als Standard verwendet. Ein Standort kann sie bei Bedarf mit eigenen Overrides überschreiben.
|
Diese Zugangsdaten werden pro Quellsystem als Standard verwendet. Ein Standort kann sie bei Bedarf mit eigenen Overrides überschreiben.
|
||||||
</MudAlert>
|
</MudAlert>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" md="4">
|
<MudItem xs="12">
|
||||||
<MudText Typo="Typo.h6" Class="mb-2">SAP</MudText>
|
<MudButton Variant="Variant.Outlined" Color="Color.Primary" OnClick="AddSourceSystem"
|
||||||
<MudTextField @bind-Value="_exportSettings.SapUsername" Label="SAP Username" />
|
StartIcon="@Icons.Material.Filled.Add" Class="mb-3">
|
||||||
<MudTextField @bind-Value="_exportSettings.SapPassword" Label="SAP Password" InputType="InputType.Password" />
|
Quellsystem hinzufuegen
|
||||||
<MudButton Variant="Variant.Outlined" Color="Color.Info" OnClick='@(() => TestCentralCredentials("SAP"))'
|
|
||||||
StartIcon="@Icons.Material.Filled.NetworkCheck" Disabled='@_testingSystems.Contains("SAP")' Class="mt-2">
|
|
||||||
@if (_testingSystems.Contains("SAP"))
|
|
||||||
{
|
|
||||||
<MudProgressCircular Size="Size.Small" Indeterminate Class="mr-2" />
|
|
||||||
@("Teste...")
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@("SAP testen")
|
|
||||||
}
|
|
||||||
</MudButton>
|
|
||||||
</MudItem>
|
|
||||||
<MudItem xs="12" md="4">
|
|
||||||
<MudText Typo="Typo.h6" Class="mb-2">BI1</MudText>
|
|
||||||
<MudTextField @bind-Value="_exportSettings.Bi1Username" Label="BI1 Username" />
|
|
||||||
<MudTextField @bind-Value="_exportSettings.Bi1Password" Label="BI1 Password" InputType="InputType.Password" />
|
|
||||||
<MudButton Variant="Variant.Outlined" Color="Color.Info" OnClick='@(() => TestCentralCredentials("BI1"))'
|
|
||||||
StartIcon="@Icons.Material.Filled.NetworkCheck" Disabled='@_testingSystems.Contains("BI1")' Class="mt-2">
|
|
||||||
@if (_testingSystems.Contains("BI1"))
|
|
||||||
{
|
|
||||||
<MudProgressCircular Size="Size.Small" Indeterminate Class="mr-2" />
|
|
||||||
@("Teste...")
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@("BI1 testen")
|
|
||||||
}
|
|
||||||
</MudButton>
|
|
||||||
</MudItem>
|
|
||||||
<MudItem xs="12" md="4">
|
|
||||||
<MudText Typo="Typo.h6" Class="mb-2">SAGE</MudText>
|
|
||||||
<MudTextField @bind-Value="_exportSettings.SageUsername" Label="SAGE Username" />
|
|
||||||
<MudTextField @bind-Value="_exportSettings.SagePassword" Label="SAGE Password" InputType="InputType.Password" />
|
|
||||||
<MudButton Variant="Variant.Outlined" Color="Color.Info" OnClick='@(() => TestCentralCredentials("SAGE"))'
|
|
||||||
StartIcon="@Icons.Material.Filled.NetworkCheck" Disabled='@_testingSystems.Contains("SAGE")' Class="mt-2">
|
|
||||||
@if (_testingSystems.Contains("SAGE"))
|
|
||||||
{
|
|
||||||
<MudProgressCircular Size="Size.Small" Indeterminate Class="mr-2" />
|
|
||||||
@("Teste...")
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@("SAGE testen")
|
|
||||||
}
|
|
||||||
</MudButton>
|
</MudButton>
|
||||||
|
<MudTable Items="_sourceSystems" Dense Hover Striped Breakpoint="Breakpoint.Md">
|
||||||
|
<HeaderContent>
|
||||||
|
<MudTh>Code</MudTh>
|
||||||
|
<MudTh>Name</MudTh>
|
||||||
|
<MudTh>Anschlussart</MudTh>
|
||||||
|
<MudTh>Zentrale URL</MudTh>
|
||||||
|
<MudTh>User</MudTh>
|
||||||
|
<MudTh>Aktiv</MudTh>
|
||||||
|
<MudTh>Test</MudTh>
|
||||||
|
<MudTh></MudTh>
|
||||||
|
</HeaderContent>
|
||||||
|
<RowTemplate>
|
||||||
|
<MudTd>@context.Code</MudTd>
|
||||||
|
<MudTd>@context.DisplayName</MudTd>
|
||||||
|
<MudTd>@GetConnectionKindLabel(context.ConnectionKind)</MudTd>
|
||||||
|
<MudTd>@GetServiceUrlSummary(context)</MudTd>
|
||||||
|
<MudTd>@GetUsernameSummary(context)</MudTd>
|
||||||
|
<MudTd>
|
||||||
|
@if (context.IsActive)
|
||||||
|
{
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success" Size="Size.Small" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudIcon Icon="@Icons.Material.Filled.Cancel" Color="Color.Default" Size="Size.Small" />
|
||||||
|
}
|
||||||
|
</MudTd>
|
||||||
|
<MudTd>
|
||||||
|
@if (!UsesManualImport(context))
|
||||||
|
{
|
||||||
|
<MudButton Variant="Variant.Outlined" Color="Color.Info" Size="Size.Small"
|
||||||
|
OnClick='@(() => TestCentralCredentials(context.Code))'
|
||||||
|
Disabled='@_testingSystems.Contains(context.Code)'>
|
||||||
|
@(_testingSystems.Contains(context.Code) ? "Teste..." : "Testen")
|
||||||
|
</MudButton>
|
||||||
|
}
|
||||||
|
</MudTd>
|
||||||
|
<MudTd>
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Edit" Color="Color.Primary" Size="Size.Small"
|
||||||
|
OnClick="() => EditSourceSystem(context)" />
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" Size="Size.Small"
|
||||||
|
OnClick="() => RemoveSourceSystem(context)" />
|
||||||
|
</MudTd>
|
||||||
|
</RowTemplate>
|
||||||
|
</MudTable>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12">
|
<MudItem xs="12">
|
||||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveExportSettings"
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveSourceSystems"
|
||||||
StartIcon="@Icons.Material.Filled.Save">
|
StartIcon="@Icons.Material.Filled.Save">
|
||||||
Speichern
|
Quellsysteme speichern
|
||||||
</MudButton>
|
</MudButton>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
</MudGrid>
|
</MudGrid>
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
|
|
||||||
|
<MudDialog @bind-Visible="_sourceSystemDialogVisible" Options="_sourceSystemDialogOptions">
|
||||||
|
<TitleContent>
|
||||||
|
<MudText Typo="Typo.h6">@(_editingSourceSystem.Id == 0 ? "Quellsystem hinzufuegen" : "Quellsystem bearbeiten")</MudText>
|
||||||
|
</TitleContent>
|
||||||
|
<DialogContent>
|
||||||
|
<MudTextField @bind-Value="_editingSourceSystem.Code" Label="Code" Required />
|
||||||
|
<MudTextField @bind-Value="_editingSourceSystem.DisplayName" Label="Name" Required />
|
||||||
|
<MudSelect T="string" @bind-Value="_editingSourceSystem.ConnectionKind" Label="Anschlussart" Required>
|
||||||
|
@foreach (var kind in SourceSystemConnectionKinds.All)
|
||||||
|
{
|
||||||
|
<MudSelectItem Value="@kind">@GetConnectionKindLabel(kind)</MudSelectItem>
|
||||||
|
}
|
||||||
|
</MudSelect>
|
||||||
|
@if (UsesSapGateway(_editingSourceSystem))
|
||||||
|
{
|
||||||
|
<MudTextField @bind-Value="_editingSourceSystem.CentralServiceUrl" Label="Zentrale SAP Service URL"
|
||||||
|
HelperText="Zentrale Standard-URL fuer SAP Gateway. Ein Standort darf sie nur bei Bedarf ueberschreiben." />
|
||||||
|
}
|
||||||
|
<MudTextField @bind-Value="_editingSourceSystem.CentralUsername" Label="Zentraler Username" />
|
||||||
|
<MudTextField @bind-Value="_editingSourceSystem.CentralPassword" Label="Zentrales Passwort" InputType="InputType.Password" />
|
||||||
|
<MudCheckBox @bind-Value="_editingSourceSystem.IsActive" Label="Aktiv" />
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<MudButton OnClick="CloseSourceSystemDialog">Abbrechen</MudButton>
|
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveSourceSystemEdit">Uebernehmen</MudButton>
|
||||||
|
</DialogActions>
|
||||||
|
</MudDialog>
|
||||||
|
|
||||||
<MudText Typo="Typo.h5" Class="mb-2">Wechselkurse</MudText>
|
<MudText Typo="Typo.h5" Class="mb-2">Wechselkurse</MudText>
|
||||||
<MudPaper Class="pa-4 mb-6" Elevation="1">
|
<MudPaper Class="pa-4 mb-6" Elevation="1">
|
||||||
<MudText Typo="Typo.body2" Class="mb-3">
|
<MudText Typo="Typo.body2" Class="mb-3">
|
||||||
@@ -250,7 +278,7 @@
|
|||||||
<MudItem xs="12" md="4">
|
<MudItem xs="12" md="4">
|
||||||
<MudSwitch @bind-Value="_exportSettings.DebugLoggingEnabled" Label="Debug Live-Logging" Color="Color.Warning" />
|
<MudSwitch @bind-Value="_exportSettings.DebugLoggingEnabled" Label="Debug Live-Logging" Color="Color.Warning" />
|
||||||
<MudText Typo="Typo.caption">
|
<MudText Typo="Typo.caption">
|
||||||
Schreibt zusätzliche technische Fortschrittsmeldungen für HANA- und SAP-Lesevorgänge ins Dashboard und in die Logs.
|
Schreibt zusätzliche technische Fortschrittsmeldungen für HANA- und SAP-Lesevorgänge ins Dashboard und in die Logs.
|
||||||
</MudText>
|
</MudText>
|
||||||
</MudItem>
|
</MudItem>
|
||||||
<MudItem xs="12" md="6">
|
<MudItem xs="12" md="6">
|
||||||
@@ -285,6 +313,8 @@
|
|||||||
@code {
|
@code {
|
||||||
private SharePointConfig _spConfig = new();
|
private SharePointConfig _spConfig = new();
|
||||||
private ExportSettings _exportSettings = new();
|
private ExportSettings _exportSettings = new();
|
||||||
|
private List<SourceSystemDefinition> _sourceSystems = [];
|
||||||
|
private SourceSystemDefinition _editingSourceSystem = new();
|
||||||
private bool _testingSp;
|
private bool _testingSp;
|
||||||
private bool _includeSecretsInExport;
|
private bool _includeSecretsInExport;
|
||||||
private bool _exportingConfig;
|
private bool _exportingConfig;
|
||||||
@@ -293,12 +323,15 @@
|
|||||||
private string _sharePointTestPreview = string.Empty;
|
private string _sharePointTestPreview = string.Empty;
|
||||||
private List<CurrencyExchangeRate> _exchangeRates = [];
|
private List<CurrencyExchangeRate> _exchangeRates = [];
|
||||||
private readonly HashSet<string> _testingSystems = [];
|
private readonly HashSet<string> _testingSystems = [];
|
||||||
|
private bool _sourceSystemDialogVisible;
|
||||||
|
private readonly DialogOptions _sourceSystemDialogOptions = new() { MaxWidth = MaxWidth.Small, FullWidth = true };
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
using var db = await DbFactory.CreateDbContextAsync();
|
using var db = await DbFactory.CreateDbContextAsync();
|
||||||
_spConfig = await db.SharePointConfigs.FirstOrDefaultAsync() ?? new SharePointConfig();
|
_spConfig = await db.SharePointConfigs.FirstOrDefaultAsync() ?? new SharePointConfig();
|
||||||
_exportSettings = await db.ExportSettings.FirstOrDefaultAsync() ?? new ExportSettings();
|
_exportSettings = await db.ExportSettings.FirstOrDefaultAsync() ?? new ExportSettings();
|
||||||
|
_sourceSystems = await db.SourceSystemDefinitions.OrderBy(x => x.Code).ToListAsync();
|
||||||
_exchangeRates = await db.CurrencyExchangeRates
|
_exchangeRates = await db.CurrencyExchangeRates
|
||||||
.OrderBy(x => x.FromCurrency)
|
.OrderBy(x => x.FromCurrency)
|
||||||
.ThenBy(x => x.ToCurrency)
|
.ThenBy(x => x.ToCurrency)
|
||||||
@@ -370,18 +403,138 @@
|
|||||||
existing.DebugLoggingEnabled = _exportSettings.DebugLoggingEnabled;
|
existing.DebugLoggingEnabled = _exportSettings.DebugLoggingEnabled;
|
||||||
existing.LocalSiteExportFolder = _exportSettings.LocalSiteExportFolder;
|
existing.LocalSiteExportFolder = _exportSettings.LocalSiteExportFolder;
|
||||||
existing.LocalConsolidatedExportFolder = _exportSettings.LocalConsolidatedExportFolder;
|
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();
|
await db.SaveChangesAsync();
|
||||||
TimerService.Recalculate();
|
TimerService.Recalculate();
|
||||||
Snackbar.Add("Export Einstellungen gespeichert", Severity.Success);
|
Snackbar.Add("Export Einstellungen gespeichert", 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("Code und Name fuer das Quellsystem sind Pflicht.", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_sourceSystems.Any(x => x.Id != _editingSourceSystem.Id && x.Code == _editingSourceSystem.Code))
|
||||||
|
{
|
||||||
|
Snackbar.Add($"Quellsystem-Code doppelt vorhanden: {_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()
|
||||||
|
{
|
||||||
|
var normalized = _sourceSystems
|
||||||
|
.Select(x => new SourceSystemDefinition
|
||||||
|
{
|
||||||
|
Id = x.Id,
|
||||||
|
Code = NormalizeSourceSystemCode(x.Code),
|
||||||
|
DisplayName = NormalizeConfigValue(x.DisplayName),
|
||||||
|
ConnectionKind = NormalizeConnectionKind(x.ConnectionKind),
|
||||||
|
IsActive = x.IsActive,
|
||||||
|
CentralServiceUrl = NormalizeConfigValue(x.CentralServiceUrl),
|
||||||
|
CentralUsername = NormalizeConfigValue(x.CentralUsername),
|
||||||
|
CentralPassword = x.CentralPassword ?? string.Empty
|
||||||
|
})
|
||||||
|
.Where(x => !string.IsNullOrWhiteSpace(x.Code))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (normalized.Any(x => string.IsNullOrWhiteSpace(x.DisplayName)))
|
||||||
|
{
|
||||||
|
Snackbar.Add("Jedes Quellsystem braucht einen Anzeigenamen.", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var duplicates = normalized
|
||||||
|
.GroupBy(x => x.Code)
|
||||||
|
.FirstOrDefault(g => g.Count() > 1);
|
||||||
|
if (duplicates is not null)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"Quellsystem-Code doppelt vorhanden: {duplicates.Key}", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var db = await DbFactory.CreateDbContextAsync();
|
||||||
|
var existing = await db.SourceSystemDefinitions.ToListAsync();
|
||||||
|
if (existing.Count > 0)
|
||||||
|
db.SourceSystemDefinitions.RemoveRange(existing);
|
||||||
|
|
||||||
|
db.SourceSystemDefinitions.AddRange(normalized);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
|
_sourceSystems = await db.SourceSystemDefinitions.OrderBy(x => x.Code).ToListAsync();
|
||||||
|
Snackbar.Add("Quellsysteme gespeichert", Severity.Success);
|
||||||
|
}
|
||||||
|
|
||||||
private void AddExchangeRate()
|
private void AddExchangeRate()
|
||||||
{
|
{
|
||||||
_exchangeRates.Add(new CurrencyExchangeRate
|
_exchangeRates.Add(new CurrencyExchangeRate
|
||||||
@@ -493,6 +646,7 @@
|
|||||||
using var db = await DbFactory.CreateDbContextAsync();
|
using var db = await DbFactory.CreateDbContextAsync();
|
||||||
_spConfig = await db.SharePointConfigs.FirstOrDefaultAsync() ?? new SharePointConfig();
|
_spConfig = await db.SharePointConfigs.FirstOrDefaultAsync() ?? new SharePointConfig();
|
||||||
_exportSettings = await db.ExportSettings.FirstOrDefaultAsync() ?? new ExportSettings();
|
_exportSettings = await db.ExportSettings.FirstOrDefaultAsync() ?? new ExportSettings();
|
||||||
|
_sourceSystems = await db.SourceSystemDefinitions.OrderBy(x => x.Code).ToListAsync();
|
||||||
_exchangeRates = await db.CurrencyExchangeRates
|
_exchangeRates = await db.CurrencyExchangeRates
|
||||||
.OrderBy(x => x.FromCurrency)
|
.OrderBy(x => x.FromCurrency)
|
||||||
.ThenBy(x => x.ToCurrency)
|
.ThenBy(x => x.ToCurrency)
|
||||||
@@ -513,61 +667,72 @@
|
|||||||
|
|
||||||
private async Task TestCentralCredentials(string sourceSystem)
|
private async Task TestCentralCredentials(string sourceSystem)
|
||||||
{
|
{
|
||||||
if (sourceSystem == "SAP")
|
var definition = _sourceSystems.FirstOrDefault(x => string.Equals(x.Code, sourceSystem, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (definition is null)
|
||||||
{
|
{
|
||||||
await TestCentralSapCredentials();
|
Snackbar.Add($"Quellsystem '{sourceSystem}' nicht gefunden.", Severity.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await TestCentralHanaCredentials(sourceSystem);
|
if (string.Equals(definition.ConnectionKind, SourceSystemConnectionKinds.SapGateway, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
await TestCentralSapCredentials(definition);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(definition.ConnectionKind, SourceSystemConnectionKinds.Hana, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
await TestCentralHanaCredentials(definition);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task TestCentralHanaCredentials(string sourceSystem)
|
private async Task TestCentralHanaCredentials(SourceSystemDefinition definition)
|
||||||
{
|
{
|
||||||
|
var sourceSystem = definition.Code;
|
||||||
if (!_testingSystems.Add(sourceSystem))
|
if (!_testingSystems.Add(sourceSystem))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var username = GetCentralUsername(sourceSystem);
|
var username = definition.CentralUsername;
|
||||||
var password = GetCentralPassword(sourceSystem);
|
var password = definition.CentralPassword;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
||||||
{
|
{
|
||||||
Snackbar.Add($"Für {sourceSystem} sind keine zentralen Zugangsdaten gepflegt.", Severity.Warning);
|
Snackbar.Add($"Für {sourceSystem} sind keine zentralen Zugangsdaten gepflegt.", Severity.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var db = await DbFactory.CreateDbContextAsync();
|
using var db = await DbFactory.CreateDbContextAsync();
|
||||||
var site = await db.Sites
|
var centralServer = await db.HanaServers
|
||||||
.Include(s => s.HanaServer)
|
.Where(s => s.SourceSystem == sourceSystem)
|
||||||
.Where(s => (string.IsNullOrWhiteSpace(s.SourceSystem) ? "SAP" : s.SourceSystem) == sourceSystem)
|
.OrderBy(s => s.Id)
|
||||||
.OrderBy(s => s.Land)
|
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
if (site?.HanaServer is null)
|
if (centralServer is null || string.IsNullOrWhiteSpace(centralServer.Host))
|
||||||
{
|
{
|
||||||
Snackbar.Add($"Kein Standort mit Quellsystem {sourceSystem} und HANA-Verbindung gefunden.", Severity.Warning);
|
Snackbar.Add($"Keine zentrale HANA-Konfiguration fuer {sourceSystem} gefunden.", Severity.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var testServer = new HanaServer
|
var testServer = new HanaServer
|
||||||
{
|
{
|
||||||
|
SourceSystem = sourceSystem,
|
||||||
Name = $"{sourceSystem} Central Test",
|
Name = $"{sourceSystem} Central Test",
|
||||||
Host = site.HanaServer.Host,
|
Host = centralServer.Host,
|
||||||
Port = site.HanaServer.Port,
|
Port = centralServer.Port,
|
||||||
Username = username.Trim(),
|
Username = username.Trim(),
|
||||||
Password = password.Trim(),
|
Password = password.Trim(),
|
||||||
DatabaseName = site.HanaServer.DatabaseName,
|
DatabaseName = centralServer.DatabaseName,
|
||||||
UseSsl = site.HanaServer.UseSsl,
|
UseSsl = centralServer.UseSsl,
|
||||||
ValidateCertificate = site.HanaServer.ValidateCertificate,
|
ValidateCertificate = centralServer.ValidateCertificate,
|
||||||
AdditionalParams = site.HanaServer.AdditionalParams
|
AdditionalParams = centralServer.AdditionalParams
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = await Task.Run(() => HanaService.TestConnectionDetailed(testServer));
|
var result = await Task.Run(() => HanaService.TestConnectionDetailed(testServer));
|
||||||
if (result.Success)
|
if (result.Success)
|
||||||
{
|
{
|
||||||
Snackbar.Add($"{sourceSystem}: Verbindung erfolgreich über Standort '{site.Land}'.", Severity.Success);
|
Snackbar.Add($"{sourceSystem}: Zentrale HANA-Verbindung erfolgreich.", Severity.Success);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -580,42 +745,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task TestCentralSapCredentials()
|
private async Task TestCentralSapCredentials(SourceSystemDefinition definition)
|
||||||
{
|
{
|
||||||
const string sourceSystem = "SAP";
|
var sourceSystem = definition.Code;
|
||||||
if (!_testingSystems.Add(sourceSystem))
|
if (!_testingSystems.Add(sourceSystem))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var username = GetCentralUsername(sourceSystem);
|
var username = definition.CentralUsername;
|
||||||
var password = GetCentralPassword(sourceSystem);
|
var password = definition.CentralPassword;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
||||||
{
|
{
|
||||||
Snackbar.Add("Für SAP sind keine zentralen Gateway-Zugangsdaten gepflegt.", Severity.Warning);
|
Snackbar.Add("Für SAP sind keine zentralen Gateway-Zugangsdaten gepflegt.", Severity.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
using var db = await DbFactory.CreateDbContextAsync();
|
if (string.IsNullOrWhiteSpace(definition.CentralServiceUrl))
|
||||||
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);
|
Snackbar.Add($"Fuer {sourceSystem} ist keine zentrale SAP Service URL gepflegt.", Severity.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await SapGatewayService.TestConnectionAsync(site.SapServiceUrl, username.Trim(), password.Trim());
|
await SapGatewayService.TestConnectionAsync(definition.CentralServiceUrl, username.Trim(), password.Trim());
|
||||||
Snackbar.Add($"SAP: Gateway-Verbindung erfolgreich über Standort '{site.Land}'.", Severity.Success);
|
Snackbar.Add($"{sourceSystem}: Zentrale SAP Gateway-Verbindung erfolgreich.", Severity.Success);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Snackbar.Add($"SAP: {ex.Message}", Severity.Error);
|
Snackbar.Add($"{sourceSystem}: {ex.Message}", Severity.Error);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -623,19 +781,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetCentralUsername(string sourceSystem) => sourceSystem switch
|
private static string NormalizeSourceSystemCode(string? code) => NormalizeConfigValue(code).ToUpperInvariant();
|
||||||
|
|
||||||
|
private static string NormalizeConnectionKind(string? connectionKind)
|
||||||
|
=> SourceSystemConnectionKinds.All.Contains(connectionKind ?? string.Empty, StringComparer.OrdinalIgnoreCase)
|
||||||
|
? (connectionKind ?? string.Empty).Trim().ToUpperInvariant()
|
||||||
|
: SourceSystemConnectionKinds.Hana;
|
||||||
|
|
||||||
|
private static string GetConnectionKindLabel(string connectionKind) => connectionKind switch
|
||||||
{
|
{
|
||||||
"BI1" => _exportSettings.Bi1Username,
|
SourceSystemConnectionKinds.Hana => "HANA",
|
||||||
"SAGE" => _exportSettings.SageUsername,
|
SourceSystemConnectionKinds.SapGateway => "SAP Gateway",
|
||||||
_ => _exportSettings.SapUsername
|
SourceSystemConnectionKinds.ManualExcel => "Manual Excel",
|
||||||
|
_ => connectionKind
|
||||||
};
|
};
|
||||||
|
|
||||||
private string GetCentralPassword(string sourceSystem) => sourceSystem switch
|
private static bool UsesManualImport(SourceSystemDefinition definition)
|
||||||
{
|
=> string.Equals(definition.ConnectionKind, SourceSystemConnectionKinds.ManualExcel, StringComparison.OrdinalIgnoreCase);
|
||||||
"BI1" => _exportSettings.Bi1Password,
|
|
||||||
"SAGE" => _exportSettings.SagePassword,
|
private static bool UsesSapGateway(SourceSystemDefinition definition)
|
||||||
_ => _exportSettings.SapPassword
|
=> 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) => value?.Trim() ?? string.Empty;
|
private static string NormalizeConfigValue(string? value) => value?.Trim() ?? string.Empty;
|
||||||
|
|
||||||
@@ -664,3 +835,4 @@
|
|||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@page "/standorte"
|
@page "/standorte"
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
@using Microsoft.EntityFrameworkCore
|
@using Microsoft.EntityFrameworkCore
|
||||||
@using System.Text.Json
|
@using System.Text.Json
|
||||||
@@ -17,27 +17,30 @@
|
|||||||
|
|
||||||
<MudText Typo="Typo.h4" Class="mb-4">Standorte</MudText>
|
<MudText Typo="Typo.h4" Class="mb-4">Standorte</MudText>
|
||||||
|
|
||||||
<MudText Typo="Typo.h5" Class="mb-2">HANA Server</MudText>
|
<MudText Typo="Typo.h5" Class="mb-2">Zentrale HANA-Technik</MudText>
|
||||||
<MudPaper Class="pa-4 mb-6" Elevation="1">
|
<MudPaper Class="pa-4 mb-6" Elevation="1">
|
||||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add"
|
<MudAlert Severity="Severity.Info" Dense="true" Variant="Variant.Outlined" Class="mb-3">
|
||||||
OnClick="AddServer" Class="mb-3">
|
Hier erscheinen nur Quellsysteme mit Anschlussart HANA. SAP wird zentral unter Settings -> Quellsysteme gepflegt.
|
||||||
Server hinzufügen
|
Standorte mit `BI1` oder `SAGE` verwenden diese technischen HANA-Werte automatisch. Im Standort selbst bleiben nur Schema, TSC, Land und optionale Username-/Password-Overrides.
|
||||||
</MudButton>
|
</MudAlert>
|
||||||
|
<MudText Typo="Typo.body2" Class="mb-3">
|
||||||
|
Neue HANA-Zeilen entstehen aus den zentral gepflegten Quellsystemen. Falls hier etwas fehlt, lege das Quellsystem in Settings -> Quellsysteme mit Anschlussart `HANA` an.
|
||||||
|
</MudText>
|
||||||
|
|
||||||
<MudTable Items="_servers" Dense Hover Striped>
|
<MudTable Items="_servers" Dense Hover Striped>
|
||||||
<HeaderContent>
|
<HeaderContent>
|
||||||
|
<MudTh>Quellsystem</MudTh>
|
||||||
<MudTh>Name</MudTh>
|
<MudTh>Name</MudTh>
|
||||||
<MudTh>Host</MudTh>
|
<MudTh>Host</MudTh>
|
||||||
<MudTh>Port</MudTh>
|
<MudTh>Port</MudTh>
|
||||||
<MudTh>Username</MudTh>
|
|
||||||
<MudTh>Verbindungsstatus</MudTh>
|
<MudTh>Verbindungsstatus</MudTh>
|
||||||
<MudTh>Aktionen</MudTh>
|
<MudTh>Aktionen</MudTh>
|
||||||
</HeaderContent>
|
</HeaderContent>
|
||||||
<RowTemplate>
|
<RowTemplate>
|
||||||
|
<MudTd>@context.SourceSystem</MudTd>
|
||||||
<MudTd>@context.Name</MudTd>
|
<MudTd>@context.Name</MudTd>
|
||||||
<MudTd>@context.Host</MudTd>
|
<MudTd>@context.Host</MudTd>
|
||||||
<MudTd>@context.Port</MudTd>
|
<MudTd>@context.Port</MudTd>
|
||||||
<MudTd>@context.Username</MudTd>
|
|
||||||
<MudTd>
|
<MudTd>
|
||||||
@if (_connectionStatus.TryGetValue(context.Id, out var status))
|
@if (_connectionStatus.TryGetValue(context.Id, out var status))
|
||||||
{
|
{
|
||||||
@@ -68,7 +71,7 @@
|
|||||||
<MudPaper Class="pa-4" Elevation="1">
|
<MudPaper Class="pa-4" Elevation="1">
|
||||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add"
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add"
|
||||||
OnClick="AddSite" Class="mb-3">
|
OnClick="AddSite" Class="mb-3">
|
||||||
Neuen Standort hinzufügen
|
Neuen Standort hinzufügen
|
||||||
</MudButton>
|
</MudButton>
|
||||||
|
|
||||||
<MudTable Items="_sites" Dense Hover Striped>
|
<MudTable Items="_sites" Dense Hover Striped>
|
||||||
@@ -109,22 +112,21 @@
|
|||||||
|
|
||||||
<MudDialog @bind-Visible="_serverDialogVisible" Options="_dialogOptions">
|
<MudDialog @bind-Visible="_serverDialogVisible" Options="_dialogOptions">
|
||||||
<TitleContent>
|
<TitleContent>
|
||||||
<MudText Typo="Typo.h6">@(_editingServer.Id == 0 ? "Server hinzufügen" : "Server bearbeiten")</MudText>
|
<MudText Typo="Typo.h6">Zentrale HANA-Technik bearbeiten</MudText>
|
||||||
</TitleContent>
|
</TitleContent>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
|
<MudTextField Value="_editingServer.SourceSystem" Label="Quellsystem" ReadOnly />
|
||||||
<MudTextField @bind-Value="_editingServer.Name" Label="Name" Required />
|
<MudTextField @bind-Value="_editingServer.Name" Label="Name" Required />
|
||||||
<MudTextField @bind-Value="_editingServer.Host" Label="Host" Required
|
<MudTextField @bind-Value="_editingServer.Host" Label="Host" Required
|
||||||
HelperText="IP oder Hostname (ohne Protokoll)" />
|
HelperText="IP oder Hostname (ohne Protokoll)" />
|
||||||
<MudNumericField @bind-Value="_editingServer.Port" Label="Port"
|
<MudNumericField @bind-Value="_editingServer.Port" Label="Port"
|
||||||
HelperText="Typisch 30015 (Tenant), 30013 (SystemDB), 3xx15 für Instanz xx" />
|
HelperText="Typisch 30015 (Tenant), 30013 (SystemDB), 3xx15 für Instanz xx" />
|
||||||
<MudTextField @bind-Value="_editingServer.Username" Label="Username" />
|
|
||||||
<MudTextField @bind-Value="_editingServer.Password" Label="Password" InputType="InputType.Password" />
|
|
||||||
<MudTextField @bind-Value="_editingServer.DatabaseName" Label="Database Name (MDC)"
|
<MudTextField @bind-Value="_editingServer.DatabaseName" Label="Database Name (MDC)"
|
||||||
HelperText="Nur bei Multi-Tenant Setup angeben, sonst leer lassen" />
|
HelperText="Nur bei Multi-Tenant Setup angeben, sonst leer lassen" />
|
||||||
<MudSwitch @bind-Value="_editingServer.UseSsl" Label="SSL/TLS verwenden (encrypt=true)" Color="Color.Primary" />
|
<MudSwitch @bind-Value="_editingServer.UseSsl" Label="SSL/TLS verwenden (encrypt=true)" Color="Color.Primary" />
|
||||||
<MudSwitch @bind-Value="_editingServer.ValidateCertificate" Label="SSL-Zertifikat validieren" Color="Color.Primary"
|
<MudSwitch @bind-Value="_editingServer.ValidateCertificate" Label="SSL-Zertifikat validieren" Color="Color.Primary"
|
||||||
Disabled="!_editingServer.UseSsl" />
|
Disabled="!_editingServer.UseSsl" />
|
||||||
<MudTextField @bind-Value="_editingServer.AdditionalParams" Label="Zusätzliche Parameter"
|
<MudTextField @bind-Value="_editingServer.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>
|
||||||
@@ -135,16 +137,16 @@
|
|||||||
|
|
||||||
<MudDialog @bind-Visible="_siteDialogVisible" Options="_dialogOptions">
|
<MudDialog @bind-Visible="_siteDialogVisible" Options="_dialogOptions">
|
||||||
<TitleContent>
|
<TitleContent>
|
||||||
<MudText Typo="Typo.h6">@(_editingSite.Id == 0 ? "Standort hinzufügen" : "Standort bearbeiten")</MudText>
|
<MudText Typo="Typo.h6">@(_editingSite.Id == 0 ? "Standort hinzufügen" : "Standort bearbeiten")</MudText>
|
||||||
</TitleContent>
|
</TitleContent>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<MudTextField @bind-Value="_editingSite.Schema" Label="Schema" Required />
|
<MudTextField @bind-Value="_editingSite.Schema" Label="Schema" Required />
|
||||||
<MudTextField @bind-Value="_editingSite.TSC" Label="TSC" Required />
|
<MudTextField @bind-Value="_editingSite.TSC" Label="TSC" Required />
|
||||||
<MudTextField @bind-Value="_editingSite.Land" Label="Land" Required />
|
<MudTextField @bind-Value="_editingSite.Land" Label="Land" Required />
|
||||||
<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 GetAvailableSourceSystems())
|
||||||
{
|
{
|
||||||
<MudSelectItem Value="@system">@system</MudSelectItem>
|
<MudSelectItem Value="@system.Code">@GetSourceSystemLabel(system)</MudSelectItem>
|
||||||
}
|
}
|
||||||
</MudSelect>
|
</MudSelect>
|
||||||
<MudTextField @bind-Value="_editingSite.UsernameOverride" Label="Username Override"
|
<MudTextField @bind-Value="_editingSite.UsernameOverride" Label="Username Override"
|
||||||
@@ -152,7 +154,7 @@
|
|||||||
<MudTextField @bind-Value="_editingSite.PasswordOverride" Label="Password Override" InputType="InputType.Password"
|
<MudTextField @bind-Value="_editingSite.PasswordOverride" Label="Password Override" InputType="InputType.Password"
|
||||||
HelperText="Optional. Wenn leer, wird das zentrale Passwort des Quellsystems verwendet." />
|
HelperText="Optional. Wenn leer, wird das zentrale Passwort des Quellsystems verwendet." />
|
||||||
<MudTextField @bind-Value="_editingSite.LocalExportFolderOverride" Label="Lokaler Exportpfad Override"
|
<MudTextField @bind-Value="_editingSite.LocalExportFolderOverride" Label="Lokaler Exportpfad Override"
|
||||||
HelperText="Optional. Wenn leer, wird der zentrale Standardpfad für Standort-Dateien verwendet." />
|
HelperText="Optional. Wenn leer, wird der zentrale Standardpfad für Standort-Dateien verwendet." />
|
||||||
<MudCheckBox @bind-Value="_editingSite.IsActive" Label="Aktiv" />
|
<MudCheckBox @bind-Value="_editingSite.IsActive" Label="Aktiv" />
|
||||||
|
|
||||||
<MudDivider Class="my-4" />
|
<MudDivider Class="my-4" />
|
||||||
@@ -161,10 +163,11 @@
|
|||||||
{
|
{
|
||||||
<MudText Typo="Typo.h6" Class="mb-2">SAP Gateway</MudText>
|
<MudText Typo="Typo.h6" Class="mb-2">SAP Gateway</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">
|
||||||
Die Service-URL zeigt auf den OData-Service. Die verfügbaren Entity Sets werden nur per Knopfdruck aktualisiert und lokal zwischengespeichert.
|
Die Service-URL zeigt auf den OData-Service. Die verfügbaren Entity Sets werden nur per Knopfdruck aktualisiert und lokal zwischengespeichert.
|
||||||
</MudAlert>
|
</MudAlert>
|
||||||
<MudTextField @bind-Value="_editingSite.SapServiceUrl" Label="SAP Service URL" Required
|
<MudText Typo="Typo.body2">Zentrale SAP Service URL: @GetCentralSapServiceUrlSummary(_editingSite.SourceSystem)</MudText>
|
||||||
HelperText="z.B. http://server:8000/sap/opu/odata/sap/ZPOWERBI_EINKAUF_SRV/" />
|
<MudTextField @bind-Value="_editingSite.SapServiceUrl" Label="SAP Service URL Override"
|
||||||
|
HelperText="Optional. Wenn leer, wird die zentrale SAP Service URL des Quellsystems verwendet." />
|
||||||
<MudStack Row Spacing="2" Class="mb-3">
|
<MudStack Row Spacing="2" Class="mb-3">
|
||||||
<MudButton Variant="Variant.Outlined" Color="Color.Info" OnClick="RefreshSapEntitySets"
|
<MudButton Variant="Variant.Outlined" Color="Color.Info" OnClick="RefreshSapEntitySets"
|
||||||
StartIcon="@Icons.Material.Filled.Refresh" Disabled="_refreshingSapEntitySets">
|
StartIcon="@Icons.Material.Filled.Refresh" Disabled="_refreshingSapEntitySets">
|
||||||
@@ -188,16 +191,16 @@
|
|||||||
<MudDivider Class="my-4" />
|
<MudDivider Class="my-4" />
|
||||||
<MudStack Row Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center" Class="mb-2">
|
<MudStack Row Justify="Justify.SpaceBetween" AlignItems="AlignItems.Center" Class="mb-2">
|
||||||
<MudText Typo="Typo.h6">SAP Quellen</MudText>
|
<MudText Typo="Typo.h6">SAP Quellen</MudText>
|
||||||
<MudButton Variant="Variant.Outlined" StartIcon="@Icons.Material.Filled.Add" OnClick="AddSapSource">Quelle hinzufügen</MudButton>
|
<MudButton Variant="Variant.Outlined" StartIcon="@Icons.Material.Filled.Add" OnClick="AddSapSource">Quelle hinzufügen</MudButton>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
<MudText Typo="Typo.caption" Class="mb-2">
|
<MudText Typo="Typo.caption" Class="mb-2">
|
||||||
Pro Quelle Alias und Entity Set definieren. Joins verwenden links/rechts kommagetrennte Schlüsselfelder wie `VBELN,POSNR`. Feldmappings erwarten `Alias.Feldname` oder Konstanten wie `=SAP`.
|
Pro Quelle Alias und Entity Set definieren. Joins verwenden links/rechts kommagetrennte Schlüsselfelder wie `VBELN,POSNR`. Feldmappings erwarten `Alias.Feldname` oder Konstanten wie `=SAP`.
|
||||||
</MudText>
|
</MudText>
|
||||||
<MudTable Items="_sapSources" Dense Hover Striped>
|
<MudTable Items="_sapSources" Dense Hover Striped>
|
||||||
<HeaderContent>
|
<HeaderContent>
|
||||||
<MudTh>Alias</MudTh>
|
<MudTh>Alias</MudTh>
|
||||||
<MudTh>Entity Set</MudTh>
|
<MudTh>Entity Set</MudTh>
|
||||||
<MudTh>Primär</MudTh>
|
<MudTh>Primär</MudTh>
|
||||||
<MudTh>Aktiv</MudTh>
|
<MudTh>Aktiv</MudTh>
|
||||||
<MudTh>Aktionen</MudTh>
|
<MudTh>Aktionen</MudTh>
|
||||||
</HeaderContent>
|
</HeaderContent>
|
||||||
@@ -225,7 +228,7 @@
|
|||||||
OnClick="AutoMatchSapJoins">
|
OnClick="AutoMatchSapJoins">
|
||||||
Auto-Match
|
Auto-Match
|
||||||
</MudButton>
|
</MudButton>
|
||||||
<MudButton Variant="Variant.Outlined" StartIcon="@Icons.Material.Filled.Add" OnClick="AddSapJoin">Join hinzufügen</MudButton>
|
<MudButton Variant="Variant.Outlined" StartIcon="@Icons.Material.Filled.Add" OnClick="AddSapJoin">Join hinzufügen</MudButton>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
<MudTable Items="_sapJoins" Dense Hover Striped>
|
<MudTable Items="_sapJoins" Dense Hover Striped>
|
||||||
@@ -305,11 +308,11 @@
|
|||||||
@("Felder aus Quellen laden")
|
@("Felder aus Quellen laden")
|
||||||
}
|
}
|
||||||
</MudButton>
|
</MudButton>
|
||||||
<MudButton Variant="Variant.Outlined" StartIcon="@Icons.Material.Filled.Add" OnClick="AddSapMapping">Mapping hinzufügen</MudButton>
|
<MudButton Variant="Variant.Outlined" StartIcon="@Icons.Material.Filled.Add" OnClick="AddSapMapping">Mapping hinzufügen</MudButton>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
<MudText Typo="Typo.caption" Class="mb-2">
|
<MudText Typo="Typo.caption" Class="mb-2">
|
||||||
Source Expressions werden aus den hinzugefügten SAP-Quellen als `Alias.Feldname` gelesen. Vorhandene manuelle Werte bleiben auswählbar.
|
Source Expressions werden aus den hinzugefügten SAP-Quellen als `Alias.Feldname` gelesen. Vorhandene manuelle Werte bleiben auswählbar.
|
||||||
</MudText>
|
</MudText>
|
||||||
<MudTable Items="_sapMappings" Dense Hover Striped>
|
<MudTable Items="_sapMappings" Dense Hover Striped>
|
||||||
<HeaderContent>
|
<HeaderContent>
|
||||||
@@ -346,7 +349,7 @@
|
|||||||
{
|
{
|
||||||
<MudText Typo="Typo.h6" Class="mb-2">Manueller Excel-Import</MudText>
|
<MudText Typo="Typo.h6" Class="mb-2">Manueller Excel-Import</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">
|
||||||
Für diesen Standort wird keine SAP- oder HANA-Verbindung verwendet. Es wird die hier hinterlegte Excel-Datei gelesen und in `CentralSalesRecords` übernommen.
|
Für diesen Standort wird keine SAP- oder HANA-Verbindung verwendet. Es wird die hier hinterlegte Excel-Datei gelesen und in `CentralSalesRecords` übernommen.
|
||||||
</MudAlert>
|
</MudAlert>
|
||||||
<InputFile OnChange="UploadManualImportFileAsync" accept=".xlsx" />
|
<InputFile OnChange="UploadManualImportFileAsync" accept=".xlsx" />
|
||||||
@if (_uploadingManualImport)
|
@if (_uploadingManualImport)
|
||||||
@@ -371,23 +374,12 @@
|
|||||||
{
|
{
|
||||||
<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.
|
Die technische HANA-Verbindung kommt aus der zentralen HANA-Konfiguration des Quellsystems. Im Standort selbst pflegst du nur fachliche Standortdaten und optionale Username-/Password-Overrides.
|
||||||
</MudAlert>
|
</MudAlert>
|
||||||
<MudTextField @bind-Value="_editingSiteServer.Name" Label="Verbindungsname" Required
|
<MudText Typo="Typo.body2">Aktive Zentralverbindung: @GetCentralHanaSummary(_editingSite.SourceSystem)</MudText>
|
||||||
HelperText="Interner Anzeigename für diesen Standort" />
|
<MudText Typo="Typo.caption" Class="mt-2">
|
||||||
<MudTextField @bind-Value="_editingSiteServer.Host" Label="Host oder ServerNode" Required
|
Host, Port, SSL und technische Parameter bearbeitest du oben in der zentralen HANA-Konfiguration.
|
||||||
HelperText="z.B. hana01 oder hana01:30015 oder derselbe HanaServer-Wert wie in Power BI" />
|
</MudText>
|
||||||
<MudNumericField @bind-Value="_editingSiteServer.Port" Label="Port"
|
|
||||||
HelperText="Wird ignoriert, wenn im Host bereits ein Port enthalten ist" />
|
|
||||||
<MudTextField @bind-Value="_editingSiteServer.Username" Label="Username" />
|
|
||||||
<MudTextField @bind-Value="_editingSiteServer.Password" Label="Password" InputType="InputType.Password" />
|
|
||||||
<MudTextField @bind-Value="_editingSiteServer.DatabaseName" Label="Database Name (MDC)"
|
|
||||||
HelperText="Nur bei Multi-Tenant Setup angeben, sonst leer lassen" />
|
|
||||||
<MudSwitch @bind-Value="_editingSiteServer.UseSsl" Label="SSL/TLS verwenden (encrypt=true)" Color="Color.Primary" />
|
|
||||||
<MudSwitch @bind-Value="_editingSiteServer.ValidateCertificate" Label="SSL-Zertifikat validieren" Color="Color.Primary"
|
|
||||||
Disabled="!_editingSiteServer.UseSsl" />
|
|
||||||
<MudTextField @bind-Value="_editingSiteServer.AdditionalParams" Label="Zusätzliche Parameter"
|
|
||||||
HelperText="Optional, z.B. sslCryptoProvider=openssl;communicationTimeout=0" />
|
|
||||||
}
|
}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
@@ -397,10 +389,10 @@
|
|||||||
</MudDialog>
|
</MudDialog>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private readonly string[] _sourceSystems = ["SAP", "BI1", "SAGE", "MANUAL_EXCEL"];
|
|
||||||
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<SourceSystemDefinition> _sourceSystemDefinitions = new();
|
||||||
private List<string> _sapEntitySetsCache = [];
|
private List<string> _sapEntitySetsCache = [];
|
||||||
private List<string> _sapAvailableSourceExpressions = [];
|
private List<string> _sapAvailableSourceExpressions = [];
|
||||||
private Dictionary<string, List<string>> _sapSourceFieldMap = new(StringComparer.OrdinalIgnoreCase);
|
private Dictionary<string, List<string>> _sapSourceFieldMap = new(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -413,7 +405,6 @@
|
|||||||
.ToArray();
|
.ToArray();
|
||||||
private HanaServer _editingServer = new();
|
private HanaServer _editingServer = new();
|
||||||
private Site _editingSite = new();
|
private Site _editingSite = new();
|
||||||
private HanaServer _editingSiteServer = new();
|
|
||||||
private bool _serverDialogVisible;
|
private bool _serverDialogVisible;
|
||||||
private bool _siteDialogVisible;
|
private bool _siteDialogVisible;
|
||||||
private bool _refreshingSapEntitySets;
|
private bool _refreshingSapEntitySets;
|
||||||
@@ -431,16 +422,17 @@
|
|||||||
private async Task LoadDataAsync()
|
private async Task LoadDataAsync()
|
||||||
{
|
{
|
||||||
using var db = await DbFactory.CreateDbContextAsync();
|
using var db = await DbFactory.CreateDbContextAsync();
|
||||||
_servers = await db.HanaServers.OrderBy(s => s.Name).ToListAsync();
|
_sourceSystemDefinitions = await db.SourceSystemDefinitions
|
||||||
|
.OrderBy(x => x.Code)
|
||||||
|
.ToListAsync();
|
||||||
|
_servers = await db.HanaServers
|
||||||
|
.Where(s => GetHanaSourceSystemCodes().Contains(s.SourceSystem))
|
||||||
|
.OrderBy(s => s.SourceSystem)
|
||||||
|
.ThenBy(s => s.Name)
|
||||||
|
.ToListAsync();
|
||||||
_sites = await db.Sites.Include(s => s.HanaServer).OrderBy(s => s.Land).ToListAsync();
|
_sites = await db.Sites.Include(s => s.HanaServer).OrderBy(s => s.Land).ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddServer()
|
|
||||||
{
|
|
||||||
_editingServer = new HanaServer { Port = 30015 };
|
|
||||||
_serverDialogVisible = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EditServer(HanaServer server)
|
private void EditServer(HanaServer server)
|
||||||
{
|
{
|
||||||
_editingServer = CloneServer(server);
|
_editingServer = CloneServer(server);
|
||||||
@@ -455,21 +447,50 @@
|
|||||||
_savingServer = true;
|
_savingServer = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_editingServer.SourceSystem = string.IsNullOrWhiteSpace(_editingServer.SourceSystem)
|
||||||
|
? GetHanaSourceSystemCodes().FirstOrDefault() ?? string.Empty
|
||||||
|
: _editingServer.SourceSystem.Trim().ToUpperInvariant();
|
||||||
|
_editingServer.Name = string.IsNullOrWhiteSpace(_editingServer.Name) ? _editingServer.SourceSystem : _editingServer.Name.Trim();
|
||||||
|
_editingServer.Host = _editingServer.Host.Trim();
|
||||||
|
_editingServer.DatabaseName = _editingServer.DatabaseName.Trim();
|
||||||
|
_editingServer.AdditionalParams = _editingServer.AdditionalParams.Trim();
|
||||||
|
_editingServer.Username = string.Empty;
|
||||||
|
_editingServer.Password = string.Empty;
|
||||||
using var db = await DbFactory.CreateDbContextAsync();
|
using var db = await DbFactory.CreateDbContextAsync();
|
||||||
if (_editingServer.Id == 0)
|
if (_editingServer.Id == 0)
|
||||||
{
|
{
|
||||||
db.HanaServers.Add(_editingServer);
|
var existingForSourceSystem = await db.HanaServers
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.FirstOrDefaultAsync(x => x.SourceSystem == _editingServer.SourceSystem);
|
||||||
|
|
||||||
|
if (existingForSourceSystem is null)
|
||||||
|
{
|
||||||
|
db.HanaServers.Add(_editingServer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
existingForSourceSystem.Name = _editingServer.Name;
|
||||||
|
existingForSourceSystem.Host = _editingServer.Host;
|
||||||
|
existingForSourceSystem.Port = _editingServer.Port;
|
||||||
|
existingForSourceSystem.Username = string.Empty;
|
||||||
|
existingForSourceSystem.Password = string.Empty;
|
||||||
|
existingForSourceSystem.DatabaseName = _editingServer.DatabaseName;
|
||||||
|
existingForSourceSystem.UseSsl = _editingServer.UseSsl;
|
||||||
|
existingForSourceSystem.ValidateCertificate = _editingServer.ValidateCertificate;
|
||||||
|
existingForSourceSystem.AdditionalParams = _editingServer.AdditionalParams;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var existing = await db.HanaServers.FindAsync(_editingServer.Id);
|
var existing = await db.HanaServers.FindAsync(_editingServer.Id);
|
||||||
if (existing is not null)
|
if (existing is not null)
|
||||||
{
|
{
|
||||||
|
existing.SourceSystem = _editingServer.SourceSystem;
|
||||||
existing.Name = _editingServer.Name;
|
existing.Name = _editingServer.Name;
|
||||||
existing.Host = _editingServer.Host;
|
existing.Host = _editingServer.Host;
|
||||||
existing.Port = _editingServer.Port;
|
existing.Port = _editingServer.Port;
|
||||||
existing.Username = _editingServer.Username;
|
existing.Username = string.Empty;
|
||||||
existing.Password = _editingServer.Password;
|
existing.Password = string.Empty;
|
||||||
existing.DatabaseName = _editingServer.DatabaseName;
|
existing.DatabaseName = _editingServer.DatabaseName;
|
||||||
existing.UseSsl = _editingServer.UseSsl;
|
existing.UseSsl = _editingServer.UseSsl;
|
||||||
existing.ValidateCertificate = _editingServer.ValidateCertificate;
|
existing.ValidateCertificate = _editingServer.ValidateCertificate;
|
||||||
@@ -490,10 +511,16 @@
|
|||||||
|
|
||||||
private async Task DeleteServer(HanaServer server)
|
private async Task DeleteServer(HanaServer server)
|
||||||
{
|
{
|
||||||
|
if (IsHanaSourceSystem(server.SourceSystem))
|
||||||
|
{
|
||||||
|
Snackbar.Add($"Die zentrale HANA-Konfiguration fuer {server.SourceSystem} kann nicht geloescht werden.", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var result = await DialogService.ShowMessageBox(
|
var result = await DialogService.ShowMessageBox(
|
||||||
"Server löschen",
|
"Server löschen",
|
||||||
$"Server '{server.Name}' wirklich löschen?",
|
$"Server '{server.Name}' wirklich löschen?",
|
||||||
yesText: "Löschen", cancelText: "Abbrechen");
|
yesText: "Löschen", cancelText: "Abbrechen");
|
||||||
|
|
||||||
if (result != true) return;
|
if (result != true) return;
|
||||||
|
|
||||||
@@ -509,7 +536,7 @@
|
|||||||
if (linkedSites.Count > 0)
|
if (linkedSites.Count > 0)
|
||||||
{
|
{
|
||||||
Snackbar.Add(
|
Snackbar.Add(
|
||||||
$"Server kann nicht gelöscht werden. Noch verknüpfte Standorte: {string.Join(", ", linkedSites)}",
|
$"Server kann nicht gelöscht werden. Noch verknüpfte Standorte: {string.Join(", ", linkedSites)}",
|
||||||
Severity.Warning);
|
Severity.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -523,19 +550,51 @@
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Snackbar.Add($"Server konnte nicht gelöscht werden: {ex.Message}", Severity.Error);
|
Snackbar.Add($"Server konnte nicht gelöscht werden: {ex.Message}", Severity.Error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await LoadDataAsync();
|
await LoadDataAsync();
|
||||||
Snackbar.Add("Server gelöscht", Severity.Info);
|
Snackbar.Add("Server gelöscht", Severity.Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task TestServerConnection(HanaServer server)
|
private async Task TestServerConnection(HanaServer server)
|
||||||
{
|
{
|
||||||
|
using var db = await DbFactory.CreateDbContextAsync();
|
||||||
|
var sourceDefinition = await db.SourceSystemDefinitions
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.FirstOrDefaultAsync(x => x.Code == server.SourceSystem);
|
||||||
|
|
||||||
|
if (sourceDefinition is null)
|
||||||
|
{
|
||||||
|
Snackbar.Add($"Quellsystem '{server.SourceSystem}' nicht gefunden.", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(sourceDefinition.CentralUsername) || string.IsNullOrWhiteSpace(sourceDefinition.CentralPassword))
|
||||||
|
{
|
||||||
|
Snackbar.Add($"Fuer {server.SourceSystem} sind keine zentralen Zugangsdaten im Quellsystem gepflegt.", Severity.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var testServer = new HanaServer
|
||||||
|
{
|
||||||
|
Id = server.Id,
|
||||||
|
SourceSystem = server.SourceSystem,
|
||||||
|
Name = server.Name,
|
||||||
|
Host = server.Host,
|
||||||
|
Port = server.Port,
|
||||||
|
Username = sourceDefinition.CentralUsername.Trim(),
|
||||||
|
Password = sourceDefinition.CentralPassword,
|
||||||
|
DatabaseName = server.DatabaseName,
|
||||||
|
UseSsl = server.UseSsl,
|
||||||
|
ValidateCertificate = server.ValidateCertificate,
|
||||||
|
AdditionalParams = server.AdditionalParams
|
||||||
|
};
|
||||||
|
|
||||||
await AppEventLogService.WriteAsync("HANA", "Server-Test aus UI gestartet",
|
await AppEventLogService.WriteAsync("HANA", "Server-Test aus UI gestartet",
|
||||||
details: server.GetConnectionStringPreview());
|
details: testServer.GetConnectionStringPreview());
|
||||||
var result = await Task.Run(() => HanaService.TestConnectionDetailed(server));
|
var result = await Task.Run(() => HanaService.TestConnectionDetailed(testServer));
|
||||||
_connectionStatus[server.Id] = result;
|
_connectionStatus[server.Id] = result;
|
||||||
|
|
||||||
if (result.Success)
|
if (result.Success)
|
||||||
@@ -562,7 +621,7 @@
|
|||||||
_editingSite = new Site
|
_editingSite = new Site
|
||||||
{
|
{
|
||||||
IsActive = true,
|
IsActive = true,
|
||||||
SourceSystem = "SAP",
|
SourceSystem = GetAvailableSourceSystems().FirstOrDefault()?.Code ?? "SAP",
|
||||||
HanaServerId = null,
|
HanaServerId = null,
|
||||||
ManualImportFilePath = string.Empty
|
ManualImportFilePath = string.Empty
|
||||||
};
|
};
|
||||||
@@ -572,7 +631,6 @@
|
|||||||
_sapSources = [];
|
_sapSources = [];
|
||||||
_sapJoins = [];
|
_sapJoins = [];
|
||||||
_sapMappings = [];
|
_sapMappings = [];
|
||||||
_editingSiteServer = CreateDefaultSiteServer();
|
|
||||||
_siteDialogVisible = true;
|
_siteDialogVisible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,7 +643,9 @@
|
|||||||
Schema = site.Schema,
|
Schema = site.Schema,
|
||||||
TSC = site.TSC,
|
TSC = site.TSC,
|
||||||
Land = site.Land,
|
Land = site.Land,
|
||||||
SourceSystem = string.IsNullOrWhiteSpace(site.SourceSystem) ? "SAP" : site.SourceSystem,
|
SourceSystem = string.IsNullOrWhiteSpace(site.SourceSystem)
|
||||||
|
? GetAvailableSourceSystems().FirstOrDefault()?.Code ?? "SAP"
|
||||||
|
: site.SourceSystem,
|
||||||
UsernameOverride = site.UsernameOverride,
|
UsernameOverride = site.UsernameOverride,
|
||||||
PasswordOverride = site.PasswordOverride,
|
PasswordOverride = site.PasswordOverride,
|
||||||
LocalExportFolderOverride = site.LocalExportFolderOverride,
|
LocalExportFolderOverride = site.LocalExportFolderOverride,
|
||||||
@@ -604,9 +664,6 @@
|
|||||||
_sapMappings = db.SapFieldMappings.Where(m => m.SiteId == site.Id).OrderBy(m => m.SortOrder).ThenBy(m => m.Id).ToList();
|
_sapMappings = db.SapFieldMappings.Where(m => m.SiteId == site.Id).OrderBy(m => m.SortOrder).ThenBy(m => m.Id).ToList();
|
||||||
_sapAvailableSourceExpressions = BuildSourceExpressionsFromMappings();
|
_sapAvailableSourceExpressions = BuildSourceExpressionsFromMappings();
|
||||||
_sapSourceFieldMap = BuildSourceFieldMapFromJoins();
|
_sapSourceFieldMap = BuildSourceFieldMapFromJoins();
|
||||||
_editingSiteServer = site.HanaServer is null
|
|
||||||
? CreateDefaultSiteServer(site)
|
|
||||||
: CloneServer(site.HanaServer);
|
|
||||||
_siteDialogVisible = true;
|
_siteDialogVisible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -619,7 +676,7 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var db = await DbFactory.CreateDbContextAsync();
|
using var db = await DbFactory.CreateDbContextAsync();
|
||||||
var serverId = UsesHanaConnection() ? await SaveOrCreateSiteServerAsync(db) : (int?)null;
|
var serverId = UsesHanaConnection() ? await ResolveCentralHanaServerIdAsync(db, _editingSite.SourceSystem) : (int?)null;
|
||||||
_editingSite.HanaServerId = serverId;
|
_editingSite.HanaServerId = serverId;
|
||||||
_editingSite.SapEntitySetsCache = SerializeSapEntitySets(_sapEntitySetsCache);
|
_editingSite.SapEntitySetsCache = SerializeSapEntitySets(_sapEntitySetsCache);
|
||||||
|
|
||||||
@@ -669,9 +726,9 @@
|
|||||||
private async Task DeleteSite(Site site)
|
private async Task DeleteSite(Site site)
|
||||||
{
|
{
|
||||||
var result = await DialogService.ShowMessageBox(
|
var result = await DialogService.ShowMessageBox(
|
||||||
"Standort löschen",
|
"Standort löschen",
|
||||||
$"Standort '{site.Land}' wirklich löschen?",
|
$"Standort '{site.Land}' wirklich löschen?",
|
||||||
yesText: "Löschen", cancelText: "Abbrechen");
|
yesText: "Löschen", cancelText: "Abbrechen");
|
||||||
|
|
||||||
if (result != true) return;
|
if (result != true) return;
|
||||||
|
|
||||||
@@ -692,7 +749,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
await LoadDataAsync();
|
await LoadDataAsync();
|
||||||
Snackbar.Add("Standort gelöscht", Severity.Info);
|
Snackbar.Add("Standort gelöscht", Severity.Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetServerNode(HanaServer? server)
|
private static string GetServerNode(HanaServer? server)
|
||||||
@@ -703,40 +760,17 @@
|
|||||||
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;
|
|
||||||
if (string.Equals(sourceSystem, "MANUAL_EXCEL", StringComparison.OrdinalIgnoreCase))
|
|
||||||
return string.IsNullOrWhiteSpace(site.ManualImportFilePath) ? "-" : Path.GetFileName(site.ManualImportFilePath);
|
|
||||||
|
|
||||||
return GetServerNode(site.HanaServer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private HanaServer CreateDefaultSiteServer(Site? site = null)
|
|
||||||
{
|
|
||||||
var label = !string.IsNullOrWhiteSpace(site?.Land) ? site!.Land : site?.TSC;
|
|
||||||
if (string.IsNullOrWhiteSpace(label))
|
|
||||||
label = "Neuer Standort";
|
|
||||||
|
|
||||||
return new HanaServer
|
|
||||||
{
|
|
||||||
Name = $"{label} HANA",
|
|
||||||
Port = 30015
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static HanaServer CloneServer(HanaServer server)
|
private static HanaServer CloneServer(HanaServer server)
|
||||||
{
|
{
|
||||||
return new HanaServer
|
return new HanaServer
|
||||||
{
|
{
|
||||||
Id = server.Id,
|
Id = server.Id,
|
||||||
|
SourceSystem = server.SourceSystem,
|
||||||
Name = server.Name,
|
Name = server.Name,
|
||||||
Host = server.Host,
|
Host = server.Host,
|
||||||
Port = server.Port,
|
Port = server.Port,
|
||||||
Username = server.Username,
|
Username = string.Empty,
|
||||||
Password = server.Password,
|
Password = string.Empty,
|
||||||
DatabaseName = server.DatabaseName,
|
DatabaseName = server.DatabaseName,
|
||||||
UseSsl = server.UseSsl,
|
UseSsl = server.UseSsl,
|
||||||
ValidateCertificate = server.ValidateCertificate,
|
ValidateCertificate = server.ValidateCertificate,
|
||||||
@@ -744,66 +778,98 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<int> SaveOrCreateSiteServerAsync(AppDbContext db)
|
private async Task<int> ResolveCentralHanaServerIdAsync(AppDbContext db, string sourceSystem)
|
||||||
{
|
{
|
||||||
_editingSiteServer.Name = string.IsNullOrWhiteSpace(_editingSiteServer.Name)
|
|
||||||
? $"{_editingSite.Land} HANA".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.LocalExportFolderOverride = _editingSite.LocalExportFolderOverride.Trim();
|
_editingSite.LocalExportFolderOverride = _editingSite.LocalExportFolderOverride.Trim();
|
||||||
_editingSite.ManualImportFilePath = _editingSite.ManualImportFilePath.Trim();
|
_editingSite.ManualImportFilePath = _editingSite.ManualImportFilePath.Trim();
|
||||||
_editingSite.SapServiceUrl = _editingSite.SapServiceUrl.Trim();
|
_editingSite.SapServiceUrl = _editingSite.SapServiceUrl.Trim();
|
||||||
_editingSite.SapEntitySet = _editingSite.SapEntitySet.Trim();
|
_editingSite.SapEntitySet = _editingSite.SapEntitySet.Trim();
|
||||||
_editingSiteServer.Host = _editingSiteServer.Host.Trim();
|
|
||||||
_editingSiteServer.Username = _editingSiteServer.Username.Trim();
|
|
||||||
_editingSiteServer.DatabaseName = _editingSiteServer.DatabaseName.Trim();
|
|
||||||
_editingSiteServer.AdditionalParams = _editingSiteServer.AdditionalParams.Trim();
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(_editingSiteServer.Host))
|
var normalizedSourceSystem = string.IsNullOrWhiteSpace(sourceSystem) ? string.Empty : sourceSystem.Trim().ToUpperInvariant();
|
||||||
throw new InvalidOperationException("Host oder ServerNode muss gesetzt sein.");
|
var centralServer = await db.HanaServers
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.FirstOrDefaultAsync(x => x.SourceSystem == normalizedSourceSystem);
|
||||||
|
|
||||||
if (_editingSite.HanaServerId == 0)
|
if (centralServer is null || string.IsNullOrWhiteSpace(centralServer.Host))
|
||||||
{
|
throw new InvalidOperationException($"Fuer Quellsystem '{normalizedSourceSystem}' ist keine gueltige zentrale HANA-Konfiguration vorhanden.");
|
||||||
db.HanaServers.Add(_editingSiteServer);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
return _editingSiteServer.Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sharedUseCount = await db.Sites.CountAsync(s => s.HanaServerId == _editingSite.HanaServerId && s.Id != _editingSite.Id);
|
return centralServer.Id;
|
||||||
if (sharedUseCount > 0)
|
|
||||||
{
|
|
||||||
var dedicatedServer = CloneServer(_editingSiteServer);
|
|
||||||
dedicatedServer.Id = 0;
|
|
||||||
db.HanaServers.Add(dedicatedServer);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
return dedicatedServer.Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
var existingServer = await db.HanaServers.FindAsync(_editingSite.HanaServerId);
|
|
||||||
if (existingServer is null)
|
|
||||||
{
|
|
||||||
db.HanaServers.Add(_editingSiteServer);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
return _editingSiteServer.Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
existingServer.Name = _editingSiteServer.Name;
|
|
||||||
existingServer.Host = _editingSiteServer.Host;
|
|
||||||
existingServer.Port = _editingSiteServer.Port;
|
|
||||||
existingServer.Username = _editingSiteServer.Username;
|
|
||||||
existingServer.Password = _editingSiteServer.Password;
|
|
||||||
existingServer.DatabaseName = _editingSiteServer.DatabaseName;
|
|
||||||
existingServer.UseSsl = _editingSiteServer.UseSsl;
|
|
||||||
existingServer.ValidateCertificate = _editingSiteServer.ValidateCertificate;
|
|
||||||
existingServer.AdditionalParams = _editingSiteServer.AdditionalParams;
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
return existingServer.Id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsSapSite() => string.Equals(_editingSite.SourceSystem, "SAP", StringComparison.OrdinalIgnoreCase);
|
private IEnumerable<SourceSystemDefinition> GetAvailableSourceSystems()
|
||||||
private bool IsManualExcelSite() => string.Equals(_editingSite.SourceSystem, "MANUAL_EXCEL", StringComparison.OrdinalIgnoreCase);
|
=> _sourceSystemDefinitions
|
||||||
private bool UsesHanaConnection() => !IsSapSite() && !IsManualExcelSite();
|
.Where(x => x.IsActive || string.Equals(x.Code, _editingSite.SourceSystem, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.OrderBy(x => x.DisplayName)
|
||||||
|
.ThenBy(x => x.Code);
|
||||||
|
|
||||||
|
private List<string> GetHanaSourceSystemCodes()
|
||||||
|
=> _sourceSystemDefinitions
|
||||||
|
.Where(x => string.Equals(x.ConnectionKind, SourceSystemConnectionKinds.Hana, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Select(x => x.Code)
|
||||||
|
.OrderBy(x => x)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
private string GetSourceSystemConnectionKind(string? sourceSystem)
|
||||||
|
=> _sourceSystemDefinitions
|
||||||
|
.FirstOrDefault(x => string.Equals(x.Code, sourceSystem, StringComparison.OrdinalIgnoreCase))
|
||||||
|
?.ConnectionKind
|
||||||
|
?? SourceSystemConnectionKinds.SapGateway;
|
||||||
|
|
||||||
|
private bool IsHanaSourceSystem(string? sourceSystem)
|
||||||
|
=> string.Equals(GetSourceSystemConnectionKind(sourceSystem), SourceSystemConnectionKinds.Hana, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
private bool IsSapSite()
|
||||||
|
=> string.Equals(GetSourceSystemConnectionKind(_editingSite.SourceSystem), SourceSystemConnectionKinds.SapGateway, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
private bool IsManualExcelSite()
|
||||||
|
=> string.Equals(GetSourceSystemConnectionKind(_editingSite.SourceSystem), SourceSystemConnectionKinds.ManualExcel, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
private bool UsesHanaConnection() => IsHanaSourceSystem(_editingSite.SourceSystem);
|
||||||
|
|
||||||
|
private string GetSourceSystemLabel(SourceSystemDefinition definition)
|
||||||
|
=> string.IsNullOrWhiteSpace(definition.DisplayName) ? definition.Code : $"{definition.DisplayName} ({definition.Code})";
|
||||||
|
|
||||||
|
private string GetConnectionTarget(Site site)
|
||||||
|
{
|
||||||
|
var connectionKind = GetSourceSystemConnectionKind(site.SourceSystem);
|
||||||
|
if (string.Equals(connectionKind, SourceSystemConnectionKinds.SapGateway, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return GetEffectiveSapServiceUrl(site);
|
||||||
|
if (string.Equals(connectionKind, SourceSystemConnectionKinds.ManualExcel, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return string.IsNullOrWhiteSpace(site.ManualImportFilePath) ? "-" : Path.GetFileName(site.ManualImportFilePath);
|
||||||
|
|
||||||
|
return GetServerNode(site.HanaServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetEffectiveSapServiceUrl(Site site)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(site.SapServiceUrl))
|
||||||
|
return site.SapServiceUrl;
|
||||||
|
|
||||||
|
var sourceDefinition = _sourceSystemDefinitions
|
||||||
|
.FirstOrDefault(x => string.Equals(x.Code, site.SourceSystem, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
return string.IsNullOrWhiteSpace(sourceDefinition?.CentralServiceUrl) ? "-" : sourceDefinition.CentralServiceUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetCentralSapServiceUrlSummary(string sourceSystem)
|
||||||
|
{
|
||||||
|
var sourceDefinition = _sourceSystemDefinitions
|
||||||
|
.FirstOrDefault(x => string.Equals(x.Code, sourceSystem, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
return string.IsNullOrWhiteSpace(sourceDefinition?.CentralServiceUrl) ? "-" : sourceDefinition.CentralServiceUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetCentralHanaSummary(string sourceSystem)
|
||||||
|
{
|
||||||
|
var normalizedSourceSystem = string.IsNullOrWhiteSpace(sourceSystem) ? string.Empty : sourceSystem.Trim().ToUpperInvariant();
|
||||||
|
var centralServer = _servers.FirstOrDefault(x => x.SourceSystem == normalizedSourceSystem);
|
||||||
|
if (centralServer is null || string.IsNullOrWhiteSpace(centralServer.Host))
|
||||||
|
return $"keine zentrale HANA-Konfiguration fuer {normalizedSourceSystem}";
|
||||||
|
|
||||||
|
return $"{centralServer.Name} | {GetServerNode(centralServer)}";
|
||||||
|
}
|
||||||
|
|
||||||
private async Task RefreshSapEntitySets()
|
private async Task RefreshSapEntitySets()
|
||||||
{
|
{
|
||||||
@@ -813,20 +879,28 @@
|
|||||||
_refreshingSapEntitySets = true;
|
_refreshingSapEntitySets = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(_editingSite.SapServiceUrl))
|
|
||||||
throw new InvalidOperationException("SAP Service URL muss gesetzt sein.");
|
|
||||||
|
|
||||||
using var db = await DbFactory.CreateDbContextAsync();
|
using var db = await DbFactory.CreateDbContextAsync();
|
||||||
var settings = await db.ExportSettings.FirstOrDefaultAsync() ?? new();
|
var sourceDefinition = await db.SourceSystemDefinitions
|
||||||
var username = string.IsNullOrWhiteSpace(_editingSite.UsernameOverride) ? settings.SapUsername : _editingSite.UsernameOverride;
|
.OrderBy(x => x.Id)
|
||||||
var password = string.IsNullOrWhiteSpace(_editingSite.PasswordOverride) ? settings.SapPassword : _editingSite.PasswordOverride;
|
.FirstOrDefaultAsync(x => x.Code == _editingSite.SourceSystem);
|
||||||
|
var serviceUrl = string.IsNullOrWhiteSpace(_editingSite.SapServiceUrl)
|
||||||
|
? sourceDefinition?.CentralServiceUrl ?? string.Empty
|
||||||
|
: _editingSite.SapServiceUrl;
|
||||||
|
if (string.IsNullOrWhiteSpace(serviceUrl))
|
||||||
|
throw new InvalidOperationException("Es ist weder eine zentrale SAP Service URL noch ein Standort-Override gesetzt.");
|
||||||
|
var username = string.IsNullOrWhiteSpace(_editingSite.UsernameOverride)
|
||||||
|
? sourceDefinition?.CentralUsername ?? string.Empty
|
||||||
|
: _editingSite.UsernameOverride;
|
||||||
|
var password = string.IsNullOrWhiteSpace(_editingSite.PasswordOverride)
|
||||||
|
? sourceDefinition?.CentralPassword ?? string.Empty
|
||||||
|
: _editingSite.PasswordOverride;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
||||||
throw new InvalidOperationException("Für SAP sind weder zentrale Zugangsdaten noch Standort-Overrides gesetzt.");
|
throw new InvalidOperationException("Für SAP sind weder zentrale Zugangsdaten noch Standort-Overrides gesetzt.");
|
||||||
|
|
||||||
await AppEventLogService.WriteAsync("SAP", "Refresh aus UI gestartet", siteId: _editingSite.Id, land: _editingSite.Land,
|
await AppEventLogService.WriteAsync("SAP", "Refresh aus UI gestartet", siteId: _editingSite.Id, land: _editingSite.Land,
|
||||||
details: _editingSite.SapServiceUrl);
|
details: serviceUrl);
|
||||||
var entitySets = await SapGatewayService.GetEntitySetsAsync(_editingSite.SapServiceUrl, username.Trim(), password.Trim());
|
var entitySets = await SapGatewayService.GetEntitySetsAsync(serviceUrl, username.Trim(), password.Trim());
|
||||||
_sapEntitySetsCache = entitySets;
|
_sapEntitySetsCache = entitySets;
|
||||||
_editingSite.SapEntitySetsCache = SerializeSapEntitySets(entitySets);
|
_editingSite.SapEntitySetsCache = SerializeSapEntitySets(entitySets);
|
||||||
_editingSite.SapEntitySetsRefreshedAtUtc = DateTime.UtcNow;
|
_editingSite.SapEntitySetsRefreshedAtUtc = DateTime.UtcNow;
|
||||||
@@ -884,7 +958,7 @@
|
|||||||
var extension = Path.GetExtension(file.Name);
|
var extension = Path.GetExtension(file.Name);
|
||||||
if (!string.Equals(extension, ".xlsx", StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(extension, ".xlsx", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Bitte eine Excel-Datei mit Endung .xlsx auswählen.");
|
throw new InvalidOperationException("Bitte eine Excel-Datei mit Endung .xlsx auswählen.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var uploadDirectory = Path.Combine(AppContext.BaseDirectory, "manual-imports");
|
var uploadDirectory = Path.Combine(AppContext.BaseDirectory, "manual-imports");
|
||||||
@@ -974,13 +1048,13 @@
|
|||||||
|
|
||||||
if (activeSources.Count < 2)
|
if (activeSources.Count < 2)
|
||||||
{
|
{
|
||||||
Snackbar.Add("Für Auto-Match werden mindestens zwei aktive SAP-Quellen benötigt.", Severity.Warning);
|
Snackbar.Add("Für Auto-Match werden mindestens zwei aktive SAP-Quellen benötigt.", Severity.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_sapSourceFieldMap.Count == 0)
|
if (_sapSourceFieldMap.Count == 0)
|
||||||
{
|
{
|
||||||
Snackbar.Add("Bitte zuerst 'Felder aus Quellen laden' ausführen.", Severity.Warning);
|
Snackbar.Add("Bitte zuerst 'Felder aus Quellen laden' ausführen.", Severity.Warning);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1038,7 +1112,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
NormalizeSapConfigCollections();
|
NormalizeSapConfigCollections();
|
||||||
Snackbar.Add($"{createdOrUpdated} Join-Vorschläge gesetzt.", Severity.Success);
|
Snackbar.Add($"{createdOrUpdated} Join-Vorschläge gesetzt.", Severity.Success);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveSapJoin(SapJoinDefinition join)
|
private void RemoveSapJoin(SapJoinDefinition join)
|
||||||
@@ -1116,9 +1190,6 @@
|
|||||||
_refreshingSapSourceFields = true;
|
_refreshingSapSourceFields = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(_editingSite.SapServiceUrl))
|
|
||||||
throw new InvalidOperationException("SAP Service URL muss gesetzt sein.");
|
|
||||||
|
|
||||||
var activeSources = _sapSources
|
var activeSources = _sapSources
|
||||||
.Where(s => s.IsActive && !string.IsNullOrWhiteSpace(s.Alias) && !string.IsNullOrWhiteSpace(s.EntitySet))
|
.Where(s => s.IsActive && !string.IsNullOrWhiteSpace(s.Alias) && !string.IsNullOrWhiteSpace(s.EntitySet))
|
||||||
.OrderBy(s => s.SortOrder)
|
.OrderBy(s => s.SortOrder)
|
||||||
@@ -1129,18 +1200,29 @@
|
|||||||
throw new InvalidOperationException("Es gibt keine aktiven SAP-Quellen mit Alias und Entity Set.");
|
throw new InvalidOperationException("Es gibt keine aktiven SAP-Quellen mit Alias und Entity Set.");
|
||||||
|
|
||||||
using var db = await DbFactory.CreateDbContextAsync();
|
using var db = await DbFactory.CreateDbContextAsync();
|
||||||
var settings = await db.ExportSettings.FirstOrDefaultAsync() ?? new();
|
var sourceDefinition = await db.SourceSystemDefinitions
|
||||||
var username = string.IsNullOrWhiteSpace(_editingSite.UsernameOverride) ? settings.SapUsername : _editingSite.UsernameOverride;
|
.OrderBy(x => x.Id)
|
||||||
var password = string.IsNullOrWhiteSpace(_editingSite.PasswordOverride) ? settings.SapPassword : _editingSite.PasswordOverride;
|
.FirstOrDefaultAsync(x => x.Code == _editingSite.SourceSystem);
|
||||||
|
var serviceUrl = string.IsNullOrWhiteSpace(_editingSite.SapServiceUrl)
|
||||||
|
? sourceDefinition?.CentralServiceUrl ?? string.Empty
|
||||||
|
: _editingSite.SapServiceUrl;
|
||||||
|
if (string.IsNullOrWhiteSpace(serviceUrl))
|
||||||
|
throw new InvalidOperationException("Es ist weder eine zentrale SAP Service URL noch ein Standort-Override gesetzt.");
|
||||||
|
var username = string.IsNullOrWhiteSpace(_editingSite.UsernameOverride)
|
||||||
|
? sourceDefinition?.CentralUsername ?? string.Empty
|
||||||
|
: _editingSite.UsernameOverride;
|
||||||
|
var password = string.IsNullOrWhiteSpace(_editingSite.PasswordOverride)
|
||||||
|
? sourceDefinition?.CentralPassword ?? string.Empty
|
||||||
|
: _editingSite.PasswordOverride;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
||||||
throw new InvalidOperationException("Für SAP sind weder zentrale Zugangsdaten noch Standort-Overrides gesetzt.");
|
throw new InvalidOperationException("Für SAP sind weder zentrale Zugangsdaten noch Standort-Overrides gesetzt.");
|
||||||
|
|
||||||
var expressions = new List<string> { "=SAP" };
|
var expressions = new List<string> { "=SAP" };
|
||||||
var sourceFieldMap = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
|
var sourceFieldMap = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
|
||||||
foreach (var source in activeSources)
|
foreach (var source in activeSources)
|
||||||
{
|
{
|
||||||
var fieldNames = await SapGatewayService.GetEntityFieldNamesAsync(_editingSite.SapServiceUrl, source.EntitySet, username.Trim(), password.Trim());
|
var fieldNames = await SapGatewayService.GetEntityFieldNamesAsync(serviceUrl, source.EntitySet, username.Trim(), password.Trim());
|
||||||
sourceFieldMap[source.Alias] = fieldNames;
|
sourceFieldMap[source.Alias] = fieldNames;
|
||||||
expressions.AddRange(fieldNames.Select(field => $"{source.Alias}.{field}"));
|
expressions.AddRange(fieldNames.Select(field => $"{source.Alias}.{field}"));
|
||||||
}
|
}
|
||||||
@@ -1251,3 +1333,5 @@
|
|||||||
.ToHashSet(StringComparer.OrdinalIgnoreCase)
|
.ToHashSet(StringComparer.OrdinalIgnoreCase)
|
||||||
?? [];
|
?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -45,9 +45,9 @@
|
|||||||
<MudTd><MudCheckBox @bind-Value="context.IsActive" /></MudTd>
|
<MudTd><MudCheckBox @bind-Value="context.IsActive" /></MudTd>
|
||||||
<MudTd>
|
<MudTd>
|
||||||
<MudSelect T="string" Value="@context.SourceSystem" ValueChanged="@(v => context.SourceSystem = v)" Dense>
|
<MudSelect T="string" Value="@context.SourceSystem" ValueChanged="@(v => context.SourceSystem = v)" Dense>
|
||||||
@foreach (var system in _systems)
|
@foreach (var system in _sourceSystems.Where(x => x.IsActive))
|
||||||
{
|
{
|
||||||
<MudSelectItem Value="@system">@system</MudSelectItem>
|
<MudSelectItem Value="@system.Code">@system.DisplayName (@system.Code)</MudSelectItem>
|
||||||
}
|
}
|
||||||
</MudSelect>
|
</MudSelect>
|
||||||
</MudTd>
|
</MudTd>
|
||||||
@@ -176,7 +176,6 @@
|
|||||||
</MudDialog>
|
</MudDialog>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private readonly string[] _systems = ["SAP", "BI1", "SAGE", "MANUAL_EXCEL"];
|
|
||||||
private readonly string[] _ruleScopes = ["Value", "Record"];
|
private readonly string[] _ruleScopes = ["Value", "Record"];
|
||||||
private readonly string[] _recordFields = typeof(SalesRecord)
|
private readonly string[] _recordFields = typeof(SalesRecord)
|
||||||
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||||
@@ -185,6 +184,7 @@
|
|||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
private List<FieldTransformationRule> _rules = new();
|
private List<FieldTransformationRule> _rules = new();
|
||||||
|
private List<SourceSystemDefinition> _sourceSystems = [];
|
||||||
private IReadOnlyList<TransformationCatalogItem> _catalogItems = [];
|
private IReadOnlyList<TransformationCatalogItem> _catalogItems = [];
|
||||||
private bool _codeDialogVisible;
|
private bool _codeDialogVisible;
|
||||||
private FieldTransformationRule? _selectedRule;
|
private FieldTransformationRule? _selectedRule;
|
||||||
@@ -200,6 +200,7 @@
|
|||||||
private async Task LoadAsync()
|
private async Task LoadAsync()
|
||||||
{
|
{
|
||||||
using var db = await DbFactory.CreateDbContextAsync();
|
using var db = await DbFactory.CreateDbContextAsync();
|
||||||
|
_sourceSystems = await db.SourceSystemDefinitions.OrderBy(x => x.Code).ToListAsync();
|
||||||
_rules = await db.FieldTransformationRules.OrderBy(r => r.SortOrder).ThenBy(r => r.Id).ToListAsync();
|
_rules = await db.FieldTransformationRules.OrderBy(r => r.SortOrder).ThenBy(r => r.Id).ToListAsync();
|
||||||
|
|
||||||
foreach (var rule in _rules)
|
foreach (var rule in _rules)
|
||||||
@@ -217,7 +218,7 @@
|
|||||||
var nextSort = _rules.Count == 0 ? 10 : _rules.Max(r => r.SortOrder) + 10;
|
var nextSort = _rules.Count == 0 ? 10 : _rules.Max(r => r.SortOrder) + 10;
|
||||||
_rules.Add(new FieldTransformationRule
|
_rules.Add(new FieldTransformationRule
|
||||||
{
|
{
|
||||||
SourceSystem = "SAP",
|
SourceSystem = _sourceSystems.FirstOrDefault(x => x.IsActive)?.Code ?? "SAP",
|
||||||
RuleScope = "Value",
|
RuleScope = "Value",
|
||||||
SourceField = nameof(SalesRecord.Material),
|
SourceField = nameof(SalesRecord.Material),
|
||||||
TargetField = nameof(SalesRecord.Material),
|
TargetField = nameof(SalesRecord.Material),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ public class AppDbContext : DbContext
|
|||||||
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
|
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
|
||||||
|
|
||||||
public DbSet<HanaServer> HanaServers => Set<HanaServer>();
|
public DbSet<HanaServer> HanaServers => Set<HanaServer>();
|
||||||
|
public DbSet<SourceSystemDefinition> SourceSystemDefinitions => Set<SourceSystemDefinition>();
|
||||||
public DbSet<Site> Sites => Set<Site>();
|
public DbSet<Site> Sites => Set<Site>();
|
||||||
public DbSet<SharePointConfig> SharePointConfigs => Set<SharePointConfig>();
|
public DbSet<SharePointConfig> SharePointConfigs => Set<SharePointConfig>();
|
||||||
public DbSet<ExportSettings> ExportSettings => Set<ExportSettings>();
|
public DbSet<ExportSettings> ExportSettings => Set<ExportSettings>();
|
||||||
|
|||||||
@@ -43,6 +43,426 @@ Ergebnis:
|
|||||||
- bekannte Warnung bleibt:
|
- bekannte Warnung bleibt:
|
||||||
- SAP HANA Architekturwarnung `MSB3270`
|
- SAP HANA Architekturwarnung `MSB3270`
|
||||||
|
|
||||||
|
## Architekturpruefung 2026-04-17
|
||||||
|
|
||||||
|
Es wurde eine erneute Gesamtpruefung der Architektur gemacht, ausdruecklich ohne neue Implementierung.
|
||||||
|
|
||||||
|
### Gesamturteil
|
||||||
|
|
||||||
|
Die Grundrichtung ist weiterhin sinnvoll:
|
||||||
|
|
||||||
|
- klare Trennung der Quellsysteme `SAP`, `BI1`, `SAGE`, `MANUAL_EXCEL`
|
||||||
|
- zentrales fachliches Zielschema ueber `SalesRecord`
|
||||||
|
- zentrale technische Ablage ueber `CentralSalesRecords`
|
||||||
|
- separater Orchestrator fuer Standort- und Konsolidierungsexport
|
||||||
|
- Transformationssystem als eigener Layer
|
||||||
|
|
||||||
|
Aber:
|
||||||
|
|
||||||
|
- die Architektur ist **noch nicht stabil genug**, um sie als "fertig sauber" zu betrachten
|
||||||
|
- die groessten Risiken liegen aktuell nicht in SAP oder Waehrungen, sondern in
|
||||||
|
- Start-/Schema-Initialisierung
|
||||||
|
- Config-Import
|
||||||
|
- Verteilung von Logik zwischen Razor-Seiten und Services
|
||||||
|
|
||||||
|
### Wichtigste Architektur-Risiken
|
||||||
|
|
||||||
|
#### 1. Start-/Schema-Initialisierung ist fragil
|
||||||
|
|
||||||
|
`DatabaseInitializationService` mischt derzeit:
|
||||||
|
|
||||||
|
- `EnsureCreated`
|
||||||
|
- manuelle `ALTER TABLE`-Pflege
|
||||||
|
- FK-Reparaturlogik
|
||||||
|
- Seeding
|
||||||
|
- empfohlenes Regel-Seeding
|
||||||
|
|
||||||
|
Das ist funktional hilfreich, aber architektonisch gefaehrlich, weil:
|
||||||
|
|
||||||
|
- die App-Initialisierung dadurch viel implizite Datenmigration enthaelt
|
||||||
|
- Verhalten schwer vorhersehbar wird
|
||||||
|
- Fehler im Migrationspfad sofort produktive Daten treffen
|
||||||
|
|
||||||
|
Wichtiger konkreter Befund aus der Pruefung:
|
||||||
|
|
||||||
|
- beim Kopieren von `Sites_old` nach `Sites` ist die Spaltenreihenfolge im SQL inkonsistent
|
||||||
|
- dadurch koennen Werte wie `ManualImportFilePath`, `SapServiceUrl`, `SapEntitySet` verschoben gespeichert werden
|
||||||
|
- das ist eine reale Datenkorruptionsgefahr und kein reines Architekturthema
|
||||||
|
|
||||||
|
### 2. Config-Import ist destruktiv und nicht atomar
|
||||||
|
|
||||||
|
`ConfigTransferService.ImportJsonAsync` loescht aktuell zuerst grosse Teile der Konfiguration und Daten:
|
||||||
|
|
||||||
|
- Sites
|
||||||
|
- HanaServers
|
||||||
|
- Transformation Rules
|
||||||
|
- SAP-Konfiguration
|
||||||
|
- Wechselkurse
|
||||||
|
- sogar `CentralSalesRecords`
|
||||||
|
|
||||||
|
und baut danach mit mehreren `SaveChangesAsync()`-Zwischenschritten neu auf.
|
||||||
|
|
||||||
|
Risiko:
|
||||||
|
|
||||||
|
- wenn der Import in der Mitte scheitert, bleibt das System teilweise geloescht zurueck
|
||||||
|
- `CentralSalesRecords` gehoeren fachlich ohnehin nicht sauber in einen normalen Config-Import
|
||||||
|
|
||||||
|
### 3. Zu viel Fach- und Persistenzlogik in Razor-Seiten
|
||||||
|
|
||||||
|
`Settings.razor` und `Standorte.razor` machen aktuell sehr viel direkt:
|
||||||
|
|
||||||
|
- `DbContext` oeffnen
|
||||||
|
- Daten laden und speichern
|
||||||
|
- Konfigurationsimport/-export anstossen
|
||||||
|
- SAP-Refresh
|
||||||
|
- Upload-Handling
|
||||||
|
- Teile der Validierung / Persistenzlogik
|
||||||
|
|
||||||
|
Das funktioniert momentan, fuehrt aber langfristig zu:
|
||||||
|
|
||||||
|
- schwer testbarer UI-Logik
|
||||||
|
- verstreuten Regeln
|
||||||
|
- hoeherem Seiteneffekt-Risiko bei Erweiterungen
|
||||||
|
|
||||||
|
### 4. Vertrag zwischen Orchestrator und konsolidiertem Export ist unscharf
|
||||||
|
|
||||||
|
`ExportOrchestrationService` sammelt bei `ExportAllAsync` bereits `consolidatedRecords`, uebergibt sie weiter, aber `ConsolidatedExportService` ignoriert diesen Parameter und liest erneut aus `CentralSalesRecords`.
|
||||||
|
|
||||||
|
Das zeigt ein offenes Architekturthema:
|
||||||
|
|
||||||
|
- Soll die zentrale Datei aus dem Live-Exportlauf gebaut werden?
|
||||||
|
- oder immer nur aus dem persistenten Read Model `CentralSalesRecords`?
|
||||||
|
|
||||||
|
Aktuell ist beides halb vorhanden.
|
||||||
|
|
||||||
|
### 5. Reporting-/Cockpit-Logik ist noch nicht voll verallgemeinert
|
||||||
|
|
||||||
|
Bei der Pruefung wurde gesehen:
|
||||||
|
|
||||||
|
- `ManagementCockpitService` enthaelt noch hartcodierte Jahreslogik fuer `2025` und `2026`
|
||||||
|
- die Rohsicht bleibt bewusst ohne CHF-Umrechnung
|
||||||
|
|
||||||
|
Das ist fuer den aktuellen Stand akzeptabel, zeigt aber:
|
||||||
|
|
||||||
|
- Reporting ist noch kein voll abstrahierter fachlicher Layer
|
||||||
|
|
||||||
|
## Empfohlenes Sollbild
|
||||||
|
|
||||||
|
Die naechste Architektur-Stufe sollte in diese Richtung gehen:
|
||||||
|
|
||||||
|
### 1. Klare Schichten
|
||||||
|
|
||||||
|
- UI:
|
||||||
|
- Razor nur fuer Interaktion, Anzeige, Formularzustand
|
||||||
|
- Application:
|
||||||
|
- Use Cases / Commands / Queries fuer Export, Config, SAP-Refresh, Wechselkurse, Standortpflege
|
||||||
|
- Domain / Fachlogik:
|
||||||
|
- Transformationen, Mappingregeln, Waehrungsumrechnung, Cockpit-Berechnungen
|
||||||
|
- Infrastructure:
|
||||||
|
- HANA, SAP Gateway, SQLite, SharePoint, Dateisystem
|
||||||
|
|
||||||
|
### 2. Versionierte Migrationen statt manueller Start-Reparaturen
|
||||||
|
|
||||||
|
Statt immer mehr Reparaturlogik beim App-Start:
|
||||||
|
|
||||||
|
- Schema-Aenderungen versionieren
|
||||||
|
- Migrationspfade testbar machen
|
||||||
|
- Startlogik nur noch fuer minimale Bootstrap-Aufgaben behalten
|
||||||
|
|
||||||
|
### 3. Config-Import als atomarer Vorgang
|
||||||
|
|
||||||
|
Ziel:
|
||||||
|
|
||||||
|
- alles in einer Transaktion oder bewusst in klar getrennten Phasen
|
||||||
|
- kein halb geloeschter Zustand bei Fehlern
|
||||||
|
- `CentralSalesRecords` aus normalem Config-Import eher herausnehmen
|
||||||
|
|
||||||
|
### 4. Zentrale Export-Semantik entscheiden
|
||||||
|
|
||||||
|
Explizit festlegen:
|
||||||
|
|
||||||
|
- zentrale Datei immer aus `CentralSalesRecords`
|
||||||
|
oder
|
||||||
|
- zentrale Datei aus dem aktuellen Export-Snapshot
|
||||||
|
|
||||||
|
Danach die doppelte Semantik entfernen.
|
||||||
|
|
||||||
|
## Priorisierung aus Architektursicht
|
||||||
|
|
||||||
|
Wenn nach Stabilitaet priorisiert wird, dann in dieser Reihenfolge:
|
||||||
|
|
||||||
|
1. `DatabaseInitializationService` / Migrationspfad absichern
|
||||||
|
2. `ConfigTransferService.ImportJsonAsync` atomar und weniger destruktiv machen
|
||||||
|
3. Logik aus `Settings.razor` und `Standorte.razor` in Anwendungsservices verschieben
|
||||||
|
4. Export-Semantik fuer Konsolidierung vereinheitlichen
|
||||||
|
5. erst danach weitere Fachfeatures wie Cockpit-CHF, Budget, Gruppenlogik
|
||||||
|
|
||||||
|
## Kurzfazit
|
||||||
|
|
||||||
|
Die Architektur ist nicht schlecht. Das Grundmodell traegt.
|
||||||
|
|
||||||
|
Aber:
|
||||||
|
|
||||||
|
- sie ist noch nicht robust genug fuer ruhigen weiteren Ausbau ohne technische Konsolidierung
|
||||||
|
- die aktuelle Hauptgefahr liegt in Infrastruktur- und Persistenzlogik, nicht in den Fachfeatures
|
||||||
|
|
||||||
|
Fuer den naechsten Einstieg nach Absturz gilt daher:
|
||||||
|
|
||||||
|
1. zuerst diesen Architektur-Nachtrag lesen
|
||||||
|
2. dann `DatabaseInitializationService` und `ConfigTransferService` als Risikobloecke ansehen
|
||||||
|
3. neue Fachfeatures erst nach dieser technischen Konsolidierung beginnen
|
||||||
|
|
||||||
|
## Nachtrag HANA-/Standort-Workflow 2026-04-17
|
||||||
|
|
||||||
|
Nach der Architekturpruefung wurde der doppelte HANA-Workflow bereinigt.
|
||||||
|
|
||||||
|
### Altes Problem
|
||||||
|
|
||||||
|
Vorher gab es zwei konkurrierende Stellen fuer HANA-Konfiguration:
|
||||||
|
|
||||||
|
- oben eine eigene `HANA Server`-Verwaltung
|
||||||
|
- unten im Standortdialog noch einmal eine fast vollstaendige HANA-Verbindung
|
||||||
|
|
||||||
|
Dadurch war unklar:
|
||||||
|
|
||||||
|
- was die zentrale Wahrheit ist
|
||||||
|
- wann ein zentraler Server geaendert wird
|
||||||
|
- wann still ein separater Server pro Standort entsteht
|
||||||
|
|
||||||
|
### Neue Logik
|
||||||
|
|
||||||
|
Oben gilt jetzt:
|
||||||
|
|
||||||
|
- `HANA Server` ist zentrale HANA-Konfiguration pro Quellsystem
|
||||||
|
- aktuell relevant fuer:
|
||||||
|
- `BI1`
|
||||||
|
- `SAGE`
|
||||||
|
|
||||||
|
Unten im Standort gilt jetzt:
|
||||||
|
|
||||||
|
- Standort pflegt nur noch standortspezifische Daten
|
||||||
|
- `Schema`
|
||||||
|
- `TSC`
|
||||||
|
- `Land`
|
||||||
|
- `SourceSystem`
|
||||||
|
- optionale Username-/Password-Overrides
|
||||||
|
- die technische HANA-Verbindung kommt aus der zentralen Konfiguration des Quellsystems
|
||||||
|
|
||||||
|
### Technische Umsetzung
|
||||||
|
|
||||||
|
- `HanaServer` hat jetzt zusaetzlich `SourceSystem`
|
||||||
|
- `DatabaseInitializationService` stellt zentrale Eintraege fuer `BI1` und `SAGE` sicher
|
||||||
|
- bestehende verknuepfte HANA-Server werden dabei moeglichst auf `BI1` / `SAGE` gemappt
|
||||||
|
- `SiteExportService` baut HANA-Verbindungen jetzt aus der zentralen HANA-Konfiguration des Quellsystems
|
||||||
|
- `Settings.razor` testet BI1/SAGE nicht mehr ueber einen Beispiel-Standort, sondern ueber die zentrale HANA-Konfiguration
|
||||||
|
- `Standorte.razor` speichert im Standort fuer HANA-basierte Systeme keine eigene Vollverbindung mehr
|
||||||
|
|
||||||
|
### Wichtige Konsequenz
|
||||||
|
|
||||||
|
Fachlich gilt jetzt:
|
||||||
|
|
||||||
|
- oben = Standardkonfiguration pro Quellsystem
|
||||||
|
- unten = Standort + optionale Credential-Overrides
|
||||||
|
|
||||||
|
Das entspricht der gewuenschten Logik:
|
||||||
|
|
||||||
|
- gleiche BI1-/SAGE-Standorte koennen zentrale Verbindungswerte teilen
|
||||||
|
- Ausnahmen koennen weiter ueber Username-/Password-Overrides reagieren
|
||||||
|
|
||||||
|
### UI-Nachtrag
|
||||||
|
|
||||||
|
Die frueher doppelte und dadurch verwirrende UI wurde danach auch sichtbar bereinigt.
|
||||||
|
|
||||||
|
Aktueller UI-Stand:
|
||||||
|
|
||||||
|
- oben heisst der Bereich jetzt klar `Zentrale HANA-Konfiguration`
|
||||||
|
- im Standortdialog gibt es fuer HANA keine zweite technische Eingabestrecke mehr
|
||||||
|
- dort wird nur noch die aktive Zentralverbindung angezeigt
|
||||||
|
- Host, Port, SSL und technische Parameter werden explizit nach oben verwiesen
|
||||||
|
- der zentrale Verbindungstest in `Settings.razor` meldet jetzt sauber die zentrale HANA-Verbindung
|
||||||
|
|
||||||
|
## Nachtrag Quellsystem-Verwaltung 2026-04-17
|
||||||
|
|
||||||
|
Die bisher noch hart codierten Quellsystem-Listen wurden entfernt und durch echte Stammdaten ersetzt.
|
||||||
|
|
||||||
|
### Neuer Stand
|
||||||
|
|
||||||
|
- neues Modell `SourceSystemDefinition`
|
||||||
|
- Quellsysteme werden jetzt zentral in der DB gehalten statt in Razor-Arrays
|
||||||
|
- pro Quellsystem werden gepflegt:
|
||||||
|
- `Code`
|
||||||
|
- `DisplayName`
|
||||||
|
- `ConnectionKind`
|
||||||
|
- `IsActive`
|
||||||
|
- `CentralUsername`
|
||||||
|
- `CentralPassword`
|
||||||
|
|
||||||
|
### Neue GUI-Logik
|
||||||
|
|
||||||
|
- `Settings.razor` enthaelt jetzt eine pflegbare Quellsystem-Tabelle
|
||||||
|
- dort koennen Quellsysteme per GUI angelegt, bearbeitet und gespeichert werden
|
||||||
|
- Anschlussart ist nicht mehr implizit im Code, sondern pro Quellsystem konfigurierbar
|
||||||
|
- zentrale Zugangsdaten haengen jetzt am Quellsystem selbst
|
||||||
|
|
||||||
|
### Anschlussarten
|
||||||
|
|
||||||
|
Aktuell technisch vorgesehen:
|
||||||
|
|
||||||
|
- `HANA`
|
||||||
|
- `SAP_GATEWAY`
|
||||||
|
- `MANUAL_EXCEL`
|
||||||
|
|
||||||
|
Damit gilt:
|
||||||
|
|
||||||
|
- HANA-Konfiguration oben in `Standorte.razor` nur noch fuer Quellsysteme mit Anschlussart `HANA`
|
||||||
|
- Standort-Dropdown zieht seine Quellsysteme jetzt aus `SourceSystemDefinitions`
|
||||||
|
- Transformationsregeln ziehen ihre Quellsystem-Auswahl ebenfalls aus `SourceSystemDefinitions`
|
||||||
|
|
||||||
|
### Technische Umsetzung
|
||||||
|
|
||||||
|
- `AppDbContext` hat jetzt `DbSet<SourceSystemDefinition>`
|
||||||
|
- `DatabaseInitializationService` erzeugt und seedet `SourceSystemDefinitions`
|
||||||
|
- `SiteExportService` loest zentrale Credentials jetzt ueber `SourceSystemDefinition`
|
||||||
|
- `ConfigTransferService` exportiert/importiert jetzt auch `SourceSystemDefinitions`
|
||||||
|
|
||||||
|
### Verifikation
|
||||||
|
|
||||||
|
Nach dieser Umstellung geprueft:
|
||||||
|
|
||||||
|
```text
|
||||||
|
dotnet build .\TrafagSalesExporter.csproj -v minimal
|
||||||
|
dotnet test .\TrafagSalesExporter.Tests\TrafagSalesExporter.Tests.csproj --verbosity minimal
|
||||||
|
```
|
||||||
|
|
||||||
|
Ergebnis:
|
||||||
|
|
||||||
|
- Build erfolgreich
|
||||||
|
- Tests erfolgreich
|
||||||
|
- `31/31` Tests gruen
|
||||||
|
|
||||||
|
### Bereinigung der Legacy-Credentials
|
||||||
|
|
||||||
|
Danach wurden auch die alten zentralen Credential-Felder technisch bereinigt.
|
||||||
|
|
||||||
|
Aktueller Stand:
|
||||||
|
|
||||||
|
- `ExportSettings` enthaelt keine alten Felder mehr fuer `SapUsername`, `Bi1Username`, `SageUsername` usw.
|
||||||
|
- der Config-Export schreibt zentrale Zugangsdaten nur noch ueber `SourceSystemDefinitions`
|
||||||
|
- `ConfigTransferService` hat keinen aktiven Legacy-Credential-Pfad mehr
|
||||||
|
- die fruehere Temp-Datei `standorte_numbered.tmp` wurde entfernt
|
||||||
|
|
||||||
|
Wichtig:
|
||||||
|
|
||||||
|
- bestehende DB-Spalten koennen physisch noch vorhanden sein, sind aber kein aktiver Codepfad mehr
|
||||||
|
- fuehrende Wahrheit fuer zentrale Zugangsdaten ist jetzt ausschliesslich `SourceSystemDefinition`
|
||||||
|
|
||||||
|
### Schema-Bereinigung
|
||||||
|
|
||||||
|
Danach wurde auch die SQLite-Schemabereinigung nachgezogen.
|
||||||
|
|
||||||
|
Aktueller Stand:
|
||||||
|
|
||||||
|
- `DatabaseInitializationService` erkennt alte Credential-Spalten in `ExportSettings`
|
||||||
|
- wenn diese Legacy-Spalten noch existieren, wird `ExportSettings` beim Start auf das neue Schema rekonstruiert
|
||||||
|
- erhalten bleiben nur die noch gueltigen Felder:
|
||||||
|
- `DateFilter`
|
||||||
|
- `TimerHour`
|
||||||
|
- `TimerMinute`
|
||||||
|
- `TimerEnabled`
|
||||||
|
- `DebugLoggingEnabled`
|
||||||
|
- `LocalSiteExportFolder`
|
||||||
|
- `LocalConsolidatedExportFolder`
|
||||||
|
|
||||||
|
Damit gilt jetzt:
|
||||||
|
|
||||||
|
- alte zentrale SAP/BI1/SAGE-Credentials sind nicht nur logisch entfernt
|
||||||
|
- sie werden bei bestehender DB auch aktiv aus dem `ExportSettings`-Schema entfernt
|
||||||
|
|
||||||
|
### Letzte Bereinigung HANA-Credentials
|
||||||
|
|
||||||
|
Danach wurde auch die letzte doppelte Credential-Stelle in der HANA-Verwaltung entfernt.
|
||||||
|
|
||||||
|
Aktueller Stand:
|
||||||
|
|
||||||
|
- zentrale HANA-Konfiguration speichert nur noch technische Verbindungsdaten
|
||||||
|
- `Host`
|
||||||
|
- `Port`
|
||||||
|
- `DatabaseName`
|
||||||
|
- `UseSsl`
|
||||||
|
- `ValidateCertificate`
|
||||||
|
- `AdditionalParams`
|
||||||
|
- Username/Password werden nicht mehr in der zentralen HANA-UI gepflegt
|
||||||
|
- HANA-Verbindungstests in `Standorte.razor` verwenden jetzt die zentralen Credentials aus `SourceSystemDefinition`
|
||||||
|
- `SiteExportService` faellt bei HANA nicht mehr auf in `HanaServer` gespeicherte Credentials zurueck
|
||||||
|
- `ConfigTransferService` exportiert/importiert fuer `HanaServer` keine Username-/Password-Werte mehr
|
||||||
|
- `DatabaseInitializationService` bereinigt bei bestehender DB auch das `HanaServers`-Schema und entfernt die Altspalten `Username` / `Password`
|
||||||
|
|
||||||
|
Die fachliche Reihenfolge ist jetzt eindeutig:
|
||||||
|
|
||||||
|
1. zentrale Credentials aus `SourceSystemDefinition`
|
||||||
|
2. optionale Override-Credentials am `Site`
|
||||||
|
3. technische HANA-Verbindung aus der zentralen HANA-Konfiguration
|
||||||
|
|
||||||
|
### EF-/SQLite-Fix
|
||||||
|
|
||||||
|
Beim ersten Lauf nach der Schema-Bereinigung trat noch ein Mapping-Fehler auf:
|
||||||
|
|
||||||
|
- `SQLite Error 1: 'no such column: h.Password'`
|
||||||
|
|
||||||
|
Ursache:
|
||||||
|
|
||||||
|
- `HanaServers`-Schema war bereits ohne `Username` / `Password`
|
||||||
|
- das EF-Modell `HanaServer` hat diese Properties aber noch als normale Spalten behandelt
|
||||||
|
|
||||||
|
Fix:
|
||||||
|
|
||||||
|
- `HanaServer.Username` und `HanaServer.Password` sind jetzt `[NotMapped]`
|
||||||
|
- damit bleiben sie fuer Laufzeit-Verbindungsaufbau und Tests nutzbar
|
||||||
|
- EF erwartet sie aber nicht mehr als Datenbankspalten
|
||||||
|
|
||||||
|
## Nachtrag Zentrale SAP-Steuerung 2026-04-17
|
||||||
|
|
||||||
|
Der verbleibende Architekturbruch bei SAP wurde ebenfalls bereinigt.
|
||||||
|
|
||||||
|
### Neuer Stand
|
||||||
|
|
||||||
|
- `SourceSystemDefinition` enthaelt jetzt auch `CentralServiceUrl`
|
||||||
|
- zentrale SAP-Service-URL wird damit am Quellsystem gepflegt, nicht mehr primaer am Standort
|
||||||
|
- `Standorte.razor` behandelt `SapServiceUrl` jetzt als Override
|
||||||
|
- wenn kein Override gesetzt ist, zieht SAP die URL zentral aus dem Quellsystem
|
||||||
|
|
||||||
|
### UI
|
||||||
|
|
||||||
|
- `Settings.razor` hat fuer Quellsysteme jetzt eine Dialogbearbeitung statt nur Inline-Tabellenfelder
|
||||||
|
- dadurch ist das Quellsystem sauber editierbar
|
||||||
|
- fuer `SAP_GATEWAY` wird dort die zentrale SAP-Service-URL gepflegt
|
||||||
|
- `Standorte.razor` zeigt bei SAP jetzt:
|
||||||
|
- zentrale SAP Service URL
|
||||||
|
- optionales `SAP Service URL Override`
|
||||||
|
|
||||||
|
### Laufzeitlogik
|
||||||
|
|
||||||
|
- `SiteExportService` verwendet bei SAP die effektive URL aus
|
||||||
|
- Standort-Override
|
||||||
|
- sonst `SourceSystemDefinition.CentralServiceUrl`
|
||||||
|
- SAP-Verbindungstest in `Settings.razor` testet die zentrale URL direkt aus dem Quellsystem
|
||||||
|
- Dashboard zeigt fuer SAP jetzt ebenfalls die effektive zentrale bzw. ueberschriebene URL
|
||||||
|
|
||||||
|
### Verifikation
|
||||||
|
|
||||||
|
Nach der Umstellung geprueft:
|
||||||
|
|
||||||
|
```text
|
||||||
|
dotnet build .\TrafagSalesExporter.csproj -v minimal
|
||||||
|
dotnet test .\TrafagSalesExporter.Tests\TrafagSalesExporter.Tests.csproj --verbosity minimal
|
||||||
|
```
|
||||||
|
|
||||||
|
Ergebnis:
|
||||||
|
|
||||||
|
- Build erfolgreich
|
||||||
|
- Tests erfolgreich
|
||||||
|
- `31/31` Tests gruen
|
||||||
|
|
||||||
## Nachtrag 2026-04-16
|
## Nachtrag 2026-04-16
|
||||||
|
|
||||||
Seit dem letzten Handoff wurden weitere Funktionen umgesetzt, die unten im alten Stand noch nicht voll enthalten sind.
|
Seit dem letzten Handoff wurden weitere Funktionen umgesetzt, die unten im alten Stand noch nicht voll enthalten sind.
|
||||||
@@ -518,3 +938,45 @@ Ergebnis:
|
|||||||
- bekannte Warnungen bleiben:
|
- bekannte Warnungen bleiben:
|
||||||
- SAP HANA Architekturwarnung `MSB3270`
|
- SAP HANA Architekturwarnung `MSB3270`
|
||||||
- MudBlazor Analyzer `Dense`
|
- MudBlazor Analyzer `Dense`
|
||||||
|
|
||||||
|
## Nachtrag 2026-04-17 UI-Klarstellung HANA vs. SAP
|
||||||
|
|
||||||
|
- `Components/Pages/Standorte.razor`
|
||||||
|
- Bereich oben heisst jetzt bewusst `Zentrale HANA-Technik`
|
||||||
|
- Hinweistext stellt klar: dort erscheinen nur Quellsysteme mit Anschlussart `HANA`
|
||||||
|
- `SAP` wird zentral unter `Settings -> Quellsysteme` gepflegt und gehoert nicht in diese Box
|
||||||
|
- der irrefuehrende Button `Server hinzufuegen` wurde entfernt
|
||||||
|
- neue HANA-Zeilen entstehen aus den Quellsystem-Stammdaten, nicht mehr aus einer zweiten UI-Erfassung
|
||||||
|
- Dialogtitel fuer HANA wurde auf reine Bearbeitung der zentralen Technik reduziert
|
||||||
|
|
||||||
|
Fachliche Regel jetzt:
|
||||||
|
|
||||||
|
- `Quellsysteme` verwalten die zentralen Systeme und deren Anschlussart
|
||||||
|
- `Standorte` zeigen fuer HANA nur noch die technische Zentralverbindung
|
||||||
|
- `SAP` wird nicht mehr implizit in der HANA-Box erwartet
|
||||||
|
|
||||||
|
## Nachtrag 2026-04-17 Pruefung Config-Import/Export
|
||||||
|
|
||||||
|
Der aktuelle Config-Transfer wurde nach den Umbauten nochmals geprueft.
|
||||||
|
|
||||||
|
Status:
|
||||||
|
|
||||||
|
- Das aktuelle Import-/Exportformat passt zum neuen Modell.
|
||||||
|
- `SourceSystemDefinitions` werden mit `ConnectionKind`, `CentralServiceUrl`, `CentralUsername`, `CentralPassword` importiert/exportiert.
|
||||||
|
- `HanaServers` enthalten nur noch technische HANA-Verbindungsdaten und keine Credentials mehr.
|
||||||
|
- Standort-Overrides fuer Username/Password sowie SAP Service URL gehen weiterhin mit.
|
||||||
|
- Die vorhandenen `ConfigTransferServiceTests` laufen grün.
|
||||||
|
|
||||||
|
Weiterhin offene Architekturpunkte:
|
||||||
|
|
||||||
|
- `ConfigTransferService.ImportJsonAsync` ist weiterhin destruktiv und nicht atomar.
|
||||||
|
- Erst werden bestehende Daten geloescht, danach wird in mehreren Schritten neu aufgebaut.
|
||||||
|
- Wenn der Import in der Mitte scheitert, bleibt ein teilweiser Zustand zurueck.
|
||||||
|
- Altformat-Risiko bei `ConnectionKind`:
|
||||||
|
- Wenn ein aelteres JSON bereits `SourceSystemDefinitions` enthaelt, aber noch ohne `ConnectionKind`, faellt der DTO-Default auf `HANA`.
|
||||||
|
- Dadurch koennte ein altes `SAP` beim Import falsch als `HANA` landen.
|
||||||
|
|
||||||
|
Fazit:
|
||||||
|
|
||||||
|
- Fuer Exporte aus dem aktuellen Stand ist der Config-Transfer konsistent.
|
||||||
|
- Fuer aeltere JSON-Staende braucht der Import noch eine explizite Migrations-/Fallback-Logik.
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public class ConfigTransferPackage
|
|||||||
public bool IncludesSecrets { get; set; }
|
public bool IncludesSecrets { get; set; }
|
||||||
public ConfigTransferSharePoint? SharePointConfig { get; set; }
|
public ConfigTransferSharePoint? SharePointConfig { get; set; }
|
||||||
public ConfigTransferExportSettings? ExportSettings { get; set; }
|
public ConfigTransferExportSettings? ExportSettings { get; set; }
|
||||||
|
public List<ConfigTransferSourceSystemDefinition> SourceSystemDefinitions { get; set; } = [];
|
||||||
public List<ConfigTransferCurrencyExchangeRate> CurrencyExchangeRates { get; set; } = [];
|
public List<ConfigTransferCurrencyExchangeRate> CurrencyExchangeRates { get; set; } = [];
|
||||||
public List<ConfigTransferHanaServer> HanaServers { get; set; } = [];
|
public List<ConfigTransferHanaServer> HanaServers { get; set; } = [];
|
||||||
public List<ConfigTransferSite> Sites { get; set; } = [];
|
public List<ConfigTransferSite> Sites { get; set; } = [];
|
||||||
@@ -16,6 +17,17 @@ public class ConfigTransferPackage
|
|||||||
public List<ConfigTransferSapFieldMapping> SapFieldMappings { get; set; } = [];
|
public List<ConfigTransferSapFieldMapping> SapFieldMappings { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ConfigTransferSourceSystemDefinition
|
||||||
|
{
|
||||||
|
public string Code { get; set; } = string.Empty;
|
||||||
|
public string DisplayName { get; set; } = string.Empty;
|
||||||
|
public string ConnectionKind { get; set; } = SourceSystemConnectionKinds.Hana;
|
||||||
|
public bool IsActive { get; set; } = true;
|
||||||
|
public string CentralServiceUrl { get; set; } = string.Empty;
|
||||||
|
public string? CentralUsername { get; set; }
|
||||||
|
public string? CentralPassword { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class ConfigTransferSharePoint
|
public class ConfigTransferSharePoint
|
||||||
{
|
{
|
||||||
public string SiteUrl { get; set; } = string.Empty;
|
public string SiteUrl { get; set; } = string.Empty;
|
||||||
@@ -35,12 +47,6 @@ public class ConfigTransferExportSettings
|
|||||||
public bool DebugLoggingEnabled { get; set; }
|
public bool DebugLoggingEnabled { get; set; }
|
||||||
public string LocalSiteExportFolder { get; set; } = string.Empty;
|
public string LocalSiteExportFolder { get; set; } = string.Empty;
|
||||||
public string LocalConsolidatedExportFolder { get; set; } = string.Empty;
|
public string LocalConsolidatedExportFolder { get; set; } = string.Empty;
|
||||||
public string? SapUsername { get; set; }
|
|
||||||
public string? SapPassword { get; set; }
|
|
||||||
public string? Bi1Username { get; set; }
|
|
||||||
public string? Bi1Password { get; set; }
|
|
||||||
public string? SageUsername { get; set; }
|
|
||||||
public string? SagePassword { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ConfigTransferCurrencyExchangeRate
|
public class ConfigTransferCurrencyExchangeRate
|
||||||
@@ -57,11 +63,10 @@ public class ConfigTransferCurrencyExchangeRate
|
|||||||
public class ConfigTransferHanaServer
|
public class ConfigTransferHanaServer
|
||||||
{
|
{
|
||||||
public string Key { get; set; } = Guid.NewGuid().ToString("N");
|
public string Key { get; set; } = Guid.NewGuid().ToString("N");
|
||||||
|
public string SourceSystem { get; set; } = string.Empty;
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
public string Host { get; set; } = string.Empty;
|
public string Host { get; set; } = string.Empty;
|
||||||
public int Port { get; set; } = 30015;
|
public int Port { get; set; } = 30015;
|
||||||
public string? Username { get; set; }
|
|
||||||
public string? Password { get; set; }
|
|
||||||
public string DatabaseName { get; set; } = string.Empty;
|
public string DatabaseName { get; set; } = string.Empty;
|
||||||
public bool UseSsl { get; set; }
|
public bool UseSsl { get; set; }
|
||||||
public bool ValidateCertificate { get; set; }
|
public bool ValidateCertificate { get; set; }
|
||||||
@@ -75,7 +80,7 @@ public class ConfigTransferSite
|
|||||||
public string Schema { get; set; } = string.Empty;
|
public string Schema { get; set; } = string.Empty;
|
||||||
public string TSC { get; set; } = string.Empty;
|
public string TSC { get; set; } = string.Empty;
|
||||||
public string Land { get; set; } = string.Empty;
|
public string Land { get; set; } = string.Empty;
|
||||||
public string SourceSystem { get; set; } = "SAP";
|
public string SourceSystem { get; set; } = string.Empty;
|
||||||
public string? UsernameOverride { get; set; }
|
public string? UsernameOverride { get; set; }
|
||||||
public string? PasswordOverride { get; set; }
|
public string? PasswordOverride { get; set; }
|
||||||
public string LocalExportFolderOverride { get; set; } = string.Empty;
|
public string LocalExportFolderOverride { get; set; } = string.Empty;
|
||||||
|
|||||||
@@ -10,10 +10,4 @@ public class ExportSettings
|
|||||||
public bool DebugLoggingEnabled { get; set; }
|
public bool DebugLoggingEnabled { get; set; }
|
||||||
public string LocalSiteExportFolder { get; set; } = string.Empty;
|
public string LocalSiteExportFolder { get; set; } = string.Empty;
|
||||||
public string LocalConsolidatedExportFolder { get; set; } = string.Empty;
|
public string LocalConsolidatedExportFolder { get; set; } = string.Empty;
|
||||||
public string SapUsername { get; set; } = string.Empty;
|
|
||||||
public string SapPassword { get; set; } = string.Empty;
|
|
||||||
public string Bi1Username { get; set; } = string.Empty;
|
|
||||||
public string Bi1Password { get; set; } = string.Empty;
|
|
||||||
public string SageUsername { get; set; } = string.Empty;
|
|
||||||
public string SagePassword { get; set; } = string.Empty;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ public class FieldTransformationRule
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public string SourceSystem { get; set; } = "SAP";
|
public string SourceSystem { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public string SourceField { get; set; } = nameof(SalesRecord.Material);
|
public string SourceField { get; set; } = nameof(SalesRecord.Material);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Data.Common;
|
using System.Data.Common;
|
||||||
|
|
||||||
namespace TrafagSalesExporter.Models;
|
namespace TrafagSalesExporter.Models;
|
||||||
@@ -7,6 +8,9 @@ public class HanaServer
|
|||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string SourceSystem { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
|
|
||||||
@@ -15,8 +19,10 @@ public class HanaServer
|
|||||||
|
|
||||||
public int Port { get; set; } = 30015;
|
public int Port { get; set; } = 30015;
|
||||||
|
|
||||||
|
[NotMapped]
|
||||||
public string Username { get; set; } = string.Empty;
|
public string Username { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[NotMapped]
|
||||||
public string Password { get; set; } = string.Empty;
|
public string Password { get; set; } = string.Empty;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -66,6 +72,7 @@ public class HanaServer
|
|||||||
var pwdMasked = string.IsNullOrEmpty(Password) ? "" : "***";
|
var pwdMasked = string.IsNullOrEmpty(Password) ? "" : "***";
|
||||||
var copy = new HanaServer
|
var copy = new HanaServer
|
||||||
{
|
{
|
||||||
|
SourceSystem = SourceSystem,
|
||||||
Host = Host,
|
Host = Host,
|
||||||
Port = Port,
|
Port = Port,
|
||||||
Username = Username,
|
Username = Username,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class Site
|
|||||||
public string Land { get; set; } = string.Empty;
|
public string Land { get; set; } = string.Empty;
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public string SourceSystem { get; set; } = "SAP";
|
public string SourceSystem { get; set; } = string.Empty;
|
||||||
|
|
||||||
public string UsernameOverride { get; set; } = string.Empty;
|
public string UsernameOverride { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace TrafagSalesExporter.Models;
|
||||||
|
|
||||||
|
public class SourceSystemDefinition
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string Code { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string DisplayName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public string ConnectionKind { get; set; } = SourceSystemConnectionKinds.Hana;
|
||||||
|
|
||||||
|
public bool IsActive { get; set; } = true;
|
||||||
|
|
||||||
|
public string CentralServiceUrl { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string CentralUsername { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public string CentralPassword { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SourceSystemConnectionKinds
|
||||||
|
{
|
||||||
|
public const string Hana = "HANA";
|
||||||
|
public const string SapGateway = "SAP_GATEWAY";
|
||||||
|
public const string ManualExcel = "MANUAL_EXCEL";
|
||||||
|
|
||||||
|
public static readonly string[] All = [Hana, SapGateway, ManualExcel];
|
||||||
|
}
|
||||||
@@ -27,6 +27,147 @@ Was fuer Waehrungen trotzdem noch offen bleibt:
|
|||||||
- bestaetigen, fuer welche Sichten CHF die Zielwaehrung sein soll
|
- bestaetigen, fuer welche Sichten CHF die Zielwaehrung sein soll
|
||||||
- Management-Cockpit-Rohsicht nur dann auf CHF umstellen, wenn fachlich gewuenscht
|
- Management-Cockpit-Rohsicht nur dann auf CHF umstellen, wenn fachlich gewuenscht
|
||||||
|
|
||||||
|
## Architektur-Nachtrag 2026-04-17
|
||||||
|
|
||||||
|
Nach einer separaten Architekturpruefung wurden die naechsten Schritte neu priorisiert.
|
||||||
|
|
||||||
|
Wichtig:
|
||||||
|
|
||||||
|
- neue Fachfeatures sind aktuell **nicht** der erste Engpass
|
||||||
|
- zuerst muessen die Architektur-Risiken in Initialisierung, Config-Import und UI-Service-Schnitt bereinigt werden
|
||||||
|
|
||||||
|
### Neue Top-Prioritaeten
|
||||||
|
|
||||||
|
#### 1. `DatabaseInitializationService` absichern
|
||||||
|
|
||||||
|
Prio sehr hoch.
|
||||||
|
|
||||||
|
Gruende:
|
||||||
|
|
||||||
|
- Startlogik enthaelt manuelle Schema-Migrationen
|
||||||
|
- FK-Reparaturen laufen produktiv beim App-Start
|
||||||
|
- dort wurde ein konkretes Risiko fuer verschobene Spaltenwerte beim `Sites_old`-Kopierpfad erkannt
|
||||||
|
|
||||||
|
Vor weiterer Fachentwicklung:
|
||||||
|
|
||||||
|
- Initialisierungspfad genau pruefen
|
||||||
|
- SQL-Kopierlogik validieren
|
||||||
|
- moeglichst Richtung versionierte Migrationen bewegen
|
||||||
|
|
||||||
|
#### 2. `ConfigTransferService.ImportJsonAsync` neu denken
|
||||||
|
|
||||||
|
Prio sehr hoch.
|
||||||
|
|
||||||
|
Aktuelles Problem:
|
||||||
|
|
||||||
|
- Import loescht sehr viel und baut danach stueckweise neu auf
|
||||||
|
- nicht atomar
|
||||||
|
- potenziell teilzerstoerter Zustand bei Fehlern
|
||||||
|
- `CentralSalesRecords` werden mitimportiert/mitgeloescht, obwohl sie eher Laufzeitdaten als Konfiguration sind
|
||||||
|
|
||||||
|
Ziel:
|
||||||
|
|
||||||
|
- atomarer Import
|
||||||
|
- saubere Trennung zwischen Konfiguration und Betriebsdaten
|
||||||
|
|
||||||
|
#### 3. Razor-Seiten entlasten
|
||||||
|
|
||||||
|
Prio hoch.
|
||||||
|
|
||||||
|
Betroffen vor allem:
|
||||||
|
|
||||||
|
- `Components/Pages/Settings.razor`
|
||||||
|
- `Components/Pages/Standorte.razor`
|
||||||
|
|
||||||
|
Ziel:
|
||||||
|
|
||||||
|
- DB- und Fachlogik aus UI-Code in Services / Application-Layer verschieben
|
||||||
|
- Seiten nur noch fuer Interaktion und Formularzustand
|
||||||
|
|
||||||
|
#### 4. Konsolidierten Export semantisch klaeren
|
||||||
|
|
||||||
|
Prio mittel.
|
||||||
|
|
||||||
|
Offene Frage:
|
||||||
|
|
||||||
|
- zentrale Datei aus laufendem Snapshot
|
||||||
|
oder
|
||||||
|
- zentrale Datei immer aus `CentralSalesRecords`
|
||||||
|
|
||||||
|
Aktuell ist die Verantwortung unscharf.
|
||||||
|
|
||||||
|
#### 5. Reporting verallgemeinern
|
||||||
|
|
||||||
|
Prio mittel.
|
||||||
|
|
||||||
|
Erst nach den Infrastrukturthemen:
|
||||||
|
|
||||||
|
- hartcodierte Jahreslogik im Cockpit entfernen
|
||||||
|
- fachlich entscheiden, ob und wo CHF-Rohsicht gebraucht wird
|
||||||
|
|
||||||
|
### Praktische Reihenfolge fuer den naechsten Wiedereinstieg
|
||||||
|
|
||||||
|
Wenn nach erneutem Absturz oder Kontextverlust weitergemacht wird:
|
||||||
|
|
||||||
|
1. `HANDOFF_2026-04-15.md` lesen, speziell die Architekturpruefung vom 2026-04-17
|
||||||
|
2. `DatabaseInitializationService` als ersten Risikoblock ansehen
|
||||||
|
3. `ConfigTransferService.ImportJsonAsync` als zweiten Risikoblock ansehen
|
||||||
|
4. erst danach wieder an Cockpit / CHF / weitere Fachfeatures gehen
|
||||||
|
|
||||||
|
## Nachtrag HANA-/Standort-Workflow 2026-04-17
|
||||||
|
|
||||||
|
Der doppelte HANA-Workflow wurde inzwischen bereits bereinigt.
|
||||||
|
|
||||||
|
Neuer Stand:
|
||||||
|
|
||||||
|
- oben zentrale HANA-Konfiguration pro Quellsystem `BI1` / `SAGE`
|
||||||
|
- unten im Standort keine eigene wirksame Voll-HANA-Konfiguration mehr
|
||||||
|
- HANA-basierte Standorte ziehen ihre technische Verbindung aus der zentralen Quellsystem-Konfiguration
|
||||||
|
- Standort bleibt fuer fachliche Daten und optionale Credential-Overrides zustaendig
|
||||||
|
- die frueher doppelte HANA-UI im Standortdialog ist inzwischen auch sichtbar entfernt
|
||||||
|
- der Verbindungstest in `Settings.razor` prueft und meldet jetzt die zentrale HANA-Verbindung klar
|
||||||
|
|
||||||
|
### Was dazu noch praktisch geprueft werden sollte
|
||||||
|
|
||||||
|
- `Standorte`-Seite im UI manuell durchklicken
|
||||||
|
- pruefen, ob `BI1`- und `SAGE`-Standort beim Speichern sauber auf die zentrale HANA-Konfiguration zeigen
|
||||||
|
- pruefen, ob Aenderung oben bei zentraler HANA-Konfiguration in nachfolgenden Exporten wirklich greift
|
||||||
|
|
||||||
|
### Anschlussarbeiten
|
||||||
|
|
||||||
|
- `ConfigTransferService` spaeter auf das neue zentrale HANA-Modell fachlich nachziehen und kritisch pruefen
|
||||||
|
- `DatabaseInitializationService` weiter konsolidieren, damit die Zuordnung alter HANA-Daten langfristig robuster wird
|
||||||
|
|
||||||
|
## Nachtrag Quellsystem-Verwaltung 2026-04-17
|
||||||
|
|
||||||
|
Die bisher hart codierten Quellsystem-Listen wurden ersetzt.
|
||||||
|
|
||||||
|
Neuer Stand:
|
||||||
|
|
||||||
|
- `SourceSystemDefinition` ist jetzt die zentrale Stammdatenquelle fuer Quellsysteme
|
||||||
|
- `Settings.razor` hat jetzt eine GUI zur Pflege von Quellsystemen
|
||||||
|
- `Standorte.razor` zieht seine Quellsystem-Auswahl aus diesen Stammdaten
|
||||||
|
- `Transformations.razor` zieht die Systemauswahl ebenfalls aus diesen Stammdaten
|
||||||
|
- zentrale Credentials haengen jetzt am Quellsystem selbst
|
||||||
|
- HANA-Zentralverbindungen werden nur noch fuer Quellsysteme mit Anschlussart `HANA` gezeigt
|
||||||
|
- alte zentrale Credential-Felder in `ExportSettings` sind aus dem aktiven Codepfad entfernt
|
||||||
|
- `ExportSettings` wird beim Start auch schematisch auf das neue Feldset bereinigt
|
||||||
|
- HANA speichert zentral keine eigenen Credentials mehr; dort bleiben nur technische Verbindungsdaten
|
||||||
|
- `HanaServer.Username` / `Password` sind nur noch Laufzeitfelder und nicht mehr im EF-Schema gemappt
|
||||||
|
- SAP Service URL wird jetzt zentral im Quellsystem gepflegt; der Standort haelt nur noch ein optionales Override
|
||||||
|
- Quellsysteme werden jetzt per Dialog bearbeitet statt nur ueber Inline-Tabellenfelder
|
||||||
|
|
||||||
|
### Was dazu noch praktisch geprueft werden sollte
|
||||||
|
|
||||||
|
- in `Settings` ein neues Quellsystem per GUI anlegen
|
||||||
|
- pruefen, ob es danach in `Standorte` und `Transformations` sofort auswählbar ist
|
||||||
|
- pruefen, ob deaktivierte Quellsysteme in neuen Standort-/Regelanlagen nicht mehr normal angeboten werden
|
||||||
|
- pruefen, ob Aenderung der Anschlussart von `HANA` auf `SAP_GATEWAY` oder `MANUAL_EXCEL` fachlich sauber wirkt
|
||||||
|
- pruefen, ob bestehende BI1/SAGE/SAP-Daten nach Startmigration korrekt in `SourceSystemDefinitions` stehen
|
||||||
|
- pruefen, ob Konfiguration-Export/Import ohne die alten Credential-Felder sauber mit `SourceSystemDefinitions` arbeitet
|
||||||
|
- pruefen, ob zentrale SAP Service URL ohne Override sauber fuer Refresh, Export und Dashboard greift
|
||||||
|
- pruefen, ob SAP Service URL Override am Standort die zentrale URL erwartungsgemaess uebersteuert
|
||||||
|
|
||||||
## Nachtrag 2026-04-16
|
## Nachtrag 2026-04-16
|
||||||
|
|
||||||
Seit dem letzten Stand kamen mehrere groessere Erweiterungen dazu. Die offenen Punkte unten muessen deshalb im neuen Kontext gelesen werden.
|
Seit dem letzten Stand kamen mehrere groessere Erweiterungen dazu. Die offenen Punkte unten muessen deshalb im neuen Kontext gelesen werden.
|
||||||
@@ -183,3 +324,37 @@ Aktueller Teststatus:
|
|||||||
Fuer den vollstaendigen Kontext zuerst lesen:
|
Fuer den vollstaendigen Kontext zuerst lesen:
|
||||||
|
|
||||||
- `HANDOFF_2026-04-15.md`
|
- `HANDOFF_2026-04-15.md`
|
||||||
|
|
||||||
|
## 8. Letzte bereinigte UI-Irritation
|
||||||
|
|
||||||
|
Stand 2026-04-17:
|
||||||
|
|
||||||
|
- In `Standorte` wurde die obere Box auf `Zentrale HANA-Technik` geklaert.
|
||||||
|
- Dort gibt es keinen `Server hinzufuegen`-Pfad mehr.
|
||||||
|
- Grund: zentrale HANA-Eintraege werden aus `Quellsystemen` mit Anschlussart `HANA` abgeleitet.
|
||||||
|
- `SAP` gehoert fachlich nicht in diese Box, sondern in `Settings -> Quellsysteme`.
|
||||||
|
|
||||||
|
Wichtig fuer den naechsten Wiedereinstieg:
|
||||||
|
|
||||||
|
- Wenn ein Benutzer fragt `wo ist SAP?`, ist die richtige Antwort: nicht in der HANA-Box, sondern in der zentralen Quellsystem-Verwaltung.
|
||||||
|
- Wenn ein HANA-System oben fehlt, zuerst `Settings -> Quellsysteme` pruefen und dort Anschlussart `HANA` setzen.
|
||||||
|
|
||||||
|
## 9. Config-Transfer erneut geprueft
|
||||||
|
|
||||||
|
Stand 2026-04-17:
|
||||||
|
|
||||||
|
- Der aktuelle Config-Import/-Export passt zum neuen Datenmodell.
|
||||||
|
- Zentral verwaltete Quellsysteme, SAP-Zentral-URL, HANA-Technik ohne HANA-Credentials und Standort-Overrides werden korrekt im Transferformat abgebildet.
|
||||||
|
- Die vorhandenen `ConfigTransferServiceTests` bestaetigen den aktuellen Rundlauf.
|
||||||
|
|
||||||
|
Fuer den naechsten Wiedereinstieg wichtig:
|
||||||
|
|
||||||
|
- Das aktuelle Format ist fuer heutige Exporte konsistent.
|
||||||
|
- `ImportJsonAsync` ist aber weiterhin nicht atomar und loescht zuerst produktive Konfiguration.
|
||||||
|
- Zusaetzlich gibt es ein Altformat-Risiko:
|
||||||
|
- aeltere JSONs mit `SourceSystemDefinitions`, aber ohne `ConnectionKind`, koennen wegen DTO-Default falsch als `HANA` interpretiert werden.
|
||||||
|
|
||||||
|
Naechste saubere Haertung fuer dieses Thema:
|
||||||
|
|
||||||
|
- Config-Import transaktional machen
|
||||||
|
- Legacy-Fallback fuer fehlendes `ConnectionKind` einbauen
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ public class ConfigTransferService : IConfigTransferService
|
|||||||
using var db = await _dbFactory.CreateDbContextAsync();
|
using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
var sharePoint = await db.SharePointConfigs.FirstOrDefaultAsync();
|
var sharePoint = await db.SharePointConfigs.FirstOrDefaultAsync();
|
||||||
var exportSettings = await db.ExportSettings.FirstOrDefaultAsync();
|
var exportSettings = await db.ExportSettings.FirstOrDefaultAsync();
|
||||||
|
var sourceSystems = await db.SourceSystemDefinitions.OrderBy(x => x.Code).ToListAsync();
|
||||||
var exchangeRates = await db.CurrencyExchangeRates
|
var exchangeRates = await db.CurrencyExchangeRates
|
||||||
.OrderBy(x => x.FromCurrency)
|
.OrderBy(x => x.FromCurrency)
|
||||||
.ThenBy(x => x.ToCurrency)
|
.ThenBy(x => x.ToCurrency)
|
||||||
@@ -55,14 +56,18 @@ public class ConfigTransferService : IConfigTransferService
|
|||||||
TimerEnabled = exportSettings.TimerEnabled,
|
TimerEnabled = exportSettings.TimerEnabled,
|
||||||
DebugLoggingEnabled = exportSettings.DebugLoggingEnabled,
|
DebugLoggingEnabled = exportSettings.DebugLoggingEnabled,
|
||||||
LocalSiteExportFolder = exportSettings.LocalSiteExportFolder,
|
LocalSiteExportFolder = exportSettings.LocalSiteExportFolder,
|
||||||
LocalConsolidatedExportFolder = exportSettings.LocalConsolidatedExportFolder,
|
LocalConsolidatedExportFolder = exportSettings.LocalConsolidatedExportFolder
|
||||||
SapUsername = includeSecrets ? exportSettings.SapUsername : null,
|
|
||||||
SapPassword = includeSecrets ? exportSettings.SapPassword : null,
|
|
||||||
Bi1Username = includeSecrets ? exportSettings.Bi1Username : null,
|
|
||||||
Bi1Password = includeSecrets ? exportSettings.Bi1Password : null,
|
|
||||||
SageUsername = includeSecrets ? exportSettings.SageUsername : null,
|
|
||||||
SagePassword = includeSecrets ? exportSettings.SagePassword : null
|
|
||||||
},
|
},
|
||||||
|
SourceSystemDefinitions = sourceSystems.Select(system => new ConfigTransferSourceSystemDefinition
|
||||||
|
{
|
||||||
|
Code = system.Code,
|
||||||
|
DisplayName = system.DisplayName,
|
||||||
|
ConnectionKind = system.ConnectionKind,
|
||||||
|
IsActive = system.IsActive,
|
||||||
|
CentralServiceUrl = system.CentralServiceUrl,
|
||||||
|
CentralUsername = includeSecrets ? system.CentralUsername : null,
|
||||||
|
CentralPassword = includeSecrets ? system.CentralPassword : null
|
||||||
|
}).ToList(),
|
||||||
CurrencyExchangeRates = exchangeRates.Select(rate => new ConfigTransferCurrencyExchangeRate
|
CurrencyExchangeRates = exchangeRates.Select(rate => new ConfigTransferCurrencyExchangeRate
|
||||||
{
|
{
|
||||||
FromCurrency = rate.FromCurrency,
|
FromCurrency = rate.FromCurrency,
|
||||||
@@ -76,11 +81,10 @@ public class ConfigTransferService : IConfigTransferService
|
|||||||
HanaServers = hanaServers.Select(server => new ConfigTransferHanaServer
|
HanaServers = hanaServers.Select(server => new ConfigTransferHanaServer
|
||||||
{
|
{
|
||||||
Key = serverKeyMap[server.Id],
|
Key = serverKeyMap[server.Id],
|
||||||
|
SourceSystem = server.SourceSystem,
|
||||||
Name = server.Name,
|
Name = server.Name,
|
||||||
Host = server.Host,
|
Host = server.Host,
|
||||||
Port = server.Port,
|
Port = server.Port,
|
||||||
Username = includeSecrets ? server.Username : null,
|
|
||||||
Password = includeSecrets ? server.Password : null,
|
|
||||||
DatabaseName = server.DatabaseName,
|
DatabaseName = server.DatabaseName,
|
||||||
UseSsl = server.UseSsl,
|
UseSsl = server.UseSsl,
|
||||||
ValidateCertificate = server.ValidateCertificate,
|
ValidateCertificate = server.ValidateCertificate,
|
||||||
@@ -158,6 +162,7 @@ public class ConfigTransferService : IConfigTransferService
|
|||||||
using var db = await _dbFactory.CreateDbContextAsync();
|
using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
var existingSharePoint = await db.SharePointConfigs.FirstOrDefaultAsync();
|
var existingSharePoint = await db.SharePointConfigs.FirstOrDefaultAsync();
|
||||||
var existingSettings = await db.ExportSettings.FirstOrDefaultAsync();
|
var existingSettings = await db.ExportSettings.FirstOrDefaultAsync();
|
||||||
|
var existingSourceSystems = await db.SourceSystemDefinitions.ToListAsync();
|
||||||
var existingServers = await db.HanaServers.ToListAsync();
|
var existingServers = await db.HanaServers.ToListAsync();
|
||||||
var existingExchangeRates = await db.CurrencyExchangeRates.ToListAsync();
|
var existingExchangeRates = await db.CurrencyExchangeRates.ToListAsync();
|
||||||
var existingSites = await db.Sites.ToListAsync();
|
var existingSites = await db.Sites.ToListAsync();
|
||||||
@@ -168,20 +173,10 @@ public class ConfigTransferService : IConfigTransferService
|
|||||||
var existingCentralRecords = await db.CentralSalesRecords.ToListAsync();
|
var existingCentralRecords = await db.CentralSalesRecords.ToListAsync();
|
||||||
|
|
||||||
var preservedSharePointSecret = existingSharePoint?.ClientSecret ?? string.Empty;
|
var preservedSharePointSecret = existingSharePoint?.ClientSecret ?? string.Empty;
|
||||||
var preservedSecrets = existingSettings is null
|
var preservedSourceSystemSecrets = existingSourceSystems.ToDictionary(
|
||||||
? new ConfigTransferExportSettings()
|
x => x.Code,
|
||||||
: new ConfigTransferExportSettings
|
x => (CentralUsername: x.CentralUsername, CentralPassword: x.CentralPassword),
|
||||||
{
|
StringComparer.OrdinalIgnoreCase);
|
||||||
SapUsername = existingSettings.SapUsername,
|
|
||||||
SapPassword = existingSettings.SapPassword,
|
|
||||||
Bi1Username = existingSettings.Bi1Username,
|
|
||||||
Bi1Password = existingSettings.Bi1Password,
|
|
||||||
SageUsername = existingSettings.SageUsername,
|
|
||||||
SagePassword = existingSettings.SagePassword
|
|
||||||
};
|
|
||||||
var preservedServerSecrets = existingServers.ToDictionary(
|
|
||||||
x => BuildServerSignature(x.Name, x.Host, x.Port, x.DatabaseName),
|
|
||||||
x => (x.Username, x.Password));
|
|
||||||
var preservedSiteSecrets = existingSites.ToDictionary(
|
var preservedSiteSecrets = existingSites.ToDictionary(
|
||||||
x => BuildSiteSignature(x.Land, x.TSC, x.Schema, x.SourceSystem),
|
x => BuildSiteSignature(x.Land, x.TSC, x.Schema, x.SourceSystem),
|
||||||
x => (x.UsernameOverride, x.PasswordOverride));
|
x => (x.UsernameOverride, x.PasswordOverride));
|
||||||
@@ -194,6 +189,7 @@ public class ConfigTransferService : IConfigTransferService
|
|||||||
if (existingCentralRecords.Count > 0) db.CentralSalesRecords.RemoveRange(existingCentralRecords);
|
if (existingCentralRecords.Count > 0) db.CentralSalesRecords.RemoveRange(existingCentralRecords);
|
||||||
if (existingSites.Count > 0) db.Sites.RemoveRange(existingSites);
|
if (existingSites.Count > 0) db.Sites.RemoveRange(existingSites);
|
||||||
if (existingServers.Count > 0) db.HanaServers.RemoveRange(existingServers);
|
if (existingServers.Count > 0) db.HanaServers.RemoveRange(existingServers);
|
||||||
|
if (existingSourceSystems.Count > 0) db.SourceSystemDefinitions.RemoveRange(existingSourceSystems);
|
||||||
if (existingSharePoint is not null) db.SharePointConfigs.Remove(existingSharePoint);
|
if (existingSharePoint is not null) db.SharePointConfigs.Remove(existingSharePoint);
|
||||||
if (existingSettings is not null) db.ExportSettings.Remove(existingSettings);
|
if (existingSettings is not null) db.ExportSettings.Remove(existingSettings);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
@@ -218,15 +214,28 @@ public class ConfigTransferService : IConfigTransferService
|
|||||||
TimerEnabled = importedSettings.TimerEnabled,
|
TimerEnabled = importedSettings.TimerEnabled,
|
||||||
DebugLoggingEnabled = importedSettings.DebugLoggingEnabled,
|
DebugLoggingEnabled = importedSettings.DebugLoggingEnabled,
|
||||||
LocalSiteExportFolder = importedSettings.LocalSiteExportFolder,
|
LocalSiteExportFolder = importedSettings.LocalSiteExportFolder,
|
||||||
LocalConsolidatedExportFolder = importedSettings.LocalConsolidatedExportFolder,
|
LocalConsolidatedExportFolder = importedSettings.LocalConsolidatedExportFolder
|
||||||
SapUsername = package.IncludesSecrets ? importedSettings.SapUsername ?? string.Empty : preservedSecrets.SapUsername ?? string.Empty,
|
|
||||||
SapPassword = package.IncludesSecrets ? importedSettings.SapPassword ?? string.Empty : preservedSecrets.SapPassword ?? string.Empty,
|
|
||||||
Bi1Username = package.IncludesSecrets ? importedSettings.Bi1Username ?? string.Empty : preservedSecrets.Bi1Username ?? string.Empty,
|
|
||||||
Bi1Password = package.IncludesSecrets ? importedSettings.Bi1Password ?? string.Empty : preservedSecrets.Bi1Password ?? string.Empty,
|
|
||||||
SageUsername = package.IncludesSecrets ? importedSettings.SageUsername ?? string.Empty : preservedSecrets.SageUsername ?? string.Empty,
|
|
||||||
SagePassword = package.IncludesSecrets ? importedSettings.SagePassword ?? string.Empty : preservedSecrets.SagePassword ?? string.Empty
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var importedSourceSystems = package.SourceSystemDefinitions.Count > 0
|
||||||
|
? package.SourceSystemDefinitions
|
||||||
|
: BuildDefaultSourceSystems();
|
||||||
|
|
||||||
|
foreach (var sourceSystem in importedSourceSystems)
|
||||||
|
{
|
||||||
|
preservedSourceSystemSecrets.TryGetValue(sourceSystem.Code, out var preserved);
|
||||||
|
db.SourceSystemDefinitions.Add(new SourceSystemDefinition
|
||||||
|
{
|
||||||
|
Code = sourceSystem.Code,
|
||||||
|
DisplayName = sourceSystem.DisplayName,
|
||||||
|
ConnectionKind = sourceSystem.ConnectionKind,
|
||||||
|
IsActive = sourceSystem.IsActive,
|
||||||
|
CentralServiceUrl = sourceSystem.CentralServiceUrl,
|
||||||
|
CentralUsername = package.IncludesSecrets ? sourceSystem.CentralUsername ?? string.Empty : preserved.CentralUsername ?? string.Empty,
|
||||||
|
CentralPassword = package.IncludesSecrets ? sourceSystem.CentralPassword ?? string.Empty : preserved.CentralPassword ?? string.Empty
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (package.CurrencyExchangeRates.Count > 0)
|
if (package.CurrencyExchangeRates.Count > 0)
|
||||||
{
|
{
|
||||||
db.CurrencyExchangeRates.AddRange(package.CurrencyExchangeRates.Select(rate => new CurrencyExchangeRate
|
db.CurrencyExchangeRates.AddRange(package.CurrencyExchangeRates.Select(rate => new CurrencyExchangeRate
|
||||||
@@ -244,14 +253,14 @@ public class ConfigTransferService : IConfigTransferService
|
|||||||
var serverIdMap = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
var serverIdMap = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||||
foreach (var server in package.HanaServers)
|
foreach (var server in package.HanaServers)
|
||||||
{
|
{
|
||||||
preservedServerSecrets.TryGetValue(BuildServerSignature(server.Name, server.Host, server.Port, server.DatabaseName), out var preserved);
|
|
||||||
var entity = new HanaServer
|
var entity = new HanaServer
|
||||||
{
|
{
|
||||||
|
SourceSystem = server.SourceSystem,
|
||||||
Name = server.Name,
|
Name = server.Name,
|
||||||
Host = server.Host,
|
Host = server.Host,
|
||||||
Port = server.Port,
|
Port = server.Port,
|
||||||
Username = package.IncludesSecrets ? server.Username ?? string.Empty : preserved.Username ?? string.Empty,
|
Username = string.Empty,
|
||||||
Password = package.IncludesSecrets ? server.Password ?? string.Empty : preserved.Password ?? string.Empty,
|
Password = string.Empty,
|
||||||
DatabaseName = server.DatabaseName,
|
DatabaseName = server.DatabaseName,
|
||||||
UseSsl = server.UseSsl,
|
UseSsl = server.UseSsl,
|
||||||
ValidateCertificate = server.ValidateCertificate,
|
ValidateCertificate = server.ValidateCertificate,
|
||||||
@@ -355,10 +364,42 @@ public class ConfigTransferService : IConfigTransferService
|
|||||||
|
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string BuildServerSignature(string name, string host, int port, string databaseName)
|
|
||||||
=> $"{name}|{host}|{port}|{databaseName}".ToUpperInvariant();
|
|
||||||
|
|
||||||
private static string BuildSiteSignature(string land, string tsc, string schema, string sourceSystem)
|
private static string BuildSiteSignature(string land, string tsc, string schema, string sourceSystem)
|
||||||
=> $"{land}|{tsc}|{schema}|{sourceSystem}".ToUpperInvariant();
|
=> $"{land}|{tsc}|{schema}|{sourceSystem}".ToUpperInvariant();
|
||||||
|
|
||||||
|
private static List<ConfigTransferSourceSystemDefinition> BuildDefaultSourceSystems()
|
||||||
|
{
|
||||||
|
return
|
||||||
|
[
|
||||||
|
new ConfigTransferSourceSystemDefinition
|
||||||
|
{
|
||||||
|
Code = "SAP",
|
||||||
|
DisplayName = "SAP",
|
||||||
|
ConnectionKind = SourceSystemConnectionKinds.SapGateway,
|
||||||
|
IsActive = true,
|
||||||
|
CentralServiceUrl = string.Empty
|
||||||
|
},
|
||||||
|
new ConfigTransferSourceSystemDefinition
|
||||||
|
{
|
||||||
|
Code = "BI1",
|
||||||
|
DisplayName = "BI1",
|
||||||
|
ConnectionKind = SourceSystemConnectionKinds.Hana,
|
||||||
|
IsActive = true
|
||||||
|
},
|
||||||
|
new ConfigTransferSourceSystemDefinition
|
||||||
|
{
|
||||||
|
Code = "SAGE",
|
||||||
|
DisplayName = "SAGE",
|
||||||
|
ConnectionKind = SourceSystemConnectionKinds.Hana,
|
||||||
|
IsActive = true
|
||||||
|
},
|
||||||
|
new ConfigTransferSourceSystemDefinition
|
||||||
|
{
|
||||||
|
Code = "MANUAL_EXCEL",
|
||||||
|
DisplayName = "Manual Excel",
|
||||||
|
ConnectionKind = SourceSystemConnectionKinds.ManualExcel,
|
||||||
|
IsActive = true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,10 @@ public class DatabaseInitializationService : IDatabaseInitializationService
|
|||||||
private static void EnsureSchema(AppDbContext db)
|
private static void EnsureSchema(AppDbContext db)
|
||||||
{
|
{
|
||||||
EnsureSitesTableSupportsOptionalHanaServer(db);
|
EnsureSitesTableSupportsOptionalHanaServer(db);
|
||||||
|
EnsureExportSettingsTableSupportsCurrentSchema(db);
|
||||||
|
EnsureHanaServersTableSupportsCurrentSchema(db);
|
||||||
RepairBrokenSiteForeignKeys(db);
|
RepairBrokenSiteForeignKeys(db);
|
||||||
|
AddColumnIfMissing(db, "HanaServers", "SourceSystem", "TEXT NOT NULL DEFAULT ''");
|
||||||
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");
|
||||||
@@ -61,12 +64,6 @@ public class DatabaseInitializationService : IDatabaseInitializationService
|
|||||||
AddColumnIfMissing(db, "Sites", "SapEntitySet", "TEXT NOT NULL DEFAULT ''");
|
AddColumnIfMissing(db, "Sites", "SapEntitySet", "TEXT NOT NULL DEFAULT ''");
|
||||||
AddColumnIfMissing(db, "Sites", "SapEntitySetsCache", "TEXT NOT NULL DEFAULT ''");
|
AddColumnIfMissing(db, "Sites", "SapEntitySetsCache", "TEXT NOT NULL DEFAULT ''");
|
||||||
AddColumnIfMissing(db, "Sites", "SapEntitySetsRefreshedAtUtc", "TEXT NULL");
|
AddColumnIfMissing(db, "Sites", "SapEntitySetsRefreshedAtUtc", "TEXT NULL");
|
||||||
AddColumnIfMissing(db, "ExportSettings", "SapUsername", "TEXT NOT NULL DEFAULT ''");
|
|
||||||
AddColumnIfMissing(db, "ExportSettings", "SapPassword", "TEXT NOT NULL DEFAULT ''");
|
|
||||||
AddColumnIfMissing(db, "ExportSettings", "Bi1Username", "TEXT NOT NULL DEFAULT ''");
|
|
||||||
AddColumnIfMissing(db, "ExportSettings", "Bi1Password", "TEXT NOT NULL DEFAULT ''");
|
|
||||||
AddColumnIfMissing(db, "ExportSettings", "SageUsername", "TEXT NOT NULL DEFAULT ''");
|
|
||||||
AddColumnIfMissing(db, "ExportSettings", "SagePassword", "TEXT NOT NULL DEFAULT ''");
|
|
||||||
AddColumnIfMissing(db, "ExportSettings", "DebugLoggingEnabled", "INTEGER NOT NULL DEFAULT 0");
|
AddColumnIfMissing(db, "ExportSettings", "DebugLoggingEnabled", "INTEGER NOT NULL DEFAULT 0");
|
||||||
AddColumnIfMissing(db, "ExportSettings", "LocalSiteExportFolder", "TEXT NOT NULL DEFAULT ''");
|
AddColumnIfMissing(db, "ExportSettings", "LocalSiteExportFolder", "TEXT NOT NULL DEFAULT ''");
|
||||||
AddColumnIfMissing(db, "ExportSettings", "LocalConsolidatedExportFolder", "TEXT NOT NULL DEFAULT ''");
|
AddColumnIfMissing(db, "ExportSettings", "LocalConsolidatedExportFolder", "TEXT NOT NULL DEFAULT ''");
|
||||||
@@ -75,11 +72,57 @@ public class DatabaseInitializationService : IDatabaseInitializationService
|
|||||||
EnsureTransformationTable(db);
|
EnsureTransformationTable(db);
|
||||||
AddColumnIfMissing(db, "FieldTransformationRules", "RuleScope", "TEXT NOT NULL DEFAULT 'Value'");
|
AddColumnIfMissing(db, "FieldTransformationRules", "RuleScope", "TEXT NOT NULL DEFAULT 'Value'");
|
||||||
EnsureCurrencyExchangeRateTable(db);
|
EnsureCurrencyExchangeRateTable(db);
|
||||||
|
EnsureSourceSystemDefinitionTable(db);
|
||||||
|
AddColumnIfMissing(db, "SourceSystemDefinitions", "CentralServiceUrl", "TEXT NOT NULL DEFAULT ''");
|
||||||
EnsureSapSourceTable(db);
|
EnsureSapSourceTable(db);
|
||||||
EnsureSapJoinTable(db);
|
EnsureSapJoinTable(db);
|
||||||
EnsureSapFieldMappingTable(db);
|
EnsureSapFieldMappingTable(db);
|
||||||
EnsureCentralSalesRecordTable(db);
|
EnsureCentralSalesRecordTable(db);
|
||||||
EnsureAppEventLogTable(db);
|
EnsureAppEventLogTable(db);
|
||||||
|
EnsureSourceSystemDefinitions(db);
|
||||||
|
EnsureCentralHanaServerRecords(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureExportSettingsTableSupportsCurrentSchema(AppDbContext db)
|
||||||
|
{
|
||||||
|
var conn = db.Database.GetDbConnection();
|
||||||
|
if (conn.State != ConnectionState.Open)
|
||||||
|
conn.Open();
|
||||||
|
|
||||||
|
var columns = GetTableColumns(conn, transaction: null, "ExportSettings");
|
||||||
|
if (columns.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var legacyColumns = new[]
|
||||||
|
{
|
||||||
|
"SapUsername",
|
||||||
|
"SapPassword",
|
||||||
|
"Bi1Username",
|
||||||
|
"Bi1Password",
|
||||||
|
"SageUsername",
|
||||||
|
"SagePassword"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!legacyColumns.Any(columns.Contains))
|
||||||
|
return;
|
||||||
|
|
||||||
|
RebuildTable(conn, "ExportSettings", GetExportSettingsCreateSql());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureHanaServersTableSupportsCurrentSchema(AppDbContext db)
|
||||||
|
{
|
||||||
|
var conn = db.Database.GetDbConnection();
|
||||||
|
if (conn.State != ConnectionState.Open)
|
||||||
|
conn.Open();
|
||||||
|
|
||||||
|
var columns = GetTableColumns(conn, transaction: null, "HanaServers");
|
||||||
|
if (columns.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!columns.Contains("Username") && !columns.Contains("Password"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
RebuildTable(conn, "HanaServers", GetHanaServersCreateSql());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EnsureSitesTableSupportsOptionalHanaServer(AppDbContext db)
|
private static void EnsureSitesTableSupportsOptionalHanaServer(AppDbContext db)
|
||||||
@@ -272,7 +315,7 @@ FROM Sites_old;";
|
|||||||
enableFk.ExecuteNonQuery();
|
enableFk.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<string> GetSharedColumns(System.Data.Common.DbConnection connection, System.Data.Common.DbTransaction transaction, string newTableName, string oldTableName)
|
private static List<string> GetSharedColumns(System.Data.Common.DbConnection connection, System.Data.Common.DbTransaction? transaction, string newTableName, string oldTableName)
|
||||||
{
|
{
|
||||||
var newColumns = GetTableColumns(connection, transaction, newTableName);
|
var newColumns = GetTableColumns(connection, transaction, newTableName);
|
||||||
var oldColumns = GetTableColumns(connection, transaction, oldTableName);
|
var oldColumns = GetTableColumns(connection, transaction, oldTableName);
|
||||||
@@ -280,7 +323,7 @@ FROM Sites_old;";
|
|||||||
return newColumns.Where(oldColumns.Contains).ToList();
|
return newColumns.Where(oldColumns.Contains).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HashSet<string> GetTableColumns(System.Data.Common.DbConnection connection, System.Data.Common.DbTransaction transaction, string tableName)
|
private static HashSet<string> GetTableColumns(System.Data.Common.DbConnection connection, System.Data.Common.DbTransaction? transaction, string tableName)
|
||||||
{
|
{
|
||||||
var columns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
var columns = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
@@ -315,6 +358,31 @@ CREATE TABLE ExportLogs (
|
|||||||
FOREIGN KEY (SiteId) REFERENCES Sites (Id)
|
FOREIGN KEY (SiteId) REFERENCES Sites (Id)
|
||||||
);";
|
);";
|
||||||
|
|
||||||
|
private static string GetExportSettingsCreateSql() => @"
|
||||||
|
CREATE TABLE ExportSettings (
|
||||||
|
Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
DateFilter TEXT NOT NULL,
|
||||||
|
TimerHour INTEGER NOT NULL,
|
||||||
|
TimerMinute INTEGER NOT NULL,
|
||||||
|
TimerEnabled INTEGER NOT NULL,
|
||||||
|
DebugLoggingEnabled INTEGER NOT NULL DEFAULT 0,
|
||||||
|
LocalSiteExportFolder TEXT NOT NULL DEFAULT '',
|
||||||
|
LocalConsolidatedExportFolder TEXT NOT NULL DEFAULT ''
|
||||||
|
);";
|
||||||
|
|
||||||
|
private static string GetHanaServersCreateSql() => @"
|
||||||
|
CREATE TABLE HanaServers (
|
||||||
|
Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
SourceSystem TEXT NOT NULL,
|
||||||
|
Name TEXT NOT NULL,
|
||||||
|
Host TEXT NOT NULL,
|
||||||
|
Port INTEGER NOT NULL,
|
||||||
|
DatabaseName TEXT NOT NULL DEFAULT '',
|
||||||
|
UseSsl INTEGER NOT NULL DEFAULT 0,
|
||||||
|
ValidateCertificate INTEGER NOT NULL DEFAULT 0,
|
||||||
|
AdditionalParams TEXT NOT NULL DEFAULT ''
|
||||||
|
);";
|
||||||
|
|
||||||
private static string GetAppEventLogsCreateSql() => @"
|
private static string GetAppEventLogsCreateSql() => @"
|
||||||
CREATE TABLE AppEventLogs (
|
CREATE TABLE AppEventLogs (
|
||||||
Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
@@ -604,21 +672,42 @@ CREATE TABLE IF NOT EXISTS AppEventLogs (
|
|||||||
cmd.ExecuteNonQuery();
|
cmd.ExecuteNonQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void EnsureSourceSystemDefinitionTable(AppDbContext db)
|
||||||
|
{
|
||||||
|
var conn = db.Database.GetDbConnection();
|
||||||
|
if (conn.State != ConnectionState.Open)
|
||||||
|
conn.Open();
|
||||||
|
|
||||||
|
using var cmd = conn.CreateCommand();
|
||||||
|
cmd.CommandText = @"
|
||||||
|
CREATE TABLE IF NOT EXISTS SourceSystemDefinitions (
|
||||||
|
Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
Code TEXT NOT NULL,
|
||||||
|
DisplayName TEXT NOT NULL,
|
||||||
|
ConnectionKind TEXT NOT NULL,
|
||||||
|
IsActive INTEGER NOT NULL DEFAULT 1,
|
||||||
|
CentralServiceUrl TEXT NOT NULL DEFAULT '',
|
||||||
|
CentralUsername TEXT NOT NULL DEFAULT '',
|
||||||
|
CentralPassword TEXT NOT NULL DEFAULT ''
|
||||||
|
);";
|
||||||
|
cmd.ExecuteNonQuery();
|
||||||
|
}
|
||||||
|
|
||||||
private static void SeedIfEmpty(AppDbContext db)
|
private static void SeedIfEmpty(AppDbContext db)
|
||||||
{
|
{
|
||||||
if (db.HanaServers.Any())
|
if (db.Sites.Any() || db.HanaServers.Any() || db.SharePointConfigs.Any() || db.ExportSettings.Any())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var serverInternal = new HanaServer { Name = "Internal", Host = "travtrp0", Port = 30015, Username = "", Password = "" };
|
var serverBi1 = new HanaServer { SourceSystem = "BI1", Name = "BI1", Host = "travtrp0", Port = 30015, Username = "", Password = "" };
|
||||||
var serverIndia = new HanaServer { Name = "India", Host = "20.197.20.60", Port = 30015, Username = "", Password = "" };
|
var serverSage = new HanaServer { SourceSystem = "SAGE", Name = "SAGE", Host = "20.197.20.60", Port = 30015, Username = "", Password = "" };
|
||||||
db.HanaServers.AddRange(serverInternal, serverIndia);
|
db.HanaServers.AddRange(serverBi1, serverSage);
|
||||||
db.SaveChanges();
|
db.SaveChanges();
|
||||||
|
|
||||||
db.Sites.AddRange(
|
db.Sites.AddRange(
|
||||||
new Site { HanaServerId = serverInternal.Id, Schema = "fr01_p", TSC = "TRFR", Land = "Frankreich", IsActive = true },
|
new Site { HanaServerId = serverBi1.Id, Schema = "fr01_p", TSC = "TRFR", Land = "Frankreich", SourceSystem = "BI1", IsActive = true },
|
||||||
new Site { HanaServerId = serverInternal.Id, Schema = "it01_p", TSC = "TRIT", Land = "Italien", IsActive = true },
|
new Site { HanaServerId = serverBi1.Id, Schema = "it01_p", TSC = "TRIT", Land = "Italien", SourceSystem = "BI1", IsActive = true },
|
||||||
new Site { HanaServerId = serverInternal.Id, Schema = "us01_p", TSC = "TRUS", Land = "USA", IsActive = true },
|
new Site { HanaServerId = serverBi1.Id, Schema = "us01_p", TSC = "TRUS", Land = "USA", SourceSystem = "BI1", IsActive = true },
|
||||||
new Site { HanaServerId = serverIndia.Id, Schema = "TRAFAG_LIVE", TSC = "TRIN", Land = "Indien", IsActive = true }
|
new Site { HanaServerId = serverSage.Id, Schema = "TRAFAG_LIVE", TSC = "TRIN", Land = "Indien", SourceSystem = "SAGE", IsActive = true }
|
||||||
);
|
);
|
||||||
|
|
||||||
db.SharePointConfigs.Add(new SharePointConfig
|
db.SharePointConfigs.Add(new SharePointConfig
|
||||||
@@ -695,4 +784,121 @@ CREATE TABLE IF NOT EXISTS AppEventLogs (
|
|||||||
if (hasChanges)
|
if (hasChanges)
|
||||||
db.SaveChanges();
|
db.SaveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void EnsureCentralHanaServerRecords(AppDbContext db)
|
||||||
|
{
|
||||||
|
var centralSystems = db.SourceSystemDefinitions
|
||||||
|
.AsNoTracking()
|
||||||
|
.Where(x => x.ConnectionKind == SourceSystemConnectionKinds.Hana)
|
||||||
|
.OrderBy(x => x.Code)
|
||||||
|
.Select(x => x.Code)
|
||||||
|
.ToList();
|
||||||
|
var changed = false;
|
||||||
|
|
||||||
|
foreach (var sourceSystem in centralSystems)
|
||||||
|
{
|
||||||
|
var existingCentral = db.HanaServers
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.FirstOrDefault(x => x.SourceSystem == sourceSystem);
|
||||||
|
|
||||||
|
if (existingCentral is not null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(existingCentral.Name))
|
||||||
|
{
|
||||||
|
existingCentral.Name = sourceSystem;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var linkedServer = db.Sites
|
||||||
|
.Include(x => x.HanaServer)
|
||||||
|
.Where(x => x.SourceSystem == sourceSystem && x.HanaServerId != null && x.HanaServer != null)
|
||||||
|
.Select(x => x.HanaServer!)
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (linkedServer is not null)
|
||||||
|
{
|
||||||
|
linkedServer.SourceSystem = sourceSystem;
|
||||||
|
if (string.IsNullOrWhiteSpace(linkedServer.Name))
|
||||||
|
linkedServer.Name = sourceSystem;
|
||||||
|
changed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
db.HanaServers.Add(new HanaServer
|
||||||
|
{
|
||||||
|
SourceSystem = sourceSystem,
|
||||||
|
Name = sourceSystem,
|
||||||
|
Host = string.Empty,
|
||||||
|
Port = 30015,
|
||||||
|
Username = string.Empty,
|
||||||
|
Password = string.Empty,
|
||||||
|
DatabaseName = string.Empty,
|
||||||
|
AdditionalParams = string.Empty
|
||||||
|
});
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
db.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureSourceSystemDefinitions(AppDbContext db)
|
||||||
|
{
|
||||||
|
var defaults = new[]
|
||||||
|
{
|
||||||
|
new SourceSystemDefinition { Code = "SAP", DisplayName = "SAP", ConnectionKind = SourceSystemConnectionKinds.SapGateway, IsActive = true },
|
||||||
|
new SourceSystemDefinition { Code = "BI1", DisplayName = "BI1", ConnectionKind = SourceSystemConnectionKinds.Hana, IsActive = true },
|
||||||
|
new SourceSystemDefinition { Code = "SAGE", DisplayName = "SAGE", ConnectionKind = SourceSystemConnectionKinds.Hana, IsActive = true },
|
||||||
|
new SourceSystemDefinition { Code = "MANUAL_EXCEL", DisplayName = "Manual Excel", ConnectionKind = SourceSystemConnectionKinds.ManualExcel, IsActive = true }
|
||||||
|
};
|
||||||
|
|
||||||
|
var existing = db.SourceSystemDefinitions.ToList();
|
||||||
|
var changed = false;
|
||||||
|
|
||||||
|
foreach (var item in defaults)
|
||||||
|
{
|
||||||
|
var current = existing.FirstOrDefault(x => x.Code == item.Code);
|
||||||
|
if (current is null)
|
||||||
|
{
|
||||||
|
db.SourceSystemDefinitions.Add(item);
|
||||||
|
existing.Add(item);
|
||||||
|
changed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(current.DisplayName))
|
||||||
|
{
|
||||||
|
current.DisplayName = item.DisplayName;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(current.ConnectionKind))
|
||||||
|
{
|
||||||
|
current.ConnectionKind = item.ConnectionKind;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(current.CentralServiceUrl) &&
|
||||||
|
string.Equals(current.ConnectionKind, SourceSystemConnectionKinds.SapGateway, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var sapSite = db.Sites
|
||||||
|
.Where(x => x.SourceSystem == current.Code && !string.IsNullOrWhiteSpace(x.SapServiceUrl))
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (sapSite is not null)
|
||||||
|
{
|
||||||
|
current.CentralServiceUrl = sapSite.SapServiceUrl;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
db.SaveChanges();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,13 +65,19 @@ public class SiteExportService : ISiteExportService
|
|||||||
var spConfig = await db.SharePointConfigs.FirstOrDefaultAsync();
|
var spConfig = await db.SharePointConfigs.FirstOrDefaultAsync();
|
||||||
var outputDir = ResolveSiteOutputDirectory(settings, site);
|
var outputDir = ResolveSiteOutputDirectory(settings, site);
|
||||||
var sourceSystem = NormalizeSourceSystem(site.SourceSystem);
|
var sourceSystem = NormalizeSourceSystem(site.SourceSystem);
|
||||||
|
var sourceDefinition = await db.SourceSystemDefinitions
|
||||||
|
.AsNoTracking()
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.FirstOrDefaultAsync(x => x.Code == sourceSystem)
|
||||||
|
?? throw new InvalidOperationException($"Quellsystem '{sourceSystem}' ist nicht konfiguriert.");
|
||||||
var records = new List<SalesRecord>();
|
var records = new List<SalesRecord>();
|
||||||
string filePath;
|
string filePath;
|
||||||
|
|
||||||
if (sourceSystem == "SAP")
|
if (string.Equals(sourceDefinition.ConnectionKind, SourceSystemConnectionKinds.SapGateway, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var credentials = ResolveCredentials(site, settings, sourceSystem);
|
var credentials = ResolveCredentials(site, sourceDefinition);
|
||||||
if (string.IsNullOrWhiteSpace(site.SapServiceUrl))
|
var sapServiceUrl = ResolveSapServiceUrl(site, sourceDefinition);
|
||||||
|
if (string.IsNullOrWhiteSpace(sapServiceUrl))
|
||||||
throw new InvalidOperationException($"Standort '{site.Land}' hat keine SAP Service URL.");
|
throw new InvalidOperationException($"Standort '{site.Land}' hat keine SAP Service URL.");
|
||||||
var sapSources = await db.SapSourceDefinitions.Where(s => s.SiteId == site.Id).ToListAsync();
|
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 sapJoins = await db.SapJoinDefinitions.Where(j => j.SiteId == site.Id).ToListAsync();
|
||||||
@@ -84,7 +90,8 @@ public class SiteExportService : ISiteExportService
|
|||||||
updateStatus?.Invoke("SAP Quellen laden...");
|
updateStatus?.Invoke("SAP Quellen laden...");
|
||||||
await _appEventLogService.WriteAsync("Export", "SAP Quellen laden", siteId: site.Id, land: site.Land,
|
await _appEventLogService.WriteAsync("Export", "SAP Quellen laden", siteId: site.Id, land: site.Land,
|
||||||
details: $"Sources={sapSources.Count} | Mappings={sapMappings.Count}");
|
details: $"Sources={sapSources.Count} | Mappings={sapMappings.Count}");
|
||||||
records = await _sapCompositionService.BuildSalesRecordsAsync(site, sapSources, sapJoins, sapMappings, credentials.Username, credentials.Password);
|
var effectiveSite = CloneSiteWithSapServiceUrl(site, sapServiceUrl);
|
||||||
|
records = await _sapCompositionService.BuildSalesRecordsAsync(effectiveSite, sapSources, sapJoins, sapMappings, credentials.Username, credentials.Password);
|
||||||
updateStatus?.Invoke("Transformationen anwenden...");
|
updateStatus?.Invoke("Transformationen anwenden...");
|
||||||
await _appEventLogService.WriteAsync("Export", "Transformationen anwenden", siteId: site.Id, land: site.Land,
|
await _appEventLogService.WriteAsync("Export", "Transformationen anwenden", siteId: site.Id, land: site.Land,
|
||||||
details: $"Records vor Transformation={records.Count}");
|
details: $"Records vor Transformation={records.Count}");
|
||||||
@@ -99,7 +106,7 @@ public class SiteExportService : ISiteExportService
|
|||||||
filePath = _excelService.CreateExcelFile(outputDir, site.TSC, DateTime.UtcNow.Date, records);
|
filePath = _excelService.CreateExcelFile(outputDir, site.TSC, DateTime.UtcNow.Date, records);
|
||||||
log.RowCount = records.Count;
|
log.RowCount = records.Count;
|
||||||
}
|
}
|
||||||
else if (sourceSystem == "MANUAL_EXCEL")
|
else if (string.Equals(sourceDefinition.ConnectionKind, SourceSystemConnectionKinds.ManualExcel, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(site.ManualImportFilePath))
|
if (string.IsNullOrWhiteSpace(site.ManualImportFilePath))
|
||||||
throw new InvalidOperationException($"Standort '{site.Land}' hat keine manuelle Excel-Datei.");
|
throw new InvalidOperationException($"Standort '{site.Land}' hat keine manuelle Excel-Datei.");
|
||||||
@@ -125,7 +132,7 @@ public class SiteExportService : ISiteExportService
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var exportServer = BuildEffectiveServer(site, settings, sourceSystem);
|
var exportServer = await BuildEffectiveServerAsync(db, site, sourceDefinition);
|
||||||
updateStatus?.Invoke("HANA Abfrage...");
|
updateStatus?.Invoke("HANA Abfrage...");
|
||||||
await _appEventLogService.WriteAsync("Export", "HANA Abfrage gestartet", siteId: site.Id, land: site.Land,
|
await _appEventLogService.WriteAsync("Export", "HANA Abfrage gestartet", siteId: site.Id, land: site.Land,
|
||||||
details: exportServer.GetConnectionStringPreview());
|
details: exportServer.GetConnectionStringPreview());
|
||||||
@@ -208,45 +215,40 @@ public class SiteExportService : ISiteExportService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static HanaServer BuildEffectiveServer(Site site, ExportSettings settings, string sourceSystem)
|
private static async Task<HanaServer> BuildEffectiveServerAsync(AppDbContext db, Site site, SourceSystemDefinition sourceDefinition)
|
||||||
{
|
{
|
||||||
if (site.HanaServer is null)
|
var centralServer = await db.HanaServers
|
||||||
throw new InvalidOperationException($"Standort '{site.Land}' hat keinen HANA-Server.");
|
.AsNoTracking()
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.FirstOrDefaultAsync(x => x.SourceSystem == sourceDefinition.Code);
|
||||||
|
|
||||||
var credentials = ResolveCredentials(site, settings, sourceSystem);
|
if (centralServer is null)
|
||||||
|
throw new InvalidOperationException($"Fuer Quellsystem '{sourceDefinition.Code}' ist keine zentrale HANA-Konfiguration vorhanden.");
|
||||||
|
|
||||||
|
var credentials = ResolveCredentials(site, sourceDefinition);
|
||||||
|
|
||||||
return new HanaServer
|
return new HanaServer
|
||||||
{
|
{
|
||||||
Id = site.HanaServer.Id,
|
Id = centralServer.Id,
|
||||||
Name = site.HanaServer.Name,
|
SourceSystem = centralServer.SourceSystem,
|
||||||
Host = site.HanaServer.Host,
|
Name = centralServer.Name,
|
||||||
Port = site.HanaServer.Port,
|
Host = centralServer.Host,
|
||||||
Username = FirstNonEmpty(credentials.Username, site.HanaServer.Username),
|
Port = centralServer.Port,
|
||||||
Password = FirstNonEmpty(credentials.Password, site.HanaServer.Password),
|
Username = credentials.Username,
|
||||||
DatabaseName = site.HanaServer.DatabaseName,
|
Password = credentials.Password,
|
||||||
UseSsl = site.HanaServer.UseSsl,
|
DatabaseName = centralServer.DatabaseName,
|
||||||
ValidateCertificate = site.HanaServer.ValidateCertificate,
|
UseSsl = centralServer.UseSsl,
|
||||||
AdditionalParams = site.HanaServer.AdditionalParams
|
ValidateCertificate = centralServer.ValidateCertificate,
|
||||||
|
AdditionalParams = centralServer.AdditionalParams
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static (string Username, string Password) ResolveCredentials(Site site, ExportSettings settings, string sourceSystem)
|
private static (string Username, string Password) ResolveCredentials(Site site, SourceSystemDefinition sourceDefinition)
|
||||||
=> (FirstNonEmpty(site.UsernameOverride, GetCentralUsername(sourceSystem, settings)),
|
=> (FirstNonEmpty(site.UsernameOverride, sourceDefinition.CentralUsername),
|
||||||
FirstNonEmpty(site.PasswordOverride, GetCentralPassword(sourceSystem, settings)));
|
FirstNonEmpty(site.PasswordOverride, sourceDefinition.CentralPassword));
|
||||||
|
|
||||||
private static string GetCentralUsername(string sourceSystem, ExportSettings settings) => sourceSystem switch
|
private static string ResolveSapServiceUrl(Site site, SourceSystemDefinition sourceDefinition)
|
||||||
{
|
=> FirstNonEmpty(site.SapServiceUrl, sourceDefinition.CentralServiceUrl);
|
||||||
"BI1" => settings.Bi1Username,
|
|
||||||
"SAGE" => settings.SageUsername,
|
|
||||||
_ => settings.SapUsername
|
|
||||||
};
|
|
||||||
|
|
||||||
private static string GetCentralPassword(string sourceSystem, ExportSettings settings) => sourceSystem switch
|
|
||||||
{
|
|
||||||
"BI1" => settings.Bi1Password,
|
|
||||||
"SAGE" => settings.SagePassword,
|
|
||||||
_ => settings.SapPassword
|
|
||||||
};
|
|
||||||
|
|
||||||
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();
|
||||||
@@ -269,4 +271,28 @@ public class SiteExportService : ISiteExportService
|
|||||||
? Path.Combine(AppContext.BaseDirectory, "output")
|
? Path.Combine(AppContext.BaseDirectory, "output")
|
||||||
: configured;
|
: configured;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,14 +47,16 @@ public class ConfigTransferServiceTests : IDisposable
|
|||||||
|
|
||||||
Assert.False(package.IncludesSecrets);
|
Assert.False(package.IncludesSecrets);
|
||||||
Assert.NotNull(package.ExportSettings);
|
Assert.NotNull(package.ExportSettings);
|
||||||
Assert.Null(package.ExportSettings.SapUsername);
|
|
||||||
Assert.Null(package.ExportSettings.SapPassword);
|
|
||||||
Assert.NotNull(package.SharePointConfig);
|
Assert.NotNull(package.SharePointConfig);
|
||||||
Assert.Null(package.SharePointConfig.ClientSecret);
|
Assert.Null(package.SharePointConfig.ClientSecret);
|
||||||
|
Assert.NotEmpty(package.SourceSystemDefinitions);
|
||||||
|
Assert.All(package.SourceSystemDefinitions, system =>
|
||||||
|
{
|
||||||
|
Assert.Null(system.CentralUsername);
|
||||||
|
Assert.Null(system.CentralPassword);
|
||||||
|
});
|
||||||
|
|
||||||
var server = Assert.Single(package.HanaServers);
|
Assert.Single(package.HanaServers);
|
||||||
Assert.Null(server.Username);
|
|
||||||
Assert.Null(server.Password);
|
|
||||||
|
|
||||||
var site = Assert.Single(package.Sites);
|
var site = Assert.Single(package.Sites);
|
||||||
Assert.Null(site.UsernameOverride);
|
Assert.Null(site.UsernameOverride);
|
||||||
@@ -90,14 +92,47 @@ public class ConfigTransferServiceTests : IDisposable
|
|||||||
TimerEnabled = false,
|
TimerEnabled = false,
|
||||||
DebugLoggingEnabled = true,
|
DebugLoggingEnabled = true,
|
||||||
LocalSiteExportFolder = "D:\\site",
|
LocalSiteExportFolder = "D:\\site",
|
||||||
LocalConsolidatedExportFolder = "D:\\consolidated",
|
LocalConsolidatedExportFolder = "D:\\consolidated"
|
||||||
SapUsername = null,
|
|
||||||
SapPassword = null,
|
|
||||||
Bi1Username = null,
|
|
||||||
Bi1Password = null,
|
|
||||||
SageUsername = null,
|
|
||||||
SagePassword = null
|
|
||||||
},
|
},
|
||||||
|
SourceSystemDefinitions =
|
||||||
|
[
|
||||||
|
new ConfigTransferSourceSystemDefinition
|
||||||
|
{
|
||||||
|
Code = "SAP",
|
||||||
|
DisplayName = "SAP",
|
||||||
|
ConnectionKind = SourceSystemConnectionKinds.SapGateway,
|
||||||
|
IsActive = true,
|
||||||
|
CentralUsername = null,
|
||||||
|
CentralPassword = null
|
||||||
|
},
|
||||||
|
new ConfigTransferSourceSystemDefinition
|
||||||
|
{
|
||||||
|
Code = "BI1",
|
||||||
|
DisplayName = "BI1",
|
||||||
|
ConnectionKind = SourceSystemConnectionKinds.Hana,
|
||||||
|
IsActive = true,
|
||||||
|
CentralUsername = null,
|
||||||
|
CentralPassword = null
|
||||||
|
},
|
||||||
|
new ConfigTransferSourceSystemDefinition
|
||||||
|
{
|
||||||
|
Code = "SAGE",
|
||||||
|
DisplayName = "SAGE",
|
||||||
|
ConnectionKind = SourceSystemConnectionKinds.Hana,
|
||||||
|
IsActive = true,
|
||||||
|
CentralUsername = null,
|
||||||
|
CentralPassword = null
|
||||||
|
},
|
||||||
|
new ConfigTransferSourceSystemDefinition
|
||||||
|
{
|
||||||
|
Code = "MANUAL_EXCEL",
|
||||||
|
DisplayName = "Manual Excel",
|
||||||
|
ConnectionKind = SourceSystemConnectionKinds.ManualExcel,
|
||||||
|
IsActive = true,
|
||||||
|
CentralUsername = null,
|
||||||
|
CentralPassword = null
|
||||||
|
}
|
||||||
|
],
|
||||||
HanaServers =
|
HanaServers =
|
||||||
[
|
[
|
||||||
new ConfigTransferHanaServer
|
new ConfigTransferHanaServer
|
||||||
@@ -106,8 +141,6 @@ public class ConfigTransferServiceTests : IDisposable
|
|||||||
Name = "Server A",
|
Name = "Server A",
|
||||||
Host = "hana-a",
|
Host = "hana-a",
|
||||||
Port = 30015,
|
Port = 30015,
|
||||||
Username = null,
|
|
||||||
Password = null,
|
|
||||||
DatabaseName = "DB1",
|
DatabaseName = "DB1",
|
||||||
UseSsl = true,
|
UseSsl = true,
|
||||||
ValidateCertificate = false,
|
ValidateCertificate = false,
|
||||||
@@ -152,20 +185,34 @@ public class ConfigTransferServiceTests : IDisposable
|
|||||||
await using var db = await _dbFactory.CreateDbContextAsync();
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
var settings = await db.ExportSettings.SingleAsync();
|
var settings = await db.ExportSettings.SingleAsync();
|
||||||
var sharePoint = await db.SharePointConfigs.SingleAsync();
|
var sharePoint = await db.SharePointConfigs.SingleAsync();
|
||||||
|
var systems = await db.SourceSystemDefinitions.OrderBy(x => x.Code).ToListAsync();
|
||||||
var server = await db.HanaServers.SingleAsync();
|
var server = await db.HanaServers.SingleAsync();
|
||||||
var site = await db.Sites.SingleAsync();
|
var site = await db.Sites.SingleAsync();
|
||||||
var rule = await db.FieldTransformationRules.SingleAsync();
|
var rule = await db.FieldTransformationRules.SingleAsync();
|
||||||
|
|
||||||
Assert.Equal("preserved-sap-user", settings.SapUsername);
|
Assert.Equal("2026-01-01", settings.DateFilter);
|
||||||
Assert.Equal("preserved-sap-password", settings.SapPassword);
|
Assert.Equal(5, settings.TimerHour);
|
||||||
Assert.Equal("preserved-bi1-user", settings.Bi1Username);
|
Assert.Equal(30, settings.TimerMinute);
|
||||||
Assert.Equal("preserved-sage-password", settings.SagePassword);
|
Assert.False(settings.TimerEnabled);
|
||||||
|
Assert.True(settings.DebugLoggingEnabled);
|
||||||
|
Assert.Equal("D:\\site", settings.LocalSiteExportFolder);
|
||||||
|
Assert.Equal("D:\\consolidated", settings.LocalConsolidatedExportFolder);
|
||||||
|
|
||||||
Assert.Equal("preserved-sharepoint-secret", sharePoint.ClientSecret);
|
Assert.Equal("preserved-sharepoint-secret", sharePoint.ClientSecret);
|
||||||
Assert.Equal("new-tenant", sharePoint.TenantId);
|
Assert.Equal("new-tenant", sharePoint.TenantId);
|
||||||
|
|
||||||
Assert.Equal("preserved-server-user", server.Username);
|
var sapSystem = Assert.Single(systems, x => x.Code == "SAP");
|
||||||
Assert.Equal("preserved-server-password", server.Password);
|
Assert.Equal("preserved-sap-user", sapSystem.CentralUsername);
|
||||||
|
Assert.Equal("preserved-sap-password", sapSystem.CentralPassword);
|
||||||
|
var bi1System = Assert.Single(systems, x => x.Code == "BI1");
|
||||||
|
Assert.Equal("preserved-bi1-user", bi1System.CentralUsername);
|
||||||
|
Assert.Equal("preserved-bi1-password", bi1System.CentralPassword);
|
||||||
|
var sageSystem = Assert.Single(systems, x => x.Code == "SAGE");
|
||||||
|
Assert.Equal("preserved-sage-user", sageSystem.CentralUsername);
|
||||||
|
Assert.Equal("preserved-sage-password", sageSystem.CentralPassword);
|
||||||
|
|
||||||
|
Assert.Equal(string.Empty, server.Username);
|
||||||
|
Assert.Equal(string.Empty, server.Password);
|
||||||
Assert.True(server.UseSsl);
|
Assert.True(server.UseSsl);
|
||||||
|
|
||||||
Assert.Equal("preserved-site-user", site.UsernameOverride);
|
Assert.Equal("preserved-site-user", site.UsernameOverride);
|
||||||
@@ -188,23 +235,50 @@ public class ConfigTransferServiceTests : IDisposable
|
|||||||
ClientId = "client",
|
ClientId = "client",
|
||||||
ClientSecret = "secret"
|
ClientSecret = "secret"
|
||||||
});
|
});
|
||||||
db.ExportSettings.Add(new ExportSettings
|
db.ExportSettings.Add(new ExportSettings());
|
||||||
{
|
db.SourceSystemDefinitions.AddRange(
|
||||||
SapUsername = "sap-user",
|
new SourceSystemDefinition
|
||||||
SapPassword = "sap-password",
|
{
|
||||||
Bi1Username = "bi1-user",
|
Code = "SAP",
|
||||||
Bi1Password = "bi1-password",
|
DisplayName = "SAP",
|
||||||
SageUsername = "sage-user",
|
ConnectionKind = SourceSystemConnectionKinds.SapGateway,
|
||||||
SagePassword = "sage-password"
|
IsActive = true,
|
||||||
});
|
CentralUsername = "sap-user",
|
||||||
|
CentralPassword = "sap-password"
|
||||||
|
},
|
||||||
|
new SourceSystemDefinition
|
||||||
|
{
|
||||||
|
Code = "BI1",
|
||||||
|
DisplayName = "BI1",
|
||||||
|
ConnectionKind = SourceSystemConnectionKinds.Hana,
|
||||||
|
IsActive = true,
|
||||||
|
CentralUsername = "bi1-user",
|
||||||
|
CentralPassword = "bi1-password"
|
||||||
|
},
|
||||||
|
new SourceSystemDefinition
|
||||||
|
{
|
||||||
|
Code = "SAGE",
|
||||||
|
DisplayName = "SAGE",
|
||||||
|
ConnectionKind = SourceSystemConnectionKinds.Hana,
|
||||||
|
IsActive = true,
|
||||||
|
CentralUsername = "sage-user",
|
||||||
|
CentralPassword = "sage-password"
|
||||||
|
},
|
||||||
|
new SourceSystemDefinition
|
||||||
|
{
|
||||||
|
Code = "MANUAL_EXCEL",
|
||||||
|
DisplayName = "Manual Excel",
|
||||||
|
ConnectionKind = SourceSystemConnectionKinds.ManualExcel,
|
||||||
|
IsActive = true
|
||||||
|
});
|
||||||
db.HanaServers.Add(new HanaServer
|
db.HanaServers.Add(new HanaServer
|
||||||
{
|
{
|
||||||
Id = 1,
|
Id = 1,
|
||||||
Name = "Server A",
|
Name = "Server A",
|
||||||
Host = "hana-a",
|
Host = "hana-a",
|
||||||
Port = 30015,
|
Port = 30015,
|
||||||
Username = "server-user",
|
Username = string.Empty,
|
||||||
Password = "server-password",
|
Password = string.Empty,
|
||||||
DatabaseName = "DB1"
|
DatabaseName = "DB1"
|
||||||
});
|
});
|
||||||
db.Sites.Add(new Site
|
db.Sites.Add(new Site
|
||||||
@@ -246,15 +320,42 @@ public class ConfigTransferServiceTests : IDisposable
|
|||||||
ClientId = "old-client",
|
ClientId = "old-client",
|
||||||
ClientSecret = "preserved-sharepoint-secret"
|
ClientSecret = "preserved-sharepoint-secret"
|
||||||
});
|
});
|
||||||
db.ExportSettings.Add(new ExportSettings
|
db.ExportSettings.Add(new ExportSettings());
|
||||||
{
|
db.SourceSystemDefinitions.AddRange(
|
||||||
SapUsername = "preserved-sap-user",
|
new SourceSystemDefinition
|
||||||
SapPassword = "preserved-sap-password",
|
{
|
||||||
Bi1Username = "preserved-bi1-user",
|
Code = "SAP",
|
||||||
Bi1Password = "preserved-bi1-password",
|
DisplayName = "SAP",
|
||||||
SageUsername = "preserved-sage-user",
|
ConnectionKind = SourceSystemConnectionKinds.SapGateway,
|
||||||
SagePassword = "preserved-sage-password"
|
IsActive = true,
|
||||||
});
|
CentralUsername = "preserved-sap-user",
|
||||||
|
CentralPassword = "preserved-sap-password"
|
||||||
|
},
|
||||||
|
new SourceSystemDefinition
|
||||||
|
{
|
||||||
|
Code = "BI1",
|
||||||
|
DisplayName = "BI1",
|
||||||
|
ConnectionKind = SourceSystemConnectionKinds.Hana,
|
||||||
|
IsActive = true,
|
||||||
|
CentralUsername = "preserved-bi1-user",
|
||||||
|
CentralPassword = "preserved-bi1-password"
|
||||||
|
},
|
||||||
|
new SourceSystemDefinition
|
||||||
|
{
|
||||||
|
Code = "SAGE",
|
||||||
|
DisplayName = "SAGE",
|
||||||
|
ConnectionKind = SourceSystemConnectionKinds.Hana,
|
||||||
|
IsActive = true,
|
||||||
|
CentralUsername = "preserved-sage-user",
|
||||||
|
CentralPassword = "preserved-sage-password"
|
||||||
|
},
|
||||||
|
new SourceSystemDefinition
|
||||||
|
{
|
||||||
|
Code = "MANUAL_EXCEL",
|
||||||
|
DisplayName = "Manual Excel",
|
||||||
|
ConnectionKind = SourceSystemConnectionKinds.ManualExcel,
|
||||||
|
IsActive = true
|
||||||
|
});
|
||||||
db.HanaServers.Add(new HanaServer
|
db.HanaServers.Add(new HanaServer
|
||||||
{
|
{
|
||||||
Id = 1,
|
Id = 1,
|
||||||
|
|||||||
Reference in New Issue
Block a user