Document finance and mapping updates

This commit is contained in:
2026-06-02 18:06:30 +02:00
parent 6470cb8751
commit 37a175551b
9 changed files with 511 additions and 33 deletions
Binary file not shown.
@@ -1,9 +1,15 @@
param( param(
[string]$ServerInstance = "localhost", [string]$ServerInstance = "localhost",
[string]$Database = "Sage", [string]$Database = "Sage",
[ValidateSet("Full", "Range")]
[string]$ExportMode = "Full",
[ValidateSet("InvoiceDate", "LineRegistrationDate")]
[string]$DateFilter = "InvoiceDate",
[int]$Year = 2025,
[datetime]$FromDate = "2025-01-01", [datetime]$FromDate = "2025-01-01",
[datetime]$ToDate = "2026-01-01", [datetime]$ToDate = "2026-01-01",
[string]$OutputDirectory = (Join-Path $env:USERPROFILE "Desktop") [string]$OutputDirectory = (Join-Path $env:USERPROFILE "Desktop"),
[string]$OutputFileName = ""
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
@@ -97,12 +103,41 @@ function Export-QueryToCsv {
} }
} }
if ($ExportMode -eq "Full") {
$FromDate = [datetime]::new($Year, 1, 1)
$ToDate = $FromDate.AddYears(1)
}
else {
if (-not $PSBoundParameters.ContainsKey("ToDate")) {
throw "Range export requires -ToDate. Example: -ExportMode Range -FromDate '2025-05-01' -ToDate '2025-06-01'"
}
}
if ($ToDate.Date -le $FromDate.Date) {
throw "ToDate must be later than FromDate. FromDate=$($FromDate.ToString("yyyy-MM-dd")), ToDate=$($ToDate.ToString("yyyy-MM-dd"))"
}
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss" $timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$runDirectory = Join-Path $OutputDirectory "Sage_Spain_Sales_Export_$timestamp" $runDirectory = Join-Path $OutputDirectory "Sage_Spain_Sales_Export_$timestamp"
New-Item -ItemType Directory -Path $runDirectory -Force | Out-Null New-Item -ItemType Directory -Path $runDirectory -Force | Out-Null
$csvPath = Join-Path $runDirectory "Spain_Sales_2025.csv" if ([string]::IsNullOrWhiteSpace($OutputFileName)) {
$summaryPath = Join-Path $runDirectory "Spain_Sales_2025_summary.txt" $fromToken = $FromDate.ToString("yyyyMMdd")
$toToken = $ToDate.Date.AddDays(-1).ToString("yyyyMMdd")
$kindToken = $ExportMode.ToLowerInvariant()
$OutputFileName = "Spain_Sales_${kindToken}_${fromToken}_to_${toToken}.csv"
}
$csvPath = Join-Path $runDirectory $OutputFileName
$summaryPath = Join-Path $runDirectory ([System.IO.Path]::GetFileNameWithoutExtension($OutputFileName) + "_summary.txt")
$datePredicate = if ($DateFilter -eq "LineRegistrationDate") {
"COALESCE(l.FechaRegistro, c.FechaFactura) >= @FromDate
AND COALESCE(l.FechaRegistro, c.FechaFactura) < @ToDate"
} else {
"c.FechaFactura >= @FromDate
AND c.FechaFactura < @ToDate"
}
$sql = @" $sql = @"
SELECT SELECT
@@ -170,8 +205,7 @@ JOIN dbo.LineasAlbaranCliente l
AND l.EjercicioAlbaran = c.EjercicioAlbaran AND l.EjercicioAlbaran = c.EjercicioAlbaran
AND l.SerieAlbaran = c.SerieAlbaran AND l.SerieAlbaran = c.SerieAlbaran
AND l.NumeroAlbaran = c.NumeroAlbaran AND l.NumeroAlbaran = c.NumeroAlbaran
WHERE c.FechaFactura >= @FromDate WHERE $datePredicate
AND c.FechaFactura < @ToDate
ORDER BY ORDER BY
c.FechaFactura, c.FechaFactura,
c.SerieFactura, c.SerieFactura,
@@ -188,6 +222,8 @@ Sage Spain Sales CSV export
Created: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Created: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
Server instance: $ServerInstance Server instance: $ServerInstance
Database: $Database Database: $Database
Export mode: $ExportMode
Date filter mode: $DateFilter
From date: $($FromDate.ToString("yyyy-MM-dd")) From date: $($FromDate.ToString("yyyy-MM-dd"))
To date: $($ToDate.ToString("yyyy-MM-dd")) To date: $($ToDate.ToString("yyyy-MM-dd"))
@@ -204,14 +240,15 @@ Source:
dbo.CabeceraAlbaranCliente joined with dbo.LineasAlbaranCliente dbo.CabeceraAlbaranCliente joined with dbo.LineasAlbaranCliente
Filter: Filter:
CabeceraAlbaranCliente.FechaFactura >= FromDate $datePredicate
CabeceraAlbaranCliente.FechaFactura < ToDate
Notes: Notes:
- Currency is set to EUR because Sage exports EnEuros_=-1 and CodigoDivisa is empty in the analysed rows. - Currency is set to EUR because Sage exports EnEuros_=-1 and CodigoDivisa is empty in the analysed rows.
- SalesPriceValue uses LineasAlbaranCliente.ImporteNeto; credit notes are forced negative. - SalesPriceValue uses LineasAlbaranCliente.ImporteNeto; credit notes are forced negative.
- DocumentNetAmount uses CabeceraAlbaranCliente.BaseImponible; credit notes are forced negative. - DocumentNetAmount uses CabeceraAlbaranCliente.BaseImponible; credit notes are forced negative.
- Credit notes are marked when TipoNuevaFra=2, SerieFactura='REC', or StatusAbono is non-zero. - Credit notes are marked when TipoNuevaFra=2, SerieFactura='REC', or StatusAbono is non-zero.
- Full exports use the complete selected year.
- Range exports use the explicit FromDate/ToDate window.
"@ | Set-Content -LiteralPath $summaryPath -Encoding UTF8 "@ | Set-Content -LiteralPath $summaryPath -Encoding UTF8
Write-Host "Created:" Write-Host "Created:"
@@ -8,14 +8,27 @@ PowerShell commands:
Set-ExecutionPolicy -Scope Process Bypass Set-ExecutionPolicy -Scope Process Bypass
.\Export-SageSpainSalesCsv.ps1 .\Export-SageSpainSalesCsv.ps1
Full export, default year 2025:
.\Export-SageSpainSalesCsv.ps1 -ExportMode Full -Year 2025
Range export, explicit window:
.\Export-SageSpainSalesCsv.ps1 -ExportMode Range -FromDate "2025-05-01" -ToDate "2025-06-01"
Range export by registration date, useful for new/changed records registered in a period:
.\Export-SageSpainSalesCsv.ps1 -ExportMode Range -DateFilter LineRegistrationDate -FromDate "2025-05-01" -ToDate "2025-06-01"
Output folder on Desktop: Output folder on Desktop:
Sage_Spain_Sales_Export_YYYYMMDD_HHMMSS Sage_Spain_Sales_Export_YYYYMMDD_HHMMSS
Files created: Files created:
- Spain_Sales_2025.csv - Spain_Sales_full_YYYY0101_to_YYYY1231.csv for full export
- Spain_Sales_2025_summary.txt - Spain_Sales_range_YYYYMMDD_to_YYYYMMDD.csv for range export
- Matching *_summary.txt file
The script only reads SQL Server data. It does not change Sage or SQL Server. The script only reads SQL Server data. It does not change Sage or SQL Server.
@@ -24,9 +37,12 @@ Default source:
- Database: Sage - Database: Sage
- Header: dbo.CabeceraAlbaranCliente - Header: dbo.CabeceraAlbaranCliente
- Lines: dbo.LineasAlbaranCliente - Lines: dbo.LineasAlbaranCliente
- Date filter: CabeceraAlbaranCliente.FechaFactura from 2025-01-01 to 2026-01-01 - Full export date filter: CabeceraAlbaranCliente.FechaFactura from YYYY-01-01 to next YYYY-01-01
- Range export date filter: explicit FromDate/ToDate
- DateFilter InvoiceDate uses CabeceraAlbaranCliente.FechaFactura
- DateFilter LineRegistrationDate uses LineasAlbaranCliente.FechaRegistro, with fallback to FechaFactura
- Sales value: LineasAlbaranCliente.ImporteNeto - Sales value: LineasAlbaranCliente.ImporteNeto
If the SQL instance or database name differs: If the SQL instance or database name differs:
.\Export-SageSpainSalesCsv.ps1 -ServerInstance "localhost" -Database "Sage" .\Export-SageSpainSalesCsv.ps1 -ServerInstance "localhost" -Database "Sage" -ExportMode Full -Year 2025
@@ -932,27 +932,38 @@ public class DatabaseSeedService : IDatabaseSeedService
private static void EnsureBudgetExchangeRateDefaults(AppDbContext db) private static void EnsureBudgetExchangeRateDefaults(AppDbContext db)
{ {
var defaults = new (string From, string To, decimal Rate)[] var defaults = new (int Year, string From, string To, decimal Rate)[]
{ {
("CHF", "CHF", 1m), (2025, "CHF", "CHF", 1m),
("USD", "CHF", 0.85m), (2025, "USD", "CHF", 0.85m),
("EUR", "CHF", 0.95m), (2025, "EUR", "CHF", 0.95m),
("GBP", "CHF", 1.13m), (2025, "GBP", "CHF", 1.13m),
("CNY", "CHF", 1m / 8.50m), (2025, "CNY", "CHF", 1m / 8.50m),
("INR", "CHF", 1m / 90.91m), (2025, "INR", "CHF", 1m / 90.91m),
("CZK", "CHF", 1m / 25.64m), (2025, "CZK", "CHF", 1m / 25.64m),
("PLN", "CHF", 0.22m), (2025, "PLN", "CHF", 0.22m),
("JPY", "CHF", 1m / 156.25m) (2025, "JPY", "CHF", 1m / 156.25m),
(2026, "CHF", "CHF", 1m),
(2026, "USD", "CHF", 0.80m),
(2026, "EUR", "CHF", 0.94m),
(2026, "GBP", "CHF", 1.09m),
(2026, "CNY", "CHF", 1m / 8.50m),
(2026, "INR", "CHF", 1m / 110m),
(2026, "CZK", "CHF", 1m / 26m),
(2026, "PLN", "CHF", 0.22m),
(2026, "JPY", "CHF", 1m / 175m)
}; };
var changed = false; var changed = false;
foreach (var item in defaults) foreach (var item in defaults)
{ {
var validFrom = new DateTime(item.Year, 1, 1);
var notes = $"Budget {item.Year}";
var exists = db.CurrencyExchangeRates.Any(x => var exists = db.CurrencyExchangeRates.Any(x =>
x.FromCurrency == item.From && x.FromCurrency == item.From &&
x.ToCurrency == item.To && x.ToCurrency == item.To &&
x.ValidFrom == new DateTime(2025, 1, 1) && x.ValidFrom == validFrom &&
x.Notes == "Budget 2025"); x.Notes == notes);
if (exists) if (exists)
continue; continue;
@@ -961,9 +972,9 @@ public class DatabaseSeedService : IDatabaseSeedService
FromCurrency = item.From, FromCurrency = item.From,
ToCurrency = item.To, ToCurrency = item.To,
Rate = item.Rate, Rate = item.Rate,
ValidFrom = new DateTime(2025, 1, 1), ValidFrom = validFrom,
ValidTo = new DateTime(2025, 12, 31), ValidTo = new DateTime(item.Year, 12, 31),
Notes = "Budget 2025", Notes = notes,
IsActive = true IsActive = true
}); });
changed = true; changed = true;
@@ -0,0 +1,251 @@
# Finance Sitzungspunkte Andreas
Stand: 2026-06-02
Zweck: Kompakte Zusammenfassung der letzten Finance-/Andreas-Punkte und was davon bereits umgesetzt bzw. noch offen ist.
## Kurzfazit
- Das Finance Dashboard bleibt technisch produktiv nutzbar.
- Fuehrende Sicht fuer Soll/Ist bleibt `Finance Summary`.
- `Management Analyse` bleibt Diagnose: Laender, IC/2nd-party, Rohdaten, Abweichungen, Spartenanalyse.
- IC wird nicht automatisch aus dem Standard-Ist entfernt.
- Budgetkurse 2026 sind im Programm und auf dem Server eingespielt.
- Spanien-Sage-Serverexport kann jetzt Full oder Range exportieren.
## Letzte Sitzungspunkte
| Thema | Punkt von Andreas / Finance | Stand |
| --- | --- | --- |
| Intercompany | Pro Land klaeren, ob IC bereits in Quelle/Sollwert herausgerechnet ist. | Teilweise geklaert: FR, IN, US passen ohne IC-Abgrenzung; IT braucht Sonderabgrenzung; DE, UK, CH, AT bleiben offen. |
| Spanien Sollwert | ES hat keine echte Ist-Abweichung; alter Sollwert war falsch. | Fachlich dokumentiert: ES-Ist und korrigierter Sollwert `3'082'320.18 EUR`. |
| Spanien Export | Nicht jedes Mal Volljahr exportieren, wenn bereits Basisexport vorhanden ist. | Sage-Server-Script unterstuetzt jetzt `-ExportMode Full` und `-ExportMode Range`. |
| Wechselkurse | Offizieller Kurstyp und Kursdatum klaeren. | Budgetkurse 2026 wurden als Budgetkurse eingetragen. Kursdatum bleibt in Settings konfigurierbar. |
| Spartenanalyse | >90% nicht zugeordnet ist fachlich unplausibel. | Aktuelle Daten zeigen: Spartenfelder sind fast nur bei CH und teilweise AT gefuellt. Viele Laender haben keine Produktspartenfelder in `CentralSalesRecords`; deshalb ist es ein Mapping-/Referenzproblem, nicht fachlich "nur 10% Umsatz mit Sparte". |
| DE | Welche Kundenlaender / Filter gehoeren offiziell zum deutschen Ist? | Offen. Nicht einfach IC abziehen; Hauptfrage sind Kundenlaender/Filter. |
| UK | Sage-Differenz klaeren. | Offen: Exportvollstaendigkeit, Discounts, Freight/Charges und 2nd-party/IC pruefen. |
| IT | Neue IT-Abgrenzung pruefen. | IT ist Sonderfall: `Trafag Italia` aus externem IT-Ist ausschliessen; doppelte Einzelpositionen mit leerem Supplier country nur einmal zaehlen. |
| CH / AT | `FKDAT` als Perioden-/Buchungsdatum bestaetigen. | Offen. |
| Kosten | Entscheiden, ob Kosten/Marge Teil des Dashboards werden. | Offen; weiterhin nicht in Umsatzfreigabe mischen. |
## IC-Stand je Land
| Land | Aussage | Filter / Abgrenzung |
| --- | --- | --- |
| FR | Ohne IC-Abgrenzung stimmt der Soll/Ist-Vergleich. | IC-Filter ignorieren; B1 Positions-Netto verwenden. |
| IN | Ohne IC-Abgrenzung stimmt der Soll/Ist-Vergleich. | IC-Filter ignorieren; INR-Hauswaehrung verwenden. |
| US | Ohne IC-Abgrenzung stimmt der Soll/Ist-Vergleich. | IC-Filter ignorieren; B1 Positions-Netto verwenden. |
| IT | Ohne Abgrenzung stimmt es nicht sauber. | IT-Sonderabgrenzung anwenden: `Trafag Italia` aus externem Ist ausschliessen; Duplikate mit leerem `Supplier country` nur einmal zaehlen. |
| ES | IC ist aktuell nicht der relevante Punkt. | Sage `ImporteNeto`; Credit Notes/REC negativ; korrigierter Sollwert `3'082'320.18 EUR`. |
| DE | Nicht final beurteilbar. | Kundenlaender/Kundenfilter offen; nicht einfach IC abziehen. |
| UK | Nicht final beurteilbar. | Sage-Export, Discounts/Freight/Charges und 2nd-party/IC pruefen. |
| CH | Nicht beurteilbar. | Kein Sollwert im Seed. |
| AT | Abweichung vorhanden, Ursache unklar. | IC nicht automatisch abziehen; Filter/Datum weiter pruefen. |
## Spanien Sage Server Export
Relevant ist das Paket:
```text
SageSpainFinalExportPackage.zip
```
Wichtige Datei im Paket:
```text
Export-SageSpainSalesCsv.ps1
```
Full Export:
```powershell
.\Export-SageSpainSalesCsv.ps1 -ExportMode Full -Year 2025
```
Range Export:
```powershell
.\Export-SageSpainSalesCsv.ps1 -ExportMode Range -FromDate "2025-05-01" -ToDate "2025-06-01"
```
Range nach Registrierungsdatum:
```powershell
.\Export-SageSpainSalesCsv.ps1 -ExportMode Range -DateFilter LineRegistrationDate -FromDate "2025-05-01" -ToDate "2025-06-01"
```
Hinweis:
- `ToDate` ist exklusiv.
- Wenn der alte Vollbestand bereits vorhanden ist, braucht Spanien fuer Folgeupdates nur noch Range-Dateien fuer den neuen oder korrigierten Zeitraum.
- Das Sage-Script liest nur Daten aus SQL Server und aendert nichts in Sage.
- Unser Importprozess wird danach separat entschieden; der Serverexport ist jetzt nur die Dateierzeugung.
## Spartenanalyse / Produktmapping
### Warum nur rund 10% Abdeckung?
Die Spartenanalyse zaehlt Umsatz nur als sauber zugeordnet, wenn eine Materialnummer gegen die zentrale TR-AG-/SAP-Referenz gematcht wird und dort eine gueltige Produktsparte vorhanden ist.
Aktueller Befund aus der lokalen Datenbank:
| Kennzahl | Wert |
| --- | ---: |
| `CentralSalesRecords` total | `75'089` |
| Zeilen 2025 nach Invoice/Extraction | `54'682` |
| Zeilen mit Produktsparte-Code | `36'847` |
| Zeilen mit gueltigem Assigned-Flag | `27'047` |
| Zeilen mit gueltiger Produktsparte | `27'047` |
| Zeilen `UNASS` | `9'800` |
| Zeilen ohne Materialnummer | `59` |
Laenderbefund:
| Land | Zeilen | Gueltig zugeordnet | `UNASS` | Keine Produktfelder |
| --- | ---: | ---: | ---: | ---: |
| CH | `38'838` | `26'337` | `9'187` | `3'314` |
| AT | `1'454` | `710` | `613` | `131` |
| Italien | `16'850` | `0` | `0` | `16'850` |
| Indien | `5'515` | `0` | `0` | `5'515` |
| Deutschland | `4'548` | `0` | `0` | `4'548` |
| Spanien | `4'341` | `0` | `0` | `4'341` |
| Frankreich | `2'285` | `0` | `0` | `2'285` |
| USA | `1'256` | `0` | `0` | `1'256` |
| England | `2` | `0` | `0` | `2` |
Interpretation:
- Die niedrige Abdeckung ist nicht als fachliche Umsatzverteilung zu lesen.
- Sie zeigt, dass das zentrale Produktmapping aktuell nur fuer CH und teilweise AT in den Daten ankommt.
- Bei IT, IN, DE, ES, FR, US und UK sind die Produktspartenfelder aktuell leer.
- Wahrscheinliche Ursachen: lokale Materialnummern passen nicht direkt zu TR-AG `MATNR`, fehlende Referenzdaten in `ProductDivisionRefSet`, Join trifft nicht oder Produktfelder werden bei diesen Laendern nicht befuellt.
### Prozessfluss vom Holen bis Anwenden
```text
1. SAP/TR-AG stellt zentrale Produktreferenz bereit
EntitySet: ProductDivisionRefSet
Felder: Matnr, Paph1, Paph1Text, Wwpfa, WwpfaText, Wwpsp, WwpspText, IsAssigned
2. Webprogramm holt beim SAP-Standort zwei Quellen
Z = FinanzdataSchweizOeSet
P = ProductDivisionRefSet
3. Join im Web-Exporter
Z.Matnr = P.Matnr
JoinType = Left
4. Mapping ins interne SalesRecord-Modell
Z.Matnr -> Material
Z.Prodh -> ProductGroup
P.Paph1 -> ProductHierarchyCode
P.Paph1Text -> ProductHierarchyText
P.Wwpfa -> ProductFamilyCode
P.WwpfaText -> ProductFamilyText
P.Wwpsp -> ProductDivisionCode
P.WwpspText -> ProductDivisionText
P.IsAssigned -> ProductMappingAssigned
5. Speicherung in CentralSalesRecords
Die Produktfelder werden mit jeder Umsatzzeile gespeichert.
6. Management Analyse > Spartenanalyse
Statuslogik:
- Material leer -> Material fehlt
- kein Treffer in zentraler Referenz -> Nicht im TR-AG-Stamm
- Treffer, aber IsAssigned nicht wahr oder Division = UNASS -> Nicht zugeordnet
- Treffer, IsAssigned wahr und Division nicht UNASS -> Zugeordnet
7. Sparten-Finanzanalyse
Nur Status Zugeordnet geht in Umsatz nach Produktsparte / Produktfamilie / PAPH1.
```
### SAP-Objektnamen und Code-Stand
| Objekt | Name |
| --- | --- |
| SAP Gateway Service | `ZPOWERBI_EINKAUF_SRV` |
| Sales EntitySet | `FinanzdataSchweizOeSet` |
| Sales Gateway-Methode | `FINANZDATASCHWEI_GET_ENTITYSET` |
| Produktsparten EntitySet | `ProductDivisionRefSet` |
| Produktsparten Gateway-Methode | `PRODUCTDIVISIONR_GET_ENTITYSET` |
| Provider-Klasse | `ZCL_PRODSPARTE_PROVIDER` |
| Mapping-Aufbau-Report | `Z_PRODSPARTE_MAP_BUILD` |
| Test-/ALV-Report | `Z_PRODSPARTE_REPORT` |
| Mapping-Tabelle | `ZPRODSPARTE_MAP` |
| DDIC-Struktur | `ZSTR_PRODSPARTE_OUT` |
Vorhandener Code im Repo:
- `docs/abap/ZCL_PRODSPARTE_PROVIDER.abap`
- `docs/abap/Z_PRODSPARTE_MAP_BUILD.abap`
- `docs/abap/Z_PRODSPARTE_REPORT.abap`
Nicht als vollstaendiger Code im Repo vorhanden:
- `FINANZDATASCHWEI_GET_ENTITYSET`
- `PRODUCTDIVISIONR_GET_ENTITYSET`
Diese beiden Gateway-Methoden sind in den MDs beschrieben, aber der komplette DPC_EXT-Methodencode muss aus SAP geholt oder neu formuliert werden.
## Budgetkurse
### Budget 2026
Budget 2026 wurde eingetragen und deployed:
| Von | Nach | Rate |
| --- | --- | ---: |
| CHF | CHF | 1 |
| USD | CHF | 0.80 |
| EUR | CHF | 0.94 |
| GBP | CHF | 1.09 |
| CNY | CHF | 0.11764706 |
| INR | CHF | 0.00909091 |
| CZK | CHF | 0.03846154 |
| PLN | CHF | 0.22 |
| JPY | CHF | 0.00571429 |
Technische Ablage:
```text
CurrencyExchangeRates
Notes = Budget 2026
ValidFrom = 2026-01-01
ValidTo = 2026-12-31
```
### Budget 2025
Andreas hat neue Budget-2025-Werte geliefert. Noch nicht final eingetragen, weil CNY noch fehlt:
| Waehrung | Rate nach CHF |
| --- | ---: |
| EUR | 0.937034700 |
| GBP | 1.093822350 |
| INR | 0.009532034 |
| PLN | 0.221032130 |
| RUB | 0.009958327 |
| CZK | 0.037963103 |
| JPY | 0.005553195 |
| USD | 0.830651790 |
| CNY | offen |
## Deploy-Stand
Deploy wurde am 2026-06-02 ausgefuehrt:
- Publish nach `\\trch-webapp-bidashboard.trafagch.local\BiDashboard$\`
- `BiDashboard.dll` Zeitstempel nach Deploy: `2026-06-02 15:15:20`
- Server-DB enthaelt `9` Budget-2026-Kurse.
- Testlauf vor Deploy: `82/82` Tests gruen.
## Naechste klare To-dos
1. CNY Budget 2025 von Andreas nachliefern lassen.
2. Budget 2025 Seedwerte danach gesammelt aktualisieren und deployen.
3. DE: offizielle Kundenlaender/Kundenfilter bestaetigen.
4. UK: Sage-Exportvollstaendigkeit und Discounts/Freight/Charges pruefen.
5. CH/AT: `FKDAT` als fachliches Periodendatum bestaetigen.
6. Spartenmapping weiter pruefen: Gateway-Methodencode `PRODUCTDIVISIONR_GET_ENTITYSET` aus SAP holen oder sauber neu formulieren; danach pruefen, warum Produktfelder nur bei CH/AT gefuellt sind.
7. Entscheiden, ob CHF-Sicht nur Reporting bleibt oder offizieller Vergleichswert wird.
@@ -508,3 +508,108 @@ Deploy:
- `ProductRows = 36'847` - `ProductRows = 36'847`
- `TR-AG Referenzmaterialien = 6'805` - `TR-AG Referenzmaterialien = 6'805`
- `P ProductDivisionRefSet` aktiv. - `P ProductDivisionRefSet` aktiv.
## Nachtrag 2026-06-02 Prozessfluss Und Offene Gateway-Codes
### Prozess Vom Holen Bis Anwenden
```text
SAP/TR-AG
|
| ProductDivisionRefSet
| Matnr, Paph1, Paph1Text, Wwpfa, WwpfaText, Wwpsp, WwpspText, IsAssigned
v
Webprogramm / SAP-Komposition
|
| Quelle Z = FinanzdataSchweizOeSet
| Quelle P = ProductDivisionRefSet
| Join = Z.Matnr = P.Matnr, Left Join
v
SalesRecord
|
| ProductHierarchyCode/Text
| ProductFamilyCode/Text
| ProductDivisionCode/Text
| ProductMappingAssigned
v
CentralSalesRecords
|
v
Management Analyse > Spartenanalyse
|
| Zugeordnet / Nicht zugeordnet / Nicht im TR-AG-Stamm / Material fehlt
v
Sparten-Finanzanalyse
|
| Umsatz nach Produktsparte nur fuer Status Zugeordnet
```
### Technische Objektliste
| Rolle | Name |
| --- | --- |
| Gateway Service | `ZPOWERBI_EINKAUF_SRV` |
| Sales EntitySet | `FinanzdataSchweizOeSet` |
| Sales Gateway-Methode | `FINANZDATASCHWEI_GET_ENTITYSET` |
| Produktsparten EntitySet | `ProductDivisionRefSet` |
| Produktsparten Gateway-Methode | `PRODUCTDIVISIONR_GET_ENTITYSET` |
| Produktsparten Provider | `ZCL_PRODSPARTE_PROVIDER` |
| Mapping-Aufbau | `Z_PRODSPARTE_MAP_BUILD` |
| ALV-Test | `Z_PRODSPARTE_REPORT` |
| Persistente Mapping-Tabelle | `ZPRODSPARTE_MAP` |
| Gateway/DDIC-Struktur | `ZSTR_PRODSPARTE_OUT` |
### Code-Stand Im Repository
Vollstaendig als ABAP-Dateien vorhanden:
- `docs/abap/ZCL_PRODSPARTE_PROVIDER.abap`
- `docs/abap/Z_PRODSPARTE_MAP_BUILD.abap`
- `docs/abap/Z_PRODSPARTE_REPORT.abap`
Nur dokumentiert, aber nicht als kompletter Gateway-Methodencode vorhanden:
- `FINANZDATASCHWEI_GET_ENTITYSET`
- `PRODUCTDIVISIONR_GET_ENTITYSET`
Fachlich gilt:
- `FINANZDATASCHWEI_GET_ENTITYSET` muss den bestehenden Sales-Select aus `ZSCHWEIZ` behalten.
- Produktspartenlogik gehoert in `PRODUCTDIVISIONR_GET_ENTITYSET`.
- `PRODUCTDIVISIONR_GET_ENTITYSET` soll die flache Referenz fuer `ProductDivisionRefSet` liefern, idealerweise ueber `ZCL_PRODSPARTE_PROVIDER->GET_DATA( )`.
### Befund Zur Niedrigen Abdeckung
Die Sparten-Finanzanalyse zeigt aktuell nur eine niedrige zugeordnete Umsatzabdeckung. Das ist nicht als fachliche Aussage "nur 10% des Umsatzes hat eine Sparte" zu lesen, sondern als Mapping-/Referenzbefund.
Lokaler DB-Stand 2026-06-02:
| Kennzahl | Wert |
| --- | ---: |
| `CentralSalesRecords` total | `75'089` |
| Zeilen mit Produktsparte-Code | `36'847` |
| Zeilen mit gueltigem Assigned-Flag | `27'047` |
| Zeilen mit gueltiger Produktsparte | `27'047` |
| Zeilen `UNASS` | `9'800` |
| Zeilen ohne Materialnummer | `59` |
Laenderdetail:
| Land | Zeilen | Gueltig zugeordnet | `UNASS` | Keine Produktfelder |
| --- | ---: | ---: | ---: | ---: |
| CH | `38'838` | `26'337` | `9'187` | `3'314` |
| AT | `1'454` | `710` | `613` | `131` |
| Italien | `16'850` | `0` | `0` | `16'850` |
| Indien | `5'515` | `0` | `0` | `5'515` |
| Deutschland | `4'548` | `0` | `0` | `4'548` |
| Spanien | `4'341` | `0` | `0` | `4'341` |
| Frankreich | `2'285` | `0` | `0` | `2'285` |
| USA | `1'256` | `0` | `0` | `1'256` |
| England | `2` | `0` | `0` | `2` |
Naechste Pruefpunkte:
- Gateway-Methodencode `PRODUCTDIVISIONR_GET_ENTITYSET` aus SAP sichern oder im Repo nachbauen.
- Pruefen, ob `ProductDivisionRefSet` vollstaendig genug ist.
- Pruefen, warum Produktfelder bei IT, IN, DE, ES, FR, US und UK leer bleiben.
- Klaeren, ob fuer lokale Materialnummern eine zusaetzliche Cross-Reference gegen TR-AG-Materialnummern notwendig ist.
@@ -100,6 +100,27 @@ Stand: 2026-05-29
- UNASS -> `HelpOutline` - UNASS -> `HelpOutline`
- sonst -> `Category` - sonst -> `Category`
- Finance-Schulung hat einen neuen Tab `Spartenanalyse`. - Finance-Schulung hat einen neuen Tab `Spartenanalyse`.
## Stand 2026-06-02
- Niedrige Spartenabdeckung ist als Mapping-/Referenzproblem zu lesen, nicht als fachliche Umsatzverteilung.
- Lokaler DB-Befund: `75'089` CentralSalesRecords, davon `27'047` mit gueltiger zugeordneter Produktsparte und `9'800` mit `UNASS`.
- Produktfelder sind aktuell fast nur bei CH und teilweise AT gefuellt; IT, IN, DE, ES, FR, US und UK haben in den Daten keine Produktspartenfelder.
- Prozessfluss:
- SAP Service `ZPOWERBI_EINKAUF_SRV`
- Sales EntitySet `FinanzdataSchweizOeSet`
- Produktmapping EntitySet `ProductDivisionRefSet`
- Web-Join `Z.Matnr = P.Matnr`
- Speicherung in `CentralSalesRecords`
- Anwendung in `Management Analyse > Spartenanalyse`
- Vollstaendige ABAP-Dateien im Repo:
- `docs/abap/ZCL_PRODSPARTE_PROVIDER.abap`
- `docs/abap/Z_PRODSPARTE_MAP_BUILD.abap`
- `docs/abap/Z_PRODSPARTE_REPORT.abap`
- Nicht als kompletter Gateway-Methodencode im Repo vorhanden:
- `FINANZDATASCHWEI_GET_ENTITYSET`
- `PRODUCTDIVISIONR_GET_ENTITYSET`
- Naechster Schritt: Gateway-Methodencode aus SAP sichern oder `PRODUCTDIVISIONR_GET_ENTITYSET` sauber neu formulieren.
- Browser-Favicon wurde ergaenzt: `wwwroot/favicon.svg`. - Browser-Favicon wurde ergaenzt: `wwwroot/favicon.svg`.
- Letzter dokumentierter Deploy: 2026-05-29 13:47, Tests `80/80` gruen. - Letzter dokumentierter Deploy: 2026-05-29 13:47, Tests `80/80` gruen.
@@ -1,9 +1,15 @@
param( param(
[string]$ServerInstance = "localhost", [string]$ServerInstance = "localhost",
[string]$Database = "Sage", [string]$Database = "Sage",
[ValidateSet("Full", "Range")]
[string]$ExportMode = "Full",
[ValidateSet("InvoiceDate", "LineRegistrationDate")]
[string]$DateFilter = "InvoiceDate",
[int]$Year = 2025,
[datetime]$FromDate = "2025-01-01", [datetime]$FromDate = "2025-01-01",
[datetime]$ToDate = "2026-01-01", [datetime]$ToDate = "2026-01-01",
[string]$OutputDirectory = (Join-Path $env:USERPROFILE "Desktop") [string]$OutputDirectory = (Join-Path $env:USERPROFILE "Desktop"),
[string]$OutputFileName = ""
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
@@ -97,12 +103,41 @@ function Export-QueryToCsv {
} }
} }
if ($ExportMode -eq "Full") {
$FromDate = [datetime]::new($Year, 1, 1)
$ToDate = $FromDate.AddYears(1)
}
else {
if (-not $PSBoundParameters.ContainsKey("ToDate")) {
throw "Range export requires -ToDate. Example: -ExportMode Range -FromDate '2025-05-01' -ToDate '2025-06-01'"
}
}
if ($ToDate.Date -le $FromDate.Date) {
throw "ToDate must be later than FromDate. FromDate=$($FromDate.ToString("yyyy-MM-dd")), ToDate=$($ToDate.ToString("yyyy-MM-dd"))"
}
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss" $timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
$runDirectory = Join-Path $OutputDirectory "Sage_Spain_Sales_Export_$timestamp" $runDirectory = Join-Path $OutputDirectory "Sage_Spain_Sales_Export_$timestamp"
New-Item -ItemType Directory -Path $runDirectory -Force | Out-Null New-Item -ItemType Directory -Path $runDirectory -Force | Out-Null
$csvPath = Join-Path $runDirectory "Spain_Sales_2025.csv" if ([string]::IsNullOrWhiteSpace($OutputFileName)) {
$summaryPath = Join-Path $runDirectory "Spain_Sales_2025_summary.txt" $fromToken = $FromDate.ToString("yyyyMMdd")
$toToken = $ToDate.Date.AddDays(-1).ToString("yyyyMMdd")
$kindToken = $ExportMode.ToLowerInvariant()
$OutputFileName = "Spain_Sales_${kindToken}_${fromToken}_to_${toToken}.csv"
}
$csvPath = Join-Path $runDirectory $OutputFileName
$summaryPath = Join-Path $runDirectory ([System.IO.Path]::GetFileNameWithoutExtension($OutputFileName) + "_summary.txt")
$datePredicate = if ($DateFilter -eq "LineRegistrationDate") {
"COALESCE(l.FechaRegistro, c.FechaFactura) >= @FromDate
AND COALESCE(l.FechaRegistro, c.FechaFactura) < @ToDate"
} else {
"c.FechaFactura >= @FromDate
AND c.FechaFactura < @ToDate"
}
$sql = @" $sql = @"
SELECT SELECT
@@ -170,8 +205,7 @@ JOIN dbo.LineasAlbaranCliente l
AND l.EjercicioAlbaran = c.EjercicioAlbaran AND l.EjercicioAlbaran = c.EjercicioAlbaran
AND l.SerieAlbaran = c.SerieAlbaran AND l.SerieAlbaran = c.SerieAlbaran
AND l.NumeroAlbaran = c.NumeroAlbaran AND l.NumeroAlbaran = c.NumeroAlbaran
WHERE c.FechaFactura >= @FromDate WHERE $datePredicate
AND c.FechaFactura < @ToDate
ORDER BY ORDER BY
c.FechaFactura, c.FechaFactura,
c.SerieFactura, c.SerieFactura,
@@ -188,6 +222,8 @@ Sage Spain Sales CSV export
Created: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Created: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
Server instance: $ServerInstance Server instance: $ServerInstance
Database: $Database Database: $Database
Export mode: $ExportMode
Date filter mode: $DateFilter
From date: $($FromDate.ToString("yyyy-MM-dd")) From date: $($FromDate.ToString("yyyy-MM-dd"))
To date: $($ToDate.ToString("yyyy-MM-dd")) To date: $($ToDate.ToString("yyyy-MM-dd"))
@@ -204,14 +240,15 @@ Source:
dbo.CabeceraAlbaranCliente joined with dbo.LineasAlbaranCliente dbo.CabeceraAlbaranCliente joined with dbo.LineasAlbaranCliente
Filter: Filter:
CabeceraAlbaranCliente.FechaFactura >= FromDate $datePredicate
CabeceraAlbaranCliente.FechaFactura < ToDate
Notes: Notes:
- Currency is set to EUR because Sage exports EnEuros_=-1 and CodigoDivisa is empty in the analysed rows. - Currency is set to EUR because Sage exports EnEuros_=-1 and CodigoDivisa is empty in the analysed rows.
- SalesPriceValue uses LineasAlbaranCliente.ImporteNeto; credit notes are forced negative. - SalesPriceValue uses LineasAlbaranCliente.ImporteNeto; credit notes are forced negative.
- DocumentNetAmount uses CabeceraAlbaranCliente.BaseImponible; credit notes are forced negative. - DocumentNetAmount uses CabeceraAlbaranCliente.BaseImponible; credit notes are forced negative.
- Credit notes are marked when TipoNuevaFra=2, SerieFactura='REC', or StatusAbono is non-zero. - Credit notes are marked when TipoNuevaFra=2, SerieFactura='REC', or StatusAbono is non-zero.
- Full exports use the complete selected year.
- Range exports use the explicit FromDate/ToDate window.
"@ | Set-Content -LiteralPath $summaryPath -Encoding UTF8 "@ | Set-Content -LiteralPath $summaryPath -Encoding UTF8
Write-Host "Created:" Write-Host "Created:"