From 7e9a61f8772b2ef8a0712b8bccc64a9e60d08d74 Mon Sep 17 00:00:00 2001 From: metacube Date: Fri, 29 May 2026 08:49:37 +0200 Subject: [PATCH] Add product division gateway mapping --- .../Components/Pages/Standorte.razor | 7 + .../Models/CentralSalesRecord.cs | 7 + TrafagSalesExporter/Models/SalesRecord.cs | 7 + .../Services/CentralSalesRecordService.cs | 27 +++- .../Services/ConfigTransferService.cs | 7 + ...DatabaseInitializationService.SchemaSql.cs | 7 + .../DatabaseSchemaMaintenanceService.cs | 7 + .../Services/ExcelExportService.cs | 82 +++++++----- .../Services/ManualExcelImportService.cs | 14 ++ .../ExcelExportServiceTests.cs | 8 +- .../PRODUCT_SPARTEN_MAPPING_2026-05-27.md | 125 ++++++++++++++++++ .../docs/abap/README_PRODSPARTE.md | 67 ++++++++++ .../docs/rag/PRODUCT_MAPPING.md | 35 ++++- TrafagSalesExporter/lastchange.md | 46 +++++++ 14 files changed, 402 insertions(+), 44 deletions(-) diff --git a/TrafagSalesExporter/Components/Pages/Standorte.razor b/TrafagSalesExporter/Components/Pages/Standorte.razor index 62e2b28..00ecd98 100644 --- a/TrafagSalesExporter/Components/Pages/Standorte.razor +++ b/TrafagSalesExporter/Components/Pages/Standorte.razor @@ -1100,6 +1100,13 @@ [nameof(SalesRecord.Material)] = ["ArtikelNummer", "Material", "Groesse"], [nameof(SalesRecord.Name)] = ["ArtikelBezeichnung", "Name"], [nameof(SalesRecord.ProductGroup)] = ["Warengruppen-Bezeichnung", "Product Group"], + [nameof(SalesRecord.ProductHierarchyCode)] = ["Product Hierarchy Code", "Produkthierarchie Code", "PAPH1"], + [nameof(SalesRecord.ProductHierarchyText)] = ["Product Hierarchy Text", "Produkthierarchie Text", "PAPH1_TEXT"], + [nameof(SalesRecord.ProductFamilyCode)] = ["Product Family Code", "Produktfamilie Code", "WWPFA"], + [nameof(SalesRecord.ProductFamilyText)] = ["Product Family Text", "Produktfamilie Text", "WWPFA_TEXT"], + [nameof(SalesRecord.ProductDivisionCode)] = ["Product Division Code", "Produktsparte Code", "WWPSP"], + [nameof(SalesRecord.ProductDivisionText)] = ["Product Division Text", "Produktsparte Text", "WWPSP_TEXT"], + [nameof(SalesRecord.ProductMappingAssigned)] = ["Product Mapping Assigned", "Produktmapping Zugeordnet", "IS_ASSIGNED"], [nameof(SalesRecord.Quantity)] = ["Anz. VE", "Quantity"], [nameof(SalesRecord.SupplierNumber)] = ["Lieferanten Nummer", "Supplier number"], [nameof(SalesRecord.SupplierName)] = ["Name Lieferant", "Supplier name"], diff --git a/TrafagSalesExporter/Models/CentralSalesRecord.cs b/TrafagSalesExporter/Models/CentralSalesRecord.cs index 2d01399..60a4d99 100644 --- a/TrafagSalesExporter/Models/CentralSalesRecord.cs +++ b/TrafagSalesExporter/Models/CentralSalesRecord.cs @@ -20,6 +20,13 @@ public class CentralSalesRecord public string Material { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public string ProductGroup { get; set; } = string.Empty; + public string ProductHierarchyCode { get; set; } = string.Empty; + public string ProductHierarchyText { get; set; } = string.Empty; + public string ProductFamilyCode { get; set; } = string.Empty; + public string ProductFamilyText { get; set; } = string.Empty; + public string ProductDivisionCode { get; set; } = string.Empty; + public string ProductDivisionText { get; set; } = string.Empty; + public string ProductMappingAssigned { get; set; } = string.Empty; public decimal Quantity { get; set; } public string SupplierNumber { get; set; } = string.Empty; public string SupplierName { get; set; } = string.Empty; diff --git a/TrafagSalesExporter/Models/SalesRecord.cs b/TrafagSalesExporter/Models/SalesRecord.cs index ffaed3a..96b80ee 100644 --- a/TrafagSalesExporter/Models/SalesRecord.cs +++ b/TrafagSalesExporter/Models/SalesRecord.cs @@ -11,6 +11,13 @@ public class SalesRecord public string Material { get; set; } = string.Empty; public string Name { get; set; } = string.Empty; public string ProductGroup { get; set; } = string.Empty; + public string ProductHierarchyCode { get; set; } = string.Empty; + public string ProductHierarchyText { get; set; } = string.Empty; + public string ProductFamilyCode { get; set; } = string.Empty; + public string ProductFamilyText { get; set; } = string.Empty; + public string ProductDivisionCode { get; set; } = string.Empty; + public string ProductDivisionText { get; set; } = string.Empty; + public string ProductMappingAssigned { get; set; } = string.Empty; public decimal Quantity { get; set; } public string SupplierNumber { get; set; } = string.Empty; public string SupplierName { get; set; } = string.Empty; diff --git a/TrafagSalesExporter/Services/CentralSalesRecordService.cs b/TrafagSalesExporter/Services/CentralSalesRecordService.cs index 77b7037..d22d1a7 100644 --- a/TrafagSalesExporter/Services/CentralSalesRecordService.cs +++ b/TrafagSalesExporter/Services/CentralSalesRecordService.cs @@ -70,6 +70,13 @@ public class CentralSalesRecordService : ICentralSalesRecordService Material = r.Material, Name = r.Name, ProductGroup = r.ProductGroup, + ProductHierarchyCode = r.ProductHierarchyCode, + ProductHierarchyText = r.ProductHierarchyText, + ProductFamilyCode = r.ProductFamilyCode, + ProductFamilyText = r.ProductFamilyText, + ProductDivisionCode = r.ProductDivisionCode, + ProductDivisionText = r.ProductDivisionText, + ProductMappingAssigned = r.ProductMappingAssigned, Quantity = r.Quantity, SupplierNumber = r.SupplierNumber, SupplierName = r.SupplierName, @@ -164,7 +171,8 @@ public class CentralSalesRecordService : ICentralSalesRecordService command.CommandText = """ INSERT INTO CentralSalesRecords ( StoredAtUtc, SiteId, SourceSystem, ExtractionDate, Tsc, DocumentEntry, InvoiceNumber, PositionOnInvoice, - Material, Name, ProductGroup, Quantity, SupplierNumber, SupplierName, SupplierCountry, + Material, Name, ProductGroup, ProductHierarchyCode, ProductHierarchyText, ProductFamilyCode, ProductFamilyText, + ProductDivisionCode, ProductDivisionText, ProductMappingAssigned, Quantity, SupplierNumber, SupplierName, SupplierCountry, CustomerNumber, CustomerName, CustomerCountry, CustomerIndustry, StandardCost, StandardCostCurrency, PurchaseOrderNumber, SalesPriceValue, SalesCurrency, Incoterms2020, DocumentCurrency, DocumentTotalForeignCurrency, DocumentTotalLocalCurrency, VatSumForeignCurrency, @@ -172,7 +180,8 @@ public class CentralSalesRecordService : ICentralSalesRecordService ) VALUES ( $storedAtUtc, $siteId, $sourceSystem, $extractionDate, $tsc, $documentEntry, $invoiceNumber, $positionOnInvoice, - $material, $name, $productGroup, $quantity, $supplierNumber, $supplierName, $supplierCountry, + $material, $name, $productGroup, $productHierarchyCode, $productHierarchyText, $productFamilyCode, $productFamilyText, + $productDivisionCode, $productDivisionText, $productMappingAssigned, $quantity, $supplierNumber, $supplierName, $supplierCountry, $customerNumber, $customerName, $customerCountry, $customerIndustry, $standardCost, $standardCostCurrency, $purchaseOrderNumber, $salesPriceValue, $salesCurrency, $incoterms2020, $documentCurrency, $documentTotalForeignCurrency, $documentTotalLocalCurrency, $vatSumForeignCurrency, @@ -191,6 +200,13 @@ public class CentralSalesRecordService : ICentralSalesRecordService command.Parameters.Add("$material", SqliteType.Text); command.Parameters.Add("$name", SqliteType.Text); command.Parameters.Add("$productGroup", SqliteType.Text); + command.Parameters.Add("$productHierarchyCode", SqliteType.Text); + command.Parameters.Add("$productHierarchyText", SqliteType.Text); + command.Parameters.Add("$productFamilyCode", SqliteType.Text); + command.Parameters.Add("$productFamilyText", SqliteType.Text); + command.Parameters.Add("$productDivisionCode", SqliteType.Text); + command.Parameters.Add("$productDivisionText", SqliteType.Text); + command.Parameters.Add("$productMappingAssigned", SqliteType.Text); command.Parameters.Add("$quantity", SqliteType.Real); command.Parameters.Add("$supplierNumber", SqliteType.Text); command.Parameters.Add("$supplierName", SqliteType.Text); @@ -235,6 +251,13 @@ public class CentralSalesRecordService : ICentralSalesRecordService command.Parameters["$material"].Value = record.Material ?? string.Empty; command.Parameters["$name"].Value = record.Name ?? string.Empty; command.Parameters["$productGroup"].Value = record.ProductGroup ?? string.Empty; + command.Parameters["$productHierarchyCode"].Value = record.ProductHierarchyCode ?? string.Empty; + command.Parameters["$productHierarchyText"].Value = record.ProductHierarchyText ?? string.Empty; + command.Parameters["$productFamilyCode"].Value = record.ProductFamilyCode ?? string.Empty; + command.Parameters["$productFamilyText"].Value = record.ProductFamilyText ?? string.Empty; + command.Parameters["$productDivisionCode"].Value = record.ProductDivisionCode ?? string.Empty; + command.Parameters["$productDivisionText"].Value = record.ProductDivisionText ?? string.Empty; + command.Parameters["$productMappingAssigned"].Value = record.ProductMappingAssigned ?? string.Empty; command.Parameters["$quantity"].Value = record.Quantity; command.Parameters["$supplierNumber"].Value = record.SupplierNumber ?? string.Empty; command.Parameters["$supplierName"].Value = record.SupplierName ?? string.Empty; diff --git a/TrafagSalesExporter/Services/ConfigTransferService.cs b/TrafagSalesExporter/Services/ConfigTransferService.cs index 91247ad..6177273 100644 --- a/TrafagSalesExporter/Services/ConfigTransferService.cs +++ b/TrafagSalesExporter/Services/ConfigTransferService.cs @@ -428,6 +428,13 @@ public class ConfigTransferService : IConfigTransferService Material = record.Material, Name = record.Name, ProductGroup = record.ProductGroup, + ProductHierarchyCode = record.ProductHierarchyCode, + ProductHierarchyText = record.ProductHierarchyText, + ProductFamilyCode = record.ProductFamilyCode, + ProductFamilyText = record.ProductFamilyText, + ProductDivisionCode = record.ProductDivisionCode, + ProductDivisionText = record.ProductDivisionText, + ProductMappingAssigned = record.ProductMappingAssigned, Quantity = record.Quantity, SupplierNumber = record.SupplierNumber, SupplierName = record.SupplierName, diff --git a/TrafagSalesExporter/Services/DatabaseInitializationService.SchemaSql.cs b/TrafagSalesExporter/Services/DatabaseInitializationService.SchemaSql.cs index a7ff796..d5aee8b 100644 --- a/TrafagSalesExporter/Services/DatabaseInitializationService.SchemaSql.cs +++ b/TrafagSalesExporter/Services/DatabaseInitializationService.SchemaSql.cs @@ -91,6 +91,13 @@ CREATE TABLE CentralSalesRecords ( Material TEXT NOT NULL, Name TEXT NOT NULL, ProductGroup TEXT NOT NULL, + ProductHierarchyCode TEXT NOT NULL DEFAULT '', + ProductHierarchyText TEXT NOT NULL DEFAULT '', + ProductFamilyCode TEXT NOT NULL DEFAULT '', + ProductFamilyText TEXT NOT NULL DEFAULT '', + ProductDivisionCode TEXT NOT NULL DEFAULT '', + ProductDivisionText TEXT NOT NULL DEFAULT '', + ProductMappingAssigned TEXT NOT NULL DEFAULT '', Quantity TEXT NOT NULL, SupplierNumber TEXT NOT NULL, SupplierName TEXT NOT NULL, diff --git a/TrafagSalesExporter/Services/DatabaseSchemaMaintenanceService.cs b/TrafagSalesExporter/Services/DatabaseSchemaMaintenanceService.cs index d0c3f65..b5025ee 100644 --- a/TrafagSalesExporter/Services/DatabaseSchemaMaintenanceService.cs +++ b/TrafagSalesExporter/Services/DatabaseSchemaMaintenanceService.cs @@ -52,6 +52,13 @@ public class DatabaseSchemaMaintenanceService : IDatabaseSchemaMaintenanceServic AddColumnIfMissing(db, "CentralSalesRecords", "VatSumLocalCurrency", "TEXT NOT NULL DEFAULT '0'"); AddColumnIfMissing(db, "CentralSalesRecords", "DocumentRate", "TEXT NOT NULL DEFAULT '0'"); AddColumnIfMissing(db, "CentralSalesRecords", "CompanyCurrency", "TEXT NOT NULL DEFAULT ''"); + AddColumnIfMissing(db, "CentralSalesRecords", "ProductHierarchyCode", "TEXT NOT NULL DEFAULT ''"); + AddColumnIfMissing(db, "CentralSalesRecords", "ProductHierarchyText", "TEXT NOT NULL DEFAULT ''"); + AddColumnIfMissing(db, "CentralSalesRecords", "ProductFamilyCode", "TEXT NOT NULL DEFAULT ''"); + AddColumnIfMissing(db, "CentralSalesRecords", "ProductFamilyText", "TEXT NOT NULL DEFAULT ''"); + AddColumnIfMissing(db, "CentralSalesRecords", "ProductDivisionCode", "TEXT NOT NULL DEFAULT ''"); + AddColumnIfMissing(db, "CentralSalesRecords", "ProductDivisionText", "TEXT NOT NULL DEFAULT ''"); + AddColumnIfMissing(db, "CentralSalesRecords", "ProductMappingAssigned", "TEXT NOT NULL DEFAULT ''"); AddColumnIfMissing(db, "CentralSalesRecords", "PostingDate", "TEXT NULL"); EnsureAppEventLogTable(db); } diff --git a/TrafagSalesExporter/Services/ExcelExportService.cs b/TrafagSalesExporter/Services/ExcelExportService.cs index 09c93cb..24458a8 100644 --- a/TrafagSalesExporter/Services/ExcelExportService.cs +++ b/TrafagSalesExporter/Services/ExcelExportService.cs @@ -84,6 +84,13 @@ public class ExcelExportService : IExcelExportService "Material", "Name", "Product Group", + "Product Hierarchy Code", + "Product Hierarchy Text", + "Product Family Code", + "Product Family Text", + "Product Division Code", + "Product Division Text", + "Product Mapping Assigned", "Quantity", "Supplier number", "Supplier name", @@ -137,44 +144,51 @@ public class ExcelExportService : IExcelExportService ws.Cell(row, 6).Value = record.Material; ws.Cell(row, 7).Value = record.Name; ws.Cell(row, 8).Value = record.ProductGroup; - ws.Cell(row, 9).Value = record.Quantity; - ws.Cell(row, 10).Value = record.SupplierNumber; - ws.Cell(row, 11).Value = record.SupplierName; - ws.Cell(row, 12).Value = record.SupplierCountry; - ws.Cell(row, 13).Value = record.CustomerNumber; - ws.Cell(row, 14).Value = record.CustomerName; - ws.Cell(row, 15).Value = record.CustomerCountry; - ws.Cell(row, 16).Value = record.CustomerIndustry; - ws.Cell(row, 17).Value = record.StandardCost; - ws.Cell(row, 18).Value = record.StandardCostCurrency; - ws.Cell(row, 19).Value = record.PurchaseOrderNumber; - ws.Cell(row, 20).Value = record.SalesPriceValue; - ws.Cell(row, 21).Value = record.SalesCurrency; - ws.Cell(row, 22).Value = record.DocumentCurrency; - ws.Cell(row, 23).Value = record.DocumentTotalForeignCurrency; - ws.Cell(row, 24).Value = record.DocumentTotalLocalCurrency; - ws.Cell(row, 25).Value = record.VatSumForeignCurrency; - ws.Cell(row, 26).Value = record.VatSumLocalCurrency; - ws.Cell(row, 27).Value = record.DocumentRate; - ws.Cell(row, 28).Value = record.CompanyCurrency; - ws.Cell(row, 29).Value = record.Incoterms2020; - ws.Cell(row, 30).Value = record.SalesResponsibleEmployee; - ws.Cell(row, 31).Value = record.PostingDate?.ToString("dd.MM.yyyy") ?? string.Empty; - ws.Cell(row, 32).Value = record.InvoiceDate?.ToString("dd.MM.yyyy") ?? string.Empty; - ws.Cell(row, 33).Value = record.OrderDate?.ToString("dd.MM.yyyy") ?? string.Empty; - ws.Cell(row, 34).Value = record.Land; - ws.Cell(row, 35).Value = record.DocumentType; + ws.Cell(row, 9).Value = record.ProductHierarchyCode; + ws.Cell(row, 10).Value = record.ProductHierarchyText; + ws.Cell(row, 11).Value = record.ProductFamilyCode; + ws.Cell(row, 12).Value = record.ProductFamilyText; + ws.Cell(row, 13).Value = record.ProductDivisionCode; + ws.Cell(row, 14).Value = record.ProductDivisionText; + ws.Cell(row, 15).Value = record.ProductMappingAssigned; + ws.Cell(row, 16).Value = record.Quantity; + ws.Cell(row, 17).Value = record.SupplierNumber; + ws.Cell(row, 18).Value = record.SupplierName; + ws.Cell(row, 19).Value = record.SupplierCountry; + ws.Cell(row, 20).Value = record.CustomerNumber; + ws.Cell(row, 21).Value = record.CustomerName; + ws.Cell(row, 22).Value = record.CustomerCountry; + ws.Cell(row, 23).Value = record.CustomerIndustry; + ws.Cell(row, 24).Value = record.StandardCost; + ws.Cell(row, 25).Value = record.StandardCostCurrency; + ws.Cell(row, 26).Value = record.PurchaseOrderNumber; + ws.Cell(row, 27).Value = record.SalesPriceValue; + ws.Cell(row, 28).Value = record.SalesCurrency; + ws.Cell(row, 29).Value = record.DocumentCurrency; + ws.Cell(row, 30).Value = record.DocumentTotalForeignCurrency; + ws.Cell(row, 31).Value = record.DocumentTotalLocalCurrency; + ws.Cell(row, 32).Value = record.VatSumForeignCurrency; + ws.Cell(row, 33).Value = record.VatSumLocalCurrency; + ws.Cell(row, 34).Value = record.DocumentRate; + ws.Cell(row, 35).Value = record.CompanyCurrency; + ws.Cell(row, 36).Value = record.Incoterms2020; + ws.Cell(row, 37).Value = record.SalesResponsibleEmployee; + ws.Cell(row, 38).Value = record.PostingDate?.ToString("dd.MM.yyyy") ?? string.Empty; + ws.Cell(row, 39).Value = record.InvoiceDate?.ToString("dd.MM.yyyy") ?? string.Empty; + ws.Cell(row, 40).Value = record.OrderDate?.ToString("dd.MM.yyyy") ?? string.Empty; + ws.Cell(row, 41).Value = record.Land; + ws.Cell(row, 42).Value = record.DocumentType; var financeCountryKey = ResolveFinanceCountryKey(record.Land, record.Tsc); var financeDate = financeRuleEngine.ResolveFinanceDate(record, financeCountryKey); var financeInclude = financeRuleEngine.ShouldInclude(record, financeCountryKey); var financeNetSalesActual = financeRuleEngine.ResolveNetSalesActual(record, financeCountryKey, financeInclude); - ws.Cell(row, 36).Value = financeDate.Year; - ws.Cell(row, 37).Value = financeCountryKey; - ws.Cell(row, 38).Value = financeDate.ToString("dd.MM.yyyy"); - ws.Cell(row, 39).Value = financeNetSalesActual; - ws.Cell(row, 40).Value = ResolveFinanceCurrency(record); - ws.Cell(row, 41).Value = financeInclude && financeNetSalesActual != 0m ? "TRUE" : "FALSE"; - ws.Cell(row, 42).Value = financeInclude + ws.Cell(row, 43).Value = financeDate.Year; + ws.Cell(row, 44).Value = financeCountryKey; + ws.Cell(row, 45).Value = financeDate.ToString("dd.MM.yyyy"); + ws.Cell(row, 46).Value = financeNetSalesActual; + ws.Cell(row, 47).Value = ResolveFinanceCurrency(record); + ws.Cell(row, 48).Value = financeInclude && financeNetSalesActual != 0m ? "TRUE" : "FALSE"; + ws.Cell(row, 49).Value = financeInclude ? "Sales Price/Value" : financeRuleEngine.ResolveExclusionReason(record, financeCountryKey); row++; diff --git a/TrafagSalesExporter/Services/ManualExcelImportService.cs b/TrafagSalesExporter/Services/ManualExcelImportService.cs index 966fa0b..48639b1 100644 --- a/TrafagSalesExporter/Services/ManualExcelImportService.cs +++ b/TrafagSalesExporter/Services/ManualExcelImportService.cs @@ -24,6 +24,13 @@ public class ManualExcelImportService : IManualExcelImportService ["material"] = nameof(SalesRecord.Material), ["name"] = nameof(SalesRecord.Name), ["productgroup"] = nameof(SalesRecord.ProductGroup), + ["producthierarchycode"] = nameof(SalesRecord.ProductHierarchyCode), + ["producthierarchytext"] = nameof(SalesRecord.ProductHierarchyText), + ["productfamilycode"] = nameof(SalesRecord.ProductFamilyCode), + ["productfamilytext"] = nameof(SalesRecord.ProductFamilyText), + ["productdivisioncode"] = nameof(SalesRecord.ProductDivisionCode), + ["productdivisiontext"] = nameof(SalesRecord.ProductDivisionText), + ["productmappingassigned"] = nameof(SalesRecord.ProductMappingAssigned), ["quantity"] = nameof(SalesRecord.Quantity), ["suppliernumber"] = nameof(SalesRecord.SupplierNumber), ["suppliername"] = nameof(SalesRecord.SupplierName), @@ -162,6 +169,13 @@ public class ManualExcelImportService : IManualExcelImportService Material = ReadString(headerIndexes, fields, nameof(SalesRecord.Material)), Name = ReadString(headerIndexes, fields, nameof(SalesRecord.Name)), ProductGroup = ReadString(headerIndexes, fields, nameof(SalesRecord.ProductGroup)), + ProductHierarchyCode = ReadString(headerIndexes, fields, nameof(SalesRecord.ProductHierarchyCode)), + ProductHierarchyText = ReadString(headerIndexes, fields, nameof(SalesRecord.ProductHierarchyText)), + ProductFamilyCode = ReadString(headerIndexes, fields, nameof(SalesRecord.ProductFamilyCode)), + ProductFamilyText = ReadString(headerIndexes, fields, nameof(SalesRecord.ProductFamilyText)), + ProductDivisionCode = ReadString(headerIndexes, fields, nameof(SalesRecord.ProductDivisionCode)), + ProductDivisionText = ReadString(headerIndexes, fields, nameof(SalesRecord.ProductDivisionText)), + ProductMappingAssigned = ReadString(headerIndexes, fields, nameof(SalesRecord.ProductMappingAssigned)), Quantity = ReadDecimal(headerIndexes, fields, nameof(SalesRecord.Quantity)), SupplierNumber = ReadString(headerIndexes, fields, nameof(SalesRecord.SupplierNumber)), SupplierName = ReadString(headerIndexes, fields, nameof(SalesRecord.SupplierName)), diff --git a/TrafagSalesExporter/TrafagSalesExporter.Tests/ExcelExportServiceTests.cs b/TrafagSalesExporter/TrafagSalesExporter.Tests/ExcelExportServiceTests.cs index 62c2657..f1b0795 100644 --- a/TrafagSalesExporter/TrafagSalesExporter.Tests/ExcelExportServiceTests.cs +++ b/TrafagSalesExporter/TrafagSalesExporter.Tests/ExcelExportServiceTests.cs @@ -37,13 +37,13 @@ public class ExcelExportServiceTests var sales = workbook.Worksheet("Sales"); var includedGermanyRows = sales.RowsUsed() .Skip(1) - .Where(row => row.Cell(36).GetValue() == 2025) - .Where(row => row.Cell(37).GetString() == "DE") - .Where(row => row.Cell(41).GetString() == "TRUE") + .Where(row => row.Cell(43).GetValue() == 2025) + .Where(row => row.Cell(44).GetString() == "DE") + .Where(row => row.Cell(48).GetString() == "TRUE") .ToList(); Assert.Equal(2, includedGermanyRows.Count); - Assert.Equal(80m, includedGermanyRows.Sum(row => row.Cell(39).GetValue())); + Assert.Equal(80m, includedGermanyRows.Sum(row => row.Cell(46).GetValue())); var details = workbook.Worksheet("Finance Details"); var includedGermanyDetailRows = details.RowsUsed() diff --git a/TrafagSalesExporter/docs/PRODUCT_SPARTEN_MAPPING_2026-05-27.md b/TrafagSalesExporter/docs/PRODUCT_SPARTEN_MAPPING_2026-05-27.md index 3b2c90d..43f56c9 100644 --- a/TrafagSalesExporter/docs/PRODUCT_SPARTEN_MAPPING_2026-05-27.md +++ b/TrafagSalesExporter/docs/PRODUCT_SPARTEN_MAPPING_2026-05-27.md @@ -150,3 +150,128 @@ Noch zu pruefen: ## Abgrenzung Dieser Task ist keine Finance-Soll/Ist-Regel. Die Klassifikation kann spaeter Finance- und Management-Auswertungen ergaenzen, sollte aber fachlich getrennt von Net-Sales-Abgrenzungen bleiben. + +## Nachtrag 2026-05-29 Umsetzung SAP Gateway Und Web + +SAP/DDIC: + +- Struktur `ZSTR_PRODSPARTE_OUT` wurde fuer den Gateway-Entity-Type verwendet. +- `PAPH1`, `WWPFA` und `WWPSP` duerfen in SE11 nicht vorausgesetzt als globale Datenelemente typisiert werden. +- Pragmatische Typisierung ist moeglich: + - `PAPH1`: `CHAR 5` + - `WWPFA`: Laenge wie `CE11000-WWPFA` + - `WWPSP`: Laenge wie `CE11000-WWPSP` + - Textfelder: z.B. `CHAR 50` +- Aktivierungsfehler bei `ZSTR_PRODSPARTE_OUT` waren durch nicht vorhandene/aktive Datenelemente fuer `PAPH1`, `WWPFA`, `WWPSP` verursacht. + +SAP/ABAP: + +- `ZCL_PRODSPARTE_PROVIDER` wurde als globale Klasse in SE24/quelltextbasiert angelegt. +- `Z_PRODSPARTE_REPORT` und `Z_PRODSPARTE_MAP_BUILD` wurden als Reports angelegt. +- `Z_PRODSPARTE_MAP_BUILD` hat `ZPRODSPARTE_MAP` befuellt. +- Beispielhafte Mapping-Luecken aus dem ALV-Test: + - `0509 Multistat` nicht zugeordnet + - `0540 Multistat` nicht zugeordnet + - `0519 Multistat` zugeordnet zu `0007` / `0001` +- Diese Luecken werden spaeter fachlich geprueft; technisch funktioniert der Provider. + +SAP Gateway: + +- Bestehender Service wird weiterverwendet: + - `ZPOWERBI_EINKAUF_SRV` + - Service Root: `http://travt762.sap.trafag.com:8000/sap/opu/odata/sap/ZPOWERBI_EINKAUF_SRV/` +- Es wurde ein zusaetzlicher Entity Type aus `ZSTR_PRODSPARTE_OUT` erstellt: + - Entity Type: `ProductDivisionRef` + - Entity Set: `ProductDivisionRefSet` +- Der bestehende Sales-EntitySet bleibt separat: + - `FinanzdataSchweizOeSet` bzw. generierte Methode `FINANZDATASCHWEI_GET_ENTITYSET` + - Diese Methode muss weiter aus `ZSCHWEIZ` lesen und darf nicht durch Produktsparten-Code ersetzt werden. +- Produktsparten-Code gehoert in die separat generierte/redefinierte Methode: + - `PRODUCTDIVISIONR_GET_ENTITYSET` +- Test-URL: + - `http://travt762.sap.trafag.com:8000/sap/opu/odata/sap/ZPOWERBI_EINKAUF_SRV/ProductDivisionRefSet` +- OData-Feldnamen aus dem Gateway sind CamelCase: + - `Matnr` + - `Maktx` + - `Paph1` + - `Paph1Text` + - `Wwpfa` + - `WwpfaText` + - `Wwpsp` + - `WwpspText` + - `IsAssigned` +- Erfolgreicher Testdatensatz aus Gateway: + - `Matnr = VCP1000` + - `Maktx = VC TRANSMITTER` + - `Paph1 = 9999` + - `Paph1Text = Zubehoer` + - `Wwpsp = UNASS` + - `WwpspText = Nicht zugeordnet` + - `IsAssigned = false` + +Wichtige Gateway-Korrektur: + +- Fehler `/IWFND/MED/170` trat auf, weil Service und EntitySet ohne Slash zusammengesetzt wurden. +- Falsch: + - `.../ZPOWERBI_EINKAUF_SRVProductDivisionRSet` +- Richtig: + - `.../ZPOWERBI_EINKAUF_SRV/ProductDivisionRefSet` + +Webprogramm / Datenmodell: + +- `SalesRecord` und `CentralSalesRecord` wurden um folgende Felder erweitert: + - `ProductHierarchyCode` + - `ProductHierarchyText` + - `ProductFamilyCode` + - `ProductFamilyText` + - `ProductDivisionCode` + - `ProductDivisionText` + - `ProductMappingAssigned` +- SQLite-Schema wurde erweitert: + - Neue Installationen erhalten die Felder in `CentralSalesRecords`. + - Bestehende Datenbanken erhalten die Felder per `DatabaseSchemaMaintenanceService`. +- `CentralSalesRecordService` schreibt und liest die neuen Felder. +- `ConfigTransferService` erhaelt die Produktfelder beim Preserve bestehender `CentralSalesRecords`. +- Excel-Export fuehrt die neuen Produktfelder im Blatt `Sales` direkt nach `Product Group`. +- Finance-Spalten im Export verschieben sich dadurch nach hinten; Tests wurden angepasst. +- Manual-Excel-Import und Auto-Match kennen die neuen Feldnamen ebenfalls. + +Aktive lokale Web-Konfiguration: + +- Standort: + - `Schweiz/Oesterreich` + - `TSC = ZSCHWEIZ` + - `SourceSystem = SAP` +- SAP Service URL: + - `http://travt762.sap.trafag.com:8000/sap/opu/odata/sap/ZPOWERBI_EINKAUF_SRV/` +- SAP-Quellen: + - Alias `Z`: bestehender Sales-EntitySet + - Alias `P`: `ProductDivisionRefSet` +- Join: + - `Z.Matnr = P.Matnr` +- Feldmappings: + - `ProductHierarchyCode <- P.Paph1` + - `ProductHierarchyText <- P.Paph1Text` + - `ProductFamilyCode <- P.Wwpfa` + - `ProductFamilyText <- P.WwpfaText` + - `ProductDivisionCode <- P.Wwpsp` + - `ProductDivisionText <- P.WwpspText` + - `ProductMappingAssigned <- P.IsAssigned` + +Lokaler Neustart / Validierung: + +- Lokaler Webprozess wurde neu gestartet. +- App antwortet lokal mit HTTP 200 auf `http://localhost:55416/`. +- Neue Spalten sind in `CentralSalesRecords` vorhanden. +- Validierung: + - `dotnet test TrafagSalesExporter.sln --verbosity minimal --artifacts-path C:\TMP\trafag-test-artifacts-productmapping` + - Ergebnis: `79/79` Tests gruen. + +Naechster fachlicher/technischer Schritt: + +- Standort `ZSCHWEIZ` im Export Dashboard einmal neu laufen lassen. +- Danach pruefen: + - Sind Produktfelder in `CentralSalesRecords` gefuellt? + - Stimmen Join-Treffer fuer bekannte Materialien? + - Wie viele Zeilen bleiben `UNASS` / `Nicht zugeordnet`? +- SAP-seitig muss `FINANZDATASCHWEI_GET_ENTITYSET` auf den alten `ZSCHWEIZ`-Select-Code zurueckgesetzt sein, falls er versehentlich mit Produktsparten-Code ueberschrieben wurde. diff --git a/TrafagSalesExporter/docs/abap/README_PRODSPARTE.md b/TrafagSalesExporter/docs/abap/README_PRODSPARTE.md index c9bdaf7..7bc3920 100644 --- a/TrafagSalesExporter/docs/abap/README_PRODSPARTE.md +++ b/TrafagSalesExporter/docs/abap/README_PRODSPARTE.md @@ -35,6 +35,33 @@ Optional fuer Gateway/DDIC: - Struktur `ZSTR_PRODSPARTE_OUT` - Tabellentyp `ZTT_PRODSPARTE_OUT` +## DDIC-Hinweis Zu `ZSTR_PRODSPARTE_OUT` + +Wenn `ZSTR_PRODSPARTE_OUT` in SE11 angelegt wird, duerfen `PAPH1`, +`WWPFA` und `WWPSP` nicht blind als Komponententypen eingetragen werden. +In manchen SAP-Systemen sind das keine aktiven globalen Datenelemente, +sondern nur Feldnamen in der CO-PA-Tabelle `CE11000`. Dann kann die +Nametab der Struktur nicht generiert werden. + +Empfohlene Anlage: + +| Komponente | Komponententyp / Alternative | +| --- | --- | +| `MATNR` | `MATNR` | +| `MAKTX` | `MAKTX` | +| `PAPH1` | eigenes Datenelement `ZDE_PAPH1` mit Laenge wie `CE11000-PAPH1`; aktuell fachlich erwartet: `CHAR 5` | +| `PAPH1_TEXT` | `VTEXT` oder eigenes Text-Datenelement | +| `WWPFA` | eigenes Datenelement `ZDE_WWPFA` mit exakt gleicher Laenge wie `CE11000-WWPFA` | +| `WWPFA_TEXT` | `BEZEK` oder eigenes Text-Datenelement | +| `WWPSP` | eigenes Datenelement `ZDE_WWPSP` mit exakt gleicher Laenge wie `CE11000-WWPSP` | +| `WWPSP_TEXT` | `BEZEK` oder eigenes Text-Datenelement | +| `IS_ASSIGNED` | `BOOLE_D` oder `XFELD` | + +Alternativ kann die Struktur fuer den ersten ALV-Test entfallen, weil die +Provider-Klasse intern mit `CE11000-PAPH1`, `CE11000-WWPFA` und +`CE11000-WWPSP` typisiert. Fuer Gateway/OData ist eine globale Struktur +aber sinnvoll. + ## Gepruefte Anpassungen Gegenueber Erstentwurf - Provider-Logik aus Report in globale Klasse ausgelagert. @@ -53,3 +80,43 @@ Optional fuer Gateway/DDIC: - Ist `CE11000` der richtige CO-PA-Einzelposten fuer den relevanten Ergebnisbereich? - Ist Fallback-Code `UNASS` in Feld `WWPSP` lang genug/zulässig? - Soll `VTWEG` zwingend selektiert werden statt "kleinster VTWEG gewinnt"? + +## Gateway-Stand 2026-05-29 + +Der bestehende Gateway-Service wurde erweitert, statt einen separaten +Service zu verwenden: + +- Service: `ZPOWERBI_EINKAUF_SRV` +- Service Root: `http://travt762.sap.trafag.com:8000/sap/opu/odata/sap/ZPOWERBI_EINKAUF_SRV/` +- Entity Type: `ProductDivisionRef` +- Entity Set: `ProductDivisionRefSet` +- DDIC-Struktur fuer Entity Type: `ZSTR_PRODSPARTE_OUT` + +Wichtig: + +- `FINANZDATASCHWEI_GET_ENTITYSET` gehoert zum bestehenden Sales-EntitySet + und muss den bisherigen `SELECT * FROM zschweiz ...` behalten. +- Produktspartenlogik gehoert in die separat generierte/redefinierte Methode + `PRODUCTDIVISIONR_GET_ENTITYSET`. +- Wenn `/IWFND/MED/170` mit einem Servicenamen wie + `ZPOWERBI_EINKAUF_SRVPRODUCTDIVISIONRSET` erscheint, fehlt in der URL der + Slash zwischen Service und EntitySet. + +Korrekte Test-URL: + +```text +http://travt762.sap.trafag.com:8000/sap/opu/odata/sap/ZPOWERBI_EINKAUF_SRV/ProductDivisionRefSet +``` + +Gateway-Feldnamen, wie sie im Web-Mapping verwendet werden: + +| Gateway-Feld | Web-Zielfeld | +| --- | --- | +| `Matnr` | Join gegen `Z.Matnr` | +| `Paph1` | `ProductHierarchyCode` | +| `Paph1Text` | `ProductHierarchyText` | +| `Wwpfa` | `ProductFamilyCode` | +| `WwpfaText` | `ProductFamilyText` | +| `Wwpsp` | `ProductDivisionCode` | +| `WwpspText` | `ProductDivisionText` | +| `IsAssigned` | `ProductMappingAssigned` | diff --git a/TrafagSalesExporter/docs/rag/PRODUCT_MAPPING.md b/TrafagSalesExporter/docs/rag/PRODUCT_MAPPING.md index 9c03055..33ef963 100644 --- a/TrafagSalesExporter/docs/rag/PRODUCT_MAPPING.md +++ b/TrafagSalesExporter/docs/rag/PRODUCT_MAPPING.md @@ -1,6 +1,6 @@ # RAG Product Mapping -Stand: 2026-05-27 +Stand: 2026-05-29 ## Kurzstand @@ -14,7 +14,16 @@ Stand: 2026-05-27 ## Aktueller Code-Stand - Vorhanden: `Material`, `ProductGroup`. -- Noch nicht vorhanden: explizite Felder fuer Produkthierarchie, Produktfamilie, Produktsparte. +- Neu vorhanden in `SalesRecord` und `CentralSalesRecord`: + - `ProductHierarchyCode` + - `ProductHierarchyText` + - `ProductFamilyCode` + - `ProductFamilyText` + - `ProductDivisionCode` + - `ProductDivisionText` + - `ProductMappingAssigned` +- `CentralSalesRecords` wird per Schema-Maintenance um diese Spalten erweitert. +- Excel-Export fuehrt die neuen Produktfelder im Blatt `Sales` direkt nach `Product Group`. - SAP-Seed-Mapping nutzt aktuell `Z.Matnr` -> `Material` und `Z.Prodh` -> `ProductGroup`. - Zu klaeren: Ist `Z.Prodh` fachlich die Produkthierarchie? @@ -31,11 +40,28 @@ Stand: 2026-05-27 - Eindeutige `PAPH1 -> WWPFA -> WWPSP` werden in `ZPRODSPARTE_MAP` gespeichert. - Mehrdeutige PAPH1 werden protokolliert und nicht geschrieben. - `ZCL_PRODSPARTE_PROVIDER` liest `MVKE-PRODH`, Texte und Mapping. -- OData-Service ruft spaeter dieselbe Provider-Klasse. +- OData-Service ruft dieselbe Provider-Klasse. + +## Stand 2026-05-29 + +- SAP Gateway nutzt bestehenden Service `ZPOWERBI_EINKAUF_SRV`. +- Service Root: `http://travt762.sap.trafag.com:8000/sap/opu/odata/sap/ZPOWERBI_EINKAUF_SRV/`. +- Produktmapping-EntitySet: `ProductDivisionRefSet`. +- Test-Endpoint liefert Daten, z.B. `Matnr=VCP1000`, `Paph1=9999`, `Wwpsp=UNASS`. +- OData-Felder sind CamelCase: `Matnr`, `Paph1`, `Paph1Text`, `Wwpfa`, `WwpfaText`, `Wwpsp`, `WwpspText`, `IsAssigned`. +- Bestehender Sales-EntitySet bleibt `FinanzdataSchweizOeSet`. +- Wichtig: `FINANZDATASCHWEI_GET_ENTITYSET` muss weiter den alten `ZSCHWEIZ`-Select enthalten. +- Produktmapping-Code gehoert in `PRODUCTDIVISIONR_GET_ENTITYSET`. +- Lokale App-Konfiguration fuer Standort `ZSCHWEIZ`: + - Quelle `Z`: bestehender Sales-EntitySet. + - Quelle `P`: `ProductDivisionRefSet`. + - Join: `Z.Matnr = P.Matnr`. + - Mappings: `P.Paph1`, `P.Paph1Text`, `P.Wwpfa`, `P.WwpfaText`, `P.Wwpsp`, `P.WwpspText`, `P.IsAssigned`. +- Lokale App wurde neu gestartet; `http://localhost:55416/` antwortet mit HTTP 200. +- Validierung: `79/79` Tests gruen mit separatem Artefaktpfad. ## Offene Punkte Fuer Sitzung -- Quelle und Format des TR-AG-Artikelstamms. - Normalisierung der Materialnummern. - Struktur der Mapping-Tabelle von Kendra. - Matching-Regeln: exakt, Prefix, Range, Prioritaet. @@ -44,6 +70,7 @@ Stand: 2026-05-27 - `PAPH1 = MVKE-PRODH(5)` fachlich/technisch bestaetigen. - Richtige Texttabellen fuer `WWPFA`/`WWPSP` bestaetigen. - VKORG/VTWEG fuer TR-AG-Referenzlauf bestaetigen. +- Standort `ZSCHWEIZ` im Export Dashboard neu laufen lassen und Fuellung der neuen Produktfelder pruefen. ## Rohquelle Nur Bei Bedarf diff --git a/TrafagSalesExporter/lastchange.md b/TrafagSalesExporter/lastchange.md index be25445..7754ebd 100644 --- a/TrafagSalesExporter/lastchange.md +++ b/TrafagSalesExporter/lastchange.md @@ -14,6 +14,52 @@ Diese Datei ist fuer tokenarme RAG-Nutzung komprimiert. - Neu dokumentiert: Upgreat-Firewall-Freigabe muss fuer den publizierten Webserver `10.120.1.17` erfolgen, nicht fuer den lokalen Entwicklungs-PC. - Neu umgesetzt: `Management Analyse` im Finance Cockpit hat zusaetzliche Reiter fuer Laender, Datenstatus, Abweichungen, Gutschriften-Kandidaten und Datenqualitaet. - Neu erstellt: ABAP-Arbeitsstand fuer Produktsparten-Mapping mit Provider-Klasse, ALV-Report und Mapping-Build-Report. +- Neu umgesetzt: Produktspartenfelder im Web-Datenmodell, Gateway-Join-Konfiguration fuer `ProductDivisionRefSet` und Excel-Ausgabe. + +## Nachtrag 2026-05-29 Produktsparten-Mapping Gateway/Web + +SAP/Gateway: + +- Bestehender Service wird verwendet: `ZPOWERBI_EINKAUF_SRV`. +- Service Root: `http://travt762.sap.trafag.com:8000/sap/opu/odata/sap/ZPOWERBI_EINKAUF_SRV/`. +- Neuer Entity Type/Entity Set: + - `ProductDivisionRef` + - `ProductDivisionRefSet` +- Entity Type basiert auf `ZSTR_PRODSPARTE_OUT`. +- Gateway-Test liefert Daten, Beispiel: + - `Matnr = VCP1000` + - `Paph1 = 9999` + - `Wwpsp = UNASS` + - `WwpspText = Nicht zugeordnet` +- Wichtig: `FINANZDATASCHWEI_GET_ENTITYSET` ist der bestehende Sales-EntitySet und muss den alten `ZSCHWEIZ`-Select behalten. Produktspartenlogik gehoert in `PRODUCTDIVISIONR_GET_ENTITYSET`. +- Fehler `/IWFND/MED/170` wurde als fehlender Slash zwischen Service und EntitySet identifiziert. + +Web/App: + +- Neue Felder in `SalesRecord` und `CentralSalesRecord`: + - `ProductHierarchyCode` + - `ProductHierarchyText` + - `ProductFamilyCode` + - `ProductFamilyText` + - `ProductDivisionCode` + - `ProductDivisionText` + - `ProductMappingAssigned` +- `CentralSalesRecords` erhaelt die Spalten per Schema-Maintenance. +- `CentralSalesRecordService` liest/schreibt die Felder. +- Excel-Export fuehrt die Produktfelder im Blatt `Sales` direkt nach `Product Group`. +- Manual-Excel-Header-Mapping kennt die neuen Feldnamen. +- Lokale DB-Konfiguration fuer Standort `ZSCHWEIZ`: + - Quelle `P`: `ProductDivisionRefSet` + - Join: `Z.Matnr = P.Matnr` + - Mappings: `P.Paph1`, `P.Paph1Text`, `P.Wwpfa`, `P.WwpfaText`, `P.Wwpsp`, `P.WwpspText`, `P.IsAssigned` +- Lokaler Neustart durchgefuehrt; `http://localhost:55416/` antwortet mit HTTP 200. +- Validierung: `dotnet test TrafagSalesExporter.sln --verbosity minimal --artifacts-path C:\TMP\trafag-test-artifacts-productmapping` mit `79/79` Tests gruen. + +Offen: + +- `ZSCHWEIZ` im Export Dashboard neu laufen lassen. +- Danach Fuellung der neuen Produktfelder und Quote `UNASS` pruefen. +- Fachliche Mapping-Luecken wie `0509`/`0540` spaeter mit Andreas/Kendra klaeren. ## Nachtrag 2026-05-28 ABAP Produktsparten-Mapping