From 1dfeeea3809388bc267d54b4fe363051a6564c7e Mon Sep 17 00:00:00 2001 From: Henning Ivan Solberg Date: Wed, 11 Oct 2023 14:05:30 +0200 Subject: [PATCH] Lagt inn caching i DB for valutakurser fra ECB (#4058) Co-authored-by: marius-nav --- .../ba/sak/integrasjoner/ecb/ECBService.kt | 47 ++++++-- .../ecb/domene/ECBValutakursCache.kt | 34 ++++++ .../domene/ECBValutakursCacheRepository.kt | 8 ++ .../ba/sak/internal/ForvalterController.kt | 10 ++ .../migration/V253__ecb_valutakurs_cache.sql | 15 +++ .../sak/integrasjoner/ecb/ECBServiceTest.kt | 11 ++ .../integrasjoner/ecb/ECBIntegrationTest.kt | 107 ++++++++++++++++++ 7 files changed, 220 insertions(+), 12 deletions(-) create mode 100644 src/main/kotlin/no/nav/familie/ba/sak/integrasjoner/ecb/domene/ECBValutakursCache.kt create mode 100644 src/main/kotlin/no/nav/familie/ba/sak/integrasjoner/ecb/domene/ECBValutakursCacheRepository.kt create mode 100644 src/main/resources/db/migration/V253__ecb_valutakurs_cache.sql create mode 100644 src/test/integrasjonstester/kotlin/no/nav/familie/ba/sak/integrasjoner/ecb/ECBIntegrationTest.kt diff --git a/src/main/kotlin/no/nav/familie/ba/sak/integrasjoner/ecb/ECBService.kt b/src/main/kotlin/no/nav/familie/ba/sak/integrasjoner/ecb/ECBService.kt index 543b54e335f..138a34c31e4 100644 --- a/src/main/kotlin/no/nav/familie/ba/sak/integrasjoner/ecb/ECBService.kt +++ b/src/main/kotlin/no/nav/familie/ba/sak/integrasjoner/ecb/ECBService.kt @@ -2,11 +2,15 @@ package no.nav.familie.ba.sak.integrasjoner.ecb import no.nav.familie.ba.sak.common.del import no.nav.familie.ba.sak.common.tilKortString +import no.nav.familie.ba.sak.integrasjoner.ecb.domene.ECBValutakursCache +import no.nav.familie.ba.sak.integrasjoner.ecb.domene.ECBValutakursCacheRepository import no.nav.familie.valutakurs.Frequency import no.nav.familie.valutakurs.ValutakursRestClient import no.nav.familie.valutakurs.domene.ExchangeRate import no.nav.familie.valutakurs.domene.exchangeRateForCurrency import no.nav.familie.valutakurs.exception.ValutakursClientException +import org.slf4j.Logger +import org.slf4j.LoggerFactory import org.springframework.context.annotation.Import import org.springframework.stereotype.Service import java.math.BigDecimal @@ -14,7 +18,9 @@ import java.time.LocalDate @Service @Import(ValutakursRestClient::class) -class ECBService(private val ecbClient: ValutakursRestClient) { +class ECBService(private val ecbClient: ValutakursRestClient, private val ecbValutakursCacheRepository: ECBValutakursCacheRepository) { + + private val logger: Logger = LoggerFactory.getLogger(ECBService::class.java) /** * @param utenlandskValuta valutaen vi skal konvertere til NOK @@ -23,19 +29,36 @@ class ECBService(private val ecbClient: ValutakursRestClient) { */ @Throws(ECBServiceException::class) fun hentValutakurs(utenlandskValuta: String, kursDato: LocalDate): BigDecimal { - try { - val exchangeRates = - ecbClient.hentValutakurs(Frequency.Daily, listOf(ECBConstants.NOK, utenlandskValuta), kursDato) - validateExchangeRates(utenlandskValuta, kursDato, exchangeRates) - val valutakursNOK = exchangeRates.exchangeRateForCurrency(ECBConstants.NOK)!! - if (utenlandskValuta == ECBConstants.EUR) { - return valutakursNOK.exchangeRate + val valutakurs = ecbValutakursCacheRepository.findByValutakodeAndValutakursdato(utenlandskValuta, kursDato) + if (valutakurs == null) { + logger.info("Henter valutakurs for $utenlandskValuta på $kursDato") + try { + val exchangeRates = + ecbClient.hentValutakurs(Frequency.Daily, listOf(ECBConstants.NOK, utenlandskValuta), kursDato) + validateExchangeRates(utenlandskValuta, kursDato, exchangeRates) + val valutakursNOK = exchangeRates.exchangeRateForCurrency(ECBConstants.NOK)!! + if (utenlandskValuta == ECBConstants.EUR) { + ecbValutakursCacheRepository.save(ECBValutakursCache(kurs = valutakursNOK.exchangeRate, valutakode = utenlandskValuta, valutakursdato = kursDato)) + return valutakursNOK.exchangeRate + } + val valutakursUtenlandskValuta = exchangeRates.exchangeRateForCurrency(utenlandskValuta)!! + ecbValutakursCacheRepository.save( + ECBValutakursCache( + kurs = beregnValutakurs( + valutakursUtenlandskValuta.exchangeRate, + valutakursNOK.exchangeRate, + ), + valutakode = utenlandskValuta, + valutakursdato = kursDato, + ), + ) + return beregnValutakurs(valutakursUtenlandskValuta.exchangeRate, valutakursNOK.exchangeRate) + } catch (e: ValutakursClientException) { + throw ECBServiceException(e.message, e) } - val valutakursUtenlandskValuta = exchangeRates.exchangeRateForCurrency(utenlandskValuta)!! - return beregnValutakurs(valutakursUtenlandskValuta.exchangeRate, valutakursNOK.exchangeRate) - } catch (e: ValutakursClientException) { - throw ECBServiceException(e.message, e) } + logger.info("Valutakurs ble hentet fra cache for $utenlandskValuta på $kursDato") + return valutakurs.kurs } private fun beregnValutakurs(valutakursUtenlandskValuta: BigDecimal, valutakursNOK: BigDecimal) = diff --git a/src/main/kotlin/no/nav/familie/ba/sak/integrasjoner/ecb/domene/ECBValutakursCache.kt b/src/main/kotlin/no/nav/familie/ba/sak/integrasjoner/ecb/domene/ECBValutakursCache.kt new file mode 100644 index 00000000000..88eb9b57ef7 --- /dev/null +++ b/src/main/kotlin/no/nav/familie/ba/sak/integrasjoner/ecb/domene/ECBValutakursCache.kt @@ -0,0 +1,34 @@ +package no.nav.familie.ba.sak.integrasjoner.ecb.domene + +import jakarta.persistence.Column +import jakarta.persistence.Entity +import jakarta.persistence.EntityListeners +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.SequenceGenerator +import jakarta.persistence.Table +import no.nav.familie.ba.sak.common.BaseEntitet +import no.nav.familie.ba.sak.sikkerhet.RollestyringMotDatabase +import java.math.BigDecimal +import java.time.LocalDate + +@EntityListeners(RollestyringMotDatabase::class) +@Entity(name = "EcbValutakursCache") +@Table(name = "ECBVALUTAKURSCACHE") +data class ECBValutakursCache( + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ecbvalutakurscache_seq_generator") + @SequenceGenerator(name = "ecbvalutakurscache_seq_generator", sequenceName = "ecbvalutakurscache_seq", allocationSize = 50) + val id: Long = 0, + + @Column(name = "valutakursdato", columnDefinition = "DATE") + val valutakursdato: LocalDate? = null, + + @Column(name = "valutakode") + val valutakode: String? = null, + + @Column(name = "kurs", nullable = false) + val kurs: BigDecimal, +) : BaseEntitet() diff --git a/src/main/kotlin/no/nav/familie/ba/sak/integrasjoner/ecb/domene/ECBValutakursCacheRepository.kt b/src/main/kotlin/no/nav/familie/ba/sak/integrasjoner/ecb/domene/ECBValutakursCacheRepository.kt new file mode 100644 index 00000000000..694d7007e08 --- /dev/null +++ b/src/main/kotlin/no/nav/familie/ba/sak/integrasjoner/ecb/domene/ECBValutakursCacheRepository.kt @@ -0,0 +1,8 @@ +package no.nav.familie.ba.sak.integrasjoner.ecb.domene + +import org.springframework.data.jpa.repository.JpaRepository +import java.time.LocalDate + +interface ECBValutakursCacheRepository : JpaRepository { + fun findByValutakodeAndValutakursdato(valutakode: String, valutakursdato: LocalDate): ECBValutakursCache? +} diff --git a/src/main/kotlin/no/nav/familie/ba/sak/internal/ForvalterController.kt b/src/main/kotlin/no/nav/familie/ba/sak/internal/ForvalterController.kt index b8379049770..246a684acfd 100644 --- a/src/main/kotlin/no/nav/familie/ba/sak/internal/ForvalterController.kt +++ b/src/main/kotlin/no/nav/familie/ba/sak/internal/ForvalterController.kt @@ -1,6 +1,7 @@ package no.nav.familie.ba.sak.internal import no.nav.familie.ba.sak.common.secureLogger +import no.nav.familie.ba.sak.integrasjoner.ecb.ECBService import no.nav.familie.ba.sak.integrasjoner.familieintegrasjoner.IntegrasjonClient import no.nav.familie.ba.sak.integrasjoner.oppgave.domene.OppgaveRepository import no.nav.familie.ba.sak.kjerne.autovedtak.småbarnstillegg.RestartAvSmåbarnstilleggService @@ -15,7 +16,10 @@ import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController +import java.math.BigDecimal +import java.time.LocalDate import java.util.UUID import kotlin.concurrent.thread @@ -27,6 +31,7 @@ class ForvalterController( private val integrasjonClient: IntegrasjonClient, private val restartAvSmåbarnstilleggService: RestartAvSmåbarnstilleggService, private val forvalterService: ForvalterService, + private val ecbService: ECBService, ) { private val logger: Logger = LoggerFactory.getLogger(ForvalterController::class.java) @@ -111,6 +116,11 @@ class ForvalterController( return ResponseEntity.ok(Pair("callId", callId)) } + @GetMapping("/hentValutakurs/") + fun finnFagsakerSomSkalAvsluttes(@RequestParam valuta: String, @RequestParam dato: LocalDate): ResponseEntity { + return ResponseEntity.ok(ecbService.hentValutakurs(valuta, dato)) + } + @GetMapping("/finnÅpneFagsakerMedFlereMigreringsbehandlingerOgLøpendeSakIInfotrygd") fun finnÅpneFagsakerMedFlereMigreringsbehandlingerOgLøpendeSakIInfotrygd(): ResponseEntity>> { val åpneFagsakerMedFlereMigreringsbehandlingerOgLøpendeSakIInfotrygd = diff --git a/src/main/resources/db/migration/V253__ecb_valutakurs_cache.sql b/src/main/resources/db/migration/V253__ecb_valutakurs_cache.sql new file mode 100644 index 00000000000..b88b4ab45f2 --- /dev/null +++ b/src/main/resources/db/migration/V253__ecb_valutakurs_cache.sql @@ -0,0 +1,15 @@ +CREATE TABLE ecbvalutakurscache +( + ID BIGINT NOT NULL PRIMARY KEY, + VALUTAKURSDATO TIMESTAMP(3) DEFAULT null, + VALUTAKODE VARCHAR DEFAULT null, + KURS DECIMAL DEFAULT null, + VERSJON BIGINT DEFAULT 0 NOT NULL, + OPPRETTET_AV VARCHAR DEFAULT 'VL' NOT NULL, + OPPRETTET_TID TIMESTAMP(3) DEFAULT localtimestamp NOT NULL, + ENDRET_AV VARCHAR, + ENDRET_TID TIMESTAMP(3) +); + +CREATE SEQUENCE ecbvalutakurscache_seq INCREMENT BY 50 START WITH 1 NO CYCLE; +CREATE INDEX valutakode_valutadato_idx ON ecbvalutakurscache (VALUTAKURSDATO, VALUTAKODE); \ No newline at end of file diff --git a/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/integrasjoner/ecb/ECBServiceTest.kt b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/integrasjoner/ecb/ECBServiceTest.kt index 304d08444c2..5f403740965 100644 --- a/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/integrasjoner/ecb/ECBServiceTest.kt +++ b/src/test/enhetstester/kotlin/no/nav/familie/ba/sak/integrasjoner/ecb/ECBServiceTest.kt @@ -5,6 +5,8 @@ import io.mockk.impl.annotations.InjectMockKs import io.mockk.impl.annotations.MockK import io.mockk.junit5.MockKExtension import io.mockk.unmockkAll +import no.nav.familie.ba.sak.integrasjoner.ecb.domene.ECBValutakursCache +import no.nav.familie.ba.sak.integrasjoner.ecb.domene.ECBValutakursCacheRepository import no.nav.familie.valutakurs.Frequency import no.nav.familie.valutakurs.ValutakursRestClient import no.nav.familie.valutakurs.domene.ECBExchangeRate @@ -31,6 +33,9 @@ class ECBServiceTest { @MockK private lateinit var ecbClient: ValutakursRestClient + @MockK + private lateinit var ECBValutakursCacheRepository: ECBValutakursCacheRepository + @InjectMockKs private lateinit var ecbService: ECBService @@ -47,6 +52,8 @@ class ECBServiceTest { listOf(Pair("NOK", BigDecimal.valueOf(10.337)), Pair("SEK", BigDecimal.valueOf(10.6543))), valutakursDato.toString(), ) + every { ECBValutakursCacheRepository.findByValutakodeAndValutakursdato(any(), any()) } returns null + every { ECBValutakursCacheRepository.save(any()) } returns ECBValutakursCache(kurs = BigDecimal.valueOf(10.6543), valutakode = "SEK", valutakursdato = valutakursDato) every { ecbClient.hentValutakurs( Frequency.Daily, @@ -66,6 +73,7 @@ class ECBServiceTest { listOf(Pair("NOK", BigDecimal.valueOf(10.337))), valutakursDato.toString(), ) + every { ECBValutakursCacheRepository.findByValutakodeAndValutakursdato(any(), any()) } returns null every { ecbClient.hentValutakurs( Frequency.Daily, @@ -84,6 +92,7 @@ class ECBServiceTest { listOf(Pair("NOK", BigDecimal.valueOf(10.337)), Pair("SEK", BigDecimal.valueOf(10.6543))), valutakursDato.minusDays(1).toString(), ) + every { ECBValutakursCacheRepository.findByValutakodeAndValutakursdato(any(), any()) } returns null every { ecbClient.hentValutakurs( Frequency.Daily, @@ -103,6 +112,8 @@ class ECBServiceTest { listOf(Pair("NOK", BigDecimal.valueOf(9.4567))), valutakursDato.toString(), ) + every { ECBValutakursCacheRepository.findByValutakodeAndValutakursdato(any(), any()) } returns null + every { ECBValutakursCacheRepository.save(any()) } returns ECBValutakursCache(kurs = BigDecimal.valueOf(9.4567), valutakode = "EUR", valutakursdato = valutakursDato) every { ecbClient.hentValutakurs( Frequency.Daily, diff --git a/src/test/integrasjonstester/kotlin/no/nav/familie/ba/sak/integrasjoner/ecb/ECBIntegrationTest.kt b/src/test/integrasjonstester/kotlin/no/nav/familie/ba/sak/integrasjoner/ecb/ECBIntegrationTest.kt new file mode 100644 index 00000000000..04429de7c95 --- /dev/null +++ b/src/test/integrasjonstester/kotlin/no/nav/familie/ba/sak/integrasjoner/ecb/ECBIntegrationTest.kt @@ -0,0 +1,107 @@ +package no.nav.familie.ba.sak.integrasjoner.ecb + +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.junit5.MockKExtension +import io.mockk.verify +import no.nav.familie.ba.sak.config.AbstractSpringIntegrationTest +import no.nav.familie.ba.sak.config.DatabaseCleanupService +import no.nav.familie.ba.sak.integrasjoner.ecb.domene.ECBValutakursCacheRepository +import no.nav.familie.valutakurs.Frequency +import no.nav.familie.valutakurs.ValutakursRestClient +import no.nav.familie.valutakurs.domene.ECBExchangeRate +import no.nav.familie.valutakurs.domene.ECBExchangeRateDate +import no.nav.familie.valutakurs.domene.ECBExchangeRateKey +import no.nav.familie.valutakurs.domene.ECBExchangeRateValue +import no.nav.familie.valutakurs.domene.ECBExchangeRatesData +import no.nav.familie.valutakurs.domene.ECBExchangeRatesDataSet +import no.nav.familie.valutakurs.domene.ECBExchangeRatesForCurrency +import no.nav.familie.valutakurs.domene.toExchangeRates +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import org.junit.jupiter.api.extension.ExtendWith +import org.springframework.beans.factory.annotation.Autowired +import java.math.BigDecimal +import java.time.LocalDate + +@ExtendWith(MockKExtension::class) +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class ECBIntegrationTest : AbstractSpringIntegrationTest() { + + @MockK + private lateinit var ecbClient: ValutakursRestClient + + @Autowired + private lateinit var ecbService: ECBService + + @Autowired + private lateinit var ecbValutakursCacheRepository: ECBValutakursCacheRepository + + @Autowired + private lateinit var databaseCleanupService: DatabaseCleanupService + + @BeforeEach + fun setUp() { + ecbService = ECBService( + ecbClient = ecbClient, + ecbValutakursCacheRepository = ecbValutakursCacheRepository, + ) + databaseCleanupService.truncate() + } + + @Test + fun `Skal teste at valutakurs hentes fra cache dersom valutakursen allerede er hentet fra ECB`() { + val valutakursDato = LocalDate.of(2022, 7, 20) + val ecbExchangeRatesData = createECBResponse( + Frequency.Daily, + listOf(Pair("NOK", BigDecimal.valueOf(9.4567))), + valutakursDato.toString(), + ) + every { + ecbClient.hentValutakurs( + any(), + any(), + any(), + ) + } returns ecbExchangeRatesData.toExchangeRates() + + ecbService.hentValutakurs("EUR", valutakursDato) + val valutakurs = ecbValutakursCacheRepository.findByValutakodeAndValutakursdato("EUR", valutakursDato) + assertEquals(valutakurs!!.kurs, BigDecimal.valueOf(9.4567)) + ecbService.hentValutakurs("EUR", valutakursDato) + verify(exactly = 1) { + ecbClient.hentValutakurs( + any(), + any(), + any(), + ) + } + } + + private fun createECBResponse( + frequency: Frequency, + exchangeRates: List>, + exchangeRateDate: String, + ): ECBExchangeRatesData { + return ECBExchangeRatesData( + ECBExchangeRatesDataSet( + exchangeRates.map { + ECBExchangeRatesForCurrency( + listOf( + ECBExchangeRateKey("CURRENCY", it.first), + ECBExchangeRateKey("FREQ", frequency.toFrequencyParam()), + ), + listOf( + ECBExchangeRate( + ECBExchangeRateDate(exchangeRateDate), + ECBExchangeRateValue((it.second)), + ), + ), + ) + }, + ), + ) + } +}