Add Alphaplan discovery exporter and finance docs

This commit is contained in:
2026-06-08 16:06:44 +02:00
parent d7e96edf14
commit 4ca8c31e6c
8 changed files with 1841 additions and 6 deletions
Binary file not shown.
@@ -0,0 +1,136 @@
Alphaplan SQL Discovery Exporter
================================
Purpose
-------
Run this package on the German Alphaplan SQL Server machine.
It performs Phase 1 discovery only:
- scan accessible SQL Server databases
- identify tables/views that look relevant for finance, invoices, sales, customers, articles and amounts
- write candidate_objects.csv
- write export_summary.csv
- optionally write small sample_*.csv files
- optionally upload the run folder to SharePoint with rclone
The script only reads SQL Server metadata/data. It does not change Alphaplan, SQL Server or BiDashboard.
Default SharePoint target
-------------------------
The default rclone target is:
trafag-bi:Import/Finance/Deutschland/AlphaplanRaw
Use this raw folder first so the existing Germany import is not disturbed.
Typical commands
----------------
Open PowerShell on the Alphaplan server in this package folder.
Allow script execution for this PowerShell window:
Set-ExecutionPolicy -Scope Process Bypass
Run discovery and upload with defaults:
.\Run-AlphaplanDiscoveryAndUpload.ps1
Run discovery for one known database:
.\Run-AlphaplanDiscoveryAndUpload.ps1 -Database "ALPHAPLAN"
Run discovery without SharePoint upload:
.\Run-AlphaplanDiscoveryAndUpload.ps1 -SkipUpload
Run discovery and include small samples from top candidate tables/views:
.\Run-AlphaplanDiscoveryAndUpload.ps1 -Database "ALPHAPLAN" -ExportSamples
Use another SQL Server instance:
.\Run-AlphaplanDiscoveryAndUpload.ps1 -ServerInstance "SERVERNAME\INSTANCE" -Database "ALPHAPLAN"
Use SQL authentication:
$cred = Get-Credential
.\Run-AlphaplanDiscoveryAndUpload.ps1 -ServerInstance "SERVERNAME\INSTANCE" -Database "ALPHAPLAN" -SqlCredential $cred
Use another rclone remote name:
.\Run-AlphaplanDiscoveryAndUpload.ps1 -RcloneRemote "YOUR_REMOTE"
Use another rclone executable:
.\Run-AlphaplanDiscoveryAndUpload.ps1 -RcloneExe "C:\Tools\rclone\rclone.exe"
Output
------
Default local folder:
C:\Trafag\AlphaplanExport\out\Alphaplan_SQL_Discovery_YYYYMMDD_HHMMSS
Main files:
- candidate_objects.csv
- export_summary.csv
- README.txt
- sample_*.csv when -ExportSamples is used
rclone prerequisites
--------------------
rclone must already be configured on the Alphaplan server.
Expected remote:
trafag-bi
The remote should point to the "Shared Documents" document library of:
https://trafagag.sharepoint.com/sites/WorldwideBIPlatform
Quick rclone checks:
rclone lsd trafag-bi:
rclone lsd trafag-bi:"Import/Finance"
rclone lsd trafag-bi:"Import/Finance/Deutschland"
Recommended Phase 1 workflow
----------------------------
1. Run discovery without samples:
.\Run-AlphaplanDiscoveryAndUpload.ps1 -SkipUpload
2. Check candidate_objects.csv locally.
3. If the result looks plausible, run with upload:
.\Run-AlphaplanDiscoveryAndUpload.ps1
4. If Andreas/DE IT needs examples, run samples:
.\Run-AlphaplanDiscoveryAndUpload.ps1 -ExportSamples
5. Use candidate_objects.csv to identify the correct invoice header, invoice line, customer, article and credit note/storno objects.
Notes
-----
- If -Database is empty, all accessible user databases are scanned.
- If the SQL user has limited permissions, candidate_objects.csv may be empty or incomplete.
- Use a read-only SQL user or Windows account.
- For Phase 1, no BiDashboard import mapping is required.
@@ -0,0 +1,584 @@
param(
[string]$ServerInstance = "localhost",
[string]$Database = "",
[System.Management.Automation.PSCredential]$SqlCredential,
[string]$BaseDirectory = "C:\Trafag\AlphaplanExport",
[int]$MaxCandidatesPerDatabase = 80,
[switch]$ExportSamples,
[int]$MaxSampleObjects = 15,
[int]$SampleRows = 200,
[switch]$IncludeSystemDatabases,
[switch]$SkipUpload,
[string]$RcloneExe = "C:\Tools\rclone.exe",
[string]$RcloneRemote = "trafag-bi",
[string]$RcloneTarget = "Import/Finance/Deutschland/AlphaplanRaw"
)
$ErrorActionPreference = "Stop"
function New-Connection {
param([string]$DbName)
$builder = New-Object System.Data.SqlClient.SqlConnectionStringBuilder
$builder["Data Source"] = $ServerInstance
$builder["Initial Catalog"] = $DbName
$builder["TrustServerCertificate"] = $true
$builder["Connect Timeout"] = 15
if ($null -ne $SqlCredential) {
$builder["Integrated Security"] = $false
$builder["User ID"] = $SqlCredential.UserName
$builder["Password"] = $SqlCredential.GetNetworkCredential().Password
}
else {
$builder["Integrated Security"] = $true
}
return New-Object System.Data.SqlClient.SqlConnection($builder.ConnectionString)
}
function Add-SqlParameter {
param(
[System.Data.SqlClient.SqlCommand]$Command,
[string]$Name,
$Value
)
if ($Value -is [int]) {
$parameter = $Command.Parameters.Add("@$Name", [System.Data.SqlDbType]::Int)
$parameter.Value = $Value
return
}
if ($Value -is [datetime]) {
$parameter = $Command.Parameters.Add("@$Name", [System.Data.SqlDbType]::DateTime)
$parameter.Value = $Value
return
}
$textParameter = $Command.Parameters.Add("@$Name", [System.Data.SqlDbType]::NVarChar, 4000)
if ($null -eq $Value) {
$textParameter.Value = [System.DBNull]::Value
}
else {
$textParameter.Value = [string]$Value
}
}
function Invoke-DataTable {
param(
[string]$DbName,
[string]$Sql,
[hashtable]$Parameters = @{}
)
$conn = New-Connection $DbName
$cmd = $conn.CreateCommand()
$cmd.CommandText = $Sql
$cmd.CommandTimeout = 300
foreach ($key in $Parameters.Keys) {
Add-SqlParameter -Command $cmd -Name $key -Value $Parameters[$key]
}
$table = New-Object System.Data.DataTable
try {
$conn.Open()
$reader = $cmd.ExecuteReader()
$table.Load($reader)
$reader.Dispose()
}
finally {
$cmd.Dispose()
$conn.Dispose()
}
return $table
}
function Convert-DataRowToObject {
param([System.Data.DataRow]$Row)
$props = [ordered]@{}
foreach ($column in $Row.Table.Columns) {
$value = $Row[$column.ColumnName]
if ($null -eq $value -or $value -is [System.DBNull]) {
$props[$column.ColumnName] = ""
}
else {
$props[$column.ColumnName] = $value
}
}
return [pscustomobject]$props
}
function Convert-ToCsvValue {
param($Value)
if ($null -eq $Value -or $Value -is [System.DBNull]) {
return ""
}
if ($Value -is [datetime]) {
$text = $Value.ToString("yyyy-MM-dd HH:mm:ss")
}
else {
$text = [string]$Value
}
$text = $text.Replace('"', '""')
return '"' + $text + '"'
}
function Export-QueryToCsv {
param(
[string]$DbName,
[string]$Sql,
[string]$Path
)
$conn = New-Connection $DbName
$cmd = $conn.CreateCommand()
$cmd.CommandText = $Sql
$cmd.CommandTimeout = 0
$writer = New-Object System.IO.StreamWriter($Path, $false, [System.Text.Encoding]::UTF8)
$rowCount = 0
try {
$conn.Open()
$reader = $cmd.ExecuteReader()
$headers = for ($i = 0; $i -lt $reader.FieldCount; $i++) {
Convert-ToCsvValue $reader.GetName($i)
}
$writer.WriteLine(($headers -join ";"))
while ($reader.Read()) {
$values = for ($i = 0; $i -lt $reader.FieldCount; $i++) {
Convert-ToCsvValue $reader.GetValue($i)
}
$writer.WriteLine(($values -join ";"))
$rowCount++
}
$reader.Dispose()
}
finally {
$writer.Dispose()
$cmd.Dispose()
$conn.Dispose()
}
return $rowCount
}
function Quote-NamePart {
param([string]$Name)
return "[" + $Name.Replace("]", "]]") + "]"
}
function Normalize-FileName {
param([string]$Value)
$safe = ($Value -replace '[\\/:*?"<>|]', '_')
if ($safe.Length -gt 150) {
return $safe.Substring(0, 150)
}
return $safe
}
function Get-UserDatabases {
$systemFilter = if ($IncludeSystemDatabases) { "" } else { "AND database_id > 4" }
$sql = @"
SELECT name
FROM sys.databases
WHERE state_desc = 'ONLINE'
AND HAS_DBACCESS(name) = 1
$systemFilter
ORDER BY name;
"@
$table = Invoke-DataTable "master" $sql
$result = New-Object System.Collections.Generic.List[string]
foreach ($row in $table.Rows) {
$result.Add([string]$row["name"])
}
return @($result)
}
function Get-CandidateObjects {
param([string]$DbName)
$sql = @"
WITH object_columns AS (
SELECT
o.object_id AS ObjectId,
s.name AS SchemaName,
o.name AS ObjectName,
o.type_desc AS ObjectType,
c.name AS ColumnName,
t.name AS TypeName,
c.column_id AS ColumnId
FROM sys.objects o
JOIN sys.schemas s ON s.schema_id = o.schema_id
JOIN sys.columns c ON c.object_id = o.object_id
JOIN sys.types t ON t.user_type_id = c.user_type_id
WHERE o.type IN ('U', 'V')
AND o.is_ms_shipped = 0
),
scored AS (
SELECT
ObjectId,
SchemaName,
ObjectName,
ObjectType,
COUNT(*) AS ColumnCount,
SUM(
CASE WHEN LOWER(ObjectName) LIKE '%rechnung%' OR LOWER(ObjectName) LIKE '%fakt%' OR LOWER(ObjectName) LIKE '%invoice%' OR LOWER(ObjectName) LIKE '%fact%' OR LOWER(ObjectName) LIKE '%beleg%' THEN 8 ELSE 0 END +
CASE WHEN LOWER(ObjectName) LIKE '%umsatz%' OR LOWER(ObjectName) LIKE '%verkauf%' OR LOWER(ObjectName) LIKE '%sales%' OR LOWER(ObjectName) LIKE '%revenue%' THEN 6 ELSE 0 END +
CASE WHEN LOWER(ObjectName) LIKE '%position%' OR LOWER(ObjectName) LIKE '%zeile%' OR LOWER(ObjectName) LIKE '%line%' THEN 4 ELSE 0 END +
CASE WHEN LOWER(ObjectName) LIKE '%auftrag%' OR LOWER(ObjectName) LIKE '%order%' THEN 4 ELSE 0 END +
CASE WHEN LOWER(ObjectName) LIKE '%artikel%' OR LOWER(ObjectName) LIKE '%material%' OR LOWER(ObjectName) LIKE '%item%' OR LOWER(ObjectName) LIKE '%produkt%' THEN 5 ELSE 0 END +
CASE WHEN LOWER(ObjectName) LIKE '%kunde%' OR LOWER(ObjectName) LIKE '%debitor%' OR LOWER(ObjectName) LIKE '%customer%' OR LOWER(ObjectName) LIKE '%adresse%' THEN 4 ELSE 0 END +
CASE WHEN LOWER(ObjectName) LIKE '%gutschrift%' OR LOWER(ObjectName) LIKE '%storno%' OR LOWER(ObjectName) LIKE '%credit%' THEN 4 ELSE 0 END +
CASE WHEN LOWER(ColumnName) LIKE '%datum%' OR LOWER(ColumnName) LIKE '%date%' OR LOWER(ColumnName) LIKE '%zeit%' THEN 2 ELSE 0 END +
CASE WHEN LOWER(ColumnName) LIKE '%rechnung%' OR LOWER(ColumnName) LIKE '%fakt%' OR LOWER(ColumnName) LIKE '%invoice%' OR LOWER(ColumnName) LIKE '%beleg%' THEN 2 ELSE 0 END +
CASE WHEN LOWER(ColumnName) LIKE '%kunde%' OR LOWER(ColumnName) LIKE '%debitor%' OR LOWER(ColumnName) LIKE '%customer%' OR LOWER(ColumnName) LIKE '%adresse%' THEN 2 ELSE 0 END +
CASE WHEN LOWER(ColumnName) LIKE '%artikel%' OR LOWER(ColumnName) LIKE '%material%' OR LOWER(ColumnName) LIKE '%item%' OR LOWER(ColumnName) LIKE '%produkt%' OR LOWER(ColumnName) LIKE '%artnr%' THEN 2 ELSE 0 END +
CASE WHEN LOWER(ColumnName) LIKE '%betrag%' OR LOWER(ColumnName) LIKE '%netto%' OR LOWER(ColumnName) LIKE '%umsatz%' OR LOWER(ColumnName) LIKE '%amount%' OR LOWER(ColumnName) LIKE '%price%' OR LOWER(ColumnName) LIKE '%preis%' OR LOWER(ColumnName) LIKE '%summe%' THEN 3 ELSE 0 END +
CASE WHEN LOWER(ColumnName) LIKE '%menge%' OR LOWER(ColumnName) LIKE '%anzahl%' OR LOWER(ColumnName) LIKE '%quantity%' OR LOWER(ColumnName) LIKE '%qty%' THEN 2 ELSE 0 END +
CASE WHEN LOWER(ColumnName) LIKE '%waehr%' OR LOWER(ColumnName) LIKE '%currency%' OR LOWER(ColumnName) LIKE '%whrg%' THEN 1 ELSE 0 END +
CASE WHEN LOWER(ColumnName) LIKE '%warengruppe%' OR LOWER(ColumnName) LIKE '%produktgruppe%' OR LOWER(ColumnName) LIKE '%productgroup%' THEN 1 ELSE 0 END
) AS Score
FROM object_columns
GROUP BY ObjectId, SchemaName, ObjectName, ObjectType
)
SELECT TOP (@MaxCandidates)
DB_NAME() AS DatabaseName,
s.SchemaName,
s.ObjectName,
s.ObjectType,
s.Score,
s.ColumnCount,
ISNULL((
SELECT SUM(p.row_count)
FROM sys.dm_db_partition_stats p
WHERE p.object_id = s.ObjectId
AND p.index_id IN (0, 1)
), 0) AS RowCountEstimate,
STUFF((
SELECT ', ' + oc.ColumnName
FROM object_columns oc
WHERE oc.ObjectId = s.ObjectId
AND (
oc.TypeName IN ('date', 'datetime', 'datetime2', 'smalldatetime')
OR LOWER(oc.ColumnName) LIKE '%datum%'
OR LOWER(oc.ColumnName) LIKE '%date%'
OR LOWER(oc.ColumnName) LIKE '%zeit%'
)
ORDER BY oc.ColumnId
FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'), 1, 2, '') AS DateColumnCandidates,
STUFF((
SELECT ', ' + oc.ColumnName
FROM object_columns oc
WHERE oc.ObjectId = s.ObjectId
AND (
LOWER(oc.ColumnName) LIKE '%betrag%'
OR LOWER(oc.ColumnName) LIKE '%netto%'
OR LOWER(oc.ColumnName) LIKE '%umsatz%'
OR LOWER(oc.ColumnName) LIKE '%amount%'
OR LOWER(oc.ColumnName) LIKE '%price%'
OR LOWER(oc.ColumnName) LIKE '%preis%'
OR LOWER(oc.ColumnName) LIKE '%summe%'
)
ORDER BY oc.ColumnId
FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'), 1, 2, '') AS AmountColumnCandidates,
STUFF((
SELECT ', ' + oc.ColumnName
FROM object_columns oc
WHERE oc.ObjectId = s.ObjectId
AND (
LOWER(oc.ColumnName) LIKE '%rechnung%'
OR LOWER(oc.ColumnName) LIKE '%fakt%'
OR LOWER(oc.ColumnName) LIKE '%beleg%'
OR LOWER(oc.ColumnName) LIKE '%kunde%'
OR LOWER(oc.ColumnName) LIKE '%debitor%'
OR LOWER(oc.ColumnName) LIKE '%artikel%'
OR LOWER(oc.ColumnName) LIKE '%material%'
OR LOWER(oc.ColumnName) LIKE '%position%'
)
ORDER BY oc.ColumnId
FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'), 1, 2, '') AS KeyColumnCandidates,
STUFF((
SELECT ', ' + oc.ColumnName
FROM object_columns oc
WHERE oc.ObjectId = s.ObjectId
ORDER BY oc.ColumnId
FOR XML PATH(''), TYPE
).value('.', 'nvarchar(max)'), 1, 2, '') AS Columns
FROM scored s
WHERE s.Score > 0
ORDER BY s.Score DESC, RowCountEstimate DESC, s.ObjectName;
"@
$table = Invoke-DataTable $DbName $sql @{ MaxCandidates = $MaxCandidatesPerDatabase }
$result = New-Object System.Collections.Generic.List[object]
foreach ($row in $table.Rows) {
$result.Add((Convert-DataRowToObject $row))
}
return @($result)
}
function Build-SampleSql {
param(
[string]$SchemaName,
[string]$ObjectName,
[int]$Rows
)
$safeRows = [Math]::Max(1, $Rows)
$qualifiedName = "$(Quote-NamePart $SchemaName).$(Quote-NamePart $ObjectName)"
return "SELECT TOP ($safeRows) * FROM $qualifiedName;"
}
function Resolve-RcloneExecutable {
param([string]$ConfiguredPath)
$scriptDirectory = Split-Path -Parent $MyInvocation.MyCommand.Path
$candidates = @(
$ConfiguredPath,
(Join-Path $scriptDirectory "rclone.exe"),
"C:\Tools\rclone.exe",
"C:\Tools\rclone\rclone.exe",
"C:\Tools\rclone\rclone\rclone.exe",
"rclone"
) | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }
foreach ($candidate in $candidates) {
if (Test-Path -LiteralPath $candidate) {
return (Resolve-Path -LiteralPath $candidate).Path
}
$command = Get-Command $candidate -ErrorAction SilentlyContinue
if ($null -ne $command) {
return $command.Source
}
}
throw "rclone executable not found. Checked: $($candidates -join ', ')"
}
function Throw-RcloneError {
param(
[string]$Message,
[string]$LogPath
)
if (Test-Path -LiteralPath $LogPath) {
Write-Host ""
Write-Host "Last rclone log lines:"
Get-Content -LiteralPath $LogPath -Tail 80 | ForEach-Object { Write-Host $_ }
}
throw "$Message Log: $LogPath"
}
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$outputDirectory = Join-Path $BaseDirectory "out"
$logDirectory = Join-Path $BaseDirectory "logs"
New-Item -ItemType Directory -Force -Path $outputDirectory, $logDirectory | Out-Null
$runDirectory = Join-Path $outputDirectory "Alphaplan_SQL_Discovery_$timestamp"
New-Item -ItemType Directory -Path $runDirectory -Force | Out-Null
$summary = New-Object System.Collections.Generic.List[object]
$allCandidates = New-Object System.Collections.Generic.List[object]
Write-Host "Alphaplan SQL discovery"
Write-Host "Server instance: $ServerInstance"
Write-Host "Database filter: $(if ([string]::IsNullOrWhiteSpace($Database)) { '(all accessible user databases)' } else { $Database })"
Write-Host "Run directory: $runDirectory"
$databases = if ([string]::IsNullOrWhiteSpace($Database)) {
@(Get-UserDatabases)
}
else {
@($Database)
}
if ($databases.Count -eq 0) {
throw "No accessible databases found."
}
foreach ($db in $databases) {
Write-Host "Scanning database: $db"
try {
$candidates = @(Get-CandidateObjects $db)
foreach ($candidate in $candidates) {
$allCandidates.Add($candidate)
}
$summary.Add([pscustomobject]@{
Created = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
Database = $db
Object = ""
Action = "Discovery completed"
Rows = $candidates.Count
File = "candidate_objects.csv"
Error = ""
})
}
catch {
$summary.Add([pscustomobject]@{
Created = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
Database = $db
Object = ""
Action = "Discovery failed"
Rows = 0
File = ""
Error = $_.Exception.Message
})
}
}
$candidatePath = Join-Path $runDirectory "candidate_objects.csv"
if ($allCandidates.Count -gt 0) {
$allCandidates |
Sort-Object DatabaseName, @{ Expression = "Score"; Descending = $true }, ObjectName |
Export-Csv -LiteralPath $candidatePath -NoTypeInformation -Encoding UTF8 -Delimiter ";"
}
else {
"" | Set-Content -LiteralPath $candidatePath -Encoding UTF8
}
if ($ExportSamples -and $allCandidates.Count -gt 0) {
$sampleObjects = @(
$allCandidates |
Sort-Object @{ Expression = "Score"; Descending = $true }, DatabaseName, ObjectName |
Select-Object -First $MaxSampleObjects
)
foreach ($candidate in $sampleObjects) {
$db = [string]$candidate.DatabaseName
$schema = [string]$candidate.SchemaName
$objectName = [string]$candidate.ObjectName
$fileName = Normalize-FileName "sample_$db.$schema.$objectName.csv"
$samplePath = Join-Path $runDirectory $fileName
try {
Write-Host "Exporting sample: $db.$schema.$objectName"
$sql = Build-SampleSql -SchemaName $schema -ObjectName $objectName -Rows $SampleRows
$rows = Export-QueryToCsv -DbName $db -Sql $sql -Path $samplePath
$summary.Add([pscustomobject]@{
Created = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
Database = $db
Object = "$schema.$objectName"
Action = "Sample exported"
Rows = $rows
File = $fileName
Error = ""
})
}
catch {
$summary.Add([pscustomobject]@{
Created = (Get-Date -Format "yyyy-MM-dd HH:mm:ss")
Database = $db
Object = "$schema.$objectName"
Action = "Sample export failed"
Rows = 0
File = $fileName
Error = $_.Exception.Message
})
}
}
}
$summaryPath = Join-Path $runDirectory "export_summary.csv"
$summary | Export-Csv -LiteralPath $summaryPath -NoTypeInformation -Encoding UTF8 -Delimiter ";"
$readmePath = Join-Path $runDirectory "README.txt"
@"
Alphaplan SQL discovery export
==============================
Created: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
Server instance: $ServerInstance
Database filter: $(if ([string]::IsNullOrWhiteSpace($Database)) { "(all accessible user databases)" } else { $Database })
Export samples: $ExportSamples
Sample rows: $SampleRows
Files:
- candidate_objects.csv: SQL tables/views that look relevant for finance, invoices, sales, customers, articles or amounts.
- export_summary.csv: discovery and optional sample export status.
- sample_*.csv: optional small samples from top candidate objects when -ExportSamples is used.
Important:
- The script only reads SQL Server metadata and data.
- It does not change Alphaplan or SQL Server.
- Sample exports are limited with SELECT TOP.
- This is Phase 1 only. The BiDashboard import mapping is a separate step.
Recommended next step:
Send candidate_objects.csv and export_summary.csv to the Alphaplan/DE key user or IT.
They should identify the correct invoice header, invoice line, customer, article and credit note/storno objects.
"@ | Set-Content -LiteralPath $readmePath -Encoding UTF8
if (-not $SkipUpload) {
$resolvedRclone = Resolve-RcloneExecutable -ConfiguredPath $RcloneExe
$target = "${RcloneRemote}:$RcloneTarget"
$rcloneLog = Join-Path $logDirectory ("rclone-alphaplan-discovery-" + (Get-Date -Format "yyyyMMdd") + ".log")
Write-Host "Using rclone: $resolvedRclone"
Write-Host "Checking SharePoint target: $target"
& $resolvedRclone mkdir $target --log-file $rcloneLog --log-level INFO
if ($LASTEXITCODE -ne 0) {
Throw-RcloneError -Message "Could not create/check SharePoint target '$target'. rclone exit code $LASTEXITCODE." -LogPath $rcloneLog
}
$targetListing = & $resolvedRclone lsf $target --max-depth 1 --log-file $rcloneLog --log-level INFO
if ($LASTEXITCODE -ne 0) {
Throw-RcloneError -Message "SharePoint target '$target' is not reachable. rclone exit code $LASTEXITCODE." -LogPath $rcloneLog
}
Write-Host "SharePoint target reachable. Existing items: $(@($targetListing).Count)"
Write-Host "Uploading discovery folder to SharePoint target: $target"
& $resolvedRclone copy $runDirectory $target `
--include "*.csv" `
--include "*.txt" `
--log-file $rcloneLog `
--log-level INFO
if ($LASTEXITCODE -ne 0) {
Throw-RcloneError -Message "rclone upload failed with exit code $LASTEXITCODE." -LogPath $rcloneLog
}
$uploadedCandidate = & $resolvedRclone lsf $target --files-only --include "candidate_objects.csv" --log-file $rcloneLog --log-level INFO
if ($LASTEXITCODE -ne 0 -or -not ($uploadedCandidate | Where-Object { $_ -eq "candidate_objects.csv" })) {
Throw-RcloneError -Message "Upload verification failed. candidate_objects.csv was not listed in '$target'." -LogPath $rcloneLog
}
Write-Host "Upload verified."
Write-Host "rclone log: $rcloneLog"
}
else {
Write-Host "Upload skipped because -SkipUpload was used."
}
Write-Host ""
Write-Host "Alphaplan discovery finished."
Write-Host "Local export: $runDirectory"
Write-Host "Candidates: $candidatePath"
Write-Host "Summary: $summaryPath"
Write-Host "Candidate count: $($allCandidates.Count)"
Write-Host "Upload target: $(if ($SkipUpload) { '(skipped)' } else { "${RcloneRemote}:$RcloneTarget" })"
@@ -441,6 +441,14 @@
</MudItem> </MudItem>
</MudGrid> </MudGrid>
@if (IsProductFinanceMixedCurrency)
{
<MudAlert Severity="Severity.Warning" Dense Variant="Variant.Outlined" Class="mb-4">
@T("Diese Sparten-Finanzanalyse enthaelt mehrere Waehrungen. Die Summary-Karten mit `Mixed` addieren lokale Werte numerisch; fuer belastbare Prozentwerte bitte eine einzelne Waehrung oder ein einzelnes Land filtern.",
"This division finance analysis contains multiple currencies. Summary cards marked `Mixed` add local values numerically; filter to one currency or one country for reliable percentages.")
</MudAlert>
}
<MudPaper Class="pa-4 mb-4" Elevation="1"> <MudPaper Class="pa-4 mb-4" Elevation="1">
<MudGrid Class="mb-2"> <MudGrid Class="mb-2">
<MudItem xs="12" md="6"> <MudItem xs="12" md="6">
@@ -507,6 +515,37 @@
</MudTable> </MudTable>
</MudPaper> </MudPaper>
<MudPaper Class="pa-4 mb-4" Elevation="1">
<MudText Typo="Typo.h6" Class="mb-2">@T("Groesste Treiber: Nicht im Stamm", "Top drivers: not in master")</MudText>
<MudTable Items="BuildMissingReferenceDriverRows()" Dense Hover Striped>
<HeaderContent>
<MudTh>@T("Land", "Country")</MudTh>
<MudTh>TSC</MudTh>
<MudTh>@T("Material", "Material")</MudTh>
<MudTh>@T("Text", "Text")</MudTh>
<MudTh>@T("Umsatz", "Sales")</MudTh>
<MudTh>@T("Zeilen", "Rows")</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd>@FormatCountryWithFlag(context.CountryKey)</MudTd>
<MudTd>@context.Tsc</MudTd>
<MudTd>@context.Material</MudTd>
<MudTd>
<MudTooltip Text="@context.ArticleName">
<MudText Typo="Typo.caption" Style="max-width:520px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; display:block;">
@context.ArticleName
</MudText>
</MudTooltip>
</MudTd>
<MudTd>@FormatValue(context.NetSalesActual, context.Currency)</MudTd>
<MudTd>@context.RowCount.ToString("N0")</MudTd>
</RowTemplate>
<NoRecordsContent>
<MudText Typo="Typo.body2">@T("Keine fehlenden TR-AG-Referenzen fuer diese Filter.", "No missing TR AG references for these filters.")</MudText>
</NoRecordsContent>
</MudTable>
</MudPaper>
<MudPaper Class="pa-4" Elevation="1"> <MudPaper Class="pa-4" Elevation="1">
<MudText Typo="Typo.h6" Class="mb-2">@T("Umsatzabdeckung nach Land", "Sales coverage by country")</MudText> <MudText Typo="Typo.h6" Class="mb-2">@T("Umsatzabdeckung nach Land", "Sales coverage by country")</MudText>
<MudTable Items="_financeResult.ProductFinanceCountryRows" Dense Hover Striped> <MudTable Items="_financeResult.ProductFinanceCountryRows" Dense Hover Striped>
@@ -1093,9 +1132,9 @@
]; ];
private readonly List<ProductFinanceGroupingOption> _productFinanceGroupingOptions = private readonly List<ProductFinanceGroupingOption> _productFinanceGroupingOptions =
[ [
new(ProductFinanceGroupLevels.Hierarchy, "PAPH1 Detail", "PAPH1 detail"), new(ProductFinanceGroupLevels.Division, "Produktsparte", "Product division"),
new(ProductFinanceGroupLevels.Family, "Produktfamilie", "Product family"), new(ProductFinanceGroupLevels.Family, "Produktfamilie", "Product family"),
new(ProductFinanceGroupLevels.Division, "Produktsparte", "Product division") new(ProductFinanceGroupLevels.Hierarchy, "PAPH1 Detail", "PAPH1 detail")
]; ];
private readonly List<Finance3dIndicatorOption> _finance3dIndicatorOptions = private readonly List<Finance3dIndicatorOption> _finance3dIndicatorOptions =
[ [
@@ -1145,7 +1184,7 @@
private int _activeOverviewTabIndex; private int _activeOverviewTabIndex;
private int _activeFinanceTabIndex; private int _activeFinanceTabIndex;
private int _activeDivisionTabIndex; private int _activeDivisionTabIndex;
private string _productFinanceGroupLevel = ProductFinanceGroupLevels.Hierarchy; private string _productFinanceGroupLevel = ProductFinanceGroupLevels.Division;
private bool _limitProductFinanceTop10; private bool _limitProductFinanceTop10;
private string _finance3dIndicator = Finance3dIndicators.Actual; private string _finance3dIndicator = Finance3dIndicators.Actual;
private string _finance3dChartType = Finance3dChartTypes.Bar; private string _finance3dChartType = Finance3dChartTypes.Bar;
@@ -1513,6 +1552,23 @@
? T(option.GermanLabel, option.EnglishLabel) ? T(option.GermanLabel, option.EnglishLabel)
: T("Net Sales Actual", "Net sales actual"); : T("Net Sales Actual", "Net sales actual");
private bool IsProductFinanceMixedCurrency
=> string.Equals(_financeResult?.ProductFinanceSummary.DisplayCurrency, "Mixed", StringComparison.OrdinalIgnoreCase);
private IReadOnlyList<ManagementProductAssignmentRow> BuildMissingReferenceDriverRows()
{
if (_financeResult is null)
return [];
return _financeResult.ProductAssignmentRows
.Where(row => string.Equals(row.Status, "Nicht im TR-AG-Stamm", StringComparison.OrdinalIgnoreCase))
.OrderByDescending(row => Math.Abs(row.NetSalesActual))
.ThenBy(row => row.CountryKey, StringComparer.OrdinalIgnoreCase)
.ThenBy(row => row.Material, StringComparer.OrdinalIgnoreCase)
.Take(15)
.ToList();
}
private IReadOnlyList<ManagementProductDivisionFinanceRow> BuildProductFinanceRows() private IReadOnlyList<ManagementProductDivisionFinanceRow> BuildProductFinanceRows()
{ {
if (_financeResult is null) if (_financeResult is null)
@@ -0,0 +1,270 @@
# Alphaplan Discovery Exporter Guide
Stand: 2026-06-08
Zweck: Diese Anleitung dokumentiert das Phase-1-Paket fuer Deutschland/Alphaplan. Das Paket soll auf dem deutschen Alphaplan-/SQL-Server laufen, relevante SQL-Datenbanken, Tabellen und Views finden, CSV-Dateien erzeugen und diese optional per `rclone` nach SharePoint laden.
## Status
- Paket erstellt: `AlphaplanExportPackage`.
- ZIP erstellt: `AlphaplanExportPackage.zip`.
- Phase: Discovery / Rohdatenanalyse.
- BiDashboard-Import wird dadurch noch nicht angepasst.
- BiDashboard-App-Code wird fuer diesen Exporter nicht benoetigt.
- PowerShell-Syntax des Scripts wurde lokal geprueft.
## Dateien
| Datei | Zweck |
| --- | --- |
| `AlphaplanExportPackage/Run-AlphaplanDiscoveryAndUpload.ps1` | All-in-one Discovery, optional Samples, optional rclone Upload |
| `AlphaplanExportPackage/README.txt` | Kurzanleitung fuer den DE-Server |
| `AlphaplanExportPackage.zip` | Kopierbares Paket fuer den DE-Server |
| `docs/ALPHAPLAN_SQL_RCLONE_KONZEPT_DE_2026-06-08.md` | Fach-/Technikkonzept |
| `docs/ALPHAPLAN_DISCOVERY_EXPORTER_GUIDE_2026-06-08.md` | Diese Nachdokumentation |
## Zielbild
Der Export laeuft direkt auf dem Alphaplan-Server in Deutschland:
1. PowerShell startet das Script.
2. Script verbindet sich read-only mit SQL Server.
3. Script scannt SQL-Datenbanken, Tabellen und Views.
4. Script bewertet Kandidaten anhand von Namen und Spalten.
5. Script schreibt `candidate_objects.csv`.
6. Script schreibt `export_summary.csv`.
7. Optional schreibt es kleine `sample_*.csv`.
8. Optional laedt `rclone` die Dateien nach SharePoint.
## SharePoint-Ziel
Default:
```text
trafag-bi:Import/Finance/Deutschland/AlphaplanRaw
```
Dieser Rohdatenordner ist bewusst getrennt vom produktiven Deutschland-Import. So koennen die Discovery-CSV nicht versehentlich vom bestehenden Import als finale Finance-Dateien gelesen werden.
## Lokaler Zielordner auf dem DE-Server
Default:
```text
C:\Trafag\AlphaplanExport
```
Unterordner:
```text
C:\Trafag\AlphaplanExport\out
C:\Trafag\AlphaplanExport\logs
```
Pro Lauf entsteht:
```text
C:\Trafag\AlphaplanExport\out\Alphaplan_SQL_Discovery_YYYYMMDD_HHMMSS
```
## Ergebnisdateien
| Datei | Inhalt |
| --- | --- |
| `candidate_objects.csv` | Relevante SQL-Tabellen/Views mit Score, Spalten, Datums-/Betrags-/Key-Kandidaten |
| `export_summary.csv` | Status pro Datenbank und optional pro Sample-Export |
| `README.txt` | Laufbezogene Kurzbeschreibung |
| `sample_*.csv` | Optionale kleine Beispielauszuege aus Top-Kandidaten |
## Bewertung der Kandidaten
Das Script bewertet Tabellen/Views ueber Objekt- und Spaltennamen. Treffer gibt es u. a. fuer:
- Rechnung, Faktura, Invoice, Beleg
- Umsatz, Verkauf, Sales, Revenue
- Position, Zeile, Line
- Auftrag, Order
- Kunde, Debitor, Customer, Adresse
- Artikel, Material, Item, Produkt
- Betrag, Netto, Umsatz, Amount, Price, Preis, Summe
- Menge, Anzahl, Quantity, Qty
- Waehrung, Currency
- Warengruppe, Produktgruppe
- Gutschrift, Storno, Credit
Die Discovery ist kein finaler Beweis, sondern eine Vorauswahl fuer DE/IT und Finance.
## Wichtige Parameter
| Parameter | Default | Zweck |
| --- | --- | --- |
| `-ServerInstance` | `localhost` | SQL Server / Instanz |
| `-Database` | leer | leer = alle erreichbaren User-Datenbanken scannen |
| `-SqlCredential` | leer | optional SQL-Login statt Windows Integrated |
| `-BaseDirectory` | `C:\Trafag\AlphaplanExport` | lokaler Arbeitsordner |
| `-MaxCandidatesPerDatabase` | `80` | maximale Kandidaten je Datenbank |
| `-ExportSamples` | aus | kleine Beispiel-CSV erzeugen |
| `-MaxSampleObjects` | `15` | maximale Sample-Objekte |
| `-SampleRows` | `200` | Zeilen pro Sample |
| `-SkipUpload` | aus | nur lokal erzeugen, kein rclone Upload |
| `-RcloneExe` | `C:\Tools\rclone.exe` | bevorzugter rclone Pfad |
| `-RcloneRemote` | `trafag-bi` | rclone Remote |
| `-RcloneTarget` | `Import/Finance/Deutschland/AlphaplanRaw` | SharePoint-Zielpfad |
## Standardlauf lokal ohne Upload
Auf dem DE-Server im Paketordner:
```powershell
Set-ExecutionPolicy -Scope Process Bypass
.\Run-AlphaplanDiscoveryAndUpload.ps1 -SkipUpload
```
Das ist der empfohlene erste Test, weil keine SharePoint-/rclone-Abhaengigkeit geprueft werden muss.
## Standardlauf mit Upload
```powershell
Set-ExecutionPolicy -Scope Process Bypass
.\Run-AlphaplanDiscoveryAndUpload.ps1
```
Das Script prueft/erstellt das SharePoint-Ziel mit `rclone mkdir`, laedt CSV/TXT hoch und verifiziert danach `candidate_objects.csv` per `rclone lsf`.
## Lauf fuer bekannte Datenbank
Wenn der Alphaplan-Datenbankname bekannt ist:
```powershell
.\Run-AlphaplanDiscoveryAndUpload.ps1 -Database "ALPHAPLAN"
```
Wenn die SQL-Instanz nicht `localhost` ist:
```powershell
.\Run-AlphaplanDiscoveryAndUpload.ps1 -ServerInstance "SERVERNAME\INSTANCE" -Database "ALPHAPLAN"
```
## Lauf mit Samples
```powershell
.\Run-AlphaplanDiscoveryAndUpload.ps1 -Database "ALPHAPLAN" -ExportSamples
```
Samples sind auf `TOP 200` pro Objekt limitiert. Sie helfen Andreas/DE IT, die Inhalte der Top-Kandidaten schnell zu beurteilen.
## SQL Authentifizierung
Windows Integrated ist Default. Fuer SQL-Login:
```powershell
$cred = Get-Credential
.\Run-AlphaplanDiscoveryAndUpload.ps1 -ServerInstance "SERVERNAME\INSTANCE" -Database "ALPHAPLAN" -SqlCredential $cred
```
Empfohlen ist ein read-only Benutzer oder Windows-Servicekonto.
## rclone Voraussetzungen
`rclone` muss auf dem DE-Server eingerichtet sein.
Erwarteter Remote-Name:
```text
trafag-bi
```
Der Remote soll auf die Dokumentbibliothek `Shared Documents` dieser Site zeigen:
```text
https://trafagag.sharepoint.com/sites/WorldwideBIPlatform
```
Tests:
```powershell
rclone lsd trafag-bi:
rclone lsd trafag-bi:"Import/Finance"
rclone lsd trafag-bi:"Import/Finance/Deutschland"
```
Das Script sucht `rclone` automatisch an diesen Stellen:
```text
- Parameter -RcloneExe
- rclone.exe im Scriptordner
- C:\Tools\rclone.exe
- C:\Tools\rclone\rclone.exe
- C:\Tools\rclone\rclone\rclone.exe
- rclone aus PATH
```
## Firewall und Berechtigungen
Vom DE-Server benoetigt:
- SQL Zugriff auf die lokale/interne Alphaplan-Datenbank.
- Ausgehend TCP 443 zu Microsoft 365/SharePoint fuer `rclone`.
- Bei Erstkonfiguration optional Browser-/Device-Login fuer Microsoft 365.
Vom BiDashboard-Server benoetigt:
- Kein direkter SQL-Zugriff auf Alphaplan fuer Phase 1.
SQL-Rechte:
- read-only
- idealerweise Zugriff nur auf benoetigte Datenbanken, Tabellen oder Views
- keine Schreibrechte
## Abgrenzung zum finalen Import
Phase 1 erzeugt noch kein finales Finance-Format. Ziel ist nur:
- richtige Datenbanken finden
- relevante Tabellen/Views finden
- Feldnamen und Beziehungen erkennen
- Beispiele fuer Andreas/DE IT bereitstellen
Der spaetere Import kann danach auf eine finale SQL-View oder ein gemapptes CSV umgestellt werden. Empfohlene finale View:
```text
vw_BiDashboard_FinanceSales_DE
```
## Checkliste fuer DE/IT
Nach dem ersten Discovery-Lauf bitte pruefen:
- Welche Datenbank ist Alphaplan produktiv?
- Welche Tabelle/View ist Rechnungs-Kopf?
- Welche Tabelle/View ist Rechnungs-Position?
- Wie werden Kopf und Position verbunden?
- Welche Spalte ist Rechnungsnummer?
- Welche Spalte ist Positionsnummer?
- Welche Spalte ist Rechnungsdatum oder Buchungsdatum?
- Welche Spalte ist Nettoumsatz auf Positionsebene?
- Welche Spalte ist Menge?
- Welche Spalte ist Artikel-/Materialnummer?
- Welche Spalten enthalten Kundenname, Kundennummer und Kundenland?
- Wie erkennt man Gutschriften?
- Wie erkennt man Stornos?
- Gibt es eine Aenderungs-/Erfassungsdatumsspalte fuer Delta?
## Bekannte Grenzen
- Discovery erkennt Kandidaten heuristisch anhand von Namen. Tabellen mit neutralen Alphaplan-Namen koennen fehlen.
- Wenn SQL-Rechte zu eng sind, sind die CSV leer oder unvollstaendig.
- `RowCountEstimate` ist eine SQL-Server-Schaetzung aus Partition-Stats.
- Sample-Dateien sind nur Auszuege und nicht fuer Summenabgleich geeignet.
- Der Upload prueft aktuell `candidate_objects.csv`, nicht jede einzelne Sample-Datei.
## Naechster Schritt
1. ZIP auf DE-Server kopieren.
2. Discovery lokal ohne Upload ausfuehren.
3. `candidate_objects.csv` pruefen.
4. Discovery mit Upload ausfuehren.
5. Andreas/DE IT markiert die relevanten Alphaplan-Objekte.
6. Danach wird der finale Alphaplan-Finance-Export oder die Importanpassung definiert.
@@ -0,0 +1,203 @@
# Alphaplan SQL/rclone Konzept Deutschland
Stand: 2026-06-08
Ziel: Auf dem deutschen Alphaplan-Server sollen SQL-Tabellen oder Views direkt als CSV exportiert und anschliessend mit `rclone` nach SharePoint hochgeladen werden. Der BiDashboard-/Finance-Import wird danach separat angepasst. Es werden in diesem Konzept keine Programmcode-Aenderungen am BiDashboard beschrieben.
## Umsetzungsstand 2026-06-08
- Phase-1-Discovery-Paket erstellt: `AlphaplanExportPackage`.
- Kopierbares ZIP erstellt: `AlphaplanExportPackage.zip`.
- Hauptscript: `AlphaplanExportPackage/Run-AlphaplanDiscoveryAndUpload.ps1`.
- Kurzanleitung im Paket: `AlphaplanExportPackage/README.txt`.
- Detailanleitung: `docs/ALPHAPLAN_DISCOVERY_EXPORTER_GUIDE_2026-06-08.md`.
- Status: PowerShell-Syntax lokal geprueft; noch kein Lauf auf dem deutschen Alphaplan-Server.
- Abgrenzung: Noch keine BiDashboard-Importanpassung und kein produktiver Deutschland-Import aus Alphaplan-Rohdaten.
## Kurzfazit
- Der Export soll auf dem Alphaplan-/SQL-Server in Deutschland laufen, nicht auf dem BiDashboard-Server.
- Der erste Schritt ist bewusst ein Rohdaten-Export von ausgewaehlten SQL-Tabellen oder Views.
- Upload erfolgt analog Spanien mit `rclone` nach SharePoint.
- Fuer Rohdaten sollte ein eigener SharePoint-Unterordner verwendet werden, damit der aktuelle Deutschland-Import nicht versehentlich falsche CSV-Dateien einliest.
- Spaeter kann aus den erkannten Alphaplan-Tabellen eine stabile Finance-Export-View oder ein finaler gemappter CSV-Export entstehen.
## Annahmen
- Alphaplan verwendet Microsoft SQL Server oder eine SQL-Server-kompatible Datenbank.
- Das Export-Script laeuft lokal auf dem deutschen Server mit Zugriff auf die Alphaplan-Datenbank.
- Der Export benoetigt nur Leserechte.
- `rclone` ist auf dem deutschen Server installiert und fuer die SharePoint-Dokumentbibliothek konfiguriert.
## Zielbild Datenfluss
1. Windows Task Scheduler startet das Alphaplan-Export-Script auf dem DE-Server.
2. Das Script verbindet sich lokal oder intern mit der Alphaplan-SQL-Datenbank.
3. Das Script exportiert definierte Tabellen/Views als CSV in einen lokalen Laufordner.
4. Das Script erstellt eine Summary-Datei mit Server, Datenbank, exportierten Objekten, Zeilenanzahl und Fehlern.
5. `rclone` prueft oder erstellt den SharePoint-Zielordner.
6. `rclone` laedt CSV und Summary nach SharePoint hoch.
7. Der BiDashboard-Import wird spaeter auf diese SharePoint-Dateien angepasst.
## SharePoint-Ziel
Empfohlen fuer Rohdaten:
`trafag-bi:Import/Finance/Deutschland/AlphaplanRaw`
Begruendung:
- Der aktuelle Deutschland-Import kann weiterhin unveraendert bleiben.
- Rohdaten-CSV aus Tabellen/Views haben noch nicht zwingend das finale Finance-Spaltenformat.
- Wenn der Import spaeter fertig ist, kann das Ziel entweder direkt weiterverwendet oder auf `Import/Finance/Deutschland` umgestellt werden.
Fuer einen spaeteren finalen Finance-CSV-Export waere passend:
`trafag-bi:Import/Finance/Deutschland`
## Export-Phasen
### Phase 1: Discovery
Ziel: Herausfinden, welche Alphaplan-Datenbanken, Tabellen und Views relevant sind.
Ergebnisdateien:
- `candidate_objects.csv`
- `export_summary.csv`
- optional kleine Beispiel-CSV je Kandidat
Kandidaten sollten anhand von Namen und Spalten gefunden werden, z. B.:
- Rechnung / Faktura / Beleg
- Auftrag / Lieferschein
- Position / Zeile
- Kunde / Adresse
- Artikel / Material
- Warengruppe / Produktgruppe
- Menge / Preis / Netto / Umsatz
- Datum / Belegdatum / Rechnungsdatum
### Phase 2: Rohdaten-Export
Ziel: Ausgewaehlte Alphaplan-Tabellen oder Views komplett oder zeitlich gefiltert exportieren.
Typische Exportdateien:
- `Alphaplan.<schema>.<table>.csv`
- `Alphaplan.<schema>.<view>.csv`
- `export_summary.csv`
Die Dateien muessen noch nicht dem Finance-Importformat entsprechen. Wichtig ist, dass Andreas/IT daraus die richtigen Tabellen, Schluessel und Felder erkennen kann.
### Phase 3: Finaler Finance-Export
Ziel: Sobald die Tabellenbeziehungen klar sind, wird eine stabile SQL-View oder ein finaler Query definiert, der direkt Finance-taugliche Spalten liefert.
Empfohlen ist eine read-only View, z. B.:
`vw_BiDashboard_FinanceSales_DE`
Diese View sollte pro Rechnungsposition eine Zeile liefern. Dann bleibt das Export-Script einfach und stabil, auch wenn Alphaplan intern mehrere Tabellen benoetigt.
## Benoetigte Informationen von Deutschland/IT
- SQL Server Hostname/Instanz.
- Datenbankname.
- Authentifizierung: Windows Integrated oder SQL User.
- Read-only Benutzer oder Service Account.
- Liste relevanter Tabellen/Views fuer Rechnungen und Rechnungspositionen.
- Datumsspalte fuer Voll-/Delta-Export.
- Kennzeichen fuer Rechnung, Gutschrift, Storno.
- Waehrungsfeld und Standardwaehrung.
- Netto-Umsatzfeld auf Positionsebene.
- Artikelnummer/Materialnummer.
- Kundenland und Kundenname.
- Produktgruppe/Warengruppe, falls in Alphaplan vorhanden.
## Minimal benoetigte Finance-Felder spaeter
Fuer den spaeteren Import reichen als Startpunkt:
| Zielfeld | Bedeutung |
| --- | --- |
| `TSC` | `TRDE` |
| `Land` | `Deutschland` |
| `SourceSystem` | `Alphaplan` |
| `InvoiceNumber` | Rechnungsnummer |
| `PositionOnInvoice` | Positionsnummer |
| `Material` | Artikel-/Materialnummer |
| `Name` | Artikeltext |
| `Quantity` | Menge |
| `CustomerNumber` | Kundennummer |
| `CustomerName` | Kundenname |
| `CustomerCountry` | Kundenland |
| `SalesPriceValue` | Nettoumsatz Positionszeile |
| `SalesCurrency` | Umsatzwaehrung |
| `PostingDate` | Buchungs-/Belegdatum |
| `InvoiceDate` | Rechnungsdatum |
| `DocumentType` | Rechnung/Gutschrift/Storno |
Weitere Felder wie Warengruppe, Lieferant, Incoterms, Auftrag, Branche und Kosten koennen spaeter ergaenzt werden.
## Delta oder Vollfile
Fuer den Anfang ist ein Vollfile pro Jahr am einfachsten:
- weniger Risiko bei geaenderten oder stornierten Belegen
- einfacher Abgleich gegen Alphaplan
- Import kann pro Standort/Jahr neu aufgebaut werden
Delta ist erst sinnvoll, wenn klar ist:
- welche Spalte neue/geaenderte Datensaetze erkennt
- wie Stornos und Korrekturen geliefert werden
- ob der Import vorhandene DE-Daten mergen oder ersetzen soll
Wichtig: Wenn der aktuelle Import Standortdaten komplett ersetzt, darf kein kleines Delta so importiert werden, als waere es ein Vollbestand.
## Sicherheit und Firewall
Da der Export auf dem deutschen Alphaplan-Server laeuft, muss der BiDashboard-Server keinen direkten SQL-Zugriff auf Alphaplan bekommen.
Benoetigt auf dem DE-Server:
- SQL Zugriff lokal/intern auf Alphaplan.
- Ausgehend HTTPS/TCP 443 zu Microsoft 365/SharePoint fuer `rclone`.
- Optional Zugriff fuer rclone OAuth/Device Login bei der Erstkonfiguration.
SQL Benutzer:
- nur read-only
- keine Schreibrechte
- idealerweise Zugriff nur auf benoetigte Views oder Tabellen
## Betrieb
Empfohlener lokaler Ordner auf dem DE-Server:
`C:\Trafag\AlphaplanExport`
Unterordner:
- `out` fuer CSV-Exports
- `logs` fuer Script- und rclone-Logs
Empfohlener Task:
- taeglich frueh morgens
- zuerst Voll-/Jahresfile, spaeter optional Range
- Exitcode und Logdatei pruefen
- Upload per `rclone lsf` verifizieren
## Abnahmekriterien
Der technische Export gilt als bereit, wenn:
- Discovery-Datei mit Kandidatentabellen/Views auf SharePoint liegt.
- Mindestens ein relevanter Tabellen-/View-Export auf SharePoint liegt.
- Summary-Datei Zeilenanzahl und Quelle zeigt.
- rclone Upload im Log ohne Fehler abgeschlossen ist.
- Deutschland/IT bestaetigt, welche Tabellen/Views fuer Finance korrekt sind.
Der Finance-Import ist danach ein separater Schritt.
@@ -0,0 +1,461 @@
# Finance Datenfluss fuer Andreas
Stand: 2026-06-08
Zweck: Diese Notiz beschreibt den tatsaechlichen technischen Datenfluss im Finance Cockpit: wo Daten geholt werden, wann Felder veraendert werden, wann Wechselkurse wirken, wie die zentrale Excel entsteht und welche Quelle die Sparteninformationen liefert.
## Kurzfazit
- Finance Summary, Management Analyse und Spartenanalyse lesen nicht aus dem SharePoint-Excel, sondern direkt aus der App-Datenbank `CentralSalesRecords`.
- Das SharePoint-Excel `Sales_All_*.xlsx` ist ein Export-/Ablageergebnis, nicht die Live-Quelle der Cockpit-Anzeige.
- Jeder Standortexport ersetzt in `CentralSalesRecords` nur die Daten dieses Standorts.
- Die zentrale Excel wird danach aus dem aktuellen Stand von `CentralSalesRecords` erzeugt.
- Wechselkurse veraendern den Standortexport und `CentralSalesRecords` normalerweise nicht. Sie wirken in Analyse-/Anzeige-Sichten, wenn eine Zielwaehrung wie CHF/EUR/USD ausgewaehlt ist, oder in explizit konfigurierten Transformationen.
- Sparteninformationen kommen fuehrend aus SAP/TR-AG `ProductDivisionRefSet`. Aktuell werden sie beim ZSCHWEIZ-/CH-/AT-Export direkt mitgeladen. Andere Laender werden in der Analyse ueber ihre Materialnummer gegen diese TR-AG-Referenz gematcht.
## Technischer Ablauf Standortexport
Ausloeser:
- Export Dashboard: einzelner Standort oder `Alle exportieren`.
- Timer, falls aktiv.
- Intern: `ExportOrchestrationService` ruft `SiteExportService.ExportAsync(...)`.
Ablauf pro Standort:
1. Standort und Quellsystem werden geladen.
2. Adapter wird nach `ConnectionKind` gewaehlt:
- `HanaDataSourceAdapter` fuer BI1/HANA.
- `SapGatewayDataSourceAdapter` fuer SAP OData.
- `ManualExcelDataSourceAdapter` fuer Excel/CSV/SharePoint-Dateien.
3. Rohdaten werden als `SalesRecord`-Liste aufgebaut.
4. Aktive `FieldTransformationRules` fuer das Quellsystem werden angewendet.
5. Eine lokale Standort-Excel `Sales_<TSC>_<Datum>.xlsx` wird erzeugt.
6. `CentralSalesRecords` wird fuer diesen Standort ersetzt:
- alte Saetze mit `SiteId = Standort` loeschen.
- neue Saetze einfuegen.
7. Falls SharePoint komplett konfiguriert ist, wird die Standort-Excel nach SharePoint hochgeladen.
Wichtig: Die Reihenfolge ist zuerst Daten holen, dann Transformationen, dann lokale Excel, dann zentrale Tabelle, dann SharePoint-Upload. Der SharePoint-Upload entscheidet nicht, was in der Cockpit-Anzeige erscheint.
## Datenquellen pro Quelltyp
### HANA / SAP B1
Betroffen:
| Land | TSC | Schema | Quelle |
| --- | --- | --- | --- |
| Frankreich | `TRFR` | `fr01_p` / lokal teils `FR01_p` | BI1/HANA |
| Italien | `TRIT` | `it01_p` | BI1/HANA |
| USA | `TRUS` | `us01_p` | BI1/HANA |
| Indien | `TRIN` | `TRAFAG_LIVE` | HANA/Sage-Quelle |
Standard-B1-Abfrage:
- Rechnungen aus `OINV` + `INV1`.
- Gutschriften aus `ORIN` + `RIN1`.
- Stornierte Belege werden ausgeschlossen: `CANCELED = 'N'`.
- Datumsfilter auf `DocDate >= ExportSettings.DateFilter`.
Wichtige Feldbelegung:
| Zielfeld | HANA/B1-Feld |
| --- | --- |
| `PostingDate` | `OINV.DocDate` / `ORIN.DocDate` |
| `InvoiceDate` | `OINV.TaxDate` / `ORIN.TaxDate` |
| `Material` | `INV1.ItemCode` / `RIN1.ItemCode` |
| `Name` | `INV1.Dscription` / `RIN1.Dscription` |
| `ProductGroup` | `OITB.ItmsGrpNam` |
| `Quantity` | Invoice positiv, Credit Note negativ |
| `SalesPriceValue` | `LineTotal`, bei Credit Note negativ |
| `SalesCurrency` | `OADM.MainCurncy` |
| `DocumentCurrency` | `DocCur` |
| `DocumentTotalForeignCurrency` | `DocTotalFC`, bei Credit Note negativ |
| `DocumentTotalLocalCurrency` | `DocTotal`, bei Credit Note negativ |
| `VatSumForeignCurrency` | `VatSumFC`, bei Credit Note negativ |
| `VatSumLocalCurrency` | `VatSum`, bei Credit Note negativ |
| `DocumentRate` | `DocRate` |
| `CompanyCurrency` | `OADM.MainCurncy` |
| `DocumentType` | `INV` oder `CRN` |
Italien hat zusaetzlich einen HANA-Filter:
- `AcctCode LIKE '47005%'`
- `AcctCode NOT LIKE '4700504%'`
- bestimmte `CardCode`-Ausschluesse.
Diese HANA-Filter greifen bereits beim Datenholen, also vor Transformation, Excel und `CentralSalesRecords`.
### SAP OData / ZSCHWEIZ
Betroffen:
| Land | TSC | Quelle |
| --- | --- | --- |
| CH / AT | `ZSCHWEIZ` | SAP OData `ZPOWERBI_EINKAUF_SRV` |
Aktive OData-Quellen:
| Alias | EntitySet | Zweck |
| --- | --- | --- |
| `Z` | `FinanzdataSchweizOeSet` | Verkaufs-/Finance-Zeilen CH/AT |
| `P` | `ProductDivisionRefSet` | zentrale TR-AG-Spartenreferenz |
Join:
```text
Z.Matnr = P.Matnr
```
Wichtige Feldbelegung:
| Zielfeld | Quelle |
| --- | --- |
| `Tsc` | `Z.Tsc` |
| `Land` | `Z.Land1` |
| `InvoiceNumber` | `Z.Vbeln` |
| `PositionOnInvoice` | `Z.Posnr` |
| `PostingDate` | `Z.Fkdat` |
| `InvoiceDate` | `Z.Fkdat` |
| `Material` | `Z.Matnr` |
| `Name` | `Z.Arktx` |
| `ProductGroup` | `Z.Prodh` |
| `SalesPriceValue` | `Z.NetwrHc` |
| `SalesCurrency` | `Z.Hwaer` |
| `DocumentCurrency` | `Z.Waerk` |
| `DocumentTotalForeignCurrency` | `Z.NetwrDc` |
| `DocumentTotalLocalCurrency` | `Z.NetwrHc` |
| `VatSumForeignCurrency` | `0` |
| `VatSumLocalCurrency` | `0` |
| `DocumentRate` | `Z.Kurrf` |
| `CompanyCurrency` | `Z.Hwaer` |
| `DocumentType` | `Z.Fkart` |
Spartenfelder aus `P = ProductDivisionRefSet`:
| Zielfeld | Quelle |
| --- | --- |
| `ProductHierarchyCode` | `P.Paph1` |
| `ProductHierarchyText` | `P.Paph1Text` |
| `ProductFamilyCode` | `P.Wwpfa` |
| `ProductFamilyText` | `P.WwpfaText` |
| `ProductDivisionCode` | `P.Wwpsp` |
| `ProductDivisionText` | `P.WwpspText` |
| `ProductMappingAssigned` | `P.IsAssigned` |
Diese Spartenfelder werden beim ZSCHWEIZ-Export direkt in `CentralSalesRecords` gespeichert.
### Manuelle Excel/CSV / SharePoint-Dateien
Betroffen:
| Land | Beispiel | Quelle |
| --- | --- | --- |
| UK | `TRUK` | SharePoint-Ordner `Import/Finance/UK_B1`, fachlich Sage |
| Spanien | `TRES` / lokal auch `TRSE` in alten Daten | Sage CSV / SharePoint `Import/Finance/Spanien` |
| Deutschland | `TRDE` | Alphaplan Excel |
Ablauf:
- Datei wird lokal, per UNC oder aus SharePoint geladen.
- Bei SharePoint-Ordnern wird die passende neueste Datei bzw. bei Spanien alle `Spain_Sales*.csv` gelesen.
- Spalten werden ueber manuelle Mappings oder Headernamen auf `SalesRecord` gemappt.
- UK nutzt `SageNetSales([Sales Price/Value], [Quantity], [Document Type], ...)`.
- Spanien-Deltas werden nach `SourceLineId`, sonst `TSC + InvoiceNumber + PositionOnInvoice + Material`, dedupliziert.
Manuelle Dateien koennen Spartenfelder enthalten, wenn sie passende Spalten liefern. Im aktuellen fachlichen Zielbild sind lokale ERP-Sparten aber nicht fuehrend; die Spartenanalyse matched gegen TR-AG/SAP-Referenz.
## Wann werden Felder veraendert?
### Beim Datenholen / Mapping
Die meisten Felder werden beim Import initial gesetzt:
- HANA/B1: SQL-Abfrage erzeugt `SalesRecord` direkt.
- SAP OData: OData-Zeilen werden per Mapping/Join in `SalesRecord` geschrieben.
- Manual Excel/CSV: Excel-/CSV-Spalten werden per Mapping in `SalesRecord` geschrieben.
Beispiele fuer aktive Veraenderung beim Holen:
- B1-Gutschriften werden bereits negativ geladen:
- `Quantity * -1`
- `LineTotal * -1`
- `DocTotal * -1`
- `VatSum * -1`
- ZSCHWEIZ setzt `VatSum* = 0`, weil Nettowerte aus `NetwrHc`/`NetwrDc` kommen.
- ZSCHWEIZ haengt Spartenfelder aus `ProductDivisionRefSet` per Join an.
- UK berechnet den Positionswert aus Stueckpreis mal Menge.
- Spanien setzt Credit Notes / REC negativ.
### Nach dem Datenholen: Transformationen
Danach laufen aktive `FieldTransformationRules`:
- Einfache Feldtransformationen kopieren/normalisieren Werte von `SourceField` nach `TargetField`.
- `FirstNonEmpty` setzt ein Zielfeld aus dem ersten nicht-leeren Kandidatenfeld.
- `ConvertCurrency` kann einen Betrag ueber die Kurstabelle in eine Zielwaehrung umrechnen und optional ein Ziel-Waehrungsfeld setzen.
Wichtig: Transformationen laufen vor lokaler Standort-Excel und vor `CentralSalesRecords`. Falls eine aktive Transformation ein Feld veraendert, wird der veraenderte Wert in Excel und Datenbank gespeichert.
### Beim Schreiben in CentralSalesRecords
Es wird fachlich nichts neu berechnet. Die App speichert die nach Import und Transformation vorliegenden `SalesRecord`-Werte.
Pro Standort:
```text
DELETE CentralSalesRecords WHERE SiteId = <Standort>
INSERT neue Records fuer diesen Standort
```
## Wann wirkt der Wechselkurs?
Es gibt drei getrennte Faelle.
### 1. Standard-Finance-Soll/Ist und Finance Summary
Kein allgemeiner Wechselkurs wird angewendet.
- Fuehrend ist die Hauswaehrung / lokale Finance-Waehrung.
- `Finance Summary` nutzt `SalesPriceValue` nach Finance-Regeln.
- Anzeige bleibt nach vorhandener Waehrung gruppiert.
- Wenn mehrere Waehrungen im Scope sind, zeigt die UI `Mixed`.
Das gilt fuer:
- Management Analyse > Finance Summary.
- Management Analyse > Spartenanalyse.
- Zentrale Excel-Blatt `Finance Summary`.
- Zentrale Excel-Blatt `Finance Details`.
### 2. Management Analyse / Rohdaten-Diagnose mit Zielwaehrung
Wenn in Analyse-Sichten eine Zielwaehrung wie `CHF`, `EUR` oder `USD` ausgewaehlt wird, rechnet die UI-Anzeige zur Laufzeit um.
Quelle:
- `CurrencyExchangeRates`
- gewaehltes `ExchangeRateDateField` aus Settings:
- `PostingDate`
- `InvoiceDate`
- `ExtractionDate`
Die Umrechnung passiert beim Anzeigen/Aggregieren in `ManagementCockpitService`, nicht beim Standortexport und nicht beim Erzeugen der zentralen Excel.
Wenn kein Kurs gefunden wird:
- Anzeige-Wert wird fuer diese Zeile `0` in der Zielwaehrung.
- `MissingExchangeRate` wird gezaehlt und in Diagnosehinweisen angezeigt.
### 3. Explizite Transformation `ConvertCurrency`
Falls eine aktive `FieldTransformationRule` vom Typ `ConvertCurrency` gepflegt ist, passiert die Umrechnung beim Standortexport vor `CentralSalesRecords`.
Dann wird das konfigurierte Zielfeld dauerhaft mit dem umgerechneten Betrag beschrieben.
Standardlogik fuer Datum bei `ConvertCurrency`:
- konfiguriertes `dateField`, falls angegeben.
- sonst `InvoiceDate`.
- sonst `OrderDate`.
- sonst `ExtractionDate`.
## Finance-Regeln und Finance-Wert
Die Finance-Sicht wird nicht durch Excel-Formeln bestimmt, sondern durch `FinanceRuleEngine`.
Zeitliche Abgrenzung:
```text
FinanceDate = PostingDate
sonst InvoiceDate
sonst ExtractionDate
```
Ausnahme:
- DE kann per Finance-Regel auf Jahr 2025 gezwungen werden, weil Alphaplan als Jahresfile geliefert wird.
Include/Exclude:
- Regeln koennen Zeilen ausschliessen.
- Regeln koennen Werte negativ setzen.
- Regeln koennen IT-Duplikate mit leerem Supplier Country deduplizieren.
Aktuelle Default-/Fachregeln:
- DE:
- Jahresfile 2025.
- `Trafag AG` ausschliessen.
- `Magnetic Sense` ausschliessen.
- `GS2510095` ausschliessen.
- Gutschriften mit `GS...` negativ.
- IT:
- `CustomerName` enthaelt `Trafag Italia` ausschliessen.
- IT-Zeilen mit leerem Supplier Country deduplizieren.
Der Finance-Wert ist:
```text
Net Sales Actual = SalesPriceValue nach FinanceRuleEngine
```
## Zentrale Excel
Ausloeser:
- Export Dashboard > `Zentrale Datei neu erzeugen`.
- Nach `Alle exportieren` automatisch am Ende.
Ablauf:
1. `ConsolidatedExportService` liest alle Saetze aus `CentralSalesRecords`.
2. `ExcelExportService.CreateConsolidatedExcelFile(...)` erzeugt `Sales_All_<Datum>.xlsx`.
3. Die Datei wird lokal geschrieben.
4. Falls SharePoint konfiguriert ist, wird sie hochgeladen.
Ziel lokal:
- `ExportSettings.LocalConsolidatedExportFolder`, falls gesetzt.
- sonst `ExportSettings.LocalSiteExportFolder`, falls gesetzt.
- sonst `<App-Verzeichnis>/output`.
Ziel SharePoint:
- Wenn `CentralExportFolder` gesetzt ist: direkt dorthin.
- Sonst: `ExportFolder/Alle`.
Die zentrale Excel enthaelt:
- Blatt `Finance Summary`.
- Blatt `Finance Details`.
- Blatt `Sales`.
- optional Hilfeblatt.
Wichtig:
- `Finance Summary` im Excel wird beim Schreiben aus den Records berechnet.
- Es liest nicht aus einem vorherigen SharePoint-Excel.
- Wechselkurs-Zielwaehrung aus der UI wird dabei nicht angewendet.
## Finance Summary und Spartenanalyse in der App
Die App-Anzeigen lesen direkt aus:
```text
CentralSalesRecords
```
Nicht aus:
```text
SharePoint Sales_All_*.xlsx
```
Das bedeutet:
- Lokal zeigt die App lokale DB-Daten.
- Publizierter Server zeigt Server-DB-Daten.
- Wenn lokale und Server-DB gleich sind, sehen beide gleich aus.
- Ein SharePoint-Upload veraendert die App-Anzeige nicht.
## Spartenanalyse: genaue Logik
### Quelle der Spartenreferenz
Fuehrend ist `ProductDivisionRefSet` aus SAP/TR-AG.
Technisch landet diese Referenz aktuell ueber den ZSCHWEIZ-Export in `CentralSalesRecords`, weil ZSCHWEIZ die Quelle `P = ProductDivisionRefSet` per `Z.Matnr = P.Matnr` joint.
Die Analyse baut daraus eine Referenz:
```text
Materialnummer -> PAPH1 / Produktfamilie / Produktsparte / IsAssigned
```
Materialnummern werden normalisiert:
- Trim.
- Grossschreibung.
- Leerzeichen entfernen.
- fuehrende Nullen entfernen.
### Status in der Spartenanalyse
| Status | Bedeutung |
| --- | --- |
| `Zugeordnet` | Material in TR-AG-Referenz gefunden und `ProductMappingAssigned` ist wahr, Produktsparte ist nicht leer und nicht `UNASS`. |
| `Nicht zugeordnet` | Material in TR-AG-Referenz gefunden, aber Spartenwert leer/`UNASS`/nicht assigned. |
| `Nicht im TR-AG-Stamm` | Materialnummer aus lokalem Finance-Umsatz hat keinen Treffer in der TR-AG-Referenz. |
| `Material fehlt` | Finance-Zeile hat keine Materialnummer. |
### Warum aktuell viele `Nicht im Stamm` entstehen koennen
Die aktuellen Daten zeigen Produktfelder nur bei CH/AT direkt gefuellt. Andere Laender werden ueber Materialnummern gegen die TR-AG-Referenz gematcht.
Wenn lokale Artikelnummern nicht identisch mit TR-AG-Materialnummern sind, entstehen `Nicht im TR-AG-Stamm`.
Typische Ursachen:
- lokale Materialnummern statt TR-AG-MATNR.
- Sage-/B1-Artikelnummern, die nicht im TR-AG-Stamm existieren.
- Indien-Artikel wie `DM000010`, `DM000001`, `PT000003`, `IC15415`, die lokal hohe Werte tragen, aber keinen Treffer in der TR-AG-Referenz haben.
- fehlende oder unvollstaendige `ProductDivisionRefSet`-Fuellung.
- Materialnummernformat trotz Normalisierung nicht kompatibel.
## Datenflussdiagramm Textform
```text
Standort Export starten
|
+-- HANA/B1: OINV/INV1 + ORIN/RIN1 lesen
| -> Gutschriften bereits negativ
| -> IT-HANA-Filter bereits in SQL
|
+-- SAP OData ZSCHWEIZ:
| -> FinanzdataSchweizOeSet lesen
| -> ProductDivisionRefSet lesen
| -> Join Z.Matnr = P.Matnr
| -> Spartenfelder in SalesRecord
|
+-- Manual Excel/CSV/SharePoint:
-> Datei(en) laden
-> Mapping/SageNetSales/Spain-Dedupe
SalesRecord-Liste
|
+-- FieldTransformationRules anwenden
| -> optional Feldkopien, FirstNonEmpty, ConvertCurrency
|
+-- Standort-Excel Sales_<TSC>_<Datum>.xlsx lokal schreiben
|
+-- CentralSalesRecords fuer SiteId ersetzen
|
+-- Standort-Excel optional nach SharePoint hochladen
Finance Summary / Spartenanalyse
|
+-- liest CentralSalesRecords
+-- FinanceRuleEngine rechnet Include/Exclude/Net Sales Actual
+-- Spartenanalyse matched lokale Materialien gegen TR-AG-Referenz aus CentralSalesRecords
Zentrale Excel
|
+-- liest CentralSalesRecords
+-- erzeugt Sales_All_<Datum>.xlsx lokal
+-- erzeugt Finance Summary / Finance Details im Excel
+-- laedt Datei optional nach SharePoint
```
## Wichtige Klarstellungen fuer Finance
1. SharePoint ist Ablage und Quelle fuer manuelle Dateien, aber nicht Live-Quelle der Finance Summary.
2. `CentralSalesRecords` ist der operative zentrale Datenbestand der App.
3. Sparten kommen fachlich aus TR-AG/SAP `ProductDivisionRefSet`, nicht aus lokalen ERP-Sparten.
4. CH/AT bekommen Spartenfelder direkt beim ZSCHWEIZ-Export.
5. Andere Laender bekommen Sparten in der Analyse nur, wenn ihre Materialnummern zur TR-AG-Referenz matchen.
6. Wechselkurse sind keine stille Vorverarbeitung fuer den Standard-Soll/Ist-Abgleich.
7. `Mixed` bedeutet: mehrere Waehrungen im Filter. Prozentwerte auf `Mixed` sind nur eingeschraenkt interpretierbar; fuer belastbare Spartenanteile nach Wert muss Land oder Waehrung gefiltert werden.
8. Die zentrale Excel wird nach den Standortexporten aus `CentralSalesRecords` erstellt. Sie ist Ergebnis, nicht Eingang.
+128 -3
View File
@@ -1,6 +1,6 @@
# Last Change # Last Change
Stand: 2026-06-05 Stand: 2026-06-08
Diese Datei ist fuer tokenarme RAG-Nutzung komprimiert. Diese Datei ist fuer tokenarme RAG-Nutzung komprimiert.
@@ -8,8 +8,17 @@ Diese Datei ist fuer tokenarme RAG-Nutzung komprimiert.
- Fuehrender Kurzkontext: `docs/rag/PROJECT.md`. - Fuehrender Kurzkontext: `docs/rag/PROJECT.md`.
- Themenrouter: `docs/RAG_ROUTER.md`. - Themenrouter: `docs/RAG_ROUTER.md`.
- Letzter dokumentierter Code-Stand: Finance-Dashboard-Vereinfachung, Expertenbereich mit 3D-Datenanalyse und Spanien-Sage-All-in-one-rclone-Upload. - Letzter dokumentierter Code-Stand: Finance-Spartenanalyse lokal weiter verbessert, Datenfluss fuer Andreas dokumentiert, Alphaplan-Discovery-Exporter fuer Deutschland erstellt.
- Letzte dokumentierte Validierung: `dotnet test TrafagSalesExporter.sln --verbosity minimal --artifacts-path C:\TMP\trafag-test-artifacts-finance-session-proof` mit `82/82` Tests gruen. - Letzte dokumentierte Validierung: `dotnet test TrafagSalesExporter.sln --no-restore --verbosity minimal` mit `83/83` Tests gruen.
- Wichtig: Die lokalen Finance-/Alphaplan-Aenderungen vom 2026-06-08 sind dokumentiert, aber noch nicht als neuer Deploy auf den IIS-Server beschrieben.
- Neu lokal: Sparten-Finanzanalyse gruppiert standardmaessig nach `Produktsparte`; `Produktfamilie` und `PAPH1 Detail` bleiben als Umschaltoptionen erhalten.
- Neu lokal: Sparten-Finanzanalyse zeigt bei `Mixed`-Waehrung einen Warnhinweis, weil Summen/Anteile ueber mehrere Waehrungen fachlich nur eingeschraenkt belastbar sind.
- Neu lokal: Sparten-Finanzanalyse zeigt die groessten Treiber fuer `Nicht im TR-AG-Stamm`, damit hohe nicht zugeordnete Umsaetze nach Land/TSC/Material analysiert werden koennen.
- Neu dokumentiert: genauer Finance-Datenfluss fuer Andreas in `docs/FINANCE_DATENFLUSS_ANDREAS_2026-06-08.md`.
- Neu dokumentiert: Alphaplan SQL/rclone Konzept Deutschland in `docs/ALPHAPLAN_SQL_RCLONE_KONZEPT_DE_2026-06-08.md`.
- Neu erstellt: Alphaplan Phase-1-Discovery-Paket `AlphaplanExportPackage` und `AlphaplanExportPackage.zip`.
- Neu dokumentiert: Alphaplan Discovery Exporter Guide in `docs/ALPHAPLAN_DISCOVERY_EXPORTER_GUIDE_2026-06-08.md`.
- Alphaplan-Ziel: DE-Server exportiert SQL-Discovery/Rohdaten lokal und laedt per `rclone` nach `trafag-bi:Import/Finance/Deutschland/AlphaplanRaw`; BiDashboard-Importanpassung ist separater Folgeschritt.
- Neu umgesetzt und deployed: Finance bekommt links eine einfache Schnelluebersicht; die bisherigen tieferen Analysefunktionen bleiben unter `Experten`. - Neu umgesetzt und deployed: Finance bekommt links eine einfache Schnelluebersicht; die bisherigen tieferen Analysefunktionen bleiben unter `Experten`.
- Neu umgesetzt und deployed: `Experten > 3D Datenanalyse` mit drehbarer 3D-Visualisierung, Achsenbeschriftung, waehlbaren Indikatoren, Diagrammarten und Simulation. - Neu umgesetzt und deployed: `Experten > 3D Datenanalyse` mit drehbarer 3D-Visualisierung, Achsenbeschriftung, waehlbaren Indikatoren, Diagrammarten und Simulation.
- Neu umgesetzt und deployed: 3D-Simulation mit Schiebereglern, u. a. fuer Wechselkurs-/Szenarioveraenderungen; Grafik reagiert in Echtzeit. - Neu umgesetzt und deployed: 3D-Simulation mit Schiebereglern, u. a. fuer Wechselkurs-/Szenarioveraenderungen; Grafik reagiert in Echtzeit.
@@ -56,6 +65,122 @@ Diese Datei ist fuer tokenarme RAG-Nutzung komprimiert.
- Neu umgesetzt: `Einkauf Dashboard > 3D Simulation` mit festen Canvas-Abmessungen, Achsenbeschriftung, Diagrammarten, Labelgroesse und Szenario-Slider fuer Preis-/Wechselkurswirkung. - Neu umgesetzt: `Einkauf Dashboard > 3D Simulation` mit festen Canvas-Abmessungen, Achsenbeschriftung, Diagrammarten, Labelgroesse und Szenario-Slider fuer Preis-/Wechselkurswirkung.
- Letzte Validierung: `dotnet test TrafagSalesExporter.sln --verbosity minimal` mit `83/83` Tests gruen; Test prueft auch Einkaufs-SAP-Seed mit Quellen/Joins/Mappings. - Letzte Validierung: `dotnet test TrafagSalesExporter.sln --verbosity minimal` mit `83/83` Tests gruen; Test prueft auch Einkaufs-SAP-Seed mit Quellen/Joins/Mappings.
## Nachtrag 2026-06-08 Alphaplan Deutschland / SQL Discovery / rclone Upload
Ziel:
- Deutschland nutzt Alphaplan.
- Vor einer finalen Importanpassung sollen direkt auf dem deutschen Alphaplan-/SQL-Server relevante Datenbanken, Tabellen und Views gefunden werden.
- Die Discovery-Ergebnisse sollen analog Spanien per `rclone` nach SharePoint hochgeladen werden.
Erstellte Dateien:
- `docs/ALPHAPLAN_SQL_RCLONE_KONZEPT_DE_2026-06-08.md`
- `docs/ALPHAPLAN_DISCOVERY_EXPORTER_GUIDE_2026-06-08.md`
- `AlphaplanExportPackage/Run-AlphaplanDiscoveryAndUpload.ps1`
- `AlphaplanExportPackage/README.txt`
- `AlphaplanExportPackage.zip`
SharePoint-Ziel:
```text
trafag-bi:Import/Finance/Deutschland/AlphaplanRaw
```
Grund fuer eigenen Rohdatenordner:
- Die Discovery-Dateien entsprechen noch nicht dem finalen Finance-Importformat.
- Der bestehende Deutschland-Import soll nicht gestoert werden.
Script-Funktion:
- SQL Server read-only scannen.
- Wenn `-Database` leer ist: alle erreichbaren User-Datenbanken scannen.
- Kandidaten fuer Rechnung, Faktura, Beleg, Umsatz, Verkauf, Position, Artikel, Material, Kunde, Betrag, Netto, Menge, Waehrung, Warengruppe, Gutschrift und Storno erkennen.
- `candidate_objects.csv` mit Score, Spalten, Datums-/Betrags-/Key-Kandidaten schreiben.
- `export_summary.csv` schreiben.
- Optional mit `-ExportSamples` kleine `sample_*.csv` aus Top-Kandidaten schreiben.
- Optional mit `-SkipUpload` ohne SharePoint laufen.
- Standardmaessig Upload per `rclone` nach SharePoint und Verifikation von `candidate_objects.csv`.
Wichtige Befehle fuer den DE-Server:
```powershell
Set-ExecutionPolicy -Scope Process Bypass
.\Run-AlphaplanDiscoveryAndUpload.ps1 -SkipUpload
```
```powershell
.\Run-AlphaplanDiscoveryAndUpload.ps1 -Database "ALPHAPLAN" -ExportSamples
```
```powershell
$cred = Get-Credential
.\Run-AlphaplanDiscoveryAndUpload.ps1 -ServerInstance "SERVERNAME\INSTANCE" -Database "ALPHAPLAN" -SqlCredential $cred
```
Default-Pfade:
- Lokaler Arbeitsordner: `C:\Trafag\AlphaplanExport`
- Runs: `C:\Trafag\AlphaplanExport\out\Alphaplan_SQL_Discovery_YYYYMMDD_HHMMSS`
- Logs: `C:\Trafag\AlphaplanExport\logs`
rclone-Kontext:
- Erwarteter Remote: `trafag-bi`
- Remote soll auf `Shared Documents` von `https://trafagag.sharepoint.com/sites/WorldwideBIPlatform` zeigen.
- Fuer Phase 1 braucht nur der DE-Server ausgehend TCP 443 zu Microsoft 365/SharePoint.
- Der BiDashboard-Server braucht keinen direkten SQL-Zugriff auf Alphaplan.
Validierung:
- PowerShell-Syntax des Scripts lokal mit Parser geprueft: OK.
- Noch kein echter Lauf gegen den deutschen Alphaplan-Server.
- Noch keine BiDashboard-Importanpassung fuer Alphaplan-Rohdaten.
Naechster Schritt:
- ZIP auf DE-Server kopieren.
- Discovery zuerst mit `-SkipUpload` starten.
- `candidate_objects.csv` von DE/IT/Andreas pruefen lassen.
- Danach finale Alphaplan-View oder gemappten CSV-Export definieren.
## Nachtrag 2026-06-08 Finance Spartenanalyse / Datenfluss Andreas
Umgesetzt lokal:
- Sparten-Finanzanalyse gruppiert standardmaessig nach `Produktsparte`.
- Gruppierungsoptionen bleiben: `Produktsparte`, `Produktfamilie`, `PAPH1 Detail`.
- Bei `Mixed`-Waehrung wird ein Warnhinweis angezeigt, weil Umsaetze aus mehreren Waehrungen numerisch addiert werden.
- Zusaetzlich wird eine Tabelle `Groesste Treiber: Nicht im Stamm` angezeigt.
- Diese Tabelle zeigt Land, TSC, Material, Bezeichnung, Umsatz und Zeilenanzahl fuer die wichtigsten nicht gematchten Materialien.
Analyseergebnis:
- Finance Summary, Management Analyse und Spartenanalyse lesen aus `CentralSalesRecords`, nicht aus dem SharePoint-Zentral-Excel.
- `Nicht im TR-AG-Stamm` entsteht, wenn Materialnummern aus lokalen Systemen nicht gegen die TR-AG-Referenz `ProductDivisionRefSet` gematcht werden.
- In der lokalen Analyse war Indien der groesste Treiber, weil lokale/Sage/B1-Materialnummern wie `DM000010`, `DM000001`, `DM000018`, `IC15415` usw. nicht in der TR-AG-Referenz gefunden wurden.
- `Mixed`-Summen sind fachlich vorsichtig zu interpretieren, weil INR/EUR/CHF/USD ohne Zielwaehrungsauswahl addiert werden.
Dokumentiert:
- `docs/FINANCE_DATENFLUSS_ANDREAS_2026-06-08.md`
Wichtige Aussagen fuer Andreas:
- Standortexport: Daten holen, Transformationen anwenden, lokale Standort-Excel erstellen, `CentralSalesRecords` fuer Standort ersetzen, optional SharePoint-Upload.
- Zentrale Excel wird aus `CentralSalesRecords` erzeugt und ist nicht die Live-Quelle der Cockpit-Anzeige.
- Wechselkurse veraendern `CentralSalesRecords` normalerweise nicht; sie wirken in Anzeige-/Analyse-Sichten bei Zielwaehrung oder in expliziten `ConvertCurrency`-Transformationen.
- Sparteninformationen kommen fuehrend aus SAP/TR-AG `ProductDivisionRefSet`; CH/AT werden direkt damit geladen, andere Laender werden in der Analyse ueber Materialnummern gematcht.
Validierung:
```text
dotnet test TrafagSalesExporter.sln --no-restore --verbosity minimal
```
Ergebnis: `83/83` Tests gruen.
## Nachtrag 2026-06-05 Einkauf / PBIX ## Nachtrag 2026-06-05 Einkauf / PBIX
Quelle: Quelle: