From dea171862ccd41efdefaf30b1724befa4d185688 Mon Sep 17 00:00:00 2001 From: metacube Date: Thu, 7 May 2026 14:55:30 +0200 Subject: [PATCH] Ensure ZSCHWEIZ OData mapping seed --- TrafagSalesExporter/HANDOFF_2026-04-15.md | 51 +++++++++ TrafagSalesExporter/LLM_SYSTEM_GUIDE.md | 42 +++++++ TrafagSalesExporter/NEXT_STEPS_2026-04-15.md | 37 ++++++ .../Services/DatabaseSeedService.cs | 107 +++++++++++++----- TrafagSalesExporter/lastchange.md | 55 +++++++++ 5 files changed, 263 insertions(+), 29 deletions(-) diff --git a/TrafagSalesExporter/HANDOFF_2026-04-15.md b/TrafagSalesExporter/HANDOFF_2026-04-15.md index 17daff1..e69b510 100644 --- a/TrafagSalesExporter/HANDOFF_2026-04-15.md +++ b/TrafagSalesExporter/HANDOFF_2026-04-15.md @@ -2,6 +2,57 @@ Stand: 2026-05-05 +## Nachtrag 2026-05-07 SAP OData / ZSCHWEIZ / Schweiz-Oesterreich + +Aktueller Architekturentscheid: + +- `ZSCHWEIZ` wird ueber SAP OData/Gateway gelesen. +- Direkter HANA-Spezialcode fuer `ZSCHWEIZ` wurde vermieden. +- Der grafische Quellen- und Feldmapper wird fuer SAP OData verwendet. +- Fuer direkte HANA-Tabellen/Views gibt es ebenfalls grafisches Mapping; das ist aber nicht der geplante Pfad fuer `ZSCHWEIZ`. + +Quellsysteme: + +- `SAP` = `SAP OData`, Anschlussart `SAP_GATEWAY`. +- `SAP_HANA` = `SAP HANA Tables/Views`, Anschlussart `HANA`. +- `BI1` und `SAGE` bleiben HANA-basierte Quellsysteme. +- `MANUAL_EXCEL` bleibt Excel/CSV. + +ABAP/SAP: + +- Datei `report.abap` enthaelt Report `ZTRAFAG_SCHWEIZ_EXPORT`. +- Ziel-Tabelle in SAP: `ZSCHWEIZ`. +- `BUKRS 1100` wird als Schweiz (`TRCH`, `CH`) geschrieben. +- `BUKRS 1200` wird als Oesterreich (`TRAT`, `AT`) geschrieben. +- `CUSTOMER_LAND` enthaelt das urspruengliche Kundenland. +- Der Report schreibt paketweise per Upsert. + +App-Seed: + +- Standort `ZSCHWEIZ` / `Schweiz/Oesterreich` wird inaktiv angelegt bzw. repariert. +- `SourceSystem = SAP`. +- Quelle `Z`, EntitySet `ZSCHWEIZSet`. +- Quelle und Feldmapping werden beim App-Start per Upsert nachgezogen; eine teilweise vorhandene ZSCHWEIZ-Konfiguration bleibt dadurch nicht leer. +- Initiales Mapping: + - `Tsc <- Z.TSC` + - `Land <- Z.LAND1` + - `InvoiceNumber <- Z.VBELN` + - `PositionOnInvoice <- Z.POSNR` + - `SalesPriceValue <- Z.NETWR_HC` + - `SalesCurrency <- Z.HWAER` + - `CustomerCountry <- Z.CUSTOMER_LAND` + +Wichtig fuer naechsten Einstieg: + +- Wenn die zentrale SAP-Service-URL noch auf `ZPOWERBI_EINKAUF_SRV` zeigt, muss beim Standort `ZSCHWEIZ` ein Service-URL-Override fuer den `ZSCHWEIZ`-OData-Service gesetzt werden. +- Feldinfos kommen ueber `$metadata`; manuelle Feldliste ist nur noetig, wenn Gateway/Metadata nicht funktioniert. +- Nach URL-Setzung: `Entity Sets refreshen`, `Felder aus Quellen laden`, Mapping kontrollieren, Standort aktivieren, Export testen. + +Verifikation: + +- Hauptprojekt Build erfolgreich. +- Tests `50/50` erfolgreich. + ## Nachtrag 2026-05-05 Aktueller Handoff FinanceProbe / Laenderabgleich Der aktuelle Arbeitsstand fuer den naechsten Einstieg ist der lokale FinanceProbe: diff --git a/TrafagSalesExporter/LLM_SYSTEM_GUIDE.md b/TrafagSalesExporter/LLM_SYSTEM_GUIDE.md index 79f944c..01c7546 100644 --- a/TrafagSalesExporter/LLM_SYSTEM_GUIDE.md +++ b/TrafagSalesExporter/LLM_SYSTEM_GUIDE.md @@ -17,6 +17,48 @@ Lokaler FinanceProbe: http://localhost:55417/finance ``` +## Aktueller Zusatzstand 2026-05-07 SAP OData / ZSCHWEIZ + +Schweiz/Oesterreich werden ueber eine neue SAP-Tabelle `ZSCHWEIZ` bereitgestellt. + +Wichtige Punkte: + +- ABAP-Report: `report.abap` +- SAP-Tabelle: `ZSCHWEIZ` +- OData EntitySet: `ZSCHWEIZSet` +- App-Standort: `ZSCHWEIZ` / `Schweiz/Oesterreich` +- Geplanter App-Pfad: `SAP` = `SAP OData`, nicht direkter HANA-Spezialcode + +Quellsystem-Codes: + +- `SAP`: SAP OData/Gateway, DisplayName `SAP OData` +- `SAP_HANA`: direkte HANA-Tabellen/Views, DisplayName `SAP HANA Tables/Views` +- `BI1`: HANA +- `SAGE`: HANA +- `MANUAL_EXCEL`: Excel/CSV + +Mapper: + +- SAP OData nutzt `SapSourceDefinition`, `SapJoinDefinition`, `SapFieldMapping`. +- Direkte HANA-Tabellen/Views koennen dieselben Mapping-Tabellen ebenfalls nutzen. +- Bei HANA mit gepflegten Quellen/Mappings nutzt `HanaDataSourceAdapter` den generischen Mapping-Pfad. +- Ohne HANA-Mapping bleibt der alte B1-HANA-Standardpfad fuer `OINV/INV1/ORIN/RIN1` aktiv. + +ZSCHWEIZ-Seed: + +- Quelle Alias `Z` +- EntitySet `ZSCHWEIZSet` +- Mapping auf `SalesRecord` ist vorbefuellt und grafisch editierbar. +- Beim App-Start wird die ZSCHWEIZ-Quelle samt Feldmapping per Upsert angelegt oder repariert. +- Wenn Gateway `$metadata` liefert, koennen Felder in der UI per `Felder aus Quellen laden` gelesen werden. + +ABAP-Fachlogik: + +- `BUKRS 1100` = Schweiz, `TSC TRCH`, `LAND1 CH` +- `BUKRS 1200` = Oesterreich, `TSC TRAT`, `LAND1 AT` +- `CUSTOMER_LAND` = Kundenland aus `KNA1-LAND1` +- Netto-/Steuerwerte werden in Belegwaehrung und Hauswaehrung geschrieben. + Aktuelle FinanceProbe-Funktionen: - `Meeting Ampel 2025` fuer alle Laender aus `check.xlsx` diff --git a/TrafagSalesExporter/NEXT_STEPS_2026-04-15.md b/TrafagSalesExporter/NEXT_STEPS_2026-04-15.md index 8c96f4f..6e8fa3b 100644 --- a/TrafagSalesExporter/NEXT_STEPS_2026-04-15.md +++ b/TrafagSalesExporter/NEXT_STEPS_2026-04-15.md @@ -2,6 +2,43 @@ Stand: 2026-05-05 +## Nachtrag 2026-05-07 ZSCHWEIZ ueber SAP OData + +Finaler Stand fuer Schweiz/Oesterreich: + +- ABAP-Report `report.abap` fuellt SAP-Tabelle `ZSCHWEIZ`. +- Buchungskreis `1100` = Schweiz, `1200` = Oesterreich. +- `LAND1` in `ZSCHWEIZ` ist Reporting-Land aus Buchungskreis. +- `CUSTOMER_LAND` ist Kundenland aus `KNA1-LAND1`. +- Die App liest `ZSCHWEIZ` ueber SAP OData, nicht ueber direkten HANA-Spezialcode. + +In der App: + +- Quellsystem-Code `SAP` bleibt bestehen, DisplayName jetzt `SAP OData`. +- `SAP_HANA` ist nur fuer direkte HANA-Tabellen/Views und heisst `SAP HANA Tables/Views`. +- Der grafische Mapper funktioniert fuer SAP OData und fuer HANA-Tabellen/Views. +- Vorkonfigurierter Standort: + - `TSC = ZSCHWEIZ` + - `Land = Schweiz/Oesterreich` + - `SourceSystem = SAP` + - Quelle `Z` + - EntitySet `ZSCHWEIZSet` +- Quelle und Feldmapping werden beim App-Start per Seed-/Repair-Logik nachgezogen, auch wenn der Standort bereits existiert. + +Naechste Schritte: + +1. App neu starten, damit die Seed-/Repair-Logik laeuft. +2. In `Settings -> Quellsysteme` pruefen, ob `SAP` als `SAP OData` angezeigt wird. +3. In `Standorte` den Standort `ZSCHWEIZ` oeffnen. +4. Falls die zentrale SAP-Service-URL noch auf `ZPOWERBI_EINKAUF_SRV` zeigt, beim Standort `SAP Service URL Override` auf den finalen OData-Service fuer `ZSCHWEIZ` setzen. +5. `Entity Sets refreshen`. +6. Quelle `Z` auf `ZSCHWEIZSet` kontrollieren. +7. `Felder aus Quellen laden`. +8. Grafisches Mapping kontrollieren; manuell mappen musst du nur, wenn Gateway-Feldnamen vom erwarteten `ZSCHWEIZ`-Layout abweichen. +9. Standort aktivieren und Export testen. + +Keine manuelle Feldliste ist noetig, wenn der Gateway-Service `$metadata` korrekt liefert. + ## Nachtrag 2026-05-05 Abschlussstand FinanceProbe / Spanien / Deutschland Aktueller lokaler Testpunkt: diff --git a/TrafagSalesExporter/Services/DatabaseSeedService.cs b/TrafagSalesExporter/Services/DatabaseSeedService.cs index 34a52c2..20c8c9c 100644 --- a/TrafagSalesExporter/Services/DatabaseSeedService.cs +++ b/TrafagSalesExporter/Services/DatabaseSeedService.cs @@ -341,36 +341,51 @@ public class DatabaseSeedService : IDatabaseSeedService private static void EnsureSapODataDachMapping(AppDbContext db, int siteId) { - var existingSources = db.SapSourceDefinitions.Where(x => x.SiteId == siteId).ToList(); - var existingMappings = db.SapFieldMappings.Where(x => x.SiteId == siteId).ToList(); + var changed = false; + var source = db.SapSourceDefinitions + .OrderBy(x => x.Id) + .FirstOrDefault(x => x.SiteId == siteId && x.Alias == "Z"); - if (existingSources.Count > 0 || existingMappings.Count > 0) + if (source is null) { - var changed = false; - foreach (var source in existingSources.Where(x => - string.Equals(x.Alias, "Z", StringComparison.OrdinalIgnoreCase) && - string.Equals(x.EntitySet, "ZSCHWEIZ", StringComparison.OrdinalIgnoreCase))) + db.SapSourceDefinitions.Add(new SapSourceDefinition + { + SiteId = siteId, + Alias = "Z", + EntitySet = "ZSCHWEIZSet", + IsPrimary = true, + IsActive = true, + SortOrder = 0 + }); + changed = true; + } + else + { + if (source.EntitySet != "ZSCHWEIZSet") { source.EntitySet = "ZSCHWEIZSet"; changed = true; } - if (changed) - db.SaveChanges(); + if (!source.IsPrimary) + { + source.IsPrimary = true; + changed = true; + } - return; + if (!source.IsActive) + { + source.IsActive = true; + changed = true; + } + + if (source.SortOrder != 0) + { + source.SortOrder = 0; + changed = true; + } } - db.SapSourceDefinitions.Add(new SapSourceDefinition - { - SiteId = siteId, - Alias = "Z", - EntitySet = "ZSCHWEIZSet", - IsPrimary = true, - IsActive = true, - SortOrder = 0 - }); - var mappings = new (string Target, string Source, bool Required)[] { (nameof(SalesRecord.Tsc), "Z.TSC", true), @@ -402,17 +417,51 @@ public class DatabaseSeedService : IDatabaseSeedService for (var i = 0; i < mappings.Length; i++) { - db.SapFieldMappings.Add(new SapFieldMapping + var mapping = db.SapFieldMappings + .OrderBy(x => x.Id) + .FirstOrDefault(x => x.SiteId == siteId && x.TargetField == mappings[i].Target); + + if (mapping is null) { - SiteId = siteId, - TargetField = mappings[i].Target, - SourceExpression = mappings[i].Source, - IsRequired = mappings[i].Required, - IsActive = true, - SortOrder = i - }); + db.SapFieldMappings.Add(new SapFieldMapping + { + SiteId = siteId, + TargetField = mappings[i].Target, + SourceExpression = mappings[i].Source, + IsRequired = mappings[i].Required, + IsActive = true, + SortOrder = i + }); + changed = true; + continue; + } + + if (mapping.SourceExpression != mappings[i].Source) + { + mapping.SourceExpression = mappings[i].Source; + changed = true; + } + + if (mapping.IsRequired != mappings[i].Required) + { + mapping.IsRequired = mappings[i].Required; + changed = true; + } + + if (!mapping.IsActive) + { + mapping.IsActive = true; + changed = true; + } + + if (mapping.SortOrder != i) + { + mapping.SortOrder = i; + changed = true; + } } - db.SaveChanges(); + if (changed) + db.SaveChanges(); } } diff --git a/TrafagSalesExporter/lastchange.md b/TrafagSalesExporter/lastchange.md index 0fbc245..f636449 100644 --- a/TrafagSalesExporter/lastchange.md +++ b/TrafagSalesExporter/lastchange.md @@ -1,5 +1,60 @@ # Last Change 2026-05-04 +## SAP OData / ZSCHWEIZ / HANA Mapping 2026-05-07 + +Aktueller Entscheid: + +- `ZSCHWEIZ` wird nicht direkt als SAP-HANA-Spezialfall gelesen. +- `ZSCHWEIZ` wird ueber den bestehenden SAP-OData/Gateway-Pfad gelesen. +- Der grafische Quellen- und Feldmapper bleibt dafuer aktiv. +- Feldinfos muessen nicht hart codiert werden, solange der Gateway-Service `$metadata` fuer das EntitySet liefert. + +Quellsystem-Namen wurden zur Entwirrung geschaerft: + +- Code `SAP` bleibt technisch bestehen, DisplayName ist jetzt `SAP OData`. +- Code `SAP_HANA` bleibt fuer direkte HANA-Tabellen/Views bestehen, DisplayName ist jetzt `SAP HANA Tables/Views`. +- Bestehende Konfigurationen bleiben dadurch kompatibel. + +Seed / Vorkonfiguration: + +- Standort `ZSCHWEIZ` / Land `Schweiz/Oesterreich` wird als inaktiver Standort angelegt bzw. repariert. +- `SourceSystem = SAP`. +- Quelle: Alias `Z`, EntitySet `ZSCHWEIZSet`. +- Mapping ist grafisch editierbar und wird auf die Felder der Tabelle `ZSCHWEIZ` gesetzt. +- Die Seed-/Repair-Logik zieht Quelle und Mapping auch bei bereits vorhandener ZSCHWEIZ-Konfiguration nach; manuelles Mapping ist nur noetig, wenn die Gateway-Feldnamen vom erwarteten `ZSCHWEIZ`-Layout abweichen. + +Wichtig fuer die UI: + +1. App neu starten, damit Seed/Repair laeuft. +2. `Settings -> Quellsysteme`: `SAP` sollte als `SAP OData` erscheinen. +3. `Standorte -> ZSCHWEIZ`: + - Quellsystem `SAP OData (SAP)` + - SAP Service URL Override auf den finalen OData-Service fuer `ZSCHWEIZ` setzen, falls die zentrale SAP-URL noch auf `ZPOWERBI_EINKAUF_SRV` zeigt. + - `Entity Sets refreshen`. + - Quelle `Z` soll auf `ZSCHWEIZSet` zeigen. + - `Felder aus Quellen laden`. + - Mapping kontrollieren. + +ABAP / SAP: + +- ABAP-Report liegt in `report.abap`. +- Report fuellt Tabelle `ZSCHWEIZ` aus Buchungskreis `1100` = Schweiz und `1200` = Oesterreich. +- `LAND1` ist Reporting-Land aus Buchungskreis. +- `CUSTOMER_LAND` ist Kundenland aus `KNA1-LAND1`. +- Upsert erfolgt per `MODIFY zschweiz FROM TABLE`. + +Letzte technische Verifikation: + +```text +dotnet build .\TrafagSalesExporter.csproj --no-restore -p:UseAppHost=false --verbosity minimal +dotnet test .\TrafagSalesExporter.Tests\TrafagSalesExporter.Tests.csproj --no-restore --verbosity minimal +``` + +Ergebnis: + +- Build erfolgreich +- Tests erfolgreich, `50/50` + ## Finance-Abgrenzung: Antworten Andreas 2026-05-07 Fachliche Vorgabe nach Rueckmeldung: