@page "/finance-cockpit/vergleich" @using TrafagSalesExporter.Models @using TrafagSalesExporter.Services @inject IFinanceReconciliationService FinanceReconciliationService @inject IUiTextService UiText @T("Soll/Ist Vergleich", "Actual/reference comparison") @T("Soll/Ist Vergleich", "Actual/reference comparison")
@T("Net Sales Actuals 2025 Referenz", "Net sales actuals 2025 reference") @T("Verbindliche Finance-Sicht aus CentralSalesRecords", "Authoritative finance view from CentralSalesRecords")
@T("Ohne Ist", "Without empty actuals") @string.Format(T("{0:N0}/{1:N0} Zeilen", "{0:N0}/{1:N0} rows"), FilteredNetSalesReferenceRows.Count, _netSalesReferenceRows.Count) @(_loading ? T("Lade...", "Loading...") : T("Aktualisieren", "Refresh"))
@T("Ampel", "Status") @T("Land", "Country") @T("Ist 2025", "Actual 2025") @T("Referenz", "Reference") @T("Differenz", "Difference") @T("Waehrung", "Currency") @T("Berechnung", "Calculation") @T("Zeilen", "Rows") @T("Varianten", "Variants") @StatusText(context.Status) @context.Label @context.Key @FormatAmount(context.ActualValue) @FormatAmount(context.ReferenceValue) @FormatAmount(context.Difference) @FormatCurrency(context) @(string.IsNullOrWhiteSpace(context.ValueField) ? "-" : context.ValueField) @BuildCalculationHint(context) @(context.RowCount > 0 ? context.RowCount.ToString("N0") : "-") @if (context.Candidates.Count > 0) {
@context.Candidates.Count @T("Varianten anzeigen", "show variants") @foreach (var candidate in context.Candidates) { }
@T("Abgrenzung", "Scope") @T("Waehrung", "Currency") @T("Wert", "Value") @T("Diff.", "Diff.") @T("IC", "IC") @T("Diff ohne IC", "Diff excl. IC")
@candidate.Label @candidate.Currency @FormatAmount(candidate.Value) @FormatAmount(candidate.Difference) @FormatAmount(candidate.IntercompanyValue) @FormatAmount(candidate.DifferenceExcludingIntercompany)
} else { - }
@T("Keine Referenzdaten fuer aktive Standorte gefunden.", "No reference data found for active sites.")
@T("Diese Seite nutzt dieselbe FinanceReconciliationService-Logik wie das lokale Testprogramm. Vergleich: Jahr 2025 aus Buchungsdatum, sonst Invoice Date, sonst Extraction Date. Das Summenfeld wird automatisch aus Sales Price/Value, DocTotalFC - VatSumFC oder DocTotal - VatSum gewaehlt; Belegkopfwerte werden pro DocEntry nur einmal gezaehlt. IC-Abzug ist eine Diagnose fuer den aktuellen Abgleich und veraendert die Originaldaten nicht.", "This page uses the same FinanceReconciliationService logic as the local test program. Comparison: year 2025 from posting date, otherwise invoice date, otherwise extraction date. The value field is selected automatically from Sales Price/Value, DocTotalFC - VatSumFC, or DocTotal - VatSum; document header values are counted only once per DocEntry. IC deduction is a diagnostic value for the current reconciliation and does not change the original data.")
@code { private List _netSalesReferenceRows = new(); private bool _hideRowsWithoutActual = true; private bool _loading = true; private List FilteredNetSalesReferenceRows => _hideRowsWithoutActual ? _netSalesReferenceRows.Where(row => row.ActualValue.HasValue).ToList() : _netSalesReferenceRows; private void ToggleActualFilter() { _hideRowsWithoutActual = !_hideRowsWithoutActual; } protected override async Task OnInitializedAsync() { await LoadAsync(); } private async Task LoadAsync() { _loading = true; _netSalesReferenceRows = await FinanceReconciliationService.BuildNetSalesReferenceRowsAsync(2025); _loading = false; } private static string FormatAmount(decimal? value) => value.HasValue ? value.Value.ToString("N2") : "-"; private static string FormatCurrency(NetSalesReferenceRow row) { if (!string.IsNullOrWhiteSpace(row.ActualCurrency)) return row.ReferenceCurrency == "LC" ? $"{row.ActualCurrency} / Soll LC" : row.ActualCurrency; return string.IsNullOrWhiteSpace(row.Currencies) ? "-" : row.Currencies; } private string BuildCalculationHint(NetSalesReferenceRow row) { if (row.Key.Equals("UK", StringComparison.OrdinalIgnoreCase)) return T("Sage Netto in GBP; Credit Notes negativ; Soll ist Local Currency.", "Sage net in GBP; credit notes negative; reference is local currency."); if (row.Key.Equals("ES", StringComparison.OrdinalIgnoreCase)) return T("Sage ImporteNeto; REC/Credit Notes negativ; Zuschlaege/Nebenkosten noch pruefen.", "Sage ImporteNeto; REC/credit notes negative; surcharges/charges still to check."); if (row.Key.Equals("IT", StringComparison.OrdinalIgnoreCase)) return T("Bestaetigte IT-Regel: Trafag Italia ausgeschlossen; doppelte Zeilen ohne Supplier country nur einmal.", "Confirmed IT rule: Trafag Italia excluded; duplicate rows without supplier country counted once."); if (row.Key.Equals("DE", StringComparison.OrdinalIgnoreCase)) return T("Alphaplan Excel; Kundenlaender/Filter fuer offiziellen DE-Istwert noch fachlich offen.", "Alphaplan Excel; customer countries/filters for official DE actual are still open."); if (row.Key.Equals("FR", StringComparison.OrdinalIgnoreCase) || row.Key.Equals("IN", StringComparison.OrdinalIgnoreCase) || row.Key.Equals("US", StringComparison.OrdinalIgnoreCase)) return T("Passt gegen Soll; Sales Price/Value ist bevorzugte Variante.", "Matches reference; Sales Price/Value is the preferred variant."); return row.ReferenceCurrency == "LC" ? T("Vergleich gegen Local Currency Referenz.", "Compared against local currency reference.") : T("Vergleich gegen Check-/Sollwert.", "Compared against check/reference value."); } private Color StatusColor(string status) => status == "OK" ? Color.Success : status == "Pruefen" ? Color.Warning : Color.Default; private string StatusText(string status) => status == "OK" ? "OK" : status == "Pruefen" ? T("Pruefen", "Check") : T("Keine Daten", "No data"); private string T(string german, string english) => UiText.Text(german, english); }