From f23fa1662e03898544f6dd590bc232216b9770eb Mon Sep 17 00:00:00 2001 From: metacube Date: Wed, 10 Jun 2026 16:17:02 +0200 Subject: [PATCH] Add product division map fallback --- .../Services/DatabaseSeedService.cs | 134 ++++- .../Services/MappedSalesRecordComposer.cs | 42 ++ .../DatabaseInitializationServiceTests.cs | 46 ++ .../MappedSalesRecordComposerTests.cs | 120 +++++ .../docs/DEPLOYMENT_IIS_HANDOFF_2026-05-19.md | 77 +++ .../docs/MD_DOKUMENTENSTATUS_2026-05-20.md | 2 +- TrafagSalesExporter/docs/rag/DEPLOYMENT.md | 7 +- TrafagSalesExporter/docs/rag/PROJECT.md | 5 +- TrafagSalesExporter/lastchange.md | 13 +- .../UEBERGABE_PRODUKTSPARTEN_ZUORDNUNG.md | 486 ++++++++++++++++++ ...PC_EXT_PRODUCTDIVISIONM_GET_ENTITYSET.abap | 91 ++++ ...PC_EXT_PRODUCTDIVISIONR_GET_ENTITYSET.abap | 37 ++ .../spartenlogic/ZCL_PRODSPARTE_PROVIDER.abap | 157 ++++++ .../spartenlogic/Z_PRODSPARTE_ALL.abap | 98 ++++ .../Z_PRODSPARTE_KEDR_K9R_FIND.abap | 219 ++++++++ .../Z_PRODSPARTE_KEDR_RULE_FIND.abap | 161 ++++++ .../spartenlogic/Z_PRODSPARTE_MAP_BUILD.abap | 408 +++++++++++++++ .../spartenlogic/Z_PRODSPARTE_MAP_EXPORT.abap | 49 ++ .../spartenlogic/Z_PRODSPARTE_MAP_IMPORT.abap | 265 ++++++++++ 19 files changed, 2399 insertions(+), 18 deletions(-) create mode 100644 TrafagSalesExporter/spartenlogic/UEBERGABE_PRODUKTSPARTEN_ZUORDNUNG.md create mode 100644 TrafagSalesExporter/spartenlogic/ZCL_PRODSPARTE_DPC_EXT_PRODUCTDIVISIONM_GET_ENTITYSET.abap create mode 100644 TrafagSalesExporter/spartenlogic/ZCL_PRODSPARTE_DPC_EXT_PRODUCTDIVISIONR_GET_ENTITYSET.abap create mode 100644 TrafagSalesExporter/spartenlogic/ZCL_PRODSPARTE_PROVIDER.abap create mode 100644 TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_ALL.abap create mode 100644 TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_KEDR_K9R_FIND.abap create mode 100644 TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_KEDR_RULE_FIND.abap create mode 100644 TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_MAP_BUILD.abap create mode 100644 TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_MAP_EXPORT.abap create mode 100644 TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_MAP_IMPORT.abap diff --git a/TrafagSalesExporter/Services/DatabaseSeedService.cs b/TrafagSalesExporter/Services/DatabaseSeedService.cs index e608e4a..8340490 100644 --- a/TrafagSalesExporter/Services/DatabaseSeedService.cs +++ b/TrafagSalesExporter/Services/DatabaseSeedService.cs @@ -7,6 +7,8 @@ namespace TrafagSalesExporter.Services; public class DatabaseSeedService : IDatabaseSeedService { + private const string SpainSharePointFolder = "https://trafagag.sharepoint.com/sites/WorldwideBIPlatform/Import/Finance/Spanien"; + public void SeedDefaults(AppDbContext db) { SeedIfEmpty(db); @@ -529,6 +531,12 @@ public class DatabaseSeedService : IDatabaseSeedService changed = true; } + if (ShouldRepairSpainManualImportPath(existing.ManualImportFilePath)) + { + existing.ManualImportFilePath = SpainSharePointFolder; + changed = true; + } + if (changed) db.SaveChanges(); @@ -541,11 +549,22 @@ public class DatabaseSeedService : IDatabaseSeedService TSC = "TRES", Land = "Spanien", SourceSystem = "MANUAL_EXCEL", + ManualImportFilePath = SpainSharePointFolder, IsActive = false }); db.SaveChanges(); } + private static bool ShouldRepairSpainManualImportPath(string? path) + { + if (string.IsNullOrWhiteSpace(path)) + return true; + + var normalized = path.Trim().Replace('\\', '/'); + return normalized.Contains("/Import/Finance/Spanien/Spain_Sales_", StringComparison.OrdinalIgnoreCase) || + normalized.EndsWith("/Import/Finance/Spanien/Spain_Sales_2025.csv", StringComparison.OrdinalIgnoreCase); + } + private static void EnsureGermanyManualExcelSite(AppDbContext db) { if (db.Sites.Count() <= 1) @@ -912,7 +931,7 @@ public class DatabaseSeedService : IDatabaseSeedService } var obsoleteSources = db.SapSourceDefinitions - .Where(x => x.SiteId == siteId && x.Alias != "Z" && x.Alias != "P") + .Where(x => x.SiteId == siteId && x.Alias != "Z" && x.Alias != "P" && x.Alias != "M") .ToList(); foreach (var obsoleteSource in obsoleteSources) { @@ -973,6 +992,50 @@ public class DatabaseSeedService : IDatabaseSeedService } } + var productMapSource = db.SapSourceDefinitions + .OrderBy(x => x.Id) + .FirstOrDefault(x => x.SiteId == siteId && x.Alias == "M"); + + if (productMapSource is null) + { + db.SapSourceDefinitions.Add(new SapSourceDefinition + { + SiteId = siteId, + Alias = "M", + EntitySet = "ProductDivisionMapSet", + IsPrimary = false, + IsActive = true, + SortOrder = 2 + }); + changed = true; + } + else + { + if (productMapSource.EntitySet != "ProductDivisionMapSet") + { + productMapSource.EntitySet = "ProductDivisionMapSet"; + changed = true; + } + + if (productMapSource.IsPrimary) + { + productMapSource.IsPrimary = false; + changed = true; + } + + if (!productMapSource.IsActive) + { + productMapSource.IsActive = true; + changed = true; + } + + if (productMapSource.SortOrder != 2) + { + productMapSource.SortOrder = 2; + changed = true; + } + } + var productJoin = db.SapJoinDefinitions .OrderBy(x => x.Id) .FirstOrDefault(x => @@ -1028,6 +1091,61 @@ public class DatabaseSeedService : IDatabaseSeedService } } + var productMapJoin = db.SapJoinDefinitions + .OrderBy(x => x.Id) + .FirstOrDefault(x => + x.SiteId == siteId && + x.LeftAlias == "Z" && + x.RightAlias == "M"); + + if (productMapJoin is null) + { + db.SapJoinDefinitions.Add(new SapJoinDefinition + { + SiteId = siteId, + LeftAlias = "Z", + RightAlias = "M", + LeftKeys = "Prodh", + RightKeys = "Paph1", + JoinType = "Left", + IsActive = true, + SortOrder = 2 + }); + changed = true; + } + else + { + if (productMapJoin.LeftKeys != "Prodh") + { + productMapJoin.LeftKeys = "Prodh"; + changed = true; + } + + if (productMapJoin.RightKeys != "Paph1") + { + productMapJoin.RightKeys = "Paph1"; + changed = true; + } + + if (productMapJoin.JoinType != "Left") + { + productMapJoin.JoinType = "Left"; + changed = true; + } + + if (!productMapJoin.IsActive) + { + productMapJoin.IsActive = true; + changed = true; + } + + if (productMapJoin.SortOrder != 2) + { + productMapJoin.SortOrder = 2; + changed = true; + } + } + var mappings = new (string Target, string Source, bool Required)[] { (nameof(SalesRecord.Tsc), "Z.Tsc", true), @@ -1040,13 +1158,13 @@ public class DatabaseSeedService : IDatabaseSeedService (nameof(SalesRecord.Material), "Z.Matnr", false), (nameof(SalesRecord.Name), "Z.Arktx", false), (nameof(SalesRecord.ProductGroup), "Z.Prodh", false), - (nameof(SalesRecord.ProductHierarchyCode), "P.Paph1", false), - (nameof(SalesRecord.ProductHierarchyText), "P.Paph1Text", false), - (nameof(SalesRecord.ProductFamilyCode), "P.Wwpfa", false), - (nameof(SalesRecord.ProductFamilyText), "P.WwpfaText", false), - (nameof(SalesRecord.ProductDivisionCode), "P.Wwpsp", false), - (nameof(SalesRecord.ProductDivisionText), "P.WwpspText", false), - (nameof(SalesRecord.ProductMappingAssigned), "P.IsAssigned", false), + (nameof(SalesRecord.ProductHierarchyCode), "FirstNonEmpty(P.Paph1, M.Paph1)", false), + (nameof(SalesRecord.ProductHierarchyText), "FirstNonEmpty(P.Paph1Text, M.Paph1Text)", false), + (nameof(SalesRecord.ProductFamilyCode), "FirstNonEmpty(P.Wwpfa, M.Wwpfa)", false), + (nameof(SalesRecord.ProductFamilyText), "FirstNonEmpty(P.WwpfaText, M.WwpfaText)", false), + (nameof(SalesRecord.ProductDivisionCode), "FirstNonEmpty(P.Wwpsp, M.Wwpsp)", false), + (nameof(SalesRecord.ProductDivisionText), "FirstNonEmpty(P.WwpspText, M.WwpspText)", false), + (nameof(SalesRecord.ProductMappingAssigned), "FirstNonEmpty(P.IsAssigned, M.IsAssigned)", false), (nameof(SalesRecord.Quantity), "Z.Fkimg", false), (nameof(SalesRecord.CustomerNumber), "Z.Kunnr", false), (nameof(SalesRecord.CustomerName), "Z.Name1", false), diff --git a/TrafagSalesExporter/Services/MappedSalesRecordComposer.cs b/TrafagSalesExporter/Services/MappedSalesRecordComposer.cs index a34ff86..3837521 100644 --- a/TrafagSalesExporter/Services/MappedSalesRecordComposer.cs +++ b/TrafagSalesExporter/Services/MappedSalesRecordComposer.cs @@ -129,12 +129,54 @@ public sealed class MappedSalesRecordComposer : IMappedSalesRecordComposer if (value.StartsWith('=')) return value[1..]; + if (TryEvaluateFirstNonEmpty(row, value, out var firstNonEmpty)) + return firstNonEmpty; + if (row.TryGetValue(value, out var direct)) return direct; return null; } + private static bool TryEvaluateFirstNonEmpty(Dictionary row, string expression, out object? result) + { + result = null; + const string functionName = "FirstNonEmpty"; + if (!expression.StartsWith(functionName, StringComparison.OrdinalIgnoreCase)) + return false; + + var openParen = expression.IndexOf('('); + var closeParen = expression.LastIndexOf(')'); + if (openParen < functionName.Length || closeParen <= openParen) + return false; + + var arguments = expression[(openParen + 1)..closeParen] + .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + foreach (var argument in arguments) + { + var value = EvaluateExpression(row, argument); + if (!IsEmptyValue(value)) + { + result = value; + return true; + } + } + + return true; + } + + private static bool IsEmptyValue(object? value) + { + if (value is null) + return true; + + if (value is string text) + return string.IsNullOrWhiteSpace(text); + + return string.IsNullOrWhiteSpace(value.ToString()); + } + private static void ApplyValue(SalesRecord record, string targetField, object? value) { var property = typeof(SalesRecord).GetProperty(targetField); diff --git a/TrafagSalesExporter/TrafagSalesExporter.Tests/DatabaseInitializationServiceTests.cs b/TrafagSalesExporter/TrafagSalesExporter.Tests/DatabaseInitializationServiceTests.cs index 588b59d..6e597a9 100644 --- a/TrafagSalesExporter/TrafagSalesExporter.Tests/DatabaseInitializationServiceTests.cs +++ b/TrafagSalesExporter/TrafagSalesExporter.Tests/DatabaseInitializationServiceTests.cs @@ -135,6 +135,23 @@ public class DatabaseInitializationServiceTests : IDisposable Assert.Equal(sageServer.Id, india.HanaServerId); } + [Fact] + public async Task InitializeAsync_Repairs_Spain_Manual_Import_File_To_Folder() + { + await PrepareSpainManualImportFilePathAsync(); + + var service = CreateService(); + await service.InitializeAsync(); + + await using var db = await _dbFactory.CreateDbContextAsync(); + var spain = Assert.Single(db.Sites, x => x.TSC == "TRSE"); + + Assert.Equal("MANUAL_EXCEL", spain.SourceSystem); + Assert.Equal( + "https://trafagag.sharepoint.com/sites/WorldwideBIPlatform/Import/Finance/Spanien", + spain.ManualImportFilePath); + } + private async Task PrepareLegacySitesTableAsync() { await using var db = await _dbFactory.CreateDbContextAsync(); @@ -240,6 +257,35 @@ VALUES ( await db.SaveChangesAsync(); } + private async Task PrepareSpainManualImportFilePathAsync() + { + await using var db = await _dbFactory.CreateDbContextAsync(); + + db.HanaServers.RemoveRange(db.HanaServers); + db.Sites.RemoveRange(db.Sites); + await db.SaveChangesAsync(); + + db.Sites.AddRange( + new Site + { + Schema = "fr01_p", + TSC = "TRFR", + Land = "Frankreich", + SourceSystem = "BI1", + IsActive = true + }, + new Site + { + Schema = "Spanien", + TSC = "TRSE", + Land = "Spanien", + SourceSystem = "MANUAL_EXCEL", + ManualImportFilePath = "https://trafagag.sharepoint.com/sites/WorldwideBIPlatform/Import/Finance/Spanien/Spain_Sales_2025.csv", + IsActive = true + }); + await db.SaveChangesAsync(); + } + private async Task PrepareBrokenHanaServerForeignKeyAsync() { await using var db = await _dbFactory.CreateDbContextAsync(); diff --git a/TrafagSalesExporter/TrafagSalesExporter.Tests/MappedSalesRecordComposerTests.cs b/TrafagSalesExporter/TrafagSalesExporter.Tests/MappedSalesRecordComposerTests.cs index d32a09a..0e3d0c2 100644 --- a/TrafagSalesExporter/TrafagSalesExporter.Tests/MappedSalesRecordComposerTests.cs +++ b/TrafagSalesExporter/TrafagSalesExporter.Tests/MappedSalesRecordComposerTests.cs @@ -108,6 +108,126 @@ public class MappedSalesRecordComposerTests Assert.Equal("HANA", result[0].DocumentType); } + [Fact] + public void Compose_UsesFirstNonEmptyFallbackWhenPrimaryProductReferenceIsMissing() + { + var composer = new MappedSalesRecordComposer(); + var site = new Site { TSC = "TRAT", Land = "Oesterreich" }; + var sources = new[] + { + new SapSourceDefinition { Alias = "Z", EntitySet = "Sales", IsPrimary = true, IsActive = true }, + new SapSourceDefinition { Alias = "P", EntitySet = "ProductDivisionRefSet", IsActive = true, SortOrder = 1 }, + new SapSourceDefinition { Alias = "M", EntitySet = "ProductDivisionMapSet", IsActive = true, SortOrder = 2 } + }; + var joins = new[] + { + new SapJoinDefinition { LeftAlias = "Z", RightAlias = "P", LeftKeys = "Matnr", RightKeys = "Matnr", IsActive = true, SortOrder = 1 }, + new SapJoinDefinition { LeftAlias = "Z", RightAlias = "M", LeftKeys = "Prodh", RightKeys = "Paph1", IsActive = true, SortOrder = 2 } + }; + var mappings = new[] + { + Mapping(nameof(SalesRecord.Material), "Z.Matnr"), + Mapping(nameof(SalesRecord.ProductGroup), "Z.Prodh"), + Mapping(nameof(SalesRecord.ProductHierarchyCode), "FirstNonEmpty(P.Paph1, M.Paph1)"), + Mapping(nameof(SalesRecord.ProductFamilyCode), "FirstNonEmpty(P.Wwpfa, M.Wwpfa)"), + Mapping(nameof(SalesRecord.ProductDivisionCode), "FirstNonEmpty(P.Wwpsp, M.Wwpsp)"), + Mapping(nameof(SalesRecord.ProductMappingAssigned), "FirstNonEmpty(P.IsAssigned, M.IsAssigned)") + }; + var rows = new Dictionary>>(StringComparer.OrdinalIgnoreCase) + { + ["Z"] = + [ + new(StringComparer.OrdinalIgnoreCase) + { + ["Matnr"] = "900720", + ["Prodh"] = "9999" + } + ], + ["P"] = [], + ["M"] = + [ + new(StringComparer.OrdinalIgnoreCase) + { + ["Paph1"] = "9999", + ["Wwpfa"] = "0043", + ["Wwpsp"] = "0008", + ["IsAssigned"] = true + } + ] + }; + + var result = composer.Compose(site, sources, joins, mappings, rows, "SAP"); + + Assert.Single(result); + Assert.Equal("900720", result[0].Material); + Assert.Equal("9999", result[0].ProductGroup); + Assert.Equal("9999", result[0].ProductHierarchyCode); + Assert.Equal("0043", result[0].ProductFamilyCode); + Assert.Equal("0008", result[0].ProductDivisionCode); + Assert.Equal("True", result[0].ProductMappingAssigned); + } + + [Fact] + public void Compose_FirstNonEmptyKeepsMaterialReferenceBeforeProductHierarchyFallback() + { + var composer = new MappedSalesRecordComposer(); + var site = new Site { TSC = "TRCH", Land = "Schweiz" }; + var sources = new[] + { + new SapSourceDefinition { Alias = "Z", EntitySet = "Sales", IsPrimary = true, IsActive = true }, + new SapSourceDefinition { Alias = "P", EntitySet = "ProductDivisionRefSet", IsActive = true, SortOrder = 1 }, + new SapSourceDefinition { Alias = "M", EntitySet = "ProductDivisionMapSet", IsActive = true, SortOrder = 2 } + }; + var joins = new[] + { + new SapJoinDefinition { LeftAlias = "Z", RightAlias = "P", LeftKeys = "Matnr", RightKeys = "Matnr", IsActive = true, SortOrder = 1 }, + new SapJoinDefinition { LeftAlias = "Z", RightAlias = "M", LeftKeys = "Prodh", RightKeys = "Paph1", IsActive = true, SortOrder = 2 } + }; + var mappings = new[] + { + Mapping(nameof(SalesRecord.ProductHierarchyCode), "FirstNonEmpty(P.Paph1, M.Paph1)"), + Mapping(nameof(SalesRecord.ProductFamilyCode), "FirstNonEmpty(P.Wwpfa, M.Wwpfa)"), + Mapping(nameof(SalesRecord.ProductDivisionCode), "FirstNonEmpty(P.Wwpsp, M.Wwpsp)") + }; + var rows = new Dictionary>>(StringComparer.OrdinalIgnoreCase) + { + ["Z"] = + [ + new(StringComparer.OrdinalIgnoreCase) + { + ["Matnr"] = "6", + ["Prodh"] = "9999" + } + ], + ["P"] = + [ + new(StringComparer.OrdinalIgnoreCase) + { + ["Matnr"] = "6", + ["Paph1"] = "0414", + ["Wwpfa"] = "0004", + ["Wwpsp"] = "0001" + } + ], + ["M"] = + [ + new(StringComparer.OrdinalIgnoreCase) + { + ["Paph1"] = "9999", + ["Wwpfa"] = "0043", + ["Wwpsp"] = "0008" + } + ] + }; + + var result = composer.Compose(site, sources, joins, mappings, rows, "SAP"); + + Assert.Single(result); + Assert.Equal("0414", result[0].ProductHierarchyCode); + Assert.Equal("0004", result[0].ProductFamilyCode); + Assert.Equal("0001", result[0].ProductDivisionCode); + } + private static SapFieldMapping Mapping(string targetField, string sourceExpression) => new() { diff --git a/TrafagSalesExporter/docs/DEPLOYMENT_IIS_HANDOFF_2026-05-19.md b/TrafagSalesExporter/docs/DEPLOYMENT_IIS_HANDOFF_2026-05-19.md index 3867994..796f547 100644 --- a/TrafagSalesExporter/docs/DEPLOYMENT_IIS_HANDOFF_2026-05-19.md +++ b/TrafagSalesExporter/docs/DEPLOYMENT_IIS_HANDOFF_2026-05-19.md @@ -2,6 +2,83 @@ Letzter Nachtrag: 2026-06-10 +## Nachtrag 2026-06-10 Deploy Produktsparten-Fallback `ProductDivisionMapSet` + +Durchgefuehrt: + +- Release-Publish aus `TrafagSalesExporter` nach: + +```text +\\trch-webapp-bidashboard.trafagch.local\BiDashboard$\ +``` + +- Befehl: + +```powershell +dotnet publish .\TrafagSalesExporter.csproj -c Release --no-restore /p:PublishProfile=FolderProfile --verbosity minimal +``` + +- App wurde fuer Publish und Server-DB-Aktualisierung per `app_offline.htm` gestoppt und danach wieder online geschaltet. + +Deploy-Inhalt: + +- Neuer SAP-OData-Fallback fuer CH/AT: + - Quelle `M = ProductDivisionMapSet` + - Join `Z.Prodh = M.Paph1` + - Produktfelder nutzen `FirstNonEmpty(P.*, M.*)` +- Materialbasierter Treffer aus `ProductDivisionRefSet` bleibt fuehrend. +- Wenn die Materialreferenz fehlt, aber `Z.Prodh` gefuellt ist, wird die flache `PAPH1 -> WWPFA -> WWPSP`-Map genutzt. +- Wenn auch `Z.Prodh` leer ist, bleibt die Zeile ohne Produktreferenz; dafuer gibt es keinen technischen Schluessel. +- Spain-Seed-Reparatur bleibt enthalten: `TRSE` zeigt auf den SharePoint-Ordner fuer Basis- und Range-/Delta-CSV. + +Share-/DB-Pruefung: + +- `BiDashboard.dll` Zeitstempel nach Deploy: `10.06.2026 16:09:44`. +- `app_offline.htm` wurde entfernt. +- Server-DB-Backup vor Seed/Import: + +```text +\\trch-webapp-bidashboard.trafagch.local\BiDashboard$\trafag_exporter.db.before-productdivision-map-20260610-161022.bak +``` + +- Server-DB nach Seed: + - `ZSCHWEIZ` Quellen: `Z:FinanzdataSchweizOeSet`, `P:ProductDivisionRefSet`, `M:ProductDivisionMapSet` + - Joins: `Z.Matnr = P.Matnr`, `Z.Prodh = M.Paph1` + - Produkt-Mappings: `FirstNonEmpty(P.*, M.*)` + +CH/AT-Import auf Server-DB: + +```text +FetchedRecords = 40'292 +Assigned = 36'953 +UnassignedWithReference = 0 +``` + +Aufteilung nach TSC: + +```text +TRCH Rows 38'838, Assigned 35'526, NoReferenceFields 3'312 +TRAT Rows 1'454, Assigned 1'427, NoReferenceFields 27 +``` + +Validierung: + +```powershell +dotnet test TrafagSalesExporter.sln --verbosity minimal +``` + +Ergebnis: + +```text +87/87 Tests gruen +``` + +SAP-Gateway-Vorbedingung: + +- `ProductDivisionMapSet` ist im Service `ZPOWERBI_EINKAUF_SRV` aktiv. +- `$metadata` enthaelt `ProductDivisionMap` mit `WwpfaText`. +- `ProductDivisionMapSet` liefert 1'296 Zeilen. + ## Nachtrag 2026-06-10 Deploy India / SAGE HANA Mapping Durchgefuehrt: diff --git a/TrafagSalesExporter/docs/MD_DOKUMENTENSTATUS_2026-05-20.md b/TrafagSalesExporter/docs/MD_DOKUMENTENSTATUS_2026-05-20.md index a2d403a..b24fbab 100644 --- a/TrafagSalesExporter/docs/MD_DOKUMENTENSTATUS_2026-05-20.md +++ b/TrafagSalesExporter/docs/MD_DOKUMENTENSTATUS_2026-05-20.md @@ -66,7 +66,7 @@ Bereinigung 2026-06-09: - Historische Finance-Stubs und der alte Finance-Handoff wurden aus der aktiven Doku entfernt, weil der Volltext im Raw-Archiv liegt. - Die alte deutsche Spanien-rclone-Anleitung wurde entfernt, weil der aktuelle All-in-one-Workflow im Guide vom 2026-06-05 dokumentiert ist. - Die Alphaplan-Konzept- und Anleitungsdateien vom 2026-06-08 wurden bewusst nicht veraendert. -- Delta 2026-06-10: India/SAGE-HANA-Deploy und Server-DB-Seed wurden in `docs/DEPLOYMENT_IIS_HANDOFF_2026-05-19.md` und `lastchange.md` nachdokumentiert. +- Delta 2026-06-10: Produktsparten-Fallback `ProductDivisionMapSet`, India/SAGE-HANA-Deploy und Server-DB-Seeds wurden in `docs/DEPLOYMENT_IIS_HANDOFF_2026-05-19.md`, `spartenlogic/UEBERGABE_PRODUKTSPARTEN_ZUORDNUNG.md`, `docs/rag/DEPLOYMENT.md`, `docs/rag/PROJECT.md` und `lastchange.md` nachdokumentiert. Weiterhin gilt: diff --git a/TrafagSalesExporter/docs/rag/DEPLOYMENT.md b/TrafagSalesExporter/docs/rag/DEPLOYMENT.md index add8200..a984cee 100644 --- a/TrafagSalesExporter/docs/rag/DEPLOYMENT.md +++ b/TrafagSalesExporter/docs/rag/DEPLOYMENT.md @@ -5,9 +5,12 @@ Stand: 2026-06-10 ## Kurzstand - `TrafagSalesExporter` wird als ASP.NET/IIS-Webanwendung im bisherigen `BiDashboard`-Schema publiziert. -- Letzter dokumentierter Deploy: 2026-06-10 India/SAGE-HANA-Fix. +- Letzter dokumentierter Deploy: 2026-06-10 Produktsparten-Fallback `ProductDivisionMapSet`. - Publish-Ziel: `\\trch-webapp-bidashboard.trafagch.local\BiDashboard$\`. -- Letzter Deploy-Zeitstempel: `BiDashboard.dll` am `10.06.2026 08:20:25`. +- Letzter Deploy-Zeitstempel: `BiDashboard.dll` am `10.06.2026 16:09:44`. +- Produktive CH/AT-DB-Konfiguration nach Seed: `ZSCHWEIZ` Quellen `Z:FinanzdataSchweizOeSet`, `P:ProductDivisionRefSet`, `M:ProductDivisionMapSet`; Joins `Z.Matnr=P.Matnr` und `Z.Prodh=M.Paph1`. +- CH/AT-Import nach Deploy: `FetchedRecords=40'292`, `Assigned=36'953`, `UnassignedWithReference=0`. +- DB-Backup vor Produktsparten-Seed/Import: `\\trch-webapp-bidashboard.trafagch.local\BiDashboard$\trafag_exporter.db.before-productdivision-map-20260610-161022.bak`. - Produktive India-DB-Konfiguration nach Seed: `TRIN -> SAGE -> 20.197.20.60:30015`, Schema `TRAFAG_LIVE`, User-Override `TRAFAGCONTROLS`. - DB-Backup vor India-Seed: `\\trch-webapp-bidashboard.trafagch.local\BiDashboard$\trafag_exporter.db.before-india-sage-20260610-0825.bak`. - Lokaler Uebergangsserver: `http://172.16.9.185:5000` im Trafag-Netz, IP kann wechseln. diff --git a/TrafagSalesExporter/docs/rag/PROJECT.md b/TrafagSalesExporter/docs/rag/PROJECT.md index 586d637..fdfe321 100644 --- a/TrafagSalesExporter/docs/rag/PROJECT.md +++ b/TrafagSalesExporter/docs/rag/PROJECT.md @@ -5,9 +5,10 @@ Stand: 2026-06-10 ## Kurzstand - Fuehrende App: `TrafagSalesExporter`, publiziert als `BiDashboard`. -- Letzter dokumentierter Stand: India/TRIN SAGE-HANA-Fix deployed, Finance-Kursworkflow dokumentiert, aktive Markdown-Doku bereinigt. -- Validierung laut Doku: `84/84` Tests gruen fuer den India-Fix; fruehere UI-/Deploy-Schritte wurden einzeln umgesetzt und deployed. +- Letzter dokumentierter Stand: CH/AT-Produktsparten-Fallback ueber `ProductDivisionMapSet` deployed; India/TRIN SAGE-HANA-Fix und Spanien-SharePoint-Pfad bleiben abgesichert. +- Validierung laut Doku: `87/87` Tests gruen fuer den Produktsparten-Fallback; fruehere UI-/Deploy-Schritte wurden einzeln umgesetzt und deployed. - Letzter dokumentierter Deploy: 2026-06-10 auf `\\trch-webapp-bidashboard.trafagch.local\BiDashboard$\`. +- Produktsparten CH/AT: `ProductDivisionRefSet` bleibt materialbasiert fuehrend; Fallback `ProductDivisionMapSet` joined `Z.Prodh = M.Paph1`. Server-Import: 40'292 CH/AT-Datensaetze, 36'953 assigned, 0 `UnassignedWithReference`. - India/TRIN: produktive Server-DB steht auf `TRIN -> SAGE -> 20.197.20.60:30015`, Schema `TRAFAG_LIVE`, User-Override `TRAFAGCONTROLS`. - Doku-Delta: `docs/FINANCE_KURS_WORKFLOW_2026-06-09.md` plus SVG; alte Finance-Stubs aus aktiver Markdown-Struktur entfernt, Volltexte bleiben im Raw-Archiv. - Neu im Finance/Management-Cockpit: einfache Schnelluebersicht links sichtbar; tiefere Funktionen bleiben unter `Experten`. diff --git a/TrafagSalesExporter/lastchange.md b/TrafagSalesExporter/lastchange.md index 4170d20..2dce636 100644 --- a/TrafagSalesExporter/lastchange.md +++ b/TrafagSalesExporter/lastchange.md @@ -8,14 +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: India/TRIN HANA-Route gegen SourceSystem-Drift abgesichert; Finance-Kursworkflow und Markdown-Bereinigung dokumentiert. -- Letzte dokumentierte Validierung: `dotnet test TrafagSalesExporter.Tests\TrafagSalesExporter.Tests.csproj --verbosity minimal` mit `84/84` Tests gruen. -- Letzter dokumentierter Deploy: 2026-06-10 India/SAGE-HANA-Fix nach `\\trch-webapp-bidashboard.trafagch.local\BiDashboard$\`. +- Letzter dokumentierter Code-Stand: CH/AT-Produktsparten-Fallback ueber `ProductDivisionMapSet` deployed; India/TRIN HANA-Route und Spanien-SharePoint-Pfad bleiben im Seed abgesichert. +- Letzte dokumentierte Validierung: `dotnet test TrafagSalesExporter.sln --verbosity minimal` mit `87/87` Tests gruen. +- Letzter dokumentierter Deploy: 2026-06-10 Produktsparten-Fallback nach `\\trch-webapp-bidashboard.trafagch.local\BiDashboard$\`. +- Neu umgesetzt und deployed: `ZSCHWEIZ` nutzt zusaetzlich `M = ProductDivisionMapSet` und den Join `Z.Prodh = M.Paph1`; Produktfelder fallen per `FirstNonEmpty(P.*, M.*)` von Materialreferenz auf PAPH1-Mapping zurueck. +- Server-DB am 2026-06-10 aktualisiert: CH/AT neu importiert, `FetchedRecords=40'292`, `Assigned=36'953`, `UnassignedWithReference=0`; Backup: `\\trch-webapp-bidashboard.trafagch.local\BiDashboard$\trafag_exporter.db.before-productdivision-map-20260610-161022.bak`. +- Deploy-Status 2026-06-10: `BiDashboard.dll` Zeitstempel `10.06.2026 16:09:44`; `app_offline.htm` wurde entfernt. - Neu umgesetzt und deployed: `TRIN`/Indien wird beim Seed auf `SourceSystem=SAGE`, Schema `TRAFAG_LIVE` und zentralen SAGE-HANA-Server `20.197.20.60:30015` repariert; Standort-User-/Passwort-Override bleibt erhalten. - Server-DB am 2026-06-10 korrigiert: `TRIN -> SAGE -> 20.197.20.60:30015`, User-Override `TRAFAGCONTROLS`, Passwort-Override vorhanden. Backup: `\\trch-webapp-bidashboard.trafagch.local\BiDashboard$\trafag_exporter.db.before-india-sage-20260610-0825.bak`. -- Deploy-Status 2026-06-10: `BiDashboard.dll` Zeitstempel `10.06.2026 08:20:25`; `app_offline.htm` wurde entfernt. +- Server-DB am 2026-06-10 korrigiert: Spanien (`TRSE`) zeigt im manuellen Import jetzt auf den SharePoint-Ordner `https://trafagag.sharepoint.com/sites/WorldwideBIPlatform/Import/Finance/Spanien` statt auf die Einzeldatei `Spain_Sales_2025.csv`, damit Basis- und Range-/Delta-CSV zusammen gelesen werden. Backup: `\\trch-webapp-bidashboard.trafagch.local\BiDashboard$\trafag_exporter.db.before-spain-folder-path-20260610-100627.bak`. - Git-Commit India-Fix: `586adc3 Fix India SAGE HANA mapping`. -- Neu dokumentiert: Delta zum India-Deploy in `docs/DEPLOYMENT_IIS_HANDOFF_2026-05-19.md`. +- Neu dokumentiert: Delta zum Produktsparten-Fallback-Deploy in `docs/DEPLOYMENT_IIS_HANDOFF_2026-05-19.md` und `spartenlogic/UEBERGABE_PRODUKTSPARTEN_ZUORDNUNG.md`. - 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. diff --git a/TrafagSalesExporter/spartenlogic/UEBERGABE_PRODUKTSPARTEN_ZUORDNUNG.md b/TrafagSalesExporter/spartenlogic/UEBERGABE_PRODUKTSPARTEN_ZUORDNUNG.md new file mode 100644 index 0000000..7596cf0 --- /dev/null +++ b/TrafagSalesExporter/spartenlogic/UEBERGABE_PRODUKTSPARTEN_ZUORDNUNG.md @@ -0,0 +1,486 @@ +# Uebergabe: Produktsparten-Zuordnung Trafag AG + +Stand: 2026-06-10 + +## Ziel + +Fuer das Group-Sales-Dashboard wird eine flache Referenztabelle benoetigt: + +`Materialnummer -> Produkthierarchie PAPH1 -> Produktfamilie WWPFA -> Produktsparte WWPSP` + +SAP/Trafag AG bleibt fuehrend. Das Dashboard soll keine Ableitungslogik nachbauen, sondern nur per Material bzw. PAPH1 nachschlagen. Nicht zuordenbare Artikel laufen im Dashboard als "Nicht zugeordnet". + +## Finale Architektur + +Die fuehrende fachliche Quelle ist KEDE/KEDR, nicht Excel und nicht das Dashboard. + +Finaler technischer Ablauf: + +1. `Z_PRODSPARTE_MAP_BUILD` liest die KEDE/KEDR-Regeltabellen direkt. +2. Die Von-bis-Regeln werden in einzelne PAPH1-Werte expandiert. +3. Das Ergebnis wird in `ZPRODSPARTE_MAP` als flache Tabelle geschrieben: + `PAPH1 -> WWPFA -> WWPSP` +4. `ZCL_PRODSPARTE_PROVIDER=>GET_DATA` liest `MVKE`/`MAKT` und macht einen exakten Lookup auf `ZPRODSPARTE_MAP`. +5. `Z_PRODSPARTE_ALL` und der OData-Service rufen nur noch den Provider. + +Damit bleibt die Laufzeitlogik einfach: kein CSV-Import im produktiven Ablauf, keine Excel-Abhaengigkeit, keine Von-bis-Aufloesung im Dashboard. + +## Warum die Architektur geaendert wurde + +Der erste Ansatz war, die bereits abgeleiteten Werte direkt aus `CE11000` je Material zu lesen. Das war fuer verkaufte Artikel plausibel, aber in der Praxis nicht vollstaendig. + +Test mit `sapdataexport.csv`: + +- SAP-Materialzeilen: 42'232 +- Zugeordnet: 34'462 +- Nicht zugeordnet: 7'770 +- Davon laut Excel/KEDE-Regeln fachlich abgedeckt: 7'768 +- Uebrige echte Luecke: `PAPH1 = 8950` + +Schlussfolgerung: + +`CE11000` reicht als primaere Quelle nicht aus, weil Materialien ohne passende CO-PA-Belegableitung oder mit anderer Beleglage fehlen koennen. Die Regeln muessen aus KEDE/KEDR kommen. + +## Verifizierte SAP-Quellen + +Systemkontext: + +- S/4HANA, S4CORE 108 +- Ergebnisbereich: `ERKRS = 1000` +- KEDR-Strategie: `DERI` +- Applikationsklasse: `KE` +- Fuehrende Pflege in KEDE/KEDR + +Verifizierte Tabellen aus `TKEDRS`: + +- Schritt `0028` + - `METHOD = DRULE` + - `KEDRENV = 1000` + - `PARAM_1 = K9RT761000002` + - Bedeutung: `PAPH1 von-bis -> WWPFA` +- Schritt `0031` + - `METHOD = DRULE` + - `KEDRENV = 1000` + - `PARAM_1 = K9RT761000003` + - Bedeutung: `WWPFA von-bis -> WWPSP` + +Feldstruktur `K9RT761000002`: + +- `SOUR1_FROM` CHAR5: ProdHierarchie01-1 von +- `SOUR1_TO` CHAR5: ProdHierarchie01-1 bis +- `VALID_FROM` DATS +- `TARGET1` CHAR6: Produktfamilie +- `DELETE_FLG` +- `ADDED_BY` +- `ADDED_ON` + +Feldstruktur `K9RT761000003`: + +- `SOUR1_FROM` CHAR6: Produktfamilie von +- `SOUR1_TO` CHAR6: Produktfamilie bis +- `VALID_FROM` DATS +- `TARGET1` CHAR6: Produktsparte +- `DELETE_FLG` +- `ADDED_BY` +- `ADDED_ON` + +Weitere relevante Tabellen: + +- `MVKE-PRODH`: Produkthierarchie am Material, CHAR18, bei Trafag fachlich 4-stellig gepflegt +- `MAKT`: Materialkurztext +- `T179T`: Text zur Produkthierarchie +- `T25A0`: Texte Produktfamilie `WWPFA` +- `T25A1`: Texte Produktsparte `WWPSP` +- `ZPRODSPARTE_MAP`: flache Mapping-Tabelle `PAPH1 -> WWPFA -> WWPSP` + +## Von-bis-Logik + +KEDE pflegt die Produktfamilie nicht nur als Einzelwerte, sondern als Bereiche. + +Beispiele: + +- `0104` bis `0199` -> `WWPFA = 0001` +- `8412` bis `8413` -> `WWPFA = 0028` +- `8280` ohne bis -> Einzelwert `8280` -> `WWPFA = 0032` + +Regeln: + +- Wenn `SOUR1_TO` leer ist, gilt die Zeile als Einzelwert. +- Numerische Bereiche werden mit fuehrenden Nullen auf die urspruengliche Breite expandiert. +- Alphanumerische 4-stellige Bereiche mit gleichem 2-stelligem Praefix werden ueber die Zeichenfolge `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ` expandiert. +- Der Vergleich fuer die Regelauflosung bleibt CHAR-/String-basiert, passend zu KEDR. +- Zeilen mit `DELETE_FLG = X` werden ignoriert. +- Bei Ueberlappungen gewinnt die neueste `VALID_FROM`. +- Bei gleicher `VALID_FROM` gewinnt der spezifische Einzelwert gegenueber einem Bereich. + +Wichtig: `ZPRODSPARTE_MAP` braucht keine Felder `PAPH1_VON`, `PAPH1_BIS` oder `GUELTAB`. Die Von-bis-Logik wird beim Build auf einzelne PAPH1-Zeilen aufgeloest. + +## ABAP-Objekte + +### `Z_PRODSPARTE_KEDR_K9R_FIND` + +Diagnose-Report zur Ermittlung der relevanten KEDR/K9R-Tabellen. + +Ergebnis: + +- `K9RT761000002` ist die Regel `ProdHierarchie01-1 -> Produktfamilie`. +- `K9RT761000003` ist die Regel `Produktfamilie -> Produktsparte`. + +### `Z_PRODSPARTE_MAP_BUILD` + +Finaler Build-Report fuer `ZPRODSPARTE_MAP`. + +Aufgaben: + +- liest `K9RT761000002` +- liest `K9RT761000003` +- expandiert alle PAPH1-Von-bis-Regeln +- nimmt optional zusaetzliche reale PAPH1-Codes aus `MVKE` und `CE11000` auf +- loest `PAPH1 -> WWPFA` +- loest `WWPFA -> WWPSP` +- schreibt die flache Tabelle `ZPRODSPARTE_MAP` + +Parameter: + +- `P_VKORG`: optional, fuer reale Zusatzcodes aus `MVKE` +- `P_VTWEG`: optional, fuer reale Zusatzcodes aus `MVKE` +- `P_CE`: optional, liest zusaetzliche reale PAPH1 aus `CE11000` +- `P_TEST`: Testlauf ohne DB-Schreiben + +Letzter Testlauf: + +- KEDR-Regeln `PAPH1 -> WWPFA`: 124 +- KEDR-Regeln `WWPFA -> WWPSP`: 81 +- PAPH1-Codes aus KEDE-Expansion: 1'297 +- Zusaetzliche reale Code-Versuche: 689 +- Nicht expandierbare Bereiche: 0 +- PAPH1-Codes gesamt eindeutig: 1'297 +- Saetze fuer `ZPRODSPARTE_MAP`: 1'296 +- PAPH1 ohne Produktfamilie: 1 +- PAPH1 ohne Produktsparte: 0 + +Interpretation: + +- Die 1'296 Mapping-Saetze entsprechen der Excel/Data(4)-Referenz. +- Der eine zusaetzliche PAPH1 ohne Produktfamilie ist die bekannte Luecke `8950`. +- `8950` kommt aus realen SAP-Daten, ist aber nicht in KEDE/Excel gepflegt. + +### `Z_PRODSPARTE_MAP_EXPORT` + +Exportiert die flache Tabelle `ZPRODSPARTE_MAP` fuer den Kontrollvergleich gegen Excel/Data(4). + +Exportformat: + +`PAPH1;WWPFA;WWPSP` + +Beispiel-Datei: + +`C:\temp\zprodspartesap3.csv` + +Dieser Export ist der richtige Vergleich gegen Excel/Data(4), nicht der Materialexport. + +### `ZCL_PRODSPARTE_PROVIDER` + +Globale Klasse mit statischer Methode `GET_DATA`. + +Wichtige Signatur: + +- `IV_VKORG TYPE VKORG` +- `IV_VTWEG TYPE VTWEG OPTIONAL` +- `IV_SPRAS TYPE SPRAS DEFAULT SY-LANGU` +- `IV_FALLBACK TYPE BEZEK DEFAULT 'Nicht zugeordnet'` +- `VALUE(RT_OUT) TYPE TT_OUT` + +Rueckgabefelder: + +- `MATNR` +- `MAKTX` +- `PAPH1` +- `PAPH1_TEXT` +- `WWPFA` +- `WWPFA_TEXT` +- `WWPSP` +- `WWPSP_TEXT` +- `IS_ASSIGNED` + +Logik: + +- liest Materialdaten aus `MVKE` +- liest Materialtext aus `MAKT` +- setzt `PAPH1 = MVKE-PRODH(5)` +- liest `ZPRODSPARTE_MAP` +- setzt `IS_ASSIGNED = X`, wenn ein Mapping gefunden wurde +- setzt sonst Fallback `WWPSP = UNASS`, Text `Nicht zugeordnet` +- liest Texte aus `T179T`, `T25A0`, `T25A1` + +Wichtig: + +`GET_DATA` muss in SE24 als `CLASS-METHODS` angelegt sein. Sonst kommt der Fehler: + +`Die Angabe "class=>method" darf nur bei statischen Methoden verwendet werden.` + +### `Z_PRODSPARTE_ALL` + +Ausfuehrbarer Report fuer Materialexport und ALV-Kontrolle. + +Aufgaben: + +- ruft `ZCL_PRODSPARTE_PROVIDER=>GET_DATA` +- zeigt optional ALV +- exportiert optional tab-getrennte CSV per `cl_gui_frontend_services=>gui_download` + +Dieser Export enthaelt Materialzeilen. Er ist nicht identisch mit der Excel/Data(4)-Mappingreferenz, weil Excel auch PAPH1-Codes ohne aktuelles Material enthalten kann. + +### `PRODUCTDIVISIONR_GET_ENTITYSET` + +OData-GET_ENTITYSET-Methode fuer die Produktspartenreferenz. + +Aufgaben: + +- verwendet ohne Filter die Default-TR-AG-Verkaufsorganisation `VKORG = 1100` +- liest optional `VKORG`, falls die Entity spaeter um dieses Property erweitert wird +- liest optional `VTWEG` +- liest optional `SPRAS` +- ruft `ZCL_PRODSPARTE_PROVIDER=>GET_DATA` +- gibt die Daten per `CORRESPONDING` an das EntitySet zurueck + +Wichtig: Die aktuelle Gateway-Metadata fuer `ProductDivisionRef` enthaelt kein Property `VKORG`. +Darum darf die DPC_EXT-Methode `VKORG` nicht als OData-Pflichtfilter erzwingen. Sonst kann die App +`ProductDivisionRefSet` nicht laden: ohne Filter kommt `Filter VKORG ist erforderlich`, mit Filter +kommt `Property VKORG not found in type ProductDivisionRef`. + +### `Z_PRODSPARTE_MAP_IMPORT` + +Nur noch Fallback/Hilfsprogramm. + +Es kann CSV/Excel-Daten in die Mapping-Tabelle bringen, ist aber fuer die finale Architektur nicht die fuehrende Loesung. Fuehrend ist KEDE/KEDR ueber `Z_PRODSPARTE_MAP_BUILD`. + +## Validierung + +### Materialexport nach KEDR-Build + +Datei: + +`prodspartesap2.csv` + +Ergebnis: + +- Materialzeilen: 42'232 +- Zugeordnet: 42'230 +- Nicht zugeordnet: 2 +- Nicht zugeordneter PAPH1: `8950` +- WWPFA-Abweichungen gegen Excel fuer abgedeckte Codes: 0 + +Interpretation: + +Die Materialdaten sind bis auf die fachlich ungepflegte Luecke `8950` zugeordnet. + +### Mappingexport gegen Excel/Data(4) + +Dateien: + +- Excel-Referenz: `exceldataexport.csv` +- SAP-Mappingexport: `C:\temp\zprodspartesap3.csv` + +Vergleichsergebnis: + +```text +Excel-Rohzeilen: 897 +Excel expandiert auf PAPH1: 1296 +SAP-Mapping-Zeilen: 1296 +Fehlt in SAP: 0 +Zusätzlich in SAP: 0 +WWPFA-Abweichungen: 0 +Duplikate PAPH1: 0 +Leere WWPSP: 0 +``` + +Fazit: + +`ZPRODSPARTE_MAP` entspricht der Excel/Data(4)-Referenz vollstaendig fuer `PAPH1 -> WWPFA`. `WWPSP` ist zusaetzlich aus der zweiten KEDE-Regel `K9RT761000003` gefuellt. + +## Offene fachliche Punkte + +### OData-Import `ProductDivisionRefSet` + +Am 2026-06-10 wurde der echte App-Import fuer `ZSCHWEIZ` getestet. + +Konfiguration in der App-DB: + +- Site-ID: `9` +- TSC: `ZSCHWEIZ` +- SAP-Service: `ZPOWERBI_EINKAUF_SRV` +- aktive Quelle `Z`: `FinanzdataSchweizOeSet` +- aktive Quelle `P`: `ProductDivisionRefSet` +- aktiver Join: `Z.Matnr = P.Matnr` +- Spartenfelder werden aus `P.Paph1`, `P.Wwpfa`, `P.Wwpsp`, `P.IsAssigned` gemappt + +Gefundener Fehler vor ABAP-Korrektur: + +```text +ProductDivisionRefSet?$format=json +HTTP 400 +Filter VKORG ist erforderlich + +ProductDivisionRefSet?$format=json&$filter=VKORG eq '1100' +HTTP 400 +Property VKORG not found in type ProductDivisionRef +``` + +Metadata `ProductDivisionRef`: + +```text +Matnr +Maktx +Paph1 +Paph1Text +Wwpfa +WwpfaText +Wwpsp +WwpspText +IsAssigned +``` + +Korrektur: + +- `ZCL_PRODSPARTE_DPC_EXT_PRODUCTDIVISIONR_GET_ENTITYSET.abap` setzt `lv_vkorg = '1100'` als Default. +- Die Exception `Filter VKORG ist erforderlich` wurde entfernt. +- Damit kann die bestehende App `ProductDivisionRefSet` ohne Filter laden. + +Nach Aktivierung dieser ABAP-Aenderung in SAP muss der `ZSCHWEIZ`-Import erneut laufen. + +Status nach Aktivierung `VKORG = 1100`: + +- `ProductDivisionRefSet` liefert 42'232 Zeilen. +- Beispielzeilen sind korrekt gefuellt, z.B. `Matnr=6`, `Paph1=0414`, `WWPFA=0004`, `WWPSP=0001`, `IsAssigned=True`. +- Der gezielte App-Import fuer `ZSCHWEIZ` lief erfolgreich durch. +- Importierte CH/AT-Zeilen: 40'292 +- Davon mit Spartenreferenz zugeordnet: 36'847 +- `Nicht zugeordnet` mit vorhandener Referenz/`UNASS`: 0 +- Ohne Treffer in `ProductDivisionRefSet`: 3'445 + +Aufteilung nach TSC: + +```text +TSC Rows Assigned Nicht zugeordnet Kein Referenztreffer +TRCH 38'838 35'524 0 3'314 +TRAT 1'454 1'323 0 131 +``` + +Interpretation: + +- Die eigentliche Spartenlogik im Webservice ist fuer CH/AT jetzt sauber: kein `UNASS`/`Nicht zugeordnet`. +- Die verbleibenden Zeilen sind kein Spartenregelproblem, sondern Materialnummern ohne Treffer in der TR-AG-Referenz. + +## Dashboard-Fallback fuer fehlende Materialreferenzen + +Es gibt Verkaufszeilen, bei denen der materialbasierte OData-Service `ProductDivisionRefSet` keinen Treffer liefert. Ein Teil dieser Zeilen hat aber in der Verkaufsquelle noch eine Produktgruppe/Produkthierarchie (`Z.Prodh`). Deshalb wurde ein zweiter, flacher OData-Service vorgesehen: + +- `ProductDivisionRefSet`: Materialnummer `MATNR` -> `PAPH1/WWPFA/WWPSP` +- `ProductDivisionMapSet`: Produkthierarchie `PAPH1` -> `WWPFA/WWPSP` + +Der neue ABAP-GET_ENTITYSET fuer `ProductDivisionMapSet` liest direkt aus `ZPRODSPARTE_MAP` und liefert eine Zeile pro `PAPH1`. Im Dashboard wird dieser Service als Quelle `M` angebunden und per Left Join auf die Verkaufsdaten gelegt: + +```text +Z.Prodh = M.Paph1 +``` + +Die Mapping-Felder verwenden danach `FirstNonEmpty(P.*, M.*)`. Das bedeutet: + +- Wenn die materialbasierte Referenz `P` einen Treffer liefert, gewinnt `P`. +- Wenn `P` leer ist, aber `Z.Prodh` in `M` gefunden wird, werden Familie und Sparte aus der flachen Mapping-Tabelle genommen. +- Wenn auch `Z.Prodh` leer ist oder nicht in `ZPRODSPARTE_MAP` steht, bleibt die Zeile ohne Produktsparten-Zuordnung. + +Aktueller CH/AT-Befund vor diesem Fallback: + +- 3'445 Verkaufszeilen hatten keinen Materialreferenztreffer. +- 106 davon hatten `Z.Prodh = 9999`. +- `9999` ist in Excel/Data(4) und in `ZPRODSPARTE_MAP` vorhanden: `9999 -> 0043 -> 0008`. +- Diese 106 Zeilen sollten durch `ProductDivisionMapSet` zuordenbar werden. +- Die restlichen Zeilen mit leerer Produktgruppe koennen technisch nicht zugeordnet werden, solange SAP keine Produktgruppe/Materialreferenz liefert. + +Verifizierter Stand nach Aktivierung von `ProductDivisionMapSet`: + +- `$metadata` enthaelt `ProductDivisionMap` mit `WwpfaText` korrekt geschrieben. +- `ProductDivisionMapSet` liefert HTTP 200 und 1'296 Zeilen. +- Lokaler CH/AT-Import verwendet Quellen `Z`, `P`, `M` und zwei Left Joins. +- Ergebnis CH/AT gesamt: 40'292 Verkaufszeilen, 36'953 zugeordnet, 0 `UnassignedWithReference`. +- Gegenueber dem vorherigen Stand sind genau 106 zusaetzliche Zeilen zugeordnet. +- Rest ohne Referenz: TRCH 3'312, TRAT 27; bei diesen Zeilen ist `ProductGroup/Z.Prodh` leer. + +### `PAPH1 = 8950` + +`8950` taucht in echten SAP-Daten auf, ist aber nicht in KEDE/Excel gepflegt. + +Auswirkung: + +- Im Mappingexport fehlt `8950` korrekt, weil Excel/KEDE es auch nicht enthaelt. +- Im Materialexport bleibt `8950` als nicht zugeordnet. + +Moegliche Ursachen: + +- Material-PRODH ist falsch oder veraltet. +- Finance hat fuer `8950` noch keine KEDE-Regel gepflegt. +- Der Code soll fachlich bewusst nicht zugeordnet sein. + +Naechster Schritt: + +Finance/SAP-Verantwortliche muessen entscheiden, ob fuer `8950` eine KEDE-Regel gepflegt wird oder ob der Materialstamm korrigiert wird. + +## Bedienablauf fuer naechste Pruefung + +1. `Z_PRODSPARTE_MAP_BUILD` mit `P_TEST = X` laufen lassen. +2. Pruefen: + - `Nicht expandierbare Bereiche = 0` + - `PAPH1 ohne Produktsparte = 0` + - `PAPH1 ohne Produktfamilie` nur bekannte Luecken, aktuell `8950` +3. `Z_PRODSPARTE_MAP_BUILD` ohne `P_TEST` laufen lassen. +4. `Z_PRODSPARTE_MAP_EXPORT` laufen lassen. +5. Export gegen Excel/Data(4) vergleichen. +6. `Z_PRODSPARTE_ALL` laufen lassen, um Materialzuordnung zu pruefen. +7. OData-Service testen. + +## Wichtige Unterscheidung der CSV-Dateien + +`prodspartesap2.csv` oder Export aus `Z_PRODSPARTE_ALL`: + +- Materialexport +- eine Zeile pro Material +- darf weniger PAPH1-Codes enthalten als Excel, weil nicht jeder Referenzcode ein Material haben muss +- dient zur Dashboard-/Materialpruefung + +`zprodspartesap3.csv` oder Export aus `Z_PRODSPARTE_MAP_EXPORT`: + +- Mappingexport +- eine Zeile pro PAPH1 +- muss gegen Excel/Data(4) identisch sein bei `PAPH1 -> WWPFA` +- ist die richtige Datei fuer den Regelabgleich + +## Bekannte technische Stolperstellen + +- `GET_DATA` muss statisch sein, wenn mit `zcl_prodsparte_provider=>get_data` aufgerufen wird. +- `TEXT-001` im Report `Z_PRODSPARTE_ALL` muss als Textsymbol existieren. +- CSV-Downloads mit `cl_gui_frontend_services=>gui_download` funktionieren nur im SAP GUI. +- `CORRESPONDING #( lt_data )` im OData-Code funktioniert nur bei passenden Feld-/Property-Namen. +- `PAPH1 = MVKE-PRODH(5)` ist aktuell korrekt, weil KEDE `SOUR1_FROM` in `K9RT761000002` CHAR5 ist. +- `T179T-PRODH` ist CHAR18; falls `PAPH1_TEXT` leer bleibt, muss der Text-Key linksbuendig/padding-geprueft werden. +- `DELETE ADJACENT DUPLICATES COMPARING matnr` im Provider nimmt bei mehreren Vertriebswegen den ersten sortierten Satz. + +## Dateien im Ordner `spartenlogic` + +- `Z_PRODSPARTE_KEDR_K9R_FIND.abap`: Diagnose der TKEDRS/K9R-Regeltabellen +- `Z_PRODSPARTE_MAP_BUILD.abap`: finaler Build aus KEDE/KEDR nach `ZPRODSPARTE_MAP` +- `Z_PRODSPARTE_MAP_EXPORT.abap`: Export der flachen Mapping-Tabelle +- `ZCL_PRODSPARTE_PROVIDER.abap`: Provider fuer Materialdaten und Mapping-Lookup +- `Z_PRODSPARTE_ALL.abap`: ALV/CSV-Materialexport +- `ZCL_PRODSPARTE_DPC_EXT_PRODUCTDIVISIONR_GET_ENTITYSET.abap`: OData-GET_ENTITYSET-Methode +- `ZCL_PRODSPARTE_DPC_EXT_PRODUCTDIVISIONM_GET_ENTITYSET.abap`: OData-GET_ENTITYSET fuer die flache PAPH1-Mappingquelle `ProductDivisionMapSet` +- `Z_PRODSPARTE_MAP_IMPORT.abap`: Fallback-Import, nicht fuehrend +- `exceldataexport.csv`: Excel/Data(4)-Referenzexport +- `prodspartesap2.csv`: Materialexport nach KEDR-Build +- `markregell.png`: Screenshot der KEDE-von/bis-Regel +- `ruleresult.txt`: Diagnoseergebnis aus SAP + +## Fazit + +Die Produktspartenlogik ist fachlich und technisch final auf KEDE/KEDR als Quelle ausgerichtet. Die flache Tabelle `ZPRODSPARTE_MAP` wird aus den SAP-Regeln aufgebaut und wurde gegen Excel/Data(4) ohne Abweichung validiert. Der Dashboard-/OData-Pfad nutzt diese Tabelle als Lookup: zuerst materialbasiert ueber `ProductDivisionRefSet`, danach als Fallback ueber `ProductDivisionMapSet` anhand `Z.Prodh`. Die einzige bekannte fachliche Luecke ist `PAPH1 = 8950`; Zeilen ohne Produktgruppe bleiben mangels Schluessel nicht zuordenbar. diff --git a/TrafagSalesExporter/spartenlogic/ZCL_PRODSPARTE_DPC_EXT_PRODUCTDIVISIONM_GET_ENTITYSET.abap b/TrafagSalesExporter/spartenlogic/ZCL_PRODSPARTE_DPC_EXT_PRODUCTDIVISIONM_GET_ENTITYSET.abap new file mode 100644 index 0000000..098fff6 --- /dev/null +++ b/TrafagSalesExporter/spartenlogic/ZCL_PRODSPARTE_DPC_EXT_PRODUCTDIVISIONM_GET_ENTITYSET.abap @@ -0,0 +1,91 @@ + METHOD productdivisionm_get_entityset. + TYPES: BEGIN OF ty_out, + paph1 TYPE zprodsparte_map-paph1, + paph1_text TYPE t179t-vtext, + wwpfa TYPE zprodsparte_map-wwpfa, + wwpfa_text TYPE t25a0-bezek, + wwpsp TYPE zprodsparte_map-wwpsp, + wwpsp_text TYPE t25a1-bezek, + is_assigned TYPE abap_bool, + END OF ty_out. + + DATA: lv_spras TYPE spras. + lv_spras = sy-langu. + + LOOP AT it_filter_select_options INTO DATA(ls_filter). + READ TABLE ls_filter-select_options INTO DATA(ls_so) INDEX 1. + IF sy-subrc <> 0. + CONTINUE. + ENDIF. + + DATA(lv_property) = ls_filter-property. + TRANSLATE lv_property TO UPPER CASE. + + CASE lv_property. + WHEN 'SPRAS'. + lv_spras = ls_so-low. + ENDCASE. + ENDLOOP. + + SELECT paph1, wwpfa, wwpsp + FROM zprodsparte_map + INTO TABLE @DATA(lt_map) + WHERE paph1 <> @space. + + IF lt_map IS INITIAL. + RETURN. + ENDIF. + + SORT lt_map BY paph1. + DELETE ADJACENT DUPLICATES FROM lt_map COMPARING paph1. + + SELECT prodh, vtext + FROM t179t + INTO TABLE @DATA(lt_h) + WHERE spras = @lv_spras. + SORT lt_h BY prodh. + + SELECT wwpfa, bezek + FROM t25a0 + INTO TABLE @DATA(lt_fam) + WHERE spras = @lv_spras. + SORT lt_fam BY wwpfa. + + SELECT wwpsp, bezek + FROM t25a1 + INTO TABLE @DATA(lt_spa) + WHERE spras = @lv_spras. + SORT lt_spa BY wwpsp. + + DATA lt_out TYPE STANDARD TABLE OF ty_out WITH DEFAULT KEY. + + LOOP AT lt_map INTO DATA(ls_map). + DATA(ls_out) = VALUE ty_out( + paph1 = ls_map-paph1 + wwpfa = ls_map-wwpfa + wwpsp = ls_map-wwpsp + is_assigned = abap_true ). + + READ TABLE lt_h INTO DATA(ls_h) + WITH KEY prodh = ls_map-paph1 BINARY SEARCH. + IF sy-subrc = 0. + ls_out-paph1_text = ls_h-vtext. + ENDIF. + + READ TABLE lt_fam INTO DATA(ls_f) + WITH KEY wwpfa = ls_map-wwpfa BINARY SEARCH. + IF sy-subrc = 0. + ls_out-wwpfa_text = ls_f-bezek. + ENDIF. + + READ TABLE lt_spa INTO DATA(ls_s) + WITH KEY wwpsp = ls_map-wwpsp BINARY SEARCH. + IF sy-subrc = 0. + ls_out-wwpsp_text = ls_s-bezek. + ENDIF. + + APPEND ls_out TO lt_out. + ENDLOOP. + + et_entityset = CORRESPONDING #( lt_out ). + ENDMETHOD. diff --git a/TrafagSalesExporter/spartenlogic/ZCL_PRODSPARTE_DPC_EXT_PRODUCTDIVISIONR_GET_ENTITYSET.abap b/TrafagSalesExporter/spartenlogic/ZCL_PRODSPARTE_DPC_EXT_PRODUCTDIVISIONR_GET_ENTITYSET.abap new file mode 100644 index 0000000..2238ef2 --- /dev/null +++ b/TrafagSalesExporter/spartenlogic/ZCL_PRODSPARTE_DPC_EXT_PRODUCTDIVISIONR_GET_ENTITYSET.abap @@ -0,0 +1,37 @@ + METHOD productdivisionr_get_entityset. + DATA: lv_vkorg TYPE vkorg, + lv_vtweg TYPE vtweg, + lv_spras TYPE spras. + +* ProductDivisionRef enthaelt laut Gateway-Metadata kein VKORG-Feld. +* Der Dashboard-Import kann deshalb keinen VKORG-Filter senden. +* Default ist die fuehrende TR-AG-Verkaufsorganisation. + lv_vkorg = '1100'. + lv_spras = sy-langu. + + LOOP AT it_filter_select_options INTO DATA(ls_filter). + READ TABLE ls_filter-select_options INTO DATA(ls_so) INDEX 1. + IF sy-subrc <> 0. + CONTINUE. + ENDIF. + + DATA(lv_property) = ls_filter-property. + TRANSLATE lv_property TO UPPER CASE. + + CASE lv_property. + WHEN 'VKORG'. + lv_vkorg = ls_so-low. + WHEN 'VTWEG'. + lv_vtweg = ls_so-low. + WHEN 'SPRAS'. + lv_spras = ls_so-low. + ENDCASE. + ENDLOOP. + + DATA(lt_data) = zcl_prodsparte_provider=>get_data( + iv_vkorg = lv_vkorg + iv_vtweg = lv_vtweg + iv_spras = lv_spras ). + + et_entityset = CORRESPONDING #( lt_data ). + ENDMETHOD. diff --git a/TrafagSalesExporter/spartenlogic/ZCL_PRODSPARTE_PROVIDER.abap b/TrafagSalesExporter/spartenlogic/ZCL_PRODSPARTE_PROVIDER.abap new file mode 100644 index 0000000..156b12c --- /dev/null +++ b/TrafagSalesExporter/spartenlogic/ZCL_PRODSPARTE_PROVIDER.abap @@ -0,0 +1,157 @@ +CLASS-POOL zcl_prodsparte_provider. + +CLASS zcl_prodsparte_provider DEFINITION + PUBLIC + FINAL + CREATE PUBLIC. + + PUBLIC SECTION. + TYPES: BEGIN OF ty_out, + matnr TYPE mvke-matnr, + maktx TYPE makt-maktx, + paph1 TYPE ce11000-paph1, + paph1_text TYPE t179t-vtext, + wwpfa TYPE ce11000-wwpfa, + wwpfa_text TYPE t25a0-bezek, + wwpsp TYPE ce11000-wwpsp, + wwpsp_text TYPE t25a1-bezek, + is_assigned TYPE abap_bool, + END OF ty_out. + TYPES tt_out TYPE STANDARD TABLE OF ty_out WITH DEFAULT KEY. + + CLASS-METHODS get_data + IMPORTING + iv_vkorg TYPE vkorg + iv_vtweg TYPE vtweg OPTIONAL + iv_spras TYPE spras DEFAULT sy-langu + iv_fallback TYPE t25a1-bezek DEFAULT 'Nicht zugeordnet' + RETURNING + VALUE(rt_out) TYPE tt_out. +ENDCLASS. + +CLASS zcl_prodsparte_provider IMPLEMENTATION. + METHOD get_data. + TYPES: BEGIN OF ty_base, + matnr TYPE mvke-matnr, + vtweg TYPE mvke-vtweg, + prodh TYPE mvke-prodh, + paph1 TYPE ce11000-paph1, + maktx TYPE makt-maktx, + END OF ty_base. + + TYPES: BEGIN OF ty_map, + paph1 TYPE zprodsparte_map-paph1, + wwpfa TYPE zprodsparte_map-wwpfa, + wwpsp TYPE zprodsparte_map-wwpsp, + END OF ty_map. + + DATA: lt_base TYPE STANDARD TABLE OF ty_base WITH DEFAULT KEY, + lt_map TYPE STANDARD TABLE OF ty_map WITH DEFAULT KEY. + + IF iv_vtweg IS INITIAL. + SELECT mvke~matnr, + mvke~vtweg, + mvke~prodh, + makt~maktx + FROM mvke + LEFT OUTER JOIN makt + ON makt~matnr = mvke~matnr + AND makt~spras = @iv_spras + INTO CORRESPONDING FIELDS OF TABLE @lt_base + WHERE mvke~vkorg = @iv_vkorg + AND mvke~prodh <> @space. + ELSE. + SELECT mvke~matnr, + mvke~vtweg, + mvke~prodh, + makt~maktx + FROM mvke + LEFT OUTER JOIN makt + ON makt~matnr = mvke~matnr + AND makt~spras = @iv_spras + INTO CORRESPONDING FIELDS OF TABLE @lt_base + WHERE mvke~vkorg = @iv_vkorg + AND mvke~vtweg = @iv_vtweg + AND mvke~prodh <> @space. + ENDIF. + + IF lt_base IS INITIAL. + RETURN. + ENDIF. + + LOOP AT lt_base ASSIGNING FIELD-SYMBOL(). + -paph1 = -prodh(5). + ENDLOOP. + + SORT lt_base BY matnr vtweg. + DELETE ADJACENT DUPLICATES FROM lt_base COMPARING matnr. + + SELECT paph1, wwpfa, wwpsp + FROM zprodsparte_map + INTO TABLE @lt_map + WHERE wwpfa <> @space. + SORT lt_map BY paph1. + + SELECT prodh, vtext + FROM t179t + INTO TABLE @DATA(lt_h) + WHERE spras = @iv_spras. + SORT lt_h BY prodh. + + SELECT wwpfa, bezek + FROM t25a0 + INTO TABLE @DATA(lt_fam) + WHERE spras = @iv_spras. + SORT lt_fam BY wwpfa. + + SELECT wwpsp, bezek + FROM t25a1 + INTO TABLE @DATA(lt_spa) + WHERE spras = @iv_spras. + SORT lt_spa BY wwpsp. + + LOOP AT lt_base INTO DATA(ls_base). + DATA(ls_out) = VALUE ty_out( + matnr = ls_base-matnr + maktx = ls_base-maktx + paph1 = ls_base-paph1 + wwpsp = 'UNASS' + wwpsp_text = iv_fallback + is_assigned = abap_false ). + + READ TABLE lt_h INTO DATA(ls_h) + WITH KEY prodh = ls_base-prodh BINARY SEARCH. + IF sy-subrc <> 0. + DATA(lv_prodh_key) = VALUE t179t-prodh( ). + lv_prodh_key = ls_out-paph1. + READ TABLE lt_h INTO ls_h + WITH KEY prodh = lv_prodh_key BINARY SEARCH. + ENDIF. + IF sy-subrc = 0. + ls_out-paph1_text = ls_h-vtext. + ENDIF. + + READ TABLE lt_map INTO DATA(ls_map) + WITH KEY paph1 = ls_out-paph1 BINARY SEARCH. + IF sy-subrc = 0. + ls_out-wwpfa = ls_map-wwpfa. + ls_out-wwpsp = ls_map-wwpsp. + ls_out-is_assigned = abap_true. + + READ TABLE lt_fam INTO DATA(ls_f) + WITH KEY wwpfa = ls_map-wwpfa BINARY SEARCH. + IF sy-subrc = 0. + ls_out-wwpfa_text = ls_f-bezek. + ENDIF. + + READ TABLE lt_spa INTO DATA(ls_s) + WITH KEY wwpsp = ls_map-wwpsp BINARY SEARCH. + IF sy-subrc = 0. + ls_out-wwpsp_text = ls_s-bezek. + ENDIF. + ENDIF. + + APPEND ls_out TO rt_out. + ENDLOOP. + ENDMETHOD. +ENDCLASS. diff --git a/TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_ALL.abap b/TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_ALL.abap new file mode 100644 index 0000000..1b8630b --- /dev/null +++ b/TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_ALL.abap @@ -0,0 +1,98 @@ +*&---------------------------------------------------------------------* +*& Report Z_PRODSPARTE_ALL +*&---------------------------------------------------------------------* +*& Zweck: Einziger ausfuehrbarer Report rund um die Provider-Logik. +*& Ruft ZCL_PRODSPARTE_PROVIDER=>GET_DATA und kann das Ergebnis +*& als ALV anzeigen oder als tab-getrennte CSV exportieren. +*&---------------------------------------------------------------------* +REPORT z_prodsparte_all. + +PARAMETERS: p_vkorg TYPE vkorg OBLIGATORY. +PARAMETERS: p_vtweg TYPE vtweg. +PARAMETERS: p_spras TYPE spras DEFAULT sy-langu. +PARAMETERS: p_fallb TYPE t25a1-bezek DEFAULT 'Nicht zugeordnet'. + +SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME. +PARAMETERS: p_alv TYPE abap_bool DEFAULT 'X' AS CHECKBOX. +PARAMETERS: p_csv TYPE abap_bool AS CHECKBOX. +PARAMETERS: p_file TYPE string LOWER CASE + DEFAULT 'C:\temp\prodsparte_export.csv'. +SELECTION-SCREEN END OF BLOCK b1. + +START-OF-SELECTION. + + DATA(lt_data) = zcl_prodsparte_provider=>get_data( + iv_vkorg = p_vkorg + iv_vtweg = p_vtweg + iv_spras = p_spras + iv_fallback = p_fallb ). + + IF lt_data IS INITIAL. + MESSAGE 'Keine Daten - VKORG/VTWEG pruefen.' TYPE 'I'. + RETURN. + ENDIF. + + WRITE: / 'Gelesene Saetze:', lines( lt_data ). + + IF p_csv = abap_true. + PERFORM export_csv USING lt_data p_file. + ENDIF. + + IF p_alv = abap_true. + PERFORM show_alv USING lt_data. + ENDIF. + +FORM export_csv USING it_data TYPE zcl_prodsparte_provider=>tt_out + iv_file TYPE string. + + DATA: lt_csv TYPE STANDARD TABLE OF string, + lv_sep TYPE c LENGTH 1. + + lv_sep = cl_abap_char_utilities=>horizontal_tab. + + APPEND |MATNR{ lv_sep }PAPH1{ lv_sep }PAPH1_TEXT{ lv_sep }WWPFA{ lv_sep }| + && |WWPFA_TEXT{ lv_sep }WWPSP{ lv_sep }WWPSP_TEXT{ lv_sep }| + && |IS_ASSIGNED{ lv_sep }MAKTX| TO lt_csv. + + LOOP AT it_data INTO DATA(ls). + APPEND |{ ls-matnr }{ lv_sep }{ ls-paph1 }{ lv_sep }{ ls-paph1_text }| + && |{ lv_sep }{ ls-wwpfa }{ lv_sep }{ ls-wwpfa_text }| + && |{ lv_sep }{ ls-wwpsp }{ lv_sep }{ ls-wwpsp_text }| + && |{ lv_sep }{ ls-is_assigned }{ lv_sep }{ ls-maktx }| + TO lt_csv. + ENDLOOP. + + cl_gui_frontend_services=>gui_download( + EXPORTING + filename = iv_file + filetype = 'ASC' + CHANGING + data_tab = lt_csv + EXCEPTIONS + OTHERS = 1 ). + + IF sy-subrc = 0. + WRITE: / lines( it_data ), 'Saetze exportiert nach', iv_file. + ELSE. + WRITE: / 'Download-Fehler, sy-subrc=', sy-subrc. + ENDIF. + +ENDFORM. + +FORM show_alv USING it_data TYPE zcl_prodsparte_provider=>tt_out. + + DATA lt_alv TYPE zcl_prodsparte_provider=>tt_out. + + lt_alv = it_data. + + cl_salv_table=>factory( + IMPORTING + r_salv_table = DATA(lo_alv) + CHANGING + t_table = lt_alv ). + + lo_alv->get_functions( )->set_all( abap_true ). + lo_alv->get_columns( )->set_optimize( abap_true ). + lo_alv->display( ). + +ENDFORM. diff --git a/TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_KEDR_K9R_FIND.abap b/TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_KEDR_K9R_FIND.abap new file mode 100644 index 0000000..5f97a60 --- /dev/null +++ b/TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_KEDR_K9R_FIND.abap @@ -0,0 +1,219 @@ +*&---------------------------------------------------------------------* +*& Report Z_PRODSPARTE_KEDR_K9R_FIND +*&---------------------------------------------------------------------* +*& Zweck: Technische KEDE/KEDR-Ableitungsregel finden. +*& +*& Hintergrund: +*& Ableitungsregeln werden in generierten K9R*-Tabellen gespeichert. +*& Die Feldnamen koennen generiert sein und muessen nicht PAPH1/WWPFA +*& heissen. Deshalb sucht dieser Report: +*& 1) TKEDRS-Zeilen mit K9R-/Produkt-/PAPH-/WWPFA-Hinweisen +*& 2) alle DDIC-Tabellen K9R* mit Feldliste und Beispielwerten +*& +*& Bitte die Ausgabe der passenden K9R-Tabelle + Feldliste schicken. +*&---------------------------------------------------------------------* +REPORT z_prodsparte_kedr_k9r_find. + +PARAMETERS: p_appl TYPE c LENGTH 2 DEFAULT 'KE', + p_like TYPE dd02l-tabname DEFAULT 'K9R%', + p_rows TYPE i DEFAULT 5. +PARAMETERS: p_all TYPE abap_bool AS CHECKBOX. + +FIELD-SYMBOLS: TYPE any, + TYPE STANDARD TABLE, + TYPE any. + +START-OF-SELECTION. + + PERFORM show_tkedrs. + SKIP 2. + PERFORM show_k9r_tables. + +FORM show_tkedrs. + + DATA lt_tkedrs TYPE STANDARD TABLE OF tkedrs. + + SELECT * + FROM tkedrs + INTO TABLE lt_tkedrs. + + WRITE: / '=== TKEDRS Diagnose ==='. + WRITE: / 'Gelesene TKEDRS-Zeilen:', lines( lt_tkedrs ). + ULINE. + + LOOP AT lt_tkedrs ASSIGNING . + DATA(lv_skip) = abap_false. + + ASSIGN COMPONENT 'APPLCLASS' OF STRUCTURE TO . + IF sy-subrc = 0 AND p_appl IS NOT INITIAL AND <> p_appl. + lv_skip = abap_true. + ENDIF. + IF lv_skip = abap_true. + CONTINUE. + ENDIF. + + DATA(lv_interesting) = p_all. + PERFORM row_contains USING 'K9R' CHANGING lv_interesting. + PERFORM row_contains USING 'PAPH' CHANGING lv_interesting. + PERFORM row_contains USING 'WWPFA' CHANGING lv_interesting. + PERFORM row_contains USING 'PROD' CHANGING lv_interesting. + PERFORM row_contains USING 'FAMIL' CHANGING lv_interesting. + + IF lv_interesting <> abap_true. + CONTINUE. + ENDIF. + + WRITE: / '--- TKEDRS Kandidat ---'. + PERFORM print_non_initial_components USING . + ULINE. + ENDLOOP. + +ENDFORM. + +FORM show_k9r_tables. + + DATA: lt_dd02l TYPE STANDARD TABLE OF dd02l, + lv_count TYPE i. + + SELECT * + FROM dd02l + INTO TABLE lt_dd02l + WHERE tabname LIKE p_like + AND as4local = 'A'. + + SORT lt_dd02l BY tabname. + + WRITE: / '=== K9R Tabellen ==='. + WRITE: / 'Gefundene Tabellen:', lines( lt_dd02l ). + ULINE. + + LOOP AT lt_dd02l INTO DATA(ls_dd02l). + CLEAR lv_count. + TRY. + SELECT COUNT( * ) + FROM (ls_dd02l-tabname) + INTO lv_count. + CATCH cx_sy_dynamic_osql_error. + CONTINUE. + ENDTRY. + + IF lv_count = 0 AND p_all <> abap_true. + CONTINUE. + ENDIF. + + WRITE: / '--- Tabelle:', ls_dd02l-tabname, + 'Klasse:', ls_dd02l-tabclass, + 'Zeilen:', lv_count. + + PERFORM show_fields USING ls_dd02l-tabname. + PERFORM show_samples USING ls_dd02l-tabname p_rows. + ULINE. + ENDLOOP. + +ENDFORM. + +FORM show_fields USING iv_tab TYPE dd02l-tabname. + + DATA lt_dfies TYPE STANDARD TABLE OF dfies. + + CALL FUNCTION 'DDIF_FIELDINFO_GET' + EXPORTING + tabname = iv_tab + TABLES + dfies_tab = lt_dfies + EXCEPTIONS + OTHERS = 1. + IF sy-subrc <> 0. + WRITE: / 'Feldliste nicht lesbar.'. + RETURN. + ENDIF. + + WRITE: / 'Felder:'. + LOOP AT lt_dfies INTO DATA(ls_f). + WRITE: / ls_f-position, + 6 ls_f-fieldname, + 28 ls_f-datatype, + 38 ls_f-leng, + 48 ls_f-fieldtext. + ENDLOOP. + +ENDFORM. + +FORM show_samples USING iv_tab TYPE dd02l-tabname + iv_rows TYPE i. + + DATA lr_table TYPE REF TO data. + + CREATE DATA lr_table TYPE STANDARD TABLE OF (iv_tab). + ASSIGN lr_table->* TO . + IF sy-subrc <> 0. + RETURN. + ENDIF. + + TRY. + SELECT * + FROM (iv_tab) + INTO TABLE + UP TO iv_rows ROWS. + CATCH cx_sy_dynamic_osql_error. + WRITE: / 'Beispielzeilen nicht lesbar.'. + RETURN. + ENDTRY. + + WRITE: / 'Beispielzeilen:'. + LOOP AT ASSIGNING . + PERFORM print_non_initial_components USING . + ULINE. + ENDLOOP. + +ENDFORM. + +FORM row_contains USING is_row TYPE any + iv_pattern TYPE string + CHANGING cv_found TYPE abap_bool. + + IF cv_found = abap_true. + RETURN. + ENDIF. + + DATA lo_desc TYPE REF TO cl_abap_typedescr. + DATA lo_str TYPE REF TO cl_abap_structdescr. + + lo_desc = cl_abap_typedescr=>describe_by_data( is_row ). + lo_str ?= lo_desc. + + LOOP AT lo_str->components INTO DATA(ls_comp). + ASSIGN COMPONENT ls_comp-name OF STRUCTURE is_row TO . + IF sy-subrc <> 0 OR IS INITIAL. + CONTINUE. + ENDIF. + + DATA(lv_value) = |{ }|. + TRANSLATE lv_value TO UPPER CASE. + + IF lv_value CS iv_pattern. + cv_found = abap_true. + RETURN. + ENDIF. + ENDLOOP. + +ENDFORM. + +FORM print_non_initial_components USING is_row TYPE any. + + DATA lo_desc TYPE REF TO cl_abap_typedescr. + DATA lo_str TYPE REF TO cl_abap_structdescr. + + lo_desc = cl_abap_typedescr=>describe_by_data( is_row ). + lo_str ?= lo_desc. + + LOOP AT lo_str->components INTO DATA(ls_comp). + ASSIGN COMPONENT ls_comp-name OF STRUCTURE is_row TO . + IF sy-subrc <> 0 OR IS INITIAL. + CONTINUE. + ENDIF. + + WRITE: / ls_comp-name, 32 '=', 35 . + ENDLOOP. + +ENDFORM. diff --git a/TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_KEDR_RULE_FIND.abap b/TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_KEDR_RULE_FIND.abap new file mode 100644 index 0000000..b5d08cc --- /dev/null +++ b/TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_KEDR_RULE_FIND.abap @@ -0,0 +1,161 @@ +*&---------------------------------------------------------------------* +*& Report Z_PRODSPARTE_KEDR_RULE_FIND +*&---------------------------------------------------------------------* +*& Zweck: Findet die technische Ablage der KEDE/KEDR-Regel +*& "Produktfamilie aus Produkthierarchie 1". +*& +*& Hintergrund: +*& In KEDE/KEDR ist fachlich PAPH1 von-bis -> WWPFA gepflegt. +*& Der Name der generierten Regeltabelle ist systemabhaengig. +*& Dieses Diagnoseprogramm sucht DDIC-Tabellen mit PAPH1/WWPFA +*& und zeigt Beispielzeilen inkl. moeglicher BIS-/Datumsfelder. +*&---------------------------------------------------------------------* +REPORT z_prodsparte_kedr_rule_find. + +PARAMETERS: p_max TYPE i DEFAULT 20. + +TYPES: BEGIN OF ty_tab, + tabname TYPE dd03l-tabname, + END OF ty_tab. + +TYPES: BEGIN OF ty_sample_exact, + paph1 TYPE ce11000-paph1, + wwpfa TYPE ce11000-wwpfa, + END OF ty_sample_exact. + +TYPES: BEGIN OF ty_sample_range, + paph1 TYPE ce11000-paph1, + paph1_bis TYPE ce11000-paph1, + wwpfa TYPE ce11000-wwpfa, + END OF ty_sample_range. + +START-OF-SELECTION. + + DATA: lt_paph1 TYPE STANDARD TABLE OF ty_tab, + lt_wwpfa TYPE STANDARD TABLE OF ty_tab, + lt_tabs TYPE STANDARD TABLE OF ty_tab. + + SELECT DISTINCT tabname + FROM dd03l + INTO TABLE @lt_paph1 + WHERE fieldname = 'PAPH1' + AND as4local = 'A'. + + SELECT DISTINCT tabname + FROM dd03l + INTO TABLE @lt_wwpfa + WHERE fieldname = 'WWPFA' + AND as4local = 'A'. + + SORT lt_paph1 BY tabname. + SORT lt_wwpfa BY tabname. + + LOOP AT lt_paph1 INTO DATA(ls_paph1). + READ TABLE lt_wwpfa TRANSPORTING NO FIELDS + WITH KEY tabname = ls_paph1-tabname BINARY SEARCH. + IF sy-subrc = 0. + APPEND ls_paph1 TO lt_tabs. + ENDIF. + ENDLOOP. + + SORT lt_tabs BY tabname. + DELETE ADJACENT DUPLICATES FROM lt_tabs COMPARING tabname. + + WRITE: / 'Tabellen mit Feldern PAPH1 und WWPFA:', lines( lt_tabs ). + ULINE. + + LOOP AT lt_tabs INTO DATA(ls_tab). + PERFORM show_candidate USING ls_tab-tabname p_max. + ENDLOOP. + +FORM show_candidate USING iv_tab TYPE dd03l-tabname + iv_max TYPE i. + + DATA: lt_dfies TYPE STANDARD TABLE OF dfies, + lv_count TYPE i, + lv_has_bis TYPE abap_bool. + + CALL FUNCTION 'DDIF_FIELDINFO_GET' + EXPORTING + tabname = iv_tab + TABLES + dfies_tab = lt_dfies + EXCEPTIONS + OTHERS = 1. + IF sy-subrc <> 0. + RETURN. + ENDIF. + + READ TABLE lt_dfies TRANSPORTING NO FIELDS + WITH KEY fieldname = 'PAPH1_BIS'. + IF sy-subrc = 0. + lv_has_bis = abap_true. + ENDIF. + + TRY. + SELECT COUNT( * ) + FROM (iv_tab) + INTO @lv_count + WHERE paph1 <> @space + AND wwpfa <> @space. + CATCH cx_sy_dynamic_osql_error. + RETURN. + ENDTRY. + + IF lv_count = 0. + RETURN. + ENDIF. + + WRITE: / '--- Kandidat:', iv_tab, 'Saetze mit PAPH1/WWPFA:', lv_count. + WRITE: / 'Relevante Felder:'. + LOOP AT lt_dfies INTO DATA(ls_f) + WHERE fieldname CS 'PAPH' + OR fieldname CS 'WWPFA' + OR fieldname CS 'BIS' + OR fieldname CS 'LOW' + OR fieldname CS 'HIGH' + OR fieldname CS 'VON' + OR fieldname CS 'DAT' + OR fieldname CS 'DEL' + OR fieldname CS 'LOE'. + WRITE: / ls_f-fieldname, 18 ls_f-datatype, 28 ls_f-leng, 36 ls_f-fieldtext. + ENDLOOP. + ULINE. + + IF lv_has_bis = abap_true. + DATA lt_range TYPE STANDARD TABLE OF ty_sample_range. + TRY. + SELECT paph1, paph1_bis, wwpfa + FROM (iv_tab) + INTO TABLE @lt_range + UP TO @iv_max ROWS + WHERE paph1 <> @space + AND wwpfa <> @space. + WRITE: / 'Beispiele PAPH1 / PAPH1_BIS / WWPFA:'. + LOOP AT lt_range INTO DATA(ls_range). + WRITE: / ls_range-paph1, 12 ls_range-paph1_bis, 24 ls_range-wwpfa. + ENDLOOP. + CATCH cx_sy_dynamic_osql_error. + WRITE: / 'Beispiele mit PAPH1_BIS konnten nicht gelesen werden.'. + ENDTRY. + ELSE. + DATA lt_exact TYPE STANDARD TABLE OF ty_sample_exact. + TRY. + SELECT paph1, wwpfa + FROM (iv_tab) + INTO TABLE @lt_exact + UP TO @iv_max ROWS + WHERE paph1 <> @space + AND wwpfa <> @space. + WRITE: / 'Beispiele PAPH1 / WWPFA:'. + LOOP AT lt_exact INTO DATA(ls_exact). + WRITE: / ls_exact-paph1, 12 ls_exact-wwpfa. + ENDLOOP. + CATCH cx_sy_dynamic_osql_error. + WRITE: / 'Beispiele konnten nicht gelesen werden.'. + ENDTRY. + ENDIF. + + ULINE. + +ENDFORM. diff --git a/TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_MAP_BUILD.abap b/TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_MAP_BUILD.abap new file mode 100644 index 0000000..7b49fe0 --- /dev/null +++ b/TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_MAP_BUILD.abap @@ -0,0 +1,408 @@ +*&---------------------------------------------------------------------* +*& Report Z_PRODSPARTE_MAP_BUILD +*&---------------------------------------------------------------------* +*& Zweck: Baut ZPRODSPARTE_MAP direkt aus den KEDE/KEDR-Regeltabellen. +*& +*& Quelle: +*& K9RT761000002 PAPH1 von-bis -> Produktfamilie WWPFA +*& K9RT761000003 Produktfamilie von-bis -> Produktsparte WWPSP +*& +*& Ergebnis: +*& ZPRODSPARTE_MAP bleibt eine flache Einzelwert-Tabelle: +*& PAPH1 -> WWPFA -> WWPSP +*& +*& Die Von-bis-Regeln werden vollstaendig in Einzel-PAPH1 expandiert, +*& damit ZPRODSPARTE_MAP mindestens alle KEDE-/Data(4)-Referenzcodes +*& enthaelt. Reale PAPH1-Codes aus MVKE/CE11000 werden optional +*& zusaetzlich aufgenommen, aendern aber nicht die Referenzabdeckung. +*&---------------------------------------------------------------------* +REPORT z_prodsparte_map_build. + +PARAMETERS: p_vkorg TYPE vkorg, + p_vtweg TYPE vtweg. +PARAMETERS: p_ce TYPE abap_bool DEFAULT 'X' AS CHECKBOX. +PARAMETERS: p_test TYPE abap_bool DEFAULT 'X' AS CHECKBOX. + +TYPES: BEGIN OF ty_code, + paph1 TYPE zprodsparte_map-paph1, + END OF ty_code. + +TYPES: BEGIN OF ty_pfa_rule, + sour1_from TYPE k9rt761000002-sour1_from, + sour1_to TYPE k9rt761000002-sour1_to, + valid_from TYPE k9rt761000002-valid_from, + target1 TYPE k9rt761000002-target1, + END OF ty_pfa_rule. + +TYPES: BEGIN OF ty_spa_rule, + sour1_from TYPE k9rt761000003-sour1_from, + sour1_to TYPE k9rt761000003-sour1_to, + valid_from TYPE k9rt761000003-valid_from, + target1 TYPE k9rt761000003-target1, + END OF ty_spa_rule. + +TYPES tt_code TYPE STANDARD TABLE OF ty_code WITH DEFAULT KEY. +TYPES tt_map TYPE STANDARD TABLE OF zprodsparte_map WITH DEFAULT KEY. + +DATA: gt_code TYPE SORTED TABLE OF ty_code WITH UNIQUE KEY paph1, + gt_pfa TYPE STANDARD TABLE OF ty_pfa_rule WITH DEFAULT KEY, + gt_spa TYPE STANDARD TABLE OF ty_spa_rule WITH DEFAULT KEY. + +DATA: gv_rule_expanded TYPE i, + gv_real_added TYPE i, + gv_unsupported TYPE i. + +START-OF-SELECTION. + + PERFORM load_rules. + PERFORM load_codes. + PERFORM build_map. + +FORM load_codes. + + DATA lt_prodh TYPE STANDARD TABLE OF mvke-prodh WITH DEFAULT KEY. + DATA lt_ce_paph1 TYPE STANDARD TABLE OF ce11000-paph1 WITH DEFAULT KEY. + + LOOP AT gt_pfa INTO DATA(ls_pfa_rule). + PERFORM add_paph1_range USING ls_pfa_rule-sour1_from + ls_pfa_rule-sour1_to. + ENDLOOP. + + IF p_vkorg IS INITIAL AND p_vtweg IS INITIAL. + SELECT DISTINCT prodh + FROM mvke + INTO TABLE @lt_prodh + WHERE prodh <> @space. + ELSEIF p_vkorg IS NOT INITIAL AND p_vtweg IS INITIAL. + SELECT DISTINCT prodh + FROM mvke + INTO TABLE @lt_prodh + WHERE vkorg = @p_vkorg + AND prodh <> @space. + ELSEIF p_vkorg IS INITIAL AND p_vtweg IS NOT INITIAL. + SELECT DISTINCT prodh + FROM mvke + INTO TABLE @lt_prodh + WHERE vtweg = @p_vtweg + AND prodh <> @space. + ELSE. + SELECT DISTINCT prodh + FROM mvke + INTO TABLE @lt_prodh + WHERE vkorg = @p_vkorg + AND vtweg = @p_vtweg + AND prodh <> @space. + ENDIF. + + LOOP AT lt_prodh INTO DATA(lv_prodh). + INSERT VALUE ty_code( paph1 = lv_prodh(5) ) INTO TABLE gt_code. + gv_real_added = gv_real_added + 1. + ENDLOOP. + + IF p_ce = abap_true. + SELECT DISTINCT paph1 + FROM ce11000 + INTO TABLE @lt_ce_paph1 + WHERE paph1 <> @space. + + LOOP AT lt_ce_paph1 INTO DATA(lv_paph1). + INSERT VALUE ty_code( paph1 = lv_paph1 ) INTO TABLE gt_code. + gv_real_added = gv_real_added + 1. + ENDLOOP. + ENDIF. + + WRITE: / 'PAPH1-Codes aus KEDE-Expansion :', gv_rule_expanded. + WRITE: / 'Zusaetzliche reale Code-Versuche:', gv_real_added. + WRITE: / 'Nicht expandierbare Bereiche :', gv_unsupported. + WRITE: / 'PAPH1-Codes gesamt eindeutig :', lines( gt_code ). + + IF gt_code IS INITIAL. + MESSAGE 'Keine PAPH1-Codes gefunden.' TYPE 'E'. + ENDIF. + +ENDFORM. + +FORM add_paph1_range USING iv_from TYPE k9rt761000002-sour1_from + iv_to TYPE k9rt761000002-sour1_to. + + DATA: lv_from TYPE string, + lv_to TYPE string. + + lv_from = iv_from. + lv_to = iv_to. + CONDENSE: lv_from, lv_to. + + IF lv_from IS INITIAL. + gv_unsupported = gv_unsupported + 1. + RETURN. + ENDIF. + + IF lv_to IS INITIAL. + lv_to = lv_from. + ENDIF. + + IF lv_from CO '0123456789' + AND lv_to CO '0123456789'. + DATA(lv_from_i) = CONV i( lv_from ). + DATA(lv_to_i) = CONV i( lv_to ). + DATA(lv_width) = strlen( lv_from ). + + IF lv_to_i < lv_from_i. + gv_unsupported = gv_unsupported + 1. + RETURN. + ENDIF. + + DATA(lv_count) = lv_to_i - lv_from_i + 1. + + DO lv_count TIMES. + DATA(lv_num) = lv_from_i + sy-index - 1. + DATA(lv_paph1_num) = + |{ lv_num ALIGN = RIGHT PAD = '0' WIDTH = lv_width }|. + INSERT VALUE ty_code( paph1 = lv_paph1_num ) INTO TABLE gt_code. + gv_rule_expanded = gv_rule_expanded + 1. + ENDDO. + RETURN. + ENDIF. + + IF strlen( lv_from ) = 4 + AND strlen( lv_to ) = 4 + AND lv_from(2) = lv_to(2). + DATA(lv_alpha) = `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ`. + DATA(lv_alpha_len) = strlen( lv_alpha ). + DATA(lv_prefix) = lv_from(2). + + DO lv_alpha_len TIMES. + DATA(lv_off3) = sy-index - 1. + DATA(lv_c3) = lv_alpha+lv_off3(1). + + DO lv_alpha_len TIMES. + DATA(lv_off4) = sy-index - 1. + DATA(lv_c4) = lv_alpha+lv_off4(1). + DATA(lv_paph1_alpha) = |{ lv_prefix }{ lv_c3 }{ lv_c4 }|. + + IF lv_paph1_alpha >= lv_from + AND lv_paph1_alpha <= lv_to. + INSERT VALUE ty_code( paph1 = lv_paph1_alpha ) INTO TABLE gt_code. + gv_rule_expanded = gv_rule_expanded + 1. + ENDIF. + ENDDO. + ENDDO. + RETURN. + ENDIF. + + gv_unsupported = gv_unsupported + 1. + +ENDFORM. + +FORM load_rules. + + SELECT sour1_from, sour1_to, valid_from, target1 + FROM k9rt761000002 + INTO TABLE @gt_pfa + WHERE sour1_from <> @space + AND target1 <> @space + AND delete_flg <> 'X'. + + SELECT sour1_from, sour1_to, valid_from, target1 + FROM k9rt761000003 + INTO TABLE @gt_spa + WHERE sour1_from <> @space + AND target1 <> @space + AND delete_flg <> 'X'. + + SORT gt_pfa BY valid_from DESCENDING sour1_from sour1_to. + SORT gt_spa BY valid_from DESCENDING sour1_from sour1_to. + + WRITE: / 'KEDR-Regeln PAPH1->WWPFA :', lines( gt_pfa ). + WRITE: / 'KEDR-Regeln WWPFA->WWPSP :', lines( gt_spa ). + ULINE. + + IF gt_pfa IS INITIAL. + MESSAGE 'Keine KEDE-Regeln in K9RT761000002 gefunden.' TYPE 'E'. + ENDIF. + + IF gt_spa IS INITIAL. + MESSAGE 'Keine KEDE-Regeln in K9RT761000003 gefunden.' TYPE 'E'. + ENDIF. + +ENDFORM. + +FORM build_map. + + DATA: lt_insert TYPE tt_map, + lt_no_pfa TYPE tt_code, + lt_no_spa TYPE tt_code. + + LOOP AT gt_code INTO DATA(ls_code). + DATA: lv_wwpfa TYPE zprodsparte_map-wwpfa, + lv_wwpsp TYPE zprodsparte_map-wwpsp, + lv_found_pfa TYPE abap_bool, + lv_found_spa TYPE abap_bool, + lv_best_date TYPE dats, + lv_best_exact TYPE abap_bool. + + CLEAR: lv_wwpfa, + lv_wwpsp, + lv_found_pfa, + lv_found_spa, + lv_best_date, + lv_best_exact. + + LOOP AT gt_pfa INTO DATA(ls_pfa). + DATA(lv_pfa_to) = ls_pfa-sour1_to. + IF lv_pfa_to IS INITIAL. + lv_pfa_to = ls_pfa-sour1_from. + ENDIF. + + IF ls_code-paph1 < ls_pfa-sour1_from + OR ls_code-paph1 > lv_pfa_to. + CONTINUE. + ENDIF. + + DATA(lv_exact_pfa) = abap_false. + IF ls_code-paph1 = ls_pfa-sour1_from + AND ls_code-paph1 = lv_pfa_to. + lv_exact_pfa = abap_true. + ENDIF. + + IF lv_found_pfa = abap_false + OR ls_pfa-valid_from > lv_best_date + OR ( ls_pfa-valid_from = lv_best_date + AND lv_best_exact = abap_false + AND lv_exact_pfa = abap_true ). + lv_found_pfa = abap_true. + lv_best_date = ls_pfa-valid_from. + lv_best_exact = lv_exact_pfa. + lv_wwpfa = ls_pfa-target1. + ENDIF. + ENDLOOP. + + IF lv_found_pfa = abap_false. + APPEND ls_code TO lt_no_pfa. + CONTINUE. + ENDIF. + + CLEAR: lv_best_date, lv_best_exact. + + LOOP AT gt_spa INTO DATA(ls_spa). + DATA(lv_spa_to) = ls_spa-sour1_to. + IF lv_spa_to IS INITIAL. + lv_spa_to = ls_spa-sour1_from. + ENDIF. + + IF lv_wwpfa < ls_spa-sour1_from + OR lv_wwpfa > lv_spa_to. + CONTINUE. + ENDIF. + + DATA(lv_exact_spa) = abap_false. + IF lv_wwpfa = ls_spa-sour1_from + AND lv_wwpfa = lv_spa_to. + lv_exact_spa = abap_true. + ENDIF. + + IF lv_found_spa = abap_false + OR ls_spa-valid_from > lv_best_date + OR ( ls_spa-valid_from = lv_best_date + AND lv_best_exact = abap_false + AND lv_exact_spa = abap_true ). + lv_found_spa = abap_true. + lv_best_date = ls_spa-valid_from. + lv_best_exact = lv_exact_spa. + lv_wwpsp = ls_spa-target1. + ENDIF. + ENDLOOP. + + IF lv_found_spa = abap_false. + APPEND ls_code TO lt_no_spa. + ENDIF. + + APPEND VALUE zprodsparte_map( + paph1 = ls_code-paph1 + wwpfa = lv_wwpfa + wwpsp = lv_wwpsp + crdate = sy-datum + cruser = sy-uname ) TO lt_insert. + ENDLOOP. + + WRITE: / 'Saetze fuer ZPRODSPARTE_MAP :', lines( lt_insert ). + WRITE: / 'PAPH1 ohne Produktfamilie :', lines( lt_no_pfa ). + WRITE: / 'PAPH1 ohne Produktsparte :', lines( lt_no_spa ). + ULINE. + + PERFORM show_preview USING lt_insert lt_no_pfa lt_no_spa. + + IF lt_insert IS INITIAL. + WRITE: / 'Keine Saetze zum Schreiben. Tabelle bleibt unveraendert.'. + RETURN. + ENDIF. + + IF p_test = abap_true. + WRITE: / 'TESTLAUF - keine DB-Aenderung.'. + RETURN. + ENDIF. + + DELETE FROM zprodsparte_map. + INSERT zprodsparte_map FROM TABLE lt_insert. + + IF sy-subrc = 0. + COMMIT WORK. + WRITE: / lines( lt_insert ), 'Saetze in ZPRODSPARTE_MAP geschrieben.'. + ELSE. + ROLLBACK WORK. + WRITE: / 'Fehler beim Schreiben, sy-subrc=', sy-subrc. + ENDIF. + +ENDFORM. + +FORM show_preview USING it_insert TYPE tt_map + it_no_pfa TYPE tt_code + it_no_spa TYPE tt_code. + + FIELD-SYMBOLS: TYPE zprodsparte_map, + TYPE ty_code. + + WRITE: / '=== Vorschau Mapping max. 30 ==='. + WRITE: / 'PAPH1', 10 'WWPFA', 20 'WWPSP'. + ULINE. + + DATA lv_i TYPE i. + LOOP AT it_insert ASSIGNING . + lv_i = lv_i + 1. + IF lv_i > 30. + EXIT. + ENDIF. + WRITE: / -paph1, + 10 -wwpfa, + 20 -wwpsp. + ENDLOOP. + + IF it_no_pfa IS NOT INITIAL. + ULINE. + WRITE: / '=== Erste PAPH1 ohne Produktfamilie ==='. + CLEAR lv_i. + LOOP AT it_no_pfa ASSIGNING . + lv_i = lv_i + 1. + IF lv_i > 30. + EXIT. + ENDIF. + WRITE: / -paph1. + ENDLOOP. + ENDIF. + + IF it_no_spa IS NOT INITIAL. + ULINE. + WRITE: / '=== Erste PAPH1 ohne Produktsparte ==='. + CLEAR lv_i. + LOOP AT it_no_spa ASSIGNING . + lv_i = lv_i + 1. + IF lv_i > 30. + EXIT. + ENDIF. + WRITE: / -paph1. + ENDLOOP. + ENDIF. + + ULINE. + +ENDFORM. diff --git a/TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_MAP_EXPORT.abap b/TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_MAP_EXPORT.abap new file mode 100644 index 0000000..85151aa --- /dev/null +++ b/TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_MAP_EXPORT.abap @@ -0,0 +1,49 @@ +*&---------------------------------------------------------------------* +*& Report Z_PRODSPARTE_MAP_EXPORT +*&---------------------------------------------------------------------* +*& Zweck: Exportiert die flache Mapping-Tabelle ZPRODSPARTE_MAP zur +*& Kontrolle gegen Data(4)/KEDE-Referenz. +*& +*& Erwartung nach Z_PRODSPARTE_MAP_BUILD: +*& Alle Data(4)-Referenzcodes muessen hier mit gleicher WWPFA stehen. +*&---------------------------------------------------------------------* +REPORT z_prodsparte_map_export. + +PARAMETERS: p_file TYPE string LOWER CASE + DEFAULT 'C:\temp\zprodsparte_map_export.csv'. + +START-OF-SELECTION. + + SELECT paph1, wwpfa, wwpsp + FROM zprodsparte_map + INTO TABLE @DATA(lt_map) + WHERE paph1 <> @space. + + SORT lt_map BY paph1. + + DATA: lt_csv TYPE STANDARD TABLE OF string, + lv_sep TYPE c LENGTH 1. + + lv_sep = ';'. + + APPEND |PAPH1{ lv_sep }WWPFA{ lv_sep }WWPSP| TO lt_csv. + + LOOP AT lt_map INTO DATA(ls_map). + APPEND |{ ls_map-paph1 }{ lv_sep }{ ls_map-wwpfa }{ lv_sep }{ ls_map-wwpsp }| + TO lt_csv. + ENDLOOP. + + cl_gui_frontend_services=>gui_download( + EXPORTING + filename = p_file + filetype = 'ASC' + CHANGING + data_tab = lt_csv + EXCEPTIONS + OTHERS = 1 ). + + IF sy-subrc = 0. + WRITE: / lines( lt_map ), 'Mapping-Saetze exportiert nach', p_file. + ELSE. + WRITE: / 'Download-Fehler, sy-subrc=', sy-subrc. + ENDIF. diff --git a/TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_MAP_IMPORT.abap b/TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_MAP_IMPORT.abap new file mode 100644 index 0000000..606c8ff --- /dev/null +++ b/TrafagSalesExporter/spartenlogic/Z_PRODSPARTE_MAP_IMPORT.abap @@ -0,0 +1,265 @@ +*&---------------------------------------------------------------------* +*& Report Z_PRODSPARTE_MAP_IMPORT +*&---------------------------------------------------------------------* +*& Zweck: Importiert die in KEDE/KEDR gepflegten Regeln +*& PAPH1 von-bis -> WWPFA aus einem Regel-Export und expandiert +*& Bereiche in Einzelwerte. +*& +*& Ergebnis in ZPRODSPARTE_MAP: +*& PAPH1 -> WWPFA -> WWPSP +*& +*& Keine Tabellenerweiterung fuer PAPH1_BIS/GUELTAB erforderlich. +*& Die Von-bis-Information wird nur beim Import verarbeitet. +*&---------------------------------------------------------------------* +REPORT z_prodsparte_map_import. + +PARAMETERS: p_file TYPE string LOWER CASE + DEFAULT 'C:\temp\kede_regeln.csv' OBLIGATORY. +PARAMETERS: p_sep TYPE c LENGTH 1 DEFAULT ';'. +PARAMETERS: p_test TYPE abap_bool DEFAULT 'X' AS CHECKBOX. + +TYPES: BEGIN OF ty_rule, + paph1 TYPE zprodsparte_map-paph1, + wwpfa TYPE zprodsparte_map-wwpfa, + gueltab TYPE dats, + END OF ty_rule. + +TYPES: BEGIN OF ty_fs, + wwpfa TYPE ce11000-wwpfa, + wwpsp TYPE ce11000-wwpsp, + cnt TYPE i, + END OF ty_fs. + +START-OF-SELECTION. + + DATA lt_raw TYPE STANDARD TABLE OF string. + + cl_gui_frontend_services=>gui_upload( + EXPORTING + filename = p_file + filetype = 'ASC' + CHANGING + data_tab = lt_raw + EXCEPTIONS + OTHERS = 1 ). + IF sy-subrc <> 0. + MESSAGE |Datei { p_file } nicht lesbar, sy-subrc={ sy-subrc }| TYPE 'E'. + ENDIF. + + DATA: lt_rule TYPE STANDARD TABLE OF ty_rule, + lt_fields TYPE STANDARD TABLE OF string, + lv_from TYPE string, + lv_to TYPE string, + lv_date TYPE string, + lv_del TYPE string, + lv_wwpfa TYPE string, + lv_gueltab TYPE dats, + lv_skipped TYPE i, + lv_expanded TYPE i, + lv_unsupported TYPE i. + + LOOP AT lt_raw INTO DATA(lv_line). + CLEAR: lt_fields, lv_from, lv_to, lv_date, lv_del, lv_wwpfa, lv_gueltab. + SPLIT lv_line AT p_sep INTO TABLE lt_fields. + + IF lines( lt_fields ) < 7. + lv_skipped = lv_skipped + 1. + CONTINUE. + ENDIF. + + READ TABLE lt_fields INTO lv_from INDEX 1. + READ TABLE lt_fields INTO lv_to INDEX 3. + READ TABLE lt_fields INTO lv_date INDEX 5. + READ TABLE lt_fields INTO lv_del INDEX 6. + READ TABLE lt_fields INTO lv_wwpfa INDEX 7. + + CONDENSE: lv_from, lv_to, lv_date, lv_del, lv_wwpfa. + + IF lv_from IS INITIAL + OR lv_wwpfa IS INITIAL + OR lv_from CS 'ProdHierarchie' + OR lv_from CS 'Seite'. + lv_skipped = lv_skipped + 1. + CONTINUE. + ENDIF. + + IF lv_del = 'X' OR lv_del = 'x'. + lv_skipped = lv_skipped + 1. + CONTINUE. + ENDIF. + + IF strlen( lv_date ) = 10 + AND lv_date+2(1) = '.' + AND lv_date+5(1) = '.'. + lv_gueltab = |{ lv_date+6(4) }{ lv_date+3(2) }{ lv_date(2) }|. + ENDIF. + + IF lv_wwpfa CO '0123456789' + AND strlen( lv_wwpfa ) < 4. + lv_wwpfa = |{ lv_wwpfa ALIGN = RIGHT PAD = '0' WIDTH = 4 }|. + ENDIF. + + IF lv_to IS INITIAL. + APPEND VALUE ty_rule( + paph1 = lv_from + wwpfa = lv_wwpfa + gueltab = lv_gueltab ) TO lt_rule. + CONTINUE. + ENDIF. + + IF lv_from CO '0123456789' + AND lv_to CO '0123456789'. + DATA(lv_from_i) = CONV i( lv_from ). + DATA(lv_to_i) = CONV i( lv_to ). + DATA(lv_width) = strlen( lv_from ). + + IF lv_to_i < lv_from_i. + lv_unsupported = lv_unsupported + 1. + CONTINUE. + ENDIF. + + DATA(lv_count) = lv_to_i - lv_from_i + 1. + + DO lv_count TIMES. + DATA(lv_num) = lv_from_i + sy-index - 1. + DATA(lv_paph1_num) = + |{ lv_num ALIGN = RIGHT PAD = '0' WIDTH = lv_width }|. + APPEND VALUE ty_rule( + paph1 = lv_paph1_num + wwpfa = lv_wwpfa + gueltab = lv_gueltab ) TO lt_rule. + lv_expanded = lv_expanded + 1. + ENDDO. + CONTINUE. + ENDIF. + + " Bekannter alphanumerischer Finance-Bereich: z.B. 09B0 bis 09M4. + IF strlen( lv_from ) = 4 + AND strlen( lv_to ) = 4 + AND lv_from(2) = lv_to(2). + DATA(lv_alpha) = `0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ`. + DATA(lv_prefix) = lv_from(2). + + DO strlen( lv_alpha ) TIMES. + DATA(lv_off3) = sy-index - 1. + DATA(lv_c3) = lv_alpha+lv_off3(1). + + DO strlen( lv_alpha ) TIMES. + DATA(lv_off4) = sy-index - 1. + DATA(lv_c4) = lv_alpha+lv_off4(1). + DATA(lv_paph1_alpha) = |{ lv_prefix }{ lv_c3 }{ lv_c4 }|. + + IF lv_paph1_alpha >= lv_from + AND lv_paph1_alpha <= lv_to. + APPEND VALUE ty_rule( + paph1 = lv_paph1_alpha + wwpfa = lv_wwpfa + gueltab = lv_gueltab ) TO lt_rule. + lv_expanded = lv_expanded + 1. + ENDIF. + ENDDO. + ENDDO. + ELSE. + lv_unsupported = lv_unsupported + 1. + ENDIF. + ENDLOOP. + + SORT lt_rule BY paph1 gueltab DESCENDING. + DELETE ADJACENT DUPLICATES FROM lt_rule COMPARING paph1. + + WRITE: / 'CSV-Zeilen gelesen :', lines( lt_raw ). + WRITE: / 'Einzelwerte nach Expansion :', lines( lt_rule ). + WRITE: / 'Expandierte Bereichswerte :', lv_expanded. + WRITE: / 'Uebersprungene Zeilen :', lv_skipped. + WRITE: / 'Nicht expandierbare Bereiche:', lv_unsupported. + ULINE. + + IF lt_rule IS INITIAL. + MESSAGE 'Keine gueltigen Regeln gefunden.' TYPE 'E'. + ENDIF. + + DATA: lt_fs TYPE STANDARD TABLE OF ty_fs, + ls_fs TYPE ty_fs. + + SELECT DISTINCT wwpfa, wwpsp + FROM ce11000 + INTO TABLE @DATA(lt_combo) + WHERE wwpfa <> @space + AND wwpsp <> @space. + + LOOP AT lt_combo INTO DATA(ls_combo). + READ TABLE lt_fs INTO ls_fs WITH KEY wwpfa = ls_combo-wwpfa. + IF sy-subrc <> 0. + ls_fs-wwpfa = ls_combo-wwpfa. + ls_fs-wwpsp = ls_combo-wwpsp. + ls_fs-cnt = 1. + APPEND ls_fs TO lt_fs. + ELSEIF ls_fs-wwpsp <> ls_combo-wwpsp. + ls_fs-cnt = ls_fs-cnt + 1. + MODIFY lt_fs FROM ls_fs TRANSPORTING cnt + WHERE wwpfa = ls_combo-wwpfa. + ENDIF. + ENDLOOP. + + LOOP AT lt_fs INTO ls_fs WHERE cnt > 1. + WRITE: / 'WARNUNG: Familie', ls_fs-wwpfa, + 'hat mehrere Sparten in CE11000 - WWPSP bleibt leer.'. + ENDLOOP. + DELETE lt_fs WHERE cnt > 1. + SORT lt_fs BY wwpfa. + + DATA: lt_insert TYPE STANDARD TABLE OF zprodsparte_map, + lv_no_fs TYPE i. + + LOOP AT lt_rule INTO DATA(ls_rule). + READ TABLE lt_fs INTO ls_fs + WITH KEY wwpfa = ls_rule-wwpfa BINARY SEARCH. + + DATA(ls_insert) = VALUE zprodsparte_map( + paph1 = ls_rule-paph1 + wwpfa = ls_rule-wwpfa + crdate = sy-datum + cruser = sy-uname ). + + IF sy-subrc = 0. + ls_insert-wwpsp = ls_fs-wwpsp. + ELSE. + lv_no_fs = lv_no_fs + 1. + ENDIF. + + APPEND ls_insert TO lt_insert. + ENDLOOP. + + WRITE: / 'Familien ohne eindeutige Sparte:', lv_no_fs. + ULINE. + WRITE: / '=== Vorschau max. 30 ==='. + WRITE: / 'PAPH1', 10 'WWPFA', 20 'WWPSP'. + ULINE. + + DATA lv_i TYPE i. + LOOP AT lt_insert INTO DATA(ls_preview). + lv_i = lv_i + 1. + IF lv_i > 30. + EXIT. + ENDIF. + WRITE: / ls_preview-paph1, + 10 ls_preview-wwpfa, + 20 ls_preview-wwpsp. + ENDLOOP. + ULINE. + + IF p_test = abap_true. + WRITE: / 'TESTLAUF - keine DB-Aenderung.'. + RETURN. + ENDIF. + + DELETE FROM zprodsparte_map. + INSERT zprodsparte_map FROM TABLE lt_insert. + + IF sy-subrc = 0. + COMMIT WORK. + WRITE: / lines( lt_insert ), 'Einzelwerte in ZPRODSPARTE_MAP geschrieben.'. + ELSE. + ROLLBACK WORK. + WRITE: / 'Fehler beim Schreiben, sy-subrc=', sy-subrc. + ENDIF.