Add division finance grouping controls
This commit is contained in:
@@ -96,7 +96,7 @@
|
|||||||
</HeaderContent>
|
</HeaderContent>
|
||||||
<RowTemplate>
|
<RowTemplate>
|
||||||
<MudTd>@context.Year</MudTd>
|
<MudTd>@context.Year</MudTd>
|
||||||
<MudTd>@context.CountryKey</MudTd>
|
<MudTd>@FormatCountryWithFlag(context.CountryKey)</MudTd>
|
||||||
<MudTd>@context.Currency</MudTd>
|
<MudTd>@context.Currency</MudTd>
|
||||||
<MudTd>@FormatValue(context.NetSalesActual, context.Currency)</MudTd>
|
<MudTd>@FormatValue(context.NetSalesActual, context.Currency)</MudTd>
|
||||||
<MudTd>@context.IncludedRows.ToString("N0")</MudTd>
|
<MudTd>@context.IncludedRows.ToString("N0")</MudTd>
|
||||||
@@ -158,7 +158,7 @@
|
|||||||
</HeaderContent>
|
</HeaderContent>
|
||||||
<RowTemplate>
|
<RowTemplate>
|
||||||
<MudTd><MudChip T="string" Size="Size.Small" Color="@StatusColor(context.Status)" Variant="Variant.Outlined">@context.Status</MudChip></MudTd>
|
<MudTd><MudChip T="string" Size="Size.Small" Color="@StatusColor(context.Status)" Variant="Variant.Outlined">@context.Status</MudChip></MudTd>
|
||||||
<MudTd>@context.CountryKey</MudTd>
|
<MudTd>@FormatCountryWithFlag(context.CountryKey)</MudTd>
|
||||||
<MudTd>@context.Tscs</MudTd>
|
<MudTd>@context.Tscs</MudTd>
|
||||||
<MudTd>@context.SourceSystems</MudTd>
|
<MudTd>@context.SourceSystems</MudTd>
|
||||||
<MudTd>@context.Currency</MudTd>
|
<MudTd>@context.Currency</MudTd>
|
||||||
@@ -220,7 +220,7 @@
|
|||||||
</HeaderContent>
|
</HeaderContent>
|
||||||
<RowTemplate>
|
<RowTemplate>
|
||||||
<MudTd><MudChip T="string" Size="Size.Small" Color="@StatusColor(context.Status)" Variant="Variant.Outlined">@context.Status</MudChip></MudTd>
|
<MudTd><MudChip T="string" Size="Size.Small" Color="@StatusColor(context.Status)" Variant="Variant.Outlined">@context.Status</MudChip></MudTd>
|
||||||
<MudTd>@context.CountryKey</MudTd>
|
<MudTd>@FormatCountryWithFlag(context.CountryKey)</MudTd>
|
||||||
<MudTd>@context.Currency</MudTd>
|
<MudTd>@context.Currency</MudTd>
|
||||||
<MudTd>@FormatValue(context.NetSalesActual, context.Currency)</MudTd>
|
<MudTd>@FormatValue(context.NetSalesActual, context.Currency)</MudTd>
|
||||||
<MudTd>@FormatNullableValue(context.ReferenceValue, context.Currency)</MudTd>
|
<MudTd>@FormatNullableValue(context.ReferenceValue, context.Currency)</MudTd>
|
||||||
@@ -251,7 +251,7 @@
|
|||||||
<MudTh>@T("Grund", "Reason")</MudTh>
|
<MudTh>@T("Grund", "Reason")</MudTh>
|
||||||
</HeaderContent>
|
</HeaderContent>
|
||||||
<RowTemplate>
|
<RowTemplate>
|
||||||
<MudTd>@context.CountryKey</MudTd>
|
<MudTd>@FormatCountryWithFlag(context.CountryKey)</MudTd>
|
||||||
<MudTd>@context.Tsc</MudTd>
|
<MudTd>@context.Tsc</MudTd>
|
||||||
<MudTd>@context.InvoiceNumber</MudTd>
|
<MudTd>@context.InvoiceNumber</MudTd>
|
||||||
<MudTd>@context.DocumentType</MudTd>
|
<MudTd>@context.DocumentType</MudTd>
|
||||||
@@ -319,12 +319,39 @@
|
|||||||
</MudGrid>
|
</MudGrid>
|
||||||
|
|
||||||
<MudPaper Class="pa-4 mb-4" Elevation="1">
|
<MudPaper Class="pa-4 mb-4" Elevation="1">
|
||||||
<MudText Typo="Typo.h6" Class="mb-2">@T("Umsatz nach Produktsparte", "Sales by product division")</MudText>
|
<MudGrid Class="mb-2">
|
||||||
<MudTable Items="_financeResult.ProductDivisionFinanceRows" Dense Hover Striped>
|
<MudItem xs="12" md="6">
|
||||||
|
<MudText Typo="Typo.h6">@T("Umsatz nach Produktsparte", "Sales by product division")</MudText>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudSelect T="string" @bind-Value="_productFinanceGroupLevel" Label="@T("Gruppierung", "Grouping")" Dense>
|
||||||
|
@foreach (var option in _productFinanceGroupingOptions)
|
||||||
|
{
|
||||||
|
<MudSelectItem Value="@option.Key">@T(option.GermanLabel, option.EnglishLabel)</MudSelectItem>
|
||||||
|
}
|
||||||
|
</MudSelect>
|
||||||
|
</MudItem>
|
||||||
|
<MudItem xs="12" sm="6" md="3">
|
||||||
|
<MudButton Variant="@(_limitProductFinanceTop10 ? Variant.Filled : Variant.Outlined)"
|
||||||
|
Color="Color.Primary"
|
||||||
|
StartIcon="@Icons.Material.Filled.FilterAlt"
|
||||||
|
OnClick="ToggleProductFinanceTop10"
|
||||||
|
FullWidth>
|
||||||
|
@T("Top 10 anzeigen", "Show top 10")
|
||||||
|
</MudButton>
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
<MudTable Items="BuildProductFinanceRows()" Dense Hover Striped>
|
||||||
<HeaderContent>
|
<HeaderContent>
|
||||||
<MudTh>@T("Produktsparte", "Product division")</MudTh>
|
<MudTh>@T("Produktsparte", "Product division")</MudTh>
|
||||||
|
@if (ShowProductFamilyColumn)
|
||||||
|
{
|
||||||
<MudTh>@T("Produktfamilie", "Product family")</MudTh>
|
<MudTh>@T("Produktfamilie", "Product family")</MudTh>
|
||||||
|
}
|
||||||
|
@if (ShowProductHierarchyColumn)
|
||||||
|
{
|
||||||
<MudTh>PAPH1</MudTh>
|
<MudTh>PAPH1</MudTh>
|
||||||
|
}
|
||||||
<MudTh>@T("Umsatz", "Sales")</MudTh>
|
<MudTh>@T("Umsatz", "Sales")</MudTh>
|
||||||
<MudTh>@T("Anteil", "Share")</MudTh>
|
<MudTh>@T("Anteil", "Share")</MudTh>
|
||||||
<MudTh>@T("Materialien", "Materials")</MudTh>
|
<MudTh>@T("Materialien", "Materials")</MudTh>
|
||||||
@@ -333,13 +360,19 @@
|
|||||||
</HeaderContent>
|
</HeaderContent>
|
||||||
<RowTemplate>
|
<RowTemplate>
|
||||||
<MudTd>@BuildCodeText(context.ProductDivisionCode, context.ProductDivisionText)</MudTd>
|
<MudTd>@BuildCodeText(context.ProductDivisionCode, context.ProductDivisionText)</MudTd>
|
||||||
|
@if (ShowProductFamilyColumn)
|
||||||
|
{
|
||||||
<MudTd>@BuildCodeText(context.ProductFamilyCode, context.ProductFamilyText)</MudTd>
|
<MudTd>@BuildCodeText(context.ProductFamilyCode, context.ProductFamilyText)</MudTd>
|
||||||
|
}
|
||||||
|
@if (ShowProductHierarchyColumn)
|
||||||
|
{
|
||||||
<MudTd>@BuildCodeText(context.ProductHierarchyCode, context.ProductHierarchyText)</MudTd>
|
<MudTd>@BuildCodeText(context.ProductHierarchyCode, context.ProductHierarchyText)</MudTd>
|
||||||
|
}
|
||||||
<MudTd>@FormatValue(context.NetSalesActual, context.Currency)</MudTd>
|
<MudTd>@FormatValue(context.NetSalesActual, context.Currency)</MudTd>
|
||||||
<MudTd>@FormatPercent(context.SharePercent)</MudTd>
|
<MudTd>@FormatPercent(context.SharePercent)</MudTd>
|
||||||
<MudTd>@context.MaterialCount.ToString("N0")</MudTd>
|
<MudTd>@context.MaterialCount.ToString("N0")</MudTd>
|
||||||
<MudTd>@context.RowCount.ToString("N0")</MudTd>
|
<MudTd>@context.RowCount.ToString("N0")</MudTd>
|
||||||
<MudTd>@context.Countries</MudTd>
|
<MudTd>@FormatCountriesWithFlags(context.Countries)</MudTd>
|
||||||
</RowTemplate>
|
</RowTemplate>
|
||||||
<NoRecordsContent>
|
<NoRecordsContent>
|
||||||
<MudText Typo="Typo.body2">@T("Keine zugeordneten Spartenumsaetze fuer diese Filter.", "No assigned division sales for these filters.")</MudText>
|
<MudText Typo="Typo.body2">@T("Keine zugeordneten Spartenumsaetze fuer diese Filter.", "No assigned division sales for these filters.")</MudText>
|
||||||
@@ -361,7 +394,7 @@
|
|||||||
<MudTh>@T("Abdeckung", "Coverage")</MudTh>
|
<MudTh>@T("Abdeckung", "Coverage")</MudTh>
|
||||||
</HeaderContent>
|
</HeaderContent>
|
||||||
<RowTemplate>
|
<RowTemplate>
|
||||||
<MudTd>@context.CountryKey</MudTd>
|
<MudTd>@FormatCountryWithFlag(context.CountryKey)</MudTd>
|
||||||
<MudTd>@context.Tsc</MudTd>
|
<MudTd>@context.Tsc</MudTd>
|
||||||
<MudTd>@FormatValue(context.TotalValue, context.Currency)</MudTd>
|
<MudTd>@FormatValue(context.TotalValue, context.Currency)</MudTd>
|
||||||
<MudTd>@FormatValue(context.AssignedValue, context.Currency)</MudTd>
|
<MudTd>@FormatValue(context.AssignedValue, context.Currency)</MudTd>
|
||||||
@@ -435,7 +468,7 @@
|
|||||||
<MudTh>@T("Trefferquote", "Match rate")</MudTh>
|
<MudTh>@T("Trefferquote", "Match rate")</MudTh>
|
||||||
</HeaderContent>
|
</HeaderContent>
|
||||||
<RowTemplate>
|
<RowTemplate>
|
||||||
<MudTd>@context.CountryKey</MudTd>
|
<MudTd>@FormatCountryWithFlag(context.CountryKey)</MudTd>
|
||||||
<MudTd>@context.Tsc</MudTd>
|
<MudTd>@context.Tsc</MudTd>
|
||||||
<MudTd>@context.DistinctMaterialCount.ToString("N0")</MudTd>
|
<MudTd>@context.DistinctMaterialCount.ToString("N0")</MudTd>
|
||||||
<MudTd>@context.MatchedMaterialCount.ToString("N0")</MudTd>
|
<MudTd>@context.MatchedMaterialCount.ToString("N0")</MudTd>
|
||||||
@@ -468,7 +501,7 @@
|
|||||||
</HeaderContent>
|
</HeaderContent>
|
||||||
<RowTemplate>
|
<RowTemplate>
|
||||||
<MudTd><MudChip T="string" Size="Size.Small" Color="@ProductAssignmentColor(context.Status)" Variant="Variant.Outlined">@context.Status</MudChip></MudTd>
|
<MudTd><MudChip T="string" Size="Size.Small" Color="@ProductAssignmentColor(context.Status)" Variant="Variant.Outlined">@context.Status</MudChip></MudTd>
|
||||||
<MudTd>@context.CountryKey</MudTd>
|
<MudTd>@FormatCountryWithFlag(context.CountryKey)</MudTd>
|
||||||
<MudTd>@context.Tsc</MudTd>
|
<MudTd>@context.Tsc</MudTd>
|
||||||
<MudTd>@context.Material</MudTd>
|
<MudTd>@context.Material</MudTd>
|
||||||
<MudTd>@context.ArticleName</MudTd>
|
<MudTd>@context.ArticleName</MudTd>
|
||||||
@@ -844,6 +877,12 @@
|
|||||||
new(ManagementCockpitCurrencyOptions.Usd, "USD"),
|
new(ManagementCockpitCurrencyOptions.Usd, "USD"),
|
||||||
new(ManagementCockpitCurrencyOptions.Native, "Original")
|
new(ManagementCockpitCurrencyOptions.Native, "Original")
|
||||||
];
|
];
|
||||||
|
private readonly List<ProductFinanceGroupingOption> _productFinanceGroupingOptions =
|
||||||
|
[
|
||||||
|
new(ProductFinanceGroupLevels.Hierarchy, "PAPH1 Detail", "PAPH1 detail"),
|
||||||
|
new(ProductFinanceGroupLevels.Family, "Produktfamilie", "Product family"),
|
||||||
|
new(ProductFinanceGroupLevels.Division, "Produktsparte", "Product division")
|
||||||
|
];
|
||||||
private string? _selectedFilePath;
|
private string? _selectedFilePath;
|
||||||
private ManagementCockpitResult? _result;
|
private ManagementCockpitResult? _result;
|
||||||
private ManagementCockpitCentralResult? _centralResult;
|
private ManagementCockpitCentralResult? _centralResult;
|
||||||
@@ -866,6 +905,12 @@
|
|||||||
private bool _analyzingFinance;
|
private bool _analyzingFinance;
|
||||||
private int _activeFinanceTabIndex;
|
private int _activeFinanceTabIndex;
|
||||||
private int _activeDivisionTabIndex;
|
private int _activeDivisionTabIndex;
|
||||||
|
private string _productFinanceGroupLevel = ProductFinanceGroupLevels.Hierarchy;
|
||||||
|
private bool _limitProductFinanceTop10;
|
||||||
|
|
||||||
|
private bool ShowProductFamilyColumn => _productFinanceGroupLevel != ProductFinanceGroupLevels.Division;
|
||||||
|
|
||||||
|
private bool ShowProductHierarchyColumn => _productFinanceGroupLevel == ProductFinanceGroupLevels.Hierarchy;
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
@@ -998,6 +1043,84 @@
|
|||||||
_centralTscFilter = null;
|
_centralTscFilter = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ToggleProductFinanceTop10()
|
||||||
|
{
|
||||||
|
_limitProductFinanceTop10 = !_limitProductFinanceTop10;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IReadOnlyList<ManagementProductDivisionFinanceRow> BuildProductFinanceRows()
|
||||||
|
{
|
||||||
|
if (_financeResult is null)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
var sourceRows = _financeResult.ProductDivisionFinanceRows;
|
||||||
|
var totalsByCurrency = sourceRows
|
||||||
|
.GroupBy(row => row.Currency, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToDictionary(group => group.Key, group => group.Sum(row => row.NetSalesActual), StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var rows = sourceRows
|
||||||
|
.GroupBy(row => BuildProductFinanceGroupKey(row))
|
||||||
|
.Select(group =>
|
||||||
|
{
|
||||||
|
var value = group.Sum(row => row.NetSalesActual);
|
||||||
|
totalsByCurrency.TryGetValue(group.Key.Currency, out var total);
|
||||||
|
|
||||||
|
return new ManagementProductDivisionFinanceRow
|
||||||
|
{
|
||||||
|
ProductDivisionCode = group.Key.ProductDivisionCode,
|
||||||
|
ProductDivisionText = group.Key.ProductDivisionText,
|
||||||
|
ProductFamilyCode = group.Key.ProductFamilyCode,
|
||||||
|
ProductFamilyText = group.Key.ProductFamilyText,
|
||||||
|
ProductHierarchyCode = group.Key.ProductHierarchyCode,
|
||||||
|
ProductHierarchyText = group.Key.ProductHierarchyText,
|
||||||
|
Currency = group.Key.Currency,
|
||||||
|
NetSalesActual = value,
|
||||||
|
SharePercent = PercentOf(value, total),
|
||||||
|
MaterialCount = group.Sum(row => row.MaterialCount),
|
||||||
|
RowCount = group.Sum(row => row.RowCount),
|
||||||
|
Countries = JoinCountries(group.Select(row => row.Countries))
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.OrderByDescending(row => Math.Abs(row.NetSalesActual))
|
||||||
|
.ThenBy(row => row.ProductDivisionCode, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ThenBy(row => row.ProductFamilyCode, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ThenBy(row => row.ProductHierarchyCode, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return _limitProductFinanceTop10 ? rows.Take(10).ToList() : rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProductFinanceGroupKey BuildProductFinanceGroupKey(ManagementProductDivisionFinanceRow row)
|
||||||
|
{
|
||||||
|
return _productFinanceGroupLevel switch
|
||||||
|
{
|
||||||
|
ProductFinanceGroupLevels.Division => new ProductFinanceGroupKey(
|
||||||
|
row.ProductDivisionCode,
|
||||||
|
row.ProductDivisionText,
|
||||||
|
string.Empty,
|
||||||
|
string.Empty,
|
||||||
|
string.Empty,
|
||||||
|
string.Empty,
|
||||||
|
row.Currency),
|
||||||
|
ProductFinanceGroupLevels.Family => new ProductFinanceGroupKey(
|
||||||
|
row.ProductDivisionCode,
|
||||||
|
row.ProductDivisionText,
|
||||||
|
row.ProductFamilyCode,
|
||||||
|
row.ProductFamilyText,
|
||||||
|
string.Empty,
|
||||||
|
string.Empty,
|
||||||
|
row.Currency),
|
||||||
|
_ => new ProductFinanceGroupKey(
|
||||||
|
row.ProductDivisionCode,
|
||||||
|
row.ProductDivisionText,
|
||||||
|
row.ProductFamilyCode,
|
||||||
|
row.ProductFamilyText,
|
||||||
|
row.ProductHierarchyCode,
|
||||||
|
row.ProductHierarchyText,
|
||||||
|
row.Currency)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static Severity MapSeverity(string severity) => severity switch
|
private static Severity MapSeverity(string severity) => severity switch
|
||||||
{
|
{
|
||||||
"Warning" => Severity.Warning,
|
"Warning" => Severity.Warning,
|
||||||
@@ -1024,6 +1147,9 @@
|
|||||||
private static string FormatPercent(decimal? value)
|
private static string FormatPercent(decimal? value)
|
||||||
=> value.HasValue ? $"{value.Value:N1}%" : "-";
|
=> value.HasValue ? $"{value.Value:N1}%" : "-";
|
||||||
|
|
||||||
|
private static decimal PercentOf(decimal value, decimal total)
|
||||||
|
=> total == 0m ? 0m : value * 100m / total;
|
||||||
|
|
||||||
private static string FormatDateTime(DateTime? value)
|
private static string FormatDateTime(DateTime? value)
|
||||||
=> value.HasValue ? value.Value.ToLocalTime().ToString("dd.MM.yyyy HH:mm") : "-";
|
=> value.HasValue ? value.Value.ToLocalTime().ToString("dd.MM.yyyy HH:mm") : "-";
|
||||||
|
|
||||||
@@ -1071,6 +1197,36 @@
|
|||||||
return string.IsNullOrWhiteSpace(text) ? code : $"{code} - {text}";
|
return string.IsNullOrWhiteSpace(text) ? code : $"{code} - {text}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string JoinCountries(IEnumerable<string> countryValues)
|
||||||
|
{
|
||||||
|
var countries = countryValues
|
||||||
|
.SelectMany(value => value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
|
||||||
|
.Where(value => !string.IsNullOrWhiteSpace(value))
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.OrderBy(value => value, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Select(FormatCountryWithFlag);
|
||||||
|
|
||||||
|
return string.Join(", ", countries);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatCountriesWithFlags(string countries)
|
||||||
|
=> string.IsNullOrWhiteSpace(countries)
|
||||||
|
? "-"
|
||||||
|
: JoinCountries([countries]);
|
||||||
|
|
||||||
|
private static string FormatCountryWithFlag(string country)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(country))
|
||||||
|
return "-";
|
||||||
|
|
||||||
|
var normalized = country.Trim().ToUpperInvariant();
|
||||||
|
if (normalized.Length != 2 || normalized.Any(character => character is < 'A' or > 'Z'))
|
||||||
|
return country;
|
||||||
|
|
||||||
|
var flag = string.Concat(normalized.Select(character => char.ConvertFromUtf32(0x1F1E6 + character - 'A')));
|
||||||
|
return $"{flag} {normalized}";
|
||||||
|
}
|
||||||
|
|
||||||
private void SetSelectedCentralAdditionalValueFields(IEnumerable<string> values)
|
private void SetSelectedCentralAdditionalValueFields(IEnumerable<string> values)
|
||||||
{
|
{
|
||||||
_selectedCentralAdditionalValueFields = values
|
_selectedCentralAdditionalValueFields = values
|
||||||
@@ -1090,9 +1246,25 @@
|
|||||||
: $"{formattedValue} / {value.MissingExchangeRateCount} ohne Kurs";
|
: $"{formattedValue} / {value.MissingExchangeRateCount} ohne Kurs";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string T(string german, string english) => UiText.Text(german, english);
|
||||||
|
|
||||||
|
private static class ProductFinanceGroupLevels
|
||||||
|
{
|
||||||
|
public const string Hierarchy = "hierarchy";
|
||||||
|
public const string Family = "family";
|
||||||
|
public const string Division = "division";
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed record ProductFinanceGroupingOption(string Key, string GermanLabel, string EnglishLabel);
|
||||||
|
|
||||||
|
private sealed record ProductFinanceGroupKey(
|
||||||
|
string ProductDivisionCode,
|
||||||
|
string ProductDivisionText,
|
||||||
|
string ProductFamilyCode,
|
||||||
|
string ProductFamilyText,
|
||||||
|
string ProductHierarchyCode,
|
||||||
|
string ProductHierarchyText,
|
||||||
|
string Currency);
|
||||||
|
|
||||||
private sealed record CurrencySelectOption(string Key, string Label);
|
private sealed record CurrencySelectOption(string Key, string Label);
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
|
||||||
private string T(string german, string english) => UiText.Text(german, english);
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user