tests wähurngsverwaltung
This commit is contained in:
@@ -2,6 +2,47 @@
|
||||
|
||||
Stand: 2026-04-15
|
||||
|
||||
## Nachtrag 2026-04-17
|
||||
|
||||
Der dokumentierte Stand in diesem Handoff war bei der Waehrungslogik nicht mehr aktuell.
|
||||
|
||||
Inzwischen gilt:
|
||||
|
||||
- Kurstabellen fuer `CurrencyExchangeRates` sind im System vorhanden
|
||||
- `Settings` enthaelt bereits eine Pflegeoberflaeche fuer Wechselkurse
|
||||
- `ExchangeRateImportService` importiert ECB-Tageskurse nach `CurrencyExchangeRates`
|
||||
- `NormalizeCurrencyCode` ist als Value-Transformation vorhanden
|
||||
- `ConvertCurrency` ist als Record-Transformation vorhanden
|
||||
- `Program.cs` registriert beide Strategien sowie `CurrencyExchangeRateService` und `ExchangeRateImportService`
|
||||
|
||||
Wichtig:
|
||||
|
||||
- die Roh-Auswertung im `Management Cockpit` rechnet Stand heute weiterhin bewusst **nicht** in CHF um
|
||||
- dort bleibt der Umsatz weiterhin in `Sales Currency`
|
||||
- die Waehrungsumrechnung ist aktuell Teil des allgemeinen Transformations-/Mapping-Systems, nicht der Cockpit-Rohsicht
|
||||
|
||||
Zusatzlich wurden am 2026-04-17 fehlende Unit-Tests fuer die Waehrungslogik nachgezogen:
|
||||
|
||||
- `CurrencyExchangeRateServiceTests`
|
||||
- `ExchangeRateImportServiceTests`
|
||||
- Erweiterungen in
|
||||
- `TransformationStrategiesTests`
|
||||
- `RecordTransformationServiceTests`
|
||||
- `TransformationCatalogTests`
|
||||
|
||||
Aktueller Teststatus nach diesem Nachtrag:
|
||||
|
||||
```text
|
||||
dotnet test .\TrafagSalesExporter.Tests\TrafagSalesExporter.Tests.csproj --verbosity minimal
|
||||
```
|
||||
|
||||
Ergebnis:
|
||||
|
||||
- erfolgreich
|
||||
- `31/31` Tests gruen
|
||||
- bekannte Warnung bleibt:
|
||||
- SAP HANA Architekturwarnung `MSB3270`
|
||||
|
||||
## Nachtrag 2026-04-16
|
||||
|
||||
Seit dem letzten Handoff wurden weitere Funktionen umgesetzt, die unten im alten Stand noch nicht voll enthalten sind.
|
||||
|
||||
@@ -2,6 +2,31 @@
|
||||
|
||||
Stand: 2026-04-15
|
||||
|
||||
## Nachtrag 2026-04-17
|
||||
|
||||
Der Punkt `CHF-Umrechnung / Wechselkurse` ist nicht mehr komplett offen.
|
||||
|
||||
Der aktuelle Ist-Stand ist:
|
||||
|
||||
- `CurrencyExchangeRateService` ist implementiert
|
||||
- `ExchangeRateImportService` importiert ECB-Kurse
|
||||
- `NormalizeCurrencyCode` und `ConvertCurrency` sind im Transformationssystem registriert
|
||||
- fehlende Unit-Tests dafuer wurden am 2026-04-17 ergaenzt
|
||||
|
||||
Neuer Teststand:
|
||||
|
||||
- `dotnet test .\TrafagSalesExporter.Tests\TrafagSalesExporter.Tests.csproj --verbosity minimal`
|
||||
- erfolgreich
|
||||
- `31/31` Tests gruen
|
||||
|
||||
Was fuer Waehrungen trotzdem noch offen bleibt:
|
||||
|
||||
- fachlicher Einsatz der `ConvertCurrency`-Regeln in echten Standortkonfigurationen pruefen
|
||||
- UI-Flow fuer Wechselkurspflege in `Settings.razor` manuell gegenpruefen
|
||||
- ECB-Import einmal real ueber die UI bzw. App-Funktion pruefen
|
||||
- bestaetigen, fuer welche Sichten CHF die Zielwaehrung sein soll
|
||||
- Management-Cockpit-Rohsicht nur dann auf CHF umstellen, wenn fachlich gewuenscht
|
||||
|
||||
## Nachtrag 2026-04-16
|
||||
|
||||
Seit dem letzten Stand kamen mehrere groessere Erweiterungen dazu. Die offenen Punkte unten muessen deshalb im neuen Kontext gelesen werden.
|
||||
@@ -131,7 +156,7 @@ Dateien:
|
||||
Noch nicht final umsetzen ohne Rueckmeldung Fachseite:
|
||||
|
||||
- Intercompany-Filter
|
||||
- CHF-Umrechnung / Wechselkurse
|
||||
- fachliche Nutzung der CHF-Umrechnung in Cockpit / Reports
|
||||
- Budgetvergleich
|
||||
- Gruppenlogik
|
||||
- Spartenlogik
|
||||
@@ -144,13 +169,14 @@ Diese Punkte sollen spaeter moeglichst dynamisch auf dem neuen Transformations-/
|
||||
Wenn weiter in Tests investiert wird, sind die naechsten Kandidaten:
|
||||
|
||||
- `ExportOrchestrationService`
|
||||
- spaeter End-to-End-Tests fuer den Wechselkurs-/Transformationspfad
|
||||
- spaeter evtl. SQLite-nahe Integrationstests fuer `DatabaseInitializationService`
|
||||
|
||||
Aktueller Teststatus:
|
||||
|
||||
- `dotnet test TrafagSalesExporter.sln --verbosity minimal`
|
||||
- `dotnet test .\TrafagSalesExporter.Tests\TrafagSalesExporter.Tests.csproj --verbosity minimal`
|
||||
- erfolgreich
|
||||
- `18/18` Tests gruen
|
||||
- `31/31` Tests gruen
|
||||
|
||||
## 7. Referenzdatei
|
||||
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TrafagSalesExporter.Data;
|
||||
using TrafagSalesExporter.Models;
|
||||
using TrafagSalesExporter.Services;
|
||||
|
||||
namespace TrafagSalesExporter.Tests;
|
||||
|
||||
public class CurrencyExchangeRateServiceTests : IDisposable
|
||||
{
|
||||
private readonly SqliteConnection _connection;
|
||||
private readonly TestDbContextFactory _dbFactory;
|
||||
private readonly CurrencyExchangeRateService _service;
|
||||
|
||||
public CurrencyExchangeRateServiceTests()
|
||||
{
|
||||
_connection = new SqliteConnection("DataSource=:memory:");
|
||||
_connection.Open();
|
||||
|
||||
var options = new DbContextOptionsBuilder<AppDbContext>()
|
||||
.UseSqlite(_connection)
|
||||
.Options;
|
||||
|
||||
using var db = new AppDbContext(options);
|
||||
db.Database.EnsureCreated();
|
||||
|
||||
_dbFactory = new TestDbContextFactory(options);
|
||||
_service = new CurrencyExchangeRateService(_dbFactory);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_connection.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveRate_Returns_Direct_Rate_For_Valid_Date()
|
||||
{
|
||||
await SeedRatesAsync(new CurrencyExchangeRate
|
||||
{
|
||||
FromCurrency = "USD",
|
||||
ToCurrency = "EUR",
|
||||
Rate = 0.92m,
|
||||
ValidFrom = new DateTime(2026, 1, 1),
|
||||
ValidTo = null,
|
||||
IsActive = true
|
||||
});
|
||||
|
||||
var rate = _service.ResolveRate("USD", "EUR", new DateTime(2026, 4, 1));
|
||||
|
||||
Assert.Equal(0.92m, rate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveRate_Uses_Inverse_Rate_When_Only_Reverse_Rate_Exists()
|
||||
{
|
||||
await SeedRatesAsync(new CurrencyExchangeRate
|
||||
{
|
||||
FromCurrency = "EUR",
|
||||
ToCurrency = "CHF",
|
||||
Rate = 1.10m,
|
||||
ValidFrom = new DateTime(2026, 1, 1),
|
||||
ValidTo = null,
|
||||
IsActive = true
|
||||
});
|
||||
|
||||
var rate = _service.ResolveRate("CHF", "EUR", new DateTime(2026, 4, 1));
|
||||
|
||||
Assert.NotNull(rate);
|
||||
Assert.Equal(1m / 1.10m, rate!.Value, 6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResolveRate_Uses_Eur_Cross_Rate_When_No_Direct_Rate_Exists()
|
||||
{
|
||||
await SeedRatesAsync(
|
||||
new CurrencyExchangeRate
|
||||
{
|
||||
FromCurrency = "CHF",
|
||||
ToCurrency = "EUR",
|
||||
Rate = 0.95m,
|
||||
ValidFrom = new DateTime(2026, 1, 1),
|
||||
ValidTo = null,
|
||||
IsActive = true
|
||||
},
|
||||
new CurrencyExchangeRate
|
||||
{
|
||||
FromCurrency = "EUR",
|
||||
ToCurrency = "USD",
|
||||
Rate = 1.08m,
|
||||
ValidFrom = new DateTime(2026, 1, 1),
|
||||
ValidTo = null,
|
||||
IsActive = true
|
||||
});
|
||||
|
||||
var rate = _service.ResolveRate("CHF", "USD", new DateTime(2026, 4, 1));
|
||||
|
||||
Assert.NotNull(rate);
|
||||
Assert.Equal(1.026m, rate!.Value, 6);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("$", "USD")]
|
||||
[InlineData("US$", "USD")]
|
||||
[InlineData("EUR", "EUR")]
|
||||
[InlineData("sfr", "CHF")]
|
||||
[InlineData("cad", "CAD")]
|
||||
[InlineData("xyz", "XYZ")]
|
||||
public void NormalizeCurrencyCode_Normalizes_Known_And_Unknown_Codes(string input, string expected)
|
||||
{
|
||||
var normalized = _service.NormalizeCurrencyCode(input);
|
||||
|
||||
Assert.Equal(expected, normalized);
|
||||
}
|
||||
|
||||
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 sealed class TestDbContextFactory : IDbContextFactory<AppDbContext>
|
||||
{
|
||||
private readonly DbContextOptions<AppDbContext> _options;
|
||||
|
||||
public TestDbContextFactory(DbContextOptions<AppDbContext> options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public AppDbContext CreateDbContext() => new(_options);
|
||||
|
||||
public Task<AppDbContext> CreateDbContextAsync(CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(new AppDbContext(_options));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TrafagSalesExporter.Data;
|
||||
using TrafagSalesExporter.Services;
|
||||
|
||||
namespace TrafagSalesExporter.Tests;
|
||||
|
||||
public class ExchangeRateImportServiceTests : IDisposable
|
||||
{
|
||||
private readonly SqliteConnection _connection;
|
||||
private readonly TestDbContextFactory _dbFactory;
|
||||
|
||||
public ExchangeRateImportServiceTests()
|
||||
{
|
||||
_connection = new SqliteConnection("DataSource=:memory:");
|
||||
_connection.Open();
|
||||
|
||||
var options = new DbContextOptionsBuilder<AppDbContext>()
|
||||
.UseSqlite(_connection)
|
||||
.Options;
|
||||
|
||||
using var db = new AppDbContext(options);
|
||||
db.Database.EnsureCreated();
|
||||
|
||||
_dbFactory = new TestDbContextFactory(options);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_connection.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RefreshEcbRatesAsync_Imports_Rates_And_Replaces_Previous_Ecb_Rows_For_Same_Day()
|
||||
{
|
||||
const string xml = """
|
||||
<gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
|
||||
<Cube>
|
||||
<Cube time="2026-04-17">
|
||||
<Cube currency="USD" rate="1.1200" />
|
||||
<Cube currency="CHF" rate="0.9700" />
|
||||
</Cube>
|
||||
</Cube>
|
||||
</gesmes:Envelope>
|
||||
""";
|
||||
|
||||
await using (var db = await _dbFactory.CreateDbContextAsync())
|
||||
{
|
||||
db.CurrencyExchangeRates.Add(new()
|
||||
{
|
||||
FromCurrency = "EUR",
|
||||
ToCurrency = "USD",
|
||||
Rate = 1.10m,
|
||||
ValidFrom = new DateTime(2026, 4, 17),
|
||||
Notes = "ECB daily reference rate",
|
||||
IsActive = true
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
var service = new ExchangeRateImportService(
|
||||
new FakeHttpClientFactory(xml),
|
||||
_dbFactory);
|
||||
|
||||
var result = await service.RefreshEcbRatesAsync();
|
||||
|
||||
Assert.Equal(2, result.ImportedCount);
|
||||
Assert.Equal(new DateTime(2026, 4, 17), result.RateDate);
|
||||
Assert.Equal("ECB", result.SourceName);
|
||||
|
||||
await using var verifyDb = await _dbFactory.CreateDbContextAsync();
|
||||
var rates = await verifyDb.CurrencyExchangeRates
|
||||
.OrderBy(x => x.ToCurrency)
|
||||
.ToListAsync();
|
||||
|
||||
Assert.Equal(2, rates.Count);
|
||||
Assert.Collection(rates,
|
||||
chf =>
|
||||
{
|
||||
Assert.Equal("EUR", chf.FromCurrency);
|
||||
Assert.Equal("CHF", chf.ToCurrency);
|
||||
Assert.Equal(0.97m, chf.Rate);
|
||||
},
|
||||
usd =>
|
||||
{
|
||||
Assert.Equal("EUR", usd.FromCurrency);
|
||||
Assert.Equal("USD", usd.ToCurrency);
|
||||
Assert.Equal(1.12m, usd.Rate);
|
||||
});
|
||||
}
|
||||
|
||||
private sealed class TestDbContextFactory : IDbContextFactory<AppDbContext>
|
||||
{
|
||||
private readonly DbContextOptions<AppDbContext> _options;
|
||||
|
||||
public TestDbContextFactory(DbContextOptions<AppDbContext> options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public AppDbContext CreateDbContext() => new(_options);
|
||||
|
||||
public Task<AppDbContext> CreateDbContextAsync(CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(new AppDbContext(_options));
|
||||
}
|
||||
|
||||
private sealed class FakeHttpClientFactory : IHttpClientFactory
|
||||
{
|
||||
private readonly string _xml;
|
||||
|
||||
public FakeHttpClientFactory(string xml)
|
||||
{
|
||||
_xml = xml;
|
||||
}
|
||||
|
||||
public HttpClient CreateClient(string name)
|
||||
{
|
||||
return new HttpClient(new FakeHttpMessageHandler(_xml));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FakeHttpMessageHandler : HttpMessageHandler
|
||||
{
|
||||
private readonly string _xml;
|
||||
|
||||
public FakeHttpMessageHandler(string xml)
|
||||
{
|
||||
_xml = xml;
|
||||
}
|
||||
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(_xml, Encoding.UTF8, "application/xml")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,8 @@ public class RecordTransformationServiceTests
|
||||
];
|
||||
IRecordTransformationStrategy[] recordStrategies =
|
||||
[
|
||||
new FirstNonEmptyRecordTransformationStrategy()
|
||||
new FirstNonEmptyRecordTransformationStrategy(),
|
||||
new ConvertCurrencyRecordTransformationStrategy(new FakeCurrencyExchangeRateService())
|
||||
];
|
||||
|
||||
_service = new RecordTransformationService(valueStrategies, recordStrategies);
|
||||
@@ -141,4 +142,43 @@ public class RecordTransformationServiceTests
|
||||
|
||||
Assert.Equal(42, records[0].PositionOnInvoice);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Apply_Uses_ConvertCurrency_Record_Strategy()
|
||||
{
|
||||
var records = new List<SalesRecord>
|
||||
{
|
||||
new()
|
||||
{
|
||||
SalesPriceValue = 100m,
|
||||
SalesCurrency = "CHF",
|
||||
InvoiceDate = new DateTime(2026, 4, 17)
|
||||
}
|
||||
};
|
||||
var rules = new[]
|
||||
{
|
||||
new FieldTransformationRule
|
||||
{
|
||||
IsActive = true,
|
||||
RuleScope = "Record",
|
||||
TargetField = nameof(SalesRecord.SalesPriceValue),
|
||||
TransformationType = "ConvertCurrency",
|
||||
Argument = "amountField=SalesPriceValue;currencyField=SalesCurrency;targetCurrency=EUR;dateField=InvoiceDate;targetCurrencyField=SalesCurrency",
|
||||
SortOrder = 10
|
||||
}
|
||||
};
|
||||
|
||||
_service.Apply(records, rules);
|
||||
|
||||
Assert.Equal(95m, records[0].SalesPriceValue);
|
||||
Assert.Equal("EUR", records[0].SalesCurrency);
|
||||
}
|
||||
|
||||
private sealed class FakeCurrencyExchangeRateService : ICurrencyExchangeRateService
|
||||
{
|
||||
public decimal? ResolveRate(string fromCurrency, string toCurrency, DateTime? effectiveDate) => 0.95m;
|
||||
|
||||
public string NormalizeCurrencyCode(string? currencyCode)
|
||||
=> currencyCode?.Trim().ToUpperInvariant() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,13 @@ public class TransformationCatalogTests
|
||||
ITransformationStrategy[] valueStrategies =
|
||||
[
|
||||
new CopyTransformationStrategy(),
|
||||
new ConstantTransformationStrategy()
|
||||
new ConstantTransformationStrategy(),
|
||||
new NormalizeCurrencyCodeTransformationStrategy()
|
||||
];
|
||||
IRecordTransformationStrategy[] recordStrategies =
|
||||
[
|
||||
new FirstNonEmptyRecordTransformationStrategy()
|
||||
new FirstNonEmptyRecordTransformationStrategy(),
|
||||
new ConvertCurrencyRecordTransformationStrategy(new FakeCurrencyExchangeRateService())
|
||||
];
|
||||
|
||||
var catalog = new TransformationCatalog(valueStrategies, recordStrategies);
|
||||
@@ -23,6 +25,16 @@ public class TransformationCatalogTests
|
||||
|
||||
Assert.Contains(all, x => x.RuleScope == "Value" && x.Key == "Copy");
|
||||
Assert.Contains(all, x => x.RuleScope == "Value" && x.Key == "Constant");
|
||||
Assert.Contains(all, x => x.RuleScope == "Value" && x.Key == "NormalizeCurrencyCode");
|
||||
Assert.Contains(all, x => x.RuleScope == "Record" && x.Key == "FirstNonEmpty");
|
||||
Assert.Contains(all, x => x.RuleScope == "Record" && x.Key == "ConvertCurrency");
|
||||
}
|
||||
|
||||
private sealed class FakeCurrencyExchangeRateService : ICurrencyExchangeRateService
|
||||
{
|
||||
public decimal? ResolveRate(string fromCurrency, string toCurrency, DateTime? effectiveDate) => 1m;
|
||||
|
||||
public string NormalizeCurrencyCode(string? currencyCode)
|
||||
=> currencyCode?.Trim().ToUpperInvariant() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,4 +47,79 @@ public class TransformationStrategiesTests
|
||||
|
||||
Assert.Equal("Fallback Supplier", record.CustomerName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NormalizeCurrencyCodeStrategy_Uses_BuiltIn_And_Custom_Aliases()
|
||||
{
|
||||
var strategy = new NormalizeCurrencyCodeTransformationStrategy();
|
||||
|
||||
var normalizedDollar = strategy.Transform("$", null);
|
||||
var normalizedRupee = strategy.Transform("rs", null);
|
||||
var normalizedCustom = strategy.Transform("fr.", "fr.=>CHF");
|
||||
|
||||
Assert.Equal("USD", normalizedDollar);
|
||||
Assert.Equal("INR", normalizedRupee);
|
||||
Assert.Equal("CHF", normalizedCustom);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConvertCurrencyRecordStrategy_Converts_Amount_And_Updates_Target_Currency()
|
||||
{
|
||||
var exchangeRateService = new FakeCurrencyExchangeRateService(rate: 0.95m);
|
||||
var strategy = new ConvertCurrencyRecordTransformationStrategy(exchangeRateService);
|
||||
var record = new SalesRecord
|
||||
{
|
||||
SalesPriceValue = 100m,
|
||||
SalesCurrency = "CHF",
|
||||
InvoiceDate = new DateTime(2026, 4, 17)
|
||||
};
|
||||
var rule = new FieldTransformationRule
|
||||
{
|
||||
RuleScope = "Record",
|
||||
TargetField = nameof(SalesRecord.SalesPriceValue),
|
||||
TransformationType = "ConvertCurrency",
|
||||
Argument = "amountField=SalesPriceValue;currencyField=SalesCurrency;targetCurrency=EUR;dateField=InvoiceDate;targetCurrencyField=SalesCurrency;round=2"
|
||||
};
|
||||
|
||||
strategy.Transform(record, rule);
|
||||
|
||||
Assert.Equal("CHF", exchangeRateService.LastFromCurrency);
|
||||
Assert.Equal("EUR", exchangeRateService.LastToCurrency);
|
||||
Assert.Equal(new DateTime(2026, 4, 17), exchangeRateService.LastEffectiveDate);
|
||||
Assert.Equal(95.00m, record.SalesPriceValue);
|
||||
Assert.Equal("EUR", record.SalesCurrency);
|
||||
}
|
||||
|
||||
private sealed class FakeCurrencyExchangeRateService : ICurrencyExchangeRateService
|
||||
{
|
||||
private readonly decimal? _rate;
|
||||
|
||||
public FakeCurrencyExchangeRateService(decimal? rate)
|
||||
{
|
||||
_rate = rate;
|
||||
}
|
||||
|
||||
public string LastFromCurrency { get; private set; } = string.Empty;
|
||||
public string LastToCurrency { get; private set; } = string.Empty;
|
||||
public DateTime? LastEffectiveDate { get; private set; }
|
||||
|
||||
public decimal? ResolveRate(string fromCurrency, string toCurrency, DateTime? effectiveDate)
|
||||
{
|
||||
LastFromCurrency = fromCurrency;
|
||||
LastToCurrency = toCurrency;
|
||||
LastEffectiveDate = effectiveDate;
|
||||
return _rate;
|
||||
}
|
||||
|
||||
public string NormalizeCurrencyCode(string? currencyCode)
|
||||
{
|
||||
var trimmed = currencyCode?.Trim() ?? string.Empty;
|
||||
return trimmed switch
|
||||
{
|
||||
"$" => "USD",
|
||||
"SFR" => "CHF",
|
||||
_ => trimmed.ToUpperInvariant()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user