Expand finance 3D indicators

This commit is contained in:
2026-06-04 14:22:22 +02:00
parent 1049216049
commit e33a2fd9e6
4 changed files with 124 additions and 36 deletions
@@ -1086,10 +1086,21 @@
]; ];
private readonly List<Finance3dIndicatorOption> _finance3dIndicatorOptions = private readonly List<Finance3dIndicatorOption> _finance3dIndicatorOptions =
[ [
new(Finance3dIndicators.Actual, "Net Sales Actual", "Net sales actual"), new(Finance3dIndicators.Actual, "Ist Umsatz inkl. IC", "Actual sales incl. IC"),
new(Finance3dIndicators.ActualExcludingIntercompany, "Ist Umsatz ohne IC", "Actual sales excl. IC"),
new(Finance3dIndicators.IntercompanyValue, "Intercompany Wert", "Intercompany value"),
new(Finance3dIndicators.IntercompanyShare, "Intercompany Anteil %", "Intercompany share %"),
new(Finance3dIndicators.Quantity, "Menge", "Quantity"),
new(Finance3dIndicators.CreditValue, "Gutschriften Wert", "Credit-note value"),
new(Finance3dIndicators.CreditRows, "Gutschriften Zeilen", "Credit-note rows"),
new(Finance3dIndicators.TotalRows, "Alle Zeilen", "All rows"),
new(Finance3dIndicators.IncludedRows, "Enthaltene Zeilen", "Included rows"), new(Finance3dIndicators.IncludedRows, "Enthaltene Zeilen", "Included rows"),
new(Finance3dIndicators.ExcludedRows, "Ausgeschlossene Zeilen", "Excluded rows"), new(Finance3dIndicators.ExcludedRows, "Ausgeschlossene Zeilen", "Excluded rows"),
new(Finance3dIndicators.Deviation, "Soll/Ist Differenz Filterjahr", "Actual/reference difference filter year") new(Finance3dIndicators.IncludeRate, "Include Quote %", "Include rate %"),
new(Finance3dIndicators.ExcludeRate, "Exclude Quote %", "Exclude rate %"),
new(Finance3dIndicators.ReferenceValue, "Sollwert Filterjahr", "Reference value filter year"),
new(Finance3dIndicators.Deviation, "Soll/Ist Differenz Filterjahr", "Actual/reference difference filter year"),
new(Finance3dIndicators.DeviationPercent, "Soll/Ist Abweichung % Filterjahr", "Actual/reference deviation % filter year")
]; ];
private readonly List<Finance3dChartTypeOption> _finance3dChartTypeOptions = private readonly List<Finance3dChartTypeOption> _finance3dChartTypeOptions =
[ [
@@ -1130,7 +1141,13 @@
private bool _finance3dNeedsRender; private bool _finance3dNeedsRender;
private bool ShowProductFamilyColumn => _productFinanceGroupLevel != ProductFinanceGroupLevels.Division; private bool ShowProductFamilyColumn => _productFinanceGroupLevel != ProductFinanceGroupLevels.Division;
private bool Finance3dScenarioAffectsValue => _finance3dIndicator is Finance3dIndicators.Actual or Finance3dIndicators.Deviation; private bool Finance3dScenarioAffectsValue => _finance3dIndicator is
Finance3dIndicators.Actual or
Finance3dIndicators.ActualExcludingIntercompany or
Finance3dIndicators.IntercompanyValue or
Finance3dIndicators.CreditValue or
Finance3dIndicators.ReferenceValue or
Finance3dIndicators.Deviation;
private decimal Finance3dBaseTotal => CalculateFinance3dBaseTotal(); private decimal Finance3dBaseTotal => CalculateFinance3dBaseTotal();
private decimal Finance3dScenarioTotal => Finance3dScenarioAffectsValue private decimal Finance3dScenarioTotal => Finance3dScenarioAffectsValue
? Finance3dBaseTotal * (decimal)_finance3dScenarioFactor ? Finance3dBaseTotal * (decimal)_finance3dScenarioFactor
@@ -1366,14 +1383,14 @@
if (_financeResult is null) if (_financeResult is null)
return []; return [];
var deviationsByKey = _financeResult.CountryRows var countryRowsByKey = _financeResult.CountryRows
.Where(row => row.Difference.HasValue)
.GroupBy(row => $"{row.Year}|{row.CountryKey}", StringComparer.OrdinalIgnoreCase) .GroupBy(row => $"{row.Year}|{row.CountryKey}", StringComparer.OrdinalIgnoreCase)
.ToDictionary( .ToDictionary(
group => group.Key, group => group.Key,
group => group.Sum(row => Math.Abs(row.Difference!.Value))); group => group.ToList(),
StringComparer.OrdinalIgnoreCase);
var sourceRows = _finance3dIndicator == Finance3dIndicators.Deviation var sourceRows = IsFinance3dReferenceYearIndicator(_finance3dIndicator)
? _financeResult.Rows ? _financeResult.Rows
: (_financeResult.YearCountryRows.Count > 0 ? _financeResult.YearCountryRows : _financeResult.Rows); : (_financeResult.YearCountryRows.Count > 0 ? _financeResult.YearCountryRows : _financeResult.Rows);
@@ -1382,14 +1399,8 @@
.ThenBy(row => row.Year) .ThenBy(row => row.Year)
.Select(row => .Select(row =>
{ {
deviationsByKey.TryGetValue($"{row.Year}|{row.CountryKey}", out var deviation); countryRowsByKey.TryGetValue($"{row.Year}|{row.CountryKey}", out var countryRows);
var value = _finance3dIndicator switch var value = ResolveFinance3dRowValue(row, countryRows);
{
Finance3dIndicators.IncludedRows => row.IncludedRows,
Finance3dIndicators.ExcludedRows => row.ExcludedRows,
Finance3dIndicators.Deviation => deviation,
_ => Math.Abs(row.NetSalesActual)
};
return new return new
{ {
country = row.CountryKey, country = row.CountryKey,
@@ -1407,23 +1418,67 @@
if (_financeResult is null) if (_financeResult is null)
return 0m; return 0m;
var sourceRows = _finance3dIndicator == Finance3dIndicators.Deviation var countryRowsByKey = _financeResult.CountryRows
.GroupBy(row => $"{row.Year}|{row.CountryKey}", StringComparer.OrdinalIgnoreCase)
.ToDictionary(
group => group.Key,
group => group.ToList(),
StringComparer.OrdinalIgnoreCase);
var sourceRows = IsFinance3dReferenceYearIndicator(_finance3dIndicator)
? _financeResult.Rows ? _financeResult.Rows
: (_financeResult.YearCountryRows.Count > 0 ? _financeResult.YearCountryRows : _financeResult.Rows); : (_financeResult.YearCountryRows.Count > 0 ? _financeResult.YearCountryRows : _financeResult.Rows);
if (_finance3dIndicator == Finance3dIndicators.Deviation) var values = sourceRows
.Select(row =>
{
countryRowsByKey.TryGetValue($"{row.Year}|{row.CountryKey}", out var countryRows);
return ResolveFinance3dRowValue(row, countryRows);
})
.ToList();
if (IsFinance3dPercentIndicator(_finance3dIndicator))
{ {
return _financeResult.CountryRows var nonZeroValues = values.Where(value => value != 0m).ToList();
.Where(row => row.Difference.HasValue) return nonZeroValues.Count == 0 ? 0m : nonZeroValues.Average();
.Sum(row => Math.Abs(row.Difference!.Value));
} }
return _finance3dIndicator switch return values.Sum();
}
private decimal ResolveFinance3dRowValue(ManagementFinanceSummaryRow row, IReadOnlyCollection<ManagementFinanceCountryStatusRow>? countryRows)
=> _finance3dIndicator switch
{ {
Finance3dIndicators.IncludedRows => sourceRows.Sum(row => row.IncludedRows), Finance3dIndicators.ActualExcludingIntercompany => Math.Abs(row.NetSalesActualExcludingIntercompany),
Finance3dIndicators.ExcludedRows => sourceRows.Sum(row => row.ExcludedRows), Finance3dIndicators.IntercompanyValue => Math.Abs(row.IntercompanyValue),
_ => sourceRows.Sum(row => Math.Abs(row.NetSalesActual)) Finance3dIndicators.IntercompanyShare => Math.Abs(row.IntercompanySharePercent),
Finance3dIndicators.Quantity => Math.Abs(row.Quantity),
Finance3dIndicators.CreditValue => Math.Abs(row.CreditValue),
Finance3dIndicators.CreditRows => row.CreditRows,
Finance3dIndicators.TotalRows => row.TotalRows,
Finance3dIndicators.IncludedRows => row.IncludedRows,
Finance3dIndicators.ExcludedRows => row.ExcludedRows,
Finance3dIndicators.IncludeRate => row.IncludeRatePercent,
Finance3dIndicators.ExcludeRate => row.ExcludeRatePercent,
Finance3dIndicators.ReferenceValue => Math.Abs(countryRows?.Sum(item => item.ReferenceValue ?? 0m) ?? 0m),
Finance3dIndicators.Deviation => Math.Abs(countryRows?.Where(item => item.Difference.HasValue).Sum(item => item.Difference!.Value) ?? 0m),
Finance3dIndicators.DeviationPercent => Math.Abs(AverageNullablePercent(countryRows?.Select(item => item.DifferencePercent))),
_ => Math.Abs(row.NetSalesActual)
}; };
private static bool IsFinance3dReferenceYearIndicator(string indicator)
=> indicator is Finance3dIndicators.ReferenceValue or Finance3dIndicators.Deviation or Finance3dIndicators.DeviationPercent;
private static bool IsFinance3dPercentIndicator(string indicator)
=> indicator is Finance3dIndicators.IntercompanyShare or Finance3dIndicators.IncludeRate or Finance3dIndicators.ExcludeRate or Finance3dIndicators.DeviationPercent;
private static decimal AverageNullablePercent(IEnumerable<decimal?>? values)
{
if (values is null)
return 0m;
var actualValues = values.Where(value => value.HasValue).Select(value => value!.Value).ToList();
return actualValues.Count == 0 ? 0m : actualValues.Average();
} }
private string FormatFinance3dValue(decimal value) private string FormatFinance3dValue(decimal value)
@@ -1740,9 +1795,20 @@
private static class Finance3dIndicators private static class Finance3dIndicators
{ {
public const string Actual = "actual"; public const string Actual = "actual";
public const string ActualExcludingIntercompany = "actualExcludingIntercompany";
public const string IntercompanyValue = "intercompanyValue";
public const string IntercompanyShare = "intercompanyShare";
public const string Quantity = "quantity";
public const string CreditValue = "creditValue";
public const string CreditRows = "creditRows";
public const string TotalRows = "totalRows";
public const string IncludedRows = "includedRows"; public const string IncludedRows = "includedRows";
public const string ExcludedRows = "excludedRows"; public const string ExcludedRows = "excludedRows";
public const string IncludeRate = "includeRate";
public const string ExcludeRate = "excludeRate";
public const string ReferenceValue = "referenceValue";
public const string Deviation = "deviation"; public const string Deviation = "deviation";
public const string DeviationPercent = "deviationPercent";
} }
private static class Finance3dChartTypes private static class Finance3dChartTypes
@@ -171,15 +171,22 @@ public class ManagementFinanceSummaryRow
public string Currency { get; set; } = string.Empty; public string Currency { get; set; } = string.Empty;
public int IncludedRows { get; set; } public int IncludedRows { get; set; }
public int ExcludedRows { get; set; } public int ExcludedRows { get; set; }
public int TotalRows { get; set; }
public decimal NetSalesActual { get; set; } public decimal NetSalesActual { get; set; }
public decimal NetSalesActualExcludingIntercompany { get; set; }
public decimal IntercompanyValue { get; set; }
public decimal IntercompanySharePercent { get; set; }
public decimal Quantity { get; set; }
public decimal CreditValue { get; set; }
public int CreditRows { get; set; }
public decimal IncludeRatePercent { get; set; }
public decimal ExcludeRatePercent { get; set; }
} }
public class ManagementFinanceCountryStatusRow : ManagementFinanceSummaryRow public class ManagementFinanceCountryStatusRow : ManagementFinanceSummaryRow
{ {
public string SourceSystems { get; set; } = string.Empty; public string SourceSystems { get; set; } = string.Empty;
public string Tscs { get; set; } = string.Empty; public string Tscs { get; set; } = string.Empty;
public decimal IntercompanyValue { get; set; }
public decimal NetSalesActualExcludingIntercompany { get; set; }
public decimal? ReferenceValue { get; set; } public decimal? ReferenceValue { get; set; }
public decimal? Difference { get; set; } public decimal? Difference { get; set; }
public decimal? DifferencePercent { get; set; } public decimal? DifferencePercent { get; set; }
@@ -1045,14 +1045,31 @@ public class ManagementCockpitService : IManagementCockpitService
IEnumerable<FinanceAggregationRow> rows) IEnumerable<FinanceAggregationRow> rows)
{ {
var rowList = rows.ToList(); var rowList = rows.ToList();
var includedRows = rowList.Count(row => row.Include);
var excludedRows = rowList.Count(row => !row.Include);
var actual = rowList.Sum(row => row.Value);
var intercompanyValue = rowList.Where(row => row.IsIntercompany).Sum(row => row.Value);
var creditRows = rowList.Count(row => row.Value < 0m || row.RawSalesValue < 0m || LooksLikeCreditDocument(row.DocumentType, row.InvoiceNumber));
var creditValue = rowList
.Where(row => row.Value < 0m || row.RawSalesValue < 0m || LooksLikeCreditDocument(row.DocumentType, row.InvoiceNumber))
.Sum(row => Math.Abs(row.Value));
return new ManagementFinanceSummaryRow return new ManagementFinanceSummaryRow
{ {
Year = year, Year = year,
CountryKey = countryKey, CountryKey = countryKey,
Currency = currency, Currency = currency,
IncludedRows = rowList.Count(row => row.Include), IncludedRows = includedRows,
ExcludedRows = rowList.Count(row => !row.Include), ExcludedRows = excludedRows,
NetSalesActual = rowList.Sum(row => row.Value) TotalRows = rowList.Count,
NetSalesActual = actual,
NetSalesActualExcludingIntercompany = actual - intercompanyValue,
IntercompanyValue = intercompanyValue,
IntercompanySharePercent = actual == 0m ? 0m : intercompanyValue / actual * 100m,
Quantity = rowList.Sum(row => row.Quantity),
CreditValue = creditValue,
CreditRows = creditRows,
IncludeRatePercent = rowList.Count == 0 ? 0m : includedRows * 100m / rowList.Count,
ExcludeRatePercent = rowList.Count == 0 ? 0m : excludedRows * 100m / rowList.Count
}; };
} }
+5 -7
View File
@@ -42,7 +42,8 @@ body,
.finance-3d-surface { .finance-3d-surface {
width: 100%; width: 100%;
min-height: 560px; height: calc(100vh - 260px);
min-height: 680px;
overflow: hidden; overflow: hidden;
background: #f7f9fb; background: #f7f9fb;
} }
@@ -50,7 +51,7 @@ body,
.finance-3d-canvas { .finance-3d-canvas {
display: block; display: block;
width: 100%; width: 100%;
height: 560px; height: 100%;
touch-action: none; touch-action: none;
} }
@@ -73,10 +74,7 @@ body,
@media (max-width: 700px) { @media (max-width: 700px) {
.finance-3d-surface { .finance-3d-surface {
min-height: 420px; height: calc(100vh - 300px);
} min-height: 480px;
.finance-3d-canvas {
height: 420px;
} }
} }