diff --git a/TrafagSalesExporter/IC_Abgrenzung_Laender_Andreas_2026-06-02.xlsx b/TrafagSalesExporter/IC_Abgrenzung_Laender_Andreas_2026-06-02.xlsx new file mode 100644 index 0000000..1319e9a Binary files /dev/null and b/TrafagSalesExporter/IC_Abgrenzung_Laender_Andreas_2026-06-02.xlsx differ diff --git a/TrafagSalesExporter/SageSpainFinalExportPackage.zip b/TrafagSalesExporter/SageSpainFinalExportPackage.zip index 6b1ce98..8b313b3 100644 Binary files a/TrafagSalesExporter/SageSpainFinalExportPackage.zip and b/TrafagSalesExporter/SageSpainFinalExportPackage.zip differ diff --git a/TrafagSalesExporter/SageSpainFinalExportPackage/Export-SageSpainSalesCsv.ps1 b/TrafagSalesExporter/SageSpainFinalExportPackage/Export-SageSpainSalesCsv.ps1 index 2204d88..9f7d716 100644 --- a/TrafagSalesExporter/SageSpainFinalExportPackage/Export-SageSpainSalesCsv.ps1 +++ b/TrafagSalesExporter/SageSpainFinalExportPackage/Export-SageSpainSalesCsv.ps1 @@ -1,9 +1,15 @@ param( [string]$ServerInstance = "localhost", [string]$Database = "Sage", + [ValidateSet("Full", "Range")] + [string]$ExportMode = "Full", + [ValidateSet("InvoiceDate", "LineRegistrationDate")] + [string]$DateFilter = "InvoiceDate", + [int]$Year = 2025, [datetime]$FromDate = "2025-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" @@ -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" $runDirectory = Join-Path $OutputDirectory "Sage_Spain_Sales_Export_$timestamp" New-Item -ItemType Directory -Path $runDirectory -Force | Out-Null -$csvPath = Join-Path $runDirectory "Spain_Sales_2025.csv" -$summaryPath = Join-Path $runDirectory "Spain_Sales_2025_summary.txt" +if ([string]::IsNullOrWhiteSpace($OutputFileName)) { + $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 = @" SELECT @@ -170,8 +205,7 @@ JOIN dbo.LineasAlbaranCliente l AND l.EjercicioAlbaran = c.EjercicioAlbaran AND l.SerieAlbaran = c.SerieAlbaran AND l.NumeroAlbaran = c.NumeroAlbaran -WHERE c.FechaFactura >= @FromDate - AND c.FechaFactura < @ToDate +WHERE $datePredicate ORDER BY c.FechaFactura, c.SerieFactura, @@ -188,6 +222,8 @@ Sage Spain Sales CSV export Created: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Server instance: $ServerInstance Database: $Database +Export mode: $ExportMode +Date filter mode: $DateFilter From date: $($FromDate.ToString("yyyy-MM-dd")) To date: $($ToDate.ToString("yyyy-MM-dd")) @@ -204,14 +240,15 @@ Source: dbo.CabeceraAlbaranCliente joined with dbo.LineasAlbaranCliente Filter: -CabeceraAlbaranCliente.FechaFactura >= FromDate -CabeceraAlbaranCliente.FechaFactura < ToDate +$datePredicate Notes: - 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. - DocumentNetAmount uses CabeceraAlbaranCliente.BaseImponible; credit notes are forced negative. - 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 Write-Host "Created:" diff --git a/TrafagSalesExporter/SageSpainFinalExportPackage/README.txt b/TrafagSalesExporter/SageSpainFinalExportPackage/README.txt index 845ea13..6c6816f 100644 --- a/TrafagSalesExporter/SageSpainFinalExportPackage/README.txt +++ b/TrafagSalesExporter/SageSpainFinalExportPackage/README.txt @@ -8,14 +8,27 @@ PowerShell commands: Set-ExecutionPolicy -Scope Process Bypass .\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: Sage_Spain_Sales_Export_YYYYMMDD_HHMMSS Files created: -- Spain_Sales_2025.csv -- Spain_Sales_2025_summary.txt +- Spain_Sales_full_YYYY0101_to_YYYY1231.csv for full export +- 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. @@ -24,9 +37,12 @@ Default source: - Database: Sage - Header: dbo.CabeceraAlbaranCliente - 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 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 diff --git a/TrafagSalesExporter/Services/DatabaseSeedService.cs b/TrafagSalesExporter/Services/DatabaseSeedService.cs index 5bfe819..c059320 100644 --- a/TrafagSalesExporter/Services/DatabaseSeedService.cs +++ b/TrafagSalesExporter/Services/DatabaseSeedService.cs @@ -932,27 +932,38 @@ public class DatabaseSeedService : IDatabaseSeedService 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), - ("USD", "CHF", 0.85m), - ("EUR", "CHF", 0.95m), - ("GBP", "CHF", 1.13m), - ("CNY", "CHF", 1m / 8.50m), - ("INR", "CHF", 1m / 90.91m), - ("CZK", "CHF", 1m / 25.64m), - ("PLN", "CHF", 0.22m), - ("JPY", "CHF", 1m / 156.25m) + (2025, "CHF", "CHF", 1m), + (2025, "USD", "CHF", 0.85m), + (2025, "EUR", "CHF", 0.95m), + (2025, "GBP", "CHF", 1.13m), + (2025, "CNY", "CHF", 1m / 8.50m), + (2025, "INR", "CHF", 1m / 90.91m), + (2025, "CZK", "CHF", 1m / 25.64m), + (2025, "PLN", "CHF", 0.22m), + (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; foreach (var item in defaults) { + var validFrom = new DateTime(item.Year, 1, 1); + var notes = $"Budget {item.Year}"; var exists = db.CurrencyExchangeRates.Any(x => x.FromCurrency == item.From && x.ToCurrency == item.To && - x.ValidFrom == new DateTime(2025, 1, 1) && - x.Notes == "Budget 2025"); + x.ValidFrom == validFrom && + x.Notes == notes); if (exists) continue; @@ -961,9 +972,9 @@ public class DatabaseSeedService : IDatabaseSeedService FromCurrency = item.From, ToCurrency = item.To, Rate = item.Rate, - ValidFrom = new DateTime(2025, 1, 1), - ValidTo = new DateTime(2025, 12, 31), - Notes = "Budget 2025", + ValidFrom = validFrom, + ValidTo = new DateTime(item.Year, 12, 31), + Notes = notes, IsActive = true }); changed = true; diff --git a/TrafagSalesExporter/docs/FINANCE_SITZUNGSPUNKTE_ANDREAS_2026-06-02.md b/TrafagSalesExporter/docs/FINANCE_SITZUNGSPUNKTE_ANDREAS_2026-06-02.md new file mode 100644 index 0000000..0b1a6c2 --- /dev/null +++ b/TrafagSalesExporter/docs/FINANCE_SITZUNGSPUNKTE_ANDREAS_2026-06-02.md @@ -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. diff --git a/TrafagSalesExporter/docs/PRODUCT_SPARTEN_MAPPING_2026-05-27.md b/TrafagSalesExporter/docs/PRODUCT_SPARTEN_MAPPING_2026-05-27.md index daaad98..51040d3 100644 --- a/TrafagSalesExporter/docs/PRODUCT_SPARTEN_MAPPING_2026-05-27.md +++ b/TrafagSalesExporter/docs/PRODUCT_SPARTEN_MAPPING_2026-05-27.md @@ -508,3 +508,108 @@ Deploy: - `ProductRows = 36'847` - `TR-AG Referenzmaterialien = 6'805` - `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. diff --git a/TrafagSalesExporter/docs/rag/PRODUCT_MAPPING.md b/TrafagSalesExporter/docs/rag/PRODUCT_MAPPING.md index 83bc193..28a35c4 100644 --- a/TrafagSalesExporter/docs/rag/PRODUCT_MAPPING.md +++ b/TrafagSalesExporter/docs/rag/PRODUCT_MAPPING.md @@ -100,6 +100,27 @@ Stand: 2026-05-29 - UNASS -> `HelpOutline` - sonst -> `Category` - 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`. - Letzter dokumentierter Deploy: 2026-05-29 13:47, Tests `80/80` gruen. diff --git a/TrafagSalesExporter/scripts/Export-SageSpainSalesCsv.ps1 b/TrafagSalesExporter/scripts/Export-SageSpainSalesCsv.ps1 index 2204d88..9f7d716 100644 --- a/TrafagSalesExporter/scripts/Export-SageSpainSalesCsv.ps1 +++ b/TrafagSalesExporter/scripts/Export-SageSpainSalesCsv.ps1 @@ -1,9 +1,15 @@ param( [string]$ServerInstance = "localhost", [string]$Database = "Sage", + [ValidateSet("Full", "Range")] + [string]$ExportMode = "Full", + [ValidateSet("InvoiceDate", "LineRegistrationDate")] + [string]$DateFilter = "InvoiceDate", + [int]$Year = 2025, [datetime]$FromDate = "2025-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" @@ -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" $runDirectory = Join-Path $OutputDirectory "Sage_Spain_Sales_Export_$timestamp" New-Item -ItemType Directory -Path $runDirectory -Force | Out-Null -$csvPath = Join-Path $runDirectory "Spain_Sales_2025.csv" -$summaryPath = Join-Path $runDirectory "Spain_Sales_2025_summary.txt" +if ([string]::IsNullOrWhiteSpace($OutputFileName)) { + $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 = @" SELECT @@ -170,8 +205,7 @@ JOIN dbo.LineasAlbaranCliente l AND l.EjercicioAlbaran = c.EjercicioAlbaran AND l.SerieAlbaran = c.SerieAlbaran AND l.NumeroAlbaran = c.NumeroAlbaran -WHERE c.FechaFactura >= @FromDate - AND c.FechaFactura < @ToDate +WHERE $datePredicate ORDER BY c.FechaFactura, c.SerieFactura, @@ -188,6 +222,8 @@ Sage Spain Sales CSV export Created: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss") Server instance: $ServerInstance Database: $Database +Export mode: $ExportMode +Date filter mode: $DateFilter From date: $($FromDate.ToString("yyyy-MM-dd")) To date: $($ToDate.ToString("yyyy-MM-dd")) @@ -204,14 +240,15 @@ Source: dbo.CabeceraAlbaranCliente joined with dbo.LineasAlbaranCliente Filter: -CabeceraAlbaranCliente.FechaFactura >= FromDate -CabeceraAlbaranCliente.FechaFactura < ToDate +$datePredicate Notes: - 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. - DocumentNetAmount uses CabeceraAlbaranCliente.BaseImponible; credit notes are forced negative. - 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 Write-Host "Created:"