From 5e305ae39681f1bd873b4276758240eaf996e798 Mon Sep 17 00:00:00 2001 From: metacube Date: Wed, 20 May 2026 12:21:06 +0200 Subject: [PATCH] Add DE finance rules and IIS path base --- TrafagSalesExporter/Components/App.razor | 20 +++++- TrafagSalesExporter/Program.cs | 5 ++ .../Services/DatabaseSeedService.cs | 8 ++- .../Services/ExcelExportService.cs | 72 +++++++++++++++++-- .../ExcelExportServiceTests.cs | 70 ++++++++++++++++++ TrafagSalesExporter/web.config | 1 + 6 files changed, 167 insertions(+), 9 deletions(-) create mode 100644 TrafagSalesExporter/TrafagSalesExporter.Tests/ExcelExportServiceTests.cs diff --git a/TrafagSalesExporter/Components/App.razor b/TrafagSalesExporter/Components/App.razor index cd44eb8..df9469f 100644 --- a/TrafagSalesExporter/Components/App.razor +++ b/TrafagSalesExporter/Components/App.razor @@ -4,7 +4,7 @@ Trafag Finanze/Sales Management Cockpit - + @@ -17,3 +17,21 @@ + +@code { + [Inject] + private IConfiguration Configuration { get; set; } = default!; + + private string BaseHref + { + get + { + var pathBase = Configuration["ASPNETCORE_PATHBASE"]?.Trim(); + if (string.IsNullOrWhiteSpace(pathBase) || pathBase == "/") + return "/"; + + pathBase = "/" + pathBase.Trim('/'); + return $"{pathBase}/"; + } + } +} diff --git a/TrafagSalesExporter/Program.cs b/TrafagSalesExporter/Program.cs index 81724c8..c0b6492 100644 --- a/TrafagSalesExporter/Program.cs +++ b/TrafagSalesExporter/Program.cs @@ -110,6 +110,11 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); var app = builder.Build(); +var pathBase = app.Configuration["ASPNETCORE_PATHBASE"]; +if (!string.IsNullOrWhiteSpace(pathBase)) +{ + app.UsePathBase(pathBase.Trim()); +} using (var scope = app.Services.CreateScope()) { diff --git a/TrafagSalesExporter/Services/DatabaseSeedService.cs b/TrafagSalesExporter/Services/DatabaseSeedService.cs index 0fccab9..8a6b621 100644 --- a/TrafagSalesExporter/Services/DatabaseSeedService.cs +++ b/TrafagSalesExporter/Services/DatabaseSeedService.cs @@ -760,7 +760,7 @@ public class DatabaseSeedService : IDatabaseSeedService new FinanceReference { Key = "CH", Label = "Trafag CH", Year = 2025 }, new FinanceReference { Key = "CN", Label = "Trafag CN", Year = 2025 }, new FinanceReference { Key = "CZ", Label = "Trafag CZ", Year = 2025, LocalCurrencyValue = 95458782m }, - new FinanceReference { Key = "DE", Label = "Trafag DE", Year = 2025, LocalCurrencyValue = 3635923m }, + new FinanceReference { Key = "DE", Label = "Trafag DE", Year = 2025, LocalCurrencyValue = 3652394.46m }, new FinanceReference { Key = "ES", Label = "Trafag ES", Year = 2025, LocalCurrencyValue = 3102333.61m }, new FinanceReference { Key = "FR", Label = "Trafag FR", Year = 2025, LocalCurrencyValue = 1450582m, CheckValue = 1471218m }, new FinanceReference { Key = "GFS", Label = "Trafag GfS", Year = 2025, LocalCurrencyValue = 6495513m }, @@ -803,6 +803,12 @@ public class DatabaseSeedService : IDatabaseSeedService changed = true; } + if (current.Key == "DE" && current.Year == 2025 && current.LocalCurrencyValue != 3652394.46m) + { + current.LocalCurrencyValue = 3652394.46m; + changed = true; + } + continue; } diff --git a/TrafagSalesExporter/Services/ExcelExportService.cs b/TrafagSalesExporter/Services/ExcelExportService.cs index f18bc2c..3187ae2 100644 --- a/TrafagSalesExporter/Services/ExcelExportService.cs +++ b/TrafagSalesExporter/Services/ExcelExportService.cs @@ -5,6 +5,8 @@ namespace TrafagSalesExporter.Services; public class ExcelExportService : IExcelExportService { + private const int GermanyAlphaplanFinanceYear = 2025; + public string CreateExcelFile(string outputDirectory, string tsc, DateTime fileDate, List records) { Directory.CreateDirectory(outputDirectory); @@ -132,12 +134,13 @@ public class ExcelExportService : IExcelExportService var financeDate = ResolveFinanceDate(record); var financeCountryKey = ResolveFinanceCountryKey(record.Land, record.Tsc); var financeInclude = ResolveFinanceInclude(record, financeCountryKey, italyBlankSupplierCountryRows); + var financeNetSalesActual = ResolveFinanceNetSalesActual(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 = financeInclude ? record.SalesPriceValue : 0m; + ws.Cell(row, 39).Value = financeNetSalesActual; ws.Cell(row, 40).Value = ResolveFinanceCurrency(record); - ws.Cell(row, 41).Value = financeInclude && record.SalesPriceValue != 0m ? "TRUE" : "FALSE"; + ws.Cell(row, 41).Value = financeInclude && financeNetSalesActual != 0m ? "TRUE" : "FALSE"; ws.Cell(row, 42).Value = financeInclude ? "Sales Price/Value" : ResolveFinanceExclusionReason(record, financeCountryKey); @@ -186,14 +189,16 @@ public class ExcelExportService : IExcelExportService { var financeDate = ResolveFinanceDate(record); var countryKey = ResolveFinanceCountryKey(record.Land, record.Tsc); - var include = ResolveFinanceInclude(record, countryKey, italyBlankSupplierCountryRows) && record.SalesPriceValue != 0m; + var rawInclude = ResolveFinanceInclude(record, countryKey, italyBlankSupplierCountryRows); + var value = ResolveFinanceNetSalesActual(record, countryKey, rawInclude); + var include = rawInclude && value != 0m; return new { Year = financeDate.Year, CountryKey = countryKey, Currency = ResolveFinanceCurrency(record), Include = include, - Value = include ? record.SalesPriceValue : 0m + Value = value }; }) .GroupBy(row => new { row.Year, row.CountryKey, row.Currency }) @@ -231,7 +236,7 @@ public class ExcelExportService : IExcelExportService private static string BuildFinanceSummaryHint(string countryKey) => countryKey.ToUpperInvariant() switch { - "DE" => "DE Alphaplan ist technisch vorbereitet; Kundenlaender/Filter fachlich noch bestaetigen.", + "DE" => "DE Alphaplan Jahresfile 2025: Weiterberechnungen ausgeschlossen; GS negativ, GS2510095 2024.", "IT" => "IT: Trafag Italia ausgeschlossen; doppelte Blank-Supplier-Zeilen nur einmal.", "UK" => "UK: Sage/Manual Excel, Credit Notes negativ.", "ES" => "ES: Sage CSV/Manual Excel, REC/Credit Notes negativ.", @@ -253,8 +258,9 @@ public class ExcelExportService : IExcelExportService ("3. Gueltige Zeilen filtern", "Finance | Include = TRUE"), ("4. Summe bilden", "Finance | Net Sales Actual summieren"), ("Waehrung", "Finance | Currency zeigt die fuer den Finance-Abgleich fuehrende Hauswaehrung."), - ("Datum", "Finance | Date verwendet PostingDate, danach InvoiceDate, danach ExtractionDate."), + ("Datum", "Finance | Date verwendet PostingDate, danach InvoiceDate, danach ExtractionDate. DE Alphaplan wird als Jahresfile 2025 behandelt."), ("Wertquelle", "Finance | Source Value Field zeigt, aus welchem Rohfeld der Finance-Wert kommt."), + ("DE-Sonderregel", "Fuer DE gilt die Deutschland-Rueckmeldung: Trafag AG und Magnetic Sense ausgeschlossen, GS-Gutschriften negativ, GS2510095 nicht in 2025."), ("IT-Sonderregel", "Fuer IT wird Trafag Italia im Finance-Wert ausgeschlossen; doppelte IT-Zeilen ohne Supplier country werden nur einmal gezaehlt."), ("Nicht verwenden", "Nicht Land, TSC, Document Total LC oder andere Betragsspalten fuer den CFO-Abgleich erraten."), ("Hinweis", "Offene fachliche Differenzen bleiben sichtbar; diese Excel-Sicht soll die gleiche Ist-Summe wie das Testprogramm reproduzieren.") @@ -293,7 +299,13 @@ public class ExcelExportService : IExcelExportService } private static DateTime ResolveFinanceDate(SalesRecord record) - => record.PostingDate ?? record.InvoiceDate ?? record.ExtractionDate; + { + var countryKey = ResolveFinanceCountryKey(record.Land, record.Tsc); + if (countryKey.Equals("DE", StringComparison.OrdinalIgnoreCase)) + return new DateTime(GermanyAlphaplanFinanceYear, 12, 31); + + return record.PostingDate ?? record.InvoiceDate ?? record.ExtractionDate; + } private static string ResolveFinanceCurrency(SalesRecord record) => ResolveFinanceCountryKey(record.Land, record.Tsc) switch @@ -330,6 +342,9 @@ public class ExcelExportService : IExcelExportService private static bool ResolveFinanceInclude(SalesRecord record, string financeCountryKey, HashSet italyBlankSupplierCountryRows) { + if (financeCountryKey.Equals("DE", StringComparison.OrdinalIgnoreCase)) + return IsIncludedGermanyFinanceRow(record); + if (!financeCountryKey.Equals("IT", StringComparison.OrdinalIgnoreCase)) return true; @@ -350,9 +365,52 @@ public class ExcelExportService : IExcelExportService if (financeCountryKey.Equals("IT", StringComparison.OrdinalIgnoreCase) && string.IsNullOrWhiteSpace(record.SupplierCountry)) return "Excluded IT duplicate without Supplier country"; + if (financeCountryKey.Equals("DE", StringComparison.OrdinalIgnoreCase)) + return ResolveGermanyExclusionReason(record); + return "Excluded"; } + private static decimal ResolveFinanceNetSalesActual(SalesRecord record, string financeCountryKey, bool financeInclude) + { + if (!financeInclude) + return 0m; + + if (financeCountryKey.Equals("DE", StringComparison.OrdinalIgnoreCase) && IsGermanyCreditNote(record)) + return -Math.Abs(record.SalesPriceValue); + + return record.SalesPriceValue; + } + + private static bool IsIncludedGermanyFinanceRow(SalesRecord record) + => !IsGermanyTrafagAgRecharge(record) && + !IsGermanyMagneticSenseRecharge(record) && + !IsGermanyCreditNoteAlreadyCapturedInPriorYear(record); + + private static string ResolveGermanyExclusionReason(SalesRecord record) + { + if (IsGermanyTrafagAgRecharge(record)) + return "Excluded DE Weiterberechnung Trafag AG"; + if (IsGermanyMagneticSenseRecharge(record)) + return "Excluded DE Weiterberechnung Magnetic Sense"; + if (IsGermanyCreditNoteAlreadyCapturedInPriorYear(record)) + return "Excluded DE GS2510095 already captured in 2024"; + + return "Excluded DE"; + } + + private static bool IsGermanyTrafagAgRecharge(SalesRecord record) + => NormalizeFinanceText(record.CustomerName) == "TRAFAG AG"; + + private static bool IsGermanyMagneticSenseRecharge(SalesRecord record) + => NormalizeFinanceText(record.CustomerName).Contains("MAGNETIC SENSE", StringComparison.OrdinalIgnoreCase); + + private static bool IsGermanyCreditNote(SalesRecord record) + => (record.InvoiceNumber ?? string.Empty).Trim().StartsWith("GS", StringComparison.OrdinalIgnoreCase); + + private static bool IsGermanyCreditNoteAlreadyCapturedInPriorYear(SalesRecord record) + => (record.InvoiceNumber ?? string.Empty).Trim().Equals("GS2510095", StringComparison.OrdinalIgnoreCase); + private static bool IsExcludedItalyCustomer(SalesRecord record) => NormalizeFinanceText(record.CustomerName).Contains("TRAFAG ITALIA", StringComparison.OrdinalIgnoreCase); diff --git a/TrafagSalesExporter/TrafagSalesExporter.Tests/ExcelExportServiceTests.cs b/TrafagSalesExporter/TrafagSalesExporter.Tests/ExcelExportServiceTests.cs new file mode 100644 index 0000000..e8a767c --- /dev/null +++ b/TrafagSalesExporter/TrafagSalesExporter.Tests/ExcelExportServiceTests.cs @@ -0,0 +1,70 @@ +using ClosedXML.Excel; +using TrafagSalesExporter.Models; +using TrafagSalesExporter.Services; + +namespace TrafagSalesExporter.Tests; + +public class ExcelExportServiceTests +{ + [Fact] + public void CreateConsolidatedExcelFile_Uses_Germany_Finance_Response_Rules() + { + var outputDirectory = Path.Combine(Path.GetTempPath(), $"trafag-export-{Guid.NewGuid():N}"); + var service = new ExcelExportService(); + var records = new List + { + CreateGermanyRecord("Normal GmbH", "Deutschland", "RE250001", 100m), + CreateGermanyRecord("Trafag AG", "Schweiz", "RE250002", 40m), + CreateGermanyRecord("Magnetic Sense GmbH", "Deutschland", "RE250003", 30m), + CreateGermanyRecord("Normal GmbH", "Deutschland", "GS2510096", 20m), + CreateGermanyRecord("Normal GmbH", "Deutschland", "GS2510095", 10m) + }; + + try + { + var path = service.CreateConsolidatedExcelFile(outputDirectory, new DateTime(2026, 5, 20), records); + + using var workbook = new XLWorkbook(path); + var summary = workbook.Worksheet("Finance Summary"); + var deSummaryRow = summary.RowsUsed() + .Where(row => row.RowNumber() > 4) + .Single(row => row.Cell(1).GetValue() == 2025 && row.Cell(2).GetString() == "DE"); + + Assert.Equal(2, deSummaryRow.Cell(4).GetValue()); + Assert.Equal(80m, deSummaryRow.Cell(5).GetValue()); + Assert.Equal(3, deSummaryRow.Cell(6).GetValue()); + + 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") + .ToList(); + + Assert.Equal(2, includedGermanyRows.Count); + Assert.Equal(80m, includedGermanyRows.Sum(row => row.Cell(39).GetValue())); + } + finally + { + if (Directory.Exists(outputDirectory)) + Directory.Delete(outputDirectory, recursive: true); + } + } + + private static SalesRecord CreateGermanyRecord(string customerName, string customerCountry, string invoiceNumber, decimal value) + => new() + { + ExtractionDate = new DateTime(2026, 5, 20), + Tsc = "TRDE", + Land = "Deutschland", + InvoiceNumber = invoiceNumber, + PositionOnInvoice = 1, + CustomerName = customerName, + CustomerCountry = customerCountry, + SalesPriceValue = value, + SalesCurrency = "EUR", + CompanyCurrency = "EUR", + DocumentType = "Alphaplan Excel" + }; +} diff --git a/TrafagSalesExporter/web.config b/TrafagSalesExporter/web.config index 025c4c1..a84e558 100644 --- a/TrafagSalesExporter/web.config +++ b/TrafagSalesExporter/web.config @@ -13,6 +13,7 @@ hostingModel="outofprocess"> +