Add product division gateway mapping

This commit is contained in:
2026-05-29 08:49:37 +02:00
parent d3d75ccea7
commit 7e9a61f877
14 changed files with 402 additions and 44 deletions
@@ -1100,6 +1100,13 @@
[nameof(SalesRecord.Material)] = ["ArtikelNummer", "Material", "Groesse"], [nameof(SalesRecord.Material)] = ["ArtikelNummer", "Material", "Groesse"],
[nameof(SalesRecord.Name)] = ["ArtikelBezeichnung", "Name"], [nameof(SalesRecord.Name)] = ["ArtikelBezeichnung", "Name"],
[nameof(SalesRecord.ProductGroup)] = ["Warengruppen-Bezeichnung", "Product Group"], [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.Quantity)] = ["Anz. VE", "Quantity"],
[nameof(SalesRecord.SupplierNumber)] = ["Lieferanten Nummer", "Supplier number"], [nameof(SalesRecord.SupplierNumber)] = ["Lieferanten Nummer", "Supplier number"],
[nameof(SalesRecord.SupplierName)] = ["Name Lieferant", "Supplier name"], [nameof(SalesRecord.SupplierName)] = ["Name Lieferant", "Supplier name"],
@@ -20,6 +20,13 @@ public class CentralSalesRecord
public string Material { get; set; } = string.Empty; public string Material { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public string ProductGroup { 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 decimal Quantity { get; set; }
public string SupplierNumber { get; set; } = string.Empty; public string SupplierNumber { get; set; } = string.Empty;
public string SupplierName { get; set; } = string.Empty; public string SupplierName { get; set; } = string.Empty;
@@ -11,6 +11,13 @@ public class SalesRecord
public string Material { get; set; } = string.Empty; public string Material { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public string ProductGroup { 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 decimal Quantity { get; set; }
public string SupplierNumber { get; set; } = string.Empty; public string SupplierNumber { get; set; } = string.Empty;
public string SupplierName { get; set; } = string.Empty; public string SupplierName { get; set; } = string.Empty;
@@ -70,6 +70,13 @@ public class CentralSalesRecordService : ICentralSalesRecordService
Material = r.Material, Material = r.Material,
Name = r.Name, Name = r.Name,
ProductGroup = r.ProductGroup, 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, Quantity = r.Quantity,
SupplierNumber = r.SupplierNumber, SupplierNumber = r.SupplierNumber,
SupplierName = r.SupplierName, SupplierName = r.SupplierName,
@@ -164,7 +171,8 @@ public class CentralSalesRecordService : ICentralSalesRecordService
command.CommandText = """ command.CommandText = """
INSERT INTO CentralSalesRecords ( INSERT INTO CentralSalesRecords (
StoredAtUtc, SiteId, SourceSystem, ExtractionDate, Tsc, DocumentEntry, InvoiceNumber, PositionOnInvoice, 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, CustomerNumber, CustomerName, CustomerCountry, CustomerIndustry, StandardCost,
StandardCostCurrency, PurchaseOrderNumber, SalesPriceValue, SalesCurrency, Incoterms2020, StandardCostCurrency, PurchaseOrderNumber, SalesPriceValue, SalesCurrency, Incoterms2020,
DocumentCurrency, DocumentTotalForeignCurrency, DocumentTotalLocalCurrency, VatSumForeignCurrency, DocumentCurrency, DocumentTotalForeignCurrency, DocumentTotalLocalCurrency, VatSumForeignCurrency,
@@ -172,7 +180,8 @@ public class CentralSalesRecordService : ICentralSalesRecordService
) )
VALUES ( VALUES (
$storedAtUtc, $siteId, $sourceSystem, $extractionDate, $tsc, $documentEntry, $invoiceNumber, $positionOnInvoice, $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, $customerNumber, $customerName, $customerCountry, $customerIndustry, $standardCost,
$standardCostCurrency, $purchaseOrderNumber, $salesPriceValue, $salesCurrency, $incoterms2020, $standardCostCurrency, $purchaseOrderNumber, $salesPriceValue, $salesCurrency, $incoterms2020,
$documentCurrency, $documentTotalForeignCurrency, $documentTotalLocalCurrency, $vatSumForeignCurrency, $documentCurrency, $documentTotalForeignCurrency, $documentTotalLocalCurrency, $vatSumForeignCurrency,
@@ -191,6 +200,13 @@ public class CentralSalesRecordService : ICentralSalesRecordService
command.Parameters.Add("$material", SqliteType.Text); command.Parameters.Add("$material", SqliteType.Text);
command.Parameters.Add("$name", SqliteType.Text); command.Parameters.Add("$name", SqliteType.Text);
command.Parameters.Add("$productGroup", 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("$quantity", SqliteType.Real);
command.Parameters.Add("$supplierNumber", SqliteType.Text); command.Parameters.Add("$supplierNumber", SqliteType.Text);
command.Parameters.Add("$supplierName", 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["$material"].Value = record.Material ?? string.Empty;
command.Parameters["$name"].Value = record.Name ?? string.Empty; command.Parameters["$name"].Value = record.Name ?? string.Empty;
command.Parameters["$productGroup"].Value = record.ProductGroup ?? 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["$quantity"].Value = record.Quantity;
command.Parameters["$supplierNumber"].Value = record.SupplierNumber ?? string.Empty; command.Parameters["$supplierNumber"].Value = record.SupplierNumber ?? string.Empty;
command.Parameters["$supplierName"].Value = record.SupplierName ?? string.Empty; command.Parameters["$supplierName"].Value = record.SupplierName ?? string.Empty;
@@ -428,6 +428,13 @@ public class ConfigTransferService : IConfigTransferService
Material = record.Material, Material = record.Material,
Name = record.Name, Name = record.Name,
ProductGroup = record.ProductGroup, 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, Quantity = record.Quantity,
SupplierNumber = record.SupplierNumber, SupplierNumber = record.SupplierNumber,
SupplierName = record.SupplierName, SupplierName = record.SupplierName,
@@ -91,6 +91,13 @@ CREATE TABLE CentralSalesRecords (
Material TEXT NOT NULL, Material TEXT NOT NULL,
Name TEXT NOT NULL, Name TEXT NOT NULL,
ProductGroup 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, Quantity TEXT NOT NULL,
SupplierNumber TEXT NOT NULL, SupplierNumber TEXT NOT NULL,
SupplierName TEXT NOT NULL, SupplierName TEXT NOT NULL,
@@ -52,6 +52,13 @@ public class DatabaseSchemaMaintenanceService : IDatabaseSchemaMaintenanceServic
AddColumnIfMissing(db, "CentralSalesRecords", "VatSumLocalCurrency", "TEXT NOT NULL DEFAULT '0'"); AddColumnIfMissing(db, "CentralSalesRecords", "VatSumLocalCurrency", "TEXT NOT NULL DEFAULT '0'");
AddColumnIfMissing(db, "CentralSalesRecords", "DocumentRate", "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", "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"); AddColumnIfMissing(db, "CentralSalesRecords", "PostingDate", "TEXT NULL");
EnsureAppEventLogTable(db); EnsureAppEventLogTable(db);
} }
@@ -84,6 +84,13 @@ public class ExcelExportService : IExcelExportService
"Material", "Material",
"Name", "Name",
"Product Group", "Product Group",
"Product Hierarchy Code",
"Product Hierarchy Text",
"Product Family Code",
"Product Family Text",
"Product Division Code",
"Product Division Text",
"Product Mapping Assigned",
"Quantity", "Quantity",
"Supplier number", "Supplier number",
"Supplier name", "Supplier name",
@@ -137,44 +144,51 @@ public class ExcelExportService : IExcelExportService
ws.Cell(row, 6).Value = record.Material; ws.Cell(row, 6).Value = record.Material;
ws.Cell(row, 7).Value = record.Name; ws.Cell(row, 7).Value = record.Name;
ws.Cell(row, 8).Value = record.ProductGroup; ws.Cell(row, 8).Value = record.ProductGroup;
ws.Cell(row, 9).Value = record.Quantity; ws.Cell(row, 9).Value = record.ProductHierarchyCode;
ws.Cell(row, 10).Value = record.SupplierNumber; ws.Cell(row, 10).Value = record.ProductHierarchyText;
ws.Cell(row, 11).Value = record.SupplierName; ws.Cell(row, 11).Value = record.ProductFamilyCode;
ws.Cell(row, 12).Value = record.SupplierCountry; ws.Cell(row, 12).Value = record.ProductFamilyText;
ws.Cell(row, 13).Value = record.CustomerNumber; ws.Cell(row, 13).Value = record.ProductDivisionCode;
ws.Cell(row, 14).Value = record.CustomerName; ws.Cell(row, 14).Value = record.ProductDivisionText;
ws.Cell(row, 15).Value = record.CustomerCountry; ws.Cell(row, 15).Value = record.ProductMappingAssigned;
ws.Cell(row, 16).Value = record.CustomerIndustry; ws.Cell(row, 16).Value = record.Quantity;
ws.Cell(row, 17).Value = record.StandardCost; ws.Cell(row, 17).Value = record.SupplierNumber;
ws.Cell(row, 18).Value = record.StandardCostCurrency; ws.Cell(row, 18).Value = record.SupplierName;
ws.Cell(row, 19).Value = record.PurchaseOrderNumber; ws.Cell(row, 19).Value = record.SupplierCountry;
ws.Cell(row, 20).Value = record.SalesPriceValue; ws.Cell(row, 20).Value = record.CustomerNumber;
ws.Cell(row, 21).Value = record.SalesCurrency; ws.Cell(row, 21).Value = record.CustomerName;
ws.Cell(row, 22).Value = record.DocumentCurrency; ws.Cell(row, 22).Value = record.CustomerCountry;
ws.Cell(row, 23).Value = record.DocumentTotalForeignCurrency; ws.Cell(row, 23).Value = record.CustomerIndustry;
ws.Cell(row, 24).Value = record.DocumentTotalLocalCurrency; ws.Cell(row, 24).Value = record.StandardCost;
ws.Cell(row, 25).Value = record.VatSumForeignCurrency; ws.Cell(row, 25).Value = record.StandardCostCurrency;
ws.Cell(row, 26).Value = record.VatSumLocalCurrency; ws.Cell(row, 26).Value = record.PurchaseOrderNumber;
ws.Cell(row, 27).Value = record.DocumentRate; ws.Cell(row, 27).Value = record.SalesPriceValue;
ws.Cell(row, 28).Value = record.CompanyCurrency; ws.Cell(row, 28).Value = record.SalesCurrency;
ws.Cell(row, 29).Value = record.Incoterms2020; ws.Cell(row, 29).Value = record.DocumentCurrency;
ws.Cell(row, 30).Value = record.SalesResponsibleEmployee; ws.Cell(row, 30).Value = record.DocumentTotalForeignCurrency;
ws.Cell(row, 31).Value = record.PostingDate?.ToString("dd.MM.yyyy") ?? string.Empty; ws.Cell(row, 31).Value = record.DocumentTotalLocalCurrency;
ws.Cell(row, 32).Value = record.InvoiceDate?.ToString("dd.MM.yyyy") ?? string.Empty; ws.Cell(row, 32).Value = record.VatSumForeignCurrency;
ws.Cell(row, 33).Value = record.OrderDate?.ToString("dd.MM.yyyy") ?? string.Empty; ws.Cell(row, 33).Value = record.VatSumLocalCurrency;
ws.Cell(row, 34).Value = record.Land; ws.Cell(row, 34).Value = record.DocumentRate;
ws.Cell(row, 35).Value = record.DocumentType; 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 financeCountryKey = ResolveFinanceCountryKey(record.Land, record.Tsc);
var financeDate = financeRuleEngine.ResolveFinanceDate(record, financeCountryKey); var financeDate = financeRuleEngine.ResolveFinanceDate(record, financeCountryKey);
var financeInclude = financeRuleEngine.ShouldInclude(record, financeCountryKey); var financeInclude = financeRuleEngine.ShouldInclude(record, financeCountryKey);
var financeNetSalesActual = financeRuleEngine.ResolveNetSalesActual(record, financeCountryKey, financeInclude); var financeNetSalesActual = financeRuleEngine.ResolveNetSalesActual(record, financeCountryKey, financeInclude);
ws.Cell(row, 36).Value = financeDate.Year; ws.Cell(row, 43).Value = financeDate.Year;
ws.Cell(row, 37).Value = financeCountryKey; ws.Cell(row, 44).Value = financeCountryKey;
ws.Cell(row, 38).Value = financeDate.ToString("dd.MM.yyyy"); ws.Cell(row, 45).Value = financeDate.ToString("dd.MM.yyyy");
ws.Cell(row, 39).Value = financeNetSalesActual; ws.Cell(row, 46).Value = financeNetSalesActual;
ws.Cell(row, 40).Value = ResolveFinanceCurrency(record); ws.Cell(row, 47).Value = ResolveFinanceCurrency(record);
ws.Cell(row, 41).Value = financeInclude && financeNetSalesActual != 0m ? "TRUE" : "FALSE"; ws.Cell(row, 48).Value = financeInclude && financeNetSalesActual != 0m ? "TRUE" : "FALSE";
ws.Cell(row, 42).Value = financeInclude ws.Cell(row, 49).Value = financeInclude
? "Sales Price/Value" ? "Sales Price/Value"
: financeRuleEngine.ResolveExclusionReason(record, financeCountryKey); : financeRuleEngine.ResolveExclusionReason(record, financeCountryKey);
row++; row++;
@@ -24,6 +24,13 @@ public class ManualExcelImportService : IManualExcelImportService
["material"] = nameof(SalesRecord.Material), ["material"] = nameof(SalesRecord.Material),
["name"] = nameof(SalesRecord.Name), ["name"] = nameof(SalesRecord.Name),
["productgroup"] = nameof(SalesRecord.ProductGroup), ["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), ["quantity"] = nameof(SalesRecord.Quantity),
["suppliernumber"] = nameof(SalesRecord.SupplierNumber), ["suppliernumber"] = nameof(SalesRecord.SupplierNumber),
["suppliername"] = nameof(SalesRecord.SupplierName), ["suppliername"] = nameof(SalesRecord.SupplierName),
@@ -162,6 +169,13 @@ public class ManualExcelImportService : IManualExcelImportService
Material = ReadString(headerIndexes, fields, nameof(SalesRecord.Material)), Material = ReadString(headerIndexes, fields, nameof(SalesRecord.Material)),
Name = ReadString(headerIndexes, fields, nameof(SalesRecord.Name)), Name = ReadString(headerIndexes, fields, nameof(SalesRecord.Name)),
ProductGroup = ReadString(headerIndexes, fields, nameof(SalesRecord.ProductGroup)), 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)), Quantity = ReadDecimal(headerIndexes, fields, nameof(SalesRecord.Quantity)),
SupplierNumber = ReadString(headerIndexes, fields, nameof(SalesRecord.SupplierNumber)), SupplierNumber = ReadString(headerIndexes, fields, nameof(SalesRecord.SupplierNumber)),
SupplierName = ReadString(headerIndexes, fields, nameof(SalesRecord.SupplierName)), SupplierName = ReadString(headerIndexes, fields, nameof(SalesRecord.SupplierName)),
@@ -37,13 +37,13 @@ public class ExcelExportServiceTests
var sales = workbook.Worksheet("Sales"); var sales = workbook.Worksheet("Sales");
var includedGermanyRows = sales.RowsUsed() var includedGermanyRows = sales.RowsUsed()
.Skip(1) .Skip(1)
.Where(row => row.Cell(36).GetValue<int>() == 2025) .Where(row => row.Cell(43).GetValue<int>() == 2025)
.Where(row => row.Cell(37).GetString() == "DE") .Where(row => row.Cell(44).GetString() == "DE")
.Where(row => row.Cell(41).GetString() == "TRUE") .Where(row => row.Cell(48).GetString() == "TRUE")
.ToList(); .ToList();
Assert.Equal(2, includedGermanyRows.Count); Assert.Equal(2, includedGermanyRows.Count);
Assert.Equal(80m, includedGermanyRows.Sum(row => row.Cell(39).GetValue<decimal>())); Assert.Equal(80m, includedGermanyRows.Sum(row => row.Cell(46).GetValue<decimal>()));
var details = workbook.Worksheet("Finance Details"); var details = workbook.Worksheet("Finance Details");
var includedGermanyDetailRows = details.RowsUsed() var includedGermanyDetailRows = details.RowsUsed()
@@ -150,3 +150,128 @@ Noch zu pruefen:
## Abgrenzung ## 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. 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.
@@ -35,6 +35,33 @@ Optional fuer Gateway/DDIC:
- Struktur `ZSTR_PRODSPARTE_OUT` - Struktur `ZSTR_PRODSPARTE_OUT`
- Tabellentyp `ZTT_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 ## Gepruefte Anpassungen Gegenueber Erstentwurf
- Provider-Logik aus Report in globale Klasse ausgelagert. - 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 `CE11000` der richtige CO-PA-Einzelposten fuer den relevanten Ergebnisbereich?
- Ist Fallback-Code `UNASS` in Feld `WWPSP` lang genug/zulässig? - Ist Fallback-Code `UNASS` in Feld `WWPSP` lang genug/zulässig?
- Soll `VTWEG` zwingend selektiert werden statt "kleinster VTWEG gewinnt"? - 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` |
@@ -1,6 +1,6 @@
# RAG Product Mapping # RAG Product Mapping
Stand: 2026-05-27 Stand: 2026-05-29
## Kurzstand ## Kurzstand
@@ -14,7 +14,16 @@ Stand: 2026-05-27
## Aktueller Code-Stand ## Aktueller Code-Stand
- Vorhanden: `Material`, `ProductGroup`. - 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`. - SAP-Seed-Mapping nutzt aktuell `Z.Matnr` -> `Material` und `Z.Prodh` -> `ProductGroup`.
- Zu klaeren: Ist `Z.Prodh` fachlich die Produkthierarchie? - 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. - Eindeutige `PAPH1 -> WWPFA -> WWPSP` werden in `ZPRODSPARTE_MAP` gespeichert.
- Mehrdeutige PAPH1 werden protokolliert und nicht geschrieben. - Mehrdeutige PAPH1 werden protokolliert und nicht geschrieben.
- `ZCL_PRODSPARTE_PROVIDER` liest `MVKE-PRODH`, Texte und Mapping. - `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 ## Offene Punkte Fuer Sitzung
- Quelle und Format des TR-AG-Artikelstamms.
- Normalisierung der Materialnummern. - Normalisierung der Materialnummern.
- Struktur der Mapping-Tabelle von Kendra. - Struktur der Mapping-Tabelle von Kendra.
- Matching-Regeln: exakt, Prefix, Range, Prioritaet. - Matching-Regeln: exakt, Prefix, Range, Prioritaet.
@@ -44,6 +70,7 @@ Stand: 2026-05-27
- `PAPH1 = MVKE-PRODH(5)` fachlich/technisch bestaetigen. - `PAPH1 = MVKE-PRODH(5)` fachlich/technisch bestaetigen.
- Richtige Texttabellen fuer `WWPFA`/`WWPSP` bestaetigen. - Richtige Texttabellen fuer `WWPFA`/`WWPSP` bestaetigen.
- VKORG/VTWEG fuer TR-AG-Referenzlauf 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 ## Rohquelle Nur Bei Bedarf
+46
View File
@@ -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 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 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 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 ## Nachtrag 2026-05-28 ABAP Produktsparten-Mapping