Enhance management cockpit analysis
This commit is contained in:
@@ -122,6 +122,111 @@ public class ManagementCockpitServiceTests : IDisposable
|
||||
Assert.Equal(2, result.MonthlyTotals.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeCentralAsync_Can_Convert_Selected_Value_To_Eur()
|
||||
{
|
||||
await SeedRatesAsync(
|
||||
CreateRate("EUR", "CHF", 2m),
|
||||
CreateRate("EUR", "USD", 1.25m));
|
||||
await SeedCentralRowsAsync(
|
||||
CreateRow("SAP", "Schweiz", "TRCH", "INV-1", "CHF", 100m, new DateTime(2025, 1, 10)),
|
||||
CreateRow("SAP", "USA", "TRUS", "INV-2", "USD", 100m, new DateTime(2025, 1, 11)),
|
||||
CreateRow("SAP", "Deutschland", "TRDE", "INV-3", "EUR", 100m, new DateTime(2025, 1, 12)));
|
||||
|
||||
var result = await _service.AnalyzeCentralAsync(2025, null, new ManagementCockpitAnalysisOptions
|
||||
{
|
||||
ValueField = ManagementCockpitValueFieldKeys.SalesPriceValue,
|
||||
TargetCurrency = ManagementCockpitCurrencyOptions.Eur
|
||||
});
|
||||
|
||||
Assert.Equal("EUR", result.Summary.DisplayCurrency);
|
||||
Assert.Equal(230m, result.Summary.ValueTotal);
|
||||
Assert.Equal(0, result.Summary.MissingExchangeRateCount);
|
||||
|
||||
Assert.All(result.CountryTotals, row => Assert.Equal("EUR", row.Currency));
|
||||
Assert.Equal(50m, Assert.Single(result.CountryTotals, x => x.Label == "Schweiz").SalesValue);
|
||||
Assert.Equal(80m, Assert.Single(result.CountryTotals, x => x.Label == "USA").SalesValue);
|
||||
Assert.Equal(100m, Assert.Single(result.CountryTotals, x => x.Label == "Deutschland").SalesValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeCentralAsync_Caches_Exchange_Rates_Per_Currency_Target_And_Date()
|
||||
{
|
||||
var exchangeRates = new CountingCurrencyExchangeRateService();
|
||||
var service = new ManagementCockpitService(_dbFactory, exchangeRates);
|
||||
|
||||
await SeedCentralRowsAsync(
|
||||
CreateRow("SAP", "USA", "TRUS", "INV-1", "USD", 100m, new DateTime(2025, 1, 10), quantity: 2m, standardCost: 10m),
|
||||
CreateRow("SAP", "USA", "TRUS", "INV-2", "USD", 50m, new DateTime(2025, 1, 10), quantity: 3m, standardCost: 20m));
|
||||
|
||||
var result = await service.AnalyzeCentralAsync(2025, 1, new ManagementCockpitAnalysisOptions
|
||||
{
|
||||
ValueField = ManagementCockpitValueFieldKeys.SalesPriceValue,
|
||||
AdditionalValueFields = [ManagementCockpitValueFieldKeys.StandardCostTotal],
|
||||
TargetCurrency = ManagementCockpitCurrencyOptions.Eur
|
||||
});
|
||||
|
||||
Assert.Equal(300m, result.Summary.ValueTotal);
|
||||
Assert.Equal(160m, Assert.Single(result.MonthlyTotals).AdditionalValues[ManagementCockpitValueFieldKeys.StandardCostTotal].Value);
|
||||
Assert.Equal(1, exchangeRates.ResolveRateCallCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeCentralAsync_Can_Sum_Quantity_Without_Currency_Conversion()
|
||||
{
|
||||
await SeedCentralRowsAsync(
|
||||
CreateRow("SAP", "Schweiz", "TRCH", "INV-1", "CHF", 100m, new DateTime(2025, 1, 10), quantity: 2m),
|
||||
CreateRow("SAP", "USA", "TRUS", "INV-2", "USD", 100m, new DateTime(2025, 1, 11), quantity: 3m));
|
||||
|
||||
var result = await _service.AnalyzeCentralAsync(2025, null, new ManagementCockpitAnalysisOptions
|
||||
{
|
||||
ValueField = ManagementCockpitValueFieldKeys.Quantity,
|
||||
TargetCurrency = ManagementCockpitCurrencyOptions.Eur
|
||||
});
|
||||
|
||||
Assert.Equal(ManagementCockpitValueFieldKeys.Quantity, result.Summary.ValueFieldKey);
|
||||
Assert.Equal("-", result.Summary.DisplayCurrency);
|
||||
Assert.Equal(5m, result.Summary.ValueTotal);
|
||||
Assert.Equal(0, result.Summary.MissingExchangeRateCount);
|
||||
Assert.Equal(2m, Assert.Single(result.CountryTotals, x => x.Label == "Schweiz").SalesValue);
|
||||
Assert.Equal(3m, Assert.Single(result.CountryTotals, x => x.Label == "USA").SalesValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeCentralAsync_Adds_Selected_Additional_Value_Fields_To_Time_Rows()
|
||||
{
|
||||
await SeedCentralRowsAsync(
|
||||
CreateRow("SAP", "Deutschland", "TRDE", "INV-1", "EUR", 100m, new DateTime(2025, 1, 10), quantity: 2m, standardCost: 5m),
|
||||
CreateRow("SAP", "Deutschland", "TRDE", "INV-2", "EUR", 50m, new DateTime(2025, 2, 10), quantity: 3m, standardCost: 7m));
|
||||
|
||||
var result = await _service.AnalyzeCentralAsync(2025, null, new ManagementCockpitAnalysisOptions
|
||||
{
|
||||
ValueField = ManagementCockpitValueFieldKeys.SalesPriceValue,
|
||||
AdditionalValueFields =
|
||||
[
|
||||
ManagementCockpitValueFieldKeys.Quantity,
|
||||
ManagementCockpitValueFieldKeys.StandardCostTotal
|
||||
],
|
||||
TargetCurrency = ManagementCockpitCurrencyOptions.Eur
|
||||
});
|
||||
|
||||
Assert.Equal(2, result.AdditionalValueFields.Count);
|
||||
|
||||
var yearly = Assert.Single(result.YearlyTotals);
|
||||
Assert.Equal(150m, yearly.SalesValue);
|
||||
Assert.Equal(5m, yearly.AdditionalValues[ManagementCockpitValueFieldKeys.Quantity].Value);
|
||||
Assert.Equal("-", yearly.AdditionalValues[ManagementCockpitValueFieldKeys.Quantity].Currency);
|
||||
Assert.Equal(31m, yearly.AdditionalValues[ManagementCockpitValueFieldKeys.StandardCostTotal].Value);
|
||||
Assert.Equal("EUR", yearly.AdditionalValues[ManagementCockpitValueFieldKeys.StandardCostTotal].Currency);
|
||||
|
||||
Assert.Contains(result.MonthlyTotals, row =>
|
||||
row.Label == "2025-01" &&
|
||||
row.AdditionalValues[ManagementCockpitValueFieldKeys.Quantity].Value == 2m);
|
||||
Assert.Contains(result.MonthlyTotals, row =>
|
||||
row.Label == "2025-02" &&
|
||||
row.AdditionalValues[ManagementCockpitValueFieldKeys.StandardCostTotal].Value == 21m);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AnalyzeCentralAsync_Throws_When_No_Rows_Exist_For_Selected_Period()
|
||||
{
|
||||
@@ -142,7 +247,36 @@ public class ManagementCockpitServiceTests : IDisposable
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static CentralSalesRecord CreateRow(string sourceSystem, string land, string tsc, string invoiceNumber, string currency, decimal salesValue, DateTime? invoiceDate, DateTime? extractionDate = null)
|
||||
private async Task SeedRatesAsync(params CurrencyExchangeRate[] rates)
|
||||
{
|
||||
await using var db = await _dbFactory.CreateDbContextAsync();
|
||||
db.CurrencyExchangeRates.RemoveRange(db.CurrencyExchangeRates);
|
||||
await db.SaveChangesAsync();
|
||||
db.CurrencyExchangeRates.AddRange(rates);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private static CurrencyExchangeRate CreateRate(string fromCurrency, string toCurrency, decimal rate)
|
||||
=> new()
|
||||
{
|
||||
FromCurrency = fromCurrency,
|
||||
ToCurrency = toCurrency,
|
||||
Rate = rate,
|
||||
ValidFrom = new DateTime(2024, 1, 1),
|
||||
IsActive = true
|
||||
};
|
||||
|
||||
private static CentralSalesRecord CreateRow(
|
||||
string sourceSystem,
|
||||
string land,
|
||||
string tsc,
|
||||
string invoiceNumber,
|
||||
string currency,
|
||||
decimal salesValue,
|
||||
DateTime? invoiceDate,
|
||||
DateTime? extractionDate = null,
|
||||
decimal quantity = 1m,
|
||||
decimal standardCost = 1m)
|
||||
{
|
||||
return new CentralSalesRecord
|
||||
{
|
||||
@@ -156,7 +290,7 @@ public class ManagementCockpitServiceTests : IDisposable
|
||||
Material = "MAT",
|
||||
Name = "Article",
|
||||
ProductGroup = "PG",
|
||||
Quantity = 1m,
|
||||
Quantity = quantity,
|
||||
SupplierNumber = "SUP",
|
||||
SupplierName = "Supplier",
|
||||
SupplierCountry = "CH",
|
||||
@@ -164,7 +298,7 @@ public class ManagementCockpitServiceTests : IDisposable
|
||||
CustomerName = "Customer",
|
||||
CustomerCountry = "CH",
|
||||
CustomerIndustry = "Industry",
|
||||
StandardCost = 1m,
|
||||
StandardCost = standardCost,
|
||||
StandardCostCurrency = currency,
|
||||
PurchaseOrderNumber = "PO",
|
||||
SalesPriceValue = salesValue,
|
||||
@@ -192,4 +326,18 @@ public class ManagementCockpitServiceTests : IDisposable
|
||||
public Task<AppDbContext> CreateDbContextAsync(CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(new AppDbContext(_options));
|
||||
}
|
||||
|
||||
private sealed class CountingCurrencyExchangeRateService : ICurrencyExchangeRateService
|
||||
{
|
||||
public int ResolveRateCallCount { get; private set; }
|
||||
|
||||
public decimal? ResolveRate(string fromCurrency, string toCurrency, DateTime? effectiveDate)
|
||||
{
|
||||
ResolveRateCallCount++;
|
||||
return 2m;
|
||||
}
|
||||
|
||||
public string NormalizeCurrencyCode(string? currencyCode)
|
||||
=> string.IsNullOrWhiteSpace(currencyCode) ? string.Empty : currencyCode.Trim().ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user