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: