From 8cafdb9d79ac21fa9deff36d9be5a546fc17acaa Mon Sep 17 00:00:00 2001 From: Nummedal Date: Fri, 12 Sep 2025 08:47:29 +0200 Subject: [PATCH 1/2] Legg til kodeverk intergrasjon med postnummer og landkode --- .nais/dev.yml | 2 + .nais/prod.yml | 2 + build.gradle.kts | 35 +- src/main/kotlin/no/nav/Consumers.kt | 3 + src/main/kotlin/no/nav/Env.kt | 4 + src/main/kotlin/no/nav/Services.kt | 5 +- .../no/nav/api/kodeverk/KodeverkClient.kt | 56 +++ .../no/nav/api/kodeverk/KodeverkService.kt | 84 ++++ src/main/kotlin/no/nav/api/pdl/PdlService.kt | 36 +- src/main/resources/kodeverk/openapi.json | 460 ++++++++++++++++++ src/main/resources/pdl/schema.graphqls | 17 +- src/main/resources/saf/schema.graphqls | 56 +++ .../nav/api/kodeverk/KodeverkServiceTest.kt | 19 + .../no/nav/api/kodeverk/KodeverkTest.kt | 93 ++++ src/test/kotlin/no/nav/mock/MockConsumers.kt | 53 ++ src/test/kotlin/no/nav/mock/MockEnv.kt | 2 + 16 files changed, 900 insertions(+), 27 deletions(-) create mode 100644 src/main/kotlin/no/nav/api/kodeverk/KodeverkClient.kt create mode 100644 src/main/kotlin/no/nav/api/kodeverk/KodeverkService.kt create mode 100644 src/main/resources/kodeverk/openapi.json create mode 100644 src/test/kotlin/no/nav/api/kodeverk/KodeverkServiceTest.kt create mode 100644 src/test/kotlin/no/nav/api/kodeverk/KodeverkTest.kt diff --git a/.nais/dev.yml b/.nais/dev.yml index e008fef..1bf8118 100644 --- a/.nais/dev.yml +++ b/.nais/dev.yml @@ -63,6 +63,8 @@ spec: namespace: teamsykefravr - application: logging namespace: nais-system + - application: kodeverk-api + namespace: team-rocket inbound: rules: - application: rpa-medlemskap-og-avgift diff --git a/.nais/prod.yml b/.nais/prod.yml index 8b949a1..eb03774 100644 --- a/.nais/prod.yml +++ b/.nais/prod.yml @@ -63,6 +63,8 @@ spec: namespace: teamsykefravr - application: logging namespace: nais-system + - application: kodeverk-api + namespace: team-rocket inbound: rules: - application: rpa-medlemskap-og-avgift diff --git a/build.gradle.kts b/build.gradle.kts index 63ca46f..8ff6efc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -24,6 +24,7 @@ plugins { id("org.jetbrains.kotlin.plugin.serialization") version "2.2.0" id("com.gradleup.shadow") version "8.3.8" id("com.expediagroup.graphql") version "8.8.1" + id("org.openapi.generator") version "7.15.0" } group = "no.nav" @@ -161,8 +162,40 @@ val generatePDLClient by tasks.creating(GraphQLGenerateClientTask::class) { dependsOn("downloadPDLSchema") } +val generatedSourcesPath = "$buildDir/generated/source/openapi" + +openApiGenerate { + inputSpec.set("${project.projectDir}/src/main/resources/kodeverk/openapi.json") + outputDir.set(generatedSourcesPath) + generatorName.set("kotlin") + packageName.set("no.nav.api.generated.kodeverk") + configOptions.set( + mapOf( + "library" to "jvm-ktor", + "serializationLibrary" to "kotlinx_serialization", + "dateLibrary" to "kotlinx-datetime", + ), + ) +} + tasks { + register("generateApi") { + group = "build" + description = "Generate API" + dependsOn("openApiGenerate") + } processResources { - dependsOn("generatePDLClient") + dependsOn("generateApi", "generatePDLClient") + } + compileKotlin { + dependsOn("generateApi") + } +} + +sourceSets { + main { + kotlin { + srcDir("$generatedSourcesPath/src/main/kotlin/") + } } } diff --git a/src/main/kotlin/no/nav/Consumers.kt b/src/main/kotlin/no/nav/Consumers.kt index 03b9c44..f4e9c31 100644 --- a/src/main/kotlin/no/nav/Consumers.kt +++ b/src/main/kotlin/no/nav/Consumers.kt @@ -3,6 +3,7 @@ package no.nav import no.nav.api.dialog.saf.SafClient import no.nav.api.dialog.sf.SFClient import no.nav.api.digdir.DigdirClient +import no.nav.api.kodeverk.KodeverkClient import no.nav.api.kontonummer.KontonummerRegister import no.nav.api.oppfolging.Nom import no.nav.api.oppfolging.OppfolgingClient @@ -29,6 +30,7 @@ interface Consumers { val kontonummerRegister: KontonummerRegister val utbetalingerClient: UtbetalingerClient val sfClient: SFClient + val kodeverkClient: KodeverkClient } class ConsumersImpl( @@ -49,6 +51,7 @@ class ConsumersImpl( override val oppfolgingClient: OppfolgingClient = OppfolgingClient(env.oppfolgingUrl, oboTokenClient.bindTo(env.oppfolgingScope)) override val syfoClient: SyfoClient = SyfoClient(env.syfoUrl, oboTokenClient.bindTo(env.syfoScope)) override val nom: NomClient = Nom(env.nomUrl, tokenclient.bindTo(env.nomScope)).client + override val kodeverkClient: KodeverkClient = KodeverkClient(env.kodeverkUrl, tokenclient.bindTo(env.kodeverkScope)) override val skrivestotteClient: SkrivestotteClient = SkrivestotteClient(env.skrivestotteUrl) override val pdlClient: PdlClient = PdlClient(env.pdlUrl, oboTokenClient.bindTo(env.pdlScope)) override val safClient: SafClient = SafClient(env.safUrl, oboTokenClient.bindTo(env.safScope)) diff --git a/src/main/kotlin/no/nav/Env.kt b/src/main/kotlin/no/nav/Env.kt index e709541..c1dffb4 100644 --- a/src/main/kotlin/no/nav/Env.kt +++ b/src/main/kotlin/no/nav/Env.kt @@ -29,6 +29,8 @@ interface Env { val skrivestotteUrl: String val sfUrl: String val sfScope: DownstreamApi + val kodeverkUrl: String + val kodeverkScope: DownstreamApi val identAllowList: List } @@ -53,6 +55,8 @@ class EnvImpl : Env { override val skrivestotteUrl: String = getRequiredConfig("SKRIVESTOTTE_URL") override val sfUrl: String = getRequiredConfig("SF_HENVENDELSE_URL") override val sfScope: DownstreamApi = getRequiredConfig("SF_HENVENDELSE_SCOPE").toDownstreamApi() + override val kodeverkUrl: String = getRequiredConfig("KODEVERK_URL") + override val kodeverkScope: DownstreamApi = getRequiredConfig("KODEVERK_SCOPE").toDownstreamApi() override val identAllowList: List = getRequiredConfig("IDENT_ALLOW_LIST").uppercase().split(",") } diff --git a/src/main/kotlin/no/nav/Services.kt b/src/main/kotlin/no/nav/Services.kt index 54b0a79..2de3d67 100644 --- a/src/main/kotlin/no/nav/Services.kt +++ b/src/main/kotlin/no/nav/Services.kt @@ -4,6 +4,7 @@ import no.nav.api.dialog.DialogService import no.nav.api.dialog.saf.SafService import no.nav.api.dialog.sf.SFService import no.nav.api.digdir.DigdirService +import no.nav.api.kodeverk.KodeverkService import no.nav.api.oppfolging.OppfolgingService import no.nav.api.pdl.PdlService import no.nav.api.skrivestotte.SkrivestotteService @@ -20,6 +21,7 @@ interface Services { val sfService: SFService val dialogService: DialogService val utbetalingerService: UtbetalingerService + val kodeverkService: KodeverkService } class ServicesImpl( @@ -33,7 +35,8 @@ class ServicesImpl( override val syfoService = SyfoService(consumers.syfoClient, consumers.nom) override val skrivestotteService = SkrivestotteService(consumers.skrivestotteClient) override val digdirService = DigdirService(consumers.digdirClient) - override val pdlService = PdlService(consumers.pdlClient) + override val kodeverkService = KodeverkService(consumers.kodeverkClient) + override val pdlService = PdlService(consumers.pdlClient, kodeverkService) override val safService = SafService(consumers.safClient) override val sfService = SFService(consumers.sfClient) override val dialogService = DialogService(safService, sfService, pdlService) diff --git a/src/main/kotlin/no/nav/api/kodeverk/KodeverkClient.kt b/src/main/kotlin/no/nav/api/kodeverk/KodeverkClient.kt new file mode 100644 index 0000000..94729e6 --- /dev/null +++ b/src/main/kotlin/no/nav/api/kodeverk/KodeverkClient.kt @@ -0,0 +1,56 @@ + +package no.nav.api.kodeverk +import io.ktor.client.engine.HttpClientEngine +import io.ktor.client.engine.okhttp.* +import no.nav.api.generated.kodeverk.apis.KodeverkApi +import no.nav.api.generated.kodeverk.models.GetKodeverkKoderBetydningerResponse +import no.nav.utils.* + +class KodeverkClient( + kodeverkUrl: String, + private val tokenClient: BoundedMachineToMachineTokenClient, + httpEngine: HttpClientEngine = + OkHttp.create { + addInterceptor(XCorrelationIdInterceptor()) + addInterceptor( + LoggingInterceptor( + name = "kodeverk", + callIdExtractor = { getCallId() }, + ), + ) + addInterceptor( + HeadersInterceptor { + mapOf( + "Nav-Consumer-Id" to navConsumerId, + ) + }, + ) + addInterceptor( + AuthorizationInterceptor { + tokenClient.createMachineToMachineToken() + }, + ) + }, +) { + private val api = + KodeverkApi(kodeverkUrl, httpEngine) { config -> + config.installContentNegotiationAndIgnoreUnknownKeys() + } + + suspend fun hentKodeverkRaw(navn: String): GetKodeverkKoderBetydningerResponse = + externalServiceCall { + val response = + api.betydningUsingGET( + navCallId = getCallId(), + navConsumerId = "modia-robot-api", + kodeverksnavn = navn, + spraak = listOf("nb"), + ekskluderUgyldige = null, + oppslagsdato = null, + ) + when (response.success) { + true -> response.body() + else -> error("Feil ved henting av kodeverk: ${response.status}") + } + } +} diff --git a/src/main/kotlin/no/nav/api/kodeverk/KodeverkService.kt b/src/main/kotlin/no/nav/api/kodeverk/KodeverkService.kt new file mode 100644 index 0000000..b5e115b --- /dev/null +++ b/src/main/kotlin/no/nav/api/kodeverk/KodeverkService.kt @@ -0,0 +1,84 @@ +package no.nav.api.kodeverk + +import kotlinx.coroutines.runBlocking +import no.nav.api.generated.kodeverk.models.GetKodeverkKoderBetydningerResponse +import no.nav.personoversikt.common.utils.Retry +import no.nav.personoversikt.common.utils.SelftestGenerator +import kotlin.collections.set +import kotlin.concurrent.fixedRateTimer +import kotlin.time.Duration.Companion.hours +import kotlin.time.Duration.Companion.seconds + +enum class KodeverkNavn( + val kodeverkString: String, +) { + LAND("Landkoder"), + POSTNUMMER("Postnummer"), +} + +class KodeverkService( + private val kodeverkClient: KodeverkClient, +) { + private var kodeverkCache: MutableMap> = mutableMapOf() + private val emptyKodeverk: Map = emptyMap() + private val reporter = SelftestGenerator.Reporter(name = "KodeverkService", critical = false) + private val retry = + Retry( + Retry.Config( + initDelay = 30.seconds, + growthFactor = 2.0, + delayLimit = 1.hours, + ), + ) + + init { + fixedRateTimer( + name = "Prepopuler cache kodeverk", + daemon = true, + period = 1.hours.inWholeMilliseconds, + initialDelay = 0, + ) { + runBlocking { + reporter.ping { + prepopulerCache() + } + } + } + } + private fun hentKodeverk(navn: KodeverkNavn): Map = (kodeverkCache[navn] ?: emptyKodeverk) + + internal fun parseTilKodeverk(respons: GetKodeverkKoderBetydningerResponse): Map { + val res = + respons.betydninger.mapValues { entry -> + entry.value + .first() + .beskrivelser["nb"] + ?.term ?: entry.key + } + return res + } + + + fun hentKodeBeskrivelse( + kodeverkNavn: KodeverkNavn, + kodeRef: String, + default: String, + ): String { + val kodeverk = this.hentKodeverk(kodeverkNavn) + val beskrivelse = kodeverk[kodeRef] + if (beskrivelse == null) { + return default + } + return beskrivelse + } + + internal fun prepopulerCache() { + KodeverkNavn.entries.forEach { navn -> + runBlocking { + retry.run { + kodeverkCache[navn] = parseTilKodeverk(kodeverkClient.hentKodeverkRaw(navn.kodeverkString)) + } + } + } + } +} diff --git a/src/main/kotlin/no/nav/api/pdl/PdlService.kt b/src/main/kotlin/no/nav/api/pdl/PdlService.kt index 48c4bb4..1211471 100644 --- a/src/main/kotlin/no/nav/api/pdl/PdlService.kt +++ b/src/main/kotlin/no/nav/api/pdl/PdlService.kt @@ -2,11 +2,14 @@ package no.nav.api.pdl import kotlinx.datetime.* import no.nav.api.generated.pdl.hentpersonalia.* +import no.nav.api.kodeverk.KodeverkNavn +import no.nav.api.kodeverk.KodeverkService import no.nav.utils.TjenestekallLogger import no.nav.utils.now class PdlService( private val client: PdlClient, + private val kodeverk: KodeverkService, ) { data class Navn( val fornavn: String, @@ -173,10 +176,13 @@ class PdlService( } private fun lagAdresseFraVegadresse(adresse: Vegadresse) = - // TODO hente postnummer kodeverk PdlAdresse( linje1 = listOf(adresse.adressenavn, adresse.husnummer, adresse.husbokstav, adresse.bruksenhetsnummer), - linje2 = listOf(adresse.postnummer), + linje2 = + listOf( + adresse.postnummer, + adresse.postnummer?.let { kodeverk.hentKodeBeskrivelse(KodeverkNavn.POSTNUMMER, it, it) }, + ), ) private fun lagAdresseFraMatrikkeladresse(adresse: Matrikkeladresse) = @@ -186,15 +192,19 @@ class PdlService( ) private fun lagAdresseFraUtenlandskadresse(adresse: UtenlandskAdresse) = - // TODO hente land-kodeverk PdlAdresse( linje1 = listOf(adresse.postboksNummerNavn, adresse.adressenavnNummer, adresse.bygningEtasjeLeilighet), linje2 = listOf(adresse.postkode, adresse.bySted, adresse.regionDistriktOmraade), - linje3 = listOf(adresse.landkode), + linje3 = + listOf( + adresse.landkode, + adresse.landkode.let { + kodeverk.hentKodeBeskrivelse(KodeverkNavn.LAND, it, it) + }, + ), ) private fun lagAdresseFraUtenlandskadresseFrittFormat(adresse: UtenlandskAdresseIFrittFormat) = - // TODO hente land-kodeverk PdlAdresse( linje1 = listOf(adresse.adresselinje1), linje2 = listOf(adresse.adresselinje2), @@ -204,6 +214,9 @@ class PdlService( adresse.postkode, adresse.byEllerStedsnavn, adresse.landkode, + adresse.landkode.let { + kodeverk.hentKodeBeskrivelse(KodeverkNavn.LAND, it, it) + }, ), ) @@ -227,18 +240,23 @@ class PdlService( ) private fun lagAdresseFraPostboksadresse(adresse: Postboksadresse) = - // TODO hente postnummer kodeverk PdlAdresse( linje1 = listOf(adresse.postbokseier), linje2 = listOf("Postboks", adresse.postboks), - linje3 = listOf(adresse.postnummer), + linje3 = listOf(adresse.postnummer, adresse.postnummer?.let { kodeverk.hentKodeBeskrivelse(KodeverkNavn.POSTNUMMER, it, it) }), ) private fun lagAdresseFraFrittformat(adresse: PostadresseIFrittFormat) = - // TODO hente postnummer kodeverk PdlAdresse( linje1 = listOf(adresse.adresselinje1), linje2 = listOf(adresse.adresselinje2), - linje3 = listOf(adresse.adresselinje3, adresse.postnummer), + linje3 = + listOf( + adresse.adresselinje3, + adresse.postnummer, + adresse.postnummer?.let { + kodeverk.hentKodeBeskrivelse(KodeverkNavn.POSTNUMMER, it, it) + }, + ), ) } diff --git a/src/main/resources/kodeverk/openapi.json b/src/main/resources/kodeverk/openapi.json new file mode 100644 index 0000000..b7fdf24 --- /dev/null +++ b/src/main/resources/kodeverk/openapi.json @@ -0,0 +1,460 @@ +{ + "swagger": "2.0", + "info": { + "description": "REST-grensesnittet som er tilgjengelig for konsumering av andre applikasjoner og komponenter, for å hente informasjon om kodeverkene som finnes.", + "title": "API versjon 1", + "version": "1.0.0" + }, + "host": "kodeverk.nais.preprod.local", + "basePath": "/", + "tags": [ + { + "name": "hierarki", + "description": "Endepunkt for å hente hierarki" + }, + { + "name": "kodeverk", + "description": "Endepunkt for å hente kodeverk" + } + ], + "paths": { + "/api/v1/hierarki": { + "get": { + "tags": [ + "hierarki" + ], + "summary": "Returnerer en liste med navnene på alle hierarkiene som er registrert.", + "operationId": "hierarkiUsingGET", + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "Nav-Call-Id", + "in": "header", + "description": "En ID som identifiserer kallkjeden som dette kallet er en del av.", + "required": true, + "type": "string" + }, + { + "name": "Nav-Consumer-Id", + "in": "header", + "description": "ID'en på systemet som gjør kallet, som regel service brukeren til applikasjonen, for eksempel \"srvmedl2\".", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/GetHierarkiResponse" + } + }, + "400": { + "description": "Bad Request" + } + }, + "deprecated": false + } + }, + "/api/v1/hierarki/{hierarkinavn}/noder": { + "get": { + "tags": [ + "hierarki" + ], + "summary": "Returnerer to lister som viser informasjon om et hierarki. Første liste er nivåer, andre liste er hierarkiet.", + "operationId": "noderTilHierarkiUsingGET", + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "Nav-Call-Id", + "in": "header", + "description": "En ID som identifiserer kallkjeden som dette kallet er en del av.", + "required": true, + "type": "string" + }, + { + "name": "Nav-Consumer-Id", + "in": "header", + "description": "Nav-Consumer-Id", + "required": true, + "type": "string" + }, + { + "name": "hierarkinavn", + "in": "path", + "description": "Hvilket hierarki man skal hente.", + "required": true, + "type": "string" + }, + { + "name": "spraak", + "in": "query", + "description": "En liste over de språkene som termene skal returneres på. Tjenesten vil hente ut alle termene på norsk. Om du ønsker flere spraak kan du angi det. Eksempelverdier er \"nb\" og \"nn\" for henholdsvis bokmål og nynorsk.", + "required": false, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "allowEmptyValue": false + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/GetHierarkiNoderResponse" + } + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + }, + "deprecated": false + } + }, + "/api/v1/kodeverk": { + "get": { + "tags": [ + "kodeverk" + ], + "summary": "Returnerer en liste med navnene på alle kodeverkene som er registrert.", + "operationId": "kodeverkUsingGET", + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "Nav-Call-Id", + "in": "header", + "description": "En ID som identifiserer kallkjeden som dette kallet er en del av.", + "required": true, + "type": "string" + }, + { + "name": "Nav-Consumer-Id", + "in": "header", + "description": "ID'en på systemet som gjør kallet, som regel service brukeren til applikasjonen, for eksempel \"srvmedl2\".", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/GetKodeverkResponse" + } + }, + "400": { + "description": "Bad Request" + } + }, + "deprecated": false + } + }, + "/api/v1/kodeverk/{kodeverksnavn}/koder": { + "get": { + "tags": [ + "kodeverk" + ], + "summary": "Returnerer en liste med de kodene som er registrert under det angitte kodeverket.", + "operationId": "koderUsingGET", + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "Nav-Call-Id", + "in": "header", + "description": "En ID som identifiserer kallkjeden som dette kallet er en del av.", + "required": true, + "type": "string" + }, + { + "name": "Nav-Consumer-Id", + "in": "header", + "description": "Nav-Consumer-Id", + "required": true, + "type": "string" + }, + { + "name": "kodeverksnavn", + "in": "path", + "description": "Hvilket kodeverk man skal hente koder fra.", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/GetKodeverkKoderResponse" + } + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + }, + "deprecated": false + } + }, + "/api/v1/kodeverk/{kodeverksnavn}/koder/betydninger": { + "get": { + "tags": [ + "kodeverk" + ], + "summary": "Returnerer informasjon om betydningene av kodene som finnes i et gitt kodeverk.", + "operationId": "betydningUsingGET", + "produces": [ + "application/json" + ], + "parameters": [ + { + "name": "Nav-Call-Id", + "in": "header", + "description": "En ID som identifiserer kallkjeden som dette kallet er en del av.", + "required": true, + "type": "string" + }, + { + "name": "Nav-Consumer-Id", + "in": "header", + "description": "Nav-Consumer-Id", + "required": true, + "type": "string" + }, + { + "name": "ekskluderUgyldige", + "in": "query", + "description": "Kan brukes for filtrering av betydninger basert på gyldighetsperiodene. Er denne satt til \"false\" så vil alle betydningene for alle kodene i kodeverket returneres, og er den \"true\" så vil kun de betydningene som er gyldige på den angitte \"oppslagsdato\" inkluderes. Dersom denne ikke er spesifisert vil den settes til \"true\".", + "required": false, + "type": "boolean", + "allowEmptyValue": false + }, + { + "name": "kodeverksnavn", + "in": "path", + "description": "Hvilket kodeverk man skal hente koders betydninger fra.", + "required": true, + "type": "string" + }, + { + "name": "oppslagsdato", + "in": "query", + "description": "Den funksjonelle datoen man henter betydninger for, på YYYY-MM-DD format. Denne parameteren har ingen effekt med mindre \"ekskluderUgyldige\" er satt til \"true\". Dersom denne ikke er spesifisert vil dagens dato brukes.", + "required": false, + "type": "string", + "format": "date", + "allowEmptyValue": false + }, + { + "name": "spraak", + "in": "query", + "description": "En liste over de språkene som beskrivelsene skal returneres på. Tjenesten vil ikke feile dersom de angitte språkene er utilgjengelige, men beskrivelsene vil komme på bokmål isteden. Eksempelverdier er \"nb\" og \"nn\" for henholdsvis bokmål og nynorsk.", + "required": false, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "allowEmptyValue": false + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/GetKodeverkKoderBetydningerResponse" + } + }, + "400": { + "description": "Bad Request" + }, + "404": { + "description": "Not Found" + } + }, + "deprecated": false + } + } + }, + "definitions": { + "Beskrivelse": { + "type": "object", + "required": [ + "tekst", + "term" + ], + "properties": { + "tekst": { + "type": "string", + "description": "En mer utfyllende versjon av beskrivelsen, og derfor passer denne verdien bedre som ledetekster der antall tegn ikke er et like stort problem. Ikke alle beskrivelser har en utfyllende versjon, og i de tilfellene vil kortversjonen gå igjen i dette feltet." + }, + "term": { + "type": "string", + "description": "En kort versjon av beskrivelsen, og passer derfor godt til fremvisning i GUI-elementer." + } + }, + "title": "Beskrivelse", + "description": "En beskrivelse er den tekstlige delen av betydningen til en kode, og den kan derfor komme på flere språk. For eksempel, landkoden \"NOR\" kan ha beskrivelsen \"Norge\" på norsk, men \"Norway\" på engelsk. Dersom man ber om å få beskrivelsene på et språk som ikke finnes, så vil bokmålsversjonen brukes isteden." + }, + "Betydning": { + "type": "object", + "required": [ + "beskrivelser", + "gyldigFra", + "gyldigTil" + ], + "properties": { + "beskrivelser": { + "type": "object", + "description": "En samling beskrivelser for denne betydningen, mappet til en språkkode.", + "additionalProperties": { + "$ref": "#/definitions/Beskrivelse" + } + }, + "gyldigFra": { + "type": "string", + "format": "date", + "description": "Når denne betydningen trådte i kraft, på YYYY-MM-DD format." + }, + "gyldigTil": { + "type": "string", + "format": "date", + "description": "Når denne betydningen slutter å være gyldig, på YYYY-MM-DD format." + } + }, + "title": "Betydning", + "description": "En betydning er en tidsbegrenset periode hvor en gitt kode har en reell betydning. For eksempel kunne koden \"OSLO\" hatt to betydninger: en fra 1048 til 1624, og en fra 1925. Dette er fordi Oslo ble omdøpt til Christiania i en periode." + }, + "GetHierarkiNoderResponse": { + "type": "object", + "required": [ + "hierarkinivaaer", + "noder" + ], + "properties": { + "hierarkinivaaer": { + "type": "array", + "description": "En liste over kodeverk som tilsvarer nivåene i hierarkiet. ", + "items": { + "type": "string" + } + }, + "noder": { + "type": "object", + "description": "Et map med alle de gyldige nodene i et hierarki. ", + "additionalProperties": { + "$ref": "#/definitions/Hierarkinode" + } + } + }, + "title": "GetHierarkiNoderResponse", + "description": "Responsen fra GET /api/v1/hierarki/{hierarkinavn}/noder/." + }, + "GetHierarkiResponse": { + "type": "object", + "required": [ + "hierarkinavn" + ], + "properties": { + "hierarkinavn": { + "type": "array", + "description": "En liste med navnene på alle eksisterende hierarki.", + "items": { + "type": "string" + } + } + }, + "title": "GetHierarkiResponse", + "description": "Responsen fra GET /api/v1/hierarki." + }, + "GetKodeverkKoderBetydningerResponse": { + "type": "object", + "required": [ + "betydninger" + ], + "properties": { + "betydninger": { + "type": "object", + "description": "Et map med alle eksisterende koder for kodeverket og alle tilhørende betydninger som passer søkekriteriene.", + "additionalProperties": { + "type": "array", + "items": { + "$ref": "#/definitions/Betydning" + } + } + } + }, + "title": "GetKodeverkKoderBetydningerResponse", + "description": "Responsen fra GET /api/v1/kodeverk/{kodeverksnavn}/koder/betydninger." + }, + "GetKodeverkKoderResponse": { + "type": "object", + "required": [ + "koder" + ], + "properties": { + "koder": { + "type": "array", + "description": "En liste med alle de eksisterende kodene som tilhører kodeverket.", + "items": { + "type": "string" + } + } + }, + "title": "GetKodeverkKoderResponse", + "description": "Responsen fra GET /api/v1/kodeverk/{kodeverksnavn}/koder." + }, + "GetKodeverkResponse": { + "type": "object", + "required": [ + "kodeverksnavn" + ], + "properties": { + "kodeverksnavn": { + "type": "array", + "description": "En liste med navnene på alle eksisterende kodeverk.", + "items": { + "type": "string" + } + } + }, + "title": "GetKodeverkResponse", + "description": "Responsen fra GET /api/v1/kodeverk." + }, + "Hierarkinode": { + "type": "object", + "required": [ + "kode" + ], + "properties": { + "kode": { + "type": "string", + "description": "Kode er navn på kode i et kodeverk og vil være navnet til en node." + }, + "termer": { + "type": "object", + "description": "Termene er er beskrivelsen for noden, mappet til en språkkode.", + "additionalProperties": { + "type": "string" + } + } + }, + "title": "Hierarkinode", + "description": "Et hierarki inneholder kodeverk og koder som er fremstilt på en hierarkisk måte. For eksempel, hierarkiet Geografi, inneholder flere nivåer av kodeverk som inneholder koder. " + } + } +} \ No newline at end of file diff --git a/src/main/resources/pdl/schema.graphqls b/src/main/resources/pdl/schema.graphqls index 826c693..5d6cbed 100644 --- a/src/main/resources/pdl/schema.graphqls +++ b/src/main/resources/pdl/schema.graphqls @@ -145,7 +145,6 @@ type Person { folkeregisterpersonstatus(historikk: Boolean = false): [Folkeregisterpersonstatus!]! forelderBarnRelasjon: [ForelderBarnRelasjon!]! foreldreansvar(historikk: Boolean = false): [Foreldreansvar!]! - fullmakt(historikk: Boolean = false): [Fullmakt!]! identitetsgrunnlag(historikk: Boolean = false): [Identitetsgrunnlag!]! kjoenn(historikk: Boolean = false): [Kjoenn!]! kontaktadresse(historikk: Boolean = false): [Kontaktadresse!]! @@ -533,20 +532,6 @@ type Tolk { spraak: String } -enum FullmaktsRolle { - FULLMAKTSGIVER, - FULLMEKTIG -} - -type Fullmakt { - motpartsPersonident: String! - motpartsRolle: FullmaktsRolle! - omraader: [String!]! - gyldigFraOgMed: Date! - gyldigTilOgMed: Date! - metadata: Metadata! -} - type Folkeregisteridentifikator { identifikasjonsnummer: String! status: String! @@ -788,7 +773,7 @@ input SearchRule { toExcluding:String # [Flag] Kan brukes til å overstyre standard oppførsellen for søk i felter (standard er case insensitive) - caseSensitive:Boolean + caseSensitive:Boolean @deprecated(reason: "Denne funksjonen ble lite brukt og er deprecated, dette flagget vil ikke lenger ha noe effekt og vil bli fjernet i fremtiden") # [Flag] Brukes til å deaktivere fonetisk søk feltene som har dette som standard (Navn) disablePhonetic:Boolean # Boost brukes til å gi ett søkekriterie høyere eller lavere vektlegging en de andre søke kriteriene. diff --git a/src/main/resources/saf/schema.graphqls b/src/main/resources/saf/schema.graphqls index 574ec22..108d797 100644 --- a/src/main/resources/saf/schema.graphqls +++ b/src/main/resources/saf/schema.graphqls @@ -212,6 +212,7 @@ type Journalpost { journalpostId: String! # Beskriver innholdet i journalposten samlet, f.eks. "Ettersendelse til søknad om foreldrepenger" + # Hvis NAV ansatt ikke har tilgang til tema returneres tittel: "*****" tittel: String # Sier hvorvidt journalposten er et inngående dokument, et utgående dokument eller et notat. @@ -281,6 +282,9 @@ type Journalpost { # Datoen journalposten ble opprettet i arkivet. Datoen settes automatisk og kan ikke overskrives. Selv om hver journalpost har mange datoer (se `RelevantDato`) er datoOpprettet å anse som "fasit" på journalpostens alder. datoOpprettet: DateTime! + # Brukes for sortering av journalposter og for å få riktig rekkefølge på dokumenter. + datoSortering: DateTime! + # Liste over datoer som kan være relevante for denne journalposten, f.eks. DATO_EKSPEDERT. Hvilke relevante datoer som returneres, avhenger av journalposttypen. relevanteDatoer: [RelevantDato] @@ -291,6 +295,14 @@ type Journalpost { # Eksempler på eksternReferanseId kan være sykmeldingsId for sykmeldinger, Altinn ArchiveReference for Altinn-skjema eller SEDid for SED. eksternReferanseId: String + # Viser om pålogget bruker i selvbetjeningsløsningen på Nav.no kan se denne journalposten. + brukerHarTilgang: Boolean! + + # Begrunnelse for hvorfor pålogget bruker i selvbetjeningsløsningen eventuelt nektes tilgang til å se denne journalposten. + # Hvis brukerHarTilgang er false, inneholder denne en liste med en eller flere grunner til at brukeren ikke har tilgang + # til å se Journalposten i selvbetjeningsløsningen. Om brukerHarTilgang er true, er denne listen tom. + brukerTilgangAvvistBegrunnelser: [TilgangAvvistBegrunnelse!]! + # Metadata om distribusjon av utgående journalpost. # * Forteller hvilken adresse en utgående forsendelse er distribuert til (digital postkasse eller fysisk post) # * Eller hvilken epost/telefonnummer og varseltekst, varsel fra nav.no er sendt til @@ -479,6 +491,7 @@ type DokumentInfo { dokumentInfoId: String! # Dokumentets tittel, f.eks. *"Søknad om foreldrepenger ved fødsel"* eller *"Legeerklæring"*. + # Hvis NAV ansatt ikke har tilgang til tema returneres tittel: "*****" tittel: String # Kode som sier noe om dokumentets innhold og oppbygning. @@ -513,6 +526,7 @@ type LogiskVedlegg { logiskVedleggId: String! # Tittel på det logiske vedlegget, f.eks. *"Legeerklæring"* + # Hvis NAV ansatt ikke har tilgang til tema returneres tittel: "*****" tittel: String } @@ -545,6 +559,14 @@ type Dokumentvariant { # Uttrykker at tilgangen til metadata for dette dokumentet er begrenset, og at dataene ikke skal brukes i ordinær saksbehandling. skjerming: SkjermingType + + # Viser om pålogget bruker i selvbetjeningsløsningen på Nav.no kan se dokumentet til denne dokumentvarianten. + brukerHarTilgang: Boolean! @deprecated(reason: "Feltet er eksperimentelt og under utvikling. Det har ingen mening i produksjon, og skal ikke brukes uten avtale med Team Dokumentløsninger. Feltet kan plutselig endres og/eller forsvinne.") + + # Begrunnelse for hvorfor pålogget bruker i selvbetjeningsløsningen eventuelt nektes tilgang til å se dokumentet. + # Hvis brukerHarTilgang er false, inneholder denne en liste med en eller flere grunner til at brukeren ikke har tilgang + # til å se dokumentet i selvbetjeningsløsningen. Om brukerHarTilgang er true, er denne listen tom. + brukerTilgangAvvistBegrunnelser: [TilgangAvvistBegrunnelse!]! @deprecated(reason: "Feltet er eksperimentelt og under utvikling. Det har ingen mening i produksjon, og skal ikke brukes uten avtale med Team Dokumentløsninger. Feltet kan plutselig endres og/eller forsvinne.") } # Beskriver hvorfor journalposten eller dokumentet er skjermet. Det kan komme flere verdier enn disse i fremtiden. @@ -736,10 +758,18 @@ enum Kanal { # Forsendelsen inneholder en komplett chatdialog (inngående og utgående meldinger) mellom en bruker og en veileder i NAV. NAV_NO_CHAT + # Presentert direkte på nav.no for innlogget bruker + NAV_NO_UTEN_VARSLING + + # Brevet er sendt til virksomhet som Taushetsbelagt Post via Altinn. # * Brukes for utgående journalposter. DPVT + # Brevet er sendt til virksomhet gjennom eFormidling-kanalen DPO. + # * Brukes for utgående journalposter. + DPO + # Forsendelsen er mottatt på e-post. # * Brukes for inngående journalposter. E_POST @@ -748,6 +778,10 @@ enum Kanal { # * Brukes for inngående journalposter. ALTINN_INNBOKS + # Forsendelsen er sendt fra et HR-system med integrasjon mot Nav. + # * Brukes for inngående journalposter. + HR_SYSTEM_API + # Forsendelsen har ingen kjent kanal. UKJENT } @@ -764,6 +798,9 @@ enum Tema { # Ajourhold – grunnopplysninger AGR + # Aktivitetsplan med dialoger + AKT + # Arbeidsrådgivning – psykologtester ARP @@ -863,12 +900,18 @@ enum Tema { # Oppfølging OPP + # Innsyn + PAI + # Pensjon PEN # Permittering og masseoppsigelser PER + # Innsyn etter personopplysningsloven + POI + # Rehabiliteringspenger REH @@ -929,6 +972,9 @@ enum Tema { # Ukjent UKJ + # Ungdomsprogramytelsen + UNG + # Ventelønn VEN @@ -1018,3 +1064,13 @@ enum Sakstype { # Vil si at saken tilhører et fagsystem. Hvilket fagsystem saken tilhører, finnes i feltet fagsaksystem. FAGSAK } + +# Begrunnelse for hvorfor pålogget bruker i selvbetjeningsløsningen eventuelt nektes tilgang til å se for eksempel en journalpost +# eller et dokument. +type TilgangAvvistBegrunnelse { + # En kode som beskriver grunnen at brukeren ikke har tilgang i selvbetjeningsløsningen + kode: String! + # En menneske-vennlig tekst som forklarer grunnen beskrevet med kode over. Teksten er formulert med mål om at den skal + # gi mening for en saksbehandler, men ikke nødvendigvis for en sivilist. + begrunnelse: String! +} diff --git a/src/test/kotlin/no/nav/api/kodeverk/KodeverkServiceTest.kt b/src/test/kotlin/no/nav/api/kodeverk/KodeverkServiceTest.kt new file mode 100644 index 0000000..8a48031 --- /dev/null +++ b/src/test/kotlin/no/nav/api/kodeverk/KodeverkServiceTest.kt @@ -0,0 +1,19 @@ +package no.nav.api.kodeverk + +import no.nav.mock.MockConsumers +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals + +class KodeverkServiceTest { + private val kodeverkService: KodeverkService = KodeverkService(MockConsumers.kodeverkClient) + + @Test + fun `skal populere kodeverk cache`() { + kodeverkService.prepopulerCache() + val landkode = kodeverkService.hentKodeBeskrivelse(KodeverkNavn.LAND, "NO", "NO") + val poststed = kodeverkService.hentKodeBeskrivelse(KodeverkNavn.POSTNUMMER, "0660", "0660") + + assertEquals("Oslo", poststed) + assertEquals("Norge", landkode) + } +} diff --git a/src/test/kotlin/no/nav/api/kodeverk/KodeverkTest.kt b/src/test/kotlin/no/nav/api/kodeverk/KodeverkTest.kt new file mode 100644 index 0000000..7233cdd --- /dev/null +++ b/src/test/kotlin/no/nav/api/kodeverk/KodeverkTest.kt @@ -0,0 +1,93 @@ +package no.nav.api.kodeverk + +import io.ktor.client.engine.mock.* +import io.ktor.http.* +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.runBlocking +import no.nav.utils.BoundedMachineToMachineTokenClient +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test + +internal class KodeverkTest { + @Test + fun `should be able to deserialize kodeverk response`() = + runBlocking { + val mockEngine = + MockEngine { _ -> + respond( + status = HttpStatusCode.OK, + headers = + headersOf( + HttpHeaders.ContentType, + "application/json", + ), + content = + """ + { + "betydninger": { + "0660": [ + { + "gyldigFra": "2025-09-11", + "gyldigTil": "2025-09-11", + "beskrivelser": { + "nb": { + "term": "Oslo", + "tekst": "lengre tekst" + } + + } + } + ], + "5003": [ + { + "gyldigFra": "2025-09-11", + "gyldigTil": "2025-09-11", + "beskrivelser": { + "nb": { + "term": "Bergen", + "tekst": "lengre tekst" + } + + } + } + ], + "6893": [ + { + "gyldigFra": "2025-09-11", + "gyldigTil": "2025-09-11", + "beskrivelser": { + "nb": { + "term": "Vik i Sogn", + "tekst": "lengre tekst" + } + + } + } + ] + } + } + """.trimIndent(), + ) + } + val tokenClient = mockk() + every { tokenClient.createMachineToMachineToken() } returns "new_token" + + val kodeverkClient = KodeverkClient("http://no.no", tokenClient, mockEngine) + val kodeverkPostnummer = kodeverkClient.hentKodeverkRaw("Postnummer") + + val kodeverkService = KodeverkService(kodeverkClient) + val kodeverkBetydninger = kodeverkService.parseTilKodeverk(kodeverkPostnummer) + + assertEquals( + 3, + kodeverkPostnummer.betydninger.size, + ) + + assertTrue(kodeverkBetydninger.containsKey("0660")) + assertTrue(kodeverkBetydninger.containsKey("6893")) + assertEquals("Oslo", kodeverkBetydninger["0660"]) + assertEquals("Bergen", kodeverkBetydninger["5003"]) + } +} diff --git a/src/test/kotlin/no/nav/mock/MockConsumers.kt b/src/test/kotlin/no/nav/mock/MockConsumers.kt index a84f2a2..9b843b7 100644 --- a/src/test/kotlin/no/nav/mock/MockConsumers.kt +++ b/src/test/kotlin/no/nav/mock/MockConsumers.kt @@ -10,6 +10,9 @@ import no.nav.api.dialog.saf.SafClient import no.nav.api.dialog.sf.SFClient import no.nav.api.digdir.DigdirClient import no.nav.api.digdir.DigdirClient.* +import no.nav.api.generated.kodeverk.models.Beskrivelse +import no.nav.api.generated.kodeverk.models.Betydning +import no.nav.api.generated.kodeverk.models.GetKodeverkKoderBetydningerResponse import no.nav.api.generated.pdl.HentAktorid import no.nav.api.generated.pdl.HentNavn import no.nav.api.generated.pdl.HentPersonalia @@ -21,6 +24,8 @@ import no.nav.api.generated.saf.HentBrukerssaker import no.nav.api.generated.saf.enums.Sakstype import no.nav.api.generated.saf.enums.Tema import no.nav.api.generated.saf.hentbrukerssaker.Sak +import no.nav.api.kodeverk.KodeverkClient +import no.nav.api.kodeverk.KodeverkNavn import no.nav.api.kontonummer.KontonummerRegister import no.nav.api.oppfolging.OppfolgingClient import no.nav.api.pdl.PdlClient @@ -51,6 +56,7 @@ object MockConsumers : Consumers { override val pdlClient = pdlClientMock override val safClient = safClientMock override val sfClient = sfClientMock + override val kodeverkClient = kodeverkClientMock } private val tokenClientMock = @@ -101,6 +107,53 @@ private val nomClientMock = .setVisningsNavn("Fornavn Etternavn") } +private val kodeverkClientMock = + mockOf { client -> + coEvery { client.hentKodeverkRaw(KodeverkNavn.POSTNUMMER.kodeverkString) } returns + GetKodeverkKoderBetydningerResponse( + betydninger = + mapOf( + "0660" to + listOf( + Betydning( + beskrivelser = + mapOf( + "nb" to + Beskrivelse( + term = "Oslo", + tekst = "Oslo er hovedstaden i Norge", + ), + ), + gyldigFra = LocalDate.now(), + gyldigTil = LocalDate.now(), + ), + ), + ), + ) + + coEvery { client.hentKodeverkRaw(KodeverkNavn.LAND.kodeverkString) } returns + GetKodeverkKoderBetydningerResponse( + betydninger = + mapOf( + "NO" to + listOf( + Betydning( + beskrivelser = + mapOf( + "nb" to + Beskrivelse( + term = "Norge", + tekst = "Norge er et land", + ), + ), + gyldigFra = LocalDate.now(), + gyldigTil = LocalDate.now(), + ), + ), + ), + ) + } + private val skrivestotteClientMock = mockOf { client -> val hardkodetUUID = UUID.fromString("0a4df913-3651-4667-aac7-ea9a86f1d916") diff --git a/src/test/kotlin/no/nav/mock/MockEnv.kt b/src/test/kotlin/no/nav/mock/MockEnv.kt index b923b22..7a533d8 100644 --- a/src/test/kotlin/no/nav/mock/MockEnv.kt +++ b/src/test/kotlin/no/nav/mock/MockEnv.kt @@ -26,4 +26,6 @@ object MockEnv : Env { override val skrivestotteUrl: String = "" override val sfUrl: String = "" override val sfScope: DownstreamApi = DownstreamApi.parse("::") + override val kodeverkUrl: String = "" + override val kodeverkScope: DownstreamApi = DownstreamApi.parse("::") } From ed8fdbb96a374e93c860ff6203077cf8ef9db68c Mon Sep 17 00:00:00 2001 From: Nummedal Date: Fri, 12 Sep 2025 09:54:59 +0200 Subject: [PATCH 2/2] Legg til navn i pdl respons --- .../kotlin/no/nav/api/kodeverk/KodeverkService.kt | 2 +- src/main/kotlin/no/nav/api/pdl/PdlRoutes.kt | 3 ++- src/main/kotlin/no/nav/api/pdl/PdlService.kt | 12 ++++++++++++ .../resources/pdl/queries/hentPersonalia.graphql | 5 +++++ src/test/kotlin/no/nav/api/pdl/PdlTest.kt | 12 ++++++++++++ src/test/kotlin/no/nav/mock/MockConsumers.kt | 2 ++ 6 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/no/nav/api/kodeverk/KodeverkService.kt b/src/main/kotlin/no/nav/api/kodeverk/KodeverkService.kt index b5e115b..91eb33e 100644 --- a/src/main/kotlin/no/nav/api/kodeverk/KodeverkService.kt +++ b/src/main/kotlin/no/nav/api/kodeverk/KodeverkService.kt @@ -45,6 +45,7 @@ class KodeverkService( } } } + private fun hentKodeverk(navn: KodeverkNavn): Map = (kodeverkCache[navn] ?: emptyKodeverk) internal fun parseTilKodeverk(respons: GetKodeverkKoderBetydningerResponse): Map { @@ -58,7 +59,6 @@ class KodeverkService( return res } - fun hentKodeBeskrivelse( kodeverkNavn: KodeverkNavn, kodeRef: String, diff --git a/src/main/kotlin/no/nav/api/pdl/PdlRoutes.kt b/src/main/kotlin/no/nav/api/pdl/PdlRoutes.kt index e512256..562bffa 100644 --- a/src/main/kotlin/no/nav/api/pdl/PdlRoutes.kt +++ b/src/main/kotlin/no/nav/api/pdl/PdlRoutes.kt @@ -3,11 +3,11 @@ package no.nav.api.pdl import io.bkbn.kompendium.core.metadata.PostInfo import io.bkbn.kompendium.core.plugin.NotarizedRoute import io.ktor.http.* -import io.ktor.server.application.* import io.ktor.server.response.* import io.ktor.server.routing.* import kotlinx.serialization.Serializable import no.nav.api.CommonModels +import no.nav.api.generated.pdl.hentpersonalia.Navn import no.nav.models.FnrRequest import no.nav.models.deserializeFnr import no.nav.utils.getJWT @@ -77,6 +77,7 @@ data class AktorIdResponse( @Serializable data class PdlPersonalia( + val navn: Navn? = null, val alder: Int? = null, val bostedsAdresse: PdlAdresse? = null, val kontaktAdresse: PdlAdresse? = null, diff --git a/src/main/kotlin/no/nav/api/pdl/PdlService.kt b/src/main/kotlin/no/nav/api/pdl/PdlService.kt index 1211471..6c365ec 100644 --- a/src/main/kotlin/no/nav/api/pdl/PdlService.kt +++ b/src/main/kotlin/no/nav/api/pdl/PdlService.kt @@ -6,6 +6,7 @@ import no.nav.api.kodeverk.KodeverkNavn import no.nav.api.kodeverk.KodeverkService import no.nav.utils.TjenestekallLogger import no.nav.utils.now +import no.nav.api.generated.pdl.hentpersonalia.Navn as PdlNavn class PdlService( private val client: PdlClient, @@ -29,6 +30,7 @@ class PdlService( ): PdlPersonalia { val person = client.hentPersonalia(fnr, token).data?.hentPerson return PdlPersonalia( + navn = person?.let(::hentNavnPersonalia), alder = person?.let(::hentAlder), bostedsAdresse = person?.let(::hentBostedsAdresse), kontaktAdresse = person?.let(::hentKontaktAdresse), @@ -80,6 +82,16 @@ class PdlService( ) } + fun hentNavnPersonalia(person: Person?): PdlNavn { + val navn = + person?.navn?.firstOrNull() ?: PdlNavn("", "", "") + return PdlNavn( + fornavn = navn.fornavn, + mellomnavn = navn.mellomnavn, + etternavn = navn.etternavn, + ) + } + private fun hentAlder(person: Person?): Int? = person ?.foedselsdato diff --git a/src/main/resources/pdl/queries/hentPersonalia.graphql b/src/main/resources/pdl/queries/hentPersonalia.graphql index c353116..8837e6c 100644 --- a/src/main/resources/pdl/queries/hentPersonalia.graphql +++ b/src/main/resources/pdl/queries/hentPersonalia.graphql @@ -47,6 +47,11 @@ fragment postboksadresse on Postboksadresse { query ($ident: ID!) { hentPerson(ident: $ident) { + navn { + fornavn + mellomnavn + etternavn + } foedselsdato { foedselsdato } diff --git a/src/test/kotlin/no/nav/api/pdl/PdlTest.kt b/src/test/kotlin/no/nav/api/pdl/PdlTest.kt index 6033175..d34f10f 100644 --- a/src/test/kotlin/no/nav/api/pdl/PdlTest.kt +++ b/src/test/kotlin/no/nav/api/pdl/PdlTest.kt @@ -33,6 +33,9 @@ internal class PdlTest { { "data": { "hentPerson": { + "navn": [ + { "fornavn": "Ola", "mellomnavn": null, "etternavn": "Nordmann" } + ], "foedselsdato": [ { "foedselsdato": "2020-06-06" } ], @@ -66,6 +69,15 @@ internal class PdlTest { ?.get(0) ?.foedselsdato, ) + + assertEquals( + "Nordmann", + person.data + ?.hentPerson + ?.navn + ?.first() + ?.etternavn, + ) } @Test diff --git a/src/test/kotlin/no/nav/mock/MockConsumers.kt b/src/test/kotlin/no/nav/mock/MockConsumers.kt index 9b843b7..157d13c 100644 --- a/src/test/kotlin/no/nav/mock/MockConsumers.kt +++ b/src/test/kotlin/no/nav/mock/MockConsumers.kt @@ -42,6 +42,7 @@ import no.nav.common.types.identer.NavIdent import no.nav.utils.minus import no.nav.utils.now import java.util.* +import no.nav.api.generated.pdl.hentpersonalia.Navn as PdlNavn object MockConsumers : Consumers { override val tokenclient = tokenClientMock @@ -218,6 +219,7 @@ private val pdlClientMock = HentPersonalia.Result( hentPerson = Person( + navn = listOf(PdlNavn(fornavn = "Test", etternavn = "Testesen")), foedselsdato = listOf( Foedselsdato(