Add product division map fallback

This commit is contained in:
2026-06-10 16:17:02 +02:00
parent d1d50e9c0c
commit f23fa1662e
19 changed files with 2399 additions and 18 deletions
@@ -7,6 +7,8 @@ namespace TrafagSalesExporter.Services;
public class DatabaseSeedService : IDatabaseSeedService public class DatabaseSeedService : IDatabaseSeedService
{ {
private const string SpainSharePointFolder = "https://trafagag.sharepoint.com/sites/WorldwideBIPlatform/Import/Finance/Spanien";
public void SeedDefaults(AppDbContext db) public void SeedDefaults(AppDbContext db)
{ {
SeedIfEmpty(db); SeedIfEmpty(db);
@@ -529,6 +531,12 @@ public class DatabaseSeedService : IDatabaseSeedService
changed = true; changed = true;
} }
if (ShouldRepairSpainManualImportPath(existing.ManualImportFilePath))
{
existing.ManualImportFilePath = SpainSharePointFolder;
changed = true;
}
if (changed) if (changed)
db.SaveChanges(); db.SaveChanges();
@@ -541,11 +549,22 @@ public class DatabaseSeedService : IDatabaseSeedService
TSC = "TRES", TSC = "TRES",
Land = "Spanien", Land = "Spanien",
SourceSystem = "MANUAL_EXCEL", SourceSystem = "MANUAL_EXCEL",
ManualImportFilePath = SpainSharePointFolder,
IsActive = false IsActive = false
}); });
db.SaveChanges(); 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) private static void EnsureGermanyManualExcelSite(AppDbContext db)
{ {
if (db.Sites.Count() <= 1) if (db.Sites.Count() <= 1)
@@ -912,7 +931,7 @@ public class DatabaseSeedService : IDatabaseSeedService
} }
var obsoleteSources = db.SapSourceDefinitions 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(); .ToList();
foreach (var obsoleteSource in obsoleteSources) 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 var productJoin = db.SapJoinDefinitions
.OrderBy(x => x.Id) .OrderBy(x => x.Id)
.FirstOrDefault(x => .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)[] var mappings = new (string Target, string Source, bool Required)[]
{ {
(nameof(SalesRecord.Tsc), "Z.Tsc", true), (nameof(SalesRecord.Tsc), "Z.Tsc", true),
@@ -1040,13 +1158,13 @@ public class DatabaseSeedService : IDatabaseSeedService
(nameof(SalesRecord.Material), "Z.Matnr", false), (nameof(SalesRecord.Material), "Z.Matnr", false),
(nameof(SalesRecord.Name), "Z.Arktx", false), (nameof(SalesRecord.Name), "Z.Arktx", false),
(nameof(SalesRecord.ProductGroup), "Z.Prodh", false), (nameof(SalesRecord.ProductGroup), "Z.Prodh", false),
(nameof(SalesRecord.ProductHierarchyCode), "P.Paph1", false), (nameof(SalesRecord.ProductHierarchyCode), "FirstNonEmpty(P.Paph1, M.Paph1)", false),
(nameof(SalesRecord.ProductHierarchyText), "P.Paph1Text", false), (nameof(SalesRecord.ProductHierarchyText), "FirstNonEmpty(P.Paph1Text, M.Paph1Text)", false),
(nameof(SalesRecord.ProductFamilyCode), "P.Wwpfa", false), (nameof(SalesRecord.ProductFamilyCode), "FirstNonEmpty(P.Wwpfa, M.Wwpfa)", false),
(nameof(SalesRecord.ProductFamilyText), "P.WwpfaText", false), (nameof(SalesRecord.ProductFamilyText), "FirstNonEmpty(P.WwpfaText, M.WwpfaText)", false),
(nameof(SalesRecord.ProductDivisionCode), "P.Wwpsp", false), (nameof(SalesRecord.ProductDivisionCode), "FirstNonEmpty(P.Wwpsp, M.Wwpsp)", false),
(nameof(SalesRecord.ProductDivisionText), "P.WwpspText", false), (nameof(SalesRecord.ProductDivisionText), "FirstNonEmpty(P.WwpspText, M.WwpspText)", false),
(nameof(SalesRecord.ProductMappingAssigned), "P.IsAssigned", false), (nameof(SalesRecord.ProductMappingAssigned), "FirstNonEmpty(P.IsAssigned, M.IsAssigned)", false),
(nameof(SalesRecord.Quantity), "Z.Fkimg", false), (nameof(SalesRecord.Quantity), "Z.Fkimg", false),
(nameof(SalesRecord.CustomerNumber), "Z.Kunnr", false), (nameof(SalesRecord.CustomerNumber), "Z.Kunnr", false),
(nameof(SalesRecord.CustomerName), "Z.Name1", false), (nameof(SalesRecord.CustomerName), "Z.Name1", false),
@@ -129,12 +129,54 @@ public sealed class MappedSalesRecordComposer : IMappedSalesRecordComposer
if (value.StartsWith('=')) if (value.StartsWith('='))
return value[1..]; return value[1..];
if (TryEvaluateFirstNonEmpty(row, value, out var firstNonEmpty))
return firstNonEmpty;
if (row.TryGetValue(value, out var direct)) if (row.TryGetValue(value, out var direct))
return direct; return direct;
return null; return null;
} }
private static bool TryEvaluateFirstNonEmpty(Dictionary<string, object?> 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) private static void ApplyValue(SalesRecord record, string targetField, object? value)
{ {
var property = typeof(SalesRecord).GetProperty(targetField); var property = typeof(SalesRecord).GetProperty(targetField);
@@ -135,6 +135,23 @@ public class DatabaseInitializationServiceTests : IDisposable
Assert.Equal(sageServer.Id, india.HanaServerId); 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() private async Task PrepareLegacySitesTableAsync()
{ {
await using var db = await _dbFactory.CreateDbContextAsync(); await using var db = await _dbFactory.CreateDbContextAsync();
@@ -240,6 +257,35 @@ VALUES (
await db.SaveChangesAsync(); 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() private async Task PrepareBrokenHanaServerForeignKeyAsync()
{ {
await using var db = await _dbFactory.CreateDbContextAsync(); await using var db = await _dbFactory.CreateDbContextAsync();
@@ -108,6 +108,126 @@ public class MappedSalesRecordComposerTests
Assert.Equal("HANA", result[0].DocumentType); 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<string, List<Dictionary<string, object?>>>(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<string, List<Dictionary<string, object?>>>(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) private static SapFieldMapping Mapping(string targetField, string sourceExpression)
=> new() => new()
{ {
@@ -2,6 +2,83 @@
Letzter Nachtrag: 2026-06-10 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 ## Nachtrag 2026-06-10 Deploy India / SAGE HANA Mapping
Durchgefuehrt: Durchgefuehrt:
@@ -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. - 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 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. - 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: Weiterhin gilt:
+5 -2
View File
@@ -5,9 +5,12 @@ Stand: 2026-06-10
## Kurzstand ## Kurzstand
- `TrafagSalesExporter` wird als ASP.NET/IIS-Webanwendung im bisherigen `BiDashboard`-Schema publiziert. - `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$\`. - 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`. - 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`. - 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. - Lokaler Uebergangsserver: `http://172.16.9.185:5000` im Trafag-Netz, IP kann wechseln.
+3 -2
View File
@@ -5,9 +5,10 @@ Stand: 2026-06-10
## Kurzstand ## Kurzstand
- Fuehrende App: `TrafagSalesExporter`, publiziert als `BiDashboard`. - Fuehrende App: `TrafagSalesExporter`, publiziert als `BiDashboard`.
- Letzter dokumentierter Stand: India/TRIN SAGE-HANA-Fix deployed, Finance-Kursworkflow dokumentiert, aktive Markdown-Doku bereinigt. - Letzter dokumentierter Stand: CH/AT-Produktsparten-Fallback ueber `ProductDivisionMapSet` deployed; India/TRIN SAGE-HANA-Fix und Spanien-SharePoint-Pfad bleiben abgesichert.
- Validierung laut Doku: `84/84` Tests gruen fuer den India-Fix; fruehere UI-/Deploy-Schritte wurden einzeln umgesetzt und deployed. - 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$\`. - 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`. - 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. - 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`. - Neu im Finance/Management-Cockpit: einfache Schnelluebersicht links sichtbar; tiefere Funktionen bleiben unter `Experten`.
+8 -5
View File
@@ -8,14 +8,17 @@ Diese Datei ist fuer tokenarme RAG-Nutzung komprimiert.
- Fuehrender Kurzkontext: `docs/rag/PROJECT.md`. - Fuehrender Kurzkontext: `docs/rag/PROJECT.md`.
- Themenrouter: `docs/RAG_ROUTER.md`. - Themenrouter: `docs/RAG_ROUTER.md`.
- Letzter dokumentierter Code-Stand: India/TRIN HANA-Route gegen SourceSystem-Drift abgesichert; Finance-Kursworkflow und Markdown-Bereinigung dokumentiert. - 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.Tests\TrafagSalesExporter.Tests.csproj --verbosity minimal` mit `84/84` Tests gruen. - Letzte dokumentierte Validierung: `dotnet test TrafagSalesExporter.sln --verbosity minimal` mit `87/87` Tests gruen.
- Letzter dokumentierter Deploy: 2026-06-10 India/SAGE-HANA-Fix nach `\\trch-webapp-bidashboard.trafagch.local\BiDashboard$\`. - 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. - 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`. - 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`. - 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 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 bei `Mixed`-Waehrung einen Warnhinweis, weil Summen/Anteile ueber mehrere Waehrungen fachlich nur eingeschraenkt belastbar sind.
- Neu lokal: Sparten-Finanzanalyse zeigt die groessten Treiber fuer `Nicht im TR-AG-Stamm`, damit hohe nicht zugeordnete Umsaetze nach Land/TSC/Material analysiert werden koennen. - Neu 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.
@@ -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.
@@ -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.
@@ -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.
@@ -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(<ls_base>).
<ls_base>-paph1 = <ls_base>-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.
@@ -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.
@@ -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: <ls_any> TYPE any,
<lt_any> TYPE STANDARD TABLE,
<lv_any> 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 <ls_any>.
DATA(lv_skip) = abap_false.
ASSIGN COMPONENT 'APPLCLASS' OF STRUCTURE <ls_any> TO <lv_any>.
IF sy-subrc = 0 AND p_appl IS NOT INITIAL AND <lv_any> <> p_appl.
lv_skip = abap_true.
ENDIF.
IF lv_skip = abap_true.
CONTINUE.
ENDIF.
DATA(lv_interesting) = p_all.
PERFORM row_contains USING <ls_any> 'K9R' CHANGING lv_interesting.
PERFORM row_contains USING <ls_any> 'PAPH' CHANGING lv_interesting.
PERFORM row_contains USING <ls_any> 'WWPFA' CHANGING lv_interesting.
PERFORM row_contains USING <ls_any> 'PROD' CHANGING lv_interesting.
PERFORM row_contains USING <ls_any> 'FAMIL' CHANGING lv_interesting.
IF lv_interesting <> abap_true.
CONTINUE.
ENDIF.
WRITE: / '--- TKEDRS Kandidat ---'.
PERFORM print_non_initial_components USING <ls_any>.
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 <lt_any>.
IF sy-subrc <> 0.
RETURN.
ENDIF.
TRY.
SELECT *
FROM (iv_tab)
INTO TABLE <lt_any>
UP TO iv_rows ROWS.
CATCH cx_sy_dynamic_osql_error.
WRITE: / 'Beispielzeilen nicht lesbar.'.
RETURN.
ENDTRY.
WRITE: / 'Beispielzeilen:'.
LOOP AT <lt_any> ASSIGNING <ls_any>.
PERFORM print_non_initial_components USING <ls_any>.
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 <lv_any>.
IF sy-subrc <> 0 OR <lv_any> IS INITIAL.
CONTINUE.
ENDIF.
DATA(lv_value) = |{ <lv_any> }|.
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 <lv_any>.
IF sy-subrc <> 0 OR <lv_any> IS INITIAL.
CONTINUE.
ENDIF.
WRITE: / ls_comp-name, 32 '=', 35 <lv_any>.
ENDLOOP.
ENDFORM.
@@ -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.
@@ -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: <ls_insert> TYPE zprodsparte_map,
<ls_code> 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 <ls_insert>.
lv_i = lv_i + 1.
IF lv_i > 30.
EXIT.
ENDIF.
WRITE: / <ls_insert>-paph1,
10 <ls_insert>-wwpfa,
20 <ls_insert>-wwpsp.
ENDLOOP.
IF it_no_pfa IS NOT INITIAL.
ULINE.
WRITE: / '=== Erste PAPH1 ohne Produktfamilie ==='.
CLEAR lv_i.
LOOP AT it_no_pfa ASSIGNING <ls_code>.
lv_i = lv_i + 1.
IF lv_i > 30.
EXIT.
ENDIF.
WRITE: / <ls_code>-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 <ls_code>.
lv_i = lv_i + 1.
IF lv_i > 30.
EXIT.
ENDIF.
WRITE: / <ls_code>-paph1.
ENDLOOP.
ENDIF.
ULINE.
ENDFORM.
@@ -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.
@@ -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.