Apply confirmed Italy finance method

This commit is contained in:
2026-05-20 08:10:02 +02:00
parent b2aa7b046f
commit 6f8528ac54
8 changed files with 274 additions and 7 deletions
@@ -91,6 +91,7 @@ public class ExcelExportService : IExcelExportService
}
var row = 2;
var italyBlankSupplierCountryRows = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var record in records)
{
ws.Cell(row, 1).Value = record.ExtractionDate.ToString("dd.MM.yyyy HH:mm:ss");
@@ -129,13 +130,17 @@ public class ExcelExportService : IExcelExportService
ws.Cell(row, 34).Value = record.Land;
ws.Cell(row, 35).Value = record.DocumentType;
var financeDate = ResolveFinanceDate(record);
var financeCountryKey = ResolveFinanceCountryKey(record.Land, record.Tsc);
var financeInclude = ResolveFinanceInclude(record, financeCountryKey, italyBlankSupplierCountryRows);
ws.Cell(row, 36).Value = financeDate.Year;
ws.Cell(row, 37).Value = ResolveFinanceCountryKey(record.Land, record.Tsc);
ws.Cell(row, 37).Value = financeCountryKey;
ws.Cell(row, 38).Value = financeDate.ToString("dd.MM.yyyy");
ws.Cell(row, 39).Value = record.SalesPriceValue;
ws.Cell(row, 39).Value = financeInclude ? record.SalesPriceValue : 0m;
ws.Cell(row, 40).Value = ResolveFinanceCurrency(record);
ws.Cell(row, 41).Value = record.SalesPriceValue != 0m ? "TRUE" : "FALSE";
ws.Cell(row, 42).Value = "Sales Price/Value";
ws.Cell(row, 41).Value = financeInclude && record.SalesPriceValue != 0m ? "TRUE" : "FALSE";
ws.Cell(row, 42).Value = financeInclude
? "Sales Price/Value"
: ResolveFinanceExclusionReason(record, financeCountryKey);
row++;
}
@@ -163,6 +168,7 @@ public class ExcelExportService : IExcelExportService
("Waehrung", "Finance | Currency zeigt die fuer den Finance-Abgleich fuehrende Hauswaehrung."),
("Datum", "Finance | Date verwendet PostingDate, danach InvoiceDate, danach ExtractionDate."),
("Wertquelle", "Finance | Source Value Field zeigt, aus welchem Rohfeld der Finance-Wert kommt."),
("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.")
};
@@ -235,6 +241,62 @@ public class ExcelExportService : IExcelExportService
return normalizedTsc.Replace("TR", string.Empty);
}
private static bool ResolveFinanceInclude(SalesRecord record, string financeCountryKey, HashSet<string> italyBlankSupplierCountryRows)
{
if (!financeCountryKey.Equals("IT", StringComparison.OrdinalIgnoreCase))
return true;
if (IsExcludedItalyCustomer(record))
return false;
if (!string.IsNullOrWhiteSpace(record.SupplierCountry))
return true;
return italyBlankSupplierCountryRows.Add(BuildItalyBlankSupplierCountryDeduplicationKey(record));
}
private static string ResolveFinanceExclusionReason(SalesRecord record, string financeCountryKey)
{
if (financeCountryKey.Equals("IT", StringComparison.OrdinalIgnoreCase) && IsExcludedItalyCustomer(record))
return "Excluded IT customer: Trafag Italia";
if (financeCountryKey.Equals("IT", StringComparison.OrdinalIgnoreCase) && string.IsNullOrWhiteSpace(record.SupplierCountry))
return "Excluded IT duplicate without Supplier country";
return "Excluded";
}
private static bool IsExcludedItalyCustomer(SalesRecord record)
=> NormalizeFinanceText(record.CustomerName).Contains("TRAFAG ITALIA", StringComparison.OrdinalIgnoreCase);
private static string BuildItalyBlankSupplierCountryDeduplicationKey(SalesRecord record)
=> string.Join("|",
record.Tsc,
record.DocumentType,
record.DocumentEntry,
record.InvoiceNumber,
record.PositionOnInvoice,
record.Material,
record.Name,
record.Quantity,
record.CustomerNumber,
record.CustomerName,
record.SalesPriceValue,
record.DocumentTotalForeignCurrency,
record.DocumentTotalLocalCurrency,
record.VatSumForeignCurrency,
record.VatSumLocalCurrency,
record.PostingDate?.ToString("O") ?? string.Empty,
record.InvoiceDate?.ToString("O") ?? string.Empty);
private static string NormalizeFinanceText(string value)
=> (value ?? string.Empty)
.Replace("\u00e4", "ae", StringComparison.OrdinalIgnoreCase)
.Replace("\u00f6", "oe", StringComparison.OrdinalIgnoreCase)
.Replace("\u00fc", "ue", StringComparison.OrdinalIgnoreCase)
.Trim()
.ToUpperInvariant();
private static void WriteGenericWorkbook(string fullPath, string worksheetName, IReadOnlyList<IReadOnlyDictionary<string, object?>> rows)
{
using var workbook = new XLWorkbook();
@@ -40,12 +40,17 @@ public sealed class FinanceReconciliationService : IFinanceReconciliationService
r.Tsc,
r.DocumentEntry,
r.InvoiceNumber,
r.PositionOnInvoice,
r.Material,
r.Name,
r.Quantity,
r.DocumentType,
r.PostingDate,
r.InvoiceDate,
r.ExtractionDate,
r.CustomerNumber,
r.CustomerName,
r.SupplierCountry,
r.SalesCurrency,
r.DocumentCurrency,
r.CompanyCurrency,
@@ -150,7 +155,7 @@ public sealed class FinanceReconciliationService : IFinanceReconciliationService
IReadOnlyDictionary<string, decimal> budgetRatesToChf,
IReadOnlyList<FinanceIntercompanyRule> intercompanyRules)
{
var rowList = rows.ToList();
var rowList = ApplyCountryFinanceRules(referenceKey, rows).ToList();
var houseCurrency = ResolveHouseCurrency(referenceKey, rowList);
var documentRows = rowList
.GroupBy(row => BuildDocumentKey(row.Tsc, row.DocumentType, row.DocumentEntry, row.InvoiceNumber), StringComparer.OrdinalIgnoreCase)
@@ -239,6 +244,50 @@ public sealed class FinanceReconciliationService : IFinanceReconciliationService
return repeatedGroups / (decimal)multiLineGroups.Count >= 0.8m;
}
private static IEnumerable<NetSalesActualSourceRow> ApplyCountryFinanceRules(
string referenceKey,
IEnumerable<NetSalesActualSourceRow> rows)
{
if (!referenceKey.Equals("IT", StringComparison.OrdinalIgnoreCase))
return rows;
var seenBlankSupplierCountryRows = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
return rows.Where(row =>
{
if (IsExcludedItalyCustomer(row))
return false;
if (!string.IsNullOrWhiteSpace(row.SupplierCountry))
return true;
return seenBlankSupplierCountryRows.Add(BuildItalyBlankSupplierCountryDeduplicationKey(row));
});
}
private static bool IsExcludedItalyCustomer(NetSalesActualSourceRow row)
=> ResolveReferenceKey(row.Land, row.Tsc).Equals("IT", StringComparison.OrdinalIgnoreCase) &&
NormalizeRuleText(row.CustomerName).Contains("TRAFAG ITALIA", StringComparison.OrdinalIgnoreCase);
private static string BuildItalyBlankSupplierCountryDeduplicationKey(NetSalesActualSourceRow row)
=> string.Join("|",
row.Tsc,
row.DocumentType,
row.DocumentEntry,
row.InvoiceNumber,
row.PositionOnInvoice,
row.Material,
row.Name,
row.Quantity,
row.CustomerNumber,
row.CustomerName,
row.SalesPriceValue,
row.DocumentTotalForeignCurrency,
row.DocumentTotalLocalCurrency,
row.VatSumForeignCurrency,
row.VatSumLocalCurrency,
row.PostingDate?.ToString("O") ?? string.Empty,
row.InvoiceDate?.ToString("O") ?? string.Empty);
private static decimal ConvertHouseCurrencyNetToBudgetChf(
string houseCurrency,
NetSalesActualSourceRow row,
@@ -398,12 +447,17 @@ internal sealed record NetSalesActualSourceRow(
string Tsc,
int DocumentEntry,
string InvoiceNumber,
int PositionOnInvoice,
string Material,
string Name,
decimal Quantity,
string DocumentType,
DateTime? PostingDate,
DateTime? InvoiceDate,
DateTime ExtractionDate,
string CustomerNumber,
string CustomerName,
string SupplierCountry,
string SalesCurrency,
string DocumentCurrency,
string CompanyCurrency,