diff --git a/TrafagSalesExporter/AlphaplanExportPackage.zip b/TrafagSalesExporter/AlphaplanExportPackage.zip
new file mode 100644
index 0000000..8cd660c
Binary files /dev/null and b/TrafagSalesExporter/AlphaplanExportPackage.zip differ
diff --git a/TrafagSalesExporter/AlphaplanExportPackage/README.txt b/TrafagSalesExporter/AlphaplanExportPackage/README.txt
new file mode 100644
index 0000000..ab828a9
--- /dev/null
+++ b/TrafagSalesExporter/AlphaplanExportPackage/README.txt
@@ -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.
+
diff --git a/TrafagSalesExporter/AlphaplanExportPackage/Run-AlphaplanDiscoveryAndUpload.ps1 b/TrafagSalesExporter/AlphaplanExportPackage/Run-AlphaplanDiscoveryAndUpload.ps1
new file mode 100644
index 0000000..63736f7
--- /dev/null
+++ b/TrafagSalesExporter/AlphaplanExportPackage/Run-AlphaplanDiscoveryAndUpload.ps1
@@ -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" })"
+
diff --git a/TrafagSalesExporter/Components/Pages/ManagementCockpit.razor b/TrafagSalesExporter/Components/Pages/ManagementCockpit.razor
index 50dce1b..e14e10c 100644
--- a/TrafagSalesExporter/Components/Pages/ManagementCockpit.razor
+++ b/TrafagSalesExporter/Components/Pages/ManagementCockpit.razor
@@ -441,6 +441,14 @@
+ @if (IsProductFinanceMixedCurrency)
+ {
+
+ @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.")
+
+ }
+
@@ -507,6 +515,37 @@
+
+ @T("Groesste Treiber: Nicht im Stamm", "Top drivers: not in master")
+
+
+ @T("Land", "Country")
+ TSC
+ @T("Material", "Material")
+ @T("Text", "Text")
+ @T("Umsatz", "Sales")
+ @T("Zeilen", "Rows")
+
+
+ @FormatCountryWithFlag(context.CountryKey)
+ @context.Tsc
+ @context.Material
+
+
+
+ @context.ArticleName
+
+
+
+ @FormatValue(context.NetSalesActual, context.Currency)
+ @context.RowCount.ToString("N0")
+
+
+ @T("Keine fehlenden TR-AG-Referenzen fuer diese Filter.", "No missing TR AG references for these filters.")
+
+
+
+
@T("Umsatzabdeckung nach Land", "Sales coverage by country")
@@ -1093,9 +1132,9 @@
];
private readonly List _productFinanceGroupingOptions =
[
- new(ProductFinanceGroupLevels.Hierarchy, "PAPH1 Detail", "PAPH1 detail"),
+ new(ProductFinanceGroupLevels.Division, "Produktsparte", "Product division"),
new(ProductFinanceGroupLevels.Family, "Produktfamilie", "Product family"),
- new(ProductFinanceGroupLevels.Division, "Produktsparte", "Product division")
+ new(ProductFinanceGroupLevels.Hierarchy, "PAPH1 Detail", "PAPH1 detail")
];
private readonly List _finance3dIndicatorOptions =
[
@@ -1145,7 +1184,7 @@
private int _activeOverviewTabIndex;
private int _activeFinanceTabIndex;
private int _activeDivisionTabIndex;
- private string _productFinanceGroupLevel = ProductFinanceGroupLevels.Hierarchy;
+ private string _productFinanceGroupLevel = ProductFinanceGroupLevels.Division;
private bool _limitProductFinanceTop10;
private string _finance3dIndicator = Finance3dIndicators.Actual;
private string _finance3dChartType = Finance3dChartTypes.Bar;
@@ -1513,6 +1552,23 @@
? T(option.GermanLabel, option.EnglishLabel)
: T("Net Sales Actual", "Net sales actual");
+ private bool IsProductFinanceMixedCurrency
+ => string.Equals(_financeResult?.ProductFinanceSummary.DisplayCurrency, "Mixed", StringComparison.OrdinalIgnoreCase);
+
+ private IReadOnlyList 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 BuildProductFinanceRows()
{
if (_financeResult is null)
diff --git a/TrafagSalesExporter/docs/ALPHAPLAN_DISCOVERY_EXPORTER_GUIDE_2026-06-08.md b/TrafagSalesExporter/docs/ALPHAPLAN_DISCOVERY_EXPORTER_GUIDE_2026-06-08.md
new file mode 100644
index 0000000..b6477f5
--- /dev/null
+++ b/TrafagSalesExporter/docs/ALPHAPLAN_DISCOVERY_EXPORTER_GUIDE_2026-06-08.md
@@ -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.
+
diff --git a/TrafagSalesExporter/docs/ALPHAPLAN_SQL_RCLONE_KONZEPT_DE_2026-06-08.md b/TrafagSalesExporter/docs/ALPHAPLAN_SQL_RCLONE_KONZEPT_DE_2026-06-08.md
new file mode 100644
index 0000000..ba8ea11
--- /dev/null
+++ b/TrafagSalesExporter/docs/ALPHAPLAN_SQL_RCLONE_KONZEPT_DE_2026-06-08.md
@@ -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...csv`
+- `Alphaplan...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.
diff --git a/TrafagSalesExporter/docs/FINANCE_DATENFLUSS_ANDREAS_2026-06-08.md b/TrafagSalesExporter/docs/FINANCE_DATENFLUSS_ANDREAS_2026-06-08.md
new file mode 100644
index 0000000..e37829b
--- /dev/null
+++ b/TrafagSalesExporter/docs/FINANCE_DATENFLUSS_ANDREAS_2026-06-08.md
@@ -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__.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 =
+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_.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 `/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__.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_.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.
+
diff --git a/TrafagSalesExporter/lastchange.md b/TrafagSalesExporter/lastchange.md
index a809601..5cd01a6 100644
--- a/TrafagSalesExporter/lastchange.md
+++ b/TrafagSalesExporter/lastchange.md
@@ -1,6 +1,6 @@
# Last Change
-Stand: 2026-06-05
+Stand: 2026-06-08
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`.
- Themenrouter: `docs/RAG_ROUTER.md`.
-- Letzter dokumentierter Code-Stand: Finance-Dashboard-Vereinfachung, Expertenbereich mit 3D-Datenanalyse und Spanien-Sage-All-in-one-rclone-Upload.
-- Letzte dokumentierte Validierung: `dotnet test TrafagSalesExporter.sln --verbosity minimal --artifacts-path C:\TMP\trafag-test-artifacts-finance-session-proof` mit `82/82` Tests gruen.
+- 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 --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: `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.
@@ -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.
- 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
Quelle: