Add configurable HANA mapping for ZSCHWEIZ
This commit is contained in:
@@ -191,15 +191,22 @@
|
|||||||
|
|
||||||
<MudDivider Class="my-4" />
|
<MudDivider Class="my-4" />
|
||||||
|
|
||||||
@if (IsSapSite())
|
@if (IsMappedSourceSite())
|
||||||
{
|
{
|
||||||
<MudText Typo="Typo.h6" Class="mb-2">SAP Gateway</MudText>
|
<MudText Typo="Typo.h6" Class="mb-2">@GetMappingSectionTitle()</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.
|
Quellen und Feldmappings werden grafisch gepflegt. Bei SAP Gateway sind Quellen Entity Sets; bei HANA sind Quellen Tabellen oder Views im gewaehlten Schema.
|
||||||
</MudAlert>
|
</MudAlert>
|
||||||
<MudText Typo="Typo.body2">Zentrale SAP Service URL: @GetCentralSapServiceUrlSummary(_editingSite.SourceSystem)</MudText>
|
@if (IsSapSite())
|
||||||
<MudTextField @bind-Value="_editingSite.SapServiceUrl" Label="SAP Service URL Override"
|
{
|
||||||
HelperText="Optional. Wenn leer, wird die zentrale SAP Service URL des Quellsystems verwendet." />
|
<MudText Typo="Typo.body2">Zentrale SAP Service URL: @GetCentralSapServiceUrlSummary(_editingSite.SourceSystem)</MudText>
|
||||||
|
<MudTextField @bind-Value="_editingSite.SapServiceUrl" Label="SAP Service URL Override"
|
||||||
|
HelperText="Optional. Wenn leer, wird die zentrale SAP Service URL des Quellsystems verwendet." />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudText Typo="Typo.body2">Zentrale HANA-Verbindung: @GetCentralHanaSummary(_editingSite.SourceSystem)</MudText>
|
||||||
|
}
|
||||||
<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">
|
||||||
@@ -210,7 +217,7 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@("Quellen refreshen")
|
@(IsSapSite() ? "Entity Sets refreshen" : "Tabellen/Views refreshen")
|
||||||
}
|
}
|
||||||
</MudButton>
|
</MudButton>
|
||||||
@if (_editingSite.SapEntitySetsRefreshedAtUtc.HasValue)
|
@if (_editingSite.SapEntitySetsRefreshedAtUtc.HasValue)
|
||||||
@@ -222,16 +229,16 @@
|
|||||||
</MudStack>
|
</MudStack>
|
||||||
<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">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 bzw. HANA Tabelle/View definieren. Joins verwenden links/rechts kommagetrennte Schluesselfelder wie `VBELN,POSNR`. Feldmappings erwarten `Alias.Feldname` oder Konstanten wie `=SAP` / `=HANA`.
|
||||||
</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>@(IsSapSite() ? "Entity Set" : "Tabelle/View")</MudTh>
|
||||||
<MudTh>Primär</MudTh>
|
<MudTh>Primär</MudTh>
|
||||||
<MudTh>Aktiv</MudTh>
|
<MudTh>Aktiv</MudTh>
|
||||||
<MudTh>Aktionen</MudTh>
|
<MudTh>Aktionen</MudTh>
|
||||||
@@ -344,7 +351,7 @@
|
|||||||
</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 hinzugefuegten Quellen als `Alias.Feldname` gelesen. Vorhandene manuelle Werte bleiben auswaehlbar.
|
||||||
</MudText>
|
</MudText>
|
||||||
<MudTable Items="_sapMappings" Dense Hover Striped>
|
<MudTable Items="_sapMappings" Dense Hover Striped>
|
||||||
<HeaderContent>
|
<HeaderContent>
|
||||||
@@ -659,7 +666,7 @@
|
|||||||
_savingSite = true;
|
_savingSite = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await StandortePageService.SaveSiteAsync(_editingSite, UsesHanaConnection(), IsSapSite(), IsManualExcelSite(), _sapSources, _sapJoins, _sapMappings, _manualExcelMappings, _sapEntitySetsCache);
|
await StandortePageService.SaveSiteAsync(_editingSite, UsesHanaConnection(), IsMappedSourceSite(), IsManualExcelSite(), _sapSources, _sapJoins, _sapMappings, _manualExcelMappings, _sapEntitySetsCache);
|
||||||
_siteDialogVisible = false;
|
_siteDialogVisible = false;
|
||||||
await LoadDataAsync();
|
await LoadDataAsync();
|
||||||
Snackbar.Add("Standort gespeichert", Severity.Success);
|
Snackbar.Add("Standort gespeichert", Severity.Success);
|
||||||
@@ -752,11 +759,17 @@
|
|||||||
private bool IsSapSite()
|
private bool IsSapSite()
|
||||||
=> string.Equals(GetSourceSystemConnectionKind(_editingSite.SourceSystem), SourceSystemConnectionKinds.SapGateway, StringComparison.OrdinalIgnoreCase);
|
=> string.Equals(GetSourceSystemConnectionKind(_editingSite.SourceSystem), SourceSystemConnectionKinds.SapGateway, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
private bool IsMappedSourceSite()
|
||||||
|
=> IsSapSite() || UsesHanaConnection();
|
||||||
|
|
||||||
private bool IsManualExcelSite()
|
private bool IsManualExcelSite()
|
||||||
=> string.Equals(GetSourceSystemConnectionKind(_editingSite.SourceSystem), SourceSystemConnectionKinds.ManualExcel, StringComparison.OrdinalIgnoreCase);
|
=> string.Equals(GetSourceSystemConnectionKind(_editingSite.SourceSystem), SourceSystemConnectionKinds.ManualExcel, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
private bool UsesHanaConnection() => IsHanaSourceSystem(_editingSite.SourceSystem);
|
private bool UsesHanaConnection() => IsHanaSourceSystem(_editingSite.SourceSystem);
|
||||||
|
|
||||||
|
private string GetMappingSectionTitle()
|
||||||
|
=> IsSapSite() ? "SAP Gateway Mapping" : "HANA Quellen und Feldmapping";
|
||||||
|
|
||||||
private string GetSourceSystemLabel(SourceSystemDefinition definition)
|
private string GetSourceSystemLabel(SourceSystemDefinition definition)
|
||||||
=> string.IsNullOrWhiteSpace(definition.DisplayName) ? definition.Code : $"{definition.DisplayName} ({definition.Code})";
|
=> string.IsNullOrWhiteSpace(definition.DisplayName) ? definition.Code : $"{definition.DisplayName} ({definition.Code})";
|
||||||
|
|
||||||
@@ -1182,7 +1195,7 @@
|
|||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (activeSources.Count == 0)
|
if (activeSources.Count == 0)
|
||||||
throw new InvalidOperationException("Es gibt keine aktiven SAP-Quellen mit Alias und Entity Set.");
|
throw new InvalidOperationException("Es gibt keine aktiven Quellen mit Alias und Entity Set/Tabelle.");
|
||||||
|
|
||||||
var result = await StandortePageService.RefreshSapSourceFieldsAsync(_editingSite, activeSources, _sapMappings);
|
var result = await StandortePageService.RefreshSapSourceFieldsAsync(_editingSite, activeSources, _sapMappings);
|
||||||
_sapAvailableSourceExpressions = result.SourceExpressions;
|
_sapAvailableSourceExpressions = result.SourceExpressions;
|
||||||
|
|||||||
@@ -29,14 +29,32 @@ public sealed class HanaDataSourceAdapter : IDataSourceAdapter
|
|||||||
|
|
||||||
using var db = await _dbFactory.CreateDbContextAsync();
|
using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
var exportServer = await BuildEffectiveServerAsync(db, site, sourceDefinition);
|
var exportServer = await BuildEffectiveServerAsync(db, site, sourceDefinition);
|
||||||
|
var sourceMappings = await db.SapSourceDefinitions
|
||||||
|
.Where(s => s.SiteId == site.Id)
|
||||||
|
.OrderBy(s => s.SortOrder)
|
||||||
|
.ThenBy(s => s.Id)
|
||||||
|
.ToListAsync();
|
||||||
|
var joins = await db.SapJoinDefinitions
|
||||||
|
.Where(j => j.SiteId == site.Id)
|
||||||
|
.OrderBy(j => j.SortOrder)
|
||||||
|
.ThenBy(j => j.Id)
|
||||||
|
.ToListAsync();
|
||||||
|
var fieldMappings = await db.SapFieldMappings
|
||||||
|
.Where(m => m.SiteId == site.Id)
|
||||||
|
.OrderBy(m => m.SortOrder)
|
||||||
|
.ThenBy(m => m.Id)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
context.UpdateStatus?.Invoke("HANA Abfrage...");
|
context.UpdateStatus?.Invoke("HANA Abfrage...");
|
||||||
await _appEventLogService.WriteAsync("Export", "HANA Abfrage gestartet",
|
await _appEventLogService.WriteAsync("Export", "HANA Abfrage gestartet",
|
||||||
siteId: site.Id, land: site.Land,
|
siteId: site.Id, land: site.Land,
|
||||||
details: exportServer.GetConnectionStringPreview());
|
details: exportServer.GetConnectionStringPreview());
|
||||||
|
|
||||||
var records = await _hanaService.GetSalesRecordsAsync(
|
var records = sourceMappings.Count > 0 && fieldMappings.Count > 0
|
||||||
exportServer, site.Schema, site.TSC, site.Land, context.Settings.DateFilter);
|
? await _hanaService.GetMappedSalesRecordsAsync(
|
||||||
|
exportServer, site.Schema, site, sourceMappings, joins, fieldMappings, context.Settings.DateFilter)
|
||||||
|
: await _hanaService.GetSalesRecordsAsync(
|
||||||
|
exportServer, site.Schema, site.TSC, site.Land, context.Settings.DateFilter);
|
||||||
|
|
||||||
return new DataSourceFetchResult { Records = records };
|
return new DataSourceFetchResult { Records = records };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ public class DatabaseSeedService : IDatabaseSeedService
|
|||||||
EnsureRecommendedTransformationRules(db);
|
EnsureRecommendedTransformationRules(db);
|
||||||
EnsureSourceSystemDefinitions(db);
|
EnsureSourceSystemDefinitions(db);
|
||||||
EnsureCentralHanaServerRecords(db);
|
EnsureCentralHanaServerRecords(db);
|
||||||
|
EnsureSpainManualExcelSite(db);
|
||||||
|
EnsureSapHanaDachSite(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SeedIfEmpty(AppDbContext db)
|
private static void SeedIfEmpty(AppDbContext db)
|
||||||
@@ -172,6 +174,7 @@ public class DatabaseSeedService : IDatabaseSeedService
|
|||||||
var defaults = new[]
|
var defaults = new[]
|
||||||
{
|
{
|
||||||
new SourceSystemDefinition { Code = "SAP", DisplayName = "SAP", ConnectionKind = SourceSystemConnectionKinds.SapGateway, IsActive = true },
|
new SourceSystemDefinition { Code = "SAP", DisplayName = "SAP", ConnectionKind = SourceSystemConnectionKinds.SapGateway, IsActive = true },
|
||||||
|
new SourceSystemDefinition { Code = "SAP_HANA", DisplayName = "SAP HANA", ConnectionKind = SourceSystemConnectionKinds.Hana, IsActive = true },
|
||||||
new SourceSystemDefinition { Code = "BI1", DisplayName = "BI1", ConnectionKind = SourceSystemConnectionKinds.Hana, 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 = "SAGE", DisplayName = "SAGE", ConnectionKind = SourceSystemConnectionKinds.Hana, IsActive = true },
|
||||||
new SourceSystemDefinition { Code = "MANUAL_EXCEL", DisplayName = "Manual Excel", ConnectionKind = SourceSystemConnectionKinds.ManualExcel, IsActive = true }
|
new SourceSystemDefinition { Code = "MANUAL_EXCEL", DisplayName = "Manual Excel", ConnectionKind = SourceSystemConnectionKinds.ManualExcel, IsActive = true }
|
||||||
@@ -222,4 +225,173 @@ public class DatabaseSeedService : IDatabaseSeedService
|
|||||||
if (changed)
|
if (changed)
|
||||||
db.SaveChanges();
|
db.SaveChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void EnsureSpainManualExcelSite(AppDbContext db)
|
||||||
|
{
|
||||||
|
if (db.Sites.Count() <= 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var existing = db.Sites
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.FirstOrDefault(x =>
|
||||||
|
x.TSC == "TRSE" ||
|
||||||
|
x.TSC == "TRES" ||
|
||||||
|
x.Land == "Spanien" ||
|
||||||
|
x.Land == "Spain");
|
||||||
|
|
||||||
|
if (existing is not null)
|
||||||
|
{
|
||||||
|
var changed = false;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(existing.TSC))
|
||||||
|
{
|
||||||
|
existing.TSC = "TRES";
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(existing.Land))
|
||||||
|
{
|
||||||
|
existing.Land = "Spanien";
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(existing.SourceSystem))
|
||||||
|
{
|
||||||
|
existing.SourceSystem = "MANUAL_EXCEL";
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
db.SaveChanges();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Sites.Add(new Site
|
||||||
|
{
|
||||||
|
Schema = string.Empty,
|
||||||
|
TSC = "TRES",
|
||||||
|
Land = "Spanien",
|
||||||
|
SourceSystem = "MANUAL_EXCEL",
|
||||||
|
IsActive = false
|
||||||
|
});
|
||||||
|
db.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureSapHanaDachSite(AppDbContext db)
|
||||||
|
{
|
||||||
|
if (db.Sites.Count() <= 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var existing = db.Sites
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.FirstOrDefault(x =>
|
||||||
|
x.TSC == "ZSCHWEIZ" ||
|
||||||
|
x.Land == "Schweiz/Oesterreich" ||
|
||||||
|
x.Land == "DACH");
|
||||||
|
|
||||||
|
if (existing is not null)
|
||||||
|
{
|
||||||
|
var changed = false;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(existing.TSC))
|
||||||
|
{
|
||||||
|
existing.TSC = "ZSCHWEIZ";
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(existing.Land))
|
||||||
|
{
|
||||||
|
existing.Land = "Schweiz/Oesterreich";
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(existing.SourceSystem))
|
||||||
|
{
|
||||||
|
existing.SourceSystem = "SAP_HANA";
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
db.SaveChanges();
|
||||||
|
|
||||||
|
EnsureSapHanaDachMapping(db, existing.Id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var site = new Site
|
||||||
|
{
|
||||||
|
Schema = string.Empty,
|
||||||
|
TSC = "ZSCHWEIZ",
|
||||||
|
Land = "Schweiz/Oesterreich",
|
||||||
|
SourceSystem = "SAP_HANA",
|
||||||
|
IsActive = false
|
||||||
|
};
|
||||||
|
db.Sites.Add(site);
|
||||||
|
db.SaveChanges();
|
||||||
|
EnsureSapHanaDachMapping(db, site.Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureSapHanaDachMapping(AppDbContext db, int siteId)
|
||||||
|
{
|
||||||
|
if (db.SapSourceDefinitions.Any(x => x.SiteId == siteId) ||
|
||||||
|
db.SapFieldMappings.Any(x => x.SiteId == siteId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
db.SapSourceDefinitions.Add(new SapSourceDefinition
|
||||||
|
{
|
||||||
|
SiteId = siteId,
|
||||||
|
Alias = "Z",
|
||||||
|
EntitySet = "ZSCHWEIZ",
|
||||||
|
IsPrimary = true,
|
||||||
|
IsActive = true,
|
||||||
|
SortOrder = 0
|
||||||
|
});
|
||||||
|
|
||||||
|
var mappings = new (string Target, string Source, bool Required)[]
|
||||||
|
{
|
||||||
|
(nameof(SalesRecord.Tsc), "Z.TSC", true),
|
||||||
|
(nameof(SalesRecord.Land), "Z.LAND1", true),
|
||||||
|
(nameof(SalesRecord.DocumentEntry), "Z.VBELN", false),
|
||||||
|
(nameof(SalesRecord.InvoiceNumber), "Z.VBELN", true),
|
||||||
|
(nameof(SalesRecord.PositionOnInvoice), "Z.POSNR", true),
|
||||||
|
(nameof(SalesRecord.InvoiceDate), "Z.FKDAT", true),
|
||||||
|
(nameof(SalesRecord.Material), "Z.MATNR", false),
|
||||||
|
(nameof(SalesRecord.Name), "Z.ARKTX", false),
|
||||||
|
(nameof(SalesRecord.ProductGroup), "Z.PRODH", false),
|
||||||
|
(nameof(SalesRecord.Quantity), "Z.FKIMG", false),
|
||||||
|
(nameof(SalesRecord.CustomerNumber), "Z.KUNNR", false),
|
||||||
|
(nameof(SalesRecord.CustomerName), "Z.NAME1", false),
|
||||||
|
(nameof(SalesRecord.CustomerCountry), "Z.CUSTOMER_LAND", false),
|
||||||
|
(nameof(SalesRecord.StandardCost), "=0", false),
|
||||||
|
(nameof(SalesRecord.StandardCostCurrency), "Z.HWAER", false),
|
||||||
|
(nameof(SalesRecord.SalesPriceValue), "Z.NETWR_HC", true),
|
||||||
|
(nameof(SalesRecord.SalesCurrency), "Z.HWAER", true),
|
||||||
|
(nameof(SalesRecord.DocumentCurrency), "Z.WAERK", false),
|
||||||
|
(nameof(SalesRecord.DocumentTotalForeignCurrency), "Z.NETWR_DC", false),
|
||||||
|
(nameof(SalesRecord.DocumentTotalLocalCurrency), "Z.NETWR_HC", false),
|
||||||
|
(nameof(SalesRecord.VatSumForeignCurrency), "Z.TAX_DC", false),
|
||||||
|
(nameof(SalesRecord.VatSumLocalCurrency), "Z.TAX_HC", false),
|
||||||
|
(nameof(SalesRecord.DocumentRate), "Z.KURRF", false),
|
||||||
|
(nameof(SalesRecord.CompanyCurrency), "Z.HWAER", true),
|
||||||
|
(nameof(SalesRecord.DocumentType), "Z.FKART", false)
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var i = 0; i < mappings.Length; i++)
|
||||||
|
{
|
||||||
|
db.SapFieldMappings.Add(new SapFieldMapping
|
||||||
|
{
|
||||||
|
SiteId = siteId,
|
||||||
|
TargetField = mappings[i].Target,
|
||||||
|
SourceExpression = mappings[i].Source,
|
||||||
|
IsRequired = mappings[i].Required,
|
||||||
|
IsActive = true,
|
||||||
|
SortOrder = i
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
db.SaveChanges();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Globalization;
|
||||||
using Sap.Data.Hana;
|
using Sap.Data.Hana;
|
||||||
using TrafagSalesExporter.Models;
|
using TrafagSalesExporter.Models;
|
||||||
|
|
||||||
@@ -142,6 +143,125 @@ public class HanaQueryService : IHanaQueryService
|
|||||||
return schemas;
|
return schemas;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<List<string>> GetAvailableTablesAsync(HanaServer server, string schema, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var connectionString = server.BuildConnectionString();
|
||||||
|
using var connection = new HanaConnection(connectionString);
|
||||||
|
await connection.OpenAsync(cancellationToken);
|
||||||
|
|
||||||
|
const string query = """
|
||||||
|
SELECT table_name
|
||||||
|
FROM sys.tables
|
||||||
|
WHERE schema_name = :schema
|
||||||
|
UNION
|
||||||
|
SELECT view_name AS table_name
|
||||||
|
FROM sys.views
|
||||||
|
WHERE schema_name = :schema
|
||||||
|
ORDER BY table_name;
|
||||||
|
""";
|
||||||
|
|
||||||
|
using var command = new HanaCommand(query, connection);
|
||||||
|
command.Parameters.Add(new HanaParameter("schema", HanaDbType.NVarChar) { Value = schema.Trim().ToUpperInvariant() });
|
||||||
|
using var reader = await command.ExecuteReaderAsync(cancellationToken);
|
||||||
|
|
||||||
|
var tables = new List<string>();
|
||||||
|
while (await reader.ReadAsync(cancellationToken))
|
||||||
|
{
|
||||||
|
var table = reader["table_name"]?.ToString()?.Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(table))
|
||||||
|
tables.Add(table);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tables;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<string>> GetTableFieldNamesAsync(HanaServer server, string schema, string tableName, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var connectionString = server.BuildConnectionString();
|
||||||
|
using var connection = new HanaConnection(connectionString);
|
||||||
|
await connection.OpenAsync(cancellationToken);
|
||||||
|
|
||||||
|
const string query = """
|
||||||
|
SELECT column_name
|
||||||
|
FROM sys.table_columns
|
||||||
|
WHERE schema_name = :schema AND table_name = :table
|
||||||
|
UNION
|
||||||
|
SELECT column_name
|
||||||
|
FROM sys.view_columns
|
||||||
|
WHERE schema_name = :schema AND view_name = :table
|
||||||
|
ORDER BY column_name;
|
||||||
|
""";
|
||||||
|
|
||||||
|
using var command = new HanaCommand(query, connection);
|
||||||
|
command.Parameters.Add(new HanaParameter("schema", HanaDbType.NVarChar) { Value = schema.Trim().ToUpperInvariant() });
|
||||||
|
command.Parameters.Add(new HanaParameter("table", HanaDbType.NVarChar) { Value = tableName.Trim().ToUpperInvariant() });
|
||||||
|
using var reader = await command.ExecuteReaderAsync(cancellationToken);
|
||||||
|
|
||||||
|
var fields = new List<string>();
|
||||||
|
while (await reader.ReadAsync(cancellationToken))
|
||||||
|
{
|
||||||
|
var field = reader["column_name"]?.ToString()?.Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(field))
|
||||||
|
fields.Add(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<SalesRecord>> GetMappedSalesRecordsAsync(
|
||||||
|
HanaServer server,
|
||||||
|
string schema,
|
||||||
|
Site site,
|
||||||
|
IReadOnlyList<SapSourceDefinition> sources,
|
||||||
|
IReadOnlyList<SapJoinDefinition> joins,
|
||||||
|
IReadOnlyList<SapFieldMapping> mappings,
|
||||||
|
string dateFilter,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var activeSources = sources
|
||||||
|
.Where(s => s.IsActive)
|
||||||
|
.OrderBy(s => s.SortOrder)
|
||||||
|
.ThenBy(s => s.Id)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (activeSources.Count == 0)
|
||||||
|
throw new InvalidOperationException($"Standort '{site.Land}' hat keine aktiven HANA-Quellen.");
|
||||||
|
if (!mappings.Any(m => m.IsActive))
|
||||||
|
throw new InvalidOperationException($"Standort '{site.Land}' hat keine aktiven HANA-Feldmappings.");
|
||||||
|
|
||||||
|
var connectionString = server.BuildConnectionString();
|
||||||
|
using var connection = new HanaConnection(connectionString);
|
||||||
|
await connection.OpenAsync(cancellationToken);
|
||||||
|
|
||||||
|
var parsedDateFilter = ParseDateFilter(dateFilter);
|
||||||
|
var sourceRows = new Dictionary<string, List<Dictionary<string, object?>>>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var source in activeSources)
|
||||||
|
{
|
||||||
|
await _appEventLogService.WriteDebugAsync("HANA", "Mapping-Quelle wird gelesen", site.Id, site.Land,
|
||||||
|
$"Alias={source.Alias} | Tabelle/View={source.EntitySet}");
|
||||||
|
sourceRows[source.Alias] = await ReadMappedSourceRowsAsync(connection, schema, source.EntitySet, parsedDateFilter, cancellationToken);
|
||||||
|
await _appEventLogService.WriteDebugAsync("HANA", "Mapping-Quelle gelesen", site.Id, site.Land,
|
||||||
|
$"Alias={source.Alias} | Tabelle/View={source.EntitySet} | Zeilen={sourceRows[source.Alias].Count}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var primarySource = activeSources.FirstOrDefault(s => s.IsPrimary) ?? activeSources.First();
|
||||||
|
var composedRows = sourceRows[primarySource.Alias]
|
||||||
|
.Select(r => PrefixRow(primarySource.Alias, r))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var join in joins.Where(j => j.IsActive).OrderBy(j => j.SortOrder).ThenBy(j => j.Id))
|
||||||
|
{
|
||||||
|
if (!sourceRows.TryGetValue(join.RightAlias, out var rightRows))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
composedRows = ApplyLeftJoin(composedRows, join.LeftAlias, join.LeftKeys, join.RightAlias, join.RightKeys, rightRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
return composedRows
|
||||||
|
.Select(row => MapToSalesRecord(site, row, mappings))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<List<SalesRecord>> ReadRecordsAsync(HanaConnection connection, string query, string tsc, DateTime dateFilter, string land, string queryName, CancellationToken cancellationToken)
|
private async Task<List<SalesRecord>> ReadRecordsAsync(HanaConnection connection, string query, string tsc, DateTime dateFilter, string land, string queryName, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var records = new List<SalesRecord>();
|
var records = new List<SalesRecord>();
|
||||||
@@ -203,6 +323,209 @@ public class HanaQueryService : IHanaQueryService
|
|||||||
return records;
|
return records;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task<List<Dictionary<string, object?>>> ReadMappedSourceRowsAsync(
|
||||||
|
HanaConnection connection,
|
||||||
|
string schema,
|
||||||
|
string tableName,
|
||||||
|
DateTime dateFilter,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var schemaPrefix = BuildSchemaPrefix(schema);
|
||||||
|
var tableIdentifier = BuildIdentifier(tableName);
|
||||||
|
var hasFkdat = await HasColumnAsync(connection, schema, tableName, "FKDAT", cancellationToken);
|
||||||
|
var query = hasFkdat
|
||||||
|
? $@"SELECT * FROM {schemaPrefix}{tableIdentifier} WHERE ""FKDAT"" >= :{DateFilterParameterName}"
|
||||||
|
: $@"SELECT * FROM {schemaPrefix}{tableIdentifier}";
|
||||||
|
|
||||||
|
using var command = new HanaCommand(query, connection);
|
||||||
|
if (hasFkdat)
|
||||||
|
command.Parameters.Add(new HanaParameter(DateFilterParameterName, HanaDbType.Date) { Value = dateFilter.Date });
|
||||||
|
|
||||||
|
using var reader = await command.ExecuteReaderAsync(cancellationToken);
|
||||||
|
var rows = new List<Dictionary<string, object?>>();
|
||||||
|
while (await reader.ReadAsync(cancellationToken))
|
||||||
|
{
|
||||||
|
var row = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
for (var i = 0; i < reader.FieldCount; i++)
|
||||||
|
row[reader.GetName(i)] = reader.IsDBNull(i) ? null : reader.GetValue(i);
|
||||||
|
rows.Add(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<bool> HasColumnAsync(HanaConnection connection, string schema, string tableName, string columnName, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
const string query = """
|
||||||
|
SELECT COUNT(*) AS cnt
|
||||||
|
FROM (
|
||||||
|
SELECT column_name
|
||||||
|
FROM sys.table_columns
|
||||||
|
WHERE schema_name = :schema AND table_name = :table AND column_name = :column
|
||||||
|
UNION ALL
|
||||||
|
SELECT column_name
|
||||||
|
FROM sys.view_columns
|
||||||
|
WHERE schema_name = :schema AND view_name = :table AND column_name = :column
|
||||||
|
) x;
|
||||||
|
""";
|
||||||
|
|
||||||
|
using var command = new HanaCommand(query, connection);
|
||||||
|
command.Parameters.Add(new HanaParameter("schema", HanaDbType.NVarChar) { Value = schema.Trim().ToUpperInvariant() });
|
||||||
|
command.Parameters.Add(new HanaParameter("table", HanaDbType.NVarChar) { Value = tableName.Trim().ToUpperInvariant() });
|
||||||
|
command.Parameters.Add(new HanaParameter("column", HanaDbType.NVarChar) { Value = columnName.Trim().ToUpperInvariant() });
|
||||||
|
var count = await command.ExecuteScalarAsync(cancellationToken);
|
||||||
|
return Convert.ToInt32(count) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, object?> PrefixRow(string alias, Dictionary<string, object?> row)
|
||||||
|
=> row.ToDictionary(kvp => $"{alias}.{kvp.Key}", kvp => kvp.Value, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
private static List<Dictionary<string, object?>> ApplyLeftJoin(
|
||||||
|
List<Dictionary<string, object?>> leftRows,
|
||||||
|
string leftAlias,
|
||||||
|
string leftKeys,
|
||||||
|
string rightAlias,
|
||||||
|
string rightKeys,
|
||||||
|
List<Dictionary<string, object?>> rightRows)
|
||||||
|
{
|
||||||
|
var leftKeyParts = SplitKeys(leftKeys);
|
||||||
|
var rightKeyParts = SplitKeys(rightKeys);
|
||||||
|
if (leftKeyParts.Count == 0 || leftKeyParts.Count != rightKeyParts.Count)
|
||||||
|
return leftRows;
|
||||||
|
|
||||||
|
var rightLookup = rightRows
|
||||||
|
.GroupBy(r => BuildKey(r, rightKeyParts))
|
||||||
|
.ToDictionary(g => g.Key, g => g.ToList(), StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var results = new List<Dictionary<string, object?>>();
|
||||||
|
foreach (var leftRow in leftRows)
|
||||||
|
{
|
||||||
|
var leftKey = BuildKey(leftRow, leftAlias, leftKeyParts);
|
||||||
|
if (rightLookup.TryGetValue(leftKey, out var matches) && matches.Count > 0)
|
||||||
|
{
|
||||||
|
foreach (var match in matches)
|
||||||
|
{
|
||||||
|
var merged = new Dictionary<string, object?>(leftRow, StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var kvp in PrefixRow(rightAlias, match))
|
||||||
|
merged[kvp.Key] = kvp.Value;
|
||||||
|
results.Add(merged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
results.Add(leftRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SalesRecord MapToSalesRecord(Site site, Dictionary<string, object?> row, IReadOnlyList<SapFieldMapping> mappings)
|
||||||
|
{
|
||||||
|
var record = new SalesRecord
|
||||||
|
{
|
||||||
|
ExtractionDate = DateTime.UtcNow,
|
||||||
|
Tsc = site.TSC,
|
||||||
|
Land = site.Land,
|
||||||
|
DocumentType = "HANA"
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var mapping in mappings.Where(m => m.IsActive).OrderBy(m => m.SortOrder).ThenBy(m => m.Id))
|
||||||
|
{
|
||||||
|
var value = EvaluateExpression(row, mapping.SourceExpression);
|
||||||
|
ApplyValue(record, mapping.TargetField, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record.ExtractionDate == default)
|
||||||
|
record.ExtractionDate = DateTime.UtcNow;
|
||||||
|
if (string.IsNullOrWhiteSpace(record.Tsc))
|
||||||
|
record.Tsc = site.TSC;
|
||||||
|
if (string.IsNullOrWhiteSpace(record.Land))
|
||||||
|
record.Land = site.Land;
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object? EvaluateExpression(Dictionary<string, object?> row, string expression)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(expression))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var value = expression.Trim();
|
||||||
|
if (value.StartsWith('='))
|
||||||
|
return value[1..];
|
||||||
|
|
||||||
|
if (row.TryGetValue(value, out var direct))
|
||||||
|
return direct;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyValue(SalesRecord record, string targetField, object? value)
|
||||||
|
{
|
||||||
|
var property = typeof(SalesRecord).GetProperty(targetField);
|
||||||
|
if (property is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (property.PropertyType == typeof(string))
|
||||||
|
{
|
||||||
|
property.SetValue(record, value?.ToString() ?? string.Empty);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property.PropertyType == typeof(int))
|
||||||
|
{
|
||||||
|
if (int.TryParse(value?.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out var intValue))
|
||||||
|
property.SetValue(record, intValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property.PropertyType == typeof(decimal))
|
||||||
|
{
|
||||||
|
if (decimal.TryParse(value?.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out var decimalValue))
|
||||||
|
property.SetValue(record, decimalValue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property.PropertyType == typeof(DateTime?) || property.PropertyType == typeof(DateTime))
|
||||||
|
{
|
||||||
|
if (TryParseDate(value?.ToString(), out var date))
|
||||||
|
property.SetValue(record, date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Invalid field mappings should not stop the remaining row mapping.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryParseDate(string? value, out DateTime date)
|
||||||
|
{
|
||||||
|
date = default;
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return DateTime.TryParse(value.Trim(), CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out date)
|
||||||
|
|| DateTime.TryParse(value.Trim(), CultureInfo.GetCultureInfo("de-CH"), DateTimeStyles.AssumeLocal, out date);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildKey(Dictionary<string, object?> row, IReadOnlyList<string> keys)
|
||||||
|
=> string.Join("||", keys.Select(k => NormalizeKeyValue(row.TryGetValue(k, out var value) ? value : null)));
|
||||||
|
|
||||||
|
private static string BuildKey(Dictionary<string, object?> row, string alias, IReadOnlyList<string> keys)
|
||||||
|
=> string.Join("||", keys.Select(k =>
|
||||||
|
{
|
||||||
|
row.TryGetValue($"{alias}.{k}", out var value);
|
||||||
|
return NormalizeKeyValue(value);
|
||||||
|
}));
|
||||||
|
|
||||||
|
private static string NormalizeKeyValue(object? value) => value?.ToString()?.Trim() ?? string.Empty;
|
||||||
|
|
||||||
|
private static List<string> SplitKeys(string keys)
|
||||||
|
=> keys.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList();
|
||||||
|
|
||||||
private static string GetInvoiceQuery(string schema)
|
private static string GetInvoiceQuery(string schema)
|
||||||
{
|
{
|
||||||
var schemaPrefix = BuildSchemaPrefix(schema);
|
var schemaPrefix = BuildSchemaPrefix(schema);
|
||||||
@@ -345,6 +668,21 @@ ORDER BY h.""DocDate"" DESC, h.""DocNum"", p.""LineNum""";
|
|||||||
|
|
||||||
return $"{value}.";
|
return $"{value}.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string BuildIdentifier(string identifier)
|
||||||
|
{
|
||||||
|
var value = identifier?.Trim() ?? string.Empty;
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
throw new InvalidOperationException("HANA-Identifier darf nicht leer sein.");
|
||||||
|
|
||||||
|
foreach (var ch in value)
|
||||||
|
{
|
||||||
|
if (!(char.IsLetterOrDigit(ch) || ch == '_'))
|
||||||
|
throw new InvalidOperationException($"Ungueltiger HANA-Identifier: '{identifier}'.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"\"{value}\"";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ConnectionTestResult
|
public class ConnectionTestResult
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ namespace TrafagSalesExporter.Services;
|
|||||||
public interface IHanaQueryService
|
public interface IHanaQueryService
|
||||||
{
|
{
|
||||||
Task<List<SalesRecord>> GetSalesRecordsAsync(HanaServer server, string schema, string tsc, string land, string dateFilter, CancellationToken cancellationToken = default);
|
Task<List<SalesRecord>> GetSalesRecordsAsync(HanaServer server, string schema, string tsc, string land, string dateFilter, CancellationToken cancellationToken = default);
|
||||||
|
Task<List<SalesRecord>> GetMappedSalesRecordsAsync(HanaServer server, string schema, Site site, IReadOnlyList<SapSourceDefinition> sources, IReadOnlyList<SapJoinDefinition> joins, IReadOnlyList<SapFieldMapping> mappings, string dateFilter, CancellationToken cancellationToken = default);
|
||||||
Task<List<string>> GetAvailableSchemasAsync(HanaServer server, CancellationToken cancellationToken = default);
|
Task<List<string>> GetAvailableSchemasAsync(HanaServer server, CancellationToken cancellationToken = default);
|
||||||
|
Task<List<string>> GetAvailableTablesAsync(HanaServer server, string schema, CancellationToken cancellationToken = default);
|
||||||
|
Task<List<string>> GetTableFieldNamesAsync(HanaServer server, string schema, string tableName, CancellationToken cancellationToken = default);
|
||||||
Task<ConnectionTestResult> TestConnectionDetailedAsync(HanaServer server, CancellationToken cancellationToken = default);
|
Task<ConnectionTestResult> TestConnectionDetailedAsync(HanaServer server, CancellationToken cancellationToken = default);
|
||||||
Task TestConnectionAsync(HanaServer server, CancellationToken cancellationToken = default);
|
Task TestConnectionAsync(HanaServer server, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,6 +282,20 @@ public sealed class StandortePageService : IStandortePageService
|
|||||||
{
|
{
|
||||||
await using var db = await _dbFactory.CreateDbContextAsync();
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
var sourceDefinition = await db.SourceSystemDefinitions.OrderBy(x => x.Id).FirstOrDefaultAsync(x => x.Code == site.SourceSystem);
|
var sourceDefinition = await db.SourceSystemDefinitions.OrderBy(x => x.Id).FirstOrDefaultAsync(x => x.Code == site.SourceSystem);
|
||||||
|
if (string.Equals(sourceDefinition?.ConnectionKind, SourceSystemConnectionKinds.Hana, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var server = await BuildEffectiveHanaServerAsync(db, site, sourceDefinition);
|
||||||
|
if (string.IsNullOrWhiteSpace(site.Schema))
|
||||||
|
throw new InvalidOperationException("Bitte zuerst ein HANA-Schema eintragen.");
|
||||||
|
|
||||||
|
var tables = await _hanaService.GetAvailableTablesAsync(server, site.Schema);
|
||||||
|
return new SapEntitySetRefreshResult
|
||||||
|
{
|
||||||
|
EntitySets = tables,
|
||||||
|
RefreshedAtUtc = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var serviceUrl = string.IsNullOrWhiteSpace(site.SapServiceUrl) ? sourceDefinition?.CentralServiceUrl ?? string.Empty : site.SapServiceUrl;
|
var serviceUrl = string.IsNullOrWhiteSpace(site.SapServiceUrl) ? sourceDefinition?.CentralServiceUrl ?? string.Empty : site.SapServiceUrl;
|
||||||
if (string.IsNullOrWhiteSpace(serviceUrl))
|
if (string.IsNullOrWhiteSpace(serviceUrl))
|
||||||
throw new InvalidOperationException("Es ist weder eine zentrale SAP Service URL noch ein Standort-Override gesetzt.");
|
throw new InvalidOperationException("Es ist weder eine zentrale SAP Service URL noch ein Standort-Override gesetzt.");
|
||||||
@@ -315,6 +329,38 @@ public sealed class StandortePageService : IStandortePageService
|
|||||||
|
|
||||||
await using var db = await _dbFactory.CreateDbContextAsync();
|
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||||
var sourceDefinition = await db.SourceSystemDefinitions.OrderBy(x => x.Id).FirstOrDefaultAsync(x => x.Code == site.SourceSystem);
|
var sourceDefinition = await db.SourceSystemDefinitions.OrderBy(x => x.Id).FirstOrDefaultAsync(x => x.Code == site.SourceSystem);
|
||||||
|
if (string.Equals(sourceDefinition?.ConnectionKind, SourceSystemConnectionKinds.Hana, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var server = await BuildEffectiveHanaServerAsync(db, site, sourceDefinition);
|
||||||
|
if (string.IsNullOrWhiteSpace(site.Schema))
|
||||||
|
throw new InvalidOperationException("Bitte zuerst ein HANA-Schema eintragen.");
|
||||||
|
|
||||||
|
var hanaExpressions = new List<string> { "=HANA" };
|
||||||
|
var hanaSourceFieldMap = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var source in activeSources)
|
||||||
|
{
|
||||||
|
var fieldNames = await _hanaService.GetTableFieldNamesAsync(server, site.Schema, source.EntitySet);
|
||||||
|
hanaSourceFieldMap[source.Alias] = fieldNames;
|
||||||
|
hanaExpressions.AddRange(fieldNames.Select(field => $"{source.Alias}.{field}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var current in sapMappings.Select(m => m.SourceExpression).Where(x => !string.IsNullOrWhiteSpace(x)))
|
||||||
|
{
|
||||||
|
if (!hanaExpressions.Contains(current, StringComparer.OrdinalIgnoreCase))
|
||||||
|
hanaExpressions.Add(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new SapSourceFieldRefreshResult
|
||||||
|
{
|
||||||
|
SourceFieldMap = hanaSourceFieldMap,
|
||||||
|
SourceExpressions = hanaExpressions
|
||||||
|
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.OrderBy(x => x, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToList()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var serviceUrl = string.IsNullOrWhiteSpace(site.SapServiceUrl) ? sourceDefinition?.CentralServiceUrl ?? string.Empty : site.SapServiceUrl;
|
var serviceUrl = string.IsNullOrWhiteSpace(site.SapServiceUrl) ? sourceDefinition?.CentralServiceUrl ?? string.Empty : site.SapServiceUrl;
|
||||||
if (string.IsNullOrWhiteSpace(serviceUrl))
|
if (string.IsNullOrWhiteSpace(serviceUrl))
|
||||||
throw new InvalidOperationException("Es ist weder eine zentrale SAP Service URL noch ein Standort-Override gesetzt.");
|
throw new InvalidOperationException("Es ist weder eine zentrale SAP Service URL noch ein Standort-Override gesetzt.");
|
||||||
@@ -573,6 +619,36 @@ public sealed class StandortePageService : IStandortePageService
|
|||||||
|
|
||||||
return centralServer.Id;
|
return centralServer.Id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task<HanaServer> BuildEffectiveHanaServerAsync(AppDbContext db, Site site, SourceSystemDefinition? sourceDefinition)
|
||||||
|
{
|
||||||
|
var normalizedSourceSystem = string.IsNullOrWhiteSpace(site.SourceSystem) ? string.Empty : site.SourceSystem.Trim().ToUpperInvariant();
|
||||||
|
var centralServer = await db.HanaServers
|
||||||
|
.AsNoTracking()
|
||||||
|
.OrderBy(x => x.Id)
|
||||||
|
.FirstOrDefaultAsync(x => x.SourceSystem == normalizedSourceSystem)
|
||||||
|
?? throw new InvalidOperationException($"Fuer Quellsystem '{normalizedSourceSystem}' ist keine zentrale HANA-Konfiguration vorhanden.");
|
||||||
|
|
||||||
|
var username = string.IsNullOrWhiteSpace(site.UsernameOverride) ? sourceDefinition?.CentralUsername ?? string.Empty : site.UsernameOverride;
|
||||||
|
var password = string.IsNullOrWhiteSpace(site.PasswordOverride) ? sourceDefinition?.CentralPassword ?? string.Empty : site.PasswordOverride;
|
||||||
|
if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password))
|
||||||
|
throw new InvalidOperationException($"Fuer {normalizedSourceSystem} sind weder zentrale Zugangsdaten noch Standort-Overrides gesetzt.");
|
||||||
|
|
||||||
|
return new HanaServer
|
||||||
|
{
|
||||||
|
Id = centralServer.Id,
|
||||||
|
SourceSystem = centralServer.SourceSystem,
|
||||||
|
Name = centralServer.Name,
|
||||||
|
Host = centralServer.Host,
|
||||||
|
Port = centralServer.Port,
|
||||||
|
Username = username.Trim(),
|
||||||
|
Password = password,
|
||||||
|
DatabaseName = centralServer.DatabaseName,
|
||||||
|
UseSsl = centralServer.UseSsl,
|
||||||
|
ValidateCertificate = centralServer.ValidateCertificate,
|
||||||
|
AdditionalParams = centralServer.AdditionalParams
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class StandortePageState
|
public sealed class StandortePageState
|
||||||
|
|||||||
@@ -0,0 +1,386 @@
|
|||||||
|
*&---------------------------------------------------------------------*
|
||||||
|
*& Report ZTRAFAG_SCHWEIZ_EXPORT
|
||||||
|
*&---------------------------------------------------------------------*
|
||||||
|
*& Zweck
|
||||||
|
*& Ermittelt SD-Faktura-Positionen fuer Schweiz/Oesterreich aus
|
||||||
|
*& Buchungskreis 1100/1200 und schreibt sie per Upsert in Tabelle
|
||||||
|
*& ZSCHWEIZ.
|
||||||
|
*& ZSCHWEIZ kann danach aus SAP HANA in TrafagSalesExporter geladen
|
||||||
|
*& werden.
|
||||||
|
*&
|
||||||
|
*& HANA-Anbindung
|
||||||
|
*& - Tabelle/View-Basis: ZSCHWEIZ
|
||||||
|
*& - im .NET-Programm Standort mit Quellsystem SAP_HANA verwenden
|
||||||
|
*& - HANA-Schema auf das ABAP/HANA-Schema setzen
|
||||||
|
*& - grafische Quelle: Alias Z, Tabelle/View ZSCHWEIZ
|
||||||
|
*& - grafische Feldmappings koennen auf die unten vorgeschlagenen
|
||||||
|
*& Felder gemappt werden.
|
||||||
|
*&
|
||||||
|
*& Fachliche Annahmen
|
||||||
|
*& - Hauswaehrung ist fuehrend.
|
||||||
|
*& - Nettofakturawert wird pro Belegposition ermittelt.
|
||||||
|
*& - Gutschriften/Stornos werden ueber den Fakturatyp negativ bewertet.
|
||||||
|
*& - Buchungskreis 1100 = Schweiz.
|
||||||
|
*& - Buchungskreis 1200 = Oesterreich.
|
||||||
|
*& - TSC/Reporting-Land werden aus BUKRS abgeleitet; Kundenland
|
||||||
|
*& (KNA1-LAND1) bleibt als Infofeld erhalten.
|
||||||
|
*&
|
||||||
|
*& DDIC-Vorschlag fuer ZSCHWEIZ
|
||||||
|
*& Client-dependent Tabelle, Auslieferungsklasse A, Datenpflege erlaubt.
|
||||||
|
*&
|
||||||
|
*& Schluesselfelder:
|
||||||
|
*& MANDT MANDT SAP Mandant
|
||||||
|
*& BUKRS BUKRS Buchungskreis
|
||||||
|
*& GJAHR GJAHR Geschaeftsjahr aus FKDAT
|
||||||
|
*& VBELN VBELN_VF Fakturanummer
|
||||||
|
*& POSNR POSNR_VF Fakturaposition
|
||||||
|
*&
|
||||||
|
*& Datenfelder:
|
||||||
|
*& LAND1 LAND1 Reporting-Land aus BUKRS, z.B. CH/AT
|
||||||
|
*& CUSTOMER_LAND LAND1 Kundenland aus KNA1-LAND1
|
||||||
|
*& TSC CHAR10 Reporting-Standort, z.B. TRCH/TRAT
|
||||||
|
*& FKDAT FKDAT Fakturadatum
|
||||||
|
*& FKART FKART Fakturatyp
|
||||||
|
*& VBTYP VBTYP SD-Belegkategorie
|
||||||
|
*& KUNNR KUNNR Auftraggeber/Sold-to
|
||||||
|
*& NAME1 NAME1_GP Kundenname
|
||||||
|
*& MATNR MATNR Material
|
||||||
|
*& ARKTX ARKTX Positionsbezeichnung
|
||||||
|
*& PRODH PRODH_D Produkthierarchie
|
||||||
|
*& FKIMG FKIMG Fakturamenge
|
||||||
|
*& VRKME VRKME Verkaufsmengeneinheit
|
||||||
|
*& WAERK WAERK Belegwaehrung
|
||||||
|
*& HWAER WAERS Hauswaehrung aus T001
|
||||||
|
*& NETWR_DC CURR 23,2 Positions-Netto in Belegwaehrung
|
||||||
|
*& TAX_DC CURR 23,2 Positions-Steuer in Belegwaehrung
|
||||||
|
*& NETWR_HC CURR 23,2 Positions-Netto in Hauswaehrung
|
||||||
|
*& TAX_HC CURR 23,2 Positions-Steuer in Hauswaehrung
|
||||||
|
*& KURRF KURRF Rechnungsumrechnungskurs
|
||||||
|
*& IS_CREDIT BOOLE_D X = Gutschrift/Storno negativ bewertet
|
||||||
|
*& PARTY_CLASS CHAR10 2ND/3RD fuer spaetere IC-Abgrenzung
|
||||||
|
*& ERDAT_SRC ERDAT Anlage-/Quell-Erfassungsdatum
|
||||||
|
*& AEDAT_SRC AEDAT Aenderungsdatum, falls vorhanden
|
||||||
|
*& CREATED_AT TIMESTAMPL Insert-Zeitpunkt
|
||||||
|
*& CHANGED_AT TIMESTAMPL Update-Zeitpunkt
|
||||||
|
*& CREATED_BY SYUNAME Insert-User
|
||||||
|
*& CHANGED_BY SYUNAME Update-User
|
||||||
|
*&
|
||||||
|
*& Sekundaerindex empfohlen:
|
||||||
|
*& Z01: BUKRS, LAND1, GJAHR, FKDAT
|
||||||
|
*& Z02: KUNNR, GJAHR
|
||||||
|
*&---------------------------------------------------------------------*
|
||||||
|
|
||||||
|
REPORT ztrafag_schweiz_export.
|
||||||
|
|
||||||
|
TABLES: vbrk, vbrp, kna1.
|
||||||
|
|
||||||
|
TYPES: BEGIN OF ty_billing,
|
||||||
|
bukrs TYPE vbrk-bukrs,
|
||||||
|
vbeln TYPE vbrk-vbeln,
|
||||||
|
fkdat TYPE vbrk-fkdat,
|
||||||
|
fkart TYPE vbrk-fkart,
|
||||||
|
vbtyp TYPE vbrk-vbtyp,
|
||||||
|
waerk TYPE vbrk-waerk,
|
||||||
|
kurrf TYPE vbrk-kurrf,
|
||||||
|
kunag TYPE vbrk-kunag,
|
||||||
|
erdat TYPE vbrk-erdat,
|
||||||
|
posnr TYPE vbrp-posnr,
|
||||||
|
matnr TYPE vbrp-matnr,
|
||||||
|
arktx TYPE vbrp-arktx,
|
||||||
|
prodh TYPE vbrp-prodh,
|
||||||
|
fkimg TYPE vbrp-fkimg,
|
||||||
|
vrkme TYPE vbrp-vrkme,
|
||||||
|
netwr TYPE vbrp-netwr,
|
||||||
|
mwsbp TYPE vbrp-mwsbp,
|
||||||
|
customer_land TYPE kna1-land1,
|
||||||
|
name1 TYPE kna1-name1,
|
||||||
|
hwaer TYPE t001-waers,
|
||||||
|
END OF ty_billing.
|
||||||
|
|
||||||
|
TYPES: BEGIN OF ty_zschweiz,
|
||||||
|
mandt TYPE mandt,
|
||||||
|
bukrs TYPE bukrs,
|
||||||
|
gjahr TYPE gjahr,
|
||||||
|
vbeln TYPE vbeln_vf,
|
||||||
|
posnr TYPE posnr_vf,
|
||||||
|
land1 TYPE land1,
|
||||||
|
customer_land TYPE land1,
|
||||||
|
tsc TYPE c LENGTH 10,
|
||||||
|
fkdat TYPE fkdat,
|
||||||
|
fkart TYPE fkart,
|
||||||
|
vbtyp TYPE vbtyp,
|
||||||
|
kunnr TYPE kunnr,
|
||||||
|
name1 TYPE name1_gp,
|
||||||
|
matnr TYPE matnr,
|
||||||
|
arktx TYPE arktx,
|
||||||
|
prodh TYPE prodh_d,
|
||||||
|
fkimg TYPE fkimg,
|
||||||
|
vrkme TYPE vrkme,
|
||||||
|
waerk TYPE waerk,
|
||||||
|
hwaer TYPE waers,
|
||||||
|
netwr_dc TYPE p LENGTH 23 DECIMALS 2,
|
||||||
|
tax_dc TYPE p LENGTH 23 DECIMALS 2,
|
||||||
|
netwr_hc TYPE p LENGTH 23 DECIMALS 2,
|
||||||
|
tax_hc TYPE p LENGTH 23 DECIMALS 2,
|
||||||
|
kurrf TYPE kurrf,
|
||||||
|
is_credit TYPE boole_d,
|
||||||
|
party_class TYPE c LENGTH 10,
|
||||||
|
erdat_src TYPE erdat,
|
||||||
|
aedat_src TYPE aedat,
|
||||||
|
created_at TYPE timestampl,
|
||||||
|
changed_at TYPE timestampl,
|
||||||
|
created_by TYPE syuname,
|
||||||
|
changed_by TYPE syuname,
|
||||||
|
END OF ty_zschweiz.
|
||||||
|
|
||||||
|
DATA: gt_billing TYPE STANDARD TABLE OF ty_billing WITH EMPTY KEY,
|
||||||
|
gt_zschweiz TYPE STANDARD TABLE OF zschweiz WITH EMPTY KEY,
|
||||||
|
gs_zschweiz TYPE zschweiz.
|
||||||
|
|
||||||
|
SELECTION-SCREEN BEGIN OF BLOCK b01 WITH FRAME TITLE TEXT-t01.
|
||||||
|
PARAMETERS: p_gjahr TYPE gjahr DEFAULT sy-datum(4) OBLIGATORY.
|
||||||
|
SELECT-OPTIONS: s_bukrs FOR vbrk-bukrs,
|
||||||
|
s_fkart FOR vbrk-fkart,
|
||||||
|
s_vbeln FOR vbrk-vbeln.
|
||||||
|
PARAMETERS: p_test AS CHECKBOX DEFAULT abap_true.
|
||||||
|
SELECTION-SCREEN END OF BLOCK b01.
|
||||||
|
|
||||||
|
INITIALIZATION.
|
||||||
|
TEXT-t01 = 'Finance Export Selektion'.
|
||||||
|
s_bukrs-sign = 'I'.
|
||||||
|
s_bukrs-option = 'EQ'.
|
||||||
|
s_bukrs-low = '1100'.
|
||||||
|
APPEND s_bukrs.
|
||||||
|
s_bukrs-low = '1200'.
|
||||||
|
APPEND s_bukrs.
|
||||||
|
|
||||||
|
START-OF-SELECTION.
|
||||||
|
PERFORM read_billing_data.
|
||||||
|
PERFORM map_to_zschweiz.
|
||||||
|
PERFORM persist_zschweiz.
|
||||||
|
|
||||||
|
FORM read_billing_data.
|
||||||
|
DATA(lv_date_from) = CONV fkdat( |{ p_gjahr }0101| ).
|
||||||
|
DATA(lv_date_to) = CONV fkdat( |{ p_gjahr }1231| ).
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
h~bukrs,
|
||||||
|
h~vbeln,
|
||||||
|
h~fkdat,
|
||||||
|
h~fkart,
|
||||||
|
h~vbtyp,
|
||||||
|
h~waerk,
|
||||||
|
h~kurrf,
|
||||||
|
h~kunag,
|
||||||
|
h~erdat,
|
||||||
|
i~posnr,
|
||||||
|
i~matnr,
|
||||||
|
i~arktx,
|
||||||
|
i~prodh,
|
||||||
|
i~fkimg,
|
||||||
|
i~vrkme,
|
||||||
|
i~netwr,
|
||||||
|
i~mwsbp,
|
||||||
|
k~land1 AS customer_land,
|
||||||
|
k~name1,
|
||||||
|
c~waers AS hwaer
|
||||||
|
FROM vbrk AS h
|
||||||
|
INNER JOIN vbrp AS i
|
||||||
|
ON i~vbeln = h~vbeln
|
||||||
|
LEFT OUTER JOIN kna1 AS k
|
||||||
|
ON k~kunnr = h~kunag
|
||||||
|
LEFT OUTER JOIN t001 AS c
|
||||||
|
ON c~bukrs = h~bukrs
|
||||||
|
WHERE h~bukrs IN @s_bukrs
|
||||||
|
AND h~fkdat BETWEEN @lv_date_from AND @lv_date_to
|
||||||
|
AND h~vbeln IN @s_vbeln
|
||||||
|
AND h~fkart IN @s_fkart
|
||||||
|
AND h~fksto = @space
|
||||||
|
INTO TABLE @gt_billing.
|
||||||
|
|
||||||
|
WRITE: / 'Gelesene Fakturapositionen:', lines( gt_billing ).
|
||||||
|
ENDFORM.
|
||||||
|
|
||||||
|
FORM map_to_zschweiz.
|
||||||
|
DATA: lv_sign TYPE i,
|
||||||
|
lv_netwr_hc TYPE p LENGTH 23 DECIMALS 2,
|
||||||
|
lv_tax_hc TYPE p LENGTH 23 DECIMALS 2,
|
||||||
|
lv_timestamp TYPE timestampl,
|
||||||
|
lv_party TYPE c LENGTH 10.
|
||||||
|
|
||||||
|
GET TIME STAMP FIELD lv_timestamp.
|
||||||
|
|
||||||
|
LOOP AT gt_billing ASSIGNING FIELD-SYMBOL(<ls_billing>).
|
||||||
|
CLEAR: gs_zschweiz, lv_netwr_hc, lv_tax_hc, lv_party.
|
||||||
|
|
||||||
|
lv_sign = 1.
|
||||||
|
IF <ls_billing>-vbtyp = 'O'
|
||||||
|
OR <ls_billing>-vbtyp = 'N'
|
||||||
|
OR <ls_billing>-fkart CP 'G*'
|
||||||
|
OR <ls_billing>-fkart CP 'S*'.
|
||||||
|
lv_sign = -1.
|
||||||
|
ENDIF.
|
||||||
|
|
||||||
|
PERFORM convert_to_house_currency
|
||||||
|
USING <ls_billing>-netwr <ls_billing>-waerk <ls_billing>-hwaer <ls_billing>-fkdat <ls_billing>-kurrf
|
||||||
|
CHANGING lv_netwr_hc.
|
||||||
|
|
||||||
|
PERFORM convert_to_house_currency
|
||||||
|
USING <ls_billing>-mwsbp <ls_billing>-waerk <ls_billing>-hwaer <ls_billing>-fkdat <ls_billing>-kurrf
|
||||||
|
CHANGING lv_tax_hc.
|
||||||
|
|
||||||
|
PERFORM classify_party
|
||||||
|
USING <ls_billing>-kunnr <ls_billing>-name1
|
||||||
|
CHANGING lv_party.
|
||||||
|
|
||||||
|
gs_zschweiz-mandt = sy-mandt.
|
||||||
|
gs_zschweiz-bukrs = <ls_billing>-bukrs.
|
||||||
|
gs_zschweiz-gjahr = p_gjahr.
|
||||||
|
gs_zschweiz-vbeln = <ls_billing>-vbeln.
|
||||||
|
gs_zschweiz-posnr = <ls_billing>-posnr.
|
||||||
|
gs_zschweiz-land1 = SWITCH #( <ls_billing>-bukrs WHEN '1100' THEN 'CH' WHEN '1200' THEN 'AT' ELSE <ls_billing>-customer_land ).
|
||||||
|
gs_zschweiz-customer_land = <ls_billing>-customer_land.
|
||||||
|
gs_zschweiz-tsc = SWITCH #( <ls_billing>-bukrs WHEN '1100' THEN 'TRCH' WHEN '1200' THEN 'TRAT' ELSE <ls_billing>-bukrs ).
|
||||||
|
gs_zschweiz-fkdat = <ls_billing>-fkdat.
|
||||||
|
gs_zschweiz-fkart = <ls_billing>-fkart.
|
||||||
|
gs_zschweiz-vbtyp = <ls_billing>-vbtyp.
|
||||||
|
gs_zschweiz-kunnr = <ls_billing>-kunag.
|
||||||
|
gs_zschweiz-name1 = <ls_billing>-name1.
|
||||||
|
gs_zschweiz-matnr = <ls_billing>-matnr.
|
||||||
|
gs_zschweiz-arktx = <ls_billing>-arktx.
|
||||||
|
gs_zschweiz-prodh = <ls_billing>-prodh.
|
||||||
|
gs_zschweiz-fkimg = <ls_billing>-fkimg * lv_sign.
|
||||||
|
gs_zschweiz-vrkme = <ls_billing>-vrkme.
|
||||||
|
gs_zschweiz-waerk = <ls_billing>-waerk.
|
||||||
|
gs_zschweiz-hwaer = <ls_billing>-hwaer.
|
||||||
|
gs_zschweiz-netwr_dc = <ls_billing>-netwr * lv_sign.
|
||||||
|
gs_zschweiz-tax_dc = <ls_billing>-mwsbp * lv_sign.
|
||||||
|
gs_zschweiz-netwr_hc = lv_netwr_hc * lv_sign.
|
||||||
|
gs_zschweiz-tax_hc = lv_tax_hc * lv_sign.
|
||||||
|
gs_zschweiz-kurrf = <ls_billing>-kurrf.
|
||||||
|
gs_zschweiz-is_credit = COND #( WHEN lv_sign < 0 THEN abap_true ELSE abap_false ).
|
||||||
|
gs_zschweiz-party_class = lv_party.
|
||||||
|
gs_zschweiz-erdat_src = <ls_billing>-erdat.
|
||||||
|
gs_zschweiz-aedat_src = sy-datum.
|
||||||
|
gs_zschweiz-created_at = lv_timestamp.
|
||||||
|
gs_zschweiz-changed_at = lv_timestamp.
|
||||||
|
gs_zschweiz-created_by = sy-uname.
|
||||||
|
gs_zschweiz-changed_by = sy-uname.
|
||||||
|
|
||||||
|
APPEND gs_zschweiz TO gt_zschweiz.
|
||||||
|
ENDLOOP.
|
||||||
|
|
||||||
|
WRITE: / 'Aufbereitete ZSCHWEIZ-Zeilen:', lines( gt_zschweiz ).
|
||||||
|
ENDFORM.
|
||||||
|
|
||||||
|
FORM convert_to_house_currency
|
||||||
|
USING iv_amount TYPE any
|
||||||
|
iv_from TYPE waerk
|
||||||
|
iv_to TYPE waers
|
||||||
|
iv_date TYPE fkdat
|
||||||
|
iv_kurrf TYPE kurrf
|
||||||
|
CHANGING cv_amount TYPE any.
|
||||||
|
|
||||||
|
IF iv_from = iv_to OR iv_from IS INITIAL OR iv_to IS INITIAL.
|
||||||
|
cv_amount = iv_amount.
|
||||||
|
RETURN.
|
||||||
|
ENDIF.
|
||||||
|
|
||||||
|
CALL FUNCTION 'CONVERT_TO_LOCAL_CURRENCY'
|
||||||
|
EXPORTING
|
||||||
|
date = iv_date
|
||||||
|
foreign_amount = iv_amount
|
||||||
|
foreign_currency = iv_from
|
||||||
|
local_currency = iv_to
|
||||||
|
rate = iv_kurrf
|
||||||
|
IMPORTING
|
||||||
|
local_amount = cv_amount
|
||||||
|
EXCEPTIONS
|
||||||
|
no_rate_found = 1
|
||||||
|
overflow = 2
|
||||||
|
no_factors_found = 3
|
||||||
|
no_spread_found = 4
|
||||||
|
derived_2_times = 5
|
||||||
|
OTHERS = 6.
|
||||||
|
|
||||||
|
IF sy-subrc <> 0.
|
||||||
|
"Fallback: Wenn SD bereits einen Rechnungsumrechnungskurs liefert,
|
||||||
|
"verwenden wir diesen, damit die Position nicht verloren geht.
|
||||||
|
IF iv_kurrf IS NOT INITIAL.
|
||||||
|
cv_amount = iv_amount * iv_kurrf.
|
||||||
|
ELSE.
|
||||||
|
cv_amount = 0.
|
||||||
|
ENDIF.
|
||||||
|
ENDIF.
|
||||||
|
ENDFORM.
|
||||||
|
|
||||||
|
FORM classify_party
|
||||||
|
USING iv_kunnr TYPE kunnr
|
||||||
|
iv_name1 TYPE name1_gp
|
||||||
|
CHANGING cv_party TYPE c.
|
||||||
|
|
||||||
|
DATA(lv_name) = to_upper( iv_name1 ).
|
||||||
|
|
||||||
|
IF lv_name CS 'TRAFAG'
|
||||||
|
OR lv_name CS 'MAGNETIC SENSE'
|
||||||
|
OR lv_name CS 'MAGNETS SENSE'
|
||||||
|
OR lv_name CS 'GESELLSCHAFT FUER SENSORIK'
|
||||||
|
OR lv_name CS 'GESELLSCHAFT FUR SENSORIK'.
|
||||||
|
cv_party = '2ND'.
|
||||||
|
ELSE.
|
||||||
|
cv_party = '3RD'.
|
||||||
|
ENDIF.
|
||||||
|
ENDFORM.
|
||||||
|
|
||||||
|
FORM persist_zschweiz.
|
||||||
|
IF p_test = abap_true.
|
||||||
|
WRITE: / 'Testlauf aktiv: keine Daten in ZSCHWEIZ geschrieben.'.
|
||||||
|
PERFORM write_totals.
|
||||||
|
RETURN.
|
||||||
|
ENDIF.
|
||||||
|
|
||||||
|
MODIFY zschweiz FROM TABLE gt_zschweiz.
|
||||||
|
|
||||||
|
IF sy-subrc = 0.
|
||||||
|
COMMIT WORK AND WAIT.
|
||||||
|
WRITE: / 'ZSCHWEIZ Upsert erfolgreich. Zeilen:', lines( gt_zschweiz ).
|
||||||
|
ELSE.
|
||||||
|
ROLLBACK WORK.
|
||||||
|
MESSAGE 'ZSCHWEIZ Upsert fehlgeschlagen' TYPE 'E'.
|
||||||
|
ENDIF.
|
||||||
|
|
||||||
|
PERFORM write_totals.
|
||||||
|
ENDFORM.
|
||||||
|
|
||||||
|
FORM write_totals.
|
||||||
|
TYPES: BEGIN OF ty_total,
|
||||||
|
land1 TYPE land1,
|
||||||
|
hwaer TYPE waers,
|
||||||
|
netwr_hc TYPE p LENGTH 23 DECIMALS 2,
|
||||||
|
tax_hc TYPE p LENGTH 23 DECIMALS 2,
|
||||||
|
rows TYPE i,
|
||||||
|
END OF ty_total.
|
||||||
|
|
||||||
|
DATA lt_totals TYPE HASHED TABLE OF ty_total WITH UNIQUE KEY land1 hwaer.
|
||||||
|
|
||||||
|
LOOP AT gt_zschweiz ASSIGNING FIELD-SYMBOL(<ls_fin>).
|
||||||
|
ASSIGN lt_totals[ land1 = <ls_fin>-land1 hwaer = <ls_fin>-hwaer ] TO FIELD-SYMBOL(<ls_total>).
|
||||||
|
IF sy-subrc <> 0.
|
||||||
|
INSERT VALUE #( land1 = <ls_fin>-land1 hwaer = <ls_fin>-hwaer ) INTO TABLE lt_totals ASSIGNING <ls_total>.
|
||||||
|
ENDIF.
|
||||||
|
|
||||||
|
<ls_total>-netwr_hc = <ls_total>-netwr_hc + <ls_fin>-netwr_hc.
|
||||||
|
<ls_total>-tax_hc = <ls_total>-tax_hc + <ls_fin>-tax_hc.
|
||||||
|
<ls_total>-rows = <ls_total>-rows + 1.
|
||||||
|
ENDLOOP.
|
||||||
|
|
||||||
|
SKIP.
|
||||||
|
WRITE: / 'Summen nach Land/Hauswaehrung'.
|
||||||
|
LOOP AT lt_totals ASSIGNING FIELD-SYMBOL(<ls_sum>).
|
||||||
|
WRITE: / <ls_sum>-land1,
|
||||||
|
<ls_sum>-hwaer,
|
||||||
|
'Netto:', <ls_sum>-netwr_hc,
|
||||||
|
'Steuer:', <ls_sum>-tax_hc,
|
||||||
|
'Zeilen:', <ls_sum>-rows.
|
||||||
|
ENDLOOP.
|
||||||
|
ENDFORM.
|
||||||
Reference in New Issue
Block a user