@page "/management-cockpit"
@rendermode @(Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer)
@using Microsoft.AspNetCore.Components
@using TrafagSalesExporter.Models
@using TrafagSalesExporter.Services
@inject IManagementCockpitPageService CockpitPageService
@inject ISnackbar Snackbar
@inject IUiTextService UiText
@T("Management Analyse", "Management analysis")
@T("Management Analyse", "Management analysis")
@foreach (var year in _financeYearOptions)
{
@year
}
@foreach (var option in _financeCountryOptions)
{
@option
}
@foreach (var option in _financeCurrencyOptions)
{
@option
}
@(_analyzingFinance ? T("Lade...", "Loading...") : T("Finance Summary laden", "Load finance summary"))
@if (_financeResult is not null)
{
@T("Net Sales Actual", "Net sales actual")
@FormatValue(_financeResult.NetSalesActual, _financeResult.DisplayCurrency)
@T("gefiltertes Endergebnis", "filtered final result")
@T("Enthaltene Zeilen", "Included rows")
@_financeResult.IncludedRows.ToString("N0")
@T("Finance Include = TRUE", "Finance Include = TRUE")
@T("Ausgeschlossen", "Excluded")
@_financeResult.ExcludedRows.ToString("N0")
@T("Finance-Regeln", "Finance rules")
@T("Laender / Waehrungen", "Countries / currencies")
@($"{_financeResult.CountryCount:N0} / {_financeResult.CurrencyCount:N0}")
@($"{_financeResult.Filter.Year}")
@T("Summen wie im Excel-Blatt Finance Summary", "Totals matching the Finance Summary Excel sheet")
@T("Jahr", "Year")
@T("Land", "Country")
@T("Waehrung", "Currency")
@T("Net Sales Actual", "Net sales actual")
@T("Enthalten", "Included")
@T("Ausgeschlossen", "Excluded")
@context.Year
@context.CountryKey
@context.Currency
@FormatValue(context.NetSalesActual, context.Currency)
@context.IncludedRows.ToString("N0")
@context.ExcludedRows.ToString("N0")
@T("Keine Finance-Summary-Daten fuer diese Filter.", "No finance summary data for these filters.")
@T("Hinweise", "Notes")
@foreach (var notice in _financeResult.Notices)
{
@notice
}
@T("Jahresvergleich mit aktuellem Filter", "Year comparison with current filter")
@T("Jahr", "Year")
@T("Waehrung", "Currency")
@T("Net Sales Actual", "Net sales actual")
@T("Enthalten", "Included")
@T("Ausgeschlossen", "Excluded")
@context.Year
@context.Currency
@FormatValue(context.NetSalesActual, context.Currency)
@context.IncludedRows.ToString("N0")
@context.ExcludedRows.ToString("N0")
@T("Finance-Status nach Land", "Finance status by country")
@T("Status", "Status")
@T("Land", "Country")
TSC
@T("Quelle", "Source")
@T("Waehrung", "Currency")
@T("Ist", "Actual")
@T("Soll", "Reference")
@T("Differenz", "Difference")
@T("Zeilen", "Rows")
@context.Status
@context.CountryKey
@context.Tscs
@context.SourceSystems
@context.Currency
@FormatValue(context.NetSalesActual, context.Currency)
@FormatNullableValue(context.ReferenceValue, context.Currency)
@FormatNullableValue(context.Difference, context.Currency)
@context.IncludedRows.ToString("N0") / @context.ExcludedRows.ToString("N0")
@T("Keine Laenderdaten fuer diese Filter.", "No country data for these filters.")
@T("Datenbestand nach Standort", "Data inventory by site")
@T("Aktiv", "Active")
@T("Land", "Country")
TSC
@T("Quelle", "Source")
@T("Zentrale Zeilen", "Central rows")
@T("Letzter Export", "Latest export")
@T("Exportstatus", "Export status")
@T("Letzte Speicherung", "Latest stored")
@T("Manual Import", "Manual import")
@context.Land
@context.Tsc
@context.SourceSystem
@context.RowCount.ToString("N0")
@FormatDateTime(context.LatestExportAt)
@(string.IsNullOrWhiteSpace(context.LatestExportStatus) ? "-" : context.LatestExportStatus)
@FormatDateTime(context.LatestStoredAtUtc)
@FormatManualImportStatus(context)
@T("Soll/Ist-Abweichungen", "Actual/reference deviations")
@T("Status", "Status")
@T("Land", "Country")
@T("Waehrung", "Currency")
@T("Ist", "Actual")
@T("Soll", "Reference")
@T("Differenz", "Difference")
%
@context.Status
@context.CountryKey
@context.Currency
@FormatValue(context.NetSalesActual, context.Currency)
@FormatNullableValue(context.ReferenceValue, context.Currency)
@FormatNullableValue(context.Difference, context.Currency)
@FormatPercent(context.DifferencePercent)
@T("Keine Sollwerte oder keine Abweichungen fuer diese Filter.", "No reference values or deviations for these filters.")
@T("Gutschriften-Kandidaten", "Credit-note candidates")
@T("Diese Sicht zeigt technische Kandidaten anhand negativer Werte und erkennbarer Belegtypen/-nummern. Sie ersetzt keine landesspezifische Fachfreigabe.",
"This view shows technical candidates based on negative values and recognizable document types/numbers. It does not replace country-specific business approval.")
@T("Land", "Country")
TSC
@T("Rechnung", "Invoice")
@T("Typ", "Type")
@T("Wert", "Value")
@T("Menge", "Quantity")
@T("Grund", "Reason")
@context.CountryKey
@context.Tsc
@context.InvoiceNumber
@context.DocumentType
@FormatValue(context.NetSalesActual, context.Currency)
@context.Quantity.ToString("N2")
@context.Reason
@T("Keine Gutschriften-Kandidaten fuer diese Filter.", "No credit-note candidates for these filters.")
@T("Pruefpunkte", "Checkpoints")
@T("Status", "Status")
@T("Pruefpunkt", "Checkpoint")
@T("Anzahl", "Count")
@context.Severity
@context.Issue
@context.Count.ToString("N0")
@T("Keine Datenqualitaetsauffaelligkeiten fuer diese Filter.", "No data-quality findings for these filters.")
@T("Gesamtumsatz", "Total sales")
@FormatValue(_financeResult.ProductFinanceSummary.TotalValue, _financeResult.ProductFinanceSummary.DisplayCurrency)
@T("Zugeordneter Umsatz", "Assigned sales")
@FormatValue(_financeResult.ProductFinanceSummary.AssignedValue, _financeResult.ProductFinanceSummary.DisplayCurrency)
@FormatPercent(_financeResult.ProductFinanceSummary.AssignedValuePercent)
@T("Nicht zugeordnet", "Unassigned")
@FormatValue(_financeResult.ProductFinanceSummary.UnassignedValue, _financeResult.ProductFinanceSummary.DisplayCurrency)
@FormatPercent(_financeResult.ProductFinanceSummary.UnassignedValuePercent)
@T("Nicht im Stamm", "Not in master")
@FormatValue(_financeResult.ProductFinanceSummary.MissingReferenceValue, _financeResult.ProductFinanceSummary.DisplayCurrency)
@FormatPercent(_financeResult.ProductFinanceSummary.MissingReferenceValuePercent)
@T("Umsatz nach Produktsparte", "Sales by product division")
@T("Produktsparte", "Product division")
@T("Produktfamilie", "Product family")
PAPH1
@T("Umsatz", "Sales")
@T("Anteil", "Share")
@T("Materialien", "Materials")
@T("Zeilen", "Rows")
@T("Laender", "Countries")
@BuildCodeText(context.ProductDivisionCode, context.ProductDivisionText)
@BuildCodeText(context.ProductFamilyCode, context.ProductFamilyText)
@BuildCodeText(context.ProductHierarchyCode, context.ProductHierarchyText)
@FormatValue(context.NetSalesActual, context.Currency)
@FormatPercent(context.SharePercent)
@context.MaterialCount.ToString("N0")
@context.RowCount.ToString("N0")
@context.Countries
@T("Keine zugeordneten Spartenumsaetze fuer diese Filter.", "No assigned division sales for these filters.")
@T("Umsatzabdeckung nach Land", "Sales coverage by country")
@T("Land", "Country")
TSC
@T("Gesamt", "Total")
@T("Zugeordnet", "Assigned")
@T("Nicht zugeordnet", "Unassigned")
@T("Nicht im Stamm", "Not in master")
@T("Material fehlt", "Material missing")
@T("Abdeckung", "Coverage")
@context.CountryKey
@context.Tsc
@FormatValue(context.TotalValue, context.Currency)
@FormatValue(context.AssignedValue, context.Currency)
@FormatValue(context.UnassignedValue, context.Currency)
@FormatValue(context.MissingReferenceValue, context.Currency)
@FormatValue(context.MissingMaterialValue, context.Currency)
@FormatPercent(context.AssignedValuePercent)
@T("Keine Umsatzabdeckung fuer diese Filter.", "No sales coverage for these filters.")
@T("Materialien", "Materials")
@_financeResult.ProductAssignmentSummary.DistinctMaterialCount.ToString("N0")
@T("Zugeordnet", "Assigned")
@_financeResult.ProductAssignmentSummary.MatchedMaterialCount.ToString("N0")
@T("Nicht zugeordnet", "Unassigned")
@_financeResult.ProductAssignmentSummary.UnassignedMaterialCount.ToString("N0")
@T("Nicht im Stamm", "Not in master")
@_financeResult.ProductAssignmentSummary.MissingReferenceMaterialCount.ToString("N0")
@T("Material fehlt", "Material missing")
@_financeResult.ProductAssignmentSummary.MissingMaterialNumberCount.ToString("N0")
@T("TR-AG Referenz", "TR AG reference")
@_financeResult.ProductAssignmentSummary.ReferenceMaterialCount.ToString("N0")
@T("Diese Sicht prueft Materialnummern aller gefilterten Laender gegen die fuehrende TR-AG-Referenz aus `ProductDivisionRefSet`. Die Produktsparten der lokalen ERPs werden nicht verwendet.",
"This view checks material numbers from all filtered countries against the leading TR AG reference from `ProductDivisionRefSet`. Local ERP product divisions are not used.")
@T("Abdeckung nach Land", "Coverage by country")
@T("Land", "Country")
TSC
@T("Materialien", "Materials")
@T("Zugeordnet", "Assigned")
@T("Nicht zugeordnet", "Unassigned")
@T("Nicht im Stamm", "Not in master")
@T("Material fehlt", "Material missing")
@T("Trefferquote", "Match rate")
@context.CountryKey
@context.Tsc
@context.DistinctMaterialCount.ToString("N0")
@context.MatchedMaterialCount.ToString("N0")
@context.UnassignedMaterialCount.ToString("N0")
@context.MissingReferenceMaterialCount.ToString("N0")
@context.MissingMaterialNumberCount.ToString("N0")
@FormatPercent(context.MatchPercent)
@T("Keine Materialdaten fuer diese Filter.", "No material data for these filters.")
@T("Materialpruefung gegen TR-AG-Referenz", "Material check against TR AG reference")
@T("Status", "Status")
@T("Land", "Country")
TSC
@T("Land-Material", "Local material")
@T("Land-Text", "Local text")
@T("TR-AG-MATNR", "TR AG MATNR")
PAPH1
@T("Produktfamilie", "Product family")
@T("Produktsparte", "Product division")
@T("Zeilen", "Rows")
@T("Finance-Wert", "Finance value")
@context.Status
@context.CountryKey
@context.Tsc
@context.Material
@context.ArticleName
@context.ReferenceMaterial
@BuildCodeText(context.ProductHierarchyCode, context.ProductHierarchyText)
@BuildCodeText(context.ProductFamilyCode, context.ProductFamilyText)
@BuildCodeText(context.ProductDivisionCode, context.ProductDivisionText)
@context.RowCount.ToString("N0")
@FormatValue(context.NetSalesActual, context.Currency)
@T("Keine Materialpruefung fuer diese Filter.", "No material check for these filters.")
@foreach (var file in _files)
{
@file.DisplayName
}
@foreach (var option in _valueFieldOptions)
{
@option.Label
}
@foreach (var option in _currencyOptions)
{
@option.Label
}
@T("Dateien laden", "Load files")
@(_analyzing ? T("Analysiere...", "Analyzing...") : T("Cockpit erzeugen", "Build cockpit"))
@T("Zentrale Roh-Auswertung", "Central raw analysis")
@T("Diese Sicht arbeitet direkt auf `CentralSalesRecords`. Summenfeld und Anzeige-Waehrung koennen gewaehlt werden; fachliche Filter wie Intercompany, Budget und Spartenlogik sind weiterhin nicht enthalten.", "This view works directly on `CentralSalesRecords`. Value field and display currency can be selected; business filters such as intercompany, budget and divisional logic are still not included.")
@T("Diese Analyse ist eine Plausibilitaets- und Rohdatensicht. Fuer den verbindlichen Finance-Abgleich bitte `Soll/Ist Vergleich` oder im Endexcel die `Finance | ...`-Spalten verwenden.",
"This analysis is a plausibility/raw-data view. For the authoritative finance reconciliation, use `Actual/reference comparison` or the `Finance | ...` columns in the final Excel.")
@foreach (var year in _centralYears)
{
@year
}
@foreach (var month in Enumerable.Range(1, 12))
{
@($"{month:D2}")
}
@foreach (var option in _valueFieldOptions)
{
@option.Label
}
@foreach (var option in _valueFieldOptions)
{
@option.Label
}
@foreach (var option in _currencyOptions)
{
@option.Label
}
@(_analyzingCentral ? T("Analysiere...", "Analyzing...") : T("Zentrale Auswertung laden", "Load central analysis"))
@T("Global", "Global")
@if (!string.IsNullOrWhiteSpace(_centralLandFilter) || !string.IsNullOrWhiteSpace(_centralTscFilter))
{
@T("Gefiltert", "Filtered"): @($"{(_centralLandFilter ?? "-")} / {(_centralTscFilter ?? "-")}")
}
@if (_result is not null)
{
@T("Land", "Country")@_result.Summary.Land
TSC@_result.Summary.Tsc
@_result.Summary.ValueFieldLabel@FormatValue(_result.Summary.AggregatedValueTotal, _result.Summary.DisplayCurrency)
@T("Nicht umgerechnet", "Not converted")@_result.Summary.MissingExchangeRateCount.ToString("N0")
@T("Management Aussagen", "Management statements")
@foreach (var finding in _result.Findings)
{
@finding.Title: @finding.Detail
}
@T("Top Kunden", "Top customers")
@foreach (var item in _result.TopCustomers)
{
@($"{item.Label}: {FormatValue(item.Value, _result.Summary.DisplayCurrency)} ({item.SharePercent:F1}%)")
}
@T("Top Produktgruppen", "Top product groups")
@foreach (var item in _result.TopProductGroups)
{
@($"{item.Label}: {FormatValue(item.Value, _result.Summary.DisplayCurrency)} ({item.SharePercent:F1}%)")
}
@T("Top Sales Owner", "Top sales owner")
@foreach (var item in _result.TopSalesEmployees)
{
@($"{item.Label}: {FormatValue(item.Value, _result.Summary.DisplayCurrency)} ({item.SharePercent:F1}%)")
}
@T("Datenqualitaet", "Data quality")
@foreach (var entry in _result.DataQualityCounts.OrderByDescending(x => x.Value))
{
@($"{entry.Key}: {entry.Value}")
}
}
@if (_centralResult is not null)
{
@T("Zeilen", "Rows")@_centralResult.Summary.RowCount.ToString("N0")
@T("Rechnungen", "Invoices")@_centralResult.Summary.InvoiceCount.ToString("N0")
@T("Standorte", "Sites")@_centralResult.Summary.SiteCount.ToString("N0")
@T("Laender", "Countries")@_centralResult.Summary.CountryCount.ToString("N0")
@_centralResult.Summary.ValueFieldLabel@FormatValue(_centralResult.Summary.ValueTotal, _centralResult.Summary.DisplayCurrency)
@T("Nicht umgerechnet", "Not converted")@_centralResult.Summary.MissingExchangeRateCount.ToString("N0")
@T("Hinweise", "Notes")
@foreach (var notice in _centralResult.Notices)
{
@notice
}
@T("Jahreswerte", "Yearly values")
@T("Jahr", "Year")
@T("Waehrung", "Currency")
@_centralResult.Summary.ValueFieldLabel
@foreach (var field in _centralResult.AdditionalValueFields)
{
@field.Label
}
@T("Zeilen", "Rows")
@context.Year
@context.Currency
@FormatValue(context.SalesValue, context.Currency)
@foreach (var field in _centralResult.AdditionalValueFields)
{
@FormatAdditionalValue(context, field.Key)
}
@context.RowCount.ToString("N0")
@T("Monatswerte", "Monthly values")
@T("Monat", "Month")
@T("Waehrung", "Currency")
@_centralResult.Summary.ValueFieldLabel
@foreach (var field in _centralResult.AdditionalValueFields)
{
@field.Label
}
@T("Zeilen", "Rows")
@context.Label
@context.Currency
@FormatValue(context.SalesValue, context.Currency)
@foreach (var field in _centralResult.AdditionalValueFields)
{
@FormatAdditionalValue(context, field.Key)
}
@context.RowCount.ToString("N0")
@T("Tageswerte im ausgewaehlten Monat", "Daily values in selected month")
@T("Tag", "Day")
@T("Waehrung", "Currency")
@_centralResult.Summary.ValueFieldLabel
@foreach (var field in _centralResult.AdditionalValueFields)
{
@field.Label
}
@T("Zeilen", "Rows")
@context.Label
@context.Currency
@FormatValue(context.SalesValue, context.Currency)
@foreach (var field in _centralResult.AdditionalValueFields)
{
@FormatAdditionalValue(context, field.Key)
}
@context.RowCount.ToString("N0")
@T("Fuer die Tagessicht bitte zusaetzlich einen Monat waehlen.", "Please select a month as well for the daily view.")
@T("Werte nach Quelle", "Values by source")
@T("Quelle", "Source")
@T("Waehrung", "Currency")
@_centralResult.Summary.ValueFieldLabel
@T("Rechnungen", "Invoices")
@context.Label
@context.Currency
@FormatValue(context.SalesValue, context.Currency)
@context.InvoiceCount.ToString("N0")
@T("Werte nach Land", "Values by country")
@T("Land", "Country")
@T("Waehrung", "Currency")
@_centralResult.Summary.ValueFieldLabel
@T("Rechnungen", "Invoices")
@T("Zeilen", "Rows")
@context.Label
@context.Currency
@FormatValue(context.SalesValue, context.Currency)
@context.InvoiceCount.ToString("N0")
@context.RowCount.ToString("N0")
}
}
@code {
[Parameter]
[SupplyParameterFromQuery(Name = "section")]
public string? Section { get; set; }
[Parameter]
[SupplyParameterFromQuery(Name = "division")]
public string? Division { get; set; }
private List _files = [];
private List _centralYears = [];
private List _financeYearOptions = [];
private List _financeCountryOptions = [];
private List _financeCurrencyOptions = [];
private List _valueFieldOptions = [];
private readonly List _currencyOptions =
[
new(ManagementCockpitCurrencyOptions.Eur, "EUR"),
new(ManagementCockpitCurrencyOptions.Usd, "USD"),
new(ManagementCockpitCurrencyOptions.Native, "Original")
];
private string? _selectedFilePath;
private ManagementCockpitResult? _result;
private ManagementCockpitCentralResult? _centralResult;
private ManagementFinanceSummaryResult? _financeResult;
private int _selectedFinanceYear;
private string? _selectedFinanceCountryKey;
private string? _selectedFinanceCurrency;
private int _selectedCentralYear;
private int? _selectedCentralMonth;
private string? _centralLandFilter;
private string? _centralTscFilter;
private string _selectedFileValueField = ManagementCockpitValueFieldKeys.SalesPriceValue;
private string _selectedCentralValueField = ManagementCockpitValueFieldKeys.SalesPriceValue;
private IEnumerable _selectedCentralAdditionalValueFields = [];
private string _selectedFileTargetCurrency = ManagementCockpitCurrencyOptions.Eur;
private string _selectedCentralTargetCurrency = ManagementCockpitCurrencyOptions.Native;
private bool _loadingFiles;
private bool _analyzing;
private bool _analyzingCentral;
private bool _analyzingFinance;
private int _activeFinanceTabIndex;
private int _activeDivisionTabIndex;
protected override void OnParametersSet()
{
if (string.Equals(Section, "division", StringComparison.OrdinalIgnoreCase))
{
_activeFinanceTabIndex = 6;
_activeDivisionTabIndex = string.Equals(Division, "central", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
}
else if (string.IsNullOrWhiteSpace(Section))
{
_activeFinanceTabIndex = 0;
_activeDivisionTabIndex = 0;
}
}
protected override async Task OnInitializedAsync()
{
var state = await CockpitPageService.InitializeAsync(_selectedFilePath, _selectedCentralYear);
_files = state.Files;
_valueFieldOptions = state.ValueFieldOptions;
_centralYears = state.CentralYears;
_selectedFilePath = state.SelectedFilePath;
_selectedCentralYear = state.SelectedCentralYear;
_selectedFinanceYear = _selectedCentralYear;
await AnalyzeFinanceSummary();
}
private async Task ReloadFiles()
{
_loadingFiles = true;
try
{
_files = await CockpitPageService.LoadFilesAsync();
_selectedFilePath ??= _files.FirstOrDefault()?.Path;
}
finally
{
_loadingFiles = false;
}
}
private async Task ReloadCentralYears()
{
_centralYears = await CockpitPageService.LoadCentralYearsAsync();
if (_selectedCentralYear == 0)
_selectedCentralYear = _centralYears.LastOrDefault();
}
private async Task Analyze()
{
if (string.IsNullOrWhiteSpace(_selectedFilePath))
return;
_analyzing = true;
try
{
_result = await CockpitPageService.AnalyzeAsync(_selectedFilePath, new ManagementCockpitAnalysisOptions
{
ValueField = _selectedFileValueField,
TargetCurrency = _selectedFileTargetCurrency
});
_centralLandFilter = _result.Summary.Land;
_centralTscFilter = _result.Summary.Tsc;
}
catch (Exception ex)
{
Snackbar.Add(string.Format(T("Cockpit konnte nicht erzeugt werden: {0}", "Could not build cockpit: {0}"), ex.Message), Severity.Error);
}
finally
{
_analyzing = false;
}
}
private async Task AnalyzeCentral()
{
if (_selectedCentralYear == 0)
return;
_analyzingCentral = true;
try
{
_centralResult = await CockpitPageService.AnalyzeCentralAsync(_selectedCentralYear, _selectedCentralMonth, new ManagementCockpitAnalysisOptions
{
ValueField = _selectedCentralValueField,
AdditionalValueFields = _selectedCentralAdditionalValueFields.ToList(),
TargetCurrency = _selectedCentralTargetCurrency,
LandFilter = _centralLandFilter,
TscFilter = _centralTscFilter
});
}
catch (Exception ex)
{
Snackbar.Add(string.Format(T("Zentrale Auswertung konnte nicht erzeugt werden: {0}", "Could not build central analysis: {0}"), ex.Message), Severity.Error);
}
finally
{
_analyzingCentral = false;
}
}
private async Task AnalyzeFinanceSummary()
{
_analyzingFinance = true;
try
{
_financeResult = await CockpitPageService.AnalyzeFinanceSummaryAsync(
_selectedFinanceYear,
_selectedFinanceCountryKey,
_selectedFinanceCurrency);
_financeYearOptions = _financeResult.YearOptions;
_financeCountryOptions = _financeResult.CountryOptions;
_financeCurrencyOptions = _financeResult.CurrencyOptions;
_selectedFinanceYear = _financeResult.Filter.Year;
}
catch (Exception ex)
{
Snackbar.Add(string.Format(T("Finance Summary konnte nicht erzeugt werden: {0}", "Could not build finance summary: {0}"), ex.Message), Severity.Error);
}
finally
{
_analyzingFinance = false;
}
}
private void ClearCentralScope()
{
_centralLandFilter = null;
_centralTscFilter = null;
}
private static Severity MapSeverity(string severity) => severity switch
{
"Warning" => Severity.Warning,
"Error" => Severity.Error,
_ => Severity.Info
};
private static string BuildPeriodLabel(ManagementCockpitCentralResult result)
{
if (result.Summary.PeriodStart is null || result.Summary.PeriodEnd is null)
return "-";
return $"{result.Summary.PeriodStart.Value:dd.MM.yyyy} - {result.Summary.PeriodEnd.Value:dd.MM.yyyy}";
}
private static string FormatValue(decimal value, string currency)
=> string.IsNullOrWhiteSpace(currency) || currency == "-"
? value.ToString("N2")
: $"{value:N2} {currency}";
private static string FormatNullableValue(decimal? value, string currency)
=> value.HasValue ? FormatValue(value.Value, currency) : "-";
private static string FormatPercent(decimal? value)
=> value.HasValue ? $"{value.Value:N1}%" : "-";
private static string FormatDateTime(DateTime? value)
=> value.HasValue ? value.Value.ToLocalTime().ToString("dd.MM.yyyy HH:mm") : "-";
private static string FormatManualImportStatus(ManagementFinanceDataStatusRow row)
{
if (!string.Equals(row.SourceSystem, "MANUAL_EXCEL", StringComparison.OrdinalIgnoreCase))
return "-";
if (!string.IsNullOrWhiteSpace(row.ManualImportFilePath))
return row.ManualImportLastUploadedAtUtc.HasValue
? $"{System.IO.Path.GetFileName(row.ManualImportFilePath)} / {FormatDateTime(row.ManualImportLastUploadedAtUtc)}"
: System.IO.Path.GetFileName(row.ManualImportFilePath);
return "kein Pfad";
}
private static Color StatusColor(string status) => status switch
{
"OK" => Color.Success,
"Pruefen" => Color.Warning,
_ => Color.Default
};
private static Color SeverityColor(string severity) => severity switch
{
"Warning" => Color.Warning,
"Error" => Color.Error,
_ => Color.Info
};
private static Color ProductAssignmentColor(string status) => status switch
{
"Zugeordnet" => Color.Success,
"Nicht zugeordnet" => Color.Warning,
"Nicht im TR-AG-Stamm" => Color.Error,
"Material fehlt" => Color.Default,
_ => Color.Info
};
private static string BuildCodeText(string code, string text)
{
if (string.IsNullOrWhiteSpace(code))
return string.IsNullOrWhiteSpace(text) ? "-" : text;
return string.IsNullOrWhiteSpace(text) ? code : $"{code} - {text}";
}
private void SetSelectedCentralAdditionalValueFields(IEnumerable values)
{
_selectedCentralAdditionalValueFields = values
.Where(value => !string.IsNullOrWhiteSpace(value))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
}
private static string FormatAdditionalValue(ManagementCockpitTimeValueRow row, string fieldKey)
{
if (!row.AdditionalValues.TryGetValue(fieldKey, out var value))
return "-";
var formattedValue = FormatValue(value.Value, value.Currency);
return value.MissingExchangeRateCount == 0
? formattedValue
: $"{formattedValue} / {value.MissingExchangeRateCount} ohne Kurs";
}
private sealed record CurrencySelectOption(string Key, string Label);
}
@code {
private string T(string german, string english) => UiText.Text(german, english);
}